c
This commit is contained in:
297
inc/Hura8/System/Model/AuthModel.php
Normal file
297
inc/Hura8/System/Model/AuthModel.php
Normal file
@@ -0,0 +1,297 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Model;
|
||||
|
||||
|
||||
use Hura8\Database\iConnectDB;
|
||||
use Hura8\System\IDGenerator;
|
||||
|
||||
class AuthModel
|
||||
{
|
||||
|
||||
/* @var iConnectDB $db */
|
||||
protected $db;
|
||||
|
||||
private $tb_login = '';
|
||||
private $tb_access_code = '';
|
||||
|
||||
private $tb_onetime_key = '';
|
||||
|
||||
public function __construct($tb_login, $tb_access_code)
|
||||
{
|
||||
$this->tb_login = $tb_login;
|
||||
$this->tb_access_code = $tb_access_code;
|
||||
$this->db = get_db('', ENABLE_DB_DEBUG);
|
||||
}
|
||||
|
||||
|
||||
private const ACCESS_CODE_LENGTH = 30;
|
||||
const ONE_TIME_KEY_LENGTH = 15;
|
||||
|
||||
|
||||
public function checkOneTimeKey($auth_key) {
|
||||
$db_response = $this->db->select(
|
||||
$this->tb_onetime_key,
|
||||
['user_id', 'user_name', 'client_id', 'create_time'],
|
||||
[
|
||||
'auth_key' => ["=", $auth_key],
|
||||
],
|
||||
'',
|
||||
1
|
||||
);
|
||||
|
||||
if($db_response->getCode()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$info = $db_response->getData();
|
||||
|
||||
if($info) {
|
||||
|
||||
// used ONCE and delete the key
|
||||
$this->db->runQuery("DELETE FROM `".$this->tb_onetime_key."` WHERE `user_id` = ? ", ['s'], [$info['user_id']]);
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// auth key allows users to export data (i.e. export to excel) for offline use
|
||||
public function createNewOneTimeKey($user_id) {
|
||||
|
||||
// and make a new one
|
||||
$auth_key = IDGenerator::createStringId(self::ONE_TIME_KEY_LENGTH);
|
||||
|
||||
$this->db->insert($this->tb_onetime_key, [
|
||||
'user_id' => $user_id,
|
||||
'auth_key' => $auth_key,
|
||||
'create_time' => CURRENT_TIME,
|
||||
]);
|
||||
|
||||
return $auth_key;
|
||||
}
|
||||
|
||||
|
||||
// for all subsequent requests, API need to provide access-code in the request's header
|
||||
// the server will verify the code
|
||||
public function checkAccessCode($access_code) {
|
||||
return $this->db->select(
|
||||
$this->tb_access_code,
|
||||
['user_id', 'create_time'],
|
||||
[
|
||||
'access_code' => ["=", $access_code],
|
||||
],
|
||||
'',
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function deleteAllAccessCode($user_id) {
|
||||
$this->db->runQuery("DELETE FROM `".$this->tb_access_code."` WHERE `user_id` = ? ", ['s'], [$user_id]);
|
||||
}
|
||||
|
||||
|
||||
protected function createNewAccessCode($user_id) {
|
||||
|
||||
// when use login here, delete all other access code
|
||||
$this->deleteAllAccessCode($user_id);
|
||||
|
||||
// and make a new one
|
||||
$access_code = IDGenerator::createStringId(self::ACCESS_CODE_LENGTH);
|
||||
$user_device = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
|
||||
|
||||
$db_response = $this->db->insert($this->tb_access_code, [
|
||||
'user_id' => $user_id,
|
||||
'access_code' => $access_code,
|
||||
'user_device' => substr($user_device, 0, 150),
|
||||
'create_time' => CURRENT_TIME,
|
||||
]);
|
||||
|
||||
return $access_code;
|
||||
}
|
||||
|
||||
/**
|
||||
* allow to login via mobile
|
||||
* - step 1: sms an OTP code to mobile number
|
||||
* - step 2: verify OTP code (mobile number and otp code sent along in the form)
|
||||
*/
|
||||
|
||||
public function checkLoginViaMobile($mobile, $otp) {
|
||||
// todo
|
||||
}
|
||||
|
||||
/**
|
||||
* @description An OTP code is sent to user's email. This method helps user need not remember the password
|
||||
* @param $email string
|
||||
* @param $otp string
|
||||
*/
|
||||
|
||||
public function checkLoginByOTP($user_id, $otp) {
|
||||
$info = $this->db->select(
|
||||
$this->tb_login,
|
||||
[],
|
||||
[
|
||||
'user_id' => ["=", $user_id],
|
||||
'login_otp' => ["=", $otp],
|
||||
],
|
||||
'',
|
||||
1
|
||||
);
|
||||
|
||||
if($info) {
|
||||
// return to browser
|
||||
return array(
|
||||
'user_id' => $user_id,
|
||||
'access_code' => $this->createNewAccessCode($user_id),
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function createLoginOTP($user_id) {
|
||||
// check email exist
|
||||
$info = $this->db->select(
|
||||
$this->tb_login,
|
||||
[],
|
||||
[
|
||||
'user_id' => ["=", $user_id],
|
||||
],
|
||||
'',
|
||||
1
|
||||
);
|
||||
|
||||
if($info) {
|
||||
$otp = IDGenerator::createStringId(6);
|
||||
$this->db->update(
|
||||
$this->tb_login,
|
||||
[
|
||||
'login_otp' => $otp
|
||||
],
|
||||
[
|
||||
'user_id' => $info['user_id'],
|
||||
]
|
||||
);
|
||||
|
||||
return $otp;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function createOrUpdatePassword($user_id, $new_password) {
|
||||
$query = $this->db->runQuery(
|
||||
"SELECT `user_id` FROM `".$this->tb_login."` WHERE `user_id` = ? LIMIT 1 ",
|
||||
['d'], [ $user_id ]
|
||||
);
|
||||
|
||||
if($this->db->fetchAssoc($query)) {
|
||||
return $this->updatePassword($user_id, $new_password);
|
||||
}
|
||||
|
||||
return $this->createPassword($user_id, $new_password);
|
||||
}
|
||||
|
||||
|
||||
protected function createPassword($user_id, $new_password) {
|
||||
return $this->db->insert(
|
||||
$this->tb_login,
|
||||
[
|
||||
'user_id' => $user_id,
|
||||
'password_hash' => $this->hashPassword($new_password),
|
||||
'create_time' => CURRENT_TIME,
|
||||
'create_by' => '',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
protected function updatePassword($user_id, $new_password) {
|
||||
return $this->db->update(
|
||||
$this->tb_login,
|
||||
[
|
||||
'password_hash' => $this->hashPassword($new_password),
|
||||
'last_update' => CURRENT_TIME,
|
||||
'last_update_by' => ADMIN_NAME,
|
||||
],
|
||||
[
|
||||
'user_id' => $user_id,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $user_id int
|
||||
* @param $password string
|
||||
* @return array|false
|
||||
*/
|
||||
public function checkLogin($user_id, $password) {
|
||||
|
||||
$info = $this->db->select(
|
||||
$this->tb_login,
|
||||
[ 'password_hash'],
|
||||
[
|
||||
'user_id' => ["=", $user_id],
|
||||
],
|
||||
'',
|
||||
1
|
||||
);
|
||||
|
||||
//test password
|
||||
if($info && $this->verifyHash($password, $info['password_hash'])) {
|
||||
|
||||
$this->updateUserLogin($user_id);
|
||||
|
||||
// return to browser
|
||||
return array(
|
||||
'user_id' => $user_id,
|
||||
'access_code' => $this->createNewAccessCode($user_id),
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private function updateUserLogin($user_id){
|
||||
return $this->db->update(
|
||||
$this->tb_login,
|
||||
[
|
||||
'last_login_time' => CURRENT_TIME,
|
||||
'last_login_ip' => USER_IP,
|
||||
'last_login_device' => '',
|
||||
'last_login_session_id' => \Hura8\System\Security\Session::id() ?: '',
|
||||
'last_login_browser' => USER_AGENT,
|
||||
],
|
||||
[
|
||||
'user_id' => $user_id,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $str string
|
||||
* @return string
|
||||
*/
|
||||
private function hashPassword($str) {
|
||||
return password_hash($str, PASSWORD_BCRYPT, array('cost' => 12 ));
|
||||
}
|
||||
|
||||
/**
|
||||
* 15-04-2016 verify string with given hash
|
||||
* @param $str_to_verify string
|
||||
* @param $hash string
|
||||
* @return boolean
|
||||
*/
|
||||
private function verifyHash($str_to_verify, $hash) {
|
||||
return (password_verify($str_to_verify, $hash));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
153
inc/Hura8/System/Model/DomainModel.php
Normal file
153
inc/Hura8/System/Model/DomainModel.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Model;
|
||||
|
||||
use Hura8\Interfaces\AppResponse;
|
||||
use Hura8\Interfaces\EntityType;
|
||||
|
||||
class DomainModel extends aEntityBaseModel
|
||||
{
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(EntityType::DOMAIN);
|
||||
}
|
||||
|
||||
protected function extendedFilterOptions(): array
|
||||
{
|
||||
return [
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function addNewDomain($clean_domain, $language = DEFAULT_LANGUAGE){
|
||||
return $this->create([
|
||||
'domain' => $clean_domain,
|
||||
'lang' => $language,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
protected function getInfoByDomain($clean_domain){
|
||||
$query = $this->db->runQuery(
|
||||
"SELECT * from ".$this->tb_entity." WHERE `domain` = ? LIMIT 1 ",
|
||||
['s'], [ $clean_domain ]
|
||||
);
|
||||
|
||||
return $this->db->fetchAssoc($query);
|
||||
}
|
||||
|
||||
|
||||
public function deleteDomain($clean_domain){
|
||||
$domain_info = $this->getInfoByDomain($clean_domain);
|
||||
if($domain_info) {
|
||||
$this->delete($domain_info['id']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function setDomainMain($domain, $language){
|
||||
|
||||
$this->db->update(
|
||||
$this->tb_entity,
|
||||
[ "is_main" => 0, ],
|
||||
[
|
||||
"lang" => $language,
|
||||
"is_main" => 1
|
||||
]
|
||||
);
|
||||
|
||||
$this->db->update(
|
||||
$this->tb_entity,
|
||||
[
|
||||
"is_main" => 1,
|
||||
"last_update" => CURRENT_TIME,
|
||||
"last_update_by" => ADMIN_NAME,
|
||||
],
|
||||
[
|
||||
"domain" => $domain,
|
||||
]
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function setDomainLayout($domain, $layout = 'pc'){
|
||||
$this->db->update(
|
||||
$this->tb_entity,
|
||||
[ "layout" => $layout, ],
|
||||
[ "domain" => $domain, ]
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected function _buildQueryConditionExtend(array $condition) : ?array
|
||||
{
|
||||
$where_condition = "";
|
||||
$bind_types = [];
|
||||
$bind_values = [];
|
||||
|
||||
if(isset($condition['language']) && $condition['language']) {
|
||||
$where_condition = " AND `lang` = ? ";
|
||||
$bind_types[] = 's';
|
||||
$bind_values[] = $condition['language'];
|
||||
}
|
||||
|
||||
return [
|
||||
$where_condition,
|
||||
$bind_types,
|
||||
$bind_values,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
protected function beforeCreateItem(array $input_info) : AppResponse
|
||||
{
|
||||
$info = $input_info;
|
||||
|
||||
// if domain exist
|
||||
if($this->getInfoByDomain($info['domain'])) {
|
||||
return new AppResponse('error', "Domain exist");
|
||||
}
|
||||
|
||||
$info['create_time'] = CURRENT_TIME;
|
||||
$info['create_by'] = ADMIN_NAME;
|
||||
|
||||
return new AppResponse('ok', null, $info);
|
||||
}
|
||||
|
||||
protected function afterCreateItem($new_item_id, $new_item_info)
|
||||
{
|
||||
// TODO: Implement afterCreateItem() method.
|
||||
}
|
||||
|
||||
protected function beforeUpdateItem($item_id, $current_item_info, $new_input_info) : AppResponse
|
||||
{
|
||||
$info = $new_input_info;
|
||||
|
||||
$info['last_update'] = CURRENT_TIME;
|
||||
$info['last_update_by'] = ADMIN_NAME;
|
||||
|
||||
return new AppResponse('ok', null, $info);
|
||||
}
|
||||
|
||||
protected function afterUpdateItem($item_id, $old_item_info, $new_item_info)
|
||||
{
|
||||
// TODO: Implement afterUpdateItem() method.
|
||||
}
|
||||
|
||||
protected function beforeDeleteItem($item_id, $item_info) : AppResponse
|
||||
{
|
||||
return new AppResponse('ok');
|
||||
}
|
||||
|
||||
protected function afterDeleteItem($item_id, $item_info)
|
||||
{
|
||||
// TODO: Implement afterDeleteItem() method.
|
||||
}
|
||||
}
|
||||
456
inc/Hura8/System/Model/EntityLanguageModel.php
Normal file
456
inc/Hura8/System/Model/EntityLanguageModel.php
Normal file
@@ -0,0 +1,456 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Model;
|
||||
|
||||
use Hura8\Database\iConnectDB;
|
||||
use Hura8\Interfaces\AppResponse;
|
||||
use Hura8\Interfaces\EntityType;
|
||||
use Hura8\Interfaces\iEntityLanguageModel;
|
||||
use Hura8\Interfaces\iEntityModel;
|
||||
use Hura8\System\Config;
|
||||
use Hura8\System\Controller\UrlManagerController;
|
||||
use Hura8\System\ModuleManager;
|
||||
|
||||
abstract class EntityLanguageModel implements iEntityLanguageModel
|
||||
{
|
||||
|
||||
/* @var iConnectDB $db */
|
||||
protected $db;
|
||||
|
||||
protected $entity_type = '';
|
||||
protected $tb_entity = "";
|
||||
|
||||
protected $tb_entity_lang_template = "tb_entity_lang_template";
|
||||
protected $tb_track_entity_lang = "tb_track_entity_lang";
|
||||
|
||||
// these fields are set in config file at: config/client/language_fields.php
|
||||
protected $language_fields = [
|
||||
//"title",
|
||||
//"summary",
|
||||
];
|
||||
|
||||
protected $tb_entity_lang = "";
|
||||
|
||||
protected $language = ''; // to be set by controllers
|
||||
|
||||
protected $allow_richtext_fields = []; // only table's column fields in this list allowed to retain html tags, else strip all
|
||||
|
||||
|
||||
public function __construct($entity_type, $tb_entity = "", $allow_richtext_fields = [])
|
||||
{
|
||||
$this->entity_type = $entity_type;
|
||||
$this->tb_entity = $this->inferTbEntity($tb_entity);
|
||||
$this->tb_entity_lang = $this->tb_entity."_lang";
|
||||
|
||||
$this->language_fields = (Config::getEntityLanguageFields())[$this->tb_entity] ?? [];
|
||||
if(!sizeof($this->language_fields)) {
|
||||
die("No language_fields found for ".$this->tb_entity." on file: config/client/language_fields.php");
|
||||
}
|
||||
|
||||
$this->allow_richtext_fields = $allow_richtext_fields;
|
||||
$this->db = get_db('', ENABLE_DB_DEBUG);
|
||||
}
|
||||
|
||||
|
||||
protected function inferTbEntity(string $tb_entity = '') : string
|
||||
{
|
||||
if(!$tb_entity) {
|
||||
// auto infer
|
||||
$tb_name = str_replace("-", "_", preg_replace("/[^a-z0-9_\-]/i", "", $this->entity_type));
|
||||
return "tb_".strtolower($tb_name);
|
||||
}
|
||||
|
||||
return $tb_entity;
|
||||
}
|
||||
|
||||
|
||||
// set language to be used by model
|
||||
public function setLanguage($language) : bool
|
||||
{
|
||||
$this->language = $language;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function createTableLang(): AppResponse {
|
||||
$result = $this->db->checkTableExist($this->tb_entity_lang);
|
||||
|
||||
// check if table exist
|
||||
if(!$result) {
|
||||
$this->db->runQuery("CREATE TABLE `".$this->tb_entity_lang."` LIKE `".$this->tb_entity_lang_template."` ");
|
||||
|
||||
// recheck
|
||||
$result = $this->db->checkTableExist($this->tb_entity_lang);
|
||||
}
|
||||
|
||||
return new AppResponse($result ? 'ok': 'error');
|
||||
}
|
||||
|
||||
|
||||
public function recreateTableLang() {
|
||||
if($this->db->checkTableExist($this->tb_entity_lang)) {
|
||||
$this->db->runQuery("DROP TABLE `".$this->tb_entity_lang."` ");
|
||||
}
|
||||
$this->db->runQuery("CREATE TABLE `".$this->tb_entity_lang."` LIKE `".$this->tb_entity_lang_template."` ");
|
||||
}
|
||||
|
||||
|
||||
// any fields to have language: title, summary, description, price, etc...
|
||||
public function setLanguageFields(array $language_fields) {
|
||||
$this->language_fields = $language_fields;
|
||||
}
|
||||
|
||||
|
||||
public function getLanguageFields() : array {
|
||||
return $this->language_fields;
|
||||
}
|
||||
|
||||
|
||||
protected function beforeCreateItem(array $input_info) {
|
||||
$info = [];
|
||||
foreach ($input_info as $key => $value) {
|
||||
|
||||
if(!in_array($key, $this->language_fields)) continue;
|
||||
|
||||
$info[$key] = $value;
|
||||
}
|
||||
|
||||
return (sizeof($info) > 0) ? $info : false;
|
||||
}
|
||||
|
||||
protected function beforeUpdateItem($current_item_info, $new_input_info) {
|
||||
$info = [];
|
||||
$has_change = false;
|
||||
foreach ($new_input_info as $key => $value) {
|
||||
if(!in_array($key, $this->language_fields)) continue;
|
||||
|
||||
if(!isset($current_item_info[$key]) || $current_item_info[$key] != $value) {
|
||||
$has_change = true;
|
||||
}
|
||||
|
||||
$info[$key] = $value;
|
||||
}
|
||||
|
||||
|
||||
return ($has_change) ? $info : false;
|
||||
}
|
||||
|
||||
public function getEntityType() : string
|
||||
{
|
||||
return $this->entity_type;
|
||||
}
|
||||
|
||||
public function getTranslatedIds(): array
|
||||
{
|
||||
$query = $this->db->runQuery(
|
||||
"SELECT `item_id` FROM `".$this->tb_track_entity_lang."` WHERE `item_type` = ? AND `lang` = ? LIMIT 50000",
|
||||
['s', 's'],
|
||||
[ $this->entity_type, $this->language]
|
||||
);
|
||||
|
||||
return array_map(function ($item) {
|
||||
return $item['item_id'];
|
||||
}, $this->db->fetchAll($query));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @description prevent xss attack by stripping all html tags from un-permitted fields (which mostly are)
|
||||
* - only fields in ::allow_richtext_fields can keep html tags. Example: description
|
||||
* - if value is array (example data=['title' => '', 'description' => html tags ]) ...
|
||||
then to keep `description`, both `data` and `description` must be in the ::allow_richtext_fields
|
||||
because this method checks on array value recursively
|
||||
* @param array $input_info
|
||||
* @return array
|
||||
*/
|
||||
protected function cleanRichtextFields(array $input_info) : array {
|
||||
$cleaned_info = [];
|
||||
foreach ($input_info as $key => $value) {
|
||||
if(in_array($key, $this->allow_richtext_fields)) {
|
||||
$cleaned_info[$key] = (is_array($value)) ? $this->cleanRichtextFields($value) : $value;
|
||||
}else{
|
||||
$cleaned_info[$key] = (is_array($value)) ? $this->cleanRichtextFields($value) : strip_tags($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $cleaned_info;
|
||||
}
|
||||
|
||||
public function update($id, array $new_input_info, $search_keyword = "") : AppResponse
|
||||
{
|
||||
|
||||
$new_input_info = $this->cleanRichtextFields($new_input_info);
|
||||
|
||||
// clean url_index if any
|
||||
if(isset($new_input_info['url_index']) && $new_input_info['url_index']) {
|
||||
$new_input_info['url_index'] = UrlManagerController::create_url_index($new_input_info['url_index']);
|
||||
|
||||
// create url for this language if url_index setup
|
||||
if(in_array('url_index', $this->language_fields) && isset($new_input_info['url_index'])) {
|
||||
$request_path = $this->updateUrl($id, $new_input_info['url_index']);
|
||||
$new_input_info['request_path'] = $request_path;
|
||||
|
||||
// request_path must be auto allowed in language_fields if url_index in there. If not, update the language_fields
|
||||
// so the beforeCreateItem method can accept it
|
||||
// request_path is needed for PublicEntityBaseControllerTraits to build public urls for items in when view the language
|
||||
if(!in_array('request_path', $this->language_fields)){
|
||||
$this->setLanguageFields(array_merge($this->language_fields, ['request_path']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$current_info = $this->getInfo($id);
|
||||
|
||||
if(!$current_info) {
|
||||
|
||||
$data = $this->beforeCreateItem($new_input_info);
|
||||
if(!$data) {
|
||||
//debug_var($new_input_info);
|
||||
//die("EntityLanguageModel ".$this->entity_type.": beforeCreateItem no data");
|
||||
return new AppResponse("error", "EntityLanguageModel ".$this->entity_type.": beforeCreateItem no data");
|
||||
}
|
||||
|
||||
// create
|
||||
$this->db->insert(
|
||||
$this->tb_entity_lang,
|
||||
array(
|
||||
"id" => $id,
|
||||
"lang" => $this->language,
|
||||
"data" => $data,
|
||||
"create_time" => CURRENT_TIME,
|
||||
"create_by" => ADMIN_NAME,
|
||||
"last_update" => CURRENT_TIME,
|
||||
"last_update_by" => ADMIN_NAME,
|
||||
)
|
||||
);
|
||||
|
||||
// track item
|
||||
$this->db->insert(
|
||||
$this->tb_track_entity_lang,
|
||||
array(
|
||||
"item_type" => $this->entity_type,
|
||||
"item_id" => $id,
|
||||
"lang" => $this->language,
|
||||
"create_time" => CURRENT_TIME,
|
||||
"create_by" => ADMIN_NAME,
|
||||
)
|
||||
);
|
||||
|
||||
return new AppResponse("ok");
|
||||
}
|
||||
|
||||
// update
|
||||
$new_change_info = $this->beforeUpdateItem($current_info, $new_input_info);
|
||||
if(!$new_change_info) {
|
||||
return new AppResponse("error", "EntityLanguageModel ".$this->entity_type.": Nothing changed");
|
||||
}
|
||||
|
||||
|
||||
$updated_info = [
|
||||
"data" => array_merge($current_info, $new_change_info), // make sure update 1 key won't affect the other keys in the language
|
||||
"last_update" => CURRENT_TIME,
|
||||
"last_update_by" => ADMIN_NAME,
|
||||
];
|
||||
|
||||
$result = $this->db->update(
|
||||
$this->tb_entity_lang,
|
||||
$updated_info,
|
||||
array(
|
||||
"id" => $id,
|
||||
"lang" => $this->language,
|
||||
)
|
||||
);
|
||||
|
||||
return new AppResponse("ok", null, $result);
|
||||
}
|
||||
|
||||
|
||||
protected function updateUrl($id, $lang_url_index) {
|
||||
|
||||
/*
|
||||
Steps:
|
||||
- infer entity model -> get default url setting (url_type, request_path, ...)
|
||||
- find RequestPathConfig for entity
|
||||
- create RequestPath for this language
|
||||
- insert/update in tb-url
|
||||
|
||||
'url_type' => $url_type,
|
||||
'request_path' => $request_path ,
|
||||
'id_path' => $id_path,
|
||||
'redirect_code' => $redirect_code,
|
||||
*/
|
||||
|
||||
$baseModel = $this->createEntityBaseModelInstance();
|
||||
$base_info = $baseModel->getInfo($id);
|
||||
|
||||
if(!$base_info || !$base_info['request_path']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//debug_var($base_info['request_path']);
|
||||
|
||||
$objUrlManager = new UrlManagerController();
|
||||
$item_url_info = $objUrlManager->getUrlInfoByRequestPath($base_info['request_path']);
|
||||
|
||||
//debug_var($item_url_info);
|
||||
|
||||
$url_module = $item_url_info['module'] ;
|
||||
$url_view = $item_url_info['view'] ;
|
||||
|
||||
$module_routing = ModuleManager::getModuleRouting($url_module);
|
||||
$request_path_config = isset($module_routing[$url_view]) ? $module_routing[$url_view]['url_manager']['request_path'] : '';
|
||||
|
||||
if(!$request_path_config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$request_path = UrlManagerController::translateRequestPathConfig($request_path_config, $id, $lang_url_index);
|
||||
|
||||
if($request_path == $item_url_info['request_path']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$id_path = $item_url_info['id_path']."/_l:".$this->language;
|
||||
|
||||
$objUrlManager->createUrl($item_url_info['url_type'], $request_path, $id_path, 0);
|
||||
|
||||
return $request_path;
|
||||
}
|
||||
|
||||
|
||||
protected function createEntityBaseModelInstance() : iEntityModel
|
||||
{
|
||||
$class = EntityType::getModelClass($this->entity_type);
|
||||
if(class_exists($class)) {
|
||||
return _init_class($class);
|
||||
}
|
||||
|
||||
die($class." not exist!");
|
||||
}
|
||||
|
||||
|
||||
// delete all languages for items
|
||||
public function deleteAll($id): AppResponse
|
||||
{
|
||||
|
||||
$this->db->runQuery(
|
||||
"DELETE FROM `".$this->tb_entity_lang."` WHERE `id` = ? ",
|
||||
['d'],
|
||||
[$id]
|
||||
);
|
||||
|
||||
$this->db->runQuery(
|
||||
"DELETE FROM `".$this->tb_track_entity_lang."` WHERE `item_type` = ? AND `item_id` = ? ",
|
||||
['s', 's'],
|
||||
[ $this->entity_type, $id]
|
||||
);
|
||||
|
||||
return new AppResponse("ok");
|
||||
}
|
||||
|
||||
|
||||
public function delete($id): AppResponse
|
||||
{
|
||||
|
||||
$result = $this->db->runQuery(
|
||||
"DELETE FROM `".$this->tb_entity_lang."` WHERE `id` = ? AND `lang` = ? LIMIT 1 ",
|
||||
['d', 's'],
|
||||
[$id, $this->language], true
|
||||
);
|
||||
|
||||
if($result) {
|
||||
$this->db->runQuery(
|
||||
"DELETE FROM `".$this->tb_track_entity_lang."` WHERE `item_type` = ? AND `item_id` = ? AND `lang` = ? LIMIT 1 ",
|
||||
['s', 's', 's'],
|
||||
[ $this->entity_type ,$id, $this->language],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
return new AppResponse("ok", null, $result);
|
||||
}
|
||||
|
||||
|
||||
public function getListByIds(array $list_id): array
|
||||
{
|
||||
|
||||
if(!sizeof($list_id)) return array();
|
||||
|
||||
list($parameterized_ids, $bind_types) = create_bind_sql_parameter_from_value_list($list_id, 'int');
|
||||
|
||||
$bind_types[] = "s";
|
||||
$bind_values = $list_id;
|
||||
$bind_values[] = $this->language;
|
||||
|
||||
$query = $this->db->runQuery(
|
||||
" SELECT * FROM ".$this->tb_entity_lang." WHERE `id` IN (".$parameterized_ids.") AND `lang` = ? ",
|
||||
$bind_types,
|
||||
$bind_values
|
||||
);
|
||||
|
||||
$item_list = [];
|
||||
foreach ($this->db->fetchAll($query) as $item) {
|
||||
$item_list[$item['id']] = $this->formatItemInfo($item);
|
||||
}
|
||||
|
||||
return $item_list;
|
||||
}
|
||||
|
||||
|
||||
public function getInfo($id) : ?array
|
||||
{
|
||||
$query = $this->db->runQuery(
|
||||
"SELECT * FROM `".$this->tb_entity_lang."` WHERE `id` = ? AND `lang` = ? LIMIT 1 ",
|
||||
['d', 's'],
|
||||
[$id, $this->language]
|
||||
) ;
|
||||
|
||||
if( $item_info = $this->db->fetchAssoc($query)){
|
||||
return $this->formatItemInfo($item_info);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
protected function formatItemInfo(array $item_info) : array
|
||||
{
|
||||
return ($item_info['data']) ? \json_decode($item_info['data'], true) : [];
|
||||
}
|
||||
|
||||
|
||||
public function getTotal(array $condition): int
|
||||
{
|
||||
$query = $this->db->runQuery(
|
||||
" SELECT COUNT(*) AS total FROM `".$this->tb_entity_lang."` WHERE `lang` = ? " ,
|
||||
['s'], [ $this->language ]
|
||||
);
|
||||
$total = 0;
|
||||
if ($rs = $this->db->fetchAssoc($query)) {
|
||||
$total = $rs['total'];
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
|
||||
// get empty/default item for form
|
||||
public function getEmptyInfo() : array {
|
||||
$empty_info = [
|
||||
"id" => 0,
|
||||
"lang" => $this->language,
|
||||
"create_time" => 0,
|
||||
"create_by" => "",
|
||||
"last_update" => 0,
|
||||
"last_update_by" => "",
|
||||
];
|
||||
foreach ($this->language_fields as $field) {
|
||||
$empty_info[$field] = '';
|
||||
}
|
||||
|
||||
return $empty_info;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
341
inc/Hura8/System/Model/RelationModel.php
Normal file
341
inc/Hura8/System/Model/RelationModel.php
Normal file
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Model;
|
||||
|
||||
use Hura8\Database\iConnectDB;
|
||||
use Hura8\System\Security\DataClean;
|
||||
use Hura8\System\Security\DataType;
|
||||
|
||||
|
||||
class RelationModel
|
||||
{
|
||||
|
||||
/* @var $db iConnectDB */
|
||||
protected $db;
|
||||
|
||||
protected $tb_relation = "tb_relation";
|
||||
|
||||
protected $main_item_type= '';
|
||||
protected $main_item_id= 0;
|
||||
|
||||
public function __construct($main_item_type, $main_item_id = 0) {
|
||||
$this->db = get_db('', ENABLE_DB_DEBUG);
|
||||
$this->main_item_type = $main_item_type;
|
||||
$this->main_item_id = $main_item_id;
|
||||
}
|
||||
|
||||
|
||||
//given related_type and type_id, get all the main item ids
|
||||
public function getMainItems($related_item_type, $related_item_id = 0, array $condition = [], $return_type = 'list') {
|
||||
|
||||
$where_clause = " AND `main_item_type` = '" . $this->db->escape($this->main_item_type) . "'
|
||||
AND `main_item_id` = '".intval($this->main_item_id)."' ";
|
||||
|
||||
$where_clause .= " AND `related_item_type` = '" . $this->db->escape($related_item_type) . "' ";
|
||||
|
||||
if($related_item_id) $where_clause .= " AND `related_item_id` = '".intval($related_item_id)."' ";
|
||||
|
||||
//excluded_ids
|
||||
if(isset($condition["excluded_ids"]) && $condition["excluded_ids"] ){
|
||||
$list_ids = DataClean::makeListOfInputSafe(explode(",", $condition["excluded_ids"]), DataType::INTEGER);
|
||||
|
||||
if(sizeof($list_ids)) $where_clause .= " AND `related_item_id` NOT IN (".join(',', $list_ids ).") ";
|
||||
}
|
||||
|
||||
//included_ids
|
||||
if(isset($condition["included_ids"]) && $condition["included_ids"] ){
|
||||
$list_ids = DataClean::makeListOfInputSafe(explode(",", $condition["included_ids"]), DataType::INTEGER);
|
||||
if(sizeof($list_ids)) $where_clause .= " AND `related_item_id` IN (".join(',', $list_ids ).") ";
|
||||
}
|
||||
|
||||
|
||||
if($return_type == 'total') {
|
||||
|
||||
$query = $this->db->query("
|
||||
SELECT COUNT(*) as total FROM `".$this->tb_relation."`
|
||||
WHERE 1 " . $where_clause . " ");
|
||||
|
||||
$total = 0;
|
||||
if ($rs = $this->db->fetchAssoc($query)) {
|
||||
$total = $rs['total'];
|
||||
}
|
||||
|
||||
return $total;
|
||||
|
||||
} else {
|
||||
|
||||
$page = isset($condition['page']) ? intval($condition['page']) : 1;
|
||||
$numPerPage = isset($condition['numPerPage']) ? intval($condition['numPerPage']) : 10;
|
||||
|
||||
$query = $this->db->query("
|
||||
SELECT `related_item_id` FROM `". $this->tb_relation ."`
|
||||
WHERE 1 " . $where_clause . "
|
||||
ORDER BY `ordering` DESC, `id` DESC
|
||||
LIMIT " . ($page - 1) * $numPerPage . ", ". $numPerPage ."
|
||||
");
|
||||
|
||||
$result = array();
|
||||
foreach ( $this->db->fetchAll($query) as $info ) {
|
||||
$result[] = $info['related_item_id'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
public function updateOrdering($related_item_id, $new_order) {
|
||||
|
||||
$this->db->runQuery(
|
||||
"UPDATE `".$this->tb_relation."` SET
|
||||
`ordering` = ?
|
||||
WHERE `main_item_type` = ? AND `main_item_id` = ? AND `related_item_id` = ? ",
|
||||
['d', 's', 'd', 'd'],
|
||||
[ $new_order, $this->main_item_type, $this->main_item_id, $related_item_id ]
|
||||
);
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'message' => ''
|
||||
];
|
||||
}
|
||||
|
||||
//@warn: this does not check if records exist.
|
||||
public function create(array $related_items, $both_way_relation = true) {
|
||||
|
||||
$build_insert = array();
|
||||
foreach ($related_items as $item) {
|
||||
if(!$this->checkExist($this->main_item_type, $this->main_item_id, $item['type'], $item['id'])) {
|
||||
$build_insert[] = [
|
||||
"main_item_type" => $this->main_item_type,
|
||||
"main_item_id" => intval($this->main_item_id),
|
||||
"related_item_type" => $item['type'],
|
||||
"related_item_id" => intval($item['id']),
|
||||
"create_time" => CURRENT_TIME,
|
||||
"create_by" => ADMIN_NAME,
|
||||
];
|
||||
}
|
||||
|
||||
//if 2-way relation, create item->main
|
||||
if($both_way_relation) {
|
||||
if(!$this->checkExist($item['type'], $item['id'], $this->main_item_type, $this->main_item_id)) {
|
||||
|
||||
$build_insert[] = [
|
||||
"main_item_type" => $item['type'],
|
||||
"main_item_id" => intval($item['id']),
|
||||
"related_item_type" => $this->main_item_type,
|
||||
"related_item_id" => intval($this->main_item_id),
|
||||
"create_time" => CURRENT_TIME,
|
||||
"create_by" => ADMIN_NAME,
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(sizeof($build_insert)) {
|
||||
$this->db->bulk_insert($this->tb_relation, $build_insert);
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'message' => ''
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 'error',
|
||||
'message' => 'Đã tồn tại'
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function checkExist($main_item_type, $main_item_id, $related_item_type, $related_item_id) {
|
||||
|
||||
// itself
|
||||
if($main_item_type == $related_item_type && $main_item_id == $related_item_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$query = $this->db->runQuery(
|
||||
"SELECT id FROM `".$this->tb_relation."`
|
||||
WHERE `main_item_type` = ? AND `main_item_id` = ? AND `related_item_type` = ? AND `related_item_id` = ?
|
||||
LIMIT 1" ,
|
||||
[
|
||||
's', 'd', 's', 'd'
|
||||
],
|
||||
[
|
||||
$main_item_type,
|
||||
intval($main_item_id),
|
||||
$related_item_type,
|
||||
intval($related_item_id)
|
||||
]
|
||||
);
|
||||
|
||||
if($rs = $this->db->fetchAssoc($query)) {
|
||||
return $rs['id'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//remove a related-item
|
||||
public function remove($related_item_type, $related_item_id, $remove_both_way = true) {
|
||||
|
||||
$main_item_query = ( $this->main_item_id ) ? " `main_item_id` = '".intval($this->main_item_id)."' AND " : "";
|
||||
|
||||
$this->db->query("DELETE FROM `".$this->tb_relation."` WHERE
|
||||
`main_item_type` = '" . $this->db->escape($this->main_item_type) . "' AND
|
||||
". $main_item_query ."
|
||||
`related_item_type` = '".$this->db->escape($related_item_type)."' AND
|
||||
`related_item_id` = '".intval($related_item_id)."' ");
|
||||
|
||||
if($remove_both_way) {
|
||||
|
||||
$related_item_query = ( $this->main_item_id ) ? " `related_item_id` = '".intval($this->main_item_id)."' AND " : "";
|
||||
|
||||
$this->db->query("DELETE FROM `".$this->tb_relation."` WHERE
|
||||
`related_item_type` = '" . $this->db->escape($this->main_item_type) . "' AND
|
||||
" . $related_item_query . "
|
||||
`main_item_type` = '".$this->db->escape($related_item_type)."' AND
|
||||
`main_item_id` = '".intval($related_item_id)."' ");
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'message' => ''
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
//remove all relate items
|
||||
public function truncate() {
|
||||
$this->db->runQuery(
|
||||
"DELETE FROM `".$this->tb_relation."` WHERE `main_item_type` = ? AND `main_item_id` = ? ",
|
||||
[ 's', 's' ],
|
||||
[ $this->main_item_type, $this->main_item_id ]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
//count related items
|
||||
public function getRelatedItemCount() {
|
||||
|
||||
$query = $this->db->runQuery(
|
||||
"
|
||||
SELECT `related_item_type`, COUNT(`related_item_id`) AS total FROM `". $this->tb_relation ."`
|
||||
WHERE `main_item_type` = ? AND `main_item_id` = ?
|
||||
GROUP BY `related_item_type`
|
||||
",
|
||||
[ 's', 's' ], [ $this->main_item_type, $this->main_item_id ]
|
||||
);
|
||||
$result = array();
|
||||
foreach ( $this->db->fetchAll($query) as $info ) {
|
||||
$result[$info['related_item_type']] = $info['total'];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
//extend getRelatedItems to get for list of main_item_ids
|
||||
public function getRelatedItemsForList(array $main_item_list_ids, array $related_item_types = []) {
|
||||
|
||||
if(!sizeof($main_item_list_ids)) return [];
|
||||
|
||||
$bind_values = $main_item_list_ids;
|
||||
list($parameterized_ids, $bind_types) = create_bind_sql_parameter_from_value_list($main_item_list_ids, 'int');
|
||||
$condition = " AND `main_item_id` IN (". $parameterized_ids .") ";
|
||||
|
||||
$bind_types[] = 's';
|
||||
$bind_values[] = $this->main_item_type;
|
||||
$condition .= " AND `main_item_type` = ? ";
|
||||
|
||||
if(sizeof($related_item_types)) {
|
||||
$type_condition = [];
|
||||
foreach ($related_item_types as $_type) {
|
||||
$type_condition[] = " `related_item_type` = ? ";
|
||||
|
||||
$bind_types[] = 's';
|
||||
$bind_values[] = $_type;
|
||||
}
|
||||
|
||||
$condition .= " AND ( ".join(' OR ', $type_condition )." ) ";
|
||||
}
|
||||
|
||||
$query = $this->db->runQuery("
|
||||
SELECT
|
||||
`main_item_id` ,
|
||||
`related_item_type`,
|
||||
`related_item_id`,
|
||||
`ordering`
|
||||
FROM `". $this->tb_relation ."`
|
||||
WHERE 1 ". $condition ."
|
||||
ORDER BY `ordering` DESC, `id` DESC
|
||||
LIMIT 5000
|
||||
", $bind_types, $bind_values);
|
||||
|
||||
$result = array();
|
||||
foreach ( $this->db->fetchAll($query) as $info ) {
|
||||
$result[$info['main_item_id']][$info['related_item_type']][$info['related_item_id']] = [
|
||||
"item_id" => $info['related_item_id'],
|
||||
"ordering" => $info['ordering'],
|
||||
];
|
||||
}
|
||||
|
||||
//final result
|
||||
$final_result = [];
|
||||
foreach ($main_item_list_ids as $_id) {
|
||||
$final_result[$_id] = (isset($result[$_id])) ? $result[$_id] : false;
|
||||
}
|
||||
|
||||
return $final_result;
|
||||
}
|
||||
|
||||
//get all related items and group them by type
|
||||
public function getRelatedItems(array $related_item_types = []) {
|
||||
|
||||
$bind_types = ['s', 's'];
|
||||
$bind_values = [$this->main_item_type, $this->main_item_id];
|
||||
|
||||
$condition = "";
|
||||
if(sizeof($related_item_types)) {
|
||||
$type_condition = [];
|
||||
foreach ($related_item_types as $_type) {
|
||||
$type_condition[] = " `related_item_type` = ? ";
|
||||
|
||||
$bind_types[] = 's';
|
||||
$bind_values[] = $_type;
|
||||
}
|
||||
|
||||
$condition .= " AND ( ".join(' OR ', $type_condition )." ) ";
|
||||
}
|
||||
|
||||
|
||||
$query = $this->db->runQuery("
|
||||
SELECT
|
||||
`related_item_type`,
|
||||
`related_item_id`,
|
||||
`ordering`
|
||||
FROM `". $this->tb_relation ."`
|
||||
WHERE `main_item_type` = ? AND `main_item_id` = ? ". $condition ."
|
||||
ORDER BY `ordering` DESC, `id` DESC
|
||||
LIMIT 5000
|
||||
", $bind_types, $bind_values );
|
||||
$result = array();
|
||||
foreach ( $this->db->fetchAll($query) as $info ) {
|
||||
$result[$info['related_item_type']][$info['related_item_id']] = [
|
||||
"item_id" => $info['related_item_id'],
|
||||
"ordering" => $info['ordering'],
|
||||
];
|
||||
}
|
||||
|
||||
//check if we get only single type, then return all the results
|
||||
if(sizeof($related_item_types) == 1) {
|
||||
$related_item_type = $related_item_types[0];
|
||||
return isset($result[$related_item_type]) ? $result[$related_item_type] : array();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
173
inc/Hura8/System/Model/SettingModel.php
Normal file
173
inc/Hura8/System/Model/SettingModel.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Model;
|
||||
|
||||
|
||||
use Hura8\Database\iConnectDB;
|
||||
use Hura8\System\Security\DataClean;
|
||||
use Hura8\System\Security\DataType;
|
||||
|
||||
class SettingModel
|
||||
{
|
||||
|
||||
protected $db;
|
||||
|
||||
protected $tb_setting = 'tb_settings';
|
||||
|
||||
const KEY = [
|
||||
'pcbuilder_config' => 'pcbuilder_config',
|
||||
];
|
||||
|
||||
|
||||
public function __construct(){
|
||||
$this->db = get_db('', ENABLE_DB_DEBUG);
|
||||
}
|
||||
|
||||
|
||||
public function getAll(){
|
||||
$query = $this->db->runQuery(
|
||||
"SELECT * FROM `".$this->tb_setting."` WHERE 1 ORDER BY `id` DESC LIMIT 100 "
|
||||
);
|
||||
|
||||
$result = [];
|
||||
foreach ( $this->db->fetchAll($query) as $rs ){
|
||||
if($rs['setting_value']) {
|
||||
$rs['setting_value'] = \unserialize($rs['setting_value']);
|
||||
}
|
||||
|
||||
$result[] = $rs;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
// get a list of keys
|
||||
public function getList(array $keys){
|
||||
$conditions = [];
|
||||
$bind_types = [];
|
||||
$bind_values = [];
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$conditions[] = " `setting_key`= ? ";
|
||||
$bind_types[] = 's';
|
||||
$bind_values[] = $key;
|
||||
}
|
||||
|
||||
if(sizeof($conditions)) {
|
||||
$query = $this->db->runQuery(
|
||||
" SELECT `setting_key`, `setting_value` FROM `".$this->tb_setting."` WHERE ".join(" OR ", $conditions)." LIMIT 100 ",
|
||||
$bind_types, $bind_values
|
||||
);
|
||||
|
||||
$result = [];
|
||||
foreach ( $this->db->fetchAll($query) as $rs ){
|
||||
$result[$rs['setting_key']] = \unserialize($rs['setting_value']);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
// get a single key, = false if not found and no default is set
|
||||
public function get($key, $default = null){
|
||||
$key = $this->cleanKey($key);
|
||||
if(!$key) return null;
|
||||
|
||||
$query = $this->db->runQuery(
|
||||
"SELECT `setting_value` FROM `".$this->tb_setting."` WHERE `setting_key`= ? LIMIT 1 ",
|
||||
['s'], [ $key ]
|
||||
);
|
||||
|
||||
if($rs = $this->db->fetchAssoc($query)){
|
||||
return \unserialize($rs['setting_value']);
|
||||
}
|
||||
|
||||
return ($default) ?: null;
|
||||
}
|
||||
|
||||
|
||||
public function delete($key){
|
||||
return $this->db->runQuery(
|
||||
"DELETE FROM `" . $this->tb_setting . "` WHERE `setting_key` = ? LIMIT 1 ",
|
||||
['s'], [ $key ]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// update or create
|
||||
public function updateOrCreate($key, $value = null, $comment = ''){
|
||||
$key = $this->cleanKey($key);
|
||||
if(!$key) return false;
|
||||
|
||||
if($this->checkKeyExist($key)) {
|
||||
return $this->db->update(
|
||||
$this->tb_setting ,
|
||||
[
|
||||
'setting_value' => ($value) ? \serialize($value) : null,
|
||||
'comment' => subString($comment, 80),
|
||||
'last_update' => CURRENT_TIME,
|
||||
'last_update_by' => ADMIN_NAME,
|
||||
],
|
||||
[
|
||||
'setting_key' => $key,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return $this->db->insert(
|
||||
$this->tb_setting ,
|
||||
[
|
||||
'setting_key' => $key,
|
||||
'setting_value' => ($value) ? \serialize($value) : null,
|
||||
'comment' => subString($comment, 80),
|
||||
'create_time' => CURRENT_TIME,
|
||||
'create_by' => ADMIN_NAME,
|
||||
'last_update' => CURRENT_TIME,
|
||||
'last_update_by' => ADMIN_NAME,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function populateKeys(array $key_list) {
|
||||
$build_inserts = [];
|
||||
foreach ($key_list as $key) {
|
||||
if( ! $this->checkKeyExist($key)) {
|
||||
$build_inserts[] = [
|
||||
"setting_key" => $key,
|
||||
"create_time" => CURRENT_TIME,
|
||||
"create_by" => 'system',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if(sizeof($build_inserts)) {
|
||||
$this->db->insert($this->tb_setting, $build_inserts);
|
||||
}
|
||||
|
||||
return sizeof($build_inserts);
|
||||
}
|
||||
|
||||
|
||||
protected function checkKeyExist($key) : bool {
|
||||
$key = $this->cleanKey($key);
|
||||
if(!$key) return false;
|
||||
|
||||
$query = $this->db->runQuery(
|
||||
" SELECT `id` FROM `".$this->tb_setting."` WHERE `setting_key`= ? LIMIT 1 ", ['s'], [ $key ]
|
||||
);
|
||||
|
||||
return (bool) $this->db->fetchAssoc($query);
|
||||
}
|
||||
|
||||
|
||||
// only allow some characters
|
||||
protected function cleanKey($key) {
|
||||
return DataClean::makeInputSafe($key, DataType::ID);
|
||||
}
|
||||
|
||||
}
|
||||
216
inc/Hura8/System/Model/UrlModel.php
Normal file
216
inc/Hura8/System/Model/UrlModel.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Model;
|
||||
|
||||
use Hura8\Interfaces\AppResponse;
|
||||
use Hura8\Interfaces\TableName;
|
||||
|
||||
class UrlModel extends aEntityBaseModel
|
||||
{
|
||||
|
||||
private $tb_url = TableName::URL; ///
|
||||
private $tb_url_meta = TableName::URL_META; ///
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct("url");
|
||||
}
|
||||
|
||||
|
||||
public function getUrlByIdPath($id_path) {
|
||||
$query = $this->db->runQuery(
|
||||
"SELECT * FROM `".$this->tb_url."` WHERE `id_path` = ? LIMIT 1 ",
|
||||
['s'], [ $id_path ]
|
||||
);
|
||||
|
||||
return $this->db->fetchAssoc($query);
|
||||
}
|
||||
|
||||
|
||||
public function deleteByType(string $url_type) {
|
||||
$this->db->runQuery(
|
||||
"DELETE FROM `".$this->tb_url."` WHERE `url_type` = ? ",
|
||||
['s'], [ $url_type ]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function deleteByRequestPath($request_path) {
|
||||
$this->db->runQuery(
|
||||
"DELETE FROM `".$this->tb_url."` WHERE `request_path_index` = ? LIMIT 1 ",
|
||||
['s'], [ md5($request_path) ]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function getUrlByRequestPath($request_path) {
|
||||
$query = $this->db->runQuery(
|
||||
"SELECT * FROM `".$this->tb_url."` WHERE `request_path_index` = ? LIMIT 1 ",
|
||||
['s'], [ md5($request_path) ]
|
||||
);
|
||||
|
||||
return $this->db->fetchAssoc($query);
|
||||
}
|
||||
|
||||
|
||||
//02-12-2015
|
||||
public function getUrlMetaInfo($request_path){
|
||||
return $this->db->getItemInfo($this->tb_url_meta, md5($request_path), 'request_path_index');
|
||||
}
|
||||
|
||||
|
||||
public function getUrlMetaInfoById($id){
|
||||
return $this->db->getItemInfo($this->tb_url_meta, $id, 'id');
|
||||
}
|
||||
|
||||
|
||||
public function getEmptyUrlMetaInfo() {
|
||||
return array(
|
||||
'id' => 0,
|
||||
'request_path' => '',
|
||||
'h1' => '',
|
||||
'meta_title' => '',
|
||||
'meta_keyword' => '',
|
||||
'meta_description' => '',
|
||||
'og_image' => '',
|
||||
'summary' => '',
|
||||
'description' => '',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
//02-12-2015
|
||||
public function createUrlMeta(array $info){
|
||||
|
||||
//prevent duplication
|
||||
if($this->getUrlMetaInfo($info['request_path'])) return false;
|
||||
|
||||
if(!defined("ADMIN_NAME")) define("ADMIN_NAME", "system");
|
||||
|
||||
$copy = $info;
|
||||
$copy['request_path_index'] = md5($info['request_path']);
|
||||
$copy['last_update'] = CURRENT_TIME;
|
||||
$copy['last_update_by'] = ADMIN_NAME;
|
||||
|
||||
return $this->db->insert(
|
||||
$this->tb_url_meta ,
|
||||
$copy
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function updateUrlMeta($id, array $info){
|
||||
$copy = $info;
|
||||
$copy['last_update'] = CURRENT_TIME;
|
||||
$copy['last_update_by'] = ADMIN_NAME;
|
||||
|
||||
return $this->db->update(
|
||||
$this->tb_url_meta ,
|
||||
$copy,
|
||||
[
|
||||
'id' => $id,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
protected function _buildQueryConditionExtend(array $condition): ?array
|
||||
{
|
||||
|
||||
/*$condition = array(
|
||||
"letter" => "",
|
||||
);*/
|
||||
|
||||
$catCondition = [];
|
||||
$bind_types = [];
|
||||
$bind_values = [];
|
||||
|
||||
//loc theo is_redirect
|
||||
if(isset($condition["request_path"]) && $condition["request_path"]) {
|
||||
$path = preg_replace("/[^a-z0-9_\.\/\-]/i", '', $condition["request_path"]);
|
||||
if($path) $catCondition[] = " AND `request_path` LIKE '%".$path."%' ";
|
||||
}
|
||||
|
||||
//loc theo is_redirect
|
||||
if(isset($condition["is_redirect"]) && $condition["is_redirect"]) {
|
||||
if($condition["is_redirect"] == 1) $catCondition[] = " AND `url_type` = 'redirect' ";
|
||||
else if($condition["is_redirect"] == -1) $catCondition[] = " AND `url_type` != 'redirect' ";
|
||||
}
|
||||
|
||||
return array( join(" ", $catCondition), $bind_types, $bind_values);
|
||||
}
|
||||
|
||||
|
||||
protected function beforeCreateItem(array $input_info): AppResponse
|
||||
{
|
||||
$request_path = $input_info['request_path'] ?? '';
|
||||
$id_path = $input_info['id_path'] ?? '';
|
||||
|
||||
if(!$request_path || $this->getUrlByRequestPath($request_path)) {
|
||||
return new AppResponse('error', "request_path exist");
|
||||
}
|
||||
|
||||
if($id_path && $this->getUrlByIdPath($id_path)) {
|
||||
return new AppResponse('error', "id path exist");
|
||||
}
|
||||
|
||||
$admin_name = (defined("ADMIN_NAME")) ? ADMIN_NAME : "system";
|
||||
|
||||
$info = $input_info;
|
||||
|
||||
$info['request_path_index'] = md5($info['request_path']);
|
||||
|
||||
$info['create_time'] = CURRENT_TIME;
|
||||
$info['create_by'] = $admin_name;
|
||||
|
||||
$info['last_update'] = CURRENT_TIME;
|
||||
$info['last_update_by'] = $admin_name;
|
||||
|
||||
return new AppResponse('ok', null, $info);
|
||||
}
|
||||
|
||||
|
||||
protected function afterCreateItem($new_item_id, $new_item_info)
|
||||
{
|
||||
// TODO: Implement afterCreateItem() method.
|
||||
}
|
||||
|
||||
|
||||
protected function beforeUpdateItem($item_id, $current_item_info, $new_input_info): AppResponse
|
||||
{
|
||||
$info = $new_input_info;
|
||||
unset($info['id_path']);
|
||||
|
||||
if(isset($info['request_path']) && $info['request_path']) {
|
||||
$info['request_path_index'] = md5($info['request_path']);
|
||||
}
|
||||
|
||||
$info['last_update'] = CURRENT_TIME;
|
||||
$info['last_update_by'] = ADMIN_NAME;
|
||||
|
||||
return new AppResponse('ok', null, $info);
|
||||
}
|
||||
|
||||
|
||||
protected function afterUpdateItem($item_id, $old_item_info, $new_item_info)
|
||||
{
|
||||
// TODO: Implement afterUpdateItem() method.
|
||||
}
|
||||
|
||||
|
||||
protected function beforeDeleteItem($item_id, $item_info): AppResponse
|
||||
{
|
||||
return new AppResponse('ok');
|
||||
}
|
||||
|
||||
|
||||
protected function afterDeleteItem($item_id, $item_info)
|
||||
{
|
||||
// TODO: Implement afterDeleteItem() method.
|
||||
}
|
||||
|
||||
|
||||
protected function extendedFilterOptions(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
39
inc/Hura8/System/Model/UtilityModel.php
Normal file
39
inc/Hura8/System/Model/UtilityModel.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Model;
|
||||
|
||||
use Hura8\Database\iConnectDB;
|
||||
use Hura8\Interfaces\AppResponse;
|
||||
|
||||
/**
|
||||
* @date 21-Dec-2023
|
||||
* @description various utility for management and setup
|
||||
*/
|
||||
class UtilityModel
|
||||
{
|
||||
protected $db;
|
||||
|
||||
protected $tb_entity_lang_template = "tb_entity_lang_template";
|
||||
|
||||
|
||||
public function __construct($db_id = '') {
|
||||
$this->db = get_db($db_id, ENABLE_DB_DEBUG);
|
||||
}
|
||||
|
||||
|
||||
public function createTableLang($tb_entity_lang): AppResponse {
|
||||
$result = $this->db->checkTableExist($tb_entity_lang);
|
||||
|
||||
// check if table exist
|
||||
if(!$result) {
|
||||
$this->db->runQuery("CREATE TABLE `".$tb_entity_lang."` LIKE `".$this->tb_entity_lang_template."` ");
|
||||
|
||||
// recheck
|
||||
$result = $this->db->checkTableExist($tb_entity_lang);
|
||||
}
|
||||
|
||||
return new AppResponse($result ? 'ok': 'error');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
92
inc/Hura8/System/Model/WebUserModel.php
Normal file
92
inc/Hura8/System/Model/WebUserModel.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Model;
|
||||
|
||||
use Hura8\Database\iConnectDB;
|
||||
use Hura8\Interfaces\TableName;
|
||||
|
||||
class WebUserModel
|
||||
{
|
||||
|
||||
protected $user_browser_id = '';
|
||||
protected $user_db_id = 0;
|
||||
private $tb_user = TableName::WEB_USER; //"web_user_info";
|
||||
|
||||
protected $db;
|
||||
|
||||
|
||||
public function __construct($user_browser_id)
|
||||
{
|
||||
$this->db = get_db('', ENABLE_DB_DEBUG);
|
||||
$this->user_browser_id = preg_replace("/[^a-z0-9]/i", "", $user_browser_id);
|
||||
$this->user_db_id = (defined("USER_ID")) ? USER_ID : 0;
|
||||
}
|
||||
|
||||
public function getValue($key) {
|
||||
$key = $this->cleanKey($key);
|
||||
|
||||
$query = $this->db->runQuery("SELECT `content` FROM `".$this->tb_user."`
|
||||
WHERE `user_id` = ? AND `key` = ?
|
||||
LIMIT 1 ", ['s', 's'], [ $this->user_browser_id , $key ]) ;
|
||||
if($rs = $this->db->fetchAssoc($query )){
|
||||
return \unserialize($rs['content']);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setValue($key, $value) {
|
||||
$key = $this->cleanKey($key);
|
||||
|
||||
if($row_id = $this->checkKey($key)) {
|
||||
return $this->db->update(
|
||||
$this->tb_user ,
|
||||
[
|
||||
'content' => \serialize($value) ,
|
||||
'user_db_id' => $this->user_db_id ,
|
||||
'last_update' => CURRENT_TIME ,
|
||||
],
|
||||
[
|
||||
'id' => $row_id,
|
||||
]
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return $this->db->insert(
|
||||
$this->tb_user ,
|
||||
[
|
||||
'user_id' => $this->user_browser_id ,
|
||||
'user_db_id' => $this->user_db_id,
|
||||
'key' => $key,
|
||||
'content' => \serialize($value),
|
||||
'create_time' => CURRENT_TIME ,
|
||||
'last_update' => CURRENT_TIME,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
//delete all user history
|
||||
public function deleteUser($key) {
|
||||
$this->db->runQuery(
|
||||
"DELETE FROM `".$this->tb_user."` WHERE `user_id` = ? LIMIT 1 ",
|
||||
['s'],
|
||||
[ $this->user_browser_id ]
|
||||
) ;
|
||||
}
|
||||
|
||||
protected function cleanKey($key) {
|
||||
return preg_replace("/[^a-z0-9]/i", "", $key);
|
||||
}
|
||||
|
||||
protected function checkKey($key) {
|
||||
$query = $this->db->runQuery("SELECT `id` FROM `".$this->tb_user."`
|
||||
WHERE `user_id` = ? AND `key` = ?
|
||||
LIMIT 1", ['s', 's'], [ $this->user_browser_id , $key ] ) ;
|
||||
if($rs = $this->db->fetchAssoc($query )){
|
||||
return $rs['id'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
292
inc/Hura8/System/Model/aCategoryBaseModel.php
Normal file
292
inc/Hura8/System/Model/aCategoryBaseModel.php
Normal file
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Model;
|
||||
|
||||
|
||||
use Hura8\Interfaces\AppResponse;
|
||||
use Hura8\System\Security;
|
||||
use Hura8\System\Security\DataType;
|
||||
|
||||
|
||||
abstract class aCategoryBaseModel extends aEntityBaseModel
|
||||
{
|
||||
|
||||
public function __construct($entity_type, $tb_name = "") {
|
||||
parent::__construct($entity_type, $tb_name);
|
||||
}
|
||||
|
||||
|
||||
protected function extendedFilterOptions() : array
|
||||
{
|
||||
return [
|
||||
// empty for now
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// get all categories and group by parents
|
||||
public function getAllByParent(array $condition = array()) : array
|
||||
{
|
||||
$item_list = array(); // parentId=>array(childId)
|
||||
foreach ( $this->getAll($condition) as $item ) {
|
||||
$item_list[$item['parent_id']][$item['id']] = $item;
|
||||
}
|
||||
|
||||
return $item_list;
|
||||
}
|
||||
|
||||
|
||||
// get all categories
|
||||
public function getAll(array $condition = array()){
|
||||
/*$condition = [
|
||||
"parent_id" => 1,
|
||||
"status" => 1,
|
||||
];*/
|
||||
|
||||
$bind_types = [];
|
||||
$bind_values = [];
|
||||
$where_clause = '';
|
||||
|
||||
if(isset($condition['status'])) {
|
||||
$where_clause .= " AND `status` = ? ";
|
||||
$bind_types[] = 'd';
|
||||
$bind_values[] = intval($condition['status']);
|
||||
}
|
||||
|
||||
$query = $this->db->runQuery(
|
||||
" SELECT * FROM `". $this->tb_entity ."` WHERE 1 ".$where_clause." ORDER BY `ordering` DESC LIMIT 5000 ",
|
||||
$bind_types, $bind_values
|
||||
);
|
||||
|
||||
return $this->db->fetchAll($query);
|
||||
}
|
||||
|
||||
|
||||
protected function beforeCreateItem(array $input_info) : AppResponse
|
||||
{
|
||||
$parent_id = isset($input_info['parent_id']) ? $input_info['parent_id'] : 0;
|
||||
$api_key = (isset($input_info['api_key']) && $input_info['api_key']) ? $input_info['api_key'] : $input_info['title'];
|
||||
$api_key = Security\DataClean::makeInputSafe($api_key, DataType::ID);
|
||||
|
||||
$api_key = $this->createUniqueAPIKey(0, $api_key);
|
||||
|
||||
if(!isset($input_info['url_index']) || !$input_info['url_index']) {
|
||||
$input_info['url_index'] = $this->createUniqueUrlIndex(0, $input_info['title']);
|
||||
}else{
|
||||
$input_info['url_index'] = $this->createUniqueUrlIndex(0, $input_info['url_index']);
|
||||
}
|
||||
|
||||
$info = array_merge($input_info, array(
|
||||
"parent_id" => $parent_id,
|
||||
"api_key" => $api_key,
|
||||
"create_by" => ADMIN_NAME,
|
||||
"create_time" => CURRENT_TIME,
|
||||
"last_update_by" => ADMIN_NAME,
|
||||
"last_update" => CURRENT_TIME,
|
||||
) );
|
||||
|
||||
return new AppResponse('ok', null, $info);
|
||||
}
|
||||
|
||||
|
||||
protected function afterCreateItem($new_item_id, $new_item_info) {
|
||||
//update path&child
|
||||
$this->updatePath($new_item_id);
|
||||
$this->updateChild($new_item_id);
|
||||
$this->updateChild($new_item_info['parent_id']);
|
||||
}
|
||||
|
||||
|
||||
protected function beforeUpdateItem($item_id, $current_item_info, $new_input_info) : AppResponse
|
||||
{
|
||||
$info = $new_input_info;
|
||||
|
||||
if(isset($info['url_index'])) {
|
||||
if(!$info['url_index']) {
|
||||
$info['url_index'] = $this->createUniqueUrlIndex($item_id, $info['title']);
|
||||
}else{
|
||||
$info['url_index'] = $this->createUniqueUrlIndex($item_id, $info['url_index']);
|
||||
}
|
||||
}
|
||||
|
||||
$info['last_update'] = CURRENT_TIME;
|
||||
$info['last_update_by'] = ADMIN_NAME;
|
||||
|
||||
return new AppResponse('ok', null, $info);
|
||||
}
|
||||
|
||||
|
||||
protected function afterUpdateItem($item_id, $old_item_info, $new_item_info) {
|
||||
//update cat-path for category
|
||||
$this->updatePath($item_id);
|
||||
$this->updateChild($item_id);
|
||||
|
||||
//update child_ids for new/old parents and parents of parent
|
||||
if($new_item_info['parent_id'] != $old_item_info['parent_id']) {
|
||||
$this->updateChild($new_item_info['parent_id']);
|
||||
$this->updateChild($old_item_info['parent_id']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function afterDeleteItem($item_id, $item_info){
|
||||
$this->updateChild($item_info["parent_id"]);
|
||||
}
|
||||
|
||||
|
||||
//create an unique request-path
|
||||
protected function createUniqueAPIKey($id, $api_key){
|
||||
|
||||
//if exist and belong other id, create a new one
|
||||
$query = $this->db->runQuery("SELECT `id` FROM `".$this->tb_entity."` WHERE `api_key` = ? LIMIT 1 ", ['s'], [$api_key]) ;
|
||||
if($info = $this->db->fetchAssoc($query)){
|
||||
if($info['id'] != $id) {
|
||||
$new_api_key = $api_key."-1";
|
||||
return $this->createUniqueAPIKey($id, $new_api_key);
|
||||
}
|
||||
}
|
||||
|
||||
return $api_key;
|
||||
}
|
||||
|
||||
|
||||
protected function updatePathAndChildAll($id, $child_ids, $parent_id, $old_parent_id) {
|
||||
$this->updatePathAndChild($id);
|
||||
|
||||
//update for childs
|
||||
$list_child_to_update = array_filter(explode(",", $child_ids));
|
||||
foreach($list_child_to_update as $_id) {
|
||||
if($_id != $id) $this->updatePathAndChild($_id);
|
||||
}
|
||||
|
||||
//cap nhat lai child list cua danh muc old_parent and new parent id, and parent of these parents
|
||||
$query = $this->db->runQuery(
|
||||
"SELECT cat_path FROM `". $this->tb_entity ."` WHERE `id`= ? OR `id`= ? ",
|
||||
['d', 'd'], [$parent_id, $old_parent_id]
|
||||
);
|
||||
|
||||
$cat_path_all = join(":", array_map(function ($item){
|
||||
return $item['cat_path'];
|
||||
} , $this->db->fetchAll($query)));
|
||||
|
||||
|
||||
$list_parent_to_update = array_unique(array_filter(explode(":", $cat_path_all)));
|
||||
|
||||
foreach($list_parent_to_update as $_id) {
|
||||
if($_id > 0) $this->updatePathAndChild($_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function updatePathAndChild($id) {
|
||||
if(!$id) return false;
|
||||
|
||||
$new_cat_path = $this->findCatPath($id);
|
||||
$new_child_list = $this->findChildList($id);
|
||||
$is_parent = ( $new_child_list === $id) ? 0 : 1;
|
||||
|
||||
return $this->db->update(
|
||||
$this->tb_entity ,
|
||||
[
|
||||
'cat_path' => $new_cat_path,
|
||||
'child_ids' => $new_child_list,
|
||||
'is_parent' => $is_parent,
|
||||
],
|
||||
[
|
||||
'id' => $id,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
protected function updatePath($id) {
|
||||
if(!$id) return false;
|
||||
|
||||
$new_cat_path = $this->findCatPath($id);
|
||||
|
||||
return $this->db->update(
|
||||
$this->tb_entity ,
|
||||
[
|
||||
'cat_path' => $new_cat_path,
|
||||
],
|
||||
[
|
||||
'id' => $id,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
//update childs for current id and its parents and its parents' parents....
|
||||
protected function updateChild($id) {
|
||||
if(!$id) return false;
|
||||
|
||||
$query = $this->db->runQuery("SELECT `cat_path` FROM `".$this->tb_entity."` WHERE `id` = ? LIMIT 1 ", ['d'], [$id]) ;
|
||||
|
||||
if($item_info = $this->db->fetchAssoc($query)){
|
||||
$cat_id_list = array_filter(explode(":", $item_info['cat_path']));
|
||||
|
||||
foreach ($cat_id_list as $_id) {
|
||||
$new_child_list = $this->findChildList($_id);
|
||||
$is_parent = ( $new_child_list === $_id) ? 0 : 1;
|
||||
|
||||
$this->db->update(
|
||||
$this->tb_entity ,
|
||||
[
|
||||
'child_ids' => $new_child_list,
|
||||
'is_parent' => $is_parent,
|
||||
],
|
||||
[
|
||||
'id' => $_id,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected function _buildQueryConditionExtend(array $condition) : ?array
|
||||
{
|
||||
$catCondition = [];
|
||||
$bind_types = [];
|
||||
$bind_values = [];
|
||||
|
||||
return array(join(" ", $catCondition), $bind_types, $bind_values);
|
||||
}
|
||||
|
||||
|
||||
//build category path 0:parent:categoryId
|
||||
protected function findCatPath($categoryId){
|
||||
$path = ":".$categoryId;
|
||||
$query = $this->db->runQuery("SELECT `parent_id` FROM `".$this->tb_entity."` WHERE `id` = ? LIMIT 1", ['d'], [$categoryId]);
|
||||
if($rs = $this->db->fetchAssoc($query)){
|
||||
if($rs['parent_id']) $path .= $this->findCatPath($rs['parent_id']);
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
//lay toan bo cac muc la con
|
||||
protected function findChildList($p_category_id){
|
||||
$all_categories = $this->getAllByParent();
|
||||
|
||||
$list = $p_category_id;
|
||||
if(isset($all_categories[$p_category_id])) {
|
||||
foreach ( $all_categories[$p_category_id] as $rs ) {
|
||||
$list .= ",". $this->findChildList($rs['id']);
|
||||
}
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
|
||||
protected function getAllParent(array $condition = []) {
|
||||
$cache_key = 'all-category-by-parent-'.$this->getEntityType();
|
||||
|
||||
return self::getCache($cache_key, function () use ($condition){
|
||||
return $this->getAllByParent($condition);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
428
inc/Hura8/System/Model/aEntityBaseModel.php
Normal file
428
inc/Hura8/System/Model/aEntityBaseModel.php
Normal file
@@ -0,0 +1,428 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Model;
|
||||
|
||||
use Hura8\Database\iConnectDB;
|
||||
use Hura8\Database\MysqlValue;
|
||||
use Hura8\Interfaces\iSearch;
|
||||
|
||||
|
||||
abstract class aEntityBaseModel
|
||||
{
|
||||
|
||||
|
||||
/* @var iConnectDB $db */
|
||||
protected $db;
|
||||
|
||||
protected $entity_type = '';
|
||||
protected $tb_entity = "";
|
||||
protected $allow_richtext_fields = []; // only table's column fields in this list allowed to retain html tags, else strip all
|
||||
|
||||
|
||||
protected $base_filter_condition_keys = [
|
||||
'q' => '', // keyword search
|
||||
'q_options' => [
|
||||
'field_filters' => [],
|
||||
'fulltext_fields' => ['keywords'],
|
||||
'limit_result' => 2000
|
||||
], // q_options as in iSearch->find($keyword, array $field_filters = [], array $fulltext_fields = ["keywords"], $limit_result = 2000)
|
||||
'featured' => '', // 1|-1
|
||||
'status' => '', // 1|-1
|
||||
'excluded_ids' => null, // [id1, id2, ...] or null
|
||||
'included_ids' => null, // [id1, id2, ...] or null
|
||||
];
|
||||
|
||||
/* @var ?iSearch $iSearchModel */
|
||||
protected $iSearchModel;
|
||||
|
||||
public function __construct(
|
||||
$entity_type,
|
||||
$tb_entity = "",
|
||||
$iSearchModel = null,
|
||||
$allow_richtext_fields = []
|
||||
) {
|
||||
|
||||
$this->entity_type = $entity_type;
|
||||
|
||||
$this->db = get_db('', ENABLE_DB_DEBUG);
|
||||
$this->iSearchModel = $iSearchModel;
|
||||
$this->allow_richtext_fields = $allow_richtext_fields;
|
||||
|
||||
if($tb_entity) {
|
||||
$this->tb_entity = $tb_entity;
|
||||
}else{
|
||||
//auto infer
|
||||
$tb_name = str_replace("-", "_", preg_replace("/[^a-z0-9_\-]/i", "", $entity_type));
|
||||
$this->tb_entity = "tb_".strtolower($tb_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description to be extended by extending class
|
||||
* besides the $base_filter_condition_keys which allows to find items in ::getList($condition)
|
||||
* example:
|
||||
[
|
||||
'category' => array[],
|
||||
'brand' => array[],
|
||||
]
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function extendedFilterOptions() : array ;
|
||||
|
||||
|
||||
/**
|
||||
* @description utility to get all possible entity filter during module development
|
||||
* @return array
|
||||
*/
|
||||
public function getAllowedFilterConditionKeys(): array
|
||||
{
|
||||
return array_merge(
|
||||
$this->extendedFilterOptions(),
|
||||
$this->base_filter_condition_keys
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function setRichtextFields(array $allow_richtext_fields = []) {
|
||||
$this->allow_richtext_fields = $allow_richtext_fields;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @description
|
||||
* @param array $filter_condition
|
||||
* @return array|null array($string_where, $bind_types, $bind_values );
|
||||
*/
|
||||
protected function _buildQueryConditionExtend(array $filter_condition): ?array {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $sort_by
|
||||
* @return string
|
||||
*/
|
||||
protected function _buildQueryOrderBy(string $sort_by = "new") {
|
||||
return " `id` DESC ";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $item_info
|
||||
* @return array
|
||||
*/
|
||||
protected function formatItemInList(array $item_info) : array {
|
||||
return $item_info;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $item_info
|
||||
* @return array|null
|
||||
*/
|
||||
protected function formatItemInfo(array $item_info) : ?array {
|
||||
return $item_info;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getEntityType() : string {
|
||||
return $this->entity_type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description prevent xss attack by stripping all html tags from un-permitted fields (which mostly are)
|
||||
* - only fields in ::allow_richtext_fields can keep html tags. Example: description
|
||||
* - if value is array (example data=['title' => '', 'description' => html tags ]) ...
|
||||
then to keep `description`, both `data` and `description` must be in the ::allow_richtext_fields
|
||||
because this method checks on array value recursively
|
||||
* @param array $input_info
|
||||
* @return array
|
||||
*/
|
||||
protected function cleanRichtextFields(array $input_info) : array {
|
||||
$cleaned_info = [];
|
||||
foreach ($input_info as $key => $value) {
|
||||
if($value instanceof MysqlValue) {
|
||||
$cleaned_info[$key] = $value;
|
||||
} else {
|
||||
if(in_array($key, $this->allow_richtext_fields)) {
|
||||
$cleaned_info[$key] = (is_array($value)) ? $this->cleanRichtextFields($value) : $value;
|
||||
}else{
|
||||
$cleaned_info[$key] = (is_array($value)) ? $this->cleanRichtextFields($value) : strip_tags($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $cleaned_info;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getListByIds(array $list_id, array $condition = array()) : array
|
||||
{
|
||||
if(!sizeof($list_id)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
list($parameterized_ids, $bind_types) = create_bind_sql_parameter_from_value_list($list_id, 'int');
|
||||
|
||||
$where_clause = "";
|
||||
$bind_values = $list_id;
|
||||
if(isset($condition['status'])) {
|
||||
$where_clause .= " AND `status` = ? ";
|
||||
$bind_types[] = 'd';
|
||||
$bind_values[] = intval($condition['status']);
|
||||
}
|
||||
|
||||
$query = $this->db->runQuery(
|
||||
" SELECT * FROM ".$this->tb_entity." WHERE `id` IN (".$parameterized_ids.") ".$where_clause,
|
||||
$bind_types,
|
||||
$bind_values
|
||||
);
|
||||
|
||||
$item_list = [];
|
||||
foreach ($this->db->fetchAll($query) as $item) {
|
||||
$item_list[$item['id']] = $this->formatItemInList($item);
|
||||
}
|
||||
|
||||
return $item_list;
|
||||
}
|
||||
|
||||
|
||||
public function getInfo($id) : ?array
|
||||
{
|
||||
$query = $this->db->runQuery("SELECT * FROM `".$this->tb_entity."` WHERE `id` = ? LIMIT 1 ", ['d'], [$id]) ;
|
||||
if( $item_info = $this->db->fetchAssoc($query)){
|
||||
return $this->formatItemInfo($item_info);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description utility to inspect the actual filters which will be used in getList by Model
|
||||
* @param array $condition
|
||||
* @return array
|
||||
*/
|
||||
public function getQueryCondition(array $condition) : array
|
||||
{
|
||||
return $this->_buildQueryCondition($condition);
|
||||
}
|
||||
|
||||
|
||||
public function getTotal(array $condition) : int
|
||||
{
|
||||
list( $where_clause, $bind_types, $bind_values) = $this->_buildQueryCondition($condition);
|
||||
|
||||
$query = $this->db->runQuery(
|
||||
" SELECT COUNT(*) as total FROM `".$this->tb_entity."` WHERE 1 " . $where_clause,
|
||||
$bind_types, $bind_values
|
||||
);
|
||||
|
||||
$total = 0;
|
||||
if ($rs = $this->db->fetchAssoc($query)) {
|
||||
$total = $rs['total'];
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
|
||||
public function getList(array $condition) : array
|
||||
{
|
||||
list( $where_clause, $bind_types, $bind_values) = $this->_buildQueryCondition($condition);
|
||||
|
||||
//debug_var([$where_clause, $bind_types, $bind_values]);
|
||||
|
||||
$numPerPage = (isset($condition['numPerPage']) && $condition['numPerPage'] > 0 ) ? intval($condition['numPerPage']) : 20 ;
|
||||
$page = (isset($condition['page']) && $condition['page'] > 0 ) ? intval($condition['page']) : 1 ;
|
||||
|
||||
$sort_by = (isset($condition['sort_by']) && $condition['sort_by']) ? $condition['sort_by'] : 'new' ;
|
||||
$order_by = $this->_buildQueryOrderBy($sort_by);
|
||||
|
||||
$query = $this->db->runQuery(
|
||||
"SELECT * FROM ".$this->tb_entity." WHERE 1 ". $where_clause . "
|
||||
ORDER BY ".$order_by."
|
||||
LIMIT ".(($page-1) * $numPerPage).", ".$numPerPage ,
|
||||
$bind_types,
|
||||
$bind_values
|
||||
) ;
|
||||
|
||||
$item_list = array();
|
||||
$counter = ($page-1) * $numPerPage;
|
||||
|
||||
foreach ( $this->db->fetchAll($query) as $item_info ) {
|
||||
$counter += 1;
|
||||
|
||||
$item = $item_info;
|
||||
$item['counter'] = $counter;
|
||||
|
||||
$item_list[] = $this->formatItemInList($item);
|
||||
}
|
||||
|
||||
return $item_list;
|
||||
}
|
||||
|
||||
|
||||
//to avoid duplicate codes, extended class should implement this method for extra filter keys
|
||||
// which are not in the base filter ::_buildQueryConditionBase
|
||||
protected function _buildQueryCondition(array $filter_condition)
|
||||
{
|
||||
// these keys are for pagination and ordering list, which are commonly included in the $filter_condition
|
||||
$excluded_keys = [
|
||||
'numPerPage', 'page', 'sort_by'
|
||||
];
|
||||
|
||||
|
||||
$base_filter_conditions = [];
|
||||
$extend_filter_conditions = [];
|
||||
foreach ($filter_condition as $key => $value) {
|
||||
if(in_array($key, $excluded_keys)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(array_key_exists($key, $this->base_filter_condition_keys) ) {
|
||||
$base_filter_conditions[$key] = $value;
|
||||
}else{
|
||||
$extend_filter_conditions[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
list($base_where, $base_bind_types, $base_bind_values) = $this->_buildQueryConditionBase($base_filter_conditions);
|
||||
|
||||
list(
|
||||
$extend_where,
|
||||
$extend_bind_types,
|
||||
$extend_bind_values
|
||||
) = $this->_buildQueryConditionExtend($extend_filter_conditions) ?: [ '', null, null];
|
||||
|
||||
|
||||
if($extend_where) {
|
||||
return array(
|
||||
join(" ", [$base_where, $extend_where]),
|
||||
array_merge($base_bind_types, $extend_bind_types),
|
||||
array_merge($base_bind_values, $extend_bind_values),
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
$base_where,
|
||||
$base_bind_types,
|
||||
$base_bind_values
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected function _buildQueryConditionBase(array $filter_conditions)
|
||||
{
|
||||
/*
|
||||
$filter_conditions = [
|
||||
'q' => '', // keyword search
|
||||
'q_options' => [
|
||||
'field_filters' => [],
|
||||
'fulltext_fields' => ['keywords'],
|
||||
'limit_result' => 2000
|
||||
], // q_options as in iSearch->find($keyword, array $field_filters = [], array $fulltext_fields = ["keywords"], $limit_result = 2000)
|
||||
|
||||
'featured' => '', // 1|-1
|
||||
'status' => '', // 1|-1
|
||||
'excluded_ids' => [], // [id1, id2, ...] or null
|
||||
'included_ids' => [], // [id1, id2, ...] or null
|
||||
];
|
||||
*/
|
||||
|
||||
$catCondition = [];
|
||||
$bind_types = [];
|
||||
$bind_values = [];
|
||||
|
||||
//Tim kiem theo tu khoa
|
||||
if(isset($filter_conditions["q"]) && $filter_conditions["q"] && $this->iSearchModel ){
|
||||
|
||||
$q_options = $filter_conditions["q_options"] ?? [];
|
||||
$field_filters = $q_options['field_filters'] ?? [];
|
||||
$fulltext_fields = $q_options['fulltext_fields'] ?? [];
|
||||
$limit_result = $q_options['limit_result'] ?? 2000;
|
||||
|
||||
$match_result = $this->iSearchModel->find($filter_conditions["q"], $field_filters, $fulltext_fields, $limit_result);
|
||||
|
||||
list($parameterized_ids, $sub_bind_types) = create_bind_sql_parameter_from_value_list($match_result, 'int');
|
||||
|
||||
if($parameterized_ids) {
|
||||
$catCondition[] = " AND `id` IN (".$parameterized_ids.") ";
|
||||
$bind_types = array_merge($bind_types, $sub_bind_types);
|
||||
$bind_values = array_merge($bind_values, $match_result);
|
||||
}else{
|
||||
$catCondition[] = " AND `id` = -1 ";
|
||||
}
|
||||
}
|
||||
|
||||
//loc theo featured
|
||||
if(isset($filter_conditions["featured"]) && $filter_conditions["featured"]) {
|
||||
$catCondition[] = " AND `is_featured` = ? ";
|
||||
$bind_types[] = 'd';
|
||||
$bind_values[] = ($filter_conditions["featured"] == 1) ? 1 : 0;
|
||||
}
|
||||
|
||||
//loc theo status
|
||||
if(isset($filter_conditions["status"]) && $filter_conditions["status"]) {
|
||||
$catCondition[] = " AND `status` = ? ";
|
||||
$bind_types[] = 'd';
|
||||
$bind_values[] = ($filter_conditions["status"] == 1) ? 1 : 0;
|
||||
}
|
||||
|
||||
// excluded_ids
|
||||
if(isset($filter_conditions['excluded_ids']) && is_array($filter_conditions['excluded_ids']) ) {
|
||||
list($parameterized_ids, $sub_bind_types) = create_bind_sql_parameter_from_value_list($filter_conditions['excluded_ids'], 'int');
|
||||
|
||||
if($parameterized_ids) {
|
||||
$catCondition[] = " AND `id` NOT IN (".$parameterized_ids.") ";
|
||||
$bind_types = array_merge($bind_types, $sub_bind_types);
|
||||
$bind_values = array_merge($bind_values, $filter_conditions['excluded_ids']);
|
||||
}
|
||||
}
|
||||
|
||||
// included_ids
|
||||
if(isset($filter_conditions['included_ids']) && is_array($filter_conditions['included_ids']) ) {
|
||||
list($parameterized_ids, $sub_bind_types) = create_bind_sql_parameter_from_value_list($filter_conditions['included_ids'], 'int');
|
||||
|
||||
if($parameterized_ids) {
|
||||
$catCondition[] = " AND `id` IN (".$parameterized_ids.") ";
|
||||
$bind_types = array_merge($bind_types, $sub_bind_types);
|
||||
$bind_values = array_merge($bind_values, $filter_conditions['included_ids']);
|
||||
}else{
|
||||
$catCondition[] = " AND `id` = -1 ";
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
join(" ", $catCondition),
|
||||
$bind_types,
|
||||
$bind_values
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// get empty/default item for form
|
||||
public function getEmptyInfo($addition_field_value = []) : array
|
||||
{
|
||||
$table_info = $this->db->getTableInfo($this->tb_entity);
|
||||
$empty_info = [];
|
||||
foreach ($table_info as $field => $field_info) {
|
||||
$empty_info[$field] = ( in_array($field_info['DATA_TYPE'], ['int', 'float', 'mediumint', 'smallint', 'tinyint']) ) ? 0 : '' ;
|
||||
}
|
||||
|
||||
if(sizeof($addition_field_value)) {
|
||||
return array_merge($empty_info, $addition_field_value);
|
||||
}
|
||||
|
||||
return $empty_info;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
530
inc/Hura8/System/Model/aSearchBaseModel.php
Normal file
530
inc/Hura8/System/Model/aSearchBaseModel.php
Normal file
@@ -0,0 +1,530 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Model;
|
||||
|
||||
use Hura8\Interfaces\AppResponse;
|
||||
use Hura8\System\Language;
|
||||
use Hura8\Database\iConnectDB;
|
||||
use Hura8\Interfaces\iSearch;
|
||||
use Hura8\Traits\ClassCacheTrait;
|
||||
|
||||
abstract class aSearchBaseModel implements iSearch
|
||||
{
|
||||
|
||||
use ClassCacheTrait;
|
||||
|
||||
/* @var iConnectDB $db */
|
||||
protected $db;
|
||||
|
||||
protected $tb_main = "";
|
||||
protected $tb_fulltext = "";
|
||||
|
||||
// if false then search 'nhà tôi' = 'nha toi', if true then 'nhà tôi' != 'nha toi'
|
||||
// require rebuilding search if change this value
|
||||
protected $search_vietnamese = false;
|
||||
|
||||
// require rebuilding search if change this value
|
||||
protected $star_search = true; //values: 1|0, enable star search on fulltext: abc* -> matches: abcd, abc, abcde
|
||||
|
||||
// require rebuilding search if change this value
|
||||
protected $min_star_search_length = 2; //min star word length: result: abcd -> keywords: ab, abc, abcd NOT a
|
||||
|
||||
// define list of fields to be the filters
|
||||
protected $config_filter_fields = [
|
||||
//format: field_name => map table_name.field
|
||||
//'price' => "tb_product.price",
|
||||
//'quantity' => "tb_product.quantity",
|
||||
];
|
||||
|
||||
protected $config_fulltext_fields = [
|
||||
//format: field_name => map [table_name.field]
|
||||
//"product_keywords" => ["tb_product.title", "tb_product.model", "tb_product.sku"],
|
||||
//"category_keywords" => ["tb_category.title"],
|
||||
];
|
||||
|
||||
|
||||
public function __construct(
|
||||
$tb_main,
|
||||
array $config_fulltext_fields ,
|
||||
array $config_filter_fields = []
|
||||
) {
|
||||
|
||||
// ovewrite default
|
||||
if(defined('CONFIG_STAR_SEARCH')) $this->star_search = CONFIG_STAR_SEARCH;
|
||||
if(defined('CONFIG_STAR_SEARCH_MIN_LENGTH')) $this->min_star_search_length = CONFIG_STAR_SEARCH_MIN_LENGTH;
|
||||
if(defined('CONFIG_SEARCH_VIETNAMESE')) $this->search_vietnamese = CONFIG_SEARCH_VIETNAMESE;
|
||||
|
||||
$this->db = get_db('', ENABLE_DB_DEBUG);
|
||||
$this->config_fulltext_fields = $config_fulltext_fields;
|
||||
$this->config_filter_fields = $config_filter_fields;
|
||||
|
||||
$this->tb_main = $tb_main;
|
||||
$part_name = str_replace("tb_", "", preg_replace("/[^a-z0-9_]/i", "", $tb_main));
|
||||
$this->tb_fulltext = "tb_search_".strtolower($part_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description get filter fields
|
||||
* @param array[string]
|
||||
*/
|
||||
public function getFilterFields(): array {
|
||||
return $this->config_filter_fields;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description get fulltext fields
|
||||
* @param array[string]
|
||||
*/
|
||||
public function getFulltextFields(): array {
|
||||
return $this->config_fulltext_fields;
|
||||
}
|
||||
|
||||
|
||||
public function getSampleData() : array {
|
||||
$query = $this->db->runQuery(
|
||||
"SELECT * FROM `".$this->tb_fulltext."` ORDER BY `item_id` DESC LIMIT 10"
|
||||
);
|
||||
|
||||
return $this->db->fetchAll($query);
|
||||
}
|
||||
|
||||
|
||||
public function getTableName() : string {
|
||||
return $this->tb_fulltext;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $field_filters ["price" => [">", 100], "brand" => ["=", 1]]
|
||||
* @return array
|
||||
*/
|
||||
protected function _buildFieldFilters(array $field_filters = []) {
|
||||
$permit_operations = [">","=","<",">=","<=", "BETWEEN", "IN"];
|
||||
$where_clause = [];
|
||||
$bind_types = [];
|
||||
$bind_values = [];
|
||||
|
||||
foreach ($field_filters as $field => $info) {
|
||||
list($operation, $value) = $info;
|
||||
|
||||
$operation = strtoupper($operation);
|
||||
|
||||
if(!in_array(strtolower($operation), $permit_operations)) continue;
|
||||
|
||||
if($operation == 'BETWEEN') {
|
||||
// $value must be array(value_int_1, value_int_2)
|
||||
if(is_array($value) && sizeof($value) == 2) {
|
||||
list($value_1, $value_2) = $value;
|
||||
|
||||
if(is_int($value_1) && is_int($value_2) && $value_1 < $value_2) {
|
||||
$where_clause[] = " AND `".$field."` BETWEEN (?, ?) ";
|
||||
|
||||
$bind_types[] = "d";
|
||||
$bind_types[] = "d";
|
||||
|
||||
$bind_values[] = $value_1;
|
||||
$bind_values[] = $value_2;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if($operation == 'IN') {
|
||||
// $value must be array(value1, value2)
|
||||
if(is_array($value) && sizeof($value) > 0) {
|
||||
$parameterized = [];
|
||||
foreach ($value as $_v) {
|
||||
$parameterized[] = "?";
|
||||
$bind_types[] = "s";
|
||||
$bind_values[] = $_v;
|
||||
}
|
||||
|
||||
$where_clause[] = " AND `".$field."` IN (".join(",", $parameterized).") ";
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// default operation, comparison requires value to be a digit
|
||||
$where_clause[] = " AND `".$field."` ".$operation." ? ";
|
||||
$bind_types[] = "d";
|
||||
$bind_values[] = $value;
|
||||
}
|
||||
|
||||
return array(join(" ", $where_clause), $bind_types, $bind_values);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $keyword
|
||||
* @param array $field_filters ["price" => [">", 100], "brand" => ["=", 1]]
|
||||
* @param array $fulltext_fields ['title_keywords', "category_keywords"]
|
||||
* @param int $limit_result
|
||||
* @return int[]
|
||||
*/
|
||||
public function find($keyword, array $field_filters = [], array $fulltext_fields = [], $limit_result = 2000) : array
|
||||
{
|
||||
|
||||
if(!sizeof($fulltext_fields)) $fulltext_fields = array_keys($this->config_fulltext_fields);
|
||||
|
||||
$cache_key = md5("find-".$keyword);
|
||||
|
||||
return static::getCache($cache_key, function () use ($keyword, $field_filters, $fulltext_fields, $limit_result){
|
||||
|
||||
$keyword_clean = ($this->search_vietnamese) ? $this->make_text_search_vn($keyword) : $this->make_text_clean($keyword) ; //make_text_search_vn
|
||||
|
||||
$keyword_clean_ele = array_filter(explode(" ", $keyword_clean));
|
||||
$build_boolean_search = "+".join(" +", $keyword_clean_ele);
|
||||
|
||||
if(!$limit_result || $limit_result > 5000) $limit_result = 5000;
|
||||
$limit_condition = ($limit_result > 0) ? " LIMIT ".$limit_result : "";
|
||||
|
||||
list($where_clause, $bind_types, $bind_values) = $this->_buildFieldFilters($field_filters);
|
||||
|
||||
//validate search fields
|
||||
$validated_fulltext_fields = array_filter($fulltext_fields, function ($item) { return array_key_exists($item, $this->config_fulltext_fields); });
|
||||
|
||||
if(!sizeof($validated_fulltext_fields)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = $this->db->runQuery(
|
||||
"SELECT `item_id` FROM `".$this->tb_fulltext."`
|
||||
WHERE MATCH(".join(',', $validated_fulltext_fields).") AGAINST('".$build_boolean_search."' IN BOOLEAN MODE)
|
||||
".$where_clause."
|
||||
ORDER BY `item_id` DESC
|
||||
".$limit_condition."
|
||||
",
|
||||
$bind_types, $bind_values
|
||||
);
|
||||
|
||||
return array_map(function ($item){ return $item['item_id']; }, $this->db->fetchAll($query) );
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected function getItemInfo($item_id)
|
||||
{
|
||||
$query = $this->db->runQuery(
|
||||
"SELECT * FROM `".$this->tb_fulltext."` WHERE `item_id` = ? LIMIT 1",
|
||||
['d'], [$item_id]
|
||||
);
|
||||
|
||||
return $this->db->fetchAssoc($query);
|
||||
}
|
||||
|
||||
|
||||
protected function checkExist($item_id) : bool
|
||||
{
|
||||
$query = $this->db->runQuery(
|
||||
"SELECT `item_id` FROM `".$this->tb_fulltext."` WHERE `item_id` = ? LIMIT 1",
|
||||
['d'], [$item_id]
|
||||
);
|
||||
|
||||
return (bool) $this->db->fetchAssoc($query);
|
||||
}
|
||||
|
||||
|
||||
// get all table_fields to watch which will affect the search
|
||||
protected function getWatchTableFields() {
|
||||
$result = [];
|
||||
foreach ($this->config_filter_fields as $field => $table_field ) {
|
||||
$result[] = $table_field;
|
||||
}
|
||||
|
||||
foreach ($this->config_fulltext_fields as $field => $table_fields ) {
|
||||
$result = array_merge($result, $table_fields);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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",
|
||||
|
||||
"tb_product.field_not_be_update" => "Mmodel",
|
||||
];
|
||||
* @return AppResponse
|
||||
*/
|
||||
public function updateItem($item_id, array $table_field_values = []) : AppResponse
|
||||
{
|
||||
|
||||
$fulltext_fields = [];
|
||||
foreach ($this->config_fulltext_fields as $fulltext_field => $fulltext_map_fields ) {
|
||||
foreach ($fulltext_map_fields as $_table_field) {
|
||||
if(array_key_exists($_table_field, $table_field_values)) {
|
||||
$fulltext_fields[$fulltext_field][$_table_field] = $table_field_values[$_table_field];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//echo "fulltext_fields";
|
||||
//debug_var($fulltext_fields);
|
||||
//echo "<br />";
|
||||
|
||||
$field_filters = [];
|
||||
foreach ($this->config_filter_fields as $filter_field => $filter_map_field ) {
|
||||
if(array_key_exists($filter_map_field, $table_field_values)) {
|
||||
$field_filters[$filter_field] = $table_field_values[$filter_map_field];
|
||||
}
|
||||
}
|
||||
|
||||
//echo "field_filters";
|
||||
//debug_var($field_filters);
|
||||
|
||||
// nothing to update
|
||||
if(!sizeof($fulltext_fields) && !sizeof($field_filters)) {
|
||||
return new AppResponse('error', "nothing to update" );
|
||||
}
|
||||
|
||||
|
||||
// create entry if not exist
|
||||
if(!$this->checkExist($item_id)) {
|
||||
$this->db->insert($this->tb_fulltext, ['item_id' => $item_id, 'create_time' => CURRENT_TIME]);
|
||||
}
|
||||
|
||||
$current_info = $this->getItemInfo($item_id);
|
||||
if(!$current_info) {
|
||||
return new AppResponse('error', "Cannot find record for ".$item_id );
|
||||
}
|
||||
|
||||
// update
|
||||
$updated_fulltext = [];
|
||||
|
||||
foreach ($this->config_fulltext_fields as $_filter_field => $_n ) {
|
||||
if(!isset($fulltext_fields[$_filter_field])) continue;
|
||||
|
||||
$current_filter_field_base = ($current_info[$_filter_field."_base"]) ? \json_decode($current_info[$_filter_field."_base"], true) : [];
|
||||
|
||||
$new_filter_field_base = $current_filter_field_base;
|
||||
|
||||
foreach ($fulltext_fields[$_filter_field] as $_table_field => $_new_value) {
|
||||
$new_filter_field_base[$_table_field] = $_new_value;
|
||||
}
|
||||
|
||||
if(json_encode($new_filter_field_base) != json_encode($current_filter_field_base)) {
|
||||
$updated_fulltext[$_filter_field."_base"] = $new_filter_field_base;
|
||||
$updated_fulltext[$_filter_field] = ($this->search_vietnamese) ? $this->make_text_search_vn(join(" ", array_values($new_filter_field_base))) : $this->make_text_clean(join(" ", array_values($new_filter_field_base)));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$updated_filters = [];
|
||||
foreach ($this->config_filter_fields as $_filter_field => $_table_field ) {
|
||||
if(!isset($field_filters[$_filter_field])) continue;
|
||||
|
||||
if($field_filters[$_filter_field] != $current_info[$_filter_field] ) {
|
||||
$updated_filters[$_filter_field] = $field_filters[$_filter_field];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// nothing to update
|
||||
if(!sizeof($updated_filters) && !sizeof($updated_fulltext)) {
|
||||
return new AppResponse('error', "nothing to update" );
|
||||
}
|
||||
|
||||
$updated_info = array_merge($updated_filters, $updated_fulltext);
|
||||
$updated_info['last_update'] = CURRENT_TIME;
|
||||
|
||||
//echo "updated_info = <br /> ";
|
||||
//debug_var($updated_info);
|
||||
|
||||
$this->db->update(
|
||||
$this->tb_fulltext,
|
||||
$updated_info,
|
||||
['item_id' => $item_id]
|
||||
);
|
||||
|
||||
return new AppResponse('ok');
|
||||
}
|
||||
|
||||
public function deleteItem($item_id) : AppResponse
|
||||
{
|
||||
$this->db->runQuery(
|
||||
"DELETE FROM `".$this->tb_fulltext."` WHERE `item_id` = ? LIMIT 1",
|
||||
['d'], [$item_id]
|
||||
);
|
||||
|
||||
return new AppResponse('ok');
|
||||
}
|
||||
|
||||
public function deleteItems(array $item_list_ids) : AppResponse
|
||||
{
|
||||
list($parameterized_ids, $bind_types) = create_bind_sql_parameter_from_value_list($item_list_ids, "int");
|
||||
|
||||
$this->db->runQuery(
|
||||
"DELETE FROM `".$this->tb_fulltext."` WHERE `item_id` IN (".$parameterized_ids.") ",
|
||||
$bind_types,
|
||||
$item_list_ids
|
||||
);
|
||||
|
||||
return new AppResponse('ok');
|
||||
}
|
||||
|
||||
public function recreateTableSearch() {
|
||||
if($this->db->checkTableExist($this->tb_fulltext)) {
|
||||
$this->db->runQuery(
|
||||
"DROP TABLE `".$this->tb_fulltext."` "
|
||||
);
|
||||
}
|
||||
|
||||
$this->createTableSearch();
|
||||
}
|
||||
|
||||
public function createTableSearch() {
|
||||
// check if table exist
|
||||
if($this->db->checkTableExist($this->tb_fulltext)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!$this->db->checkTableExist($this->tb_main)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = "CREATE TABLE `".$this->tb_fulltext."` ( `item_id` INT(11) UNSIGNED NOT NULL DEFAULT '0', ";
|
||||
|
||||
// add fields
|
||||
|
||||
foreach ($this->config_filter_fields as $_filter_field => $_map_table_field ) {
|
||||
list($_table, $_f) = explode(".", $_map_table_field);
|
||||
|
||||
$_table_info = $this->db->getTableInfo($_table);
|
||||
|
||||
$column_default = $_table_info[$_f]['COLUMN_DEFAULT'];
|
||||
|
||||
$default_value = (in_array($_table_info[$_f]['DATA_TYPE'], ['mediumint', 'int', 'tinyint', 'double', 'float'])) ? 0 : $column_default;
|
||||
|
||||
$sql .= " `".$_filter_field."` ".$_table_info[$_f]['COLUMN_TYPE']." NOT NULL DEFAULT '".$default_value."', ";
|
||||
}
|
||||
|
||||
foreach ($this->config_fulltext_fields as $_filter_field => $_t ) {
|
||||
$sql .= " `".$_filter_field."` TEXT NULL , ";
|
||||
|
||||
// create field-data to compare new values before re-indexing
|
||||
$sql .= " `".$_filter_field."_base` TEXT NULL , ";
|
||||
}
|
||||
|
||||
$sql .= " `create_time` int(11) NOT NULL DEFAULT '0', ";
|
||||
$sql .= " `last_update` int(11) NOT NULL DEFAULT '0', ";
|
||||
|
||||
// index
|
||||
|
||||
foreach ($this->config_filter_fields as $_filter_field => $_data_type ) {
|
||||
$sql .= " INDEX (`".$_filter_field."`), ";
|
||||
}
|
||||
|
||||
// fulltext on separate columns
|
||||
foreach ($this->config_fulltext_fields as $_filter_field => $_t ) {
|
||||
$sql .= " FULLTEXT INDEX (`".$_filter_field."` ) , ";
|
||||
}
|
||||
|
||||
// create full text for all columns combined, so we can do query match(col_1, col_2) against keyword
|
||||
$_filter_fields = array_keys($this->config_fulltext_fields);
|
||||
if(sizeof($_filter_fields)) $sql .= " FULLTEXT INDEX (".join(", ", $_filter_fields).") , ";
|
||||
|
||||
$sql .= " PRIMARY KEY ( `item_id` ) ";
|
||||
$sql .= " ) ENGINE=InnoDB DEFAULT CHARSET=utf8";
|
||||
|
||||
//return $sql;
|
||||
$this->db->runQuery($sql);
|
||||
|
||||
// add foreign key constraint
|
||||
$this->db->runQuery("
|
||||
ALTER TABLE `".$this->tb_fulltext."` ADD FOREIGN KEY (`item_id`) REFERENCES `".$this->tb_main."`(`id`) ON DELETE CASCADE;
|
||||
");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//11-11-2012 chuyen text thanh tieng viet voi dau chuyen doi: vd. nhà = nhas
|
||||
//because mysql allow fulltext search with minimum 4 characters, we have a trick: add hura to everyone what has length < 4
|
||||
protected function make_text_search_vn($text, $enable_star = false){
|
||||
$text = Language::convertText($text);
|
||||
$text = preg_replace("@[^a-z0-9\s]@si", " ", strtolower($text));
|
||||
//$text = str_replace(" "," ",$text);
|
||||
$text_ele = array_filter(explode(" ", $text));
|
||||
$new_text = "";
|
||||
foreach($text_ele as $ele) {
|
||||
if($ele == '/' ) continue; //skip this character if it stands alone
|
||||
|
||||
if(strlen($ele) < 4 || $ele == 'plus') $new_text .= " hura".$ele;
|
||||
else $new_text .= " ".$ele;
|
||||
}
|
||||
return trim($new_text);
|
||||
}
|
||||
|
||||
//11-11-2012 chuyen text thanh tieng viet khong dau: vd. nhà = nha
|
||||
/*
|
||||
@variable:
|
||||
- $text: text to be index
|
||||
|
||||
//16-11-2012
|
||||
- $enable_star:
|
||||
true: allow search abcd by word: ab or abc
|
||||
false: not enabled
|
||||
- $min_len: if $enable_star = true, abcd -> tao thanh: ab, abd, abcd (if $min_len = 2)
|
||||
*/
|
||||
protected function make_text_clean($_text, $enable_star = false){
|
||||
//$text = Language::chuyenKhongdau(Language::convert_lower($text));
|
||||
$text = Language::chuyenKhongdau($_text);
|
||||
|
||||
$text = preg_replace("@[^a-z0-9\s]@si", " ", strtolower($text));
|
||||
|
||||
$text_ele = array_filter(explode(" ", $text));
|
||||
$new_text = "";
|
||||
foreach($text_ele as $ele) {
|
||||
|
||||
if($this->star_search && $enable_star) {
|
||||
|
||||
$word_list = static::create_star_search($ele, $this->min_star_search_length);
|
||||
foreach($word_list as $new_ele) {
|
||||
if(strlen($new_ele) < 4 || $ele == 'plus') $new_text .= " hura".$new_ele;
|
||||
else $new_text .= " ".$new_ele;
|
||||
}
|
||||
|
||||
}else{
|
||||
if(strlen($ele) < 4 || $ele == 'plus') $new_text .= " hura".$ele;
|
||||
else $new_text .= " ".$ele;
|
||||
}
|
||||
}
|
||||
return trim($new_text);
|
||||
}
|
||||
|
||||
//16-11-2012
|
||||
//create all possible combination for a word for star search: abcd -> tao thanh: ab, abd, abcd, bc, bcd, cd (if $min_len = 2)
|
||||
protected function create_star_search($word, $min_len = 2){
|
||||
if($min_len < 2) $min_len = 2; //in case missing value for CONFIG_STAR_SEARCH_MIN_LENGTH
|
||||
$word_len = strlen($word);
|
||||
$result = array();
|
||||
if($word_len <= $min_len) return array($word);
|
||||
for($i = $min_len; $i < $word_len; $i ++ ){
|
||||
$result[] = substr($word, 0, $i);
|
||||
}
|
||||
$result[] = $word;
|
||||
|
||||
//09-01-2012
|
||||
//reduce $word 1 character to create new word and create combination from there
|
||||
$new_word = substr($word, 1);
|
||||
$new_result = self::create_star_search($new_word, $min_len = 2);
|
||||
foreach($new_result as $el){
|
||||
$result[] = $el;
|
||||
}
|
||||
|
||||
return array_unique($result);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user