1<?php 2/** 3 * Add-New-Page Plugin: a simple form for adding new pages. 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author iDO <ido@idotech.info> 7 * @author Sam Wilson <sam@samwilson.id.au> 8 * 9 * @noinspection PhpUnused, 10 * PhpMissingParamTypeInspection, PhpMissingReturnTypeInspection 11 */ 12 13// must be run within Dokuwiki 14use dokuwiki\File\PageResolver; 15 16if(!defined('DOKU_INC')) die(); 17 18class syntax_plugin_addnewpage extends DokuWiki_Syntax_Plugin { 19 20 /** 21 * Syntax Type 22 */ 23 public function getType() { 24 return 'substition'; 25 } 26 27 /** 28 * Paragraph Type 29 */ 30 public function getPType() { 31 return 'block'; 32 } 33 34 /** 35 * @return int 36 */ 37 public function getSort() { 38 return 199; 39 } 40 41 /** 42 * @param string $mode 43 */ 44 public function connectTo($mode) { 45 $this->Lexer->addSpecialPattern('\{\{NEWPAGE[^\}]*\}\}', $mode, 'plugin_addnewpage'); 46 } 47 48 /** 49 * Handler to prepare matched data for the rendering process 50 * 51 * Handled syntax options: 52 * {{NEWPAGE}} 53 * {{NEWPAGE>your:namespace}} 54 * {{NEWPAGE#newtpl1,newtpl2}} 55 * {{NEWPAGE#newtpl1|Title1,newtpl2|Title1}} 56 * {{NEWPAGE>your:namespace#newtpl1|Title1,newtpl2|Title1}} 57 * {{NEWPAGE>your:namespace#newtpl1|Title1,newtpl2|Title1#@HI@,Howdy}} 58 * 59 * @param string $match The text matched by the patterns 60 * @param int $state The lexer state for the match 61 * @param int $pos The character position of the matched text 62 * @param Doku_Handler $handler The Doku_Handler object 63 * @return array Return an array with all data you want to use in render 64 * @codingStandardsIgnoreStart 65 */ 66 public function handle($match, $state, $pos, Doku_Handler $handler) { 67 /* @codingStandardsIgnoreEnd */ 68 $options = substr($match, 9, -2); // strip markup 69 $options = explode('#', $options, 3); 70 71 $namespace = trim(ltrim($options[0], '>')); 72 $templates = explode(',', $options[1] ?? ''); 73 $templates = array_map('trim', $templates); 74 $newpagevars = trim($options[2] ?? ''); 75 return array( 76 'namespace' => $namespace, 77 'newpagetemplates' => $templates, 78 'newpagevars' => $newpagevars 79 ); 80 } 81 82 /** 83 * Create the new-page form. 84 * 85 * @param $format string output format being rendered 86 * @param $renderer Doku_Renderer the current renderer object 87 * @param $data array data created by handler() 88 * @return boolean rendered correctly? 89 */ 90 public function render($format, Doku_Renderer $renderer, $data) { 91 global $lang; 92 93 if($format == 'xhtml') { 94 $disablecache = false; 95 $namespaceinput = $this->_htmlNamespaceInput($data['namespace'], $disablecache); 96 if($namespaceinput === false) { 97 if($this->getConf('addpage_hideACL')) { 98 $renderer->doc .= ''; 99 } else { 100 $renderer->doc .= $this->getLang('nooption'); 101 } 102 return true; 103 } 104 if($disablecache) $renderer->info['cache'] = false; 105 106 $newpagetemplateinput = $this->_htmlTemplateInput($data['newpagetemplates']); 107 108 $form = '<div class="addnewpage"><p>' 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 . '</p></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 } 202 203 foreach($subnamespaces as $ns) { 204 205 if(auth_quickaclcheck($ns . ":") < AUTH_CREATE) continue; 206 207 // Pop any elements off the stack that are not ancestors of the current namespace 208 while(!empty($ancestor_stack) && strpos($ns, $ancestor_stack[count($ancestor_stack) - 1] . ':') !== 0) { 209 array_pop($ancestor_stack); 210 } 211 212 $nsparts = explode(':', $ns); 213 $first_unprinted_depth = empty($ancestor_stack)? 1 : (2 + substr_count($ancestor_stack[count($ancestor_stack) - 1], ':')); 214 for ($i = $first_unprinted_depth, $end = count($nsparts); $i <= $end; $i++) { 215 $namespace = implode(':', array_slice($nsparts, 0, $i)); 216 $ancestor_stack[] = $namespace; 217 $selectOptionText = str_repeat(' ', substr_count($namespace, ':')) . $nsparts[$i - 1]; 218 $ret .= '<option ' . 219 (($currentns == $namespace) ? 'selected ' : '') . 220 ($i == $end? ('value="' . $namespace . '">') : 'disabled>') . 221 $selectOptionText . 222 '</option>'; 223 } 224 $someopt = true; 225 $disablecache = true; 226 } 227 228 $ret .= '</select>'; 229 230 if($someopt) { 231 return $ret; 232 } else { 233 return false; 234 } 235 } 236 237 /** 238 * Get a list of namespaces below the given namespace. 239 * Recursively fetches subnamespaces. 240 * 241 * @param string $topns The top namespace 242 * @return array Multi-dimensional array of all namespaces below $tns 243 */ 244 protected function _getNamespaceList($topns = '') { 245 global $conf; 246 247 $topns = utf8_encodeFN(str_replace(':', '/', $topns)); 248 249 $excludes = $this->getConf('addpage_exclude'); 250 if($excludes == "") { 251 $excludes = array(); 252 } else { 253 $excludes = @explode(';', strtolower($excludes)); 254 } 255 $searchdata = array(); 256 search($searchdata, $conf['datadir'], 'search_namespaces', array(), $topns); 257 258 $namespaces = array(); 259 foreach($searchdata as $ns) { 260 foreach($excludes as $exclude) { 261 if( ! empty($exclude) && strpos($ns['id'], $exclude) === 0) { 262 continue 2; 263 } 264 } 265 $namespaces[] = $ns['id']; 266 } 267 268 return $namespaces; 269 } 270 271 /** 272 * Create html for selection of namespace templates 273 * 274 * @param array $newpagetemplates array of namespace templates 275 * @return string html of select or hidden input 276 */ 277 public function _htmlTemplateInput($newpagetemplates) { 278 $cnt = count($newpagetemplates); 279 if($cnt < 1 || $cnt == 1 && $newpagetemplates[0] == '') { 280 $input = ''; 281 282 } else { 283 if($cnt == 1) { 284 list($template, ) = $this->_parseNSTemplatePage($newpagetemplates[0]); 285 $input = '<input type="hidden" name="newpagetemplate" value="' . formText($template) . '" />'; 286 } else { 287 $first = true; 288 $input = '<select name="newpagetemplate" tabindex="3">'; 289 foreach($newpagetemplates as $template) { 290 $p = ($first ? ' selected="selected"' : ''); 291 $first = false; 292 293 list($template, $name) = $this->_parseNSTemplatePage($template); 294 $p .= ' value="'.formText($template).'"'; 295 $input .= "<option $p>".formText($name)."</option>"; 296 } 297 $input .= '</select>'; 298 } 299 $input = DOKU_TAB . DOKU_TAB . $input . DOKU_LF; 300 } 301 return $input; 302 } 303 304 /** 305 * Parses and resolves the namespace template page 306 * 307 * @param $nstemplate 308 * @return array 309 */ 310 protected function _parseNSTemplatePage($nstemplate) { 311 global $ID; 312 313 @list($template, $name) = explode('|', $nstemplate, 2); 314 $template = (new PageResolver($ID))->resolveId($template); 315 if (is_null($name)) $name = $template; 316 317 return array($template, $name); 318 } 319 320} 321