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