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