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 $pages = array(); // filechain of included pages 17 var $page = array(); // associative array with data about the page to include 18 var $ins = array(); // instructions array 19 var $doc = ''; // the final output XHTML string 20 var $mode = 'section'; // inclusion mode: 'page' or 'section' 21 var $clevel = 0; // current section level 22 var $firstsec = 0; // show first section only 23 var $editbtn = 1; // show edit button 24 var $footer = 1; // show metaline below page 25 var $noheader = 0; // omit header 26 var $header = array(); // included page / section header 27 var $renderer = NULL; // DokuWiki renderer object 28 29 // private variables 30 var $_offset = NULL; 31 32 /** 33 * Constructor loads some config settings 34 */ 35 function helper_plugin_include(){ 36 $this->clevel = 0; 37 $this->firstsec = $this->getConf('firstseconly'); 38 $this->editbtn = $this->getConf('showeditbtn'); 39 $this->footer = $this->getConf('showfooter'); 40 $this->noheader = 0; 41 $this->header = array(); 42 $this->_offset = NULL; 43 } 44 45 function getInfo(){ 46 return array( 47 'author' => 'Esther Brunner', 48 'email' => 'wikidesign@gmail.com', 49 'date' => '2007-08-10', 50 'name' => 'Include Plugin (helper class)', 51 'desc' => 'Functions to include another page in a wiki page', 52 'url' => 'http://www.wikidesign/en/plugin/include/start', 53 ); 54 } 55 56 function getMethods(){ 57 $result = array(); 58 $result[] = array( 59 'name' => 'setPage', 60 'desc' => 'sets the page to include', 61 'params' => array("page attributes, 'id' required, 'section' for filtering" => 'array'), 62 'return' => array('success' => 'boolean'), 63 ); 64 $result[] = array( 65 'name' => 'setMode', 66 'desc' => 'sets inclusion mode: should indention be merged?', 67 'params' => array("'page' (original) or 'section' (merged indention)" => 'string'), 68 ); 69 $result[] = array( 70 'name' => 'setLevel', 71 'desc' => 'sets the indention for the current section level', 72 'params' => array('level: 0 to 5' => 'integer'), 73 'return' => array('success' => 'boolean'), 74 ); 75 $result[] = array( 76 'name' => 'setFlags', 77 'desc' => 'overrides standard values for showfooter and firstseconly settings', 78 'params' => array('flags' => 'array'), 79 ); 80 $result[] = array( 81 'name' => 'renderXHTML', 82 'desc' => 'renders the XHTML output of the included page', 83 'params' => array('DokuWiki renderer' => 'object'), 84 'return' => array('XHTML' => 'string'), 85 ); 86 return $result; 87 } 88 89 /** 90 * Sets the page to include if it is not already included (prevent recursion) 91 * and the current user is allowed to read it 92 */ 93 function setPage($page){ 94 global $ID; 95 96 $id = $page['id']; 97 $fullid = $id.'#'.$page['section']; 98 99 if (!$id) return false; // no page id given 100 if ($id == $ID) return false; // page can't include itself 101 102 // prevent include recursion 103 if ((isset($this->pages[$id.'#'])) || (isset($this->pages[$fullid]))) return false; 104 105 // we need to make sure 'perm', 'file' and 'exists' are set 106 if (!isset($page['perm'])) $page['perm'] = auth_quickaclcheck($page['id']); 107 if (!isset($page['file'])) $page['file'] = wikiFN($page['id']); 108 if (!isset($page['exists'])) $page['exists'] = @file_exists($page['file']); 109 110 // check permission 111 if ($page['perm'] < AUTH_READ) return false; 112 113 // add the page to the filechain 114 $this->pages[$fullid] = $page; 115 $this->page =& $this->pages[$fullid]; 116 return true; 117 } 118 119 /** 120 * Sets the inclusion mode 121 */ 122 function setMode($mode){ 123 $this->mode = $mode; 124 } 125 126 /** 127 * Sets the right indention for a given section level 128 */ 129 function setLevel($level){ 130 if ((is_numeric($level)) && ($level >= 0) && ($level <= 5)){ 131 $this->clevel = $level; 132 return true; 133 } 134 return false; 135 } 136 137 /** 138 * Overrides standard values for showfooter and firstseconly settings 139 */ 140 function setFlags($flags){ 141 foreach ($flags as $flag){ 142 switch ($flag){ 143 case 'footer': 144 $this->footer = 1; 145 break; 146 case 'nofooter': 147 $this->footer = 0; 148 break; 149 case 'firstseconly': 150 $this->firstsec = 1; 151 break; 152 case 'fullpage': 153 $this->firstsec = 0; 154 break; 155 case 'noheader': 156 $this->noheader = 1; 157 break; 158 case 'editbtn': 159 $this->editbtn = 1; 160 break; 161 case 'noeditbtn': 162 $this->editbtn = 0; 163 break; 164 } 165 } 166 } 167 168 /** 169 * Builds the XHTML to embed the page to include 170 */ 171 function renderXHTML(&$renderer){ 172 if (!$this->page['id']) return ''; // page must be set first 173 if (!$this->page['exists'] && ($this->page['perm'] < AUTH_CREATE)) return ''; 174 175 // prepare variables 176 $this->doc = ''; 177 $this->renderer =& $renderer; 178 179 // get instructions and render them on the fly 180 $this->ins = p_cached_instructions($this->page['file']); 181 182 // show only a given section? 183 if ($this->page['section'] && $this->page['exists']) $this->_getSection(); 184 185 // convert relative links 186 $this->_convertInstructions(); 187 188 // render the included page 189 $content = '<div class="entry-content">'.DOKU_LF. 190 $this->_cleanXHTML(p_render('xhtml', $this->ins, $info)).DOKU_LF. 191 '</div>'.DOKU_LF; 192 193 // embed the included page 194 $class = ($this->page['draft'] ? 'include draft' : 'include'); 195 $renderer->doc .= '<div class="'.$class.' hentry"'.$this->_showTagLogos().'>'.DOKU_LF; 196 if (!$this->header && $this->clevel && ($this->mode == 'section')) 197 $renderer->doc .= '<div class="level'.$this->clevel.'">'.DOKU_LF; 198 if ((@file_exists(DOKU_PLUGIN.'editsections/action.php')) 199 && (!plugin_isdisabled('editsections'))){ // for Edit Section Reorganizer Plugin 200 $renderer->doc .= $this->_editButton().$content; 201 } else { 202 $renderer->doc .= $content.$this->_editButton(); 203 } 204 205 if (!$this->header && $this->clevel && ($this->mode == 'section')) 206 $renderer->doc .= '</div>'.DOKU_LF; // class="level?" 207 $renderer->doc .= '</div>'.DOKU_LF; // class="include hentry" 208 209 // output meta line (if wanted) and remove page from filechain 210 $renderer->doc .= $this->_footer(array_pop($this->pages)); 211 $this->helper_plugin_include(); 212 213 return $this->doc; 214 } 215 216/* ---------- Private Methods ---------- */ 217 218 /** 219 * Get a section including its subsections 220 */ 221 function _getSection(){ 222 foreach ($this->ins as $ins){ 223 if ($ins[0] == 'header'){ 224 225 // found the right header 226 if (cleanID($ins[1][0]) == $this->page['section']){ 227 $level = $ins[1][1]; 228 $i[] = $ins; 229 230 // next header of the same or higher level -> exit 231 } elseif ($ins[1][1] <= $level){ 232 $this->ins = $i; 233 return true; 234 } elseif (isset($level)){ 235 $i[] = $ins; 236 } 237 238 // add instructions from our section 239 } elseif (isset($level)){ 240 $i[] = $ins; 241 } 242 } 243 $this->ins = $i; 244 return true; 245 } 246 247 /** 248 * Corrects relative internal links and media and 249 * converts headers of included pages to subheaders of the current page 250 */ 251 function _convertInstructions(){ 252 global $ID; 253 254 if (!$this->page['exists']) return false; 255 256 // check if included page is in same namespace 257 $inclNS = getNS($this->page['id']); 258 if (getNS($ID) == $inclNS) $convert = false; 259 else $convert = true; 260 261 $n = count($this->ins); 262 for ($i = 0; $i < $n; $i++){ 263 $current = $this->ins[$i][0]; 264 265 // convert internal links and media from relative to absolute 266 if ($convert && (substr($current, 0, 8) == 'internal')){ 267 $this->ins[$i][1][0] = $this->_convertInternalLinks($i, $inclNS); 268 269 // set header level to current section level + header level 270 } elseif ($current == 'header'){ 271 $this->_convertHeaders($i); 272 273 // the same for sections 274 } elseif ($current == 'section_open'){ 275 $this->ins[$i][1][0] = $this->_convertSectionLevel($this->ins[$i][1][0]); 276 277 // show only the first section? 278 } elseif ($this->firstsec && ($current == 'section_close') 279 && ($this->ins[$i-1][0] != 'section_open')){ 280 $this->_readMore($i); 281 return true; 282 } 283 } 284 $this->_finishConvert(); 285 return true; 286 } 287 288 /** 289 * Convert relative internal links and media 290 * 291 * @param integer $i: counter for current instruction 292 * @param string $ns: namespace of included page 293 * @return string $link: converted, now absolute link 294 */ 295 function _convertInternalLinks($i, $ns){ 296 297 // relative subnamespace 298 if ($this->ins[$i][1][0]{0} == '.'){ 299 300 // parent namespace 301 if ($this->ins[$i][1][0]{1} == '.') 302 return getNS($ns).':'.substr($this->ins[$i][1][0], 2); 303 304 // current namespace 305 else 306 return $ns.':'.substr($this->ins[$i][1][0], 1); 307 308 // relative link 309 } elseif (strpos($this->ins[$i][1][0], ':') === false){ 310 return $ns.':'.$this->ins[$i][1][0]; 311 } 312 } 313 314 /** 315 * Convert header level and add header to TOC 316 * 317 * @param integer $i: counter for current instruction 318 * @return boolean true 319 */ 320 function _convertHeaders($i){ 321 global $conf; 322 323 $text = $this->ins[$i][1][0]; 324 $hid = $this->renderer->_headerToLink($text, 'true'); 325 if (empty($this->header)){ 326 $this->_offset = $this->clevel - $this->ins[$i][1][1] + 1; 327 $level = $this->_convertSectionLevel(1); 328 $this->header = array('hid' => $hid, 'title' => hsc($text), 'level' => $level); 329 if ($this->noheader){ 330 unset($this->ins[$i]); 331 return true; 332 } 333 } else { 334 $level = $this->_convertSectionLevel($this->ins[$i][1][1]); 335 } 336 $this->ins[$i][1][1] = $level; 337 338 // add TOC item 339 if (($level >= $conf['toptoclevel']) && ($level <= $conf['maxtoclevel'])){ 340 $this->renderer->toc[] = array( 341 'hid' => $hid, 342 'title' => $text, 343 'type' => 'ul', 344 'level' => $level - $conf['toptoclevel'] + 1 345 ); 346 } 347 return true; 348 } 349 350 /** 351 * Convert the level of headers and sections 352 * 353 * @param integer $in: current level 354 * @return integer $out: converted level 355 */ 356 function _convertSectionLevel($in){ 357 $out = $in + $this->_offset; 358 if ($out >= 5) return 5; 359 if ($out <= $this->clevel + 1) return $this->clevel + 1; 360 return $out; 361 } 362 363 /** 364 * Adds a read more... link at the bottom of the first section 365 * 366 * @param integer $i: counter for current instruction 367 * @return boolean true 368 */ 369 function _readMore($i){ 370 $more = ((is_array($this->ins[$i+1])) && ($this->ins[$i+1][0] != 'document_end')); 371 372 if ($this->ins[0][0] == 'document_start') $this->ins = array_slice($this->ins, 1, $i); 373 else $this->ins = array_slice($this->ins, 0, $i); 374 375 if ($more){ 376 array_unshift($this->ins, array('document_start', array(), 0)); 377 $last = array_pop($this->ins); 378 $this->ins[] = array('p_open', array(), $last[2]); 379 $this->ins[] = array('internallink',array($this->page['id'], $this->getLang('readmore')),$last[2]); 380 $this->ins[] = array('p_close', array(), $last[2]); 381 $this->ins[] = $last; 382 $this->ins[] = array('document_end', array(), $last[2]); 383 } else { 384 $this->_finishConvert(); 385 } 386 return true; 387 } 388 389 /** 390 * Adds 'document_start' and 'document_end' instructions if not already there 391 */ 392 function _finishConvert(){ 393 if ($this->ins[0][0] != 'document_start'){ 394 array_unshift($this->ins, array('document_start', array(), 0)); 395 $this->ins[] = array('document_end', array(), 0); 396 } 397 } 398 399 /** 400 * Remove TOC, section edit buttons and tags 401 */ 402 function _cleanXHTML($xhtml){ 403 preg_match('!<div class="tags">.*?</div>!s', $xhtml, $match); 404 $this->page['tags'] = $match[0]; 405 $replace = array( 406 '!<div class="toc">.*?(</div>\n</div>)!s' => '', // remove toc 407 '#<!-- SECTION "(.*?)" \[(\d+-\d*)\] -->#e' => '', // remove section edit buttons 408 '!<div class="tags">.*?(</div>)!s' => '', // remove category tags 409 ); 410 $xhtml = preg_replace(array_keys($replace), array_values($replace), $xhtml); 411 return $xhtml; 412 } 413 414 /** 415 * Optionally display logo for the first tag found in the included page 416 */ 417 function _showTagLogos(){ 418 if (!$this->getConf('showtaglogos')) return ''; 419 420 preg_match_all('/<a [^>]*title="(.*?)" rel="tag"[^>]*>([^<]*)</', $this->page['tags'], $tag); 421 $logoID = getNS($tag[1][0]).':'.$tag[2][0]; 422 $logosrc = mediaFN($logoID); 423 $types = array('.png', '.jpg', '.gif'); // auto-detect filetype 424 foreach ($types as $type){ 425 if (!@file_exists($logosrc.$type)) continue; 426 $logoID .= $type; 427 $logosrc .= $type; 428 list($w, $h, $t, $a) = getimagesize($logosrc); 429 return ' style="min-height: '.$h.'px">'. 430 '<img class="mediaright" src="'.ml($logoID).'" alt="'.$tag[2][0].'"/'; 431 } 432 return ''; 433 } 434 435 /** 436 * Display an edit button for the included page 437 */ 438 function _editButton(){ 439 if ($this->page['exists']){ 440 if (($this->page['perm'] >= AUTH_EDIT) && (is_writable($this->page['file']))) 441 $action = 'edit'; 442 else return ''; 443 } elseif ($this->page['perm'] >= AUTH_CREATE){ 444 $action = 'create'; 445 } 446 if ($this->editbtn){ 447 return '<div class="secedit">'.DOKU_LF.DOKU_TAB. 448 html_btn($action, $this->page['id'], '', array('do' => 'edit'), 'post').DOKU_LF. 449 '</div>'.DOKU_LF; 450 } else { 451 return ''; 452 } 453 } 454 455 /** 456 * Returns the meta line below the included page 457 */ 458 function _footer($page){ 459 global $conf; 460 461 if (!$this->footer) return ''; // '<div class="inclmeta"> </div>'.DOKU_LF; 462 463 $id = $page['id']; 464 $meta = p_get_metadata($id); 465 $ret = array(); 466 467 // permalink 468 if ($this->getConf('showlink')){ 469 $title = ($page['title'] ? $page['title'] : $meta['title']); 470 if (!$title) $title = str_replace('_', ' ', noNS($id)); 471 $class = ($page['exists'] ? 'wikilink1' : 'wikilink2'); 472 $link = array( 473 'url' => wl($id), 474 'title' => $id, 475 'name' => hsc($title), 476 'target' => $conf['target']['wiki'], 477 'class' => $class.' permalink', 478 'more' => 'rel="bookmark"', 479 ); 480 $ret[] = $this->renderer->_formatLink($link); 481 } 482 483 // date 484 if ($this->getConf('showdate')){ 485 $date = ($page['date'] ? $page['date'] : $meta['date']['created']); 486 if ($date) 487 $ret[] = '<abbr class="published" title="'.gmdate('Y-m-d\TH:i:s\Z', $date).'">'. 488 date($conf['dformat'], $date). 489 '</abbr>'; 490 } 491 492 // author 493 if ($this->getConf('showuser')){ 494 $author = ($page['user'] ? $page['user'] : $meta['creator']); 495 if ($author){ 496 $userpage = cleanID($this->getConf('usernamespace').':'.$author); 497 resolve_pageid(getNS($ID), $id, $exists); 498 $class = ($exists ? 'wikilink1' : 'wikilink2'); 499 $link = array( 500 'url' => wl($userpage), 501 'title' => $userpage, 502 'name' => hsc($author), 503 'target' => $conf['target']['wiki'], 504 'class' => $class.' url fn', 505 'pre' => '<span class="vcard author">', 506 'suf' => '</span>', 507 ); 508 $ret[] = $this->renderer->_formatLink($link); 509 } 510 } 511 512 // comments - let Discussion Plugin do the work for us 513 if (!$page['section'] && $this->getConf('showcomments') 514 && (!plugin_isdisabled('discussion')) 515 && ($discussion =& plugin_load('helper', 'discussion'))){ 516 $disc = $discussion->td($id); 517 if ($disc) $ret[] = '<span class="comment">'.$disc.'</span>'; 518 } 519 520 // linkbacks - let Linkback Plugin do the work for us 521 if (!$page['section'] && $this->getConf('showlinkbacks') 522 && (!plugin_isdisabled('linkback')) 523 && ($linkback =& plugin_load('helper', 'linkback'))){ 524 $link = $linkback->td($id); 525 if ($link) $ret[] = '<span class="linkback">'.$link.'</span>'; 526 } 527 528 $ret = implode(' · ', $ret); 529 530 // tags 531 if (($this->getConf('showtags')) && ($page['tags'])){ 532 $ret = $this->page['tags'].$ret; 533 } 534 535 if (!$ret) $ret = ' '; 536 return '<div class="inclmeta">'.DOKU_LF.$ret.DOKU_LF.'</div>'.DOKU_LF; 537 } 538 539} 540 541//Setup VIM: ex: et ts=4 enc=utf-8 : 542