1<?php 2/** 3 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 4 * @author Esther Brunner <wikidesign@gmail.com> 5 */ 6 7// must be run within Dokuwiki 8if (!defined('DOKU_INC')) die(); 9 10if (!defined('DOKU_LF')) define('DOKU_LF', "\n"); 11if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t"); 12if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/'); 13 14class helper_plugin_include extends DokuWiki_Plugin { // DokuWiki_Helper_Plugin 15 16 var $page = array(); // associative array with data about the page to include 17 var $ins = array(); // instructions array 18 var $doc = ''; // the final output XHTML string 19 var $mode = 'section'; // inclusion mode: 'page' or 'section' 20 var $clevel = 0; // current section level 21 var $firstsec = 0; // show first section only 22 var $hasheader = 0; // included page has header 23 24 function getInfo(){ 25 return array( 26 'author' => 'Esther Brunner', 27 'email' => 'wikidesign@gmail.com', 28 'date' => '2006-12-15', 29 'name' => 'Include Plugin (helper class)', 30 'desc' => 'Functions to include another page in a wiki page', 31 'url' => 'http://www.wikidesign/en/plugin/include/start', 32 ); 33 } 34 35 function getMethods(){ 36 $result = array(); 37 $result[] = array( 38 'name' => 'setPage', 39 'desc' => 'sets the page to include', 40 'params' => array("page attributes, 'id' required, 'section' for filtering" => 'array'), 41 'return' => array('success' => 'boolean'), 42 ); 43 $result[] = array( 44 'name' => 'setMode', 45 'desc' => 'sets inclusion mode: should indention be merged?', 46 'params' => array("'page' (original) or 'section' (merged indention)" => 'string'), 47 ); 48 $result[] = array( 49 'name' => 'setLevel', 50 'desc' => 'sets the indention for the current section level', 51 'params' => array('level: 0 to 5' => 'integer'), 52 'return' => array('success' => 'boolean'), 53 ); 54 $result[] = array( 55 'name' => 'getXHTML', 56 'desc' => 'generates the XHTML output', 57 'params' => array('DokuWiki renderer' => 'object'), 58 'return' => array('xhtml' => 'string'), 59 ); 60 return $result; 61 } 62 63 /** 64 * Sets the page to include if it is not already included (prevent recursion) 65 */ 66 function setPage($page){ 67 global $ID; 68 global $filechain; 69 if (!isset($filechain)) $filechain = array(); 70 71 if ($this->page['id'] == $ID) return true; // page can't include itself 72 73 $id = preg_quote($this->page['id'], '/'); 74 $section = preg_quote($this->page['section'], '/'); 75 76 $pattern = ($section ? "/^($id#$section|$id#)$/" : "/^$id#/"); 77 $match = preg_grep($pattern, $filechain); 78 if (empty($match)){ 79 $this->page = $page; 80 return true; 81 } 82 return false; 83 } 84 85 /** 86 * Sets the inclusion mode 87 */ 88 function setMode($mode){ 89 $this->mode = $mode; 90 } 91 92 /** 93 * Sets the right indention for a given section level 94 */ 95 function setLevel($level){ 96 if ((is_numeric($level)) && ($level >= 0) && ($level <= 5)){ 97 $this->clevel = $level; 98 return true; 99 } 100 return false; 101 } 102 103 /** 104 * Builds the XHTML to embed the page to include 105 */ 106 function getXHTML(&$renderer){ 107 global $filechain; 108 if (!isset($filechain)) $filechain = array(); 109 110 $this->doc = ''; 111 if (!$this->page['id']) return $this->doc; // page must be set first 112 113 // add the page to the filechain 114 array_push($filechain, $his->page['id'].'#'.$this->page['section']); 115 116 $this->firstsec = $this->getConf('firstseconly'); 117 118 // get instructions and render them on the fly 119 $this->page['file'] = wikiFN($this->page['id']); 120 $this->ins = p_cached_instructions($this->page['file']); 121 122 if (!empty($this->ins)){ 123 124 // show only a given section? 125 if ($this->page['section']) $this->_getSection(); 126 127 // convert relative links 128 $this->_convertInstructions($renderer); 129 130 // insert a read more link if only first section is shown 131 if ($this->firstsec) $this->_readMore(); 132 133 // render the included page 134 $content = $this->_cleanXHTML(p_render('xhtml', $this->ins, $info)); 135 136 // embed the included page 137 $this->doc .= '<div class="include"'.$this->_showTagLogos().'>'.DOKU_LF; 138 if (!$this->hasheader && $this->clevel && ($this->mode == 'section')) 139 $this->doc .= '<div class="level'.$this->clevel.'">'.DOKU_LF; 140 $this->doc .= $content.DOKU_LF.$this->_editButton(); 141 if (!$this->hasheader && $this->clevel && ($this->mode == 'section')) 142 $this->doc .= '</div>'.DOKU_LF; 143 $this->doc .= '</div>'.DOKU_LF; 144 $this->doc .= $this->_metaLine($renderer); 145 } 146 147 // remove the page from the filechain again 148 array_pop($filechain); 149 150 return $this->doc; 151 } 152 153/* ---------- Private Methods ---------- */ 154 155 /** 156 * Get a section including its subsections 157 */ 158 function _getSection(){ 159 foreach ($this->ins as $ins){ 160 if ($ins[0] == 'header'){ 161 162 // found the right header 163 if (cleanID($ins[1][0]) == $this->page['section']){ 164 $level = $ins[1][1]; 165 $i[] = $ins; 166 167 // next header of the same or higher level -> exit 168 } elseif ($ins[1][1] <= $level){ 169 $this->ins = $i; 170 return true; 171 } elseif (isset($level)){ 172 $i[] = $ins; 173 } 174 175 // add instructions from our section 176 } elseif (isset($level)){ 177 $i[] = $ins; 178 } 179 } 180 $this->ins = $i; 181 return true; 182 } 183 184 /** 185 * Corrects relative internal links and media and 186 * converts headers of included pages to subheaders of the current page 187 */ 188 function _convertInstructions(&$renderer){ 189 global $ID; 190 global $conf; 191 192 // check if included page is in same namespace 193 $inclNS = getNS($this->page['id']); 194 if (getNS($ID) == $inclNS) $convert = false; 195 else $convert = true; 196 197 $n = count($this->ins); 198 for ($i = 0; $i < $n; $i++){ 199 200 // convert internal links and media from relative to absolute 201 if ($convert && (substr($this->ins[$i][0], 0, 8) == 'internal')){ 202 203 // relative subnamespace 204 if ($this->ins[$i][1][0]{0} == '.'){ 205 // parent namespace 206 if ($this->ins[$i][1][0]{1} == '.') 207 $ithis->ns[$i][1][0] = getNS($inclNS).':'.substr($this->ins[$i][1][0], 2); 208 // current namespace 209 else 210 $this->ins[$i][1][0] = $inclNS.':'.substr($this->ins[$i][1][0], 1); 211 212 // relative link 213 } elseif (strpos($this->ins[$i][1][0], ':') === false){ 214 $this->ins[$i][1][0] = $inclNS.':'.$this->ins[$i][1][0]; 215 } 216 217 // set header level to current section level + header level 218 } elseif ($this->ins[$i][0] == 'header'){ 219 $level = $this->ins[$i][1][1] + $this->clevel; 220 if ($level > 5) $level = 5; 221 $this->ins[$i][1][1] = $level; 222 223 // add TOC items 224 if (($level >= $conf['toptoclevel']) && ($level <= $conf['maxtoclevel'])){ 225 $text = $this->ins[$i][1][0]; 226 $hid = $renderer->_headerToLink($text, 'true'); 227 $renderer->toc[] = array( 228 'hid' => $hid, 229 'title' => $text, 230 'type' => 'ul', 231 'level' => $level - $conf['toptoclevel'] + 1 232 ); 233 234 $this->hasheader = true; 235 } 236 237 // the same for sections 238 } elseif ($this->ins[$i][0] == 'section_open'){ 239 $level = $this->ins[$i][1][0] + $this->clevel; 240 if ($level > 5) $level = 5; 241 $this->ins[$i][1][0] = $level; 242 243 // show only the first section? 244 } elseif ($this->firstsec && ($this->ins[$i][0] == 'section_close') 245 && ($this->ins[$i-1][0] != 'section_open')){ 246 if ($this->ins[0][0] == 'document_start'){ 247 $this->ins = array_slice($this->ins, 1, $i); 248 return true; 249 } else { 250 $this->ins = array_slice($this->ins, 0, $i); 251 return true; 252 } 253 } 254 } 255 if ($this->ins[0][0] == 'document_start') $this->ins = array_slice($this->ins, 1, -1); 256 return true; 257 } 258 259 /** 260 * Remove TOC, section edit buttons and tags 261 */ 262 function _cleanXHTML($xhtml){ 263 preg_match('!<div class="tags">.*?</div>!s', $xhtml, $match); 264 $this->page['tags'] = $match[0]; 265 $replace = array( 266 '!<div class="toc">.*?(</div>\n</div>)!s' => '', // remove toc 267 '#<!-- SECTION "(.*?)" \[(\d+-\d*)\] -->#e' => '', // remove section edit buttons 268 '!<div class="tags">.*?(</div>)!s' => '', // remove category tags 269 ); 270 $xhtml = preg_replace(array_keys($replace), array_values($replace), $xhtml); 271 return $xhtml; 272 } 273 274 /** 275 * Optionally display logo for the first tag found in the included page 276 */ 277 function _showTagLogos(){ 278 if (!$this->getConf('showtaglogos')) return ''; 279 280 preg_match_all('/<a [^>]*title="(.*?)" rel="tag"[^>]*>([^<]*)</', $this->page['tags'], $tag); 281 $logoID = getNS($tag[1][0]).':'.$tag[2][0]; 282 $logosrc = mediaFN($logoID); 283 $types = array('.png', '.jpg', '.gif'); // auto-detect filetype 284 foreach ($types as $type){ 285 if (!@file_exists($logosrc.$type)) continue; 286 $logoID .= $type; 287 $logosrc .= $type; 288 list($w, $h, $t, $a) = getimagesize($logosrc); 289 return ' style="min-height: '.$h.'px">'. 290 '<img class="mediaright" src="'.ml($logoID).'" alt="'.$tag[2][0].'"/'; 291 } 292 return ''; 293 } 294 295 /** 296 * Display an edit button for the included page 297 */ 298 function _editButton(){ 299 if (!isset($this->page['perm'])) 300 $this->page['perm'] = auth_quickaclcheck($this->page['id']); 301 if (@file_exists($this->page['file'])){ 302 if (($this->page['perm'] >= AUTH_EDIT) && (is_writable($this->page['file']))) 303 $action = 'edit'; 304 else return ''; 305 } elseif ($this->page['perm'] >= AUTH_CREATE){ 306 $action = 'create'; 307 } 308 return '<div class="secedit">'.DOKU_LF.DOKU_TAB. 309 html_btn($action, $this->page['id'], '', array('do' => 'edit'), 'post').DOKU_LF. 310 '</div>'.DOKU_LF; 311 } 312 313 /** 314 * Adds a read more... link at the bottom of the first section 315 */ 316 function _readMore(){ 317 $last = $this->ins[count($this->ins) - 1]; 318 if ($last[0] == 'section_close') $this->ins = array_slice($this->ins, 0, -1); 319 $this->ins[] = array('p_open', array(), $last[2]); 320 $this->ins[] = array('internallink', array($this->page['id'], $this->getLang('readmore')), $last[2]); 321 $this->ins[] = array('p_close', array(), $last[2]); 322 if ($last[0] == 'section_close') $this->ins[] = $last; 323 } 324 325 /** 326 * Returns the meta line below the included page 327 */ 328 function _metaLine(&$renderer){ 329 global $conf; 330 331 if (!$this->getConf('showmetaline')) 332 return '<div class="inclmeta"> </div>'.DOKU_LF; 333 334 $id = $this->page['id']; 335 $meta = p_get_metadata($id); 336 $ret = array(); 337 338 // permalink 339 if ($this->getConf('showlink')){ 340 $title = ($this->page['title'] ? $this->page['title'] : $meta['title']); 341 if (!$title) $title = str_replace('_', ' ', noNS($id)); 342 $ret[] = $renderer->internallink($id, $title, '', true); 343 } 344 345 // date 346 if ($this->getConf('showdate')){ 347 $date = ($this->page['date'] ? $this->page['date'] : $meta['date']['created']); 348 if ($date) $ret[] = date($conf['dformat'], $date); 349 } 350 351 // author 352 if ($this->getConf('showuser')){ 353 $author = ($this->page['user'] ? $this->page['user'] : $meta['creator']); 354 if ($author){ 355 $userpage = cleanID($this->getConf('usernamespace').':'.$author); 356 $ret[] = $renderer->internallink($userpage, $author, '', true); 357 } 358 } 359 360 // comments - let Discussion Plugin do the work for us 361 if (!$this->page['section'] && $this->getConf('showcomments') 362 && (!plugin_isdisabled('discussion')) 363 && ($discussion =& plugin_load('helper', 'discussion'))){ 364 $disc = $discussion->td($id); 365 if ($disc) $ret[] = $disc; 366 } 367 368 $ret = implode(' · ', $ret); 369 370 // tags 371 if (($this->getConf('showtags')) && ($this->page['tags'])){ 372 $ret = $this->page['tags'].$ret; 373 } 374 375 if (!$ret) $ret = ' '; 376 return '<div class="inclmeta">'.DOKU_LF.$ret.DOKU_LF.'</div>'.DOKU_LF; 377 } 378 379} 380 381//Setup VIM: ex: et ts=4 enc=utf-8 : 382