1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Forms;
9:
10: use Nette;
11:
12:
13: 14: 15: 16: 17:
18: class Rules extends Nette\Object implements \IteratorAggregate
19: {
20:
21: const VALIDATE_PREFIX = 'validate';
22:
23:
24: public static $defaultMessages = array(
25: Form::PROTECTION => 'Your session has expired. Please return to the home page and try again.',
26: Form::EQUAL => 'Please enter %s.',
27: Form::NOT_EQUAL => 'This value should not be %s.',
28: Form::FILLED => 'This field is required.',
29: Form::BLANK => 'This field should be blank.',
30: Form::MIN_LENGTH => 'Please enter at least %d characters.',
31: Form::MAX_LENGTH => 'Please enter no more than %d characters.',
32: Form::LENGTH => 'Please enter a value between %d and %d characters long.',
33: Form::EMAIL => 'Please enter a valid email address.',
34: Form::URL => 'Please enter a valid URL.',
35: Form::INTEGER => 'Please enter a valid integer.',
36: Form::FLOAT => 'Please enter a valid number.',
37: Form::RANGE => 'Please enter a value between %d and %d.',
38: Form::MAX_FILE_SIZE => 'The size of the uploaded file can be up to %d bytes.',
39: Form::MAX_POST_SIZE => 'The uploaded data exceeds the limit of %d bytes.',
40: Form::IMAGE => 'The uploaded file must be image in format JPEG, GIF or PNG.',
41: Form::MIME_TYPE => 'The uploaded file is not in the expected format.',
42: Nette\Forms\Controls\SelectBox::VALID => 'Please select a valid option.',
43: );
44:
45:
46: private $required;
47:
48:
49: private $rules = array();
50:
51:
52: private $parent;
53:
54:
55: private $toggles = array();
56:
57:
58: private $control;
59:
60:
61: public function __construct(IControl $control)
62: {
63: $this->control = $control;
64: }
65:
66:
67: 68: 69: 70: 71:
72: public function setRequired($value = TRUE)
73: {
74: if ($value) {
75: $this->addRule(Form::REQUIRED, is_string($value) ? $value : NULL);
76: } else {
77: $this->required = NULL;
78: }
79: return $this;
80: }
81:
82:
83: 84: 85: 86:
87: public function isRequired()
88: {
89: return $this->required instanceof Rule ? !$this->required->isNegative : FALSE;
90: }
91:
92:
93: 94: 95: 96: 97: 98: 99:
100: public function addRule($operation, $message = NULL, $arg = NULL)
101: {
102: $rule = new Rule;
103: $rule->control = $this->control;
104: $rule->operation = $operation;
105: $this->adjustOperation($rule);
106: $rule->arg = $arg;
107: $rule->type = Rule::VALIDATOR;
108: $rule->message = $message;
109: if ($rule->operation === Form::REQUIRED) {
110: $this->required = $rule;
111: } else {
112: $this->rules[] = $rule;
113: }
114: return $this;
115: }
116:
117:
118: 119: 120: 121: 122: 123:
124: public function addCondition($operation, $arg = NULL)
125: {
126: return $this->addConditionOn($this->control, $operation, $arg);
127: }
128:
129:
130: 131: 132: 133: 134: 135: 136:
137: public function addConditionOn(IControl $control, $operation, $arg = NULL)
138: {
139: $rule = new Rule;
140: $rule->control = $control;
141: $rule->operation = $operation;
142: $this->adjustOperation($rule);
143: $rule->arg = $arg;
144: $rule->type = Rule::CONDITION;
145: $rule->subRules = new static($this->control);
146: $rule->subRules->parent = $this;
147:
148: $this->rules[] = $rule;
149: return $rule->subRules;
150: }
151:
152:
153: 154: 155: 156:
157: public function elseCondition()
158: {
159: $rule = clone end($this->parent->rules);
160: $rule->isNegative = !$rule->isNegative;
161: $rule->subRules = new static($this->parent->control);
162: $rule->subRules->parent = $this->parent;
163: $this->parent->rules[] = $rule;
164: return $rule->subRules;
165: }
166:
167:
168: 169: 170: 171:
172: public function endCondition()
173: {
174: return $this->parent;
175: }
176:
177:
178: 179: 180: 181: 182: 183:
184: public function toggle($id, $hide = TRUE)
185: {
186: $this->toggles[$id] = $hide;
187: return $this;
188: }
189:
190:
191: 192: 193: 194:
195: public function getToggles($actual = FALSE)
196: {
197: return $actual ? $this->getToggleStates() : $this->toggles;
198: }
199:
200:
201: 202: 203: 204:
205: public function getToggleStates($toggles = array(), $success = TRUE)
206: {
207: foreach ($this->toggles as $id => $hide) {
208: $toggles[$id] = ($success xor !$hide) || !empty($toggles[$id]);
209: }
210:
211: foreach ($this as $rule) {
212: if ($rule->type === Rule::CONDITION) {
213: $toggles = $rule->subRules->getToggleStates($toggles, $success && static::validateRule($rule));
214: }
215: }
216: return $toggles;
217: }
218:
219:
220: 221: 222: 223:
224: public function validate()
225: {
226: foreach ($this as $rule) {
227: $success = $this->validateRule($rule);
228:
229: if ($rule->type === Rule::CONDITION && $success && !$rule->subRules->validate()) {
230: return FALSE;
231:
232: } elseif ($rule->type === Rule::VALIDATOR && !$success) {
233: $rule->control->addError(static::formatMessage($rule, TRUE));
234: return FALSE;
235: }
236: }
237: return TRUE;
238: }
239:
240:
241: 242: 243: 244:
245: public static function validateRule(Rule $rule)
246: {
247: $args = is_array($rule->arg) ? $rule->arg : array($rule->arg);
248: foreach ($args as & $val) {
249: $val = $val instanceof IControl ? $val->getValue() : $val;
250: }
251: return $rule->isNegative
252: xor call_user_func(self::getCallback($rule), $rule->control, is_array($rule->arg) ? $args : $args[0]);
253: }
254:
255:
256: 257: 258: 259:
260: public function getIterator()
261: {
262: $rules = $this->rules;
263: if ($this->required) {
264: array_unshift($rules, $this->required);
265: }
266: return new \ArrayIterator($rules);
267: }
268:
269:
270: 271: 272: 273: 274:
275: private function adjustOperation($rule)
276: {
277: if (is_string($rule->operation) && ord($rule->operation[0]) > 127) {
278: $rule->isNegative = TRUE;
279: $rule->operation = ~$rule->operation;
280: }
281:
282: if (!is_callable($this->getCallback($rule))) {
283: $operation = is_scalar($rule->operation) ? " '$rule->operation'" : '';
284: throw new Nette\InvalidArgumentException("Unknown operation$operation for control '{$rule->control->name}'.");
285: }
286: }
287:
288:
289: private static function getCallback($rule)
290: {
291: $op = $rule->operation;
292: if (is_string($op) && strncmp($op, ':', 1) === 0) {
293: return get_class($rule->control) . '::' . self::VALIDATE_PREFIX . ltrim($op, ':');
294: } else {
295: return $op;
296: }
297: }
298:
299:
300: public static function formatMessage($rule, $withValue)
301: {
302: $message = $rule->message;
303: if ($message instanceof Nette\Utils\Html) {
304: return $message;
305:
306: } elseif ($message === NULL && is_string($rule->operation) && isset(self::$defaultMessages[$rule->operation])) {
307: $message = self::$defaultMessages[$rule->operation];
308:
309: } elseif ($message == NULL) {
310: trigger_error("Missing validation message for control '{$rule->control->name}'.", E_USER_WARNING);
311: }
312:
313: if ($translator = $rule->control->getForm()->getTranslator()) {
314: $message = $translator->translate($message, is_int($rule->arg) ? $rule->arg : NULL);
315: }
316:
317: $message = preg_replace_callback('#%(name|label|value|\d+\$[ds]|[ds])#', function ($m) use ($rule, $withValue) {
318: static $i = -1;
319: switch ($m[1]) {
320: case 'name': return $rule->control->getName();
321: case 'label': return $rule->control->translate($rule->control->caption);
322: case 'value': return $withValue ? $rule->control->getValue() : $m[0];
323: default:
324: $args = is_array($rule->arg) ? $rule->arg : array($rule->arg);
325: $i = (int) $m[1] ? $m[1] - 1 : $i + 1;
326: return isset($args[$i]) ? ($args[$i] instanceof IControl ? ($withValue ? $args[$i]->getValue() : "%$i") : $args[$i]) : '';
327: }
328: }, $message);
329: return $message;
330: }
331:
332: }
333: