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

TControl::getPage()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 4
nop 0
dl 0
loc 11
rs 9.2
c 0
b 0
f 0
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
 * @copyright Copyright &copy; 2005-2016 The PRADO Group
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 * @package Prado\Web\UI
10
 */
11
12
namespace Prado\Web\UI;
13
14
use Exception;
15
use Prado\Exceptions\TInvalidDataValueException;
16
use Prado\Exceptions\TInvalidOperationException;
17
use Prado\Prado;
18
use Prado\TPropertyValue;
19
use Prado\Web\UI\ActiveControls\IActiveControl;
20
use Prado\Collections\TAttributeCollection;
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 {@link 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 {@link getParent Parent} property, and its
43
 * {@link 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 {@link 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 {@link render()} method (the method is invoked
59
 * by the framework.) Descendant control classes may override this method for
60
 * customized rendering. By default, {@link render()} invokes {@link renderChildren()}
61
 * which is responsible for rendering of children of the control.
62
 * Control's {@link 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
 * @package Prado\Web\UI
71
 * @since 3.0
72
 */
73
class TControl extends \Prado\TApplicationComponent implements IRenderable, IBindable
74
{
75
	/**
76
	 * format of control ID
77
	 */
78
	const ID_FORMAT = '/^[a-zA-Z_]\\w*$/';
79
	/**
80
	 * separator char between IDs in a UniqueID
81
	 */
82
	const ID_SEPARATOR = '$';
83
	/**
84
	 * separator char between IDs in a ClientID
85
	 */
86
	const CLIENT_ID_SEPARATOR = '_';
87
	/**
88
	 * prefix to an ID automatically generated
89
	 */
90
	const AUTOMATIC_ID_PREFIX = 'ctl';
91
92
	/**
93
	 * the stage of lifecycles that the control is currently at
94
	 */
95
	const CS_CONSTRUCTED = 0;
96
	const CS_CHILD_INITIALIZED = 1;
97
	const CS_INITIALIZED = 2;
98
	const CS_STATE_LOADED = 3;
99
	const CS_LOADED = 4;
100
	const CS_PRERENDERED = 5;
101
102
	/**
103
	 * State bits.
104
	 */
105
	const IS_ID_SET = 0x01;
106
	const IS_DISABLE_VIEWSTATE = 0x02;
107
	const IS_SKIN_APPLIED = 0x04;
108
	const IS_STYLESHEET_APPLIED = 0x08;
109
	const IS_DISABLE_THEMING = 0x10;
110
	const IS_CHILD_CREATED = 0x20;
111
	const IS_CREATING_CHILD = 0x40;
112
113
	/**
114
	 * Indexes for the rare fields.
115
	 * In order to save memory, rare fields will only be created if they are needed.
116
	 */
117
	const RF_CONTROLS = 0;			// child controls
118
	const RF_CHILD_STATE = 1;			// child state field
119
	const RF_NAMED_CONTROLS = 2;		// list of controls whose namingcontainer is this control
120
	const RF_NAMED_CONTROLS_ID = 3;	// counter for automatic id
121
	const RF_SKIN_ID = 4;				// skin ID
122
	const RF_DATA_BINDINGS = 5;		// data bindings
123
	const RF_EVENTS = 6;				// event handlers
124
	const RF_CONTROLSTATE = 7;		// controlstate
125
	const RF_NAMED_OBJECTS = 8;		// controls declared with ID on template
126
	const RF_ADAPTER = 9;				// adapter
127
	const RF_AUTO_BINDINGS = 10;		// auto data bindings
128
129
	/**
130
	 * @var string control ID
131
	 */
132
	private $_id = '';
133
	/**
134
	 * @var string control unique ID
135
	 */
136
	private $_uid;
137
	/**
138
	 * @var TControl parent of the control
139
	 */
140
	private $_parent;
141
	/**
142
	 * @var TPage page that the control resides in
143
	 */
144
	private $_page;
145
	/**
146
	 * @var TControl naming container of the control
147
	 */
148
	private $_namingContainer;
149
	/**
150
	 * @var TTemplateControl control whose template contains the control
151
	 */
152
	private $_tplControl;
153
	/**
154
	 * @var array viewstate data
155
	 */
156
	private $_viewState = [];
157
	/**
158
	 * @var array temporary state (usually set in template)
159
	 */
160
	private $_tempState = [];
161
	/**
162
	 * @var bool whether we should keep state in viewstate
163
	 */
164
	private $_trackViewState = true;
165
	/**
166
	 * @var int the current stage of the control lifecycles
167
	 */
168
	private $_stage = 0;
169
	/**
170
	 * @var int representation of the state bits
171
	 */
172
	private $_flags = 0;
173
	/**
174
	 * @var array a collection of rare control data
175
	 */
176
	private $_rf = [];
177
178
	/**
179
	 * Returns a property value by name or a control by ID.
180
	 * This overrides the parent implementation by allowing accessing
181
	 * a control via its ID using the following syntax,
182
	 * <code>
183
	 * $menuBar=$this->menuBar;
184
	 * </code>
185
	 * Note, the control must be configured in the template
186
	 * with explicit ID. If the name matches both a property and a control ID,
187
	 * the control ID will take the precedence.
188
	 *
189
	 * @param string $name the property name or control ID
190
	 * @throws TInvalidOperationException if the property is not defined.
191
	 * @return mixed the property value or the target control
192
	 * @see registerObject
193
	 */
194
	public function __get($name)
195
	{
196
		if (isset($this->_rf[self::RF_NAMED_OBJECTS][$name])) {
197
			return $this->_rf[self::RF_NAMED_OBJECTS][$name];
198
		} else {
199
			return parent::__get($name);
200
		}
201
	}
202
203
	/**
204
	 * Checks for the existance of a property value by name or a control by ID.
205
	 * This overrides the parent implementation by allowing checking for the
206
	 * existance of a control via its ID using the following syntax,
207
	 * <code>
208
	 * $menuBarExists = isset($this->menuBar);
209
	 * </code>
210
	 * Do not call this method. This is a PHP magic method that we override
211
	 * to allow using isset() to detect if a component property is set or not.
212
	 * Note, the control must be configured in the template
213
	 * with explicit ID. If the name matches both a property and a control ID,
214
	 * the control ID will take the precedence.
215
	 *
216
	 * @param string $name the property name or control ID
217
	 * @return bool wether the control or property exists
218
	 * @see __get
219
	 */
220
	public function __isset($name)
221
	{
222
		if (isset($this->_rf[self::RF_NAMED_OBJECTS][$name])) {
223
			return true;
224
		} else {
225
			return parent::__isset($name);
226
		}
227
	}
228
229
	/**
230
	 * @return bool whether there is an adapter for this control
231
	 */
232
	public function getHasAdapter()
233
	{
234
		return isset($this->_rf[self::RF_ADAPTER]);
235
	}
236
237
	/**
238
	 * @return TControlAdapter control adapter. Null if not exists.
239
	 */
240
	public function getAdapter()
241
	{
242
		return isset($this->_rf[self::RF_ADAPTER]) ? $this->_rf[self::RF_ADAPTER] : null;
243
	}
244
245
	/**
246
	 * @param TControlAdapter $adapter control adapter
247
	 */
248
	public function setAdapter(TControlAdapter $adapter)
249
	{
250
		$this->_rf[self::RF_ADAPTER] = $adapter;
251
	}
252
253
	/**
254
	 * @return TControl the parent of this control
255
	 */
256
	public function getParent()
257
	{
258
		return $this->_parent;
259
	}
260
261
	/**
262
	 * @return TControl the naming container of this control
263
	 */
264
	public function getNamingContainer()
265
	{
266
		if (!$this->_namingContainer && $this->_parent) {
267
			if ($this->_parent instanceof INamingContainer) {
268
				$this->_namingContainer = $this->_parent;
269
			} else {
270
				$this->_namingContainer = $this->_parent->getNamingContainer();
271
			}
272
		}
273
		return $this->_namingContainer;
274
	}
275
276
	/**
277
	 * @return TPage the page that contains this control
278
	 */
279
	public function getPage()
280
	{
281
		if (!$this->_page) {
282
			if ($this->_parent) {
283
				$this->_page = $this->_parent->getPage();
284
			} elseif ($this->_tplControl) {
285
				$this->_page = $this->_tplControl->getPage();
286
			}
287
		}
288
		return $this->_page;
289
	}
290
291
	/**
292
	 * Sets the page for a control.
293
	 * Only framework developers should use this method.
294
	 * @param TPage $page the page that contains this control
295
	 */
296
	public function setPage($page)
297
	{
298
		$this->_page = $page;
299
	}
300
301
	/**
302
	 * Sets the control whose template contains this control.
303
	 * Only framework developers should use this method.
304
	 * @param TTemplateControl $control the control whose template contains this control
305
	 */
306
	public function setTemplateControl($control)
307
	{
308
		$this->_tplControl = $control;
309
	}
310
311
	/**
312
	 * @return TTemplateControl the control whose template contains this control
313
	 */
314
	public function getTemplateControl()
315
	{
316
		if (!$this->_tplControl && $this->_parent) {
317
			$this->_tplControl = $this->_parent->getTemplateControl();
318
		}
319
		return $this->_tplControl;
320
	}
321
322
	/**
323
	 * @return TTemplateControl the control whose template is loaded from
324
	 * some external storage, such as file, db, and whose template ultimately
325
	 * contains this control.
326
	 */
327
	public function getSourceTemplateControl()
328
	{
329
		$control = $this;
330
		while (($control instanceof TControl) && ($control = $control->getTemplateControl()) !== null) {
331
			if (($control instanceof TTemplateControl) && $control->getIsSourceTemplateControl()) {
332
				return $control;
333
			}
334
		}
335
		return $this->getPage();
336
	}
337
338
	/**
339
	 * Gets the lifecycle step the control is currently at.
340
	 * This method should only be used by control developers.
341
	 * @return int the lifecycle step the control is currently at.
342
	 * The value can be CS_CONSTRUCTED, CS_CHILD_INITIALIZED, CS_INITIALIZED,
343
	 * CS_STATE_LOADED, CS_LOADED, CS_PRERENDERED.
344
	 */
345
	protected function getControlStage()
346
	{
347
		return $this->_stage;
348
	}
349
350
	/**
351
	 * Sets the lifecycle step the control is currently at.
352
	 * This method should only be used by control developers.
353
	 * @param int $value the lifecycle step the control is currently at.
354
	 * Valid values include CS_CONSTRUCTED, CS_CHILD_INITIALIZED, CS_INITIALIZED,
355
	 * CS_STATE_LOADED, CS_LOADED, CS_PRERENDERED.
356
	 */
357
	protected function setControlStage($value)
358
	{
359
		$this->_stage = $value;
360
	}
361
362
	/**
363
	 * Returns the id of the control.
364
	 * Control ID can be either manually set or automatically generated.
365
	 * If $hideAutoID is true, automatically generated ID will be returned as an empty string.
366
	 * @param bool $hideAutoID whether to hide automatically generated ID
367
	 * @return string the ID of the control
368
	 */
369
	public function getID($hideAutoID = true)
370
	{
371
		if ($hideAutoID) {
372
			return ($this->_flags & self::IS_ID_SET) ? $this->_id : '';
373
		} else {
374
			return $this->_id;
375
		}
376
	}
377
378
	/**
379
	 * @param string $id the new control ID. The value must consist of word characters [a-zA-Z0-9_] only
380
	 * @throws TInvalidDataValueException if ID is in a bad format
381
	 */
382
	public function setID($id)
383
	{
384
		if (!preg_match(self::ID_FORMAT, $id)) {
385
			throw new TInvalidDataValueException('control_id_invalid', get_class($this), $id);
386
		}
387
		$this->_id = $id;
388
		$this->_flags |= self::IS_ID_SET;
389
		$this->clearCachedUniqueID($this instanceof INamingContainer);
390
		if ($this->_namingContainer) {
391
			$this->_namingContainer->clearNameTable();
392
		}
393
	}
394
395
	/**
396
	 * Returns a unique ID that identifies the control in the page hierarchy.
397
	 * A unique ID is the contenation of all naming container controls' IDs and the control ID.
398
	 * These IDs are separated by '$' character.
399
	 * Control users should not rely on the specific format of UniqueID, however.
400
	 * @return string a unique ID that identifies the control in the page hierarchy
401
	 */
402
	public function getUniqueID()
403
	{
404
		if ($this->_uid === '' || $this->_uid === null) {	// need to build the UniqueID
405
			$this->_uid = '';  // set to not-null, so that clearCachedUniqueID() may take action
406
			if ($namingContainer = $this->getNamingContainer()) {
407
				if ($this->getPage() === $namingContainer) {
408
					return ($this->_uid = $this->_id);
409
				} elseif (($prefix = $namingContainer->getUniqueID()) === '') {
410
					return $this->_id;
411
				} else {
412
					return ($this->_uid = $prefix . self::ID_SEPARATOR . $this->_id);
413
				}
414
			} else {	// no naming container
415
				return $this->_id;
416
			}
417
		} else {
418
			return $this->_uid;
419
		}
420
	}
421
422
	/**
423
	 * Sets input focus to this control.
424
	 */
425
	public function focus()
426
	{
427
		$this->getPage()->setFocus($this);
428
	}
429
430
	/**
431
	 * Returns the client ID of the control.
432
	 * The client ID can be used to uniquely identify
433
	 * the control in client-side scripts (such as JavaScript).
434
	 * Do not rely on the explicit format of the return ID.
435
	 * @return string the client ID of the control
436
	 */
437
	public function getClientID()
438
	{
439
		return strtr($this->getUniqueID(), self::ID_SEPARATOR, self::CLIENT_ID_SEPARATOR);
440
	}
441
442
	/**
443
	 * Converts a unique ID to a client ID.
444
	 * @param string $uniqueID the unique ID of a control
445
	 * @return string the client ID of the control
446
	 */
447
	public static function convertUniqueIdToClientId($uniqueID)
448
	{
449
		return strtr($uniqueID, self::ID_SEPARATOR, self::CLIENT_ID_SEPARATOR);
450
	}
451
452
	/**
453
	 * @return string the skin ID of this control, '' if not set
454
	 */
455
	public function getSkinID()
456
	{
457
		return isset($this->_rf[self::RF_SKIN_ID]) ? $this->_rf[self::RF_SKIN_ID] : '';
458
	}
459
460
	/**
461
	 * @param string $value the skin ID of this control
462
	 * @throws TInvalidOperationException if the SkinID is set in a stage later than PreInit, or if the skin is applied already.
463
	 */
464
	public function setSkinID($value)
465
	{
466
		if (($this->_flags & self::IS_SKIN_APPLIED) || $this->_stage >= self::CS_CHILD_INITIALIZED) {
467
			throw new TInvalidOperationException('control_skinid_unchangeable', get_class($this));
468
		} else {
469
			$this->_rf[self::RF_SKIN_ID] = $value;
470
		}
471
	}
472
473
	/**
474
	 * @return bool if a skin is applied.
475
	 */
476
	public function getIsSkinApplied()
477
	{
478
		return ($this->_flags & self::IS_SKIN_APPLIED);
479
	}
480
481
	/**
482
	 * @return bool whether theming is enabled for this control.
483
	 * The theming is enabled if the control and all its parents have it enabled.
484
	 */
485
	public function getEnableTheming()
486
	{
487
		if ($this->_flags & self::IS_DISABLE_THEMING) {
488
			return false;
489
		} else {
490
			return $this->_parent ? $this->_parent->getEnableTheming() : true;
491
		}
492
	}
493
494
	/**
495
	 * @param bool $value whether to enable theming
496
	 * @throws TInvalidOperationException if this method is invoked after OnPreInit
497
	 */
498
	public function setEnableTheming($value)
499
	{
500
		if ($this->_stage >= self::CS_CHILD_INITIALIZED) {
501
			throw new TInvalidOperationException('control_enabletheming_unchangeable', get_class($this), $this->getUniqueID());
502
		} elseif (TPropertyValue::ensureBoolean($value)) {
503
			$this->_flags &= ~self::IS_DISABLE_THEMING;
504
		} else {
505
			$this->_flags |= self::IS_DISABLE_THEMING;
506
		}
507
	}
508
509
	/**
510
	 * Returns custom data associated with this control.
511
	 * A control may be associated with some custom data for various purposes.
512
	 * For example, a button may be associated with a string to identify itself
513
	 * in a generic OnClick event handler.
514
	 * @return mixed custom data associated with this control. Defaults to null.
515
	 */
516
	public function getCustomData()
517
	{
518
		return $this->getViewState('CustomData', null);
519
	}
520
521
	/**
522
	 * Associates custom data with this control.
523
	 * Note, the custom data must be serializable and unserializable.
524
	 * @param mixed $value custom data
525
	 */
526
	public function setCustomData($value)
527
	{
528
		$this->setViewState('CustomData', $value, null);
529
	}
530
531
	/**
532
	 * @return bool whether the control has child controls
533
	 */
534
	public function getHasControls()
535
	{
536
		return isset($this->_rf[self::RF_CONTROLS]) && $this->_rf[self::RF_CONTROLS]->getCount() > 0;
537
	}
538
539
	/**
540
	 * @return TControlCollection the child control collection
541
	 */
542
	public function getControls()
543
	{
544
		if (!isset($this->_rf[self::RF_CONTROLS])) {
545
			$this->_rf[self::RF_CONTROLS] = $this->createControlCollection();
546
		}
547
		return $this->_rf[self::RF_CONTROLS];
548
	}
549
550
	/**
551
	 * Creates a control collection object that is to be used to hold child controls
552
	 * @return TControlCollection control collection
553
	 * @see getControls
554
	 */
555
	protected function createControlCollection()
556
	{
557
		return $this->getAllowChildControls() ? new TControlCollection($this) : new TEmptyControlCollection($this);
558
	}
559
560
	/**
561
	 * Checks if a control is visible.
562
	 * If parent check is required, then a control is visible only if the control
563
	 * and all its ancestors are visible.
564
	 * @param bool $checkParents whether the parents should also be checked if visible
565
	 * @return bool whether the control is visible (default=true).
566
	 */
567 View Code Duplication
	public function getVisible($checkParents = true)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
568
	{
569
		if ($checkParents) {
570
			for ($control = $this;$control;$control = $control->_parent) {
571
				if (!$control->getVisible(false)) {
572
					return false;
573
				}
574
			}
575
			return true;
576
		} else {
577
			return $this->getViewState('Visible', true);
578
		}
579
	}
580
581
	/**
582
	 * @param bool $value whether the control is visible
583
	 */
584
	public function setVisible($value)
585
	{
586
		$this->setViewState('Visible', TPropertyValue::ensureBoolean($value), true);
587
	}
588
589
	/**
590
	 * Returns a value indicating whether the control is enabled.
591
	 * A control is enabled if it allows client user interaction.
592
	 * If $checkParents is true, all parent controls will be checked,
593
	 * and unless they are all enabled, false will be returned.
594
	 * The property Enabled is mainly used for {@link TWebControl}
595
	 * derived controls.
596
	 * @param bool $checkParents whether the parents should also be checked enabled
597
	 * @return bool whether the control is enabled.
598
	 */
599 View Code Duplication
	public function getEnabled($checkParents = false)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
600
	{
601
		if ($checkParents) {
602
			for ($control = $this;$control;$control = $control->_parent) {
603
				if (!$control->getViewState('Enabled', true)) {
604
					return false;
605
				}
606
			}
607
			return true;
608
		} else {
609
			return $this->getViewState('Enabled', true);
610
		}
611
	}
612
613
	/**
614
	 * @param bool $value whether the control is to be enabled.
615
	 */
616
	public function setEnabled($value)
617
	{
618
		$this->setViewState('Enabled', TPropertyValue::ensureBoolean($value), true);
619
	}
620
621
	/**
622
	 * @return bool whether the control has custom attributes
623
	 */
624
	public function getHasAttributes()
625
	{
626
		if ($attributes = $this->getViewState('Attributes', null)) {
627
			return $attributes->getCount() > 0;
628
		} else {
629
			return false;
630
		}
631
	}
632
633
	/**
634
	 * Returns the list of custom attributes.
635
	 * Custom attributes are name-value pairs that may be rendered
636
	 * as HTML tags' attributes.
637
	 * @return TAttributeCollection the list of custom attributes
638
	 */
639 View Code Duplication
	public function getAttributes()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
640
	{
641
		if ($attributes = $this->getViewState('Attributes', null)) {
642
			return $attributes;
643
		} else {
644
			$attributes = new TAttributeCollection;
645
			$this->setViewState('Attributes', $attributes, null);
646
			return $attributes;
647
		}
648
	}
649
650
	/**
651
	 * @param mixed $name
652
	 * @return bool whether the named attribute exists
653
	 */
654
	public function hasAttribute($name)
655
	{
656
		if ($attributes = $this->getViewState('Attributes', null)) {
657
			return $attributes->contains($name);
658
		} else {
659
			return false;
660
		}
661
	}
662
663
	/**
664
	 * @param mixed $name
665
	 * @return string attribute value, null if attribute does not exist
666
	 */
667 View Code Duplication
	public function getAttribute($name)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
668
	{
669
		if ($attributes = $this->getViewState('Attributes', null)) {
670
			return $attributes->itemAt($name);
671
		} else {
672
			return null;
673
		}
674
	}
675
676
	/**
677
	 * Sets a custom control attribute.
678
	 * @param string $name attribute name
679
	 * @param string $value value of the attribute
680
	 */
681
	public function setAttribute($name, $value)
682
	{
683
		$this->getAttributes()->add($name, $value);
684
	}
685
686
	/**
687
	 * Removes the named attribute.
688
	 * @param string $name the name of the attribute to be removed.
689
	 * @return string attribute value removed, null if attribute does not exist.
690
	 */
691 View Code Duplication
	public function removeAttribute($name)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
692
	{
693
		if ($attributes = $this->getViewState('Attributes', null)) {
694
			return $attributes->remove($name);
695
		} else {
696
			return null;
697
		}
698
	}
699
700
	/**
701
	 * @param mixed $checkParents
702
	 * @return bool whether viewstate is enabled
703
	 */
704
	public function getEnableViewState($checkParents = false)
705
	{
706
		if ($checkParents) {
707
			for ($control = $this;$control !== null;$control = $control->getParent()) {
708
				if ($control->_flags & self::IS_DISABLE_VIEWSTATE) {
709
					return false;
710
				}
711
			}
712
			return true;
713
		} else {
714
			return !($this->_flags & self::IS_DISABLE_VIEWSTATE);
715
		}
716
	}
717
718
	/**
719
	 * @param bool $value set whether to enable viewstate
720
	 */
721 View Code Duplication
	public function setEnableViewState($value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
722
	{
723
		if (TPropertyValue::ensureBoolean($value)) {
724
			$this->_flags &= ~self::IS_DISABLE_VIEWSTATE;
725
		} else {
726
			$this->_flags |= self::IS_DISABLE_VIEWSTATE;
727
		}
728
	}
729
730
	/**
731
	 * Returns a controlstate value.
732
	 *
733
	 * This function is mainly used in defining getter functions for control properties
734
	 * that must be kept in controlstate.
735
	 * @param string $key the name of the controlstate value to be returned
736
	 * @param mixed $defaultValue the default value. If $key is not found in controlstate, $defaultValue will be returned
737
	 * @return mixed the controlstate value corresponding to $key
738
	 */
739
	protected function getControlState($key, $defaultValue = null)
740
	{
741
		return isset($this->_rf[self::RF_CONTROLSTATE][$key]) ? $this->_rf[self::RF_CONTROLSTATE][$key] : $defaultValue;
742
	}
743
744
	/**
745
	 * Sets a controlstate value.
746
	 *
747
	 * This function is very useful in defining setter functions for control properties
748
	 * that must be kept in controlstate.
749
	 * Make sure that the controlstate value must be serializable and unserializable.
750
	 * @param string $key the name of the controlstate value
751
	 * @param mixed $value the controlstate value to be set
752
	 * @param null|mixed $defaultValue default value. If $value===$defaultValue, the item will be cleared from controlstate
753
	 */
754
	protected function setControlState($key, $value, $defaultValue = null)
755
	{
756
		if ($value === $defaultValue) {
757
			unset($this->_rf[self::RF_CONTROLSTATE][$key]);
758
		} else {
759
			$this->_rf[self::RF_CONTROLSTATE][$key] = $value;
760
		}
761
	}
762
763
	/**
764
	 * Clears a controlstate value.
765
	 * @param string $key the name of the controlstate value to be cleared
766
	 */
767
	protected function clearControlState($key)
768
	{
769
		unset($this->_rf[self::RF_CONTROLSTATE][$key]);
770
	}
771
772
	/**
773
	 * Sets a value indicating whether we should keep data in viewstate.
774
	 * When it is false, data saved via setViewState() will not be persisted.
775
	 * By default, it is true, meaning data will be persisted across postbacks.
776
	 * @param bool $enabled whether data should be persisted
777
	 */
778
	public function trackViewState($enabled)
779
	{
780
		$this->_trackViewState = TPropertyValue::ensureBoolean($enabled);
781
	}
782
783
	/**
784
	 * Returns a viewstate value.
785
	 *
786
	 * This function is very useful in defining getter functions for component properties
787
	 * that must be kept in viewstate.
788
	 * @param string $key the name of the viewstate value to be returned
789
	 * @param mixed $defaultValue the default value. If $key is not found in viewstate, $defaultValue will be returned
790
	 * @return mixed the viewstate value corresponding to $key
791
	 */
792
	public function getViewState($key, $defaultValue = null)
793
	{
794
		if (isset($this->_viewState[$key])) {
795
			return $this->_viewState[$key] !== null ? $this->_viewState[$key] : $defaultValue;
796
		} elseif (isset($this->_tempState[$key])) {
797
			if (is_object($this->_tempState[$key]) && $this->_trackViewState) {
798
				$this->_viewState[$key] = $this->_tempState[$key];
799
			}
800
			return $this->_tempState[$key];
801
		} else {
802
			return $defaultValue;
803
		}
804
	}
805
806
	/**
807
	 * Sets a viewstate value.
808
	 *
809
	 * This function is very useful in defining setter functions for control properties
810
	 * that must be kept in viewstate.
811
	 * Make sure that the viewstate value must be serializable and unserializable.
812
	 * @param string $key the name of the viewstate value
813
	 * @param mixed $value the viewstate value to be set
814
	 * @param null|mixed $defaultValue default value. If $value===$defaultValue, the item will be cleared from the viewstate.
815
	 */
816
	public function setViewState($key, $value, $defaultValue = null)
817
	{
818
		if ($this->_trackViewState) {
819
			unset($this->_tempState[$key]);
820
			$this->_viewState[$key] = $value;
821
		} else {
822
			unset($this->_viewState[$key]);
823
			if ($value === $defaultValue) {
824
				unset($this->_tempState[$key]);
825
			} else {
826
				$this->_tempState[$key] = $value;
827
			}
828
		}
829
	}
830
831
	/**
832
	 * Clears a viewstate value.
833
	 * @param string $key the name of the viewstate value to be cleared
834
	 */
835
	public function clearViewState($key)
836
	{
837
		unset($this->_viewState[$key]);
838
		unset($this->_tempState[$key]);
839
	}
840
841
	/**
842
	 * Sets up the binding between a property (or property path) and an expression.
843
	 * The context of the expression is the template control (or the control itself if it is a page).
844
	 * @param string $name the property name, or property path
845
	 * @param string $expression the expression
846
	 */
847
	public function bindProperty($name, $expression)
848
	{
849
		$this->_rf[self::RF_DATA_BINDINGS][$name] = $expression;
850
	}
851
852
	/**
853
	 * Breaks the binding between a property (or property path) and an expression.
854
	 * @param string $name the property name (or property path)
855
	 */
856
	public function unbindProperty($name)
857
	{
858
		unset($this->_rf[self::RF_DATA_BINDINGS][$name]);
859
	}
860
861
	/**
862
	 * Sets up the binding between a property (or property path) and an expression.
863
	 * Unlike regular databinding, the expression bound by this method
864
	 * is automatically evaluated during {@link prerenderRecursive()}.
865
	 * The context of the expression is the template control (or the control itself if it is a page).
866
	 * @param string $name the property name, or property path
867
	 * @param string $expression the expression
868
	 */
869
	public function autoBindProperty($name, $expression)
870
	{
871
		$this->_rf[self::RF_AUTO_BINDINGS][$name] = $expression;
872
	}
873
874
	/**
875
	 * Performs the databinding for this control.
876
	 */
877
	public function dataBind()
878
	{
879
		$this->dataBindProperties();
880
		$this->onDataBinding(null);
881
		$this->dataBindChildren();
882
	}
883
884
	/**
885
	 * Databinding properties of the control.
886
	 */
887 View Code Duplication
	protected function dataBindProperties()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
888
	{
889
		Prado::trace("Data bind properties", 'Prado\Web\UI\TControl');
890
		if (isset($this->_rf[self::RF_DATA_BINDINGS])) {
891
			if (($context = $this->getTemplateControl()) === null) {
892
				$context = $this;
893
			}
894
			foreach ($this->_rf[self::RF_DATA_BINDINGS] as $property => $expression) {
895
				$this->setSubProperty($property, $context->evaluateExpression($expression));
896
			}
897
		}
898
	}
899
900
	/**
901
	 * Auto databinding properties of the control.
902
	 */
903 View Code Duplication
	protected function autoDataBindProperties()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
904
	{
905
		if (isset($this->_rf[self::RF_AUTO_BINDINGS])) {
906
			if (($context = $this->getTemplateControl()) === null) {
907
				$context = $this;
908
			}
909
			foreach ($this->_rf[self::RF_AUTO_BINDINGS] as $property => $expression) {
910
				$this->setSubProperty($property, $context->evaluateExpression($expression));
911
			}
912
		}
913
	}
914
915
	/**
916
	 * Databinding child controls.
917
	 */
918
	protected function dataBindChildren()
919
	{
920
		Prado::trace("dataBindChildren()", 'Prado\Web\UI\TControl');
921
		if (isset($this->_rf[self::RF_CONTROLS])) {
922
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
923
				if ($control instanceof IBindable) {
924
					$control->dataBind();
925
				}
926
			}
927
		}
928
	}
929
930
	/**
931
	 * @return bool whether child controls have been created
932
	 */
933
	final protected function getChildControlsCreated()
934
	{
935
		return ($this->_flags & self::IS_CHILD_CREATED) !== 0;
936
	}
937
938
	/**
939
	 * Sets a value indicating whether child controls are created.
940
	 * If false, any existing child controls will be cleared up.
941
	 * @param bool $value whether child controls are created
942
	 */
943
	final protected function setChildControlsCreated($value)
944
	{
945
		if ($value) {
946
			$this->_flags |= self::IS_CHILD_CREATED;
947
		} else {
948
			if ($this->getHasControls() && ($this->_flags & self::IS_CHILD_CREATED)) {
949
				$this->getControls()->clear();
950
			}
951
			$this->_flags &= ~self::IS_CHILD_CREATED;
952
		}
953
	}
954
955
	/**
956
	 * Ensures child controls are created.
957
	 * If child controls are not created yet, this method will invoke
958
	 * {@link createChildControls} to create them.
959
	 */
960
	public function ensureChildControls()
961
	{
962
		if (!($this->_flags & self::IS_CHILD_CREATED) && !($this->_flags & self::IS_CREATING_CHILD)) {
963
			try {
964
				$this->_flags |= self::IS_CREATING_CHILD;
965
				if (isset($this->_rf[self::RF_ADAPTER])) {
966
					$this->_rf[self::RF_ADAPTER]->createChildControls();
967
				} else {
968
					$this->createChildControls();
969
				}
970
				$this->_flags &= ~self::IS_CREATING_CHILD;
971
				$this->_flags |= self::IS_CHILD_CREATED;
972
			} catch (Exception $e) {
973
				$this->_flags &= ~self::IS_CREATING_CHILD;
974
				$this->_flags |= self::IS_CHILD_CREATED;
975
				throw $e;
976
			}
977
		}
978
	}
979
980
	/**
981
	 * Creates child controls.
982
	 * This method can be overriden for controls who want to have their controls.
983
	 * Do not call this method directly. Instead, call {@link ensureChildControls}
984
	 * to ensure child controls are created only once.
985
	 */
986
	public function createChildControls()
987
	{
988
	}
989
990
	/**
991
	 * Finds a control by ID path within the current naming container.
992
	 * The current naming container is either the control itself
993
	 * if it implements {@link INamingContainer} or the control's naming container.
994
	 * The ID path is an ID sequence separated by {@link TControl::ID_SEPARATOR}.
995
	 * For example, 'Repeater1.Item1.Button1' looks for a control with ID 'Button1'
996
	 * whose naming container is 'Item1' whose naming container is 'Repeater1'.
997
	 * @param string $id ID of the control to be looked up
998
	 * @throws TInvalidDataValueException if a control's ID is found not unique within its naming container.
999
	 * @return null|TControl the control found, null if not found
1000
	 */
1001
	public function findControl($id)
1002
	{
1003
		$id = strtr($id, '.', self::ID_SEPARATOR);
1004
		$container = ($this instanceof INamingContainer) ? $this : $this->getNamingContainer();
1005
		if (!$container || !$container->getHasControls()) {
1006
			return null;
1007
		}
1008
		if (!isset($container->_rf[self::RF_NAMED_CONTROLS])) {
1009
			$container->_rf[self::RF_NAMED_CONTROLS] = [];
1010
			$container->fillNameTable($container, $container->_rf[self::RF_CONTROLS]);
1011
		}
1012
		if (($pos = strpos($id, self::ID_SEPARATOR)) === false) {
1013
			return isset($container->_rf[self::RF_NAMED_CONTROLS][$id]) ? $container->_rf[self::RF_NAMED_CONTROLS][$id] : null;
1014
		} else {
1015
			$cid = substr($id, 0, $pos);
1016
			$sid = substr($id, $pos + 1);
1017
			if (isset($container->_rf[self::RF_NAMED_CONTROLS][$cid])) {
1018
				return $container->_rf[self::RF_NAMED_CONTROLS][$cid]->findControl($sid);
1019
			} else {
1020
				return null;
1021
			}
1022
		}
1023
	}
1024
1025
	/**
1026
	 * Finds all child and grand-child controls that are of the specified type.
1027
	 * @param string $type the class name
1028
	 * @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.
1029
	 * @return array list of controls found
1030
	 */
1031
	public function findControlsByType($type, $strict = true)
1032
	{
1033
		$controls = [];
1034
		if ($this->getHasControls()) {
1035
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1036
				if (is_object($control) && (get_class($control) === $type || (!$strict && ($control instanceof $type)))) {
1037
					$controls[] = $control;
1038
				}
1039
				if (($control instanceof TControl) && $control->getHasControls()) {
1040
					$controls = array_merge($controls, $control->findControlsByType($type, $strict));
1041
				}
1042
			}
1043
		}
1044
		return $controls;
1045
	}
1046
1047
	/**
1048
	 * Finds all child and grand-child controls with the specified ID.
1049
	 * Note, this method is different from {@link findControl} in that
1050
	 * it searches through all controls that have this control as the ancestor
1051
	 * while {@link findcontrol} only searches through controls that have this
1052
	 * control as the direct naming container.
1053
	 * @param string $id the ID being looked for
1054
	 * @return array list of controls found
1055
	 */
1056
	public function findControlsByID($id)
1057
	{
1058
		$controls = [];
1059
		if ($this->getHasControls()) {
1060
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1061
				if ($control instanceof TControl) {
1062
					if ($control->_id === $id) {
1063
						$controls[] = $control;
1064
					}
1065
					$controls = array_merge($controls, $control->findControlsByID($id));
1066
				}
1067
			}
1068
		}
1069
		return $controls;
1070
	}
1071
1072
	/**
1073
	 * Resets the control as a naming container.
1074
	 * Only framework developers should use this method.
1075
	 */
1076
	public function clearNamingContainer()
1077
	{
1078
		unset($this->_rf[self::RF_NAMED_CONTROLS_ID]);
1079
		$this->clearNameTable();
1080
	}
1081
1082
	/**
1083
	 * Registers an object by a name.
1084
	 * A registered object can be accessed like a public member variable.
1085
	 * This method should only be used by framework and control developers.
1086
	 * @param string $name name of the object
1087
	 * @param object $object object to be declared
1088
	 * @see __get
1089
	 */
1090
	public function registerObject($name, $object)
1091
	{
1092
		if (isset($this->_rf[self::RF_NAMED_OBJECTS][$name])) {
1093
			throw new TInvalidOperationException('control_object_reregistered', $name);
1094
		}
1095
		$this->_rf[self::RF_NAMED_OBJECTS][$name] = $object;
1096
	}
1097
1098
	/**
1099
	 * Unregisters an object by name.
1100
	 * @param string $name name of the object
1101
	 * @see registerObject
1102
	 */
1103
	public function unregisterObject($name)
1104
	{
1105
		unset($this->_rf[self::RF_NAMED_OBJECTS][$name]);
1106
	}
1107
1108
	/**
1109
	 * @see registerObject
1110
	 * @param mixed $name
1111
	 * @return bool whether an object has been registered with the name
1112
	 */
1113
	public function isObjectRegistered($name)
1114
	{
1115
		return isset($this->_rf[self::RF_NAMED_OBJECTS][$name]);
1116
	}
1117
1118
	/**
1119
	 * @return bool true if the child control has been initialized.
1120
	 */
1121
	public function getHasChildInitialized()
1122
	{
1123
		return $this->getControlStage() >= self::CS_CHILD_INITIALIZED;
1124
	}
1125
1126
	/**
1127
	 * @return bool true if the onInit event has raised.
1128
	 */
1129
	public function getHasInitialized()
1130
	{
1131
		return $this->getControlStage() >= self::CS_INITIALIZED;
1132
	}
1133
1134
	/**
1135
	 * @return bool true if the control has loaded post data.
1136
	 */
1137
	public function getHasLoadedPostData()
1138
	{
1139
		return $this->getControlStage() >= self::CS_STATE_LOADED;
1140
	}
1141
1142
	/**
1143
	 * @return bool true if the onLoad event has raised.
1144
	 */
1145
	public function getHasLoaded()
1146
	{
1147
		return $this->getControlStage() >= self::CS_LOADED;
1148
	}
1149
1150
	/**
1151
	 * @return bool true if onPreRender event has raised.
1152
	 */
1153
	public function getHasPreRendered()
1154
	{
1155
		return $this->getControlStage() >= self::CS_PRERENDERED;
1156
	}
1157
1158
	/**
1159
	 * Returns the named registered object.
1160
	 * A component with explicit ID on a template will be registered to
1161
	 * the template owner. This method allows you to obtain this component
1162
	 * with the ID.
1163
	 * @param mixed $name
1164
	 * @return mixed the named registered object. Null if object is not found.
1165
	 */
1166
	public function getRegisteredObject($name)
1167
	{
1168
		return isset($this->_rf[self::RF_NAMED_OBJECTS][$name]) ? $this->_rf[self::RF_NAMED_OBJECTS][$name] : null;
1169
	}
1170
1171
	/**
1172
	 * @return bool whether body contents are allowed for this control. Defaults to true.
1173
	 */
1174
	public function getAllowChildControls()
1175
	{
1176
		return true;
1177
	}
1178
1179
	/**
1180
	 * Adds the object instantiated on a template to the child control collection.
1181
	 * This method overrides the parent implementation.
1182
	 * Only framework developers and control developers should use this method.
1183
	 * @param string|TComponent $object text string or component parsed and instantiated in template
1184
	 * @see createdOnTemplate
1185
	 */
1186
	public function addParsedObject($object)
1187
	{
1188
		$this->getControls()->add($object);
1189
	}
1190
1191
	/**
1192
	 * Clears up the child state data.
1193
	 * After a control loads its state, those state that do not belong to
1194
	 * any existing child controls are stored as child state.
1195
	 * This method will remove these state.
1196
	 * Only frameworker developers and control developers should use this method.
1197
	 */
1198
	final protected function clearChildState()
1199
	{
1200
		unset($this->_rf[self::RF_CHILD_STATE]);
1201
	}
1202
1203
	/**
1204
	 * @param TControl $ancestor the potential ancestor control
1205
	 * @return bool if the control is a descendent (parent, parent of parent, etc.)
1206
	 * of the specified control
1207
	 */
1208
	final protected function isDescendentOf($ancestor)
1209
	{
1210
		$control = $this;
1211
		while ($control !== $ancestor && $control->_parent) {
1212
			$control = $control->_parent;
1213
		}
1214
		return $control === $ancestor;
1215
	}
1216
1217
	/**
1218
	 * Adds a control into the child collection of the control.
1219
	 * Control lifecycles will be caught up during the addition.
1220
	 * Only framework developers should use this method.
1221
	 * @param TControl $control the new child control
1222
	 */
1223
	public function addedControl($control)
1224
	{
1225
		if ($control->_parent) {
1226
			$control->_parent->getControls()->remove($control);
1227
		}
1228
		$control->_parent = $this;
1229
		$control->_page = $this->getPage();
1230
		$namingContainer = ($this instanceof INamingContainer) ? $this : $this->_namingContainer;
1231
		if ($namingContainer) {
1232
			$control->_namingContainer = $namingContainer;
1233
			if ($control->_id === '') {
1234
				$control->generateAutomaticID();
1235
			} else {
1236
				$namingContainer->clearNameTable();
1237
			}
1238
			$control->clearCachedUniqueID($control instanceof INamingContainer);
1239
		}
1240
1241
		if ($this->_stage >= self::CS_CHILD_INITIALIZED) {
1242
			$control->initRecursive($namingContainer);
1243
			if ($this->_stage >= self::CS_STATE_LOADED) {
1244
				if (isset($this->_rf[self::RF_CHILD_STATE][$control->_id])) {
1245
					$state = $this->_rf[self::RF_CHILD_STATE][$control->_id];
1246
					unset($this->_rf[self::RF_CHILD_STATE][$control->_id]);
1247
				} else {
1248
					$state = null;
1249
				}
1250
				$control->loadStateRecursive($state, !($this->_flags & self::IS_DISABLE_VIEWSTATE));
1251
				if ($this->_stage >= self::CS_LOADED) {
1252
					$control->loadRecursive();
1253
					if ($this->_stage >= self::CS_PRERENDERED) {
1254
						$control->preRenderRecursive();
1255
					}
1256
				}
1257
			}
1258
		}
1259
	}
1260
1261
	/**
1262
	 * Removes a control from the child collection of the control.
1263
	 * Only framework developers should use this method.
1264
	 * @param TControl $control the child control removed
1265
	 */
1266
	public function removedControl($control)
1267
	{
1268
		if ($this->_namingContainer) {
1269
			$this->_namingContainer->clearNameTable();
1270
		}
1271
		$control->unloadRecursive();
1272
		$control->_parent = null;
1273
		$control->_page = null;
1274
		$control->_namingContainer = null;
1275
		$control->_tplControl = null;
1276
		//$control->_stage=self::CS_CONSTRUCTED;
1277
		if (!($control->_flags & self::IS_ID_SET)) {
1278
			$control->_id = '';
1279
		} else {
1280
			unset($this->_rf[self::RF_NAMED_OBJECTS][$control->_id]);
1281
		}
1282
		$control->clearCachedUniqueID(true);
1283
	}
1284
1285
	/**
1286
	 * Performs the Init step for the control and all its child controls.
1287
	 * Only framework developers should use this method.
1288
	 * @param TControl $namingContainer the naming container control
1289
	 */
1290
	protected function initRecursive($namingContainer = null)
1291
	{
1292
		$this->ensureChildControls();
1293
		if ($this->getHasControls()) {
1294
			if ($this instanceof INamingContainer) {
1295
				$namingContainer = $this;
1296
			}
1297
			$page = $this->getPage();
1298
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1299
				if ($control instanceof TControl) {
1300
					$control->_namingContainer = $namingContainer;
1301
					$control->_page = $page;
1302
					if ($control->_id === '' && $namingContainer) {
1303
						$control->generateAutomaticID();
1304
					}
1305
					$control->initRecursive($namingContainer);
1306
				}
1307
			}
1308
		}
1309
		if ($this->_stage < self::CS_INITIALIZED) {
1310
			$this->_stage = self::CS_CHILD_INITIALIZED;
1311
			if (($page = $this->getPage()) && $this->getEnableTheming() && !($this->_flags & self::IS_SKIN_APPLIED)) {
1312
				$page->applyControlSkin($this);
1313
				$this->_flags |= self::IS_SKIN_APPLIED;
1314
			}
1315
			if (isset($this->_rf[self::RF_ADAPTER])) {
1316
				$this->_rf[self::RF_ADAPTER]->onInit(null);
1317
			} else {
1318
				$this->onInit(null);
1319
			}
1320
			$this->_stage = self::CS_INITIALIZED;
1321
		}
1322
	}
1323
1324
	/**
1325
	 * Performs the Load step for the control and all its child controls.
1326
	 * Only framework developers should use this method.
1327
	 */
1328
	protected function loadRecursive()
1329
	{
1330
		if ($this->_stage < self::CS_LOADED) {
1331
			if (isset($this->_rf[self::RF_ADAPTER])) {
1332
				$this->_rf[self::RF_ADAPTER]->onLoad(null);
1333
			} else {
1334
				$this->onLoad(null);
1335
			}
1336
		}
1337
		if ($this->getHasControls()) {
1338
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1339
				if ($control instanceof TControl) {
1340
					$control->loadRecursive();
1341
				}
1342
			}
1343
		}
1344
		if ($this->_stage < self::CS_LOADED) {
1345
			$this->_stage = self::CS_LOADED;
1346
		}
1347
	}
1348
1349
	/**
1350
	 * Performs the PreRender step for the control and all its child controls.
1351
	 * Only framework developers should use this method.
1352
	 */
1353
	protected function preRenderRecursive()
1354
	{
1355
		$this->autoDataBindProperties();
1356
1357
		if ($this->getVisible(false)) {
1358
			if (isset($this->_rf[self::RF_ADAPTER])) {
1359
				$this->_rf[self::RF_ADAPTER]->onPreRender(null);
1360
			} else {
1361
				$this->onPreRender(null);
1362
			}
1363
			if ($this->getHasControls()) {
1364
				foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1365
					if ($control instanceof TControl) {
1366
						$control->preRenderRecursive();
1367
					} elseif ($control instanceof TCompositeLiteral) {
1368
						$control->evaluateDynamicContent();
1369
					}
1370
				}
1371
			}
1372
		}
1373
		$this->_stage = self::CS_PRERENDERED;
1374
	}
1375
1376
	/**
1377
	 * Performs the Unload step for the control and all its child controls.
1378
	 * Only framework developers should use this method.
1379
	 */
1380
	protected function unloadRecursive()
1381
	{
1382
		if (!($this->_flags & self::IS_ID_SET)) {
1383
			$this->_id = '';
1384
		}
1385
		if ($this->getHasControls()) {
1386
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1387
				if ($control instanceof TControl) {
1388
					$control->unloadRecursive();
1389
				}
1390
			}
1391
		}
1392
		if (isset($this->_rf[self::RF_ADAPTER])) {
1393
			$this->_rf[self::RF_ADAPTER]->onUnload(null);
1394
		} else {
1395
			$this->onUnload(null);
1396
		}
1397
	}
1398
1399
	/**
1400
	 * This method is invoked when the control enters 'OnInit' stage.
1401
	 * The method raises 'OnInit' event.
1402
	 * If you override this method, be sure to call the parent implementation
1403
	 * so that the event handlers can be invoked.
1404
	 * @param TEventParameter $param event parameter to be passed to the event handlers
1405
	 */
1406
	public function onInit($param)
1407
	{
1408
		$this->raiseEvent('OnInit', $this, $param);
1409
	}
1410
1411
	/**
1412
	 * This method is invoked when the control enters 'OnLoad' stage.
1413
	 * The method raises 'OnLoad' event.
1414
	 * If you override this method, be sure to call the parent implementation
1415
	 * so that the event handlers can be invoked.
1416
	 * @param TEventParameter $param event parameter to be passed to the event handlers
1417
	 */
1418
	public function onLoad($param)
1419
	{
1420
		$this->raiseEvent('OnLoad', $this, $param);
1421
	}
1422
1423
	/**
1424
	 * Raises 'OnDataBinding' event.
1425
	 * This method is invoked when {@link dataBind} is invoked.
1426
	 * @param TEventParameter $param event parameter to be passed to the event handlers
1427
	 */
1428
	public function onDataBinding($param)
1429
	{
1430
		Prado::trace("onDataBinding()", 'Prado\Web\UI\TControl');
1431
		$this->raiseEvent('OnDataBinding', $this, $param);
1432
	}
1433
1434
1435
	/**
1436
	 * This method is invoked when the control enters 'OnUnload' stage.
1437
	 * The method raises 'OnUnload' event.
1438
	 * If you override this method, be sure to call the parent implementation
1439
	 * so that the event handlers can be invoked.
1440
	 * @param TEventParameter $param event parameter to be passed to the event handlers
1441
	 */
1442
	public function onUnload($param)
1443
	{
1444
		$this->raiseEvent('OnUnload', $this, $param);
1445
	}
1446
1447
	/**
1448
	 * This method is invoked when the control enters 'OnPreRender' stage.
1449
	 * The method raises 'OnPreRender' event.
1450
	 * If you override this method, be sure to call the parent implementation
1451
	 * so that the event handlers can be invoked.
1452
	 * @param TEventParameter $param event parameter to be passed to the event handlers
1453
	 */
1454
	public function onPreRender($param)
1455
	{
1456
		$this->raiseEvent('OnPreRender', $this, $param);
1457
	}
1458
1459
	/**
1460
	 * Invokes the parent's bubbleEvent method.
1461
	 * A control who wants to bubble an event must call this method in its onEvent method.
1462
	 * @param TControl $sender sender of the event
1463
	 * @param TEventParameter $param event parameter
1464
	 * @see bubbleEvent
1465
	 */
1466
	protected function raiseBubbleEvent($sender, $param)
1467
	{
1468
		$control = $this;
1469
		while ($control = $control->_parent) {
1470
			if ($control->bubbleEvent($sender, $param)) {
1471
				break;
1472
			}
1473
		}
1474
	}
1475
1476
	/**
1477
	 * This method responds to a bubbled event.
1478
	 * This method should be overriden to provide customized response to a bubbled event.
1479
	 * Check the type of event parameter to determine what event is bubbled currently.
1480
	 * @param TControl $sender sender of the event
1481
	 * @param TEventParameter $param event parameters
1482
	 * @return bool true if the event bubbling is handled and no more bubbling.
1483
	 * @see raiseBubbleEvent
1484
	 */
1485
	public function bubbleEvent($sender, $param)
1486
	{
1487
		return false;
1488
	}
1489
1490
	/**
1491
	 * Broadcasts an event.
1492
	 * The event will be sent to all controls on the current page hierarchy.
1493
	 * If a control defines the event, the event will be raised for the control.
1494
	 * If a control implements {@link IBroadcastEventReceiver}, its
1495
	 * {@link IBroadcastEventReceiver::broadcastEventReceived broadcastEventReceived()} method will
1496
	 * be invoked which gives the control a chance to respond to the event.
1497
	 * For example, when broadcasting event 'OnClick', all controls having 'OnClick'
1498
	 * event will have this event raised, and all controls implementing
1499
	 * {@link IBroadcastEventReceiver} will also have its
1500
	 * {@link IBroadcastEventReceiver::broadcastEventReceived broadcastEventReceived()}
1501
	 * invoked.
1502
	 * @param string $name name of the broadcast event
1503
	 * @param TControl $sender sender of this event
1504
	 * @param TEventParameter $param event parameter
1505
	 */
1506
	public function broadcastEvent($name, $sender, $param)
1507
	{
1508
		$rootControl = (($page = $this->getPage()) === null) ? $this : $page;
1509
		$rootControl->broadcastEventInternal($name, $sender, new TBroadcastEventParameter($name, $param));
1510
	}
1511
1512
	/**
1513
	 * Recursively broadcasts an event.
1514
	 * This method should only be used by framework developers.
1515
	 * @param string $name name of the broadcast event
1516
	 * @param TControl $sender sender of the event
1517
	 * @param TBroadcastEventParameter $param event parameter
1518
	 */
1519
	private function broadcastEventInternal($name, $sender, $param)
1520
	{
1521
		if ($this->hasEvent($name)) {
1522
			$this->raiseEvent($name, $sender, $param->getParameter());
1523
		}
1524
		if ($this instanceof IBroadcastEventReceiver) {
1525
			$this->broadcastEventReceived($sender, $param);
1526
		}
1527
		if ($this->getHasControls()) {
1528
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1529
				if ($control instanceof TControl) {
1530
					$control->broadcastEventInternal($name, $sender, $param);
1531
				}
1532
			}
1533
		}
1534
	}
1535
1536
	/**
1537
	 * Traverse the whole control hierarchy rooted at this control.
1538
	 * Callback function may be invoked for each control being visited.
1539
	 * A pre-callback is invoked before traversing child controls;
1540
	 * A post-callback is invoked after traversing child controls.
1541
	 * Callback functions can be global functions or class methods.
1542
	 * They must be of the following signature:
1543
	 * <code>
1544
	 * function callback_func($control,$param) {...}
1545
	 * </code>
1546
	 * where $control refers to the control being visited and $param
1547
	 * is the parameter that is passed originally when calling this traverse function.
1548
	 *
1549
	 * @param mixed $param parameter to be passed to callbacks for each control
1550
	 * @param null|callable $preCallback callback invoked before traversing child controls. If null, it is ignored.
1551
	 * @param null|callable $postCallback callback invoked after traversing child controls. If null, it is ignored.
1552
	 */
1553
	protected function traverseChildControls($param, $preCallback = null, $postCallback = null)
1554
	{
1555
		if ($preCallback !== null) {
1556
			call_user_func($preCallback, $this, $param);
1557
		}
1558
		if ($this->getHasControls()) {
1559
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1560
				if ($control instanceof TControl) {
1561
					$control->traverseChildControls($param, $preCallback, $postCallback);
1562
				}
1563
			}
1564
		}
1565
		if ($postCallback !== null) {
1566
			call_user_func($postCallback, $this, $param);
1567
		}
1568
	}
1569
1570
	/**
1571
	 * Renders the control.
1572
	 * Only when the control is visible will the control be rendered.
1573
	 * @param THtmlWriter $writer the writer used for the rendering purpose
1574
	 */
1575
	public function renderControl($writer)
1576
	{
1577
		if ($this instanceof IActiveControl || $this->getVisible(false)) {
1578
			if (isset($this->_rf[self::RF_ADAPTER])) {
1579
				$this->_rf[self::RF_ADAPTER]->render($writer);
1580
			} else {
1581
				$this->render($writer);
1582
			}
1583
		}
1584
	}
1585
1586
	/**
1587
	 * Renders the control.
1588
	 * This method is invoked by {@link renderControl} when the control is visible.
1589
	 * You can override this method to provide customized rendering of the control.
1590
	 * By default, the control simply renders all its child contents.
1591
	 * @param THtmlWriter $writer the writer used for the rendering purpose
1592
	 */
1593
	public function render($writer)
1594
	{
1595
		$this->renderChildren($writer);
1596
	}
1597
1598
	/**
1599
	 * Renders the children of the control.
1600
	 * This method iterates through all child controls and static text strings
1601
	 * and renders them in order.
1602
	 * @param THtmlWriter $writer the writer used for the rendering purpose
1603
	 */
1604
	public function renderChildren($writer)
1605
	{
1606
		if ($this->getHasControls()) {
1607
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1608
				if (is_string($control)) {
1609
					$writer->write($control);
1610
				} elseif ($control instanceof TControl) {
1611
					$control->renderControl($writer);
1612
				} elseif ($control instanceof IRenderable) {
1613
					$control->render($writer);
1614
				}
1615
			}
1616
		}
1617
	}
1618
1619
	/**
1620
	 * This method is invoked when control state is to be saved.
1621
	 * You can override this method to do last step state saving.
1622
	 * Parent implementation must be invoked.
1623
	 */
1624
	public function saveState()
1625
	{
1626
	}
1627
1628
	/**
1629
	 * This method is invoked right after the control has loaded its state.
1630
	 * You can override this method to initialize data from the control state.
1631
	 * Parent implementation must be invoked.
1632
	 */
1633
	public function loadState()
1634
	{
1635
	}
1636
1637
	/**
1638
	 * Loads state (viewstate and controlstate) into a control and its children.
1639
	 * This method should only be used by framework developers.
1640
	 * @param array $state the collection of the state
1641
	 * @param bool $needViewState whether the viewstate should be loaded
1642
	 */
1643
	protected function loadStateRecursive(&$state, $needViewState = true)
1644
	{
1645
		if (is_array($state)) {
1646
			// A null state means the stateful properties all take default values.
1647
			// So if the state is enabled, we have to assign the null value.
1648
			$needViewState = ($needViewState && !($this->_flags & self::IS_DISABLE_VIEWSTATE));
1649
			if (isset($state[1])) {
1650
				$this->_rf[self::RF_CONTROLSTATE] = &$state[1];
1651
				unset($state[1]);
1652
			} else {
1653
				unset($this->_rf[self::RF_CONTROLSTATE]);
1654
			}
1655
			if ($needViewState) {
1656
				if (isset($state[0])) {
1657
					$this->_viewState = &$state[0];
1658
				} else {
1659
					$this->_viewState = [];
1660
				}
1661
			}
1662
			unset($state[0]);
1663
			if ($this->getHasControls()) {
1664
				foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1665
					if ($control instanceof TControl) {
1666
						if (isset($state[$control->_id])) {
1667
							$control->loadStateRecursive($state[$control->_id], $needViewState);
1668
							unset($state[$control->_id]);
1669
						}
1670
					}
1671
				}
1672
			}
1673
			if (!empty($state)) {
1674
				$this->_rf[self::RF_CHILD_STATE] = &$state;
1675
			}
1676
		}
1677
		$this->_stage = self::CS_STATE_LOADED;
1678
		if (isset($this->_rf[self::RF_ADAPTER])) {
1679
			$this->_rf[self::RF_ADAPTER]->loadState();
1680
		} else {
1681
			$this->loadState();
1682
		}
1683
	}
1684
1685
	/**
1686
	 * Saves all control state (viewstate and controlstate) as a collection.
1687
	 * This method should only be used by framework developers.
1688
	 * @param bool $needViewState whether the viewstate should be saved
1689
	 * @return array the collection of the control state (including its children's state).
1690
	 */
1691
	protected function &saveStateRecursive($needViewState = true)
1692
	{
1693
		if (isset($this->_rf[self::RF_ADAPTER])) {
1694
			$this->_rf[self::RF_ADAPTER]->saveState();
1695
		} else {
1696
			$this->saveState();
1697
		}
1698
		$needViewState = ($needViewState && !($this->_flags & self::IS_DISABLE_VIEWSTATE));
1699
		$state = [];
1700
		if ($this->getHasControls()) {
1701
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1702
				if ($control instanceof TControl) {
1703
					if (count($tmp = &$control->saveStateRecursive($needViewState))) {
1704
						$state[$control->_id] = $tmp;
1705
					}
1706
				}
1707
			}
1708
		}
1709
		if ($needViewState && !empty($this->_viewState)) {
1710
			$state[0] = &$this->_viewState;
1711
		}
1712
		if (isset($this->_rf[self::RF_CONTROLSTATE])) {
1713
			$state[1] = &$this->_rf[self::RF_CONTROLSTATE];
1714
		}
1715
		return $state;
1716
	}
1717
1718
	/**
1719
	 * Applies a stylesheet skin to a control.
1720
	 * @param TPage $page the page containing the control
1721
	 * @throws TInvalidOperationException if the stylesheet skin is applied already
1722
	 */
1723
	public function applyStyleSheetSkin($page)
1724
	{
1725
		if ($page && !($this->_flags & self::IS_STYLESHEET_APPLIED)) {
1726
			$page->applyControlStyleSheet($this);
1727
			$this->_flags |= self::IS_STYLESHEET_APPLIED;
1728
		} elseif ($this->_flags & self::IS_STYLESHEET_APPLIED) {
1729
			throw new TInvalidOperationException('control_stylesheet_applied', get_class($this));
1730
		}
1731
	}
1732
1733
	/**
1734
	 * Clears the cached UniqueID.
1735
	 * If $recursive=true, all children's cached UniqueID will be cleared as well.
1736
	 * @param bool $recursive whether the clearing is recursive.
1737
	 */
1738
	private function clearCachedUniqueID($recursive)
1739
	{
1740
		if ($recursive && $this->_uid !== null && isset($this->_rf[self::RF_CONTROLS])) {
1741
			foreach ($this->_rf[self::RF_CONTROLS] as $control) {
1742
				if ($control instanceof TControl) {
1743
					$control->clearCachedUniqueID($recursive);
1744
				}
1745
			}
1746
		}
1747
		$this->_uid = null;
1748
	}
1749
1750
	/**
1751
	 * Generates an automatic ID for the control.
1752
	 */
1753
	private function generateAutomaticID()
1754
	{
1755
		$this->_flags &= ~self::IS_ID_SET;
1756
		if (!isset($this->_namingContainer->_rf[self::RF_NAMED_CONTROLS_ID])) {
1757
			$this->_namingContainer->_rf[self::RF_NAMED_CONTROLS_ID] = 0;
1758
		}
1759
		$id = $this->_namingContainer->_rf[self::RF_NAMED_CONTROLS_ID]++;
1760
		$this->_id = self::AUTOMATIC_ID_PREFIX . $id;
1761
		$this->_namingContainer->clearNameTable();
1762
	}
1763
1764
	/**
1765
	 * Clears the list of the controls whose IDs are managed by the specified naming container.
1766
	 */
1767
	private function clearNameTable()
1768
	{
1769
		unset($this->_rf[self::RF_NAMED_CONTROLS]);
1770
	}
1771
1772
	/**
1773
	 * Updates the list of the controls whose IDs are managed by the specified naming container.
1774
	 * @param TControl $container the naming container
1775
	 * @param TControlCollection $controls list of controls
1776
	 * @throws TInvalidDataValueException if a control's ID is not unique within its naming container.
1777
	 */
1778
	private function fillNameTable($container, $controls)
1779
	{
1780
		foreach ($controls as $control) {
1781
			if ($control instanceof TControl) {
1782
				if ($control->_id !== '') {
1783
					if (isset($container->_rf[self::RF_NAMED_CONTROLS][$control->_id])) {
1784
						throw new TInvalidDataValueException('control_id_nonunique', get_class($control), $control->_id);
1785
					} else {
1786
						$container->_rf[self::RF_NAMED_CONTROLS][$control->_id] = $control;
1787
					}
1788
				}
1789
				if (!($control instanceof INamingContainer) && $control->getHasControls()) {
1790
					$this->fillNameTable($container, $control->_rf[self::RF_CONTROLS]);
1791
				}
1792
			}
1793
		}
1794
	}
1795
}
1796