Remote Procedure Call (RPC) Plugin
The RPC plugin provides an easy way to implement a Remote Procedure Call API. The plugin uses JSON-RPC protocol version 2. You can read the specification here.
Install
RPC Plugin is dependent on Jimbo.
- Check the Jimbo plugin has been installed
- Add the plugin:
git submodule add git@gitlab.varteq.com:FestiPlugins/php_festi_plugin_rpc.git plugins/Rpc - Run dump from install folder
- Add area and rule
And add this rule toINSERT INTO festi_url_areas (ident) VALUES ('rpc'); INSERT INTO festi_url_rules (plugin, pattern, method) VALUES ('Rpc', '~^/rpc/(.*)$~', 'onJsonCallMethod');festi_url_rules2areasfor arearpc - Call RPC method
syncRpcMethods - Configure permission section
- Add column
access_tokento users table - Configure
index.phpand.htaccess. Examples can be found ininstall/rpc/ - Add composer package
festi-team/festi-framework-serialization
Authentication
Access token should be stored in the users table in the access_token column. You have two ways for authentication:
- Put token to GET param
token:https://RPC_HOST/?token=XXX - Put token in the
X-Authorizationheader:X-Authorization: XXX
Request
You can send parameters using an array or object:
{"jsonrpc":"2.0", "method":"getScraperActions", "params":["linkedin"], "id":1}
{"jsonrpc":"2.0", "method":"getScraperActions", "params":{"ident":"linkedin"}, "id":1}
If you use an object, please ensure that the key name matches the parameter name in the RPC method annotation.
The RPC plugin supports batch requests:
[
{"jsonrpc":"2.0","method":"getScraperActions","params":["linkedin"],"id":1},
{"jsonrpc":"2.0","method":"getScraperActions","params":{"ident":"google"},"id":2}
]
For easy debugging, you can use the GET parameter rawRequest:
https://RPC_HOST/?rawRequest={"jsonrpc":"2.0","method":"syncRpcMethods"}
RPC Method Implementation
Use @rpc annotation to define RPC method:
class ScraperPlugin extends DisplayPlugin
{
/**
* @rpc getScraperActions
* @param string $ident
* @section scraper
* @return array
*/
public function getActionsByIdent(string $ident): array
{
...
}
}
If @rpc doesn't have a name, the real method name will be used:
/**
* @rpc
* @param bool $idUser
* @return mixed
* @throws PermissionsException
*/
public function getMentors($idUser = false)
{
...
}
You can use on* methods and methods with DGS as well:
/**
* @rpc getProjects
* @urlRule ~^/projects/$~
* @area backend
* @param Response $response
* @return bool
* @throws SystemException
* @section manage_projects
*/
public function onDisplayList(Response &$response)
{
$store = $this->createStoreInstance("projects");
$store->onRequest($response);
return true;
}
Parameters for the
resultwill be retrieved from theResponseobject
Events
The RPC plugin dispatches two events during sync (when building the list of RPC methods from plugin annotations). Other plugins can listen to extend or customize method discovery and stored values.
BeforePluginAnnotationsParseEvent::TYPE
This event is triggered once per plugin, before PluginAnnotations::parse() is called for that plugin. It can be used to add extra annotation names so they are parsed from docblocks and appear in $methodInfo (e.g. mcpReadOnly, mcpDestructive for MCP tools).
Event data:
* getPluginContext() – plugin being scanned
* getPluginAnnotations() – the PluginAnnotations instance
* getAnnotationNames() / setAnnotationNames(array) – list of annotation names to parse (listeners can add more)
init.php:
$this->core->addEventListener(
BeforePluginAnnotationsParseEvent::TYPE,
function (BeforePluginAnnotationsParseEvent $event): void {
$names = $event->getAnnotationNames();
$names[] = 'mcpReadOnly';
$names[] = 'mcpDestructive';
$event->setAnnotationNames($names);
}
);
PrepareRpcMethodValuesEvent::TYPE
This event is triggered once per RPC method, after the method item (plugin, method, rpc_method, params, description, etc.) is built and before it is written to the rpc_methods table. It can be used to add or change keys in the method values (e.g. is_read_only) so they are stored and available later.
Event data:
* getValues() / setValues(array) – the method row to store (mutate and set back)
* getPluginContext() – plugin
* getMethodName() – method name
* getMethodInfo() – parsed annotations for this method (including any added via BeforePluginAnnotationsParseEvent)
init.php:
$this->core->addEventListener(
PrepareRpcMethodValuesEvent::TYPE,
function (PrepareRpcMethodValuesEvent $event): void {
$values = $event->getValues();
$methodInfo = $event->getMethodInfo();
$values['is_read_only'] = array_key_exists('mcpDestructive', $methodInfo) ? 0 : 1;
$event->setValues($values);
}
);
Update RPC Methods
All RPC methods are stored in the rpc_methods table. To update them, you can call the RPC method syncRpcMethods or call the method from code:
Core::getInstance()->getPluginInstance('Rpc')->syncRpcMethods();
or
https://RPC_HOST/?rawRequest={"jsonrpc":"2.0","method":"syncRpcMethods"}
Client
- jQuery - https://github.com/Textalk/jquery.jsonrpcclient.js
let rpc = new jQuery.JsonRpcClient({ ajaxUrl: 'https://RPC_HOST/' });
rpc.call(
'getScraperActions', ['linkedin'],
function (response) {
console.log("RESULT:", response);
},
function (error) {
console.error(error);
}
);
rpc.batch(
function (batch) {
batch.call('getScraperActions', ['linkedin'], function (response) {
console.log("1", response);
}, function (error) {
console.error(error);
});
batch.call('getScraperActions', { "ident": "linkedin" }, function (response) {
console.log("2", response);
}, function (error) {
console.error(error);
});
},
function (all_result_array) { alert('All done.'); },
function (error_data) { alert('Error in batch response.'); }
);
Override Authorization Logic
init.php:
assert($this instanceof Core);
$this->addEventListener(Core::EVENT_ON_AFTER_INIT, function () {
$this->addEventListener(IRpc::EVENT_ON_TOKEN_LOGIN, function (FestiEvent &$event) {
Core::getInstance()->getPluginInstance('YourNewPlugin')->onInterceptLoginByToken($event);
});
});
public function onInterceptLoginByToken(FestiEvent &$event): void
{
$isAuth = &$event->getTargetValueByKey('is_auth');
$token = &$event->getTargetValueByKey('token');
if (mb_strlen($token) == 32) { // system access token
$isAuth = $this->core->getSystemPlugin()->signinByToken($token);
} else {
$isAuth = $this->_signInByGoogleTokenID($token);
}
if (!$isAuth) {
throw new PermissionsException("Undefined access token.");
}
}
Reusable API
The RPC plugin provides reusable type handling utilities that can be used by other plugins:
ParameterProcessor
The ParameterProcessor class provides public methods for type checking and casting:
isSimpleType(string $typeName): bool- Checks if a type is a simple/primitive typecastToType(mixed $value, string $typeName): mixed- Casts a value to the specified type
These methods are used internally by RPC for parameter processing and are also available for use by dependent plugins (e.g., MCPServer) to maintain consistency in type handling across the codebase.
Error Handling
The RPC plugin follows the JSON-RPC 2.0 specification for error responses. Errors are returned in the following format:
{
"jsonrpc": "2.0",
"error": {
"code": -32000,
"message": "Server error",
"data": {}
},
"id": 1
}
Common error codes:
- -32700 - Parse error
- -32600 - Invalid Request
- -32601 - Method not found
- -32602 - Invalid params
- -32603 - Internal error
- -32000 to -32099 - Server error (custom errors)
Permissions
RPC methods can be protected using the @section annotation. The section name should match a permission section defined in your permissions system. If a user doesn't have access to the required section, a PermissionsException will be thrown.
For detailed information about the permissions system, see the Permissions documentation.
How Permission Checking Works
The permission system uses the following database tables:
festi_sections- Defines permission sections (identified by the@sectionannotation)festi_section_actions- Maps plugin methods to sections and required permission masksfesti_sections_user_permission- User-specific permissions (overrides user type permissions)festi_sections_user_types_permission- User type/role-based permissions
When an RPC method is called:
- The system looks up the method in
festi_section_actionsto find: - The associated section (
id_section) -
The required permission mask (2 = Read, 4 = Write, 6 = Execute)
-
The system then checks the user's permissions by querying:
festi_sections_user_permissionfor user-specific permissions (takes priority)-
festi_sections_user_types_permissionfor role-based permissions -
The user's granted mask must be greater than or equal to the required mask for the action.
Example
/**
* @rpc getProjects
* @section manage_projects
* @return array
*/
public function getProjects(): array
{
// Only users with 'manage_projects' permission can call this
}
To set up permissions for this method, you need to:
-
Create the section in
festi_sections(if it doesn't exist):INSERT INTO festi_sections (caption, ident, mask) VALUES ('Project Management', 'manage_projects', '6'); -
Register the action in
festi_section_actions:INSERT INTO festi_section_actions (id_section, plugin, method, mask, comment) VALUES ( (SELECT id FROM festi_sections WHERE ident = 'manage_projects'), 'MyPlugin', 'getProjects', '2', -- Read permission required 'Get projects list' ); -
Assign permissions to users or user types as needed.
Built-in RPC Methods
getDataGridStoreModel
Returns the DataGrid Store (DGS) model schema for a given plugin and store.
Parameters:
- pluginName (string) or [0] - Name of the plugin
- storeName (string) or [1] - Name of the store
Example:
{"jsonrpc":"2.0", "method":"getDataGridStoreModel", "params":["MyPlugin", "users"], "id":1}
Based on: RpcPlugin::onJsonDataGridStore
syncRpcMethods
Scans the project and updates the RPC methods registry in the database. This method should be called after adding or modifying RPC methods.
Parameters: None
Example:
{"jsonrpc":"2.0", "method":"syncRpcMethods", "params":[], "id":1}
Based on: RpcPlugin::syncRpcMethods
getStructureMenu
Returns the menu structure for a given area.
Parameters:
- area (string) or [0] - Area identifier
Example:
{"jsonrpc":"2.0", "method":"getStructureMenu", "params":["backend"], "id":1}
Based on: Jimbo::getStructureMenu
getSchemeByUrl
Returns all schemes (URL rules and handlers) for a given URL and area.
Parameters:
- url (string) or [0] - URL to analyze
- area (string|null) or [1] - Optional area identifier
Example:
{"jsonrpc":"2.0", "method":"getSchemeByUrl", "params":["/projects/", "backend"], "id":1}
Based on: RpcPlugin::getSchemeByUrl
execUrl
Executes a request for a given URL and area, returning the response.
Parameters:
- url (string) or [0] - URL to execute
- area (string|null) or [1] - Optional area identifier
Example:
{"jsonrpc":"2.0", "method":"execUrl", "params":["/projects/list", "backend"], "id":1}
Based on: RpcPlugin::execUrl