c
This commit is contained in:
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user