Skip to content

Commit

Permalink
CRM-17867 - Api4 core patches
Browse files Browse the repository at this point in the history
Most of api4 is in an extension - these are the changes needed to core.
  • Loading branch information
colemanw committed Feb 9, 2016
1 parent c5906cd commit 93e1f1e
Show file tree
Hide file tree
Showing 10 changed files with 388 additions and 316 deletions.
2 changes: 1 addition & 1 deletion CRM/Admin/Page/APIExplorer.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public function run() {
->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());

Expand Down
163 changes: 163 additions & 0 deletions Civi/API/Api3SelectQuery.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?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;
}

}
82 changes: 61 additions & 21 deletions Civi/API/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,45 +61,46 @@ public function __construct($dispatcher, $apiProviders = array()) {
}

/**
* @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
* Create, get, delete or some special action name.
* @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);
Expand All @@ -125,7 +126,8 @@ public function run($entity, $action, $params, $extra = NULL) {
* @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
Expand All @@ -145,6 +147,32 @@ public function runAuthorize($entity, $action, $params, $extra = NULL) {
}
}

/**
* 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.
*/
Expand All @@ -154,6 +182,18 @@ public function boot() {
_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.
*
Expand Down
Loading

0 comments on commit 93e1f1e

Please sign in to comment.