diff options
Diffstat (limited to 'www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits')
3 files changed, 509 insertions, 0 deletions
diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/ArrayQueryActionTrait.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/ArrayQueryActionTrait.php new file mode 100644 index 00000000..1d223f1b --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/ArrayQueryActionTrait.php @@ -0,0 +1,197 @@ +<?php + +namespace Civi\Api4\Generic\Traits; +use Civi\API\Exception\NotImplementedException; + +/** + * Helper functions for performing api queries on arrays of data. + * + * @package Civi\Api4\Generic + */ +trait ArrayQueryActionTrait { + + /** + * @param array $values + * List of all rows + * @return array + * Filtered list of rows + */ + protected function queryArray($values) { + $values = $this->filterArray($values); + $values = $this->sortArray($values); + $values = $this->selectArray($values); + $values = $this->limitArray($values); + return $values; + } + + /** + * @param array $values + * @return array + */ + protected function filterArray($values) { + if ($this->getWhere()) { + $values = array_filter($values, [$this, 'evaluateFilters']); + } + return array_values($values); + } + + /** + * @param array $row + * @return bool + */ + private function evaluateFilters($row) { + $where = $this->getWhere(); + $allConditions = in_array($where[0], ['AND', 'OR', 'NOT']) ? $where : ['AND', $where]; + return $this->walkFilters($row, $allConditions); + } + + /** + * @param array $row + * @param array $filters + * @return bool + * @throws \Civi\API\Exception\NotImplementedException + */ + private function walkFilters($row, $filters) { + switch ($filters[0]) { + case 'AND': + case 'NOT': + $result = TRUE; + foreach ($filters[1] as $filter) { + if (!$this->walkFilters($row, $filter)) { + $result = FALSE; + break; + } + } + return $result == ($filters[0] == 'AND'); + + case 'OR': + $result = !count($filters[1]); + foreach ($filters[1] as $filter) { + if ($this->walkFilters($row, $filter)) { + return TRUE; + } + } + return $result; + + default: + return $this->filterCompare($row, $filters); + } + } + + /** + * @param array $row + * @param array $condition + * @return bool + * @throws \Civi\API\Exception\NotImplementedException + */ + private function filterCompare($row, $condition) { + if (!is_array($condition)) { + throw new NotImplementedException('Unexpected where syntax; expecting array.'); + } + $value = isset($row[$condition[0]]) ? $row[$condition[0]] : NULL; + $operator = $condition[1]; + $expected = isset($condition[2]) ? $condition[2] : NULL; + switch ($operator) { + case '=': + case '!=': + case '<>': + $equal = $value == $expected; + // PHP is too imprecise about comparing the number 0 + if ($expected === 0 || $expected === '0') { + $equal = ($value === 0 || $value === '0'); + } + // PHP is too imprecise about comparing empty strings + if ($expected === '') { + $equal = ($value === ''); + } + return $equal == ($operator == '='); + + case 'IS NULL': + case 'IS NOT NULL': + return is_null($value) == ($operator == 'IS NULL'); + + case '>': + return $value > $expected; + + case '>=': + return $value >= $expected; + + case '<': + return $value < $expected; + + case '<=': + return $value <= $expected; + + case 'BETWEEN': + case 'NOT BETWEEN': + $between = ($value >= $expected[0] && $value <= $expected[1]); + return $between == ($operator == 'BETWEEN'); + + case 'LIKE': + case 'NOT LIKE': + $pattern = '/^' . str_replace('%', '.*', preg_quote($expected, '/')) . '$/i'; + return !preg_match($pattern, $value) == ($operator != 'LIKE'); + + case 'IN': + return in_array($value, $expected); + + case 'NOT IN': + return !in_array($value, $expected); + + default: + throw new NotImplementedException("Unsupported operator: '$operator' cannot be used with array data"); + } + } + + /** + * @param $values + * @return array + */ + protected function sortArray($values) { + if ($this->getOrderBy()) { + usort($values, [$this, 'sortCompare']); + } + return $values; + } + + private function sortCompare($a, $b) { + foreach ($this->getOrderBy() as $field => $dir) { + $modifier = $dir == 'ASC' ? 1 : -1; + if (isset($a[$field]) && isset($b[$field])) { + if ($a[$field] == $b[$field]) { + continue; + } + return (strnatcasecmp($a[$field], $b[$field]) * $modifier); + } + elseif (isset($a[$field]) || isset($b[$field])) { + return ((isset($a[$field]) ? 1 : -1) * $modifier); + } + } + return 0; + } + + /** + * @param $values + * @return array + */ + protected function selectArray($values) { + if ($this->getSelect()) { + foreach ($values as &$value) { + $value = array_intersect_key($value, array_flip($this->getSelect())); + } + } + return $values; + } + + /** + * @param $values + * @return array + */ + protected function limitArray($values) { + if ($this->getOffset() || $this->getLimit()) { + $values = array_slice($values, $this->getOffset() ?: 0, $this->getLimit() ?: NULL); + } + return $values; + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/CustomValueActionTrait.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/CustomValueActionTrait.php new file mode 100644 index 00000000..6a765b40 --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/CustomValueActionTrait.php @@ -0,0 +1,83 @@ +<?php + +namespace Civi\Api4\Generic\Traits; + +use Civi\Api4\Utils\FormattingUtil; +use Civi\Api4\Utils\CoreUtil; + +/** + * Helper functions for working with custom values + * + * @package Civi\Api4\Generic + */ +trait CustomValueActionTrait { + + function __construct($customGroup, $actionName) { + $this->customGroup = $customGroup; + parent::__construct('CustomValue', $actionName, ['id', 'entity_id']); + } + + /** + * Custom Group name if this is a CustomValue pseudo-entity. + * + * @var string + */ + private $customGroup; + + /** + * @inheritDoc + */ + public function getEntityName() { + return 'Custom_' . $this->getCustomGroup(); + } + + /** + * @inheritDoc + */ + protected function writeObjects($items) { + $result = []; + foreach ($items as $item) { + FormattingUtil::formatWriteParams($item, $this->getEntityName(), $this->getEntityFields()); + + $result[] = \CRM_Core_BAO_CustomValueTable::setValues($item); + } + return $result; + } + + /** + * @inheritDoc + */ + protected function deleteObjects($items) { + $customTable = CoreUtil::getCustomTableByName($this->getCustomGroup()); + $ids = []; + foreach ($items as $item) { + \CRM_Utils_Hook::pre('delete', $this->getEntityName(), $item['id'], \CRM_Core_DAO::$_nullArray); + \CRM_Utils_SQL_Delete::from($customTable) + ->where('id = #value') + ->param('#value', $item['id']) + ->execute(); + \CRM_Utils_Hook::post('delete', $this->getEntityName(), $item['id'], \CRM_Core_DAO::$_nullArray); + $ids[] = $item['id']; + } + return $ids; + } + + /** + * @inheritDoc + */ + protected function fillDefaults(&$params) { + foreach ($this->getEntityFields() as $name => $field) { + if (!isset($params[$name]) && isset($field['default_value'])) { + $params[$name] = $field['default_value']; + } + } + } + + /** + * @return string + */ + public function getCustomGroup() { + return $this->customGroup; + } + +} diff --git a/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/DAOActionTrait.php b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/DAOActionTrait.php new file mode 100644 index 00000000..1c92906b --- /dev/null +++ b/www/crm/wp-content/plugins/civicrm/civicrm/ext/api4/Civi/Api4/Generic/Traits/DAOActionTrait.php @@ -0,0 +1,229 @@ +<?php +namespace Civi\Api4\Generic\Traits; + +use CRM_Utils_Array as UtilsArray; +use Civi\Api4\Utils\FormattingUtil; +use Civi\Api4\Query\Api4SelectQuery; + +trait DAOActionTrait { + + /** + * @return \CRM_Core_DAO|string + */ + protected function getBaoName() { + require_once 'api/v3/utils.php'; + return \_civicrm_api3_get_BAO($this->getEntityName()); + } + + /** + * Extract the true fields from a BAO + * + * (Used by create and update actions) + * @param object $bao + * @return array + */ + public static function baoToArray($bao) { + $fields = $bao->fields(); + $values = []; + foreach ($fields as $key => $field) { + $name = $field['name']; + if (property_exists($bao, $name)) { + $values[$name] = $bao->$name; + } + } + return $values; + } + + /** + * @return array|int + */ + protected function getObjects() { + $query = new Api4SelectQuery($this->getEntityName(), $this->getCheckPermissions()); + $query->select = $this->getSelect(); + $query->where = $this->getWhere(); + $query->orderBy = $this->getOrderBy(); + $query->limit = $this->getLimit(); + $query->offset = $this->getOffset(); + return $query->run(); + } + + /** + * Write a bao object as part of a create/update action. + * + * @param array $items + * The record to write to the DB. + * @return array + * The record after being written to the DB (e.g. including newly assigned "id"). + * @throws \API_Exception + */ + protected function writeObjects($items) { + $baoName = $this->getBaoName(); + + // Some BAOs are weird and don't support a straightforward "create" method. + $oddballs = [ + 'Address' => 'add', + 'GroupContact' => 'add', + 'Website' => 'add', + ]; + $method = UtilsArray::value($this->getEntityName(), $oddballs, 'create'); + if (!method_exists($baoName, $method)) { + $method = 'add'; + } + + $result = []; + + foreach ($items as $item) { + $entityId = UtilsArray::value('id', $item); + FormattingUtil::formatWriteParams($item, $this->getEntityName(), $this->getEntityFields()); + $this->formatCustomParams($item, $entityId); + $item['check_permissions'] = $this->getCheckPermissions(); + + $apiKeyPermission = $this->getEntityName() != 'Contact' || !$this->getCheckPermissions() || array_key_exists('api_key', $this->getEntityFields()) + || ($entityId && \CRM_Core_Permission::check('edit own api keys') && \CRM_Core_Session::getLoggedInContactID() == $entityId); + + if (!$apiKeyPermission && array_key_exists('api_key', $item)) { + throw new \Civi\API\Exception\UnauthorizedException('Permission denied to modify api key'); + } + + // For some reason the contact bao requires this + if ($entityId && $this->getEntityName() == 'Contact') { + $item['contact_id'] = $entityId; + } + + if ($this->getCheckPermissions() && $entityId) { + $this->checkContactPermissions($baoName, $item); + } + + if (method_exists($baoName, $method)) { + $createResult = $baoName::$method($item); + } + else { + $createResult = $this->genericCreateMethod($item); + } + + if (!$createResult) { + $errMessage = sprintf('%s write operation failed', $this->getEntityName()); + throw new \API_Exception($errMessage); + } + + if (!empty($this->reload) && is_a($createResult, 'CRM_Core_DAO')) { + $createResult->find(TRUE); + } + + // trim back the junk and just get the array: + $resultArray = $this->baoToArray($createResult); + + if (!$apiKeyPermission && array_key_exists('api_key', $resultArray)) { + unset($resultArray['api_key']); + } + + $result[] = $resultArray; + } + return $result; + } + + /** + * Fallback when a BAO does not contain create or add functions + * + * @param $params + * @return mixed + */ + private function genericCreateMethod($params) { + $baoName = $this->getBaoName(); + $hook = empty($params['id']) ? 'create' : 'edit'; + + \CRM_Utils_Hook::pre($hook, $this->getEntityName(), UtilsArray::value('id', $params), $params); + /** @var \CRM_Core_DAO $instance */ + $instance = new $baoName(); + $instance->copyValues($params, TRUE); + $instance->save(); + \CRM_Utils_Hook::post($hook, $this->getEntityName(), $instance->id, $instance); + + return $instance; + } + + /** + * @param array $params + * @param int $entityId + * @return mixed + */ + private function formatCustomParams(&$params, $entityId) { + $customParams = []; + + // $customValueID is the ID of the custom value in the custom table for this + // entity (i guess this assumes it's not a multi value entity) + foreach ($params as $name => $value) { + if (strpos($name, '.') === FALSE) { + continue; + } + + list($customGroup, $customField) = explode('.', $name); + + $customFieldId = \CRM_Core_BAO_CustomField::getFieldValue( + \CRM_Core_DAO_CustomField::class, + $customField, + 'id', + 'name' + ); + $customFieldType = \CRM_Core_BAO_CustomField::getFieldValue( + \CRM_Core_DAO_CustomField::class, + $customField, + 'html_type', + 'name' + ); + $customFieldExtends = \CRM_Core_BAO_CustomGroup::getFieldValue( + \CRM_Core_DAO_CustomGroup::class, + $customGroup, + 'extends', + 'name' + ); + + // todo are we sure we don't want to allow setting to NULL? need to test + if ($customFieldId && NULL !== $value) { + + if ($customFieldType == 'CheckBox') { + // this function should be part of a class + formatCheckBoxField($value, 'custom_' . $customFieldId, $this->getEntityName()); + } + + \CRM_Core_BAO_CustomField::formatCustomField( + $customFieldId, + $customParams, + $value, + $customFieldExtends, + NULL, // todo check when this is needed + $entityId, + FALSE, + FALSE, + TRUE + ); + } + } + + if ($customParams) { + $params['custom'] = $customParams; + } + } + + /** + * Check edit/delete permissions for contacts and related entities. + * + * @param $baoName + * @param $item + * @throws \Civi\API\Exception\UnauthorizedException + */ + protected function checkContactPermissions($baoName, $item) { + if ($baoName == 'CRM_Contact_BAO_Contact') { + $permission = $this->getActionName() == 'delete' ? \CRM_Core_Permission::DELETE : \CRM_Core_Permission::EDIT; + if (!\CRM_Contact_BAO_Contact_Permission::allow($item['id'], $permission)) { + throw new \Civi\API\Exception\UnauthorizedException('Permission denied to modify contact record'); + } + } + else { + // Fixme: decouple from v3 + require_once 'api/v3/utils.php'; + _civicrm_api3_check_edit_permissions($baoName, ['check_permissions' => 1] + $item); + } + } + +} |