1<?php 2/** 3 * DokuWiki Plugin ifauthex (Syntax Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Pietro Saccardi <lizardm4@gmail.com> 7 */ 8 9// must be run within Dokuwiki 10if (!defined('DOKU_INC')) { 11 die(); 12} 13 14require_once(__DIR__ . '/lib/grammar.php'); 15 16class syntax_plugin_ifauthex extends DokuWiki_Syntax_Plugin 17{ 18 private $_nestedRenderPermission = [true]; 19 20 /** 21 * Returns a boolean representing whether at this depth level rendering is allowed. 22 */ 23 private function innermostShouldRender() 24 { 25 return end($this->_nestedRenderPermission); 26 } 27 28 /** 29 * Pushes the permission to render of the current tag into the stack. 30 * It might not change the value of @ref innermostShouldRender, if rendering was 31 * already previously disabled by an outer tag. 32 */ 33 private function pushInnerPermission($perm) { 34 array_push($this->_nestedRenderPermission, $perm && $this->innermostShouldRender()); 35 } 36 37 /** 38 * Returns a boolean representing whether the content at the current nesting level is being rendered. 39 * It then pops this boolean from the stack. 40 */ 41 private function popShouldRender() { 42 return array_pop($this->_nestedRenderPermission); 43 } 44 45 /** @inheritDoc */ 46 public function getType() 47 { 48 return 'formatting'; 49 } 50 51 /** @inheritDoc */ 52 function getAllowedTypes() 53 { 54 return array('container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs', 'baseonly'); 55 } 56 57 58 /** @inheritDoc */ 59 function accepts($mode) { 60 // Allow nesting the mode 61 if ($mode === 'plugin_ifauthex') { 62 return true; 63 } 64 return parent::accepts($mode); 65 } 66 67 /** @inheritDoc */ 68 function getPType() 69 { 70 return 'stack'; 71 } 72 73 /** @inheritDoc */ 74 public function getSort() 75 { 76 return 195; 77 } 78 79 /** @inheritDoc */ 80 public function connectTo($mode) 81 { 82 $this->Lexer->addEntryPattern('<ifauth\b.*?>(?=.*?</ifauth>)', $mode, 'plugin_ifauthex'); 83 } 84 85 /** @inheritDoc */ 86 public function postConnect() 87 { 88 $this->Lexer->addExitPattern('</ifauth>', 'plugin_ifauthex'); 89 } 90 91 /** @inheritDoc */ 92 public function handle($match, $state, $pos, Doku_Handler $handler) 93 { 94 global $conf; 95 switch ($state) { 96 case DOKU_LEXER_ENTER: 97 $matches = null; 98 preg_match('/^<ifauth\b(.*?)>$/', $match, $matches); 99 if (is_array($matches) && count($matches) > 0) { 100 // The last group contains the expression. 101 // Can't already pre-parse because DokuWiki serializes the 102 // objects that are returned, but it doesn't know about our 103 // custom classes at this point. 104 return array($state, $matches[count($matches) - 1]); 105 } 106 return array($state, null); 107 case DOKU_LEXER_MATCHED: 108 break; 109 case DOKU_LEXER_UNMATCHED: 110 return array($state, $match); 111 case DOKU_LEXER_EXIT: 112 return array($state, null); 113 } 114 return false; 115 } 116 117 /** @inheritDoc */ 118 public function render($mode, Doku_Renderer $renderer, $data) 119 { 120 list($state, $exprOrMatch) = $data; 121 122 // never cache 123 $renderer->nocache(); 124 125 switch ($state) { 126 case DOKU_LEXER_ENTER: 127 if ($exprOrMatch === null) { 128 // something went wrong 129 return false; 130 } 131 try { 132 // check if current user should see the content 133 $exprOrMatch = auth_expr_parse($exprOrMatch); 134 $exprPermission = (bool) $exprOrMatch->evaluate(); 135 $shouldRender = $this->innermostShouldRender() && $exprPermission; 136 137 // Save the state only at the first occurrence 138 if ($this->innermostShouldRender() && !$exprPermission) { 139 if ($renderer->getFormat() === 'xhtml') { 140 // save the level so we can detect if we have opened a new section via a header or not 141 $renderer->meta['ifauthex.originalLevel'] = $renderer->getLastlevel(); 142 } 143 144 // point the renderer's doc to something else, remembering the old one 145 $renderer->meta['ifauthex.originalDoc'] = &$renderer->doc; 146 $ignoredDoc = is_array($renderer->doc) ? [] : ''; 147 $renderer->doc = &$ignoredDoc; 148 149 // do the same for the toc list 150 $ignoredToc = []; 151 $renderer->meta['ifauthex.originalToc'] = &$renderer->toc; 152 $renderer->toc = &$ignoredToc; 153 154 // patch the global TOC, if defined 155 global $TOC; 156 $renderer->meta['ifauthex.originalGlobalToc'] = &$TOC; 157 $TOC = is_array($TOC) ? [] : null; 158 } 159 160 // Push the new rendering permission 161 $this->pushInnerPermission($exprPermission); 162 163 } catch (Exception $e) { 164 // something went wrong parsing the expression 165 msg(hsc($e->getMessage()), -1); 166 return false; 167 } 168 break; 169 case DOKU_LEXER_UNMATCHED: 170 $renderer->cdata($exprOrMatch); 171 break; 172 case DOKU_LEXER_EXIT: 173 // point the renderer's doc and toc back to the original 174 $shouldRenderOld = $this->popShouldRender(); 175 176 // Become active if innermostShouldRender changes from false to true 177 if (!$shouldRenderOld && $this->innermostShouldRender()) { 178 $renderer->doc = &$renderer->meta['ifauthex.originalDoc']; 179 $renderer->toc = &$renderer->meta['ifauthex.originalToc']; 180 181 global $TOC; 182 $TOC = &$renderer->meta['ifauthex.originalGlobalToc']; 183 184 if ($renderer->getFormat() === 'xhtml') { 185 /* 186 Detect whether a section was opened in the meanwhile. This happens due to a header being issued inside 187 the hidden section. However, if we started at lev 0, and gotten at lev > 0, the lexer/parser combo will 188 have included a section_close command somewhere in the remaining part of the document. 189 We thus have to match that section_close with a corresponding section_open; the opening has occurred 190 within the hidden body, so we manually add a patch section_open entry. 191 See https://github.com/mittelab/ifauthex-dokuwiki-plugin/issues/8. 192 */ 193 if ($renderer->meta['ifauthex.originalLevel'] === 0 && $renderer->getLastlevel() > 0) { 194 $renderer->section_open($renderer->getLastlevel()); 195 } 196 } 197 } 198 break; 199 } 200 201 return true; 202 } 203} 204