Development

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

You must first sign up to be able to contribute.

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

Revision 29678, 33.3 kB (checked in by Kris.Wallsmith, 4 years ago)

[1.3, 1.4] made sfForm::getName() more strict (closes #8318)

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