1<?php
2/**
3 * DokuWiki Plugin pageredirect (Syntax Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Elan Ruusamäe <glen@delfi.ee>
7 * @author  David Lorentsen <zyberdog@quakenet.org>
8 */
9
10use dokuwiki\File\PageResolver;
11
12// must be run within Dokuwiki
13if(!defined('DOKU_INC')) die();
14
15class syntax_plugin_pageredirect extends DokuWiki_Syntax_Plugin {
16    /**
17     * @return string Syntax mode type
18     */
19    public function getType() {
20        return 'substition';
21    }
22
23    /**
24     * @return string Paragraph type
25     */
26    public function getPType() {
27        return 'block';
28    }
29
30    /**
31     * @return int Sort order - Low numbers go before high numbers
32     */
33    public function getSort() {
34        return 1;
35    }
36
37    /**
38     * Connect lookup pattern to lexer.
39     *
40     * @param string $mode Parser mode
41     */
42    public function connectTo($mode) {
43        // NOTE: each document is surrounted with \n by dokuwiki parser
44        // so it's safe to use \n in the pattern
45        // this fixes creole greedyness:
46        // https://github.com/glensc/dokuwiki-plugin-pageredirect/issues/18#issuecomment-249386268
47        $this->Lexer->addSpecialPattern('(?:~~REDIRECT>.+?~~|\n#(?i:redirect) [^\r\n]+)', $mode, 'plugin_pageredirect');
48    }
49
50    /**
51     * Handler to prepare matched data for the rendering process
52     *
53     * This function can only pass data to render() via its return value
54     * render() may be not be run during the object's current life.
55     *
56     * Usually you should only need the $match param.
57     *
58     * @param   string       $match   The text matched by the patterns
59     * @param   int          $state   The lexer state for the match
60     * @param   int          $pos     The character position of the matched text
61     * @param   Doku_Handler $handler Reference to the Doku_Handler object
62     * @return  array Return an array with all data you want to use in render
63     */
64    public function handle($match, $state, $pos, Doku_Handler $handler) {
65        // strip leading "\n" from creole-compatible pattern
66        $match = trim($match);
67
68        // extract target page from match pattern
69        if($match[0] == '#') {
70            # #REDIRECT PAGE
71            $id = substr($match, 10);
72        } else {
73            # ~~REDIRECT>PAGE~~
74            $id = substr($match, 11, -2);
75        }
76        $id = trim($id);
77
78        $is_external = preg_match('#^https?://#i', $id);
79
80        // resolve and clean the $id if it is not external
81        if(!$is_external) {
82            global $ID;
83            if (class_exists('dokuwiki\File\PageResolver')) {
84                $resolver = new PageResolver($ID);
85                $id = $resolver->resolveId($id);
86            } else {
87                $exists = null;
88                resolve_pageid(getNS($ID), $id, $exists);
89            }
90        }
91
92        return array($id, $is_external);
93    }
94
95    /**
96     * Handles the actual output creation.
97     *
98     * The function must not assume any other of the classes methods have been run
99     * during the object's current life. The only reliable data it receives are its
100     * parameters.
101     *
102     * The function should always check for the given output format and return false
103     * when a format isn't supported.
104     *
105     * $renderer contains a reference to the renderer object which is
106     * currently handling the rendering. You need to use it for writing
107     * the output. How this is done depends on the renderer used (specified
108     * by $format
109     *
110     * The contents of the $data array depends on what the handler() function above
111     * created
112     *
113     * @param string        $format   output format being rendered
114     * @param Doku_Renderer $renderer reference to the current renderer object
115     * @param array         $data     data created by handler()
116     * @return boolean return true if rendered correctly
117     */
118    public function render($format, Doku_Renderer $renderer, $data) {
119        if($format == 'xhtml') {
120            // add prepared note about redirection
121            $renderer->doc .= $this->redirect_message($data);
122
123            // hook into the post render event to allow the page to be redirected
124            global $EVENT_HANDLER;
125            /** @var action_plugin_pageredirect $action */
126            $action = plugin_load('action', 'pageredirect');
127            $EVENT_HANDLER->register_hook('TPL_ACT_RENDER', 'AFTER', $action, 'handle_dokuwiki_started');
128
129            return true;
130        }
131
132        if($format == 'metadata') {
133            // add redirection to metadata
134            $renderer->meta['relation']['isreplacedby'] = $data;
135            if (!$data[1]) {
136                $page = preg_split("/\?|#/", $data[0])[0];
137                $renderer->meta['relation']['references'][$page] = page_exists($page);
138            }
139
140            return true;
141        }
142
143        return false;
144    }
145
146    /**
147     * Create redirection message html
148     *
149     * @param string $page
150     * @return string
151     */
152    private function redirect_message($metadata) {
153        list($page, $is_external) = $metadata;
154        if(!$is_external) {
155            $link = html_wikilink($page);
156        } else {
157            $link = '<a href="' . hsc($page) . '" class="urlextern">' . hsc($page) . '</a>';
158        }
159
160        // prepare message here instead of in render
161        $message = '<div class="noteredirect">' . sprintf($this->getLang('redirect_to'), $link) . '</div>';
162
163        return $message;
164    }
165}
166