summaryrefslogtreecommitdiff
path: root/platform/www/lib/plugins/pageredirect/action.php
blob: 735a89c8e980e661d70703d1276a2a715ba5cdc4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
<?php
/**
 * DokuWiki Plugin pageredirect (Action Component)
 *
 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
 * @author  Elan Ruusamäe <glen@delfi.ee>
 * @author  David Lorentsen <zyberdog@quakenet.org>
 */

// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();

class action_plugin_pageredirect extends DokuWiki_Action_Plugin {
    /**
     * Registers a callback function for a given event
     *
     * @param Doku_Event_Handler $controller DokuWiki's event controller object
     */
    public function register(Doku_Event_Handler $controller) {
        /* @see action_plugin_pageredirect::handle_dokuwiki_started() */
        $controller->register_hook('DOKUWIKI_STARTED', 'BEFORE', $this, 'handle_dokuwiki_started');
        /* @see action_plugin_pageredirect::handle_parser_metadata_render() */
        $controller->register_hook('PARSER_METADATA_RENDER', 'BEFORE', $this, 'handle_parser_metadata_render');

        // This plugin goes first, PR#555, requires dokuwiki 2014-05-05 (Ponder Stibbons)
        /* @see action_plugin_pageredirect::handle_tpl_act_render() */
        $controller->register_hook('TPL_ACT_RENDER', 'BEFORE', $this, 'handle_tpl_act_render', null, PHP_INT_MIN);

        $controller->register_hook('INDEXER_PAGE_ADD', 'BEFORE', $this, 'handle_indexer');

        // Handle move plugin
        $controller->register_hook('PLUGIN_MOVE_HANDLERS_REGISTER', 'BEFORE', $this, 'handle_move_register');

    }

    public function handle_dokuwiki_started(&$event, $param) {
        global $ID, $ACT, $REV;

        // skip when looking page history or action is not 'show'
        if(($ACT != 'show' && $ACT != '') || $REV) {
            return;
        }

        $metadata = $this->get_metadata($ID);

        // return if no redirection data
        if(!$metadata) {
            return;
        }
        list($page, $is_external) = $metadata;

        global $INPUT;
        $redirect = $INPUT->get->str('redirect', '0');

        // return if redirection is temporarily disabled,
        // or we have been redirected 5 times in a row
        if($redirect == 'no' || $redirect > 4) {
            return;
        }
        $redirect = (int)$redirect+1;

        // verify metadata currency
        // FIXME: why
        if(@filemtime(metaFN($ID, '.meta')) < @filemtime(wikiFN($ID))) {
            throw new Exception('should not get here');
            return;
        }

        // preserve #section from $page
        list($page, $section) = explode('#', $page, 2);
        if(isset($section)) {
            $section = '#' . $section;
        } else {
            $section = '';
        }

        // prepare link for internal redirects, keep external targets
        if(!$is_external) {
            $page = wl($page, array('redirect' => $redirect), true, '&');

            if($this->getConf('show_note')) {
                $this->flash_message($ID);
            }

            // add anchor if not external redirect
            $page .= $section;
        }

        $this->redirect($page);
    }

    public function handle_tpl_act_render(&$event, $param) {
        global $ACT;

        // handle on do=show
        if($ACT != 'show' && $ACT != '') {
            return true;
        }

        if($this->getConf('show_note')) {
            $this->render_flash();
        }

        return true;
    }

    public function handle_parser_metadata_render(&$event, $param) {
        if(isset($event->data->meta['relation'])) {
            // FIXME: why is this needed here?!
            unset($event->data->meta['relation']['isreplacedby']);
        }
    }

    public function handle_indexer(Doku_Event $event, $param) {
        $new_references = array();
        foreach ($event->data['metadata']['relation_references'] as $target) {
            $redirect_target = $this->get_metadata($target);

            if ($redirect_target) {
                list($page, $is_external) = $redirect_target;

                if (!$is_external) {
                    $new_references[] = $page;
                }
            }
        }

        if (count($new_references) > 0) {
            $event->data['metadata']['relation_references'] = array_unique(array_merge($new_references, $event->data['metadata']['relation_references']));
        }

        // FIXME: if the currently indexed page contains a redirect, all pages pointing to it need a new backlink entry!
        // Note that these entries need to be added for every source page separately.
        // 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.
    }

    /**
     * remember to show note about being redirected from another page
     * @param string $ID page id from where the redirect originated
     */
    private function flash_message($ID) {
        if(headers_sent()) {
            // too late to do start session
            // and following code requires session
            return;
        }

        session_start();
        $_SESSION[DOKU_COOKIE]['redirect'] = $ID;
    }

    /**
     * show note about being redirected from another page
     */
    private function render_flash() {
        global $INPUT;

        $redirect = $INPUT->get->str('redirect');

        // loop counter
        if($redirect <= 0 || $redirect > 5) {
            return;
        }

        $ID = isset($_SESSION[DOKU_COOKIE]['redirect']) ? $_SESSION[DOKU_COOKIE]['redirect'] : null;
        if(!$ID) {
            return;
        }
        unset($_SESSION[DOKU_COOKIE]['redirect']);

        $page        = cleanID($ID);
        $use_heading = useHeading('navigation') && p_get_first_heading($page);
        $title       = hsc($use_heading ? p_get_first_heading($page) : $page);

        $url  = wl($page, array('redirect' => 'no'), true, '&');
        $link = '<a href="' . $url . '" class="wikilink1" title="' . $page . '">' . $title . '</a>';
        echo '<div class="noteredirect">' . sprintf($this->getLang('redirected_from'), $link) . '</div><br/>';
    }

    private function get_metadata($ID) {
        // make sure we always get current metadata, but simple cache logic (i.e. render when page is newer than metadata) is enough
        $metadata = p_get_metadata($ID, 'relation isreplacedby', METADATA_RENDER_USING_SIMPLE_CACHE|METADATA_RENDER_UNLIMITED);

        // legacy compat
        if(is_string($metadata)) {
            $metadata = array($metadata);
        }

        return $metadata;
    }

    /**
     * Redirect to url.
     * @param string $url
     */
    private function redirect($url) {
        header("HTTP/1.1 301 Moved Permanently");
        send_redirect($url);
    }
    
    public function handle_move_register(Doku_Event $event, $params) {
        $event->data['handlers']['pageredirect'] = array($this, 'rewrite_redirect');
    }

    public function rewrite_redirect($match, $state, $pos, $plugin, helper_plugin_move_handler $handler) {

        $metadata = $this->get_metadata($ID);
        if ($metadata[1]) return $match;  // Fail-safe for external redirection (Do not rewrite)

        $match = trim($match);

        if (substr($match, 0, 1) == "~") {
            // "~~REDIRECT>pagename#anchor~~" pattern
            
            // Strip syntax
            $match = substr($match, 2, strlen($match) - 4);

            list($syntax, $src, $anchor) = array_pad(preg_split("/>|#/", $match), 3, "");

            // Resolve new source.
            if (method_exists($handler, 'adaptRelativeId')) {
                $new_src = $handler->adaptRelativeId($src);
            } else {
                $new_src = $handler->resolveMoves($src, 'page');
                $new_src = $handler->relativeLink($src, $new_src, 'page');
            }

            $result = "~~".$syntax.">".$new_src;
            if (!empty($anchor)) $result .= "#".$anchor;
            $result .= "~~";

            return $result;

        } else if (substr($match, 0, 1) == "#") {
            // "#REDIRECT pagename#anchor" pattern

            // Strip syntax
            $match = substr($match, 1);

            list($syntax, $src, $anchor) = array_pad(preg_split("/ |#/", $match), 3, "");

            // Resolve new source.
            if (method_exists($handler, 'adaptRelativeId')) {
                $new_src = $handler->adaptRelativeId($src);
            } else {
                $new_src = $handler->resolveMoves($src, 'page');
                $new_src = $handler->relativeLink($src, $new_src, 'page');
            }

            $result = "\n#".$syntax." ".$new_src;
            if (!empty($anchor)) $result .= "#".$anchor;

            return $result;
        }

        // Fail-safe
        return $match;

    }
}