Merge pull request #17950 from lcdservices/dev-core-1895
[civicrm-core.git] / Civi / Api4 / Service / Spec / FieldSpec.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 */
18
19
20 namespace Civi\Api4\Service\Spec;
21
22 use Civi\Api4\Utils\CoreUtil;
23
24 class FieldSpec {
25 /**
26 * @var mixed
27 */
28 protected $defaultValue;
29
30 /**
31 * @var string
32 */
33 protected $name;
34
35 /**
36 * @var string
37 */
38 protected $title;
39
40 /**
41 * @var string
42 */
43 protected $entity;
44
45 /**
46 * @var string
47 */
48 protected $description;
49
50 /**
51 * @var bool
52 */
53 protected $required = FALSE;
54
55 /**
56 * @var bool
57 */
58 protected $requiredIf;
59
60 /**
61 * @var array|bool
62 */
63 protected $options;
64
65 /**
66 * @var string
67 */
68 protected $dataType;
69
70 /**
71 * @var string
72 */
73 protected $inputType;
74
75 /**
76 * @var array
77 */
78 protected $inputAttrs = [];
79
80 /**
81 * @var string
82 */
83 protected $fkEntity;
84
85 /**
86 * @var int
87 */
88 protected $serialize;
89
90 /**
91 * @var string
92 */
93 protected $helpPre;
94
95 /**
96 * @var string
97 */
98 protected $helpPost;
99
100 /**
101 * @var array
102 */
103 protected $permission;
104
105 /**
106 * @var string
107 */
108 protected $columnName;
109
110 /**
111 * Aliases for the valid data types
112 *
113 * @var array
114 */
115 public static $typeAliases = [
116 'Int' => 'Integer',
117 'Link' => 'Url',
118 'Memo' => 'Text',
119 ];
120
121 /**
122 * @param string $name
123 * @param string $entity
124 * @param string $dataType
125 */
126 public function __construct($name, $entity, $dataType = 'String') {
127 $this->entity = $entity;
128 $this->name = $this->columnName = $name;
129 $this->setDataType($dataType);
130 }
131
132 /**
133 * @return mixed
134 */
135 public function getDefaultValue() {
136 return $this->defaultValue;
137 }
138
139 /**
140 * @param mixed $defaultValue
141 *
142 * @return $this
143 */
144 public function setDefaultValue($defaultValue) {
145 $this->defaultValue = $defaultValue;
146
147 return $this;
148 }
149
150 /**
151 * @return string
152 */
153 public function getName() {
154 return $this->name;
155 }
156
157 /**
158 * @param string $name
159 *
160 * @return $this
161 */
162 public function setName($name) {
163 $this->name = $name;
164
165 return $this;
166 }
167
168 /**
169 * @return string
170 */
171 public function getTitle() {
172 return $this->title;
173 }
174
175 /**
176 * @param string $title
177 *
178 * @return $this
179 */
180 public function setTitle($title) {
181 $this->title = $title;
182
183 return $this;
184 }
185
186 /**
187 * @return string
188 */
189 public function getEntity() {
190 return $this->entity;
191 }
192
193 /**
194 * @return string
195 */
196 public function getDescription() {
197 return $this->description;
198 }
199
200 /**
201 * @param string $description
202 *
203 * @return $this
204 */
205 public function setDescription($description) {
206 $this->description = $description;
207
208 return $this;
209 }
210
211 /**
212 * @return bool
213 */
214 public function isRequired() {
215 return $this->required;
216 }
217
218 /**
219 * @param bool $required
220 *
221 * @return $this
222 */
223 public function setRequired($required) {
224 $this->required = $required;
225
226 return $this;
227 }
228
229 /**
230 * @return bool
231 */
232 public function getRequiredIf() {
233 return $this->requiredIf;
234 }
235
236 /**
237 * @param bool $requiredIf
238 *
239 * @return $this
240 */
241 public function setRequiredIf($requiredIf) {
242 $this->requiredIf = $requiredIf;
243
244 return $this;
245 }
246
247 /**
248 * @return string
249 */
250 public function getDataType() {
251 return $this->dataType;
252 }
253
254 /**
255 * @param $dataType
256 *
257 * @return $this
258 * @throws \Exception
259 */
260 public function setDataType($dataType) {
261 if (array_key_exists($dataType, self::$typeAliases)) {
262 $dataType = self::$typeAliases[$dataType];
263 }
264
265 if (!in_array($dataType, $this->getValidDataTypes())) {
266 throw new \Exception(sprintf('Invalid data type "%s', $dataType));
267 }
268
269 $this->dataType = $dataType;
270
271 return $this;
272 }
273
274 /**
275 * @return int
276 */
277 public function getSerialize() {
278 return $this->serialize;
279 }
280
281 /**
282 * @param int|null $serialize
283 * @return $this
284 */
285 public function setSerialize($serialize) {
286 $this->serialize = $serialize;
287
288 return $this;
289 }
290
291 /**
292 * @param array $permission
293 * @return $this
294 */
295 public function setPermission($permission) {
296 $this->permission = $permission;
297 return $this;
298 }
299
300 /**
301 * @return array
302 */
303 public function getPermission() {
304 return $this->permission;
305 }
306
307 /**
308 * @return string
309 */
310 public function getInputType() {
311 return $this->inputType;
312 }
313
314 /**
315 * @param string $inputType
316 * @return $this
317 */
318 public function setInputType($inputType) {
319 $this->inputType = $inputType;
320
321 return $this;
322 }
323
324 /**
325 * @return array
326 */
327 public function getInputAttrs() {
328 return $this->inputAttrs;
329 }
330
331 /**
332 * @param array $inputAttrs
333 * @return $this
334 */
335 public function setInputAttrs($inputAttrs) {
336 $this->inputAttrs = $inputAttrs;
337
338 return $this;
339 }
340
341 /**
342 * @return string|NULL
343 */
344 public function getHelpPre() {
345 return $this->helpPre;
346 }
347
348 /**
349 * @param string|NULL $helpPre
350 */
351 public function setHelpPre($helpPre) {
352 $this->helpPre = is_string($helpPre) && strlen($helpPre) ? $helpPre : NULL;
353 }
354
355 /**
356 * @return string|NULL
357 */
358 public function getHelpPost() {
359 return $this->helpPost;
360 }
361
362 /**
363 * @param string|NULL $helpPost
364 */
365 public function setHelpPost($helpPost) {
366 $this->helpPost = is_string($helpPost) && strlen($helpPost) ? $helpPost : NULL;
367 }
368
369 /**
370 * Add valid types that are not not part of \CRM_Utils_Type::dataTypes
371 *
372 * @return array
373 */
374 private function getValidDataTypes() {
375 $extraTypes = ['Boolean', 'Text', 'Float', 'Url', 'Array'];
376 $extraTypes = array_combine($extraTypes, $extraTypes);
377
378 return array_merge(\CRM_Utils_Type::dataTypes(), $extraTypes);
379 }
380
381 /**
382 * @param array $values
383 * @param array|bool $return
384 * @return array
385 */
386 public function getOptions($values = [], $return = TRUE) {
387 if (!isset($this->options) || $this->options === TRUE) {
388 $fieldName = $this->getName();
389
390 if ($this instanceof CustomFieldSpec) {
391 // buildOptions relies on the custom_* type of field names
392 $fieldName = sprintf('custom_%d', $this->getCustomFieldId());
393 }
394
395 // BAO::buildOptions returns a single-dimensional list, we call that first because of the hook contract,
396 // @see CRM_Utils_Hook::fieldOptions
397 // We then supplement the data with additional properties if requested.
398 $bao = CoreUtil::getBAOFromApiName($this->getEntity());
399 $optionLabels = $bao::buildOptions($fieldName, NULL, $values);
400
401 if (!is_array($optionLabels) || !$optionLabels) {
402 $this->options = FALSE;
403 }
404 else {
405 $this->options = \CRM_Utils_Array::makeNonAssociative($optionLabels, 'id', 'label');
406 if (is_array($return)) {
407 self::addOptionProps($bao, $fieldName, $values, $return);
408 }
409 }
410 }
411 return $this->options;
412 }
413
414 /**
415 * Supplement the data from
416 *
417 * @param \CRM_Core_DAO $baoName
418 * @param string $fieldName
419 * @param array $values
420 * @param array $return
421 */
422 private function addOptionProps($baoName, $fieldName, $values, $return) {
423 // FIXME: For now, call the buildOptions function again and then combine the arrays. Not an ideal approach.
424 // TODO: Teach CRM_Core_Pseudoconstant to always load multidimensional option lists so we can get more properties like 'color' and 'icon',
425 // however that might require a change to the hook_civicrm_fieldOptions signature so that's a bit tricky.
426 if (in_array('name', $return)) {
427 $props['name'] = $baoName::buildOptions($fieldName, 'validate', $values);
428 }
429 $return = array_diff($return, ['id', 'name', 'label']);
430 // CRM_Core_Pseudoconstant doesn't know how to fetch extra stuff like icon, description, color, etc., so we have to invent that wheel here...
431 if ($return) {
432 $optionIds = implode(',', array_column($this->options, 'id'));
433 $optionIndex = array_flip(array_column($this->options, 'id'));
434 if ($this instanceof CustomFieldSpec) {
435 $optionGroupId = \CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', $this->getCustomFieldId(), 'option_group_id');
436 }
437 else {
438 $dao = new $baoName();
439 $fieldSpec = $dao->getFieldSpec($fieldName);
440 $pseudoconstant = $fieldSpec['pseudoconstant'] ?? NULL;
441 $optionGroupName = $pseudoconstant['optionGroupName'] ?? NULL;
442 $optionGroupId = $optionGroupName ? \CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', $optionGroupName, 'id', 'name') : NULL;
443 }
444 if (!empty($optionGroupId)) {
445 $extraStuff = \CRM_Core_BAO_OptionValue::getOptionValuesArray($optionGroupId);
446 $keyColumn = $pseudoconstant['keyColumn'] ?? 'value';
447 foreach ($extraStuff as $item) {
448 if (isset($optionIndex[$item[$keyColumn]])) {
449 foreach ($return as $ret) {
450 $this->options[$optionIndex[$item[$keyColumn]]][$ret] = $item[$ret] ?? NULL;
451 }
452 }
453 }
454 }
455 else {
456 // Fetch the abbr if requested using context: abbreviate
457 if (in_array('abbr', $return)) {
458 $props['abbr'] = $baoName::buildOptions($fieldName, 'abbreviate', $values);
459 $return = array_diff($return, ['abbr']);
460 }
461 // Fetch anything else (color, icon, description)
462 if ($return && !empty($pseudoconstant['table']) && \CRM_Utils_Rule::commaSeparatedIntegers($optionIds)) {
463 $sql = "SELECT * FROM {$pseudoconstant['table']} WHERE id IN (%1)";
464 $query = \CRM_Core_DAO::executeQuery($sql, [1 => [$optionIds, 'CommaSeparatedIntegers']]);
465 while ($query->fetch()) {
466 foreach ($return as $ret) {
467 if (property_exists($query, $ret)) {
468 $this->options[$optionIndex[$query->id]][$ret] = $query->$ret;
469 }
470 }
471 }
472 }
473 }
474 }
475 if (isset($props)) {
476 foreach ($this->options as &$option) {
477 foreach ($props as $name => $prop) {
478 $option[$name] = $prop[$option['id']] ?? NULL;
479 }
480 }
481 }
482 }
483
484 /**
485 * @param array|bool $options
486 *
487 * @return $this
488 */
489 public function setOptions($options) {
490 $this->options = $options;
491 return $this;
492 }
493
494 /**
495 * @return string
496 */
497 public function getFkEntity() {
498 return $this->fkEntity;
499 }
500
501 /**
502 * @param string $fkEntity
503 *
504 * @return $this
505 */
506 public function setFkEntity($fkEntity) {
507 $this->fkEntity = $fkEntity;
508
509 return $this;
510 }
511
512 /**
513 * @return string
514 */
515 public function getColumnName() {
516 return $this->columnName;
517 }
518
519 /**
520 * @param string $columnName
521 *
522 * @return $this
523 */
524 public function setColumnName($columnName) {
525 $this->columnName = $columnName;
526 return $this;
527 }
528
529 /**
530 * @param array $values
531 * @return array
532 */
533 public function toArray($values = []) {
534 $ret = [];
535 foreach (get_object_vars($this) as $key => $val) {
536 $key = strtolower(preg_replace('/(?=[A-Z])/', '_$0', $key));
537 if (!$values || in_array($key, $values)) {
538 $ret[$key] = $val;
539 }
540 }
541 return $ret;
542 }
543
544 }