Commit | Line | Data |
---|---|---|
19b53e5b C |
1 | <?php |
2 | ||
380f3545 TO |
3 | /* |
4 | +--------------------------------------------------------------------+ | |
41498ac5 | 5 | | Copyright CiviCRM LLC. All rights reserved. | |
380f3545 | 6 | | | |
41498ac5 TO |
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 | | |
380f3545 TO |
10 | +--------------------------------------------------------------------+ |
11 | */ | |
12 | ||
19b53e5b C |
13 | namespace Civi\Api4\Generic; |
14 | ||
29ab318b | 15 | use Civi\API\Exception\UnauthorizedException; |
4bf92107 | 16 | use Civi\Api4\Event\ValidateValuesEvent; |
29ab318b | 17 | use Civi\Api4\Utils\CoreUtil; |
4bf92107 | 18 | |
19b53e5b | 19 | /** |
fc95d9a5 | 20 | * Base class for all `Update` api actions |
19b53e5b C |
21 | * |
22 | * @method $this setValues(array $values) Set all field values from an array of key => value pairs. | |
19b53e5b C |
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() | |
26 | * | |
27 | * @package Civi\Api4\Generic | |
28 | */ | |
29 | abstract class AbstractUpdateAction extends AbstractBatchAction { | |
30 | ||
31 | /** | |
32 | * Field values to update. | |
33 | * | |
34 | * @var array | |
35 | * @required | |
36 | */ | |
37 | protected $values = []; | |
38 | ||
39 | /** | |
e3c6d5ff | 40 | * Reload $ENTITIES after saving. |
19b53e5b | 41 | * |
fc95d9a5 CW |
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. | |
19b53e5b C |
44 | * |
45 | * @var bool | |
46 | */ | |
47 | protected $reload = FALSE; | |
48 | ||
29ab318b CW |
49 | /** |
50 | * Criteria for selecting items to update. | |
51 | * | |
52 | * Required if no id is supplied in values. | |
53 | * | |
54 | * @var array | |
55 | */ | |
56 | protected $where = []; | |
57 | ||
58 | abstract protected function updateRecords(array $items): array; | |
59 | ||
60 | /** | |
61 | * @inheritDoc | |
62 | */ | |
63 | public function _run(Result $result) { | |
64 | $primaryKeys = CoreUtil::getInfoItem($this->getEntityName(), 'primary_key'); | |
65 | $this->formatWriteValues($this->values); | |
66 | ||
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]); | |
73 | } | |
74 | elseif (!($wheres[$id][1] === '=' && $wheres[$id][2] == $this->values[$id])) { | |
75 | throw new \Exception("Cannot update the $id of an existing " . $this->getEntityName() . '.'); | |
76 | } | |
77 | } | |
78 | } | |
79 | ||
80 | // Require WHERE if we didn't get primary keys from values | |
81 | if (!$this->where) { | |
82 | throw new \API_Exception('Parameter "where" is required unless primary keys are supplied in values.'); | |
83 | } | |
84 | ||
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"); | |
90 | } | |
91 | $items = [$this->values]; | |
92 | $this->validateValues(); | |
93 | $result->exchangeArray($this->updateRecords($items)); | |
94 | return; | |
95 | } | |
96 | ||
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"); | |
103 | } | |
104 | } | |
105 | ||
106 | $this->validateValues(); | |
107 | $result->exchangeArray($this->updateRecords($items)); | |
108 | } | |
109 | ||
19b53e5b | 110 | /** |
121ec912 | 111 | * @param string $fieldName |
19b53e5b C |
112 | * |
113 | * @return mixed|null | |
114 | */ | |
121ec912 | 115 | public function getValue(string $fieldName) { |
2e1f50d6 | 116 | return $this->values[$fieldName] ?? NULL; |
121ec912 CW |
117 | } |
118 | ||
119 | /** | |
fc95d9a5 CW |
120 | * Add an item to the values array. |
121 | * | |
121ec912 CW |
122 | * @param string $fieldName |
123 | * @param mixed $value | |
124 | * @return $this | |
125 | */ | |
126 | public function addValue(string $fieldName, $value) { | |
127 | $this->values[$fieldName] = $value; | |
128 | return $this; | |
19b53e5b C |
129 | } |
130 | ||
a9aac3bf TO |
131 | /** |
132 | * @throws \API_Exception | |
133 | */ | |
134 | protected function validateValues() { | |
4bf92107 | 135 | // FIXME: There should be a protocol to report a full list of errors... Perhaps a subclass of API_Exception? |
9f04ea33 TO |
136 | $e = new ValidateValuesEvent($this, [$this->values], new \CRM_Utils_LazyArray(function () { |
137 | $existing = $this->getBatchAction()->setSelect(['*'])->execute(); | |
138 | $result = []; | |
139 | foreach ($existing as $record) { | |
140 | $result[] = ['old' => $record, 'new' => $this->values]; | |
141 | } | |
142 | return $result; | |
143 | })); | |
4bf92107 TO |
144 | \Civi::dispatcher()->dispatch('civi.api4.validate', $e); |
145 | if (!empty($e->errors)) { | |
146 | throw $e->toException(); | |
147 | } | |
a9aac3bf TO |
148 | } |
149 | ||
19b53e5b | 150 | } |