694 lines
20 KiB
PHP
694 lines
20 KiB
PHP
<?php
|
|
/**
|
|
* 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 = null, $filter)
|
|
{
|
|
$callback = $filter[0];
|
|
$options = $filter[1];
|
|
if (!is_array($options)) {
|
|
$options = array();
|
|
}
|
|
array_unshift($options, $value);
|
|
$value = call_user_func_array($callback, $options);
|
|
}
|
|
|
|
}
|
|
?>
|