1<?php
2
3
4namespace ComboStrap\Meta\Form;
5
6
7use ComboStrap\Canonical;
8use ComboStrap\DataType;
9use ComboStrap\ExceptionBadArgument;
10use ComboStrap\ExceptionNotFound;
11use ComboStrap\ExceptionRuntimeInternal;
12use ComboStrap\Html;
13use ComboStrap\LogUtility;
14use ComboStrap\Meta\Api\Metadata;
15use ComboStrap\Meta\Api\MetadataSystem;
16use ComboStrap\Meta\Api\MetadataMultiple;
17use ComboStrap\Meta\Api\MetadataTabular;
18use ComboStrap\PageDescription;
19use ComboStrap\PluginUtility;
20use ComboStrap\ResourceName;
21use ComboStrap\Web\Url;
22
23/**
24 * Class FormField
25 * @package ComboStrap
26 *
27 * A class that represents a tree of form field.
28 *
29 * Each field can be a scalar, a list or
30 * tabular by adding child fields.
31 *
32 *
33 */
34class FormMetaField
35{
36
37
38    /**
39     * The JSON attribute
40     */
41    public const TAB_ATTRIBUTE = "tab";
42    public const LABEL_ATTRIBUTE = "label";
43    public const URL_ATTRIBUTE = "url";
44    public const MUTABLE_ATTRIBUTE = "mutable";
45    /**
46     * A value may be a scalar or an array
47     */
48    public const VALUE_ATTRIBUTE = "value";
49    public const DEFAULT_VALUE_ATTRIBUTE = "default";
50
51    public const DOMAIN_VALUES_ATTRIBUTE = "domain-values";
52    public const WIDTH_ATTRIBUTE = "width";
53    public const CHILDREN_ATTRIBUTE = "children";
54    const DESCRIPTION_ATTRIBUTE = PageDescription::PROPERTY_NAME;
55    const NAME_ATTRIBUTE = ResourceName::PROPERTY_NAME;
56    const MULTIPLE_ATTRIBUTE = "multiple";
57    const TYPE_ATTRIBUTE = DataType::PROPERTY_NAME;
58
59
60    private $name;
61    /**
62     * @var bool
63     */
64    private $mutable;
65    /**
66     * @var string
67     */
68    private $tab;
69    /**
70     * @var string
71     */
72    private $label;
73    private $description;
74    /**
75     * If canonical is set, an url is also send
76     */
77    private string $canonical;
78    private array $values = [];
79    private array $defaults = [];
80    /**
81     * @var string
82     */
83    private $type;
84    /**
85     * @var array
86     */
87    private $domainValues;
88    /**
89     * @var FormMetaField[]
90     */
91    private $children;
92    /**
93     * @var int
94     */
95    private $width;
96    /**
97     * Multiple value can be chosen
98     * @var bool
99     */
100    private $multiple = false;
101
102
103    /**
104     * FormField constructor.
105     * The name is mandatory
106     * and the type to be able to control the values
107     */
108    public function __construct($name, $type)
109    {
110        $this->name = $name;
111        $this->label = ucfirst($name);
112        $this->description = $name;
113        if (!in_array($type, DataType::TYPES)) {
114            throw new ExceptionRuntimeInternal("The type ($type) is not a known field type");
115        }
116        $this->type = $type;
117        $this->mutable = true;
118    }
119
120    public static function create(string $name, string $type): FormMetaField
121    {
122        return new FormMetaField($name, $type);
123    }
124
125    /**
126     * Almost because a form does not allow hierarchical data
127     * We send an error in this case
128     * @param Metadata $metadata
129     * @return FormMetaField
130     */
131    public static function createFromMetadata(Metadata $metadata): FormMetaField
132    {
133        $field = FormMetaField::create($metadata->getName(), $metadata->getDataType());
134
135        self::setCommonDataToFieldFromMetadata($field, $metadata);
136
137        $childrenMetadata = $metadata->getChildrenClass();
138
139        try {
140            $metadata->getParent();
141        } catch (ExceptionNotFound $e) {
142            /**
143             * Only the top field have a tab value
144             */
145            $field->setTab($metadata->getTab());
146        }
147
148
149        /**
150         * No children
151         */
152        if (count($childrenMetadata) === 0) {
153
154            static::setLeafDataToFieldFromMetadata($field, $metadata);
155
156            /**
157             * When tabular, the value comes from the parent
158             */
159            if ($metadata->isScalar()) {
160                $value = $metadata->toStoreValue();
161                $defaultValue = $metadata->toStoreDefaultValue();
162                $field->addValue($value, $defaultValue);
163            }
164
165        } else {
166
167            if ($metadata instanceof MetadataTabular) {
168
169                $childFields = [];
170                foreach ($metadata->getChildrenClass() as $childMetadataClass) {
171
172                    try {
173                        $childMetadata = MetadataSystem::toMetadataObject($childMetadataClass, $metadata);
174                    } catch (ExceptionBadArgument $e) {
175                        // should happen only internally
176                        LogUtility::internalError("The metadata class/object ($childMetadataClass) is not a metadata class");
177                        continue;
178                    }
179                    $childField = FormMetaField::createFromMetadata($childMetadata);
180                    static::setCommonDataToFieldFromMetadata($childField, $childMetadata);
181                    static::setLeafDataToFieldFromMetadata($childField, $childMetadata);
182                    $field->addColumn($childField);
183                    $childFields[$childMetadata::getPersistentName()] = $childField;
184                }
185                try {
186                    $rows = $metadata->getValue();
187                } catch (ExceptionNotFound $e) {
188                    $rows = null;
189                }
190                if ($rows !== null) {
191                    $defaultRow = null;
192                    try {
193                        $defaultRows = $metadata->getDefaultValue();
194                        $defaultRow = $defaultRows[0];
195                    } catch (ExceptionNotFound $e) {
196                        // no default row
197                    }
198                    foreach ($rows as $row) {
199                        foreach ($childFields as $childName => $childField) {
200                            $colValue = $row[$childName] ?? null;
201                            if ($colValue === null) {
202                                if ($defaultRow === null) {
203                                    continue;
204                                }
205                                $colValue = $defaultRow[$childName];
206                                if ($colValue === null) {
207                                    continue;
208                                }
209                            }
210                            $storeValue = $colValue->toStoreValue();
211                            $defaultStoreValue = $colValue->toStoreDefaultValue();
212                            $childField->addValue($storeValue, $defaultStoreValue);
213                        }
214
215                    }
216
217                    // Add an extra empty row to allow adding an image
218                    if ($defaultRow !== null) {
219                        foreach ($defaultRow as $colName => $colMetadata) {
220                            if ($colName === null) {
221                                LogUtility::internalError("The persistence name (column name) should be set as key for the default rows");
222                                continue;
223                            }
224                            $defaultColValue = null;
225                            if ($colMetadata !== null) {
226                                $defaultColValue = $colMetadata->toStoreDefaultValue();
227                            }
228                            $childField = $childFields[$colName];
229                            $childField->addValue(null, $defaultColValue);
230                        }
231                    }
232                } else {
233
234
235                    // No rows, show the default rows
236                    try {
237                        $rows = $metadata->getDefaultValue();
238                    } catch (ExceptionNotFound $e) {
239                        // no default row ok
240                        $rows = [];
241                    }
242                    foreach ($rows as $row) {
243                        foreach ($row as $colName => $colValue) {
244                            if ($colValue === null) {
245                                continue;
246                            }
247                            $childField = $childFields[$colName];
248                            $childField->addValue(null, $colValue->toStoreValue());
249                        }
250                    }
251
252                }
253
254
255            } else {
256
257                LogUtility::msg("Hierarchical data is not supported in a form. Metadata ($metadata) has children and is not tabular");
258            }
259        }
260        return $field;
261
262    }
263
264
265    public
266    function toAssociativeArray(): array
267    {
268        /**
269         * Mandatory attributes
270         */
271        $associative = [
272            self::NAME_ATTRIBUTE => $this->name,
273            self::LABEL_ATTRIBUTE => $this->label,
274            self::TYPE_ATTRIBUTE => $this->type
275        ];
276
277        try {
278            $associative[self::URL_ATTRIBUTE] = $this->getUrl()->toString();
279        } catch (ExceptionNotFound $e) {
280            // ok
281        }
282
283        if ($this->description != null) {
284            $associative[self::DESCRIPTION_ATTRIBUTE] = $this->description;
285        }
286        /**
287         * For child form field (ie column), there is no tab
288         */
289        if ($this->tab != null) {
290            $associative[self::TAB_ATTRIBUTE] = $this->tab;
291        }
292
293
294        if ($this->width !== null) {
295            $associative[self::WIDTH_ATTRIBUTE] = $this->width;
296        }
297        if ($this->children !== null) {
298            foreach ($this->children as $column) {
299                $associative[self::CHILDREN_ATTRIBUTE][] = $column->toAssociativeArray();
300            }
301        } else {
302
303            /**
304             * Only valid for leaf field
305             */
306            if ($this->getValue() !== null && $this->getValue() !== "") {
307                $associative[self::VALUE_ATTRIBUTE] = $this->getValue();
308            }
309
310            if ($this->getDefaultValue() !== null) {
311                $associative[self::DEFAULT_VALUE_ATTRIBUTE] = $this->getDefaultValue();
312            }
313
314            if ($this->domainValues !== null) {
315                $associative[self::DOMAIN_VALUES_ATTRIBUTE] = $this->domainValues;
316                if ($this->multiple) {
317                    $associative[self::MULTIPLE_ATTRIBUTE] = $this->multiple;
318                }
319            }
320
321            $associative[self::MUTABLE_ATTRIBUTE] = $this->mutable;
322
323
324        }
325
326
327        return $associative;
328    }
329
330    public
331    function setMutable(bool $bool): FormMetaField
332    {
333        $this->mutable = $bool;
334        return $this;
335    }
336
337
338    public
339    function setTab(string $tabName): FormMetaField
340    {
341        Html::validNameGuard($tabName);
342        $this->tab = $tabName;
343        return $this;
344    }
345
346    public
347    function setLabel(string $label): FormMetaField
348    {
349        $this->label = $label;
350        return $this;
351    }
352
353    /**
354     * @throws ExceptionNotFound
355     */
356    public
357    function getUrl(): Url
358    {
359        if (!isset($this->canonical)) {
360            throw new ExceptionNotFound();
361        }
362
363        return Canonical::createFromValue($this->canonical)
364            ->getComboStrapUrlForDocumentation();
365
366    }
367
368    public
369    function setCanonical(string $canonical): FormMetaField
370    {
371        $this->canonical = $canonical;
372        return $this;
373    }
374
375    public
376    function setDescription(string $string): FormMetaField
377    {
378        $this->description = $string;
379        return $this;
380    }
381
382    /**
383     * @param boolean|string|null $value
384     * @param boolean|string|null $defaultValuePlaceholderOrReturned - the value set as placeholder or return value for a checked checkbox
385     * @return $this
386     */
387    public
388    function addValue($value, $defaultValuePlaceholderOrReturned = null): FormMetaField
389    {
390        if ($this->getType() === DataType::BOOLEAN_TYPE_VALUE) {
391            if ($value != null && !DataType::isBoolean($value)) {
392                throw new ExceptionRuntimeInternal("The value ($value) is not a boolean");
393            }
394            if ($defaultValuePlaceholderOrReturned != null && !DataType::isBoolean($defaultValuePlaceholderOrReturned)) {
395                throw new ExceptionRuntimeInternal("The default value ($defaultValuePlaceholderOrReturned) is not a boolean");
396            }
397        }
398        $this->values[] = $value;
399        $this->defaults[] = $defaultValuePlaceholderOrReturned;
400        return $this;
401    }
402
403
404    public
405    function setDomainValues(array $domainValues): FormMetaField
406    {
407        $this->domainValues = $domainValues;
408        return $this;
409    }
410
411    public
412    function addColumn(FormMetaField $formField): FormMetaField
413    {
414        $this->type = DataType::TABULAR_TYPE_VALUE;
415        // A parent node is not mutable
416        $this->mutable = false;
417        $this->children[] = $formField;
418        return $this;
419    }
420
421    public
422    function setWidth(int $int): FormMetaField
423    {
424        $this->width = $int;
425        return $this;
426    }
427
428    public
429    function getTab(): string
430    {
431        return $this->tab;
432    }
433
434    public
435    function getName()
436    {
437        return $this->name;
438    }
439
440    public
441    function getValue()
442    {
443        switch (sizeof($this->values)) {
444            case 0:
445                return null;
446            case 1:
447                $value = $this->values[0];
448                if ($value !== null) {
449                    return $this->values[0];
450                }
451                return null;
452            default:
453                return $this->values;
454        }
455    }
456
457    public
458    function isMutable(): bool
459    {
460        return $this->mutable;
461    }
462
463    public
464    function getChildren(): ?array
465    {
466        return $this->children;
467    }
468
469    public
470    function getType(): string
471    {
472        return $this->type;
473    }
474
475    public
476    function getDefaultValue()
477    {
478        switch (sizeof($this->defaults)) {
479            case 0:
480                return null;
481            case 1:
482                $value = $this->defaults[0];
483                if ($value !== null) {
484                    return $value;
485                }
486                return null;
487            default:
488                return $this->defaults;
489        }
490    }
491
492    public
493    function setMultiple(bool $bool): FormMetaField
494    {
495        $this->multiple = $bool;
496        return $this;
497    }
498
499    public
500    function isMultiple(): bool
501    {
502        return $this->multiple;
503    }
504
505    /**
506     * If this is a scalar value, you can set/overwrite the value
507     * with this function
508     * @param $value
509     * @param null $default
510     * @return $this
511     */
512    public
513    function setValue($value, $default = null): FormMetaField
514    {
515        $this->values = [];
516        $this->defaults = [];
517        return $this->addValue($value, $default);
518
519    }
520
521    /**
522     * Common metadata to all field from a leaf to a tabular
523     * @param FormMetaField $field
524     * @param Metadata $metadata
525     */
526    private
527    static
528    function setCommonDataToFieldFromMetadata(FormMetaField $field, Metadata $metadata)
529    {
530        $field
531            ->setCanonical($metadata->getCanonical())
532            ->setLabel($metadata->getLabel())
533            ->setDescription($metadata->getDescription());
534    }
535
536    /**
537     * @param FormMetaField $field
538     * @param Metadata $metadata
539     * Add the field metadata that are only available for leaf metadata
540     */
541    private
542    static
543    function setLeafDataToFieldFromMetadata(FormMetaField $field, Metadata $metadata)
544    {
545        $field->setMutable($metadata->isMutable());
546
547        $formControlWidth = $metadata->getFormControlWidth();
548        if ($formControlWidth !== null) {
549            $field->setWidth($formControlWidth);
550        }
551        $possibleValues = $metadata->getPossibleValues();
552        if ($possibleValues !== null) {
553            $field->setDomainValues($possibleValues);
554            if ($metadata instanceof MetadataMultiple) {
555                $field->setMultiple(true);
556            }
557        }
558
559    }
560
561    public function __toString()
562    {
563        return $this->getName();
564    }
565
566
567}
568