diff --git a/public/include/autoloader.inc.php b/public/include/autoloader.inc.php index 99ec89a4..a4663223 100644 --- a/public/include/autoloader.inc.php +++ b/public/include/autoloader.inc.php @@ -30,6 +30,7 @@ if ($detect->isMobile() && $setting->getValue('website_mobile_theme')) { } define('THEME', $theme); +//Required for Smarty require_once(CLASS_DIR . '/template.class.php'); // Load smarty now that we have our theme defined require_once(INCLUDE_DIR . '/smarty.inc.php'); diff --git a/public/include/classes/template.class.php b/public/include/classes/template.class.php index f57818ae..007ddd4b 100644 --- a/public/include/classes/template.class.php +++ b/public/include/classes/template.class.php @@ -6,6 +6,17 @@ if (!defined('SECURITY')) class Template extends Base { protected $table = 'templates'; + /** + * Get filepath for template name based on current PAGE and ACTION + */ + public function getFullpath($name) { + $chunks = array(PAGE); + if( ACTION ) + $chunks[] = ACTION; + $chunks[] = $name; + + return join('/', $chunks); + } /** * Get all available themes @@ -23,6 +34,41 @@ class Template extends Base { return $aThemes; } + /** + * Cached getActiveTemplates method + * + * @see getActiveTemplates + */ + private static $active_templates; + public function cachedGetActiveTemplates() { + if ( is_null(self::$active_templates) ) { + self::$active_templates = $this->getActiveTemplates(); + } + return self::$active_templates; + } + /** + * Return the all active templates as hash, + * where key is template and value is modified_at + * + * @return array - list of active templates + */ + public function getActiveTemplates() { + $this->debug->append("STA " . __METHOD__, 4); + $stmt = $this->mysqli->prepare("SELECT template, modified_at FROM $this->table WHERE active = 1"); + if ($stmt && $stmt->execute() && $result = $stmt->get_result()) { + $rows = $result->fetch_all(MYSQLI_ASSOC); + $hash = array(); + foreach($rows as $row) { + $hash[$row['template']] = strtotime($row['modified_at']); + } + return $hash; + } + + $this->setErrorMessage('Failed to get active templates'); + $this->debug->append('Template::getActiveTemplates failed: ' . $this->mysqli->error); + return false; + } + /** * Return the content of specific template file * @@ -57,15 +103,15 @@ class Template extends Base { } /** - * Return specific template form database + * Return specific template from database * * @param $template - name (filepath) of the template * @return array - result from database */ - public function getEntry($template) { + public function getEntry($template, $columns = "*") { $this->debug->append("STA " . __METHOD__, 4); - $stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE template = ?"); + $stmt = $this->mysqli->prepare("SELECT $columns FROM $this->table WHERE template = ?"); if ($stmt && $stmt->bind_param('s', $template) && $stmt->execute() && $result = $stmt->get_result()) return $result->fetch_assoc(); @@ -74,6 +120,22 @@ class Template extends Base { return false; } + /** + * Return last modified time of specific template from database + * + * @param $template - name (filepath) of the template + * @return timestamp - last modified time of template + */ + public function getEntryMTime($template) { + $this->debug->append("STA " . __METHOD__, 4); + + $entry = $this->getEntry($template, "modified_at, active"); + if ( $entry && $entry['active']) + return strtotime($entry['modified_at']); + + return false; + } + /** * Update template in database * diff --git a/public/include/smarty.inc.php b/public/include/smarty.inc.php index dfd1f4f6..20344ccb 100644 --- a/public/include/smarty.inc.php +++ b/public/include/smarty.inc.php @@ -10,6 +10,143 @@ define('SMARTY_DIR', INCLUDE_DIR . '/smarty/libs/'); // Include the actual smarty class file include(SMARTY_DIR . 'Smarty.class.php'); +/** + * Custom Smarty Template Resource for Pages + * Get templates from Database + * Allow admin to manage his templates from Backoffice + */ +class Smarty_Resource_Database extends Smarty_Resource_Custom { + protected $template; + + public function __construct($template) { + $this->template = $template; + } + /** + * Fetch a template and its modification time from database + * + * @param string $name template name + * @param string $source template source + * @param integer $mtime template modification timestamp (epoch) + * @return void + */ + protected function fetch($name, &$source, &$mtime) { + $oTemplate = $this->template->getEntry($this->fullTemplateName($name)); + if ( $oTemplate && $oTemplate['active'] ) { + $source = $oTemplate['content']; + $mtime = strtotime($oTemplate['modified_at']); + } else { + $source = null; + $mtime = null; + } + } + + /** + * Fetch a template's modification time from database + * + * @note implementing this method is optional. Only implement it if modification times can be accessed faster than loading the comple template source. + * @param string $name template name + * @return integer timestamp (epoch) the template was modified + */ + protected function fetchTimestamp($name) { + $templates = $this->template->cachedGetActiveTemplates(); + $mtime = @$templates[$this->fullTemplateName($name)]; + return $mtime ? $mtime : false; + } + + /** + * Prepend THEME name to template name to get valid DB primary key + * + * @param string $name template name + */ + protected function fullTemplateName($name) { + return $this->normalisePath(THEME . "/" . $name); + } + + /** + * Normalise a file path string so that it can be checked safely. + * + * Attempt to avoid invalid encoding bugs by transcoding the path. Then + * remove any unnecessary path components including '.', '..' and ''. + * + * @param $path string + * The path to normalise. + * @return string + * The path, normalised. + * @see https://gist.github.com/thsutton/772287 + */ + protected function normalisePath($path) { + // Process the components + $parts = explode('/', $path); + $safe = array(); + foreach ($parts as $idx => $part) { + if (empty($part) || ('.' == $part)) { + continue; + } elseif ('..' == $part) { + array_pop($safe); + continue; + } else { + $safe[] = $part; + } + } + // Return the "clean" path + $path = implode(DIRECTORY_SEPARATOR, $safe); + return $path; + } + +} + +class Smarty_Resource_Hybrid extends Smarty_Resource { + + protected $databaseResource; + + protected $fileResource; + + public function __construct($dbResource, $fileResource) { + $this->databaseResource = $dbResource; + $this->fileResource = $fileResource; + } + + /** + * populate Source Object with meta data from Resource + * + * @param Smarty_Template_Source $source source object + * @param Smarty_Internal_Template $_template template object + */ + public function populate(Smarty_Template_Source $source, Smarty_Internal_Template $_template=null) { + $this->databaseResource->populate($source, $_template); + if ( !$source->exists ) { + $source->type = 'file'; + return $this->fileResource->populate($source, $_template); + } + } + + /** + * Load template's source into current template object + * + * @param Smarty_Template_Source $source source object + * @return string template source + * @throws SmartyException if source cannot be loaded + */ + public function getContent(Smarty_Template_Source $source) { + try { + return $this->databaseResource->getContent($source); + } catch(SmartyException $e) { + return $this->fileResource->getContent($source); + } + } + + /** + * Determine basename for compiled filename + * + * @param Smarty_Template_Source $source source object + * @return string resource's basename + */ + public function getBasename(Smarty_Template_Source $source) { + return $this->fileResource->getBasename($source); + } + +} + // We initialize smarty here $debug->append('Instantiating Smarty Object', 3); $smarty = new Smarty; @@ -18,6 +155,11 @@ $smarty = new Smarty; $debug->append('Define Smarty Paths', 3); $smarty->template_dir = BASEPATH . 'templates/' . THEME . '/'; $smarty->compile_dir = BASEPATH . 'templates/compile/'; +$smarty->registerResource('hybrid', new Smarty_Resource_Hybrid( + new Smarty_Resource_Database($template), + new Smarty_Internal_Resource_File() +)); +$smarty->default_resource_type = "hybrid"; $smarty_cache_key = md5(serialize($_REQUEST) . serialize(@$_SESSION['USERDATA']['id'])); // Optional smarty caching, check Smarty documentation for details diff --git a/public/index.php b/public/index.php index db94059e..5a5c8d73 100644 --- a/public/index.php +++ b/public/index.php @@ -80,6 +80,9 @@ if (!empty($action)) { require_once(PAGES_DIR . '/' . $arrPages[$page]); } +define('PAGE', $page); +define('ACTION', $action); + // For our content inclusion $smarty->assign("PAGE", $page); $smarty->assign("ACTION", $action);