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 23c3437056SNickeauuse ComboStrap\Aliases; 24c3437056SNickeauuse ComboStrap\CacheExpirationFrequency; 25c3437056SNickeauuse ComboStrap\Canonical; 26c3437056SNickeauuse ComboStrap\EndDate; 27c3437056SNickeauuse ComboStrap\ExceptionCombo; 28c3437056SNickeauuse ComboStrap\ExceptionComboRuntime; 29c3437056SNickeauuse ComboStrap\FileSystems; 30c3437056SNickeauuse ComboStrap\Lang; 31c3437056SNickeauuse ComboStrap\LdJson; 32007225e5Sgerardnicouse ComboStrap\LogUtility; 33c3437056SNickeauuse ComboStrap\LowQualityPageOverwrite; 3437748cd8SNickeauuse ComboStrap\MediaLink; 35c3437056SNickeauuse ComboStrap\Message; 36c3437056SNickeauuse ComboStrap\Metadata; 37c3437056SNickeauuse ComboStrap\MetadataDokuWikiStore; 38c3437056SNickeauuse ComboStrap\MetadataFrontmatterStore; 39c3437056SNickeauuse ComboStrap\MetadataStoreTransfer; 4071f916b9Sgerardnicouse ComboStrap\Page; 41c3437056SNickeauuse ComboStrap\PageH1; 42c3437056SNickeauuse ComboStrap\PageId; 43c3437056SNickeauuse ComboStrap\PageImagePath; 44c3437056SNickeauuse ComboStrap\PageImages; 45c3437056SNickeauuse ComboStrap\PageKeywords; 46c3437056SNickeauuse ComboStrap\PageLayout; 47c3437056SNickeauuse ComboStrap\PagePath; 48c3437056SNickeauuse ComboStrap\PagePublicationDate; 49c3437056SNickeauuse ComboStrap\PageTitle; 50c3437056SNickeauuse ComboStrap\PageType; 51a6bf47aaSNickeauuse ComboStrap\PluginUtility; 52c3437056SNickeauuse ComboStrap\QualityDynamicMonitoringOverwrite; 53c3437056SNickeauuse ComboStrap\Region; 54c3437056SNickeauuse ComboStrap\ResourceName; 55c3437056SNickeauuse ComboStrap\StartDate; 56007225e5Sgerardnico 5737748cd8SNickeaurequire_once(__DIR__ . '/../ComboStrap/PluginUtility.php'); 58007225e5Sgerardnico 59007225e5Sgerardnico 60007225e5Sgerardnico/** 61007225e5Sgerardnico * All DokuWiki plugins to extend the parser/rendering mechanism 62007225e5Sgerardnico * need to inherit from this class 63d5303bc5Sgerardnico * 64d5303bc5Sgerardnico * For a list of meta, see also https://ghost.org/docs/publishing/#api-data 65007225e5Sgerardnico */ 66007225e5Sgerardnicoclass syntax_plugin_combo_frontmatter extends DokuWiki_Syntax_Plugin 67007225e5Sgerardnico{ 68007225e5Sgerardnico const PARSING_STATE_EMPTY = "empty"; 69007225e5Sgerardnico const PARSING_STATE_ERROR = "error"; 70007225e5Sgerardnico const PARSING_STATE_SUCCESSFUL = "successful"; 715f891b7eSNickeau const STATUS = "status"; 725f891b7eSNickeau const CANONICAL = "frontmatter"; 7321913ab3SNickeau const CONF_ENABLE_SECTION_EDITING = 'enableFrontMatterSectionEditing'; 74c3437056SNickeau const CONF_ENABLE_FRONT_MATTER_ON_SUBMIT = "enableFrontMatterOnSubmit"; 75c3437056SNickeau const CONF_ENABLE_FRONT_MATTER_ON_SUBMIT_DEFAULT = 0; 76007225e5Sgerardnico 77007225e5Sgerardnico /** 7837748cd8SNickeau * Used in the move plugin 7937748cd8SNickeau * !!! The two last word of the plugin class !!! 8037748cd8SNickeau */ 8137748cd8SNickeau const COMPONENT = 'combo_' . self::CANONICAL; 8237748cd8SNickeau const START_TAG = '---json'; 8337748cd8SNickeau const END_TAG = '---'; 8437748cd8SNickeau const METADATA_IMAGE_CANONICAL = "metadata:image"; 85c3437056SNickeau const PATTERN = self::START_TAG . '.*?' . self::END_TAG; 8637748cd8SNickeau 8737748cd8SNickeau /** 88c3437056SNickeau * The update status for the update of the frontmatter 8937748cd8SNickeau */ 90c3437056SNickeau const UPDATE_EXIT_CODE_DONE = 000; 91c3437056SNickeau const UPDATE_EXIT_CODE_NOT_ENABLED = 100; 92c3437056SNickeau const UPDATE_EXIT_CODE_NOT_CHANGED = 200; 93c3437056SNickeau const UPDATE_EXIT_CODE_ERROR = 500; 9437748cd8SNickeau 9537748cd8SNickeau 9637748cd8SNickeau /** 97007225e5Sgerardnico * Syntax Type. 98007225e5Sgerardnico * 99007225e5Sgerardnico * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 100007225e5Sgerardnico * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 101007225e5Sgerardnico * 102007225e5Sgerardnico * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php 103007225e5Sgerardnico * 104007225e5Sgerardnico * baseonly - run only in the base 105007225e5Sgerardnico */ 106c3437056SNickeau function getType(): string 107007225e5Sgerardnico { 108007225e5Sgerardnico return 'baseonly'; 109007225e5Sgerardnico } 110007225e5Sgerardnico 111531e725cSNickeau public function getPType() 112531e725cSNickeau { 11385e82846SNickeau /** 11485e82846SNickeau * This element create a section 11585e82846SNickeau * element that is a div 11685e82846SNickeau * that should not be in paragraph 11785e82846SNickeau * 11885e82846SNickeau * We make it a block 11985e82846SNickeau */ 12085e82846SNickeau return "block"; 121531e725cSNickeau } 122531e725cSNickeau 123531e725cSNickeau 124007225e5Sgerardnico /** 125007225e5Sgerardnico * @see Doku_Parser_Mode::getSort() 126007225e5Sgerardnico * Higher number than the teaser-columns 127007225e5Sgerardnico * because the mode with the lowest sort number will win out 128007225e5Sgerardnico */ 129007225e5Sgerardnico function getSort() 130007225e5Sgerardnico { 131007225e5Sgerardnico return 99; 132007225e5Sgerardnico } 133007225e5Sgerardnico 134007225e5Sgerardnico /** 135007225e5Sgerardnico * Create a pattern that will called this plugin 136007225e5Sgerardnico * 137007225e5Sgerardnico * @param string $mode 138007225e5Sgerardnico * @see Doku_Parser_Mode::connectTo() 139007225e5Sgerardnico */ 140007225e5Sgerardnico function connectTo($mode) 141007225e5Sgerardnico { 142007225e5Sgerardnico if ($mode == "base") { 143007225e5Sgerardnico // only from the top 144c3437056SNickeau $this->Lexer->addSpecialPattern(self::PATTERN, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 145007225e5Sgerardnico } 146007225e5Sgerardnico } 147007225e5Sgerardnico 148007225e5Sgerardnico /** 149007225e5Sgerardnico * 150007225e5Sgerardnico * The handle function goal is to parse the matched syntax through the pattern function 151007225e5Sgerardnico * and to return the result for use in the renderer 152007225e5Sgerardnico * This result is always cached until the page is modified. 153007225e5Sgerardnico * @param string $match 154007225e5Sgerardnico * @param int $state 155007225e5Sgerardnico * @param int $pos 156007225e5Sgerardnico * @param Doku_Handler $handler 157007225e5Sgerardnico * @return array|bool 158007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::handle() 159007225e5Sgerardnico * 160007225e5Sgerardnico */ 161007225e5Sgerardnico function handle($match, $state, $pos, Doku_Handler $handler) 162007225e5Sgerardnico { 163007225e5Sgerardnico 164007225e5Sgerardnico if ($state == DOKU_LEXER_SPECIAL) { 165007225e5Sgerardnico 166a6bf47aaSNickeau $result = []; 167c3437056SNickeau $page = Page::createPageFromGlobalDokuwikiId(); 168c3437056SNickeau try { 169c3437056SNickeau $frontMatterStore = MetadataFrontmatterStore::createFromFrontmatterString($page, $match); 170c3437056SNickeau $result[self::STATUS] = self::PARSING_STATE_SUCCESSFUL; 171c3437056SNickeau } catch (ExceptionCombo $e) { 172c3437056SNickeau // Decode problem 173a6bf47aaSNickeau $result[self::STATUS] = self::PARSING_STATE_ERROR; 174a6bf47aaSNickeau $result[PluginUtility::PAYLOAD] = $match; 175c3437056SNickeau return $result; 176c3437056SNickeau } 17737748cd8SNickeau 178c3437056SNickeau /** 179c3437056SNickeau * Empty string 180c3437056SNickeau * Rare case, we delete all mutable meta if present 181c3437056SNickeau */ 182c3437056SNickeau $frontmatterData = $frontMatterStore->getData(); 183c3437056SNickeau if ($frontmatterData === null) { 184c3437056SNickeau global $ID; 185c3437056SNickeau $meta = p_read_metadata($ID); 186c3437056SNickeau foreach (Metadata::MUTABLE_METADATA as $metaKey) { 187c3437056SNickeau if (isset($meta['persistent'][$metaKey])) { 188c3437056SNickeau unset($meta['persistent'][$metaKey]); 189c3437056SNickeau } 190c3437056SNickeau } 191c3437056SNickeau p_save_metadata($ID, $meta); 19237748cd8SNickeau return array(self::STATUS => self::PARSING_STATE_EMPTY); 19337748cd8SNickeau } 19437748cd8SNickeau 195c3437056SNickeau 19637748cd8SNickeau /** 197c3437056SNickeau * Sync 19837748cd8SNickeau */ 199c3437056SNickeau $targetStore = MetadataDokuWikiStore::getOrCreateFromResource($page); 200c3437056SNickeau $transfer = MetadataStoreTransfer::createForPage($page) 201c3437056SNickeau ->fromStore($frontMatterStore) 202c3437056SNickeau ->toStore($targetStore) 203c3437056SNickeau ->process($frontmatterData); 204c3437056SNickeau 205c3437056SNickeau $messages = $transfer->getMessages(); 206c3437056SNickeau $dataForRenderer = $transfer->getNormalizedDataArray(); 207c3437056SNickeau 208c3437056SNickeau 20937748cd8SNickeau /** 210c3437056SNickeau * Database update 21137748cd8SNickeau */ 212c3437056SNickeau try { 213c3437056SNickeau $databasePage = $page->getDatabasePage(); 214c3437056SNickeau $databasePage->replicateMetaAttributes(); 215c3437056SNickeau } catch (Exception $e) { 216c3437056SNickeau $message = Message::createErrorMessage($e->getMessage()); 217c3437056SNickeau if ($e instanceof ExceptionCombo) { 218c3437056SNickeau $message->setCanonical($e->getCanonical()); 2191fa8c418SNickeau } 220c3437056SNickeau $messages[] = $message; 22137748cd8SNickeau } 22237748cd8SNickeau 223c3437056SNickeau 224c3437056SNickeau foreach ($messages as $message) { 225c3437056SNickeau $message->sendLogMsg(); 2261fa8c418SNickeau } 2271fa8c418SNickeau 228c3437056SNickeau /** 229c3437056SNickeau * Return them for metadata rendering 230c3437056SNickeau */ 231c3437056SNickeau $result[PluginUtility::ATTRIBUTES] = $dataForRenderer; 232c3437056SNickeau 2331fa8c418SNickeau } 2341fa8c418SNickeau 235531e725cSNickeau 236531e725cSNickeau /** 237531e725cSNickeau * End position is the length of the match + 1 for the newline 238531e725cSNickeau */ 239531e725cSNickeau $newLine = 1; 240531e725cSNickeau $endPosition = $pos + strlen($match) + $newLine; 241531e725cSNickeau $result[PluginUtility::POSITION] = [$pos, $endPosition]; 242007225e5Sgerardnico 243007225e5Sgerardnico return $result; 244007225e5Sgerardnico 245007225e5Sgerardnico } 246007225e5Sgerardnico 247007225e5Sgerardnico /** 248007225e5Sgerardnico * Render the output 249007225e5Sgerardnico * @param string $format 250007225e5Sgerardnico * @param Doku_Renderer $renderer 251007225e5Sgerardnico * @param array $data - what the function handle() return'ed 252007225e5Sgerardnico * @return boolean - rendered correctly? (however, returned value is not used at the moment) 253007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::render() 254007225e5Sgerardnico * 255007225e5Sgerardnico * 256007225e5Sgerardnico */ 257c3437056SNickeau function render($format, Doku_Renderer $renderer, $data): bool 258007225e5Sgerardnico { 259007225e5Sgerardnico 260007225e5Sgerardnico switch ($format) { 261007225e5Sgerardnico case 'xhtml': 262007225e5Sgerardnico global $ID; 263007225e5Sgerardnico /** @var Doku_Renderer_xhtml $renderer */ 26421913ab3SNickeau 2655f891b7eSNickeau $state = $data[self::STATUS]; 266007225e5Sgerardnico if ($state == self::PARSING_STATE_ERROR) { 267c3437056SNickeau $json = MetadataFrontmatterStore::stripFrontmatterTag($data[PluginUtility::PAYLOAD]); 268c3437056SNickeau LogUtility::msg("Front Matter: The json object for the page ($ID) is not valid. " . \ComboStrap\Json::getValidationLink($json), LogUtility::LVL_MSG_ERROR); 269007225e5Sgerardnico } 27021913ab3SNickeau 27121913ab3SNickeau /** 27221913ab3SNickeau * Section 27321913ab3SNickeau */ 27421913ab3SNickeau list($startPosition, $endPosition) = $data[PluginUtility::POSITION]; 27521913ab3SNickeau if (PluginUtility::getConfValue(self::CONF_ENABLE_SECTION_EDITING, 1)) { 27621913ab3SNickeau $position = $startPosition; 27721913ab3SNickeau $name = self::CANONICAL; 27821913ab3SNickeau PluginUtility::startSection($renderer, $position, $name); 27921913ab3SNickeau $renderer->finishSectionEdit($endPosition); 28021913ab3SNickeau } 281007225e5Sgerardnico break; 282c3437056SNickeau 283531e725cSNickeau case renderer_plugin_combo_analytics::RENDERER_FORMAT: 284a6bf47aaSNickeau 285a6bf47aaSNickeau if ($data[self::STATUS] != self::PARSING_STATE_SUCCESSFUL) { 286a6bf47aaSNickeau return false; 287a6bf47aaSNickeau } 288a6bf47aaSNickeau 28937748cd8SNickeau 290007225e5Sgerardnico /** @var renderer_plugin_combo_analytics $renderer */ 291c3437056SNickeau $frontMatterJsonArray = $data[PluginUtility::ATTRIBUTES]; 292c3437056SNickeau foreach ($frontMatterJsonArray as $key => $value) { 29337748cd8SNickeau 294c3437056SNickeau $renderer->setAnalyticsMetaForReporting($key, $value); 295c3437056SNickeau if ($key === PageImages::PROPERTY_NAME) { 29637748cd8SNickeau $this->updateImageStatistics($value, $renderer); 297007225e5Sgerardnico } 29837748cd8SNickeau 2999b9e6d1fSgerardnico } 300007225e5Sgerardnico break; 30137748cd8SNickeau 302a6bf47aaSNickeau case "metadata": 303a6bf47aaSNickeau 304c3437056SNickeau global $ID; 30537748cd8SNickeau /** @var Doku_Renderer_metadata $renderer */ 306c3437056SNickeau if ($data[self::STATUS] === self::PARSING_STATE_ERROR) { 307c3437056SNickeau if (PluginUtility::isDevOrTest()) { 308c3437056SNickeau // fail if test 309c3437056SNickeau throw new ExceptionComboRuntime("Front Matter: The json object for the page ($ID) is not valid.", LogUtility::LVL_MSG_ERROR); 310c3437056SNickeau } 311a6bf47aaSNickeau return false; 312a6bf47aaSNickeau } 313a6bf47aaSNickeau 314c3437056SNickeau /** 315c3437056SNickeau * Register media in index 316c3437056SNickeau */ 317c3437056SNickeau $page = Page::createPageFromId($ID); 318c3437056SNickeau $frontMatterJsonArray = $data[PluginUtility::ATTRIBUTES]; 319c3437056SNickeau if (isset($frontMatterJsonArray[PageImages::getPersistentName()])) { 320c3437056SNickeau $value = $frontMatterJsonArray[PageImages::getPersistentName()]; 321a6bf47aaSNickeau 322c3437056SNickeau /** 323c3437056SNickeau * @var PageImages $pageImages 324c3437056SNickeau */ 325c3437056SNickeau $pageImages = PageImages::createForPage($page) 326c3437056SNickeau ->buildFromStoreValue($value); 327*0e43c1dbSgerardnico $pageImagesObject = $pageImages->getValueAsPageImages(); 328*0e43c1dbSgerardnico foreach ($pageImagesObject as $imageValue) { 329c3437056SNickeau $imagePath = $imageValue->getImage()->getPath()->toAbsolutePath()->toString(); 330c3437056SNickeau $attributes = [PagePath::PROPERTY_NAME => $imagePath]; 331c3437056SNickeau if (media_isexternal($imagePath)) { 332c3437056SNickeau $attributes[MediaLink::MEDIA_DOKUWIKI_TYPE] = MediaLink::EXTERNAL_MEDIA_CALL_NAME; 333c3437056SNickeau } else { 334c3437056SNickeau $attributes[MediaLink::MEDIA_DOKUWIKI_TYPE] = MediaLink::INTERNAL_MEDIA_CALL_NAME; 335a6bf47aaSNickeau } 33637748cd8SNickeau syntax_plugin_combo_media::registerImageMeta($attributes, $renderer); 33737748cd8SNickeau } 33837748cd8SNickeau 339a6bf47aaSNickeau } 340a6bf47aaSNickeau 341a6bf47aaSNickeau break; 342007225e5Sgerardnico 343007225e5Sgerardnico } 344007225e5Sgerardnico return true; 345007225e5Sgerardnico } 346007225e5Sgerardnico 347007225e5Sgerardnico 34837748cd8SNickeau private function updateImageStatistics($value, $renderer) 34937748cd8SNickeau { 350c3437056SNickeau if (is_array($value) && sizeof($value) > 0) { 351c3437056SNickeau $firstKey = array_keys($value)[0]; 352c3437056SNickeau if (is_numeric($firstKey)) { 35337748cd8SNickeau foreach ($value as $subImage) { 35437748cd8SNickeau $this->updateImageStatistics($subImage, $renderer); 35537748cd8SNickeau } 356c3437056SNickeau return; 35737748cd8SNickeau } 35837748cd8SNickeau } 35937748cd8SNickeau 360c3437056SNickeau /** 361c3437056SNickeau * Code below is fucked up 362c3437056SNickeau */ 363c3437056SNickeau $path = $value; 364c3437056SNickeau if (is_array($value) && isset($value[PageImagePath::getPersistentName()])) { 365c3437056SNickeau $path = $value[PageImagePath::getPersistentName()]; 36637748cd8SNickeau } 367c3437056SNickeau $media = MediaLink::createFromRenderMatch($path); 368c3437056SNickeau $attributes = $media->toCallStackArray(); 369c3437056SNickeau syntax_plugin_combo_media::updateStatistics($attributes, $renderer); 370c3437056SNickeau 37137748cd8SNickeau } 37237748cd8SNickeau 373007225e5Sgerardnico 374007225e5Sgerardnico} 375007225e5Sgerardnico 376