Include plugin (modified)

Mods over standard:

  • no separate include file, its has been merged with the plugin class in syntax.php.
  • support multiple non-recursive section includes in the same page,
  • offers two syntaxes, {{page> ... }} & {{section> ... }}.
    • page doesn't attempt to merge indentation, allowing the include to be used within other block syntax modes - e.g. folded or box.
    • section will attempt to align the include information with the current indentation level, allowing sections to be included seemlessly. This should only be used in plain DokuWiki markup as it will result in invalid XHTML if used within other blocks.

Section is aimed more at including sections and page at including whole pages, however both can be used to include whole pages or individual sections. The key difference is how they will interact with sections and block syntax modes on the host wiki page.

<?php
/**
 * Include Plugin: displays a wiki page within another
 * Usage:
 * {{page>page}} for "page" in same namespace
 * {{page>:page}} for "page" in top namespace
 * {{page>namespace:page}} for "page" in namespace "namespace"
 * {{page>.namespace:page}} for "page" in subnamespace "namespace"
 * {{page>page#section}} for a section of "page"
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Esther Brunner <esther@kaffeehaus.ch>
 */
 
if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
require_once(DOKU_PLUGIN.'syntax.php');
 
/**
 * All DokuWiki plugins to extend the parser/rendering mechanism
 * need to inherit from this class
 */
class syntax_plugin_include extends DokuWiki_Syntax_Plugin {
 
  var $filechain = array();
  var $sectionchain = array();
 
  /**
   * return some info
   */
  function getInfo(){
    return array(
      'author' => 'Esther Brunner',
      'email'  => 'esther@kaffeehaus.ch',
      'date'   => '2006-04-27',
      'name'   => 'Include Plugin',
      'desc'   => 'Displays a wiki page (or a section thereof) within another',
      'url'    => 'http://wiki.splitbrain.org/plugin:include',
    );
  }
 
  function getType(){ return 'substition'; }
  function getSort(){ return 303; }
  function getPType(){ return 'block'; }
  function connectTo($mode) { 
    $this->Lexer->addSpecialPattern("{{page>.+?}}",$mode,'plugin_include'); 
    $this->Lexer->addSpecialPattern("{{section>.+?}}",$mode,'plugin_include');
  }
 
  /**
   * Handle the match
   */
  function handle($match, $state, $pos, &$handler){
 
      // break the pattern up into its constituent parts
      list($include, $id, $section) = preg_split('/>|#/u',substr($match,2,-2),3);
      return array($include, $id, cleanID($section));
  }    
 
  /**
   * Create output
   */
  function render($mode, &$renderer, $data) {
    global $ID;
 
    list($include, $id, $section) = $data;
 
    $id = $this->_applyMacro($id);
    resolve_pageid(getNS($ID), $id, $exists); // resolve shortcuts
 
    // check for permission
    $perm = auth_quickaclcheck($id);
    if ($perm < AUTH_READ) return true;
 
    // prevent include recursion
    if ($id == $ID) return $this->_error($mode, $renderer, 'error_currentpage');
 
    if ($this->_in_filechain($id,$section)) return $this->_error($mode, $renderer, 'error_recursion');    
 
    array_push($this->filechain, $id.'#'.$section);
 
    // prevent caching to ensure the included page is always fresh
    $renderer->info['cache'] = FALSE;
 
    $ok = $this->_include($mode, $renderer, $include, $id, $section);
 
    array_pop($this->filechain);
    return $ok;
  }
 
  function _in_filechain($id,$section) {
    $id = preg_quote($id,'/');
    $section = preg_quote($section,'/');
 
    $pattern = $section ? "/^($id#$section|$id#)$/" : "/^$id#/";
    $match = preg_grep($pattern, $this->filechain);
    return (!empty($match));
  }
 
  function _error($mode, &$renderer, $error) {
//    $renderer->doc .= "<p>".$this->getLang($error)."</p>\n";
    return false;
  }
 
  function _include($mode, &$renderer, $include, $id, $section) {
 
    $file    = wikiFN($id);
 
    if ($mode == 'xhtml') {
      // current section level
      $matches = array();
      preg_match_all('|<div class="level(\d)">|i', $renderer->doc, $matches, PREG_SET_ORDER);
      $n = count($matches)-1;
      if ($n > -1) $clevel = $matches[$n][1];
      else $clevel = 0;
 
      // get instructions and render them on the fly
      $instr   = p_cached_instructions($file, false);
      if (!empty($instr)) {
        if ($section) $instr = $this->_getSection($section, $instr);
        $instr   = $this->_convertInstructions($instr, $id, $renderer, $clevel, $ok);
        $content = p_render('xhtml', $instr, $info);
        $content = $this->_cleanXHTML($content);
      } else {
//    $renderer->doc .= "<p>".$this->getLang('error_pagenotfound')."</p>\n";
      }
 
      // embed the included page
      if ($clevel && $ok  && ($include=='section')) $renderer->doc .= '</div>';
      $renderer->doc .= '<div class="include">';
      $renderer->doc .= $content.$this->_editButton($id, $file, $perm);
      $renderer->doc .= '</div>';
      if ($clevel && $ok  && ($include=='section')) $renderer->doc .= '<div class="level'.$clevel.'">'; 
 
      return true;
    }
 
    return false; 
  }
 
  function _applyMacro($id){
    global $INFO;
 
    $replace = array(
      '@USER@'  => cleanID($_SERVER['REMOTE_USER']),
      '@NAME@'  => cleanID($INFO['userinfo']['name']),
      '@YEAR@'  => date('Y'),
      '@MONTH@' => date('m'),
      '@DAY@'   => date('d'),
    );
    return str_replace(array_keys($replace), array_values($replace), $id);
  }
 
  /**
   * Get a section including its subsections
   */
  function _getSection($title, $instructions){
    foreach ($instructions as $instruction){
      if ($instruction[0] == 'header'){
 
        // found the right header
        if (cleanID($instruction[1][0]) == $title){
          $level = $instruction[1][1];
          $i[] = $instruction;
 
        // next header of the same or higher level -> exit
        } elseif ($instruction[1][1] <= $level){
          return $i;
        } elseif (isset($level)){
          $i[] = $instruction;
        }
 
      // add instructions from our section
      } elseif (isset($level)){
        $i[] = $instruction;
      }
    }
    return $i;
  }
 
  /**
   * Corrects relative internal links and media and
   * converts headers of included pages to subheaders of the current page
   */
  function _convertInstructions($instr, $incl, &$renderer, $clevel, &$ok, $firstseconly=false){
    global $ID;
    global $conf;
 
    // check if included page is in same namespace
    $iNS = getNS($incl);
    if (getNS($ID) == $iNS) $convert = false;
    else $convert = true;
 
    $n = count($instr);
    for ($i = 0; $i < $n; $i++){
 
      // convert internal links and media from relative to absolute
      if ($convert && (substr($instr[$i][0], 0, 8) == 'internal')){
 
        // relative subnamespace
        if ($instr[$i][1][0]{0} == '.'){
          $instr[$i][1][0] = $iNS.':'.substr($instr[$i][1][0], 1);
 
        // relative link
        } elseif (strpos($instr[$i][1][0],':') === false) {
          $instr[$i][1][0] = $iNS.':'.$instr[$i][1][0];
        }
 
      // set header level to current section level + header level
      } elseif ($instr[$i][0] == 'header'){
        $level = $instr[$i][1][1]+$clevel;
        if ($level > 5) $level = 5;
        $instr[$i][1][1] = $level;
 
        // add TOC items
        if ($level >= $conf['toptoclevel'] && $level <= $conf['maxtoclevel']){
          $text = $instr[$i][1][0];
          $hid  = $renderer->_headerToLink($text, 'true');
          $renderer->toc[] = array(
            'hid'   => $hid,
            'title' => $text,
            'type'  => 'ul',
            'level' => $level-$conf['toptoclevel']+1
          );
 
          $ok = true;
        }
 
      // the same for sections
      } elseif ($instr[$i][0] == 'section_open'){
        $level = $instr[$i][1][0]+$clevel;
        if ($level > 5) $level = 5;
        $instr[$i][1][0] = $level;
 
      // show only the first section?
      } elseif ($firstseconly && ($instr[$i][0] == 'section_close') && ($instr[$i-1][0] != 'section_open')){
        if ($instr[0][0] == 'document_start') return array_slice($instr, 1, $i);
        else return array_slice($instr, 0, $i);
 
      }
    }
    if ($instr[0][0] == 'document_start') return array_slice($instr, 1, -1);
    else return $instr;
  }
 
  /**
   * Remove TOC, section edit buttons and tags
   */
  function _cleanXHTML($xhtml){
    $replace  = array(
      '!<div class="toc">.*?(</div>\n</div>)!s' => '', // remove toc
      '#<!-- SECTION \[(\d*-\d*)\] -->#e'       => '', // remove section edit buttons
      '!<div id="blog__tags">.*?(</div>)!s'     => ''  // remove category tags
    );
    $xhtml  = preg_replace(array_keys($replace), array_values($replace), $xhtml);
    return $xhtml;
  }
 
  /**
   * Display an edit button for the included page
   */
  function _editButton($id, $file, $perm){
    $ret = '';
    if (@file_exists($file)){
      if (($perm >= AUTH_EDIT) && (is_writable($file))) $ret = '<div class="secedit">'.html_btn('edit', $id, 'e', array('do' => 'edit'), 'post').'</div>';
    } elseif ($perm >= AUTH_CREATE){
      $ret = '<div class="secedit">'.html_btn('create', $id, 'e', array('do' => 'edit'), 'post').'</div>';
    }
    return $ret;
  }
}
 
//Setup VIM: ex: et ts=4 enc=utf-8 :
 
tutorials/include.txt · Last modified: 2010/05/31 16:21 (external edit)
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki