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