Development

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

You must first sign up to be able to contribute.

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

Revision 17023, 23.3 kB (checked in by fabien, 5 years ago)

[1.1, 1.2, 1.3] fixed management in sfWebRequest and sfForm (closes #4273, #5075)

  • 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 forms is composed of a validator schema and a widget form schema.
15  *
16  * sfForm also takes care of CSRF protection by default.
17  *
18  * @package    symfony
19  * @subpackage form
20  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
21  * @version    SVN: $Id$
22  */
23 class sfForm implements ArrayAccess
24 {
25   protected static
26     $CSRFProtection    = false,
27     $CSRFSecret        = null,
28     $CSRFFieldName     = '_csrf_token',
29     $toStringException = null;
30
31   protected
32     $widgetSchema    = null,
33     $validatorSchema = null,
34     $errorSchema     = null,
35     $formFieldSchema = null,
36     $formFields      = array(),
37     $isBound         = false,
38     $taintedValues   = array(),
39     $taintedFiles    = array(),
40     $values          = null,
41     $defaults        = array(),
42     $options         = array();
43
44   /**
45    * Constructor.
46    *
47    * @param array  $defaults    An array of field default values
48    * @param array  $options     An array of options
49    * @param string $CRFSSecret  A CSRF secret (false to disable CSRF protection, null to use the global CSRF secret)
50    */
51   public function __construct($defaults = array(), $options = array(), $CSRFSecret = null)
52   {
53     $this->setDefaults($defaults);
54     $this->options = $options;
55
56     $this->validatorSchema = new sfValidatorSchema();
57     $this->widgetSchema    = new sfWidgetFormSchema();
58     $this->errorSchema     = new sfValidatorErrorSchema($this->validatorSchema);
59
60     $this->setup();
61     $this->configure();
62
63     $this->addCSRFProtection($CSRFSecret);
64     $this->resetFormFields();
65   }
66
67   /**
68    * Returns a string representation of the form.
69    *
70    * @return string A string representation of the form
71    *
72    * @see render()
73    */
74   public function __toString()
75   {
76     try
77     {
78       return $this->render();
79     }
80     catch (Exception $e)
81     {
82       self::setToStringException($e);
83
84       // we return a simple Exception message in case the form framework is used out of symfony.
85       return 'Exception: '.$e->getMessage();
86     }
87   }
88
89   /**
90    * Configures the current form.
91    */
92   public function configure()
93   {
94   }
95
96   /**
97    * Setups the current form.
98    *
99    * This method is overridden by generator.
100    *
101    * If you want to do something at initialization, you have to override the configure() method.
102    *
103    * @see configure()
104    */
105   public function setup()
106   {
107   }
108
109   /**
110    * Renders the widget schema associated with this form.
111    *
112    * @param  array  $attributes  An array of HTML attributes
113    *
114    * @return string The rendered widget schema
115    */
116   public function render($attributes = array())
117   {
118     return $this->getFormFieldSchema()->render($attributes);
119   }
120
121   /**
122    * Renders global errors associated with this form.
123    *
124    * @return string The rendered global errors
125    */
126   public function renderGlobalErrors()
127   {
128     return $this->widgetSchema->getFormFormatter()->formatErrorsForRow($this->getGlobalErrors());
129   }
130
131   /**
132    * Returns true if the form has some global errors.
133    *
134    * @return Boolean true if the form has some global errors, false otherwise
135    */
136   public function hasGlobalErrors()
137   {
138     return (Boolean) count($this->getGlobalErrors());
139   }
140
141   /**
142    * Gets the global errors associated with the form.
143    *
144    * @return array An array of global errors
145    */
146   public function getGlobalErrors()
147   {
148     return $this->widgetSchema->getGlobalErrors($this->getErrorSchema());
149   }
150
151   /**
152    * Binds the form with input values.
153    *
154    * It triggers the validator schema validation.
155    *
156    * @param array $taintedValues  An array of input values
157    * @param array $taintedFiles   An array of uploaded files (in the $_FILES or $_GET format)
158    */
159   public function bind(array $taintedValues = null, array $taintedFiles = null)
160   {
161     $this->taintedValues = $taintedValues;
162     $this->taintedFiles  = $taintedFiles;
163     $this->isBound = true;
164     $this->resetFormFields();
165
166     if (is_null($this->taintedValues))
167     {
168       $this->taintedValues = array();
169     }
170
171     if (is_null($this->taintedFiles))
172     {
173       if ($this->isMultipart())
174       {
175         throw new InvalidArgumentException('This form is multipart, which means you need to supply a files array as the bind() method second argument.');
176       }
177
178       $this->taintedFiles = array();
179     }
180
181     try
182     {
183       $this->values = $this->validatorSchema->clean(self::deepArrayUnion($this->taintedValues, self::convertFileInformation($this->taintedFiles)));
184       $this->errorSchema = new sfValidatorErrorSchema($this->validatorSchema);
185
186       // remove CSRF token
187       unset($this->values[self::$CSRFFieldName]);
188     }
189     catch (sfValidatorErrorSchema $e)
190     {
191       $this->values = array();
192       $this->errorSchema = $e;
193     }
194   }
195
196   /**
197    * Returns true if the form is bound to input values.
198    *
199    * @return Boolean true if the form is bound to input values, false otherwise
200    */
201   public function isBound()
202   {
203     return $this->isBound;
204   }
205
206   /**
207    * Returns true if the form is valid.
208    *
209    * It returns false if the form is not bound.
210    *
211    * @return Boolean true if the form is valid, false otherwise
212    */
213   public function isValid()
214   {
215     if (!$this->isBound)
216     {
217       return false;
218     }
219
220     return 0 == count($this->errorSchema);
221   }
222
223   /**
224    * Returns the array of cleaned values.
225    *
226    * If the form is not bound, it returns an empty array.
227    *
228    * @return array An array of cleaned values
229    */
230   public function getValues()
231   {
232     return $this->isBound ? $this->values : array();
233   }
234
235   /**
236    * Returns a cleaned value by field name.
237    *
238    * If the form is not bound, it will return null.
239    *
240    * @param  string  $field  The name of the value required
241    * @return string  The cleaned value
242    */
243   public function getValue($field)
244   {
245     return ($this->isBound && isset($this->values[$field])) ? $this->values[$field] : null;
246   }
247
248   /**
249    * Gets the error schema associated with the form.
250    *
251    * @return sfValidatorErrorSchema A sfValidatorErrorSchema instance
252    */
253   public function getErrorSchema()
254   {
255     return $this->errorSchema;
256   }
257
258   /**
259    * Embeds a sfForm into the current form.
260    *
261    * @param string $name       The field name
262    * @param sfForm $form       A sfForm instance
263    * @param string $decorator  A HTML decorator for the embedded form
264    */
265   public function embedForm($name, sfForm $form, $decorator = null)
266   {
267     $name = (string) $name;
268     if (true === $this->isBound() || true === $form->isBound())
269     {
270       throw new LogicException('A bound form cannot be embedded');
271     }
272
273     $form = clone $form;
274     unset($form[self::$CSRFFieldName]);
275
276     $widgetSchema = $form->getWidgetSchema();
277
278     $this->setDefault($name, $form->getDefaults());
279
280     $decorator = is_null($decorator) ? $widgetSchema->getFormFormatter()->getDecoratorFormat() : $decorator;
281
282     $this->widgetSchema[$name] = new sfWidgetFormSchemaDecorator($widgetSchema, $decorator);
283     $this->validatorSchema[$name] = $form->getValidatorSchema();
284
285     $this->resetFormFields();
286   }
287
288   /**
289    * Embeds a sfForm into the current form n times.
290    *
291    * @param string  $name             The field name
292    * @param sfForm  $form             A sfForm instance
293    * @param integer $n                The number of times to embed the form
294    * @param string  $decorator        A HTML decorator for the main form around embedded forms
295    * @param string  $innerDecorator   A HTML decorator for each embedded form
296    * @param array   $options          Options for schema
297    * @param array   $attributes       Attributes for schema
298    * @param array   $labels           Labels for schema
299    */
300   public function embedFormForEach($name, sfForm $form, $n, $decorator = null, $innerDecorator = null, $options = array(), $attributes = array(), $labels = array())
301   {
302     if (true === $this->isBound() || true === $form->isBound())
303     {
304       throw new LogicException('A bound form cannot be embedded');
305     }
306
307     $form = clone $form;
308     unset($form[self::$CSRFFieldName]);
309
310     $widgetSchema = $form->getWidgetSchema();
311
312     // generate labels and default values
313     $defaults = array();
314     for ($i = 0; $i < $n; $i++)
315     {
316       if (!isset($labels[$i]))
317       {
318         $labels[$i] = sprintf('%s (%s)', $widgetSchema->getFormFormatter()->generateLabelName($name), $i);
319       }
320
321       $defaults[$i] = $form->getDefaults();
322     }
323
324     $this->setDefault($name, $defaults);
325
326     $decorator = is_null($decorator) ? $widgetSchema->getFormFormatter()->getDecoratorFormat() : $decorator;
327     $innerDecorator = is_null($innerDecorator) ? $widgetSchema->getFormFormatter()->getDecoratorFormat() : $innerDecorator;
328
329     $this->widgetSchema[$name] = new sfWidgetFormSchemaDecorator(new sfWidgetFormSchemaForEach(new sfWidgetFormSchemaDecorator($widgetSchema, $innerDecorator), $n, $options, $attributes, $labels), $decorator);
330     $this->validatorSchema[$name] = new sfValidatorSchemaForEach($form->getValidatorSchema(), $n);
331
332     $this->resetFormFields();
333   }
334
335   /**
336    * Merges current form widget and validator schemas with the ones from the
337    * sfForm object passed as parameter. Please note it also merge defaults.
338    *
339    * @param  sfForm   $form      The sfForm instance to merge with current form
340    *
341    * @throws LogicException      If one of the form has already been bound
342    */
343   public function mergeForm(sfForm $form)
344   {
345     if (true === $this->isBound() || true === $form->isBound())
346     {
347       throw new LogicException('A bound form cannot be merged');
348     }
349
350     $form = clone $form;
351     unset($form[self::$CSRFFieldName]);
352
353     $this->defaults = array_merge($this->defaults, $form->getDefaults());
354
355     $widgets = $form->getWidgetSchema()->getFields();
356     foreach ($form->getWidgetSchema()->getPositions() as $field)
357     {
358       $this->widgetSchema[$field] = $widgets[$field];
359     }
360
361     foreach ($form->getValidatorSchema()->getFields() as $field => $validator)
362     {
363       $this->validatorSchema[$field] = $validator;
364     }
365
366     $this->getWidgetSchema()->setLabels(array_merge($this->getWidgetSchema()->getLabels(), $form->getWidgetSchema()->getLabels()));
367     $this->getWidgetSchema()->setHelps(array_merge($this->getWidgetSchema()->getHelps(), $form->getWidgetSchema()->getHelps()));
368
369     $this->mergePreValidator($form->getValidatorSchema()->getPreValidator());
370     $this->mergePostValidator($form->getValidatorSchema()->getPostValidator());
371
372     $this->resetFormFields();
373   }
374
375   /**
376    * Merges a validator with the current pre validators.
377    *
378    * @param sfValidatorBase $validator A validator to be merged
379    */
380   public function mergePreValidator(sfValidatorBase $validator = null)
381   {
382     if (is_null($validator))
383     {
384       return;
385     }
386
387     if (is_null($this->validatorSchema->getPreValidator()))
388     {
389       $this->validatorSchema->setPreValidator($validator);
390     }
391     else
392     {
393       $this->validatorSchema->setPreValidator(new sfValidatorAnd(array(
394         $this->validatorSchema->getPreValidator(),
395         $validator,
396       )));
397     }
398   }
399
400   /**
401    * Merges a validator with the current post validators.
402    *
403    * @param sfValidatorBase $validator A validator to be merged
404    */
405   public function mergePostValidator(sfValidatorBase $validator = null)
406   {
407     if (is_null($validator))
408     {
409       return;
410     }
411
412     if (is_null($this->validatorSchema->getPostValidator()))
413     {
414       $this->validatorSchema->setPostValidator($validator);
415     }
416     else
417     {
418       $this->validatorSchema->setPostValidator(new sfValidatorAnd(array(
419         $this->validatorSchema->getPostValidator(),
420         $validator,
421       )));
422     }
423   }
424
425   /**
426    * Sets the validators associated with this form.
427    *
428    * @param array $validators An array of named validators
429    */
430   public function setValidators(array $validators)
431   {
432     $this->setValidatorSchema(new sfValidatorSchema($validators));
433   }
434
435   /**
436    * Sets the validator schema associated with this form.
437    *
438    * @param sfValidatorSchema $validatorSchema A sfValidatorSchema instance
439    */
440   public function setValidatorSchema(sfValidatorSchema $validatorSchema)
441   {
442     $this->validatorSchema = $validatorSchema;
443
444     $this->resetFormFields();
445   }
446
447   /**
448    * Gets the validator schema associated with this form.
449    *
450    * @return sfValidatorSchema A sfValidatorSchema instance
451    */
452   public function getValidatorSchema()
453   {
454     return $this->validatorSchema;
455   }
456
457   /**
458    * Sets the widgets associated with this form.
459    *
460    * @param array $widgets An array of named widgets
461    */
462   public function setWidgets(array $widgets)
463   {
464     $this->setWidgetSchema(new sfWidgetFormSchema($widgets));
465   }
466
467   /**
468    * Sets the widget schema associated with this form.
469    *
470    * @param sfWidgetFormSchema $widgetSchema A sfWidgetFormSchema instance
471    */
472   public function setWidgetSchema(sfWidgetFormSchema $widgetSchema)
473   {
474     $this->widgetSchema = $widgetSchema;
475
476     $this->resetFormFields();
477   }
478
479   /**
480    * Gets the widget schema associated with this form.
481    *
482    * @return sfWidgetFormSchema A sfWidgetFormSchema instance
483    */
484   public function getWidgetSchema()
485   {
486     return $this->widgetSchema;
487   }
488
489   /**
490    * Sets an option value.
491    *
492    * @param string $name  The option name
493    * @param mixed  $value The default value
494    */
495   public function setOption($name, $value)
496   {
497     $this->options[$name] = $value;
498   }
499
500   /**
501    * Gets an option value.
502    *
503    * @param string $name    The option name
504    * @param mixed  $default The default value (null by default)
505    *
506    * @param mixed  The default value
507    */
508   public function getOption($name, $default = null)
509   {
510     return isset($this->options[$name]) ? $this->options[$name] : $default;
511   }
512
513   /**
514    * Sets a default value for a form field.
515    *
516    * @param string $name    The field name
517    * @param mixed  $default The default value
518    */
519   public function setDefault($name, $default)
520   {
521     $this->defaults[$name] = $default;
522
523     $this->resetFormFields();
524   }
525
526   /**
527    * Gets a default value for a form field.
528    *
529    * @param string $name The field name
530    *
531    * @param mixed  The default value
532    */
533   public function getDefault($name)
534   {
535     return isset($this->defaults[$name]) ? $this->defaults[$name] : null;
536   }
537
538   /**
539    * Returns true if the form has a default value for a form field.
540    *
541    * @param string $name The field name
542    *
543    * @param Boolean true if the form has a default value for this field, false otherwise
544    */
545   public function hasDefault($name)
546   {
547     return array_key_exists($name, $this->defaults);
548   }
549
550   /**
551    * Sets the default values for the form.
552    *
553    * The default values are only used if the form is not bound.
554    *
555    * @param array $defaults An array of default values
556    */
557   public function setDefaults($defaults)
558   {
559     $this->defaults = $defaults;
560
561     if (self::$CSRFProtection)
562     {
563       $this->setDefault(self::$CSRFFieldName, $this->getCSRFToken(self::$CSRFSecret));
564     }
565
566     $this->resetFormFields();
567   }
568
569   /**
570    * Gets the default values for the form.
571    *
572    * @return array An array of default values
573    */
574   public function getDefaults()
575   {
576     return $this->defaults;
577   }
578
579   /**
580    * Adds CSRF protection to the current form.
581    *
582    * @param string $secret The secret to use to compute the CSRF token
583    */
584   public function addCSRFProtection($secret)
585   {
586     if (false === $secret || (is_null($secret) && !self::$CSRFProtection))
587     {
588       return;
589     }
590
591     if (is_null($secret))
592     {
593       if (is_null(self::$CSRFSecret))
594       {
595         self::$CSRFSecret = md5(__FILE__.php_uname());
596       }
597
598       $secret = self::$CSRFSecret;
599     }
600
601     $token = $this->getCSRFToken($secret);
602
603     $this->validatorSchema[self::$CSRFFieldName] = new sfValidatorCSRFToken(array('token' => $token));
604     $this->widgetSchema[self::$CSRFFieldName] = new sfWidgetFormInputHidden();
605     $this->setDefault(self::$CSRFFieldName, $token);
606   }
607
608   /**
609    * Returns a CSRF token, given a secret.
610    *
611    * If you want to change the algorithm used to compute the token, you
612    * can override this method.
613    *
614    * @param string $secret The secret string to use
615    *
616    * @return string A token string
617    */
618   public function getCSRFToken($secret)
619   {
620     return md5($secret.session_id().get_class($this));
621   }
622
623   /**
624    * @return true if this form is CSRF protected
625    */
626   public function isCSRFProtected()
627   {
628     return !is_null($this->validatorSchema[self::$CSRFFieldName]);
629   }
630
631   /**
632    * Sets the CSRF field name.
633    *
634    * @param string $name The CSRF field name
635    */
636   static public function setCSRFFieldName($name)
637   {
638     self::$CSRFFieldName = $name;
639   }
640
641   /**
642    * Gets the CSRF field name.
643    *
644    * @return string The CSRF field name
645    */
646   static public function getCSRFFieldName()
647   {
648     return self::$CSRFFieldName;
649   }
650
651   /**
652    * Enables CSRF protection for all forms.
653    *
654    * The given secret will be used for all forms, except if you pass a secret in the constructor.
655    * Even if a secret is automatically generated if you don't provide a secret, you're strongly advised
656    * to provide one by yourself.
657    *
658    * @param string $secret A secret to use when computing the CSRF token
659    */
660   static public function enableCSRFProtection($secret = null)
661   {
662     if (false === $secret)
663     {
664       return self::disableCSRFProtection();
665     }
666
667     self::$CSRFProtection = true;
668
669     if (!is_null($secret))
670     {
671       self::$CSRFSecret = $secret;
672     }
673   }
674
675   /**
676    * Disables CSRF protection for all forms.
677    */
678   static public function disableCSRFProtection()
679   {
680     self::$CSRFProtection = false;
681   }
682
683   /**
684    * Returns true if the form is multipart.
685    *
686    * @return Boolean true if the form is multipart
687    */
688   public function isMultipart()
689   {
690     return $this->widgetSchema->needsMultipartForm();
691   }
692
693   public function resetFormFields()
694   {
695     $this->formFields = array();
696     $this->formFieldSchema = null;
697   }
698
699   /**
700    * Returns true if the bound field exists (implements the ArrayAccess interface).
701    *
702    * @param  string $name The name of the bound field
703    *
704    * @return Boolean true if the widget exists, false otherwise
705    */
706   public function offsetExists($name)
707   {
708     return isset($this->widgetSchema[$name]);
709   }
710
711   /**
712    * Returns the form field associated with the name (implements the ArrayAccess interface).
713    *
714    * @param  string $name  The offset of the value to get
715    *
716    * @return sfFormField   A form field instance
717    */
718   public function offsetGet($name)
719   {
720     if (!isset($this->formFields[$name]))
721     {
722       if (!$widget = $this->widgetSchema[$name])
723       {
724         throw new InvalidArgumentException(sprintf('Widget "%s" does not exist.', $name));
725       }
726
727       $values = $this->isBound ? $this->taintedValues : $this->defaults;
728
729       $class = $widget instanceof sfWidgetFormSchema ? 'sfFormFieldSchema' : 'sfFormField';
730
731       $this->formFields[$name] = new $class($widget, $this->getFormFieldSchema(), $name, isset($values[$name]) ? $values[$name] : null, $this->errorSchema[$name]);
732     }
733
734     return $this->formFields[$name];
735   }
736
737   /**
738    * Throws an exception saying that values cannot be set (implements the ArrayAccess interface).
739    *
740    * @param string $offset (ignored)
741    * @param string $value (ignored)
742    *
743    * @throws <b>LogicException</b>
744    */
745   public function offsetSet($offset, $value)
746   {
747     throw new LogicException('Cannot update form fields.');
748   }
749
750   /**
751    * Removes a field from the form.
752    *
753    * It removes the widget, validator and any values stored for the given field.
754    *
755    * @param string $offset The field name
756    */
757   public function offsetUnset($offset)
758   {
759     unset(
760       $this->widgetSchema[$offset],
761       $this->validatorSchema[$offset],
762       $this->defaults[$offset],
763       $this->taintedValues[$offset],
764       $this->values[$offset]
765     );
766
767     $this->resetFormFields();
768   }
769
770   /**
771    * Returns a form field for the main widget schema.
772    *
773    * @return sfFormFieldSchema A sfFormFieldSchema instance
774    */
775   public function getFormFieldSchema()
776   {
777     if (is_null($this->formFieldSchema))
778     {
779       $this->formFieldSchema = new sfFormFieldSchema($this->widgetSchema, null, null, $this->isBound ? $this->taintedValues : $this->defaults, $this->errorSchema);
780     }
781
782     return $this->formFieldSchema;
783   }
784
785   /**
786    * Converts uploaded file array to a format following the $_GET and $POST naming convention.
787    *
788    * It's safe to pass an already converted array, in which case this method just returns the original array unmodified.
789    *
790    * @param  array $taintedFiles An array representing uploaded file information
791    *
792    * @return array An array of re-ordered uploaded file information
793    */
794   static public function convertFileInformation(array $taintedFiles)
795   {
796     $files = array();
797     foreach ($taintedFiles as $key => $data)
798     {
799       $files[$key] = self::fixPhpFilesArray($data);
800     }
801
802     return $files;
803   }
804
805   static protected function fixPhpFilesArray($data)
806   {
807     $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type');
808     $keys = array_keys($data);
809     sort($keys);
810
811     if ($fileKeys != $keys || !isset($data['name']) || !is_array($data['name']))
812     {
813       return $data;
814     }
815
816     $files = $data;
817     foreach ($fileKeys as $k)
818     {
819       unset($files[$k]);
820     }
821     foreach (array_keys($data['name']) as $key)
822     {
823       $files[$key] = self::fixPhpFilesArray(array(
824         'error'    => $data['error'][$key],
825         'name'     => $data['name'][$key],
826         'type'     => $data['type'][$key],
827         'tmp_name' => $data['tmp_name'][$key],
828         'size'     => $data['size'][$key],
829       ));
830     }
831
832     return $files;
833   }
834
835   /**
836    * Returns true if a form thrown an exception in the __toString() method
837    *
838    * This is a hack needed because PHP does not allow to throw exceptions in __toString() magic method.
839    *
840    * @return boolean
841    */
842   static public function hasToStringException()
843   {
844     return !is_null(self::$toStringException);
845   }
846
847   /**
848    * Gets the exception if one was thrown in the __toString() method.
849    *
850    * This is a hack needed because PHP does not allow to throw exceptions in __toString() magic method.
851    *
852    * @return Exception
853    */
854   static public function getToStringException()
855   {
856     return self::$toStringException;
857   }
858
859   /**
860    * Sets an exception thrown by the __toString() method.
861    *
862    * This is a hack needed because PHP does not allow to throw exceptions in __toString() magic method.
863    *
864    * @param Exception $e The exception thrown by __toString()
865    */
866   static public function setToStringException(Exception $e)
867   {
868     if (is_null(self::$toStringException))
869     {
870       self::$toStringException = $e;
871     }
872   }
873
874   public function __clone()
875   {
876     $this->widgetSchema    = clone $this->widgetSchema;
877     $this->validatorSchema = clone $this->validatorSchema;
878
879     // we rebind the cloned form because Exceptions are not clonable
880     if ($this->isBound())
881     {
882       $this->bind($this->taintedValues, $this->taintedFiles);
883     }
884   }
885
886   /**
887    * Merges two arrays without reindexing numeric keys.
888    *
889    * @param array $array1 An array to merge
890    * @param array $array2 An array to merge
891    *
892    * @return array The merged array
893    */
894   static protected function deepArrayUnion($array1, $array2)
895   {
896     foreach ($array2 as $key => $value)
897     {
898       if (is_array($value) && isset($array1[$key]) && is_array($array1[$key]))
899       {
900         $array1[$key] = self::deepArrayUnion($array1[$key], $value);
901       }
902       else
903       {
904         $array1[$key] = $value;
905       }
906     }
907
908     return $array1;
909   }
910 }
911
Note: See TracBrowser for help on using the browser.