Completed
Push — master ( 46a77a...a5d155 )
by Fabio
53:55
created

TPage::setEnableStateIGBinary()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * TPage class file
4
 *
5
 * @author Qiang Xue <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @copyright Copyright &copy; 2005-2016 The PRADO Group
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 * @package Prado\Web\UI
10
 */
11
12
namespace Prado\Web\UI;
13
14
use Prado\Collections\TList;
15
use Prado\Collections\TMap;
16
use Prado\Collections\TStack;
17
use Prado\Exceptions\TConfigurationException;
18
use Prado\Exceptions\THttpException;
19
use Prado\Exceptions\TInvalidDataValueException;
20
use Prado\Exceptions\TInvalidDataTypeException;
21
use Prado\Exceptions\TInvalidOperationException;
22
use Prado\Prado;
23
use Prado\TPropertyValue;
24
use Prado\Web\Javascripts\TJavaScript;
25
use Prado\Web\UI\ActiveControls\TActivePageAdapter;
26
use Prado\Web\UI\ActiveControls\TCallbackClientScript;
27
use Prado\Web\UI\WebControls\THead;
28
29
Prado::using('System.Web.UI.*');
30
Prado::using('System.Web.UI.WebControls.*');
31
32
/**
33
 * TPage class
34
 *
35
 * @author Qiang Xue <[email protected]>
36
 * @package Prado\Web\UI
37
 * @since 3.0
38
 */
39
class TPage extends TTemplateControl
40
{
41
	/**
42
	 * system post fields
43
	 */
44
	const FIELD_POSTBACK_TARGET = 'PRADO_POSTBACK_TARGET';
45
	const FIELD_POSTBACK_PARAMETER = 'PRADO_POSTBACK_PARAMETER';
46
	const FIELD_LASTFOCUS = 'PRADO_LASTFOCUS';
47
	const FIELD_PAGESTATE = 'PRADO_PAGESTATE';
48
	const FIELD_CALLBACK_TARGET = 'PRADO_CALLBACK_TARGET';
49
	const FIELD_CALLBACK_PARAMETER = 'PRADO_CALLBACK_PARAMETER';
50
51
	/**
52
	 * @var array system post fields
53
	 */
54
	private static $_systemPostFields = [
55
		'PRADO_POSTBACK_TARGET' => true,
56
		'PRADO_POSTBACK_PARAMETER' => true,
57
		'PRADO_LASTFOCUS' => true,
58
		'PRADO_PAGESTATE' => true,
59
		'PRADO_CALLBACK_TARGET' => true,
60
		'PRADO_CALLBACK_PARAMETER' => true
61
	];
62
	/**
63
	 * @var TForm form instance
64
	 */
65
	private $_form;
66
	/**
67
	 * @var THead head instance
68
	 */
69
	private $_head;
70
	/**
71
	 * @var array list of registered validators
72
	 */
73
	private $_validators = [];
74
	/**
75
	 * @var bool if validation has been performed
76
	 */
77
	private $_validated = false;
78
	/**
79
	 * @var TTheme page theme
80
	 */
81
	private $_theme;
82
	/**
83
	 * @var string page title set when Head is not in page yet
84
	 */
85
	private $_title;
86
	/**
87
	 * @var TTheme page stylesheet theme
88
	 */
89
	private $_styleSheet;
90
	/**
91
	 * @var TClientScriptManager client script manager
92
	 */
93
	private $_clientScript;
94
	/**
95
	 * @var TMap data post back by user
96
	 */
97
	protected $_postData;
98
	/**
99
	 * @var TMap postback data that is not handled during first invocation of LoadPostData.
100
	 */
101
	protected $_restPostData;
102
	/**
103
	 * @var array list of controls whose data have been changed due to the postback
104
	 */
105
	protected $_controlsPostDataChanged = [];
106
	/**
107
	 * @var array list of controls that need to load post data in the current request
108
	 */
109
	protected $_controlsRequiringPostData = [];
110
	/**
111
	 * @var array list of controls that need to load post data in the next postback
112
	 */
113
	protected $_controlsRegisteredForPostData = [];
114
	/**
115
	 * @var TControl control that needs to raise postback event
116
	 */
117
	private $_postBackEventTarget;
118
	/**
119
	 * @var string postback event parameter
120
	 */
121
	private $_postBackEventParameter;
122
	/**
123
	 * @var bool whether the form has been rendered
124
	 */
125
	protected $_formRendered = false;
126
	/**
127
	 * @var bool whether the current rendering is within a form
128
	 */
129
	protected $_inFormRender = false;
130
	/**
131
	 * @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
132
	 */
133
	private $_focus;
134
	/**
135
	 * @var string page path to this page
136
	 */
137
	private $_pagePath = '';
138
	/**
139
	 * @var bool whether page state should be HMAC validated
140
	 */
141
	private $_enableStateValidation = true;
142
	/**
143
	 * @var bool whether page state should be encrypted
144
	 */
145
	private $_enableStateEncryption = false;
146
	/**
147
	 * @var bool whether page state should be compressed
148
	 * @since 3.1.6
149
	 */
150
	private $_enableStateCompression = true;
151
	/**
152
	 * @var bool whether to use the igbinary serializer if available
153
	 * @since 4.1
154
	 */
155
	private $_enableStateIGBinary = true;
156
	/**
157
	 * @var string page state persister class name
158
	 */
159
	private $_statePersisterClass = '\Prado\Web\UI\TPageStatePersister';
160
	/**
161
	 * @var mixed page state persister
162
	 */
163
	private $_statePersister;
164
	/**
165
	 * @var TStack stack used to store currently active caching controls
166
	 */
167
	private $_cachingStack;
168
	/**
169
	 * @var string state string to be stored on the client side
170
	 */
171
	private $_clientState = '';
172
	/**
173
	 * @var bool true if loading post data.
174
	 */
175
	protected $_isLoadingPostData = false;
176
	/**
177
	 * @var bool whether client supports javascript
178
	 */
179
	private $_enableJavaScript = true;
180
	/**
181
	 * @var THtmlWriter current html render writer
182
	 */
183
	private $_writer;
184
185
	/**
186
	 * Constructor.
187
	 * Sets the page object to itself.
188
	 * Derived classes must call parent implementation.
189
	 */
190
	public function __construct()
191
	{
192
		$this->setPage($this);
193
	}
194
195
	/**
196
	 * Runs through the page lifecycles.
197
	 * @param THtmlTextWriter $writer the HTML writer
198
	 */
199
	public function run($writer)
200
	{
201
		Prado::trace("Running page life cycles", 'Prado\Web\UI\TPage');
202
		$this->_writer = $writer;
0 ignored issues
show
Documentation Bug introduced by
It seems like $writer of type object<Prado\Web\UI\THtmlTextWriter> is incompatible with the declared type object<Prado\Web\UI\THtmlWriter> of property $_writer.

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...
203
204
		$this->determinePostBackMode();
205
206
		if ($this->getIsPostBack()) {
207
			if ($this->getIsCallback()) {
208
				$this->processCallbackRequest($writer);
209
			} else {
210
				$this->processPostBackRequest($writer);
211
			}
212
		} else {
213
			$this->processNormalRequest($writer);
214
		}
215
216
		$this->_writer = null;
217
	}
218
219
	protected function processNormalRequest($writer)
220
	{
221
		Prado::trace("Page onPreInit()", 'Prado\Web\UI\TPage');
222
		$this->onPreInit(null);
223
224
		Prado::trace("Page initRecursive()", 'Prado\Web\UI\TPage');
225
		$this->initRecursive();
226
227
		Prado::trace("Page onInitComplete()", 'Prado\Web\UI\TPage');
228
		$this->onInitComplete(null);
229
230
		Prado::trace("Page onPreLoad()", 'Prado\Web\UI\TPage');
231
		$this->onPreLoad(null);
232
		Prado::trace("Page loadRecursive()", 'Prado\Web\UI\TPage');
233
		$this->loadRecursive();
234
		Prado::trace("Page onLoadComplete()", 'Prado\Web\UI\TPage');
235
		$this->onLoadComplete(null);
236
237
		Prado::trace("Page preRenderRecursive()", 'Prado\Web\UI\TPage');
238
		$this->preRenderRecursive();
239
		Prado::trace("Page onPreRenderComplete()", 'Prado\Web\UI\TPage');
240
		$this->onPreRenderComplete(null);
241
242
		Prado::trace("Page savePageState()", 'Prado\Web\UI\TPage');
243
		$this->savePageState();
244
		Prado::trace("Page onSaveStateComplete()", 'Prado\Web\UI\TPage');
245
		$this->onSaveStateComplete(null);
246
247
		Prado::trace("Page renderControl()", 'Prado\Web\UI\TPage');
248
		$this->renderControl($writer);
249
		Prado::trace("Page unloadRecursive()", 'Prado\Web\UI\TPage');
250
		$this->unloadRecursive();
251
	}
252
253
	protected function processPostBackRequest($writer)
254
	{
255
		Prado::trace("Page onPreInit()", 'Prado\Web\UI\TPage');
256
		$this->onPreInit(null);
257
258
		Prado::trace("Page initRecursive()", 'Prado\Web\UI\TPage');
259
		$this->initRecursive();
260
261
		Prado::trace("Page onInitComplete()", 'Prado\Web\UI\TPage');
262
		$this->onInitComplete(null);
263
264
		$this->_restPostData = new TMap;
265
		Prado::trace("Page loadPageState()", 'Prado\Web\UI\TPage');
266
		$this->loadPageState();
267
		Prado::trace("Page processPostData()", 'Prado\Web\UI\TPage');
268
		$this->processPostData($this->_postData, true);
269
		Prado::trace("Page onPreLoad()", 'Prado\Web\UI\TPage');
270
		$this->onPreLoad(null);
271
		Prado::trace("Page loadRecursive()", 'Prado\Web\UI\TPage');
272
		$this->loadRecursive();
273
		Prado::trace("Page processPostData()", 'Prado\Web\UI\TPage');
274
		$this->processPostData($this->_restPostData, false);
275
		Prado::trace("Page raiseChangedEvents()", 'Prado\Web\UI\TPage');
276
		$this->raiseChangedEvents();
277
		Prado::trace("Page raisePostBackEvent()", 'Prado\Web\UI\TPage');
278
		$this->raisePostBackEvent();
279
		Prado::trace("Page onLoadComplete()", 'Prado\Web\UI\TPage');
280
		$this->onLoadComplete(null);
281
282
		Prado::trace("Page preRenderRecursive()", 'Prado\Web\UI\TPage');
283
		$this->preRenderRecursive();
284
		Prado::trace("Page onPreRenderComplete()", 'Prado\Web\UI\TPage');
285
		$this->onPreRenderComplete(null);
286
287
		Prado::trace("Page savePageState()", 'Prado\Web\UI\TPage');
288
		$this->savePageState();
289
		Prado::trace("Page onSaveStateComplete()", 'Prado\Web\UI\TPage');
290
		$this->onSaveStateComplete(null);
291
292
		Prado::trace("Page renderControl()", 'Prado\Web\UI\TPage');
293
		$this->renderControl($writer);
294
		Prado::trace("Page unloadRecursive()", 'Prado\Web\UI\TPage');
295
		$this->unloadRecursive();
296
	}
297
298
	protected static function decodeUTF8($data, $enc)
299
	{
300
		if (is_array($data)) {
301
			foreach ($data as $k => $v) {
302
				$data[$k] = self::decodeUTF8($v, $enc);
303
			}
304
			return $data;
305
		} elseif (is_string($data)) {
306
			return iconv('UTF-8', $enc . '//IGNORE', $data);
307
		} else {
308
			return $data;
309
		}
310
	}
311
312
	/**
313
	 * Sets Adapter to TActivePageAdapter and calls apter to process the
314
	 * callback request.
315
	 * @param mixed $writer
316
	 */
317
	protected function processCallbackRequest($writer)
318
	{
319
		$this->setAdapter(new TActivePageAdapter($this));
320
321
		$callbackEventParameter = $this->getRequest()->itemAt(TPage::FIELD_CALLBACK_PARAMETER);
322
		if (strlen($callbackEventParameter) > 0) {
323
			$this->_postData[TPage::FIELD_CALLBACK_PARAMETER] = TJavaScript::jsonDecode((string) $callbackEventParameter);
324
		}
325
326
		// Decode Callback postData from UTF-8 to current Charset
327
		if (($g = $this->getApplication()->getGlobalization(false)) !== null &&
328
			strtoupper($enc = $g->getCharset()) != 'UTF-8') {
329
			foreach ($this->_postData as $k => $v) {
330
				$this->_postData[$k] = self::decodeUTF8($v, $enc);
331
			}
332
		}
333
334
		Prado::trace("Page onPreInit()", 'Prado\Web\UI\TPage');
335
		$this->onPreInit(null);
336
337
		Prado::trace("Page initRecursive()", 'Prado\Web\UI\TPage');
338
		$this->initRecursive();
339
340
		Prado::trace("Page onInitComplete()", 'Prado\Web\UI\TPage');
341
		$this->onInitComplete(null);
342
343
		$this->_restPostData = new TMap;
344
		Prado::trace("Page loadPageState()", 'Prado\Web\UI\TPage');
345
		$this->loadPageState();
346
		Prado::trace("Page processPostData()", 'Prado\Web\UI\TPage');
347
		$this->processPostData($this->_postData, true);
348
		Prado::trace("Page onPreLoad()", 'Prado\Web\UI\TPage');
349
		$this->onPreLoad(null);
350
		Prado::trace("Page loadRecursive()", 'Prado\Web\UI\TPage');
351
		$this->loadRecursive();
352
353
		Prado::trace("Page processPostData()", 'Prado\Web\UI\TPage');
354
		$this->processPostData($this->_restPostData, false);
355
356
		Prado::trace("Page raiseChangedEvents()", 'Prado\Web\UI\TPage');
357
		$this->raiseChangedEvents();
358
359
360
		$this->getAdapter()->processCallbackEvent($writer);
361
362
		/*
363
				Prado::trace("Page raisePostBackEvent()",'Prado\Web\UI\TPage');
364
				$this->raisePostBackEvent();
365
		*/
366
		Prado::trace("Page onLoadComplete()", 'Prado\Web\UI\TPage');
367
		$this->onLoadComplete(null);
368
369
		Prado::trace("Page preRenderRecursive()", 'Prado\Web\UI\TPage');
370
		$this->preRenderRecursive();
371
		Prado::trace("Page onPreRenderComplete()", 'Prado\Web\UI\TPage');
372
		$this->onPreRenderComplete(null);
373
374
		Prado::trace("Page savePageState()", 'Prado\Web\UI\TPage');
375
		$this->savePageState();
376
		Prado::trace("Page onSaveStateComplete()", 'Prado\Web\UI\TPage');
377
		$this->onSaveStateComplete(null);
378
379
		/*
380
				Prado::trace("Page renderControl()",'Prado\Web\UI\TPage');
381
				$this->renderControl($writer);
382
		*/
383
		$this->getAdapter()->renderCallbackResponse($writer);
384
385
		Prado::trace("Page unloadRecursive()", 'Prado\Web\UI\TPage');
386
		$this->unloadRecursive();
387
	}
388
389
	/**
390
	 * Gets the callback client script handler that allows javascript functions
391
	 * to be executed during the callback response.
392
	 * @return TCallbackClientScript interface to client-side javascript code.
393
	 */
394
	public function getCallbackClient()
395
	{
396
		if ($this->getAdapter() !== null) {
397
			return $this->getAdapter()->getCallbackClientHandler();
398
		} else {
399
			return new TCallbackClientScript();
400
		}
401
	}
402
403
	/**
404
	 * Set a new callback client handler.
405
	 * @param TCallbackClientScript $client new callback client script handler.
406
	 */
407
	public function setCallbackClient($client)
408
	{
409
		$this->getAdapter()->setCallbackClientHandler($client);
410
	}
411
412
	/**
413
	 * @return TControl the control responsible for the current callback event,
414
	 * null if nonexistent
415
	 */
416
	public function getCallbackEventTarget()
417
	{
418
		return $this->getAdapter()->getCallbackEventTarget();
419
	}
420
421
	/**
422
	 * Registers a control to raise callback event in the current request.
423
	 * @param 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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->_validators of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
479
			$this->_validators = new TList;
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Prado\Collections\TList() of type object<Prado\Collections\TList> is incompatible with the declared type array of property $_validators.

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...
480
		}
481
		if (empty($validationGroup) === true) {
482
			return $this->_validators;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->_validators; of type Prado\Collections\TList|array adds the type array to the return on line 482 which is incompatible with the return type documented by Prado\Web\UI\TPage::getValidators of type Prado\Collections\TList.
Loading history...
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()", 'Prado\Web\UI\TPage');
503
		$this->_validated = true;
504
		if ($this->_validators && $this->_validators->getCount()) {
0 ignored issues
show
Bug introduced by
The method getCount cannot be called on $this->_validators (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
Bug Best Practice introduced by
The expression $this->_validators of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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 {@link validate} is called.
522
	 * @throws TInvalidOperationException if {@link 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()) {
0 ignored issues
show
Bug introduced by
The method getCount cannot be called on $this->_validators (of type array).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
Bug Best Practice introduced by
The expression $this->_validators of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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 View Code Duplication
	public function getTheme()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
545
	{
546
		if (is_string($this->_theme)) {
547
			$this->_theme = $this->getService()->getThemeManager()->getTheme($this->_theme);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Prado\IService as the method getThemeManager() does only exist in the following implementations of said interface: Prado\Web\Services\TPageService, Prado\Wsat\TWsatService.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
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 object<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 View Code Duplication
	public function getStyleSheetTheme()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
566
	{
567
		if (is_string($this->_styleSheet)) {
568
			$this->_styleSheet = $this->getService()->getThemeManager()->getTheme($this->_styleSheet);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Prado\IService as the method getThemeManager() does only exist in the following implementations of said interface: Prado\Web\Services\TPageService, Prado\Wsat\TWsatService.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
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 object<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 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 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
Bug introduced by
It seems like you code against a concrete implementation and not the interface Prado\IService as the method getClientScriptManagerClass() does only exist in the following implementations of said interface: Prado\Web\Services\TPageService, Prado\Wsat\TWsatService.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
Unused Code introduced by
$classPath is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
613
614
			if ($className !== '\Prado\Web\UI\TClientScriptManager' && !is_subclass_of($className, '\Prado\Web\UI\TClientScriptManager')) {
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 {@link onInit OnInit} stage.
626
	 * You may override this method to provide additional initialization that
627
	 * should be done before {@link onInit OnInit} (e.g. setting {@link setTheme Theme} or
628
	 * {@link 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 {@link onInit OnInit} stage and before {@link onLoad OnLoad} stage.
640
	 * You may override this method to provide additional initialization that
641
	 * should be done after {@link 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 {@link onLoad OnLoad} stage.
653
	 * You may override this method to provide additional page loading logic that
654
	 * should be done before {@link 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 {@link onLoad OnLoad} stage.
666
	 * You may override this method to provide additional page loading logic that
667
	 * should be done after {@link 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 {@link onPreRender OnPreRender} stage.
679
	 * You may override this method to provide additional preparation for page rendering
680
	 * that should be done after {@link 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 View Code Duplication
		if ($theme instanceof ITheme) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
690
			foreach ($theme->getStyleSheetFiles() as $url) {
691
				$cs->registerStyleSheetFile($url, $url, $this->getCssMediaType($url));
692
			}
693
			foreach ($theme->getJavaScriptFiles() as $url) {
694
				$cs->registerHeadScriptFile($url, $url);
695
			}
696
		}
697
		$styleSheet = $this->getStyleSheetTheme();
698 View Code Duplication
		if ($styleSheet instanceof ITheme) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
699
			foreach ($styleSheet->getStyleSheetFiles() as $url) {
700
				$cs->registerStyleSheetFile($url, $url, $this->getCssMediaType($url));
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
	 * @return string media type of the CSS file
719
	 */
720
	private function getCssMediaType($url)
721
	{
722
		$segs = explode('.', basename($url));
723
		if (isset($segs[2])) {
724
			return $segs[count($segs) - 2];
725
		} else {
726
			return '';
727
		}
728
	}
729
730
	/**
731
	 * Raises OnSaveStateComplete event.
732
	 * This method is invoked right after {@link onSaveState OnSaveState} stage.
733
	 * You may override this method to provide additional logic after page state is saved.
734
	 * Remember to call the parent implementation to ensure OnSaveStateComplete event is raised.
735
	 * @param mixed $param event parameter
736
	 */
737
	public function onSaveStateComplete($param)
738
	{
739
		$this->raiseEvent('OnSaveStateComplete', $this, $param);
740
	}
741
742
	/**
743
	 * Determines whether the current page request is a postback.
744
	 * Call {@link getIsPostBack} to get the result.
745
	 */
746
	private function determinePostBackMode()
747
	{
748
		$postData = $this->getRequest();
749
		if ($postData->contains(self::FIELD_PAGESTATE) || $postData->contains(self::FIELD_POSTBACK_TARGET)) {
750
			$this->_postData = $postData;
0 ignored issues
show
Documentation Bug introduced by
It seems like $postData of type object<Prado\Web\THttpRequest> is incompatible with the declared type object<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...
751
		}
752
	}
753
754
	/**
755
	 * @return bool whether the current page request is a postback
756
	 */
757
	public function getIsPostBack()
758
	{
759
		return $this->_postData !== null;
760
	}
761
762
	/**
763
	 * @return bool whether this is a callback request
764
	 */
765
	public function getIsCallback()
766
	{
767
		return $this->getIsPostBack() && $this->getRequest()->contains(self::FIELD_CALLBACK_TARGET);
768
	}
769
770
	/**
771
	 * This method is invoked when control state is to be saved.
772
	 * You can override this method to do last step state saving.
773
	 * Parent implementation must be invoked.
774
	 */
775
	public function saveState()
776
	{
777
		parent::saveState();
778
		$this->setViewState('ControlsRequiringPostBack', $this->_controlsRegisteredForPostData, []);
779
	}
780
781
	/**
782
	 * This method is invoked right after the control has loaded its state.
783
	 * You can override this method to initialize data from the control state.
784
	 * Parent implementation must be invoked.
785
	 */
786
	public function loadState()
787
	{
788
		parent::loadState();
789
		$this->_controlsRequiringPostData = $this->getViewState('ControlsRequiringPostBack', []);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getViewState('Con...ringPostBack', array()) of type * is incompatible with the declared type array of property $_controlsRequiringPostData.

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...
790
	}
791
792
	/**
793
	 * Loads page state from persistent storage.
794
	 */
795
	protected function loadPageState()
796
	{
797
		Prado::trace("Loading state", 'Prado\Web\UI\TPage');
798
		$state = $this->getStatePersister()->load();
799
		$this->loadStateRecursive($state, $this->getEnableViewState());
800
	}
801
802
	/**
803
	 * Saves page state from persistent storage.
804
	 */
805
	protected function savePageState()
806
	{
807
		Prado::trace("Saving state", 'Prado\Web\UI\TPage');
808
		$state = &$this->saveStateRecursive($this->getEnableViewState());
809
		$this->getStatePersister()->save($state);
810
	}
811
812
	/**
813
	 * @param string $field the field name
814
	 * @return bool whether the specified field is a system field in postback data
815
	 */
816
	protected function isSystemPostField($field)
817
	{
818
		return isset(self::$_systemPostFields[$field]);
819
	}
820
821
	/**
822
	 * Registers a control for loading post data in the next postback.
823
	 * This method needs to be invoked if the control to load post data
824
	 * may not have a post variable in some cases. For example, a checkbox,
825
	 * if not checked, will not have a post value.
826
	 * @param TControl $control control registered for loading post data
827
	 */
828
	public function registerRequiresPostData($control)
829
	{
830
		$id = is_string($control) ? $control : $control->getUniqueID();
831
		$this->_controlsRegisteredForPostData[$id] = true;
832
		$params = func_get_args();
0 ignored issues
show
Unused Code introduced by
$params is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
833
		foreach ($this->getCachingStack() as $item) {
834
			$item->registerAction('Page', 'registerRequiresPostData', [$id]);
835
		}
836
	}
837
838
	/**
839
	 * @return TControl the control responsible for the current postback event, null if nonexistent
840
	 */
841
	public function getPostBackEventTarget()
842
	{
843
		if ($this->_postBackEventTarget === null && $this->_postData !== null) {
844
			$eventTarget = $this->_postData->itemAt(self::FIELD_POSTBACK_TARGET);
845
			if (!empty($eventTarget)) {
846
				$this->_postBackEventTarget = $this->findControl($eventTarget);
847
			}
848
		}
849
		return $this->_postBackEventTarget;
850
	}
851
852
	/**
853
	 * Registers a control to raise postback event in the current request.
854
	 * @param TControl $control control registered to raise postback event.
855
	 */
856
	public function setPostBackEventTarget(TControl $control)
857
	{
858
		$this->_postBackEventTarget = $control;
859
	}
860
861
	/**
862
	 * @return string postback event parameter
863
	 */
864
	public function getPostBackEventParameter()
865
	{
866
		if ($this->_postBackEventParameter === null && $this->_postData !== null) {
867
			if (($this->_postBackEventParameter = $this->_postData->itemAt(self::FIELD_POSTBACK_PARAMETER)) === null) {
868
				$this->_postBackEventParameter = '';
869
			}
870
		}
871
		return $this->_postBackEventParameter;
872
	}
873
874
	/**
875
	 * @param string $value postback event parameter
876
	 */
877
	public function setPostBackEventParameter($value)
878
	{
879
		$this->_postBackEventParameter = $value;
880
	}
881
882
	/**
883
	 * Processes post data.
884
	 * @param TMap $postData post data to be processed
885
	 * @param bool $beforeLoad whether this method is invoked before {@link onLoad OnLoad}.
886
	 */
887
	protected function processPostData($postData, $beforeLoad)
888
	{
889
		$this->_isLoadingPostData = true;
890
		if ($beforeLoad) {
891
			$this->_restPostData = new TMap;
892
		}
893
		foreach ($postData as $key => $value) {
894
			if ($this->isSystemPostField($key)) {
895
				continue;
896
			} elseif ($control = $this->findControl($key)) {
897
				if ($control instanceof \Prado\Web\UI\IPostBackDataHandler) {
898
					if ($control->loadPostData($key, $postData)) {
899
						$this->_controlsPostDataChanged[] = $control;
900
					}
901
				} elseif ($control instanceof IPostBackEventHandler &&
902
					empty($this->_postData[self::FIELD_POSTBACK_TARGET])) {
903
					// not calling setPostBackEventTarget() because the control may be removed later
904
					$this->_postData->add(self::FIELD_POSTBACK_TARGET, $key);
905
				}
906
				unset($this->_controlsRequiringPostData[$key]);
907
			} elseif ($beforeLoad) {
908
				$this->_restPostData->add($key, $value);
909
			}
910
		}
911
912
		foreach ($this->_controlsRequiringPostData as $key => $value) {
913
			if ($control = $this->findControl($key)) {
914
				if ($control instanceof \Prado\Web\UI\IPostBackDataHandler) {
915
					if ($control->loadPostData($key, $this->_postData)) {
916
						$this->_controlsPostDataChanged[] = $control;
917
					}
918
				} else {
919
					throw new TInvalidDataValueException('page_postbackcontrol_invalid', $key);
920
				}
921
				unset($this->_controlsRequiringPostData[$key]);
922
			}
923
		}
924
		$this->_isLoadingPostData = false;
925
	}
926
927
	/**
928
	 * @return bool true if loading post data.
929
	 */
930
	public function getIsLoadingPostData()
931
	{
932
		return $this->_isLoadingPostData;
933
	}
934
935
	/**
936
	 * Raises OnPostDataChangedEvent for controls whose data have been changed due to the postback.
937
	 */
938
	protected function raiseChangedEvents()
939
	{
940
		foreach ($this->_controlsPostDataChanged as $control) {
941
			$control->raisePostDataChangedEvent();
942
		}
943
	}
944
945
	/**
946
	 * Raises PostBack event.
947
	 */
948
	protected function raisePostBackEvent()
949
	{
950
		if (($postBackHandler = $this->getPostBackEventTarget()) === null) {
951
			$this->validate();
952
		} elseif ($postBackHandler instanceof IPostBackEventHandler) {
953
			$postBackHandler->raisePostBackEvent($this->getPostBackEventParameter());
954
		}
955
	}
956
957
	/**
958
	 * @return bool Whether form rendering is in progress
959
	 */
960
	public function getInFormRender()
961
	{
962
		return $this->_inFormRender;
963
	}
964
965
	/**
966
	 * Ensures the control is rendered within a form.
967
	 * @param TControl $control the control to be rendered
968
	 * @throws TConfigurationException if the control is outside of the form
969
	 */
970
	public function ensureRenderInForm($control)
971
	{
972
		if (!$this->getIsCallback() && !$this->_inFormRender) {
973
			throw new TConfigurationException('page_control_outofform', get_class($control), $control ? $control->getUniqueID() : null);
974
		}
975
	}
976
977
	/**
978
	 * @internal This method is invoked by TForm at the beginning of its rendering
979
	 * @param mixed $writer
980
	 */
981
	public function beginFormRender($writer)
982
	{
983
		if ($this->_formRendered) {
984
			throw new TConfigurationException('page_form_duplicated');
985
		}
986
		$this->_formRendered = true;
987
		$this->getClientScript()->registerHiddenField(self::FIELD_PAGESTATE, $this->getClientState());
988
		$this->_inFormRender = true;
989
	}
990
991
	/**
992
	 * @internal This method is invoked by TForm  at the end of its rendering
993
	 * @param mixed $writer
994
	 */
995
	public function endFormRender($writer)
996
	{
997
		if ($this->_focus) {
998
			if (($this->_focus instanceof TControl) && $this->_focus->getVisible(true)) {
999
				$focus = $this->_focus->getClientID();
1000
			} else {
1001
				$focus = $this->_focus;
1002
			}
1003
			$this->getClientScript()->registerFocusControl($focus);
1004
		} elseif ($this->_postData && ($lastFocus = $this->_postData->itemAt(self::FIELD_LASTFOCUS)) !== null) {
1005
			$this->getClientScript()->registerFocusControl($lastFocus);
1006
		}
1007
		$this->_inFormRender = false;
1008
	}
1009
1010
	/**
1011
	 * Sets input focus on a control after the page is rendered to users.
1012
	 * @param string|TControl $value control to receive focus, or the ID of the element on the page to receive focus
1013
	 */
1014
	public function setFocus($value)
1015
	{
1016
		$this->_focus = $value;
1017
	}
1018
1019
	/**
1020
	 * @return bool whether client supports javascript. Defaults to true.
1021
	 */
1022
	public function getClientSupportsJavaScript()
1023
	{
1024
		return $this->_enableJavaScript;
1025
	}
1026
1027
	/**
1028
	 * @param bool $value whether client supports javascript. If false, javascript will not be generated for controls.
1029
	 */
1030
	public function setClientSupportsJavaScript($value)
1031
	{
1032
		$this->_enableJavaScript = TPropertyValue::ensureBoolean($value);
1033
	}
1034
1035
	/**
1036
	 * @return THead page head, null if not available
1037
	 */
1038
	public function getHead()
1039
	{
1040
		return $this->_head;
1041
	}
1042
1043
	/**
1044
	 * @param THead $value page head
1045
	 * @throws TInvalidOperationException if a head already exists
1046
	 */
1047
	public function setHead(THead $value)
1048
	{
1049
		if ($this->_head) {
1050
			throw new TInvalidOperationException('page_head_duplicated');
1051
		}
1052
		$this->_head = $value;
1053
		if ($this->_title !== null) {
1054
			$this->_head->setTitle($this->_title);
1055
			$this->_title = null;
1056
		}
1057
	}
1058
1059
	/**
1060
	 * @return string page title.
1061
	 */
1062
	public function getTitle()
1063
	{
1064
		if ($this->_head) {
1065
			return $this->_head->getTitle();
1066
		} else {
1067
			return $this->_title === null ? '' : $this->_title;
1068
		}
1069
	}
1070
1071
	/**
1072
	 * Sets the page title.
1073
	 * Note, a {@link THead} control needs to place on the page
1074
	 * in order that this title be rendered.
1075
	 * @param string $value page title. This will override the title set in {@link getHead Head}.
1076
	 */
1077
	public function setTitle($value)
1078
	{
1079
		if ($this->_head) {
1080
			$this->_head->setTitle($value);
1081
		} else {
1082
			$this->_title = $value;
1083
		}
1084
	}
1085
1086
	/**
1087
	 * Returns the state to be stored on the client side.
1088
	 * This method should only be used by framework and control developers.
1089
	 * @return string the state to be stored on the client side
1090
	 */
1091
	public function getClientState()
1092
	{
1093
		return $this->_clientState;
1094
	}
1095
1096
	/**
1097
	 * Sets the state to be stored on the client side.
1098
	 * This method should only be used by framework and control developers.
1099
	 * @param string $state the state to be stored on the client side
1100
	 */
1101
	public function setClientState($state)
1102
	{
1103
		$this->_clientState = $state;
1104
	}
1105
1106
	/**
1107
	 * @return string the state postback from client side
1108
	 */
1109
	public function getRequestClientState()
1110
	{
1111
		return $this->getRequest()->itemAt(self::FIELD_PAGESTATE);
1112
	}
1113
1114
	/**
1115
	 * @return string class name of the page state persister. Defaults to TPageStatePersister.
1116
	 */
1117
	public function getStatePersisterClass()
1118
	{
1119
		return $this->_statePersisterClass;
1120
	}
1121
1122
	/**
1123
	 * @param string $value class name of the page state persister.
1124
	 */
1125
	public function setStatePersisterClass($value)
1126
	{
1127
		$this->_statePersisterClass = $value;
1128
	}
1129
1130
	/**
1131
	 * @return IPageStatePersister page state persister
1132
	 */
1133
	public function getStatePersister()
1134
	{
1135
		if ($this->_statePersister === null) {
1136
			$this->_statePersister = Prado::createComponent($this->_statePersisterClass);
1137
			if (!($this->_statePersister instanceof IPageStatePersister)) {
1138
				throw new TInvalidDataTypeException('page_statepersister_invalid');
1139
			}
1140
			$this->_statePersister->setPage($this);
1141
		}
1142
		return $this->_statePersister;
1143
	}
1144
1145
	/**
1146
	 * @return bool whether page state should be HMAC validated. Defaults to true.
1147
	 */
1148
	public function getEnableStateValidation()
1149
	{
1150
		return $this->_enableStateValidation;
1151
	}
1152
1153
	/**
1154
	 * @param bool $value whether page state should be HMAC validated.
1155
	 */
1156
	public function setEnableStateValidation($value)
1157
	{
1158
		$this->_enableStateValidation = TPropertyValue::ensureBoolean($value);
1159
	}
1160
1161
	/**
1162
	 * @return bool whether page state should be encrypted. Defaults to false.
1163
	 */
1164
	public function getEnableStateEncryption()
1165
	{
1166
		return $this->_enableStateEncryption;
1167
	}
1168
1169
	/**
1170
	 * @param bool $value whether page state should be encrypted.
1171
	 */
1172
	public function setEnableStateEncryption($value)
1173
	{
1174
		$this->_enableStateEncryption = TPropertyValue::ensureBoolean($value);
1175
	}
1176
1177
	/**
1178
	 * @return bool whether page state should be compressed. Defaults to true.
1179
	 * @since 3.1.6
1180
	 */
1181
	public function getEnableStateCompression()
1182
	{
1183
		return $this->_enableStateCompression;
1184
	}
1185
1186
	/**
1187
	 * @param bool $value whether page state should be compressed.
1188
	 * @since 3.1.6
1189
	 */
1190
	public function setEnableStateCompression($value)
1191
	{
1192
		$this->_enableStateCompression = TPropertyValue::ensureBoolean($value);
1193
	}
1194
1195
	/**
1196
	 * @return bool whether page state should be serialized using igbinary if available. Defaults to true.
1197
	 * @since 4.1
1198
	 */
1199
	public function getEnableStateIGBinary()
1200
	{
1201
		return $this->_enableStateIGBinary;
1202
	}
1203
1204
	/**
1205
	 * @param bool $value whether page state should be serialized using igbinary if available.
1206
	 * @since 4.1
1207
	 */
1208
	public function setEnableStateIGBinary($value)
1209
	{
1210
		$this->_enableStateIGBinary = TPropertyValue::ensureBoolean($value);
1211
	}
1212
1213
	/**
1214
	 * @return string the requested page path for this page
1215
	 */
1216
	public function getPagePath()
1217
	{
1218
		return $this->_pagePath;
1219
	}
1220
1221
	/**
1222
	 * @param string $value the requested page path for this page
1223
	 */
1224
	public function setPagePath($value)
1225
	{
1226
		$this->_pagePath = $value;
1227
	}
1228
1229
	/**
1230
	 * Registers an action associated with the content being cached.
1231
	 * The registered action will be replayed if the content stored
1232
	 * in the cache is served to end-users.
1233
	 * @param string $context context of the action method. This is a property-path
1234
	 * referring to the context object (e.g. Page, Page.ClientScript).
1235
	 * @param string $funcName method name of the context object
1236
	 * @param array $funcParams list of parameters to be passed to the action method
1237
	 */
1238
	public function registerCachingAction($context, $funcName, $funcParams)
1239
	{
1240
		if ($this->_cachingStack) {
1241
			foreach ($this->_cachingStack as $cache) {
1242
				$cache->registerAction($context, $funcName, $funcParams);
1243
			}
1244
		}
1245
	}
1246
1247
	/**
1248
	 * @return TStack stack of {@link TOutputCache} objects
1249
	 */
1250
	public function getCachingStack()
1251
	{
1252
		if (!$this->_cachingStack) {
1253
			$this->_cachingStack = new TStack;
1254
		}
1255
		return $this->_cachingStack;
1256
	}
1257
1258
	/**
1259
	 * Flushes output
1260
	 */
1261
	public function flushWriter()
1262
	{
1263
		if ($this->_writer) {
1264
			$this->Response->write($this->_writer->flush());
1265
		}
1266
	}
1267
}
1268