Commit | Line | Data |
---|---|---|
6a488035 | 1 | <?php |
6a488035 TO |
2 | |
3 | /** | |
ade83e4b CB |
4 | * |
5 | * This class allows to consume the API, either from within a module that knows civicrm already: | |
6 | * | |
0b882a86 | 7 | * ``` |
0cd417ef | 8 | * require_once('api/class.api.php'); |
ade83e4b | 9 | * $api = new civicrm_api3(); |
0b882a86 | 10 | * ``` |
ade83e4b CB |
11 | * |
12 | * or from any code on the same server as civicrm | |
13 | * | |
0b882a86 | 14 | * ``` |
ade83e4b CB |
15 | * require_once('/your/civi/folder/api/class.api.php'); |
16 | * // the path to civicrm.settings.php | |
36cf2d7e | 17 | * $api = new civicrm_api3 (['conf_path'=> '/your/path/to/your/civicrm/or/joomla/site']); |
0b882a86 | 18 | * ``` |
ade83e4b CB |
19 | * |
20 | * or to query a remote server via the rest api | |
21 | * | |
0b882a86 | 22 | * ``` |
36cf2d7e AS |
23 | * $api = new civicrm_api3 (['server' => 'http://example.org', |
24 | * 'api_key'=>'theusersecretkey', | |
25 | * 'key'=>'thesitesecretkey']); | |
0b882a86 | 26 | * ``` |
ade83e4b CB |
27 | * |
28 | * No matter how initialised and if civicrm is local or remote, you use the class the same way. | |
29 | * | |
0b882a86 | 30 | * ``` |
ade83e4b | 31 | * $api->{entity}->{action}($params); |
0b882a86 | 32 | * ``` |
ade83e4b CB |
33 | * |
34 | * So, to get the individual contacts: | |
35 | * | |
0b882a86 | 36 | * ``` |
36cf2d7e | 37 | * if ($api->Contact->Get(['contact_type'=>'Individual','return'=>'sort_name,current_employer']) { |
ade83e4b CB |
38 | * // each key of the result array is an attribute of the api |
39 | * echo "\n contacts found " . $api->count; | |
40 | * foreach ($api->values as $c) { | |
41 | * echo "\n".$c->sort_name. " working for ". $c->current_employer; | |
42 | * } | |
43 | * // in theory, doesn't append | |
44 | * } else { | |
45 | * echo $api->errorMsg(); | |
46 | * } | |
0b882a86 | 47 | * ``` |
ade83e4b CB |
48 | * |
49 | * Or, to create an event: | |
50 | * | |
0b882a86 | 51 | * ``` |
36cf2d7e | 52 | * if ($api->Event->Create(['title'=>'Test','event_type_id' => 1,'is_public' => 1,'start_date' => 19430429])) { |
ade83e4b CB |
53 | * echo "created event id:". $api->id; |
54 | * } else { | |
55 | * echo $api->errorMsg(); | |
56 | * } | |
0b882a86 | 57 | * ``` |
ade83e4b CB |
58 | * |
59 | * To make it easier, the Actions can either take for input an | |
60 | * associative array $params, or simply an id. The following two lines | |
61 | * are equivalent. | |
62 | * | |
0b882a86 | 63 | * ``` |
ade83e4b | 64 | * $api->Activity->Get (42); |
36cf2d7e | 65 | * $api->Activity->Get (['id'=>42]); |
0b882a86 | 66 | * ``` |
ade83e4b CB |
67 | * |
68 | * | |
69 | * You can also get the result like civicrm_api does, but as an object | |
70 | * instead of an array (eg $entity->attribute instead of | |
71 | * $entity['attribute']). | |
72 | * | |
0b882a86 | 73 | * ``` |
ade83e4b CB |
74 | * $result = $api->result; |
75 | * // is the json encoded result | |
76 | * echo $api; | |
0b882a86 | 77 | * ``` |
dc9b5019 AS |
78 | * |
79 | * For remote calls, you may need to set the UserAgent and Referer strings for some environments (eg WordFence) | |
80 | * Add 'referer' and 'useragent' to the initialisation config: | |
81 | * | |
82 | * ``` | |
83 | * $api = new civicrm_api3 (['server' => 'http://example.org', | |
84 | * 'api_key'=>'theusersecretkey', | |
85 | * 'key'=>'thesitesecretkey', | |
86 | * 'referer'=>'https://my_site', | |
87 | * 'useragent'=>'curl']); | |
88 | * ``` | |
6a488035 TO |
89 | */ |
90 | class civicrm_api3 { | |
ade83e4b CB |
91 | |
92 | /** | |
dc64d047 EM |
93 | * Class constructor. |
94 | * | |
decce855 | 95 | * @param array $config API configuration. |
ade83e4b | 96 | */ |
3bdca100 | 97 | public function __construct($config = NULL) { |
6a488035 | 98 | $this->local = TRUE; |
cf8f0fff CW |
99 | $this->input = []; |
100 | $this->lastResult = []; | |
36cf2d7e | 101 | if (!empty($config) && !empty($config['server'])) { |
6a488035 TO |
102 | // we are calling a remote server via REST |
103 | $this->local = FALSE; | |
104 | $this->uri = $config['server']; | |
36cf2d7e | 105 | if (!empty($config['path'])) { |
6a488035 TO |
106 | $this->uri .= "/" . $config['path']; |
107 | } | |
ade83e4b CB |
108 | else { |
109 | $this->uri .= '/sites/all/modules/civicrm/extern/rest.php'; | |
110 | } | |
36cf2d7e | 111 | if (!empty($config['key'])) { |
6a488035 TO |
112 | $this->key = $config['key']; |
113 | } | |
114 | else { | |
115 | die("\nFATAL:param['key] missing\n"); | |
116 | } | |
36cf2d7e | 117 | if (!empty($config['api_key'])) { |
6a488035 TO |
118 | $this->api_key = $config['api_key']; |
119 | } | |
120 | else { | |
121 | die("\nFATAL:param['api_key] missing\n"); | |
122 | } | |
dc9b5019 AS |
123 | $this->referer = !empty($config['referer']) ? $config['referer'] : ''; |
124 | $this->useragent = !empty($config['useragent']) ? $config['useragent'] : 'curl'; | |
6a488035 TO |
125 | return; |
126 | } | |
36cf2d7e | 127 | if (!empty($config) && !empty($config['conf_path'])) { |
a788b190 | 128 | if (!defined('CIVICRM_SETTINGS_PATH')) { |
129 | define('CIVICRM_SETTINGS_PATH', $config['conf_path'] . '/civicrm.settings.php'); | |
130 | } | |
6a488035 | 131 | require_once CIVICRM_SETTINGS_PATH; |
f9c7ce43 | 132 | require_once 'CRM/Core/ClassLoader.php'; |
6a488035 TO |
133 | require_once 'api/api.php'; |
134 | require_once "api/v3/utils.php"; | |
f9c7ce43 | 135 | CRM_Core_ClassLoader::singleton()->register(); |
6a488035 TO |
136 | $this->cfg = CRM_Core_Config::singleton(); |
137 | $this->init(); | |
138 | } | |
139 | else { | |
140 | $this->cfg = CRM_Core_Config::singleton(); | |
141 | } | |
142 | } | |
143 | ||
ade83e4b | 144 | /** |
dc64d047 EM |
145 | * Convert to string. |
146 | * | |
c490a46a | 147 | * @return string |
ade83e4b | 148 | */ |
6a488035 TO |
149 | public function __toString() { |
150 | return json_encode($this->lastResult); | |
151 | } | |
152 | ||
ade83e4b | 153 | /** |
dc64d047 EM |
154 | * Perform action. |
155 | * | |
2884d956 BT |
156 | * @param string $action |
157 | * @param array $params | |
dc64d047 | 158 | * |
645ee340 | 159 | * @return bool |
ade83e4b | 160 | */ |
6a488035 | 161 | public function __call($action, $params) { |
ade83e4b | 162 | // @TODO Check if it's a valid action. |
6a488035 TO |
163 | if (isset($params[0])) { |
164 | return $this->call($this->currentEntity, $action, $params[0]); | |
165 | } | |
166 | else { | |
167 | return $this->call($this->currentEntity, $action, $this->input); | |
168 | } | |
169 | } | |
170 | ||
ade83e4b | 171 | /** |
dc64d047 EM |
172 | * Call via rest. |
173 | * | |
2884d956 BT |
174 | * @param string $entity |
175 | * @param string $action | |
645ee340 | 176 | * @param array $params |
dc64d047 | 177 | * |
645ee340 | 178 | * @return \stdClass |
ade83e4b | 179 | */ |
cf8f0fff | 180 | private function remoteCall($entity, $action, $params = []) { |
735682e9 | 181 | $query = $this->uri . "?entity=$entity&action=$action"; |
cf8f0fff | 182 | $fields = http_build_query([ |
5a28e17c CW |
183 | 'key' => $this->key, |
184 | 'api_key' => $this->api_key, | |
185 | 'json' => json_encode($params), | |
cf8f0fff | 186 | ]); |
735682e9 | 187 | |
6a488035 | 188 | if (function_exists('curl_init')) { |
ade83e4b CB |
189 | // To facilitate debugging without leaking info, entity & action |
190 | // are GET, other data is POST. | |
6a488035 TO |
191 | $ch = curl_init(); |
192 | curl_setopt($ch, CURLOPT_URL, $query); | |
735682e9 | 193 | curl_setopt($ch, CURLOPT_POST, TRUE); |
6a488035 TO |
194 | curl_setopt($ch, CURLOPT_POSTFIELDS, $fields); |
195 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); | |
dc9b5019 AS |
196 | curl_setopt($ch, CURLOPT_USERAGENT, $this->useragent); |
197 | if ($this->referer) { | |
198 | curl_setopt($ch, CURLOPT_REFERER, $this->referer); | |
199 | } | |
6a488035 | 200 | $result = curl_exec($ch); |
7a514fb1 CB |
201 | // CiviCRM expects to get back a CiviCRM error object. |
202 | if (curl_errno($ch)) { | |
3bdca100 | 203 | $res = new stdClass(); |
ade83e4b | 204 | $res->is_error = 1; |
7922c1d3 XD |
205 | $res->error_message = curl_error($ch); |
206 | $res->level = "cURL"; | |
cf8f0fff | 207 | $res->error = ['cURL error' => curl_error($ch)]; |
ade83e4b | 208 | return $res; |
8ef12e64 | 209 | } |
6a488035 | 210 | curl_close($ch); |
6a488035 TO |
211 | } |
212 | else { | |
ade83e4b CB |
213 | // Should be discouraged, because the API credentials and data |
214 | // are submitted as GET data, increasing chance of exposure.. | |
6a488035 | 215 | $result = file_get_contents($query . '&' . $fields); |
6a488035 | 216 | } |
78a069a9 | 217 | if (!$res = json_decode($result)) { |
3bdca100 | 218 | $res = new stdClass(); |
78a069a9 CB |
219 | $res->is_error = 1; |
220 | $res->error_message = 'Unable to parse returned JSON'; | |
221 | $res->level = 'json_decode'; | |
cf8f0fff | 222 | $res->error = ['Unable to parse returned JSON' => $result]; |
78a069a9 CB |
223 | $res->row_result = $result; |
224 | } | |
225 | return $res; | |
6a488035 TO |
226 | } |
227 | ||
aa1b1481 | 228 | /** |
dc64d047 EM |
229 | * Call api function. |
230 | * | |
2884d956 | 231 | * @param string $entity |
aa1b1481 EM |
232 | * @param string $action |
233 | * @param array $params | |
234 | * | |
235 | * @return bool | |
236 | */ | |
cf8f0fff | 237 | private function call($entity, $action = 'Get', $params = []) { |
6a488035 | 238 | if (is_int($params)) { |
cf8f0fff | 239 | $params = ['id' => $params]; |
6a488035 TO |
240 | } |
241 | elseif (is_string($params)) { | |
242 | $params = json_decode($params); | |
243 | } | |
244 | ||
245 | if (!isset($params['version'])) { | |
6a488035 TO |
246 | $params['version'] = 3; |
247 | } | |
248 | if (!isset($params['sequential'])) { | |
249 | $params['sequential'] = 1; | |
250 | } | |
251 | ||
252 | if (!$this->local) { | |
253 | $this->lastResult = $this->remoteCall($entity, $action, $params); | |
254 | } | |
255 | else { | |
ade83e4b | 256 | // Converts a multi-dimentional array into an object. |
6a488035 TO |
257 | $this->lastResult = json_decode(json_encode(civicrm_api($entity, $action, $params))); |
258 | } | |
ade83e4b | 259 | // Reset the input to be ready for a new call. |
cf8f0fff | 260 | $this->input = []; |
6a488035 TO |
261 | if (property_exists($this->lastResult, 'is_error')) { |
262 | return !$this->lastResult->is_error; | |
263 | } | |
ade83e4b | 264 | // getsingle doesn't have is_error. |
6a488035 TO |
265 | return TRUE; |
266 | } | |
267 | ||
ade83e4b CB |
268 | /** |
269 | * Helper method for long running programs (eg bots). | |
270 | */ | |
3bdca100 | 271 | public function ping() { |
6a488035 TO |
272 | global $_DB_DATAOBJECT; |
273 | foreach ($_DB_DATAOBJECT['CONNECTIONS'] as & $c) { | |
274 | if (!$c->connection->ping()) { | |
275 | $c->connect($this->cfg->dsn); | |
276 | if (!$c->connection->ping()) { | |
277 | die("we couldn't connect"); | |
278 | } | |
279 | } | |
280 | } | |
281 | } | |
282 | ||
ade83e4b CB |
283 | /** |
284 | * Return the last error message. | |
c490a46a | 285 | * @return string |
ade83e4b | 286 | */ |
3bdca100 | 287 | public function errorMsg() { |
6a488035 TO |
288 | return $this->lastResult->error_message; |
289 | } | |
290 | ||
ade83e4b | 291 | /** |
1747ab99 | 292 | * Initialize. |
ade83e4b | 293 | */ |
3bdca100 | 294 | public function init() { |
6a488035 TO |
295 | CRM_Core_DAO::init($this->cfg->dsn); |
296 | } | |
297 | ||
ade83e4b | 298 | /** |
1747ab99 EM |
299 | * Get attribute. |
300 | * | |
2884d956 BT |
301 | * @param string $name |
302 | * @param mixed $value | |
1747ab99 | 303 | * |
c490a46a | 304 | * @return $this |
6a488035 | 305 | */ |
6a488035 TO |
306 | public function attr($name, $value = NULL) { |
307 | if ($value === NULL) { | |
308 | if (property_exists($this->lastResult, $name)) { | |
309 | return $this->lastResult->$name; | |
310 | } | |
311 | } | |
312 | else { | |
313 | $this->input[$name] = $value; | |
314 | } | |
315 | return $this; | |
316 | } | |
317 | ||
ade83e4b | 318 | /** |
dc64d047 EM |
319 | * Is this an error. |
320 | * | |
c490a46a | 321 | * @return bool |
ade83e4b | 322 | */ |
6a488035 TO |
323 | public function is_error() { |
324 | return (property_exists($this->lastResult, 'is_error') && $this->lastResult->is_error); | |
325 | } | |
326 | ||
ade83e4b | 327 | /** |
1747ab99 EM |
328 | * Check if var is set. |
329 | * | |
100fef9d | 330 | * @param string $name |
dc64d047 | 331 | * |
c490a46a | 332 | * @return bool |
ade83e4b | 333 | */ |
6a488035 TO |
334 | public function is_set($name) { |
335 | return (isset($this->lastResult->$name)); | |
336 | } | |
337 | ||
ade83e4b | 338 | /** |
1747ab99 EM |
339 | * Get object. |
340 | * | |
341 | * @param string $name | |
dc64d047 | 342 | * |
c490a46a | 343 | * @return $this |
ade83e4b | 344 | */ |
6a488035 | 345 | public function __get($name) { |
ade83e4b | 346 | // @TODO Test if valid entity. |
6a488035 | 347 | if (strtolower($name) !== $name) { |
ade83e4b CB |
348 | // Cheap and dumb test to differentiate call to |
349 | // $api->Entity->Action & value retrieval. | |
6a488035 TO |
350 | $this->currentEntity = $name; |
351 | return $this; | |
352 | } | |
6a488035 | 353 | if ($name === 'result') { |
6a488035 TO |
354 | return $this->lastResult; |
355 | } | |
356 | if ($name === 'values') { | |
357 | return $this->lastResult->values; | |
358 | } | |
6a488035 | 359 | if (property_exists($this->lastResult, $name)) { |
6a488035 TO |
360 | return $this->lastResult->$name; |
361 | } | |
6a488035 TO |
362 | $this->currentEntity = $name; |
363 | return $this; | |
364 | } | |
365 | ||
ade83e4b CB |
366 | /** |
367 | * Or use $api->value. | |
c490a46a | 368 | * @return array |
ade83e4b | 369 | */ |
6a488035 TO |
370 | public function values() { |
371 | if (is_array($this->lastResult)) { | |
372 | return $this->lastResult['values']; | |
373 | } | |
ade83e4b CB |
374 | else { |
375 | return $this->lastResult->values; | |
376 | } | |
6a488035 TO |
377 | } |
378 | ||
ade83e4b CB |
379 | /** |
380 | * Or use $api->result. | |
c490a46a | 381 | * @return array |
ade83e4b | 382 | */ |
6a488035 TO |
383 | public function result() { |
384 | return $this->lastResult; | |
385 | } | |
96025800 | 386 | |
6a488035 | 387 | } |