PageRenderTime 56ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/View/View.php

http://github.com/cakephp/cakephp
PHP | 1684 lines | 755 code | 182 blank | 747 comment | 78 complexity | dc5a8bf50798921fd5f8a9dc7ad5ce82 MD5 | raw file
Possible License(s): JSON

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 0.10.0
  14. * @license https://www.opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\View;
  17. use Cake\Cache\Cache;
  18. use Cake\Core\App;
  19. use Cake\Core\InstanceConfigTrait;
  20. use Cake\Core\Plugin;
  21. use Cake\Event\EventDispatcherInterface;
  22. use Cake\Event\EventDispatcherTrait;
  23. use Cake\Event\EventManager;
  24. use Cake\Http\Response;
  25. use Cake\Http\ServerRequest;
  26. use Cake\Log\LogTrait;
  27. use Cake\Routing\Router;
  28. use Cake\Utility\Inflector;
  29. use Cake\View\Exception\MissingElementException;
  30. use Cake\View\Exception\MissingLayoutException;
  31. use Cake\View\Exception\MissingTemplateException;
  32. use InvalidArgumentException;
  33. use LogicException;
  34. use RuntimeException;
  35. use Throwable;
  36. /**
  37. * View, the V in the MVC triad. View interacts with Helpers and view variables passed
  38. * in from the controller to render the results of the controller action. Often this is HTML,
  39. * but can also take the form of JSON, XML, PDF's or streaming files.
  40. *
  41. * CakePHP uses a two-step-view pattern. This means that the template content is rendered first,
  42. * and then inserted into the selected layout. This also means you can pass data from the template to the
  43. * layout using `$this->set()`
  44. *
  45. * View class supports using plugins as themes. You can set
  46. *
  47. * ```
  48. * public function beforeRender(\Cake\Event\EventInterface $event)
  49. * {
  50. * $this->viewBuilder()->setTheme('SuperHot');
  51. * }
  52. * ```
  53. *
  54. * in your Controller to use plugin `SuperHot` as a theme. Eg. If current action
  55. * is PostsController::index() then View class will look for template file
  56. * `plugins/SuperHot/templates/Posts/index.php`. If a theme template
  57. * is not found for the current action the default app template file is used.
  58. *
  59. * @property \Cake\View\Helper\BreadcrumbsHelper $Breadcrumbs
  60. * @property \Cake\View\Helper\FlashHelper $Flash
  61. * @property \Cake\View\Helper\FormHelper $Form
  62. * @property \Cake\View\Helper\HtmlHelper $Html
  63. * @property \Cake\View\Helper\NumberHelper $Number
  64. * @property \Cake\View\Helper\PaginatorHelper $Paginator
  65. * @property \Cake\View\Helper\TextHelper $Text
  66. * @property \Cake\View\Helper\TimeHelper $Time
  67. * @property \Cake\View\Helper\UrlHelper $Url
  68. * @property \Cake\View\ViewBlock $Blocks
  69. */
  70. class View implements EventDispatcherInterface
  71. {
  72. use CellTrait {
  73. cell as public;
  74. }
  75. use EventDispatcherTrait;
  76. use InstanceConfigTrait {
  77. getConfig as private _getConfig;
  78. }
  79. use LogTrait;
  80. /**
  81. * Helpers collection
  82. *
  83. * @var \Cake\View\HelperRegistry
  84. */
  85. protected $_helpers;
  86. /**
  87. * ViewBlock instance.
  88. *
  89. * @var \Cake\View\ViewBlock
  90. */
  91. protected $Blocks;
  92. /**
  93. * The name of the plugin.
  94. *
  95. * @var string|null
  96. */
  97. protected $plugin;
  98. /**
  99. * Name of the controller that created the View if any.
  100. *
  101. * @var string
  102. */
  103. protected $name = '';
  104. /**
  105. * An array of names of built-in helpers to include.
  106. *
  107. * @var array
  108. */
  109. protected $helpers = [];
  110. /**
  111. * The name of the subfolder containing templates for this View.
  112. *
  113. * @var string
  114. */
  115. protected $templatePath = '';
  116. /**
  117. * The name of the template file to render. The name specified
  118. * is the filename in `templates/<SubFolder>/` without the .php extension.
  119. *
  120. * @var string
  121. */
  122. protected $template = '';
  123. /**
  124. * The name of the layout file to render the template inside of. The name specified
  125. * is the filename of the layout in `templates/layout/` without the .php
  126. * extension.
  127. *
  128. * @var string
  129. */
  130. protected $layout = 'default';
  131. /**
  132. * The name of the layouts subfolder containing layouts for this View.
  133. *
  134. * @var string
  135. */
  136. protected $layoutPath = '';
  137. /**
  138. * Turns on or off CakePHP's conventional mode of applying layout files. On by default.
  139. * Setting to off means that layouts will not be automatically applied to rendered templates.
  140. *
  141. * @var bool
  142. */
  143. protected $autoLayout = true;
  144. /**
  145. * An array of variables
  146. *
  147. * @var array<string, mixed>
  148. */
  149. protected $viewVars = [];
  150. /**
  151. * File extension. Defaults to ".php".
  152. *
  153. * @var string
  154. */
  155. protected $_ext = '.php';
  156. /**
  157. * Sub-directory for this template file. This is often used for extension based routing.
  158. * Eg. With an `xml` extension, $subDir would be `xml/`
  159. *
  160. * @var string
  161. */
  162. protected $subDir = '';
  163. /**
  164. * The view theme to use.
  165. *
  166. * @var string|null
  167. */
  168. protected $theme;
  169. /**
  170. * An instance of a \Cake\Http\ServerRequest object that contains information about the current request.
  171. * This object contains all the information about a request and several methods for reading
  172. * additional information about the request.
  173. *
  174. * @var \Cake\Http\ServerRequest
  175. */
  176. protected $request;
  177. /**
  178. * Reference to the Response object
  179. *
  180. * @var \Cake\Http\Response
  181. */
  182. protected $response;
  183. /**
  184. * The Cache configuration View will use to store cached elements. Changing this will change
  185. * the default configuration elements are stored under. You can also choose a cache config
  186. * per element.
  187. *
  188. * @var string
  189. * @see \Cake\View\View::element()
  190. */
  191. protected $elementCache = 'default';
  192. /**
  193. * List of variables to collect from the associated controller.
  194. *
  195. * @var array<string>
  196. */
  197. protected $_passedVars = [
  198. 'viewVars', 'autoLayout', 'helpers', 'template', 'layout', 'name', 'theme',
  199. 'layoutPath', 'templatePath', 'plugin',
  200. ];
  201. /**
  202. * Default custom config options.
  203. *
  204. * @var array<string, mixed>
  205. */
  206. protected $_defaultConfig = [];
  207. /**
  208. * Holds an array of paths.
  209. *
  210. * @var array<string>
  211. */
  212. protected $_paths = [];
  213. /**
  214. * Holds an array of plugin paths.
  215. *
  216. * @var array<string[]>
  217. */
  218. protected $_pathsForPlugin = [];
  219. /**
  220. * The names of views and their parents used with View::extend();
  221. *
  222. * @var array<string>
  223. */
  224. protected $_parents = [];
  225. /**
  226. * The currently rendering view file. Used for resolving parent files.
  227. *
  228. * @var string
  229. */
  230. protected $_current;
  231. /**
  232. * Currently rendering an element. Used for finding parent fragments
  233. * for elements.
  234. *
  235. * @var string
  236. */
  237. protected $_currentType = '';
  238. /**
  239. * Content stack, used for nested templates that all use View::extend();
  240. *
  241. * @var array<string>
  242. */
  243. protected $_stack = [];
  244. /**
  245. * ViewBlock class.
  246. *
  247. * @var string
  248. * @psalm-var class-string<\Cake\View\ViewBlock>
  249. */
  250. protected $_viewBlockClass = ViewBlock::class;
  251. /**
  252. * Constant for view file type 'template'.
  253. *
  254. * @var string
  255. */
  256. public const TYPE_TEMPLATE = 'template';
  257. /**
  258. * Constant for view file type 'element'
  259. *
  260. * @var string
  261. */
  262. public const TYPE_ELEMENT = 'element';
  263. /**
  264. * Constant for view file type 'layout'
  265. *
  266. * @var string
  267. */
  268. public const TYPE_LAYOUT = 'layout';
  269. /**
  270. * Constant for type used for App::path().
  271. *
  272. * @var string
  273. */
  274. public const NAME_TEMPLATE = 'templates';
  275. /**
  276. * Constant for folder name containing files for overriding plugin templates.
  277. *
  278. * @var string
  279. */
  280. public const PLUGIN_TEMPLATE_FOLDER = 'plugin';
  281. /**
  282. * Constructor
  283. *
  284. * @param \Cake\Http\ServerRequest|null $request Request instance.
  285. * @param \Cake\Http\Response|null $response Response instance.
  286. * @param \Cake\Event\EventManager|null $eventManager Event manager instance.
  287. * @param array<string, mixed> $viewOptions View options. See {@link View::$_passedVars} for list of
  288. * options which get set as class properties.
  289. */
  290. public function __construct(
  291. ?ServerRequest $request = null,
  292. ?Response $response = null,
  293. ?EventManager $eventManager = null,
  294. array $viewOptions = []
  295. ) {
  296. foreach ($this->_passedVars as $var) {
  297. if (isset($viewOptions[$var])) {
  298. $this->{$var} = $viewOptions[$var];
  299. }
  300. }
  301. $this->setConfig(array_diff_key(
  302. $viewOptions,
  303. array_flip($this->_passedVars)
  304. ));
  305. if ($eventManager !== null) {
  306. $this->setEventManager($eventManager);
  307. }
  308. if ($request === null) {
  309. $request = Router::getRequest() ?: new ServerRequest(['base' => '', 'url' => '', 'webroot' => '/']);
  310. }
  311. $this->request = $request;
  312. $this->response = $response ?: new Response();
  313. $this->Blocks = new $this->_viewBlockClass();
  314. $this->initialize();
  315. $this->loadHelpers();
  316. }
  317. /**
  318. * Initialization hook method.
  319. *
  320. * Properties like $helpers etc. cannot be initialized statically in your custom
  321. * view class as they are overwritten by values from controller in constructor.
  322. * So this method allows you to manipulate them as required after view instance
  323. * is constructed.
  324. *
  325. * @return void
  326. */
  327. public function initialize(): void
  328. {
  329. }
  330. /**
  331. * Gets the request instance.
  332. *
  333. * @return \Cake\Http\ServerRequest
  334. * @since 3.7.0
  335. */
  336. public function getRequest(): ServerRequest
  337. {
  338. return $this->request;
  339. }
  340. /**
  341. * Sets the request objects and configures a number of controller properties
  342. * based on the contents of the request. The properties that get set are:
  343. *
  344. * - $this->request - To the $request parameter
  345. * - $this->plugin - To the value returned by $request->getParam('plugin')
  346. *
  347. * @param \Cake\Http\ServerRequest $request Request instance.
  348. * @return $this
  349. */
  350. public function setRequest(ServerRequest $request)
  351. {
  352. $this->request = $request;
  353. $this->plugin = $request->getParam('plugin');
  354. return $this;
  355. }
  356. /**
  357. * Gets the response instance.
  358. *
  359. * @return \Cake\Http\Response
  360. */
  361. public function getResponse(): Response
  362. {
  363. return $this->response;
  364. }
  365. /**
  366. * Sets the response instance.
  367. *
  368. * @param \Cake\Http\Response $response Response instance.
  369. * @return $this
  370. */
  371. public function setResponse(Response $response)
  372. {
  373. $this->response = $response;
  374. return $this;
  375. }
  376. /**
  377. * Get path for templates files.
  378. *
  379. * @return string
  380. */
  381. public function getTemplatePath(): string
  382. {
  383. return $this->templatePath;
  384. }
  385. /**
  386. * Set path for templates files.
  387. *
  388. * @param string $path Path for template files.
  389. * @return $this
  390. */
  391. public function setTemplatePath(string $path)
  392. {
  393. $this->templatePath = $path;
  394. return $this;
  395. }
  396. /**
  397. * Get path for layout files.
  398. *
  399. * @return string
  400. */
  401. public function getLayoutPath(): string
  402. {
  403. return $this->layoutPath;
  404. }
  405. /**
  406. * Set path for layout files.
  407. *
  408. * @param string $path Path for layout files.
  409. * @return $this
  410. */
  411. public function setLayoutPath(string $path)
  412. {
  413. $this->layoutPath = $path;
  414. return $this;
  415. }
  416. /**
  417. * Returns if CakePHP's conventional mode of applying layout files is enabled.
  418. * Disabled means that layouts will not be automatically applied to rendered views.
  419. *
  420. * @return bool
  421. */
  422. public function isAutoLayoutEnabled(): bool
  423. {
  424. return $this->autoLayout;
  425. }
  426. /**
  427. * Turns on or off CakePHP's conventional mode of applying layout files.
  428. * On by default. Setting to off means that layouts will not be
  429. * automatically applied to rendered views.
  430. *
  431. * @param bool $enable Boolean to turn on/off.
  432. * @return $this
  433. */
  434. public function enableAutoLayout(bool $enable = true)
  435. {
  436. $this->autoLayout = $enable;
  437. return $this;
  438. }
  439. /**
  440. * Turns off CakePHP's conventional mode of applying layout files.
  441. * Layouts will not be automatically applied to rendered views.
  442. *
  443. * @return $this
  444. */
  445. public function disableAutoLayout()
  446. {
  447. $this->autoLayout = false;
  448. return $this;
  449. }
  450. /**
  451. * Get the current view theme.
  452. *
  453. * @return string|null
  454. */
  455. public function getTheme(): ?string
  456. {
  457. return $this->theme;
  458. }
  459. /**
  460. * Set the view theme to use.
  461. *
  462. * @param string|null $theme Theme name.
  463. * @return $this
  464. */
  465. public function setTheme(?string $theme)
  466. {
  467. $this->theme = $theme;
  468. return $this;
  469. }
  470. /**
  471. * Get the name of the template file to render. The name specified is the
  472. * filename in `templates/<SubFolder>/` without the .php extension.
  473. *
  474. * @return string
  475. */
  476. public function getTemplate(): string
  477. {
  478. return $this->template;
  479. }
  480. /**
  481. * Set the name of the template file to render. The name specified is the
  482. * filename in `templates/<SubFolder>/` without the .php extension.
  483. *
  484. * @param string $name Template file name to set.
  485. * @return $this
  486. */
  487. public function setTemplate(string $name)
  488. {
  489. $this->template = $name;
  490. return $this;
  491. }
  492. /**
  493. * Get the name of the layout file to render the template inside of.
  494. * The name specified is the filename of the layout in `templates/layout/`
  495. * without the .php extension.
  496. *
  497. * @return string
  498. */
  499. public function getLayout(): string
  500. {
  501. return $this->layout;
  502. }
  503. /**
  504. * Set the name of the layout file to render the template inside of.
  505. * The name specified is the filename of the layout in `templates/layout/`
  506. * without the .php extension.
  507. *
  508. * @param string $name Layout file name to set.
  509. * @return $this
  510. */
  511. public function setLayout(string $name)
  512. {
  513. $this->layout = $name;
  514. return $this;
  515. }
  516. /**
  517. * Get config value.
  518. *
  519. * Currently if config is not set it fallbacks to checking corresponding
  520. * view var with underscore prefix. Using underscore prefixed special view
  521. * vars is deprecated and this fallback will be removed in CakePHP 4.1.0.
  522. *
  523. * @param string|null $key The key to get or null for the whole config.
  524. * @param mixed $default The return value when the key does not exist.
  525. * @return mixed Config value being read.
  526. * @psalm-suppress PossiblyNullArgument
  527. */
  528. public function getConfig(?string $key = null, $default = null)
  529. {
  530. $value = $this->_getConfig($key);
  531. if ($value !== null) {
  532. return $value;
  533. }
  534. if (isset($this->viewVars["_{$key}"])) {
  535. deprecationWarning(sprintf(
  536. 'Setting special view var "_%s" is deprecated. Use ViewBuilder::setOption(\'%s\', $value) instead.',
  537. $key,
  538. $key
  539. ));
  540. return $this->viewVars["_{$key}"];
  541. }
  542. return $default;
  543. }
  544. /**
  545. * Renders a piece of PHP with provided parameters and returns HTML, XML, or any other string.
  546. *
  547. * This realizes the concept of Elements, (or "partial layouts") and the $params array is used to send
  548. * data to be used in the element. Elements can be cached improving performance by using the `cache` option.
  549. *
  550. * @param string $name Name of template file in the `templates/element/` folder,
  551. * or `MyPlugin.template` to use the template element from MyPlugin. If the element
  552. * is not found in the plugin, the normal view path cascade will be searched.
  553. * @param array $data Array of data to be made available to the rendered view (i.e. the Element)
  554. * @param array<string, mixed> $options Array of options. Possible keys are:
  555. *
  556. * - `cache` - Can either be `true`, to enable caching using the config in View::$elementCache. Or an array
  557. * If an array, the following keys can be used:
  558. *
  559. * - `config` - Used to store the cached element in a custom cache configuration.
  560. * - `key` - Used to define the key used in the Cache::write(). It will be prefixed with `element_`
  561. *
  562. * - `callbacks` - Set to true to fire beforeRender and afterRender helper callbacks for this element.
  563. * Defaults to false.
  564. * - `ignoreMissing` - Used to allow missing elements. Set to true to not throw exceptions.
  565. * - `plugin` - setting to false will force to use the application's element from plugin templates, when the
  566. * plugin has element with same name. Defaults to true
  567. * @return string Rendered Element
  568. * @throws \Cake\View\Exception\MissingElementException When an element is missing and `ignoreMissing`
  569. * is false.
  570. * @psalm-param array{cache?:array|true, callbacks?:bool, plugin?:string|false, ignoreMissing?:bool} $options
  571. */
  572. public function element(string $name, array $data = [], array $options = []): string
  573. {
  574. $options += ['callbacks' => false, 'cache' => null, 'plugin' => null, 'ignoreMissing' => false];
  575. if (isset($options['cache'])) {
  576. $options['cache'] = $this->_elementCache(
  577. $name,
  578. $data,
  579. array_diff_key($options, ['callbacks' => false, 'plugin' => null, 'ignoreMissing' => null])
  580. );
  581. }
  582. $pluginCheck = $options['plugin'] !== false;
  583. $file = $this->_getElementFileName($name, $pluginCheck);
  584. if ($file && $options['cache']) {
  585. return $this->cache(function () use ($file, $data, $options): void {
  586. echo $this->_renderElement($file, $data, $options);
  587. }, $options['cache']);
  588. }
  589. if ($file) {
  590. return $this->_renderElement($file, $data, $options);
  591. }
  592. if ($options['ignoreMissing']) {
  593. return '';
  594. }
  595. [$plugin, $elementName] = $this->pluginSplit($name, $pluginCheck);
  596. $paths = iterator_to_array($this->getElementPaths($plugin));
  597. throw new MissingElementException([$name . $this->_ext, $elementName . $this->_ext], $paths);
  598. }
  599. /**
  600. * Create a cached block of view logic.
  601. *
  602. * This allows you to cache a block of view output into the cache
  603. * defined in `elementCache`.
  604. *
  605. * This method will attempt to read the cache first. If the cache
  606. * is empty, the $block will be run and the output stored.
  607. *
  608. * @param callable $block The block of code that you want to cache the output of.
  609. * @param array<string, mixed> $options The options defining the cache key etc.
  610. * @return string The rendered content.
  611. * @throws \RuntimeException When $options is lacking a 'key' option.
  612. */
  613. public function cache(callable $block, array $options = []): string
  614. {
  615. $options += ['key' => '', 'config' => $this->elementCache];
  616. if (empty($options['key'])) {
  617. throw new RuntimeException('Cannot cache content with an empty key');
  618. }
  619. $result = Cache::read($options['key'], $options['config']);
  620. if ($result) {
  621. return $result;
  622. }
  623. $bufferLevel = ob_get_level();
  624. ob_start();
  625. try {
  626. $block();
  627. } catch (Throwable $exception) {
  628. while (ob_get_level() > $bufferLevel) {
  629. ob_end_clean();
  630. }
  631. throw $exception;
  632. }
  633. $result = ob_get_clean();
  634. Cache::write($options['key'], $result, $options['config']);
  635. return $result;
  636. }
  637. /**
  638. * Checks if an element exists
  639. *
  640. * @param string $name Name of template file in the `templates/element/` folder,
  641. * or `MyPlugin.template` to check the template element from MyPlugin. If the element
  642. * is not found in the plugin, the normal view path cascade will be searched.
  643. * @return bool Success
  644. */
  645. public function elementExists(string $name): bool
  646. {
  647. return (bool)$this->_getElementFileName($name);
  648. }
  649. /**
  650. * Renders view for given template file and layout.
  651. *
  652. * Render triggers helper callbacks, which are fired before and after the template are rendered,
  653. * as well as before and after the layout. The helper callbacks are called:
  654. *
  655. * - `beforeRender`
  656. * - `afterRender`
  657. * - `beforeLayout`
  658. * - `afterLayout`
  659. *
  660. * If View::$autoLayout is set to `false`, the template will be returned bare.
  661. *
  662. * Template and layout names can point to plugin templates or layouts. Using the `Plugin.template` syntax
  663. * a plugin template/layout/ can be used instead of the app ones. If the chosen plugin is not found
  664. * the template will be located along the regular view path cascade.
  665. *
  666. * @param string|null $template Name of template file to use
  667. * @param string|false|null $layout Layout to use. False to disable.
  668. * @return string Rendered content.
  669. * @throws \Cake\Core\Exception\CakeException If there is an error in the view.
  670. * @triggers View.beforeRender $this, [$templateFileName]
  671. * @triggers View.afterRender $this, [$templateFileName]
  672. */
  673. public function render(?string $template = null, $layout = null): string
  674. {
  675. $defaultLayout = '';
  676. $defaultAutoLayout = null;
  677. if ($layout === false) {
  678. $defaultAutoLayout = $this->autoLayout;
  679. $this->autoLayout = false;
  680. } elseif ($layout !== null) {
  681. $defaultLayout = $this->layout;
  682. $this->layout = $layout;
  683. }
  684. $templateFileName = $this->_getTemplateFileName($template);
  685. $this->_currentType = static::TYPE_TEMPLATE;
  686. $this->dispatchEvent('View.beforeRender', [$templateFileName]);
  687. $this->Blocks->set('content', $this->_render($templateFileName));
  688. $this->dispatchEvent('View.afterRender', [$templateFileName]);
  689. if ($this->autoLayout) {
  690. if (empty($this->layout)) {
  691. throw new RuntimeException(
  692. 'View::$layout must be a non-empty string.' .
  693. 'To disable layout rendering use method View::disableAutoLayout() instead.'
  694. );
  695. }
  696. $this->Blocks->set('content', $this->renderLayout('', $this->layout));
  697. }
  698. if ($layout !== null) {
  699. $this->layout = $defaultLayout;
  700. }
  701. if ($defaultAutoLayout !== null) {
  702. $this->autoLayout = $defaultAutoLayout;
  703. }
  704. return $this->Blocks->get('content');
  705. }
  706. /**
  707. * Renders a layout. Returns output from _render().
  708. *
  709. * Several variables are created for use in layout.
  710. *
  711. * @param string $content Content to render in a template, wrapped by the surrounding layout.
  712. * @param string|null $layout Layout name
  713. * @return string Rendered output.
  714. * @throws \Cake\Core\Exception\CakeException if there is an error in the view.
  715. * @triggers View.beforeLayout $this, [$layoutFileName]
  716. * @triggers View.afterLayout $this, [$layoutFileName]
  717. */
  718. public function renderLayout(string $content, ?string $layout = null): string
  719. {
  720. $layoutFileName = $this->_getLayoutFileName($layout);
  721. if (!empty($content)) {
  722. $this->Blocks->set('content', $content);
  723. }
  724. $this->dispatchEvent('View.beforeLayout', [$layoutFileName]);
  725. $title = $this->Blocks->get('title');
  726. if ($title === '') {
  727. $title = Inflector::humanize(str_replace(DIRECTORY_SEPARATOR, '/', $this->templatePath));
  728. $this->Blocks->set('title', $title);
  729. }
  730. $this->_currentType = static::TYPE_LAYOUT;
  731. $this->Blocks->set('content', $this->_render($layoutFileName));
  732. $this->dispatchEvent('View.afterLayout', [$layoutFileName]);
  733. return $this->Blocks->get('content');
  734. }
  735. /**
  736. * Returns a list of variables available in the current View context
  737. *
  738. * @return array<string> Array of the set view variable names.
  739. */
  740. public function getVars(): array
  741. {
  742. return array_keys($this->viewVars);
  743. }
  744. /**
  745. * Returns the contents of the given View variable.
  746. *
  747. * @param string $var The view var you want the contents of.
  748. * @param mixed $default The default/fallback content of $var.
  749. * @return mixed The content of the named var if its set, otherwise $default.
  750. */
  751. public function get(string $var, $default = null)
  752. {
  753. return $this->viewVars[$var] ?? $default;
  754. }
  755. /**
  756. * Saves a variable or an associative array of variables for use inside a template.
  757. *
  758. * @param array|string $name A string or an array of data.
  759. * @param mixed $value Value in case $name is a string (which then works as the key).
  760. * Unused if $name is an associative array, otherwise serves as the values to $name's keys.
  761. * @return $this
  762. * @throws \RuntimeException If the array combine operation failed.
  763. */
  764. public function set($name, $value = null)
  765. {
  766. if (is_array($name)) {
  767. if (is_array($value)) {
  768. /** @var array|false $data */
  769. $data = array_combine($name, $value);
  770. if ($data === false) {
  771. throw new RuntimeException(
  772. 'Invalid data provided for array_combine() to work: Both $name and $value require same count.'
  773. );
  774. }
  775. } else {
  776. $data = $name;
  777. }
  778. } else {
  779. $data = [$name => $value];
  780. }
  781. $this->viewVars = $data + $this->viewVars;
  782. return $this;
  783. }
  784. /**
  785. * Get the names of all the existing blocks.
  786. *
  787. * @return array<string> An array containing the blocks.
  788. * @see \Cake\View\ViewBlock::keys()
  789. */
  790. public function blocks(): array
  791. {
  792. return $this->Blocks->keys();
  793. }
  794. /**
  795. * Start capturing output for a 'block'
  796. *
  797. * You can use start on a block multiple times to
  798. * append or prepend content in a capture mode.
  799. *
  800. * ```
  801. * // Append content to an existing block.
  802. * $this->start('content');
  803. * echo $this->fetch('content');
  804. * echo 'Some new content';
  805. * $this->end();
  806. *
  807. * // Prepend content to an existing block
  808. * $this->start('content');
  809. * echo 'Some new content';
  810. * echo $this->fetch('content');
  811. * $this->end();
  812. * ```
  813. *
  814. * @param string $name The name of the block to capture for.
  815. * @return $this
  816. * @see \Cake\View\ViewBlock::start()
  817. */
  818. public function start(string $name)
  819. {
  820. $this->Blocks->start($name);
  821. return $this;
  822. }
  823. /**
  824. * Append to an existing or new block.
  825. *
  826. * Appending to a new block will create the block.
  827. *
  828. * @param string $name Name of the block
  829. * @param mixed $value The content for the block. Value will be type cast
  830. * to string.
  831. * @return $this
  832. * @see \Cake\View\ViewBlock::concat()
  833. */
  834. public function append(string $name, $value = null)
  835. {
  836. $this->Blocks->concat($name, $value);
  837. return $this;
  838. }
  839. /**
  840. * Prepend to an existing or new block.
  841. *
  842. * Prepending to a new block will create the block.
  843. *
  844. * @param string $name Name of the block
  845. * @param mixed $value The content for the block. Value will be type cast
  846. * to string.
  847. * @return $this
  848. * @see \Cake\View\ViewBlock::concat()
  849. */
  850. public function prepend(string $name, $value)
  851. {
  852. $this->Blocks->concat($name, $value, ViewBlock::PREPEND);
  853. return $this;
  854. }
  855. /**
  856. * Set the content for a block. This will overwrite any
  857. * existing content.
  858. *
  859. * @param string $name Name of the block
  860. * @param mixed $value The content for the block. Value will be type cast
  861. * to string.
  862. * @return $this
  863. * @see \Cake\View\ViewBlock::set()
  864. */
  865. public function assign(string $name, $value)
  866. {
  867. $this->Blocks->set($name, $value);
  868. return $this;
  869. }
  870. /**
  871. * Reset the content for a block. This will overwrite any
  872. * existing content.
  873. *
  874. * @param string $name Name of the block
  875. * @return $this
  876. * @see \Cake\View\ViewBlock::set()
  877. */
  878. public function reset(string $name)
  879. {
  880. $this->assign($name, '');
  881. return $this;
  882. }
  883. /**
  884. * Fetch the content for a block. If a block is
  885. * empty or undefined '' will be returned.
  886. *
  887. * @param string $name Name of the block
  888. * @param string $default Default text
  889. * @return string The block content or $default if the block does not exist.
  890. * @see \Cake\View\ViewBlock::get()
  891. */
  892. public function fetch(string $name, string $default = ''): string
  893. {
  894. return $this->Blocks->get($name, $default);
  895. }
  896. /**
  897. * End a capturing block. The compliment to View::start()
  898. *
  899. * @return $this
  900. * @see \Cake\View\ViewBlock::end()
  901. */
  902. public function end()
  903. {
  904. $this->Blocks->end();
  905. return $this;
  906. }
  907. /**
  908. * Check if a block exists
  909. *
  910. * @param string $name Name of the block
  911. * @return bool
  912. */
  913. public function exists(string $name): bool
  914. {
  915. return $this->Blocks->exists($name);
  916. }
  917. /**
  918. * Provides template or element extension/inheritance. Templates can extends a
  919. * parent template and populate blocks in the parent template.
  920. *
  921. * @param string $name The template or element to 'extend' the current one with.
  922. * @return $this
  923. * @throws \LogicException when you extend a template with itself or make extend loops.
  924. * @throws \LogicException when you extend an element which doesn't exist
  925. */
  926. public function extend(string $name)
  927. {
  928. $type = $name[0] === '/' ? static::TYPE_TEMPLATE : $this->_currentType;
  929. switch ($type) {
  930. case static::TYPE_ELEMENT:
  931. $parent = $this->_getElementFileName($name);
  932. if (!$parent) {
  933. [$plugin, $name] = $this->pluginSplit($name);
  934. $paths = $this->_paths($plugin);
  935. $defaultPath = $paths[0] . static::TYPE_ELEMENT . DIRECTORY_SEPARATOR;
  936. throw new LogicException(sprintf(
  937. 'You cannot extend an element which does not exist (%s).',
  938. $defaultPath . $name . $this->_ext
  939. ));
  940. }
  941. break;
  942. case static::TYPE_LAYOUT:
  943. $parent = $this->_getLayoutFileName($name);
  944. break;
  945. default:
  946. $parent = $this->_getTemplateFileName($name);
  947. }
  948. if ($parent === $this->_current) {
  949. throw new LogicException('You cannot have templates extend themselves.');
  950. }
  951. if (isset($this->_parents[$parent]) && $this->_parents[$parent] === $this->_current) {
  952. throw new LogicException('You cannot have templates extend in a loop.');
  953. }
  954. $this->_parents[$this->_current] = $parent;
  955. return $this;
  956. }
  957. /**
  958. * Retrieve the current template type
  959. *
  960. * @return string
  961. */
  962. public function getCurrentType(): string
  963. {
  964. return $this->_currentType;
  965. }
  966. /**
  967. * Magic accessor for helpers.
  968. *
  969. * @param string $name Name of the attribute to get.
  970. * @return \Cake\View\Helper|null
  971. */
  972. public function __get(string $name)
  973. {
  974. $registry = $this->helpers();
  975. if (!isset($registry->{$name})) {
  976. return null;
  977. }
  978. $this->{$name} = $registry->{$name};
  979. return $registry->{$name};
  980. }
  981. /**
  982. * Interact with the HelperRegistry to load all the helpers.
  983. *
  984. * @return $this
  985. */
  986. public function loadHelpers()
  987. {
  988. $registry = $this->helpers();
  989. $helpers = $registry->normalizeArray($this->helpers);
  990. foreach ($helpers as $properties) {
  991. $this->loadHelper($properties['class'], $properties['config']);
  992. }
  993. return $this;
  994. }
  995. /**
  996. * Renders and returns output for given template filename with its
  997. * array of data. Handles parent/extended templates.
  998. *
  999. * @param string $templateFile Filename of the template
  1000. * @param array $data Data to include in rendered view. If empty the current
  1001. * View::$viewVars will be used.
  1002. * @return string Rendered output
  1003. * @throws \LogicException When a block is left open.
  1004. * @triggers View.beforeRenderFile $this, [$templateFile]
  1005. * @triggers View.afterRenderFile $this, [$templateFile, $content]
  1006. */
  1007. protected function _render(string $templateFile, array $data = []): string
  1008. {
  1009. if (empty($data)) {
  1010. $data = $this->viewVars;
  1011. }
  1012. $this->_current = $templateFile;
  1013. $initialBlocks = count($this->Blocks->unclosed());
  1014. $this->dispatchEvent('View.beforeRenderFile', [$templateFile]);
  1015. $content = $this->_evaluate($templateFile, $data);
  1016. $afterEvent = $this->dispatchEvent('View.afterRenderFile', [$templateFile, $content]);
  1017. if ($afterEvent->getResult() !== null) {
  1018. $content = $afterEvent->getResult();
  1019. }
  1020. if (isset($this->_parents[$templateFile])) {
  1021. $this->_stack[] = $this->fetch('content');
  1022. $this->assign('content', $content);
  1023. $content = $this->_render($this->_parents[$templateFile]);
  1024. $this->assign('content', array_pop($this->_stack));
  1025. }
  1026. $remainingBlocks = count($this->Blocks->unclosed());
  1027. if ($initialBlocks !== $remainingBlocks) {
  1028. throw new LogicException(sprintf(
  1029. 'The "%s" block was left open. Blocks are not allowed to cross files.',
  1030. (string)$this->Blocks->active()
  1031. ));
  1032. }
  1033. return $content;
  1034. }
  1035. /**
  1036. * Sandbox method to evaluate a template / view script in.
  1037. *
  1038. * @param string $templateFile Filename of the template.
  1039. * @param array $dataForView Data to include in rendered view.
  1040. * @return string Rendered output
  1041. */
  1042. protected function _evaluate(string $templateFile, array $dataForView): string
  1043. {
  1044. extract($dataForView);
  1045. $bufferLevel = ob_get_level();
  1046. ob_start();
  1047. try {
  1048. // Avoiding $templateFile here due to collision with extract() vars.
  1049. include func_get_arg(0);
  1050. } catch (Throwable $exception) {
  1051. while (ob_get_level() > $bufferLevel) {
  1052. ob_end_clean();
  1053. }
  1054. throw $exception;
  1055. }
  1056. return ob_get_clean();
  1057. }
  1058. /**
  1059. * Get the helper registry in use by this View class.
  1060. *
  1061. * @return \Cake\View\HelperRegistry
  1062. */
  1063. public function helpers(): HelperRegistry
  1064. {
  1065. if ($this->_helpers === null) {
  1066. $this->_helpers = new HelperRegistry($this);
  1067. }
  1068. return $this->_helpers;
  1069. }
  1070. /**
  1071. * Loads a helper. Delegates to the `HelperRegistry::load()` to load the helper
  1072. *
  1073. * @param string $name Name of the helper to load.
  1074. * @param array<string, mixed> $config Settings for the helper
  1075. * @return \Cake\View\Helper a constructed helper object.
  1076. * @see \Cake\View\HelperRegistry::load()
  1077. */
  1078. public function loadHelper(string $name, array $config = []): Helper
  1079. {
  1080. [, $class] = pluginSplit($name);
  1081. $helpers = $this->helpers();
  1082. return $this->{$class} = $helpers->load($name, $config);
  1083. }
  1084. /**
  1085. * Set sub-directory for this template files.
  1086. *
  1087. * @param string $subDir Sub-directory name.
  1088. * @return $this
  1089. * @see \Cake\View\View::$subDir
  1090. * @since 3.7.0
  1091. */
  1092. public function setSubDir(string $subDir)
  1093. {
  1094. $this->subDir = $subDir;
  1095. return $this;
  1096. }
  1097. /**
  1098. * Get sub-directory for this template files.
  1099. *
  1100. * @return string
  1101. * @see \Cake\View\View::$subDir
  1102. * @since 3.7.0
  1103. */
  1104. public function getSubDir(): string
  1105. {
  1106. return $this->subDir;
  1107. }
  1108. /**
  1109. * Returns the View's controller name.
  1110. *
  1111. * @return string
  1112. * @since 3.7.7
  1113. */
  1114. public function getName(): string
  1115. {
  1116. return $this->name;
  1117. }
  1118. /**
  1119. * Returns the plugin name.
  1120. *
  1121. * @return string|null
  1122. * @since 3.7.0
  1123. */
  1124. public function getPlugin(): ?string
  1125. {
  1126. return $this->plugin;
  1127. }
  1128. /**
  1129. * Sets the plugin name.
  1130. *
  1131. * @param string|null $name Plugin name.
  1132. * @return $this
  1133. * @since 3.7.0
  1134. */
  1135. public function setPlugin(?string $name)
  1136. {
  1137. $this->plugin = $name;
  1138. return $this;
  1139. }
  1140. /**
  1141. * Set The cache configuration View will use to store cached elements
  1142. *
  1143. * @param string $elementCache Cache config name.
  1144. * @return $this
  1145. * @see \Cake\View\View::$elementCache
  1146. * @since 3.7.0
  1147. */
  1148. public function setElementCache(string $elementCache)
  1149. {
  1150. $this->elementCache = $elementCache;
  1151. return $this;
  1152. }
  1153. /**
  1154. * Returns filename of given action's template file as a string.
  1155. * CamelCased action names will be under_scored by default.
  1156. * This means that you can have LongActionNames that refer to
  1157. * long_action_names.php templates. You can change the inflection rule by
  1158. * overriding _inflectTemplateFileName.
  1159. *
  1160. * @param string|null $name Controller action to find template filename for
  1161. * @return string Template filename
  1162. * @throws \Cake\View\Exception\MissingTemplateException when a template file could not be found.
  1163. * @throws \RuntimeException When template name not provided.
  1164. */
  1165. protected function _getTemplateFileName(?string $name = null): string
  1166. {
  1167. $templatePath = $subDir = '';
  1168. if ($this->templatePath) {
  1169. $templatePath = $this->templatePath . DIRECTORY_SEPARATOR;
  1170. }
  1171. if ($this->subDir !== '') {
  1172. $subDir = $this->subDir . DIRECTORY_SEPARATOR;
  1173. // Check if templatePath already terminates with subDir
  1174. if ($templatePath != $subDir && substr($templatePath, -strlen($subDir)) === $subDir) {
  1175. $subDir = '';
  1176. }
  1177. }
  1178. if ($name === null) {
  1179. $name = $this->template;
  1180. }
  1181. if (empty($name)) {
  1182. throw new RuntimeException('Template name not provided');
  1183. }
  1184. [$plugin, $name] = $this->pluginSplit($name);
  1185. $name = str_replace('/', DIRECTORY_SEPARATOR, $name);
  1186. if (strpos($name, DIRECTORY_SEPARATOR) === false && $name !== '' && $name[0] !== '.') {
  1187. $name = $templatePath . $subDir . $this->_inflectTemplateFileName($name);
  1188. } elseif (strpos($name, DIRECTORY_SEPARATOR) !== false) {
  1189. if ($name[0] === DIRECTORY_SEPARATOR || $name[1] === ':') {
  1190. $name = trim($name, DIRECTORY_SEPARATOR);
  1191. } elseif (!$plugin || $this->templatePath !== $this->name) {
  1192. $name = $templatePath . $subDir . $name;
  1193. } else {
  1194. $name = $subDir . $name;
  1195. }
  1196. }
  1197. $name .= $this->_ext;
  1198. $paths = $this->_paths($plugin);
  1199. foreach ($paths as $path) {
  1200. if (is_file($path . $name)) {
  1201. return $this->_checkFilePath($path . $name, $path);
  1202. }
  1203. }
  1204. throw new MissingTemplateException($name, $paths);
  1205. }
  1206. /**
  1207. * Change the name of a view template file into underscored format.
  1208. *
  1209. * @param string $name Name of file which should be inflected.
  1210. * @return string File name after conversion
  1211. */
  1212. protected function _inflectTemplateFileName(string $name): string
  1213. {
  1214. return Inflector::underscore($name);
  1215. }
  1216. /**
  1217. * Check that a view file path does not go outside of the defined template paths.
  1218. *
  1219. * Only paths that contain `..` will be checked, as they are the ones most likely to
  1220. * have the ability to resolve to files outside of the template paths.
  1221. *
  1222. * @param string $file The path to the template file.
  1223. * @param string $path Base path that $file should be inside of.
  1224. * @return string The file path
  1225. * @throws \InvalidArgumentException
  1226. */
  1227. protected function _checkFilePath(string $file, string $path): string
  1228. {
  1229. if (strpos($file, '..') === false) {
  1230. return $file;
  1231. }
  1232. $absolute = realpath($file);
  1233. if (strpos($absolute, $path) !== 0) {
  1234. throw new InvalidArgumentException(sprintf(
  1235. 'Cannot use "%s" as a template, it is not within any view template path.',
  1236. $file
  1237. ));
  1238. }
  1239. return $absolute;
  1240. }
  1241. /**
  1242. * Splits a dot syntax plugin name into its plugin and filename.
  1243. * If $name does not have a dot, then index 0 will be null.
  1244. * It checks if the plugin is loaded, else filename will stay unchanged for filenames containing dot
  1245. *
  1246. * @param string $name The name you want to plugin split.
  1247. * @param bool $fallback If true uses the plugin set in the current Request when parsed plugin is not loaded
  1248. * @return array Array with 2 indexes. 0 => plugin name, 1 => filename.
  1249. * @psalm-return array{string|null, string}
  1250. */
  1251. public function pluginSplit(string $name, bool $fallback = true): array
  1252. {
  1253. $plugin = null;
  1254. [$first, $second] = pluginSplit($name);
  1255. if ($first && Plugin::isLoaded($first)) {
  1256. $name = $second;
  1257. $plugin = $first;
  1258. }
  1259. if (isset($this->plugin) && !$plugin && $fallback) {
  1260. $plugin = $this->plugin;
  1261. }
  1262. return [$plugin, $name];
  1263. }
  1264. /**
  1265. * Returns layout filename for this template as a string.
  1266. *
  1267. * @param string|null $name The name of the layout to find.
  1268. * @return string Filename for layout file.
  1269. * @throws \Cake\View\Exception\MissingLayoutException when a layout cannot be located
  1270. * @throws \RuntimeException
  1271. */
  1272. protected function _getLayoutFileName(?string $name = null): string
  1273. {
  1274. if ($name === null) {
  1275. if (empty($this->layout)) {
  1276. throw new RuntimeException(
  1277. 'View::$layout must be a non-empty string.' .
  1278. 'To disable layout rendering use method View::disableAutoLayout() instead.'
  1279. );
  1280. }
  1281. $name = $this->layout;
  1282. }
  1283. [$plugin, $name] = $this->pluginSplit($name);
  1284. $name .= $this->_ext;
  1285. foreach ($this->getLayoutPaths($plugin) as $path) {
  1286. if (is_file($path . $name)) {
  1287. return $this->_checkFilePath($path . $name, $path);
  1288. }
  1289. }
  1290. $paths = iterator_to_array($this->getLayoutPaths($plugin));
  1291. throw new MissingLayoutException($name, $paths);
  1292. }
  1293. /**
  1294. * Get an iterator for layout paths.
  1295. *
  1296. * @param string|null $plugin The plugin to fetch paths for.
  1297. * @return \Generator
  1298. */
  1299. protected function getLayoutPaths(?string $plugin)
  1300. {
  1301. $subDir = '';
  1302. if ($this->layoutPath) {
  1303. $subDir = $this->layoutPath . DIRECTORY_SEPARATOR;
  1304. }
  1305. $layoutPaths = $this->_getSubPaths(static::TYPE_LAYOUT . DIRECTORY_SEPARATOR . $subDir);
  1306. foreach ($this->_paths($plugin) as $path) {
  1307. foreach ($layoutPaths as $layoutPath) {
  1308. yield $path . $layoutPath;
  1309. }
  1310. }
  1311. }
  1312. /**
  1313. * Finds an element filename, returns false on failure.
  1314. *
  1315. * @param string $name The name of the element to find.
  1316. * @param bool $pluginCheck - if false will ignore the request's plugin if parsed plugin is not loaded
  1317. * @return string|false Either a string to the element filename or false when one can't be found.
  1318. */
  1319. protected function _getElementFileName(string $name, bool $pluginCheck = true)
  1320. {
  1321. [$plugin, $name] = $this->pluginSplit($name, $pluginCheck);
  1322. $name .= $this->_ext;
  1323. foreach ($this->getElementPaths($plugin) as $path) {
  1324. if (is_file($path . $name)) {
  1325. return $path . $name;
  1326. }
  1327. }
  1328. return false;
  1329. }
  1330. /**
  1331. * Get an iterator for element paths.
  1332. *
  1333. * @param string|null $plugin The plugin to fetch paths for.
  1334. * @return \Generator
  1335. */
  1336. protected function getElementPaths(?string $plugin)
  1337. {
  1338. $elementPaths = $this->_getSubPaths(static::TYPE_ELEMENT);
  1339. foreach ($this->_paths($plugin) as $path) {
  1340. foreach ($elementPaths as $subdir) {
  1341. yield $path . $subdir . DIRECTORY_SEPARATOR;
  1342. }
  1343. }
  1344. }
  1345. /**
  1346. * Find all sub templates path, based on $basePath
  1347. * If a prefix is defined in the current request, this method will prepend
  1348. * the prefixed template path to the $basePath, cascading up in case the prefix
  1349. * is nested.
  1350. * This is essentially used to find prefixed template paths for elements
  1351. * and layouts.
  1352. *
  1353. * @param string $basePath Base path on which to get the prefixed one.
  1354. * @return array<string> Array with all the templates paths.
  1355. */
  1356. protected function _getSubPaths(string $basePath): array
  1357. {
  1358. $paths = [$basePath];
  1359. if ($this->request->getParam('prefix')) {
  1360. $prefixPath = explode('/', $this->request->getParam('prefix'));
  1361. $path = '';
  1362. foreach ($prefixPath as $prefixPart) {
  1363. $path .= Inflector::camelize($prefixPart) . DIRECTORY_SEPARATOR;
  1364. array_unshift(
  1365. $paths,
  1366. $path . $basePath
  1367. );
  1368. }
  1369. }
  1370. return $paths;
  1371. }
  1372. /**
  1373. * Return all possible paths to find view files in order
  1374. *
  1375. * @param string|null $plugin Optional plugin name to scan for view files.
  1376. * @param bool $cached Set to false to force a refresh of view paths. Default true.
  1377. * @return array<string> paths
  1378. */
  1379. protected function _paths(?string $plugin = null, bool $cached = true): array
  1380. {
  1381. if ($cached === true) {
  1382. if ($plugin === null && !empty($this->_paths)) {
  1383. return $this->_paths;
  1384. }
  1385. if ($plugin !== null && isset($this->_pathsForPlugin[$plugin])) {
  1386. return $this->_pathsForPlugin[$plugin];
  1387. }
  1388. }
  1389. $templatePaths = App::path(static::NAME_TEMPLATE);
  1390. $pluginPaths = $themePaths = [];
  1391. if (!empty($plugin)) {
  1392. for ($i = 0, $count = count($templatePaths); $i < $count; $i++) {
  1393. $pluginPaths[] = $templatePaths[$i]
  1394. . static::PLUGIN_TEMPLATE_FOLDER
  1395. . DIRECTORY_SEPARATOR
  1396. . $plugin
  1397. . DIRECTORY_SEPARATOR;
  1398. }
  1399. $pluginPaths[] = Plugin::templatePath($plugin);
  1400. }
  1401. if (!empty($this->theme)) {
  1402. $themePaths[] = Plugin::templatePath(Inflector::camelize($this->theme));
  1403. if ($plugin) {
  1404. for ($i = 0, $count = count($themePaths); $i < $count; $i++) {
  1405. array_unshift(
  1406. $themePaths,
  1407. $themePaths[$i]
  1408. . static::PLUGIN_TEMPLATE_FOLDER
  1409. . DIRECTORY_SEPARATOR
  1410. . $plugin
  1411. . DIRECTORY_SEPARATOR
  1412. );
  1413. }
  1414. }
  1415. }
  1416. $paths = array_merge(
  1417. $themePaths,
  1418. $pluginPaths,
  1419. $templatePaths,
  1420. App::core('templates')
  1421. );
  1422. if ($plugin !== null) {
  1423. return $this->_pathsForPlugin[$plugin] = $paths;
  1424. }
  1425. return $this->_paths = $paths;
  1426. }
  1427. /**
  1428. * Generate the cache configuration options for an element.
  1429. *
  1430. * @param string $name Element name
  1431. * @param array $data Data
  1432. * @param array<string, mixed> $options Element options
  1433. * @return array Element Cache configuration.
  1434. * @psalm-return array{key:string, config:string}
  1435. */
  1436. protected function _elementCache(string $name, array $data, array $options): array
  1437. {
  1438. if (isset($options['cache']['key'], $options['cache']['config'])) {
  1439. /** @psalm-var array{key:string, config:string}*/
  1440. $cache = $options['cache'];
  1441. $cache['key'] = 'element_' . $cache['key'];
  1442. return $cache;
  1443. }
  1444. [$plugin, $name] = $this->pluginSplit($name);
  1445. $pluginKey = null;
  1446. if ($plugin) {
  1447. $pluginKey = str_replace('/', '_', Inflector::underscore($plugin));
  1448. }
  1449. $elementKey = str_replace(['\\', '/'], '_', $name);
  1450. $cache = $options['cache'];
  1451. unset($options['cache']);
  1452. $keys = array_merge(
  1453. [$pluginKey, $elementKey],
  1454. array_keys($options),
  1455. array_keys($data)

Large files files are truncated, but you can click here to view the full file