'', // 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; } }