PageRenderTime 59ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/application/libraries/Template.php

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