3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.4 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
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
;
39 * @copyright CiviCRM LLC (c) 2004-2013
45 * @var \Symfony\Component\EventDispatcher\EventDispatcher
47 protected $dispatcher;
50 * @var array<ProviderInterface>
52 protected $apiProviders;
54 function __construct($dispatcher, $apiProviders = array()) {
55 $this->apiProviders
= $apiProviders;
56 $this->dispatcher
= $dispatcher;
60 * @param string $entity
61 * type of entities to deal with
62 * @param string $action
63 * create, get, delete or some special action name.
64 * @param array $params
65 * array to be passed to function
70 public function run($entity, $action, $params, $extra) {
72 * @var $apiProvider \Civi\API\Provider\ProviderInterface|NULL
76 // TODO Define alternative calling convention makes it easier to construct $apiRequest
77 // without the ambiguity of "data" vs "options"
78 $apiRequest = $this->createRequest($entity, $action, $params, $extra);
81 if (!is_array($params)) {
82 throw new \
API_Exception('Input variable `params` is not an array', 2000);
86 $errorScope = \CRM_Core_TemporaryErrorScope
::useException();
88 list($apiProvider, $apiRequest) = $this->resolve($apiRequest);
89 $this->authorize($apiProvider, $apiRequest);
90 $apiRequest = $this->prepare($apiProvider, $apiRequest);
91 $result = $apiProvider->invoke($apiRequest);
93 if (\CRM_Utils_Array
::value('is_error', $result, 0) == 0) {
94 _civicrm_api_call_nested_api($apiRequest['params'], $result, $apiRequest['action'], $apiRequest['entity'], $apiRequest['version']);
97 $apiResponse = $this->respond($apiProvider, $apiRequest, $result);
98 return $this->formatResult($apiRequest, $apiResponse);
100 catch (\Exception
$e) {
101 $this->dispatcher
->dispatch(Events
::EXCEPTION
, new ExceptionEvent($e, $apiProvider, $apiRequest));
103 if ($e instanceof \PEAR_Exception
) {
104 $err = $this->formatPearException($e, $apiRequest);
105 } elseif ($e instanceof \API_Exception
) {
106 $err = $this->formatApiException($e, $apiRequest);
108 $err = $this->formatException($e, $apiRequest);
111 return $this->formatResult($apiRequest, $err);
117 * Create a formatted/normalized request object.
119 * @param string $entity
120 * @param string $action
121 * @param array $params
122 * @param mixed $extra
123 * @return array the request descriptor; keys:
127 * - params: array (string $key => mixed $value) [deprecated in v4]
128 * - extra: unspecified
129 * - fields: NULL|array (string $key => array $fieldSpec)
130 * - options: \CRM_Utils_OptionBag derived from params [v4-only]
131 * - data: \CRM_Utils_OptionBag derived from params [v4-only]
132 * - chains: unspecified derived from params [v4-only]
134 public function createRequest($entity, $action, $params, $extra) {
135 $apiRequest = array(); // new \Civi\API\Request();
136 $apiRequest['version'] = civicrm_get_api_version($params);
137 $apiRequest['params'] = $params;
138 $apiRequest['extra'] = $extra;
139 $apiRequest['fields'] = NULL;
141 if ($apiRequest['version'] <= 3) {
142 // APIv1-v3 munges entity/action names, which means that the same name can be written
143 // multiple ways. That makes it harder to work with.
144 $apiRequest['entity'] = \CRM_Utils_String
::munge($entity);
145 $apiRequest['action'] = \CRM_Utils_String
::munge($action);
148 // APIv4 requires exact entity/action name; deviations should cause errors
149 if (!preg_match('/^[a-zA-Z][a-zA-Z0-9]*$/', $entity)) {
150 throw new \
API_Exception("Malformed entity");
152 if (!preg_match('/^[a-zA-Z][a-zA-Z0-9]*$/', $action)) {
153 throw new \
API_Exception("Malformed action");
155 $apiRequest['entity'] = $entity;
156 $apiRequest['action'] = $action;
159 // APIv1-v3 mix data+options in $params which means that each API callback is responsible
160 // for splitting the two. In APIv4, the split is done systematically so that we don't
161 // so much parsing logic spread around.
162 if ($apiRequest['version'] >= 4) {
166 foreach ($params as $key => $value) {
167 if ($key == 'options') {
168 $options = array_merge($options, $value);
170 elseif ($key == 'return') {
171 if (!isset($options['return'])) {
172 $options['return'] = array();
174 $options['return'] = array_merge($options['return'], $value);
176 elseif (preg_match('/^option\.(.*)$/', $key, $matches)) {
177 $options[$matches[1]] = $value;
179 elseif (preg_match('/^return\.(.*)$/', $key, $matches)) {
181 if (!isset($options['return'])) {
182 $options['return'] = array();
184 $options['return'][] = $matches[1];
187 elseif (preg_match('/^format\.(.*)$/', $key, $matches)) {
189 if (!isset($options['format'])) {
190 $options['format'] = $matches[1];
193 throw new \
API_Exception("Too many API formats specified");
197 elseif (preg_match('/^api\./', $key)) {
198 // FIXME: represent subrequests as instances of "Request"
199 $chains[$key] = $value;
201 elseif ($key == 'debug') {
202 $options['debug'] = $value;
204 elseif ($key == 'version') {
208 $data[$key] = $value;
212 $apiRequest['options'] = new \
CRM_Utils_OptionBag($options);
213 $apiRequest['data'] = new \
CRM_Utils_OptionBag($data);
214 $apiRequest['chains'] = $chains;
220 public function boot() {
221 require_once ('api/v3/utils.php');
222 require_once 'api/Exception.php';
223 _civicrm_api3_initialize();
227 * Determine which, if any, service will execute the API request.
231 * @throws \API_Exception
233 public function resolve($apiRequest) {
234 $resolveEvent = $this->dispatcher
->dispatch(Events
::RESOLVE
, new ResolveEvent($apiRequest));
235 $apiRequest = $resolveEvent->getApiRequest();
236 if (!$resolveEvent->getApiProvider()) {
237 throw new \Civi\API\Exception\
NotImplementedException("API (" . $apiRequest['entity'] . ", " . $apiRequest['action'] . ") does not exist (join the API team and implement it!)");
239 return array($resolveEvent->getApiProvider(), $apiRequest);
243 * Determine if the API request is allowed (under current policy)
245 * @param ProviderInterface $apiProvider
246 * @param array $apiRequest
247 * @throws \API_Exception
249 public function authorize($apiProvider, $apiRequest) {
250 $event = $this->dispatcher
->dispatch(Events
::AUTHORIZE
, new AuthorizeEvent($apiProvider, $apiRequest));
251 if (!$event->isAuthorized()) {
252 throw new \Civi\API\Exception\
UnauthorizedException("Authorization failed");
257 * Allow third-party code to manipulate the API request before execution.
259 * @param ProviderInterface $apiProvider
260 * @param array $apiRequest
263 public function prepare($apiProvider, $apiRequest) {
264 $event = $this->dispatcher
->dispatch(Events
::PREPARE
, new PrepareEvent($apiProvider, $apiRequest));
265 return $event->getApiRequest();
269 * Allow third-party code to manipulate the API response after execution.
271 * @param ProviderInterface $apiProvider
272 * @param array $apiRequest
273 * @param array $result
276 public function respond($apiProvider, $apiRequest, $result) {
277 $event = $this->dispatcher
->dispatch(Events
::RESPOND
, new RespondEvent($apiProvider, $apiRequest, $result));
278 return $event->getResponse();
282 * @param \Exception $e
283 * @param array $apiRequest
284 * @return array (API response)
286 public function formatException($e, $apiRequest) {
288 if (!empty($apiRequest['params']['debug'])) {
289 $data['trace'] = $e->getTraceAsString();
291 return $this->createError($e->getMessage(), $data, $apiRequest, $e->getCode());
295 * @param \API_Exception $e
296 * @param array $apiRequest
297 * @return array (API response)
299 public function formatApiException($e, $apiRequest) {
300 $data = $e->getExtraParams();
301 $data['entity'] = \CRM_Utils_Array
::value('entity', $apiRequest);
302 $data['action'] = \CRM_Utils_Array
::value('action', $apiRequest);
304 if (\CRM_Utils_Array
::value('debug', \CRM_Utils_Array
::value('params', $apiRequest))
305 && empty($data['trace']) // prevent recursion
307 $data['trace'] = $e->getTraceAsString();
310 return $this->createError($e->getMessage(), $data, $apiRequest, $e->getCode());
314 * @param \PEAR_Exception $e
315 * @param array $apiRequest
316 * @return array (API response)
318 public function formatPearException($e, $apiRequest) {
320 $error = $e->getCause();
321 if ($error instanceof \DB_Error
) {
322 $data["error_code"] = \DB
::errorMessage($error->getCode());
323 $data["sql"] = $error->getDebugInfo();
325 if (!empty($apiRequest['params']['debug'])) {
326 if (method_exists($e, 'getUserInfo')) {
327 $data['debug_info'] = $error->getUserInfo();
329 if (method_exists($e, 'getExtraData')) {
330 $data['debug_info'] = $data +
$error->getExtraData();
332 $data['trace'] = $e->getTraceAsString();
335 $data['tip'] = "add debug=1 to your API call to have more info about the error";
338 return $this->createError($e->getMessage(), $data, $apiRequest);
343 * @param <type> $data
345 * @param object $apiRequest DAO / BAO object to be freed here
347 * @throws API_Exception
348 * @return array <type>
350 function createError($msg, $data, $apiRequest, $code = NULL) {
351 // FIXME what to do with $code?
352 if ($msg == 'DB Error: constraint violation' ||
substr($msg, 0, 9) == 'DB Error:' ||
$msg == 'DB Error: already exists') {
354 $fields = _civicrm_api3_api_getfields($apiRequest);
355 _civicrm_api3_validate_fields($apiRequest['entity'], $apiRequest['action'], $apiRequest['params'], $fields, TRUE);
356 } catch (Exception
$e) {
357 $msg = $e->getMessage();
361 $data = civicrm_api3_create_error($msg, $data);
363 if (isset($apiRequest['params']) && is_array($apiRequest['params']) && !empty($apiRequest['params']['api.has_parent'])) {
364 $errorCode = empty($data['error_code']) ?
'chained_api_failed' : $data['error_code'];
365 throw new \
API_Exception('Error in call to ' . $apiRequest['entity'] . '_' . $apiRequest['action'] . ' : ' . $msg, $errorCode, $data);
374 public function formatResult($apiRequest, $result) {
375 if (isset($apiRequest, $apiRequest['params'])) {
376 if (isset($apiRequest['params']['format.is_success']) && $apiRequest['params']['format.is_success'] == 1) {
377 return (empty($result['is_error'])) ?
1 : 0;
380 if (!empty($apiRequest['params']['format.only_id']) && isset($result['id'])) {
382 return $result['id'];