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 23007225e5Sgerardnicouse ComboStrap\LogUtility; 2471f916b9Sgerardnicouse ComboStrap\Page; 25a6bf47aaSNickeauuse ComboStrap\PluginUtility; 26007225e5Sgerardnico 27a6bf47aaSNickeaurequire_once(__DIR__ . '/../class/PluginUtility.php'); 28007225e5Sgerardnico 29007225e5Sgerardnicoif (!defined('DOKU_INC')) { 30007225e5Sgerardnico die(); 31007225e5Sgerardnico} 32007225e5Sgerardnico 33007225e5Sgerardnico/** 34007225e5Sgerardnico * All DokuWiki plugins to extend the parser/rendering mechanism 35007225e5Sgerardnico * need to inherit from this class 36d5303bc5Sgerardnico * 37d5303bc5Sgerardnico * For a list of meta, see also https://ghost.org/docs/publishing/#api-data 38007225e5Sgerardnico */ 39007225e5Sgerardnicoclass syntax_plugin_combo_frontmatter extends DokuWiki_Syntax_Plugin 40007225e5Sgerardnico{ 41007225e5Sgerardnico const PARSING_STATE_EMPTY = "empty"; 42007225e5Sgerardnico const PARSING_STATE_ERROR = "error"; 43007225e5Sgerardnico const PARSING_STATE_SUCCESSFUL = "successful"; 445f891b7eSNickeau const STATUS = "status"; 455f891b7eSNickeau const CANONICAL = "frontmatter"; 4621913ab3SNickeau const CONF_ENABLE_SECTION_EDITING = 'enableFrontMatterSectionEditing'; 47007225e5Sgerardnico 48007225e5Sgerardnico /** 49007225e5Sgerardnico * Syntax Type. 50007225e5Sgerardnico * 51007225e5Sgerardnico * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 52007225e5Sgerardnico * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 53007225e5Sgerardnico * 54007225e5Sgerardnico * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php 55007225e5Sgerardnico * 56007225e5Sgerardnico * baseonly - run only in the base 57007225e5Sgerardnico */ 58007225e5Sgerardnico function getType() 59007225e5Sgerardnico { 60007225e5Sgerardnico return 'baseonly'; 61007225e5Sgerardnico } 62007225e5Sgerardnico 63531e725cSNickeau public function getPType() 64531e725cSNickeau { 65*85e82846SNickeau /** 66*85e82846SNickeau * This element create a section 67*85e82846SNickeau * element that is a div 68*85e82846SNickeau * that should not be in paragraph 69*85e82846SNickeau * 70*85e82846SNickeau * We make it a block 71*85e82846SNickeau */ 72*85e82846SNickeau return "block"; 73531e725cSNickeau } 74531e725cSNickeau 75531e725cSNickeau 76007225e5Sgerardnico /** 77007225e5Sgerardnico * @see Doku_Parser_Mode::getSort() 78007225e5Sgerardnico * Higher number than the teaser-columns 79007225e5Sgerardnico * because the mode with the lowest sort number will win out 80007225e5Sgerardnico */ 81007225e5Sgerardnico function getSort() 82007225e5Sgerardnico { 83007225e5Sgerardnico return 99; 84007225e5Sgerardnico } 85007225e5Sgerardnico 86007225e5Sgerardnico /** 87007225e5Sgerardnico * Create a pattern that will called this plugin 88007225e5Sgerardnico * 89007225e5Sgerardnico * @param string $mode 90007225e5Sgerardnico * @see Doku_Parser_Mode::connectTo() 91007225e5Sgerardnico */ 92007225e5Sgerardnico function connectTo($mode) 93007225e5Sgerardnico { 94007225e5Sgerardnico if ($mode == "base") { 95007225e5Sgerardnico // only from the top 969337a630SNickeau $this->Lexer->addSpecialPattern('---json.*?---', $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 97007225e5Sgerardnico } 98007225e5Sgerardnico } 99007225e5Sgerardnico 100007225e5Sgerardnico /** 101007225e5Sgerardnico * 102007225e5Sgerardnico * The handle function goal is to parse the matched syntax through the pattern function 103007225e5Sgerardnico * and to return the result for use in the renderer 104007225e5Sgerardnico * This result is always cached until the page is modified. 105007225e5Sgerardnico * @param string $match 106007225e5Sgerardnico * @param int $state 107007225e5Sgerardnico * @param int $pos 108007225e5Sgerardnico * @param Doku_Handler $handler 109007225e5Sgerardnico * @return array|bool 110007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::handle() 111007225e5Sgerardnico * 112007225e5Sgerardnico */ 113007225e5Sgerardnico function handle($match, $state, $pos, Doku_Handler $handler) 114007225e5Sgerardnico { 115007225e5Sgerardnico 116007225e5Sgerardnico if ($state == DOKU_LEXER_SPECIAL) { 117007225e5Sgerardnico 118007225e5Sgerardnico // strip 119007225e5Sgerardnico // from start `---json` + eol = 8 120007225e5Sgerardnico // from end `---` + eol = 4 121531e725cSNickeau $jsonString = substr($match, 7, -3); 122007225e5Sgerardnico 123007225e5Sgerardnico // Empty front matter 124531e725cSNickeau if (trim($jsonString) == "") { 125a6bf47aaSNickeau $this->deleteKnownMetaThatAreNoMorePresent(); 1265f891b7eSNickeau return array(self::STATUS => self::PARSING_STATE_EMPTY); 127007225e5Sgerardnico } 128007225e5Sgerardnico 129007225e5Sgerardnico // Otherwise you get an object ie $arrayFormat-> syntax 130007225e5Sgerardnico $arrayFormat = true; 131531e725cSNickeau $jsonArray = json_decode($jsonString, $arrayFormat); 132007225e5Sgerardnico 133a6bf47aaSNickeau $result = []; 134007225e5Sgerardnico // Decodage problem 135531e725cSNickeau if ($jsonArray == null) { 136a6bf47aaSNickeau $result[self::STATUS] = self::PARSING_STATE_ERROR; 137a6bf47aaSNickeau $result[PluginUtility::PAYLOAD] = $match; 138a6bf47aaSNickeau } else { 1395f891b7eSNickeau $result[self::STATUS] = self::PARSING_STATE_SUCCESSFUL; 140a6bf47aaSNickeau $result[PluginUtility::ATTRIBUTES] = $jsonArray; 141a6bf47aaSNickeau } 142531e725cSNickeau 143531e725cSNickeau /** 144531e725cSNickeau * End position is the length of the match + 1 for the newline 145531e725cSNickeau */ 146531e725cSNickeau $newLine = 1; 147531e725cSNickeau $endPosition = $pos + strlen($match) + $newLine; 148531e725cSNickeau $result[PluginUtility::POSITION] = [$pos, $endPosition]; 149007225e5Sgerardnico 150007225e5Sgerardnico return $result; 151007225e5Sgerardnico } 152007225e5Sgerardnico 153007225e5Sgerardnico return array(); 154007225e5Sgerardnico } 155007225e5Sgerardnico 156007225e5Sgerardnico /** 157007225e5Sgerardnico * Render the output 158007225e5Sgerardnico * @param string $format 159007225e5Sgerardnico * @param Doku_Renderer $renderer 160007225e5Sgerardnico * @param array $data - what the function handle() return'ed 161007225e5Sgerardnico * @return boolean - rendered correctly? (however, returned value is not used at the moment) 162007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::render() 163007225e5Sgerardnico * 164007225e5Sgerardnico * 165007225e5Sgerardnico */ 166007225e5Sgerardnico function render($format, Doku_Renderer $renderer, $data) 167007225e5Sgerardnico { 168007225e5Sgerardnico 169007225e5Sgerardnico switch ($format) { 170007225e5Sgerardnico case 'xhtml': 171007225e5Sgerardnico global $ID; 172007225e5Sgerardnico /** @var Doku_Renderer_xhtml $renderer */ 17321913ab3SNickeau 1745f891b7eSNickeau $state = $data[self::STATUS]; 175007225e5Sgerardnico if ($state == self::PARSING_STATE_ERROR) { 1765f891b7eSNickeau $json = $data[PluginUtility::PAYLOAD]; 1775f891b7eSNickeau LogUtility::msg("Front Matter: The json object for the page ($ID) is not valid. See the errors it by clicking on <a href=\"https://jsonformatter.curiousconcept.com/?data=" . urlencode($json) . "\">this link</a>.", LogUtility::LVL_MSG_ERROR); 178007225e5Sgerardnico } 17921913ab3SNickeau 18021913ab3SNickeau /** 18121913ab3SNickeau * Section 18221913ab3SNickeau */ 18321913ab3SNickeau list($startPosition, $endPosition) = $data[PluginUtility::POSITION]; 18421913ab3SNickeau if (PluginUtility::getConfValue(self::CONF_ENABLE_SECTION_EDITING, 1)) { 18521913ab3SNickeau $position = $startPosition; 18621913ab3SNickeau $name = self::CANONICAL; 18721913ab3SNickeau PluginUtility::startSection($renderer, $position, $name); 18821913ab3SNickeau $renderer->finishSectionEdit($endPosition); 18921913ab3SNickeau } 190007225e5Sgerardnico break; 191531e725cSNickeau case renderer_plugin_combo_analytics::RENDERER_FORMAT: 192a6bf47aaSNickeau 193a6bf47aaSNickeau if ($data[self::STATUS] != self::PARSING_STATE_SUCCESSFUL) { 194a6bf47aaSNickeau return false; 195a6bf47aaSNickeau } 196a6bf47aaSNickeau 197007225e5Sgerardnico /** @var renderer_plugin_combo_analytics $renderer */ 198a6bf47aaSNickeau $jsonArray = $data[PluginUtility::ATTRIBUTES]; 199a6bf47aaSNickeau if (array_key_exists("description", $jsonArray)) { 200a6bf47aaSNickeau $renderer->setMeta("description", $jsonArray["description"]); 201007225e5Sgerardnico } 202a6bf47aaSNickeau if (array_key_exists(Page::CANONICAL_PROPERTY, $jsonArray)) { 203a6bf47aaSNickeau $renderer->setMeta(Page::CANONICAL_PROPERTY, $jsonArray[Page::CANONICAL_PROPERTY]); 204007225e5Sgerardnico } 205a6bf47aaSNickeau if (array_key_exists(Page::TITLE_PROPERTY, $jsonArray)) { 206a6bf47aaSNickeau $renderer->setMeta(Page::TITLE_PROPERTY, $jsonArray[Page::TITLE_PROPERTY]); 207f3748b38Sgerardnico } 208a6bf47aaSNickeau if (array_key_exists(Page::LOW_QUALITY_PAGE_INDICATOR, $jsonArray)) { 209a6bf47aaSNickeau $renderer->setMeta(Page::LOW_QUALITY_PAGE_INDICATOR, $jsonArray[Page::LOW_QUALITY_PAGE_INDICATOR]); 2109b9e6d1fSgerardnico } 211007225e5Sgerardnico break; 212a6bf47aaSNickeau case "metadata": 213a6bf47aaSNickeau 214a6bf47aaSNickeau if ($data[self::STATUS] != self::PARSING_STATE_SUCCESSFUL) { 215a6bf47aaSNickeau return false; 216a6bf47aaSNickeau } 217a6bf47aaSNickeau 218a6bf47aaSNickeau global $ID; 219a6bf47aaSNickeau $jsonArray = $data[PluginUtility::ATTRIBUTES]; 220a6bf47aaSNickeau 221a6bf47aaSNickeau 222a6bf47aaSNickeau $notModifiableMeta = [ 223a6bf47aaSNickeau "date", 224a6bf47aaSNickeau "user", 225a6bf47aaSNickeau "last_change", 226a6bf47aaSNickeau "creator", 227a6bf47aaSNickeau "contributor" 228a6bf47aaSNickeau ]; 229a6bf47aaSNickeau 230a6bf47aaSNickeau foreach ($jsonArray as $key => $value) { 231a6bf47aaSNickeau 232a6bf47aaSNickeau $lowerCaseKey = trim(strtolower($key)); 233a6bf47aaSNickeau 234a6bf47aaSNickeau // Not modifiable metadata 235a6bf47aaSNickeau if (in_array($lowerCaseKey, $notModifiableMeta)) { 236a6bf47aaSNickeau LogUtility::msg("Front Matter: The metadata ($lowerCaseKey) is a protected metadata and cannot be modified", LogUtility::LVL_MSG_WARNING); 237a6bf47aaSNickeau continue; 238a6bf47aaSNickeau } 239a6bf47aaSNickeau 240a6bf47aaSNickeau switch ($lowerCaseKey) { 241a6bf47aaSNickeau 242a6bf47aaSNickeau case Page::DESCRIPTION_PROPERTY: 243a6bf47aaSNickeau /** 244a6bf47aaSNickeau * Overwrite also the actual description 245a6bf47aaSNickeau */ 246a6bf47aaSNickeau p_set_metadata($ID, array(Page::DESCRIPTION_PROPERTY => array( 247a6bf47aaSNickeau "abstract" => $value, 248a6bf47aaSNickeau "origin" => syntax_plugin_combo_frontmatter::CANONICAL 249a6bf47aaSNickeau ))); 250a6bf47aaSNickeau /** 251a6bf47aaSNickeau * Continue because 252a6bf47aaSNickeau * the description value was already stored 253a6bf47aaSNickeau * We don't want to override it 254a6bf47aaSNickeau * And continue 2 because continue == break in a switch 255a6bf47aaSNickeau */ 256a6bf47aaSNickeau continue 2; 257a6bf47aaSNickeau 258a6bf47aaSNickeau 259a6bf47aaSNickeau // Canonical should be lowercase 260a6bf47aaSNickeau case Page::CANONICAL_PROPERTY: 261a6bf47aaSNickeau $value = strtolower($value); 262a6bf47aaSNickeau break; 263a6bf47aaSNickeau 264a6bf47aaSNickeau } 265a6bf47aaSNickeau // Set the value persistently 266a6bf47aaSNickeau p_set_metadata($ID, array($lowerCaseKey => $value)); 267a6bf47aaSNickeau 268a6bf47aaSNickeau } 269a6bf47aaSNickeau 270a6bf47aaSNickeau $this->deleteKnownMetaThatAreNoMorePresent($jsonArray); 271a6bf47aaSNickeau 272a6bf47aaSNickeau break; 273007225e5Sgerardnico 274007225e5Sgerardnico } 275007225e5Sgerardnico return true; 276007225e5Sgerardnico } 277007225e5Sgerardnico 278007225e5Sgerardnico /** 279007225e5Sgerardnico * 280007225e5Sgerardnico * @param array $json - The Json 281007225e5Sgerardnico * Delete the controlled meta that are no more present if they exists 282007225e5Sgerardnico * @return bool 283007225e5Sgerardnico */ 2845f891b7eSNickeau public 285a6bf47aaSNickeau function deleteKnownMetaThatAreNoMorePresent(array $json = array()) 286007225e5Sgerardnico { 287007225e5Sgerardnico global $ID; 288007225e5Sgerardnico 289007225e5Sgerardnico /** 290007225e5Sgerardnico * The managed meta with the exception of 291007225e5Sgerardnico * the {@link action_plugin_combo_metadescription::DESCRIPTION_META_KEY description} 292007225e5Sgerardnico * because it's already managed by dokuwiki in description['abstract'] 293007225e5Sgerardnico */ 294007225e5Sgerardnico $managedMeta = [ 29571f916b9Sgerardnico Page::CANONICAL_PROPERTY, 296007225e5Sgerardnico action_plugin_combo_metatitle::TITLE_META_KEY, 297007225e5Sgerardnico syntax_plugin_combo_disqus::META_DISQUS_IDENTIFIER 298007225e5Sgerardnico ]; 299007225e5Sgerardnico $meta = p_read_metadata($ID); 300007225e5Sgerardnico foreach ($managedMeta as $metaKey) { 301007225e5Sgerardnico if (!array_key_exists($metaKey, $json)) { 302007225e5Sgerardnico if (isset($meta['persistent'][$metaKey])) { 303007225e5Sgerardnico unset($meta['persistent'][$metaKey]); 304007225e5Sgerardnico } 305007225e5Sgerardnico } 306007225e5Sgerardnico } 307007225e5Sgerardnico return p_save_metadata($ID, $meta); 308007225e5Sgerardnico } 309007225e5Sgerardnico 310007225e5Sgerardnico 311007225e5Sgerardnico} 312007225e5Sgerardnico 313