1<?php
2/**
3 * Allows markup syntax in the header
4 */
5
6// must be run within Dokuwiki
7if(!defined('DOKU_INC')) die();
8
9/**
10 * All DokuWiki plugins to extend the parser/rendering mechanism
11 * need to inherit from this class
12 */
13class syntax_plugin_header2 extends DokuWiki_Syntax_Plugin {
14
15    function getType() { return 'baseonly';}
16    function getPType() { return 'block';}
17    function getAllowedTypes() { return array('formatting', 'substition', 'disabled'); }
18    function getSort() { return 49; }
19
20    /**
21     * Connect pattern to lexer
22     */
23    function connectTo($mode) {
24        // For compatibility with old wiki versions:
25        //   xhtml renderer crashes with this syntax
26        //   so only apply it when header2 renderer is configured
27        global $updateVersion, $conf;
28        if ($updateVersion<38 and $conf['renderer_xhtml']!='header2') return;
29
30        $this->Lexer->addEntryPattern("[ \t]*={2,}(?=[^\n]+={2,}[ \t]*\n)", $mode, 'plugin_header2');
31    }
32
33    function postConnect() {
34        // For compatibility with old wiki versions
35        global $updateVersion, $conf;
36        if ($updateVersion<38 and $conf['renderer_xhtml']!='header2') return;
37
38        $this->Lexer->addExitPattern('={2,}[ \t]*(?=\n)', 'plugin_header2');
39    }
40
41    /**
42     * Handle the match
43     */
44    function handle($match, $state, $pos, Doku_Handler $handler){
45        switch ($state) {
46            case DOKU_LEXER_ENTER :
47                $this->h_level = 7 - strspn($match,"=");
48                $this->h_pos = $pos;
49                if ($handler->status['section']) $handler->_addCall('section_close',array(),$pos);
50                $handler->addPluginCall('header2',array($state),$state,$pos,$match);
51                $handler->CallWriter = & new Doku_Handler_Nest($handler->CallWriter,'nest_close');
52                return false;
53            case DOKU_LEXER_UNMATCHED :
54                $handler->_addCall('cdata', array($match), $pos);
55                return false;
56            case DOKU_LEXER_EXIT :
57                $handler->_addCall('nest_close', array(), $pos);
58                $handler->CallWriter->process();
59                $handler->CallWriter = & $handler->CallWriter->CallWriter;
60                $handler->addPluginCall('header2',array($state,$this->h_level,$this->h_pos),$state,$pos,$match);
61                $handler->_addCall('section_open',array($this->h_level),$pos);
62                $handler->status['section'] = true;
63                return false;
64        }
65        return false;
66    }
67
68    /**
69     * Create output
70     */
71    function render($format, Doku_Renderer $renderer, $data) {
72        list($state,$level,$pos) = $data;
73        switch ($state) {
74            case DOKU_LEXER_ENTER :
75                // store current parsed content
76                $this->store = $renderer->doc;
77                $renderer->doc  = '';
78                // metadata renderer should always parse content in the header
79                if ($format=='metadata') {
80                    $this->capture = $renderer->capture;
81                    $renderer->capture = true;
82                }
83                break;
84            case DOKU_LEXER_EXIT :
85                // retrieve content parsed by nest parser (i.e. in the header)
86                $title = trim($renderer->doc);
87                $renderer->doc = $this->store;
88                $this->store = '';
89                // restore variable
90                if ($format=='metadata') {
91                    $renderer->capture = $this->capture;
92                }
93                // create header
94                if($level < 1) $level = 1;
95                $method = '_' . $format . '_header';
96                if (method_exists($this,$method)) {
97                    // we have special procedure for the renderer
98                    $this->$method($title, $level, $pos, $renderer);
99                } else {
100                    // fall back to default renderer behavior
101                    $renderer->header($title,$level,$pos);
102                }
103                break;
104        }
105        return true;
106    }
107
108    // simple function to strip all html tags
109    function _remove_html_tags($text) {
110        return preg_replace( "#<[^>]*?>#", "" ,  $text);
111    }
112
113    /**
114     * Revised procedures for renderers
115     *
116     * Basically adds &$renderer argument and replaces $this to $renderer,
117     * and our procedures of course.
118     */
119
120    function _xhtml_header($text, $level, $pos, &$renderer) {
121        global $conf;
122
123        $displaytext = $text;  // <= added
124        $text = htmlspecialchars_decode($this->_remove_html_tags($text),ENT_QUOTES);  // <= added
125        if(!$text) return; //skip empty headlines
126
127        $hid = $renderer->_headerToLink($text,true);
128
129        //only add items within configured levels
130        $renderer->toc_additem($hid, $text, $level);
131
132        // adjust $node to reflect hierarchy of levels
133        $renderer->node[$level-1]++;
134        if ($level < $renderer->lastlevel) {
135            for ($i = 0; $i < $renderer->lastlevel-$level; $i++) {
136                $renderer->node[$renderer->lastlevel-$i-1] = 0;
137            }
138        }
139        $renderer->lastlevel = $level;
140
141        if ($level <= $conf['maxseclevel'] &&
142            count($renderer->sectionedits) > 0 &&
143            $renderer->sectionedits[count($renderer->sectionedits) - 1][2] === 'section') {
144            $renderer->finishSectionEdit($pos - 1);
145        }
146
147        // write the header
148        $renderer->doc .= DOKU_LF.'<h'.$level;
149        if ($level <= $conf['maxseclevel']) {
150            $renderer->doc .= ' class="' . $renderer->startSectionEdit($pos, 'section', $text) . '"';
151        }
152        $renderer->doc .= '><a name="'.$hid.'" id="'.$hid.'">';
153        $renderer->doc .= $displaytext;  // <= revised
154        $renderer->doc .= "</a></h$level>".DOKU_LF;
155    }
156
157    function _odt_header($text, $level, $pos, &$renderer){
158        $displaytext = $text;  // <= added
159        $text = $this->_remove_html_tags($text);  // <= added
160        $hid = $renderer->_headerToLink($text,true);
161        $renderer->doc .= '<text:h text:style-name="Heading_20_'.$level.'" text:outline-level="'.$level.'">';
162        $renderer->doc .= '<text:bookmark-start text:name="'.$hid.'"/>';
163        $renderer->doc .= $displaytext;  // <= revised
164        $renderer->doc .= '<text:bookmark-end text:name="'.$hid.'"/>';
165        $renderer->doc .= '</text:h>';
166    }
167
168    function _xml_header($text, $level, $pos, &$renderer){
169        if (!$text) return; //skip empty headlines
170        $renderer->nextHeader  = '<header level="' . $level . '" pos="' . $pos . '">'.
171        $renderer->nextHeader .= $text;  // <= revised
172        $renderer->nextHeader .= '</header>'.DOKU_LF;
173    }
174}
175