xref: /template/strap/ComboStrap/Meta/Store/MetadataDokuWikiStore.php (revision cc61058412fea34c761a0cc9a2fd45c3b979220b)
104fd306cSNickeau<?php
204fd306cSNickeau
304fd306cSNickeau
404fd306cSNickeaunamespace ComboStrap\Meta\Store;
504fd306cSNickeau
604fd306cSNickeauuse ComboStrap\DataType;
704fd306cSNickeauuse ComboStrap\ExceptionBadState;
804fd306cSNickeauuse ComboStrap\ExceptionCast;
904fd306cSNickeauuse ComboStrap\ExceptionNotExists;
1004fd306cSNickeauuse ComboStrap\ExceptionNotFound;
1104fd306cSNickeauuse ComboStrap\ExceptionRuntime;
1204fd306cSNickeauuse ComboStrap\ExceptionRuntimeInternal;
1304fd306cSNickeauuse ComboStrap\ExecutionContext;
1404fd306cSNickeauuse ComboStrap\FetcherMarkup;
1504fd306cSNickeauuse ComboStrap\LocalPath;
1604fd306cSNickeauuse ComboStrap\LogUtility;
1704fd306cSNickeauuse ComboStrap\MarkupPath;
1804fd306cSNickeauuse ComboStrap\Meta\Api\Metadata;
1904fd306cSNickeauuse ComboStrap\Meta\Api\MetadataStore;
2004fd306cSNickeauuse ComboStrap\Meta\Api\MetadataStoreAbs;
2104fd306cSNickeauuse ComboStrap\Path;
2204fd306cSNickeauuse ComboStrap\ResourceCombo;
2304fd306cSNickeauuse ComboStrap\WikiPath;
2404fd306cSNickeau
2504fd306cSNickeau/**
2604fd306cSNickeau * Class MetadataFileSystemStore
2704fd306cSNickeau * @package ComboStrap
2804fd306cSNickeau *
2904fd306cSNickeau * A wrapper around the dokuwiki meta file system store.
3004fd306cSNickeau *
3104fd306cSNickeau */
3204fd306cSNickeauclass MetadataDokuWikiStore extends MetadataStoreAbs
3304fd306cSNickeau{
3404fd306cSNickeau
3504fd306cSNickeau    /**
3604fd306cSNickeau     * Current metadata / runtime metadata / calculated metadata
3704fd306cSNickeau     * This metadata can only be set when  {@link Syntax::render() rendering}
3804fd306cSNickeau     * The data may be deleted
3904fd306cSNickeau     * https://www.dokuwiki.org/devel:metadata#metadata_persistence
4004fd306cSNickeau     *
4104fd306cSNickeau     * This is generally where the default data is located
4204fd306cSNickeau     * if not found in the persistent
4304fd306cSNickeau     */
4404fd306cSNickeau    public const CURRENT_METADATA = "current";
4504fd306cSNickeau
4604fd306cSNickeau
4704fd306cSNickeau    const CANONICAL = Metadata::CANONICAL;
4804fd306cSNickeau    /**
4904fd306cSNickeau     * Persistent metadata (data that should be in a backup)
5004fd306cSNickeau     *
5104fd306cSNickeau     * They are used as the default of the current metadata
5204fd306cSNickeau     * and is never cleaned
5304fd306cSNickeau     *
5404fd306cSNickeau     * https://www.dokuwiki.org/devel:metadata#metadata_persistence
5504fd306cSNickeau     *
5604fd306cSNickeau     * Because the current is only usable in rendering, all
5704fd306cSNickeau     * metadata are persistent inside dokuwiki
5804fd306cSNickeau     */
5904fd306cSNickeau    public const PERSISTENT_DOKUWIKI_KEY = "persistent";
6004fd306cSNickeau
6104fd306cSNickeau
6204fd306cSNickeau    /**
6304fd306cSNickeau     * @return MetadataDokuWikiStore
6404fd306cSNickeau     * We don't use a global static variable
6504fd306cSNickeau     * because we are working with php as cgi script
6604fd306cSNickeau     * and there is no notion of request
6704fd306cSNickeau     * to be able to flush the data on the disk
6804fd306cSNickeau     *
6904fd306cSNickeau     * The scope of the data will be then the store
7004fd306cSNickeau     */
7104fd306cSNickeau    public static function getOrCreateFromResource(ResourceCombo $resourceCombo): MetadataStore
7204fd306cSNickeau    {
7304fd306cSNickeau
7404fd306cSNickeau        $context = ExecutionContext::getActualOrCreateFromEnv();
7504fd306cSNickeau
7604fd306cSNickeau        try {
7704fd306cSNickeau            $executionCachedStores = &$context->getRuntimeObject(MetadataDokuWikiStore::class);
7804fd306cSNickeau        } catch (ExceptionNotFound $e) {
7904fd306cSNickeau            $executionCachedStores = [];
8004fd306cSNickeau            $context->setRuntimeObject(MetadataDokuWikiStore::class, $executionCachedStores);
8104fd306cSNickeau        }
8204fd306cSNickeau        $path = $resourceCombo->getPathObject()->toAbsoluteId();
8304fd306cSNickeau        if (isset($executionCachedStores[$path])) {
8404fd306cSNickeau            return $executionCachedStores[$path];
8504fd306cSNickeau        }
8604fd306cSNickeau
8704fd306cSNickeau        $metadataStore = new MetadataDokuWikiStore($resourceCombo);
8804fd306cSNickeau        $executionCachedStores[$path] = $metadataStore;
8904fd306cSNickeau        return $metadataStore;
9004fd306cSNickeau
9104fd306cSNickeau    }
9204fd306cSNickeau
9304fd306cSNickeau    /**
9404fd306cSNickeau     *
9504fd306cSNickeau     * In a rendering, you should not use the {@link p_set_metadata()}
9604fd306cSNickeau     * but use {@link \Doku_Renderer_metadata::meta} and {@link \Doku_Renderer_metadata::$persistent}
9704fd306cSNickeau     * to set the metadata
9804fd306cSNickeau     *
9904fd306cSNickeau     * Why ?
10004fd306cSNickeau     * The metadata are set in $METADATA_RENDERERS (A global cache variable where the persistent data is set/exist
10104fd306cSNickeau     * only during metadata rendering with the function {@link p_render_metadata()}) and then
10204fd306cSNickeau     * saved at the end
10304fd306cSNickeau     *
10404fd306cSNickeau     */
10504fd306cSNickeau    private static function isRendering(string $wikiId): bool
10604fd306cSNickeau    {
10704fd306cSNickeau        global $METADATA_RENDERERS;
10804fd306cSNickeau        if (isset($METADATA_RENDERERS[$wikiId])) {
10904fd306cSNickeau            return true;
11004fd306cSNickeau        }
11104fd306cSNickeau        return false;
11204fd306cSNickeau    }
11304fd306cSNickeau
11404fd306cSNickeau
11504fd306cSNickeau    /**
11604fd306cSNickeau     * Delete the globals
11704fd306cSNickeau     */
11804fd306cSNickeau    public static function unsetGlobalVariables()
11904fd306cSNickeau    {
12004fd306cSNickeau        /**
12104fd306cSNickeau         * {@link p_read_metadata() global cache}
12204fd306cSNickeau         */
12304fd306cSNickeau        unset($GLOBALS['cache_metadata']);
12404fd306cSNickeau
12504fd306cSNickeau        /**
12604fd306cSNickeau         * {@link p_render_metadata()} temporary render cache
12704fd306cSNickeau         * global $METADATA_RENDERERS;
12804fd306cSNickeau         */
12904fd306cSNickeau        unset($GLOBALS['METADATA_RENDERERS']);
13004fd306cSNickeau
13104fd306cSNickeau    }
13204fd306cSNickeau
13304fd306cSNickeau
13404fd306cSNickeau    /**
13504fd306cSNickeau     * @throws ExceptionBadState - if for any reason, it's not possible to store the data
13604fd306cSNickeau     */
13704fd306cSNickeau    public function set(Metadata $metadata)
13804fd306cSNickeau    {
13904fd306cSNickeau
14004fd306cSNickeau        $name = $metadata->getName();
14104fd306cSNickeau        $persistentValue = $metadata->toStoreValue();
14204fd306cSNickeau        $defaultValue = $metadata->toStoreDefaultValue();
14304fd306cSNickeau        $resource = $metadata->getResource();
14404fd306cSNickeau        $this->checkResource($resource);
14504fd306cSNickeau        if ($resource === null) {
14604fd306cSNickeau            throw new ExceptionBadState("A resource is mandatory", self::CANONICAL);
14704fd306cSNickeau        }
14804fd306cSNickeau        if (!($resource instanceof MarkupPath)) {
14904fd306cSNickeau            throw new ExceptionBadState("The DokuWiki metadata store is only for page resource", self::CANONICAL);
15004fd306cSNickeau        }
15104fd306cSNickeau        $this->setFromPersistentName($name, $persistentValue, $defaultValue);
15204fd306cSNickeau    }
15304fd306cSNickeau
15404fd306cSNickeau    /**
15504fd306cSNickeau     * @param Metadata $metadata
15604fd306cSNickeau     * @param null $default
15704fd306cSNickeau     * @return mixed|null
15804fd306cSNickeau     */
15904fd306cSNickeau    public function get(Metadata $metadata, $default = null)
16004fd306cSNickeau    {
16104fd306cSNickeau
16204fd306cSNickeau        $resource = $metadata->getResource();
16304fd306cSNickeau        $this->checkResource($resource);
16404fd306cSNickeau        if ($resource === null) {
16504fd306cSNickeau            throw new ExceptionRuntime("A resource is mandatory", self::CANONICAL);
16604fd306cSNickeau        }
16704fd306cSNickeau        if (!($resource instanceof MarkupPath)) {
16804fd306cSNickeau            throw new ExceptionRuntime("The DokuWiki metadata store is only for page resource", self::CANONICAL);
16904fd306cSNickeau        }
17004fd306cSNickeau        return $this->getFromName($metadata->getName(), $default);
17104fd306cSNickeau
17204fd306cSNickeau
17304fd306cSNickeau    }
17404fd306cSNickeau
17504fd306cSNickeau    /**
17604fd306cSNickeau     *
17704fd306cSNickeau     * Getting a metadata for a resource via its name
17804fd306cSNickeau     * when we don't want to create a class
17904fd306cSNickeau     *
18004fd306cSNickeau     * This function is used primarily by derived / process metadata
18104fd306cSNickeau     *
18204fd306cSNickeau     * @param string $name
18304fd306cSNickeau     * @param null $default
18404fd306cSNickeau     * @return mixed
18504fd306cSNickeau     */
18604fd306cSNickeau    public function getFromName(string $name, $default = null)
18704fd306cSNickeau    {
18804fd306cSNickeau        /**
18904fd306cSNickeau         * We don't use {@link p_get_metadata()}
19004fd306cSNickeau         * because it will trigger a {@link p_render_metadata()}
19104fd306cSNickeau         * But we may just want to check if there is a {@link PageId}
19204fd306cSNickeau         * before rendering
19304fd306cSNickeau         */
19404fd306cSNickeau        $data = $this->getData();
1953e169323Sgerardnico        $value = $data[$name] ?? null;
19604fd306cSNickeau
19704fd306cSNickeau        /**
19804fd306cSNickeau         * Empty string return null
19904fd306cSNickeau         * because Dokuwiki does not allow to delete keys
20004fd306cSNickeau         * {@link p_set_metadata()}
20104fd306cSNickeau         */
20204fd306cSNickeau        if ($value !== null && $value !== "") {
20304fd306cSNickeau            return $value;
20404fd306cSNickeau        }
20504fd306cSNickeau        return $default;
20604fd306cSNickeau    }
20704fd306cSNickeau
20804fd306cSNickeau
20904fd306cSNickeau    public function persist()
21004fd306cSNickeau    {
21104fd306cSNickeau
21204fd306cSNickeau        /**
21304fd306cSNickeau         * Done on set via the dokuwiki function
21404fd306cSNickeau         */
21504fd306cSNickeau
21604fd306cSNickeau    }
21704fd306cSNickeau
21804fd306cSNickeau    /**
21904fd306cSNickeau     * @param string $name
22004fd306cSNickeau     * @param string|array $value
22104fd306cSNickeau     * @param null $default
22204fd306cSNickeau     * @return MetadataDokuWikiStore
22304fd306cSNickeau     */
22404fd306cSNickeau    public function setFromPersistentName(string $name, $value, $default = null): MetadataDokuWikiStore
22504fd306cSNickeau    {
22604fd306cSNickeau        $oldValue = $this->getFromName($name);
227*cc610584Sgerardnico        if (is_bool($value) && $oldValue !== null) {
22804fd306cSNickeau            $oldValue = DataType::toBoolean($oldValue);
22904fd306cSNickeau        }
23004fd306cSNickeau        if ($oldValue !== $value) {
23104fd306cSNickeau
23204fd306cSNickeau            /**
23304fd306cSNickeau             * Metadata in Dokuwiki is fucked up.
23404fd306cSNickeau             *
23504fd306cSNickeau             * You can't remove a metadata,
23604fd306cSNickeau             * You need to know if this is a rendering or not
23704fd306cSNickeau             *
23804fd306cSNickeau             * See just how fucked {@link p_set_metadata()} is
23904fd306cSNickeau             *
24004fd306cSNickeau             * Also don't change the type of the value to a string
24104fd306cSNickeau             * otherwise dokuwiki will not see a change
24204fd306cSNickeau             * between true and a string and will not persist the value
24304fd306cSNickeau             *
24404fd306cSNickeau             * By default, the value is copied in the current and persistent array
24504fd306cSNickeau             * and there is no render
24604fd306cSNickeau             */
24704fd306cSNickeau            $wikiId = $this->getWikiId();
24804fd306cSNickeau            if (self::isRendering($wikiId)) {
24904fd306cSNickeau
25004fd306cSNickeau                /**
25104fd306cSNickeau                 * It seems that {@link p_set_metadata()} uses it also
25204fd306cSNickeau                 * but we show it here
25304fd306cSNickeau                 */
25404fd306cSNickeau                global $METADATA_RENDERERS;
25504fd306cSNickeau                $METADATA_RENDERERS[$wikiId][self::CURRENT_METADATA][$name] = $value;
25604fd306cSNickeau                $METADATA_RENDERERS[$wikiId][self::PERSISTENT_DOKUWIKI_KEY][$name] = $value;
25704fd306cSNickeau
25804fd306cSNickeau            } else {
25904fd306cSNickeau
26004fd306cSNickeau                p_set_metadata($wikiId,
26104fd306cSNickeau                    [
26204fd306cSNickeau                        $name => $value
26304fd306cSNickeau                    ]
26404fd306cSNickeau                );
26504fd306cSNickeau
26604fd306cSNickeau            }
26704fd306cSNickeau            $this->setGlobalCacheIfAny($name, $value);
26804fd306cSNickeau        }
26904fd306cSNickeau        return $this;
27004fd306cSNickeau    }
27104fd306cSNickeau
27204fd306cSNickeau    public function getData(): array
27304fd306cSNickeau    {
27404fd306cSNickeau        /**
27504fd306cSNickeau         * We return only the current data.
27604fd306cSNickeau         *
27704fd306cSNickeau         * Why ?
27804fd306cSNickeau         * To be consistent with {@link p_get_metadata()} that retrieves only from the `current` array
27904fd306cSNickeau         * Therefore the `persistent` array values should always be duplicated in the `current` array
28004fd306cSNickeau         *
28104fd306cSNickeau         * (the only diff is that the persistent value are still available during a {@link p_render_metadata() metadata render})
28204fd306cSNickeau         *
28304fd306cSNickeau         * Note that Dokuwiki load them also for the requested path
28404fd306cSNickeau         * at `global $INFO, $info['meta']` with {@link pageinfo()}
28504fd306cSNickeau         * and is synced in {@link p_save_metadata()}
28604fd306cSNickeau         *
28704fd306cSNickeau         */
28804fd306cSNickeau        return $this->getDataCurrentAndPersistent()[self::CURRENT_METADATA];
289*cc610584Sgerardnico
29004fd306cSNickeau    }
29104fd306cSNickeau
29204fd306cSNickeau    private function getWikiId(): string
29304fd306cSNickeau    {
29404fd306cSNickeau        try {
29504fd306cSNickeau            return $this->getResource()->getPathObject()->toWikiPath()->getWikiId();
29604fd306cSNickeau        } catch (ExceptionCast $e) {
29704fd306cSNickeau            throw new ExceptionRuntimeInternal("Should not happen", $e);
29804fd306cSNickeau        }
29904fd306cSNickeau    }
30004fd306cSNickeau
30104fd306cSNickeau
30204fd306cSNickeau    /**
30304fd306cSNickeau     *
30404fd306cSNickeau     * @param $name
30504fd306cSNickeau     * @return mixed|null
30604fd306cSNickeau     * @deprecated - the data should always be replicated in current use {@link self::getFromName()}
30704fd306cSNickeau     */
30804fd306cSNickeau    public
30904fd306cSNickeau    function getCurrentFromName($name)
31004fd306cSNickeau    {
31104fd306cSNickeau        return $this->getFromName($name);
31204fd306cSNickeau    }
31304fd306cSNickeau
31404fd306cSNickeau    /**
31504fd306cSNickeau     * @return MetadataDokuWikiStore
31604fd306cSNickeau     * @deprecated should use a fetcher markup ?
31704fd306cSNickeau     */
31804fd306cSNickeau    public function renderAndPersist(): MetadataDokuWikiStore
31904fd306cSNickeau    {
32004fd306cSNickeau        /**
32104fd306cSNickeau         * Read/render the metadata from the file
32204fd306cSNickeau         * with parsing
32304fd306cSNickeau         */
32404fd306cSNickeau        $wikiPage = $this->getResource();
32504fd306cSNickeau        if (!$wikiPage instanceof WikiPath) {
32604fd306cSNickeau            LogUtility::errorIfDevOrTest("The resource is not a wiki path");
32704fd306cSNickeau            return $this;
32804fd306cSNickeau        }
32904fd306cSNickeau        try {
33004fd306cSNickeau            FetcherMarkup::confRoot()
33104fd306cSNickeau                ->setRequestedExecutingPath($wikiPage)
33204fd306cSNickeau                ->setRequestedContextPath($wikiPage)
33304fd306cSNickeau                ->setRequestedMimeToMetadata()
33404fd306cSNickeau                ->build()
33504fd306cSNickeau                ->processMetadataIfNotYetDone();
33604fd306cSNickeau        } catch (ExceptionNotExists $e) {
33704fd306cSNickeau            LogUtility::error("Metadata Build Error", self::CANONICAL, $e);
33804fd306cSNickeau        }
33904fd306cSNickeau
34004fd306cSNickeau        return $this;
34104fd306cSNickeau    }
34204fd306cSNickeau
34304fd306cSNickeau
34404fd306cSNickeau    public function isHierarchicalTextBased(): bool
34504fd306cSNickeau    {
34604fd306cSNickeau        return true;
34704fd306cSNickeau    }
34804fd306cSNickeau
34904fd306cSNickeau    /**
35004fd306cSNickeau     * @return Path - the full path to the meta file
35104fd306cSNickeau     */
35204fd306cSNickeau    public
35304fd306cSNickeau    function getMetaFilePath(): ?Path
35404fd306cSNickeau    {
35504fd306cSNickeau        $dokuwikiId = $this->getWikiId();
35604fd306cSNickeau        return LocalPath::createFromPathString(metaFN($dokuwikiId, '.meta'));
35704fd306cSNickeau    }
35804fd306cSNickeau
35904fd306cSNickeau    public function __toString()
36004fd306cSNickeau    {
36104fd306cSNickeau        return "DokuMeta ({$this->getWikiId()}";
36204fd306cSNickeau    }
36304fd306cSNickeau
36404fd306cSNickeau
36504fd306cSNickeau    public function deleteAndFlush()
36604fd306cSNickeau    {
36704fd306cSNickeau        $emptyMeta = [MetadataDokuWikiStore::CURRENT_METADATA => [], self::PERSISTENT_DOKUWIKI_KEY => []];
36804fd306cSNickeau        $dokuwikiId = $this->getWikiId();
36904fd306cSNickeau        p_save_metadata($dokuwikiId, $emptyMeta);
37004fd306cSNickeau    }
37104fd306cSNickeau
37204fd306cSNickeau
37304fd306cSNickeau    public function reset()
37404fd306cSNickeau    {
37504fd306cSNickeau        self::unsetGlobalVariables();
37604fd306cSNickeau    }
37704fd306cSNickeau
37804fd306cSNickeau    /**
37904fd306cSNickeau     * In {@link p_read_metadata()}, there is a global cache
38004fd306cSNickeau     * @param string $name
38104fd306cSNickeau     * @param mixed $value
38204fd306cSNickeau     */
38304fd306cSNickeau    private function setGlobalCacheIfAny(string $name, $value)
38404fd306cSNickeau    {
38504fd306cSNickeau        global $cache_metadata;
38604fd306cSNickeau
38704fd306cSNickeau        $id = $this->getWikiId();
38804fd306cSNickeau        if (isset($cache_metadata[$id])) {
38904fd306cSNickeau            $cache_metadata[$id]['persistent'][$name] = $value;
39004fd306cSNickeau            $cache_metadata[$id]['current'][$name] = $value;
39104fd306cSNickeau        }
39204fd306cSNickeau
39304fd306cSNickeau    }
39404fd306cSNickeau
39504fd306cSNickeau    /**
39604fd306cSNickeau     * @return array -the full array only needed by the rendering process
39704fd306cSNickeau     * You should use {@link self::getData()} otherwise
39804fd306cSNickeau     */
39904fd306cSNickeau    public function getDataCurrentAndPersistent(): array
40004fd306cSNickeau    {
40104fd306cSNickeau
40204fd306cSNickeau        $id = $this->getWikiId();
40304fd306cSNickeau        $data = p_read_metadata($id, true);
40404fd306cSNickeau        if (empty($data)) {
40504fd306cSNickeau            LogUtility::internalError("The metadata cache was empty");
40604fd306cSNickeau            $data = p_read_metadata($id);
40704fd306cSNickeau        }
40804fd306cSNickeau        return $data;
40904fd306cSNickeau    }
41004fd306cSNickeau}
411