Commit | Line | Data |
---|---|---|
19b53e5b | 1 | <?php |
380f3545 TO |
2 | |
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 | ||
13 | /** | |
14 | * | |
15 | * @package CRM | |
ca5cec67 | 16 | * @copyright CiviCRM LLC https://civicrm.org/licensing |
380f3545 TO |
17 | * $Id$ |
18 | * | |
19 | */ | |
20 | ||
19b53e5b C |
21 | namespace Civi\Api4\Generic\Traits; |
22 | ||
19b53e5b C |
23 | use Civi\Api4\Utils\FormattingUtil; |
24 | use Civi\Api4\Query\Api4SelectQuery; | |
25 | ||
26 | /** | |
27 | * @method string getLanguage() | |
28 | * @method setLanguage(string $language) | |
29 | */ | |
30 | trait DAOActionTrait { | |
31 | ||
32 | /** | |
33 | * Specify the language to use if this is a multi-lingual environment. | |
34 | * | |
35 | * E.g. "en_US" or "fr_CA" | |
36 | * | |
37 | * @var string | |
38 | */ | |
39 | protected $language; | |
40 | ||
41 | /** | |
42 | * @return \CRM_Core_DAO|string | |
43 | */ | |
44 | protected function getBaoName() { | |
45 | require_once 'api/v3/utils.php'; | |
46 | return \_civicrm_api3_get_BAO($this->getEntityName()); | |
47 | } | |
48 | ||
49 | /** | |
a4c7afc0 | 50 | * Convert saved object to array |
19b53e5b | 51 | * |
a4c7afc0 CW |
52 | * Used by create, update & save actions |
53 | * | |
54 | * @param \CRM_Core_DAO $bao | |
55 | * @param array $input | |
19b53e5b C |
56 | * @return array |
57 | */ | |
a4c7afc0 CW |
58 | public function baoToArray($bao, $input) { |
59 | $allFields = array_column($bao->fields(), 'name'); | |
60 | if (!empty($this->reload)) { | |
61 | $inputFields = $allFields; | |
62 | $bao->find(TRUE); | |
63 | } | |
64 | else { | |
65 | $inputFields = array_keys($input); | |
66 | // Convert 'null' input to true null | |
67 | foreach ($input as $key => $val) { | |
68 | if ($val === 'null') { | |
69 | $bao->$key = NULL; | |
70 | } | |
71 | } | |
72 | } | |
19b53e5b | 73 | $values = []; |
a4c7afc0 CW |
74 | foreach ($allFields as $field) { |
75 | if (isset($bao->$field) || in_array($field, $inputFields)) { | |
76 | $values[$field] = $bao->$field ?? NULL; | |
19b53e5b C |
77 | } |
78 | } | |
79 | return $values; | |
80 | } | |
81 | ||
82 | /** | |
83 | * @return array|int | |
84 | */ | |
85 | protected function getObjects() { | |
86 | $query = new Api4SelectQuery($this->getEntityName(), $this->getCheckPermissions(), $this->entityFields()); | |
87 | $query->select = $this->getSelect(); | |
88 | $query->where = $this->getWhere(); | |
89 | $query->orderBy = $this->getOrderBy(); | |
90 | $query->limit = $this->getLimit(); | |
91 | $query->offset = $this->getOffset(); | |
b65fa6dc CW |
92 | if ($this->getDebug()) { |
93 | $query->debugOutput =& $this->_debugOutput; | |
94 | } | |
c9b7a552 TO |
95 | $result = $query->run(); |
96 | if (is_array($result)) { | |
97 | \CRM_Utils_API_HTMLInputCoder::singleton()->decodeRows($result); | |
98 | } | |
99 | return $result; | |
19b53e5b C |
100 | } |
101 | ||
102 | /** | |
103 | * Fill field defaults which were declared by the api. | |
104 | * | |
105 | * Note: default values from core are ignored because the BAO or database layer will supply them. | |
106 | * | |
107 | * @param array $params | |
108 | */ | |
109 | protected function fillDefaults(&$params) { | |
110 | $fields = $this->entityFields(); | |
111 | $bao = $this->getBaoName(); | |
112 | $coreFields = array_column($bao::fields(), NULL, 'name'); | |
113 | ||
114 | foreach ($fields as $name => $field) { | |
115 | // If a default value in the api field is different than in core, the api should override it. | |
116 | if (!isset($params[$name]) && !empty($field['default_value']) && $field['default_value'] != \CRM_Utils_Array::pathGet($coreFields, [$name, 'default'])) { | |
117 | $params[$name] = $field['default_value']; | |
118 | } | |
119 | } | |
120 | } | |
121 | ||
122 | /** | |
123 | * Write bao objects as part of a create/update action. | |
124 | * | |
125 | * @param array $items | |
126 | * The records to write to the DB. | |
127 | * @return array | |
128 | * The records after being written to the DB (e.g. including newly assigned "id"). | |
129 | * @throws \API_Exception | |
130 | */ | |
131 | protected function writeObjects($items) { | |
132 | $baoName = $this->getBaoName(); | |
133 | ||
134 | // Some BAOs are weird and don't support a straightforward "create" method. | |
135 | $oddballs = [ | |
136 | 'EntityTag' => 'add', | |
137 | 'GroupContact' => 'add', | |
138 | 'Website' => 'add', | |
139 | ]; | |
140 | $method = $oddballs[$this->getEntityName()] ?? 'create'; | |
141 | if (!method_exists($baoName, $method)) { | |
142 | $method = 'add'; | |
143 | } | |
144 | ||
145 | $result = []; | |
146 | ||
147 | foreach ($items as $item) { | |
2929a8fb | 148 | $entityId = $item['id'] ?? NULL; |
19b53e5b C |
149 | FormattingUtil::formatWriteParams($item, $this->getEntityName(), $this->entityFields()); |
150 | $this->formatCustomParams($item, $entityId); | |
151 | $item['check_permissions'] = $this->getCheckPermissions(); | |
152 | ||
153 | // For some reason the contact bao requires this | |
154 | if ($entityId && $this->getEntityName() == 'Contact') { | |
155 | $item['contact_id'] = $entityId; | |
156 | } | |
157 | ||
158 | if ($this->getCheckPermissions()) { | |
159 | $this->checkContactPermissions($baoName, $item); | |
160 | } | |
161 | ||
162 | if ($this->getEntityName() == 'Address') { | |
163 | $createResult = $baoName::add($item, $this->fixAddress); | |
164 | } | |
165 | elseif (method_exists($baoName, $method)) { | |
166 | $createResult = $baoName::$method($item); | |
167 | } | |
168 | else { | |
169 | $createResult = $this->genericCreateMethod($item); | |
170 | } | |
171 | ||
172 | if (!$createResult) { | |
173 | $errMessage = sprintf('%s write operation failed', $this->getEntityName()); | |
174 | throw new \API_Exception($errMessage); | |
175 | } | |
176 | ||
a4c7afc0 | 177 | $result[] = $this->baoToArray($createResult, $item); |
19b53e5b | 178 | } |
2929a8fb | 179 | FormattingUtil::formatOutputValues($result, $this->entityFields(), $this->getEntityName()); |
19b53e5b C |
180 | return $result; |
181 | } | |
182 | ||
183 | /** | |
184 | * Fallback when a BAO does not contain create or add functions | |
185 | * | |
186 | * @param $params | |
187 | * @return mixed | |
188 | */ | |
189 | private function genericCreateMethod($params) { | |
190 | $baoName = $this->getBaoName(); | |
191 | $hook = empty($params['id']) ? 'create' : 'edit'; | |
192 | ||
2929a8fb | 193 | \CRM_Utils_Hook::pre($hook, $this->getEntityName(), $params['id'] ?? NULL, $params); |
19b53e5b C |
194 | /** @var \CRM_Core_DAO $instance */ |
195 | $instance = new $baoName(); | |
fc944198 | 196 | $instance->copyValues($params); |
19b53e5b C |
197 | $instance->save(); |
198 | \CRM_Utils_Hook::post($hook, $this->getEntityName(), $instance->id, $instance); | |
199 | ||
200 | return $instance; | |
201 | } | |
202 | ||
203 | /** | |
204 | * @param array $params | |
205 | * @param int $entityId | |
206 | * @return mixed | |
207 | */ | |
208 | protected function formatCustomParams(&$params, $entityId) { | |
209 | $customParams = []; | |
210 | ||
211 | // $customValueID is the ID of the custom value in the custom table for this | |
212 | // entity (i guess this assumes it's not a multi value entity) | |
213 | foreach ($params as $name => $value) { | |
214 | if (strpos($name, '.') === FALSE) { | |
215 | continue; | |
216 | } | |
217 | ||
218 | list($customGroup, $customField) = explode('.', $name); | |
219 | ||
220 | $customFieldId = \CRM_Core_BAO_CustomField::getFieldValue( | |
221 | \CRM_Core_DAO_CustomField::class, | |
222 | $customField, | |
223 | 'id', | |
224 | 'name' | |
225 | ); | |
226 | $customFieldType = \CRM_Core_BAO_CustomField::getFieldValue( | |
227 | \CRM_Core_DAO_CustomField::class, | |
228 | $customField, | |
229 | 'html_type', | |
230 | 'name' | |
231 | ); | |
232 | $customFieldExtends = \CRM_Core_BAO_CustomGroup::getFieldValue( | |
233 | \CRM_Core_DAO_CustomGroup::class, | |
234 | $customGroup, | |
235 | 'extends', | |
236 | 'name' | |
237 | ); | |
238 | ||
239 | // todo are we sure we don't want to allow setting to NULL? need to test | |
240 | if ($customFieldId && NULL !== $value) { | |
241 | ||
242 | if ($customFieldType == 'CheckBox') { | |
243 | // this function should be part of a class | |
244 | formatCheckBoxField($value, 'custom_' . $customFieldId, $this->getEntityName()); | |
245 | } | |
246 | ||
247 | \CRM_Core_BAO_CustomField::formatCustomField( | |
248 | $customFieldId, | |
249 | $customParams, | |
250 | $value, | |
251 | $customFieldExtends, | |
252 | // todo check when this is needed | |
253 | NULL, | |
254 | $entityId, | |
255 | FALSE, | |
256 | FALSE, | |
257 | TRUE | |
258 | ); | |
259 | } | |
260 | } | |
261 | ||
262 | if ($customParams) { | |
263 | $params['custom'] = $customParams; | |
264 | } | |
265 | } | |
266 | ||
267 | /** | |
268 | * Check edit/delete permissions for contacts and related entities. | |
269 | * | |
270 | * @param $baoName | |
271 | * @param $item | |
272 | * @throws \Civi\API\Exception\UnauthorizedException | |
273 | */ | |
274 | protected function checkContactPermissions($baoName, $item) { | |
275 | if ($baoName == 'CRM_Contact_BAO_Contact' && !empty($item['id'])) { | |
276 | $permission = $this->getActionName() == 'delete' ? \CRM_Core_Permission::DELETE : \CRM_Core_Permission::EDIT; | |
277 | if (!\CRM_Contact_BAO_Contact_Permission::allow($item['id'], $permission)) { | |
278 | throw new \Civi\API\Exception\UnauthorizedException('Permission denied to modify contact record'); | |
279 | } | |
280 | } | |
281 | else { | |
282 | // Fixme: decouple from v3 | |
283 | require_once 'api/v3/utils.php'; | |
284 | _civicrm_api3_check_edit_permissions($baoName, ['check_permissions' => 1] + $item); | |
285 | } | |
286 | } | |
287 | ||
288 | } |