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