/** * @param \Civi\API\Event\Event $event * * @throws \Exception */ public function onApiPrepare_validate(\Civi\API\Event\Event $event) { $apiRequest = $event->getApiRequest(); // Not sure why this is omitted for generic actions. It would make sense to omit 'getfields', but that's only one generic action. if (isset($apiRequest['function']) && !$apiRequest['is_generic'] && isset($apiRequest['fields'])) { _civicrm_api3_validate_fields($apiRequest['entity'], $apiRequest['action'], $apiRequest['params'], $apiRequest['fields']); $event->setApiRequest($apiRequest); } }
function test_civicrm_api3_validate_fields_exception() { $params = array('join_date' => 'abc'); try { $fields = civicrm_api3('Membership', 'getfields', array('action' => 'get')); _civicrm_api3_validate_fields('Membership', 'get', $params, $fields['values']); } catch (Exception $expected) { $this->assertEquals('join_date is not a valid date: abc', $expected->getMessage()); } }
/** * Perform input validation on params that use the join syntax * * Arguably this should be done at the api wrapper level, but doing it here provides a bit more consistency * in that api permissions to perform the join are checked first. * * @param $fieldName * @param $value * @throws \Exception */ private function validateNestedInput($fieldName, &$value) { $stack = explode('.', $fieldName); $spec = $this->apiFieldSpec; $fieldName = array_pop($stack); foreach ($stack as $depth => $name) { $entity = $spec[$name]['FKApiName']; $spec = $spec[$name]['FKApiSpec']; } $params = array($fieldName => $value); \_civicrm_api3_validate_fields($entity, 'get', $params, $spec); $value = $params[$fieldName]; }
/** * * @param <type> $msg * @param <type> $data * @param object $dao DAO / BAO object to be freed here * * @return <type> */ function civicrm_api3_create_error($msg, $data = array(), &$dao = NULL) { //fix me - $dao should be param 4 & 3 should be $apiRequest if (is_object($dao)) { $dao->free(); } if (is_array($dao)) { if ($msg == 'DB Error: constraint violation' || $msg == 'DB Error: already exists') { try { _civicrm_api3_validate_fields($dao['entity'], $dao['action'], $dao['params'], True); } catch (Exception $e) { $msg = $e->getMessage(); } } } $data['is_error'] = 1; $data['error_message'] = $msg; if (is_array($dao) && isset($dao['params']) && is_array($dao['params']) && CRM_Utils_Array::value('api.has_parent', $dao['params'])) { throw new Exception('Error in call to ' . $dao['entity'] . '_' . $dao['action'] . ' : ' . $msg); } return $data; }
/** * @param string $msg * Descriptive error message. * @param array $data * Error data. * @param array $apiRequest * The full description of the API request. * @param mixed $code * Doesn't appear to be used. * * @throws \API_Exception * @return array * Array<type>. */ public function createError($msg, $data, $apiRequest, $code = NULL) { // FIXME what to do with $code? if ($msg == 'DB Error: constraint violation' || substr($msg, 0, 9) == 'DB Error:' || $msg == 'DB Error: already exists') { try { $fields = _civicrm_api3_api_getfields($apiRequest); _civicrm_api3_validate_fields($apiRequest['entity'], $apiRequest['action'], $apiRequest['params'], $fields, TRUE); } catch (\Exception $e) { $msg = $e->getMessage(); } } $data = civicrm_api3_create_error($msg, $data); if (isset($apiRequest['params']) && is_array($apiRequest['params']) && !empty($apiRequest['params']['api.has_parent'])) { $errorCode = empty($data['error_code']) ? 'chained_api_failed' : $data['error_code']; throw new \API_Exception('Error in call to ' . $apiRequest['entity'] . '_' . $apiRequest['action'] . ' : ' . $msg, $errorCode, $data); } return $data; }
function validateFields($entity, $params, $action = 'create') { $bao = 'CRM_Appraisals_BAO_' . $entity; $fields = $bao::fields(); $fieldKeys = $bao::fieldKeys(); $mappedParams = array(); foreach ($fieldKeys as $key => $value) { if (!empty($params[$key])) { $mappedParams[$value] = $params[$key]; } } _civicrm_api3_validate_fields($entity, $action, $mappedParams, $fields); foreach ($fieldKeys as $key => $value) { if (!empty($mappedParams[$value])) { $params[$key] = $mappedParams[$value]; } } return $params; }
/** * @param string $entity * type of entities to deal with * @param string $action * create, get, delete or some special action name. * @param array $params * array to be passed to function * @param null $extra * * @return array|int */ function civicrm_api($entity, $action, $params, $extra = NULL) { $apiRequest = array(); $apiRequest['entity'] = CRM_Utils_String::munge($entity); $apiRequest['action'] = CRM_Utils_String::munge($action); $apiRequest['version'] = civicrm_get_api_version($params); $apiRequest['params'] = $params; $apiRequest['extra'] = $extra; $apiWrappers = array(CRM_Utils_API_HTMLInputCoder::singleton(), CRM_Utils_API_NullOutputCoder::singleton(), CRM_Utils_API_ReloadOption::singleton(), CRM_Utils_API_MatchOption::singleton()); CRM_Utils_Hook::apiWrappers($apiWrappers, $apiRequest); try { require_once 'api/v3/utils.php'; require_once 'api/Exception.php'; if (!is_array($params)) { throw new API_Exception('Input variable `params` is not an array', 2000); } _civicrm_api3_initialize(); $errorScope = CRM_Core_TemporaryErrorScope::useException(); // look up function, file, is_generic $apiRequest += _civicrm_api_resolve($apiRequest); if (strtolower($action) == 'create' || strtolower($action) == 'delete' || strtolower($action) == 'submit') { $apiRequest['is_transactional'] = 1; $transaction = new CRM_Core_Transaction(); } // support multi-lingual requests if ($language = CRM_Utils_Array::value('option.language', $params)) { _civicrm_api_set_locale($language); } _civicrm_api3_api_check_permission($apiRequest['entity'], $apiRequest['action'], $apiRequest['params']); $fields = _civicrm_api3_api_getfields($apiRequest); // we do this before we _civicrm_api3_swap_out_aliases($apiRequest, $fields); if (strtolower($action) != 'getfields') { if (empty($apiRequest['params']['id'])) { $apiRequest['params'] = array_merge(_civicrm_api3_getdefaults($apiRequest, $fields), $apiRequest['params']); } //if 'id' is set then only 'version' will be checked but should still be checked for consistency civicrm_api3_verify_mandatory($apiRequest['params'], NULL, _civicrm_api3_getrequired($apiRequest, $fields)); } // For input filtering, process $apiWrappers in forward order foreach ($apiWrappers as $apiWrapper) { $apiRequest = $apiWrapper->fromApiInput($apiRequest); } $function = $apiRequest['function']; if ($apiRequest['function'] && $apiRequest['is_generic']) { // Unlike normal API implementations, generic implementations require explicit // knowledge of the entity and action (as well as $params). Bundle up these bits // into a convenient data structure. $result = $function($apiRequest); } elseif ($apiRequest['function'] && !$apiRequest['is_generic']) { _civicrm_api3_validate_fields($apiRequest['entity'], $apiRequest['action'], $apiRequest['params'], $fields); $result = isset($extra) ? $function($apiRequest['params'], $extra) : $function($apiRequest['params']); } else { return civicrm_api3_create_error("API (" . $apiRequest['entity'] . ", " . $apiRequest['action'] . ") does not exist (join the API team and implement it!)"); } // For output filtering, process $apiWrappers in reverse order foreach (array_reverse($apiWrappers) as $apiWrapper) { $result = $apiWrapper->toApiOutput($apiRequest, $result); } if (CRM_Utils_Array::value('format.is_success', $apiRequest['params']) == 1) { if ($result['is_error'] === 0) { return 1; } else { return 0; } } if (!empty($apiRequest['params']['format.only_id']) && isset($result['id'])) { return $result['id']; } if (CRM_Utils_Array::value('is_error', $result, 0) == 0) { _civicrm_api_call_nested_api($apiRequest['params'], $result, $apiRequest['action'], $apiRequest['entity'], $apiRequest['version']); } if (function_exists('xdebug_time_index') && CRM_Utils_Array::value('debug', $apiRequest['params']) && is_array($result)) { $result['xdebug']['peakMemory'] = xdebug_peak_memory_usage(); $result['xdebug']['memory'] = xdebug_memory_usage(); $result['xdebug']['timeIndex'] = xdebug_time_index(); } return $result; } catch (PEAR_Exception $e) { if (CRM_Utils_Array::value('format.is_success', $apiRequest['params']) == 1) { return 0; } $error = $e->getCause(); if ($error instanceof DB_Error) { $data["error_code"] = DB::errorMessage($error->getCode()); $data["sql"] = $error->getDebugInfo(); } if (!empty($apiRequest['params']['debug'])) { if (method_exists($e, 'getUserInfo')) { $data['debug_info'] = $error->getUserInfo(); } if (method_exists($e, 'getExtraData')) { $data['debug_info'] = $data + $error->getExtraData(); } $data['trace'] = $e->getTraceAsString(); } else { $data['tip'] = "add debug=1 to your API call to have more info about the error"; } $err = civicrm_api3_create_error($e->getMessage(), $data, $apiRequest); if (!empty($apiRequest['is_transactional'])) { $transaction->rollback(); } return $err; } catch (API_Exception $e) { if (!isset($apiRequest)) { $apiRequest = array(); } if (CRM_Utils_Array::value('format.is_success', CRM_Utils_Array::value('params', $apiRequest)) == 1) { return 0; } $data = $e->getExtraParams(); $data['entity'] = CRM_Utils_Array::value('entity', $apiRequest); $data['action'] = CRM_Utils_Array::value('action', $apiRequest); $err = civicrm_api3_create_error($e->getMessage(), $data, $apiRequest, $e->getCode()); if (CRM_Utils_Array::value('debug', CRM_Utils_Array::value('params', $apiRequest)) && empty($data['trace'])) { $err['trace'] = $e->getTraceAsString(); } if (!empty($apiRequest['is_transactional'])) { $transaction->rollback(); } return $err; } catch (Exception $e) { if (CRM_Utils_Array::value('format.is_success', $apiRequest['params']) == 1) { return 0; } $data = array(); $err = civicrm_api3_create_error($e->getMessage(), $data, $apiRequest, $e->getCode()); if (!empty($apiRequest['params']['debug'])) { $err['trace'] = $e->getTraceAsString(); } if (!empty($apiRequest['is_transactional'])) { $transaction->rollback(); } return $err; } }
/** * * @param <type> $data * @param array $data * @param object $dao DAO / BAO object to be freed here * * @throws API_Exception * @return array <type> */ function civicrm_api3_create_error($msg, $data = array(), &$dao = NULL) { //fix me - $dao should be param 4 & 3 should be $apiRequest if (is_object($dao)) { $dao->free(); } if (is_array($dao)) { if ($msg == 'DB Error: constraint violation' || substr($msg, 0, 9) == 'DB Error:' || $msg == 'DB Error: already exists') { try { $fields = _civicrm_api3_api_getfields($dao); _civicrm_api3_validate_fields($dao['entity'], $dao['action'], $dao['params'], $fields, TRUE); } catch (Exception $e) { $msg = $e->getMessage(); } } } $data['is_error'] = 1; $data['error_message'] = $msg; // we will show sql to privelledged user only (not sure of a specific // security hole here but seems sensible - perhaps should apply to the trace as well? if (isset($data['sql']) && CRM_Core_Permission::check('Administer CiviCRM')) { $data['debug_information'] = $data['sql']; } if (is_array($dao) && isset($dao['params']) && is_array($dao['params']) && !empty($dao['params']['api.has_parent'])) { $errorCode = empty($data['error_code']) ? 'chained_api_failed' : $data['error_code']; throw new API_Exception('Error in call to ' . $dao['entity'] . '_' . $dao['action'] . ' : ' . $msg, $errorCode, $data); } return $data; }
function civicrm_api($entity, $action, $params, $extra = NULL) { $apiWrappers = array(CRM_Core_HTMLInputCoder::singleton()); try { require_once 'api/v3/utils.php'; require_once 'api/Exception.php'; if (!is_array($params)) { throw new API_Exception('Input variable `params` is not an array', 2000); } _civicrm_api3_initialize(); $errorScope = CRM_Core_TemporaryErrorScope::useException(); require_once 'CRM/Utils/String.php'; require_once 'CRM/Utils/Array.php'; $apiRequest = array(); $apiRequest['entity'] = CRM_Utils_String::munge($entity); $apiRequest['action'] = CRM_Utils_String::munge($action); $apiRequest['version'] = civicrm_get_api_version($params); $apiRequest['params'] = $params; $apiRequest['extra'] = $extra; // look up function, file, is_generic $apiRequest += _civicrm_api_resolve($apiRequest); if (strtolower($action) == 'create' || strtolower($action) == 'delete') { $apiRequest['is_transactional'] = 1; $tx = new CRM_Core_Transaction(); } $errorFnName = $apiRequest['version'] == 2 ? 'civicrm_create_error' : 'civicrm_api3_create_error'; if ($apiRequest['version'] > 2) { _civicrm_api3_api_check_permission($apiRequest['entity'], $apiRequest['action'], $apiRequest['params']); } // we do this before we _civicrm_api3_swap_out_aliases($apiRequest); if (strtolower($action) != 'getfields') { if (!CRM_Utils_Array::value('id', $params)) { $apiRequest['params'] = array_merge(_civicrm_api3_getdefaults($apiRequest), $apiRequest['params']); } //if 'id' is set then only 'version' will be checked but should still be checked for consistency civicrm_api3_verify_mandatory($apiRequest['params'], NULL, _civicrm_api3_getrequired($apiRequest)); } foreach ($apiWrappers as $apiWrapper) { $apiRequest = $apiWrapper->fromApiInput($apiRequest); } $function = $apiRequest['function']; if ($apiRequest['function'] && $apiRequest['is_generic']) { // Unlike normal API implementations, generic implementations require explicit // knowledge of the entity and action (as well as $params). Bundle up these bits // into a convenient data structure. $result = $function($apiRequest); } elseif ($apiRequest['function'] && !$apiRequest['is_generic']) { _civicrm_api3_validate_fields($apiRequest['entity'], $apiRequest['action'], $apiRequest['params']); $result = isset($extra) ? $function($apiRequest['params'], $extra) : $function($apiRequest['params']); } else { return $errorFnName("API (" . $apiRequest['entity'] . "," . $apiRequest['action'] . ") does not exist (join the API team and implement it!)"); } foreach ($apiWrappers as $apiWrapper) { $result = $apiWrapper->toApiOutput($apiRequest, $result); } if (CRM_Utils_Array::value('format.is_success', $apiRequest['params']) == 1) { if ($result['is_error'] === 0) { return 1; } else { return 0; } } if (CRM_Utils_Array::value('format.only_id', $apiRequest['params']) && isset($result['id'])) { return $result['id']; } if (CRM_Utils_Array::value('is_error', $result, 0) == 0) { _civicrm_api_call_nested_api($apiRequest['params'], $result, $apiRequest['action'], $apiRequest['entity'], $apiRequest['version']); } if (CRM_Utils_Array::value('format.smarty', $apiRequest['params']) || CRM_Utils_Array::value('format_smarty', $apiRequest['params'])) { // return _civicrm_api_parse_result_through_smarty($result,$apiRequest['params']); } if (function_exists('xdebug_time_index') && CRM_Utils_Array::value('debug', $apiRequest['params']) && is_array($result)) { $result['xdebug']['peakMemory'] = xdebug_peak_memory_usage(); $result['xdebug']['memory'] = xdebug_memory_usage(); $result['xdebug']['timeIndex'] = xdebug_time_index(); } return $result; } catch (PEAR_Exception $e) { if (CRM_Utils_Array::value('format.is_success', $apiRequest['params']) == 1) { return 0; } $data = array(); $err = civicrm_api3_create_error($e->getMessage(), $data, $apiRequest); if (CRM_Utils_Array::value('debug', $apiRequest['params'])) { $err['trace'] = $e->getTraceSafe(); } else { $err['tip'] = "add debug=1 to your API call to have more info about the error"; } if (CRM_Utils_Array::value('is_transactional', $apiRequest)) { $tx->rollback(); } return $err; } catch (API_Exception $e) { if (!isset($apiRequest)) { $apiRequest = array(); } if (CRM_Utils_Array::value('format.is_success', CRM_Utils_Array::value('params', $apiRequest)) == 1) { return 0; } $data = $e->getExtraParams(); $err = civicrm_api3_create_error($e->getMessage(), $data, $apiRequest, $e->getCode()); if (CRM_Utils_Array::value('debug', CRM_Utils_Array::value('params', $apiRequest))) { $err['trace'] = $e->getTraceAsString(); } if (CRM_Utils_Array::value('is_transactional', CRM_Utils_Array::value('params', $apiRequest))) { $tx->rollback(); } return $err; } catch (Exception $e) { if (CRM_Utils_Array::value('format.is_success', $apiRequest['params']) == 1) { return 0; } $data = array(); $err = civicrm_api3_create_error($e->getMessage(), $data, $apiRequest, $e->getCode()); if (CRM_Utils_Array::value('debug', $apiRequest['params'])) { $err['trace'] = $e->getTraceAsString(); } if (CRM_Utils_Array::value('is_transactional', $apiRequest)) { $tx->rollback(); } return $err; } }