diff --git a/docs/TODO.md b/docs/TODO.md
index f980ae5..14544b3 100644
--- a/docs/TODO.md
+++ b/docs/TODO.md
@@ -21,7 +21,7 @@
[.] → Models
- [ ] → Views (Twig/Liquid/PHP)
+ [x] → Views (Twig/Liquid/PHP)
[x] → JavaScript/CSS Asset loading
diff --git a/src/bootstrap/requires.php b/src/bootstrap/requires.php
index 2e5cdc7..b0a0620 100644
--- a/src/bootstrap/requires.php
+++ b/src/bootstrap/requires.php
@@ -117,6 +117,7 @@ final class requires {
$file_name = common::get_string_left($file, $pos);
$file_kind = common::get_string_right($file, common::string_length($file) - $pos);
$file_type = match ($file_kind) {
+ ".twig" => ".twig",
".tpl" => ".tpl",
default => ".php",
};
diff --git a/src/classes/enums/view_type.php b/src/classes/enums/view_type.php
index 2119660..b4b7b1f 100644
--- a/src/classes/enums/view_type.php
+++ b/src/classes/enums/view_type.php
@@ -13,4 +13,8 @@ enum view_type: string {
case LIQUID = ".tpl";
case TWIG = ".twig";
case PHP = ".php";
+
+ public static function get_file_extension_for(view_type $type): string {
+ return $type->value;
+ }
}
\ No newline at end of file
diff --git a/src/classes/php_file_cache.php b/src/classes/php_file_cache.php
new file mode 100644
index 0000000..91df421
--- /dev/null
+++ b/src/classes/php_file_cache.php
@@ -0,0 +1,35 @@
+
+ * @copyright (c) 2025, Robert Strutts
+ * @license MIT
+ */
+
+class php_file_cache {
+ protected $cache_path;
+
+ public function __construct($path) {
+ $this->cache_path = rtrim($path, '/') . '/';
+ if (!is_dir($this->cache_path)) {
+ mkdir($this->cache_path, 0775, true);
+ }
+ }
+
+ public function get($key) {
+ $file = $this->cache_path . md5($key) . '.cache.php';
+ if (file_exists($file)) {
+ return unserialize(file_get_contents($file));
+ }
+ return null;
+ }
+
+ public function set($key, $value) {
+ $file = $this->cache_path . md5($key) . '.cache.php';
+ file_put_contents($file, serialize($value));
+ }
+}
diff --git a/src/classes/services/liquid_templates.php b/src/classes/services/liquid_templates.php
new file mode 100644
index 0000000..7b40da1
--- /dev/null
+++ b/src/classes/services/liquid_templates.php
@@ -0,0 +1,78 @@
+
+ * @copyright (c) 2025, Robert Strutts
+ * @license MIT
+ */
+
+namespace CodeHydrater\services;
+
+use CodeHydrater\bootstrap\registry as Reg;
+use CodeHydrater\php_file_cache as FileCache;
+use Liquid\{Liquid, Template, Context};
+use Liquid\Cache\Local;
+
+final class liquid_templates {
+ private $use_local_cache = false;
+ private $liquid;
+ private $dir;
+ private $extension = 'tpl';
+
+ public function __construct(string $template_extension = 'tpl') {
+ $this->extension = $template_extension;
+ if (! Reg::get('loader')->is_loaded('Liquid')) {
+ Reg::get('loader')->add_namespace('Liquid', CodeHydrater_PROJECT . 'vendor/liquid/liquid/src/Liquid');
+ }
+
+ Liquid::set('INCLUDE_SUFFIX', $template_extension);
+ Liquid::set('INCLUDE_PREFIX', '');
+
+ $this->dir = CodeHydrater_PROJECT . 'views/liquid';
+ $this->liquid = new Template($this->dir);
+ }
+
+ public function whitespace_control() {
+ /* Force whitespace control to be used by switching tags from {%- to {%
+ * Also, will error out if you try {%-
+ * Need to figure out how to turn back on whitespaces with {%@ errors! not important...
+ */
+ Liquid::set('TAG_START', '(?:{%@)|\s*{%');
+ Liquid::set('TAG_END', '(?:@%}|%}\s*)');
+ }
+
+ public function parse(string $source) {
+ return $this->liquid->parse($source);
+ }
+
+ public function parse_file(string $file) {
+ $safe_file = \CodeHydrater\security::filter_uri($file);
+ if ($this->use_local_cache) {
+ $cache = new FileCache(BaseDir . "/protected/runtime/liquid_cache");
+
+ $templateName = $safe_file . "." . $this->extension;
+ $templatePath = $this->dir . "/". $safe_file . "." . $this->extension;
+
+ $templateSource = file_get_contents($templatePath);
+ $cached = $cache->get($templateName);
+ if (!$cached) {
+ $this->liquid->parseFile($safe_file);
+ $cache->set($templateName, $this->liquid);
+ } else {
+ $this->liquid = $cached;
+ }
+ } else {
+ $this->liquid->parseFile($safe_file);
+ }
+ }
+
+ public function render(array $assigns = array(), $filters = null, array $registers = array()): string {
+ return $this->liquid->render($assigns, $filters, $registers);
+ }
+
+ public function get_engine() {
+ return $this->liquid;
+ }
+}
\ No newline at end of file
diff --git a/src/classes/services/twig.php b/src/classes/services/twig.php
new file mode 100644
index 0000000..2f2965f
--- /dev/null
+++ b/src/classes/services/twig.php
@@ -0,0 +1,23 @@
+
+ * @copyright (c) 2025, Robert Strutts
+ * @license MIT
+ */
+namespace CodeHydrater\services;
+
+class twig {
+ public static function init() {
+ \CodeHydrater\bootstrap\registry::get('loader')->add_namespace("Twig", CodeHydrater_PROJECT. "/vendor/twig/twig/src");
+ \CodeHydrater\bootstrap\registry::get('loader')->add_namespace("Symfony\Polyfill\Mbstring", CodeHydrater_PROJECT. "/vendor/symfony/polyfill-mbstring");
+ \CodeHydrater\bootstrap\registry::get('loader')->add_namespace("Symfony\Polyfill\Ctype", CodeHydrater_PROJECT. "/vendor/symfony/polyfill-ctype");
+ $loader = new \Twig\Loader\FilesystemLoader(CodeHydrater_PROJECT. "views/twig");
+ $twig = new \Twig\Environment($loader, [
+ 'cache' => BaseDir . "/protected/runtime/compilation_cache",
+ ]);
+ return $twig;
+ }
+}
diff --git a/src/classes/view.php b/src/classes/view.php
index b72437b..51ba69f 100644
--- a/src/classes/view.php
+++ b/src/classes/view.php
@@ -10,23 +10,23 @@ declare(strict_types=1);
namespace CodeHydrater;
+use \CodeHydrater\enums\view_type as ViewType;
+
final class view {
public $white_space_control = false;
public $page_output;
private $vars = [];
- private $project_dir = ""; // Not used anymore
private $files = [];
private $template = false;
private $use_template_engine = false;
+ private $use_template_engine_twig = false;
+ private $use_template_engine_liquid = false;
private $template_type = 'tpl';
- protected $tempalte_engine = null;
+ protected $tempalte_engine_twig = null;
+ protected $tempalte_engine_liquid = null;
private function get_file(string $view_file, string $default): string {
- $file_ext = bootstrap\common::get_string_right($view_file, 4);
- if (! bootstrap\common::is_string_found($file_ext, '.')) {
- $view_file .= '.php';
- }
- $file = (empty($default)) ? "{$this->project_dir}/views/{$view_file}" : "{$this->project_dir}/views/{$default}/{$view_file}";
+ $file = (empty($default)) ? "views/{$view_file}" : "views/{$default}/{$view_file}";
$path = bootstrap\site_helper::get_root();
$vf = $path . $file;
if ( bootstrap\requires::safer_file_exists($vf) !== false) {
@@ -38,18 +38,23 @@ final class view {
/**
* Alias to set_view
*/
- public function include(string $file): void {
+ public function include(string $file, ViewType $type = ViewType::PHP): void {
$this->set_view($file);
}
/**
* Alias to set_view
*/
- public function add_view(string $file): void {
+ public function add_view(string $file, ViewType $type = ViewType::PHP): void {
$this->set_view($file);
}
- private function find_view_path(string $view_file) {
+ /**
+ * Check the $_GET['render'] for which folder to use to render the view
+ * @param string $view_file
+ * @return file | false
+ */
+ private function find_view_path(string $view_file): string|false {
$found = false;
$default_paths = bootstrap\configure::get('view_mode', 'default_paths');
@@ -70,44 +75,41 @@ final class view {
}
return ($found) ? $file : false;
}
-
- private function find_template_path($tpl_file) {
- $file = "{$this->project_dir}/views/includes/{$tpl_file}";
- $path = bootstrap\site_helper::get_root();
- $vf = $path . $file;
- return bootstrap\requires::safer_file_exists($vf);
- }
-
+
/**
* Use View File
* @param string $view_file
* @param string $render_path
* @throws Exception
*/
- public function set_view(string $view_file = null): void {
+ public function set_view(string $view_file = null, ViewType $type = ViewType::PHP): void {
if ($view_file === null) {
return;
}
- $file_ext = bootstrap\common::get_string_right($view_file, 4);
- if (! bootstrap\common::is_string_found($file_ext, '.')) {
- $file_ext = '.php';
- } else if ($file_ext !== '.php') {
- $this->use_template_engine = true;
+ $file_ext = ViewType::get_file_extension_for($type);
+ $file = $this->find_view_path($view_file . $file_ext);
+ if ($type == ViewType::TWIG) {
+ $this->use_template_engine_twig = true;
$this->template_type = str_replace('.', '', $file_ext);
+ $path = bootstrap\site_helper::get_root();
+ if ($file !== false) {
+ $file = str_replace("views/twig", "", $file); // Remove Path, as it is defined in the service file
+ }
}
-
- if ($file_ext === '.php') {
- $file = $this->find_view_path($view_file);
- } else {
- $file = $this->find_template_path($view_file);
+ if ($type == ViewType::LIQUID) {
+ $this->use_template_engine_liquid = true;
+ $this->template_type = str_replace('.', '', $file_ext);
+ if ($file !== false) {
+ $file = ltrim(str_replace("views/liquid", "", $file), "/"); // Remove Path, as it is defined in the service file
+ }
}
-
+
if ($file === false) {
echo "No view file exists for: {$view_file}!";
throw new \Exception("View File does not exist: " . $view_file);
} else {
- $this->files[] = array('file'=>$file, 'path'=>"project", 'file_type'=>$file_ext);
+ $this->files[] = array('file'=>$file, 'path'=> bootstrap\UseDir::PROJECT, 'file_type'=>$file_ext);
}
}
@@ -116,17 +118,16 @@ final class view {
}
/**
- * Use Template with view
+ * Use PHP Template with view
* @param string $render_page
* @return bool was found
*/
public function set_template(string $render_page): bool {
if (! empty($render_page)) {
$render_page = str_replace('.php', '', $render_page);
- $templ = "{$this->project_dir}/templates/{$render_page}";
+ $templ = "/templates/{$render_page}";
if (bootstrap\requires::safer_file_exists(bootstrap\site_helper::get_root() . $templ. '.php') !== false) {
$this->template = $templ;
-// echo $this->template;
return true;
}
}
@@ -161,8 +162,8 @@ final class view {
* @param type $local
* @param string $file
*/
- public function render($local, string $file = null) {
- echo $this->fetch($local, $file);
+ public function render($local, string $file = null, ViewType $type = ViewType::PHP) {
+ echo $this->fetch($local, $file, $type);
}
/**
@@ -170,11 +171,11 @@ final class view {
* @param type $local = $this
* @param $file (optional view file)
*/
- public function fetch($local, string $file = null): string {
+ public function fetch($local, string $file = null, ViewType $type = ViewType::PHP): string {
$page_output = ob_get_clean(); // Get echos before View
bootstrap\views::ob_start();
$saved_ob_level = ob_get_level();
- $this->set_view($file);
+ $this->set_view($file, $type);
unset($file);
@@ -182,25 +183,38 @@ final class view {
$local = $this; // FALL Back, please use fetch($this);
}
- if ($this->use_template_engine) {
- $this->tempalte_engine = bootstrap\registry::get('di')->get_service('templates', [$this->template_type]);
+ if ($this->use_template_engine_liquid) {
+ $this->tempalte_engine_liquid = bootstrap\registry::get('di')->get_service('liquid', [$this->template_type]);
if ($this->white_space_control) {
$this->tempalte_engine->whitespace_control();
}
}
+ if ($this->use_template_engine_twig) {
+ $this->tempalte_engine_twig = bootstrap\registry::get('di')->get_service('twig', [$this->template_type]);
+ }
+
if (count($this->files) > 0) {
foreach ($this->files as $view_file) {
if ($view_file['file_type'] == '.php') {
- bootstrap\requires::secure_include($view_file['file'], bootstrap\UseDir::PROJECT, $local, $this->vars); // Include the file
- } else {
+ bootstrap\requires::secure_include($view_file['file'], bootstrap\UseDir::PROJECT, $local, $this->vars); // Include the PHP file
+
+ } else if ($view_file['file_type'] == '.tpl') {
+ // Liquid uses .tpl by default, so remove that File Ext
$template_file = str_replace('.tpl', '', $view_file['file']);
- $this->tempalte_engine->parse_file($template_file);
+ $this->tempalte_engine_liquid->parse_file($template_file);
+
$assigns = $this->vars['template_assigns'] ?? [];
$filters = $this->vars['template_filters'] ?? null;
$registers = $this->vars['template_registers'] ?? [];
- $assigns['production'] =(\main_tts\is_live());
- echo $this->tempalte_engine->render($assigns, $filters, $registers);
+ $assigns['production'] =(bootstrap\is_live());
+ echo $this->tempalte_engine_liquid->render($assigns, $filters, $registers);
+
+ } else if ($view_file['file_type'] == '.twig') {
+ $twig_data = $this->vars['twig_data'] ?? [];
+ echo $this->tempalte_engine_twig->render($view_file['file'], $twig_data);
+ } else {
+ throw new \Exception("Unable View File Type");
}
}
}
@@ -208,19 +222,19 @@ final class view {
$page_output .= ob_get_clean();
try {
- if (bootstrap\common::get_bool(bootstrap\configure::get('CodeHydrater', 'check_HTML_tags')) === true) {
- $tags = \CodeHydrater\tag_matches::check_tags($page_output);
- if (! empty($tags['output'])) {
- $page_output .= $tags['output'];
- $page_output .= '';
- foreach($this->files as $bad) {
- $page_output .= "";
+ if (bootstrap\common::get_bool(bootstrap\configure::get('CodeHydrater', 'check_HTML_tags')) === true) {
+ $tags = \CodeHydrater\tag_matches::check_tags($page_output);
+ if (! empty($tags['output'])) {
+ $page_output .= $tags['output'];
+ $page_output .= '';
+ foreach($this->files as $bad) {
+ $page_output .= "";
+ }
+ }
}
- }
- }
} catch (exceptions\Bool_Exception $e) {
if (bootstrap\configure::get('CodeHydrater', 'live') === false) {
- $page_output .= assets::alert('SET Config for tts: check_HTML_tags');
+ $page_output .= assets::alert('SET Config for: check_HTML_tags');
}
}