Merge pull request #16849 from colemanw/ufMatch
[civicrm-core.git] / Civi / Api4 / Generic / Traits / DAOActionTrait.php
CommitLineData
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
21namespace Civi\Api4\Generic\Traits;
22
19b53e5b
C
23use Civi\Api4\Utils\FormattingUtil;
24use Civi\Api4\Query\Api4SelectQuery;
25
26/**
27 * @method string getLanguage()
28 * @method setLanguage(string $language)
29 */
30trait 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}