|
- <?php
-
- /**
- * Base class for form fields
- *
- * This class provides basic functionality for many form fields. It supports
- * labels, basic validation and template-based XHTML output.
- *
- * @author Adrian Lang <lang@cosmocode.de>
- **/
-
- /**
- * Class helper_plugin_bureaucracy_field
- *
- * base class for all the form fields
- */
- class helper_plugin_bureaucracy_field extends syntax_plugin_bureaucracy {
-
- protected $mandatory_args = 2;
- public $opt = array();
- /** @var string|array */
- protected $tpl;
- protected $checks = array();
- public $hidden = false;
- protected $error = false;
- protected $checktypes = array(
- '/' => 'match',
- '<' => 'max',
- '>' => 'min'
- );
-
- /**
- * Construct a helper_plugin_bureaucracy_field object
- *
- * This constructor initializes a helper_plugin_bureaucracy_field object
- * based on a given definition.
- *
- * The first two items represent:
- * * the type of the field
- * * and the label the field has been given.
- * Additional arguments are type-specific mandatory extra arguments and optional arguments.
- *
- * The optional arguments may add constraints to the field value, provide a
- * default value, mark the field as optional or define that the field is
- * part of a pagename (when using the template action).
- *
- * Since the field objects are cached, this constructor may not reference
- * request data.
- *
- * @param array $args The tokenized definition, only split at spaces
- */
- public function initialize($args) {
- $this->init($args);
- $this->standardArgs($args);
- }
-
- /**
- * Return false to prevent DokuWiki reusing instances of the plugin
- *
- * @return bool
- */
- public function isSingleton() {
- return false;
- }
-
- /**
- * Checks number of arguments and store 'cmd', 'label' and 'display' values
- *
- * @param array $args array with the definition
- */
- protected function init(&$args) {
- if(count($args) < $this->mandatory_args){
- msg(sprintf($this->getLang('e_missingargs'), hsc($args[0]),
- hsc($args[1])), -1);
- return;
- }
-
- // get standard arguments
- $this->opt = array();
- foreach (array('cmd', 'label') as $key) {
- if (count($args) === 0) break;
- $this->opt[$key] = array_shift($args);
- }
- $this->opt['display'] = $this->opt['label']; // allow to modify display value independently
- }
-
- /**
- * Check for additional arguments and store their values
- *
- * @param array $args array with remaining definition arguments
- */
- protected function standardArgs($args) {
- // parse additional arguments
- foreach($args as $arg){
- if ($arg[0] == '=') {
- $this->setVal(substr($arg,1));
- } elseif ($arg == '!') {
- $this->opt['optional'] = true;
- } elseif ($arg == '^') {
- //only one field has focus
- if (helper_plugin_bureaucracy_field::hasFocus()) {
- $this->opt['id'] = 'focus__this';
- }
- } elseif($arg == '@') {
- $this->opt['pagename'] = true;
- } elseif($arg == '@@') {
- $this->opt['replyto'] = true;
- } elseif(preg_match('/x\d/', $arg)) {
- $this->opt['rows'] = substr($arg,1);
- } elseif($arg[0] == '.') {
- $this->opt['class'] = substr($arg, 1);
- } elseif(preg_match('/^0{2,}$/', $arg)) {
- $this->opt['leadingzeros'] = strlen($arg);
- } elseif($arg[0].$arg[1] == '**') {
- $this->opt['matchexplanation'] = substr($arg,2);
- } else {
- $t = $arg[0];
- $d = substr($arg,1);
- if (in_array($t, array('>', '<')) && !is_numeric($d)) {
- break;
- }
- if ($t == '/') {
- if (substr($d, -1) !== '/') {
- break;
- }
- $d = substr($d, 0, -1);
- }
- if (!isset($this->checktypes[$t]) || !method_exists($this, 'validate_' . $this->checktypes[$t])) {
- msg(sprintf($this->getLang('e_unknownconstraint'), hsc($t).' ('.hsc($arg).')'), -1);
- return;
- }
- $this->checks[] = array('t' => $t, 'd' => $d);
- }
- }
- }
-
- /**
- * Add parsed element to Form which generates XHTML
- *
- * Outputs the represented field using the passed Doku_Form object.
- * Additional parameters (CSS class & HTML name) are passed in $params.
- * HTML output is created by passing the template $this->tpl to the simple
- * template engine _parse_tpl.
- *
- * @param array $params Additional HTML specific parameters
- * @param Doku_Form $form The target Doku_Form object
- * @param int $formid unique identifier of the form which contains this field
- */
- public function renderfield($params, Doku_Form $form, $formid) {
- $this->_handlePreload();
- if(!$form->_infieldset){
- $form->startFieldset('');
- }
- if ($this->error) {
- $params['class'] = 'bureaucracy_error';
- }
-
- $params = array_merge($this->opt, $params);
- $form->addElement($this->_parse_tpl($this->tpl, $params));
- }
-
- /**
- * Only the first use get the focus, next calls not
- *
- * @return bool
- */
- protected static function hasFocus(){
- static $focus = true;
- if($focus) {
- $focus = false;
- return true;
- } else {
- return false;
- }
- }
-
-
- /**
- * Check for preload value in the request url
- */
- protected function _handlePreload() {
- $preload_name = '@' . strtr($this->getParam('label'),' .','__') . '@';
- if (isset($_GET[$preload_name])) {
- $this->setVal($_GET[$preload_name]);
- }
- }
-
- /**
- * Handle a post to the field
- *
- * Accepts and validates a posted value.
- *
- * (Overridden by fieldset, which has as argument an array with the form array by reference)
- *
- * @param string $value The passed value or array or null if none given
- * @param helper_plugin_bureaucracy_field[] $fields (reference) form fields (POST handled upto $this field)
- * @param int $index index number of field in form
- * @param int $formid unique identifier of the form which contains this field
- * @return bool Whether the passed value is valid
- */
- public function handle_post($value, &$fields, $index, $formid) {
- return $this->hidden || $this->setVal($value);
- }
-
- /**
- * Get the field type
- *
- * @return string
- **/
- public function getFieldType() {
- return $this->opt['cmd'];
- }
-
- /**
- * Get the replacement pattern used by action
- *
- * @return string
- */
- public function getReplacementPattern() {
- $label = $this->getParam('label');
- $value = $this->getParam('value');
-
- if (is_array($value)) {
- return '/(@@|##)' . preg_quote($label, '/') .
- '(?:\((?P<delimiter>.*?)\))?' .//delimiter
- '(?:\|(?P<default>.*?))' . (count($value) == 0 ? '' : '?') .
- '\1/si';
- }
-
- return '/(@@|##)' . preg_quote($label, '/') .
- '(?:\|(.*?))' . (is_null($value) ? '' : '?') .
- '\1/si';
- }
-
- /**
- * Used as an callback for preg_replace_callback
- *
- * @param $matches
- * @return string
- */
- public function replacementMultiValueCallback($matches) {
- $value = $this->opt['value'];
-
- //default value
- if (is_null($value) || $value === false) {
- if (isset($matches['default']) && $matches['default'] != '') {
- return $matches['default'];
- }
- return $matches[0];
- }
-
- //check if matched string containts a pair of brackets
- $delimiter = preg_match('/\(.*\)/s', $matches[0]) ? $matches['delimiter'] : ', ';
-
- return implode($delimiter, $value);
- }
-
- /**
- * Get the value used by action
- * If value is a callback preg_replace_callback is called instead preg_replace
- *
- * @return mixed|string
- */
- public function getReplacementValue() {
- $value = $this->getParam('value');
-
- if (is_array($value)) {
- return array($this, 'replacementMultiValueCallback');
- }
-
- return is_null($value) || $value === false ? '$2' : $value;
- }
-
- /**
- * Validate value and stores it
- *
- * @param mixed $value value entered into field
- * @return bool whether the passed value is valid
- */
- protected function setVal($value) {
- if ($value === '') {
- $value = null;
- }
- $this->opt['value'] = $value;
- try {
- $this->_validate();
- $this->error = false;
- } catch (Exception $e) {
- msg($e->getMessage(), -1);
- $this->error = true;
- }
- return !$this->error;
- }
-
- /**
- * Whether the field is true (used for depending fieldsets)
- *
- * @return bool whether field is set
- */
- public function isSet_() {
- return !is_null($this->getParam('value'));
- }
-
- /**
- * Validate value of field and throws exceptions for bad values.
- *
- * @throws Exception when field didn't validate.
- */
- protected function _validate() {
- $value = $this->getParam('value');
- if (is_null($value)) {
- if(!isset($this->opt['optional'])) {
- throw new Exception(sprintf($this->getLang('e_required'),hsc($this->opt['label'])));
- }
- return;
- }
-
- foreach ($this->checks as $check) {
- $checktype = $this->checktypes[$check['t']];
- if (!call_user_func(array($this, 'validate_' . $checktype), $check['d'], $value)) {
- //replacement is custom explanation or just the regexp or the requested value
- if(isset($this->opt['matchexplanation'])) {
- $replacement = hsc($this->opt['matchexplanation']);
- } elseif($checktype == 'match') {
- $replacement = sprintf($this->getLang('checkagainst'), hsc($check['d']));
- } else {
- $replacement = hsc($check['d']);
- }
-
- throw new Exception(sprintf($this->getLang('e_' . $checktype), hsc($this->opt['label']), $replacement));
- }
- }
- }
-
- /**
- * Get an arbitrary parameter
- *
- * @param string $name
- * @return mixed|null
- */
- public function getParam($name) {
- if (!isset($this->opt[$name]) || $name === 'value' && $this->hidden) {
- return null;
- }
- if ($name === 'pagename') {
- // If $this->opt['pagename'] is set, return the escaped value of the field.
- $value = $this->getParam('value');
- if (is_null($value)) {
- return null;
- }
- global $conf;
- if($conf['useslash']) $value = str_replace('/',' ',$value);
- return str_replace(':',' ',$value);
- }
- return $this->opt[$name];
- }
-
- /**
- * Parse a template with given parameters
- *
- * Replaces variables specified like @@VARNAME|default@@ using the passed
- * value map.
- *
- * @param string|array $tpl The template as string or array
- * @param array $params A hash mapping parameters to values
- *
- * @return string|array The parsed template
- */
- protected function _parse_tpl($tpl, $params) {
- // addElement supports a special array format as well. In this case
- // not all elements should be escaped.
- $is_simple = !is_array($tpl);
- if ($is_simple) $tpl = array($tpl);
-
- foreach ($tpl as &$val) {
- // Select box passes options as an array. We do not escape those.
- if (is_array($val)) continue;
-
- // find all variables and their defaults or param values
- preg_match_all('/@@([A-Z]+)(?:\|((?:[^@]|@$|@[^@])*))?@@/', $val, $pregs);
- for ($i = 0 ; $i < count($pregs[2]) ; ++$i) {
- if (isset($params[strtolower($pregs[1][$i])])) {
- $pregs[2][$i] = $params[strtolower($pregs[1][$i])];
- }
- }
- // we now have placeholders in $pregs[0] and their values in $pregs[2]
- $replacements = array(); // check if empty to prevent php 5.3 warning
- if (!empty($pregs[0])) {
- $replacements = array_combine($pregs[0], $pregs[2]);
- }
-
- if($is_simple){
- // for simple string templates, we escape all replacements
- $replacements = array_map('hsc', $replacements);
- }else{
- // for the array ones, we escape the label and display only
- if(isset($replacements['@@LABEL@@'])) $replacements['@@LABEL@@'] = hsc($replacements['@@LABEL@@']);
- if(isset($replacements['@@DISPLAY@@'])) $replacements['@@DISPLAY@@'] = hsc($replacements['@@DISPLAY@@']);
- }
-
- // we attach a mandatory marker to the display
- if(isset($replacements['@@DISPLAY@@']) && !isset($params['optional'])){
- $replacements['@@DISPLAY@@'] .= ' <sup>*</sup>';
- }
- $val = str_replace(array_keys($replacements), array_values($replacements), $val);
- }
- return $is_simple ? $tpl[0] : $tpl;
- }
-
- /**
- * Executed after performing the action hooks
- */
- public function after_action() {
- }
-
- /**
- * Constraint function: value of field should match this regexp
- *
- * @param string $d regexp
- * @param mixed $value
- * @return int|bool
- */
- protected function validate_match($d, $value) {
- return @preg_match('/' . $d . '/i', $value);
- }
-
- /**
- * Constraint function: value of field should be bigger
- *
- * @param int|number $d lower bound
- * @param mixed $value of field
- * @return bool
- */
- protected function validate_min($d, $value) {
- return $value > $d;
- }
-
- /**
- * Constraint function: value of field should be smaller
- *
- * @param int|number $d upper bound
- * @param mixed $value of field
- * @return bool
- */
- protected function validate_max($d, $value) {
- return $value < $d;
- }
-
- /**
- * Available methods
- *
- * @return array
- */
- public function getMethods() {
- $result = array();
- $result[] = array(
- 'name' => 'initialize',
- 'desc' => 'Initiate object, first parameters are at least cmd and label',
- 'params' => array(
- 'params' => 'array'
- )
- );
- $result[] = array(
- 'name' => 'renderfield',
- 'desc' => 'Add parsed element to Form which generates XHTML',
- 'params' => array(
- 'params' => 'array',
- 'form' => 'Doku_Form',
- 'formid' => 'integer'
- )
- );
- $result[] = array(
- 'name' => 'handle_post',
- 'desc' => 'Handle a post to the field',
- 'params' => array(
- 'value' => 'array',
- 'fields' => 'helper_plugin_bureaucracy_field[]',
- 'index' => 'Doku_Form',
- 'formid' => 'integer'
- ),
- 'return' => array('isvalid' => 'bool')
- );
- $result[] = array(
- 'name' => 'getFieldType',
- 'desc' => 'Get the field type',
- 'return' => array('fieldtype' => 'string')
- );
- $result[] = array(
- 'name' => 'isSet_',
- 'desc' => 'Whether the field is true (used for depending fieldsets) ',
- 'return' => array('isset' => 'bool')
- );
- $result[] = array(
- 'name' => 'getParam',
- 'desc' => 'Get an arbitrary parameter',
- 'params' => array(
- 'name' => 'string'
- ),
- 'return' => array('Parameter value' => 'mixed|null')
- );
- $result[] = array(
- 'name' => 'after_action',
- 'desc' => 'Executed after performing the action hooks'
- );
- return $result;
- }
-
- }
|