Passed
Push — master ( 3d13ab...202a73 )
by Fabio
05:43
created

TControl::getEnableTheming()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
eloc 4
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 6
ccs 0
cts 4
cp 0
crap 12
rs 10
1
<?php
2
/**
3
 * TControl, TControlCollection, TEventParameter and INamingContainer class file
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 * @package Prado\Web\UI
9
 */
10
11
namespace Prado\Web\UI;
12
13
use Exception;
14
use Prado\Exceptions\TInvalidDataValueException;
15
use Prado\Exceptions\TInvalidOperationException;
16
use Prado\Prado;
17
use Prado\TPropertyValue;
18
use Prado\Web\UI\ActiveControls\IActiveControl;
19
use Prado\Collections\TAttributeCollection;
20
21
/**
22
 * TControl class
23
 *
24
 * TControl is the base class for all components on a page hierarchy.
25
 * It implements the following features for UI-related functionalities:
26
 * - databinding feature
27
 * - parent and child relationship
28
 * - naming container and containee relationship
29
 * - viewstate and controlstate features
30
 * - rendering scheme
31
 * - control lifecycles
32
 *
33
 * A property can be data-bound with an expression. By calling {@link dataBind},
34
 * expressions bound to properties will be evaluated and the results will be
35
 * set to the corresponding properties.
36
 *
37
 * Parent and child relationship determines how the presentation of controls are
38
 * enclosed within each other. A parent will determine where to place
39
 * the presentation of its child controls. For example, a TPanel will enclose
40
 * all its child controls' presentation within a div html tag. A control's parent
41
 * can be obtained via {@link getParent Parent} property, and its
42
 * {@link getControls Controls} property returns a list of the control's children,
43
 * including controls and static texts. The property can be manipulated
44
 * like an array for adding or removing a child (see {@link TList} for more details).
45
 *
46
 * A naming container control implements INamingContainer and ensures that
47
 * its containee controls can be differentiated by their ID property values.
48
 * Naming container and containee realtionship specifies a protocol to uniquely
49
 * identify an arbitrary control on a page hierarchy by an ID path (concatenation
50
 * of all naming containers' IDs and the target control's ID).
51
 *
52
 * Viewstate and controlstate are two approaches to preserve state across
53
 * page postback requests. ViewState is mainly related with UI specific state
54
 * and can be disabled if not needed. ControlState represents crucial logic state
55
 * and cannot be disabled.
56
 *
57
 * A control is rendered via its {@link render()} method (the method is invoked
58
 * by the framework.) Descendant control classes may override this method for
59
 * customized rendering. By default, {@link render()} invokes {@link renderChildren()}
60
 * which is responsible for rendering of children of the control.
61
 * Control's {@link getVisible Visible} property governs whether the control
62
 * should be rendered or not.
63
 *
64
 * Each control on a page will undergo a series of lifecycles, including
65
 * control construction, Init, Load, PreRender, Render, and OnUnload.
66
 * They work together with page lifecycles to process a page request.
67
 *
68
 * @author Qiang Xue <[email protected]>
69
 * @package Prado\Web\UI
70
 * @since 3.0
71
 */
72
class TControl extends \Prado\TApplicationComponent implements IRenderable, IBindable
73
{
74
	/**
75
	 * format of control ID
76
	 */
77
	const ID_FORMAT = '/^[a-zA-Z_]\\w*$/';
78
	/**
79
	 * separator char between IDs in a UniqueID
80
	 */
81
	const ID_SEPARATOR = '$';
82
	/**
83
	 * separator char between IDs in a ClientID
84
	 */
85
	const CLIENT_ID_SEPARATOR = '_';
86
	/**
87
	 * prefix to an ID automatically generated
88
	 */
89
	const AUTOMATIC_ID_PREFIX = 'ctl';
90
91
	/**
92
	 * the stage of lifecycles that the control is currently at
93
	 */
94
	const CS_CONSTRUCTED = 0;
95
	const CS_CHILD_INITIALIZED = 1;
96
	const CS_INITIALIZED = 2;
97
	const CS_STATE_LOADED = 3;
98
	const CS_LOADED = 4;
99
	const CS_PRERENDERED = 5;
100
101
	/**
102
	 * State bits.
103
	 */
104
	const IS_ID_SET = 0x01;
105
	const IS_DISABLE_VIEWSTATE = 0x02;
106
	const IS_SKIN_APPLIED = 0x04;
107
	const IS_STYLESHEET_APPLIED = 0x08;
108
	const IS_DISABLE_THEMING = 0x10;
109
	const IS_CHILD_CREATED = 0x20;
110
	const IS_CREATING_CHILD = 0x40;
111
112
	/**
113
	 * Indexes for the rare fields.
114
	 * In order to save memory, rare fields will only be created if they are needed.
115
	 */
116
	const RF_CONTROLS = 0;			// child controls
117
	const RF_CHILD_STATE = 1;			// child state field
118
	const RF_NAMED_CONTROLS = 2;		// list of controls whose namingcontainer is this control
119
	const RF_NAMED_CONTROLS_ID = 3;	// counter for automatic id
120
	const RF_SKIN_ID = 4;				// skin ID
121
	const RF_DATA_BINDINGS = 5;		// data bindings
122
	const RF_EVENTS = 6;				// event handlers
123
	const RF_CONTROLSTATE = 7;		// controlstate
124
	const RF_NAMED_OBJECTS = 8;		// controls declared with ID on template
125
	const RF_ADAPTER = 9;				// adapter
126
	const RF_AUTO_BINDINGS = 10;		// auto data bindings
127
128
	/**
129
	 * @var string control ID
130
	 */
131
	private $_id = '';
132
	/**
133
	 * @var string control unique ID
134
	 */
135
	private $_uid;
136
	/**
137
	 * @var \Prado\Web\UI\TControl parent of the control
138
	 */
139
	private $_parent;
140
	/**
141
	 * @var TPage page that the control resides in
142
	 */
143
	private $_page;
144
	/**
145
	 * @var \Prado\Web\UI\TControl naming container of the control
146
	 */
147
	private $_namingContainer;
148
	/**
149
	 * @var TTemplateControl control whose template contains the control
150
	 */
151
	private $_tplControl;
152
	/**
153
	 * @var array viewstate data
154
	 */
155
	private $_viewState = [];
156
	/**
157
	 * @var array temporary state (usually set in template)
158
	 */
159
	private $_tempState = [];
160
	/**
161
	 * @var bool whether we should keep state in viewstate
162
	 */
163
	private $_trackViewState = true;
164
	/**
165
	 * @var int the current stage of the control lifecycles
166
	 */
167
	private $_stage = 0;
168
	/**
169
	 * @var int representation of the state bits
170
	 */
171
	private $_flags = 0;
172
	/**
173
	 * @var array a collection of rare control data
174
	 */
175
	private $_rf = [];
176
177
	/**
178
	 * TControl need not auto listen to global events because class
179
	 * behaviors are not typically added in the middle of running a page
180
	 * and the overhead of so many TControls listening and unlistening.
181
	 *
182
	 * @return bool returns false
183
	 */
184
	public function getAutoGlobalListen()
185
	{
186
		return false;
187
	}
188
189
	/**
190
	 * Returns a property value by name or a control by ID.
191
	 * This overrides the parent implementation by allowing accessing
192
	 * a control via its ID using the following syntax,
193
	 * <code>
194
	 * $menuBar=$this->menuBar;
195
	 * </code>
196
	 * Note, the control must be configured in the template
197
	 * with explicit ID. If the name matches both a property and a control ID,
198
	 * the control ID will take the precedence.
199
	 *
200
	 * @param string $name the property name or control ID
201
	 * @throws TInvalidOperationException if the property is not defined.
202
	 * @return mixed the property value or the target control
203
	 * @see registerObject
204
	 */
205
	public function __get($name)
206
	{
207
		if (isset($this->_rf[self::RF_NAMED_OBJECTS][$name])) {
208
			return $this->_rf[self::RF_NAMED_OBJECTS][$name];
209
		} else {
210
			return parent::__get($name);
211
		}
212
	}
213
214
	/**
215
	 * Checks for the existance of a property value by name or a control by ID.
216
	 * This overrides the parent implementation by allowing checking for the
217
	 * existance of a control via its ID using the following syntax,
218
	 * <code>
219
	 * $menuBarExists = isset($this->menuBar);
220
	 * </code>
221
	 * Do not call this method. This is a PHP magic method that we override
222
	 * to allow using isset() to detect if a component property is set or not.
223
	 * Note, the control must be configured in the template
224
	 * with explicit ID. If the name matches both a property and a control ID,
225
	 * the control ID will take the precedence.
226
	 *
227
	 * @param string $name the property name or control ID
228
	 * @return bool wether the control or property exists
229
	 * @see __get
230
	 */
231
	public function __isset($name)
232
	{
233
		if (isset($this->_rf[self::RF_NAMED_OBJECTS][$name])) {
234
			return true;
235
		} else {
236
			return parent::__isset($name);
237
		}
238
	}
239 1
240
	/**
241 1
	 * @return bool whether there is an adapter for this control
242
	 */
243
	public function getHasAdapter()
244
	{
245
		return isset($this->_rf[self::RF_ADAPTER]);
246
	}
247 1
248
	/**
249 1
	 * @return TControlAdapter control adapter. Null if not exists.
250 1
	 */
251
	public function getAdapter()
252
	{
253
		return $this->_rf[self::RF_ADAPTER] ?? null;
254
	}
255
256
	/**
257
	 * @param TControlAdapter $adapter control adapter
258
	 */
259
	public function setAdapter(TControlAdapter $adapter)
260
	{
261
		$this->_rf[self::RF_ADAPTER] = $adapter;
262
	}
263
264
	/**
265
	 * @return \Prado\Web\UI\TControl the parent of this control
266
	 */
267
	public function getParent()
268
	{
269
		return $this->_parent;
270
	}
271
272
	/**
273
	 * @return \Prado\Web\UI\TControl the naming container of this control
274
	 */
275
	public function getNamingContainer()
276
	{
277
		if (!$this->_namingContainer && $this->_parent) {
278
			if ($this->_parent instanceof INamingContainer) {
279
				$this->_namingContainer = $this->_parent;
280
			} else {
281
				$this->_namingContainer = $this->_parent->getNamingContainer();
282
			}
283
		}
284
		return $this->_namingContainer;
285
	}
286
287
	/**
288
	 * @return TPage the page that contains this control
289
	 */
290
	public function getPage()
291
	{
292
		if (!$this->_page) {
293
			if ($this->_parent) {
294
				$this->_page = $this->_parent->getPage();
295
			} elseif ($this->_tplControl) {
296
				$this->_page = $this->_tplControl->getPage();
297
			}
298
		}
299
		return $this->_page;
300
	}
301
302
	/**
303
	 * Sets the page for a control.
304
	 * Only framework developers should use this method.
305
	 * @param TPage $page the page that contains this control
306
	 */
307
	public function setPage($page)
308
	{
309
		$this->_page = $page;
310
	}
311
312
	/**
313
	 * Sets the control whose template contains this control.
314
	 * Only framework developers should use this method.
315
	 * @param TTemplateControl $control the control whose template contains this control
316
	 */
317
	public function setTemplateControl($control)
318
	{
319
		$this->_tplControl = $control;
320
	}
321
322
	/**
323
	 * @return TTemplateControl the control whose template contains this control
324
	 */
325
	public function getTemplateControl()
326
	{
327
		if (!$this->_tplControl && $this->_parent) {
328
			$this->_tplControl = $this->_parent->getTemplateControl();
329
		}
330
		return $this->_tplControl;
331
	}
332
333
	/**
334
	 * @return TTemplateControl the control whose template is loaded from
335
	 * some external storage, such as file, db, and whose template ultimately
336
	 * contains this control.
337
	 */
338
	public function getSourceTemplateControl()
339
	{
340
		$control = $this;
341
		while (($control instanceof TControl) && ($control = $control->getTemplateControl()) !== null) {
342
			if (($control instanceof TTemplateControl) && $control->getIsSourceTemplateControl()) {
343
				return $control;
344 1
			}
345
		}
346 1
		return $this->getPage();
347
	}
348
349
	/**
350
	 * Gets the lifecycle step the control is currently at.
351
	 * This method should only be used by control developers.
352
	 * @return int the lifecycle step the control is currently at.
353
	 * The value can be CS_CONSTRUCTED, CS_CHILD_INITIALIZED, CS_INITIALIZED,
354
	 * CS_STATE_LOADED, CS_LOADED, CS_PRERENDERED.
355
	 */
356
	protected function getControlStage()
357
	{
358
		return $this->_stage;
359
	}
360
361
	/**
362
	 * Sets the lifecycle step the control is currently at.
363
	 * This method should only be used by control developers.
364
	 * @param int $value the lifecycle step the control is currently at.
365
	 * Valid values include CS_CONSTRUCTED, CS_CHILD_INITIALIZED, CS_INITIALIZED,
366
	 * CS_STATE_LOADED, CS_LOADED, CS_PRERENDERED.
367
	 */
368
	protected function setControlStage($value)
369
	{
370
		$this->_stage = $value;
371
	}
372
373
	/**
374
	 * Returns the id of the control.
375
	 * Control ID can be either manually set or automatically generated.
376
	 * If $hideAutoID is true, automatically generated ID will be returned as an empty string.
377
	 * @param bool $hideAutoID whether to hide automatically generated ID
378
	 * @return string the ID of the control
379
	 */
380
	public function getID($hideAutoID = true)
381
	{
382
		if ($hideAutoID) {
383
			return ($this->_flags & self::IS_ID_SET) ? $this->_id : '';
384
		} else {
385
			return $this->_id;
386
		}
387
	}
388
389
	/**
390
	 * @param string $id the new control ID. The value must consist of word characters [a-zA-Z0-9_] only
391
	 * @throws TInvalidDataValueException if ID is in a bad format
392
	 */
393
	public function setID($id)
394
	{
395
		if (!preg_match(self::ID_FORMAT, $id)) {
396
			throw new TInvalidDataValueException('control_id_invalid', get_class($this), $id);
397
		}
398
		$this->_id = $id;
399
		$this->_flags |= self::IS_ID_SET;
400
		$this->clearCachedUniqueID($this instanceof INamingContainer);
401
		if ($this->_namingContainer) {
402
			$this->_namingContainer->clearNameTable();
403
		}
404
	}
405
406
	/**
407
	 * Returns a unique ID that identifies the control in the page hierarchy.
408
	 * A unique ID is the contenation of all naming container controls' IDs and the control ID.
409
	 * These IDs are separated by '$' character.
410
	 * Control users should not rely on the specific format of UniqueID, however.
411
	 * @return string a unique ID that identifies the control in the page hierarchy
412
	 */
413
	public function getUniqueID()
414
	{
415
		if ($this->_uid === '' || $this->_uid === null) {	// need to build the UniqueID
416
			$this->_uid = '';  // set to not-null, so that clearCachedUniqueID() may take action
417
			if ($namingContainer = $this->getNamingContainer()) {
418
				if ($this->getPage() === $namingContainer) {
419
					return ($this->_uid = $this->_id);
420
				} elseif (($prefix = $namingContainer->getUniqueID()) === '') {
421
					return $this->_id;
422
				} else {
423
					return ($this->_uid = $prefix . self::ID_SEPARATOR . $this->_id);
424
				}
425
			} else {	// no naming container
426
				return $this->_id;
427
			}
428
		} else {
429
			return $this->_uid;
430
		}
431
	}
432
433
	/**
434
	 * Sets input focus to this control.
435
	 */
436
	public function focus()
437
	{
438
		$this->getPage()->setFocus($this);
439
	}
440
441
	/**
442
	 * Returns the client ID of the control.
443
	 * The client ID can be used to uniquely identify
444
	 * the control in client-side scripts (such as JavaScript).
445
	 * Do not rely on the explicit format of the return ID.
446
	 * @return string the client ID of the control
447
	 */
448
	public function getClientID()
449
	{
450
		return strtr($this->getUniqueID(), self::ID_SEPARATOR, self::CLIENT_ID_SEPARATOR);
451
	}
452
453
	/**
454
	 * Converts a unique ID to a client ID.
455
	 * @param string $uniqueID the unique ID of a control
456
	 * @return string the client ID of the control
457
	 */
458
	public static function convertUniqueIdToClientId($uniqueID)
459
	{
460
		return strtr($uniqueID, self::ID_SEPARATOR, self::CLIENT_ID_SEPARATOR);
461
	}
462
463
	/**
464
	 * @return string the skin ID of this control, '' if not set
465
	 */
466
	public function getSkinID()
467
	{
468
		return $this->_rf[self::RF_SKIN_ID] ?? '';
469
	}
470
471
	/**
472
	 * @param string $value the skin ID of this control
473
	 * @throws TInvalidOperationException if the SkinID is set in a stage later than PreInit, or if the skin is applied already.
474
	 */
475
	public function setSkinID($value)
476
	{
477
		if (($this->_flags & self::IS_SKIN_APPLIED) || $this->_stage >= self::CS_CHILD_INITIALIZED) {
478
			throw new TInvalidOperationException('control_skinid_unchangeable', get_class($this));
479
		} else {
480
			$this->_rf[self::RF_SKIN_ID] = $value;
481
		}
482
	}
483
484
	/**
485
	 * @return bool if a skin is applied.
486
	 */
487
	public function getIsSkinApplied()
488
	{
489
		return ($this->_flags & self::IS_SKIN_APPLIED);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_flags & self::IS_SKIN_APPLIED returns the type integer which is incompatible with the documented return type boolean.
Loading history...
490
	}
491
492
	/**
493
	 * @return bool whether theming is enabled for this control.
494
	 * The theming is enabled if the control and all its parents have it enabled.
495
	 */
496
	public function getEnableTheming()
497
	{
498
		if ($this->_flags & self::IS_DISABLE_THEMING) {
499
			return false;
500
		} else {
501
			return $this->_parent ? $this->_parent->getEnableTheming() : true;
502
		}
503
	}
504
505
	/**
506
	 * @param bool $value whether to enable theming
507
	 * @throws TInvalidOperationException if this method is invoked after OnPreInit
508
	 */
509
	public function setEnableTheming($value)
510
	{
511
		if ($this->_stage >= self::CS_CHILD_INITIALIZED) {
512
			throw new TInvalidOperationException('control_enabletheming_unchangeable', get_class($this), $this->getUniqueID());
513
		} elseif (TPropertyValue::ensureBoolean($value)) {
514
			$this->_flags &= ~self::IS_DISABLE_THEMING;
515
		} else {
516
			$this->_flags |= self::IS_DISABLE_THEMING;
517
		}
518
	}
519
520
	/**
521
	 * Returns custom data associated with this control.
522
	 * A control may be associated with some custom data for various purposes.
523
	 * For example, a button may be associated with a string to identify itself
524
	 * in a generic OnClick event handler.
525
	 * @return mixed custom data associated with this control. Defaults to null.
526
	 */
527
	public function getCustomData()
528
	{
529
		return $this->getViewState('CustomData', null);
530
	}
531
532
	/**
533
	 * Associates custom data with this control.
534
	 * Note, the custom data must be serializable and unserializable.
535
	 * @param mixed $value custom data
536
	 */
537
	public function setCustomData($value)
538
	{
539
		$this->setViewState('CustomData', $value, null);
540
	}
541
542
	/**
543
	 * @return bool whether the control has child controls
544
	 */
545
	public function getHasControls()
546
	{
547
		return isset($this->_rf[self::RF_CONTROLS]) && $this->_rf[self::RF_CONTROLS]->getCount() > 0;
548
	}
549
550
	/**
551
	 * @return TControlCollection the child control collection
552
	 */
553
	public function getControls()
554
	{
555
		if (!isset($this->_rf[self::RF_CONTROLS])) {
556
			$this->_rf[self::RF_CONTROLS] = $this->createControlCollection();
557
		}
558
		return $this->_rf[self::RF_CONTROLS];
559
	}
560
561
	/**
562
	 * Creates a control collection object that is to be used to hold child controls
563
	 * @return TControlCollection control collection
564
	 * @see getControls
565
	 */
566
	protected function createControlCollection()
567
	{
568
		return $this->getAllowChildControls() ? new TControlCollection($this) : new TEmptyControlCollection($this);
569
	}
570
571
	/**
572
	 * Checks if a control is visible.
573
	 * If parent check is required, then a control is visible only if the control
574
	 * and all its ancestors are visible.
575
	 * @param bool $checkParents whether the parents should also be checked if visible
576
	 * @return bool whether the control is visible (default=true).
577
	 */
578
	public function getVisible($checkParents = true)
579
	{
580
		if ($checkParents) {
581
			for ($control = $this; $control; $control = $control->_parent) {
582
				if (!$control->getVisible(false)) {
583
					return false;
584
				}
585
			}
586
			return true;
587
		} else {
588
			return $this->getViewState('Visible', true);
589
		}
590
	}
591
592
	/**
593
	 * @param bool $value whether the control is visible
594
	 */
595
	public function setVisible($value)
596
	{
597
		$this->setViewState('Visible', TPropertyValue::ensureBoolean($value), true);
598
	}
599
600
	/**
601
	 * Returns a value indicating whether the control is enabled.
602
	 * A control is enabled if it allows client user interaction.
603
	 * If $checkParents is true, all parent controls will be checked,
604
	 * and unless they are all enabled, false will be returned.
605
	 * The property Enabled is mainly used for {@link TWebControl}
606
	 * derived controls.
607
	 * @param bool $checkParents whether the parents should also be checked enabled
608
	 * @return bool whether the control is enabled.
609
	 */
610
	public function getEnabled($checkParents = false)
611
	{
612
		if ($checkParents) {
613
			for ($control = $this; $control; $control = $control->_parent) {
614
				if (!$control->getViewState('Enabled', true)) {
615
					return false;
616
				}
617
			}
618
			return true;
619
		} else {
620
			return $this->getViewState('Enabled', true);
621
		}
622
	}
623
624
	/**
625
	 * @param bool $value whether the control is to be enabled.
626
	 */
627
	public function setEnabled($value)
628
	{
629
		$this->setViewState('Enabled', TPropertyValue::ensureBoolean($value), true);
630
	}
631
632
	/**
633
	 * @return bool whether the control has custom attributes
634
	 */
635
	public function getHasAttributes()
636
	{
637
		if ($attributes = $this->getViewState('Attributes', null)) {
638
			return $attributes->getCount() > 0;
639
		} else {
640
			return false;
641
		}
642
	}
643
644
	/**
645
	 * Returns the list of custom attributes.
646
	 * Custom attributes are name-value pairs that may be rendered
647
	 * as HTML tags' attributes.
648
	 * @return TAttributeCollection the list of custom attributes
649
	 */
650
	public function getAttributes()
651
	{
652
		if ($attributes = $this->getViewState('Attributes', null)) {
653
			return $attributes;
654
		} else {
655
			$attributes = new TAttributeCollection;
656
			$this->setViewState('Attributes', $attributes, null);
657
			return $attributes;
658
		}
659
	}
660
661
	/**
662
	 * @param mixed $name
663
	 * @return bool whether the named attribute exists
664
	 */
665
	public function hasAttribute($name)
666
	{
667
		if ($attributes = $this->getViewState('Attributes', null)) {
668
			return $attributes->contains($name);
669
		} else {
670
			return false;
671
		}
672
	}
673
674
	/**
675
	 * @param mixed $name
676
	 * @return string attribute value, null if attribute does not exist
677
	 */
678
	public function getAttribute($name)
679
	{
680
		if ($attributes = $this->getViewState('Attributes', null)) {
681
			return $attributes->itemAt($name);
682
		} else {
683
			return null;
684
		}
685
	}
686
687
	/**
688
	 * Sets a custom control attribute.
689
	 * @param string $name attribute name
690
	 * @param string $value value of the attribute
691
	 */
692
	public function setAttribute($name, $value)
693
	{
694
		$this->getAttributes()->add($name, $value);
695
	}
696
697
	/**
698
	 * Removes the named attribute.
699
	 * @param string $name the name of the attribute to be removed.
700
	 * @return string attribute value removed, null if attribute does not exist.
701
	 */
702
	public function removeAttribute($name)
703
	{
704
		if ($attributes = $this->getViewState('Attributes', null)) {
705
			return $attributes->remove($name);
706
		} else {
707
			return null;
708
		}
709
	}
710
711
	/**
712
	 * @param mixed $checkParents
713
	 * @return bool whether viewstate is enabled
714
	 */
715
	public function getEnableViewState($checkParents = false)
716
	{
717
		if ($checkParents) {
718
			for ($control = $this; $control !== null; $control = $control->getParent()) {
719
				if ($control->_flags & self::IS_DISABLE_VIEWSTATE) {
720
					return false;
721
				}
722
			}
723
			return true;
724
		} else {
725
			return !($this->_flags & self::IS_DISABLE_VIEWSTATE);
726
		}
727
	}
728
729
	/**
730
	 * @param bool $value set whether to enable viewstate
731
	 */
732
	public function setEnableViewState($value)
733
	{
734
		if (TPropertyValue::ensureBoolean($value)) {
735
			$this->_flags &= ~self::IS_DISABLE_VIEWSTATE;
736
		} else {
737
			$this->_flags |= self::IS_DISABLE_VIEWSTATE;
738
		}
739
	}
740
741
	/**
742
	 * Returns a controlstate value.
743
	 *
744
	 * This function is mainly used in defining getter functions for control properties
745
	 * that must be kept in controlstate.
746
	 * @param string $key the name of the controlstate value to be returned
747
	 * @param mixed $defaultValue the default value. If $key is not found in controlstate, $defaultValue will be returned
748
	 * @return mixed the controlstate value corresponding to $key
749
	 */
750
	protected function getControlState($key, $defaultValue = null)
751
	{
752
		return $this->_rf[self::RF_CONTROLSTATE][$key] ?? $defaultValue;
753
	}
754
755
	/**
756
	 * Sets a controlstate value.
757
	 *
758
	 * This function is very useful in defining setter functions for control properties
759
	 * that must be kept in controlstate.
760
	 * Make sure that the controlstate value must be serializable and unserializable.
761
	 * @param string $key the name of the controlstate value
762
	 * @param mixed $value the controlstate value to be set
763
	 * @param null|mixed $defaultValue default value. If $value===$defaultValue, the item will be cleared from controlstate
764
	 */
765
	protected function setControlState($key, $value, $defaultValue = null)
766
	{
767
		if ($value === $defaultValue) {
768
			unset($this->_rf[self::RF_CONTROLSTATE][$key]);
769
		} else {
770
			$this->_rf[self::RF_CONTROLSTATE][$key] = $value;
771
		}
772
	}
773
774
	/**
775
	 * Clears a controlstate value.
776
	 * @param string $key the name of the controlstate value to be cleared
777
	 */
778
	protected function clearControlState($key)
779
	{
780
		unset($this->_rf[self::RF_CONTROLSTATE][$key]);
781
	}
782
783
	/**
784
	 * Sets a value indicating whether we should keep data in viewstate.
785
	 * When it is false, data saved via setViewState() will not be persisted.
786
	 * By default, it is true, meaning data will be persisted across postbacks.
787
	 * @param bool $enabled whether data should be persisted
788
	 */
789
	public function trackViewState($enabled)
790
	{
791 14
		$this->_trackViewState = TPropertyValue::ensureBoolean($enabled);
792
	}
793 14
794 12
	/**
795 7
	 * Returns a viewstate value.
796
	 *
797
	 * This function is very useful in defining getter functions for component properties
798
	 * that must be kept in viewstate.
799
	 * @param string $key the name of the viewstate value to be returned
800
	 * @param mixed $defaultValue the default value. If $key is not found in viewstate, $defaultValue will be returned
801 7
	 * @return mixed the viewstate value corresponding to $key
802
	 */
803
	public function getViewState($key, $defaultValue = null)
804
	{
805
		if (isset($this->_viewState[$key])) {
806
			return $this->_viewState[$key] !== null ? $this->_viewState[$key] : $defaultValue;
807
		} elseif (isset($this->_tempState[$key])) {
808
			if (is_object($this->_tempState[$key]) && $this->_trackViewState) {
809
				$this->_viewState[$key] = $this->_tempState[$key];
810
			}
811
			return $this->_tempState[$key];
812
		} else {
813
			return $defaultValue;
814
		}
815 13
	}
816
817 13
	/**
818 13
	 * Sets a viewstate value.
819 13
	 *
820
	 * This function is very useful in defining setter functions for control properties
821
	 * that must be kept in viewstate.
822
	 * Make sure that the viewstate value must be serializable and unserializable.
823
	 * @param string $key the name of the viewstate value
824
	 * @param mixed $value the viewstate value to be set
825
	 * @param null|mixed $defaultValue default value. If $value===$defaultValue, the item will be cleared from the viewstate.
826
	 */
827
	public function setViewState($key, $value, $defaultValue = null)
828 13
	{
829
		if ($this->_trackViewState) {
830
			unset($this->_tempState[$key]);
831
			$this->_viewState[$key] = $value;
832
		} else {
833
			unset($this->_viewState[$key]);
834 1
			if ($value === $defaultValue) {
835
				unset($this->_tempState[$key]);
836 1
			} else {
837 1
				$this->_tempState[$key] = $value;
838 1
			}
839
		}
840
	}
841
842
	/**
843
	 * Clears a viewstate value.
844
	 * @param string $key the name of the viewstate value to be cleared
845
	 */
846
	public function clearViewState($key)
847
	{
848
		unset($this->_viewState[$key]);
849
		unset($this->_tempState[$key]);
850
	}
851
852
	/**
853
	 * Sets up the binding between a property (or property path) and an expression.
854
	 * The context of the expression is the template control (or the control itself if it is a page).
855
	 * @param string $name the property name, or property path
856
	 * @param string $expression the expression
857
	 */
858
	public function bindProperty($name, $expression)
859
	{
860
		$this->_rf[self::RF_DATA_BINDINGS][$name] = $expression;
861
	}
862
863
	/**
864
	 * Breaks the binding between a property (or property path) and an expression.
865
	 * @param string $name the property name (or property path)
866
	 */
867
	public function unbindProperty($name)
868
	{
869
		unset($this->_rf[self::RF_DATA_BINDINGS][$name]);
870
	}
871
872
	/**
873
	 * Sets up the binding between a property (or property path) and an expression.
874
	 * Unlike regular databinding, the expression bound by this method
875
	 * is automatically evaluated during {@link prerenderRecursive()}.
876
	 * The context of the expression is the template control (or the control itself if it is a page).
877
	 * @param string $name the property name, or property path
878
	 * @param string $expression the expression
879
	 */
880
	public function autoBindProperty($name, $expression)
881
	{
882
		$this->_rf[self::RF_AUTO_BINDINGS][$name] = $expression;
883
	}
884
885
	/**
886 1
	 * Performs the databinding for this control.
887
	 */
888 1
	public function dataBind()
889 1
	{
890
		$this->dataBindProperties();
891
		$this->onDataBinding(null);
892
		$this->dataBindChildren();
893
	}
894
895
	/**
896
	 * Databinding properties of the control.
897 1
	 */
898
	protected function dataBindProperties()
899
	{
900
		Prado::trace("Data bind properties", 'Prado\Web\UI\TControl');
901
		if (isset($this->_rf[self::RF_DATA_BINDINGS])) {
902
			if (($context = $this->getTemplateControl()) === null) {
903
				$context = $this;
904
			}
905
			foreach ($this->_rf[self::RF_DATA_BINDINGS] as $property => $expression) {
906
				$this->setSubProperty($property, $context->evaluateExpression($expression));
907
			}
908
		}
909
	}
910
911
	/**
912
	 * Auto databinding properties of the control.
913
	 */
914
	protected function autoDataBindProperties()
915
	{
916
		if (isset($this->_rf[self::RF_AUTO_BINDINGS])) {
917
			if (($context = $this->getTemplateControl()) === null) {
918
				$context = $this;
919
			}
920
			foreach ($this->_rf[self::RF_AUTO_BINDINGS] as $property => $expression) {
921
				$this->setSubProperty($property, $context->evaluateExpression($expression));
922
			}
923
		}
924
	}
925
926
	/**
927
	 * Databinding child controls.
928
	 */
929
	protected function dataBindChildren()
930
	{
931
		Prado::trace("dataBindChildren()", 'Prado\Web\UI\TControl');
932
		if (isset($this->_rf[self::RF_CONTROLS])) {
933
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
934
				if ($control instanceof IBindable) {
935
					$control->dataBind();
936
				}
937
			}
938
		}
939
	}
940
941
	/**
942
	 * @return bool whether child controls have been created
943
	 */
944
	final protected function getChildControlsCreated()
945
	{
946
		return ($this->_flags & self::IS_CHILD_CREATED) !== 0;
947
	}
948
949
	/**
950
	 * Sets a value indicating whether child controls are created.
951
	 * If false, any existing child controls will be cleared up.
952
	 * @param bool $value whether child controls are created
953
	 */
954
	final protected function setChildControlsCreated($value)
955
	{
956
		if ($value) {
957
			$this->_flags |= self::IS_CHILD_CREATED;
958
		} else {
959
			if ($this->getHasControls() && ($this->_flags & self::IS_CHILD_CREATED)) {
960
				$this->getControls()->clear();
961
			}
962
			$this->_flags &= ~self::IS_CHILD_CREATED;
963
		}
964
	}
965
966
	/**
967
	 * Ensures child controls are created.
968
	 * If child controls are not created yet, this method will invoke
969
	 * {@link createChildControls} to create them.
970
	 */
971
	public function ensureChildControls()
972
	{
973
		if (!($this->_flags & self::IS_CHILD_CREATED) && !($this->_flags & self::IS_CREATING_CHILD)) {
974
			try {
975
				$this->_flags |= self::IS_CREATING_CHILD;
976
				if (isset($this->_rf[self::RF_ADAPTER])) {
977
					$this->_rf[self::RF_ADAPTER]->createChildControls();
978
				} else {
979
					$this->createChildControls();
980
				}
981
				$this->_flags &= ~self::IS_CREATING_CHILD;
982
				$this->_flags |= self::IS_CHILD_CREATED;
983
			} catch (Exception $e) {
984
				$this->_flags &= ~self::IS_CREATING_CHILD;
985
				$this->_flags |= self::IS_CHILD_CREATED;
986
				throw $e;
987
			}
988
		}
989
	}
990
991
	/**
992
	 * Creates child controls.
993
	 * This method can be overriden for controls who want to have their controls.
994
	 * Do not call this method directly. Instead, call {@link ensureChildControls}
995
	 * to ensure child controls are created only once.
996
	 */
997
	public function createChildControls()
998
	{
999
	}
1000
1001
	/**
1002
	 * Finds a control by ID path within the current naming container.
1003
	 * The current naming container is either the control itself
1004
	 * if it implements {@link INamingContainer} or the control's naming container.
1005
	 * The ID path is an ID sequence separated by {@link TControl::ID_SEPARATOR}.
1006
	 * For example, 'Repeater1.Item1.Button1' looks for a control with ID 'Button1'
1007
	 * whose naming container is 'Item1' whose naming container is 'Repeater1'.
1008
	 * @param string $id ID of the control to be looked up
1009
	 * @throws TInvalidDataValueException if a control's ID is found not unique within its naming container.
1010
	 * @return null|mixed the control found, null if not found
1011
	 */
1012
	public function findControl($id)
1013
	{
1014
		$id = strtr($id, '.', self::ID_SEPARATOR);
1015
		$container = ($this instanceof INamingContainer) ? $this : $this->getNamingContainer();
1016
		if (!$container || !$container->getHasControls()) {
1017
			return null;
1018
		}
1019
		if (!isset($container->_rf[self::RF_NAMED_CONTROLS])) {
1020
			$container->_rf[self::RF_NAMED_CONTROLS] = [];
1021
			$container->fillNameTable($container, $container->_rf[self::RF_CONTROLS]);
1022
		}
1023
		if (($pos = strpos($id, self::ID_SEPARATOR)) === false) {
1024
			return $container->_rf[self::RF_NAMED_CONTROLS][$id] ?? null;
1025
		} else {
1026
			$cid = substr($id, 0, $pos);
1027
			$sid = substr($id, $pos + 1);
1028
			if (isset($container->_rf[self::RF_NAMED_CONTROLS][$cid])) {
1029
				return $container->_rf[self::RF_NAMED_CONTROLS][$cid]->findControl($sid);
1030
			} else {
1031
				return null;
1032
			}
1033
		}
1034
	}
1035
1036
	/**
1037
	 * Finds all child and grand-child controls that are of the specified type.
1038
	 * @param string $type the class name
1039
	 * @param bool $strict whether the type comparison is strict or not. If false, controls of the parent classes of the specified class will also be returned.
1040
	 * @return array list of controls found
1041
	 */
1042
	public function findControlsByType($type, $strict = true)
1043
	{
1044
		$controls = [];
1045
		if ($this->getHasControls()) {
1046
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1047
				if (is_object($control) && (get_class($control) === $type || (!$strict && ($control instanceof $type)))) {
1048
					$controls[] = $control;
1049
				}
1050
				if (($control instanceof TControl) && $control->getHasControls()) {
1051
					$controls = array_merge($controls, $control->findControlsByType($type, $strict));
1052
				}
1053
			}
1054
		}
1055
		return $controls;
1056
	}
1057
1058
	/**
1059
	 * Finds all child and grand-child controls with the specified ID.
1060
	 * Note, this method is different from {@link findControl} in that
1061
	 * it searches through all controls that have this control as the ancestor
1062
	 * while {@link findcontrol} only searches through controls that have this
1063
	 * control as the direct naming container.
1064
	 * @param string $id the ID being looked for
1065
	 * @return array list of controls found
1066
	 */
1067
	public function findControlsByID($id)
1068
	{
1069
		$controls = [];
1070
		if ($this->getHasControls()) {
1071
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1072
				if ($control instanceof TControl) {
1073
					if ($control->_id === $id) {
1074
						$controls[] = $control;
1075
					}
1076
					$controls = array_merge($controls, $control->findControlsByID($id));
1077
				}
1078
			}
1079
		}
1080
		return $controls;
1081
	}
1082
1083
	/**
1084
	 * Resets the control as a naming container.
1085
	 * Only framework developers should use this method.
1086
	 */
1087
	public function clearNamingContainer()
1088
	{
1089
		unset($this->_rf[self::RF_NAMED_CONTROLS_ID]);
1090
		$this->clearNameTable();
1091
	}
1092
1093
	/**
1094
	 * Registers an object by a name.
1095
	 * A registered object can be accessed like a public member variable.
1096
	 * This method should only be used by framework and control developers.
1097
	 * @param string $name name of the object
1098
	 * @param object $object object to be declared
1099
	 * @see __get
1100
	 */
1101
	public function registerObject($name, $object)
1102
	{
1103
		if (isset($this->_rf[self::RF_NAMED_OBJECTS][$name])) {
1104
			throw new TInvalidOperationException('control_object_reregistered', $name);
1105
		}
1106
		$this->_rf[self::RF_NAMED_OBJECTS][$name] = $object;
1107
	}
1108
1109
	/**
1110
	 * Unregisters an object by name.
1111
	 * @param string $name name of the object
1112
	 * @see registerObject
1113
	 */
1114
	public function unregisterObject($name)
1115
	{
1116
		unset($this->_rf[self::RF_NAMED_OBJECTS][$name]);
1117
	}
1118
1119
	/**
1120 1
	 * @see registerObject
1121
	 * @param mixed $name
1122 1
	 * @return bool whether an object has been registered with the name
1123
	 */
1124
	public function isObjectRegistered($name)
1125
	{
1126
		return isset($this->_rf[self::RF_NAMED_OBJECTS][$name]);
1127
	}
1128
1129
	/**
1130
	 * @return bool true if the child control has been initialized.
1131
	 */
1132
	public function getHasChildInitialized()
1133
	{
1134
		return $this->getControlStage() >= self::CS_CHILD_INITIALIZED;
1135
	}
1136
1137
	/**
1138
	 * @return bool true if the onInit event has raised.
1139
	 */
1140
	public function getHasInitialized()
1141
	{
1142
		return $this->getControlStage() >= self::CS_INITIALIZED;
1143
	}
1144
1145
	/**
1146
	 * @return bool true if the control has loaded post data.
1147
	 */
1148
	public function getHasLoadedPostData()
1149
	{
1150
		return $this->getControlStage() >= self::CS_STATE_LOADED;
1151
	}
1152
1153
	/**
1154
	 * @return bool true if the onLoad event has raised.
1155
	 */
1156
	public function getHasLoaded()
1157
	{
1158
		return $this->getControlStage() >= self::CS_LOADED;
1159
	}
1160
1161
	/**
1162
	 * @return bool true if onPreRender event has raised.
1163
	 */
1164
	public function getHasPreRendered()
1165
	{
1166
		return $this->getControlStage() >= self::CS_PRERENDERED;
1167
	}
1168
1169
	/**
1170
	 * Returns the named registered object.
1171
	 * A component with explicit ID on a template will be registered to
1172
	 * the template owner. This method allows you to obtain this component
1173
	 * with the ID.
1174
	 * @param mixed $name
1175
	 * @return mixed the named registered object. Null if object is not found.
1176
	 */
1177
	public function getRegisteredObject($name)
1178
	{
1179
		return $this->_rf[self::RF_NAMED_OBJECTS][$name] ?? null;
1180
	}
1181
1182
	/**
1183
	 * @return bool whether body contents are allowed for this control. Defaults to true.
1184
	 */
1185
	public function getAllowChildControls()
1186
	{
1187
		return true;
1188
	}
1189
1190
	/**
1191
	 * Adds the object instantiated on a template to the child control collection.
1192
	 * This method overrides the parent implementation.
1193
	 * Only framework developers and control developers should use this method.
1194
	 * @param \Prado\TComponent|string $object text string or component parsed and instantiated in template
1195
	 * @see createdOnTemplate
1196
	 */
1197
	public function addParsedObject($object)
1198
	{
1199
		$this->getControls()->add($object);
1200
	}
1201
1202
	/**
1203
	 * Clears up the child state data.
1204
	 * After a control loads its state, those state that do not belong to
1205
	 * any existing child controls are stored as child state.
1206
	 * This method will remove these state.
1207
	 * Only frameworker developers and control developers should use this method.
1208
	 */
1209
	final protected function clearChildState()
1210
	{
1211
		unset($this->_rf[self::RF_CHILD_STATE]);
1212
	}
1213
1214
	/**
1215
	 * @param \Prado\Web\UI\TControl $ancestor the potential ancestor control
1216
	 * @return bool if the control is a descendent (parent, parent of parent, etc.)
1217
	 * of the specified control
1218
	 */
1219
	final protected function isDescendentOf($ancestor)
1220
	{
1221
		$control = $this;
1222
		while ($control !== $ancestor && $control->_parent) {
1223
			$control = $control->_parent;
1224
		}
1225
		return $control === $ancestor;
1226
	}
1227
1228
	/**
1229
	 * Adds a control into the child collection of the control.
1230
	 * Control lifecycles will be caught up during the addition.
1231
	 * Only framework developers should use this method.
1232
	 * @param \Prado\Web\UI\TControl $control the new child control
1233
	 */
1234
	public function addedControl($control)
1235
	{
1236
		if ($control->_parent) {
1237
			$control->_parent->getControls()->remove($control);
1238
		}
1239
		$control->_parent = $this;
1240
		$control->_page = $this->getPage();
1241
		$namingContainer = ($this instanceof INamingContainer) ? $this : $this->_namingContainer;
1242
		if ($namingContainer) {
1243
			$control->_namingContainer = $namingContainer;
1244
			if ($control->_id === '') {
1245
				$control->generateAutomaticID();
1246
			} else {
1247
				$namingContainer->clearNameTable();
1248
			}
1249
			$control->clearCachedUniqueID($control instanceof INamingContainer);
1250
		}
1251
1252
		if ($this->_stage >= self::CS_CHILD_INITIALIZED) {
1253
			$control->initRecursive($namingContainer);
1254
			if ($this->_stage >= self::CS_STATE_LOADED) {
1255
				if (isset($this->_rf[self::RF_CHILD_STATE][$control->_id])) {
1256
					$state = $this->_rf[self::RF_CHILD_STATE][$control->_id];
1257
					unset($this->_rf[self::RF_CHILD_STATE][$control->_id]);
1258
				} else {
1259
					$state = null;
1260
				}
1261
				$control->loadStateRecursive($state, !($this->_flags & self::IS_DISABLE_VIEWSTATE));
1262
				if ($this->_stage >= self::CS_LOADED) {
1263
					$control->loadRecursive();
1264
					if ($this->_stage >= self::CS_PRERENDERED) {
1265
						$control->preRenderRecursive();
1266
					}
1267
				}
1268
			}
1269
		}
1270
	}
1271
1272
	/**
1273
	 * Removes a control from the child collection of the control.
1274
	 * Only framework developers should use this method.
1275
	 * @param \Prado\Web\UI\TControl $control the child control removed
1276
	 */
1277
	public function removedControl($control)
1278
	{
1279
		if ($this->_namingContainer) {
1280
			$this->_namingContainer->clearNameTable();
1281
		}
1282
		$control->unloadRecursive();
1283
		$control->_parent = null;
1284
		$control->_page = null;
1285
		$control->_namingContainer = null;
1286
		$control->_tplControl = null;
1287
		//$control->_stage=self::CS_CONSTRUCTED;
1288
		if (!($control->_flags & self::IS_ID_SET)) {
1289
			$control->_id = '';
1290
		} else {
1291
			unset($this->_rf[self::RF_NAMED_OBJECTS][$control->_id]);
1292
		}
1293
		$control->clearCachedUniqueID(true);
1294
	}
1295
1296
	/**
1297
	 * Performs the Init step for the control and all its child controls.
1298
	 * Only framework developers should use this method.
1299
	 * @param \Prado\Web\UI\TControl $namingContainer the naming container control
1300
	 */
1301
	protected function initRecursive($namingContainer = null)
1302
	{
1303
		$this->ensureChildControls();
1304
		if ($this->getHasControls()) {
1305
			if ($this instanceof INamingContainer) {
1306
				$namingContainer = $this;
1307
			}
1308
			$page = $this->getPage();
1309
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1310
				if ($control instanceof TControl) {
1311
					$control->_namingContainer = $namingContainer;
1312
					$control->_page = $page;
1313
					if ($control->_id === '' && $namingContainer) {
1314
						$control->generateAutomaticID();
1315
					}
1316
					$control->initRecursive($namingContainer);
1317
				}
1318
			}
1319
		}
1320
		if ($this->_stage < self::CS_INITIALIZED) {
1321
			$this->_stage = self::CS_CHILD_INITIALIZED;
1322
			if (($page = $this->getPage()) && $this->getEnableTheming() && !($this->_flags & self::IS_SKIN_APPLIED)) {
1323
				$page->applyControlSkin($this);
1324
				$this->_flags |= self::IS_SKIN_APPLIED;
1325
			}
1326
			if (isset($this->_rf[self::RF_ADAPTER])) {
1327
				$this->_rf[self::RF_ADAPTER]->onInit(null);
1328
			} else {
1329
				$this->onInit(null);
1330
			}
1331
			$this->_stage = self::CS_INITIALIZED;
1332
		}
1333
	}
1334
1335
	/**
1336
	 * Performs the Load step for the control and all its child controls.
1337
	 * Only framework developers should use this method.
1338
	 */
1339
	protected function loadRecursive()
1340
	{
1341
		if ($this->_stage < self::CS_LOADED) {
1342
			if (isset($this->_rf[self::RF_ADAPTER])) {
1343
				$this->_rf[self::RF_ADAPTER]->onLoad(null);
1344
			} else {
1345
				$this->onLoad(null);
1346
			}
1347
		}
1348
		if ($this->getHasControls()) {
1349
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1350
				if ($control instanceof TControl) {
1351
					$control->loadRecursive();
1352
				}
1353
			}
1354
		}
1355
		if ($this->_stage < self::CS_LOADED) {
1356
			$this->_stage = self::CS_LOADED;
1357
		}
1358
	}
1359
1360
	/**
1361
	 * Performs the PreRender step for the control and all its child controls.
1362
	 * Only framework developers should use this method.
1363
	 */
1364
	protected function preRenderRecursive()
1365
	{
1366
		$this->autoDataBindProperties();
1367
1368
		if ($this->getVisible(false)) {
1369
			if (isset($this->_rf[self::RF_ADAPTER])) {
1370
				$this->_rf[self::RF_ADAPTER]->onPreRender(null);
1371
			} else {
1372
				$this->onPreRender(null);
1373
			}
1374
			if ($this->getHasControls()) {
1375
				foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1376
					if ($control instanceof TControl) {
1377
						$control->preRenderRecursive();
1378
					} elseif ($control instanceof TCompositeLiteral) {
1379
						$control->evaluateDynamicContent();
1380
					}
1381
				}
1382
			}
1383
		}
1384
		$this->_stage = self::CS_PRERENDERED;
1385
	}
1386
1387
	/**
1388
	 * Performs the Unload step for the control and all its child controls.
1389
	 * Only framework developers should use this method.
1390
	 */
1391
	protected function unloadRecursive()
1392
	{
1393
		if (!($this->_flags & self::IS_ID_SET)) {
1394
			$this->_id = '';
1395
		}
1396
		if ($this->getHasControls()) {
1397
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1398
				if ($control instanceof TControl) {
1399
					$control->unloadRecursive();
1400
				}
1401
			}
1402
		}
1403
		if (isset($this->_rf[self::RF_ADAPTER])) {
1404
			$this->_rf[self::RF_ADAPTER]->onUnload(null);
1405
		} else {
1406
			$this->onUnload(null);
1407
		}
1408
	}
1409
1410
	/**
1411
	 * This method is invoked when the control enters 'OnInit' stage.
1412
	 * The method raises 'OnInit' event.
1413
	 * If you override this method, be sure to call the parent implementation
1414
	 * so that the event handlers can be invoked.
1415
	 * @param \Prado\TEventParameter $param event parameter to be passed to the event handlers
1416
	 */
1417
	public function onInit($param)
1418
	{
1419
		$this->raiseEvent('OnInit', $this, $param);
1420
	}
1421
1422
	/**
1423
	 * This method is invoked when the control enters 'OnLoad' stage.
1424
	 * The method raises 'OnLoad' event.
1425
	 * If you override this method, be sure to call the parent implementation
1426
	 * so that the event handlers can be invoked.
1427 1
	 * @param \Prado\TEventParameter $param event parameter to be passed to the event handlers
1428
	 */
1429 1
	public function onLoad($param)
1430 1
	{
1431 1
		$this->raiseEvent('OnLoad', $this, $param);
1432
	}
1433
1434
	/**
1435
	 * Raises 'OnDataBinding' event.
1436
	 * This method is invoked when {@link dataBind} is invoked.
1437
	 * @param \Prado\TEventParameter $param event parameter to be passed to the event handlers
1438
	 */
1439
	public function onDataBinding($param)
1440
	{
1441
		Prado::trace("onDataBinding()", 'Prado\Web\UI\TControl');
1442
		$this->raiseEvent('OnDataBinding', $this, $param);
1443
	}
1444
1445
1446
	/**
1447
	 * This method is invoked when the control enters 'OnUnload' stage.
1448
	 * The method raises 'OnUnload' event.
1449
	 * If you override this method, be sure to call the parent implementation
1450
	 * so that the event handlers can be invoked.
1451
	 * @param \Prado\TEventParameter $param event parameter to be passed to the event handlers
1452
	 */
1453
	public function onUnload($param)
1454
	{
1455
		$this->raiseEvent('OnUnload', $this, $param);
1456
	}
1457
1458
	/**
1459
	 * This method is invoked when the control enters 'OnPreRender' stage.
1460
	 * The method raises 'OnPreRender' event.
1461
	 * If you override this method, be sure to call the parent implementation
1462
	 * so that the event handlers can be invoked.
1463
	 * @param \Prado\TEventParameter $param event parameter to be passed to the event handlers
1464
	 */
1465
	public function onPreRender($param)
1466
	{
1467
		$this->raiseEvent('OnPreRender', $this, $param);
1468
	}
1469
1470
	/**
1471
	 * Invokes the parent's bubbleEvent method.
1472
	 * A control who wants to bubble an event must call this method in its onEvent method.
1473
	 * @param \Prado\Web\UI\TControl $sender sender of the event
1474
	 * @param \Prado\TEventParameter $param event parameter
1475
	 * @see bubbleEvent
1476
	 */
1477
	protected function raiseBubbleEvent($sender, $param)
1478
	{
1479
		$control = $this;
1480
		while ($control = $control->_parent) {
1481
			if ($control->bubbleEvent($sender, $param)) {
1482
				break;
1483
			}
1484
		}
1485
	}
1486
1487
	/**
1488
	 * This method responds to a bubbled event.
1489
	 * This method should be overriden to provide customized response to a bubbled event.
1490
	 * Check the type of event parameter to determine what event is bubbled currently.
1491
	 * @param \Prado\Web\UI\TControl $sender sender of the event
1492
	 * @param \Prado\TEventParameter $param event parameters
1493
	 * @return bool true if the event bubbling is handled and no more bubbling.
1494
	 * @see raiseBubbleEvent
1495
	 */
1496
	public function bubbleEvent($sender, $param)
1497
	{
1498
		return false;
1499
	}
1500
1501
	/**
1502
	 * Broadcasts an event.
1503
	 * The event will be sent to all controls on the current page hierarchy.
1504
	 * If a control defines the event, the event will be raised for the control.
1505
	 * If a control implements {@link IBroadcastEventReceiver}, its
1506
	 * {@link IBroadcastEventReceiver::broadcastEventReceived broadcastEventReceived()} method will
1507
	 * be invoked which gives the control a chance to respond to the event.
1508
	 * For example, when broadcasting event 'OnClick', all controls having 'OnClick'
1509
	 * event will have this event raised, and all controls implementing
1510
	 * {@link IBroadcastEventReceiver} will also have its
1511
	 * {@link IBroadcastEventReceiver::broadcastEventReceived broadcastEventReceived()}
1512
	 * invoked.
1513
	 * @param string $name name of the broadcast event
1514
	 * @param \Prado\Web\UI\TControl $sender sender of this event
1515
	 * @param \Prado\TEventParameter $param event parameter
1516
	 */
1517
	public function broadcastEvent($name, $sender, $param)
1518
	{
1519
		$rootControl = (($page = $this->getPage()) === null) ? $this : $page;
1520
		$rootControl->broadcastEventInternal($name, $sender, new TBroadcastEventParameter($name, $param));
1521
	}
1522
1523
	/**
1524
	 * Recursively broadcasts an event.
1525
	 * This method should only be used by framework developers.
1526
	 * @param string $name name of the broadcast event
1527
	 * @param \Prado\Web\UI\TControl $sender sender of the event
1528
	 * @param TBroadcastEventParameter $param event parameter
1529
	 */
1530
	private function broadcastEventInternal($name, $sender, $param)
1531
	{
1532
		if ($this->hasEvent($name)) {
1533
			$this->raiseEvent($name, $sender, $param->getParameter());
1534
		}
1535
		if ($this instanceof IBroadcastEventReceiver) {
1536
			$this->broadcastEventReceived($sender, $param);
0 ignored issues
show
Bug introduced by
The method broadcastEventReceived() does not exist on Prado\Web\UI\TControl. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1536
			$this->/** @scrutinizer ignore-call */ 
1537
          broadcastEventReceived($sender, $param);
Loading history...
1537
		}
1538
		if ($this->getHasControls()) {
1539
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1540
				if ($control instanceof TControl) {
1541
					$control->broadcastEventInternal($name, $sender, $param);
1542
				}
1543
			}
1544
		}
1545
	}
1546
1547
	/**
1548
	 * Traverse the whole control hierarchy rooted at this control.
1549
	 * Callback function may be invoked for each control being visited.
1550
	 * A pre-callback is invoked before traversing child controls;
1551
	 * A post-callback is invoked after traversing child controls.
1552
	 * Callback functions can be global functions or class methods.
1553
	 * They must be of the following signature:
1554
	 * <code>
1555
	 * function callback_func($control,$param) {...}
1556
	 * </code>
1557
	 * where $control refers to the control being visited and $param
1558
	 * is the parameter that is passed originally when calling this traverse function.
1559
	 *
1560
	 * @param mixed $param parameter to be passed to callbacks for each control
1561
	 * @param null|callable $preCallback callback invoked before traversing child controls. If null, it is ignored.
1562
	 * @param null|callable $postCallback callback invoked after traversing child controls. If null, it is ignored.
1563
	 */
1564
	protected function traverseChildControls($param, $preCallback = null, $postCallback = null)
1565
	{
1566
		if ($preCallback !== null) {
1567
			call_user_func($preCallback, $this, $param);
1568
		}
1569
		if ($this->getHasControls()) {
1570
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1571
				if ($control instanceof TControl) {
1572
					$control->traverseChildControls($param, $preCallback, $postCallback);
1573
				}
1574
			}
1575
		}
1576
		if ($postCallback !== null) {
1577
			call_user_func($postCallback, $this, $param);
1578
		}
1579
	}
1580
1581
	/**
1582
	 * Renders the control.
1583
	 * Only when the control is visible will the control be rendered.
1584
	 * @param \Prado\Web\UI\THtmlWriter $writer the writer used for the rendering purpose
1585
	 */
1586
	public function renderControl($writer)
1587
	{
1588
		if ($this instanceof IActiveControl || $this->getVisible(false)) {
1589
			if (isset($this->_rf[self::RF_ADAPTER])) {
1590
				$this->_rf[self::RF_ADAPTER]->render($writer);
1591
			} else {
1592
				$this->render($writer);
1593
			}
1594
		}
1595
	}
1596
1597
	/**
1598
	 * Renders the control.
1599
	 * This method is invoked by {@link renderControl} when the control is visible.
1600
	 * You can override this method to provide customized rendering of the control.
1601
	 * By default, the control simply renders all its child contents.
1602
	 * @param \Prado\Web\UI\THtmlWriter $writer the writer used for the rendering purpose
1603
	 */
1604
	public function render($writer)
1605
	{
1606
		$this->renderChildren($writer);
1607
	}
1608
1609
	/**
1610
	 * Renders the children of the control.
1611
	 * This method iterates through all child controls and static text strings
1612
	 * and renders them in order.
1613
	 * @param \Prado\Web\UI\THtmlWriter $writer the writer used for the rendering purpose
1614
	 */
1615
	public function renderChildren($writer)
1616
	{
1617
		if ($this->getHasControls()) {
1618
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1619
				if (is_string($control)) {
1620
					$writer->write($control);
1621
				} elseif ($control instanceof TControl) {
1622
					$control->renderControl($writer);
1623
				} elseif ($control instanceof IRenderable) {
1624
					$control->render($writer);
1625
				}
1626
			}
1627
		}
1628
	}
1629
1630
	/**
1631
	 * This method is invoked when control state is to be saved.
1632
	 * You can override this method to do last step state saving.
1633
	 * Parent implementation must be invoked.
1634
	 */
1635
	public function saveState()
1636
	{
1637
	}
1638
1639
	/**
1640
	 * This method is invoked right after the control has loaded its state.
1641
	 * You can override this method to initialize data from the control state.
1642
	 * Parent implementation must be invoked.
1643
	 */
1644
	public function loadState()
1645
	{
1646
	}
1647
1648
	/**
1649
	 * Loads state (viewstate and controlstate) into a control and its children.
1650
	 * This method should only be used by framework developers.
1651
	 * @param array $state the collection of the state
1652
	 * @param bool $needViewState whether the viewstate should be loaded
1653
	 */
1654
	protected function loadStateRecursive(&$state, $needViewState = true)
1655
	{
1656
		if (is_array($state)) {
0 ignored issues
show
introduced by
The condition is_array($state) is always true.
Loading history...
1657
			// A null state means the stateful properties all take default values.
1658
			// So if the state is enabled, we have to assign the null value.
1659
			$needViewState = ($needViewState && !($this->_flags & self::IS_DISABLE_VIEWSTATE));
1660
			if (isset($state[1])) {
1661
				$this->_rf[self::RF_CONTROLSTATE] = &$state[1];
1662
				unset($state[1]);
1663
			} else {
1664
				unset($this->_rf[self::RF_CONTROLSTATE]);
1665
			}
1666
			if ($needViewState) {
1667
				if (isset($state[0])) {
1668
					$this->_viewState = &$state[0];
1669
				} else {
1670
					$this->_viewState = [];
1671
				}
1672
			}
1673
			unset($state[0]);
1674
			if ($this->getHasControls()) {
1675
				foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1676
					if ($control instanceof TControl) {
1677
						if (isset($state[$control->_id])) {
1678
							$control->loadStateRecursive($state[$control->_id], $needViewState);
1679
							unset($state[$control->_id]);
1680
						}
1681
					}
1682
				}
1683
			}
1684
			if (!empty($state)) {
1685
				$this->_rf[self::RF_CHILD_STATE] = &$state;
1686
			}
1687
		}
1688
		$this->_stage = self::CS_STATE_LOADED;
1689
		if (isset($this->_rf[self::RF_ADAPTER])) {
1690
			$this->_rf[self::RF_ADAPTER]->loadState();
1691
		} else {
1692
			$this->loadState();
1693
		}
1694
	}
1695
1696
	/**
1697
	 * Saves all control state (viewstate and controlstate) as a collection.
1698
	 * This method should only be used by framework developers.
1699
	 * @param bool $needViewState whether the viewstate should be saved
1700
	 * @return array the collection of the control state (including its children's state).
1701
	 */
1702
	protected function &saveStateRecursive($needViewState = true)
1703
	{
1704
		if (isset($this->_rf[self::RF_ADAPTER])) {
1705
			$this->_rf[self::RF_ADAPTER]->saveState();
1706
		} else {
1707
			$this->saveState();
1708
		}
1709
		$needViewState = ($needViewState && !($this->_flags & self::IS_DISABLE_VIEWSTATE));
1710
		$state = [];
1711
		if ($this->getHasControls()) {
1712
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1713
				if ($control instanceof TControl) {
1714
					if (count($tmp = &$control->saveStateRecursive($needViewState))) {
1715
						$state[$control->_id] = $tmp;
1716
					}
1717
				}
1718
			}
1719
		}
1720
		if ($needViewState && !empty($this->_viewState)) {
1721
			$state[0] = &$this->_viewState;
1722
		}
1723
		if (isset($this->_rf[self::RF_CONTROLSTATE])) {
1724
			$state[1] = &$this->_rf[self::RF_CONTROLSTATE];
1725
		}
1726
		return $state;
1727
	}
1728
1729
	/**
1730
	 * Applies a stylesheet skin to a control.
1731
	 * @param TPage $page the page containing the control
1732
	 * @throws TInvalidOperationException if the stylesheet skin is applied already
1733
	 */
1734
	public function applyStyleSheetSkin($page)
1735
	{
1736
		if ($page && !($this->_flags & self::IS_STYLESHEET_APPLIED)) {
1737
			$page->applyControlStyleSheet($this);
1738
			$this->_flags |= self::IS_STYLESHEET_APPLIED;
1739
		} elseif ($this->_flags & self::IS_STYLESHEET_APPLIED) {
1740
			throw new TInvalidOperationException('control_stylesheet_applied', get_class($this));
1741
		}
1742
	}
1743
1744
	/**
1745
	 * Clears the cached UniqueID.
1746
	 * If $recursive=true, all children's cached UniqueID will be cleared as well.
1747
	 * @param bool $recursive whether the clearing is recursive.
1748
	 */
1749
	private function clearCachedUniqueID($recursive)
1750
	{
1751
		if ($recursive && $this->_uid !== null && isset($this->_rf[self::RF_CONTROLS])) {
1752
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1753
				if ($control instanceof TControl) {
1754
					$control->clearCachedUniqueID($recursive);
1755
				}
1756
			}
1757
		}
1758
		$this->_uid = null;
1759
	}
1760
1761
	/**
1762
	 * Generates an automatic ID for the control.
1763
	 */
1764
	private function generateAutomaticID()
1765
	{
1766
		$this->_flags &= ~self::IS_ID_SET;
1767
		if (!isset($this->_namingContainer->_rf[self::RF_NAMED_CONTROLS_ID])) {
1768
			$this->_namingContainer->_rf[self::RF_NAMED_CONTROLS_ID] = 0;
1769
		}
1770
		$id = $this->_namingContainer->_rf[self::RF_NAMED_CONTROLS_ID]++;
1771
		$this->_id = self::AUTOMATIC_ID_PREFIX . $id;
1772
		$this->_namingContainer->clearNameTable();
1773
	}
1774
1775
	/**
1776
	 * Clears the list of the controls whose IDs are managed by the specified naming container.
1777
	 */
1778
	private function clearNameTable()
1779
	{
1780
		unset($this->_rf[self::RF_NAMED_CONTROLS]);
1781
	}
1782
1783
	/**
1784
	 * Updates the list of the controls whose IDs are managed by the specified naming container.
1785
	 * @param \Prado\Web\UI\TControl $container the naming container
1786
	 * @param TControlCollection $controls list of controls
1787
	 * @throws TInvalidDataValueException if a control's ID is not unique within its naming container.
1788
	 */
1789
	private function fillNameTable($container, $controls)
1790
	{
1791
		foreach ($controls as $control) {
1792
			if ($control instanceof TControl) {
1793
				if ($control->_id !== '') {
1794
					if (isset($container->_rf[self::RF_NAMED_CONTROLS][$control->_id])) {
1795
						throw new TInvalidDataValueException('control_id_nonunique', get_class($control), $control->_id);
1796
					} else {
1797
						$container->_rf[self::RF_NAMED_CONTROLS][$control->_id] = $control;
1798
					}
1799
				}
1800
				if (!($control instanceof INamingContainer) && $control->getHasControls()) {
1801
					$this->fillNameTable($container, $control->_rf[self::RF_CONTROLS]);
1802
				}
1803
			}
1804
		}
1805
	}
1806
}
1807