PageRenderTime 46ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/phy/classes/template/_class.php

https://github.com/mullanaphy/phy
PHP | 1097 lines | 769 code | 59 blank | 269 comment | 137 complexity | 59392d9abf608fa0115f448295e38d95 MD5 | raw file
  1. <?php
  2. namespace PHY;
  3. /**
  4. * @package Template
  5. * @category Frontend
  6. * @author John Mullanaphy
  7. */
  8. final class Template {
  9. private $generated = false,
  10. $css = array(
  11. 'added' => array(),
  12. 'core' => array(),
  13. 'modules' => array(),
  14. 'print' => array()
  15. ),
  16. $js = array(
  17. 'added' => array(),
  18. 'core' => array(),
  19. 'modules' => array(),
  20. 'footer' => array()
  21. ),
  22. $keywords = array(
  23. 'added' => array(),
  24. 'invalid' => array('panda')
  25. ),
  26. $meta = array(),
  27. $rss = array(
  28. 'core' => array(),
  29. 'added' => array()
  30. ),
  31. $scripts = array(),
  32. $sections = array(),
  33. $show = array(
  34. 'footer' => true,
  35. 'header' => true,
  36. 'theme' => true,
  37. 'title' => true
  38. ),
  39. $tag = NULL,
  40. $title = '';
  41. /**
  42. * You can only initiate one Template object.
  43. *
  44. * Will call $this->init(); on initiation.
  45. */
  46. public function __construct() {
  47. # Meta data.
  48. $this->meta = array(
  49. 'header_content_type' => array(
  50. 'http-equiv' => 'content-type',
  51. 'content' => 'text/html;charset=utf-8'
  52. ),
  53. 'name_cache_control' => array(
  54. 'name' => 'cache-control',
  55. 'content' => 'public'
  56. ),
  57. 'name_expires' => array(
  58. 'name' => 'expires',
  59. 'content' => date('Y-m-d@H:i:s T',strtotime('1 week'))
  60. ),
  61. 'name_last_modified' => array(
  62. 'name' => 'last-modified',
  63. 'content' => date('Y-m-d@H:i:s T')
  64. ),
  65. 'name_description' => array(
  66. 'name' => 'description',
  67. 'content' => \PHY\Registry::get('config/site/description')
  68. ),
  69. 'name_keywords' => array(
  70. 'name' => 'keywords',
  71. 'content' => \PHY\Registry::get('config/site/keywords')
  72. ),
  73. 'name_author' => array(
  74. 'name' => 'author',
  75. 'content' => \PHY\Registry::get('config/site/name')
  76. ),
  77. 'name_contact' => array(
  78. 'name' => 'contact',
  79. 'content' => \PHY\Registry::get('config/site/email')
  80. ),
  81. 'name_robots' => array(
  82. 'name' => 'robots',
  83. 'content' => 'noodp'
  84. ),
  85. 'name_blogcatalog' => array(
  86. 'name' => 'blogcatalog',
  87. 'content' => '9BC9576594'
  88. ),
  89. 'og:site_name' => array(
  90. 'property' => 'og:site_name',
  91. 'content' => \PHY\Registry::get('config/site/name')
  92. )
  93. );
  94. # HTML Version to use.
  95. $this->tag = \PHY\Markup::instance();
  96. /* switch(true):
  97. case (Headers::ie6()):
  98. $this->tag = \PHY\Markup::instance('HTML4');
  99. break;
  100. case (Headers::bot()):
  101. $this->tag = \PHY\Markup::instance('HTML4');
  102. break;
  103. case (Headers::mobile()):
  104. $this->meta[] = array(
  105. 'name' => 'viewport',
  106. 'content' => 'user-scalable=no,width=device-width,minimum-scale=1.0,maximum-scale=1.0'
  107. );
  108. default:
  109. endswitch; */
  110. $this->js['core'] = \PHY\Registry::get('config/files/js');
  111. $this->css['core'] = \PHY\Registry::get('config/files/css');
  112. echo $this->tag->DOCTYPE,
  113. $this->tag->OPENER;
  114. flush();
  115. if(ob_list_handlers()) ob_flush();
  116. }
  117. /**
  118. * Will call $this->generate(); on object destruction.
  119. */
  120. public function __destruct() {
  121. if(!$this->generated) $this->generate();
  122. }
  123. /**
  124. * returns all the content.
  125. *
  126. * @return string
  127. */
  128. public function __toString() {
  129. return $this->head().$this->body().$this->tag->CLOSER;
  130. }
  131. /**
  132. * Echos out all generated HTML.
  133. */
  134. public function generate() {
  135. $this->generated = true;
  136. echo $this->head();
  137. flush();
  138. if(ob_list_handlers()) ob_flush();
  139. echo $this->body().$this->tag->CLOSER;
  140. }
  141. /**
  142. * Append content into the current Section/Column
  143. *
  144. * @param mixed $content
  145. * @param array $attributes
  146. * @return bool
  147. */
  148. public function append($content=false,$attributes=NULL) {
  149. if($content === false) return false;
  150. if(is_numeric($attributes)) $attributes = array('style' => 'width:'.(($attributes >= 1)
  151. ?(int)$attributes.'px'
  152. :(int)($attributes * 100).'%'));
  153. else $attributes = $this->_attributes($attributes);
  154. if(!count($this->sections)) $this->section('normal');
  155. elseif(!count($this->sections[count($this->sections) - 1]['columns'])) $this->column();
  156. $this->sections[count($this->sections) - 1]['columns'][count($this->sections[count($this->sections) - 1]['columns']) - 1]['content'][] = array(
  157. 'attributes' => $attributes,
  158. 'content' => $content
  159. );
  160. return true;
  161. }
  162. /**
  163. * Start a new column.
  164. *
  165. * If you send an array of attributes, 'width' => '' will be appended to
  166. * style in the same way that sending just a decimal or integer would.
  167. *
  168. * @param int|float|array $attributes
  169. */
  170. public function column($attributes=NULL) {
  171. if(is_numeric($attributes)) $attributes = array('style' => 'width:'.(($attributes >= 1)
  172. ?(int)$attributes.'px'
  173. :(int)($attributes * 100).'%'));
  174. else $attributes = $this->_attributes($attributes);
  175. if(isset($attributes['width'])):
  176. if(isset($attributes['style'])) $attributes['style'] .= 'width:'.(($attributes['width'] >= 1)
  177. ?(int)$attributes['width'].'px'
  178. :(int)($attributes['width'] * 100).'%').';';
  179. else $attributes['style'] = 'width:'.(($attributes['width'] >= 1)
  180. ?(int)$attributes['width'].'px'
  181. :(int)($attributes['width'] * 100).'%').';';
  182. unset($attributes['width']);
  183. endif;
  184. if(!count($this->sections)) $this->section('normal');
  185. $this->sections[count($this->sections) - 1]['columns'][] = array(
  186. 'attributes' => $attributes,
  187. 'containers' => array()
  188. );
  189. }
  190. /**
  191. * Create a heading section.
  192. *
  193. * @param mixed $content
  194. * @param string $tag Type of container tag to use.
  195. * @param array $attributes
  196. * @return bool
  197. */
  198. public function heading($content=NULL,$tag='h2',$attributes=NULL) {
  199. if($content === NULL) return false;
  200. if(is_array($tag)):
  201. $attributes = $tag;
  202. $tag = 'h2';
  203. elseif(!preg_match('#h[0-6]#i',$tag)):
  204. $tag = 'h2';
  205. endif;
  206. if(!is_array($attributes)) $attributes = array();
  207. if(isset($attributes['class'])) $attributes['class'] = 'heading '.$attributes['class'];
  208. else $attributes['class'] = 'heading';
  209. if(!count($this->sections)) $this->section('normal');
  210. elseif(!count($this->sections[count($this->sections) - 1]['columns'])) $this->column();
  211. $this->sections[count($this->sections) - 1]['heading'] = $this->tag->$tag($content,$attributes);
  212. return true;
  213. }
  214. /**
  215. * Prepend content into the current Section/Column
  216. *
  217. * @param mixed $content
  218. * @param array $attributes
  219. * @return bool
  220. */
  221. public function prepend($content=false,$attributes=NULL) {
  222. if($content === false) return false;
  223. if(is_numeric($attributes)) $attributes = array('style' => 'width:'.(($attributes >= 1)
  224. ?(int)$attributes.'px'
  225. :(int)($attributes * 100).'%'));
  226. else $attributes = $this->_attributes($attributes);
  227. array_unshift(
  228. $this->sections[count($this->sections) - 1]['columns'][count($this->sections[count($this->sections) - 1]['columns']) - 1]['content'],array(
  229. 'attributes' => $attributes,
  230. 'content' => $content
  231. )
  232. );
  233. return true;
  234. }
  235. /**
  236. * Add javascript to be run on its own.
  237. *
  238. * @param type $content
  239. * @param type $attributes
  240. * @return array If no content is sent it returns back current scripts.
  241. */
  242. public function script($content=NULL,$attributes=NULL) {
  243. if($content === NULL) return $this->scripts;
  244. # Make sure a type is set.
  245. if(!is_array($attributes)) $attributes = array('type' => 'text/javascript');
  246. elseif(!isset($attributes['type'])) $attributes['type'] = 'text/javascript';
  247. # Store it.
  248. $this->scripts[] = $this->tag->script($content,$attributes);
  249. }
  250. /**
  251. * Start a new section on our page.
  252. *
  253. * IF $attributes is a bool then $expand = $attributes.
  254. *
  255. * @param type $type Class name of our section.
  256. * @param array $attributes Attributes for the section or a bool.
  257. * @param bool $expand If the section should expand the whole viewport.
  258. */
  259. public function section($type='normal',$attributes=NULL,$expand=NULL) {
  260. if(is_bool($attributes)):
  261. $expanded = !!$attributes;
  262. $attributes = $this->_attributes($expand);
  263. else:
  264. $expanded = false;
  265. $attributes = $this->_attributes($attributes);
  266. endif;
  267. if(isset($attributes['class'])) $attributes['class'] = $type.' '.$attributes['class'];
  268. else $attributes['class'] = $type;
  269. $this->sections[] = array(
  270. 'attributes' => $attributes,
  271. 'columns' => array(),
  272. 'expanded' => $expanded,
  273. 'heading' => NULL
  274. );
  275. }
  276. /**
  277. * Set|Get CSS.
  278. *
  279. * If no parameters are set then this method will return all current
  280. * CSS files that have been set.
  281. *
  282. * @param string,... $css
  283. * @return mixed
  284. */
  285. public function css($css=NULL) {
  286. if($css === NULL) return array_merge($this->css['added'],$this->css['modules']);
  287. foreach(func_get_args() as $css):
  288. if(is_array($css)):
  289. foreach($css as $file):
  290. if(preg_match('#print\.css|print/#',$file)) $this->css['print'][] = $file;
  291. else $this->css['added'][] = $file;
  292. endforeach;
  293. else:
  294. if(preg_match('#print\.css|print/#',$css)) $this->css['print'][] = $css;
  295. else $this->css['added'][] = $css;
  296. endif;
  297. endforeach;
  298. $this->css['added'] = array_unique($this->css['added']);
  299. $this->css['print'] = array_unique($this->css['print']);
  300. return true;
  301. }
  302. /**
  303. * Set|Get meta description.
  304. *
  305. * If called without a parameter then this will return the currently set
  306. * meta description.
  307. *
  308. * @param string $description
  309. * @return mixed
  310. */
  311. public function description($description=NULL) {
  312. if($description === NULL) return $this->meta['name_description']['content'];
  313. $this->meta['name_description']['content'] = $description;
  314. }
  315. /**
  316. * Set|Get CSS and JS files.
  317. *
  318. * If no parameters are set then this method will return all current
  319. * files that have been set (CSS and JS).
  320. *
  321. * @param string,... $file
  322. * @return mixed
  323. */
  324. public function files($files=NULL) {
  325. if($files === NULL) return array_merge($this->css['added'],$this->css['modules'],$this->js['added'],$this->js['modules']);
  326. foreach(func_get_args() as $files):
  327. if(is_array($files)):
  328. foreach($files as $file):
  329. if(substr($file,-4) === '.css') $this->css['modules'][] = $file;
  330. elseif(substr($file,-3) === '.js') $this->js['modules'][] = $file;
  331. endforeach;
  332. else:
  333. if(substr($files,-4) === '.css') $this->css['modules'][] = $files;
  334. elseif(substr($files,-3) === '.js') $this->js['modules'][] = $files;
  335. endif;
  336. endforeach;
  337. $this->css['modules'] = array_unique($this->css['modules']);
  338. $this->js['modules'] = array_unique($this->js['modules']);
  339. return true;
  340. }
  341. /**
  342. * Turn options off.
  343. *
  344. * Can hide:
  345. * 'title'
  346. * 'footer'
  347. * 'header'
  348. * 'theme'
  349. *
  350. * If no parameters are set then it will return currently hidden.
  351. *
  352. * @param $hide,... Option to hide.
  353. * @return mixed
  354. */
  355. public function hide() {
  356. if(!count(func_get_args())):
  357. $hidden = array();
  358. foreach($this->show as $option => $show) if(!$show) $hidden[] = $option;
  359. return $hidden;
  360. endif;
  361. foreach(func_get_args() as $hide) if(isset($this->show[$hide])) $this->show[$hide] = false;
  362. }
  363. /**
  364. * Set|Get JS.
  365. *
  366. * If no parameters are set then this method will return all current
  367. * JS files that have been set.
  368. *
  369. * @param string,... $js
  370. * @return mixed
  371. */
  372. public function js($js=NULL) {
  373. if($js === NULL) return array_merge($this->js['added'],$this->js['modules']);
  374. foreach(func_get_args() as $js):
  375. if(is_array($js)) foreach($js as $file) $this->js['added'][] = $file;
  376. else $this->js['added'][] = $js;
  377. endforeach;
  378. $this->js['added'] = array_unique($this->js['added']);
  379. return true;
  380. }
  381. /**
  382. * Set|Get meta keywords.
  383. *
  384. * If called without a parameter then this will return the currently set
  385. * meta keyword.
  386. *
  387. * @param string,... $keyword
  388. * @return mixed
  389. */
  390. public function keywords() {
  391. if(!count(func_get_args())) return $this->meta['name_keywords']['content'];
  392. $this->meta['name_keywords']['content'] = join(', ',func_get_args());
  393. }
  394. /**
  395. * Set|Get a meta tag.
  396. *
  397. * If called without a parameter then this will return the currently set
  398. * meta tags.
  399. *
  400. * @param array $attributes Attributes of the new meta tag.
  401. * @return mixed
  402. */
  403. public function meta(array $attributes=NULL) {
  404. if($attributes === NULL) return $this->meta;
  405. if($attributes) $this->meta[] = $meta;
  406. }
  407. /**
  408. * Set|Get RSS.
  409. *
  410. * If no parameters are set then this method will return all current
  411. * RSS files that have been set.
  412. *
  413. * @param string,... $rss
  414. * @return mixed
  415. */
  416. public function rss($rss=NULL) {
  417. if($rss === NULL) return $this->rss['added'];
  418. foreach(func_get_args() as $rss):
  419. if(is_array($rss)) foreach($rss as $file) $this->rss['added'][] = $file;
  420. else $this->rss['added'][] = $rss;
  421. endforeach;
  422. $this->rss['added'] = array_unique($this->rss['added']);
  423. }
  424. /**
  425. * Turn options off.
  426. *
  427. * Can show:
  428. * 'title'
  429. * 'footer'
  430. * 'header'
  431. * 'theme'
  432. *
  433. * If no parameters are set then it will return currently shown.
  434. *
  435. * @param $show,... Option to show.
  436. * @return mixed
  437. */
  438. public function show() {
  439. if(!count(func_get_args())):
  440. $shown = array();
  441. foreach($this->show as $option => $show) if($show) $shown[] = $option;
  442. return $shown;
  443. endif;
  444. foreach(func_get_args() as $show) if(isset($this->show[$show])) $this->show[$show] = false;
  445. }
  446. /**
  447. * Set|Get meta title.
  448. *
  449. * If called without a parameter then this will return the currently set
  450. * meta title.
  451. *
  452. * @param string $title
  453. * @return mixed
  454. */
  455. public function title($title=false) {
  456. if($title) $this->title = $title;
  457. return $this->title;
  458. }
  459. /**
  460. * Append a template source.
  461. *
  462. * @param string $source
  463. * @param bool $return If true it will return Markup instead of appending it.
  464. * @return string|Markup_Abstract
  465. */
  466. public function template($source=NULL,$return=false) {
  467. if(!is_string($source) || !$source):
  468. \PHY\Debug::error('No source was set to append.');
  469. return false;
  470. else:
  471. $file = false;
  472. foreach(array(ROOT_PATH.'/phy/templates/'.\PHY\Registry::theme().'/'.$source,ROOT_PATH.'/phy/templates/default/'.$source,BASE_PATH.'phy/templates/'.\PHY\Registry::theme().'/'.$source,BASE_PATH.'phy/templates/default/'.$source) as $check):
  473. if(is_file($check)):
  474. $file = $check;
  475. break;
  476. endif;
  477. endforeach;
  478. if(!$file):
  479. \PHY\Debug::error('Source "'.$source.'" could not be found.');
  480. return false;
  481. endif;
  482. endif;
  483. ob_start();
  484. include $file;
  485. $content = ob_get_contents();
  486. ob_end_clean();
  487. if($return) return $content;
  488. else $this->append($content);
  489. }
  490. /**
  491. * Return a Markup object containing all of our body content.
  492. *
  493. * @return Markup_Abstract
  494. */
  495. protected function body() {
  496. $body = $this->tag->body;
  497. # Process the header.
  498. if($this->show['header']) $body->append($this->header());
  499. # Process the actual page.
  500. $i = false;
  501. foreach($this->sections as $group):
  502. if(!$i):
  503. $i = true;
  504. if(isset($group['attributes']['class'])) $group['attributes']['class'] = 'first '.$group['attributes']['class'];
  505. else $group['attributes']['class'] = 'first';
  506. endif;
  507. if(isset($group['attributes']['class'])) $group['attributes']['class'] = 'section '.$group['attributes']['class'];
  508. else $group['attributes']['class'] = 'section';
  509. $section = $this->tag->div;
  510. $section->attributes($group['attributes']);
  511. $holder = $this->tag->div;
  512. if(!$group['expanded']) $holder->attributes(array('class' => 'holder'));
  513. foreach($group['columns'] as $col):
  514. $column = $this->tag->div;
  515. if(isset($col['attributes']['class'])) $col['attributes']['class'] = 'column '.$col['attributes']['class'];
  516. else $col['attributes']['class'] = 'column';
  517. $column->attributes($col['attributes']);
  518. if(isset($col['content']) && is_array($col['content'])) foreach($col['content'] as $container) $column->append($container);
  519. $holder->append($column);
  520. endforeach;
  521. $section->append($holder);
  522. if($group['heading'] !== NULL) $section->prepend($group['heading']);
  523. $body->append($section);
  524. endforeach;
  525. # Process the footer.
  526. if($this->show['footer']) $body->append($this->footer());
  527. if(!\PHY\Registry::get('config/site/production')):
  528. foreach($this->js['footer'] as $js) $body->append(
  529. $this->tag->script(
  530. NULL,array(
  531. 'src' => (substr($js,0,4) === 'http'
  532. ?$js
  533. :'/js/'.$js),
  534. 'type' => 'text/javascript'
  535. )
  536. )
  537. );
  538. else:
  539. if(!in_array(USER_BROWSER,$this->browsers['bots'] + $this->browsers['text'])):
  540. $footer = array();
  541. foreach($this->js['footer'] as $js):
  542. if(substr($js,0,7) === 'http://' || substr($js,0,8) === 'https://') $body->append(
  543. $this->tag->script(
  544. NULL,array(
  545. 'src' => $js,
  546. 'type' => 'text/javascript'
  547. )
  548. )
  549. );
  550. elseif(is_file(BASE_PATH.'js/'.$js)) $footer[] = $js;
  551. endforeach;
  552. if(!is_file(BASE_PATH.'js/cached/footer.'.md5(join('',$footer)).'.js')):
  553. $files_content = NULL;
  554. foreach($footer as $js):
  555. $FILE = fopen(BASE_PATH.'js/'.$js,'r');
  556. $files_content .= '/* '.$js.' */'."\n".fread($FILE,filesize(BASE_PATH.'js/'.$js))."\n";
  557. fclose($FILE);
  558. endforeach;
  559. if(strlen($files_content) > 0):
  560. $FILE = fopen(BASE_PATH.'js/cached/footer.'.md5(join('',$footer)).'.js','w');
  561. fwrite($FILE,MinifyJS::minify($files_content));
  562. fclose($FILE);
  563. endif;
  564. endif;
  565. $body->append(
  566. $this->tag->script(
  567. NULL,array(
  568. 'src' => '/scripts/cached/footer.'.md5(join('',$this->js['footer'])).'.js',
  569. 'type' => 'text/javascript'
  570. )
  571. )
  572. );
  573. endif;
  574. $body->append(
  575. $this->tag->script(
  576. 'var gaJsHost=((\'https:\'==document.location.protocol)?\'https://ssl.\':\'http://www.\');document.write(unescape("%3Cscript src=\'"+gaJsHost+"google-analytics.com/ga.js\' type=\'text/javascript\'%3E%3C/script%3E"));',array('type' => 'text/javascript')
  577. )
  578. );
  579. $body->append(
  580. $this->tag->script(
  581. 'try{var pageTracker=_gat._getTracker(\'UA-2763315-2\');pageTracker._trackPageview();}catch(e){}',array('type' => 'text/javascript')
  582. )
  583. );
  584. endif;
  585. $body->append(
  586. $this->tag->script(
  587. 'try{if(console)console.log(\'Generation: '.Debug::timer().'; Elements: '.\PHY\Markup::elements().'; Server: '.$_SERVER['SERVER_ADDR'].'\');}catch(e){};',array('type' => 'text/javascript')
  588. )
  589. );
  590. return $body;
  591. }
  592. /**
  593. * Internal function for generating all the files and merging if we are
  594. * on a production server.
  595. *
  596. * @access private
  597. * @internal
  598. * @return mixed
  599. */
  600. private function _files() {
  601. $files = array();
  602. # Devo and Beta servers. We will not combine files on these servers.
  603. if(in_array(USER_BROWSER,array('ie','ie6')) || !\PHY\Registry::get('config/site/production')):
  604. foreach(array_merge($this->css['core'],$this->css['added'],$this->css['modules']) as $css) $files[] = $this->tag->link(
  605. array(
  606. 'href' => $css,
  607. 'rel' => 'stylesheet',
  608. 'type' => 'text/css'
  609. )
  610. );
  611. # Theme.
  612. /* Goes here */
  613. # Print CSS.
  614. if($this->css['print']) foreach($this->css['print'] as $css) $files[] = $this->tag->link(
  615. array(
  616. 'href' => $css,
  617. 'media' => 'print',
  618. 'rel' => 'stylesheet',
  619. 'type' => 'text/css'
  620. )
  621. );
  622. foreach(array_merge($this->js['core'],$this->js['added'],$this->js['modules']) as $js) $files[] = $this->tag->script(
  623. NULL,array(
  624. 'src' => (substr($js,0,4) === 'http'
  625. ?$js
  626. :'/js/'.$js),
  627. 'type' => 'text/javascript'
  628. )
  629. );
  630. # Live servers, we combine the files to make less requests per page.
  631. elseif(!Headers::bot()):
  632. # Core CSS.
  633. if(!is_file(ROOT_PATH.'/css/cached/core.'.md5(join('',$this->css['core'])).'.css')):
  634. $files_content = NULL;
  635. foreach($this->css['core'] as $css):
  636. if(substr($css,0,7) === 'http://' || substr($css,0,8) === 'https://'):
  637. $files[] = $this->tag->link(
  638. array(
  639. 'href' => $css,
  640. 'rel' => 'stylesheet',
  641. 'type' => 'text/css'
  642. )
  643. );
  644. elseif(is_file(ROOT_PATH.$css)):
  645. $FILE = fopen(ROOT_PATH.$css,'r');
  646. $files_content .= '/* '.$css.' */'."\n".fread($FILE,filesize(ROOT_PATH.$css))."\n";
  647. fclose($FILE);
  648. endif;
  649. endforeach;
  650. if(strlen($files_content) > 0):
  651. $FILE = fopen(ROOT_PATH.'/css/cached/core.'.md5(join('',$this->css['core'])).'.css','w');
  652. fwrite($FILE,MinifyCSS::minify($files_content));
  653. fclose($FILE);
  654. endif;
  655. endif;
  656. $files[] = $this->tag->link(
  657. array(
  658. 'href' => '/css/cached/core.'.md5(join('',$this->css['core'])).'.css',
  659. 'rel' => 'stylesheet',
  660. 'type' => 'text/css'
  661. )
  662. );
  663. # Added CSS.
  664. if(count($this->css['added'])):
  665. if(!is_file(ROOT_PATH.'/css/cached/hash.'.md5(join('',$this->css['added'])).'.css')):
  666. $files_content = NULL;
  667. foreach($this->css['added'] as $css):
  668. if(substr($css,0,7) === 'http://' || substr($css,0,8) === 'https://'):
  669. $files[] = $this->tag->link(
  670. array(
  671. 'href' => $css,
  672. 'rel' => 'stylesheet',
  673. 'type' => 'text/css'
  674. )
  675. );
  676. elseif(is_file(ROOT_PATH.$css)):
  677. $FILE = fopen(ROOT_PATH.$css,'r');
  678. $files_content .= '/* '.$css.' */'."\n".fread($FILE,filesize(ROOT_PATH.$css))."\n";
  679. fclose($FILE);
  680. endif;
  681. endforeach;
  682. if(strlen($files_content) > 0):
  683. $FILE = fopen(ROOT_PATH.'/css/cached/hash.'.md5(join('',$this->css['added'])).'.css','w');
  684. fwrite($FILE,MinifyCSS::minify($files_content));
  685. fclose($FILE);
  686. endif;
  687. endif;
  688. $files[] = $this->tag->link(
  689. array(
  690. 'href' => '/css/cached/hash.'.md5(join('',$this->css['added'])).'.css',
  691. 'rel' => 'stylesheet',
  692. 'type' => 'text/css'
  693. )
  694. );
  695. endif;
  696. # Modular CSS.
  697. if(count($this->css['modules'])):
  698. if(!is_file(ROOT_PATH.'/css/cached/modules.'.md5(join('',$this->css['modules'])).'.css')):
  699. $files_content = NULL;
  700. foreach($this->css['modules'] as $css):
  701. if(substr($css,0,7) === 'http://' || substr($css,0,8) === 'https://'):
  702. $files[] = $this->tag->link(
  703. array(
  704. 'href' => $css,
  705. 'rel' => 'stylesheet',
  706. 'type' => 'text/css'
  707. )
  708. );
  709. elseif(is_file(ROOT_PATH.$css)):
  710. $FILE = fopen(ROOT_PATH.$css,'r');
  711. $files_content .= '/* '.$css.' */'."\n".fread($FILE,filesize(ROOT_PATH.$css))."\n";
  712. fclose($FILE);
  713. endif;
  714. endforeach;
  715. if(strlen($files_content) > 0):
  716. $FILE = fopen(ROOT_PATH.'/css/cached/modules.'.md5(join('',$this->css['modules'])).'.css','w');
  717. fwrite($FILE,MinifyCSS::minify($files_content));
  718. fclose($FILE);
  719. endif;
  720. else:
  721. foreach($this->css['modules'] as $css) if(substr($css,0,7) === 'http://' || substr($css,0,8) === 'https://') $files[] = $this->tag->link(
  722. array(
  723. 'href' => $css,
  724. 'rel' => 'stylesheet',
  725. 'type' => 'text/css'
  726. )
  727. );
  728. endif;
  729. $files[] = $this->tag->link(
  730. array(
  731. 'href' => '/css/cached/modules.'.md5(join('',$this->css['modules'])).'.css',
  732. 'rel' => 'stylesheet',
  733. 'type' => 'text/css'
  734. )
  735. );
  736. endif;
  737. # Theme CSS.
  738. /* */
  739. # Print CSS.
  740. if($this->css['print']) foreach($this->css['print'] as $css) $files[] = $this->tag->link(
  741. array(
  742. 'href' => $css,
  743. 'media' => 'print',
  744. 'rel' => 'stylesheet',
  745. 'type' => 'text/css'
  746. )
  747. );
  748. # Core JS.
  749. if(!is_file(ROOT_PATH.'/js/cached/core.'.md5(join('',$this->js['core'])).'.js')):
  750. $files_content = NULL;
  751. foreach($this->js['core'] as $js):
  752. if(substr($js,0,7) === 'http://' || substr($js,0,8) === 'https://'):
  753. $files[] = $this->tag->script(
  754. NULL,array(
  755. 'src' => $js,
  756. 'type' => 'text/javascript'
  757. )
  758. );
  759. elseif(is_file(ROOT_PATH.$js)):
  760. $FILE = fopen(ROOT_PATH.$js,'r');
  761. $files_content .= '/* '.$js.' */'."\n".fread($FILE,filesize(ROOT_PATH.$js))."\n";
  762. fclose($FILE);
  763. endif;
  764. endforeach;
  765. if(strlen($files_content) > 0):
  766. $FILE = fopen(ROOT_PATH.'/js/cached/core.'.md5(join('',$this->js['core'])).'.js','w');
  767. fwrite($FILE,MinifyJS::minify($files_content));
  768. fclose($FILE);
  769. endif;
  770. else:
  771. foreach($this->js['core'] as $js) if(substr($js,0,7) === 'http://' || substr($js,0,8) === 'https://') $files[] = $this->tag->script(
  772. NULL,array(
  773. 'src' => $js,
  774. 'type' => 'text/javascript'
  775. )
  776. );
  777. endif;
  778. $files[] = $this->tag->script(
  779. NULL,array(
  780. 'src' => '/js/cached/core.'.md5(join('',$this->js['core'])).'.js',
  781. 'type' => 'text/javascript'
  782. )
  783. );
  784. # Added JS.
  785. if(count($this->js['added'])):
  786. if(!is_file(ROOT_PATH.'/js/cached/hash.'.md5(join('',$this->js['added'])).'.js')):
  787. $files_content = NULL;
  788. foreach($this->js['added'] as $js):
  789. if(substr($js,0,7) === 'http://' || substr($js,0,8) === 'https://'):
  790. $files[] = $this->tag->script(
  791. NULL,array(
  792. 'src' => $js,
  793. 'type' => 'text/javascript'
  794. )
  795. );
  796. elseif(is_file(ROOT_PATH.$js)):
  797. $FILE = fopen(ROOT_PATH.$js,'r');
  798. $files_content .= '/* '.$js.' */'."\n".fread($FILE,filesize(ROOT_PATH.$js))."\n";
  799. fclose($FILE);
  800. endif;
  801. endforeach;
  802. if(strlen($files_content) > 0):
  803. $FILE = fopen(ROOT_PATH.'/js/cached/hash.'.md5(join('',$this->js['added'])).'.js','w');
  804. fwrite($FILE,MinifyJS::minify($files_content));
  805. fclose($FILE);
  806. endif;
  807. else:
  808. foreach($this->js['added'] as $js) if(substr($js,0,7) === 'http://' || substr($js,0,8) === 'https://') $files[] = $this->tag->script(
  809. NULL,array(
  810. 'src' => $js,
  811. 'type' => 'text/javascript'
  812. )
  813. );
  814. endif;
  815. $files[] = $this->tag->script(
  816. NULL,array(
  817. 'src' => '/js/cached/hash.'.md5(join('',$this->js['added'])).'.js',
  818. 'type' => 'text/javascript'
  819. )
  820. );
  821. if(Headers::ie6()):
  822. $files[] = $this->tag->script(
  823. NULL,array(
  824. '/js/pngfix.js',
  825. 'type' => 'text/javascript'
  826. )
  827. );
  828. endif;
  829. endif;
  830. # Modular JS.
  831. if(count($this->js['modules'])):
  832. if(!is_file(ROOT_PATH.'/js/cached/modules.'.md5(join('',$this->js['modules'])).'.js')):
  833. $files_content = NULL;
  834. foreach($this->js['modules'] as $js):
  835. if(substr($js,0,7) === 'http://' || substr($js,0,8) === 'https://'):
  836. $files[] = $this->tag->script(
  837. NULL,array(
  838. 'src' => $js,
  839. 'type' => 'text/javascript'
  840. )
  841. );
  842. elseif(is_file(ROOT_PATH.$js)):
  843. $FILE = fopen(ROOT_PATH.$js,'r');
  844. $files_content .= '/* '.$js.' */'."\n".fread($FILE,filesize(ROOT_PATH.$js))."\n";
  845. fclose($FILE);
  846. endif;
  847. endforeach;
  848. if(strlen($files_content) > 0):
  849. $FILE = fopen(ROOT_PATH.'/js/cached/modules.'.md5(join('',$this->js['modules'])).'.js','w');
  850. fwrite($FILE,MinifyJS::minify($files_content));
  851. fclose($FILE);
  852. endif;
  853. else:
  854. foreach($this->js['modules'] as $js) if(substr($js,0,7) === 'http://' || substr($js,0,8) === 'https://') $files[] = $this->tag->script(
  855. NULL,array(
  856. 'src' => $js,
  857. 'type' => 'text/javascript'
  858. )
  859. );
  860. endif;
  861. $files[] = $this->tag->script(
  862. NULL,array(
  863. 'src' => '/js/cached/modules.'.md5(join('',$this->js['modules'])).'.js',
  864. 'type' => 'text/javascript'
  865. )
  866. );
  867. endif;
  868. # RSS.
  869. if(count($this->rss['core'] + $this->rss['added'])):
  870. foreach($this->rss['core'] + $this->rss['added'] as $title => $url):
  871. $files[] = $this->tag->link(
  872. array(
  873. 'href' => $url,
  874. 'rel' => 'alternate',
  875. 'title' => $title,
  876. 'type' => 'application/rss+xml'
  877. )
  878. );
  879. endforeach;
  880. endif;
  881. else:
  882. $files = NULL;
  883. endif;
  884. return $files;
  885. }
  886. /**
  887. * Generate and return the HEAD tag of our Page.
  888. *
  889. * @return Markup_Abstract
  890. */
  891. public function head() {
  892. $head = $this->tag->head;
  893. # Page title.
  894. $head->append(
  895. $this->tag->title(
  896. (
  897. $this->show['title']
  898. ?\PHY\Registry::get('config/site/name').
  899. (
  900. $this->title
  901. ?' - '
  902. :NULL
  903. )
  904. :NULL
  905. ).
  906. $this->title
  907. )
  908. );
  909. # Shebang handler. Urls that might have been index via Google as #!/page
  910. if(isset($_GET['_escaped_fragment_'])) $head->append(
  911. $this->tag->noscript(
  912. $this->tag->meta(
  913. array(
  914. 'http-equiv' => 'refresh',
  915. 'content' => '0; URL='.$_GET['_escaped_fragment_']
  916. )
  917. )
  918. )
  919. );
  920. # Add meta tags.
  921. $meta = $this->_meta();
  922. $this->meta['name_keywords']['content'] = join(', ',Registry::get('config/site/keywords')).($meta
  923. ?', '.$meta
  924. :NULL).', '.join(', ',$this->meta['name_keywords']['content']);
  925. foreach($this->meta as $meta):
  926. $tag = $this->tag->meta;
  927. $tag->attributes($meta);
  928. $head->append($tag);
  929. endforeach;
  930. $head->append($this->_files());
  931. $this->script(
  932. 'if(window.location.hash.toString().match(\'!\')){var url=window.location.hash.toString().split(\'!\');window.location=url[1];}'.
  933. 'if(typeof $===\'undefined\')var $={};$.user={xsrf:\''.Cookie::get('xsrf_id').'\'};'
  934. );
  935. foreach($this->scripts as $script) $head->append($script);
  936. return $head;
  937. }
  938. /**
  939. * Generate and return the FOOTER tag of our Page.
  940. *
  941. * @return Markup_Abstract
  942. */
  943. public function footer() {
  944. $content = $this->template('footer.phtml',true);
  945. /* if(!$content):
  946. $content = $this->tag->footer;
  947. $content->attributes(array('id' => 'footer'));
  948. $content->append('<!-- Footer was not defined -->');
  949. endif; */
  950. return $content;
  951. }
  952. /**
  953. * Generate the HEADER section of our BODY section.
  954. *
  955. * @return string|Markup_Abstract
  956. */
  957. public function header() {
  958. $content = $this->template('header.phtml',true);
  959. /* if(!$content):
  960. $content = $this->tag->header;
  961. $content->attributes(array('id' => 'header'));
  962. $content->append('<!-- Header was not defined -->');
  963. endif; */
  964. return $content;
  965. }
  966. /**
  967. * Internal cleaner for attributes.
  968. *
  969. * If you send just a string then it will be set as the class
  970. * If you send a string with a : then it will set key:value.
  971. *
  972. * @param mixed $attributes
  973. * @internal
  974. * @return mixed
  975. */
  976. private function _attributes($attributes=NULL) {
  977. if($attributes === NULL) return;
  978. if(is_string($attributes)):
  979. $split = explode(':',$attributes);
  980. if(isset($split[1])) $attributes[$split[0]] = $split[1];
  981. else $attributes = array('class' => $attributes);
  982. endif;
  983. return $attributes;
  984. }
  985. /**
  986. * Check our Markup heap for important tags. Return them if they exist.
  987. *
  988. * @param type $limit
  989. * @return type
  990. */
  991. private function _extract($limit=15) {
  992. if(\PHY\Markup::elements() > 20 && \PHY\Markup::important()):
  993. $rows = array();
  994. foreach(Markup::important() as $row) $rows[] = $row->content;
  995. \PHY\Markup::important()->rewind();
  996. $rows = array_unique($rows);
  997. $parsed = array();
  998. foreach($rows as $row) foreach(explode('{}',str_replace(array('&',',','.',':',';','?','!','- ',"'",'"'),'{}',$row)) as $item) if(strlen(trim($item)) > 2) $parsed[] = trim($item);
  999. return array_slice($parsed,0,$limit);
  1000. else:
  1001. return array();
  1002. endif;
  1003. }
  1004. /**
  1005. * Attempt to grab relevant meta tags on page generation. We use this if
  1006. * meta details weren't set.
  1007. *
  1008. * @return string
  1009. */
  1010. private function _meta() {
  1011. $this->keywords['added'] = $this->_extract(15);
  1012. $keywords = array();
  1013. foreach($this->keywords['added'] as $words):
  1014. if(!$words) continue;
  1015. $words = explode(' ',$words);
  1016. $word = NULL;
  1017. for($i = 0; $i < 3; ++$i):
  1018. if(isset($words[$i]) && strlen($words[$i]) > 3):
  1019. $word = trim($words[$i]);
  1020. $validated = strtolower(preg_replace('#[^abcdefghijklmnopqrstuvwxyz ]#i','',$word));
  1021. if(strlen($validated) > 5 && !in_array(strtolower($validated),$this->keywords['invalid'])):
  1022. if(isset($keywords[$validated])) ++$keywords[$validated];
  1023. else $keywords[$validated] = 1;
  1024. endif;
  1025. endif;
  1026. endfor;
  1027. endforeach;
  1028. arsort($keywords);
  1029. $return = array();
  1030. foreach($keywords as $word => $count) if($count > 1) $return[] = $word;
  1031. return ((count($return))
  1032. ?join(', ',$return).', '
  1033. :NULL);
  1034. }
  1035. }