xref: /plugin/combo/syntax/related.php (revision 1fa8c418ed5809db58049141be41b7738471dd32)
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