PageRenderTime 94ms CodeModel.GetById 35ms RepoModel.GetById 2ms app.codeStats 0ms

/Phrozn/Site/View/Base.php

https://github.com/billortell/phrozn
PHP | 532 lines | 237 code | 56 blank | 239 comment | 23 complexity | 3c5b4c0a3f73a5cfe292e7b2a08f1c62 MD5 | raw file
  1. <?php
  2. /**
  3. * Copyright 2011 Victor Farazdagi
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. * @category Phrozn
  18. * @package Phrozn\Site\View
  19. * @author Victor Farazdagi
  20. * @copyright 2011 Victor Farazdagi
  21. * @license http://www.apache.org/licenses/LICENSE-2.0
  22. */
  23. namespace Phrozn\Site\View;
  24. use Symfony\Component\Yaml\Yaml,
  25. Phrozn\Site\View\Factory,
  26. Phrozn\Site\Layout\DefaultLayout as Layout,
  27. Phrozn\Site\View\OutputPath\Entry as OutputFile,
  28. Phrozn\Autoloader as Loader;
  29. /**
  30. * Base implementation of Phrozn view
  31. *
  32. * @category Phrozn
  33. * @package Phrozn\Site\View
  34. * @author Victor Farazdagi
  35. */
  36. abstract class Base
  37. implements \Phrozn\Site\View
  38. {
  39. /**
  40. * Input file path
  41. * @var string
  42. */
  43. private $inputFile;
  44. /**
  45. * Output directory path
  46. * @var string
  47. */
  48. private $outputDir;
  49. /**
  50. * Output file path
  51. * @var string
  52. */
  53. private $outputFile;
  54. /**
  55. * Registered text processors
  56. * @var array of \Phrozn\Processor
  57. */
  58. private $processors;
  59. /**
  60. * Template source text
  61. * @var string
  62. */
  63. private $source;
  64. /**
  65. * Whether given concrete view must be wrapped into layout or not
  66. */
  67. private $hasLayout = true;
  68. /**
  69. * Cache object for extracted template
  70. * @var string
  71. */
  72. private $template;
  73. /**
  74. * Cache object for extracted FM
  75. * @var array
  76. */
  77. private $frontMatter;
  78. /**
  79. * Loaded content of site/config.yml
  80. * @var array
  81. */
  82. private $siteConfig;
  83. /**
  84. * Loaded content of configs/phrozn.yml
  85. * @var array
  86. */
  87. private $appConfig;
  88. /**
  89. * Initialize page
  90. *
  91. * @param string $inputFile Path to page source file
  92. * @param string $outputDir File destination path
  93. *
  94. * @return \Phrozn\Site\View
  95. */
  96. public function __construct($inputFile = null, $outputDir = null)
  97. {
  98. $this
  99. ->setInputFile($inputFile)
  100. ->setOutputDir($outputDir);
  101. }
  102. /**
  103. * Create static version of a concrete page
  104. *
  105. * @param array $vars List of variables passed to template engine
  106. *
  107. * @return \Phrozn\Site\View
  108. */
  109. public function compile($vars = array())
  110. {
  111. $out = $this->render($vars);
  112. $destinationDir = dirname($this->getOutputFile());
  113. if (!is_dir($destinationDir)) {
  114. mkdir($destinationDir, 0777, true);
  115. }
  116. file_put_contents($this->getOutputFile(), $out);
  117. return $out;
  118. }
  119. /**
  120. * Render view
  121. *
  122. * @param array $vars List of variables passed to text processors
  123. *
  124. * @return string
  125. */
  126. public function render($vars = array())
  127. {
  128. // inject front matter options into template
  129. $vars = array_merge($vars, $this->getParams());
  130. // convert view into static representation
  131. $view = $this->getTemplate();
  132. foreach ($this->getProcessors() as $processor) {
  133. $view = $processor->render($view, $vars);
  134. }
  135. return $view;
  136. }
  137. /**
  138. * Set input file path
  139. *
  140. * @param string $file Path to file
  141. *
  142. * @return \Phrozn\Site\View
  143. */
  144. public function setInputFile($path)
  145. {
  146. $this->inputFile = $path;
  147. return $this;
  148. }
  149. /**
  150. * Get input file path
  151. *
  152. * @return string
  153. */
  154. public function getInputFile()
  155. {
  156. return $this->inputFile;
  157. }
  158. /**
  159. * Set output file path
  160. *
  161. * @param string $path File path
  162. *
  163. * @return \Phrozn\Site\View
  164. */
  165. public function setOutputFile($path)
  166. {
  167. $this->outputFile = $path;
  168. return $this;
  169. }
  170. /**
  171. * Get output file path
  172. *
  173. * @return string
  174. */
  175. public function getOutputFile()
  176. {
  177. $path = new OutputFile($this);
  178. return $path->get();
  179. }
  180. /**
  181. * Set output directory path
  182. *
  183. * @param string $path Directory path
  184. *
  185. * @return \Phrozn\Site\View
  186. */
  187. public function setOutputDir($path)
  188. {
  189. $this->outputDir = $path;
  190. return $this;
  191. }
  192. /**
  193. * Get output directory path
  194. *
  195. * @return string
  196. */
  197. public function getOutputDir()
  198. {
  199. return $this->outputDir;
  200. }
  201. /**
  202. * Add text processor
  203. *
  204. * @param \Phrozn\Processor
  205. *
  206. * @return \Phrozn\Site\View
  207. */
  208. public function addProcessor($processor)
  209. {
  210. $this->processors[get_class($processor)] = $processor;
  211. return $this;
  212. }
  213. /**
  214. * Remove text processor
  215. *
  216. * @param \Phrozn\Processor
  217. *
  218. * @return \Phrozn\Site\View
  219. */
  220. public function removeProcessor($processor)
  221. {
  222. unset($this->processors[get_class($processor)]);
  223. return $this;
  224. }
  225. /**
  226. * Get list of registered processors
  227. *
  228. * @return array of \Phrozn\Processor items
  229. */
  230. public function getProcessors()
  231. {
  232. return $this->processors;
  233. }
  234. /**
  235. * Set param
  236. *
  237. * @param string $param Name of the parameter to set
  238. * @param mixed $value Value of the parameter
  239. *
  240. * @return \Phrozn\Has\Param
  241. */
  242. public function setParam($param, $value)
  243. {
  244. $this->parse();
  245. $this->frontMatter[$param] = $value;
  246. return $this;
  247. }
  248. /**
  249. * Get param value
  250. *
  251. * @param string $param Parameter name to obtain value for
  252. * @param mixed $default Default parameter value, if non found in FM
  253. *
  254. * @return mixed
  255. */
  256. public function getParam($param, $default = null)
  257. {
  258. try {
  259. $this->parse();
  260. $value = $this->getParams($param, null);
  261. } catch (\Exception $e) {
  262. // skip error on file read problems, just return default value
  263. }
  264. if (isset($value)) {
  265. return $value;
  266. } else {
  267. return $default;
  268. }
  269. }
  270. /**
  271. * Get view parameters from both front matter and general site options
  272. *
  273. * @param string $param Parameter to get value for. Levels are separated with dots
  274. * @param string $default Default value to fetch if param is not found
  275. *
  276. * @return array
  277. */
  278. public function getParams($param = null, $default = array())
  279. {
  280. $params['page'] = $this->getFrontMatter();
  281. $params['site'] = $this->getSiteConfig();
  282. $params['phr'] = $this->getAppConfig();
  283. // also create merged configuration
  284. if (isset($params['page'], $params['site'])) {
  285. $params['this'] = array_merge($params['page'], $params['site']);
  286. } else {
  287. $params['this'] = array();
  288. }
  289. if (null !== $param) {
  290. $params = $this->locateParam($params, $param);
  291. }
  292. return isset($params) ? $params : $default;
  293. }
  294. /**
  295. * Locate nested param (levels separated with dot) in params array
  296. *
  297. * @return mixed
  298. */
  299. private function locateParam($params, $param)
  300. {
  301. $value = null;
  302. $keys = explode('.', $param);
  303. for ($i = 0, $mx = count($keys); $i < $mx; $i++) {
  304. $key = $keys[$i];
  305. if (isset($params[$key])) {
  306. $value = $params[$key];
  307. if ((($i + 1) < $mx) && is_array($value)) {
  308. return $this->locateParam($value, implode('.', array_slice($keys, $i)));
  309. }
  310. }
  311. }
  312. return $value;
  313. }
  314. /**
  315. * Set site configuration
  316. *
  317. * @param array $config Array of options
  318. *
  319. * @return \Phrozn\Has\SiteConfig
  320. */
  321. public function setSiteConfig($config)
  322. {
  323. $this->siteConfig = $config;
  324. return $this;
  325. }
  326. /**
  327. * Get site configuration
  328. *
  329. * @return array
  330. */
  331. public function getSiteConfig()
  332. {
  333. if (null === $this->siteConfig) {
  334. $this->siteConfig = array();
  335. }
  336. return $this->siteConfig;
  337. }
  338. /**
  339. * Set front matter
  340. *
  341. * @param array $frontMatter Array of options
  342. *
  343. * @return \Phrozn\Has\FrontMatter
  344. */
  345. public function setFrontMatter($frontMatter)
  346. {
  347. $this->frontMatter = $frontMatter;
  348. return $this;
  349. }
  350. /**
  351. * Get YAML front matter from input view
  352. *
  353. * @return array
  354. */
  355. public function getFrontMatter()
  356. {
  357. if (null === $this->frontMatter) {
  358. $this->parse();
  359. }
  360. if (null === $this->frontMatter) {
  361. $this->frontMatter = array();
  362. }
  363. return $this->frontMatter;
  364. }
  365. /**
  366. * Set template
  367. *
  368. * @param string $template Source template
  369. *
  370. * @return \Phrozn\Has\Template
  371. */
  372. public function setTemplate($template)
  373. {
  374. $this->template = $template;
  375. return $this;
  376. }
  377. /**
  378. * Get template content from input view
  379. *
  380. * @return string
  381. */
  382. public function getTemplate()
  383. {
  384. if (null === $this->template) {
  385. $this->parse();
  386. }
  387. return $this->template;
  388. }
  389. /**
  390. * Two step view is used. View to wrap is provided with content variable.
  391. *
  392. * @param string $content View text to wrap into layout
  393. * @param array $vars List of variables passed to processors
  394. *
  395. * @return string
  396. */
  397. protected function applyLayout($content, $vars)
  398. {
  399. $layoutName = $this->getParam('page.layout', Factory::DEFAULT_LAYOUT_SCRIPT);
  400. $inputFile = $this->getInputFile();
  401. $pos = strpos($inputFile, '/entries');
  402. // make sure that input path is normalized to root entries directory
  403. if (false !== $pos) {
  404. $inputFile = substr($inputFile, 0, $pos + 8) . '/entry';
  405. }
  406. $layoutPath = realpath(dirname($inputFile) . '/../layouts/' . $layoutName);
  407. $factory = new Factory($layoutPath);
  408. $layout = $factory->create(); // essentially layout is Site\View as well
  409. $layout->hasLayout(false); // no nested layouts
  410. return $layout->render(array('content' => $content));
  411. }
  412. /**
  413. * Get/set hasLayout setting. Allows to enable/disable layout for a given view
  414. *
  415. * @param boolean $value Value to set to hasLayout option
  416. *
  417. * @return boolean
  418. */
  419. protected function hasLayout($value = null)
  420. {
  421. if (null !== $value) {
  422. $this->hasLayout = $value;
  423. }
  424. return $this->hasLayout;
  425. }
  426. /**
  427. * Parses input file into front matter and actual template content
  428. *
  429. * @return \Phrozn\Site\View
  430. */
  431. private function parse()
  432. {
  433. if (isset($this->template, $this->frontMatter)) {
  434. return $this;
  435. }
  436. $source = $this->readSourceFile();
  437. $pos = strpos($source, '---');
  438. if ($pos !== false) {
  439. $this->template = trim(substr($source, $pos + 3));
  440. $frontMatter = substr($source, 0, $pos);
  441. $this->frontMatter = Yaml::load($frontMatter);
  442. } else {
  443. $this->template = $source;
  444. $this->frontMatter = null;
  445. }
  446. return $this;
  447. }
  448. /**
  449. * Read input file
  450. *
  451. * @return string
  452. */
  453. private function readSourceFile()
  454. {
  455. if (null == $this->source) {
  456. $path = $this->getInputFile();
  457. if (null === $path) {
  458. throw new \Exception("View input file not specified.");
  459. }
  460. try {
  461. $this->source = \file_get_contents($path);
  462. } catch (\Exception $e) {
  463. throw new \Exception(sprintf('View "%s" file can not be read', $path));
  464. }
  465. }
  466. return $this->source;
  467. }
  468. private function getAppConfig()
  469. {
  470. if (null === $this->appConfig) {
  471. $path = Loader::getInstance()->getPath('configs'). '/phrozn.yml';
  472. $this->appConfig = Yaml::load(file_get_contents($path));
  473. }
  474. return $this->appConfig;
  475. }
  476. }