1. sfForm.class.php
  2. /** * sfForm represents a form. * * A form is composed of a validator schema and a widget form schema. * * sfForm also takes care of CSRF protection by default. * * A CSRF secret can be any random string. If set to false, it disables the * CSRF protection, and if set to null, it forces the form to use the global * CSRF secret. If the global CSRF secret is also null, then a random one * is generated on the fly. * * @package symfony * @subpackage form * @author Fabien Potencier * @version SVN: $Id: sfForm.class.php 24278 2009-11-23 15:21:09Z Kris.Wallsmith $ */
  3. class sfForm implements ArrayAccess, Iterator, Countable
  4. {
  5. protected static
  6. $CSRFSecret = false,
  7. $CSRFFieldName = '_csrf_token',
  8. $toStringException = null;
  9. protected
  10. $widgetSchema = null,
  11. $validatorSchema = null,
  12. $errorSchema = null,
  13. $formFieldSchema = null,
  14. $formFields = array(),
  15. $isBound = false,
  16. $taintedValues = array(),
  17. $taintedFiles = array(),
  18. $values = null,
  19. $defaults = array(),
  20. $fieldNames = array(),
  21. $options = array(),
  22. $count = 0,
  23. $localCSRFSecret = null,
  24. $embeddedForms = array();
  25. /**
  26. * Constructor.
  27. *
  28. * @param array $defaults An array of field default values
  29. * @param array $options An array of options
  30. * @param string $CSRFSecret A CSRF secret
  31. */
  32. public function __construct($defaults = array(), $options = array(), $CSRFSecret = null)
  33. {
  34. $this->setDefaults($defaults);
  35. $this->options = $options;
  36. $this->localCSRFSecret = $CSRFSecret;
  37. $this->validatorSchema = new sfValidatorSchema();
  38. $this->widgetSchema = new sfWidgetFormSchema();
  39. $this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);
  40. $this->setup();
  41. $this->configure();
  42. $this->addCSRFProtection($this->localCSRFSecret);
  43. $this->resetFormFields();
  44. }
  45. /**
  46. * Returns a string representation of the form.
  47. *
  48. * @return string A string representation of the form
  49. *
  50. * @see render()
  51. */
  52. public function __toString()
  53. {
  54. try
  55. {
  56. return $this->render();
  57. }
  58. catch (Exception $e)
  59. {
  60. self::setToStringException($e);
  61. // we return a simple Exception message in case the form framework is used out of symfony.
  62. return 'Exception: '.$e->getMessage();
  63. }
  64. }
  65. /**
  66. * Configures the current form.
  67. */
  68. public function configure()
  69. {
  70. }
  71. /**
  72. * Setups the current form.
  73. *
  74. * This method is overridden by generator.
  75. *
  76. * If you want to do something at initialization, you have to override the configure() method.
  77. *
  78. * @see configure()
  79. */
  80. public function setup()
  81. {
  82. }
  83. /**
  84. * Renders the widget schema associated with this form.
  85. *
  86. * @param array $attributes An array of HTML attributes
  87. *
  88. * @return string The rendered widget schema
  89. */
  90. public function render($attributes = array())
  91. {
  92. return $this->getFormFieldSchema()->render($attributes);
  93. }
  94. /**
  95. * Renders the widget schema using a specific form formatter
  96. *
  97. * @param string $formatterName The form formatter name
  98. * @param array $attributes An array of HTML attributes
  99. *
  100. * @return string The rendered widget schema
  101. */
  102. public function renderUsing($formatterName, $attributes = array())
  103. {
  104. $currentFormatterName = $this->widgetSchema->getFormFormatterName();
  105. $this->widgetSchema->setFormFormatterName($formatterName);
  106. $output = $this->render($attributes);
  107. $this->widgetSchema->setFormFormatterName($currentFormatterName);
  108. return $output;
  109. }
  110. /**
  111. * Renders hidden form fields.
  112. *
  113. * @param boolean $recursive False will prevent hidden fields from embedded forms from rendering
  114. *
  115. * @return string
  116. *
  117. * @see sfFormFieldSchema
  118. */
  119. public function renderHiddenFields($recursive = true)
  120. {
  121. return $this->getFormFieldSchema()->renderHiddenFields($recursive);
  122. }
  123. /**
  124. * Renders global errors associated with this form.
  125. *
  126. * @return string The rendered global errors
  127. */
  128. public function renderGlobalErrors()
  129. {
  130. return $this->widgetSchema->getFormFormatter()->formatErrorsForRow($this->getGlobalErrors());
  131. }
  132. /**
  133. * Returns true if the form has some global errors.
  134. *
  135. * @return Boolean true if the form has some global errors, false otherwise
  136. */
  137. public function hasGlobalErrors()
  138. {
  139. return (Boolean) count($this->getGlobalErrors());
  140. }
  141. /**
  142. * Gets the global errors associated with the form.
  143. *
  144. * @return array An array of global errors
  145. */
  146. public function getGlobalErrors()
  147. {
  148. return $this->widgetSchema->getGlobalErrors($this->getErrorSchema());
  149. }
  150. /**
  151. * Binds the form with input values.
  152. *
  153. * It triggers the validator schema validation.
  154. *
  155. * @param array $taintedValues An array of input values
  156. * @param array $taintedFiles An array of uploaded files (in the $_FILES or $_GET format)
  157. */
  158. public function bind(array $taintedValues = null, array $taintedFiles = null)
  159. {
  160. $this->taintedValues = $taintedValues;
  161. $this->taintedFiles = $taintedFiles;
  162. $this->isBound = true;
  163. $this->resetFormFields();
  164. if (null === $this->taintedValues)
  165. {
  166. $this->taintedValues = array();
  167. }
  168. if (null === $this->taintedFiles)
  169. {
  170. if ($this->isMultipart())
  171. {
  172. throw new InvalidArgumentException('This form is multipart, which means you need to supply a files array as the bind() method second argument.');
  173. }
  174. $this->taintedFiles = array();
  175. }
  176. try
  177. {
  178. $this->doBind(self::deepArrayUnion($this->taintedValues, self::convertFileInformation($this->taintedFiles)));
  179. $this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);
  180. // remove CSRF token
  181. unset($this->values[self::$CSRFFieldName]);
  182. }
  183. catch (sfValidatorErrorSchema $e)
  184. {
  185. $this->values = array();
  186. $this->errorSchema = $e;
  187. }
  188. }
  189. /**
  190. * Cleans and binds values to the current form.
  191. *
  192. * @param array $values A merged array of values and files
  193. */
  194. protected function doBind(array $values)
  195. {
  196. $this->values = $this->validatorSchema->clean($values);
  197. }
  198. /**
  199. * Returns true if the form is bound to input values.
  200. *
  201. * @return Boolean true if the form is bound to input values, false otherwise
  202. */
  203. public function isBound()
  204. {
  205. return $this->isBound;
  206. }
  207. /**
  208. * Returns the submitted tainted values.
  209. *
  210. * @return array An array of tainted values
  211. */
  212. public function getTaintedValues()
  213. {
  214. if (!$this->isBound)
  215. {
  216. return array();
  217. }
  218. return $this->taintedValues;
  219. }
  220. /**
  221. * Returns true if the form is valid.
  222. *
  223. * It returns false if the form is not bound.
  224. *
  225. * @return Boolean true if the form is valid, false otherwise
  226. */
  227. public function isValid()
  228. {
  229. if (!$this->isBound)
  230. {
  231. return false;
  232. }
  233. return 0 == count($this->errorSchema);
  234. }
  235. /**
  236. * Returns true if the form has some errors.
  237. *
  238. * It returns false if the form is not bound.
  239. *
  240. * @return Boolean true if the form has no errors, false otherwise
  241. */
  242. public function hasErrors()
  243. {
  244. if (!$this->isBound)
  245. {
  246. return false;
  247. }
  248. return count($this->errorSchema) > 0;
  249. }
  250. /**
  251. * Returns the array of cleaned values.
  252. *
  253. * If the form is not bound, it returns an empty array.
  254. *
  255. * @return array An array of cleaned values
  256. */
  257. public function getValues()
  258. {
  259. return $this->isBound ? $this->values : array();
  260. }
  261. /**
  262. * Returns a cleaned value by field name.
  263. *
  264. * If the form is not bound, it will return null.
  265. *
  266. * @param string $field The name of the value required
  267. * @return string The cleaned value
  268. */
  269. public function getValue($field)
  270. {
  271. return ($this->isBound && isset($this->values[$field])) ? $this->values[$field] : null;
  272. }
  273. /**
  274. * Returns the array name under which user data can retrieved.
  275. *
  276. * If the user data is not stored under an array, it returns null.
  277. *
  278. * @return string The name
  279. */
  280. public function getName()
  281. {
  282. if ('%s' == $nameFormat = $this->widgetSchema->getNameFormat())
  283. {
  284. return false;
  285. }
  286. return str_replace('[%s]', '', $nameFormat);
  287. }
  288. /**
  289. * Gets the error schema associated with the form.
  290. *
  291. * @return sfValidatorErrorSchema A sfValidatorErrorSchema instance
  292. */
  293. public function getErrorSchema()
  294. {
  295. return $this->errorSchema;
  296. }
  297. /**
  298. * Embeds a sfForm into the current form.
  299. *
  300. * @param string $name The field name
  301. * @param sfForm $form A sfForm instance
  302. * @param string $decorator A HTML decorator for the embedded form
  303. */
  304. public function embedForm($name, sfForm $form, $decorator = null)
  305. {
  306. $name = (string) $name;
  307. if (true === $this->isBound() || true === $form->isBound())
  308. {
  309. throw new LogicException('A bound form cannot be embedded');
  310. }
  311. $this->embeddedForms[$name] = $form;
  312. $form = clone $form;
  313. unset($form[self::$CSRFFieldName]);
  314. $widgetSchema = $form->getWidgetSchema();
  315. $this->setDefault($name, $form->getDefaults());
  316. $decorator = null === $decorator ? $widgetSchema->getFormFormatter()->getDecoratorFormat() : $decorator;
  317. $this->widgetSchema[$name] = new sfWidgetFormSchemaDecorator($widgetSchema, $decorator);
  318. $this->validatorSchema[$name] = $form->getValidatorSchema();
  319. $this->resetFormFields();
  320. }
  321. /**
  322. * Embeds a sfForm into the current form n times.
  323. *
  324. * @param string $name The field name
  325. * @param sfForm $form A sfForm instance
  326. * @param integer $n The number of times to embed the form
  327. * @param string $decorator A HTML decorator for the main form around embedded forms
  328. * @param string $innerDecorator A HTML decorator for each embedded form
  329. * @param array $options Options for schema
  330. * @param array $attributes Attributes for schema
  331. * @param array $labels Labels for schema
  332. */
  333. public function embedFormForEach($name, sfForm $form, $n, $decorator = null, $innerDecorator = null, $options = array(), $attributes = array(), $labels = array())
  334. {
  335. if (true === $this->isBound() || true === $form->isBound())
  336. {
  337. throw new LogicException('A bound form cannot be embedded');
  338. }
  339. $this->embeddedForms[$name] = new sfForm();
  340. $form = clone $form;
  341. unset($form[self::$CSRFFieldName]);
  342. $widgetSchema = $form->getWidgetSchema();
  343. // generate default values
  344. $defaults = array();
  345. for ($i = 0; $i < $n; $i++)
  346. {
  347. $defaults[$i] = $form->getDefaults();
  348. $this->embeddedForms[$name]->embedForm($i, $form);
  349. }
  350. $this->setDefault($name, $defaults);
  351. $decorator = null === $decorator ? $widgetSchema->getFormFormatter()->getDecoratorFormat() : $decorator;
  352. $innerDecorator = null === $innerDecorator ? $widgetSchema->getFormFormatter()->getDecoratorFormat() : $innerDecorator;
  353. $this->widgetSchema[$name] = new sfWidgetFormSchemaDecorator(new sfWidgetFormSchemaForEach(new sfWidgetFormSchemaDecorator($widgetSchema, $innerDecorator), $n, $options, $attributes), $decorator);
  354. $this->validatorSchema[$name] = new sfValidatorSchemaForEach($form->getValidatorSchema(), $n);
  355. // generate labels
  356. for ($i = 0; $i < $n; $i++)
  357. {
  358. if (!isset($labels[$i]))
  359. {
  360. $labels[$i] = sprintf('%s (%s)', $this->widgetSchema->getFormFormatter()->generateLabelName($name), $i);
  361. }
  362. }
  363. $this->widgetSchema[$name]->setLabels($labels);
  364. $this->resetFormFields();
  365. }
  366. /**
  367. * Gets the list of embedded forms.
  368. *
  369. * @return array An array of embedded forms
  370. */
  371. public function getEmbeddedForms()
  372. {
  373. return $this->embeddedForms;
  374. }
  375. /**
  376. * Returns an embedded form.
  377. *
  378. * @param string $name The name used to embed the form
  379. *
  380. * @return sfForm
  381. *
  382. * @throws InvalidArgumentException If there is no form embedded with the supplied name
  383. */
  384. public function getEmbeddedForm($name)
  385. {
  386. if (!isset($this->embeddedForms[$name]))
  387. {
  388. throw new InvalidArgumentException(sprintf('There is no embedded "%s" form.', $name));
  389. }
  390. return $this->embeddedForms[$name];
  391. }
  392. /**
  393. * Merges current form widget and validator schemas with the ones from the
  394. * sfForm object passed as parameter. Please note it also merge defaults.
  395. *
  396. * @param sfForm $form The sfForm instance to merge with current form
  397. *
  398. * @throws LogicException If one of the form has already been bound
  399. */
  400. public function mergeForm(sfForm $form)
  401. {
  402. if (true === $this->isBound() || true === $form->isBound())
  403. {
  404. throw new LogicException('A bound form cannot be merged');
  405. }
  406. $form = clone $form;
  407. unset($form[self::$CSRFFieldName]);
  408. $this->defaults = array_merge($this->defaults, $form->getDefaults());
  409. foreach ($form->getWidgetSchema()->getPositions() as $field)
  410. {
  411. $this->widgetSchema[$field] = $form->getWidget($field);
  412. }
  413. foreach ($form->getValidatorSchema()->getFields() as $field => $validator)
  414. {
  415. $this->validatorSchema[$field] = $validator;
  416. }
  417. $this->getWidgetSchema()->setLabels(array_merge($this->getWidgetSchema()->getLabels(), $form->getWidgetSchema()->getLabels()));
  418. $this->getWidgetSchema()->setHelps(array_merge($this->getWidgetSchema()->getHelps(), $form->getWidgetSchema()->getHelps()));
  419. $this->mergePreValidator($form->getValidatorSchema()->getPreValidator());
  420. $this->mergePostValidator($form->getValidatorSchema()->getPostValidator());
  421. $this->resetFormFields();
  422. }
  423. /**
  424. * Merges a validator with the current pre validators.
  425. *
  426. * @param sfValidatorBase $validator A validator to be merged
  427. */
  428. public function mergePreValidator(sfValidatorBase $validator = null)
  429. {
  430. if (null === $validator)
  431. {
  432. return;
  433. }
  434. if (null === $this->validatorSchema->getPreValidator())
  435. {
  436. $this->validatorSchema->setPreValidator($validator);
  437. }
  438. else
  439. {
  440. $this->validatorSchema->setPreValidator(new sfValidatorAnd(array(
  441. $this->validatorSchema->getPreValidator(),
  442. $validator,
  443. )));
  444. }
  445. }
  446. /**
  447. * Merges a validator with the current post validators.
  448. *
  449. * @param sfValidatorBase $validator A validator to be merged
  450. */
  451. public function mergePostValidator(sfValidatorBase $validator = null)
  452. {
  453. if (null === $validator)
  454. {
  455. return;
  456. }
  457. if (null === $this->validatorSchema->getPostValidator())
  458. {
  459. $this->validatorSchema->setPostValidator($validator);
  460. }
  461. else
  462. {
  463. $this->validatorSchema->setPostValidator(new sfValidatorAnd(array(
  464. $this->validatorSchema->getPostValidator(),
  465. $validator,
  466. )));
  467. }
  468. }
  469. /**
  470. * Sets the validators associated with this form.
  471. *
  472. * @param array $validators An array of named validators
  473. *
  474. * @return sfForm The current form instance
  475. */
  476. public function setValidators(array $validators)
  477. {
  478. $this->setValidatorSchema(new sfValidatorSchema($validators));
  479. return $this;
  480. }
  481. /**
  482. * Set a validator for the given field name.
  483. *
  484. * @param string $name The field name
  485. * @param sfValidator $validator The validator
  486. *
  487. * @return sfForm The current form instance
  488. */
  489. public function setValidator($name, sfValidatorBase $validator)
  490. {
  491. $this->validatorSchema[$name] = $validator;
  492. $this->resetFormFields();
  493. return $this;
  494. }
  495. /**
  496. * Gets a validator for the given field name.
  497. *
  498. * @param string $name The field name
  499. *
  500. * @return sfValidator $validator The validator
  501. */
  502. public function getValidator($name)
  503. {
  504. if (!isset($this->validatorSchema[$name]))
  505. {
  506. throw new InvalidArgumentException(sprintf('The validator "%s" does not exist.', $name));
  507. }
  508. return $this->validatorSchema[$name];
  509. }
  510. /**
  511. * Sets the validator schema associated with this form.
  512. *
  513. * @param sfValidatorSchema $validatorSchema A sfValidatorSchema instance
  514. *
  515. * @return sfForm The current form instance
  516. */
  517. public function setValidatorSchema(sfValidatorSchema $validatorSchema)
  518. {
  519. $this->validatorSchema = $validatorSchema;
  520. $this->resetFormFields();
  521. return $this;
  522. }
  523. /**
  524. * Gets the validator schema associated with this form.
  525. *
  526. * @return sfValidatorSchema A sfValidatorSchema instance
  527. */
  528. public function getValidatorSchema()
  529. {
  530. return $this->validatorSchema;
  531. }
  532. /**
  533. * Sets the widgets associated with this form.
  534. *
  535. * @param array $widgets An array of named widgets
  536. *
  537. * @return sfForm The current form instance
  538. */
  539. public function setWidgets(array $widgets)
  540. {
  541. $this->setWidgetSchema(new sfWidgetFormSchema($widgets));
  542. return $this;
  543. }
  544. /**
  545. * Set a widget for the given field name.
  546. *
  547. * @param string $name The field name
  548. * @param sfWidgetForm $widget The widget
  549. *
  550. * @return sfForm The current form instance
  551. */
  552. public function setWidget($name, sfWidgetForm $widget)
  553. {
  554. $this->widgetSchema[$name] = $widget;
  555. $this->resetFormFields();
  556. return $this;
  557. }
  558. /**
  559. * Gets a widget for the given field name.
  560. *
  561. * @param string $name The field name
  562. *
  563. * @return sfWidgetForm $widget The widget
  564. */
  565. public function getWidget($name)
  566. {
  567. if (!isset($this->widgetSchema[$name]))
  568. {
  569. throw new InvalidArgumentException(sprintf('The widget "%s" does not exist.', $name));
  570. }
  571. return $this->widgetSchema[$name];
  572. }
  573. /**
  574. * Sets the widget schema associated with this form.
  575. *
  576. * @param sfWidgetFormSchema $widgetSchema A sfWidgetFormSchema instance
  577. *
  578. * @return sfForm The current form instance
  579. */
  580. public function setWidgetSchema(sfWidgetFormSchema $widgetSchema)
  581. {
  582. $this->widgetSchema = $widgetSchema;
  583. $this->resetFormFields();
  584. return $this;
  585. }
  586. /**
  587. * Gets the widget schema associated with this form.
  588. *
  589. * @return sfWidgetFormSchema A sfWidgetFormSchema instance
  590. */
  591. public function getWidgetSchema()
  592. {
  593. return $this->widgetSchema;
  594. }
  595. /**
  596. * Gets the stylesheet paths associated with the form.
  597. *
  598. * @return array An array of stylesheet paths
  599. */
  600. public function getStylesheets()
  601. {
  602. return $this->widgetSchema->getStylesheets();
  603. }
  604. /**
  605. * Gets the JavaScript paths associated with the form.
  606. *
  607. * @return array An array of JavaScript paths
  608. */
  609. public function getJavaScripts()
  610. {
  611. return $this->widgetSchema->getJavaScripts();
  612. }
  613. /**
  614. * Returns the current form's options.
  615. *
  616. * @return array The current form's options
  617. */
  618. public function getOptions()
  619. {
  620. return $this->options;
  621. }
  622. /**
  623. * Sets an option value.
  624. *
  625. * @param string $name The option name
  626. * @param mixed $value The default value
  627. *
  628. * @return sfForm The current form instance
  629. */
  630. public function setOption($name, $value)
  631. {
  632. $this->options[$name] = $value;
  633. return $this;
  634. }
  635. /**
  636. * Gets an option value.
  637. *
  638. * @param string $name The option name
  639. * @param mixed $default The default value (null by default)
  640. *
  641. * @param mixed The default value
  642. */
  643. public function getOption($name, $default = null)
  644. {
  645. return isset($this->options[$name]) ? $this->options[$name] : $default;
  646. }
  647. /**
  648. * Sets a default value for a form field.
  649. *
  650. * @param string $name The field name
  651. * @param mixed $default The default value
  652. *
  653. * @return sfForm The current form instance
  654. */
  655. public function setDefault($name, $default)
  656. {
  657. $this->defaults[$name] = $default;
  658. $this->resetFormFields();
  659. return $this;
  660. }
  661. /**
  662. * Gets a default value for a form field.
  663. *
  664. * @param string $name The field name
  665. *
  666. * @param mixed The default value
  667. */
  668. public function getDefault($name)
  669. {
  670. return isset($this->defaults[$name]) ? $this->defaults[$name] : null;
  671. }
  672. /**
  673. * Returns true if the form has a default value for a form field.
  674. *
  675. * @param string $name The field name
  676. *
  677. * @param Boolean true if the form has a default value for this field, false otherwise
  678. */
  679. public function hasDefault($name)
  680. {
  681. return array_key_exists($name, $this->defaults);
  682. }
  683. /**
  684. * Sets the default values for the form.
  685. *
  686. * The default values are only used if the form is not bound.
  687. *
  688. * @param array $defaults An array of default values
  689. *
  690. * @return sfForm The current form instance
  691. */
  692. public function setDefaults($defaults)
  693. {
  694. $this->defaults = null === $defaults ? array() : $defaults;
  695. if ($this->isCSRFProtected())
  696. {
  697. $this->setDefault(self::$CSRFFieldName, $this->getCSRFToken(self::$CSRFSecret));
  698. }
  699. $this->resetFormFields();
  700. return $this;
  701. }
  702. /**
  703. * Gets the default values for the form.
  704. *
  705. * @return array An array of default values
  706. */
  707. public function getDefaults()
  708. {
  709. return $this->defaults;
  710. }
  711. /**
  712. * Adds CSRF protection to the current form.
  713. *
  714. * @param string $secret The secret to use to compute the CSRF token
  715. *
  716. * @return sfForm The current form instance
  717. */
  718. public function addCSRFProtection($secret = null)
  719. {
  720. if (null === $secret)
  721. {
  722. $secret = $this->localCSRFSecret;
  723. }
  724. if (false === $secret || (null === $secret && false === self::$CSRFSecret))
  725. {
  726. return $this;
  727. }
  728. if (null === $secret)
  729. {
  730. if (null === self::$CSRFSecret)
  731. {
  732. self::$CSRFSecret = md5(__FILE__.php_uname());
  733. }
  734. $secret = self::$CSRFSecret;
  735. }
  736. $token = $this->getCSRFToken($secret);
  737. $this->validatorSchema[self::$CSRFFieldName] = new sfValidatorCSRFToken(array('token' => $token));
  738. $this->widgetSchema[self::$CSRFFieldName] = new sfWidgetFormInputHidden();
  739. $this->setDefault(self::$CSRFFieldName, $token);
  740. return $this;
  741. }
  742. /**
  743. * Returns a CSRF token, given a secret.
  744. *
  745. * If you want to change the algorithm used to compute the token, you
  746. * can override this method.
  747. *
  748. * @param string $secret The secret string to use (null to use the current secret)
  749. *
  750. * @return string A token string
  751. */
  752. public function getCSRFToken($secret = null)
  753. {
  754. if (null === $secret)
  755. {
  756. $secret = self::$CSRFSecret;
  757. }
  758. return md5($secret.session_id().get_class($this));
  759. }
  760. /**
  761. * @return true if this form is CSRF protected
  762. */
  763. public function isCSRFProtected()
  764. {
  765. return null !== $this->validatorSchema[self::$CSRFFieldName];
  766. }
  767. /**
  768. * Sets the CSRF field name.
  769. *
  770. * @param string $name The CSRF field name
  771. */
  772. static public function setCSRFFieldName($name)
  773. {
  774. self::$CSRFFieldName = $name;
  775. }
  776. /**
  777. * Gets the CSRF field name.
  778. *
  779. * @return string The CSRF field name
  780. */
  781. static public function getCSRFFieldName()
  782. {
  783. return self::$CSRFFieldName;
  784. }
  785. /**
  786. * Enables CSRF protection for this form.
  787. *
  788. * @param string $secret A secret to use when computing the CSRF token
  789. */
  790. public function enableLocalCSRFProtection($secret = null)
  791. {
  792. $this->localCSRFSecret = $secret;
  793. }
  794. /**
  795. * Disables CSRF protection for this form.
  796. */
  797. public function disableLocalCSRFProtection()
  798. {
  799. $this->localCSRFSecret = false;
  800. }
  801. /**
  802. * Enables CSRF protection for all forms.
  803. *
  804. * The given secret will be used for all forms, except if you pass a secret in the constructor.
  805. * Even if a secret is automatically generated if you don't provide a secret, you're strongly advised
  806. * to provide one by yourself.
  807. *
  808. * @param string $secret A secret to use when computing the CSRF token
  809. */
  810. static public function enableCSRFProtection($secret = null)
  811. {
  812. self::$CSRFSecret = $secret;
  813. }
  814. /**
  815. * Disables CSRF protection for all forms.
  816. */
  817. static public function disableCSRFProtection()
  818. {
  819. self::$CSRFSecret = false;
  820. }
  821. /**
  822. * Returns true if the form is multipart.
  823. *
  824. * @return Boolean true if the form is multipart
  825. */
  826. public function isMultipart()
  827. {
  828. return $this->widgetSchema->needsMultipartForm();
  829. }
  830. /**
  831. * Renders the form tag.
  832. *
  833. * This methods only renders the opening form tag.
  834. * You need to close it after the form rendering.
  835. *
  836. * This method takes into account the multipart widgets
  837. * and converts PUT and DELETE methods to a hidden field
  838. * for later processing.
  839. *
  840. * @param string $url The URL for the action
  841. * @param array $attributes An array of HTML attributes
  842. *
  843. * @return string An HTML representation of the opening form tag
  844. */
  845. public function renderFormTag($url, array $attributes = array())
  846. {
  847. $attributes['action'] = $url;
  848. $attributes['method'] = isset($attributes['method']) ? strtolower($attributes['method']) : 'post';
  849. if ($this->isMultipart())
  850. {
  851. $attributes['enctype'] = 'multipart/form-data';
  852. }
  853. $html = '';
  854. if (!in_array($attributes['method'], array('get', 'post')))
  855. {
  856. $html = $this->getWidgetSchema()->renderTag('input', array('type' => 'hidden', 'name' => 'sf_method', 'value' => $attributes['method'], 'id' => false));
  857. $attributes['method'] = 'post';
  858. }
  859. return sprintf('<form%s>', $this->getWidgetSchema()->attributesToHtml($attributes)).$html;
  860. }
  861. public function resetFormFields()
  862. {
  863. $this->formFields = array();
  864. $this->formFieldSchema = null;
  865. }
  866. /**
  867. * Returns true if the bound field exists (implements the ArrayAccess interface).
  868. *
  869. * @param string $name The name of the bound field
  870. *
  871. * @return Boolean true if the widget exists, false otherwise
  872. */
  873. public function offsetExists($name)
  874. {
  875. return isset($this->widgetSchema[$name]);
  876. }
  877. /**
  878. * Returns the form field associated with the name (implements the ArrayAccess interface).
  879. *
  880. * @param string $name The offset of the value to get
  881. *
  882. * @return sfFormField A form field instance
  883. */
  884. public function offsetGet($name)
  885. {
  886. if (!isset($this->formFields[$name]))
  887. {
  888. if (!$widget = $this->widgetSchema[$name])
  889. {
  890. throw new InvalidArgumentException(sprintf('Widget "%s" does not exist.', $name));
  891. }
  892. if ($this->isBound)
  893. {
  894. $value = isset($this->taintedValues[$name]) ? $this->taintedValues[$name] : null;
  895. }
  896. else if (isset($this->defaults[$name]))
  897. {
  898. $value = $this->defaults[$name];
  899. }
  900. else
  901. {
  902. $value = $widget instanceof sfWidgetFormSchema ? $widget->getDefaults() : $widget->getDefault();
  903. }
  904. $class = $widget instanceof sfWidgetFormSchema ? 'sfFormFieldSchema' : 'sfFormField';
  905. $this->formFields[$name] = new $class($widget, $this->getFormFieldSchema(), $name, $value, $this->errorSchema[$name]);
  906. }
  907. return $this->formFields[$name];
  908. }
  909. /**
  910. * Throws an exception saying that values cannot be set (implements the ArrayAccess interface).
  911. *
  912. * @param string $offset (ignored)
  913. * @param string $value (ignored)
  914. *
  915. * @throws <b>LogicException</b>
  916. */
  917. public function offsetSet($offset, $value)
  918. {
  919. throw new LogicException('Cannot update form fields.');
  920. }
  921. /**
  922. * Removes a field from the form.
  923. *
  924. * It removes the widget and the validator for the given field.
  925. *
  926. * @param string $offset The field name
  927. */
  928. public function offsetUnset($offset)
  929. {
  930. unset(
  931. $this->widgetSchema[$offset],
  932. $this->validatorSchema[$offset],
  933. $this->defaults[$offset],
  934. $this->taintedValues[$offset],
  935. $this->values[$offset],
  936. $this->embeddedForms[$offset]
  937. );
  938. $this->resetFormFields();
  939. }
  940. /**
  941. * Removes all visible fields from the form except the ones given as an argument.
  942. *
  943. * Hidden fields are not affected.
  944. *
  945. * @param array $fields An array of field names
  946. * @param Boolean $ordered Whether to use the array of field names to reorder the fields
  947. */
  948. public function useFields(array $fields = array(), $ordered = true)
  949. {
  950. $hidden = array();
  951. foreach ($this as $name => $field)
  952. {
  953. if ($field->isHidden())
  954. {
  955. $hidden[] = $name;
  956. }
  957. else if (!in_array($name, $fields))
  958. {
  959. unset($this[$name]);
  960. }
  961. }
  962. if ($ordered)
  963. {
  964. $this->widgetSchema->setPositions(array_merge($fields, $hidden));
  965. }
  966. }
  967. /**
  968. * Returns a form field for the main widget schema.
  969. *
  970. * @return sfFormFieldSchema A sfFormFieldSchema instance
  971. */
  972. public function getFormFieldSchema()
  973. {
  974. if (null === $this->formFieldSchema)
  975. {
  976. $values = $this->isBound ? $this->taintedValues : array_merge($this->widgetSchema->getDefaults(), $this->defaults);
  977. $this->formFieldSchema = new sfFormFieldSchema($this->widgetSchema, null, null, $values, $this->errorSchema);
  978. }
  979. return $this->formFieldSchema;
  980. }
  981. /**
  982. * Resets the field names array to the beginning (implements the Iterator interface).
  983. */
  984. public function rewind()
  985. {
  986. $this->fieldNames = $this->widgetSchema->getPositions();
  987. reset($this->fieldNames);
  988. $this->count = count($this->fieldNames);
  989. }
  990. /**
  991. * Gets the key associated with the current form field (implements the Iterator interface).
  992. *
  993. * @return string The key
  994. */
  995. public function key()
  996. {
  997. return current($this->fieldNames);
  998. }
  999. /**
  1000. * Returns the current form field (implements the Iterator interface).
  1001. *
  1002. * @return mixed The escaped value
  1003. */
  1004. public function current()
  1005. {
  1006. return $this[current($this->fieldNames)];
  1007. }
  1008. /**
  1009. * Moves to the next form field (implements the Iterator interface).
  1010. */
  1011. public function next()
  1012. {
  1013. next($this->fieldNames);
  1014. --$this->count;
  1015. }
  1016. /**
  1017. * Returns true if the current form field is valid (implements the Iterator interface).
  1018. *
  1019. * @return boolean The validity of the current element; true if it is valid
  1020. */
  1021. public function valid()
  1022. {
  1023. return $this->count > 0;
  1024. }
  1025. /**
  1026. * Returns the number of form fields (implements the Countable interface).
  1027. *
  1028. * @return integer The number of embedded form fields
  1029. */
  1030. public function count()
  1031. {
  1032. return count($this->getFormFieldSchema());
  1033. }
  1034. /**
  1035. * Converts uploaded file array to a format following the $_GET and $POST naming convention.
  1036. *
  1037. * It's safe to pass an already converted array, in which case this method just returns the original array unmodified.
  1038. *
  1039. * @param array $taintedFiles An array representing uploaded file information
  1040. *
  1041. * @return array An array of re-ordered uploaded file information
  1042. */
  1043. static public function convertFileInformation(array $taintedFiles)
  1044. {
  1045. $files = array();
  1046. foreach ($taintedFiles as $key => $data)
  1047. {
  1048. $files[$key] = self::fixPhpFilesArray($data);
  1049. }
  1050. return $files;
  1051. }
  1052. static protected function fixPhpFilesArray($data)
  1053. {
  1054. $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type');
  1055. $keys = array_keys($data);
  1056. sort($keys);
  1057. if ($fileKeys != $keys || !isset($data['name']) || !is_array($data['name']))
  1058. {
  1059. return $data;
  1060. }
  1061. $files = $data;
  1062. foreach ($fileKeys as $k)
  1063. {
  1064. unset($files[$k]);
  1065. }
  1066. foreach (array_keys($data['name']) as $key)
  1067. {
  1068. $files[$key] = self::fixPhpFilesArray(array(
  1069. 'error' => $data['error'][$key],
  1070. 'name' => $data['name'][$key],
  1071. 'type' => $data['type'][$key],
  1072. 'tmp_name' => $data['tmp_name'][$key],
  1073. 'size' => $data['size'][$key],
  1074. ));
  1075. }
  1076. return $files;
  1077. }
  1078. /**
  1079. * Returns true if a form thrown an exception in the __toString() method
  1080. *
  1081. * This is a hack needed because PHP does not allow to throw exceptions in __toString() magic method.
  1082. *
  1083. * @return boolean
  1084. */
  1085. static public function hasToStringException()
  1086. {
  1087. return null !== self::$toStringException;
  1088. }
  1089. /**
  1090. * Gets the exception if one was thrown in the __toString() method.
  1091. *
  1092. * This is a hack needed because PHP does not allow to throw exceptions in __toString() magic method.
  1093. *
  1094. * @return Exception
  1095. */
  1096. static public function getToStringException()
  1097. {
  1098. return self::$toStringException;
  1099. }
  1100. /**
  1101. * Sets an exception thrown by the __toString() method.
  1102. *
  1103. * This is a hack needed because PHP does not allow to throw exceptions in __toString() magic method.
  1104. *
  1105. * @param Exception $e The exception thrown by __toString()
  1106. */
  1107. static public function setToStringException(Exception $e)
  1108. {
  1109. if (null === self::$toStringException)
  1110. {
  1111. self::$toStringException = $e;
  1112. }
  1113. }
  1114. public function __clone()
  1115. {
  1116. $this->widgetSchema = clone $this->widgetSchema;
  1117. $this->validatorSchema = clone $this->validatorSchema;
  1118. // we rebind the cloned form because Exceptions are not clonable
  1119. if ($this->isBound())
  1120. {
  1121. $this->bind($this->taintedValues, $this->taintedFiles);
  1122. }
  1123. }
  1124. /**
  1125. * Merges two arrays without reindexing numeric keys.
  1126. *
  1127. * @param array $array1 An array to merge
  1128. * @param array $array2 An array to merge
  1129. *
  1130. * @return array The merged array
  1131. */
  1132. static protected function deepArrayUnion($array1, $array2)
  1133. {
  1134. foreach ($array2 as $key => $value)
  1135. {
  1136. if (is_array($value) && isset($array1[$key]) && is_array($array1[$key]))
  1137. {
  1138. $array1[$key] = self::deepArrayUnion($array1[$key], $value);
  1139. }
  1140. else
  1141. {
  1142. $array1[$key] = $value;
  1143. }
  1144. }
  1145. return $array1;
  1146. }
  1147. }

Debug toolbar