1 | <?php |
||
2 | namespace Elgg; |
||
3 | |||
4 | use Elgg\Application\CacheHandler; |
||
5 | use Elgg\Cache\SystemCache; |
||
6 | use Elgg\Filesystem\Directory; |
||
7 | use Elgg\Http\Input; |
||
8 | |||
9 | /** |
||
10 | * WARNING: API IN FLUX. DO NOT USE DIRECTLY. |
||
11 | * |
||
12 | * Use the elgg_* versions instead. |
||
13 | * |
||
14 | * @access private |
||
15 | * |
||
16 | * @since 1.9.0 |
||
17 | */ |
||
18 | class ViewsService { |
||
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 fileExists |
||
27 | * @var array |
||
28 | */ |
||
29 | protected $file_exists_cache = []; |
||
30 | |||
31 | /** |
||
32 | * @var array |
||
33 | * |
||
34 | * [viewtype][view] => '/path/to/views/style.css' |
||
35 | */ |
||
36 | private $locations = []; |
||
37 | |||
38 | /** |
||
39 | * @var array Tracks location changes for views |
||
40 | * |
||
41 | * [viewtype][view][] => '/path/to/views/style.css' |
||
42 | */ |
||
43 | private $overrides = []; |
||
44 | |||
45 | /** |
||
46 | * @var array Simplecache views (view names are keys) |
||
47 | * |
||
48 | * [view] = true |
||
49 | */ |
||
50 | private $simplecache_views = []; |
||
51 | |||
52 | /** |
||
53 | * @var array |
||
54 | * |
||
55 | * [view][priority] = extension_view |
||
56 | */ |
||
57 | private $extensions = []; |
||
58 | |||
59 | /** |
||
60 | * @var string[] A list of fallback viewtypes |
||
61 | */ |
||
62 | private $fallbacks = []; |
||
63 | |||
64 | /** |
||
65 | * @var PluginHooksService |
||
66 | */ |
||
67 | private $hooks; |
||
68 | |||
69 | /** |
||
70 | * @var Logger |
||
71 | */ |
||
72 | private $logger; |
||
73 | |||
74 | /** |
||
75 | * @var SystemCache|null This is set if the views are configured via cache |
||
76 | */ |
||
77 | private $cache; |
||
78 | |||
79 | /** |
||
80 | * @var Input |
||
81 | */ |
||
82 | private $input; |
||
83 | |||
84 | /** |
||
85 | * @var string |
||
86 | */ |
||
87 | private $viewtype; |
||
88 | |||
89 | /** |
||
90 | * Constructor |
||
91 | * |
||
92 | * @param PluginHooksService $hooks The hooks service |
||
93 | * @param Logger $logger Logger |
||
94 | * @param Input $input Input service |
||
95 | */ |
||
96 | 695 | public function __construct(PluginHooksService $hooks, Logger $logger, Input $input = null) { |
|
97 | 695 | $this->hooks = $hooks; |
|
98 | 695 | $this->logger = $logger; |
|
99 | 695 | $this->input = $input; |
|
100 | 695 | } |
|
101 | |||
102 | /** |
||
103 | * Set the viewtype |
||
104 | * |
||
105 | * @param string $viewtype Viewtype |
||
106 | * |
||
107 | * @return bool |
||
108 | */ |
||
109 | 96 | public function setViewtype($viewtype = '') { |
|
110 | 96 | if (!$viewtype) { |
|
111 | 73 | $this->viewtype = null; |
|
112 | 73 | return true; |
|
113 | } |
||
114 | 23 | if ($this->isValidViewtype($viewtype)) { |
|
115 | 23 | $this->viewtype = $viewtype; |
|
116 | 23 | return true; |
|
117 | } |
||
118 | |||
119 | return false; |
||
120 | } |
||
121 | |||
122 | /** |
||
123 | * Get the viewtype |
||
124 | * |
||
125 | * @return string |
||
126 | */ |
||
127 | 838 | public function getViewtype() { |
|
128 | 838 | if ($this->viewtype === null) { |
|
129 | 682 | $this->viewtype = $this->resolveViewtype(); |
|
130 | } |
||
131 | 838 | return $this->viewtype; |
|
132 | } |
||
133 | |||
134 | /** |
||
135 | * If the current viewtype has no views, reset it to "default" |
||
136 | * |
||
137 | * @return void |
||
138 | */ |
||
139 | 18 | public function clampViewtypeToPopulatedViews() { |
|
140 | 18 | $viewtype = $this->getViewtype(); |
|
141 | 18 | if (empty($this->locations[$viewtype])) { |
|
142 | $this->viewtype = 'default'; |
||
143 | } |
||
144 | 18 | } |
|
145 | |||
146 | /** |
||
147 | * Resolve the initial viewtype |
||
148 | * |
||
149 | * @return string |
||
150 | */ |
||
151 | 682 | private function resolveViewtype() { |
|
152 | 682 | if ($this->input) { |
|
153 | 651 | $view = $this->input->get('view', '', false); |
|
154 | 651 | if ($this->isValidViewtype($view)) { |
|
155 | 3 | return $view; |
|
156 | } |
||
157 | } |
||
158 | 682 | $view = elgg_get_config('view'); |
|
159 | 682 | if ($this->isValidViewtype($view)) { |
|
160 | return $view; |
||
161 | } |
||
162 | |||
163 | 682 | return 'default'; |
|
164 | } |
||
165 | |||
166 | /** |
||
167 | * Checks if $viewtype is a string suitable for use as a viewtype name |
||
168 | * |
||
169 | * @param string $viewtype Potential viewtype name. Alphanumeric chars plus _ allowed. |
||
170 | * |
||
171 | * @return bool |
||
172 | */ |
||
173 | 1026 | public function isValidViewtype($viewtype) { |
|
174 | 1026 | if (!is_string($viewtype) || $viewtype === '') { |
|
175 | 682 | return false; |
|
176 | } |
||
177 | |||
178 | 784 | if (preg_match('/\W/', $viewtype)) { |
|
179 | return false; |
||
180 | } |
||
181 | |||
182 | 784 | return true; |
|
183 | } |
||
184 | |||
185 | /** |
||
186 | * Takes a view name and returns the canonical name for that view. |
||
187 | * |
||
188 | * @param string $alias The possibly non-canonical view name. |
||
189 | * |
||
190 | * @return string The canonical view name. |
||
191 | */ |
||
192 | 985 | public static function canonicalizeViewName($alias) { |
|
193 | 985 | if (!is_string($alias)) { |
|
194 | 34 | return false; |
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
195 | } |
||
196 | |||
197 | 985 | $canonical = $alias; |
|
198 | |||
199 | 985 | $extension = pathinfo($canonical, PATHINFO_EXTENSION); |
|
200 | 985 | $hasValidFileExtension = isset(CacheHandler::$extensions[$extension]); |
|
201 | |||
202 | 985 | if (strpos($canonical, "js/") === 0) { |
|
203 | 109 | $canonical = substr($canonical, 3); |
|
204 | 109 | if (!$hasValidFileExtension) { |
|
205 | 109 | $canonical .= ".js"; |
|
206 | } |
||
207 | 985 | } else if (strpos($canonical, "css/") === 0) { |
|
208 | 37 | $canonical = substr($canonical, 4); |
|
209 | 37 | if (!$hasValidFileExtension) { |
|
210 | 32 | $canonical .= ".css"; |
|
211 | } |
||
212 | } |
||
213 | |||
214 | 985 | return $canonical; |
|
215 | } |
||
216 | |||
217 | /** |
||
218 | * Auto-registers views from a location. |
||
219 | * |
||
220 | * @param string $view_base Optional The base of the view name without the view type. |
||
221 | * @param string $folder Required The folder to begin looking in |
||
222 | * @param string $viewtype The type of view we're looking at (default, rss, etc) |
||
223 | * |
||
224 | * @return bool returns false if folder can't be read |
||
225 | * |
||
226 | * @see autoregister_views() |
||
227 | * @access private |
||
228 | */ |
||
229 | 636 | public function autoregisterViews($view_base, $folder, $viewtype) { |
|
230 | 636 | $folder = rtrim($folder, '/\\'); |
|
231 | 636 | $view_base = rtrim($view_base, '/\\'); |
|
232 | |||
233 | 636 | $handle = opendir($folder); |
|
234 | 636 | if (!$handle) { |
|
235 | return false; |
||
236 | } |
||
237 | |||
238 | 636 | while ($entry = readdir($handle)) { |
|
239 | 636 | if ($entry[0] === '.') { |
|
240 | 636 | continue; |
|
241 | } |
||
242 | |||
243 | 636 | $path = "$folder/$entry"; |
|
244 | |||
245 | 636 | if (!empty($view_base)) { |
|
246 | 636 | $view_base_new = $view_base . "/"; |
|
247 | } else { |
||
248 | 636 | $view_base_new = ""; |
|
249 | } |
||
250 | |||
251 | 636 | if (is_dir($path)) { |
|
252 | 636 | $this->autoregisterViews($view_base_new . $entry, $path, $viewtype); |
|
253 | } else { |
||
254 | 636 | $view = $view_base_new . basename($entry, '.php'); |
|
255 | 636 | $this->setViewLocation($view, $viewtype, $path); |
|
256 | } |
||
257 | } |
||
258 | |||
259 | 636 | return true; |
|
260 | } |
||
261 | |||
262 | /** |
||
263 | * Find the view file |
||
264 | * |
||
265 | * @param string $view View name |
||
266 | * @param string $viewtype Viewtype |
||
267 | * |
||
268 | * @return string Empty string if not found |
||
269 | * @access private |
||
270 | * @internal Plugins should not use this. |
||
271 | */ |
||
272 | 975 | public function findViewFile($view, $viewtype) { |
|
273 | 975 | if (!isset($this->locations[$viewtype][$view])) { |
|
274 | 463 | return ""; |
|
275 | } |
||
276 | |||
277 | 971 | $path = $this->locations[$viewtype][$view]; |
|
278 | 971 | if ($this->fileExists($path)) { |
|
279 | 971 | return $path; |
|
280 | } |
||
281 | |||
282 | return ""; |
||
283 | } |
||
284 | |||
285 | /** |
||
286 | * Set an alternative base location for a view |
||
287 | * |
||
288 | * @param string $view Name of the view |
||
289 | * @param string $location Full path to the view file |
||
290 | * @param string $viewtype The viewtype to register this under |
||
291 | * |
||
292 | * @return void |
||
293 | * |
||
294 | * @see elgg_set_view_location() |
||
295 | * @access private |
||
296 | */ |
||
297 | 1 | public function setViewDir($view, $location, $viewtype = '') { |
|
298 | 1 | $view = self::canonicalizeViewName($view); |
|
299 | |||
300 | 1 | if (empty($viewtype)) { |
|
301 | 1 | $viewtype = 'default'; |
|
302 | } |
||
303 | |||
304 | 1 | $location = rtrim($location, '/\\'); |
|
305 | |||
306 | 1 | if ($this->fileExists("$location/$viewtype/$view.php")) { |
|
307 | $this->setViewLocation($view, $viewtype, "$location/$viewtype/$view.php"); |
||
308 | 1 | } elseif ($this->fileExists("$location/$viewtype/$view")) { |
|
309 | 1 | $this->setViewLocation($view, $viewtype, "$location/$viewtype/$view"); |
|
310 | } |
||
311 | 1 | } |
|
312 | |||
313 | /** |
||
314 | * Register a viewtype to fall back to a default view if a view isn't |
||
315 | * found for that viewtype. |
||
316 | * |
||
317 | * @param string $viewtype The viewtype to register |
||
318 | * |
||
319 | * @return void |
||
320 | * |
||
321 | * @see elgg_register_viewtype_fallback() |
||
322 | * @access private |
||
323 | */ |
||
324 | 2 | public function registerViewtypeFallback($viewtype) { |
|
325 | 2 | $this->fallbacks[] = $viewtype; |
|
326 | 2 | } |
|
327 | |||
328 | /** |
||
329 | * Checks if a viewtype falls back to default. |
||
330 | * |
||
331 | * @param string $viewtype Viewtype |
||
332 | * |
||
333 | * @return bool |
||
334 | * |
||
335 | * @see elgg_does_viewtype_fallback() |
||
336 | * @access private |
||
337 | */ |
||
338 | 745 | public function doesViewtypeFallback($viewtype) { |
|
339 | 745 | return in_array($viewtype, $this->fallbacks); |
|
340 | } |
||
341 | |||
342 | /** |
||
343 | * Display a view with a deprecation notice. No missing view NOTICE is logged |
||
344 | * |
||
345 | * @param string $view The name and location of the view to use |
||
346 | * @param array $vars Variables to pass to the view |
||
347 | * @param string $suggestion Suggestion with the deprecation message |
||
348 | * @param string $version Human-readable *release* version: 1.7, 1.8, ... |
||
349 | * |
||
350 | * @return string The parsed view |
||
351 | * |
||
352 | * @access private |
||
353 | * @see elgg_view() |
||
354 | */ |
||
355 | public function renderDeprecatedView($view, array $vars, $suggestion, $version) { |
||
356 | $view = self::canonicalizeViewName($view); |
||
357 | |||
358 | $rendered = $this->renderView($view, $vars, '', false); |
||
359 | if ($rendered) { |
||
360 | elgg_deprecated_notice("The $view view has been deprecated. $suggestion", $version, 3); |
||
361 | } |
||
362 | return $rendered; |
||
363 | } |
||
364 | |||
365 | /** |
||
366 | * Get the views, including extensions, used to render a view |
||
367 | * |
||
368 | * Keys returned are view priorities. View existence is not checked. |
||
369 | * |
||
370 | * @param string $view View name |
||
371 | * @return string[] |
||
372 | * @access private |
||
373 | */ |
||
374 | 727 | public function getViewList($view) { |
|
375 | 727 | if (isset($this->extensions[$view])) { |
|
376 | 35 | return $this->extensions[$view]; |
|
377 | } else { |
||
378 | 725 | return [self::BASE_VIEW_PRIORITY => $view]; |
|
379 | } |
||
380 | } |
||
381 | |||
382 | /** |
||
383 | * Renders a view |
||
384 | * |
||
385 | * @param string $view Name of the view |
||
386 | * @param array $vars Variables to pass to the view |
||
387 | * @param string $viewtype Viewtype to use |
||
388 | * @param bool $issue_missing_notice Should a missing notice be issued |
||
389 | * @param array $extensions_tree Array of views that are before the current view in the extension path |
||
390 | * |
||
391 | * @return string |
||
392 | * |
||
393 | * @see elgg_view() |
||
394 | */ |
||
395 | 292 | public function renderView($view, array $vars = [], $viewtype = '', $issue_missing_notice = true, array $extensions_tree = []) { |
|
396 | 292 | $view = self::canonicalizeViewName($view); |
|
397 | |||
398 | 292 | if (!is_string($view) || !is_string($viewtype)) { |
|
399 | $this->logger->log("View and Viewtype in views must be a strings: $view", 'NOTICE'); |
||
400 | return ''; |
||
401 | } |
||
402 | // basic checking for bad paths |
||
403 | 292 | if (strpos($view, '..') !== false) { |
|
404 | return ''; |
||
405 | } |
||
406 | |||
407 | // check for extension deadloops |
||
408 | 292 | if (in_array($view, $extensions_tree)) { |
|
409 | 1 | $this->logger->log("View $view is detected as an extension of itself. This is not allowed", 'ERROR'); |
|
410 | 1 | return ''; |
|
411 | } |
||
412 | 292 | $extensions_tree[] = $view; |
|
413 | |||
414 | 292 | if (!is_array($vars)) { |
|
415 | $this->logger->log("Vars in views must be an array: $view", 'ERROR'); |
||
416 | $vars = []; |
||
417 | } |
||
418 | |||
419 | // Get the current viewtype |
||
420 | 292 | if ($viewtype === '' || !$this->isValidViewtype($viewtype)) { |
|
421 | 160 | $viewtype = $this->getViewtype(); |
|
422 | } |
||
423 | |||
424 | // allow altering $vars |
||
425 | $vars_hook_params = [ |
||
426 | 292 | 'view' => $view, |
|
427 | 292 | 'vars' => $vars, |
|
428 | 292 | 'viewtype' => $viewtype, |
|
429 | ]; |
||
430 | 292 | $vars = $this->hooks->trigger(self::VIEW_VARS_HOOK, $view, $vars_hook_params, $vars); |
|
431 | |||
432 | // allow $vars to hijack output |
||
433 | 292 | if (isset($vars[self::OUTPUT_KEY])) { |
|
434 | 1 | return (string) $vars[self::OUTPUT_KEY]; |
|
435 | } |
||
436 | |||
437 | 291 | $viewlist = $this->getViewList($view); |
|
438 | |||
439 | 291 | $content = ''; |
|
440 | 291 | foreach ($viewlist as $priority => $view_name) { |
|
441 | 291 | if ($priority !== self::BASE_VIEW_PRIORITY) { |
|
442 | // the others are extensions |
||
443 | 33 | $content .= $this->renderView($view_name, $vars, $viewtype, $issue_missing_notice, $extensions_tree); |
|
444 | 33 | continue; |
|
445 | } |
||
446 | |||
447 | // actual rendering of a single view |
||
448 | 291 | $rendering = $this->renderViewFile($view_name, $vars, $viewtype, $issue_missing_notice); |
|
449 | 291 | if ($rendering !== false) { |
|
450 | 289 | $content .= $rendering; |
|
451 | 289 | continue; |
|
452 | } |
||
453 | |||
454 | // attempt to load default view |
||
455 | 104 | if ($viewtype !== 'default' && $this->doesViewtypeFallback($viewtype)) { |
|
456 | 1 | $rendering = $this->renderViewFile($view_name, $vars, 'default', $issue_missing_notice); |
|
457 | 1 | if ($rendering !== false) { |
|
458 | 104 | $content .= $rendering; |
|
459 | } |
||
460 | } |
||
461 | } |
||
462 | |||
463 | // Plugin hook |
||
464 | $params = [ |
||
465 | 291 | 'view' => $view, |
|
466 | 291 | 'vars' => $vars, |
|
467 | 291 | 'viewtype' => $viewtype, |
|
468 | ]; |
||
469 | 291 | $content = $this->hooks->trigger(self::VIEW_HOOK, $view, $params, $content); |
|
470 | |||
471 | 291 | return $content; |
|
472 | } |
||
473 | |||
474 | /** |
||
475 | * Wrapper for file_exists() that caches false results (the stat cache only caches true results). |
||
476 | * This saves us from many unneeded file stat calls when a common view uses a fallback. |
||
477 | * |
||
478 | * @param string $path Path to the file |
||
479 | * @return bool |
||
480 | */ |
||
481 | 971 | protected function fileExists($path) { |
|
482 | 971 | if (!isset($this->file_exists_cache[$path])) { |
|
483 | 635 | $this->file_exists_cache[$path] = file_exists($path); |
|
484 | } |
||
485 | 971 | return $this->file_exists_cache[$path]; |
|
486 | } |
||
487 | |||
488 | /** |
||
489 | * Includes view PHP or static file |
||
490 | * |
||
491 | * @param string $view The view name |
||
492 | * @param array $vars Variables passed to view |
||
493 | * @param string $viewtype The viewtype |
||
494 | * @param bool $issue_missing_notice Log a notice if the view is missing |
||
495 | * |
||
496 | * @return string|false output generated by view file inclusion or false |
||
497 | */ |
||
498 | 291 | private function renderViewFile($view, array $vars, $viewtype, $issue_missing_notice) { |
|
499 | 291 | $file = $this->findViewFile($view, $viewtype); |
|
500 | 291 | if (!$file) { |
|
501 | 104 | if ($issue_missing_notice) { |
|
502 | 104 | $this->logger->log("$viewtype/$view view does not exist.", 'NOTICE'); |
|
503 | } |
||
504 | 104 | return false; |
|
505 | } |
||
506 | |||
507 | 290 | if (pathinfo($file, PATHINFO_EXTENSION) === 'php') { |
|
508 | 282 | ob_start(); |
|
509 | |||
510 | // don't isolate, scripts use the local $vars |
||
511 | 282 | include $file; |
|
512 | |||
513 | 282 | return ob_get_clean(); |
|
514 | } |
||
515 | |||
516 | 14 | return file_get_contents($file); |
|
517 | } |
||
518 | |||
519 | /** |
||
520 | * Returns whether the specified view exists |
||
521 | * |
||
522 | * @param string $view The view name |
||
523 | * @param string $viewtype If set, forces the viewtype |
||
524 | * @param bool $recurse If false, do not check extensions |
||
525 | * |
||
526 | * @return bool |
||
527 | * |
||
528 | * @see elgg_view_exists() |
||
529 | * @access private |
||
530 | */ |
||
531 | 799 | public function viewExists($view, $viewtype = '', $recurse = true) { |
|
532 | 799 | $view = self::canonicalizeViewName($view); |
|
533 | |||
534 | 799 | if (empty($view) || !is_string($view)) { |
|
535 | 40 | return false; |
|
536 | } |
||
537 | |||
538 | // Detect view type |
||
539 | 798 | if ($viewtype === '' || !$this->isValidViewtype($viewtype)) { |
|
540 | 97 | $viewtype = $this->getViewtype(); |
|
541 | } |
||
542 | |||
543 | |||
544 | 798 | $file = $this->findViewFile($view, $viewtype); |
|
545 | 798 | if ($file) { |
|
546 | 790 | return true; |
|
547 | } |
||
548 | |||
549 | // If we got here then check whether this exists as an extension |
||
550 | // We optionally recursively check whether the extended view exists also for the viewtype |
||
551 | 76 | if ($recurse && isset($this->extensions[$view])) { |
|
552 | foreach ($this->extensions[$view] as $view_extension) { |
||
553 | // do not recursively check to stay away from infinite loops |
||
554 | if ($this->viewExists($view_extension, $viewtype, false)) { |
||
555 | return true; |
||
556 | } |
||
557 | } |
||
558 | } |
||
559 | |||
560 | // Now check if the default view exists if the view is registered as a fallback |
||
561 | 76 | if ($viewtype != 'default' && $this->doesViewtypeFallback($viewtype)) { |
|
562 | 1 | return $this->viewExists($view, 'default'); |
|
563 | } |
||
564 | |||
565 | 75 | return false; |
|
566 | |||
567 | } |
||
568 | |||
569 | /** |
||
570 | * Extends a view with another view |
||
571 | * |
||
572 | * @param string $view The view to extend. |
||
573 | * @param string $view_extension This view is added to $view |
||
574 | * @param int $priority The priority, from 0 to 1000, to add at (lowest numbers displayed first) |
||
575 | * |
||
576 | * @return void |
||
577 | * |
||
578 | * @see elgg_extend_view() |
||
579 | * @access private |
||
580 | */ |
||
581 | 39 | public function extendView($view, $view_extension, $priority = 501) { |
|
582 | 39 | $view = self::canonicalizeViewName($view); |
|
583 | 39 | $view_extension = self::canonicalizeViewName($view_extension); |
|
584 | |||
585 | 39 | if ($view === $view_extension) { |
|
586 | // do not allow direct extension on self with self |
||
587 | 1 | return; |
|
588 | } |
||
589 | |||
590 | 38 | if (!isset($this->extensions[$view])) { |
|
591 | 25 | $this->extensions[$view][self::BASE_VIEW_PRIORITY] = (string) $view; |
|
592 | } |
||
593 | |||
594 | // raise priority until it doesn't match one already registered |
||
595 | 38 | while (isset($this->extensions[$view][$priority])) { |
|
596 | 31 | $priority++; |
|
597 | } |
||
598 | |||
599 | 38 | $this->extensions[$view][$priority] = (string) $view_extension; |
|
600 | 38 | ksort($this->extensions[$view]); |
|
601 | 38 | } |
|
602 | |||
603 | /** |
||
604 | * Is the given view extended? |
||
605 | * |
||
606 | * @param string $view View name |
||
607 | * |
||
608 | * @return bool |
||
609 | * @internal Plugins should not use this |
||
610 | * @access private |
||
611 | */ |
||
612 | 433 | public function viewIsExtended($view) { |
|
613 | 433 | return count($this->getViewList($view)) > 1; |
|
614 | } |
||
615 | |||
616 | /** |
||
617 | * Do hook handlers exist to modify the view? |
||
618 | * |
||
619 | * @param string $view View name |
||
620 | * |
||
621 | * @return bool |
||
622 | * @internal Plugins should not use this |
||
623 | * @access private |
||
624 | */ |
||
625 | public function viewHasHookHandlers($view) { |
||
626 | return $this->hooks->hasHandler('view', $view) || $this->hooks->hasHandler('view_vars', $view); |
||
627 | } |
||
628 | |||
629 | /** |
||
630 | * Unextends a view. |
||
631 | * |
||
632 | * @param string $view The view that was extended. |
||
633 | * @param string $view_extension This view that was added to $view |
||
634 | * |
||
635 | * @return bool |
||
636 | * |
||
637 | * @see elgg_unextend_view() |
||
638 | * @access private |
||
639 | */ |
||
640 | 33 | public function unextendView($view, $view_extension) { |
|
641 | 33 | $view = self::canonicalizeViewName($view); |
|
642 | 33 | $view_extension = self::canonicalizeViewName($view_extension); |
|
643 | |||
644 | 33 | if (!isset($this->extensions[$view])) { |
|
645 | return false; |
||
646 | } |
||
647 | |||
648 | 33 | $extensions = $this->extensions[$view]; |
|
649 | 33 | unset($extensions[self::BASE_VIEW_PRIORITY]); // we do not want the base view to be removed from the list |
|
650 | |||
651 | 33 | $priority = array_search($view_extension, $extensions); |
|
652 | 33 | if ($priority === false) { |
|
653 | 2 | return false; |
|
654 | } |
||
655 | |||
656 | 32 | unset($this->extensions[$view][$priority]); |
|
657 | |||
658 | 32 | return true; |
|
659 | } |
||
660 | |||
661 | /** |
||
662 | * Register a view a cacheable |
||
663 | * |
||
664 | * @param string $view the view name |
||
665 | * |
||
666 | * @return void |
||
667 | * |
||
668 | * @access private |
||
669 | */ |
||
670 | 38 | public function registerCacheableView($view) { |
|
671 | 38 | $view = self::canonicalizeViewName($view); |
|
672 | |||
673 | 38 | $this->simplecache_views[$view] = true; |
|
674 | 38 | } |
|
675 | |||
676 | /** |
||
677 | * Is the view cacheable |
||
678 | * |
||
679 | * @param string $view the view name |
||
680 | * |
||
681 | * @return bool |
||
682 | * |
||
683 | * @access private |
||
684 | */ |
||
685 | 636 | public function isCacheableView($view) { |
|
686 | 636 | $view = self::canonicalizeViewName($view); |
|
687 | 636 | if (isset($this->simplecache_views[$view])) { |
|
688 | 132 | return true; |
|
689 | } |
||
690 | |||
691 | // build list of viewtypes to check |
||
692 | 636 | $current_viewtype = $this->getViewtype(); |
|
693 | 636 | $viewtypes = [$current_viewtype]; |
|
694 | |||
695 | 636 | if ($this->doesViewtypeFallback($current_viewtype) && $current_viewtype != 'default') { |
|
696 | $viewtypes[] = 'default'; |
||
697 | } |
||
698 | |||
699 | // If a static view file is found in any viewtype, it's considered cacheable |
||
700 | 636 | foreach ($viewtypes as $viewtype) { |
|
701 | 636 | $file = $this->findViewFile($view, $viewtype); |
|
702 | |||
703 | 636 | if ($file && pathinfo($file, PATHINFO_EXTENSION) !== 'php') { |
|
704 | 553 | $this->simplecache_views[$view] = true; |
|
705 | 636 | return true; |
|
706 | } |
||
707 | } |
||
708 | |||
709 | // Assume not-cacheable by default |
||
710 | 636 | return false; |
|
711 | } |
||
712 | |||
713 | /** |
||
714 | * Register a plugin's views |
||
715 | * |
||
716 | * @param string $path Base path of the plugin |
||
717 | * @param string $failed_dir This var is set to the failed directory if registration fails |
||
718 | * @return bool |
||
719 | * |
||
720 | * @access private |
||
721 | */ |
||
722 | 527 | public function registerPluginViews($path, &$failed_dir = '') { |
|
723 | 527 | $path = rtrim($path, "\\/"); |
|
724 | 527 | $view_dir = "$path/views/"; |
|
725 | |||
726 | // plugins don't have to have views. |
||
727 | 527 | if (!is_dir($view_dir)) { |
|
728 | return true; |
||
729 | } |
||
730 | |||
731 | // but if they do, they have to be readable |
||
732 | 527 | $handle = opendir($view_dir); |
|
733 | 527 | if (!$handle) { |
|
734 | $failed_dir = $view_dir; |
||
735 | return false; |
||
736 | } |
||
737 | |||
738 | 527 | while (false !== ($view_type = readdir($handle))) { |
|
739 | 527 | $view_type_dir = $view_dir . $view_type; |
|
740 | |||
741 | 527 | if ('.' !== substr($view_type, 0, 1) && is_dir($view_type_dir)) { |
|
742 | 527 | if (!$this->autoregisterViews('', $view_type_dir, $view_type)) { |
|
743 | $failed_dir = $view_type_dir; |
||
744 | return false; |
||
745 | } |
||
746 | } |
||
747 | } |
||
748 | |||
749 | 527 | return true; |
|
750 | } |
||
751 | |||
752 | /** |
||
753 | * Merge a specification of absolute view paths |
||
754 | * |
||
755 | * @param array $spec Specification |
||
756 | * viewtype => [ |
||
757 | * view_name => path or array of paths |
||
758 | * ] |
||
759 | * |
||
760 | * @return void |
||
761 | * |
||
762 | * @access private |
||
763 | */ |
||
764 | 11 | public function mergeViewsSpec(array $spec) { |
|
765 | 11 | foreach ($spec as $viewtype => $list) { |
|
766 | 11 | foreach ($list as $view => $paths) { |
|
767 | 11 | if (!is_array($paths)) { |
|
768 | 11 | $paths = [$paths]; |
|
769 | } |
||
770 | |||
771 | 11 | foreach ($paths as $path) { |
|
772 | 11 | if (preg_match('~^([/\\\\]|[a-zA-Z]\:)~', $path)) { |
|
773 | // absolute path |
||
774 | } else { |
||
775 | // relative path |
||
776 | 11 | $path = Directory\Local::projectRoot()->getPath($path); |
|
777 | } |
||
778 | |||
779 | 11 | if (substr($view, -1) === '/') { |
|
780 | // prefix |
||
781 | 10 | $this->autoregisterViews($view, $path, $viewtype); |
|
782 | } else { |
||
783 | 11 | $this->setViewLocation($view, $viewtype, $path); |
|
784 | } |
||
785 | } |
||
786 | } |
||
787 | } |
||
788 | 11 | } |
|
789 | |||
790 | /** |
||
791 | * List all views in a viewtype |
||
792 | * |
||
793 | * @param string $viewtype Viewtype |
||
794 | * |
||
795 | * @return string[] |
||
796 | * |
||
797 | * @access private |
||
798 | */ |
||
799 | 1 | public function listViews($viewtype = 'default') { |
|
800 | 1 | if (empty($this->locations[$viewtype])) { |
|
801 | 1 | return []; |
|
802 | } |
||
803 | 1 | return array_keys($this->locations[$viewtype]); |
|
804 | } |
||
805 | |||
806 | /** |
||
807 | * Get inspector data |
||
808 | * |
||
809 | * @return array |
||
810 | * |
||
811 | * @access private |
||
812 | */ |
||
813 | 1 | public function getInspectorData() { |
|
814 | 1 | $overrides = $this->overrides; |
|
815 | |||
816 | 1 | if ($this->cache) { |
|
817 | 1 | $data = $this->cache->load('view_overrides'); |
|
818 | 1 | if ($data) { |
|
819 | 1 | $overrides = unserialize($data); |
|
820 | } |
||
821 | } |
||
822 | |||
823 | return [ |
||
824 | 1 | 'locations' => $this->locations, |
|
825 | 1 | 'overrides' => $overrides, |
|
826 | 1 | 'extensions' => $this->extensions, |
|
827 | 1 | 'simplecache' => $this->simplecache_views, |
|
828 | ]; |
||
829 | } |
||
830 | |||
831 | /** |
||
832 | * Configure locations from the cache |
||
833 | * |
||
834 | * @param SystemCache $cache The system cache |
||
835 | * @return bool |
||
836 | * @access private |
||
837 | */ |
||
838 | 13 | public function configureFromCache(SystemCache $cache) { |
|
839 | 13 | $data = $cache->load('view_locations'); |
|
840 | 13 | if (!is_string($data)) { |
|
841 | 2 | return false; |
|
842 | } |
||
843 | // format changed, check version |
||
844 | 12 | $data = unserialize($data); |
|
845 | 12 | if (empty($data['version']) || $data['version'] !== '2.0') { |
|
846 | return false; |
||
847 | } |
||
848 | 12 | $this->locations = $data['locations']; |
|
849 | 12 | $this->cache = $cache; |
|
850 | |||
851 | 12 | return true; |
|
852 | } |
||
853 | |||
854 | /** |
||
855 | * Cache the configuration |
||
856 | * |
||
857 | * @param SystemCache $cache The system cache |
||
858 | * @return void |
||
859 | * @access private |
||
860 | */ |
||
861 | 3 | public function cacheConfiguration(SystemCache $cache) { |
|
862 | 3 | $cache->save('view_locations', serialize([ |
|
863 | 3 | 'version' => '2.0', |
|
864 | 3 | 'locations' => $this->locations, |
|
865 | ])); |
||
866 | |||
867 | // this is saved just for the inspector and is not loaded in loadAll() |
||
868 | 3 | $cache->save('view_overrides', serialize($this->overrides)); |
|
869 | 3 | } |
|
870 | |||
871 | /** |
||
872 | * Update the location of a view file |
||
873 | * |
||
874 | * @param string $view View name |
||
875 | * @param string $viewtype Viewtype |
||
876 | * @param string $path File path |
||
877 | * |
||
878 | * @return void |
||
879 | */ |
||
880 | 636 | private function setViewLocation($view, $viewtype, $path) { |
|
881 | 636 | $view = self::canonicalizeViewName($view); |
|
882 | 636 | $path = strtr($path, '\\', '/'); |
|
883 | |||
884 | 636 | if (isset($this->locations[$viewtype][$view]) && $path !== $this->locations[$viewtype][$view]) { |
|
885 | 2 | $this->overrides[$viewtype][$view][] = $this->locations[$viewtype][$view]; |
|
886 | } |
||
887 | 636 | $this->locations[$viewtype][$view] = $path; |
|
888 | |||
889 | // Test if view is cacheable and push it to the cacheable views stack, |
||
890 | // if it's not registered as cacheable explicitly |
||
891 | 636 | $this->isCacheableView($view); |
|
892 | 636 | } |
|
893 | } |
||
894 |