1007225e5Sgerardnico<?php 2007225e5Sgerardnico/** 3007225e5Sgerardnico * DokuWiki Syntax Plugin Related. 4007225e5Sgerardnico * 5007225e5Sgerardnico */ 6007225e5Sgerardnico 7*1fa8c418SNickeauuse ComboStrap\LinkUtility; 8ef295d81Sgerardnicouse ComboStrap\Page; 9007225e5Sgerardnicouse ComboStrap\PluginUtility; 10*1fa8c418SNickeauuse ComboStrap\TagAttributes; 11007225e5Sgerardnico 12007225e5Sgerardnico 13007225e5Sgerardnicorequire_once(DOKU_INC . 'inc/parserutils.php'); 14007225e5Sgerardnico 15007225e5Sgerardnico/** 16007225e5Sgerardnico * All DokuWiki plugins to extend the parser/rendering mechanism 17007225e5Sgerardnico * need to inherit from this class 18007225e5Sgerardnico * 19007225e5Sgerardnico * The name of the class must follow a pattern (don't change it) 20007225e5Sgerardnico * 21007225e5Sgerardnico * The index and the metadata key for backlinks is called 'relation_references' 22007225e5Sgerardnico * It's the key value that you need to pass in the {@link lookupKey} of the {@link \dokuwiki\Search\Indexer} 23007225e5Sgerardnico * 24007225e5Sgerardnico * Type of conf[index]/index: 25007225e5Sgerardnico * * page.idx (id of the page is the element number) 26007225e5Sgerardnico * * title 27007225e5Sgerardnico * * relation_references_w.idx - _w for words 28007225e5Sgerardnico * * relation_references_w.idx - _i for lines (index by lines) 29007225e5Sgerardnico * 30007225e5Sgerardnico * The index is a associative map by key 31007225e5Sgerardnico * 32007225e5Sgerardnico * 33007225e5Sgerardnico */ 34007225e5Sgerardnicoclass syntax_plugin_combo_related extends DokuWiki_Syntax_Plugin 35007225e5Sgerardnico{ 36007225e5Sgerardnico 37007225e5Sgerardnico 38007225e5Sgerardnico // Conf property key 39007225e5Sgerardnico const MAX_LINKS_CONF = 'maxLinks'; 40007225e5Sgerardnico // For when you come from another plugin (such as backlinks) and that you don't want to change the pattern on each page 41007225e5Sgerardnico const EXTRA_PATTERN_CONF = 'extra_pattern'; 42007225e5Sgerardnico 43007225e5Sgerardnico // This is a fake page ID that is added 44007225e5Sgerardnico // to the related page array when the number of backlinks is bigger than the max 45007225e5Sgerardnico // Poisoning object strategy 46007225e5Sgerardnico const MORE_PAGE_ID = 'related_more'; 47007225e5Sgerardnico 48007225e5Sgerardnico // The array key of an array of related page 49007225e5Sgerardnico const RELATED_PAGE_ID_PROP = 'id'; 50007225e5Sgerardnico const RELATED_BACKLINKS_COUNT_PROP = 'backlinks'; 51007225e5Sgerardnico 52007225e5Sgerardnico 53007225e5Sgerardnico /** 54007225e5Sgerardnico * Syntax Type. 55007225e5Sgerardnico * 56007225e5Sgerardnico * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 57007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::getType() 58007225e5Sgerardnico */ 59007225e5Sgerardnico function getType() 60007225e5Sgerardnico { 61007225e5Sgerardnico return 'substition'; 62007225e5Sgerardnico } 63007225e5Sgerardnico 64007225e5Sgerardnico /** 65007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::getPType() 66007225e5Sgerardnico */ 67007225e5Sgerardnico function getPType() 68007225e5Sgerardnico { 69007225e5Sgerardnico return 'block'; 70007225e5Sgerardnico } 71007225e5Sgerardnico 72007225e5Sgerardnico /** 73007225e5Sgerardnico * @see Doku_Parser_Mode::getSort() 74007225e5Sgerardnico */ 75007225e5Sgerardnico function getSort() 76007225e5Sgerardnico { 77007225e5Sgerardnico return 100; 78007225e5Sgerardnico } 79007225e5Sgerardnico 80007225e5Sgerardnico /** 81007225e5Sgerardnico * Create a pattern that will called this plugin 82007225e5Sgerardnico * 83007225e5Sgerardnico * @param string $mode 84007225e5Sgerardnico * @see Doku_Parser_Mode::connectTo() 85007225e5Sgerardnico */ 86007225e5Sgerardnico function connectTo($mode) 87007225e5Sgerardnico { 88007225e5Sgerardnico // The basic 89*1fa8c418SNickeau $this->Lexer->addSpecialPattern('<' . self::getTag() . '[^>]*>', $mode, 'plugin_' . PluginUtility::PLUGIN_BASE_NAME . '_' . $this->getPluginComponent()); 90007225e5Sgerardnico 91007225e5Sgerardnico // To replace backlinks, you may add it in the configuration 92007225e5Sgerardnico $extraPattern = $this->getConf(self::EXTRA_PATTERN_CONF); 93007225e5Sgerardnico if ($extraPattern != "") { 94007225e5Sgerardnico $this->Lexer->addSpecialPattern($extraPattern, $mode, 'plugin_' . PluginUtility::PLUGIN_BASE_NAME . '_' . $this->getPluginComponent()); 95007225e5Sgerardnico } 96007225e5Sgerardnico 97007225e5Sgerardnico } 98007225e5Sgerardnico 99007225e5Sgerardnico /** 100007225e5Sgerardnico * 101007225e5Sgerardnico * The handle function goal is to parse the matched syntax through the pattern function 102007225e5Sgerardnico * and to return the result for use in the renderer 103007225e5Sgerardnico * This result is always cached until the page is modified. 104007225e5Sgerardnico * @param string $match 105007225e5Sgerardnico * @param int $state 106007225e5Sgerardnico * @param int $pos 107007225e5Sgerardnico * @param Doku_Handler $handler 108007225e5Sgerardnico * @return array|bool 109007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::handle() 110007225e5Sgerardnico * 111007225e5Sgerardnico */ 112007225e5Sgerardnico function handle($match, $state, $pos, Doku_Handler $handler) 113007225e5Sgerardnico { 114007225e5Sgerardnico 115007225e5Sgerardnico switch ($state) { 116007225e5Sgerardnico 117007225e5Sgerardnico // As there is only one call to connect to in order to a add a pattern, 118007225e5Sgerardnico // there is only one state entering the function 119007225e5Sgerardnico // but I leave it for better understanding of the process flow 120007225e5Sgerardnico case DOKU_LEXER_SPECIAL : 121007225e5Sgerardnico 122007225e5Sgerardnico // Parse the parameters 123*1fa8c418SNickeau $match = substr($match, strlen(self::getTag()), -1); 124007225e5Sgerardnico $parameters = array(); 125007225e5Sgerardnico 126007225e5Sgerardnico // /i not case sensitive 127007225e5Sgerardnico $attributePattern = "\\s*(\w+)\\s*=\\s*[\'\"]{1}([^\`\"]*)[\'\"]{1}\\s*"; 128007225e5Sgerardnico $result = preg_match_all('/' . $attributePattern . '/i', $match, $matches); 129007225e5Sgerardnico if ($result != 0) { 130007225e5Sgerardnico foreach ($matches[1] as $key => $parameterKey) { 131007225e5Sgerardnico $parameter = strtolower($parameterKey); 132007225e5Sgerardnico $value = $matches[2][$key]; 133007225e5Sgerardnico $parameters[$parameter] = $value; 134007225e5Sgerardnico } 135007225e5Sgerardnico } 136007225e5Sgerardnico // Cache the values 137007225e5Sgerardnico return array($state, $parameters); 138007225e5Sgerardnico 139007225e5Sgerardnico } 140007225e5Sgerardnico 141007225e5Sgerardnico // Cache the values 142007225e5Sgerardnico return array($state); 143007225e5Sgerardnico } 144007225e5Sgerardnico 145007225e5Sgerardnico /** 146007225e5Sgerardnico * Render the output 147007225e5Sgerardnico * @param string $format 148007225e5Sgerardnico * @param Doku_Renderer $renderer 149007225e5Sgerardnico * @param array $data - what the function handle() return'ed 150007225e5Sgerardnico * @return boolean - rendered correctly? (however, returned value is not used at the moment) 151007225e5Sgerardnico * @see DokuWiki_Syntax_Plugin::render() 152007225e5Sgerardnico * 153007225e5Sgerardnico * 154007225e5Sgerardnico */ 155007225e5Sgerardnico function render($format, Doku_Renderer $renderer, $data) 156007225e5Sgerardnico { 157007225e5Sgerardnico global $lang; 158007225e5Sgerardnico global $INFO; 159007225e5Sgerardnico global $ID; 160007225e5Sgerardnico 161007225e5Sgerardnico $id = $ID; 162007225e5Sgerardnico // If it's a sidebar, get the original id. 163007225e5Sgerardnico if (isset($INFO)) { 164007225e5Sgerardnico $id = $INFO['id']; 165007225e5Sgerardnico } 166007225e5Sgerardnico 167007225e5Sgerardnico if ($format == 'xhtml') { 168007225e5Sgerardnico 169007225e5Sgerardnico $relatedPages = $this->related($id); 170*1fa8c418SNickeau $tagAttributes = TagAttributes::createEmpty(self::getTag()); 171*1fa8c418SNickeau $tagAttributes->addClassName("d-print-none"); 172*1fa8c418SNickeau $renderer->doc .= $tagAttributes->toHtmlEnterTag("div"); 173007225e5Sgerardnico 174007225e5Sgerardnico if (empty($relatedPages)) { 175007225e5Sgerardnico 176007225e5Sgerardnico // Dokuwiki debug 177007225e5Sgerardnico dbglog("No Backlinks", "Related plugins: all backlinks for page: $id"); 178*1fa8c418SNickeau $renderer->doc .= "<strong>Plugin " . PluginUtility::PLUGIN_BASE_NAME . " - Component " . self::getTag() . ": " . $lang['nothingfound'] . "</strong>" . DOKU_LF; 179007225e5Sgerardnico 180007225e5Sgerardnico } else { 181007225e5Sgerardnico 182007225e5Sgerardnico // Dokuwiki debug 183007225e5Sgerardnico 184007225e5Sgerardnico $renderer->doc .= '<ul>' . DOKU_LF; 185007225e5Sgerardnico 186007225e5Sgerardnico foreach ($relatedPages as $backlink) { 187007225e5Sgerardnico $backlinkId = $backlink[self::RELATED_PAGE_ID_PROP]; 188007225e5Sgerardnico $renderer->doc .= '<li>'; 189007225e5Sgerardnico if ($backlinkId != self::MORE_PAGE_ID) { 190*1fa8c418SNickeau $linkUtility = LinkUtility::createFromPageId($backlinkId); 191*1fa8c418SNickeau $renderer->doc .= $linkUtility->renderOpenTag($renderer); 192*1fa8c418SNickeau $renderer->doc .= ucfirst($linkUtility->getName()); 193*1fa8c418SNickeau $renderer->doc .= $linkUtility->renderClosingTag(); 194007225e5Sgerardnico } else { 195007225e5Sgerardnico $renderer->doc .= 196007225e5Sgerardnico tpl_link( 197007225e5Sgerardnico wl($id) . '?do=backlink', 198007225e5Sgerardnico "More ...", 199007225e5Sgerardnico 'class="" rel="nofollow" title="More..."', 200007225e5Sgerardnico $return = true 201007225e5Sgerardnico ); 202007225e5Sgerardnico } 203007225e5Sgerardnico $renderer->doc .= '</li>' . DOKU_LF; 204007225e5Sgerardnico } 205007225e5Sgerardnico 206007225e5Sgerardnico $renderer->doc .= '</ul>' . DOKU_LF; 207007225e5Sgerardnico 208007225e5Sgerardnico } 209007225e5Sgerardnico 210007225e5Sgerardnico $renderer->doc .= '</div>' . DOKU_LF; 211007225e5Sgerardnico 212007225e5Sgerardnico return true; 213007225e5Sgerardnico } 214007225e5Sgerardnico return false; 215007225e5Sgerardnico } 216007225e5Sgerardnico 217007225e5Sgerardnico /** 218007225e5Sgerardnico * @param $id 219007225e5Sgerardnico * @param $max 220007225e5Sgerardnico * @return array 221007225e5Sgerardnico */ 222*1fa8c418SNickeau public function related($id, $max = NULL): array 223007225e5Sgerardnico { 224007225e5Sgerardnico if ($max == NULL) { 225007225e5Sgerardnico $max = $this->getConf(self::MAX_LINKS_CONF); 226007225e5Sgerardnico } 227007225e5Sgerardnico // Call the dokuwiki backlinks function 228007225e5Sgerardnico // @require_once(DOKU_INC . 'inc/fulltext.php'); 229007225e5Sgerardnico // Backlinks called the indexer, for more info 230007225e5Sgerardnico // See: https://www.dokuwiki.org/devel:metadata#metadata_index 231007225e5Sgerardnico $backlinks = ft_backlinks($id, $ignore_perms = false); 232007225e5Sgerardnico 233007225e5Sgerardnico // To minimize the pressure on the index 234007225e5Sgerardnico // as we asks then the backlinks of the backlinks on the next step 235007225e5Sgerardnico if (sizeof($backlinks) > 50) { 236007225e5Sgerardnico $backlinks = array_slice($backlinks, 0, 50); 237007225e5Sgerardnico } 238007225e5Sgerardnico 239007225e5Sgerardnico $related = array(); 240007225e5Sgerardnico foreach ($backlinks as $backlink) { 241007225e5Sgerardnico $page = array(); 242007225e5Sgerardnico $page[self::RELATED_PAGE_ID_PROP] = $backlink; 243007225e5Sgerardnico $page[self::RELATED_BACKLINKS_COUNT_PROP] = sizeof(ft_backlinks($backlink, $ignore_perms = false)); 244007225e5Sgerardnico $related[] = $page; 245007225e5Sgerardnico } 246007225e5Sgerardnico 247007225e5Sgerardnico usort($related, function ($a, $b) { 248007225e5Sgerardnico return $b[self::RELATED_BACKLINKS_COUNT_PROP] - $a[self::RELATED_BACKLINKS_COUNT_PROP]; 249007225e5Sgerardnico }); 250007225e5Sgerardnico 251007225e5Sgerardnico if (sizeof($related) > $max) { 252007225e5Sgerardnico $related = array_slice($related, 0, $max); 253007225e5Sgerardnico $page = array(); 254007225e5Sgerardnico $page[self::RELATED_PAGE_ID_PROP] = self::MORE_PAGE_ID; 255007225e5Sgerardnico $related[] = $page; 256007225e5Sgerardnico } 257007225e5Sgerardnico 258007225e5Sgerardnico return $related; 259007225e5Sgerardnico 260007225e5Sgerardnico } 261007225e5Sgerardnico 262*1fa8c418SNickeau public static function getTag(): string 263007225e5Sgerardnico { 264007225e5Sgerardnico return "related"; 265007225e5Sgerardnico } 266007225e5Sgerardnico 267007225e5Sgerardnico 268007225e5Sgerardnico} 269