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 // Handle move plugin 32 $controller->register_hook('PLUGIN_MOVE_HANDLERS_REGISTER', 'BEFORE', $this, 'handle_move_register'); 33 } 34 35 public function handle_dokuwiki_started(&$event, $param) { 36 global $ID, $ACT, $REV; 37 38 // skip when looking page history or action is not 'show' 39 if(($ACT != 'show' && $ACT != '') || $REV) { 40 return; 41 } 42 43 $metadata = $this->get_metadata($ID); 44 45 // return if no redirection data 46 if(!$metadata) { 47 return; 48 } 49 list($page, $is_external) = $metadata; 50 51 // return if external redirect is not allowed 52 if($is_external && !$this->getConf('allow_external')) { 53 return; 54 } 55 56 global $INPUT; 57 $redirect = $INPUT->get->str('redirect', '0'); 58 59 // return if redirection is temporarily disabled, 60 // or we have been redirected 5 times in a row 61 if($redirect == 'no' || $redirect > 4) { 62 return; 63 } 64 $redirect = (int)$redirect+1; 65 66 // verify metadata currency 67 // FIXME: why 68 if(@filemtime(metaFN($ID, '.meta')) < @filemtime(wikiFN($ID))) { 69 throw new Exception('should not get here'); 70 return; 71 } 72 73 // preserve #section from $page 74 list($page, $section) = array_pad(explode('#', $page, 2), 2, null); 75 if(isset($section)) { 76 $section = '#' . $section; 77 } else { 78 $section = ''; 79 } 80 81 // prepare link for internal redirects, keep external targets 82 if(!$is_external) { 83 $page = wl($page, array('redirect' => $redirect), true, '&'); 84 85 if($this->getConf('show_note')) { 86 $this->flash_message($ID); 87 } 88 89 // add anchor if not external redirect 90 $page .= $section; 91 } 92 93 $this->redirect($page); 94 } 95 96 public function handle_tpl_act_render(&$event, $param) { 97 global $ACT; 98 99 // handle on do=show 100 if($ACT != 'show' && $ACT != '') { 101 return true; 102 } 103 104 if($this->getConf('show_note')) { 105 $this->render_flash(); 106 } 107 108 return true; 109 } 110 111 public function handle_parser_metadata_render(&$event, $param) { 112 if(isset($event->data->meta['relation'])) { 113 // FIXME: why is this needed here?! 114 unset($event->data->meta['relation']['isreplacedby']); 115 } 116 } 117 118 public function handle_indexer(Doku_Event $event, $param) { 119 $new_references = array(); 120 foreach ($event->data['metadata']['relation_references'] as $target) { 121 $redirect_target = $this->get_metadata($target); 122 123 if ($redirect_target) { 124 list($page, $is_external) = $redirect_target; 125 126 if (!$is_external) { 127 $new_references[] = $page; 128 } 129 } 130 } 131 132 if (count($new_references) > 0) { 133 $event->data['metadata']['relation_references'] = array_unique(array_merge($new_references, $event->data['metadata']['relation_references'])); 134 } 135 136 // FIXME: if the currently indexed page contains a redirect, all pages pointing to it need a new backlink entry! 137 // Note that these entries need to be added for every source page separately. 138 // 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. 139 } 140 141 /** 142 * remember to show note about being redirected from another page 143 * @param string $ID page id from where the redirect originated 144 */ 145 private function flash_message($ID) { 146 if(headers_sent()) { 147 // too late to do start session 148 // and following code requires session 149 return; 150 } 151 152 session_start(); 153 $_SESSION[DOKU_COOKIE]['redirect'] = $ID; 154 } 155 156 /** 157 * show note about being redirected from another page 158 */ 159 private function render_flash() { 160 global $INPUT; 161 162 $redirect = $INPUT->get->str('redirect'); 163 164 // loop counter 165 if($redirect <= 0 || $redirect > 5) { 166 return; 167 } 168 169 $ID = isset($_SESSION[DOKU_COOKIE]['redirect']) ? $_SESSION[DOKU_COOKIE]['redirect'] : null; 170 if(!$ID) { 171 return; 172 } 173 unset($_SESSION[DOKU_COOKIE]['redirect']); 174 175 $page = cleanID($ID); 176 $use_heading = useHeading('navigation') && p_get_first_heading($page); 177 $title = hsc($use_heading ? p_get_first_heading($page) : $page); 178 179 $url = wl($page, array('redirect' => 'no'), true, '&'); 180 $link = '<a href="' . $url . '" class="wikilink1" title="' . $page . '">' . $title . '</a>'; 181 echo '<div class="noteredirect">' . sprintf($this->getLang('redirected_from'), $link) . '</div><br/>'; 182 } 183 184 private function get_metadata($ID) { 185 // make sure we always get current metadata, but simple cache logic (i.e. render when page is newer than metadata) is enough 186 $metadata = p_get_metadata($ID, 'relation isreplacedby', METADATA_RENDER_USING_SIMPLE_CACHE|METADATA_RENDER_UNLIMITED); 187 188 // legacy compat 189 if(is_string($metadata)) { 190 $metadata = array($metadata); 191 } 192 193 return $metadata; 194 } 195 196 /** 197 * Redirect to url. 198 * @param string $url 199 */ 200 private function redirect($url) { 201 header("HTTP/1.1 301 Moved Permanently"); 202 send_redirect($url); 203 } 204 205 public function handle_move_register(Doku_Event $event, $params) { 206 $event->data['handlers']['pageredirect'] = array($this, 'rewrite_redirect'); 207 } 208 209 public function rewrite_redirect($match, $state, $pos, $plugin, helper_plugin_move_handler $handler) { 210 $metadata = $this->get_metadata($ID); 211 if ($metadata[1]) return $match; // Fail-safe for external redirection (Do not rewrite) 212 213 $match = trim($match); 214 215 if (substr($match, 0, 1) == "~") { 216 // "~~REDIRECT>pagename#anchor~~" pattern 217 218 // Strip syntax 219 $match = substr($match, 2, strlen($match) - 4); 220 221 list($syntax, $src, $anchor) = array_pad(preg_split("/>|#/", $match), 3, ""); 222 223 // Resolve new source. 224 if (method_exists($handler, 'adaptRelativeId')) { 225 $new_src = $handler->adaptRelativeId($src); 226 } else { 227 $new_src = $handler->resolveMoves($src, 'page'); 228 $new_src = $handler->relativeLink($src, $new_src, 'page'); 229 } 230 231 $result = "~~".$syntax.">".$new_src; 232 if (!empty($anchor)) $result .= "#".$anchor; 233 $result .= "~~"; 234 235 return $result; 236 237 } else if (substr($match, 0, 1) == "#") { 238 // "#REDIRECT pagename#anchor" pattern 239 240 // Strip syntax 241 $match = substr($match, 1); 242 243 list($syntax, $src, $anchor) = array_pad(preg_split("/ |#/", $match), 3, ""); 244 245 // Resolve new source. 246 if (method_exists($handler, 'adaptRelativeId')) { 247 $new_src = $handler->adaptRelativeId($src); 248 } else { 249 $new_src = $handler->resolveMoves($src, 'page'); 250 $new_src = $handler->relativeLink($src, $new_src, 'page'); 251 } 252 253 $result = "\n#".$syntax." ".$new_src; 254 if (!empty($anchor)) $result .= "#".$anchor; 255 256 return $result; 257 } 258 259 // Fail-safe 260 return $match; 261 262 } 263} 264