xref: /template/strap/ComboStrap/Meta/Store/MetadataDokuWikiStore.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1*04fd306cSNickeau<?php
2*04fd306cSNickeau
3*04fd306cSNickeau
4*04fd306cSNickeaunamespace ComboStrap\Meta\Store;
5*04fd306cSNickeau
6*04fd306cSNickeauuse ComboStrap\DataType;
7*04fd306cSNickeauuse ComboStrap\ExceptionBadState;
8*04fd306cSNickeauuse ComboStrap\ExceptionCast;
9*04fd306cSNickeauuse ComboStrap\ExceptionNotExists;
10*04fd306cSNickeauuse ComboStrap\ExceptionNotFound;
11*04fd306cSNickeauuse ComboStrap\ExceptionRuntime;
12*04fd306cSNickeauuse ComboStrap\ExceptionRuntimeInternal;
13*04fd306cSNickeauuse ComboStrap\ExecutionContext;
14*04fd306cSNickeauuse ComboStrap\FetcherMarkup;
15*04fd306cSNickeauuse ComboStrap\LocalPath;
16*04fd306cSNickeauuse ComboStrap\LogUtility;
17*04fd306cSNickeauuse ComboStrap\MarkupPath;
18*04fd306cSNickeauuse ComboStrap\Meta\Api\Metadata;
19*04fd306cSNickeauuse ComboStrap\Meta\Api\MetadataStore;
20*04fd306cSNickeauuse ComboStrap\Meta\Api\MetadataStoreAbs;
21*04fd306cSNickeauuse ComboStrap\Path;
22*04fd306cSNickeauuse ComboStrap\ResourceCombo;
23*04fd306cSNickeauuse ComboStrap\WikiPath;
24*04fd306cSNickeau
25*04fd306cSNickeau/**
26*04fd306cSNickeau * Class MetadataFileSystemStore
27*04fd306cSNickeau * @package ComboStrap
28*04fd306cSNickeau *
29*04fd306cSNickeau * A wrapper around the dokuwiki meta file system store.
30*04fd306cSNickeau *
31*04fd306cSNickeau */
32*04fd306cSNickeauclass MetadataDokuWikiStore extends MetadataStoreAbs
33*04fd306cSNickeau{
34*04fd306cSNickeau
35*04fd306cSNickeau    /**
36*04fd306cSNickeau     * Current metadata / runtime metadata / calculated metadata
37*04fd306cSNickeau     * This metadata can only be set when  {@link Syntax::render() rendering}
38*04fd306cSNickeau     * The data may be deleted
39*04fd306cSNickeau     * https://www.dokuwiki.org/devel:metadata#metadata_persistence
40*04fd306cSNickeau     *
41*04fd306cSNickeau     * This is generally where the default data is located
42*04fd306cSNickeau     * if not found in the persistent
43*04fd306cSNickeau     */
44*04fd306cSNickeau    public const CURRENT_METADATA = "current";
45*04fd306cSNickeau
46*04fd306cSNickeau
47*04fd306cSNickeau    const CANONICAL = Metadata::CANONICAL;
48*04fd306cSNickeau    /**
49*04fd306cSNickeau     * Persistent metadata (data that should be in a backup)
50*04fd306cSNickeau     *
51*04fd306cSNickeau     * They are used as the default of the current metadata
52*04fd306cSNickeau     * and is never cleaned
53*04fd306cSNickeau     *
54*04fd306cSNickeau     * https://www.dokuwiki.org/devel:metadata#metadata_persistence
55*04fd306cSNickeau     *
56*04fd306cSNickeau     * Because the current is only usable in rendering, all
57*04fd306cSNickeau     * metadata are persistent inside dokuwiki
58*04fd306cSNickeau     */
59*04fd306cSNickeau    public const PERSISTENT_DOKUWIKI_KEY = "persistent";
60*04fd306cSNickeau
61*04fd306cSNickeau
62*04fd306cSNickeau    /**
63*04fd306cSNickeau     * @return MetadataDokuWikiStore
64*04fd306cSNickeau     * We don't use a global static variable
65*04fd306cSNickeau     * because we are working with php as cgi script
66*04fd306cSNickeau     * and there is no notion of request
67*04fd306cSNickeau     * to be able to flush the data on the disk
68*04fd306cSNickeau     *
69*04fd306cSNickeau     * The scope of the data will be then the store
70*04fd306cSNickeau     */
71*04fd306cSNickeau    public static function getOrCreateFromResource(ResourceCombo $resourceCombo): MetadataStore
72*04fd306cSNickeau    {
73*04fd306cSNickeau
74*04fd306cSNickeau        $context = ExecutionContext::getActualOrCreateFromEnv();
75*04fd306cSNickeau
76*04fd306cSNickeau        try {
77*04fd306cSNickeau            $executionCachedStores = &$context->getRuntimeObject(MetadataDokuWikiStore::class);
78*04fd306cSNickeau        } catch (ExceptionNotFound $e) {
79*04fd306cSNickeau            $executionCachedStores = [];
80*04fd306cSNickeau            $context->setRuntimeObject(MetadataDokuWikiStore::class, $executionCachedStores);
81*04fd306cSNickeau        }
82*04fd306cSNickeau        $path = $resourceCombo->getPathObject()->toAbsoluteId();
83*04fd306cSNickeau        if (isset($executionCachedStores[$path])) {
84*04fd306cSNickeau            return $executionCachedStores[$path];
85*04fd306cSNickeau        }
86*04fd306cSNickeau
87*04fd306cSNickeau        $metadataStore = new MetadataDokuWikiStore($resourceCombo);
88*04fd306cSNickeau        $executionCachedStores[$path] = $metadataStore;
89*04fd306cSNickeau        return $metadataStore;
90*04fd306cSNickeau
91*04fd306cSNickeau    }
92*04fd306cSNickeau
93*04fd306cSNickeau    /**
94*04fd306cSNickeau     *
95*04fd306cSNickeau     * In a rendering, you should not use the {@link p_set_metadata()}
96*04fd306cSNickeau     * but use {@link \Doku_Renderer_metadata::meta} and {@link \Doku_Renderer_metadata::$persistent}
97*04fd306cSNickeau     * to set the metadata
98*04fd306cSNickeau     *
99*04fd306cSNickeau     * Why ?
100*04fd306cSNickeau     * The metadata are set in $METADATA_RENDERERS (A global cache variable where the persistent data is set/exist
101*04fd306cSNickeau     * only during metadata rendering with the function {@link p_render_metadata()}) and then
102*04fd306cSNickeau     * saved at the end
103*04fd306cSNickeau     *
104*04fd306cSNickeau     */
105*04fd306cSNickeau    private static function isRendering(string $wikiId): bool
106*04fd306cSNickeau    {
107*04fd306cSNickeau        global $METADATA_RENDERERS;
108*04fd306cSNickeau        if (isset($METADATA_RENDERERS[$wikiId])) {
109*04fd306cSNickeau            return true;
110*04fd306cSNickeau        }
111*04fd306cSNickeau        return false;
112*04fd306cSNickeau    }
113*04fd306cSNickeau
114*04fd306cSNickeau
115*04fd306cSNickeau    /**
116*04fd306cSNickeau     * Delete the globals
117*04fd306cSNickeau     */
118*04fd306cSNickeau    public static function unsetGlobalVariables()
119*04fd306cSNickeau    {
120*04fd306cSNickeau        /**
121*04fd306cSNickeau         * {@link p_read_metadata() global cache}
122*04fd306cSNickeau         */
123*04fd306cSNickeau        unset($GLOBALS['cache_metadata']);
124*04fd306cSNickeau
125*04fd306cSNickeau        /**
126*04fd306cSNickeau         * {@link p_render_metadata()} temporary render cache
127*04fd306cSNickeau         * global $METADATA_RENDERERS;
128*04fd306cSNickeau         */
129*04fd306cSNickeau        unset($GLOBALS['METADATA_RENDERERS']);
130*04fd306cSNickeau
131*04fd306cSNickeau    }
132*04fd306cSNickeau
133*04fd306cSNickeau
134*04fd306cSNickeau    /**
135*04fd306cSNickeau     * @throws ExceptionBadState - if for any reason, it's not possible to store the data
136*04fd306cSNickeau     */
137*04fd306cSNickeau    public function set(Metadata $metadata)
138*04fd306cSNickeau    {
139*04fd306cSNickeau
140*04fd306cSNickeau        $name = $metadata->getName();
141*04fd306cSNickeau        $persistentValue = $metadata->toStoreValue();
142*04fd306cSNickeau        $defaultValue = $metadata->toStoreDefaultValue();
143*04fd306cSNickeau        $resource = $metadata->getResource();
144*04fd306cSNickeau        $this->checkResource($resource);
145*04fd306cSNickeau        if ($resource === null) {
146*04fd306cSNickeau            throw new ExceptionBadState("A resource is mandatory", self::CANONICAL);
147*04fd306cSNickeau        }
148*04fd306cSNickeau        if (!($resource instanceof MarkupPath)) {
149*04fd306cSNickeau            throw new ExceptionBadState("The DokuWiki metadata store is only for page resource", self::CANONICAL);
150*04fd306cSNickeau        }
151*04fd306cSNickeau        $this->setFromPersistentName($name, $persistentValue, $defaultValue);
152*04fd306cSNickeau    }
153*04fd306cSNickeau
154*04fd306cSNickeau    /**
155*04fd306cSNickeau     * @param Metadata $metadata
156*04fd306cSNickeau     * @param null $default
157*04fd306cSNickeau     * @return mixed|null
158*04fd306cSNickeau     */
159*04fd306cSNickeau    public function get(Metadata $metadata, $default = null)
160*04fd306cSNickeau    {
161*04fd306cSNickeau
162*04fd306cSNickeau        $resource = $metadata->getResource();
163*04fd306cSNickeau        $this->checkResource($resource);
164*04fd306cSNickeau        if ($resource === null) {
165*04fd306cSNickeau            throw new ExceptionRuntime("A resource is mandatory", self::CANONICAL);
166*04fd306cSNickeau        }
167*04fd306cSNickeau        if (!($resource instanceof MarkupPath)) {
168*04fd306cSNickeau            throw new ExceptionRuntime("The DokuWiki metadata store is only for page resource", self::CANONICAL);
169*04fd306cSNickeau        }
170*04fd306cSNickeau        return $this->getFromName($metadata->getName(), $default);
171*04fd306cSNickeau
172*04fd306cSNickeau
173*04fd306cSNickeau    }
174*04fd306cSNickeau
175*04fd306cSNickeau    /**
176*04fd306cSNickeau     *
177*04fd306cSNickeau     * Getting a metadata for a resource via its name
178*04fd306cSNickeau     * when we don't want to create a class
179*04fd306cSNickeau     *
180*04fd306cSNickeau     * This function is used primarily by derived / process metadata
181*04fd306cSNickeau     *
182*04fd306cSNickeau     * @param string $name
183*04fd306cSNickeau     * @param null $default
184*04fd306cSNickeau     * @return mixed
185*04fd306cSNickeau     */
186*04fd306cSNickeau    public function getFromName(string $name, $default = null)
187*04fd306cSNickeau    {
188*04fd306cSNickeau        /**
189*04fd306cSNickeau         * We don't use {@link p_get_metadata()}
190*04fd306cSNickeau         * because it will trigger a {@link p_render_metadata()}
191*04fd306cSNickeau         * But we may just want to check if there is a {@link PageId}
192*04fd306cSNickeau         * before rendering
193*04fd306cSNickeau         */
194*04fd306cSNickeau        $data = $this->getData();
195*04fd306cSNickeau        $value = $data[$name];
196*04fd306cSNickeau
197*04fd306cSNickeau        /**
198*04fd306cSNickeau         * Empty string return null
199*04fd306cSNickeau         * because Dokuwiki does not allow to delete keys
200*04fd306cSNickeau         * {@link p_set_metadata()}
201*04fd306cSNickeau         */
202*04fd306cSNickeau        if ($value !== null && $value !== "") {
203*04fd306cSNickeau            return $value;
204*04fd306cSNickeau        }
205*04fd306cSNickeau        return $default;
206*04fd306cSNickeau    }
207*04fd306cSNickeau
208*04fd306cSNickeau
209*04fd306cSNickeau    public function persist()
210*04fd306cSNickeau    {
211*04fd306cSNickeau
212*04fd306cSNickeau        /**
213*04fd306cSNickeau         * Done on set via the dokuwiki function
214*04fd306cSNickeau         */
215*04fd306cSNickeau
216*04fd306cSNickeau    }
217*04fd306cSNickeau
218*04fd306cSNickeau    /**
219*04fd306cSNickeau     * @param string $name
220*04fd306cSNickeau     * @param string|array $value
221*04fd306cSNickeau     * @param null $default
222*04fd306cSNickeau     * @return MetadataDokuWikiStore
223*04fd306cSNickeau     */
224*04fd306cSNickeau    public function setFromPersistentName(string $name, $value, $default = null): MetadataDokuWikiStore
225*04fd306cSNickeau    {
226*04fd306cSNickeau        $oldValue = $this->getFromName($name);
227*04fd306cSNickeau        if (is_bool($value)) {
228*04fd306cSNickeau            if ($oldValue === null) {
229*04fd306cSNickeau                $oldValue = $default;
230*04fd306cSNickeau            } else {
231*04fd306cSNickeau                $oldValue = DataType::toBoolean($oldValue);
232*04fd306cSNickeau            }
233*04fd306cSNickeau        }
234*04fd306cSNickeau        if ($oldValue !== $value) {
235*04fd306cSNickeau
236*04fd306cSNickeau            /**
237*04fd306cSNickeau             * Metadata in Dokuwiki is fucked up.
238*04fd306cSNickeau             *
239*04fd306cSNickeau             * You can't remove a metadata,
240*04fd306cSNickeau             * You need to know if this is a rendering or not
241*04fd306cSNickeau             *
242*04fd306cSNickeau             * See just how fucked {@link p_set_metadata()} is
243*04fd306cSNickeau             *
244*04fd306cSNickeau             * Also don't change the type of the value to a string
245*04fd306cSNickeau             * otherwise dokuwiki will not see a change
246*04fd306cSNickeau             * between true and a string and will not persist the value
247*04fd306cSNickeau             *
248*04fd306cSNickeau             * By default, the value is copied in the current and persistent array
249*04fd306cSNickeau             * and there is no render
250*04fd306cSNickeau             */
251*04fd306cSNickeau            $wikiId = $this->getWikiId();
252*04fd306cSNickeau            if (self::isRendering($wikiId)) {
253*04fd306cSNickeau
254*04fd306cSNickeau                /**
255*04fd306cSNickeau                 * It seems that {@link p_set_metadata()} uses it also
256*04fd306cSNickeau                 * but we show it here
257*04fd306cSNickeau                 */
258*04fd306cSNickeau                global $METADATA_RENDERERS;
259*04fd306cSNickeau                $METADATA_RENDERERS[$wikiId][self::CURRENT_METADATA][$name] = $value;
260*04fd306cSNickeau                $METADATA_RENDERERS[$wikiId][self::PERSISTENT_DOKUWIKI_KEY][$name] = $value;
261*04fd306cSNickeau
262*04fd306cSNickeau            } else {
263*04fd306cSNickeau
264*04fd306cSNickeau                p_set_metadata($wikiId,
265*04fd306cSNickeau                    [
266*04fd306cSNickeau                        $name => $value
267*04fd306cSNickeau                    ]
268*04fd306cSNickeau                );
269*04fd306cSNickeau
270*04fd306cSNickeau            }
271*04fd306cSNickeau            $this->setGlobalCacheIfAny($name, $value);
272*04fd306cSNickeau        }
273*04fd306cSNickeau        return $this;
274*04fd306cSNickeau    }
275*04fd306cSNickeau
276*04fd306cSNickeau    public function getData(): array
277*04fd306cSNickeau    {
278*04fd306cSNickeau        /**
279*04fd306cSNickeau         * We return only the current data.
280*04fd306cSNickeau         *
281*04fd306cSNickeau         * Why ?
282*04fd306cSNickeau         * To be consistent with {@link p_get_metadata()} that retrieves only from the `current` array
283*04fd306cSNickeau         * Therefore the `persistent` array values should always be duplicated in the `current` array
284*04fd306cSNickeau         *
285*04fd306cSNickeau         * (the only diff is that the persistent value are still available during a {@link p_render_metadata() metadata render})
286*04fd306cSNickeau         *
287*04fd306cSNickeau         * Note that Dokuwiki load them also for the requested path
288*04fd306cSNickeau         * at `global $INFO, $info['meta']` with {@link pageinfo()}
289*04fd306cSNickeau         * and is synced in {@link p_save_metadata()}
290*04fd306cSNickeau         *
291*04fd306cSNickeau         */
292*04fd306cSNickeau        return $this->getDataCurrentAndPersistent()[self::CURRENT_METADATA];
293*04fd306cSNickeau    }
294*04fd306cSNickeau
295*04fd306cSNickeau    private function getWikiId(): string
296*04fd306cSNickeau    {
297*04fd306cSNickeau        try {
298*04fd306cSNickeau            return $this->getResource()->getPathObject()->toWikiPath()->getWikiId();
299*04fd306cSNickeau        } catch (ExceptionCast $e) {
300*04fd306cSNickeau            throw new ExceptionRuntimeInternal("Should not happen", $e);
301*04fd306cSNickeau        }
302*04fd306cSNickeau    }
303*04fd306cSNickeau
304*04fd306cSNickeau
305*04fd306cSNickeau    /**
306*04fd306cSNickeau     *
307*04fd306cSNickeau     * @param $name
308*04fd306cSNickeau     * @return mixed|null
309*04fd306cSNickeau     * @deprecated - the data should always be replicated in current use {@link self::getFromName()}
310*04fd306cSNickeau     */
311*04fd306cSNickeau    public
312*04fd306cSNickeau    function getCurrentFromName($name)
313*04fd306cSNickeau    {
314*04fd306cSNickeau        return $this->getFromName($name);
315*04fd306cSNickeau    }
316*04fd306cSNickeau
317*04fd306cSNickeau    /**
318*04fd306cSNickeau     * @return MetadataDokuWikiStore
319*04fd306cSNickeau     * @deprecated should use a fetcher markup ?
320*04fd306cSNickeau     */
321*04fd306cSNickeau    public function renderAndPersist(): MetadataDokuWikiStore
322*04fd306cSNickeau    {
323*04fd306cSNickeau        /**
324*04fd306cSNickeau         * Read/render the metadata from the file
325*04fd306cSNickeau         * with parsing
326*04fd306cSNickeau         */
327*04fd306cSNickeau        $wikiPage = $this->getResource();
328*04fd306cSNickeau        if (!$wikiPage instanceof WikiPath) {
329*04fd306cSNickeau            LogUtility::errorIfDevOrTest("The resource is not a wiki path");
330*04fd306cSNickeau            return $this;
331*04fd306cSNickeau        }
332*04fd306cSNickeau        try {
333*04fd306cSNickeau            FetcherMarkup::confRoot()
334*04fd306cSNickeau                ->setRequestedExecutingPath($wikiPage)
335*04fd306cSNickeau                ->setRequestedContextPath($wikiPage)
336*04fd306cSNickeau                ->setRequestedMimeToMetadata()
337*04fd306cSNickeau                ->build()
338*04fd306cSNickeau                ->processMetadataIfNotYetDone();
339*04fd306cSNickeau        } catch (ExceptionNotExists $e) {
340*04fd306cSNickeau            LogUtility::error("Metadata Build Error", self::CANONICAL, $e);
341*04fd306cSNickeau        }
342*04fd306cSNickeau
343*04fd306cSNickeau        return $this;
344*04fd306cSNickeau    }
345*04fd306cSNickeau
346*04fd306cSNickeau
347*04fd306cSNickeau    public function isHierarchicalTextBased(): bool
348*04fd306cSNickeau    {
349*04fd306cSNickeau        return true;
350*04fd306cSNickeau    }
351*04fd306cSNickeau
352*04fd306cSNickeau    /**
353*04fd306cSNickeau     * @return Path - the full path to the meta file
354*04fd306cSNickeau     */
355*04fd306cSNickeau    public
356*04fd306cSNickeau    function getMetaFilePath(): ?Path
357*04fd306cSNickeau    {
358*04fd306cSNickeau        $dokuwikiId = $this->getWikiId();
359*04fd306cSNickeau        return LocalPath::createFromPathString(metaFN($dokuwikiId, '.meta'));
360*04fd306cSNickeau    }
361*04fd306cSNickeau
362*04fd306cSNickeau    public function __toString()
363*04fd306cSNickeau    {
364*04fd306cSNickeau        return "DokuMeta ({$this->getWikiId()}";
365*04fd306cSNickeau    }
366*04fd306cSNickeau
367*04fd306cSNickeau
368*04fd306cSNickeau    public function deleteAndFlush()
369*04fd306cSNickeau    {
370*04fd306cSNickeau        $emptyMeta = [MetadataDokuWikiStore::CURRENT_METADATA => [], self::PERSISTENT_DOKUWIKI_KEY => []];
371*04fd306cSNickeau        $dokuwikiId = $this->getWikiId();
372*04fd306cSNickeau        p_save_metadata($dokuwikiId, $emptyMeta);
373*04fd306cSNickeau    }
374*04fd306cSNickeau
375*04fd306cSNickeau
376*04fd306cSNickeau    public function reset()
377*04fd306cSNickeau    {
378*04fd306cSNickeau        self::unsetGlobalVariables();
379*04fd306cSNickeau    }
380*04fd306cSNickeau
381*04fd306cSNickeau    /**
382*04fd306cSNickeau     * In {@link p_read_metadata()}, there is a global cache
383*04fd306cSNickeau     * @param string $name
384*04fd306cSNickeau     * @param mixed $value
385*04fd306cSNickeau     */
386*04fd306cSNickeau    private function setGlobalCacheIfAny(string $name, $value)
387*04fd306cSNickeau    {
388*04fd306cSNickeau        global $cache_metadata;
389*04fd306cSNickeau
390*04fd306cSNickeau        $id = $this->getWikiId();
391*04fd306cSNickeau        if (isset($cache_metadata[$id])) {
392*04fd306cSNickeau            $cache_metadata[$id]['persistent'][$name] = $value;
393*04fd306cSNickeau            $cache_metadata[$id]['current'][$name] = $value;
394*04fd306cSNickeau        }
395*04fd306cSNickeau
396*04fd306cSNickeau    }
397*04fd306cSNickeau
398*04fd306cSNickeau    /**
399*04fd306cSNickeau     * @return array -the full array only needed by the rendering process
400*04fd306cSNickeau     * You should use {@link self::getData()} otherwise
401*04fd306cSNickeau     */
402*04fd306cSNickeau    public function getDataCurrentAndPersistent(): array
403*04fd306cSNickeau    {
404*04fd306cSNickeau
405*04fd306cSNickeau        $id = $this->getWikiId();
406*04fd306cSNickeau        $data = p_read_metadata($id, true);
407*04fd306cSNickeau        if (empty($data)) {
408*04fd306cSNickeau            LogUtility::internalError("The metadata cache was empty");
409*04fd306cSNickeau            $data = p_read_metadata($id);
410*04fd306cSNickeau        }
411*04fd306cSNickeau        return $data;
412*04fd306cSNickeau    }
413*04fd306cSNickeau}
414