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\Analytics; 24007225e5Sgerardnicouse ComboStrap\LogUtility; 25007225e5Sgerardnicouse ComboStrap\PluginUtility; 2671f916b9Sgerardnicouse ComboStrap\Page; 27007225e5Sgerardnico 28007225e5Sgerardnicorequire_once(__DIR__ . '/../class/Analytics.php'); 29007225e5Sgerardnico 30007225e5Sgerardnicoif (!defined('DOKU_INC')) { 31007225e5Sgerardnico die(); 32007225e5Sgerardnico} 33007225e5Sgerardnico 34007225e5Sgerardnico/** 35007225e5Sgerardnico * All DokuWiki plugins to extend the parser/rendering mechanism 36007225e5Sgerardnico * need to inherit from this class 37007225e5Sgerardnico */ 38007225e5Sgerardnicoclass syntax_plugin_combo_frontmatter extends DokuWiki_Syntax_Plugin 39007225e5Sgerardnico{ 40007225e5Sgerardnico const PARSING_STATE_EMPTY = "empty"; 41007225e5Sgerardnico const PARSING_STATE_ERROR = "error"; 42007225e5Sgerardnico const PARSING_STATE_SUCCESSFUL = "successful"; 43007225e5Sgerardnico 44007225e5Sgerardnico /** 45007225e5Sgerardnico * Syntax Type. 46007225e5Sgerardnico * 47007225e5Sgerardnico * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 48007225e5Sgerardnico * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 49007225e5Sgerardnico * 50007225e5Sgerardnico * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php 51007225e5Sgerardnico * 52007225e5Sgerardnico * baseonly - run only in the base 53007225e5Sgerardnico */ 54007225e5Sgerardnico function getType() 55007225e5Sgerardnico { 56007225e5Sgerardnico return 'baseonly'; 57007225e5Sgerardnico } 58007225e5Sgerardnico 59007225e5Sgerardnico /** 60007225e5Sgerardnico * @see Doku_Parser_Mode::getSort() 61007225e5Sgerardnico * Higher number than the teaser-columns 62007225e5Sgerardnico * because the mode with the lowest sort number will win out 63007225e5Sgerardnico */ 64007225e5Sgerardnico function getSort() 65007225e5Sgerardnico { 66007225e5Sgerardnico return 99; 67007225e5Sgerardnico } 68007225e5Sgerardnico 69007225e5Sgerardnico /** 70007225e5Sgerardnico * Create a pattern that will called this plugin 71007225e5Sgerardnico * 72007225e5Sgerardnico * @param string $mode 73007225e5Sgerardnico * @see Doku_Parser_Mode::connectTo() 74007225e5Sgerardnico */ 75007225e5Sgerardnico function connectTo($mode) 76007225e5Sgerardnico { 77007225e5Sgerardnico if ($mode == "base") { 78007225e5Sgerardnico // only from the top 79007225e5Sgerardnico $this->Lexer->addSpecialPattern('---json.*?---', $mode, PluginUtility::getModeForComponent($this->getPluginComponent())); 80007225e5Sgerardnico } 81007225e5Sgerardnico } 82007225e5Sgerardnico 83007225e5Sgerardnico /** 84007225e5Sgerardnico * 85007225e5Sgerardnico * The handle function goal is to parse the matched syntax through the pattern function 86007225e5Sgerardnico * and to return the result for use in the renderer 87007225e5Sgerardnico * This result is always cached until the page is modified. 88007225e5Sgerardnico * @param string $match 89007225e5Sgerardnico * @param int $state 90007225e5Sgerardnico * @param int $pos 91007225e5Sgerardnico * @param Doku_Handler $handler 92007225e5Sgerardnico * @return array|bool 93007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::handle() 94007225e5Sgerardnico * 95007225e5Sgerardnico */ 96007225e5Sgerardnico function handle($match, $state, $pos, Doku_Handler $handler) 97007225e5Sgerardnico { 98007225e5Sgerardnico 99007225e5Sgerardnico if ($state == DOKU_LEXER_SPECIAL) { 100007225e5Sgerardnico 101007225e5Sgerardnico global $ID; 102007225e5Sgerardnico 103007225e5Sgerardnico // strip 104007225e5Sgerardnico // from start `---json` + eol = 8 105007225e5Sgerardnico // from end `---` + eol = 4 106007225e5Sgerardnico $match = substr($match, 7, -3); 107007225e5Sgerardnico 108007225e5Sgerardnico // Empty front matter 109007225e5Sgerardnico if (trim($match) == "") { 110007225e5Sgerardnico $this->closeParsing(); 111007225e5Sgerardnico return array("state" => self::PARSING_STATE_EMPTY); 112007225e5Sgerardnico } 113007225e5Sgerardnico 114007225e5Sgerardnico // Otherwise you get an object ie $arrayFormat-> syntax 115007225e5Sgerardnico $arrayFormat = true; 116007225e5Sgerardnico $json = json_decode($match, $arrayFormat); 117007225e5Sgerardnico 118007225e5Sgerardnico // Decodage problem 119007225e5Sgerardnico if ($json == null) { 120007225e5Sgerardnico return array("state" => self::PARSING_STATE_ERROR); 121007225e5Sgerardnico } 122007225e5Sgerardnico 123007225e5Sgerardnico // Trim it 124007225e5Sgerardnico $jsonKey = array_map('trim', array_keys($json)); 125007225e5Sgerardnico // We will get a php warning here because the values may be an array 126007225e5Sgerardnico // and trim accept only string 127007225e5Sgerardnico $oldLevel = error_reporting(E_ERROR); 128007225e5Sgerardnico $jsonValues = array_map('trim', $json); 129007225e5Sgerardnico error_reporting($oldLevel); 130007225e5Sgerardnico $json = array_combine($jsonKey, $jsonValues); 131007225e5Sgerardnico 132007225e5Sgerardnico 133007225e5Sgerardnico $notModifiableMeta = [ 134007225e5Sgerardnico "date", 135007225e5Sgerardnico "user", 136007225e5Sgerardnico "last_change", 137007225e5Sgerardnico "creator", 138007225e5Sgerardnico "contributor" 139007225e5Sgerardnico ]; 140007225e5Sgerardnico $result = array(); 141007225e5Sgerardnico foreach ($json as $key => $value) { 142007225e5Sgerardnico 143007225e5Sgerardnico // Not modifiable metadata 144007225e5Sgerardnico if (in_array($key, $notModifiableMeta)) { 145007225e5Sgerardnico LogUtility::msg("Front Matter: The metadata ($key) is a protected metadata and cannot be modified", LogUtility::LVL_MSG_WARNING); 146007225e5Sgerardnico continue; 147007225e5Sgerardnico } 148007225e5Sgerardnico 149007225e5Sgerardnico // Description is special 150007225e5Sgerardnico if ($key == "description") { 151007225e5Sgerardnico $result["description"] = $value; 152007225e5Sgerardnico p_set_metadata($ID, array("description" => array("abstract" => $value))); 153007225e5Sgerardnico continue; 154007225e5Sgerardnico } 155007225e5Sgerardnico 156f3748b38Sgerardnico /** 157*9b9e6d1fSgerardnico * Pass the title to the metadata 158f3748b38Sgerardnico * to advertise that it's in the front-matter 159f3748b38Sgerardnico * for the quality rules 160f3748b38Sgerardnico */ 161f3748b38Sgerardnico if ($key == Page::TITLE_PROPERTY) { 162f3748b38Sgerardnico $result[Page::TITLE_PROPERTY] = $value; 163f3748b38Sgerardnico } 164f3748b38Sgerardnico 165*9b9e6d1fSgerardnico /** 166*9b9e6d1fSgerardnico * Pass the low quality indicator 167*9b9e6d1fSgerardnico * to advertise that it's in the front-matter 168*9b9e6d1fSgerardnico */ 169*9b9e6d1fSgerardnico if ($key == Page::LOW_QUALITY_PAGE_INDICATOR) { 170*9b9e6d1fSgerardnico $result[Page::LOW_QUALITY_PAGE_INDICATOR] = $value; 171*9b9e6d1fSgerardnico } 172*9b9e6d1fSgerardnico 173007225e5Sgerardnico // Canonical should be lowercase 17471f916b9Sgerardnico if ($key == Page::CANONICAL_PROPERTY) { 17571f916b9Sgerardnico $result[Page::CANONICAL_PROPERTY] = $value; 176007225e5Sgerardnico $value = strtolower($value); 177007225e5Sgerardnico } 178007225e5Sgerardnico 179007225e5Sgerardnico // Set the value persistently 180007225e5Sgerardnico p_set_metadata($ID, array($key => $value)); 181007225e5Sgerardnico 182007225e5Sgerardnico } 183007225e5Sgerardnico 184007225e5Sgerardnico $this->closeParsing($json); 185007225e5Sgerardnico 186007225e5Sgerardnico $result["state"]= self::PARSING_STATE_SUCCESSFUL; 187007225e5Sgerardnico 188007225e5Sgerardnico return $result; 189007225e5Sgerardnico } 190007225e5Sgerardnico 191007225e5Sgerardnico return array(); 192007225e5Sgerardnico } 193007225e5Sgerardnico 194007225e5Sgerardnico /** 195007225e5Sgerardnico * Render the output 196007225e5Sgerardnico * @param string $format 197007225e5Sgerardnico * @param Doku_Renderer $renderer 198007225e5Sgerardnico * @param array $data - what the function handle() return'ed 199007225e5Sgerardnico * @return boolean - rendered correctly? (however, returned value is not used at the moment) 200007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::render() 201007225e5Sgerardnico * 202007225e5Sgerardnico * 203007225e5Sgerardnico */ 204007225e5Sgerardnico function render($format, Doku_Renderer $renderer, $data) 205007225e5Sgerardnico { 206007225e5Sgerardnico // TODO: https://developers.google.com/search/docs/data-types/breadcrumb#breadcrumb-list 207007225e5Sgerardnico // News article: https://developers.google.com/search/docs/data-types/article 208007225e5Sgerardnico // News article: https://developers.google.com/search/docs/data-types/paywalled-content 209007225e5Sgerardnico // What is ?: https://developers.google.com/search/docs/data-types/qapage 210007225e5Sgerardnico // How to ?: https://developers.google.com/search/docs/data-types/how-to 211007225e5Sgerardnico 212007225e5Sgerardnico switch ($format) { 213007225e5Sgerardnico case 'xhtml': 214007225e5Sgerardnico global $ID; 215007225e5Sgerardnico /** @var Doku_Renderer_xhtml $renderer */ 216007225e5Sgerardnico $state = $data["state"]; 217007225e5Sgerardnico if ($state == self::PARSING_STATE_ERROR) { 218007225e5Sgerardnico LogUtility::msg("Front Matter: The json object for the page ($ID) is not valid", LogUtility::LVL_MSG_ERROR); 219007225e5Sgerardnico } 220007225e5Sgerardnico break; 221007225e5Sgerardnico case Analytics::RENDERER_FORMAT: 222007225e5Sgerardnico /** @var renderer_plugin_combo_analytics $renderer */ 223007225e5Sgerardnico if (array_key_exists("description", $data)) { 224007225e5Sgerardnico $renderer->setMeta("description", $data["description"]); 225007225e5Sgerardnico } 22671f916b9Sgerardnico if (array_key_exists(Page::CANONICAL_PROPERTY, $data)) { 22771f916b9Sgerardnico $renderer->setMeta(Page::CANONICAL_PROPERTY, $data[Page::CANONICAL_PROPERTY]); 228007225e5Sgerardnico } 229f3748b38Sgerardnico if (array_key_exists(Page::TITLE_PROPERTY, $data)) { 230f3748b38Sgerardnico $renderer->setMeta(Page::TITLE_PROPERTY, $data[Page::TITLE_PROPERTY]); 231f3748b38Sgerardnico } 232*9b9e6d1fSgerardnico if (array_key_exists(Page::LOW_QUALITY_PAGE_INDICATOR, $data)) { 233*9b9e6d1fSgerardnico $renderer->setMeta(Page::LOW_QUALITY_PAGE_INDICATOR, $data[Page::LOW_QUALITY_PAGE_INDICATOR]); 234*9b9e6d1fSgerardnico } 235007225e5Sgerardnico break; 236007225e5Sgerardnico 237007225e5Sgerardnico } 238007225e5Sgerardnico return true; 239007225e5Sgerardnico } 240007225e5Sgerardnico 241007225e5Sgerardnico /** 242007225e5Sgerardnico * 243007225e5Sgerardnico * @param array $json - The Json 244007225e5Sgerardnico * Delete the controlled meta that are no more present if they exists 245007225e5Sgerardnico * @return bool 246007225e5Sgerardnico */ 247007225e5Sgerardnico public function closeParsing(array $json = array()) 248007225e5Sgerardnico { 249007225e5Sgerardnico global $ID; 250007225e5Sgerardnico 251007225e5Sgerardnico /** 252007225e5Sgerardnico * The managed meta with the exception of 253007225e5Sgerardnico * the {@link action_plugin_combo_metadescription::DESCRIPTION_META_KEY description} 254007225e5Sgerardnico * because it's already managed by dokuwiki in description['abstract'] 255007225e5Sgerardnico */ 256007225e5Sgerardnico $managedMeta = [ 25771f916b9Sgerardnico Page::CANONICAL_PROPERTY, 258007225e5Sgerardnico action_plugin_combo_metatitle::TITLE_META_KEY, 259007225e5Sgerardnico syntax_plugin_combo_disqus::META_DISQUS_IDENTIFIER 260007225e5Sgerardnico ]; 261007225e5Sgerardnico $meta = p_read_metadata($ID); 262007225e5Sgerardnico foreach ($managedMeta as $metaKey) { 263007225e5Sgerardnico if (!array_key_exists($metaKey, $json)) { 264007225e5Sgerardnico if (isset($meta['persistent'][$metaKey])) { 265007225e5Sgerardnico unset($meta['persistent'][$metaKey]); 266007225e5Sgerardnico } 267007225e5Sgerardnico } 268007225e5Sgerardnico } 269007225e5Sgerardnico return p_save_metadata($ID, $meta); 270007225e5Sgerardnico } 271007225e5Sgerardnico 272007225e5Sgerardnico 273007225e5Sgerardnico} 274007225e5Sgerardnico 275