/app/Module/Page/Template.php

https://github.com/stackboxcms/stackboxcms · PHP · 342 lines · 178 code · 54 blank · 110 comment · 18 complexity · 65975cf88f1d00e6a815a50864d59eb2 MD5 · raw file

  1. <?php
  2. namespace Module\Page;
  3. use Alloy;
  4. /**
  5. * Page Template Parser
  6. *
  7. * Parses page templates for tokens to replace with content
  8. *
  9. * @package Stackbox CMS
  10. * @link http://stackboxcms.com/
  11. */
  12. class Template extends Alloy\View\Template
  13. {
  14. // Extension type (inherited from \Alloy\View\Template)
  15. protected $_default_format = 'html';
  16. protected $_default_extenstion = 'tpl';
  17. // Parsing storage
  18. protected $_dom;
  19. protected $_content;
  20. protected $_tokens;
  21. // Special predefined types
  22. protected $_tokenTagType = 'tag';
  23. protected $_tags = array();
  24. protected $_tokenRegionType = 'region';
  25. protected $_regions = array();
  26. protected $_regionsType = array();
  27. protected $_regionMain;
  28. /**
  29. * Parse template contents
  30. *
  31. * @param $content string
  32. */
  33. public function parse()
  34. {
  35. if($this->_tokens) {
  36. return $this->_tokens;
  37. }
  38. // Get template content
  39. $content = $this->content();
  40. // Ensure errors due to malformed HTML document will not throw PHP errors
  41. libxml_use_internal_errors(true);
  42. // Parse template with DOMDocument
  43. $dom = new \DOMDocument();
  44. $dom->registerNodeClass('DOMElement', 'Module\Page\Template\DOMElement');
  45. $dom->loadHTML($content);
  46. // Clear internal error buffer (free memory)
  47. libxml_clear_errors();
  48. // REGIONS
  49. $tokens = array();
  50. $xpath = new \DOMXPath($dom);
  51. $regions = $xpath->query("//*[contains(@class, 'cms_region') or contains(@class, 'cms_region_main') or contains(@class, 'cms_region_global')]");
  52. foreach($regions as $region) {
  53. $regionName = $region->getAttribute('id');
  54. $regionClass = $region->getAttribute('class');
  55. $regionType = 'page';
  56. // Global and main region types
  57. if(false !== strpos($regionClass, 'cms_region_global')) {
  58. $regionType = 'global';
  59. // Add the standard 'cms_region' class in addition
  60. $regionClass = $region->setAttribute('class', $region->getAttribute('class') . ' cms_region');
  61. } elseif(false !== strpos($regionClass, 'cms_region_main')) {
  62. $regionType = 'main';
  63. // Add the standard 'cms_region' class in addition
  64. $regionClass = $region->setAttribute('class', $region->getAttribute('class') . ' cms_region');
  65. // Ensure there is only ONE main region
  66. if(null !== $this->_regionMain) {
  67. throw new Template\Exception("Template can only have one main region. Second one encountered at:<br />(" . \htmlentities($region->saveHTML()) . ")");
  68. }
  69. }
  70. // Ensure region has a name (id attribute)
  71. if(!$regionName) {
  72. throw new Template\Exception("Template region does not have an id attribute.\n<br /> Parsing (" . \htmlentities($region->saveHTML()) . ")");
  73. }
  74. // Ouput array
  75. $token = array(
  76. 'name' => $regionName,
  77. 'element' => $region,
  78. 'type' => $this->_tokenRegionType,
  79. 'namespace' => 'cms',
  80. 'attributes' => $region->getAttributes(),
  81. 'content' => $region->innerHTML
  82. );
  83. $this->_regions[$regionName] = $token;
  84. $this->_regionsType[$regionType][] = $regionName;
  85. // Store main region
  86. if('main' == $regionType) {
  87. $this->_regionMain = $token;
  88. }
  89. $tokens[] = $token;
  90. }
  91. // Ensure a main region exists
  92. if(null === $this->_regionMain) {
  93. throw new Template\Exception("Template must have a main region maked with the CSS class 'cms_region_main'.");
  94. }
  95. // TAGS
  96. $xpath = new \DOMXPath($dom);
  97. $tags = $xpath->query("//*[contains(@class, 'cms_tag_')]");
  98. foreach($tags as $tag) {
  99. $tagName = str_replace('cms_tag_', '', $tag->getAttribute('class'));
  100. $token = array(
  101. 'element' => $tag,
  102. 'type' => $this->_tokenTagType,
  103. 'namespace' => 'cms',
  104. 'attributes' => $tag->getAttributes(),
  105. 'content' => $tag->innerHTML
  106. );
  107. $this->_tags[$tagName][] = $token;
  108. $tokens[] = $token;
  109. }
  110. $this->_dom = $dom;
  111. $this->_tokens = $tokens;
  112. return $tokens;
  113. }
  114. /**
  115. * Get all found tokens, regardless of type
  116. *
  117. * @return array
  118. */
  119. public function tokens()
  120. {
  121. // Parse template if is has not been parsed already
  122. if(!$this->_tokens) {
  123. $this->parse();
  124. }
  125. return $this->_tokens;
  126. }
  127. /**
  128. * Get all found tags
  129. *
  130. * @return array
  131. */
  132. public function tags()
  133. {
  134. // Parse template if is has not been parsed already
  135. if(!$this->_tags) {
  136. $this->parse();
  137. }
  138. return $this->_tags;
  139. }
  140. /**
  141. * Get all found regions
  142. *
  143. * @return array
  144. */
  145. public function regions()
  146. {
  147. // Parse template if is has not been parsed already
  148. if(!$this->_regions) {
  149. $this->parse();
  150. }
  151. return $this->_regions;
  152. }
  153. /**
  154. * Get all found global regions
  155. *
  156. * @return array
  157. */
  158. public function regionsType($type)
  159. {
  160. // Parse template if is has not been parsed already
  161. if(!$this->_regionsType) {
  162. $this->parse();
  163. }
  164. return isset($this->_regionsType[$type]) ? $this->_regionsType[$type] : array();
  165. }
  166. /**
  167. * Get all found global regions
  168. *
  169. * @return array
  170. */
  171. public function regionMain()
  172. {
  173. // Parse template if is has not been parsed already
  174. if(!$this->_regionMain) {
  175. $this->parse();
  176. }
  177. return $this->_regionMain;
  178. }
  179. /**
  180. * Cleans up template and replaces content with current DOM tree
  181. */
  182. public function clean()
  183. {
  184. $this->_content = $this->_dom->saveHTML();
  185. // Remove unused tags
  186. foreach($this->tags() as $tags) {
  187. foreach($tags as $tag) {
  188. $el = $tag['element'];
  189. $this->_content = str_replace($el->saveHTML(), '', $this->_content);
  190. }
  191. //var_dump($el->saveHTML());
  192. //$el->replaceWith($tag['content']);
  193. //$el->innerHTML = $tag['content'];
  194. }
  195. }
  196. /**
  197. * Replace given tag with content
  198. *
  199. * @param $tag string Full token string to replace
  200. * @param $replacement string
  201. *
  202. * @return boolean
  203. */
  204. public function replaceToken($tag, $replacement)
  205. {
  206. $this->_content = str_replace($tag, $replacement, $this->content());
  207. return true;
  208. }
  209. /**
  210. * Replace template tag with content
  211. *
  212. * @param $tagName string Name of template tag to replace
  213. * @param $replacement string
  214. *
  215. * @return boolean
  216. */
  217. public function replaceTag($tagName, $replacement)
  218. {
  219. $tags = $this->tags();
  220. if(isset($tags[$tagName])) {
  221. $tags = $tags[$tagName];
  222. foreach($tags as $tag) {
  223. //$this->_content = str_replace($tags[$tagName]['tag'], $replacement, $this->content());
  224. $el = $tag['element'];
  225. $el->replaceWith($replacement);
  226. }
  227. return true;
  228. } else {
  229. return false;
  230. }
  231. }
  232. /**
  233. * Place content inside template region
  234. *
  235. * @param $regionName string Name of template region to replace
  236. * @param $content string
  237. *
  238. * @return boolean
  239. */
  240. public function regionContent($regionName, $content)
  241. {
  242. $regions = $this->regions();
  243. if(isset($regions[$regionName])) {
  244. //$region = $this->_dom->getElementById($regionName);
  245. $region = $regions[$regionName]['element'];
  246. $region->innerHTML = $content;
  247. return true;
  248. } else {
  249. return false;
  250. }
  251. }
  252. /**
  253. * Returns full template filename with format and extension
  254. *
  255. * @param OPTIONAL $template string (Name of the template to return full file format)
  256. * @return string
  257. */
  258. public function templateFilename($template = null)
  259. {
  260. if(null === $template) {
  261. $template = $this->template();
  262. }
  263. return $template . '.' . $this->_default_extenstion . '.' . $this->format();
  264. }
  265. /**
  266. * Set template contents
  267. *
  268. * @param string $content
  269. * @return string
  270. */
  271. public function content($parsePHP = true)
  272. {
  273. if(null === $this->_content) {
  274. $this->_content = parent::content(false);
  275. }
  276. return $this->_content;
  277. }
  278. /**
  279. * Converts view object to string on the fly
  280. *
  281. * @return string
  282. */
  283. public function __toString()
  284. {
  285. $this->clean();
  286. return parent::__toString();
  287. }
  288. }