Elgg /
Elgg
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'); |
||
| 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
Bug
introduced
by
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 |