View.php 53 KB

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