TPageService::getBasePageClass()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
ccs 0
cts 1
cp 0
crap 2
1
<?php
2
3
/**
4
 * TPageService class file.
5
 *
6
 * @author Qiang Xue <[email protected]>
7
 * @link https://github.com/pradosoft/prado
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 */
10
11
namespace Prado\Web\Services;
12
13
use Prado\Prado;
14
use Prado\Exceptions\TConfigurationException;
15
use Prado\Exceptions\THttpException;
16
use Prado\Exceptions\TInvalidOperationException;
17
use Prado\TApplication;
18
use Prado\TApplicationMode;
19
use Prado\Web\UI\TPage;
20
use Prado\Web\UI\TTemplateManager;
21
use Prado\Web\UI\TThemeManager;
22
23
/**
24
 * TPageService class.
25
 *
26
 * TPageService implements the service for serving user page requests.
27
 *
28
 * Pages that are available to client users are stored under a directory specified by
29
 * {@see setBasePath BasePath}. The directory may contain subdirectories.
30
 * Pages serving for a similar goal are usually placed under the same directory.
31
 * A directory may contain a configuration file <b>config.xml</b> whose content
32
 * is similar to that of application configuration file.
33
 *
34
 * A page is requested via page path, which is a dot-connected directory names
35
 * appended by the page name. Assume '<BasePath>/Users/Admin' is the directory
36
 * containing the page 'Update'. Then the page can be requested via 'Users.Admin.Update'.
37
 * By default, the {@see setBasePath BasePath} of the page service is the "pages"
38
 * directory under the application base path. You may change this default
39
 * by setting {@see setBasePath BasePath} with a different path you prefer.
40
 *
41
 * Page name refers to the file name (without extension) of the page template.
42
 * In order to differentiate from the common control template files, the extension
43
 * name of the page template files must be '.page'. If there is a PHP file with
44
 * the same page name under the same directory as the template file, that file
45
 * will be considered as the page class file and the file name is the page class name.
46
 * If such a file is not found, the page class is assumed as {@see \Prado\Web\UI\TPage}.
47
 *
48
 * Modules can be configured and loaded in page directory configurations.
49
 * Configuration of a module in a subdirectory will overwrite its parent
50
 * directory's configuration, if both configurations refer to the same module.
51
 *
52
 * By default, TPageService will automatically load two modules:
53
 * - {@see \Prado\Web\UI\TTemplateManager} : manages page and control templates
54
 * - {@see \Prado\Web\UI\TThemeManager} : manages themes used in a Prado application
55
 *
56
 * In page directory configurations, static authorization rules can also be specified,
57
 * which governs who and which roles can access particular pages.
58
 * Refer to {@see \Prado\Security\TAuthorizationRule} for more details about authorization rules.
59
 * Page authorization rules can be configured within an <authorization> tag in
60
 * each page directory configuration as follows,
61
 * <authorization>
62
 *   <deny pages="Update" users="?" />
63
 *   <allow pages="Admin" roles="administrator" />
64
 *   <deny pages="Admin" users="*" />
65
 * </authorization>
66
 * where the 'pages' attribute may be filled with a sequence of comma-separated
67
 * page IDs. If 'pages' attribute does not appear in a rule, the rule will be
68
 * applied to all pages in this directory and all subdirectories (recursively).
69
 * Application of authorization rules are in a bottom-up fashion, starting from
70
 * the directory containing the requested page up to all parent directories.
71
 * The first matching rule will be used. The last rule always allows all users
72
 * accessing to any resources.
73
 *
74
 * @author Qiang Xue <[email protected]>
75
 * @author Carl G. Mathisen <[email protected]>
76
 * @since 3.0
77
 */
78
class TPageService extends \Prado\TService
79
{
80
	/**
81
	 * Configuration file name
82
	 */
83
	public const CONFIG_FILE_XML = 'config.xml';
84
	/**
85
	 * Configuration file name
86
	 */
87
	public const CONFIG_FILE_PHP = 'config.php';
88
	/**
89
	 * Default base path
90
	 */
91
	public const DEFAULT_BASEPATH = 'Pages';
92
	/**
93
	 * Fallback base path - used to be the default up to Prado < 3.2
94
	 */
95
	public const FALLBACK_BASEPATH = 'pages';
96
	/**
97
	 * Prefix of ID used for storing parsed configuration in cache
98
	 */
99
	public const CONFIG_CACHE_PREFIX = 'prado:pageservice:';
100
	/**
101
	 * Page template file extension
102
	 */
103
	public const PAGE_FILE_EXT = '.page';
104
	/**
105
	 * Prefix of Pages used for instantiating new pages
106
	 */
107
	public const PAGE_NAMESPACE_PREFIX = 'Application\\Pages\\';
108
	/**
109
	 * @var string root path of pages
110
	 */
111
	private $_basePath;
112
	/**
113
	 * @var string base path class in namespace format
114
	 */
115
	private $_basePageClass = \Prado\Web\UI\TPage::class;
116
	/**
117
	 * @var string clientscript manager class in namespace format
118
	 * @since 3.1.7
119
	 */
120
	private $_clientScriptManagerClass = \Prado\Web\UI\TClientScriptManager::class;
121
	/**
122
	 * @var string default page
123
	 */
124
	private $_defaultPage = 'Home';
125
	/**
126
	 * @var string requested page (path)
127
	 */
128
	private $_pagePath;
129
	/**
130
	 * @var TPage the requested page
131
	 */
132
	private $_page;
133
	/**
134
	 * @var array list of initial page property values
135
	 */
136
	private $_properties = [];
137
	/**
138
	 * @var bool whether service is initialized
139
	 */
140
	private $_initialized = false;
141
142
	/**
143
	 * Initializes the service.
144
	 * This method is required by IService interface and is invoked by application.
145
	 * @param \Prado\Xml\TXmlElement $config service configuration
146
	 */
147
	public function init($config)
148
	{
149
		Prado::trace("Initializing TPageService", TPageService::class);
150
151
		$pageConfig = $this->loadPageConfig($config);
152
153
		$this->initPageContext($pageConfig);
154
155
		$this->_initialized = true;
156
	}
157
158
	/**
159
	 * Initializes page context.
160
	 * Page context includes path alias settings, namespace usages,
161
	 * parameter initialization, module loadings, page initial properties
162
	 * and authorization rules.
163
	 * @param TPageConfiguration $pageConfig
164
	 */
165
	protected function initPageContext($pageConfig)
166
	{
167
		$application = $this->getApplication();
168
		foreach ($pageConfig->getApplicationConfigurations() as $appConfig) {
169
			$application->applyConfiguration($appConfig);
170
		}
171
172
		$this->applyConfiguration($pageConfig);
173
	}
174
175
	/**
176
	 * Applies a page configuration.
177
	 * @param TPageConfiguration $config the configuration
178
	 */
179
	protected function applyConfiguration($config)
180
	{
181
		// initial page properties (to be set when page runs)
182
		$this->_properties = array_merge($this->_properties, $config->getProperties());
183
		$this->getApplication()->getAuthorizationRules()->mergeWith($config->getRules());
184
		$pagePath = $this->getRequestedPagePath();
185
		// external configurations
186
		foreach ($config->getExternalConfigurations() as $filePath => $params) {
187
			[$configPagePath, $condition] = $params;
188
			if ($condition !== true) {
189
				$condition = $this->evaluateExpression($condition);
190
			}
191
			if ($condition) {
192
				if (($path = Prado::getPathOfNamespace($filePath, Prado::getApplication()->getConfigurationFileExt())) === null || !is_file($path)) {
193
					throw new TConfigurationException('pageservice_includefile_invalid', $filePath);
194
				}
195
				$c = new TPageConfiguration($pagePath);
196
				$c->loadFromFile($path, $configPagePath);
197
				$this->applyConfiguration($c);
198
			}
199
		}
200
		$config->dyApplyConfiguration();
201
	}
202
203
	/**
204
	 * Determines the requested page path.
205
	 * @return string page path requested
206
	 */
207
	protected function determineRequestedPagePath()
208
	{
209
		$pagePath = $this->getRequest()->getServiceParameter();
210
		if (empty($pagePath)) {
211
			$pagePath = $this->getDefaultPage();
212
		}
213
		return $pagePath;
214
	}
215
216
	/**
217
	 * Collects configuration for a page.
218
	 * @param \Prado\Xml\TXmlElement $config additional configuration specified in the application configuration
219
	 * @return TPageConfiguration
220
	 */
221
	protected function loadPageConfig($config)
222
	{
223
		$application = $this->getApplication();
224
		$pagePath = $this->getRequestedPagePath();
225
		$pageConfig = null;
226
		if (($cache = $application->getCache()) === null) {
227
			$pageConfig = new TPageConfiguration($pagePath);
228
			if ($config !== null) {
229
				if ($application->getConfigurationType() == TApplication::CONFIG_TYPE_PHP) {
230
					$pageConfig->loadPageConfigurationFromPhp($config, $application->getBasePath(), '');
0 ignored issues
show
Bug introduced by
$config of type Prado\Xml\TXmlElement is incompatible with the type array expected by parameter $config of Prado\Web\Services\TPage...eConfigurationFromPhp(). ( Ignorable by Annotation )

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

230
					$pageConfig->loadPageConfigurationFromPhp(/** @scrutinizer ignore-type */ $config, $application->getBasePath(), '');
Loading history...
231
				} else {
232
					$pageConfig->loadPageConfigurationFromXml($config, $application->getBasePath(), '');
233
				}
234
			}
235
			$pageConfig->loadFromFiles($this->getBasePath());
236
		} else {
237
			$configCached = true;
238
			$currentTimestamp = [];
239
			$arr = $cache->get(self::CONFIG_CACHE_PREFIX . $this->getID() . $pagePath);
240
			if (is_array($arr)) {
241
				[$pageConfig, $timestamps] = $arr;
242
				if ($application->getMode() !== TApplicationMode::Performance) {
243
					foreach ($timestamps as $fileName => $timestamp) {
244
						if ($fileName === 0) { // application config file
245
							$appConfigFile = $application->getConfigurationFile();
246
							$currentTimestamp[0] = $appConfigFile === null ? 0 : @filemtime($appConfigFile);
247
							if ($currentTimestamp[0] > $timestamp || ($timestamp > 0 && !$currentTimestamp[0])) {
248
								$configCached = false;
249
							}
250
						} else {
251
							$currentTimestamp[$fileName] = @filemtime($fileName);
252
							if ($currentTimestamp[$fileName] > $timestamp || ($timestamp > 0 && !$currentTimestamp[$fileName])) {
253
								$configCached = false;
254
							}
255
						}
256
					}
257
				}
258
			} else {
259
				$configCached = false;
260
				$paths = explode('.', $pagePath);
261
				$configPath = $this->getBasePath();
262
				$fileName = $this->getApplication()->getConfigurationType() == TApplication::CONFIG_TYPE_PHP
263
					? self::CONFIG_FILE_PHP
264
					: self::CONFIG_FILE_XML;
265
				foreach ($paths as $path) {
266
					$configFile = $configPath . DIRECTORY_SEPARATOR . $fileName;
267
					$currentTimestamp[$configFile] = @filemtime($configFile);
268
					$configPath .= DIRECTORY_SEPARATOR . $path;
269
				}
270
				$appConfigFile = $application->getConfigurationFile();
271
				$currentTimestamp[0] = $appConfigFile === null ? 0 : @filemtime($appConfigFile);
0 ignored issues
show
introduced by
The condition $appConfigFile === null is always false.
Loading history...
272
			}
273
			if (!$configCached) {
274
				$pageConfig = new TPageConfiguration($pagePath);
275
				if ($config !== null) {
276
					if ($application->getConfigurationType() == TApplication::CONFIG_TYPE_PHP) {
277
						$pageConfig->loadPageConfigurationFromPhp($config, $application->getBasePath(), '');
278
					} else {
279
						$pageConfig->loadPageConfigurationFromXml($config, $application->getBasePath(), '');
280
					}
281
				}
282
				$pageConfig->loadFromFiles($this->getBasePath());
283
				$cache->set(self::CONFIG_CACHE_PREFIX . $this->getID() . $pagePath, [$pageConfig, $currentTimestamp]);
284
			}
285
		}
286
		return $pageConfig;
287
	}
288
289
	/**
290
	 * @return TTemplateManager template manager
291
	 * @deprecated since 4.3.0
292
	 */
293
	public function getTemplateManager()
294
	{
295
		return Prado::getApplication()->getTemplateManager();
296
	}
297
298
	/**
299
	 * @param TTemplateManager $value template manager
300
	 * @deprecated since 4.3.0
301
	 */
302
	public function setTemplateManager(TTemplateManager $value)
303
	{
304
		Prado::getApplication()->setTemplateManager($value);
305
	}
306
307
	/**
308
	 * @return TThemeManager theme manager
309
	 * @deprecated since 4.3.0
310
	 */
311
	public function getThemeManager()
312
	{
313
		return Prado::getApplication()->getThemeManager();
314
	}
315
316
	/**
317
	 * @param TThemeManager $value theme manager
318
	 * @deprecated since 4.3.0
319
	 */
320
	public function setThemeManager(TThemeManager $value)
321
	{
322
		Prado::getApplication()->setThemeManager($value);
323
	}
324
325
	/**
326
	 * @return string the requested page path
327
	 */
328
	public function getRequestedPagePath()
329
	{
330
		if ($this->_pagePath === null) {
331
			$this->_pagePath = strtr($this->determineRequestedPagePath(), '/\\', '..');
332
			if (empty($this->_pagePath)) {
333
				throw new THttpException(404, 'pageservice_page_required');
334
			}
335
		}
336
		return $this->_pagePath;
337
	}
338
339
	/**
340
	 * @return TPage the requested page
341
	 */
342
	public function getRequestedPage()
343
	{
344
		return $this->_page;
345
	}
346
347
	/**
348
	 * @return string default page path to be served if no explicit page is request. Defaults to 'Home'.
349
	 */
350
	public function getDefaultPage()
351
	{
352
		return $this->_defaultPage;
353
	}
354
355
	/**
356
	 * @param string $value default page path to be served if no explicit page is request
357
	 * @throws TInvalidOperationException if the page service is initialized
358
	 */
359
	public function setDefaultPage($value)
360
	{
361
		if ($this->_initialized) {
362
			throw new TInvalidOperationException('pageservice_defaultpage_unchangeable');
363
		} else {
364
			$this->_defaultPage = $value;
365
		}
366
	}
367
368
	/**
369
	 * @return string the URL for the default page
370
	 */
371
	public function getDefaultPageUrl()
372
	{
373
		return $this->constructUrl($this->getDefaultPage());
374
	}
375
376
	/**
377
	 * @return string the root directory for storing pages. Defaults to the 'pages' directory under the application base path.
378
	 */
379
	public function getBasePath()
380
	{
381
		if ($this->_basePath === null) {
382
			$basePath = $this->getApplication()->getBasePath() . DIRECTORY_SEPARATOR . self::DEFAULT_BASEPATH;
383
			if (($this->_basePath = realpath($basePath)) === false || !is_dir($this->_basePath)) {
384
				$basePath = $this->getApplication()->getBasePath() . DIRECTORY_SEPARATOR . self::FALLBACK_BASEPATH;
385
				if (($this->_basePath = realpath($basePath)) === false || !is_dir($this->_basePath)) {
386
					throw new TConfigurationException('pageservice_basepath_invalid', $basePath);
387
				}
388
			}
389
		}
390
		return $this->_basePath;
391
	}
392
393
	/**
394
	 * @param string $value root directory (in namespace form) storing pages
395
	 * @throws TInvalidOperationException if the service is initialized already or basepath is invalid
396
	 */
397
	public function setBasePath($value)
398
	{
399
		if ($this->_initialized) {
400
			throw new TInvalidOperationException('pageservice_basepath_unchangeable');
401
		} elseif (($path = Prado::getPathOfNamespace($value)) === null || !is_dir($path)) {
402
			throw new TConfigurationException('pageservice_basepath_invalid', $value);
403
		}
404
		$this->_basePath = realpath($path);
405
	}
406
407
	/**
408
	 * Sets the base page class name (in namespace format).
409
	 * If a page only has a template file without page class file,
410
	 * this base page class will be instantiated.
411
	 * @param string $value class name
412
	 */
413
	public function setBasePageClass($value)
414
	{
415
		$this->_basePageClass = $value;
416
	}
417
418
	/**
419
	 * @return string base page class name in namespace format. Defaults to 'TPage'.
420
	 */
421
	public function getBasePageClass()
422
	{
423
		return $this->_basePageClass;
424
	}
425
426
	/**
427
	 * Sets the clientscript manager class (in namespace format).
428
	 * @param string $value class name
429
	 * @since 3.1.7
430
	 */
431
	public function setClientScriptManagerClass($value)
432
	{
433
		$this->_clientScriptManagerClass = $value;
434
	}
435
436
	/**
437
	 * @return string clientscript manager class in namespace format. Defaults to 'Prado\Web\UI\TClientScriptManager'.
438
	 * @since 3.1.7
439
	 */
440
	public function getClientScriptManagerClass()
441
	{
442
		return $this->_clientScriptManagerClass;
443
	}
444
445
	/**
446
	 * Runs the service.
447
	 * This will create the requested page, initializes it with the property values
448
	 * specified in the configuration, and executes the page.
449
	 */
450
	public function run()
451
	{
452
		Prado::trace("Running page service", TPageService::class);
453
		$this->_page = $this->createPage($this->getRequestedPagePath());
454
		$this->runPage($this->_page, $this->_properties);
455
	}
456
457
	/**
458
	 * Creates a page instance based on requested page path.  If the Page is not
459
	 * found in the BasePath then this method raises onAdditionalPagePaths($pagePath)
460
	 * to query for any additional page paths. eg. from composer package modules.
461
	 * @param string $pagePath requested page path
462
	 * @throws THttpException if requested page path is invalid
463
	 * @throws TConfigurationException if the page class cannot be found
464
	 * @return TPage the requested page instance
465
	 */
466
	protected function createPage($pagePath)
467
	{
468
		$path = $this->getBasePath() . DIRECTORY_SEPARATOR . strtr($pagePath, '.', DIRECTORY_SEPARATOR);
469
		$hasTemplateFile = is_file($path . self::PAGE_FILE_EXT);
470
		$hasClassFile = is_file($path . Prado::CLASS_FILE_EXT);
471
472
		if (!$hasTemplateFile && !$hasClassFile) {
473
			$paths = $this->onAdditionalPagePaths($pagePath);
474
			$applicationPath = Prado::getPathOfAlias('Application') ?? '';
475
			$throwException = true;
476
			foreach ($paths as $path) {
477
				if (stripos($path, $applicationPath) !== 0) {
478
					throw new THttpException(403, 'pageservice_security_violation', $path);
479
				}
480
				$hasTemplateFile = is_file($path . self::PAGE_FILE_EXT);
481
				$hasClassFile = is_file($path . Prado::CLASS_FILE_EXT);
482
				if ($hasTemplateFile || $hasClassFile) {
483
					$throwException = false;
484
					break;
485
				}
486
			}
487
			if ($throwException) {
488
				throw new THttpException(404, 'pageservice_page_unknown', $pagePath);
489
			}
490
		}
491
492
		if ($hasClassFile) {
493
			$className = basename($path);
494
			$namespacedClassName = static::PAGE_NAMESPACE_PREFIX . str_replace('.', '\\', $pagePath);
495
496
			if (!class_exists($className, false) && !class_exists($namespacedClassName, false)) {
497
				include_once($path . Prado::CLASS_FILE_EXT);
498
			}
499
500
			if (!class_exists($className, false)) {
501
				$className = $namespacedClassName;
502
			}
503
		} else {
504
			$className = $this->getBasePageClass();
505
			Prado::using($className);
506
			if (($pos = strrpos($className, '.')) !== false) {
507
				$className = substr($className, $pos + 1);
508
			}
509
		}
510
511
		if ($className !== TPage::class && !is_subclass_of($className, TPage::class)) {
512
			throw new THttpException(404, 'pageservice_page_unknown', $pagePath);
513
		}
514
515
		$page = Prado::createComponent($className);
516
		$page->setPagePath($pagePath);
517
518
		if ($hasTemplateFile) {
519
			$page->setTemplate($this->getTemplateManager()->getTemplateByFileName($path . self::PAGE_FILE_EXT));
0 ignored issues
show
Deprecated Code introduced by
The function Prado\Web\Services\TPage...e::getTemplateManager() has been deprecated: since 4.3.0 ( Ignorable by Annotation )

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

519
			$page->setTemplate(/** @scrutinizer ignore-deprecated */ $this->getTemplateManager()->getTemplateByFileName($path . self::PAGE_FILE_EXT));

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
520
		}
521
522
		return $page;
523
	}
524
525
	/**
526
	 * Executes a page.  Between setting the subproperties and running the page, onPreRunPage event is raised
527
	 * @param TPage $page the page instance to be run
528
	 * @param array $properties list of initial page properties
529
	 */
530
	protected function runPage($page, $properties)
531
	{
532
		foreach ($properties as $name => $value) {
533
			$page->setSubProperty($name, $value);
534
		}
535
		$this->onPreRunPage($page);
536
		$page->run($this->getResponse()->createHtmlWriter());
537
	}
538
539
	/**
540
	 * This event is raised if the page is not found in the BasePath.
541
	 * This provides additional possible Page Paths to look for the page.
542
	 * The typical handler would look like:
543
	 * ```php
544
	 * public function additionalPagePaths($service, $pagePath)
545
	 * {
546
	 *	 return $this->getPluginPagesPath() . DIRECTORY_SEPARATOR . strtr($pagePath, '.', DIRECTORY_SEPARATOR);
547
	 * }
548
	 * ```
549
	 * @param mixed $param what is passed as the parameter to the event
550
	 * @since 4.2.0
551
	 */
552
	public function onAdditionalPagePaths($param)
553
	{
554
		return $this->raiseEvent('onAdditionalPagePaths', $this, $param);
555
	}
556
557
	/**
558
	 * This event is raised just before the page is run.  Any part of the system can  patch into the page
559
	 * events with an onPreRunPage handler.
560
	 * @param mixed $param what is passed as the parameter to the event
561
	 * @since 4.2.0
562
	 */
563
	public function onPreRunPage($param)
564
	{
565
		$this->raiseEvent('onPreRunPage', $this, $param);
566
	}
567
568
	/**
569
	 * Constructs a URL with specified page path and GET parameters.
570
	 * @param string $pagePath page path
571
	 * @param array $getParams list of GET parameters, null if no GET parameters required
572
	 * @param bool $encodeAmpersand whether to encode the ampersand in URL, defaults to true.
573
	 * @param bool $encodeGetItems whether to encode the GET parameters (their names and values), defaults to true.
574
	 * @return string URL for the page and GET parameters
575
	 */
576
	public function constructUrl($pagePath, $getParams = null, $encodeAmpersand = true, $encodeGetItems = true)
577
	{
578
		return $this->getRequest()->constructUrl($this->getID(), $pagePath, $getParams, $encodeAmpersand, $encodeGetItems);
579
	}
580
}
581