First commit
This commit is contained in:
commit
c6e2478c40
13918 changed files with 2303184 additions and 0 deletions
185
sites/all/modules/civicrm/Civi/API/Api3SelectQuery.php
Normal file
185
sites/all/modules/civicrm/Civi/API/Api3SelectQuery.php
Normal file
|
@ -0,0 +1,185 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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() {
|
||||
$filters = array();
|
||||
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) {
|
||||
// If we check a custom field on 'IS NULL', it should also work when there is no
|
||||
// record in the custom value table, see CRM-20740.
|
||||
$side = empty($value['IS NULL']) ? 'INNER' : 'LEFT OUTER';
|
||||
list($table_name, $column_name) = $this->addCustomField($this->apiFieldSpec['custom_' . $cf_id], $side);
|
||||
}
|
||||
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;
|
||||
}
|
||||
$operator = is_array($value) ? \CRM_Utils_Array::first(array_keys($value)) : NULL;
|
||||
if (!in_array($operator, \CRM_Core_DAO::acceptedSQLOperators(), TRUE)) {
|
||||
$value = array('=' => $value);
|
||||
}
|
||||
$filters[$key] = \CRM_Core_DAO::createSQLFilter("{$table_name}.{$column_name}", $value);
|
||||
}
|
||||
// Support OR groups
|
||||
if (!empty($this->where['options']['or'])) {
|
||||
$orGroups = $this->where['options']['or'];
|
||||
if (is_string($orGroups)) {
|
||||
$orGroups = array_map('trim', explode(',', $orGroups));
|
||||
}
|
||||
if (!is_array(\CRM_Utils_Array::first($orGroups))) {
|
||||
$orGroups = array($orGroups);
|
||||
}
|
||||
foreach ($orGroups as $orGroup) {
|
||||
$orClause = array();
|
||||
foreach ($orGroup as $key) {
|
||||
if (!isset($filters[$key])) {
|
||||
throw new \CiviCRM_API3_Exception("'$key' specified in OR group but not added to params");
|
||||
}
|
||||
$orClause[] = $filters[$key];
|
||||
unset($filters[$key]);
|
||||
}
|
||||
$this->query->where(implode(' OR ', $orClause));
|
||||
}
|
||||
}
|
||||
// Add the remaining params using AND
|
||||
foreach ($filters as $filter) {
|
||||
$this->query->where($filter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*
|
||||
* @param string $fieldName
|
||||
* Field name.
|
||||
* @return NULL|mixed
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
55
sites/all/modules/civicrm/Civi/API/Event/AuthorizeEvent.php
Normal file
55
sites/all/modules/civicrm/Civi/API/Event/AuthorizeEvent.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Event;
|
||||
|
||||
/**
|
||||
* Class AuthorizeEvent
|
||||
* @package Civi\API\Event
|
||||
*/
|
||||
class AuthorizeEvent extends Event {
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $authorized = FALSE;
|
||||
|
||||
/**
|
||||
* Mark the request as authorized.
|
||||
*/
|
||||
public function authorize() {
|
||||
$this->authorized = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* TRUE if the request has been authorized.
|
||||
*/
|
||||
public function isAuthorized() {
|
||||
return $this->authorized;
|
||||
}
|
||||
|
||||
}
|
91
sites/all/modules/civicrm/Civi/API/Event/Event.php
Normal file
91
sites/all/modules/civicrm/Civi/API/Event/Event.php
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Event;
|
||||
|
||||
/**
|
||||
* Class Event
|
||||
* @package Civi\API\Event
|
||||
*/
|
||||
class Event extends \Symfony\Component\EventDispatcher\Event {
|
||||
|
||||
/**
|
||||
* @var \Civi\API\Kernel
|
||||
*/
|
||||
protected $apiKernel;
|
||||
|
||||
/**
|
||||
* @var \Civi\API\Provider\ProviderInterface
|
||||
* The API provider responsible for executing the request.
|
||||
*/
|
||||
protected $apiProvider;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* The full description of the API request.
|
||||
*
|
||||
* @see \Civi\API\Request::create
|
||||
*/
|
||||
protected $apiRequest;
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Provider\ProviderInterface $apiProvider
|
||||
* The API responsible for executing the request.
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @param \Civi\API\Kernel $apiKernel
|
||||
*/
|
||||
public function __construct($apiProvider, $apiRequest, $apiKernel) {
|
||||
$this->apiKernel = $apiKernel;
|
||||
$this->apiProvider = $apiProvider;
|
||||
$this->apiRequest = $apiRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get api kernel.
|
||||
*
|
||||
* @return \Civi\API\Kernel
|
||||
*/
|
||||
public function getApiKernel() {
|
||||
return $this->apiKernel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Civi\API\Provider\ProviderInterface
|
||||
*/
|
||||
public function getApiProvider() {
|
||||
return $this->apiProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getApiRequest() {
|
||||
return $this->apiRequest;
|
||||
}
|
||||
|
||||
}
|
63
sites/all/modules/civicrm/Civi/API/Event/ExceptionEvent.php
Normal file
63
sites/all/modules/civicrm/Civi/API/Event/ExceptionEvent.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Event;
|
||||
|
||||
/**
|
||||
* Class ExceptionEvent
|
||||
* @package Civi\API\Event
|
||||
*/
|
||||
class ExceptionEvent extends Event {
|
||||
|
||||
/**
|
||||
* @var \Exception
|
||||
*/
|
||||
private $exception;
|
||||
|
||||
/**
|
||||
* @param \Exception $exception
|
||||
* The exception which arose while processing the API request.
|
||||
* @param \Civi\API\Provider\ProviderInterface $apiProvider
|
||||
* The API provider responsible for executing the request.
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @param \Civi\API\Kernel $apiKernel
|
||||
* The kernel which fired the event.
|
||||
*/
|
||||
public function __construct($exception, $apiProvider, $apiRequest, $apiKernel) {
|
||||
$this->exception = $exception;
|
||||
parent::__construct($apiProvider, $apiRequest, $apiKernel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Exception
|
||||
*/
|
||||
public function getException() {
|
||||
return $this->exception;
|
||||
}
|
||||
|
||||
}
|
45
sites/all/modules/civicrm/Civi/API/Event/PrepareEvent.php
Normal file
45
sites/all/modules/civicrm/Civi/API/Event/PrepareEvent.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Event;
|
||||
|
||||
/**
|
||||
* Class PrepareEvent
|
||||
* @package Civi\API\Event
|
||||
*/
|
||||
class PrepareEvent extends Event {
|
||||
/**
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @return PrepareEvent
|
||||
*/
|
||||
public function setApiRequest($apiRequest) {
|
||||
$this->apiRequest = $apiRequest;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
61
sites/all/modules/civicrm/Civi/API/Event/ResolveEvent.php
Normal file
61
sites/all/modules/civicrm/Civi/API/Event/ResolveEvent.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Event;
|
||||
|
||||
/**
|
||||
* Class ResolveEvent
|
||||
* @package Civi\API\Event
|
||||
*/
|
||||
class ResolveEvent extends Event {
|
||||
/**
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @param \Civi\API\Kernel $apiKernel
|
||||
* The kernel which fired the event.
|
||||
*/
|
||||
public function __construct($apiRequest, $apiKernel) {
|
||||
parent::__construct(NULL, $apiRequest, $apiKernel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Provider\ProviderInterface $apiProvider
|
||||
* The API provider responsible for executing the request.
|
||||
*/
|
||||
public function setApiProvider($apiProvider) {
|
||||
$this->apiProvider = $apiProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
*/
|
||||
public function setApiRequest($apiRequest) {
|
||||
$this->apiRequest = $apiRequest;
|
||||
}
|
||||
|
||||
}
|
70
sites/all/modules/civicrm/Civi/API/Event/RespondEvent.php
Normal file
70
sites/all/modules/civicrm/Civi/API/Event/RespondEvent.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Event;
|
||||
|
||||
/**
|
||||
* Class RespondEvent
|
||||
* @package Civi\API\Event
|
||||
*/
|
||||
class RespondEvent extends Event {
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $response;
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Provider\ProviderInterface $apiProvider
|
||||
* The API provider responsible for executing the request.
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @param mixed $response
|
||||
* The response to return to the client.
|
||||
* @param \Civi\API\Kernel $apiKernel
|
||||
* The kernel which fired the event.
|
||||
*/
|
||||
public function __construct($apiProvider, $apiRequest, $response, $apiKernel) {
|
||||
$this->response = $response;
|
||||
parent::__construct($apiProvider, $apiRequest, $apiKernel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getResponse() {
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $response
|
||||
* The response to return to the client.
|
||||
*/
|
||||
public function setResponse($response) {
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
}
|
120
sites/all/modules/civicrm/Civi/API/Events.php
Normal file
120
sites/all/modules/civicrm/Civi/API/Events.php
Normal file
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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;
|
||||
|
||||
/**
|
||||
* The API kernel dispatches a series of events while processing each API request.
|
||||
* For a successful API request, the sequence is RESOLVE => AUTHORIZE => PREPARE => RESPOND.
|
||||
* If an exception arises in any stage, then the sequence is aborted and the EXCEPTION
|
||||
* event is dispatched.
|
||||
*
|
||||
* Event subscribers which are concerned about the order of execution should assign
|
||||
* a weight to their subscription (such as W_EARLY, W_MIDDLE, or W_LATE).
|
||||
* W_LATE).
|
||||
*/
|
||||
class Events {
|
||||
|
||||
/**
|
||||
* Determine whether the API request is allowed for the current user.
|
||||
* For successful execution, at least one listener must invoke
|
||||
* $event->authorize().
|
||||
*
|
||||
* @see AuthorizeEvent
|
||||
*/
|
||||
const AUTHORIZE = 'civi.api.authorize';
|
||||
|
||||
/**
|
||||
* Determine which API provider executes the given request. For successful
|
||||
* execution, at least one listener must invoke
|
||||
* $event->setProvider($provider).
|
||||
*
|
||||
* @see ResolveEvent
|
||||
*/
|
||||
const RESOLVE = 'civi.api.resolve';
|
||||
|
||||
/**
|
||||
* Apply pre-execution logic
|
||||
*
|
||||
* @see PrepareEvent
|
||||
*/
|
||||
const PREPARE = 'civi.api.prepare';
|
||||
|
||||
/**
|
||||
* Apply post-execution logic
|
||||
*
|
||||
* @see RespondEvent
|
||||
*/
|
||||
const RESPOND = 'civi.api.respond';
|
||||
|
||||
/**
|
||||
* Handle any exceptions.
|
||||
*
|
||||
* @see ExceptionEvent
|
||||
*/
|
||||
const EXCEPTION = 'civi.api.exception';
|
||||
|
||||
/**
|
||||
* Weight - Early
|
||||
*/
|
||||
const W_EARLY = 100;
|
||||
|
||||
/**
|
||||
* Weight - Middle
|
||||
*/
|
||||
const W_MIDDLE = 0;
|
||||
|
||||
/**
|
||||
* Weight - Late
|
||||
*/
|
||||
const W_LATE = -100;
|
||||
|
||||
/**
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function allEvents() {
|
||||
return array(
|
||||
self::AUTHORIZE,
|
||||
self::EXCEPTION,
|
||||
self::PREPARE,
|
||||
self::RESOLVE,
|
||||
self::RESPOND,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\Core\Event\GenericHookEvent $e
|
||||
* @see \CRM_Utils_Hook::eventDefs
|
||||
*/
|
||||
public static function hookEventDefs($e) {
|
||||
$e->inspector->addEventClass(self::AUTHORIZE, 'Civi\API\Event\AuthorizeEvent');
|
||||
$e->inspector->addEventClass(self::EXCEPTION, 'Civi\API\Event\ExceptionEvent');
|
||||
$e->inspector->addEventClass(self::PREPARE, 'Civi\API\Event\PrepareEvent');
|
||||
$e->inspector->addEventClass(self::RESOLVE, 'Civi\API\Event\ResolveEvent');
|
||||
$e->inspector->addEventClass(self::RESPOND, 'Civi\API\Event\RespondEvent');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
namespace Civi\API\Exception;
|
||||
|
||||
require_once 'api/Exception.php';
|
||||
|
||||
/**
|
||||
* Class NotImplementedException
|
||||
* @package Civi\API\Exception
|
||||
*/
|
||||
class NotImplementedException extends \API_Exception {
|
||||
/**
|
||||
* @param string $message
|
||||
* The human friendly error message.
|
||||
* @param array $extraParams
|
||||
* Extra params to return. eg an extra array of ids. It is not mandatory,
|
||||
* but can help the computer using the api. Keep in mind the api consumer
|
||||
* isn't to be trusted. eg. the database password is NOT a good extra data.
|
||||
* @param \Exception|NULL $previous
|
||||
* A previous exception which caused this new exception.
|
||||
*/
|
||||
public function __construct($message, $extraParams = array(), \Exception $previous = NULL) {
|
||||
parent::__construct($message, \API_Exception::NOT_IMPLEMENTED, $extraParams, $previous);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
namespace Civi\API\Exception;
|
||||
|
||||
require_once 'api/Exception.php';
|
||||
|
||||
/**
|
||||
* Class UnauthorizedException
|
||||
* @package Civi\API\Exception
|
||||
*/
|
||||
class UnauthorizedException extends \API_Exception {
|
||||
/**
|
||||
* @param string $message
|
||||
* The human friendly error message.
|
||||
* @param array $extraParams
|
||||
* Extra params to return. eg an extra array of ids. It is not mandatory,
|
||||
* but can help the computer using the api. Keep in mind the api consumer
|
||||
* isn't to be trusted. eg. the database password is NOT a good extra data.
|
||||
* @param \Exception|NULL $previous
|
||||
* A previous exception which caused this new exception.
|
||||
*/
|
||||
public function __construct($message, $extraParams = array(), \Exception $previous = NULL) {
|
||||
parent::__construct($message, \API_Exception::UNAUTHORIZED, $extraParams, $previous);
|
||||
}
|
||||
|
||||
}
|
251
sites/all/modules/civicrm/Civi/API/ExternalBatch.php
Normal file
251
sites/all/modules/civicrm/Civi/API/ExternalBatch.php
Normal file
|
@ -0,0 +1,251 @@
|
|||
<?php
|
||||
namespace Civi\API;
|
||||
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
/**
|
||||
* Class ExternalBatch
|
||||
* @package Civi\API
|
||||
*
|
||||
* Perform a series of external, asynchronous, concurrent API call.
|
||||
*/
|
||||
class ExternalBatch {
|
||||
/**
|
||||
* The time to wait when polling for process status (microseconds).
|
||||
*/
|
||||
const POLL_INTERVAL = 10000;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* Array(int $idx => array $apiCall).
|
||||
*/
|
||||
protected $apiCalls;
|
||||
|
||||
protected $defaultParams;
|
||||
|
||||
protected $root;
|
||||
|
||||
protected $settingsPath;
|
||||
|
||||
protected $env;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* Array(int $idx => Process $process).
|
||||
*/
|
||||
protected $processes;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* Array(int $idx => array $apiResult).
|
||||
*/
|
||||
protected $apiResults;
|
||||
|
||||
/**
|
||||
* @param array $defaultParams
|
||||
* Default values to merge into any API calls.
|
||||
*/
|
||||
public function __construct($defaultParams = array()) {
|
||||
global $civicrm_root;
|
||||
$this->root = $civicrm_root;
|
||||
$this->settingsPath = defined('CIVICRM_SETTINGS_PATH') ? CIVICRM_SETTINGS_PATH : NULL;
|
||||
$this->defaultParams = $defaultParams;
|
||||
$this->env = $_ENV;
|
||||
if (empty($_ENV['PATH'])) {
|
||||
// FIXME: If we upgrade to newer Symfony\Process and use the newer
|
||||
// inheritEnv feature, then this becomes unnecessary.
|
||||
throw new \CRM_Core_Exception('ExternalBatch cannot detect environment: $_ENV is missing. (Tip: Set variables_order=EGPCS in php.ini.)');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entity
|
||||
* @param string $action
|
||||
* @param array $params
|
||||
* @return ExternalBatch
|
||||
*/
|
||||
public function addCall($entity, $action, $params = array()) {
|
||||
$params = array_merge($this->defaultParams, $params);
|
||||
|
||||
$this->apiCalls[] = array(
|
||||
'entity' => $entity,
|
||||
'action' => $action,
|
||||
'params' => $params,
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $env
|
||||
* List of environment variables to add.
|
||||
* @return static
|
||||
*/
|
||||
public function addEnv($env) {
|
||||
$this->env = array_merge($this->env, $env);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all the API calls concurrently.
|
||||
*
|
||||
* @return static
|
||||
* @throws \CRM_Core_Exception
|
||||
*/
|
||||
public function start() {
|
||||
foreach ($this->apiCalls as $idx => $apiCall) {
|
||||
$process = $this->createProcess($apiCall);
|
||||
$process->start();
|
||||
$this->processes[$idx] = $process;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* The number of running processes.
|
||||
*/
|
||||
public function getRunningCount() {
|
||||
$count = 0;
|
||||
foreach ($this->processes as $process) {
|
||||
if ($process->isRunning()) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function wait() {
|
||||
while (!empty($this->processes)) {
|
||||
usleep(self::POLL_INTERVAL);
|
||||
foreach (array_keys($this->processes) as $idx) {
|
||||
/** @var Process $process */
|
||||
$process = $this->processes[$idx];
|
||||
if (!$process->isRunning()) {
|
||||
$parsed = json_decode($process->getOutput(), TRUE);
|
||||
if ($process->getExitCode() || $parsed === NULL) {
|
||||
$this->apiResults[] = array(
|
||||
'is_error' => 1,
|
||||
'error_message' => 'External API returned malformed response.',
|
||||
'trace' => array(
|
||||
'code' => $process->getExitCode(),
|
||||
'stdout' => $process->getOutput(),
|
||||
'stderr' => $process->getErrorOutput(),
|
||||
),
|
||||
);
|
||||
}
|
||||
else {
|
||||
$this->apiResults[] = $parsed;
|
||||
}
|
||||
unset($this->processes[$idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getResults() {
|
||||
return $this->apiResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $idx
|
||||
* @return array
|
||||
*/
|
||||
public function getResult($idx = 0) {
|
||||
return $this->apiResults[$idx];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the local environment supports running API calls
|
||||
* externally.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSupported() {
|
||||
// If you try in Windows, feel free to change this...
|
||||
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' || !function_exists('proc_open')) {
|
||||
return FALSE;
|
||||
}
|
||||
if (!file_exists($this->root . '/bin/cli.php') || !file_exists($this->settingsPath)) {
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $apiCall
|
||||
* Array with keys: entity, action, params.
|
||||
* @return Process
|
||||
* @throws \CRM_Core_Exception
|
||||
*/
|
||||
public function createProcess($apiCall) {
|
||||
$parts = array();
|
||||
|
||||
if (defined('CIVICRM_TEST') && CIVICRM_TEST) {
|
||||
// When testing, civicrm.settings.php may rely on $_CV, which is only
|
||||
// populated/propagated if we execute through `cv`.
|
||||
$parts[] = 'cv api';
|
||||
$parts[] = escapeshellarg($apiCall['entity'] . '.' . $apiCall['action']);
|
||||
$parts[] = "--out=json-strict";
|
||||
foreach ($apiCall['params'] as $key => $value) {
|
||||
$parts[] = escapeshellarg("$key=$value");
|
||||
}
|
||||
}
|
||||
else {
|
||||
// But in production, we may not have `cv` installed.
|
||||
$executableFinder = new PhpExecutableFinder();
|
||||
$php = $executableFinder->find();
|
||||
if (!$php) {
|
||||
throw new \CRM_Core_Exception("Failed to locate PHP interpreter.");
|
||||
}
|
||||
$parts[] = $php;
|
||||
$parts[] = escapeshellarg($this->root . '/bin/cli.php');
|
||||
$parts[] = escapeshellarg("-e=" . $apiCall['entity']);
|
||||
$parts[] = escapeshellarg("-a=" . $apiCall['action']);
|
||||
$parts[] = "--json";
|
||||
$parts[] = escapeshellarg("-u=dummyuser");
|
||||
foreach ($apiCall['params'] as $key => $value) {
|
||||
$parts[] = escapeshellarg("--$key=$value");
|
||||
}
|
||||
}
|
||||
|
||||
$command = implode(" ", $parts);
|
||||
$env = array_merge($this->env, array(
|
||||
'CIVICRM_SETTINGS' => $this->settingsPath,
|
||||
));
|
||||
return new Process($command, $this->root, $env);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRoot() {
|
||||
return $this->root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $root
|
||||
*/
|
||||
public function setRoot($root) {
|
||||
$this->root = $root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getSettingsPath() {
|
||||
return $this->settingsPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $settingsPath
|
||||
*/
|
||||
public function setSettingsPath($settingsPath) {
|
||||
$this->settingsPath = $settingsPath;
|
||||
}
|
||||
|
||||
}
|
491
sites/all/modules/civicrm/Civi/API/Kernel.php
Normal file
491
sites/all/modules/civicrm/Civi/API/Kernel.php
Normal file
|
@ -0,0 +1,491 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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;
|
||||
|
||||
use Civi\API\Event\AuthorizeEvent;
|
||||
use Civi\API\Event\PrepareEvent;
|
||||
use Civi\API\Event\ExceptionEvent;
|
||||
use Civi\API\Event\ResolveEvent;
|
||||
use Civi\API\Event\RespondEvent;
|
||||
use Civi\API\Provider\ProviderInterface;
|
||||
|
||||
/**
|
||||
* @package Civi
|
||||
* @copyright CiviCRM LLC (c) 2004-2017
|
||||
*/
|
||||
class Kernel {
|
||||
|
||||
/**
|
||||
* @var \Symfony\Component\EventDispatcher\EventDispatcher
|
||||
*/
|
||||
protected $dispatcher;
|
||||
|
||||
/**
|
||||
* @var array<ProviderInterface>
|
||||
*/
|
||||
protected $apiProviders;
|
||||
|
||||
/**
|
||||
* @param \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
|
||||
* The event dispatcher which receives kernel events.
|
||||
* @param array $apiProviders
|
||||
* Array of ProviderInterface.
|
||||
*/
|
||||
public function __construct($dispatcher, $apiProviders = array()) {
|
||||
$this->apiProviders = $apiProviders;
|
||||
$this->dispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @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
|
||||
* Unused/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
|
||||
* Unused/deprecated.
|
||||
*
|
||||
* @return array|int
|
||||
* @throws \API_Exception
|
||||
*/
|
||||
public function runSafe($entity, $action, $params, $extra = NULL) {
|
||||
$apiRequest = Request::create($entity, $action, $params, $extra);
|
||||
|
||||
try {
|
||||
$apiResponse = $this->runRequest($apiRequest);
|
||||
return $this->formatResult($apiRequest, $apiResponse);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->dispatcher->dispatch(Events::EXCEPTION, new ExceptionEvent($e, NULL, $apiRequest, $this));
|
||||
|
||||
if ($e instanceof \PEAR_Exception) {
|
||||
$err = $this->formatPearException($e, $apiRequest);
|
||||
}
|
||||
elseif ($e instanceof \API_Exception) {
|
||||
$err = $this->formatApiException($e, $apiRequest);
|
||||
}
|
||||
else {
|
||||
$err = $this->formatException($e, $apiRequest);
|
||||
}
|
||||
|
||||
return $this->formatResult($apiRequest, $err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a hypothetical API call would be authorized.
|
||||
*
|
||||
* @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 mixed $extra
|
||||
* Unused/deprecated.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if authorization would succeed.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function runAuthorize($entity, $action, $params, $extra = NULL) {
|
||||
$apiProvider = NULL;
|
||||
$apiRequest = Request::create($entity, $action, $params, $extra);
|
||||
|
||||
try {
|
||||
$this->boot($apiRequest);
|
||||
list($apiProvider, $apiRequest) = $this->resolve($apiRequest);
|
||||
$this->authorize($apiProvider, $apiRequest);
|
||||
return TRUE;
|
||||
}
|
||||
catch (\Civi\API\Exception\UnauthorizedException $e) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an API request.
|
||||
*
|
||||
* The request must be in canonical format. Exceptions will be propagated out.
|
||||
*
|
||||
* @param array $apiRequest
|
||||
* @return array
|
||||
* @throws \API_Exception
|
||||
* @throws \Civi\API\Exception\NotImplementedException
|
||||
* @throws \Civi\API\Exception\UnauthorizedException
|
||||
*/
|
||||
public function runRequest($apiRequest) {
|
||||
$this->boot($apiRequest);
|
||||
$errorScope = \CRM_Core_TemporaryErrorScope::useException();
|
||||
|
||||
list($apiProvider, $apiRequest) = $this->resolve($apiRequest);
|
||||
$this->authorize($apiProvider, $apiRequest);
|
||||
$apiRequest = $this->prepare($apiProvider, $apiRequest);
|
||||
$result = $apiProvider->invoke($apiRequest);
|
||||
|
||||
return $this->respond($apiProvider, $apiRequest, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap - Load basic dependencies and sanity-check inputs.
|
||||
*
|
||||
* @param \Civi\API\V4\Action|array $apiRequest
|
||||
* @throws \API_Exception
|
||||
*/
|
||||
public function boot($apiRequest) {
|
||||
require_once 'api/Exception.php';
|
||||
|
||||
if (!is_array($apiRequest['params'])) {
|
||||
throw new \API_Exception('Input variable `params` is not an array', 2000);
|
||||
}
|
||||
switch ($apiRequest['version']) {
|
||||
case 2:
|
||||
case 3:
|
||||
require_once 'api/v3/utils.php';
|
||||
_civicrm_api3_initialize();
|
||||
break;
|
||||
|
||||
case 4:
|
||||
// nothing to do
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new \API_Exception('Unknown api version', 2000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $apiRequest
|
||||
* @throws \API_Exception
|
||||
*/
|
||||
protected function validate($apiRequest) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine which, if any, service will execute the API request.
|
||||
*
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @throws Exception\NotImplementedException
|
||||
* @return array
|
||||
* A tuple with the provider-object and a revised apiRequest.
|
||||
* Array(0 => ProviderInterface, 1 => array $apiRequest).
|
||||
*/
|
||||
public function resolve($apiRequest) {
|
||||
/** @var ResolveEvent $resolveEvent */
|
||||
$resolveEvent = $this->dispatcher->dispatch(Events::RESOLVE, new ResolveEvent($apiRequest, $this));
|
||||
$apiRequest = $resolveEvent->getApiRequest();
|
||||
if (!$resolveEvent->getApiProvider()) {
|
||||
throw new \Civi\API\Exception\NotImplementedException("API (" . $apiRequest['entity'] . ", " . $apiRequest['action'] . ") does not exist (join the API team and implement it!)");
|
||||
}
|
||||
return array($resolveEvent->getApiProvider(), $apiRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the API request is allowed (under current policy)
|
||||
*
|
||||
* @param ProviderInterface $apiProvider
|
||||
* The API provider responsible for executing the request.
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @throws Exception\UnauthorizedException
|
||||
*/
|
||||
public function authorize($apiProvider, $apiRequest) {
|
||||
/** @var AuthorizeEvent $event */
|
||||
$event = $this->dispatcher->dispatch(Events::AUTHORIZE, new AuthorizeEvent($apiProvider, $apiRequest, $this));
|
||||
if (!$event->isAuthorized()) {
|
||||
throw new \Civi\API\Exception\UnauthorizedException("Authorization failed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow third-party code to manipulate the API request before execution.
|
||||
*
|
||||
* @param ProviderInterface $apiProvider
|
||||
* The API provider responsible for executing the request.
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @return array
|
||||
* The revised API request.
|
||||
*/
|
||||
public function prepare($apiProvider, $apiRequest) {
|
||||
/** @var PrepareEvent $event */
|
||||
$event = $this->dispatcher->dispatch(Events::PREPARE, new PrepareEvent($apiProvider, $apiRequest, $this));
|
||||
return $event->getApiRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow third-party code to manipulate the API response after execution.
|
||||
*
|
||||
* @param ProviderInterface $apiProvider
|
||||
* The API provider responsible for executing the request.
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @param array $result
|
||||
* The response to return to the client.
|
||||
* @return mixed
|
||||
* The revised $result.
|
||||
*/
|
||||
public function respond($apiProvider, $apiRequest, $result) {
|
||||
/** @var RespondEvent $event */
|
||||
$event = $this->dispatcher->dispatch(Events::RESPOND, new RespondEvent($apiProvider, $apiRequest, $result, $this));
|
||||
return $event->getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $version
|
||||
* API version.
|
||||
* @return array
|
||||
* Array<string>.
|
||||
*/
|
||||
public function getEntityNames($version) {
|
||||
// Question: Would it better to eliminate $this->apiProviders and just use $this->dispatcher?
|
||||
$entityNames = array();
|
||||
foreach ($this->getApiProviders() as $provider) {
|
||||
/** @var ProviderInterface $provider */
|
||||
$entityNames = array_merge($entityNames, $provider->getEntityNames($version));
|
||||
}
|
||||
$entityNames = array_unique($entityNames);
|
||||
sort($entityNames);
|
||||
return $entityNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $version
|
||||
* API version.
|
||||
* @param string $entity
|
||||
* API entity.
|
||||
* @return array
|
||||
* Array<string>
|
||||
*/
|
||||
public function getActionNames($version, $entity) {
|
||||
// Question: Would it better to eliminate $this->apiProviders and just use $this->dispatcher?
|
||||
$actionNames = array();
|
||||
foreach ($this->getApiProviders() as $provider) {
|
||||
/** @var ProviderInterface $provider */
|
||||
$actionNames = array_merge($actionNames, $provider->getActionNames($version, $entity));
|
||||
}
|
||||
$actionNames = array_unique($actionNames);
|
||||
sort($actionNames);
|
||||
return $actionNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Exception $e
|
||||
* An unhandled exception.
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @return array
|
||||
* API response.
|
||||
*/
|
||||
public function formatException($e, $apiRequest) {
|
||||
$data = array();
|
||||
if (!empty($apiRequest['params']['debug'])) {
|
||||
$data['trace'] = $e->getTraceAsString();
|
||||
}
|
||||
return $this->createError($e->getMessage(), $data, $apiRequest, $e->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \API_Exception $e
|
||||
* An unhandled exception.
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @return array
|
||||
* (API response)
|
||||
*/
|
||||
public function formatApiException($e, $apiRequest) {
|
||||
$data = $e->getExtraParams();
|
||||
$data['entity'] = \CRM_Utils_Array::value('entity', $apiRequest);
|
||||
$data['action'] = \CRM_Utils_Array::value('action', $apiRequest);
|
||||
|
||||
if (\CRM_Utils_Array::value('debug', \CRM_Utils_Array::value('params', $apiRequest))
|
||||
&& empty($data['trace']) // prevent recursion
|
||||
) {
|
||||
$data['trace'] = $e->getTraceAsString();
|
||||
}
|
||||
|
||||
return $this->createError($e->getMessage(), $data, $apiRequest, $e->getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \PEAR_Exception $e
|
||||
* An unhandled exception.
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @return array
|
||||
* API response.
|
||||
*/
|
||||
public function formatPearException($e, $apiRequest) {
|
||||
$data = array();
|
||||
$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";
|
||||
}
|
||||
|
||||
return $this->createError($e->getMessage(), $data, $apiRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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_foreign_keys($apiRequest['entity'], $apiRequest['action'], $apiRequest['params'], $fields);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @param array $result
|
||||
* The response to return to the client.
|
||||
* @return mixed
|
||||
*/
|
||||
public function formatResult($apiRequest, $result) {
|
||||
if (isset($apiRequest, $apiRequest['params'])) {
|
||||
if (isset($apiRequest['params']['format.is_success']) && $apiRequest['params']['format.is_success'] == 1) {
|
||||
return (empty($result['is_error'])) ? 1 : 0;
|
||||
}
|
||||
|
||||
if (!empty($apiRequest['params']['format.only_id']) && isset($result['id'])) {
|
||||
// FIXME dispatch
|
||||
return $result['id'];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<ProviderInterface>
|
||||
*/
|
||||
public function getApiProviders() {
|
||||
return $this->apiProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $apiProviders
|
||||
* Array<ProviderInterface>.
|
||||
* @return Kernel
|
||||
*/
|
||||
public function setApiProviders($apiProviders) {
|
||||
$this->apiProviders = $apiProviders;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ProviderInterface $apiProvider
|
||||
* The API provider responsible for executing the request.
|
||||
* @return Kernel
|
||||
*/
|
||||
public function registerApiProvider($apiProvider) {
|
||||
$this->apiProviders[] = $apiProvider;
|
||||
if ($apiProvider instanceof \Symfony\Component\EventDispatcher\EventSubscriberInterface) {
|
||||
$this->getDispatcher()->addSubscriber($apiProvider);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Symfony\Component\EventDispatcher\EventDispatcher
|
||||
*/
|
||||
public function getDispatcher() {
|
||||
return $this->dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
|
||||
* The event dispatcher which receives kernel events.
|
||||
* @return Kernel
|
||||
*/
|
||||
public function setDispatcher($dispatcher) {
|
||||
$this->dispatcher = $dispatcher;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
168
sites/all/modules/civicrm/Civi/API/Provider/AdhocProvider.php
Normal file
168
sites/all/modules/civicrm/Civi/API/Provider/AdhocProvider.php
Normal file
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Provider;
|
||||
|
||||
use Civi\API\Events;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* An adhoc provider is useful for creating mock API implementations.
|
||||
*/
|
||||
class AdhocProvider implements EventSubscriberInterface, ProviderInterface {
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
// Using a high priority allows adhoc implementations
|
||||
// to override standard implementations -- which is
|
||||
// handy for testing/mocking.
|
||||
return array(
|
||||
Events::RESOLVE => array(
|
||||
array('onApiResolve', Events::W_EARLY),
|
||||
),
|
||||
Events::AUTHORIZE => array(
|
||||
array('onApiAuthorize', Events::W_EARLY),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array (string $name => array('perm' => string, 'callback' => callable))
|
||||
*/
|
||||
protected $actions = array();
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $version;
|
||||
|
||||
/**
|
||||
* @param int $version
|
||||
* API version.
|
||||
* @param string $entity
|
||||
* API entity.
|
||||
*/
|
||||
public function __construct($version, $entity) {
|
||||
$this->entity = $entity;
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new API.
|
||||
*
|
||||
* @param string $name
|
||||
* Action name.
|
||||
* @param string $perm
|
||||
* Permissions required for invoking the action.
|
||||
* @param mixed $callback
|
||||
* The function which executes the API.
|
||||
* @return AdhocProvider
|
||||
*/
|
||||
public function addAction($name, $perm, $callback) {
|
||||
$this->actions[strtolower($name)] = array(
|
||||
'perm' => $perm,
|
||||
'callback' => $callback,
|
||||
);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Event\ResolveEvent $event
|
||||
* API resolution event.
|
||||
*/
|
||||
public function onApiResolve(\Civi\API\Event\ResolveEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
if ($this->matchesRequest($apiRequest)) {
|
||||
$event->setApiRequest($apiRequest);
|
||||
$event->setApiProvider($this);
|
||||
$event->stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Event\AuthorizeEvent $event
|
||||
* API authorization event.
|
||||
*/
|
||||
public function onApiAuthorize(\Civi\API\Event\AuthorizeEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
if ($this->matchesRequest($apiRequest) && \CRM_Core_Permission::check($this->actions[strtolower($apiRequest['action'])]['perm'])) {
|
||||
$event->authorize();
|
||||
$event->stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param array $apiRequest
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function invoke($apiRequest) {
|
||||
return call_user_func($this->actions[strtolower($apiRequest['action'])]['callback'], $apiRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param int $version
|
||||
* @return array
|
||||
*/
|
||||
public function getEntityNames($version) {
|
||||
return array($this->entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param int $version
|
||||
* @param string $entity
|
||||
* @return array
|
||||
*/
|
||||
public function getActionNames($version, $entity) {
|
||||
if ($version == $this->version && $entity == $this->entity) {
|
||||
return array_keys($this->actions);
|
||||
}
|
||||
else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function matchesRequest($apiRequest) {
|
||||
return $apiRequest['entity'] == $this->entity && $apiRequest['version'] == $this->version && isset($this->actions[strtolower($apiRequest['action'])]);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Provider;
|
||||
|
||||
use Civi\API\Events;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* This class manages the loading of API's using strict file+function naming
|
||||
* conventions.
|
||||
*/
|
||||
class MagicFunctionProvider implements EventSubscriberInterface, ProviderInterface {
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return array(
|
||||
Events::RESOLVE => array(
|
||||
array('onApiResolve', Events::W_MIDDLE),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array (string $cachekey => array('function' => string, 'is_generic' => bool))
|
||||
*/
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->cache = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Event\ResolveEvent $event
|
||||
* API resolution event.
|
||||
*/
|
||||
public function onApiResolve(\Civi\API\Event\ResolveEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
$resolved = $this->resolve($apiRequest);
|
||||
if ($resolved['function']) {
|
||||
$apiRequest += $resolved;
|
||||
$event->setApiRequest($apiRequest);
|
||||
$event->setApiProvider($this);
|
||||
$event->stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param array $apiRequest
|
||||
* @return array
|
||||
*/
|
||||
public function invoke($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']) {
|
||||
$result = $function($apiRequest['params']);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param int $version
|
||||
* @return array
|
||||
*/
|
||||
public function getEntityNames($version) {
|
||||
$entities = array();
|
||||
$include_dirs = array_unique(explode(PATH_SEPARATOR, get_include_path()));
|
||||
#$include_dirs = array(dirname(__FILE__). '/../../');
|
||||
foreach ($include_dirs as $include_dir) {
|
||||
$api_dir = implode(DIRECTORY_SEPARATOR,
|
||||
array($include_dir, 'api', 'v' . $version));
|
||||
if (!is_dir($api_dir)) {
|
||||
continue;
|
||||
}
|
||||
$iterator = new \DirectoryIterator($api_dir);
|
||||
foreach ($iterator as $fileinfo) {
|
||||
$file = $fileinfo->getFilename();
|
||||
|
||||
// Check for entities with a master file ("api/v3/MyEntity.php")
|
||||
$parts = explode(".", $file);
|
||||
if (end($parts) == "php" && $file != "utils.php" && !preg_match('/Tests?.php$/', $file)) {
|
||||
// without the ".php"
|
||||
$entities[] = substr($file, 0, -4);
|
||||
}
|
||||
|
||||
// Check for entities with standalone action files (eg "api/v3/MyEntity/MyAction.php").
|
||||
$action_dir = $api_dir . DIRECTORY_SEPARATOR . $file;
|
||||
if (preg_match('/^[A-Z][A-Za-z0-9]*$/', $file) && is_dir($action_dir)) {
|
||||
if (count(glob("$action_dir/[A-Z]*.php")) > 0) {
|
||||
$entities[] = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$entities = array_diff($entities, array('Generic'));
|
||||
$entities = array_unique($entities);
|
||||
sort($entities);
|
||||
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param int $version
|
||||
* @param string $entity
|
||||
* @return array
|
||||
*/
|
||||
public function getActionNames($version, $entity) {
|
||||
$entity = _civicrm_api_get_camel_name($entity);
|
||||
$entities = $this->getEntityNames($version);
|
||||
if (!in_array($entity, $entities)) {
|
||||
return array();
|
||||
}
|
||||
$this->loadEntity($entity, $version);
|
||||
|
||||
$functions = get_defined_functions();
|
||||
$actions = array();
|
||||
$prefix = 'civicrm_api' . $version . '_' . _civicrm_api_get_entity_name_from_camel($entity) . '_';
|
||||
$prefixGeneric = 'civicrm_api' . $version . '_generic_';
|
||||
foreach ($functions['user'] as $fct) {
|
||||
if (strpos($fct, $prefix) === 0) {
|
||||
$actions[] = substr($fct, strlen($prefix));
|
||||
}
|
||||
elseif (strpos($fct, $prefixGeneric) === 0) {
|
||||
$actions[] = substr($fct, strlen($prefixGeneric));
|
||||
}
|
||||
}
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the implementation for a given API request.
|
||||
*
|
||||
* @param array $apiRequest
|
||||
* Array with keys:
|
||||
* - entity: string, required.
|
||||
* - action: string, required.
|
||||
* - params: array.
|
||||
* - version: scalar, required.
|
||||
*
|
||||
* @return array
|
||||
* Array with keys:
|
||||
* - function: callback (mixed)
|
||||
* - is_generic: boolean
|
||||
*/
|
||||
protected function resolve($apiRequest) {
|
||||
$cachekey = strtolower($apiRequest['entity']) . ':' . strtolower($apiRequest['action']) . ':' . $apiRequest['version'];
|
||||
if (isset($this->cache[$cachekey])) {
|
||||
return $this->cache[$cachekey];
|
||||
}
|
||||
|
||||
$camelName = _civicrm_api_get_camel_name($apiRequest['entity'], $apiRequest['version']);
|
||||
$actionCamelName = _civicrm_api_get_camel_name($apiRequest['action']);
|
||||
|
||||
// Determine if there is an entity-specific implementation of the action
|
||||
$stdFunction = $this->getFunctionName($apiRequest['entity'], $apiRequest['action'], $apiRequest['version']);
|
||||
if (function_exists($stdFunction)) {
|
||||
// someone already loaded the appropriate file
|
||||
// FIXME: This has the affect of masking bugs in load order; this is
|
||||
// included to provide bug-compatibility.
|
||||
$this->cache[$cachekey] = array('function' => $stdFunction, 'is_generic' => FALSE);
|
||||
return $this->cache[$cachekey];
|
||||
}
|
||||
|
||||
$stdFiles = array(
|
||||
// By convention, the $camelName.php is more likely to contain the
|
||||
// function, so test it first
|
||||
'api/v' . $apiRequest['version'] . '/' . $camelName . '.php',
|
||||
'api/v' . $apiRequest['version'] . '/' . $camelName . '/' . $actionCamelName . '.php',
|
||||
);
|
||||
foreach ($stdFiles as $stdFile) {
|
||||
if (\CRM_Utils_File::isIncludable($stdFile)) {
|
||||
require_once $stdFile;
|
||||
if (function_exists($stdFunction)) {
|
||||
$this->cache[$cachekey] = array('function' => $stdFunction, 'is_generic' => FALSE);
|
||||
return $this->cache[$cachekey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if there is a generic implementation of the action
|
||||
require_once 'api/v3/Generic.php';
|
||||
# $genericFunction = 'civicrm_api3_generic_' . $apiRequest['action'];
|
||||
$genericFunction = $this->getFunctionName('generic', $apiRequest['action'], $apiRequest['version']);
|
||||
$genericFiles = array(
|
||||
// By convention, the Generic.php is more likely to contain the
|
||||
// function, so test it first
|
||||
'api/v' . $apiRequest['version'] . '/Generic.php',
|
||||
'api/v' . $apiRequest['version'] . '/Generic/' . $actionCamelName . '.php',
|
||||
);
|
||||
foreach ($genericFiles as $genericFile) {
|
||||
if (\CRM_Utils_File::isIncludable($genericFile)) {
|
||||
require_once $genericFile;
|
||||
if (function_exists($genericFunction)) {
|
||||
$this->cache[$cachekey] = array('function' => $genericFunction, 'is_generic' => TRUE);
|
||||
return $this->cache[$cachekey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->cache[$cachekey] = array('function' => FALSE, 'is_generic' => FALSE);
|
||||
return $this->cache[$cachekey];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the function name for a given API request.
|
||||
*
|
||||
* @param string $entity
|
||||
* API entity name.
|
||||
* @param string $action
|
||||
* API action name.
|
||||
* @param int $version
|
||||
* API version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getFunctionName($entity, $action, $version) {
|
||||
$entity = _civicrm_api_get_entity_name_from_camel($entity);
|
||||
return 'civicrm_api' . $version . '_' . $entity . '_' . $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load/require all files related to an entity.
|
||||
*
|
||||
* This should not normally be called because it's does a file-system scan; it's
|
||||
* only appropriate when introspection is really required (eg for "getActions").
|
||||
*
|
||||
* @param string $entity
|
||||
* API entity name.
|
||||
* @param int $version
|
||||
* API version.
|
||||
*/
|
||||
protected function loadEntity($entity, $version) {
|
||||
$camelName = _civicrm_api_get_camel_name($entity, $version);
|
||||
|
||||
// Check for master entity file; to match _civicrm_api_resolve(), only load the first one
|
||||
$stdFile = 'api/v' . $version . '/' . $camelName . '.php';
|
||||
if (\CRM_Utils_File::isIncludable($stdFile)) {
|
||||
require_once $stdFile;
|
||||
}
|
||||
|
||||
// Check for standalone action files; to match _civicrm_api_resolve(), only load the first one
|
||||
$loaded_files = array(); // array($relativeFilePath => TRUE)
|
||||
$include_dirs = array_unique(explode(PATH_SEPARATOR, get_include_path()));
|
||||
foreach ($include_dirs as $include_dir) {
|
||||
foreach (array($camelName, 'Generic') as $name) {
|
||||
$action_dir = implode(DIRECTORY_SEPARATOR,
|
||||
array($include_dir, 'api', "v${version}", $name));
|
||||
if (!is_dir($action_dir)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$iterator = new \DirectoryIterator($action_dir);
|
||||
foreach ($iterator as $fileinfo) {
|
||||
$file = $fileinfo->getFilename();
|
||||
if (array_key_exists($file, $loaded_files)) {
|
||||
continue; // action provided by an earlier item on include_path
|
||||
}
|
||||
|
||||
$parts = explode(".", $file);
|
||||
if (end($parts) == "php" && !preg_match('/Tests?\.php$/', $file)) {
|
||||
require_once $action_dir . DIRECTORY_SEPARATOR . $file;
|
||||
$loaded_files[$file] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Provider;
|
||||
|
||||
use Civi\API\Events;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* An API "provider" provides a means to execute API requests.
|
||||
*/
|
||||
interface ProviderInterface {
|
||||
/**
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @return array
|
||||
* structured response data (per civicrm_api3_create_success)
|
||||
* @see civicrm_api3_create_success
|
||||
* @throws \API_Exception
|
||||
*/
|
||||
public function invoke($apiRequest);
|
||||
|
||||
/**
|
||||
* @param int $version
|
||||
* API version.
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getEntityNames($version);
|
||||
|
||||
/**
|
||||
* @param int $version
|
||||
* API version.
|
||||
* @param string $entity
|
||||
* API entity.
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getActionNames($version, $entity);
|
||||
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Provider;
|
||||
|
||||
use Civi\API\Events;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* This class defines operations for inspecting the API's metadata.
|
||||
*/
|
||||
class ReflectionProvider implements EventSubscriberInterface, ProviderInterface {
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return array(
|
||||
Events::RESOLVE => array(
|
||||
// TODO decide if we really want to override others
|
||||
array('onApiResolve', Events::W_EARLY),
|
||||
),
|
||||
Events::AUTHORIZE => array(
|
||||
// TODO decide if we really want to override others
|
||||
array('onApiAuthorize', Events::W_EARLY),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var \Civi\API\Kernel
|
||||
*/
|
||||
private $apiKernel;
|
||||
|
||||
/**
|
||||
* @var array (string $entityName => array(string $actionName))
|
||||
*/
|
||||
private $actions;
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Kernel $apiKernel
|
||||
* The API kernel.
|
||||
*/
|
||||
public function __construct($apiKernel) {
|
||||
$this->apiKernel = $apiKernel;
|
||||
$this->actions = array(
|
||||
'Entity' => array('get', 'getactions'),
|
||||
'*' => array('getactions'), // 'getfields'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Event\ResolveEvent $event
|
||||
* API resolution event.
|
||||
*/
|
||||
public function onApiResolve(\Civi\API\Event\ResolveEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
$actions = $this->getActionNames($apiRequest['version'], $apiRequest['entity']);
|
||||
if (in_array($apiRequest['action'], $actions)) {
|
||||
$apiRequest['is_metadata'] = TRUE;
|
||||
$event->setApiRequest($apiRequest);
|
||||
$event->setApiProvider($this);
|
||||
$event->stopPropagation();
|
||||
// TODO decide if we really want to override others
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Event\AuthorizeEvent $event
|
||||
* API authorization event.
|
||||
*/
|
||||
public function onApiAuthorize(\Civi\API\Event\AuthorizeEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
if (isset($apiRequest['is_metadata'])) {
|
||||
// if (\CRM_Core_Permission::check('access AJAX API')
|
||||
// || \CRM_Core_Permission::check('access CiviCRM')) {
|
||||
$event->authorize();
|
||||
$event->stopPropagation();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param array $apiRequest
|
||||
* @return array
|
||||
* @throws \API_Exception
|
||||
*/
|
||||
public function invoke($apiRequest) {
|
||||
if (strtolower($apiRequest['entity']) == 'entity' && $apiRequest['action'] == 'get') {
|
||||
return civicrm_api3_create_success($this->apiKernel->getEntityNames($apiRequest['version']), $apiRequest['params'], 'entity', 'get');
|
||||
}
|
||||
switch ($apiRequest['action']) {
|
||||
case 'getactions':
|
||||
return civicrm_api3_create_success($this->apiKernel->getActionNames($apiRequest['version'], $apiRequest['entity']), $apiRequest['params'], $apiRequest['entity'], $apiRequest['action']);
|
||||
|
||||
//case 'getfields':
|
||||
// return $this->doGetFields($apiRequest);
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
// We shouldn't get here because onApiResolve() checks $this->actions
|
||||
throw new \API_Exception("Unsupported action (" . $apiRequest['entity'] . '.' . $apiRequest['action'] . ']');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param int $version
|
||||
* @return array
|
||||
*/
|
||||
public function getEntityNames($version) {
|
||||
return array('Entity');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param int $version
|
||||
* @param string $entity
|
||||
* @return array
|
||||
*/
|
||||
public function getActionNames($version, $entity) {
|
||||
$entity = _civicrm_api_get_camel_name($entity, $version);
|
||||
return isset($this->actions[$entity]) ? $this->actions[$entity] : $this->actions['*'];
|
||||
}
|
||||
|
||||
}
|
156
sites/all/modules/civicrm/Civi/API/Provider/StaticProvider.php
Normal file
156
sites/all/modules/civicrm/Civi/API/Provider/StaticProvider.php
Normal file
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Provider;
|
||||
|
||||
use Civi\API\Events;
|
||||
|
||||
/**
|
||||
* A static provider is useful for creating mock API implementations which
|
||||
* manages records in-memory.
|
||||
*
|
||||
* TODO Add a static provider to SyntaxConformanceTest to ensure that it's
|
||||
* representative.
|
||||
*/
|
||||
class StaticProvider extends AdhocProvider {
|
||||
protected $records;
|
||||
protected $fields;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return array(
|
||||
Events::RESOLVE => array(
|
||||
array('onApiResolve', Events::W_MIDDLE),
|
||||
),
|
||||
Events::AUTHORIZE => array(
|
||||
array('onApiAuthorize', Events::W_MIDDLE),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $version
|
||||
* API version.
|
||||
* @param string $entity
|
||||
* API entity.
|
||||
* @param array $fields
|
||||
* List of fields in this fake entity.
|
||||
* @param array $perms
|
||||
* Array(string $action => string $perm).
|
||||
* @param array $records
|
||||
* List of mock records to be read/updated by API calls.
|
||||
*/
|
||||
public function __construct($version, $entity, $fields, $perms = array(), $records = array()) {
|
||||
parent::__construct($version, $entity);
|
||||
|
||||
$perms = array_merge(array(
|
||||
'create' => \CRM_Core_Permission::ALWAYS_ALLOW_PERMISSION,
|
||||
'get' => \CRM_Core_Permission::ALWAYS_ALLOW_PERMISSION,
|
||||
'delete' => \CRM_Core_Permission::ALWAYS_ALLOW_PERMISSION,
|
||||
), $perms);
|
||||
|
||||
$this->records = \CRM_Utils_Array::index(array('id'), $records);
|
||||
$this->fields = $fields;
|
||||
|
||||
$this->addAction('create', $perms['create'], array($this, 'doCreate'));
|
||||
$this->addAction('get', $perms['get'], array($this, 'doGet'));
|
||||
$this->addAction('delete', $perms['delete'], array($this, 'doDelete'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getRecords() {
|
||||
return $this->records;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $records
|
||||
* List of mock records to be read/updated by API calls.
|
||||
*/
|
||||
public function setRecords($records) {
|
||||
$this->records = $records;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @return array
|
||||
* Formatted API result
|
||||
* @throws \API_Exception
|
||||
*/
|
||||
public function doCreate($apiRequest) {
|
||||
if (isset($apiRequest['params']['id'])) {
|
||||
$id = $apiRequest['params']['id'];
|
||||
}
|
||||
else {
|
||||
$id = max(array_keys($this->records)) + 1;
|
||||
$this->records[$id] = array();
|
||||
}
|
||||
|
||||
if (!isset($this->records[$id])) {
|
||||
throw new \API_Exception("Invalid ID: $id");
|
||||
}
|
||||
|
||||
foreach ($this->fields as $field) {
|
||||
if (isset($apiRequest['params'][$field])) {
|
||||
$this->records[$id][$field] = $apiRequest['params'][$field];
|
||||
}
|
||||
}
|
||||
|
||||
return civicrm_api3_create_success($this->records[$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @return array
|
||||
* Formatted API result
|
||||
* @throws \API_Exception
|
||||
*/
|
||||
public function doGet($apiRequest) {
|
||||
return _civicrm_api3_basic_array_get($apiRequest['entity'], $apiRequest['params'], $this->records, 'id', $this->fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $apiRequest
|
||||
* The full description of the API request.
|
||||
* @return array
|
||||
* Formatted API result
|
||||
* @throws \API_Exception
|
||||
*/
|
||||
public function doDelete($apiRequest) {
|
||||
$id = @$apiRequest['params']['id'];
|
||||
if ($id && isset($this->records[$id])) {
|
||||
unset($this->records[$id]);
|
||||
}
|
||||
return civicrm_api3_create_success(array());
|
||||
}
|
||||
|
||||
}
|
118
sites/all/modules/civicrm/Civi/API/Request.php
Normal file
118
sites/all/modules/civicrm/Civi/API/Request.php
Normal file
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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 Request
|
||||
* @package Civi\API
|
||||
*/
|
||||
class Request {
|
||||
private static $nextId = 1;
|
||||
|
||||
/**
|
||||
* Create a formatted/normalized request object.
|
||||
*
|
||||
* @param string $entity
|
||||
* API entity name.
|
||||
* @param string $action
|
||||
* API action name.
|
||||
* @param array $params
|
||||
* API parameters.
|
||||
* @param mixed $extra
|
||||
* Who knows? ...
|
||||
*
|
||||
* @throws \API_Exception
|
||||
* @return array
|
||||
* the request descriptor; keys:
|
||||
* - version: int
|
||||
* - entity: string
|
||||
* - action: string
|
||||
* - params: array (string $key => mixed $value) [deprecated in v4]
|
||||
* - extra: unspecified
|
||||
* - fields: NULL|array (string $key => array $fieldSpec)
|
||||
* - options: \CRM_Utils_OptionBag derived from params [v4-only]
|
||||
* - data: \CRM_Utils_OptionBag derived from params [v4-only]
|
||||
* - chains: unspecified derived from params [v4-only]
|
||||
*/
|
||||
public static function create($entity, $action, $params, $extra = NULL) {
|
||||
$version = \CRM_Utils_Array::value('version', $params);
|
||||
switch ($version) {
|
||||
default:
|
||||
$apiRequest = array();
|
||||
$apiRequest['id'] = self::$nextId++;
|
||||
$apiRequest['version'] = (int) $version;
|
||||
$apiRequest['params'] = $params;
|
||||
$apiRequest['extra'] = $extra;
|
||||
$apiRequest['fields'] = NULL;
|
||||
$apiRequest['entity'] = self::normalizeEntityName($entity, $apiRequest['version']);
|
||||
$apiRequest['action'] = self::normalizeActionName($action, $apiRequest['version']);
|
||||
return $apiRequest;
|
||||
|
||||
case 4:
|
||||
$callable = array("Civi\\Api4\\Entity\\$entity", $action);
|
||||
if (!is_callable($callable)) {
|
||||
throw new Exception\NotImplementedException("API ($entity, $action) does not exist (join the API team and implement it!)");
|
||||
}
|
||||
$apiCall = call_user_func($callable);
|
||||
$apiRequest['id'] = self::$nextId++;
|
||||
unset($params['version']);
|
||||
foreach ($params as $name => $param) {
|
||||
$setter = 'set' . ucfirst($name);
|
||||
$apiCall->$setter($param);
|
||||
}
|
||||
return $apiCall;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize entity to be CamelCase.
|
||||
*
|
||||
* APIv1-v3 munges entity/action names, and accepts any mixture of case and underscores.
|
||||
*
|
||||
* @param string $entity
|
||||
* @param int $version
|
||||
* @return string
|
||||
*/
|
||||
public static function normalizeEntityName($entity, $version) {
|
||||
return \CRM_Utils_String::convertStringToCamel(\CRM_Utils_String::munge($entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize api action name to be lowercase.
|
||||
*
|
||||
* APIv1-v3 munges entity/action names, and accepts any mixture of case and underscores.
|
||||
*
|
||||
* @param $action
|
||||
* @param $version
|
||||
* @return string
|
||||
*/
|
||||
public static function normalizeActionName($action, $version) {
|
||||
return strtolower(\CRM_Utils_String::munge($action));
|
||||
}
|
||||
|
||||
}
|
512
sites/all/modules/civicrm/Civi/API/SelectQuery.php
Normal file
512
sites/all/modules/civicrm/Civi/API/SelectQuery.php
Normal file
|
@ -0,0 +1,512 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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;
|
||||
use Civi\API\Exception\UnauthorizedException;
|
||||
|
||||
/**
|
||||
* Query builder for civicrm_api_basic_get.
|
||||
*
|
||||
* Fetches an entity based on specified params for the "where" clause,
|
||||
* return properties for the "select" clause,
|
||||
* as well as limit and order.
|
||||
*
|
||||
* Automatically joins on custom fields to return or filter by them.
|
||||
*
|
||||
* Supports an additional sql fragment which the calling api can provide.
|
||||
*
|
||||
* @package Civi\API
|
||||
*/
|
||||
abstract class SelectQuery {
|
||||
|
||||
const
|
||||
MAX_JOINS = 4,
|
||||
MAIN_TABLE_ALIAS = 'a';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $entity;
|
||||
public $select = array();
|
||||
public $where = array();
|
||||
public $orderBy = array();
|
||||
public $limit;
|
||||
public $offset;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $selectFields = array();
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $isFillUniqueFields = FALSE;
|
||||
/**
|
||||
* @var \CRM_Utils_SQL_Select
|
||||
*/
|
||||
protected $query;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $joins = array();
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $apiFieldSpec;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $entityFieldNames;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $aclFields = array();
|
||||
/**
|
||||
* @var string|bool
|
||||
*/
|
||||
protected $checkPermissions;
|
||||
|
||||
protected $apiVersion;
|
||||
|
||||
/**
|
||||
* @param string $entity
|
||||
* @param bool $checkPermissions
|
||||
*/
|
||||
public function __construct($entity, $checkPermissions) {
|
||||
$this->entity = $entity;
|
||||
require_once 'api/v3/utils.php';
|
||||
$baoName = _civicrm_api3_get_BAO($entity);
|
||||
$bao = new $baoName();
|
||||
|
||||
$this->entityFieldNames = _civicrm_api3_field_names(_civicrm_api3_build_fields_array($bao));
|
||||
$this->apiFieldSpec = $this->getFields();
|
||||
|
||||
$this->query = \CRM_Utils_SQL_Select::from($bao->tableName() . ' ' . self::MAIN_TABLE_ALIAS);
|
||||
$bao->free();
|
||||
|
||||
// Add ACLs first to avoid redundant subclauses
|
||||
$this->checkPermissions = $checkPermissions;
|
||||
$this->query->where($this->getAclClause(self::MAIN_TABLE_ALIAS, $baoName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build & execute the query and return results array
|
||||
*
|
||||
* @return array|int
|
||||
* @throws \API_Exception
|
||||
* @throws \CRM_Core_Exception
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function run() {
|
||||
$this->buildSelectFields();
|
||||
|
||||
$this->buildWhereClause();
|
||||
if (in_array('count_rows', $this->select)) {
|
||||
$this->query->select("count(*) as c");
|
||||
}
|
||||
else {
|
||||
foreach ($this->selectFields as $column => $alias) {
|
||||
$this->query->select("$column as `$alias`");
|
||||
}
|
||||
// Order by
|
||||
$this->buildOrderBy();
|
||||
}
|
||||
|
||||
// Limit
|
||||
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 (in_array('count_rows', $this->select)) {
|
||||
$result_dao->free();
|
||||
return (int) $result_dao->c;
|
||||
}
|
||||
$result_entities[$result_dao->id] = array();
|
||||
foreach ($this->selectFields as $column => $alias) {
|
||||
$returnName = $alias;
|
||||
$alias = str_replace('.', '_', $alias);
|
||||
if (property_exists($result_dao, $alias) && $result_dao->$alias != NULL) {
|
||||
$result_entities[$result_dao->id][$returnName] = $result_dao->$alias;
|
||||
}
|
||||
// Backward compatibility on fields names.
|
||||
if ($this->isFillUniqueFields && !empty($this->apiFieldSpec[$alias]['uniqueName'])) {
|
||||
$result_entities[$result_dao->id][$this->apiFieldSpec[$alias]['uniqueName']] = $result_dao->$alias;
|
||||
}
|
||||
foreach ($this->apiFieldSpec as $returnName => $spec) {
|
||||
if (empty($result_entities[$result_dao->id][$returnName]) && !empty($result_entities[$result_dao->id][$spec['name']])) {
|
||||
$result_entities[$result_dao->id][$returnName] = $result_entities[$result_dao->id][$spec['name']];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
$result_dao->free();
|
||||
return $result_entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \CRM_Utils_SQL_Select $sqlFragment
|
||||
* @return SelectQuery
|
||||
*/
|
||||
public function merge($sqlFragment) {
|
||||
$this->query->merge($sqlFragment);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins onto an fk field
|
||||
*
|
||||
* Adds one or more joins to the query to make this field available for use in a clause.
|
||||
*
|
||||
* Enforces permissions at the api level and by appending the acl clause for that entity to the join.
|
||||
*
|
||||
* @param $fkFieldName
|
||||
* @param $side
|
||||
*
|
||||
* @return array|null
|
||||
* Returns the table and field name for adding this field to a SELECT or WHERE clause
|
||||
* @throws \API_Exception
|
||||
* @throws \Civi\API\Exception\UnauthorizedException
|
||||
*/
|
||||
protected function addFkField($fkFieldName, $side) {
|
||||
$stack = explode('.', $fkFieldName);
|
||||
if (count($stack) < 2) {
|
||||
return NULL;
|
||||
}
|
||||
$prev = self::MAIN_TABLE_ALIAS;
|
||||
foreach ($stack as $depth => $fieldName) {
|
||||
// Setup variables then skip the first level
|
||||
if (!$depth) {
|
||||
$fk = $fieldName;
|
||||
// We only join on core fields
|
||||
// @TODO: Custom contact ref fields could be supported too
|
||||
if (!in_array($fk, $this->entityFieldNames)) {
|
||||
return NULL;
|
||||
}
|
||||
$fkField = &$this->apiFieldSpec[$fk];
|
||||
continue;
|
||||
}
|
||||
// More than 4 joins deep seems excessive - DOS attack?
|
||||
if ($depth > self::MAX_JOINS) {
|
||||
throw new UnauthorizedException("Maximum number of joins exceeded in parameter $fkFieldName");
|
||||
}
|
||||
$subStack = array_slice($stack, 0, $depth);
|
||||
$this->getJoinInfo($fkField, $subStack);
|
||||
if (!isset($fkField['FKApiName']) || !isset($fkField['FKClassName'])) {
|
||||
// Join doesn't exist - might be another param with a dot in it for some reason, we'll just ignore it.
|
||||
return NULL;
|
||||
}
|
||||
// Ensure we have permission to access the other api
|
||||
if (!$this->checkPermissionToJoin($fkField['FKApiName'], $subStack)) {
|
||||
throw new UnauthorizedException("Authorization failed to join onto {$fkField['FKApiName']} api in parameter $fkFieldName");
|
||||
}
|
||||
if (!isset($fkField['FKApiSpec'])) {
|
||||
$fkField['FKApiSpec'] = \_civicrm_api_get_fields($fkField['FKApiName']);
|
||||
}
|
||||
$fieldInfo = \CRM_Utils_Array::value($fieldName, $fkField['FKApiSpec']);
|
||||
|
||||
$keyColumn = \CRM_Utils_Array::value('FKKeyColumn', $fkField, 'id');
|
||||
if (!$fieldInfo || !isset($fkField['FKApiSpec'][$keyColumn])) {
|
||||
// Join doesn't exist - might be another param with a dot in it for some reason, we'll just ignore it.
|
||||
return NULL;
|
||||
}
|
||||
$fkTable = \CRM_Core_DAO_AllCoreTables::getTableForClass($fkField['FKClassName']);
|
||||
$tableAlias = implode('_to_', $subStack) . "_to_$fkTable";
|
||||
|
||||
// Add acl condition
|
||||
$joinCondition = array_merge(
|
||||
array("$prev.$fk = $tableAlias.$keyColumn"),
|
||||
$this->getAclClause($tableAlias, \_civicrm_api3_get_BAO($fkField['FKApiName']), $subStack)
|
||||
);
|
||||
|
||||
if (!empty($fkField['FKCondition'])) {
|
||||
$joinCondition[] = str_replace($fkTable, $tableAlias, $fkField['FKCondition']);
|
||||
}
|
||||
|
||||
$this->join($side, $fkTable, $tableAlias, $joinCondition);
|
||||
|
||||
if (strpos($fieldName, 'custom_') === 0) {
|
||||
list($tableAlias, $fieldName) = $this->addCustomField($fieldInfo, $side, $tableAlias);
|
||||
}
|
||||
|
||||
// Get ready to recurse to the next level
|
||||
$fk = $fieldName;
|
||||
$fkField = &$fkField['FKApiSpec'][$fieldName];
|
||||
$prev = $tableAlias;
|
||||
}
|
||||
return array($tableAlias, $fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get join info for dynamically-joined fields (e.g. "entity_id", "option_group")
|
||||
*
|
||||
* @param $fkField
|
||||
* @param $stack
|
||||
*/
|
||||
protected function getJoinInfo(&$fkField, $stack) {
|
||||
if ($fkField['name'] == 'entity_id') {
|
||||
$entityTableParam = substr(implode('.', $stack), 0, -2) . 'table';
|
||||
$entityTable = \CRM_Utils_Array::value($entityTableParam, $this->where);
|
||||
if ($entityTable && is_string($entityTable) && \CRM_Core_DAO_AllCoreTables::getClassForTable($entityTable)) {
|
||||
$fkField['FKClassName'] = \CRM_Core_DAO_AllCoreTables::getClassForTable($entityTable);
|
||||
$fkField['FKApiName'] = \CRM_Core_DAO_AllCoreTables::getBriefName($fkField['FKClassName']);
|
||||
}
|
||||
}
|
||||
if (!empty($fkField['pseudoconstant']['optionGroupName'])) {
|
||||
$fkField['FKClassName'] = 'CRM_Core_DAO_OptionValue';
|
||||
$fkField['FKApiName'] = 'OptionValue';
|
||||
$fkField['FKKeyColumn'] = 'value';
|
||||
$fkField['FKCondition'] = "civicrm_option_value.option_group_id = (SELECT id FROM civicrm_option_group WHERE name = '{$fkField['pseudoconstant']['optionGroupName']}')";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins onto a custom field
|
||||
*
|
||||
* Adds a join to the query to make this field available for use in a clause.
|
||||
*
|
||||
* @param array $customField
|
||||
* @param string $side
|
||||
* @param string $baseTable
|
||||
* @return array
|
||||
* Returns the table and field name for adding this field to a SELECT or WHERE clause
|
||||
*/
|
||||
protected function addCustomField($customField, $side, $baseTable = self::MAIN_TABLE_ALIAS) {
|
||||
$tableName = $customField["table_name"];
|
||||
$columnName = $customField["column_name"];
|
||||
$tableAlias = "{$baseTable}_to_$tableName";
|
||||
$this->join($side, $tableName, $tableAlias, array("`$tableAlias`.entity_id = `$baseTable`.id"));
|
||||
return array($tableAlias, $columnName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a field from the getFields list
|
||||
*
|
||||
* @param string $fieldName
|
||||
* @return array|null
|
||||
*/
|
||||
abstract protected function getField($fieldName);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected 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];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check permission to join onto another api entity
|
||||
*
|
||||
* @param string $entity
|
||||
* @param array $fieldStack
|
||||
* The stack of fields leading up to this join
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkPermissionToJoin($entity, $fieldStack) {
|
||||
if (!$this->checkPermissions) {
|
||||
return TRUE;
|
||||
}
|
||||
// Build an array of params that relate to the joined entity
|
||||
$params = array(
|
||||
'version' => 3,
|
||||
'return' => array(),
|
||||
'check_permissions' => $this->checkPermissions,
|
||||
);
|
||||
$prefix = implode('.', $fieldStack) . '.';
|
||||
$len = strlen($prefix);
|
||||
foreach ($this->select as $key => $ret) {
|
||||
if (strpos($key, $prefix) === 0) {
|
||||
$params['return'][substr($key, $len)] = $ret;
|
||||
}
|
||||
}
|
||||
foreach ($this->where as $key => $param) {
|
||||
if (strpos($key, $prefix) === 0) {
|
||||
$params[substr($key, $len)] = $param;
|
||||
}
|
||||
}
|
||||
|
||||
return \Civi::service('civi_api_kernel')->runAuthorize($entity, 'get', $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get acl clause for an entity
|
||||
*
|
||||
* @param string $tableAlias
|
||||
* @param string $baoName
|
||||
* @param array $stack
|
||||
* @return array
|
||||
*/
|
||||
protected function getAclClause($tableAlias, $baoName, $stack = array()) {
|
||||
if (!$this->checkPermissions) {
|
||||
return array();
|
||||
}
|
||||
// Prevent (most) redundant acl sub clauses if they have already been applied to the main entity.
|
||||
// FIXME: Currently this only works 1 level deep, but tracking through multiple joins would increase complexity
|
||||
// and just doing it for the first join takes care of most acl clause deduping.
|
||||
if (count($stack) === 1 && in_array($stack[0], $this->aclFields)) {
|
||||
return array();
|
||||
}
|
||||
$clauses = $baoName::getSelectWhereClause($tableAlias);
|
||||
if (!$stack) {
|
||||
// Track field clauses added to the main entity
|
||||
$this->aclFields = array_keys($clauses);
|
||||
}
|
||||
return array_filter($clauses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Orders the query by one or more fields
|
||||
*
|
||||
* @throws \API_Exception
|
||||
* @throws \Civi\API\Exception\UnauthorizedException
|
||||
*/
|
||||
protected function buildOrderBy() {
|
||||
$sortParams = is_string($this->orderBy) ? explode(',', $this->orderBy) : (array) $this->orderBy;
|
||||
foreach ($sortParams as $index => $item) {
|
||||
$item = trim($item);
|
||||
if ($item == '(1)') {
|
||||
continue;
|
||||
}
|
||||
$words = preg_split("/[\s]+/", $item);
|
||||
if ($words) {
|
||||
// Direction defaults to ASC unless DESC is specified
|
||||
$direction = strtoupper(\CRM_Utils_Array::value(1, $words, '')) == 'DESC' ? ' DESC' : '';
|
||||
$field = $this->getField($words[0]);
|
||||
if ($field) {
|
||||
$this->query->orderBy(self::MAIN_TABLE_ALIAS . '.' . $field['name'] . $direction, NULL, $index);
|
||||
}
|
||||
elseif (strpos($words[0], '.')) {
|
||||
$join = $this->addFkField($words[0], 'LEFT');
|
||||
if ($join) {
|
||||
$this->query->orderBy("`{$join[0]}`.`{$join[1]}`$direction", NULL, $index);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new \API_Exception("Unknown field specified for sort. Cannot order by '$item'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $side
|
||||
* @param string $tableName
|
||||
* @param string $tableAlias
|
||||
* @param array $conditions
|
||||
*/
|
||||
public function join($side, $tableName, $tableAlias, $conditions) {
|
||||
// INNER JOINs take precedence over LEFT JOINs
|
||||
if ($side != 'LEFT' || !isset($this->joins[$tableAlias])) {
|
||||
$this->joins[$tableAlias] = $side;
|
||||
$this->query->join($tableAlias, "$side JOIN `$tableName` `$tableAlias` ON " . implode(' AND ', $conditions));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 if the table has one.
|
||||
if (array_key_exists('id', $this->apiFieldSpec)) {
|
||||
$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();
|
||||
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Subscriber;
|
||||
|
||||
use Civi\API\Events;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* This class determines what fields are allowed for a request
|
||||
* and validates that the fields are provided correctly.
|
||||
*/
|
||||
class APIv3SchemaAdapter implements EventSubscriberInterface {
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return array(
|
||||
Events::PREPARE => array(
|
||||
array('onApiPrepare', Events::W_MIDDLE),
|
||||
array('onApiPrepare_validate', Events::W_LATE),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Event\PrepareEvent $event
|
||||
* API preparation event.
|
||||
*
|
||||
* @throws \API_Exception
|
||||
*/
|
||||
public function onApiPrepare(\Civi\API\Event\PrepareEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
if ($apiRequest['version'] > 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
$apiRequest['fields'] = _civicrm_api3_api_getfields($apiRequest);
|
||||
|
||||
_civicrm_api3_swap_out_aliases($apiRequest, $apiRequest['fields']);
|
||||
if (strtolower($apiRequest['action']) != 'getfields') {
|
||||
if (empty($apiRequest['params']['id'])) {
|
||||
$apiRequest['params'] = array_merge($this->getDefaults($apiRequest['fields']), $apiRequest['params']);
|
||||
}
|
||||
// Note: If 'id' is set then verify_mandatory will only check 'version'.
|
||||
civicrm_api3_verify_mandatory($apiRequest['params'], NULL, $this->getRequired($apiRequest['fields']));
|
||||
}
|
||||
|
||||
$event->setApiRequest($apiRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Event\Event $event
|
||||
* API preparation 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of defaults for the given API (function is a wrapper on getfields).
|
||||
* @param $fields
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaults($fields) {
|
||||
$defaults = array();
|
||||
|
||||
foreach ($fields as $field => $values) {
|
||||
if (isset($values['api.default'])) {
|
||||
$defaults[$field] = $values['api.default'];
|
||||
}
|
||||
}
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of required fields for the given API (function is a wrapper on getfields).
|
||||
* @param $fields
|
||||
* @return array
|
||||
*/
|
||||
public function getRequired($fields) {
|
||||
$required = array('version');
|
||||
|
||||
foreach ($fields as $field => $values) {
|
||||
if (!empty($values['api.required'])) {
|
||||
$required[] = $field;
|
||||
}
|
||||
}
|
||||
return $required;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,224 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Subscriber;
|
||||
|
||||
use Civi\API\Events;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* The ChainSubscriber looks for API parameters which specify a nested or
|
||||
* chained API call. For example:
|
||||
*
|
||||
* @code
|
||||
* $result = civicrm_api('Contact', 'create', array(
|
||||
* 'version' => 3,
|
||||
* 'first_name' => 'Amy',
|
||||
* 'api.Email.create' => array(
|
||||
* 'email' => 'amy@example.com',
|
||||
* 'location_type_id' => 123,
|
||||
* ),
|
||||
* ));
|
||||
* @endcode
|
||||
*
|
||||
* The ChainSubscriber looks for any parameters of the form "api.Email.create";
|
||||
* if found, it issues the nested API call (and passes some extra context --
|
||||
* eg Amy's contact_id).
|
||||
*/
|
||||
class ChainSubscriber implements EventSubscriberInterface {
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return array(
|
||||
Events::RESPOND => array('onApiRespond', Events::W_EARLY),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Event\RespondEvent $event
|
||||
* API response event.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function onApiRespond(\Civi\API\Event\RespondEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call any nested api calls.
|
||||
*
|
||||
* TODO: We don't really need this to be a separate function.
|
||||
* @param \Civi\API\Kernel $apiKernel
|
||||
* @param $params
|
||||
* @param $result
|
||||
* @param $action
|
||||
* @param $entity
|
||||
* @param $version
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function callNestedApi($apiKernel, &$params, &$result, $action, $entity, $version) {
|
||||
$lowercase_entity = _civicrm_api_get_entity_name_from_camel($entity);
|
||||
|
||||
// We don't need to worry about nested api in the getfields/getoptions
|
||||
// actions, so just return immediately.
|
||||
if (in_array($action, array('getfields', 'getfield', 'getoptions'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($action == 'getsingle') {
|
||||
// I don't understand the protocol here, but we don't want
|
||||
// $result to be a recursive array
|
||||
// $result['values'][0] = $result;
|
||||
$oldResult = $result;
|
||||
$result = array('values' => array(0 => $oldResult));
|
||||
}
|
||||
foreach ($params as $field => $newparams) {
|
||||
if ((is_array($newparams) || $newparams === 1) && $field <> 'api.has_parent' && substr($field, 0, 3) == 'api') {
|
||||
|
||||
// 'api.participant.delete' => 1 is a valid options - handle 1
|
||||
// instead of an array
|
||||
if ($newparams === 1) {
|
||||
$newparams = array('version' => $version);
|
||||
}
|
||||
// can be api_ or api.
|
||||
$separator = $field[3];
|
||||
if (!($separator == '.' || $separator == '_')) {
|
||||
continue;
|
||||
}
|
||||
$subAPI = explode($separator, $field);
|
||||
|
||||
$subaction = empty($subAPI[2]) ? $action : $subAPI[2];
|
||||
$subParams = array(
|
||||
'debug' => \CRM_Utils_Array::value('debug', $params),
|
||||
);
|
||||
$subEntity = _civicrm_api_get_entity_name_from_camel($subAPI[1]);
|
||||
|
||||
// Hard coded list of entitys that have fields starting api_ and shouldn't be automatically
|
||||
// deemed to be chained API calls
|
||||
$skipList = array(
|
||||
'SmsProvider' => array('type', 'url', 'params'),
|
||||
'Job' => array('prefix', 'entity', 'action'),
|
||||
'Contact' => array('key'),
|
||||
);
|
||||
if (isset($skipList[$entity]) && in_array($subEntity, $skipList[$entity])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($result['values'] as $idIndex => $parentAPIValues) {
|
||||
|
||||
if ($subEntity != 'contact') {
|
||||
//contact spits the dummy at activity_id so what else won't it like?
|
||||
//set entity_id & entity table based on the parent's id & entity.
|
||||
//e.g for something like note if the parent call is contact
|
||||
//'entity_table' will be set to 'contact' & 'id' to the contact id
|
||||
//from the parent call. in this case 'contact_id' will also be
|
||||
//set to the parent's id
|
||||
if (!($subEntity == 'line_item' && $lowercase_entity == 'contribution' && $action != 'create')) {
|
||||
$subParams["entity_id"] = $parentAPIValues['id'];
|
||||
$subParams['entity_table'] = 'civicrm_' . $lowercase_entity;
|
||||
}
|
||||
|
||||
$addEntityId = TRUE;
|
||||
if ($subEntity == 'relationship' && $lowercase_entity == 'contact') {
|
||||
// if a relationship call is chained to a contact call, we need
|
||||
// to check whether contact_id_a or contact_id_b for the
|
||||
// relationship is given. If so, don't add an extra subParam
|
||||
// "contact_id" => parent_id.
|
||||
// See CRM-16084.
|
||||
foreach (array_keys($newparams) as $key) {
|
||||
if (substr($key, 0, 11) == 'contact_id_') {
|
||||
$addEntityId = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($addEntityId) {
|
||||
$subParams[$lowercase_entity . "_id"] = $parentAPIValues['id'];
|
||||
}
|
||||
}
|
||||
if ($entity != 'Contact' && \CRM_Utils_Array::value(strtolower($subEntity . "_id"), $parentAPIValues)) {
|
||||
//e.g. if event_id is in the values returned & subentity is event
|
||||
//then pass in event_id as 'id' don't do this for contact as it
|
||||
//does some weird things like returning primary email &
|
||||
//thus limiting the ability to chain email
|
||||
//TODO - this might need the camel treatment
|
||||
$subParams['id'] = $parentAPIValues[$subEntity . "_id"];
|
||||
}
|
||||
|
||||
if (\CRM_Utils_Array::value('entity_table', $result['values'][$idIndex]) == $subEntity) {
|
||||
$subParams['id'] = $result['values'][$idIndex]['entity_id'];
|
||||
}
|
||||
// if we are dealing with the same entity pass 'id' through
|
||||
// (useful for get + delete for example)
|
||||
if ($lowercase_entity == $subEntity) {
|
||||
$subParams['id'] = $result['values'][$idIndex]['id'];
|
||||
}
|
||||
|
||||
$subParams['version'] = $version;
|
||||
if (!empty($params['check_permissions'])) {
|
||||
$subParams['check_permissions'] = $params['check_permissions'];
|
||||
}
|
||||
$subParams['sequential'] = 1;
|
||||
$subParams['api.has_parent'] = 1;
|
||||
if (array_key_exists(0, $newparams)) {
|
||||
$genericParams = $subParams;
|
||||
// it is a numerically indexed array - ie. multiple creates
|
||||
foreach ($newparams as $entityparams) {
|
||||
$subParams = array_merge($genericParams, $entityparams);
|
||||
_civicrm_api_replace_variables($subParams, $result['values'][$idIndex], $separator);
|
||||
$result['values'][$idIndex][$field][] = $apiKernel->run($subEntity, $subaction, $subParams);
|
||||
if ($result['is_error'] === 1) {
|
||||
throw new \Exception($subEntity . ' ' . $subaction . 'call failed with' . $result['error_message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
$subParams = array_merge($subParams, $newparams);
|
||||
_civicrm_api_replace_variables($subParams, $result['values'][$idIndex], $separator);
|
||||
$result['values'][$idIndex][$field] = $apiKernel->run($subEntity, $subaction, $subParams);
|
||||
if (!empty($result['is_error'])) {
|
||||
throw new \Exception($subEntity . ' ' . $subaction . 'call failed with' . $result['error_message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($action == 'getsingle') {
|
||||
$result = $result['values'][0];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,375 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Subscriber;
|
||||
|
||||
use Civi\API\Events;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Given an entity which dynamically attaches itself to another entity,
|
||||
* determine if one has permission to the other entity.
|
||||
*
|
||||
* Example: Suppose one tries to manipulate a File which is attached to a
|
||||
* Mailing. DynamicFKAuthorization will enforce permissions on the File by
|
||||
* imitating the permissions of the Mailing.
|
||||
*
|
||||
* Note: This enforces a constraint: all matching API calls must define
|
||||
* "id" (e.g. for the file) or "entity_table+entity_id" or
|
||||
* "field_name+entity_id".
|
||||
*
|
||||
* Note: The permission guard does not exactly authorize the request, but it
|
||||
* may veto authorization.
|
||||
*/
|
||||
class DynamicFKAuthorization implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return array(
|
||||
Events::AUTHORIZE => array(
|
||||
array('onApiAuthorize', Events::W_EARLY),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var \Civi\API\Kernel
|
||||
*
|
||||
* Treat as private. Marked public due to PHP 5.3-compatibility issues.
|
||||
*/
|
||||
public $kernel;
|
||||
|
||||
/**
|
||||
* @var string, the entity for which we want to manage permissions
|
||||
*/
|
||||
protected $entityName;
|
||||
|
||||
/**
|
||||
* @var array <string> the actions for which we want to manage permissions
|
||||
*/
|
||||
protected $actions;
|
||||
|
||||
/**
|
||||
* @var string, SQL. Given a file ID, determine the entity+table it's attached to.
|
||||
*
|
||||
* ex: "SELECT if(cf.id,1,0) as is_valid, cef.entity_table, cef.entity_id
|
||||
* FROM civicrm_file cf
|
||||
* INNER JOIN civicrm_entity_file cef ON cf.id = cef.file_id
|
||||
* WHERE cf.id = %1"
|
||||
*
|
||||
* Note: %1 is a parameter
|
||||
* Note: There are three parameters
|
||||
* - is_valid: "1" if %1 identifies an actual record; otherwise "0"
|
||||
* - entity_table: NULL or the name of a related table
|
||||
* - entity_id: NULL or the ID of a row in the related table
|
||||
*/
|
||||
protected $lookupDelegateSql;
|
||||
|
||||
/**
|
||||
* @var string, SQL. Get a list of (field_name, table_name, extends) tuples.
|
||||
*
|
||||
* For example, one tuple might be ("custom_123", "civicrm_value_mygroup_4",
|
||||
* "Activity").
|
||||
*/
|
||||
protected $lookupCustomFieldSql;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*
|
||||
* Each item is an array(field_name => $, table_name => $, extends => $)
|
||||
*/
|
||||
protected $lookupCustomFieldCache;
|
||||
|
||||
/**
|
||||
* @var array list of related tables for which FKs are allowed
|
||||
*/
|
||||
protected $allowedDelegates;
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Kernel $kernel
|
||||
* The API kernel.
|
||||
* @param string $entityName
|
||||
* The entity for which we want to manage permissions (e.g. "File" or
|
||||
* "Note").
|
||||
* @param array $actions
|
||||
* The actions for which we want to manage permissions (e.g. "create",
|
||||
* "get", "delete").
|
||||
* @param string $lookupDelegateSql
|
||||
* See docblock in DynamicFKAuthorization::$lookupDelegateSql.
|
||||
* @param string $lookupCustomFieldSql
|
||||
* See docblock in DynamicFKAuthorization::$lookupCustomFieldSql.
|
||||
* @param array|NULL $allowedDelegates
|
||||
* e.g. "civicrm_mailing","civicrm_activity"; NULL to allow any.
|
||||
*/
|
||||
public function __construct($kernel, $entityName, $actions, $lookupDelegateSql, $lookupCustomFieldSql, $allowedDelegates = NULL) {
|
||||
$this->kernel = $kernel;
|
||||
$this->entityName = \CRM_Utils_String::convertStringToCamel($entityName);
|
||||
$this->actions = $actions;
|
||||
$this->lookupDelegateSql = $lookupDelegateSql;
|
||||
$this->lookupCustomFieldSql = $lookupCustomFieldSql;
|
||||
$this->allowedDelegates = $allowedDelegates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Event\AuthorizeEvent $event
|
||||
* API authorization event.
|
||||
* @throws \API_Exception
|
||||
* @throws \Civi\API\Exception\UnauthorizedException
|
||||
*/
|
||||
public function onApiAuthorize(\Civi\API\Event\AuthorizeEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
if ($apiRequest['version'] == 3 && \CRM_Utils_String::convertStringToCamel($apiRequest['entity']) == $this->entityName && in_array(strtolower($apiRequest['action']), $this->actions)) {
|
||||
if (isset($apiRequest['params']['field_name'])) {
|
||||
$fldIdx = \CRM_Utils_Array::index(array('field_name'), $this->getCustomFields());
|
||||
if (empty($fldIdx[$apiRequest['params']['field_name']])) {
|
||||
throw new \Exception("Failed to map custom field to entity table");
|
||||
}
|
||||
$apiRequest['params']['entity_table'] = $fldIdx[$apiRequest['params']['field_name']]['entity_table'];
|
||||
unset($apiRequest['params']['field_name']);
|
||||
}
|
||||
|
||||
if (/*!$isTrusted */
|
||||
empty($apiRequest['params']['id']) && empty($apiRequest['params']['entity_table'])
|
||||
) {
|
||||
throw new \API_Exception("Mandatory key(s) missing from params array: 'id' or 'entity_table'");
|
||||
}
|
||||
|
||||
if (isset($apiRequest['params']['id'])) {
|
||||
list($isValidId, $entityTable, $entityId) = $this->getDelegate($apiRequest['params']['id']);
|
||||
if ($isValidId && $entityTable && $entityId) {
|
||||
$this->authorizeDelegate($apiRequest['action'], $entityTable, $entityId, $apiRequest);
|
||||
$this->preventReassignment($apiRequest['params']['id'], $entityTable, $entityId, $apiRequest);
|
||||
return;
|
||||
}
|
||||
elseif ($isValidId) {
|
||||
throw new \API_Exception("Failed to match record to related entity");
|
||||
}
|
||||
elseif (!$isValidId && strtolower($apiRequest['action']) == 'get') {
|
||||
// The matches will be an empty set; doesn't make a difference if we
|
||||
// reject or accept.
|
||||
// To pass SyntaxConformanceTest, we won't veto "get" on empty-set.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($apiRequest['params']['entity_table'])) {
|
||||
$this->authorizeDelegate(
|
||||
$apiRequest['action'],
|
||||
$apiRequest['params']['entity_table'],
|
||||
\CRM_Utils_Array::value('entity_id', $apiRequest['params'], NULL),
|
||||
$apiRequest
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new \API_Exception("Failed to run permission check");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $action
|
||||
* The API action (e.g. "create").
|
||||
* @param string $entityTable
|
||||
* The target entity table (e.g. "civicrm_mailing").
|
||||
* @param int|NULL $entityId
|
||||
* The target entity ID.
|
||||
* @param array $apiRequest
|
||||
* The full API request.
|
||||
* @throws \Exception
|
||||
* @throws \API_Exception
|
||||
* @throws \Civi\API\Exception\UnauthorizedException
|
||||
*/
|
||||
public function authorizeDelegate($action, $entityTable, $entityId, $apiRequest) {
|
||||
$entity = $this->getDelegatedEntityName($entityTable);
|
||||
if (!$entity) {
|
||||
throw new \API_Exception("Failed to run permission check: Unrecognized target entity table ($entityTable)");
|
||||
}
|
||||
if (!$entityId) {
|
||||
throw new \Civi\API\Exception\UnauthorizedException("Authorization failed on ($entity): Missing entity_id");
|
||||
}
|
||||
|
||||
if ($this->isTrusted($apiRequest)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var \Exception $exception
|
||||
*/
|
||||
$exception = NULL;
|
||||
$self = $this;
|
||||
\CRM_Core_Transaction::create(TRUE)->run(function($tx) use ($entity, $action, $entityId, &$exception, $self) {
|
||||
$tx->rollback(); // Just to be safe.
|
||||
|
||||
$params = array(
|
||||
'version' => 3,
|
||||
'check_permissions' => 1,
|
||||
'id' => $entityId,
|
||||
);
|
||||
|
||||
$result = $self->kernel->run($entity, $self->getDelegatedAction($action), $params);
|
||||
if ($result['is_error'] || empty($result['values'])) {
|
||||
$exception = new \Civi\API\Exception\UnauthorizedException("Authorization failed on ($entity,$entityId)", array(
|
||||
'cause' => $result,
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
if ($exception) {
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the request attempts to change the entity_table/entity_id of an
|
||||
* existing record, then generate an error.
|
||||
*
|
||||
* @param int $fileId
|
||||
* The main record being changed.
|
||||
* @param string $entityTable
|
||||
* The saved FK.
|
||||
* @param int $entityId
|
||||
* The saved FK.
|
||||
* @param array $apiRequest
|
||||
* The full API request.
|
||||
* @throws \API_Exception
|
||||
*/
|
||||
public function preventReassignment($fileId, $entityTable, $entityId, $apiRequest) {
|
||||
if (strtolower($apiRequest['action']) == 'create' && $fileId && !$this->isTrusted($apiRequest)) {
|
||||
// TODO: no change in field_name?
|
||||
if (isset($apiRequest['params']['entity_table']) && $entityTable != $apiRequest['params']['entity_table']) {
|
||||
throw new \API_Exception("Cannot modify entity_table");
|
||||
}
|
||||
if (isset($apiRequest['params']['entity_id']) && $entityId != $apiRequest['params']['entity_id']) {
|
||||
throw new \API_Exception("Cannot modify entity_id");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $entityTable
|
||||
* The target entity table (e.g. "civicrm_mailing" or "civicrm_activity").
|
||||
* @return string|NULL
|
||||
* The target entity name (e.g. "Mailing" or "Activity").
|
||||
*/
|
||||
public function getDelegatedEntityName($entityTable) {
|
||||
if ($this->allowedDelegates === NULL || in_array($entityTable, $this->allowedDelegates)) {
|
||||
$className = \CRM_Core_DAO_AllCoreTables::getClassForTable($entityTable);
|
||||
if ($className) {
|
||||
$entityName = \CRM_Core_DAO_AllCoreTables::getBriefName($className);
|
||||
if ($entityName) {
|
||||
return $entityName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $action
|
||||
* API action name -- e.g. "create" ("When running *create* on a file...").
|
||||
* @return string
|
||||
* e.g. "create" ("Check for *create* permission on the mailing to which
|
||||
* it is attached.")
|
||||
*/
|
||||
public function getDelegatedAction($action) {
|
||||
switch ($action) {
|
||||
case 'get':
|
||||
// reading attachments requires reading the other entity
|
||||
return 'get';
|
||||
|
||||
case 'create':
|
||||
case 'delete':
|
||||
// creating/updating/deleting an attachment requires editing
|
||||
// the other entity
|
||||
return 'create';
|
||||
|
||||
default:
|
||||
return $action;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* e.g. file ID.
|
||||
* @return array
|
||||
* (0 => bool $isValid, 1 => string $entityTable, 2 => int $entityId)
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getDelegate($id) {
|
||||
$query = \CRM_Core_DAO::executeQuery($this->lookupDelegateSql, array(
|
||||
1 => array($id, 'Positive'),
|
||||
));
|
||||
if ($query->fetch()) {
|
||||
if (!preg_match('/^civicrm_value_/', $query->entity_table)) {
|
||||
// A normal attachment directly on its entity.
|
||||
return array($query->is_valid, $query->entity_table, $query->entity_id);
|
||||
}
|
||||
|
||||
// Ex: Translate custom-field table ("civicrm_value_foo_4") to
|
||||
// entity table ("civicrm_activity").
|
||||
$tblIdx = \CRM_Utils_Array::index(array('table_name'), $this->getCustomFields());
|
||||
if (isset($tblIdx[$query->entity_table])) {
|
||||
return array($query->is_valid, $tblIdx[$query->entity_table]['entity_table'], $query->entity_id);
|
||||
}
|
||||
throw new \Exception('Failed to lookup entity table for custom field.');
|
||||
}
|
||||
else {
|
||||
return array(FALSE, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $apiRequest
|
||||
* The full API request.
|
||||
* @return bool
|
||||
*/
|
||||
public function isTrusted($apiRequest) {
|
||||
// isn't this redundant?
|
||||
return empty($apiRequest['params']['check_permissions']) or $apiRequest['params']['check_permissions'] == FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* Each item has keys 'field_name', 'table_name', 'extends', 'entity_table'
|
||||
*/
|
||||
public function getCustomFields() {
|
||||
$query = \CRM_Core_DAO::executeQuery($this->lookupCustomFieldSql);
|
||||
$rows = array();
|
||||
while ($query->fetch()) {
|
||||
$rows[] = array(
|
||||
'field_name' => $query->field_name,
|
||||
'table_name' => $query->table_name,
|
||||
'extends' => $query->extends,
|
||||
'entity_table' => \CRM_Core_BAO_CustomGroup::getTableNameByEntityName($query->extends),
|
||||
);
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
}
|
108
sites/all/modules/civicrm/Civi/API/Subscriber/I18nSubscriber.php
Normal file
108
sites/all/modules/civicrm/Civi/API/Subscriber/I18nSubscriber.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Subscriber;
|
||||
|
||||
use Civi\API\Events;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Class I18nSubscriber
|
||||
* @package Civi\API\Subscriber
|
||||
*/
|
||||
class I18nSubscriber implements EventSubscriberInterface {
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return array(
|
||||
Events::PREPARE => array('onApiPrepare', Events::W_MIDDLE),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Event\Event $event
|
||||
* API preparation event.
|
||||
*
|
||||
* @throws \API_Exception
|
||||
*/
|
||||
public function onApiPrepare(\Civi\API\Event\Event $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
|
||||
// support multi-lingual requests
|
||||
if ($language = \CRM_Utils_Array::value('option.language', $apiRequest['params'])) {
|
||||
$this->setLocale($language);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tsLocale and dbLocale for multi-lingual sites.
|
||||
* Some code duplication from CRM/Core/BAO/ConfigSetting.php retrieve()
|
||||
* to avoid regressions from refactoring.
|
||||
* @param $lcMessagesRequest
|
||||
* @throws \API_Exception
|
||||
*/
|
||||
public function setLocale($lcMessagesRequest) {
|
||||
// We must validate whether the locale is valid, otherwise setting a bad
|
||||
// dbLocale could probably lead to sql-injection.
|
||||
$domain = new \CRM_Core_DAO_Domain();
|
||||
$domain->id = \CRM_Core_Config::domainID();
|
||||
$domain->find(TRUE);
|
||||
|
||||
// are we in a multi-language setup?
|
||||
$multiLang = $domain->locales ? TRUE : FALSE;
|
||||
$lcMessages = NULL;
|
||||
|
||||
// on multi-lang sites based on request and civicrm_uf_match
|
||||
if ($multiLang) {
|
||||
$config = \CRM_Core_Config::singleton();
|
||||
$languageLimit = array();
|
||||
if (isset($config->languageLimit) and $config->languageLimit) {
|
||||
$languageLimit = $config->languageLimit;
|
||||
}
|
||||
|
||||
if (in_array($lcMessagesRequest, array_keys($languageLimit))) {
|
||||
$lcMessages = $lcMessagesRequest;
|
||||
}
|
||||
else {
|
||||
throw new \API_Exception(ts('Language not enabled: %1', array(1 => $lcMessagesRequest)));
|
||||
}
|
||||
}
|
||||
|
||||
global $dbLocale;
|
||||
|
||||
// set suffix for table names - use views if more than one language
|
||||
if ($lcMessages) {
|
||||
$dbLocale = $multiLang && $lcMessages ? "_{$lcMessages}" : '';
|
||||
|
||||
// FIXME: an ugly hack to fix CRM-4041
|
||||
global $tsLocale;
|
||||
$tsLocale = $lcMessages;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Subscriber;
|
||||
|
||||
use Civi\API\Events;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* For any API requests that correspond to a Doctrine entity
|
||||
* ($apiRequest['doctrineClass']), check permissions specified in
|
||||
* Civi\API\Annotation\Permission.
|
||||
*/
|
||||
class PermissionCheck implements EventSubscriberInterface {
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return array(
|
||||
Events::AUTHORIZE => array(
|
||||
array('onApiAuthorize', Events::W_LATE),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Event\AuthorizeEvent $event
|
||||
* API authorization event.
|
||||
*
|
||||
* @throws \Civi\API\Exception\UnauthorizedException
|
||||
*/
|
||||
public function onApiAuthorize(\Civi\API\Event\AuthorizeEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
if ($apiRequest['version'] < 4) {
|
||||
// return early unless we’re told explicitly to do the permission check
|
||||
if (empty($apiRequest['params']['check_permissions']) or $apiRequest['params']['check_permissions'] == FALSE) {
|
||||
$event->authorize();
|
||||
$event->stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
require_once 'CRM/Core/DAO/permissions.php';
|
||||
$permissions = _civicrm_api3_permissions($apiRequest['entity'], $apiRequest['action'], $apiRequest['params']);
|
||||
|
||||
// $params might’ve been reset by the alterAPIPermissions() hook
|
||||
if (isset($apiRequest['params']['check_permissions']) and $apiRequest['params']['check_permissions'] == FALSE) {
|
||||
$event->authorize();
|
||||
$event->stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\CRM_Core_Permission::check($permissions) and !self::checkACLPermission($apiRequest)) {
|
||||
if (is_array($permissions)) {
|
||||
foreach ($permissions as &$permission) {
|
||||
if (is_array($permission)) {
|
||||
$permission = '( ' . implode(' or ', $permission) . ' )';
|
||||
}
|
||||
}
|
||||
$permissions = implode(' and ', $permissions);
|
||||
}
|
||||
// FIXME: Generating the exception ourselves allows for detailed error
|
||||
// but doesn't play well with multiple authz subscribers.
|
||||
throw new \Civi\API\Exception\UnauthorizedException("API permission check failed for {$apiRequest['entity']}/{$apiRequest['action']} call; insufficient permission: require $permissions");
|
||||
}
|
||||
|
||||
$event->authorize();
|
||||
$event->stopPropagation();
|
||||
}
|
||||
elseif ($apiRequest['version'] == 4) {
|
||||
if (!$apiRequest->getCheckPermissions()) {
|
||||
$event->authorize();
|
||||
$event->stopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check API for ACL permission.
|
||||
*
|
||||
* @param array $apiRequest
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function checkACLPermission($apiRequest) {
|
||||
switch ($apiRequest['entity']) {
|
||||
case 'UFGroup':
|
||||
case 'UFField':
|
||||
$ufGroups = \CRM_Core_PseudoConstant::get('CRM_Core_DAO_UFField', 'uf_group_id');
|
||||
$aclCreate = \CRM_ACL_API::group(\CRM_Core_Permission::CREATE, NULL, 'civicrm_uf_group', $ufGroups);
|
||||
$aclEdit = \CRM_ACL_API::group(\CRM_Core_Permission::EDIT, NULL, 'civicrm_uf_group', $ufGroups);
|
||||
$ufGroupId = $apiRequest['entity'] == 'UFGroup' ? $apiRequest['params']['id'] : $apiRequest['params']['uf_group_id'];
|
||||
if (in_array($ufGroupId, $aclEdit) or $aclCreate) {
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
|
||||
//CRM-16777: Disable schedule reminder with ACLs.
|
||||
case 'ActionSchedule':
|
||||
$events = \CRM_Event_BAO_Event::getEvents();
|
||||
$aclEdit = \CRM_ACL_API::group(\CRM_Core_Permission::EDIT, NULL, 'civicrm_event', $events);
|
||||
$param = array('id' => $apiRequest['params']['id']);
|
||||
$eventId = \CRM_Core_BAO_ActionSchedule::retrieve($param, $value = array());
|
||||
if (in_array($eventId->entity_value, $aclEdit)) {
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Subscriber;
|
||||
|
||||
use Civi\API\Events;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Class TransactionSubscriber
|
||||
*
|
||||
* Implement transaction management for API calls. Two API options are accepted:
|
||||
* - is_transactional: bool|'nest' - if true, then all work is done inside a
|
||||
* transaction. By default, true for mutator actions (C-UD). 'nest' will
|
||||
* force creation of a nested transaction; otherwise, the default is to
|
||||
* re-use any existing transactions.
|
||||
* - options.force_rollback: bool - if true, all work is done in a nested
|
||||
* transaction which will be rolled back.
|
||||
*
|
||||
* @package Civi\API\Subscriber
|
||||
*/
|
||||
class TransactionSubscriber implements EventSubscriberInterface {
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return array(
|
||||
Events::PREPARE => array('onApiPrepare', Events::W_EARLY),
|
||||
Events::RESPOND => array('onApiRespond', Events::W_MIDDLE),
|
||||
Events::EXCEPTION => array('onApiException', Events::W_EARLY),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array (scalar $apiRequestId => CRM_Core_Transaction $tx)
|
||||
*/
|
||||
private $transactions = array();
|
||||
|
||||
/**
|
||||
* @var array (scalar $apiRequestId => bool)
|
||||
*
|
||||
* A list of requests which should be forcibly rolled back to
|
||||
* their save points.
|
||||
*/
|
||||
private $forceRollback = array();
|
||||
|
||||
/**
|
||||
* Determine if an API request should be treated as transactional.
|
||||
*
|
||||
* @param \Civi\API\Provider\ProviderInterface $apiProvider
|
||||
* The API provider responsible for this request.
|
||||
* @param array $apiRequest
|
||||
* The full API request.
|
||||
* @return bool
|
||||
*/
|
||||
public function isTransactional($apiProvider, $apiRequest) {
|
||||
if ($this->isForceRollback($apiProvider, $apiRequest)) {
|
||||
return TRUE;
|
||||
}
|
||||
if (isset($apiRequest['params']['is_transactional'])) {
|
||||
return \CRM_Utils_String::strtobool($apiRequest['params']['is_transactional']) || $apiRequest['params']['is_transactional'] == 'nest';
|
||||
}
|
||||
return strtolower($apiRequest['action']) == 'create' || strtolower($apiRequest['action']) == 'delete' || strtolower($apiRequest['action']) == 'submit';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if caller wants us to *always* rollback.
|
||||
*
|
||||
* @param \Civi\API\Provider\ProviderInterface $apiProvider
|
||||
* The API provider responsible for this request.
|
||||
* @param array $apiRequest
|
||||
* The full API request.
|
||||
* @return bool
|
||||
*/
|
||||
public function isForceRollback($apiProvider, $apiRequest) {
|
||||
// FIXME: When APIv3 uses better parsing, only one check will be needed.
|
||||
if (isset($apiRequest['params']['options']['force_rollback'])) {
|
||||
return \CRM_Utils_String::strtobool($apiRequest['params']['options']['force_rollback']);
|
||||
}
|
||||
if (isset($apiRequest['options']['force_rollback'])) {
|
||||
return \CRM_Utils_String::strtobool($apiRequest['options']['force_rollback']);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if caller wants a nested transaction or a re-used transaction.
|
||||
*
|
||||
* @param \Civi\API\Provider\ProviderInterface $apiProvider
|
||||
* The API provider responsible for this request.
|
||||
* @param array $apiRequest
|
||||
* The full API request.
|
||||
* @return bool
|
||||
* True if a new nested transaction is required; false if active tx may be used
|
||||
*/
|
||||
public function isNested($apiProvider, $apiRequest) {
|
||||
if ($this->isForceRollback($apiProvider, $apiRequest)) {
|
||||
return TRUE;
|
||||
}
|
||||
if (isset($apiRequest['params']['is_transactional']) && $apiRequest['params']['is_transactional'] === 'nest') {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a new transaction instance (if appropriate in the current policy)
|
||||
*
|
||||
* @param \Civi\API\Event\PrepareEvent $event
|
||||
* API preparation event.
|
||||
*/
|
||||
public function onApiPrepare(\Civi\API\Event\PrepareEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
if ($this->isTransactional($event->getApiProvider(), $apiRequest)) {
|
||||
$this->transactions[$apiRequest['id']] = new \CRM_Core_Transaction($this->isNested($event->getApiProvider(), $apiRequest));
|
||||
}
|
||||
if ($this->isForceRollback($event->getApiProvider(), $apiRequest)) {
|
||||
$this->transactions[$apiRequest['id']]->rollback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close any pending transactions.
|
||||
*
|
||||
* @param \Civi\API\Event\RespondEvent $event
|
||||
* API response event.
|
||||
*/
|
||||
public function onApiRespond(\Civi\API\Event\RespondEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
if (isset($this->transactions[$apiRequest['id']])) {
|
||||
if (civicrm_error($event->getResponse())) {
|
||||
$this->transactions[$apiRequest['id']]->rollback();
|
||||
}
|
||||
unset($this->transactions[$apiRequest['id']]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback the pending transaction.
|
||||
*
|
||||
* @param \Civi\API\Event\ExceptionEvent $event
|
||||
* API exception event.
|
||||
*/
|
||||
public function onApiException(\Civi\API\Event\ExceptionEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
if (isset($this->transactions[$apiRequest['id']])) {
|
||||
$this->transactions[$apiRequest['id']]->rollback();
|
||||
unset($this->transactions[$apiRequest['id']]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Subscriber;
|
||||
|
||||
use Civi\API\Events;
|
||||
use Civi\API\Event\AuthorizeEvent;
|
||||
use Civi\API\Event\RespondEvent;
|
||||
use Civi\API\WhitelistRule;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* The WhitelistSubscriber listens to API requests and matches them against
|
||||
* a whitelist of allowed API calls. If an API call does NOT appear in the
|
||||
* whitelist, then it generates an error.
|
||||
*
|
||||
* @package Civi
|
||||
* @copyright CiviCRM LLC (c) 2004-2017
|
||||
*/
|
||||
class WhitelistSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return array(
|
||||
Events::AUTHORIZE => array('onApiAuthorize', Events::W_EARLY),
|
||||
Events::RESPOND => array('onApiRespond', Events::W_MIDDLE),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Array(WhitelistRule).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $rules;
|
||||
|
||||
/**
|
||||
* Array (scalar $reqId => WhitelistRule $rule).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $activeRules;
|
||||
|
||||
/**
|
||||
* @param array $rules
|
||||
* Array of WhitelistRule.
|
||||
* @see WhitelistRule
|
||||
* @throws \CRM_Core_Exception
|
||||
*/
|
||||
public function __construct($rules) {
|
||||
$this->rules = array();
|
||||
foreach ($rules as $rule) {
|
||||
/** @var WhitelistRule $rule */
|
||||
if ($rule->isValid()) {
|
||||
$this->rules[] = $rule;
|
||||
}
|
||||
else {
|
||||
throw new \CRM_Core_Exception("Invalid rule");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine which, if any, whitelist rules apply this request.
|
||||
* Reject unauthorized requests.
|
||||
*
|
||||
* @param AuthorizeEvent $event
|
||||
* @throws \CRM_Core_Exception
|
||||
*/
|
||||
public function onApiAuthorize(AuthorizeEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
if (empty($apiRequest['params']['check_permissions']) || $apiRequest['params']['check_permissions'] !== 'whitelist') {
|
||||
return;
|
||||
}
|
||||
foreach ($this->rules as $rule) {
|
||||
if (TRUE === $rule->matches($apiRequest)) {
|
||||
$this->activeRules[$apiRequest['id']] = $rule;
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new \CRM_Core_Exception('The request does not match any active API authorizations.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply any filtering rules based on the chosen whitelist rule.
|
||||
* @param RespondEvent $event
|
||||
*/
|
||||
public function onApiRespond(RespondEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
$id = $apiRequest['id'];
|
||||
if (isset($this->activeRules[$id])) {
|
||||
$event->setResponse($this->activeRules[$id]->filter($apiRequest, $event->getResponse()));
|
||||
unset($this->activeRules[$id]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
107
sites/all/modules/civicrm/Civi/API/Subscriber/WrapperAdapter.php
Normal file
107
sites/all/modules/civicrm/Civi/API/Subscriber/WrapperAdapter.php
Normal file
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Subscriber;
|
||||
|
||||
use Civi\API\Events;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* This is a wrapper for the legacy "API Wrapper" interface which allows
|
||||
* wrappers to run through the new kernel. It translates from dispatcher events
|
||||
* ('civi.api.prepare', 'civi.api.respond') to wrapper calls ('fromApiInput', 'toApiOutput').
|
||||
*/
|
||||
class WrapperAdapter implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return array(
|
||||
Events::PREPARE => array('onApiPrepare', Events::W_MIDDLE),
|
||||
Events::RESPOND => array('onApiRespond', Events::W_EARLY * 2),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array(\API_Wrapper)
|
||||
*/
|
||||
protected $defaults;
|
||||
|
||||
/**
|
||||
* @param array $defaults
|
||||
* array(\API_Wrapper).
|
||||
*/
|
||||
public function __construct($defaults = array()) {
|
||||
$this->defaults = $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Event\PrepareEvent $event
|
||||
* API preparation event.
|
||||
*/
|
||||
public function onApiPrepare(\Civi\API\Event\PrepareEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
|
||||
// For input filtering, process $apiWrappers in forward order
|
||||
foreach ($this->getWrappers($apiRequest) as $apiWrapper) {
|
||||
$apiRequest = $apiWrapper->fromApiInput($apiRequest);
|
||||
}
|
||||
|
||||
$event->setApiRequest($apiRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Event\RespondEvent $event
|
||||
* API response event.
|
||||
*/
|
||||
public function onApiRespond(\Civi\API\Event\RespondEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
$result = $event->getResponse();
|
||||
|
||||
// For output filtering, process $apiWrappers in reverse order
|
||||
foreach (array_reverse($this->getWrappers($apiRequest)) as $apiWrapper) {
|
||||
$result = $apiWrapper->toApiOutput($apiRequest, $result);
|
||||
}
|
||||
|
||||
$event->setResponse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $apiRequest
|
||||
* The full API request.
|
||||
* @return array<\API_Wrapper>
|
||||
*/
|
||||
public function getWrappers($apiRequest) {
|
||||
if (!isset($apiRequest['wrappers'])) {
|
||||
$apiRequest['wrappers'] = $this->defaults;
|
||||
\CRM_Utils_Hook::apiWrappers($apiRequest['wrappers'], $apiRequest);
|
||||
}
|
||||
return $apiRequest['wrappers'];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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\Subscriber;
|
||||
|
||||
use Civi\API\Events;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Class XDebugSubscriber
|
||||
* @package Civi\API\Subscriber
|
||||
*/
|
||||
class XDebugSubscriber implements EventSubscriberInterface {
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
return array(
|
||||
Events::RESPOND => array('onApiRespond', Events::W_LATE),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Civi\API\Event\RespondEvent $event
|
||||
* API response event.
|
||||
*/
|
||||
public function onApiRespond(\Civi\API\Event\RespondEvent $event) {
|
||||
$apiRequest = $event->getApiRequest();
|
||||
$result = $event->getResponse();
|
||||
if (function_exists('xdebug_time_index')
|
||||
&& \CRM_Utils_Array::value('debug', $apiRequest['params'])
|
||||
// result would not be an array for getvalue
|
||||
&& is_array($result)
|
||||
) {
|
||||
$result['xdebug']['peakMemory'] = xdebug_peak_memory_usage();
|
||||
$result['xdebug']['memory'] = xdebug_memory_usage();
|
||||
$result['xdebug']['timeIndex'] = xdebug_time_index();
|
||||
$event->setResponse($result);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
282
sites/all/modules/civicrm/Civi/API/WhitelistRule.php
Normal file
282
sites/all/modules/civicrm/Civi/API/WhitelistRule.php
Normal file
|
@ -0,0 +1,282 @@
|
|||
<?php
|
||||
/*
|
||||
+--------------------------------------------------------------------+
|
||||
| CiviCRM version 4.7 |
|
||||
+--------------------------------------------------------------------+
|
||||
| Copyright CiviCRM LLC (c) 2004-2017 |
|
||||
+--------------------------------------------------------------------+
|
||||
| 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;
|
||||
|
||||
/**
|
||||
* A WhitelistRule is used to determine if an API call is authorized.
|
||||
* For example:
|
||||
*
|
||||
* @code
|
||||
* new WhitelistRule(array(
|
||||
* 'entity' => 'Contact',
|
||||
* 'actions' => array('get','getsingle'),
|
||||
* 'required' => array('contact_type' => 'Organization'),
|
||||
* 'fields' => array('id', 'display_name', 'sort_name', 'created_date'),
|
||||
* ));
|
||||
* @endcode
|
||||
*
|
||||
* This rule would allow API requests that attempt to get contacts of type "Organization",
|
||||
* but only a handful of fields ('id', 'display_name', 'sort_name', 'created_date')
|
||||
* can be filtered or returned.
|
||||
*
|
||||
* Class WhitelistRule
|
||||
* @package Civi\API\Subscriber
|
||||
*/
|
||||
class WhitelistRule {
|
||||
|
||||
static $IGNORE_FIELDS = array(
|
||||
'check_permissions',
|
||||
'debug',
|
||||
'offset',
|
||||
'option_offset',
|
||||
'option_limit',
|
||||
'option_sort',
|
||||
'options',
|
||||
'return',
|
||||
'rowCount',
|
||||
'sequential',
|
||||
'sort',
|
||||
'version',
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a batch of rules from an array.
|
||||
*
|
||||
* @param array $rules
|
||||
* @return array
|
||||
*/
|
||||
public static function createAll($rules) {
|
||||
$whitelist = array();
|
||||
foreach ($rules as $rule) {
|
||||
$whitelist[] = new WhitelistRule($rule);
|
||||
}
|
||||
return $whitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $version;
|
||||
|
||||
/**
|
||||
* Entity name or '*' (all entities)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $entity;
|
||||
|
||||
/**
|
||||
* List of actions which match, or '*' (all actions)
|
||||
*
|
||||
* @var string|array
|
||||
*/
|
||||
public $actions;
|
||||
|
||||
/**
|
||||
* List of key=>value pairs that *must* appear in $params.
|
||||
*
|
||||
* If there are no required fields, use an empty array.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $required;
|
||||
|
||||
/**
|
||||
* List of fields which may be optionally inputted or returned, or '*" (all fields)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $fields;
|
||||
|
||||
public function __construct($ruleSpec) {
|
||||
$this->version = $ruleSpec['version'];
|
||||
|
||||
if ($ruleSpec['entity'] === '*') {
|
||||
$this->entity = '*';
|
||||
}
|
||||
else {
|
||||
$this->entity = Request::normalizeEntityName($ruleSpec['entity'], $ruleSpec['version']);
|
||||
}
|
||||
|
||||
if ($ruleSpec['actions'] === '*') {
|
||||
$this->actions = '*';
|
||||
}
|
||||
else {
|
||||
$this->actions = array();
|
||||
foreach ((array) $ruleSpec['actions'] as $action) {
|
||||
$this->actions[] = Request::normalizeActionName($action, $ruleSpec['version']);
|
||||
}
|
||||
}
|
||||
|
||||
$this->required = $ruleSpec['required'];
|
||||
$this->fields = $ruleSpec['fields'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid() {
|
||||
if (empty($this->version)) {
|
||||
return FALSE;
|
||||
}
|
||||
if (empty($this->entity)) {
|
||||
return FALSE;
|
||||
}
|
||||
if (!is_array($this->actions) && $this->actions !== '*') {
|
||||
return FALSE;
|
||||
}
|
||||
if (!is_array($this->fields) && $this->fields !== '*') {
|
||||
return FALSE;
|
||||
}
|
||||
if (!is_array($this->required)) {
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $apiRequest
|
||||
* Parsed API request.
|
||||
* @return string|TRUE
|
||||
* If match, return TRUE. Otherwise, return a string with an error code.
|
||||
*/
|
||||
public function matches($apiRequest) {
|
||||
if (!$this->isValid()) {
|
||||
return 'invalid';
|
||||
}
|
||||
|
||||
if ($this->version != $apiRequest['version']) {
|
||||
return 'version';
|
||||
}
|
||||
if ($this->entity !== '*' && $this->entity !== $apiRequest['entity']) {
|
||||
return 'entity';
|
||||
}
|
||||
if ($this->actions !== '*' && !in_array($apiRequest['action'], $this->actions)) {
|
||||
return 'action';
|
||||
}
|
||||
|
||||
// These params *must* be included for the API request to proceed.
|
||||
foreach ($this->required as $param => $value) {
|
||||
if (!isset($apiRequest['params'][$param])) {
|
||||
return 'required-missing-' . $param;
|
||||
}
|
||||
if ($value !== '*' && $apiRequest['params'][$param] != $value) {
|
||||
return 'required-wrong-' . $param;
|
||||
}
|
||||
}
|
||||
|
||||
// These params *may* be included at the caller's discretion
|
||||
if ($this->fields !== '*') {
|
||||
$activatedFields = array_keys($apiRequest['params']);
|
||||
$activatedFields = preg_grep('/^api\./', $activatedFields, PREG_GREP_INVERT);
|
||||
if ($apiRequest['action'] == 'get') {
|
||||
// Kind'a silly we need to (re(re))parse here for each rule; would be more
|
||||
// performant if pre-parsed by Request::create().
|
||||
$options = _civicrm_api3_get_options_from_params($apiRequest['params'], TRUE, $apiRequest['entity'], 'get');
|
||||
$return = \CRM_Utils_Array::value('return', $options, array());
|
||||
$activatedFields = array_merge($activatedFields, array_keys($return));
|
||||
}
|
||||
|
||||
$unknowns = array_diff(
|
||||
$activatedFields,
|
||||
array_keys($this->required),
|
||||
$this->fields,
|
||||
self::$IGNORE_FIELDS
|
||||
);
|
||||
|
||||
if (!empty($unknowns)) {
|
||||
return 'unknown-' . implode(',', $unknowns);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the return values comply with the whitelist's
|
||||
* "fields" policy.
|
||||
*
|
||||
* Most API's follow a convention where the result includes
|
||||
* a 'values' array (which in turn is a list of records). Unfortunately,
|
||||
* some don't. If the API result doesn't meet our expectation,
|
||||
* then we probably don't know what's going on, so we abort the
|
||||
* request.
|
||||
*
|
||||
* This will probably break some of the layered-sugar APIs (like
|
||||
* getsingle, getvalue). Just use the meat-and-potatoes API instead.
|
||||
* Or craft a suitably targeted patch.
|
||||
*
|
||||
* @param array $apiRequest
|
||||
* API request.
|
||||
* @param array $apiResult
|
||||
* API result.
|
||||
* @return array
|
||||
* Modified API result.
|
||||
* @throws \API_Exception
|
||||
*/
|
||||
public function filter($apiRequest, $apiResult) {
|
||||
if ($this->fields === '*') {
|
||||
return $apiResult;
|
||||
}
|
||||
if (isset($apiResult['values']) && empty($apiResult['values'])) {
|
||||
// No data; filtering doesn't matter.
|
||||
return $apiResult;
|
||||
}
|
||||
if (is_array($apiResult['values'])) {
|
||||
$firstRow = \CRM_Utils_Array::first($apiResult['values']);
|
||||
if (is_array($firstRow)) {
|
||||
$fields = $this->filterFields(array_keys($firstRow));
|
||||
$apiResult['values'] = \CRM_Utils_Array::filterColumns($apiResult['values'], $fields);
|
||||
return $apiResult;
|
||||
}
|
||||
}
|
||||
throw new \API_Exception(sprintf('Filtering failed for %s.%s. Unrecognized result format.', $apiRequest['entity'], $apiRequest['action']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine which elements in $keys are acceptable under
|
||||
* the whitelist policy.
|
||||
*
|
||||
* @param array $keys
|
||||
* List of possible keys.
|
||||
* @return array
|
||||
* List of acceptable keys.
|
||||
*/
|
||||
protected function filterFields($keys) {
|
||||
$r = array();
|
||||
foreach ($keys as $key) {
|
||||
if (in_array($key, $this->fields)) {
|
||||
$r[] = $key;
|
||||
}
|
||||
elseif (preg_match('/^api\./', $key)) {
|
||||
$r[] = $key;
|
||||
}
|
||||
}
|
||||
return $r;
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue