This commit is contained in:
2025-10-04 11:46:59 +07:00
commit 97427d7cff
498 changed files with 47596 additions and 0 deletions

7
.htaccess Normal file
View File

@@ -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|jpeg|ico)$
RewriteRule ^(.*)$ /index.php [QSA,L]

74
README.md Normal file
View File

@@ -0,0 +1,74 @@
<h1>Hướng dẫn</h1>
<p>Link thiết kế: <a href="https://www.figma.com/design/qqipE1AsUMl31zk6wezduD/X-Store?node-id=110-11&t=eJARcn1sDXYeb031-0" target="_blank"> <strong>Giao diện Xstore</strong> </a></p>
<p>Repo: <a href="https://repo.hurasoft.com/tieptk/xstore" target="_blank">https://repo.hurasoft.com/tieptk/xstore</a></p>
<p>Theo dõi tiến độ: <a href="https://docs.google.com/spreadsheets/d/1Po3ANsG00pm_Y3dnrwuV81cidTCCqepYmR3yPDlIq6c/edit#gid=0" target="_blank">https://docs.google.com/spreadsheets/d/1Po3ANsG00pm_Y3dnrwuV81cidTCCqepYmR3yPDlIq6c/edit#gid=0</a></p>
<h2>Cài đặt hệ thống</h2>
<p>Test và làm việc chính tại web: <a href="http://local.hura8_admin/"> http://local.xstore/</a> </p>
<ul>
<li>Tải phần mềm XAMPP tại <a href="https://www.apachefriends.org/download.html" target="_blank">https://www.apachefriends.org/download.html</a> để chạy PHP</li>
<li>Chỉnh file hosts trong máy tính C:\Windows\System32\drivers\etc\hosts:
<pre><code>
127.0.0.1 local.xstore/
</code></pre>
</li>
<li>
Cài đặt ../xampp/apache/conf/extra/httpd-vhosts.conf của apache trong XAMPP
<pre><code>
&lt;VirtualHost *:80&gt;
DocumentRoot "/thuc-muc-check-out/xstore"
ServerName local.hura8_admin
&lt;Directory "/thuc-muc-check-out/xstore/"&gt;
Require all granted
&lt;/Directory&gt;
&lt;/VirtualHost&gt;
</code></pre>
</li>
<li>
Cài đặt ../xampp/apache/conf/httpd.conf của apache trong XAMPP
<pre><code>
# 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
</code></pre>
</li>
</ul>
<h2>Cấu trúc thư mục</h2>
<ul>
<li>/template: các file template html chia theo module/view</li>
<li>/data: cung cấp dữ liệu cho template và hiển thị dữ liệu trên template qua code Liquid <a href="https://shopify.github.io/liquid/" target="_blank">https://shopify.github.io/liquid/</a></li>
<li>/assets: lưu các file ảnh/css/js dùng cho giao diện</li>
<li>/package: thư viện code PHP hỗ trợ</li>
<li>/inc: các code PHP hỗ trợ</li>
<li>index.php: file gốc để truy cập nội dung</li>
<li>ajax.php: file gốc để gọi ajax</li>
</ul>
<h2>Cài đặt Composer (dùng tải package của PHP)</h2>
<p>Xem hướng dẫn: <a href="https://getcomposer.org/doc/00-intro.md#installation-windows" target="_blank">https://getcomposer.org/doc/00-intro.md#installation-windows</a> </p>
<p>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.</p>
<pre><code>
> cd /thuc-muc-check-out/xstore/package
> composer i
</code></pre>
<h2> Sử dụng <u>Tailwind</u> để style giao diện.</h2>
<ul>
<li>[Dùng chính] <a href="https://daisyui.com/components/" target="_blank">https://daisyui.com/components/</a></li>
<li><a href="https://tailwindui.com/" target="_blank"> https://tailwindui.com/ </a></li>
<li><a href="https://tailblocks.cc/" target="_blank"> https://tailblocks.cc/ </a></li>
<li><a href="https://www.hyperui.dev/" target="_blank"> https://www.hyperui.dev/ </a></li>
<li><a href="https://flowbite.com/docs/plugins/charts/" target="_blank"> Biểu đồ </a></li>
</ul>

17
_shared.php Normal file
View File

@@ -0,0 +1,17 @@
<?php
const ROOT_DIR = __DIR__;
const CONFIG_DIR = ROOT_DIR . '/inc/config';
const IMAGE_FILE_SEPARATOR = "-";
define("CURRENT_TIME", time());
const STATIC_DOMAIN = "http://hura8.hurasoft.com";
const ENABLE_DB_DEBUG = true;
const LANGUAGE = 'vi';
const IS_DEFAULT_LANGUAGE = true;
include ROOT_DIR."/inc/common.php";
include ROOT_DIR."/inc/fun.db.php";
// start autoload
init_autoload();

7
ajax.php Normal file
View File

@@ -0,0 +1,7 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
include __DIR__."/_shared.php";

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 KiB

BIN
assets/images/congcuseo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

BIN
assets/images/icon_2025.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

BIN
assets/images/keotha.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/images/mygear.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 KiB

314
assets/js/main.js Normal file
View File

@@ -0,0 +1,314 @@
$(document).ready(function () {
// menu
ShowMenu();
// click tab
enableSmoothScroll("#tab a", 800);
btnPricing();
showInput();
showSelect();
})
function ShowMenu() {
$menu = $('#js-show-menu .item-menu a');
$box_menu = $('#box-menu');
$menu.on('click', function () {
$data_menu = $(this).attr('data-menu');
$('.content-menu').addClass('hidden')
$('#' + $data_menu).removeClass('hidden');
if ($(this).hasClass('active')) {
$(this).removeClass('active');
$('#background-opacity').addClass('hidden');
$('#box-menu').addClass('translate-y-[-100%]');
$(this).find($('i')).removeClass('fa-sort-up mt-[10px]');
} else {
$(this).addClass('active');
$('#background-opacity').removeClass('hidden');
$('#box-menu').removeClass('translate-y-[-100%]');
$(this).find($('i')).addClass('fa-sort-up mt-[10px]');
}
})
}
function closeBackgorund() {
$('#background-opacity').addClass('hidden');
$('#box-menu').addClass('translate-y-[-100%]');
$('#js-show-menu .item-menu').find($('i')).removeClass('fa-sort-up mt-[10px]');
}
function toggleFaq(el) {
var $el = $(el);
var $content = $el.next(".content-faq");
var $icon = $el.find("i");
if ($content.hasClass("open")) {
// Đóng
$content.removeClass("open").css("max-height", 0);
$icon.removeClass("rotate-180");
} else {
// Đóng các FAQ khác (nếu muốn accordion)
$(".content-faq").removeClass("open").css("max-height", 0);
$(".title i").removeClass("rotate-180");
// Mở
$content.addClass("open").css("max-height", $content.prop("scrollHeight") + "px");
$icon.addClass("rotate-180");
}
}
function enableSmoothScroll(selector, speed) {
$(selector).click(function (e) {
e.preventDefault();
var target = $($(this).attr("href"));
if (target.length) {
$("html, body").animate(
{ scrollTop: target.offset().top - 100 },
speed
);
}
});
}
function btnPricing() {
$('#billingToggle').on('change', function () {
if ($(this).is(':checked')) {
// Annually
$('#labelMonthly').removeClass('text-black').addClass('text-gray-500');
$('#labelAnnually').removeClass('text-gray-500').addClass('text-black');
$('#toggleBox').removeClass('border-gray-300').addClass('border-black')
.addClass('after:translate-x-6');
} else {
// Monthly
$('#labelAnnually').removeClass('text-black').addClass('text-gray-500');
$('#labelMonthly').removeClass('text-gray-500').addClass('text-black');
$('#toggleBox').removeClass('border-black').addClass('border-gray-300')
.removeClass('after:translate-x-6');
}
})
}
function showCompare() {
$('.item-compare i').toggleClass('fa-plus fa-minus');
$('.content-compare').toggleClass('hidden mt-[15px] pt-[15px]');
}
function showInput() {
$('.form-input input').on('focus', function () {
$lable = $(this).closest($('.form-input')).find($('label'));
$error = $(this).closest($('.check-form')).find('.note-error');
$lable.toggleClass('top-[-1.75rem] text-[15px] text-[12px]');
$error.html('');
})
}
function showSelect() {
$('.form-select').on('click', function () {
$(this).find($('label')).removeClass('text-[15px] top-[2px]')
$(this).find($('label')).addClass('top-[-1.75rem] text-[12px]');
})
}
function checkInputName() {
var error = false;
var check_name = document.getElementById('first_name').value;
var $name = $('#first_name');
var item_name = $name.parents(".check-form");
if (check_name.length < 4) {
item_name.find($('.note-error')).html("Tên quá ngắn");
error = true;
} else if (check_name.indexOf('<script') > -1) {
item_name.find($('.note-error')).html("Họ tên chứa các ký tự không hợp lệ, bạn vui lòng kiểm tra lại");
error = true;
} else {
item_name.find($('.note-error')).html("");
}
return error
}
function checkInputPhone() {
var number_regex1 = /^[0]\d{9}$/i;
var number_regex2 = /^[0]\d{10}$/i;
var check_tel = document.getElementById('phone').value;
var $tel = $("#phone");
var item_tel = $tel.parents(".check-form");
if (check_tel.length < 4) {
item_tel.addClass('error')
item_tel.find($('.note-error')).html("Bạn chưa nhập SĐT");
error = true;
} else if (!check_tel.match(number_regex1) && !check_tel.match(number_regex2)) {
item_tel.addClass('error')
item_tel.find($('.note-error')).html("Số điện thoại chưa chính xác");
error = true;
} else {
item_tel.removeClass('error');
item_tel.find($('.note-error')).html("");
}
return error
}
function checkInputEmail() {
var check_email = document.getElementById('email').value;
var $email = $("#email");
var item_email = $email.parents(".check-form");
if (check_email.length < 4) {
item_email.addClass('error')
item_email.find($('.note-error')).html("Bạn chưa nhập Email");
error = true;
} else if (!validateEmail(check_email)) {
item_email.addClass('error')
item_email.find($('.note-error')).html("Địa chỉ email chưa chính xác");
error = true;
} else {
item_email.removeClass('error');
item_email.find($('.note-error')).html("");
}
return error
}
function checkProvince() {
var check_province = document.getElementById('province').value;
var $check_province = $('#province');
var item_province = $check_province.parents('.check-form');
if (check_province == 0) {
item_province.addClass('error');
item_province.find($('.note-error')).html("Bạn chưa chọn Tỉnh/Thành phố");
error = true;
} else {
item_province.removeClass('error');
item_province.find($('.note-error')).html("");
}
return error;
}
function checkInputShop() {
var error = false;
var check_shop = document.getElementById('shop').value;
var $shop = $('#shop');
var item_shop = $shop.parents(".check-form");
if (check_shop.length < 4) {
item_shop.find($('.note-error')).html("Tên quá ngắn");
error = true;
} else if (check_shop.indexOf('<script') > -1) {
item_shop.find($('.note-error')).html("Tên chứa các ký tự không hợp lệ, bạn vui lòng kiểm tra lại");
error = true;
} else {
item_shop.find($('.note-error')).html("");
}
return error
}
function checkformTrial() {
var error = false;
checkInputName();
checkInputPhone();
checkInputEmail();
checkInputShop();
checkProvince();
if (error) {
alert('Vui lòng kiểm tra lại thông tin');
return false;
} else {
$(".button-send").css("pointer-events", "none");
$(".button-send").html("ĐANG XỬ LÝ...");
localStorage.getItem("Key_voucher");
return true;
}
}
function validateEmail(sEmail) {
var filter = /^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/;
if (filter.test(sEmail)) {
return true;
}
else {
return false;
}
}
function initPasswordStrength() {
const inputPassword = document.getElementById('password');
const toggleBtn = document.getElementById('togglePassword');
const strengthBar = document.getElementById('strengthBar');
const strengthText = document.getElementById('strengthText');
const submitBtn = document.getElementById('submitBtn');
// Đánh giá mật khẩu
function evaluateStrength(password) {
let score = 0;
if (password.length >= 8) score++;
if (/[A-Z]/.test(password)) score++;
if (/\d/.test(password)) score++;
if (/[^A-Za-z0-9]/.test(password)) score++;
return score; // 0..4
}
// Render UI
function renderStrength(score) {
let label = "—", color = "bg-gray-300", width = "0%";
if (score === 1) { label = "yếu"; color = "bg-red-500"; width = "25%"; }
if (score === 2) { label = "trung bình"; color = "bg-yellow-500"; width = "50%"; }
if (score === 3) { label = "khá"; color = "bg-lime-500"; width = "75%"; }
if (score === 4) { label = "mạnh"; color = "bg-green-500"; width = "100%"; }
const levels = [
{ label: "—", color: "bg-gray-300", width: "0%" },
{ label: "yếu", color: "bg-red-500", width: "25%" },
{ label: "trung bình", color: "bg-yellow-500", width: "50%" },
{ label: "khá", color: "bg-lime-500", width: "75%" },
{ label: "mạnh", color: "bg-green-500", width: "100%" }
];
const level = levels[score];
strengthBar.className = "h-1 rounded transition-all " + level.color;
strengthBar.style.width = width;
strengthText.textContent = "Độ mạnh của mật khẩu: " + level.label;
submitBtn.disabled = score < 3;
}
// Event nhập mật khẩu
inputPassword.addEventListener('input', e => {
$('#check-pass').removeClass('hidden');
const score = evaluateStrength(e.target.value);
renderStrength(score);
});
// Toggle hiển thị/ẩn mật khẩu
toggleBtn.addEventListener('click', () => {
inputPassword.type = inputPassword.type === 'password' ? 'text' : 'password';
});
renderStrength(0); // khởi tạo
}
initPasswordStrength();

202
assets/script/style.css Normal file
View File

@@ -0,0 +1,202 @@
:root {
--color-blue: #0f5edd;
--text-green: #00c75d;
}
::-webkit-scrollbar-track {
background-color: #f5f5f5;
border-radius: 10px;
}
::-webkit-scrollbar {
width: 7px;
background-color: #fff;
}
::-webkit-scrollbar-thumb {
background: #00112b;
border-radius: 20px;
background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(0.7, #00112b), color-stop(0.5, transparent), to(transparent));
}
body {
font-family: "Roboto", sans-serif;
font-size: 14px;
background: #fcfcfc;
max-width: 1920px;
display: flex;
flex-direction: column;
min-height: 100vh;
justify-content: space-between;
}
html {
font-family: "Roboto", sans-serif;
}
.clearfix::after {
content: "";
clear: both;
display: table;
}
.container {
max-width: 1200px !important;
margin: 0 auto;
}
.icon_2025 {
display: block;
background: url(../images/icon_2025.png) no-repeat;
background-size: 92px 82px;
}
.icon_2025.checkbox {
width: 18px;
height: 23px;
background-position: 0 0;
}
.icon_2025.boxreview {
width: 18px;
height: 23px;
background-position: -34px 0;
}
.icon_2025.cart {
width: 23px;
height: 23px;
background-position: -68px 0;
}
.icon_2025.up {
width: 22px;
height: 22px;
background-position: 1px -29px;
}
.icon_2025.thuhut {
width: 22px;
height: 22px;
background-position: -34px -29px;
}
.icon_2025.tietkiem {
width: 22px;
height: 22px;
background-position: -69px -29px;
}
.icon_2025.up-sale {
width: 22px;
height: 22px;
background-position: 1px -58px;
}
.icon_2025.setting {
width: 22px;
height: 22px;
background-position: -33px -58px;
}
.icon_2025.support {
width: 24px;
height: 24px;
background-position: -68px -60px;
}
.homepage .banner {
background: url(../images/background-banner.jpg) no-repeat;
background-size: 100% 100%;
min-height: 920px;
}
.homepage .background-tamnhin {
background: url(../images/background-tamnhin.jpg) no-repeat;
background-size: 100% 100%;
background-position: center;
}
.effect-image {
position: relative;
overflow: hidden;
}
.effect-image:before {
position: absolute;
top: 0;
left: -100%;
z-index: 2;
display: block;
content: "";
width: 50%;
height: 100%;
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.3) 100%);
transform: skewX(-25deg);
cursor: pointer;
}
.effect-image:hover:before {
animation: image 1.1s;
}
@keyframes image {
100% {
left: 125%;
}
}
.breadcrumb li:last-child i {
display: none;
}
.breadcrumb li a span:hover {
color: var(--color-blue);
font-weight: bold;
}
.background-pricing {
background: url(../images/background-pricing.png) no-repeat;
background-size: 100% 100%;
background-position: center center;
}
.content-compare li {
margin-bottom: 10px;
position: relative;
padding-left: 15px;
color: #5d5d69;
}
.content-compare li::before {
position: absolute;
content: "";
left: 0;
top: 7px;
width: 5px;
height: 5px;
background: #000;
clip-path: polygon(100% 0, 0% 100%, 100% 100%);
}
.background-free {
background: url(../images/background-banner.jpg) no-repeat;
background-size: 100% 100%;
min-height: 640px;
}
.form-input label::before {
position: absolute;
content: "*";
right: -10px;
top: -3px;
color: red;
}
.form-select select:focus {
outline: none;
}
.form-select option {
padding: 0 10px;
}
.form-select label::before {
position: absolute;
content: "*";
right: -10px;
top: -3px;
color: red;
}
.page-login {
background: url(../images/background-login.jpg) no-repeat;
background-size: 100% 100%;
background-position: center;
}/*# sourceMappingURL=style.css.map */

View File

@@ -0,0 +1 @@
{"version":3,"sources":["style.scss","style.css"],"names":[],"mappings":"AAAA;EACI,qBAAA;EACA,qBAAA;ACCJ;;ADCA;EACI,yBAAA;EACA,mBAAA;ACEJ;;ADAA;EACI,UAAA;EACA,sBAAA;ACGJ;;ADDA;EACI,mBAAA;EACA,mBAAA;EACA,gIAAA;ACIJ;;ADKA;EACI,iCAAA;EACA,eAAA;EACA,mBAAA;EACA,iBAAA;EACA,aAAA;EACA,sBAAA;EACA,iBAAA;EACA,8BAAA;ACFJ;;ADIA;EACI,iCAAA;ACDJ;;ADGA;EACI,WAAA;EACA,WAAA;EACA,cAAA;ACAJ;;ADEA;EACI,4BAAA;EACA,cAAA;ACCJ;;ADEA;EACI,cAAA;EACA,kDAAA;EACA,0BAAA;ACCJ;ADAI;EACI,WAAA;EACA,YAAA;EACA,wBAAA;ACER;ADAI;EACI,WAAA;EACA,YAAA;EACA,4BAAA;ACER;ADAI;EACI,WAAA;EACA,YAAA;EACA,4BAAA;ACER;ADAI;EACI,WAAA;EACA,YAAA;EACA,8BAAA;ACER;ADAI;EACI,WAAA;EACA,YAAA;EACA,gCAAA;ACER;ADAI;EACI,WAAA;EACA,YAAA;EACA,gCAAA;ACER;ADAI;EACI,WAAA;EACA,YAAA;EACA,8BAAA;ACER;ADAI;EACI,WAAA;EACA,YAAA;EACA,gCAAA;ACER;ADAI;EACI,WAAA;EACA,YAAA;EACA,gCAAA;ACER;;ADGI;EACI,0DAAA;EACA,0BAAA;EACA,iBAAA;ACAR;ADEI;EACI,2DAAA;EACA,0BAAA;EACA,2BAAA;ACAR;;ADIA;EACI,kBAAA;EACA,gBAAA;ACDJ;;ADIA;EACI,kBAAA;EACA,MAAA;EACA,WAAA;EACA,UAAA;EACA,cAAA;EACA,WAAA;EACA,UAAA;EACA,YAAA;EACA,+FAAA;EACA,wBAAA;EACA,eAAA;ACDJ;;ADIA;EACI,qBAAA;ACDJ;;ADIA;EACI;IACI,UAAA;ECDN;AACF;ADIA;EACI,aAAA;ACFJ;;ADKA;EACI,wBAAA;EACA,iBAAA;ACFJ;;ADKA;EACI,2DAAA;EACA,0BAAA;EACA,kCAAA;ACFJ;;ADMI;EACI,mBAAA;EACA,kBAAA;EACA,kBAAA;EACA,cAAA;ACHR;ADIQ;EACI,kBAAA;EACA,WAAA;EACA,OAAA;EACA,QAAA;EACA,UAAA;EACA,WAAA;EACA,gBAAA;EACA,8CAAA;ACFZ;;ADMA;EACI,0DAAA;EACA,0BAAA;EACA,iBAAA;ACHJ;;ADQQ;EACI,kBAAA;EACA,YAAA;EACA,YAAA;EACA,SAAA;EACA,UAAA;ACLZ;;ADWI;EACI,aAAA;ACRR;ADUI;EACI,eAAA;ACRR;ADWQ;EACI,kBAAA;EACA,YAAA;EACA,YAAA;EACA,SAAA;EACA,UAAA;ACTZ;;ADcA;EACI,yDAAA;EACA,0BAAA;EACA,2BAAA;ACXJ","file":"style.css"}

214
assets/script/style.scss Normal file
View File

@@ -0,0 +1,214 @@
:root {
--color-blue: #0f5edd;
--text-green: #00c75d;
}
::-webkit-scrollbar-track {
background-color: #f5f5f5;
border-radius: 10px;
}
::-webkit-scrollbar {
width: 7px;
background-color: #fff;
}
::-webkit-scrollbar-thumb {
background: #00112b;
border-radius: 20px;
background-image: -webkit-gradient(
linear,
0 0,
0 100%,
color-stop(0.7, #00112b),
color-stop(0.5, transparent),
to(transparent)
);
}
body {
font-family: "Roboto", sans-serif;
font-size: 14px;
background: #fcfcfc;
max-width: 1920px;
display: flex;
flex-direction: column;
min-height: 100vh;
justify-content: space-between;
}
html {
font-family: "Roboto", sans-serif;
}
.clearfix::after {
content: "";
clear: both;
display: table;
}
.container {
max-width: 1200px !important;
margin: 0 auto;
}
.icon_2025 {
display: block;
background: url(../images/icon_2025.png) no-repeat;
background-size: 92px 82px;
&.checkbox {
width: 18px;
height: 23px;
background-position: 0 0;
}
&.boxreview {
width: 18px;
height: 23px;
background-position: -34px 0;
}
&.cart {
width: 23px;
height: 23px;
background-position: -68px 0;
}
&.up {
width: 22px;
height: 22px;
background-position: 1px -29px;
}
&.thuhut {
width: 22px;
height: 22px;
background-position: -34px -29px;
}
&.tietkiem {
width: 22px;
height: 22px;
background-position: -69px -29px;
}
&.up-sale {
width: 22px;
height: 22px;
background-position: 1px -58px;
}
&.setting {
width: 22px;
height: 22px;
background-position: -33px -58px;
}
&.support {
width: 24px;
height: 24px;
background-position: -68px -60px;
}
}
.homepage {
.banner {
background: url(../images/background-banner.jpg) no-repeat;
background-size: 100% 100%;
min-height: 920px;
}
.background-tamnhin {
background: url(../images/background-tamnhin.jpg) no-repeat;
background-size: 100% 100%;
background-position: center;
}
}
.effect-image {
position: relative;
overflow: hidden;
}
.effect-image:before {
position: absolute;
top: 0;
left: -100%;
z-index: 2;
display: block;
content: "";
width: 50%;
height: 100%;
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.3) 100%);
transform: skewX(-25deg);
cursor: pointer;
}
.effect-image:hover:before {
animation: image 1.1s;
}
@keyframes image {
100% {
left: 125%;
}
}
.breadcrumb li:last-child i {
display: none;
}
.breadcrumb li a span:hover {
color: var(--color-blue);
font-weight: bold;
}
.background-pricing {
background: url(../images/background-pricing.png) no-repeat;
background-size: 100% 100%;
background-position: center center;
}
.content-compare {
li {
margin-bottom: 10px;
position: relative;
padding-left: 15px;
color: #5d5d69;
&::before {
position: absolute;
content: "";
left: 0;
top: 7px;
width: 5px;
height: 5px;
background: #000;
clip-path: polygon(100% 0, 0% 100%, 100% 100%);
}
}
}
.background-free {
background: url(../images/background-banner.jpg) no-repeat;
background-size: 100% 100%;
min-height: 640px;
}
.form-input {
label {
&::before {
position: absolute;
content: "*";
right: -10px;
top: -3px;
color: red;
}
}
}
.form-select {
select:focus {
outline: none;
}
option {
padding: 0 10px;
}
label {
&::before {
position: absolute;
content: "*";
right: -10px;
top: -3px;
color: red;
}
}
}
.page-login {
background: url(../images/background-login.jpg) no-repeat;
background-size: 100% 100%;
background-position: center;
}

View File

@@ -0,0 +1 @@
<?php

1
data/features/create.php Normal file
View File

@@ -0,0 +1 @@
<?php

1
data/features/grow.php Normal file
View File

@@ -0,0 +1 @@
<?php

1
data/features/market.php Normal file
View File

@@ -0,0 +1 @@
<?php

View File

@@ -0,0 +1 @@
<?php

1
data/free-trial/home.php Normal file
View File

@@ -0,0 +1 @@
<?php

1
data/home/home.php Normal file
View File

@@ -0,0 +1 @@
<?php

1
data/login/home.php Normal file
View File

@@ -0,0 +1 @@
<?php

1
data/menu.php Normal file
View File

@@ -0,0 +1 @@
<?php

1
data/pricing/home.php Normal file
View File

@@ -0,0 +1 @@
<?php

View File

@@ -0,0 +1,261 @@
<?php
namespace Hura8;
/**
* Before attempting to make new filter, see built-in filters: https://shopify.github.io/liquid/
*/
class AdminTemplateFilter
{
/**
*
* @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 . "/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" => "<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>']) ;
}
}

212
inc/Hura8/AppAdmin.php Normal file
View File

@@ -0,0 +1,212 @@
<?php
namespace Hura8;
use Liquid\Liquid;
use Liquid\Template as LiquidTemplate;
class AppAdmin
{
protected $tpl_path = "template";
protected $current_route_info = [
"module" => '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;
}else{
$data = ['file data '. $module_file .' not found!'];
}
$global_data = [
"module" => $this->current_route_info['module'],
"view" => $this->current_route_info['view'],
"url" => $this->current_route_info['url'],
"main_menu" => include_once ROOT_DIR."/data/menu.php",
];
$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() {
return join(DIRECTORY_SEPARATOR, [
"data",
$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( AdminTemplateFilter::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: <br />
- Chi tiết lỗi: %s<br />
- File template: %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);
}
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Hura8\Components\Analytics\Controller;
use Hura8\Components\Analytics\Model\TrackingModel;
class bTrackingController
{
protected $objTrackingModel;
public function __construct()
{
$this->objTrackingModel = new TrackingModel();
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Hura8\Components\Analytics\Model;
class TrackDeviceInfo
{
public $ip_address;
public $user_agent;
public $referrer;
public $is_mobile;
public function __construct(string $ip_address, string $user_agent, string $referrer, bool $is_mobile)
{
$this->ip_address = $ip_address;
$this->user_agent = $user_agent;
$this->referrer = $referrer;
$this->is_mobile = $is_mobile;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Hura8\Components\Analytics\Model;
class TrackRouteInfo
{
public $url;
public $module;
public $view;
public $view_id;
public $query;
public function __construct(
string $url,
string $module,
string $view,
string $view_id,
array $query = []
)
{
$this->url = $url;
$this->module = $module;
$this->view = $view;
$this->view_id = $view_id;
$this->query = $query;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Hura8\Components\Analytics\Model;
class TrackUserInfo
{
public $web_user_id;
public $customer_id;
public $is_crawler;
public function __construct(string $web_user_id, string $customer_id, bool $is_crawler)
{
$this->web_user_id = $web_user_id;
$this->customer_id = $customer_id;
$this->is_crawler = $is_crawler ? 1 : 0;
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Hura8\Components\Analytics\Model;
use Hura8\Interfaces\AppResponse;
use Hura8\Interfaces\iEntityModel;
use Hura8\System\Model\aEntityBaseModel;
class TrackingModel extends aEntityBaseModel implements iEntityModel
{
protected $tb_track_ip = "tb_analyics_track_ip";
public function __construct() {
parent::__construct(
"analyics_user_log"
);
}
protected function extendedFilterOptions(): array
{
// TODO: Implement extendedFilterOptions() method.
}
protected function _buildQueryConditionExtend(array $filter_condition): ?array
{
// TODO: Implement _buildQueryConditionExtend() method.
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Hura8\Components\Article\AdminController;
use Hura8\Components\Article\Controller\bArticleCategoryController;
use Hura8\Interfaces\iEntityAdminCategoryController;
use Hura8\Traits\AdminEntityCategoryControllerTraits;
class AArticleCategoryController extends bArticleCategoryController implements iEntityAdminCategoryController
{
use AdminEntityCategoryControllerTraits;
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Hura8\Components\Article\AdminController;
use Hura8\Components\Article\Controller\bArticleController;
use Hura8\Interfaces\iEntityAdminController;
use Hura8\Traits\AdminEntityBaseControllerTraits;
class AArticleController extends bArticleController implements iEntityAdminController
{
use AdminEntityBaseControllerTraits;
public function updateTableInfo($item_id, array $new_item_info) {
if(!$this->isDefaultLanguage()) {
return $this->iEntityLanguageModel->update($item_id, $new_item_info);
}
return $this->objArticleModel->updateTableInfo($item_id, $new_item_info);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Hura8\Components\Article\Controller;
use Hura8\Components\Article\Model\ArticleCategoryLanguageModel;
use Hura8\Components\Article\Model\ArticleCategoryModel;
use Hura8\System\Controller\aCategoryBaseController;
class bArticleCategoryController extends aCategoryBaseController
{
/* @var ArticleCategoryModel $objArticleCategoryModel */
protected $objArticleCategoryModel;
public function __construct()
{
$this->objArticleCategoryModel = new ArticleCategoryModel();
if(!$this->isDefaultLanguage()) {
parent::__construct(
$this->objArticleCategoryModel,
new ArticleCategoryLanguageModel()
);
} else {
parent::__construct($this->objArticleCategoryModel);
}
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace Hura8\Components\Article\Controller;
use Hura8\Components\Article\Model\ArticleLanguageModel;
use Hura8\Components\Article\Model\ArticleModel;
use Hura8\System\Controller\aEntityBaseController;
class bArticleController extends aEntityBaseController
{
static $image_folder = "media/article";
static $resized_sizes = array(
't' => ['width' => 200,] ,
'l' => ['width' => 600,] ,
);
/* @var ArticleModel $objArticleModel */
protected $objArticleModel;
public function __construct()
{
$this->objArticleModel = new ArticleModel();
if(!$this->isDefaultLanguage()) {
parent::__construct(
$this->objArticleModel,
new ArticleLanguageModel()
);
} else {
parent::__construct($this->objArticleModel);
}
}
public function getFullInfo($id)
{
if(!$id) return null;
return self::getCache("getFullInfo-".$id."-".$this->view_language, function () use ($id){
$info = $this->objArticleModel->getFullInfo($id);
if($this->iEntityLanguageModel && $info ) {
$item_language_info = $this->iEntityLanguageModel->getInfo($id) ?? ["not_translated" => true];
return $this->formatItemInfo(array_merge($info, $item_language_info));
}
return ($info) ? $this->formatItemInfo($info) : null;
});
}
protected function formatItemInList(array $item_info)
{
return $this->formatItemInfo($item_info);
}
protected function formatItemInfo(array $item_info)
{
if(!$item_info) return null;
$info = $item_info;
$info['image'] = self::getResizedImageCollection($info['thumbnail']);
return $info;
}
public static function getResizedImageCollection($image_name) {
$image = [];
$size_in_full = [
't' => 'thumb' ,
's' => 'small' ,
'l' => 'large' ,
];
foreach (static::$resized_sizes as $size => $value) {
$image[$size_in_full[$size]] = ($image_name) ? STATIC_DOMAIN . "/". static::$image_folder . "/". $size. IMAGE_FILE_SEPARATOR . $image_name : '';
}
return $image;
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Hura8\Components\Article\Model;
use Hura8\System\Model\EntityLanguageModel;
use Hura8\Interfaces\EntityType;
class ArticleCategoryLanguageModel extends EntityLanguageModel
{
protected $richtext_fields = [
'description',
];
public function __construct() {
parent::__construct(EntityType::ARTICLE_CATEGORY, '', $this->richtext_fields);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Hura8\Components\Article\Model;
use Hura8\System\Model\aCategoryBaseModel;
use Hura8\Interfaces\iEntityCategoryModel;
use Hura8\Interfaces\EntityType;
class ArticleCategoryModel extends aCategoryBaseModel implements iEntityCategoryModel
{
static $url_module = "article";
static $url_view = "category";
static $url_type = "article:category";
protected $tb_article_per_category = "tb_article_per_category";
public function __construct() {
parent::__construct(EntityType::ARTICLE_CATEGORY);
}
protected function _buildQueryConditionExtend(array $filter_condition) : ?array
{
return null;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Hura8\Components\Article\Model;
use Hura8\System\Model\EntityLanguageModel;
use Hura8\Interfaces\EntityType;
class ArticleLanguageModel extends EntityLanguageModel
{
protected $richtext_fields = [
'description',
];
public function __construct() {
parent::__construct(EntityType::ARTICLE, '', $this->richtext_fields);
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace Hura8\Components\Article\Model;
use Hura8\System\Controller\UrlManagerController;
use Hura8\System\Model\aEntityBaseModel;
use Hura8\System\ModuleManager;
use Hura8\Interfaces\iEntityModel;
use Hura8\Interfaces\EntityType;
class ArticleModel extends aEntityBaseModel implements iEntityModel
{
static $url_type = "article:detail";
protected $tb_article_info = "tb_article_info";
protected $tb_article_per_category = 'tb_article_per_category';
public function __construct() {
parent::__construct(
EntityType::ARTICLE,
"",
new ArticleSearchModel()
);
}
protected function extendedFilterOptions() : array
{
return [
// empty for now
];
}
public function getFullInfo($id) : ?array
{
$query = $this->db->runQuery(
"SELECT * FROM `".$this->tb_entity."` basic, `".$this->tb_article_info."` info
WHERE basic.`id` = info.`article_id` AND basic.id = ?
LIMIT 1 ",
['d'], [$id]
);
if( $item_info = $this->db->fetchAssoc($query)){
return $item_info;
}
return null;
}
protected function _buildQueryConditionExtend(array $filter_condition) : ?array
{
/*$condition = array(
"category" => getRequestInt("category"),
"no_image" => 0,//1
);*/
$catCondition = [];
$bind_types = [];
$bind_values = [];
//Tim danh muc
if(isset($filter_condition["category"]) && $filter_condition["category"]) {
$objArticleCategoryModel = new ArticleCategoryModel();
$category_info = $objArticleCategoryModel->getInfo($filter_condition["category"]);
if($category_info) {
if($category_info['is_parent']) {
$catCondition[] = " AND `id` IN (SELECT `item_id` FROM `".$this->tb_article_per_category."` WHERE `category_id` IN (".$category_info['child_ids'].") ) ";
//$bind_types[] = 'd';
//$bind_values[] = $filter_condition["category"];
}else{
$catCondition[] = " AND `id` IN (SELECT `item_id` FROM `".$this->tb_article_per_category."` WHERE `category_id` = ? ) ";
$bind_types[] = 'd';
$bind_values[] = $filter_condition["category"];
}
}
}
return array( join(" ", $catCondition), $bind_types, $bind_values);
}
protected function addArticleToCategory($item_id, array $category_list_id) {
$this->db->runQuery("DELETE FROM `".$this->tb_article_per_category."` WHERE `item_id` = ? ", ['d'], [$item_id]);
$bulk_inserts = [];
foreach($category_list_id as $cat_id) {
if (! $cat_id) continue;
$bulk_inserts[] = [
'category_id' => $cat_id,
'item_id' => $item_id,
'status' => 1,
'create_time' => CURRENT_TIME,
];
}
if(sizeof($bulk_inserts)) {
$this->db->bulk_insert($this->tb_article_per_category, $bulk_inserts);
}
// update counter
$objArticleCategoryModel = new ArticleCategoryModel();
foreach($category_list_id as $cat_id) {
$objArticleCategoryModel->updateItemCount($cat_id);
}
}
public function updateUrl($id, $url_index): bool
{
$module_routing = ModuleManager::getModuleRouting("article");
$request_path_config = isset($module_routing["detail"]) ? $module_routing["detail"]['url_manager']['request_path'] : '';
if(!$request_path_config) {
return false;
}
$request_path = UrlManagerController::translateRequestPathConfig($request_path_config, $id, $url_index);
$id_path = UrlManagerController::createIdPath("article", "detail", $id);
$objUrlManager = new UrlManagerController();
$new_request_path = $objUrlManager->createUrl("article:detail", $request_path, $id_path, 0);
if($new_request_path) {
$this->db->update(
$this->tb_entity,
[
'request_path' => $new_request_path,
],
[
'id' => $id,
]
);
}
return true;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Hura8\Components\Article\Model;
use Hura8\Interfaces\iSearch;
use Hura8\System\Model\aSearchBaseModel;
class ArticleSearchModel extends aSearchBaseModel implements iSearch
{
private $filter_fields = [
"status"=> "tb_article.status",
];
private $fulltext_fields = [
"keywords" => ["tb_article.title", ],
];
public function __construct()
{
parent::__construct(
"tb_article",
$this->fulltext_fields,
$this->filter_fields
);
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace Hura8\Components\Article\Model;
class UArticleModel extends ArticleModel
{
public function getSameCategoryArticle($main_id, $category_id){
$query = $this->db->runQuery("
(
SELECT `item_id`
FROM ".$this->tb_article_per_category."
WHERE `category_id` = ? AND `status`=1 AND `item_id` > ?
ORDER BY `item_id` DESC
LIMIT 10
) UNION ALL (
SELECT `item_id`
FROM ".$this->tb_article_per_category."
WHERE `category_id` = ? AND `status`=1 AND `item_id` < ?
ORDER BY `item_id` DESC
LIMIT 10
)
",
['d', 'd', 'd', 'd'],
[$category_id, $main_id, $category_id, $main_id]
);
$article_list_id = [];
$article_item_info = array();
$article_item = [];
foreach ( $this->db->fetchAll($query) as $rs ) {
if(!isset($article_item_info[$rs["item_id"]])) $article_item_info[$rs["item_id"]] = array();
if(!in_array($rs["item_id"], $article_list_id)) $article_list_id[] = $rs["item_id"];
if($rs["item_id"] > $main_id) {
$article_item['new'][$rs["item_id"]] = &$article_item_info[$rs["item_id"]];
}
else {
$article_item['old'][$rs["item_id"]] = &$article_item_info[$rs["item_id"]];
}
}
$list_article_info = $this->getListByIds($article_list_id);
foreach ($article_list_id as $_id) {
if(isset($list_article_info[$_id])) $article_item_info[$_id] = $list_article_info[$_id];
}
return $article_item;
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Hura8\Components\Banner\AdminController;
use Hura8\Components\Banner\Controller\bBannerController;
use Hura8\Interfaces\iEntityAdminController;
use Hura8\Traits\AdminEntityBaseControllerTraits;
class ABannerController extends bBannerController implements iEntityAdminController
{
use AdminEntityBaseControllerTraits;
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Hura8\Components\Banner\AdminController;
use Hura8\Components\Banner\Model\BannerLocationModel;
use Hura8\System\Controller\aAdminEntityBaseController;
class ABannerLocationController extends aAdminEntityBaseController
{
public function __construct()
{
parent::__construct(new BannerLocationModel());
}
public function getTemplateBanner() {
return ABannerController::$template_banners;
}
protected function deleteFileBeforeDeleteItem($item_id): bool
{
return true;
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Hura8\Components\Banner\Controller;
use Hura8\Components\Banner\Model\BannerLocationModel;
use Hura8\Components\Banner\Model\BannerModel;
use Hura8\System\Controller\aEntityBaseController;
class bBannerController extends aEntityBaseController
{
static $image_folder = "media/banner";
static $template_banners = array(
//"index" => "Toàn bộ website" ,
"header" => "Đầu trang" ,
"homepage" => "Trang chủ" ,
"column_left" => "Cột trái" ,
"column_right" => "Cột phải" ,
"footer" => "Chân trang" ,
"product_detail"=> "Chi tiết sản phẩm" ,
"product_list" => "Danh sách &amp; Danh mục sản phẩm" ,
"collection_list" => "Bộ sưu tập" ,
"article_home" => "Trang chủ tin tức" ,
"brand_detail" => "Chi tiết thương hiệu",
);
protected $objBannerModel;
protected $objBannerLocationModel;
public function __construct()
{
$this->objBannerModel = new BannerModel();
$this->objBannerLocationModel = new BannerLocationModel();
parent::__construct($this->objBannerModel);
}
protected function formatItemInList(array $item_info)
{
return self::formatFile($item_info);
}
protected function formatItemInfo(array $item_info)
{
return self::formatFile($item_info);
}
public static function formatFile(array $item_info)
{
if($item_info['file_url']) {
$item_info['display_file'] = STATIC_DOMAIN ."/". static::$image_folder ."/". $item_info['file_url'];
}else if($item_info['file_external_url']) {
$item_info['display_file'] = $item_info['file_external_url'];
}
$item_info['html_code'] = "<a href=\"/ad.php?id=".$item_info['tracking_id']."\" target='_blank' rel='nofollow'><img border='0' src=\"".$item_info['display_file']."\" alt=\"".htmlspecialchars($item_info['title'])."\" /></a>";
return $item_info;
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Hura8\Components\Banner\Model;
use Hura8\Interfaces\AppResponse;
use Hura8\Interfaces\iEntityModel;
use Hura8\Interfaces\EntityType;
use Hura8\System\Model\aEntityBaseModel;
use Hura8\System\Security\DataClean;
use Hura8\System\Security\DataType;
class BannerLocationModel extends aEntityBaseModel implements iEntityModel
{
public function __construct() {
parent::__construct(EntityType::BANNER_LOCATION);
}
protected function extendedFilterOptions(): array
{
return [];
}
protected function _buildQueryConditionExtend(array $filter_condition) : ?array
{
return null;
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Hura8\Components\Banner\Model;
use Hura8\System\Model\aEntityBaseModel;
use Hura8\Interfaces\iEntityModel;
use Hura8\Interfaces\EntityType;
class BannerModel extends aEntityBaseModel implements iEntityModel
{
protected $tb_banner_location = "tb_banner_location";
protected $tb_banner_per_category = "tb_banner_per_category";
public function __construct() {
parent::__construct(
EntityType::BANNER, "", new BannerSearchModel()
);
}
protected function extendedFilterOptions() : array
{
return [
// empty for now
];
}
public function getInfoByTrackingId($tracking_id)
{
$query = $this->db->runQuery("SELECT * FROM `".$this->tb_entity."` WHERE `tracking_id` = ? LIMIT 1 ", ['s'], [$tracking_id]) ;
if( $item_info = $this->db->fetchAssoc($query)){
return $this->formatItemInfo($item_info);
}
return false;
}
public function getBannerPerTemplate(array $template_list, $numberOfBannerPerTpl=100){
$all_bind_types = [];
$all_bind_values = [];
$view_id = 0;
$build_query = [];
foreach($template_list as $tpl) {
list($where_condition, $bind_types, $bind_values) = $this->buildQueryPerTpl($tpl, $view_id, $numberOfBannerPerTpl);
$build_query[] = " (".$where_condition.") ";
$all_bind_types = array_merge($all_bind_types, $bind_types);
$all_bind_values = array_merge($all_bind_values, $bind_values);
}
if(!sizeof($build_query)) return [];
$query = $this->db->runQuery(join(" UNION ALL ", $build_query), $all_bind_types, $all_bind_values);
return $this->db->fetchAll($query);
}
protected function _buildQueryConditionExtend(array $filter_condition) : ?array
{
/*$condition = array(
[location] => 2
[category] => 0
);*/
$catCondition = [];
$bind_types = [];
$bind_values = [];
if(isset($filter_condition['location']) && $filter_condition['location']) {
$catCondition[] = " AND `location` = ? ";
$bind_types[] = 'd';
$bind_values[] = $filter_condition['location'];
}
if(isset($filter_condition['category']) && $filter_condition['category']) {
$catCondition[] = " AND `id` IN ( SELECT `banner_id` FROM `".$this->tb_banner_per_category."` WHERE `category_id` = ? ) ";
$bind_types[] = 'd';
$bind_values[] = $filter_condition['category'];
}
return array( join(" ", $catCondition), $bind_types, $bind_values);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Hura8\Components\Banner\Model;
use Hura8\Interfaces\iSearch;
use Hura8\System\Model\aSearchBaseModel;
class BannerSearchModel extends aSearchBaseModel implements iSearch
{
private $filter_fields = [
"location" => "tb_banner.location",
"status" => "tb_banner.status",
];
private $fulltext_fields = [
"keywords" => ["tb_banner.title",],
];
public function __construct()
{
parent::__construct(
"tb_banner",
$this->fulltext_fields,
$this->filter_fields
);
//$this->createTableSearch();
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Hura8\Components\Brand\AdminController;
use Hura8\Components\Brand\Controller\bBrandController;
use Hura8\Interfaces\iEntityAdminController;
use Hura8\Traits\AdminEntityBaseControllerTraits;
class ABrandController extends bBrandController implements iEntityAdminController
{
use AdminEntityBaseControllerTraits;
public function getGroupByFirstLetter() {
return $this->objBrandModel->getGroupByFirstLetter();
}
protected function deleteFileBeforeDeleteItem($item_id): bool
{
// delete thumb files
$item_info = $this->getInfo($item_id);
if($item_info['thumbnail']) {
foreach (static::$resized_sizes as $size => $value) {
$file_local_path = PUBLIC_DIR . "/". static::$image_folder . "/". $size. IMAGE_FILE_SEPARATOR . $item_info['thumbnail'];
unlink($file_local_path);
}
// remove original file
$file_local_path = PUBLIC_DIR . "/". static::$image_folder . "/". $item_info['thumbnail'];
unlink($file_local_path);
}
//delete media files?
// todo:
// ok
return true;
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace Hura8\Components\Brand\Controller;
use Hura8\Components\Brand\Model\BrandLanguageModel;
use Hura8\Components\Brand\Model\BrandModel;
use Hura8\System\Controller\aEntityBaseController;
class bBrandController extends aEntityBaseController
{
static $image_folder = "media/brand";
static $resized_sizes = array(
's' => ['width' => 200,] ,
);
/* @var BrandModel $objBrandModel */
protected $objBrandModel;
/* @var BrandLanguageModel $objBrandLanguageModel */
protected $objBrandLanguageModel;
protected $view_language = '';
public function __construct()
{
$this->objBrandModel = new BrandModel();
if(!$this->isDefaultLanguage()) {
$this->objBrandLanguageModel = new BrandLanguageModel();
//$this->objVideoLanguageModel->createTableLang();
parent::__construct($this->objBrandModel, $this->objBrandLanguageModel);
}else{
parent::__construct($this->objBrandModel);
}
}
public function getInfoByUrl(string $band_index) : ?array
{
return $this->objBrandModel->getInfoByUrl($band_index);
}
protected function formatItemInList(array $item_info) : array
{
return $this->formatItemInfo($item_info);
}
protected function formatItemInfo(array $item_info) : ?array
{
if(!$item_info) return null;
$info = static::formatItemImage($item_info);
$info['url'] = "/brand/".$info['brand_index'];
return $info;
}
public static function formatItemImage(array $item_info) {
$info = $item_info;
foreach (static::$resized_sizes as $size => $value) {
$info['image'][$size] = ($info['thumbnail']) ? STATIC_DOMAIN . "/". static::$image_folder . "/". $size. IMAGE_FILE_SEPARATOR . $info['thumbnail'] : '';
}
return $info;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Hura8\Components\Brand\Model;
use Hura8\System\Model\EntityLanguageModel;
use Hura8\Interfaces\EntityType;
class BrandLanguageModel extends EntityLanguageModel
{
protected $richtext_fields = [
'description',
];
public function __construct() {
parent::__construct(EntityType::BRAND, '', $this->richtext_fields);
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Hura8\Components\Brand\Model;
use Hura8\Interfaces\AppResponse;
use Hura8\System\Config;
use Hura8\System\Controller\UrlManagerController;
use Hura8\System\Model\aEntityBaseModel;
use Hura8\Interfaces\iEntityModel;
use Hura8\Interfaces\EntityType;
use Hura8\Interfaces\TableName;
class BrandModel extends aEntityBaseModel implements iEntityModel
{
static $url_type = "brand:detail";
public function __construct() {
parent::__construct(EntityType::BRAND);
}
protected function extendedFilterOptions() : array
{
return [
// empty for now
];
}
public function getGroupByFirstLetter() {
$query = $this->db->runQuery(
"SELECT `letter`, COUNT(*) AS item_count FROM `".$this->tb_entity."` GROUP BY `letter` ORDER BY `letter` ASC "
);
return $this->db->fetchAll($query);
}
protected function _buildQueryConditionExtend(array $filter_condition): ?array
{
/*$condition = array(
"letter" => "",
);*/
$catCondition = [];
$bind_types = [];
$bind_values = [];
if(isset($filter_condition["letter"]) && strlen($filter_condition["letter"]) == 1){
$catCondition[] = " AND `letter` = ? ";
$bind_types[] = 's';
$bind_values[] = $filter_condition["letter"];
}
return array( join(" ", $catCondition), $bind_types, $bind_values);
}
public function getInfoByUrl($brand_index) : ?array
{
$brand_index = preg_replace("/[^a-z0-9\.\-\_]/i", '', $brand_index);
$query = $this->db->runQuery("SELECT * FROM `".$this->tb_entity."` WHERE `brand_index` = ? LIMIT 1 ", ['s'], [$brand_index]);
if($item_info = $this->db->fetchAssoc($query)){
return $this->formatItemInfo($item_info);
}
return null;
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Hura8\Components\ComboSet\AdminController;
use Hura8\Components\ComboSet\Controller\bComboSetController;
use Hura8\Components\Product\AdminController\AProductController;
use Hura8\Interfaces\iEntityAdminController;
use Hura8\Traits\AdminEntityBaseControllerTraits;
class AComboSetController extends bComboSetController implements iEntityAdminController
{
use AdminEntityBaseControllerTraits;
}

View File

@@ -0,0 +1,148 @@
<?php
namespace Hura8\Components\ComboSet\Controller;
use Hura8\Components\ComboSet\Model\ComboSetLanguageModel;
use Hura8\Components\ComboSet\Model\ComboSetModel;
use Hura8\System\Controller\aEntityBaseController;
class bComboSetController extends aEntityBaseController
{
/* @var ComboSetModel $objComboSetModel */
protected $objComboSetModel;
/* @var ComboSetLanguageModel $objComboSetLanguageModel */
protected $objComboSetLanguageModel;
public function __construct()
{
$this->objComboSetModel = new ComboSetModel();
if(!$this->isDefaultLanguage()) {
$this->objComboSetLanguageModel = new ComboSetLanguageModel();
//$this->objVideoLanguageModel->createTableLang();
parent::__construct($this->objComboSetModel, $this->objComboSetLanguageModel);
}else{
parent::__construct($this->objComboSetModel);
}
}
public function getAllSetIdsForAProduct($product_id)
{
return $this->objComboSetModel->getAllSetIdsForAProduct($product_id);
}
public function getTotalProductUseSet($set_id)
{
return $this->objComboSetModel->getTotalProductUseSet($set_id);
}
public function getListProductUseSet($set_id, $numPerPage)
{
return $this->objComboSetModel->getListProductUseSet($set_id, $numPerPage);
}
public function getProductListInfoInConfig(array $category) {
$product_list_ids = [];
foreach ($category as $index => $_category_info) {
foreach ($_category_info['suggest_list'] as $_proindex => $_pro_info) {
$product_list_ids[] = $_pro_info['real_id'];
}
}
return array_unique($product_list_ids);
}
public function buildConfig( $category, $product) {
$group_category = [];
foreach ($category as $category_index => $_category_info) {
$category_product = [];
foreach ($product[$category_index] as $product_index => $_product_info) {
//$_product_info['price'] = clean_price($_product_info['price']);
$category_product[] = $_product_info;
}
$group_category[] = [
"title" => $_category_info['title'],
//"type" => "category",
//"real_id" => $_category_info['real_id'],
//"select_type" => $_category_info['select_type'],//checkbox|radio
"suggest_list" => $category_product,
];
}
return $group_category;
}
public function decomposeConfig($config) {
$tab = [];
$group = [];
$category = [];
$product = [];
$group_index = 0;
$category_index = 0;
$product_index = 0;
foreach ($config as $tab_index => $tab_info) {
//construct tab
$tab[$tab_index] = [
'title' => $tab_info['title'],
];
//construct group
foreach ($tab_info['child'] as $child_group) {
$group_index += 1;
$group[$tab_index][$group_index] = [
'title' => $child_group['title'],
];
//construct category
foreach ($child_group['child'] as $child_category) {
$category_index += 1;
$category[$group_index][$category_index] = [
'title' => $child_category['title'],
'real_id' => $child_category['real_id'],
'select_type' => $child_category['select_type'],
];
//construct product
foreach ($child_category['suggest_list'] as $child_product) {
$product_index += 1;
$product[$category_index][$product_index] = [
'title' => $child_product['title'],
'real_id' => $child_product['real_id'],
'is_default' => $child_product['is_default'],
];
}
}
}
}
return [
"tab" => $tab,
"group" => $group,
'category' => $category,
'product' => $product,
];
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Hura8\Components\ComboSet\Model;
use Hura8\Interfaces\EntityType;
use Hura8\System\Model\EntityLanguageModel;
class ComboSetLanguageModel extends EntityLanguageModel
{
public function __construct() {
parent::__construct('combo_set');
}
}

View File

@@ -0,0 +1,165 @@
<?php
namespace Hura8\Components\ComboSet\Model;
use Hura8\Components\Product\AdminController\AProductController;
use Hura8\Components\Product\Model\ProductSearchModel;
use Hura8\Interfaces\AppResponse;
use Hura8\Interfaces\iEntityModel;
use Hura8\System\Model\aEntityBaseModel;
class ComboSetModel extends aEntityBaseModel implements iEntityModel
{
protected $tb_set_product = 'tb_combo_set_product';
public function __construct() {
parent::__construct('combo_set');
}
protected function extendedFilterOptions() : array
{
return [
// empty for now
];
}
public function getAllSetIdsForAProduct($product_id)
{
$query = $this->db->runQuery(
" SELECT `set_id` FROM ".$this->tb_set_product." WHERE `product_id` = ? ",
['d'], [$product_id]
);
$item_list = array();
foreach ( $this->db->fetchAll($query) as $info ) {
$item_list[] = $info['set_id'];
}
return $item_list;
}
public function getTotalProductUseSet($set_id)
{
// search
$keyword = getRequest("q");
if($keyword) {
$search = new ProductSearchModel();
$match_result = $search->find($keyword);
$catCondition = (sizeof($match_result) > 0) ? " AND `product_id` IN (".join(",", $match_result).") " : " AND `product_id` = -1 ";
$query = $this->db->runQuery("
SELECT COUNT(product_id) AS total_product
FROM ".$this->tb_set_product."
WHERE `set_id` = ? " . $catCondition ."
", ['d'], [$set_id]);
if ($info = $this->db->fetchAssoc($query)) {
return $info['total_product'];
}
return 0;
} else {
$set_info = $this->getInfo($set_id);
return $set_info['product_count'];
}
}
public function getListProductUseSet($set_id, $numPerPage)
{
$page = getPageId();
// search
$catCondition = "";
$keyword = getRequest("q");
if($keyword) {
$search = new ProductSearchModel();
$match_result = $search->find($keyword);
$catCondition = (sizeof($match_result) > 0) ? " AND `product_id` IN (".join(",", $match_result).") " : " AND `product_id` = -1 ";
}
$query = $this->db->runQuery("
SELECT `product_id`
FROM ".$this->tb_set_product."
WHERE `set_id` = ? " . $catCondition ."
ORDER BY id desc
LIMIT ".($page - 1) * $numPerPage .", ".$numPerPage."
", ['d'], [$set_id]);
$item_list = array();
foreach ( $this->db->fetchAll($query) as $info ) {
$item_list[] = $info['product_id'];
}
return $item_list;
}
protected function _buildQueryOrderBy(string $sort_by = "new")
{
$order_condition = "";
switch ($sort_by) {
case "ordering";
$order_condition = " `ordering` desc ";
break;
case "old";
$order_condition = " id asc ";
break;
case "last_show_time";
$order_condition = " last_show_time ASC ";
break;
}
return $order_condition;
}
protected function formatItemInfo(array $item_info) : array
{
$from_time = $item_info['from_time'];
$from_time_date = ($from_time > 0) ? date("d-m-Y", $from_time) : '';
$from_time_minute = ($from_time > 0) ? date("H:i", $from_time) : "00:00";
$to_time = $item_info['to_time'];
$to_time_date = ($to_time > 0) ? date("d-m-Y", $to_time) : '';
$to_time_minute = ($to_time > 0) ? date("H:i", $to_time) : "00:00";
$item_info['from_time_date'] = $from_time_date;
$item_info['from_time_minute'] = $from_time_minute;
$item_info['to_time_date'] = $to_time_date;
$item_info['to_time_minute'] = $to_time_minute;
return $item_info;
}
///---------
///
protected function _buildQueryConditionExtend(array $filter_condition) : ?array
{
$catCondition = "";
$bind_types = [];
$bind_values = [];
if(isset($filter_condition["product_id"]) && $filter_condition["product_id"]){
$catCondition .= " AND `id` IN ( SELECT `set_id` FROM ".$this->tb_set_product." WHERE `product_id` = ? ) ";
$bind_types[] = 'd';
$bind_values[] = $filter_condition['product_id'];
}
return [$catCondition, $bind_types, $bind_values];
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Hura8\Components\ConfigGroup\AdminController;
use Hura8\Components\ConfigGroup\Controller\bConfigGroupController;
use Hura8\Interfaces\iEntityAdminController;
use Hura8\Traits\AdminEntityBaseControllerTraits;
class AConfigGroupController extends bConfigGroupController implements iEntityAdminController
{
use AdminEntityBaseControllerTraits;
public function deleteAttribute($id, $group_id = 0) {
$this->objConfigGroupModel->deleteAttribute($id, $group_id);
}
public function updateAttribute($id, $info) {
$this->objConfigGroupModel->updateAttribute($id, $info) ;
}
public function createAttribute($info) {
return $this->objConfigGroupModel->createAttribute($info) ;
}
public function createAttributeValue($info) {
$this->objConfigGroupModel->createAttributeValue($info);
}
public function deleteAttributeValue($id) {
$this->objConfigGroupModel->deleteAttributeValue($id);
}
public function updateAttributeValue($id, $info) {
$this->objConfigGroupModel->updateAttributeValue($id, $info);
}
public function createProduct($product_id, $group_id, array $attribute_config) {
$this->objConfigGroupModel->createProduct($product_id, $group_id, $attribute_config);
}
public function deleteProduct($product_id, $group_id) {
$this->objConfigGroupModel->deleteProduct($product_id, $group_id);
}
public function updateProduct($product_id, $group_id, array $attribute_config, $product_name_in_group = '') {
return $this->objConfigGroupModel->updateProduct($product_id, $group_id, $attribute_config, $product_name_in_group);
}
public function getProductInGroup($group_id){
return $this->objConfigGroupModel->getProductInGroup($group_id);
}
public function getGroupConfig($item_id) {
return $this->objConfigGroupModel->getGroupConfig($item_id);
}
protected function deleteFileBeforeDeleteItem($item_id): bool
{
return true;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Hura8\Components\ConfigGroup\Controller;
use Hura8\Components\ConfigGroup\Model\ConfigGroupModel;
use Hura8\System\Controller\aEntityBaseController;
class bConfigGroupController extends aEntityBaseController
{
/* @var ConfigGroupModel $objConfigGroupModel */
protected $objConfigGroupModel;
public function __construct()
{
$this->objConfigGroupModel = new ConfigGroupModel();
parent::__construct($this->objConfigGroupModel);
}
}

View File

@@ -0,0 +1,500 @@
<?php
namespace Hura8\Components\ConfigGroup\Model;
use Hura8\Components\Product\Model\ProductModel;
use Hura8\Interfaces\AppResponse;
use Hura8\System\Model\aEntityBaseModel;
class ConfigGroupModel extends aEntityBaseModel
{
protected $tb_config_group = "";
protected $tb_config_group_product = "tb_config_group_product";
protected $tb_config_group_product_cache = "tb_config_group_product_cache";
protected $tb_config_group_attribute = "tb_config_group_attribute";
protected $tb_config_group_attribute_value = "tb_config_group_attribute_value";
public function __construct() {
parent::__construct('config_group');
$this->tb_config_group = $this->tb_entity;
}
protected function extendedFilterOptions() : array
{
return [
// empty for now
];
}
protected function updateGroupAttributeCount($group_id) {
$this->db->runQuery(
"UPDATE `".$this->tb_config_group."` SET
`attribute_count` = ( SELECT COUNT(*) FROM `".$this->tb_config_group_attribute."` WHERE `group_id` = ? )
WHERE `id` = ? LIMIT 1 ",
['d', 'd'], [ $group_id, $group_id ]
);
}
protected function updateGroupProductCount($group_id) {
$this->db->runQuery(
"UPDATE `".$this->tb_config_group."` SET
`item_count` = ( SELECT COUNT(*) FROM `".$this->tb_config_group_product."` WHERE `group_id` = ? )
WHERE `id` = ? LIMIT 1 ",
['d', 'd'], [ $group_id, $group_id ]
);
}
//attribute value
public function deleteAttributeValue($att_value_id) {
$attr_id = 0;
$query = $this->db->runQuery("SELECT `attr_id` FROM `".$this->tb_config_group_attribute_value."` WHERE `id` = ? LIMIT 1 ", ['d'], [ $att_value_id ]);
if ($info = $this->db->fetchAssoc($query)) {
$attr_id = $info['attr_id'];
}
$group_id = $this->getGroupIdFromAttribute($attr_id);
$this->db->runQuery("DELETE FROM `".$this->tb_config_group_attribute_value."` WHERE `id` = ? LIMIT 1 ", ['d'], [ $att_value_id ]);
$this->resetProductConfigCache($group_id);
$this->updateAttributeValueCount($attr_id);
}
protected function updateAttributeValueCount($attr_id) {
$this->db->runQuery(
"UPDATE `".$this->tb_config_group_attribute."` SET
`value_count` = ( SELECT COUNT(*) FROM `".$this->tb_config_group_attribute_value."` WHERE `attr_id` = ? )
WHERE `id` = ? LIMIT 1 ",
['d', 'd'],
[$attr_id, $attr_id ]
);
}
public function updateAttributeValue($id, $info) {
$updated_info = $info;
$updated_info['last_update'] = CURRENT_TIME;
$updated_info['last_update_by'] = ADMIN_NAME;
$this->db->update(
$this->tb_config_group_attribute_value,
$updated_info,
[
"id" => $id,
]
);
$group_id = $this->getGroupIdFromAttributeValue($id);
$this->resetProductConfigCache($group_id);
}
public function createAttributeValue($info) {
$updated_info = $info;
$updated_info['create_time'] = CURRENT_TIME;
$updated_info['create_by'] = ADMIN_NAME;
$updated_info['last_update'] = CURRENT_TIME;
$updated_info['last_update_by'] = ADMIN_NAME;
$this->db->insert($this->tb_config_group_attribute_value, $updated_info );
$group_id = $this->getGroupIdFromAttribute($info['attr_id']);
$this->resetProductConfigCache($group_id);
$this->updateAttributeValueCount($info['attr_id']);
}
//attribute
public function deleteAttribute($id, $group_id = 0) {
if(!$group_id) $group_id = $this->getGroupIdFromAttribute($id);
$this->db->runQuery("DELETE FROM `".$this->tb_config_group_attribute."` WHERE `id` = ? LIMIT 1 ", ['d'], [ $id ]);
$this->db->runQuery("DELETE FROM `".$this->tb_config_group_attribute_value."` WHERE `attr_id` = ? LIMIT 1 ", ['d'], [ $id ]);
$this->updateGroupAttributeCount($group_id);
//todo: update for product attribute_config ?
$this->resetProductConfigCache($group_id);
}
public function updateAttribute($id, $info) {
$updated_info = $info;
$updated_info['last_update'] = CURRENT_TIME;
$updated_info['last_update_by'] = ADMIN_NAME;
$this->db->update(
$this->tb_config_group_attribute,
$updated_info,
[
'id' => $id,
]
);
$group_id = $this->getGroupIdFromAttribute($id);
$this->resetProductConfigCache($group_id);
}
public function createAttribute($info) {
$updated_info = $info;
$updated_info['create_time'] = CURRENT_TIME;
$updated_info['create_by'] = ADMIN_NAME;
$updated_info['last_update'] = CURRENT_TIME;
$updated_info['last_update_by'] = ADMIN_NAME;
$this->db->insert($this->tb_config_group_attribute, $updated_info );
$this->resetProductConfigCache($info['group_id']);
$this->updateGroupAttributeCount($info['group_id']);
return $this->db->get_insert_id();
}
private function getGroupIdFromAttributeValue($att_value_id) {
$attr_id = 0;
$query = $this->db->runQuery("SELECT `attr_id` FROM `".$this->tb_config_group_attribute_value."` WHERE `id` = ? LIMIT 1 ", ['d'], [ $att_value_id ]);
if ($info = $this->db->fetchAssoc($query)) {
$attr_id = $info['attr_id'];
}
return $this->getGroupIdFromAttribute($attr_id);
}
private function getGroupIdFromAttribute($attr_id) {
$group_id = 0;
$query = $this->db->runQuery("SELECT `group_id` FROM `".$this->tb_config_group_attribute."` WHERE `id` = ? LIMIT 1 ", ['d'], [ $attr_id ]);
if ($info = $this->db->fetchAssoc($query)) {
$group_id = $info['group_id'];
}
return $group_id;
}
//create or update product in a group
public function createProduct($product_id, $group_id, array $attribute_config) {
if($this->isProductInGroup($product_id, $group_id)) {
$this->updateProduct($product_id, $group_id, $attribute_config);
}else {
$updated_info = [
"product_id" => $product_id,
"group_id" => $group_id,
"attribute_config" => json_encode($attribute_config),
"create_time" => CURRENT_TIME,
"create_by" => ADMIN_NAME,
"last_update" => CURRENT_TIME,
"last_update_by" => ADMIN_NAME,
];
$this->db->insert($this->tb_config_group_product, $updated_info );
$this->updateGroupProductCount($group_id);
}
$this->resetProductConfigCache($group_id);
}
public function deleteProduct($product_id, $group_id) {
$this->db->runQuery(
"DELETE FROM `".$this->tb_config_group_product."` WHERE `product_id` = ? AND `group_id` = ? LIMIT 1 ",
['d', 'd'], [ $product_id, $group_id ]
);
$this->deleteProductConfigCache($product_id);
$this->updateGroupProductCount($group_id);
}
public function updateProduct($product_id, $group_id, array $attribute_config, $product_name_in_group = '') {
$this->db->update(
$this->tb_config_group_product,
[
"attribute_config" => json_encode($attribute_config),
"product_name_in_group" => substr($product_name_in_group, 0, 140),
"last_update" => CURRENT_TIME,
"last_update_by" => ADMIN_NAME,
],
[
"product_id" => $product_id,
"group_id" => $group_id,
]
);
$this->resetProductConfigCache($group_id);
return true;
}
//we want to reset all caches for products in a group
//we need to do this when we make changes to group's attributes, or create new products/ update product in group
protected function resetProductConfigCache($group_id) {
$query = $this->db->runQuery("SELECT `product_id` FROM `".$this->tb_config_group_product."` WHERE `group_id` = ? ", ['d'], [$group_id]) ;
$product_list = array();
foreach( $this->db->fetchAll($query) as $item ){
$product_list[] = $item['product_id'];
}
if(sizeof($product_list)) {
$this->db->query("UPDATE `".$this->tb_config_group_product_cache."` SET
`value` = NULL
WHERE `product_id` IN (".join(",", $product_list).") ") ;
}
}
protected function deleteProductConfigCache($product_id) {
$this->db->runQuery("DELETE FROM `".$this->tb_config_group_product_cache."` WHERE `product_id` = ? LIMIT 1 ", ['d'], [$product_id]) ;
}
public function saveProductConfigCache($product_id, $value) {
$query = $this->db->runQuery(
"SELECT `product_id` FROM `".$this->tb_config_group_product_cache."` WHERE `product_id` = ? LIMIT 1 ",
['d'], [$product_id]
) ;
if($this->db->fetchAssoc($query)){
$this->db->runQuery(
"UPDATE `".$this->tb_config_group_product_cache."` SET
`value` = '".$this->db->escape(json_encode($value))."'
WHERE `product_id` = ? LIMIT 1 ",
['d'], [$product_id]
) ;
}else{
$this->db->runQuery("INSERT INTO `".$this->tb_config_group_product_cache."` (`product_id`, `value`)
VALUES ('". (int) $product_id."', '".$this->db->escape(json_encode($value))."') ") ;
}
}
public function getProductConfigCache($product_id) {
$query = $this->db->runQuery("SELECT `value` FROM `".$this->tb_config_group_product_cache."` WHERE `product_id` = ? LIMIT 1 ", ['d'], [$product_id]) ;
if($item_info = $this->db->fetchAssoc($query)){
return ($item_info['value']) ? \json_decode($item_info['value'], true) : false;
}
return false;
}
//get all group config
public function getGroupConfig($group_id) {
$query = $this->db->runQuery(
"SELECT
a.id AS attribute_id ,
a.name AS attribute_name ,
a.ordering AS attr_ordering ,
v.id AS value_id ,
v.name AS value_name ,
v.image AS image ,
v.color_code AS color ,
v.ordering ,
v.description AS description
FROM `".$this->tb_config_group_attribute."` a
LEFT JOIN `".$this->tb_config_group_attribute_value."` v ON a.id = v.attr_id
WHERE a.group_id = ?
ORDER BY attr_ordering DESC, `ordering` DESC
",
['d'], [$group_id]
);
$group_attribute = array();
foreach ( $this->db->fetchAll($query) as $info ) {
if(!isset($group_attribute[$info['attribute_id']])) {
$group_attribute[$info['attribute_id']] = array(
'id' => $info['attribute_id'],
'name' => $info['attribute_name'],
'ordering' => $info['attr_ordering'],
'list' => array(),
);
}
if($info['value_id']) {
$group_attribute[$info['attribute_id']]['list'][] = array(
'id' => $info['value_id'],
'name' => $info['value_name'],
'image' => $info['image'],
'color' => $info['color'],
'ordering' => $info['ordering'],
'description' => $info['description'],
);
}
}
return $group_attribute;
}
public function getProductConfigGroupId($product_id) {
$query = $this->db->runQuery(
"SELECT `group_id` FROM `".$this->tb_config_group_product."` WHERE `product_id` = ? LIMIT 1 ",
['d'], [$product_id]
) ;
if($item_info = $this->db->fetchAssoc($query)){
return $item_info['group_id'];
}
return 0;
}
public function getProductInGroup($group_id){
$query = $this->db->runQuery(
"SELECT `product_id`, `product_name_in_group`, `attribute_config`
FROM `".$this->tb_config_group_product."`
WHERE `group_id` = ?
",
['d'], [$group_id]
);
$product_list = array();
$product_ids = array();
foreach ( $this->db->fetchAll($query) as $info ) {
$product_ids[] = $info['product_id'];
$product_list[$info['product_id']] = array(
"id" => $info['product_id'],
"name" => "",
"product_name_in_group" => $info['product_name_in_group'],
"attribute" => \json_decode($info['attribute_config'], true),
"url" => "",
"sku" => "",
"price" => 0,
"image" => "",
"status" => "",
);
}
//find product urls
if(sizeof($product_ids)) {
$objProductModel = new ProductModel();
$product_list_info = $objProductModel->getListByIds($product_ids);
// debug_var($product_list_info);
// update $product_list
foreach ($product_list as $_pro_id => $_info) {
$_pro_info = $product_list_info[$_pro_id] ?? null;
if($_pro_info) {
$product_list[$_pro_id]['name'] = $_pro_info['title'];
$product_list[$_pro_id]['price'] = $_pro_info['price'];
$product_list[$_pro_id]['sku'] = $_pro_info['sku'];
$product_list[$_pro_id]['image'] = $_pro_info['thumbnail'];
$product_list[$_pro_id]['url'] = $_pro_info['request_path'];
$product_list[$_pro_id]['status'] = $_pro_info['status'];
}
}
}
return $product_list;
}
protected function isProductInGroup($product_id, $group_id) {
$query = $this->db->runQuery(
"SELECT * FROM `".$this->tb_config_group_product."` WHERE `product_id` = ? AND `group_id` = ? LIMIT 1 ",
['d', 'd'],
[$product_id, $group_id]
) ;
return ($this->db->fetchAssoc($query));
}
protected function _buildQueryConditionExtend(array $filter_condition) : ?array
{
/*$condition = array(
"q" => "",
"numPerPage" => 20,
"order_by" => '',
);*/
$catCondition = "";
return [
$catCondition,
[],
[]
];
}
protected function beforeCreateItem(array $input_info): AppResponse
{
$info = $input_info;
$info['create_time'] = CURRENT_TIME;
$info['create_by'] = ADMIN_NAME;
$info['last_update'] = CURRENT_TIME;
$info['last_update_by'] = ADMIN_NAME;
return new AppResponse('ok', null, $info);
}
protected function afterCreateItem($new_item_id, $new_item_info)
{
// TODO: Implement afterCreateItem() method.
}
protected function beforeUpdateItem($item_id, $current_item_info, $new_input_info):AppResponse
{
$info = $new_input_info;
$info['last_update'] = CURRENT_TIME;
$info['last_update_by'] = ADMIN_NAME;
return new AppResponse('ok', null, $info);
}
protected function afterUpdateItem($item_id, $old_item_info, $new_item_info)
{
// TODO: Implement afterUpdateItem() method.
}
protected function beforeDeleteItem($item_id, $item_info):AppResponse
{
return new AppResponse('ok' );
}
protected function afterDeleteItem($item_id, $item_info)
{
$query = $this->db->runQuery("SELECT `id` FROM `".$this->tb_config_group_attribute."` WHERE `group_id` = ? ", ['d'], [$item_id]);
foreach ( $this->db->fetchAll($query) as $info ) {
$this->deleteAttribute($info['id'], $item_id);
}
$this->db->runQuery(
"DELETE FROM `".$this->tb_config_group_product_cache."` WHERE `product_id` IN (SELECT product_id FROM config_group_product WHERE `group_id` = ? ) ",
['d'], [$item_id]
) ;
$this->db->runQuery("DELETE FROM `".$this->tb_config_group_product."` WHERE `group_id` = ? ", ['d'], [$item_id]) ;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Hura8\Components\Customer\AdminController;
use Hura8\Components\Customer\Controller\bCustomerController;
use Hura8\Interfaces\iEntityAdminController;
use Hura8\Traits\AdminEntityBaseControllerTraits;
class ACustomerController extends bCustomerController implements iEntityAdminController
{
use AdminEntityBaseControllerTraits;
protected function deleteFileBeforeDeleteItem($item_id): bool
{
// TODO: Implement deleteFileBeforeDeleteItem() method.
return true;
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Hura8\Components\Customer\AdminController;
use Hura8\Components\Customer\Model\CustomerGroupModel;
use Hura8\Components\Province\AdminController\AProvinceController;
use Hura8\System\Controller\aAdminEntityBaseController;
class ACustomerGroupController extends aAdminEntityBaseController
{
/* @var CustomerGroupModel $objCustomerGroupModel */
protected $objCustomerGroupModel;
public function __construct()
{
$this->objCustomerGroupModel = new CustomerGroupModel();
parent::__construct($this->objCustomerGroupModel);
}
public function getAllGroup() {
return $this->objCustomerGroupModel->getList(["numPerPage" => 1000]);
}
public function removeCustomer($customer_id, $group_id)
{
return $this->objCustomerGroupModel->removeCustomer($customer_id, $group_id);
}
public function addCustomer($customer_id, $group_id)
{
return $this->objCustomerGroupModel->addCustomer($customer_id, $group_id);
}
public function getTotalCustomer($group_id, array $condition = [])
{
return $this->objCustomerGroupModel->getTotalCustomer($group_id, $condition);
}
public function getListCustomer($group_id, array $condition = []) {
$objProvinceController = new AProvinceController();
return array_map(function ($item) use ($objProvinceController){
$item['province_name'] = $objProvinceController->getProvinceName($item['province']);
return $item;
}, $this->objCustomerGroupModel->getListCustomer($group_id, $condition));
}
protected function deleteFileBeforeDeleteItem($item_id): bool
{
// TODO: Implement deleteFileBeforeDeleteItem() method.
return true;
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Hura8\Components\Customer\AdminController;
use Hura8\Components\Customer\Controller\bCustomerLoyaltyController;
class ACustomerLoyaltyController extends bCustomerLoyaltyController {
}

View File

@@ -0,0 +1,40 @@
<?php
namespace Hura8\Components\Customer\Controller;
use Hura8\Components\Customer\Model\CustomerModel;
use Hura8\Components\Province\AdminController\AProvinceController;
use Hura8\System\Controller\aEntityBaseController;
class bCustomerController extends aEntityBaseController
{
/* @var CustomerModel $objCustomerModel */
protected $objCustomerModel;
/* @var AProvinceController $objProvinceController */
protected $objProvinceController;
public function __construct()
{
$this->objCustomerModel = new CustomerModel();
$this->objProvinceController = new AProvinceController();
parent::__construct($this->objCustomerModel);
}
public function formatItemInList(array $item_info)
{
return $this->formatItemInfo($item_info);
}
public function formatItemInfo(array $item_info)
{
$info = $item_info;
$info['province_name'] = $this->objProvinceController->getProvinceName($item_info['province']);
return $info;
}
}

View File

@@ -0,0 +1,206 @@
<?php
namespace Hura8\Components\Customer\Controller;
use ClientExtend\UserLoyaltyPointCalculation;
use Hura8\Components\Customer\Model\CustomerLoyaltyModel;
class bCustomerLoyaltyController
{
public static $POINT_NAME = 'điểm';
protected $point_setting = [];
protected $point_setting_config_file = ROOT_DIR . "/config/build/customer_point.php";
protected $level_setting = [];
protected $level_setting_config_file = ROOT_DIR . "/config/client/customer_level.php";
protected $level_by = '';// point|total_purchase_value as set by constant CHANGE_CUSTOMER_LEVEL_BY
/* @var $objUserLoyaltyPointCalculation UserLoyaltyPointCalculation */
protected $objUserLoyaltyPointCalculation;
/* @var CustomerLoyaltyModel $objCustomerLoyaltyModel */
protected $objCustomerLoyaltyModel;
public function __construct()
{
$this->objCustomerLoyaltyModel = new CustomerLoyaltyModel();
// import settings
//$new_info_file = "../config/build/customer_point.php" ;
//$config_file = ROOT_DIR . "/config/build/customer_point.php";
if(@file_exists($this->point_setting_config_file)) {
$this->point_setting = include $this->point_setting_config_file;
}
// customer level based on point gain
if( defined("ENABLE_CUSTOMER_POINT") && ENABLE_CUSTOMER_POINT ) {
if(@file_exists($this->level_setting_config_file)) {
$this->level_setting = include $this->level_setting_config_file;
}
}
// default is point
$this->level_by = (defined('CHANGE_CUSTOMER_LEVEL_BY')) ? CHANGE_CUSTOMER_LEVEL_BY : 'point';
$this->objUserLoyaltyPointCalculation = new UserLoyaltyPointCalculation($this->point_setting);
}
public function getPointSettingConfigFile() {
return $this->point_setting_config_file;
}
public function getPointSetting() {
return $this->point_setting;
}
public function getLevelSetting(){
return $this->level_setting;
}
// show estimate cart point
public function getEstimateCartPoint($cart_value){
$conversion_rate = (isset($this->point_setting['reward']['buy']['rate'])) ? $this->point_setting['reward']['buy']['rate'] : 0;
return ($conversion_rate) ? round($cart_value / $conversion_rate) : 0;
}
public function getUserPoint($user_id, array $condition, $return_type)
{
return $this->objCustomerLoyaltyModel->getUserPoint($user_id, $condition, $return_type);
}
// remove rewarded point i.e. reward for successfull order and now order is marked canceled
public function reclaimRewardedPoint($user_id, $activity_type_tracker){
// todo:
}
public function usePoint($user_id, $use_point, $activity_type, $activity_type_tracker, $reason = '', $point_args = ['order_value' => 0]){
// no user or no config
if(!$user_id || !ENABLE_CUSTOMER_POINT) return false;
$result = $this->objUserLoyaltyPointCalculation->calculateUsePoint($user_id, $use_point, $activity_type, $activity_type_tracker, $point_args);
$this->pointOp('use', $user_id, $result['use_point'], $activity_type, $activity_type_tracker, $reason);
return $result;
}
public function rewardPoint($user_id, $activity_type, $activity_type_tracker, $reason = '', $point_args = ['order_id' => 0]){
// no user or no config
if(!$user_id || !ENABLE_CUSTOMER_POINT) return false;
$point = $this->objUserLoyaltyPointCalculation->calculateRewardPoint($user_id, $activity_type, $activity_type_tracker, $point_args);
$this->pointOp('reward', $user_id, $point, $activity_type, $activity_type_tracker, $reason);
return $point;
}
// $operation: reward|use
// $change_point: positive (reward) or nagative (use)
protected function pointOp($operation, $user_id, $change_point, $activity_type, $activity_type_tracker, $reason = '') {
if(!$change_point) return false;
$reason_prefix = ($operation == 'use') ? 'Sử dụng' : 'Thưởng';
if($activity_type == 'return') $reason_prefix = 'Hoàn lại';
$full_reason = join(" ", [$reason_prefix, $change_point, static::$POINT_NAME, ":", $reason]);
if($operation == 'use') $change_point = -1 * $change_point;
// security: hash the row to avoid editing point directly in the database
$hash_value = sha1(join(".", [
$operation,
$user_id,
$change_point,
$activity_type,
$activity_type_tracker,
CURRENT_TIME,
'ass@ss'
]));
$new_id = $this->db->insert(
$this->tb_point ,
[
'customer_id' => $user_id ,
'activity_type' => $activity_type,
'activity_type_tracker' => $activity_type_tracker ,
'operation' => $operation,
'point' => $change_point,
'create_time' => CURRENT_TIME,
'reason' => substr($full_reason, 0, 200),
'referer_url' => substr(REFERER_URL, 0, 150) ,
'hash_value' => $hash_value ,
]
);
//update user reward balance
if($new_id) {
$this->updateStat($user_id, $change_point);
}
return $new_id;
}
protected function updateStat($user_id, $changed_point, $changed_order_value=0) {
return $this->objCustomerLoyaltyModel->updateStat($user_id, $changed_point, $changed_order_value);
}
private function calculateLevelByPoint($point) {
//if the point in between -> return the lowest level
$all_level = array_keys($this->level_setting);
foreach ( $all_level as $level) {
$next_level = $level + 1;
if(!in_array($next_level, $all_level)) $next_level = 0;
if($next_level) {
if( $point >= $this->level_setting[$level]["point_require"]
&& $point < $this->level_setting[$next_level]["point_require"] ) {
return $level;
}
}else{
if($point >= $this->level_setting[$level]["point_require"]) {
return $level;
}
}
}
return 0;
}
private function calculateLevelByOrderValue($aggregate_purchase_value = 0) {
//if the point in between -> return the lowest level
$all_level = array_keys($this->level_setting);
//tinh hang thanh vien theo so tien tich luy
foreach ( $all_level as $level ) {
$next_level = $level + 1;
if(!in_array($next_level, $all_level)) $next_level = 0;
if($next_level) {
if( $aggregate_purchase_value >= $this->level_setting[$level]["total_order_value"] &&
$aggregate_purchase_value < $this->level_setting[$next_level]["total_order_value"]
) {
return $level;
}
}else{
if( $aggregate_purchase_value >= $this->level_setting[$level]["total_order_value"]) {
return $level;
}
}
}
return 0;
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace Hura8\Components\Customer\Model;
use Hura8\System\Model\AuthModel;
class CustomerAuthModel extends AuthModel
{
private $tb_customer_login = "tb_customer_login";
private $tb_customer_access_code = "tb_customer_access_code";
private $tb_customer_login_log = "tb_customer_login_log";
public function __construct() {
parent::__construct($this->tb_customer_login, $this->tb_customer_access_code);
}
public function getLoginListByIds(array $staff_ids) {
if(!sizeof($staff_ids)) {
return [];
}
list($parameterized_ids, $bind_types) = create_bind_sql_parameter_from_value_list($staff_ids, 'int');
$bind_values = $staff_ids;
$query = $this->db->runQuery(
"SELECT `user_id`, `last_login_time`, `last_login_ip`, `last_login_device`, `last_login_browser`
FROM ".$this->tb_customer_login."
WHERE `user_id` IN (".$parameterized_ids.") ",
$bind_types,
$bind_values
);
$item_list = [];
foreach ($this->db->fetchAll($query) as $item) {
$item_list[$item['user_id']] = $item;
}
return $item_list;
}
public function getLoginLog(array $conditions = []) {
$bind_types = [];
$bind_values = [];
$query = $this->db->runQuery(
"SELECT * FROM ".$this->tb_customer_login_log." WHERE 1 ORDER BY `id` DESC LIMIT 100 ",
$bind_types,
$bind_values
);
return $this->db->fetchAll($query) ;
}
/**
* @param $email
* @param string $login_status ok or error
* @param string $login_msg
*/
public function logLogin($email, $login_status, $login_msg) {
$this->db->insert(
$this->tb_customer_login_log,
[
"email" => substr($email, 0, 45),
"login_status" => $login_status,
"login_msg" => substr($login_msg, 0, 45),
"ip_address" => substr(USER_IP, 0, 45),
"user_agent" => substr(USER_AGENT, 0, 99),
"create_time" => CURRENT_TIME,
]
);
}
}

View File

@@ -0,0 +1,213 @@
<?php
namespace Hura8\Components\Customer\Model;
use Hura8\Interfaces\AppResponse;
use Hura8\Interfaces\iEntityModel;
use Hura8\System\Controller\UrlManagerController;
use Hura8\System\IDGenerator;
use Hura8\System\Model\aEntityBaseModel;
class CustomerGroupModel extends aEntityBaseModel implements iEntityModel
{
protected $tb_customer_per_group = "tb_customer_per_group";
public function __construct() {
parent::__construct('customer_group');
}
public function updateItemCount($group_id) {
$this->db->runQuery(
"UPDATE `".$this->tb_entity."` SET
`customer_count` = (SELECT COUNT(*) AS total FROM `".$this->tb_customer_per_group."` WHERE `group_id` = ? )
WHERE `id` = ? LIMIT 1
",
['d', 'd'], [$group_id, $group_id]
);
}
public function getTotalCustomer($group_id, array $condition = [])
{
$query = $this->db->runQuery(
" SELECT COUNT(*) as total FROM `".$this->tb_customer_per_group."` WHERE `group_id` = ? ",
['d'], [$group_id]
);
$total = 0;
if ($rs = $this->db->fetchAssoc($query)) {
$total = $rs['total'];
}
return $total;
}
public function getListCustomer($group_id, array $condition = [])
{
$numPerPage = (isset($condition['numPerPage']) && $condition['numPerPage'] > 0 ) ? intval($condition['numPerPage']) : 20 ;
$page = (isset($condition['page']) && $condition['page'] > 0 ) ? intval($condition['page']) : 1 ;
$order_by = " `id` DESC";
$query = $this->db->runQuery(
"SELECT `customer_id` FROM ".$this->tb_customer_per_group." WHERE `group_id` = ?
ORDER BY ".$order_by."
LIMIT ".(($page-1) * $numPerPage).", ".$numPerPage ,
['d'], [$group_id]
) ;
$item_list_ids = array_map(function ($item){ return $item['customer_id'];}, $this->db->fetchAll($query));
$objCustomerModel = new CustomerModel();
$list_info = $objCustomerModel->getListByIds($item_list_ids);
// final list
$final_list = [];
foreach ($item_list_ids as $_id) {
$final_list[] = $list_info[$_id] ?? null;
}
return $final_list;
}
public function removeCustomerFromAllGroup($customer_id)
{
$this->db->runQuery(
"DELETE FROM `".$this->tb_customer_per_group."` WHERE `customer_id` = ?",
['d'], [$customer_id]
);
return true;
}
public function removeCustomer($customer_id, $group_id)
{
$this->db->runQuery(
"DELETE FROM `".$this->tb_customer_per_group."` WHERE `group_id` =? AND `customer_id` = ? LIMIT 1 ",
['d', 'd'],
[$group_id, $customer_id]
);
$this->updateItemCount($group_id);
return true;
}
public function addCustomer($customer_id, $group_id)
{
$query = $this->db->runQuery(
" SELECT * FROM `".$this->tb_customer_per_group."` WHERE `group_id` = ? AND `customer_id` = ? LIMIT 1 ",
['d', 'd'],
[$group_id, $customer_id]
);
if ($this->db->fetchAssoc($query)) {
return false;
}
$this->db->insert(
$this->tb_customer_per_group,
[
"group_id" => $group_id,
"customer_id" => $customer_id,
]
);
$this->updateItemCount($group_id);
return true;
}
protected function _buildQueryConditionExtend(array $filter_condition): ?array
{
/*$condition = array(
"q" => "",
"status" => 0,
);*/
$catCondition = [];
$bind_types = [];
$bind_values = [];
return array( join(" ", $catCondition), $bind_types, $bind_values);
}
protected function beforeCreateItem(array $input_info) : AppResponse
{
$info = $input_info;
if(!$info['group_code']) $info['group_code'] = $info['title'];
$info['group_code'] = $this->createUniqueCode(0, $info['group_code']);
$info['create_time'] = CURRENT_TIME;
$info['create_by'] = ADMIN_NAME;
return new AppResponse('ok', null, $info);
}
protected function afterCreateItem($new_item_id, $new_item_info)
{
}
protected function beforeUpdateItem($item_id, $current_item_info, $new_input_info): AppResponse
{
$info = $new_input_info;
if(isset($info['group_code'])) {
if(!$info['group_code']) $info['group_code'] = $info['title'];
$info['group_code'] = $this->createUniqueCode($item_id, $info['group_code']);
}
$info['last_update'] = CURRENT_TIME;
$info['last_update_by'] = ADMIN_NAME;
return new AppResponse('ok', null, $info);
}
protected function createUniqueCode($current_item_id, $wanted_code){
$clean_code = UrlManagerController::create_url_index($wanted_code);
//if exist and belong other id, create a new one
$query = $this->db->runQuery("SELECT `id` FROM `".$this->tb_entity."` WHERE `group_code` = ? LIMIT 1 ", ['s'], [$clean_code]) ;
if($info = $this->db->fetchAssoc($query)){
if($info['id'] != $current_item_id) {
$new_code = $clean_code."-".IDGenerator::createStringId(3);
return $this->createUniqueCode($current_item_id, $new_code);
}
}
return $clean_code;
}
protected function afterUpdateItem($item_id, $old_item_info, $new_item_info)
{
}
protected function beforeDeleteItem($item_id, $item_info) : AppResponse
{
return new AppResponse('ok');
}
protected function afterDeleteItem($item_id, $item_info)
{
}
protected function extendedFilterOptions(): array
{
return [];
}
}

View File

@@ -0,0 +1,268 @@
<?php
namespace Hura8\Components\Customer\Model;
use ClientExtend\UserLoyaltyPointCalculation;
use Hura8\Database\iConnectDB;
use Hura8\Interfaces\TableName;
class CustomerLoyaltyModel
{
/* @var iConnectDB $db */
protected $db;
protected $tb_point = TableName::CUSTOMER_POINT; // "idv_customer_point";
protected $tb_customer = TableName::CUSTOMER; // "idv_customer";
public static $POINT_NAME = 'điểm';
protected $point_setting = [];
protected $point_setting_config_file = ROOT_DIR . "/config/build/customer_point.php";
protected $level_setting = [];
protected $level_setting_config_file = ROOT_DIR . "/config/client/customer_level.php";
protected $level_by = '';// point|total_purchase_value as set by constant CHANGE_CUSTOMER_LEVEL_BY
/* @var $objUserLoyaltyPointCalculation UserLoyaltyPointCalculation */
protected $objUserLoyaltyPointCalculation;
public function __construct()
{
$this->db = get_db("", ENABLE_DB_DEBUG);
// import settings
//$new_info_file = "../config/build/customer_point.php" ;
//$config_file = ROOT_DIR . "/config/build/customer_point.php";
if(@file_exists($this->point_setting_config_file)) {
$this->point_setting = include $this->point_setting_config_file;
}
// customer level based on point gain
if( defined("ENABLE_CUSTOMER_POINT") && ENABLE_CUSTOMER_POINT ) {
if(@file_exists($this->level_setting_config_file)) {
$this->level_setting = include $this->level_setting_config_file;
}
}
// default is point
$this->level_by = (defined('CHANGE_CUSTOMER_LEVEL_BY')) ? CHANGE_CUSTOMER_LEVEL_BY : 'point';
$this->objUserLoyaltyPointCalculation = new UserLoyaltyPointCalculation($this->point_setting);
}
public function getPointSettingConfigFile() {
return $this->point_setting_config_file;
}
public function getPointSetting() {
return $this->point_setting;
}
public function getLevelSetting(){
return $this->level_setting;
}
// show estimate cart point
public function getEstimateCartPoint($cart_value){
$conversion_rate = (isset($this->point_setting['reward']['buy']['rate'])) ? $this->point_setting['reward']['buy']['rate'] : 0;
return ($conversion_rate) ? round($cart_value / $conversion_rate) : 0;
}
public function getUserPoint($user_id, array $condition, $return_type)
{
if($return_type == "total") {
//Lay tong so
$query = $this->db->runQuery("SELECT COUNT(*) AS total FROM `". $this->tb_point ."` WHERE `customer_id` = ? " , ['d'], [$user_id]);
if($resultTotal = $this->db->fetchAssoc($query)){
return $resultTotal['total'];
}
return 0;
} else {
$numPerPage = (isset($condition['numPerPage'])) ? intval($condition['numPerPage']) : 50;
$page = getPageId();
$query = $this->db->runQuery("
SELECT * FROM `". $this->tb_point ."`
WHERE `customer_id` = ?
ORDER BY id DESC
LIMIT ".($page - 1) * $numPerPage .", ".$numPerPage." " , ['d'], [$user_id]);
$result = array();
$i = ($page - 1) * $numPerPage;
foreach ( $this->db->fetchAll($query) as $rs){
$i++;
$rs['counter'] = $i;
$rs['activity_type_name'] = (isset($this->point_setting[$rs['operation']][$rs['activity_type']])) ? $this->point_setting[$rs['operation']][$rs['activity_type']]['name'] : '--';
$result[] = $rs;
}
return $result;
}
}
public function usePoint($user_id, $use_point, $activity_type, $activity_type_tracker, $reason = '', $point_args = ['order_value' => 0]){
// no user or no config
if(!$user_id || !ENABLE_CUSTOMER_POINT) return false;
$result = $this->objUserLoyaltyPointCalculation->calculateUsePoint($user_id, $use_point, $activity_type, $activity_type_tracker, $point_args);
$this->pointOp('use', $user_id, $result['use_point'], $activity_type, $activity_type_tracker, $reason);
return $result;
}
public function rewardPoint($user_id, $activity_type, $activity_type_tracker, $reason = '', $point_args = ['order_id' => 0]){
// no user or no config
if(!$user_id || !ENABLE_CUSTOMER_POINT) return false;
$point = $this->objUserLoyaltyPointCalculation->calculateRewardPoint($user_id, $activity_type, $activity_type_tracker, $point_args);
$this->pointOp('reward', $user_id, $point, $activity_type, $activity_type_tracker, $reason);
return $point;
}
// $operation: reward|use
// $change_point: positive (reward) or nagative (use)
protected function pointOp($operation, $user_id, $change_point, $activity_type, $activity_type_tracker, $reason = '') {
if(!$change_point) return false;
$reason_prefix = ($operation == 'use') ? 'Sử dụng' : 'Thưởng';
if($activity_type == 'return') $reason_prefix = 'Hoàn lại';
$full_reason = join(" ", [$reason_prefix, $change_point, static::$POINT_NAME, ":", $reason]);
if($operation == 'use') $change_point = -1 * $change_point;
// security: hash the row to avoid editing point directly in the database
$hash_value = sha1(join(".", [
$operation,
$user_id,
$change_point,
$activity_type,
$activity_type_tracker,
CURRENT_TIME,
'ass@ss'
]));
$new_id = $this->db->insert(
$this->tb_point ,
[
'customer_id' => $user_id ,
'activity_type' => $activity_type,
'activity_type_tracker' => $activity_type_tracker ,
'operation' => $operation,
'point' => $change_point,
'create_time' => CURRENT_TIME,
'reason' => substr($full_reason, 0, 200),
'referer_url' => substr(REFERER_URL, 0, 150) ,
'hash_value' => $hash_value ,
]
);
//update user reward balance
if($new_id) {
$this->updateStat($user_id, $change_point);
}
return $new_id;
}
public function updateStat($user_id, $changed_point, $changed_order_value=0) {
$user_id = intval($user_id);
$query = $this->db->runQuery("SELECT
`loyalty_point`,
`loyalty_level`,
`total_value_success`
FROM ".$this->tb_customer."
WHERE `id` = ?
LIMIT 1 " , ['d'], [$user_id]);
if($current = $this->db->fetchAssoc($query)){
$new_point = $current['loyalty_point'] + $changed_point;
$new_purchase_value = $current['total_value_success'] + $changed_order_value;
$level = $current['loyalty_level'];
if($this->level_by == 'point' && $changed_point != 0) $level = $this->calculateLevelByPoint($new_point);
else if($changed_order_value) $level = $this->calculateLevelByOrderValue($new_purchase_value);
$this->db->update(
$this->tb_customer ,
[
'loyalty_point' => $new_point,
'loyalty_level' => $level,
'total_value_success' => $new_purchase_value,
],
[
'id' => $user_id,
],
1
);
}
return true;
}
private function calculateLevelByPoint($point) {
//if the point in between -> return the lowest level
$all_level = array_keys($this->level_setting);
foreach ( $all_level as $level) {
$next_level = $level + 1;
if(!in_array($next_level, $all_level)) $next_level = 0;
if($next_level) {
if( $point >= $this->level_setting[$level]["point_require"]
&& $point < $this->level_setting[$next_level]["point_require"] ) {
return $level;
}
}else{
if($point >= $this->level_setting[$level]["point_require"]) {
return $level;
}
}
}
return 0;
}
private function calculateLevelByOrderValue($aggregate_purchase_value = 0) {
//if the point in between -> return the lowest level
$all_level = array_keys($this->level_setting);
//tinh hang thanh vien theo so tien tich luy
foreach ( $all_level as $level ) {
$next_level = $level + 1;
if(!in_array($next_level, $all_level)) $next_level = 0;
if($next_level) {
if( $aggregate_purchase_value >= $this->level_setting[$level]["total_order_value"] &&
$aggregate_purchase_value < $this->level_setting[$next_level]["total_order_value"]
) {
return $level;
}
}else{
if( $aggregate_purchase_value >= $this->level_setting[$level]["total_order_value"]) {
return $level;
}
}
}
return 0;
}
}

View File

@@ -0,0 +1,128 @@
<?php
namespace Hura8\Components\Customer\Model;
use Hura8\Interfaces\AppResponse;
use Hura8\Interfaces\iSearch;
use Hura8\System\IDGenerator;
use Hura8\System\Model\aEntityBaseModel;
use Hura8\Interfaces\iEntityModel;
use Hura8\Interfaces\EntityType;
use Hura8\System\Security\DataValidator;
class CustomerModel extends aEntityBaseModel implements iEntityModel
{
protected $user_types = [
"auto" => "Chưa đăng ký",
"register" => "Đăng ký thành viên",
];
/* @var iSearch $objSearchModel */
protected $objSearchModel;
public function __construct() {
$this->objSearchModel = new CustomerSearchModel();
parent::__construct(
EntityType::CUSTOMER,
"",
$this->objSearchModel
);
}
protected function extendedFilterOptions() : array
{
return [
'province' => 0,
'user_type' => '',
];
}
public function getInfoByCRMCode($code)
{
$query = $this->db->runQuery("SELECT * FROM `".$this->tb_entity."` WHERE `crm_code` = ? LIMIT 1 ", ['s'], [$code]) ;
if( $item_info = $this->db->fetchAssoc($query)){
return $this->formatItemInfo($item_info);
}
return false;
}
public function getInfoByEmail($email, $user_type='register')
{
$query = $this->db->runQuery(
"SELECT * FROM `".$this->tb_entity."` WHERE `email` = ? AND `type` = ? LIMIT 1 ",
['s', 's'], [$email, $user_type]
) ;
if( $item_info = $this->db->fetchAssoc($query)){
return $this->formatItemInfo($item_info);
}
return null;
}
public function getInfoByVerifiedEmail($email)
{
$info = $this->getInfoByEmail($email);
if($info && $info['is_email_verify']) {
return $info;
}
return null;
}
protected function _buildQueryConditionExtend(array $filter_condition): ?array
{
/*$condition = array(
"user_type" => ''
"q" => "",
"status" => 0,
);*/
$catCondition = [];
$bind_types = [];
$bind_values = [];
if(isset($filter_condition["province"]) && $filter_condition["province"]) {
$catCondition[] = " AND `province` = ? ";
$bind_types[] = 'd';
$bind_values[] = $filter_condition["province"];
}
// user_type
if(isset($filter_condition["user_type"]) && array_key_exists($filter_condition["user_type"], $this->user_types) ){
$catCondition[] = " AND `type` = ? ";
$bind_types[] = 's';
$bind_values[] = $filter_condition["user_type"];
}
return array( join(" ", $catCondition), $bind_types, $bind_values);
}
protected function formatItemInfo(array $item_info): array
{
if($item_info["birth_day"] && $item_info["birth_year"]) {
$item_info["birth_day"] = $item_info["birth_day"]."-".$item_info["birth_year"];
}
return $item_info;
}
protected function createWebCRMCode($input_code = '') {
return $input_code ?: "Web-".IDGenerator::createStringId(8);
}
}

Some files were not shown because too many files have changed in this diff Show More