1<?php 2/** 3 * Base class for simple HTML_QuickForm2 containers 4 * 5 * PHP version 5 6 * 7 * LICENSE: 8 * 9 * Copyright (c) 2006-2010, Alexey Borzov <avb@php.net>, 10 * Bertrand Mansion <golgote@mamasam.com> 11 * All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 17 * * Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * * Redistributions in binary form must reproduce the above copyright 20 * notice, this list of conditions and the following disclaimer in the 21 * documentation and/or other materials provided with the distribution. 22 * * The names of the authors may not be used to endorse or promote products 23 * derived from this software without specific prior written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 26 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 27 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 30 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 31 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 32 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 33 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 34 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 35 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 * 37 * @category HTML 38 * @package HTML_QuickForm2 39 * @author Alexey Borzov <avb@php.net> 40 * @author Bertrand Mansion <golgote@mamasam.com> 41 * @license http://opensource.org/licenses/bsd-license.php New BSD License 42 * @version SVN: $Id: Container.php 300722 2010-06-24 10:15:52Z mansion $ 43 * @link http://pear.php.net/package/HTML_QuickForm2 44 */ 45 46/** 47 * Base class for all HTML_QuickForm2 elements 48 */ 49// require_once 'HTML/QuickForm2/Node.php'; 50 51 52/** 53 * Implements a recursive iterator for the container elements 54 * 55 * @category HTML 56 * @package HTML_QuickForm2 57 * @author Alexey Borzov <avb@php.net> 58 * @author Bertrand Mansion <golgote@mamasam.com> 59 * @version Release: @package_version@ 60 */ 61class HTML_QuickForm2_ContainerIterator extends RecursiveArrayIterator implements RecursiveIterator 62{ 63 public function __construct(HTML_QuickForm2_Container $container) 64 { 65 parent::__construct($container->getElements()); 66 } 67 68 public function hasChildren() 69 { 70 return $this->current() instanceof HTML_QuickForm2_Container; 71 } 72 73 public function getChildren() 74 { 75 return new HTML_QuickForm2_ContainerIterator($this->current()); 76 } 77} 78 79/** 80 * Abstract base class for simple QuickForm2 containers 81 * 82 * @category HTML 83 * @package HTML_QuickForm2 84 * @author Alexey Borzov <avb@php.net> 85 * @author Bertrand Mansion <golgote@mamasam.com> 86 * @version Release: @package_version@ 87 */ 88abstract class HTML_QuickForm2_Container extends HTML_QuickForm2_Node 89 implements IteratorAggregate, Countable 90{ 91 /** 92 * Array of elements contained in this container 93 * @var array 94 */ 95 protected $elements = array(); 96 97 98 public function setName($name) 99 { 100 $this->attributes['name'] = (string)$name; 101 return $this; 102 } 103 104 public function toggleFrozen($freeze = null) 105 { 106 if (null !== $freeze) { 107 foreach ($this as $child) { 108 $child->toggleFrozen($freeze); 109 } 110 } 111 return parent::toggleFrozen($freeze); 112 } 113 114 public function persistentFreeze($persistent = null) 115 { 116 if (null !== $persistent) { 117 foreach ($this as $child) { 118 $child->persistentFreeze($persistent); 119 } 120 } 121 return parent::persistentFreeze($persistent); 122 } 123 124 /** 125 * Whether container prepends its name to names of contained elements 126 * 127 * @return bool 128 */ 129 protected function prependsName() 130 { 131 return false; 132 } 133 134 /** 135 * Returns the element's value 136 * 137 * The default implementation for Containers is to return an array with 138 * contained elements' values. The array is indexed the same way $_GET and 139 * $_POST arrays would be for these elements. 140 * 141 * @return array|null 142 */ 143 public function getValue() 144 { 145 $values = array(); 146 foreach ($this as $child) { 147 $value = $child->getValue(); 148 if (null !== $value) { 149 if ($child instanceof HTML_QuickForm2_Container 150 && !$child->prependsName() 151 ) { 152 $values = self::arrayMerge($values, $value); 153 } else { 154 $name = $child->getName(); 155 if (!strpos($name, '[')) { 156 $values[$name] = $value; 157 } else { 158 $tokens = explode('[', str_replace(']', '', $name)); 159 $valueAry =& $values; 160 do { 161 $token = array_shift($tokens); 162 if (!isset($valueAry[$token])) { 163 $valueAry[$token] = array(); 164 } 165 $valueAry =& $valueAry[$token]; 166 } while (count($tokens) > 1); 167 $valueAry[$tokens[0]] = $value; 168 } 169 } 170 } 171 } 172 return empty($values)? null: $this->applyFilters($values); 173 } 174 175 /** 176 * Merges two arrays 177 * 178 * Merges two arrays like the PHP function array_merge_recursive does, 179 * the difference being that existing integer keys will not be renumbered. 180 * 181 * @param array 182 * @param array 183 * @return array resulting array 184 */ 185 protected static function arrayMerge($a, $b) 186 { 187 foreach ($b as $k => $v) { 188 if (!is_array($v) || isset($a[$k]) && !is_array($a[$k])) { 189 $a[$k] = $v; 190 } else { 191 $a[$k] = self::arrayMerge(isset($a[$k])? $a[$k]: array(), $v); 192 } 193 } 194 return $a; 195 } 196 197 /** 198 * Returns an array of this container's elements 199 * 200 * @return array Container elements 201 */ 202 public function getElements() 203 { 204 return $this->elements; 205 } 206 207 /** 208 * Appends an element to the container 209 * 210 * If the element was previously added to the container or to another 211 * container, it is first removed there. 212 * 213 * @param HTML_QuickForm2_Node Element to add 214 * @return HTML_QuickForm2_Node Added element 215 * @throws HTML_QuickForm2_InvalidArgumentException 216 */ 217 public function appendChild(HTML_QuickForm2_Node $element) 218 { 219 if ($this === $element->getContainer()) { 220 $this->removeChild($element); 221 } 222 $element->setContainer($this); 223 $this->elements[] = $element; 224 return $element; 225 } 226 227 /** 228 * Appends an element to the container (possibly creating it first) 229 * 230 * If the first parameter is an instance of HTML_QuickForm2_Node then all 231 * other parameters are ignored and the method just calls {@link appendChild()}. 232 * In the other case the element is first created via 233 * {@link HTML_QuickForm2_Factory::createElement()} and then added via the 234 * same method. This is a convenience method to reduce typing and ease 235 * porting from HTML_QuickForm. 236 * 237 * @param string|HTML_QuickForm2_Node Either type name (treated 238 * case-insensitively) or an element instance 239 * @param mixed Element name 240 * @param mixed Element attributes 241 * @param array Element-specific data 242 * @return HTML_QuickForm2_Node Added element 243 * @throws HTML_QuickForm2_InvalidArgumentException 244 * @throws HTML_QuickForm2_NotFoundException 245 */ 246 public function addElement($elementOrType, $name = null, $attributes = null, 247 array $data = array()) 248 { 249 if ($elementOrType instanceof HTML_QuickForm2_Node) { 250 return $this->appendChild($elementOrType); 251 } else { 252 return $this->appendChild(HTML_QuickForm2_Factory::createElement( 253 $elementOrType, $name, $attributes, $data 254 )); 255 } 256 } 257 258 /** 259 * Removes the element from this container 260 * 261 * If the reference object is not given, the element will be appended. 262 * 263 * @param HTML_QuickForm2_Node Element to remove 264 * @return HTML_QuickForm2_Node Removed object 265 */ 266 public function removeChild(HTML_QuickForm2_Node $element) 267 { 268 269 if ($element->getContainer() !== $this) { 270 throw new HTML_QuickForm2_NotFoundException( 271 "Element with name '".$element->getName()."' was not found" 272 ); 273 } 274 foreach ($this as $key => $child){ 275 if ($child === $element) { 276 unset($this->elements[$key]); 277 $element->setContainer(null); 278 break; 279 } 280 } 281 return $element; 282 } 283 284 285 /** 286 * Returns an element if its id is found 287 * 288 * @param string Element id to find 289 * @return HTML_QuickForm2_Node|null 290 */ 291 public function getElementById($id) 292 { 293 foreach ($this->getRecursiveIterator() as $element) { 294 if ($id == $element->getId()) { 295 return $element; 296 } 297 } 298 return null; 299 } 300 301 /** 302 * Returns an array of elements which name corresponds to element 303 * 304 * @param string Elements name to find 305 * @return array 306 */ 307 public function getElementsByName($name) 308 { 309 $found = array(); 310 foreach ($this->getRecursiveIterator() as $element) { 311 if ($element->getName() == $name) { 312 $found[] = $element; 313 } 314 } 315 return $found; 316 } 317 318 /** 319 * Inserts an element in the container 320 * 321 * If the reference object is not given, the element will be appended. 322 * 323 * @param HTML_QuickForm2_Node Element to insert 324 * @param HTML_QuickForm2_Node Reference to insert before 325 * @return HTML_QuickForm2_Node Inserted element 326 */ 327 public function insertBefore(HTML_QuickForm2_Node $element, HTML_QuickForm2_Node $reference = null) 328 { 329 if (null === $reference) { 330 return $this->appendChild($element); 331 } 332 $offset = 0; 333 foreach ($this as $child) { 334 if ($child === $reference) { 335 if ($this === $element->getContainer()) { 336 $this->removeChild($element); 337 } 338 $element->setContainer($this); 339 array_splice($this->elements, $offset, 0, array($element)); 340 return $element; 341 } 342 $offset++; 343 } 344 throw new HTML_QuickForm2_NotFoundException( 345 "Reference element with name '".$reference->getName()."' was not found" 346 ); 347 } 348 349 /** 350 * Returns a recursive iterator for the container elements 351 * 352 * @return HTML_QuickForm2_ContainerIterator 353 */ 354 public function getIterator(): HTML_QuickForm2_ContainerIterator 355 { 356 return new HTML_QuickForm2_ContainerIterator($this); 357 } 358 359 /** 360 * Returns a recursive iterator iterator for the container elements 361 * 362 * @param int mode passed to RecursiveIteratorIterator 363 * @return RecursiveIteratorIterator 364 */ 365 public function getRecursiveIterator($mode = RecursiveIteratorIterator::SELF_FIRST): RecursiveIteratorIterator 366 { 367 return new RecursiveIteratorIterator( 368 new HTML_QuickForm2_ContainerIterator($this), $mode 369 ); 370 } 371 372 /** 373 * Returns the number of elements in the container 374 * 375 * @return int 376 */ 377 public function count(): int 378 { 379 return count($this->elements); 380 } 381 382 /** 383 * Called when the element needs to update its value from form's data sources 384 * 385 * The default behaviour is just to call the updateValue() methods of 386 * contained elements, since default Container doesn't have any value itself 387 */ 388 public function updateValue() 389 { 390 foreach ($this as $child) { 391 $child->updateValue(); 392 } 393 } 394 395 396 /** 397 * Performs the server-side validation 398 * 399 * This method also calls validate() on all contained elements. 400 * 401 * @return boolean Whether the container and all contained elements are valid 402 */ 403 protected function validate() 404 { 405 $valid = parent::validate(); 406 foreach ($this as $child) { 407 $valid = $child->validate() && $valid; 408 } 409 return $valid; 410 } 411 412 /** 413 * Appends an element to the container, creating it first 414 * 415 * The element will be created via {@link HTML_QuickForm2_Factory::createElement()} 416 * and then added via the {@link appendChild()} method. 417 * The element type is deduced from the method name. 418 * This is a convenience method to reduce typing. 419 * 420 * @param mixed Element name 421 * @param mixed Element attributes 422 * @param array Element-specific data 423 * @return HTML_QuickForm2_Node Added element 424 * @throws HTML_QuickForm2_InvalidArgumentException 425 * @throws HTML_QuickForm2_NotFoundException 426 */ 427 public function __call($m, $a) 428 { 429 if (preg_match('/^(add)([a-zA-Z0-9_]+)$/', $m, $match)) { 430 if ($match[1] == 'add') { 431 $type = strtolower($match[2]); 432 $name = isset($a[0]) ? $a[0] : null; 433 $attr = isset($a[1]) ? $a[1] : null; 434 $data = isset($a[2]) ? $a[2] : array(); 435 return $this->addElement($type, $name, $attr, $data); 436 } 437 } 438 trigger_error("Fatal error: Call to undefined method ".get_class($this)."::".$m."()", E_USER_ERROR); 439 } 440 441 /** 442 * Renders the container using the given renderer 443 * 444 * @param HTML_QuickForm2_Renderer Renderer instance 445 * @return HTML_QuickForm2_Renderer 446 */ 447 public function render(HTML_QuickForm2_Renderer $renderer) 448 { 449 foreach ($this->rules as $rule) { 450 if ($rule[1] & HTML_QuickForm2_Rule::RUNAT_CLIENT) { 451 $renderer->getJavascriptBuilder()->addRule($rule[0]); 452 } 453 } 454 $renderer->startContainer($this); 455 foreach ($this as $element) { 456 $element->render($renderer); 457 } 458 $renderer->finishContainer($this); 459 return $renderer; 460 } 461 462 public function __toString() 463 { 464 // require_once 'HTML/QuickForm2/Renderer.php'; 465 466 return $this->render(HTML_QuickForm2_Renderer::factory('default'))->__toString(); 467 } 468 469 /** 470 * Returns Javascript code for getting the element's value 471 * 472 * @return string 473 */ 474 public function getJavascriptValue() 475 { 476 $args = array(); 477 foreach ($this as $child) { 478 if ($child instanceof HTML_QuickForm2_Container) { 479 $args[] = $child->getJavascriptValue(); 480 } else { 481 $args[] = "'" . $child->getId() . "'"; 482 } 483 } 484 return 'qf.form.getContainerValue(' . implode(', ', $args) . ')'; 485 } 486} 487 488?> 489