Checks if the types of the passed arguments in a function/method call are compatible.
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; |
|||
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'); |
||||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
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'); |
|||
0 ignored issues
–
show
'NOTICE' of type string is incompatible with the type integer expected by parameter $level of Elgg\Logger::log() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
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 |