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