TControl::traverseChildControls()   A
last analyzed

Complexity

Conditions 6
Paths 8

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

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

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