| /** | |
| * Base class for all HTML_QuickForm2 elements | |
| * | |
| * PHP version 5 | |
| * | |
| * LICENSE: | |
| * | |
| * Copyright (c) 2006-2010, Alexey Borzov <avb@php.net>, | |
| * Bertrand Mansion <golgote@mamasam.com> | |
| * All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| * are met: | |
| * | |
| * * Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * * Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * * The names of the authors may not be used to endorse or promote products | |
| * derived from this software without specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS | |
| * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, | |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
| * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| * | |
| * @category HTML | |
| * @package HTML_QuickForm2 | |
| * @author Alexey Borzov <avb@php.net> | |
| * @author Bertrand Mansion <golgote@mamasam.com> | |
| * @license http://opensource.org/licenses/bsd-license.php New BSD License | |
| * @version SVN: $Id: Node.php 300747 2010-06-25 16:16:50Z mansion $ | |
| * @link http://pear.php.net/package/HTML_QuickForm2 | |
| */ | |
| /** | |
| * HTML_Common2 - base class for HTML elements | |
| */ | |
| // require_once 'HTML/Common2.php'; | |
| // By default, we generate element IDs with numeric indexes appended even for | |
| // elements with unique names. If you want IDs to be equal to the element | |
| // names by default, set this configuration option to false. | |
| if (null === HTML_Common2::getOption('id_force_append_index')) { | |
| HTML_Common2::setOption('id_force_append_index', true); | |
| } | |
| /** | |
| * Exception classes for HTML_QuickForm2 | |
| */ | |
| // require_once 'HTML/QuickForm2/Exception.php'; | |
| require_once dirname(__FILE__) . '/Exception.php'; | |
| /** | |
| * Static factory class for QuickForm2 elements | |
| */ | |
| // require_once 'HTML/QuickForm2/Factory.php'; | |
| /** | |
| * Base class for HTML_QuickForm2 rules | |
| */ | |
| // require_once 'HTML/QuickForm2/Rule.php'; | |
| /** | |
| * Abstract base class for all QuickForm2 Elements and Containers | |
| * | |
| * This class is mostly here to define the interface that should be implemented | |
| * by the subclasses. It also contains static methods handling generation | |
| * of unique ids for elements which do not have ids explicitly set. | |
| * | |
| * @category HTML | |
| * @package HTML_QuickForm2 | |
| * @author Alexey Borzov <avb@php.net> | |
| * @author Bertrand Mansion <golgote@mamasam.com> | |
| * @version Release: @package_version@ | |
| */ | |
| abstract class HTML_QuickForm2_Node extends HTML_Common2 | |
| { | |
| /** | |
| * Array containing the parts of element ids | |
| * @var array | |
| */ | |
| protected static $ids = array(); | |
| /** | |
| * Element's "frozen" status | |
| * @var boolean | |
| */ | |
| protected $frozen = false; | |
| /** | |
| * Whether element's value should persist when element is frozen | |
| * @var boolean | |
| */ | |
| protected $persistent = false; | |
| /** | |
| * Element containing current | |
| * @var HTML_QuickForm2_Container | |
| */ | |
| protected $container = null; | |
| /** | |
| * Contains options and data used for the element creation | |
| * @var array | |
| */ | |
| protected $data = array(); | |
| /** | |
| * Validation rules for element | |
| * @var array | |
| */ | |
| protected $rules = array(); | |
| /** | |
| * An array of callback filters for element | |
| * @var array | |
| */ | |
| protected $filters = array(); | |
| /** | |
| * Error message (usually set via Rule if validation fails) | |
| * @var string | |
| */ | |
| protected $error = null; | |
| /** | |
| * Changing 'name' and 'id' attributes requires some special handling | |
| * @var array | |
| */ | |
| protected $watchedAttributes = array('id', 'name'); | |
| /** | |
| * Intercepts setting 'name' and 'id' attributes | |
| * | |
| * These attributes should always be present and thus trying to remove them | |
| * will result in an exception. Changing their values is delegated to | |
| * setName() and setId() methods, respectively | |
| * | |
| * @param string Attribute name | |
| * @param string Attribute value, null if attribute is being removed | |
| * @throws HTML_QuickForm2_InvalidArgumentException if trying to | |
| * remove a required attribute | |
| */ | |
| protected function onAttributeChange($name, $value = null) | |
| { | |
| if ('name' == $name) { | |
| if (null === $value) { | |
| throw new HTML_QuickForm2_InvalidArgumentException( | |
| "Required attribute 'name' can not be removed" | |
| ); | |
| } else { | |
| $this->setName($value); | |
| } | |
| } elseif ('id' == $name) { | |
| if (null === $value) { | |
| throw new HTML_QuickForm2_InvalidArgumentException( | |
| "Required attribute 'id' can not be removed" | |
| ); | |
| } else { | |
| $this->setId($value); | |
| } | |
| } | |
| } | |
| /** | |
| * Class constructor | |
| * | |
| * @param string Element name | |
| * @param mixed Attributes (either a string or an array) | |
| * @param array Element data (label, options and data used for element creation) | |
| */ | |
| public function __construct($name = null, $attributes = null, $data = null) | |
| { | |
| parent::__construct($attributes); | |
| $this->setName($name); | |
| // Autogenerating the id if not set on previous steps | |
| if ('' == $this->getId()) { | |
| $this->setId(); | |
| } | |
| if (!empty($data)) { | |
| $this->data = array_merge($this->data, $data); | |
| } | |
| } | |
| /** | |
| * Generates an id for the element | |
| * | |
| * Called when an element is created without explicitly given id | |
| * | |
| * @param string Element name | |
| * @return string The generated element id | |
| */ | |
| protected static function generateId($elementName) | |
| { | |
| $stop = !self::getOption('id_force_append_index'); | |
| $tokens = strlen($elementName) | |
| ? explode('[', str_replace(']', '', $elementName)) | |
| : ($stop? array('qfauto', ''): array('qfauto')); | |
| $container =& self::$ids; | |
| $id = ''; | |
| do { | |
| $token = array_shift($tokens); | |
| // Handle the 'array[]' names | |
| if ('' === $token) { | |
| if (empty($container)) { | |
| $token = 0; | |
| } else { | |
| $keys = array_keys($container); | |
| $token = end($keys); | |
| while (isset($container[$token])) { | |
| $token++; | |
| } | |
| } | |
| } | |
| $id .= '-' . $token; | |
| if (!isset($container[$token])) { | |
| $container[$token] = array(); | |
| // Handle duplicate names when not having mandatory indexes | |
| } elseif (empty($tokens) && $stop) { | |
| $tokens[] = ''; | |
| } | |
| // Handle mandatory indexes | |
| if (empty($tokens) && !$stop) { | |
| $tokens[] = ''; | |
| $stop = true; | |
| } | |
| $container =& $container[$token]; | |
| } while (!empty($tokens)); | |
| return substr($id, 1); | |
| } | |
| /** | |
| * Stores the explicitly given id to prevent duplicate id generation | |
| * | |
| * @param string Element id | |
| */ | |
| protected static function storeId($id) | |
| { | |
| $tokens = explode('-', $id); | |
| $container =& self::$ids; | |
| do { | |
| $token = array_shift($tokens); | |
| if (!isset($container[$token])) { | |
| $container[$token] = array(); | |
| } | |
| $container =& $container[$token]; | |
| } while (!empty($tokens)); | |
| } | |
| /** | |
| * Returns the element options | |
| * | |
| * @return array | |
| */ | |
| public function getData() | |
| { | |
| return $this->data; | |
| } | |
| /** | |
| * Returns the element's type | |
| * | |
| * @return string | |
| */ | |
| abstract public function getType(); | |
| /** | |
| * Returns the element's name | |
| * | |
| * @return string | |
| */ | |
| public function getName() | |
| { | |
| return isset($this->attributes['name'])? $this->attributes['name']: null; | |
| } | |
| /** | |
| * Sets the element's name | |
| * | |
| * @param string | |
| * @return HTML_QuickForm2_Node | |
| */ | |
| abstract public function setName($name); | |
| /** | |
| * Returns the element's id | |
| * | |
| * @return string | |
| */ | |
| public function getId() | |
| { | |
| return isset($this->attributes['id'])? $this->attributes['id']: null; | |
| } | |
| /** | |
| * Sets the elements id | |
| * | |
| * Please note that elements should always have an id in QuickForm2 and | |
| * therefore it will not be possible to remove the element's id or set it to | |
| * an empty value. If id is not explicitly given, it will be autogenerated. | |
| * | |
| * @param string Element's id, will be autogenerated if not given | |
| * @return HTML_QuickForm2_Node | |
| */ | |
| public function setId($id = null) | |
| { | |
| if (is_null($id)) { | |
| $id = self::generateId($this->getName()); | |
| } else { | |
| self::storeId($id); | |
| } | |
| $this->attributes['id'] = (string)$id; | |
| return $this; | |
| } | |
| /** | |
| * Returns the element's value | |
| * | |
| * @return mixed | |
| */ | |
| abstract public function getValue(); | |
| /** | |
| * Sets the element's value | |
| * | |
| * @param mixed | |
| * @return HTML_QuickForm2_Node | |
| */ | |
| abstract public function setValue($value); | |
| /** | |
| * Returns the element's label(s) | |
| * | |
| * @return string|array | |
| */ | |
| public function getLabel() | |
| { | |
| if (isset($this->data['label'])) { | |
| return $this->data['label']; | |
| } | |
| return null; | |
| } | |
| /** | |
| * Sets the element's label(s) | |
| * | |
| * @param string|array Label for the element (may be an array of labels) | |
| * @return HTML_QuickForm2_Node | |
| */ | |
| public function setLabel($label) | |
| { | |
| $this->data['label'] = $label; | |
| return $this; | |
| } | |
| /** | |
| * Changes the element's frozen status | |
| * | |
| * @param bool Whether the element should be frozen or editable. If | |
| * omitted, the method will not change the frozen status, | |
| * just return its current value | |
| * @return bool Old value of element's frozen status | |
| */ | |
| public function toggleFrozen($freeze = null) | |
| { | |
| $old = $this->frozen; | |
| if (null !== $freeze) { | |
| $this->frozen = (bool)$freeze; | |
| } | |
| return $old; | |
| } | |
| /** | |
| * Changes the element's persistent freeze behaviour | |
| * | |
| * If persistent freeze is on, the element's value will be kept (and | |
| * submitted) in a hidden field when the element is frozen. | |
| * | |
| * @param bool New value for "persistent freeze". If omitted, the | |
| * method will not set anything, just return the current | |
| * value of the flag. | |
| * @return bool Old value of "persistent freeze" flag | |
| */ | |
| public function persistentFreeze($persistent = null) | |
| { | |
| $old = $this->persistent; | |
| if (null !== $persistent) { | |
| $this->persistent = (bool)$persistent; | |
| } | |
| return $old; | |
| } | |
| /** | |
| * Adds the link to the element containing current | |
| * | |
| * @param HTML_QuickForm2_Container Element containing the current one, | |
| * null if the link should really be | |
| * removed (if removing from container) | |
| * @throws HTML_QuickForm2_InvalidArgumentException If trying to set a | |
| * child of an element as its container | |
| */ | |
| protected function setContainer(?HTML_QuickForm2_Container $container = null) | |
| { | |
| if (null !== $container) { | |
| $check = $container; | |
| do { | |
| if ($this === $check) { | |
| throw new HTML_QuickForm2_InvalidArgumentException( | |
| 'Cannot set an element or its child as its own container' | |
| ); | |
| } | |
| } while ($check = $check->getContainer()); | |
| if (null !== $this->container && $container !== $this->container) { | |
| $this->container->removeChild($this); | |
| } | |
| } | |
| $this->container = $container; | |
| if (null !== $container) { | |
| $this->updateValue(); | |
| } | |
| } | |
| /** | |
| * Returns the element containing current | |
| * | |
| * @return HTML_QuickForm2_Container|null | |
| */ | |
| public function getContainer() | |
| { | |
| return $this->container; | |
| } | |
| /** | |
| * Returns the data sources for this element | |
| * | |
| * @return array | |
| */ | |
| protected function getDataSources() | |
| { | |
| if (empty($this->container)) { | |
| return array(); | |
| } else { | |
| return $this->container->getDataSources(); | |
| } | |
| } | |
| /** | |
| * Called when the element needs to update its value from form's data sources | |
| */ | |
| abstract public function updateValue(); | |
| /** | |
| * Adds a validation rule | |
| * | |
| * @param HTML_QuickForm2_Rule|string Validation rule or rule type | |
| * @param string|int If first parameter is rule type, then | |
| * message to display if validation fails, otherwise constant showing | |
| * whether to perfom validation client-side and/or server-side | |
| * @param mixed Additional data for the rule | |
| * @param int Whether to perfom validation server-side | |
| * and/or client side. Combination of HTML_QuickForm2_Rule::RUNAT_* constants | |
| * @return HTML_QuickForm2_Rule The added rule | |
| * @throws HTML_QuickForm2_InvalidArgumentException if $rule is of a | |
| * wrong type or rule name isn't registered with Factory | |
| * @throws HTML_QuickForm2_NotFoundException if class for a given rule | |
| * name cannot be found | |
| * @todo Need some means to mark the Rules for running client-side | |
| */ | |
| public function addRule($rule, $messageOrRunAt = '', $options = null, | |
| $runAt = HTML_QuickForm2_Rule::RUNAT_SERVER) | |
| { | |
| if ($rule instanceof HTML_QuickForm2_Rule) { | |
| $rule->setOwner($this); | |
| $runAt = '' == $messageOrRunAt? HTML_QuickForm2_Rule::RUNAT_SERVER: $messageOrRunAt; | |
| } elseif (is_string($rule)) { | |
| $rule = HTML_QuickForm2_Factory::createRule($rule, $this, $messageOrRunAt, $options); | |
| } else { | |
| throw new HTML_QuickForm2_InvalidArgumentException( | |
| 'addRule() expects either a rule type or ' . | |
| 'a HTML_QuickForm2_Rule instance' | |
| ); | |
| } | |
| $this->rules[] = array($rule, $runAt); | |
| return $rule; | |
| } | |
| /** | |
| * Removes a validation rule | |
| * | |
| * The method will *not* throw an Exception if the rule wasn't added to the | |
| * element. | |
| * | |
| * @param HTML_QuickForm2_Rule Validation rule to remove | |
| * @return HTML_QuickForm2_Rule Removed rule | |
| */ | |
| public function removeRule(HTML_QuickForm2_Rule $rule) | |
| { | |
| foreach ($this->rules as $i => $r) { | |
| if ($r[0] === $rule) { | |
| unset($this->rules[$i]); | |
| break; | |
| } | |
| } | |
| return $rule; | |
| } | |
| /** | |
| * Creates a validation rule | |
| * | |
| * This method is mostly useful when when chaining several rules together | |
| * via {@link HTML_QuickForm2_Rule::and_()} and {@link HTML_QuickForm2_Rule::or_()} | |
| * methods: | |
| * <code> | |
| * $first->addRule('nonempty', 'Fill in either first or second field') | |
| * ->or_($second->createRule('nonempty')); | |
| * </code> | |
| * | |
| * @param string Rule type | |
| * @param string Message to display if validation fails | |
| * @param mixed Additional data for the rule | |
| * @return HTML_QuickForm2_Rule The created rule | |
| * @throws HTML_QuickForm2_InvalidArgumentException If rule type is unknown | |
| * @throws HTML_QuickForm2_NotFoundException If class for the rule | |
| * can't be found and/or loaded from file | |
| */ | |
| public function createRule($type, $message = '', $options = null) | |
| { | |
| return HTML_QuickForm2_Factory::createRule($type, $this, $message, $options); | |
| } | |
| /** | |
| * Checks whether an element is required | |
| * | |
| * @return boolean | |
| */ | |
| public function isRequired() | |
| { | |
| foreach ($this->rules as $rule) { | |
| if ($rule[0] instanceof HTML_QuickForm2_Rule_Required) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Performs the server-side validation | |
| * | |
| * @return boolean Whether the element is valid | |
| */ | |
| protected function validate() | |
| { | |
| foreach ($this->rules as $rule) { | |
| if (strlen($this->error ?? '')) { | |
| break; | |
| } | |
| if ($rule[1] & HTML_QuickForm2_Rule::RUNAT_SERVER) { | |
| $rule[0]->validate(); | |
| } | |
| } | |
| return !strlen($this->error ?? ''); | |
| } | |
| /** | |
| * Sets the error message to the element | |
| * | |
| * @param string | |
| * @return HTML_QuickForm2_Node | |
| */ | |
| public function setError($error = null) | |
| { | |
| $this->error = (string)$error; | |
| return $this; | |
| } | |
| /** | |
| * Returns the error message for the element | |
| * | |
| * @return string | |
| */ | |
| public function getError() | |
| { | |
| return $this->error; | |
| } | |
| /** | |
| * Returns Javascript code for getting the element's value | |
| * | |
| * @return string | |
| */ | |
| abstract public function getJavascriptValue(); | |
| /** | |
| * Adds a filter | |
| * | |
| * A filter is simply a PHP callback which will be applied to the element value | |
| * when getValue() is called. A filter is by default applied recursively : | |
| * if the value is an array, each elements it contains will | |
| * also be filtered, unless the recursive flag is set to false. | |
| * | |
| * @param callback The PHP callback used for filter | |
| * @param array Optional arguments for the callback. The first parameter | |
| * will always be the element value, then these options will | |
| * be used as parameters for the callback. | |
| * @param bool Whether to apply the filter recursively to contained elements | |
| * @return HTML_QuickForm2_Node The element | |
| * @throws HTML_QuickForm2_InvalidArgumentException If callback is incorrect | |
| */ | |
| public function addFilter($callback, ?array $options = null, $recursive = true) | |
| { | |
| if (!is_callable($callback, false, $callbackName)) { | |
| throw new HTML_QuickForm2_InvalidArgumentException( | |
| 'Callback Filter requires a valid callback, \'' . $callbackName . | |
| '\' was given' | |
| ); | |
| } | |
| $this->filters[] = array($callback, $options, 'recursive' => $recursive); | |
| return $this; | |
| } | |
| /** | |
| * Removes all element filters | |
| */ | |
| public function removeFilters() | |
| { | |
| $this->filters = array(); | |
| } | |
| /** | |
| * Applies element filters on element value | |
| * @param mixed Element value | |
| * @return mixed Filtered value | |
| */ | |
| protected function applyFilters($value) | |
| { | |
| foreach ($this->filters as $filter) { | |
| if (is_array($value) && !empty($filter['recursive'])) { | |
| array_walk_recursive($value, | |
| array('HTML_QuickForm2_Node', 'applyFilter'), $filter); | |
| } else { | |
| self::applyFilter($value, null, $filter); | |
| } | |
| } | |
| return $value; | |
| } | |
| protected static function applyFilter(&$value, $key, $filter) | |
| { | |
| $callback = $filter[0]; | |
| $options = $filter[1]; | |
| if (!is_array($options)) { | |
| $options = array(); | |
| } | |
| array_unshift($options, $value); | |
| $value = call_user_func_array($callback, $options); | |
| } | |
| } | |