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