. * */ namespace Vvveb\System\Core; use function Vvveb\__; use function Vvveb\addTranslationDomain; use function Vvveb\config; use function Vvveb\isEditor; use Vvveb\System\Component\Component; use Vvveb\System\Event; use Vvveb\System\Sites; include DIR_SYSTEM . 'vtpl' . DS . 'vtpl.php'; #[\AllowDynamicProperties] class View { static private $instance; private $templatePath; private $theme; private $htmlPath; private $template; private $tplFile; private $templateEngine; private $compiledTemplate; private $serviceTemplate; private $component; private $componentCount; private $componentContent; private $html; private $useComponent; private $isEditor = false; private $_type = 'html'; function getTemplateEngineInstance() { if (! $this->templateEngine) { self :: getInstance(); } return $this->templateEngine; } function __construct($app = null) { //$this->theme = \Vvveb\config($this->app . '.theme', 'default'); $this->app = $app ?? APP; $relativePath = PUBLIC_PATH . 'themes' . DS; if ($this->app == 'app') { $this->theme = Sites::getTheme(SITE_ID) ?? 'default'; $themePath = DIR_ROOT . join(DS, ['public', 'themes']) . DS . $this->theme . DS; } else { $this->theme = config($this->app . '.theme', 'default'); $relativePath = PUBLIC_PATH . $this->app . DS; $themePath = DIR_ROOT . DS . 'public' . DS . $this->app . DS . $this->theme . DS; } if (isEditor()) { $this->isEditor = true; } /* $domain = $this->theme . '-domain'; addTranslationDomain($domain); */ $this->htmlPath = $themePath; $this->templatePath = $themePath; //\Vvveb\config($this->app . '.theme', 'default') . DS; if (isset($_REQUEST['_component_ajax']) && $this->isEditor) { $this->component = \Vvveb\filter('/[a-z\-]+/', $_REQUEST['_component_ajax'], 80); $this->componentCount = \Vvveb\filter('/\d+/', $_REQUEST['_component_id'], 4); //$this->componentCount = 0; //if (isset($_REQUEST['_server_template'])) { $this->componentContent = $_POST['_component_content'] ?? ''; $this->html = $_POST['html'] ?? ''; //} } $selector = $count = null; if (isset($this->component)) { $selector = '[data-v-component-' . $this->component . ']'; } $templateEngine = 'Vtpl'; $template = new $templateEngine($selector, $this->componentCount, $this->componentContent); $template->addTemplatePath(DIR_ROOT . APP . DS . 'template' . DS); $template->setHtmlPath($this->htmlPath); if ($this->isEditor) { $this->isEditor = true; $template->removeVattrs(false); } else { //$template->removeVattrs(true); $template->setRelativePath($relativePath . $this->theme . DS); } $this->templateEngine = $template; } static function getInstance() { if (self :: $instance === NULL) { return self :: $instance = new self(); //create class instance } return self :: $instance; } function removeVattrs($flag = true) { return $this->templateEngine->removeVattrs($flag); } function setTheme($theme = false) { $relativePath = PUBLIC_PATH . 'themes' . DS; if (! $theme) { if ($this->app == 'app') { $this->theme = Sites::getTheme(SITE_ID) ?? 'default'; $themePath = DIR_ROOT . join(DS, ['public', 'themes']) . DS . $this->theme . DS; } else { $this->theme = config($this->app . '.theme', 'default'); $relativePath = PUBLIC_PATH . $this->app . DS; $themePath = DIR_ROOT . DS . 'public' . DS . $this->app . DS . $this->theme . DS; } } else { $theme = \Vvveb\filter('/[a-z0-9_-]*/', $theme, 30); $this->theme = $theme; $themePath = DIR_ROOT . join(DS, ['public', 'themes']) . DS . $this->theme . DS; } $this->htmlPath = $themePath; $this->templatePath = $themePath; $this->templateEngine->setHtmlPath($this->htmlPath); if (! $this->isEditor) { $this->templateEngine->setRelativePath($relativePath . $this->theme . DS); } } function getTheme() { return $this->theme; } function getDocumentType() { return $this->templateEngine->getDocumentType(); } function getTemplatePath() { return $this->templatePath; } function compiledTemplate() { return $this->compiledTemplate; } function set($data) { if ($data && is_array($data)) { foreach ($data as $key => &$value) { $this->$key = $value; } } } function serviceTemplate() { return $this->serviceTemplate; } function checkNeedRecompile($service = false) { if (! $this->template) { return false; } $this->template = str_replace(['/', '\\'], DS, $this->template); $templatePath = $this->templatePath; $template = $this->template; $templateMtime = null; $templateFile = $templatePath . $template; $html = DIR_ROOT . APP . DS . 'template' . DS . $this->tplFile; //absolute path if ($this->template[0] == DS) { $templatePath = dirname($this->template) . DS; $template = basename($this->template); $templateFile = $this->template; } else { if (strpos($this->template, 'plugins' . DS) === 0) { $templatePath = DIR_PUBLIC . 'plugins' . DS; //$templatePath = $this->templatePath = DIR_PUBLIC . 'plugins' . DS; $template = str_replace('plugins' . DS, '', $template); $p = strpos($template, DS); $pluginName = substr($template, 0, $p); $nameSpace = substr($template, $p + 1); if ($this->app == 'admin') { $tpl = $pluginName . DS . join(DS, [$this->app, 'template']) . DS . $nameSpace; $template = $pluginName . DS . $this->app . DS . $nameSpace; } else { $tpl = $pluginName . DS . join(DS, [$this->app, 'template']) . DS . $nameSpace; $template = $pluginName . DS . $nameSpace; } $this->tplFile = str_replace(['.html', '.xml', '.json'], '.tpl', $tpl); $html = DIR_PLUGINS . $this->tplFile; $templateFile = $templatePath . $template; } } if (! file_exists($templatePath . $template)) { if ($template == 'error404.html' || $template == 'error500.html') { //if theme is missing error page then use the default $templateFile = DIR_PUBLIC . $template; } else { FrontController::notFound(true, [ 'message' => __('Html template not found!'), 'file' => $templatePath . $template, ]); } } //$this->tplFile = $templateFile; $this->templatePath = $templatePath; if (file_exists($templateFile)) { $templateMtime = filemtime($templateFile); } $htmlMtime = 0; if (file_exists($html)) { $htmlMtime = filemtime($html); } $compiledMtime = 0; if (file_exists($this->compiledTemplate)) { $compiledMtime = @filemtime($this->compiledTemplate); } if ((max($templateMtime, $htmlMtime) > $compiledMtime || ! file_exists($this->compiledTemplate)) /*|| (defined('DEBUG') && DEBUG) */ || isset($_POST['_component_content'])) { $this->compile($templateFile, $this->compiledTemplate, $service); } } private function compile($filename, $file, $service = false) { //@touch($file); //if recompiling takes longer avoid avoid other recompile requests //regenerate component file if ($this->useComponent && ! defined('CLI')) { //regenerate components cache if (! $service) { if ($this->html) { $html = $this->html; } else { $html = $this->componentContent; } $service = Component::getInstance($this, true, $html); } } if (is_dir($this->htmlPath . 'template')) { $this->templateEngine->addTemplatePath($this->htmlPath . 'template' . DS); } list($this->template, $filename, $this->tplFile) = Event :: trigger(__CLASS__,__FUNCTION__, $this->template, $filename, $this->tplFile, $this->templateEngine, $this); if ($this->html) { $errors = $this->templateEngine->loadHtml($this->html); } else { $errors = $this->templateEngine->loadHtmlTemplate($filename); } //if no template defined use the default if ($this->tplFile[0] == DS) { $this->tplFile = DIR_ROOT . APP . DS . 'template' . DS . 'common.tpl'; $this->templateEngine->loadTemplateFile($this->tplFile); } else { if (strpos($this->template, 'plugins' . DS) === 0) { //$template = str_replace('plugins' . DS, '', $this->template); $template = $this->tplFile; $p = strpos($template, DS); $pluginName = substr($template, 0, $p); $nameSpace = substr($template, $p + 1); if (file_exists(DIR_PLUGINS . $this->tplFile)) { $this->tplFile = DIR_PLUGINS . $this->tplFile; } else { $this->tplFile = DIR_ROOT . APP . DS . 'template' . DS . 'common.tpl'; } //$this->tplFile = $pluginName . DS . $this->app . DS . 'template' . DS . $nameSpace; $this->templateEngine->loadTemplateFile($this->tplFile); /* if ($this->app == 'admin') { $this->tplFile = $pluginName . DS . $this->app ."/template/$nameSpace"; } else { $this->tplFile = "$pluginName/admin/template/$nameSpace"; }*/ } else { if (! file_exists(DIR_ROOT . APP . DS . 'template' . DS . $this->tplFile)) { $this->tplFile = 'common.tpl'; } $this->templateEngine->loadTemplateFileFromPath($this->tplFile); } } list($this->template, $filename, $this->tplFile) = Event :: trigger(__CLASS__,__FUNCTION__ . ':after', $this->template, $filename, $this->tplFile, $this->templateEngine, $this); $this->templateEngine->saveCompiledTemplate($file); } function tplFile($filename) { $this->tplFile = $filename; } function template($filename = null) { if ($filename === false) { $this->template = false; return; } if ($filename) { $filename = str_replace('..', '', $filename); $compiledFilename = DIR_COMPILED_TEMPLATES . $this->app . '_' . (defined('SITE_ID') ? SITE_ID : '-') . '_' . ((is_null($this->component)) ? '' : $this->component . $this->componentCount . '_') . $this->theme . '_' . str_replace([DS, '/', '\\'] , '_', $filename) . ($this->isEditor ? '-edit' : ''); list($filename, $compiledFilename) = Event :: trigger(__CLASS__,__FUNCTION__, $filename, $compiledFilename, $this); $this->compiledTemplate = $compiledFilename; $this->serviceTemplate = $compiledFilename; $this->tplFile = str_replace(['.html', '.xml', '.json'], '.tpl', $filename); $this->template = $filename; } return $this->template; } function fragment($selector, $index = 0) { $this->component = $selector; $this->componentCount = $index; } function setType($type) { $this->_type = $type; } function getType() { return $this->_type; } function render($useComponent = true, $output = true, $service = false) { Event :: trigger(__CLASS__,__FUNCTION__, $this->template, $this->tplFile, $this->templateEngine, $this); $this->useComponent = $useComponent; if ($useComponent && ! defined('CLI')) { if ($service) { $service = new Component($this, $this->componentContent ? true : false, $this->componentContent); } else { $service = Component::getInstance($this, $this->componentContent ? true : false, $this->componentContent); } } /* if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest' && ! isset($_REQUEST['_component_ajax'])) { $this->_type = 'json'; }*/ if ($this->_type == 'text') { if (isset($this->text)) { if ($output) { echo $this->text; } else { return $this->text; } } return; } if ($this->_type == 'json') { $jsonFlags = 0; //if (defined('CLI')) { $jsonFlags = JSON_PRETTY_PRINT; //} ob_start(); if (isset($this->json)) { $json = json_encode($this->json, $jsonFlags); } else { $json = json_encode($this, $jsonFlags); } if ($output) { echo $json; } else { return $json; } } else { //html if (! $this->template) { self::template(); } $this->checkNeedRecompile($service); if ($this->compiledTemplate) { if (! file_exists($this->compiledTemplate)) { return FrontController::notFound(true, [ 'message' => __('Compiled template not found!'), 'file' => $templatePath . $template, ]); } try { if ($output) { include $this->compiledTemplate; } else { ob_start(); include $this->compiledTemplate; $return = ob_get_contents(); ob_end_clean(); return $return; } } catch (\ParseError | \Error $e) { $data = \Vvveb\System\Core\exceptionToArray($e, $this->compiledTemplate); return FrontController :: notFound(false, $data, 500); } } } } }