* @author Sam Wilson * * @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 = '

' . '

' . $namespaceinput . '' . $newpagetemplateinput . '' . '' . '' . '' . '
' . '

'; $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 */ 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 * @author Michael Braun * @author Andreas Gohr * @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 ''; } else { return false; } } //show select of given namespace $currentns = getNS($ID); $ret = ''; 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 = ''; } else { $first = true; $input = ''; } $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); } }