1<?php 2/** 3 * Add-New-Page (Deluxe) Plugin: a simple form for adding new pages. 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Bjoern Ellebrecht <development@c0de8.com> 7 * 8 * @noinspection PhpUnused, 9 * PhpMissingParamTypeInspection, PhpMissingReturnTypeInspection 10 */ 11 12// must be run within Dokuwiki 13use dokuwiki\File\PageResolver; 14 15if(!defined('DOKU_INC')) die(); 16 17class syntax_plugin_addnewpagedeluxe extends DokuWiki_Syntax_Plugin 18{ 19 /** 20 * Syntax Type 21 */ 22 public function getType() { 23 return 'substition'; 24 } 25 26 /** 27 * Paragraph Type 28 */ 29 public function getPType() { 30 return 'block'; 31 } 32 33 /** 34 * @return int 35 */ 36 public function getSort() { 37 return 199; 38 } 39 40 /** 41 * @param string $mode 42 */ 43 public function connectTo($mode) { 44 $this->Lexer->addSpecialPattern('\{\{NEWPAGE[^\}]*\}\}', $mode, 'plugin_addnewpagedeluxe'); 45 } 46 47 /** 48 * Handler to prepare matched data for the rendering process 49 * 50 * Handled syntax options: 51 * {{NEWPAGE}} 52 * {{NEWPAGE>your:namespace}} 53 * {{NEWPAGE#newtpl1,newtpl2}} 54 * {{NEWPAGE#newtpl1|Title1,newtpl2|Title1}} 55 * {{NEWPAGE>your:namespace#newtpl1|Title1,newtpl2|Title1}} 56 * {{NEWPAGE>your:namespace#newtpl1|Title1,newtpl2|Title1#@HI@,Howdy}} 57 * 58 * @param string $match The text matched by the patterns 59 * @param int $state The lexer state for the match 60 * @param int $pos The character position of the matched text 61 * @param Doku_Handler $handler The Doku_Handler object 62 * @return array Return an array with all data you want to use in render 63 * @codingStandardsIgnoreStart 64 */ 65 public function handle($match, $state, $pos, Doku_Handler $handler) { 66 /* @codingStandardsIgnoreEnd */ 67 $options = substr($match, 9, -2); // strip markup 68 $options = explode('#', $options, 3); 69 70 $namespace = trim(ltrim($options[0], '>')); 71 $templates = explode(',', $options[1] ?? ''); 72 $templates = array_map('trim', $templates); 73 $newpagevars = trim($options[2] ?? ''); 74 return array( 75 'namespace' => $namespace, 76 'newpagetemplates' => $templates, 77 'newpagevars' => $newpagevars 78 ); 79 } 80 81 /** 82 * Create the new-page form. 83 * 84 * @param $format string output format being rendered 85 * @param $renderer Doku_Renderer the current renderer object 86 * @param $data array data created by handler() 87 * @return boolean rendered correctly? 88 */ 89 public function render($format, Doku_Renderer $renderer, $data) { 90 global $lang; 91 92 if($format == 'xhtml') { 93 $disablecache = false; 94 $namespaceinput = $this->_htmlNamespaceInput($data['namespace'], $disablecache); 95 if($namespaceinput === false) { 96 if($this->getConf('addpage_hideACL')) { 97 $renderer->doc .= ''; 98 } else { 99 $renderer->doc .= $this->getLang('nooption'); 100 } 101 return true; 102 } 103 if($disablecache) $renderer->info['cache'] = false; 104 105 $newpagetemplateinput = $this->_htmlTemplateInput($data['newpagetemplates']); 106 107 $form = '<div class="addnewpage">' 108 . '<label>' . $this->getLang('newpage') . ': </label>' 109 . '<form name="addnewpage" method="get" action="' . DOKU_BASE . DOKU_SCRIPT . '" accept-charset="' . $lang['encoding'] . '">' 110 . $namespaceinput 111 . '<input class="edit" type="text" name="title" size="20" maxlength="255" tabindex="2" />' 112 . $newpagetemplateinput 113 . '<input type="hidden" name="newpagevars" value="' . $data['newpagevars'] . '"/>' 114 . '<input type="hidden" name="do" value="edit" />' 115 . '<input type="hidden" name="id" />' 116 . '<input class="button" type="submit" value="' . $this->getLang('okbutton') . '" tabindex="4" />' 117 . '</form>' 118 . '</div>'; 119 120 $renderer->doc .= $form; 121 122 return true; 123 } 124 return false; 125 } 126 127 /** 128 * Parse namespace request 129 * 130 * @author Samuele Tognini <samuele@cli.di.unipi.it> 131 * @author Michael Braun <michael-dev@fami-braun.de> 132 */ 133 protected function _parseNS($ns) { 134 $ID=getID(); 135 if(strpos($ns, '@PAGE@') !== false) { 136 return cleanID(str_replace('@PAGE@', $ID, $ns)); 137 } 138 if($ns == "@NS@") return getNS($ID); 139 $ns = preg_replace("/^\.(:|$)/", dirname(str_replace(':', '/', $ID)) . "$1", $ns); 140 $ns = str_replace("/", ":", $ns); 141 142 return cleanID($ns); 143 } 144 145 /** 146 * Create the HTML Select element for namespace selection. 147 * 148 * @param string|false $dest_ns The destination namespace, or false if none provided. 149 * @param bool $disablecache reference indicates if caching need to be disabled 150 * @global string $ID The page ID 151 * @return string Select element with appropriate NS selected. 152 */ 153 protected function _htmlNamespaceInput($dest_ns, &$disablecache) { 154 global $ID; 155 $disablecache = false; 156 157 // If a NS has been provided: 158 // Whether to hide the NS selection (otherwise, show only subnamespaces). 159 $hide = $this->getConf('addpage_hide'); 160 161 $parsed_dest_ns = $this->_parseNS($dest_ns); 162 // Whether the user can create pages in the provided NS (or root, if no 163 // destination NS has been set. 164 $can_create = (auth_quickaclcheck($parsed_dest_ns . ":") >= AUTH_CREATE); 165 166 //namespace given, but hidden 167 if($hide && !empty($dest_ns)) { 168 if($can_create) { 169 return '<input type="hidden" name="np_cat" id="np_cat" value="' . $parsed_dest_ns . '"/>'; 170 } else { 171 return false; 172 } 173 } 174 175 //show select of given namespace 176 $currentns = getNS($ID); 177 178 $ret = '<select class="edit" id="np_cat" name="np_cat" tabindex="1">'; 179 180 // Whether the NS select element has any options 181 $someopt = false; 182 183 // Show root namespace if requested and allowed 184 if($this->getConf('addpage_showroot') && $can_create) { 185 if(empty($dest_ns)) { 186 // If no namespace has been provided, add an option for the root NS. 187 $ret .= '<option ' . (($currentns == '') ? 'selected ' : '') . ' value="">' . $this->getLang('namespaceRoot') . '</option>'; 188 } else { 189 // If a namespace has been provided, add an option for it. 190 $ret .= '<option ' . (($currentns == $dest_ns) ? 'selected ' : '') . ' value="' . formText($dest_ns) . '">' . formText($dest_ns) . '</option>'; 191 } 192 $someopt = true; 193 } 194 195 $subnamespaces = $this->_getNamespaceList($dest_ns); 196 197 // The top of this stack will always be the last printed ancestor namespace 198 $ancestor_stack = array(); 199 if (!empty($dest_ns)) { 200 $ancestor_stack[] = $dest_ns; 201 } 202echo '<!-- '; 203 print_r($dest_ns); 204echo "\n"; 205 print_r($subnamespaces); 206echo ' -->'; 207 208 foreach($subnamespaces as $ns) { 209 210 if(auth_quickaclcheck($ns . ":") < AUTH_CREATE) continue; 211 212 // Pop any elements off the stack that are not ancestors of the current namespace 213 while(!empty($ancestor_stack) && strpos($ns, $ancestor_stack[count($ancestor_stack) - 1] . ':') !== 0) { 214 array_pop($ancestor_stack); 215 } 216 217 $nsparts = explode(':', $ns); 218 $first_unprinted_depth = empty($ancestor_stack)? 1 : (2 + substr_count($ancestor_stack[count($ancestor_stack) - 1], ':')); 219 for ($i = $first_unprinted_depth, $end = count($nsparts); $i <= $end; $i++) { 220 $namespace = implode(':', array_slice($nsparts, 0, $i)); 221 $ancestor_stack[] = $namespace; 222 $selectOptionText = str_repeat(' ', substr_count($namespace, ':')) . $nsparts[$i - 1]; 223 $ret .= '<option ' . 224 (($currentns == $namespace) ? 'selected ' : '') . 225 ($i == $end? ('value="' . $namespace . '">') : 'disabled>') . 226 $selectOptionText . 227 '</option>'; 228 } 229 $someopt = true; 230 $disablecache = true; 231 } 232 233 $ret .= '</select>'; 234 235 if($someopt) { 236 return $ret; 237 } else { 238 return false; 239 } 240 } 241 242 /** 243 * Get a list of namespaces below the given namespace. 244 * Recursively fetches subnamespaces. 245 * 246 * @param string $topns The top namespace 247 * @return array Multi-dimensional array of all namespaces below $tns 248 */ 249 protected function _getNamespaceList($topns = '') { 250 global $conf; 251 252 $topns = utf8_encodeFN(str_replace(':', '/', $topns)); 253 254 $excludes = $this->getConf('addpage_exclude'); 255 if($excludes == "") { 256 $excludes = array(); 257 } else { 258 $excludes = @explode(';', strtolower($excludes)); 259 } 260 $searchdata = array(); 261 search($searchdata, $conf['datadir'], 'search_namespaces', array(), $topns); 262 263 $namespaces = array(); 264 foreach($searchdata as $ns) { 265 foreach($excludes as $exclude) { 266 if( ! empty($exclude) && strpos($ns['id'], $exclude) === 0) { 267 continue 2; 268 } 269 } 270 $namespaces[] = $ns['id']; 271 } 272 273 return $namespaces; 274 } 275 276 /** 277 * Create html for selection of namespace templates 278 * 279 * @param array $newpagetemplates array of namespace templates 280 * @return string html of select or hidden input 281 */ 282 public function _htmlTemplateInput($newpagetemplates) { 283 $cnt = count($newpagetemplates); 284 if($cnt < 1 || $cnt == 1 && $newpagetemplates[0] == '') { 285 $input = ''; 286 287 } else { 288 if($cnt == 1) { 289 list($template, ) = $this->_parseNSTemplatePage($newpagetemplates[0]); 290 $input = '<input type="hidden" name="newpagetemplate" value="' . formText($template) . '" />'; 291 } else { 292 $first = true; 293 $input = '<select name="newpagetemplate" tabindex="3">'; 294 foreach($newpagetemplates as $template) { 295 $p = ($first ? ' selected="selected"' : ''); 296 $first = false; 297 298 list($template, $name) = $this->_parseNSTemplatePage($template); 299 $p .= ' value="'.formText($template).'"'; 300 $input .= "<option $p>".formText($name)."</option>"; 301 } 302 $input .= '</select>'; 303 } 304 $input = DOKU_TAB . DOKU_TAB . $input . DOKU_LF; 305 } 306 return $input; 307 } 308 309 /** 310 * Parses and resolves the namespace template page 311 * 312 * @param $nstemplate 313 * @return array 314 */ 315 protected function _parseNSTemplatePage($nstemplate) { 316 global $ID; 317 318 @list($template, $name) = explode('|', $nstemplate, 2); 319 $template = (new PageResolver($ID))->resolveId($template); 320 if (is_null($name)) $name = $template; 321 322 return array($template, $name); 323 } 324 325} 326