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 $supportedModes = array('xhtml'); 23 var $helper = null; 24 25 function action_plugin_include() { 26 $this->helper = plugin_load('helper', 'include'); 27 } 28 29 /** 30 * return some info 31 */ 32 function getInfo() { 33 return array( 34 'author' => 'Gina Häußge, Michael Klier, Christopher Smith', 35 'email' => 'dokuwiki@chimeric.de', 36 'date' => @file_get_contents(DOKU_PLUGIN . 'blog/VERSION'), 37 'name' => 'Include Plugin', 38 'desc' => 'Improved cache handling for included pages and redirect-handling', 39 'url' => 'http://wiki.splitbrain.org/plugin:include', 40 ); 41 } 42 43 /** 44 * plugin should use this method to register its handlers with the dokuwiki's event controller 45 */ 46 function register(&$controller) { 47 $controller->register_hook('PARSER_CACHE_USE','BEFORE', $this, '_cache_prepare'); 48// $controller->register_hook('PARSER_CACHE_USE','AFTER', $this, '_cache_result'); // debugging only 49 $controller->register_hook('HTML_EDITFORM_OUTPUT', 'BEFORE', $this, 'handle_form'); 50 $controller->register_hook('HTML_CONFLICTFORM_OUTPUT', 'BEFORE', $this, 'handle_form'); 51 $controller->register_hook('HTML_DRAFTFORM_OUTPUT', 'BEFORE', $this, 'handle_form'); 52 $controller->register_hook('ACTION_SHOW_REDIRECT', 'BEFORE', $this, 'handle_redirect'); 53 $controller->register_hook('PARSER_HANDLER_DONE', 'AFTER', $this, 'handle_parser'); 54 $controller->register_hook('TPL_TOC_RENDER', 'BEFORE', $this, 'handle_toc'); 55 } 56 57 /** 58 * Handles toc generation 59 * 60 * @author Michael Klier <chi@chimeric.de> 61 */ 62 function handle_toc(&$event, $param) { 63 $event->data = $this->helper->toc; 64 } 65 66 /** 67 * Supplies the current section level to the include syntax plugin 68 * 69 * @author Michael Klier <chi@chimeric.de> 70 */ 71 function handle_parser(&$event, $param) { 72 global $ID; 73 74 if(!isset($this->helper->toplevel_id)) $this->helper->toplevel_id = $ID; 75 76 $ins =& $event->data->calls; 77 $num = count($ins); 78 79 $toc = array(); 80 $lvl = 1; 81 for($i=0; $i<$num; $i++) { 82 if($ins[$i][0] == 'header' && ($ID == $this->helper->toplevel_id)) { 83 array_push($toc, array($ins[$i][1][0], $ins[$i][1][1])); 84 } 85 if($ins[$i][0] == 'section_open') { 86 $lvl = $ins[$i][1][0]; 87 } 88 if($ins[$i][0] == 'plugin' && $ins[$i][1][0] == 'include_include' ) { 89 $ins[$i][1][1][4] = $lvl; 90 $ins[$i][1][1][5] = $toc; 91 $toc = array(); 92 } 93 } 94 } 95 96 /** 97 * add a hidden input to the form to preserve the redirect_id 98 */ 99 function handle_form(&$event, $param) { 100 if (array_key_exists('redirect_id', $_REQUEST)) { 101 $event->data->addHidden('redirect_id', cleanID($_REQUEST['redirect_id'])); 102 } 103 } 104 105 /** 106 * modify the data for the redirect when there is a redirect_id set 107 */ 108 function handle_redirect(&$event, $param) { 109 if (array_key_exists('redirect_id', $_REQUEST)) { 110 $event->data['id'] = cleanID($_REQUEST['redirect_id']); 111 $event->data['title'] = ''; 112 } 113 } 114 115 /** 116 * prepare the cache object for default _useCache action 117 */ 118 function _cache_prepare(&$event, $param) { 119 $cache =& $event->data; 120 121 // we're only interested in wiki pages and supported render modes 122 if (!isset($cache->page)) return; 123 if (!isset($cache->mode) || !in_array($cache->mode, $this->supportedModes)) return; 124 125 $key = ''; 126 $depends = array(); 127 $expire = $this->_inclusion_check($cache->page, $key, $depends); 128 129// global $debug; 130// $debug[] = compact('key','expire','depends','cache'); 131 132 // empty $key implies no includes, so nothing to do 133 if (empty($key)) return; 134 135 // mark the cache as being modified by the include plugin 136 $cache->include = true; 137 138 // set new cache key & cache name - now also dependent on included page ids and their ACL_READ status 139 $cache->key .= $key; 140 $cache->cache = getCacheName($cache->key, $cache->ext); 141 142 // inclusion check was able to determine the cache must be invalid 143 if ($expire) { 144 $event->preventDefault(); 145 $event->stopPropagation(); 146 $event->result = false; 147 return; 148 } 149 150 // update depends['files'] array to include all included files 151 $cache->depends['files'] = !empty($cache->depends['files']) ? array_merge($cache->depends['files'], $depends) : $depends; 152 } 153 154 /** 155 * carry out included page checks: 156 * - to establish proper cache name, its dependent on the read status of included pages 157 * - to establish file dependencies, the included raw wiki pages 158 * 159 * @param string $id wiki page name 160 * @param string $key (reference) cache key 161 * @param array $depends array of include file dependencies 162 * 163 * @return bool expire the cache 164 */ 165 function _inclusion_check($id, &$key, &$depends) { 166 $hasPart = p_get_metadata($id, 'relation haspart'); 167 if (empty($hasPart)) return false; 168 169 $expire = false; 170 foreach ($hasPart as $page => $exists) { 171 // ensure its a wiki page 172 if (strpos($page,'/') || cleanID($page) != $page) continue; 173 174 // recursive includes aren't allowed and there is no need to do the same page twice 175 $file = wikiFN($page); 176 if (in_array($file, $depends)) continue; 177 178 // file existence state is different from state recorded in metadata 179 if (@file_exists($file) != $exists) { 180 181 if (($acl = $this->_acl_read_check($page)) != 'NONE') { $expire = true; } 182 183 } else if ($exists) { 184 185 // carry out an inclusion check on the included page, that will update $key & $depends 186 if ($this->_inclusion_check($page, $key, $depends)) { $expire = true; } 187 if (($acl = $this->_acl_read_check($page)) != 'NONE') { $depends[] = $file; } 188 189 } else { 190 $acl = 'NONE'; 191 } 192 193 // add this page and acl status to the key 194 $key .= '#'.$page.'|'.$acl; 195 } 196 197 return $expire; 198 } 199 200 function _acl_read_check($id) { 201 return (AUTH_READ <= auth_quickaclcheck($id)) ? 'READ' : 'NONE'; 202 } 203 204 function _cache_result(&$event, $param) { 205 $cache =& $event->data; 206 if (empty($cache->include)) return; 207 208// global $debug; 209// $debug['cache_result'][] = $event->result ? 'true' : 'false'; 210 } 211 212} 213//vim:ts=4:sw=4:et:enc=utf-8: 214