* SPDX-License-Identifier: AGPL-3.0-only ************************************/ /* crmv@31780 */ /* crmv@33097 - Initial offline support */ /* crmv@34559 - ModComments, filters */ /* crmv@38476 - SDK */ /* crmv@39106 - Documents, PDFMaker */ /* crmv@39110 - Profiling */ /* crmv@42537 - Messages */ /* crmv@49398 - New webservice version, offline support */ /* crmv@55371 - Bugfix */ /* crmv@56798 - Caching */ /* crmv@71388 - File upload */ /* crmv@99131 - Support for Charts */ /* crmv@99132 - Support for Wizards */ /* crmv@95788 - Support for replay check */ /* crmv@107655 - Support for Messages accounts */ /* * TODO Touch: * 1. gestione centralizzata dei messaggi di errore */ require_once('modules/Touch/TouchWSClass.php'); require_once('modules/Touch/TouchUtils.php'); require_once('modules/Touch/TouchCache.php'); require_once('include/Webservices/Login.php'); class Touch extends SDKExtendableUniqueClass { // crmv@42024 // versione API Touch lato server - quando si cambia, l'app mostra un warning al login public $version = "2.2"; // versione to show to legacy api calls public $legacyVersion = "1.4.1"; // limite di elementi per pagina public $listPageLimit = 50; // limite per autocomplete public $autocompleteLimit = 5; // crmv@86915 public $longOperationTime = 600; // seconds for long webservices /** * These modules are completely excluded from the App */ public $excluded_modules = array('Fax', 'Sms', 'Emails', 'PriceBooks', 'PBXManager', 'WebMails', 'Projects', 'Newsletter', 'Targets', 'Campaigns', 'Telemarketing', 'Transitions'); // crmv@33311 crmv@164120 /** * These modules are excluded only from the main list */ public $excluded_modules_list = array('ModNotifications', 'ModComments'); /** * Force the modules in the home page with this ordering * Special values: Recents, Favourites, Search, Areas, PageBreak * PageBreak = force the creation of a new page * Areas = replaced with modules in the areas, separated by PageBreak * All the remaining modules (not listed here, or in any Area) go to the end of the list */ public $modules_list_order = array( 'Calendar', 'Events', 'Processes', 'Messages', 'MyNotes', 'Myfiles', 'Recents', 'Favourites', 'Search', 'PageBreak', // crmv@188277 'Areas', /*'Contacts', 'Leads', 'Accounts', 'Vendors', 'PageBreak', 'ProjectPlan', 'ProjectTask', 'ProjectMilestone', 'PageBreak', 'Potentials', 'Quotes', 'Invoice', 'SalesOrder', 'PageBreak', 'Products', 'Services', 'PriceBooks', 'ProductLines', 'PurchaseOrder', 'PageBreak',*/ ); // hide these pages from some modules (valid values: recents, favourites, filters, search, folders, foldercont) public $hide_module_pages = array( 'Myfiles' => array('recents', 'favourites', 'filters'), 'MyNotes' => array('recents', 'favourites', 'filters', 'folders', 'foldercont'), 'Processes' => array('recents', 'favourites', 'folders', 'foldercont'), // crmv@188277 'Charts' => array('filters'), ); // force these permissions for these modules, instead of using the isPermitted check public $force_module_premissions = array( 'Processes' => array('perm_create' => false), // crmv@198545 //'Myfiles' => array('perm_create' => false, 'perm_write' => false), ); /** * DEPRECATED */ public $offline_max_items = 100; /** * DEPRECATED */ public $offline_chunks = 50; // crmv@85213 /** * The strategy to use when handling with save collision generated from Offline App * eg: A record is saved in the app offline, then updated in the VTE, than the app goes online * and try to save the record. * 4 Modes available: * "": no collision checking * "VTE": the edit from the VTE wins, regardless of the timestamp * "App": the edit from the App wins, regardless of the timestamp * "Last": the last edit wins */ public $collision_strategy = 'Last'; /** * Not implemented yet! * "record" or "field" */ public $collision_level = 'record'; // crmv@85213 /** * If true, the replay check is enabled (if the app supports this feature). * This means that the same request cannot be processed more than once if * it was succesful. This is especially useful when the client is on a faulty * connection, and it doesn't receive the server response */ public $replay_check = true; public $table_name_prop = ''; public $table_name_user_prop = ''; public $table_name_user_dev_prop = ''; public $table_requests = ''; /** * List of Touch Webservices * The value can be a single PHP file or an array in the form array('class'=>classname, 'file'=>filename) * The latter is useful when overriding some webservices (only version >= 2), or part of them. * In this case you can extend the class Touch and redefine the desired webservice with something like * array('class'=>'MyNewWebservice', 'file'=>'MyNewFile.php') and in the MyNewFile.php the class * MyNewWebservice extends the old webservice to add/modify functionalities */ public $webservices = array( 'Login' => 'TouchLogin.php', 'Logout' => 'TouchLogout.php', // crmv@91082 'ModulesList' => 'TouchModulesList.php', 'GetBlocks' => 'TouchGetBlocks.php', 'GetComments' => 'TouchGetComments.php', 'GetTicketComments' => 'TouchGetTicketComments.php', 'WriteComment' => 'TouchWriteComment.php', 'EditComment' => 'TouchEditComment.php', // crmv@93148 'WriteTicketComment' => 'TouchWriteTicketComment.php', 'GetFavorites' => 'TouchGetFavorites.php', 'SetFavorites' => 'TouchSetFavorites.php', 'GetNotifications' => 'TouchGetNotifications.php', 'SeeNotifications' => 'TouchSeeNotifications.php', 'UnseeNotifications' => 'TouchUnseeNotifications.php', 'GetChanges' => 'TouchGetChanges.php', 'GetRecents' => 'TouchGetRecents.php', 'GetTodos' => 'TouchGetTodos.php', 'GetList' => 'TouchGetList.php', 'GetFilterList' => 'TouchGetFilterList.php', 'GetRecord' => 'TouchGetRecord.php', 'GetRecords' => 'TouchGetRecords.php', 'GetRelated' => 'TouchGetRelated.php', 'GetRelatedList' => 'TouchGetRelatedList.php', 'GetUsers' => 'TouchGetUsers.php', 'GetRoles' => 'TouchGetRoles.php', 'GetGroups' => 'TouchGetGroups.php', 'GetAvatars' => 'TouchGetAvatars.php', 'SaveRecord' => 'TouchSaveRecord.php', 'DeleteRecord' => 'TouchDeleteRecord.php', 'GetAssociatedProducts' => 'TouchGetAssociatedProducts.php', 'DeleteRelation' => 'TouchDeleteRelation.php', 'SaveRelation' => 'TouchSaveRelation.php', 'SaveFilter' => 'TouchSaveFilter.php', 'GetOfflineData' => 'TouchGetOfflineData.php', 'GetOverrideFile' => 'TouchGetOverrideFile.php', 'PDFMaker' => 'TouchPDFMaker.php', 'SimpleEdit' => 'TouchSimpleEdit.php', // crmv@39110 'GetMenuList' => 'TouchGetMenuList.php', // crmv@42707 'GetMessagesMeta' => 'TouchGetMessagesMeta.php', // crmv@42537 'GetMessagesCount' => 'TouchGetMessagesCount.php', 'Autocomplete' => 'TouchAutocomplete.php', 'SendMail' => 'TouchSendMail.php', 'SetFlag' => 'TouchSetFlag.php', 'MoveMessage' => 'TouchMoveMessage.php', // crmv@57010 'SetRecent' => 'TouchSetRecent.php', 'GetLinkedRecords' => 'TouchGetLinkedRecords.php', 'LinkModules' => 'TouchLinkModules.php', 'GetAreas' => 'TouchGetAreas.php', 'GlobalSearch' => 'TouchGlobalSearch.php', 'ShareToken' => 'TouchShareToken.php', 'AnswerInvitation' => 'TouchAnswerInvitation.php', 'MultiCall' => 'TouchMultiCall.php', 'Geolocation' => 'TouchGeolocation.php', 'UploadFile' => 'TouchUploadFile.php', 'ConvertLead' => 'TouchConvertLead.php', 'ModuleAutocomplete' => 'TouchModuleAutocomplete.php', // crmv@86915 'Geocoding' => 'TouchGeocoding.php', // crmv@86915 'GetWebservices' => 'TouchGetWebservices.php', // crmv@93148 'GetChartData' => 'TouchGetChartData.php', 'GetWizards' => 'TouchGetWizards.php', 'GetMessagesAccounts' => 'TouchGetMessagesAccounts.php', 'SaveMessagesAccount' => 'TouchSaveMessagesAccount.php', 'SaveMessagesFolders' => 'TouchSaveMessagesFolders.php', 'DeleteMessagesAccount' => 'TouchDeleteMessagesAccount.php', 'GetEmailDirectory' => 'TouchGetEmailDirectory.php', 'GetCurrencies' => 'TouchGetCurrencies.php', // crmv@134732 'TrackingList' => 'TouchTrackingList.php', // crmv@124979 'MessagesIcs' => 'TouchMessagesIcs.php', // crmv@174249 'GetProcessesCount' => 'TouchGetProcessesCount.php', // crmv@188277 ); protected $isPermitted = null; public function __construct() { global $table_prefix; $this->table_name_prop = $table_prefix.'_touch_prop'; $this->table_name_user_prop = $table_prefix.'_touch_user_prop'; $this->table_name_user_dev_prop = $table_prefix.'_touch_user_dev_prop'; $this->table_requests = $table_prefix.'_touch_requests'; $this->table_tempids = $table_prefix.'_touch_tempids'; // crmv@106521 $this->table_wipedata = $table_prefix.'_touch_wipedata'; // crmv@161368 $this->checkTables(); } protected function checkTables() { global $adb; $schema_table = ' ENGINE=InnoDB
'; if(!Vtecrm_Utils::CheckTable($this->table_name_prop)) { $schema_obj = new adoSchema($adb->database); $schema_obj->ExecuteSchema($schema_obj->ParseSchemaString($schema_table)); } $schema_table = ' ENGINE=InnoDB
'; if(!Vtecrm_Utils::CheckTable($this->table_name_user_prop)) { $schema_obj = new adoSchema($adb->database); $schema_obj->ExecuteSchema($schema_obj->ParseSchemaString($schema_table)); } $schema_table = ' ENGINE=InnoDB
'; if(!Vtecrm_Utils::CheckTable($this->table_name_user_dev_prop)) { $schema_obj = new adoSchema($adb->database); $schema_obj->ExecuteSchema($schema_obj->ParseSchemaString($schema_table)); } // crmv@95788 $schema_table = ' ENGINE=InnoDB status
'; if(!Vtecrm_Utils::CheckTable($this->table_requests)) { $schema_obj = new adoSchema($adb->database); $schema_obj->ExecuteSchema($schema_obj->ParseSchemaString($schema_table)); } // crmv@95788e // crmv@106521 $schema_table = ' ENGINE=InnoDB insert_date
'; if(!Vtecrm_Utils::CheckTable($this->table_tempids)) { $schema_obj = new adoSchema($adb->database); $schema_obj->ExecuteSchema($schema_obj->ParseSchemaString($schema_table)); } // crmv@106521e // crmv@161368 $schema_table = ' ENGINE=InnoDB
'; if(!Vtecrm_Utils::CheckTable($this->table_wipedata)) { $schema_obj = new adoSchema($adb->database); $schema_obj->ExecuteSchema($schema_obj->ParseSchemaString($schema_table)); } // crmv@161368e } public function initDefaultProperties() { // set properties if not already set $list = array( 'use_offline_cache' => 1, // crmv@73256 'use_geolocation' => 0, // crmv@59610 'app_theme' => 'vte16', // crmv@86915 ); foreach ($list as $prop=>$value) { $oldVal = $this->getProperty($prop); if ($oldVal === false) $this->setProperty($prop, $value); } } // FALSE if not found public function getProperty($property) { global $adb, $table_prefix; $r = $adb->pquery("SELECT value FROM {$this->table_name_prop} WHERE property = ?", array($property)); if ($r && $adb->num_rows($r) > 0) { return $adb->query_result_no_html($r, 0, 'value'); } return false; } public function getUserProperty($userid, $property) { global $adb, $table_prefix; $r = $adb->pquery("SELECT value FROM {$this->table_name_user_prop} WHERE userid = ? AND property = ?", array($userid, $property)); if ($r && $adb->num_rows($r) > 0) { return $adb->query_result_no_html($r, 0, 'value'); } return false; } public function getUserDeviceProperty($userid, $deviceid, $property) { global $adb, $table_prefix; $r = $adb->pquery("SELECT value FROM {$this->table_name_user_dev_prop} WHERE userid = ? AND deviceid = ? AND property = ?", array($userid, $deviceid, $property)); if ($r && $adb->num_rows($r) > 0) { return $adb->query_result_no_html($r, 0, 'value'); } return false; } public function setProperty($property, $value) { global $adb, $table_prefix; $r = $adb->pquery("SELECT value FROM {$this->table_name_prop} WHERE property = ?", array($property)); if ($r) { if ($adb->num_rows($r) > 0) { // update $r = $adb->pquery("UPDATE {$this->table_name_prop} SET value = ? WHERE property = ?", array($value, $property)); } else { // insert $r = $adb->pquery("INSERT INTO {$this->table_name_prop} (property, value) VALUES (?,?)", array($property, $value)); } } } public function setUserProperty($userid, $property, $value) { global $adb, $table_prefix; $r = $adb->pquery("SELECT value FROM {$this->table_name_user_prop} WHERE userid = ? AND property = ?", array($userid, $property)); if ($r) { if ($adb->num_rows($r) > 0) { // update $r = $adb->pquery("UPDATE {$this->table_name_user_prop} SET value = ? WHERE userid = ? AND property = ?", array($value, $userid, $property)); } else { // insert $r = $adb->pquery("INSERT INTO {$this->table_name_user_prop} (userid, property, value) VALUES (?,?,?)", array($userid, $property, $value)); } } } public function setUserDeviceProperty($userid, $deviceid, $property, $value) { global $adb, $table_prefix; $r = $adb->pquery("SELECT value FROM {$this->table_name_user_dev_prop} WHERE userid = ? AND deviceid = ? AND property = ?", array($userid, $deviceid, $property)); if ($r) { if ($adb->num_rows($r) > 0) { // update $r = $adb->pquery("UPDATE {$this->table_name_user_dev_prop} SET value = ? WHERE userid = ? AND deviceid = ? AND property = ?", array($value, $userid, $deviceid, $property)); } else { // insert $r = $adb->pquery("INSERT INTO {$this->table_name_user_dev_prop} (userid, deviceid, property, value) VALUES (?,?,?,?)", array($userid, $deviceid, $property, $value)); } } } public function deleteProperty($property) { global $adb, $table_prefix; $r = $adb->pquery("DELETE FROM {$this->table_name_prop} WHERE property = ?", array($property)); } public function deleteUserProperty($userid, $property) { global $adb, $table_prefix; $r = $adb->pquery("DELETE FROM {$this->table_name_user_prop} WHERE userid = ? AND property = ?", array($userid, $property)); } public function deleteUserDeviceProperty($userid, $deviceid, $property) { global $adb, $table_prefix; $r = $adb->pquery("DELETE FROM {$this->table_name_user_dev_prop} WHERE userid = ? AND deviceid = ? AND property = ?", array($userid, $deviceid, $property)); } // crmv@106521 public function setTempId($userid, $deviceId, $crmid, $tempid) { global $adb, $table_prefix; if (!$deviceId) { $deviceId = $this->getCurrentDeviceId(); } if ($userid > 0 && $deviceId) { $params = array($userid, $deviceId, $crmid, $tempid, date('Y-m-d H:i:s')); $adb->pquery("INSERT INTO {$this->table_tempids} (userid, deviceid, crmid, temp_crmid, insert_date) VALUES (?,?,?,?,?)", $params); return true; } return false; } /** * Get the pair tempid, crmid for the user */ public function getTempId($userid, $deviceId, $crmid) { global $adb, $table_prefix; if (!$deviceId) { $deviceId = $this->getCurrentDeviceId(); } if ($userid > 0 && $deviceId) { $params = array($userid, $deviceId, $crmid); $res = $adb->pquery("SELECT temp_crmid FROM {$this->table_tempids} WHERE userid = ? AND deviceid = ? AND crmid = ?", $params); if ($res && $adb->num_rows($res) > 0) { $tcrmid = $adb->query_result_no_html($res, 0, 'temp_crmid'); return $tcrmid; } } return null; } // crmv@159113 /** * Get the rela crmid for the specified temporary id */ public function getRealId($userid, $deviceId, $tcrmid) { global $adb, $table_prefix; if (!$deviceId) { $deviceId = $this->getCurrentDeviceId(); } if ($userid > 0 && $deviceId) { $params = array($userid, $deviceId, $tcrmid); $res = $adb->pquery("SELECT crmid FROM {$this->table_tempids} WHERE userid = ? AND deviceid = ? AND temp_crmid = ?", $params); if ($res && $adb->num_rows($res) > 0) { $crmid = $adb->query_result_no_html($res, 0, 'crmid'); return $crmid; } } return null; } // crmv@159113e /** * Clean old temp ids */ public function cleanTempIds($userid = null) { global $adb, $table_prefix, $current_user; $pruneDays = 7; // delete stuff older than 7 days $sql = "DELETE FROM {$this->table_tempids} WHERE insert_date < ?"; $params = array(date('Y-m-d H:i:s', time()-$pruneDays*3600*24)); if ($userid > 0) { $sql .= " AND userid = ?"; $params[] = $userid; } $res = $adb->pquery($sql, $params); } // crmv@106521e public function processLoginData($data) { global $current_user; $data = Zend_Json::decode(base64_decode($data)); if (is_array($data) && is_array($data['s']) && $data['d']) { $this->setUserDeviceProperty($current_user->id, $data['d'], 'login_data', base64_encode(implode(':', $data['s']))); // crmv@91082 - save the deviceid in the session VteSession::set('touch_device_id', $data['d']); // crmv@91082e } } public function retrieveLoginData($deviceid) { global $current_user; $sums = base64_decode($this->getUserDeviceProperty($current_user->id, $deviceid, 'login_data')); return base64_encode(Zend_Json::encode(array('s' => $sums, 'd'=>$deviceid))); } // crmv@91082 public function getCurrentDeviceId() { $devid = VteSession::get('touch_device_id'); if (empty($devid) && !empty($_REQUEST['deviceid'])) { $devid = $_REQUEST['deviceid']; VteSession::set('touch_device_id', $devid); } return $devid; } // crmv@91082e /** * DEPRECATED */ public function isOfflineEnabled() { return true; } // cache the isPermitted result to avoid multiple checks protected function isTouchPermitted() { if (is_null($this->isPermitted)) { $this->isPermitted = (isPermitted('Touch', 'DetailView') == 'yes'); } return $this->isPermitted; } /** * Return the folder which contains the webservices for the specified version */ protected function getVersionFolder($version) { $folder = 'vtws'; if (!empty($version) && version_compare($version, '2.0', '>=')) { $folder = 'vtws2'; } return $folder; } /** * Return True if the webservice exists */ public function wsExists($wsname, $version = null) { $fname = $this->getWSFile($wsname, $version); return ($fname && is_readable($fname)); } /** * Return the filename for the specified webservice and version. NULL if not found */ public function getWSFile($wsname, $version = null) { $folder = $this->getVersionFolder($version); $filename = $this->webservices[$wsname]; if (is_array($filename) && !empty($filename['file'])) $filename = $filename['file']; if ($filename) return "modules/Touch/$folder/$filename"; else return null; } /** * Get the class name for the specified webservice */ public function getWSClassName($wsname, $version = null) { $classname = str_replace('.', '', $wsname); if (!empty($version) && version_compare($version, '2.0', '>=')) { $wsfile = $this->webservices[$wsname]; if (is_array($wsfile) && !empty($wsfile['class'])) { $classname = $wsfile['class']; } else { $classname = 'Touch'.$classname; } } return $classname; } /** * Get the filename containing the class for the specified webservice */ public function getWSClassFile($wsname, $version = null) { $folder = $this->getVersionFolder($version); if ($folder == 'vtws') { return "modules/Touch/$folder/classes/".$this->getWSClassName($wsname, $version).'.class.php'; } else { return $this->getWSFile($wsname, $version); } } /** * Create an instance of the webservice's class */ public function getWSClassInstance($wsname, $version = null) { $classfile = $this->getWSClassFile($wsname, $version); $classname = $this->getWSClassName($wsname, $version); if (!class_exists($classname)) require($classfile); $wsclass = new $classname($version); return $wsclass; } // start the session so the session-cache can be used /** * Starts a session specific for the Touch webservices only. * You can use this session to store some data as a cache. */ public function startWSSession() { VteSession::$sessionName = 'TOUCHSESSID'; // set the sessionid from a custom header or a request parameter as fallback if (!empty($_SERVER['HTTP_TOUCH_SESSION_ID'])) { $_REQUEST[VteSession::$sessionName] = $_SERVER['HTTP_TOUCH_SESSION_ID']; } elseif (!empty($_REQUEST['touch_session_id'])) { $_REQUEST[VteSession::$sessionName] = $_REQUEST['touch_session_id']; } VteSession::start(); header('Touch-Session-Id: '.VteSession::getId()); // useful variable to detect the App-mode VteSession::set("app_unique_key", 'WSMobile_'.time()); } public function resetWSSession() { VteSession::reset(); } // crmv@91082 public function destroyWSSession() { VteSession::destroy(); } // crmv@91082e // crmv@93148 /** * Suspend the current session, so the session file doesn't lock concurrent requests */ public function closeWSSession() { VteSession::close(true); } /** * Reopen the current session, in order to write in it */ public function reopenWSSession() { VteSession::reopen(true); return true; } // crmv@93148e /** * For Post-Login webservices, validate the provided credentials */ protected function executeWSLogin(&$request) { global $current_user, $default_language, $current_language; $result = $this->checkLogin($request['username'], $request['password']); $userId = $result['userid']; // utente if ($result['success'] && $userId > 0) { $current_user = CRMEntity::getInstance('Users'); $current_user->id = $userId; $current_user->retrieveCurrentUserInfoFromFile($userId); // check active state if ($current_user->column_fields['status'] != 'Active') return false; // lingua if (!empty($current_user->column_fields['default_language'])) { $default_language = $current_language = $current_user->column_fields['default_language']; } return true; } return false; } /** * Executes a Touch Webservice */ public function executeWS($wsname, $wsversion, &$request) { $filename = $this->getWSClassFile($wsname, $wsversion); $classname = $this->getWSClassName($wsname, $wsversion); if (!is_readable($filename) || empty($filename)) { return $this->outputFailure('Webservice file is missing'); } // include only if necessary if (!class_exists($classname)) require($filename); if (!class_exists($classname)) { return $this->outputFailure('Webservice class not found'); } else { $wsclass = new $classname($wsversion); // check magic cache params if ($request['touch_no_cache'] == '1') { global $touchCache; $touchCache->disable(); } if (!$wsclass->preLogin && !$this->executeWSLogin($request)) { return $this->outputFailure('Invalid credentials'); } else { //auditing // crmv@202301 require_once('modules/Settings/AuditTrail.php'); $AuditTrail = new AuditTrail(); $AuditTrail->processTouchWS($request); // crmv@202301e // Check if Touch module isactive, I can do it only after the login if (!$wsclass->preLogin && !$this->isTouchPermitted()) { return $this->outputFailure('Touch module is not active'); } if ($wsclass->longOperation) { set_time_limit($this->longOperationTime); } // crmv@106521 // clean temp ids in case of logout, login or every 2 hours global $current_user; // crmv@204438 if ($wsname == 'Login' || $wsname == 'Logout' || VteSession::isEmpty('touch_clean_tempids_ts') || VteSession::get('touch_clean_tempids_ts') < time()-7200) { $this->cleanTempIds($current_user->id); VteSession::set('touch_clean_tempids_ts', time()); } //crmv@95788 $hasReplayCheck = ($this->replay_check && !empty($request['touch_request_id'])); $return_data = null; if ($hasReplayCheck && !$this->checkReplay($request, $return_data)) { $replayInfo = array('already_processed' => true); if (is_array($return_data)) { $replayInfo['data'] = $return_data; } return $this->outputFailure('This request has already been processed', $replayInfo); } // crmv@204438 if (VteSession::isEmpty('authenticated_user_id')) { VteSession::set('authenticated_user_id', $current_user->id); } // crmv@204438e $result = null; $r = $wsclass->execute($request, $result); if ($hasReplayCheck) { $this->closeReplay($request, $result); } return $r; //crmv@95788e crmv@106521e } } } /** * Calls a webservice and returns its results */ public function callWS($wsname, $wsversion, &$request) { global $userId; $filename = $this->getWSClassFile($wsname, $wsversion); $classname = $this->getWSClassName($wsname, $wsversion); if (!is_readable($filename) || empty($filename)) { return $this->createOutput(null, 'Webservice file is missing', false); } // include only if necessary if (!class_exists($classname)) require($filename); if (!class_exists($classname)) { return $this->createOutput(null, 'Webservice class not found', false); } else { $wsclass = new $classname($wsversion); // Check if Touch module isactive, I can do it only after the login if (!$wsclass->preLogin && !$this->isTouchPermitted()) { return $this->createOutput(null, 'Touch module is not active', false); } if ($wsclass->longOperation) { set_time_limit($this->longOperationTime); } return $wsclass->call($request); } } public function checkLogin($username, $accesskey) { $login = false; $userInst = CRMEntity::getInstance('Users'); $userId = $userInst->retrieve_user_id($username); $accessKey = vtws_getUserAccessKey($userId); if (strcmp($accessKey, $accesskey) === 0) { $login = true; } return array('success'=> $login, 'userid' => $userId); } protected function detectSuccess($data) { $ret = true; if (is_array($data) && ($data['success'] === false || !empty($data['error']))) $ret = false; return $ret; } public function createOutput($data = array(), $message = '', $success = null) { if (!is_array($data)) $data = array($data); if (is_null($success)) $success = $this->detectSuccess($data); if (!$success) { $data['success'] = false; $data['error'] = $message; } else { $data['success'] = true; $data['error'] = ''; } return $data; } public function outputFailure($message, $extra = array()) { $payload = $this->createOutput($extra, $message, false); $this->outputRaw($payload); return false; } public function outputSuccess($data = array()) { $payload = $this->createOutput($data, '', true); $this->outputRaw($payload); return true; } public function outputRaw($data) { // add the session parameter $sessid = VteSession::getId(); if (is_array($data) && array_key_exists('success', $data) && !empty($sessid)) { $data['touch_session_id'] = $sessid; } // output result header('Content-type: application/json'); echo Zend_Json::encode($data); return $this->detectSuccess($data); } //crmv@95788 /** * Check if the request has already been processed (it's a replay) */ protected function checkReplay(&$request, &$return_data = null) { // crmv@106521 global $adb, $table_prefix, $current_user; $skipCheckWs = array('GetWebservices', 'Login', 'Logout', 'MultiCall'); $requestId = $request['touch_request_id']; $deviceId = $this->getCurrentDeviceId(); $wsname = $request['wsname']; // no deviceid, the replay is not checked if (empty($requestId) || empty($deviceId)) return true; // clean the requests if login, logout or every hour if ($wsname == 'Login' || $wsname == 'Logout' || VteSession::isEmpty('touch_clean_replay_ts') || VteSession::get('touch_clean_replay_ts') < time()-3600) { $this->cleanReplayTable($current_user->id); VteSession::set('touch_clean_replay_ts', time()); } if (in_array($wsname, $skipCheckWs)) return true; // check for an existing requests $res = $adb->pquery( "SELECT * FROM {$this->table_requests} WHERE userid = ? AND deviceid = ? AND requestid = ? AND status IN (?,?)", // crmv@189084 array($current_user->id, $deviceId, $requestId, 'COMPLETED', 'PROCESSING') // crmv@189084 ); if ($res && $adb->num_rows($res) > 0) { // already processed $return_data = Zend_Json::decode($adb->query_result_no_html($res, 0, 'return_data')); // crmv@106521 return false; } else { // new request, insert it! $values = array( 'userid' => $current_user->id, 'deviceid' => $deviceId, 'requestid' => $requestId, 'status' => 'PROCESSING', 'request_date' => date('Y-m-d H:i:s'), 'wsname' => $wsname, ); $adb->pquery("INSERT INTO {$this->table_requests} (".implode(',', array_keys($values)).") VALUES (".generateQuestionMarks($values).")", $values); } return true; } // crmv@106521 protected function closeReplay(&$request, $result) { global $adb, $table_prefix, $current_user; $requestId = $request['touch_request_id']; $deviceId = $this->getCurrentDeviceId(); if (empty($requestId) || empty($deviceId)) return; $returnData = $this->extractReplayReturnData($request, $result); $params = array(date('Y-m-d H:i:s'), 'COMPLETED', Zend_Json::encode($returnData), $current_user->id, $deviceId, $requestId); $adb->pquery("UPDATE {$this->table_requests} SET completion_date = ?, status = ?, return_data = ? WHERE userid = ? AND deviceid = ? AND requestid = ?", $params); } protected function extractReplayReturnData($request, $result) { $wsname = $request['wsname']; $return = array(); switch ($wsname) { case 'WriteComment': $return = $result['records'][0]; break; } return $return; } // crmv@106521e public function cleanReplayTable($userid = null) { global $adb, $table_prefix, $current_user; $pruneHours = 6; // delete stuff older than 6 hours $sql = "DELETE FROM {$this->table_requests} WHERE request_date < ?"; $params = array(date('Y-m-d H:i:s', time()-$pruneHours*3600)); if ($userid > 0) { $sql .= " AND userid = ?"; $params[] = $userid; } $res = $adb->pquery($sql, $params); } //crmv@95788e // crmv@161368 /** * Logout the user from the app at the next access */ public function remoteWipe($userid) { global $adb; $now = date('Y-m-d H:i:s'); $adb->pquery("DELETE FROM {$this->table_wipedata} WHERE userid = ?", array($userid)); $adb->pquery("INSERT INTO {$this->table_wipedata} (userid, wipe_date) VALUES (?,?)", array($userid, $now)); } /** * Get the wipe date for the specified user */ public function getWipeDate($userid) { global $adb; $res = $adb->pquery("SELECT wipe_date FROM {$this->table_wipedata} WHERE userid = ?", array($userid)); if ($res && $adb->num_rows($res) > 0) { return $adb->query_result_no_html($res, 0, 'wipe_date'); } return false; } // crmv@161368e /** * Convert field values from VTE style to App style * If $onlydisplay == true, the field is formatted for display only */ public function field2Touch($module, $fieldname, $fieldvalue, $onlydisplay = false, &$focus = null, $fieldinfo = array()) { global $log, $adb, $table_prefix, $current_user, $site_URL, $touchUtils; if (empty($fieldinfo)) { $fields = $touchUtils->getModuleFields($module, $current_user->id); if (!is_array($fields)) return $fieldvalue; $fieldinfo = $fields[$fieldname]; } if (is_array($fieldinfo)) { $type = $fieldinfo['type']['name']; switch ($type) { case 'reference': $fieldvalue = intval($fieldvalue); if ($fieldvalue > 0) { // trovo il modulo collegato (dato che possono essere multipli) // crmv@136394 $refersTo = $fieldinfo['type']['refersTo'][0]; if ($refersTo == 'Users') { $displayname = getOwnerName($fieldvalue); $fieldvalue = array('crmid'=>$fieldvalue, 'display'=>$displayname, 'setype'=>$refersTo); } else { $setype = getSalesEntityType($fieldvalue); if (!empty($setype) && in_array($setype, $fieldinfo['type']['refersTo'])) { $displayname = $touchUtils->getEntityNameFromFields($setype, $fieldvalue); $fieldvalue = array('crmid'=>$fieldvalue, 'display'=>$displayname, 'setype'=>$setype); // crmv@147766 } elseif ($refersTo == 'DocumentFolders') { $folderinfo = getEntityFolder($fieldvalue); if ($folderinfo) { $displayname = getTranslatedString($folderinfo['foldername'], 'Documents'); } $fieldvalue = strval($fieldvalue); } // crmv@147766e } // crmv@136394e } if ($onlydisplay) { $fieldvalue = (empty($displayname) ? '' : $displayname); } break; // crmv@167740 case 'time': if ($fieldinfo['uitype'] == 73) { require_once('modules/SDK/src/73/73Utils.php'); $uitypeTimeUtils = UitypeTimeUtils::getInstance(); $fieldvalue = $uitypeTimeUtils->seconds2Time($fieldvalue); } break; // crmv@167740e case 'date': // reverse date format if (preg_match('/\d{2}-\d{2}\-\d{4}/', $fieldvalue)) { $ndate = date_parse_from_format('d-m-Y', $fieldvalue); $fieldvalue = $ndate['year'].'-'.$ndate['month'].'-'.$ndate['day']; } break; case 'picklist': if ($onlydisplay) $fieldvalue = getTranslatedString($fieldvalue, $module); break; case 'picklistmultilanguage': if ($onlydisplay) { $plid = $fieldvalue; $fieldvalue = Picklistmulti::getTranslatedPicklist($plid, $fieldname); } break; case 'multipicklist': if ($onlydisplay) { $values = explode(' |##| ', $fieldvalue); if (count($values) > 0) { foreach ($values as $k=>$v) $values[$k] = getTranslatedString($v, $module); } $fieldvalue = implode(', ', $values); } else { $fieldvalue = str_replace(' |##| ', ',', $fieldvalue); } break; case 'boolean': if ($onlydisplay) { $fieldvalue = getTranslatedString(($fieldvalue ? 'LBL_YES' : 'LBL_NO'), 'APP_STRINGS'); } break; case 'owner': if ($onlydisplay) { $fieldvalue = $touchUtils->getOwnerName($fieldvalue); // crmv@148861 } break; case 'file': // fix internal link if (!empty($fieldvalue) && $focus && in_array($module, array('Documents', 'Myfiles')) && in_array($focus->column_fields['filelocationtype'], array('I', 'B')) && $focus->column_fields['filestatus'] == 1) { // get direct link $res = $adb->pquery("select * from {$table_prefix}_seattachmentsrel inner join {$table_prefix}_attachments on {$table_prefix}_attachments.attachmentsid = {$table_prefix}_seattachmentsrel.attachmentsid where {$table_prefix}_seattachmentsrel.crmid = ?", array($focus->column_fields['record_id'])); if ($res && $adb->num_rows($res) > 0) { $attid = $adb->query_result_no_html($res, 0, 'attachmentsid'); $name = $adb->query_result_no_html($res, 0, 'name'); $filepath = $adb->query_result($res, 0, 'path'); $saved_filename = $attid."_".$name; $fieldvalue = $site_URL."/".$filepath.$saved_filename; //$fieldvalue = $site_URL."/index.php?module=uploads&action=downloadfile&fileid={$attid}&entityid={$focus->column_fields['record_id']}"; } } break; } // crmv@67656 crmv@187823 - calendar, masked fields if ($module == 'Events' && $focus && !empty($focus->column_fields['visibility'])) { if (empty($focus->column_fields['assigned_user_id']) && !empty($focus->column_fields['smownerid'])) { $focus->column_fields['assigned_user_id'] = $focus->column_fields['smownerid']; } if ($focus->isFieldMasked($focus->column_fields['record_id'], $fieldname, $focus->column_fields)) { if ($fieldname == 'subject') { $fieldvalue = getTranslatedString('Private Event', 'Calendar'); } else { $fieldvalue = ''; } } } // crmv@67656e crmv@187823e // campi documenti if (in_array($module, array('Documents', 'Myfiles')) && $fieldname == 'filesize' && !empty($fieldvalue)) { if ($fieldvalue < 1024) $fieldvalue = $fieldvalue.' B'; elseif ($fieldvalue > 1024 && $fieldvalue < 1048576) $fieldvalue = round($fieldvalue/1024,2).' KB'; else $fieldvalue = round($fieldvalue/(1024*1024),2).' MB'; } // campi cifrati if ($fieldinfo['uitype'] == 208) { $fieldvalue = "-- ".getTranslatedString('LBL_CIPHERED', 'APP_STRINGS')." --"; // crmv@99131 } elseif ($fieldinfo['uitype'] == 206) { $res = $adb->pquery("select reportname from {$table_prefix}_report where reportid = ?", array($fieldvalue)); if ($res) { $fieldvalue = $adb->query_result_no_html($res, 0, 'reportname'); } } // crmv@99131e // crmv@187823 - organizer if ($fieldinfo['uitype'] == 49) { require_once('modules/SDK/src/49/OrganizerField.php'); $ofield = OrganizerField::getInstance($module, $fieldname); $val = $ofield->getValue($focus->column_fields['record_id']); $fieldvalue = $ofield->getDisplayValue($val); $fieldvalue = strip_tags($fieldvalue); } // crmv@187823e // descrizione email, use the cleaned_body, avoiding the expensive call to magicHTML when possible if ($module == 'Messages' && $fieldname == 'description') { if ($focus) { if (empty($focus->column_fields['cleaned_body'])) { $attachments_info = $focus->getAttachmentsInfo(); $message_data = array('other'=>$attachments_info); $magicHTML = $focus->magicHTML($fieldvalue, $focus->column_fields['xuid'], $message_data); $description = $magicHTML['html']; $content_ids = $magicHTML['content_ids']; // save them $focus->saveCleanedBody($focus->id, $description, $content_ids); $focus->column_fields['cleaned_body'] = $description; } $fieldvalue = $focus->column_fields['cleaned_body']; } // fix for double encoding $fieldvalue = str_replace(array('&gt;', '&lt;'), array('>', '<'), $fieldvalue); } } return $fieldvalue; } /** * Convert field values from App style to VTE style */ public function touch2Field($module, $fieldname, $fieldvalue, $fieldinfo = array()) { global $current_user, $touchUtils; if (empty($fieldinfo)) { $fields = $touchUtils->getModuleFields($module, $current_user->id); if (!is_array($fields)) return $fieldvalue; $fieldinfo = $fields[$fieldname]; } if (is_array($fieldinfo)) { $type = $fieldinfo['type']['name']; switch ($type) { case 'owner': $fieldvalue = intval($fieldvalue); if ($fieldvalue <= 0) $fieldvalue = 1; // assegno ad admin in caso di dato non valido // crmv@34947 try { $otype = vtws_getOwnerType($fieldvalue); } catch (Exception $e) { $otype = 'Users'; } $fieldvalue = vtws_getWebserviceEntityId($otype, $fieldvalue); // crmv@34947e break; // crmv@167740 case 'time': if ($fieldinfo['uitype'] == 73) { require_once('modules/SDK/src/73/73Utils.php'); $uitypeTimeUtils = UitypeTimeUtils::getInstance(); $fieldvalue = $uitypeTimeUtils->time2Seconds($fieldvalue); } break; // crmv@167740e // crmv@121586 case 'date': $time = strtotime($fieldvalue); if ($time !== false) { $fieldvalue = date('Y-m-d', $time); } else { // old method $fieldvalue = substr(str_replace('T', ' ', $fieldvalue), 0, 10); } break; case 'datetime': case 'timestamp': $time = strtotime($fieldvalue); if ($time !== false) { $fieldvalue = date('Y-m-d H:i:s', $time); } else { // old method $fieldvalue = str_replace('T', ' ', $fieldvalue); } break; // crmv@121586e // gli id arrivano in formato ID, non webservice case 'reference': { $fieldvalue = intval($fieldvalue); // crmv@159113 if ($fieldvalue != 0) { if ($fieldvalue < 0) { // temporary id, find the correct crmid $fieldvalue = $this->getRealId($current_user->id, null, $fieldvalue); } // trovo il modulo collegato (dato che possono essere multipli) if ($fieldinfo['type']['refersTo'][0] == 'DocumentFolders') { $setype = 'DocumentFolders'; // crmv@153778 } elseif (in_array($fieldinfo['uitype'], array(50,51,52))) { $setype = 'Users'; // crmv@153778e } elseif ($fieldvalue > 0) { // crmv@186151 if ($fieldinfo['uitype'] == 117) { $setype = 'Currency'; } else { $setype = getSalesEntityType($fieldvalue); if (empty($setype)) $setype = $fieldinfo['type']['refersTo'][0]; // for Currency and maybe others } // crmv@186151e } if (!empty($setype) && in_array($setype, $fieldinfo['type']['refersTo'])) { $fieldvalue = vtws_getWebserviceEntityId($setype, $fieldvalue); } } else { $fieldvalue = ''; } // crmv@159113e break; } case 'multipicklist': // TODO: e se i valori contengono la virgola? $fieldvalue = str_replace(',', ' |##| ', $fieldvalue); break; case 'boolean': $fieldvalue = ($fieldvalue ? 'on' : 'off'); break; default: $fieldvalue = str_replace('&', '&', $fieldvalue); break; } } return $fieldvalue; } /** * The standard vtlib handler */ function vtlib_handler($moduleName, $event_type) { global $adb, $table_prefix; if($event_type == 'module.postinstall') { $adb->pquery("UPDATE {$table_prefix}_tab SET customized = 0 WHERE name=?", array($moduleName)); $this->initDefaultProperties(); } else if($event_type == 'module.disabled') { // TODO Handle actions when this module is disabled. } else if($event_type == 'module.enabled') { // TODO Handle actions when this module is enabled. } else if($event_type == 'module.preuninstall') { // TODO Handle actions when this module is about to be deleted. } else if($event_type == 'module.preupdate') { // TODO Handle actions before this module is updated. } else if($event_type == 'module.postupdate') { $this->initDefaultProperties(); } } }