├── README.md ├── doc └── images │ └── vtiger-user-access-key.png ├── examples └── php │ ├── RestfulApi.php │ ├── RestfulApiCalls.php │ └── index.php └── src ├── languages ├── en_us │ └── RestfulApi.php └── fr_fr │ └── RestfulApi.php ├── manifest.xml └── modules └── RestfulApi ├── .htaccess ├── RestfulApi.php ├── actions ├── Api.php └── Auth.php ├── index.php ├── models └── Rest.php └── schema.xml /README.md: -------------------------------------------------------------------------------- 1 | # VTRestfulAPI 2 | 3 | RestfulApi is a Vtiger module which allows to communicate with your CRM using a REST API. 4 | 5 | ## Installation 6 | 7 | Simply install RestfulApi like a classic Vtiger module. 8 | 9 | __CRM Settings > Modules > Install from zip__ 10 | 11 | ## Use 12 | 13 | You can easily communicate with your CRM using the REST protocol. 14 | 15 | **GET:** Authentificate an user / Retrieve an entity or a list of entities / Retrieve PickList values 16 | 17 | **POST:** Add an entity 18 | 19 | **PUT:** Update an entity 20 | 21 | **DELETE:** Remove an entity 22 | 23 | ### Authentification 24 | 25 | To communicate with your CRM you must be authentificated. 26 | 27 | There are 2 solutions to login an user: 28 | 29 | 1. __With login and password__ 30 | 31 | ``` 32 | http://yourcrm.com/modules/RestfulApi/auth/LOGIN/PASSWORD 33 | 34 | or 35 | 36 | http://yourcrm.com/modules/RestfulApi/index.php?module=Auth&login=LOGIN&password=PASSWORD 37 | ``` 38 | 39 | *Replace LOGIN and PASSWORD by your credentials.* 40 | 41 | 42 | 2. __With access Key__ 43 | 44 | ``` 45 | http://yourcrm.com/modules/RestfulApi/auth/USER_ACCESS_KEY 46 | 47 | or 48 | 49 | http://yourcrm.com/modules/RestfulApi/index.php?module=Auth&key=USER_ACCESS_KEY 50 | ``` 51 | 52 | *Replace USER_ACCESS_KEY by the user's access key auto-generated by Vtiger.* 53 | 54 | ![User's Access Key](https://github.com/sardoj/VTRestfulAPI/blob/master/doc/images/vtiger-user-access-key.png "User's acess key") 55 | 56 | 57 | **Result:** 58 | 59 | ``` 60 | {"success":true,"result":"f7171f3d8e13326762a45641c2dd1e39"} 61 | 62 | //Token: f7171f3d8e13326762a45641c2dd1e39 63 | ``` 64 | 65 | You must use the generated token in your other calls. 66 | 67 | *N.B: With this autentification method the Vtiger ACL are respected.* 68 | 69 | ### Retrieve data (GET) 70 | 71 | We must use the generated token for all calls. 72 | In the following examples we will use the Accounts module, but you can use RestfulApi with all Vtiger modules. 73 | 74 | #### Get entities list 75 | 76 | ``` 77 | http://yourcrm.com/modules/RestfulApi/Accounts/f7171f3d8e13326762a45641c2dd1e39 78 | 79 | or 80 | 81 | http://yourcrm.com/modules/RestfulApi/index.php?module=Accounts&token=f7171f3d8e13326762a45641c2dd1e39 82 | ``` 83 | 84 | Result: 85 | 86 | ``` 87 | {"success":true,"result":[{"accountname":"MEDIA 3+","account_no":"ACC1","phone":"","website":"","fax":"","tickersymbol":"","otherphone":"","account_id":"0","email1":"","employees":"0","email2":"","ownership":"","rating":"","industry":"","siccode":"","accounttype":"","annual_revenue":"0.00000000","emailoptout":"0","notify_owner":"0","assigned_user_id":"1","createdtime":"2016-10-28 14:40:37","modifiedtime":"2017-09-04 09:29:15","modifiedby":"1","bill_street":"34 all\u00e9e des Fr\u00eanes","ship_street":"34 all\u00e9e des Fr\u00eanes","bill_city":"Champs sur Marne","ship_city":"Champs sur Marne","bill_state":"","ship_state":"","bill_code":"77420","ship_code":"77420","bill_country":"France","ship_country":"France","bill_pobox":"","ship_pobox":"","description":"","campaignrelstatus":"","isconvertedfromlead":"0","record_id":"3","record_module":"Accounts","api_date_now":"2017-09-04 09:29:22"},{"accountname":"MEDIACRM","account_no":"ACC2","phone":"","website":"www.mediacrm.fr","fax":"","tickersymbol":"","otherphone":"","account_id":"0","email1":"","employees":"0","email2":"","ownership":"","rating":"","industry":"","siccode":"","accounttype":"","annual_revenue":"0.00000000","emailoptout":"0","notify_owner":"0","assigned_user_id":"1","createdtime":"2017-09-04 09:28:27","modifiedtime":"2017-09-04 09:28:27","modifiedby":"1","bill_street":"","ship_street":"","bill_city":"","ship_city":"","bill_state":"","ship_state":"","bill_code":"","ship_code":"","bill_country":"","ship_country":"","bill_pobox":"","ship_pobox":"","description":"","campaignrelstatus":"","isconvertedfromlead":"0","record_id":"24","record_module":"Accounts","api_date_now":"2017-09-04 09:29:22"}]} 88 | ``` 89 | 90 | #### Get entities list with pagination 91 | 92 | You can manage pagination through the API. By default only the 100 first entities are returned. 93 | 94 | You can use ```start```, ```length```, and ```order```params to manage the pagination. 95 | 96 | ``` 97 | http://yourcrm.com/modules/RestfulApi/Accounts/start/0/length/2/order/vtiger_account.accountname%20DESC/f7171f3d8e13326762a45641c2dd1e39 98 | 99 | or 100 | 101 | http://yourcrm.com/modules/RestfulApi/index.php?module=Accounts&start=0&length=2&order=vtiger_account.accountname%20DESC&token=f7171f3d8e13326762a45641c2dd1e39 102 | ``` 103 | 104 | Result: 105 | 106 | ``` 107 | {"success":true,"result":[{"accountname":"MEDIACRM","account_no":"ACC2","phone":"","website":"www.mediacrm.fr","fax":"","tickersymbol":"","otherphone":"","account_id":"0","email1":"","employees":"0","email2":"","ownership":"","rating":"","industry":"","siccode":"","accounttype":"","annual_revenue":"0.00000000","emailoptout":"0","notify_owner":"0","assigned_user_id":"1","createdtime":"2017-09-04 09:28:27","modifiedtime":"2017-09-04 09:28:27","modifiedby":"1","bill_street":"","ship_street":"","bill_city":"","ship_city":"","bill_state":"","ship_state":"","bill_code":"","ship_code":"","bill_country":"","ship_country":"","bill_pobox":"","ship_pobox":"","description":"","campaignrelstatus":"","isconvertedfromlead":"0","record_id":"24","record_module":"Accounts","api_date_now":"2017-09-04 10:14:38"},{"accountname":"MEDIA 3+","account_no":"ACC1","phone":"","website":"","fax":"","tickersymbol":"","otherphone":"","account_id":"0","email1":"","employees":"0","email2":"","ownership":"","rating":"","industry":"","siccode":"","accounttype":"","annual_revenue":"0.00000000","emailoptout":"0","notify_owner":"0","assigned_user_id":"1","createdtime":"2016-10-28 14:40:37","modifiedtime":"2017-09-04 09:29:15","modifiedby":"1","bill_street":"34 all\u00e9e des Fr\u00eanes","ship_street":"34 all\u00e9e des Fr\u00eanes","bill_city":"Champs sur Marne","ship_city":"Champs sur Marne","bill_state":"","ship_state":"","bill_code":"77420","ship_code":"77420","bill_country":"France","ship_country":"France","bill_pobox":"","ship_pobox":"","description":"","campaignrelstatus":"","isconvertedfromlead":"0","record_id":"3","record_module":"Accounts","api_date_now":"2017-09-04 10:14:38"}]} 108 | ``` 109 | 110 | #### Search entities list 111 | 112 | Add criteria to the SQL query. You can concatenate several criteria with a semicolon. 113 | 114 | One criterium pattern : ```table.column:value``` 115 | 116 | Several criteria pattern : ```table.column1:value1;table.column2:value2;...;table.columnN:valueN``` 117 | 118 | ``` 119 | http://yourcrm.com/modules/RestfulApi/Accounts/criteria/vtiger_account.accountname:MEDIACRM;vtiger_account.website:www.mediacrm.fr/f7171f3d8e13326762a45641c2dd1e39 120 | 121 | or 122 | 123 | http://yourcrm.com/modules/RestfulApi/index.php?module=Accounts&criteria=vtiger_account.accountname:MEDIACRM;vtiger_account.website:www.mediacrm.fr&token=f7171f3d8e13326762a45641c2dd1e39 124 | ``` 125 | 126 | Result: 127 | 128 | ``` 129 | {"success":true,"result":[{"accountname":"MEDIACRM","account_no":"ACC2","phone":"","website":"www.mediacrm.fr","fax":"","tickersymbol":"","otherphone":"","account_id":"0","email1":"","employees":"0","email2":"","ownership":"","rating":"","industry":"","siccode":"","accounttype":"","annual_revenue":"0.00000000","emailoptout":"0","notify_owner":"0","assigned_user_id":"1","createdtime":"2017-09-04 09:28:27","modifiedtime":"2017-09-04 09:28:27","modifiedby":"1","bill_street":"","ship_street":"","bill_city":"","ship_city":"","bill_state":"","ship_state":"","bill_code":"","ship_code":"","bill_country":"","ship_country":"","bill_pobox":"","ship_pobox":"","description":"","campaignrelstatus":"","isconvertedfromlead":"0","record_id":"24","record_module":"Accounts","api_date_now":"2017-09-04 09:47:16"}]} 130 | ``` 131 | 132 | #### Retrieve entity by ID 133 | 134 | In the following example we retrieve the account with ID = 3. 135 | 136 | ``` 137 | http://yourcrm.com/modules/RestfulApi/Accounts/3/f7171f3d8e13326762a45641c2dd1e39 138 | 139 | or 140 | 141 | http://yourcrm.com/modules/RestfulApi/index.php?module=Accounts&id=3&token=f7171f3d8e13326762a45641c2dd1e39 142 | ``` 143 | 144 | Result: 145 | 146 | ``` 147 | {"success":true,"result":{"accountname":"MEDIA 3+","account_no":"ACC1","phone":"","website":"","fax":"","tickersymbol":"","otherphone":"","account_id":"0","email1":"","employees":"0","email2":"","ownership":"","rating":"","industry":"","siccode":"","accounttype":"","annual_revenue":"0.00000000","emailoptout":"0","notify_owner":"0","assigned_user_id":"1","createdtime":"2016-10-28 14:40:37","modifiedtime":"2017-09-04 09:29:15","modifiedby":"1","bill_street":"34 all\u00e9e des Fr\u00eanes","ship_street":"34 all\u00e9e des Fr\u00eanes","bill_city":"Champs sur Marne","ship_city":"Champs sur Marne","bill_state":"","ship_state":"","bill_code":"77420","ship_code":"77420","bill_country":"France","ship_country":"France","bill_pobox":"","ship_pobox":"","description":"","campaignrelstatus":"","isconvertedfromlead":"0","record_id":"3","record_module":"Accounts","api_date_now":"2017-09-04 09:53:04"}} 148 | ``` 149 | 150 | #### Retrieve PickList values 151 | 152 | You can retrieve the values from a PickList of a module. 153 | Simply specify the module and the PickList fieldname. 154 | 155 | 156 | ``` 157 | http://yourcrm.com/modules/RestfulApi/Accounts/picklist/rating/f7171f3d8e13326762a45641c2dd1e39 158 | 159 | or 160 | 161 | http://yourcrm.com/modules/RestfulApi/index.php?module=Accounts&picklist=rating&token=f7171f3d8e13326762a45641c2dd1e39 162 | ``` 163 | 164 | Result: 165 | 166 | ``` 167 | {"success":true,"result":{"values":{"Acquired":"Acquired","Active":"Actif","Market Failed":"Market Failed","Project Cancelled":"Project Cancelled","Shutdown":"Shutdown"}}} 168 | ``` 169 | 170 | You can also retrieve PickList dependencies if exists. Simply add ```&picklistdep=1``` param 171 | 172 | ``` 173 | http://yourcrm.com/modules/RestfulApi/Accounts/picklist/rating/picklistdep/1/f7171f3d8e13326762a45641c2dd1e39 174 | 175 | or 176 | 177 | http://yourcrm.com/modules/RestfulApi/index.php?module=Accounts&picklist=rating&picklistdep=1&token=f7171f3d8e13326762a45641c2dd1e39 178 | ``` 179 | 180 | Result: 181 | 182 | ``` 183 | {"success":true,"result":{"values":{"Acquired":"Acquired","Active":"Actif","Market Failed":"Market Failed","Project Cancelled":"Project Cancelled","Shutdown":"Shutdown"},"dependencies":{"Acquired":{"accounttype":["Customer","Integrator","Investor","Partner","Press","Prospect","Reseller","Other"]},"__DEFAULT__":{"accounttype":["Analyst","Competitor","Customer","Integrator","Investor","Partner","Press","Prospect","Reseller","Other"]},"Active":{"accounttype":["Analyst","Competitor","Investor","Partner","Press","Prospect","Reseller","Other"]},"Market Failed":{"accounttype":["Analyst","Competitor","Customer","Integrator","Press","Prospect","Reseller","Other"]},"Project Cancelled":{"accounttype":["Analyst","Competitor","Customer","Integrator","Investor","Partner","Reseller","Other"]},"Shutdown":{"accounttype":["Analyst","Competitor","Customer","Integrator","Investor","Partner","Press","Prospect"]}}}} 184 | ``` 185 | 186 | ### Add entity (POST) 187 | 188 | We must use the generated token for all calls. 189 | In the following examples we will use the Accounts module, but you can use RestfulApi with all Vtiger modules. 190 | 191 | To create a new entity you must at least specify the mandatory fields of the module. It is not mandatory to add all other fields. 192 | 193 | To specify a field you must use its fieldname (you can find it in vtiger_field table). 194 | 195 | ``` 196 | URL: 197 | 198 | http://localhost/vtiger6.5/modules/RestfulApi/Accounts/f7171f3d8e13326762a45641c2dd1e39 199 | 200 | or 201 | 202 | http://yourcrm.com/modules/RestfulApi/index.php?module=Accounts&token=f7171f3d8e13326762a45641c2dd1e39 203 | 204 | Data to post: 205 | 206 | accountname=MediaToolBox&email1=mediatoolbox@yopmail.com&phone=+33656896325&bill_street=100 street beta test\r\nBat. B&bill_code=34000&bill_city=Montpellier&bill_country=France 207 | ``` 208 | 209 | Result: 210 | ``` 211 | {"success": true,"result": 25} 212 | 213 | //25 is the ID of the new created entity 214 | ``` 215 | 216 | ### Update entity (PUT or POST) 217 | 218 | We must use the generated token for all calls. 219 | In the following examples we will use the Accounts module, but you can use RestfulApi with all Vtiger modules. 220 | 221 | To update an entity you must specify its ID and the fields values you want to update. 222 | 223 | To specify a field you must use its fieldname (you can find it in vtiger_field table). 224 | 225 | ``` 226 | URL: 227 | 228 | http://localhost/vtiger6.5/modules/RestfulApi/Accounts/25/f7171f3d8e13326762a45641c2dd1e39 229 | 230 | or 231 | 232 | http://yourcrm.com/modules/RestfulApi/index.php?module=Accounts&id=25&token=f7171f3d8e13326762a45641c2dd1e39 233 | 234 | Data to post: 235 | 236 | accountname=MediaToolBox2&email1=mediatoolbox2@yopmail.com 237 | ``` 238 | 239 | Result: 240 | ``` 241 | {"success": true,"result": 25} 242 | 243 | //25 is the ID of the updated entity 244 | ``` 245 | 246 | ### Delete entity (DELETE) 247 | 248 | We must use the generated token for all calls. 249 | In the following examples we will use the Accounts module, but you can use RestfulApi with all Vtiger modules. 250 | 251 | To remove an entity you must specify its ID. 252 | 253 | ``` 254 | http://yourcrm.com/modules/RestfulApi/Accounts/25/f7171f3d8e13326762a45641c2dd1e39 255 | 256 | or 257 | 258 | http://yourcrm.com/modules/RestfulApi/index.php?module=Accounts&id=25&token=f7171f3d8e13326762a45641c2dd1e39 259 | ``` 260 | 261 | Result: 262 | 263 | ``` 264 | {"success": true,"result": true} 265 | ``` 266 | -------------------------------------------------------------------------------- /doc/images/vtiger-user-access-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sardoj/VTRestfulAPI/312aa03353d518633e941bfa3640f9f4ee6079bd/doc/images/vtiger-user-access-key.png -------------------------------------------------------------------------------- /examples/php/RestfulApi.php: -------------------------------------------------------------------------------- 1 | crmUrl = $crmUrl; 29 | 30 | $url = $this->crmUrl . 'modules/RestfulApi/index.php?module=Auth&login=' . $userLogin . '&password=' . $userPassword; 31 | 32 | $response = $this->callUrl($url); 33 | 34 | if($response->success) 35 | { 36 | $this->token = $response->result; 37 | 38 | $result = $this->token; 39 | } 40 | else 41 | { 42 | $result = $response; 43 | } 44 | 45 | return $result; 46 | } 47 | 48 | public function signInByKey($crmUrl, $userAccessKey) 49 | { 50 | $result = false; 51 | 52 | if(substr($crmUrl, -1) != '/') 53 | { 54 | $crmUrl .= '/'; 55 | } 56 | 57 | $this->crmUrl = $crmUrl; 58 | 59 | $url = $this->crmUrl . 'modules/RestfulApi/index.php?module=Auth&key=' . $userAccessKey; 60 | 61 | $response = $this->callUrl($url); 62 | 63 | if($response->success) 64 | { 65 | $this->token = $response->result; 66 | 67 | $result = $this->token; 68 | } 69 | else 70 | { 71 | $result = $response; 72 | } 73 | 74 | return $result; 75 | } 76 | 77 | public function setToken($crmUrl, $token) 78 | { 79 | if(substr($crmUrl, -1) != '/') 80 | { 81 | $crmUrl .= '/'; 82 | } 83 | 84 | $this->crmUrl = $crmUrl; 85 | $this->token = $token; 86 | } 87 | 88 | public function call($url_params, $a_params=array()) 89 | { 90 | if(!preg_match('`&token=`', $url)) 91 | { 92 | $url_params .= '&token=' . $this->token; 93 | } 94 | 95 | $crmUrl = $this->crmUrl; 96 | 97 | if(!preg_match('`^' . $crmUrl . '`', $url_params)) 98 | { 99 | $url = $this->crmUrl . "modules/RestfulApi/index.php?" . $url_params; 100 | } 101 | else 102 | { 103 | $url = $url_params; 104 | } 105 | 106 | return $this->callUrl($url, $a_params); 107 | } 108 | 109 | protected function callUrl($url, $a_params=array()) 110 | { 111 | //Use Curl 112 | $ch = curl_init(); 113 | 114 | if(!empty($a_params) && $a_params != "DELETE") 115 | { 116 | //Sanitize params 117 | $a_params = $this->sanitizeParams($a_params); 118 | 119 | //url-ify the data for the POST 120 | $params_string = ""; 121 | foreach ($a_params as $key => $value) { 122 | $params_string .= $key . '=' . $value . '&'; 123 | } 124 | rtrim($params_string, '&'); 125 | 126 | 127 | curl_setopt($ch, CURLOPT_POST, count($a_params)); 128 | curl_setopt($ch, CURLOPT_POSTFIELDS, $params_string); 129 | } 130 | 131 | //If there is ?id= or &id= in the url, set PUT method 132 | if(preg_match('/[?|&]id=/', $url) && $a_params=="DELETE") 133 | { 134 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); 135 | } 136 | elseif(preg_match('/[?|&]id=/', $url) && !empty($a_params)) 137 | { 138 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT"); 139 | } 140 | 141 | //Set the url, number of POST vars, POST data 142 | curl_setopt($ch, CURLOPT_URL, $url); 143 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 144 | curl_setopt($ch, CURLOPT_HEADER, 0); 145 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); 146 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); 147 | 148 | //Execute request 149 | $result = curl_exec($ch); 150 | //echo 'Curl error: ' . curl_error($ch); 151 | 152 | //Close connection 153 | curl_close($ch); 154 | 155 | $o_result = json_decode($result); 156 | 157 | return $o_result; 158 | } 159 | 160 | protected function sanitizeParams($a_params) 161 | { 162 | if(is_array($a_params)) 163 | { 164 | foreach ($a_params as $key => $value) { 165 | $a_params[$key] = str_replace( 166 | array( 167 | "'", 168 | "+", 169 | "/" 170 | ), 171 | array( 172 | "%27", 173 | "%2B", 174 | "\/" 175 | ), 176 | $value 177 | ); 178 | } 179 | } 180 | 181 | return $a_params; 182 | } 183 | } -------------------------------------------------------------------------------- /examples/php/RestfulApiCalls.php: -------------------------------------------------------------------------------- 1 | api = new RestfulApi(); 23 | } 24 | 25 | public function setToken($crmUrl, $token) 26 | { 27 | return $this->api->setToken($crmUrl, $token); 28 | } 29 | 30 | public function signInByLoginPassword($crmUrl, $userLogin, $userPassword) 31 | { 32 | return $this->api->signInByLoginPassword($crmUrl, $userLogin, $userPassword); 33 | } 34 | 35 | public function signInByKey($crmUrl, $userAccessKey) 36 | { 37 | return $this->api->signInByKey($crmUrl, $userAccessKey); 38 | } 39 | 40 | public function getEntities($moduleName, $criteria="", $start=0, $length=20, $order="CE.createdtime DESC") 41 | { 42 | $url_params = "module=$moduleName&start=$start&length=$length&order=".urlencode($order)."&criteria=$criteria"; 43 | 44 | $o_response = $this->api->call($url_params); 45 | 46 | if($o_response->success) 47 | { 48 | $a_entities = $o_response->result; 49 | } 50 | else 51 | { 52 | $a_entities = $o_response; 53 | } 54 | 55 | return $a_entities; 56 | } 57 | 58 | public function getEntityById($moduleName, $id) 59 | { 60 | $o_entity = null; 61 | 62 | $url_params = "module=$moduleName&id=$id"; 63 | 64 | $o_response = $this->api->call($url_params); 65 | 66 | if($o_response->success) 67 | { 68 | $o_entity = $o_response->result; 69 | } 70 | else 71 | { 72 | $o_entity = $o_response; 73 | } 74 | 75 | return $o_entity; 76 | } 77 | 78 | public function getPickListValues($moduleName, $pickListName, $getDependencies=false) 79 | { 80 | $url_params = "module=$moduleName&picklist=$pickListName"; 81 | 82 | if($getDependencies) 83 | { 84 | $url_params .= '&picklistdep=1'; 85 | } 86 | 87 | $o_response = $this->api->call($url_params); 88 | 89 | if($o_response->success) 90 | { 91 | $return = $o_response->result; 92 | } 93 | else 94 | { 95 | $return = $o_response; 96 | } 97 | 98 | return $return; 99 | } 100 | 101 | public function createEntity($moduleName, $a_params) 102 | { 103 | $url_params = "module=$moduleName"; 104 | 105 | $o_response = $this->api->call($url_params, $a_params); 106 | 107 | if($o_response->success) 108 | { 109 | $entityId = $o_response->result; 110 | } 111 | else 112 | { 113 | $entityId = $o_response; 114 | } 115 | 116 | return $entityId; 117 | } 118 | 119 | public function updateEntity($moduleName, $id, $a_params) 120 | { 121 | $url_params = "module=$moduleName&id=$id"; 122 | 123 | $o_response = $this->api->call($url_params, $a_params); 124 | 125 | if($o_response->success) 126 | { 127 | $entityId = $o_response->result; 128 | } 129 | else 130 | { 131 | $entityId = $o_response; 132 | } 133 | 134 | return $entityId; 135 | } 136 | 137 | public function deleteEntity($moduleName, $id) 138 | { 139 | $b_deleted = false; 140 | 141 | $url_params = "module=$moduleName&id=$id"; 142 | 143 | $o_response = $this->api->call($url_params, "DELETE"); 144 | 145 | if($o_response->success && $o_response->result != false) 146 | { 147 | $b_deleted = true; 148 | } 149 | 150 | return $b_deleted; 151 | } 152 | } -------------------------------------------------------------------------------- /examples/php/index.php: -------------------------------------------------------------------------------- 1 | Token'; 30 | if(empty($_SESSION["RestfulApiToken"])) 31 | { 32 | //Signin by user access key... 33 | $token = $api->signInByKey($crmUrl, $userAccessKey); 34 | 35 | // ... or Signin by user login and password 36 | //$token = $api->signInByLoginPassword($crmUrl, $userLogin, $userPassword); 37 | 38 | if(!empty($token)) //Store token into session 39 | { 40 | $_SESSION["RestfulApiToken"] = $token; 41 | } 42 | 43 | pr($token); 44 | } 45 | else 46 | { 47 | $token = $_SESSION["RestfulApiToken"]; //Get token from session 48 | $api->setToken($crmUrl, $token); 49 | pr($token.' (from session)'); 50 | } 51 | 52 | //Get products list (only actives products) 53 | $a_products = $api->getEntities('Products', 'vtiger_products.discontinued:1'); 54 | echo '

Products

'; 55 | pr($a_products); 56 | 57 | //Get product categories 58 | $a_categories = $api->getPickListValues('Products', 'productcategory'); 59 | echo '

Product categories

'; 60 | pr($a_categories); 61 | 62 | //Get product categories with dependencies 63 | $a_categories = $api->getPickListValues('Products', 'productcategory', true); 64 | echo '

Product categories with dependencies

'; 65 | pr($a_categories); 66 | 67 | //Create a product 68 | $product = array( 69 | "productname" => "Test", 70 | "unit_price" => "20.52" 71 | ); 72 | $productId = $api->createEntity('Products', $product); 73 | echo '

New added product id

'; 74 | pr($productId); 75 | 76 | //Update product 77 | $product_update = array( 78 | "productname" => "My product", 79 | ); 80 | $productId = $api->updateEntity('Products', $productId, $product_update); 81 | echo '

Updated product id

'; 82 | pr($productId); 83 | 84 | //Get product by id 85 | $o_product = $api->getEntityById('Products', $productId); 86 | 87 | //Create a quote 88 | $quantity = 3; 89 | $vat_rate = 0.20; //20% 90 | $quote = array( 91 | "subject" => "New Quote", 92 | "account_id" => $accountId, 93 | "contact_id" => "", 94 | "quote_date" => date("Y-m-d"), 95 | "validtill" => date("Y-m-d", strtotime("+30 days", mktime())), 96 | "quotestage" => "Created", 97 | "bill_street" => "100 street of beta test", 98 | "bill_code" => "34000", 99 | "bill_city" => "Montpellier", 100 | "bill_country" => "France", 101 | "ship_street" => "100 street of beta test", 102 | "ship_code" => "34000", 103 | "ship_city" => "Montpellier", 104 | "ship_country" => "France", 105 | "display_bank_name" => "1", 106 | "display_iban" => "1", 107 | "display_swift_code" => "1", 108 | "taxtype" => "group", 109 | "discount_percentage_final" => 0, 110 | "discount_amount_final" => 0, 111 | "shipping_handling_charge" => 0, 112 | "tax1_group_percentage" => 20, 113 | "tax2_group_percentage" => 0, 114 | "tax3_group_percentage" => 0, 115 | "shtax1_sh_percent" => 0, 116 | "pre_tax_total" => $o_product->unit_price * $quantity, 117 | "adjustment" => 0, 118 | "subtotal" => $o_product->unit_price * $quantity, 119 | "total" => $o_product->unit_price * $quantity * (1 + $vat_rate), 120 | "items" => json_encode(array( 121 | array( 122 | 'hdnProductId' => $o_product->record_id, 123 | 'qty' => $quantity, 124 | 'listPrice' => $o_product->unit_price, 125 | 'comment' => $o_product->description, 126 | 'discount_amount' => 0, 127 | 'discount_percentage' => 0 128 | ) 129 | )) 130 | ); 131 | 132 | $quoteId = $api->createEntity('Quotes', $quote); 133 | echo '

New added quote id

'; 134 | pr($quoteId); 135 | 136 | //Delete quote 137 | $result = $api->deleteEntity('Quotes', $quoteId); 138 | echo '

Delete quote

'; 139 | pr($result); 140 | 141 | 142 | 143 | function pr($data) 144 | { 145 | echo '
';
146 |     print_r($data);
147 |     echo '
'; 148 | } -------------------------------------------------------------------------------- /src/languages/en_us/RestfulApi.php: -------------------------------------------------------------------------------- 1 | 'Api Tokens', 13 | 'SINGLE_RestfulApi' => 'Api Token', 14 | 'LBL_BLOCK_GENERAL_INFORMATION' => 'General information', 15 | 'LBL_BLOCK_SYSTEM_INFORMATION' => 'System information', 16 | 'LBL_TOKEN' => 'Token', 17 | 'LBL_IP' => 'IP', 18 | 'LBL_EXPIRATION_DATE' => 'Expiration date', 19 | 'LBL_CALLS' => 'Calls', 20 | 21 | ); 22 | 23 | ?> 24 | -------------------------------------------------------------------------------- /src/languages/fr_fr/RestfulApi.php: -------------------------------------------------------------------------------- 1 | 'Api Tokens', 13 | 'SINGLE_RestfulApi' => 'Api Token', 14 | 'LBL_BLOCK_GENERAL_INFORMATION' => 'Informations générales', 15 | 'LBL_BLOCK_SYSTEM_INFORMATION' => 'Informations système', 16 | 'LBL_TOKEN' => 'Token', 17 | 'LBL_IP' => 'IP', 18 | 'LBL_EXPIRATION_DATE' => 'Date d\'expiration', 19 | 'LBL_CALLS' => 'Appels', 20 | 21 | ); 22 | 23 | ?> 24 | -------------------------------------------------------------------------------- /src/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2017-09-04 07:01:35 4 | RestfulApi 5 | 6 | 7 | ion ion-ios-pricetags 8 | 1.0.0 9 | 10 | 6.0.0 11 | 12 | 13 | 14 | vtiger_restfulapi 15 | 24 |
25 | 26 | vtiger_restfulapicf 27 | 31 |
32 |
33 | 34 | 35 | 36 | 37 | 38 | token 39 | 1 40 | token 41 | vtiger_restfulapi 42 | 1 43 | LBL_TOKEN 44 | 1 45 | 2 46 | 47 | 1 48 | 100 49 | V~O 50 | 0 51 | 0 52 | 1 53 | BAS 54 | 55 | 1 56 | 0 57 | 58 | restfulapiid 59 | restfulapiid 60 | 61 | 62 | 63 | ip 64 | 1 65 | ip 66 | vtiger_restfulapi 67 | 1 68 | LBL_IP 69 | 1 70 | 2 71 | 72 | 2 73 | 100 74 | V~O 75 | 0 76 | 0 77 | 1 78 | BAS 79 | 80 | 1 81 | 0 82 | 83 | 84 | calls 85 | 7 86 | calls 87 | vtiger_restfulapi 88 | 1 89 | LBL_CALLS 90 | 1 91 | 2 92 | 0 93 | 3 94 | 100 95 | I~O 96 | 1 97 | 98 | 1 99 | BAS 100 | 101 | 1 102 | 0 103 | 104 | 105 | expiration_date 106 | 70 107 | expiration_date 108 | vtiger_restfulapi 109 | 2 110 | LBL_EXPIRATION_DATE 111 | 1 112 | 2 113 | 114 | 4 115 | 100 116 | DT~O 117 | 0 118 | 0 119 | 1 120 | BAS 121 | 122 | 1 123 | 0 124 | 125 | 126 | user_id 127 | 7 128 | user_id 129 | vtiger_restfulapi 130 | 1 131 | LBL_USER_ID 132 | 1 133 | 2 134 | 135 | 5 136 | 100 137 | N~O 138 | 1 139 | 140 | 1 141 | BAS 142 | 143 | 1 144 | 0 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | assigned_user_id 153 | 53 154 | smownerid 155 | vtiger_crmentity 156 | 1 157 | Assigned To 158 | 1 159 | 2 160 | 161 | 1 162 | 100 163 | V~M 164 | 0 165 | 0 166 | 1 167 | BAS 168 | 169 | 1 170 | 0 171 | 172 | 173 | createdtime 174 | 70 175 | createdtime 176 | vtiger_crmentity 177 | 1 178 | Created Time 179 | 1 180 | 2 181 | 182 | 2 183 | 100 184 | T~O 185 | 0 186 | 0 187 | 2 188 | BAS 189 | 190 | 0 191 | 0 192 | 193 | 194 | modifiedtime 195 | 70 196 | modifiedtime 197 | vtiger_crmentity 198 | 1 199 | Modified Time 200 | 1 201 | 2 202 | 203 | 3 204 | 100 205 | DT~O 206 | 0 207 | 0 208 | 2 209 | BAS 210 | 211 | 0 212 | 0 213 | 214 | 215 | 216 | 217 | 218 | 219 | All 220 | true 221 | false 222 | 223 | 224 | token 225 | 1 226 | 227 | 228 | ip 229 | 2 230 | 231 | 232 | calls 233 | 3 234 | 235 | 236 | expiration_date 237 | 4 238 | 239 | 240 | 241 | 242 | 243 | public_readwritedelete 244 | 245 | 246 | 247 | 248 | enabled 249 | 250 | 251 | 252 | enabled 253 | 254 | 255 | 256 | enabled 257 | 258 | 259 | 260 | 261 | DETAILVIEWBASIC 262 | View History 263 | 264 | 265 | 0 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 |
274 | -------------------------------------------------------------------------------- /src/modules/RestfulApi/.htaccess: -------------------------------------------------------------------------------- 1 | Options -Indexes 2 | Options +FollowSymlinks 3 | 4 | RewriteEngine on 5 | RewriteCond %{REQUEST_FILENAME} -s [OR] 6 | RewriteCond %{REQUEST_FILENAME} -l [OR] 7 | RewriteCond %{REQUEST_FILENAME} -d 8 | RewriteRule ^.*$ - [NC,L] 9 | 10 | RewriteRule ^[auth|Auth]+/([^/]+)$ index.php?module=Auth&key=$1 [L] 11 | RewriteRule ^[auth|Auth]+/([^/]+)/([^/]+)$ index.php?module=Auth&login=$1&password=$2 [L] 12 | RewriteRule ^([^/]+)/([0-9]+)/([a-zA-Z0-9]{32})$ index.php?module=$1&id=$2&token=$3 [L] 13 | RewriteRule ^([^/]+)/criteria/([^/]+)/([a-zA-Z0-9]{32})$ index.php?module=$1&token=$3&criteria=$2 [L] 14 | RewriteRule ^([^/]+)/criteria/([^/]+)/(.+)/([a-zA-Z0-9]{32})$ index.php?module=$1&token=$4&criteria=$2¶ms=$3 [L] 15 | RewriteRule ^([^/]+)/picklist/([^/]+)/([a-zA-Z0-9]{32})$ index.php?module=$1&token=$3&picklist=$2 [L] 16 | RewriteRule ^([^/]+)/action/([^/]+)/(.+)/([a-zA-Z0-9]{32})$ index.php?module=$1&action=$2&token=$4¶ms=$3 [L] 17 | RewriteRule ^([^/]+)/(.+)/([a-zA-Z0-9]{32})$ index.php?module=$1&token=$3¶ms=$2 [L] 18 | RewriteRule ^([^/]+)/([^/]+)$ index.php?module=$1&token=$2 [L] -------------------------------------------------------------------------------- /src/modules/RestfulApi/RestfulApi.php: -------------------------------------------------------------------------------- 1 | 'crmid', 32 | 'vtiger_restfulapi' => 'restfulapiid', 33 | 'vtiger_restfulapicf'=>'restfulapiid'); 34 | 35 | /** 36 | * Mandatory for Listing (Related listview) 37 | */ 38 | var $list_fields = Array ( 39 | 'LBL_TOKEN' => array('restfulapi', 'token'), 40 | 'LBL_IP' => array('restfulapi', 'ip'), 41 | 'LBL_EXPIRATION_DATE' => array('restfulapi', 'expiration_date'), 42 | 'LBL_CALLS' => array('restfulapi', 'calls'), 43 | 44 | ); 45 | var $list_fields_name = Array ( 46 | 'LBL_TOKEN' => 'token', 47 | 'LBL_IP' => 'ip', 48 | 'LBL_EXPIRATION_DATE' => 'expiration_date', 49 | 'LBL_CALLS' => 'calls', 50 | 51 | ); 52 | 53 | // Make the field link to detail view 54 | var $list_link_field = 'token'; 55 | 56 | // For Popup listview and UI type support 57 | var $search_fields = Array( 58 | 'LBL_CALLS' => array('restfulapi', 'calls'), 59 | 'LBL_EXPIRATION_DATE' => array('restfulapi', 'expiration_date'), 60 | 'LBL_IP' => array('restfulapi', 'ip'), 61 | 'LBL_TOKEN' => array('restfulapi', 'token'), 62 | 63 | ); 64 | var $search_fields_name = Array ( 65 | 'LBL_CALLS' => 'calls', 66 | 'LBL_EXPIRATION_DATE' => 'expiration_date', 67 | 'LBL_IP' => 'ip', 68 | 'LBL_TOKEN' => 'token', 69 | 70 | ); 71 | 72 | // For Popup window record selection 73 | var $popup_fields = Array ('token'); 74 | 75 | // For Alphabetical search 76 | var $def_basicsearch_col = 'token'; 77 | 78 | // Column value to use on detail view record text display 79 | var $def_detailview_recname = 'token'; 80 | 81 | // Used when enabling/disabling the mandatory fields for the module. 82 | // Refers to vtiger_field.fieldname values. 83 | var $mandatory_fields = Array('token','assigned_user_id'); 84 | 85 | var $default_order_by = 'token'; 86 | var $default_sort_order='ASC'; 87 | 88 | function RestfulApi() { 89 | $this->log =LoggerManager::getLogger('RestfulApi'); 90 | $this->db = PearDatabase::getInstance(); 91 | $this->column_fields = getColumnFields('RestfulApi'); 92 | } 93 | 94 | /** 95 | * Invoked when special actions are performed on the module. 96 | * @param String Module name 97 | * @param String Event Type 98 | */ 99 | function vtlib_handler($moduleName, $eventType) { 100 | if($eventType == 'module.postinstall') { 101 | //Enable ModTracker for the module 102 | static::enableModTracker($moduleName); 103 | //Create Related Lists 104 | static::createRelatedLists(); 105 | } else if($eventType == 'module.disabled') { 106 | // Handle actions before this module is being uninstalled. 107 | } else if($eventType == 'module.preuninstall') { 108 | // Handle actions when this module is about to be deleted. 109 | } else if($eventType == 'module.preupdate') { 110 | // Handle actions before this module is updated. 111 | } else if($eventType == 'module.postupdate') { 112 | //Create Related Lists 113 | static::createRelatedLists(); 114 | } 115 | } 116 | 117 | /** 118 | * Enable ModTracker for the module 119 | */ 120 | public static function enableModTracker($moduleName) 121 | { 122 | include_once 'vtlib/Vtiger/Module.php'; 123 | include_once 'modules/ModTracker/ModTracker.php'; 124 | 125 | //Enable ModTracker for the module 126 | $moduleInstance = Vtiger_Module::getInstance($moduleName); 127 | ModTracker::enableTrackingForModule($moduleInstance->getId()); 128 | } 129 | 130 | protected static function createRelatedLists() 131 | { 132 | include_once('vtlib/Vtiger/Module.php'); 133 | 134 | } 135 | } -------------------------------------------------------------------------------- /src/modules/RestfulApi/actions/Api.php: -------------------------------------------------------------------------------- 1 | module = $request->get('module'); 34 | 35 | if($this->module == 'Auth') //Auth 36 | { 37 | //Get auth action 38 | $this->_getAuthAction($request); 39 | } 40 | else //Call 41 | { 42 | //Get user from token 43 | $this->_getUserFromToken($request); 44 | 45 | // Get asked action 46 | $this->_getRequestAction($request); 47 | 48 | // Get passed parameters 49 | $this->_getRequestParams($request); 50 | } 51 | } 52 | 53 | public function process(Vtiger_Request $request) 54 | { 55 | //PreProcess 56 | $this->preProcess($request); 57 | 58 | //Process 59 | if(!empty($this->module) && !empty($this->action)) 60 | { 61 | if($this->module == 'Auth') 62 | { 63 | //Login 64 | $m_result = $this->_login(); 65 | } 66 | else 67 | { 68 | //Call module action 69 | $m_result = $this->_callModuleAction(); 70 | } 71 | } 72 | else 73 | { 74 | $this->response("", 406); 75 | } 76 | 77 | //PostProcess 78 | $this->postProcess($m_result); 79 | } 80 | 81 | protected function postProcess($m_result) 82 | { 83 | if($this->action == 'Create' && preg_match('`^[0-9]+`', $m_result)) 84 | { 85 | $this->response($m_result, 201); //Created 86 | } 87 | elseif(is_array($m_result) && isset($m_result["success"]) && $m_result["success"] === false) 88 | { 89 | $this->response($m_result, 200, true); //OK with error 90 | } 91 | else 92 | { 93 | $this->response($m_result, 200); //OK 94 | } 95 | } 96 | 97 | public function response($data, $status, $error=false) //Must be public as in class RestFulApi_Rest_Model 98 | { 99 | $this->_code = ($status) ? $status : 200; 100 | $this->set_headers(); 101 | 102 | $this->response = new Vtiger_Response(); 103 | 104 | if(!$error && ($status == 200 || $status == 201)) 105 | { 106 | $this->response->setResult($data); 107 | } 108 | else 109 | { 110 | $this->response->setError($data["code"], $data["message"]); 111 | } 112 | 113 | $this->response->emit(); 114 | 115 | die(); //To do not launch the following actions 116 | } 117 | 118 | protected function _getAuthAction(Vtiger_Request $request) 119 | { 120 | if($request->has('key')) 121 | { 122 | $this->action = 'loginByKey'; 123 | $this->a_params = array("key" => $request->get('key')); 124 | } 125 | elseif($request->has('login') && $request->has('password')) 126 | { 127 | $this->action = 'login'; 128 | $this->a_params = array("login" => $request->get('login'), "password" => $request->get('password')); 129 | } 130 | else 131 | { 132 | $this->response("", 406); 133 | } 134 | } 135 | 136 | protected function _getUserFromToken(Vtiger_Request $request) 137 | { 138 | if(!$request->has('token')) 139 | { 140 | $error = array("code" => "TOKEN_NOT_FOUND", "message" => "Token not found"); 141 | } 142 | else 143 | { 144 | $authController = new RestfulApi_Auth_Action(); 145 | $m_result = $authController->checkToken($request->get('token')); 146 | 147 | if($m_result["success"] === false) 148 | { 149 | $error = $m_result; 150 | } 151 | else 152 | { 153 | $this->userId = $m_result["user_id"]; 154 | } 155 | } 156 | if(!empty($error)) 157 | { 158 | $this->response($error, 400, true); 159 | } 160 | } 161 | 162 | protected function _getRequestAction(Vtiger_Request $request) 163 | { 164 | switch($this->get_request_method()) 165 | { 166 | //CRUD 167 | case 'POST': 168 | if($request->has("id") && $request->get("id") > 0 ) 169 | { 170 | $this->action = 'Update'; //U 171 | } 172 | else 173 | { 174 | $this->action = 'Create'; //C 175 | } 176 | break; 177 | case 'GET': 178 | $this->action = 'Retrieve'; //R 179 | break; 180 | case 'PUT': 181 | $this->action = 'Update'; //U 182 | break; 183 | case 'DELETE': 184 | $this->action = 'Delete'; //D 185 | break; 186 | default: 187 | $this->action = null; 188 | break; 189 | } 190 | } 191 | 192 | protected function _getRequestParams(Vtiger_Request $request) 193 | { 194 | $this->a_params = array(); 195 | foreach($this->_request as $key => $val) 196 | { 197 | if($key != 'module' && $key != 'action' && $key != 'token') 198 | { 199 | if(!preg_match('`[^\\\]/`', $val)) 200 | { 201 | $this->a_params[$key] = str_replace("\/", "/", $val); 202 | } 203 | else 204 | { 205 | $a_data = explode("/", $val); 206 | 207 | for($i=0; $ia_params[$param_name] = str_replace("\/", "/", $param_value); 213 | } 214 | } 215 | } 216 | elseif($key == 'action') 217 | { 218 | $this->action = $val; 219 | } 220 | } 221 | 222 | if($this->action == 'Update' || $this->action == 'Delete') 223 | { 224 | if($request->has("id")) 225 | { 226 | $this->a_params["id"] = $request->get("id"); 227 | } 228 | else 229 | { 230 | $this->response("", 406); 231 | } 232 | } 233 | } 234 | 235 | protected function _login() 236 | { 237 | $m_result = null; 238 | 239 | $authController = new RestFulApi_Auth_Action(); 240 | 241 | if(!empty($this->a_params["key"])) 242 | { 243 | $m_result = $authController->loginByKey($this->a_params["key"]); 244 | } 245 | else 246 | { 247 | $m_result = $authController->login($this->a_params["login"], $this->a_params["password"]); 248 | } 249 | 250 | return $m_result; 251 | } 252 | 253 | protected function _callModuleAction() 254 | { 255 | $moduleInstance = Vtiger_Module::getInstance($this->module); 256 | 257 | if(empty($moduleInstance)) 258 | { 259 | $this->response("", 404); 260 | } 261 | else 262 | { 263 | switch($this->action) 264 | { 265 | //CRUD 266 | 267 | case 'Create': //C 268 | $m_result = $this->_createItem(); 269 | break; 270 | 271 | case 'Retrieve': //R 272 | $id = !empty($this->a_params["id"]) ? $this->a_params["id"] : null; 273 | $start = !empty($this->a_params["start"]) ? $this->a_params["start"] : 0; 274 | $length = !empty($this->a_params["length"]) ? $this->a_params["length"] : 20; 275 | $order = !empty($this->a_params["order"]) ? $this->a_params["order"] : ''; 276 | $criteria = !empty($this->a_params["criteria"]) ? $this->a_params["criteria"] : ''; 277 | $picklist = !empty($this->a_params["picklist"]) ? $this->a_params["picklist"] : ''; 278 | $picklistDependencies = !empty($this->a_params["picklistdep"]) ? $this->a_params["picklistdep"] : false; 279 | 280 | if(!empty($id)) 281 | { 282 | //Unique item 283 | $m_result = $this->_retrieveItem($id); 284 | } 285 | elseif(!empty($picklist)) 286 | { 287 | //Picklist values 288 | $m_result = $this->_retrievePickListValues($picklist, $picklistDependencies); 289 | } 290 | else 291 | { 292 | //Multiples items 293 | $m_result = $this->_retrieveItems($start, $length, $criteria, $order); 294 | } 295 | break; 296 | 297 | case 'Update': //U 298 | $id = !empty($this->a_params["id"]) ? $this->a_params["id"] : null; 299 | 300 | $m_result = $this->_updateItem($id); 301 | break; 302 | 303 | case 'Delete': //D 304 | $id = !empty($this->a_params["id"]) ? $this->a_params["id"] : null; 305 | 306 | if(!empty($id)) 307 | { 308 | //Delete item 309 | $m_result = $this->_deleteItem($id); 310 | } 311 | break; 312 | } 313 | } 314 | 315 | return $m_result; 316 | } 317 | 318 | //Create 319 | public function _createItem() 320 | { 321 | require_once("modules/{$this->module}/{$this->module}.php"); //Mandatory else there is the following exeption: 'Sorry! Attempt to access restricted file.' 322 | 323 | //New item 324 | $focus = CRMEntity::getInstance($this->module); 325 | $focus->mode = ''; 326 | 327 | //Set item data 328 | $this->_setItemData($focus); 329 | 330 | if($this->_isInventory() && !empty($this->a_params["items"])) 331 | { 332 | /*Si on est en inventory, on enregistre toutes les lignes d'une meme entité en une fois: 333 | - soit dans la boucle while() 334 | - soit apres la boucle pour la fin du fichier 335 | 336 | a chaque iteration dans le while(), on ajoute les lignes produits numérotés dans $lastEntity*/ 337 | 338 | $a_items = json_decode($this->a_params["items"]); 339 | 340 | $focus->column_fields['totalProductCount'] += count($a_items); 341 | 342 | //Set currency automaticaly if necessary 343 | if(empty($focus->column_fields['currency_id'])) 344 | { 345 | $focus->column_fields['currency_id'] = CurrencyField::getDBCurrencyId(); 346 | } 347 | 348 | foreach($a_items as $j => $item) 349 | { 350 | $index = $j + 1; 351 | 352 | foreach ($this->productFields as $field) 353 | { 354 | $focus->column_fields[ $field.$index ] = $item->{$field}; 355 | } 356 | 357 | if(!empty($item->discount_percentage) && $item->discount_percentage > 0) 358 | { 359 | $focus->column_fields["discount$index"] = "on"; 360 | $focus->column_fields["discount_type$index"] = "percentage"; 361 | } 362 | elseif(!empty($item->discount_amount) && $item->discount_amount > 0) 363 | { 364 | $focus->column_fields["discount$index"] = "on"; 365 | $focus->column_fields["discount_type$index"] = "amount"; 366 | } 367 | } 368 | 369 | $focusId = $this->_createInventoryEntity($focus->column_fields); 370 | } 371 | else //NOT Inventory 372 | { 373 | //Save data 374 | $focus->save($this->module); 375 | $focusId = $focus->id; 376 | 377 | } 378 | 379 | return $focusId; 380 | } 381 | 382 | protected function _createInventoryEntity($a_columnFields) 383 | { 384 | if(!empty($a_columnFields["discount_percentage_final"]) && $a_columnFields["discount_percentage_final"] > 0) 385 | { 386 | $a_columnFields["discount_final"] = "on"; 387 | $a_columnFields["discount_type_final"] = "percentage"; 388 | } 389 | elseif(!empty($a_columnFields["discount_amount_final"]) && $a_columnFields["discount_amount_final"] > 0) 390 | { 391 | $a_columnFields["discount_final"] = "on"; 392 | $a_columnFields["discount_type_final"] = "amount"; 393 | } 394 | 395 | $_REQUEST = $a_columnFields; 396 | 397 | $focus = CRMEntity::getInstance($this->module); 398 | $focus->mode = ''; 399 | $focus->column_fields = $_REQUEST; 400 | 401 | $focus->save($this->module); 402 | 403 | return $focus->id; 404 | } 405 | 406 | //Retrieve multiple 407 | protected function _retrieveItems($start=0, $length=20, $criteriaList='', $order='') 408 | { 409 | $a_items = array(); 410 | 411 | $result = $this->_doRetrieveQuery($start, $length, $criteriaList, $order); 412 | 413 | $db = PearDatabase::getInstance(); 414 | while($row = $db->fetchByAssoc($result)) 415 | { 416 | $item = $this->_retrieveItem($row["id"], false); //Don't check entity existence because here this existence is obvious (returned by the previous query) 417 | 418 | if(!empty($item)) 419 | { 420 | $a_items[] = $item; 421 | } 422 | } 423 | 424 | return $a_items; 425 | } 426 | 427 | //Retrieve unique 428 | protected function _retrieveItem($id, $checkModuleEntityExistence=true) 429 | { 430 | $m_result = null; 431 | 432 | require_once("modules/{$this->module}/{$this->module}.php"); 433 | 434 | //Check if an entity exists for the module and the id 435 | if($checkModuleEntityExistence) 436 | { 437 | $db = PearDatabase::getInstance(); 438 | $query = "SELECT * 439 | FROM vtiger_crmentity CE 440 | WHERE CE.deleted = 0 441 | AND CE.setype LIKE ? 442 | AND CE.crmid = ?"; 443 | 444 | $result = $db->pquery($query, array($this->module, $id)); 445 | $count = $db->num_rows($result); 446 | } 447 | 448 | //If entity exists, retrieve info 449 | if(!$checkModuleEntityExistence || $count > 0) 450 | { 451 | $focus = CRMEntity::getInstance($this->module); 452 | $focus->retrieve_entity_info($id, $this->module); 453 | 454 | foreach($focus->column_fields as &$field) 455 | { 456 | $field = vtlib_purify($field); 457 | } 458 | 459 | $m_result = $focus->column_fields; 460 | $m_result["api_date_now"] = date("Y-m-d H:i:s"); //Added to control the serveur hour 461 | } 462 | 463 | return $m_result; 464 | } 465 | 466 | //Retrieve picklist values 467 | protected function _retrievePickListValues($picklistFieldName, $getDependencies=false) 468 | { 469 | $m_result = null; 470 | 471 | require_once("modules/PickList/PickListUtils.php"); 472 | //require_once("modules/PickList/DependentPickListUtils.php"); 473 | 474 | $a_translations = $this->_getTranslations(); 475 | 476 | $a_values = getAllPickListValues($picklistFieldName, $a_translations); 477 | 478 | $m_result = array( 479 | "values" => $a_values 480 | ); 481 | 482 | if($getDependencies) 483 | { 484 | $m_result["dependencies"] = array(); 485 | 486 | //Get field dependencies 487 | $a_dependancies = Vtiger_DependencyPicklist::getPicklistDependencyDatasource($this->module); 488 | 489 | foreach($a_dependancies as $fieldName => $dependency) 490 | { 491 | if($fieldName == $picklistFieldName) 492 | { 493 | $m_result["dependencies"] = $dependency; 494 | } 495 | } 496 | } 497 | 498 | return $m_result; 499 | } 500 | 501 | //Update 502 | protected function _updateItem($id) 503 | { 504 | $m_result = false; 505 | 506 | $item = $this->_retrieveItem($id); 507 | 508 | if(!empty($item)) 509 | { 510 | //Get item 511 | $focus = CRMEntity::getInstance($this->module); 512 | $focus->retrieve_entity_info($id, $this->module); 513 | 514 | foreach($focus->column_fields as &$field) 515 | { 516 | $field = vtlib_purify($field); 517 | } 518 | 519 | $focus->mode = 'edit'; 520 | $focus->id = $id; 521 | 522 | //Set item data 523 | $this->_setItemData($focus); 524 | 525 | //Save data 526 | try 527 | { 528 | $focus->save($this->module); 529 | 530 | $m_result = $id; 531 | } 532 | catch(Exception $e) 533 | { 534 | $m_result = $e; 535 | } 536 | } 537 | 538 | return $m_result; 539 | } 540 | 541 | //Delete 542 | protected function _deleteItem($id) 543 | { 544 | $b_deleted = false; 545 | 546 | $item = $this->_retrieveItem($id); 547 | 548 | if(!empty($item)) 549 | { 550 | $db = PearDatabase::getInstance(); 551 | $query = "UPDATE vtiger_crmentity 552 | SET deleted = 1 553 | WHERE crmid = ?"; 554 | $db->pquery($query, array($id)); 555 | 556 | $b_deleted = true; 557 | } 558 | 559 | return $b_deleted; 560 | } 561 | 562 | protected function _doRetrieveQuery($start=0, $length=20, $criteriaList='', $order='') 563 | { 564 | //Get module database data 565 | require_once("modules/{$this->module}/{$this->module}.php"); 566 | 567 | $moduleName = $this->module; 568 | $o_module = new $moduleName(); 569 | $a_moduleTables = $o_module->tab_name_index; //List of used tables 570 | 571 | $moduleInstance = Vtiger_Module::getInstance($this->module); 572 | 573 | $tableName = $moduleInstance->basetable; 574 | $tableId = $moduleInstance->basetableid; 575 | 576 | $order = !empty($order) ? $order : 'CE.createdtime ASC'; 577 | 578 | //Get criteria 579 | $a_criteriaParams = array(); 580 | $a_criteria = explode(";", $criteriaList); 581 | if(!empty($a_criteria)) 582 | { 583 | $criteriaQuery = "1 "; 584 | 585 | foreach($a_criteria as $criteria) 586 | { 587 | $a_criteriaFields = explode(":", $criteria); 588 | 589 | //Without operator 590 | if(count($a_criteriaFields) == 2) 591 | { 592 | $field = trim($a_criteriaFields[0]); 593 | $value = trim($a_criteriaFields[1]); 594 | 595 | if(!empty($field) && !empty($value)) 596 | { 597 | $criteriaQuery .= "AND $field = ? "; 598 | 599 | $a_criteriaParams[] = $value; 600 | } 601 | else 602 | { 603 | $criteriaQuery .= "AND 0 "; 604 | } 605 | } 606 | //With operator 607 | elseif(count($a_criteriaFields) > 2) 608 | { 609 | $field = trim($a_criteriaFields[0]); 610 | $operatorStr = trim($a_criteriaFields[1]); 611 | $value = trim($a_criteriaFields[2]); 612 | 613 | //Securize query 614 | switch($operatorStr) 615 | { 616 | case 'neq': 617 | $operator = '!='; 618 | break; 619 | 620 | case 'lt': 621 | $operator = '<'; 622 | break; 623 | 624 | case 'gt': 625 | $operator = '>'; 626 | break; 627 | 628 | case 'lte': 629 | $operator = '<='; 630 | break; 631 | 632 | case 'gte': 633 | $operator = '>='; 634 | break; 635 | 636 | case 'like': 637 | $operator = 'LIKE'; 638 | 639 | case 'eq': 640 | default: 641 | $operator = '='; 642 | break; 643 | } 644 | 645 | if(!empty($field) && !empty($operator) && !empty($value)) 646 | { 647 | $criteriaQuery .= "AND $field $operator ? "; 648 | 649 | $a_criteriaParams[] = $value; 650 | } 651 | else 652 | { 653 | $criteriaQuery .= "AND 0 "; 654 | } 655 | } 656 | } 657 | } 658 | 659 | //Add SELECT clause 660 | $query = "SELECT T.$tableId AS id 661 | FROM $tableName T "; 662 | 663 | //Add JOIN clauses 664 | $query .= "INNER JOIN vtiger_crmentity CE ON CE.crmid = T.$tableId AND CE.deleted = 0 "; 665 | foreach($a_moduleTables as $table => $idField) 666 | { 667 | if($table != 'vtiger_crmentity') 668 | { 669 | $query .= "LEFT JOIN $table ON $table.$idField = T.$tableId "; 670 | } 671 | } 672 | 673 | //Add WHERE, ORDERY BY, LIMIT clauses 674 | $query .= "WHERE $criteriaQuery 675 | ORDER BY $order 676 | LIMIT $start, $length"; 677 | 678 | $db = PearDatabase::getInstance(); 679 | $result = $db->pquery($query, array($a_criteriaParams)); 680 | 681 | return $result; 682 | } 683 | 684 | protected function _setItemData(&$focus) 685 | { 686 | foreach($this->a_params as $fieldName => $fieldValue) 687 | { 688 | if(is_array($fieldValue)) 689 | { 690 | $focus->column_fields[$fieldName] = $fieldValue; 691 | } 692 | else if($fieldValue !== null) 693 | { 694 | $focus->column_fields[$fieldName] = decode_html($fieldValue); 695 | } 696 | } 697 | 698 | if(empty($focus->column_fields["assigned_user_id"])) 699 | { 700 | $focus->column_fields["assigned_user_id"] = $this->userId; 701 | } 702 | } 703 | 704 | protected function _getTranslations() 705 | { 706 | global $default_language; 707 | 708 | //Get translations 709 | $userLanguage = Vtiger_Language_Handler::getLanguage(); 710 | $languageStrings = $jsLanguageStrings = array(); 711 | 712 | $a_translations = array(); 713 | 714 | if(file_exists("../../languages/$userLanguage/Vtiger.php")) //User language. Warning: file_exists() do not take in consideration the include path (so we add ../../) 715 | { 716 | include("languages/$userLanguage/Vtiger.php"); //CRM default language 717 | } 718 | else 719 | { 720 | include("languages/fr_fr/Vtiger.php"); 721 | } 722 | 723 | $a_translations = array_merge($languageStrings, $jsLanguageStrings); 724 | 725 | //Module 726 | if(file_exists("../../languages/$userLanguage/".$this->module.".php")) //User language. Warning: file_exists() do not take in consideration the include path (so we add ../../) 727 | { 728 | include("languages/$userLanguage/".$this->module.".php"); //CRM default language 729 | } 730 | else 731 | { 732 | include("languages/$default_language/".$this->module.".php"); 733 | } 734 | 735 | $a_moduleTranslations = array_merge($languageStrings, $jsLanguageStrings); 736 | 737 | foreach($a_moduleTranslations as $label => $translation) 738 | { 739 | $a_translations[$label] = vtlib_purify($translation); 740 | } 741 | 742 | return $a_translations; 743 | } 744 | 745 | protected function _isInventory() 746 | { 747 | return in_array($this->module, $this->inventoryModules); 748 | } 749 | } -------------------------------------------------------------------------------- /src/modules/RestfulApi/actions/Auth.php: -------------------------------------------------------------------------------- 1 | column_fields['user_name'] = $username; 29 | 30 | $isAuthentificated = $user->doLogin($password); 31 | 32 | if(!$isAuthentificated) 33 | { 34 | return array("success" => false, "error" => array("code" => "USER_NOT_FOUND", "message" => "User not found")); 35 | } 36 | 37 | //Retrieve user id 38 | $user_id = $user->retrieve_user_id($username); 39 | 40 | //Simulate a login for the user 41 | $this->loginAsUser($user_id); 42 | 43 | //Generate a token 44 | return $this->generateToken($user_id); 45 | } 46 | 47 | public function loginByKey($key) 48 | { 49 | $db = PearDatabase::getInstance(); 50 | $query = "SELECT id, user_name 51 | FROM vtiger_users 52 | WHERE accesskey = ? 53 | AND deleted = 0 54 | LIMIT 1"; 55 | $result = $db->pquery($query, array($key)); 56 | 57 | $user = $db->fetchByAssoc($result); 58 | 59 | if(empty($user)) 60 | { 61 | return array("success" => false, "error" => array("code" => "KEY_NOT_FOUND", "message" => "Key not found")); 62 | } 63 | 64 | $user_id = $user["id"]; 65 | 66 | //Simulate a login for the user 67 | $this->loginAsUser($user_id); 68 | 69 | //Generate a token 70 | return $this->generateToken($user_id); 71 | } 72 | 73 | protected function loginAsUser($user_id) 74 | { 75 | global $current_user; 76 | 77 | //On simule que l'utilisateur se connecte 78 | $user = CRMEntity::getInstance('Users'); 79 | $current_user = $user->retrieveCurrentUserInfoFromFile($user_id); 80 | } 81 | 82 | protected function generateToken($user_id) 83 | { 84 | $db = PearDatabase::getInstance(); 85 | 86 | //Generate a new token 87 | $token = md5($_SERVER["REMOTE_ADDR"]."-".mktime()); 88 | 89 | //Save the token 90 | $focus = new RestfulApi(); 91 | $focus->mode = ''; 92 | $focus->column_fields["token"] = $token; 93 | $focus->column_fields["assigned_user_id"] = $user_id; 94 | $focus->column_fields["ip"] = $_SERVER["REMOTE_ADDR"]; 95 | $focus->column_fields["calls"] = 0; 96 | $focus->column_fields["expiration_date"] = date("Y-m-d H:i:s", mktime()+$this->expirationTimeout); 97 | $focus->column_fields["user_id"] = $user_id; 98 | $focus->save("RestfulApi"); 99 | 100 | $tokenId = $focus->id; 101 | 102 | if(is_int($tokenId) && $tokenId > -1) 103 | { 104 | return $token; 105 | } 106 | 107 | return false; 108 | } 109 | 110 | public function checkToken($token) 111 | { 112 | $ip = $_SERVER["REMOTE_ADDR"]; 113 | $expirationDate = date("Y-m-d H:i:s"); 114 | 115 | 116 | $db = PearDatabase::getInstance(); 117 | $query = "SELECT API.*, API.restfulapiid AS id 118 | FROM vtiger_restfulapi API 119 | INNER JOIN vtiger_crmentity CE ON CE.crmid = API.restfulapiid AND CE.deleted = 0 120 | WHERE API.token = ? 121 | AND API.expiration_date >= ? 122 | AND API.ip = ? 123 | LIMIT 1"; 124 | $result = $db->pquery($query, array($token, $expirationDate, $ip)); 125 | 126 | $row = $db->fetchByAssoc($result); 127 | 128 | if(!empty($row["id"])) 129 | { 130 | $this->loginAsUser($row["user_id"]); 131 | 132 | $focus = new RestfulApi(); 133 | $focus->retrieve_entity_info($row["id"], 'RestfulApi'); 134 | $focus->id = $row["id"]; 135 | 136 | } 137 | 138 | if(empty($focus)) 139 | { 140 | return array("success" => false, "error" => array("code" => "INVALID_TOKEN", "message" => "Invalid token")); 141 | } 142 | 143 | //Update token 144 | $focus->mode = 'edit'; 145 | $focus->column_fields["calls"] = $focus->column_fields["calls"] + 1; 146 | $focus->column_fields["expiration_date"] = date("Y-m-d H:i:s", mktime()+$this->expirationTimeout); 147 | $focus->save("RestfulApi"); 148 | 149 | return array("success" => true, "user_id" => $row["user_id"]); 150 | } 151 | } -------------------------------------------------------------------------------- /src/modules/RestfulApi/index.php: -------------------------------------------------------------------------------- 1 | process($request); -------------------------------------------------------------------------------- /src/modules/RestfulApi/models/Rest.php: -------------------------------------------------------------------------------- 1 | inputs(); 16 | } 17 | 18 | public function get_referer(){ 19 | return $_SERVER['HTTP_REFERER']; 20 | } 21 | 22 | public function response($data,$status){ 23 | $this->_code = ($status)?$status:200; 24 | $this->set_headers(); 25 | echo $data; 26 | exit; 27 | } 28 | 29 | private function get_status_message(){ 30 | $status = array( 31 | 100 => 'Continue', 32 | 101 => 'Switching Protocols', 33 | 200 => 'OK', 34 | 201 => 'Created', 35 | 202 => 'Accepted', 36 | 203 => 'Non-Authoritative Information', 37 | 204 => 'No Content', 38 | 205 => 'Reset Content', 39 | 206 => 'Partial Content', 40 | 300 => 'Multiple Choices', 41 | 301 => 'Moved Permanently', 42 | 302 => 'Found', 43 | 303 => 'See Other', 44 | 304 => 'Not Modified', 45 | 305 => 'Use Proxy', 46 | 306 => '(Unused)', 47 | 307 => 'Temporary Redirect', 48 | 400 => 'Bad Request', 49 | 401 => 'Unauthorized', 50 | 402 => 'Payment Required', 51 | 403 => 'Forbidden', 52 | 404 => 'Not Found', 53 | 405 => 'Method Not Allowed', 54 | 406 => 'Not Acceptable', 55 | 407 => 'Proxy Authentication Required', 56 | 408 => 'Request Timeout', 57 | 409 => 'Conflict', 58 | 410 => 'Gone', 59 | 411 => 'Length Required', 60 | 412 => 'Precondition Failed', 61 | 413 => 'Request Entity Too Large', 62 | 414 => 'Request-URI Too Long', 63 | 415 => 'Unsupported Media Type', 64 | 416 => 'Requested Range Not Satisfiable', 65 | 417 => 'Expectation Failed', 66 | 500 => 'Internal Server Error', 67 | 501 => 'Not Implemented', 68 | 502 => 'Bad Gateway', 69 | 503 => 'Service Unavailable', 70 | 504 => 'Gateway Timeout', 71 | 505 => 'HTTP Version Not Supported'); 72 | return ($status[$this->_code])?$status[$this->_code]:$status[500]; 73 | } 74 | 75 | public function get_request_method(){ 76 | return $_SERVER['REQUEST_METHOD']; 77 | } 78 | 79 | private function inputs(){ 80 | switch($this->get_request_method()){ 81 | case "POST": 82 | if(!empty($_POST)) 83 | { 84 | $this->_request = $this->cleanInputs($_POST); 85 | } 86 | else 87 | { 88 | $data = get_object_vars(json_decode(file_get_contents("php://input"))); 89 | $this->_request = $this->cleanInputs($data); 90 | } 91 | break; 92 | case "GET": 93 | case "DELETE": 94 | $this->_request = $this->cleanInputs($_GET); 95 | break; 96 | case "PUT": 97 | parse_str(file_get_contents("php://input"),$this->_request); 98 | $this->_request = $this->cleanInputs($this->_request); 99 | break; 100 | default: 101 | $this->response('',406); 102 | break; 103 | } 104 | } 105 | 106 | private function cleanInputs($data){ 107 | $clean_input = array(); 108 | if(is_array($data)){ 109 | foreach($data as $k => $v){ 110 | $clean_input[$k] = $this->cleanInputs($v); 111 | } 112 | }else{ 113 | if(get_magic_quotes_gpc()){ 114 | $data = trim(stripslashes($data)); 115 | } 116 | $data = strip_tags($data); 117 | $clean_input = trim($data); 118 | } 119 | return $clean_input; 120 | } 121 | 122 | protected function set_headers(){ 123 | header("HTTP/1.1 ".$this->_code." ".$this->get_status_message()); 124 | header("Content-Type:".$this->_content_type); 125 | } 126 | } 127 | ?> -------------------------------------------------------------------------------- /src/modules/RestfulApi/schema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | vtiger_restfulapi 6 | 14 |
15 | 16 | vtiger_restfulapicf 17 | 21 |
22 |
23 |
24 | --------------------------------------------------------------------------------