1<?php 2/** 3 * Base class for HTML_QuickForm2 rules 4 * 5 * PHP version 5 6 * 7 * LICENSE: 8 * 9 * Copyright (c) 2006-2010, Alexey Borzov <avb@php.net>, 10 * Bertrand Mansion <golgote@mamasam.com> 11 * All rights reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 17 * * Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * * Redistributions in binary form must reproduce the above copyright 20 * notice, this list of conditions and the following disclaimer in the 21 * documentation and/or other materials provided with the distribution. 22 * * The names of the authors may not be used to endorse or promote products 23 * derived from this software without specific prior written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 26 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 27 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 28 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 29 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 30 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 31 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 32 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 33 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 34 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 35 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 * 37 * @category HTML 38 * @package HTML_QuickForm2 39 * @author Alexey Borzov <avb@php.net> 40 * @author Bertrand Mansion <golgote@mamasam.com> 41 * @license http://opensource.org/licenses/bsd-license.php New BSD License 42 * @version SVN: $Id: Rule.php 299706 2010-05-24 18:32:37Z avb $ 43 * @link http://pear.php.net/package/HTML_QuickForm2 44 */ 45 46/** 47 * Abstract base class for HTML_QuickForm2 rules 48 * 49 * This class provides methods that allow chaining several rules together. 50 * Its validate() method executes the whole rule chain starting from this rule. 51 * 52 * @category HTML 53 * @package HTML_QuickForm2 54 * @author Alexey Borzov <avb@php.net> 55 * @author Bertrand Mansion <golgote@mamasam.com> 56 * @version Release: @package_version@ 57 */ 58abstract class HTML_QuickForm2_Rule 59{ 60 /** 61 * Constant showing that validation should be run server-side 62 * @see HTML_QuickForm2_Node::addRule() 63 */ 64 const RUNAT_SERVER = 1; 65 66 /** 67 * Constant showing that validation should be run client-side 68 * @see HTML_QuickForm2_Node::addRule() 69 */ 70 const RUNAT_CLIENT = 2; 71 72 /** 73 * An element whose value will be validated by this rule 74 * @var HTML_QuickForm2_Node 75 */ 76 protected $owner; 77 78 /** 79 * An error message to display if validation fails 80 * @var string 81 */ 82 protected $message; 83 84 /** 85 * Configuration data for the rule 86 * @var mixed 87 */ 88 protected $config; 89 90 /** 91 * Rules chained to this via "and" and "or" operators 92 * 93 * The contents can be described as "disjunctive normal form", where an outer 94 * array represents a disjunction of conjunctive clauses represented by inner 95 * arrays. 96 * 97 * @var array 98 */ 99 protected $chainedRules = array(array()); 100 101 102 /** 103 * Class constructor 104 * 105 * @param HTML_QuickForm2_Node Element to validate 106 * @param string Error message to display if validation fails 107 * @param mixed Configuration data for the rule 108 */ 109 public function __construct(HTML_QuickForm2_Node $owner, $message = '', $config = null) 110 { 111 $this->setOwner($owner); 112 $this->setMessage($message); 113 $this->setConfig($config); 114 } 115 116 /** 117 * Merges local configuration with that provided for registerRule() 118 * 119 * Default behaviour is for global config to override local one, different 120 * Rules may implement more complex merging behaviours. 121 * 122 * @param mixed Local configuration 123 * @param mixed Global configuration, usually provided to {@link HTML_QuickForm2_Factory::registerRule()} 124 * @return mixed Merged configuration 125 */ 126 public static function mergeConfig($localConfig, $globalConfig) 127 { 128 return is_null($globalConfig)? $localConfig: $globalConfig; 129 } 130 131 /** 132 * Sets configuration data for the rule 133 * 134 * @param mixed Rule configuration data (specific for a Rule) 135 * @return HTML_QuickForm2_Rule 136 * @throws HTML_QuickForm2_InvalidArgumentException in case of invalid 137 * configuration data 138 */ 139 public function setConfig($config) 140 { 141 $this->config = $config; 142 return $this; 143 } 144 145 /** 146 * Returns the rule's configuration data 147 * 148 * @return mixed Configuration data (specific for a Rule) 149 */ 150 public function getConfig() 151 { 152 return $this->config; 153 } 154 155 /** 156 * Sets the error message output by the rule 157 * 158 * @param string Error message to display if validation fails 159 * @return HTML_QuickForm2_Rule 160 */ 161 public function setMessage($message) 162 { 163 $this->message = (string)$message; 164 return $this; 165 } 166 167 /** 168 * Returns the error message output by the rule 169 * 170 * @return string Error message 171 */ 172 public function getMessage() 173 { 174 return $this->message; 175 } 176 177 /** 178 * Sets the element that will be validated by this rule 179 * 180 * @param HTML_QuickForm2_Node Element to validate 181 */ 182 public function setOwner(HTML_QuickForm2_Node $owner) 183 { 184 if (null !== $this->owner) { 185 $this->owner->removeRule($this); 186 } 187 $this->owner = $owner; 188 } 189 190 /** 191 * Adds a rule to the chain with an "and" operator 192 * 193 * Evaluation is short-circuited, next rule will not be evaluated if the 194 * previous one returns false. The method is named this way because "and" is 195 * a reserved word in PHP. 196 * 197 * @param HTML_QuickForm2_Rule 198 * @return HTML_QuickForm2_Rule first rule in the chain (i.e. $this) 199 * @throws HTML_QuickForm2_InvalidArgumentException when trying to add 200 * a "required" rule to the chain 201 */ 202 public function and_(HTML_QuickForm2_Rule $next) 203 { 204 if ($next instanceof HTML_QuickForm2_Rule_Required) { 205 throw new HTML_QuickForm2_InvalidArgumentException( 206 'and_(): Cannot add a "required" rule' 207 ); 208 } 209 $this->chainedRules[count($this->chainedRules) - 1][] = $next; 210 return $this; 211 } 212 213 /** 214 * Adds a rule to the chain with an "or" operator 215 * 216 * Evaluation is short-circuited, next rule will not be evaluated if the 217 * previous one returns true. The method is named this way because "or" is 218 * a reserved word in PHP. 219 * 220 * @param HTML_QuickForm2_Rule 221 * @return HTML_QuickForm2_Rule first rule in the chain (i.e. $this) 222 * @throws HTML_QuickForm2_InvalidArgumentException when trying to add 223 * a "required" rule to the chain 224 */ 225 public function or_(HTML_QuickForm2_Rule $next) 226 { 227 if ($next instanceof HTML_QuickForm2_Rule_Required) { 228 throw new HTML_QuickForm2_InvalidArgumentException( 229 'or_(): Cannot add a "required" rule' 230 ); 231 } 232 $this->chainedRules[] = array($next); 233 return $this; 234 } 235 236 /** 237 * Performs validation 238 * 239 * The whole rule chain is executed. Note that the side effect of this 240 * method is setting the error message on element if validation fails 241 * 242 * @return boolean Whether the element is valid 243 */ 244 public function validate() 245 { 246 $globalValid = false; 247 $localValid = $this->validateOwner(); 248 foreach ($this->chainedRules as $item) { 249 foreach ($item as $multiplier) { 250 if (!($localValid = $localValid && $multiplier->validate())) { 251 break; 252 } 253 } 254 if ($globalValid = $globalValid || $localValid) { 255 break; 256 } 257 $localValid = true; 258 } 259 $globalValid or $this->setOwnerError(); 260 return $globalValid; 261 } 262 263 /** 264 * Validates the owner element 265 * 266 * @return bool Whether owner element is valid according to the rule 267 */ 268 abstract protected function validateOwner(); 269 270 /** 271 * Sets the error message on the owner element 272 */ 273 protected function setOwnerError() 274 { 275 if (strlen($this->getMessage()) && !$this->owner->getError()) { 276 $this->owner->setError($this->getMessage()); 277 } 278 } 279 280 /** 281 * Returns the client-side validation callback 282 * 283 * This essentially builds a Javascript version of validateOwner() method, 284 * with element ID and Rule configuration hardcoded. 285 * 286 * @return string Javascript function to validate the element's value 287 * @throws HTML_QuickForm2_Exception if Rule can only be run server-side 288 */ 289 protected function getJavascriptCallback() 290 { 291 throw new HTML_QuickForm2_Exception( 292 get_class($this) . ' does not implement javascript validation' 293 ); 294 } 295 296 /** 297 * Returns the client-side representation of the Rule 298 * 299 * The Javascript object returned contains the following fields: 300 * - callback: {@see getJavascriptCallback()} 301 * - elementId: element ID to set error for if validation fails 302 * - errorMessage: error message to set if validation fails 303 * - chained: chained rules, array of arrays like in $chainedRules property 304 * 305 * @return string 306 * @throws HTML_QuickForm2_Exception if Rule or its chained Rules can only 307 * be run server-side 308 */ 309 public function getJavascript() 310 { 311 $js = "{\n\tcallback: " . $this->getJavascriptCallback() . ",\n" . 312 "\telementId: '" . $this->owner->getId() . "',\n" . 313 "\terrorMessage: '" . strtr($this->getMessage(), array( 314 "\r" => '\r', 315 "\n" => '\n', 316 "\t" => '\t', 317 "'" => "\\'", 318 '"' => '\"', 319 '\\' => '\\\\' 320 )) . "',\n\tchained: ["; 321 $chained = array(); 322 foreach ($this->chainedRules as $item) { 323 $multipliers = array(); 324 foreach ($item as $multiplier) { 325 $multipliers[] = $multiplier->getJavascript(); 326 } 327 $chained[] = '[' . implode(",\n", $multipliers) . ']'; 328 } 329 $js .= implode(",\n", $chained) . "]\n}"; 330 return $js; 331 } 332} 333?> 334