3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2015 |
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
;
34 use Civi\API\Provider\ProviderInterface
;
38 * @copyright CiviCRM LLC (c) 2004-2015
43 * @var \Symfony\Component\EventDispatcher\EventDispatcher
45 protected $dispatcher;
48 * @var array<ProviderInterface>
50 protected $apiProviders;
53 * @param \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
54 * The event dispatcher which receives kernel events.
55 * @param array $apiProviders
56 * Array of ProviderInterface.
58 public function __construct($dispatcher, $apiProviders = array()) {
59 $this->apiProviders
= $apiProviders;
60 $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) {
77 * @var $apiProvider \Civi\API\Provider\ProviderInterface|NULL
81 // TODO Define alternative calling convention makes it easier to construct $apiRequest
82 // without the ambiguity of "data" vs "options"
83 $apiRequest = Request
::create($entity, $action, $params, $extra);
86 if (!is_array($params)) {
87 throw new \
API_Exception('Input variable `params` is not an array', 2000);
91 $errorScope = \CRM_Core_TemporaryErrorScope
::useException();
93 list($apiProvider, $apiRequest) = $this->resolve($apiRequest);
94 $this->authorize($apiProvider, $apiRequest);
95 $apiRequest = $this->prepare($apiProvider, $apiRequest);
96 $result = $apiProvider->invoke($apiRequest);
98 $apiResponse = $this->respond($apiProvider, $apiRequest, $result);
99 return $this->formatResult($apiRequest, $apiResponse);
101 catch (\Exception
$e) {
102 $this->dispatcher
->dispatch(Events
::EXCEPTION
, new ExceptionEvent($e, $apiProvider, $apiRequest, $this));
104 if ($e instanceof \PEAR_Exception
) {
105 $err = $this->formatPearException($e, $apiRequest);
107 elseif ($e instanceof \API_Exception
) {
108 $err = $this->formatApiException($e, $apiRequest);
111 $err = $this->formatException($e, $apiRequest);
114 return $this->formatResult($apiRequest, $err);
119 * Determine if a hypothetical API call would be authorized.
121 * @param string $entity
122 * Type of entities to deal with.
123 * @param string $action
124 * Create, get, delete or some special action name.
125 * @param array $params
126 * Array to be passed to function.
127 * @param mixed $extra
130 * TRUE if authorization would succeed.
133 public function runAuthorize($entity, $action, $params, $extra = NULL) {
135 $apiRequest = Request
::create($entity, $action, $params, $extra);
139 list($apiProvider, $apiRequest) = $this->resolve($apiRequest);
140 $this->authorize($apiProvider, $apiRequest);
143 catch (\Civi\API\Exception\UnauthorizedException
$e) {
149 * Bootstrap - Load basic dependencies.
151 public function boot() {
152 require_once 'api/v3/utils.php';
153 require_once 'api/Exception.php';
154 _civicrm_api3_initialize();
158 * Determine which, if any, service will execute the API request.
160 * @param array $apiRequest
161 * The full description of the API request.
162 * @throws Exception\NotImplementedException
164 * Array(0 => ProviderInterface, 1 => array).
166 public function resolve($apiRequest) {
167 /** @var ResolveEvent $resolveEvent */
168 $resolveEvent = $this->dispatcher
->dispatch(Events
::RESOLVE
, new ResolveEvent($apiRequest, $this));
169 $apiRequest = $resolveEvent->getApiRequest();
170 if (!$resolveEvent->getApiProvider()) {
171 throw new \Civi\API\Exception\
NotImplementedException("API (" . $apiRequest['entity'] . ", " . $apiRequest['action'] . ") does not exist (join the API team and implement it!)");
173 return array($resolveEvent->getApiProvider(), $apiRequest);
177 * Determine if the API request is allowed (under current policy)
179 * @param ProviderInterface $apiProvider
180 * The API provider responsible for executing the request.
181 * @param array $apiRequest
182 * The full description of the API request.
183 * @throws Exception\UnauthorizedException
185 public function authorize($apiProvider, $apiRequest) {
186 /** @var AuthorizeEvent $event */
187 $event = $this->dispatcher
->dispatch(Events
::AUTHORIZE
, new AuthorizeEvent($apiProvider, $apiRequest, $this));
188 if (!$event->isAuthorized()) {
189 throw new \Civi\API\Exception\
UnauthorizedException("Authorization failed");
194 * Allow third-party code to manipulate the API request before execution.
196 * @param ProviderInterface $apiProvider
197 * The API provider responsible for executing the request.
198 * @param array $apiRequest
199 * The full description of the API request.
202 public function prepare($apiProvider, $apiRequest) {
203 /** @var PrepareEvent $event */
204 $event = $this->dispatcher
->dispatch(Events
::PREPARE
, new PrepareEvent($apiProvider, $apiRequest, $this));
205 return $event->getApiRequest();
209 * Allow third-party code to manipulate the API response after execution.
211 * @param ProviderInterface $apiProvider
212 * The API provider responsible for executing the request.
213 * @param array $apiRequest
214 * The full description of the API request.
215 * @param array $result
216 * The response to return to the client.
219 public function respond($apiProvider, $apiRequest, $result) {
220 /** @var RespondEvent $event */
221 $event = $this->dispatcher
->dispatch(Events
::RESPOND
, new RespondEvent($apiProvider, $apiRequest, $result, $this));
222 return $event->getResponse();
226 * @param int $version
231 public function getEntityNames($version) {
232 // Question: Would it better to eliminate $this->apiProviders and just use $this->dispatcher?
233 $entityNames = array();
234 foreach ($this->getApiProviders() as $provider) {
235 /** @var ProviderInterface $provider */
236 $entityNames = array_merge($entityNames, $provider->getEntityNames($version));
238 $entityNames = array_unique($entityNames);
244 * @param int $version
246 * @param string $entity
251 public function getActionNames($version, $entity) {
252 // Question: Would it better to eliminate $this->apiProviders and just use $this->dispatcher?
253 $actionNames = array();
254 foreach ($this->getApiProviders() as $provider) {
255 /** @var ProviderInterface $provider */
256 $actionNames = array_merge($actionNames, $provider->getActionNames($version, $entity));
258 $actionNames = array_unique($actionNames);
264 * @param \Exception $e
265 * An unhandled exception.
266 * @param array $apiRequest
267 * The full description of the API request.
271 public function formatException($e, $apiRequest) {
273 if (!empty($apiRequest['params']['debug'])) {
274 $data['trace'] = $e->getTraceAsString();
276 return $this->createError($e->getMessage(), $data, $apiRequest, $e->getCode());
280 * @param \API_Exception $e
281 * An unhandled exception.
282 * @param array $apiRequest
283 * The full description of the API request.
287 public function formatApiException($e, $apiRequest) {
288 $data = $e->getExtraParams();
289 $data['entity'] = \CRM_Utils_Array
::value('entity', $apiRequest);
290 $data['action'] = \CRM_Utils_Array
::value('action', $apiRequest);
292 if (\CRM_Utils_Array
::value('debug', \CRM_Utils_Array
::value('params', $apiRequest))
293 && empty($data['trace']) // prevent recursion
295 $data['trace'] = $e->getTraceAsString();
298 return $this->createError($e->getMessage(), $data, $apiRequest, $e->getCode());
302 * @param \PEAR_Exception $e
303 * An unhandled exception.
304 * @param array $apiRequest
305 * The full description of the API request.
309 public function formatPearException($e, $apiRequest) {
311 $error = $e->getCause();
312 if ($error instanceof \DB_Error
) {
313 $data["error_code"] = \DB
::errorMessage($error->getCode());
314 $data["sql"] = $error->getDebugInfo();
316 if (!empty($apiRequest['params']['debug'])) {
317 if (method_exists($e, 'getUserInfo')) {
318 $data['debug_info'] = $error->getUserInfo();
320 if (method_exists($e, 'getExtraData')) {
321 $data['debug_info'] = $data +
$error->getExtraData();
323 $data['trace'] = $e->getTraceAsString();
326 $data['tip'] = "add debug=1 to your API call to have more info about the error";
329 return $this->createError($e->getMessage(), $data, $apiRequest);
334 * Descriptive error message.
337 * @param array $apiRequest
338 * The full description of the API request.
340 * Doesn't appear to be used.
342 * @throws \API_Exception
346 public function createError($msg, $data, $apiRequest, $code = NULL) {
347 // FIXME what to do with $code?
348 if ($msg == 'DB Error: constraint violation' ||
substr($msg, 0, 9) == 'DB Error:' ||
$msg == 'DB Error: already exists') {
350 $fields = _civicrm_api3_api_getfields($apiRequest);
351 _civicrm_api3_validate_fields($apiRequest['entity'], $apiRequest['action'], $apiRequest['params'], $fields, TRUE);
353 catch (\Exception
$e) {
354 $msg = $e->getMessage();
358 $data = civicrm_api3_create_error($msg, $data);
360 if (isset($apiRequest['params']) && is_array($apiRequest['params']) && !empty($apiRequest['params']['api.has_parent'])) {
361 $errorCode = empty($data['error_code']) ?
'chained_api_failed' : $data['error_code'];
362 throw new \
API_Exception('Error in call to ' . $apiRequest['entity'] . '_' . $apiRequest['action'] . ' : ' . $msg, $errorCode, $data);
369 * @param array $apiRequest
370 * The full description of the API request.
371 * @param array $result
372 * The response to return to the client.
375 public function formatResult($apiRequest, $result) {
376 if (isset($apiRequest, $apiRequest['params'])) {
377 if (isset($apiRequest['params']['format.is_success']) && $apiRequest['params']['format.is_success'] == 1) {
378 return (empty($result['is_error'])) ?
1 : 0;
381 if (!empty($apiRequest['params']['format.only_id']) && isset($result['id'])) {
383 return $result['id'];
390 * @return array<ProviderInterface>
392 public function getApiProviders() {
393 return $this->apiProviders
;
397 * @param array $apiProviders
398 * Array<ProviderInterface>.
401 public function setApiProviders($apiProviders) {
402 $this->apiProviders
= $apiProviders;
407 * @param ProviderInterface $apiProvider
408 * The API provider responsible for executing the request.
411 public function registerApiProvider($apiProvider) {
412 $this->apiProviders
[] = $apiProvider;
413 if ($apiProvider instanceof \Symfony\Component\EventDispatcher\EventSubscriberInterface
) {
414 $this->getDispatcher()->addSubscriber($apiProvider);
420 * @return \Symfony\Component\EventDispatcher\EventDispatcher
422 public function getDispatcher() {
423 return $this->dispatcher
;
427 * @param \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
428 * The event dispatcher which receives kernel events.
431 public function setDispatcher($dispatcher) {
432 $this->dispatcher
= $dispatcher;