CustomField - Reformat data when modifying field serialize property.
[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
19b53e5b
C
13namespace Civi\Api4\Generic\Traits;
14
c9e3994d 15use Civi\Api4\CustomField;
721c9da1 16use Civi\Api4\Service\Schema\Joinable\CustomGroupJoinable;
19b53e5b 17use Civi\Api4\Utils\FormattingUtil;
19b53e5b
C
18
19/**
20 * @method string getLanguage()
14a9c588 21 * @method $this setLanguage(string $language)
19b53e5b
C
22 */
23trait DAOActionTrait {
24
25 /**
26 * Specify the language to use if this is a multi-lingual environment.
27 *
28 * E.g. "en_US" or "fr_CA"
29 *
30 * @var string
31 */
32 protected $language;
33
34 /**
35 * @return \CRM_Core_DAO|string
36 */
37 protected function getBaoName() {
38 require_once 'api/v3/utils.php';
39 return \_civicrm_api3_get_BAO($this->getEntityName());
40 }
41
42 /**
a4c7afc0 43 * Convert saved object to array
19b53e5b 44 *
a4c7afc0
CW
45 * Used by create, update & save actions
46 *
47 * @param \CRM_Core_DAO $bao
48 * @param array $input
19b53e5b
C
49 * @return array
50 */
a4c7afc0
CW
51 public function baoToArray($bao, $input) {
52 $allFields = array_column($bao->fields(), 'name');
53 if (!empty($this->reload)) {
54 $inputFields = $allFields;
55 $bao->find(TRUE);
56 }
57 else {
58 $inputFields = array_keys($input);
59 // Convert 'null' input to true null
60 foreach ($input as $key => $val) {
61 if ($val === 'null') {
62 $bao->$key = NULL;
63 }
64 }
65 }
19b53e5b 66 $values = [];
a4c7afc0
CW
67 foreach ($allFields as $field) {
68 if (isset($bao->$field) || in_array($field, $inputFields)) {
69 $values[$field] = $bao->$field ?? NULL;
19b53e5b
C
70 }
71 }
72 return $values;
73 }
74
19b53e5b
C
75 /**
76 * Fill field defaults which were declared by the api.
77 *
78 * Note: default values from core are ignored because the BAO or database layer will supply them.
79 *
80 * @param array $params
81 */
82 protected function fillDefaults(&$params) {
83 $fields = $this->entityFields();
84 $bao = $this->getBaoName();
85 $coreFields = array_column($bao::fields(), NULL, 'name');
86
87 foreach ($fields as $name => $field) {
88 // If a default value in the api field is different than in core, the api should override it.
89 if (!isset($params[$name]) && !empty($field['default_value']) && $field['default_value'] != \CRM_Utils_Array::pathGet($coreFields, [$name, 'default'])) {
90 $params[$name] = $field['default_value'];
91 }
92 }
93 }
94
95 /**
96 * Write bao objects as part of a create/update action.
97 *
98 * @param array $items
99 * The records to write to the DB.
14a9c588 100 *
19b53e5b
C
101 * @return array
102 * The records after being written to the DB (e.g. including newly assigned "id").
103 * @throws \API_Exception
14a9c588 104 * @throws \CRM_Core_Exception
19b53e5b
C
105 */
106 protected function writeObjects($items) {
107 $baoName = $this->getBaoName();
108
109 // Some BAOs are weird and don't support a straightforward "create" method.
110 $oddballs = [
111 'EntityTag' => 'add',
112 'GroupContact' => 'add',
113 'Website' => 'add',
114 ];
115 $method = $oddballs[$this->getEntityName()] ?? 'create';
116 if (!method_exists($baoName, $method)) {
117 $method = 'add';
118 }
119
120 $result = [];
121
122 foreach ($items as $item) {
2929a8fb 123 $entityId = $item['id'] ?? NULL;
37d82abe 124 FormattingUtil::formatWriteParams($item, $this->entityFields());
19b53e5b
C
125 $this->formatCustomParams($item, $entityId);
126 $item['check_permissions'] = $this->getCheckPermissions();
127
128 // For some reason the contact bao requires this
14a9c588 129 if ($entityId && $this->getEntityName() === 'Contact') {
19b53e5b
C
130 $item['contact_id'] = $entityId;
131 }
132
133 if ($this->getCheckPermissions()) {
134 $this->checkContactPermissions($baoName, $item);
135 }
136
14a9c588 137 if ($this->getEntityName() === 'Address') {
19b53e5b
C
138 $createResult = $baoName::add($item, $this->fixAddress);
139 }
140 elseif (method_exists($baoName, $method)) {
141 $createResult = $baoName::$method($item);
142 }
143 else {
2ee9afab 144 $createResult = $baoName::writeRecord($item);
19b53e5b
C
145 }
146
147 if (!$createResult) {
148 $errMessage = sprintf('%s write operation failed', $this->getEntityName());
149 throw new \API_Exception($errMessage);
150 }
151
a4c7afc0 152 $result[] = $this->baoToArray($createResult, $item);
19b53e5b 153 }
2929a8fb 154 FormattingUtil::formatOutputValues($result, $this->entityFields(), $this->getEntityName());
19b53e5b
C
155 return $result;
156 }
157
19b53e5b
C
158 /**
159 * @param array $params
160 * @param int $entityId
14a9c588 161 *
19b53e5b 162 * @return mixed
14a9c588 163 *
164 * @throws \API_Exception
165 * @throws \CRM_Core_Exception
19b53e5b
C
166 */
167 protected function formatCustomParams(&$params, $entityId) {
168 $customParams = [];
169
170 // $customValueID is the ID of the custom value in the custom table for this
171 // entity (i guess this assumes it's not a multi value entity)
172 foreach ($params as $name => $value) {
c9e3994d
CW
173 $field = $this->getCustomFieldInfo($name);
174 if (!$field) {
19b53e5b
C
175 continue;
176 }
177
19b53e5b 178 // todo are we sure we don't want to allow setting to NULL? need to test
c9e3994d 179 if (NULL !== $value) {
19b53e5b 180
c9e3994d 181 if ($field['suffix']) {
721c9da1 182 $options = FormattingUtil::getPseudoconstantList($field, $field['suffix'], $params, $this->getActionName());
961e974c
CW
183 $value = FormattingUtil::replacePseudoconstant($options, $value, TRUE);
184 }
185
c9e3994d 186 if ($field['html_type'] === 'CheckBox') {
19b53e5b 187 // this function should be part of a class
c9e3994d 188 formatCheckBoxField($value, 'custom_' . $field['id'], $this->getEntityName());
19b53e5b
C
189 }
190
191 \CRM_Core_BAO_CustomField::formatCustomField(
c9e3994d 192 $field['id'],
19b53e5b
C
193 $customParams,
194 $value,
c9e3994d 195 $field['custom_group.extends'],
19b53e5b
C
196 // todo check when this is needed
197 NULL,
198 $entityId,
199 FALSE,
200 FALSE,
201 TRUE
202 );
203 }
204 }
205
206 if ($customParams) {
207 $params['custom'] = $customParams;
208 }
209 }
210
c9e3994d
CW
211 /**
212 * Gets field info needed to save custom data
213 *
721c9da1 214 * @param string $fieldExpr
c9e3994d
CW
215 * Field identifier with possible suffix, e.g. MyCustomGroup.MyField1:label
216 * @return array|NULL
217 */
721c9da1
CW
218 protected function getCustomFieldInfo(string $fieldExpr) {
219 if (strpos($fieldExpr, '.') === FALSE) {
c9e3994d
CW
220 return NULL;
221 }
721c9da1 222 list($groupName, $fieldName) = explode('.', $fieldExpr);
c9e3994d 223 list($fieldName, $suffix) = array_pad(explode(':', $fieldName), 2, NULL);
721c9da1
CW
224 $cacheKey = "APIv4_Custom_Fields-$groupName";
225 $info = \Civi::cache('metadata')->get($cacheKey);
226 if (!isset($info[$fieldName])) {
227 $info = [];
228 $fields = CustomField::get(FALSE)
c9e3994d
CW
229 ->addSelect('id', 'name', 'html_type', 'custom_group.extends')
230 ->addWhere('custom_group.name', '=', $groupName)
231 ->execute()->indexBy('name');
721c9da1
CW
232 foreach ($fields as $name => $field) {
233 $field['custom_field_id'] = $field['id'];
234 $field['name'] = $groupName . '.' . $name;
235 $field['entity'] = CustomGroupJoinable::getEntityFromExtends($field['custom_group.extends']);
236 $info[$name] = $field;
237 }
238 \Civi::cache('metadata')->set($cacheKey, $info);
c9e3994d 239 }
721c9da1 240 return isset($info[$fieldName]) ? ['suffix' => $suffix] + $info[$fieldName] : NULL;
c9e3994d
CW
241 }
242
19b53e5b
C
243 /**
244 * Check edit/delete permissions for contacts and related entities.
245 *
14a9c588 246 * @param string $baoName
247 * @param array $item
248 *
19b53e5b
C
249 * @throws \Civi\API\Exception\UnauthorizedException
250 */
251 protected function checkContactPermissions($baoName, $item) {
14a9c588 252 if ($baoName === 'CRM_Contact_BAO_Contact' && !empty($item['id'])) {
253 $permission = $this->getActionName() === 'delete' ? \CRM_Core_Permission::DELETE : \CRM_Core_Permission::EDIT;
19b53e5b
C
254 if (!\CRM_Contact_BAO_Contact_Permission::allow($item['id'], $permission)) {
255 throw new \Civi\API\Exception\UnauthorizedException('Permission denied to modify contact record');
256 }
257 }
258 else {
259 // Fixme: decouple from v3
260 require_once 'api/v3/utils.php';
2ee9afab 261 _civicrm_api3_check_edit_permissions($baoName, $item);
19b53e5b
C
262 }
263 }
264
265}