PageRenderTime 48ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/Cake/View/View.php

https://bitbucket.org/udeshika/fake_twitter
PHP | 762 lines | 357 code | 72 blank | 333 comment | 74 complexity | b5c21bcabe4bc9432d39945520a922ec MD5 | raw file
  1. <?php
  2. /**
  3. * Methods for displaying presentation data in the view.
  4. *
  5. * PHP 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice.
  12. *
  13. * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package Cake.View
  16. * @since CakePHP(tm) v 0.10.0.1076
  17. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  18. */
  19. App::uses('HelperCollection', 'View');
  20. App::uses('AppHelper', 'View/Helper');
  21. App::uses('Router', 'Routing');
  22. /**
  23. * View, the V in the MVC triad. View interacts with Helpers and view variables passed
  24. * in from the controller to render the results of the controller action. Often this is HTML,
  25. * but can also take the form of JSON, XML, PDF's or streaming files.
  26. *
  27. * CakePHP uses a two-step-view pattern. This means that the view content is rendered first,
  28. * and then inserted into the selected layout. A special `$content_for_layout` variable is available
  29. * in the layout, and it contains the rendered view. This also means you can pass data from the view to the
  30. * layout using `$this->set()`
  31. *
  32. * @package Cake.View
  33. * @property CacheHelper $Cache
  34. * @property FormHelper $Form
  35. * @property HtmlHelper $Html
  36. * @property JsHelper $Js
  37. * @property NumberHelper $Number
  38. * @property PaginatorHelper $Paginator
  39. * @property RssHelper $Rss
  40. * @property SessionHelper $Session
  41. * @property TextHelper $Text
  42. * @property TimeHelper $Time
  43. */
  44. class View extends Object {
  45. /**
  46. * Helpers collection
  47. *
  48. * @var HelperCollection
  49. */
  50. public $Helpers;
  51. /**
  52. * Name of the plugin.
  53. *
  54. * @link http://manual.cakephp.org/chapter/plugins
  55. * @var string
  56. */
  57. public $plugin = null;
  58. /**
  59. * Name of the controller.
  60. *
  61. * @var string Name of controller
  62. */
  63. public $name = null;
  64. /**
  65. * Current passed params
  66. *
  67. * @var mixed
  68. */
  69. public $passedArgs = array();
  70. /**
  71. * An array of names of built-in helpers to include.
  72. *
  73. * @var mixed A single name as a string or a list of names as an array.
  74. */
  75. public $helpers = array('Html');
  76. /**
  77. * Path to View.
  78. *
  79. * @var string Path to View
  80. */
  81. public $viewPath = null;
  82. /**
  83. * Variables for the view
  84. *
  85. * @var array
  86. */
  87. public $viewVars = array();
  88. /**
  89. * Name of view to use with this View.
  90. *
  91. * @var string
  92. */
  93. public $view = null;
  94. /**
  95. * Name of layout to use with this View.
  96. *
  97. * @var string
  98. */
  99. public $layout = 'default';
  100. /**
  101. * Path to Layout.
  102. *
  103. * @var string Path to Layout
  104. */
  105. public $layoutPath = null;
  106. /**
  107. * Turns on or off Cake's conventional mode of applying layout files. On by default.
  108. * Setting to off means that layouts will not be automatically applied to rendered views.
  109. *
  110. * @var boolean
  111. */
  112. public $autoLayout = true;
  113. /**
  114. * File extension. Defaults to Cake's template ".ctp".
  115. *
  116. * @var string
  117. */
  118. public $ext = '.ctp';
  119. /**
  120. * Sub-directory for this view file. This is often used for extension based routing.
  121. * Eg. With an `xml` extension, $subDir would be `xml/`
  122. *
  123. * @var string
  124. */
  125. public $subDir = null;
  126. /**
  127. * Theme name. If you are using themes, you should remember to use ThemeView as well.
  128. *
  129. * @var string
  130. */
  131. public $theme = null;
  132. /**
  133. * Used to define methods a controller that will be cached.
  134. *
  135. * @see Controller::$cacheAction
  136. * @var mixed
  137. */
  138. public $cacheAction = false;
  139. /**
  140. * Holds current errors for the model validation.
  141. *
  142. * @var array
  143. */
  144. public $validationErrors = array();
  145. /**
  146. * True when the view has been rendered.
  147. *
  148. * @var boolean
  149. */
  150. public $hasRendered = false;
  151. /**
  152. * List of generated DOM UUIDs.
  153. *
  154. * @var array
  155. */
  156. public $uuids = array();
  157. /**
  158. * Holds View output.
  159. *
  160. * @var string
  161. */
  162. public $output = false;
  163. /**
  164. * An instance of a CakeRequest object that contains information about the current request.
  165. * This object contains all the information about a request and several methods for reading
  166. * additional information about the request.
  167. *
  168. * @var CakeRequest
  169. */
  170. public $request;
  171. /**
  172. * The Cache configuration View will use to store cached elements. Changing this will change
  173. * the default configuration elements are stored under. You can also choose a cache config
  174. * per element.
  175. *
  176. * @var string
  177. * @see View::element()
  178. */
  179. public $elementCache = 'default';
  180. /**
  181. * List of variables to collect from the associated controller.
  182. *
  183. * @var array
  184. */
  185. protected $_passedVars = array(
  186. 'viewVars', 'autoLayout', 'ext', 'helpers', 'view', 'layout', 'name',
  187. 'layoutPath', 'viewPath', 'request', 'plugin', 'passedArgs', 'cacheAction'
  188. );
  189. /**
  190. * Scripts (and/or other <head /> tags) for the layout.
  191. *
  192. * @var array
  193. */
  194. protected $_scripts = array();
  195. /**
  196. * Holds an array of paths.
  197. *
  198. * @var array
  199. */
  200. protected $_paths = array();
  201. /**
  202. * Indicate that helpers have been loaded.
  203. *
  204. * @var boolean
  205. */
  206. protected $_helpersLoaded = false;
  207. /**
  208. * Constructor
  209. *
  210. * @param Controller $controller A controller object to pull View::_passedVars from.
  211. */
  212. public function __construct($controller) {
  213. if (is_object($controller)) {
  214. $count = count($this->_passedVars);
  215. for ($j = 0; $j < $count; $j++) {
  216. $var = $this->_passedVars[$j];
  217. $this->{$var} = $controller->{$var};
  218. }
  219. }
  220. $this->Helpers = new HelperCollection($this);
  221. parent::__construct();
  222. }
  223. /**
  224. * Renders a piece of PHP with provided parameters and returns HTML, XML, or any other string.
  225. *
  226. * This realizes the concept of Elements, (or "partial layouts") and the $params array is used to send
  227. * data to be used in the element. Elements can be cached improving performance by using the `cache` option.
  228. *
  229. * @param string $name Name of template file in the/app/View/Elements/ folder
  230. * @param array $data Array of data to be made available to the rendered view (i.e. the Element)
  231. * @param array $options Array of options. Possible keys are:
  232. * - `cache` - Can either be `true`, to enable caching using the config in View::$elementCache. Or an array
  233. * If an array, the following keys can be used:
  234. * - `config` - Used to store the cached element in a custom cache configuration.
  235. * - `key` - Used to define the key used in the Cache::write(). It will be prefixed with `element_`
  236. * - `plugin` - Load an element from a specific plugin.
  237. * - `callbacks` - Set to true to fire beforeRender and afterRender helper callbacks for this element.
  238. * Defaults to false.
  239. * @return string Rendered Element
  240. */
  241. public function element($name, $data = array(), $options = array()) {
  242. $file = $plugin = $key = null;
  243. $callbacks = false;
  244. if (isset($options['plugin'])) {
  245. $plugin = Inflector::camelize($options['plugin']);
  246. }
  247. if (isset($this->plugin) && !$plugin) {
  248. $plugin = $this->plugin;
  249. }
  250. if (isset($options['callbacks'])) {
  251. $callbacks = $options['callbacks'];
  252. }
  253. if (isset($options['cache'])) {
  254. $underscored = null;
  255. if ($plugin) {
  256. $underscored = Inflector::underscore($plugin);
  257. }
  258. $keys = array_merge(array($underscored, $name), array_keys($options), array_keys($data));
  259. $caching = array(
  260. 'config' => $this->elementCache,
  261. 'key' => implode('_', $keys)
  262. );
  263. if (is_array($options['cache'])) {
  264. $defaults = array(
  265. 'config' => $this->elementCache,
  266. 'key' => $caching['key']
  267. );
  268. $caching = array_merge($defaults, $options['cache']);
  269. }
  270. $key = 'element_' . $caching['key'];
  271. $contents = Cache::read($key, $caching['config']);
  272. if ($contents !== false) {
  273. return $contents;
  274. }
  275. }
  276. $file = $this->_getElementFilename($name, $plugin);
  277. if ($file) {
  278. if (!$this->_helpersLoaded) {
  279. $this->loadHelpers();
  280. }
  281. if ($callbacks) {
  282. $this->Helpers->trigger('beforeRender', array($file));
  283. }
  284. $element = $this->_render($file, array_merge($this->viewVars, $data));
  285. if ($callbacks) {
  286. $this->Helpers->trigger('afterRender', array($file, $element));
  287. }
  288. if (isset($options['cache'])) {
  289. Cache::write($key, $element, $caching['config']);
  290. }
  291. return $element;
  292. }
  293. $file = 'Elements' . DS . $name . $this->ext;
  294. if (Configure::read('debug') > 0) {
  295. return "Element Not Found: " . $file;
  296. }
  297. }
  298. /**
  299. * Renders view for given view file and layout.
  300. *
  301. * Render triggers helper callbacks, which are fired before and after the view are rendered,
  302. * as well as before and after the layout. The helper callbacks are called:
  303. *
  304. * - `beforeRender`
  305. * - `afterRender`
  306. * - `beforeLayout`
  307. * - `afterLayout`
  308. *
  309. * If View::$autoRender is false and no `$layout` is provided, the view will be returned bare.
  310. *
  311. * @param string $view Name of view file to use
  312. * @param string $layout Layout to use.
  313. * @return string Rendered Element
  314. * @throws CakeException if there is an error in the view.
  315. */
  316. public function render($view = null, $layout = null) {
  317. if ($this->hasRendered) {
  318. return true;
  319. }
  320. if (!$this->_helpersLoaded) {
  321. $this->loadHelpers();
  322. }
  323. $this->output = null;
  324. if ($view !== false && $viewFileName = $this->_getViewFileName($view)) {
  325. $this->Helpers->trigger('beforeRender', array($viewFileName));
  326. $this->output = $this->_render($viewFileName);
  327. $this->Helpers->trigger('afterRender', array($viewFileName));
  328. }
  329. if ($layout === null) {
  330. $layout = $this->layout;
  331. }
  332. if ($this->output === false) {
  333. throw new CakeException(__d('cake_dev', "Error in view %s, got no content.", $viewFileName));
  334. }
  335. if ($layout && $this->autoLayout) {
  336. $this->output = $this->renderLayout($this->output, $layout);
  337. }
  338. $this->hasRendered = true;
  339. return $this->output;
  340. }
  341. /**
  342. * Renders a layout. Returns output from _render(). Returns false on error.
  343. * Several variables are created for use in layout.
  344. *
  345. * - `title_for_layout` - A backwards compatible place holder, you should set this value if you want more control.
  346. * - `content_for_layout` - contains rendered view file
  347. * - `scripts_for_layout` - contains scripts added to header
  348. *
  349. * @param string $content_for_layout Content to render in a view, wrapped by the surrounding layout.
  350. * @param string $layout Layout name
  351. * @return mixed Rendered output, or false on error
  352. * @throws CakeException if there is an error in the view.
  353. */
  354. public function renderLayout($content_for_layout, $layout = null) {
  355. $layoutFileName = $this->_getLayoutFileName($layout);
  356. if (empty($layoutFileName)) {
  357. return $this->output;
  358. }
  359. if (!$this->_helpersLoaded) {
  360. $this->loadHelpers();
  361. }
  362. $this->Helpers->trigger('beforeLayout', array($layoutFileName));
  363. $this->viewVars = array_merge($this->viewVars, array(
  364. 'content_for_layout' => $content_for_layout,
  365. 'scripts_for_layout' => implode("\n\t", $this->_scripts),
  366. ));
  367. if (!isset($this->viewVars['title_for_layout'])) {
  368. $this->viewVars['title_for_layout'] = Inflector::humanize($this->viewPath);
  369. }
  370. $this->output = $this->_render($layoutFileName);
  371. if ($this->output === false) {
  372. throw new CakeException(__d('cake_dev', "Error in layout %s, got no content.", $layoutFileName));
  373. }
  374. $this->Helpers->trigger('afterLayout', array($layoutFileName));
  375. return $this->output;
  376. }
  377. /**
  378. * Render cached view. Works in concert with CacheHelper and Dispatcher to
  379. * render cached view files.
  380. *
  381. * @param string $filename the cache file to include
  382. * @param string $timeStart the page render start time
  383. * @return boolean Success of rendering the cached file.
  384. */
  385. public function renderCache($filename, $timeStart) {
  386. ob_start();
  387. include ($filename);
  388. if (Configure::read('debug') > 0 && $this->layout != 'xml') {
  389. echo "<!-- Cached Render Time: " . round(microtime(true) - $timeStart, 4) . "s -->";
  390. }
  391. $out = ob_get_clean();
  392. if (preg_match('/^<!--cachetime:(\\d+)-->/', $out, $match)) {
  393. if (time() >= $match['1']) {
  394. @unlink($filename);
  395. unset ($out);
  396. return false;
  397. } else {
  398. if ($this->layout === 'xml') {
  399. header('Content-type: text/xml');
  400. }
  401. $commentLength = strlen('<!--cachetime:' . $match['1'] . '-->');
  402. echo substr($out, $commentLength);
  403. return true;
  404. }
  405. }
  406. }
  407. /**
  408. * Returns a list of variables available in the current View context
  409. *
  410. * @return array Array of the set view variable names.
  411. */
  412. public function getVars() {
  413. return array_keys($this->viewVars);
  414. }
  415. /**
  416. * Returns the contents of the given View variable(s)
  417. *
  418. * @param string $var The view var you want the contents of.
  419. * @return mixed The content of the named var if its set, otherwise null.
  420. */
  421. public function getVar($var) {
  422. if (!isset($this->viewVars[$var])) {
  423. return null;
  424. } else {
  425. return $this->viewVars[$var];
  426. }
  427. }
  428. /**
  429. * Adds a script block or other element to be inserted in $scripts_for_layout in
  430. * the `<head />` of a document layout
  431. *
  432. * @param string $name Either the key name for the script, or the script content. Name can be used to
  433. * update/replace a script element.
  434. * @param string $content The content of the script being added, optional.
  435. * @return void
  436. */
  437. public function addScript($name, $content = null) {
  438. if (empty($content)) {
  439. if (!in_array($name, array_values($this->_scripts))) {
  440. $this->_scripts[] = $name;
  441. }
  442. } else {
  443. $this->_scripts[$name] = $content;
  444. }
  445. }
  446. /**
  447. * Generates a unique, non-random DOM ID for an object, based on the object type and the target URL.
  448. *
  449. * @param string $object Type of object, i.e. 'form' or 'link'
  450. * @param string $url The object's target URL
  451. * @return string
  452. */
  453. public function uuid($object, $url) {
  454. $c = 1;
  455. $url = Router::url($url);
  456. $hash = $object . substr(md5($object . $url), 0, 10);
  457. while (in_array($hash, $this->uuids)) {
  458. $hash = $object . substr(md5($object . $url . $c), 0, 10);
  459. $c++;
  460. }
  461. $this->uuids[] = $hash;
  462. return $hash;
  463. }
  464. /**
  465. * Allows a template or element to set a variable that will be available in
  466. * a layout or other element. Analogous to Controller::set().
  467. *
  468. * @param mixed $one A string or an array of data.
  469. * @param mixed $two Value in case $one is a string (which then works as the key).
  470. * Unused if $one is an associative array, otherwise serves as the values to $one's keys.
  471. * @return void
  472. */
  473. public function set($one, $two = null) {
  474. $data = null;
  475. if (is_array($one)) {
  476. if (is_array($two)) {
  477. $data = array_combine($one, $two);
  478. } else {
  479. $data = $one;
  480. }
  481. } else {
  482. $data = array($one => $two);
  483. }
  484. if ($data == null) {
  485. return false;
  486. }
  487. $this->viewVars = $data + $this->viewVars;
  488. }
  489. /**
  490. * Magic accessor for helpers. Provides access to attributes that were deprecated.
  491. *
  492. * @param string $name Name of the attribute to get.
  493. * @return mixed
  494. */
  495. public function __get($name) {
  496. if (isset($this->Helpers->{$name})) {
  497. return $this->Helpers->{$name};
  498. }
  499. switch ($name) {
  500. case 'base':
  501. case 'here':
  502. case 'webroot':
  503. case 'data':
  504. return $this->request->{$name};
  505. case 'action':
  506. return isset($this->request->params['action']) ? $this->request->params['action'] : '';
  507. case 'params':
  508. return $this->request;
  509. }
  510. return null;
  511. }
  512. /**
  513. * Interact with the HelperCollection to load all the helpers.
  514. *
  515. * @return void
  516. */
  517. public function loadHelpers() {
  518. $helpers = HelperCollection::normalizeObjectArray($this->helpers);
  519. foreach ($helpers as $name => $properties) {
  520. list($plugin, $class) = pluginSplit($properties['class']);
  521. $this->{$class} = $this->Helpers->load($properties['class'], $properties['settings']);
  522. }
  523. $this->_helpersLoaded = true;
  524. }
  525. /**
  526. * Renders and returns output for given view filename with its
  527. * array of data.
  528. *
  529. * @param string $___viewFn Filename of the view
  530. * @param array $___dataForView Data to include in rendered view. If empty the current View::$viewVars will be used.
  531. * @return string Rendered output
  532. */
  533. protected function _render($___viewFn, $___dataForView = array()) {
  534. if (empty($___dataForView)) {
  535. $___dataForView = $this->viewVars;
  536. }
  537. extract($___dataForView, EXTR_SKIP);
  538. ob_start();
  539. include $___viewFn;
  540. return ob_get_clean();
  541. }
  542. /**
  543. * Loads a helper. Delegates to the `HelperCollection::load()` to load the helper
  544. *
  545. * @param string $helperName Name of the helper to load.
  546. * @param array $settings Settings for the helper
  547. * @return Helper a constructed helper object.
  548. * @see HelperCollection::load()
  549. */
  550. public function loadHelper($helperName, $settings = array()) {
  551. return $this->Helpers->load($helperName, $settings);
  552. }
  553. /**
  554. * Returns filename of given action's template file (.ctp) as a string.
  555. * CamelCased action names will be under_scored! This means that you can have
  556. * LongActionNames that refer to long_action_names.ctp views.
  557. *
  558. * @param string $name Controller action to find template filename for
  559. * @return string Template filename
  560. * @throws MissingViewException when a view file could not be found.
  561. */
  562. protected function _getViewFileName($name = null) {
  563. $subDir = null;
  564. if (!is_null($this->subDir)) {
  565. $subDir = $this->subDir . DS;
  566. }
  567. if ($name === null) {
  568. $name = $this->view;
  569. }
  570. $name = str_replace('/', DS, $name);
  571. if (strpos($name, DS) === false && $name[0] !== '.') {
  572. $name = $this->viewPath . DS . $subDir . Inflector::underscore($name);
  573. } elseif (strpos($name, DS) !== false) {
  574. if ($name[0] === DS || $name[1] === ':') {
  575. if (is_file($name)) {
  576. return $name;
  577. }
  578. $name = trim($name, DS);
  579. } else if ($name[0] === '.') {
  580. $name = substr($name, 3);
  581. } else {
  582. $name = $this->viewPath . DS . $subDir . $name;
  583. }
  584. }
  585. $paths = $this->_paths($this->plugin);
  586. $exts = $this->_getExtensions();
  587. foreach ($exts as $ext) {
  588. foreach ($paths as $path) {
  589. if (file_exists($path . $name . $ext)) {
  590. return $path . $name . $ext;
  591. }
  592. }
  593. }
  594. $defaultPath = $paths[0];
  595. if ($this->plugin) {
  596. $pluginPaths = App::path('plugins');
  597. foreach ($paths as $path) {
  598. if (strpos($path, $pluginPaths[0]) === 0) {
  599. $defaultPath = $path;
  600. break;
  601. }
  602. }
  603. }
  604. throw new MissingViewException(array('file' => $defaultPath . $name . $this->ext));
  605. }
  606. /**
  607. * Returns layout filename for this template as a string.
  608. *
  609. * @param string $name The name of the layout to find.
  610. * @return string Filename for layout file (.ctp).
  611. * @throws MissingLayoutException when a layout cannot be located
  612. */
  613. protected function _getLayoutFileName($name = null) {
  614. if ($name === null) {
  615. $name = $this->layout;
  616. }
  617. $subDir = null;
  618. if (!is_null($this->layoutPath)) {
  619. $subDir = $this->layoutPath . DS;
  620. }
  621. $paths = $this->_paths($this->plugin);
  622. $file = 'Layouts' . DS . $subDir . $name;
  623. $exts = $this->_getExtensions();
  624. foreach ($exts as $ext) {
  625. foreach ($paths as $path) {
  626. if (file_exists($path . $file . $ext)) {
  627. return $path . $file . $ext;
  628. }
  629. }
  630. }
  631. throw new MissingLayoutException(array('file' => $paths[0] . $file . $this->ext));
  632. }
  633. /**
  634. * Get the extensions that view files can use.
  635. *
  636. * @return array Array of extensions view files use.
  637. */
  638. protected function _getExtensions() {
  639. $exts = array($this->ext);
  640. if ($this->ext !== '.ctp') {
  641. array_push($exts, '.ctp');
  642. }
  643. return $exts;
  644. }
  645. /**
  646. * Finds an element filename, returns false on failure.
  647. *
  648. * @param string $name The name of the element to find.
  649. * @param string $plugin The plugin name the element is in.
  650. * @return mixed Either a string to the element filename or false when one can't be found.
  651. */
  652. protected function _getElementFileName($name, $plugin = null) {
  653. $paths = $this->_paths($plugin);
  654. $exts = $this->_getExtensions();
  655. foreach ($exts as $ext) {
  656. foreach ($paths as $path) {
  657. if (file_exists($path . 'Elements' . DS . $name . $ext)) {
  658. return $path . 'Elements' . DS . $name . $ext;
  659. }
  660. }
  661. }
  662. return false;
  663. }
  664. /**
  665. * Return all possible paths to find view files in order
  666. *
  667. * @param string $plugin Optional plugin name to scan for view files.
  668. * @param boolean $cached Set to true to force a refresh of view paths.
  669. * @return array paths
  670. */
  671. protected function _paths($plugin = null, $cached = true) {
  672. if ($plugin === null && $cached === true && !empty($this->_paths)) {
  673. return $this->_paths;
  674. }
  675. $paths = array();
  676. $viewPaths = App::path('View');
  677. $corePaths = array_flip(App::core('View'));
  678. if (!empty($plugin)) {
  679. $count = count($viewPaths);
  680. for ($i = 0; $i < $count; $i++) {
  681. if (!isset($corePaths[$viewPaths[$i]])) {
  682. $paths[] = $viewPaths[$i] . 'Plugin' . DS . $plugin . DS;
  683. }
  684. }
  685. $paths = array_merge($paths, App::path('View', $plugin));
  686. }
  687. $this->_paths = array_unique(array_merge($paths, $viewPaths, array_keys($corePaths)));
  688. return $this->_paths;
  689. }
  690. }