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

View 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();
}
}

View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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] : [];
}
}

View 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;
}
}

View 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);
}
}

View 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[] = " &gt;&gt; ";
}
}
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;
}
}

View 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());
}
}
}

View 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;
});
}
}

View 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();
}
}

View 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);
}
}

View 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;
});
}
}

View 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;
}
}