Passed
Push — master ( dc84c0...f35cb6 )
by Fabio
06:45
created

TPage::setCallbackClient()   A

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 1
dl 0
loc 3
ccs 0
cts 1
cp 0
crap 2
rs 10
1
<?php
2
/**
3
 * TPage class file
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 * @package Prado\Web\UI
9
 */
10
11
namespace Prado\Web\UI;
12
13
use 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
 * @package Prado\Web\UI
33
 * @since 3.0
34
 * @method TActivePageAdapter getAdapter()
35
 * @method \Prado\Web\Services\TPageService getService()
36
 */
37
class TPage extends TTemplateControl
38
{
39
	/**
40
	 * system post fields
41
	 */
42
	public const FIELD_POSTBACK_TARGET = 'PRADO_POSTBACK_TARGET';
43
	public const FIELD_POSTBACK_PARAMETER = 'PRADO_POSTBACK_PARAMETER';
44
	public const FIELD_LASTFOCUS = 'PRADO_LASTFOCUS';
45
	public const FIELD_PAGESTATE = 'PRADO_PAGESTATE';
46
	public const FIELD_CALLBACK_TARGET = 'PRADO_CALLBACK_TARGET';
47
	public const FIELD_CALLBACK_PARAMETER = 'PRADO_CALLBACK_PARAMETER';
48
49
	/**
50
	 * @var array system post fields
51
	 */
52
	private static $_systemPostFields = [
53
		'PRADO_POSTBACK_TARGET' => true,
54
		'PRADO_POSTBACK_PARAMETER' => true,
55
		'PRADO_LASTFOCUS' => true,
56
		'PRADO_PAGESTATE' => true,
57
		'PRADO_CALLBACK_TARGET' => true,
58
		'PRADO_CALLBACK_PARAMETER' => true
59
	];
60
	/**
61
	 * @var TForm form instance
62
	 */
63
	private $_form;
64
	/**
65
	 * @var THead head instance
66
	 */
67
	private $_head;
68
	/**
69
	 * @var TList list of registered validators
70
	 */
71
	private $_validators;
72
	/**
73
	 * @var bool if validation has been performed
74
	 */
75
	private $_validated = false;
76
	/**
77
	 * @var TTheme page theme
78
	 */
79
	private $_theme;
80
	/**
81
	 * @var string page title set when Head is not in page yet
82
	 */
83
	private $_title;
84
	/**
85
	 * @var TTheme page stylesheet theme
86
	 */
87
	private $_styleSheet;
88
	/**
89
	 * @var TClientScriptManager client script manager
90
	 */
91
	private $_clientScript;
92
	/**
93
	 * @var TMap data post back by user
94
	 */
95
	protected $_postData;
96
	/**
97
	 * @var TMap postback data that is not handled during first invocation of LoadPostData.
98
	 */
99
	protected $_restPostData;
100
	/**
101
	 * @var array list of controls whose data have been changed due to the postback
102
	 */
103
	protected $_controlsPostDataChanged = [];
104
	/**
105
	 * @var array list of controls that need to load post data in the current request
106
	 */
107
	protected $_controlsRequiringPostData = [];
108
	/**
109
	 * @var array list of controls that need to load post data in the next postback
110
	 */
111
	protected $_controlsRegisteredForPostData = [];
112
	/**
113
	 * @var \Prado\Web\UI\TControl control that needs to raise postback event
114
	 */
115
	private $_postBackEventTarget;
116
	/**
117
	 * @var string postback event parameter
118
	 */
119
	private $_postBackEventParameter;
120
	/**
121
	 * @var bool whether the form has been rendered
122
	 */
123
	protected $_formRendered = false;
124
	/**
125
	 * @var bool whether the current rendering is within a form
126
	 */
127
	protected $_inFormRender = false;
128
	/**
129
	 * @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
130
	 */
131
	private $_focus;
132
	/**
133
	 * @var string page path to this page
134
	 */
135
	private $_pagePath = '';
136
	/**
137
	 * @var bool whether page state should be HMAC validated
138
	 */
139
	private $_enableStateValidation = true;
140
	/**
141
	 * @var bool whether page state should be encrypted
142
	 */
143
	private $_enableStateEncryption = false;
144
	/**
145
	 * @var bool whether page state should be compressed
146
	 * @since 3.1.6
147
	 */
148
	private $_enableStateCompression = true;
149
	/**
150
	 * @var bool whether to use the igbinary serializer if available
151
	 * @since 4.1
152
	 */
153
	private $_enableStateIGBinary = true;
154
	/**
155
	 * @var string page state persister class name
156
	 */
157
	private $_statePersisterClass = '\Prado\Web\UI\TPageStatePersister';
158
	/**
159
	 * @var mixed page state persister
160
	 */
161
	private $_statePersister;
162
	/**
163
	 * @var TStack stack used to store currently active caching controls
164
	 */
165
	private $_cachingStack;
166
	/**
167
	 * @var string state string to be stored on the client side
168
	 */
169
	private $_clientState = '';
170
	/**
171
	 * @var bool true if loading post data.
172
	 */
173
	protected $_isLoadingPostData = false;
174
	/**
175
	 * @var bool whether client supports javascript
176
	 */
177
	private $_enableJavaScript = true;
178
	/**
179
	 * @var THtmlWriter current html render writer
180
	 */
181
	private $_writer;
182
183
	/**
184
	 * Constructor.
185
	 * Sets the page object to itself.
186
	 * Derived classes must call parent implementation.
187
	 */
188
	public function __construct()
189
	{
190
		$this->setPage($this);
191
		parent::__construct();
192
	}
193
194
	/**
195
	 * TPage does autoGlobalListen and unlisten.  Developers
196
	 * may put fx events in the page that need to be picked up.
197
	 *
198
	 * @return bool returns true
199
	 */
200
	public function getAutoGlobalListen()
201
	{
202
		return true;
203
	}
204
205
	/**
206
	 * Runs through the page lifecycles.
207
	 * @param \Prado\Web\UI\THtmlWriter $writer the HTML writer
208
	 */
209
	public function run($writer)
210
	{
211
		Prado::trace("Running page life cycles", 'Prado\Web\UI\TPage');
212
		$this->_writer = $writer;
213
214
		$this->determinePostBackMode();
215
216
		if ($this->getIsPostBack()) {
217
			if ($this->getIsCallback()) {
218
				$this->processCallbackRequest($writer);
219
			} else {
220
				$this->processPostBackRequest($writer);
221
			}
222
		} else {
223
			$this->processNormalRequest($writer);
224
		}
225
226
		$this->_writer = null;
227
	}
228
229
	protected function processNormalRequest($writer)
230
	{
231
		Prado::trace("Page onPreInit()", 'Prado\Web\UI\TPage');
232
		$this->onPreInit(null);
233
234
		Prado::trace("Page initRecursive()", 'Prado\Web\UI\TPage');
235
		$this->initRecursive();
236
237
		Prado::trace("Page onInitComplete()", 'Prado\Web\UI\TPage');
238
		$this->onInitComplete(null);
239
240
		Prado::trace("Page onPreLoad()", 'Prado\Web\UI\TPage');
241
		$this->onPreLoad(null);
242
		Prado::trace("Page loadRecursive()", 'Prado\Web\UI\TPage');
243
		$this->loadRecursive();
244
		Prado::trace("Page onLoadComplete()", 'Prado\Web\UI\TPage');
245
		$this->onLoadComplete(null);
246
247
		Prado::trace("Page preRenderRecursive()", 'Prado\Web\UI\TPage');
248
		$this->preRenderRecursive();
249
		Prado::trace("Page onPreRenderComplete()", 'Prado\Web\UI\TPage');
250
		$this->onPreRenderComplete(null);
251
252
		Prado::trace("Page savePageState()", 'Prado\Web\UI\TPage');
253
		$this->savePageState();
254
		Prado::trace("Page onSaveStateComplete()", 'Prado\Web\UI\TPage');
255
		$this->onSaveStateComplete(null);
256
257
		Prado::trace("Page renderControl()", 'Prado\Web\UI\TPage');
258
		$this->renderControl($writer);
259
		Prado::trace("Page unloadRecursive()", 'Prado\Web\UI\TPage');
260
		$this->unloadRecursive();
261
	}
262
263
	protected function processPostBackRequest($writer)
264
	{
265
		Prado::trace("Page onPreInit()", 'Prado\Web\UI\TPage');
266
		$this->onPreInit(null);
267
268
		Prado::trace("Page initRecursive()", 'Prado\Web\UI\TPage');
269
		$this->initRecursive();
270
271
		Prado::trace("Page onInitComplete()", 'Prado\Web\UI\TPage');
272
		$this->onInitComplete(null);
273
274
		$this->_restPostData = new TMap;
275
		Prado::trace("Page loadPageState()", 'Prado\Web\UI\TPage');
276
		$this->loadPageState();
277
		Prado::trace("Page processPostData()", 'Prado\Web\UI\TPage');
278
		$this->processPostData($this->_postData, true);
279
		Prado::trace("Page onPreLoad()", 'Prado\Web\UI\TPage');
280
		$this->onPreLoad(null);
281
		Prado::trace("Page loadRecursive()", 'Prado\Web\UI\TPage');
282
		$this->loadRecursive();
283
		Prado::trace("Page processPostData()", 'Prado\Web\UI\TPage');
284
		$this->processPostData($this->_restPostData, false);
285
		Prado::trace("Page raiseChangedEvents()", 'Prado\Web\UI\TPage');
286
		$this->raiseChangedEvents();
287
		Prado::trace("Page raisePostBackEvent()", 'Prado\Web\UI\TPage');
288
		$this->raisePostBackEvent();
289
		Prado::trace("Page onLoadComplete()", 'Prado\Web\UI\TPage');
290
		$this->onLoadComplete(null);
291
292
		Prado::trace("Page preRenderRecursive()", 'Prado\Web\UI\TPage');
293
		$this->preRenderRecursive();
294
		Prado::trace("Page onPreRenderComplete()", 'Prado\Web\UI\TPage');
295
		$this->onPreRenderComplete(null);
296
297
		Prado::trace("Page savePageState()", 'Prado\Web\UI\TPage');
298
		$this->savePageState();
299
		Prado::trace("Page onSaveStateComplete()", 'Prado\Web\UI\TPage');
300
		$this->onSaveStateComplete(null);
301
302
		Prado::trace("Page renderControl()", 'Prado\Web\UI\TPage');
303
		$this->renderControl($writer);
304
		Prado::trace("Page unloadRecursive()", 'Prado\Web\UI\TPage');
305
		$this->unloadRecursive();
306
	}
307
308
	protected static function decodeUTF8($data, $enc)
309
	{
310
		if (is_array($data)) {
311
			foreach ($data as $k => $v) {
312
				$data[$k] = self::decodeUTF8($v, $enc);
313
			}
314
			return $data;
315
		} elseif (is_string($data)) {
316
			return iconv('UTF-8', $enc . '//IGNORE', $data);
317
		} else {
318
			return $data;
319
		}
320
	}
321
322
	/**
323
	 * Sets Adapter to TActivePageAdapter and calls apter to process the
324
	 * callback request.
325
	 * @param mixed $writer
326
	 */
327
	protected function processCallbackRequest($writer)
328
	{
329
		$this->setAdapter(new TActivePageAdapter($this));
330
331
		$callbackEventParameter = $this->getRequest()->itemAt(TPage::FIELD_CALLBACK_PARAMETER);
332
		if (strlen($callbackEventParameter) > 0) {
0 ignored issues
show
Bug introduced by
It seems like $callbackEventParameter can also be of type null; however, parameter $string of strlen() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

332
		if (strlen(/** @scrutinizer ignore-type */ $callbackEventParameter) > 0) {
Loading history...
333
			$this->_postData[TPage::FIELD_CALLBACK_PARAMETER] = TJavaScript::jsonDecode((string) $callbackEventParameter);
334
		}
335
336
		// Decode Callback postData from UTF-8 to current Charset
337
		if (($g = $this->getApplication()->getGlobalization(false)) !== null &&
338
			strtoupper($enc = $g->getCharset()) != 'UTF-8') {
339
			foreach ($this->_postData as $k => $v) {
340
				$this->_postData[$k] = self::decodeUTF8($v, $enc);
341
			}
342
		}
343
344
		Prado::trace("Page onPreInit()", 'Prado\Web\UI\TPage');
345
		$this->onPreInit(null);
346
347
		Prado::trace("Page initRecursive()", 'Prado\Web\UI\TPage');
348
		$this->initRecursive();
349
350
		Prado::trace("Page onInitComplete()", 'Prado\Web\UI\TPage');
351
		$this->onInitComplete(null);
352
353
		$this->_restPostData = new TMap;
354
		Prado::trace("Page loadPageState()", 'Prado\Web\UI\TPage');
355
		$this->loadPageState();
356
		Prado::trace("Page processPostData()", 'Prado\Web\UI\TPage');
357
		$this->processPostData($this->_postData, true);
358
		Prado::trace("Page onPreLoad()", 'Prado\Web\UI\TPage');
359
		$this->onPreLoad(null);
360
		Prado::trace("Page loadRecursive()", 'Prado\Web\UI\TPage');
361
		$this->loadRecursive();
362
363
		Prado::trace("Page processPostData()", 'Prado\Web\UI\TPage');
364
		$this->processPostData($this->_restPostData, false);
365
366
		Prado::trace("Page raiseChangedEvents()", 'Prado\Web\UI\TPage');
367
		$this->raiseChangedEvents();
368
369
370
		$this->getAdapter()->processCallbackEvent($writer);
371
372
		/*
373
				Prado::trace("Page raisePostBackEvent()",'Prado\Web\UI\TPage');
374
				$this->raisePostBackEvent();
375
		*/
376
		Prado::trace("Page onLoadComplete()", 'Prado\Web\UI\TPage');
377
		$this->onLoadComplete(null);
378
379
		Prado::trace("Page preRenderRecursive()", 'Prado\Web\UI\TPage');
380
		$this->preRenderRecursive();
381
		Prado::trace("Page onPreRenderComplete()", 'Prado\Web\UI\TPage');
382
		$this->onPreRenderComplete(null);
383
384
		Prado::trace("Page savePageState()", 'Prado\Web\UI\TPage');
385
		$this->savePageState();
386
		Prado::trace("Page onSaveStateComplete()", 'Prado\Web\UI\TPage');
387
		$this->onSaveStateComplete(null);
388
389
		/*
390
				Prado::trace("Page renderControl()",'Prado\Web\UI\TPage');
391
				$this->renderControl($writer);
392
		*/
393
		$this->getAdapter()->renderCallbackResponse($writer);
394
395
		Prado::trace("Page unloadRecursive()", 'Prado\Web\UI\TPage');
396
		$this->unloadRecursive();
397
	}
398
399
	/**
400
	 * Gets the callback client script handler that allows javascript functions
401
	 * to be executed during the callback response.
402
	 * @return TCallbackClientScript interface to client-side javascript code.
403
	 */
404
	public function getCallbackClient()
405
	{
406
		if ($this->getAdapter() !== null) {
407
			return $this->getAdapter()->getCallbackClientHandler();
408
		} else {
409
			return new TCallbackClientScript();
410
		}
411
	}
412
413
	/**
414
	 * @return \Prado\Web\UI\TControl the control responsible for the current callback event,
415
	 * null if nonexistent
416
	 */
417
	public function getCallbackEventTarget()
418
	{
419
		return $this->getAdapter()->getCallbackEventTarget();
420
	}
421
422
	/**
423
	 * Registers a control to raise callback event in the current request.
424
	 * @param \Prado\Web\UI\TControl $control control registered to raise callback event.
425
	 */
426
	public function setCallbackEventTarget(TControl $control)
427
	{
428
		$this->getAdapter()->setCallbackEventTarget($control);
429
	}
430
431
	/**
432
	 * Callback parameter is decoded assuming JSON encoding.
433
	 * @return string callback event parameter
434
	 */
435
	public function getCallbackEventParameter()
436
	{
437
		return $this->getAdapter()->getCallbackEventParameter();
438
	}
439
440
	/**
441
	 * @param mixed $value callback event parameter
442
	 */
443
	public function setCallbackEventParameter($value)
444
	{
445
		$this->getAdapter()->setCallbackEventParameter($value);
446
	}
447
448
	/**
449
	 * @return TForm the form on the page
450
	 */
451
	public function getForm()
452
	{
453
		return $this->_form;
454
	}
455
456
	/**
457
	 * Registers a TForm instance to the page.
458
	 * Note, a page can contain at most one TForm instance.
459
	 * @param TForm $form the form on the page
460
	 * @throws TInvalidOperationException if this method is invoked twice or more.
461
	 */
462
	public function setForm(TForm $form)
463
	{
464
		if ($this->_form === null) {
465
			$this->_form = $form;
466
		} else {
467
			throw new TInvalidOperationException('page_form_duplicated');
468
		}
469
	}
470
471
	/**
472
	 * Returns a list of registered validators.
473
	 * If validation group is specified, only the validators in that group will be returned.
474
	 * @param null|string $validationGroup validation group
475
	 * @return TList registered validators in the requested group. If the group is null, all validators will be returned.
476
	 */
477
	public function getValidators($validationGroup = null)
478
	{
479
		if (!$this->_validators) {
480
			$this->_validators = new TList;
481
		}
482
		if (empty($validationGroup) === true) {
483
			return $this->_validators;
484
		} else {
485
			$list = new TList;
486
			foreach ($this->_validators as $validator) {
487
				if ($validator->getValidationGroup() === $validationGroup) {
488
					$list->add($validator);
489
				}
490
			}
491
			return $list;
492
		}
493
	}
494
495
	/**
496
	 * Performs input validation.
497
	 * This method will invoke the registered validators to perform the actual validation.
498
	 * If validation group is specified, only the validators in that group will be invoked.
499
	 * @param string $validationGroup validation group. If null, all validators will perform validation.
500
	 */
501
	public function validate($validationGroup = null)
502
	{
503
		Prado::trace("Page validate()", 'Prado\Web\UI\TPage');
504
		$this->_validated = true;
505
		if ($this->_validators && $this->_validators->getCount()) {
506
			if ($validationGroup === null) {
507
				foreach ($this->_validators as $validator) {
508
					$validator->validate();
509
				}
510
			} else {
511
				foreach ($this->_validators as $validator) {
512
					if ($validator->getValidationGroup() === $validationGroup) {
513
						$validator->validate();
514
					}
515
				}
516
			}
517
		}
518
	}
519
520
	/**
521
	 * Returns whether user input is valid or not.
522
	 * This method must be invoked after {@link validate} is called.
523
	 * @throws TInvalidOperationException if {@link validate} is not invoked yet.
524
	 * @return bool whether the user input is valid or not.
525
	 */
526
	public function getIsValid()
527
	{
528
		if ($this->_validated) {
529
			if ($this->_validators && $this->_validators->getCount()) {
530
				foreach ($this->_validators as $validator) {
531
					if (!$validator->getIsValid()) {
532
						return false;
533
					}
534
				}
535
			}
536
			return true;
537
		} else {
538
			throw new TInvalidOperationException('page_isvalid_unknown');
539
		}
540
	}
541
542
	/**
543
	 * @return TTheme the theme used for the page. Defaults to null.
544
	 */
545
	public function getTheme()
546
	{
547
		if (is_string($this->_theme)) {
0 ignored issues
show
introduced by
The condition is_string($this->_theme) is always false.
Loading history...
548
			$this->_theme = $this->getService()->getThemeManager()->getTheme($this->_theme);
549
		}
550
		return $this->_theme;
551
	}
552
553
	/**
554
	 * Sets the theme to be used for the page.
555
	 * @param string|TTheme $value the theme name or the theme object to be used for the page.
556
	 */
557
	public function setTheme($value)
558
	{
559
		$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...
560
	}
561
562
563
	/**
564
	 * @return TTheme the stylesheet theme used for the page. Defaults to null.
565
	 */
566
	public function getStyleSheetTheme()
567
	{
568
		if (is_string($this->_styleSheet)) {
0 ignored issues
show
introduced by
The condition is_string($this->_styleSheet) is always false.
Loading history...
569
			$this->_styleSheet = $this->getService()->getThemeManager()->getTheme($this->_styleSheet);
570
		}
571
		return $this->_styleSheet;
572
	}
573
574
	/**
575
	 * Sets the stylesheet theme to be used for the page.
576
	 * @param string|TTheme $value the stylesheet theme name or the stylesheet theme object to be used for the page.
577
	 */
578
	public function setStyleSheetTheme($value)
579
	{
580
		$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...
581
	}
582
583
	/**
584
	 * Applies a skin in the current theme to a control.
585
	 * This method should only be used by framework developers.
586
	 * @param \Prado\Web\UI\TControl $control a control to be applied skin with
587
	 */
588
	public function applyControlSkin($control)
589
	{
590
		if (($theme = $this->getTheme()) !== null) {
591
			$theme->applySkin($control);
592
		}
593
	}
594
595
	/**
596
	 * Applies a stylesheet skin in the current theme to a control.
597
	 * This method should only be used by framework developers.
598
	 * @param \Prado\Web\UI\TControl $control a control to be applied stylesheet skin with
599
	 */
600
	public function applyControlStyleSheet($control)
601
	{
602
		if (($theme = $this->getStyleSheetTheme()) !== null) {
603
			$theme->applySkin($control);
604
		}
605
	}
606
607
	/**
608
	 * @return TClientScriptManager client script manager
609
	 */
610
	public function getClientScript()
611
	{
612
		if (!$this->_clientScript) {
613
			$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...
614
615
			if ($className !== '\Prado\Web\UI\TClientScriptManager' && !is_subclass_of($className, '\Prado\Web\UI\TClientScriptManager')) {
616
				throw new THttpException(404, 'page_csmanagerclass_invalid', $className);
617
			}
618
619
			$this->_clientScript = new $className($this);
620
		}
621
		return $this->_clientScript;
622
	}
623
624
	/**
625
	 * Raises OnPreInit event.
626
	 * This method is invoked right before {@link onInit OnInit} stage.
627
	 * You may override this method to provide additional initialization that
628
	 * should be done before {@link onInit OnInit} (e.g. setting {@link setTheme Theme} or
629
	 * {@link setStyleSheetTheme StyleSheetTheme}).
630
	 * Remember to call the parent implementation to ensure OnPreInit event is raised.
631
	 * @param mixed $param event parameter
632
	 */
633
	public function onPreInit($param)
634
	{
635
		$this->raiseEvent('OnPreInit', $this, $param);
636
	}
637
638
	/**
639
	 * Raises OnInitComplete event.
640
	 * This method is invoked right after {@link onInit OnInit} stage and before {@link onLoad OnLoad} stage.
641
	 * You may override this method to provide additional initialization that
642
	 * should be done after {@link onInit OnInit}.
643
	 * Remember to call the parent implementation to ensure OnInitComplete event is raised.
644
	 * @param mixed $param event parameter
645
	 */
646
	public function onInitComplete($param)
647
	{
648
		$this->raiseEvent('OnInitComplete', $this, $param);
649
	}
650
651
	/**
652
	 * Raises OnPreLoad event.
653
	 * This method is invoked right before {@link onLoad OnLoad} stage.
654
	 * You may override this method to provide additional page loading logic that
655
	 * should be done before {@link onLoad OnLoad}.
656
	 * Remember to call the parent implementation to ensure OnPreLoad event is raised.
657
	 * @param mixed $param event parameter
658
	 */
659
	public function onPreLoad($param)
660
	{
661
		$this->raiseEvent('OnPreLoad', $this, $param);
662
	}
663
664
	/**
665
	 * Raises OnLoadComplete event.
666
	 * This method is invoked right after {@link onLoad OnLoad} stage.
667
	 * You may override this method to provide additional page loading logic that
668
	 * should be done after {@link onLoad OnLoad}.
669
	 * Remember to call the parent implementation to ensure OnLoadComplete event is raised.
670
	 * @param mixed $param event parameter
671
	 */
672
	public function onLoadComplete($param)
673
	{
674
		$this->raiseEvent('OnLoadComplete', $this, $param);
675
	}
676
677
	/**
678
	 * Raises OnPreRenderComplete event.
679
	 * This method is invoked right after {@link onPreRender OnPreRender} stage.
680
	 * You may override this method to provide additional preparation for page rendering
681
	 * that should be done after {@link onPreRender OnPreRender}.
682
	 * Remember to call the parent implementation to ensure OnPreRenderComplete event is raised.
683
	 * @param mixed $param event parameter
684
	 */
685
	public function onPreRenderComplete($param)
686
	{
687
		$this->raiseEvent('OnPreRenderComplete', $this, $param);
688
		$cs = $this->getClientScript();
689
		$theme = $this->getTheme();
690
		if ($theme instanceof ITheme) {
0 ignored issues
show
introduced by
$theme is always a sub-type of Prado\Web\UI\ITheme.
Loading history...
691
			foreach ($theme->getStyleSheetFiles() as $url) {
692
				$cs->registerStyleSheetFile($url, $url, $this->getCssMediaType($url, $theme));
693
			}
694
			foreach ($theme->getJavaScriptFiles() as $url) {
695
				$cs->registerHeadScriptFile($url, $url);
696
			}
697
		}
698
		$styleSheet = $this->getStyleSheetTheme();
699
		if ($styleSheet instanceof ITheme) {
0 ignored issues
show
introduced by
$styleSheet is always a sub-type of Prado\Web\UI\ITheme.
Loading history...
700
			foreach ($styleSheet->getStyleSheetFiles() as $url) {
701
				$cs->registerStyleSheetFile($url, $url, $this->getCssMediaType($url, $styleSheet));
702
			}
703
			foreach ($styleSheet->getJavaScriptFiles() as $url) {
704
				$cs->registerHeadScriptFile($url, $url);
705
			}
706
		}
707
708
		if ($cs->getRequiresHead() && $this->getHead() === null) {
709
			throw new TConfigurationException('page_head_required');
710
		}
711
	}
712
713
	/**
714
	 * Determines the media type of the CSS file.
715
	 * The media type is determined according to the following file name pattern:
716
	 *        xxx.media-type.extension
717
	 * For example, 'mystyle.print.css' means its media type is 'print'.
718
	 * @param string $url CSS URL
719
	 * @param object $theme the theme being applied
720
	 * @return string media type of the CSS file
721
	 */
722
	private function getCssMediaType($url, $theme)
723
	{
724
		if ($type = $theme->dyCssMediaType(null, $url)) {
725
			return $type;
726
		}
727
		$segs = explode('.', basename($url));
728
		if (isset($segs[2])) {
729
			return $segs[count($segs) - 2];
730
		} else {
731
			return '';
732
		}
733
	}
734
735
	/**
736
	 * Raises OnSaveStateComplete event.
737
	 * This method is invoked right after {@link onSaveState OnSaveState} stage.
738
	 * You may override this method to provide additional logic after page state is saved.
739
	 * Remember to call the parent implementation to ensure OnSaveStateComplete event is raised.
740
	 * @param mixed $param event parameter
741
	 */
742
	public function onSaveStateComplete($param)
743
	{
744
		$this->raiseEvent('OnSaveStateComplete', $this, $param);
745
	}
746
747
	/**
748
	 * Determines whether the current page request is a postback.
749
	 * Call {@link getIsPostBack} to get the result.
750
	 */
751
	private function determinePostBackMode()
752
	{
753
		$postData = $this->getRequest();
754
		if ($postData->contains(self::FIELD_PAGESTATE) || $postData->contains(self::FIELD_POSTBACK_TARGET)) {
755
			$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...
756
		}
757
	}
758
759
	/**
760
	 * @return bool whether the current page request is a postback
761
	 */
762
	public function getIsPostBack()
763
	{
764
		return $this->_postData !== null;
765
	}
766
767
	/**
768
	 * @return bool whether this is a callback request
769
	 */
770
	public function getIsCallback()
771
	{
772
		return $this->getIsPostBack() && $this->getRequest()->contains(self::FIELD_CALLBACK_TARGET);
773
	}
774
775
	/**
776
	 * This method is invoked when control state is to be saved.
777
	 * You can override this method to do last step state saving.
778
	 * Parent implementation must be invoked.
779
	 */
780
	public function saveState()
781
	{
782
		parent::saveState();
783
		$this->setViewState('ControlsRequiringPostBack', $this->_controlsRegisteredForPostData, []);
784
	}
785
786
	/**
787
	 * This method is invoked right after the control has loaded its state.
788
	 * You can override this method to initialize data from the control state.
789
	 * Parent implementation must be invoked.
790
	 */
791
	public function loadState()
792
	{
793
		parent::loadState();
794
		$this->_controlsRequiringPostData = $this->getViewState('ControlsRequiringPostBack', []);
795
	}
796
797
	/**
798
	 * Loads page state from persistent storage.
799
	 */
800
	protected function loadPageState()
801
	{
802
		Prado::trace("Loading state", 'Prado\Web\UI\TPage');
803
		$state = $this->getStatePersister()->load();
804
		$this->loadStateRecursive($state, $this->getEnableViewState());
805
	}
806
807
	/**
808
	 * Saves page state from persistent storage.
809
	 */
810
	protected function savePageState()
811
	{
812
		Prado::trace("Saving state", 'Prado\Web\UI\TPage');
813
		$state = &$this->saveStateRecursive($this->getEnableViewState());
814
		$this->getStatePersister()->save($state);
815
	}
816
817
	/**
818
	 * @param string $field the field name
819
	 * @return bool whether the specified field is a system field in postback data
820
	 */
821
	protected function isSystemPostField($field)
822
	{
823
		return isset(self::$_systemPostFields[$field]);
824
	}
825
826
	/**
827
	 * Registers a control for loading post data in the next postback.
828
	 * This method needs to be invoked if the control to load post data
829
	 * may not have a post variable in some cases. For example, a checkbox,
830
	 * if not checked, will not have a post value.
831
	 * @param \Prado\Web\UI\TControl $control control registered for loading post data
832
	 */
833
	public function registerRequiresPostData($control)
834
	{
835
		$id = is_string($control) ? $control : $control->getUniqueID();
836
		$this->_controlsRegisteredForPostData[$id] = true;
837
		$params = func_get_args();
0 ignored issues
show
Unused Code introduced by
The assignment to $params is dead and can be removed.
Loading history...
838
		foreach ($this->getCachingStack() as $item) {
839
			$item->registerAction('Page', 'registerRequiresPostData', [$id]);
840
		}
841
	}
842
843
	/**
844
	 * @return \Prado\Web\UI\TControl the control responsible for the current postback event, null if nonexistent
845
	 */
846
	public function getPostBackEventTarget()
847
	{
848
		if ($this->_postBackEventTarget === null && $this->_postData !== null) {
849
			$eventTarget = $this->_postData->itemAt(self::FIELD_POSTBACK_TARGET);
850
			if (!empty($eventTarget)) {
851
				$this->_postBackEventTarget = $this->findControl($eventTarget);
852
			}
853
		}
854
		return $this->_postBackEventTarget;
855
	}
856
857
	/**
858
	 * Registers a control to raise postback event in the current request.
859
	 * @param \Prado\Web\UI\TControl $control control registered to raise postback event.
860
	 */
861
	public function setPostBackEventTarget(TControl $control)
862
	{
863
		$this->_postBackEventTarget = $control;
864
	}
865
866
	/**
867
	 * @return string postback event parameter
868
	 */
869
	public function getPostBackEventParameter()
870
	{
871
		if ($this->_postBackEventParameter === null && $this->_postData !== null) {
872
			if (($this->_postBackEventParameter = $this->_postData->itemAt(self::FIELD_POSTBACK_PARAMETER)) === null) {
873
				$this->_postBackEventParameter = '';
874
			}
875
		}
876
		return $this->_postBackEventParameter;
877
	}
878
879
	/**
880
	 * @param string $value postback event parameter
881
	 */
882
	public function setPostBackEventParameter($value)
883
	{
884
		$this->_postBackEventParameter = $value;
885
	}
886
887
	/**
888
	 * Processes post data.
889
	 * @param TMap $postData post data to be processed
890
	 * @param bool $beforeLoad whether this method is invoked before {@link onLoad OnLoad}.
891
	 */
892
	protected function processPostData($postData, $beforeLoad)
893
	{
894
		$this->_isLoadingPostData = true;
895
		if ($beforeLoad) {
896
			$this->_restPostData = new TMap;
897
		}
898
		foreach ($postData as $key => $value) {
899
			if ($this->isSystemPostField($key)) {
900
				continue;
901
			} elseif ($control = $this->findControl($key)) {
902
				if ($control instanceof \Prado\Web\UI\IPostBackDataHandler) {
903
					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

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

958
			$postBackHandler->/** @scrutinizer ignore-call */ 
959
                     raisePostBackEvent($this->getPostBackEventParameter());
Loading history...
959
		}
960
	}
961
962
	/**
963
	 * @return bool Whether form rendering is in progress
964
	 */
965
	public function getInFormRender()
966
	{
967
		return $this->_inFormRender;
968
	}
969
970
	/**
971
	 * Ensures the control is rendered within a form.
972
	 * @param \Prado\Web\UI\TControl $control the control to be rendered
973
	 * @throws TConfigurationException if the control is outside of the form
974
	 */
975
	public function ensureRenderInForm($control)
976
	{
977
		if (!$this->getIsCallback() && !$this->_inFormRender) {
978
			throw new TConfigurationException('page_control_outofform', get_class($control), $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...
979
		}
980
	}
981
982
	/**
983
	 * @internal This method is invoked by TForm at the beginning of its rendering
984
	 * @param mixed $writer
985
	 */
986
	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

986
	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...
987
	{
988
		if ($this->_formRendered) {
989
			throw new TConfigurationException('page_form_duplicated');
990
		}
991
		$this->_formRendered = true;
992
		$this->getClientScript()->registerHiddenField(self::FIELD_PAGESTATE, $this->getClientState());
993
		$this->_inFormRender = true;
994
	}
995
996
	/**
997
	 * @internal This method is invoked by TForm  at the end of its rendering
998
	 * @param mixed $writer
999
	 */
1000
	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

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