web/Zend/Filter/Input.php
changeset 0 4eba9c11703f
equal deleted inserted replaced
-1:000000000000 0:4eba9c11703f
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Filter
       
    17  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    18  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    19  * @version    $Id: Input.php 22472 2010-06-20 07:36:16Z thomas $
       
    20  */
       
    21 
       
    22 /**
       
    23  * @see Zend_Loader
       
    24  */
       
    25 require_once 'Zend/Loader.php';
       
    26 
       
    27 /**
       
    28  * @see Zend_Filter
       
    29  */
       
    30 require_once 'Zend/Filter.php';
       
    31 
       
    32 /**
       
    33  * @see Zend_Validate
       
    34  */
       
    35 require_once 'Zend/Validate.php';
       
    36 
       
    37 /**
       
    38  * @category   Zend
       
    39  * @package    Zend_Filter
       
    40  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    41  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    42  */
       
    43 class Zend_Filter_Input
       
    44 {
       
    45 
       
    46     const ALLOW_EMPTY           = 'allowEmpty';
       
    47     const BREAK_CHAIN           = 'breakChainOnFailure';
       
    48     const DEFAULT_VALUE         = 'default';
       
    49     const MESSAGES              = 'messages';
       
    50     const ESCAPE_FILTER         = 'escapeFilter';
       
    51     const FIELDS                = 'fields';
       
    52     const FILTER                = 'filter';
       
    53     const FILTER_CHAIN          = 'filterChain';
       
    54     const MISSING_MESSAGE       = 'missingMessage';
       
    55     const INPUT_NAMESPACE       = 'inputNamespace';
       
    56     const VALIDATOR_NAMESPACE   = 'validatorNamespace';
       
    57     const FILTER_NAMESPACE      = 'filterNamespace';
       
    58     const NOT_EMPTY_MESSAGE     = 'notEmptyMessage';
       
    59     const PRESENCE              = 'presence';
       
    60     const PRESENCE_OPTIONAL     = 'optional';
       
    61     const PRESENCE_REQUIRED     = 'required';
       
    62     const RULE                  = 'rule';
       
    63     const RULE_WILDCARD         = '*';
       
    64     const VALIDATE              = 'validate';
       
    65     const VALIDATOR             = 'validator';
       
    66     const VALIDATOR_CHAIN       = 'validatorChain';
       
    67     const VALIDATOR_CHAIN_COUNT = 'validatorChainCount';
       
    68 
       
    69     /**
       
    70      * @var array Input data, before processing.
       
    71      */
       
    72     protected $_data = array();
       
    73 
       
    74     /**
       
    75      * @var array Association of rules to filters.
       
    76      */
       
    77     protected $_filterRules = array();
       
    78 
       
    79     /**
       
    80      * @var array Association of rules to validators.
       
    81      */
       
    82     protected $_validatorRules = array();
       
    83 
       
    84     /**
       
    85      * @var array After processing data, this contains mapping of valid fields
       
    86      * to field values.
       
    87      */
       
    88     protected $_validFields = array();
       
    89 
       
    90     /**
       
    91      * @var array After processing data, this contains mapping of validation
       
    92      * rules that did not pass validation to the array of messages returned
       
    93      * by the validator chain.
       
    94      */
       
    95     protected $_invalidMessages = array();
       
    96 
       
    97     /**
       
    98      * @var array After processing data, this contains mapping of validation
       
    99      * rules that did not pass validation to the array of error identifiers
       
   100      * returned by the validator chain.
       
   101      */
       
   102     protected $_invalidErrors = array();
       
   103 
       
   104     /**
       
   105      * @var array After processing data, this contains mapping of validation
       
   106      * rules in which some fields were missing to the array of messages
       
   107      * indicating which fields were missing.
       
   108      */
       
   109     protected $_missingFields = array();
       
   110 
       
   111     /**
       
   112      * @var array After processing, this contains a copy of $_data elements
       
   113      * that were not mentioned in any validation rule.
       
   114      */
       
   115     protected $_unknownFields = array();
       
   116 
       
   117     /**
       
   118      * @var Zend_Filter_Interface The filter object that is run on values
       
   119      * returned by the getEscaped() method.
       
   120      */
       
   121     protected $_defaultEscapeFilter = null;
       
   122 
       
   123     /**
       
   124      * Plugin loaders
       
   125      * @var array
       
   126      */
       
   127     protected $_loaders = array();
       
   128 
       
   129     /**
       
   130      * @var array Default values to use when processing filters and validators.
       
   131      */
       
   132     protected $_defaults = array(
       
   133         self::ALLOW_EMPTY         => false,
       
   134         self::BREAK_CHAIN         => false,
       
   135         self::ESCAPE_FILTER       => 'HtmlEntities',
       
   136         self::MISSING_MESSAGE     => "Field '%field%' is required by rule '%rule%', but the field is missing",
       
   137         self::NOT_EMPTY_MESSAGE   => "You must give a non-empty value for field '%field%'",
       
   138         self::PRESENCE            => self::PRESENCE_OPTIONAL
       
   139     );
       
   140 
       
   141     /**
       
   142      * @var boolean Set to False initially, this is set to True after the
       
   143      * input data have been processed.  Reset to False in setData() method.
       
   144      */
       
   145     protected $_processed = false;
       
   146 
       
   147     /**
       
   148      * Translation object
       
   149      * @var Zend_Translate
       
   150      */
       
   151     protected $_translator;
       
   152 
       
   153     /**
       
   154      * Is translation disabled?
       
   155      * @var Boolean
       
   156      */
       
   157     protected $_translatorDisabled = false;
       
   158 
       
   159     /**
       
   160      * @param array $filterRules
       
   161      * @param array $validatorRules
       
   162      * @param array $data       OPTIONAL
       
   163      * @param array $options    OPTIONAL
       
   164      */
       
   165     public function __construct($filterRules, $validatorRules, array $data = null, array $options = null)
       
   166     {
       
   167         if ($options) {
       
   168             $this->setOptions($options);
       
   169         }
       
   170 
       
   171         $this->_filterRules = (array) $filterRules;
       
   172         $this->_validatorRules = (array) $validatorRules;
       
   173 
       
   174         if ($data) {
       
   175             $this->setData($data);
       
   176         }
       
   177     }
       
   178 
       
   179     /**
       
   180      * @param mixed $namespaces
       
   181      * @return Zend_Filter_Input
       
   182      * @deprecated since 1.5.0RC1 - use addFilterPrefixPath() or addValidatorPrefixPath instead.
       
   183      */
       
   184     public function addNamespace($namespaces)
       
   185     {
       
   186         if (!is_array($namespaces)) {
       
   187             $namespaces = array($namespaces);
       
   188         }
       
   189 
       
   190         foreach ($namespaces as $namespace) {
       
   191             $prefix = $namespace;
       
   192             $path = str_replace('_', DIRECTORY_SEPARATOR, $prefix);
       
   193             $this->addFilterPrefixPath($prefix, $path);
       
   194             $this->addValidatorPrefixPath($prefix, $path);
       
   195         }
       
   196 
       
   197         return $this;
       
   198     }
       
   199 
       
   200     /**
       
   201      * Add prefix path for all elements
       
   202      *
       
   203      * @param  string $prefix
       
   204      * @param  string $path
       
   205      * @return Zend_Filter_Input
       
   206      */
       
   207     public function addFilterPrefixPath($prefix, $path)
       
   208     {
       
   209         $this->getPluginLoader(self::FILTER)->addPrefixPath($prefix, $path);
       
   210 
       
   211         return $this;
       
   212     }
       
   213 
       
   214     /**
       
   215      * Add prefix path for all elements
       
   216      *
       
   217      * @param  string $prefix
       
   218      * @param  string $path
       
   219      * @return Zend_Filter_Input
       
   220      */
       
   221     public function addValidatorPrefixPath($prefix, $path)
       
   222     {
       
   223         $this->getPluginLoader(self::VALIDATE)->addPrefixPath($prefix, $path);
       
   224 
       
   225         return $this;
       
   226     }
       
   227 
       
   228     /**
       
   229      * Set plugin loaders for use with decorators and elements
       
   230      *
       
   231      * @param  Zend_Loader_PluginLoader_Interface $loader
       
   232      * @param  string $type 'filter' or 'validate'
       
   233      * @return Zend_Filter_Input
       
   234      * @throws Zend_Filter_Exception on invalid type
       
   235      */
       
   236     public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader, $type)
       
   237     {
       
   238         $type = strtolower($type);
       
   239         switch ($type) {
       
   240             case self::FILTER:
       
   241             case self::VALIDATE:
       
   242                 $this->_loaders[$type] = $loader;
       
   243                 return $this;
       
   244             default:
       
   245                 require_once 'Zend/Filter/Exception.php';
       
   246                 throw new Zend_Filter_Exception(sprintf('Invalid type "%s" provided to setPluginLoader()', $type));
       
   247         }
       
   248 
       
   249         return $this;
       
   250     }
       
   251 
       
   252     /**
       
   253      * Retrieve plugin loader for given type
       
   254      *
       
   255      * $type may be one of:
       
   256      * - filter
       
   257      * - validator
       
   258      *
       
   259      * If a plugin loader does not exist for the given type, defaults are
       
   260      * created.
       
   261      *
       
   262      * @param  string $type 'filter' or 'validate'
       
   263      * @return Zend_Loader_PluginLoader_Interface
       
   264      * @throws Zend_Filter_Exception on invalid type
       
   265      */
       
   266     public function getPluginLoader($type)
       
   267     {
       
   268         $type = strtolower($type);
       
   269         if (!isset($this->_loaders[$type])) {
       
   270             switch ($type) {
       
   271                 case self::FILTER:
       
   272                     $prefixSegment = 'Zend_Filter_';
       
   273                     $pathSegment   = 'Zend/Filter/';
       
   274                     break;
       
   275                 case self::VALIDATE:
       
   276                     $prefixSegment = 'Zend_Validate_';
       
   277                     $pathSegment   = 'Zend/Validate/';
       
   278                     break;
       
   279                 default:
       
   280                     require_once 'Zend/Filter/Exception.php';
       
   281                     throw new Zend_Filter_Exception(sprintf('Invalid type "%s" provided to getPluginLoader()', $type));
       
   282             }
       
   283 
       
   284             require_once 'Zend/Loader/PluginLoader.php';
       
   285             $this->_loaders[$type] = new Zend_Loader_PluginLoader(
       
   286                 array($prefixSegment => $pathSegment)
       
   287             );
       
   288         }
       
   289 
       
   290         return $this->_loaders[$type];
       
   291     }
       
   292 
       
   293     /**
       
   294      * @return array
       
   295      */
       
   296     public function getMessages()
       
   297     {
       
   298         $this->_process();
       
   299         return array_merge($this->_invalidMessages, $this->_missingFields);
       
   300     }
       
   301 
       
   302     /**
       
   303      * @return array
       
   304      */
       
   305     public function getErrors()
       
   306     {
       
   307         $this->_process();
       
   308         return $this->_invalidErrors;
       
   309     }
       
   310 
       
   311     /**
       
   312      * @return array
       
   313      */
       
   314     public function getInvalid()
       
   315     {
       
   316         $this->_process();
       
   317         return $this->_invalidMessages;
       
   318     }
       
   319 
       
   320     /**
       
   321      * @return array
       
   322      */
       
   323     public function getMissing()
       
   324     {
       
   325         $this->_process();
       
   326         return $this->_missingFields;
       
   327     }
       
   328 
       
   329     /**
       
   330      * @return array
       
   331      */
       
   332     public function getUnknown()
       
   333     {
       
   334         $this->_process();
       
   335         return $this->_unknownFields;
       
   336     }
       
   337 
       
   338     /**
       
   339      * @param string $fieldName OPTIONAL
       
   340      * @return mixed
       
   341      */
       
   342     public function getEscaped($fieldName = null)
       
   343     {
       
   344         $this->_process();
       
   345         $this->_getDefaultEscapeFilter();
       
   346 
       
   347         if ($fieldName === null) {
       
   348             return $this->_escapeRecursive($this->_validFields);
       
   349         }
       
   350         if (array_key_exists($fieldName, $this->_validFields)) {
       
   351             return $this->_escapeRecursive($this->_validFields[$fieldName]);
       
   352         }
       
   353         return null;
       
   354     }
       
   355 
       
   356     /**
       
   357      * @param mixed $value
       
   358      * @return mixed
       
   359      */
       
   360     protected function _escapeRecursive($data)
       
   361     {
       
   362         if($data === null) {
       
   363             return $data;
       
   364         }
       
   365 
       
   366         if (!is_array($data)) {
       
   367             return $this->_getDefaultEscapeFilter()->filter($data);
       
   368         }
       
   369         foreach ($data as &$element) {
       
   370             $element = $this->_escapeRecursive($element);
       
   371         }
       
   372         return $data;
       
   373     }
       
   374 
       
   375     /**
       
   376      * @param string $fieldName OPTIONAL
       
   377      * @return mixed
       
   378      */
       
   379     public function getUnescaped($fieldName = null)
       
   380     {
       
   381         $this->_process();
       
   382         if ($fieldName === null) {
       
   383             return $this->_validFields;
       
   384         }
       
   385         if (array_key_exists($fieldName, $this->_validFields)) {
       
   386             return $this->_validFields[$fieldName];
       
   387         }
       
   388         return null;
       
   389     }
       
   390 
       
   391     /**
       
   392      * @param string $fieldName
       
   393      * @return mixed
       
   394      */
       
   395     public function __get($fieldName)
       
   396     {
       
   397         return $this->getEscaped($fieldName);
       
   398     }
       
   399 
       
   400     /**
       
   401      * @return boolean
       
   402      */
       
   403     public function hasInvalid()
       
   404     {
       
   405         $this->_process();
       
   406         return !(empty($this->_invalidMessages));
       
   407     }
       
   408 
       
   409     /**
       
   410      * @return boolean
       
   411      */
       
   412     public function hasMissing()
       
   413     {
       
   414         $this->_process();
       
   415         return !(empty($this->_missingFields));
       
   416     }
       
   417 
       
   418     /**
       
   419      * @return boolean
       
   420      */
       
   421     public function hasUnknown()
       
   422     {
       
   423         $this->_process();
       
   424         return !(empty($this->_unknownFields));
       
   425     }
       
   426 
       
   427     /**
       
   428      * @return boolean
       
   429      */
       
   430     public function hasValid()
       
   431     {
       
   432         $this->_process();
       
   433         return !(empty($this->_validFields));
       
   434     }
       
   435 
       
   436     /**
       
   437      * @param string $fieldName
       
   438      * @return boolean
       
   439      */
       
   440     public function isValid($fieldName = null)
       
   441     {
       
   442         $this->_process();
       
   443         if ($fieldName === null) {
       
   444             return !($this->hasMissing() || $this->hasInvalid());
       
   445         }
       
   446         return array_key_exists($fieldName, $this->_validFields);
       
   447     }
       
   448 
       
   449     /**
       
   450      * @param string $fieldName
       
   451      * @return boolean
       
   452      */
       
   453     public function __isset($fieldName)
       
   454     {
       
   455         $this->_process();
       
   456         return isset($this->_validFields[$fieldName]);
       
   457     }
       
   458 
       
   459     /**
       
   460      * @return Zend_Filter_Input
       
   461      * @throws Zend_Filter_Exception
       
   462      */
       
   463     public function process()
       
   464     {
       
   465         $this->_process();
       
   466         if ($this->hasInvalid()) {
       
   467             require_once 'Zend/Filter/Exception.php';
       
   468             throw new Zend_Filter_Exception("Input has invalid fields");
       
   469         }
       
   470         if ($this->hasMissing()) {
       
   471             require_once 'Zend/Filter/Exception.php';
       
   472             throw new Zend_Filter_Exception("Input has missing fields");
       
   473         }
       
   474 
       
   475         return $this;
       
   476     }
       
   477 
       
   478     /**
       
   479      * @param array $data
       
   480      * @return Zend_Filter_Input
       
   481      */
       
   482     public function setData(array $data)
       
   483     {
       
   484         $this->_data = $data;
       
   485 
       
   486         /**
       
   487          * Reset to initial state
       
   488          */
       
   489         $this->_validFields = array();
       
   490         $this->_invalidMessages = array();
       
   491         $this->_invalidErrors = array();
       
   492         $this->_missingFields = array();
       
   493         $this->_unknownFields = array();
       
   494 
       
   495         $this->_processed = false;
       
   496 
       
   497         return $this;
       
   498     }
       
   499 
       
   500     /**
       
   501      * @param mixed $escapeFilter
       
   502      * @return Zend_Filter_Interface
       
   503      */
       
   504     public function setDefaultEscapeFilter($escapeFilter)
       
   505     {
       
   506         if (is_string($escapeFilter) || is_array($escapeFilter)) {
       
   507             $escapeFilter = $this->_getFilter($escapeFilter);
       
   508         }
       
   509         if (!$escapeFilter instanceof Zend_Filter_Interface) {
       
   510             require_once 'Zend/Filter/Exception.php';
       
   511             throw new Zend_Filter_Exception('Escape filter specified does not implement Zend_Filter_Interface');
       
   512         }
       
   513         $this->_defaultEscapeFilter = $escapeFilter;
       
   514         return $escapeFilter;
       
   515     }
       
   516 
       
   517     /**
       
   518      * @param array $options
       
   519      * @return Zend_Filter_Input
       
   520      * @throws Zend_Filter_Exception if an unknown option is given
       
   521      */
       
   522     public function setOptions(array $options)
       
   523     {
       
   524         foreach ($options as $option => $value) {
       
   525             switch ($option) {
       
   526                 case self::ESCAPE_FILTER:
       
   527                     $this->setDefaultEscapeFilter($value);
       
   528                     break;
       
   529                 case self::INPUT_NAMESPACE:
       
   530                     $this->addNamespace($value);
       
   531                     break;
       
   532                 case self::VALIDATOR_NAMESPACE:
       
   533                     if(is_string($value)) {
       
   534                         $value = array($value);
       
   535                     }
       
   536 
       
   537                     foreach($value AS $prefix) {
       
   538                         $this->addValidatorPrefixPath(
       
   539                                 $prefix,
       
   540                                 str_replace('_', DIRECTORY_SEPARATOR, $prefix)
       
   541                         );
       
   542                     }
       
   543                     break;
       
   544                 case self::FILTER_NAMESPACE:
       
   545                     if(is_string($value)) {
       
   546                         $value = array($value);
       
   547                     }
       
   548 
       
   549                     foreach($value AS $prefix) {
       
   550                         $this->addFilterPrefixPath(
       
   551                                 $prefix,
       
   552                                 str_replace('_', DIRECTORY_SEPARATOR, $prefix)
       
   553                         );
       
   554                     }
       
   555                     break;
       
   556                 case self::ALLOW_EMPTY:
       
   557                 case self::BREAK_CHAIN:
       
   558                 case self::MISSING_MESSAGE:
       
   559                 case self::NOT_EMPTY_MESSAGE:
       
   560                 case self::PRESENCE:
       
   561                     $this->_defaults[$option] = $value;
       
   562                     break;
       
   563                 default:
       
   564                     require_once 'Zend/Filter/Exception.php';
       
   565                     throw new Zend_Filter_Exception("Unknown option '$option'");
       
   566                     break;
       
   567             }
       
   568         }
       
   569 
       
   570         return $this;
       
   571     }
       
   572 
       
   573     /**
       
   574      * Set translation object
       
   575      *
       
   576      * @param  Zend_Translate|Zend_Translate_Adapter|null $translator
       
   577      * @return Zend_Filter_Input
       
   578      */
       
   579     public function setTranslator($translator = null)
       
   580     {
       
   581         if ((null === $translator) || ($translator instanceof Zend_Translate_Adapter)) {
       
   582             $this->_translator = $translator;
       
   583         } elseif ($translator instanceof Zend_Translate) {
       
   584             $this->_translator = $translator->getAdapter();
       
   585         } else {
       
   586             require_once 'Zend/Validate/Exception.php';
       
   587             throw new Zend_Validate_Exception('Invalid translator specified');
       
   588         }
       
   589 
       
   590         return $this;
       
   591     }
       
   592 
       
   593     /**
       
   594      * Return translation object
       
   595      *
       
   596      * @return Zend_Translate_Adapter|null
       
   597      */
       
   598     public function getTranslator()
       
   599     {
       
   600         if ($this->translatorIsDisabled()) {
       
   601             return null;
       
   602         }
       
   603 
       
   604         if ($this->_translator === null) {
       
   605             require_once 'Zend/Registry.php';
       
   606             if (Zend_Registry::isRegistered('Zend_Translate')) {
       
   607                 $translator = Zend_Registry::get('Zend_Translate');
       
   608                 if ($translator instanceof Zend_Translate_Adapter) {
       
   609                     return $translator;
       
   610                 } elseif ($translator instanceof Zend_Translate) {
       
   611                     return $translator->getAdapter();
       
   612                 }
       
   613             }
       
   614         }
       
   615 
       
   616         return $this->_translator;
       
   617     }
       
   618 
       
   619     /**
       
   620      * Indicate whether or not translation should be disabled
       
   621      *
       
   622      * @param  bool $flag
       
   623      * @return Zend_Filter_Input
       
   624      */
       
   625     public function setDisableTranslator($flag)
       
   626     {
       
   627         $this->_translatorDisabled = (bool) $flag;
       
   628         return $this;
       
   629     }
       
   630 
       
   631     /**
       
   632      * Is translation disabled?
       
   633      *
       
   634      * @return bool
       
   635      */
       
   636     public function translatorIsDisabled()
       
   637     {
       
   638         return $this->_translatorDisabled;
       
   639     }
       
   640 
       
   641     /*
       
   642      * Protected methods
       
   643      */
       
   644 
       
   645     /**
       
   646      * @return void
       
   647      */
       
   648     protected function _filter()
       
   649     {
       
   650         foreach ($this->_filterRules as $ruleName => &$filterRule) {
       
   651             /**
       
   652              * Make sure we have an array representing this filter chain.
       
   653              * Don't typecast to (array) because it might be a Zend_Filter object
       
   654              */
       
   655             if (!is_array($filterRule)) {
       
   656                 $filterRule = array($filterRule);
       
   657             }
       
   658 
       
   659             /**
       
   660              * Filters are indexed by integer, metacommands are indexed by string.
       
   661              * Pick out the filters.
       
   662              */
       
   663             $filterList = array();
       
   664             foreach ($filterRule as $key => $value) {
       
   665                 if (is_int($key)) {
       
   666                     $filterList[] = $value;
       
   667                 }
       
   668             }
       
   669 
       
   670             /**
       
   671              * Use defaults for filter metacommands.
       
   672              */
       
   673             $filterRule[self::RULE] = $ruleName;
       
   674             if (!isset($filterRule[self::FIELDS])) {
       
   675                 $filterRule[self::FIELDS] = $ruleName;
       
   676             }
       
   677 
       
   678             /**
       
   679              * Load all the filter classes and add them to the chain.
       
   680              */
       
   681             if (!isset($filterRule[self::FILTER_CHAIN])) {
       
   682                 $filterRule[self::FILTER_CHAIN] = new Zend_Filter();
       
   683                 foreach ($filterList as $filter) {
       
   684                     if (is_string($filter) || is_array($filter)) {
       
   685                         $filter = $this->_getFilter($filter);
       
   686                     }
       
   687                     $filterRule[self::FILTER_CHAIN]->addFilter($filter);
       
   688                 }
       
   689             }
       
   690 
       
   691             /**
       
   692              * If the ruleName is the special wildcard rule,
       
   693              * then apply the filter chain to all input data.
       
   694              * Else just process the field named by the rule.
       
   695              */
       
   696             if ($ruleName == self::RULE_WILDCARD) {
       
   697                 foreach (array_keys($this->_data) as $field)  {
       
   698                     $this->_filterRule(array_merge($filterRule, array(self::FIELDS => $field)));
       
   699                 }
       
   700             } else {
       
   701                 $this->_filterRule($filterRule);
       
   702             }
       
   703         }
       
   704     }
       
   705 
       
   706     /**
       
   707      * @param array $filterRule
       
   708      * @return void
       
   709      */
       
   710     protected function _filterRule(array $filterRule)
       
   711     {
       
   712         $field = $filterRule[self::FIELDS];
       
   713         if (!array_key_exists($field, $this->_data)) {
       
   714             return;
       
   715         }
       
   716         if (is_array($this->_data[$field])) {
       
   717             foreach ($this->_data[$field] as $key => $value) {
       
   718                 $this->_data[$field][$key] = $filterRule[self::FILTER_CHAIN]->filter($value);
       
   719             }
       
   720         } else {
       
   721             $this->_data[$field] =
       
   722                 $filterRule[self::FILTER_CHAIN]->filter($this->_data[$field]);
       
   723         }
       
   724     }
       
   725 
       
   726     /**
       
   727      * @return Zend_Filter_Interface
       
   728      */
       
   729     protected function _getDefaultEscapeFilter()
       
   730     {
       
   731         if ($this->_defaultEscapeFilter !== null) {
       
   732             return $this->_defaultEscapeFilter;
       
   733         }
       
   734         return $this->setDefaultEscapeFilter($this->_defaults[self::ESCAPE_FILTER]);
       
   735     }
       
   736 
       
   737     /**
       
   738      * @param string $rule
       
   739      * @param string $field
       
   740      * @return string
       
   741      */
       
   742     protected function _getMissingMessage($rule, $field)
       
   743     {
       
   744         $message = $this->_defaults[self::MISSING_MESSAGE];
       
   745 
       
   746         if (null !== ($translator = $this->getTranslator())) {
       
   747             if ($translator->isTranslated(self::MISSING_MESSAGE)) {
       
   748                 $message = $translator->translate(self::MISSING_MESSAGE);
       
   749             } else {
       
   750                 $message = $translator->translate($message);
       
   751             }
       
   752         }
       
   753 
       
   754         $message = str_replace('%rule%', $rule, $message);
       
   755         $message = str_replace('%field%', $field, $message);
       
   756         return $message;
       
   757     }
       
   758 
       
   759     /**
       
   760      * @return string
       
   761      */
       
   762     protected function _getNotEmptyMessage($rule, $field)
       
   763     {
       
   764         $message = $this->_defaults[self::NOT_EMPTY_MESSAGE];
       
   765 
       
   766         if (null !== ($translator = $this->getTranslator())) {
       
   767             if ($translator->isTranslated(self::NOT_EMPTY_MESSAGE)) {
       
   768                 $message = $translator->translate(self::NOT_EMPTY_MESSAGE);
       
   769             } else {
       
   770                 $message = $translator->translate($message);
       
   771             }
       
   772         }
       
   773 
       
   774         $message = str_replace('%rule%', $rule, $message);
       
   775         $message = str_replace('%field%', $field, $message);
       
   776         return $message;
       
   777     }
       
   778 
       
   779     /**
       
   780      * @return void
       
   781      */
       
   782     protected function _process()
       
   783     {
       
   784         if ($this->_processed === false) {
       
   785             $this->_filter();
       
   786             $this->_validate();
       
   787             $this->_processed = true;
       
   788         }
       
   789     }
       
   790 
       
   791     /**
       
   792      * @return void
       
   793      */
       
   794     protected function _validate()
       
   795     {
       
   796         /**
       
   797          * Special case: if there are no validators, treat all fields as valid.
       
   798          */
       
   799         if (!$this->_validatorRules) {
       
   800             $this->_validFields = $this->_data;
       
   801             $this->_data = array();
       
   802             return;
       
   803         }
       
   804 
       
   805         foreach ($this->_validatorRules as $ruleName => &$validatorRule) {
       
   806             /**
       
   807              * Make sure we have an array representing this validator chain.
       
   808              * Don't typecast to (array) because it might be a Zend_Validate object
       
   809              */
       
   810             if (!is_array($validatorRule)) {
       
   811                 $validatorRule = array($validatorRule);
       
   812             }
       
   813 
       
   814             /**
       
   815              * Validators are indexed by integer, metacommands are indexed by string.
       
   816              * Pick out the validators.
       
   817              */
       
   818             $validatorList = array();
       
   819             foreach ($validatorRule as $key => $value) {
       
   820                 if (is_int($key)) {
       
   821                     $validatorList[$key] = $value;
       
   822                 }
       
   823             }
       
   824 
       
   825             /**
       
   826              * Use defaults for validation metacommands.
       
   827              */
       
   828             $validatorRule[self::RULE] = $ruleName;
       
   829             if (!isset($validatorRule[self::FIELDS])) {
       
   830                 $validatorRule[self::FIELDS] = $ruleName;
       
   831             }
       
   832             if (!isset($validatorRule[self::BREAK_CHAIN])) {
       
   833                 $validatorRule[self::BREAK_CHAIN] = $this->_defaults[self::BREAK_CHAIN];
       
   834             }
       
   835             if (!isset($validatorRule[self::PRESENCE])) {
       
   836                 $validatorRule[self::PRESENCE] = $this->_defaults[self::PRESENCE];
       
   837             }
       
   838             if (!isset($validatorRule[self::ALLOW_EMPTY])) {
       
   839                 $validatorRule[self::ALLOW_EMPTY] = $this->_defaults[self::ALLOW_EMPTY];
       
   840             }
       
   841 
       
   842             if (!isset($validatorRule[self::MESSAGES])) {
       
   843                 $validatorRule[self::MESSAGES] = array();
       
   844             } else if (!is_array($validatorRule[self::MESSAGES])) {
       
   845                 $validatorRule[self::MESSAGES] = array($validatorRule[self::MESSAGES]);
       
   846             } else if (array_intersect_key($validatorList, $validatorRule[self::MESSAGES])) {
       
   847                 // There are now corresponding numeric keys in the validation rule messages array
       
   848                 // Treat it as a named messages list for all rule validators
       
   849                 $unifiedMessages = $validatorRule[self::MESSAGES];
       
   850                 $validatorRule[self::MESSAGES] = array();
       
   851 
       
   852                 foreach ($validatorList as $key => $validator) {
       
   853                     if (array_key_exists($key, $unifiedMessages)) {
       
   854                         $validatorRule[self::MESSAGES][$key] = $unifiedMessages[$key];
       
   855                     }
       
   856                 }
       
   857             }
       
   858 
       
   859             /**
       
   860              * Load all the validator classes and add them to the chain.
       
   861              */
       
   862             if (!isset($validatorRule[self::VALIDATOR_CHAIN])) {
       
   863                 $validatorRule[self::VALIDATOR_CHAIN] = new Zend_Validate();
       
   864 
       
   865                 foreach ($validatorList as $key => $validator) {
       
   866                     if (is_string($validator) || is_array($validator)) {
       
   867                         $validator = $this->_getValidator($validator);
       
   868                     }
       
   869 
       
   870                     if (isset($validatorRule[self::MESSAGES][$key])) {
       
   871                         $value = $validatorRule[self::MESSAGES][$key];
       
   872                         if (is_array($value)) {
       
   873                             $validator->setMessages($value);
       
   874                         } else {
       
   875                             $validator->setMessage($value);
       
   876                         }
       
   877 
       
   878                         if ($validator instanceof Zend_Validate_NotEmpty) {
       
   879                             $this->_defaults[self::NOT_EMPTY_MESSAGE] = $value;
       
   880                         }
       
   881                     }
       
   882 
       
   883                     $validatorRule[self::VALIDATOR_CHAIN]->addValidator($validator, $validatorRule[self::BREAK_CHAIN]);
       
   884                 }
       
   885                 $validatorRule[self::VALIDATOR_CHAIN_COUNT] = count($validatorList);
       
   886             }
       
   887 
       
   888             /**
       
   889              * If the ruleName is the special wildcard rule,
       
   890              * then apply the validator chain to all input data.
       
   891              * Else just process the field named by the rule.
       
   892              */
       
   893             if ($ruleName == self::RULE_WILDCARD) {
       
   894                 foreach (array_keys($this->_data) as $field)  {
       
   895                     $this->_validateRule(array_merge($validatorRule, array(self::FIELDS => $field)));
       
   896                 }
       
   897             } else {
       
   898                 $this->_validateRule($validatorRule);
       
   899             }
       
   900         }
       
   901 
       
   902         /**
       
   903          * Unset fields in $_data that have been added to other arrays.
       
   904          * We have to wait until all rules have been processed because
       
   905          * a given field may be referenced by multiple rules.
       
   906          */
       
   907         foreach (array_merge(array_keys($this->_missingFields), array_keys($this->_invalidMessages)) as $rule) {
       
   908             foreach ((array) $this->_validatorRules[$rule][self::FIELDS] as $field) {
       
   909                 unset($this->_data[$field]);
       
   910             }
       
   911         }
       
   912         foreach ($this->_validFields as $field => $value) {
       
   913             unset($this->_data[$field]);
       
   914         }
       
   915 
       
   916         /**
       
   917          * Anything left over in $_data is an unknown field.
       
   918          */
       
   919         $this->_unknownFields = $this->_data;
       
   920     }
       
   921 
       
   922     /**
       
   923      * @param array $validatorRule
       
   924      * @return void
       
   925      */
       
   926     protected function _validateRule(array $validatorRule)
       
   927     {
       
   928         /**
       
   929          * Get one or more data values from input, and check for missing fields.
       
   930          * Apply defaults if fields are missing.
       
   931          */
       
   932         $data = array();
       
   933         foreach ((array) $validatorRule[self::FIELDS] as $key => $field) {
       
   934             if (array_key_exists($field, $this->_data)) {
       
   935                 $data[$field] = $this->_data[$field];
       
   936             } else if (isset($validatorRule[self::DEFAULT_VALUE])) {
       
   937                 /** @todo according to this code default value can't be an array. It has to be reviewed */
       
   938                 if (!is_array($validatorRule[self::DEFAULT_VALUE])) {
       
   939                     // Default value is a scalar
       
   940                     $data[$field] = $validatorRule[self::DEFAULT_VALUE];
       
   941                 } else {
       
   942                     // Default value is an array. Search for corresponding key
       
   943                     if (isset($validatorRule[self::DEFAULT_VALUE][$key])) {
       
   944                         $data[$field] = $validatorRule[self::DEFAULT_VALUE][$key];
       
   945                     } else if ($validatorRule[self::PRESENCE] == self::PRESENCE_REQUIRED) {
       
   946                         // Default value array is provided, but it doesn't have an entry for current field
       
   947                         // and presence is required
       
   948                         $this->_missingFields[$validatorRule[self::RULE]][] =
       
   949                            $this->_getMissingMessage($validatorRule[self::RULE], $field);
       
   950                     }
       
   951                 }
       
   952             } else if ($validatorRule[self::PRESENCE] == self::PRESENCE_REQUIRED) {
       
   953                 $this->_missingFields[$validatorRule[self::RULE]][] =
       
   954                     $this->_getMissingMessage($validatorRule[self::RULE], $field);
       
   955             }
       
   956         }
       
   957 
       
   958         /**
       
   959          * If any required fields are missing, break the loop.
       
   960          */
       
   961         if (isset($this->_missingFields[$validatorRule[self::RULE]]) && count($this->_missingFields[$validatorRule[self::RULE]]) > 0) {
       
   962             return;
       
   963         }
       
   964 
       
   965         /**
       
   966          * Evaluate the inputs against the validator chain.
       
   967          */
       
   968         if (count((array) $validatorRule[self::FIELDS]) > 1) {
       
   969             if (!$validatorRule[self::ALLOW_EMPTY]) {
       
   970                 $emptyFieldsFound = false;
       
   971                 $errorsList       = array();
       
   972                 $messages         = array();
       
   973 
       
   974                 foreach ($data as $fieldKey => $field) {
       
   975                     $notEmptyValidator = $this->_getValidator('NotEmpty');
       
   976                     $notEmptyValidator->setMessage($this->_getNotEmptyMessage($validatorRule[self::RULE], $fieldKey));
       
   977 
       
   978                     if (!$notEmptyValidator->isValid($field)) {
       
   979                         foreach ($notEmptyValidator->getMessages() as $messageKey => $message) {
       
   980                             if (!isset($messages[$messageKey])) {
       
   981                                 $messages[$messageKey] = $message;
       
   982                             } else {
       
   983                                 $messages[] = $message;
       
   984                             }
       
   985                         }
       
   986                         $errorsList[] = $notEmptyValidator->getErrors();
       
   987                         $emptyFieldsFound = true;
       
   988                     }
       
   989                 }
       
   990 
       
   991                 if ($emptyFieldsFound) {
       
   992                     $this->_invalidMessages[$validatorRule[self::RULE]] = $messages;
       
   993                     $this->_invalidErrors[$validatorRule[self::RULE]]   = array_unique(call_user_func_array('array_merge', $errorsList));
       
   994                     return;
       
   995                 }
       
   996             }
       
   997 
       
   998             if (!$validatorRule[self::VALIDATOR_CHAIN]->isValid($data)) {
       
   999                 $this->_invalidMessages[$validatorRule[self::RULE]] = $validatorRule[self::VALIDATOR_CHAIN]->getMessages();
       
  1000                 $this->_invalidErrors[$validatorRule[self::RULE]] = $validatorRule[self::VALIDATOR_CHAIN]->getErrors();
       
  1001                 return;
       
  1002             }
       
  1003         } else if (count($data) > 0) {
       
  1004             // $data is actually a one element array
       
  1005             $fieldNames = array_keys($data);
       
  1006             $fieldName = reset($fieldNames);
       
  1007             $field     = reset($data);
       
  1008 
       
  1009             $failed = false;
       
  1010             if (!is_array($field)) {
       
  1011                 $field = array($field);
       
  1012             }
       
  1013 
       
  1014             $notEmptyValidator = $this->_getValidator('NotEmpty');
       
  1015             $notEmptyValidator->setMessage($this->_getNotEmptyMessage($validatorRule[self::RULE], $fieldName));
       
  1016             if ($validatorRule[self::ALLOW_EMPTY]) {
       
  1017                 $validatorChain = $validatorRule[self::VALIDATOR_CHAIN];
       
  1018             } else {
       
  1019                 $validatorChain = new Zend_Validate();
       
  1020                 $validatorChain->addValidator($notEmptyValidator, true /* Always break on failure */);
       
  1021                 $validatorChain->addValidator($validatorRule[self::VALIDATOR_CHAIN]);
       
  1022             }
       
  1023 
       
  1024             foreach ($field as $value) {
       
  1025                 if ($validatorRule[self::ALLOW_EMPTY]  &&  !$notEmptyValidator->isValid($value)) {
       
  1026                     // Field is empty AND it's allowed. Do nothing.
       
  1027                     continue;
       
  1028                 }
       
  1029 
       
  1030                 if (!$validatorChain->isValid($value)) {
       
  1031                     if (isset($this->_invalidMessages[$validatorRule[self::RULE]])) {
       
  1032                         $collectedMessages = $this->_invalidMessages[$validatorRule[self::RULE]];
       
  1033                     } else {
       
  1034                         $collectedMessages = array();
       
  1035                     }
       
  1036 
       
  1037                     foreach ($validatorChain->getMessages() as $messageKey => $message) {
       
  1038                         if (!isset($collectedMessages[$messageKey])) {
       
  1039                             $collectedMessages[$messageKey] = $message;
       
  1040                         } else {
       
  1041                             $collectedMessages[] = $message;
       
  1042                         }
       
  1043                     }
       
  1044 
       
  1045                     $this->_invalidMessages[$validatorRule[self::RULE]] = $collectedMessages;
       
  1046                     if (isset($this->_invalidErrors[$validatorRule[self::RULE]])) {
       
  1047                         $this->_invalidErrors[$validatorRule[self::RULE]] = array_merge($this->_invalidErrors[$validatorRule[self::RULE]],
       
  1048                                                                                         $validatorChain->getErrors());
       
  1049                     } else {
       
  1050                         $this->_invalidErrors[$validatorRule[self::RULE]] = $validatorChain->getErrors();
       
  1051                     }
       
  1052                     unset($this->_validFields[$fieldName]);
       
  1053                     $failed = true;
       
  1054                     if ($validatorRule[self::BREAK_CHAIN]) {
       
  1055                         return;
       
  1056                     }
       
  1057                 }
       
  1058             }
       
  1059             if ($failed) {
       
  1060                 return;
       
  1061             }
       
  1062         }
       
  1063 
       
  1064         /**
       
  1065          * If we got this far, the inputs for this rule pass validation.
       
  1066          */
       
  1067         foreach ((array) $validatorRule[self::FIELDS] as $field) {
       
  1068             if (array_key_exists($field, $data)) {
       
  1069                 $this->_validFields[$field] = $data[$field];
       
  1070             }
       
  1071         }
       
  1072     }
       
  1073 
       
  1074     /**
       
  1075      * @param mixed $classBaseName
       
  1076      * @return Zend_Filter_Interface
       
  1077      */
       
  1078     protected function _getFilter($classBaseName)
       
  1079     {
       
  1080         return $this->_getFilterOrValidator(self::FILTER, $classBaseName);
       
  1081     }
       
  1082 
       
  1083     /**
       
  1084      * @param mixed $classBaseName
       
  1085      * @return Zend_Validate_Interface
       
  1086      */
       
  1087     protected function _getValidator($classBaseName)
       
  1088     {
       
  1089         return $this->_getFilterOrValidator(self::VALIDATE, $classBaseName);
       
  1090     }
       
  1091 
       
  1092     /**
       
  1093      * @param string $type
       
  1094      * @param mixed $classBaseName
       
  1095      * @return Zend_Filter_Interface|Zend_Validate_Interface
       
  1096      * @throws Zend_Filter_Exception
       
  1097      */
       
  1098     protected function _getFilterOrValidator($type, $classBaseName)
       
  1099     {
       
  1100         $args = array();
       
  1101 
       
  1102         if (is_array($classBaseName)) {
       
  1103             $args = $classBaseName;
       
  1104             $classBaseName = array_shift($args);
       
  1105         }
       
  1106 
       
  1107         $interfaceName = 'Zend_' . ucfirst($type) . '_Interface';
       
  1108         $className = $this->getPluginLoader($type)->load(ucfirst($classBaseName));
       
  1109 
       
  1110         $class = new ReflectionClass($className);
       
  1111 
       
  1112         if (!$class->implementsInterface($interfaceName)) {
       
  1113             require_once 'Zend/Filter/Exception.php';
       
  1114             throw new Zend_Filter_Exception("Class '$className' based on basename '$classBaseName' must implement the '$interfaceName' interface");
       
  1115         }
       
  1116 
       
  1117         if ($class->hasMethod('__construct')) {
       
  1118             $object = $class->newInstanceArgs($args);
       
  1119         } else {
       
  1120             $object = $class->newInstance();
       
  1121         }
       
  1122 
       
  1123         return $object;
       
  1124     }
       
  1125 
       
  1126 }