xref: /plugin/combo/syntax/related.php (revision e55e4e71208cf11ca8c608d736f6c1a20fb4c5c5)
1<?php
2/**
3 * DokuWiki Syntax Plugin Related.
4 *
5 */
6
7use ComboStrap\Page;
8use ComboStrap\PluginUtility;
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 class="' . self::getElementName() . '-container d-print-none">' . 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                    $backlinkPage = new Page($backlinkId);
193                    $name = $backlinkPage->getH1();
194                    /**
195                     * Hack, moving title to h1
196                     * in case if the page was not still analyzed
197                     * the h1 is saved in title by dokuwiki
198                     */
199                    if (empty($name)) {
200                        $name = $backlinkPage->getTitle();
201                        $name = substr($name, 0, 50);
202                    }
203                    $renderer->doc .= '<li>';
204                    if ($backlinkId != self::MORE_PAGE_ID) {
205                        $renderer->doc .= html_wikilink(':' . $backlinkId, $name);
206                    } else {
207                        $renderer->doc .=
208                            tpl_link(
209                                wl($id) . '?do=backlink',
210                                "More ...",
211                                'class="" rel="nofollow" title="More..."',
212                                $return = true
213                            );
214                    }
215                    $renderer->doc .= '</li>' . DOKU_LF;
216                }
217
218                $renderer->doc .= '</ul>' . DOKU_LF;
219
220            }
221
222            $renderer->doc .= '</div>' . DOKU_LF;
223
224            return true;
225        }
226        return false;
227    }
228
229    /**
230     * @param $id
231     * @param $max
232     * @return array
233     */
234    public function related($id, $max = NULL)
235    {
236        if ($max == NULL) {
237            $max = $this->getConf(self::MAX_LINKS_CONF);
238        }
239        // Call the dokuwiki backlinks function
240        // @require_once(DOKU_INC . 'inc/fulltext.php');
241        // Backlinks called the indexer, for more info
242        // See: https://www.dokuwiki.org/devel:metadata#metadata_index
243        $backlinks = ft_backlinks($id, $ignore_perms = false);
244
245        // To minimize the pressure on the index
246        // as we asks then the backlinks of the backlinks on the next step
247        if (sizeof($backlinks) > 50) {
248            $backlinks = array_slice($backlinks, 0, 50);
249        }
250
251        $related = array();
252        foreach ($backlinks as $backlink) {
253            $page = array();
254            $page[self::RELATED_PAGE_ID_PROP] = $backlink;
255            $page[self::RELATED_BACKLINKS_COUNT_PROP] = sizeof(ft_backlinks($backlink, $ignore_perms = false));
256            $related[] = $page;
257        }
258
259        usort($related, function ($a, $b) {
260            return $b[self::RELATED_BACKLINKS_COUNT_PROP] - $a[self::RELATED_BACKLINKS_COUNT_PROP];
261        });
262
263        if (sizeof($related) > $max) {
264            $related = array_slice($related, 0, $max);
265            $page = array();
266            $page[self::RELATED_PAGE_ID_PROP] = self::MORE_PAGE_ID;
267            $related[] = $page;
268        }
269
270        return $related;
271
272    }
273
274    public static function getElementName()
275    {
276        return "related";
277    }
278
279
280}
281