xref: /plugin/combo/ComboStrap/Meta/Api/MetadataTabular.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1<?php
2
3
4namespace ComboStrap\Meta\Api;
5
6
7use ComboStrap\DataType;
8use ComboStrap\ExceptionBadArgument;
9use ComboStrap\ExceptionNotFound;
10use ComboStrap\ExceptionRuntime;
11use ComboStrap\ExceptionRuntimeInternal;
12use ComboStrap\LogUtility;
13use ComboStrap\Meta\Field\Aliases;
14use ComboStrap\Path;
15use ComboStrap\References;
16
17/**
18 * Class MetadataTabular
19 * @package ComboStrap
20 * A list of row represented as a list of column
21 * ie an entity with a map that has a key
22 *
23 * The value of a tabular is an array of row (where a row is an associative array)
24 */
25abstract class MetadataTabular extends Metadata
26{
27
28    /**
29     * In the array, the identifier may be the persistent one
30     * or the identifier one
31     */
32    const PERSISTENT_NAME = "persistent";
33    const IDENTIFIER_NAME = "identifier";
34
35
36    /**
37     * A array of rows where
38     * - the key is the `identifier value`
39     * - the value is a list of column metadata
40     *     * the key is the {@link Metadata::getPersistentName()}
41     *     * the value is the metadata
42     *
43     * @var Metadata[][]
44     */
45    protected ?array $rows = null;
46
47
48    /**
49     * @return array - the rows in associate array format
50     * where the identifier value is the key
51     * @throws ExceptionNotFound
52     */
53    public function getValue(): array
54    {
55        $this->buildCheck();
56        if ($this->rows === null) {
57            throw new ExceptionNotFound("No tabular data found.");
58        }
59        return $this->rows;
60    }
61
62    public function setValue($value): Metadata
63    {
64        if ($value === null) {
65            return $this;
66        }
67        if (!is_array($value)) {
68            throw new ExceptionRuntime("The data set is not an array (The tabular data is an array of rows)");
69        }
70        $keys = array_keys($value);
71        foreach ($keys as $key) {
72            if (!is_numeric($key)) {
73                throw new ExceptionRuntime("The element of the array are not rows. The index ($key) should be numeric and is not");
74            }
75        }
76        $this->rows = $value;
77        return $this;
78
79    }
80
81
82    public function toStoreValue(): ?array
83    {
84        $this->buildCheck();
85        if ($this->rows === null) {
86            return null;
87        }
88        return $this->rowsToStore($this->rows);
89    }
90
91    /**
92     * /**
93     * A list of rows that contains a list of metadata
94     * with their value
95     * Each row has the key has value
96     * @return Metadata[][] - an array of rows
97     * @throws ExceptionNotFound
98     */
99    public abstract function getDefaultValue(): array;
100
101    public function toStoreDefaultValue(): ?array
102    {
103        try {
104            $defaultRows = $this->getDefaultValue();
105        } catch (ExceptionNotFound $e) {
106            return null;
107        }
108
109        return $this->rowsToStore($defaultRows);
110    }
111
112    /**
113     * The value is:
114     *   * a string for a unique identifier value
115     *   * an array of columns or an array of rows
116     */
117    public function setFromStoreValueWithoutException($value): Metadata
118    {
119
120        if ($value === null) {
121            return $this;
122        }
123
124        /**
125         * Value of the metadata id
126         */
127        $identifierMetadataObject = $this->getUidObject();
128        $identifierPersistentName = $identifierMetadataObject::getPersistentName();
129
130        /**
131         * Single value
132         */
133        if (is_string($value)) {
134            /**
135             * @var Metadata $identifierMetadata
136             */
137            $identifierMetadata = (new $identifierMetadataObject());
138            $identifierMetadata->setValue($value);
139            $rowId = $this->getRowId($identifierMetadata);
140            $this->rows[$rowId] = [$identifierPersistentName => $identifierMetadata];
141            return $this;
142        }
143
144        /**
145         * Array
146         */
147        if (!is_array($value)) {
148            LogUtility::msg("The tabular value is nor a string nor an array");
149            return $this;
150        }
151
152
153        /**
154         * List of columns ({@link MetadataFormDataStore form html way}
155         *
156         * For example: for {@link Aliases}, a form will have two fields
157         *  alias-path:
158         *    0: path1
159         *    1: path2
160         *  alias-type:
161         *    0: redirect
162         *    1: redirect
163         *
164         * or just a list ({@link References}
165         *   references:
166         *    0: reference-1
167         *    1: $colValue-2
168         */
169        $keys = array_keys($value);
170        $firstElement = array_shift($keys);
171        if (!is_numeric($firstElement)) {
172            /**
173             * Check which kind of key is used
174             * Resistance to the property key !
175             */
176            $identifierName = $identifierMetadataObject::getPersistentName();
177            $identifierNameType = self::PERSISTENT_NAME;
178            if (!isset($value[$identifierName])) {
179                $identifierNameType = self::IDENTIFIER_NAME;
180                $identifierName = $identifierMetadataObject::getName();
181            }
182            $identifierValues = $value[$identifierName];
183            if ($identifierValues === null || $identifierValues === "") {
184                // No data
185                return $this;
186            }
187            $index = 0;
188            if (!is_array($identifierValues)) {
189                // only one value
190                $identifierValues = [$identifierValues];
191            }
192            foreach ($identifierValues as $identifierValue) {
193                $row = [];
194                if ($identifierValue === "") {
195                    // an empty row in the table
196                    continue;
197                }
198                $row[$identifierPersistentName] = MetadataSystem::toMetadataObject($identifierMetadataObject, $this)
199                    ->setFromStoreValue($identifierValue);
200                foreach ($this->getChildrenClass() as $childClass) {
201                    if ($childClass === get_class($identifierMetadataObject)) {
202                        continue;
203                    }
204                    $metadataChildObject = MetadataSystem::toMetadataObject($childClass, $this);
205                    $name = $metadataChildObject::getPersistentName();
206                    if ($identifierNameType === self::IDENTIFIER_NAME) {
207                        $name = $metadataChildObject::getName();
208                    }
209                    $childValue = $value[$name];
210                    if (is_array($childValue)) {
211                        $childValue = $childValue[$index];
212                        $index++;
213                    }
214                    $metadataChildObject->setFromStoreValue($childValue);
215                    $row[$metadataChildObject::getPersistentName()] = $metadataChildObject;
216                }
217                $this->rows[] = $row;
218            }
219
220            return $this;
221        }
222
223        /**
224         * List of row (frontmatter, dokuwiki, ...)
225         */
226        $childClassesByPersistentName = [];
227        foreach ($this->getChildrenObject() as $childObject) {
228            $childClassesByPersistentName[$childObject::getPersistentName()] = $childObject;
229        }
230        foreach ($value as $item) {
231
232            /**
233             * By default, the single value is the identifier
234             *
235             * (Note that from {@link MetadataFormDataStore}, tabular data
236             * should be build before via the {@link self::buildFromReadStore()}
237             * as tabular data is represented as a series of column)
238             *
239             */
240            if (is_string($item)) {
241                try {
242                    $identifierMetadata = MetadataSystem::toMetadataObject($identifierMetadataObject, $this)->setFromStoreValue($item);
243                } catch (ExceptionBadArgument $e) {
244                    throw ExceptionRuntimeInternal::withMessageAndError("The $identifierMetadataObject should be known", $e);
245                }
246                $identifierValue = $this->getRowId($identifierMetadata);
247                $this->rows[$identifierValue] = [$identifierPersistentName => $identifierMetadata];
248                continue;
249            }
250            if (!is_array($item)) {
251                LogUtility::msg("The tabular value is not a string nor an array");
252            }
253            $row = [];
254            $idValue = null;
255            foreach ($item as $colName => $colValue) {
256                $childClass = $childClassesByPersistentName[$colName];
257                if ($childClass === null) {
258                    LogUtility::msg("The column ($colName) does not have a metadata definition");
259                    continue;
260                }
261                $childObject = MetadataSystem::toMetadataObject($childClass, $this);
262                $childObject->setFromStoreValueWithoutException($colValue);
263                $row[$childObject::getPersistentName()] = $childObject;
264                if ($childObject::getPersistentName() === $identifierPersistentName) {
265                    $idValue = $this->getRowId($childObject);
266                }
267            }
268            if ($idValue === null) {
269                LogUtility::msg("The value for the identifier ($identifierPersistentName) was not found");
270                continue;
271            }
272            $this->rows[$idValue] = $row;
273
274        }
275        return $this;
276    }
277
278
279    public function valueIsNotNull(): bool
280    {
281        return $this->rows !== null;
282    }
283
284    public
285    function remove(String $identifierValue): MetadataTabular
286    {
287        $this->buildCheck();
288        if ($this->rows === null) {
289            return $this;
290        }
291        unset($this->rows[$identifierValue]);
292        return $this;
293    }
294
295
296    public
297    function has($identifierValue): bool
298    {
299        $this->buildCheck();
300        if ($this->rows === null) {
301            return false;
302        }
303        return isset($this->rows[$identifierValue]);
304    }
305
306    public
307    function getSize(): int
308    {
309        $this->buildCheck();
310        if ($this->rows === null) {
311            return 0;
312        }
313        return sizeof($this->rows);
314    }
315
316    public function getRow($id): ?array
317    {
318        $this->buildCheck();
319        $normalizedValue = $this->getUidObject()
320            ->setFromStoreValueWithoutException($id)
321            ->getValue();
322        if(is_object($normalizedValue)){
323            $normalizedValue = $normalizedValue->__toString();
324        }
325        return $this->rows[$normalizedValue];
326    }
327
328    static public function getDataType(): string
329    {
330        return DataType::TABULAR_TYPE_VALUE;
331    }
332
333    /**
334     * @throws ExceptionNotFound - if the identifier or it's value was not found
335     * @var Metadata[] $metas
336     */
337    public function addRow(array $metas): MetadataTabular
338    {
339        $row = [];
340        $identifier = null;
341        foreach ($metas as $meta) {
342            $row[$meta::getPersistentName()] = $meta;
343            if (get_class($meta) === $this->getUidClass()) {
344                $identifier = $meta->getValue();
345            }
346        }
347        if ($identifier === null) {
348            throw new ExceptionNotFound("The identifier value was not found in the row");
349        }
350        $this->rows[$identifier] = $row;
351        return $this;
352    }
353
354    private function rowsToStore(array $defaultRows): array
355    {
356        $rowsArray = [];
357        ksort($defaultRows);
358        foreach ($defaultRows as $row) {
359            $rowArray = [];
360            foreach ($row as $metadata) {
361                $toStoreValue = $metadata->toStoreValue();
362                $toDefaultStoreValue = $metadata->toStoreDefaultValue();
363                if (
364                    $toStoreValue !== null
365                    && $toStoreValue !== $toDefaultStoreValue
366                ) {
367                    $rowArray[$metadata::getPersistentName()] = $toStoreValue;
368                }
369            }
370            $rowsArray[] = $rowArray;
371        }
372        return $rowsArray;
373    }
374
375    /**
376     * @param Metadata $identifierMetadata
377     * @return mixed
378     * Due to dokuwiki, the same id may be a markup or media path
379     * We build the object from the same id but the url is not the same
380     * This function takes the metadata, get the value and
381     * return the identifier suitable for an array
382     */
383    private function getRowId(Metadata $identifierMetadata)
384    {
385        try {
386            $identifierValue = $identifierMetadata->getValue(); // normalize if any
387        } catch (ExceptionNotFound $e) {
388            throw ExceptionRuntimeInternal::withMessageAndError("The meta identifier ($identifierMetadata) should have a value", $e);
389        }
390        if (DataType::isObject($identifierValue)) {
391            /**
392             * An object cannot be the key of an array
393             */
394            $identifierValue = $identifierValue->__toString();
395        }
396        return $identifierValue;
397    }
398
399}
400