1<?php 2/** 3 * DokuWiki Syntax Plugin Related. 4 * 5 */ 6 7use ComboStrap\PluginUtility; 8use ComboStrap\TitleUtility; 9 10 11require_once(DOKU_INC . 'inc/parserutils.php'); 12 13/** 14 * All DokuWiki plugins to extend the parser/rendering mechanism 15 * need to inherit from this class 16 * 17 * The name of the class must follow a pattern (don't change it) 18 * 19 * The index and the metadata key for backlinks is called 'relation_references' 20 * It's the key value that you need to pass in the {@link lookupKey} of the {@link \dokuwiki\Search\Indexer} 21 * 22 * Type of conf[index]/index: 23 * * page.idx (id of the page is the element number) 24 * * title 25 * * relation_references_w.idx - _w for words 26 * * relation_references_w.idx - _i for lines (index by lines) 27 * 28 * The index is a associative map by key 29 * 30 * 31 */ 32class syntax_plugin_combo_related extends DokuWiki_Syntax_Plugin 33{ 34 35 36 // Conf property key 37 const MAX_LINKS_CONF = 'maxLinks'; 38 // For when you come from another plugin (such as backlinks) and that you don't want to change the pattern on each page 39 const EXTRA_PATTERN_CONF = 'extra_pattern'; 40 41 // This is a fake page ID that is added 42 // to the related page array when the number of backlinks is bigger than the max 43 // Poisoning object strategy 44 const MORE_PAGE_ID = 'related_more'; 45 46 // The array key of an array of related page 47 const RELATED_PAGE_ID_PROP = 'id'; 48 const RELATED_BACKLINKS_COUNT_PROP = 'backlinks'; 49 50 51 public static function getElementId() 52 { 53 return PluginUtility::PLUGIN_BASE_NAME . "_" . self::getElementName(); 54 } 55 56 57 /** 58 * Syntax Type. 59 * 60 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 61 * @see DokuWiki_Syntax_Plugin::getType() 62 */ 63 function getType() 64 { 65 return 'substition'; 66 } 67 68 /** 69 * @see DokuWiki_Syntax_Plugin::getPType() 70 */ 71 function getPType() 72 { 73 return 'block'; 74 } 75 76 /** 77 * @see Doku_Parser_Mode::getSort() 78 */ 79 function getSort() 80 { 81 return 100; 82 } 83 84 /** 85 * Create a pattern that will called this plugin 86 * 87 * @param string $mode 88 * @see Doku_Parser_Mode::connectTo() 89 */ 90 function connectTo($mode) 91 { 92 // The basic 93 $this->Lexer->addSpecialPattern('<' . self::getElementName() . '[^>]*>', $mode, 'plugin_' . PluginUtility::PLUGIN_BASE_NAME . '_' . $this->getPluginComponent()); 94 95 // To replace backlinks, you may add it in the configuration 96 $extraPattern = $this->getConf(self::EXTRA_PATTERN_CONF); 97 if ($extraPattern != "") { 98 $this->Lexer->addSpecialPattern($extraPattern, $mode, 'plugin_' . PluginUtility::PLUGIN_BASE_NAME . '_' . $this->getPluginComponent()); 99 } 100 101 } 102 103 /** 104 * 105 * The handle function goal is to parse the matched syntax through the pattern function 106 * and to return the result for use in the renderer 107 * This result is always cached until the page is modified. 108 * @param string $match 109 * @param int $state 110 * @param int $pos 111 * @param Doku_Handler $handler 112 * @return array|bool 113 * @see DokuWiki_Syntax_Plugin::handle() 114 * 115 */ 116 function handle($match, $state, $pos, Doku_Handler $handler) 117 { 118 119 switch ($state) { 120 121 // As there is only one call to connect to in order to a add a pattern, 122 // there is only one state entering the function 123 // but I leave it for better understanding of the process flow 124 case DOKU_LEXER_SPECIAL : 125 126 // Parse the parameters 127 $match = substr($match, strlen(self::getElementName()), -1); 128 $parameters=array(); 129 130 // /i not case sensitive 131 $attributePattern = "\\s*(\w+)\\s*=\\s*[\'\"]{1}([^\`\"]*)[\'\"]{1}\\s*"; 132 $result = preg_match_all('/' . $attributePattern . '/i', $match, $matches); 133 if ($result != 0) { 134 foreach ($matches[1] as $key => $parameterKey) { 135 $parameter = strtolower($parameterKey); 136 $value = $matches[2][$key]; 137 $parameters[$parameter] = $value; 138 } 139 } 140 // Cache the values 141 return array($state, $parameters); 142 143 } 144 145 // Cache the values 146 return array($state); 147 } 148 149 /** 150 * Render the output 151 * @param string $format 152 * @param Doku_Renderer $renderer 153 * @param array $data - what the function handle() return'ed 154 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 155 * @see DokuWiki_Syntax_Plugin::render() 156 * 157 * 158 */ 159 function render($format, Doku_Renderer $renderer, $data) 160 { 161 global $lang; 162 global $INFO; 163 global $ID; 164 165 $id = $ID; 166 // If it's a sidebar, get the original id. 167 if (isset($INFO)) { 168 $id = $INFO['id']; 169 } 170 171 if ($format == 'xhtml') { 172 173 $relatedPages = $this->related($id); 174 175 $renderer->doc .= '<div id="' . self::getElementId() . '" class="' . self::getElementName() . '-container">' . DOKU_LF; 176 177 if (empty($relatedPages)) { 178 179 // Dokuwiki debug 180 dbglog("No Backlinks", "Related plugins: all backlinks for page: $id"); 181 $renderer->doc .= "<strong>Plugin " . PluginUtility::PLUGIN_BASE_NAME . " - Component " . self::getElementName() . ": " . $lang['nothingfound'] . "</strong>" . DOKU_LF; 182 183 } else { 184 185 // Dokuwiki debug 186 dbglog($relatedPages, self::getElementName() . " plugins: all backlinks for page: $id"); 187 188 $renderer->doc .= '<ul>' . DOKU_LF; 189 190 foreach ($relatedPages as $backlink) { 191 $backlinkId = $backlink[self::RELATED_PAGE_ID_PROP]; 192 $name = TitleUtility::getPageTitle($backlinkId); 193 if (empty($name)) { 194 $name = $backlinkId; 195 } 196 $renderer->doc .= '<li>'; 197 if ($backlinkId != self::MORE_PAGE_ID) { 198 $renderer->doc .= html_wikilink(':' . $backlinkId, $name); 199 } else { 200 $renderer->doc .= 201 tpl_link( 202 wl($id) . '?do=backlink', 203 "More ...", 204 'class="" rel="nofollow" title="More..."', 205 $return = true 206 ); 207 } 208 $renderer->doc .= '</li>' . DOKU_LF; 209 } 210 211 $renderer->doc .= '</ul>' . DOKU_LF; 212 213 } 214 215 $renderer->doc .= '</div>' . DOKU_LF; 216 217 return true; 218 } 219 return false; 220 } 221 222 /** 223 * @param $id 224 * @param $max 225 * @return array 226 */ 227 public function related($id, $max = NULL) 228 { 229 if ($max == NULL) { 230 $max = $this->getConf(self::MAX_LINKS_CONF); 231 } 232 // Call the dokuwiki backlinks function 233 // @require_once(DOKU_INC . 'inc/fulltext.php'); 234 // Backlinks called the indexer, for more info 235 // See: https://www.dokuwiki.org/devel:metadata#metadata_index 236 $backlinks = ft_backlinks($id, $ignore_perms = false); 237 238 // To minimize the pressure on the index 239 // as we asks then the backlinks of the backlinks on the next step 240 if (sizeof($backlinks) > 50) { 241 $backlinks = array_slice($backlinks, 0, 50); 242 } 243 244 $related = array(); 245 foreach ($backlinks as $backlink) { 246 $page = array(); 247 $page[self::RELATED_PAGE_ID_PROP] = $backlink; 248 $page[self::RELATED_BACKLINKS_COUNT_PROP] = sizeof(ft_backlinks($backlink, $ignore_perms = false)); 249 $related[] = $page; 250 } 251 252 usort($related, function ($a, $b) { 253 return $b[self::RELATED_BACKLINKS_COUNT_PROP] - $a[self::RELATED_BACKLINKS_COUNT_PROP]; 254 }); 255 256 if (sizeof($related) > $max) { 257 $related = array_slice($related, 0, $max); 258 $page = array(); 259 $page[self::RELATED_PAGE_ID_PROP] = self::MORE_PAGE_ID; 260 $related[] = $page; 261 } 262 263 return $related; 264 265 } 266 267 public static function getElementName() 268 { 269 return "related"; 270 } 271 272 273} 274