1007225e5Sgerardnico<?php 2007225e5Sgerardnico/** 3007225e5Sgerardnico * Front Matter implementation to add metadata 4007225e5Sgerardnico * 5007225e5Sgerardnico * 6007225e5Sgerardnico * that enhance the metadata dokuwiki system 7007225e5Sgerardnico * https://www.dokuwiki.org/metadata 8007225e5Sgerardnico * that use the Dublin Core Standard 9007225e5Sgerardnico * http://dublincore.org/ 10007225e5Sgerardnico * by adding the front matter markup specification 11007225e5Sgerardnico * https://gerardnico.com/markup/front-matter 12007225e5Sgerardnico * 13007225e5Sgerardnico * Inspiration 14007225e5Sgerardnico * https://github.com/dokufreaks/plugin-meta/blob/master/syntax.php 15007225e5Sgerardnico * https://www.dokuwiki.org/plugin:semantic 16007225e5Sgerardnico * 17007225e5Sgerardnico * See also structured plugin 18007225e5Sgerardnico * https://www.dokuwiki.org/plugin:data 19007225e5Sgerardnico * https://www.dokuwiki.org/plugin:struct 20007225e5Sgerardnico * 21007225e5Sgerardnico */ 22007225e5Sgerardnico 23*04fd306cSNickeauuse ComboStrap\ExceptionBadArgument; 24*04fd306cSNickeauuse ComboStrap\ExceptionBadSyntax; 25*04fd306cSNickeauuse ComboStrap\ExceptionCompile; 26*04fd306cSNickeauuse ComboStrap\ExceptionNotFound; 27*04fd306cSNickeauuse ComboStrap\ExceptionRuntime; 28*04fd306cSNickeauuse ComboStrap\ExecutionContext; 29007225e5Sgerardnicouse ComboStrap\LogUtility; 30*04fd306cSNickeauuse ComboStrap\MarkupPath; 31*04fd306cSNickeauuse ComboStrap\MarkupRef; 32*04fd306cSNickeauuse ComboStrap\MediaMarkup; 33*04fd306cSNickeauuse ComboStrap\Meta\Api\Metadata; 34*04fd306cSNickeauuse ComboStrap\Meta\Api\MetadataSystem; 35*04fd306cSNickeauuse ComboStrap\Meta\Store\MetadataDokuWikiStore; 36c3437056SNickeauuse ComboStrap\MetadataFrontmatterStore; 37c3437056SNickeauuse ComboStrap\MetadataStoreTransfer; 38*04fd306cSNickeauuse ComboStrap\PageDescription; 39*04fd306cSNickeauuse ComboStrap\Meta\Field\PageImagePath; 40*04fd306cSNickeauuse ComboStrap\Meta\Field\PageImages; 41a6bf47aaSNickeauuse ComboStrap\PluginUtility; 42007225e5Sgerardnico 4337748cd8SNickeaurequire_once(__DIR__ . '/../ComboStrap/PluginUtility.php'); 44007225e5Sgerardnico 45007225e5Sgerardnico 46007225e5Sgerardnico/** 47007225e5Sgerardnico * All DokuWiki plugins to extend the parser/rendering mechanism 48007225e5Sgerardnico * need to inherit from this class 49d5303bc5Sgerardnico * 50d5303bc5Sgerardnico * For a list of meta, see also https://ghost.org/docs/publishing/#api-data 51007225e5Sgerardnico */ 52007225e5Sgerardnicoclass syntax_plugin_combo_frontmatter extends DokuWiki_Syntax_Plugin 53007225e5Sgerardnico{ 54*04fd306cSNickeau 55*04fd306cSNickeau const PARSING_STATE_ERROR = 1; 56*04fd306cSNickeau const PARSING_STATE_SUCCESSFUL = 0; 57*04fd306cSNickeau 585f891b7eSNickeau const CANONICAL = "frontmatter"; 59*04fd306cSNickeau const TAG = "frontmatter"; 60c3437056SNickeau const CONF_ENABLE_FRONT_MATTER_ON_SUBMIT_DEFAULT = 0; 61007225e5Sgerardnico 62007225e5Sgerardnico /** 6337748cd8SNickeau * Used in the move plugin 6437748cd8SNickeau * !!! The two last word of the plugin class !!! 6537748cd8SNickeau */ 6637748cd8SNickeau const COMPONENT = 'combo_' . self::CANONICAL; 6737748cd8SNickeau const START_TAG = '---json'; 6837748cd8SNickeau const END_TAG = '---'; 6937748cd8SNickeau const METADATA_IMAGE_CANONICAL = "metadata:image"; 70c3437056SNickeau const PATTERN = self::START_TAG . '.*?' . self::END_TAG; 7137748cd8SNickeau 7237748cd8SNickeau /** 73c3437056SNickeau * The update status for the update of the frontmatter 7437748cd8SNickeau */ 75c3437056SNickeau const UPDATE_EXIT_CODE_DONE = 000; 76c3437056SNickeau const UPDATE_EXIT_CODE_NOT_ENABLED = 100; 77c3437056SNickeau const UPDATE_EXIT_CODE_NOT_CHANGED = 200; 78c3437056SNickeau const UPDATE_EXIT_CODE_ERROR = 500; 7937748cd8SNickeau 8037748cd8SNickeau 8137748cd8SNickeau /** 82007225e5Sgerardnico * Syntax Type. 83007225e5Sgerardnico * 84007225e5Sgerardnico * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 85007225e5Sgerardnico * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 86007225e5Sgerardnico * 87007225e5Sgerardnico * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php 88007225e5Sgerardnico * 89007225e5Sgerardnico * baseonly - run only in the base 90007225e5Sgerardnico */ 91c3437056SNickeau function getType(): string 92007225e5Sgerardnico { 93007225e5Sgerardnico return 'baseonly'; 94007225e5Sgerardnico } 95007225e5Sgerardnico 96*04fd306cSNickeau public function getPType(): string 97531e725cSNickeau { 9885e82846SNickeau /** 9985e82846SNickeau * This element create a section 10085e82846SNickeau * element that is a div 10185e82846SNickeau * that should not be in paragraph 10285e82846SNickeau * 10385e82846SNickeau * We make it a block 10485e82846SNickeau */ 10585e82846SNickeau return "block"; 106531e725cSNickeau } 107531e725cSNickeau 108531e725cSNickeau 109007225e5Sgerardnico /** 110007225e5Sgerardnico * @see Doku_Parser_Mode::getSort() 111007225e5Sgerardnico * Higher number than the teaser-columns 112007225e5Sgerardnico * because the mode with the lowest sort number will win out 113007225e5Sgerardnico */ 114007225e5Sgerardnico function getSort() 115007225e5Sgerardnico { 116007225e5Sgerardnico return 99; 117007225e5Sgerardnico } 118007225e5Sgerardnico 119007225e5Sgerardnico /** 120007225e5Sgerardnico * Create a pattern that will called this plugin 121007225e5Sgerardnico * 122007225e5Sgerardnico * @param string $mode 123007225e5Sgerardnico * @see Doku_Parser_Mode::connectTo() 124007225e5Sgerardnico */ 125007225e5Sgerardnico function connectTo($mode) 126007225e5Sgerardnico { 127007225e5Sgerardnico if ($mode == "base") { 128007225e5Sgerardnico // only from the top 129c3437056SNickeau $this->Lexer->addSpecialPattern(self::PATTERN, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 130007225e5Sgerardnico } 131007225e5Sgerardnico } 132007225e5Sgerardnico 133007225e5Sgerardnico /** 134007225e5Sgerardnico * 135007225e5Sgerardnico * The handle function goal is to parse the matched syntax through the pattern function 136007225e5Sgerardnico * and to return the result for use in the renderer 137007225e5Sgerardnico * This result is always cached until the page is modified. 138007225e5Sgerardnico * @param string $match 139007225e5Sgerardnico * @param int $state 140007225e5Sgerardnico * @param int $pos 141007225e5Sgerardnico * @param Doku_Handler $handler 142*04fd306cSNickeau * @return array 143007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::handle() 144007225e5Sgerardnico * 145007225e5Sgerardnico */ 146*04fd306cSNickeau function handle($match, $state, $pos, Doku_Handler $handler): array 147007225e5Sgerardnico { 148007225e5Sgerardnico 149a6bf47aaSNickeau $result = []; 150*04fd306cSNickeau 151c3437056SNickeau try { 152*04fd306cSNickeau $wikiPath = ExecutionContext::getActualOrCreateFromEnv()->getExecutingWikiPath(); 153*04fd306cSNickeau $parsedPage = MarkupPath::createPageFromPathObject($wikiPath); 154*04fd306cSNickeau } catch (ExceptionCompile $e) { 155*04fd306cSNickeau LogUtility::error("The global ID is unknown, we couldn't get the requested page", self::CANONICAL); 156*04fd306cSNickeau return []; 157*04fd306cSNickeau } 158*04fd306cSNickeau try { 159*04fd306cSNickeau 160*04fd306cSNickeau $frontMatterStore = MetadataFrontmatterStore::createFromFrontmatterString($parsedPage, $match); 161*04fd306cSNickeau $result[PluginUtility::EXIT_CODE] = self::PARSING_STATE_SUCCESSFUL; 162*04fd306cSNickeau } catch (ExceptionCompile $e) { 163c3437056SNickeau // Decode problem 164*04fd306cSNickeau $result[PluginUtility::EXIT_CODE] = self::PARSING_STATE_ERROR; 165*04fd306cSNickeau $result[PluginUtility::EXIT_MESSAGE] = $match; 166c3437056SNickeau return $result; 167c3437056SNickeau } 16837748cd8SNickeau 16937748cd8SNickeau 170*04fd306cSNickeau $targetStore = MetadataDokuWikiStore::getOrCreateFromResource($parsedPage); 171*04fd306cSNickeau $frontMatterData = $frontMatterStore->getData(); 172c3437056SNickeau 173*04fd306cSNickeau $transfer = MetadataStoreTransfer::createForPage($parsedPage) 174c3437056SNickeau ->fromStore($frontMatterStore) 175c3437056SNickeau ->toStore($targetStore) 176*04fd306cSNickeau ->setMetadatas($frontMatterData) 177*04fd306cSNickeau ->validate(); 178c3437056SNickeau 179c3437056SNickeau $messages = $transfer->getMessages(); 180*04fd306cSNickeau $validatedMetadatas = $transfer->getValidatedMetadatas(); 181*04fd306cSNickeau $renderMetadata = []; 182*04fd306cSNickeau foreach ($validatedMetadatas as $metadataObject) { 183*04fd306cSNickeau $renderMetadata[$metadataObject::getPersistentName()] = $metadataObject->toStoreValue(); 1841fa8c418SNickeau } 185c3437056SNickeau 186c3437056SNickeau foreach ($messages as $message) { 187*04fd306cSNickeau $message->sendToLogUtility(); 1881fa8c418SNickeau } 1891fa8c418SNickeau 190c3437056SNickeau /** 191c3437056SNickeau * Return them for metadata rendering 192c3437056SNickeau */ 193*04fd306cSNickeau $result[PluginUtility::ATTRIBUTES] = $renderMetadata; 194007225e5Sgerardnico return $result; 195007225e5Sgerardnico 196007225e5Sgerardnico } 197007225e5Sgerardnico 198007225e5Sgerardnico /** 199007225e5Sgerardnico * Render the output 200007225e5Sgerardnico * @param string $format 201007225e5Sgerardnico * @param Doku_Renderer $renderer 202007225e5Sgerardnico * @param array $data - what the function handle() return'ed 203007225e5Sgerardnico * @return boolean - rendered correctly? (however, returned value is not used at the moment) 204007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::render() 205007225e5Sgerardnico * 206007225e5Sgerardnico * 207007225e5Sgerardnico */ 208c3437056SNickeau function render($format, Doku_Renderer $renderer, $data): bool 209007225e5Sgerardnico { 210007225e5Sgerardnico 211*04fd306cSNickeau try { 212*04fd306cSNickeau $executingPath = ExecutionContext::getActualOrCreateFromEnv() 213*04fd306cSNickeau ->getExecutingWikiPath(); 214*04fd306cSNickeau } catch (ExceptionNotFound $e) { 215*04fd306cSNickeau // markup string rendering 216*04fd306cSNickeau return false; 217*04fd306cSNickeau } 218007225e5Sgerardnico switch ($format) { 219007225e5Sgerardnico case 'xhtml': 220*04fd306cSNickeau 221007225e5Sgerardnico /** @var Doku_Renderer_xhtml $renderer */ 222*04fd306cSNickeau $exitCode = $data[PluginUtility::EXIT_CODE]; 223*04fd306cSNickeau if ($exitCode == self::PARSING_STATE_ERROR) { 224*04fd306cSNickeau $json = MetadataFrontmatterStore::stripFrontmatterTag($data[PluginUtility::EXIT_MESSAGE]); 225*04fd306cSNickeau LogUtility::error("Front Matter: The json object for the page ($executingPath) is not valid. " . \ComboStrap\Json::getValidationLink($json), self::CANONICAL); 226007225e5Sgerardnico } 227*04fd306cSNickeau return true; 228c3437056SNickeau 229531e725cSNickeau case renderer_plugin_combo_analytics::RENDERER_FORMAT: 230a6bf47aaSNickeau 231*04fd306cSNickeau if ($data[PluginUtility::EXIT_CODE] !== self::PARSING_STATE_SUCCESSFUL) { 232*04fd306cSNickeau return true; 233a6bf47aaSNickeau } 234a6bf47aaSNickeau 235007225e5Sgerardnico /** @var renderer_plugin_combo_analytics $renderer */ 236c3437056SNickeau $frontMatterJsonArray = $data[PluginUtility::ATTRIBUTES]; 237c3437056SNickeau foreach ($frontMatterJsonArray as $key => $value) { 23837748cd8SNickeau 239*04fd306cSNickeau /** 240*04fd306cSNickeau * Hack while metadata and analtyics stats are not together 241*04fd306cSNickeau */ 242*04fd306cSNickeau if ($key === PageDescription::DESCRIPTION_PROPERTY) { 243*04fd306cSNickeau $value = $value['abstract']; 244*04fd306cSNickeau } 245c3437056SNickeau $renderer->setAnalyticsMetaForReporting($key, $value); 246c3437056SNickeau if ($key === PageImages::PROPERTY_NAME) { 24737748cd8SNickeau $this->updateImageStatistics($value, $renderer); 248007225e5Sgerardnico } 24937748cd8SNickeau 2509b9e6d1fSgerardnico } 251*04fd306cSNickeau return true; 25237748cd8SNickeau 253a6bf47aaSNickeau case "metadata": 254a6bf47aaSNickeau 255*04fd306cSNickeau 25637748cd8SNickeau /** @var Doku_Renderer_metadata $renderer */ 257*04fd306cSNickeau if ($data[PluginUtility::EXIT_CODE] === self::PARSING_STATE_ERROR) { 258*04fd306cSNickeau if (PluginUtility::isTest()) { 259c3437056SNickeau // fail if test 260*04fd306cSNickeau throw new ExceptionRuntime("Front Matter: The json object for the page () is not valid.", LogUtility::LVL_MSG_ERROR); 261c3437056SNickeau } 262a6bf47aaSNickeau return false; 263a6bf47aaSNickeau } 264a6bf47aaSNickeau 265c3437056SNickeau /** 266*04fd306cSNickeau * Empty string 267*04fd306cSNickeau * Rare case, we delete all mutable meta if present 268*04fd306cSNickeau */ 269*04fd306cSNickeau $frontmatterData = $data[PluginUtility::ATTRIBUTES]; 270*04fd306cSNickeau if (sizeof($frontmatterData) === 0) { 271*04fd306cSNickeau foreach (MetadataSystem::getMutableMetadata() as $metaData) { 272*04fd306cSNickeau $metaKey = $metaData::getName(); 273*04fd306cSNickeau if ($metaKey === PageDescription::PROPERTY_NAME) { 274*04fd306cSNickeau // array 275*04fd306cSNickeau continue; 276*04fd306cSNickeau } 277*04fd306cSNickeau // runtime 278*04fd306cSNickeau if ($renderer->meta[$metaKey]) { 279*04fd306cSNickeau unset($renderer->meta[$metaKey]); 280*04fd306cSNickeau } 281*04fd306cSNickeau // persistent 282*04fd306cSNickeau if ($renderer->persistent[$metaKey]) { 283*04fd306cSNickeau unset($renderer->persistent[$metaKey]); 284*04fd306cSNickeau } 285*04fd306cSNickeau } 286*04fd306cSNickeau return true; 287*04fd306cSNickeau } 288*04fd306cSNickeau 289*04fd306cSNickeau /** 290*04fd306cSNickeau * Meta update 291*04fd306cSNickeau * (The {@link p_get_metadata()} starts {@link p_render_metadata()} 292*04fd306cSNickeau * and stores them if there is any diff 293*04fd306cSNickeau */ 294*04fd306cSNickeau foreach ($frontmatterData as $metaKey => $metaValue) { 295*04fd306cSNickeau 296*04fd306cSNickeau $renderer->meta[$metaKey] = $metaValue; 297*04fd306cSNickeau 298*04fd306cSNickeau /** 299*04fd306cSNickeau * Persistence is just a duplicate of the meta (ie current) 300*04fd306cSNickeau * 301*04fd306cSNickeau * Why from https://www.dokuwiki.org/devel:metadata#metadata_persistence 302*04fd306cSNickeau * The persistent array holds ****duplicates**** 303*04fd306cSNickeau * as the {@link p_get_metadata()} returns only `current` data 304*04fd306cSNickeau * which should not be cleared during the rendering process. 305*04fd306cSNickeau */ 306*04fd306cSNickeau $renderer->persistent[$metaKey] = $metaValue; 307*04fd306cSNickeau 308*04fd306cSNickeau } 309*04fd306cSNickeau 310*04fd306cSNickeau 311*04fd306cSNickeau /** 312c3437056SNickeau * Register media in index 313c3437056SNickeau */ 314c3437056SNickeau $frontMatterJsonArray = $data[PluginUtility::ATTRIBUTES]; 315c3437056SNickeau if (isset($frontMatterJsonArray[PageImages::getPersistentName()])) { 316c3437056SNickeau $value = $frontMatterJsonArray[PageImages::getPersistentName()]; 317a6bf47aaSNickeau 318c3437056SNickeau /** 319c3437056SNickeau * @var PageImages $pageImages 320c3437056SNickeau */ 321*04fd306cSNickeau $page = MarkupPath::createPageFromPathObject($executingPath); 322c3437056SNickeau $pageImages = PageImages::createForPage($page) 323*04fd306cSNickeau ->setFromStoreValueWithoutException($value); 3240e43c1dbSgerardnico $pageImagesObject = $pageImages->getValueAsPageImages(); 3250e43c1dbSgerardnico foreach ($pageImagesObject as $imageValue) { 326*04fd306cSNickeau $dokuwikiId = $imageValue->getImagePath()->getWikiId(); 327*04fd306cSNickeau $attributes = [MarkupRef::REF_ATTRIBUTE => ":$dokuwikiId"]; 328*04fd306cSNickeau try { 32937748cd8SNickeau syntax_plugin_combo_media::registerImageMeta($attributes, $renderer); 330*04fd306cSNickeau } catch (\Exception $e) { 331*04fd306cSNickeau LogUtility::internalError("The image registration did not work. Error: {$e->getMessage()}"); 33237748cd8SNickeau } 333a6bf47aaSNickeau } 334*04fd306cSNickeau } 335a6bf47aaSNickeau break; 336007225e5Sgerardnico 337007225e5Sgerardnico } 338007225e5Sgerardnico return true; 339007225e5Sgerardnico } 340007225e5Sgerardnico 341007225e5Sgerardnico 34237748cd8SNickeau private function updateImageStatistics($value, $renderer) 34337748cd8SNickeau { 344c3437056SNickeau if (is_array($value) && sizeof($value) > 0) { 345c3437056SNickeau $firstKey = array_keys($value)[0]; 346c3437056SNickeau if (is_numeric($firstKey)) { 34737748cd8SNickeau foreach ($value as $subImage) { 34837748cd8SNickeau $this->updateImageStatistics($subImage, $renderer); 34937748cd8SNickeau } 350c3437056SNickeau return; 35137748cd8SNickeau } 35237748cd8SNickeau } 35337748cd8SNickeau 354c3437056SNickeau /** 355c3437056SNickeau * Code below is fucked up 356c3437056SNickeau */ 357c3437056SNickeau $path = $value; 358c3437056SNickeau if (is_array($value) && isset($value[PageImagePath::getPersistentName()])) { 359c3437056SNickeau $path = $value[PageImagePath::getPersistentName()]; 36037748cd8SNickeau } 361*04fd306cSNickeau try { 362*04fd306cSNickeau $media = MediaMarkup::createFromRef($path); 363*04fd306cSNickeau } catch (ExceptionBadArgument|ExceptionNotFound|ExceptionBadSyntax $e) { 364*04fd306cSNickeau LogUtility::internalError("The media image statistics could not be created. The media markup could not be instantiated with the path ($path). Error:{$e->getMessage()}"); 365*04fd306cSNickeau return; 366*04fd306cSNickeau } 367*04fd306cSNickeau 368c3437056SNickeau $attributes = $media->toCallStackArray(); 369c3437056SNickeau syntax_plugin_combo_media::updateStatistics($attributes, $renderer); 370c3437056SNickeau 37137748cd8SNickeau } 37237748cd8SNickeau 373007225e5Sgerardnico 374007225e5Sgerardnico} 375007225e5Sgerardnico 376