PageRenderTime 52ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/oiclient/data/symfony/form/sfForm.class.php

http://openirudi.googlecode.com/
PHP | 941 lines | 462 code | 117 blank | 362 comment | 37 complexity | 0631f7fb02b57e41b12e721fc79c73ee MD5 | raw file
Possible License(s): LGPL-2.1, AGPL-3.0
  1. <?php
  2. /*
  3. * This file is part of the symfony package.
  4. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. /**
  10. * sfForm represents a form.
  11. *
  12. * A forms is composed of a validator schema and a widget form schema.
  13. *
  14. * sfForm also takes care of CSRF protection by default.
  15. *
  16. * @package symfony
  17. * @subpackage form
  18. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  19. * @version SVN: $Id: sfForm.class.php 11621 2008-09-17 18:46:34Z nicolas $
  20. */
  21. class sfForm implements ArrayAccess
  22. {
  23. protected static
  24. $CSRFProtection = false,
  25. $CSRFSecret = null,
  26. $CSRFFieldName = '_csrf_token',
  27. $toStringException = null;
  28. protected
  29. $widgetSchema = null,
  30. $validatorSchema = null,
  31. $errorSchema = null,
  32. $formFieldSchema = null,
  33. $formFields = array(),
  34. $isBound = false,
  35. $taintedValues = array(),
  36. $taintedFiles = array(),
  37. $values = null,
  38. $defaults = array(),
  39. $options = array();
  40. /**
  41. * Constructor.
  42. *
  43. * @param array $defaults An array of field default values
  44. * @param array $options An array of options
  45. * @param string $CRFSSecret A CSRF secret (false to disable CSRF protection, null to use the global CSRF secret)
  46. */
  47. public function __construct($defaults = array(), $options = array(), $CSRFSecret = null)
  48. {
  49. $this->setDefaults($defaults);
  50. $this->options = $options;
  51. $this->validatorSchema = new sfValidatorSchema();
  52. $this->widgetSchema = new sfWidgetFormSchema();
  53. $this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);
  54. $this->setup();
  55. $this->configure();
  56. $this->addCSRFProtection($CSRFSecret);
  57. $this->resetFormFields();
  58. }
  59. /**
  60. * Returns a string representation of the form.
  61. *
  62. * @return string A string representation of the form
  63. *
  64. * @see render()
  65. */
  66. public function __toString()
  67. {
  68. try
  69. {
  70. return $this->render();
  71. }
  72. catch (Exception $e)
  73. {
  74. self::setToStringException($e);
  75. // we return a simple Exception message in case the form framework is used out of symfony.
  76. return 'Exception: '.$e->getMessage();
  77. }
  78. }
  79. /**
  80. * Configures the current form.
  81. */
  82. public function configure()
  83. {
  84. }
  85. /**
  86. * Setups the current form.
  87. *
  88. * This method is overridden by generator.
  89. *
  90. * If you want to do something at initialization, you have to override the configure() method.
  91. *
  92. * @see configure()
  93. */
  94. public function setup()
  95. {
  96. }
  97. /**
  98. * Renders the widget schema associated with this form.
  99. *
  100. * @param array $attributes An array of HTML attributes
  101. *
  102. * @return string The rendered widget schema
  103. */
  104. public function render($attributes = array())
  105. {
  106. return $this->getFormFieldSchema()->render($attributes);
  107. }
  108. /**
  109. * Renders global errors associated with this form.
  110. *
  111. * @return string The rendered global errors
  112. */
  113. public function renderGlobalErrors()
  114. {
  115. return $this->widgetSchema->getFormFormatter()->formatErrorsForRow($this->getGlobalErrors());
  116. }
  117. /**
  118. * Returns true if the form has some global errors.
  119. *
  120. * @return Boolean true if the form has some global errors, false otherwise
  121. */
  122. public function hasGlobalErrors()
  123. {
  124. return (Boolean) count($this->getGlobalErrors());
  125. }
  126. /**
  127. * Gets the global errors associated with the form.
  128. *
  129. * @return array An array of global errors
  130. */
  131. public function getGlobalErrors()
  132. {
  133. return $this->widgetSchema->getGlobalErrors($this->getErrorSchema());
  134. }
  135. /**
  136. * Binds the form with input values.
  137. *
  138. * It triggers the validator schema validation.
  139. *
  140. * @param array $taintedValues An array of input values
  141. * @param array $taintedFiles An array of uploaded files (in the $_FILES or $_GET format)
  142. */
  143. public function bind(array $taintedValues = null, array $taintedFiles = null)
  144. {
  145. $this->taintedValues = $taintedValues;
  146. $this->taintedFiles = $taintedFiles;
  147. $this->isBound = true;
  148. $this->resetFormFields();
  149. if (is_null($this->taintedValues))
  150. {
  151. $this->taintedValues = array();
  152. }
  153. if (is_null($this->taintedFiles))
  154. {
  155. if ($this->isMultipart())
  156. {
  157. throw new InvalidArgumentException('This form is multipart, which means you need to supply a files array as the bind() method second argument.');
  158. }
  159. $this->taintedFiles = array();
  160. }
  161. try
  162. {
  163. $this->values = $this->validatorSchema->clean(self::deepArrayUnion($this->taintedValues, self::convertFileInformation($this->taintedFiles)));
  164. $this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);
  165. // remove CSRF token
  166. unset($this->values[self::$CSRFFieldName]);
  167. }
  168. catch (sfValidatorErrorSchema $e)
  169. {
  170. $this->values = array();
  171. $this->errorSchema = $e;
  172. }
  173. }
  174. /**
  175. * Returns true if the form is bound to input values.
  176. *
  177. * @return Boolean true if the form is bound to input values, false otherwise
  178. */
  179. public function isBound()
  180. {
  181. return $this->isBound;
  182. }
  183. /**
  184. * Returns true if the form is valid.
  185. *
  186. * It returns false if the form is not bound.
  187. *
  188. * @return Boolean true if the form is valid, false otherwise
  189. */
  190. public function isValid()
  191. {
  192. if (!$this->isBound)
  193. {
  194. return false;
  195. }
  196. return 0 == count($this->errorSchema);
  197. }
  198. /**
  199. * Returns the array of cleaned values.
  200. *
  201. * If the form is not bound, it returns an empty array.
  202. *
  203. * @return array An array of cleaned values
  204. */
  205. public function getValues()
  206. {
  207. return $this->isBound ? $this->values : array();
  208. }
  209. /**
  210. * Returns a cleaned value by field name.
  211. *
  212. * If the form is not bound, it will return null.
  213. *
  214. * @param string $field The name of the value required
  215. * @return string The cleaned value
  216. */
  217. public function getValue($field)
  218. {
  219. return ($this->isBound && isset($this->values[$field])) ? $this->values[$field] : null;
  220. }
  221. /**
  222. * Gets the error schema associated with the form.
  223. *
  224. * @return sfValidatorErrorSchema A sfValidatorErrorSchema instance
  225. */
  226. public function getErrorSchema()
  227. {
  228. return $this->errorSchema;
  229. }
  230. /**
  231. * Embeds a sfForm into the current form.
  232. *
  233. * @param string $name The field name
  234. * @param sfForm $form A sfForm instance
  235. * @param string $decorator A HTML decorator for the embedded form
  236. */
  237. public function embedForm($name, sfForm $form, $decorator = null)
  238. {
  239. if (true === $this->isBound() || true === $form->isBound())
  240. {
  241. throw new LogicException('A bound form cannot be embedded');
  242. }
  243. $form = clone $form;
  244. unset($form[self::$CSRFFieldName]);
  245. $widgetSchema = $form->getWidgetSchema();
  246. $this->setDefault($name, $form->getDefaults());
  247. $decorator = is_null($decorator) ? $widgetSchema->getFormFormatter()->getDecoratorFormat() : $decorator;
  248. $this->widgetSchema[$name] = new sfWidgetFormSchemaDecorator($widgetSchema, $decorator);
  249. $this->validatorSchema[$name] = $form->getValidatorSchema();
  250. $this->resetFormFields();
  251. }
  252. /**
  253. * Embeds a sfForm into the current form n times.
  254. *
  255. * @param string $name The field name
  256. * @param sfForm $form A sfForm instance
  257. * @param integer $n The number of times to embed the form
  258. * @param string $decorator A HTML decorator for the main form around embedded forms
  259. * @param string $innerDecorator A HTML decorator for each embedded form
  260. * @param array $attributes Attributes for schema
  261. * @param array $options Options for schema
  262. * @param array $labels Labels for schema
  263. */
  264. public function embedFormForEach($name, sfForm $form, $n, $decorator = null, $innerDecorator = null, $attributes = array(), $options = array(), $labels = array())
  265. {
  266. if (true === $this->isBound() || true === $form->isBound())
  267. {
  268. throw new LogicException('A bound form cannot be embedded');
  269. }
  270. $form = clone $form;
  271. unset($form[self::$CSRFFieldName]);
  272. $widgetSchema = $form->getWidgetSchema();
  273. // generate labels and default values
  274. $defaults = array();
  275. for ($i = 0; $i < $n; $i++)
  276. {
  277. if (!isset($labels[$i]))
  278. {
  279. $labels[$i] = sprintf('%s (%s)', $widgetSchema->getFormFormatter()->generateLabelName($name), $i);
  280. }
  281. $defaults[$i] = $form->getDefaults();
  282. }
  283. $this->setDefault($name, $defaults);
  284. $decorator = is_null($decorator) ? $widgetSchema->getFormFormatter()->getDecoratorFormat() : $decorator;
  285. $innerDecorator = is_null($innerDecorator) ? $widgetSchema->getFormFormatter()->getDecoratorFormat() : $innerDecorator;
  286. $this->widgetSchema[$name] = new sfWidgetFormSchemaDecorator(new sfWidgetFormSchemaForEach(new sfWidgetFormSchemaDecorator($widgetSchema, $innerDecorator), $n, $attributes, $options, $labels), $decorator);
  287. $this->validatorSchema[$name] = new sfValidatorSchemaForEach($form->getValidatorSchema(), $n);
  288. $this->resetFormFields();
  289. }
  290. /**
  291. * Merges current form widget and validator schemas with the ones from the
  292. * sfForm object passed as parameter. Please note it also merge defaults.
  293. *
  294. * @param sfForm $form The sfForm instance to merge with current form
  295. *
  296. * @throws LogicException If one of the form has already been bound
  297. */
  298. public function mergeForm(sfForm $form)
  299. {
  300. if (true === $this->isBound() || true === $form->isBound())
  301. {
  302. throw new LogicException('A bound form cannot be merged');
  303. }
  304. $form = clone $form;
  305. unset($form[self::$CSRFFieldName]);
  306. $this->defaults = array_merge($this->defaults, $form->getDefaults());
  307. foreach ($form->getWidgetSchema()->getFields() as $field => $widget)
  308. {
  309. $this->widgetSchema[$field] = $widget;
  310. }
  311. foreach ($form->getValidatorSchema()->getFields() as $field => $validator)
  312. {
  313. $this->validatorSchema[$field] = $validator;
  314. }
  315. $this->getWidgetSchema()->setLabels(array_merge($this->getWidgetSchema()->getLabels(),
  316. $form->getWidgetSchema()->getLabels()));
  317. $this->mergePreValidator($form->getValidatorSchema()->getPreValidator());
  318. $this->mergePostValidator($form->getValidatorSchema()->getPostValidator());
  319. $this->resetFormFields();
  320. }
  321. /**
  322. * Merges a validator with the current pre validators.
  323. *
  324. * @param sfValidatorBase $validator A validator to be merged
  325. */
  326. public function mergePreValidator(sfValidatorBase $validator = null)
  327. {
  328. if (is_null($validator))
  329. {
  330. return;
  331. }
  332. if (is_null($this->validatorSchema->getPreValidator()))
  333. {
  334. $this->validatorSchema->setPreValidator($validator);
  335. }
  336. else
  337. {
  338. $this->validatorSchema->setPreValidator(new sfValidatorAnd(array(
  339. $this->validatorSchema->getPreValidator(),
  340. $validator,
  341. )));
  342. }
  343. }
  344. /**
  345. * Merges a validator with the current post validators.
  346. *
  347. * @param sfValidatorBase $validator A validator to be merged
  348. */
  349. public function mergePostValidator(sfValidatorBase $validator = null)
  350. {
  351. if (is_null($validator))
  352. {
  353. return;
  354. }
  355. if (is_null($this->validatorSchema->getPostValidator()))
  356. {
  357. $this->validatorSchema->setPostValidator($validator);
  358. }
  359. else
  360. {
  361. $this->validatorSchema->setPostValidator(new sfValidatorAnd(array(
  362. $this->validatorSchema->getPostValidator(),
  363. $validator,
  364. )));
  365. }
  366. }
  367. /**
  368. * Sets the validators associated with this form.
  369. *
  370. * @param array $validators An array of named validators
  371. */
  372. public function setValidators(array $validators)
  373. {
  374. $this->setValidatorSchema(new sfValidatorSchema($validators));
  375. }
  376. /**
  377. * Sets the validator schema associated with this form.
  378. *
  379. * @param sfValidatorSchema $validatorSchema A sfValidatorSchema instance
  380. */
  381. public function setValidatorSchema(sfValidatorSchema $validatorSchema)
  382. {
  383. $this->validatorSchema = $validatorSchema;
  384. $this->resetFormFields();
  385. }
  386. /**
  387. * Gets the validator schema associated with this form.
  388. *
  389. * @return sfValidatorSchema A sfValidatorSchema instance
  390. */
  391. public function getValidatorSchema()
  392. {
  393. return $this->validatorSchema;
  394. }
  395. /**
  396. * Sets the widgets associated with this form.
  397. *
  398. * @param array $widgets An array of named widgets
  399. */
  400. public function setWidgets(array $widgets)
  401. {
  402. $this->setWidgetSchema(new sfWidgetFormSchema($widgets));
  403. }
  404. /**
  405. * Sets the widget schema associated with this form.
  406. *
  407. * @param sfWidgetFormSchema $widgetSchema A sfWidgetFormSchema instance
  408. */
  409. public function setWidgetSchema(sfWidgetFormSchema $widgetSchema)
  410. {
  411. $this->widgetSchema = $widgetSchema;
  412. $this->resetFormFields();
  413. }
  414. /**
  415. * Gets the widget schema associated with this form.
  416. *
  417. * @return sfWidgetFormSchema A sfWidgetFormSchema instance
  418. */
  419. public function getWidgetSchema()
  420. {
  421. return $this->widgetSchema;
  422. }
  423. /**
  424. * Sets an option value.
  425. *
  426. * @param string $name The option name
  427. * @param mixed $value The default value
  428. */
  429. public function setOption($name, $value)
  430. {
  431. $this->options[$name] = $value;
  432. }
  433. /**
  434. * Gets an option value.
  435. *
  436. * @param string $name The option name
  437. * @param mixed $default The default value (null by default)
  438. *
  439. * @param mixed The default value
  440. */
  441. public function getOption($name, $default = null)
  442. {
  443. return isset($this->options[$name]) ? $this->options[$name] : $default;
  444. }
  445. /**
  446. * Sets a default value for a form field.
  447. *
  448. * @param string $name The field name
  449. * @param mixed $default The default value
  450. */
  451. public function setDefault($name, $default)
  452. {
  453. $this->defaults[$name] = $default;
  454. $this->resetFormFields();
  455. }
  456. /**
  457. * Gets a default value for a form field.
  458. *
  459. * @param string $name The field name
  460. *
  461. * @param mixed The default value
  462. */
  463. public function getDefault($name)
  464. {
  465. return isset($this->defaults[$name]) ? $this->defaults[$name] : null;
  466. }
  467. /**
  468. * Returns true if the form has a default value for a form field.
  469. *
  470. * @param string $name The field name
  471. *
  472. * @param Boolean true if the form has a default value for this field, false otherwise
  473. */
  474. public function hasDefault($name)
  475. {
  476. return array_key_exists($name, $this->defaults);
  477. }
  478. /**
  479. * Sets the default values for the form.
  480. *
  481. * The default values are only used if the form is not bound.
  482. *
  483. * @param array $defaults An array of default values
  484. */
  485. public function setDefaults($defaults)
  486. {
  487. $this->defaults = $defaults;
  488. if (self::$CSRFProtection)
  489. {
  490. $this->setDefault(self::$CSRFFieldName, $this->getCSRFToken(self::$CSRFSecret));
  491. }
  492. $this->resetFormFields();
  493. }
  494. /**
  495. * Gets the default values for the form.
  496. *
  497. * @return array An array of default values
  498. */
  499. public function getDefaults()
  500. {
  501. return $this->defaults;
  502. }
  503. /**
  504. * Adds CSRF protection to the current form.
  505. *
  506. * @param string $secret The secret to use to compute the CSRF token
  507. */
  508. public function addCSRFProtection($secret)
  509. {
  510. if (false === $secret || (is_null($secret) && !self::$CSRFProtection))
  511. {
  512. return;
  513. }
  514. if (is_null($secret))
  515. {
  516. if (is_null(self::$CSRFSecret))
  517. {
  518. self::$CSRFSecret = md5(__FILE__.php_uname());
  519. }
  520. $secret = self::$CSRFSecret;
  521. }
  522. $token = $this->getCSRFToken($secret);
  523. $this->validatorSchema[self::$CSRFFieldName] = new sfValidatorCSRFToken(array('token' => $token));
  524. $this->widgetSchema[self::$CSRFFieldName] = new sfWidgetFormInputHidden();
  525. $this->setDefault(self::$CSRFFieldName, $token);
  526. }
  527. /**
  528. * Returns a CSRF token, given a secret.
  529. *
  530. * If you want to change the algorithm used to compute the token, you
  531. * can override this method.
  532. *
  533. * @param string $secret The secret string to use
  534. *
  535. * @return string A token string
  536. */
  537. public function getCSRFToken($secret)
  538. {
  539. return md5($secret.session_id().get_class($this));
  540. }
  541. /**
  542. * @return true if this form is CSRF protected
  543. */
  544. public function isCSRFProtected()
  545. {
  546. return !is_null($this->validatorSchema[self::$CSRFFieldName]);
  547. }
  548. /**
  549. * Sets the CSRF field name.
  550. *
  551. * @param string $name The CSRF field name
  552. */
  553. static public function setCSRFFieldName($name)
  554. {
  555. self::$CSRFFieldName = $name;
  556. }
  557. /**
  558. * Gets the CSRF field name.
  559. *
  560. * @return string The CSRF field name
  561. */
  562. static public function getCSRFFieldName()
  563. {
  564. return self::$CSRFFieldName;
  565. }
  566. /**
  567. * Enables CSRF protection for all forms.
  568. *
  569. * The given secret will be used for all forms, except if you pass a secret in the constructor.
  570. * Even if a secret is automatically generated if you don't provide a secret, you're strongly advised
  571. * to provide one by yourself.
  572. *
  573. * @param string $secret A secret to use when computing the CSRF token
  574. */
  575. static public function enableCSRFProtection($secret = null)
  576. {
  577. if (false === $secret)
  578. {
  579. return self::disableCSRFProtection();
  580. }
  581. self::$CSRFProtection = true;
  582. if (!is_null($secret))
  583. {
  584. self::$CSRFSecret = $secret;
  585. }
  586. }
  587. /**
  588. * Disables CSRF protection for all forms.
  589. */
  590. static public function disableCSRFProtection()
  591. {
  592. self::$CSRFProtection = false;
  593. }
  594. /**
  595. * Returns true if the form is multipart.
  596. *
  597. * @return Boolean true if the form is multipart
  598. */
  599. public function isMultipart()
  600. {
  601. return $this->widgetSchema->needsMultipartForm();
  602. }
  603. public function resetFormFields()
  604. {
  605. $this->formFields = array();
  606. $this->formFieldSchema = null;
  607. }
  608. /**
  609. * Returns true if the bound field exists (implements the ArrayAccess interface).
  610. *
  611. * @param string $name The name of the bound field
  612. *
  613. * @return Boolean true if the widget exists, false otherwise
  614. */
  615. public function offsetExists($name)
  616. {
  617. return isset($this->widgetSchema[$name]);
  618. }
  619. /**
  620. * Returns the form field associated with the name (implements the ArrayAccess interface).
  621. *
  622. * @param string $name The offset of the value to get
  623. *
  624. * @return sfFormField A form field instance
  625. */
  626. public function offsetGet($name)
  627. {
  628. if (!isset($this->formFields[$name]))
  629. {
  630. if (!$widget = $this->widgetSchema[$name])
  631. {
  632. throw new InvalidArgumentException(sprintf('Widget "%s" does not exist.', $name));
  633. }
  634. $values = $this->isBound ? $this->taintedValues : $this->defaults;
  635. $class = $widget instanceof sfWidgetFormSchema ? 'sfFormFieldSchema' : 'sfFormField';
  636. $this->formFields[$name] = new $class($widget, $this->getFormFieldSchema(), $name, isset($values[$name]) ? $values[$name] : null, $this->errorSchema[$name]);
  637. }
  638. return $this->formFields[$name];
  639. }
  640. /**
  641. * Throws an exception saying that values cannot be set (implements the ArrayAccess interface).
  642. *
  643. * @param string $offset (ignored)
  644. * @param string $value (ignored)
  645. *
  646. * @throws <b>LogicException</b>
  647. */
  648. public function offsetSet($offset, $value)
  649. {
  650. throw new LogicException('Cannot update form fields.');
  651. }
  652. /**
  653. * Removes a field from the form.
  654. *
  655. * It removes the widget and the validator for the given field.
  656. *
  657. * @param string $offset The field name
  658. */
  659. public function offsetUnset($offset)
  660. {
  661. unset($this->widgetSchema[$offset], $this->validatorSchema[$offset]);
  662. $this->resetFormFields();
  663. }
  664. /**
  665. * Returns a form field for the main widget schema.
  666. *
  667. * @return sfFormFieldSchema A sfFormFieldSchema instance
  668. */
  669. public function getFormFieldSchema()
  670. {
  671. if (is_null($this->formFieldSchema))
  672. {
  673. $this->formFieldSchema = new sfFormFieldSchema($this->widgetSchema, null, null, $this->isBound ? $this->taintedValues : $this->defaults, $this->errorSchema);
  674. }
  675. return $this->formFieldSchema;
  676. }
  677. /**
  678. * Converts uploaded file array to a format following the $_GET and $POST naming convention.
  679. *
  680. * It's safe to pass an already converted array, in which case this method just returns the original array unmodified.
  681. *
  682. * @param array $taintedFiles An array representing uploaded file information
  683. *
  684. * @return array An array of re-ordered uploaded file information
  685. */
  686. static public function convertFileInformation(array $taintedFiles)
  687. {
  688. return self::pathsToArray(preg_replace('#^(/[^/]+)?(/name|/type|/tmp_name|/error|/size)([^\s]*)( = [^\n]*)#m', '$1$3$2$4', self::arrayToPaths($taintedFiles)));
  689. }
  690. /**
  691. * Converts a string of paths separated by newlines into an array.
  692. *
  693. * Code adapted from http://www.shauninman.com/archive/2006/11/30/fixing_the_files_superglobal
  694. * @author Shaun Inman (www.shauninman.com)
  695. *
  696. * @param string $str A string representing an array
  697. *
  698. * @return Array An array
  699. */
  700. static public function pathsToArray($str)
  701. {
  702. $array = array();
  703. $lines = explode("\n", trim($str));
  704. if (!empty($lines[0]))
  705. {
  706. foreach ($lines as $line)
  707. {
  708. list($path, $value) = explode(' = ', $line);
  709. $steps = explode('/', $path);
  710. array_shift($steps);
  711. $insertion =& $array;
  712. foreach ($steps as $step)
  713. {
  714. if (!isset($insertion[$step]))
  715. {
  716. $insertion[$step] = array();
  717. }
  718. $insertion =& $insertion[$step];
  719. }
  720. $insertion = ctype_digit($value) ? (int) $value : $value;
  721. }
  722. }
  723. return $array;
  724. }
  725. /**
  726. * Converts an array into a string containing the path to each of its values separated by a newline.
  727. *
  728. * Code adapted from http://www.shauninman.com/archive/2006/11/30/fixing_the_files_superglobal
  729. * @author Shaun Inman (www.shauninman.com)
  730. *
  731. * @param Array $array An array
  732. * @param string $prefix Prefix for internal use
  733. *
  734. * @return string A string representing the array
  735. */
  736. static public function arrayToPaths($array = array(), $prefix = '')
  737. {
  738. $str = '';
  739. $freshPrefix = $prefix;
  740. foreach ($array as $key => $value)
  741. {
  742. $freshPrefix .= "/{$key}";
  743. if (is_array($value))
  744. {
  745. $str .= self::arrayToPaths($value, $freshPrefix);
  746. $freshPrefix = $prefix;
  747. }
  748. else
  749. {
  750. $str .= "{$prefix}/{$key} = {$value}\n";
  751. }
  752. }
  753. return $str;
  754. }
  755. /**
  756. * Returns true if a form thrown an exception in the __toString() method
  757. *
  758. * This is a hack needed because PHP does not allow to throw exceptions in __toString() magic method.
  759. *
  760. * @return boolean
  761. */
  762. static public function hasToStringException()
  763. {
  764. return !is_null(self::$toStringException);
  765. }
  766. /**
  767. * Gets the exception if one was thrown in the __toString() method.
  768. *
  769. * This is a hack needed because PHP does not allow to throw exceptions in __toString() magic method.
  770. *
  771. * @return Exception
  772. */
  773. static public function getToStringException()
  774. {
  775. return self::$toStringException;
  776. }
  777. /**
  778. * Sets an exception thrown by the __toString() method.
  779. *
  780. * This is a hack needed because PHP does not allow to throw exceptions in __toString() magic method.
  781. *
  782. * @param Exception $e The exception thrown by __toString()
  783. */
  784. static public function setToStringException(Exception $e)
  785. {
  786. if (is_null(self::$toStringException))
  787. {
  788. self::$toStringException = $e;
  789. }
  790. }
  791. public function __clone()
  792. {
  793. $this->widgetSchema = clone $this->widgetSchema;
  794. $this->validatorSchema = clone $this->validatorSchema;
  795. // we rebind the cloned form because Exceptions are not clonable
  796. if ($this->isBound())
  797. {
  798. $this->bind($this->taintedValues, $this->taintedFiles);
  799. }
  800. }
  801. /**
  802. * Merges two arrays without reindexing numeric keys.
  803. *
  804. * @param array $array1 An array to merge
  805. * @param array $array2 An array to merge
  806. *
  807. * @return array The merged array
  808. */
  809. static protected function deepArrayUnion($array1, $array2)
  810. {
  811. foreach ($array2 as $key => $value)
  812. {
  813. if (is_array($value) && isset($array1[$key]) && is_array($array1[$key]))
  814. {
  815. $array1[$key] = self::deepArrayUnion($array1[$key], $value);
  816. }
  817. else
  818. {
  819. $array1[$key] = $value;
  820. }
  821. }
  822. return $array1;
  823. }
  824. }