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