PageRenderTime 53ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/system/cms/libraries/Template.php

https://github.com/abenzakour/pyrocms
PHP | 879 lines | 449 code | 139 blank | 291 comment | 45 complexity | b97f645f75d8322e9a9424337d772811 MD5 | raw file
Possible License(s): CC-BY-3.0, BSD-3-Clause
  1. <?php defined('BASEPATH') OR exit('No direct script access allowed');
  2. /**
  3. * CodeIgniter Template Class
  4. *
  5. * Build your CodeIgniter pages much easier with partials, breadcrumbs, layouts and themes
  6. *
  7. * @package CodeIgniter
  8. * @subpackage Libraries
  9. * @category Libraries
  10. * @author Philip Sturgeon
  11. * @license http://philsturgeon.co.uk/code/dbad-license
  12. * @link http://philsturgeon.co.uk/code/codeigniter-template
  13. */
  14. class Template
  15. {
  16. private $_module = '';
  17. private $_controller = '';
  18. private $_method = '';
  19. private $_theme = null;
  20. private $_theme_path = null;
  21. private $_layout = false; // By default, dont wrap the view with anything
  22. private $_layout_subdir = ''; // Layouts and partials will exist in views/layouts
  23. // but can be set to views/foo/layouts with a subdirectory
  24. private $_title = '';
  25. private $_metadata = array();
  26. private $_partials = array();
  27. private $_breadcrumbs = array();
  28. private $_title_separator = ' | ';
  29. private $_parser_enabled = true;
  30. private $_parser_body_enabled = true;
  31. private $_minify_enabled = false;
  32. private $_theme_locations = array();
  33. private $_is_mobile = false;
  34. // Seconds that cache will be alive for
  35. private $cache_lifetime = 0;//7200;
  36. private $_ci;
  37. private $_data = array();
  38. /**
  39. * Constructor - Sets Preferences
  40. *
  41. * The constructor can be passed an array of config values
  42. */
  43. public function __construct($config = array())
  44. {
  45. $this->_ci =& get_instance();
  46. if ( ! empty($config))
  47. {
  48. $this->initialize($config);
  49. }
  50. log_message('debug', 'Template class Initialized');
  51. }
  52. // --------------------------------------------------------------------
  53. /**
  54. * Initialize preferences
  55. *
  56. * @param array $config
  57. * @return void
  58. */
  59. public function initialize($config = array())
  60. {
  61. foreach ($config as $key => $val)
  62. {
  63. if ($key == 'theme' and $val != '')
  64. {
  65. $this->set_theme($val);
  66. continue;
  67. }
  68. $this->{'_'.$key} = $val;
  69. }
  70. // No locations set in config?
  71. if ($this->_theme_locations === array())
  72. {
  73. // Let's use this obvious default
  74. $this->_theme_locations = array(APPPATH . 'themes/');
  75. }
  76. // If the parse is going to be used, best make sure it's loaded
  77. if ($this->_parser_enabled === true)
  78. {
  79. $this->_ci->load->library('parser');
  80. }
  81. // Modular Separation / Modular Extensions has been detected
  82. if (method_exists( $this->_ci->router, 'fetch_module' ))
  83. {
  84. $this->_module = $this->_ci->router->fetch_module();
  85. }
  86. // What controllers or methods are in use
  87. $this->_controller = $this->_ci->router->fetch_class();
  88. $this->_method = $this->_ci->router->fetch_method();
  89. // Load user agent library if not loaded
  90. $this->_ci->load->library('user_agent');
  91. // We'll want to know this later
  92. $this->_is_mobile = $this->_ci->agent->is_mobile();
  93. }
  94. // --------------------------------------------------------------------
  95. /**
  96. * Set the module manually. Used when getting results from
  97. * another module with Modules::run('foo/bar')
  98. *
  99. * @param string $module The module slug
  100. * @return mixed
  101. */
  102. public function set_module($module)
  103. {
  104. $this->_module = $module;
  105. return $this;
  106. }
  107. // --------------------------------------------------------------------
  108. /**
  109. * Magic Get function to get data
  110. *
  111. * @param string $name
  112. * @return mixed
  113. */
  114. public function __get($name)
  115. {
  116. return isset($this->_data[$name]) ? $this->_data[$name] : null;
  117. }
  118. // --------------------------------------------------------------------
  119. /**
  120. * Magic Set function to set data
  121. *
  122. * @param string $name
  123. * @param mixed $value
  124. * @return mixed
  125. */
  126. public function __set($name, $value)
  127. {
  128. $this->_data[$name] = $value;
  129. }
  130. // --------------------------------------------------------------------
  131. /**
  132. * Set data using a chainable metod. Provide two strings or an array of data.
  133. *
  134. * @param string $name
  135. * @param mixed $value
  136. * @return object $this
  137. */
  138. public function set($name, $value = null)
  139. {
  140. // Lots of things! Set them all
  141. if (is_array($name) or is_object($name))
  142. {
  143. foreach ($name as $item => $value)
  144. {
  145. $this->_data[$item] = $value;
  146. }
  147. }
  148. // Just one thing, set that
  149. else
  150. {
  151. $this->_data[$name] = $value;
  152. }
  153. return $this;
  154. }
  155. // --------------------------------------------------------------------
  156. /**
  157. * Build the entire HTML output combining partials, layouts and views.
  158. *
  159. * @param string $view
  160. * @param array $data
  161. * @param bool $return
  162. * @param bool $IE_cache
  163. * @return string
  164. */
  165. public function build($view, $data = array(), $return = false, $IE_cache = true)
  166. {
  167. // Set whatever values are given. These will be available to all view files
  168. is_array($data) OR $data = (array) $data;
  169. // Merge in what we already have with the specific data
  170. $this->_data = array_merge($this->_data, $data);
  171. // We don't need you any more buddy
  172. unset($data);
  173. if (empty($this->_title))
  174. {
  175. $this->_title = $this->_guess_title();
  176. }
  177. // Output template variables to the template
  178. $template['title'] = strip_tags($this->_title);
  179. $template['page_title'] = $this->_title;
  180. $template['breadcrumbs'] = $this->_breadcrumbs;
  181. $template['metadata'] = $this->get_metadata() . Asset::render('extra') . $this->get_metadata('late_header');
  182. $template['partials'] = array();
  183. // Assign by reference, as all loaded views will need access to partials
  184. $this->_data['template'] =& $template;
  185. foreach ($this->_partials as $name => $partial)
  186. {
  187. // We can only work with data arrays
  188. is_array($partial['data']) OR $partial['data'] = (array) $partial['data'];
  189. // If it uses a view, load it
  190. if (isset($partial['view']))
  191. {
  192. $template['partials'][$name] = $this->_find_view($partial['view'], $partial['data']);
  193. }
  194. // Otherwise the partial must be a string
  195. else
  196. {
  197. if ($this->_parser_enabled === true)
  198. {
  199. $partial['string'] = $this->_ci->parser->parse_string($partial['string'], $this->_data + $partial['data'], true, true);
  200. }
  201. $template['partials'][$name] = $partial['string'];
  202. }
  203. }
  204. // Disable sodding IE7's constant cacheing!!
  205. // This is in a conditional because otherwise it errors when output is returned instead of output to browser.
  206. if ($IE_cache)
  207. {
  208. $this->_ci->output->set_header('Expires: Sat, 01 Jan 2000 00:00:01 GMT');
  209. $this->_ci->output->set_header('Cache-Control: no-store, no-cache, must-revalidate');
  210. $this->_ci->output->set_header('Cache-Control: post-check=0, pre-check=0, max-age=0');
  211. $this->_ci->output->set_header('Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
  212. $this->_ci->output->set_header('Pragma: no-cache');
  213. }
  214. // Let CI do the caching instead of the browser
  215. $this->cache_lifetime > 0 && $this->_ci->output->cache($this->cache_lifetime);
  216. // Test to see if this file
  217. $this->_body = $this->_find_view( $view, array(), $this->_parser_body_enabled);
  218. // Want this file wrapped with a layout file?
  219. if ($this->_layout)
  220. {
  221. // Added to $this->_data['template'] by refference
  222. $template['body'] = $this->_body;
  223. if ($this->_parser_enabled)
  224. {
  225. // Persistent tags is an experiment to parse some tags after
  226. // parsing of all other tags, so the tag persistent should be:
  227. //
  228. // a) Defined only if depends of others tags
  229. // b) Plugin that is a callback, so could retrieve runtime data.
  230. // c) Returned with a content parsed
  231. $this->_data['_tags']['persistent_tags'][] = 'template:metadata';
  232. }
  233. // Find the main body and 3rd param means parse if its a theme view (only if parser is enabled)
  234. $this->_body = self::_load_view('layouts/'.$this->_layout, $this->_data, true, self::_find_view_folder());
  235. }
  236. if ($this->_minify_enabled && function_exists('process_data_jmr1'))
  237. {
  238. $this->_body = process_data_jmr1($this->_body);
  239. }
  240. // Now that *all* parsing is sure to be done we inject the {{ noparse }} contents back into the output
  241. if (class_exists('Lex_Parser'))
  242. {
  243. $this->_body = Lex_Parser::inject_noparse($this->_body);
  244. }
  245. // Want it returned or output to browser?
  246. if ( ! $return)
  247. {
  248. $this->_ci->output->set_output($this->_body);
  249. }
  250. return $this->_body;
  251. }
  252. /**
  253. * Build the entire JSON output, setting the headers for response.
  254. *
  255. * @param array $data
  256. * @return void
  257. */
  258. public function build_json($data = array())
  259. {
  260. $this->_ci->output->set_header('Content-Type: application/json; charset=utf-8');
  261. $this->_ci->output->set_output(json_encode((object) $data));
  262. }
  263. /**
  264. * Set the title of the page
  265. *
  266. * @return object $this
  267. */
  268. public function title()
  269. {
  270. // If we have some segments passed
  271. if ($title_segments = func_get_args())
  272. {
  273. $this->_title = implode($this->_title_separator, $title_segments);
  274. }
  275. return $this;
  276. }
  277. /**
  278. * Put extra javascipt, css, meta tags, etc before all other head data
  279. *
  280. * @param string $line The line being added to head
  281. * @return object $this
  282. */
  283. public function prepend_metadata($line, $place = 'header')
  284. {
  285. //we need to declare all new key's in _metadata as an array for the unshift function to work
  286. if ( ! isset($this->_metadata[$place]))
  287. {
  288. $this->_metadata[$place] = array();
  289. }
  290. array_unshift($this->_metadata[$place], $line);
  291. return $this;
  292. }
  293. /**
  294. * Put extra javascipt, css, meta tags, etc after other head data
  295. *
  296. * @param string $line The line being added to head
  297. * @return object $this
  298. */
  299. public function append_metadata($line, $place = 'header')
  300. {
  301. $this->_metadata[$place][] = $line;
  302. return $this;
  303. }
  304. /**
  305. * Put extra javascipt, css, meta tags, etc after other head data
  306. *
  307. * @param string $line The line being added to head
  308. * @return object $this
  309. */
  310. public function append_css($files, $min_file = null, $group = 'extra')
  311. {
  312. Asset::css($files, $min_file, $group);
  313. return $this;
  314. }
  315. public function append_js($files, $min_file = null, $group = 'extra')
  316. {
  317. Asset::js($files, $min_file, $group);
  318. return $this;
  319. }
  320. /**
  321. * Set metadata for output later
  322. *
  323. * @param string $name keywords, description, etc
  324. * @param string $content The content of meta data
  325. * @param string $type Meta-data comes in a few types, links for example
  326. * @return object $this
  327. */
  328. public function set_metadata($name, $content, $type = 'meta')
  329. {
  330. $name = htmlspecialchars(strip_tags($name));
  331. $content = trim(htmlspecialchars(strip_tags($content)));
  332. // Keywords with no comments? ARG! comment them
  333. if ($name == 'keywords' and ! strpos($content, ','))
  334. {
  335. $content = preg_replace('/[\s]+/', ', ', trim($content));
  336. }
  337. switch($type)
  338. {
  339. case 'meta':
  340. $this->_metadata['header'][$name] = '<meta name="'.$name.'" content="'.$content.'" />';
  341. break;
  342. case 'link':
  343. $this->_metadata['header'][$content] = '<link rel="'.$name.'" href="'.$content.'" />';
  344. break;
  345. case 'og':
  346. $this->_metadata['header'][md5($name.$content)] = '<meta property="'.$name.'" content="'.$content.'" />';
  347. break;
  348. }
  349. return $this;
  350. }
  351. /**
  352. * Which theme are we using here?
  353. *
  354. * @param string $theme Set a theme for the template library to use
  355. * @return object $this
  356. */
  357. public function set_theme($theme = null)
  358. {
  359. $this->_theme = $theme;
  360. foreach ($this->_theme_locations as $location)
  361. {
  362. if ($this->_theme and file_exists($location.$this->_theme))
  363. {
  364. $this->_theme_path = rtrim($location.$this->_theme.'/');
  365. break;
  366. }
  367. }
  368. return $this;
  369. }
  370. /**
  371. * Get the current theme path
  372. *
  373. * @return string The current theme path
  374. */
  375. public function get_theme_path()
  376. {
  377. return $this->_theme_path;
  378. }
  379. /**
  380. * Get the current view path
  381. *
  382. * @param bool Set if should be returned the view path full (with theme path) or the view relative the theme path
  383. * @return string The current view path
  384. */
  385. public function get_views_path($relative = false)
  386. {
  387. return $relative ? substr($this->_find_view_folder(), strlen($this->get_theme_path())) : $this->_find_view_folder();
  388. }
  389. /**
  390. * Which theme layout should we using here?
  391. *
  392. * @param string $view
  393. * @param string $layout_subdir
  394. * @return object $this
  395. */
  396. public function set_layout($view, $layout_subdir = null)
  397. {
  398. $this->_layout = $view;
  399. if ($layout_subdir !== null)
  400. {
  401. $this->_layout_subdir = $layout_subdir;
  402. }
  403. return $this;
  404. }
  405. /**
  406. * Set a view partial
  407. *
  408. * @param string $name
  409. * @param string $view
  410. * @param array $data
  411. * @return object $this
  412. */
  413. public function set_partial($name, $view, $data = array())
  414. {
  415. $this->_partials[$name] = array('view' => $view, 'data' => $data);
  416. return $this;
  417. }
  418. /**
  419. * Set a view partial
  420. *
  421. * @param string $name
  422. * @param string $string
  423. * @param array $data
  424. * @return object $this
  425. */
  426. public function inject_partial($name, $string, $data = array())
  427. {
  428. $this->_partials[$name] = array('string' => $string, 'data' => $data);
  429. return $this;
  430. }
  431. /**
  432. * Helps build custom breadcrumb trails
  433. *
  434. * @param string $name What will appear as the link text
  435. * @param string $uri The URL segment
  436. * @return object $this
  437. */
  438. public function set_breadcrumb($name, $uri = '', $reset = false)
  439. {
  440. // perhaps they want to start over
  441. if ($reset)
  442. {
  443. $this->_breadcrumbs = array();
  444. }
  445. $this->_breadcrumbs[] = array('name' => $name, 'uri' => $uri );
  446. return $this;
  447. }
  448. /**
  449. * Set a the cache lifetime
  450. *
  451. * @param int $seconds
  452. * @return object $this
  453. */
  454. public function set_cache($seconds = 0)
  455. {
  456. $this->cache_lifetime = $seconds;
  457. return $this;
  458. }
  459. /**
  460. * enable_minify
  461. * Should be minify used or the output html files just delivered normally?
  462. *
  463. * @param bool $bool
  464. * @return object $this
  465. */
  466. public function enable_minify($bool)
  467. {
  468. $this->_minify_enabled = $bool;
  469. return $this;
  470. }
  471. /**
  472. * enable_parser
  473. * Should be parser be used or the view files just loaded normally?
  474. *
  475. * @param bool $bool
  476. * @return object $this
  477. */
  478. public function enable_parser($bool)
  479. {
  480. $this->_parser_enabled = $bool;
  481. return $this;
  482. }
  483. /**
  484. * enable_parser_body
  485. * Should be parser be used or the body view files just loaded normally?
  486. *
  487. * @param bool $bool
  488. * @return object $this
  489. */
  490. public function enable_parser_body($bool)
  491. {
  492. $this->_parser_body_enabled = $bool;
  493. return $this;
  494. }
  495. /**
  496. * theme_locations
  497. * List the locations where themes may be stored
  498. *
  499. * @return array
  500. */
  501. public function theme_locations()
  502. {
  503. return $this->_theme_locations;
  504. }
  505. /**
  506. * add_theme_location
  507. * Set another location for themes to be looked in
  508. *
  509. * @access public
  510. * @param string $location
  511. * @return array
  512. */
  513. public function add_theme_location($location)
  514. {
  515. $this->_theme_locations[] = $location;
  516. }
  517. /**
  518. * theme_exists
  519. * Check if a theme exists
  520. *
  521. * @param string $theme
  522. * @return bool
  523. */
  524. public function theme_exists($theme = null)
  525. {
  526. $theme OR $theme = $this->_theme;
  527. foreach ($this->_theme_locations as $location)
  528. {
  529. if (is_dir($location.$theme))
  530. {
  531. return true;
  532. }
  533. }
  534. return false;
  535. }
  536. /**
  537. * get_layouts
  538. * Get all current layouts (if using a theme you'll get a list of theme layouts)
  539. *
  540. * @return array
  541. */
  542. public function get_layouts()
  543. {
  544. $layouts = array();
  545. foreach(glob(self::_find_view_folder().'layouts/*.*') as $layout)
  546. {
  547. $layouts[] = pathinfo($layout, PATHINFO_BASENAME);
  548. }
  549. return $layouts;
  550. }
  551. public function get_metadata($place = 'header')
  552. {
  553. return isset($this->_metadata[$place]) && is_array($this->_metadata[$place])
  554. ? implode("\n\t\t", $this->_metadata[$place]) : null;
  555. }
  556. /**
  557. * get_layouts
  558. * Get all current layouts (if using a theme you'll get a list of theme layouts)
  559. *
  560. * @param string $theme
  561. * @return array
  562. */
  563. public function get_theme_layouts($theme = null)
  564. {
  565. $theme OR $theme = $this->_theme;
  566. $layouts = array();
  567. foreach ($this->_theme_locations as $location)
  568. {
  569. // Get special web layouts
  570. if( is_dir($location.$theme.'/views/web/layouts/') )
  571. {
  572. foreach(glob($location.$theme . '/views/web/layouts/*.*') as $layout)
  573. {
  574. $layouts[] = pathinfo($layout, PATHINFO_BASENAME);
  575. }
  576. break;
  577. }
  578. // So there are no web layouts, assume all layouts are web layouts
  579. if(is_dir($location.$theme.'/views/layouts/'))
  580. {
  581. foreach(glob($location.$theme . '/views/layouts/*.*') as $layout)
  582. {
  583. $layouts[] = pathinfo($layout, PATHINFO_BASENAME);
  584. }
  585. break;
  586. }
  587. }
  588. return $layouts;
  589. }
  590. /**
  591. * layout_exists
  592. * Check if a theme layout exists
  593. *
  594. * @param string $layout
  595. * @return bool
  596. */
  597. public function layout_exists($layout)
  598. {
  599. // If there is a theme, check it exists in there
  600. if ( ! empty($this->_theme) and in_array($layout, self::get_theme_layouts()))
  601. {
  602. return true;
  603. }
  604. // Otherwise look in the normal places
  605. return file_exists(self::_find_view_folder().'layouts/' . $layout . self::_ext($layout));
  606. }
  607. /**
  608. * layout_is
  609. * Check if the current theme layout is equal the $layout argument
  610. *
  611. * @param string $layout
  612. * @return bool
  613. */
  614. public function layout_is($layout)
  615. {
  616. return $layout === $this->_layout;
  617. }
  618. // find layout files, they could be mobile or web
  619. private function _find_view_folder()
  620. {
  621. if (isset($this->_ci->load->_ci_cached_vars['template_views']))
  622. {
  623. return $this->_ci->load->_ci_cached_vars['template_views'];
  624. }
  625. // Base view folder
  626. $view_folder = APPPATH.'views/';
  627. // Using a theme? Put the theme path in before the view folder
  628. if ( ! empty($this->_theme))
  629. {
  630. $view_folder = $this->_theme_path.'views/';
  631. }
  632. // Would they like the mobile version?
  633. if ($this->_is_mobile === true and is_dir($view_folder.'mobile/'))
  634. {
  635. // Use mobile as the base location for views
  636. $view_folder .= 'mobile/';
  637. }
  638. // Use the web version
  639. else if (is_dir($view_folder.'web/'))
  640. {
  641. $view_folder .= 'web/';
  642. }
  643. // Things like views/admin/web/view admin = subdir
  644. if ($this->_layout_subdir)
  645. {
  646. $view_folder .= $this->_layout_subdir.'/';
  647. }
  648. // If using themes store this for later, available to all views
  649. return $this->_ci->load->_ci_cached_vars['template_views'] = $view_folder;
  650. }
  651. // A module view file can be overriden in a theme
  652. private function _find_view($view, array $data, $parse_view = true)
  653. {
  654. // Only bother looking in themes if there is a theme
  655. if ( ! empty($this->_theme))
  656. {
  657. $location = $this->get_theme_path();
  658. $theme_views = array(
  659. $this->get_views_path(true) . 'modules/' . $this->_module . '/' . $view,
  660. // This allows build('pages/page') to still overload same as build('page')
  661. $this->get_views_path(true) . 'modules/' . $view,
  662. $this->get_views_path(true) . $view
  663. );
  664. foreach ($theme_views as $theme_view)
  665. {
  666. if (file_exists($location . $theme_view . self::_ext($theme_view)))
  667. {
  668. return self::_load_view($theme_view, $this->_data + $data, $parse_view, $location);
  669. }
  670. }
  671. }
  672. // Not found it yet? Just load, its either in the module or root view
  673. return self::_load_view($view, $this->_data + $data, $parse_view);
  674. }
  675. private function _load_view($view, array $data, $parse_view = true, $override_view_path = null)
  676. {
  677. // Sevear hackery to load views from custom places AND maintain compatibility with Modular Extensions
  678. if ($override_view_path !== null)
  679. {
  680. if ($this->_parser_enabled === true and $parse_view === true)
  681. {
  682. // Load content and pass through the parser
  683. $content = $this->_ci->parser->parse_string($this->_ci->load->_ci_load(array(
  684. '_ci_path' => $override_view_path.$view.self::_ext($view),
  685. '_ci_vars' => $data,
  686. '_ci_return' => true
  687. )), $data, true);
  688. }
  689. else
  690. {
  691. // Load it directly, bypassing $this->load->view() as ME resets _ci_view
  692. $content = $this->_ci->load->_ci_load(array(
  693. '_ci_path' => $override_view_path.$view.self::_ext($view),
  694. '_ci_vars' => $data,
  695. '_ci_return' => true
  696. ));
  697. }
  698. }
  699. // Can just run as usual
  700. else
  701. {
  702. // Grab the content of the view (parsed or loaded)
  703. $content = ($this->_parser_enabled === true AND $parse_view === true)
  704. // Parse that bad boy
  705. ? $this->_ci->parser->parse($view, $data, true )
  706. // None of that fancy stuff for me!
  707. : $this->_ci->load->view($view, $data, true );
  708. }
  709. return $content;
  710. }
  711. private function _guess_title()
  712. {
  713. $this->_ci->load->helper('inflector');
  714. // Obviously no title, lets get making one
  715. $title_parts = array();
  716. // If the method is something other than index, use that
  717. if ($this->_method != 'index')
  718. {
  719. $title_parts[] = $this->_method;
  720. }
  721. // Make sure controller name is not the same as the method name
  722. if ( ! in_array($this->_controller, $title_parts))
  723. {
  724. $title_parts[] = $this->_controller;
  725. }
  726. // Is there a module? Make sure it is not named the same as the method or controller
  727. if ( ! empty($this->_module) and !in_array($this->_module, $title_parts))
  728. {
  729. $title_parts[] = $this->_module;
  730. }
  731. // Glue the title pieces together using the title separator setting
  732. $title = humanize(implode($this->_title_separator, $title_parts));
  733. return $title;
  734. }
  735. private function _ext($file)
  736. {
  737. return pathinfo($file, PATHINFO_EXTENSION) ? '' : EXT;
  738. }
  739. }
  740. // END Template class