Most of api4 is in an extension - these are the changes needed to core.
->addScriptFile('civicrm', 'templates/CRM/Admin/Page/APIExplorer.js')
->addScriptFile('civicrm', 'bower_components/google-code-prettify/bin/prettify.min.js', 99)
->addStyleFile('civicrm', 'bower_components/google-code-prettify/bin/prettify.min.css', 99)
- ->addVars('explorer', array('max_joins' => \Civi\API\SelectQuery::MAX_JOINS));
+ ->addVars('explorer', array('max_joins' => \Civi\API\Api3SelectQuery::MAX_JOINS));
$this->assign('operators', CRM_Core_DAO::acceptedSQLOperators());
--- /dev/null
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.7 |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2015 |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM. |
+ | |
+ | CiviCRM is free software; you can copy, modify, and distribute it |
+ | under the terms of the GNU Affero General Public License |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
+ | |
+ | CiviCRM is distributed in the hope that it will be useful, but |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
+ | See the GNU Affero General Public License for more details. |
+ | |
+ | You should have received a copy of the GNU Affero General Public |
+ | License and the CiviCRM Licensing Exception along |
+ | with this program; if not, contact CiviCRM LLC |
+ | at info[AT]civicrm[DOT]org. If you have questions about the |
+ | GNU Affero General Public License or the licensing of CiviCRM, |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+namespace Civi\API;
+
+/**
+ */
+class Api3SelectQuery extends SelectQuery {
+
+ protected $apiVersion = 3;
+
+ /**
+ * @inheritDoc
+ */
+ protected function buildWhereClause() {
+ foreach ($this->where as $key => $value) {
+ $table_name = NULL;
+ $column_name = NULL;
+
+ if (substr($key, 0, 7) == 'filter.') {
+ // Legacy support for old filter syntax per the test contract.
+ // (Convert the style to the later one & then deal with them).
+ $filterArray = explode('.', $key);
+ $value = array($filterArray[1] => $value);
+ $key = 'filters';
+ }
+
+ // Legacy support for 'filter's construct.
+ if ($key == 'filters') {
+ foreach ($value as $filterKey => $filterValue) {
+ if (substr($filterKey, -4, 4) == 'high') {
+ $key = substr($filterKey, 0, -5);
+ $value = array('<=' => $filterValue);
+ }
+
+ if (substr($filterKey, -3, 3) == 'low') {
+ $key = substr($filterKey, 0, -4);
+ $value = array('>=' => $filterValue);
+ }
+
+ if ($filterKey == 'is_current' || $filterKey == 'isCurrent') {
+ // Is current is almost worth creating as a 'sql filter' in the DAO function since several entities have the concept.
+ $todayStart = date('Ymd000000', strtotime('now'));
+ $todayEnd = date('Ymd235959', strtotime('now'));
+ $a = self::MAIN_TABLE_ALIAS;
+ $this->query->where("($a.start_date <= '$todayStart' OR $a.start_date IS NULL)
+ AND ($a.end_date >= '$todayEnd' OR $a.end_date IS NULL)
+ AND a.is_active = 1");
+ }
+ }
+ }
+ // Ignore the "options" param if it is referring to api options and not a field in this entity
+ if (
+ $key === 'options' && is_array($value)
+ && !in_array(\CRM_Utils_Array::first(array_keys($value)), \CRM_Core_DAO::acceptedSQLOperators())
+ ) {
+ continue;
+ }
+ $field = $this->getField($key);
+ if ($field) {
+ $key = $field['name'];
+ }
+ if (in_array($key, $this->entityFieldNames)) {
+ $table_name = self::MAIN_TABLE_ALIAS;
+ $column_name = $key;
+ }
+ elseif (($cf_id = \CRM_Core_BAO_CustomField::getKeyID($key)) != FALSE) {
+ list($table_name, $column_name) = $this->addCustomField($this->apiFieldSpec['custom_' . $cf_id], 'INNER');
+ }
+ elseif (strpos($key, '.')) {
+ $fkInfo = $this->addFkField($key, 'INNER');
+ if ($fkInfo) {
+ list($table_name, $column_name) = $fkInfo;
+ $this->validateNestedInput($key, $value);
+ }
+ }
+ // I don't know why I had to specifically exclude 0 as a key - wouldn't the others have caught it?
+ // We normally silently ignore null values passed in - if people want IS_NULL they can use acceptedSqlOperator syntax.
+ if ((!$table_name) || empty($key) || is_null($value)) {
+ // No valid filter field. This might be a chained call or something.
+ // Just ignore this for the $where_clause.
+ continue;
+ }
+ if (!is_array($value)) {
+ $this->query->where(array("`$table_name`.`$column_name` = @value"), array(
+ "@value" => $value,
+ ));
+ }
+ else {
+ // We expect only one element in the array, of the form
+ // "operator" => "rhs".
+ $operator = \CRM_Utils_Array::first(array_keys($value));
+ if (!in_array($operator, \CRM_Core_DAO::acceptedSQLOperators())) {
+ $this->query->where(array("{$table_name}.{$column_name} = @value"), array("@value" => $value));
+ }
+ else {
+ $this->query->where(\CRM_Core_DAO::createSQLFilter("{$table_name}.{$column_name}", $value));
+ }
+ }
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function getFields() {
+ require_once 'api/v3/Generic.php';
+ // Call this function directly instead of using the api wrapper to force unique field names off
+ $apiSpec = \civicrm_api3_generic_getfields(array(
+ 'entity' => $this->entity,
+ 'version' => 3,
+ 'params' => array('action' => 'get'),
+ ), FALSE);
+ return $apiSpec['values'];
+ }
+
+ /**
+ * Fetch a field from the getFields list
+ *
+ * Searches by name, uniqueName, and api.aliases
+ */
+ protected function getField($fieldName) {
+ if (!$fieldName) {
+ return NULL;
+ }
+ if (isset($this->apiFieldSpec[$fieldName])) {
+ return $this->apiFieldSpec[$fieldName];
+ }
+ foreach ($this->apiFieldSpec as $field) {
+ if (
+ $fieldName == \CRM_Utils_Array::value('uniqueName', $field) ||
+ array_search($fieldName, \CRM_Utils_Array::value('api.aliases', $field, array())) !== FALSE
+ ) {
+ return $field;
+ }
+ }
+ return NULL;
+ }
+
+}
}
/**
+ * @deprecated
+ * @return array|int
+ * @see runSafe
+ */
+ public function run($entity, $action, $params, $extra = NULL) {
+ return $this->runSafe($entity, $action, $params, $extra);
+ }
+
+ /**
+ * Parse and execute an API request. Any errors will be converted to
+ * normal format.
+ *
* @param string $entity
* Type of entities to deal with.
* @param string $action
* @param array $params
* Array to be passed to API function.
* @param mixed $extra
- * Who knows.
+ * Unused/deprecated.
*
* @return array|int
+ * @throws \API_Exception
*/
- public function run($entity, $action, $params, $extra = NULL) {
+ public function runSafe($entity, $action, $params, $extra = NULL) {
+ // TODO Define alternative calling convention makes it easier to construct $apiRequest
+ // without the ambiguity of "data" vs "options"
+ $apiRequest = Request::create($entity, $action, $params, $extra);
+
/**
* @var $apiProvider \Civi\API\Provider\ProviderInterface|NULL
*/
$apiProvider = NULL;
- // TODO Define alternative calling convention makes it easier to construct $apiRequest
- // without the ambiguity of "data" vs "options"
- $apiRequest = Request::create($entity, $action, $params, $extra);
-
try {
- if (!is_array($params)) {
- throw new \API_Exception('Input variable `params` is not an array', 2000);
- }
-
- $this->boot();
- $errorScope = \CRM_Core_TemporaryErrorScope::useException();
-
- list($apiProvider, $apiRequest) = $this->resolve($apiRequest);
- $this->authorize($apiProvider, $apiRequest);
- $apiRequest = $this->prepare($apiProvider, $apiRequest);
- $result = $apiProvider->invoke($apiRequest);
-
- $apiResponse = $this->respond($apiProvider, $apiRequest, $result);
+ $apiResponse = $this->runRequest($apiRequest);
return $this->formatResult($apiRequest, $apiResponse);
}
catch (\Exception $e) {
- $this->dispatcher->dispatch(Events::EXCEPTION, new ExceptionEvent($e, $apiProvider, $apiRequest, $this));
+ $this->dispatcher->dispatch(Events::EXCEPTION, new ExceptionEvent($e, NULL, $apiRequest, $this));
if ($e instanceof \PEAR_Exception) {
$err = $this->formatPearException($e, $apiRequest);
* @param array $params
* Array to be passed to function.
* @param mixed $extra
- * Who knows.
+ * Unused/deprecated.
+ *
* @return bool
* TRUE if authorization would succeed.
* @throws \Exception
}
}
+ /**
+ * Execute an API request.
+ *
+ * The request must be in canonical format. Exceptions will be propagated out.
+ *
+ * @param $apiRequest
+ * @return array
+ * @throws \API_Exception
+ * @throws \Civi\API\Exception\NotImplementedException
+ * @throws \Civi\API\Exception\UnauthorizedException
+ */
+ public function runRequest($apiRequest) {
+ $this->validate($apiRequest);
+
+ $this->boot();
+ $errorScope = \CRM_Core_TemporaryErrorScope::useException();
+
+ list($apiProvider, $apiRequest) = $this->resolve($apiRequest);
+ $this->authorize($apiProvider, $apiRequest);
+ $apiRequest = $this->prepare($apiProvider, $apiRequest);
+ $result = $apiProvider->invoke($apiRequest);
+
+ $apiResponse = $this->respond($apiProvider, $apiRequest, $result);
+ return $apiResponse;
+ }
+
/**
* Bootstrap - Load basic dependencies.
*/
_civicrm_api3_initialize();
}
+ /**
+ * @param $apiRequest
+ * @throws \API_Exception
+ */
+ protected function validate($apiRequest) {
+ if ($apiRequest['version'] === 3) {
+ if (!is_array($apiRequest['params'])) {
+ throw new \API_Exception('Input variable `params` is not an array', 2000);
+ }
+ }
+ }
+
/**
* Determine which, if any, service will execute the API request.
*
* - data: \CRM_Utils_OptionBag derived from params [v4-only]
* - chains: unspecified derived from params [v4-only]
*/
- public static function create($entity, $action, $params, $extra) {
- $apiRequest = array(); // new \Civi\API\Request();
- $apiRequest['id'] = self::$nextId++;
- $apiRequest['version'] = self::parseVersion($params);
- $apiRequest['params'] = $params;
- $apiRequest['extra'] = $extra;
- $apiRequest['fields'] = NULL;
+ public static function create($entity, $action, $params, $extra = NULL) {
+ $version = self::parseVersion($params);
- $apiRequest['entity'] = $entity = self::normalizeEntityName($entity, $apiRequest['version']);
- $apiRequest['action'] = $action = self::normalizeActionName($action, $apiRequest['version']);
+ switch ($version) {
+ case 2:
+ case 3:
+ $apiRequest = array(); // new \Civi\API\Request();
+ $apiRequest['id'] = self::$nextId++;
+ $apiRequest['version'] = $version;
+ $apiRequest['params'] = $params;
+ $apiRequest['extra'] = $extra;
+ $apiRequest['fields'] = NULL;
- // APIv1-v3 mix data+options in $params which means that each API callback is responsible
- // for splitting the two. In APIv4, the split is done systematically so that we don't
- // so much parsing logic spread around.
- if ($apiRequest['version'] >= 4) {
- $options = array();
- $data = array();
- $chains = array();
- foreach ($params as $key => $value) {
- if ($key == 'options') {
- $options = array_merge($options, $value);
- }
- elseif ($key == 'return') {
- if (!isset($options['return'])) {
- $options['return'] = array();
- }
- $options['return'] = array_merge($options['return'], $value);
- }
- elseif (preg_match('/^option\.(.*)$/', $key, $matches)) {
- $options[$matches[1]] = $value;
- }
- elseif (preg_match('/^return\.(.*)$/', $key, $matches)) {
- if ($value) {
- if (!isset($options['return'])) {
- $options['return'] = array();
- }
- $options['return'][] = $matches[1];
- }
- }
- elseif (preg_match('/^format\.(.*)$/', $key, $matches)) {
- if ($value) {
- if (!isset($options['format'])) {
- $options['format'] = $matches[1];
- }
- else {
- throw new \API_Exception("Too many API formats specified");
- }
- }
- }
- elseif (preg_match('/^api\./', $key)) {
- // FIXME: represent subrequests as instances of "Request"
- $chains[$key] = $value;
- }
- elseif ($key == 'debug') {
- $options['debug'] = $value;
- }
- elseif ($key == 'version') {
- // ignore
- }
- else {
- $data[$key] = $value;
+ $apiRequest['entity'] = $entity = self::normalizeEntityName($entity, $apiRequest['version']);
+ $apiRequest['action'] = $action = self::normalizeActionName($action, $apiRequest['version']);
+ return $apiRequest;
+
+ case 4:
+ $apiCall = call_user_func(array("Civi\\Api4\\$entity", $action));
+ unset($params['version']);
+ foreach ($params as $name => $param) {
+ $setter = 'set' . ucfirst($name);
+ $apiCall->$setter($param);
}
- }
- $apiRequest['options'] = new \CRM_Utils_OptionBag($options);
- $apiRequest['data'] = new \CRM_Utils_OptionBag($data);
- $apiRequest['chains'] = $chains;
+ return $apiCall;
+
+ default:
}
- return $apiRequest;
}
/**
return \CRM_Utils_String::convertStringToCamel(\CRM_Utils_String::munge($entity));
}
else {
- // APIv4 requires exact spelling & capitalization of entity/action name; deviations should cause errors
- if (!preg_match('/^[a-zA-Z][a-zA-Z0-9]*$/', $entity)) {
- throw new \API_Exception("Malformed entity");
- }
- return $entity;
+ throw new \API_Exception("Unknown api version");
}
}
return strtolower(\CRM_Utils_String::munge($action));
}
else {
- // APIv4 requires exact spelling & capitalization of entity/action name; deviations should cause errors
- if (!preg_match('/^[a-zA-Z][a-zA-Z0-9]*$/', $action)) {
- throw new \API_Exception("Malformed action");
- }
- // TODO: Not sure about camelCase actions - in v3 they are all lowercase.
- return strtolower($action{0}) . substr($action, 1);
+ throw new \API_Exception("Unknown api version");
}
}
*
* @package Civi\API
*/
-class SelectQuery {
+abstract class SelectQuery {
const
MAX_JOINS = 4,
* @var string
*/
protected $entity;
+ public $select = array();
+ public $where = array();
+ public $orderBy = array();
+ public $limit;
+ public $offset;
/**
* @var array
*/
- protected $params;
- /**
- * @var array
- */
- protected $options;
+ protected $selectFields = array();
/**
* @var bool
*/
- protected $isFillUniqueFields;
+ public $isFillUniqueFields = FALSE;
/**
* @var \CRM_Utils_SQL_Select
*/
/**
* @var array
*/
- private $joins = array();
+ protected $joins = array();
/**
* @var array
*/
/**
* @var string|bool
*/
- protected $checkPermissions;
+ public $checkPermissions;
+
+ protected $apiVersion;
/**
- * @param string $baoName
- * Name of BAO
- * @param array $params
- * As passed into api get function.
- * @param bool $isFillUniqueFields
- * Do we need to ensure unique fields continue to be populated for this api? (backward compatibility).
+ * @param string $entity
*/
- public function __construct($baoName, $params, $isFillUniqueFields) {
+ public function __construct($entity) {
+ $this->entity = $entity;
+ require_once 'api/v3/utils.php';
+ $baoName = _civicrm_api3_get_BAO($entity);
$bao = new $baoName();
- $this->entity = _civicrm_api_get_entity_name_from_dao($bao);
- $this->params = $params;
- $this->isFillUniqueFields = $isFillUniqueFields;
- $this->checkPermissions = \CRM_Utils_Array::value('check_permissions', $this->params, FALSE);
- $this->options = _civicrm_api3_get_options_from_params($this->params);
$this->entityFieldNames = _civicrm_api3_field_names(_civicrm_api3_build_fields_array($bao));
- // Call this function directly instead of using the api wrapper to force unique field names off
- require_once 'api/v3/Generic.php';
- $apiSpec = \civicrm_api3_generic_getfields(array('entity' => $this->entity, 'version' => 3, 'params' => array('action' => 'get')), FALSE);
- $this->apiFieldSpec = $apiSpec['values'];
+ $this->apiFieldSpec = $this->getFields();
$this->query = \CRM_Utils_SQL_Select::from($bao->tableName() . ' ' . self::MAIN_TABLE_ALIAS);
$bao->free();
* @throws \Exception
*/
public function run() {
- // $select_fields maps column names to the field names of the result values.
- $select_fields = $custom_fields = array();
+ $this->buildSelectFields();
- // populate $select_fields
- $return_all_fields = (empty($this->options['return']) || !is_array($this->options['return']));
- $return = $return_all_fields ? array_fill_keys($this->entityFieldNames, 1) : $this->options['return'];
-
- // core return fields
- foreach ($return as $field_name => $include) {
- if ($include) {
- $field = $this->getField($field_name);
- if ($field && in_array($field['name'], $this->entityFieldNames)) {
- $select_fields[self::MAIN_TABLE_ALIAS . ".{$field['name']}"] = $field['name'];
- }
- elseif ($include && strpos($field_name, '.')) {
- $fkField = $this->addFkField($field_name, 'LEFT');
- if ($fkField) {
- $select_fields[implode('.', $fkField)] = $field_name;
- }
- }
- }
- }
-
- // Do custom fields IF the params contain the word "custom" or we are returning *
- if ($return_all_fields || strpos(json_encode($this->params), 'custom')) {
- $custom_fields = _civicrm_api3_custom_fields_for_entity($this->entity);
- foreach ($custom_fields as $cf_id => $custom_field) {
- $field_name = "custom_$cf_id";
- if ($return_all_fields || !empty($this->options['return'][$field_name])
- ||
- // This is a tested format so we support it.
- !empty($this->options['return']['custom'])
- ) {
- list($table_name, $column_name) = $this->addCustomField($custom_field, 'LEFT');
-
- if ($custom_field["data_type"] != "ContactReference") {
- // 'ordinary' custom field. We will select the value as custom_XX.
- $select_fields["$table_name.$column_name"] = $field_name;
- }
- else {
- // contact reference custom field. The ID will be stored in custom_XX_id.
- // custom_XX will contain the sort name of the contact.
- $this->query->join("c_$cf_id", "LEFT JOIN civicrm_contact c_$cf_id ON c_$cf_id.id = `$table_name`.`$column_name`");
- $select_fields["$table_name.$column_name"] = $field_name . "_id";
- // We will call the contact table for the join c_XX.
- $select_fields["c_$cf_id.sort_name"] = $field_name;
- }
- }
- }
- }
- // Always select the ID.
- $select_fields[self::MAIN_TABLE_ALIAS . ".id"] = "id";
-
- // populate where_clauses
- foreach ($this->params as $key => $value) {
- $table_name = NULL;
- $column_name = NULL;
-
- if (substr($key, 0, 7) == 'filter.') {
- // Legacy support for old filter syntax per the test contract.
- // (Convert the style to the later one & then deal with them).
- $filterArray = explode('.', $key);
- $value = array($filterArray[1] => $value);
- $key = 'filters';
- }
-
- // Legacy support for 'filter's construct.
- if ($key == 'filters') {
- foreach ($value as $filterKey => $filterValue) {
- if (substr($filterKey, -4, 4) == 'high') {
- $key = substr($filterKey, 0, -5);
- $value = array('<=' => $filterValue);
- }
+ $this->buildWhereClause();
- if (substr($filterKey, -3, 3) == 'low') {
- $key = substr($filterKey, 0, -4);
- $value = array('>=' => $filterValue);
- }
-
- if ($filterKey == 'is_current' || $filterKey == 'isCurrent') {
- // Is current is almost worth creating as a 'sql filter' in the DAO function since several entities have the concept.
- $todayStart = date('Ymd000000', strtotime('now'));
- $todayEnd = date('Ymd235959', strtotime('now'));
- $a = self::MAIN_TABLE_ALIAS;
- $this->query->where("($a.start_date <= '$todayStart' OR $a.start_date IS NULL)
- AND ($a.end_date >= '$todayEnd' OR $a.end_date IS NULL)
- AND a.is_active = 1");
- }
- }
- }
- // Ignore the "options" param if it is referring to api options and not a field in this entity
- if (
- $key === 'options' && is_array($value)
- && !in_array(\CRM_Utils_Array::first(array_keys($value)), \CRM_Core_DAO::acceptedSQLOperators())
- ) {
- continue;
- }
- $field = $this->getField($key);
- if ($field) {
- $key = $field['name'];
- }
- if (in_array($key, $this->entityFieldNames)) {
- $table_name = self::MAIN_TABLE_ALIAS;
- $column_name = $key;
- }
- elseif (($cf_id = \CRM_Core_BAO_CustomField::getKeyID($key)) != FALSE) {
- list($table_name, $column_name) = $this->addCustomField($custom_fields[$cf_id], 'INNER');
- }
- elseif (strpos($key, '.')) {
- $fkInfo = $this->addFkField($key, 'INNER');
- if ($fkInfo) {
- list($table_name, $column_name) = $fkInfo;
- $this->validateNestedInput($key, $value);
- }
- }
- // I don't know why I had to specifically exclude 0 as a key - wouldn't the others have caught it?
- // We normally silently ignore null values passed in - if people want IS_NULL they can use acceptedSqlOperator syntax.
- if ((!$table_name) || empty($key) || is_null($value)) {
- // No valid filter field. This might be a chained call or something.
- // Just ignore this for the $where_clause.
- continue;
- }
- if (!is_array($value)) {
- $this->query->where(array("`$table_name`.`$column_name` = @value"), array(
- "@value" => $value,
- ));
- }
- else {
- // We expect only one element in the array, of the form
- // "operator" => "rhs".
- $operator = \CRM_Utils_Array::first(array_keys($value));
- if (!in_array($operator, \CRM_Core_DAO::acceptedSQLOperators())) {
- $this->query->where(array(
- "{$table_name}.{$column_name} = @value"), array("@value" => $value)
- );
- }
- else {
- $this->query->where(\CRM_Core_DAO::createSQLFilter("{$table_name}.{$column_name}", $value));
- }
- }
+ if (in_array('count', $this->select)) {
+ $this->query->select("count(*) as c");
}
-
- if (!$this->options['is_count']) {
- foreach ($select_fields as $column => $alias) {
+ else {
+ foreach ($this->selectFields as $column => $alias) {
$this->query->select("$column as `$alias`");
}
- }
- else {
- $this->query->select("count(*) as c");
- }
-
- // Order by
- if (!empty($this->options['sort'])) {
- $this->orderBy($this->options['sort']);
+ // Order by
+ $this->buildOrderBy();
}
// Limit
- if (!empty($this->options['limit']) || !empty($this->options['offset'])) {
- $this->query->limit($this->options['limit'], $this->options['offset']);
+ if (!empty($this->limit) || !empty($this->offset)) {
+ $this->query->limit($this->limit, $this->offset);
}
$result_entities = array();
$result_dao = \CRM_Core_DAO::executeQuery($this->query->toSQL());
while ($result_dao->fetch()) {
- if ($this->options['is_count']) {
+ if (in_array('count', $this->select)) {
$result_dao->free();
return (int) $result_dao->c;
}
$result_entities[$result_dao->id] = array();
- foreach ($select_fields as $column => $alias) {
+ foreach ($this->selectFields as $column => $alias) {
$returnName = $alias;
$alias = str_replace('.', '_', $alias);
if (property_exists($result_dao, $alias) && $result_dao->$alias != NULL) {
* @throws \API_Exception
* @throws \Civi\API\Exception\UnauthorizedException
*/
- private function addFkField($fkFieldName, $side) {
+ protected function addFkField($fkFieldName, $side) {
$stack = explode('.', $fkFieldName);
if (count($stack) < 2) {
return NULL;
* @return array
* Returns the table and field name for adding this field to a SELECT or WHERE clause
*/
- private function addCustomField($customField, $side, $baseTable = self::MAIN_TABLE_ALIAS) {
+ protected function addCustomField($customField, $side, $baseTable = self::MAIN_TABLE_ALIAS) {
$tableName = $customField["table_name"];
$columnName = $customField["column_name"];
$tableAlias = "{$baseTable}_to_$tableName";
/**
* Fetch a field from the getFields list
*
- * Searches by name, uniqueName, and api.aliases
- *
* @param string $fieldName
* @return array|null
*/
- private function getField($fieldName) {
- if (!$fieldName) {
- return NULL;
- }
- if (isset($this->apiFieldSpec[$fieldName])) {
- return $this->apiFieldSpec[$fieldName];
- }
- foreach ($this->apiFieldSpec as $field) {
- if (
- $fieldName == \CRM_Utils_Array::value('uniqueName', $field) ||
- array_search($fieldName, \CRM_Utils_Array::value('api.aliases', $field, array())) !== FALSE
- ) {
- return $field;
- }
- }
- return NULL;
- }
+ abstract protected function getField($fieldName);
/**
* Perform input validation on params that use the join syntax
* @param $value
* @throws \Exception
*/
- private function validateNestedInput($fieldName, &$value) {
+ protected function validateNestedInput($fieldName, &$value) {
$stack = explode('.', $fieldName);
$spec = $this->apiFieldSpec;
$fieldName = array_pop($stack);
* The stack of fields leading up to this join
* @return bool
*/
- private function checkPermissionToJoin($entity, $fieldStack) {
+ protected function checkPermissionToJoin($entity, $fieldStack) {
if (!$this->checkPermissions) {
return TRUE;
}
);
$prefix = implode('.', $fieldStack) . '.';
$len = strlen($prefix);
- foreach ($this->options['return'] as $key => $ret) {
+ foreach ($this->select as $key => $ret) {
if (strpos($key, $prefix) === 0) {
$params['return'][substr($key, $len)] = $ret;
}
}
- foreach ($this->params as $key => $param) {
+ foreach ($this->where as $key => $param) {
if (strpos($key, $prefix) === 0) {
$params[substr($key, $len)] = $param;
}
* @param array $stack
* @return array
*/
- private function getAclClause($tableAlias, $baoName, $stack = array()) {
+ protected function getAclClause($tableAlias, $baoName, $stack = array()) {
if (!$this->checkPermissions) {
return array();
}
/**
* Orders the query by one or more fields
*
- * e.g.
- * @code
- * $this->orderBy(array('last_name DESC', 'birth_date'));
- * @endcode
- *
- * @param string|array $sortParams
* @throws \API_Exception
* @throws \Civi\API\Exception\UnauthorizedException
*/
- public function orderBy($sortParams) {
+ protected function buildOrderBy() {
$orderBy = array();
- foreach (is_array($sortParams) ? $sortParams : explode(',', $sortParams) as $item) {
+ $sortParams = is_string($this->orderBy) ? explode(',', $this->orderBy) : (array) $this->orderBy;
+ foreach ($sortParams as $item) {
$words = preg_split("/[\s]+/", trim($item));
if ($words) {
// Direction defaults to ASC unless DESC is specified
}
}
+ /**
+ * Populate where clauses
+ *
+ * @throws \Civi\API\Exception\UnauthorizedException
+ * @throws \Exception
+ */
+ abstract protected function buildWhereClause();
+
+ /**
+ * Populate $this->selectFields
+ *
+ * @throws \Civi\API\Exception\UnauthorizedException
+ */
+ protected function buildSelectFields() {
+ $return_all_fields = (empty($this->select) || !is_array($this->select));
+ $return = $return_all_fields ? $this->entityFieldNames : $this->select;
+ if ($return_all_fields || in_array('custom', $this->select)) {
+ foreach (array_keys($this->apiFieldSpec) as $fieldName) {
+ if (strpos($fieldName, 'custom_') === 0) {
+ $return[] = $fieldName;
+ }
+ }
+ }
+
+ // Always select the ID.
+ $this->selectFields[self::MAIN_TABLE_ALIAS . ".id"] = "id";
+
+ // core return fields
+ foreach ($return as $fieldName) {
+ $field = $this->getField($fieldName);
+ if ($field && in_array($field['name'], $this->entityFieldNames)) {
+ $this->selectFields[self::MAIN_TABLE_ALIAS . ".{$field['name']}"] = $field['name'];
+ }
+ elseif (strpos($fieldName, '.')) {
+ $fkField = $this->addFkField($fieldName, 'LEFT');
+ if ($fkField) {
+ $this->selectFields[implode('.', $fkField)] = $fieldName;
+ }
+ }
+ elseif ($field && strpos($fieldName, 'custom_') === 0) {
+ list($table_name, $column_name) = $this->addCustomField($field, 'LEFT');
+
+ if ($field['data_type'] != 'ContactReference') {
+ // 'ordinary' custom field. We will select the value as custom_XX.
+ $this->selectFields["$table_name.$column_name"] = $fieldName;
+ }
+ else {
+ // contact reference custom field. The ID will be stored in custom_XX_id.
+ // custom_XX will contain the sort name of the contact.
+ $this->query->join("c_$fieldName", "LEFT JOIN civicrm_contact c_$fieldName ON c_$fieldName.id = `$table_name`.`$column_name`");
+ $this->selectFields["$table_name.$column_name"] = $fieldName . "_id";
+ // We will call the contact table for the join c_XX.
+ $this->selectFields["c_$fieldName.sort_name"] = $fieldName;
+ }
+ }
+ }
+ }
+
+ /**
+ * Load entity fields
+ * @return array
+ */
+ abstract protected function getFields();
+
}
*/
public function onApiRespond(\Civi\API\Event\RespondEvent $event) {
$apiRequest = $event->getApiRequest();
- $result = $event->getResponse();
- if (\CRM_Utils_Array::value('is_error', $result, 0) == 0) {
- $this->callNestedApi($event->getApiKernel(), $apiRequest['params'], $result, $apiRequest['action'], $apiRequest['entity'], $apiRequest['version']);
- $event->setResponse($result);
+ if ($apiRequest['version'] < 4) {
+ $result = $event->getResponse();
+ if (\CRM_Utils_Array::value('is_error', $result, 0) == 0) {
+ $this->callNestedApi($event->getApiKernel(), $apiRequest['params'], $result, $apiRequest['action'], $apiRequest['entity'], $apiRequest['version']);
+ $event->setResponse($result);
+ }
}
}
$event->authorize();
$event->stopPropagation();
}
+ elseif ($apiRequest['version'] == 4) {
+ if (!$apiRequest->getCheckPermissions()) {
+ $event->authorize();
+ $event->stopPropagation();
+ }
+ }
}
/**
<?php
namespace Civi\Core;
+use Civi\API\Provider\ActionObjectProvider;
use Civi\Core\Event\SystemInstallEvent;
use Civi\Core\Lock\LockManager;
use Doctrine\Common\Annotations\AnnotationReader;
*/
function civicrm_api3($entity, $action, $params = array()) {
$params['version'] = 3;
- $result = civicrm_api($entity, $action, $params);
+ $result = \Civi::service('civi_api_kernel')->run($entity, $action, $params);
if (is_array($result) && !empty($result['is_error'])) {
throw new CiviCRM_API3_Exception($result['error_message'], CRM_Utils_Array::value('error_code', $result, 'undefined'), $result);
}
* return the DAO name to manipulate this function
* eg. "civicrm_contact_create" or "Contact" will return "CRM_Contact_BAO_Contact"
*
- * @return mixed
+ * @return string|null
*/
function _civicrm_api3_get_BAO($name) {
// FIXME: DAO should be renamed CRM_Badge_DAO_BadgeLayout
* @return array
*/
function _civicrm_api3_basic_get($bao_name, $params, $returnAsSuccess = TRUE, $entity = "", $sql = NULL, $uniqueFields = FALSE) {
- $query = new \Civi\API\SelectQuery($bao_name, $params, $uniqueFields);
+ $entity = CRM_Core_DAO_AllCoreTables::getBriefName(str_replace('_BAO_', '_DAO_', $bao_name));
+ $options = _civicrm_api3_get_options_from_params($params);
+
+ $query = new \Civi\API\Api3SelectQuery($entity);
+ $query->where = $params;
+ if ($options['is_count']) {
+ $query->select = array('count');
+ }
+ else {
+ $query->select = array_keys(array_filter($options['return']));
+ $query->orderBy = $options['sort'];
+ $query->isFillUniqueFields = $uniqueFields;
+ }
+ $query->limit = $options['limit'];
+ $query->offset = $options['offset'];
+ $query->checkPermissions = CRM_Utils_Array::value('check_permissions', $params, FALSE);
$query->merge($sql);
$result = $query->run();
+
if ($returnAsSuccess) {
return civicrm_api3_create_success($result, $params, $entity, 'get');
}