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