1<?php 2/** 3 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 4 * @author Esther Brunner <wikidesign@gmail.com> 5 * @author Christopher Smith <chris@jalakai.co.uk> 6 * @author Gina Häußge, Michael Klier <dokuwiki@chimeric.de> 7 */ 8 9// must be run within Dokuwiki 10if (!defined('DOKU_INC')) die(); 11 12if (!defined('DOKU_LF')) define('DOKU_LF', "\n"); 13if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t"); 14if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/'); 15 16class helper_plugin_include extends DokuWiki_Plugin { // DokuWiki_Helper_Plugin 17 18 var $pages = array(); // filechain of included pages 19 var $page = array(); // associative array with data about the page to include 20 var $ins = array(); // instructions array 21 var $doc = ''; // the final output XHTML string 22 var $mode = 'section'; // inclusion mode: 'page' or 'section' 23 var $clevel = 0; // current section level 24 var $firstsec = 0; // show first section only 25 var $editbtn = 1; // show edit button 26 var $footer = 1; // show metaline below page 27 var $noheader = 0; // omit header 28 var $permalink = 0; // make first headline permalink to included page 29 var $redirect = 1; // redirect back to original page after an edit 30 var $header = array(); // included page / section header 31 var $renderer = NULL; // DokuWiki renderer object 32 33 var $INCLUDE_LIMIT = 12; 34 35 // private variables 36 var $_offset = NULL; 37 38 /** 39 * Constructor loads some config settings 40 */ 41 function helper_plugin_include() { 42 $this->firstsec = $this->getConf('firstseconly'); 43 $this->editbtn = $this->getConf('showeditbtn'); 44 $this->footer = $this->getConf('showfooter'); 45 $this->redirect = $this->getConf('doredirect'); 46 $this->noheader = 0; 47 $this->permalink = 0; 48 $this->header = array(); 49 } 50 51 function getInfo() { 52 return array( 53 'author' => 'Gina Häußge, Michael Klier, Esther Brunner', 54 'email' => 'dokuwiki@chimeric.de', 55 'date' => @file_get_contents(DOKU_PLUGIN . 'blog/VERSION'), 56 'name' => 'Include Plugin (helper class)', 57 'desc' => 'Functions to include another page in a wiki page', 58 'url' => 'http://wiki.splitbrain.org/plugin:include', 59 ); 60 } 61 62 function getMethods() { 63 $result = array(); 64 $result[] = array( 65 'name' => 'setPage', 66 'desc' => 'sets the page to include', 67 'params' => array("page attributes, 'id' required, 'section' for filtering" => 'array'), 68 'return' => array('success' => 'boolean'), 69 ); 70 $result[] = array( 71 'name' => 'setMode', 72 'desc' => 'sets inclusion mode: should indention be merged?', 73 'params' => array("'page' (original) or 'section' (merged indention)" => 'string'), 74 ); 75 $result[] = array( 76 'name' => 'setLevel', 77 'desc' => 'sets the indention for the current section level', 78 'params' => array('level: 0 to 5' => 'integer'), 79 'return' => array('success' => 'boolean'), 80 ); 81 $result[] = array( 82 'name' => 'setFlags', 83 'desc' => 'overrides standard values for showfooter and firstseconly settings', 84 'params' => array('flags' => 'array'), 85 ); 86 $result[] = array( 87 'name' => 'renderXHTML', 88 'desc' => 'renders the XHTML output of the included page', 89 'params' => array('DokuWiki renderer' => 'object'), 90 'return' => array('XHTML' => 'string'), 91 ); 92 return $result; 93 } 94 95 /** 96 * Sets the page to include if it is not already included (prevent recursion) 97 * and the current user is allowed to read it 98 */ 99 function setPage($page) { 100 global $ID; 101 102 $id = $page['id']; 103 $fullid = $id.'#'.$page['section']; 104 105 if (!$id) return false; // no page id given 106 if ($id == $ID) return false; // page can't include itself 107 108 // prevent include recursion 109 if ($this->_in_filechain($id,$page['section']) || (count($this->pages) >= $this->INCLUDE_LIMIT)) return false; 110 111 // we need to make sure 'perm', 'file' and 'exists' are set 112 if (!isset($page['perm'])) $page['perm'] = auth_quickaclcheck($page['id']); 113 if (!isset($page['file'])) $page['file'] = wikiFN($page['id']); 114 if (!isset($page['exists'])) $page['exists'] = @file_exists($page['file']); 115 116 // check permission 117 if ($page['perm'] < AUTH_READ) return false; 118 119 // add the page to the filechain 120 $this->page = $page; 121 return true; 122 } 123 124 function _push_page($id,$section) { 125 global $ID; 126 if (empty($this->pages)) array_push($this->pages, $ID.'#'); 127 array_push($this->pages, $id.'#'.$section); 128 } 129 130 function _pop_page() { 131 $page = array_pop($this->pages); 132 if (count($this->pages=1)) $this->pages = array(); 133 134 return $page; 135 } 136 137 function _in_filechain($id,$section) { 138 $pattern = $section ? "/^($id#$section|$id#)$/" : "/^$id#/"; 139 $match = preg_grep($pattern, $this->pages); 140 141 return (!empty($match)); 142 } 143 144 /** 145 * Sets the inclusion mode: 'page' or 'section' 146 */ 147 function setMode($mode) { 148 $this->mode = $mode; 149 } 150 151 /** 152 * Sets the right indention for a given section level 153 */ 154 function setLevel($level) { 155 if ((is_numeric($level)) && ($level >= 0) && ($level <= 5)) { 156 $this->clevel = $level; 157 return true; 158 } 159 return false; 160 } 161 162 /** 163 * Overrides standard values for showfooter and firstseconly settings 164 */ 165 function setFlags($flags) { 166 foreach ($flags as $flag) { 167 switch ($flag) { 168 case 'footer': 169 $this->footer = 1; 170 break; 171 case 'nofooter': 172 $this->footer = 0; 173 break; 174 case 'firstseconly': 175 case 'firstsectiononly': 176 $this->firstsec = 1; 177 break; 178 case 'fullpage': 179 $this->firstsec = 0; 180 break; 181 case 'noheader': 182 $this->noheader = 1; 183 break; 184 case 'editbtn': 185 case 'editbutton': 186 $this->editbtn = 1; 187 break; 188 case 'noeditbtn': 189 case 'noeditbutton': 190 $this->editbtn = 0; 191 break; 192 case 'permalink': 193 $this->permalink = 1; 194 break; 195 case 'nopermalink': 196 $this->permalink = 0; 197 break; 198 case 'redirect': 199 $this->redirect = 1; 200 break; 201 case 'noredirect': 202 $this->redirect = 0; 203 break; 204 } 205 } 206 } 207 208 /** 209 * Builds the XHTML to embed the page to include 210 */ 211 function renderXHTML(&$renderer, &$info) { 212 global $ID; 213 214 if (!$this->page['id']) return ''; // page must be set first 215 if (!$this->page['exists'] && ($this->page['perm'] < AUTH_CREATE)) return ''; 216 217 $this->_push_page($this->page['id'],$this->page['section']); 218 219 // prepare variables 220 $rdoc = $renderer->doc; 221 $doc = ''; 222 $this->renderer =& $renderer; 223 224 $page = $this->page; 225 $clevel = $this->clevel; 226 $mode = $this->mode; 227 228 // exchange page ID for included one 229 $backupID = $ID; // store the current ID 230 $ID = $this->page['id']; // change ID to the included page 231 232 // get instructions and render them on the fly 233 $this->ins = p_cached_instructions($this->page['file']); 234 235 // show only a given section? 236 if ($this->page['section'] && $this->page['exists']) $this->_getSection(); 237 238 // convert relative links 239 $this->_convertInstructions(); 240 241 $xhtml = p_render('xhtml', $this->ins, $info); 242 $ID = $backupID; // restore ID 243 244 $this->mode = $mode; 245 $this->clevel = $clevel; 246 $this->page = $page; 247 248 $xhtml = $this->_cleanXHTML($xhtml); 249 $xhtml = $this->_convertFootnotes($xhtml, $this->page['id']); 250 251 // render the included page 252 $content = '<div class="entry-content">'.DOKU_LF. 253 $xhtml.DOKU_LF. 254 '</div><!-- .entry-content -->'.DOKU_LF; 255 256 // restore ID 257 $ID = $backupID; 258 259 // embed the included page 260 $class = ($this->page['draft'] ? 'include draft' : 'include'); 261 262 $doc .= DOKU_LF.'<!-- including '.$this->page['id'].' // '.$this->page['file'].' -->'.DOKU_LF; 263 $doc .= '<div class="'.$class.' hentry"'.$this->_showTagLogos().'>'.DOKU_LF; 264 if (!$this->header && $this->clevel && ($this->mode == 'section')) 265 $doc .= '<div class="level'.$this->clevel.'">'.DOKU_LF; 266 267 if ((@file_exists(DOKU_PLUGIN.'editsections/action.php')) 268 && (!plugin_isdisabled('editsections'))) { // for Edit Section Reorganizer Plugin 269 $doc .= $this->_editButton().$content; 270 } else { 271 $doc .= $content.$this->_editButton(); 272 } 273 274 // output meta line (if wanted) and remove page from filechain 275 $doc .= $this->_footer($this->page); 276 277 if (!$this->header && $this->clevel && ($this->mode == 'section')) 278 $doc .= '</div>'.DOKU_LF; // class="level?" 279 $doc .= '</div>'.DOKU_LF; // class="include hentry" 280 $doc .= DOKU_LF.'<!-- /including '.$this->page['id'].' -->'.DOKU_LF; 281 282 // reset defaults 283 $this->helper_plugin_include(); 284 $this->_pop_page(); 285 286 // return XHTML 287 $renderer->doc = $rdoc.$doc; 288 return $doc; 289 } 290 291 /* ---------- Private Methods ---------- */ 292 293 /** 294 * Get a section including its subsections 295 */ 296 function _getSection() { 297 foreach ($this->ins as $ins) { 298 if ($ins[0] == 'header') { 299 300 // found the right header 301 if (cleanID($ins[1][0]) == $this->page['section']) { 302 $level = $ins[1][1]; 303 $i[] = $ins; 304 305 // next header of the same or higher level -> exit 306 } elseif ($ins[1][1] <= $level) { 307 $this->ins = $i; 308 return true; 309 } elseif (isset($level)) { 310 $i[] = $ins; 311 } 312 313 // add instructions from our section 314 } elseif (isset($level)) { 315 $i[] = $ins; 316 } 317 } 318 $this->ins = $i; 319 return true; 320 } 321 322 /** 323 * Corrects relative internal links and media and 324 * converts headers of included pages to subheaders of the current page 325 */ 326 function _convertInstructions() { 327 global $ID; 328 329 if (!$this->page['exists']) return false; 330 331 // check if included page is in same namespace 332 $ns = getNS($this->page['id']); 333 $convert = (getNS($ID) == $ns ? false : true); 334 335 $n = count($this->ins); 336 for ($i = 0; $i < $n; $i++) { 337 $current = $this->ins[$i][0]; 338 339 // convert internal links and media from relative to absolute 340 if ($convert && (substr($current, 0, 8) == 'internal')) { 341 $this->ins[$i][1][0] = $this->_convertInternalLink($this->ins[$i][1][0], $ns); 342 343 // set header level to current section level + header level 344 } elseif ($current == 'header') { 345 $this->_convertHeader($i); 346 347 // the same for sections 348 } elseif (($current == 'section_open') && ($this->mode == 'section')) { 349 $this->ins[$i][1][0] = $this->_convertSectionLevel($this->ins[$i][1][0]); 350 351 // show only the first section? 352 } elseif ($this->firstsec && ($current == 'section_close') 353 && ($this->ins[$i-1][0] != 'section_open')) { 354 $this->_readMore($i); 355 return true; 356 } 357 } 358 $this->_finishConvert(); 359 return true; 360 } 361 362 /** 363 * Convert relative internal links and media 364 * 365 * @param integer $i: counter for current instruction 366 * @param string $ns: namespace of included page 367 * @return string $link: converted, now absolute link 368 */ 369 function _convertInternalLink($link, $ns) { 370 371 // relative subnamespace 372 if ($link{0} == '.') { 373 if ($link{1} == '.') return getNS($ns).':'.substr($link, 2); // parent namespace 374 else return $ns.':'.substr($link, 1); // current namespace 375 376 // relative link 377 } elseif (strpos($link, ':') === false) { 378 return $ns.':'.$link; 379 380 // absolute link - don't change 381 } else { 382 return $link; 383 } 384 } 385 386 /** 387 * Convert header level and add header to TOC 388 * 389 * @param integer $i: counter for current instruction 390 * @return boolean true 391 */ 392 function _convertHeader($i) { 393 global $conf; 394 395 $text = $this->ins[$i][1][0]; 396 $hid = $this->renderer->_headerToLink($text, 'true'); 397 if (empty($this->header)) { 398 $this->_offset = $this->clevel - $this->ins[$i][1][1] + 1; 399 $level = $this->_convertSectionLevel(1); 400 $this->header = array('hid' => $hid, 'title' => hsc($text), 'level' => $level); 401 if ($this->noheader) { 402 unset($this->ins[$i]); 403 return true; 404 } else if ($this->permalink){ 405 $this->ins[$i] = $this->_permalinkHeader($text, $level, $this->ins[$i][1][2]); 406 } 407 } else { 408 $level = $this->_convertSectionLevel($this->ins[$i][1][1]); 409 } 410 if ($this->mode == 'section') { 411 if (is_array($this->ins[$i][1][1])) { // permalink header 412 $this->ins[$i][1][1][1] = $level; 413 } else { // normal header 414 $this->ins[$i][1][1] = $level; 415 } 416 } 417 418 // add TOC item 419 if (($level >= $conf['toptoclevel']) && ($level <= $conf['maxtoclevel'])) { 420 $this->renderer->toc[] = array( 421 'hid' => $hid, 422 'title' => $text, 423 'type' => 'ul', 424 'level' => $level - $conf['toptoclevel'] + 1 425 ); 426 } 427 return true; 428 } 429 430 /** 431 * Create instruction item for a permalink header 432 * 433 * @param string $text: Headline text 434 * @param integer $level: Headline level 435 * @param integer $pos: I wish I knew what this is for... 436 * 437 * @author Gina Haeussge <osd@foosel.net> 438 */ 439 function _permalinkHeader($text, $level, $pos) { 440 $newIns = array( 441 'plugin', 442 array( 443 'include_header', 444 array( 445 $text, 446 $level 447 ), 448 ), 449 $pos 450 ); 451 452 return $newIns; 453 } 454 455 /** 456 * Convert the level of headers and sections 457 * 458 * @param integer $in: current level 459 * @return integer $out: converted level 460 */ 461 function _convertSectionLevel($in) { 462 $out = $in + $this->_offset; 463 if ($out >= 5) return 5; 464 if ($out <= $this->clevel + 1) return $this->clevel + 1; 465 return $out; 466 } 467 468 /** 469 * Adds a read more... link at the bottom of the first section 470 * 471 * @param integer $i: counter for current instruction 472 * @return boolean true 473 */ 474 function _readMore($i) { 475 $more = ((is_array($this->ins[$i+1])) && ($this->ins[$i+1][0] != 'document_end')); 476 477 if ($this->ins[0][0] == 'document_start') $this->ins = array_slice($this->ins, 1, $i); 478 else $this->ins = array_slice($this->ins, 0, $i); 479 480 if ($more) { 481 array_unshift($this->ins, array('document_start', array(), 0)); 482 $last = array_pop($this->ins); 483 $this->ins[] = array('p_open', array(), $last[2]); 484 $this->ins[] = array('internallink',array($this->page['id'], $this->getLang('readmore')),$last[2]); 485 $this->ins[] = array('p_close', array(), $last[2]); 486 $this->ins[] = $last; 487 $this->ins[] = array('document_end', array(), $last[2]); 488 } else { 489 $this->_finishConvert(); 490 } 491 return true; 492 } 493 494 /** 495 * Adds 'document_start' and 'document_end' instructions if not already there 496 */ 497 function _finishConvert() { 498 if ($this->ins[0][0] != 'document_start') 499 array_unshift($this->ins, array('document_start', array(), 0)); 500 $c = count($this->ins) - 1; 501 if ($this->ins[$c][0] != 'document_end') 502 $this->ins[] = array('document_end', array(), 0); 503 } 504 505 /** 506 * Remove TOC, section edit buttons and tags 507 */ 508 function _cleanXHTML($xhtml) { 509 $replace = array( 510 '!<div class="toc">.*?(</div>\n</div>)!s' => '', // remove toc 511 '#<!-- SECTION "(.*?)" \[(\d+-\d*)\] -->#e' => '', // remove section edit buttons 512 '!<div class="tags">.*?(</div>)!s' => '', // remove category tags 513 ); 514 if ($this->clevel) 515 $replace['#<div class="footnotes">#s'] = '<div class="footnotes level'.$this->clevel.'">'; 516 $xhtml = preg_replace(array_keys($replace), array_values($replace), $xhtml); 517 return $xhtml; 518 } 519 520 /** 521 * Convert footnotes to include page id to make them unique if more than 522 * one page or section are included in one wiki node. (FS#93) 523 * 524 * Gotta admit, this fix is kind of ugly, but since we have no chance to 525 * fix the generated footnote ids on instruction level, this has to be 526 * done on the generated XHTML. 527 * 528 * @param $xhtml XHTML code of the page 529 * @param $id included page's id 530 * @return XHTML code with converted footnote anchors and ids 531 * 532 * @author Gina Haeussge <osd@foosel.net> 533 */ 534 function _convertFootnotes($xhtml, $id) { 535 $id = str_replace(':', '_', $id); 536 $replace = array( 537 '!<a href="#fn__(\d+)" name="fnt__(\d+)" id="fnt__(\d+)" class="fn_top">!' => 538 '<a href="#fn__'.$id.'__\1" name="fnt__'.$id.'__\2" id="fnt__'.$id.'__\3" class="fn_top">', 539 '!<a href="#fnt__(\d+)" id="fn__(\d+)" name="fn__(\d+)" class="fn_bot">!' => 540 '<a href="#fnt__'.$id.'__\1" name="fn__'.$id.'__\2" id="fn__'.$id.'__\3" class="fn_bot">', 541 ); 542 $xhtml = preg_replace(array_keys($replace), array_values($replace), $xhtml); 543 return $xhtml; 544 } 545 546 /** 547 * Optionally display logo for the first tag found in the included page 548 */ 549 function _showTagLogos() { 550 if ((!$this->getConf('showtaglogos')) 551 || (plugin_isdisabled('tag')) 552 || (!$taghelper =& plugin_load('helper', 'tag'))) 553 return ''; 554 555 $subject = p_get_metadata($this->page['id'], 'subject'); 556 if (is_array($subject)) $tag = $subject[0]; 557 else list($tag, $rest) = explode(' ', $subject, 2); 558 $title = str_replace('_', ' ', noNS($tag)); 559 resolve_pageid($taghelper->namespace, $tag, $exists); // resolve shortcuts 560 561 $logosrc = mediaFN($logoID); 562 $types = array('.png', '.jpg', '.gif'); // auto-detect filetype 563 foreach ($types as $type) { 564 if (!@file_exists($logosrc.$type)) continue; 565 $logoID = $tag.$type; 566 $logosrc .= $type; 567 list($w, $h, $t, $a) = getimagesize($logosrc); 568 return ' style="min-height: '.$h.'px">'. 569 '<img class="mediaright" src="'.ml($logoID).'" alt="'.$title.'"/'; 570 } 571 return ''; 572 } 573 574 /** 575 * Display an edit button for the included page 576 */ 577 function _editButton() { 578 global $ID; 579 if ($this->page['exists']) { 580 if (($this->page['perm'] >= AUTH_EDIT) && (is_writable($this->page['file']))) 581 $action = 'edit'; 582 else return ''; 583 } elseif ($this->page['perm'] >= AUTH_CREATE) { 584 $action = 'create'; 585 } 586 if ($this->editbtn) { 587 $params = array('do' => 'edit'); 588 if ($this->redirect) 589 $params['redirect_id'] = $ID; 590 return '<div class="secedit">'.DOKU_LF.DOKU_TAB. 591 html_btn($action, $this->page['id'], '', $params, 'post').DOKU_LF. 592 '</div>'.DOKU_LF; 593 } else { 594 return ''; 595 } 596 } 597 598 /** 599 * Returns the meta line below the included page 600 */ 601 function _footer($page) { 602 global $conf, $ID; 603 604 if (!$this->footer) return ''; // '<div class="inclmeta"> </div>'.DOKU_LF; 605 606 $id = $page['id']; 607 $meta = p_get_metadata($id); 608 $ret = array(); 609 610 // permalink 611 if ($this->getConf('showlink')) { 612 $title = ($page['title'] ? $page['title'] : $meta['title']); 613 if (!$title) $title = str_replace('_', ' ', noNS($id)); 614 $class = ($page['exists'] ? 'wikilink1' : 'wikilink2'); 615 $link = array( 616 'url' => wl($id), 617 'title' => $id, 618 'name' => hsc($title), 619 'target' => $conf['target']['wiki'], 620 'class' => $class.' permalink', 621 'more' => 'rel="bookmark"', 622 ); 623 $ret[] = $this->renderer->_formatLink($link); 624 } 625 626 // date 627 if ($this->getConf('showdate')) { 628 $date = ($page['date'] ? $page['date'] : $meta['date']['created']); 629 if ($date) 630 $ret[] = '<abbr class="published" title="'.strftime('%Y-%m-%dT%H:%M:%SZ', $date).'">'. 631 strftime($conf['dformat'], $date). 632 '</abbr>'; 633 } 634 635 // author 636 if ($this->getConf('showuser')) { 637 $author = ($page['user'] ? $page['user'] : $meta['creator']); 638 if ($author) { 639 $userpage = cleanID($this->getConf('usernamespace').':'.$author); 640 resolve_pageid(getNS($ID), $userpage, $exists); 641 $class = ($exists ? 'wikilink1' : 'wikilink2'); 642 $link = array( 643 'url' => wl($userpage), 644 'title' => $userpage, 645 'name' => hsc($author), 646 'target' => $conf['target']['wiki'], 647 'class' => $class.' url fn', 648 'pre' => '<span class="vcard author">', 649 'suf' => '</span>', 650 ); 651 $ret[] = $this->renderer->_formatLink($link); 652 } 653 } 654 655 // comments - let Discussion Plugin do the work for us 656 if (!$page['section'] && $this->getConf('showcomments') 657 && (!plugin_isdisabled('discussion')) 658 && ($discussion =& plugin_load('helper', 'discussion'))) { 659 $disc = $discussion->td($id); 660 if ($disc) $ret[] = '<span class="comment">'.$disc.'</span>'; 661 } 662 663 // linkbacks - let Linkback Plugin do the work for us 664 if (!$page['section'] && $this->getConf('showlinkbacks') 665 && (!plugin_isdisabled('linkback')) 666 && ($linkback =& plugin_load('helper', 'linkback'))) { 667 $link = $linkback->td($id); 668 if ($link) $ret[] = '<span class="linkback">'.$link.'</span>'; 669 } 670 671 $ret = implode(DOKU_LF.DOKU_TAB.'· ', $ret); 672 673 // tags - let Tag Plugin do the work for us 674 if (!$page['section'] && $this->getConf('showtags') 675 && (!plugin_isdisabled('tag')) 676 && ($tag =& plugin_load('helper', 'tag'))) { 677 $page['tags'] = '<div class="tags"><span>'.DOKU_LF. 678 DOKU_TAB.$tag->td($id).DOKU_LF. 679 DOKU_TAB.'</span></div>'.DOKU_LF; 680 $ret = $page['tags'].DOKU_TAB.$ret; 681 } 682 683 if (!$ret) $ret = ' '; 684 $class = 'inclmeta'; 685 if ($this->header && $this->clevel && ($this->mode == 'section')) 686 $class .= ' level'.$this->clevel; 687 return '<div class="'.$class.'">'.DOKU_LF.DOKU_TAB.$ret.DOKU_LF.'</div>'.DOKU_LF; 688 } 689 690 /** 691 * Builds the ODT to embed the page to include 692 */ 693 function renderODT(&$renderer) { 694 global $ID; 695 696 if (!$this->page['id']) return ''; // page must be set first 697 if (!$this->page['exists'] && ($this->page['perm'] < AUTH_CREATE)) return ''; 698 699 // prepare variable 700 $this->renderer =& $renderer; 701 702 // get instructions and render them on the fly 703 $this->ins = p_cached_instructions($this->page['file']); 704 705 // show only a given section? 706 if ($this->page['section'] && $this->page['exists']) $this->_getSection(); 707 708 // convert relative links 709 $this->_convertInstructions(); 710 711 // render the included page 712 $backupID = $ID; // store the current ID 713 $ID = $this->page['id']; // change ID to the included page 714 // remove document_start and document_end to avoid zipping 715 $this->ins = array_slice($this->ins, 1, -1); 716 p_render('odt', $this->ins, $info); 717 $ID = $backupID; // restore ID 718 // reset defaults 719 $this->helper_plugin_include(); 720 } 721} 722//vim:ts=4:sw=4:et:enc=utf-8: 723