diff options
Diffstat (limited to 'plugins/55/addnewpage/syntax.php')
-rw-r--r-- | plugins/55/addnewpage/syntax.php | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/plugins/55/addnewpage/syntax.php b/plugins/55/addnewpage/syntax.php new file mode 100644 index 0000000..c7d7b7b --- /dev/null +++ b/plugins/55/addnewpage/syntax.php @@ -0,0 +1,423 @@ +<?php +/** + * Add-New-Page Plugin: a simple form for adding new pages. + * + * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) + * @author iDO <ido@idotech.info> + * @author Sam Wilson <sam@samwilson.id.au> + * + * @noinspection PhpUnused + * @noinspection PhpMissingParamTypeInspection, PhpMissingReturnTypeInspection + */ + +use dokuwiki\Extension\SyntaxPlugin; +use dokuwiki\File\PageResolver; + +// must be run within Dokuwiki +if(!defined('DOKU_INC')) die(); + +class syntax_plugin_addnewpage extends SyntaxPlugin { + + /** @var array the parsed options */ + protected $options; + + /** + * Syntax Type + */ + public function getType() { + return 'substition'; + } + + /** + * Paragraph Type + */ + public function getPType() { + return 'block'; + } + + /** + * @return int + */ + public function getSort() { + return 199; + } + + /** + * @param string $mode + */ + public function connectTo($mode) { + $this->Lexer->addSpecialPattern('\{\{NEWPAGE[^\}]*\}\}', $mode, 'plugin_addnewpage'); + } + + /** + * Handler to prepare matched data for the rendering process. + * + * Handled syntax options: + * - {{NEWPAGE}} + * - {{NEWPAGE>your:namespace}} + * - {{NEWPAGE>your:namespace:@INPUT@:start}} + * - {{NEWPAGE>your:namespace:[date formats]}} {@see strftime()} + * - {{NEWPAGE?config_overrides}} + * - {{NEWPAGE?label=custom}} + * - {{NEWPAGE#newtpl1,newtpl2}} + * - {{NEWPAGE#newtpl1|Title1,newtpl2|Title1}} + * - {{NEWPAGE>your:namespace#newtpl1|Title1,newtpl2|Title1}} + * - {{NEWPAGE>your:namespace#newtpl1|Title1,newtpl2|Title1#@HI@,Howdy}} + * + * Refer to {@see https://www.dokuwiki.org/plugin:addnewpage} for details. + * + * @param string $match The text matched by the patterns + * @param int $state The lexer state for the match + * @param int $pos The character position of the matched text + * @param Doku_Handler $handler The Doku_Handler object + * + * @return array Return an array with all data you want to use in render + * @codingStandardsIgnoreStart + */ + public function handle($match, $state, $pos, Doku_Handler $handler) { + /* @codingStandardsIgnoreEnd */ + $match = substr($match, 9, -2); // strip markup + + $data = array( + 'namespace' => '', + 'newpagetemplates' => array(), + 'newpagevars' => '', + 'options' => array( + 'exclude' => $this->getConf('addpage_exclude'), + 'showroot' => $this->getConf('addpage_showroot'), + 'hide' => $this->getConf('addpage_hide'), + 'hideacl' => $this->getConf('addpage_hideACL'), + 'autopage' => $this->getConf('addpage_autopage'), + 'label' => 'okbutton', + ) + ); + + if(preg_match('/>(.*?)(#|\?|$)/', $match, $m)) { + $data['namespace'] = trim($m[1]); + } + + # Extract the newpagetemplate plugin parameters + # - after the initial #: the template name + # - after optional 2nd #: custom variable names + if(preg_match('/#(.*?)(?:#(.*?))?(?:\?|$)/', $match, $m)) { + $data['newpagetemplates'] = array_map('trim', explode(',', $m[1])); + $data['newpagevars'] = trim($m[2] ?? ''); + } + + if(preg_match('/\?(.*?)(#|$)/', $match, $m)) { + $this->_parseOptions($m[1], $data['options']); + } + + return $data; + } + + /** + * Create the new-page form. + * + * @param $format string output format being rendered + * @param $renderer Doku_Renderer the current renderer object + * @param $data array data created by handler() + * @return boolean rendered correctly? + */ + public function render($format, Doku_Renderer $renderer, $data) { + global $lang; + + // make options available in class + $this->options = $data['options']; + + if($format == 'xhtml') { + $disablecache = false; + $namespaceinput = $this->_htmlNamespaceInput($data['namespace'], $disablecache); + if($namespaceinput === false) { + if($this->options['hideacl']) { + $renderer->doc .= ''; + } else { + $renderer->doc .= $this->getLang('nooption'); + } + return true; + } + if($disablecache) $renderer->info['cache'] = false; + + $newpagetemplateinput = $this->_htmlTemplateInput($data['newpagetemplates']); + + $input = 'text'; + if($this->options['autopage']) $input = 'hidden'; + + // Button label. If given string is not localized, use it as-is + $label = $this->getLang($this->options['label']); + if (!$label) { + $label = $this->options['label']; + } + + $form = '<div class="addnewpage"><p>' + . '<form name="addnewpage" method="get" action="' . DOKU_BASE . DOKU_SCRIPT + . '" accept-charset="' . $lang['encoding'] . '">' + . $namespaceinput + . '<input class="edit" type="' . $input . '" name="title" size="20" maxlength="255" tabindex="2" placeholder="' + . $this->getLang('name') . '"/>' + . $newpagetemplateinput + . '<input type="hidden" name="newpagevars" value="' . $data['newpagevars'] . '"/>' + . '<input type="hidden" name="do" value="edit" />' + . '<input type="hidden" name="id" />' + . '<input class="button" type="submit" value="' . $label . '" tabindex="4" />' + . '</form>' + . '</p></div>'; + + $renderer->doc .= $form; + + return true; + } + return false; + } + + /** + * Overwrites the $options with the ones parsed from $optstr + * + * @param string $optstr + * @param array $options + * @author Andreas Gohr <gohr@cosmocode.de> + */ + protected function _parseOptions($optstr, &$options) { + $opts = preg_split('/[,&]/', $optstr); + + foreach($opts as $opt) { + $opt_lower = strtolower(trim($opt)); + $val = true; + // booleans can be negated with a no prefix + if(substr($opt_lower, 0, 2) == 'no') { + $opt_lower = substr($opt, 2); + $val = false; + } + + // not a known option? might be a key=value pair + if(!isset($options[$opt_lower])) { + $split = array_map('trim', sexplode('=', $opt, 2)); + $opt_lower = strtolower($split[0]); + $val = $split[1]; + } + + // still unknown? skip it + if(!isset($options[$opt_lower])) continue; + + // overwrite the current value + $options[$opt_lower] = $val; + } + } + + /** + * Parse namespace request + * + * This creates the final ID to be created (still having an @INPUT@ variable + * which is filled in via JavaScript) + * + * @author Samuele Tognini <samuele@cli.di.unipi.it> + * @author Michael Braun <michael-dev@fami-braun.de> + * @author Andreas Gohr <gohr@cosmocode.de> + * @param string $ns The namespace as given in the syntax + * @return string + */ + protected function _parseNS($ns) { + global $INFO; + + $selfid = $INFO['id']; + $selfns = getNS($selfid); + // replace the input variable with something unique that survives cleanID + $keep = sha1(time()); + + // by default append the input to the namespace (except on autopage) + if(strpos($ns, '@INPUT@') === false && !$this->options['autopage']) $ns .= ":@INPUT@"; + + // date replacements + $ns = dformat(null, $ns); + + // placeholders + $replacements = array( + '/\//' => ':', // forward slashes to colons + '/@PAGE@/' => $selfid, + '/@NS@/' => $selfns, + '/^\.(:|\/|$)/' => "$selfns:", + '/@INPUT@/' => $keep, + ); + $ns = preg_replace(array_keys($replacements), array_values($replacements), $ns); + + // clean up, then reinsert the input variable + $ns = cleanID($ns); + return str_replace($keep, '@INPUT@', $ns); + } + + /** + * Create the HTML Select element for namespace selection. + * + * @param string|false $dest_ns The destination namespace, or false if none provided. + * @param bool $disablecache reference indicates if caching need to be disabled + * @global string $ID The page ID + * @return string Select element with appropriate NS selected. + */ + protected function _htmlNamespaceInput($dest_ns, &$disablecache) { + global $ID; + $disablecache = false; + + // If a NS has been provided: + // Whether to hide the NS selection (otherwise, show only subnamespaces). + $hide = $this->options['hide']; + + $parsed_dest_ns = $this->_parseNS($dest_ns); + // Whether the user can create pages in the provided NS (or root, if no + // destination NS has been set. + $can_create = (auth_quickaclcheck($parsed_dest_ns . ":") >= AUTH_CREATE); + + //namespace given, but hidden + if($hide && !empty($dest_ns)) { + if($can_create) { + return '<input type="hidden" name="np_cat" id="np_cat" value="' . $parsed_dest_ns . '"/>'; + } else { + return false; + } + } + + //show select of given namespace + $currentns = getNS($ID); + + $ret = '<select class="edit" id="np_cat" name="np_cat" tabindex="1">'; + + // Whether the NS select element has any options + $someopt = false; + + // Show root namespace if requested and allowed + if($this->options['showroot'] && $can_create) { + if(empty($dest_ns)) { + // If no namespace has been provided, add an option for the root NS. + $ret .= '<option ' . (($currentns == '') ? 'selected ' : '') . ' value="">' . $this->getLang('namespaceRoot') . '</option>'; + } else { + // If a namespace has been provided, add an option for it. + $ret .= '<option ' . (($currentns == $dest_ns) ? 'selected ' : '') . ' value="' . formText($dest_ns) . '">' . formText($dest_ns) . '</option>'; + } + $someopt = true; + } + + $subnamespaces = $this->_getNamespaceList($dest_ns); + + // The top of this stack will always be the last printed ancestor namespace + $ancestor_stack = array(); + if (!empty($dest_ns)) { + $ancestor_stack[] = $dest_ns; + } + + foreach($subnamespaces as $ns) { + + if(auth_quickaclcheck($ns . ":") < AUTH_CREATE) continue; + + // Pop any elements off the stack that are not ancestors of the current namespace + while(!empty($ancestor_stack) && strpos($ns, $ancestor_stack[count($ancestor_stack) - 1] . ':') !== 0) { + array_pop($ancestor_stack); + } + + $nsparts = explode(':', $ns); + $first_unprinted_depth = empty($ancestor_stack) ? 1 : (2 + substr_count($ancestor_stack[count($ancestor_stack) - 1], ':')); + for($i = $first_unprinted_depth, $end = count($nsparts); $i <= $end; $i++) { + $namespace = implode(':', array_slice($nsparts, 0, $i)); + $ancestor_stack[] = $namespace; + $selectOptionText = str_repeat(' ', substr_count($namespace, ':')) . $nsparts[$i - 1]; + $ret .= '<option ' . + (($currentns == $namespace) ? 'selected ' : '') . + ($i == $end ? ('value="' . $namespace . '">') : 'disabled>') . + $selectOptionText . + '</option>'; + } + $someopt = true; + $disablecache = true; + } + + $ret .= '</select>'; + + if($someopt) { + return $ret; + } else { + return false; + } + } + + /** + * Get a list of namespaces below the given namespace. + * Recursively fetches subnamespaces. + * + * @param string $topns The top namespace + * @return array Multi-dimensional array of all namespaces below $tns + */ + protected function _getNamespaceList($topns = '') { + global $conf; + + $topns = utf8_encodeFN(str_replace(':', '/', $topns)); + + $excludes = $this->options['exclude']; + if($excludes == "") { + $excludes = array(); + } else { + $excludes = @explode(';', strtolower($excludes)); + } + $searchdata = array(); + search($searchdata, $conf['datadir'], 'search_namespaces', array(), $topns); + + $namespaces = array(); + foreach($searchdata as $ns) { + foreach($excludes as $exclude) { + if(!empty($exclude) && strpos($ns['id'], $exclude) === 0) { + continue 2; + } + } + $namespaces[] = $ns['id']; + } + + return $namespaces; + } + + /** + * Create html for selection of namespace templates + * + * @param array $newpagetemplates array of namespace templates + * @return string html of select or hidden input + */ + public function _htmlTemplateInput($newpagetemplates) { + $cnt = count($newpagetemplates); + if($cnt < 1 || $cnt == 1 && $newpagetemplates[0] == '') { + $input = ''; + + } else { + if($cnt == 1) { + list($template,) = $this->_parseNSTemplatePage($newpagetemplates[0]); + $input = '<input type="hidden" name="newpagetemplate" value="' . formText($template) . '" />'; + } else { + $first = true; + $input = '<select name="newpagetemplate" tabindex="3">'; + foreach($newpagetemplates as $template) { + $p = ($first ? ' selected="selected"' : ''); + $first = false; + + list($template, $name) = $this->_parseNSTemplatePage($template); + $p .= ' value="' . formText($template) . '"'; + $input .= "<option $p>" . formText($name) . "</option>"; + } + $input .= '</select>'; + } + $input = DOKU_TAB . DOKU_TAB . $input . DOKU_LF; + } + return $input; + } + + /** + * Parses and resolves the namespace template page + * + * @param $nstemplate + * @return array + */ + protected function _parseNSTemplatePage($nstemplate) { + global $ID; + + @list($template, $name) = explode('|', $nstemplate, 2); + $template = (new PageResolver($ID))->resolveId($template); + if (is_null($name)) $name = $template; + + return array($template, $name); + } + +} |