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 | ||
19b53e5b C |
13 | namespace Civi\Api4\Generic\Traits; |
14 | ||
c9e3994d | 15 | use Civi\Api4\CustomField; |
19b53e5b | 16 | use Civi\Api4\Utils\FormattingUtil; |
19b53e5b C |
17 | |
18 | /** | |
19 | * @method string getLanguage() | |
14a9c588 | 20 | * @method $this setLanguage(string $language) |
19b53e5b C |
21 | */ |
22 | trait 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 | } |