diff --git a/README.md b/README.md
new file mode 100644
index 0000000..56288f8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,67 @@
+
Hướng dẫn
+
+Link thiết kế: Giao diện Admin
+
+Repo: https://repo.hurasoft.com/tieptk/bestpc_vn
+
+Theo dõi tiến độ: https://docs.google.com/spreadsheets/d/1Po3ANsG00pm_Y3dnrwuV81cidTCCqepYmR3yPDlIq6c/edit#gid=0
+
+Cài đặt hệ thống
+
+Test và làm việc chính tại web: http://local.bestpc_vn/
+
+
+ Tải phần mềm XAMPP tại https://www.apachefriends.org/download.html để chạy PHP
+ Chỉnh file hosts trong máy tính C:\Windows\System32\drivers\etc\hosts:
+
+127.0.0.1 local.bestpc_vn
+
+
+
+ Cài đặt ../xampp/apache/conf/extra/httpd-vhosts.conf của apache trong XAMPP
+
+ <VirtualHost *:80>
+ DocumentRoot "/thuc-muc-check-out/bestpc_vn"
+ ServerName local.bestpc_vn
+ <Directory "/thuc-muc-check-out/bestpc_vn/">
+ Require all granted
+ </Directory>
+ </VirtualHost>
+
+
+
+ Cài đặt ../xampp/apache/conf/httpd.conf của apache trong XAMPP
+
+ # AllowOverride controls what directives may be placed in .htaccess files.
+ # It can be "All", "None", or any combination of the keywords:
+ # AllowOverride FileInfo AuthConfig Limit
+ AllowOverride All
+
+
+
+
+
+
+Cấu trúc thư mục
+
+
+ /template: các file template html chia theo module/view
+ /pages: cung cấp dữ liệu cho template và hiển thị dữ liệu trên template qua code Liquid https://shopify.github.io/liquid/
+ /public_html: Code public
+ /public_html/assets: lưu các file ảnh/css/js dùng cho giao diện
+ /package: thư viện code PHP hỗ trợ
+ /src: các code PHP hỗ trợ
+
+
+Cài đặt Composer (dùng tải package của PHP)
+
+Xem hướng dẫn: https://getcomposer.org/doc/00-intro.md#installation-windows
+
+Sau khi cài đặt xong. Mở cmd của Windows và thao tác lệnh sau để cài các thư viện code PHP cần cho dự án này.
+
+
+ > cd /thuc-muc-check-out/bestpc_vn/package
+ > composer i
+
+
+
diff --git a/config/constant.php b/config/constant.php
new file mode 100644
index 0000000..c7f89cc
--- /dev/null
+++ b/config/constant.php
@@ -0,0 +1,9 @@
+ [
+ 'module' => 'buildpc',
+ 'view' => 'home',
+ 'view_id'=> 0,
+ 'query' => [],
+ 'url' => '/buildpc',
+ ],
+];
diff --git a/package/composer.json b/package/composer.json
new file mode 100644
index 0000000..abce318
--- /dev/null
+++ b/package/composer.json
@@ -0,0 +1,8 @@
+{
+ "require": {
+ "liquid/liquid": "1.4.32",
+ "ext-json": "*",
+ "ext-mysqli": "*",
+ "ext-zip": "*"
+ }
+}
diff --git a/public_html/.htaccess b/public_html/.htaccess
new file mode 100644
index 0000000..a0172c7
--- /dev/null
+++ b/public_html/.htaccess
@@ -0,0 +1,7 @@
+RewriteEngine on
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteCond %{REQUEST_URI} !^/index.php
+# prevent rewrite non-existent files
+RewriteCond %{REQUEST_URI} !\.(jpg|png|gif|css|js|php|tiff|webp|jpeg|ico)$
+RewriteRule ^(.*)$ /index.php [QSA,L]
diff --git a/public_html/_shared.php b/public_html/_shared.php
new file mode 100644
index 0000000..5c2a092
--- /dev/null
+++ b/public_html/_shared.php
@@ -0,0 +1,13 @@
+start();
diff --git a/src/Hura/App.php b/src/Hura/App.php
new file mode 100644
index 0000000..f605f28
--- /dev/null
+++ b/src/Hura/App.php
@@ -0,0 +1,211 @@
+ 'home',
+ "view" => 'home',
+ "url" => '/admin/product'
+ ];
+
+ protected $data = [];
+
+
+ public function __construct()
+ {
+
+ }
+
+ // start the app
+ public function start() {
+ $this->getRouter();
+ $this->getData();
+ echo $this->renderModule();
+ }
+
+
+ protected function getRouter() {
+ /*$route = [
+ "module" => (isset($_REQUEST['module'])) ? $_REQUEST['module'] : 'home',
+ "view" => (isset($_REQUEST['view'])) ? $_REQUEST['view'] : 'home',
+ ];*/
+
+ $objRouter = new Router();
+ $this->current_route_info = $objRouter->getRouting();
+ }
+
+
+ protected function getData() {
+ $module_file = $this->getModuleFile();
+
+ if(!file_exists($module_file)) {
+ // print_r($this->current_route_info);
+ die('Page '. $module_file .' not found!');
+ }
+
+ $data = include_once $module_file;
+
+ $global_data = [
+ "module" => $this->current_route_info['module'],
+ "view" => $this->current_route_info['view'],
+ "url" => $this->current_route_info['url'],
+ ];
+
+ $this->data = array(
+ 'global' => $global_data,
+ // module-specific data, just print {{ page }} to see all available data for the current page!!!
+ 'page' => (is_array($data)) ? $data : [],
+ );
+ }
+
+
+ protected function getModuleFile(): string
+ {
+ return SRC_DIR . DIRECTORY_SEPARATOR . join(DIRECTORY_SEPARATOR, [
+ "pages",
+ $this->current_route_info["module"],
+ str_replace("-", "_", $this->current_route_info["view"]).".php"
+ ]) ;
+ }
+
+
+ protected function renderModule() {
+
+ if(!$this->current_route_info['module'] || !$this->current_route_info['view']) {
+ die("Module not exist");
+ }
+
+ $template_file_path = $this->tpl_path ."/". $this->current_route_info['module'];
+ $template_file_name = str_replace("-", '_', $this->current_route_info['view']).".html";
+ $template_file_full_path = $template_file_path."/".$template_file_name;
+
+ //check exist
+ if(!@file_exists( $template_file_full_path)) {
+ // attempt to auto create first
+ // todo: this MUST BE TURNED OFF IN PRODUCTION, else many files will be created unintentionally
+ $module_file = $this->getModuleFile();
+ // only create if module file exist
+ if(file_exists($module_file) && !$this->autoCreateTplFile( $template_file_path, $template_file_name )) {
+ die("Please manually create template file at: ". $template_file_full_path);
+ }
+ }
+
+ $theme_file_path = $this->tpl_path ."/theme.html";
+ if( ! @file_exists( $theme_file_path)) {
+ die("Theme not exist (please create): " . $theme_file_path);
+ }
+
+ $theme_content = @file_get_contents( $theme_file_path );
+ $module_content = @file_get_contents( $template_file_full_path );
+
+ $page_content_to_parse = preg_replace([
+ "/{{(\s+)?page_content(\s+)?}}/"
+ ], [
+ $module_content,
+ ] , $theme_content );
+
+
+ return $this->parse(
+ $page_content_to_parse,
+ $template_file_path
+ );
+ }
+
+
+ protected function autoCreateTplFile($file_path, $file_name) : bool {
+ // create dir if not exist
+ if(!file_exists($file_path)) {
+ if(!mkdir($file_path, 0755, true)) {
+ return false;
+ }
+
+ if(!file_exists($file_path)) {
+ return false;
+ }
+ }
+
+ //create file
+ $file_full_path = $file_path . "/". $file_name;
+ @file_put_contents($file_full_path, $file_full_path);
+
+ return file_exists($file_full_path);
+ }
+
+
+ /*
+ * 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');
+ * */
+ protected function parse($html_to_parse = null, $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( $this->tpl_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($this->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:
+- Chi tiết lỗi: %s
+- File template: %s
+- 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);
+ }
+ }
+
+}
diff --git a/src/Hura/Router.php b/src/Hura/Router.php
new file mode 100644
index 0000000..48bbc07
--- /dev/null
+++ b/src/Hura/Router.php
@@ -0,0 +1,78 @@
+path_config = require $path_config_file;
+ }
+
+ // url: asdas.php?para1=value1
+ public function getRouting(): array
+ {
+ $parsed = Url::parse($_SERVER['REQUEST_URI']); //abc/product?param1=12¶m2=value2
+ //print_r($parsed);
+
+ // home
+ if($parsed['path'] == '/') {
+ return [
+ 'module' => preg_replace("/[^a-z0-9_\-]/i","", getRequest('module', 'home')),
+ 'view' => preg_replace("/[^a-z0-9_\-]/i","", getRequest('view', 'home')),
+ 'view_id'=> 0,
+ 'query' => $parsed['query'],
+ 'url' => $_SERVER['REQUEST_URI'],
+ ];
+ }
+
+ // check match pattern in $this->path_config
+ foreach ($this->path_config as $_config => $_route ) {
+ if(preg_match("{^".$_config."$}", $parsed['path'], $match )) {
+
+ if(isset($_route['query']) && is_array($_route['query'])) {
+ $_route['query'] = array_merge($_route['query'], $parsed['query']);
+ }else{
+ $_route['query'] = $parsed['query'];
+ }
+
+ return array_merge([
+ 'path' => $parsed['path'],
+ 'match' => $match,
+ ], $_route);
+ }
+ }
+
+ // check database
+
+ // else error
+ return [
+ 'module' => "error" ,
+ 'view' => "error" ,
+ 'view_id' => "not_found",
+ 'query' => $parsed['query'],
+ 'url' => $_SERVER['REQUEST_URI'],
+ ];
+
+
+ // auto parse path base on convention: admin/module/view/view_id
+ /* $ele = explode("/", $parsed['path']);
+
+ $module = $ele[2] ?? 'home';
+ $view = isset($ele[3]) ? $ele[3] : getRequest('view', 'home');
+ $view_id = isset($ele[4]) ? $ele[4] : getRequest('id', 'view_id');
+
+ // else error
+ return [
+ 'module' => preg_replace("/[^a-z0-9_\-]/i","", $module ) ,
+ 'view' => preg_replace("/[^a-z0-9_\-]/i","", $view ) ,
+ 'view_id' => preg_replace("/[^a-z0-9_]/i","", $view_id ),
+ 'query' => $parsed['query'],
+ 'url' => $_SERVER['REQUEST_URI'],
+ ];*/
+ }
+
+}
diff --git a/src/Hura/TemplateFilter.php b/src/Hura/TemplateFilter.php
new file mode 100644
index 0000000..38dcd94
--- /dev/null
+++ b/src/Hura/TemplateFilter.php
@@ -0,0 +1,256 @@
+ '', '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,",",".");
+ }
+ }
+
+ /**
+ *
+ * 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 . "/images/" . $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" => " ",
+ ".js" => "",
+ ".jpg" => " ",
+ ".jpeg" => " ",
+ ".gif" => " ",
+ ".png" => " ",
+ ];
+
+ 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", ['']) ;
+ }
+
+ /**
+ * 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", ['']) ;
+ }
+}
diff --git a/src/Hura/Url.php b/src/Hura/Url.php
new file mode 100644
index 0000000..46e377e
--- /dev/null
+++ b/src/Hura/Url.php
@@ -0,0 +1,47 @@
+ '',
+ 'host' => '',
+ 'port' => '',
+ 'user' => '',
+ 'pass' => '',
+ 'path' => '',
+ 'query' => [],
+ 'fragment' => '',
+ ];
+ foreach ($data as $key => $value) {
+ if(isset($default[$key])) {
+ $default[$key] = ($key == 'query') ? self::parsedQuery($value) : $value;
+ }
+ }
+
+ return $default;
+ }
+
+
+ public static function parsedQuery($query = '') {
+ if(!$query) return [];
+ $result = [];
+ $parts = explode("&", $query);
+
+ foreach ($parts as $part) {
+ $el = explode("=", $part);
+ if(sizeof($el) != 2) continue;
+
+ $cleaned_key = preg_replace("/[^a-z0-9_\-\.]/i", '', $el[0]);
+ $cleaned_value = preg_replace("/[^a-z0-9_\.\-;&]/i", '', $el[1]);
+
+ $result[$cleaned_key] = $cleaned_value;
+ }
+
+ return $result;
+ }
+}
diff --git a/src/inc/common.php b/src/inc/common.php
new file mode 100644
index 0000000..46c1c7f
--- /dev/null
+++ b/src/inc/common.php
@@ -0,0 +1,45 @@
+', $content, '']) ;
+}
+
+function init_autoload(){
+ $classLoader = require_once ROOT_DIR . '/package/vendor/autoload.php';
+ $classLoader->add("Hura", ROOT_DIR . '/src' );
+
+ return $classLoader;
+}
+
+//get current paging id
+function getPageId(){
+ return getRequestInt('page', 1);
+}
+
+function getPageSize($default=10){
+ return getRequestInt('pageSize', $default);
+}
+
+//Function to get the post value in submit
+function getPost($var, $default="", $encode = false, $keep_tag=""){
+ return isset($_POST[$var]) ? $_POST[$var] : $default;
+}
+
+//Function to get the INTERGER request value of a variable
+function getRequestInt($var, $min_value = 0){
+ $request = isset($_REQUEST[$var]) ? (int) $_REQUEST[$var] : (int) $min_value;
+ $request = ($request >= $min_value ) ? $request : $min_value; //if user tampers request parameter
+ return $request;
+}
+
+
+//Function to get the request value of a variable
+function getRequest($var, $default=""){
+ return $_REQUEST[$var] ?? $default;
+}
+
diff --git a/src/inc/fun.db.php b/src/inc/fun.db.php
new file mode 100644
index 0000000..c59b662
--- /dev/null
+++ b/src/inc/fun.db.php
@@ -0,0 +1,16 @@
+ [
+ [
+ 'id' => 1,
+ 'title' => 'ĐƠN HÀNG MỚI',
+ 'main_stat' => '1000000',
+ 'time' => ' 24/11/2023 ',
+ ],
+ ],
+
+];
diff --git a/src/pages/product/addon.php b/src/pages/product/addon.php
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++ b/src/pages/product/addon.php
@@ -0,0 +1 @@
+getAllParent();
+
+return [
+ 'category_list' => get_category_list(0, getRequest("id"), $level=1, $prefix="", $category_collection )
+];
+
+
+function get_category_list($parentId=0, $currentCat="",$level=1, $prefix="", $category_collection = array()){
+
+ $categoryTree = "";
+ $extra_space = "";
+ for($i = 1; $i < $level; $i++){
+ $extra_space .= " ";
+ }
+
+ $stt = 0;
+ if(isset($category_collection[$parentId])) {
+
+ foreach($category_collection[$parentId] as $index => $cat_info){
+ $cat_id = $cat_info['id'];
+
+ $stt ++;
+ $imgUrl = (strlen($cat_info["thumbnail"]) > 2) ? " " : "";
+
+ if($cat_info["status"]) $status = " ";
+ else $status = " ";
+
+ $edit_link ="/admin/product/category-form?id=".$cat_id;
+
+ $hide_this = ($parentId > 0) ? "style='display: none;'" : '';
+
+ $show_category_name = $cat_info["title"];
+
+ if(!IS_DEFAULT_LANGUAGE && isset($cat_info["not_translated"]) && $cat_info["not_translated"]) {
+ $show_category_name = "[Chưa dịch] ".$cat_info["title"];
+ }
+
+ if($cat_info['is_parent']) {
+ $show_category_name = "".$show_category_name." ";
+ }
+
+ $categoryTree .= "
+
+
+
+
+ ". $extra_space . $prefix . $stt.". ". $show_category_name . $imgUrl."
+
+
+
+ Xem trang
+
+
+ ".$cat_id."
+ 413787
+ 1292
+
+
+
+
+
+ SP + Danh mục con
+
+
+ ";
+
+ if(IS_DEFAULT_LANGUAGE) {
+ $categoryTree .= "
+
+ Tổng (".$cat_info['attribute_count'].")
+
+
+
+
+ ";
+
+ }else{
+ $categoryTree .= "
+
+
+
+ ";
+ }
+
+ $categoryTree .= "
+
+ ";
+
+ if($cat_info["is_parent"]) $categoryTree .= get_category_list($cat_id, $currentCat, $level + 1, $prefix . $stt.".", $category_collection);
+
+ }
+ }
+
+ return $categoryTree;
+}
+
diff --git a/src/pages/product/category_form.php b/src/pages/product/category_form.php
new file mode 100644
index 0000000..80302f2
--- /dev/null
+++ b/src/pages/product/category_form.php
@@ -0,0 +1,18 @@
+ 0) ? $objAProductCategoryController->getFullInfo($id) : null;
+if(!$item_info) $item_info = $objAProductCategoryController->getEmptyInfo([]);
+
+
+return [
+ 'item_info' => $item_info,
+ 'categoryDropBox' => $objAProductCategoryController->getDropBox( $item_info['parent_id'], 0, 1),
+ 'update_status' => getRequest("us"),
+];
diff --git a/src/pages/product/collection.php b/src/pages/product/collection.php
new file mode 100644
index 0000000..a814366
--- /dev/null
+++ b/src/pages/product/collection.php
@@ -0,0 +1 @@
+getFullInfo($product_id);
+
+$view_part = getRequest("part", "basic");
+
+$view_part_file = str_replace("-", "_", $view_part);
+
+$part_file = __DIR__."/form_components/". $view_part_file .".php";
+if(@file_exists($part_file)) {
+ include $part_file;
+}else{
+ die("File: /form_components/". $view_part_file .".php does not exist!");
+}
+
+
+return [
+ "product_info" => $product_info,
+ "product_menu" => _get_product_menu(),
+ "view_part" => $view_part,
+];
+
+// helpers
+
+function _get_product_menu() {
+ $current_selected = getRequest('part', 'basic');
+ $product_menu = array(
+ array(
+ 'id' => 'basic',
+ "name" => "Cơ bản",
+ ),
+
+ array(
+ 'id' => 'store',
+ "name" => "Cửa hàng",
+ ),
+
+ array(
+ 'id' => 'category',
+ "name" => "Danh mục",
+ ),
+
+ array(
+ 'id' => 'seo',
+ "name" => "SEO",
+ ),
+
+ array(
+ 'id' => 'description',
+ "name" => "Mô tả",
+ ),
+
+ array(
+ 'id' => 'spec-group',
+ "name" => "Thông số kỹ thuật",//"Thông số",
+ ),
+
+ /*"spec" => array(
+ "name" => "Thông số kỹ thuật nhập text", //"Bộ lọc thuộc tính", //"Thông số",
+ "must_have_id" => true,
+ "change_language" => true,
+ ),*/
+
+ /*"image-spec" => array(
+ "name" => "Ảnh thông số",
+ "must_have_id" => true,
+ "change_language" => true,
+ ),*/
+
+ array(
+ 'id' => 'instruction',
+ "name" => "Hướng dẫn sử dụng",
+ ),
+
+ array(
+ 'id' => 'image',
+ "name" => "Ảnh",
+ ),
+
+ array(
+ 'id' => 'variant',
+ "name" => "Cấu hình",
+ ),
+
+ array(
+ 'id' => 'accessory',
+ "name" => "Phụ kiện",
+ ),
+
+ array(
+ 'id' => 'addon',
+ "name" => "Dịch vụ/SP đi kèm",
+ ),
+
+ array(
+ 'id' => 'video',
+ "name" => "Youtube",
+ ),
+
+ /* "video-list" => array(
+ "name" => "Thư viện Youtube",
+ "must_have_id" => true,
+ "change_language" => false,
+ ),*/
+
+ /*"relate-article" => array(
+ "name" => "Nội dung liên quan",
+ "must_have_id" => true,
+ "change_language" => false,
+ ),*/
+
+ /*"web-link" => array(
+ "name" => "So sánh giá",
+ "must_have_id" => true,
+ "change_language" => false,
+ ),*/
+
+ array(
+ 'id' => 'tag',
+ "name" => "Tags",
+ ),
+
+ array(
+ 'id' => 'relation',
+ "name" => "Nội dung liên quan",
+ ),
+
+ array(
+ 'id' => 'customer-group',
+ "name" => "Giá theo nhóm khách hàng",
+ ),
+
+ array(
+ 'id' => 'component',
+ "name" => "Thành phần",
+ ),
+
+ array(
+ 'id' => 'configurable',
+ "name" => "Tùy chọn thành phần",
+ ),
+
+ array(
+ 'id' => 'compatible',
+ "name" => "Sp tương thích",
+ ),
+
+ array(
+ 'id' => 'similar',
+ "name" => "Sp tương tự",
+ ),
+
+ array(
+ 'id' => 'combo-set',
+ "name" => "Combo Set",
+ ),
+
+ );
+
+ return array_map(function ($item) use ($current_selected){
+ $copy = $item;
+ $copy['is_current'] = $item['id'] == $current_selected ? 1 : 0;
+
+ return $copy;
+
+ }, $product_menu);
+}
diff --git a/src/pages/product/form_components/accessory.php b/src/pages/product/form_components/accessory.php
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++ b/src/pages/product/form_components/accessory.php
@@ -0,0 +1 @@
+ explode("-", getRequest("category", '')),
+ "brand" => explode("-", getRequest("brand", '')),
+ "hotType" => explode("-", getRequest("hotType", '')),
+ "other_filter" => [getRequest("other_filter", '')],
+ "q" => getRequest("q", ''),
+ 'numPerPage' => $numPerPage,
+ 'page' => getPageId(),
+ 'translated' => getRequestInt('translated', 0),
+ //... more extended filters
+];
+//debug_var($objAProductController->getFilterConditions());
+
+$totalResults = $objAProductController->getTotal($conditions);
+$item_list = $objAProductController->getList($conditions);
+
+list($page_collection, $tb_page, $total_pages) = Paging::paging_template($totalResults, $numPerPage);
+
+return [
+ "total" => $totalResults,
+ "item_list" => $item_list,
+ "pagination" => [
+ 'collection' => $page_collection,
+ 'html' => $tb_page,
+ 'total_pages' => $total_pages,
+ ],
+
+ "list_category" => [
+ [
+ 'id' => 1,
+ 'title' => 'Màn hình máy tính',
+ 'url' => '/admin/product?category=9',
+ 'parentId' => 0,
+ 'isParent' => 1,
+ 'children' => [
+ [
+ 'id' => 10,
+ 'title' => 'Màn hình theo khoảng giá',
+ 'url' => '/admin/product?category=148',
+ 'parentId' => 1,
+ 'isParent' => 0,
+ 'totalProduct' => 0,
+ 'children' => [
+
+ ]
+ ],
+ [
+ 'id' => 11,
+ 'title' => 'Màn Hình Theo Kích Thước',
+ 'url' => '/admin/product?category=54',
+ 'parentId' => 1,
+ 'isParent' => 0,
+ 'totalProduct' => 0,
+ 'children' => [
+ [
+ 'id' => 148,
+ 'title' => '17 inch - 21.5 inch',
+ 'url' => '/admin/product?category=148',
+ 'parentId' => 11,
+ 'isParent' => 0,
+ 'totalProduct' => 5,
+ ],
+ [
+ 'id' => 66,
+ 'title' => '22 inch - 24 inch',
+ 'url' => '/admin/product?category=66',
+ 'parentId' => 11,
+ 'isParent' => 0,
+ 'totalProduct' => 41,
+ ],
+ [
+ 'id' => 67,
+ 'title' => '25 inch - 27 inch',
+ 'url' => '/admin/product?category=67',
+ 'parentId' => 11,
+ 'isParent' => 0,
+ 'totalProduct' => 42,
+ ],
+ [
+ 'id' => 68,
+ 'title' => '28 inch - 32 inch',
+ 'url' => '/admin/product?category=68',
+ 'parentId' => 11,
+ 'isParent' => 0,
+ 'totalProduct' => 11,
+ ]
+ ]
+ ]
+
+ ]
+ ],
+ [
+ 'id' => 65,
+ 'title' => 'PC, Workstation',
+ 'url' => '/admin/product?category=65',
+ 'parentId' => 0,
+ 'isParent' => 1,
+ 'children' => []
+ ],
+ [
+ 'id' => 3,
+ 'title' => 'Gaming Gear',
+ 'url' => '/admin/product?category=3',
+ 'parentId' => 0,
+ 'isParent' => 1,
+ 'children' => []
+ ],[
+ 'id' => 4,
+ 'title' => 'CPU - Bộ Vi Xử Lý',
+ 'url' => '/admin/product?category=4',
+ 'parentId' => 0,
+ 'isParent' => 1,
+ 'children' => []
+ ]
+
+ ],
+
+ "brand_letters" => [
+ [
+ 'key' => 'A',
+ 'url' => '/ajax/brand.php?action=show-brand-list&letter=A&popup=1',
+ 'total' => 13,
+ ],
+ [
+ 'key' => 'B',
+ 'url' => '/ajax/brand.php?action=show-brand-list&letter=B&popup=1',
+ 'total' => 2,
+ ],
+ [
+ 'key' => 'C',
+ 'url' => '/ajax/brand.php?action=show-brand-list&letter=C&popup=1',
+ 'total' => 5,
+ ]
+
+ ],
+
+ "list_brands" => [
+
+ 'A' => [
+ [
+ 'id' => 1,
+ 'name' => 'ABS',
+ 'letter' => 'A',
+ 'url' => '/admin/?brand=83&opt=product',
+ 'product' => 1,
+ ],
+ [
+ 'id' => 2,
+ 'name' => 'ACE GAMING',
+ 'letter' => 'A',
+ 'url' => '/admin/?brand=83&opt=product',
+ 'product' => 6,
+ ],
+ [
+ 'id' => 3,
+ 'name' => 'ADATA',
+ 'letter' => 'A',
+ 'url' => '/admin/?brand=83&opt=product',
+ 'product' => 15,
+ ]
+ ],
+
+ 'B' => [
+ [
+ 'id' => 3,
+ 'name' => 'BE QUIET ',
+ 'letter' => 'B',
+ 'url' => '/admin/?brand=83&opt=product',
+ 'product' => 2,
+ ],
+ [
+ 'id' => 3,
+ 'name' => 'BENQ',
+ 'letter' => 'B',
+ 'url' => '/admin/?brand=83&opt=product',
+ 'product' => 6,
+ ],
+ ],
+
+ 'C' => [
+ [
+ 'id' => 3,
+ 'name' => 'CISCO',
+ 'letter' => 'C',
+ 'url' => '/admin/?brand=83&opt=product',
+ 'product' => 2,
+ ],
+ [
+ 'id' => 3,
+ 'name' => 'Colorful',
+ 'letter' => 'C',
+ 'url' => '/admin/?brand=83&opt=product',
+ 'product' => 6,
+ ],
+ [
+ 'id' => 3,
+ 'name' => 'COOLER MASTER',
+ 'letter' => 'C',
+ 'url' => '/admin/?brand=83&opt=product',
+ 'product' => 6,
+ ],
+ [
+ 'id' => 3,
+ 'name' => 'COOLMOON',
+ 'letter' => 'C',
+ 'url' => '/admin/?brand=83&opt=product',
+ 'product' => 6,
+ ],
+ [
+ 'id' => 3,
+ 'name' => 'CORSAIR',
+ 'letter' => 'C',
+ 'url' => '/admin/?brand=83&opt=product',
+ 'product' => 6,
+ ],
+ ]
+ ]
+
+];
diff --git a/src/pages/product/list_competitor.php b/src/pages/product/list_competitor.php
new file mode 100644
index 0000000..a814366
--- /dev/null
+++ b/src/pages/product/list_competitor.php
@@ -0,0 +1 @@
+
+
+
+
+ Đơn
+ hàng
+ mới
+
+
+
+
+
+
+
10.000.000
+
+
+
+
+
+ 10%
+
+
+
Hôm qua
+
+
+
+
+
+
+
Khách
+ hàng
+ liên hệ qua website
+
+
+
+
+
+
+
10.000.000
+
+
+
+
+
+ 10%
+
+
+
Hôm qua
+
+
+
+
+
+
+
Đơn
+ trả
+ góp
+
+
+
+
+
+
+
10.000.000
+
+
+
+
+
+ 10%
+
+
+
Hôm qua
+
+
+
+
+
+
+
Đơn
+ hoàn
+ trả
+
+
+
+
+
+
+
10.000.000
+
+
+
+
+
+ 10%
+
+
+
Hôm qua
+
+
+
+
+
+
+
32.4k
+
Doanh thu trong tuần này
+
+
+
+ 12%
+
+
+
+
+
+
+
+
+
+
+ Last 7 days
+ Yesterday
+ Today
+ Last 7 days
+ Last 30 days
+ Last 90 days
+
+
+
+ Users Report
+
+
+
+
+
+
+
+
+
+
3.4k
+
+
+
Truy cập web trong tuần này
+
+
+
+
+
+ 42.5%
+
+
+
+
+
+
+ Lượt truy cập:
+
+ 1.458
+
+
+
+ Người xem:
+ 2.130
+
+
+
+
+
+
+
+
+ Last 7 days
+ Yesterday
+ Today
+ Last 7 days
+ Last 30 days
+ Last 90 days
+
+
+
+ Leads Report
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/template/home/home.html b/template/home/home.html
new file mode 100644
index 0000000..62a97b4
--- /dev/null
+++ b/template/home/home.html
@@ -0,0 +1,1071 @@
+
+
+
+
+
+
+
+
+
+
+ sản phẩm được đánh giá tốt nhất
+
+
+
+
+
+ Tổng hợp các sản phẩm hot theo xu hướng và có nhiều lượt review và
+ đánh giá nhất hiện nay
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tìm theo danh mục
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Doanh nghiệp NỔI BẬT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SẢN PHẨM ĐÃ LƯU
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/template/javascript/index.html b/template/javascript/index.html
new file mode 100644
index 0000000..a3734ca
--- /dev/null
+++ b/template/javascript/index.html
@@ -0,0 +1,2 @@
+
+
diff --git a/template/shared/footer.html b/template/shared/footer.html
new file mode 100644
index 0000000..1d477a4
--- /dev/null
+++ b/template/shared/footer.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
Giới thiệu
+ BESTPC là kênh tổng hợp tin khuyến mãi chuyên nghiệp đầu tiên ở Việt Nam,
+ cho phép bạn tìm
+ kiếm mã giảm giá, tin ưu đãi
+ từ nghành hàng laptop, thiết bị điện tử, máy tính, xây dựng cấu hình PC chuyên nghiệp. Và
+ còn rất nhiều mã giảm giá độc
+ quyền dành riêng cho bạn.
+
+
+
+
+
diff --git a/template/shared/header.html b/template/shared/header.html
new file mode 100644
index 0000000..a129a82
--- /dev/null
+++ b/template/shared/header.html
@@ -0,0 +1,458 @@
+
+
+
+
+
+
+
+
+
+
+
+
Bạn đang ở
+
+
+ Hà Nội
+ TP HCM
+ Đà Nẵng
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/template/theme.html b/template/theme.html
new file mode 100644
index 0000000..d61cd7b
--- /dev/null
+++ b/template/theme.html
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+ BESTPC - Trang chủ
+
+
+
+
+
+
+
+
+
+ {% include shared/header %}
+
+ {{ page_content }}
+
+ {% include shared/footer %}
+
+
+
+{% include javascript/index %}
+
+
+
+
+
+
+