c
This commit is contained in:
13
_shared.php
13
_shared.php
@@ -1,8 +1,17 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
define("ROOT_DIR", __DIR__ );
|
const ROOT_DIR = __DIR__;
|
||||||
|
const CONFIG_DIR = ROOT_DIR . '/inc/config';
|
||||||
|
|
||||||
include __DIR__."/inc/common.php";
|
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
|
// start autoload
|
||||||
init_autoload();
|
init_autoload();
|
||||||
|
|||||||
@@ -1 +1,36 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Hura8\Components\Product\AdminController\AProductController;
|
||||||
|
use Hura8\System\Paging;
|
||||||
|
|
||||||
|
$objAProductController = new AProductController();
|
||||||
|
|
||||||
|
//Paging setting
|
||||||
|
$numPerPage = getPageSize(15);
|
||||||
|
$conditions = [
|
||||||
|
"category" => explode("-", getRequest("category", '')),
|
||||||
|
"brand" => explode("-", getRequest("brand", '')),
|
||||||
|
"hotType" => explode("-", getRequest("hotType", '')),
|
||||||
|
"other_filter" => [getRequest("other_filter", '')],
|
||||||
|
"q" => getRequest("q", ''),
|
||||||
|
'numPerPage' => $numPerPage,
|
||||||
|
'page' => getPageId(),
|
||||||
|
'translated' => getRequestInt('translated', 0),
|
||||||
|
//... more extended filters
|
||||||
|
];
|
||||||
|
//debug_var($objAProductController->getFilterConditions());
|
||||||
|
|
||||||
|
$totalResults = $objAProductController->getTotal($conditions);
|
||||||
|
$item_list = $objAProductController->getList($conditions);
|
||||||
|
|
||||||
|
list($page_collection, $tb_page, $total_pages) = Paging::paging_template($totalResults, $numPerPage);
|
||||||
|
|
||||||
|
return [
|
||||||
|
"total" => $totalResults,
|
||||||
|
"item_list" => $item_list,
|
||||||
|
"pagination" => [
|
||||||
|
'collection' => $page_collection,
|
||||||
|
'html' => $tb_page,
|
||||||
|
'total_pages' => $total_pages,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\AdminController;
|
||||||
|
|
||||||
|
use Hura8\System\Controller\aAdminEntityBaseController;
|
||||||
|
use Hura8\Components\Product\Model\ProductAttributeLanguageModel;
|
||||||
|
use Hura8\Components\Product\Model\ProductAttributeModel;
|
||||||
|
use Hura8\Components\Product\Model\ProductAttributeValueModel;
|
||||||
|
use Hura8\System\Security\DataClean;
|
||||||
|
use Hura8\System\Security\DataType;
|
||||||
|
|
||||||
|
|
||||||
|
class AProductAttributeController extends aAdminEntityBaseController
|
||||||
|
{
|
||||||
|
/* @var ProductAttributeModel $objProductAttributeModel */
|
||||||
|
protected $objProductAttributeModel;
|
||||||
|
|
||||||
|
/* @var ProductAttributeLanguageModel $objProductAttributeLanguageModel */
|
||||||
|
protected $objProductAttributeLanguageModel;
|
||||||
|
|
||||||
|
protected $view_language = LANGUAGE;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->objProductAttributeModel = new ProductAttributeModel();
|
||||||
|
|
||||||
|
if(!$this->isDefaultLanguage()) {
|
||||||
|
$this->objProductAttributeLanguageModel = new ProductAttributeLanguageModel($this->view_language);
|
||||||
|
//$this->objProductAttributeLanguageModel->createTableLang();
|
||||||
|
|
||||||
|
parent::__construct($this->objProductAttributeModel, $this->objProductAttributeLanguageModel);
|
||||||
|
|
||||||
|
}else{
|
||||||
|
parent::__construct($this->objProductAttributeModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductAttributes($product_id) {
|
||||||
|
$objProductAttributeValueModel = new ProductAttributeValueModel(0);
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ($objProductAttributeValueModel->getProductAttributes($product_id) as $info) {
|
||||||
|
$result[$info['attr_id']][] = $info['attr_value_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateProductAttributes($product_id, array $current_value, array $new_value) {
|
||||||
|
/*
|
||||||
|
$current_value => Array
|
||||||
|
(
|
||||||
|
// attribute_id => [value1, value2]
|
||||||
|
[2] => Array
|
||||||
|
(
|
||||||
|
[0] => 2
|
||||||
|
[1] => 3
|
||||||
|
)
|
||||||
|
|
||||||
|
[1] => Array
|
||||||
|
(
|
||||||
|
[0] => 1
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
$new_value => Array
|
||||||
|
(
|
||||||
|
// attribute_id => new_str_values by lines
|
||||||
|
[2] => asdas
|
||||||
|
[1] => asda
|
||||||
|
)
|
||||||
|
* */
|
||||||
|
|
||||||
|
// list of values for product
|
||||||
|
$product_value_ids = [];
|
||||||
|
|
||||||
|
// use current values
|
||||||
|
foreach ($current_value as $_attr_id => $_value_ids) {
|
||||||
|
$product_value_ids = array_merge($product_value_ids, $_value_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new values for attributes
|
||||||
|
foreach ($new_value as $_attr_id => $_value_text) {
|
||||||
|
$new_value_texts = array_filter(explode("\n", $_value_text));
|
||||||
|
foreach ($new_value_texts as $new_value) {
|
||||||
|
// if exist
|
||||||
|
$check_exist = $this->getValueByTitle($_attr_id, $new_value);
|
||||||
|
if($check_exist) {
|
||||||
|
$product_value_ids[] = $check_exist['id'];
|
||||||
|
}else{
|
||||||
|
$try_create_id = $this->addValue($_attr_id, ['title' => $new_value]);
|
||||||
|
if($try_create_id) $product_value_ids[] = $try_create_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$objProductAttributeValueModel = new ProductAttributeValueModel(0);
|
||||||
|
|
||||||
|
return $objProductAttributeValueModel->updateProductAttributes($product_id, $product_value_ids);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function updateAttributeInSpecGroup($group_id, $attr_id, array $info) {
|
||||||
|
$this->objProductAttributeModel->updateAttributeInSpecGroup($group_id, $attr_id, $info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeAttributeFromSpecGroup($group_id, $attr_id) {
|
||||||
|
$this->objProductAttributeModel->removeAttributeFromSpecGroup($group_id, $attr_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addAttributeToSpecGroup($group_id, $attr_id, $ordering) {
|
||||||
|
$this->objProductAttributeModel->addAttributeToSpecGroup($group_id, $attr_id, $ordering);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateAttributeInCategory($cat_id, $attr_id, array $info) {
|
||||||
|
$this->objProductAttributeModel->updateAttributeInCategory($cat_id, $attr_id, $info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeAttributeFromCategory($cat_id, $attr_id) {
|
||||||
|
$this->objProductAttributeModel->removeAttributeFromCategory($cat_id, $attr_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addAttributeToCategory($cat_id, $attr_id, $ordering) {
|
||||||
|
$this->objProductAttributeModel->addAttributeToCategory($cat_id, $attr_id, $ordering);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllAtributes() {
|
||||||
|
return $this->objProductAttributeModel->getList(["numPerPage" => 2000]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteValue($attr_id, $value_id) {
|
||||||
|
$objProductAttributeValueModel = new ProductAttributeValueModel($attr_id);
|
||||||
|
return $objProductAttributeValueModel->delete($value_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateValue($attr_id, $value_id, array $info) {
|
||||||
|
$objProductAttributeValueModel = new ProductAttributeValueModel($attr_id);
|
||||||
|
return $objProductAttributeValueModel->updateFields($value_id, $info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValueByTitle($attr_id, $value_title) {
|
||||||
|
$objProductAttributeValueModel = new ProductAttributeValueModel($attr_id);
|
||||||
|
$filter_code = DataClean::makeInputSafe($value_title, DataType::ID);
|
||||||
|
return $objProductAttributeValueModel->getInfoByCode($filter_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addValue($attr_id, array $info) {
|
||||||
|
$objProductAttributeValueModel = new ProductAttributeValueModel($attr_id);
|
||||||
|
return $objProductAttributeValueModel->create($info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getListAttributeValues($attr_id) {
|
||||||
|
$objProductAttributeValueModel = new ProductAttributeValueModel($attr_id);
|
||||||
|
return $objProductAttributeValueModel->getList(['numPerPage' => 200]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function deleteFileBeforeDeleteItem($item_id): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\AdminController;
|
||||||
|
|
||||||
|
use Hura8\Components\Product\Controller\bProductCategoryController;
|
||||||
|
use Hura8\Interfaces\AppResponse;
|
||||||
|
use Hura8\Components\Product\Model\ProductCategoryInfoModel;
|
||||||
|
use Hura8\Interfaces\iEntityAdminCategoryController;
|
||||||
|
use Hura8\Traits\AdminEntityCategoryControllerTraits;
|
||||||
|
|
||||||
|
|
||||||
|
class AProductCategoryController extends bProductCategoryController implements iEntityAdminCategoryController
|
||||||
|
{
|
||||||
|
|
||||||
|
use AdminEntityCategoryControllerTraits;
|
||||||
|
|
||||||
|
|
||||||
|
public function updateItemCount($id) {
|
||||||
|
$this->objProductCategoryModel->updateItemCount($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getAttributeList($catId) {
|
||||||
|
return $this->objProductCategoryModel->getAttributeList($catId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function create(array $info) : AppResponse
|
||||||
|
{
|
||||||
|
$res = parent::create($info);
|
||||||
|
|
||||||
|
if($res->getStatus() == 'ok') {
|
||||||
|
$objProductCategoryInfoModel = new ProductCategoryInfoModel();
|
||||||
|
$objProductCategoryInfoModel->createInfo($res->getData(), $info);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function update($id, array $info) : AppResponse
|
||||||
|
{
|
||||||
|
if(!$this->isDefaultLanguage()) {
|
||||||
|
return parent::update($id, $info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update info
|
||||||
|
$objProductCategoryInfoModel = new ProductCategoryInfoModel();
|
||||||
|
$objProductCategoryInfoModel->updateInfo($id, $info);
|
||||||
|
|
||||||
|
return parent::update($id, $info);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\AdminController;
|
||||||
|
|
||||||
|
use Hura8\Components\Product\Controller\bProductCollectionController;
|
||||||
|
use Hura8\Interfaces\iEntityAdminCategoryController;
|
||||||
|
use Hura8\Traits\AdminEntityCategoryControllerTraits;
|
||||||
|
|
||||||
|
|
||||||
|
class AProductCollectionController extends bProductCollectionController implements iEntityAdminCategoryController
|
||||||
|
{
|
||||||
|
|
||||||
|
use AdminEntityCategoryControllerTraits;
|
||||||
|
|
||||||
|
public function updateProduct($product_id, $collection_id, array $info)
|
||||||
|
{
|
||||||
|
return $this->objProductCollectionModel->updateProduct($product_id, $collection_id, $info);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function removeProduct($product_id, $collection_id)
|
||||||
|
{
|
||||||
|
return $this->objProductCollectionModel->removeProduct($product_id, $collection_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function addProduct($product_id, $collection_id, $ordering=0)
|
||||||
|
{
|
||||||
|
return $this->objProductCollectionModel->addProduct($product_id, $collection_id, $ordering);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\AdminController;
|
||||||
|
|
||||||
|
use Hura8\Components\Product\Controller\bProductController;
|
||||||
|
use Hura8\Components\Product\Model\ProductImageModel;
|
||||||
|
use Hura8\Components\Product\Model\ProductInfoModel;
|
||||||
|
|
||||||
|
// Main class extend from base class implementing an interface
|
||||||
|
// Main class use traits shared by other classes with same responsibilities
|
||||||
|
|
||||||
|
class AProductController extends bProductController {
|
||||||
|
|
||||||
|
|
||||||
|
protected function extendFilterConditions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"price" => array('max' => 0, 'min'=> 0),
|
||||||
|
"brand" => array(), // array(1,2,3,)
|
||||||
|
"collection" => array(), // array(1,2,3,)
|
||||||
|
"supplier" => array(), // array(1,2,3,)
|
||||||
|
"rating" => array(), // array(1,2,3,)
|
||||||
|
"category" => array(), // array(1,2,3,)
|
||||||
|
"status" => array(), // array(1,2,3,)
|
||||||
|
"hotType" => array(),// array(saleoff | not | new)
|
||||||
|
"attribute" => array(), // array(1,2,3,)
|
||||||
|
"promotion" => "",
|
||||||
|
"storeId" => array(), // array(1,2,3,)
|
||||||
|
"other_filter" => array(), // array(in-stock, has-promotion etc...)
|
||||||
|
"spec_group_id" => array(), // array(1,2,3,)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
//get product image list
|
||||||
|
public function productImageList($proId){
|
||||||
|
$objProductImageModel = new ProductImageModel($proId);
|
||||||
|
$result = array();
|
||||||
|
foreach ( $objProductImageModel->getList(["numPerPage" => 100]) as $rs ) {
|
||||||
|
|
||||||
|
$rs['image'] = static::getResizedImageCollection($rs['img_name']);
|
||||||
|
|
||||||
|
$result[] = $rs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get only from tb_product_info
|
||||||
|
public function getInfoMore($id)
|
||||||
|
{
|
||||||
|
$objProductInfoModel = new ProductInfoModel();
|
||||||
|
$info = $objProductInfoModel->getInfo($id);
|
||||||
|
|
||||||
|
if(!$this->isDefaultLanguage() && $info) {
|
||||||
|
$language_info = $this->iEntityLanguageModel->getInfo($id);
|
||||||
|
$final_info = [];
|
||||||
|
foreach ($info as $_k => $_v) {
|
||||||
|
$final_info[$_k] = $language_info[$_k] ?? $_v;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $final_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInfoMoreEmpty($addition_field_value = [])
|
||||||
|
{
|
||||||
|
$objProductInfoModel = new ProductInfoModel();
|
||||||
|
return $objProductInfoModel->getEmptyInfo($addition_field_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\AdminController;
|
||||||
|
|
||||||
|
use Hura8\System\Url;
|
||||||
|
use Hura8\Components\Product\Controller\ProductFilterController;
|
||||||
|
|
||||||
|
|
||||||
|
class AProductFilterController extends ProductFilterController
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
public static function getSortOptions($order) {
|
||||||
|
return array(
|
||||||
|
array(
|
||||||
|
"selected" => '',
|
||||||
|
"name" => "Sắp xếp sản phẩm",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("order"=>"")),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
"selected" => ($order == 'ordering') ? "selected" : "",
|
||||||
|
"name" => "Thứ tự cửa hàng",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("order"=>"ordering")),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
"selected" => ($order == 'view') ? "selected" : "",
|
||||||
|
"name" => "Xem nhiều nhất",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("order"=>"view")),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
"selected" => ($order == 'new') ? "selected" : "",
|
||||||
|
"name" => "Mới nhất",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("order"=>"new")),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
"selected" => ($order == 'last-update') ? "selected" : "",
|
||||||
|
"name" => "Thời gian cập nhật",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("order"=>"last-update")),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getFilterOptions($other_filter) {
|
||||||
|
|
||||||
|
return array(
|
||||||
|
array(
|
||||||
|
"selected" => ($other_filter == 'no-price') ? "selected" : "",
|
||||||
|
"name" => "Giá bán = 0",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("other_filter"=>"no-price")),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
"selected" => ($other_filter == 'in-stock') ? "selected" : "",
|
||||||
|
"name" => "Còn hàng",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("other_filter"=>"in-stock")),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
"selected" => ($other_filter == 'out-stock') ? "selected" : "",
|
||||||
|
"name" => "Hết hàng",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("other_filter"=>"out-stock")),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
"selected" => ($other_filter == 'has-market-price') ? "selected" : "",
|
||||||
|
"name" => "Có giá thị trường",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("other_filter"=>"has-market-price")),
|
||||||
|
),
|
||||||
|
|
||||||
|
array(
|
||||||
|
"selected" => ($other_filter == 'has-promotion') ? "selected" : "",
|
||||||
|
"name" => "Có khuyến mại",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("other_filter"=>"has-promotion")),
|
||||||
|
),
|
||||||
|
|
||||||
|
array(
|
||||||
|
"selected" => ($other_filter == 'has-config') ? "selected" : "",
|
||||||
|
"name" => "Có cấu hình",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("other_filter"=>"has-config")),
|
||||||
|
),
|
||||||
|
|
||||||
|
array(
|
||||||
|
"selected" => ($other_filter == 'no-sku') ? "selected" : "",
|
||||||
|
"name" => "Chưa có mã kho",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("other_filter"=>"no-sku")),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
"selected" => ($other_filter == 'no-image') ? "selected" : "",
|
||||||
|
"name" => "Chưa có ảnh",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("other_filter"=>"no-image")),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
"selected" => ($other_filter == 'display-off') ? "selected" : "",
|
||||||
|
"name" => "Chưa hiển thị",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("other_filter"=>"display-off")),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
"selected" => ($other_filter == 'display-on') ? "selected" : "",
|
||||||
|
"name" => "Đang hiển thị",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("other_filter"=>"display-on")),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
"selected" => ($other_filter == 'no-category') ? "selected" : "",
|
||||||
|
"name" => "Chưa có danh mục",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("other_filter"=>"no-category")),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
"selected" => ($other_filter == 'no-brand') ? "selected" : "",
|
||||||
|
"name" => "Chưa có thương hiệu",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("other_filter"=>"no-brand")),
|
||||||
|
),
|
||||||
|
|
||||||
|
array(
|
||||||
|
"selected" => ($other_filter == 'no-warranty') ? "selected" : "",
|
||||||
|
"name" => "Chưa có bảo hành",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("other_filter"=>"no-warranty")),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
"selected" => ($other_filter == 'no-description') ? "selected" : "",
|
||||||
|
"name" => "Chưa có mô tả",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("other_filter"=>"no-description")),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
"selected" => ($other_filter == 'no-spec-text') ? "selected" : "",
|
||||||
|
"name" => "Chưa có thông số nhập text",
|
||||||
|
"url" => Url::buildUrl(CURRENT_URL, array("other_filter"=>"no-spec-text")),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,948 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\AdminController;
|
||||||
|
|
||||||
|
|
||||||
|
use Hura8\System\ProductFilterPrice;
|
||||||
|
use Hura8\System\Registry;
|
||||||
|
use Hura8\System\Security;
|
||||||
|
use Hura8\System\Url;
|
||||||
|
use Hura8\Components\Brand\Model\BrandModel;
|
||||||
|
use Hura8\Components\Product\Model\ProductAttributeModel;
|
||||||
|
use Hura8\Components\Product\Model\ProductCategoryModel;
|
||||||
|
use Hura8\Components\Product\Model\ProductSearchModel;
|
||||||
|
use Hura8\System\Security\DataType;
|
||||||
|
use Hura8\Interfaces\TableName;
|
||||||
|
|
||||||
|
class AProductFilterOldController
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $filters = [
|
||||||
|
"price" => false,
|
||||||
|
"brand" => array(),
|
||||||
|
"collection" => array(),
|
||||||
|
"supplier" => array(),
|
||||||
|
"rating" => array(),
|
||||||
|
"category" => array(),
|
||||||
|
"status" => "",
|
||||||
|
"query" => "",
|
||||||
|
"hotType" => "",//saleoff | not | new
|
||||||
|
"attribute" => [],//,3793,3794,
|
||||||
|
"ids" => array(),
|
||||||
|
'excluded_ids' => array(),
|
||||||
|
"promotion" => "",
|
||||||
|
"storeId" => '',
|
||||||
|
"other_filter" => array() //in-stock, has-promotion etc...
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getDefaultFilter() {
|
||||||
|
return [
|
||||||
|
"price" => self::getPriceFilterRange(),
|
||||||
|
"brand" => $this->getBrandFilter(),
|
||||||
|
"collection" => array_filter(explode(FILTER_VALUE_SEPARATOR, getRequest('collection'))),
|
||||||
|
"supplier" => array_filter(explode(FILTER_VALUE_SEPARATOR, getRequest('supplier'))),
|
||||||
|
"rating" => array_filter(explode(FILTER_VALUE_SEPARATOR, getRequest('rating'))),
|
||||||
|
"category" => array_filter(explode(FILTER_VALUE_SEPARATOR, getRequest('category'))),
|
||||||
|
//"status" => "1",
|
||||||
|
"query" => getRequest('q', ''),
|
||||||
|
"hotType" => getRequest('hotType'),//saleoff | not | new
|
||||||
|
"attribute" => $this->getAttributeFilter([]),//,3793,3794,
|
||||||
|
"promotion" => getRequest('promo', ''),
|
||||||
|
"ids" => array_filter(explode(',', str_replace(" ", "", getRequest('ids', '')))),
|
||||||
|
'excluded_ids' => [],
|
||||||
|
"storeId" => getRequest('storeId', ''),
|
||||||
|
"other_filter" => array_filter(explode(",", getRequest('other_filter', ''))), //in-stock, has-promotion etc...
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// build the user filters for product query
|
||||||
|
// we can modify the code to accept various types of url customization
|
||||||
|
public function getUserFilter(array $category_list_id) {
|
||||||
|
return [
|
||||||
|
"price" => self::getPriceFilterRange(),
|
||||||
|
"brand" => $this->getBrandFilter(),
|
||||||
|
"collection" => array_filter(explode(FILTER_VALUE_SEPARATOR, getRequest('collection'))),
|
||||||
|
"supplier" => array_filter(explode(FILTER_VALUE_SEPARATOR, getRequest('supplier'))),
|
||||||
|
"rating" => array_filter(explode(FILTER_VALUE_SEPARATOR, getRequest('rating'))),
|
||||||
|
"category" => $category_list_id,
|
||||||
|
//"status" => "1",
|
||||||
|
"query" => getRequest('q', ''),
|
||||||
|
"hotType" => getRequest('hotType'),//saleoff | not | new
|
||||||
|
"attribute" => $this->getAttributeFilter( $category_list_id),//,3793,3794,
|
||||||
|
"promotion" => getRequest('promo', ''),
|
||||||
|
"ids" => array_filter(explode(',', str_replace(" ", "", getRequest('ids', '')))),
|
||||||
|
"excluded_ids" => array_filter(explode(',', str_replace(" ", "", getRequest('excluded_ids', '')))),
|
||||||
|
"storeId" => getRequest('storeId', ''),
|
||||||
|
"other_filter" => array_filter(explode(",", getRequest('other_filter', ''))), //in-stock, has-promotion etc...
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// allow to update filters
|
||||||
|
public function setFilters(array $new_filters) {
|
||||||
|
foreach ($new_filters as $key => $value) {
|
||||||
|
if(isset($this->filters[$key])) {
|
||||||
|
$this->filters[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// accept url format:
|
||||||
|
// default: ?min=1000&max=20000
|
||||||
|
// custom: ?p=15trieu-20-trieu
|
||||||
|
public static function getPriceFilterRange(){
|
||||||
|
// default format
|
||||||
|
if(!defined('PRICE_FILTER_FORMAT') || PRICE_FILTER_FORMAT != 'p') {
|
||||||
|
return array(
|
||||||
|
"min" => getRequestInt("min", 0),
|
||||||
|
"max" => getRequestInt("max", 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// custom price range query
|
||||||
|
$price_range_format = getRequest("p"); // duoi-10trieu , 10ngan-2trieu, 3trieu-6trieu, tren-30trieu
|
||||||
|
if(strpos($price_range_format, '-') === false) {
|
||||||
|
return array(
|
||||||
|
"min" => 0,
|
||||||
|
"max" => 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$unit_translation = [
|
||||||
|
'ngan' => 1000,
|
||||||
|
'trieu' => 1000000,
|
||||||
|
'ty' => 1000000000,
|
||||||
|
];
|
||||||
|
|
||||||
|
// duoi-10trieu, duoi-10ngan
|
||||||
|
if(strpos($price_range_format, 'duoi-') !== false) {
|
||||||
|
$unit_match = self::findPriceUnitMatch(str_replace("duoi-", "", $price_range_format));
|
||||||
|
return array(
|
||||||
|
"min" => 0,
|
||||||
|
"max" => $unit_match['number'] * $unit_translation[$unit_match['unit']],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// tren-10trieu, tren-10ngan
|
||||||
|
if(strpos($price_range_format, 'tren-') !== false) {
|
||||||
|
$unit_match = self::findPriceUnitMatch(str_replace("tren-", "", $price_range_format));
|
||||||
|
return array(
|
||||||
|
"min" => $unit_match['number'] * $unit_translation[$unit_match['unit']],
|
||||||
|
"max" => 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10ngan-2trieu, 3trieu-6trieu
|
||||||
|
$parts = explode('-', $price_range_format);
|
||||||
|
$min_part = $parts[0];
|
||||||
|
$max_part = $parts[1];
|
||||||
|
$min_match = self::findPriceUnitMatch($min_part);
|
||||||
|
$max_match = self::findPriceUnitMatch($max_part);
|
||||||
|
|
||||||
|
return [
|
||||||
|
"min" => $min_match['number'] * $unit_translation[$min_match['unit']],
|
||||||
|
"max" => $max_match['number'] * $unit_translation[$max_match['unit']],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//get a list of products that match filtering conditions
|
||||||
|
public function getProductList(
|
||||||
|
$start_url,
|
||||||
|
$sort_by = "new",
|
||||||
|
$limit = 20,
|
||||||
|
$page = 1
|
||||||
|
) {
|
||||||
|
//list of available filters
|
||||||
|
/*
|
||||||
|
* - price range
|
||||||
|
* - brand id or ids
|
||||||
|
* - collection id or ids
|
||||||
|
* - supplier id
|
||||||
|
* - rating: 1-> 5
|
||||||
|
* - category id or ids
|
||||||
|
* - status: 0|1|null
|
||||||
|
* - detail_page_only: 0 | 1
|
||||||
|
* - query: search keyword
|
||||||
|
* - hotType: saleoff | not | new | or combination of types
|
||||||
|
* - attribute values
|
||||||
|
* ...
|
||||||
|
* */
|
||||||
|
|
||||||
|
/*$filters = array(
|
||||||
|
"price" => array("min"=> 1, "max" => 100),
|
||||||
|
"brand" => array(12,3),
|
||||||
|
"collection" => array(2,3),
|
||||||
|
"supplier" => array(2,3),
|
||||||
|
"rating" => array(2,3),
|
||||||
|
"category" => array(1,2,),
|
||||||
|
"status" => "1",
|
||||||
|
"detail_page_only" => 0,
|
||||||
|
"query" => "",
|
||||||
|
"hotType" => "",//saleoff | not | new
|
||||||
|
"attribute" => "",//,3793,3794,
|
||||||
|
"ids" => array(12,3),
|
||||||
|
'excluded_ids' => array(12,3),
|
||||||
|
"other_filter" => array(in-stock, has-promotion)
|
||||||
|
//...
|
||||||
|
);*/
|
||||||
|
|
||||||
|
$filterPath = [];
|
||||||
|
$filter_messages = [];
|
||||||
|
$where_query = [];
|
||||||
|
$paging_url = $start_url;
|
||||||
|
$filters = $this->cleanFilter();
|
||||||
|
// debug_var($filters);
|
||||||
|
|
||||||
|
//system controls
|
||||||
|
if(ENABLE_PRODUCT_EXPIRE) {
|
||||||
|
//$where_query[] = " AND (from_time =0 OR from_time < '".CURRENT_TIME."') AND (to_time > '".CURRENT_TIME."' OR to_time=0 ) ";
|
||||||
|
}
|
||||||
|
|
||||||
|
//other filters
|
||||||
|
if(isset($filters["other_filter"]) && sizeof($filters["other_filter"])) {
|
||||||
|
foreach ($filters["other_filter"] as $_filter) {
|
||||||
|
switch ($_filter) {
|
||||||
|
case "in-stock";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Còn hàng',
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND `quantity` > 0 ";
|
||||||
|
break;
|
||||||
|
case "has-vat";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Có thuế VAT',
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND has_vat = 1 ";
|
||||||
|
break;
|
||||||
|
case "out-stock";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Hết hàng',
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND quantity = 0 ";
|
||||||
|
break;
|
||||||
|
case "has-market-price";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Có giá thị trường',
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND market_price > 0 ";
|
||||||
|
break;
|
||||||
|
case "no-price";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Không có giá',
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND price = 0 ";
|
||||||
|
break;
|
||||||
|
case "no-warranty";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Không có bảo hành',
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND LENGTH(`warranty`) < 2 ";
|
||||||
|
break;
|
||||||
|
case "no-sku";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Không mã kho hàng',
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND LENGTH(`sku`) < 2 ";
|
||||||
|
break;
|
||||||
|
case "has-sku";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Có mã kho hàng',
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND LENGTH(`sku`) > 2 ";
|
||||||
|
break;
|
||||||
|
case "has-config";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Không có cấu hình',
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND `config_count` > 0 ";
|
||||||
|
break;
|
||||||
|
case "no-image";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Không có ảnh',
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND `image_count` = 0 ";
|
||||||
|
break;
|
||||||
|
case "no-category";
|
||||||
|
$where_query[] = " AND `category_ids` = '' ";
|
||||||
|
break;
|
||||||
|
case "no-brand";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Không có thương hiệu',
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['brand' => '']),
|
||||||
|
];
|
||||||
|
|
||||||
|
$where_query[] = " AND `brand_id` = 0 ";
|
||||||
|
break;
|
||||||
|
case "display-off";
|
||||||
|
$where_query[] = " AND `status`=0 ";
|
||||||
|
break;
|
||||||
|
case "display-on";
|
||||||
|
$where_query[] = " AND `status`=1 ";
|
||||||
|
break;
|
||||||
|
case "has-promotion":
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Có khuyến mại',
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
|
||||||
|
$where_query[] = " AND LENGTH(`special_offer`) > 5 ";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "no-description";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Không có mô tả',
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
|
||||||
|
$where_query["no-description"] = " AND `id` IN ( SELECT `id` FROM ".TableName::PRODUCT_INFO." WHERE LENGTH(`description`) < 5 ) ";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "no-spec-text";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Không có thông số',
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
|
||||||
|
$where_query["no-spec-text"] = " AND `id` IN ( SELECT `id` FROM ".TableName::PRODUCT_INFO." WHERE LENGTH(`spec`) < 5 ) ";
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//- brand id or ids or brand_indexes
|
||||||
|
if(isset($filters["brand"]) && sizeof($filters["brand"])) {
|
||||||
|
global $admin_panel;
|
||||||
|
|
||||||
|
$brand_url_format = (defined('BRAND_FILTER_FORMAT')) ? BRAND_FILTER_FORMAT : '';
|
||||||
|
if(isset($admin_panel) && $admin_panel) $brand_url_format = ''; // custom brand format cannot be used in admin panel
|
||||||
|
|
||||||
|
$objBrandModel = new BrandModel();
|
||||||
|
$condition = array();
|
||||||
|
foreach ($filters["brand"] as $_id) {
|
||||||
|
if(!$_id) continue;
|
||||||
|
|
||||||
|
$brand_info = $objBrandModel->getInfo($_id);
|
||||||
|
if(!$brand_info) continue;
|
||||||
|
|
||||||
|
$filterPath["brand"][] = array(
|
||||||
|
"id" => $brand_info['id'],
|
||||||
|
"name" => $brand_info["title"],
|
||||||
|
);
|
||||||
|
|
||||||
|
$condition[] = " `brand_id` = '".intval($brand_info['id'])."' ";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $brand_info['title'],
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['brand' => join(FILTER_VALUE_SEPARATOR, remove_item_from_array($filters["brand"], $_id))]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sizeof($condition)) {
|
||||||
|
$paging_url = Url::buildUrl($paging_url, array("brand" => join(FILTER_VALUE_SEPARATOR, $filters['brand'])));
|
||||||
|
$where_query[] = " AND ( ".join(" OR ", $condition)." )";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//- collection id or ids
|
||||||
|
if(isset($filters["collection"]) && sizeof($filters["collection"])) {
|
||||||
|
$condition = array();
|
||||||
|
foreach ($filters["collection"] as $_id) {
|
||||||
|
$filterPath["collection"][] = array(
|
||||||
|
"id" => $_id,
|
||||||
|
//"name" => $brand_info["name"],
|
||||||
|
);
|
||||||
|
$condition[] = " `id` IN ( SELECT product_id FROM ".TB_CATEGORY_SPECIAL_PRODUCT." WHERE `special_cat_id`='".intval($_id)."' ) ";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Bộ sưu tập ',
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['collection' => join(',', remove_item_from_array($filters["collection"], $_id))]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$paging_url = Url::buildUrl($paging_url, array("collection" => join(",", $filters['collection'])));
|
||||||
|
$where_query[] = " AND ( ".join(" OR ", $condition)." )";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//- category id or ids
|
||||||
|
if(isset($filters["category"]) && sizeof($filters["category"])) {
|
||||||
|
|
||||||
|
$objCategoryProductModel = new ProductCategoryModel();
|
||||||
|
|
||||||
|
$condition = array();
|
||||||
|
|
||||||
|
foreach ($filters["category"] as $cat_id) {
|
||||||
|
$cat_id = intval($cat_id);
|
||||||
|
|
||||||
|
if(!$cat_id) continue;
|
||||||
|
|
||||||
|
$cat_info = $objCategoryProductModel->getInfo($cat_id);
|
||||||
|
|
||||||
|
if($cat_info["is_parent"]) {
|
||||||
|
$childListId = ($cat_info["child_ids"]) ? $cat_info["child_ids"] : '0';
|
||||||
|
$condition[] = " `category_id` IN (".$childListId .") ";
|
||||||
|
}else{
|
||||||
|
$condition[] = " `category_id` = '".$cat_id."' ";
|
||||||
|
}
|
||||||
|
|
||||||
|
$filterPath["category"][] = array(
|
||||||
|
"id" => $cat_id,
|
||||||
|
"name" => $cat_info['title'],
|
||||||
|
);
|
||||||
|
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $cat_info['title'],
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['category' => join(',', remove_item_from_array($filters["category"], $cat_id))]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sizeof($condition)) {
|
||||||
|
$paging_url = Url::buildUrl($paging_url, array("category" => join(",", $filters['category'])));
|
||||||
|
$where_query[] = " AND `id` IN ( SELECT DISTINCT `item_id` FROM ".TableName::PRODUCT_PER_CATEGORY." WHERE " . join(" OR ", $condition) . " )";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//- status: 0|1|null
|
||||||
|
if(isset($filters["status"]) && $filters['status']) {
|
||||||
|
$where_query[] = " AND `status` = 1 ";
|
||||||
|
/*$filter_messages[] = [
|
||||||
|
'title' => 'Trạng thái: '.($filters['status'] ? 'Đang hiển thị' : 'Đang ẩn'),
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['status' => '']),
|
||||||
|
];*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//- query: search keyword
|
||||||
|
if(isset($filters["query"]) && $filters["query"]) {
|
||||||
|
$keyword_search = $filters["query"];
|
||||||
|
$search_by_product_id = intval(preg_replace('/[^0-9]/i', '', $keyword_search));
|
||||||
|
|
||||||
|
$objProductSearchModel = new ProductSearchModel();
|
||||||
|
|
||||||
|
$match_result = $objProductSearchModel->find($keyword_search);
|
||||||
|
|
||||||
|
$filterPath["search"] = array(
|
||||||
|
"id" => $filters["query"],
|
||||||
|
"name" => $filters["query"],
|
||||||
|
);
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $filters["query"],
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['q' => '']),
|
||||||
|
];
|
||||||
|
|
||||||
|
$paging_url = Url::buildUrl($paging_url, array("q" => $filters["query"]));
|
||||||
|
|
||||||
|
if(sizeof($match_result) > 0) {
|
||||||
|
$where_query[] = " AND ( `id` IN (".join(",", $match_result ).") OR `id` = '".$search_by_product_id."' ) ";
|
||||||
|
}else{
|
||||||
|
$where_query[] = " AND `id` = '".$search_by_product_id."' ";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//- hotType: saleoff | not | new | or combination of types
|
||||||
|
if(isset($filters["hotType"]) && $filters["hotType"]) {
|
||||||
|
$hot_type = preg_replace("/[^a-z0-9_\-]/i","", $filters["hotType"]);
|
||||||
|
$config_hottype = AProductFilterController::getProductHotTypeList();
|
||||||
|
if(isset($config_hottype[$hot_type])) {
|
||||||
|
$filterPath["hotType"] = array(
|
||||||
|
"id" => $filters["hotType"],
|
||||||
|
"name" => $filters["hotType"],
|
||||||
|
);
|
||||||
|
$paging_url = Url::buildUrl($paging_url, array("hotType" => $hot_type));
|
||||||
|
$where_query[] = " AND `id` IN (SELECT `pro_id` FROM ".TB_PRODUCT_HOT." WHERE hot_type = '".$hot_type."' ) ";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $filters["hotType"],
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['hotType' => '']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//- attribute values
|
||||||
|
if(isset($filters["attribute"]) && sizeof($filters["attribute"])) {
|
||||||
|
$filter_attr_value_list = $filters["attribute"];
|
||||||
|
//filter = attr_value_1-attr_value_2-attr_value_3,
|
||||||
|
$query_attr_id = [];
|
||||||
|
$count_filter = 0;
|
||||||
|
|
||||||
|
if(ENABLE_FILTER_BY_APIKEY) {
|
||||||
|
//filter = ,api_key_1,api_key_2,api_key_3,
|
||||||
|
foreach($this->translate_api_filter($filter_attr_value_list) as $attr_id){
|
||||||
|
$query_attr_id[] = $attr_id;
|
||||||
|
$count_filter ++;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
foreach($filter_attr_value_list as $attr_id){
|
||||||
|
$attr_id = (int) $attr_id;
|
||||||
|
if($attr_id) {
|
||||||
|
$query_attr_id[] = $attr_id;
|
||||||
|
$count_filter ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$objProductAttributeModel = new ProductAttributeModel();
|
||||||
|
|
||||||
|
$product_filter_id_match = $objProductAttributeModel->getProductMatchAttributeValue($query_attr_id);
|
||||||
|
|
||||||
|
|
||||||
|
$paging_url = Url::buildUrl($paging_url, array("filter" => join(FILTER_VALUE_SEPARATOR, $filter_attr_value_list)));
|
||||||
|
$where_query[] = (sizeof($product_filter_id_match)) ? " AND `id` IN (".join(', ', $product_filter_id_match).") " : " AND `id` = 0 " ;
|
||||||
|
|
||||||
|
//xay lai url de back
|
||||||
|
foreach($filter_attr_value_list as $value_id ){
|
||||||
|
$att_name = 'att_name'; //$objCategoryProduct->atrValueName($value_id);
|
||||||
|
$filterPath["attribute"][] = array(
|
||||||
|
"id" => $value_id,
|
||||||
|
"name" => $att_name,
|
||||||
|
);
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $att_name,
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['attribute' => join(FILTER_VALUE_SEPARATOR, remove_item_from_array($filters["category"], $value_id))]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//given products' ids
|
||||||
|
if(isset($filters["ids"]) && is_array($filters["ids"]) && sizeof($filters["ids"])) {
|
||||||
|
$where_query[] = " AND `id` IN (". join(",", $filters["ids"]) .") ";
|
||||||
|
}
|
||||||
|
|
||||||
|
//exclude products' ids
|
||||||
|
if(isset($filters["excluded_ids"]) && is_array($filters["excluded_ids"]) && sizeof($filters["excluded_ids"])) {
|
||||||
|
$where_query[] = " AND `id` NOT IN (". join(",", $filters["excluded_ids"]) .") ";
|
||||||
|
}
|
||||||
|
|
||||||
|
$price_range_query_limit = join(" ",$where_query);
|
||||||
|
|
||||||
|
//- price range
|
||||||
|
if(isset($filters["price"]) && is_array($filters["price"]) && sizeof($filters["price"]) && ($filters["price"]['min'] > 0 || $filters["price"]['max'] > 0)) {
|
||||||
|
//limit by price range
|
||||||
|
$maxPrice = clean_price($filters["price"]['max']);
|
||||||
|
$minPrice = clean_price($filters["price"]['min']);
|
||||||
|
$paging_url = Url::buildUrl($paging_url, array("max"=>$maxPrice, "min"=>$minPrice));
|
||||||
|
|
||||||
|
$price_range_query = '';
|
||||||
|
if($maxPrice > 0 && $minPrice > 0){
|
||||||
|
$price_range_query = " ( `price` BETWEEN '".$minPrice."' AND '".$maxPrice."' ) ";
|
||||||
|
|
||||||
|
}else if($maxPrice > 0){
|
||||||
|
$price_range_query = " `price` < '".$maxPrice."' ";
|
||||||
|
|
||||||
|
}else if($minPrice > 0){
|
||||||
|
$price_range_query = " `price` >='".$minPrice."' ";
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_query[] = " AND ". $price_range_query;
|
||||||
|
|
||||||
|
$filterPath["price"] = array(
|
||||||
|
"min" => $minPrice,
|
||||||
|
"max" => $maxPrice,
|
||||||
|
);
|
||||||
|
|
||||||
|
$price_format = ProductFilterPrice::buildPriceRangeFormat($minPrice, $maxPrice);
|
||||||
|
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $price_format['title'],
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['price' => '']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// use location price sorting
|
||||||
|
$ordering_clause = $this->getOrderingClause($sort_by, $db_condition, 0);
|
||||||
|
|
||||||
|
$db_condition = join(" ", $where_query);
|
||||||
|
|
||||||
|
$total_number = 0;
|
||||||
|
$list_ids = [];
|
||||||
|
|
||||||
|
/* $query = $this->db->runQuery("SELECT COUNT(`id`) AS total FROM ".TB_PRODUCT_LIGHT." WHERE 1 ".$db_condition." ");
|
||||||
|
if($rs = $this->db->fetchAssoc($query)) {
|
||||||
|
$total_number = $rs['total'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$category_query = " AND idv_product_category.`pro_id` IN (SELECT `id` FROM ".TB_PRODUCT_LIGHT." WHERE 1 ".$db_condition.") ";
|
||||||
|
|
||||||
|
if($location_sorting) {
|
||||||
|
|
||||||
|
$list_ids = array_slice($location_product_ids, ($page-1) * $limit, $limit);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if(in_array($sort_by, ['view', 'name'])) {
|
||||||
|
$query = $this->db->runQuery("
|
||||||
|
SELECT `id`, ".TB_PRODUCT.".proName
|
||||||
|
FROM ".TB_PRODUCT_LIGHT.", ".TB_PRODUCT."
|
||||||
|
WHERE `id` = ".TB_PRODUCT.".`id`
|
||||||
|
".$db_condition."
|
||||||
|
".$ordering_clause."
|
||||||
|
LIMIT ".($page-1) * $limit.", ".$limit."
|
||||||
|
");
|
||||||
|
}else{
|
||||||
|
$query = $this->db->runQuery("
|
||||||
|
SELECT `id` FROM ".TB_PRODUCT_LIGHT."
|
||||||
|
WHERE 1 ".$db_condition."
|
||||||
|
".$ordering_clause."
|
||||||
|
LIMIT ".($page-1) * $limit.", ".$limit."
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
$list_ids = array_map(function ($rs){
|
||||||
|
return $rs['id'];
|
||||||
|
}, $this->db->fetchAll($query));
|
||||||
|
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
return [
|
||||||
|
"full_query" => $db_condition,
|
||||||
|
"price_query" => $price_range_query_limit,
|
||||||
|
//"category_query" => $category_query,
|
||||||
|
"filter" => $filterPath,
|
||||||
|
"filter_messages" => $filter_messages,
|
||||||
|
"total" => $total_number,
|
||||||
|
"list_ids" => $list_ids,
|
||||||
|
"url" => $paging_url,
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function cleanFilter() {
|
||||||
|
$clean_filter = [];
|
||||||
|
foreach ($this->filters as $key => $value) {
|
||||||
|
$clean_filter[$key] = $this->clearFilterValue($key, $value);
|
||||||
|
}
|
||||||
|
return $clean_filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function clearFilterValue($key, $value) {
|
||||||
|
if($key == 'price') {
|
||||||
|
if(!is_array($value)) return false;
|
||||||
|
$min = isset($value['min']) ? intval($value['min']) : 0;
|
||||||
|
$max = isset($value['max']) ? intval($value['max']) : 0;
|
||||||
|
|
||||||
|
return ['min' => $min, 'max' => $max]; // array("min" => getRequestInt("min", 0), "max" => getRequestInt("max", 0)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if(in_array($key, ['collection', 'supplier', 'rating', 'category', 'ids', 'excluded_ids'])) {
|
||||||
|
return Security::makeListOfInputSafe($value, 'int'); // int1-int2 => array(int1, int2, ...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// accept a-z0-9-
|
||||||
|
if(in_array($key, ['attribute', 'brand'])) {
|
||||||
|
return Security::makeListOfInputSafe($value, 'string');
|
||||||
|
}
|
||||||
|
|
||||||
|
if($key == 'status') {
|
||||||
|
return (in_array($value, [0, 1])) ? $value : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($key == 'query') {
|
||||||
|
return substr(trim(str_replace(['\'', '"'], '', strip_tags($value))), 0, 100); // string
|
||||||
|
}
|
||||||
|
|
||||||
|
if($key == 'hotType') {
|
||||||
|
$config_hottype = AProductFilterController::getProductHotTypeList();
|
||||||
|
return (isset($config_hottype[$value])) ? $value : ''; //saleoff | not | new
|
||||||
|
}
|
||||||
|
|
||||||
|
if($key == 'promotion') {
|
||||||
|
return intval($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($key == 'storeId') {
|
||||||
|
return intval($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($key == 'detail_page_only') {
|
||||||
|
return intval($value); // 1|0|''
|
||||||
|
}
|
||||||
|
|
||||||
|
if($key == 'other_filter') {
|
||||||
|
return array_filter(
|
||||||
|
array_map(function ($item){
|
||||||
|
return preg_replace('/[^a-z0-9_\.\-\,]/i', '', $item);
|
||||||
|
}, $value )
|
||||||
|
); // array() //in-stock, has-promotion
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function filterLocationProductByPrice(array $product_list, $max_price, $min_price) {
|
||||||
|
if($max_price > 0 && $min_price > 0){
|
||||||
|
return array_filter($product_list, function ($pro) use ($max_price, $min_price) {
|
||||||
|
return ($pro['price'] >= $min_price && $pro['price'] < $max_price);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if($max_price > 0){
|
||||||
|
return array_filter($product_list, function ($pro) use ($max_price, $min_price) {
|
||||||
|
return ($pro['price'] < $max_price);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if($min_price > 0){
|
||||||
|
return array_filter($product_list, function ($pro) use ($max_price, $min_price) {
|
||||||
|
return ($pro['price'] >= $min_price);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $product_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getProductIds($price_range_query_limit) {
|
||||||
|
/*$query = $this->db->query("
|
||||||
|
SELECT `id` FROM ".TB_PRODUCT_LIGHT."
|
||||||
|
WHERE 1 ".$price_range_query_limit."
|
||||||
|
LIMIT 10000
|
||||||
|
");
|
||||||
|
|
||||||
|
return array_map(function ($rs){
|
||||||
|
return $rs['id'];
|
||||||
|
}, $this->db->fetchAll($query));*/
|
||||||
|
}
|
||||||
|
|
||||||
|
//10-04-2017 allow ?filter=api_key1,api_key2, ... and translate to id
|
||||||
|
protected function translate_api_filter($filter_api) {
|
||||||
|
/*$filter_attr_value_list = array_filter(explode(",", $filter_api));
|
||||||
|
|
||||||
|
$build_query = array();
|
||||||
|
foreach($filter_attr_value_list as $api_key){
|
||||||
|
$api_key = \preg_replace('/[^a-z0-9\_\-\.]/i', '', $api_key);
|
||||||
|
if($api_key) $build_query[] = " `api_key` = '".$this->db->escape($api_key)."' ";
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = array();
|
||||||
|
if(sizeof($build_query)) {
|
||||||
|
$query = $this->db->runQuery("SELECT id FROM ".TB_ATTRIBUTE_VALUE." WHERE ". join(" OR ", $build_query)) ;
|
||||||
|
foreach ( $this->db->fetchAll($query) as $info ) {
|
||||||
|
$result[] = $info['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;*/
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// accept url format:
|
||||||
|
// default: ?brand=1
|
||||||
|
// brand_index: ?brand=apple
|
||||||
|
protected function getBrandFilter() {
|
||||||
|
$brand_url_format = (defined('BRAND_FILTER_FORMAT')) ? BRAND_FILTER_FORMAT : '';
|
||||||
|
|
||||||
|
$module = (Registry::getVariable('global')) ? Registry::getVariable('global')['module'] : null;
|
||||||
|
|
||||||
|
if(!$module) {
|
||||||
|
// possible admin panel
|
||||||
|
return array_filter(explode(FILTER_VALUE_SEPARATOR, getRequest('brand', '')));
|
||||||
|
}
|
||||||
|
|
||||||
|
$current_brand_queries = (isset($module['query']['brand'])) ? array_filter(explode(FILTER_VALUE_SEPARATOR, $module['query']['brand'])) : [];
|
||||||
|
|
||||||
|
// fix for brand-detail page
|
||||||
|
// example: domain.com/brand/sony
|
||||||
|
if(isset($module['query']['brandName']) && $module['query']['brandName']) {
|
||||||
|
$objBrandModel = new BrandModel();
|
||||||
|
$brand_info = $objBrandModel->getInfoByUrl($module['query']['brandName']);
|
||||||
|
|
||||||
|
if($brand_url_format == 'brand_index') {
|
||||||
|
$current_brand_queries = array($brand_info['brand_index']);
|
||||||
|
}else{
|
||||||
|
$current_brand_queries = array($brand_info['id']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($brand_url_format == 'brand_index') {
|
||||||
|
//i.e ?brand=apple-canon
|
||||||
|
return $current_brand_queries;
|
||||||
|
}
|
||||||
|
|
||||||
|
$selected_brand = $current_brand_queries;
|
||||||
|
if(isset($module['query']['brand_id']) && $module['query']['brand_id']) {
|
||||||
|
$selected_brand[] = intval($module['query']['brand_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $selected_brand;
|
||||||
|
}
|
||||||
|
|
||||||
|
// accept url format:
|
||||||
|
// default: ?filter=3793-3794,
|
||||||
|
// custom: ?ram=8gb&hdd=500GB
|
||||||
|
protected function getAttributeFilter(array $category_list_id){
|
||||||
|
|
||||||
|
$request_filter_list = getRequest('filter', '');
|
||||||
|
|
||||||
|
// custom1
|
||||||
|
if( !$request_filter_list && defined('ATTRIBUTE_FILTER_FORMAT') && ATTRIBUTE_FILTER_FORMAT == 'custom1' ) {
|
||||||
|
// iUCategoryProduct $objCategoryProduct, array $category_list_id
|
||||||
|
return $this->getAttributeFilterPara($category_list_id); //join(FILTER_VALUE_SEPARATOR, $this->getAttributeFilterPara($category_list_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$request_filter_list) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// default
|
||||||
|
$clean_list = Security::makeListOfInputSafe(
|
||||||
|
explode(FILTER_VALUE_SEPARATOR, str_replace([',', '-', '_'], FILTER_VALUE_SEPARATOR, $request_filter_list) ),
|
||||||
|
DataType::INTEGER
|
||||||
|
) ; //preg_replace("/[^0-9,_\-]/", "", getRequest('filter', ''));//3793-3794,
|
||||||
|
|
||||||
|
return array_filter($clean_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function getAttributeFilterPara( array $category_list_id){
|
||||||
|
|
||||||
|
$excluded_keys = ['p', 'min', 'max', 'brand', 'page', 'request_path'];
|
||||||
|
$attr_filter_params = [];
|
||||||
|
|
||||||
|
$query_parameters = Url::parse(CURRENT_URL)['query'];
|
||||||
|
|
||||||
|
|
||||||
|
foreach ( $query_parameters as $key => $value) {
|
||||||
|
if(in_array($key, $excluded_keys) || !$value) continue;
|
||||||
|
|
||||||
|
$attr_filter_params[$key] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(sizeof($attr_filter_params)) {
|
||||||
|
/*$objCategoryProduct = new UCategoryProduct();
|
||||||
|
$category_attributes = $objCategoryProduct->getAttributesForCategory($category_list_id);
|
||||||
|
|
||||||
|
$match_attr_value_ids = [];
|
||||||
|
foreach ($attr_filter_params as $attr_filter_code => $attr_value_api_key) {
|
||||||
|
foreach ($category_attributes as $attribute) {
|
||||||
|
if($attribute['filter_code'] != $attr_filter_code) continue;
|
||||||
|
|
||||||
|
foreach ($attribute['value_list'] as $_value_id => $_value_info) {
|
||||||
|
if($_value_info['info']['api_key'] == $attr_value_api_key) {
|
||||||
|
$match_attr_value_ids[] = $_value_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $match_attr_value_ids;*/
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function findPriceUnitMatch($str){
|
||||||
|
$match = [];
|
||||||
|
$pattern = "/^([\d]+)(ngan|trieu|ty)/i";
|
||||||
|
if(preg_match($pattern, $str, $match)){
|
||||||
|
return [
|
||||||
|
"number" => $match[1],
|
||||||
|
"unit" => $match[2],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
"number" => 0,
|
||||||
|
"unit" => 'trieu',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// accept url format:
|
||||||
|
// default: ?min=1000&max=20000
|
||||||
|
// custom: ?p=tu-15-20-trieu
|
||||||
|
protected static function getPriceRange(){
|
||||||
|
// default
|
||||||
|
$min_price = getRequest("min");
|
||||||
|
$max_price = getRequest("max");
|
||||||
|
|
||||||
|
// custom price range query
|
||||||
|
$price_range = getRequest("p"); // duoi-10-trieu , tu-15-20-trieu, tren-30-trieu
|
||||||
|
|
||||||
|
// duoi-10-trieu
|
||||||
|
$match = [];
|
||||||
|
if(preg_match("/^duoi-([0-9]+)-trieu$/i", $price_range, $match)) {
|
||||||
|
$min_price = '';
|
||||||
|
$max_price = $match[1] * 1000000;
|
||||||
|
}
|
||||||
|
elseif (preg_match("/^tu-([0-9]+)-([0-9]+)-trieu$/i", $price_range, $match)) {
|
||||||
|
$min_price = $match[1] * 1000000;
|
||||||
|
$max_price = $match[2] * 1000000;
|
||||||
|
}
|
||||||
|
elseif (preg_match("/^tren-([0-9]+)-trieu$/i", $price_range, $match)) {
|
||||||
|
$min_price = $match[1] * 1000000;
|
||||||
|
$max_price = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"min" => intval($min_price),
|
||||||
|
"max" => intval($max_price),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function getOrderingClause($sort_by, &$where_query, $current_viewed_category = 0) {
|
||||||
|
|
||||||
|
// show by global order
|
||||||
|
$ordering = "ORDER BY ordering DESC, id DESC";
|
||||||
|
|
||||||
|
switch($sort_by) {
|
||||||
|
case "order-new";
|
||||||
|
$ordering = "ORDER BY `ordering` DESC, `id` DESC";
|
||||||
|
break;
|
||||||
|
case "order-last-update";
|
||||||
|
$ordering = "ORDER BY `ordering` DESC, last_update DESC";
|
||||||
|
break;
|
||||||
|
case "last-update";
|
||||||
|
$ordering = "ORDER BY last_update DESC";
|
||||||
|
break;
|
||||||
|
case "order";
|
||||||
|
$ordering = "ORDER BY `ordering` DESC";
|
||||||
|
break;
|
||||||
|
case "new";
|
||||||
|
$ordering = "ORDER BY `id` DESC";
|
||||||
|
break;
|
||||||
|
case "price-asc";
|
||||||
|
$where_query .= " AND `price` > 100 ";
|
||||||
|
$ordering = "ORDER BY `price` ASC";
|
||||||
|
break;
|
||||||
|
case "price-desc";
|
||||||
|
$ordering = " ORDER BY `price` DESC ";
|
||||||
|
break;
|
||||||
|
case "view";
|
||||||
|
$ordering = "ORDER BY `visit` desc";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $ordering;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\AdminController;
|
||||||
|
|
||||||
|
use Hura8\Components\Product\Model\ProductHotModel;
|
||||||
|
use Hura8\Traits\ClassCacheTrait;
|
||||||
|
|
||||||
|
class AProductHotController
|
||||||
|
{
|
||||||
|
use ClassCacheTrait;
|
||||||
|
|
||||||
|
/* @var ProductHotModel $objProductHotModel */
|
||||||
|
protected $objProductHotModel;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->objProductHotModel = new ProductHotModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductHot(array $product_id_array) {
|
||||||
|
$hot_script = $this->objProductHotModel->getProductHot($product_id_array);
|
||||||
|
|
||||||
|
//add empty for other products
|
||||||
|
foreach ($product_id_array as $pro_id) {
|
||||||
|
if(!isset($hot_script[$pro_id])) $hot_script[$pro_id] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $hot_script;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function updateProductHot($pro_id, array $new_types) {
|
||||||
|
return $this->objProductHotModel->updateProductHot($pro_id, $new_types);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\AdminController;
|
||||||
|
|
||||||
|
use Hura8\Components\Product\Model\ProductSpecGroupAttributeModel;
|
||||||
|
use Hura8\System\Controller\aAdminEntityBaseController;
|
||||||
|
|
||||||
|
class AProductSpecGroupAttributeController extends aAdminEntityBaseController
|
||||||
|
{
|
||||||
|
/* @var ProductSpecGroupAttributeModel $objProductSpecGroupAttributeModel */
|
||||||
|
protected $objProductSpecGroupAttributeModel;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->objProductSpecGroupAttributeModel = new ProductSpecGroupAttributeModel();
|
||||||
|
parent::__construct($this->objProductSpecGroupAttributeModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function deleteFileBeforeDeleteItem($item_id): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\AdminController;
|
||||||
|
|
||||||
|
use Hura8\Components\Product\Model\ProductAttributeModel;
|
||||||
|
use Hura8\Components\Product\Model\ProductSpecGroupModel;
|
||||||
|
use Hura8\System\Controller\aAdminEntityBaseController;
|
||||||
|
|
||||||
|
class AProductSpecGroupController extends aAdminEntityBaseController
|
||||||
|
{
|
||||||
|
/* @var ProductSpecGroupModel $objProductSpecGroupModel */
|
||||||
|
protected $objProductSpecGroupModel;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->objProductSpecGroupModel = new ProductSpecGroupModel();
|
||||||
|
parent::__construct($this->objProductSpecGroupModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSpecGroupAttributeWithValues($group_id)
|
||||||
|
{
|
||||||
|
$objProductAttributeModel = new ProductAttributeModel();
|
||||||
|
return $objProductAttributeModel->getSpecGroupAttributeWithValues($group_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSpecGroupAttribute($group_id)
|
||||||
|
{
|
||||||
|
$objProductAttributeModel = new ProductAttributeModel();
|
||||||
|
return $objProductAttributeModel->getSpecGroupAttribute($group_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function clearProductSpecGroup($product_id)
|
||||||
|
{
|
||||||
|
return $this->objProductSpecGroupModel->clearProductSpecGroup($product_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function setProductSpecGroup($product_id, $group_id)
|
||||||
|
{
|
||||||
|
return $this->objProductSpecGroupModel->setProductSpecGroup($product_id, $group_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductSpec($product_id)
|
||||||
|
{
|
||||||
|
return $this->objProductSpecGroupModel->getProductSpec($product_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductSpecGroupInfo($product_id)
|
||||||
|
{
|
||||||
|
return $this->objProductSpecGroupModel->getProductSpecGroupInfo($product_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductSpecGroupId($product_id)
|
||||||
|
{
|
||||||
|
return $this->objProductSpecGroupModel->getProductSpecGroupId($product_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getSpecGroupInfo($group_id)
|
||||||
|
{
|
||||||
|
return $this->objProductSpecGroupModel->getSpecGroupInfo($group_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function deleteFileBeforeDeleteItem($item_id): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\AdminController;
|
||||||
|
|
||||||
|
use Hura8\Components\Product\Model\ProductVariantModel;
|
||||||
|
|
||||||
|
class AProductVariantController
|
||||||
|
{
|
||||||
|
/* @var ProductVariantModel $objProductVariantModel */
|
||||||
|
protected $objProductVariantModel;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct($product_id)
|
||||||
|
{
|
||||||
|
$this->objProductVariantModel = new ProductVariantModel($product_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function delete($id) {
|
||||||
|
return $this->objProductVariantModel->delete($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function updateImage($variant_id, $image_name) {
|
||||||
|
return $this->objProductVariantModel->updateFields($variant_id, [
|
||||||
|
"thumbnail" => $image_name
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeImage($variant_id) {
|
||||||
|
return $this->objProductVariantModel->updateFields($variant_id, [
|
||||||
|
"thumbnail" => ''
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getInfo($id) {
|
||||||
|
return $this->objProductVariantModel->getInfo($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProductVariantOption($product_id){
|
||||||
|
return $this->objProductVariantModel->getProductVariantOption($product_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//update product's variant options
|
||||||
|
public function updateVariantOption($attribute) {
|
||||||
|
return $this->objProductVariantModel->updateVariantOption($attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function useVariantOptionSample($select_id) {
|
||||||
|
$sample = $this->objProductVariantModel->useVariantOptionSample($select_id);
|
||||||
|
|
||||||
|
return ($sample) ? \json_decode($sample,true) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//use a product's variant-option to create a choice, so next product can select without recreate from beginning
|
||||||
|
public function createVariantOptionSample($use_from_pro_id, $sample_title) {
|
||||||
|
return $this->objProductVariantModel->createVariantOptionSample($use_from_pro_id, $sample_title);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getVariantOptionSample() {
|
||||||
|
return $this->objProductVariantModel->getVariantOptionSample();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateVariant($variant_id, array $variant_info) {
|
||||||
|
if($variant_id) {
|
||||||
|
return $this->objProductVariantModel->update($variant_id, $variant_info);
|
||||||
|
}else{
|
||||||
|
return $this->objProductVariantModel->create($variant_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProductVariantPriceRange(){
|
||||||
|
return $this->objProductVariantModel->getProductVariantPriceRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProductVariantList(){
|
||||||
|
return $this->objProductVariantModel->getList([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,320 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Controller;
|
||||||
|
|
||||||
|
use Hura8\System\Url;
|
||||||
|
use Hura8\Components\Brand\Model\BrandModel;
|
||||||
|
|
||||||
|
|
||||||
|
class ProductFilterBuilderController {
|
||||||
|
|
||||||
|
protected $_request_url = '';
|
||||||
|
|
||||||
|
protected $_url_elements = [
|
||||||
|
'scheme' => '',
|
||||||
|
'host' => '',
|
||||||
|
'port' => '',
|
||||||
|
'user' => '',
|
||||||
|
'pass' => '',
|
||||||
|
'path' => '',
|
||||||
|
'query' => [],
|
||||||
|
'fragment' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $_price_format = '';
|
||||||
|
protected $_brand_format = '';
|
||||||
|
protected $_filter_format = '';
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct($request_url) {
|
||||||
|
|
||||||
|
if(defined('PRICE_FILTER_FORMAT') && PRICE_FILTER_FORMAT) {
|
||||||
|
$this->_price_format = PRICE_FILTER_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(defined('BRAND_FILTER_FORMAT') && BRAND_FILTER_FORMAT) {
|
||||||
|
$this->_brand_format = BRAND_FILTER_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(defined('ATTRIBUTE_FILTER_FORMAT') && ATTRIBUTE_FILTER_FORMAT ) {
|
||||||
|
$this->_filter_format = ATTRIBUTE_FILTER_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_request_url = $request_url;
|
||||||
|
$this->_url_elements = Url::parse($request_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function buildProductFilterFromUrl(array $existing_filters = []) {
|
||||||
|
$filters = [
|
||||||
|
"price" => $this->getPriceFilterRange(),
|
||||||
|
"brand" => $this->getBrandFilter(), // array(1,2,3,)
|
||||||
|
"collection" => $this->getGenericFilter('collection'), // array(1,2,3,)
|
||||||
|
"supplier" => $this->getGenericFilter('supplier'), // array(1,2,3,)
|
||||||
|
"rating" => $this->getGenericFilter('rating'), // array(1,2,3,)
|
||||||
|
"category" => $this->getGenericFilter('category'), // array(1,2,3,)
|
||||||
|
"status" => array(), // array(1,2,3,)
|
||||||
|
"query" => (isset($this->_url_elements['query']['q'])) ? $this->_url_elements['query']['q'] : '', // string search keyword
|
||||||
|
"hotType" => $this->getGenericFilter('hotType', 'string'),// array(saleoff | not | new)
|
||||||
|
"attribute" => $this->getAttributeFilter(), // array(1,2,3,)
|
||||||
|
"ids" => $this->getGenericFilter('ids'), // array(1,2,3,)
|
||||||
|
"promotion" => "",
|
||||||
|
"storeId" => $this->getGenericFilter('store'), // array(1,2,3,)
|
||||||
|
"other_filter" => $this->getGenericFilter('other_filter', 'string'), // array(in-stock, has-promotion etc...)
|
||||||
|
"spec_group_id" => $this->getGenericFilter('spec_group_id'),
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
// debug_var(array_merge($filters, $existing_filters));
|
||||||
|
if(sizeof($existing_filters)) {
|
||||||
|
foreach ($existing_filters as $key => $values) {
|
||||||
|
if(!isset($filters[$key])) continue;
|
||||||
|
if(!$values) continue;
|
||||||
|
|
||||||
|
if(is_array($values) && is_array($filters[$key])) {
|
||||||
|
$filters[$key] = array_merge($filters[$key], $values);
|
||||||
|
}else{
|
||||||
|
$filters[$key] = $values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filters ;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function getGenericFilter($url_para, $data_type='int') {
|
||||||
|
$value = (isset($this->_url_elements['query'][$url_para])) ? $this->_url_elements['query'][$url_para] : '';
|
||||||
|
|
||||||
|
if($data_type == 'int') {
|
||||||
|
$safe_value = trim(preg_replace("/[^0-9_\-\,]/i", '', $value));
|
||||||
|
}else{
|
||||||
|
$safe_value = trim(preg_replace("/[^a-z0-9_\-\.\,]/i", '', $value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$safe_value) return [];
|
||||||
|
|
||||||
|
$safe_value_list = explode(FILTER_VALUE_SEPARATOR, $safe_value);
|
||||||
|
|
||||||
|
return array_values(array_filter($safe_value_list));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function getBrandFilter()
|
||||||
|
{
|
||||||
|
if($this->_brand_format == 'brand_index'){
|
||||||
|
|
||||||
|
$objBrandModel = new BrandModel();
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
$brand_filters = $this->getGenericFilter('brand', 'string');
|
||||||
|
|
||||||
|
foreach ($brand_filters as $_brand) {
|
||||||
|
if(is_int($_brand)) {
|
||||||
|
$result[] = $_brand;
|
||||||
|
}
|
||||||
|
|
||||||
|
$brand_info = $objBrandModel->getInfoByUrl( $_brand);
|
||||||
|
if(!$brand_info) continue;
|
||||||
|
|
||||||
|
$result[] = $brand_info['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// default
|
||||||
|
return $this->getGenericFilter('brand');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function getAttributeFilter(array $category_list_id = []) {
|
||||||
|
|
||||||
|
if($this->_filter_format == 'filter_code') {
|
||||||
|
|
||||||
|
$excluded_keys = [
|
||||||
|
'q',
|
||||||
|
'p',
|
||||||
|
'min', 'max',
|
||||||
|
'brand',
|
||||||
|
'page',
|
||||||
|
'request_path',
|
||||||
|
'category',
|
||||||
|
'supplier', 'collection',
|
||||||
|
'ids',
|
||||||
|
'filter',' hotType',
|
||||||
|
'other_filter',
|
||||||
|
'rating', 'price',
|
||||||
|
// special ajax key
|
||||||
|
'action','action_type', '_auto'
|
||||||
|
];
|
||||||
|
$attr_filter_params = [];
|
||||||
|
|
||||||
|
foreach ( $this->_url_elements['query'] as $key => $value) {
|
||||||
|
if(in_array($key, $excluded_keys) || !$value) continue;
|
||||||
|
|
||||||
|
$attr_filter_params[$key] = explode(',', urldecode(urldecode($value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sizeof($attr_filter_params)) {
|
||||||
|
|
||||||
|
$category_attributes = $this->getAttributesForCategory($category_list_id);
|
||||||
|
//debug_var($category_attributes);
|
||||||
|
|
||||||
|
$match_attr_value_ids = [];
|
||||||
|
foreach ($attr_filter_params as $attr_filter_code => $attr_value_api_key_list) {
|
||||||
|
|
||||||
|
foreach ($category_attributes as $attribute) {
|
||||||
|
if($attribute['filter_code'] != $attr_filter_code) continue;
|
||||||
|
|
||||||
|
foreach ($attribute['value_list'] as $_index => $_value_info) {
|
||||||
|
|
||||||
|
foreach ($attr_value_api_key_list as $attr_value_api_key) {
|
||||||
|
if($_value_info['api_key'] == $attr_value_api_key) {
|
||||||
|
$match_attr_value_ids[] = $_value_info['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $match_attr_value_ids;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default
|
||||||
|
return $this->getGenericFilter('filter');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected function getAttributesForCategory(array $category_ids){
|
||||||
|
|
||||||
|
$category_att_id_list = array();
|
||||||
|
$attr_query_cond = (sizeof($category_ids)) ? " AND category_id IN (".join(',', $category_ids).") " : "";
|
||||||
|
|
||||||
|
$query = $this->db->runQuery(" SELECT `attr_id` FROM `idv_attribute_category` WHERE 1 ". $attr_query_cond);
|
||||||
|
foreach ($this->db->fetchAll($query) as $rs ){
|
||||||
|
$category_att_id_list[] = $rs['attr_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!sizeof($category_att_id_list)) return array();
|
||||||
|
|
||||||
|
$query = $this->db->query("
|
||||||
|
SELECT
|
||||||
|
attval.id ,
|
||||||
|
attval.attributeId ,
|
||||||
|
attval.value ,
|
||||||
|
attval.api_key ,
|
||||||
|
att.attribute_code ,
|
||||||
|
att.filter_code
|
||||||
|
FROM ".TB_ATTRIBUTE_VALUE." attval
|
||||||
|
LEFT JOIN ".TB_ATTRIBUTE." att ON attval.attributeId = att.id
|
||||||
|
WHERE att.id IN (".join(',', $category_att_id_list).") AND att.isSearch=1
|
||||||
|
ORDER BY attval.ordering DESC ");
|
||||||
|
|
||||||
|
$attribute_list = [];
|
||||||
|
|
||||||
|
foreach ($this->db->fetchAll($query) as $rs ){
|
||||||
|
$att_value_id = $rs['id'];
|
||||||
|
$att_id = $rs['attributeId'];
|
||||||
|
|
||||||
|
$value_info = array(
|
||||||
|
"id" => $att_value_id,
|
||||||
|
"api_key" => $rs['api_key'],
|
||||||
|
);
|
||||||
|
|
||||||
|
if(!array_key_exists($att_id, $attribute_list)) {
|
||||||
|
$attribute_list[$att_id] = [
|
||||||
|
"id" => $att_id,
|
||||||
|
"code" => $rs['attribute_code'],
|
||||||
|
"filter_code" => $rs['filter_code'],
|
||||||
|
'value_list' => [$value_info],
|
||||||
|
];
|
||||||
|
}else{
|
||||||
|
$attribute_list[$att_id]['value_list'][] = $value_info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return array_values($attribute_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// accept url format:
|
||||||
|
// default: ?min=1000&max=20000
|
||||||
|
// custom: ?p=15trieu-20-trieu
|
||||||
|
protected function getPriceFilterRange(){
|
||||||
|
// default format
|
||||||
|
if($this->_price_format != 'p') {
|
||||||
|
return array(
|
||||||
|
"min" => isset($this->_url_elements['query']['min']) ? intval($this->_url_elements['query']['min']) : 0 ,
|
||||||
|
"max" => isset($this->_url_elements['query']['max']) ? intval($this->_url_elements['query']['max']) : 0 ,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// custom price range query
|
||||||
|
$price_range_format = (isset($this->_url_elements['query']['p'])) ? $this->_url_elements['query']['p'] : ''; // duoi-10trieu , 10ngan-2trieu, 3trieu-6trieu, tren-30trieu
|
||||||
|
|
||||||
|
if(strpos($price_range_format, '-') === false) {
|
||||||
|
return array(
|
||||||
|
"min" => 0,
|
||||||
|
"max" => 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$unit_translation = [
|
||||||
|
'ngan' => 1000,
|
||||||
|
'trieu' => 1000000,
|
||||||
|
'ty' => 1000000000,
|
||||||
|
];
|
||||||
|
|
||||||
|
// duoi-10trieu, duoi-10ngan
|
||||||
|
if(strpos($price_range_format, 'duoi-') !== false) {
|
||||||
|
$unit_match = $this->findPriceUnitMatch(str_replace("duoi-", "", $price_range_format));
|
||||||
|
return array(
|
||||||
|
"min" => 0,
|
||||||
|
"max" => $unit_match['number'] * $unit_translation[$unit_match['unit']],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// tren-10trieu, tren-10ngan
|
||||||
|
if(strpos($price_range_format, 'tren-') !== false) {
|
||||||
|
$unit_match = $this->findPriceUnitMatch(str_replace("tren-", "", $price_range_format));
|
||||||
|
return array(
|
||||||
|
"min" => $unit_match['number'] * $unit_translation[$unit_match['unit']],
|
||||||
|
"max" => 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10ngan-2trieu, 3trieu-6trieu
|
||||||
|
$parts = explode('-', $price_range_format);
|
||||||
|
$min_part = $parts[0];
|
||||||
|
$max_part = $parts[1];
|
||||||
|
$min_match = $this->findPriceUnitMatch($min_part);
|
||||||
|
$max_match = $this->findPriceUnitMatch($max_part);
|
||||||
|
|
||||||
|
return [
|
||||||
|
"min" => $min_match['number'] * $unit_translation[$min_match['unit']],
|
||||||
|
"max" => $max_match['number'] * $unit_translation[$max_match['unit']],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function findPriceUnitMatch($str){
|
||||||
|
$match = [];
|
||||||
|
$pattern = "/^([\d]+)(ngan|trieu|ty)/i";
|
||||||
|
if(preg_match($pattern, $str, $match)){
|
||||||
|
return [
|
||||||
|
"number" => $match[1],
|
||||||
|
"unit" => $match[2],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
"number" => 0,
|
||||||
|
"unit" => 'trieu',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,797 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Controller;
|
||||||
|
|
||||||
|
use Hura8\Components\Product\Model\ProductCollectionModel;
|
||||||
|
use Hura8\System\Url;
|
||||||
|
use Hura8\Components\Brand\Model\BrandModel;
|
||||||
|
use Hura8\Components\Product\AdminController\AProductHotController;
|
||||||
|
use Hura8\Components\Product\Model\ProductAttributeModel;
|
||||||
|
use Hura8\Components\Product\Model\ProductAttributeValueModel;
|
||||||
|
use Hura8\Components\Product\Model\ProductCategoryModel;
|
||||||
|
use Hura8\Components\Product\Model\ProductFilterModel;
|
||||||
|
use Hura8\Components\Product\Model\ProductSearchModel;
|
||||||
|
use Hura8\Interfaces\TableName;
|
||||||
|
use Hura8\Traits\ClassCacheTrait;
|
||||||
|
|
||||||
|
|
||||||
|
class ProductFilterController
|
||||||
|
{
|
||||||
|
|
||||||
|
use ClassCacheTrait;
|
||||||
|
|
||||||
|
protected $_request_url = '';
|
||||||
|
|
||||||
|
/* @var ProductFilterModel $objProductFilterModel */
|
||||||
|
protected $objProductFilterModel;
|
||||||
|
|
||||||
|
/*
|
||||||
|
all types of filtering representation must be finally translated to this format
|
||||||
|
For example:
|
||||||
|
- ?brand=sony => translated to brand=>array(2,),
|
||||||
|
- ?monitor_size=12inch&color=red => translated to attribute=> array(123, 23121,)
|
||||||
|
*/
|
||||||
|
protected $filters = [
|
||||||
|
"price" => array('max' => 0, 'min'=> 0),
|
||||||
|
"brand" => array(), // array(1,2,3,)
|
||||||
|
"collection" => array(), // array(1,2,3,)
|
||||||
|
"supplier" => array(), // array(1,2,3,)
|
||||||
|
"rating" => array(), // array(1,2,3,)
|
||||||
|
"category" => array(), // array(1,2,3,)
|
||||||
|
"status" => array(), // array(1,2,3,)
|
||||||
|
"query" => "", // string search keyword
|
||||||
|
"hotType" => array(),// array(saleoff | not | new)
|
||||||
|
"attribute" => array(), // array(1,2,3,)
|
||||||
|
"ids" => array(), // array(1,2,3,)
|
||||||
|
"excluded_ids" => array(), // array(1,2,3,)
|
||||||
|
"promotion" => "",
|
||||||
|
"storeId" => array(), // array(1,2,3,)
|
||||||
|
"other_filter" => array(), // array(in-stock, has-promotion etc...)
|
||||||
|
"spec_group_id" => array(), // array(1,2,3,)
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct($request_url = '') {
|
||||||
|
$this->_request_url = $request_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getResult(
|
||||||
|
$current_page_id =1,
|
||||||
|
$number_per_page=10,
|
||||||
|
$sort_by = 'new',
|
||||||
|
array $existing_filters=[],
|
||||||
|
$current_category_ids = [],
|
||||||
|
$price_range = []
|
||||||
|
) {
|
||||||
|
|
||||||
|
$objProductFilterBuilder = new ProductFilterBuilderController($this->_request_url);
|
||||||
|
$built_filters = $objProductFilterBuilder->buildProductFilterFromUrl($existing_filters);
|
||||||
|
$this->setFilters($built_filters);
|
||||||
|
|
||||||
|
$product_result = $this->findProductListIdMatchFilters($current_page_id, $number_per_page, $sort_by);
|
||||||
|
//debug_var($product_result);
|
||||||
|
|
||||||
|
$objProductFilterOptions = new ProductFilterOptionsController(
|
||||||
|
$this->getFilters(),
|
||||||
|
$current_category_ids,
|
||||||
|
$product_result['item_list'],
|
||||||
|
$price_range
|
||||||
|
);
|
||||||
|
$objProductFilterOptionsTranslation = new ProductFilterOptionsTranslationController($this->_request_url);
|
||||||
|
|
||||||
|
list ( $filter_options, $filter_messages ) = $objProductFilterOptionsTranslation->getAllFilterOptions(
|
||||||
|
$objProductFilterOptions->categoryList(),
|
||||||
|
$objProductFilterOptions->attributeList(),
|
||||||
|
$objProductFilterOptions->brandList(),
|
||||||
|
$objProductFilterOptions->priceList()
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'total' => $product_result['total'],
|
||||||
|
'paged_product_list' => $product_result['item_list'],
|
||||||
|
'filter_options' => $filter_options,
|
||||||
|
'filter_messages' => $filter_messages,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getFilters() {
|
||||||
|
return $this->filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// allow to update filters
|
||||||
|
public function setFilters(array $new_filters) {
|
||||||
|
foreach ($new_filters as $key => $value) {
|
||||||
|
if(isset($this->filters[$key])) {
|
||||||
|
$this->filters[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function createFilterCacheKey($sort_by) {
|
||||||
|
return md5(\json_encode($this->filters) . $sort_by);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// find a list of products that match filtering conditions
|
||||||
|
protected function findProductListIdMatchFilters($current_page_id, $number_per_page, $sort_by = "new" ) {
|
||||||
|
|
||||||
|
$cache_key = $this->createFilterCacheKey($sort_by);
|
||||||
|
|
||||||
|
return self::getCache($cache_key, function () use ($current_page_id, $number_per_page, $sort_by) {
|
||||||
|
return $this->findProductListIdMatchFilters_raw($current_page_id, $number_per_page, $sort_by );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function findProductListIdMatchFilters_raw($current_page_id=1, $number_per_page=10, $sort_by = "new" ) {
|
||||||
|
|
||||||
|
list( , , , $where_query ) = $this->buildSQLConditionFromFilters($sort_by);
|
||||||
|
|
||||||
|
$ordering_clause = $this->getOrderingClause($sort_by);
|
||||||
|
|
||||||
|
$objProductFilterModel = new ProductFilterModel();
|
||||||
|
|
||||||
|
list($total_number, $item_list) = $objProductFilterModel->findProductListIdMatchFilters([
|
||||||
|
$where_query,
|
||||||
|
$current_page_id,
|
||||||
|
$number_per_page,
|
||||||
|
$ordering_clause
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
$final_result = [
|
||||||
|
"total" => $total_number,
|
||||||
|
"item_list" => $item_list,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $final_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function buildSQLConditionFromFilters( $sort_by = "new" ) {
|
||||||
|
//list of available filters
|
||||||
|
$filterPath = [];
|
||||||
|
$filter_messages = [];
|
||||||
|
$where_query = [];
|
||||||
|
|
||||||
|
// system controls
|
||||||
|
if( ENABLE_PRODUCT_EXPIRE ) {
|
||||||
|
$where_query[] = " AND (from_time =0 OR from_time < '".CURRENT_TIME."')
|
||||||
|
AND (to_time > '".CURRENT_TIME."' OR to_time=0 ) ";
|
||||||
|
}
|
||||||
|
|
||||||
|
//other filters
|
||||||
|
if( array_key_exists("other_filter", $this->filters) && sizeof($this->filters["other_filter"])) {
|
||||||
|
foreach ($this->filters["other_filter"] as $_filter) {
|
||||||
|
switch ($_filter) {
|
||||||
|
case "in-stock";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Còn hàng',
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND quantity > 0 ";
|
||||||
|
break;
|
||||||
|
case "has-vat";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Có thuế VAT',
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND `has_vat` = 1 ";
|
||||||
|
break;
|
||||||
|
case "out-stock";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Hết hàng',
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND `quantity` = 0 ";
|
||||||
|
break;
|
||||||
|
case "has-market-price";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Có giá thị trường',
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND market_price > 0 ";
|
||||||
|
break;
|
||||||
|
case "no-price";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Không có giá',
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND `price` = 0 ";
|
||||||
|
break;
|
||||||
|
case "no-warranty";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Không có bảo hành',
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND LENGTH(warranty) < 2 ";
|
||||||
|
break;
|
||||||
|
case "no-sku";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Không mã kho hàng',
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND LENGTH(sku) < 2 ";
|
||||||
|
break;
|
||||||
|
case "has-config";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Không có cấu hình',
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND `config_count` > 0 ";
|
||||||
|
break;
|
||||||
|
case "no-image";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Không có ảnh',
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND image_count = 0 ";
|
||||||
|
break;
|
||||||
|
case "no-category";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Không có danh mục',
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND `category` = '0' ";
|
||||||
|
break;
|
||||||
|
case "display-off";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Đang ẩn',
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND `status`=0 ";
|
||||||
|
break;
|
||||||
|
case "display-on";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Đang hiển thị',
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND `status`=1 ";
|
||||||
|
break;
|
||||||
|
case "has-promotion":
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Có khuyến mại',
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['other_filter' => '']),
|
||||||
|
];
|
||||||
|
$where_query[] = " AND LENGTH(`special_offer`) < 5 ";
|
||||||
|
break;
|
||||||
|
//...add more
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*// limit by admin permission
|
||||||
|
//1-1-2014 gioi han theo san pham danh muc ma quan tri nay quan ly
|
||||||
|
global $admin_permission;
|
||||||
|
$restricted_category_per_admin = (isset($admin_permission['permit'])) ? $admin_permission['permit']['product:category']['ids'] : [];
|
||||||
|
if(sizeof($restricted_category_per_admin)) {
|
||||||
|
$cat_query = $this->db->runQuery("
|
||||||
|
SELECT DISTINCT pro_id
|
||||||
|
FROM ".TB_PRODUCT_CATEGORY."
|
||||||
|
WHERE `category_id` IN (".join(",", $restricted_category_per_admin).")
|
||||||
|
LIMIT 5000
|
||||||
|
");
|
||||||
|
|
||||||
|
//lay danh sach danh muc cua san pham
|
||||||
|
$product_in_category = [];
|
||||||
|
foreach( $this->db->fetchAll($cat_query) as $cat_info ){
|
||||||
|
$product_in_category[] = $cat_info["pro_id"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_query[] = (sizeof($product_in_category)) ? " AND id IN (". join(',', $product_in_category) .") " : " AND id = 0 ";
|
||||||
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
//- brand id or ids or brand_indexes
|
||||||
|
if( array_key_exists("brand", $this->filters) && sizeof($this->filters["brand"])) {
|
||||||
|
|
||||||
|
$objBrandModel = new BrandModel();
|
||||||
|
|
||||||
|
$condition = array();
|
||||||
|
foreach ($this->filters["brand"] as $brand_id) {
|
||||||
|
if(!$brand_id) continue;
|
||||||
|
|
||||||
|
$brand_info = $objBrandModel->getInfo( $brand_id);
|
||||||
|
if(!$brand_info) continue;
|
||||||
|
|
||||||
|
$filterPath["brand"][] = array(
|
||||||
|
"id" => $brand_info['id'],
|
||||||
|
"name" => $brand_info["title"],
|
||||||
|
);
|
||||||
|
|
||||||
|
$condition[] = " brandId = '".intval($brand_info['id'])."' ";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $brand_info['title'],
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['brand' => join(FILTER_VALUE_SEPARATOR, remove_item_from_array($this->filters["brand"], $brand_id ))] ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sizeof($condition)) {
|
||||||
|
$where_query[] = " AND ( ".join(" OR ", $condition)." )";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//- collection id or ids
|
||||||
|
if( array_key_exists("collection", $this->filters) && sizeof($this->filters["collection"])) {
|
||||||
|
$condition = array();
|
||||||
|
|
||||||
|
$objProductCollectionModel = new ProductCollectionModel();
|
||||||
|
|
||||||
|
foreach ($this->filters["collection"] as $_id) {
|
||||||
|
|
||||||
|
$collection_info = $objProductCollectionModel->getInfo($_id);
|
||||||
|
if(!$collection_info) continue;
|
||||||
|
|
||||||
|
$filterPath["collection"][] = array(
|
||||||
|
"id" => $_id,
|
||||||
|
"name" => $collection_info["title"],
|
||||||
|
);
|
||||||
|
$condition[] = " `id` IN ( SELECT product_id FROM ".TableName::PRODUCT_PER_COLLECTION." WHERE `collection_id`='".intval($collection_info['id'])."' ) ";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Bộ sưu tập ',
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['collection' => join(',', remove_item_from_array($this->filters["collection"], $_id))] ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_query[] = " AND ( ".join(" OR ", $condition)." )";
|
||||||
|
}
|
||||||
|
|
||||||
|
//- supplier id
|
||||||
|
if( array_key_exists("supplier", $this->filters) && sizeof($this->filters["supplier"])) {
|
||||||
|
/*$condition = array();
|
||||||
|
|
||||||
|
if(!$this->objSupplier) $this->objSupplier = new Supplier();
|
||||||
|
|
||||||
|
foreach ($this->filters["supplier"] as $_id) {
|
||||||
|
$_id = intval($_id);
|
||||||
|
|
||||||
|
$supplier_info = $this->objSupplier->getInfo($_id);
|
||||||
|
|
||||||
|
$filterPath["supplier"][] = array(
|
||||||
|
"id" => $_id,
|
||||||
|
"name" => $supplier_info["name"],
|
||||||
|
);
|
||||||
|
$condition[] = " supplier_id = '".$_id."' ";
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $supplier_info["name"],
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['supplier' => join(',', remove_item_from_array($this->filters["supplier"], $_id))] ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_query[] = " AND ( ".join(" OR ", $condition)." )";*/
|
||||||
|
}
|
||||||
|
|
||||||
|
//- rating: 1-> 5
|
||||||
|
if( array_key_exists("rating", $this->filters) && sizeof($this->filters["rating"])) {
|
||||||
|
$condition = array();
|
||||||
|
foreach ($this->filters["rating"] as $_id) {
|
||||||
|
|
||||||
|
$condition[] = " `rating` = '".intval($_id)."' ";
|
||||||
|
$filterPath["rating"][] = array(
|
||||||
|
"id" => $_id,
|
||||||
|
"name" => $_id,
|
||||||
|
);
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => 'Đánh giá '.$_id,
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['rating' => join(',', remove_item_from_array($this->filters["rating"], $_id))] ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_query[] = " AND ( ".join(" OR ", $condition)." )";
|
||||||
|
}
|
||||||
|
|
||||||
|
//- category id or ids
|
||||||
|
if( array_key_exists("category", $this->filters) && sizeof($this->filters["category"])) {
|
||||||
|
|
||||||
|
$objProductCategoryModel = new ProductCategoryModel();
|
||||||
|
|
||||||
|
$condition = array();
|
||||||
|
foreach ($this->filters["category"] as $cat_id) {
|
||||||
|
|
||||||
|
$cat_info = $objProductCategoryModel->getInfo($cat_id);
|
||||||
|
if(!$cat_info) continue;
|
||||||
|
|
||||||
|
if($cat_info["is_parent"]) {
|
||||||
|
$childListId = ($cat_info["child_ids"]) ?: '0';
|
||||||
|
$condition[] = " `category_id` IN (".$childListId .") ";
|
||||||
|
}else{
|
||||||
|
$condition[] = " `category_id` = '".$cat_id."' ";
|
||||||
|
}
|
||||||
|
|
||||||
|
$filterPath["category"][] = array(
|
||||||
|
"id" => $cat_id,
|
||||||
|
"name" => $cat_info['title'],
|
||||||
|
);
|
||||||
|
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $cat_info['title'],
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['category' => join(FILTER_VALUE_SEPARATOR, remove_item_from_array($this->filters["category"], $cat_id))] ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sizeof($condition)) {
|
||||||
|
$where_query[] = " AND `id` IN ( SELECT DISTINCT `item_id` FROM ".TableName::PRODUCT_PER_CATEGORY." WHERE " . join(" OR ", $condition) . " )";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//- status: 0|1|null
|
||||||
|
if( array_key_exists("status", $this->filters) && sizeof($this->filters["status"])) {
|
||||||
|
$where_query[] = " AND `status` IN (".join(',', $this->filters["status"]).") ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// spec_group_id
|
||||||
|
if( array_key_exists("spec_group_id", $this->filters) && sizeof($this->filters["spec_group_id"])) {
|
||||||
|
$where_query[] = " AND `spec_group_id` IN (".join(',', $this->filters["spec_group_id"]).") ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// detail_page_only: 0 | 1
|
||||||
|
if( array_key_exists("detail_page_only", $this->filters) && $this->filters["detail_page_only"]) {
|
||||||
|
$where_query[] = " AND `detail_page_only` = '".intval($this->filters['detail_page_only'])."' ";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//- query: search keyword
|
||||||
|
if( array_key_exists("query", $this->filters) && $this->filters["query"]) {
|
||||||
|
$keyword_search = $this->filters["query"];
|
||||||
|
$search_by_product_id = intval(preg_replace('/[^0-9]/i', '', $keyword_search));
|
||||||
|
|
||||||
|
// todo: add more
|
||||||
|
$filter_search = [];
|
||||||
|
if(isset($this->filters["brand"]) && $this->filters["brand"]) {
|
||||||
|
$filter_search['brand'] = ["IN", $this->filters["brand"]];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($this->filters["status"]) && $this->filters["status"]) {
|
||||||
|
$filter_search['status'] = ["IN", $this->filters["status"]];
|
||||||
|
}
|
||||||
|
|
||||||
|
$objProductSearchModel = new ProductSearchModel();
|
||||||
|
$match_result = $objProductSearchModel->find($keyword_search, $filter_search);
|
||||||
|
|
||||||
|
//debug_var($match_result);
|
||||||
|
|
||||||
|
$filterPath["search"] = array(
|
||||||
|
"id" => $keyword_search,
|
||||||
|
"name" => $keyword_search,
|
||||||
|
);
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $keyword_search,
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['q' => ''] ),
|
||||||
|
];
|
||||||
|
|
||||||
|
if($search_by_product_id > 0) {
|
||||||
|
$where_query[] = (sizeof($match_result) > 0) ? " AND ( `id` IN (".join(',', $match_result).") OR `id` = '".$search_by_product_id."' ) " : " AND `id` = '".$search_by_product_id."' ";
|
||||||
|
}else{
|
||||||
|
$where_query[] = (sizeof($match_result) > 0) ? " AND `id` IN (".join(',', $match_result).") " : " AND `id` = -1 ";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//- hotType: saleoff | not | new | or combination of types
|
||||||
|
if( array_key_exists("hotType", $this->filters) && sizeof($this->filters["hotType"])) {
|
||||||
|
$config_hottype = AProductHotController::getProductHotTypeList();
|
||||||
|
$condition = array();
|
||||||
|
foreach ($this->filters["hotType"] as $_id) {
|
||||||
|
if(!array_key_exists($_id, $config_hottype)) continue;
|
||||||
|
|
||||||
|
$condition[] = " `hot_type` = '".$_id."' ";
|
||||||
|
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $config_hottype[$_id],
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['hotType' => '']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sizeof($condition)) {
|
||||||
|
$where_query[] = " AND `id` IN (SELECT pro_id FROM ".TB_PRODUCT_HOT." WHERE 1 AND ( ".join(" OR ", $condition)." ) ) ";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//- attribute values
|
||||||
|
/*
|
||||||
|
if( array_key_exists("attribute", $this->filters) && sizeof($this->filters["attribute"])) {
|
||||||
|
$filter_attr_value_list = $this->filters["attribute"];
|
||||||
|
//filter = attr_value_1-attr_value_2-attr_value_3,
|
||||||
|
$query_attr_id = [];
|
||||||
|
$count_filter = 0;
|
||||||
|
|
||||||
|
foreach($filter_attr_value_list as $attr_id){
|
||||||
|
$attr_id = (int) $attr_id;
|
||||||
|
if($attr_id) {
|
||||||
|
$query_attr_id[] = $attr_id;
|
||||||
|
$count_filter ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$product_filter_id_match = array();
|
||||||
|
if(sizeof($query_attr_id)) {
|
||||||
|
$query = $this->db->runQuery("
|
||||||
|
SELECT DISTINCT pro_id , COUNT(*) as num_pro
|
||||||
|
FROM ".TB_PRODUCT_ATTRIBUTE."
|
||||||
|
WHERE attr_value_id IN (".join(',', $query_attr_id).")
|
||||||
|
GROUP BY pro_id
|
||||||
|
HAVING num_pro = ".$count_filter."
|
||||||
|
LIMIT 10000
|
||||||
|
");
|
||||||
|
foreach ( $this->db->fetchAll($query) as $rs ) {
|
||||||
|
$product_filter_id_match[] = $rs["pro_id"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_query[] = (sizeof($product_filter_id_match)) ? " AND `id` IN (".join(', ', $product_filter_id_match).") " : " AND `id` = 0 " ;
|
||||||
|
|
||||||
|
//xay lai url de back
|
||||||
|
if(!$this->objCategoryProduct) $this->objCategoryProduct = new CategoryProduct();
|
||||||
|
|
||||||
|
foreach($filter_attr_value_list as $value_id ){
|
||||||
|
$att_name = $this->objCategoryProduct->atrValueName($value_id);
|
||||||
|
|
||||||
|
$filterPath["attribute"][] = array(
|
||||||
|
"id" => $value_id,
|
||||||
|
"name" => $att_name,
|
||||||
|
);
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $att_name,
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['filter' => join(FILTER_VALUE_SEPARATOR, remove_item_from_array($this->filters["attribute"], $value_id))] ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
//- attribute values
|
||||||
|
if( array_key_exists("attribute", $this->filters) && sizeof($this->filters["attribute"])) {
|
||||||
|
|
||||||
|
$filter_attr_value_list = $this->filters["attribute"];
|
||||||
|
|
||||||
|
$attr_values_per_attribute = $this->groupAttributeValuesByAttributeId($filter_attr_value_list);
|
||||||
|
|
||||||
|
$having_match_count = sizeof(array_keys($attr_values_per_attribute));
|
||||||
|
|
||||||
|
$attribute_info_list = $this->groupAttributeListInfo(array_keys($attr_values_per_attribute));
|
||||||
|
|
||||||
|
$attr_where_query = [];
|
||||||
|
foreach ($attr_values_per_attribute as $_att_id => $_att_value_list) {
|
||||||
|
// if same attribute : find products that match either ONE of these values (not match ALL values)
|
||||||
|
$_att_info = $attribute_info_list[$_att_id];
|
||||||
|
if (!$_att_info) continue;
|
||||||
|
|
||||||
|
// this attribute requires all products must have ALL selected values
|
||||||
|
if ($_att_info['value_match_all'] && sizeof($_att_value_list) > 1) {
|
||||||
|
|
||||||
|
$_product_id_match_all = $this->getProductMatchAllAttributeValueIds($_att_value_list);
|
||||||
|
|
||||||
|
if (sizeof($_product_id_match_all) > 0) {
|
||||||
|
$attr_where_query[] = " ( `id` IN (" . join(",", $_product_id_match_all) . ") ) ";
|
||||||
|
} else {
|
||||||
|
$attr_where_query[] = " ( `id` = -1 ) ";
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$attr_where_query[] = " ( SELECT DISTINCT `pro_id` FROM " . TB_PRODUCT_ATTRIBUTE . " WHERE (" .
|
||||||
|
join(" OR ", array_map(function ($_item) { return " `attr_value_id` = '" . intval($_item['id']) . "' "; }, $_att_value_list))
|
||||||
|
. " ) ) ";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$product_filter_id_match = array();
|
||||||
|
|
||||||
|
if (sizeof($attr_where_query)) {
|
||||||
|
$query = $this->db->runQuery("
|
||||||
|
SELECT `pro_id` , COUNT(*) AS num_pro
|
||||||
|
FROM ( " . join(" UNION ALL ", $attr_where_query) . " ) AS tpm_tb
|
||||||
|
GROUP BY pro_id
|
||||||
|
HAVING num_pro = " . $having_match_count . "
|
||||||
|
LIMIT 10000
|
||||||
|
");
|
||||||
|
foreach ($this->db->fetchAll($query) as $rs) {
|
||||||
|
$product_filter_id_match[] = $rs["pro_id"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_query[] = (sizeof($product_filter_id_match)) ? " AND " . TB_PRODUCT_LIGHT . ".`id` IN (" . join(', ', $product_filter_id_match) . ") " : " AND " . TB_PRODUCT_LIGHT . ".`id` = 0 ";
|
||||||
|
|
||||||
|
//xay lai url de back
|
||||||
|
$objProductAttributeModel = new ProductAttributeModel();
|
||||||
|
|
||||||
|
foreach($filter_attr_value_list as $value_id ){
|
||||||
|
$att_name = $objProductAttributeModel->getInfo($value_id)['title'];
|
||||||
|
|
||||||
|
$filterPath["attribute"][] = array(
|
||||||
|
"id" => $value_id,
|
||||||
|
"name" => $att_name,
|
||||||
|
);
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $att_name,
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['filter' => join(FILTER_VALUE_SEPARATOR, remove_item_from_array($this->filters["attribute"], $value_id))] ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*//xay lai url de back
|
||||||
|
$filterPath["attribute"] = $filter_attr_value_list;
|
||||||
|
|
||||||
|
foreach ($attr_values_per_attribute as $_att_id => $_att_value_list) {
|
||||||
|
foreach ($_att_value_list as $_item) {
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $_item['title'],
|
||||||
|
'reset' => Url::buildUrl(CURRENT_URL, ['filter' => join('-', remove_item_from_array($filter_attr_value_list, $_item['id']))]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//given products' ids
|
||||||
|
if( array_key_exists("ids", $this->filters) && sizeof($this->filters["ids"])) {
|
||||||
|
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => "IDs",
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['ids' => '']),
|
||||||
|
];
|
||||||
|
|
||||||
|
$where_query[] = " AND `id` IN (". join(",", $this->filters["ids"]) .") ";
|
||||||
|
}
|
||||||
|
|
||||||
|
// exclude some ids
|
||||||
|
if( array_key_exists("excluded_ids", $this->filters) && sizeof($this->filters["excluded_ids"])) {
|
||||||
|
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => "Excluded IDs",
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['excluded_ids' => '']),
|
||||||
|
];
|
||||||
|
|
||||||
|
$where_query[] = " AND `id` NOT IN (". join(",", $this->filters["excluded_ids"]) .") ";
|
||||||
|
}
|
||||||
|
|
||||||
|
$price_range_query_limit = join(" ",$where_query);
|
||||||
|
|
||||||
|
//- price range
|
||||||
|
if(isset($this->filters["price"]) && sizeof($this->filters["price"]) && ($this->filters["price"]['min'] > 0 || $this->filters["price"]['max'] > 0)) {
|
||||||
|
//limit by price range
|
||||||
|
$maxPrice = clean_price($this->filters["price"]['max']);
|
||||||
|
$minPrice = clean_price($this->filters["price"]['min']);
|
||||||
|
|
||||||
|
$price_range_query = '';
|
||||||
|
if($maxPrice > 0 && $minPrice > 0){
|
||||||
|
$price_range_query = " ( `price` BETWEEN '".$minPrice."' AND '".$maxPrice."' ) ";
|
||||||
|
|
||||||
|
}else if($maxPrice > 0){
|
||||||
|
$price_range_query = " `price` < '".$maxPrice."' ";
|
||||||
|
|
||||||
|
}else if($minPrice > 0){
|
||||||
|
$price_range_query = " `price` >='".$minPrice."' ";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if(ENABLE_INVENTORY_PER_STORE && USER_LOCATION) {
|
||||||
|
|
||||||
|
$list_ids = $this->getProductIds($price_range_query_limit);
|
||||||
|
$this->objCache->set("all_product_ids_location", $list_ids);
|
||||||
|
|
||||||
|
$objUStoreLocation = new UStoreLocation();
|
||||||
|
$location_products = $objUStoreLocation->getAllProductForLocation(USER_LOCATION, $list_ids, $sort_by);
|
||||||
|
$location_products = $this->filterLocationProductByPrice($location_products, $maxPrice, $minPrice);
|
||||||
|
|
||||||
|
$this->objCache->set("location_products", $location_products);
|
||||||
|
|
||||||
|
$location_product_ids = array_keys($location_products);
|
||||||
|
$where_query[] = (sizeof($location_product_ids)) ? " AND `id` IN (".join(',', $location_product_ids).") " : " AND `id` = 0";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$where_query[] = " AND ". $price_range_query;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
$where_query[] = " AND ". $price_range_query;
|
||||||
|
|
||||||
|
$filterPath["price"] = array(
|
||||||
|
"min" => $minPrice,
|
||||||
|
"max" => $maxPrice,
|
||||||
|
);
|
||||||
|
|
||||||
|
$price_format = ProductFilterPrice::buildPriceRangeFormat($minPrice, $maxPrice);
|
||||||
|
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $price_format['title'],
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['p' => '', 'min' => '', 'max' => ''])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
$price_range_query_limit,
|
||||||
|
$filterPath,
|
||||||
|
$filter_messages,
|
||||||
|
$where_query
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function getProductMatchAllAttributeValueIds(array $_att_value_list) {
|
||||||
|
$objProductAttributeModel = new ProductAttributeModel();
|
||||||
|
return $objProductAttributeModel->getProductMatchAllAttributeValueIds($_att_value_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function groupAttributeListInfo(array $attr_ids) {
|
||||||
|
|
||||||
|
$objProductAttributeModel = new ProductAttributeModel();
|
||||||
|
$attr_list_info = $objProductAttributeModel->getListByIds($attr_ids);
|
||||||
|
|
||||||
|
$final_list = [];
|
||||||
|
foreach ($attr_ids as $_id) {
|
||||||
|
$final_list[$_id] = (isset($attr_list_info[$_id])) ? $attr_list_info[$_id] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $final_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function groupAttributeValuesByAttributeId(array $attr_value_ids) {
|
||||||
|
|
||||||
|
$objProductAttributeValueModel = new ProductAttributeValueModel(0);
|
||||||
|
|
||||||
|
$result = array();
|
||||||
|
foreach ( $objProductAttributeValueModel->getListByIds($attr_value_ids) as $info ) {
|
||||||
|
$result[$info['attributeId']][] = [
|
||||||
|
'id' => $info['id'],
|
||||||
|
'title' => $info['value'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function filterLocationProductByPrice(array $product_list, $max_price, $min_price) {
|
||||||
|
if($max_price > 0 && $min_price > 0){
|
||||||
|
return array_filter($product_list, function ($pro) use ($max_price, $min_price) {
|
||||||
|
return ($pro['price'] >= $min_price && $pro['price'] < $max_price);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if($max_price > 0){
|
||||||
|
return array_filter($product_list, function ($pro) use ($max_price, $min_price) {
|
||||||
|
return ($pro['price'] < $max_price);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if($min_price > 0){
|
||||||
|
return array_filter($product_list, function ($pro) use ($max_price, $min_price) {
|
||||||
|
return ($pro['price'] >= $min_price);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $product_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function getOrderingClause($sort_by) {
|
||||||
|
$mappings = [
|
||||||
|
'order-new' => "ORDER BY `ordering` DESC, `id` DESC",
|
||||||
|
'order-last-update' => "ORDER BY `ordering` DESC, last_update DESC",
|
||||||
|
'last-update' => "ORDER BY `last_update` DESC",
|
||||||
|
'order' => "ORDER BY `ordering` DESC",
|
||||||
|
'new' => "ORDER BY `id` DESC",
|
||||||
|
'price-asc' => "ORDER BY `price` asc",
|
||||||
|
'price-desc' => "ORDER BY `price` DESC ",
|
||||||
|
'view' => "ORDER BY `visit` desc",
|
||||||
|
'ranking' => "ORDER BY `ranking` DESC",
|
||||||
|
'name' => "ORDER BY `title` asc",
|
||||||
|
];
|
||||||
|
|
||||||
|
if(array_key_exists($sort_by, $mappings)) {
|
||||||
|
return $mappings[$sort_by];
|
||||||
|
}
|
||||||
|
|
||||||
|
return "ORDER BY `ordering` DESC, `id` DESC";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Controller;
|
||||||
|
|
||||||
|
use Hura8\Components\Product\Model\ProductFilterModel;
|
||||||
|
|
||||||
|
|
||||||
|
class ProductFilterOptionsController {
|
||||||
|
|
||||||
|
protected $_current_category_ids = [];
|
||||||
|
|
||||||
|
protected $_list_product_ids = [];
|
||||||
|
|
||||||
|
protected $_price_range = [];// [1000000, 5000000, 15000000, 25000000, 50000000];
|
||||||
|
|
||||||
|
protected $_filters = [
|
||||||
|
"price" => array('max' => 0, 'min'=> 0),
|
||||||
|
"brand" => array(), // array(1,2,3,)
|
||||||
|
"collection" => array(), // array(1,2,3,)
|
||||||
|
"supplier" => array(), // array(1,2,3,)
|
||||||
|
"rating" => array(), // array(1,2,3,)
|
||||||
|
"category" => array(), // array(1,2,3,)
|
||||||
|
"status" => array(), // array(1,2,3,)
|
||||||
|
"query" => "", // string search keyword
|
||||||
|
"hotType" => array(),// array(saleoff | not | new)
|
||||||
|
"attribute" => array(), // array(1,2,3,)
|
||||||
|
"ids" => array(), // array(1,2,3,)
|
||||||
|
"promotion" => "",
|
||||||
|
"storeId" => array(), // array(1,2,3,)
|
||||||
|
"other_filter" => array() // array(in-stock, has-promotion etc...)
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $objProductFilterModel;
|
||||||
|
|
||||||
|
public function __construct($filters, $current_category_ids = [], $list_product_ids = [], $price_range = []) {
|
||||||
|
|
||||||
|
$this->objProductFilterModel = new ProductFilterModel();
|
||||||
|
|
||||||
|
$this->_filters = $filters;
|
||||||
|
$this->_current_category_ids = $current_category_ids;
|
||||||
|
$this->_list_product_ids = $list_product_ids;
|
||||||
|
$this->_price_range = $price_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function ratingList() {
|
||||||
|
|
||||||
|
$neededList = array();
|
||||||
|
|
||||||
|
foreach ( $this->objProductFilterModel->ratingList($this->_list_product_ids) as $result ) {
|
||||||
|
$neededList[] = array(
|
||||||
|
"rating" => $result['review_rate'] ,
|
||||||
|
"count" => $result['num'] ,
|
||||||
|
'is_selected' => in_array($result['review_rate'], $this->_filters['rating']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $neededList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function categoryList() {
|
||||||
|
|
||||||
|
if(!sizeof($this->_list_product_ids)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->objProductFilterModel->categoryList($this->_filters['category'], $this->_list_product_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function brandList() {
|
||||||
|
|
||||||
|
if(!sizeof($this->_list_product_ids)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->objProductFilterModel->brandList($this->_filters['brand'], $this->_list_product_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function attributeList() {
|
||||||
|
|
||||||
|
return $this->objProductFilterModel->attributeList(
|
||||||
|
$this->_filters['attribute'],
|
||||||
|
$this->_current_category_ids,
|
||||||
|
$this->_list_product_ids
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function priceList() {
|
||||||
|
|
||||||
|
return $this->objProductFilterModel->priceList(
|
||||||
|
$this->_filters['price']['max'] ,
|
||||||
|
$this->_filters['price']['min'],
|
||||||
|
$this->_list_product_ids,
|
||||||
|
$this->_price_range
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,348 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Controller;
|
||||||
|
|
||||||
|
use Hura8\System\Url;
|
||||||
|
|
||||||
|
|
||||||
|
class ProductFilterOptionsTranslationController {
|
||||||
|
|
||||||
|
protected $_request_url = '';
|
||||||
|
protected $_url_elements = [
|
||||||
|
'scheme' => '',
|
||||||
|
'host' => '',
|
||||||
|
'port' => '',
|
||||||
|
'user' => '',
|
||||||
|
'pass' => '',
|
||||||
|
'path' => '',
|
||||||
|
'query' => [],
|
||||||
|
'fragment' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $_price_format = '';
|
||||||
|
protected $_brand_format = '';
|
||||||
|
protected $_filter_format = '';
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct($request_url) {
|
||||||
|
if(defined('PRICE_FILTER_FORMAT') && PRICE_FILTER_FORMAT) {
|
||||||
|
$this->_price_format = PRICE_FILTER_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(defined('BRAND_FILTER_FORMAT') && BRAND_FILTER_FORMAT) {
|
||||||
|
$this->_brand_format = BRAND_FILTER_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(defined('ATTRIBUTE_FILTER_FORMAT') && ATTRIBUTE_FILTER_FORMAT ) {
|
||||||
|
$this->_filter_format = ATTRIBUTE_FILTER_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_request_url = $request_url;
|
||||||
|
$this->_url_elements = Url::parse($request_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getAllFilterOptions(array $category_list = [], array $attribute_list = [], array $brand_list = [], array $price_list_options = []) {
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'category' => $category_list,
|
||||||
|
'price' => $this->translatePriceList($price_list_options),
|
||||||
|
'brand' => $this->translateBrandList($brand_list),
|
||||||
|
'attribute' => $this->translateAttributeList($attribute_list),
|
||||||
|
];
|
||||||
|
|
||||||
|
$selected_categories = array_filter($category_list, function ($item){ return $item['is_selected'];});
|
||||||
|
$selected_prices = array_filter($result['price'], function ($item){ return $item['is_selected'];});
|
||||||
|
$selected_brands = array_filter($result['brand'], function ($item){ return $item['is_selected'];});
|
||||||
|
$selected_attributes = array_filter($result['attribute'], function ($item){ return $item['is_selected'];});
|
||||||
|
|
||||||
|
$filter_messages = [];
|
||||||
|
|
||||||
|
foreach ($selected_categories as $selected_category) {
|
||||||
|
$filter_messages[] = [
|
||||||
|
'name' => $selected_category['name'], // 'Danh mục: '.
|
||||||
|
'reset_url' => Url::buildUrl($this->_request_url, ['category' => ''] ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($selected_prices as $selected_price) {
|
||||||
|
$filter_messages[] = [
|
||||||
|
'name' => $selected_price['name'], // 'Giá: '.
|
||||||
|
'reset_url' => $selected_price['url'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($selected_brands as $selected_brand) {
|
||||||
|
$filter_messages[] = [
|
||||||
|
'name' => $selected_brand['name'], // 'Thương hiệu: '.
|
||||||
|
'reset_url' => $selected_brand['url'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($selected_attributes as $selected_attribute) {
|
||||||
|
|
||||||
|
$selected_attribute_values = array_filter($selected_attribute['value_list'], function ($item){ return $item['is_selected'];});
|
||||||
|
|
||||||
|
foreach ($selected_attribute_values as $selected_attribute_value) {
|
||||||
|
$filter_messages[] = [
|
||||||
|
'name' => $selected_attribute_value['name'], //$selected_attribute['name'].': '.$selected_attribute_value['name'],
|
||||||
|
'reset_url' => $selected_attribute_value['url'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return [$result, $filter_messages];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function translateAttributeList(array $attribute_list = []) {
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
foreach ($attribute_list as $attr_info) {
|
||||||
|
$copy = $attr_info;
|
||||||
|
$copy['value_list'] = array_map(function ($value_info) use ($attr_info) {
|
||||||
|
return $this->translateAttributeValue($attr_info['filter_code'], $value_info);
|
||||||
|
}, $attr_info['value_list']);
|
||||||
|
|
||||||
|
$result[] = $copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function translateAttributeValue($attribute_filter_code, array $value_info = []) {
|
||||||
|
$copy = $value_info;
|
||||||
|
|
||||||
|
if($copy['is_selected']) {
|
||||||
|
$copy['url'] = Url::buildUrl($this->_request_url, $this->buildAttributeValueUrlResetParameters($attribute_filter_code, $value_info['id'], $value_info['api_key']) );
|
||||||
|
}else{
|
||||||
|
$copy['url'] = Url::buildUrl($this->_request_url, $this->buildAttributeValueUrlParameters($attribute_filter_code, $value_info['id'], $value_info['api_key']) );
|
||||||
|
}
|
||||||
|
|
||||||
|
//$copy['reset_url'] = Url::buildUrl($this->_request_url, $this->buildAttributeValueUrlResetParameters($attribute_filter_code, $value_info['id'], $value_info['api_key']) );
|
||||||
|
|
||||||
|
return $copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function buildAttributeValueUrlParameters($attribute_filter_code, $value_id, $value_api_key) {
|
||||||
|
if($this->_filter_format == 'filter_code') {
|
||||||
|
$filter_query = (isset($this->_url_elements['query'][$attribute_filter_code])) ? $this->_url_elements['query'][$attribute_filter_code] : '';
|
||||||
|
$filter_value_list = ($filter_query) ? explode(FILTER_VALUE_SEPARATOR, $filter_query) : [];
|
||||||
|
|
||||||
|
$filter_value_list[] = $value_api_key;
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
$result[$attribute_filter_code] = join(FILTER_VALUE_SEPARATOR, $filter_value_list );
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// default
|
||||||
|
$filter_query = (isset($this->_url_elements['query']['filter'])) ? $this->_url_elements['query']['filter'] : '';
|
||||||
|
$filter_value_list = ($filter_query) ? explode(FILTER_VALUE_SEPARATOR, $filter_query) : [];
|
||||||
|
|
||||||
|
$filter_value_list[] = $value_id;
|
||||||
|
sort($filter_value_list);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'filter' => join(FILTER_VALUE_SEPARATOR, $filter_value_list ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function buildAttributeValueUrlResetParameters($attribute_filter_code, $value_id, $value_api_key) {
|
||||||
|
if($this->_filter_format == 'filter_code') {
|
||||||
|
$filter_query = (isset($this->_url_elements['query'][$attribute_filter_code])) ? $this->_url_elements['query'][$attribute_filter_code] : '';
|
||||||
|
$filter_value_list = ($filter_query) ? explode(FILTER_VALUE_SEPARATOR, $filter_query) : [];
|
||||||
|
|
||||||
|
$filter_value_list = remove_item_from_array($filter_value_list, $value_api_key);
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
$result[$attribute_filter_code] = join(FILTER_VALUE_SEPARATOR, $filter_value_list );
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// default
|
||||||
|
$filter_query = (isset($this->_url_elements['query']['filter'])) ? $this->_url_elements['query']['filter'] : '';
|
||||||
|
$filter_value_list = ($filter_query) ? explode(FILTER_VALUE_SEPARATOR, $filter_query) : [];
|
||||||
|
|
||||||
|
$filter_value_list = remove_item_from_array($filter_value_list, $value_id);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'filter' => join(FILTER_VALUE_SEPARATOR, $filter_value_list ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function translateBrandList(array $brand_list = []) {
|
||||||
|
|
||||||
|
return array_map(function ($item){
|
||||||
|
$copy = $item;
|
||||||
|
|
||||||
|
if(($copy['is_selected'])) {
|
||||||
|
$copy['url'] = Url::buildUrl($this->_request_url, $this->buildBrandUrlResetParameters($item['id'], $item['brand_index']) ) ;
|
||||||
|
}else{
|
||||||
|
$copy['url'] = Url::buildUrl($this->_request_url, $this->buildBrandUrlParameters($item['id'], $item['brand_index']) );
|
||||||
|
}
|
||||||
|
|
||||||
|
//$copy['reset_url'] = Url::buildUrl($this->_request_url, $this->buildBrandUrlResetParameters($item['id'], $item['brand_index']) );
|
||||||
|
//unset($copy['brand_index']);
|
||||||
|
|
||||||
|
return $copy;
|
||||||
|
|
||||||
|
}, $brand_list);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function buildBrandUrlResetParameters($brand_id, $brand_index){
|
||||||
|
$brand_query = (isset($this->_url_elements['query']['brand'])) ? $this->_url_elements['query']['brand'] : '';
|
||||||
|
$brand_list = ($brand_query) ? explode(FILTER_VALUE_SEPARATOR, $brand_query) : [];
|
||||||
|
|
||||||
|
if($this->_brand_format == 'brand_index') {
|
||||||
|
$brand_list = remove_item_from_array($brand_list, $brand_index);
|
||||||
|
}else{
|
||||||
|
$brand_list = remove_item_from_array($brand_list, $brand_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'brand' => join(FILTER_VALUE_SEPARATOR, $brand_list ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function buildBrandUrlParameters($brand_id, $brand_index){
|
||||||
|
$brand_query = (isset($this->_url_elements['query']['brand'])) ? $this->_url_elements['query']['brand'] : '';
|
||||||
|
$brand_list = ($brand_query) ? explode(FILTER_VALUE_SEPARATOR, $brand_query) : [];
|
||||||
|
|
||||||
|
if($this->_brand_format == 'brand_index') {
|
||||||
|
if(!in_array($brand_index, $brand_list)) $brand_list[] = $brand_index;
|
||||||
|
}else{
|
||||||
|
if(!in_array($brand_id, $brand_list)) $brand_list[] = $brand_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'brand' => join(FILTER_VALUE_SEPARATOR, $brand_list ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function translatePriceList(array $price_list_options = []) {
|
||||||
|
|
||||||
|
return array_map(function ($item){
|
||||||
|
$copy = $item;
|
||||||
|
|
||||||
|
$format = $this->buildPriceRangeFormat($item['min'], $item['max']);
|
||||||
|
|
||||||
|
$copy['name'] = $format['name'];
|
||||||
|
$copy['url'] = ($copy['is_selected']) ? Url::buildUrl($this->_request_url, $this->buildPriceUrlResetParameters()) : Url::buildUrl($this->_request_url, $format['url_para']);
|
||||||
|
|
||||||
|
//$copy['reset_url'] = Url::buildUrl($this->_request_url, $this->buildPriceUrlResetParameters());
|
||||||
|
//unset($copy['min'], $copy['max']);
|
||||||
|
|
||||||
|
return $copy;
|
||||||
|
|
||||||
|
}, $price_list_options);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function buildPriceRangeFormat($min_price, $max_price) {
|
||||||
|
if($min_price > 0 && !$max_price) {
|
||||||
|
$title = "Trên ".$this->format_search_price($min_price);
|
||||||
|
}elseif(!$min_price && $max_price > 0) {
|
||||||
|
$title = "Dưới ".$this->format_search_price($max_price);
|
||||||
|
}else{
|
||||||
|
$title = $this->format_search_price($min_price)." - ".$this->format_search_price($max_price);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'url_para' => $this->buildPriceUrlParameters($min_price, $max_price),
|
||||||
|
'name' => $title,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function format_search_price($p_price){
|
||||||
|
|
||||||
|
$price_len = strlen($p_price);
|
||||||
|
|
||||||
|
if($price_len < 7) {
|
||||||
|
return round($p_price/1000,1)." ngàn";
|
||||||
|
}
|
||||||
|
else if($price_len < 10) {
|
||||||
|
return round($p_price/1000000,1)." triệu";
|
||||||
|
}
|
||||||
|
|
||||||
|
return round($p_price/1000000000,1)." tỷ";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function buildPriceUrlResetParameters() {
|
||||||
|
if( $this->_price_format == 'p') {
|
||||||
|
return [
|
||||||
|
"p" => '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"min" => '' ,
|
||||||
|
"max" => '' ,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function buildPriceUrlParameters($min_price = 0, $max_price = 0) {
|
||||||
|
|
||||||
|
if($min_price > 0 && !$max_price) {
|
||||||
|
if( $this->_price_format == 'p') {
|
||||||
|
return [
|
||||||
|
"p" => "tren-".self::convertPriceFormatToFilter($this->format_search_price($min_price)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"min" => $min_price ,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$min_price && $max_price > 0) {
|
||||||
|
|
||||||
|
if( $this->_price_format == 'p') {
|
||||||
|
return [
|
||||||
|
"p" => "duoi-" . self::convertPriceFormatToFilter($this->format_search_price($max_price)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"max" => $max_price,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if( $this->_price_format == 'p') {
|
||||||
|
return [
|
||||||
|
"p" => self::convertPriceFormatToFilter($this->format_search_price($min_price)) . "-" . self::convertPriceFormatToFilter($this->format_search_price($max_price)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"max" => $max_price ,
|
||||||
|
"min" => $min_price,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// convert: 10 ngàn -> 10ngan, 10 triệu => 10trieu
|
||||||
|
protected static function convertPriceFormatToFilter($price_format){
|
||||||
|
$price_format = str_replace(['ngàn', 'triệu'], ['ngan', 'trieu'], $price_format);
|
||||||
|
// remove whitespace
|
||||||
|
return str_replace(" ",'', $price_format);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Controller;
|
||||||
|
|
||||||
|
use Hura8\Components\Product\Model\ProductCategoryLanguageModel;
|
||||||
|
use Hura8\Components\Product\Model\ProductCategoryModel;
|
||||||
|
use Hura8\System\Controller\aCategoryBaseController;
|
||||||
|
|
||||||
|
class bProductCategoryController extends aCategoryBaseController
|
||||||
|
{
|
||||||
|
|
||||||
|
static $image_folder = "media/category";
|
||||||
|
|
||||||
|
protected $objProductCategoryModel;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->objProductCategoryModel = new ProductCategoryModel();
|
||||||
|
|
||||||
|
if(!$this->isDefaultLanguage()) {
|
||||||
|
//$this->objProductCategoryLanguageModel->createTableLang();
|
||||||
|
parent::__construct(
|
||||||
|
$this->objProductCategoryModel,
|
||||||
|
new ProductCategoryLanguageModel()
|
||||||
|
);
|
||||||
|
|
||||||
|
}else{
|
||||||
|
parent::__construct(
|
||||||
|
$this->objProductCategoryModel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function formatItemInfo(?array $info) : ?array
|
||||||
|
{
|
||||||
|
if(!$info) return null;
|
||||||
|
|
||||||
|
if($info['icon']) $info['icon'] = STATIC_DOMAIN . "/" . static::$image_folder . "/" . $info['icon'];
|
||||||
|
|
||||||
|
return $info;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get full info- basic with description
|
||||||
|
public function getFullInfo($id): ?array
|
||||||
|
{
|
||||||
|
$info = $this->objProductCategoryModel->getFullInfo($id);
|
||||||
|
|
||||||
|
if(!$info) return null;
|
||||||
|
|
||||||
|
if($this->iEntityLanguageModel) {
|
||||||
|
$item_language_info = $this->iEntityLanguageModel->getInfo($id);
|
||||||
|
if($item_language_info) {
|
||||||
|
return $this->formatItemInfo(array_merge($info, $item_language_info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->formatItemInfo($info);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Controller;
|
||||||
|
|
||||||
|
|
||||||
|
use Hura8\Components\Product\Model\ProductCollectionLanguageModel;
|
||||||
|
use Hura8\Components\Product\Model\ProductCollectionModel;
|
||||||
|
use Hura8\System\Controller\aCategoryBaseController;
|
||||||
|
|
||||||
|
|
||||||
|
class bProductCollectionController extends aCategoryBaseController
|
||||||
|
{
|
||||||
|
|
||||||
|
static $image_folder = "media/category";
|
||||||
|
|
||||||
|
protected $objProductCollectionModel;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->objProductCollectionModel = new ProductCollectionModel();
|
||||||
|
|
||||||
|
if(!$this->isDefaultLanguage()) {
|
||||||
|
//$this->objProductCategoryLanguageModel->createTableLang();
|
||||||
|
parent::__construct(
|
||||||
|
$this->objProductCollectionModel,
|
||||||
|
new ProductCollectionLanguageModel()
|
||||||
|
);
|
||||||
|
|
||||||
|
}else{
|
||||||
|
parent::__construct(
|
||||||
|
$this->objProductCollectionModel
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getTotalProduct($collection_id, array $condition = [])
|
||||||
|
{
|
||||||
|
return $this->objProductCollectionModel->getTotalProduct($collection_id, $condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getListProduct($collection_id, array $condition = []) {
|
||||||
|
return $this->objProductCollectionModel->getListProduct($collection_id, $condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function formatItemInList(array $info) : array
|
||||||
|
{
|
||||||
|
return $this->formatItemInfo($info);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function formatItemInfo(?array $info) : ?array
|
||||||
|
{
|
||||||
|
$info['url'] = "/collection/".$info['url_index'];
|
||||||
|
return $info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
210
inc/Hura8/Components/Product/Controller/bProductController.php
Normal file
210
inc/Hura8/Components/Product/Controller/bProductController.php
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Controller;
|
||||||
|
|
||||||
|
|
||||||
|
use Hura8\Components\Product\Model\ProductImageModel;
|
||||||
|
use Hura8\Components\Product\Model\ProductLanguageModel;
|
||||||
|
use Hura8\Components\Product\Model\ProductModel;
|
||||||
|
use Hura8\System\Controller\aEntityBaseController;
|
||||||
|
use Hura8\System\Security\DataClean;
|
||||||
|
use Hura8\System\Security\DataType;
|
||||||
|
use Hura8\System\Url;
|
||||||
|
|
||||||
|
|
||||||
|
abstract class bProductController extends aEntityBaseController
|
||||||
|
{
|
||||||
|
|
||||||
|
static $image_folder = "media/product";
|
||||||
|
|
||||||
|
static $resized_sizes = array(
|
||||||
|
't' => ['width' => 100,] ,
|
||||||
|
's' => ['width' => 300,] ,
|
||||||
|
'l' => ['width' => 650,]
|
||||||
|
);
|
||||||
|
|
||||||
|
protected $objProductModel;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->objProductModel = new ProductModel();
|
||||||
|
|
||||||
|
if(!$this->isDefaultLanguage()) {
|
||||||
|
$objProductLanguageModel = new ProductLanguageModel();
|
||||||
|
// $this->objProductLanguageModel->createTableLang();
|
||||||
|
parent::__construct($this->objProductModel, $objProductLanguageModel);
|
||||||
|
}else{
|
||||||
|
parent::__construct($this->objProductModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//get product image list
|
||||||
|
public function productImageList($proId){
|
||||||
|
$objProductImageModel = new ProductImageModel($proId);
|
||||||
|
$result = array();
|
||||||
|
foreach ( $objProductImageModel->getList(["numPerPage" => 100]) as $rs ) {
|
||||||
|
$result[] = [
|
||||||
|
"size" => static::getResizedImageCollection($rs['img_name']),
|
||||||
|
"alt" => $rs['alt'],
|
||||||
|
"folder" => $rs['folder'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductDisplayOptions($current_url) {
|
||||||
|
$allowed_options = array(
|
||||||
|
"list" => "Danh sách",
|
||||||
|
"grid" => "Xem nhóm",
|
||||||
|
"detail" => "Chi tiết"
|
||||||
|
) ;
|
||||||
|
|
||||||
|
$collection = array();
|
||||||
|
foreach($allowed_options as $option => $name){
|
||||||
|
$url = Url::buildUrl($current_url, array("display" => ""));
|
||||||
|
$collection[] = array(
|
||||||
|
"url" => Url::buildUrl($url, array("display" => $option)) ,
|
||||||
|
"key" => $option ,
|
||||||
|
"name" => $name ,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductOtherFilterOptions($current_url) {
|
||||||
|
$allowed_options = array(
|
||||||
|
//"order" => $global_language['sort_by_order'] ,
|
||||||
|
"in-stock" => 'Còn hàng' ,
|
||||||
|
) ;
|
||||||
|
|
||||||
|
$_collection = array();
|
||||||
|
foreach($allowed_options as $option => $_name){
|
||||||
|
$_collection[] = array(
|
||||||
|
"url" => Url::buildUrl($current_url, array("other_filter" => $option, 'page' => '')) ,
|
||||||
|
"key" => $option ,
|
||||||
|
"name" => $_name ,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $_collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductSortOptions($current_url, array $options = []) {
|
||||||
|
//global $global_language;
|
||||||
|
|
||||||
|
if(!sizeof($options)) $options = array(
|
||||||
|
//"order" => $global_language['sort_by_order'] ,
|
||||||
|
"new" => "Mới nhất" ,
|
||||||
|
"price-asc" => "Giá tăng dần", //$global_language['sort_by_price_asc'] ,
|
||||||
|
"price-desc" => "Giá giảm dần", //$global_language['sort_by_price_desc'] ,
|
||||||
|
"view" => "Lượt xem", //$global_language['sort_by_view'],
|
||||||
|
"comment" => "Trao đổi", //$global_language['sort_by_comment'] ,
|
||||||
|
"rating" => "Đánh giá", //$global_language['sort_by_rating'] ,
|
||||||
|
"name" => "Tên A->Z" ,
|
||||||
|
) ;
|
||||||
|
|
||||||
|
$sort_by_collection = array();
|
||||||
|
foreach($options as $sort_option=>$sort_name){
|
||||||
|
// $url = build_url($current_url, array("sort" => ""));
|
||||||
|
$sort_by_collection[] = array(
|
||||||
|
"url" => Url::buildUrl($current_url, array("sort" => $sort_option)) ,
|
||||||
|
"key" => $sort_option ,
|
||||||
|
"name" => $sort_name ,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $sort_by_collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductInfoBySKU($sku) {
|
||||||
|
|
||||||
|
return self::getCache(md5("getFullInfo-".$sku), function () use ($sku) {
|
||||||
|
|
||||||
|
$info = $this->objProductModel->getProductInfoBySKU($sku);
|
||||||
|
|
||||||
|
return ($info) ? $this->formatItemInfo($info) : null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// get full info- basic with description
|
||||||
|
public function getFullInfo($id)
|
||||||
|
{
|
||||||
|
return self::getCache("getFullInfo-".$id, function () use ($id) {
|
||||||
|
|
||||||
|
$info = $this->objProductModel->getFullInfo($id);
|
||||||
|
|
||||||
|
if($this->iEntityLanguageModel && $info) {
|
||||||
|
$item_language_info = $this->iEntityLanguageModel->getInfo($id);
|
||||||
|
if($item_language_info) {
|
||||||
|
return $this->formatItemInfo(array_merge($info, $item_language_info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($info) ? $this->formatItemInfo($info) : null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function validateAndCleanFilterCondition(array $raw_filter_condition): array
|
||||||
|
{
|
||||||
|
$clean_values = parent::validateAndCleanFilterCondition($raw_filter_condition);
|
||||||
|
|
||||||
|
// special cases for 'price' which is in range
|
||||||
|
if(array_key_exists('price', $raw_filter_condition)) {
|
||||||
|
$value = $raw_filter_condition['price'];
|
||||||
|
// expect $value = array('max' => 0, 'min'=> 0)
|
||||||
|
if(isset($value['max']) && isset($value['min'])) {
|
||||||
|
$clean_values['price'] = array(
|
||||||
|
'max' => DataClean::makeInputSafe($value['max'], DataType::INTEGER),
|
||||||
|
'min' => DataClean::makeInputSafe($value['min'], DataType::INTEGER),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $clean_values;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 = $item_info;
|
||||||
|
|
||||||
|
$info['image'] = static::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 : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$image['original'] = ($image_name) ? STATIC_DOMAIN . "/". static::$image_folder . "/". $image_name : '';
|
||||||
|
|
||||||
|
return $image;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\System\Model\EntityLanguageModel;
|
||||||
|
use Hura8\Interfaces\EntityType;
|
||||||
|
|
||||||
|
|
||||||
|
class ProductAttributeLanguageModel extends EntityLanguageModel
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct(EntityType::PRODUCT_ATTRIBUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
175
inc/Hura8/Components/Product/Model/ProductAttributeModel.php
Normal file
175
inc/Hura8/Components/Product/Model/ProductAttributeModel.php
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\AppResponse;
|
||||||
|
use Hura8\System\Language;
|
||||||
|
use Hura8\System\Model\aEntityBaseModel;
|
||||||
|
use Hura8\Interfaces\iEntityModel;
|
||||||
|
use Hura8\Interfaces\EntityType;
|
||||||
|
use Hura8\System\Security\DataClean;
|
||||||
|
use Hura8\System\Security\DataType;
|
||||||
|
|
||||||
|
class ProductAttributeModel extends aEntityBaseModel {
|
||||||
|
|
||||||
|
protected $tb_product_category = "tb_product_category";
|
||||||
|
protected $tb_attribute_value = "tb_attribute_value";
|
||||||
|
|
||||||
|
protected $tb_product_spec_group = "tb_product_spec_group";
|
||||||
|
protected $tb_attribute_per_spec_group = "tb_attribute_per_spec_group";
|
||||||
|
protected $tb_attribute_per_category = "tb_attribute_per_category";
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct(EntityType::PRODUCT_ATTRIBUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function extendedFilterOptions() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// empty for now
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPEC-GROUP
|
||||||
|
|
||||||
|
public function getSpecGroupAttributeWithValues($group_id) {
|
||||||
|
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"SELECT
|
||||||
|
a.title,
|
||||||
|
a.summary,
|
||||||
|
a.is_filter,
|
||||||
|
a.value_match_all,
|
||||||
|
a.filter_code,
|
||||||
|
a.is_display,
|
||||||
|
a.is_header,
|
||||||
|
a.is_multi, a.in_summary,
|
||||||
|
a.value_count,
|
||||||
|
g.attr_id,
|
||||||
|
g.ordering,
|
||||||
|
g.status
|
||||||
|
FROM ".$this->tb_attribute_per_spec_group." g, ".$this->tb_entity." a
|
||||||
|
WHERE g.`group_id`= ? AND g.`attr_id`= a.`id`
|
||||||
|
ORDER BY g.`ordering` DESC, a.`ordering` DESC ",
|
||||||
|
['d'],
|
||||||
|
[$group_id]
|
||||||
|
) ;
|
||||||
|
|
||||||
|
$attribute_ids = [];
|
||||||
|
$value_per_attribute_ids = [];
|
||||||
|
$result = [];
|
||||||
|
foreach ($this->db->fetchAll($query) as $item) {
|
||||||
|
$attribute_ids[] = $item['attr_id'];
|
||||||
|
$value_per_attribute_ids[$item['attr_id']] = [];
|
||||||
|
|
||||||
|
$result[$item['attr_id']] = [
|
||||||
|
"attribute_info" => $item,
|
||||||
|
"attribute_values" => &$value_per_attribute_ids[$item['attr_id']],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(!sizeof($attribute_ids)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// now get all values for each attribute
|
||||||
|
list($parameterized_ids, $bind_types) = create_bind_sql_parameter_from_value_list($attribute_ids, "int");
|
||||||
|
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"SELECT * FROM ".$this->tb_attribute_value."
|
||||||
|
WHERE `attribute_id` IN (".$parameterized_ids.")
|
||||||
|
ORDER BY `ordering` DESC, `title` ASC ",
|
||||||
|
$bind_types,
|
||||||
|
$attribute_ids
|
||||||
|
) ;
|
||||||
|
|
||||||
|
foreach ($this->db->fetchAll($query) as $item) {
|
||||||
|
$value_per_attribute_ids[$item['attribute_id']][] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getSpecGroupAttribute($group_id) {
|
||||||
|
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"SELECT
|
||||||
|
a.title,
|
||||||
|
a.summary,
|
||||||
|
a.is_filter,
|
||||||
|
a.value_match_all,
|
||||||
|
a.filter_code,
|
||||||
|
a.is_display,
|
||||||
|
a.is_header, a.is_multi, a.value_count,
|
||||||
|
g.attr_id,
|
||||||
|
g.ordering,
|
||||||
|
g.status
|
||||||
|
FROM ".$this->tb_attribute_per_spec_group." g, ".$this->tb_entity." a
|
||||||
|
WHERE g.`group_id`= ? AND g.`attr_id`= a.`id`
|
||||||
|
ORDER BY g.`ordering` DESC, a.`ordering` DESC ",
|
||||||
|
['d'],
|
||||||
|
[$group_id]
|
||||||
|
) ;
|
||||||
|
|
||||||
|
return $this->db->fetchAll($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductMatchAllAttributeValueIds(array $_att_value_list) {
|
||||||
|
/*$query = $this->db->runQuery("
|
||||||
|
SELECT `pro_id` , COUNT(*) AS num_pro
|
||||||
|
FROM ".TB_PRODUCT_ATTRIBUTE."
|
||||||
|
WHERE " . join(" OR ", array_map(function ($_item){ return " `attr_value_id` = '".intval($_item['id'])."' "; }, $_att_value_list )) ."
|
||||||
|
GROUP BY pro_id
|
||||||
|
HAVING num_pro = ".sizeof($_att_value_list)."
|
||||||
|
LIMIT 10000
|
||||||
|
");
|
||||||
|
|
||||||
|
return array_map(function ($item){
|
||||||
|
return $item["pro_id"];
|
||||||
|
}, $this->db->fetchAll($query));*/
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProductMatchAttributeValue(array $value_ids) {
|
||||||
|
$product_filter_id_match = array();
|
||||||
|
// todo:
|
||||||
|
/*if(sizeof($query_attr_id)) {
|
||||||
|
$query = $this->db->runQuery("
|
||||||
|
SELECT DISTINCT pro_id , COUNT(*) as num_pro
|
||||||
|
FROM ".TB_PRODUCT_ATTRIBUTE."
|
||||||
|
WHERE attr_value_id IN (".join(',', $query_attr_id).")
|
||||||
|
GROUP BY pro_id
|
||||||
|
HAVING num_pro = ".$count_filter."
|
||||||
|
LIMIT 10000
|
||||||
|
");
|
||||||
|
foreach ( $this->db->fetchAll($query) as $rs ) {
|
||||||
|
$product_filter_id_match[] = $rs["pro_id"];
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return $product_filter_id_match;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _buildQueryConditionExtend(array $filter_condition) : ?array
|
||||||
|
{
|
||||||
|
/*$condition = array(
|
||||||
|
"q" => "",
|
||||||
|
"letter" => "",
|
||||||
|
"status" => 0,
|
||||||
|
);*/
|
||||||
|
|
||||||
|
$catCondition = [];
|
||||||
|
$bind_types = [];
|
||||||
|
$bind_values = [];
|
||||||
|
|
||||||
|
return array( join(" ", $catCondition), $bind_types, $bind_values);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\AppResponse;
|
||||||
|
use Hura8\System\Model\aEntityBaseModel;
|
||||||
|
use Hura8\Interfaces\EntityType;
|
||||||
|
use Hura8\System\Security\DataClean;
|
||||||
|
use Hura8\System\Security\DataType;
|
||||||
|
|
||||||
|
|
||||||
|
class ProductAttributeValueModel extends aEntityBaseModel
|
||||||
|
{
|
||||||
|
|
||||||
|
const MAX_VALUE_PER_ATTRIBUTE = 100; // one attribute should not have more that this number of values
|
||||||
|
|
||||||
|
protected $attribute_id = 0;
|
||||||
|
|
||||||
|
protected $tb_attribute = 'tb_attribute';
|
||||||
|
protected $tb_product_attribute = 'tb_product_attribute';
|
||||||
|
|
||||||
|
public function __construct($attribute_id) {
|
||||||
|
parent::__construct(EntityType::PRODUCT_ATTRIBUTE_VALUE);
|
||||||
|
$this->attribute_id = $attribute_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function extendedFilterOptions() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// empty for now
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductAttributes($product_id) {
|
||||||
|
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"SELECT `attr_id`, `attr_value_id` FROM `".$this->tb_product_attribute."` WHERE `pro_id` = ? ",
|
||||||
|
['d'], [ $product_id]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->db->fetchAll($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function getAttributeValueCount() {
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"SELECT `value_count` FROM `".$this->tb_attribute."` WHERE `id` = ? LIMIT 1 ",
|
||||||
|
['d'], [$this->attribute_id]
|
||||||
|
);
|
||||||
|
|
||||||
|
if($info = $this->db->fetchAssoc($query)) {
|
||||||
|
return $info['value_count'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _buildQueryConditionExtend(array $filter_condition) : ?array
|
||||||
|
{
|
||||||
|
|
||||||
|
$catCondition = [" AND `attribute_id` = ? "];
|
||||||
|
$bind_types = ["d"];
|
||||||
|
$bind_values = [$this->attribute_id];
|
||||||
|
|
||||||
|
|
||||||
|
return array( join(" ", $catCondition), $bind_types, $bind_values);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getInfoByCode($filter_code)
|
||||||
|
{
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"SELECT * FROM `".$this->tb_entity."` WHERE `attribute_id` = ? AND `filter_code` = ? LIMIT 1",
|
||||||
|
['d', 's'], [$this->attribute_id, $filter_code ]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->db->fetchAssoc($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\Database\iConnectDB;
|
||||||
|
|
||||||
|
class ProductCategoryInfoModel
|
||||||
|
{
|
||||||
|
/* @var $db iConnectDB */
|
||||||
|
protected $db;
|
||||||
|
|
||||||
|
protected $tb_category_info = "tb_product_category_info";
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = get_db();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInfo($id) {
|
||||||
|
return $this->db->select(
|
||||||
|
$this->tb_category_info,
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
'id' => ['=', $id],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
|
||||||
|
use Hura8\System\Model\EntityLanguageModel;
|
||||||
|
use Hura8\Interfaces\EntityType;
|
||||||
|
|
||||||
|
|
||||||
|
class ProductCategoryLanguageModel extends EntityLanguageModel
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $richtext_fields = [
|
||||||
|
'description',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct(EntityType::PRODUCT_CATEGORY, '', $this->richtext_fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
85
inc/Hura8/Components/Product/Model/ProductCategoryModel.php
Normal file
85
inc/Hura8/Components/Product/Model/ProductCategoryModel.php
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
|
||||||
|
use Hura8\Interfaces\AppResponse;
|
||||||
|
use Hura8\System\Controller\UrlManagerController;
|
||||||
|
use Hura8\System\Model\aCategoryBaseModel;
|
||||||
|
use Hura8\System\ModuleManager;
|
||||||
|
use Hura8\Interfaces\EntityType;
|
||||||
|
|
||||||
|
|
||||||
|
class ProductCategoryModel extends aCategoryBaseModel
|
||||||
|
{
|
||||||
|
static $url_module = "product";
|
||||||
|
static $url_view = "category";
|
||||||
|
static $url_type = "product:category";
|
||||||
|
|
||||||
|
protected $tb_category_info = "tb_product_category_info";
|
||||||
|
protected $tb_product_per_category = "tb_product_per_category";
|
||||||
|
protected $tb_attribute_per_category = "tb_attribute_per_category";
|
||||||
|
protected $tb_attribute_value = "tb_attribute_value";
|
||||||
|
protected $tb_attribute = "tb_attribute";
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct(EntityType::PRODUCT_CATEGORY);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getAttributeList($catId) {
|
||||||
|
$query = $this->db->runQuery("SELECT
|
||||||
|
a.id ,
|
||||||
|
a.filter_code ,
|
||||||
|
a.title ,
|
||||||
|
a.value_count ,
|
||||||
|
a.is_filter ,
|
||||||
|
c.ordering ,
|
||||||
|
c.status ,
|
||||||
|
a.is_header
|
||||||
|
FROM ".$this->tb_attribute." a
|
||||||
|
LEFT JOIN ".$this->tb_attribute_per_category." c ON a.id = c.attr_id
|
||||||
|
WHERE c.category_id = ?
|
||||||
|
ORDER BY c.ordering desc
|
||||||
|
", ['d'], [ $catId ]) ; // AND isSearch = 1
|
||||||
|
|
||||||
|
return $this->db->fetchAll($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getFullInfo($id) : ?array {
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"SELECT * FROM `".$this->tb_entity."` basic, `".$this->tb_category_info."` info
|
||||||
|
WHERE basic.`id` = info.`id` AND basic.id = ?
|
||||||
|
LIMIT 1 ",
|
||||||
|
['d'], [$id]
|
||||||
|
);
|
||||||
|
|
||||||
|
if( $item_info = $this->db->fetchAssoc($query)){
|
||||||
|
$item_info['settings'] = ($item_info['settings']) ? \json_decode($item_info['settings'], true) : [];
|
||||||
|
return $item_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getEmptyInfo($addition_field_value = []) : array
|
||||||
|
{
|
||||||
|
// basic table
|
||||||
|
$basic_empty = parent::getEmptyInfo($addition_field_value);
|
||||||
|
|
||||||
|
// info table
|
||||||
|
foreach ($this->db->getTableInfo($this->tb_category_info) as $field => $field_info) {
|
||||||
|
$basic_empty[$field] = ( in_array($field_info['DATA_TYPE'], ['int', 'float']) ) ? 0 : '' ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sizeof($addition_field_value)) {
|
||||||
|
return array_merge($basic_empty, $addition_field_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $basic_empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\EntityType;
|
||||||
|
use Hura8\System\Model\EntityLanguageModel;
|
||||||
|
|
||||||
|
|
||||||
|
class ProductCollectionLanguageModel extends EntityLanguageModel
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $richtext_fields = [
|
||||||
|
'description',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct(EntityType::PRODUCT_COLLECTION, '', $this->richtext_fields);
|
||||||
|
|
||||||
|
//$this->createTableLang();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\EntityType;
|
||||||
|
use Hura8\System\Model\aCategoryBaseModel;
|
||||||
|
|
||||||
|
|
||||||
|
class ProductCollectionModel extends aCategoryBaseModel
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $tb_collection_product = "tb_collection_product";
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct(EntityType::PRODUCT_COLLECTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getInfoByUrl($url): ?array
|
||||||
|
{
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
" SELECT * FROM `".$this->tb_entity."` WHERE `url_index` = ? LIMIT 1",
|
||||||
|
['s'], [$url]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->db->fetchAssoc($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalProduct($collection_id, array $condition = [])
|
||||||
|
{
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
" SELECT COUNT(*) as total FROM `".$this->tb_collection_product."` WHERE `collection_id` = ? ",
|
||||||
|
['d'], [$collection_id]
|
||||||
|
);
|
||||||
|
|
||||||
|
$total = 0;
|
||||||
|
if ($rs = $this->db->fetchAssoc($query)) {
|
||||||
|
$total = $rs['total'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $total;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getListProduct($collection_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 = " `ordering` DESC, `id` DESC";
|
||||||
|
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"SELECT `product_id`, `ordering` FROM ".$this->tb_collection_product." WHERE `collection_id` = ?
|
||||||
|
ORDER BY ".$order_by."
|
||||||
|
LIMIT ".(($page-1) * $numPerPage).", ".$numPerPage ,
|
||||||
|
['d'], [$collection_id]
|
||||||
|
) ;
|
||||||
|
|
||||||
|
$item_list = array();
|
||||||
|
$counter = ($page-1) * $numPerPage;
|
||||||
|
foreach ( $this->db->fetchAll($query) as $item ) {
|
||||||
|
$counter += 1;
|
||||||
|
|
||||||
|
$item_list[$item['product_id']] = [
|
||||||
|
'counter' => $counter,
|
||||||
|
'ordering' => $item['ordering'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$objProductModel = new ProductModel();
|
||||||
|
$product_list_info = $objProductModel->getListByIds(array_keys($item_list));
|
||||||
|
|
||||||
|
// final list
|
||||||
|
$final_list = [];
|
||||||
|
foreach ($item_list as $_pro_id => $_pro_info_in_collection) {
|
||||||
|
$pro_basic = $product_list_info[$_pro_id] ?? null;
|
||||||
|
if($pro_basic) {
|
||||||
|
$final_list[] = array_merge($pro_basic, $_pro_info_in_collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $final_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
452
inc/Hura8/Components/Product/Model/ProductFilterModel.php
Normal file
452
inc/Hura8/Components/Product/Model/ProductFilterModel.php
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\Components\Brand\Model\BrandModel;
|
||||||
|
use Hura8\Database\iConnectDB;
|
||||||
|
use Hura8\Interfaces\TableName;
|
||||||
|
use Hura8\Traits\ClassCacheTrait;
|
||||||
|
|
||||||
|
|
||||||
|
class ProductFilterModel
|
||||||
|
{
|
||||||
|
|
||||||
|
use ClassCacheTrait;
|
||||||
|
|
||||||
|
/* @var $db iConnectDB */
|
||||||
|
protected $db;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = get_db('', ENABLE_DB_DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function findProductListIdMatchFilters(array $SQLConditionFromFilters) {
|
||||||
|
list(
|
||||||
|
$where_query,
|
||||||
|
$current_page_id,
|
||||||
|
$number_per_page,
|
||||||
|
$ordering_clause
|
||||||
|
) = $SQLConditionFromFilters;
|
||||||
|
|
||||||
|
$db_condition = join(" ", $where_query);
|
||||||
|
|
||||||
|
$total_number = 0;
|
||||||
|
$query = $this->db->runQuery("SELECT COUNT(`id`) AS total FROM ".TableName::PRODUCT." WHERE 1 ".$db_condition." ");
|
||||||
|
if($rs = $this->db->fetchAssoc($query)) {
|
||||||
|
$total_number = $rs['total'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = $this->db->runQuery("
|
||||||
|
SELECT * FROM ".TableName::PRODUCT."
|
||||||
|
WHERE 1 ".$db_condition."
|
||||||
|
".$ordering_clause."
|
||||||
|
LIMIT ". ($current_page_id - 1) * $number_per_page .", ".$number_per_page."
|
||||||
|
");
|
||||||
|
|
||||||
|
$item_list = $this->db->fetchAll($query);
|
||||||
|
|
||||||
|
return [$total_number, $item_list];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function ratingList(array $list_product_ids) {
|
||||||
|
|
||||||
|
if(!sizeof($list_product_ids)) return [];
|
||||||
|
|
||||||
|
list($parameterized_ids, $bind_types) = create_bind_sql_parameter_from_value_list($list_product_ids, 'int');
|
||||||
|
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"
|
||||||
|
SELECT `review_rate`, COUNT(`id`) as num
|
||||||
|
FROM ".TableName::PRODUCT."
|
||||||
|
WHERE `id` IN (".$parameterized_ids.")
|
||||||
|
GROUP BY `review_rate`
|
||||||
|
ORDER BY review_rate ASC
|
||||||
|
",
|
||||||
|
$bind_types,
|
||||||
|
$list_product_ids
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->db->fetchAll($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function categoryList(array $current_category_ids, array $list_product_ids) {
|
||||||
|
if(!sizeof($list_product_ids)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
list($parameterized_ids, $bind_types) = create_bind_sql_parameter_from_value_list($list_product_ids, 'int');
|
||||||
|
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"
|
||||||
|
SELECT `category_id`, COUNT(`item_id`) AS numPro
|
||||||
|
FROM ".TableName::PRODUCT_PER_CATEGORY."
|
||||||
|
WHERE `item_id` IN (".$parameterized_ids.")
|
||||||
|
GROUP BY `category_id` ",
|
||||||
|
$bind_types,
|
||||||
|
$list_product_ids
|
||||||
|
);
|
||||||
|
|
||||||
|
//lay ket qua dem
|
||||||
|
$result_count = [];
|
||||||
|
foreach ($this->db->fetchAll($query) as $rs ){
|
||||||
|
$result_count[$rs['category_id']] = $rs['numPro'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sizeof($result_count)) {
|
||||||
|
|
||||||
|
$objProductCategoryModel = new ProductCategoryModel();
|
||||||
|
|
||||||
|
$final_result = [];
|
||||||
|
foreach ($objProductCategoryModel->getListByIds(array_keys($result_count)) as $rs ){
|
||||||
|
|
||||||
|
$rs['count'] = $result_count[$rs['id']];
|
||||||
|
$rs['is_selected'] = in_array($rs['id'], $current_category_ids);
|
||||||
|
|
||||||
|
$final_result[] = $rs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $final_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function brandList(array $current_brand_ids, array $list_product_ids) {
|
||||||
|
|
||||||
|
if(!sizeof($list_product_ids)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
list($parameterized_ids, $bind_types) = create_bind_sql_parameter_from_value_list($list_product_ids, 'int');
|
||||||
|
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
" SELECT `brand_id`, COUNT(`id`) AS total
|
||||||
|
FROM ".TableName::PRODUCT."
|
||||||
|
WHERE `id` IN (".$parameterized_ids.")
|
||||||
|
GROUP BY `brand_id`
|
||||||
|
ORDER BY total DESC
|
||||||
|
LIMIT 100",
|
||||||
|
$bind_types,
|
||||||
|
$list_product_ids
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
$neededList = array();
|
||||||
|
foreach ( $this->db->fetchAll($query) as $result ) {
|
||||||
|
$neededList[$result['brand_id']] = array(
|
||||||
|
"id" => $result['brand_id'] ,
|
||||||
|
"count" => $result['total'] ,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$brand_list_ids[] = array_keys($neededList);
|
||||||
|
$result_list = array();
|
||||||
|
|
||||||
|
if(sizeof($brand_list_ids)) {
|
||||||
|
|
||||||
|
$objBrandModel = new BrandModel();
|
||||||
|
|
||||||
|
foreach ( $objBrandModel->getListByIds($brand_list_ids) as $_brand ) {
|
||||||
|
|
||||||
|
$item_count = (isset($neededList[$_brand['id']])) ? $neededList[$_brand['id']]['count'] : 0;
|
||||||
|
|
||||||
|
$_brand['count'] = $item_count;
|
||||||
|
$_brand["is_selected"] = in_array($_brand['id'], $current_brand_ids);
|
||||||
|
|
||||||
|
|
||||||
|
$result_list[$_brand['id']] = $_brand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values($result_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function attributeList(array $current_filter_attribute_value_ids, array $current_category_ids, array $list_product_ids) {
|
||||||
|
if(!sizeof($list_product_ids)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$category_attribute_filter_array = $this->countNumProPerAttributeForCategory($current_category_ids, $list_product_ids);
|
||||||
|
|
||||||
|
if(!sizeof($category_attribute_filter_array)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$filter_box = array();
|
||||||
|
|
||||||
|
foreach($category_attribute_filter_array as $attributeIdGroup){
|
||||||
|
|
||||||
|
$attribute_value_array = $attributeIdGroup['value_list'];
|
||||||
|
$attribute_name = $attributeIdGroup['name'];
|
||||||
|
$attribute_selected = 0;
|
||||||
|
$filterList = array();
|
||||||
|
|
||||||
|
$count_num = 0;
|
||||||
|
|
||||||
|
foreach($attribute_value_array as $att_value_id => $rs_value){
|
||||||
|
$att_value_name = $rs_value["info"]["name"];
|
||||||
|
$att_value_description = $rs_value["info"]["description"];
|
||||||
|
$count_pro = $rs_value["pro_count"];
|
||||||
|
$is_selected = in_array($att_value_id, $current_filter_attribute_value_ids);
|
||||||
|
if($is_selected) {
|
||||||
|
$attribute_selected = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($this_filter_query, $this_filter_col_value);
|
||||||
|
|
||||||
|
//gioi han danh sach
|
||||||
|
if($count_num > 50) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$count_num++;
|
||||||
|
|
||||||
|
$filterList[] = array(
|
||||||
|
"id" => $att_value_id ,
|
||||||
|
"name" => $att_value_name ,
|
||||||
|
'api_key' => $rs_value["info"]["api_key"],
|
||||||
|
"description" => $att_value_description,
|
||||||
|
"count" => $count_pro ,
|
||||||
|
"is_selected" => $is_selected,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($count_num > 0){
|
||||||
|
$filter_box[] = [
|
||||||
|
"name" => $attribute_name,
|
||||||
|
'filter_code' => $attributeIdGroup['filter_code'],
|
||||||
|
"is_selected" => $attribute_selected,
|
||||||
|
"value_list" => $filterList ,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filter_box;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function countNumProPerAttributeForCategory(array $current_category_ids, array $list_product_ids){
|
||||||
|
|
||||||
|
if(!sizeof($list_product_ids)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$category_attributes = $this->getAttributesForCategory($current_category_ids);
|
||||||
|
$att_value_id_list = [];
|
||||||
|
foreach ($category_attributes as $attribute) {
|
||||||
|
$att_value_id_list = array_merge($att_value_id_list, array_keys($attribute['value_list']));
|
||||||
|
}
|
||||||
|
|
||||||
|
// count product per attribute-value-id
|
||||||
|
$result_count = array();
|
||||||
|
if(sizeof($att_value_id_list)) {
|
||||||
|
$query = $this->db->runQuery("
|
||||||
|
SELECT
|
||||||
|
pro_att.`attr_value_id`,
|
||||||
|
COUNT(DISTINCT ".TB_PRODUCT_CATEGORY.".pro_id) AS numPro
|
||||||
|
FROM ".TB_PRODUCT_ATTRIBUTE." pro_att
|
||||||
|
LEFT JOIN ".TB_PRODUCT_CATEGORY." ON pro_att.pro_id = ".TB_PRODUCT_CATEGORY.".pro_id
|
||||||
|
WHERE pro_att.pro_id IN (".join(',', $list_product_ids ).")
|
||||||
|
AND pro_att.`attr_value_id` IN (".join(",", $att_value_id_list).")
|
||||||
|
GROUP BY pro_att.`attr_value_id` ");
|
||||||
|
|
||||||
|
//lay ket qua dem
|
||||||
|
foreach ($this->db->fetchAll($query) as $rs ){
|
||||||
|
$result_count[$rs['attr_value_id']] = $rs['numPro'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$final_result = [];
|
||||||
|
foreach ($category_attributes as $attribute) {
|
||||||
|
$new_value_list = [];
|
||||||
|
foreach ($attribute['value_list'] as $_value_id => $_value_info) {
|
||||||
|
$pro_count = isset($result_count[$_value_id]) ? $result_count[$_value_id] : 0;
|
||||||
|
if($pro_count) {
|
||||||
|
$_value_info['pro_count'] = $pro_count;
|
||||||
|
$new_value_list[$_value_id] = $_value_info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$attribute['value_list'] = $new_value_list;
|
||||||
|
$final_result[] = $attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $final_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 15-07-2020 get all attributes and values for a list of category
|
||||||
|
protected function getAttributesForCategory(array $category_ids){
|
||||||
|
|
||||||
|
if(!sizeof($category_ids)) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$final_result = array();//array to return
|
||||||
|
|
||||||
|
$category_att_id_list = array();
|
||||||
|
$attribute_array = array(); //keep & re-order attribute
|
||||||
|
|
||||||
|
list($parameterized_ids, $bind_types) = create_bind_sql_parameter_from_value_list($category_ids, 'int');
|
||||||
|
|
||||||
|
$query = $this->db->runQuery("
|
||||||
|
SELECT `attr_id`, `ordering` FROM `idv_attribute_category`
|
||||||
|
WHERE `category_id` IN (". $parameterized_ids .") AND `status` = 1 ", $bind_types, $category_ids);
|
||||||
|
|
||||||
|
foreach ($this->db->fetchAll($query) as $rs ){
|
||||||
|
$category_att_id_list[] = $rs['attr_id'];
|
||||||
|
$attribute_array[$rs['attr_id']] = $rs['ordering'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!sizeof($category_att_id_list)) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$att_value_id_list = array();
|
||||||
|
$attribute_info = array();
|
||||||
|
$attribute_att_value_item = array();
|
||||||
|
$attribute_value_info = array();
|
||||||
|
$query = $this->db->runQuery("
|
||||||
|
SELECT
|
||||||
|
attval.id ,
|
||||||
|
attval.attributeId ,
|
||||||
|
attval.value ,
|
||||||
|
attval.description ,
|
||||||
|
attval.api_key ,
|
||||||
|
attval.value_en ,
|
||||||
|
att.attribute_code ,
|
||||||
|
att.filter_code,
|
||||||
|
att.name ,
|
||||||
|
att.ordering
|
||||||
|
FROM ".TB_ATTRIBUTE_VALUE." attval
|
||||||
|
LEFT JOIN ".TB_ATTRIBUTE." att ON attval.attributeId = att.id
|
||||||
|
WHERE att.id IN (".join(',', $category_att_id_list).") AND att.isSearch = 1
|
||||||
|
ORDER BY attval.ordering DESC ");
|
||||||
|
|
||||||
|
foreach ($this->db->fetchAll($query) as $rs ){
|
||||||
|
$att_value_id = $rs['id'];
|
||||||
|
$att_id = $rs['attributeId'];
|
||||||
|
|
||||||
|
$att_value_id_list[] = $att_value_id;
|
||||||
|
|
||||||
|
$attribute_att_value_item[$att_id][] = $att_value_id; //in ordering
|
||||||
|
|
||||||
|
$attribute_info[$att_id] = array(
|
||||||
|
"id" => $att_id,
|
||||||
|
"name" => $rs['name'],
|
||||||
|
"code" => $rs['attribute_code'],
|
||||||
|
"filter_code" => $rs['filter_code'],
|
||||||
|
);
|
||||||
|
$attribute_value_info[$att_value_id] = array(
|
||||||
|
"id" => $att_value_id,
|
||||||
|
"name" => $rs['value'],
|
||||||
|
"description" => $rs['description'],
|
||||||
|
"api_key" => $rs['api_key'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//cho ra ket qua
|
||||||
|
arsort($attribute_array); //sap xep thu tu cua attribute theo ordering
|
||||||
|
|
||||||
|
foreach($attribute_array as $att_id => $att_order){
|
||||||
|
$this_attribute_atr_value_item = array();
|
||||||
|
if(isset($attribute_att_value_item[$att_id])) {
|
||||||
|
foreach($attribute_att_value_item[$att_id] as $att_value_id){
|
||||||
|
$this_attribute_atr_value_item[$att_value_id] = array(
|
||||||
|
"id" => $att_value_id,
|
||||||
|
"info" => $attribute_value_info[$att_value_id],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($attribute_info[$att_id])) {
|
||||||
|
$info = $attribute_info[$att_id];
|
||||||
|
$info['value_list'] = $this_attribute_atr_value_item;
|
||||||
|
|
||||||
|
$final_result[] = $info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $final_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function priceList($current_max_price, $current_min_price, array $list_product_ids, array $category_price_range) {
|
||||||
|
|
||||||
|
if(!sizeof($list_product_ids)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$total_prices = sizeof($category_price_range);
|
||||||
|
if( ! $total_prices ){
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
//pad head and tail with 0
|
||||||
|
array_unshift($category_price_range, 0);
|
||||||
|
$category_price_range[] = 0;
|
||||||
|
|
||||||
|
$result = array();
|
||||||
|
$queries = [];
|
||||||
|
|
||||||
|
for($i = 0; $i <= $total_prices ; $i++){
|
||||||
|
|
||||||
|
$range_key = md5($category_price_range[$i] .'-'. $category_price_range[$i+1] );
|
||||||
|
$queries[] = $this->buildRangeQuery($list_product_ids, $range_key, $category_price_range[$i], $category_price_range[$i+1]);
|
||||||
|
|
||||||
|
$min_price = intval($category_price_range[$i]);
|
||||||
|
$max_price = intval($category_price_range[$i+1]);
|
||||||
|
$is_selected = ($max_price == $current_max_price && $min_price == $current_min_price );
|
||||||
|
|
||||||
|
$result[$range_key] = array(
|
||||||
|
"min" => $min_price ,
|
||||||
|
"max" => $max_price,
|
||||||
|
"count" => 0 ,
|
||||||
|
"is_selected" => ($is_selected) ? 1 : 0 ,
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = $this->db->runQuery(" SELECT `range_key`, `total` FROM ( ".join(' UNION ALL ', $queries)." ) AS tmp");
|
||||||
|
foreach ($this->db->fetchAll($query) as $rs) {
|
||||||
|
$result[$rs['range_key']]['count'] = $rs['total'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// get only range with product-count > 0
|
||||||
|
return array_values(array_filter( $result , function ($item){
|
||||||
|
return $item['count'] > 0;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function buildRangeQuery(array $list_product_ids, $range_key, $minPrice, $maxPrice){
|
||||||
|
|
||||||
|
$minPrice = (int) $minPrice;
|
||||||
|
$maxPrice = (int) $maxPrice;
|
||||||
|
if($minPrice < 10) $minPrice = 10;
|
||||||
|
|
||||||
|
$new_price_range = " AND `id` IN (".join(',', $list_product_ids ).") ";
|
||||||
|
|
||||||
|
if($maxPrice > 0 && $minPrice > 0){
|
||||||
|
$new_price_range .= " AND ( `price` BETWEEN '".$minPrice."' AND '".$maxPrice."' ) ";
|
||||||
|
}elseif($maxPrice > 0){
|
||||||
|
$new_price_range .= " AND `price` < '".$maxPrice."' ";
|
||||||
|
}elseif($minPrice > 0 && $maxPrice == 0){
|
||||||
|
$new_price_range .= " AND `price` >='".$minPrice."' ";
|
||||||
|
}
|
||||||
|
|
||||||
|
return " SELECT '".$range_key."' AS `range_key`, COUNT(`id`) AS total
|
||||||
|
FROM ".TableName::PRODUCT."
|
||||||
|
WHERE 1 ".$new_price_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
95
inc/Hura8/Components/Product/Model/ProductHotModel.php
Normal file
95
inc/Hura8/Components/Product/Model/ProductHotModel.php
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\Components\Product\AdminController\AProductHotController;
|
||||||
|
use Hura8\Database\iConnectDB;
|
||||||
|
use Hura8\System\Config;
|
||||||
|
|
||||||
|
|
||||||
|
class ProductHotModel
|
||||||
|
{
|
||||||
|
|
||||||
|
/* @var $db iConnectDB */
|
||||||
|
protected $db;
|
||||||
|
|
||||||
|
protected $tb_product = "tb_product";
|
||||||
|
protected $tb_product_hot = "tb_product_hot";
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->db = get_db();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateProductHot($pro_id, array $new_types) {
|
||||||
|
|
||||||
|
$this->db->runQuery("DELETE FROM " . $this->tb_product_hot . " WHERE `pro_id` = ? ", ['d'], [ $pro_id ]);
|
||||||
|
$this->db->update(
|
||||||
|
$this->tb_product,
|
||||||
|
[
|
||||||
|
"hot_type" => '',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id' => $pro_id,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
$config_hottype = Config::getProductHotTypeList();
|
||||||
|
|
||||||
|
//insert what good
|
||||||
|
$batch_insert = array();
|
||||||
|
$accepted_types = array();
|
||||||
|
foreach ($new_types as $hot_type) {
|
||||||
|
if (!isset($config_hottype[$hot_type])) continue;
|
||||||
|
|
||||||
|
$batch_insert[] = [
|
||||||
|
"pro_id" => $pro_id,
|
||||||
|
"hot_type" => $hot_type,
|
||||||
|
];
|
||||||
|
|
||||||
|
$accepted_types[] = $hot_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sizeof($batch_insert)) {
|
||||||
|
|
||||||
|
$this->db->bulk_insert($this->tb_product_hot, $batch_insert );
|
||||||
|
|
||||||
|
$this->db->update(
|
||||||
|
$this->tb_product,
|
||||||
|
[
|
||||||
|
"hot_type" => join(",", $accepted_types),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"id" => $pro_id,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductHot(array $product_ids) {
|
||||||
|
|
||||||
|
if(!sizeof($product_ids)){
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
list($parameterized_ids, $bind_types) = create_bind_sql_parameter_from_value_list($product_ids, 'int');
|
||||||
|
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"SELECT `pro_id`, `hot_type` FROM ".$this->tb_product_hot." WHERE `pro_id` IN (".$parameterized_ids.") ",
|
||||||
|
$bind_types,
|
||||||
|
$product_ids
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
foreach ( $this->db->fetchAll($query) as $rs ) {
|
||||||
|
$result[$rs['pro_id']][] = $rs['hot_type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
78
inc/Hura8/Components/Product/Model/ProductImageModel.php
Normal file
78
inc/Hura8/Components/Product/Model/ProductImageModel.php
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\Database\MysqlValue;
|
||||||
|
use Hura8\Interfaces\AppResponse;
|
||||||
|
use Hura8\System\Model\aEntityBaseModel;
|
||||||
|
|
||||||
|
class ProductImageModel extends aEntityBaseModel
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $product_id = 0;
|
||||||
|
|
||||||
|
/* @var ProductModel $objProductModel */
|
||||||
|
protected $objProductModel;
|
||||||
|
|
||||||
|
public function __construct($product_id = 0) {
|
||||||
|
parent::__construct('product_image');
|
||||||
|
|
||||||
|
$this->product_id = $product_id;
|
||||||
|
$this->objProductModel = new ProductModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function extendedFilterOptions() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// empty for now
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function setProduct($product_id) {
|
||||||
|
$this->product_id = $product_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countProductImage($product_id) {
|
||||||
|
$query = $this->db->runQuery("SELECT COUNT(*) AS total FROM `".$this->tb_entity."` WHERE `pro_id` = ? ", ['d'], [ $product_id]);
|
||||||
|
if($info = $this->db->fetchAssoc($query)) {
|
||||||
|
return $info['total'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function _buildQueryOrderBy($sort_by = "new")
|
||||||
|
{
|
||||||
|
return " `ordering` DESC, `id` DESC ";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function _buildQueryConditionExtend(array $filter_condition) : ?array
|
||||||
|
{
|
||||||
|
/*$condition = array(
|
||||||
|
"q" => "",
|
||||||
|
"letter" => "",
|
||||||
|
"status" => 0,
|
||||||
|
);*/
|
||||||
|
|
||||||
|
$catCondition = [" AND `pro_id` = ? "];
|
||||||
|
$bind_types = ['d'];
|
||||||
|
$bind_values = [$this->product_id];
|
||||||
|
|
||||||
|
return array( join(" ", $catCondition), $bind_types, $bind_values);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function getProductMainImage() {
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"SELECT `id` FROM `".$this->tb_entity."` WHERE `pro_id` = ? AND `is_main` = 1 LIMIT 1",
|
||||||
|
['d'], [ $this->product_id]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->db->fetchAssoc($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
35
inc/Hura8/Components/Product/Model/ProductInfoModel.php
Normal file
35
inc/Hura8/Components/Product/Model/ProductInfoModel.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\AppResponse;
|
||||||
|
use Hura8\System\Model\aEntityBaseModel;
|
||||||
|
|
||||||
|
class ProductInfoModel extends aEntityBaseModel
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $richtext_fields = [
|
||||||
|
'description',
|
||||||
|
'spec',
|
||||||
|
'instruction',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct('product_info', '', null, $this->richtext_fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function extendedFilterOptions() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// empty for now
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function _buildQueryConditionExtend(array $filter_condition) : ?array
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
inc/Hura8/Components/Product/Model/ProductLanguageModel.php
Normal file
21
inc/Hura8/Components/Product/Model/ProductLanguageModel.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\System\Model\EntityLanguageModel;
|
||||||
|
use Hura8\Interfaces\EntityType;
|
||||||
|
|
||||||
|
class ProductLanguageModel extends EntityLanguageModel
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $richtext_fields = [
|
||||||
|
'description',
|
||||||
|
'spec',
|
||||||
|
'instruction',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct(EntityType::PRODUCT, '', $this->richtext_fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
410
inc/Hura8/Components/Product/Model/ProductModel.php
Normal file
410
inc/Hura8/Components/Product/Model/ProductModel.php
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\iEntityModel;
|
||||||
|
use Hura8\System\Config;
|
||||||
|
use Hura8\System\Model\aEntityBaseModel;
|
||||||
|
use Hura8\Interfaces\EntityType;
|
||||||
|
use Hura8\System\Security\DataClean;
|
||||||
|
use Hura8\System\Security\DataType;
|
||||||
|
|
||||||
|
|
||||||
|
class ProductModel extends aEntityBaseModel implements iEntityModel
|
||||||
|
{
|
||||||
|
|
||||||
|
static $url_module = "product";
|
||||||
|
static $url_view = "detail";
|
||||||
|
static $url_type = "product:detail";
|
||||||
|
|
||||||
|
protected $tb_product_per_category = 'tb_product_per_category';
|
||||||
|
protected $tb_collection_product = "tb_collection_product";
|
||||||
|
protected $tb_product_info = 'tb_product_info';
|
||||||
|
protected $tb_product_hot = "tb_product_hot";
|
||||||
|
|
||||||
|
protected $richtext_fields = [
|
||||||
|
//'special_offer',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct(
|
||||||
|
EntityType::PRODUCT,
|
||||||
|
"",
|
||||||
|
new ProductSearchModel(),
|
||||||
|
$this->richtext_fields
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function extendedFilterOptions() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"price" => array('max' => 0, 'min'=> 0),
|
||||||
|
"brand" => array(), // array(1,2,3,)
|
||||||
|
"collection" => array(), // array(1,2,3,)
|
||||||
|
"supplier" => array(), // array(1,2,3,)
|
||||||
|
"rating" => array(), // array(1,2,3,)
|
||||||
|
"category" => array(), // array(1,2,3,)
|
||||||
|
"status" => array(), // array(1,2,3,)
|
||||||
|
"hotType" => array(),// array(saleoff | not | new)
|
||||||
|
"attribute" => array(), // array(1,2,3,)
|
||||||
|
"promotion" => "",
|
||||||
|
"other_filter" => array(), // array(in-stock, has-promotion etc...)
|
||||||
|
"spec_group_id" => array(), // array(1,2,3,)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductInfoBySKU(string $sku) {
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"SELECT * FROM `".$this->tb_entity."` basic, `".$this->tb_product_info."` info
|
||||||
|
WHERE basic.`id` = info.`id` AND basic.sku = ?
|
||||||
|
LIMIT 1 ",
|
||||||
|
['d'], [$sku]
|
||||||
|
);
|
||||||
|
|
||||||
|
if( $item_info = $this->db->fetchAssoc($query)){
|
||||||
|
return $item_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getFullInfo($id) {
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"SELECT * FROM `".$this->tb_entity."` basic, `".$this->tb_product_info."` info
|
||||||
|
WHERE basic.`id` = info.`id` AND basic.id = ?
|
||||||
|
LIMIT 1 ",
|
||||||
|
['d'], [$id]
|
||||||
|
);
|
||||||
|
|
||||||
|
if( $item_info = $this->db->fetchAssoc($query)){
|
||||||
|
return $item_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductCategoryList($pro_id) {
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"SELECT `category_id` FROM ".$this->tb_product_per_category." WHERE `item_id` = ? ",
|
||||||
|
['d'], [ $pro_id ]
|
||||||
|
) ;
|
||||||
|
|
||||||
|
$pro_cat = [];
|
||||||
|
foreach ( $this->db->fetchAll($query) as $rs ) {
|
||||||
|
$pro_cat[] = $rs['category_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pro_cat;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function _buildQueryConditionExtend(array $filter_condition) : ?array
|
||||||
|
{
|
||||||
|
$where_query = [];
|
||||||
|
$bind_types = [];
|
||||||
|
$bind_values = [];
|
||||||
|
|
||||||
|
//-------------------
|
||||||
|
|
||||||
|
//other filters
|
||||||
|
if( array_key_exists("other_filter", $filter_condition) && sizeof($filter_condition["other_filter"])) {
|
||||||
|
foreach ($filter_condition["other_filter"] as $_filter) {
|
||||||
|
switch ($_filter) {
|
||||||
|
case "in-stock";
|
||||||
|
$where_query[] = " AND quantity > 0 ";
|
||||||
|
break;
|
||||||
|
case "has-vat";
|
||||||
|
$where_query[] = " AND `has_vat` = 1 ";
|
||||||
|
break;
|
||||||
|
case "out-stock";
|
||||||
|
$where_query[] = " AND `quantity` = 0 ";
|
||||||
|
break;
|
||||||
|
case "has-market-price";
|
||||||
|
$where_query[] = " AND `market_price` > 0 ";
|
||||||
|
break;
|
||||||
|
case "no-price";
|
||||||
|
$where_query[] = " AND `price` = 0 ";
|
||||||
|
break;
|
||||||
|
case "no-warranty";
|
||||||
|
$where_query[] = " AND LENGTH(`warranty`) < 2 ";
|
||||||
|
break;
|
||||||
|
case "no-sku";
|
||||||
|
$where_query[] = " AND LENGTH(`sku`) < 2 ";
|
||||||
|
break;
|
||||||
|
case "has-config";
|
||||||
|
$where_query[] = " AND `config_count` > 0 ";
|
||||||
|
break;
|
||||||
|
case "no-image";
|
||||||
|
$where_query[] = " AND `image_count` = 0 ";
|
||||||
|
break;
|
||||||
|
case "no-category";
|
||||||
|
$where_query[] = " AND `category` = '0' ";
|
||||||
|
break;
|
||||||
|
case "display-off";
|
||||||
|
$where_query[] = " AND `status`=0 ";
|
||||||
|
break;
|
||||||
|
case "display-on";
|
||||||
|
$where_query[] = " AND `status`=1 ";
|
||||||
|
break;
|
||||||
|
case "has-promotion":
|
||||||
|
$where_query[] = " AND LENGTH(`special_offer`) < 5 ";
|
||||||
|
break;
|
||||||
|
//...add more
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//- brand id or ids or brand_indexes
|
||||||
|
if( array_key_exists("brand", $filter_condition) && sizeof($filter_condition["brand"])) {
|
||||||
|
|
||||||
|
$condition = array();
|
||||||
|
foreach ($filter_condition["brand"] as $brand_id) {
|
||||||
|
if(!intval($brand_id)) continue;
|
||||||
|
$condition[] = " brand_id = ? ";
|
||||||
|
$bind_types[] = 'd';
|
||||||
|
$bind_values[] = $brand_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sizeof($condition)) {
|
||||||
|
$where_query[] = " AND ( ".join(" OR ", $condition)." )";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//- collection id or ids
|
||||||
|
if( array_key_exists("collection", $filter_condition) && sizeof($filter_condition["collection"])) {
|
||||||
|
$condition = array();
|
||||||
|
|
||||||
|
foreach ($filter_condition["collection"] as $_id) {
|
||||||
|
if(!intval($_id)) continue;
|
||||||
|
|
||||||
|
$condition[] = " `id` IN ( SELECT `product_id` FROM ".$this->tb_collection_product." WHERE `collection_id` = ? ) ";
|
||||||
|
$bind_types[] = 'd';
|
||||||
|
$bind_values[] = $_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_query[] = " AND ( ".join(" OR ", $condition)." )";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//- rating: 1-> 5
|
||||||
|
if( array_key_exists("rating", $filter_condition) && sizeof($filter_condition["rating"])) {
|
||||||
|
$condition = array();
|
||||||
|
foreach ($filter_condition["rating"] as $_id) {
|
||||||
|
$condition[] = " `rating` = ? ";
|
||||||
|
$bind_types[] = 'd';
|
||||||
|
$bind_values[] = $_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_query[] = " AND ( ".join(" OR ", $condition)." )";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//- category id or ids
|
||||||
|
if( array_key_exists("category", $filter_condition) && sizeof($filter_condition["category"])) {
|
||||||
|
|
||||||
|
$objProductCategoryModel = new ProductCategoryModel();
|
||||||
|
|
||||||
|
$condition = array();
|
||||||
|
foreach ($filter_condition["category"] as $cat_id) {
|
||||||
|
|
||||||
|
$cat_info = $objProductCategoryModel->getInfo($cat_id);
|
||||||
|
if(!$cat_info) continue;
|
||||||
|
|
||||||
|
if($cat_info["is_parent"]) {
|
||||||
|
$childListId = ($cat_info["child_ids"]) ?: '0';
|
||||||
|
$condition[] = " `category_id` IN (".$childListId .") ";
|
||||||
|
}else{
|
||||||
|
$condition[] = " `category_id` = ? ";
|
||||||
|
$bind_types[] = 'd';
|
||||||
|
$bind_values[] = $cat_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sizeof($condition)) {
|
||||||
|
$where_query[] = " AND `id` IN ( SELECT DISTINCT `item_id` FROM ".$this->tb_product_per_category." WHERE " . join(" OR ", $condition) . " )";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// spec_group_id
|
||||||
|
if( array_key_exists("spec_group_id", $filter_condition) && sizeof($filter_condition["spec_group_id"])) {
|
||||||
|
|
||||||
|
$condition = array();
|
||||||
|
foreach ($filter_condition["spec_group_id"] as $_id) {
|
||||||
|
if(!$_id) continue;
|
||||||
|
|
||||||
|
$condition[] = " `spec_group_id` = ? ";
|
||||||
|
$bind_types[] = 'd';
|
||||||
|
$bind_values[] = $_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_query[] = " AND ( ".join(" OR ", $condition )." ) ";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//- hotType: saleoff | not | new | or combination of types
|
||||||
|
if( array_key_exists("hotType", $filter_condition) && sizeof($filter_condition["hotType"])) {
|
||||||
|
$config_hottype = Config::getProductHotTypeList();
|
||||||
|
$condition = array();
|
||||||
|
foreach ($filter_condition["hotType"] as $_id) {
|
||||||
|
if(!array_key_exists($_id, $config_hottype)) continue;
|
||||||
|
|
||||||
|
$condition[] = " `hot_type` = ? ";
|
||||||
|
$bind_types[] = 's';
|
||||||
|
$bind_values[] = $_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sizeof($condition)) {
|
||||||
|
$where_query[] = " AND `id` IN (SELECT `pro_id` FROM ".$this->tb_product_hot." WHERE 1 AND ( ".join(" OR ", $condition)." ) ) ";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//- attribute values
|
||||||
|
/*
|
||||||
|
if( array_key_exists("attribute", $filter_condition) && sizeof($filter_condition["attribute"])) {
|
||||||
|
$filter_attr_value_list = $filter_condition["attribute"];
|
||||||
|
//filter = attr_value_1-attr_value_2-attr_value_3,
|
||||||
|
$query_attr_id = [];
|
||||||
|
$count_filter = 0;
|
||||||
|
|
||||||
|
foreach($filter_attr_value_list as $attr_id){
|
||||||
|
$attr_id = (int) $attr_id;
|
||||||
|
if($attr_id) {
|
||||||
|
$query_attr_id[] = $attr_id;
|
||||||
|
$count_filter ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$product_filter_id_match = array();
|
||||||
|
if(sizeof($query_attr_id)) {
|
||||||
|
$query = $this->db->runQuery("
|
||||||
|
SELECT DISTINCT pro_id , COUNT(*) as num_pro
|
||||||
|
FROM ".TB_PRODUCT_ATTRIBUTE."
|
||||||
|
WHERE attr_value_id IN (".join(',', $query_attr_id).")
|
||||||
|
GROUP BY pro_id
|
||||||
|
HAVING num_pro = ".$count_filter."
|
||||||
|
LIMIT 10000
|
||||||
|
");
|
||||||
|
foreach ( $this->db->fetchAll($query) as $rs ) {
|
||||||
|
$product_filter_id_match[] = $rs["pro_id"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_query[] = (sizeof($product_filter_id_match)) ? " AND `id` IN (".join(', ', $product_filter_id_match).") " : " AND `id` = 0 " ;
|
||||||
|
|
||||||
|
//xay lai url de back
|
||||||
|
if(!$this->objCategoryProduct) $this->objCategoryProduct = new CategoryProduct();
|
||||||
|
|
||||||
|
foreach($filter_attr_value_list as $value_id ){
|
||||||
|
$att_name = $this->objCategoryProduct->atrValueName($value_id);
|
||||||
|
|
||||||
|
$filterPath["attribute"][] = array(
|
||||||
|
"id" => $value_id,
|
||||||
|
"name" => $att_name,
|
||||||
|
);
|
||||||
|
$filter_messages[] = [
|
||||||
|
'title' => $att_name,
|
||||||
|
'reset' => Url::buildUrl($this->_request_url, ['filter' => join(FILTER_VALUE_SEPARATOR, remove_item_from_array($filter_condition["attribute"], $value_id))] ),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
//- attribute values
|
||||||
|
if( array_key_exists("attribute", $filter_condition) && sizeof($filter_condition["attribute"])) {
|
||||||
|
|
||||||
|
$filter_attr_value_list = $filter_condition["attribute"];
|
||||||
|
|
||||||
|
$attr_values_per_attribute = $this->groupAttributeValuesByAttributeId($filter_attr_value_list);
|
||||||
|
|
||||||
|
$having_match_count = sizeof(array_keys($attr_values_per_attribute));
|
||||||
|
|
||||||
|
$attribute_info_list = $this->groupAttributeListInfo(array_keys($attr_values_per_attribute));
|
||||||
|
|
||||||
|
$attr_where_query = [];
|
||||||
|
foreach ($attr_values_per_attribute as $_att_id => $_att_value_list) {
|
||||||
|
// if same attribute : find products that match either ONE of these values (not match ALL values)
|
||||||
|
$_att_info = $attribute_info_list[$_att_id];
|
||||||
|
if (!$_att_info) continue;
|
||||||
|
|
||||||
|
// this attribute requires all products must have ALL selected values
|
||||||
|
if ($_att_info['value_match_all'] && sizeof($_att_value_list) > 1) {
|
||||||
|
|
||||||
|
$_product_id_match_all = $this->getProductMatchAllAttributeValueIds($_att_value_list);
|
||||||
|
|
||||||
|
if (sizeof($_product_id_match_all) > 0) {
|
||||||
|
$attr_where_query[] = " ( `id` IN (" . join(",", $_product_id_match_all) . ") ) ";
|
||||||
|
} else {
|
||||||
|
$attr_where_query[] = " ( `id` = -1 ) ";
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$attr_where_query[] = " ( SELECT DISTINCT `pro_id` FROM " . TB_PRODUCT_ATTRIBUTE . " WHERE (" .
|
||||||
|
join(" OR ", array_map(function ($_item) { return " `attr_value_id` = '" . intval($_item['id']) . "' "; }, $_att_value_list))
|
||||||
|
. " ) ) ";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$product_filter_id_match = array();
|
||||||
|
|
||||||
|
if (sizeof($attr_where_query)) {
|
||||||
|
$query = $this->db->runQuery("
|
||||||
|
SELECT `pro_id` , COUNT(*) AS num_pro
|
||||||
|
FROM ( " . join(" UNION ALL ", $attr_where_query) . " ) AS tpm_tb
|
||||||
|
GROUP BY pro_id
|
||||||
|
HAVING num_pro = " . $having_match_count . "
|
||||||
|
LIMIT 10000
|
||||||
|
");
|
||||||
|
foreach ($this->db->fetchAll($query) as $rs) {
|
||||||
|
$product_filter_id_match[] = $rs["pro_id"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_query[] = (sizeof($product_filter_id_match)) ? " AND " . TB_PRODUCT_LIGHT . ".`id` IN (" . join(', ', $product_filter_id_match) . ") " : " AND " . TB_PRODUCT_LIGHT . ".`id` = 0 ";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//- price range
|
||||||
|
if(
|
||||||
|
isset($filter_condition["price"])
|
||||||
|
&& sizeof($filter_condition["price"])
|
||||||
|
&& ($filter_condition["price"]['min'] > 0 || $filter_condition["price"]['max'] > 0)
|
||||||
|
) {
|
||||||
|
|
||||||
|
//limit by price range
|
||||||
|
$maxPrice = DataClean::makeInputSafe($filter_condition["price"]['max'], DataType::INTEGER);
|
||||||
|
$minPrice = DataClean::makeInputSafe($filter_condition["price"]['min'], DataType::INTEGER);
|
||||||
|
|
||||||
|
$price_range_query = '';
|
||||||
|
if($maxPrice > 0 && $minPrice > 0){
|
||||||
|
$price_range_query = " ( `price` BETWEEN '".$minPrice."' AND '".$maxPrice."' ) ";
|
||||||
|
|
||||||
|
}else if($maxPrice > 0){
|
||||||
|
$price_range_query = " `price` < '".$maxPrice."' ";
|
||||||
|
|
||||||
|
}else if($minPrice > 0){
|
||||||
|
$price_range_query = " `price` >='".$minPrice."' ";
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_query[] = " AND ". $price_range_query;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------
|
||||||
|
|
||||||
|
return array( join(" ", $where_query), $bind_types, $bind_values);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
33
inc/Hura8/Components/Product/Model/ProductSearchModel.php
Normal file
33
inc/Hura8/Components/Product/Model/ProductSearchModel.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\iSearch;
|
||||||
|
use Hura8\System\Model\aSearchBaseModel;
|
||||||
|
|
||||||
|
class ProductSearchModel extends aSearchBaseModel implements iSearch
|
||||||
|
{
|
||||||
|
|
||||||
|
private $filter_fields = [
|
||||||
|
"price" => "tb_product.price",
|
||||||
|
"ranking" => "tb_product.ranking",
|
||||||
|
"status" => "tb_product.status",
|
||||||
|
"brand" => "tb_product.brand_id",
|
||||||
|
];
|
||||||
|
|
||||||
|
private $fulltext_fields = [
|
||||||
|
"category_keywords" => ["tb_product_category.title", ],
|
||||||
|
"product_keywords" => ["tb_product.sku", "tb_product.title", "tb_product.model", "tb_product.related_keywords"],
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct(
|
||||||
|
"tb_product",
|
||||||
|
$this->fulltext_fields,
|
||||||
|
$this->filter_fields
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\AppResponse;
|
||||||
|
use Hura8\System\Model\aEntityBaseModel;
|
||||||
|
|
||||||
|
class ProductSpecGroupAttributeModel extends aEntityBaseModel
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $tb_product = 'tb_product';
|
||||||
|
protected $tb_product_spec = 'tb_product_spec';
|
||||||
|
protected $tb_product_spec_group = 'tb_product_spec_group';
|
||||||
|
protected $tb_product_spec_group_record = 'tb_product_spec_group_record';
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct('product_spec_group_record');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function extendedFilterOptions() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// empty for now
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getSpecGroupAttribute($group_id)
|
||||||
|
{
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
"SELECT * FROM `".$this->tb_product_spec_group_record."` WHERE `group_id` = ? ORDER BY `ordering` ASC ",
|
||||||
|
['d'], [$group_id]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->db->fetchAll($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected function updateGroupAttributeCount($group_id) {
|
||||||
|
$this->db->runQuery("UPDATE `".$this->tb_product_spec_group."` SET
|
||||||
|
`attribute_count` = (SELECT COUNT(*) FROM `".$this->tb_product_spec_group_record."` WHERE `group_id` = ? )
|
||||||
|
WHERE `id` = ? LIMIT 1 ", ['d', 'd' ], [ $group_id, $group_id ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildQueryCondition(array $condition)
|
||||||
|
{
|
||||||
|
// TODO: Implement buildQueryCondition() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function buildOrderByCondition($order_by)
|
||||||
|
{
|
||||||
|
// TODO: Implement buildOrderByCondition() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function _buildQueryConditionExtend(array $extend_filter_conditions) : ?array
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
151
inc/Hura8/Components/Product/Model/ProductSpecGroupModel.php
Normal file
151
inc/Hura8/Components/Product/Model/ProductSpecGroupModel.php
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\AppResponse;
|
||||||
|
use Hura8\System\Model\aEntityBaseModel;
|
||||||
|
|
||||||
|
class ProductSpecGroupModel extends aEntityBaseModel
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
protected $tb_product = 'tb_product';
|
||||||
|
protected $tb_product_spec_group = 'tb_product_spec_group';
|
||||||
|
protected $tb_product_attribute = 'tb_product_attribute';
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct('product_spec_group');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function extendedFilterOptions() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// empty for now
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getSpecGroupAttribute($group_id) {
|
||||||
|
$objProductSpecGroupAttributeModel = new ProductSpecGroupAttributeModel();
|
||||||
|
return $objProductSpecGroupAttributeModel->getSpecGroupAttribute($group_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function clearProductAttributes($product_id) {
|
||||||
|
$this->db->runQuery(
|
||||||
|
"DELETE FROM `".$this->tb_product_attribute."` WHERE `pro_id` = ? ",
|
||||||
|
[ 'd' ], [ $product_id ]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function clearProductSpecGroup($product_id) {
|
||||||
|
|
||||||
|
$this->clearProductAttributes($product_id);
|
||||||
|
|
||||||
|
$product_spec_group_id = $this->getProductSpecGroupId($product_id);
|
||||||
|
|
||||||
|
// update
|
||||||
|
$this->db->runQuery(
|
||||||
|
"UPDATE `".$this->tb_product."` SET
|
||||||
|
`spec_group_id` = 0
|
||||||
|
WHERE `id` = ? LIMIT 1 ",
|
||||||
|
[ 'd' ], [ $product_id ]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->updateSpecGroupProductCount($product_spec_group_id);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function setProductSpecGroup($product_id, $new_group_id)
|
||||||
|
{
|
||||||
|
$current_group_id = $this->getProductSpecGroupId($product_id);
|
||||||
|
|
||||||
|
if($current_group_id != $new_group_id) {
|
||||||
|
// update
|
||||||
|
$this->db->runQuery(
|
||||||
|
"UPDATE `".$this->tb_product."` SET
|
||||||
|
`spec_group_id` = ?
|
||||||
|
WHERE `id` = ? LIMIT 1 ",
|
||||||
|
[ 'd', 'd' ], [ $new_group_id, $product_id ]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->updateSpecGroupProductCount($new_group_id);
|
||||||
|
|
||||||
|
$this->clearProductAttributes($product_id);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function updateSpecGroupProductCount($group_id) {
|
||||||
|
$this->db->runQuery(
|
||||||
|
"UPDATE `".$this->tb_product_spec_group."` SET
|
||||||
|
`product_count` = (SELECT COUNT(*) FROM ".$this->tb_product." WHERE `spec_group_id` = ? )
|
||||||
|
WHERE `id` = ? LIMIT 1 ",
|
||||||
|
[ 'd', 'd' ], [ $group_id, $group_id ]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductSpecGroupInfo($product_id)
|
||||||
|
{
|
||||||
|
$product_spec_group_id = $this->getProductSpecGroupId($product_id);
|
||||||
|
|
||||||
|
return ($product_spec_group_id) ? $this->getSpecGroupInfo($product_spec_group_id) : false ;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductSpecGroupId($product_id)
|
||||||
|
{
|
||||||
|
$query = $this->db->runQuery("SELECT `spec_group_id` FROM `".$this->tb_product."` WHERE `id` = ? LIMIT 1 ", ['d'], [$product_id]);
|
||||||
|
|
||||||
|
if ($info = $this->db->fetchAssoc($query) ) {
|
||||||
|
return $info['spec_group_id'] ;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getSpecGroupInfo($group_id)
|
||||||
|
{
|
||||||
|
$query = $this->db->runQuery("SELECT * FROM `".$this->tb_product_spec_group."` WHERE `id` = ? ", ['d'], [$group_id]);
|
||||||
|
|
||||||
|
return $this->db->fetchAssoc($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function buildQueryCondition(array $condition)
|
||||||
|
{
|
||||||
|
$catCondition = [];
|
||||||
|
|
||||||
|
//Tim kiem theo tu khoa
|
||||||
|
if(isset($condition["q"]) && $condition["q"]){
|
||||||
|
$catCondition[] = " AND `title` LIKE '%".$this->db->escape($condition["q"])."%' ";
|
||||||
|
}
|
||||||
|
|
||||||
|
return join(" ", $catCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _buildQueryConditionExtend(array $filter_condition) : ?array
|
||||||
|
{
|
||||||
|
$where_condition = "";
|
||||||
|
$bind_types = [];
|
||||||
|
$bind_values = [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
$where_condition,
|
||||||
|
$bind_types,
|
||||||
|
$bind_values
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
181
inc/Hura8/Components/Product/Model/ProductVariantModel.php
Normal file
181
inc/Hura8/Components/Product/Model/ProductVariantModel.php
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Components\Product\Model;
|
||||||
|
|
||||||
|
use Hura8\Components\Product\Controller\ProductController;
|
||||||
|
use Hura8\Interfaces\AppResponse;
|
||||||
|
use Hura8\System\Model\aEntityBaseModel;
|
||||||
|
use Hura8\System\Security\DataClean;
|
||||||
|
use Hura8\System\Security\DataType;
|
||||||
|
|
||||||
|
|
||||||
|
class ProductVariantModel extends aEntityBaseModel
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $product_id = 0;
|
||||||
|
|
||||||
|
protected $tb_product = "tb_product";
|
||||||
|
protected $tb_variant_option_sample = "tb_product_variant_option_sample";
|
||||||
|
|
||||||
|
public function __construct($product_id) {
|
||||||
|
parent::__construct('product_variant');
|
||||||
|
$this->product_id = $product_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function extendedFilterOptions() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// empty for now
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductVariantOption($product_id){
|
||||||
|
$query = $this->db->runQuery("SELECT `variant_option` FROM `".$this->tb_product."` WHERE `id` = ? LIMIT 1", ['d'], [ $product_id ]) ;
|
||||||
|
|
||||||
|
if($rs = $this->db->fetchAssoc($query)) {
|
||||||
|
return ($rs['variant_option']) ? \json_decode($rs['variant_option'], true) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//use a product's variant-option to create a sample, so next product can select without recreate from beginning
|
||||||
|
public function createVariantOptionSample($use_from_pro_id, $sample_title) {
|
||||||
|
|
||||||
|
if( !$use_from_pro_id || strlen($sample_title) < 3 ) return false;
|
||||||
|
|
||||||
|
$pro_variant_option = $this->getProductVariantOption($use_from_pro_id);
|
||||||
|
if(!$pro_variant_option) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pro_variant_option_index = md5(\json_encode($pro_variant_option));
|
||||||
|
|
||||||
|
$check_duplicate = $this->db->runQuery(
|
||||||
|
"SELECT `id` FROM ".$this->tb_variant_option_sample." WHERE `variant_option_index` = ? LIMIT 1 ",
|
||||||
|
['s'], [ $pro_variant_option_index ]
|
||||||
|
);
|
||||||
|
|
||||||
|
if($this->db->fetchAssoc($check_duplicate)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ok to save
|
||||||
|
$this->db->insert(
|
||||||
|
$this->tb_variant_option_sample,
|
||||||
|
[
|
||||||
|
"title" => $sample_title,
|
||||||
|
"variant_option" => \json_encode($pro_variant_option),
|
||||||
|
"variant_option_index" => $pro_variant_option_index,
|
||||||
|
"create_time" => CURRENT_TIME,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVariantOptionSample() {
|
||||||
|
$query = $this->db->runQuery("SELECT * FROM ".$this->tb_variant_option_sample." ORDER BY `id` DESC LIMIT 500 ");
|
||||||
|
|
||||||
|
return $this->db->fetchAll($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProductVariantPriceRange(){
|
||||||
|
$result = [
|
||||||
|
"sale_price" => [
|
||||||
|
"min" => 0,
|
||||||
|
"max" => 0,
|
||||||
|
],
|
||||||
|
"market_price" => [
|
||||||
|
"min" => 0,
|
||||||
|
"max" => 0,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$query = $this->db->runQuery(
|
||||||
|
" SELECT `sale_price`, `market_price`, `extend` FROM `".$this->tb_entity."` WHERE `product_id` = ? ",
|
||||||
|
['d'], [$this->product_id]
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ( $this->db->fetchAll($query) as $info) {
|
||||||
|
|
||||||
|
// find min
|
||||||
|
if($info["sale_price"] > 0 && ( $info["sale_price"] < $result["sale_price"]["min"] || $result["sale_price"]["min"] == 0 ) ) {
|
||||||
|
$result["sale_price"]["min"] = $info["sale_price"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// find max
|
||||||
|
if($info["sale_price"] > 0 && $info["sale_price"] > $result["sale_price"]["max"] ) {
|
||||||
|
$result["sale_price"]["max"] = $info["sale_price"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// market_price
|
||||||
|
$market_price = $info["market_price"];
|
||||||
|
if($info['extend']) {
|
||||||
|
$extend = unserialize($info['extend']);
|
||||||
|
if(isset($extend['market_price'])) $market_price = clean_price($extend['market_price'], "vnd");
|
||||||
|
}
|
||||||
|
|
||||||
|
if($market_price > 0 && ( $market_price < $result["market_price"]["min"] || $result["market_price"]["min"] == 0 ) ) {
|
||||||
|
$result["market_price"]["min"] = $market_price;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($market_price > 0 && $market_price > $result["market_price"]["max"] ) {
|
||||||
|
$result["market_price"]["max"] = $market_price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function _buildQueryConditionExtend(array $filter_condition) : ?array
|
||||||
|
{
|
||||||
|
$where_condition = " AND `product_id` = ? ";
|
||||||
|
$bind_types = ["d"];
|
||||||
|
$bind_values = [$this->product_id];
|
||||||
|
|
||||||
|
return [
|
||||||
|
$where_condition,
|
||||||
|
$bind_types,
|
||||||
|
$bind_values
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _buildQueryOrderBy($sort_by = "new")
|
||||||
|
{
|
||||||
|
return " `ordering` DESC, `id` DESC ";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function formatItemInList(array $item_info): array
|
||||||
|
{
|
||||||
|
$info = $item_info;
|
||||||
|
|
||||||
|
if($item_info['attribute']) $info['attribute'] = \json_decode($item_info['attribute'], true);
|
||||||
|
|
||||||
|
return $info;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function updateProductVariantCount() {
|
||||||
|
$this->db->runQuery(
|
||||||
|
"UPDATE ".$this->tb_product." SET
|
||||||
|
`config_count` = ( SELECT COUNT(*) AS total FROM `".$this->tb_entity."` WHERE `product_id` = ? AND `status` = 1 )
|
||||||
|
WHERE `id` = ? ",
|
||||||
|
['d', 'd'], [ $this->product_id, $this->product_id]
|
||||||
|
) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function formatItemInfo(array $item_info) : ?array
|
||||||
|
{
|
||||||
|
$info = $item_info;
|
||||||
|
|
||||||
|
if($info['attribute']) $info['attribute'] = \json_decode($info['attribute'], true);
|
||||||
|
|
||||||
|
return $info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
503
inc/Hura8/Database/ConnectDB.php
Normal file
503
inc/Hura8/Database/ConnectDB.php
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Database;
|
||||||
|
|
||||||
|
use Hura8\Traits\ClassCacheTrait;
|
||||||
|
|
||||||
|
final class ConnectDB implements iConnectDB
|
||||||
|
{
|
||||||
|
use ClassCacheTrait;
|
||||||
|
|
||||||
|
private $debug = false;
|
||||||
|
|
||||||
|
private static $instance = [];
|
||||||
|
private static $cnn_props = [];
|
||||||
|
|
||||||
|
/* @var $connection \mysqli */
|
||||||
|
private $connection;
|
||||||
|
|
||||||
|
private static $traces = [];
|
||||||
|
|
||||||
|
private $db_id = '';
|
||||||
|
|
||||||
|
private function __construct($db_id = 'main', $debug = false)
|
||||||
|
{
|
||||||
|
// enable database debug
|
||||||
|
if($debug) $this->debug = $debug; // (defined('ENABLE_DB_DEBUG') && ENABLE_DB_DEBUG);
|
||||||
|
if($db_id) $this->db_id = $db_id;
|
||||||
|
|
||||||
|
if(!sizeof(self::$cnn_props)) {
|
||||||
|
self::$cnn_props = self::setConnectionSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function setConnectionSettings() {
|
||||||
|
return static::getCache("getConnectionSettings", function (){
|
||||||
|
$db_file = CONFIG_DIR.'/db.php';
|
||||||
|
if(file_exists($db_file)) {
|
||||||
|
return include $db_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $table
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getTableDefaultItemInfo($table) {
|
||||||
|
$column_info = $this->getTableInfo($table);
|
||||||
|
$default_info = [];
|
||||||
|
foreach ($column_info as $field => $info) {
|
||||||
|
$default_info[$field] = $info['COLUMN_DEFAULT'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $default_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//28-07-2015 get all columns of a table
|
||||||
|
public function getTableInfo($table) {
|
||||||
|
|
||||||
|
return self::getCache('getTableInfo-'.$table, function () use ($table) {
|
||||||
|
|
||||||
|
$db_props = $this->getConnectionProps();
|
||||||
|
if(!$db_props) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$database = $db_props['db'] ?? null;
|
||||||
|
if(!$database) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = $this->runQuery(
|
||||||
|
"SELECT
|
||||||
|
`COLUMN_NAME` ,
|
||||||
|
COLUMN_DEFAULT,
|
||||||
|
ORDINAL_POSITION,
|
||||||
|
COLUMN_DEFAULT,
|
||||||
|
DATA_TYPE,
|
||||||
|
CHARACTER_MAXIMUM_LENGTH,
|
||||||
|
COLUMN_TYPE,
|
||||||
|
COLUMN_KEY,
|
||||||
|
EXTRA
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE `TABLE_SCHEMA` = ? AND`TABLE_NAME` = ? ",
|
||||||
|
['s', 's'], [$database, $table]
|
||||||
|
);
|
||||||
|
|
||||||
|
$output = [];
|
||||||
|
foreach( $this->fetchAll($query) as $row){
|
||||||
|
$output[$row['COLUMN_NAME']] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function setTrace($query, $start_time, $msg = ''){
|
||||||
|
static::$traces[] = [
|
||||||
|
"msg" => $msg,
|
||||||
|
"query" => $query,
|
||||||
|
"start_time" => $start_time,
|
||||||
|
"total_time" => round($this->getCurrentTime() - $start_time, 5),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getTraces() {
|
||||||
|
$query_count = sizeof(static::$traces);
|
||||||
|
$query_time = array_sum(array_map(function ($item){ return $item['total_time'];}, static::$traces)) ;
|
||||||
|
return [
|
||||||
|
'query_count' => $query_count,
|
||||||
|
'query_time' => $query_time,
|
||||||
|
'list' => static::$traces,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function __clone(){
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $db_id
|
||||||
|
* @param bool $debug
|
||||||
|
* @return iConnectDB
|
||||||
|
*/
|
||||||
|
public static function getInstance(string $db_id = '', bool $debug = false) {
|
||||||
|
if( ! $db_id ) $db_id = 'main';
|
||||||
|
|
||||||
|
if(!isset(ConnectDB::$instance[$db_id])) {
|
||||||
|
ConnectDB::$instance[$db_id] = new self($db_id, $debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConnectDB::$instance[$db_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//close all connections
|
||||||
|
public static function close() {
|
||||||
|
foreach (ConnectDB::$instance as $db_id => $cnn) {
|
||||||
|
$cnn->disconnect();
|
||||||
|
}
|
||||||
|
// reset
|
||||||
|
ConnectDB::$instance = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function isConnected(): bool {
|
||||||
|
return ($this->connection instanceof \mysqli);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function getConnectionProps() {
|
||||||
|
return self::$cnn_props[$this->db_id] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function connect()
|
||||||
|
{
|
||||||
|
// already connect
|
||||||
|
if($this->connection instanceof \mysqli) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// connection props not set
|
||||||
|
$db_props = $this->getConnectionProps();
|
||||||
|
if(!$db_props) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$host = $db_props['host'] ?? null;
|
||||||
|
$user = $db_props['user'] ?? null;
|
||||||
|
$pass = $db_props['pass'] ?? null;
|
||||||
|
$database = $db_props['db'] ?? null;
|
||||||
|
$db_charset = $db_props['charset'] ?? 'latin1';
|
||||||
|
|
||||||
|
if(!$host || !$user || !$pass || !$database) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//create the object
|
||||||
|
$cnn = \mysqli_init();
|
||||||
|
$cnn->options(MYSQLI_OPT_CONNECT_TIMEOUT, 5);
|
||||||
|
//specify the read timeout
|
||||||
|
if (!defined('MYSQLI_OPT_READ_TIMEOUT')) {
|
||||||
|
define ('MYSQLI_OPT_READ_TIMEOUT', 11);
|
||||||
|
}
|
||||||
|
$cnn->options(MYSQLI_OPT_READ_TIMEOUT, 10);
|
||||||
|
|
||||||
|
if($cnn->real_connect($host, $user, $pass)){
|
||||||
|
$cnn->select_db($database);
|
||||||
|
$cnn->query("SET NAMES ".$db_charset); // UTF8|latin1
|
||||||
|
$cnn->query("SET sql_mode=''"); //for old version to work with mysql 5.7
|
||||||
|
//mysqli_select_db($this->connection, $database);
|
||||||
|
//mysqli_query($this->connection, "SET NAMES UTF8") ;//set utf8 if database using utf-8 encode
|
||||||
|
//mysqli_query($this->connection, "SET NAMES latin1") ;//set back to latin1
|
||||||
|
//mysqli_query($this->connection, "SET sql_mode=''") ;//for old version to work with mysql 5.7
|
||||||
|
$this->connection = $cnn;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//throw new \Exception('Unable to connect');
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}catch (\Exception $e) {
|
||||||
|
//die($e->getMessage());
|
||||||
|
echo $e->getMessage();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function disconnect()
|
||||||
|
{
|
||||||
|
if($this->connection instanceof \mysqli) {
|
||||||
|
$this->connection->close();
|
||||||
|
$this->connection = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function ping() {
|
||||||
|
if($this->connection) {
|
||||||
|
$this->connection->ping();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $query
|
||||||
|
* @param array $bind_types
|
||||||
|
* @param array $bind_values
|
||||||
|
* @param bool $get_affected_row_or_id get the affected row or newly insert-id from the query, default return the mysqli_result
|
||||||
|
* @return \mysqli_result | false | int
|
||||||
|
*/
|
||||||
|
public function runQuery($query, array $bind_types=[], array $bind_values=[], $get_affected_row_or_id = false)
|
||||||
|
{
|
||||||
|
$start_time = $this->getCurrentTime();
|
||||||
|
|
||||||
|
// connect on demand
|
||||||
|
if(!$this->connect()) {
|
||||||
|
if($this->debug) $this->setTrace($query, $start_time, 'runQuery: Connection fails');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $this->connection->prepare( $query );
|
||||||
|
if(!$stmt) {
|
||||||
|
//throw new \Exception($stmt->error);
|
||||||
|
//throw new \Exception($this->connection->error);
|
||||||
|
if($this->debug) {
|
||||||
|
$this->setTrace($query, $start_time, 'runQuery: '.$this->connection->error);
|
||||||
|
die("runQuery error: ".$this->connection->error.". Query: ".$query);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sizeof($bind_types) && sizeof($bind_types) == sizeof($bind_values)) {
|
||||||
|
if(!$stmt->bind_param(join('', $bind_types), ...$bind_values)) {
|
||||||
|
if($this->debug) {
|
||||||
|
$this->setTrace($query, $start_time, 'runQuery: bind_param '.$this->connection->error);
|
||||||
|
die('runQuery: bind_param '.$this->connection->error);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if(!$stmt->execute()) {
|
||||||
|
if($this->debug) {
|
||||||
|
$this->setTrace($query, $start_time, 'runQuery: execute '.$this->connection->error);
|
||||||
|
die("runQuery error: ".$this->connection->error.". Query: ".$query);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->debug) $this->setTrace($query, $start_time, '');
|
||||||
|
|
||||||
|
if($get_affected_row_or_id) {
|
||||||
|
return $stmt->insert_id ?: $stmt->affected_rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
// default
|
||||||
|
return $stmt->get_result();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function getCurrentTime(){
|
||||||
|
return microtime(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Unsafe, make sure all variables are properly escaped
|
||||||
|
*/
|
||||||
|
public function multi_query(array $array_query){
|
||||||
|
|
||||||
|
if(!sizeof($array_query)) return false;
|
||||||
|
|
||||||
|
$start_time = $this->getCurrentTime();
|
||||||
|
|
||||||
|
// late connect
|
||||||
|
if(!$this->connect()) {
|
||||||
|
if($this->debug) $this->setTrace(join("; ", $array_query), $start_time, 'multi_query: Connection fails');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$multi_query = join(";", $array_query);
|
||||||
|
$multi_query = str_replace("\n", " ", $multi_query);
|
||||||
|
//remove double ; if exist
|
||||||
|
$multi_query = preg_replace("/;(\s+)?;/i", ";", $multi_query);
|
||||||
|
|
||||||
|
$set = 0;
|
||||||
|
$list = [];
|
||||||
|
if(mysqli_multi_query($this->connection, $multi_query)){
|
||||||
|
// flush multi_queries, so any query after can run
|
||||||
|
do {
|
||||||
|
$set ++;
|
||||||
|
/* store first result set */
|
||||||
|
if ($result = mysqli_store_result($this->connection)) {
|
||||||
|
/*while ($row = mysqli_fetch_assoc($result)) {
|
||||||
|
$list[$set][] = $row;
|
||||||
|
}*/
|
||||||
|
$list[$set] = $this->fetchAll($result);
|
||||||
|
mysqli_free_result($result);
|
||||||
|
}
|
||||||
|
} while (mysqli_more_results($this->connection) && mysqli_next_result($this->connection));
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->debug) $this->setTrace(join(";", $array_query), $start_time);
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// a simple utitily method which we use very frequently
|
||||||
|
public function getItemInfo($table_name, $id_value, $id_field = 'id'){
|
||||||
|
return $this->select(
|
||||||
|
$table_name,
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
$id_field => ["=", $id_value],
|
||||||
|
],
|
||||||
|
'',
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//$table_name = 'table_abc'
|
||||||
|
/*$fields = array(
|
||||||
|
"field_name_1",
|
||||||
|
);*/
|
||||||
|
/*$where_condition = array(
|
||||||
|
"field_name_1" => ["=", "value_1"],
|
||||||
|
"field_name_2" => ["LIKE", "value_1"],
|
||||||
|
);*/
|
||||||
|
public function select($table_name, $fields =[], $where_condition=[], $order_by = '', $limit='1'){
|
||||||
|
|
||||||
|
// connect on demand
|
||||||
|
if(!$this->connect()) {
|
||||||
|
if($this->debug) $this->setTrace($table_name, 0, 'select: Connection fails');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$start_time = $this->getCurrentTime();
|
||||||
|
|
||||||
|
$check_fields = [];
|
||||||
|
if(sizeof($fields) && $fields[0] != '*') $check_fields = $fields;
|
||||||
|
$check_fields = array_merge($check_fields, array_keys($where_condition));
|
||||||
|
|
||||||
|
$cols_in_tables = $this->filterColumnInTable($table_name, $check_fields);
|
||||||
|
|
||||||
|
$bind_types = [];
|
||||||
|
$bind_values = [];
|
||||||
|
$condition_list = [];
|
||||||
|
$permitted_operator = [">", ">=", "<", "<=", "!=", "=", "BETWEEN", "LIKE", "IS"]; // https://dev.mysql.com/doc/refman/8.0/en/non-typed-operators.html
|
||||||
|
foreach ($where_condition as $field_name => $operator_value) {
|
||||||
|
|
||||||
|
// if any field invalid, stop the query
|
||||||
|
if(!in_array($field_name, $cols_in_tables)) {
|
||||||
|
echo "Invalid field_name ".$field_name;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// invalidate
|
||||||
|
if(!is_array($operator_value) || sizeof($operator_value) != 2) continue;
|
||||||
|
|
||||||
|
$operator = $operator_value[0];
|
||||||
|
if(!in_array($operator, $permitted_operator)) continue;
|
||||||
|
|
||||||
|
$value = $operator_value[1];
|
||||||
|
|
||||||
|
$condition_list[] = " `".$field_name."` ".$operator." ? ";
|
||||||
|
$bind_types[] = (is_int($value)) ? 'd' : 's';
|
||||||
|
$bind_values[] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$select_fields = (sizeof($fields) > 0) ? array_map(function ($field){ return "`".$field."`";}, $fields) : ["*"];
|
||||||
|
$order_by_query = ($order_by) ? "ORDER BY ".preg_replace("/[^a-z0-9\s_]/i", "", $order_by) : "";
|
||||||
|
$safe_limit = ($limit) ? preg_replace("/[^0-9\,\s]/i", "", $limit) : "1";
|
||||||
|
$where_condition = (sizeof($condition_list)) ? " AND " . join(" AND ", $condition_list) : "";
|
||||||
|
|
||||||
|
$query = "SELECT ".join(',', $select_fields)."
|
||||||
|
FROM `".$table_name."` WHERE 1 ".$where_condition."
|
||||||
|
".$order_by_query."
|
||||||
|
LIMIT ".$safe_limit;
|
||||||
|
|
||||||
|
$stmt = $this->connection->prepare( $query );
|
||||||
|
if(!$stmt) return false;
|
||||||
|
|
||||||
|
if(sizeof($bind_types)) {
|
||||||
|
$stmt->bind_param(join('', $bind_types), ...$bind_values);
|
||||||
|
}
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if($this->debug) $this->setTrace($query, $start_time);
|
||||||
|
|
||||||
|
return ($safe_limit == '1') ? $result->fetch_assoc() : $result->fetch_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function filterColumnInTable($table_name, array $list_check_fields) {
|
||||||
|
$table_columns = $this->getTableInfo($table_name);
|
||||||
|
|
||||||
|
$safe_fields = [];
|
||||||
|
foreach ($list_check_fields as $field_name) {
|
||||||
|
// make sure the field exists
|
||||||
|
if(!array_key_exists($field_name, $table_columns)) continue;
|
||||||
|
|
||||||
|
$safe_fields[] = $field_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $safe_fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//fetch array
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
*/
|
||||||
|
public function fetchArray(\mysqli_result $resource){
|
||||||
|
return $resource->fetch_array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \mysqli_result | false $resource
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function fetchAssoc( $resource){
|
||||||
|
if(!$resource) {
|
||||||
|
//die("Resource failed: code fetchAssoc 122");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $resource->fetch_assoc();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \mysqli_result | false $resource
|
||||||
|
* @return array|mixed
|
||||||
|
*/
|
||||||
|
public function fetchAll($resource){
|
||||||
|
|
||||||
|
if(!$resource) return [];
|
||||||
|
|
||||||
|
//some hosting does not enable this function, so this is the work-around
|
||||||
|
if( ! function_exists("mysqli_fetch_all")) {
|
||||||
|
$item_list = array();
|
||||||
|
while ($item = $this->fetchAssoc($resource)) {
|
||||||
|
$item_list[] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $item_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($resource) ? $resource->fetch_all(MYSQLI_ASSOC) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
//affected rows from last query
|
||||||
|
public function get_affected_rows(){
|
||||||
|
return $this->connection->affected_rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getErrorNo(){
|
||||||
|
return mysqli_errno($this->connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function __destruct() {
|
||||||
|
$this->disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
16
inc/Hura8/Database/MysqlValue.php
Normal file
16
inc/Hura8/Database/MysqlValue.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Database;
|
||||||
|
|
||||||
|
class MysqlValue
|
||||||
|
{
|
||||||
|
protected $value;
|
||||||
|
|
||||||
|
public function __construct($value = null) {
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValue() {
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
55
inc/Hura8/Database/iConnectDB.php
Normal file
55
inc/Hura8/Database/iConnectDB.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Database;
|
||||||
|
|
||||||
|
interface iConnectDB
|
||||||
|
{
|
||||||
|
|
||||||
|
public function isConnected(): bool;
|
||||||
|
|
||||||
|
public function ping() ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $query
|
||||||
|
* @param array $bind_types
|
||||||
|
* @param array $bind_values
|
||||||
|
* @param bool $get_affected_row_or_id get the affected row or newly insert-id from the query, default return the mysqli_result
|
||||||
|
* @return \mysqli_result | false | int
|
||||||
|
*/
|
||||||
|
public function runQuery($query, array $bind_types=[], array $bind_values=[], $get_affected_row_or_id = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Unsafe, make sure all variables are properly escaped
|
||||||
|
*/
|
||||||
|
public function multi_query(array $array_query);
|
||||||
|
|
||||||
|
|
||||||
|
// a simple utitily method which we use very frequently
|
||||||
|
public function getItemInfo($table_name, $id_value, $id_field = 'id');
|
||||||
|
|
||||||
|
//$table_name = 'table_abc'
|
||||||
|
/*$fields = array(
|
||||||
|
"field_name_1",
|
||||||
|
);*/
|
||||||
|
/*$where_condition = array(
|
||||||
|
"field_name_1" => ["=", "value_1"],
|
||||||
|
"field_name_2" => ["LIKE", "value_1"],
|
||||||
|
);*/
|
||||||
|
public function select($table_name, $fields =[], $where_condition=[], $order_by = '', $limit='1');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \mysqli_result | false $resource
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function fetchAssoc( $resource);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \mysqli_result | false $resource
|
||||||
|
* @return array|mixed
|
||||||
|
*/
|
||||||
|
public function fetchAll($resource);
|
||||||
|
|
||||||
|
public function getErrorNo();
|
||||||
|
|
||||||
|
}
|
||||||
42
inc/Hura8/Interfaces/APIResponse.php
Normal file
42
inc/Hura8/Interfaces/APIResponse.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021. www.Hura.vn
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
|
||||||
|
class APIResponse
|
||||||
|
{
|
||||||
|
private $errCode = 0;
|
||||||
|
private $msg = '';
|
||||||
|
private $data;
|
||||||
|
|
||||||
|
public function __construct($errCode = 0, $msg = '', $data = null) {
|
||||||
|
$this->errCode = $errCode;
|
||||||
|
$this->msg = $msg;
|
||||||
|
$this->data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getCode() {
|
||||||
|
return $this->errCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed if status = 'error'
|
||||||
|
*/
|
||||||
|
public function getMsg() {
|
||||||
|
return $this->msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed if status = 'ok'
|
||||||
|
*/
|
||||||
|
public function getData() {
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
41
inc/Hura8/Interfaces/AppResponse.php
Normal file
41
inc/Hura8/Interfaces/AppResponse.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
class AppResponse
|
||||||
|
{
|
||||||
|
const SUCCESS = 'ok';
|
||||||
|
const ERROR = 'error';
|
||||||
|
|
||||||
|
protected $status;
|
||||||
|
protected $msg = null;
|
||||||
|
protected $data;
|
||||||
|
|
||||||
|
public function __construct($status = 'ok', $msg = null, $data = null) {
|
||||||
|
$this->status = $status;
|
||||||
|
$this->msg = $msg;
|
||||||
|
$this->data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string which is 'ok' or 'error'
|
||||||
|
*/
|
||||||
|
public function getStatus() {
|
||||||
|
return $this->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed if status = 'error'
|
||||||
|
*/
|
||||||
|
public function getMsg() {
|
||||||
|
return $this->msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed if status = 'ok'
|
||||||
|
*/
|
||||||
|
public function getData() {
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
151
inc/Hura8/Interfaces/EntityType.php
Normal file
151
inc/Hura8/Interfaces/EntityType.php
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contain all entity types of this apps
|
||||||
|
*
|
||||||
|
* Dynamic Resolution Rules:
|
||||||
|
* - Format: entity_type = [parent_type]-[sub_type]
|
||||||
|
* Examples:
|
||||||
|
* + product: Product Entity
|
||||||
|
* + product-category: [Product parent-Entity]-[Product Category sub-Entity]
|
||||||
|
* + user-comment-reply: [user parent-Entity]-[user comment sub-Entity]-[user comment reply sub-Entity]
|
||||||
|
*
|
||||||
|
* - Model Resolution: EntityModel is auto inferred from entity name: EntityModel = Hura8\Components\[parent_type]\Model\[entity-name-upper first char]Model
|
||||||
|
* Examples:
|
||||||
|
* + product: Hura8\Components\Product\Model\ProductModel
|
||||||
|
* + product-category: Hura8\Components\Product\Model\ProductCategoryModel
|
||||||
|
* + user-comment: Hura8\Components\User\Model\UserCommentModel
|
||||||
|
* + user-comment-reply: Hura8\Components\User\Model\UserCommentReplyModel
|
||||||
|
*
|
||||||
|
* - Controller Resolution: similar to the Model
|
||||||
|
* Examples: public controllers
|
||||||
|
* + product: Hura8\Components\Product\PublicController\UProductController
|
||||||
|
* + product-category: Hura8\Components\Product\PublicController\UProductCategoryController
|
||||||
|
* + user-comment: Hura8\Components\User\PublicController\UUserCommentController
|
||||||
|
* + user-comment-reply: Hura8\Components\User\PublicController\UUserCommentReplyController
|
||||||
|
*
|
||||||
|
* Examples: admin controllers
|
||||||
|
* + product: Hura8\Components\Product\AdminController\AProductController
|
||||||
|
* + product-category: Hura8\Components\Product\AdminController\AProductCategoryController
|
||||||
|
* + user-comment: Hura8\Components\User\AdminController\AUserCommentController
|
||||||
|
* + user-comment-reply: Hura8\Components\User\AdminController\AUserCommentReplyController
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
final class EntityType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* - Model Resolution: EntityModel is auto inferred from entity name: EntityModel = Hura8\Components\[parent_type]\Model\[entity-name-upper first char]Model
|
||||||
|
* Examples:
|
||||||
|
* + product: Hura8\Components\Product\Model\ProductModel
|
||||||
|
* + product-category: Hura8\Components\Product\Model\ProductCategoryModel
|
||||||
|
* + user-comment: Hura8\Components\User\Model\UserCommentModel
|
||||||
|
* + user-comment-reply: Hura8\Components\User\Model\UserCommentReplyModel
|
||||||
|
*/
|
||||||
|
public static function getModelClass(string $entity_type): string {
|
||||||
|
$parts = array_map(function ($word){
|
||||||
|
return ucfirst($word);
|
||||||
|
}, explode("-", $entity_type));
|
||||||
|
|
||||||
|
$parent = $parts[0];
|
||||||
|
|
||||||
|
return "\\Hura8\\Components\\".$parent."\\Model\\".join("", $parts)."Model";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getStatisticModelClass(string $entity_type): string {
|
||||||
|
$parts = array_map(function ($word){
|
||||||
|
return ucfirst($word);
|
||||||
|
}, explode("-", $entity_type));
|
||||||
|
|
||||||
|
$parent = $parts[0];
|
||||||
|
|
||||||
|
return "\\Hura8\\Components\\".$parent."\\Model\\".join("", $parts)."StatisticModel";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* - Controller Resolution: similar to the Model
|
||||||
|
* Examples: public controllers
|
||||||
|
* + product: Hura8\Components\Product\PublicController\UProductController
|
||||||
|
* + product-category: Hura8\Components\Product\PublicController\UProductCategoryController
|
||||||
|
* + user-comment: Hura8\Components\User\PublicController\UUserCommentController
|
||||||
|
* + user-comment-reply: Hura8\Components\User\PublicController\UUserCommentReplyController
|
||||||
|
* */
|
||||||
|
public static function getControllerClass(string $entity_type, $controller_type='public'): string {
|
||||||
|
$parts = array_map(function ($word){
|
||||||
|
return ucfirst($word);
|
||||||
|
}, explode("-", $entity_type));
|
||||||
|
|
||||||
|
$parent = $parts[0];
|
||||||
|
|
||||||
|
// Hura8\Components\Product\PublicController\UProductCategoryController
|
||||||
|
if($controller_type == 'public') {
|
||||||
|
return "\\Hura8\\Components\\".$parent."\\PublicController\\U".join("", $parts)."Controller";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hura8\Components\Product\AdminController\AProductCategoryController
|
||||||
|
return "\\Hura8\\Components\\".$parent."\\AdminController\\A".join("", $parts)."Controller";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// system's entities
|
||||||
|
const PRODUCT = 'product';
|
||||||
|
const PRODUCT_CATEGORY = 'product-category';
|
||||||
|
const PRODUCT_COLLECTION = 'collection';
|
||||||
|
|
||||||
|
const PRODUCT_ATTRIBUTE = 'attribute';
|
||||||
|
const PRODUCT_ATTRIBUTE_VALUE = 'attribute-value';
|
||||||
|
|
||||||
|
const COUPON = 'coupon';
|
||||||
|
|
||||||
|
const ORDER = 'order';
|
||||||
|
|
||||||
|
const ARTICLE = 'article';
|
||||||
|
const ARTICLE_CATEGORY = 'article-category';
|
||||||
|
|
||||||
|
const BANNER = 'banner';
|
||||||
|
const BANNER_LOCATION = 'banner-location';
|
||||||
|
|
||||||
|
const CUSTOMER = 'customer'; // registered customers
|
||||||
|
|
||||||
|
const USER = 'user'; // all users, include customers
|
||||||
|
const USER_CONTACT = 'user-contact';
|
||||||
|
const USER_COMMENT = 'user-comment';
|
||||||
|
const USER_COMMENT_REPLY = 'user-comment-reply';
|
||||||
|
|
||||||
|
const BRAND = 'brand';
|
||||||
|
const DEAL = 'deal';
|
||||||
|
const MEDIA = 'media';
|
||||||
|
const TAG = 'tag';
|
||||||
|
const URL = 'url';
|
||||||
|
const COMBO_SET = 'combo-set';
|
||||||
|
|
||||||
|
const PAGE = 'page';
|
||||||
|
const PAGE_CATEGORY = 'page-category';
|
||||||
|
|
||||||
|
const ALBUM = 'album';
|
||||||
|
const ALBUM_CATEGORY = 'album-category';
|
||||||
|
const ALBUM_PHOTO = 'album-photo';
|
||||||
|
|
||||||
|
const PROVINCE = 'province';
|
||||||
|
|
||||||
|
const PROMOTION = 'promotion';
|
||||||
|
const PROMOTION_GROUP = 'promotion-group';
|
||||||
|
const DISTRIBUTOR = 'distributor';
|
||||||
|
|
||||||
|
const WARRANTY = 'warranty';
|
||||||
|
|
||||||
|
const STAFF = 'staff'; // admin web
|
||||||
|
|
||||||
|
const VIDEO = 'video';
|
||||||
|
const VIDEO_CATEGORY = 'video-category';
|
||||||
|
|
||||||
|
const JOB = 'job';
|
||||||
|
const JOB_CATEGORY = 'job-category';
|
||||||
|
|
||||||
|
const DOMAIN = 'domain';
|
||||||
|
|
||||||
|
}
|
||||||
43
inc/Hura8/Interfaces/FileHandleInfo.php
Normal file
43
inc/Hura8/Interfaces/FileHandleInfo.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
|
||||||
|
class FileHandleInfo
|
||||||
|
{
|
||||||
|
|
||||||
|
public $file_name;
|
||||||
|
public $public_path;
|
||||||
|
public $local_path;
|
||||||
|
public $mime_type;
|
||||||
|
public $file_size;
|
||||||
|
public $file_ext;
|
||||||
|
public $width = 0;
|
||||||
|
public $height = 0;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(array $file_info = []) {
|
||||||
|
/*$file_info = [
|
||||||
|
"file_name" => $clean_file_name,
|
||||||
|
"public_path" => $this->public_dir . "/".$clean_file_name,
|
||||||
|
"local_path" => $this->target_dir . "/" . $clean_file_name,
|
||||||
|
"mime_type" => $mimeType,
|
||||||
|
"file_size" => $file_size,
|
||||||
|
"file_ext" => $file_ext,
|
||||||
|
"width" => 0,
|
||||||
|
"height" => 0,
|
||||||
|
];*/
|
||||||
|
|
||||||
|
$this->file_name = $file_info['file_name'] ?? '' ;
|
||||||
|
$this->public_path = $file_info['public_path'] ?? '' ;
|
||||||
|
$this->local_path = $file_info['local_path'] ?? '' ;
|
||||||
|
$this->mime_type = $file_info['mime_type'] ?? '' ;
|
||||||
|
$this->file_size = $file_info['file_size'] ?? 0 ;
|
||||||
|
$this->file_ext = $file_info['file_ext'] ?? '' ;
|
||||||
|
$this->width = $file_info['width'] ?? 0 ;
|
||||||
|
$this->height = $file_info['height'] ?? 0 ;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
19
inc/Hura8/Interfaces/FileHandleResponse.php
Normal file
19
inc/Hura8/Interfaces/FileHandleResponse.php
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
|
||||||
|
class FileHandleResponse extends AppResponse
|
||||||
|
{
|
||||||
|
public function __construct($status = 'ok', $msg = null, ?FileHandleInfo $data = null) {
|
||||||
|
parent::__construct($status, $msg, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ?FileHandleInfo
|
||||||
|
*/
|
||||||
|
public function getData() : ?FileHandleInfo {
|
||||||
|
return parent::getData();
|
||||||
|
}
|
||||||
|
}
|
||||||
11
inc/Hura8/Interfaces/PermissionRole.php
Normal file
11
inc/Hura8/Interfaces/PermissionRole.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
class PermissionRole
|
||||||
|
{
|
||||||
|
const OWNER = 'owner';
|
||||||
|
const ADMIN = 'admin';
|
||||||
|
const EDITOR = 'editor';
|
||||||
|
const VIEWER = 'viewer';
|
||||||
|
}
|
||||||
13
inc/Hura8/Interfaces/PermissionType.php
Normal file
13
inc/Hura8/Interfaces/PermissionType.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
class PermissionType
|
||||||
|
{
|
||||||
|
const VIEW = 'view';
|
||||||
|
const CREATE = 'create';
|
||||||
|
const DELETE = 'delete';
|
||||||
|
const UPDATE = 'update';
|
||||||
|
const APPROVE = 'approve';
|
||||||
|
const LOCK = 'lock';
|
||||||
|
}
|
||||||
97
inc/Hura8/Interfaces/TableName.php
Normal file
97
inc/Hura8/Interfaces/TableName.php
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
final class TableName
|
||||||
|
{
|
||||||
|
|
||||||
|
const COLLECT_FORM = 'tb_collect_form';
|
||||||
|
const COLLECT_FORM_FILTER = 'tb_collect_form_filter';
|
||||||
|
|
||||||
|
const TEMPLATE = "tb_template";
|
||||||
|
const TEMPLATE_SET = 'tb_template_set';
|
||||||
|
const TEMPLATE_HISTORY = "tb_template_history";
|
||||||
|
|
||||||
|
const MEDIA = 'tb_media_upload';
|
||||||
|
|
||||||
|
const COMMENT = "tb_user_comment";
|
||||||
|
const COMMENT_REPLY = "tb_user_comment_reply";
|
||||||
|
|
||||||
|
const PRODUCT = 'tb_product';
|
||||||
|
const PRODUCT_INFO = 'tb_product_info';
|
||||||
|
const PRODUCT_CATEGORY = 'tb_product_category';
|
||||||
|
const PRODUCT_CATEGORY_INFO = 'tb_product_category_info';
|
||||||
|
const PRODUCT_PER_CATEGORY = 'tb_product_per_category';
|
||||||
|
|
||||||
|
const PRODUCT_VARIANT = 'tb_product_variant';
|
||||||
|
const PRODUCT_IMAGE_NAME = 'tb_product_image';
|
||||||
|
const PRODUCT_IMAGE_STOCK = 'tb_product_image_stock';
|
||||||
|
const PRODUCT_ACCESSORY = 'tb_product_accessory';
|
||||||
|
const PRODUCT_HOT = 'tb_product_hot';
|
||||||
|
const PRODUCT_COLLECTION = 'tb_collection';
|
||||||
|
const PRODUCT_PER_COLLECTION = 'tb_collection_product';
|
||||||
|
const PRODUCT_FILTER = 'tb_product_filter';
|
||||||
|
const PRODUCT_PER_ATTRIBUTE = 'tb_product_attribute';
|
||||||
|
|
||||||
|
|
||||||
|
const ATTRIBUTE = 'tb_attribute';
|
||||||
|
const ATTRIBUTE_INFO = 'tb_attribute_info';
|
||||||
|
const ATTRIBUTE_CATEGORY = 'tb_attribute_category';
|
||||||
|
const ATTRIBUTE_VALUE = 'tb_attribute_value';
|
||||||
|
|
||||||
|
const ARTICLE = 'tb_article';
|
||||||
|
const ARTICLE_COMMENT = 'tb_article_comment';
|
||||||
|
const ARTICLE_CATEGORY = 'tb_article_category';
|
||||||
|
const ARTICLE_INFO = 'tb_article_info';
|
||||||
|
const ARTICLE_PER_CATEGORY = 'tb_article_per_category';
|
||||||
|
const ARTICLE_IMAGE = 'tb_article_image';
|
||||||
|
|
||||||
|
const BRAND = 'tb_brand';
|
||||||
|
|
||||||
|
const COUPON = 'tb_coupon';
|
||||||
|
const COUPON_USE = 'tb_coupon_use';
|
||||||
|
|
||||||
|
const DEAL = 'tb_deal';
|
||||||
|
const DEAL_CONTENT = 'tb_deal_content';
|
||||||
|
|
||||||
|
const COMBO_DEAL = 'tb_combo_deal';
|
||||||
|
const COMBO_DEAL_DETAIL = 'tb_combo_deal_detail';
|
||||||
|
|
||||||
|
const ORDER = 'tb_order';
|
||||||
|
const ORDER_ITEM = 'tb_order_detail_new';
|
||||||
|
|
||||||
|
const CUSTOMER_POINT = "tb_customer_point";
|
||||||
|
const CUSTOMER = "tb_customer";
|
||||||
|
const CUSTOMER_GROUP = "tb_customer_group";
|
||||||
|
const CUSTOMER_PER_GROUP = 'tb_customer_group_list';
|
||||||
|
const CUSTOMER_ADDRESS = "tb_customer_address";
|
||||||
|
|
||||||
|
const CUSTOMER_UPLOAD = 'tb_user_upload';
|
||||||
|
const CUSTOMER_LIKE = 'tb_user_like';
|
||||||
|
const CUSTOMER_PREFERENCE = "tb_user_preference";
|
||||||
|
const CUSTOMER_CONTACT = "tb_customer_contact";
|
||||||
|
|
||||||
|
const WEB_USER = "tb_web_user_info";
|
||||||
|
|
||||||
|
const PAYGATE = 'tb_paygate';
|
||||||
|
const PAY_METHOD = 'tb_pay_method';
|
||||||
|
const SHIP_METHOD = 'tb_shipping_method';
|
||||||
|
|
||||||
|
const REPORT_VISIT_SUMMARY = 'tb_visit_summary';
|
||||||
|
const REPORT_ERROR_PAGE = 'tb_report_error_page';
|
||||||
|
const REPORT_REFERER_DOMAIN = 'tb_visit_referer_domain';
|
||||||
|
const REPORT_REFERER_SUMMARY = 'tb_visit_referer_summary';
|
||||||
|
|
||||||
|
const PROVINCE = "tb_province_list";
|
||||||
|
const PROVINCE_DISTRICT = "tb_province_district_list";
|
||||||
|
const PROVINCE_WARD = "tb_province_ward_list";
|
||||||
|
|
||||||
|
const DOMAIN = 'tb_domain';
|
||||||
|
const URL = 'tb_url';
|
||||||
|
const URL_META = 'tb_url_meta';
|
||||||
|
|
||||||
|
const STAFF = "tb_staff";
|
||||||
|
const DEPARTMENT = 'tb_department';
|
||||||
|
const STAFF_LOG = "tb_staff_log";
|
||||||
|
|
||||||
|
}
|
||||||
37
inc/Hura8/Interfaces/iClientERP.php
Normal file
37
inc/Hura8/Interfaces/iClientERP.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Created by Glee Ltd.
|
||||||
|
* Description: interface to work with our clients
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
|
||||||
|
interface iClientERP
|
||||||
|
{
|
||||||
|
public function createOrder(array $order_info) ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get log data
|
||||||
|
*/
|
||||||
|
public function getLog($type, $limit = 50);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* log data
|
||||||
|
*/
|
||||||
|
public function log($type, array $data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: clean any existing data before populate new ones
|
||||||
|
*/
|
||||||
|
public function cleanExistingData();
|
||||||
|
|
||||||
|
// get summary of products in the system
|
||||||
|
public function getProductSummary();
|
||||||
|
|
||||||
|
// save product from erp to tmp tables
|
||||||
|
public function saveProductToWeb(array $erp_product_list);
|
||||||
|
|
||||||
|
// start sync tmp tables to actual tables
|
||||||
|
public function syncProductToWeb(array $options = []);
|
||||||
|
}
|
||||||
10
inc/Hura8/Interfaces/iCustomUrlBuilder.php
Normal file
10
inc/Hura8/Interfaces/iCustomUrlBuilder.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
interface iCustomUrlBuilder
|
||||||
|
{
|
||||||
|
public function productUrl($product_id, array $input = []);
|
||||||
|
public function productCategoryUrl($category_id, array $input = []);
|
||||||
|
public function articleUrl($article_id, array $input = []);
|
||||||
|
}
|
||||||
13
inc/Hura8/Interfaces/iERPProvider.php
Normal file
13
inc/Hura8/Interfaces/iERPProvider.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface must be implemented by all erp providers as in package/provider/Provider/ERPProviders
|
||||||
|
*/
|
||||||
|
interface iERPProvider
|
||||||
|
{
|
||||||
|
public function createOrder(array $order_info);
|
||||||
|
public function getProductList(array $options = [], $debug=false);
|
||||||
|
public function getProductSummary(array $options = [], $debug=false);
|
||||||
|
}
|
||||||
20
inc/Hura8/Interfaces/iEmail.php
Normal file
20
inc/Hura8/Interfaces/iEmail.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Created by Glee Ltd.
|
||||||
|
* User: Hieu
|
||||||
|
* Date: 14-Jul-19
|
||||||
|
* Time: 3:21 PM
|
||||||
|
* Description:
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
|
||||||
|
interface iEmail
|
||||||
|
{
|
||||||
|
// setup
|
||||||
|
public function setUp(array $config);
|
||||||
|
|
||||||
|
// send email
|
||||||
|
public function send(array $to_emails, $subject, $content);
|
||||||
|
}
|
||||||
9
inc/Hura8/Interfaces/iEntityAdminCategoryController.php
Normal file
9
inc/Hura8/Interfaces/iEntityAdminCategoryController.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
interface iEntityAdminCategoryController extends iEntityCategoryController, iEntityAdminController
|
||||||
|
{
|
||||||
|
public function getDropBox($selectedId, $categoryParentId, $level=1);
|
||||||
|
public function categorySelectBox(array $array_selected, $categoryParentId, $level=1);
|
||||||
|
}
|
||||||
11
inc/Hura8/Interfaces/iEntityAdminController.php
Normal file
11
inc/Hura8/Interfaces/iEntityAdminController.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
interface iEntityAdminController extends iEntityController
|
||||||
|
{
|
||||||
|
public function create(array $info) : AppResponse;
|
||||||
|
public function update($id, array $info) : AppResponse;
|
||||||
|
public function delete($id) : AppResponse;
|
||||||
|
public function getEmptyInfo(array $additional_fields = []): array;
|
||||||
|
}
|
||||||
9
inc/Hura8/Interfaces/iEntityCategoryController.php
Normal file
9
inc/Hura8/Interfaces/iEntityCategoryController.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
interface iEntityCategoryController extends iEntityController
|
||||||
|
{
|
||||||
|
public function getAllParent(array $condition = []);
|
||||||
|
public function getNestedCategories($is_public = false);
|
||||||
|
}
|
||||||
8
inc/Hura8/Interfaces/iEntityCategoryModel.php
Normal file
8
inc/Hura8/Interfaces/iEntityCategoryModel.php
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
interface iEntityCategoryModel extends iEntityModel
|
||||||
|
{
|
||||||
|
public function getAllByParent(array $condition = array()): array;
|
||||||
|
}
|
||||||
26
inc/Hura8/Interfaces/iEntityController.php
Normal file
26
inc/Hura8/Interfaces/iEntityController.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
interface iEntityController
|
||||||
|
{
|
||||||
|
public function getListByIds(array $list_id, array $condition = array()): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description utility to inspect the actual filters which will be used in getList by Controller
|
||||||
|
* @param array $raw_filter_condition
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getActualFilterCondition(array $raw_filter_condition) : array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description utility to inspect the actual filters which will be used in getList by Model
|
||||||
|
* @param array $raw_filter_condition
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getModelFilterCondition(array $raw_filter_condition) : array;
|
||||||
|
|
||||||
|
public function getList(array $condition): array;
|
||||||
|
public function getTotal(array $condition): int;
|
||||||
|
public function getInfo($id): ?array;
|
||||||
|
}
|
||||||
38
inc/Hura8/Interfaces/iEntityLanguageModel.php
Normal file
38
inc/Hura8/Interfaces/iEntityLanguageModel.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
interface iEntityLanguageModel
|
||||||
|
{
|
||||||
|
// create a necessary language table to hold the translated data for the language
|
||||||
|
public function createTableLang(): AppResponse;
|
||||||
|
|
||||||
|
public function setLanguage(string $language): bool;
|
||||||
|
|
||||||
|
// any fields to have language: title, summary, description, price, etc...
|
||||||
|
public function setLanguageFields(array $language_fields);
|
||||||
|
|
||||||
|
public function getLanguageFields() : array ;
|
||||||
|
|
||||||
|
public function getEntityType() : string ;
|
||||||
|
|
||||||
|
public function update($id, array $new_input_info, $search_keyword = "") : AppResponse;
|
||||||
|
|
||||||
|
public function deleteAll($id): AppResponse;
|
||||||
|
|
||||||
|
public function delete($id): AppResponse;
|
||||||
|
|
||||||
|
public function getListByIds(array $list_id): array;
|
||||||
|
|
||||||
|
// get list of ids which are in the language table. This is to filter which records have been translated and which not in the main table
|
||||||
|
// this is limited to 50k items. Which means for item_type having more than 50k records, this checking method is not suitable
|
||||||
|
public function getTranslatedIds() : array;
|
||||||
|
|
||||||
|
public function getInfo($id): ?array;
|
||||||
|
|
||||||
|
public function getTotal(array $condition): int;
|
||||||
|
|
||||||
|
// get empty/default item for form
|
||||||
|
public function getEmptyInfo(): array;
|
||||||
|
|
||||||
|
}
|
||||||
14
inc/Hura8/Interfaces/iEntityModel.php
Normal file
14
inc/Hura8/Interfaces/iEntityModel.php
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
interface iEntityModel
|
||||||
|
{
|
||||||
|
public function getEntityType() : string ;
|
||||||
|
public function getListByIds(array $list_id, array $condition = array()) : array;
|
||||||
|
public function getList(array $condition) : array;
|
||||||
|
public function getTotal(array $condition) : int;
|
||||||
|
public function getQueryCondition(array $condition) : array;
|
||||||
|
public function getInfo($id): ?array;
|
||||||
|
public function getEmptyInfo(array $additional_fields = []): array;
|
||||||
|
}
|
||||||
12
inc/Hura8/Interfaces/iEntityPermission.php
Normal file
12
inc/Hura8/Interfaces/iEntityPermission.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
interface iEntityPermission
|
||||||
|
{
|
||||||
|
public function canCreate(): bool;
|
||||||
|
public function canUpdate(): bool;
|
||||||
|
public function canDelete(): bool;
|
||||||
|
public function canView(): bool;
|
||||||
|
public function canApprove(): bool;
|
||||||
|
}
|
||||||
22
inc/Hura8/Interfaces/iEntityStatistic.php
Normal file
22
inc/Hura8/Interfaces/iEntityStatistic.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
interface iEntityStatistic
|
||||||
|
{
|
||||||
|
// get data
|
||||||
|
public function getListComment(array $condition): array;
|
||||||
|
public function getTotalComment(array $condition) : int;
|
||||||
|
public function getInfoComment($id): ?array;
|
||||||
|
|
||||||
|
public function getListReview(array $condition): array;
|
||||||
|
public function getTotalReview(array $condition) : int;
|
||||||
|
public function getInfoReview($id): ?array;
|
||||||
|
|
||||||
|
// update from public
|
||||||
|
public function updateCommentCount($id, int $total=0, int $avg_rate=0): AppResponse;
|
||||||
|
public function updateReviewCount($id, int $total=0, int $avg_rate=0): AppResponse;
|
||||||
|
public function updateVisitCount($id, int $total): AppResponse;
|
||||||
|
public function updateLikeCount($id, int $total): AppResponse;
|
||||||
|
public function updateSaveCount($id, int $total): AppResponse;
|
||||||
|
}
|
||||||
20
inc/Hura8/Interfaces/iExcelDownload.php
Normal file
20
inc/Hura8/Interfaces/iExcelDownload.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Created by Glee Ltd.
|
||||||
|
* User: Hieu
|
||||||
|
* Date: 25-Apr-19
|
||||||
|
* Time: 11:16 AM
|
||||||
|
* Description:
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
|
||||||
|
interface iExcelDownload
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param array $options
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function start(array $options);
|
||||||
|
}
|
||||||
38
inc/Hura8/Interfaces/iPayGate.php
Normal file
38
inc/Hura8/Interfaces/iPayGate.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Created by Glee Ltd.
|
||||||
|
* User: Hieu
|
||||||
|
* Date: 19-Jun-19
|
||||||
|
* Time: 1:29 PM
|
||||||
|
* Description:
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
|
||||||
|
interface iPayGate
|
||||||
|
{
|
||||||
|
// create an pay url to redirect users from website to the payment gateway's site
|
||||||
|
public function createPayUrl(array $config);
|
||||||
|
|
||||||
|
// process the result return by paygate
|
||||||
|
//public function processPayResult();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create payid to sent to vnpay and also track in the system. One order can be paid by multiple installments
|
||||||
|
*
|
||||||
|
* @param $item_type string 'order', 'wait-order',
|
||||||
|
* @param $item_id int id of order
|
||||||
|
* @param $expected_amount int the amount to pay in this payment
|
||||||
|
* @param $description string
|
||||||
|
* @param $user_info array
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function createPayId($item_type, $item_id, $expected_amount, $description, array $user_info);
|
||||||
|
|
||||||
|
// process the webhook sent by paygate's server
|
||||||
|
public function processWebhook();
|
||||||
|
|
||||||
|
// get list of supported banks
|
||||||
|
//public function getBankList();
|
||||||
|
}
|
||||||
22
inc/Hura8/Interfaces/iPricing.php
Normal file
22
inc/Hura8/Interfaces/iPricing.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Created by Glee Ltd.
|
||||||
|
* User: Hieu
|
||||||
|
* Date: 18-Jan-19
|
||||||
|
* Time: 11:03 AM
|
||||||
|
* Description:
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
|
||||||
|
use Hura8\User\UProduct;
|
||||||
|
|
||||||
|
interface iPricing
|
||||||
|
{
|
||||||
|
// apply for a list of items and return modified list
|
||||||
|
public function applyForList(array $item_list, UProduct $objUProduct);
|
||||||
|
|
||||||
|
// apply for a item and return modified item
|
||||||
|
public function applyForItem(array $item_info, UProduct $objUProduct);
|
||||||
|
}
|
||||||
26
inc/Hura8/Interfaces/iProductPromotionProgram.php
Normal file
26
inc/Hura8/Interfaces/iProductPromotionProgram.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Created by Glee Ltd.
|
||||||
|
* User: Hieu
|
||||||
|
* Date: 28-Jun-19
|
||||||
|
* Time: 3:26 PM
|
||||||
|
* Description:
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
|
||||||
|
interface iProductPromotionProgram
|
||||||
|
{
|
||||||
|
public function activate($id, $status);
|
||||||
|
|
||||||
|
public function updateSetting($id, array $settings);
|
||||||
|
|
||||||
|
public function updateProductSetting($program_id, $product_id, array $settings);
|
||||||
|
|
||||||
|
public function addProduct($program_id, $product_id, array $settings);
|
||||||
|
|
||||||
|
public function removeProduct($program_id, $product_id);
|
||||||
|
|
||||||
|
public function getProductList($program_id);
|
||||||
|
}
|
||||||
12
inc/Hura8/Interfaces/iPublicEntityController.php
Normal file
12
inc/Hura8/Interfaces/iPublicEntityController.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
interface iPublicEntityController
|
||||||
|
{
|
||||||
|
// get data
|
||||||
|
public function getListByIds(array $list_id, array $condition = array()): array;
|
||||||
|
public function getList(array $condition): array;
|
||||||
|
public function getTotal(array $condition) : int;
|
||||||
|
public function getInfo($id): ?array;
|
||||||
|
}
|
||||||
16
inc/Hura8/Interfaces/iSMS.php
Normal file
16
inc/Hura8/Interfaces/iSMS.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Created by Glee Ltd.
|
||||||
|
* User: Hieu
|
||||||
|
* Date: 10-Oct-18
|
||||||
|
* Time: 11:24 AM
|
||||||
|
* Description:
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
|
||||||
|
interface iSMS
|
||||||
|
{
|
||||||
|
public function send($mobile, $content, $debug = false);
|
||||||
|
}
|
||||||
54
inc/Hura8/Interfaces/iSearch.php
Normal file
54
inc/Hura8/Interfaces/iSearch.php
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
|
||||||
|
interface iSearch
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @description get filter fields
|
||||||
|
* @param array[string]
|
||||||
|
*/
|
||||||
|
public function getFilterFields(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description get fulltext fields
|
||||||
|
* @param array[string]
|
||||||
|
*/
|
||||||
|
public function getFulltextFields(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description update or create item if not exist
|
||||||
|
* @param $item_id
|
||||||
|
* @param array $table_field_values
|
||||||
|
$table_field_values = [
|
||||||
|
"tb_product.price" => 2000000,
|
||||||
|
"tb_product.title" => "Máy tính ABC",
|
||||||
|
"tb_category.price" => "Máy tính",
|
||||||
|
];
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function updateItem($item_id, array $table_field_values = []): AppResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description delete item from index
|
||||||
|
* @param $item_id
|
||||||
|
*/
|
||||||
|
public function deleteItem($item_id): AppResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description delete items from index
|
||||||
|
* @param $item_list_ids
|
||||||
|
*/
|
||||||
|
public function deleteItems(array $item_list_ids): AppResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $keyword
|
||||||
|
* @param array $field_filters
|
||||||
|
* @param array $fulltext_fields
|
||||||
|
* @param int $limit_result
|
||||||
|
* @return array[int]
|
||||||
|
*/
|
||||||
|
public function find(string $keyword, array $field_filters = [], array $fulltext_fields = [], int $limit_result = 2000) : array;
|
||||||
|
|
||||||
|
}
|
||||||
15
inc/Hura8/Interfaces/iShippingCost.php
Normal file
15
inc/Hura8/Interfaces/iShippingCost.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
|
||||||
|
interface iShippingCost extends iShippingProvider
|
||||||
|
{
|
||||||
|
// get only options for a cart
|
||||||
|
public function getShippingOptionsForCart(array $cart_item_list, $province, $district, $ward=0) : array;
|
||||||
|
|
||||||
|
// return array [shipping_fee, cod_fee]
|
||||||
|
public function calculateShippingCostForOrder($selected_shipping_option, array $shipping_address, array $order_info) ;
|
||||||
|
|
||||||
|
}
|
||||||
13
inc/Hura8/Interfaces/iShippingProvider.php
Normal file
13
inc/Hura8/Interfaces/iShippingProvider.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
interface iShippingProvider
|
||||||
|
{
|
||||||
|
// get all available shipping options, which might or might not be suitable for an order
|
||||||
|
public function getAllShippingOptions(array $args) : array;
|
||||||
|
|
||||||
|
// get only options for a specific order (based on order's value, shipping address ...) as suggested by the shipping provider
|
||||||
|
public function getShippingOptions(array $args) : array;
|
||||||
|
|
||||||
|
}
|
||||||
11
inc/Hura8/Interfaces/iSyncWebErpProduct.php
Normal file
11
inc/Hura8/Interfaces/iSyncWebErpProduct.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\Interfaces;
|
||||||
|
|
||||||
|
interface iSyncWebErpProduct
|
||||||
|
{
|
||||||
|
public function erpToWebProductVariant();
|
||||||
|
public function erpToWebProduct();
|
||||||
|
public function webToErpProduct();
|
||||||
|
public function getErpTotalProduct();
|
||||||
|
}
|
||||||
152
inc/Hura8/System/APIClient.php
Normal file
152
inc/Hura8/System/APIClient.php
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Created by Glee Ltd.
|
||||||
|
* User: Hieu
|
||||||
|
* Date: 22-Apr-19
|
||||||
|
* Time: 11:18 AM
|
||||||
|
* Description:
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hura8\System;
|
||||||
|
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Exception\ClientException;
|
||||||
|
use GuzzleHttp\Exception\ServerException;
|
||||||
|
|
||||||
|
final class APIClient
|
||||||
|
{
|
||||||
|
private $headers = [];
|
||||||
|
|
||||||
|
/* @var $client \GuzzleHttp\Client */
|
||||||
|
protected $client;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct($endpoint, $timeout = 10)
|
||||||
|
{
|
||||||
|
$this->client = new Client([
|
||||||
|
// Base URI is used with relative requests
|
||||||
|
'base_uri' => $endpoint,
|
||||||
|
// You can set any number of default request options.
|
||||||
|
'timeout' => $timeout,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function post($path, array $parameters) {
|
||||||
|
return $this->call_service("post", $path, $parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function get($path, array $parameters) {
|
||||||
|
return $this->call_service("get", $path, $parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function setHeaders(array $headers) {
|
||||||
|
// Authorization:
|
||||||
|
/*$headers = [
|
||||||
|
'Authorization' => "Basic ". base64_encode($this->api_key),
|
||||||
|
];*/
|
||||||
|
$this->headers = $headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ref: http://docs.guzzlephp.org/en/stable/quickstart.html#query-string-parameters
|
||||||
|
// make a consistent call with the same $payload format: ["key" => "value", ...]
|
||||||
|
/*
|
||||||
|
return json {
|
||||||
|
"errCode" => 1|0, (1=error, 0=success)
|
||||||
|
"msg" => "error_message" | "",
|
||||||
|
}*/
|
||||||
|
protected function call_service($http_method, $path, array $payload) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// get
|
||||||
|
if( $http_method == 'get' ) {
|
||||||
|
|
||||||
|
$response = $this->client->request('GET', $path, [
|
||||||
|
'headers' => $this->headers,
|
||||||
|
'query' => $payload
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// post
|
||||||
|
else {
|
||||||
|
|
||||||
|
$request_options = $this->buildPostRequestOptions($this->headers, $payload);
|
||||||
|
$response = $this->client->request('POST', $path, $request_options);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"errCode" => "0",
|
||||||
|
"msg" => $response->getBody()->getContents(),
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (ServerException $e) {
|
||||||
|
$response = $e->getResponse();
|
||||||
|
//$errors = \json_decode($response->getBody()->getContents(), true);
|
||||||
|
return [
|
||||||
|
"errCode" => 1,
|
||||||
|
"msg" => $response->getBody()->getContents(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
catch (ClientException $e) {
|
||||||
|
$response = $e->getResponse();
|
||||||
|
//$errors = \json_decode($response->getBody()->getContents(), true);
|
||||||
|
return [
|
||||||
|
"errCode" => 2,
|
||||||
|
"msg" => $response->getBody()->getContents(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
catch (\Exception $e) {
|
||||||
|
return [
|
||||||
|
"errCode" => 3,
|
||||||
|
"msg" => "Exception: ".$e->getMessage(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ref: http://docs.guzzlephp.org/en/stable/request-options.html
|
||||||
|
// form_params cannot be used with the multipart option. You will need to use one or the other.
|
||||||
|
// Use form_params for application/x-www-form-urlencoded requests, and multipart for multipart/form-data requests.
|
||||||
|
protected function buildPostRequestOptions(array $headers, array $payload) {
|
||||||
|
$content_type = isset($headers["Content-Type"]) ? $headers["Content-Type"] : "";
|
||||||
|
|
||||||
|
if($content_type == "application/x-www-form-urlencoded") {
|
||||||
|
return [
|
||||||
|
'headers' => $headers,
|
||||||
|
'form_params' => $payload,
|
||||||
|
//'debug' => true
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if($content_type == "application/json") {
|
||||||
|
return [
|
||||||
|
'headers' => $headers,
|
||||||
|
'json' => $payload,
|
||||||
|
//'body' => json_encode($payload),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// reformat the payload for multipart
|
||||||
|
$multipart_request = [];
|
||||||
|
foreach ($payload as $key => $value) {
|
||||||
|
if( ! $key) continue;
|
||||||
|
|
||||||
|
$multipart_request[] = [
|
||||||
|
'name' => $key,
|
||||||
|
'contents' => (is_array($value)) ? json_encode($value) : $value,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// multipart/form-data
|
||||||
|
return [
|
||||||
|
'headers' => $headers,
|
||||||
|
'multipart' => $multipart_request,
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
152
inc/Hura8/System/CDNFileUpload.php
Normal file
152
inc/Hura8/System/CDNFileUpload.php
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Created by Hurasoft.
|
||||||
|
* Date: 28-May-2022
|
||||||
|
* Description: Use this class to send local files to the cdn host (endpoint: cdn.host/file_upload_handle.php). The cdn host also has a copy of Hura8 main class to receive the uploaded files
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hura8\System;
|
||||||
|
|
||||||
|
|
||||||
|
class CDNFileUpload {
|
||||||
|
|
||||||
|
//protected $upload_handle = 'http://local.hura8/file_upload_handle.php';
|
||||||
|
protected $upload_handle = STATIC_DOMAIN . '/file_upload_handle.php';
|
||||||
|
|
||||||
|
protected $user_id = 0;
|
||||||
|
|
||||||
|
protected $target_path_item_type_mapping = [
|
||||||
|
'product' => 'media/product', // media/product
|
||||||
|
'article' => 'media/news', // media/news
|
||||||
|
'brand' => 'media/brand',
|
||||||
|
'category' => 'media/category',
|
||||||
|
'banner' => 'media/banner',
|
||||||
|
'lib' => 'media/lib/',
|
||||||
|
'user_upload' => 'media/user_upload',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct($user_id) {
|
||||||
|
$this->user_id = $user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: an implementation of start method to upload an item's image
|
||||||
|
* @param $item_type string
|
||||||
|
* @param $content string
|
||||||
|
* @param $file_name string
|
||||||
|
* @return array | boolean
|
||||||
|
*/
|
||||||
|
public function uploadFile($item_type, $file_name, $content, $set_target_path = '')
|
||||||
|
{
|
||||||
|
//$file_name = substr(strrchr($img_url, "/"), 1);
|
||||||
|
//$content = get_url_content($img_url);
|
||||||
|
|
||||||
|
$header_setting = [
|
||||||
|
// 'Authorization' => "Basic ". base64_encode(API_KEY),
|
||||||
|
];
|
||||||
|
|
||||||
|
if($set_target_path) {
|
||||||
|
$target_path = $set_target_path;
|
||||||
|
}else{
|
||||||
|
$target_path = (isset($this->target_path_item_type_mapping[$item_type])) ? $this->target_path_item_type_mapping[$item_type] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$multipart_settings = [
|
||||||
|
[
|
||||||
|
'name' => 'upload_method',
|
||||||
|
'contents' => 'content',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'file_name',
|
||||||
|
'contents' => $file_name,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'target_path',
|
||||||
|
'contents' => $target_path,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'file_content',
|
||||||
|
'contents' => $content,
|
||||||
|
],
|
||||||
|
|
||||||
|
//for upload server
|
||||||
|
[
|
||||||
|
'name' => 'uid',
|
||||||
|
'contents' => $this->user_id,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'time',
|
||||||
|
'contents' => CURRENT_TIME,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'token',
|
||||||
|
'contents' => $this->createToken($this->user_id, CURRENT_TIME),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => 'item_type',
|
||||||
|
'contents' => $item_type ,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->start($header_setting, $multipart_settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
let settings = {
|
||||||
|
element : '',//element id to click on
|
||||||
|
holder : '', //id file holder
|
||||||
|
form_name : '',
|
||||||
|
item_type : '',
|
||||||
|
item_id : '',
|
||||||
|
file_type : '', //what is the file type: doc, photo, video ?
|
||||||
|
file_field : '', //what field in the item table this file is used for ?
|
||||||
|
resize : '200,400,600' //sizes to resize
|
||||||
|
square: '200,400', //sizes to be square
|
||||||
|
keep_width : '400', //sizes to keep the width
|
||||||
|
max_file: 1
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: a general method for upload
|
||||||
|
* @param array $header_setting
|
||||||
|
* @param array $multipart_settings
|
||||||
|
* @return array | boolean
|
||||||
|
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||||
|
*/
|
||||||
|
protected function start(array $header_setting = [], array $multipart_settings = []) {
|
||||||
|
$client = new \GuzzleHttp\Client();
|
||||||
|
|
||||||
|
$request = $client->request('POST', $this->upload_handle, [
|
||||||
|
'headers' => $header_setting,
|
||||||
|
'multipart' => $multipart_settings
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $request->getBody()->getContents();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// return string | boolean
|
||||||
|
protected function getAssetType($file_name) {
|
||||||
|
$ext = strtolower(strrchr($file_name, "."));
|
||||||
|
|
||||||
|
if( $ext == '.css') return 'css';
|
||||||
|
|
||||||
|
if( $ext == '.js') return 'js';
|
||||||
|
|
||||||
|
if( \in_array($ext, ['.jpg', '.jpeg', '.gif', '.png', '.ico'])) return 'image';
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// this salt and createToken must be the same ones as in Hura8/Base/CDNFileUploadHandle
|
||||||
|
// and different per project (or else others will use it to upload forbidden files on our clients' hosting)
|
||||||
|
const SECURITY_SALT = 'ahss@3asdaaSDFSD';
|
||||||
|
|
||||||
|
protected function createToken($id, $time) {
|
||||||
|
return sha1(join("-", [$id , $time , static::SECURITY_SALT]));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
275
inc/Hura8/System/CDNFileUploadHandle.php
Normal file
275
inc/Hura8/System/CDNFileUploadHandle.php
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Created by Hurasoft
|
||||||
|
* Date: 28-May-2022
|
||||||
|
* Time: 1:44 PM
|
||||||
|
* Description: Use this class in the cdn host to handle the upload of files from Hura8's admin and put the uploaded file in the cdn host's local directory
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hura8\System;
|
||||||
|
|
||||||
|
|
||||||
|
class CDNFileUploadHandle
|
||||||
|
{
|
||||||
|
|
||||||
|
const SECURITY_SALT = 'ahss@3asdaaSDFSD';
|
||||||
|
|
||||||
|
//default config
|
||||||
|
protected $config = [
|
||||||
|
//1. file settings
|
||||||
|
"max_file_size" => 2000000,//bytes ~ 1MB
|
||||||
|
'allowed_file_types' => [
|
||||||
|
'image' => ['.jpeg', '.jpg', '.gif', '.png'],
|
||||||
|
'script' => ['.css', '.js'],
|
||||||
|
],
|
||||||
|
|
||||||
|
//2. uploaded by client
|
||||||
|
'uid' => '',
|
||||||
|
'token' => '',
|
||||||
|
'time' => '',
|
||||||
|
'item_type' => 'product', // product|article|media
|
||||||
|
'target_path' => '',
|
||||||
|
'upload_method' => 'content', //file || content
|
||||||
|
"file_content" => "", //only needed if upload_method = content
|
||||||
|
"file_name" => "",//only needed if upload_method = content
|
||||||
|
];
|
||||||
|
private $tmp_file_prop = null; //array, temporary file's props in tmp folder
|
||||||
|
private $accepted_file_input_names = [
|
||||||
|
"file", //<input type=file name="file">
|
||||||
|
//"qqfile", //name use for files uploaded through FineUploader library
|
||||||
|
];
|
||||||
|
private $user_tmp_dir = '';
|
||||||
|
|
||||||
|
public function __construct(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createToken($id, $time) {
|
||||||
|
return sha1(join("-", [$id , $time , static::SECURITY_SALT]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function start() {
|
||||||
|
|
||||||
|
$this->_get_post_info();
|
||||||
|
|
||||||
|
if(!$this->validateUser()) {
|
||||||
|
return $this->set_return_result('error', 'User failed to verify', []);
|
||||||
|
}
|
||||||
|
|
||||||
|
//receive files
|
||||||
|
$file = $this->receive_file();
|
||||||
|
|
||||||
|
//return file-location to upload API to return to client application
|
||||||
|
return $this->set_return_result('success', 'Upload succeeded', $file );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function validateUser() {
|
||||||
|
return ($this->config['token'] == $this->createToken($this->config['uid'], $this->config['time']));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _get_post_info() {
|
||||||
|
$expected_keys = [
|
||||||
|
'upload_method',
|
||||||
|
'file_name',
|
||||||
|
'target_path',
|
||||||
|
'file_content',
|
||||||
|
'uid',
|
||||||
|
'time',
|
||||||
|
'token',
|
||||||
|
'item_type',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($expected_keys as $key) {
|
||||||
|
$this->config[$key] = isset($_POST[$key]) ? $_POST[$key] : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//return boolean
|
||||||
|
protected function remove_tmp_file() {
|
||||||
|
$this->deleteDirectory($this->user_tmp_dir);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//return boolean
|
||||||
|
protected function validate_file() {
|
||||||
|
//$this->tmp_file_prop
|
||||||
|
//validate file size
|
||||||
|
if($this->config['max_file_size'] && $this->config['max_file_size'] < $this->tmp_file_prop['size']) {
|
||||||
|
return 'Size too large';
|
||||||
|
}
|
||||||
|
|
||||||
|
//validate allowed extension: allow image but upload .docx files
|
||||||
|
$has_extension = false;
|
||||||
|
$file_type = '';
|
||||||
|
foreach ( $this->config['allowed_file_types'] as $group => $group_ext ) {
|
||||||
|
if(in_array( $this->tmp_file_prop['ext'], $group_ext )) {
|
||||||
|
$has_extension = true;
|
||||||
|
$file_type = $group;
|
||||||
|
break ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( ! $has_extension ) return "File type not allowed";
|
||||||
|
|
||||||
|
//validate claimed extension: claim image but not actual image
|
||||||
|
$full_file_path = $this->tmp_file_prop['tmp_location'] . DIRECTORY_SEPARATOR . $this->tmp_file_prop['name'];
|
||||||
|
if( $file_type == 'image' && ! v::image()->validate( $full_file_path )) {
|
||||||
|
return "Not actual image";
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//return mixed : array $tmp_file_prop or false
|
||||||
|
protected function receive_file() {
|
||||||
|
|
||||||
|
//check upload method
|
||||||
|
if( $this->config['upload_method'] == 'content' ) {
|
||||||
|
//upload by content - file created on server
|
||||||
|
$file_ext = $this->get_ext($this->config['file_name']);
|
||||||
|
$upload_folder = $this->create_upload_folder() ;
|
||||||
|
|
||||||
|
if( ! $this->move_file($upload_folder)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"ext" => $file_ext,
|
||||||
|
"name" => $this->config['file_name'],
|
||||||
|
"folder" => $upload_folder,
|
||||||
|
"size" => strlen($this->config['file_content']),
|
||||||
|
];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
//upload by file
|
||||||
|
//get list of files to be uploaded
|
||||||
|
$file_uploaded = null;
|
||||||
|
foreach ($this->accepted_file_input_names as $_name) {
|
||||||
|
if(isset($_FILES[$_name])) {
|
||||||
|
$file_uploaded = $_FILES[$_name];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! $file_uploaded ) return false;
|
||||||
|
|
||||||
|
//if file name too long, then error
|
||||||
|
if ( strlen($file_uploaded['name']) > 225 ) return false;
|
||||||
|
|
||||||
|
//move file to tmp folder
|
||||||
|
$file_ext = $this->get_ext($file_uploaded['name']);
|
||||||
|
$upload_folder = $this->create_upload_folder() ;
|
||||||
|
|
||||||
|
if( ! $this->move_file($upload_folder) ){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"ext" => $file_ext,
|
||||||
|
"name" => $this->config['file_name'],
|
||||||
|
"folder" => $upload_folder,
|
||||||
|
"size" => $file_uploaded['size'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function set_return_result($status = 'success', $message = 'Upload success', $content = []) {
|
||||||
|
return [
|
||||||
|
"status" => $status,
|
||||||
|
"message" => $message,
|
||||||
|
"files" => $content,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
//create upload folder for user: use current date, user id or app id based on $this->config
|
||||||
|
private function create_upload_folder() {
|
||||||
|
//$build_folder = [$this->tmp_dir];
|
||||||
|
/*$user_id = (isset($this->config['user_id'])) ? intval($this->config['user_id']) : null;
|
||||||
|
if($user_id) {
|
||||||
|
$build_folder[] = $user_id;
|
||||||
|
}
|
||||||
|
$build_folder[] = date("Ymd");*/
|
||||||
|
|
||||||
|
//rebuild upload folder
|
||||||
|
$build_folder = array_filter(explode("/", $this->config['target_path']));
|
||||||
|
$folder = join("/", $build_folder);
|
||||||
|
$this->createDir($folder);
|
||||||
|
|
||||||
|
return $folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
//get a file's extension
|
||||||
|
private function get_ext($fileName){
|
||||||
|
return strtolower(strrchr($fileName, '.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if find is image
|
||||||
|
private function isImage($fileName) {
|
||||||
|
return (in_array( $this->get_ext($fileName), ['.jpeg', '.jpg', '.gif', '.png'] ));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//for security reason: only allow a-z0-9_- in file name (no other dot . except for the extension)
|
||||||
|
//example: bad-file.php.js -> bad-filephp.js
|
||||||
|
private function rename_file($uploaded_file_name, $ext) {
|
||||||
|
$new_name = preg_replace("/[^a-z0-9_\-]/i", "", str_replace( strrchr($uploaded_file_name, '.'), "", $uploaded_file_name ) ) . $ext;
|
||||||
|
|
||||||
|
return strtolower($new_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function move_file($folder) {
|
||||||
|
$new_file = $folder . '/' . $this->config['file_name'];
|
||||||
|
|
||||||
|
if( $this->config['upload_method'] == 'content' ) {
|
||||||
|
// image upload by content
|
||||||
|
if( $this->isImage($new_file) ) {
|
||||||
|
//Store in the filesystem.
|
||||||
|
$fp = fopen($new_file, "w+");
|
||||||
|
$status = fwrite($fp, $this->config['file_content']);
|
||||||
|
fclose($fp);
|
||||||
|
|
||||||
|
return ($status !== false);
|
||||||
|
}
|
||||||
|
// file upload
|
||||||
|
else if( file_put_contents($new_file, $this->config['file_content'])){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function createDir($path, $folder_permission = 0750){
|
||||||
|
if(file_exists($path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mkdir($path, $folder_permission, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function deleteDirectory($dirPath) {
|
||||||
|
if (!is_dir($dirPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$objects = scandir($dirPath);
|
||||||
|
foreach ($objects as $object) {
|
||||||
|
if ($object != "." && $object !="..") {
|
||||||
|
if (filetype($dirPath . DIRECTORY_SEPARATOR . $object) == "dir") {
|
||||||
|
$this->deleteDirectory($dirPath . DIRECTORY_SEPARATOR . $object);
|
||||||
|
} else {
|
||||||
|
unlink($dirPath . DIRECTORY_SEPARATOR . $object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rmdir($dirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
100
inc/Hura8/System/Cache.php
Normal file
100
inc/Hura8/System/Cache.php
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System;
|
||||||
|
|
||||||
|
|
||||||
|
use Symfony\Component\Cache\Adapter\RedisAdapter;
|
||||||
|
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||||
|
|
||||||
|
|
||||||
|
class Cache
|
||||||
|
{
|
||||||
|
|
||||||
|
private static $redisCache = null;
|
||||||
|
private static $fileCache = null;
|
||||||
|
|
||||||
|
protected function __construct() { }
|
||||||
|
|
||||||
|
|
||||||
|
public static function getFileInstance(): FilesystemAdapter {
|
||||||
|
|
||||||
|
if(static::$fileCache instanceof FilesystemAdapter) {
|
||||||
|
return static::$fileCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
static::$fileCache = new FilesystemAdapter(
|
||||||
|
|
||||||
|
// a string used as the subdirectory of the root cache directory, where cache
|
||||||
|
// items will be stored
|
||||||
|
$namespace = '',
|
||||||
|
|
||||||
|
// the default lifetime (in seconds) for cache items that do not define their
|
||||||
|
// own lifetime, with a value 0 causing items to be stored indefinitely (i.e.
|
||||||
|
// until the files are deleted)
|
||||||
|
$defaultLifetime = 30,
|
||||||
|
|
||||||
|
// the main cache directory (the application needs read-write permissions on it)
|
||||||
|
// if none is specified, a directory is created inside the system temporary directory
|
||||||
|
$directory = CACHE_FILE_SYSTEM_DIRECTORY
|
||||||
|
);
|
||||||
|
|
||||||
|
return static::$fileCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return RedisAdapter | false
|
||||||
|
*/
|
||||||
|
public static function getRedisInstance() {
|
||||||
|
|
||||||
|
if(!is_null(static::$redisCache)) {
|
||||||
|
return static::$redisCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// pass a single DSN string to register a single server with the client
|
||||||
|
$redisConnection = RedisAdapter::createConnection( CACHE_REDIS_DSN , [
|
||||||
|
//'read_timeout' => 10,
|
||||||
|
//'retry_interval' => 2,
|
||||||
|
/*
|
||||||
|
'persistent' => 1,
|
||||||
|
'persistent_id' => null,
|
||||||
|
'timeout' => 10,
|
||||||
|
|
||||||
|
'tcp_keepalive' => 0,
|
||||||
|
'lazy' => null,
|
||||||
|
'redis_cluster' => false,
|
||||||
|
'redis_sentinel' => null,*/
|
||||||
|
] );
|
||||||
|
|
||||||
|
//var_dump($redisConnection->isConnected());
|
||||||
|
|
||||||
|
}catch (\Exception $exception) {
|
||||||
|
//echo $exception->getMessage();
|
||||||
|
$redisConnection = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$redisConnection) {
|
||||||
|
static::$redisCache = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static::$redisCache = new RedisAdapter(
|
||||||
|
// the object that stores a valid connection to your Redis system
|
||||||
|
$redisConnection,
|
||||||
|
|
||||||
|
// the string prefixed to the keys of the items stored in this cache
|
||||||
|
$namespace = '',
|
||||||
|
|
||||||
|
// the default lifetime (in seconds) for cache items that do not define their
|
||||||
|
// own lifetime, with a value 0 causing items to be stored indefinitely (i.e.
|
||||||
|
// until RedisAdapter::clear() is invoked or the server(s) are purged)
|
||||||
|
$defaultLifetime = 10
|
||||||
|
);
|
||||||
|
|
||||||
|
return static::$redisCache;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
208
inc/Hura8/System/Config.php
Normal file
208
inc/Hura8/System/Config.php
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System;
|
||||||
|
|
||||||
|
use Hura8\Traits\ClassCacheTrait;
|
||||||
|
|
||||||
|
class Config
|
||||||
|
{
|
||||||
|
|
||||||
|
use ClassCacheTrait;
|
||||||
|
|
||||||
|
|
||||||
|
public static function getRequestLanguage() : string {
|
||||||
|
return static::getCache("getRequestLanguage", function () {
|
||||||
|
$lang = $_REQUEST[Constant::LANGUAGE_ID] ?? DEFAULT_LANGUAGE;
|
||||||
|
|
||||||
|
return (array_key_exists($lang , Constant::languagePermitList())) ? $lang : DEFAULT_LANGUAGE;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getClientInfo() {
|
||||||
|
return static::getCache("getClientInfo", function () {
|
||||||
|
$config_file = CONFIG_DIR . '/client/client.info.php';
|
||||||
|
if(file_exists($config_file)) {
|
||||||
|
return include $config_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getProductHotTypeList(){
|
||||||
|
|
||||||
|
return static::getCache('getProductHotTypeList', function (){
|
||||||
|
$config_file = CONFIG_DIR . "/client/product.hottype.php";
|
||||||
|
if(!file_exists($config_file)) {
|
||||||
|
die("File: client/product.hottype.php not exist !");
|
||||||
|
}
|
||||||
|
|
||||||
|
return include $config_file;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getRoutes() {
|
||||||
|
return static::getCache("getRoutes", function () {
|
||||||
|
$config_file = CONFIG_DIR . '/client/routing_main.php';
|
||||||
|
if(!file_exists($config_file)) {
|
||||||
|
die("File not exist: config/client/routing_main.php");
|
||||||
|
}
|
||||||
|
|
||||||
|
return include $config_file;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getEntityLanguageFields() {
|
||||||
|
return static::getCache("getEntityLanguageFields", function () {
|
||||||
|
|
||||||
|
$config_file = CONFIG_DIR . '/client/language_fields.php';
|
||||||
|
if(!file_exists($config_file)) {
|
||||||
|
die("File not exist: config/client/language_fields.php");
|
||||||
|
}
|
||||||
|
|
||||||
|
return include $config_file;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getSettingsEmail() {
|
||||||
|
return static::getCache("getSettingsEmail", function () {
|
||||||
|
|
||||||
|
$system_config_file = CONFIG_DIR . '/system/settings.email.php';
|
||||||
|
if(!file_exists($system_config_file)) {
|
||||||
|
die("File not exist: config/system/settings.email.php");
|
||||||
|
}
|
||||||
|
|
||||||
|
$system_config = include $system_config_file;
|
||||||
|
|
||||||
|
// client extension
|
||||||
|
$client_config_file = CONFIG_DIR . '/client/settings.email-extend.php';
|
||||||
|
$client_config = [];
|
||||||
|
if(file_exists($client_config_file)) {
|
||||||
|
$client_config = include $client_config_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
$final_config = $system_config;
|
||||||
|
|
||||||
|
if(isset($client_config['email'])) {
|
||||||
|
$final_config['email'] = array_merge($final_config['email'], $client_config['email']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($client_config['template'])) {
|
||||||
|
$final_config['template'] = array_merge($final_config['template'], $client_config['template']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $final_config;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getSettingsPrint() {
|
||||||
|
|
||||||
|
return static::getCache("getSettingsPrint", function () {
|
||||||
|
|
||||||
|
$config_file = CONFIG_DIR . '/system/settings.print.php';
|
||||||
|
if (!file_exists($config_file)) {
|
||||||
|
// die("File not exist: config/system/settings.print.php");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return include $config_file;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getRelationConfigForItem($item_type) {
|
||||||
|
|
||||||
|
return static::getCache("getRelationConfigForItem-".$item_type, function () use ($item_type) {
|
||||||
|
|
||||||
|
$config_relation_file = ROOT_DIR . '/config/client/config_relation.php';
|
||||||
|
if(!file_exists($config_relation_file)) {
|
||||||
|
die("config/client/config_relation.php does not exist!");
|
||||||
|
}
|
||||||
|
|
||||||
|
$system_relation_file = ROOT_DIR . '/config/system/relation_config.php';
|
||||||
|
if(!file_exists($system_relation_file)) {
|
||||||
|
die("config/system/relation_config.php does not exist!");
|
||||||
|
}
|
||||||
|
|
||||||
|
$config_relation = include $config_relation_file;
|
||||||
|
$available_content = include $system_relation_file;
|
||||||
|
|
||||||
|
if(isset($config_relation[$item_type])) {
|
||||||
|
$config = array();
|
||||||
|
foreach ($config_relation[$item_type] as $type) {
|
||||||
|
if(isset($available_content[$type])) $config[$type] = $available_content[$type];
|
||||||
|
}
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getProductImageTypes(){
|
||||||
|
$config_image_type_file = ROOT_DIR . "/config/client/config_product_image_types.php";
|
||||||
|
if(!file_exists($config_image_type_file)){
|
||||||
|
return [
|
||||||
|
'standard' => 'Hình sản phẩm',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return include $config_image_type_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getProductUnitList() {
|
||||||
|
return static::getCache("getProductUnitList", function (){
|
||||||
|
$config_file = CONFIG_DIR . "/client/product.unit.php";
|
||||||
|
if(file_exists($config_file)) {
|
||||||
|
return include $config_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getLanguageCount(){
|
||||||
|
$language_list = static::getLanguageConfig();
|
||||||
|
$count = sizeof($language_list);
|
||||||
|
return ($count > 1) ? $count : 1; //always have at least 1 language
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getLanguageConfig(){
|
||||||
|
return static::getCache('getLanguageConfig', function (){
|
||||||
|
$config_file = CONFIG_DIR . "/client/language_enable.php";
|
||||||
|
if(!file_exists($config_file)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return include $config_file;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getClientBuildConfig() {
|
||||||
|
return static::getCache("getClientBuildConfig", function (){
|
||||||
|
$config_file = CONFIG_DIR . '/build/store.config.php';
|
||||||
|
if(file_exists($config_file)) {
|
||||||
|
return include $config_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
106
inc/Hura8/System/Constant.php
Normal file
106
inc/Hura8/System/Constant.php
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System;
|
||||||
|
|
||||||
|
|
||||||
|
use Hura8\Traits\ClassCacheTrait;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
|
class Constant
|
||||||
|
{
|
||||||
|
|
||||||
|
use ClassCacheTrait;
|
||||||
|
|
||||||
|
|
||||||
|
private static $constant_dir = CONFIG_DIR . '/system';
|
||||||
|
|
||||||
|
const LANGUAGE_ID = "_l"; // to identify the language setting on URL or in FORM
|
||||||
|
|
||||||
|
|
||||||
|
public static function getAllEntityTypes() {
|
||||||
|
|
||||||
|
return static::getCache('getAllEntityTypes', function () {
|
||||||
|
|
||||||
|
$extend_type_config = CONFIG_DIR . DIRECTORY_SEPARATOR . "/client/config_extend_entity_type.php";
|
||||||
|
$extended_types = [];
|
||||||
|
if(file_exists($extend_type_config)) {
|
||||||
|
$extend_types = include_once $extend_type_config;
|
||||||
|
foreach ($extend_types as $key => $title) {
|
||||||
|
$extended_types[] = preg_replace("/[^a-z0-9_\-]/i", "", $key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$list = [];
|
||||||
|
try {
|
||||||
|
$reflectionClass = new \ReflectionClass(new \Hura8\Interfaces\EntityType());
|
||||||
|
foreach ($reflectionClass->getConstants() as $handle => $value) {
|
||||||
|
$list[] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add extend
|
||||||
|
foreach ($extended_types as $type) {
|
||||||
|
if(!in_array($type, $list)) {
|
||||||
|
$list[] = $type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\ReflectionException $e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// list of languages can be enabled
|
||||||
|
public static function languagePermitList() {
|
||||||
|
return [
|
||||||
|
"vi" => "Tiếng Việt", // default
|
||||||
|
"en" => "English",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function mobileProviderPrefixList() {
|
||||||
|
return static::getCache('mobileProviderPrefixList', function () {
|
||||||
|
return include static::$constant_dir. '/mobile_provider.php';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function customerStageList() {
|
||||||
|
return static::getCache('customerStageList', function () {
|
||||||
|
return include static::$constant_dir. '/customer_stage_list.php';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function saluteList() {
|
||||||
|
return static::getCache('genderList', function () {
|
||||||
|
return include static::$constant_dir. '/salute_list.php';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function genderList() {
|
||||||
|
return static::getCache('genderList', function () {
|
||||||
|
return include static::$constant_dir. '/gender_list.php';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function industryList() {
|
||||||
|
return static::getCache('industryList', function () {
|
||||||
|
return include static::$constant_dir. '/industry_list.php';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function maritalStatusList() {
|
||||||
|
return static::getCache('maritalStatusList', function () {
|
||||||
|
return include static::$constant_dir. '/marital_status_list.php';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
124
inc/Hura8/System/Controller/DomainController.php
Normal file
124
inc/Hura8/System/Controller/DomainController.php
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System\Controller;
|
||||||
|
|
||||||
|
use Hura8\System\Model\DomainModel;
|
||||||
|
|
||||||
|
class DomainController
|
||||||
|
{
|
||||||
|
protected $objDomainModel;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->objDomainModel = new DomainModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $layout_options = [
|
||||||
|
"pc" => "Chỉ cho PC",
|
||||||
|
"mobile" => "Chỉ cho Mobile",
|
||||||
|
//"amp" => "Chỉ cho AMP",
|
||||||
|
"all" => "Cả PC & Mobile",
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
public function buildDomainConfig() {
|
||||||
|
$domain_per_languages = $this->getList('');
|
||||||
|
|
||||||
|
$config_domain_list = []; //all domains and attributes, so we can know the info of currently visited domain
|
||||||
|
$config_domain_languages = []; //domains per language, so we can redirect to main domain of a specific language
|
||||||
|
|
||||||
|
foreach ($domain_per_languages as $lang => $list_domains) {
|
||||||
|
foreach ($list_domains as $_item) {
|
||||||
|
$config_domain_languages[$lang][] = [
|
||||||
|
"domain" => $_item['domain'],
|
||||||
|
"is_main" => $_item['isMain'],
|
||||||
|
"layout" => ($_item['layout']) ? $_item['layout'] : 'pc',
|
||||||
|
];
|
||||||
|
|
||||||
|
$config_domain_list[$_item['domain']] = [
|
||||||
|
"lang" => $_item['lang'],
|
||||||
|
"is_main" => $_item['isMain'],
|
||||||
|
"layout" => ($_item['layout']) ? $_item['layout'] : 'pc',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
"language" => $config_domain_languages,
|
||||||
|
"list" => $config_domain_list,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getLayoutOption(){
|
||||||
|
return $this->layout_options;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getList($language = '') {
|
||||||
|
|
||||||
|
$item_list = $this->objDomainModel->getList([
|
||||||
|
"language" => $language,
|
||||||
|
"numPerPage" => 100,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = array();
|
||||||
|
foreach ( $item_list as $rs ) {
|
||||||
|
if(!$rs['lang']) $rs['lang'] = DEFAULT_LANGUAGE;
|
||||||
|
$result[$rs['lang']][] = $rs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function addNewDomain($domain, $language = DEFAULT_LANGUAGE){
|
||||||
|
$this->objDomainModel->addNewDomain($this->cleanDomain($domain), $language);
|
||||||
|
|
||||||
|
$this->rebuildConfigFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function deleteDomain($domain){
|
||||||
|
$this->objDomainModel->deleteDomain($this->cleanDomain($domain));
|
||||||
|
|
||||||
|
$this->rebuildConfigFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function setDomainMain($domain, $language){
|
||||||
|
$this->objDomainModel->setDomainMain($this->cleanDomain($domain), $language);
|
||||||
|
|
||||||
|
$this->rebuildConfigFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function setDomainLayout($domain, $layout = 'pc'){
|
||||||
|
$layout_option = $this->getLayoutOption();
|
||||||
|
if(!isset($layout_option[$layout])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->objDomainModel->setDomainLayout($this->cleanDomain($domain), $layout);
|
||||||
|
$this->rebuildConfigFile();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function cleanDomain($domain) {
|
||||||
|
$domain_element = parse_url($domain);
|
||||||
|
|
||||||
|
$scheme = isset($domain_element['scheme']) ? $domain_element['scheme'] . '://' : '';
|
||||||
|
$host = $domain_element['host'] ?? '';
|
||||||
|
$port = isset($domain_element['port']) ? ':' . $domain_element['port'] : '';
|
||||||
|
|
||||||
|
return strtolower(trim($scheme . $host . $port));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function rebuildConfigFile() {
|
||||||
|
$objSettingController = new SettingController();
|
||||||
|
$objSettingController->create_config_file_n_upload();
|
||||||
|
}
|
||||||
|
}
|
||||||
43
inc/Hura8/System/Controller/EntityPermissionController.php
Normal file
43
inc/Hura8/System/Controller/EntityPermissionController.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System\Controller;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\iEntityPermission;
|
||||||
|
|
||||||
|
class EntityPermissionController implements iEntityPermission
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct($entity_type) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canCreate(): bool
|
||||||
|
{
|
||||||
|
// TODO: Implement canCreate() method.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canUpdate(): bool
|
||||||
|
{
|
||||||
|
// TODO: Implement canUpdate() method.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canDelete(): bool
|
||||||
|
{
|
||||||
|
// TODO: Implement canDelete() method.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canView(): bool
|
||||||
|
{
|
||||||
|
// TODO: Implement canView() method.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function canApprove(): bool
|
||||||
|
{
|
||||||
|
// TODO: Implement canApprove() method.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
82
inc/Hura8/System/Controller/RelationController.php
Normal file
82
inc/Hura8/System/Controller/RelationController.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System\Controller;
|
||||||
|
|
||||||
|
use Hura8\System\Model\RelationModel;
|
||||||
|
use Hura8\Traits\ClassCacheTrait;
|
||||||
|
|
||||||
|
class RelationController
|
||||||
|
{
|
||||||
|
use ClassCacheTrait;
|
||||||
|
|
||||||
|
protected $objRelationModel;
|
||||||
|
|
||||||
|
private $main_item_type = '';
|
||||||
|
private $main_item_id = 0;
|
||||||
|
|
||||||
|
public function __construct($main_item_type, $main_item_id)
|
||||||
|
{
|
||||||
|
$this->objRelationModel = new RelationModel($main_item_type, $main_item_id);
|
||||||
|
$this->main_item_type = $main_item_type;
|
||||||
|
$this->main_item_id = $main_item_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function updateOrdering($related_item_id, $new_order) {
|
||||||
|
return $this->objRelationModel->updateOrdering($related_item_id, $new_order);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//@warn: this does not check if records exist.
|
||||||
|
public function create(array $related_items, $both_way_relation = true) {
|
||||||
|
return $this->objRelationModel->create($related_items, $both_way_relation);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function checkExist($main_item_type, $main_item_id, $related_item_type, $related_item_id){
|
||||||
|
return $this->objRelationModel->checkExist($main_item_type, $main_item_id, $related_item_type, $related_item_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
//remove a related-item
|
||||||
|
public function remove($related_item_type, $related_item_id, $remove_both_way = true) {
|
||||||
|
return $this->objRelationModel->remove($related_item_type, $related_item_id, $remove_both_way);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//remove all relate items
|
||||||
|
public function truncate() {
|
||||||
|
$this->objRelationModel->truncate();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getRelatedItems(array $related_item_types = []) {
|
||||||
|
return $this->objRelationModel->getRelatedItems($related_item_types);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedItemsForList(array $main_item_list_ids, array $related_item_types = []) {
|
||||||
|
return $this->objRelationModel->getRelatedItemsForList($main_item_list_ids, $related_item_types);
|
||||||
|
}
|
||||||
|
|
||||||
|
//count related items
|
||||||
|
public function getRelatedItemCount() {
|
||||||
|
return $this->objRelationModel->getRelatedItemCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function findItemUrl($item_type, $item_id) {
|
||||||
|
$url_config = array(
|
||||||
|
"product" => "/admin/?opt=product&view=form&id=".$item_id."&part=relation&l=vi&popup=".POPUP,
|
||||||
|
"product-category" => "?opt=product&view=category-form&id=".$item_id."",
|
||||||
|
|
||||||
|
"article-article" => "?opt=article&view=form&id=".$item_id."&l=&popup=".POPUP,
|
||||||
|
"article-category" => "?opt=article&view=category-form&id=".$item_id."&l=&popup=".POPUP,
|
||||||
|
|
||||||
|
"album" => "?opt=album&view=form&id=".$item_id,
|
||||||
|
"banner" => "?opt=banner&view=upload&id=".$item_id,
|
||||||
|
"page" => "?opt=page&view=form&id=".$item_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (isset($url_config[$item_type])) ? $url_config[$item_type] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
144
inc/Hura8/System/Controller/SettingController.php
Normal file
144
inc/Hura8/System/Controller/SettingController.php
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System\Controller;
|
||||||
|
|
||||||
|
use Hura8\Components\Template\AdminController\ATemplateSetController;
|
||||||
|
use Hura8\System\Model\SettingModel;
|
||||||
|
|
||||||
|
class SettingController
|
||||||
|
{
|
||||||
|
|
||||||
|
static $file_folder = "media/settings";
|
||||||
|
|
||||||
|
protected $objSettingModel;
|
||||||
|
|
||||||
|
// reserved special keys which should not be created by admin (but the system)
|
||||||
|
protected $special_keys = [];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->objSettingModel = new SettingModel();
|
||||||
|
$this->special_keys = include CONFIG_DIR . "/system/special_settings_keys.php";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getAll(){
|
||||||
|
return $this->objSettingModel->getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getSpecialKeys() {
|
||||||
|
$group_keys = $this->special_keys;
|
||||||
|
return array_merge($group_keys['design'], $group_keys['system']);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function get($key, $default =null){
|
||||||
|
return $this->objSettingModel->get($key, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function delete($key){
|
||||||
|
return $this->objSettingModel->delete($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// update bulk key-values
|
||||||
|
public function updateBulk(array $key_values){
|
||||||
|
foreach ($key_values as $key => $value) {
|
||||||
|
$this->objSettingModel->updateOrCreate($key, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function updateOrCreate($key, $value, $comment = '') {
|
||||||
|
return $this->objSettingModel->updateOrCreate($key, $value, $comment );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getList(array $keys) {
|
||||||
|
return $this->objSettingModel->getList($keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function populateSpecialKeys() {
|
||||||
|
$keys = [];
|
||||||
|
foreach ($this->special_keys as $group => $_list) {
|
||||||
|
$keys = array_merge($keys, $_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->objSettingModel->populateKeys($keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//tao config file tu database va upload vao thu muc website
|
||||||
|
// Global config variables available to the whole website
|
||||||
|
// includes:
|
||||||
|
/*
|
||||||
|
* - domain setting
|
||||||
|
* - main domain
|
||||||
|
* - template_set
|
||||||
|
* - exchange rate
|
||||||
|
* - password to unlock website
|
||||||
|
* - google analytic verification
|
||||||
|
* - google webmaster tool verification
|
||||||
|
* - number of products to display
|
||||||
|
* - default product display type: list|grid
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
public function create_config_file_n_upload(){
|
||||||
|
|
||||||
|
$objDomainController = new DomainController();
|
||||||
|
$config_domains = $objDomainController->buildDomainConfig();
|
||||||
|
|
||||||
|
$config = [];
|
||||||
|
|
||||||
|
// * - domain setting
|
||||||
|
$config['domains'] = $config_domains;
|
||||||
|
|
||||||
|
// * - template_set
|
||||||
|
$objATemplateSetController = new ATemplateSetController();
|
||||||
|
$config['template_set'] = $objATemplateSetController->getActivatedSet();
|
||||||
|
|
||||||
|
// * - exchange rate
|
||||||
|
// * - password to unlock website
|
||||||
|
// * - google analytic verification
|
||||||
|
// * - google webmaster tool verification
|
||||||
|
// * - number of products to display
|
||||||
|
// * - default product display type: list|grid
|
||||||
|
$config['setup'] = $this->getList(array(
|
||||||
|
"web_close_pass" ,
|
||||||
|
"web_close_message" ,
|
||||||
|
"exchange_rate" ,
|
||||||
|
"google_domain_verify" ,
|
||||||
|
"product_per_page" ,
|
||||||
|
"product_default_order",
|
||||||
|
"site_manager" ,
|
||||||
|
"site_manager_access_key",
|
||||||
|
));
|
||||||
|
|
||||||
|
$config_file = CONFIG_DIR . "/build/store.config.php";
|
||||||
|
|
||||||
|
$config_head = "<?php
|
||||||
|
/**
|
||||||
|
* Author: HuraSoft
|
||||||
|
* Generated time : ".date("d-m-Y, g:ia")."
|
||||||
|
*/
|
||||||
|
";
|
||||||
|
$config_content = "return ".var_export($config, true) .";";
|
||||||
|
|
||||||
|
@file_put_contents($config_file, $config_head. $this->_minifyCode($config_content));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//this is a greatly simplified version
|
||||||
|
protected function _minifyCode($text) {
|
||||||
|
//remove line break
|
||||||
|
$text = str_replace(["\n", "\r", "\t"], " ", $text);
|
||||||
|
//remove double spacings
|
||||||
|
$text = preg_replace("/(\s+)/i", " ", $text);
|
||||||
|
|
||||||
|
return trim($text);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
242
inc/Hura8/System/Controller/UrlManagerController.php
Normal file
242
inc/Hura8/System/Controller/UrlManagerController.php
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System\Controller;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\AppResponse;
|
||||||
|
use Hura8\System\IDGenerator;
|
||||||
|
use Hura8\System\Language;
|
||||||
|
use Hura8\System\Model\UrlModel;
|
||||||
|
use Hura8\System\Url;
|
||||||
|
use Hura8\Traits\ClassCacheTrait;
|
||||||
|
|
||||||
|
|
||||||
|
class UrlManagerController
|
||||||
|
{
|
||||||
|
|
||||||
|
use ClassCacheTrait;
|
||||||
|
|
||||||
|
/* @var UrlModel $objUrlModel */
|
||||||
|
protected $objUrlModel;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->objUrlModel = new UrlModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function createRedirect($info) {
|
||||||
|
$request_path = $info['request_path'] ?? '';
|
||||||
|
$redirect_code = $info['redirect_code'] ?? 0;
|
||||||
|
$redirect_url = $info['redirect_url'] ?? '';
|
||||||
|
|
||||||
|
if(!$request_path || !$redirect_url) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request_path_element = Url::parse($request_path);
|
||||||
|
|
||||||
|
$request_path_path = $request_path_element['path'];
|
||||||
|
|
||||||
|
// home page or itself is forbidden
|
||||||
|
if($request_path_path == '/' || $request_path_path == $redirect_url) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $this->objUrlModel->create([
|
||||||
|
"url_type" => "redirect",
|
||||||
|
"request_path" => $request_path_path,
|
||||||
|
"redirect_code" => $redirect_code,
|
||||||
|
"redirect_url" => $redirect_url,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getInfo($id) : ?array {
|
||||||
|
return $this->objUrlModel->getInfo($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getEmptyInfo($addition_field_value = []) : array {
|
||||||
|
return $this->objUrlModel->getEmptyInfo($addition_field_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getList(array $condition) : array
|
||||||
|
{
|
||||||
|
return $this->objUrlModel->getList($condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getTotal(array $condition) : int
|
||||||
|
{
|
||||||
|
return $this->objUrlModel->getTotal($condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function deleteByRequestPath($request_path) {
|
||||||
|
$this->objUrlModel->deleteByRequestPath($request_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function translateRequestPathConfig(
|
||||||
|
$request_path_config, // "/%extra_path%/%item_index%/ac%item_id%.html",/
|
||||||
|
$item_id = '',
|
||||||
|
$item_index = '',
|
||||||
|
$extra_path = ''
|
||||||
|
) {
|
||||||
|
|
||||||
|
$item_index = static::create_url_index($item_index); //reclean url index
|
||||||
|
|
||||||
|
$new_url = str_replace(
|
||||||
|
array('%item_id%', '%item_index%', '%extra_path%',),
|
||||||
|
array($item_id, $item_index, $extra_path),
|
||||||
|
$request_path_config
|
||||||
|
);
|
||||||
|
|
||||||
|
return str_replace("//","/",$new_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function create_url_index($name, $vietnamese=true){
|
||||||
|
if($vietnamese) $name = Language::chuyenKhongdau($name);
|
||||||
|
|
||||||
|
$name = preg_replace("/[^a-z0-9\s_\-]/i", " ", $name);
|
||||||
|
$name = preg_replace("/\s+/i", " ", $name);
|
||||||
|
$name = str_replace(" ","-", trim($name));
|
||||||
|
$name = preg_replace("/-+/","-", $name);
|
||||||
|
|
||||||
|
if (!defined("BUILD_URL_INDEX_LOWERCASE") || BUILD_URL_INDEX_LOWERCASE) {
|
||||||
|
return strtolower($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getUrlMetaInfo($request_path){
|
||||||
|
return $this->objUrlModel->getUrlMetaInfo($request_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function createUrlMeta(array $info){
|
||||||
|
return $this->objUrlModel->createUrlMeta($info);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getUrlInfoByRequestPath($request_path) {
|
||||||
|
$info = $this->objUrlModel->getUrlByRequestPath($request_path);
|
||||||
|
if($info){
|
||||||
|
|
||||||
|
$id_path_content = $this->analyze_url_id_path($info['id_path']);
|
||||||
|
|
||||||
|
$id_path_content['option'] = $id_path_content['module'];
|
||||||
|
//$id_path_content['redirect_code'] = $info['redirect_code'];
|
||||||
|
//$id_path_content['request_path'] = $info['request_path'];
|
||||||
|
|
||||||
|
return array_merge($info, $id_path_content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
///module:product/view:category/view_id:1/brand_id:55/type:hello-ther
|
||||||
|
//return:
|
||||||
|
/*
|
||||||
|
[
|
||||||
|
"module" => "product",
|
||||||
|
"view" => "category",
|
||||||
|
"view_id" => "1",
|
||||||
|
'query' => [
|
||||||
|
"brand_id" => 55,
|
||||||
|
"type" => "hello-ther",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
* */
|
||||||
|
protected function analyze_url_id_path($id_path) : array
|
||||||
|
{
|
||||||
|
$id_path_ele = explode("/", $id_path);
|
||||||
|
|
||||||
|
$result = array();
|
||||||
|
$query_string = array();
|
||||||
|
|
||||||
|
foreach ( $id_path_ele as $ele ) {
|
||||||
|
if(!$ele) continue;
|
||||||
|
|
||||||
|
$ele_part = explode(":", $ele);
|
||||||
|
if(!in_array($ele_part[0], array('module', 'view', 'view_id'))) {
|
||||||
|
$query_string[$ele_part[0]] = $ele_part[1];
|
||||||
|
}else{
|
||||||
|
$result[$ele_part[0]] = $ele_part[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result['query'] = $query_string;
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function createIdPath($module, $view, $view_id, array $other_list = []): string
|
||||||
|
{
|
||||||
|
$parameter_constr = [
|
||||||
|
"module:".$module,
|
||||||
|
"view:".$view,
|
||||||
|
"view_id:".$view_id,
|
||||||
|
];
|
||||||
|
foreach($other_list as $arg => $value) {
|
||||||
|
$parameter_constr[] = $arg.":".$value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "/".join("/", $parameter_constr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description create or update url's $request_path by checking $id_path. $new_request_path will return to be updated to item's entity
|
||||||
|
* @param string $url_type $module:$view
|
||||||
|
* @param string $wanted_request_path
|
||||||
|
* @param string $id_path
|
||||||
|
* @param string $redirect_code
|
||||||
|
* @return ?string $wanted_request_path or new $request_path if the path already exists
|
||||||
|
*/
|
||||||
|
public function createUrl(string $url_type, string $wanted_request_path, string $id_path, $redirect_code=0) : ?string
|
||||||
|
{
|
||||||
|
$new_request_path = $this->createUniqueRequestPath($wanted_request_path, $id_path);
|
||||||
|
|
||||||
|
$check_id_path_exist = $this->objUrlModel->getUrlByIdPath($id_path);
|
||||||
|
if($check_id_path_exist) {
|
||||||
|
$res = $this->objUrlModel->update($check_id_path_exist['id'], [
|
||||||
|
'request_path' => $new_request_path ,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$res = $this->objUrlModel->create([
|
||||||
|
'url_type' => $url_type,
|
||||||
|
'request_path' => $new_request_path ,
|
||||||
|
'id_path' => $id_path,
|
||||||
|
'redirect_code' => $redirect_code,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($res->getStatus() == AppResponse::SUCCESS) ? $new_request_path : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//create a unique request-path
|
||||||
|
protected function createUniqueRequestPath(string $wanted_request_path, string $id_path) : string
|
||||||
|
{
|
||||||
|
$check_exist = $this->objUrlModel->getUrlByRequestPath($wanted_request_path);
|
||||||
|
|
||||||
|
//ok, can use this one
|
||||||
|
if(!$check_exist || $check_exist['id_path'] == $id_path ) {
|
||||||
|
return $wanted_request_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
//other case, create new path and check again
|
||||||
|
$random_suffix = IDGenerator::createStringId(4, true);
|
||||||
|
$new_request_path = $wanted_request_path . '-'.$random_suffix;
|
||||||
|
|
||||||
|
return $this->createUniqueRequestPath($new_request_path, $id_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
44
inc/Hura8/System/Controller/ViewHistoryController.php
Normal file
44
inc/Hura8/System/Controller/ViewHistoryController.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System\Controller;
|
||||||
|
|
||||||
|
|
||||||
|
use Hura8\System\Model\WebUserModel;
|
||||||
|
|
||||||
|
class ViewHistoryController
|
||||||
|
{
|
||||||
|
protected $history = []; //type => [id1, id2]
|
||||||
|
|
||||||
|
protected $objWebUserModel;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->objWebUserModel = new WebUserModel(WebUserController::getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function addHistory($item_type, $item_id) {
|
||||||
|
$current_list = $this->getHistory($item_type);
|
||||||
|
|
||||||
|
// if exist, remove it
|
||||||
|
$search_key = array_search($item_id, $current_list, true);
|
||||||
|
if($search_key !== false) {
|
||||||
|
array_splice($current_list, $search_key, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to front
|
||||||
|
array_unshift($current_list, $item_id);
|
||||||
|
|
||||||
|
$this->history[$item_type] = $current_list;
|
||||||
|
|
||||||
|
// save to db
|
||||||
|
$this->objWebUserModel->setValue("view-history", $this->history);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getHistory($item_type) {
|
||||||
|
$history = $this->objWebUserModel->getValue("view-history");
|
||||||
|
return (isset($history[$item_type])) ? $history[$item_type] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
27
inc/Hura8/System/Controller/WebUserController.php
Normal file
27
inc/Hura8/System/Controller/WebUserController.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System\Controller;
|
||||||
|
|
||||||
|
use Hura8\System\Model\WebUserModel;
|
||||||
|
|
||||||
|
class WebUserController
|
||||||
|
{
|
||||||
|
const USER_BROWSER_COOKIE_NAME = 'uID';
|
||||||
|
|
||||||
|
protected $objWebUserModel;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->objWebUserModel = new WebUserModel(self::getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefer cookie-id, if set ID in url parameter then use it
|
||||||
|
public static function getUserId() {
|
||||||
|
$url_user_id = getRequest("uid", "");
|
||||||
|
$cookie_user_id = (isset($_COOKIE[self::USER_BROWSER_COOKIE_NAME])) ? $_COOKIE[self::USER_BROWSER_COOKIE_NAME] : '';
|
||||||
|
|
||||||
|
return ($url_user_id) ?: $cookie_user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
78
inc/Hura8/System/Controller/aAdminEntityBaseController.php
Normal file
78
inc/Hura8/System/Controller/aAdminEntityBaseController.php
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System\Controller;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\AppResponse;
|
||||||
|
use Hura8\Interfaces\iEntityController;
|
||||||
|
|
||||||
|
abstract class aAdminEntityBaseController extends aEntityBaseController implements iEntityController
|
||||||
|
{
|
||||||
|
|
||||||
|
public function updateFields($id, array $info): AppResponse
|
||||||
|
{
|
||||||
|
return $this->iEntityModel->updateFields($id, $info);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function updateField($id, $field, $value): AppResponse
|
||||||
|
{
|
||||||
|
|
||||||
|
$fields_list = [];
|
||||||
|
$fields_list[$field] = $value;
|
||||||
|
|
||||||
|
return $this->iEntityModel->updateFields($id, $fields_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function updateFeatured($id, $new_status): AppResponse
|
||||||
|
{
|
||||||
|
$status = ($new_status == 'on' || $new_status == 1) ? 1 : 0;
|
||||||
|
|
||||||
|
return $this->iEntityModel->updateFields($id, ['is_featured' => $status]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function updateStatus($id, $new_status): AppResponse
|
||||||
|
{
|
||||||
|
$status = ($new_status == 'on' || $new_status == 1) ? 1 : 0;
|
||||||
|
|
||||||
|
return $this->iEntityModel->updateFields($id, ['status' => $status]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function create(array $info): AppResponse
|
||||||
|
{
|
||||||
|
return $this->iEntityModel->create($info);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function update($id, array $info): AppResponse
|
||||||
|
{
|
||||||
|
if($this->iEntityLanguageModel) {
|
||||||
|
return $this->iEntityLanguageModel->update($id, $info);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->iEntityModel->update($id, $info);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
abstract protected function deleteFileBeforeDeleteItem($item_id) : bool;
|
||||||
|
|
||||||
|
|
||||||
|
public function delete($id): AppResponse
|
||||||
|
{
|
||||||
|
if($this->deleteFileBeforeDeleteItem($id)) {
|
||||||
|
return $this->iEntityModel->delete($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AppResponse('error', 'Cannot delete '.$id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getEmptyInfo(array $additional_fields = []) : array
|
||||||
|
{
|
||||||
|
return $this->iEntityModel->getEmptyInfo($additional_fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
304
inc/Hura8/System/Controller/aCategoryBaseController.php
Normal file
304
inc/Hura8/System/Controller/aCategoryBaseController.php
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System\Controller;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\AppResponse;
|
||||||
|
use Hura8\Interfaces\iEntityCategoryModel;
|
||||||
|
use Hura8\Interfaces\iEntityLanguageModel;
|
||||||
|
use Hura8\Traits\ClassCacheTrait;
|
||||||
|
|
||||||
|
abstract class aCategoryBaseController
|
||||||
|
{
|
||||||
|
|
||||||
|
use ClassCacheTrait;
|
||||||
|
|
||||||
|
/* @var iEntityCategoryModel $iEntityCategoryModel */
|
||||||
|
protected $iEntityCategoryModel;
|
||||||
|
|
||||||
|
/* @var ?iEntityLanguageModel $iEntityLanguageModel */
|
||||||
|
protected $iEntityLanguageModel = null;
|
||||||
|
|
||||||
|
protected $view_language = LANGUAGE;
|
||||||
|
|
||||||
|
public static $public_fields = [
|
||||||
|
"id", "title", "cat_path", "request_path", "image", "url", "not_translated", "children"
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
iEntityCategoryModel $iEntityCategoryModel,
|
||||||
|
?iEntityLanguageModel $iEntityLanguageModel = null
|
||||||
|
) {
|
||||||
|
$this->iEntityCategoryModel = $iEntityCategoryModel;
|
||||||
|
|
||||||
|
if(!$this->isDefaultLanguage() && $iEntityLanguageModel instanceof iEntityLanguageModel) {
|
||||||
|
$this->iEntityLanguageModel = $iEntityLanguageModel;
|
||||||
|
$this->iEntityLanguageModel->setLanguage($this->view_language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function create(array $info) : AppResponse
|
||||||
|
{
|
||||||
|
return $this->iEntityCategoryModel->create($info);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function update($id, array $info) : AppResponse
|
||||||
|
{
|
||||||
|
if($this->iEntityLanguageModel) {
|
||||||
|
return $this->iEntityLanguageModel->update($id, $info, $info['title']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->iEntityCategoryModel->update($id, $info);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function delete($id) : AppResponse
|
||||||
|
{
|
||||||
|
return $this->iEntityCategoryModel->delete($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function updateFields($id, array $info) : AppResponse
|
||||||
|
{
|
||||||
|
return $this->iEntityCategoryModel->updateFields($id, $info);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function isDefaultLanguage() : bool
|
||||||
|
{
|
||||||
|
return IS_DEFAULT_LANGUAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getListByIds(array $list_id, array $condition = array()) : array
|
||||||
|
{
|
||||||
|
$item_list = $this->iEntityCategoryModel->getListByIds($list_id, $condition);
|
||||||
|
|
||||||
|
if($this->iEntityLanguageModel) {
|
||||||
|
$item_list_language_info = $this->iEntityLanguageModel->getListByIds($list_id);
|
||||||
|
|
||||||
|
$final_list = [];
|
||||||
|
foreach ($item_list as $item) {
|
||||||
|
$item_language_info = $item_list_language_info[$item['id']] ?? ["not_translated" => true];
|
||||||
|
$final_list[] = $this->formatItemInList(array_merge($item, $item_language_info));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $final_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_map(function ($item){
|
||||||
|
return $this->formatItemInList($item);
|
||||||
|
}, $item_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function formatItemInList(array $info): array
|
||||||
|
{
|
||||||
|
return $info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getActualFilterCondition(array $raw_filter_condition) : array
|
||||||
|
{
|
||||||
|
return $raw_filter_condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getModelFilterCondition(array $raw_filter_condition) : array
|
||||||
|
{
|
||||||
|
return $this->iEntityCategoryModel->getQueryCondition( $raw_filter_condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getList(array $condition) : array
|
||||||
|
{
|
||||||
|
$item_list = $this->iEntityCategoryModel->getList($condition);
|
||||||
|
|
||||||
|
if($this->iEntityLanguageModel) {
|
||||||
|
$list_ids = array_map(function ($item){ return $item['id'];}, $item_list);
|
||||||
|
$item_list_language_info = $this->iEntityLanguageModel->getListByIds($list_ids);
|
||||||
|
|
||||||
|
$final_list = [];
|
||||||
|
foreach ($item_list as $item) {
|
||||||
|
$item_language_info = $item_list_language_info[$item['id']] ?? ["not_translated" => true];
|
||||||
|
$final_list[] = $this->formatItemInList(array_merge($item, $item_language_info));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $final_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_map(function ($item){
|
||||||
|
return $this->formatItemInList($item);
|
||||||
|
}, $item_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getTotal(array $condition) : int
|
||||||
|
{
|
||||||
|
return $this->iEntityCategoryModel->getTotal($condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function formatItemInfo(?array $info) : ?array
|
||||||
|
{
|
||||||
|
return $info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getInfo($id) : ?array
|
||||||
|
{
|
||||||
|
if(!$id) return null;
|
||||||
|
|
||||||
|
return self::getCache("getInfo-".$id."-".$this->view_language, function () use ($id){
|
||||||
|
|
||||||
|
if($this->iEntityLanguageModel) {
|
||||||
|
$info = $this->iEntityCategoryModel->getInfo($id);
|
||||||
|
$item_language_info = $this->iEntityLanguageModel->getInfo($id);
|
||||||
|
if($item_language_info) {
|
||||||
|
return $this->formatItemInfo(array_merge($info, $item_language_info));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->formatItemInfo($this->iEntityCategoryModel->getInfo($id));
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getEmptyInfo(array $additional_fields = []) : array
|
||||||
|
{
|
||||||
|
return $this->iEntityCategoryModel->getEmptyInfo($additional_fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// all categories and nested by parent
|
||||||
|
public function getNestedCategories($is_public = false) {
|
||||||
|
|
||||||
|
$cache_key = "getNestedCategories-".$this->iEntityCategoryModel->getEntityType()."-".($is_public ? 1: 0);
|
||||||
|
|
||||||
|
return self::getCache($cache_key, function () use ($is_public){
|
||||||
|
$all_categories = $this->getAllParent([
|
||||||
|
'status' => ($is_public) ? 1 : 0
|
||||||
|
]);
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
if(isset($all_categories[0])) {
|
||||||
|
foreach ($all_categories[0] as $index => $info) {
|
||||||
|
$info['children'] = ($info['is_parent']) ? $this->getChildren($info['id'], $all_categories, 1) : [];
|
||||||
|
$result[] = $this->formatItemInList($info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getChildren($parent_id, $all_categories, $current_level = 1){
|
||||||
|
// dont allow too many nested level
|
||||||
|
$max_deep_level_allow = 5;
|
||||||
|
if($current_level == $max_deep_level_allow) return [];
|
||||||
|
|
||||||
|
$result = [];
|
||||||
|
if(isset($all_categories[$parent_id])) {
|
||||||
|
foreach ($all_categories[$parent_id] as $id => $info) {
|
||||||
|
$info['children'] = $this->getChildren($id, $all_categories, $current_level + 1);
|
||||||
|
$result[] = $this->formatItemInList($info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function showPath($catPathId, $p_category){
|
||||||
|
|
||||||
|
if(!$catPathId){
|
||||||
|
$cat_info = $this->getInfo($p_category);
|
||||||
|
$catPathId = $cat_info['cat_path'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = array();
|
||||||
|
$path_build = array();
|
||||||
|
|
||||||
|
if(strlen($catPathId) > 0){
|
||||||
|
$cat_id_list = array_filter(explode(":", $catPathId));
|
||||||
|
|
||||||
|
$cat_list_info = $this->getListByIds($cat_id_list);
|
||||||
|
|
||||||
|
//reverse cat id order because the parent in the right most
|
||||||
|
krsort($cat_id_list);
|
||||||
|
|
||||||
|
$start_count = 0;
|
||||||
|
$count_cat = sizeof($cat_id_list);
|
||||||
|
|
||||||
|
foreach($cat_id_list as $catId){
|
||||||
|
|
||||||
|
$_cat_info = $cat_list_info[$catId] ?? null;
|
||||||
|
if(!$_cat_info) continue;
|
||||||
|
|
||||||
|
$start_count ++;
|
||||||
|
$path_build[] = "<a href=\"".$_cat_info['url']."\">".$_cat_info['title']."</a> ";
|
||||||
|
|
||||||
|
$result[] = array(
|
||||||
|
'id' => $catId,
|
||||||
|
'url' => $_cat_info['url'],
|
||||||
|
'title' => $_cat_info['title'],
|
||||||
|
);
|
||||||
|
|
||||||
|
if($start_count < $count_cat) $path_build[] = " >> ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'path' => $result,
|
||||||
|
'path_url' => join("", $path_build),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getAll(array $condition = []) {
|
||||||
|
return $this->iEntityCategoryModel->getAll($condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getAllParent(array $condition = []) {
|
||||||
|
|
||||||
|
if($this->iEntityLanguageModel) {
|
||||||
|
|
||||||
|
$all_categories = $this->getAll($condition);
|
||||||
|
|
||||||
|
$item_list_ids = array_map(function ($item){ return $item['id']; }, $all_categories);
|
||||||
|
$item_list_language_info = $this->iEntityLanguageModel->getListByIds($item_list_ids);
|
||||||
|
|
||||||
|
$translated_list = [];
|
||||||
|
foreach ($all_categories as $item) {
|
||||||
|
$item_language_info = $item_list_language_info[$item['id']] ?? ["not_translated" => true];
|
||||||
|
$item = array_merge($item, $item_language_info);
|
||||||
|
|
||||||
|
$translated_list[] = array_merge($item, $item_language_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get group by parent
|
||||||
|
$final_list = [];
|
||||||
|
foreach ( $translated_list as $item ) {
|
||||||
|
$final_list[$item['parent_id']][$item['id']] = $this->formatItemInList($item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $final_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// else
|
||||||
|
$all_categories = $this->getAll($condition);
|
||||||
|
$final_list = [];
|
||||||
|
foreach ( $all_categories as $item ) {
|
||||||
|
$final_list[$item['parent_id']][$item['id']] = $this->formatItemInList($item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $final_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
197
inc/Hura8/System/Controller/aERPController.php
Normal file
197
inc/Hura8/System/Controller/aERPController.php
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace Hura8\System\Controller;
|
||||||
|
|
||||||
|
use Hura8\Database\ConnectDB;
|
||||||
|
|
||||||
|
abstract class aERPController implements iClientERP
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $provider;
|
||||||
|
protected $erp_config;
|
||||||
|
|
||||||
|
protected $tb_product = TableName::PRODUCT;
|
||||||
|
protected $tb_erp_product = "erp_product";
|
||||||
|
|
||||||
|
// this table is the exact copy of $tb_erp_product
|
||||||
|
// it's used when call the syncProductToWeb method to make sure that no new products are added in the syncing process
|
||||||
|
protected $tb_erp_product_copy = "erp_product_tmp_copy";
|
||||||
|
|
||||||
|
protected $tb_log = "erp_log";
|
||||||
|
|
||||||
|
/* @var $objERPProvider iERPProvider */
|
||||||
|
protected $objERPProvider;
|
||||||
|
|
||||||
|
/* @var $db ConnectDB */
|
||||||
|
protected $db;
|
||||||
|
|
||||||
|
protected $get_erp_product_options = [];
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct($provider)
|
||||||
|
{
|
||||||
|
$this->provider = $provider;
|
||||||
|
|
||||||
|
$provider_config_file = CONFIG_DIR . '/provider/'.$provider.'_config.php';
|
||||||
|
if(file_exists($provider_config_file)) {
|
||||||
|
$this->erp_config = include $provider_config_file;
|
||||||
|
|
||||||
|
// create an instance of provider
|
||||||
|
$this->erpFactory();
|
||||||
|
|
||||||
|
$this->db = ConnectDB::getInstance('');
|
||||||
|
}else{
|
||||||
|
die("Cannot load /config/provider/".$provider."_config.php ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected function customSyncProductToWeb();
|
||||||
|
abstract protected function formatProductListFromERP(array $product_list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @overwrite on implemeting class if needed
|
||||||
|
*/
|
||||||
|
public function getProductListPerPage($page, $debug = false) {
|
||||||
|
return $this->getProductList(['page' => $page], $debug);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return iERPProvider
|
||||||
|
*/
|
||||||
|
public function getERPInstance()
|
||||||
|
{
|
||||||
|
return $this->objERPProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createOrder(array $order_info)
|
||||||
|
{
|
||||||
|
return $this->objERPProvider->createOrder($order_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $options
|
||||||
|
*/
|
||||||
|
public function syncProductToWeb(array $options = [])
|
||||||
|
{
|
||||||
|
$this->beforeSyncProductToWeb();
|
||||||
|
$this->customSyncProductToWeb();
|
||||||
|
$this->afterSyncProductToWeb();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function beforeSyncProductToWeb() {
|
||||||
|
/*$this->db->runQuery("CREATE TABLE `" . $this->tb_erp_product_copy . "` LIKE ".$this->tb_erp_product." ");
|
||||||
|
$this->db->runQuery("INSERT INTO `" . $this->tb_erp_product_copy . "` SELECT * FROM ".$this->tb_erp_product." ");
|
||||||
|
$this->db->runQuery("UPDATE `" . $this->tb_erp_product_copy . "` e, idv_sell_product_store p SET
|
||||||
|
e.product_id = p.id
|
||||||
|
WHERE e.sku = p.storeSKU AND LENGTH(e.sku) > 0 ");
|
||||||
|
$this->db->runQuery("DELETE FROM `" . $this->tb_erp_product_copy . "` WHERE `product_id` = 0 ");*/
|
||||||
|
|
||||||
|
$this->db->multi_query([
|
||||||
|
"CREATE TABLE IF NOT EXISTS `" . $this->tb_erp_product_copy . "` LIKE ".$this->tb_erp_product." ",
|
||||||
|
"INSERT INTO `" . $this->tb_erp_product_copy . "` SELECT * FROM ".$this->tb_erp_product." ",
|
||||||
|
"UPDATE `" . $this->tb_erp_product_copy . "` e, ".$this->tb_product." p SET
|
||||||
|
e.product_id = p.id
|
||||||
|
WHERE e.sku = p.storeSKU AND LENGTH(e.sku) > 0 ",
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function afterSyncProductToWeb() {
|
||||||
|
$this->db->runQuery("DROP TABLE `" . $this->tb_erp_product_copy . "` ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setERPProductOptions(array $options = []) {
|
||||||
|
foreach ($options as $key => $value) {
|
||||||
|
$this->get_erp_product_options[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: clean any existing data before populate new ones
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function cleanExistingData()
|
||||||
|
{
|
||||||
|
$this->db->runQuery("TRUNCATE `" . $this->tb_erp_product . "` ");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllStore()
|
||||||
|
{
|
||||||
|
return $this->objERPProvider->getAllStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductSummary()
|
||||||
|
{
|
||||||
|
return $this->objERPProvider->getProductSummary();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getProductList(array $options = [], $debug = false)
|
||||||
|
{
|
||||||
|
return $this->formatProductListFromERP($this->objERPProvider->getProductList($options, $debug));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get log data
|
||||||
|
*/
|
||||||
|
public function getLog($type, $limit = 50) {
|
||||||
|
$query = $this->db->runQuery("SELECT `id`, `data`, `log_time` FROM `".$this->tb_log."` WHERE `type` = ? ORDER BY `id` DESC LIMIT ".$limit, ['s'], [$type]);
|
||||||
|
|
||||||
|
return array_map(function ($item){
|
||||||
|
$copy = $item;
|
||||||
|
$copy['data'] = $item['data'] ? \json_decode($item['data'], true) : [];
|
||||||
|
|
||||||
|
return $copy;
|
||||||
|
}, $this->db->fetchAll($query));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* log data
|
||||||
|
*/
|
||||||
|
public function updateLogData($id, $new_data) {
|
||||||
|
|
||||||
|
$query = $this->db->runQuery("SELECT `data` FROM `".$this->tb_log."` WHERE `id` = ? LIMIT 1", ['d'], [$id]);
|
||||||
|
|
||||||
|
if($item_info = $this->db->fetchAssoc($query)) {
|
||||||
|
$current_data = $item_info['data'] ? \json_decode($item_info['data'], true) : [];
|
||||||
|
$updated_info = array_merge($current_data, $new_data);
|
||||||
|
|
||||||
|
return $this->db->update($this->tb_log, ['data' => $updated_info], ['id' => $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* log data
|
||||||
|
*/
|
||||||
|
public function log($type, array $data) {
|
||||||
|
$info = [];
|
||||||
|
$info['type'] = $type;
|
||||||
|
$info['data'] = $data;
|
||||||
|
$info['log_time'] = CURRENT_TIME;
|
||||||
|
|
||||||
|
return $this->db->insert($this->tb_log, $info);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function erpFactory()
|
||||||
|
{
|
||||||
|
if(!$this->provider) {
|
||||||
|
die("No provider found!");
|
||||||
|
}
|
||||||
|
|
||||||
|
$provider_class = 'Provider\\ERPProviders\\'.ucfirst($this->provider);
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
$this->objERPProvider = (new \ReflectionClass($provider_class))->newInstance($this->erp_config);
|
||||||
|
|
||||||
|
} catch (\ReflectionException $e) {
|
||||||
|
die("aClientERP/erpFactory: ".$e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
262
inc/Hura8/System/Controller/aEntityBaseController.php
Normal file
262
inc/Hura8/System/Controller/aEntityBaseController.php
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System\Controller;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\iEntityLanguageModel;
|
||||||
|
use Hura8\Interfaces\iEntityModel;
|
||||||
|
use Hura8\System\Security\DataClean;
|
||||||
|
use Hura8\System\Security\DataType;
|
||||||
|
use Hura8\Traits\ClassCacheTrait;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description shared controller for aAdminEntityBaseController and aPublicEntityBaseController
|
||||||
|
* DO NOT EXTEND THIS CLASS from other classes
|
||||||
|
*/
|
||||||
|
abstract class aEntityBaseController
|
||||||
|
{
|
||||||
|
use ClassCacheTrait;
|
||||||
|
|
||||||
|
/* @var iEntityModel $iEntityModel */
|
||||||
|
protected $iEntityModel;
|
||||||
|
|
||||||
|
/* @var ?iEntityLanguageModel $iEntityLanguageModel */
|
||||||
|
protected $iEntityLanguageModel = null;
|
||||||
|
|
||||||
|
protected $view_language = LANGUAGE;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
iEntityModel $iEntityModel,
|
||||||
|
?iEntityLanguageModel $iEntityLanguageModel = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
$this->iEntityModel = $iEntityModel;
|
||||||
|
|
||||||
|
if(!$this->isDefaultLanguage() && $iEntityLanguageModel instanceof iEntityLanguageModel) {
|
||||||
|
$this->iEntityLanguageModel = $iEntityLanguageModel;
|
||||||
|
|
||||||
|
// only controller allow to control the language for the model
|
||||||
|
$this->iEntityLanguageModel->setLanguage($this->view_language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getLanguage() {
|
||||||
|
return $this->view_language;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function isDefaultLanguage() {
|
||||||
|
return IS_DEFAULT_LANGUAGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getFilterConditions() : array {
|
||||||
|
return array_merge(
|
||||||
|
$this->baseFilterConditions(),
|
||||||
|
$this->extendFilterConditions()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// this is supposed to be overwritten by the extending class if required
|
||||||
|
protected function extendFilterConditions() : array {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// just here to show the reserved keys. That's all
|
||||||
|
protected function baseFilterConditions() {
|
||||||
|
$base_filter = [
|
||||||
|
'q' => getRequest("q", ''), // keyword search
|
||||||
|
'q_options' => [
|
||||||
|
'field_filters' => [],
|
||||||
|
'fulltext_fields' => [],
|
||||||
|
'limit_result' => 2000
|
||||||
|
], // q_options as in iSearch->find($keyword, array $field_filters = [], array $fulltext_fields = ["keywords"], $limit_result = 2000)
|
||||||
|
'featured' => getRequestInt("featured", 0), // 1|-1
|
||||||
|
'status' => getRequestInt("status", 0), // 1|-1
|
||||||
|
'excluded_ids' => null, // [id1, id2, ...]
|
||||||
|
'included_ids' => null,// [id1, id2, ...]
|
||||||
|
|
||||||
|
// to special filters for language
|
||||||
|
'translated' => getRequestInt("translated", 0), // 1|-1
|
||||||
|
|
||||||
|
// for sorting
|
||||||
|
'sort_by' => getRequest('sort', ''),
|
||||||
|
|
||||||
|
// for pagination, not exactly for filter but put here to reserve the keys
|
||||||
|
'numPerPage' => getRequestInt("show", 20),
|
||||||
|
'page' => getPageId(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if(getRequest("excluded_ids", '') != '') {
|
||||||
|
$base_filter['excluded_ids'] = explode("-", getRequest("excluded_ids", ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(getRequest("included_ids", '') != '') {
|
||||||
|
$base_filter['included_ids'] = explode("-", getRequest("included_ids", ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $base_filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function formatItemInList(array $item_info)
|
||||||
|
{
|
||||||
|
return $item_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function formatItemInfo(array $item_info)
|
||||||
|
{
|
||||||
|
return $item_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getListByIds(array $list_id, array $condition = array()) : array
|
||||||
|
{
|
||||||
|
$item_list = array_map(function ($item){
|
||||||
|
return $this->formatItemInList($item);
|
||||||
|
}, $this->iEntityModel->getListByIds($list_id, $condition));
|
||||||
|
|
||||||
|
if($this->iEntityLanguageModel) {
|
||||||
|
$item_list_language_info = $this->iEntityLanguageModel->getListByIds($list_id);
|
||||||
|
|
||||||
|
$final_list = [];
|
||||||
|
foreach ($item_list as $item) {
|
||||||
|
$item_language_info = $item_list_language_info[$item['id']] ?? ["not_translated" => true];
|
||||||
|
$final_list[] = array_merge($item, $item_language_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $final_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $item_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// for extending controller class to validate and clean data in the $raw_filter_condition (ie. from URL)
|
||||||
|
// before sending to model for data querying
|
||||||
|
// extending controller must overwrite this
|
||||||
|
protected function validateAndCleanFilterCondition(array $raw_filter_condition) : array {
|
||||||
|
|
||||||
|
$clean_values = [];
|
||||||
|
|
||||||
|
foreach ($raw_filter_condition as $key => $value) {
|
||||||
|
// default
|
||||||
|
if(is_array($value)) {
|
||||||
|
$clean_values[$key] = DataClean::makeListOfInputSafe($value, DataType::ID);
|
||||||
|
}else{
|
||||||
|
$clean_values[$key] = DataClean::makeInputSafe($value, DataType::ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $clean_values;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description utility to inspect the actual filters which will be used in getList
|
||||||
|
* make sure to edit the ::_buildQueryConditionExtend method on the Model so the Model will parse the filters provided by controller here
|
||||||
|
* @param array $raw_filter_condition
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getActualFilterCondition(array $raw_filter_condition) : array
|
||||||
|
{
|
||||||
|
return $this->buildFilterQuery($raw_filter_condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description utility to inspect the actual filters which will be used in getList by Model
|
||||||
|
* @param array $raw_filter_condition
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public function getModelFilterCondition(array $raw_filter_condition) : array
|
||||||
|
{
|
||||||
|
return $this->iEntityModel->getQueryCondition($this->buildFilterQuery($raw_filter_condition));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getList(array $raw_filter_condition) : array
|
||||||
|
{
|
||||||
|
|
||||||
|
$filter_condition = $this->buildFilterQuery($raw_filter_condition);
|
||||||
|
//debug_var($filter_condition);
|
||||||
|
|
||||||
|
$item_list = array_map(function ($item){
|
||||||
|
return $this->formatItemInList($item);
|
||||||
|
}, $this->iEntityModel->getList($filter_condition));
|
||||||
|
|
||||||
|
|
||||||
|
if($this->iEntityLanguageModel) {
|
||||||
|
|
||||||
|
$item_list_ids = array_map(function ($item){ return $item['id']; }, $item_list);
|
||||||
|
$item_list_language_info = $this->iEntityLanguageModel->getListByIds($item_list_ids);
|
||||||
|
|
||||||
|
$final_list = [];
|
||||||
|
foreach ($item_list as $item) {
|
||||||
|
$item_language_info = $item_list_language_info[$item['id']] ?? ["not_translated" => true];
|
||||||
|
$final_list[] = array_merge($item, $item_language_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $final_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $item_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getTotal(array $raw_filter_condition) : int
|
||||||
|
{
|
||||||
|
$filter_condition = $this->buildFilterQuery($raw_filter_condition);
|
||||||
|
|
||||||
|
return $this->iEntityModel->getTotal($filter_condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function buildFilterQuery(array $raw_filter_condition) : array
|
||||||
|
{
|
||||||
|
$filter_condition = $this->validateAndCleanFilterCondition($raw_filter_condition);
|
||||||
|
|
||||||
|
// special case to filter out which ids have not been translated
|
||||||
|
if(isset($filter_condition['translated']) && $filter_condition['translated'] && $this->iEntityLanguageModel) {
|
||||||
|
if($filter_condition['translated'] == 1) {
|
||||||
|
$filter_condition['included_ids'] = $this->iEntityLanguageModel->getTranslatedIds();
|
||||||
|
}else{
|
||||||
|
$filter_condition['excluded_ids'] = $this->iEntityLanguageModel->getTranslatedIds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filter_condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getInfo($id): ?array
|
||||||
|
{
|
||||||
|
if(!$id) return null;
|
||||||
|
|
||||||
|
return self::getCache("getInfo-".$id."-".$this->view_language, function () use ($id){
|
||||||
|
|
||||||
|
$info = $this->iEntityModel->getInfo($id);
|
||||||
|
|
||||||
|
if($this->iEntityLanguageModel && $info) {
|
||||||
|
$item_language_info = $this->iEntityLanguageModel->getInfo($id);
|
||||||
|
//debug_var($item_language_info);
|
||||||
|
|
||||||
|
if($item_language_info) {
|
||||||
|
return $this->formatItemInfo(array_merge($info, $item_language_info));
|
||||||
|
}else{
|
||||||
|
$info["not_translated"] = true;
|
||||||
|
|
||||||
|
return $this->formatItemInfo($info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($info) ? $this->formatItemInfo($info) : null;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
344
inc/Hura8/System/Controller/aExcelDownloadController.php
Normal file
344
inc/Hura8/System/Controller/aExcelDownloadController.php
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Created by Glee Ltd.
|
||||||
|
* User: Hieu
|
||||||
|
* Date: 20-Aug-19
|
||||||
|
* Time: 11:16 AM
|
||||||
|
* Description:
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Hura8\System\Controller;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\iExcelDownload;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||||
|
|
||||||
|
|
||||||
|
abstract class aExcelDownloadController implements iExcelDownload
|
||||||
|
{
|
||||||
|
protected static $column_names = [
|
||||||
|
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
|
||||||
|
'AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK','AL','AM',
|
||||||
|
'AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ',
|
||||||
|
//...
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $work_sheet_title = "Danh sách"; //
|
||||||
|
protected $export_file_name = '';
|
||||||
|
|
||||||
|
/* @var $objExcel Spreadsheet */
|
||||||
|
protected $objExcel;
|
||||||
|
|
||||||
|
/* @var $currentActiveSheet Worksheet */
|
||||||
|
protected $currentActiveSheet;
|
||||||
|
|
||||||
|
protected $client_excel_col_config = [
|
||||||
|
/*"A" => array(
|
||||||
|
'name' => 'ID Sản phẩm Web',
|
||||||
|
'width' => '10',
|
||||||
|
'data_field_name' => 'id',
|
||||||
|
),
|
||||||
|
"B" => array(
|
||||||
|
'name' => 'Mã kho (SKU)',
|
||||||
|
'width' => '10',
|
||||||
|
'data_field_name' => 'storeSKU',
|
||||||
|
),*/
|
||||||
|
//...
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $field_column_mappings = [
|
||||||
|
//'name' => "A",
|
||||||
|
//'price' => "B",
|
||||||
|
];
|
||||||
|
|
||||||
|
private $header_row_index = 2;
|
||||||
|
|
||||||
|
// hold cach for some operations
|
||||||
|
protected $cache = [];
|
||||||
|
|
||||||
|
protected $format_item_middlewares = []; // list of middleware object to format item
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct($client_config_file_name='', $export_file_name='', $work_sheet_title = '')
|
||||||
|
{
|
||||||
|
if($client_config_file_name) {
|
||||||
|
$this->setColumnConfigUseConfigFile($client_config_file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->export_file_name = ($export_file_name) ?: "file_".CURRENT_TIME;
|
||||||
|
$this->work_sheet_title = ($work_sheet_title) ?: "Danh sách";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function setColumnConfigUseConfigFile($client_config_file_name) {
|
||||||
|
//this from a config file for each client
|
||||||
|
$client_config_file = "config/client/excel/".$client_config_file_name;
|
||||||
|
if( ! file_exists(ROOT_DIR .'/'. $client_config_file)) {
|
||||||
|
die("Please create config file: ".$client_config_file);
|
||||||
|
}
|
||||||
|
$client_fields_config = include ROOT_DIR .'/'. $client_config_file;
|
||||||
|
|
||||||
|
// auto add excel column names based on fields' index
|
||||||
|
$this->client_excel_col_config = $this->_make_columns(array_values($client_fields_config));
|
||||||
|
|
||||||
|
// create field-col map
|
||||||
|
$this->createFieldColumnMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function createFieldColumnMappings() {
|
||||||
|
$field_column_mappings = [];
|
||||||
|
foreach ($this->client_excel_col_config as $column_name => $_prop) {
|
||||||
|
$field_column_mappings[$_prop['data_field_name']] = $column_name;
|
||||||
|
}
|
||||||
|
$this->field_column_mappings = $field_column_mappings;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function setColumnConfigManually(array $col_config) {
|
||||||
|
$this->client_excel_col_config = $this->_make_columns($col_config);
|
||||||
|
// create field-col map
|
||||||
|
$this->createFieldColumnMappings();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function _make_columns($fields_config) {
|
||||||
|
$new_array = [];
|
||||||
|
$total_names = sizeof(static::$column_names);
|
||||||
|
foreach ($fields_config as $index => $config) {
|
||||||
|
if($index >= $total_names) break;
|
||||||
|
$new_array[static::$column_names[$index]] = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $new_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function start(array $options = [
|
||||||
|
"debug_mode" => '', // show-item-list
|
||||||
|
"excelOption" => [],
|
||||||
|
"sheetOption" => [],
|
||||||
|
"sheetStartRowNumber" => 3,
|
||||||
|
"sheetHeaderRowNumber" => 2,
|
||||||
|
"getItemListOption" => [
|
||||||
|
"brand" => [],
|
||||||
|
"category" => [],
|
||||||
|
"page" => 1,
|
||||||
|
"limit" => 100,
|
||||||
|
],
|
||||||
|
"exportFileOption" => [],
|
||||||
|
]) {
|
||||||
|
|
||||||
|
$debug_mode = $options['debug_mode'] ?? false;
|
||||||
|
// debug mode
|
||||||
|
if($debug_mode) {
|
||||||
|
// show item list
|
||||||
|
if($debug_mode == 'show-item-list') {
|
||||||
|
$item_list = $this->getItemList($options['getItemListOption']);
|
||||||
|
print_r($item_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
// show formatted list
|
||||||
|
if($debug_mode == 'show-formatted-list') {
|
||||||
|
$item_list = $this->formatItemList( $this->getItemList($options['getItemListOption']) );
|
||||||
|
print_r($item_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup
|
||||||
|
if(isset($options['sheetHeaderRowNumber']) && $options['sheetHeaderRowNumber']) {
|
||||||
|
$this->header_row_index = $options['sheetHeaderRowNumber'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->createExcelObject($options['excelOption'] ?? []);
|
||||||
|
$this->createActiveSheet(0, $options['sheetOption'] ?? []);
|
||||||
|
|
||||||
|
// num beforeWriteList
|
||||||
|
$this->beforeWriteList();
|
||||||
|
|
||||||
|
// fetch all items and write till the end
|
||||||
|
$has_fetch_all_items = false;
|
||||||
|
$start_row = (isset($options["sheetStartRowNumber"])) ? $options["sheetStartRowNumber"] : 3;
|
||||||
|
|
||||||
|
$item_list = $this->formatItemList( $this->getItemList($options['getItemListOption']) );
|
||||||
|
|
||||||
|
//debug_var($item_list);
|
||||||
|
//exit;
|
||||||
|
|
||||||
|
$has_fetch_all_items = true;
|
||||||
|
$this->writeItemsToExcel($start_row, $item_list);
|
||||||
|
|
||||||
|
/*$getItemListOption = $options['getItemListOption'];
|
||||||
|
$pageIndex = 0;
|
||||||
|
|
||||||
|
while (!$has_fetch_all_items) {
|
||||||
|
// run from page 1->end
|
||||||
|
$pageIndex += 1;
|
||||||
|
$getItemListOption["page"] = $pageIndex;
|
||||||
|
$item_list = $this->getItemList($getItemListOption);
|
||||||
|
|
||||||
|
// flag
|
||||||
|
if(!sizeof($item_list)) {
|
||||||
|
$has_fetch_all_items = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// else, start write
|
||||||
|
$last_row = $this->writeItemsToExcel($start_row, $item_list);
|
||||||
|
|
||||||
|
// update $start_row
|
||||||
|
$start_row = $last_row;// + 1
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// export
|
||||||
|
if($has_fetch_all_items) {
|
||||||
|
$this->getExcelFile($options['exportFileOption']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// abstract methods
|
||||||
|
protected function beforeWriteList() { }
|
||||||
|
abstract protected function getItemList(array $options);
|
||||||
|
abstract protected function defaultFormatItemInfo(array $item_info, $index =0);
|
||||||
|
|
||||||
|
protected function registerFormatItemInfoMiddleware($middleware_ojb) {
|
||||||
|
$this->format_item_middlewares[] = $middleware_ojb;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function formatItemInfo(array $item_info, $index = 0) {
|
||||||
|
|
||||||
|
// apply middleware
|
||||||
|
if(sizeof($this->format_item_middlewares)) {
|
||||||
|
foreach ($this->format_item_middlewares as $_middleware) {
|
||||||
|
$item_info = call_user_func($_middleware, $item_info);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$item_info = $this->defaultFormatItemInfo($item_info, $index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $item_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function createExcelObject(array $options)
|
||||||
|
{
|
||||||
|
// Create new Spreadsheet object
|
||||||
|
$this->objExcel = new Spreadsheet();
|
||||||
|
|
||||||
|
// Set properties
|
||||||
|
$this->objExcel->getProperties()->setCreator("Hurasoft");
|
||||||
|
$this->objExcel->getProperties()->setLastModifiedBy("Hurasoft");
|
||||||
|
$this->objExcel->getProperties()->setTitle("Excel Document");
|
||||||
|
$this->objExcel->getProperties()->setSubject("Excel Document");
|
||||||
|
$this->objExcel->getProperties()->setDescription("Tao file excel");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function createActiveSheet($sheet_index = 0, array $options=[]) {
|
||||||
|
// Create a first sheet, representing sales data
|
||||||
|
$this->objExcel->setActiveSheetIndex($sheet_index);
|
||||||
|
$this->currentActiveSheet = $this->objExcel->getActiveSheet();
|
||||||
|
$this->currentActiveSheet->setCellValueExplicit('A1', $this->work_sheet_title, DataType::TYPE_STRING);
|
||||||
|
$this->currentActiveSheet->getStyle('A1')->getFont()->setSize(16);
|
||||||
|
$this->currentActiveSheet->getStyle('A1')->getFont()->setBold(true);
|
||||||
|
|
||||||
|
// Set header row
|
||||||
|
$row_index = $this->header_row_index;
|
||||||
|
foreach ($this->client_excel_col_config as $col_name => $_prop) {
|
||||||
|
$col_width = (isset($_prop['width']) && intval($_prop['width']) > 0) ? intval($_prop['width']) : 15;
|
||||||
|
$this->currentActiveSheet->getColumnDimension($col_name)->setWidth($col_width);
|
||||||
|
$this->currentActiveSheet->setCellValueExplicit($col_name. $row_index, $_prop["name"], DataType::TYPE_STRING);
|
||||||
|
$this->currentActiveSheet->getStyle($col_name . $row_index)->getFont()->setBold(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function formatItemList(array $item_list)
|
||||||
|
{
|
||||||
|
$new_list = [];
|
||||||
|
foreach ( $item_list as $index => $item_info ) {
|
||||||
|
$new_list[$index] = $this->formatItemInfo($item_info, $index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $new_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function writeItemsToExcel($start_row = 1, array $item_list=[])
|
||||||
|
{
|
||||||
|
$write_row = $start_row;
|
||||||
|
foreach ( $item_list as $index => $item_info ) {
|
||||||
|
|
||||||
|
// write each field to its corresponding columns
|
||||||
|
foreach ($item_info as $_field => $_value) {
|
||||||
|
if( !isset($this->field_column_mappings[$_field])) continue;
|
||||||
|
|
||||||
|
if(is_array($_value)) $_value = serialize($_value);
|
||||||
|
|
||||||
|
$write_column = $this->field_column_mappings[$_field];
|
||||||
|
|
||||||
|
$this->currentActiveSheet->setCellValueExplicit($write_column . $write_row, $_value, DataType::TYPE_STRING);
|
||||||
|
$this->currentActiveSheet->getStyle($write_column . $write_row)->getAlignment()->setWrapText(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// next rows
|
||||||
|
$write_row += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the last row
|
||||||
|
return $write_row;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function getExcelFile(array $options)
|
||||||
|
{
|
||||||
|
// write to a local file
|
||||||
|
$local_file = $options['local_file'] ?? '';
|
||||||
|
if($local_file) {
|
||||||
|
$this->_save_to_file($local_file);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// default: export to browser to download
|
||||||
|
$this->_export_to_browser();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function cleanUp(){
|
||||||
|
// clean up
|
||||||
|
$this->objExcel->disconnectWorksheets();
|
||||||
|
unset($this->objExcel);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _save_to_file($file_path){
|
||||||
|
|
||||||
|
// delete old file if exist
|
||||||
|
if(file_exists($file_path)) {
|
||||||
|
@unlink($file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$writer = new Xlsx($this->objExcel);
|
||||||
|
$writer->save($file_path);
|
||||||
|
$this->cleanUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _export_to_browser(){
|
||||||
|
// Rename sheet
|
||||||
|
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||||
|
header('Content-Disposition: attachment;filename="'.$this->export_file_name.'.xlsx"');
|
||||||
|
header('Cache-Control: max-age=0');
|
||||||
|
|
||||||
|
$writer = new Xlsx($this->objExcel);
|
||||||
|
ob_end_clean();
|
||||||
|
$writer->save('php://output');
|
||||||
|
|
||||||
|
$this->cleanUp();
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
355
inc/Hura8/System/Controller/aExcelUploadController.php
Normal file
355
inc/Hura8/System/Controller/aExcelUploadController.php
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System\Controller;
|
||||||
|
|
||||||
|
use Hura8\System\ReadExcel;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||||
|
|
||||||
|
|
||||||
|
abstract class aExcelUploadController
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $update_option = [];
|
||||||
|
|
||||||
|
protected static $column_names = [
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||||
|
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||||
|
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||||
|
//...
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $file_input_name = "file_excel"; //
|
||||||
|
|
||||||
|
protected $client_excel_col_config = [
|
||||||
|
/*"A" => array(
|
||||||
|
'name' => 'ID Sản phẩm Web',
|
||||||
|
'width' => '10',
|
||||||
|
'data_field_name' => 'id',
|
||||||
|
),
|
||||||
|
"B" => array(
|
||||||
|
'name' => 'Mã kho (SKU)',
|
||||||
|
'width' => '10',
|
||||||
|
'data_field_name' => 'storeSKU',
|
||||||
|
),*/
|
||||||
|
//...
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $field_column_mappings = [
|
||||||
|
//'name' => "A",
|
||||||
|
//'price' => "B",
|
||||||
|
];
|
||||||
|
|
||||||
|
// hold cache for some operations
|
||||||
|
protected $cache = [];
|
||||||
|
|
||||||
|
protected $format_item_middlewares = []; // list of middleware object to format item
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct($client_config_file_name = '', $file_input_name = '', array $update_option = [])
|
||||||
|
{
|
||||||
|
|
||||||
|
if(!$client_config_file_name) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//this from a config file for each client
|
||||||
|
$client_config_file = "config/client/excel/" . $client_config_file_name;
|
||||||
|
if (!file_exists(ROOT_DIR . '/' . $client_config_file)) {
|
||||||
|
die("Please create config file: " . $client_config_file);
|
||||||
|
}
|
||||||
|
$client_fields_config = include ROOT_DIR . '/' . $client_config_file;
|
||||||
|
|
||||||
|
if($file_input_name) {
|
||||||
|
$this->file_input_name = $file_input_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// auto add excel column names based on fields' index
|
||||||
|
$this->client_excel_col_config = $this->_make_columns(array_values($client_fields_config));
|
||||||
|
|
||||||
|
// create field-col map
|
||||||
|
$field_column_mappings = [];
|
||||||
|
foreach ($this->client_excel_col_config as $column_name => $_prop) {
|
||||||
|
if(!$_prop['data_field_name']) continue;
|
||||||
|
|
||||||
|
// skip column which is not for upload
|
||||||
|
if(isset($_prop['for_upload']) && !$_prop['for_upload']) continue;
|
||||||
|
|
||||||
|
$field_column_mappings[$_prop['data_field_name']] = $column_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->field_column_mappings = $field_column_mappings;
|
||||||
|
|
||||||
|
$this->update_option = $update_option;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function updateUpdateOptions(array $update_option = []) {
|
||||||
|
foreach ($update_option as $key => $value) {
|
||||||
|
$this->update_option[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getColumnNotForUpload() {
|
||||||
|
$result = [];
|
||||||
|
foreach ($this->client_excel_col_config as $column_name => $_prop) {
|
||||||
|
// skip column which is not for upload
|
||||||
|
if(isset($_prop['for_upload']) && !$_prop['for_upload']) $result[$column_name] = $_prop['name'] ;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getColumnCanUpdate() {
|
||||||
|
$result = [];
|
||||||
|
foreach ($this->client_excel_col_config as $column_name => $_prop) {
|
||||||
|
// skip column which is not for upload
|
||||||
|
if(isset($_prop['for_upload']) && !$_prop['for_upload']) continue;
|
||||||
|
|
||||||
|
// skip column which is not for update
|
||||||
|
if(isset($_prop['can_update']) && !$_prop['can_update']) continue;
|
||||||
|
|
||||||
|
$result[] = $_prop ;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function getRequiredFields() {
|
||||||
|
$result = [];
|
||||||
|
foreach ($this->client_excel_col_config as $column_name => $_prop) {
|
||||||
|
// skip column which is not for upload
|
||||||
|
if(isset($_prop['required']) && $_prop['required']) $result[] = $_prop['data_field_name'] ;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function _make_columns($fields_config)
|
||||||
|
{
|
||||||
|
$new_array = [];
|
||||||
|
$total_names = sizeof(static::$column_names);
|
||||||
|
foreach ($fields_config as $index => $config) {
|
||||||
|
if ($index >= $total_names) break;
|
||||||
|
$new_array[static::$column_names[$index]] = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $new_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function getExcelFileExt($excel_file){
|
||||||
|
$ext = substr(strrchr($excel_file, "."), 1);
|
||||||
|
return (in_array($ext, ["xls", "xlsx"])) ? $ext : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function start($sheet_start_row = 3, $batch_mode = false, $batch_size=100)
|
||||||
|
{
|
||||||
|
$ext = $this->getExcelFileExt($_FILES[$this->file_input_name]["name"]);
|
||||||
|
if(!$ext) {
|
||||||
|
return [
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Invalid excel file',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$objReadExcel = new ReadExcel($ext);
|
||||||
|
|
||||||
|
$all_rows = $objReadExcel->read(
|
||||||
|
$_FILES[$this->file_input_name]["tmp_name"],
|
||||||
|
$sheet_start_row,
|
||||||
|
$this->field_column_mappings,
|
||||||
|
'',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->beforeProcessRows($all_rows);
|
||||||
|
|
||||||
|
$success_row_counter = 0;
|
||||||
|
|
||||||
|
if($batch_mode) {
|
||||||
|
// batch mode
|
||||||
|
$small_batch = [];
|
||||||
|
$counter = 0;
|
||||||
|
foreach ($all_rows as $sheet_index => $sheet_content) {
|
||||||
|
foreach ($sheet_content as $row_id => $row_content) {
|
||||||
|
|
||||||
|
$formatted_info = $this->formatItemInfo($this->convertColToField($row_content));
|
||||||
|
if(!$formatted_info) continue;
|
||||||
|
|
||||||
|
$counter += 1;
|
||||||
|
|
||||||
|
$small_batch[] = $formatted_info;
|
||||||
|
if($counter % $batch_size == 0) {
|
||||||
|
$success_row_counter += $this->processBatchItems($small_batch);
|
||||||
|
// reset
|
||||||
|
$counter = 0;
|
||||||
|
$small_batch = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// process the remain
|
||||||
|
if(sizeof($small_batch)) {
|
||||||
|
$success_row_counter += $this->processBatchItems($small_batch);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// single item mode
|
||||||
|
foreach ($all_rows as $sheet_index => $sheet_content) {
|
||||||
|
foreach ($sheet_content as $row_index => $row_content) {
|
||||||
|
|
||||||
|
$formatted_info = $this->formatItemInfo($this->convertColToField($row_content));
|
||||||
|
if(!$formatted_info) continue;
|
||||||
|
|
||||||
|
if($this->processItem($formatted_info, $row_index)){
|
||||||
|
$success_row_counter += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//unset($all_rows);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => '',
|
||||||
|
'success_counter' => $success_row_counter,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function convertExcelDateValue($value) {
|
||||||
|
// Date value in Excel's cell can be displayed as text or date value, we need to check for both
|
||||||
|
$format_value = $this->formatExcelUploadDate($value);
|
||||||
|
|
||||||
|
if(!$format_value) {
|
||||||
|
// check if it's excel date
|
||||||
|
try {
|
||||||
|
$format_value = (is_numeric($value)) ? date("d-m-Y", Date::excelToTimestamp($value, date_default_timezone_get())) : '';
|
||||||
|
}catch (\Exception $e) {
|
||||||
|
$format_value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $format_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function formatExcelUploadDate($input_date) {
|
||||||
|
$check_date_pattern = "/\d{1,2}-\d{1,2}-\d{4}/i";
|
||||||
|
$format_date = str_replace("/", "-", $input_date);
|
||||||
|
|
||||||
|
if(preg_match($check_date_pattern, $format_date)) {
|
||||||
|
return date("d-m-Y", strtotime($format_date));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function convertExcelHourMinuteValue($value) {
|
||||||
|
$format_value = $this->formatExcelUploadHourMinute($value);
|
||||||
|
|
||||||
|
if(!$format_value) {
|
||||||
|
// check if it's excel date
|
||||||
|
try {
|
||||||
|
$format_value = (is_numeric($value)) ? date("H:i", Date::excelToTimestamp($value, date_default_timezone_get())) : '';
|
||||||
|
}catch (\Exception $e) {
|
||||||
|
$format_value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $format_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function formatExcelUploadHourMinute($input_date) {
|
||||||
|
$check_date_pattern = "/\d{1,2}:\d{1,2}/i";
|
||||||
|
//$format_date = str_replace("/", "-", $input_date);
|
||||||
|
|
||||||
|
if(preg_match($check_date_pattern, $input_date)) {
|
||||||
|
return date("H:i", strtotime($input_date));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ['A' => '12', 'B' => 'ten sp'] => ['id' => '12', 'product_name' => 'ten sp']
|
||||||
|
protected function convertColToField(array $row_content ) {
|
||||||
|
|
||||||
|
if(!$this->field_column_mappings) {
|
||||||
|
return array_values($row_content);
|
||||||
|
}
|
||||||
|
|
||||||
|
$item_info = [];
|
||||||
|
foreach ($this->field_column_mappings as $field => $col) {
|
||||||
|
//if(!isset($row_content[$col])) continue;
|
||||||
|
$item_info[$field] = $row_content[$col];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $item_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
// abstract methods
|
||||||
|
protected function beforeProcessRows(array &$all_read_rows){
|
||||||
|
// default nothing
|
||||||
|
// derived class should overwrite this method
|
||||||
|
}
|
||||||
|
abstract protected function processItem(array $item_info, $row_index=0);
|
||||||
|
abstract protected function processBatchItems(array $item_list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $item_info
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
abstract protected function formatItemInfoBeforeProcess(array $item_info);
|
||||||
|
|
||||||
|
public function registerFormatItemInfoMiddleware($middleware_ojb)
|
||||||
|
{
|
||||||
|
$this->format_item_middlewares[] = $middleware_ojb;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected $date_fields = [];
|
||||||
|
public function setDateFields(array $date_fields) {
|
||||||
|
$this->date_fields = $date_fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $hour_minute_fields = [];
|
||||||
|
public function setHourMinuteFields(array $hour_minute_fields) {
|
||||||
|
$this->hour_minute_fields = $hour_minute_fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $item_info
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function formatItemInfo(array $item_info)
|
||||||
|
{
|
||||||
|
$copy = $item_info;
|
||||||
|
|
||||||
|
// apply middleware
|
||||||
|
if (sizeof($this->format_item_middlewares)) {
|
||||||
|
foreach ($this->format_item_middlewares as $_middleware) {
|
||||||
|
$copy = call_user_func($_middleware, $copy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->date_fields as $field) {
|
||||||
|
$copy[$field] = $this->convertExcelDateValue($item_info[$field]);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->hour_minute_fields as $field) {
|
||||||
|
$copy[$field] = $this->convertExcelHourMinuteValue($item_info[$field]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->formatItemInfoBeforeProcess($copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
81
inc/Hura8/System/Controller/aPublicEntityBaseController.php
Normal file
81
inc/Hura8/System/Controller/aPublicEntityBaseController.php
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System\Controller;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\iPublicEntityController;
|
||||||
|
|
||||||
|
abstract class aPublicEntityBaseController extends aEntityBaseController implements iPublicEntityController
|
||||||
|
{
|
||||||
|
|
||||||
|
public function getListByIds(array $list_id, array $condition = array()) : array
|
||||||
|
{
|
||||||
|
$item_list = array_map(function ($item){
|
||||||
|
return $this->formatItemInList($item);
|
||||||
|
}, $this->iEntityModel->getListByIds($list_id, $condition));
|
||||||
|
|
||||||
|
if($this->iEntityLanguageModel) {
|
||||||
|
$item_list_language_info = $this->iEntityLanguageModel->getListByIds($list_id);
|
||||||
|
|
||||||
|
$final_list = [];
|
||||||
|
foreach ($item_list as $item) {
|
||||||
|
$item_language_info = isset($item_list_language_info[$item['id']]) ? $item_list_language_info[$item['id']] : ["not_translated" => true];
|
||||||
|
$final_list[] = array_merge($item, $item_language_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $final_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $item_list;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getList(array $condition) : array
|
||||||
|
{
|
||||||
|
$copy = $condition;
|
||||||
|
|
||||||
|
// public items must have status=1
|
||||||
|
$copy['status'] = 1;
|
||||||
|
|
||||||
|
return parent::getList($copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getTotal(array $condition) : int
|
||||||
|
{
|
||||||
|
$copy = $condition;
|
||||||
|
|
||||||
|
// public items must have status=1
|
||||||
|
$copy['status'] = 1;
|
||||||
|
|
||||||
|
return parent::getTotal($copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getInfo($id): ?array
|
||||||
|
{
|
||||||
|
if(!$id) return null;
|
||||||
|
|
||||||
|
return self::getCache("getInfo-".$id."-".$this->view_language, function () use ($id){
|
||||||
|
|
||||||
|
$info = $this->iEntityModel->getInfo($id);
|
||||||
|
|
||||||
|
if($this->iEntityLanguageModel && $info) {
|
||||||
|
$item_language_info = $this->iEntityLanguageModel->getInfo($id);
|
||||||
|
if($item_language_info) {
|
||||||
|
return $this->formatItemInfo(array_merge($info, $item_language_info));
|
||||||
|
}else{
|
||||||
|
$info["not_translated"] = true;
|
||||||
|
|
||||||
|
return $this->formatItemInfo($info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($info) ? $this->formatItemInfo($info) : null;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
270
inc/Hura8/System/Controller/bFileHandle.php
Normal file
270
inc/Hura8/System/Controller/bFileHandle.php
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System\Controller;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\AppResponse;
|
||||||
|
use Hura8\Interfaces\FileHandleInfo;
|
||||||
|
use Hura8\Interfaces\FileHandleResponse;
|
||||||
|
use Hura8\System\FileSystem;
|
||||||
|
use Hura8\System\HuraImage;
|
||||||
|
use Hura8\System\IDGenerator;
|
||||||
|
use League\MimeTypeDetection\FinfoMimeTypeDetector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @date 16-Jan-2024
|
||||||
|
* @description based-class to handling files, this is used for :
|
||||||
|
* - FileUpload class
|
||||||
|
* - CopyFileFromUrl class
|
||||||
|
* ...
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
abstract class bFileHandle
|
||||||
|
{
|
||||||
|
|
||||||
|
static $image_extensions = array(".jpg",".jpeg",".gif", ".png", ".webp", '.avif', ".ico");
|
||||||
|
|
||||||
|
protected $permit_file_extensions = array(
|
||||||
|
".jpg",".jpeg",".gif", ".png", ".webp", '.avif',
|
||||||
|
".doc", ".docx",".xls",".xlsx", ".ppt", ".pdf",
|
||||||
|
".rar", ".zip",
|
||||||
|
//".avi",".mov",".mpg", ".wmv", ".mpeg",
|
||||||
|
//".mp3",".mp4", ".ogg", ".oga", ".wav", ".wma"
|
||||||
|
);
|
||||||
|
|
||||||
|
protected $permit_mine_types = array(
|
||||||
|
'image/jpeg', 'image/png','image/gif', 'image/webp', 'image/avif',
|
||||||
|
|
||||||
|
// zip files
|
||||||
|
'application/zip', 'application/x-zip-compressed', 'multipart/x-zip', 'application/x-compressed',
|
||||||
|
);
|
||||||
|
|
||||||
|
// full system's path where the file will be finally stored or it's currently there for processing
|
||||||
|
// example: /var/www/html/domain.com/public_html/media/product/
|
||||||
|
protected $target_dir = "";
|
||||||
|
|
||||||
|
// directory appears to the public
|
||||||
|
// example: /media/product/
|
||||||
|
protected $public_dir = "";
|
||||||
|
|
||||||
|
protected $tmp_dir = ROOT_DIR . "/var/tmp_upload/"; // system tmp dir to store uploaded file for security examination before moving to target_dir
|
||||||
|
protected $tmp_folder = ""; // tmp folder per session in the $tmp_dir to hold user's files. This folder will be removed when operation is complete
|
||||||
|
|
||||||
|
protected $setup_success = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $target_dir will be created if not exist i.e. media/product/ or media/user_upload/date('d-m-Y')
|
||||||
|
* @param ?array $permit_file_extensions null to allow all default file extensions
|
||||||
|
*/
|
||||||
|
public function __construct( string $target_dir, ?array $permit_file_extensions = null ) {
|
||||||
|
|
||||||
|
if(is_array($permit_file_extensions)) {
|
||||||
|
$this->permit_file_extensions = $permit_file_extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($target_dir) {
|
||||||
|
$this->target_dir = PUBLIC_DIR . DIRECTORY_SEPARATOR. $target_dir;
|
||||||
|
$this->public_dir = "/".$target_dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
$setup_res = $this->setUp();
|
||||||
|
if($setup_res->getStatus() == AppResponse::SUCCESS) {
|
||||||
|
$this->setup_success = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description (rename if set) and resize file
|
||||||
|
* @param string $name
|
||||||
|
* @param string $new_name
|
||||||
|
* @param array $resized_sizes [] array('small' => ['width' => 100, 'height' => 100], 'large' => ['width' => 200, 'height' => 200] )
|
||||||
|
* @return array|false
|
||||||
|
*/
|
||||||
|
public function resizeFile(string $name, string $new_name = '', array $resized_sizes = []) {
|
||||||
|
|
||||||
|
if($new_name) {
|
||||||
|
$renamed = $this->renameFile($name, $new_name);
|
||||||
|
if(!$renamed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//$file_name = $renamed['file_name'];
|
||||||
|
//$public_path = $renamed['public_path'];
|
||||||
|
$local_path = $renamed['local_path'];
|
||||||
|
}else{
|
||||||
|
//$file_name = $name;
|
||||||
|
//$public_path = $this->public_dir . "/".$name;
|
||||||
|
$local_path = $this->target_dir . "/" . $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
$objHuraImage = new HuraImage();
|
||||||
|
|
||||||
|
list(, $expected_files, ) = $objHuraImage->resize($local_path, $resized_sizes);
|
||||||
|
|
||||||
|
return $expected_files;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description we can rename the uploaded file to new file name, for example: we want product's image have the format [PRODUCT_ID]-name
|
||||||
|
* @param string $name
|
||||||
|
* @param string $new_name
|
||||||
|
* @return array | false
|
||||||
|
*/
|
||||||
|
public function renameFile(string $name, string $new_name) {
|
||||||
|
if(@rename($this->target_dir . "/" . $name, $this->target_dir . "/" . $new_name)){
|
||||||
|
return [
|
||||||
|
"file_name" => $new_name,
|
||||||
|
"public_path" => $this->public_dir . "/".$new_name,
|
||||||
|
"local_path" => $this->target_dir . "/" . $new_name,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// public utility
|
||||||
|
public static function getFileExtension($file_name) {
|
||||||
|
return strtolower(strrchr($file_name,"."));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description run clean up after finish using the FileHandle instance
|
||||||
|
*/
|
||||||
|
public function cleanUp() {
|
||||||
|
if($this->tmp_folder) {
|
||||||
|
FileSystem::removeDir($this->tmp_folder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function processFile(
|
||||||
|
$original_file_name,
|
||||||
|
$original_file_tmp_name, // temporary uploaded file as in case of $_FILES[$input_file_name]["tmp_name"]
|
||||||
|
$fixed_file_name="",
|
||||||
|
$max_file_size=0
|
||||||
|
) : FileHandleResponse {
|
||||||
|
|
||||||
|
if(!$original_file_name) {
|
||||||
|
return new FileHandleResponse(AppResponse::ERROR, 'no file', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_size = filesize($original_file_tmp_name);
|
||||||
|
if($max_file_size > 0 && $max_file_size < $file_size) {
|
||||||
|
return new FileHandleResponse(AppResponse::ERROR, 'Size is too large: '.round($file_size/1000).'KB', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
//validate extension
|
||||||
|
$file_ext = self::getFileExtension($original_file_name);
|
||||||
|
|
||||||
|
if(!in_array($file_ext, $this->permit_file_extensions)) {
|
||||||
|
return new FileHandleResponse(AppResponse::ERROR, "Type ".$file_ext." is not allowed!", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
$file_name = substr($original_file_name, 0, strrpos($original_file_name,"."));
|
||||||
|
$file_name = preg_replace("/[^a-z0-9_-]/i","", $file_name);
|
||||||
|
$file_name = substr($file_name, 0, 50); // max- length
|
||||||
|
$clean_file_name = ($fixed_file_name) ?: $file_name . $file_ext;
|
||||||
|
|
||||||
|
$tmp_file_path = $this->tmp_folder . "/". $clean_file_name;
|
||||||
|
$return_data = null;
|
||||||
|
|
||||||
|
//debug_var([$original_data, $tmp_file_path]);
|
||||||
|
if(@rename($original_file_tmp_name, $tmp_file_path)){
|
||||||
|
|
||||||
|
$is_file_image = (in_array($file_ext, static::$image_extensions ));
|
||||||
|
|
||||||
|
if($is_file_image) {
|
||||||
|
list($width, $height) = getimagesize($tmp_file_path);
|
||||||
|
}else{
|
||||||
|
$width = 0;
|
||||||
|
$height = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$detector = new FinfoMimeTypeDetector();
|
||||||
|
$mimeType = $detector->detectMimeTypeFromPath($tmp_file_path);
|
||||||
|
|
||||||
|
// if image, we re-create and optimize it
|
||||||
|
if($is_file_image) {
|
||||||
|
if(in_array($mimeType, $this->permit_mine_types)) {
|
||||||
|
$objHuraImage = new HuraImage();
|
||||||
|
if($objHuraImage->create($tmp_file_path, $this->target_dir . DIRECTORY_SEPARATOR . $clean_file_name)){
|
||||||
|
$return_data = new FileHandleInfo([
|
||||||
|
"file_name" => $clean_file_name,
|
||||||
|
"public_path" => $this->public_dir . "/".$clean_file_name,
|
||||||
|
"local_path" => $this->target_dir . "/" . $clean_file_name,
|
||||||
|
"mime_type" => $mimeType,
|
||||||
|
"file_size" => $file_size,
|
||||||
|
"file_ext" => $file_ext,
|
||||||
|
"width" => $width,
|
||||||
|
"height" => $height,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}elseif(@rename ($tmp_file_path, $this->target_dir . DIRECTORY_SEPARATOR . $clean_file_name )) {
|
||||||
|
$return_data = new FileHandleInfo([
|
||||||
|
"file_name" => $clean_file_name,
|
||||||
|
"public_path" => $this->public_dir . "/".$clean_file_name,
|
||||||
|
"local_path" => $this->target_dir . "/" . $clean_file_name,
|
||||||
|
"mime_type" => $mimeType,
|
||||||
|
"file_size" => $file_size,
|
||||||
|
"file_ext" => $file_ext,
|
||||||
|
"width" => 0,
|
||||||
|
"height" => 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete tmp file on server
|
||||||
|
if(file_exists($original_file_tmp_name)) {
|
||||||
|
@unlink($original_file_tmp_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($return_data) {
|
||||||
|
return new FileHandleResponse(AppResponse::SUCCESS, 'Success', $return_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FileHandleResponse(AppResponse::ERROR, 'Unknown', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function setUp(): AppResponse
|
||||||
|
{
|
||||||
|
// check target dir
|
||||||
|
if($this->target_dir && !is_dir($this->target_dir)) {
|
||||||
|
@mkdir($this->target_dir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!file_exists($this->target_dir)) {
|
||||||
|
return new AppResponse(AppResponse::ERROR, $this->target_dir.' not exists');
|
||||||
|
}
|
||||||
|
|
||||||
|
// create tmp_folder to upload file to
|
||||||
|
$this->tmp_folder = $this->create_tmp_folder();
|
||||||
|
if(!$this->tmp_folder) {
|
||||||
|
return new AppResponse(AppResponse::ERROR, "Check ".$this->tmp_dir." and make sure it exists and writable");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AppResponse(AppResponse::SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function create_tmp_folder() : ?string {
|
||||||
|
$tmp_folder = $this->tmp_dir . IDGenerator::createStringId(5);
|
||||||
|
if(!@mkdir($tmp_folder, 0777, true)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// retest
|
||||||
|
if(!$tmp_folder || !is_dir($tmp_folder)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $tmp_folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
56
inc/Hura8/System/CopyFileFromUrl.php
Normal file
56
inc/Hura8/System/CopyFileFromUrl.php
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System;
|
||||||
|
|
||||||
|
|
||||||
|
use Hura8\Interfaces\AppResponse;
|
||||||
|
use Hura8\Interfaces\FileHandleResponse;
|
||||||
|
use Hura8\System\Controller\bFileHandle;
|
||||||
|
|
||||||
|
|
||||||
|
class CopyFileFromUrl extends bFileHandle
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $file_url
|
||||||
|
* @param int $max_file_size max file size in bytes accepts, default = 1MB
|
||||||
|
* @param string $fixed_file_name set uploaded name to this fixed name (ie. [item_id].jpg) so old file will be replaced
|
||||||
|
* @return FileHandleResponse
|
||||||
|
*/
|
||||||
|
public function start(string $file_url, string $fixed_file_name='', int $max_file_size = 1000000) : FileHandleResponse
|
||||||
|
{
|
||||||
|
|
||||||
|
if(!Url::isUrlValid($file_url)) {
|
||||||
|
return new FileHandleResponse(AppResponse::ERROR, $file_url." is not a valid url");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$this->setup_success) {
|
||||||
|
return new FileHandleResponse(AppResponse::ERROR, "Fail to setup");
|
||||||
|
}
|
||||||
|
|
||||||
|
$original_file_name = substr($file_url, strrpos($file_url, "/"));
|
||||||
|
$original_file_path = $this->tmp_folder . "/". $original_file_name;
|
||||||
|
|
||||||
|
$url_content = Url::getUrlContent($file_url);
|
||||||
|
if(!$url_content || !@file_put_contents($original_file_path, $url_content)) {
|
||||||
|
return new FileHandleResponse(AppResponse::ERROR, "Fail to copy");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if(!@copy($file_url, $original_file_path)) {
|
||||||
|
return new FileHandleResponse(AppResponse::ERROR, "Fail to copy");
|
||||||
|
}*/
|
||||||
|
|
||||||
|
$result = $this->processFile(
|
||||||
|
$original_file_name,
|
||||||
|
$original_file_path,
|
||||||
|
$fixed_file_name,
|
||||||
|
$max_file_size
|
||||||
|
);
|
||||||
|
|
||||||
|
unset($url_content);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
66
inc/Hura8/System/CronProcess.php
Normal file
66
inc/Hura8/System/CronProcess.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System;
|
||||||
|
|
||||||
|
final class CronProcess
|
||||||
|
{
|
||||||
|
private $pid;
|
||||||
|
|
||||||
|
/* @var FileSystem $objFileSystem */
|
||||||
|
private $objFileSystem;
|
||||||
|
|
||||||
|
const LOG_DIR = VAR_DIR . '/log/';
|
||||||
|
private $max_allow_run_time = 1800; // in seconds - max cron run time.
|
||||||
|
|
||||||
|
public function __construct($pid, $max_allow_run_time = 1800) {
|
||||||
|
$this->pid = $pid;
|
||||||
|
$this->objFileSystem = new FileSystem(self::LOG_DIR);
|
||||||
|
$this->max_allow_run_time = $max_allow_run_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPidFile() {
|
||||||
|
return $this->objFileSystem->getFile($this->pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the process is running. True if:
|
||||||
|
* - has pid file
|
||||||
|
* - the file's create-time within the time() and time() - $this->max_allow_run_time
|
||||||
|
*
|
||||||
|
* Else: False and remove pid file if exist
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isRunning() : bool {
|
||||||
|
|
||||||
|
$file_exist = ($this->objFileSystem->getFile($this->pid));
|
||||||
|
if(!$file_exist) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$last_modified_time = $this->objFileSystem->getFileLastModifiedTime($this->pid);
|
||||||
|
|
||||||
|
if($last_modified_time > 0 && $last_modified_time < time() - $this->max_allow_run_time) {
|
||||||
|
|
||||||
|
echo "Pid File exists but too old. Trying to auto-delete now". PHP_EOL;
|
||||||
|
|
||||||
|
if($this->objFileSystem->delete($this->pid)){
|
||||||
|
echo "File is deleted successfully". PHP_EOL;
|
||||||
|
}else{
|
||||||
|
echo "Auto-delete fails. File needs to be removed manually. Path: ". $this->objFileSystem->getFilename($this->pid). PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function start() {
|
||||||
|
return $this->objFileSystem->writeFile($this->pid, time());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function finish() {
|
||||||
|
return $this->objFileSystem->delete($this->pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
251
inc/Hura8/System/Email.php
Normal file
251
inc/Hura8/System/Email.php
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System;
|
||||||
|
|
||||||
|
use Hura8\Components\Template\Controller\TemplateController;
|
||||||
|
use Hura8\Interfaces\iEmail;
|
||||||
|
use Hura8\System\Controller\SettingController;
|
||||||
|
use Provider\Adman;
|
||||||
|
use Swift_Mailer;
|
||||||
|
use Swift_Message;
|
||||||
|
use Swift_SmtpTransport;
|
||||||
|
|
||||||
|
|
||||||
|
class Email implements iEmail {
|
||||||
|
|
||||||
|
protected $config = [
|
||||||
|
"mail_method" => "",
|
||||||
|
"from_name" => "",
|
||||||
|
"from_email" => "",
|
||||||
|
"reply_to" => "",
|
||||||
|
"subject" => "",
|
||||||
|
"content" => "",
|
||||||
|
];
|
||||||
|
|
||||||
|
/* @var SettingController $objSettingController */
|
||||||
|
protected $objSettingController;
|
||||||
|
|
||||||
|
//construct
|
||||||
|
public function __construct() {
|
||||||
|
// default
|
||||||
|
if(defined("MAIL_METHOD")) $this->config["mail_method"] = MAIL_METHOD;
|
||||||
|
if(defined("MAIL_NAME")) $this->config["from_name"] = MAIL_NAME;
|
||||||
|
if(defined("MAIL_ADDRESS")) $this->config["from_email"] = MAIL_ADDRESS;
|
||||||
|
|
||||||
|
if(!$this->config["from_name"]) $this->config["from_name"] = str_replace('www.', '', $_SERVER['HTTP_HOST']);
|
||||||
|
|
||||||
|
$this->objSettingController = new SettingController();
|
||||||
|
|
||||||
|
$email_settings = $this->objSettingController->getList([
|
||||||
|
'email_from_default',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$email_from = (isset($email_settings['email_from_default']) && $email_settings['email_from_default']) ? $email_settings['email_from_default'] : '' ;
|
||||||
|
if($email_from && self::validate_email($email_from) && $email_from != MAIL_ADDRESS) {
|
||||||
|
$this->setUp([
|
||||||
|
"from_email" => $email_from,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUp(array $config)
|
||||||
|
{
|
||||||
|
// over-ride default config
|
||||||
|
if(sizeof($config)) {
|
||||||
|
foreach ($config as $key => $value) {
|
||||||
|
$this->config[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(array $to_emails, $subject, $content, array $key_values = [])
|
||||||
|
{
|
||||||
|
if(!$this->checkConfig()) return [
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => "Config errors: ".\json_encode($this->config),
|
||||||
|
];
|
||||||
|
|
||||||
|
// send email using a template in email/
|
||||||
|
$this->config['subject'] = TemplateController::parse(null, $subject, $key_values);
|
||||||
|
$this->config['content'] = TemplateController::parse(null, $content, $key_values);
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch ($this->config["mail_method"]){
|
||||||
|
case "huraserver";
|
||||||
|
$result = $this->sendEmailFromHura($to_emails);
|
||||||
|
break;
|
||||||
|
case "queue";
|
||||||
|
$result = $this->addQueue($to_emails);
|
||||||
|
break;
|
||||||
|
case "adman";
|
||||||
|
$result = $this->sendFromAdman($to_emails);
|
||||||
|
break;
|
||||||
|
case "smtp";
|
||||||
|
$result = $this->sendFromSmtp($to_emails);
|
||||||
|
break;
|
||||||
|
default;
|
||||||
|
$result = null;
|
||||||
|
}
|
||||||
|
}catch (\Exception $exception) {
|
||||||
|
$result = "Error: ".$exception->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if($result) {
|
||||||
|
$this->log(array_merge($this->config, [
|
||||||
|
'to_emails' => $to_emails,
|
||||||
|
'result' => $result,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function checkConfig() {
|
||||||
|
return (
|
||||||
|
$this->config["mail_method"]
|
||||||
|
&& $this->config["from_name"]
|
||||||
|
&& $this->config["from_email"]
|
||||||
|
&& self::validate_email($this->config["from_email"])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function addQueue(array $to_emails){
|
||||||
|
// todo:
|
||||||
|
/*foreach($to_email_list as $single_email){
|
||||||
|
$this->createQueue(array(
|
||||||
|
"message_id" => 0,
|
||||||
|
"from_email" => $from_mail,
|
||||||
|
"from_name" => $from_name,
|
||||||
|
"reply_to" => $reply_to_email,
|
||||||
|
"to_email" => $single_email,
|
||||||
|
"to_name" => $to_name,
|
||||||
|
"subject" => $title,
|
||||||
|
"content" => $content,
|
||||||
|
"source" => "",
|
||||||
|
));
|
||||||
|
}*/
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function sendFromAdman(array $to_emails){
|
||||||
|
if(!defined("ADMAN_API_KEY") || ADMAN_API_KEY == '') return 'ADMAN_API_KEY not set';
|
||||||
|
if(!defined("ADMAN_USER_DOMAIN") || ADMAN_USER_DOMAIN == '') return 'ADMAN_USER_DOMAIN not set';
|
||||||
|
|
||||||
|
$recipients = array_map(function ($email){
|
||||||
|
return [
|
||||||
|
'to_email' => $email,
|
||||||
|
'to_name' => '',
|
||||||
|
'personal_settings' => array(),
|
||||||
|
];
|
||||||
|
}, $to_emails);
|
||||||
|
|
||||||
|
$payload = array(
|
||||||
|
'message' => array(
|
||||||
|
'from_email' => $this->config['from_email'],
|
||||||
|
'from_name' => $this->config['from_name'],
|
||||||
|
'reply_to_email' => $this->config['reply_to'],
|
||||||
|
'subject' => $this->config['subject'],
|
||||||
|
'settings' => array(),
|
||||||
|
'schedule_go_time_utc' => 0,
|
||||||
|
'content_html' => $this->config['content'],
|
||||||
|
'content_plain' => '',
|
||||||
|
),
|
||||||
|
'recipients' => $recipients
|
||||||
|
);
|
||||||
|
|
||||||
|
return Adman::send(ADMAN_USER_DOMAIN, ADMAN_API_KEY, $payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function sendFromSmtp(array $to_emails){
|
||||||
|
|
||||||
|
if(!SMTP_HOST || !SMTP_USERNAME || !SMTP_PASSWORD) {
|
||||||
|
return 'Smtp credentials not set';
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://swiftmailer.symfony.com/docs/introduction.html
|
||||||
|
// Create the Transport
|
||||||
|
$transport = (new Swift_SmtpTransport(SMTP_HOST, SMTP_PORT, SMTP_SECURE))
|
||||||
|
->setUsername(SMTP_USERNAME)
|
||||||
|
->setPassword(SMTP_PASSWORD)
|
||||||
|
;
|
||||||
|
$transport->setTimeout(10);
|
||||||
|
|
||||||
|
// Create the Mailer using your created Transport
|
||||||
|
$mailer = new Swift_Mailer($transport);
|
||||||
|
|
||||||
|
// Create a message
|
||||||
|
$message = (new Swift_Message())
|
||||||
|
->setSubject($this->config['subject'])
|
||||||
|
->setFrom($this->config['from_email'], $this->config['from_name'])
|
||||||
|
->setTo($to_emails)
|
||||||
|
->setBody($this->config['content'], 'text/html')
|
||||||
|
;
|
||||||
|
|
||||||
|
// Send the message
|
||||||
|
return $mailer->send($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function sendEmailFromHura(array $to_emails){
|
||||||
|
$rebuild_to_emails = [];
|
||||||
|
$max_email_allow = 3;
|
||||||
|
$counter = 0;
|
||||||
|
foreach ($to_emails as $email) {
|
||||||
|
if(self::validate_email($email)) {
|
||||||
|
$counter += 1;
|
||||||
|
if($counter >= $max_email_allow) break;
|
||||||
|
$rebuild_to_emails[] = ['email' => $email, 'name' => ''];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!sizeof($rebuild_to_emails)) {
|
||||||
|
return [
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Error. No valid emails to be sent',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'title' => $this->config['subject'],
|
||||||
|
'content' => $this->config['content'],
|
||||||
|
'to_emails' => $rebuild_to_emails,
|
||||||
|
'from_email' => $this->config['from_email'],
|
||||||
|
'from_name' => $this->config['from_name'],
|
||||||
|
'reply_to_email' => $this->config['reply_to'],
|
||||||
|
'reply_to_name' => '',
|
||||||
|
'client_id' => CLIENT_ID,
|
||||||
|
'domain' => CURRENT_DOMAIN,
|
||||||
|
];
|
||||||
|
|
||||||
|
// protect
|
||||||
|
$msg_hash = sha1(serialize($data).'asdas32');
|
||||||
|
$data['msg_hash'] = $msg_hash;
|
||||||
|
|
||||||
|
$headers = [];
|
||||||
|
|
||||||
|
return curl_post(HURAMAIL_RECEIVE_ENDPOINT, $data, $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createQueue(array $config) {
|
||||||
|
// todo:
|
||||||
|
}
|
||||||
|
|
||||||
|
//email log after sending an email and remove from queue
|
||||||
|
protected function log(array $log_content){
|
||||||
|
/*$db = ConnectDB::getInstance('');
|
||||||
|
$db->insert(
|
||||||
|
'email_log',
|
||||||
|
[
|
||||||
|
'payload' => \json_encode($log_content),
|
||||||
|
'create_time' => CURRENT_TIME,
|
||||||
|
]
|
||||||
|
);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
//validate email
|
||||||
|
public static function validate_email($email){
|
||||||
|
return filter_var($email, FILTER_VALIDATE_EMAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
47
inc/Hura8/System/Export.php
Normal file
47
inc/Hura8/System/Export.php
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
//used to export content to download
|
||||||
|
namespace Hura8\System;
|
||||||
|
|
||||||
|
|
||||||
|
class Export
|
||||||
|
{
|
||||||
|
public function export($content, $file_name, $type)
|
||||||
|
{
|
||||||
|
if($type=='xls' || $type=='xlsx') {
|
||||||
|
$this->setHeaderXLS($file_name);
|
||||||
|
}
|
||||||
|
else if($type=='doc') {
|
||||||
|
$this->setHeaderDoc($file_name);
|
||||||
|
}
|
||||||
|
else if($type=='csv') {
|
||||||
|
$this->setHeaderCSV($file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// method for Excel file
|
||||||
|
protected function setHeaderXLS($file_name)
|
||||||
|
{
|
||||||
|
header("Content-type: application/ms-excel");
|
||||||
|
header("Content-Disposition: attachment; filename=$file_name");
|
||||||
|
header("Pragma: no-cache");
|
||||||
|
header("Expires: 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// method for Doc file
|
||||||
|
protected function setHeaderDoc($file_name)
|
||||||
|
{
|
||||||
|
header("Content-type: application/x-ms-download");
|
||||||
|
header("Content-Disposition: attachment; filename=$file_name");
|
||||||
|
header('Cache-Control: public');
|
||||||
|
}
|
||||||
|
|
||||||
|
// method for CSV file
|
||||||
|
protected function setHeaderCSV($file_name)
|
||||||
|
{
|
||||||
|
header("Content-type: application/csv");
|
||||||
|
header("Content-Disposition: inline; filename=$file_name");
|
||||||
|
}
|
||||||
|
}
|
||||||
158
inc/Hura8/System/ExportExcelUseTemplate.php
Normal file
158
inc/Hura8/System/ExportExcelUseTemplate.php
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace Hura8\System;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
|
||||||
|
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||||
|
|
||||||
|
|
||||||
|
class ExportExcelUseTemplate {
|
||||||
|
|
||||||
|
/* @var $spreadsheet Spreadsheet */
|
||||||
|
private $spreadsheet = null;
|
||||||
|
|
||||||
|
/* @var $activeSheet Worksheet */
|
||||||
|
private $activeSheet = null;
|
||||||
|
|
||||||
|
private $active_sheet_index = 0;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct($tpl_file = '') {
|
||||||
|
if(file_exists($tpl_file)) {
|
||||||
|
$this->_createActiveSheet($tpl_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected function _createActiveSheet($tpl_file) {
|
||||||
|
$this->spreadsheet = IOFactory::load($tpl_file);
|
||||||
|
|
||||||
|
$this->spreadsheet->setActiveSheetIndex($this->active_sheet_index);
|
||||||
|
|
||||||
|
$this->activeSheet = $this->spreadsheet->getActiveSheet();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function setRowHeight($row_number, $height_pt) {
|
||||||
|
$this->activeSheet->getRowDimension($row_number)->setRowHeight($height_pt, 'pt');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function removeRow($row_id) {
|
||||||
|
if(!$this->activeSheet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->activeSheet->removeRow($row_id);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a cell value.
|
||||||
|
*
|
||||||
|
* @param string $cell Coordinate of the cell, eg: 'A1'
|
||||||
|
* @param mixed $data Value of the cell
|
||||||
|
* @param string $pDataType Explicit data type, see DataType::TYPE_*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function writeToCell($cell, $data, $pDataType) {
|
||||||
|
if(!$this->activeSheet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->activeSheet->setCellValueExplicit($cell, $data, $pDataType);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $start_row
|
||||||
|
* @param array $row_list array( ['field-1' => value, 'field-2' => value, 'field-3' => value], ['field-1' => value, 'field-2' => value,],)
|
||||||
|
* @param array $map_column_fields map excel column to row field . Example: array('A' => 'name', 'B' => 'price', ...)
|
||||||
|
* @param array $number_fields list of data fields that their values will be write to cell as number. Example ['price', 'qty', ]
|
||||||
|
* @param array $hyperlink_fields list of data fields that their values will be added hyperlink
|
||||||
|
* @return false|int|mixed|string
|
||||||
|
*/
|
||||||
|
public function writeRowList($start_row = 1, array $row_list =[], array $map_column_fields = [], array $number_fields = [], array $hyperlink_fields = []) {
|
||||||
|
|
||||||
|
if(!$this->activeSheet) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new rows to hold new data
|
||||||
|
for($i = 0; $i< sizeof($row_list); $i++) {
|
||||||
|
$this->activeSheet->insertNewRowBefore($start_row + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$last_insert_row_index = 0;
|
||||||
|
|
||||||
|
foreach ($row_list as $index => $item) {
|
||||||
|
|
||||||
|
$last_insert_row_index = $start_row + $index;
|
||||||
|
|
||||||
|
foreach ($map_column_fields as $column => $field) {
|
||||||
|
|
||||||
|
$cell_cor = $column . $last_insert_row_index;
|
||||||
|
|
||||||
|
if(array_key_exists($field, $number_fields)) {
|
||||||
|
$this->activeSheet->setCellValueExplicit($cell_cor, $item[$field], DataType::TYPE_NUMERIC);
|
||||||
|
}else{
|
||||||
|
$this->activeSheet->setCellValueExplicit($cell_cor, $item[$field], DataType::TYPE_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set hyperlink
|
||||||
|
if(array_key_exists($field, $hyperlink_fields)) {
|
||||||
|
|
||||||
|
// link can be a field of the item or a fixed value
|
||||||
|
$hyperlink_url = (isset($item[$hyperlink_fields[$field]])) ? $item[$hyperlink_fields[$field]] : $hyperlink_fields[$field];
|
||||||
|
|
||||||
|
$this->activeSheet->setHyperlink($cell_cor, new Hyperlink($hyperlink_url, 'Mở link để xem tại website'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// format number
|
||||||
|
if(array_key_exists($field, $number_fields)) {
|
||||||
|
|
||||||
|
// check format type
|
||||||
|
if($number_fields[$field]) {
|
||||||
|
$this->activeSheet->getStyle($cell_cor)->getNumberFormat()->setFormatCode($number_fields[$field]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}else{
|
||||||
|
$this->activeSheet->getStyle($cell_cor)->getAlignment()->setWrapText(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $last_insert_row_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function exportToBrowser($file_name='export') {
|
||||||
|
if(!$this->spreadsheet) {
|
||||||
|
die("No file");
|
||||||
|
}
|
||||||
|
|
||||||
|
// export
|
||||||
|
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||||
|
header('Content-Disposition: attachment;filename="'.$file_name.'-'.time().'.xlsx"');
|
||||||
|
header('Cache-Control: max-age=0');
|
||||||
|
|
||||||
|
$writer = new Xlsx($this->spreadsheet);
|
||||||
|
ob_end_clean();
|
||||||
|
$writer->save('php://output');
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
$this->spreadsheet->disconnectWorksheets();
|
||||||
|
$this->spreadsheet = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
212
inc/Hura8/System/FileSystem.php
Normal file
212
inc/Hura8/System/FileSystem.php
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System;
|
||||||
|
|
||||||
|
class FileSystem
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The cache directory.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $directory;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $umask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $directory The cache directory.
|
||||||
|
* @param int $umask
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function __construct($directory, $umask = 0002)
|
||||||
|
{
|
||||||
|
// YES, this needs to be *before* createPathIfNeeded()
|
||||||
|
if (! is_int($umask)) {
|
||||||
|
throw new \Exception(sprintf(
|
||||||
|
'The umask parameter is required to be integer, was: %s',
|
||||||
|
gettype($umask)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->umask = $umask;
|
||||||
|
|
||||||
|
if (! $this->createPathIfNeeded($directory)) {
|
||||||
|
throw new \Exception(sprintf(
|
||||||
|
'The directory "%s" does not exist and could not be created.',
|
||||||
|
$directory
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! is_writable($directory)) {
|
||||||
|
throw new \Exception(sprintf(
|
||||||
|
'The directory "%s" is not writable.',
|
||||||
|
$directory
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// YES, this needs to be *after* createPathIfNeeded()
|
||||||
|
$this->directory = realpath($directory);
|
||||||
|
|
||||||
|
//$this->directoryStringLength = strlen($this->directory);
|
||||||
|
//$this->isRunningOnWindows = defined('PHP_WINDOWS_VERSION_BUILD');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description extract a zip file after it has been uploaded to the server
|
||||||
|
* @param string $filename name of the zip file (abc.zip)
|
||||||
|
* @param string $uploaded_zip_path path where it's uploaded to i.e /usr/.../tmp_upload/
|
||||||
|
* @param string $extract_target_path path where the zip files will be extracted to , if not provided the $uploaded_zip_path will be used
|
||||||
|
* @return array [extracted_folder, test_folder_exist]
|
||||||
|
*/
|
||||||
|
public static function unzip(string $filename, string $uploaded_zip_path, string $extract_target_path = '') {
|
||||||
|
$extract_path = $extract_target_path ?: $uploaded_zip_path;
|
||||||
|
|
||||||
|
$zip = new \ZipArchive();
|
||||||
|
$x = $zip->open($uploaded_zip_path . DIRECTORY_SEPARATOR . $filename);
|
||||||
|
if ($x === true) {
|
||||||
|
$zip->extractTo($extract_path); // change this to the correct site path
|
||||||
|
$zip->close();
|
||||||
|
}
|
||||||
|
@unlink($uploaded_zip_path . DIRECTORY_SEPARATOR . $filename);
|
||||||
|
|
||||||
|
$expected_result = $extract_path . DIRECTORY_SEPARATOR . str_replace(".zip", "", $filename);
|
||||||
|
|
||||||
|
return array($expected_result, file_exists($expected_result));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description scan a folder recursively and return all files with sub-path
|
||||||
|
* @param string $folder full path /usr/local/.../
|
||||||
|
* @param array $file_list
|
||||||
|
*/
|
||||||
|
public static function scanDirRecursive(string $folder, array &$file_list = []) {
|
||||||
|
$dir = opendir($folder);
|
||||||
|
while(( $file = readdir($dir)) ) {
|
||||||
|
if (( $file != '.' ) && ( $file != '..' )) {
|
||||||
|
if ( is_dir($folder. '/' . $file) ) {
|
||||||
|
FileSystem::scanDirRecursive($folder. '/' . $file, $file_list);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//$file_list[] = str_replace(PUBLIC_DIR . "/", "", $folder .'/'. $file);
|
||||||
|
$file_list[] = $folder .'/'. $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir($dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
//remove directory recursively
|
||||||
|
public static function removeDir($dir) {
|
||||||
|
if (is_dir($dir)) {
|
||||||
|
$objects = scandir($dir);
|
||||||
|
foreach ($objects as $object) {
|
||||||
|
if ($object != "." && $object != "..") {
|
||||||
|
if (is_dir($dir."/".$object)) {
|
||||||
|
self::removeDir($dir."/".$object);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
@unlink($dir."/".$object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@rmdir($dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFile(string $filename)
|
||||||
|
{
|
||||||
|
$full_filepath = $this->getFilename($filename);
|
||||||
|
if(file_exists($full_filepath)) {
|
||||||
|
return file_get_contents($full_filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFileLastModifiedTime(string $filename)
|
||||||
|
{
|
||||||
|
$full_filepath = $this->getFilename($filename);
|
||||||
|
if(file_exists($full_filepath)) {
|
||||||
|
return filemtime($full_filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a string content to file in an atomic way.
|
||||||
|
*
|
||||||
|
* @param string $filename Path to the file where to write the data.
|
||||||
|
* @param string $content The content to write
|
||||||
|
*
|
||||||
|
* @return bool TRUE on success, FALSE if path cannot be created, if path is not writable or an any other error.
|
||||||
|
*/
|
||||||
|
public function writeFile(string $filename, string $content)
|
||||||
|
{
|
||||||
|
$full_filepath = $this->getFilename($filename);
|
||||||
|
$filepath = pathinfo($full_filepath, PATHINFO_DIRNAME);
|
||||||
|
|
||||||
|
if (! $this->createPathIfNeeded($filepath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! is_writable($filepath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmpFile = tempnam($filepath, 'swap');
|
||||||
|
@chmod($tmpFile, 0666 & (~$this->umask));
|
||||||
|
|
||||||
|
if (file_put_contents($tmpFile, $content) !== false) {
|
||||||
|
|
||||||
|
@chmod($tmpFile, 0666 & (~$this->umask));
|
||||||
|
if (@rename($tmpFile, $full_filepath)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@unlink($tmpFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($filename)
|
||||||
|
{
|
||||||
|
$full_filepath = $this->getFilename($filename);
|
||||||
|
|
||||||
|
return @unlink($full_filepath) || ! file_exists($full_filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $filename
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getFilename($filename)
|
||||||
|
{
|
||||||
|
return $this->directory
|
||||||
|
. DIRECTORY_SEPARATOR
|
||||||
|
. $filename ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create path if needed.
|
||||||
|
*
|
||||||
|
* @return bool TRUE on success or if path already exists, FALSE if path cannot be created.
|
||||||
|
*/
|
||||||
|
private function createPathIfNeeded(string $path)
|
||||||
|
{
|
||||||
|
if (! is_dir($path)) {
|
||||||
|
if (@mkdir($path, 0755 & (~$this->umask), true) === false && ! is_dir($path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
110
inc/Hura8/System/FileUpload.php
Normal file
110
inc/Hura8/System/FileUpload.php
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Hura8\System;
|
||||||
|
|
||||||
|
use Hura8\Interfaces\AppResponse;
|
||||||
|
use Hura8\Interfaces\FileHandleResponse;
|
||||||
|
use Hura8\System\Controller\bFileHandle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @date 25-Oct-2023
|
||||||
|
* Single upload handle for whole application
|
||||||
|
* - Manage security
|
||||||
|
* - upload file to desired destination
|
||||||
|
* - return the file path for other modules to use
|
||||||
|
*
|
||||||
|
* Security:
|
||||||
|
* - restrict file extension and mine type
|
||||||
|
*
|
||||||
|
* Sample code:
|
||||||
|
|
||||||
|
$objFileUpload = new FileUpload(
|
||||||
|
"media/banner", //.date("Y-m-d"),
|
||||||
|
array(".jpg",".jpeg",".gif", ".png", ".webp", '.avif')
|
||||||
|
);
|
||||||
|
$upload_result = $objFileUpload->handleUpload("file");
|
||||||
|
|
||||||
|
if($upload_result->getStatus() == 'ok') {
|
||||||
|
$file_info = $upload_result->getData();
|
||||||
|
|
||||||
|
$file_name = $file_info->file_name;
|
||||||
|
$file_ext = $file_info->file_ext;
|
||||||
|
$public_path = $file_info->public_path;
|
||||||
|
|
||||||
|
$renamed = $objFileUpload->renameUploadedFile($file_name, $item_id . $file_ext);
|
||||||
|
|
||||||
|
if($renamed) {
|
||||||
|
$new_file_name = $renamed['file_name'];
|
||||||
|
$new_public_path = $renamed['public_path'];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class FileUpload extends bFileHandle
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description version of handleUpload to support multiple files using array input
|
||||||
|
* example: <input type="file" size="30" name="multiple_file[]" /> instead of <input type="file" size="30" name="single_file" />
|
||||||
|
*
|
||||||
|
* @param string $input_file_name name of <input type="file" size="30" name="multiple_file[]" /> => use: multiple_file
|
||||||
|
* @param int $max_file_size max file size in bytes accepts, default = 1MB
|
||||||
|
* @return FileHandleResponse[]
|
||||||
|
*/
|
||||||
|
public function handleMultipleUpload(string $input_file_name, int $max_file_size = 1000000) : array
|
||||||
|
{
|
||||||
|
$upload_result = [];
|
||||||
|
for($i = 0; $i < sizeof($_FILES[$input_file_name]['name']); $i++){
|
||||||
|
$original_uploaded_file_name = $_FILES[$input_file_name]["name"][$i];
|
||||||
|
$original_uploaded_data = $_FILES[$input_file_name]["tmp_name"][$i];
|
||||||
|
|
||||||
|
$upload_result[] = $this->processFile(
|
||||||
|
$original_uploaded_file_name,
|
||||||
|
$original_uploaded_data,
|
||||||
|
'',
|
||||||
|
$max_file_size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
$this->cleanUp();
|
||||||
|
|
||||||
|
return $upload_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $input_file_name name of <input type="file" size="30" name="upload_file" /> (which is upload_file)
|
||||||
|
* @param int $max_file_size max file size in bytes accepts, default = 1MB
|
||||||
|
* @param string $fixed_file_name set uploaded name to this fixed name (ie. [item_id].jpg) so old file will be replaced
|
||||||
|
* @return FileHandleResponse
|
||||||
|
*/
|
||||||
|
public function handleUpload(string $input_file_name, string $fixed_file_name='', int $max_file_size = 1000000) : FileHandleResponse
|
||||||
|
{
|
||||||
|
if(!$this->setup_success) {
|
||||||
|
return new FileHandleResponse(AppResponse::ERROR, "Fail to setup");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$input_file_name || !isset($_FILES[$input_file_name])) {
|
||||||
|
return new FileHandleResponse('error', 'no file', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
$original_uploaded_file_name = $_FILES[$input_file_name]["name"] ?? "";
|
||||||
|
$original_uploaded_data = $_FILES[$input_file_name]["tmp_name"];
|
||||||
|
|
||||||
|
$upload_result = $this->processFile(
|
||||||
|
$original_uploaded_file_name,
|
||||||
|
$original_uploaded_data,
|
||||||
|
$fixed_file_name,
|
||||||
|
$max_file_size
|
||||||
|
);
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
$this->cleanUp();
|
||||||
|
|
||||||
|
return $upload_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
190
inc/Hura8/System/Firewall.php
Normal file
190
inc/Hura8/System/Firewall.php
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Simple firewall to protect DDOS attack
|
||||||
|
// It's better to use
|
||||||
|
// https://github.com/terrylinooo/shieldon
|
||||||
|
// https://github.com/antonioribeiro/firewall
|
||||||
|
// https://packagist.org/packages/middlewares/firewall
|
||||||
|
|
||||||
|
|
||||||
|
namespace Hura8\System;
|
||||||
|
|
||||||
|
|
||||||
|
use Jaybizzle\CrawlerDetect\CrawlerDetect;
|
||||||
|
|
||||||
|
class Firewall
|
||||||
|
{
|
||||||
|
|
||||||
|
const MAX_CONCURRENT_VISIT_ALLOW = 5;
|
||||||
|
const WHITE_LIST_IP = ROOT_DIR . "/config/system/white_list_ip.txt";
|
||||||
|
const BAN_IP_LIST = ROOT_DIR . "/config/build/ban_ip_list.txt";
|
||||||
|
const TEMPORARY_BAN_DURATION = 60; //seconds
|
||||||
|
|
||||||
|
/* @var $instance Firewall */
|
||||||
|
protected static $instance;
|
||||||
|
protected $log_dir = ROOT_DIR . '/cache/firewall/log/';
|
||||||
|
protected $ban_dir = ROOT_DIR . '/cache/firewall/ban/';
|
||||||
|
private $user_ip = '';
|
||||||
|
protected $white_list = [
|
||||||
|
// google bot ips
|
||||||
|
// bing bots
|
||||||
|
// others..
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function __construct() {
|
||||||
|
// todo: use Redis for performance instead of directory
|
||||||
|
/*$this->log_dir = ROOT_DIR . '/cache/firewall/log/';
|
||||||
|
$this->ban_dir = ROOT_DIR . '/cache/firewall/ban/';
|
||||||
|
if(!file_exists($this->log_dir)) {
|
||||||
|
mkdir($this->log_dir, 0755, true);
|
||||||
|
}
|
||||||
|
if(!file_exists($this->ban_dir)) {
|
||||||
|
mkdir($this->ban_dir, 0755, true);
|
||||||
|
}*/
|
||||||
|
$this->user_ip = getIpAddress();
|
||||||
|
|
||||||
|
if(file_exists(self::WHITE_LIST_IP)) {
|
||||||
|
$this->white_list = array_filter(explode("\n", file_get_contents(self::WHITE_LIST_IP)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static function getInstance() {
|
||||||
|
if(!static::$instance) {
|
||||||
|
static::$instance = new self();
|
||||||
|
}
|
||||||
|
|
||||||
|
return static::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if current user agent is an crawler
|
||||||
|
public function isCrawler() : bool {
|
||||||
|
$objCrawlerDetect = new CrawlerDetect();
|
||||||
|
//echo $objCrawlerDetect->getUserAgent();
|
||||||
|
// https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers
|
||||||
|
return $objCrawlerDetect->isCrawler();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function monitor() {
|
||||||
|
// todo:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$total_concurrent_visit = $this->logIp($this->user_ip);
|
||||||
|
if($total_concurrent_visit > self::MAX_CONCURRENT_VISIT_ALLOW) {
|
||||||
|
// ban if not in whitelist
|
||||||
|
if(!$this->isWhiteListed()) {
|
||||||
|
$this->banTemporary($this->user_ip, self::TEMPORARY_BAN_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
die("System overload");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stopIfBanned() {
|
||||||
|
if($this->isIPBanned()) {
|
||||||
|
die("Perhaps, this website is not for you.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isWhiteListed() {
|
||||||
|
$filter = new IPFilter($this->white_list);
|
||||||
|
return $filter->check($this->user_ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isIPBanned() {
|
||||||
|
|
||||||
|
// check whitelist
|
||||||
|
if($this->isWhiteListed()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check temporary ban
|
||||||
|
/*$file_id = $this->ban_dir . $this->user_ip.'.data';
|
||||||
|
if(file_exists($file_id)) {
|
||||||
|
$lift_time = file_get_contents($file_id);
|
||||||
|
// lift_time is not over yet
|
||||||
|
if($lift_time > time()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// permanent ban
|
||||||
|
$config_file = static::BAN_IP_LIST;
|
||||||
|
if(@file_exists( $config_file )) {
|
||||||
|
$list_ips = @file_get_contents( $config_file );
|
||||||
|
$list_ip_arr = array_filter(explode("\n", $list_ips));
|
||||||
|
$filter = new IPFilter($list_ip_arr);
|
||||||
|
|
||||||
|
return $filter->check($this->user_ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ban an IP temporarily
|
||||||
|
protected function banTemporary($ip, $time) {
|
||||||
|
$file_id = $this->ban_dir . $ip.'.data';
|
||||||
|
@file_put_contents($file_id, time() + $time);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// only allow 1 of these formats:
|
||||||
|
// - Full IP: 127.0.0.1
|
||||||
|
// - Wildcard: 172.0.0.*
|
||||||
|
// - Mask: 125.0.0.1/24
|
||||||
|
// - Range: 125.0.0.1-125.0.0.9
|
||||||
|
public static function validateIP($ip) {
|
||||||
|
// - Full IP: 127.0.0.1
|
||||||
|
if (filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - Wildcard: 172.0.0.*
|
||||||
|
if (strpos($ip, "*") !== false && preg_match("/^([0-9]+)\.([0-9]+)\.(([0-9]+)|\*)\.(([0-9]+)|\*)$/i", $ip)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - Mask: 125.0.0.1/24
|
||||||
|
if (strpos($ip, "/") !== false && preg_match("/^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)\/([0-9]+)$/i", $ip)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - Range: 125.0.0.1-125.0.0.9
|
||||||
|
if (strpos($ip, "-") !== false ) {
|
||||||
|
list($begin, $end) = explode('-', $ip);
|
||||||
|
return filter_var($begin, FILTER_VALIDATE_IP) && filter_var($end, FILTER_VALIDATE_IP);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function logIp($ip) {
|
||||||
|
$file_id = $this->log_dir . $ip.'.data';
|
||||||
|
$time_track = CURRENT_TIME - CURRENT_TIME % 5; // track within 5 seconds,
|
||||||
|
$data = [];
|
||||||
|
if(file_exists($file_id)) {
|
||||||
|
$data = \json_decode(file_get_contents($file_id), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isset($data[$time_track])) {
|
||||||
|
$data[$time_track] = $data[$time_track] + 1;
|
||||||
|
}else{
|
||||||
|
$data[$time_track] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only log for current time to prevent large $data's size
|
||||||
|
// our purpose is only to detect if this IP is brutally forceing the system (too many visits per time)
|
||||||
|
|
||||||
|
@file_put_contents($file_id, \json_encode($data));
|
||||||
|
|
||||||
|
return $data[$time_track];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user