TPage::getInFormRender()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 1
cp 0
crap 2
rs 10
1
<?php
2
3
/**
4
 * TPage 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 Prado\Collections\TList;
14
use Prado\Collections\TMap;
15
use Prado\Collections\TStack;
16
use Prado\Exceptions\TConfigurationException;
17
use Prado\Exceptions\THttpException;
18
use Prado\Exceptions\TInvalidDataValueException;
19
use Prado\Exceptions\TInvalidDataTypeException;
20
use Prado\Exceptions\TInvalidOperationException;
21
use Prado\Prado;
22
use Prado\TPropertyValue;
23
use Prado\Web\Javascripts\TJavaScript;
24
use Prado\Web\UI\ActiveControls\TActivePageAdapter;
25
use Prado\Web\UI\ActiveControls\TCallbackClientScript;
26
use Prado\Web\UI\WebControls\THead;
27
28
/**
29
 * TPage class
30
 *
31
 * @author Qiang Xue <[email protected]>
32
 * @since 3.0
33
 * @method TActivePageAdapter getAdapter()
34
 * @method \Prado\Web\Services\TPageService getService()
35
 */
36
class TPage extends TTemplateControl
37
{
38
	/**
39
	 * system post fields
40
	 */
41
	public const FIELD_POSTBACK_TARGET = 'PRADO_POSTBACK_TARGET';
42
	public const FIELD_POSTBACK_PARAMETER = 'PRADO_POSTBACK_PARAMETER';
43
	public const FIELD_LASTFOCUS = 'PRADO_LASTFOCUS';
44
	public const FIELD_PAGESTATE = 'PRADO_PAGESTATE';
45
	public const FIELD_CALLBACK_TARGET = 'PRADO_CALLBACK_TARGET';
46
	public const FIELD_CALLBACK_PARAMETER = 'PRADO_CALLBACK_PARAMETER';
47
48
	/**
49
	 * @var array system post fields
50
	 */
51
	private static $_systemPostFields = [
52
		'PRADO_POSTBACK_TARGET' => true,
53
		'PRADO_POSTBACK_PARAMETER' => true,
54
		'PRADO_LASTFOCUS' => true,
55
		'PRADO_PAGESTATE' => true,
56
		'PRADO_CALLBACK_TARGET' => true,
57
		'PRADO_CALLBACK_PARAMETER' => true,
58
	];
59
	/**
60
	 * @var TForm form instance
61
	 */
62
	private $_form;
63
	/**
64
	 * @var THead head instance
65
	 */
66
	private $_head;
67
	/**
68
	 * @var TList list of registered validators
69
	 */
70
	private $_validators;
71
	/**
72
	 * @var bool if validation has been performed
73
	 */
74
	private $_validated = false;
75
	/**
76
	 * @var TTheme page theme
77
	 */
78
	private $_theme;
79
	/**
80
	 * @var string page title set when Head is not in page yet
81
	 */
82
	private $_title;
83
	/**
84
	 * @var TTheme page stylesheet theme
85
	 */
86
	private $_styleSheet;
87
	/**
88
	 * @var TClientScriptManager client script manager
89
	 */
90
	private $_clientScript;
91
	/**
92
	 * @var TMap data post back by user
93
	 */
94
	protected $_postData;
95
	/**
96
	 * @var TMap postback data that is not handled during first invocation of LoadPostData.
97
	 */
98
	protected $_restPostData;
99
	/**
100
	 * @var array list of controls whose data have been changed due to the postback
101
	 */
102
	protected $_controlsPostDataChanged = [];
103
	/**
104
	 * @var array list of controls that need to load post data in the current request
105
	 */
106
	protected $_controlsRequiringPostData = [];
107
	/**
108
	 * @var array list of controls that need to load post data in the next postback
109
	 */
110
	protected $_controlsRegisteredForPostData = [];
111
	/**
112
	 * @var \Prado\Web\UI\TControl control that needs to raise postback event
113
	 */
114
	private $_postBackEventTarget;
115
	/**
116
	 * @var string postback event parameter
117
	 */
118
	private $_postBackEventParameter;
119
	/**
120
	 * @var bool whether the form has been rendered
121
	 */
122
	protected $_formRendered = false;
123
	/**
124
	 * @var bool whether the current rendering is within a form
125
	 */
126
	protected $_inFormRender = false;
127
	/**
128
	 * @var string|TControl the control or the ID of the element on the page to be focused when the page is sent back to user
129
	 */
130
	private $_focus;
131
	/**
132
	 * @var string page path to this page
133
	 */
134
	private $_pagePath = '';
135
	/**
136
	 * @var bool whether page state should be HMAC validated
137
	 */
138
	private $_enableStateValidation = true;
139
	/**
140
	 * @var bool whether page state should be encrypted
141
	 */
142
	private $_enableStateEncryption = false;
143
	/**
144
	 * @var bool whether page state should be compressed
145
	 * @since 3.1.6
146
	 */
147
	private $_enableStateCompression = true;
148
	/**
149
	 * @var bool whether to use the igbinary serializer if available
150
	 * @since 4.1
151
	 */
152
	private $_enableStateIGBinary = true;
153
	/**
154
	 * @var string page state persister class name
155
	 */
156
	private $_statePersisterClass = \Prado\Web\UI\TPageStatePersister::class;
157
	/**
158
	 * @var mixed page state persister
159
	 */
160
	private $_statePersister;
161
	/**
162
	 * @var TStack stack used to store currently active caching controls
163
	 */
164
	private $_cachingStack;
165
	/**
166
	 * @var string state string to be stored on the client side
167
	 */
168
	private $_clientState = '';
169
	/**
170
	 * @var bool true if loading post data.
171
	 */
172
	protected $_isLoadingPostData = false;
173
	/**
174
	 * @var bool whether client supports javascript
175
	 */
176
	private $_enableJavaScript = true;
177
	/**
178
	 * @var THtmlWriter current html render writer
179
	 */
180
	private $_writer;
181
182
	/**
183
	 * Constructor.
184
	 * Sets the page object to itself.
185
	 * Derived classes must call parent implementation.
186
	 */
187
	public function __construct()
188
	{
189
		$this->setPage($this);
190
		parent::__construct();
191
	}
192
193
	/**
194
	 * TPage does autoGlobalListen and unlisten.  Developers
195
	 * may put fx events in the page that need to be picked up.
196
	 *
197
	 * @return bool returns true
198
	 */
199
	public function getAutoGlobalListen()
200
	{
201
		return true;
202
	}
203
204
	/**
205
	 * Runs through the page lifecycles.
206
	 * @param \Prado\Web\UI\THtmlWriter $writer the HTML writer
207
	 */
208
	public function run($writer)
209
	{
210
		Prado::trace("Running page life cycles", TPage::class);
211
		$this->_writer = $writer;
212
213
		$this->determinePostBackMode();
214
215
		if ($this->getIsPostBack()) {
216
			if ($this->getIsCallback()) {
217
				$this->processCallbackRequest($writer);
218
			} else {
219
				$this->processPostBackRequest($writer);
220
			}
221
		} else {
222
			$this->processNormalRequest($writer);
223
		}
224
225
		$this->_writer = null;
226
	}
227
228
	protected function processNormalRequest($writer)
229
	{
230
		Prado::trace("Page onPreInit()", TPage::class);
231
		$this->onPreInit(null);
232
233
		Prado::trace("Page initRecursive()", TPage::class);
234
		$this->initRecursive();
235
236
		Prado::trace("Page onInitComplete()", TPage::class);
237
		$this->onInitComplete(null);
238
239
		Prado::trace("Page onPreLoad()", TPage::class);
240
		$this->onPreLoad(null);
241
		Prado::trace("Page loadRecursive()", TPage::class);
242
		$this->loadRecursive();
243
		Prado::trace("Page onLoadComplete()", TPage::class);
244
		$this->onLoadComplete(null);
245
246
		Prado::trace("Page preRenderRecursive()", TPage::class);
247
		$this->preRenderRecursive();
248
		Prado::trace("Page onPreRenderComplete()", TPage::class);
249
		$this->onPreRenderComplete(null);
250
251
		Prado::trace("Page savePageState()", TPage::class);
252
		$this->savePageState();
253
		Prado::trace("Page onSaveStateComplete()", TPage::class);
254
		$this->onSaveStateComplete(null);
255
256
		Prado::trace("Page renderControl()", TPage::class);
257
		$this->renderControl($writer);
258
		Prado::trace("Page unloadRecursive()", TPage::class);
259
		$this->unloadRecursive();
260
	}
261
262
	protected function processPostBackRequest($writer)
263
	{
264
		Prado::trace("Page onPreInit()", TPage::class);
265
		$this->onPreInit(null);
266
267
		Prado::trace("Page initRecursive()", TPage::class);
268
		$this->initRecursive();
269
270
		Prado::trace("Page onInitComplete()", TPage::class);
271
		$this->onInitComplete(null);
272
273
		$this->_restPostData = new TMap();
274
		Prado::trace("Page loadPageState()", TPage::class);
275
		$this->loadPageState();
276
		Prado::trace("Page processPostData()", TPage::class);
277
		$this->processPostData($this->_postData, true);
278
		Prado::trace("Page onPreLoad()", TPage::class);
279
		$this->onPreLoad(null);
280
		Prado::trace("Page loadRecursive()", TPage::class);
281
		$this->loadRecursive();
282
		Prado::trace("Page processPostData()", TPage::class);
283
		$this->processPostData($this->_restPostData, false);
284
		Prado::trace("Page raiseChangedEvents()", TPage::class);
285
		$this->raiseChangedEvents();
286
		Prado::trace("Page raisePostBackEvent()", TPage::class);
287
		$this->raisePostBackEvent();
288
		Prado::trace("Page onLoadComplete()", TPage::class);
289
		$this->onLoadComplete(null);
290
291
		Prado::trace("Page preRenderRecursive()", TPage::class);
292
		$this->preRenderRecursive();
293
		Prado::trace("Page onPreRenderComplete()", TPage::class);
294
		$this->onPreRenderComplete(null);
295
296
		Prado::trace("Page savePageState()", TPage::class);
297
		$this->savePageState();
298
		Prado::trace("Page onSaveStateComplete()", TPage::class);
299
		$this->onSaveStateComplete(null);
300
301
		Prado::trace("Page renderControl()", TPage::class);
302
		$this->renderControl($writer);
303
		Prado::trace("Page unloadRecursive()", TPage::class);
304
		$this->unloadRecursive();
305
	}
306
307
	protected static function decodeUTF8($data, $enc)
308
	{
309
		if (is_array($data)) {
310
			foreach ($data as $k => $v) {
311
				$data[$k] = self::decodeUTF8($v, $enc);
312
			}
313
			return $data;
314
		} elseif (is_string($data)) {
315
			return iconv('UTF-8', $enc . '//IGNORE', $data);
316
		} else {
317
			return $data;
318
		}
319
	}
320
321
	/**
322
	 * Sets Adapter to TActivePageAdapter and calls apter to process the
323
	 * callback request.
324
	 * @param mixed $writer
325
	 */
326
	protected function processCallbackRequest($writer)
327
	{
328
		$this->setAdapter(new TActivePageAdapter($this));
329
330
		$callbackEventParameter = $this->getRequest()->itemAt(TPage::FIELD_CALLBACK_PARAMETER);
331
		if ($callbackEventParameter && strlen($callbackEventParameter) > 0) {
332
			$this->_postData[TPage::FIELD_CALLBACK_PARAMETER] = TJavaScript::jsonDecode((string) $callbackEventParameter);
333
		}
334
335
		// Decode Callback postData from UTF-8 to current Charset
336
		if (($g = $this->getApplication()->getGlobalization(false)) !== null &&
337
			strtoupper($enc = $g->getCharset()) != 'UTF-8') {
338
			foreach ($this->_postData as $k => $v) {
339
				$this->_postData[$k] = self::decodeUTF8($v, $enc);
340
			}
341
		}
342
343
		Prado::trace("Page onPreInit()", TPage::class);
344
		$this->onPreInit(null);
345
346
		Prado::trace("Page initRecursive()", TPage::class);
347
		$this->initRecursive();
348
349
		Prado::trace("Page onInitComplete()", TPage::class);
350
		$this->onInitComplete(null);
351
352
		$this->_restPostData = new TMap();
353
		Prado::trace("Page loadPageState()", TPage::class);
354
		$this->loadPageState();
355
		Prado::trace("Page processPostData()", TPage::class);
356
		$this->processPostData($this->_postData, true);
357
		Prado::trace("Page onPreLoad()", TPage::class);
358
		$this->onPreLoad(null);
359
		Prado::trace("Page loadRecursive()", TPage::class);
360
		$this->loadRecursive();
361
362
		Prado::trace("Page processPostData()", TPage::class);
363
		$this->processPostData($this->_restPostData, false);
364
365
		Prado::trace("Page raiseChangedEvents()", TPage::class);
366
		$this->raiseChangedEvents();
367
368
369
		$this->getAdapter()->processCallbackEvent($writer);
370
371
		/*
372
				Prado::trace("Page raisePostBackEvent()",TPage::class);
373
				$this->raisePostBackEvent();
374
		*/
375
		Prado::trace("Page onLoadComplete()", TPage::class);
376
		$this->onLoadComplete(null);
377
378
		Prado::trace("Page preRenderRecursive()", TPage::class);
379
		$this->preRenderRecursive();
380
		Prado::trace("Page onPreRenderComplete()", TPage::class);
381
		$this->onPreRenderComplete(null);
382
383
		Prado::trace("Page savePageState()", TPage::class);
384
		$this->savePageState();
385
		Prado::trace("Page onSaveStateComplete()", TPage::class);
386
		$this->onSaveStateComplete(null);
387
388
		/*
389
				Prado::trace("Page renderControl()",TPage::class);
390
				$this->renderControl($writer);
391
		*/
392
		$this->getAdapter()->renderCallbackResponse($writer);
393
394
		Prado::trace("Page unloadRecursive()", TPage::class);
395
		$this->unloadRecursive();
396
	}
397
398
	/**
399
	 * Gets the callback client script handler that allows javascript functions
400
	 * to be executed during the callback response.
401
	 * @return TCallbackClientScript interface to client-side javascript code.
402
	 */
403
	public function getCallbackClient()
404
	{
405
		if ($this->getAdapter() !== null) {
406
			return $this->getAdapter()->getCallbackClientHandler();
407
		} else {
408
			return new TCallbackClientScript();
409
		}
410
	}
411
412
	/**
413
	 * @return \Prado\Web\UI\TControl the control responsible for the current callback event,
414
	 * null if nonexistent
415
	 */
416
	public function getCallbackEventTarget()
417
	{
418
		return $this->getAdapter()->getCallbackEventTarget();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getAdapter...etCallbackEventTarget() also could return the type Prado\Web\UI\ActiveControls\IActiveControl which is incompatible with the documented return type Prado\Web\UI\TControl.
Loading history...
419
	}
420
421
	/**
422
	 * Registers a control to raise callback event in the current request.
423
	 * @param \Prado\Web\UI\TControl $control control registered to raise callback event.
424
	 */
425
	public function setCallbackEventTarget(TControl $control)
426
	{
427
		$this->getAdapter()->setCallbackEventTarget($control);
428
	}
429
430
	/**
431
	 * Callback parameter is decoded assuming JSON encoding.
432
	 * @return string callback event parameter
433
	 */
434
	public function getCallbackEventParameter()
435
	{
436
		return $this->getAdapter()->getCallbackEventParameter();
437
	}
438
439
	/**
440
	 * @param mixed $value callback event parameter
441
	 */
442
	public function setCallbackEventParameter($value)
443
	{
444
		$this->getAdapter()->setCallbackEventParameter($value);
445
	}
446
447
	/**
448
	 * @return TForm the form on the page
449
	 */
450
	public function getForm()
451
	{
452
		return $this->_form;
453
	}
454
455
	/**
456
	 * Registers a TForm instance to the page.
457
	 * Note, a page can contain at most one TForm instance.
458
	 * @param TForm $form the form on the page
459
	 * @throws TInvalidOperationException if this method is invoked twice or more.
460
	 */
461
	public function setForm(TForm $form)
462
	{
463
		if ($this->_form === null) {
464
			$this->_form = $form;
465
		} else {
466
			throw new TInvalidOperationException('page_form_duplicated');
467
		}
468
	}
469
470
	/**
471
	 * Returns a list of registered validators.
472
	 * If validation group is specified, only the validators in that group will be returned.
473
	 * @param null|string $validationGroup validation group
474
	 * @return TList registered validators in the requested group. If the group is null, all validators will be returned.
475
	 */
476
	public function getValidators($validationGroup = null)
477
	{
478
		if (!$this->_validators) {
479
			$this->_validators = new TList();
480
		}
481
		if (empty($validationGroup) === true) {
482
			return $this->_validators;
483
		} else {
484
			$list = new TList();
485
			foreach ($this->_validators as $validator) {
486
				if ($validator->getValidationGroup() === $validationGroup) {
487
					$list->add($validator);
488
				}
489
			}
490
			return $list;
491
		}
492
	}
493
494
	/**
495
	 * Performs input validation.
496
	 * This method will invoke the registered validators to perform the actual validation.
497
	 * If validation group is specified, only the validators in that group will be invoked.
498
	 * @param string $validationGroup validation group. If null, all validators will perform validation.
499
	 */
500
	public function validate($validationGroup = null)
501
	{
502
		Prado::trace("Page validate()", TPage::class);
503
		$this->_validated = true;
504
		if ($this->_validators && $this->_validators->getCount()) {
505
			if ($validationGroup === null) {
506
				foreach ($this->_validators as $validator) {
507
					$validator->validate();
508
				}
509
			} else {
510
				foreach ($this->_validators as $validator) {
511
					if ($validator->getValidationGroup() === $validationGroup) {
512
						$validator->validate();
513
					}
514
				}
515
			}
516
		}
517
	}
518
519
	/**
520
	 * Returns whether user input is valid or not.
521
	 * This method must be invoked after {@see validate} is called.
522
	 * @throws TInvalidOperationException if {@see validate} is not invoked yet.
523
	 * @return bool whether the user input is valid or not.
524
	 */
525
	public function getIsValid()
526
	{
527
		if ($this->_validated) {
528
			if ($this->_validators && $this->_validators->getCount()) {
529
				foreach ($this->_validators as $validator) {
530
					if (!$validator->getIsValid()) {
531
						return false;
532
					}
533
				}
534
			}
535
			return true;
536
		} else {
537
			throw new TInvalidOperationException('page_isvalid_unknown');
538
		}
539
	}
540
541
	/**
542
	 * @return TTheme the theme used for the page. Defaults to null.
543
	 */
544
	public function getTheme()
545
	{
546
		if (is_string($this->_theme)) {
0 ignored issues
show
introduced by
The condition is_string($this->_theme) is always false.
Loading history...
547
			$this->_theme = $this->getService()->getThemeManager()->getTheme($this->_theme);
548
		}
549
		return $this->_theme;
550
	}
551
552
	/**
553
	 * Sets the theme to be used for the page.
554
	 * @param string|TTheme $value the theme name or the theme object to be used for the page.
555
	 */
556
	public function setTheme($value)
557
	{
558
		$this->_theme = empty($value) ? null : $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like empty($value) ? null : $value can also be of type string. However, the property $_theme is declared as type Prado\Web\UI\TTheme. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
559
	}
560
561
562
	/**
563
	 * @return TTheme the stylesheet theme used for the page. Defaults to null.
564
	 */
565
	public function getStyleSheetTheme()
566
	{
567
		if (is_string($this->_styleSheet)) {
0 ignored issues
show
introduced by
The condition is_string($this->_styleSheet) is always false.
Loading history...
568
			$this->_styleSheet = $this->getService()->getThemeManager()->getTheme($this->_styleSheet);
569
		}
570
		return $this->_styleSheet;
571
	}
572
573
	/**
574
	 * Sets the stylesheet theme to be used for the page.
575
	 * @param string|TTheme $value the stylesheet theme name or the stylesheet theme object to be used for the page.
576
	 */
577
	public function setStyleSheetTheme($value)
578
	{
579
		$this->_styleSheet = empty($value) ? null : $value;
0 ignored issues
show
Documentation Bug introduced by
It seems like empty($value) ? null : $value can also be of type string. However, the property $_styleSheet is declared as type Prado\Web\UI\TTheme. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
580
	}
581
582
	/**
583
	 * Applies a skin in the current theme to a control.
584
	 * This method should only be used by framework developers.
585
	 * @param \Prado\Web\UI\TControl $control a control to be applied skin with
586
	 */
587
	public function applyControlSkin($control)
588
	{
589
		if (($theme = $this->getTheme()) !== null) {
590
			$theme->applySkin($control);
591
		}
592
	}
593
594
	/**
595
	 * Applies a stylesheet skin in the current theme to a control.
596
	 * This method should only be used by framework developers.
597
	 * @param \Prado\Web\UI\TControl $control a control to be applied stylesheet skin with
598
	 */
599
	public function applyControlStyleSheet($control)
600
	{
601
		if (($theme = $this->getStyleSheetTheme()) !== null) {
602
			$theme->applySkin($control);
603
		}
604
	}
605
606
	/**
607
	 * @return TClientScriptManager client script manager
608
	 */
609
	public function getClientScript()
610
	{
611
		if (!$this->_clientScript) {
612
			$className = $classPath = $this->getService()->getClientScriptManagerClass();
0 ignored issues
show
Unused Code introduced by
The assignment to $classPath is dead and can be removed.
Loading history...
613
614
			if ($className !== TClientScriptManager::class && !is_subclass_of($className, TClientScriptManager::class)) {
615
				throw new THttpException(404, 'page_csmanagerclass_invalid', $className);
616
			}
617
618
			$this->_clientScript = new $className($this);
619
		}
620
		return $this->_clientScript;
621
	}
622
623
	/**
624
	 * Raises OnPreInit event.
625
	 * This method is invoked right before {@see onInit OnInit} stage.
626
	 * You may override this method to provide additional initialization that
627
	 * should be done before {@see onInit OnInit} (e.g. setting {@see setTheme Theme} or
628
	 * {@see setStyleSheetTheme StyleSheetTheme}).
629
	 * Remember to call the parent implementation to ensure OnPreInit event is raised.
630
	 * @param mixed $param event parameter
631
	 */
632
	public function onPreInit($param)
633
	{
634
		$this->raiseEvent('OnPreInit', $this, $param);
635
	}
636
637
	/**
638
	 * Raises OnInitComplete event.
639
	 * This method is invoked right after {@see onInit OnInit} stage and before {@see onLoad OnLoad} stage.
640
	 * You may override this method to provide additional initialization that
641
	 * should be done after {@see onInit OnInit}.
642
	 * Remember to call the parent implementation to ensure OnInitComplete event is raised.
643
	 * @param mixed $param event parameter
644
	 */
645
	public function onInitComplete($param)
646
	{
647
		$this->raiseEvent('OnInitComplete', $this, $param);
648
	}
649
650
	/**
651
	 * Raises OnPreLoad event.
652
	 * This method is invoked right before {@see onLoad OnLoad} stage.
653
	 * You may override this method to provide additional page loading logic that
654
	 * should be done before {@see onLoad OnLoad}.
655
	 * Remember to call the parent implementation to ensure OnPreLoad event is raised.
656
	 * @param mixed $param event parameter
657
	 */
658
	public function onPreLoad($param)
659
	{
660
		$this->raiseEvent('OnPreLoad', $this, $param);
661
	}
662
663
	/**
664
	 * Raises OnLoadComplete event.
665
	 * This method is invoked right after {@see onLoad OnLoad} stage.
666
	 * You may override this method to provide additional page loading logic that
667
	 * should be done after {@see onLoad OnLoad}.
668
	 * Remember to call the parent implementation to ensure OnLoadComplete event is raised.
669
	 * @param mixed $param event parameter
670
	 */
671
	public function onLoadComplete($param)
672
	{
673
		$this->raiseEvent('OnLoadComplete', $this, $param);
674
	}
675
676
	/**
677
	 * Raises OnPreRenderComplete event.
678
	 * This method is invoked right after {@see onPreRender OnPreRender} stage.
679
	 * You may override this method to provide additional preparation for page rendering
680
	 * that should be done after {@see onPreRender OnPreRender}.
681
	 * Remember to call the parent implementation to ensure OnPreRenderComplete event is raised.
682
	 * @param mixed $param event parameter
683
	 */
684
	public function onPreRenderComplete($param)
685
	{
686
		$this->raiseEvent('OnPreRenderComplete', $this, $param);
687
		$cs = $this->getClientScript();
688
		$theme = $this->getTheme();
689
		if ($theme instanceof ITheme) {
0 ignored issues
show
introduced by
$theme is always a sub-type of Prado\Web\UI\ITheme.
Loading history...
690
			foreach ($theme->getStyleSheetFiles() as $url) {
691
				$cs->registerStyleSheetFile($url, $url, $this->getCssMediaType($url, $theme));
692
			}
693
			foreach ($theme->getJavaScriptFiles() as $url) {
694
				$cs->registerHeadScriptFile($url, $url);
695
			}
696
		}
697
		$styleSheet = $this->getStyleSheetTheme();
698
		if ($styleSheet instanceof ITheme) {
0 ignored issues
show
introduced by
$styleSheet is always a sub-type of Prado\Web\UI\ITheme.
Loading history...
699
			foreach ($styleSheet->getStyleSheetFiles() as $url) {
700
				$cs->registerStyleSheetFile($url, $url, $this->getCssMediaType($url, $styleSheet));
701
			}
702
			foreach ($styleSheet->getJavaScriptFiles() as $url) {
703
				$cs->registerHeadScriptFile($url, $url);
704
			}
705
		}
706
707
		if ($cs->getRequiresHead() && $this->getHead() === null) {
708
			throw new TConfigurationException('page_head_required');
709
		}
710
	}
711
712
	/**
713
	 * Determines the media type of the CSS file.
714
	 * The media type is determined according to the following file name pattern:
715
	 *        xxx.media-type.extension
716
	 * For example, 'mystyle.print.css' means its media type is 'print'.
717
	 * @param string $url CSS URL
718
	 * @param object $theme the theme being applied
719
	 * @return string media type of the CSS file
720
	 */
721
	private function getCssMediaType($url, $theme)
722
	{
723
		if ($type = $theme->dyCssMediaType(null, $url)) {
724
			return $type;
725
		}
726
		$segs = explode('.', basename($url));
727
		if (isset($segs[2])) {
728
			return $segs[count($segs) - 2];
729
		} else {
730
			return '';
731
		}
732
	}
733
734
	/**
735
	 * Raises OnSaveStateComplete event.
736
	 * This method is invoked right after {@see onSaveState OnSaveState} stage.
737
	 * You may override this method to provide additional logic after page state is saved.
738
	 * Remember to call the parent implementation to ensure OnSaveStateComplete event is raised.
739
	 * @param mixed $param event parameter
740
	 */
741
	public function onSaveStateComplete($param)
742
	{
743
		$this->raiseEvent('OnSaveStateComplete', $this, $param);
744
	}
745
746
	/**
747
	 * Determines whether the current page request is a postback.
748
	 * Call {@see getIsPostBack} to get the result.
749
	 */
750
	private function determinePostBackMode()
751
	{
752
		$postData = $this->getRequest();
753
		if ($postData->contains(self::FIELD_PAGESTATE) || $postData->contains(self::FIELD_POSTBACK_TARGET)) {
754
			$this->_postData = $postData;
0 ignored issues
show
Documentation Bug introduced by
It seems like $postData of type Prado\Web\THttpRequest is incompatible with the declared type Prado\Collections\TMap of property $_postData.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
755
		}
756
	}
757
758
	/**
759
	 * @return bool whether the current page request is a postback
760
	 */
761
	public function getIsPostBack()
762
	{
763
		return $this->_postData !== null;
764
	}
765
766
	/**
767
	 * @return bool whether this is a callback request
768
	 */
769
	public function getIsCallback()
770
	{
771
		return $this->getIsPostBack() && $this->getRequest()->contains(self::FIELD_CALLBACK_TARGET);
772
	}
773
774
	/**
775
	 * This method is invoked when control state is to be saved.
776
	 * You can override this method to do last step state saving.
777
	 * Parent implementation must be invoked.
778
	 */
779
	public function saveState()
780
	{
781
		parent::saveState();
782
		$this->setViewState('ControlsRequiringPostBack', $this->_controlsRegisteredForPostData, []);
783
	}
784
785
	/**
786
	 * This method is invoked right after the control has loaded its state.
787
	 * You can override this method to initialize data from the control state.
788
	 * Parent implementation must be invoked.
789
	 */
790
	public function loadState()
791
	{
792
		parent::loadState();
793
		$this->_controlsRequiringPostData = $this->getViewState('ControlsRequiringPostBack', []);
794
	}
795
796
	/**
797
	 * Loads page state from persistent storage.
798
	 */
799
	protected function loadPageState()
800
	{
801
		Prado::trace("Loading state", TPage::class);
802
		$state = $this->getStatePersister()->load();
803
		$this->loadStateRecursive($state, $this->getEnableViewState());
804
	}
805
806
	/**
807
	 * Saves page state from persistent storage.
808
	 */
809
	protected function savePageState()
810
	{
811
		Prado::trace("Saving state", TPage::class);
812
		$state = &$this->saveStateRecursive($this->getEnableViewState());
813
		$this->getStatePersister()->save($state);
814
	}
815
816
	/**
817
	 * @param string $field the field name
818
	 * @return bool whether the specified field is a system field in postback data
819
	 */
820
	protected function isSystemPostField($field)
821
	{
822
		return isset(self::$_systemPostFields[$field]);
823
	}
824
825
	/**
826
	 * Registers a control for loading post data in the next postback.
827
	 * This method needs to be invoked if the control to load post data
828
	 * may not have a post variable in some cases. For example, a checkbox,
829
	 * if not checked, will not have a post value.
830
	 * @param \Prado\Web\UI\TControl $control control registered for loading post data
831
	 */
832
	public function registerRequiresPostData($control)
833
	{
834
		$id = is_string($control) ? $control : $control->getUniqueID();
835
		$this->_controlsRegisteredForPostData[$id] = true;
836
		$params = func_get_args();
0 ignored issues
show
Unused Code introduced by
The assignment to $params is dead and can be removed.
Loading history...
837
		foreach ($this->getCachingStack() as $item) {
838
			$item->registerAction('Page', 'registerRequiresPostData', [$id]);
839
		}
840
	}
841
842
	/**
843
	 * @return \Prado\Web\UI\TControl the control responsible for the current postback event, null if nonexistent
844
	 */
845
	public function getPostBackEventTarget()
846
	{
847
		if ($this->_postBackEventTarget === null && $this->_postData !== null) {
848
			$eventTarget = $this->_postData->itemAt(self::FIELD_POSTBACK_TARGET);
849
			if (!empty($eventTarget)) {
850
				$this->_postBackEventTarget = $this->findControl($eventTarget);
851
			}
852
		}
853
		return $this->_postBackEventTarget;
854
	}
855
856
	/**
857
	 * Registers a control to raise postback event in the current request.
858
	 * @param \Prado\Web\UI\TControl $control control registered to raise postback event.
859
	 */
860
	public function setPostBackEventTarget(TControl $control)
861
	{
862
		$this->_postBackEventTarget = $control;
863
	}
864
865
	/**
866
	 * @return string postback event parameter
867
	 */
868
	public function getPostBackEventParameter()
869
	{
870
		if ($this->_postBackEventParameter === null && $this->_postData !== null) {
871
			if (($this->_postBackEventParameter = $this->_postData->itemAt(self::FIELD_POSTBACK_PARAMETER)) === null) {
872
				$this->_postBackEventParameter = '';
873
			}
874
		}
875
		return $this->_postBackEventParameter;
876
	}
877
878
	/**
879
	 * @param string $value postback event parameter
880
	 */
881
	public function setPostBackEventParameter($value)
882
	{
883
		$this->_postBackEventParameter = $value;
884
	}
885
886
	/**
887
	 * Processes post data.
888
	 * @param TMap $postData post data to be processed
889
	 * @param bool $beforeLoad whether this method is invoked before {@see onLoad OnLoad}.
890
	 */
891
	protected function processPostData($postData, $beforeLoad)
892
	{
893
		$this->_isLoadingPostData = true;
894
		if ($beforeLoad) {
895
			$this->_restPostData = new TMap();
896
		}
897
		foreach ($postData as $key => $value) {
898
			if ($this->isSystemPostField($key)) {
899
				continue;
900
			} elseif ($control = $this->findControl($key)) {
901
				if ($control instanceof \Prado\Web\UI\IPostBackDataHandler) {
902
					if ($control->loadPostData($key, $postData)) {
0 ignored issues
show
Bug introduced by
$postData of type Prado\Collections\TMap is incompatible with the type array expected by parameter $values of Prado\Web\UI\IPostBackDataHandler::loadPostData(). ( Ignorable by Annotation )

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

902
					if ($control->loadPostData($key, /** @scrutinizer ignore-type */ $postData)) {
Loading history...
903
						$this->_controlsPostDataChanged[] = $control;
904
					}
905
				} elseif ($control instanceof IPostBackEventHandler &&
906
					empty($this->_postData[self::FIELD_POSTBACK_TARGET])) {
907
					// not calling setPostBackEventTarget() because the control may be removed later
908
					$this->_postData->add(self::FIELD_POSTBACK_TARGET, $key);
909
				}
910
				unset($this->_controlsRequiringPostData[$key]);
911
			} elseif ($beforeLoad) {
912
				$this->_restPostData->add($key, $value);
913
			}
914
		}
915
916
		foreach ($this->_controlsRequiringPostData as $key => $value) {
917
			if ($control = $this->findControl($key)) {
918
				if ($control instanceof \Prado\Web\UI\IPostBackDataHandler) {
919
					if ($control->loadPostData($key, $this->_postData)) {
920
						$this->_controlsPostDataChanged[] = $control;
921
					}
922
				} else {
923
					throw new TInvalidDataValueException('page_postbackcontrol_invalid', $key);
924
				}
925
				unset($this->_controlsRequiringPostData[$key]);
926
			}
927
		}
928
		$this->_isLoadingPostData = false;
929
	}
930
931
	/**
932
	 * @return bool true if loading post data.
933
	 */
934
	public function getIsLoadingPostData()
935
	{
936
		return $this->_isLoadingPostData;
937
	}
938
939
	/**
940
	 * Raises OnPostDataChangedEvent for controls whose data have been changed due to the postback.
941
	 */
942
	protected function raiseChangedEvents()
943
	{
944
		foreach ($this->_controlsPostDataChanged as $control) {
945
			$control->raisePostDataChangedEvent();
946
		}
947
	}
948
949
	/**
950
	 * Raises PostBack event.
951
	 */
952
	protected function raisePostBackEvent()
953
	{
954
		if (($postBackHandler = $this->getPostBackEventTarget()) === null) {
955
			$this->validate();
956
		} elseif ($postBackHandler instanceof IPostBackEventHandler) {
957
			$postBackHandler->raisePostBackEvent($this->getPostBackEventParameter());
0 ignored issues
show
Bug introduced by
The method raisePostBackEvent() 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

957
			$postBackHandler->/** @scrutinizer ignore-call */ 
958
                     raisePostBackEvent($this->getPostBackEventParameter());
Loading history...
958
		}
959
	}
960
961
	/**
962
	 * @return bool Whether form rendering is in progress
963
	 */
964
	public function getInFormRender()
965
	{
966
		return $this->_inFormRender;
967
	}
968
969
	/**
970
	 * Ensures the control is rendered within a form.
971
	 * @param \Prado\Web\UI\TControl $control the control to be rendered
972
	 * @throws TConfigurationException if the control is outside of the form
973
	 */
974
	public function ensureRenderInForm($control)
975
	{
976
		if (!$this->getIsCallback() && !$this->_inFormRender) {
977
			throw new TConfigurationException('page_control_outofform', $control::class, $control ? $control->getUniqueID() : null);
0 ignored issues
show
introduced by
$control is of type Prado\Web\UI\TControl, thus it always evaluated to true.
Loading history...
978
		}
979
	}
980
981
	/**
982
	 * @internal This method is invoked by TForm at the beginning of its rendering
983
	 * @param mixed $writer
984
	 */
985
	public function beginFormRender($writer)
0 ignored issues
show
Unused Code introduced by
The parameter $writer is not used and could be removed. ( Ignorable by Annotation )

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

985
	public function beginFormRender(/** @scrutinizer ignore-unused */ $writer)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
986
	{
987
		if ($this->_formRendered) {
988
			throw new TConfigurationException('page_form_duplicated');
989
		}
990
		$this->_formRendered = true;
991
		$this->getClientScript()->registerHiddenField(self::FIELD_PAGESTATE, $this->getClientState());
992
		$this->_inFormRender = true;
993
	}
994
995
	/**
996
	 * @internal This method is invoked by TForm  at the end of its rendering
997
	 * @param mixed $writer
998
	 */
999
	public function endFormRender($writer)
0 ignored issues
show
Unused Code introduced by
The parameter $writer is not used and could be removed. ( Ignorable by Annotation )

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

999
	public function endFormRender(/** @scrutinizer ignore-unused */ $writer)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1000
	{
1001
		if ($this->_focus) {
1002
			if (($this->_focus instanceof TControl) && $this->_focus->getVisible(true)) {
1003
				$focus = $this->_focus->getClientID();
1004
			} else {
1005
				$focus = $this->_focus;
1006
			}
1007
			$this->getClientScript()->registerFocusControl($focus);
1008
		} elseif ($this->_postData && ($lastFocus = $this->_postData->itemAt(self::FIELD_LASTFOCUS)) !== null) {
1009
			$this->getClientScript()->registerFocusControl($lastFocus);
1010
		}
1011
		$this->_inFormRender = false;
1012
	}
1013
1014
	/**
1015
	 * Sets input focus on a control after the page is rendered to users.
1016
	 * @param string|TControl $value control to receive focus, or the ID of the element on the page to receive focus
1017
	 */
1018
	public function setFocus($value)
1019
	{
1020
		$this->_focus = $value;
1021
	}
1022
1023
	/**
1024
	 * @return bool whether client supports javascript. Defaults to true.
1025
	 */
1026
	public function getClientSupportsJavaScript()
1027
	{
1028
		return $this->_enableJavaScript;
1029
	}
1030
1031
	/**
1032
	 * @param bool $value whether client supports javascript. If false, javascript will not be generated for controls.
1033
	 */
1034
	public function setClientSupportsJavaScript($value)
1035
	{
1036
		$this->_enableJavaScript = TPropertyValue::ensureBoolean($value);
1037
	}
1038
1039
	/**
1040
	 * @return THead page head, null if not available
1041
	 */
1042
	public function getHead()
1043
	{
1044
		return $this->_head;
1045
	}
1046
1047
	/**
1048
	 * @param THead $value page head
1049
	 * @throws TInvalidOperationException if a head already exists
1050
	 */
1051
	public function setHead(THead $value)
1052
	{
1053
		if ($this->_head) {
1054
			throw new TInvalidOperationException('page_head_duplicated');
1055
		}
1056
		$this->_head = $value;
1057
		if ($this->_title !== null) {
1058
			$this->_head->setTitle($this->_title);
1059
			$this->_title = null;
1060
		}
1061
	}
1062
1063
	/**
1064
	 * @return string page title.
1065
	 */
1066
	public function getTitle()
1067
	{
1068
		if ($this->_head) {
1069
			return $this->_head->getTitle();
1070
		} else {
1071
			return $this->_title === null ? '' : $this->_title;
1072
		}
1073
	}
1074
1075
	/**
1076
	 * Sets the page title.
1077
	 * Note, a {@see \Prado\Web\UI\WebControls\THead} control needs to place on the page
1078
	 * in order that this title be rendered.
1079
	 * @param string $value page title. This will override the title set in {@see getHead Head}.
1080
	 */
1081
	public function setTitle($value)
1082
	{
1083
		if ($this->_head) {
1084
			$this->_head->setTitle($value);
1085
		} else {
1086
			$this->_title = $value;
1087
		}
1088
	}
1089
1090
	/**
1091
	 * Returns the state to be stored on the client side.
1092
	 * This method should only be used by framework and control developers.
1093
	 * @return string the state to be stored on the client side
1094
	 */
1095
	public function getClientState()
1096
	{
1097
		return $this->_clientState;
1098
	}
1099
1100
	/**
1101
	 * Sets the state to be stored on the client side.
1102
	 * This method should only be used by framework and control developers.
1103
	 * @param string $state the state to be stored on the client side
1104
	 */
1105
	public function setClientState($state)
1106
	{
1107
		$this->_clientState = $state;
1108
	}
1109
1110
	/**
1111
	 * @return string the state postback from client side
1112
	 */
1113
	public function getRequestClientState()
1114
	{
1115
		return $this->getRequest()->itemAt(self::FIELD_PAGESTATE);
1116
	}
1117
1118
	/**
1119
	 * @return string class name of the page state persister. Defaults to TPageStatePersister.
1120
	 */
1121
	public function getStatePersisterClass()
1122
	{
1123
		return $this->_statePersisterClass;
1124
	}
1125
1126
	/**
1127
	 * @param string $value class name of the page state persister.
1128
	 */
1129
	public function setStatePersisterClass($value)
1130
	{
1131
		$this->_statePersisterClass = $value;
1132
	}
1133
1134
	/**
1135
	 * @return IPageStatePersister page state persister
1136
	 */
1137
	public function getStatePersister()
1138
	{
1139
		if ($this->_statePersister === null) {
1140
			$this->_statePersister = Prado::createComponent($this->_statePersisterClass);
1141
			if (!($this->_statePersister instanceof IPageStatePersister)) {
1142
				throw new TInvalidDataTypeException('page_statepersister_invalid');
1143
			}
1144
			$this->_statePersister->setPage($this);
1145
		}
1146
		return $this->_statePersister;
1147
	}
1148
1149
	/**
1150
	 * @return bool whether page state should be HMAC validated. Defaults to true.
1151
	 */
1152
	public function getEnableStateValidation()
1153
	{
1154
		return $this->_enableStateValidation;
1155
	}
1156
1157
	/**
1158
	 * @param bool $value whether page state should be HMAC validated.
1159
	 */
1160
	public function setEnableStateValidation($value)
1161
	{
1162
		$this->_enableStateValidation = TPropertyValue::ensureBoolean($value);
1163
	}
1164
1165
	/**
1166
	 * @return bool whether page state should be encrypted. Defaults to false.
1167
	 */
1168
	public function getEnableStateEncryption()
1169
	{
1170
		return $this->_enableStateEncryption;
1171
	}
1172
1173
	/**
1174
	 * @param bool $value whether page state should be encrypted.
1175
	 */
1176
	public function setEnableStateEncryption($value)
1177
	{
1178
		$this->_enableStateEncryption = TPropertyValue::ensureBoolean($value);
1179
	}
1180
1181
	/**
1182
	 * @return bool whether page state should be compressed. Defaults to true.
1183
	 * @since 3.1.6
1184
	 */
1185
	public function getEnableStateCompression()
1186
	{
1187
		return $this->_enableStateCompression;
1188
	}
1189
1190
	/**
1191
	 * @param bool $value whether page state should be compressed.
1192
	 * @since 3.1.6
1193
	 */
1194
	public function setEnableStateCompression($value)
1195
	{
1196
		$this->_enableStateCompression = TPropertyValue::ensureBoolean($value);
1197
	}
1198
1199
	/**
1200
	 * @return bool whether page state should be serialized using igbinary if available. Defaults to true.
1201
	 * @since 4.1
1202
	 */
1203
	public function getEnableStateIGBinary()
1204
	{
1205
		return $this->_enableStateIGBinary;
1206
	}
1207
1208
	/**
1209
	 * @param bool $value whether page state should be serialized using igbinary if available.
1210
	 * @since 4.1
1211
	 */
1212
	public function setEnableStateIGBinary($value)
1213
	{
1214
		$this->_enableStateIGBinary = TPropertyValue::ensureBoolean($value);
1215
	}
1216
1217
	/**
1218
	 * @return string the requested page path for this page
1219
	 */
1220
	public function getPagePath()
1221
	{
1222
		return $this->_pagePath;
1223
	}
1224
1225
	/**
1226
	 * @param string $value the requested page path for this page
1227
	 */
1228
	public function setPagePath($value)
1229
	{
1230
		$this->_pagePath = $value;
1231
	}
1232
1233
	/**
1234
	 * Registers an action associated with the content being cached.
1235
	 * The registered action will be replayed if the content stored
1236
	 * in the cache is served to end-users.
1237
	 * @param string $context context of the action method. This is a property-path
1238
	 * referring to the context object (e.g. Page, Page.ClientScript).
1239
	 * @param string $funcName method name of the context object
1240
	 * @param array $funcParams list of parameters to be passed to the action method
1241
	 */
1242
	public function registerCachingAction($context, $funcName, $funcParams)
1243
	{
1244
		if ($this->_cachingStack) {
1245
			foreach ($this->_cachingStack as $cache) {
1246
				$cache->registerAction($context, $funcName, $funcParams);
1247
			}
1248
		}
1249
	}
1250
1251
	/**
1252
	 * @return TStack stack of {@see \Prado\Web\UI\WebControls\TOutputCache} objects
1253
	 */
1254
	public function getCachingStack()
1255
	{
1256
		if (!$this->_cachingStack) {
1257
			$this->_cachingStack = new TStack();
1258
		}
1259
		return $this->_cachingStack;
1260
	}
1261
1262
	/**
1263
	 * Flushes output
1264
	 */
1265
	public function flushWriter()
1266
	{
1267
		if ($this->_writer) {
1268
			$this->getResponse()->write($this->_writer->flush());
1269
		}
1270
	}
1271
}
1272