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 * 38 * For a list of meta, see also https://ghost.org/docs/publishing/#api-data 39 */ 40class syntax_plugin_combo_frontmatter extends DokuWiki_Syntax_Plugin 41{ 42 const PARSING_STATE_EMPTY = "empty"; 43 const PARSING_STATE_ERROR = "error"; 44 const PARSING_STATE_SUCCESSFUL = "successful"; 45 const STATUS = "status"; 46 const CANONICAL = "frontmatter"; 47 const CONF_ENABLE_SECTION_EDITING = 'enableFrontMatterSectionEditing'; 48 49 /** 50 * Syntax Type. 51 * 52 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 53 * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 54 * 55 * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php 56 * 57 * baseonly - run only in the base 58 */ 59 function getType() 60 { 61 return 'baseonly'; 62 } 63 64 public function getPType() 65 { 66 return "normal"; 67 } 68 69 70 /** 71 * @see Doku_Parser_Mode::getSort() 72 * Higher number than the teaser-columns 73 * because the mode with the lowest sort number will win out 74 */ 75 function getSort() 76 { 77 return 99; 78 } 79 80 /** 81 * Create a pattern that will called this plugin 82 * 83 * @param string $mode 84 * @see Doku_Parser_Mode::connectTo() 85 */ 86 function connectTo($mode) 87 { 88 if ($mode == "base") { 89 // only from the top 90 $this->Lexer->addSpecialPattern('---json.*?---', $mode, PluginUtility::getModeForComponent($this->getPluginComponent())); 91 } 92 } 93 94 /** 95 * 96 * The handle function goal is to parse the matched syntax through the pattern function 97 * and to return the result for use in the renderer 98 * This result is always cached until the page is modified. 99 * @param string $match 100 * @param int $state 101 * @param int $pos 102 * @param Doku_Handler $handler 103 * @return array|bool 104 * @see DokuWiki_Syntax_Plugin::handle() 105 * 106 */ 107 function handle($match, $state, $pos, Doku_Handler $handler) 108 { 109 110 if ($state == DOKU_LEXER_SPECIAL) { 111 112 global $ID; 113 114 // strip 115 // from start `---json` + eol = 8 116 // from end `---` + eol = 4 117 $jsonString = substr($match, 7, -3); 118 119 // Empty front matter 120 if (trim($jsonString) == "") { 121 $this->closeParsing(); 122 return array(self::STATUS => self::PARSING_STATE_EMPTY); 123 } 124 125 // Otherwise you get an object ie $arrayFormat-> syntax 126 $arrayFormat = true; 127 $jsonArray = json_decode($jsonString, $arrayFormat); 128 129 // Decodage problem 130 if ($jsonArray == null) { 131 return array( 132 self::STATUS => self::PARSING_STATE_ERROR, 133 PluginUtility::PAYLOAD => $match 134 ); 135 } 136 137 $notModifiableMeta = [ 138 "date", 139 "user", 140 "last_change", 141 "creator", 142 "contributor" 143 ]; 144 $result = array(); 145 foreach ($jsonArray as $key => $value) { 146 147 $lowerCaseKey = trim(strtolower($key)); 148 149 // Not modifiable metadata 150 if (in_array($lowerCaseKey, $notModifiableMeta)) { 151 LogUtility::msg("Front Matter: The metadata ($lowerCaseKey) is a protected metadata and cannot be modified", LogUtility::LVL_MSG_WARNING); 152 continue; 153 } 154 155 switch ($lowerCaseKey) { 156 157 case Page::DESCRIPTION_PROPERTY: 158 $result["description"] = $value; 159 /** 160 * Overwrite also the actual description 161 */ 162 p_set_metadata($ID, array(Page::DESCRIPTION_PROPERTY => array( 163 "abstract" => $value, 164 "origin" => syntax_plugin_combo_frontmatter::CANONICAL 165 ))); 166 /** 167 * Continue because 168 * the description value was already stored 169 * We don't want to override it 170 * And continue 2 because continue == break in a switch 171 */ 172 continue 2; 173 break; 174 175 /** 176 * Pass the title to the metadata 177 * to advertise that it's in the front-matter 178 * for the quality rules 179 */ 180 case Page::TITLE_PROPERTY: 181 $result[Page::TITLE_PROPERTY] = $value; 182 break; 183 184 /** 185 * Pass the low quality indicator 186 * to advertise that it's in the front-matter 187 */ 188 case Page::LOW_QUALITY_PAGE_INDICATOR: 189 $result[Page::LOW_QUALITY_PAGE_INDICATOR] = $value; 190 break; 191 192 // Canonical should be lowercase 193 case Page::CANONICAL_PROPERTY: 194 $result[Page::CANONICAL_PROPERTY] = $value; 195 $value = strtolower($value); 196 break; 197 198 } 199 // Set the value persistently 200 p_set_metadata($ID, array($lowerCaseKey => $value)); 201 202 } 203 204 $this->closeParsing($jsonArray); 205 206 $result[self::STATUS] = self::PARSING_STATE_SUCCESSFUL; 207 208 /** 209 * End position is the length of the match + 1 for the newline 210 */ 211 $newLine = 1; 212 $endPosition = $pos + strlen($match) + $newLine; 213 $result[PluginUtility::POSITION]=[$pos, $endPosition]; 214 215 return $result; 216 } 217 218 return array(); 219 } 220 221 /** 222 * Render the output 223 * @param string $format 224 * @param Doku_Renderer $renderer 225 * @param array $data - what the function handle() return'ed 226 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 227 * @see DokuWiki_Syntax_Plugin::render() 228 * 229 * 230 */ 231 function render($format, Doku_Renderer $renderer, $data) 232 { 233 234 switch ($format) { 235 case 'xhtml': 236 global $ID; 237 /** @var Doku_Renderer_xhtml $renderer */ 238 239 $state = $data[self::STATUS]; 240 if ($state == self::PARSING_STATE_ERROR) { 241 $json = $data[PluginUtility::PAYLOAD]; 242 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); 243 } 244 245 /** 246 * Section 247 */ 248 list($startPosition,$endPosition) = $data[PluginUtility::POSITION]; 249 if (PluginUtility::getConfValue(self::CONF_ENABLE_SECTION_EDITING,1)) { 250 $position = $startPosition; 251 $name = self::CANONICAL; 252 PluginUtility::startSection($renderer, $position, $name); 253 $renderer->finishSectionEdit($endPosition); 254 } 255 break; 256 case renderer_plugin_combo_analytics::RENDERER_FORMAT: 257 /** @var renderer_plugin_combo_analytics $renderer */ 258 if (array_key_exists("description", $data)) { 259 $renderer->setMeta("description", $data["description"]); 260 } 261 if (array_key_exists(Page::CANONICAL_PROPERTY, $data)) { 262 $renderer->setMeta(Page::CANONICAL_PROPERTY, $data[Page::CANONICAL_PROPERTY]); 263 } 264 if (array_key_exists(Page::TITLE_PROPERTY, $data)) { 265 $renderer->setMeta(Page::TITLE_PROPERTY, $data[Page::TITLE_PROPERTY]); 266 } 267 if (array_key_exists(Page::LOW_QUALITY_PAGE_INDICATOR, $data)) { 268 $renderer->setMeta(Page::LOW_QUALITY_PAGE_INDICATOR, $data[Page::LOW_QUALITY_PAGE_INDICATOR]); 269 } 270 break; 271 272 } 273 return true; 274 } 275 276 /** 277 * 278 * @param array $json - The Json 279 * Delete the controlled meta that are no more present if they exists 280 * @return bool 281 */ 282 public 283 function closeParsing(array $json = array()) 284 { 285 global $ID; 286 287 /** 288 * The managed meta with the exception of 289 * the {@link action_plugin_combo_metadescription::DESCRIPTION_META_KEY description} 290 * because it's already managed by dokuwiki in description['abstract'] 291 */ 292 $managedMeta = [ 293 Page::CANONICAL_PROPERTY, 294 action_plugin_combo_metatitle::TITLE_META_KEY, 295 syntax_plugin_combo_disqus::META_DISQUS_IDENTIFIER 296 ]; 297 $meta = p_read_metadata($ID); 298 foreach ($managedMeta as $metaKey) { 299 if (!array_key_exists($metaKey, $json)) { 300 if (isset($meta['persistent'][$metaKey])) { 301 unset($meta['persistent'][$metaKey]); 302 } 303 } 304 } 305 return p_save_metadata($ID, $meta); 306 } 307 308 309} 310 311