berli_get_new_multi_relations

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

  1. $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:

$ids = json_decode($ids);
if (!is_array($ids)) {
    $ids = array($ids);
}
  1. So the function always works with an array of IDs.
  2. $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’.

  3. $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:

  1. 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"
    );
}
  1. If the user has no access to the module of the parent record, a WebServiceException with ACCESSDENIED is thrown.
  2. 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.
  1. 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
}
  1. 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.
  2. 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;
    }
}
  1. This prevents duplicate related IDs in the response.

5. Relation Discovery Logic

For each parent record:

  1. Set current module
global $currentModule;
$currentModule = $entityName;
  1. This is necessary for some internal vTiger APIs that depend on $currentModule.
  2. 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.
  1. 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.
  1. Execute relation queries
$res2 = $db->pquery($query, array());
  1. 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.)