<?php
/**
 * Partial Include Plugin: displays parts of a wiki page within another
 * Usage:
 * {{subpage>page}} for "page" in same namespace
 * {{subpage>:page}} for "page" in top namespace
 * {{subpage>namespace:page}} for "page" in namespace "namespace"
 * {{subpage>.namespace:page}} for "page" in subnamespace "namespace"
 * {{subpage>page#n_sections;optional_min_level}} for a section of "page"
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Gwenole Beauchesne <masked-email>
 */
 
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_partial_include extends DokuWiki_Syntax_Plugin {
 
    /**
     * return some info
     */
    function getInfo(){
        return array(
            'author' => 'Gwenole Beauchesne',
            'email'  => '<masked-email>',
            'date'   => '2005-11-01',
            'name'   => 'Partial Include Plugin',
            'desc'   => 'displays parts of a wiki page within another',
            'url'    => '',
        );
    }
 
    /**
     * What kind of syntax are we?
     */
    function getType(){
        return 'substition';
    }
 
    /**
     * Where to sort in?
     */
    function getSort(){
        return 309;
    }
 
    /**
     * Paragraph Type
     */
    function getPType(){
        return 'block';
    }
 
    /**
     * Connect pattern to lexer
     */
    function connectTo($mode) {
        $this->Lexer->addSpecialPattern('\{\{subpage.+?\}\}',$mode,'plugin_partial_include');
    }
 
    /**
     * Handle the match
     */
    function handle($match, $state, $pos, &$handler){
        global $ID;
        global $filechain;
 
        $match = substr($match,10,-2);                // strip markup
        $match = preg_split('/\#/u',$match,2);        // split hash from filename
        resolve_pageid(getNS($ID),$match[0],$exists); // resolve shortcuts
 
        // check for existence and permission
        if ((!$exists) || (auth_quickaclcheck($match[0]) < 1)) return false;
 
        // check for and establish start of $filechain
        if (!isset($filechain)) $filechain[] = $ID;
 
        // don't allow the same file to be included more than once
        if (in_array($match[0], $filechain)) return false;
 
        // add included page to the filechain
        $filechain[] = $match[0];
 
        // get sections information
        list($num, $level, $type) = explode(';',$match[1],3);
        if ($num == 0)
            $num = 1;
        if ($level == 0)
            $level = 1;
        if ($type == '')
            $type = 'header';
        switch ($type) {
        case 'header': case 'list': break;
        default: return false;
        }
 
        return array($match[0], $num, $level, $type);
    }    
 
    /**
     * Create output
     */
    function render($mode, &$renderer, $data) {
 
        if($mode == 'xhtml'){
            $file = wikiFN($data[0]);
            if (!@file_exists($file)) return false;
 
            // get instructions
            $instr = p_cached_instructions($file, false);
 
            // filter section
            $instr = $this->{'_getSection_'.$data[3]}($data[1], $data[2], $more, $instr);
 
            // correct relative internal links and media
            $instr = $this->_correctRelNS($instr, $data[0]);
 
            // render the instructions on the fly
            $text = p_render('xhtml',$instr,$info);
 
            // remove toc, section edit buttons and category tags
            $patterns = array('!<div class="toc">.*?(</div>\n</div>)!s',
                              '#<!-- SECTION \[(\d*-\d*)\] -->#e',
                              '!<div class="category">.*?</div>!s');
            $replace  = array('','','');
            $text = preg_replace($patterns,$replace,$text);
 
            // prevent caching to ensure the included page is always fresh
            $renderer->info['cache'] = FALSE;
 
            // embed the included page
            $renderer->doc .= $text;
 
            // append continuation link
            if ($more) {
                $level = $data[2];
                $mlink = $renderer->internallink($data[0],'...','','true');
                switch ($data[3]) {
                case 'header':
                    $renderer->doc .= "<div class=\"level$level\">[$mlink]</div>";
                    break;
                case 'list': // <ul> is open at this stage
                    $renderer->doc .= "<li class=\"level$level\"><div class=\"li\">[$mlink]</div></li></ul>";
                }
            }
 
            return true;
        }
        return false;
    }
 
    /**
     * Get N 'header' sections including their subsections
     */
    function _getSection_header($max_sections,$min_level,&$more,$instructions){
        $more = 0;
        $n_sections = 0;
        foreach ($instructions as $instruction){
            if ($instruction[0] == 'header'){
                $level = $instruction[1][1];
                if ($level >= $min_level) {
                    if ($level == $min_level) {
                        $n_sections += 1;
                        if ($n_sections > $max_sections) {
                            $more = 1;
                            return $i;
                        }
                    }
                    $i[] = $instruction;
                } elseif ($n_sections && $level < $min_level)
                    return $i;
            } elseif ($n_sections) {
                $i[] = $instruction;
            }
        }
        return $i;
    }
 
    /**
     * Get N 'list items including their sublists
     * XXX factorisation candidate
     */
    function _getSection_list($max_sections,$min_level,&$more,$instructions){
        $more = 0;
        $n_sections = 0;
        for ($it = 0; $it < sizeof($instructions); $it++){
            $instruction = $instructions[$it];
            if ($instruction[0] == 'listitem_open'){
                $level = $instruction[1][0];
                if ($level >= $min_level) {
                    if ($level == $min_level) {
                        $n_sections += 1;
                        if ($n_sections == 1) 
                            $i[] = $instructions[$it-1];
                        elseif ($n_sections > $max_sections) {
                            $more = 1;
                            return $i;
                        }
                    }
                    $i[] = $instruction;
                } elseif ($n_sections && $level < $min_level)
                    return $i;
            } elseif ($n_sections) {
                $i[] = $instruction;
            }
        }
        return $i;
    }
 
    /**
     * Corrects relative internal links and media
     */
    function _correctRelNS($instr,$incl){
        global $ID;
 
        // check if included page is in same namespace
        $iNS = getNS($incl);
        if (getNS($ID) == $iNS) return $instr;
 
        // convert internal links and media from relative to absolute
        $n = count($instr);
        for($i = 0; $i < $n; $i++){
            if (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];
                }
            }
        }
        return $instr;
    }
}
 
//Setup VIM: ex: et ts=4 enc=utf-8 :