1<?php
2/**
3 * DokuWiki Plugin pageredirect (Action 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
10// must be run within Dokuwiki
11if(!defined('DOKU_INC')) die();
12
13class action_plugin_pageredirect extends DokuWiki_Action_Plugin {
14    /**
15     * Registers a callback function for a given event
16     *
17     * @param Doku_Event_Handler $controller DokuWiki's event controller object
18     */
19    public function register(Doku_Event_Handler $controller) {
20        /* @see action_plugin_pageredirect::handle_dokuwiki_started() */
21        $controller->register_hook('DOKUWIKI_STARTED', 'BEFORE', $this, 'handle_dokuwiki_started');
22        /* @see action_plugin_pageredirect::handle_parser_metadata_render() */
23        $controller->register_hook('PARSER_METADATA_RENDER', 'BEFORE', $this, 'handle_parser_metadata_render');
24
25        // This plugin goes first, PR#555, requires dokuwiki 2014-05-05 (Ponder Stibbons)
26        /* @see action_plugin_pageredirect::handle_tpl_act_render() */
27        $controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'handle_tpl_act_render', null, PHP_INT_MIN);
28
29        $controller->register_hook('INDEXER_PAGE_ADD', 'BEFORE', $this, 'handle_indexer');
30    }
31
32    public function handle_dokuwiki_started(&$event, $param) {
33        global $ID, $ACT, $REV;
34
35        // skip when looking page history or action is not 'show'
36        if(($ACT != 'show' && $ACT != '') || $REV) {
37            return;
38        }
39
40        $metadata = $this->get_metadata($ID);
41
42        // return if no redirection data
43        if(!$metadata) {
44            return;
45        }
46        list($page, $is_external) = $metadata;
47
48        global $INPUT;
49        $redirect = $INPUT->get->str('redirect', '0');
50
51        // return if redirection is temporarily disabled,
52        // or we have been redirected 5 times in a row
53        if($redirect == 'no' || $redirect > 4) {
54            return;
55        }
56        $redirect = (int)$redirect+1;
57
58        // verify metadata currency
59        // FIXME: why
60        if(@filemtime(metaFN($ID, '.meta')) < @filemtime(wikiFN($ID))) {
61            throw new Exception('should not get here');
62            return;
63        }
64
65        // preserve #section from $page
66        list($page, $section) = explode('#', $page, 2);
67        if(isset($section)) {
68            $section = '#' . $section;
69        } else {
70            $section = '';
71        }
72
73        // prepare link for internal redirects, keep external targets
74        if(!$is_external) {
75            $page = wl($page, array('redirect' => $redirect), true, '&');
76
77            if($this->getConf('show_note')) {
78                $this->flash_message($ID);
79            }
80
81            // add anchor if not external redirect
82            $page .= $section;
83        }
84
85        $this->redirect($page);
86    }
87
88    public function handle_tpl_act_render(&$event, $param) {
89        global $ACT;
90
91        // handle on do=show
92        if($ACT != 'show' && $ACT != '') {
93            return true;
94        }
95
96        if($this->getConf('show_note')) {
97            $this->render_flash();
98        }
99
100        return true;
101    }
102
103    public function handle_parser_metadata_render(&$event, $param) {
104        if(isset($event->data->meta['relation'])) {
105            // FIXME: why is this needed here?!
106            unset($event->data->meta['relation']['isreplacedby']);
107        }
108    }
109
110    public function handle_indexer(Doku_Event $event, $param) {
111        $new_references = array();
112        foreach ($event->data['metadata']['relation_references'] as $target) {
113            $redirect_target = $this->get_metadata($target);
114
115            if ($redirect_target) {
116                list($page, $is_external) = $redirect_target;
117
118                if (!$is_external) {
119                    $new_references[] = $page;
120                }
121            }
122        }
123
124        if (count($new_references) > 0) {
125            $event->data['metadata']['relation_references'] = array_unique(array_merge($new_references, $event->data['metadata']['relation_references']));
126        }
127
128        // FIXME: if the currently indexed page contains a redirect, all pages pointing to it need a new backlink entry!
129        // Note that these entries need to be added for every source page separately.
130        // An alternative could be to force re-indexing of all source pages by removing their ".indexed" file but this will only happen when they are visited.
131    }
132
133    /**
134     * remember to show note about being redirected from another page
135     * @param string $ID page id from where the redirect originated
136     */
137    private function flash_message($ID) {
138        if(headers_sent()) {
139            // too late to do start session
140            // and following code requires session
141            return;
142        }
143
144        session_start();
145        $_SESSION[DOKU_COOKIE]['redirect'] = $ID;
146    }
147
148    /**
149     * show note about being redirected from another page
150     */
151    private function render_flash() {
152        global $INPUT;
153
154        $redirect = $INPUT->get->str('redirect');
155
156        // loop counter
157        if($redirect <= 0 || $redirect > 5) {
158            return;
159        }
160
161        $ID = isset($_SESSION[DOKU_COOKIE]['redirect']) ? $_SESSION[DOKU_COOKIE]['redirect'] : null;
162        if(!$ID) {
163            return;
164        }
165        unset($_SESSION[DOKU_COOKIE]['redirect']);
166
167        $page        = cleanID($ID);
168        $use_heading = useHeading('navigation') && p_get_first_heading($page);
169        $title       = hsc($use_heading ? p_get_first_heading($page) : $page);
170
171        $url  = wl($page, array('redirect' => 'no'), true, '&');
172        $link = '<a href="' . $url . '" class="wikilink1" title="' . $page . '">' . $title . '</a>';
173        echo '<div class="noteredirect">' . sprintf($this->getLang('redirected_from'), $link) . '</div><br/>';
174    }
175
176    private function get_metadata($ID) {
177        // make sure we always get current metadata, but simple cache logic (i.e. render when page is newer than metadata) is enough
178        $metadata = p_get_metadata($ID, 'relation isreplacedby', METADATA_RENDER_USING_SIMPLE_CACHE|METADATA_RENDER_UNLIMITED);
179
180        // legacy compat
181        if(is_string($metadata)) {
182            $metadata = array($metadata);
183        }
184
185        return $metadata;
186    }
187
188    /**
189     * Redirect to url.
190     * @param string $url
191     */
192    private function redirect($url) {
193        header("HTTP/1.1 301 Moved Permanently");
194        send_redirect($url);
195    }
196}
197