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 * sfValidatorSchema represents an array of fields.
13 *
14 * A field is a named validator.
15 *
16 * @package    symfony
17 * @subpackage validator
18 * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
19 * @version    SVN: $Id$
20 */
21class sfValidatorSchema extends sfValidatorBase implements ArrayAccess
22{
23  protected
24    $fields        = array(),
25    $preValidator  = null,
26    $postValidator = null;
27
28  /**
29   * Constructor.
30   *
31   * The first argument can be:
32   *
33   *  * null
34   *  * an array of named sfValidatorBase instances
35   *
36   * @param mixed $fields    Initial fields
37   * @param array $options   An array of options
38   * @param array $messages  An array of error messages
39   *
40   * @see sfValidatorBase
41   */
42  public function __construct($fields = null, $options = array(), $messages = array())
43  {
44    if (is_array($fields))
45    {
46      foreach ($fields as $name => $validator)
47      {
48        $this[$name] = $validator;
49      }
50    }
51    else if (null !== $fields)
52    {
53      throw new InvalidArgumentException('sfValidatorSchema constructor takes an array of sfValidatorBase objects.');
54    }
55
56    parent::__construct($options, $messages);
57  }
58
59  /**
60   * Configures the validator.
61   *
62   * Available options:
63   *
64   *  * allow_extra_fields:  if false, the validator adds an error if extra fields are given in the input array of values (default to false)
65   *  * filter_extra_fields: if true, the validator filters extra fields from the returned array of cleaned values (default to true)
66   *
67   * Available error codes:
68   *
69   *  * extra_fields
70   *
71   * @param array $options   An array of options
72   * @param array $messages  An array of error messages
73   *
74   * @see sfValidatorBase
75   */
76  protected function configure($options = array(), $messages = array())
77  {
78    $this->addOption('allow_extra_fields', false);
79    $this->addOption('filter_extra_fields', true);
80
81    $this->addMessage('extra_fields', 'Unexpected extra form field named "%field%".');
82    $this->addMessage('post_max_size', 'The form submission cannot be processed. It probably means that you have uploaded a file that is too big.');
83  }
84
85  /**
86   * @see sfValidatorBase
87   */
88  public function clean($values)
89  {
90    return $this->doClean($values);
91  }
92
93  /**
94   * @see sfValidatorBase
95   */
96  protected function doClean($values)
97  {
98    if (null === $values)
99    {
100      $values = array();
101    }
102
103    if (!is_array($values))
104    {
105      throw new InvalidArgumentException('You must pass an array parameter to the clean() method');
106    }
107
108    $clean  = array();
109    $unused = array_keys($this->fields);
110    $errorSchema = new sfValidatorErrorSchema($this);
111
112    // check that post_max_size has not been reached
113    if (isset($_SERVER['CONTENT_LENGTH']) && (int) $_SERVER['CONTENT_LENGTH'] > $this->getBytes(ini_get('post_max_size')) && ini_get('post_max_size') != 0)
114    {
115      $errorSchema->addError(new sfValidatorError($this, 'post_max_size'));
116
117      throw $errorSchema;
118    }
119
120    // pre validator
121    try
122    {
123      $values = $this->preClean($values);
124    }
125    catch (sfValidatorErrorSchema $e)
126    {
127      $errorSchema->addErrors($e);
128    }
129    catch (sfValidatorError $e)
130    {
131      $errorSchema->addError($e);
132    }
133
134    // validate given values
135    foreach ($values as $name => $value)
136    {
137      // field exists in our schema?
138      if (!array_key_exists($name, $this->fields))
139      {
140        if (!$this->options['allow_extra_fields'])
141        {
142          $errorSchema->addError(new sfValidatorError($this, 'extra_fields', array('field' => $name)));
143        }
144        else if (!$this->options['filter_extra_fields'])
145        {
146          $clean[$name] = $value;
147        }
148
149        continue;
150      }
151
152      unset($unused[array_search($name, $unused, true)]);
153
154      // validate value
155      try
156      {
157        $clean[$name] = $this->fields[$name]->clean($value);
158      }
159      catch (sfValidatorError $e)
160      {
161        $clean[$name] = null;
162
163        $errorSchema->addError($e, (string) $name);
164      }
165      catch (Exception $e)
166      {
167        $class = get_class($e);
168
169        throw new $class($e->getMessage().' of "'.$name.'" field');
170      }
171    }
172
173    // are non given values required?
174    foreach ($unused as $name)
175    {
176      // validate value
177      try
178      {
179        $clean[$name] = $this->fields[$name]->clean(null);
180      }
181      catch (sfValidatorError $e)
182      {
183        $clean[$name] = null;
184
185        $errorSchema->addError($e, (string) $name);
186      }
187    }
188
189    // post validator
190    try
191    {
192      $clean = $this->postClean($clean);
193    }
194    catch (sfValidatorErrorSchema $e)
195    {
196      $errorSchema->addErrors($e);
197    }
198    catch (sfValidatorError $e)
199    {
200      $errorSchema->addError($e);
201    }
202
203    if (count($errorSchema))
204    {
205      throw $errorSchema;
206    }
207
208    return $clean;
209  }
210
211  /**
212   * Cleans the input values.
213   *
214   * This method is the first validator executed by doClean().
215   *
216   * It executes the validator returned by getPreValidator()
217   * on the global array of values.
218   *
219   * @param  array $values  The input values
220   *
221   * @return array The cleaned values
222   *
223   * @throws sfValidatorError
224   */
225  public function preClean($values)
226  {
227    if (null === $validator = $this->getPreValidator())
228    {
229      return $values;
230    }
231
232    return $validator->clean($values);
233  }
234
235  /**
236   * Cleans the input values.
237   *
238   * This method is the last validator executed by doClean().
239   *
240   * It executes the validator returned by getPostValidator()
241   * on the global array of cleaned values.
242   *
243   * @param  array $values  The input values
244   *
245   * @throws sfValidatorError
246   */
247  public function postClean($values)
248  {
249    if (null === $validator = $this->getPostValidator())
250    {
251      return $values;
252    }
253
254    return $validator->clean($values);
255  }
256
257  /**
258   * Sets the pre validator.
259   *
260   * @param sfValidatorBase $validator  An sfValidatorBase instance
261   *
262   * @return sfValidatorBase The current validator instance
263   */
264  public function setPreValidator(sfValidatorBase $validator)
265  {
266    $this->preValidator = clone $validator;
267
268    return $this;
269  }
270
271  /**
272   * Returns the pre validator.
273   *
274   * @return sfValidatorBase A sfValidatorBase instance
275   */
276  public function getPreValidator()
277  {
278    return $this->preValidator;
279  }
280
281  /**
282   * Sets the post validator.
283   *
284   * @param sfValidatorBase $validator  An sfValidatorBase instance
285   *
286   * @return sfValidatorBase The current validator instance
287   */
288  public function setPostValidator(sfValidatorBase $validator)
289  {
290    $this->postValidator = clone $validator;
291
292    return $this;
293  }
294
295  /**
296   * Returns the post validator.
297   *
298   * @return sfValidatorBase An sfValidatorBase instance
299   */
300  public function getPostValidator()
301  {
302    return $this->postValidator;
303  }
304
305  /**
306   * Returns true if the schema has a field with the given name (implements the ArrayAccess interface).
307   *
308   * @param  string  $name  The field name
309   *
310   * @return bool true if the schema has a field with the given name, false otherwise
311   */
312  public function offsetExists($name)
313  {
314    return isset($this->fields[$name]);
315  }
316
317  /**
318   * Gets the field associated with the given name (implements the ArrayAccess interface).
319   *
320   * @param  string $name  The field name
321   *
322   * @return sfValidatorBase The sfValidatorBase instance associated with the given name, null if it does not exist
323   */
324  public function offsetGet($name)
325  {
326    return isset($this->fields[$name]) ? $this->fields[$name] : null;
327  }
328
329  /**
330   * Sets a field (implements the ArrayAccess interface).
331   *
332   * @param string          $name       The field name
333   * @param sfValidatorBase $validator  An sfValidatorBase instance
334   */
335  public function offsetSet($name, $validator)
336  {
337    if (!$validator instanceof sfValidatorBase)
338    {
339      throw new InvalidArgumentException('A validator must be an instance of sfValidatorBase.');
340    }
341
342    $this->fields[$name] = clone $validator;
343  }
344
345  /**
346   * Removes a field by name (implements the ArrayAccess interface).
347   *
348   * @param string $name
349   */
350  public function offsetUnset($name)
351  {
352    unset($this->fields[$name]);
353  }
354
355  /**
356   * Returns an array of fields.
357   *
358   * @return sfValidatorBase[] An array of sfValidatorBase instances
359   */
360  public function getFields()
361  {
362    return $this->fields;
363  }
364
365  /**
366   * @see sfValidatorBase
367   */
368  public function asString($indent = 0)
369  {
370    throw new Exception('Unable to convert a sfValidatorSchema to string.');
371  }
372
373  public function __clone()
374  {
375    foreach ($this->fields as $name => $field)
376    {
377      $this->fields[$name] = clone $field;
378    }
379
380    if (null !== $this->preValidator)
381    {
382      $this->preValidator = clone $this->preValidator;
383    }
384
385    if (null !== $this->postValidator)
386    {
387      $this->postValidator = clone $this->postValidator;
388    }
389  }
390
391  protected function getBytes($value)
392  {
393    $value    = trim($value);
394    $number   = (float) $value;
395    $modifier = strtolower($value[strlen($value) - 1]);
396
397    $exp_by_modifier = array(
398      'k' => 1,
399      'm' => 2,
400      'g' => 3,
401    );
402
403    if (array_key_exists($modifier, $exp_by_modifier)) {
404      $exp    = $exp_by_modifier[$modifier];
405      $number = $number * pow(1024, $exp);
406    }
407
408    return $number;
409  }
410}
411