1<?php
2
3
4namespace ComboStrap\Meta\Field;
5
6
7use ComboStrap\ExceptionCompile;
8use ComboStrap\ExceptionNotFound;
9use ComboStrap\ExceptionSqliteNotAvailable;
10use ComboStrap\LogUtility;
11use ComboStrap\MarkupPath;
12use ComboStrap\Meta\Api\Metadata;
13use ComboStrap\Meta\Api\MetadataSystem;
14use ComboStrap\Meta\Store\MetadataDokuWikiStore;
15use ComboStrap\Meta\Api\MetadataTabular;
16use ComboStrap\MetaManagerForm;
17use ComboStrap\Sqlite;
18use ComboStrap\StringUtility;
19use ComboStrap\WikiPath;
20
21class Aliases extends MetadataTabular
22{
23
24    public const PROPERTY_NAME = "aliases";
25
26
27    public static function createForPage(MarkupPath $page): Aliases
28    {
29
30        return (new Aliases())->setResource($page);
31
32    }
33
34    public static function create(): Aliases
35    {
36        return new Aliases();
37    }
38
39    /**
40     * @return Alias[]|null
41     * @throws ExceptionNotFound
42     */
43    public function getValueAsAlias(): array
44    {
45        $rows = $this->getValue();
46        $aliases = [];
47        foreach ($rows as $row) {
48            /**
49             * @var AliasPath $aliasMeta
50             */
51            $aliasMeta = $row[AliasPath::getPersistentName()];
52            $alias = Alias::create($this->getResource(), $aliasMeta->getValue());
53            /**
54             * @var AliasType $aliasType
55             */
56            $aliasType = $row[AliasType::getPersistentName()] ?? null;
57            if ($aliasType !== null) {
58                try {
59                    $alias->setType($aliasType->getValue());
60                } catch (ExceptionNotFound $e) {
61                    // ok
62                }
63
64            }
65            $aliases[] = $alias;
66        }
67        return $aliases;
68    }
69
70
71    /**
72     * @param array|null $aliasesPersistentValues
73     * return Alias[]
74     */
75    private function toNativeAliasArray(?array $aliasesPersistentValues): array
76    {
77        if ($aliasesPersistentValues === null) {
78            return [];
79        }
80        $aliases = [];
81        foreach ($aliasesPersistentValues as $key => $value) {
82            if (is_array($value)) {
83                $path = $value[AliasPath::PERSISTENT_NAME];
84                if (empty($path)) {
85                    if (is_string($key)) {
86                        // Old way (deprecated)
87                        $path = $key;
88                    } else {
89                        LogUtility::msg("The path of the alias should not be empty to create a path", Alias::CANONICAL);
90                    }
91                }
92                $type = $value[AliasType::PERSISTENT_NAME];
93
94                /**
95                 * We don't create via the {@link Aliases::addAlias()}
96                 * to not persist for each each alias value
97                 **/
98                $aliases[] = Alias::create($this->getResource(), $path)
99                    ->setType($type);
100            } else {
101                $path = $value;
102                if (empty($path)) {
103                    LogUtility::msg("The value of the alias array should not be empty as it's the alias path", Alias::CANONICAL);
104                }
105                if (!is_string($path)) {
106                    $path = StringUtility::toString($path);
107                    LogUtility::error("The alias element ($path) is not a string", Alias::CANONICAL);
108                }
109                $path = WikiPath::createMarkupPathFromId($path);
110                $aliases[] = Alias::create($this->getResource(), $path);
111            }
112        }
113        return $aliases;
114    }
115
116
117    /**
118     * @param Alias[] $aliases
119     * @return null|array - the array to be saved in a text/json file
120     */
121    public static function toMetadataArray(?array $aliases): ?array
122    {
123        if ($aliases === null) {
124            return null;
125        }
126        $array = [];
127        foreach ($aliases as $alias) {
128            $array[$alias->getPath()->toAbsoluteId()] = [
129                AliasPath::PERSISTENT_NAME => $alias->getPath()->toAbsoluteId(),
130                AliasType::PERSISTENT_NAME => $alias->getType()
131            ];
132        }
133        return array_values($array);
134    }
135
136    public static function getName(): string
137    {
138        return self::PROPERTY_NAME;
139    }
140
141
142    static public function getPersistenceType(): string
143    {
144        return MetadataDokuWikiStore::PERSISTENT_DOKUWIKI_KEY;
145    }
146
147    /**
148     * Code refactoring
149     * This method is not in the database page
150     * because it would create a cycle
151     *
152     * The old data was saved in the database
153     * but should have been saved on the file system
154     *
155     * Once
156     * @return Alias[]
157     * @throws ExceptionSqliteNotAvailable
158     * @deprecated 2021-10-31
159     */
160    private
161    function getAndDeleteDeprecatedAlias(): array
162    {
163        $sqlite = Sqlite::createOrGetSqlite();
164
165        try {
166            $canonicalOrDefault = $this->getResource()->getCanonicalOrDefault();
167        } catch (ExceptionNotFound $e) {
168            return [];
169        }
170        /** @noinspection SqlResolve */
171        $request = $sqlite
172            ->createRequest()
173            ->setQueryParametrized("select ALIAS from DEPRECATED_PAGES_ALIAS where CANONICAL = ?", [$canonicalOrDefault]);
174        $deprecatedAliases = [];
175        $deprecatedAliasInDb = [];
176        try {
177            $deprecatedAliasInDb = $request
178                ->execute()
179                ->getRows();
180        } catch (ExceptionCompile $e) {
181            LogUtility::msg("An exception has occurred with the deprecated alias selection query. {$e->getMessage()}");
182            return [];
183        } finally {
184            $request->close();
185        }
186
187        array_map(
188            function ($row) use ($deprecatedAliases) {
189                $alias = $row['ALIAS'];
190                $deprecatedAliases[$alias] = Alias::create($this->getResource(), $alias)
191                    ->setType(AliasType::REDIRECT);
192            },
193            $deprecatedAliasInDb
194        );
195
196        /**
197         * Delete them
198         */
199
200        if (sizeof($deprecatedAliasInDb) > 0) {
201            /** @noinspection SqlResolve */
202            $request = $sqlite
203                ->createRequest()
204                ->setQueryParametrized("delete from DEPRECATED_PAGE_ALIASES where CANONICAL = ?", [$canonicalOrDefault]);
205            try {
206                $request->execute();
207            } catch (ExceptionCompile $e) {
208                LogUtility::msg("An exception has occurred with the delete deprecated alias statement. {$e->getMessage()}", LogUtility::LVL_MSG_ERROR);
209            } finally {
210                $request->close();
211            }
212        }
213
214
215        /**
216         * Return
217         */
218        return $deprecatedAliases;
219
220    }
221
222
223    /**
224     * @return Metadata[][] - an list of rows of metadata columns
225     */
226    public function getDefaultValue(): array
227    {
228        return
229            [
230                [
231                    AliasPath::getPersistentName() => AliasPath::createForParent($this),
232                    AliasType::getPersistentName() => AliasType::createForParent($this)->setFromStoreValueWithoutException(AliasType::DEFAULT)
233                ]
234            ];
235    }
236
237
238    public function getValue(): array
239    {
240        $this->buildCheck();
241
242        /**
243         * We don't do that on build because
244         * we are using a set a metadata method that creates
245         * a cycle via the {@link MetadataMutation::PAGE_METADATA_MUTATION_EVENT}
246         */
247        if (
248            !$this->valueIsNotNull()
249            &&
250            $this->getReadStore() !== null
251            &&
252            $this->getReadStore() instanceof MetadataDokuWikiStore
253        ) {
254            $this->getAndDeleteDeprecatedAlias();
255            /**
256             * To validate the migration we set a value
257             * (the array may be empty)
258             */
259            try {
260                $this->sendToWriteStore();
261            } catch (ExceptionCompile $e) {
262                LogUtility::msg("Error while persisting the new data");
263            }
264        }
265
266        return parent::getValue();
267    }
268
269    /**
270     * @throws ExceptionCompile
271     */
272    public
273    function addAlias(string $aliasPath, $aliasType = null): Aliases
274    {
275        $this->addAndGetAlias($aliasPath, $aliasType);
276        return $this;
277    }
278
279    /**
280     * @throws ExceptionCompile
281     */
282    public
283    function addAndGetAlias($aliasPath, $aliasType = null): Alias
284    {
285        $this->buildCheck();
286        /**
287         * @var AliasPath $path
288         */
289        $path = MetadataSystem::toMetadataObject(AliasPath::class, $this)
290            ->setFromStoreValue($aliasPath);
291        $row[$path::getPersistentName()] = $path;
292
293        $alias = Alias::create($this->getResource(), $path->getValue());
294
295        if ($aliasType !== null) {
296            $aliasObject = MetadataSystem::toMetadataObject(AliasType::class, $this)
297                ->setFromStoreValue($aliasType);
298            $row[$aliasObject::getPersistentName()] = $aliasObject;
299            $alias->setType($aliasType);
300        }
301        $this->rows[$path->getValue()->toAbsoluteId()] = $row;
302
303        return $alias;
304    }
305
306
307    static public
308    function getCanonical(): string
309    {
310        return Alias::CANONICAL;
311    }
312
313    static public
314    function getTab(): string
315    {
316        return MetaManagerForm::TAB_REDIRECTION_VALUE;
317    }
318
319    static public
320    function getDescription(): string
321    {
322        return "Aliases that will redirect to this page.";
323    }
324
325    static public function getLabel(): string
326    {
327        return "Page Aliases";
328    }
329
330
331    static public function isMutable(): bool
332    {
333        return true;
334    }
335
336    public function getUidClass(): ?string
337    {
338        return AliasPath::class;
339    }
340
341    /**
342     * @return Metadata[]
343     */
344    static public function getChildrenClass(): array
345    {
346        return [AliasPath::class, AliasType::class];
347    }
348
349
350    static public function isOnForm(): bool
351    {
352        return true;
353    }
354}
355