This commit is contained in:
2024-01-31 11:36:25 +07:00
parent caef156a05
commit 4561bd68d1
125 changed files with 9117 additions and 58 deletions

View File

@@ -0,0 +1,216 @@
<?php
namespace Hura8\Components\Template\AdminController;
use Hura8\Components\Template\Controller\TemplateController;
use Hura8\Components\Template\Model\TemplateModel;
class ATemplateController extends TemplateController
{
/* @var TemplateModel $objTemplateModel */
protected $objTemplateModel;
protected $template_set = '';
public function __construct($template_set='default') {
parent::__construct($template_set);
$this->objTemplateModel = new TemplateModel($template_set);
$this->template_set = $template_set;
$this->checkAndCreateDirectory();
}
public function checkFileExist($file_name, $f_type ) {
return $this->objTemplateModel->checkFileExist($file_name, $f_type);
}
public function getInfo($id) {
return $this->objTemplateModel->getInfo($id);
}
public function getEmptyInfo($addition_field_value = []) {
return $this->objTemplateModel->getEmptyInfo($addition_field_value);
}
public function getModuleList() {
return $this->objTemplateModel->getModuleList();
}
/**
* @description full tpl file name has this format: language_layout_base-name
* language is omitted if language = 'vnd'
* layout is omitted if layout = 'pc'
Example:
* for vn/pc:
- index
- product_detail
* for vn/mobile:
- mobile_index
- mobile_product_detail
* for en/pc:
- en_index
- en_product_detail
* for en/mobile:
- en_mobile_index
- en_mobile_product_detail
* @description contruct a full tpl file name to store in folder filesystem
* @param string $language vn or en
* @param string $layout pc or mobile
* @param string $base_name should not contain reserved keywords used for language (i.e en) & layout (i.e mobile)
* @return string
* @test
debug_var(\Hura8\Admin\ATemplate::buildFullTplFileName("product_detail", "vn", "pc"));
debug_var(\Hura8\Admin\ATemplate::buildFullTplFileName("product_detail", "vn", "mobile"));
debug_var(\Hura8\Admin\ATemplate::buildFullTplFileName("product_detail", "en", "pc"));
debug_var(\Hura8\Admin\ATemplate::buildFullTplFileName("product_detail", "en", "mobile"));
*/
public static function buildFullTplFileName(string $base_name, string $language=DEFAULT_LANGUAGE, string $layout='pc') {
if($language == DEFAULT_LANGUAGE) {
return ($layout == 'pc') ? $base_name : join("_", [$layout, $base_name]);
}
return ($layout == 'pc') ? join("_", [$language, $base_name]) : join("_", [$language, $layout, $base_name]);
}
/**
* @description extract $language, $layout, $base_name from $full_tpl_file
* @param string $full_tpl_file
* @return array
* @test
debug_var(\Hura8\Admin\ATemplate::extractFullTplFileName("product_detail"));
debug_var(\Hura8\Admin\ATemplate::extractFullTplFileName("mobile_product_detail"));
debug_var(\Hura8\Admin\ATemplate::extractFullTplFileName("en_product_detail"));
debug_var(\Hura8\Admin\ATemplate::extractFullTplFileName("en_mobile_product_detail"));
*/
public static function extractFullTplFileName(string $full_tpl_file) {
$language = DEFAULT_LANGUAGE;
$layout = "pc";
// check for different language
if(substr($full_tpl_file, 0, 3) == "en_") {
$language = "en";
}
// check for different layout
$clean_file = str_replace($language."_", "", $full_tpl_file);
if(substr($clean_file, 0, 7) == "mobile_") {
$layout = "mobile";
}
$base_name = str_replace($layout."_", "", $clean_file);
return [ $language, $layout, $base_name ];
}
protected function checkAndCreateDirectory() {
// check and create directory
if (!is_dir($this->web_template_path)) {
mkdir($this->web_template_path, 0755, true);
}
if (!is_dir($this->assets_template_path)) {
mkdir($this->assets_template_path, 0755, true);
mkdir(join(DIRECTORY_SEPARATOR, [$this->assets_template_path, 'script']), 0755, true);
mkdir(join(DIRECTORY_SEPARATOR, [$this->assets_template_path, 'images']), 0755, true);
}
}
public function getFiles(array $conditions = []) {
$conditions['numPerPage'] = 2000;
return $this->objTemplateModel->getList($conditions);
}
public function getVersionHistory($module, $tpl_file) {
return $this->objTemplateModel->getVersionHistory($module, $tpl_file);
}
public function getVersionContent($module, $tpl_file, $version) {
return $this->objTemplateModel->getVersionContent($module, $tpl_file, $version);
}
public function getFileContent($tpl_file, $folder, $is_setting = false) {
$ext = strrchr($tpl_file, ".");
if ($ext) $tpl_file = str_replace($ext, "", $tpl_file);
switch ($ext) {
// css or js files
case ".script":
$full_file_path = join(DIRECTORY_SEPARATOR, [$this->assets_template_path, "script", $tpl_file]);
$content = (@file_exists($full_file_path)) ? @file_get_contents($full_file_path) : '<!--Chưa có file '.$tpl_file.'. Nhấn lưu lại để tạo-->';
$real_ext = strrchr($tpl_file, ".");
$editor_mode = ($real_ext == ".js") ? 'text/javascript' : 'text/css';
return "<div style='padding-left:20px; padding-right:20px'>
<textarea style='width:100%; height:600px; padding:3px' id='tpl_editor'>" . htmlspecialchars($content) . "</textarea>
<script>
var CodeMirror_editor = CodeMirror.fromTextArea(document.getElementById(\"tpl_editor\"), {
mode: \"".$editor_mode."\",
lineNumbers: true,
lineWrapping : true,
matchBrackets: true,
continueComments: \"Enter\",
extraKeys: {
\"F11\": function(cm) {
cm.setOption(\"fullScreen\", !cm.getOption(\"fullScreen\"));
},
\"Esc\": function(cm) {
if (cm.getOption(\"fullScreen\")) cm.setOption(\"fullScreen\", false);
}
}
});
</script>
</div>
";
case ".image":
$full_file_path = join(DIRECTORY_SEPARATOR, [$this->assets_template_path, "images", $tpl_file]);
return "<div style='padding-left:20px; padding-right:20px; max-width:600px; overflow:auto'>
<img src='" . $full_file_path . "' alt='' />
</div>";
default;
$full_file_path = ($folder != 'index') ?
join(DIRECTORY_SEPARATOR, [$this->web_template_path, $folder, $tpl_file.".html"]) :
join(DIRECTORY_SEPARATOR, [$this->web_template_path, $tpl_file.".html"]) ;
$content = (@file_exists($full_file_path)) ? @file_get_contents($full_file_path) : '<!--Chưa có file '.$tpl_file.'. Nhấn lưu lại để tạo-->';
return "
<style type=\"text/css\">.CodeMirror {border: 1px solid #eee;}</style>
<div style='padding-left:20px; padding-right:20px'>
<textarea style='width:100%; height:600px; padding:3px' id='tpl_editor' name='tpl_editor'>" . htmlspecialchars($content) . "</textarea>
</div>
<script>
var CodeMirror_editor = CodeMirror.fromTextArea(document.getElementById(\"tpl_editor\"), {
mode: \"text/html\",
lineNumbers: true,
lineWrapping : true,
matchBrackets: true,
continueComments: \"Enter\",
extraKeys: {
\"F11\": function(cm) {
cm.setOption(\"fullScreen\", !cm.getOption(\"fullScreen\"));
},
\"Esc\": function(cm) {
if (cm.getOption(\"fullScreen\")) cm.setOption(\"fullScreen\", false);
}
}
});
</script>
";
}
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Hura8\Components\Template\AdminController;
use Hura8\Components\Template\Controller\TemplateController;
use Hura8\Components\Template\Model\TemplateSetModel;
use Hura8\Interfaces\AppResponse;
use Hura8\System\Controller\SettingController;
class ATemplateSetController
{
/* @var TemplateSetModel $objTemplateSetModel */
protected $objTemplateSetModel;
public function __construct() {
$this->objTemplateSetModel = new TemplateSetModel();
}
// get empty/default item for form
public function getEmptyInfo($addition_field_value = []) {
return $this->objTemplateSetModel->getEmptyInfo($addition_field_value);
}
public function checkSetExist($set_name)
{
return $this->objTemplateSetModel->getInfoBySet($set_name);
}
public function getActivatedSet() {
return $this->objTemplateSetModel->getActivatedSet();
}
public function getList(array $condition)
{
return $this->objTemplateSetModel->getList($condition);
}
public function getInfo($id): ?array
{
return $this->objTemplateSetModel->getInfo($id);
}
}

View File

@@ -0,0 +1,167 @@
<?php
namespace Hura8\Components\Template\Controller;
use Liquid\Cache\File;
use Liquid\Liquid;
use Liquid\Template as LiquidTemplate;
class TemplateController
{
const TEMPLATE_DIR = ROOT_DIR ."/template/web/";
const TEMPLATE_ASSETS_DIR = PUBLIC_DIR."/static/assets/";
const TEMPLATE_TMP_UPLOAD_DIR = ROOT_DIR . "/template/_uploaded/";
protected $template_set = '';
protected $web_template_path = ''; // hold html tmp
protected $assets_template_path = ''; // hold tpl assets like css/js/images
protected $assets_public_path = ''; // to view in url
public static $setting_template_path = ROOT_DIR . '/template/setting'; // (ie. email, sms, etc...)
protected static $cache_dir = ROOT_DIR . "/var/cache/public_template/";
public function __construct($template_set='default')
{
$this->template_set = $template_set;
$this->web_template_path = self::TEMPLATE_DIR . $template_set;
$this->assets_template_path = self::TEMPLATE_ASSETS_DIR . $template_set;
$this->assets_public_path = join('/', ['', 'static', 'assets', $template_set]) ;
}
/**
* @param string $language_prefix
* @param string $layout
* @param $routing array
* @return string
*/
public static function getPublicTemplateFile($language_prefix="", $layout="pc", array $routing=[]): string{
$tpl_file = str_replace('-', '_', $routing['view']). '.html';
if($layout != 'pc') $tpl_file = $layout.'_'.$tpl_file;
if($language_prefix) $tpl_file = $language_prefix."_".$tpl_file;
return join( DIRECTORY_SEPARATOR, [
$routing['module'],
$tpl_file
]);
}
/**
* @param string $language_prefix
* @param string $layout
* @return string
*/
public static function getSiteThemeFile($language_prefix="", $layout = 'pc'): string
{
$tpl_file = "index.html";
if($layout != 'pc') $tpl_file = $layout.'_'.$tpl_file;
if($language_prefix) return $language_prefix."_".$tpl_file;
return $tpl_file;
}
public static function flushCache() {
try {
$cache = new File([
'cache_dir' => self::$cache_dir
]);
$cache->flush();
}catch (\Exception $exception) {
}
}
/*
* 2 ways to render a html template
* 1. Use $html_to_parse, which requires no dependencies
* Example:
* Template::parse(null, 'Age = {{age}}', ['age' => 21], '');
*
* 2. Use $template_file_path, which requires dependency $path
* Template::parse(Template::$setting_template_path, null, ['age' => 21], 'email/test');
*
* */
public static function parse($path = null, $html_to_parse = null, array $data = [], $template_file_path = '') {
if(!$html_to_parse && !$template_file_path) {
return 'Nothing to parse';
}
//output to html
Liquid::set('INCLUDE_SUFFIX', 'html');
Liquid::set('INCLUDE_PREFIX', '');
//Liquid::set('INCLUDE_ALLOW_EXT', true);
Liquid::set('ESCAPE_BY_DEFAULT', false);
$enable_cache = false; // default = true, turn this on-off to disable cache while working on local mode
//$enable_cache = true;
//catch exception and print friendly notice
try {
$objLiquidTemplate = new LiquidTemplate( $path );
$objLiquidTemplate->registerFilter( TemplateFilter::class );
if($enable_cache) {
$objLiquidTemplate->setCache(new File([
'cache_dir' => self::$cache_dir
]));
}
if($html_to_parse) {
$objLiquidTemplate->parse($html_to_parse);
}elseif ($template_file_path) {
$objLiquidTemplate->parseFile($template_file_path);
}
return $objLiquidTemplate->render($data);
} catch (\Exception $e) {
$result = [];
do {
//printf("%s:%d %s (%d) [%s]\n", $e->getFile(), $e->getLine(), $e->getMessage(), $e->getCode(), get_class($e));
//echo $e->getTraceAsString();
//$code = $e->getTrace()[0]['args'][0];
//if(is_array($code)) $code = serialize($code);
$result[] = sprintf(
"
Lỗi code trong file template html: <br />
- Chi tiết lỗi: %s<br />
- File template: %s<br />
- Cache Dir: %s<br />
- Hướng dẫn xử lý: Tách từng phần html để kiểm tra và nhấn F5 mỗi lần. Nếu không xuất hiện thông báo này nghĩa là phần đó không tạo lỗi
",
$e->getMessage(),
substr($template_file_path, strrpos($template_file_path, DIRECTORY_SEPARATOR) + 1 ),
static::$cache_dir
);
} while($e = $e->getPrevious());
return join(" - ", $result);
}
}
public function getWebTemplatePath() {
return $this->web_template_path;
}
public function getWebPublicAssetsUrl() {
return $this->assets_public_path;
}
public function getWebTemplateAssetsPath() {
return $this->assets_template_path;
}
}

View File

@@ -0,0 +1,262 @@
<?php
namespace Hura8\Components\Template\Controller;
/**
* Before attempting to make new filter, see built-in filters: https://shopify.github.io/liquid/
*/
class TemplateFilter
{
/**
*
* @param array $array
*
* @return string
*/
public static function to_json(array $array) {
return \json_encode($array);
}
/**
* create array of ['key' => '', 'value' => ] from [key1 => value1, key2=>value2, ...]
*
* @param array $key_values [key1 => value1, key2=>value2]
*
* @return array [['key' => 'key1', 'value' => value1], ['key' => 'key2', 'value' => value2]]
*/
public static function to_array(array $key_values) {
$result = [];
foreach ($key_values as $key => $value) {
$result[] = [
'key' => $key,
'value' => $value,
];
}
return $result;
}
/**
* split a s by line to create array
*
* @param string $txt
*
* @return array
*/
public static function get_line($txt) {
if(is_array($txt)) {
return $txt;
}
$txt = trim($txt);
if( ! $txt ) return [];
return preg_split("/\n/", $txt);
}
/**
* Implement strlen
*
* @param string $str
*
* @return int
*/
public static function length($str) {
return strlen(trim($str));
}
/**
* Make number easier to read: 1000 -> 1.000
*
* @param string $number
*
* @return string
*/
public static function format_number($number) {
if(!$number) return '';
$number = floatval($number);
$number = number_format($number, 0, ",", "."); //Vietnamese format with decimals by a coma
return $number;
}
public static function format_price($p_price, $currency = ''){
if(!$p_price) return '';
if(!$currency) $currency = (defined("DEFAULT_CURRENCY")) ? DEFAULT_CURRENCY : "vnd";
//if(is_string($p_price)) return 0;
if($currency == 'usd') {
return number_format($p_price,2,".",",");
}else {
return number_format($p_price,0,",",".");
}
}
public static function global_asset_url($file_name = '')
{
return GLOBAL_ASSETS_PATH . $file_name;
}
/**
*
* Description: get the shop's full asset url for template's images/js/css
*
* //Returns the URL of a file in the "assets" folder of a theme.
// {{ 'shop.css' | asset_url : 'arg1', 'arg2' ...}} -> //cdn.shopify.com/s/files/1/0087/0462/t/394/assets/shop.css?28253
*
* @param string $file_name
*
* @return string
*/
public static function asset_url($file_name = '')
{
if( !$file_name ) return '';
$file_ext = strtolower(strrchr($file_name, "."));
// script tags
if(in_array($file_ext, ['.js', '.css'])) return TEMPLATE_ASSET_SCRIPT . $file_name;
// default image
return TEMPLATE_ASSET_IMAGE . $file_name;
}
/**
*
* Description: construct a full html tag for images/js/css file
*
* @param string $file_path domain.com/static/style.css?v=3.1.1
*
* @return string
*/
public static function script_tag($file_path) {
if( ! $file_path ) return '';
//check for ?
if(strpos($file_path, "?") !== false) {
$file_ext = str_replace(strrchr($file_path, "?"), "", $file_path);
$file_ext = strtolower(strrchr($file_ext, "."));
} else {
$file_ext = strtolower(strrchr($file_path, "."));
}
$tag_config = [
".css" => "<link rel=\"stylesheet\" href=\"".$file_path."\" type=\"text/css\" />",
".js" => "<script src=\"".$file_path."\"></script>",
".jpg" => "<img src=\"".$file_path."\" alt=\"n\"/>",
".jpeg" => "<img src=\"".$file_path."\" alt=\"\"/>",
".gif" => "<img src=\"".$file_path."\" alt=\"\"/>",
".png" => "<img src=\"".$file_path."\" alt=\"\"/>",
];
return (isset($tag_config[$file_ext])) ? $tag_config[$file_ext] : '';
}
/**
* {{ product_info.main_image | img_url: '300x300' }} => https://cdn.shopify.com/s/files/1/1183/1048/products/boat-shoes_300x300.jpeg?1459175177
* @param string $full_path
* @param string $modifier
* $modifier:
* - must be in format: NumberxNumber or Numberx where Number must within 10 -> 9999
* - or be one of these: small | medium | large
* @return string
*/
public static function img_url($full_path, $modifier)
{
$clean_modifier = ($modifier) ? trim($modifier) : "";
// verify $modifier
// must be in format: NumberxNumber or Numberx where Number must within 10 -> 9999
if($clean_modifier
&& !preg_match("/^[0-9]{2,4}x([0-9]{2,4})?$/i", $clean_modifier)
&& !in_array($clean_modifier, ["small", "medium", "large"])
) {
$clean_modifier = "";
}
// return if no valid modifier
if( ! $clean_modifier ) {
return $full_path;
}
$last_dot_position = strrpos($full_path, ".");
if( ! $last_dot_position ) return $full_path . $clean_modifier;
return join("", [
substr($full_path, 0, $last_dot_position),
"_",
$clean_modifier,
substr($full_path, $last_dot_position)
]);
}
/**
* //Returns the URL of a file in the Files page of the admin.
//{{ 'size-chart.pdf' | file_url }} -> //cdn.shopify.com/s/files/1/0087/0462/files/size-chart.pdf?28261
*
* @param string $input
* @param string $string
*
* @return string
*/
public static function file_url($input, $string)
{
return strtoupper($input) . " = " . $string;
}
/**
* //Returns the asset URL of an image in the Files page of the admin. file_img_url accepts an image size parameter.
//{{ 'logo.png' | file_img_url: '1024x768' }} -> //cdn.shopify.com/s/files/1/0246/0527/files/logo_1024x768.png?42
*
* @param string $input
* @param string $string
*
* @return string
*/
public static function file_img_url($input, $string)
{
return '';
}
/**
* Show all content of a variable, useful for template development
*
* @param string
*
* @return string
*/
public static function print_r($input)
{
@ob_start();
print_r($input);
$content = ob_get_contents();
@ob_end_clean();
return join("\r", ['<!-- print_r debug content: ', $content, '-->']) ;
}
/**
* Show all content of a variable, useful for template development
*
* @param string
*
* @return string
*/
public static function show_var($input)
{
@ob_start();
print_r($input);
$content = ob_get_contents();
@ob_end_clean();
return join("\r", ['<textarea cols="80" rows="20">', $content, '</textarea>']) ;
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace Hura8\Components\Template\Model;
use Hura8\Interfaces\AppResponse;
use Hura8\System\FileUpload;
use Hura8\System\Model\aEntityBaseModel;
class TemplateModel extends aEntityBaseModel
{
protected $template_set = '';
protected $tb_template = '';
protected $tb_template_history = 'tb_template_history';
public function __construct($template_set='default')
{
parent::__construct('template');
$this->template_set = $template_set;
$this->tb_template = $this->tb_entity;
}
protected function extendedFilterOptions() : array
{
return [
// empty for now
];
}
public function getModuleList() {
$query = $this->db->runQuery(
" select distinct `module` from `".$this->tb_template."` where `set_name` = ? order by `module` asc ",
[ 's' ],
[ $this->template_set ]
) ;
$item_list = [];
foreach ($this->db->fetchAll($query) as $item) {
if($item['module']) $item_list[] = $item['module'];
}
return $item_list;
}
public function checkFileExist($file_name, $f_type ) {
$query = $this->db->runQuery(
"SELECT * FROM ".$this->tb_template." WHERE `file_name` = ? AND `file_type` = ? AND `set_name` = ? LIMIT 1 ",
[ 's', 's' , 's' ],
[ $file_name, $f_type, $this->template_set ]
);
return $this->db->fetchAssoc($query);
}
public function getVersionHistory($module, $tpl_file) {
if(!$tpl_file) return [];
$history_tpl_file = $this->historyFile($module, $tpl_file);
$query = $this->db->runQuery(
"SELECT tpl_version, last_update, last_update_by FROM ".$this->tb_template_history."
WHERE `template` = ? AND `set_name` = ? ORDER BY `tpl_version` DESC LIMIT 30 ",
[ 's', 's' ],
[ $history_tpl_file, $this->template_set ]
);
return $this->db->fetchAll($query);
}
public function getVersionContent($module, $tpl_file, $version) {
$history_tpl_file = $this->historyFile($module, $tpl_file);
$query = $this->db->runQuery(
"SELECT `content` FROM ".$this->tb_template_history."
WHERE `template`= ? AND `set_name` = ? AND `tpl_version` = ?
LIMIT 1 ",
[ 's', 's', 'd' ],
[ $history_tpl_file, $this->template_set, $version ]
);
if ($rs = $this->db->fetchAssoc($query)) {
return $rs['content'];
}
return '';
}
protected function historyFile($module, $tpl_file) {
return join("/", [$module, $tpl_file]);
}
protected function _buildQueryConditionExtend(array $conditions) : ?array
{
/*$conditions = [
'file_folder' => '',
'not_image' => true,
];*/
$where_clause = [ " AND `set_name` = ? "];
$bind_types = ['s'];
$bind_values = [$this->template_set];
if(isset($conditions['file_folder']) && in_array($conditions['file_folder'], ['layout', 'images', 'script']) ) {
$where_clause[] = " AND `file_folder` = ? ";
$bind_types[] = 's';
$bind_values[] = $conditions['file_folder'];
}
if(isset($conditions['not_image']) && $conditions['not_image']) {
$where_clause[] = " AND `file_folder` != 'images' ";
}
return [
join(" ", $where_clause),
$bind_types,
$bind_values
];
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Hura8\Components\Template\Model;
use Hura8\Interfaces\AppResponse;
use Hura8\System\Model\aEntityBaseModel;
class TemplateSetModel extends aEntityBaseModel
{
protected $tb_template = 'tb_template';
public function __construct()
{
parent::__construct('template_set');
}
protected function extendedFilterOptions() : array
{
return [
// empty for now
];
}
public function getInfoBySet($set_name)
{
$query = $this->db->runQuery("SELECT * FROM `". $this->tb_entity ."` WHERE `folder_name` = ? LIMIT 1 ", ['s'], [$set_name]);
if($item_info = $this->db->fetchAssoc($query)){
return $item_info;
}
return false;
}
public function getActivatedSet() {
$query = $this->db->runQuery("SELECT `folder_name` FROM `". $this->tb_entity ."` WHERE `is_activated` = 1 LIMIT 1 ");
if($item_info = $this->db->fetchAssoc($query)){
return $item_info['folder_name'];
}
return 'default';
}
protected function _buildQueryConditionExtend(array $condition) : ?array
{
$where_clause = "";
$bind_types = [];
$bind_values = [];
return [
$where_clause,
$bind_types,
$bind_values
];
}
}