1<?php
2/**
3 * TocTweak plugin for DokuWiki; Syntax autotoc
4 * set top and max level of headlines to be found in table of contents
5 * render toc placeholder to show built-in toc box in the page
6 *
7 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
8 * @author     Satoshi Sahara <sahara.satoshi@gmail.com>
9 */
10
11if(!defined('DOKU_INC')) die();
12
13class syntax_plugin_toctweak_autotoc extends DokuWiki_Syntax_Plugin {
14
15    protected $mode;
16    protected $pattern = array(
17        5 => '~~(?:TOC_HERE|NOTOC|TOC)\b.*?~~',
18    );
19
20    const TOC_HERE = '<!-- TOC_HERE -->'.DOKU_LF;
21
22    function __construct() {
23        $this->mode = substr(get_class($this), 7); // drop 'syntax_' from class name
24    }
25
26    function getType() { return 'substition'; }
27    function getPType(){ return 'block'; }
28    function getSort() { return 29; } // less than Doku_Parser_Mode_notoc = 30
29
30    /**
31     * Connect pattern to lexer
32     */
33    function connectTo($mode) {
34        $this->Lexer->addSpecialPattern($this->pattern[5], $mode, $this->mode);
35    }
36
37    /**
38     * Handle the match
39     */
40    function handle($match, $state, $pos, Doku_Handler $handler) {
41        global $ID;
42        static $call_counter = [];  // holds number of ~~TOC_HERE~~ used in the page
43
44        // load helper object
45        isset($tocTweak) || $tocTweak = $this->loadHelper($this->getPluginName());
46
47        // parse syntax
48        preg_match('/^~~([A-Z_]+)/', $match, $m);
49        $start = strlen($m[1]) +2;
50        $param = substr($match, $start+1, -2);
51        list($topLv, $maxLv, $tocClass) = $tocTweak->parse($param);
52
53        if ($m[1] == 'TOC_HERE') {
54            // ignore ~~TOC_HERE~~ macro appeared more than once in a page
55            if ($call_counter[$ID]++ > 0) return;
56            $tocPosition = -1;
57        } else {
58            // TOC or NOTOC
59            if ($m[1] == 'NOTOC') {
60                $handler->_addCall('notoc', array(), $pos);
61                $tocPosition = 9;
62            } else {
63                $tocPosition = null;
64            }
65        }
66
67        return $data = array($ID, $tocPosition, $topLv, $maxLv, $tocClass);
68    }
69
70    /**
71     * Create output
72     */
73    function render($format, Doku_Renderer $renderer, $data) {
74        global $ID;
75
76        list($id, $tocPosition, $topLv, $maxLv, $tocClass) = $data;
77
78        // skip calls that belong to different page (eg. included pages)
79        if ($id != $ID) return false;
80
81        switch ($format) {
82            case 'metadata':
83                // store matadata to overwrite $conf in PARSER_CACHE_USE event handler
84                isset($tocPosition) && $renderer->meta['toc']['position'] = $tocPosition;
85                isset($topLv)       && $renderer->meta['toc']['toptoclevel'] = $topLv;
86                isset($maxLv)       && $renderer->meta['toc']['maxtoclevel'] = $maxLv;
87                isset($tocClass)    && $renderer->meta['toc']['class'] = $tocClass;
88                return true;
89
90            case 'xhtml':
91                // render PLACEHOLDER, which will be replaced later
92                // through action event handler handlePostProcess()
93                if (isset($tocPosition)) {
94                    $renderer->doc .= self::TOC_HERE;
95                    return true;
96                }
97        } // end of switch
98        return false;
99    }
100
101}
102