1<?php 2/** 3 * Include Plugin: Display a wiki page within another wiki page 4 * 5 * Action plugin component, for cache validity determination 6 * 7 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 8 * @author Christopher Smith <chris@jalakai.co.uk> 9 * @author Michael Klier <chi@chimeric.de> 10 */ 11 12/** 13 * All DokuWiki plugins to extend the parser/rendering mechanism 14 * need to inherit from this class 15 */ 16class action_plugin_include extends DokuWiki_Action_Plugin { 17 18 /* @var helper_plugin_include $helper */ 19 var $helper = null; 20 21 function __construct() { 22 $this->helper = plugin_load('helper', 'include'); 23 } 24 25 /** 26 * plugin should use this method to register its handlers with the dokuwiki's event controller 27 */ 28 function register(Doku_Event_Handler $controller) { 29 /* @var Doku_event_handler $controller */ 30 $controller->register_hook('INDEXER_PAGE_ADD', 'BEFORE', $this, 'handle_indexer'); 31 $controller->register_hook('INDEXER_VERSION_GET', 'BEFORE', $this, 'handle_indexer_version'); 32 $controller->register_hook('PARSER_CACHE_USE','BEFORE', $this, '_cache_prepare'); 33 $controller->register_hook('HTML_EDITFORM_OUTPUT', 'BEFORE', $this, 'handle_form'); 34 $controller->register_hook('HTML_CONFLICTFORM_OUTPUT', 'BEFORE', $this, 'handle_form'); 35 $controller->register_hook('HTML_DRAFTFORM_OUTPUT', 'BEFORE', $this, 'handle_form'); 36 $controller->register_hook('ACTION_SHOW_REDIRECT', 'BEFORE', $this, 'handle_redirect'); 37 $controller->register_hook('PARSER_HANDLER_DONE', 'BEFORE', $this, 'handle_parser'); 38 $controller->register_hook('PARSER_METADATA_RENDER', 'AFTER', $this, 'handle_metadata'); 39 $controller->register_hook('HTML_SECEDIT_BUTTON', 'BEFORE', $this, 'handle_secedit_button'); 40 $controller->register_hook('PLUGIN_MOVE_HANDLERS_REGISTER', 'BEFORE', $this, 'handle_move_register'); 41 } 42 43 /** 44 * Add a version string to the index so it is rebuilt 45 * whenever the handler is updated or the safeindex setting is changed 46 */ 47 public function handle_indexer_version($event, $param) { 48 $event->data['plugin_include'] = '0.1.safeindex='.$this->getConf('safeindex'); 49 } 50 51 /** 52 * Handles the INDEXER_PAGE_ADD event, prevents indexing of metadata from included pages that aren't public if enabled 53 * 54 * @param Doku_Event $event the event object 55 * @param array $params optional parameters (unused) 56 */ 57 public function handle_indexer(Doku_Event $event, $params) { 58 global $USERINFO; 59 60 // check if the feature is enabled at all 61 if (!$this->getConf('safeindex')) return; 62 63 // is there a user logged in at all? If not everything is fine already 64 if (is_null($USERINFO) && !isset($_SERVER['REMOTE_USER'])) return; 65 66 // get the include metadata in order to see which pages were included 67 $inclmeta = p_get_metadata($event->data['page'], 'plugin_include', METADATA_RENDER_UNLIMITED); 68 $all_public = true; // are all included pages public? 69 // check if the current metadata indicates that non-public pages were included 70 if ($inclmeta !== null && isset($inclmeta['pages'])) { 71 foreach ($inclmeta['pages'] as $page) { 72 if (auth_aclcheck($page['id'], '', array()) < AUTH_READ) { // is $page public? 73 $all_public = false; 74 break; 75 } 76 } 77 } 78 79 if (!$all_public) { // there were non-public pages included - action required! 80 // backup the user information 81 $userinfo_backup = $USERINFO; 82 $remote_user = $_SERVER['REMOTE_USER']; 83 // unset user information - temporary logoff! 84 $USERINFO = null; 85 unset($_SERVER['REMOTE_USER']); 86 87 // metadata is only rendered once for a page in one request - thus we need to render manually. 88 $meta = p_read_metadata($event->data['page']); // load the original metdata 89 $meta = p_render_metadata($event->data['page'], $meta); // render the metadata 90 p_save_metadata($event->data['page'], $meta); // save the metadata so other event handlers get the public metadata, too 91 92 $meta = $meta['current']; // we are only interested in current metadata. 93 94 // check if the tag plugin handler has already been called before the include plugin 95 $tag_called = isset($event->data['metadata']['subject']); 96 97 // Reset the metadata in the renderer. This removes data from all other event handlers, but we need to be on the safe side here. 98 $event->data['metadata'] = array('title' => $meta['title']); 99 100 // restore the relation references metadata 101 if (isset($meta['relation']['references'])) { 102 $event->data['metadata']['relation_references'] = array_keys($meta['relation']['references']); 103 } else { 104 $event->data['metadata']['relation_references'] = array(); 105 } 106 107 // restore the tag metadata if the tag plugin handler has been called before the include plugin handler. 108 if ($tag_called) { 109 $tag_helper = $this->loadHelper('tag', false); 110 if ($tag_helper) { 111 if (isset($meta['subject'])) { 112 $event->data['metadata']['subject'] = $tag_helper->_cleanTagList($meta['subject']); 113 } else { 114 $event->data['metadata']['subject'] = array(); 115 } 116 } 117 } 118 119 // restore user information 120 $USERINFO = $userinfo_backup; 121 $_SERVER['REMOTE_USER'] = $remote_user; 122 } 123 } 124 125 /** 126 * Used for debugging purposes only 127 */ 128 function handle_metadata(&$event, $param) { 129 global $conf; 130 if($conf['allowdebug'] && $this->getConf('debugoutput')) { 131 dbglog('---- PLUGIN INCLUDE META DATA START ----'); 132 dbglog($event->data); 133 dbglog('---- PLUGIN INCLUDE META DATA END ----'); 134 } 135 } 136 137 /** 138 * Supplies the current section level to the include syntax plugin 139 * 140 * @author Michael Klier <chi@chimeric.de> 141 * @author Michael Hamann <michael@content-space.de> 142 */ 143 function handle_parser(Doku_Event $event, $param) { 144 global $ID; 145 146 $level = 0; 147 $ins =& $event->data->calls; 148 $num = count($ins); 149 for($i=0; $i<$num; $i++) { 150 switch($ins[$i][0]) { 151 case 'plugin': 152 switch($ins[$i][1][0]) { 153 case 'include_include': 154 $ins[$i][1][1][4] = $level; 155 break; 156 /* FIXME: this doesn't work anymore that way with the new structure 157 // some plugins already close open sections 158 // so we need to make sure we don't close them twice 159 case 'box': 160 $this->helper->sec_close = false; 161 break; 162 */ 163 } 164 break; 165 case 'section_open': 166 $level = $ins[$i][1][0]; 167 break; 168 } 169 } 170 } 171 172 /** 173 * Add a hidden input to the form to preserve the redirect_id 174 */ 175 function handle_form(Doku_Event &$event, $param) { 176 if (array_key_exists('redirect_id', $_REQUEST)) { 177 $event->data->addHidden('redirect_id', cleanID($_REQUEST['redirect_id'])); 178 } 179 } 180 181 /** 182 * Modify the data for the redirect when there is a redirect_id set 183 */ 184 function handle_redirect(Doku_Event &$event, $param) { 185 if (array_key_exists('redirect_id', $_REQUEST)) { 186 // Render metadata when this is an older DokuWiki version where 187 // metadata is not automatically re-rendered as the page has probably 188 // been changed but is not directly displayed 189 $versionData = getVersionData(); 190 if ($versionData['date'] < '2010-11-23') { 191 p_set_metadata($event->data['id'], array(), true); 192 } 193 $event->data['id'] = cleanID($_REQUEST['redirect_id']); 194 $event->data['title'] = ''; 195 } 196 } 197 198 /** 199 * prepare the cache object for default _useCache action 200 */ 201 function _cache_prepare(Doku_Event &$event, $param) { 202 global $conf; 203 204 /* @var cache_renderer $cache */ 205 $cache =& $event->data; 206 207 if(!isset($cache->page)) return; 208 if(!isset($cache->mode) || $cache->mode == 'i') return; 209 210 $depends = p_get_metadata($cache->page, 'plugin_include'); 211 212 if($conf['allowdebug'] && $this->getConf('debugoutput')) { 213 dbglog('---- PLUGIN INCLUDE CACHE DEPENDS START ----'); 214 dbglog($depends); 215 dbglog('---- PLUGIN INCLUDE CACHE DEPENDS END ----'); 216 } 217 218 if (!is_array($depends)) return; // nothing to do for us 219 220 if (!is_array($depends['pages']) || 221 !is_array($depends['instructions']) || 222 $depends['pages'] != $this->helper->_get_included_pages_from_meta_instructions($depends['instructions']) || 223 // the include_content url parameter may change the behavior for included pages 224 $depends['include_content'] != isset($_REQUEST['include_content'])) { 225 226 $cache->depends['purge'] = true; // included pages changed or old metadata - request purge. 227 if($conf['allowdebug'] && $this->getConf('debugoutput')) { 228 dbglog('---- PLUGIN INCLUDE: REQUESTING CACHE PURGE ----'); 229 dbglog('---- PLUGIN INCLUDE CACHE PAGES FROM META START ----'); 230 dbglog($depends['pages']); 231 dbglog('---- PLUGIN INCLUDE CACHE PAGES FROM META END ----'); 232 dbglog('---- PLUGIN INCLUDE CACHE PAGES FROM META_INSTRUCTIONS START ----'); 233 dbglog($this->helper->_get_included_pages_from_meta_instructions($depends['instructions'])); 234 dbglog('---- PLUGIN INCLUDE CACHE PAGES FROM META_INSTRUCTIONS END ----'); 235 236 } 237 } else { 238 // add plugin.info.txt to depends for nicer upgrades 239 $cache->depends['files'][] = dirname(__FILE__) . '/plugin.info.txt'; 240 foreach ($depends['pages'] as $page) { 241 if (!$page['exists']) continue; 242 $file = wikiFN($page['id']); 243 if (!in_array($file, $cache->depends['files'])) { 244 $cache->depends['files'][] = $file; 245 } 246 } 247 } 248 } 249 250 /** 251 * Handle special section edit buttons for the include plugin to get the current page 252 * and replace normal section edit buttons when the current page is different from the 253 * global $ID. 254 */ 255 function handle_secedit_button(Doku_Event &$event, $params) { 256 // stack of included pages in the form ('id' => page, 'rev' => modification time, 'writable' => bool) 257 static $page_stack = array(); 258 259 global $ID, $lang; 260 261 $data = $event->data; 262 263 if ($data['target'] == 'plugin_include_start' || $data['target'] == 'plugin_include_start_noredirect') { 264 // handle the "section edits" added by the include plugin 265 $fn = wikiFN($data['name']); 266 $perm = auth_quickaclcheck($data['name']); 267 array_unshift($page_stack, array( 268 'id' => $data['name'], 269 'rev' => @filemtime($fn), 270 'writable' => (page_exists($data['name']) ? (is_writable($fn) && $perm >= AUTH_EDIT) : $perm >= AUTH_CREATE), 271 'redirect' => ($data['target'] == 'plugin_include_start'), 272 )); 273 } elseif ($data['target'] == 'plugin_include_end') { 274 array_shift($page_stack); 275 } elseif ($data['target'] == 'plugin_include_editbtn') { 276 if ($page_stack[0]['writable']) { 277 $params = array('do' => 'edit', 278 'id' => $page_stack[0]['id']); 279 if ($page_stack[0]['redirect']) 280 $params['redirect_id'] = $ID; 281 $event->result = '<div class="secedit">' . DOKU_LF . 282 html_btn('incledit', $page_stack[0]['id'], '', 283 $params, 'post', 284 $data['name'], 285 $lang['btn_secedit'].' ('.$page_stack[0]['id'].')') . 286 '</div>' . DOKU_LF; 287 } 288 } elseif (!empty($page_stack)) { 289 290 // Special handling for the edittable plugin 291 if ($data['target'] == 'table' && !plugin_isdisabled('edittable')) { 292 /* @var action_plugin_edittable_editor $edittable */ 293 $edittable =& plugin_load('action', 'edittable_editor'); 294 if (is_null($edittable)) 295 $edittable =& plugin_load('action', 'edittable'); 296 $data['name'] = $edittable->getLang('secedit_name'); 297 } 298 299 if ($page_stack[0]['writable'] && isset($data['name']) && $data['name'] !== '') { 300 $name = $data['name']; 301 unset($data['name']); 302 303 $secid = $data['secid']; 304 unset($data['secid']); 305 306 if ($page_stack[0]['redirect']) 307 $data['redirect_id'] = $ID; 308 309 $event->result = "<div class='secedit editbutton_" . $data['target'] . 310 " editbutton_" . $secid . "'>" . 311 html_btn('secedit', $page_stack[0]['id'], '', 312 array_merge(array('do' => 'edit', 313 'rev' => $page_stack[0]['rev'], 314 'summary' => '['.$name.'] '), $data), 315 'post', $name) . '</div>'; 316 } else { 317 $event->result = ''; 318 } 319 } else { 320 return; // return so the event won't be stopped 321 } 322 323 $event->preventDefault(); 324 $event->stopPropagation(); 325 } 326 327 public function handle_move_register(Doku_Event $event, $params) { 328 $event->data['handlers']['include_include'] = array($this, 'rewrite_include'); 329 } 330 331 public function rewrite_include($match, $pos, $state, $plugin, helper_plugin_move_handler $handler) { 332 $syntax = substr($match, 2, -2); // strip markup 333 $replacers = explode('|', $syntax); 334 $syntax = array_shift($replacers); 335 list($syntax, $flags) = explode('&', $syntax, 2); 336 337 // break the pattern up into its parts 338 list($mode, $page, $sect) = preg_split('/>|#/u', $syntax, 3); 339 340 if (method_exists($handler, 'adaptRelativeId')) { // move plugin before version 2015-05-16 341 $newpage = $handler->adaptRelativeId($page); 342 } else { 343 $newpage = $handler->resolveMoves($page, 'page'); 344 $newpage = $handler->relativeLink($page, $newpage, 'page'); 345 } 346 347 if ($newpage == $page) { 348 return $match; 349 } else { 350 $result = '{{'.$mode.'>'.$newpage; 351 if ($sect) $result .= '#'.$sect; 352 if ($flags) $result .= '&'.$flags; 353 if ($replacers) $result .= '|'.$replacers; 354 $result .= '}}'; 355 return $result; 356 } 357 } 358} 359// vim:ts=4:sw=4:et: 360