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