,
* Bertrand Mansion
* 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
* @author Bertrand Mansion
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/HTML_QuickForm2
*/
/**
* Base class for all HTML_QuickForm2 elements
*/
require_once 'HTML/QuickForm2/Node.php';
/**
* Abstract base class for simple QuickForm2 containers
*
* @category HTML
* @package HTML_QuickForm2
* @author Alexey Borzov
* @author Bertrand Mansion
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: 2.0.2
* @link http://pear.php.net/package/HTML_QuickForm2
*
* @method HTML_QuickForm2_Element_Button addButton(string $name, $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_InputCheckbox addCheckbox(string $name, $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_Date addDate(string $name, $attributes = null, array $data = array())
* @method HTML_QuickForm2_Container_Fieldset addFieldset(string $name = '', $attributes = null, array $data = array())
* @method HTML_QuickForm2_Container_Group addGroup(string $name = '', $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_InputFile addFile(string $name, $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_InputHidden addHidden(string $name, $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_Hierselect addHierselect(string $name, $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_InputImage addImage(string $name, $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_InputButton addInputButton(string $name, $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_InputPassword addPassword(string $name, $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_InputRadio addRadio(string $name, $attributes = null, array $data = array())
* @method HTML_QuickForm2_Container_Repeat addRepeat(string $name = '', $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_InputReset addReset(string $name, $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_Script addScript(string $name = '', $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_Select addSelect(string $name, $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_Static addStatic(string $name = '', $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_InputSubmit addSubmit(string $name, $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_InputText addText(string $name, $attributes = null, array $data = array())
* @method HTML_QuickForm2_Element_Textarea addTextarea(string $name, $attributes = null, array $data = array())
*/
abstract class HTML_QuickForm2_Container extends HTML_QuickForm2_Node
implements IteratorAggregate, Countable
{
/**
* Array of elements contained in this container
* @var array
*/
protected $elements = array();
public function setName($name)
{
$this->attributes['name'] = (string)$name;
return $this;
}
public function toggleFrozen($freeze = null)
{
if (null !== $freeze) {
foreach ($this as $child) {
$child->toggleFrozen($freeze);
}
}
return parent::toggleFrozen($freeze);
}
public function persistentFreeze($persistent = null)
{
if (null !== $persistent) {
foreach ($this as $child) {
$child->persistentFreeze($persistent);
}
}
return parent::persistentFreeze($persistent);
}
/**
* Whether container prepends its name to names of contained elements
*
* @return bool
*/
protected function prependsName()
{
return false;
}
/**
* Returns the array containing child elements' values
*
* @param bool $filtered Whether child elements should apply filters on values
*
* @return array|null
*/
protected function getChildValues($filtered = false)
{
$method = $filtered? 'getValue': 'getRawValue';
$values = $forceKeys = array();
foreach ($this as $child) {
$value = $child->$method();
if (null !== $value) {
if ($child instanceof HTML_QuickForm2_Container
&& !$child->prependsName()
) {
$values = self::arrayMerge($values, $value);
} else {
$name = $child->getName();
if (!strpos($name, '[')) {
$values[$name] = $value;
} else {
$tokens = explode('[', str_replace(']', '', $name));
$valueAry =& $values;
do {
$token = array_shift($tokens);
if (!isset($valueAry[$token])) {
$valueAry[$token] = array();
}
$valueAry =& $valueAry[$token];
} while (count($tokens) > 1);
if ('' != $tokens[0]) {
$valueAry[$tokens[0]] = $value;
} else {
if (!isset($forceKeys[$name])) {
$forceKeys[$name] = 0;
}
$valueAry[$forceKeys[$name]++] = $value;
}
}
}
}
}
return empty($values)? null: $values;
}
/**
* Returns the container's value without filters applied
*
* The default implementation for Containers is to return an array with
* contained elements' values. The array is indexed the same way $_GET and
* $_POST arrays would be for these elements.
*
* @return array|null
*/
public function getRawValue()
{
return $this->getChildValues(false);
}
/**
* Returns the container's value, possibly with filters applied
*
* The default implementation for Containers is to return an array with
* contained elements' values. The array is indexed the same way $_GET and
* $_POST arrays would be for these elements.
*
* @return array|null
*/
public function getValue()
{
$value = $this->getChildValues(true);
return is_null($value)? null: $this->applyFilters($value);
}
/**
* Merges two arrays
*
* Merges two arrays like the PHP function array_merge_recursive does,
* the difference being that existing integer keys will not be renumbered.
*
* @param array $a
* @param array $b
*
* @return array resulting array
*/
public static function arrayMerge($a, $b)
{
foreach ($b as $k => $v) {
if (!is_array($v) || isset($a[$k]) && !is_array($a[$k])) {
$a[$k] = $v;
} else {
$a[$k] = self::arrayMerge(isset($a[$k])? $a[$k]: array(), $v);
}
}
return $a;
}
/**
* Returns an array of this container's elements
*
* @return array Container elements
*/
public function getElements()
{
return $this->elements;
}
/**
* Appends an element to the container
*
* If the element was previously added to the container or to another
* container, it is first removed there.
*
* @param HTML_QuickForm2_Node $element Element to add
*
* @return HTML_QuickForm2_Node Added element
* @throws HTML_QuickForm2_InvalidArgumentException
*/
public function appendChild(HTML_QuickForm2_Node $element)
{
if ($this === $element->getContainer()) {
$this->removeChild($element);
}
$element->setContainer($this);
$this->elements[] = $element;
return $element;
}
/**
* Appends an element to the container (possibly creating it first)
*
* If the first parameter is an instance of HTML_QuickForm2_Node then all
* other parameters are ignored and the method just calls {@link appendChild()}.
* In the other case the element is first created via
* {@link HTML_QuickForm2_Factory::createElement()} and then added via the
* same method. This is a convenience method to reduce typing and ease
* porting from HTML_QuickForm.
*
* @param string|HTML_QuickForm2_Node $elementOrType Either type name (treated
* case-insensitively) or an element instance
* @param string $name Element name
* @param string|array $attributes Element attributes
* @param array $data Element-specific data
*
* @return HTML_QuickForm2_Node Added element
* @throws HTML_QuickForm2_InvalidArgumentException
* @throws HTML_QuickForm2_NotFoundException
*/
public function addElement(
$elementOrType, $name = null, $attributes = null, array $data = array()
) {
if ($elementOrType instanceof HTML_QuickForm2_Node) {
return $this->appendChild($elementOrType);
} else {
return $this->appendChild(HTML_QuickForm2_Factory::createElement(
$elementOrType, $name, $attributes, $data
));
}
}
/**
* Removes the element from this container
*
* @param HTML_QuickForm2_Node $element Element to remove
*
* @return HTML_QuickForm2_Node Removed object
* @throws HTML_QuickForm2_NotFoundException
*/
public function removeChild(HTML_QuickForm2_Node $element)
{
if ($element->getContainer() !== $this) {
throw new HTML_QuickForm2_NotFoundException(
"Element with name '".$element->getName()."' was not found"
);
}
$unset = false;
foreach ($this as $key => $child) {
if ($child === $element) {
unset($this->elements[$key]);
$element->setContainer(null);
$unset = true;
break;
}
}
if ($unset) {
$this->elements = array_values($this->elements);
}
return $element;
}
/**
* Returns an element if its id is found
*
* @param string $id Element id to search for
*
* @return HTML_QuickForm2_Node|null
*/
public function getElementById($id)
{
foreach ($this->getRecursiveIterator() as $element) {
if ($id == $element->getId()) {
return $element;
}
}
return null;
}
/**
* Returns an array of elements which name corresponds to element
*
* @param string $name Element name to search for
*
* @return array
*/
public function getElementsByName($name)
{
$found = array();
foreach ($this->getRecursiveIterator() as $element) {
if ($element->getName() == $name) {
$found[] = $element;
}
}
return $found;
}
/**
* Inserts an element in the container
*
* If the reference object is not given, the element will be appended.
*
* @param HTML_QuickForm2_Node $element Element to insert
* @param HTML_QuickForm2_Node $reference Reference to insert before
*
* @return HTML_QuickForm2_Node Inserted element
*/
public function insertBefore(HTML_QuickForm2_Node $element, HTML_QuickForm2_Node $reference = null)
{
if (null === $reference) {
return $this->appendChild($element);
}
$offset = 0;
foreach ($this as $child) {
if ($child === $reference) {
if ($this === $element->getContainer()) {
$this->removeChild($element);
}
$element->setContainer($this);
array_splice($this->elements, $offset, 0, array($element));
return $element;
}
$offset++;
}
throw new HTML_QuickForm2_NotFoundException(
"Reference element with name '".$reference->getName()."' was not found"
);
}
/**
* Returns a recursive iterator for the container elements
*
* @return HTML_QuickForm2_ContainerIterator
*/
public function getIterator()
{
return new HTML_QuickForm2_ContainerIterator($this);
}
/**
* Returns a recursive iterator iterator for the container elements
*
* @param int $mode mode passed to RecursiveIteratorIterator
*
* @return RecursiveIteratorIterator
*/
public function getRecursiveIterator($mode = RecursiveIteratorIterator::SELF_FIRST)
{
return new RecursiveIteratorIterator(
new HTML_QuickForm2_ContainerIterator($this), $mode
);
}
/**
* Returns the number of elements in the container
*
* @return int
*/
public function count()
{
return count($this->elements);
}
/**
* Called when the element needs to update its value from form's data sources
*
* The default behaviour is just to call the updateValue() methods of
* contained elements, since default Container doesn't have any value itself
*/
protected function updateValue()
{
foreach ($this as $child) {
$child->updateValue();
}
}
/**
* Performs the server-side validation
*
* This method also calls validate() on all contained elements.
*
* @return boolean Whether the container and all contained elements are valid
*/
protected function validate()
{
$valid = true;
foreach ($this as $child) {
$valid = $child->validate() && $valid;
}
$valid = parent::validate() && $valid;
// additional check is needed as a Rule on Container may set errors
// on contained elements, see HTML_QuickForm2Test::testFormRule()
if ($valid) {
foreach ($this->getRecursiveIterator() as $item) {
if (0 < strlen($item->getError())) {
return false;
}
}
}
return $valid;
}
/**
* Appends an element to the container, creating it first
*
* The element will be created via {@link HTML_QuickForm2_Factory::createElement()}
* and then added via the {@link appendChild()} method.
* The element type is deduced from the method name.
* This is a convenience method to reduce typing.
*
* @param string $m Method name
* @param array $a Method arguments
*
* @return HTML_QuickForm2_Node Added element
* @throws HTML_QuickForm2_InvalidArgumentException
* @throws HTML_QuickForm2_NotFoundException
*/
public function __call($m, $a)
{
if (preg_match('/^(add)([a-zA-Z0-9_]+)$/', $m, $match)) {
if ($match[1] == 'add') {
$type = strtolower($match[2]);
$name = isset($a[0]) ? $a[0] : null;
$attr = isset($a[1]) ? $a[1] : null;
$data = isset($a[2]) ? $a[2] : array();
return $this->addElement($type, $name, $attr, $data);
}
}
trigger_error("Fatal error: Call to undefined method ".get_class($this)."::".$m."()", E_USER_ERROR);
}
/**
* Renders the container using the given renderer
*
* @param HTML_QuickForm2_Renderer $renderer
*
* @return HTML_QuickForm2_Renderer
*/
public function render(HTML_QuickForm2_Renderer $renderer)
{
$renderer->startContainer($this);
foreach ($this as $element) {
$element->render($renderer);
}
$this->renderClientRules($renderer->getJavascriptBuilder());
$renderer->finishContainer($this);
return $renderer;
}
public function __toString()
{
HTML_QuickForm2_Loader::loadClass('HTML_QuickForm2_Renderer');
$renderer = $this->render(HTML_QuickForm2_Renderer::factory('default'));
return $renderer->__toString()
. $renderer->getJavascriptBuilder()->getSetupCode(null, true);
}
/**
* Returns Javascript code for getting the element's value
*
* @param bool $inContainer Whether it should return a parameter
* for qf.form.getContainerValue()
*
* @return string
*/
public function getJavascriptValue($inContainer = false)
{
$args = array();
foreach ($this as $child) {
if ('' != ($value = $child->getJavascriptValue(true))) {
$args[] = $value;
}
}
return 'qf.$cv(' . implode(', ', $args) . ')';
}
public function getJavascriptTriggers()
{
$triggers = array();
foreach ($this as $child) {
foreach ($child->getJavascriptTriggers() as $trigger) {
$triggers[$trigger] = true;
}
}
return array_keys($triggers);
}
}
/**
* Implements a recursive iterator for the container elements
*
* @category HTML
* @package HTML_QuickForm2
* @author Alexey Borzov
* @author Bertrand Mansion
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: 2.0.2
* @link http://pear.php.net/package/HTML_QuickForm2
*/
class HTML_QuickForm2_ContainerIterator extends RecursiveArrayIterator implements RecursiveIterator
{
public function __construct(HTML_QuickForm2_Container $container)
{
parent::__construct($container->getElements());
}
public function hasChildren()
{
return $this->current() instanceof HTML_QuickForm2_Container;
}
public function getChildren()
{
return new HTML_QuickForm2_ContainerIterator($this->current());
}
}
?>