1<?php
2
3
4namespace ComboStrap\Meta\Form;
5
6
7use ComboStrap\DataType;
8use ComboStrap\ExceptionRuntime;
9use ComboStrap\Meta\Form\FormMetaField;
10use ComboStrap\Meta\Form\FormMetaTab;
11use ComboStrap\Html;
12use ComboStrap\LogUtility;
13use ComboStrap\Meta\Api\Metadata;
14use ComboStrap\Meta\Api\MetadataStore;
15
16/**
17 * Class FormMeta
18 * @package ComboStrap
19 *
20 * Represents form metadata sends via an ajax request
21 *
22 * It makes sure that the data send
23 * is coherent
24 */
25class FormMeta
26{
27
28    const FORM_TYPES = [self::FORM_NAV_TABS_TYPE, self::FORM_LIST_GROUP_TYPE];
29    const FORM_NAV_TABS_TYPE = "nav-tabs";
30    const FORM_LIST_GROUP_TYPE = "list-group";
31    const FIELDS_ATTRIBUTE = "fields";
32    const TABS_ATTRIBUTE = "tabs";
33
34    private $name;
35
36    /**
37     * @var FormMetaField[]
38     */
39    private $fields;
40    /**
41     * @var FormMetaTab[]|string[]
42     */
43    private $tabs;
44    /**
45     * @var string
46     */
47    private $type;
48
49    /**
50     * @throws ExceptionRuntime
51     */
52    public function __construct($name)
53    {
54        Html::validNameGuard($name);
55        $this->name = $name;
56    }
57
58
59    public static function create($name): FormMeta
60    {
61        return new FormMeta($name);
62    }
63
64    public function addField(FormMetaField $formField): FormMeta
65    {
66        $this->fields[] = $formField;
67        $tab = $formField->getTab();
68        if (!empty($tab) && !isset($this->tabs[$tab])) {
69            $this->tabs[$tab] = $tab;
70        }
71        return $this;
72    }
73
74    public function addTab(FormMetaTab $tab): FormMeta
75    {
76        $this->tabs[$tab->getName()] = $tab;
77        return $this;
78    }
79
80    public function toAssociativeArray(): array
81    {
82
83        $fieldsArray = [];
84        foreach ($this->fields as $element) {
85            /**
86             * The order is kept even if we add a key
87             */
88            $fieldsArray[$element->getName()] = $element->toAssociativeArray();
89        }
90        $tabs = [];
91        foreach ($this->tabs as $element) {
92            if (!($element instanceof FormMetaTab)) {
93                $tab = FormMetaTab::create($element);
94            } else {
95                $tab = $element;
96            }
97            /**
98             * The order is kept even if we add a key
99             */
100            $tabs[$tab->getName()] = $tab->toAssociativeArray();
101        }
102        return [
103            self::FIELDS_ATTRIBUTE => $fieldsArray,
104            self::TABS_ATTRIBUTE => $tabs
105        ];
106    }
107
108    /**
109     * ie nav-tabs versus list-group:
110     * https://getbootstrap.com/docs/5.0/components/list-group/#javascript-behavior
111     *
112     * @param string $type
113     * @return FormMeta
114     */
115    public function setType(string $type): FormMeta
116    {
117        if (!in_array($type, self::FORM_TYPES)) {
118            LogUtility::msg("The form type ($type) is unknown");
119            return $this;
120        }
121        $this->type = $type;
122        return $this;
123    }
124
125    /**
126     * The data as if it was send by a HTML form to the
127     * post endpoint
128     *
129     * It transforms the fields to an associative array
130     * that should be send with a post request
131     *
132     * ie Equivalent to the javascript api formdata output
133     * (Used in test to simulate a post)
134     *
135     * TODO: Not sure but with the {@link MetadataStore}, this should move to the class
136     *   where the target/read store is a {@link MetadataFormDataStore} ?
137     */
138    public function toFormData(): array
139    {
140        $data = [];
141        $this->toFormDataRecurse($data, $this->fields);
142        return $data;
143    }
144
145    /**
146     * @param $data
147     * @param FormMetaField[] $fields
148     */
149    private function toFormDataRecurse(&$data, array $fields)
150    {
151
152        foreach ($fields as $field) {
153
154            if ($field->isMutable()) {
155
156                $value = $field->getValue();
157                if ($field->getType() === DataType::BOOLEAN_TYPE_VALUE) {
158                    if ($value === $field->getDefaultValue() || $value === null) {
159                        continue;
160                    }
161                }
162                if ($value === null) {
163                    // A form would return empty string
164                    $value = "";
165                }
166                if (is_array($value)) {
167                    $temp = [];
168                    foreach ($value as $subValue) {
169                        if ($subValue === null) {
170                            $temp[] = "";
171                        } else {
172                            if (is_array($subValue) && $field->isMultiple()) {
173                                $temp[] = implode(",", $subValue);
174                            } else {
175                                $temp[] = $subValue;
176                            }
177                        }
178                    }
179                    $value = $temp;
180                }
181                $data[$field->getName()] = $value;
182
183            }
184            $formMetaChildren = $field->getChildren();
185            if ($formMetaChildren != null) {
186                $this->toFormDataRecurse($data, $formMetaChildren);
187            }
188        }
189
190    }
191
192    /**
193     * @param Metadata $metadata
194     * @return FormMeta
195     */
196    public function addFormFieldFromMetadata(Metadata $metadata): FormMeta
197    {
198        $field = FormMetaField::createFromMetadata($metadata);
199        $this->addField($field);
200        return $this;
201    }
202
203
204}
205