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