4 +--------------------------------------------------------------------+
5 | Copyright CiviCRM LLC. All rights reserved. |
7 | This work is published under the GNU AGPLv3 license with some |
8 | permitted exceptions and without any warranty. For full license |
9 | and copyright information, see https://civicrm.org/licensing |
10 +--------------------------------------------------------------------+
13 namespace Civi\Api4\Generic
;
15 use Civi\API\Exception\UnauthorizedException
;
16 use Civi\Api4\Event\ValidateValuesEvent
;
17 use Civi\Api4\Utils\CoreUtil
;
20 * Base class for all `Update` api actions
22 * @method $this setValues(array $values) Set all field values from an array of key => value pairs.
23 * @method array getValues() Get field values.
24 * @method $this setReload(bool $reload) Specify whether complete objects will be returned after saving.
25 * @method bool getReload()
27 * @package Civi\Api4\Generic
29 abstract class AbstractUpdateAction
extends AbstractBatchAction
{
32 * Field values to update.
37 protected $values = [];
40 * Reload $ENTITIES after saving.
42 * Setting to `true` will load complete records and return them as the api result.
43 * If `false` the api usually returns only the fields specified to be updated.
47 protected $reload = FALSE;
50 * Criteria for selecting items to update.
52 * Required if no id is supplied in values.
56 protected $where = [];
58 abstract protected function updateRecords(array $items): array;
63 public function _run(Result
$result) {
64 $primaryKeys = CoreUtil
::getInfoItem($this->getEntityName(), 'primary_key');
65 $this->formatWriteValues($this->values
);
67 // Add primary keys from values to WHERE clause and check for mismatch
68 foreach ($primaryKeys as $id) {
69 if (!empty($this->values
[$id])) {
70 $wheres = array_column($this->where
, NULL, 0);
71 if (!isset($wheres[$id])) {
72 $this->addWhere($id, '=', $this->values
[$id]);
74 elseif (!($wheres[$id][1] === '=' && $wheres[$id][2] == $this->values
[$id])) {
75 throw new \
Exception("Cannot update the $id of an existing " . $this->getEntityName() . '.');
80 // Require WHERE if we didn't get primary keys from values
82 throw new \
API_Exception('Parameter "where" is required unless primary keys are supplied in values.');
85 // Update a single record by primary key (if this entity has a single primary key)
86 if (count($this->where
) === 1 && count($primaryKeys) === 1 && $primaryKeys === $this->getSelect() && $this->where
[0][0] === $id && $this->where
[0][1] === '=' && !empty($this->where
[0][2])) {
87 $this->values
[$id] = $this->where
[0][2];
88 if ($this->checkPermissions
&& !CoreUtil
::checkAccessRecord($this, $this->values
, \CRM_Core_Session
::getLoggedInContactID() ?
: 0)) {
89 throw new UnauthorizedException("ACL check failed");
91 $items = [$this->values
];
92 $this->validateValues();
93 $result->exchangeArray($this->updateRecords($items));
97 // Batch update 1 or more records based on WHERE clause
98 $items = $this->getBatchRecords();
99 foreach ($items as &$item) {
100 $item = $this->values +
$item;
101 if ($this->checkPermissions
&& !CoreUtil
::checkAccessRecord($this, $item, \CRM_Core_Session
::getLoggedInContactID() ?
: 0)) {
102 throw new UnauthorizedException("ACL check failed");
106 $this->validateValues();
107 $result->exchangeArray($this->updateRecords($items));
111 * @param string $fieldName
115 public function getValue(string $fieldName) {
116 return $this->values
[$fieldName] ??
NULL;
120 * Add an item to the values array.
122 * @param string $fieldName
123 * @param mixed $value
126 public function addValue(string $fieldName, $value) {
127 $this->values
[$fieldName] = $value;
132 * @throws \API_Exception
134 protected function validateValues() {
135 // FIXME: There should be a protocol to report a full list of errors... Perhaps a subclass of API_Exception?
136 $e = new ValidateValuesEvent($this, [$this->values
], new \
CRM_Utils_LazyArray(function () {
137 $existing = $this->getBatchAction()->setSelect(['*'])->execute();
139 foreach ($existing as $record) {
140 $result[] = ['old' => $record, 'new' => $this->values
];
144 \Civi
::dispatcher()->dispatch('civi.api4.validate', $e);
145 if (!empty($e->errors
)) {
146 throw $e->toException();