PageRenderTime 59ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Symfony/Component/Form/Form.php

https://github.com/thewiredman/symfony
PHP | 457 lines | 214 code | 51 blank | 192 comment | 25 complexity | 6a125421b9e1986a54097287b7bde5da MD5 | raw file
  1. <?php
  2. namespace Symfony\Component\Form;
  3. use Symfony\Component\Validator\ValidatorInterface;
  4. use Symfony\Component\I18N\TranslatorInterface;
  5. /*
  6. * This file is part of the symfony package.
  7. * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
  8. *
  9. * For the full copyright and license information, please view the LICENSE
  10. * file that was distributed with this source code.
  11. */
  12. /**
  13. * Form represents a form.
  14. *
  15. * A form is composed of a validator schema and a widget form schema.
  16. *
  17. * Form also takes care of Csrf protection by default.
  18. *
  19. * A Csrf secret can be any random string. If set to false, it disables the
  20. * Csrf protection, and if set to null, it forces the form to use the global
  21. * Csrf secret. If the global Csrf secret is also null, then a random one
  22. * is generated on the fly.
  23. *
  24. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  25. * @version SVN: $Id: Form.php 245 2010-01-31 22:22:39Z flo $
  26. */
  27. class Form extends FieldGroup
  28. {
  29. protected static $defaultCsrfSecret = null;
  30. protected static $defaultCsrfProtection = false;
  31. protected static $defaultCsrfFieldName = '_token';
  32. protected static $defaultLocale = null;
  33. protected static $defaultTranslator = null;
  34. protected $validator = null;
  35. protected $validationGroups = null;
  36. private $csrfSecret = null;
  37. private $csrfFieldName = null;
  38. /**
  39. * Constructor.
  40. *
  41. * @param array $defaults An array of field default values
  42. * @param array $options An array of options
  43. * @param string $defaultCsrfSecret A Csrf secret
  44. */
  45. public function __construct($name, $object, ValidatorInterface $validator, array $options = array())
  46. {
  47. $this->generator = new HtmlGenerator();
  48. $this->validator = $validator;
  49. $this->setData($object);
  50. $this->setCsrfFieldName(self::$defaultCsrfFieldName);
  51. if (self::$defaultCsrfSecret !== null) {
  52. $this->setCsrfSecret(self::$defaultCsrfSecret);
  53. } else {
  54. $this->setCsrfSecret(md5(__FILE__.php_uname()));
  55. }
  56. if (self::$defaultCsrfProtection !== false) {
  57. $this->enableCsrfProtection();
  58. }
  59. if (self::$defaultLocale !== null) {
  60. $this->setLocale(self::$defaultLocale);
  61. }
  62. if (self::$defaultTranslator !== null) {
  63. $this->setTranslator(self::$defaultTranslator);
  64. }
  65. parent::__construct($name, $options);
  66. }
  67. /**
  68. * Sets the charset used for rendering HTML
  69. *
  70. * This method overrides the internal HTML generator! If you want to use
  71. * your own generator, use setGenerator() instead.
  72. *
  73. * @param string $charset
  74. */
  75. public function setCharset($charset)
  76. {
  77. $this->setGenerator(new HtmlGenerator($charset));
  78. }
  79. /**
  80. * Sets the validation groups for this form.
  81. *
  82. * @param array|string $validationGroups
  83. */
  84. public function setValidationGroups($validationGroups)
  85. {
  86. $this->validationGroups = $validationGroups === null ? $validationGroups : (array) $validationGroups;
  87. }
  88. /**
  89. * Returns the validation groups for this form.
  90. *
  91. * @return array
  92. */
  93. public function getValidationGroups()
  94. {
  95. return $this->validationGroups;
  96. }
  97. /**
  98. * Sets the default locale for newly created forms.
  99. *
  100. * @param string $defaultLocale
  101. */
  102. static public function setDefaultLocale($defaultLocale)
  103. {
  104. self::$defaultLocale = $defaultLocale;
  105. }
  106. /**
  107. * Returns the default locale for newly created forms.
  108. *
  109. * @return string
  110. */
  111. static public function getDefaultLocale()
  112. {
  113. return self::$defaultLocale;
  114. }
  115. /**
  116. * Sets the default translator for newly created forms.
  117. *
  118. * @param TranslatorInterface $defaultTranslator
  119. */
  120. static public function setDefaultTranslator(TranslatorInterface $defaultTranslator)
  121. {
  122. self::$defaultTranslator = $defaultTranslator;
  123. }
  124. /**
  125. * Returns the default translator for newly created forms.
  126. *
  127. * @return TranslatorInterface
  128. */
  129. static public function getDefaultTranslator()
  130. {
  131. return self::$defaultTranslator;
  132. }
  133. /**
  134. * Binds the form with values and files.
  135. *
  136. * This method is final because it is very easy to break a form when
  137. * overriding this method and adding logic that depends on $taintedFiles.
  138. * You should override doBind() instead where the uploaded files are
  139. * already merged into the data array.
  140. *
  141. * @param array $taintedValues The form data of the $_POST array
  142. * @param array $taintedFiles An array of uploaded files
  143. * @return boolean Whether the form is valid
  144. */
  145. final public function bind($taintedValues, array $taintedFiles = null)
  146. {
  147. if ($taintedFiles === null) {
  148. if ($this->isMultipart() && $this->getParent() === null) {
  149. throw new \InvalidArgumentException('You must provide a files array for multipart forms');
  150. }
  151. $taintedFiles = array();
  152. }
  153. if (null === $taintedValues) {
  154. $taintedValues = array();
  155. }
  156. $this->doBind(self::deepArrayUnion($taintedValues, $taintedFiles));
  157. if ($this->getParent() === null) {
  158. if ($violations = $this->validator->validate($this, $this->getValidationGroups())) {
  159. foreach ($violations as $violation) {
  160. $propertyPath = new PropertyPath($violation->getPropertyPath());
  161. if ($propertyPath->getCurrent() == 'data') {
  162. $type = self::DATA_ERROR;
  163. $propertyPath->next(); // point at the first data element
  164. } else {
  165. $type = self::FIELD_ERROR;
  166. }
  167. $this->addError($violation->getMessage(), $propertyPath, $type);
  168. }
  169. }
  170. }
  171. }
  172. /**
  173. * Binds the form with the given data.
  174. *
  175. * @param array $taintedData The data to bind to the form
  176. * @return boolean Whether the form is valid
  177. */
  178. protected function doBind(array $taintedData)
  179. {
  180. parent::bind($taintedData);
  181. }
  182. /**
  183. * Gets the stylesheet paths associated with the form.
  184. *
  185. * @return array An array of stylesheet paths
  186. */
  187. public function getStylesheets()
  188. {
  189. return $this->getWidget()->getStylesheets();
  190. }
  191. /**
  192. * Gets the JavaScript paths associated with the form.
  193. *
  194. * @return array An array of JavaScript paths
  195. */
  196. public function getJavaScripts()
  197. {
  198. return $this->getWidget()->getJavaScripts();
  199. }
  200. /**
  201. * Returns a CSRF token for the set CSRF secret
  202. *
  203. * If you want to change the algorithm used to compute the token, you
  204. * can override this method.
  205. *
  206. * @param string $secret The secret string to use (null to use the current secret)
  207. *
  208. * @return string A token string
  209. */
  210. protected function getCsrfToken()
  211. {
  212. return md5($this->csrfSecret.session_id().get_class($this));
  213. }
  214. /**
  215. * @return true if this form is CSRF protected
  216. */
  217. public function isCsrfProtected()
  218. {
  219. return $this->has($this->getCsrfFieldName());
  220. }
  221. /**
  222. * Enables CSRF protection for this form.
  223. */
  224. public function enableCsrfProtection()
  225. {
  226. if (!$this->isCsrfProtected()) {
  227. $field = new HiddenField($this->getCsrfFieldName(), array(
  228. 'property_path' => null,
  229. ));
  230. $field->setData($this->getCsrfToken());
  231. $this->add($field);
  232. }
  233. }
  234. /**
  235. * Disables CSRF protection for this form.
  236. */
  237. public function disableCsrfProtection()
  238. {
  239. if ($this->isCsrfProtected()) {
  240. $this->remove($this->getCsrfFieldName());
  241. }
  242. }
  243. /**
  244. * Sets the CSRF field name used in this form
  245. *
  246. * @param string $name The CSRF field name
  247. */
  248. public function setCsrfFieldName($name)
  249. {
  250. $this->csrfFieldName = $name;
  251. }
  252. /**
  253. * Returns the CSRF field name used in this form
  254. *
  255. * @return string The CSRF field name
  256. */
  257. public function getCsrfFieldName()
  258. {
  259. return $this->csrfFieldName;
  260. }
  261. /**
  262. * Sets the CSRF secret used in this form
  263. *
  264. * @param string $secret
  265. */
  266. public function setCsrfSecret($secret)
  267. {
  268. $this->csrfSecret = $secret;
  269. }
  270. /**
  271. * Returns the CSRF secret used in this form
  272. *
  273. * @return string
  274. */
  275. public function getCsrfSecret()
  276. {
  277. return $this->csrfSecret;
  278. }
  279. /**
  280. * Returns whether the CSRF token is valid
  281. *
  282. * @return boolean
  283. */
  284. public function isCsrfTokenValid()
  285. {
  286. if (!$this->isCsrfProtected()) {
  287. return true;
  288. } else {
  289. return $this->get($this->getCsrfFieldName())->getDisplayedData() === $this->getCsrfToken();
  290. }
  291. }
  292. /**
  293. * Enables CSRF protection for all new forms
  294. */
  295. static public function enableDefaultCsrfProtection()
  296. {
  297. self::$defaultCsrfProtection = true;
  298. }
  299. /**
  300. * Disables Csrf protection for all forms.
  301. */
  302. static public function disableDefaultCsrfProtection()
  303. {
  304. self::$defaultCsrfProtection = false;
  305. }
  306. /**
  307. * Sets the CSRF field name used in all new CSRF protected forms
  308. *
  309. * @param string $name The CSRF field name
  310. */
  311. static public function setDefaultCsrfFieldName($name)
  312. {
  313. self::$defaultCsrfFieldName = $name;
  314. }
  315. /**
  316. * Returns the default CSRF field name
  317. *
  318. * @return string The CSRF field name
  319. */
  320. static public function getDefaultCsrfFieldName()
  321. {
  322. return self::$defaultCsrfFieldName;
  323. }
  324. /**
  325. * Sets the CSRF secret used in all new CSRF protected forms
  326. *
  327. * @param string $secret
  328. */
  329. static public function setDefaultCsrfSecret($secret)
  330. {
  331. self::$defaultCsrfSecret = $secret;
  332. }
  333. /**
  334. * Returns the default CSRF secret
  335. *
  336. * @return string
  337. */
  338. static public function getDefaultCsrfSecret()
  339. {
  340. return self::$defaultCsrfSecret;
  341. }
  342. /**
  343. * Renders the form tag.
  344. *
  345. * This method only renders the opening form tag.
  346. * You need to close it after the form rendering.
  347. *
  348. * This method takes into account the multipart widgets.
  349. *
  350. * @param string $url The URL for the action
  351. * @param array $attributes An array of HTML attributes
  352. *
  353. * @return string An HTML representation of the opening form tag
  354. */
  355. public function renderFormTag($url, array $attributes = array())
  356. {
  357. return sprintf('<form%s>', $this->generator->attributes(array_merge(array(
  358. 'action' => $url,
  359. 'method' => isset($attributes['method']) ? strtolower($attributes['method']) : 'post',
  360. 'enctype' => $this->isMultipart() ? 'multipart/form-data' : null,
  361. ), $attributes)));
  362. }
  363. /**
  364. * Returns whether the maximum POST size was reached in this request.
  365. *
  366. * @return boolean
  367. */
  368. public function isPostMaxSizeReached()
  369. {
  370. if (isset($_SERVER['CONTENT_LENGTH'])) {
  371. $length = (int) $_SERVER['CONTENT_LENGTH'];
  372. $max = trim(ini_get('post_max_size'));
  373. switch (strtolower(substr($max, -1))) {
  374. // The 'G' modifier is available since PHP 5.1.0
  375. case 'g':
  376. $max *= 1024;
  377. case 'm':
  378. $max *= 1024;
  379. case 'k':
  380. $max *= 1024;
  381. }
  382. return $length > $max;
  383. } else {
  384. return false;
  385. }
  386. }
  387. /**
  388. * Merges two arrays without reindexing numeric keys.
  389. *
  390. * @param array $array1 An array to merge
  391. * @param array $array2 An array to merge
  392. *
  393. * @return array The merged array
  394. */
  395. static protected function deepArrayUnion($array1, $array2)
  396. {
  397. foreach ($array2 as $key => $value) {
  398. if (is_array($value) && isset($array1[$key]) && is_array($array1[$key])) {
  399. $array1[$key] = self::deepArrayUnion($array1[$key], $value);
  400. } else {
  401. $array1[$key] = $value;
  402. }
  403. }
  404. return $array1;
  405. }
  406. }