Passed
Push — neill-page-redirect-fixes ( f17ed8...d2b06d )
by Neill
20:03
created

Page::setByUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 1
eloc 9
nc 1
nop 1
dl 0
loc 11
rs 9.9666
c 1
b 1
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A Page::getTemplate() 0 3 2
1
<?php
2
3
namespace neon\cms\components;
4
5
use \neon\cms\models\CmsUrl;
6
use \neon\cms\models\CmsPage;
7
use \neon\cms\components\Theme;
8
use \neon\cms\components\Renderer;
9
use neon\core\form\assets\CkeditorAsset;
10
use neon\core\helpers\Hash;
11
use neon\core\helpers\Str;
12
use \neon\core\helpers\Url;
13
use \neon\core\helpers\Html;
14
use yii\base\ViewContextInterface;
15
16
/**
17
 * This class is responsible for getting the data for a page
18
 * This includes the top level title and meta information but also loaded data for widgets
19
 * The page is basically the same as the global neon()->view
20
 * Ideally we deprecate the Page component in favour of the global neon()->view component
21
 *
22
 * See @render to understand how in-template data calls are made and affect the rendering
23
 */
24
class Page implements ViewContextInterface
25
{
26
	const COBE_EDITING = 'COBE_EDITING';
27
28
	/**
29
	 * the page we landed on initially
30
	 */
31
	protected $_page = null;
32
33
	/**
34
	 * the set of requested data for a page
35
	 * @var array
36
	 */
37
	protected $requestedData = [];
38
39
	/**
40
	 * for dynamic requests, contains the 'start', 'length' and 'total
41
	 * counts for the data
42
	 * @var array
43
	 */
44
	protected $requestedDataMeta = [];
45
46
	/**
47
	 * the set of unactioned static data requests made for a page
48
	 * @var array
49
	 */
50
	protected $staticDataRequests = [];
51
52
	/**
53
	 * the set of unactioned dynamic data requests made for a page
54
	 * @var array
55
	 */
56
	protected $dynamicDataRequests = [];
57
58
	/**
59
	 * whether or not this page is in edit mode
60
	 */
61
	private $_editMode = false;
0 ignored issues
show
introduced by
The private property $_editMode is not used, and could be removed.
Loading history...
62
63
	/**
64
	 * ===============================================================================
65
	 * Methods on the current page
66
	 * ===============================================================================
67
	 * Methods related to the page landed on (the 'current' page)
68
	 */
69
70
	/**
71
	 * whether or not the page is in edit mode
72
	 *
73
	 * @return bool
74
	 */
75
	public function isInEditMode()
76
	{
77
		if (neon()->session===null) return false;
78
		return neon()->session->get(self::COBE_EDITING, false);
79
	}
80
81
	/**
82
	 * Whether the page is in draft mode
83
	 *
84
	 * @return boolean
85
	 */
86
	public function isStatusDraft()
87
	{
88
		return strtoupper($this->_page['status']) === 'DRAFT';
89
	}
90
91
	const CMS_STATIC_TABLE = 'cms_static_content';
92
93
	public function getViewPath()
94
	{
95
		return neon()->getAlias('@root/themes/default');
96
	}
97
98
	/**
99
	 * Set the page into edit mode
100
	 * You must be logged in to be able to edit pages
101
	 * @param bool $editable
102
	 */
103
	public function setEditMode($editable)
104
	{
105
		$editingRoles = neon('cms')->editingRoles;
0 ignored issues
show
Bug Best Practice introduced by
The property editingRoles does not exist on neon\core\ApplicationWeb. Since you implemented __get, consider adding a @property annotation.
Loading history...
106
		if (!empty($editingRoles) && neon()->user->hasRoles($editingRoles)) {
0 ignored issues
show
Bug introduced by
It seems like $editingRoles can also be of type object; however, parameter $roles of neon\user\services\User::hasRoles() does only seem to accept array, 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

106
		if (!empty($editingRoles) && neon()->user->hasRoles(/** @scrutinizer ignore-type */ $editingRoles)) {
Loading history...
107
			// may receive request data to ensure conversion to boolean with (bool)
108
			neon()->session->set(self::COBE_EDITING, (bool) $editable);
109
		} else {
110
			// no permission to edit so remove session variable
111
			neon()->session->remove(self::COBE_EDITING);
112
		}
113
	}
114
115
	/**
116
	 * Performs necessary page changes when in edit mode
117
	 */
118
	public function handleEditMode()
119
	{
120
		if ($this->isInEditMode()) {
121
			\neon\cms\assets\CmsEditorAsset::register(neon()->view);
122
		}
123
	}
124
125
	/**
126
	 * Whether the database page data has been found and set
127
	 * @return bool
128
	 */
129
	public function hasPage()
130
	{
131
		return $this->_page ? true : false;
132
	}
133
134
	/**
135
	 * Gets the url slug that loaded this page
136
	 *
137
	 * @return string - the url
138
	 */
139
	public function getUrl()
140
	{
141
		return $this->getPageData()['url'];
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getPageData() targeting neon\cms\components\Page::getPageData() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
142
	}
143
144
	/**
145
	 * Sets a page by its id
146
	 * @param $pageId  the id or nice_id of the page
147
	 * @return boolean  true if the page is set
148
	 */
149
	public function setById($pageId)
150
	{
151
		$page = CmsPage::find()
152
			->where(['cms_page.nice_id' => $pageId])
153
			->orWhere(['id' => $pageId])
154
			->asArray()
155
			->one();
156
		$this->setPageFromDb($page);
157
		return $this->hasPage();
158
	}
159
160
	/**
161
	 * Get the layout file if it's set
162
	 * @return string|null
163
	 */
164
	public function getLayout()
165
	{
166
		return isset($this->_page['layout']) ? $this->_page['layout'] : null;
167
	}
168
169
	/**
170
	 * Gets the template file from the database row data
171
	 * @return string|null
172
	 */
173
	public function getTemplate()
174
	{
175
		return isset($this->_page['template']) ? $this->_page['template'] : null;
176
	}
177
178
	/**
179
	 * Get the database page information
180
	 *
181
	 * @return null
182
	 */
183
	public function getPageData()
184
	{
185
		// protect against missing pages
186
		$data = ['page_params'=>[]];
187
		if (!empty($this->_page)) {
188
			$data = $this->_page;
189
			// add the nice_id as CmnUrRoute will add this on first parse through Cobe without gets but
190
			// not on requests with get parameters which has led to hard-to-debug runtime coding errors
191
			if (!isset($data['page_params']['nice_id']) && isset($data['nice_id']))
192
				$data['page_params']['nice_id'] = $data['nice_id'];
193
		}
194
		$params = array_merge($data['page_params'], neon()->request->get(), neon()->request->post());
195
		unset($data['page_params']);
196
		$data['params'] = Html::encodeEntities($params);
197
		$data['url'] = '/' . neon()->request->pathInfo;
198
		$data['uri'] = $_SERVER['REQUEST_URI'];
199
		$data['host'] = Url::base(true);
200
		$data['editing'] = $this->isInEditMode();
201
		return $data;
202
	}
203
204
	public function getId()
205
	{
206
		return $this->hasPage() ? $this->_page['id'] : null;
207
	}
208
209
	/**
210
	 * Get the page title as defined by the database
211
	 *
212
	 * @return string
213
	 */
214
	public function getTitle()
215
	{
216
		return $this->hasPage() ? $this->_page['title'] : '';
217
	}
218
219
	/**
220
	 * ===============================================================================
221
	 * Other pages
222
	 * ===============================================================================
223
	 * Methods dealing with finding information about other pages by id, nice_id
224
	 * and their URLs
225
	 */
226
227
	/**
228
	 * Get the main url for a page from its id
229
	 * @param string  $id  The id of a page or the nice id
230
	 * @deprecated - use standard url rules to convert between routes and urls @see CmsUrlRule
231
	 * @return string
232
	 */
233
	public function getUrlOfPage($id)
234
	{
235
		if (!$id) return '';
236
		return $url = url(['/cms/render/page', 'nice_id'=>$id]);
0 ignored issues
show
Unused Code introduced by
The assignment to $url is dead and can be removed.
Loading history...
237
	}
238
239
	/**
240
	 * Get the main url for a page from its nice_id
241
	 *
242
	 * @param string  $niceId  The nice id of a page
243
	 * @deprecated - use standard url rules to convert between routes and urls @see CmsUrlRule
244
	 * @return string
245
	 */
246
	public function getUrlOfPageByNiceId($niceId)
247
	{
248
		return $url = url(['/cms/render/page', 'nice_id'=>$niceId]);
0 ignored issues
show
Unused Code introduced by
The assignment to $url is dead and can be removed.
Loading history...
249
	}
250
251
	/**
252
	 * ===============================================================================
253
	 * Template functions
254
	 * ===============================================================================
255
	 * note these should not be aware of smarty as we may want to switch the template
256
	 * engine in the future (twig or other) but should do the work
257
	 *
258
	 */
259
260
	/**
261
	 * Get the global site information
262
	 *
263
	 * @return array
264
	 */
265
	public function getSiteData()
266
	{
267
		return neon('cms')->siteData;
0 ignored issues
show
Bug Best Practice introduced by
The property siteData does not exist on neon\core\ApplicationWeb. Since you implemented __get, consider adding a @property annotation.
Loading history...
268
	}
269
270
	/**
271
	 * ===============================================================================
272
	 * Page rendering, static and dynamic data methods
273
	 * ===============================================================================
274
	 * Methods relating to the rendering of the page and getting data
275
	 * from the database
276
	 */
277
278
	/**
279
	 * Renders the page
280
	 *
281
	 * If during rendering in-template data requests are made then the page is re-rendered
282
	 * once the data has been collected and passed to the template. If there are additional
283
	 * data requests that rely on the results from previous ones, then these are collected
284
	 * on additional parses. This means data can be collected in a dynamic and flexible
285
	 * way and data can be collected in bulk calls rather than individual calls throughout
286
	 * a page.
287
	 *
288
	 * This works well when the data requests are heavy compared to rendering time
289
	 * however it works not so well when rendering time is heavy compared to the db calls.
290
	 * In most cases it's proved not to be a significant performance issue and where it has
291
	 * been the following technique resolved it.
292
	 *
293
	 * To improve the rendering time of the page keep the data requests either at the top
294
	 * of the page allowing no rendering until all the data has been collected, or put them
295
	 * into a plugin called at the top of the page. Ensure the plugin uses the request
296
	 * commit approach of Daedalus rather than making individual calls so that it can take
297
	 * advantage of Daedalus' bulk database calling.
298
	 *
299
	 * Once Daedalus supports graphql and we have a page builder we can hoist all calls
300
	 * out of new templates and add a data collection method generically which would mean
301
	 * pages built that way render only once and pages built otherwise can still function.
302
	 *
303
	 * @return html | bool  the rendered page or false if the template couldn't be found
304
	 */
305
	public function render()
306
	{
307
		$this->handleEditMode();
308
309
		if (isset($_GET['theme'])) {
310
			neon()->cms->setThemeName((string) $_GET['theme']);
311
		}
312
313
314
		// use this to trouble shoot slow page rendering
315
		static $numberOfRenders = 0;
316
317
		\Neon::beginProfile('COBE::RENDER_PAGE', 'cobe');
318
319
		$template = $this->getTemplate();
320
		if ($template === null) {
321
			\Neon::endProfile('COBE::RENDER_PAGE', 'cobe');
322
			return false;
323
		}
324
325
		$renderer = $this->getRenderer();
326
		$html = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $html is dead and can be removed.
Loading history...
327
		$hasDataRequests = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $hasDataRequests is dead and can be removed.
Loading history...
328
329
		// render page until no more data requests found (see notes above)
330
		do {
331
			$numberOfRenders++;
332
			$html = $renderer->renderTemplate($template);
333
			if (($hasDataRequests = $this->hasDataRequests()))
334
				$this->makeDataRequests();
335
		} while ($hasDataRequests == true);
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
336
337
		// If the number of renders are excessive (4+), issue a warning.
338
		if ($numberOfRenders >= 4)
339
			\Neon::debug('Excessive number of renders (>=4): ' . $numberOfRenders);
340
341
		// inject assets into template
342
		$html = neon()->view->injectHtml($html);
343
344
		\Neon::endProfile('COBE::RENDER_PAGE', 'cobe');
345
		return $html;
346
	}
347
348
	/**
349
	 * A widget requests its data from the page, passing in its data request
350
	 * If the data exists, it is returned, and if not the request is noted
351
	 * ready for the next bout of requests to Daedalus or static data
352
	 * @param string | array $dataRequest
353
	 * @param array &$data  the returned data (which can be an empty array)
354
	 * @param string &$id  on return set to a unique identifier for the request.
355
	 * @param boolean $isStatic  whether or not this request is for static or
356
	 *   dynamic (daedalus) data
357
	 * @param array &$meta  if the data is dynamic, this is set to the
358
	 *   meta information for that request (currently start, length, total)
359
	 * @return boolean true if data was returned and false if the data was
360
	 *   not yet available
361
	 */
362
	public function requestWidgetData($dataRequest, &$data, &$id, $isStatic, &$meta)
363
	{
364
		$data = [];
365
		$meta = [];
366
		$id = $this->canonicaliseRequest($dataRequest, $isStatic);
367
		// see if we have the data
368
		if (isset($this->requestedData[$id])) {
369
			$data = $this->requestedData[$id];
370
			if (!$isStatic && isset($this->requestedDataMeta[$id]))
371
				$meta = $this->requestedDataMeta[$id];
372
			return true;
373
		}
374
		// if not then add it to the list of requested data
375
		if ($isStatic)
376
			$this->staticDataRequests[$id] = $dataRequest;
377
		else
378
			$this->dynamicDataRequests[$id] = $dataRequest;
379
		return false;
380
	}
381
382
	/**
383
	 * ===============================================================================
384
	 * Protected and Private functions
385
	 * ===============================================================================
386
	 * Implementation details methods
387
	 */
388
389
	/**
390
	 * Get hold of a renderer that can render the page
391
	 * @return \neon\cms\components\Renderer
392
	 */
393
	private function getRenderer()
394
	{
395
		if ($this->_renderer === null) {
396
			$this->_renderer = new Renderer($this, Theme::getHierarchy());
397
		}
398
		return $this->_renderer;
399
	}
400
	protected $_renderer = null;
401
402
	/**
403
	 * convert a request into a format that can be used in the rest of the
404
	 * page component. Return an id based on a canonicalised form of the request
405
	 * @param array | string $dataRequest  the request or a request key.
406
	 *   The content of this depends on whether or not this is a static or dynamic
407
	 *   request. For static ones, this should contain a 'key' and 'page_id'. The
408
	 *   page_id can be null. For dynamic calls, it should contain any or all of
409
	 *   'class', 'filters', 'order', and 'limit' or be a data request string
410
	 * @param
411
	 * @return string
412
	 */
0 ignored issues
show
Documentation Bug introduced by
The doc comment @return at position 0 could not be parsed: Unknown type name '@return' at position 0 in @return.
Loading history...
413
	protected function canonicaliseRequest(&$dataRequest, $isStatic=false)
414
	{
415
		if ($isStatic) {
416
			$id = $this->createStaticContentId($dataRequest);
417
			$dataRequest['id'] = $id;
418
		} else {
419
			$request = array('class'=>null, 'filters'=>[], 'order'=>[], 'limit'=>[]);
420
			$dataRequest = array_merge($request, $dataRequest);
421
			$id = md5(str_replace([' '],'', json_encode($dataRequest)));
422
		}
423
		return $id;
424
	}
425
426
	/**
427
	 * Make any data requests and store the results.
428
	 */
429
	protected function makeDataRequests()
430
	{
431
		\Neon::beginProfile('COBE::MAKE_DATA_REQUESTS', 'cosmos');
432
		$this->makeDynamicDataRequests();
433
		$this->makeStaticDataRequests();
434
		\Neon::endProfile('COBE::MAKE_DATA_REQUESTS', 'cosmos');
435
	}
436
437
438
	/**
439
	 * Make any data requests to DDS and store the results.
440
	 * This then clears the data requests so if you need to know if there
441
	 * were any data requests, use @see hasDataRequests before calling
442
	 * this method
443
	 */
444
	protected function makeDynamicDataRequests()
445
	{
446
		if (count($this->dynamicDataRequests)>0) {
447
			$dds = $this->getIDdsObjectManagement();
448
			foreach ($this->dynamicDataRequests as $id=>$dr) {
449
				$dds->addObjectRequest($dr['class'], $dr['filters'], $dr['order'], $dr['limit'], $id);
450
			}
451
			$data = $dds->commitRequests();
452
			foreach ($data as $i=>$d) {
453
				$this->requestedData[$i] = $d['rows'];
454
				$this->requestedDataMeta[$i] = [
455
					'start' => $d['start'],
456
					'length' => $d['length'],
457
					'total' => $d['total']
458
				];
459
			}
460
			$this->dynamicDataRequests = [];
461
		}
462
	}
463
464
	/**
465
	 * Make any static data requests to CosMoS and store the results.
466
	 * This then clears the data requests so if you need to know if there
467
	 * were any data requests, use @see hasDataRequests before calling
468
	 * this method
469
	 */
470
	protected function makeStaticDataRequests()
471
	{
472
		if (count($this->staticDataRequests)>0) {
473
			$cms = $this->getICmsStaticData();
474
			$data = $cms->bulkStaticContentRequest($this->staticDataRequests);
475
			foreach ($data as $i=>$d)
476
				$this->requestedData[$i] = ($d===null ? 'not found' : $d);
477
			$this->staticDataRequests = [];
478
		}
479
	}
480
481
	/**
482
	 * Check to see if there are any data requests available
483
	 * @return boolean
484
	 */
485
	protected function hasDataRequests()
486
	{
487
		return (count($this->staticDataRequests)+count($this->dynamicDataRequests) > 0);
488
	}
489
490
	/**
491
	 * Get hold of an object manager
492
	 * @return \neon\daedalus\services\ddsManager\interfaces\IDdsObjectManagement
0 ignored issues
show
Bug introduced by
The type neon\daedalus\services\d...es\IDdsObjectManagement was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
493
	 */
494
	protected function getIDdsObjectManagement()
495
	{
496
		if (!$this->_iDdsObjectManagement) {
497
			$this->_iDdsObjectManagement = neon('dds')->IDdsObjectManagement;
0 ignored issues
show
Bug Best Practice introduced by
The property IDdsObjectManagement does not exist on neon\core\ApplicationWeb. Since you implemented __get, consider adding a @property annotation.
Loading history...
498
		}
499
		return $this->_iDdsObjectManagement;
500
	}
501
	protected $_iDdsObjectManagement = null;
502
503
	/**
504
	 * create a unique id from a static page request key and page id
505
	 * @param type $request
0 ignored issues
show
Bug introduced by
The type neon\cms\components\type was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
506
	 * @return string
507
	 */
508
	private function createStaticContentId($request) {
509
		return $this->getICmsStaticData()->createStaticContentId($request);
510
	}
511
512
	/**
513
	 * private reference to a cmsStaticData handler
514
	 * @var neon\cms\services\cmsManager\interfaces\ICmsStaticData
0 ignored issues
show
Bug introduced by
The type neon\cms\components\neon...terfaces\ICmsStaticData was not found. Did you mean neon\cms\services\cmsMan...terfaces\ICmsStaticData? If so, make sure to prefix the type with \.
Loading history...
515
	 */
516
	private $cmsStaticData = null;
517
	private function getICmsStaticData()
518
	{
519
		if ($this->cmsStaticData === null)
520
			$this->cmsStaticData = neon('cms')->ICmsStaticData;
0 ignored issues
show
Bug Best Practice introduced by
The property ICmsStaticData does not exist on neon\core\ApplicationWeb. Since you implemented __get, consider adding a @property annotation.
Loading history...
521
		return $this->cmsStaticData;
522
	}
523
524
	/**
525
	 * Set the page retrieved from the database and perform any conversions
526
	 * @param array $page
527
	 */
528
	private function setPageFromDb($page)
529
	{
530
		if ($page) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $page 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...
531
			$pageParams = json_decode($page['page_params'], true);
532
			$page['page_params'] = empty($pageParams) ? [] : $pageParams;
533
			$this->_page = $page;
534
		}
535
	}
536
537
}
538