1*007225e5Sgerardnico<?php 2*007225e5Sgerardnico/** 3*007225e5Sgerardnico * Front Matter implementation to add metadata 4*007225e5Sgerardnico * 5*007225e5Sgerardnico * 6*007225e5Sgerardnico * that enhance the metadata dokuwiki system 7*007225e5Sgerardnico * https://www.dokuwiki.org/metadata 8*007225e5Sgerardnico * that use the Dublin Core Standard 9*007225e5Sgerardnico * http://dublincore.org/ 10*007225e5Sgerardnico * by adding the front matter markup specification 11*007225e5Sgerardnico * https://gerardnico.com/markup/front-matter 12*007225e5Sgerardnico * 13*007225e5Sgerardnico * Inspiration 14*007225e5Sgerardnico * https://github.com/dokufreaks/plugin-meta/blob/master/syntax.php 15*007225e5Sgerardnico * https://www.dokuwiki.org/plugin:semantic 16*007225e5Sgerardnico * 17*007225e5Sgerardnico * See also structured plugin 18*007225e5Sgerardnico * https://www.dokuwiki.org/plugin:data 19*007225e5Sgerardnico * https://www.dokuwiki.org/plugin:struct 20*007225e5Sgerardnico * 21*007225e5Sgerardnico */ 22*007225e5Sgerardnico 23*007225e5Sgerardnicouse ComboStrap\Analytics; 24*007225e5Sgerardnicouse ComboStrap\LogUtility; 25*007225e5Sgerardnicouse ComboStrap\PluginUtility; 26*007225e5Sgerardnicouse ComboStrap\UrlCanonical; 27*007225e5Sgerardnico 28*007225e5Sgerardnicorequire_once(__DIR__ . '/../class/Analytics.php'); 29*007225e5Sgerardnico 30*007225e5Sgerardnicoif (!defined('DOKU_INC')) { 31*007225e5Sgerardnico die(); 32*007225e5Sgerardnico} 33*007225e5Sgerardnico 34*007225e5Sgerardnico/** 35*007225e5Sgerardnico * All DokuWiki plugins to extend the parser/rendering mechanism 36*007225e5Sgerardnico * need to inherit from this class 37*007225e5Sgerardnico */ 38*007225e5Sgerardnicoclass syntax_plugin_combo_frontmatter extends DokuWiki_Syntax_Plugin 39*007225e5Sgerardnico{ 40*007225e5Sgerardnico const PARSING_STATE_EMPTY = "empty"; 41*007225e5Sgerardnico const PARSING_STATE_ERROR = "error"; 42*007225e5Sgerardnico const PARSING_STATE_SUCCESSFUL = "successful"; 43*007225e5Sgerardnico 44*007225e5Sgerardnico /** 45*007225e5Sgerardnico * Syntax Type. 46*007225e5Sgerardnico * 47*007225e5Sgerardnico * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 48*007225e5Sgerardnico * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 49*007225e5Sgerardnico * 50*007225e5Sgerardnico * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php 51*007225e5Sgerardnico * 52*007225e5Sgerardnico * baseonly - run only in the base 53*007225e5Sgerardnico */ 54*007225e5Sgerardnico function getType() 55*007225e5Sgerardnico { 56*007225e5Sgerardnico return 'baseonly'; 57*007225e5Sgerardnico } 58*007225e5Sgerardnico 59*007225e5Sgerardnico /** 60*007225e5Sgerardnico * @see Doku_Parser_Mode::getSort() 61*007225e5Sgerardnico * Higher number than the teaser-columns 62*007225e5Sgerardnico * because the mode with the lowest sort number will win out 63*007225e5Sgerardnico */ 64*007225e5Sgerardnico function getSort() 65*007225e5Sgerardnico { 66*007225e5Sgerardnico return 99; 67*007225e5Sgerardnico } 68*007225e5Sgerardnico 69*007225e5Sgerardnico /** 70*007225e5Sgerardnico * Create a pattern that will called this plugin 71*007225e5Sgerardnico * 72*007225e5Sgerardnico * @param string $mode 73*007225e5Sgerardnico * @see Doku_Parser_Mode::connectTo() 74*007225e5Sgerardnico */ 75*007225e5Sgerardnico function connectTo($mode) 76*007225e5Sgerardnico { 77*007225e5Sgerardnico if ($mode == "base") { 78*007225e5Sgerardnico // only from the top 79*007225e5Sgerardnico $this->Lexer->addSpecialPattern('---json.*?---', $mode, PluginUtility::getModeForComponent($this->getPluginComponent())); 80*007225e5Sgerardnico } 81*007225e5Sgerardnico } 82*007225e5Sgerardnico 83*007225e5Sgerardnico /** 84*007225e5Sgerardnico * 85*007225e5Sgerardnico * The handle function goal is to parse the matched syntax through the pattern function 86*007225e5Sgerardnico * and to return the result for use in the renderer 87*007225e5Sgerardnico * This result is always cached until the page is modified. 88*007225e5Sgerardnico * @param string $match 89*007225e5Sgerardnico * @param int $state 90*007225e5Sgerardnico * @param int $pos 91*007225e5Sgerardnico * @param Doku_Handler $handler 92*007225e5Sgerardnico * @return array|bool 93*007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::handle() 94*007225e5Sgerardnico * 95*007225e5Sgerardnico */ 96*007225e5Sgerardnico function handle($match, $state, $pos, Doku_Handler $handler) 97*007225e5Sgerardnico { 98*007225e5Sgerardnico 99*007225e5Sgerardnico if ($state == DOKU_LEXER_SPECIAL) { 100*007225e5Sgerardnico 101*007225e5Sgerardnico global $ID; 102*007225e5Sgerardnico 103*007225e5Sgerardnico // strip 104*007225e5Sgerardnico // from start `---json` + eol = 8 105*007225e5Sgerardnico // from end `---` + eol = 4 106*007225e5Sgerardnico $match = substr($match, 7, -3); 107*007225e5Sgerardnico 108*007225e5Sgerardnico // Empty front matter 109*007225e5Sgerardnico if (trim($match) == "") { 110*007225e5Sgerardnico $this->closeParsing(); 111*007225e5Sgerardnico return array("state" => self::PARSING_STATE_EMPTY); 112*007225e5Sgerardnico } 113*007225e5Sgerardnico 114*007225e5Sgerardnico // Otherwise you get an object ie $arrayFormat-> syntax 115*007225e5Sgerardnico $arrayFormat = true; 116*007225e5Sgerardnico $json = json_decode($match, $arrayFormat); 117*007225e5Sgerardnico 118*007225e5Sgerardnico // Decodage problem 119*007225e5Sgerardnico if ($json == null) { 120*007225e5Sgerardnico return array("state" => self::PARSING_STATE_ERROR); 121*007225e5Sgerardnico } 122*007225e5Sgerardnico 123*007225e5Sgerardnico // Trim it 124*007225e5Sgerardnico $jsonKey = array_map('trim', array_keys($json)); 125*007225e5Sgerardnico // We will get a php warning here because the values may be an array 126*007225e5Sgerardnico // and trim accept only string 127*007225e5Sgerardnico $oldLevel = error_reporting(E_ERROR); 128*007225e5Sgerardnico $jsonValues = array_map('trim', $json); 129*007225e5Sgerardnico error_reporting($oldLevel); 130*007225e5Sgerardnico $json = array_combine($jsonKey, $jsonValues); 131*007225e5Sgerardnico 132*007225e5Sgerardnico 133*007225e5Sgerardnico $notModifiableMeta = [ 134*007225e5Sgerardnico "date", 135*007225e5Sgerardnico "user", 136*007225e5Sgerardnico "last_change", 137*007225e5Sgerardnico "creator", 138*007225e5Sgerardnico "contributor" 139*007225e5Sgerardnico ]; 140*007225e5Sgerardnico $result = array(); 141*007225e5Sgerardnico foreach ($json as $key => $value) { 142*007225e5Sgerardnico 143*007225e5Sgerardnico // Not modifiable metadata 144*007225e5Sgerardnico if (in_array($key, $notModifiableMeta)) { 145*007225e5Sgerardnico LogUtility::msg("Front Matter: The metadata ($key) is a protected metadata and cannot be modified", LogUtility::LVL_MSG_WARNING); 146*007225e5Sgerardnico continue; 147*007225e5Sgerardnico } 148*007225e5Sgerardnico 149*007225e5Sgerardnico // Description is special 150*007225e5Sgerardnico if ($key == "description") { 151*007225e5Sgerardnico $result["description"] = $value; 152*007225e5Sgerardnico p_set_metadata($ID, array("description" => array("abstract" => $value))); 153*007225e5Sgerardnico continue; 154*007225e5Sgerardnico } 155*007225e5Sgerardnico 156*007225e5Sgerardnico // Canonical should be lowercase 157*007225e5Sgerardnico if ($key == UrlCanonical::CANONICAL_PROPERTY) { 158*007225e5Sgerardnico $result[UrlCanonical::CANONICAL_PROPERTY] = $value; 159*007225e5Sgerardnico $value = strtolower($value); 160*007225e5Sgerardnico } 161*007225e5Sgerardnico 162*007225e5Sgerardnico // Set the value persistently 163*007225e5Sgerardnico p_set_metadata($ID, array($key => $value)); 164*007225e5Sgerardnico 165*007225e5Sgerardnico } 166*007225e5Sgerardnico 167*007225e5Sgerardnico $this->closeParsing($json); 168*007225e5Sgerardnico 169*007225e5Sgerardnico $result["state"]= self::PARSING_STATE_SUCCESSFUL; 170*007225e5Sgerardnico 171*007225e5Sgerardnico return $result; 172*007225e5Sgerardnico } 173*007225e5Sgerardnico 174*007225e5Sgerardnico return array(); 175*007225e5Sgerardnico } 176*007225e5Sgerardnico 177*007225e5Sgerardnico /** 178*007225e5Sgerardnico * Render the output 179*007225e5Sgerardnico * @param string $format 180*007225e5Sgerardnico * @param Doku_Renderer $renderer 181*007225e5Sgerardnico * @param array $data - what the function handle() return'ed 182*007225e5Sgerardnico * @return boolean - rendered correctly? (however, returned value is not used at the moment) 183*007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::render() 184*007225e5Sgerardnico * 185*007225e5Sgerardnico * 186*007225e5Sgerardnico */ 187*007225e5Sgerardnico function render($format, Doku_Renderer $renderer, $data) 188*007225e5Sgerardnico { 189*007225e5Sgerardnico // TODO: https://developers.google.com/search/docs/data-types/breadcrumb#breadcrumb-list 190*007225e5Sgerardnico // News article: https://developers.google.com/search/docs/data-types/article 191*007225e5Sgerardnico // News article: https://developers.google.com/search/docs/data-types/paywalled-content 192*007225e5Sgerardnico // What is ?: https://developers.google.com/search/docs/data-types/qapage 193*007225e5Sgerardnico // How to ?: https://developers.google.com/search/docs/data-types/how-to 194*007225e5Sgerardnico 195*007225e5Sgerardnico switch ($format) { 196*007225e5Sgerardnico case 'xhtml': 197*007225e5Sgerardnico global $ID; 198*007225e5Sgerardnico /** @var Doku_Renderer_xhtml $renderer */ 199*007225e5Sgerardnico $state = $data["state"]; 200*007225e5Sgerardnico if ($state == self::PARSING_STATE_ERROR) { 201*007225e5Sgerardnico LogUtility::msg("Front Matter: The json object for the page ($ID) is not valid", LogUtility::LVL_MSG_ERROR); 202*007225e5Sgerardnico } 203*007225e5Sgerardnico break; 204*007225e5Sgerardnico case Analytics::RENDERER_FORMAT: 205*007225e5Sgerardnico /** @var renderer_plugin_combo_analytics $renderer */ 206*007225e5Sgerardnico if (array_key_exists("description", $data)) { 207*007225e5Sgerardnico $renderer->setMeta("description", $data["description"]); 208*007225e5Sgerardnico } 209*007225e5Sgerardnico if (array_key_exists(UrlCanonical::CANONICAL_PROPERTY, $data)) { 210*007225e5Sgerardnico $renderer->setMeta(UrlCanonical::CANONICAL_PROPERTY, $data[UrlCanonical::CANONICAL_PROPERTY]); 211*007225e5Sgerardnico } 212*007225e5Sgerardnico break; 213*007225e5Sgerardnico 214*007225e5Sgerardnico } 215*007225e5Sgerardnico return true; 216*007225e5Sgerardnico } 217*007225e5Sgerardnico 218*007225e5Sgerardnico /** 219*007225e5Sgerardnico * 220*007225e5Sgerardnico * @param array $json - The Json 221*007225e5Sgerardnico * Delete the controlled meta that are no more present if they exists 222*007225e5Sgerardnico * @return bool 223*007225e5Sgerardnico */ 224*007225e5Sgerardnico public function closeParsing(array $json = array()) 225*007225e5Sgerardnico { 226*007225e5Sgerardnico global $ID; 227*007225e5Sgerardnico 228*007225e5Sgerardnico /** 229*007225e5Sgerardnico * The managed meta with the exception of 230*007225e5Sgerardnico * the {@link action_plugin_combo_metadescription::DESCRIPTION_META_KEY description} 231*007225e5Sgerardnico * because it's already managed by dokuwiki in description['abstract'] 232*007225e5Sgerardnico */ 233*007225e5Sgerardnico $managedMeta = [ 234*007225e5Sgerardnico UrlCanonical::CANONICAL_PROPERTY, 235*007225e5Sgerardnico action_plugin_combo_metatitle::TITLE_META_KEY, 236*007225e5Sgerardnico syntax_plugin_combo_disqus::META_DISQUS_IDENTIFIER 237*007225e5Sgerardnico ]; 238*007225e5Sgerardnico $meta = p_read_metadata($ID); 239*007225e5Sgerardnico foreach ($managedMeta as $metaKey) { 240*007225e5Sgerardnico if (!array_key_exists($metaKey, $json)) { 241*007225e5Sgerardnico if (isset($meta['persistent'][$metaKey])) { 242*007225e5Sgerardnico unset($meta['persistent'][$metaKey]); 243*007225e5Sgerardnico } 244*007225e5Sgerardnico } 245*007225e5Sgerardnico } 246*007225e5Sgerardnico return p_save_metadata($ID, $meta); 247*007225e5Sgerardnico } 248*007225e5Sgerardnico 249*007225e5Sgerardnico 250*007225e5Sgerardnico} 251*007225e5Sgerardnico 252