PageRenderTime 66ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/src/View/BakeView.php

https://gitlab.com/BitCoding/bake
PHP | 273 lines | 134 code | 29 blank | 110 comment | 10 complexity | 028d08dd809344488c26ca653134d909 MD5 | raw file
  1. <?php
  2. namespace Bake\View;
  3. use Bit\Core\Configure;
  4. use Bit\Core\Traits\Conventions;
  5. use Bit\Core\Traits\InstanceConfig;
  6. use Bit\Event\EventManager;
  7. use Bit\Network\Request;
  8. use Bit\Network\Response;
  9. use Bit\Utility\Inflector;
  10. use Bit\View\View;
  11. class BakeView extends View
  12. {
  13. use Conventions;
  14. use InstanceConfig;
  15. protected $_viewExt = '.ptp';
  16. /**
  17. * Default class config
  18. *
  19. * This config is read when evaluating a template file.
  20. *
  21. * phpTagReplacements are applied to the contents of a bake template, to allow php tags
  22. * to be treated as plain text
  23. *
  24. * replacements are applied in order on the template contents before the template is evaluated
  25. * In order these:
  26. * swallow leading whitespace for <%- tags
  27. * swallow trailing whitespace for -%> tags
  28. * Add an extra newline to <%=, to counteract php automatically removing a newline
  29. * Replace remaining <=% with php short echo tags
  30. * Replace <% with php open tags
  31. * Replace %> with php close tags
  32. *
  33. * @var array
  34. */
  35. protected $_defaultConfig = [
  36. 'phpTagReplacements' => [
  37. '<?' => "<BitPHPBakeOpenTag",
  38. '?>' => "BitPHPBakeCloseTag>"
  39. ],
  40. 'replacements' => [
  41. '/\n[ \t]+<%-( |$)/' => "\n<% ",
  42. '/-%>/' => "?>",
  43. '/<%=(.*)\%>\n(.)/' => "<%=$1%>\n\n$2",
  44. '<%=' => '<?=',
  45. '<%' => '<?php',
  46. '%>' => '?>'
  47. ]
  48. ];
  49. /**
  50. * Path where bake's intermediary files are written.
  51. * Defaults to `TMP . 'bake' . DS`.
  52. *
  53. * @var string
  54. */
  55. protected $_tmpLocation;
  56. /**
  57. * Upon construction, append the plugin's template paths to the paths to check
  58. *
  59. * @param \Bit\Network\Request|null $request Request instance.
  60. * @param \Bit\Network\Response|null $response Response instance.
  61. * @param \Bit\Event\EventManager|null $eventManager Event manager instance.
  62. * @param array $viewOptions View options. See View::$_passedVars for list of
  63. * options which get set as class properties.
  64. */
  65. public function __construct(
  66. Request $request = null,
  67. Response $response = null,
  68. EventManager $eventManager = null,
  69. array $viewOptions = []
  70. ) {
  71. parent::__construct($request, $response, $eventManager, $viewOptions);
  72. $bakeTemplates = dirname(dirname(__FILE__)) . DS . 'Template' . DS;
  73. $paths = (array)Configure::read('App.paths.templates');
  74. if (!in_array($bakeTemplates, $paths)) {
  75. $paths[] = $bakeTemplates;
  76. Configure::write('App.paths.templates', $paths);
  77. }
  78. $this->_tmpLocation = TMP . 'bake' . DS;
  79. if (!file_exists($this->_tmpLocation)) {
  80. mkdir($this->_tmpLocation);
  81. }
  82. }
  83. /**
  84. * Renders view for given view file and layout.
  85. *
  86. * Render triggers helper callbacks, which are fired before and after the view are rendered,
  87. * as well as before and after the layout. The helper callbacks are called:
  88. *
  89. * - `beforeRender`
  90. * - `afterRender`
  91. *
  92. * View names can point to plugin views/layouts. Using the `Plugin.view` syntax
  93. * a plugin view/layout can be used instead of the app ones. If the chosen plugin is not found
  94. * the view will be located along the regular view path cascade.
  95. *
  96. * View can also be a template string, rather than the name of a view file
  97. *
  98. * @param string|null $view Name of view file to use, or a template string to render
  99. * @param string|null $layout Layout to use. Not used, for consistency with other views only
  100. * @return string|null Rendered content.
  101. * @throws \Bit\Core\Exception\Exception If there is an error in the view.
  102. */
  103. public function render($view = null, $layout = null)
  104. {
  105. $viewFileName = $this->_getViewFileName($view);
  106. $templateEventName = str_replace(
  107. ['.ctp', DS],
  108. ['', '.'],
  109. explode('Template' . DS . 'Bake' . DS, $viewFileName)[1]
  110. );
  111. $this->_currentType = static::TYPE_VIEW;
  112. $this->dispatchEvent('View.beforeRender', [$viewFileName]);
  113. $this->dispatchEvent('View.beforeRender.' . $templateEventName, [$viewFileName]);
  114. $this->Blocks->set('content', $this->_render($viewFileName));
  115. $this->dispatchEvent('View.afterRender', [$viewFileName]);
  116. $this->dispatchEvent('View.afterRender.' . $templateEventName, [$viewFileName]);
  117. if ($layout === null) {
  118. $layout = $this->layout;
  119. }
  120. if ($layout && $this->autoLayout) {
  121. $this->Blocks->set('content', $this->renderLayout('', $layout));
  122. }
  123. return $this->Blocks->get('content');
  124. }
  125. /**
  126. * Wrapper for creating and dispatching events.
  127. *
  128. * Use the Bake prefix for bake related view events
  129. *
  130. * @param string $name Name of the event.
  131. * @param array|null $data Any value you wish to be transported with this event to
  132. * it can be read by listeners.
  133. *
  134. * @param object|null $subject The object that this event applies to
  135. * ($this by default).
  136. *
  137. * @return \Bit\Event\Event
  138. */
  139. public function dispatchEvent($name, $data = null, $subject = null)
  140. {
  141. $name = preg_replace('/^View\./', 'Bake.', $name);
  142. return parent::dispatchEvent($name, $data, $subject);
  143. }
  144. /**
  145. * Sandbox method to evaluate a template / view script in.
  146. *
  147. * @param string $viewFile Filename of the view
  148. * @param array $dataForView Data to include in rendered view.
  149. * If empty the current View::$viewVars will be used.
  150. * @return string Rendered output
  151. */
  152. protected function _evaluate($viewFile, $dataForView)
  153. {
  154. $viewString = $this->_getViewFileContents($viewFile);
  155. $replacements = array_merge($this->config('phpTagReplacements') + $this->config('replacements'));
  156. foreach ($replacements as $find => $replace) {
  157. if ($this->_isRegex($find)) {
  158. $viewString = preg_replace($find, $replace, $viewString);
  159. } else {
  160. $viewString = str_replace($find, $replace, $viewString);
  161. }
  162. }
  163. $this->__viewFile = $this->_tmpLocation . Inflector::slug(preg_replace('@.*Template[/\\\\]@', '', $viewFile)) . '.php';
  164. file_put_contents($this->__viewFile, $viewString);
  165. unset($viewFile, $viewString, $replacements, $find, $replace);
  166. extract($dataForView);
  167. ob_start();
  168. include $this->__viewFile;
  169. $content = ob_get_clean();
  170. $unPhp = $this->config('phpTagReplacements');
  171. return str_replace(array_values($unPhp), array_keys($unPhp), $content);
  172. }
  173. /**
  174. * Get the contents of the template file
  175. *
  176. * @param string $filename A template filename
  177. * @return string Bake template to evaluate
  178. */
  179. protected function _getViewFileContents($filename)
  180. {
  181. return file_get_contents($filename);
  182. }
  183. /**
  184. * Return all possible paths to find view files in order
  185. *
  186. * @param string $plugin Optional plugin name to scan for view files.
  187. * @param bool $cached Set to false to force a refresh of view paths. Default true.
  188. * @return array paths
  189. */
  190. protected function _paths($plugin = null, $cached = true)
  191. {
  192. $paths = parent::_paths($plugin, false);
  193. foreach ($paths as &$path) {
  194. $path .= 'Bake' . DS;
  195. }
  196. return $paths;
  197. }
  198. /**
  199. * Check if a replacement pattern is a regex
  200. *
  201. * Use preg_match to detect invalid regexes
  202. *
  203. * @param string $maybeRegex a fixed string or a regex
  204. * @return bool
  205. */
  206. protected function _isRegex($maybeRegex)
  207. {
  208. // @codingStandardsIgnoreStart
  209. $isRegex = @preg_match($maybeRegex, '');
  210. // @codingStandardsIgnoreEnd
  211. return $isRegex !== false;
  212. }
  213. /**
  214. * Renders a layout. Returns output from _render(). Returns false on error.
  215. * Several variables are created for use in layout.
  216. *
  217. * @param string $content Content to render in a template, wrapped by the surrounding layout.
  218. * @param string|null $layout Layout name
  219. * @return mixed Rendered output, or false on error
  220. * @throws \Bit\Core\Exception\Exception if there is an error in the view.
  221. * @triggers View.beforeLayout $this, [$layoutFileName]
  222. * @triggers View.afterLayout $this, [$layoutFileName]
  223. */
  224. public function renderLayout($content, $layout = null)
  225. {
  226. $layoutFileName = $this->_getLayoutFileName($layout);
  227. if (empty($layoutFileName)) {
  228. return $this->Blocks->get('content');
  229. }
  230. if (!empty($content)) {
  231. $this->Blocks->set('content', $content);
  232. }
  233. $this->dispatchEvent('View.beforeLayout', [$layoutFileName]);
  234. $title = $this->Blocks->get('title');
  235. if ($title === '') {
  236. $title = Inflector::humanize($this->templatePath);
  237. $this->Blocks->set('title', $title);
  238. }
  239. $this->_currentType = static::TYPE_LAYOUT;
  240. $this->Blocks->set('content', $this->_render($layoutFileName));
  241. $this->dispatchEvent('View.afterLayout', [$layoutFileName]);
  242. return $this->Blocks->get('content');
  243. }
  244. }