457 lines
14 KiB
PHP
457 lines
14 KiB
PHP
|
|
<?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;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
}
|