1<?php
2/**
3 * Plugin hidden: Enable to hide details
4 * v2.4
5 * @license  GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author   Guillaume Turri <guillaume.turri@gmail.com>
7 */
8
9if(!defined('DOKU_INC')) die();
10
11/**
12 * All DokuWiki plugins to extend the parser/rendering mechanism
13 * need to inherit from this class
14 */
15class syntax_plugin_hidden extends DokuWiki_Syntax_Plugin {
16
17  function getType(){ return 'container'; }
18  function getPType(){ return 'stack'; }
19  function getAllowedTypes() {
20    return array('container', 'baseonly', 'substition','protected','disabled','formatting','paragraphs');
21  }
22  function getSort(){
23    //make sure it's greater than hiddenSwitch plugin's one in order to avoid a confusion between "<hidden.*" and "<hiddenSwitch.*"
24    return 189;
25     }
26
27  // override default accepts() method to allow nesting
28  // - ie, to get the plugin accepts its own entry syntax
29  function accepts($mode) {
30    if ($mode == substr(get_class($this), 7)) return true;
31    return parent::accepts($mode);
32    }
33
34  function connectTo($mode) {
35    $this->Lexer->addEntryPattern('<hidden\b.*?>(?=.*?</hidden>)', $mode,'plugin_hidden');
36    $this->Lexer->addSpecialPattern('<hiddenSwitch[^>]*>', $mode,'plugin_hidden');
37     }
38  function postConnect() {
39    $this->Lexer->addExitPattern('</hidden>','plugin_hidden');
40  }
41
42  function handle($match, $state, $pos, Doku_Handler $handler) {
43    switch ($state) {
44      case DOKU_LEXER_SPECIAL:
45        //hiddenSwitch
46        $return = array('text' => $this->getLang('switch.default'), 'type' => 'switch');
47        $match = trim(utf8_substr($match, 14, -1)); //14 = strlen("<hiddenSwitch ")
48        if ( $match !== '' ){
49          $return['text'] = $match;
50        }
51        $return['text'] = htmlspecialchars($return['text']);
52        return $return;
53
54      case DOKU_LEXER_ENTER :
55          $return = array(
56                'active' => 'true',
57                'element'=>Array(),
58                'onHidden'=>'',
59                'onVisible'=>'',
60                'initialState'=>'hidden',
61                'state'=>$state,
62                'printHead' => true,
63                'bytepos_start' => $pos,
64                'edit' => false,
65                'editText' => $this->getLang('edit'),
66                'onExportPdf' => ''
67              );
68           $match = substr($match, 7, -1); //7 = strlen("<hidden")
69
70        //Looking for the initial state
71        preg_match("/initialState *= *\"([^\"]*)\" ?/i", $match, $initialState);
72        if ( count($initialState) != 0) {
73          $match = str_replace($initialState[0], '', $match);
74          $initialState = strtolower(trim($initialState[1]));
75          if ( $initialState == 'visible'
76            || $initialState == 'true'
77            || $initialState == 'expand' ) {
78            $return['initialState'] = 'visible';
79          }
80        }
81
82        //Looking for the -noPrint option
83        if ( preg_match('/-noprint/i', $match, $found) ){
84            $return['printHead'] = false;
85            $match = str_replace($found[0], '', $match);
86        }
87
88        //Looking for the -editable option
89        if ( preg_match('/-edit(able)?( *= *"([^"]*)")?/i', $match, $found) ){
90            if ( count($found) > 1 ){
91                $return['editText'] = end($found);
92            }
93            $return['edit'] = true;
94            $match = str_replace($found[0], '', $match);
95        }
96
97           //Looking if this block is active
98           preg_match("/active *= *\"([^\"]*)\" ?/i", $match, $active);
99           if( count($active) != 0 ){
100             $match = str_replace($active[0], '', $match);
101             $active = strtolower(trim($active[1]));
102             if($active=='false' || $active=='f' || $active=='0' || $active=='n'){
103               $return['active'] = false;
104             }
105           }
106
107           //Looking for the element(s) of the block (ie: which switches may activate this element)
108           preg_match("/element *= *\"([^\"]*)\" ?/i", $match, $element);
109           if( count($element) != 0 ){
110             $match = str_replace($element[0], '', $match);
111             $element[1] = htmlspecialchars($element[1]);
112             $return['element'] = explode(' ', $element[1]);
113           }
114
115        //Looking for the texts to display
116        $this->_grepOption($return, 'onHidden', $match);
117        $this->_grepOption($return, 'onVisible', $match);
118        $this->_grepOption($return, 'onExportPdf', $match);
119
120        //If there were neither onHidden nor onVisible, take what's left
121        if( $return['onHidden']=='' && $return['onVisible']=='' ){
122             $text = trim($match);
123             if($text != ''){
124               $return['onHidden'] = $text;
125               $return['onVisible'] = $text;
126             } else { //if there's nothing left, take the default texts
127               $return['onHidden'] = $this->getConf('default_text_when_hidden');
128               $return['onVisible'] = $this->getConf('default_text_when_displayed');
129             }
130        } else { //if one string is specified but not the other, take the defaul text
131          $return['onHidden'] = ($return['onHidden']!='') ? $return['onHidden'] : $this->getConf('default_text_when_hidden');
132          $return['onVisible'] = ($return['onVisible']!='') ? $return['onVisible'] : $this->getConf('default_text_when_displayed');
133        }
134
135        //If we don't have an exportPdf text, take the onVisible one, since the block will be exported unfolded
136        if ( $return['onExportPdf'] == '' ){
137          $return['onExportPdf'] = $return['onVisible'];
138        }
139
140        return $return;
141
142      case DOKU_LEXER_UNMATCHED :
143        return array('state'=>$state, 'text'=>$match);
144
145      default:
146        return array('state'=>$state, 'bytepos_end' => $pos + strlen($match));
147      }
148  } // handle()
149
150  private function _grepOption(&$options, $tag, &$match){
151    preg_match("/$tag *= *\"([^\"]*)\" ?/i", $match, $text);
152    if ( count($text) != 0 ){
153      $match = str_replace($text[0], '', $match);
154      $options[$tag] = $text[1];
155    }
156  }
157
158  function render($mode, Doku_Renderer $renderer, $data) {
159    if ( $this->_exportingPDF() ){
160      $data['onVisible'] = $data['onExportPdf'];
161    }
162
163    if($mode == 'xhtml' && array_key_exists('type', $data) && $data['type'] == 'switch') {
164      $renderer->doc .= '<button class="button hiddenSwitch">'.$data['text'].'</button>';
165      return true;
166    }
167    if($mode == 'xhtml'){
168      switch ($data['state']) {
169        case DOKU_LEXER_ENTER :
170           $this->editableBlocks[] = $data['edit'];
171           $classEdit = ($data['edit'] ? $renderer->startSectionEdit($data['bytepos_start'], 'section', $data['editText']) : '');
172           $tab = array();
173           $onVisible = p_render('xhtml', p_get_instructions($data['onVisible']), $tab);
174           $onHidden = p_render('xhtml', p_get_instructions($data['onHidden']), $tab);
175
176          // "\n" are inside tags to avoid whitespaces in the DOM with FF
177          $renderer->doc .= '<div class="hiddenGlobal '.$classEdit;
178          $renderer->doc .= $data['active'] ? ' hiddenActive' : '';
179          $renderer->doc .= '">';
180
181
182
183          $renderer->doc .= '<div class="hiddenElements">';
184          foreach($data['element'] as $element){
185            $renderer->doc .= ' '.$element;
186          }
187          $renderer->doc .= "</div>";
188
189          $renderer->doc .= '<div class="hiddenHead ';
190          $renderer->doc .= $data['printHead'] ? '' : 'hiddenNoPrint';
191          $renderer->doc .= ($data['initialState'] == 'hidden') ? ' hiddenSinceBeginning' : '';
192          $renderer->doc .= '">';
193          $renderer->doc .=   '<div class="hiddenOnHidden">'.$onHidden."</div>"; //text displayed when hidden
194          $renderer->doc .=   '<div class="hiddenOnVisible">'.$onVisible."</div>"; //text displayed when expanded
195          $renderer->doc .= '</div> <!-- .hiddenHead -->';
196
197          $renderer->doc .= '<div class="hiddenBody">';
198          break;
199
200        case DOKU_LEXER_UNMATCHED :
201          $text = $renderer->_xmlEntities($data['text']);
202          if (  preg_match("/^[ \t]*={2,}[^\n]+={2,}[ \t]*$/", $text, $match) ){
203            $title = trim($match[0]);
204             $level = 7 - strspn($title,'=');
205             if($level < 1) $level = 1;
206             $title = trim($title,'=');
207             $title = trim($title);
208            $renderer->header($title, $level, 0);
209          } else {
210            $renderer->doc .= $text;
211          }
212          break;
213
214        case DOKU_LEXER_EXIT :
215          $renderer->doc .= "</div></div>"; //close hiddenBody and hiddenGlobal
216          if ( array_pop($this->editableBlocks) ){
217              $renderer->finishSectionEdit($data['bytepos_end']);
218          }
219          break;
220      }
221      return true;
222    }
223
224    if ($mode == 'odt') {
225      if ($data['state'] == DOKU_LEXER_UNMATCHED && $data['type'] != 'switch') {
226        $renderer->doc .= $renderer->_xmlEntities($data['text']);
227      }
228      return true;
229    }
230
231    return false;
232  } // render()
233
234  private function _exportingPDF(){
235    global $ACT;
236    return ($ACT == 'export_pdf' || $ACT == 'export_pdfbook' || $ACT == 'export_odt');
237  }
238
239  var $editableBlocks = array();
240
241} // class syntax_plugin_nspages
242