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