ViewsService   F
last analyzed

Complexity

Total Complexity 102

Size/Duplication

Total Lines 714
Duplicated Lines 0 %

Test Coverage

Coverage 92.34%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 221
dl 0
loc 714
ccs 217
cts 235
cp 0.9234
rs 2
c 2
b 0
f 0
wmc 102

27 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
B viewExists() 0 33 11
B mergeViewsSpec() 0 18 7
A unextendView() 0 16 3
B registerViewsFromPath() 0 27 7
A setViewtype() 0 14 3
A extendView() 0 17 4
A getViewtype() 0 6 2
A setViewLocation() 0 12 3
A fileExists() 0 6 2
A renderViewFile() 0 25 5
A getInspectorData() 0 8 2
A registerViewtypeFallback() 0 2 1
A resolveViewtype() 0 14 6
A renderDeprecatedView() 0 7 2
A registerCoreViews() 0 22 5
A autoregisterViews() 0 23 4
A isValidViewtype() 0 10 3
A isViewLocationsLoadedFromCache() 0 2 1
A getESModules() 0 18 4
A listViews() 0 2 1
A findViewFile() 0 8 3
A getViewList() 0 2 1
A doesViewtypeFallback() 0 2 1
A cacheConfiguration() 0 19 4
C renderView() 0 70 13
A configureFromCache() 0 12 3

How to fix   Complexity   

Complex Class

Complex classes like ViewsService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ViewsService, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Elgg;
4
5
use Elgg\Cache\ServerCache;
6
use Elgg\Http\Request as HttpRequest;
7
use Elgg\Project\Paths;
8
use Elgg\Traits\Loggable;
9
10
/**
11
 * Views service
12
 *
13
 * @internal
14
 * @since 1.9.0
15
 */
16
class ViewsService {
17
18
	use Loggable;
19
20
	const VIEW_HOOK = 'view';
21
	const VIEW_VARS_HOOK = 'view_vars';
22
	const OUTPUT_KEY = '__view_output';
23
	const BASE_VIEW_PRIORITY = 500;
24
25
	/**
26
	 * @see ViewsService::fileExists()
27
	 */
28
	protected array $file_exists_cache = [];
29
30
	/**
31
	 * [viewtype][view] => '/path/to/views/style.css'
32
	 */
33
	protected array $locations = [];
34
35
	/**
36
	 * Tracks location changes for views
37
	 *
38
	 * [viewtype][view][] => '/path/to/views/style.css'
39
	 */
40
	protected array $overrides = [];
41
42
	/**
43
	 * [view][priority] = extension_view
44
	 */
45
	protected array $extensions = [];
46
47
	/**
48
	 * @var string[] A list of fallback viewtypes
49
	 */
50
	protected array $fallbacks = [];
51
52
	protected ?string $viewtype;
53
	
54
	protected bool $locations_loaded_from_cache = false;
55
56
	/**
57
	 * Constructor
58
	 *
59
	 * @param EventsService      $events       Events service
60
	 * @param \Elgg\Http\Request $request      Http Request
61
	 * @param \Elgg\Config       $config       Elgg configuration
62
	 * @param ServerCache        $server_cache Server cache
63
	 */
64 7598
	public function __construct(
65
		protected EventsService $events,
66
		protected HttpRequest $request,
67
		protected Config $config,
68
		protected ServerCache $server_cache
69
	) {
70 7598
	}
71
72
	/**
73
	 * Set the viewtype
74
	 *
75
	 * @param string $viewtype Viewtype
76
	 *
77
	 * @return bool
78
	 */
79 119
	public function setViewtype(string $viewtype = ''): bool {
80 119
		if (!$viewtype) {
81 71
			$this->viewtype = null;
82
83 71
			return true;
84
		}
85
		
86 49
		if ($this->isValidViewtype($viewtype)) {
87 49
			$this->viewtype = $viewtype;
88
89 49
			return true;
90
		}
91
92
		return false;
93
	}
94
95
	/**
96
	 * Get the viewtype
97
	 *
98
	 * @return string
99
	 */
100 4114
	public function getViewtype(): string {
101 4114
		if (!isset($this->viewtype)) {
102 3701
			$this->viewtype = $this->resolveViewtype();
103
		}
104
105 4114
		return $this->viewtype;
106
	}
107
108
	/**
109
	 * Resolve the initial viewtype
110
	 *
111
	 * @return string
112
	 */
113 3701
	protected function resolveViewtype(): string {
114 3701
		if ($this->request) {
115 3701
			$view = $this->request->getParam('view', '', false);
116 3701
			if ($this->isValidViewtype($view) && !empty($this->locations[$view])) {
117 6
				return $view;
118
			}
119
		}
120
121 3699
		$view = (string) $this->config->view;
122 3699
		if ($this->isValidViewtype($view) && !empty($this->locations[$view])) {
123 1
			return $view;
124
		}
125
126 3699
		return 'default';
127
	}
128
129
	/**
130
	 * Checks if $viewtype is a string suitable for use as a viewtype name
131
	 *
132
	 * @param string $viewtype Potential viewtype name. Alphanumeric chars plus _ allowed.
133
	 *
134
	 * @return bool
135
	 */
136 4060
	public function isValidViewtype(string $viewtype): bool {
137 4060
		if ($viewtype === '') {
138 3699
			return false;
139
		}
140
141 1613
		if (preg_match('/\W/', $viewtype)) {
142 1
			return false;
143
		}
144
145 1613
		return true;
146
	}
147
	
148
	/**
149
	 * Discover the core views if the system cache did not load
150
	 *
151
	 * @return void
152
	 * @since 6.1
153
	 */
154 2957
	public function registerCoreViews(): void {
155 2957
		if ($this->isViewLocationsLoadedFromCache()) {
156 2911
			return;
157
		}
158
		
159
		// Core view files in /views
160 47
		$this->registerViewsFromPath(Paths::elgg());
161
		
162
		// Core view definitions in /engine/views.php
163 47
		$file = Paths::elgg() . 'engine/views.php';
164 47
		if (!is_file($file)) {
165
			return;
166
		}
167
		
168 47
		$spec = Includer::includeFile($file);
169 47
		if (is_array($spec)) {
170
			// check for uploaded fontawesome font
171 47
			if ($this->config->font_awesome_zip) {
172
				$spec['default']['font-awesome/'] = elgg_get_data_path() . 'fontawesome/webfont/';
173
			}
174
			
175 47
			$this->mergeViewsSpec($spec);
176
		}
177
	}
178
	
179
	/**
180
	 * Auto-registers views from a location.
181
	 *
182
	 * @param string $view_base The base of the view name without the view type.
183
	 * @param string $folder    Required The folder to begin looking in
184
	 * @param string $viewtype  The type of view we're looking at (default, rss, etc)
185
	 *
186
	 * @return bool returns false if folder can't be read
187
	 */
188 1372
	public function autoregisterViews(string $view_base, string $folder, string $viewtype): bool {
189 1372
		$folder = Paths::sanitize($folder);
190 1372
		$view_base = Paths::sanitize($view_base, false);
191 1372
		$view_base = $view_base ? $view_base . '/' : $view_base;
192
		
193
		try {
194 1372
			$dir = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS));
195
		} catch (\Throwable $t) {
196
			$this->getLogger()->error($t->getMessage());
197
			return false;
198
		}
199
200
		/* @var $file \SplFileInfo */
201 1372
		foreach ($dir as $file) {
202 1372
			$path = $file->getPath() .  '/' . $file->getBasename('.php');
203 1372
			$path = Paths::sanitize($path, false);
204
205
			// found a file add it to the views
206 1372
			$view = $view_base . substr($path, strlen($folder));
207 1372
			$this->setViewLocation($view, $viewtype, $file->getPathname());
208
		}
209
210 1372
		return true;
211
	}
212
213
	/**
214
	 * Find the view file
215
	 *
216
	 * @param string $view     View name
217
	 * @param string $viewtype Viewtype
218
	 *
219
	 * @return string Empty string if not found
220
	 */
221 4244
	public function findViewFile(string $view, string $viewtype): string {
222 4244
		if (!isset($this->locations[$viewtype][$view])) {
223 1093
			return '';
224
		}
225
226 4223
		$path = $this->locations[$viewtype][$view];
227
		
228 4223
		return $this->fileExists($path) ? $path : '';
229
	}
230
231
	/**
232
	 * Register a viewtype to fall back to a default view if a view isn't
233
	 * found for that viewtype.
234
	 *
235
	 * @param string $viewtype The viewtype to register
236
	 *
237
	 * @return void
238
	 *
239
	 * @see elgg_register_viewtype_fallback()
240
	 */
241 2
	public function registerViewtypeFallback(string $viewtype): void {
242 2
		$this->fallbacks[] = $viewtype;
243
	}
244
245
	/**
246
	 * Checks if a viewtype falls back to default.
247
	 *
248
	 * @param string $viewtype Viewtype
249
	 *
250
	 * @return bool
251
	 */
252 1455
	public function doesViewtypeFallback(string $viewtype): bool {
253 1455
		return in_array($viewtype, $this->fallbacks);
254
	}
255
256
	/**
257
	 * Display a view with a deprecation notice. No missing view NOTICE is logged
258
	 *
259
	 * @param string $view       The name and location of the view to use
260
	 * @param array  $vars       Variables to pass to the view
261
	 * @param string $suggestion Suggestion with the deprecation message
262
	 * @param string $version    Human-readable *release* version: 1.7, 1.8, ...
263
	 *
264
	 * @return string The parsed view
265
	 *
266
	 * @see elgg_view()
267
	 */
268
	public function renderDeprecatedView(string $view, array $vars, string $suggestion, string $version): string {
269
		$rendered = $this->renderView($view, $vars, '', false);
270
		if ($rendered) {
271
			$this->logDeprecatedMessage("The '{$view}' view has been deprecated. {$suggestion}", $version);
272
		}
273
274
		return $rendered;
275
	}
276
277
	/**
278
	 * Get the views, including extensions, used to render a view
279
	 *
280
	 * Keys returned are view priorities. View existence is not checked.
281
	 *
282
	 * @param string $view View name
283
	 *
284
	 * @return string[]
285
	 */
286 1737
	public function getViewList(string $view): array {
287 1737
		return $this->extensions[$view] ?? [self::BASE_VIEW_PRIORITY => $view];
288
	}
289
290
	/**
291
	 * Renders a view
292
	 *
293
	 * @param string    $view                 Name of the view
294
	 * @param array     $vars                 Variables to pass to the view
295
	 * @param string    $viewtype             Viewtype to use
296
	 * @param null|bool $issue_missing_notice Should a missing notice be issued
297
	 * @param array     $extensions_tree      Array of views that are before the current view in the extension path
298
	 *
299
	 * @return string
300
	 *
301
	 * @see elgg_view()
302
	 */
303 559
	public function renderView(string $view, array $vars = [], string $viewtype = '', ?bool $issue_missing_notice = null, array $extensions_tree = []): string {
304
		// basic checking for bad paths
305 559
		if (str_contains($view, '..')) {
306
			return '';
307
		}
308
309
		// check for extension deadloops
310 559
		if (in_array($view, $extensions_tree)) {
311 1
			$this->getLogger()->error("View {$view} is detected as an extension of itself. This is not allowed");
312
313 1
			return '';
314
		}
315
		
316 559
		$extensions_tree[] = $view;
317
318
		// Get the current viewtype
319 559
		if ($viewtype === '' || !$this->isValidViewtype($viewtype)) {
320 372
			$viewtype = $this->getViewtype();
321
		}
322
		
323 559
		if (!isset($issue_missing_notice)) {
324 559
			$issue_missing_notice = $viewtype === 'default';
325
		}
326
327
		// allow altering $vars
328 559
		$vars_event_params = [
329 559
			'view' => $view,
330 559
			'vars' => $vars,
331 559
			'viewtype' => $viewtype,
332 559
		];
333 559
		$vars = $this->events->triggerResults(self::VIEW_VARS_HOOK, $view, $vars_event_params, $vars);
334
335
		// allow $vars to hijack output
336 559
		if (isset($vars[self::OUTPUT_KEY])) {
337 1
			return (string) $vars[self::OUTPUT_KEY];
338
		}
339
340 558
		$viewlist = $this->getViewList($view);
341
342 558
		$content = '';
343 558
		foreach ($viewlist as $priority => $view_name) {
344 558
			if ($priority !== self::BASE_VIEW_PRIORITY) {
345
				// the others are extensions
346 91
				$content .= $this->renderView($view_name, $vars, $viewtype, $issue_missing_notice, $extensions_tree);
347 91
				continue;
348
			}
349
350
			// actual rendering of a single view
351 558
			$rendering = $this->renderViewFile($view_name, $vars, $viewtype, $issue_missing_notice);
352 526
			if ($rendering !== false) {
353 522
				$content .= $rendering;
354 522
				continue;
355
			}
356
357
			// attempt to load default view
358 126
			if ($viewtype !== 'default' && $this->doesViewtypeFallback($viewtype)) {
359 1
				$rendering = $this->renderViewFile($view_name, $vars, 'default', $issue_missing_notice);
360 1
				if ($rendering !== false) {
361 1
					$content .= $rendering;
362
				}
363
			}
364
		}
365
366 526
		$params = [
367 526
			'view' => $view,
368 526
			'vars' => $vars,
369 526
			'viewtype' => $viewtype,
370 526
		];
371
		
372 526
		return (string) $this->events->triggerResults(self::VIEW_HOOK, $view, $params, $content);
373
	}
374
375
	/**
376
	 * Wrapper for file_exists() that caches false results (the stat cache only caches true results).
377
	 * This saves us from many unneeded file stat calls when a common view uses a fallback.
378
	 *
379
	 * @param string $path Path to the file
380
	 *
381
	 * @return bool
382
	 */
383 4223
	protected function fileExists(string $path): bool {
384 4223
		if (!isset($this->file_exists_cache[$path])) {
385 3692
			$this->file_exists_cache[$path] = file_exists($path);
386
		}
387
388 4223
		return $this->file_exists_cache[$path];
389
	}
390
391
	/**
392
	 * Includes view PHP or static file
393
	 *
394
	 * @param string $view                 The view name
395
	 * @param array  $vars                 Variables passed to view
396
	 * @param string $viewtype             The viewtype
397
	 * @param bool   $issue_missing_notice Log a notice if the view is missing
398
	 *
399
	 * @return string|false output generated by view file inclusion or false
400
	 */
401 558
	protected function renderViewFile(string $view, array $vars, string $viewtype, bool $issue_missing_notice): string|false {
402 558
		$file = $this->findViewFile($view, $viewtype);
403 558
		if (!$file) {
404 126
			if ($issue_missing_notice) {
405 56
				$this->getLogger()->notice("{$viewtype}/{$view} view does not exist.");
406
			}
407
408 126
			return false;
409
		}
410
411 555
		if (pathinfo($file, PATHINFO_EXTENSION) === 'php') {
412 546
			ob_start();
413
414
			try {
415
				// don't isolate, scripts use the local $vars
416 546
				include $file;
417
418 514
				return ob_get_clean();
419 32
			} catch (\Exception $e) {
420 32
				ob_get_clean();
421 32
				throw $e;
422
			}
423
		}
424
425 76
		return file_get_contents($file);
426
	}
427
428
	/**
429
	 * Returns whether the specified view exists
430
	 *
431
	 * @param string $view     The view name
432
	 * @param string $viewtype If set, forces the viewtype
433
	 * @param bool   $recurse  If false, do not check extensions
434
	 *
435
	 * @return bool
436
	 *
437
	 * @see elgg_view_exists()
438
	 */
439 3903
	public function viewExists(string $view, string $viewtype = '', bool $recurse = true): bool {
440 3903
		if (empty($view)) {
441 101
			return false;
442
		}
443
444
		// Detect view type
445 3903
		if ($viewtype === '' || !$this->isValidViewtype($viewtype)) {
446 3287
			$viewtype = $this->getViewtype();
447
		}
448
449
450 3903
		$file = $this->findViewFile($view, $viewtype);
451 3903
		if ($file) {
452 3866
			return true;
453
		}
454
455
		// If we got here then check whether this exists as an extension
456
		// We optionally recursively check whether the extended view exists also for the viewtype
457 139
		if ($recurse && isset($this->extensions[$view])) {
458 7
			foreach ($this->extensions[$view] as $view_extension) {
459
				// do not recursively check to stay away from infinite loops
460 7
				if ($this->viewExists($view_extension, $viewtype, false)) {
461
					return true;
462
				}
463
			}
464
		}
465
466
		// Now check if the default view exists if the view is registered as a fallback
467 139
		if ($viewtype !== 'default' && $this->doesViewtypeFallback($viewtype)) {
468 1
			return $this->viewExists($view, 'default');
469
		}
470
471 138
		return false;
472
	}
473
474
	/**
475
	 * Extends a view with another view
476
	 *
477
	 * @param string $view           The view to extend.
478
	 * @param string $view_extension This view is added to $view
479
	 * @param int    $priority       The priority, from 0 to 1000, to add at (lowest numbers displayed first)
480
	 *
481
	 * @return void
482
	 *
483
	 * @see elgg_extend_view()
484
	 */
485 3048
	public function extendView(string $view, string $view_extension, int $priority = 501): void {
486 3048
		if ($view === $view_extension) {
487
			// do not allow direct extension on self with self
488 1
			return;
489
		}
490
491 3047
		if (!isset($this->extensions[$view])) {
492 3016
			$this->extensions[$view][self::BASE_VIEW_PRIORITY] = $view;
493
		}
494
495
		// raise priority until it doesn't match one already registered
496 3047
		while (isset($this->extensions[$view][$priority])) {
497 3032
			$priority++;
498
		}
499
500 3047
		$this->extensions[$view][$priority] = $view_extension;
501 3047
		ksort($this->extensions[$view]);
502
	}
503
504
	/**
505
	 * Unextends a view.
506
	 *
507
	 * @param string $view           The view that was extended.
508
	 * @param string $view_extension This view that was added to $view
509
	 *
510
	 * @return bool
511
	 *
512
	 * @see elgg_unextend_view()
513
	 */
514 4
	public function unextendView(string $view, string $view_extension): bool {
515 4
		if (!isset($this->extensions[$view])) {
516
			return false;
517
		}
518
519 4
		$extensions = $this->extensions[$view];
520 4
		unset($extensions[self::BASE_VIEW_PRIORITY]); // we do not want the base view to be removed from the list
521
522 4
		$priority = array_search($view_extension, $extensions);
523 4
		if ($priority === false) {
524 3
			return false;
525
		}
526
527 2
		unset($this->extensions[$view][$priority]);
528
529 2
		return true;
530
	}
531
532
	/**
533
	 * Register all views in a given path
534
	 *
535
	 * @param string $path Base path to scan for views
536
	 *
537
	 * @return bool
538
	 */
539 1294
	public function registerViewsFromPath(string $path): bool {
540 1294
		$path = Paths::sanitize($path) . 'views/';
541
542
		// do not fail on non existing views folder
543 1294
		if (!is_dir($path)) {
544 15
			return true;
545
		}
546
		
547
		try {
548 1282
			$dir = new \DirectoryIterator($path);
549
		} catch (\Throwable $t) {
550
			$this->getLogger()->error($t->getMessage());
551
			return false;
552
		}
553
		
554 1282
		foreach ($dir as $folder) {
555 1282
			$folder_name = $folder->getBasename();
556 1282
			if (!$folder->isDir() || str_starts_with($folder_name, '.')) {
557 1282
				continue;
558
			}
559
			
560 1282
			if (!$this->autoregisterViews('', $folder->getPathname(), $folder_name)) {
561
				return false;
562
			}
563
		}
564
565 1282
		return true;
566
	}
567
568
	/**
569
	 * Merge a specification of absolute view paths
570
	 *
571
	 * @param array $spec Specification
572
	 *                    viewtype => [
573
	 *                    view_name => path or array of paths
574
	 *                    ]
575
	 *
576
	 * @return void
577
	 */
578 141
	public function mergeViewsSpec(array $spec): void {
579 141
		foreach ($spec as $viewtype => $list) {
580 141
			foreach ($list as $view => $paths) {
581 141
				if (!is_array($paths)) {
582 141
					$paths = [$paths];
583
				}
584
585 141
				foreach ($paths as $path) {
586 141
					if (!preg_match('~^([/\\\\]|[a-zA-Z]\:)~', $path)) {
587
						// relative path
588 48
						$path = Paths::project() . $path;
589
					}
590
591 141
					if (str_ends_with($view, '/')) {
592
						// prefix
593 140
						$this->autoregisterViews($view, $path, $viewtype);
594
					} else {
595 54
						$this->setViewLocation($view, $viewtype, $path);
596
					}
597
				}
598
			}
599
		}
600
	}
601
602
	/**
603
	 * List all views in a viewtype
604
	 *
605
	 * @param string $viewtype Viewtype
606
	 *
607
	 * @return string[]
608
	 */
609 2
	public function listViews(string $viewtype = 'default'): array {
610 2
		return array_keys($this->locations[$viewtype] ?? []);
611
	}
612
613
	/**
614
	 * Get inspector data
615
	 *
616
	 * @return array
617
	 */
618 1
	public function getInspectorData(): array {
619 1
		$cached_overrides = $this->server_cache->load('view_overrides');
620
		
621 1
		return [
622 1
			'locations' => $this->locations,
623 1
			'overrides' => is_array($cached_overrides) ? $cached_overrides : $this->overrides,
624 1
			'extensions' => $this->extensions,
625 1
			'simplecache' => _elgg_services()->simpleCache->getCacheableViews(),
626 1
		];
627
	}
628
629
	/**
630
	 * Configure locations from the cache
631
	 *
632
	 * @return void
633
	 */
634 7598
	public function configureFromCache(): void {
635 7598
		if (!$this->server_cache->isEnabled()) {
636 4625
			return;
637
		}
638
		
639 2974
		$data = $this->server_cache->load('view_locations');
640 2974
		if (!is_array($data)) {
641 34
			return;
642
		}
643
		
644 2942
		$this->locations = $data['locations'];
645 2942
		$this->locations_loaded_from_cache = true;
646
	}
647
648
	/**
649
	 * Cache the configuration
650
	 *
651
	 * @return void
652
	 */
653 3000
	public function cacheConfiguration(): void {
654 3000
		if (!$this->server_cache->isEnabled()) {
655 26
			return;
656
		}
657
		
658
		// only cache if not already loaded
659 2974
		if ($this->isViewLocationsLoadedFromCache()) {
660 2942
			return;
661
		}
662
		
663 33
		if (empty($this->locations)) {
664 13
			$this->server_cache->delete('view_locations');
665 13
			return;
666
		}
667
		
668 20
		$this->server_cache->save('view_locations', ['locations' => $this->locations]);
669
670
		// this is saved just for the inspector and is not loaded in loadAll()
671 20
		$this->server_cache->save('view_overrides', $this->overrides);
672
	}
673
	
674
	/**
675
	 * Checks if view_locations have been loaded from cache.
676
	 * This can be used to determine if there is a need to (re)load view locations
677
	 *
678
	 * @return bool
679
	 */
680 3048
	public function isViewLocationsLoadedFromCache(): bool {
681 3048
		return $this->locations_loaded_from_cache;
682
	}
683
	
684
	/**
685
	 * Returns an array of names of ES modules detected based on view location
686
	 *
687
	 * @return array
688
	 */
689 53
	public function getESModules(): array {
690 53
		$modules = $this->server_cache->load('esmodules');
691 53
		if (is_array($modules)) {
692 46
			return $modules;
693
		}
694
		
695 7
		$modules = [];
696 7
		foreach ($this->locations['default'] as $name => $path) {
697 7
			if (!str_ends_with($name, '.mjs')) {
698 7
				continue;
699
			}
700
			
701 7
			$modules[] = $name;
702
		}
703
		
704 7
		$this->server_cache->save('esmodules', $modules);
705
		
706 7
		return $modules;
707
	}
708
709
	/**
710
	 * Update the location of a view file
711
	 *
712
	 * @param string $view     View name
713
	 * @param string $viewtype Viewtype
714
	 * @param string $path     File path
715
	 *
716
	 * @return void
717
	 */
718 1372
	protected function setViewLocation(string $view, string $viewtype, string $path): void {
719 1372
		$path = strtr($path, '\\', '/');
720
721 1372
		if (isset($this->locations[$viewtype][$view]) && $path !== $this->locations[$viewtype][$view]) {
722 37
			$this->overrides[$viewtype][$view][] = $this->locations[$viewtype][$view];
723
		}
724
		
725 1372
		$this->locations[$viewtype][$view] = $path;
726
727
		// Test if view is cacheable and push it to the cacheable views stack,
728
		// if it's not registered as cacheable explicitly
729 1372
		_elgg_services()->simpleCache->isCacheableView($view);
730
	}
731
}
732