WebService Extension
1. Purpose and Overview
This extension provides a webservice function to retrieve all related records (relations) for one or more CRM records in vTiger / berliCRM.
Key characteristics:
- Supports multiple parent records in one call.
- Can optionally restrict results to a specific related module (e.g. only Contacts, only Documents).
- Respects user permissions (profiles, sharing, field permissions):
- Only returns records that are accessible for the webservice user.
- Uses the same relation definitions as the normal vTiger UI, based on vtiger_relatedlists.
Typical use cases:
- Client portals or external integrations that need:
- All related Contacts, Documents, Trouble Tickets, etc. for a given record.
- Relations for multiple records in a single request, reducing round-trips.
- Batch synchronization of relations into external systems.
2. Function Signature
function berli_get_new_multi_relations($ids, $relModule = 'all', $user)
Parameters
- $ids (string, JSON-encoded)
- JSON string representing either:
- a single webservice ID (“11×123”), or
- an array of webservice IDs ([“11×123″,”11×456”]).
- Each ID must be a webservice ID, not a plain CRM ID:
- Format: <entityIdPrefix>x<crmId>Example: 11×123 where 11 is the entity ID and 123 is the CRM record ID.
Internally:
- JSON string representing either:
$ids = json_decode($ids);
if (!is_array($ids)) {
$ids = array($ids);
}
- So the function always works with an array of IDs.
- $relModule (string, optional)
- Default: ‘all’
- Controls which related modules to include:
- ‘all’Return relations for all related modules defined in vtiger_relatedlists for the entity.
- <ModuleName> (e.g. ‘Contacts’, ‘Documents’)Return only relations where the related module matches this module.
If the parameter is empty or omitted, it is normalized to ‘all’.
- $user (User object)
- The current Vtiger/berliCRM webservice user object.
- Used to:
- Initialize webservice handlers.
- Check access rights (hasReadAccess, hasPermission).
- Filter out entities the user is not allowed to access.
3. Return Value
array(
"<parentWsId>" => array(
"<RelatedModuleName>" => array(
"<RelationLabel>" => array(
"<relatedWsId1>",
"<relatedWsId2>",
...
),
...
),
...
),
...
)
More explicitly:
- Top level key: parent record webservice ID (e.g. “11×123”).
- Second level key: related module name (e.g. Contacts, Documents).
- Third level key: relation label as defined in vtiger_relatedlists.label (decoded from HTML entities).
- Example: “Contacts”, “Documents”, “Trouble Tickets”, or a custom label.
- Value: an array of related record webservice IDs belonging to that relation.
Example (simplified):
array(
"11x123" => array(
"Contacts" => array(
"Contacts" => array("12x5", "12x7")
),
"Documents" => array(
"Documents" => array("14x20", "14x21")
)
),
"11x456" => array(
"Contacts" => array(
"Contacts" => array("12x8")
)
)
);
Notes:
- Each related record is returned only once per parent+module+relation label (duplicates are filtered).
- If a parent record has no accessible relations for a module/label, that section may be present but empty.
4. Permission and Security Handling
The function strictly respects vTiger/berliCRM’s permission system:
- Entity type accessFor each parent ID:
$webserviceObject = VtigerWebserviceObject::fromId($db, $id);
$handler = new $handlerClass($webserviceObject, $user, $db, $log);
$meta = $handler->getMeta();
$entityName = $meta->getObjectEntityName($id);
$types = vtws_listtypes(null, $user);
if (!in_array($entityName, $types['types'])) {
throw new WebServiceException(
WebServiceErrorCode::$ACCESSDENIED,
"Permission to perform the operation is denied"
);
}
- If the user has no access to the module of the parent record, a WebServiceException with ACCESSDENIED is thrown.
- Record-level access
- Checks if the record exists and is readable:
if ($meta->hasReadAccess() === false) { ... }
if (!$meta->hasPermission(EntityMeta::$RETRIEVE, $id)) { ... }
if (!$meta->exists($idComponents[1])) { ... }
-
- If these checks fail, the function throws:
- ACCESSDENIED when the user cannot read the record.
- RECORDNOTFOUND when the CRM ID does not exist.
- If these checks fail, the function throws:
- Related module accessFor each related module:
$tmpWebserviceObject = VtigerWebserviceObject::fromName($db, $relatedModuleName);
$tmpHandler = new $handlerClass($tmpWebserviceObject, $user, $db, $log);
$tmpMeta = $tmpHandler->getMeta();
if ($tmpMeta->hasReadAccess() === true) {
// execute relation query
}
- This ensures:
- Relations are only fetched for related modules the user can access.
- Each related record is checked with hasPermission(EntityMeta::$RETRIEVE, $relCrmId) before being added to the result.
- DeduplicationFor each relation query, the function tracks already added IDs:
$alreadySet = array();
while ($row2 = $db->fetch_row($res2)) {
$relCrmId = $prefix.'x'.$row2['crmid'];
if ($tmpMeta->hasPermission(EntityMeta::$RETRIEVE, $relCrmId) && !isset($alreadySet[$relCrmId])) {
$arrRet[$id][$relatedModuleName][$label][] = $relCrmId;
$alreadySet[$relCrmId] = $relCrmId;
}
}
- This prevents duplicate related IDs in the response.
5. Relation Discovery Logic
For each parent record:
- Set current module
global $currentModule;
$currentModule = $entityName;
- This is necessary for some internal vTiger APIs that depend on $currentModule.
- Fetch relation definitionsThe function reads the relation definitions from vtiger_relatedlists combined with vtiger_tab:
$query = "SELECT * FROM vtiger_relatedlists
INNER JOIN vtiger_tab ON vtiger_tab.tabid = vtiger_relatedlists.related_tabid
WHERE vtiger_relatedlists.tabid = ? AND vtiger_tab.presence IN (0,2)";
$params[] = getTabid($entityName);
if ($relModule != 'all') {
$query .= " AND related_tabid = ?";
$params[] = getTabid($relModule);
}
$res = $db->pquery($query, array($params));
-
- tabid = module of the parent record.
- related_tabid optionally limited to the module specified by $relModule.
- presence IN (0,2) ensures only active or selectively visible modules are considered.
- Generate actual relation queriesFor each relation row:
$label = html_entity_decode($row['label']);
$relatedTabid = $row['related_tabid'];
$relatedModuleName = Vtiger_Functions::getModuleName($relatedTabid);
$parentRecordModel = Vtiger_Record_Model::getInstanceById($idComponents[1], $entityName);
$relationListView = Vtiger_RelationListView_Model::getInstance(
$parentRecordModel,
$relatedModuleName,
$label
);
$query = trim($relationListView->getRelationQuery());
-
- Vtiger_RelationListView_Model is used to build the same SQL query as used by the UI relation tabs.
- The generated query is executed if not empty.
- Execute relation queries
$res2 = $db->pquery($query, array());
- Each result row is expected to contain at least crmid of the related record.
6. Error Handling
The function can throw WebServiceException in the following situations:
- Invalid module access for parentWebServiceErrorCode::$ACCESSDENIEDWhen the user has no access to the module of the parent record.
- No read access or missing record
- ACCESSDENIED if user cannot read the specific record.
- RECORDNOTFOUND if the CRM ID does not exist.
- SQL errors
- On the relation definition query:
if (!$res) {
throw new WebServiceException(
WebServiceErrorCode::$QUERYSYNTAX,
"Error with query: $query, parameter:".serialize($params)
);
}
-
-
- On the relation records query:
-
if (!$res2) {
throw new WebServiceException(
WebServiceErrorCode::$QUERYSYNTAX,
"Error with query2: $query"
);
}
Client code consuming this webservice should be prepared to handle these exceptions.
7. Example Usage (Conceptual)
Webservice Call
Assuming the operation name is mapped to this function (for example getNewMultiRelations), a typical REST call could look like:
- GET or POST parameters:
operation=get_new_multi_relations
sessionName=<validSession>
ids=["11x123","11x456"]
relModule=Contacts
- Response (simplified JSON):
{
"success": true,
"result": {
"11x123": {
"Contacts": {
"Contacts": ["12x5", "12x7"]
}
},
"11x456": {
"Contacts": {
"Contacts": ["12x8"]
}
}
}
}
Note: The actual operation name and registration must match how you configured this function in vtiger_ws_operation and vtiger_ws_operation_parameters.
8. Performance Considerations
- The function loops over all passed IDs and, for each:
- Reads relation definitions from vtiger_relatedlists.
- Executes one relation query per relation definition.
- set_time_limit(0); is called in each outer loop iteration to avoid script timeouts for large data sets.
Recommendations:
- Avoid passing extremely large ID lists in one call.
- If you expect many relations per record, consider batching calls (e.g. 50–100 IDs per request).
9. Integration Notes
- Place getNewMultiRelations.php in a location accessible by your vTiger/berliCRM webservice environment.
- Register the function via the standard vtiger webservice configuration:
- Add an entry in vtiger_ws_operation.
- Add parameters (ids, relModule) in vtiger_ws_operation_parameters.
- Map the operation to the PHP function berli_get_new_multi_relations.
(Exact SQL or API calls for registration depend on your existing customization style.)