Test Failed
Branch master (206474)
by Fabio
18:24
created

TBaseValidator::getValidationGroup()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * TBaseValidator class file
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @copyright Copyright &copy; 2005-2016 The PRADO Group
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 * @package Prado\Web\UI\WebControls
10
 */
11
12
namespace Prado\Web\UI\WebControls;
13
14
use Prado\Exceptions\TConfigurationException;
15
use Prado\Exceptions\TNotSupportedException;
16
use Prado\TPropertyValue;
17
use Prado\Exceptions\TInvalidDataTypeException;
18
use Prado\Web\Javascripts\TJavaScript;
19
use Prado\Web\UI\IValidator;
20
21
/**
22
 * TBaseValidator class
23
 *
24
 * TBaseValidator serves as the base class for validator controls.
25
 *
26
 * Validation is performed when a postback control, such as a TButton, a TLinkButton
27
 * or a TTextBox (under AutoPostBack mode) is submitting the page and
28
 * its <b>CausesValidation</b> property is true.
29
 * You can also manually perform validation by calling {@link TPage::validate()}.
30
 * The input control to be validated is specified by {@link setControlToValidate ControlToValidate}.
31
 *
32
 * Validator controls always validate the associated input control on the serve side.
33
 * In addition, if {@link getEnableClientScript EnableClientScript} is true,
34
 * validation will also be performed on the client-side using javascript.
35
 * Client-side validation will validate user input before it is sent to the server.
36
 * The form data will not be submitted if any error is detected. This avoids
37
 * the round-trip of information necessary for server-side validation.
38
 *
39
 * You can use multiple validator controls to validate a single input control,
40
 * each responsible for validating against a different criteria.
41
 * For example, on a user registration form, you may want to make sure the user
42
 * enters a value in the username text box, and the input must consist of only word
43
 * characters. You can use a {@link TRequiredFieldValidator} to ensure the input
44
 * of username and a {@link TRegularExpressionValidator} to ensure the proper input.
45
 *
46
 * If an input control fails validation, the text specified by the {@link setErrorMessage ErrorMessage}
47
 * property is displayed in the validation control. However, if the {@link setText Text}
48
 * property is set, it will be displayed instead. If both {@link setErrorMessage ErrorMessage}
49
 * and {@link setText Text} are empty, the body content of the validator will
50
 * be displayed. Error display is controlled by {@link setDisplay Display} property.
51
 *
52
 * You can also customized the client-side behaviour by adding javascript
53
 * code to the subproperties of the {@link getClientSide ClientSide}
54
 * property. See quickstart documentation for further details.
55
 *
56
 * You can also place a {@link TValidationSummary} control on a page to display error messages
57
 * from the validators together. In this case, only the {@link setErrorMessage ErrorMessage}
58
 * property of the validators will be displayed in the {@link TValidationSummary} control.
59
 *
60
 * Validators can be partitioned into validation groups by setting their
61
 * {@link setValidationGroup ValidationGroup} property. If the control causing the
62
 * validation also sets its ValidationGroup property, only those validators having
63
 * the same ValidationGroup value will do input validation.
64
 *
65
 * Note, the {@link TPage::getIsValid IsValid} property of the current {@link TPage}
66
 * instance will be automatically updated by the validation process which occurs
67
 * after {@link TPage::onLoad onLoad} of {@link TPage} and before the postback events.
68
 * Therefore, if you use the {@link TPage::getIsValid()} property in
69
 * the {@link TPage::onLoad()} method, you must first explicitly call
70
 * the {@link TPage::validate()} method.
71
 *
72
 * <b>Notes to Inheritors</b>  When you inherit from TBaseValidator, you must
73
 * override the method {@link evaluateIsValid}.
74
 *
75
 * @author Qiang Xue <[email protected]>
76
 * @package Prado\Web\UI\WebControls
77
 * @since 3.0
78
 */
79
abstract class TBaseValidator extends TLabel implements IValidator
80
{
81
	/**
82
	 * @var bool whether the validation succeeds
83
	 */
84
	private $_isValid = true;
85
	/**
86
	 * @var bool whether the validator has been registered with the page
87
	 */
88
	private $_registered = false;
89
	/**
90
	 * @var TValidatorClientSide validator client-script options.
91
	 */
92
	private $_clientSide;
93
	/**
94
	 * Controls for which the client-side validation3.js file needs to handle
95
	 * them specially.
96
	 * @var array list of control class names
97
	 */
98
	private static $_clientClass = ['THtmlArea', 'THtmlArea4', 'TDatePicker', 'TListBox', 'TCheckBoxList'];
99
100
	/**
101
	 * Constructor.
102
	 * This method sets the foreground color to red.
103
	 */
104
	public function __construct()
105
	{
106
		parent::__construct();
107
		$this->setForeColor('red');
108
	}
109
110
	/**
111
	 * Registers the validator with page.
112
	 * @param mixed $param event parameter
113
	 */
114
	public function onInit($param)
115
	{
116
		parent::onInit($param);
117
		$this->getPage()->getValidators()->add($this);
118
		$this->_registered = true;
119
	}
120
121
	/**
122
	 * Unregisters the validator from page.
123
	 * @param mixed $param event parameter
124
	 */
125
	public function onUnload($param)
126
	{
127
		if ($this->_registered && ($page = $this->getPage()) !== null) {
128
			$page->getValidators()->remove($this);
129
		}
130
		$this->_registered = false;
131
		parent::onUnload($param);
132
	}
133
134
	/**
135
	 * Adds attributes to renderer. Calls parent implementation and renders the
136
	 * client control scripts.
137
	 * @param THtmlWriter $writer the renderer
138
	 */
139
	protected function addAttributesToRender($writer)
140
	{
141
		$display = $this->getDisplay();
142
		$visible = $this->getEnabled(true) && !$this->getIsValid();
143 View Code Duplication
		if ($display === TValidatorDisplayStyle::None || (!$visible && $display === TValidatorDisplayStyle::Dynamic)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
144
			$writer->addStyleAttribute('display', 'none');
145
		} elseif (!$visible) {
146
			$writer->addStyleAttribute('visibility', 'hidden');
147
		}
148
		$writer->addAttribute('id', $this->getClientID());
149
		parent::addAttributesToRender($writer);
150
		$this->renderClientControlScript($writer);
151
	}
152
153
	/**
154
	 * Returns an array of javascript validator options.
155
	 * @return array javascript validator options.
156
	 */
157
	protected function getClientScriptOptions()
158
	{
159
		$control = $this->getValidationTarget();
160
		$options['ID'] = $this->getClientID();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$options was never initialized. Although not strictly required by PHP, it is generally a good practice to add $options = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
161
		$options['FormID'] = $this->getPage()->getForm()->getClientID();
162
		$options['Display'] = $this->getDisplay();
163
		$options['ErrorMessage'] = $this->getErrorMessage();
164
		if ($this->getFocusOnError()) {
165
			$options['FocusOnError'] = $this->getFocusOnError();
166
			$options['FocusElementID'] = $this->getFocusElementID();
167
		}
168
		$options['ValidationGroup'] = $this->getValidationGroup();
169
		if ($control) {
170
			$options['ControlToValidate'] = $control->getClientID();
171
		}
172
		$options['ControlCssClass'] = $this->getControlCssClass();
173
174
		$options['ControlType'] = $this->getClientControlClass($control);
175
		$options['Enabled'] = $this->getEnabled(true);
176
177
		//get date format from date picker target control
178
		if ($control instanceof TDatePicker) {
179
			$options['DateFormat'] = $control->getDateFormat();
180
		}
181
182
		$options = array_merge($options, $this->getClientSide()->getOptions()->toArray());
183
184
		return $options;
185
	}
186
187
	/**
188
	 * Gets the Control type for client-side validation. If new cases exists in
189
	 * TBaseValidator::$_clientClass, be sure to update the corresponding
190
	 * "Javascript/validation3.js" file as well.
191
	 * @param TControl $control control to validate.
192
	 * @return string control type for client-side validation.
193
	 */
194
	private function getClientControlClass($control)
195
	{
196
		foreach (self::$_clientClass as $type) {
197
			if ($control instanceof $type) {
198
				return $type;
199
			}
200
		}
201
		$reflectionClass = new \ReflectionClass($control);
202
		return $reflectionClass->getShortName();
203
	}
204
205
	/**
206
	 * Gets the TValidatorClientSide that allows modification of the client-
207
	 * side validator events.
208
	 *
209
	 * The client-side validator supports the following events.
210
	 * # <tt>OnValidate</tt> -- raised before client-side validation is
211
	 * executed.
212
	 * # <tt>OnValidationSuccess</tt> -- raised after client-side validation is completed
213
	 * and is successfull, overrides default validator error messages updates.
214
	 * # <tt>OnValidationError</tt> -- raised after client-side validation is completed
215
	 * and failed, overrides default validator error message updates.
216
	 *
217
	 * You can attach custom javascript code to each of these events
218
	 *
219
	 * @return TValidatorClientSide javascript validator event options.
220
	 */
221
	public function getClientSide()
222
	{
223
		if ($this->_clientSide === null) {
224
			$this->_clientSide = $this->createClientSide();
225
		}
226
		return $this->_clientSide;
227
	}
228
229
	/**
230
	 * @return TValidatorClientSide javascript validator event options.
231
	 */
232
	protected function createClientSide()
233
	{
234
		return new TValidatorClientSide;
235
	}
236
237
	/**
238
	 * Renders the javascript code to the end script.
239
	 * If you override this method, be sure to call the parent implementation
240
	 * so that the event handlers can be invoked.
241
	 * @param THtmlWriter $writer the renderer
242
	 */
243
	public function renderClientControlScript($writer)
244
	{
245
		$scripts = $this->getPage()->getClientScript();
246
		if ($this->getEnableClientScript()) {
247
			$scripts->registerPradoScript('validator');
248
		}
249
		$formID = $this->getPage()->getForm()->getClientID();
250
		$scriptKey = "TBaseValidator:$formID";
251 View Code Duplication
		if ($this->getEnableClientScript() && !$scripts->isEndScriptRegistered($scriptKey)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
252
			$manager['FormID'] = $formID;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$manager was never initialized. Although not strictly required by PHP, it is generally a good practice to add $manager = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
253
			$options = TJavaScript::encode($manager);
254
			$scripts->registerEndScript($scriptKey, "new Prado.ValidationManager({$options});");
255
		}
256
		if ($this->getEnableClientScript()) {
257
			$this->registerClientScriptValidator();
258
		}
259
	}
260
261
	/**
262
	 * Override parent implementation to update the control CSS Class before
263
	 * the validated control is rendered
264
	 * @param mixed $param
265
	 */
266
	public function onPreRender($param)
267
	{
268
		parent::onPreRender($param);
269
		$this->updateControlCssClass();
270
	}
271
272
	/**
273
	 * Update the ControlToValidate component's css class depending
274
	 * if the ControlCssClass property is set, and whether this is valid.
275
	 * @return bool true if change, false otherwise.
276
	 */
277
	protected function updateControlCssClass()
278
	{
279
		if (($cssClass = $this->getControlCssClass()) !== '') {
280
			$control = $this->getValidationTarget();
281
			if ($control instanceof TWebControl) {
282
				$class = preg_replace('/ ' . preg_quote($cssClass) . '/', '', $control->getCssClass());
283
				if (!$this->getIsValid()) {
284
					$class .= ' ' . $cssClass;
285
					$control->setCssClass($class);
286
				} elseif ($control->getIsValid()) {
287
					$control->setCssClass($class);
288
				}
289
			}
290
		}
291
	}
292
293
	/**
294
	 * Registers the individual validator client-side javascript code.
295
	 */
296
	protected function registerClientScriptValidator()
297
	{
298
		$key = 'prado:' . $this->getClientID();
299
		if (!$this->getPage()->getClientScript()->isEndScriptRegistered($key)) {
300
			$options = TJavaScript::encode($this->getClientScriptOptions());
301
			$script = 'new ' . $this->getClientClassName() . '(' . $options . ');';
302
			$this->getPage()->getClientScript()->registerEndScript($key, $script);
303
		}
304
	}
305
306
	/**
307
	 * Gets the name of the javascript class responsible for performing validation for this control.
308
	 * This method overrides the parent implementation.
309
	 * @return string the javascript class name
310
	 */
311
	abstract protected function getClientClassName();
312
313
	/**
314
	 * This method overrides the parent implementation to forbid setting ForControl.
315
	 * @param string $value the associated control ID
316
	 * @throws TNotSupportedException whenever this method is called
317
	 */
318
	public function setForControl($value)
319
	{
320
		throw new TNotSupportedException('basevalidator_forcontrol_unsupported', get_class($this));
321
	}
322
323
	/**
324
	 * This method overrides parent's implementation by setting {@link setIsValid IsValid} to true if disabled.
325
	 * @param bool $value whether the validator is enabled.
326
	 */
327
	public function setEnabled($value)
328
	{
329
		$value = TPropertyValue::ensureBoolean($value);
330
		parent::setEnabled($value);
331
		if (!$value) {
332
			$this->_isValid = true;
333
		}
334
	}
335
336
	/**
337
	 * @return TValidatorDisplayStyle the style of displaying the error message. Defaults to TValidatorDisplayStyle::Fixed.
338
	 */
339
	public function getDisplay()
340
	{
341
		return $this->getViewState('Display', TValidatorDisplayStyle::Fixed);
342
	}
343
344
	/**
345
	 * @param TValidatorDisplayStyle $value the style of displaying the error message
346
	 */
347
	public function setDisplay($value)
348
	{
349
		$this->setViewState('Display', TPropertyValue::ensureEnum($value, 'Prado\\Web\\UI\\WebControls\\TValidatorDisplayStyle'), TValidatorDisplayStyle::Fixed);
350
	}
351
352
	/**
353
	 * @return bool whether client-side validation is enabled.
354
	 */
355
	public function getEnableClientScript()
356
	{
357
		return $this->getViewState('EnableClientScript', true);
358
	}
359
360
	/**
361
	 * @param bool $value whether client-side validation is enabled.
362
	 */
363
	public function setEnableClientScript($value)
364
	{
365
		$this->setViewState('EnableClientScript', TPropertyValue::ensureBoolean($value), true);
366
	}
367
368
	/**
369
	 * @return string the text for the error message.
370
	 */
371
	public function getErrorMessage()
372
	{
373
		return $this->getViewState('ErrorMessage', '');
374
	}
375
376
	/**
377
	 * Sets the text for the error message.
378
	 * @param string $value the error message
379
	 */
380
	public function setErrorMessage($value)
381
	{
382
		$this->setViewState('ErrorMessage', $value, '');
383
	}
384
385
	/**
386
	 * @return string the ID path of the input control to validate
387
	 */
388
	public function getControlToValidate()
389
	{
390
		return $this->getViewState('ControlToValidate', '');
391
	}
392
393
	/**
394
	 * Sets the ID path of the input control to validate.
395
	 * The ID path is the dot-connected IDs of the controls reaching from
396
	 * the validator's naming container to the target control.
397
	 * @param string $value the ID path
398
	 */
399
	public function setControlToValidate($value)
400
	{
401
		$this->setViewState('ControlToValidate', $value, '');
402
	}
403
404
	/**
405
	 * @return bool whether to set focus at the validating place if the validation fails. Defaults to false.
406
	 */
407
	public function getFocusOnError()
408
	{
409
		return $this->getViewState('FocusOnError', false);
410
	}
411
412
	/**
413
	 * @param bool $value whether to set focus at the validating place if the validation fails
414
	 */
415
	public function setFocusOnError($value)
416
	{
417
		$this->setViewState('FocusOnError', TPropertyValue::ensureBoolean($value), false);
418
	}
419
420
	/**
421
	 * Gets the ID of the HTML element that will receive focus if validation fails and {@link getFocusOnError FocusOnError} is true.
422
	 * Defaults to the client ID of the {@link getControlToValidate ControlToValidate}.
423
	 * @return string the ID of the HTML element to receive focus
424
	 */
425
	public function getFocusElementID()
426
	{
427
		if (($id = $this->getViewState('FocusElementID', '')) === '') {
428
			$target = $this->getValidationTarget();
429
			/* Workaround: TCheckBoxList and TRadioButtonList nests the actual
430
			 * inputs inside a table; we ensure the first input gets focused
431
			 */
432
			if ($target instanceof TCheckBoxList && $target->getItemCount() > 0) {
433
				$id = $target->getClientID() . '_c0';
434
			} else {
435
				$id = $target->getClientID();
436
			}
437
		}
438
		return $id;
439
	}
440
441
	/**
442
	 * Sets the ID of the HTML element that will receive focus if validation fails and {@link getFocusOnError FocusOnError} is true.
443
	 * @param string $value the ID of the HTML element to receive focus
444
	 */
445
	public function setFocusElementID($value)
446
	{
447
		$this->setViewState('FocusElementID', $value, '');
448
	}
449
450
	/**
451
	 * @return string the group which this validator belongs to
452
	 */
453
	public function getValidationGroup()
454
	{
455
		return $this->getViewState('ValidationGroup', '');
456
	}
457
458
	/**
459
	 * @param string $value the group which this validator belongs to
460
	 */
461
	public function setValidationGroup($value)
462
	{
463
		$this->setViewState('ValidationGroup', $value, '');
464
	}
465
466
	/**
467
	 * @return bool whether the validation succeeds
468
	 */
469
	public function getIsValid()
470
	{
471
		return $this->_isValid;
472
	}
473
474
	/**
475
	 * Sets the value indicating whether the validation succeeds
476
	 * @param bool $value whether the validation succeeds
477
	 */
478
	public function setIsValid($value)
479
	{
480
		$this->_isValid = TPropertyValue::ensureBoolean($value);
481
	}
482
483
	/**
484
	 * @throws TConfigurationException if {@link getControlToValidate
485
	 * ControlToValidate} is empty or does not point to a valid control
486
	 * @return TControl control to be validated. Null if no control is found.
487
	 */
488
	public function getValidationTarget()
489
	{
490
		if (($id = $this->getControlToValidate()) !== '' && ($control = $this->findControl($id)) !== null) {
491
			return $control;
492
		} else {
493
			throw new TConfigurationException('basevalidator_controltovalidate_invalid', get_class($this));
494
		}
495
	}
496
497
	/**
498
	 * Retrieves the property value of the control being validated.
499
	 * @param TControl $control control being validated
500
	 * @throws TInvalidDataTypeException if the control to be validated does not implement {@link \Prado\Web\UI\IValidatable}.
501
	 * @return string property value to be validated
502
	 */
503
	protected function getValidationValue($control)
504
	{
505
		if ($control instanceof \Prado\Web\UI\IValidatable) {
506
			return $control->getValidationPropertyValue();
507
		} else {
508
			throw new TInvalidDataTypeException('basevalidator_validatable_required', get_class($this));
509
		}
510
	}
511
512
	/**
513
	 * Validates the specified control.
514
	 * Do not override this method. Override {@link evaluateIsValid} instead.
515
	 * @return bool whether the validation succeeds
516
	 */
517
	public function validate()
518
	{
519
		$this->onValidate();
520
		if ($this->getVisible(true) && $this->getEnabled(true)) {
521
			$target = $this->getValidationTarget();
522
			// if the target is not a disabled web control
523
			if ($target === null ||
524
				($target !== null &&
525
				!($target instanceof TWebControl && !$target->getEnabled(true)))) {
526
				if ($this->evaluateIsValid()) {
527
					$this->setIsValid(true);
528
					$this->onValidationSuccess();
529
				} else {
530
					if ($target) {
531
						$target->setIsValid(false);
532
					}
533
					$this->setIsValid(false);
534
					$this->onValidationError();
535
				}
536
			} else {
537
				$this->evaluateIsValid();
538
				$this->setIsValid(true);
539
				$this->onValidationSuccess();
540
			}
541
		} else {
542
			$this->setIsValid(true);
543
		}
544
		return $this->getIsValid();
545
	}
546
547
	/**
548
	 * @return string the css class that is applied to the control being validated in case the validation fails
549
	 */
550
	public function getControlCssClass()
551
	{
552
		return $this->getViewState('ControlCssClass', '');
553
	}
554
555
	/**
556
	 * @param string $value the css class that is applied to the control being validated in case the validation fails
557
	 */
558
	public function setControlCssClass($value)
559
	{
560
		$this->setViewState('ControlCssClass', $value, '');
561
	}
562
563
	/**
564
	 * This is the major method for validation.
565
	 * Derived classes should implement this method to provide customized validation.
566
	 * @return bool whether the validation succeeds
567
	 */
568
	abstract protected function evaluateIsValid();
569
570
	/**
571
	 * This event is raised when the validator succeeds in validation.
572
	 */
573
	public function onValidationSuccess()
574
	{
575
		$this->raiseEvent('OnValidationSuccess', $this, null);
576
	}
577
578
	/**
579
	 * This event is raised when the validator fails in validation.
580
	 */
581
	public function onValidationError()
582
	{
583
		$this->raiseEvent('OnValidationError', $this, null);
584
	}
585
586
	/**
587
	 * This event is raised right before the validator starts to perform validation.
588
	 * You may use this event to change the behavior of validation.
589
	 * For example, you may disable the validator if certain condition is satisfied.
590
	 * Note, the event will NOT be raised if the validator is invisible.
591
	 */
592
	public function onValidate()
593
	{
594
		$this->raiseEvent('OnValidate', $this, null);
595
	}
596
597
	/**
598
	 * Renders the validator control.
599
	 * @param THtmlWriter $writer writer for the rendering purpose
600
	 */
601 View Code Duplication
	public function renderContents($writer)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
602
	{
603
		if (($text = $this->getText()) !== '') {
604
			$writer->write($text);
605
		} elseif (($text = $this->getErrorMessage()) !== '') {
606
			$writer->write($text);
607
		} else {
608
			parent::renderContents($writer);
609
		}
610
	}
611
}
612