xref: /plugin/combo/ComboStrap/Meta/Api/MetadataTabular.php (revision 6db62c4c788b1ebdede50727f34a618605a8e494)
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
139                ->setResource($this->getResource())
140                ->setValue($value);
141            $rowId = $this->getRowId($identifierMetadata);
142            $this->rows[$rowId] = [$identifierPersistentName => $identifierMetadata];
143            return $this;
144        }
145
146        /**
147         * Array
148         */
149        if (!is_array($value)) {
150            LogUtility::msg("The tabular value is nor a string nor an array");
151            return $this;
152        }
153
154
155        /**
156         * List of columns ({@link MetadataFormDataStore form html way}
157         *
158         * For example: for {@link Aliases}, a form will have two fields
159         *  alias-path:
160         *    0: path1
161         *    1: path2
162         *  alias-type:
163         *    0: redirect
164         *    1: redirect
165         *
166         * or just a list ({@link References}
167         *   references:
168         *    0: reference-1
169         *    1: $colValue-2
170         */
171        $keys = array_keys($value);
172        $firstElement = array_shift($keys);
173        if (!is_numeric($firstElement)) {
174            /**
175             * Check which kind of key is used
176             * Resistance to the property key !
177             */
178            $identifierName = $identifierMetadataObject::getPersistentName();
179            $identifierNameType = self::PERSISTENT_NAME;
180            if (!isset($value[$identifierName])) {
181                $identifierNameType = self::IDENTIFIER_NAME;
182                $identifierName = $identifierMetadataObject::getName();
183            }
184            $identifierValues = $value[$identifierName];
185            if ($identifierValues === null || $identifierValues === "") {
186                // No data
187                return $this;
188            }
189            $index = 0;
190            if (!is_array($identifierValues)) {
191                // only one value
192                $identifierValues = [$identifierValues];
193            }
194            foreach ($identifierValues as $identifierValue) {
195                $row = [];
196                if ($identifierValue === "") {
197                    // an empty row in the table
198                    continue;
199                }
200                $row[$identifierPersistentName] = MetadataSystem::toMetadataObject($identifierMetadataObject, $this)
201                    ->setFromStoreValue($identifierValue);
202                foreach ($this->getChildrenClass() as $childClass) {
203                    if ($childClass === get_class($identifierMetadataObject)) {
204                        continue;
205                    }
206                    $metadataChildObject = MetadataSystem::toMetadataObject($childClass, $this);
207                    $name = $metadataChildObject::getPersistentName();
208                    if ($identifierNameType === self::IDENTIFIER_NAME) {
209                        $name = $metadataChildObject::getName();
210                    }
211                    $childValue = $value[$name];
212                    if (is_array($childValue)) {
213                        $childValue = $childValue[$index];
214                        $index++;
215                    }
216                    $metadataChildObject->setFromStoreValue($childValue);
217                    $row[$metadataChildObject::getPersistentName()] = $metadataChildObject;
218                }
219                $this->rows[] = $row;
220            }
221
222            return $this;
223        }
224
225        /**
226         * List of row (frontmatter, dokuwiki, ...)
227         */
228        $childClassesByPersistentName = [];
229        foreach ($this->getChildrenObject() as $childObject) {
230            $childClassesByPersistentName[$childObject::getPersistentName()] = $childObject;
231        }
232        foreach ($value as $item) {
233
234            /**
235             * By default, the single value is the identifier
236             *
237             * (Note that from {@link MetadataFormDataStore}, tabular data
238             * should be build before via the {@link self::buildFromReadStore()}
239             * as tabular data is represented as a series of column)
240             *
241             */
242            if (is_string($item)) {
243                try {
244                    $identifierMetadata = MetadataSystem::toMetadataObject($identifierMetadataObject, $this)->setFromStoreValue($item);
245                } catch (ExceptionBadArgument $e) {
246                    throw ExceptionRuntimeInternal::withMessageAndError("The $identifierMetadataObject should be known", $e);
247                }
248                $identifierValue = $this->getRowId($identifierMetadata);
249                $this->rows[$identifierValue] = [$identifierPersistentName => $identifierMetadata];
250                continue;
251            }
252            if (!is_array($item)) {
253                LogUtility::msg("The tabular value is not a string nor an array");
254            }
255            $row = [];
256            $idValue = null;
257            foreach ($item as $colName => $colValue) {
258                $childClass = $childClassesByPersistentName[$colName];
259                if ($childClass === null) {
260                    LogUtility::msg("The column ($colName) does not have a metadata definition");
261                    continue;
262                }
263                $childObject = MetadataSystem::toMetadataObject($childClass, $this);
264                $childObject->setFromStoreValueWithoutException($colValue);
265                $row[$childObject::getPersistentName()] = $childObject;
266                if ($childObject::getPersistentName() === $identifierPersistentName) {
267                    $idValue = $this->getRowId($childObject);
268                }
269            }
270            if ($idValue === null) {
271                LogUtility::msg("The value for the identifier ($identifierPersistentName) was not found");
272                continue;
273            }
274            $this->rows[$idValue] = $row;
275
276        }
277        return $this;
278    }
279
280
281    public function valueIsNotNull(): bool
282    {
283        return $this->rows !== null;
284    }
285
286    public
287    function remove(string $identifierValue): MetadataTabular
288    {
289        $this->buildCheck();
290        if ($this->rows === null) {
291            return $this;
292        }
293        unset($this->rows[$identifierValue]);
294        return $this;
295    }
296
297
298    public
299    function has($identifierValue): bool
300    {
301        $this->buildCheck();
302        if ($this->rows === null) {
303            return false;
304        }
305        return isset($this->rows[$identifierValue]);
306    }
307
308    public
309    function getSize(): int
310    {
311        $this->buildCheck();
312        if ($this->rows === null) {
313            return 0;
314        }
315        return sizeof($this->rows);
316    }
317
318    public function getRow($id): ?array
319    {
320        $this->buildCheck();
321        $normalizedValue = $this->getUidObject()
322            ->setFromStoreValueWithoutException($id)
323            ->getValue();
324        if (is_object($normalizedValue)) {
325            $normalizedValue = $normalizedValue->__toString();
326        }
327        return $this->rows[$normalizedValue] ?? null;
328    }
329
330    static public function getDataType(): string
331    {
332        return DataType::TABULAR_TYPE_VALUE;
333    }
334
335    /**
336     * @throws ExceptionNotFound - if the identifier or it's value was not found
337     * @var Metadata[] $metas
338     */
339    public function addRow(array $metas): MetadataTabular
340    {
341        $row = [];
342        $identifier = null;
343        foreach ($metas as $meta) {
344            $row[$meta::getPersistentName()] = $meta;
345            if (get_class($meta) === $this->getUidClass()) {
346                $identifier = $meta->getValue();
347            }
348        }
349        if ($identifier === null) {
350            throw new ExceptionNotFound("The identifier value was not found in the row");
351        }
352        $this->rows[$identifier] = $row;
353        return $this;
354    }
355
356    private function rowsToStore(array $defaultRows): array
357    {
358        $rowsArray = [];
359        ksort($defaultRows);
360        foreach ($defaultRows as $row) {
361            $rowArray = [];
362            foreach ($row as $metadata) {
363                $toStoreValue = $metadata->toStoreValue();
364                $toDefaultStoreValue = $metadata->toStoreDefaultValue();
365                if (
366                    $toStoreValue !== null
367                    && $toStoreValue !== $toDefaultStoreValue
368                ) {
369                    $rowArray[$metadata::getPersistentName()] = $toStoreValue;
370                }
371            }
372            $rowsArray[] = $rowArray;
373        }
374        return $rowsArray;
375    }
376
377    /**
378     * @param Metadata $identifierMetadata
379     * @return mixed
380     * Due to dokuwiki, the same id may be a markup or media path
381     * We build the object from the same id but the url is not the same
382     * This function takes the metadata, get the value and
383     * return the identifier suitable for an array
384     */
385    private function getRowId(Metadata $identifierMetadata)
386    {
387        try {
388            $identifierValue = $identifierMetadata->getValue(); // normalize if any
389        } catch (ExceptionNotFound $e) {
390            throw ExceptionRuntimeInternal::withMessageAndError("The meta identifier ($identifierMetadata) should have a value", $e);
391        }
392        if (DataType::isObject($identifierValue)) {
393            /**
394             * An object cannot be the key of an array
395             */
396            $identifierValue = $identifierValue->__toString();
397        }
398        return $identifierValue;
399    }
400
401}
402