Development

/branches/1.4/lib/form/sfForm.class.php

You must first sign up to be able to contribute.

root/branches/1.4/lib/form/sfForm.class.php

Revision 33598, 33.8 kB (checked in by fabien, 1 year ago)

[1.4] fixed the possibility to fake a file upload

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