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