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