3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
29 use Civi\API\Event\AuthorizeEvent
;
30 use Civi\API\Event\PrepareEvent
;
31 use Civi\API\Event\ExceptionEvent
;
32 use Civi\API\Event\ResolveEvent
;
33 use Civi\API\Event\RespondEvent
;
37 * @copyright CiviCRM LLC (c) 2004-2019
42 * @var \Symfony\Component\EventDispatcher\EventDispatcher
44 protected $dispatcher;
47 * @var array<ProviderInterface>
49 protected $apiProviders;
52 * @param \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
53 * The event dispatcher which receives kernel events.
54 * @param array $apiProviders
55 * Array of ProviderInterface.
57 public function __construct($dispatcher, $apiProviders = []) {
58 $this->apiProviders
= $apiProviders;
59 $this->dispatcher
= $dispatcher;
64 * @param string $entity
65 * Type of entities to deal with.
66 * @param string $action
67 * Create, get, delete or some special action name.
68 * @param array $params
69 * Array to be passed to API function.
75 public function run($entity, $action, $params, $extra = NULL) {
76 return $this->runSafe($entity, $action, $params, $extra);
80 * Parse and execute an API request. Any errors will be converted to
83 * @param string $entity
84 * Type of entities to deal with.
85 * @param string $action
86 * Create, get, delete or some special action name.
87 * @param array $params
88 * Array to be passed to API function.
93 * @throws \API_Exception
95 public function runSafe($entity, $action, $params, $extra = NULL) {
96 $apiRequest = Request
::create($entity, $action, $params, $extra);
99 $apiResponse = $this->runRequest($apiRequest);
100 return $this->formatResult($apiRequest, $apiResponse);
102 catch (\Exception
$e) {
103 $this->dispatcher
->dispatch(Events
::EXCEPTION
, new ExceptionEvent($e, NULL, $apiRequest, $this));
105 if ($e instanceof \PEAR_Exception
) {
106 $err = $this->formatPearException($e, $apiRequest);
108 elseif ($e instanceof \API_Exception
) {
109 $err = $this->formatApiException($e, $apiRequest);
112 $err = $this->formatException($e, $apiRequest);
115 return $this->formatResult($apiRequest, $err);
120 * Determine if a hypothetical API call would be authorized.
122 * @param string $entity
123 * Type of entities to deal with.
124 * @param string $action
125 * Create, get, delete or some special action name.
126 * @param array $params
127 * Array to be passed to function.
128 * @param mixed $extra
132 * TRUE if authorization would succeed.
135 public function runAuthorize($entity, $action, $params, $extra = NULL) {
137 $apiRequest = Request
::create($entity, $action, $params, $extra);
140 $this->boot($apiRequest);
141 list($apiProvider, $apiRequest) = $this->resolve($apiRequest);
142 $this->authorize($apiProvider, $apiRequest);
145 catch (\Civi\API\Exception\UnauthorizedException
$e) {
151 * Execute an API request.
153 * The request must be in canonical format. Exceptions will be propagated out.
155 * @param array $apiRequest
157 * @throws \API_Exception
158 * @throws \Civi\API\Exception\NotImplementedException
159 * @throws \Civi\API\Exception\UnauthorizedException
161 public function runRequest($apiRequest) {
162 $this->boot($apiRequest);
163 $errorScope = \CRM_Core_TemporaryErrorScope
::useException();
165 list($apiProvider, $apiRequest) = $this->resolve($apiRequest);
166 $this->authorize($apiProvider, $apiRequest);
167 list ($apiProvider, $apiRequest) = $this->prepare($apiProvider, $apiRequest);
168 $result = $apiProvider->invoke($apiRequest);
170 return $this->respond($apiProvider, $apiRequest, $result);
174 * Bootstrap - Load basic dependencies and sanity-check inputs.
176 * @param \Civi\API\V4\Action|array $apiRequest
177 * @throws \API_Exception
179 public function boot($apiRequest) {
180 require_once 'api/Exception.php';
182 if (!is_array($apiRequest['params'])) {
183 throw new \
API_Exception('Input variable `params` is not an array', 2000);
185 switch ($apiRequest['version']) {
188 require_once 'api/v3/utils.php';
189 _civicrm_api3_initialize();
197 throw new \
API_Exception('Unknown api version', 2000);
202 * @param array $apiRequest
203 * @throws \API_Exception
205 protected function validate($apiRequest) {
209 * Determine which, if any, service will execute the API request.
211 * @param array $apiRequest
212 * The full description of the API request.
213 * @throws Exception\NotImplementedException
215 * A tuple with the provider-object and a revised apiRequest.
216 * Array(0 => ProviderInterface, 1 => array $apiRequest).
218 public function resolve($apiRequest) {
219 /** @var \Civi\API\Event\ResolveEvent $resolveEvent */
220 $resolveEvent = $this->dispatcher
->dispatch(Events
::RESOLVE
, new ResolveEvent($apiRequest, $this));
221 $apiRequest = $resolveEvent->getApiRequest();
222 if (!$resolveEvent->getApiProvider()) {
223 throw new \Civi\API\Exception\
NotImplementedException("API (" . $apiRequest['entity'] . ", " . $apiRequest['action'] . ") does not exist (join the API team and implement it!)");
225 return [$resolveEvent->getApiProvider(), $apiRequest];
229 * Determine if the API request is allowed (under current policy)
231 * @param \Civi\API\Provider\ProviderInterface $apiProvider
232 * The API provider responsible for executing the request.
233 * @param array $apiRequest
234 * The full description of the API request.
235 * @throws Exception\UnauthorizedException
237 public function authorize($apiProvider, $apiRequest) {
238 /** @var \Civi\API\Event\AuthorizeEvent $event */
239 $event = $this->dispatcher
->dispatch(Events
::AUTHORIZE
, new AuthorizeEvent($apiProvider, $apiRequest, $this));
240 if (!$event->isAuthorized()) {
241 throw new \Civi\API\Exception\
UnauthorizedException("Authorization failed");
246 * Allow third-party code to manipulate the API request before execution.
248 * @param \Civi\API\Provider\ProviderInterface $apiProvider
249 * The API provider responsible for executing the request.
250 * @param array $apiRequest
251 * The full description of the API request.
253 * [0 => ProviderInterface $provider, 1 => array $apiRequest]
254 * The revised API request.
256 public function prepare($apiProvider, $apiRequest) {
257 /** @var \Civi\API\Event\PrepareEvent $event */
258 $event = $this->dispatcher
->dispatch(Events
::PREPARE
, new PrepareEvent($apiProvider, $apiRequest, $this));
259 return [$event->getApiProvider(), $event->getApiRequest()];
263 * Allow third-party code to manipulate the API response after execution.
265 * @param \Civi\API\Provider\ProviderInterface $apiProvider
266 * The API provider responsible for executing the request.
267 * @param array $apiRequest
268 * The full description of the API request.
269 * @param array $result
270 * The response to return to the client.
272 * The revised $result.
274 public function respond($apiProvider, $apiRequest, $result) {
275 /** @var \Civi\API\Event\RespondEvent $event */
276 $event = $this->dispatcher
->dispatch(Events
::RESPOND
, new RespondEvent($apiProvider, $apiRequest, $result, $this));
277 return $event->getResponse();
281 * @param int $version
286 public function getEntityNames($version) {
287 // Question: Would it better to eliminate $this->apiProviders and just use $this->dispatcher?
289 foreach ($this->getApiProviders() as $provider) {
290 /** @var \Civi\API\Provider\ProviderInterface $provider */
291 $entityNames = array_merge($entityNames, $provider->getEntityNames($version));
293 $entityNames = array_unique($entityNames);
299 * @param int $version
301 * @param string $entity
306 public function getActionNames($version, $entity) {
307 // Question: Would it better to eliminate $this->apiProviders and just use $this->dispatcher?
309 foreach ($this->getApiProviders() as $provider) {
310 /** @var \Civi\API\Provider\ProviderInterface $provider */
311 $actionNames = array_merge($actionNames, $provider->getActionNames($version, $entity));
313 $actionNames = array_unique($actionNames);
319 * @param \Exception $e
320 * An unhandled exception.
321 * @param array $apiRequest
322 * The full description of the API request.
326 public function formatException($e, $apiRequest) {
328 if (!empty($apiRequest['params']['debug'])) {
329 $data['trace'] = $e->getTraceAsString();
331 return $this->createError($e->getMessage(), $data, $apiRequest, $e->getCode());
335 * @param \API_Exception $e
336 * An unhandled exception.
337 * @param array $apiRequest
338 * The full description of the API request.
342 public function formatApiException($e, $apiRequest) {
343 $data = $e->getExtraParams();
344 $data['entity'] = \CRM_Utils_Array
::value('entity', $apiRequest);
345 $data['action'] = \CRM_Utils_Array
::value('action', $apiRequest);
347 if (\CRM_Utils_Array
::value('debug', \CRM_Utils_Array
::value('params', $apiRequest))
349 && empty($data['trace'])
351 $data['trace'] = $e->getTraceAsString();
354 return $this->createError($e->getMessage(), $data, $apiRequest, $e->getCode());
358 * @param \PEAR_Exception $e
359 * An unhandled exception.
360 * @param array $apiRequest
361 * The full description of the API request.
365 public function formatPearException($e, $apiRequest) {
367 $error = $e->getCause();
368 if ($error instanceof \DB_Error
) {
369 $data["error_code"] = \DB
::errorMessage($error->getCode());
370 $data["sql"] = $error->getDebugInfo();
372 if (!empty($apiRequest['params']['debug'])) {
373 if (method_exists($e, 'getUserInfo')) {
374 $data['debug_info'] = $error->getUserInfo();
376 if (method_exists($e, 'getExtraData')) {
377 $data['debug_info'] = $data +
$error->getExtraData();
379 $data['trace'] = $e->getTraceAsString();
382 $data['tip'] = "add debug=1 to your API call to have more info about the error";
385 return $this->createError($e->getMessage(), $data, $apiRequest);
390 * Descriptive error message.
393 * @param array $apiRequest
394 * The full description of the API request.
396 * Doesn't appear to be used.
398 * @throws \API_Exception
402 public function createError($msg, $data, $apiRequest, $code = NULL) {
403 // FIXME what to do with $code?
404 if ($msg == 'DB Error: constraint violation' ||
substr($msg, 0, 9) == 'DB Error:' ||
$msg == 'DB Error: already exists') {
406 $fields = _civicrm_api3_api_getfields($apiRequest);
407 _civicrm_api3_validate_foreign_keys($apiRequest['entity'], $apiRequest['action'], $apiRequest['params'], $fields);
409 catch (\Exception
$e) {
410 $msg = $e->getMessage();
414 $data = \
civicrm_api3_create_error($msg, $data);
416 if (isset($apiRequest['params']) && is_array($apiRequest['params']) && !empty($apiRequest['params']['api.has_parent'])) {
417 $errorCode = empty($data['error_code']) ?
'chained_api_failed' : $data['error_code'];
418 throw new \
API_Exception('Error in call to ' . $apiRequest['entity'] . '_' . $apiRequest['action'] . ' : ' . $msg, $errorCode, $data);
425 * @param array $apiRequest
426 * The full description of the API request.
427 * @param array $result
428 * The response to return to the client.
431 public function formatResult($apiRequest, $result) {
432 if (isset($apiRequest, $apiRequest['params'])) {
433 if (isset($apiRequest['params']['format.is_success']) && $apiRequest['params']['format.is_success'] == 1) {
434 return (empty($result['is_error'])) ?
1 : 0;
437 if (!empty($apiRequest['params']['format.only_id']) && isset($result['id'])) {
439 return $result['id'];
446 * @return array<ProviderInterface>
448 public function getApiProviders() {
449 return $this->apiProviders
;
453 * @param array $apiProviders
454 * Array<ProviderInterface>.
457 public function setApiProviders($apiProviders) {
458 $this->apiProviders
= $apiProviders;
463 * @param \Civi\API\Provider\ProviderInterface $apiProvider
464 * The API provider responsible for executing the request.
467 public function registerApiProvider($apiProvider) {
468 $this->apiProviders
[] = $apiProvider;
469 if ($apiProvider instanceof \Symfony\Component\EventDispatcher\EventSubscriberInterface
) {
470 $this->getDispatcher()->addSubscriber($apiProvider);
476 * @return \Symfony\Component\EventDispatcher\EventDispatcher
478 public function getDispatcher() {
479 return $this->dispatcher
;
483 * @param \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
484 * The event dispatcher which receives kernel events.
487 public function setDispatcher($dispatcher) {
488 $this->dispatcher
= $dispatcher;