summaryrefslogtreecommitdiff
path: root/platform/www/lib/plugins/bureaucracy/syntax.php
diff options
context:
space:
mode:
Diffstat (limited to 'platform/www/lib/plugins/bureaucracy/syntax.php')
-rw-r--r--platform/www/lib/plugins/bureaucracy/syntax.php623
1 files changed, 623 insertions, 0 deletions
diff --git a/platform/www/lib/plugins/bureaucracy/syntax.php b/platform/www/lib/plugins/bureaucracy/syntax.php
new file mode 100644
index 0000000..154a9ef
--- /dev/null
+++ b/platform/www/lib/plugins/bureaucracy/syntax.php
@@ -0,0 +1,623 @@
+<?php
+/**
+ * Bureaucracy Plugin: Allows flexible creation of forms
+ *
+ * This plugin allows definition of forms in wiki pages. The forms can be
+ * submitted via email or used to create new pages from templates.
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @author Adrian Lang <dokuwiki@cosmocode.de>
+ */
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+/**
+ * All DokuWiki plugins to extend the parser/rendering mechanism
+ * need to inherit from this class
+ */
+class syntax_plugin_bureaucracy extends DokuWiki_Syntax_Plugin {
+
+ private $form_id = 0;
+ var $patterns = array();
+ var $values = array();
+ var $noreplace = null;
+ var $functions = array();
+
+ /**
+ * Prepare some replacements
+ */
+ public function __construct() {
+ $this->prepareDateTimereplacements();
+ $this->prepareNamespacetemplateReplacements();
+ $this->prepareFunctions();
+ }
+
+ /**
+ * What kind of syntax are we?
+ */
+ public function getType() {
+ return 'substition';
+ }
+
+ /**
+ * What about paragraphs?
+ */
+ public function getPType() {
+ return 'block';
+ }
+
+ /**
+ * Where to sort in?
+ */
+ public function getSort() {
+ return 155;
+ }
+
+ /**
+ * Connect pattern to lexer
+ *
+ * @param string $mode
+ */
+ public function connectTo($mode) {
+ $this->Lexer->addSpecialPattern('<form>.*?</form>', $mode, 'plugin_bureaucracy');
+ }
+
+ /**
+ * Handler to prepare matched data for the rendering process
+ *
+ * @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 bool|array Return an array with all data you want to use in render, false don't add an instruction
+ */
+ public function handle($match, $state, $pos, Doku_Handler $handler) {
+ $match = substr($match, 6, -7); // remove form wrap
+ $lines = explode("\n", $match);
+ $actions = $rawactions = array();
+ $thanks = '';
+ $labels = '';
+
+ // parse the lines into an command/argument array
+ $cmds = array();
+ while(count($lines) > 0) {
+ $line = trim(array_shift($lines));
+ if(!$line) continue;
+ $args = $this->_parse_line($line, $lines);
+ $args[0] = $this->_sanitizeClassName($args[0]);
+
+ if(in_array($args[0], array('action', 'thanks', 'labels'))) {
+ if(count($args) < 2) {
+ msg(sprintf($this->getLang('e_missingargs'), hsc($args[0]), hsc($args[1])), -1);
+ continue;
+ }
+
+ // is action element?
+ if($args[0] == 'action') {
+ array_shift($args);
+ $rawactions[] = array('type' => array_shift($args), 'argv' => $args);
+ continue;
+ }
+
+ // is thank you text?
+ if($args[0] == 'thanks') {
+ $thanks = $args[1];
+ continue;
+ }
+
+ // is labels?
+ if($args[0] == 'labels') {
+ $labels = $args[1];
+ continue;
+ }
+ }
+
+ if(strpos($args[0], '_') === false) {
+ $name = 'bureaucracy_field' . $args[0];
+ } else {
+ //name convention: plugin_componentname
+ $name = $args[0];
+ }
+
+ /** @var helper_plugin_bureaucracy_field $field */
+ $field = $this->loadHelper($name, false);
+ if($field && is_a($field, 'helper_plugin_bureaucracy_field')) {
+ $field->initialize($args);
+ $cmds[] = $field;
+ } else {
+ $evdata = array('fields' => &$cmds, 'args' => $args);
+ $event = new Doku_Event('PLUGIN_BUREAUCRACY_FIELD_UNKNOWN', $evdata);
+ if($event->advise_before()) {
+ msg(sprintf($this->getLang('e_unknowntype'), hsc($name)), -1);
+ }
+ }
+
+ }
+
+ // check if action is available
+ foreach($rawactions as $action) {
+ $action['type'] = $this->_sanitizeClassName($action['type']);
+
+ if(strpos($action['type'], '_') === false) {
+ $action['actionname'] = 'bureaucracy_action' . $action['type'];
+ } else {
+ //name convention for other plugins: plugin_componentname
+ $action['actionname'] = $action['type'];
+ }
+
+ list($plugin, $component) = explode('_', $action['actionname']);
+ $alternativename = $action['type'] . '_'. $action['type'];
+
+ // bureaucracy_action<name> or <plugin>_<componentname>
+ if(!plugin_isdisabled($action['actionname']) || @file_exists(DOKU_PLUGIN . $plugin . '/helper/' . $component . '.php')) {
+ $actions[] = $action;
+
+ // shortcut for other plugins with component name <name>_<name>
+ } elseif(plugin_isdisabled($alternativename) || !@file_exists(DOKU_PLUGIN . $action['type'] . '/helper/' . $action['type'] . '.php')) {
+ $action['actionname'] = $alternativename;
+ $actions[] = $action;
+
+ // not found
+ } else {
+ $evdata = array('actions' => &$actions, 'action' => $action);
+ $event = new Doku_Event('PLUGIN_BUREAUCRACY_ACTION_UNKNOWN', $evdata);
+ if($event->advise_before()) {
+ msg(sprintf($this->getLang('e_unknownaction'), hsc($action['actionname'])), -1);
+ }
+ }
+ }
+
+ // action(s) found?
+ if(count($actions) < 1) {
+ msg($this->getLang('e_noaction'), -1);
+ }
+
+ // set thank you message
+ if(!$thanks) {
+ $thanks = "";
+ foreach($actions as $action) {
+ $thanks .= $this->getLang($action['type'] . '_thanks');
+ }
+ } else {
+ $thanks = hsc($thanks);
+ }
+ return array(
+ 'fields' => $cmds,
+ 'actions' => $actions,
+ 'thanks' => $thanks,
+ 'labels' => $labels
+ );
+ }
+
+ /**
+ * Handles the actual output creation.
+ *
+ * @param string $format output format being rendered
+ * @param Doku_Renderer $R the current renderer object
+ * @param array $data data created by handler()
+ * @return boolean rendered correctly? (however, returned value is not used at the moment)
+ */
+ public function render($format, Doku_Renderer $R, $data) {
+ if($format != 'xhtml') return false;
+ $R->info['cache'] = false; // don't cache
+
+ /**
+ * replace some time and name placeholders in the default values
+ * @var $field helper_plugin_bureaucracy_field
+ */
+ foreach($data['fields'] as &$field) {
+ if(isset($field->opt['value'])) {
+ $field->opt['value'] = $this->replace($field->opt['value']);
+ }
+ }
+
+ if($data['labels']) $this->loadlabels($data);
+
+ $this->form_id++;
+ if(isset($_POST['bureaucracy']) && checkSecurityToken() && $_POST['bureaucracy']['$$id'] == $this->form_id) {
+ $success = $this->_handlepost($data);
+ if($success !== false) {
+ $R->doc .= '<div class="bureaucracy__plugin" id="scroll__here">' . $success . '</div>';
+ return true;
+ }
+ }
+
+ $R->doc .= $this->_htmlform($data['fields']);
+
+ return true;
+ }
+
+ /**
+ * Initializes the labels, loaded from a defined labelpage
+ *
+ * @param array $data all data passed to render()
+ */
+ protected function loadlabels(&$data) {
+ global $INFO;
+ $labelpage = $data['labels'];
+ $exists = false;
+ resolve_pageid($INFO['namespace'], $labelpage, $exists);
+ if(!$exists) {
+ msg(sprintf($this->getLang('e_labelpage'), html_wikilink($labelpage)), -1);
+ return;
+ }
+
+ // parse simple list (first level cdata only)
+ $labels = array();
+ $instructions = p_cached_instructions(wikiFN($labelpage));
+ $inli = 0;
+ $item = '';
+ foreach($instructions as $instruction) {
+ if($instruction[0] == 'listitem_open') {
+ $inli++;
+ continue;
+ }
+ if($inli === 1 && $instruction[0] == 'cdata') {
+ $item .= $instruction[1][0];
+ }
+ if($instruction[0] == 'listitem_close') {
+ $inli--;
+ if($inli === 0) {
+ list($k, $v) = explode('=', $item, 2);
+ $k = trim($k);
+ $v = trim($v);
+ if($k && $v) $labels[$k] = $v;
+ $item = '';
+ }
+ }
+ }
+
+ // apply labels to all fields
+ $len = count($data['fields']);
+ for($i = 0; $i < $len; $i++) {
+ if(isset($data['fields'][$i]->depends_on)) {
+ // translate dependency on fieldsets
+ $label = $data['fields'][$i]->depends_on[0];
+ if(isset($labels[$label])) {
+ $data['fields'][$i]->depends_on[0] = $labels[$label];
+ }
+
+ } else if(isset($data['fields'][$i]->opt['label'])) {
+ // translate field labels
+ $label = $data['fields'][$i]->opt['label'];
+ if(isset($labels[$label])) {
+ $data['fields'][$i]->opt['display'] = $labels[$label];
+ }
+ }
+ }
+
+ if(isset($data['thanks'])) {
+ if(isset($labels[$data['thanks']])) {
+ $data['thanks'] = $labels[$data['thanks']];
+ }
+ }
+
+ }
+
+ /**
+ * Validate posted data, perform action(s)
+ *
+ * @param array $data all data passed to render()
+ * @return bool|string
+ * returns thanks message when fields validated and performed the action(s) succesfully;
+ * otherwise returns false.
+ */
+ private function _handlepost($data) {
+ $success = true;
+ foreach($data['fields'] as $index => $field) {
+ /** @var $field helper_plugin_bureaucracy_field */
+
+ $isValid = true;
+ if($field->getFieldType() === 'file') {
+ $file = array();
+ foreach($_FILES['bureaucracy'] as $key => $value) {
+ $file[$key] = $value[$index];
+ }
+ $isValid = $field->handle_post($file, $data['fields'], $index, $this->form_id);
+
+ } elseif($field->getFieldType() === 'fieldset' || !$field->hidden) {
+ $isValid = $field->handle_post($_POST['bureaucracy'][$index], $data['fields'], $index, $this->form_id);
+ }
+
+ if(!$isValid) {
+ // Do not return instantly to allow validation of all fields.
+ $success = false;
+ }
+ }
+ if(!$success) {
+ return false;
+ }
+
+ $thanks_array = array();
+
+ foreach($data['actions'] as $actionData) {
+ /** @var helper_plugin_bureaucracy_action $action */
+ $action = $this->loadHelper($actionData['actionname'], false);
+
+ // action helper found?
+ if(!$action) {
+ msg(sprintf($this->getLang('e_unknownaction'), hsc($actionData['actionname'])), -1);
+ return false;
+ }
+
+ try {
+ $thanks_array[] = $action->run(
+ $data['fields'],
+ $data['thanks'],
+ $actionData['argv']
+ );
+ } catch(Exception $e) {
+ msg($e->getMessage(), -1);
+ return false;
+ }
+ }
+
+ // Perform after_action hooks
+ foreach($data['fields'] as $field) {
+ $field->after_action();
+ }
+
+ // create thanks string
+ $thanks = implode('', array_unique($thanks_array));
+
+ return $thanks;
+ }
+
+ /**
+ * Create the form
+ *
+ * @param helper_plugin_bureaucracy_field[] $fields array with form fields
+ * @return string html of the form
+ */
+ private function _htmlform($fields) {
+ global $INFO;
+
+ $form = new Doku_Form(array('class' => 'bureaucracy__plugin',
+ 'id' => 'bureaucracy__plugin' . $this->form_id,
+ 'enctype' => 'multipart/form-data'));
+ $form->addHidden('id', $INFO['id']);
+ $form->addHidden('bureaucracy[$$id]', $this->form_id);
+
+ foreach($fields as $id => $field) {
+ $field->renderfield(array('name' => 'bureaucracy[' . $id . ']'), $form, $this->form_id);
+ }
+
+ return $form->getForm();
+ }
+
+ /**
+ * Parse a line into (quoted) arguments
+ * Splits line at spaces, except when quoted
+ *
+ * @author William Fletcher <wfletcher@applestone.co.za>
+ *
+ * @param string $line line to parse
+ * @param array $lines all remaining lines
+ * @return array with all the arguments
+ */
+ private function _parse_line($line, &$lines) {
+ $args = array();
+ $inQuote = false;
+ $escapedQuote = false;
+ $arg = '';
+ do {
+ $len = strlen($line);
+ for($i = 0; $i < $len; $i++) {
+ if($line[$i] == '"') {
+ if($inQuote) {
+ if($escapedQuote) {
+ $arg .= '"';
+ $escapedQuote = false;
+ continue;
+ }
+ if($i + 1 < $len && $line[$i + 1] == '"') {
+ $escapedQuote = true;
+ continue;
+ }
+ array_push($args, $arg);
+ $inQuote = false;
+ $arg = '';
+ continue;
+ } else {
+ $inQuote = true;
+ continue;
+ }
+ } else if($line[$i] == ' ') {
+ if($inQuote) {
+ $arg .= ' ';
+ continue;
+ } else {
+ if(strlen($arg) < 1) continue;
+ array_push($args, $arg);
+ $arg = '';
+ continue;
+ }
+ }
+ $arg .= $line[$i];
+ }
+ if(!$inQuote || count($lines) === 0) break;
+ $line = array_shift($lines);
+ $arg .= "\n";
+ } while(true);
+ if(strlen($arg) > 0) array_push($args, $arg);
+ return $args;
+ }
+
+ /**
+ * Clean class name
+ *
+ * @param string $classname
+ * @return string cleaned name
+ */
+ private function _sanitizeClassName($classname) {
+ return preg_replace('/[^\w\x7f-\xff]/', '', strtolower($classname));
+ }
+
+ /**
+ * Save content in <noreplace> tags into $this->noreplace
+ *
+ * @param string $input The text to work on
+ */
+ protected function noreplace_save($input) {
+ $pattern = '/<noreplace>(.*?)<\/noreplace>/is';
+ //save content of <noreplace> tags
+ preg_match_all($pattern, $input, $matches);
+ $this->noreplace = $matches[1];
+ }
+
+ /**
+ * Apply replacement patterns and values as prepared earlier
+ * (disable $strftime to prevent double replacements with default strftime() replacements in nstemplate)
+ *
+ * @param string $input The text to work on
+ * @param bool $strftime Apply strftime() replacements
+ * @return string processed text
+ */
+ function replace($input, $strftime = true) {
+ //in helper_plugin_struct_field::setVal $input can be an array
+ //just return $input in that case
+ if (!is_string($input)) return $input;
+ if (is_null($this->noreplace)) $this->noreplace_save($input);
+
+ foreach ($this->values as $label => $value) {
+ $pattern = $this->patterns[$label];
+ if (is_callable($value)) {
+ $input = preg_replace_callback(
+ $pattern,
+ $value,
+ $input
+ );
+ } else {
+ $input = preg_replace($pattern, $value, $input);
+ }
+
+ }
+
+ if($strftime) {
+ $input = preg_replace_callback(
+ '/%./',
+ function($m){return strftime($m[0]);},
+ $input
+ );
+ }
+ // user syntax: %%.(.*?)
+ // strftime() is already applied once, so syntax is at this point: %.(.*?)
+ $input = preg_replace_callback(
+ '/@DATE\((.*?)(?:,\s*(.*?))?\)@/',
+ array($this, 'replacedate'),
+ $input
+ );
+
+ //run functions
+ foreach ($this->functions as $name => $callback) {
+ $pattern = '/@' . preg_quote($name) . '\((.*?)\)@/';
+ if (is_callable($callback)) {
+ $input = preg_replace_callback($pattern, function ($matches) use ($callback) {
+ return call_user_func($callback, $matches[1]);
+ }, $input);
+ }
+ }
+
+ //replace <noreplace> tags with their original content
+ $pattern = '/<noreplace>.*?<\/noreplace>/is';
+ if (is_array($this->noreplace)) foreach ($this->noreplace as $nr) {
+ $input = preg_replace($pattern, $nr, $input, 1);
+ }
+
+ return $input;
+ }
+
+ /**
+ * (callback) Replace date by request datestring
+ * e.g. '%m(30-11-1975)' is replaced by '11'
+ *
+ * @param array $match with [0]=>whole match, [1]=> first subpattern, [2] => second subpattern
+ * @return string
+ */
+ function replacedate($match) {
+ global $conf;
+
+ //no 2nd argument for default date format
+ $match[2] = $match[2] ?? $conf['dformat'];
+
+ return strftime($match[2], strtotime($match[1]));
+ }
+
+ /**
+ * Same replacements as applied at template namespaces
+ *
+ * @see parsePageTemplate()
+ */
+ function prepareNamespacetemplateReplacements() {
+ /* @var Input $INPUT */
+ global $INPUT;
+ global $INFO;
+ global $USERINFO;
+ global $conf;
+ global $ID;
+
+ $this->patterns['__formpage_id__'] = '/@FORMPAGE_ID@/';
+ $this->patterns['__formpage_ns__'] = '/@FORMPAGE_NS@/';
+ $this->patterns['__formpage_curns__'] = '/@FORMPAGE_CURNS@/';
+ $this->patterns['__formpage_file__'] = '/@FORMPAGE_FILE@/';
+ $this->patterns['__formpage_!file__'] = '/@FORMPAGE_!FILE@/';
+ $this->patterns['__formpage_!file!__'] = '/@FORMPAGE_!FILE!@/';
+ $this->patterns['__formpage_page__'] = '/@FORMPAGE_PAGE@/';
+ $this->patterns['__formpage_!page__'] = '/@FORMPAGE_!PAGE@/';
+ $this->patterns['__formpage_!!page__'] = '/@FORMPAGE_!!PAGE@/';
+ $this->patterns['__formpage_!page!__'] = '/@FORMPAGE_!PAGE!@/';
+ $this->patterns['__user__'] = '/@USER@/';
+ $this->patterns['__name__'] = '/@NAME@/';
+ $this->patterns['__mail__'] = '/@MAIL@/';
+ $this->patterns['__date__'] = '/@DATE@/';
+
+ // replace placeholders
+ $localid = isset($INFO['id']) ? $INFO['id'] : $ID;
+ $file = noNS($localid);
+ $page = strtr($file, $conf['sepchar'], ' ');
+ $this->values['__formpage_id__'] = $localid;
+ $this->values['__formpage_ns__'] = getNS($localid);
+ $this->values['__formpage_curns__'] = curNS($localid);
+ $this->values['__formpage_file__'] = $file;
+ $this->values['__formpage_!file__'] = utf8_ucfirst($file);
+ $this->values['__formpage_!file!__'] = utf8_strtoupper($file);
+ $this->values['__formpage_page__'] = $page;
+ $this->values['__formpage_!page__'] = utf8_ucfirst($page);
+ $this->values['__formpage_!!page__'] = utf8_ucwords($page);
+ $this->values['__formpage_!page!__'] = utf8_strtoupper($page);
+ $this->values['__user__'] = $INPUT->server->str('REMOTE_USER');
+ $this->values['__name__'] = $USERINFO['name'] ?? '';
+ $this->values['__mail__'] = $USERINFO['mail'] ?? '';
+ $this->values['__date__'] = strftime($conf['dformat']);
+ }
+
+ /**
+ * Date time replacements
+ */
+ function prepareDateTimereplacements() {
+ $this->patterns['__year__'] = '/@YEAR@/';
+ $this->patterns['__month__'] = '/@MONTH@/';
+ $this->patterns['__monthname__'] = '/@MONTHNAME@/';
+ $this->patterns['__day__'] = '/@DAY@/';
+ $this->patterns['__time__'] = '/@TIME@/';
+ $this->patterns['__timesec__'] = '/@TIMESEC@/';
+ $this->values['__year__'] = date('Y');
+ $this->values['__month__'] = date('m');
+ $this->values['__monthname__'] = date('B');
+ $this->values['__day__'] = date('d');
+ $this->values['__time__'] = date('H:i');
+ $this->values['__timesec__'] = date('H:i:s');
+
+ }
+
+ /**
+ * Functions that can be used after replacements
+ */
+ function prepareFunctions() {
+ $this->functions['curNS'] = 'curNS';
+ $this->functions['getNS'] = 'getNS';
+ $this->functions['noNS'] = 'noNS';
+ $this->functions['p_get_first_heading'] = 'p_get_first_heading';
+ }
+}