vtenext/modules/Touch/vtws2/TouchGetChanges.php
2021-04-28 20:10:26 +02:00

1144 lines
38 KiB
PHP

<?php
/*************************************
* SPDX-FileCopyrightText: 2009-2020 Vtenext S.r.l. <info@vtenext.com>
* SPDX-License-Identifier: AGPL-3.0-only
************************************/
require_once('include/Webservices/GetUpdates.php');
require_once('include/utils/SessionValidator.php'); // crmv@91082
require_once('modules/Users/LoginHistory.php'); // crmv@91082
require_once('include/utils/EmailDirectory.php'); // crmv@107655
class TouchGetChanges extends TouchWSClass {
public $listLimit = 50; // return max 50 records per module
public $zeroDate = '1970-01-02 00:00:00';
public $reloadDate = null; // date in which a complete meta reload is needed
protected $rawRequest = null;
protected $relatedCache = array();
/*
* Get the changes that should be propagated to the app
*/
public function process(&$request) {
global $adb, $table_prefix, $current_user;
global $touchInst, $touchUtils;
$this->rawRequest = $request;
$modules = Zend_Json::decode($request['modules']); // if this is a valid array, these modules will be used instead, if it's empty, all the available mods will be used
$deviceTime = $request['devicetime'];
$limit = intval($request['limit']);
if ($limit > 0) $this->listLimit = $limit;
// crmv@173184
$since = intval($request['since']);
if ($request['since_multiple']) {
$sinceAll = Zend_Json::decode($request['since_multiple']);
if (is_array($sinceAll)) {
foreach ($sinceAll as &$ts) {
$ts = $this->ts2date($ts);
}
} else {
$sinceAll = $this->ts2date($since);
}
} else {
$sinceAll = $this->ts2date($since);
}
// crmv@173184e
$metaSince = intval($request['meta_since']);
$metaSinceDate = ($metaSince > 0 ? date('Y-m-d H:i:s', $metaSince) : $this->zeroDate);
$skip_timecheck = ($request['skip_time_check'] == 1);
$skip_related = ($request['skip_record_related'] == 1);
$skip_content = ($request['skip_record_content'] == 1);
$skip_filter = ($request['skip_filter_check'] == 1);
$skip_folder = ($request['skip_folder_check'] == 1);
$skip_recents = ($request['skip_recents'] == 1);
$skip_emaildir = ($request['skip_emaildir'] == 1);
// TODO: do this comparison with a token and not with a date or pass the server time and the client resend that
// It's already like this for the modules, but should be extended and this check removed
// first compare the server and the device time, if the difference is more than 60 seconds... too much!
$localtime = time();
$diff = abs($localtime - $deviceTime);
if ($diff > 60 && !$skip_timecheck) {
return $this->error('Time difference is too big ('.$diff.'s), you must synchronize the server and the client first.');
}
// Check the session
// crmv@91082
$SV = SessionValidator::getInstance();
if ($SV->isStarted()) {
if ($SV->isValid()) {
$SV->refresh();
} else {
$loghistory = LoginHistory::getInstance();
$loghistory->user_logout($current_user->user_name, null, null, 'auto', true);
$touchInst->destroyWSSession();
$touchInst->outputFailure('Session expired', array('logout_reason' => 'concurrent'));
exit;
}
} else {
$SV->refresh();
}
// crmv@91082e
// crmv@161368
// get the wipedate if the variable here is not set
$wipeDate = $touchInst->getWipeDate($current_user->id);
if (!empty($wipeDate) && $metaSinceDate != $this->zeroDate && ($wipeDate >= $metaSinceDate)) {
return $this->success(array(
'meta_changed' => true,
'require_reload' => true,
'require_wipe' => true,
'meta'=> $metaSinceDate,
'login_data' => $touchInst->retrieveLoginData($request['deviceid'])
));
}
// crmv@161368e
// check reload date
if ($this->reloadDate && ($this->reloadDate >= $metaSinceDate)) {
return $this->success(array('meta_changed' => true, 'require_reload' => true, 'meta'=> $metaSinceDate));
}
$requireReload = false;
// crmv@93148 - close the session, to allow concurrent requests
$touchInst->closeWSSession();
// meta changes
$meta = $this->getMetaChanges($metaSinceDate);
if ($meta && $meta['total'] > 0) $requireReload = true;
// get modified/deleted records and filters
$changes = $this->getChangedRecords($modules, $sinceAll, !$skip_content, !$skip_filter, !$skip_folder, !$skip_related); // crmv@173184
// users
$users = $this->getChangedUsers($metaSinceDate);
// groups
$groups = $this->getChangedGroups($metaSinceDate);
// recents
if ($skip_recents) {
$recents = array();
} else {
$recents = $this->getRecents();
}
// crmv@107655
// email directory
if ($skip_emaildir) {
$emailDir = array();
} else {
$emailDir = $this->getEmailDirectory($metaSinceDate);
}
// crmv@107655e
$metaChanged = $requireReload || ($users['total'] > 0) || ($groups['total'] > 0);
$output = array(
'records'=>$changes,
'users' => $users,
'groups' => $groups,
'recents'=>$recents,
'email_directory' => $emailDir, // crmv@107655
'meta_changed' => $metaChanged,
'meta'=>$meta,
'require_reload' => $requireReload
);
if ($touchInst->wsExists('GetOfflineData')) {
$req = array();
$offlineInfo = $this->subcall('GetOfflineData', $req);
if ($offlineInfo && $offlineInfo['success']) {
$output['offline_info'] = $offlineInfo;
}
}
$output['login_data'] = $touchInst->retrieveLoginData($request['deviceid']);
return $this->success($output);
}
// crmv@173184
protected function ts2date($ts) {
if ($ts > 0) {
$date = max(date('Y-m-d H:i:s', $ts), $this->zeroDate);
} else {
$date = $this->zeroDate;
}
return $date;
}
// crmv@173184e
function getChangedRecords($modules = null, $sinceDates = null, $content = false, $filters = false, $folders = false, $related = false) { // crmv@173184
global $adb, $table_prefix;
global $touchInst, $touchUtils, $current_user;
// max number of records to return for each module
// this amount is not strictly observed, since if there are many records with the same timestamp,
// they will be all returned since there is no way to partition them with several requests
$limitRecords = $this->listLimit;
// get the list of modules
$modulesList = array();
if (!empty($modules)) {
if (!is_array($modules)) $modules = array($modules);
$modulesList = $modules;
} else {
$entityModules = array();
$modulesDetails = vtws_listtypes(null,$current_user);
$modulesInformation = $modulesDetails["information"];
foreach ($modulesInformation as $moduleName=>$entityInformation){
if($entityInformation["isEntity"]) $modulesList[] = $moduleName;
}
}
// crmv@173184
// find minimum date
if (is_array($sinceDates)) {
$minDate = min($sinceDates);
} else {
$minDate = $sinceDates;
}
if (empty($minDate)) $minDate = $this->zeroDate;
// crmv@173184e
// Users are done separately
$modulesList = array_diff($modulesList,$touchInst->excluded_modules, array('Users'));
if (empty($modulesList)) return array('changes'=>array(), 'total'=>0);
// do a very quick check to see if globally there are changes (there should be an index, otherwise it's pointless)
// crmv@177095
$res = $adb->query(
"SELECT MAX(maxtime) AS maxmod FROM (
SELECT MAX(modifiedtime) AS maxtime FROM {$table_prefix}_crmentity
UNION ALL
SELECT MAX(modifiedtime) AS maxtime FROM {$table_prefix}_messages
) tt"
);
// crmv@177095e
if ($res && $adb->num_rows($res) > 0) {
$maxmod = $adb->query_result_no_html($res, 0, 'maxmod');
// crmv@164122
// check also timestamp of notifications
if (in_array('ModNotifications', $modulesList)) {
$res = $adb->query("SELECT MAX(modifiedtime) AS maxmod FROM {$table_prefix}_modnotifications");
if ($res && $adb->num_rows($res) > 0) {
$maxmod = max($maxmod, $adb->query_result_no_html($res, 0, 'maxmod'));
}
}
// crmv@164122e
if ($maxmod && $maxmod < $minDate) { // crmv@173184
// last global mod time is older than sincetime, therefore no nchanges will be found for any module
return array('changes'=>array(), 'total'=>0, 'last_timestamp' => time());
}
}
$CustomView = CRMEntity::getInstance('CustomView'); // crmv@115329
$changes = array();
$globalLastTs = null; // crmv@173184
$total = 0;
// now check for changes
foreach ($modulesList as $elementType) {
// crmv@173184
if (is_array($sinceDates)) {
$sinceDate = $sinceDates[$elementType];
} else {
$sinceDate = $sinceDates;
}
if (empty($sinceDate)) $sinceDate = $this->zeroDate;
// crmv@173184e
// crmv@177095 - speed up for messages the first time
if ($sinceDate == $this->zeroDate && $elementType == 'Messages') {
$oldRelated = $related;
$related = false;
} elseif (isset($oldRelated)) {
$related = $oldRelated;
}
// crmv@177095e
$moduleInstance = $touchUtils->getModuleInstance($elementType);
$queries = $this->getChangeQuery($elementType, $sinceDate);
$query = $queries['main'];
$params = $queries['params'];
$countQuery = $queries['count'];
$idColumn = $queries['idcolumn'];
$res = $adb->limitPQuery($query, 0, $limitRecords+1, $params);
// crmv@164122 - removed line
if ($res && $adb->num_rows($res) > 0) {
$modCounter = 0;
$lastTs = 0;
$secondStep = false;
$relateds_ids = array();
while ($row = $adb->fetchByAssoc($res, -1, false)) {
$crmid = $row[$idColumn];
$entry = array('crmid'=>$crmid);
$entry['timestamp'] = strtotime($row['modifiedtime']);
$isDeleted = ($row['is_deleted'] == '1');
if (!$this->rawRequest['latest'] && !$secondStep && $modCounter == $limitRecords) {
// last+1, check the timestamp
if ($lastTs != $entry['timestamp']) break; // ok, different, so end
// re-run the query and find the first != timestamp
// suppose no more than 1000 records with same modifiedtime are present
$query2 = preg_replace('/\) sync_table/i', ' AND modifiedtime < ?) sync_table', $query);
$params[] = date('Y-m-d H:i:s', $lastTs+1);
// overwrite old res
$res = $adb->limitPQuery($query2, $limitRecords, 1000, $params);
$secondStep = true;
continue;
}
// fetch the content of the record
if ($content && !$isDeleted) {
$req = array(
'module' => $elementType,
'record' => $crmid,
'set_recent' => 0,
'set_seen' => 0,
);
$record = $this->subcall('GetRecord', $req);
if (!empty($record['crmid']) || !empty($record['record_id'])) {
// this is for modComments (they can return a different crmid)
$entry['crmid'] = ($record['crmid'] ? $record['crmid'] : $record['record_id']);
}
$entry['content'] = $record;
}
if ($filters && !$isDeleted) {
$CustomView = CRMEntity::getInstance('CustomView'); // crmv@115329
$filterList = $CustomView->getRecordViews($elementType, $crmid, true);
$entry['filters'] = $filterList;
}
if ($folders && !$isDeleted && $moduleInstance->hasFolders()) {
if ($entry['content']) {
$folderid = $entry['content']['folderid'];
} else {
$folderid = getSingleFieldValue($moduleInstance->table_name, 'folderid', $moduleInstance->table_index, $crmid);
}
$entry['folders'] = array(intval($folderid));
}
if ($related && !$isDeleted) {
// changed into multi-id retrieval
//$entry['related'] = $this->getAllRelated($elementType, $crmid);
$relateds_ids['related_list'][] = $crmid;
$relateds_ids['related_list_idx'][$crmid] = count($changes[$elementType]['updated']);
// Changed into a multi-id retrieval
//$entry['related_n1'] = $this->getN1Related($elementType, $crmid);
$relateds_ids['related_n1_list'][] = $crmid;
$relateds_ids['related_n1_list_idx'][$crmid] = count($changes[$elementType]['updated']);
}
$changes[$elementType][$isDeleted ? 'deleted' : 'updated'][] = $entry;
if ($this->rawRequest['latest']) {
if (!$lastTs) $lastTs = $entry['timestamp'];
} else {
$lastTs = $entry['timestamp'];
}
++$modCounter;
++$total;
}
// retrieve multiple related ids all at once
if (!empty($relateds_ids['related_list'])) {
$list = $this->getAllRelated($elementType, $relateds_ids['related_list']);
foreach ($list as $lid => $lists) {
$index = $relateds_ids['related_list_idx'][$lid];
if (is_int($index) && $index >= 0) {
$changes[$elementType]['updated'][$index]['related'] = $lists;
}
}
}
// retrieve multiple related ids all at once
if (!empty($relateds_ids['related_n1_list'])) {
$list = $this->getN1Related($elementType, $relateds_ids['related_n1_list']);
foreach ($list as $lid => $lists) {
$index = $relateds_ids['related_n1_list_idx'][$lid];
if (is_int($index) && $index >= 0) {
$changes[$elementType]['updated'][$index]['related_n1'] = $lists;
}
}
}
}
if (!empty($changes[$elementType])) {
$changes[$elementType]['last_timestamp'] = $lastTs;
$globalLastTs = max($globalLastTs, $lastTs); // crmv@173184
}
}
return array('changes'=>$changes, 'total'=>$total, 'last_timestamp' => $globalLastTs ?: time()); // crmv@173184
}
// crmv@164122
protected function getChangeQuery($elementType, $sinceDate) {
global $adb, $table_prefix, $current_user;
global $touchUtils;
$eventsBadType = array('Task', 'Emails', 'Webmails');
if ($elementType == 'ModNotifications') {
$idColumn = 'modnotificationsid';
$query = "SELECT 0 as is_deleted, modifiedtime, $idColumn FROM {$table_prefix}_modnotifications WHERE smownerid = '{$current_user->id}'";
if (!$this->rawRequest['latest']) {
$query .= " AND modifiedtime >= ?";
$params = array($sinceDate);
$query .= " ORDER BY modifiedtime ASC, $idColumn ASC";
} else {
$query .= " ORDER BY modifiedtime ASC";
}
$countQuery = preg_replace('/select.*?from/i', "SELECT COUNT($idColumn) AS count FROM", $query);
} else {
$handler = vtws_getModuleHandlerFromName($elementType, $current_user);
$moduleMeta = $handler->getMeta();
$deletedQueryCondition = $moduleMeta->getEntityDeletedQuery();
$moduleInstance = $touchUtils->getModuleInstance($elementType);
if ($elementType == 'Messages') {
global $current_account, $current_folder;
//config account
$mail_accountid = vtlib_purify($this->rawRequest['mail_accountid']);
if ($mail_accountid) $current_account = $mail_accountid;
if (empty($current_account)) {
$current_account = $moduleInstance->getMainUserAccount();
$current_account = $current_account['id'];
}
$moduleInstance->setAccount($current_account);
if ($current_account != 'all')
$specialFolders = $moduleInstance->getSpecialFolders(false); // crmv@170276
// config folder
$mail_folder = $this->rawRequest['mail_folder'];
if ($mail_folder) $current_folder = $mail_folder;
}
$queryGenerator = QueryGenerator::getInstance($elementType, $current_user);
$queryGenerator->setFields(array('id'));
// create the extra join
$baseTable = "{$table_prefix}_crmentity";
$baseId = 'crmid';
// crmv@173184
$modField = $queryGenerator->getWSField('modifiedtime', false);
$modTable = $modField->getTableName() ?: $table_prefix.'_crmentity';
// crmv@173184e
// condition on the date
$setype = $elementType;
if ($elementType == 'Events') {
$setype = 'Calendar';
}
if ($elementType == 'Users') {
$dateSql = "INNER JOIN (SELECT id AS crmid FROM {$table_prefix}_users WHERE date_entered >= ?) sync_table ON sync_table.crmid = {$table_prefix}_users.id";
$params = array($sinceDate);
} elseif (in_array("{$table_prefix}_crmentity", $moduleInstance->tab_name)) { // crmv@173184 crmv@200661
// TODO: fix per modcomments, prendere solo il padre
$dateSql = "INNER JOIN (SELECT crmid FROM $baseTable MARK1 WHERE MARK2 setype = ? AND modifiedtime >= ?) sync_table ON sync_table.crmid = $baseTable.crmid";
$params = array($setype, $sinceDate);
}
if (!$this->rawRequest['latest']) {
$queryGenerator->appendToFromClause($dateSql);
}
if ($elementType == 'Calendar') {
$queryGenerator->appendToWhereClause(" AND {$table_prefix}_activity.activitytype = 'Task'");
} elseif ($elementType == 'Events') {
if (count($eventsBadType) > 0) {
foreach ($eventsBadType as $caltype) $blist[] = "'$caltype'";
$queryGenerator->appendToWhereClause(" AND {$table_prefix}_activity.activitytype NOT IN (".implode(',',$blist).")");
}
// TODO: tirare su anche gli eventi a cui sono invitato?
} elseif ($elementType == 'ModComments') {
// not needed
// retrieve only parent comments
//$queryGenerator->appendToWhereClause(" AND {$table_prefix}_modcomments.parent_comments = 0");
} elseif ($elementType == 'Messages') {
// Messages in the Shared folder are retrieved when getting the ModComments
// For all the other folders, messages are private, so I just take mines
// crmv@173184
$ownerField = $queryGenerator->getWSField('assigned_user_id', false);
$ownerTable = $ownerField->getTableName();
$queryGenerator->appendToWhereClause(" AND $ownerTable.smownerid = ".$current_user->id);
$queryGenerator->appendToWhereClause(" AND $modTable.modifiedtime >= ?");
$params[] = $sinceDate;
// crmv@173184e
}
// generate query
$queryGenerator->setSkipDeletedQuery(true);
$query = $queryGenerator->getQuery();
// crmv@95586
if ($elementType == 'Messages') {
// fix forced index which is not needed here and slows things down
$query = preg_replace('/force index\s+\([a-z0-9_ ]+\)/i', '', $query);
}
// crmv@95586e
// crmv@173184
// fix where because some ugly things are added
$query = preg_replace('/MARK1.+MARK2/', 'WHERE', $query);
$query = preg_replace('/^select /i', 'select CASE WHEN ('.$deletedQueryCondition.") THEN 0 ELSE 1 END AS is_deleted, $modTable.modifiedtime, ", $query);
$countQuery = preg_replace('/select.*?from/', 'SELECT COUNT(crmid) AS count FROM', $query);
$idColumn = $moduleMeta->getIdColumn();
$baseTable = $moduleMeta->getEntityBaseTable();
if ($this->rawRequest['latest']) {
if ($elementType = 'Messages') {
$query .= " ORDER BY {$table_prefix}_messages.mdate DESC";
} else {
$query .= " ORDER BY $modTable.modifiedtime DESC";
}
} else {
if ($baseTable == $table_prefix.'_crmentity') {
$query .= " ORDER BY $modTable.modifiedtime ASC, {$table_prefix}_crmentity.crmid ASC";
} else {
$query .= " ORDER BY $modTable.modifiedtime ASC, $baseTable.$idColumn ASC";
}
}
// crmv@173184e
}
$queries = array('main' => $query, 'params' => $params, 'count' => $countQuery, 'idcolumn' => $idColumn);
return $queries;
}
// crmv@164122e
// TODO: add special related also (invitees....)
// $crmid can be an array. in this case, optimized subcalls will be used
// in this case the result will be in the format array(crmid1 => [lists], crmid2 => [lists]... )
public function getAllRelated($module, $crmid) { // crmv@177095
global $touchInst, $touchUtils;
global $adb, $table_prefix;
if (empty($this->relatedCache[$module])) {
$req = array('module' => $module, 'type'=>'RELATED');
$blocks = $this->subcall('GetBlocks', $req);
if (isInventoryModule($module)) {
$req = array('module' => $module, 'type'=>'PRODUCTS');
$blocks_prod = $this->subcall('GetBlocks', $req);
$blocks['blocks'] = array_merge($blocks['blocks'], $blocks_prod['blocks']);
}
$this->relatedCache[$module] = array_filter($blocks['blocks']);
}
$relateds = $this->relatedCache[$module];
if (empty($relateds)) return array();
$lists = array();
foreach ($relateds as $relinfo) {
$relationid = intval($relinfo['blockid']);
if ($relationid <= 0) continue;
if (is_array($crmid)) {
$relatedModule = $relinfo['related_module'];
$query = $this->getRelationQuery($module, $relinfo, $crmid);
if ($query) {
$res = $adb->limitQuery($query, 0, $this->listLimit * count($crmid));
if ($res) {
$rlists = array();
while ($row = $adb->FetchByAssoc($res, -1, false)) {
$mainCrmid = intval($row['crmid']);
$relcrmid = intval($row['relcrmid']);
if ($mainCrmid > 0 && $relcrmid > 0) {
$rlists[$mainCrmid][] = $relcrmid;
}
}
foreach ($rlists as $mainCrmid => $listrel) {
$lists[$mainCrmid][] = array(
'relationid' => $relationid,
'module' => $module,
'relmodule' => $relatedModule,
'extra' => null,
'total' => count($listrel),
'list' => $listrel,
'lineid' => null,
);
}
$missing_crmids = array_diff($crmid, array_keys($rlists));
foreach ($missing_crmids as $mainCrmid) {
$lists[$mainCrmid][] = array(
'relationid' => $relationid,
'module' => $module,
'relmodule' => $relatedModule,
'extra' => null,
'total' => 0,
'list' => array(),
'lineid' => null,
);
}
}
}
} else {
$onlyIds = 1;
if ($relinfo['type'] == 'PRODUCTS') $onlyIds = 0;
$crmRelationId = $this->getCrmRelationId($relationid);
$req = array('module' => $module, 'recordid' => $crmid, 'relationid' => $crmRelationId, 'limit' => $this->listLimit, 'onlyids' => $onlyIds);
$rlist = $this->subcall('GetRelated', $req);
if ($rlist['success'] && $rlist['entries']) {
$idlist = $touchUtils->arrayPluck('crmid', $rlist['entries']);
$extra = ($onlyIds ? null : $touchUtils->arrayPluck('extrafields', $rlist['entries']));
$lineidlist = ($onlyIds ? null : $touchUtils->arrayPluck('lineid', $rlist['entries']));
$lists[] = array(
'relationid' => $relationid,
'module' => $module,
'relmodule' => $relinfo['related_module'],
'extra' => $extra,
'total' => count($idlist),
'list' => $idlist,
'lineid' => $lineidlist,
);
}
}
}
return $lists;
}
protected function getFakeRecordId($module) {
global $adb, $table_prefix;
$setype = $module;
if ($setype == 'Events') $setype = 'Calendar';
$crmid = 1234567;
$res = $adb->limitpQuery("SELECT crmid FROM {$table_prefix}_crmentity WHERE setype = ? AND deleted = 0", 0, 1, array($setype)); // crmv@93148
if ($res && $adb->num_rows($res) > 0) {
$crmid = $adb->query_result_no_html($res, 0, 'crmid');
}
return intval($crmid) ?: 1234567;
}
protected function getRelationQuery($module, $blockinfo, $crmids) {
global $touchInst, $touchUtils, $onlyquery;
global $adb, $table_prefix;
$crmids = array_filter(array_map('intval', $crmids));
$relationId = $blockinfo['blockid'];
$relatedModule = $blockinfo['related_module'];
$crmRelationId = $this->getCrmRelationId($relationId);
$relationInfo = getRelatedListInfoById($crmRelationId);
$function_name = $relationInfo['functionName'];
if (empty($function_name)) return null;
if ($relatedModule == 'Calendar' || $relatedModule == 'Events') // crmv@54449
$mod_listquery = "activity_listquery";
else
$mod_listquery = strtolower($relatedModule)."_listquery";
$modObj = $touchUtils->getModuleInstance(($module == 'Events' ? 'Calendar' : $module));
if (!$modObj) return null;
$relmodObj = $touchUtils->getModuleInstance(($relatedModule == 'Events' ? 'Calendar' : $relatedModule)); // crmv@173184
$onlyquery = true;
$fakeRecordId = $this->getFakeRecordId($module);
// crmv@177095
if ($module == 'Messages') {
global $current_user;
$qgen = QueryGenerator::getInstance('Messages', $current_user);
$qgen->setFields(array('id'));
$qgen->appendToFromClause(
"INNER JOIN {$table_prefix}_messagesrel ON {$table_prefix}_messagesrel.messagehash = {$table_prefix}_messages.messagehash
INNER JOIN {$relmodObj->table_name} ON {$relmodObj->table_name}.{$relmodObj->table_index} = {$table_prefix}_messagesrel.crmid
INNER JOIN {$table_prefix}_crmentity c2 ON c2.crmid = {$relmodObj->table_name}.{$relmodObj->table_index} AND c2.deleted = 0"
);
$qgen->appendToWhereClause("AND {$table_prefix}_messagesrel.module = '".$adb->sql_escape_string($relatedModule)."'");
$qgen->appendToWhereClause("AND {$table_prefix}_messages.messagesid = ".intval($fakeRecordId));
$query = $qgen->getQuery();
} else {
// no need to reopen the session, since I don't care to save it
VteSession::set($mod_listquery, '');
$relatedListData = $modObj->$function_name($fakeRecordId, $relationInfo['tabid'], $relationInfo['relatedTabId'], array());
$query = VteSession::get($mod_listquery);
}
// crmv@177095e
// remove the order by clause (it's slow)
if (preg_match('/order by.+$/i', $query, $orderByMatch)) {
$query = preg_replace('/\s*order by.+$/i', '', $query);
}
if (preg_match('/([^\s()]+)\s*=\s*'.$fakeRecordId.'/', $query, $matches)) {
$relatedIndex = $matches[1];
//$query = preg_replace('/(and|or)\s*[^\s()]+\s*=\s*'.$fakeRecordId.'/i', '', $query);
$query = preg_replace('/=\s*'.$fakeRecordId.'/i', ' IN ('.implode(',', $crmids).')', $query);
$query = preg_replace('/^select .+? from/i', "SELECT {$relmodObj->table_name}.{$relmodObj->table_index} as relcrmid, $relatedIndex AS crmid FROM", $query); // crmv@173184
// crmv@177095
if (in_array($table_prefix.'_crmentity', $modObj->tab_name)) {
// now add a join with crmentity of first module because I have to filter by module
$query = $this->sql_preg_replace('/\s+where\s+/i', " INNER JOIN {$table_prefix}_crmentity crm1 ON crm1.crmid = $relatedIndex WHERE ", $query, 1);
//and add the condition
$query .= " AND crm1.setype = '$module'";
}
// crmv@177095e
}
// crmv@54449, really terrible tricks, please please, someone destroy that ugly calendar!!
if ($relatedModule == 'Events') {
// only events
$query = preg_replace("/'Task'/", "'NOTATASK'", $query);
} else {
// only tasks
$query = preg_replace("/activitytype in \(.*?\)/i", "activitytype = 'NOTANEVENT'", $query);
}
// crmv@54449e
// restore the order by
/*if ($orderByMatch[0]) {
$query .= " ".$orderByMatch[0];
}*/
// now replace the crmid
return $query;
}
//TODO: this is duplicated code, please remove me!!
// replaces pieces of a query, without replacing inside subqueries
// assume subquery starts with "(select"
protected function sql_preg_replace($regexp, $replace, $sql, $limit = -1) {
$subQueries = array();
// first replace subqueries with safe strings
while (preg_match('/\(\s*select/i', $sql, $smatches, PREG_OFFSET_CAPTURE)) {
$rpos1 = $smatches[0][1];
$rpos2 = 0;
$bcount = 1;
// now walk to find the closing bracket
for ($pos = $smatches[0][1]+1; $pos<strlen($sql); ++$pos) {
if ($sql[$pos] == '(') ++$bcount;
elseif ($sql[$pos] == ')') --$bcount;
if ($bcount == 0) {
$rpos2 = $pos+1;
break;
}
}
if ($rpos2 > $rpos1) {
$subid = '###SUBQUERY'.count($subQueries).'###';
$subQueries[$subid] = substr($sql, $rpos1, $rpos2-$rpos1);
$sql = substr($sql, 0, $rpos1) . $subid . substr($sql, $rpos2);
} else {
// if the closing bracket wasn't found
break;
}
}
// now do the replace
$sql = preg_replace($regexp, $replace, $sql, $limit);
// and restore the subqueries
if (count($subQueries) > 0) {
$sql = str_replace(array_keys($subQueries), array_values($subQueries), $sql);
}
return $sql;
}
protected function getCrmRelationId($relationId) {
global $touchUtils;
if ($touchUtils->isInviteesRelated($relationId)) {
// invitees
} elseif ($touchUtils->isProductsRelated($relationId)) {
// products
} elseif ($touchUtils->isNotesRelated($relationId)) {
// MyNotes
} elseif ($relationId >= $touchUtils->related_blockids['related_events']) {
$relationId -= $touchUtils->related_blockids['related_events'];
} else {
$relationId -= $touchUtils->related_blockids['related'];
}
return $relationId;
}
// $crmid can be an array. in this case, optimized subcalls will be used
// in this case the result will be in the format array(crmid1 => [lists], crmid2 => [lists]... )
protected function getN1Related($module, $crmid) {
global $touchUtils, $touchInst, $touchCache;
$RM = RelationManager::getInstance();
$relations = $RM->getRelations($module, ModuleRelation::$TYPE_NTO1, array(), $touchInst->excluded_modules);
$lists = array();
if (is_array($relations)) {
foreach ($relations as $rel) {
$relmodule = $rel->getSecondModule();
// crmv@93148
if (empty($this->relatedCache[$relmodule])) {
$key = 'relcache_'.$relmodule;
$relateds = $touchCache->get($key);
if ($relateds === false) {
$req = array('module' => $relmodule, 'type'=>'RELATED');
$blocks = $this->subcall('GetBlocks', $req);
$relateds = array_filter($blocks['blocks']);
$touchCache->set($key, $relateds);
}
$this->relatedCache[$relmodule] = $relateds;
}
$relationid = null;
$relateds = $this->relatedCache[$relmodule];
// crmv@93148e
// find correct related
foreach ($relateds as $relinfo) {
$relationid = intval($relinfo['blockid']) - $touchInst->related_blockids['related'];
if ($relationid <= 0) continue;
if ($relinfo['related_module'] == $module) break;
}
if (empty($relationid)) continue;
if (is_array($crmid)) {
$idlist = $this->getRelatedIdsNTO1($rel, $crmid);
if (is_array($idlist) && count($idlist) > 0) {
foreach ($idlist as $id => $listrel) {
$lists[$id][] = array(
'relationid' => ($rel->relationid ? $rel->relationid : $relationid),
'module' => $module,
'relmodule' => $relmodule,
'extra' => null,
'total' => count($listrel),
'list' => $listrel
);
}
}
$missing_crmids = array_diff($crmid, array_keys($idlist));
foreach ($missing_crmids as $id) {
$lists[$id][] = array(
'relationid' => ($rel->relationid ? $rel->relationid : $relationid),
'module' => $module,
'relmodule' => $relmodule,
'extra' => null,
'total' => 0,
'list' => array()
);
}
} else {
$idlist = $rel->getRelatedIds($crmid);
$lists[] = array(
'relationid' => ($rel->relationid ? $rel->relationid : $relationid),
'module' => $module,
'relmodule' => $relmodule,
'extra' => null,
'total' => count($idlist),
'list' => $idlist
);
}
}
}
return $lists;
}
// fast version of the ModuleRelation::getRelatedIds, which retrieves multiple ids at once
protected function getRelatedIdsNTO1($relation, $crmids) {
global $adb, $table_prefix, $touchInst, $touchUtils;
$ret = array();
if (!empty($relation->fieldid)) {
// inline getFieldValue, optimized for many records
$res = $adb->pquery(
"select {$table_prefix}_tab.name as modulename, fieldid, fieldname, tablename, columnname
from {$table_prefix}_field
inner join {$table_prefix}_tab on {$table_prefix}_tab.tabid = {$table_prefix}_field.tabid
where fieldid=? and {$table_prefix}_field.presence in (0,2)", array($relation->fieldid)
);
if ($res && $adb->num_rows($res) > 0) {
$row = $adb->FetchByAssoc($res, -1, false);
$focus = $touchUtils->getModuleInstance($row['modulename']);
if (empty($focus)) return $ret;
$indexname = $focus->tab_name_index[$row['tablename']];
if (empty($indexname)) return $ret;
if ($row['tablename'] != $table_prefix.'_crmentity') {
$join = "inner join {$table_prefix}_crmentity on {$table_prefix}_crmentity.crmid = {$row['tablename']}.$indexname";
} else {
$join = "";
}
$res2 = $adb->pquery(
"select {$row['tablename']}.$indexname AS srcid, {$row['tablename']}.{$row['columnname']} as fieldval
from {$row['tablename']}
$join
inner join {$table_prefix}_crmentity crm2 ON crm2.crmid = {$row['tablename']}.{$row['columnname']}
where {$table_prefix}_crmentity.deleted = 0 AND crm2.setype = ? AND {$row['tablename']}.$indexname IN (".generateQuestionMarks($crmid).")",
array($relation->getSecondModule(), $crmids)
);
if ($res2 && $adb->num_rows($res2) > 0) {
while ($row2 = $adb->FetchByAssoc($res2, -1, false)) {
$ret[$row2['srcid']][] = $row2['fieldval'];
}
}
}
}
return $ret;
}
// REMOVE ME
protected function getRecents() {
$recents = array();
$req = array('module' => '');
$result = $this->subcall('GetRecents', $req);
if ($result && $result['success']) {
$recents = $result['list'];
}
return $recents;
}
// warning, retrieve all the folders, not only the one visible to the user
/*function getFoldersForRecord($moduleInstance, $recordid) {
global $adb, $table_prefix;
$table = $moduleInstance->table_name;
$index = $moduleInstance->table_index;
$res = $adb->pquery("select crm.crmid from {$table_prefix}_crmentity crm inner join $table on $table.$index = crm.crmid where crm.deleted = 0 and $table.folderid = ?")
}*/
function getChangedUsers($sinceDate = null) {
global $adb, $table_prefix, $metaLogs, $touchInst, $touchUtils, $current_user; // crmv@90935
if (empty($sinceDate)) $sinceDate = $this->zeroDate;
if (!$metaLogs) $metaLogs = MetaLogs::getInstance();
$query = "SELECT id FROM {$table_prefix}_users WHERE deleted = 0 AND status = ? AND date_modified >= ?";
$params = array('Active', $sinceDate);
$list = array();
$res = $adb->pquery($query, $params);
if ($res && $adb->num_rows($res) > 0) {
while ($row = $adb->fetchByAssoc($res, -1, false)) {
$sparams = array('userid' => $row['id']);
$user = $this->subcall('GetUsers', $sparams);
if ($user['success'] && $user['total'] > 0) {
$list[] = $user['users'][0];
}
}
}
// now get also deleted users
$delUsers = $metaLogs->getChanges($metaLogs::OPERATION_DELUSER, $sinceDate);
if ($delUsers && count($delUsers) > 0) {
// add a fake user, just to know something has changes
$list[] = array('userid' => 0, 'deleted' => true);
}
//crmv@90935
// check if my password has changed
$changePwd = $metaLogs->getChanges($metaLogs::OPERATION_CHANGEUSERPWD, $sinceDate);
if ($changePwd && count($changePwd) > 0) {
foreach ($changePwd as $change) {
if ($change['objectid'] == $current_user->id) {
$list[] = array('userid' => $current_user->id, 'changed_password' => true);
}
}
}
//crmv@90935e
// clear user cache
if (count($list) > 0) {
$touchUtils->clearWSCache('GetUsers');
}
return array('changes' => $list, 'total' => count($list));
}
function getChangedGroups($sinceDate = null) {
global $adb, $table_prefix, $metaLogs, $touchUtils;
if (empty($sinceDate)) $sinceDate = $this->zeroDate;
if (!$metaLogs) $metaLogs = MetaLogs::getInstance();
$query = "SELECT groupid FROM {$table_prefix}_groups WHERE date_modified >= ?";
$params = array($sinceDate);
$list = array();
$res = $adb->pquery($query, $params);
if ($res && $adb->num_rows($res) > 0) {
while ($row = $adb->fetchByAssoc($res, -1, false)) {
$sparams = array('groupid' => $row['groupid']);
$group = $this->subcall('GetGroups', $sparams);
if ($group['success'] && $group['total'] > 0) {
$list[] = $group['groups'][0];
}
}
}
// now get also deleted groups
$delGroups = $metaLogs->getChanges($metaLogs::OPERATION_DELGROUP, $sinceDate);
if ($delGroups && count($delGroups) > 0) {
// add a fake user, just to know something has changes
$list[] = array('groupid' => 0, 'deleted' => true);
}
// clear user cache
if (count($list) > 0) {
$touchUtils->clearWSCache('GetGroups');
}
return array('changes' => $list, 'total' => count($list));
}
function getMetaChanges($sinceDate = null) {
global $adb, $table_prefix, $metaLogs, $touchCache;
if (empty($sinceDate)) $sinceDate = $this->zeroDate;
if (!$metaLogs) $metaLogs = MetaLogs::getInstance();
// ignore these types of operations
$ignoreTypes = array(
$metaLogs::OPERATION_UNKNOWN,
$metaLogs::OPERATION_REBUILDSHARES,
$metaLogs::OPERATION_ADDUSER,
$metaLogs::OPERATION_EDITUSER,
$metaLogs::OPERATION_DELUSER,
$metaLogs::OPERATION_ADDGROUP,
$metaLogs::OPERATION_EDITGROUP,
$metaLogs::OPERATION_DELGROUP,
$metaLogs::OPERATION_CHANGEUSERPWD, // crmv@90935
//crmv@146434
$metaLogs::OPERATION_ADDPANEL,
$metaLogs::OPERATION_DELPANEL,
$metaLogs::OPERATION_EDITPANEL,
$metaLogs::OPERATION_ADDRELATEDLISTTOTAB,
$metaLogs::OPERATION_EDITRELATEDLISTTOTAB,
$metaLogs::OPERATION_DELRELATEDLISTTOTAB,
//crmv@146434e
);
$req = array('onlynames' => 1);
$appModules = $this->subcall('ModulesList', $req);
$modules = array();
$changes = $metaLogs->getChanges(null, $sinceDate);
if (is_array($changes)) {
foreach ($changes as $k=>$c) {
// exclude some
if (in_array($c['operation'], $ignoreTypes)) {
unset($changes[$k]);
continue;
}
// get modules
$mods = $metaLogs->getAffectedModules($c);
$common = array_intersect($mods, $appModules);
if (in_array('All', $mods) || count($common) > 0) {
// module is relevant
$changes[$k]['modules'] = $mods;
} else {
unset($changes[$k]);
}
// crmv@57366
// now check if I need to keep it
if (!$this->filterMetaChange($changes[$k])) {
unset($changes[$k]);
}
// crmv@57366e
}
}
// clear cache
if (count($changes) > 0) {
$touchCache->clear();
}
return array('changes'=>$changes, 'total'=>count($changes), 'last_timestamp' => time()); // crmv@107655
}
// crmv@57366
function filterMetaChange($change) {
global $adb, $table_prefix, $metaLogs;
$filterchanges = array(
$metaLogs::OPERATION_ADDFILTER,
$metaLogs::OPERATION_EDITFILTER,
// always pass the delete, because I don't know if it was mobile or not
//$metaLogs::OPERATION_DELFILTER,
);
// check if it's a filter edit, of a non-mobile filter
if (in_array($change['operation'], $filterchanges)) {
$filterid = intval($change['objectid']);
if ($filterid > 0) {
$res = $adb->pquery("SELECT setmobile FROM {$table_prefix}_customview WHERE cvid = ?", array($filterid));
if ($res && $adb->num_rows($res) > 0) {
$isMobile = $adb->query_result_no_html($res, 0, 'setmobile');
if (!$isMobile) return false;
}
}
}
return true;
}
// crmv@57366e
// crmv@107655
function getEmailDirectory($sinceDate = null) {
$list = array();
$fakeRequest = array(
'since' => $sinceDate ? strtotime($sinceDate) : 0,
);
$result = $this->subcall('GetEmailDirectory', $fakeRequest);
if ($result['success']) {
$list = $result['entries'];
}
return array('entries' => $list, 'total' => count($list), 'last_timestamp' => time());
}
// crmv@107655e
}