xref: /template/strap/ComboStrap/Meta/Store/MetadataDokuWikiStore.php (revision 3e1693237c254d9ee82f94fb9f9128467b8c10e4)
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();
195*3e169323Sgerardnico        $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);
22704fd306cSNickeau        if (is_bool($value)) {
22804fd306cSNickeau            if ($oldValue === null) {
22904fd306cSNickeau                $oldValue = $default;
23004fd306cSNickeau            } else {
23104fd306cSNickeau                $oldValue = DataType::toBoolean($oldValue);
23204fd306cSNickeau            }
23304fd306cSNickeau        }
23404fd306cSNickeau        if ($oldValue !== $value) {
23504fd306cSNickeau
23604fd306cSNickeau            /**
23704fd306cSNickeau             * Metadata in Dokuwiki is fucked up.
23804fd306cSNickeau             *
23904fd306cSNickeau             * You can't remove a metadata,
24004fd306cSNickeau             * You need to know if this is a rendering or not
24104fd306cSNickeau             *
24204fd306cSNickeau             * See just how fucked {@link p_set_metadata()} is
24304fd306cSNickeau             *
24404fd306cSNickeau             * Also don't change the type of the value to a string
24504fd306cSNickeau             * otherwise dokuwiki will not see a change
24604fd306cSNickeau             * between true and a string and will not persist the value
24704fd306cSNickeau             *
24804fd306cSNickeau             * By default, the value is copied in the current and persistent array
24904fd306cSNickeau             * and there is no render
25004fd306cSNickeau             */
25104fd306cSNickeau            $wikiId = $this->getWikiId();
25204fd306cSNickeau            if (self::isRendering($wikiId)) {
25304fd306cSNickeau
25404fd306cSNickeau                /**
25504fd306cSNickeau                 * It seems that {@link p_set_metadata()} uses it also
25604fd306cSNickeau                 * but we show it here
25704fd306cSNickeau                 */
25804fd306cSNickeau                global $METADATA_RENDERERS;
25904fd306cSNickeau                $METADATA_RENDERERS[$wikiId][self::CURRENT_METADATA][$name] = $value;
26004fd306cSNickeau                $METADATA_RENDERERS[$wikiId][self::PERSISTENT_DOKUWIKI_KEY][$name] = $value;
26104fd306cSNickeau
26204fd306cSNickeau            } else {
26304fd306cSNickeau
26404fd306cSNickeau                p_set_metadata($wikiId,
26504fd306cSNickeau                    [
26604fd306cSNickeau                        $name => $value
26704fd306cSNickeau                    ]
26804fd306cSNickeau                );
26904fd306cSNickeau
27004fd306cSNickeau            }
27104fd306cSNickeau            $this->setGlobalCacheIfAny($name, $value);
27204fd306cSNickeau        }
27304fd306cSNickeau        return $this;
27404fd306cSNickeau    }
27504fd306cSNickeau
27604fd306cSNickeau    public function getData(): array
27704fd306cSNickeau    {
27804fd306cSNickeau        /**
27904fd306cSNickeau         * We return only the current data.
28004fd306cSNickeau         *
28104fd306cSNickeau         * Why ?
28204fd306cSNickeau         * To be consistent with {@link p_get_metadata()} that retrieves only from the `current` array
28304fd306cSNickeau         * Therefore the `persistent` array values should always be duplicated in the `current` array
28404fd306cSNickeau         *
28504fd306cSNickeau         * (the only diff is that the persistent value are still available during a {@link p_render_metadata() metadata render})
28604fd306cSNickeau         *
28704fd306cSNickeau         * Note that Dokuwiki load them also for the requested path
28804fd306cSNickeau         * at `global $INFO, $info['meta']` with {@link pageinfo()}
28904fd306cSNickeau         * and is synced in {@link p_save_metadata()}
29004fd306cSNickeau         *
29104fd306cSNickeau         */
29204fd306cSNickeau        return $this->getDataCurrentAndPersistent()[self::CURRENT_METADATA];
29304fd306cSNickeau    }
29404fd306cSNickeau
29504fd306cSNickeau    private function getWikiId(): string
29604fd306cSNickeau    {
29704fd306cSNickeau        try {
29804fd306cSNickeau            return $this->getResource()->getPathObject()->toWikiPath()->getWikiId();
29904fd306cSNickeau        } catch (ExceptionCast $e) {
30004fd306cSNickeau            throw new ExceptionRuntimeInternal("Should not happen", $e);
30104fd306cSNickeau        }
30204fd306cSNickeau    }
30304fd306cSNickeau
30404fd306cSNickeau
30504fd306cSNickeau    /**
30604fd306cSNickeau     *
30704fd306cSNickeau     * @param $name
30804fd306cSNickeau     * @return mixed|null
30904fd306cSNickeau     * @deprecated - the data should always be replicated in current use {@link self::getFromName()}
31004fd306cSNickeau     */
31104fd306cSNickeau    public
31204fd306cSNickeau    function getCurrentFromName($name)
31304fd306cSNickeau    {
31404fd306cSNickeau        return $this->getFromName($name);
31504fd306cSNickeau    }
31604fd306cSNickeau
31704fd306cSNickeau    /**
31804fd306cSNickeau     * @return MetadataDokuWikiStore
31904fd306cSNickeau     * @deprecated should use a fetcher markup ?
32004fd306cSNickeau     */
32104fd306cSNickeau    public function renderAndPersist(): MetadataDokuWikiStore
32204fd306cSNickeau    {
32304fd306cSNickeau        /**
32404fd306cSNickeau         * Read/render the metadata from the file
32504fd306cSNickeau         * with parsing
32604fd306cSNickeau         */
32704fd306cSNickeau        $wikiPage = $this->getResource();
32804fd306cSNickeau        if (!$wikiPage instanceof WikiPath) {
32904fd306cSNickeau            LogUtility::errorIfDevOrTest("The resource is not a wiki path");
33004fd306cSNickeau            return $this;
33104fd306cSNickeau        }
33204fd306cSNickeau        try {
33304fd306cSNickeau            FetcherMarkup::confRoot()
33404fd306cSNickeau                ->setRequestedExecutingPath($wikiPage)
33504fd306cSNickeau                ->setRequestedContextPath($wikiPage)
33604fd306cSNickeau                ->setRequestedMimeToMetadata()
33704fd306cSNickeau                ->build()
33804fd306cSNickeau                ->processMetadataIfNotYetDone();
33904fd306cSNickeau        } catch (ExceptionNotExists $e) {
34004fd306cSNickeau            LogUtility::error("Metadata Build Error", self::CANONICAL, $e);
34104fd306cSNickeau        }
34204fd306cSNickeau
34304fd306cSNickeau        return $this;
34404fd306cSNickeau    }
34504fd306cSNickeau
34604fd306cSNickeau
34704fd306cSNickeau    public function isHierarchicalTextBased(): bool
34804fd306cSNickeau    {
34904fd306cSNickeau        return true;
35004fd306cSNickeau    }
35104fd306cSNickeau
35204fd306cSNickeau    /**
35304fd306cSNickeau     * @return Path - the full path to the meta file
35404fd306cSNickeau     */
35504fd306cSNickeau    public
35604fd306cSNickeau    function getMetaFilePath(): ?Path
35704fd306cSNickeau    {
35804fd306cSNickeau        $dokuwikiId = $this->getWikiId();
35904fd306cSNickeau        return LocalPath::createFromPathString(metaFN($dokuwikiId, '.meta'));
36004fd306cSNickeau    }
36104fd306cSNickeau
36204fd306cSNickeau    public function __toString()
36304fd306cSNickeau    {
36404fd306cSNickeau        return "DokuMeta ({$this->getWikiId()}";
36504fd306cSNickeau    }
36604fd306cSNickeau
36704fd306cSNickeau
36804fd306cSNickeau    public function deleteAndFlush()
36904fd306cSNickeau    {
37004fd306cSNickeau        $emptyMeta = [MetadataDokuWikiStore::CURRENT_METADATA => [], self::PERSISTENT_DOKUWIKI_KEY => []];
37104fd306cSNickeau        $dokuwikiId = $this->getWikiId();
37204fd306cSNickeau        p_save_metadata($dokuwikiId, $emptyMeta);
37304fd306cSNickeau    }
37404fd306cSNickeau
37504fd306cSNickeau
37604fd306cSNickeau    public function reset()
37704fd306cSNickeau    {
37804fd306cSNickeau        self::unsetGlobalVariables();
37904fd306cSNickeau    }
38004fd306cSNickeau
38104fd306cSNickeau    /**
38204fd306cSNickeau     * In {@link p_read_metadata()}, there is a global cache
38304fd306cSNickeau     * @param string $name
38404fd306cSNickeau     * @param mixed $value
38504fd306cSNickeau     */
38604fd306cSNickeau    private function setGlobalCacheIfAny(string $name, $value)
38704fd306cSNickeau    {
38804fd306cSNickeau        global $cache_metadata;
38904fd306cSNickeau
39004fd306cSNickeau        $id = $this->getWikiId();
39104fd306cSNickeau        if (isset($cache_metadata[$id])) {
39204fd306cSNickeau            $cache_metadata[$id]['persistent'][$name] = $value;
39304fd306cSNickeau            $cache_metadata[$id]['current'][$name] = $value;
39404fd306cSNickeau        }
39504fd306cSNickeau
39604fd306cSNickeau    }
39704fd306cSNickeau
39804fd306cSNickeau    /**
39904fd306cSNickeau     * @return array -the full array only needed by the rendering process
40004fd306cSNickeau     * You should use {@link self::getData()} otherwise
40104fd306cSNickeau     */
40204fd306cSNickeau    public function getDataCurrentAndPersistent(): array
40304fd306cSNickeau    {
40404fd306cSNickeau
40504fd306cSNickeau        $id = $this->getWikiId();
40604fd306cSNickeau        $data = p_read_metadata($id, true);
40704fd306cSNickeau        if (empty($data)) {
40804fd306cSNickeau            LogUtility::internalError("The metadata cache was empty");
40904fd306cSNickeau            $data = p_read_metadata($id);
41004fd306cSNickeau        }
41104fd306cSNickeau        return $data;
41204fd306cSNickeau    }
41304fd306cSNickeau}
414