sfForm.class.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  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 17023 2009-04-06 06:15:22Z fabien $
  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. $name = (string) $name;
  240. if (true === $this->isBound() || true === $form->isBound())
  241. {
  242. throw new LogicException('A bound form cannot be embedded');
  243. }
  244. $form = clone $form;
  245. unset($form[self::$CSRFFieldName]);
  246. $widgetSchema = $form->getWidgetSchema();
  247. $this->setDefault($name, $form->getDefaults());
  248. $decorator = is_null($decorator) ? $widgetSchema->getFormFormatter()->getDecoratorFormat() : $decorator;
  249. $this->widgetSchema[$name] = new sfWidgetFormSchemaDecorator($widgetSchema, $decorator);
  250. $this->validatorSchema[$name] = $form->getValidatorSchema();
  251. $this->resetFormFields();
  252. }
  253. /**
  254. * Embeds a sfForm into the current form n times.
  255. *
  256. * @param string $name The field name
  257. * @param sfForm $form A sfForm instance
  258. * @param integer $n The number of times to embed the form
  259. * @param string $decorator A HTML decorator for the main form around embedded forms
  260. * @param string $innerDecorator A HTML decorator for each embedded form
  261. * @param array $options Options for schema
  262. * @param array $attributes Attributes for schema
  263. * @param array $labels Labels for schema
  264. */
  265. public function embedFormForEach($name, sfForm $form, $n, $decorator = null, $innerDecorator = null, $options = array(), $attributes = array(), $labels = array())
  266. {
  267. if (true === $this->isBound() || true === $form->isBound())
  268. {
  269. throw new LogicException('A bound form cannot be embedded');
  270. }
  271. $form = clone $form;
  272. unset($form[self::$CSRFFieldName]);
  273. $widgetSchema = $form->getWidgetSchema();
  274. // generate labels and default values
  275. $defaults = array();
  276. for ($i = 0; $i < $n; $i++)
  277. {
  278. if (!isset($labels[$i]))
  279. {
  280. $labels[$i] = sprintf('%s (%s)', $widgetSchema->getFormFormatter()->generateLabelName($name), $i);
  281. }
  282. $defaults[$i] = $form->getDefaults();
  283. }
  284. $this->setDefault($name, $defaults);
  285. $decorator = is_null($decorator) ? $widgetSchema->getFormFormatter()->getDecoratorFormat() : $decorator;
  286. $innerDecorator = is_null($innerDecorator) ? $widgetSchema->getFormFormatter()->getDecoratorFormat() : $innerDecorator;
  287. $this->widgetSchema[$name] = new sfWidgetFormSchemaDecorator(new sfWidgetFormSchemaForEach(new sfWidgetFormSchemaDecorator($widgetSchema, $innerDecorator), $n, $options, $attributes, $labels), $decorator);
  288. $this->validatorSchema[$name] = new sfValidatorSchemaForEach($form->getValidatorSchema(), $n);
  289. $this->resetFormFields();
  290. }
  291. /**
  292. * Merges current form widget and validator schemas with the ones from the
  293. * sfForm object passed as parameter. Please note it also merge defaults.
  294. *
  295. * @param sfForm $form The sfForm instance to merge with current form
  296. *
  297. * @throws LogicException If one of the form has already been bound
  298. */
  299. public function mergeForm(sfForm $form)
  300. {
  301. if (true === $this->isBound() || true === $form->isBound())
  302. {
  303. throw new LogicException('A bound form cannot be merged');
  304. }
  305. $form = clone $form;
  306. unset($form[self::$CSRFFieldName]);
  307. $this->defaults = array_merge($this->defaults, $form->getDefaults());
  308. $widgets = $form->getWidgetSchema()->getFields();
  309. foreach ($form->getWidgetSchema()->getPositions() as $field)
  310. {
  311. $this->widgetSchema[$field] = $widgets[$field];
  312. }
  313. foreach ($form->getValidatorSchema()->getFields() as $field => $validator)
  314. {
  315. $this->validatorSchema[$field] = $validator;
  316. }
  317. $this->getWidgetSchema()->setLabels(array_merge($this->getWidgetSchema()->getLabels(), $form->getWidgetSchema()->getLabels()));
  318. $this->getWidgetSchema()->setHelps(array_merge($this->getWidgetSchema()->getHelps(), $form->getWidgetSchema()->getHelps()));
  319. $this->mergePreValidator($form->getValidatorSchema()->getPreValidator());
  320. $this->mergePostValidator($form->getValidatorSchema()->getPostValidator());
  321. $this->resetFormFields();
  322. }
  323. /**
  324. * Merges a validator with the current pre validators.
  325. *
  326. * @param sfValidatorBase $validator A validator to be merged
  327. */
  328. public function mergePreValidator(sfValidatorBase $validator = null)
  329. {
  330. if (is_null($validator))
  331. {
  332. return;
  333. }
  334. if (is_null($this->validatorSchema->getPreValidator()))
  335. {
  336. $this->validatorSchema->setPreValidator($validator);
  337. }
  338. else
  339. {
  340. $this->validatorSchema->setPreValidator(new sfValidatorAnd(array(
  341. $this->validatorSchema->getPreValidator(),
  342. $validator,
  343. )));
  344. }
  345. }
  346. /**
  347. * Merges a validator with the current post validators.
  348. *
  349. * @param sfValidatorBase $validator A validator to be merged
  350. */
  351. public function mergePostValidator(sfValidatorBase $validator = null)
  352. {
  353. if (is_null($validator))
  354. {
  355. return;
  356. }
  357. if (is_null($this->validatorSchema->getPostValidator()))
  358. {
  359. $this->validatorSchema->setPostValidator($validator);
  360. }
  361. else
  362. {
  363. $this->validatorSchema->setPostValidator(new sfValidatorAnd(array(
  364. $this->validatorSchema->getPostValidator(),
  365. $validator,
  366. )));
  367. }
  368. }
  369. /**
  370. * Sets the validators associated with this form.
  371. *
  372. * @param array $validators An array of named validators
  373. */
  374. public function setValidators(array $validators)
  375. {
  376. $this->setValidatorSchema(new sfValidatorSchema($validators));
  377. }
  378. /**
  379. * Sets the validator schema associated with this form.
  380. *
  381. * @param sfValidatorSchema $validatorSchema A sfValidatorSchema instance
  382. */
  383. public function setValidatorSchema(sfValidatorSchema $validatorSchema)
  384. {
  385. $this->validatorSchema = $validatorSchema;
  386. $this->resetFormFields();
  387. }
  388. /**
  389. * Gets the validator schema associated with this form.
  390. *
  391. * @return sfValidatorSchema A sfValidatorSchema instance
  392. */
  393. public function getValidatorSchema()
  394. {
  395. return $this->validatorSchema;
  396. }
  397. /**
  398. * Sets the widgets associated with this form.
  399. *
  400. * @param array $widgets An array of named widgets
  401. */
  402. public function setWidgets(array $widgets)
  403. {
  404. $this->setWidgetSchema(new sfWidgetFormSchema($widgets));
  405. }
  406. /**
  407. * Sets the widget schema associated with this form.
  408. *
  409. * @param sfWidgetFormSchema $widgetSchema A sfWidgetFormSchema instance
  410. */
  411. public function setWidgetSchema(sfWidgetFormSchema $widgetSchema)
  412. {
  413. $this->widgetSchema = $widgetSchema;
  414. $this->resetFormFields();
  415. }
  416. /**
  417. * Gets the widget schema associated with this form.
  418. *
  419. * @return sfWidgetFormSchema A sfWidgetFormSchema instance
  420. */
  421. public function getWidgetSchema()
  422. {
  423. return $this->widgetSchema;
  424. }
  425. /**
  426. * Sets an option value.
  427. *
  428. * @param string $name The option name
  429. * @param mixed $value The default value
  430. */
  431. public function setOption($name, $value)
  432. {
  433. $this->options[$name] = $value;
  434. }
  435. /**
  436. * Gets an option value.
  437. *
  438. * @param string $name The option name
  439. * @param mixed $default The default value (null by default)
  440. *
  441. * @param mixed The default value
  442. */
  443. public function getOption($name, $default = null)
  444. {
  445. return isset($this->options[$name]) ? $this->options[$name] : $default;
  446. }
  447. /**
  448. * Sets a default value for a form field.
  449. *
  450. * @param string $name The field name
  451. * @param mixed $default The default value
  452. */
  453. public function setDefault($name, $default)
  454. {
  455. $this->defaults[$name] = $default;
  456. $this->resetFormFields();
  457. }
  458. /**
  459. * Gets a default value for a form field.
  460. *
  461. * @param string $name The field name
  462. *
  463. * @param mixed The default value
  464. */
  465. public function getDefault($name)
  466. {
  467. return isset($this->defaults[$name]) ? $this->defaults[$name] : null;
  468. }
  469. /**
  470. * Returns true if the form has a default value for a form field.
  471. *
  472. * @param string $name The field name
  473. *
  474. * @param Boolean true if the form has a default value for this field, false otherwise
  475. */
  476. public function hasDefault($name)
  477. {
  478. return array_key_exists($name, $this->defaults);
  479. }
  480. /**
  481. * Sets the default values for the form.
  482. *
  483. * The default values are only used if the form is not bound.
  484. *
  485. * @param array $defaults An array of default values
  486. */
  487. public function setDefaults($defaults)
  488. {
  489. $this->defaults = $defaults;
  490. if (self::$CSRFProtection)
  491. {
  492. $this->setDefault(self::$CSRFFieldName, $this->getCSRFToken(self::$CSRFSecret));
  493. }
  494. $this->resetFormFields();
  495. }
  496. /**
  497. * Gets the default values for the form.
  498. *
  499. * @return array An array of default values
  500. */
  501. public function getDefaults()
  502. {
  503. return $this->defaults;
  504. }
  505. /**
  506. * Adds CSRF protection to the current form.
  507. *
  508. * @param string $secret The secret to use to compute the CSRF token
  509. */
  510. public function addCSRFProtection($secret)
  511. {
  512. if (false === $secret || (is_null($secret) && !self::$CSRFProtection))
  513. {
  514. return;
  515. }
  516. if (is_null($secret))
  517. {
  518. if (is_null(self::$CSRFSecret))
  519. {
  520. self::$CSRFSecret = md5(__FILE__.php_uname());
  521. }
  522. $secret = self::$CSRFSecret;
  523. }
  524. $token = $this->getCSRFToken($secret);
  525. $this->validatorSchema[self::$CSRFFieldName] = new sfValidatorCSRFToken(array('token' => $token));
  526. $this->widgetSchema[self::$CSRFFieldName] = new sfWidgetFormInputHidden();
  527. $this->setDefault(self::$CSRFFieldName, $token);
  528. }
  529. /**
  530. * Returns a CSRF token, given a secret.
  531. *
  532. * If you want to change the algorithm used to compute the token, you
  533. * can override this method.
  534. *
  535. * @param string $secret The secret string to use
  536. *
  537. * @return string A token string
  538. */
  539. public function getCSRFToken($secret)
  540. {
  541. return md5($secret.session_id().get_class($this));
  542. }
  543. /**
  544. * @return true if this form is CSRF protected
  545. */
  546. public function isCSRFProtected()
  547. {
  548. return !is_null($this->validatorSchema[self::$CSRFFieldName]);
  549. }
  550. /**
  551. * Sets the CSRF field name.
  552. *
  553. * @param string $name The CSRF field name
  554. */
  555. static public function setCSRFFieldName($name)
  556. {
  557. self::$CSRFFieldName = $name;
  558. }
  559. /**
  560. * Gets the CSRF field name.
  561. *
  562. * @return string The CSRF field name
  563. */
  564. static public function getCSRFFieldName()
  565. {
  566. return self::$CSRFFieldName;
  567. }
  568. /**
  569. * Enables CSRF protection for all forms.
  570. *
  571. * The given secret will be used for all forms, except if you pass a secret in the constructor.
  572. * Even if a secret is automatically generated if you don't provide a secret, you're strongly advised
  573. * to provide one by yourself.
  574. *
  575. * @param string $secret A secret to use when computing the CSRF token
  576. */
  577. static public function enableCSRFProtection($secret = null)
  578. {
  579. if (false === $secret)
  580. {
  581. return self::disableCSRFProtection();
  582. }
  583. self::$CSRFProtection = true;
  584. if (!is_null($secret))
  585. {
  586. self::$CSRFSecret = $secret;
  587. }
  588. }
  589. /**
  590. * Disables CSRF protection for all forms.
  591. */
  592. static public function disableCSRFProtection()
  593. {
  594. self::$CSRFProtection = false;
  595. }
  596. /**
  597. * Returns true if the form is multipart.
  598. *
  599. * @return Boolean true if the form is multipart
  600. */
  601. public function isMultipart()
  602. {
  603. return $this->widgetSchema->needsMultipartForm();
  604. }
  605. public function resetFormFields()
  606. {
  607. $this->formFields = array();
  608. $this->formFieldSchema = null;
  609. }
  610. /**
  611. * Returns true if the bound field exists (implements the ArrayAccess interface).
  612. *
  613. * @param string $name The name of the bound field
  614. *
  615. * @return Boolean true if the widget exists, false otherwise
  616. */
  617. public function offsetExists($name)
  618. {
  619. return isset($this->widgetSchema[$name]);
  620. }
  621. /**
  622. * Returns the form field associated with the name (implements the ArrayAccess interface).
  623. *
  624. * @param string $name The offset of the value to get
  625. *
  626. * @return sfFormField A form field instance
  627. */
  628. public function offsetGet($name)
  629. {
  630. if (!isset($this->formFields[$name]))
  631. {
  632. if (!$widget = $this->widgetSchema[$name])
  633. {
  634. throw new InvalidArgumentException(sprintf('Widget "%s" does not exist.', $name));
  635. }
  636. $values = $this->isBound ? $this->taintedValues : $this->defaults;
  637. $class = $widget instanceof sfWidgetFormSchema ? 'sfFormFieldSchema' : 'sfFormField';
  638. $this->formFields[$name] = new $class($widget, $this->getFormFieldSchema(), $name, isset($values[$name]) ? $values[$name] : null, $this->errorSchema[$name]);
  639. }
  640. return $this->formFields[$name];
  641. }
  642. /**
  643. * Throws an exception saying that values cannot be set (implements the ArrayAccess interface).
  644. *
  645. * @param string $offset (ignored)
  646. * @param string $value (ignored)
  647. *
  648. * @throws <b>LogicException</b>
  649. */
  650. public function offsetSet($offset, $value)
  651. {
  652. throw new LogicException('Cannot update form fields.');
  653. }
  654. /**
  655. * Removes a field from the form.
  656. *
  657. * It removes the widget, validator and any values stored for the given field.
  658. *
  659. * @param string $offset The field name
  660. */
  661. public function offsetUnset($offset)
  662. {
  663. unset(
  664. $this->widgetSchema[$offset],
  665. $this->validatorSchema[$offset],
  666. $this->defaults[$offset],
  667. $this->taintedValues[$offset],
  668. $this->values[$offset]
  669. );
  670. $this->resetFormFields();
  671. }
  672. /**
  673. * Returns a form field for the main widget schema.
  674. *
  675. * @return sfFormFieldSchema A sfFormFieldSchema instance
  676. */
  677. public function getFormFieldSchema()
  678. {
  679. if (is_null($this->formFieldSchema))
  680. {
  681. $this->formFieldSchema = new sfFormFieldSchema($this->widgetSchema, null, null, $this->isBound ? $this->taintedValues : $this->defaults, $this->errorSchema);
  682. }
  683. return $this->formFieldSchema;
  684. }
  685. /**
  686. * Converts uploaded file array to a format following the $_GET and $POST naming convention.
  687. *
  688. * It's safe to pass an already converted array, in which case this method just returns the original array unmodified.
  689. *
  690. * @param array $taintedFiles An array representing uploaded file information
  691. *
  692. * @return array An array of re-ordered uploaded file information
  693. */
  694. static public function convertFileInformation(array $taintedFiles)
  695. {
  696. $files = array();
  697. foreach ($taintedFiles as $key => $data)
  698. {
  699. $files[$key] = self::fixPhpFilesArray($data);
  700. }
  701. return $files;
  702. }
  703. static protected function fixPhpFilesArray($data)
  704. {
  705. $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type');
  706. $keys = array_keys($data);
  707. sort($keys);
  708. if ($fileKeys != $keys || !isset($data['name']) || !is_array($data['name']))
  709. {
  710. return $data;
  711. }
  712. $files = $data;
  713. foreach ($fileKeys as $k)
  714. {
  715. unset($files[$k]);
  716. }
  717. foreach (array_keys($data['name']) as $key)
  718. {
  719. $files[$key] = self::fixPhpFilesArray(array(
  720. 'error' => $data['error'][$key],
  721. 'name' => $data['name'][$key],
  722. 'type' => $data['type'][$key],
  723. 'tmp_name' => $data['tmp_name'][$key],
  724. 'size' => $data['size'][$key],
  725. ));
  726. }
  727. return $files;
  728. }
  729. /**
  730. * Returns true if a form thrown an exception in the __toString() method
  731. *
  732. * This is a hack needed because PHP does not allow to throw exceptions in __toString() magic method.
  733. *
  734. * @return boolean
  735. */
  736. static public function hasToStringException()
  737. {
  738. return !is_null(self::$toStringException);
  739. }
  740. /**
  741. * Gets the exception if one was thrown in the __toString() method.
  742. *
  743. * This is a hack needed because PHP does not allow to throw exceptions in __toString() magic method.
  744. *
  745. * @return Exception
  746. */
  747. static public function getToStringException()
  748. {
  749. return self::$toStringException;
  750. }
  751. /**
  752. * Sets an exception thrown by the __toString() method.
  753. *
  754. * This is a hack needed because PHP does not allow to throw exceptions in __toString() magic method.
  755. *
  756. * @param Exception $e The exception thrown by __toString()
  757. */
  758. static public function setToStringException(Exception $e)
  759. {
  760. if (is_null(self::$toStringException))
  761. {
  762. self::$toStringException = $e;
  763. }
  764. }
  765. public function __clone()
  766. {
  767. $this->widgetSchema = clone $this->widgetSchema;
  768. $this->validatorSchema = clone $this->validatorSchema;
  769. // we rebind the cloned form because Exceptions are not clonable
  770. if ($this->isBound())
  771. {
  772. $this->bind($this->taintedValues, $this->taintedFiles);
  773. }
  774. }
  775. /**
  776. * Merges two arrays without reindexing numeric keys.
  777. *
  778. * @param array $array1 An array to merge
  779. * @param array $array2 An array to merge
  780. *
  781. * @return array The merged array
  782. */
  783. static protected function deepArrayUnion($array1, $array2)
  784. {
  785. foreach ($array2 as $key => $value)
  786. {
  787. if (is_array($value) && isset($array1[$key]) && is_array($array1[$key]))
  788. {
  789. $array1[$key] = self::deepArrayUnion($array1[$key], $value);
  790. }
  791. else
  792. {
  793. $array1[$key] = $value;
  794. }
  795. }
  796. return $array1;
  797. }
  798. }