c
This commit is contained in:
152
inc/Hura8/System/APIClient.php
Normal file
152
inc/Hura8/System/APIClient.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by Glee Ltd.
|
||||
* User: Hieu
|
||||
* Date: 22-Apr-19
|
||||
* Time: 11:18 AM
|
||||
* Description:
|
||||
*/
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use GuzzleHttp\Exception\ServerException;
|
||||
|
||||
final class APIClient
|
||||
{
|
||||
private $headers = [];
|
||||
|
||||
/* @var $client \GuzzleHttp\Client */
|
||||
protected $client;
|
||||
|
||||
|
||||
public function __construct($endpoint, $timeout = 10)
|
||||
{
|
||||
$this->client = new Client([
|
||||
// Base URI is used with relative requests
|
||||
'base_uri' => $endpoint,
|
||||
// You can set any number of default request options.
|
||||
'timeout' => $timeout,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function post($path, array $parameters) {
|
||||
return $this->call_service("post", $path, $parameters);
|
||||
}
|
||||
|
||||
|
||||
public function get($path, array $parameters) {
|
||||
return $this->call_service("get", $path, $parameters);
|
||||
}
|
||||
|
||||
|
||||
public function setHeaders(array $headers) {
|
||||
// Authorization:
|
||||
/*$headers = [
|
||||
'Authorization' => "Basic ". base64_encode($this->api_key),
|
||||
];*/
|
||||
$this->headers = $headers;
|
||||
}
|
||||
|
||||
//ref: http://docs.guzzlephp.org/en/stable/quickstart.html#query-string-parameters
|
||||
// make a consistent call with the same $payload format: ["key" => "value", ...]
|
||||
/*
|
||||
return json {
|
||||
"errCode" => 1|0, (1=error, 0=success)
|
||||
"msg" => "error_message" | "",
|
||||
}*/
|
||||
protected function call_service($http_method, $path, array $payload) {
|
||||
|
||||
try {
|
||||
|
||||
// get
|
||||
if( $http_method == 'get' ) {
|
||||
|
||||
$response = $this->client->request('GET', $path, [
|
||||
'headers' => $this->headers,
|
||||
'query' => $payload
|
||||
]);
|
||||
}
|
||||
|
||||
// post
|
||||
else {
|
||||
|
||||
$request_options = $this->buildPostRequestOptions($this->headers, $payload);
|
||||
$response = $this->client->request('POST', $path, $request_options);
|
||||
}
|
||||
|
||||
return [
|
||||
"errCode" => "0",
|
||||
"msg" => $response->getBody()->getContents(),
|
||||
];
|
||||
|
||||
}
|
||||
catch (ServerException $e) {
|
||||
$response = $e->getResponse();
|
||||
//$errors = \json_decode($response->getBody()->getContents(), true);
|
||||
return [
|
||||
"errCode" => 1,
|
||||
"msg" => $response->getBody()->getContents(),
|
||||
];
|
||||
}
|
||||
catch (ClientException $e) {
|
||||
$response = $e->getResponse();
|
||||
//$errors = \json_decode($response->getBody()->getContents(), true);
|
||||
return [
|
||||
"errCode" => 2,
|
||||
"msg" => $response->getBody()->getContents(),
|
||||
];
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
return [
|
||||
"errCode" => 3,
|
||||
"msg" => "Exception: ".$e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ref: http://docs.guzzlephp.org/en/stable/request-options.html
|
||||
// form_params cannot be used with the multipart option. You will need to use one or the other.
|
||||
// Use form_params for application/x-www-form-urlencoded requests, and multipart for multipart/form-data requests.
|
||||
protected function buildPostRequestOptions(array $headers, array $payload) {
|
||||
$content_type = isset($headers["Content-Type"]) ? $headers["Content-Type"] : "";
|
||||
|
||||
if($content_type == "application/x-www-form-urlencoded") {
|
||||
return [
|
||||
'headers' => $headers,
|
||||
'form_params' => $payload,
|
||||
//'debug' => true
|
||||
];
|
||||
}
|
||||
|
||||
if($content_type == "application/json") {
|
||||
return [
|
||||
'headers' => $headers,
|
||||
'json' => $payload,
|
||||
//'body' => json_encode($payload),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
// reformat the payload for multipart
|
||||
$multipart_request = [];
|
||||
foreach ($payload as $key => $value) {
|
||||
if( ! $key) continue;
|
||||
|
||||
$multipart_request[] = [
|
||||
'name' => $key,
|
||||
'contents' => (is_array($value)) ? json_encode($value) : $value,
|
||||
];
|
||||
}
|
||||
|
||||
// multipart/form-data
|
||||
return [
|
||||
'headers' => $headers,
|
||||
'multipart' => $multipart_request,
|
||||
];
|
||||
|
||||
}
|
||||
}
|
||||
152
inc/Hura8/System/CDNFileUpload.php
Normal file
152
inc/Hura8/System/CDNFileUpload.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by Hurasoft.
|
||||
* Date: 28-May-2022
|
||||
* Description: Use this class to send local files to the cdn host (endpoint: cdn.host/file_upload_handle.php). The cdn host also has a copy of Hura8 main class to receive the uploaded files
|
||||
*/
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
|
||||
class CDNFileUpload {
|
||||
|
||||
//protected $upload_handle = 'http://local.hura8/file_upload_handle.php';
|
||||
protected $upload_handle = STATIC_DOMAIN . '/file_upload_handle.php';
|
||||
|
||||
protected $user_id = 0;
|
||||
|
||||
protected $target_path_item_type_mapping = [
|
||||
'product' => 'media/product', // media/product
|
||||
'article' => 'media/news', // media/news
|
||||
'brand' => 'media/brand',
|
||||
'category' => 'media/category',
|
||||
'banner' => 'media/banner',
|
||||
'lib' => 'media/lib/',
|
||||
'user_upload' => 'media/user_upload',
|
||||
];
|
||||
|
||||
public function __construct($user_id) {
|
||||
$this->user_id = $user_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: an implementation of start method to upload an item's image
|
||||
* @param $item_type string
|
||||
* @param $content string
|
||||
* @param $file_name string
|
||||
* @return array | boolean
|
||||
*/
|
||||
public function uploadFile($item_type, $file_name, $content, $set_target_path = '')
|
||||
{
|
||||
//$file_name = substr(strrchr($img_url, "/"), 1);
|
||||
//$content = get_url_content($img_url);
|
||||
|
||||
$header_setting = [
|
||||
// 'Authorization' => "Basic ". base64_encode(API_KEY),
|
||||
];
|
||||
|
||||
if($set_target_path) {
|
||||
$target_path = $set_target_path;
|
||||
}else{
|
||||
$target_path = (isset($this->target_path_item_type_mapping[$item_type])) ? $this->target_path_item_type_mapping[$item_type] : '';
|
||||
}
|
||||
|
||||
$multipart_settings = [
|
||||
[
|
||||
'name' => 'upload_method',
|
||||
'contents' => 'content',
|
||||
],
|
||||
[
|
||||
'name' => 'file_name',
|
||||
'contents' => $file_name,
|
||||
],
|
||||
[
|
||||
'name' => 'target_path',
|
||||
'contents' => $target_path,
|
||||
],
|
||||
[
|
||||
'name' => 'file_content',
|
||||
'contents' => $content,
|
||||
],
|
||||
|
||||
//for upload server
|
||||
[
|
||||
'name' => 'uid',
|
||||
'contents' => $this->user_id,
|
||||
],
|
||||
[
|
||||
'name' => 'time',
|
||||
'contents' => CURRENT_TIME,
|
||||
],
|
||||
[
|
||||
'name' => 'token',
|
||||
'contents' => $this->createToken($this->user_id, CURRENT_TIME),
|
||||
],
|
||||
[
|
||||
'name' => 'item_type',
|
||||
'contents' => $item_type ,
|
||||
],
|
||||
];
|
||||
|
||||
return $this->start($header_setting, $multipart_settings);
|
||||
}
|
||||
|
||||
/*
|
||||
let settings = {
|
||||
element : '',//element id to click on
|
||||
holder : '', //id file holder
|
||||
form_name : '',
|
||||
item_type : '',
|
||||
item_id : '',
|
||||
file_type : '', //what is the file type: doc, photo, video ?
|
||||
file_field : '', //what field in the item table this file is used for ?
|
||||
resize : '200,400,600' //sizes to resize
|
||||
square: '200,400', //sizes to be square
|
||||
keep_width : '400', //sizes to keep the width
|
||||
max_file: 1
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description: a general method for upload
|
||||
* @param array $header_setting
|
||||
* @param array $multipart_settings
|
||||
* @return array | boolean
|
||||
* @throws \GuzzleHttp\Exception\GuzzleException
|
||||
*/
|
||||
protected function start(array $header_setting = [], array $multipart_settings = []) {
|
||||
$client = new \GuzzleHttp\Client();
|
||||
|
||||
$request = $client->request('POST', $this->upload_handle, [
|
||||
'headers' => $header_setting,
|
||||
'multipart' => $multipart_settings
|
||||
]);
|
||||
|
||||
return $request->getBody()->getContents();
|
||||
}
|
||||
|
||||
|
||||
// return string | boolean
|
||||
protected function getAssetType($file_name) {
|
||||
$ext = strtolower(strrchr($file_name, "."));
|
||||
|
||||
if( $ext == '.css') return 'css';
|
||||
|
||||
if( $ext == '.js') return 'js';
|
||||
|
||||
if( \in_array($ext, ['.jpg', '.jpeg', '.gif', '.png', '.ico'])) return 'image';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// this salt and createToken must be the same ones as in Hura8/Base/CDNFileUploadHandle
|
||||
// and different per project (or else others will use it to upload forbidden files on our clients' hosting)
|
||||
const SECURITY_SALT = 'ahss@3asdaaSDFSD';
|
||||
|
||||
protected function createToken($id, $time) {
|
||||
return sha1(join("-", [$id , $time , static::SECURITY_SALT]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
275
inc/Hura8/System/CDNFileUploadHandle.php
Normal file
275
inc/Hura8/System/CDNFileUploadHandle.php
Normal file
@@ -0,0 +1,275 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by Hurasoft
|
||||
* Date: 28-May-2022
|
||||
* Time: 1:44 PM
|
||||
* Description: Use this class in the cdn host to handle the upload of files from Hura8's admin and put the uploaded file in the cdn host's local directory
|
||||
*/
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
|
||||
class CDNFileUploadHandle
|
||||
{
|
||||
|
||||
const SECURITY_SALT = 'ahss@3asdaaSDFSD';
|
||||
|
||||
//default config
|
||||
protected $config = [
|
||||
//1. file settings
|
||||
"max_file_size" => 2000000,//bytes ~ 1MB
|
||||
'allowed_file_types' => [
|
||||
'image' => ['.jpeg', '.jpg', '.gif', '.png'],
|
||||
'script' => ['.css', '.js'],
|
||||
],
|
||||
|
||||
//2. uploaded by client
|
||||
'uid' => '',
|
||||
'token' => '',
|
||||
'time' => '',
|
||||
'item_type' => 'product', // product|article|media
|
||||
'target_path' => '',
|
||||
'upload_method' => 'content', //file || content
|
||||
"file_content" => "", //only needed if upload_method = content
|
||||
"file_name" => "",//only needed if upload_method = content
|
||||
];
|
||||
private $tmp_file_prop = null; //array, temporary file's props in tmp folder
|
||||
private $accepted_file_input_names = [
|
||||
"file", //<input type=file name="file">
|
||||
//"qqfile", //name use for files uploaded through FineUploader library
|
||||
];
|
||||
private $user_tmp_dir = '';
|
||||
|
||||
public function __construct(){
|
||||
|
||||
}
|
||||
|
||||
protected function createToken($id, $time) {
|
||||
return sha1(join("-", [$id , $time , static::SECURITY_SALT]));
|
||||
}
|
||||
|
||||
public function start() {
|
||||
|
||||
$this->_get_post_info();
|
||||
|
||||
if(!$this->validateUser()) {
|
||||
return $this->set_return_result('error', 'User failed to verify', []);
|
||||
}
|
||||
|
||||
//receive files
|
||||
$file = $this->receive_file();
|
||||
|
||||
//return file-location to upload API to return to client application
|
||||
return $this->set_return_result('success', 'Upload succeeded', $file );
|
||||
}
|
||||
|
||||
protected function validateUser() {
|
||||
return ($this->config['token'] == $this->createToken($this->config['uid'], $this->config['time']));
|
||||
}
|
||||
|
||||
protected function _get_post_info() {
|
||||
$expected_keys = [
|
||||
'upload_method',
|
||||
'file_name',
|
||||
'target_path',
|
||||
'file_content',
|
||||
'uid',
|
||||
'time',
|
||||
'token',
|
||||
'item_type',
|
||||
];
|
||||
|
||||
foreach ($expected_keys as $key) {
|
||||
$this->config[$key] = isset($_POST[$key]) ? $_POST[$key] : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//return boolean
|
||||
protected function remove_tmp_file() {
|
||||
$this->deleteDirectory($this->user_tmp_dir);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//return boolean
|
||||
protected function validate_file() {
|
||||
//$this->tmp_file_prop
|
||||
//validate file size
|
||||
if($this->config['max_file_size'] && $this->config['max_file_size'] < $this->tmp_file_prop['size']) {
|
||||
return 'Size too large';
|
||||
}
|
||||
|
||||
//validate allowed extension: allow image but upload .docx files
|
||||
$has_extension = false;
|
||||
$file_type = '';
|
||||
foreach ( $this->config['allowed_file_types'] as $group => $group_ext ) {
|
||||
if(in_array( $this->tmp_file_prop['ext'], $group_ext )) {
|
||||
$has_extension = true;
|
||||
$file_type = $group;
|
||||
break ;
|
||||
}
|
||||
}
|
||||
if( ! $has_extension ) return "File type not allowed";
|
||||
|
||||
//validate claimed extension: claim image but not actual image
|
||||
$full_file_path = $this->tmp_file_prop['tmp_location'] . DIRECTORY_SEPARATOR . $this->tmp_file_prop['name'];
|
||||
if( $file_type == 'image' && ! v::image()->validate( $full_file_path )) {
|
||||
return "Not actual image";
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
//return mixed : array $tmp_file_prop or false
|
||||
protected function receive_file() {
|
||||
|
||||
//check upload method
|
||||
if( $this->config['upload_method'] == 'content' ) {
|
||||
//upload by content - file created on server
|
||||
$file_ext = $this->get_ext($this->config['file_name']);
|
||||
$upload_folder = $this->create_upload_folder() ;
|
||||
|
||||
if( ! $this->move_file($upload_folder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
"ext" => $file_ext,
|
||||
"name" => $this->config['file_name'],
|
||||
"folder" => $upload_folder,
|
||||
"size" => strlen($this->config['file_content']),
|
||||
];
|
||||
|
||||
} else {
|
||||
|
||||
//upload by file
|
||||
//get list of files to be uploaded
|
||||
$file_uploaded = null;
|
||||
foreach ($this->accepted_file_input_names as $_name) {
|
||||
if(isset($_FILES[$_name])) {
|
||||
$file_uploaded = $_FILES[$_name];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $file_uploaded ) return false;
|
||||
|
||||
//if file name too long, then error
|
||||
if ( strlen($file_uploaded['name']) > 225 ) return false;
|
||||
|
||||
//move file to tmp folder
|
||||
$file_ext = $this->get_ext($file_uploaded['name']);
|
||||
$upload_folder = $this->create_upload_folder() ;
|
||||
|
||||
if( ! $this->move_file($upload_folder) ){
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
"ext" => $file_ext,
|
||||
"name" => $this->config['file_name'],
|
||||
"folder" => $upload_folder,
|
||||
"size" => $file_uploaded['size'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function set_return_result($status = 'success', $message = 'Upload success', $content = []) {
|
||||
return [
|
||||
"status" => $status,
|
||||
"message" => $message,
|
||||
"files" => $content,
|
||||
];
|
||||
}
|
||||
|
||||
//create upload folder for user: use current date, user id or app id based on $this->config
|
||||
private function create_upload_folder() {
|
||||
//$build_folder = [$this->tmp_dir];
|
||||
/*$user_id = (isset($this->config['user_id'])) ? intval($this->config['user_id']) : null;
|
||||
if($user_id) {
|
||||
$build_folder[] = $user_id;
|
||||
}
|
||||
$build_folder[] = date("Ymd");*/
|
||||
|
||||
//rebuild upload folder
|
||||
$build_folder = array_filter(explode("/", $this->config['target_path']));
|
||||
$folder = join("/", $build_folder);
|
||||
$this->createDir($folder);
|
||||
|
||||
return $folder;
|
||||
}
|
||||
|
||||
//get a file's extension
|
||||
private function get_ext($fileName){
|
||||
return strtolower(strrchr($fileName, '.'));
|
||||
}
|
||||
|
||||
// check if find is image
|
||||
private function isImage($fileName) {
|
||||
return (in_array( $this->get_ext($fileName), ['.jpeg', '.jpg', '.gif', '.png'] ));
|
||||
}
|
||||
|
||||
|
||||
//for security reason: only allow a-z0-9_- in file name (no other dot . except for the extension)
|
||||
//example: bad-file.php.js -> bad-filephp.js
|
||||
private function rename_file($uploaded_file_name, $ext) {
|
||||
$new_name = preg_replace("/[^a-z0-9_\-]/i", "", str_replace( strrchr($uploaded_file_name, '.'), "", $uploaded_file_name ) ) . $ext;
|
||||
|
||||
return strtolower($new_name);
|
||||
}
|
||||
|
||||
|
||||
private function move_file($folder) {
|
||||
$new_file = $folder . '/' . $this->config['file_name'];
|
||||
|
||||
if( $this->config['upload_method'] == 'content' ) {
|
||||
// image upload by content
|
||||
if( $this->isImage($new_file) ) {
|
||||
//Store in the filesystem.
|
||||
$fp = fopen($new_file, "w+");
|
||||
$status = fwrite($fp, $this->config['file_content']);
|
||||
fclose($fp);
|
||||
|
||||
return ($status !== false);
|
||||
}
|
||||
// file upload
|
||||
else if( file_put_contents($new_file, $this->config['file_content'])){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected function createDir($path, $folder_permission = 0750){
|
||||
if(file_exists($path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return mkdir($path, $folder_permission, true);
|
||||
}
|
||||
|
||||
protected function deleteDirectory($dirPath) {
|
||||
if (!is_dir($dirPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$objects = scandir($dirPath);
|
||||
foreach ($objects as $object) {
|
||||
if ($object != "." && $object !="..") {
|
||||
if (filetype($dirPath . DIRECTORY_SEPARATOR . $object) == "dir") {
|
||||
$this->deleteDirectory($dirPath . DIRECTORY_SEPARATOR . $object);
|
||||
} else {
|
||||
unlink($dirPath . DIRECTORY_SEPARATOR . $object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rmdir($dirPath);
|
||||
}
|
||||
|
||||
}
|
||||
100
inc/Hura8/System/Cache.php
Normal file
100
inc/Hura8/System/Cache.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
|
||||
use Symfony\Component\Cache\Adapter\RedisAdapter;
|
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
|
||||
|
||||
class Cache
|
||||
{
|
||||
|
||||
private static $redisCache = null;
|
||||
private static $fileCache = null;
|
||||
|
||||
protected function __construct() { }
|
||||
|
||||
|
||||
public static function getFileInstance(): FilesystemAdapter {
|
||||
|
||||
if(static::$fileCache instanceof FilesystemAdapter) {
|
||||
return static::$fileCache;
|
||||
}
|
||||
|
||||
static::$fileCache = new FilesystemAdapter(
|
||||
|
||||
// a string used as the subdirectory of the root cache directory, where cache
|
||||
// items will be stored
|
||||
$namespace = '',
|
||||
|
||||
// the default lifetime (in seconds) for cache items that do not define their
|
||||
// own lifetime, with a value 0 causing items to be stored indefinitely (i.e.
|
||||
// until the files are deleted)
|
||||
$defaultLifetime = 30,
|
||||
|
||||
// the main cache directory (the application needs read-write permissions on it)
|
||||
// if none is specified, a directory is created inside the system temporary directory
|
||||
$directory = CACHE_FILE_SYSTEM_DIRECTORY
|
||||
);
|
||||
|
||||
return static::$fileCache;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return RedisAdapter | false
|
||||
*/
|
||||
public static function getRedisInstance() {
|
||||
|
||||
if(!is_null(static::$redisCache)) {
|
||||
return static::$redisCache;
|
||||
}
|
||||
|
||||
try {
|
||||
// pass a single DSN string to register a single server with the client
|
||||
$redisConnection = RedisAdapter::createConnection( CACHE_REDIS_DSN , [
|
||||
//'read_timeout' => 10,
|
||||
//'retry_interval' => 2,
|
||||
/*
|
||||
'persistent' => 1,
|
||||
'persistent_id' => null,
|
||||
'timeout' => 10,
|
||||
|
||||
'tcp_keepalive' => 0,
|
||||
'lazy' => null,
|
||||
'redis_cluster' => false,
|
||||
'redis_sentinel' => null,*/
|
||||
] );
|
||||
|
||||
//var_dump($redisConnection->isConnected());
|
||||
|
||||
}catch (\Exception $exception) {
|
||||
//echo $exception->getMessage();
|
||||
$redisConnection = false;
|
||||
}
|
||||
|
||||
if(!$redisConnection) {
|
||||
static::$redisCache = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
static::$redisCache = new RedisAdapter(
|
||||
// the object that stores a valid connection to your Redis system
|
||||
$redisConnection,
|
||||
|
||||
// the string prefixed to the keys of the items stored in this cache
|
||||
$namespace = '',
|
||||
|
||||
// the default lifetime (in seconds) for cache items that do not define their
|
||||
// own lifetime, with a value 0 causing items to be stored indefinitely (i.e.
|
||||
// until RedisAdapter::clear() is invoked or the server(s) are purged)
|
||||
$defaultLifetime = 10
|
||||
);
|
||||
|
||||
return static::$redisCache;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
208
inc/Hura8/System/Config.php
Normal file
208
inc/Hura8/System/Config.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
use Hura8\Traits\ClassCacheTrait;
|
||||
|
||||
class Config
|
||||
{
|
||||
|
||||
use ClassCacheTrait;
|
||||
|
||||
|
||||
public static function getRequestLanguage() : string {
|
||||
return static::getCache("getRequestLanguage", function () {
|
||||
$lang = $_REQUEST[Constant::LANGUAGE_ID] ?? DEFAULT_LANGUAGE;
|
||||
|
||||
return (array_key_exists($lang , Constant::languagePermitList())) ? $lang : DEFAULT_LANGUAGE;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function getClientInfo() {
|
||||
return static::getCache("getClientInfo", function () {
|
||||
$config_file = CONFIG_DIR . '/client/client.info.php';
|
||||
if(file_exists($config_file)) {
|
||||
return include $config_file;
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function getProductHotTypeList(){
|
||||
|
||||
return static::getCache('getProductHotTypeList', function (){
|
||||
$config_file = CONFIG_DIR . "/client/product.hottype.php";
|
||||
if(!file_exists($config_file)) {
|
||||
die("File: client/product.hottype.php not exist !");
|
||||
}
|
||||
|
||||
return include $config_file;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function getRoutes() {
|
||||
return static::getCache("getRoutes", function () {
|
||||
$config_file = CONFIG_DIR . '/client/routing_main.php';
|
||||
if(!file_exists($config_file)) {
|
||||
die("File not exist: config/client/routing_main.php");
|
||||
}
|
||||
|
||||
return include $config_file;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function getEntityLanguageFields() {
|
||||
return static::getCache("getEntityLanguageFields", function () {
|
||||
|
||||
$config_file = CONFIG_DIR . '/client/language_fields.php';
|
||||
if(!file_exists($config_file)) {
|
||||
die("File not exist: config/client/language_fields.php");
|
||||
}
|
||||
|
||||
return include $config_file;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function getSettingsEmail() {
|
||||
return static::getCache("getSettingsEmail", function () {
|
||||
|
||||
$system_config_file = CONFIG_DIR . '/system/settings.email.php';
|
||||
if(!file_exists($system_config_file)) {
|
||||
die("File not exist: config/system/settings.email.php");
|
||||
}
|
||||
|
||||
$system_config = include $system_config_file;
|
||||
|
||||
// client extension
|
||||
$client_config_file = CONFIG_DIR . '/client/settings.email-extend.php';
|
||||
$client_config = [];
|
||||
if(file_exists($client_config_file)) {
|
||||
$client_config = include $client_config_file;
|
||||
}
|
||||
|
||||
$final_config = $system_config;
|
||||
|
||||
if(isset($client_config['email'])) {
|
||||
$final_config['email'] = array_merge($final_config['email'], $client_config['email']);
|
||||
}
|
||||
|
||||
if(isset($client_config['template'])) {
|
||||
$final_config['template'] = array_merge($final_config['template'], $client_config['template']);
|
||||
}
|
||||
|
||||
return $final_config;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function getSettingsPrint() {
|
||||
|
||||
return static::getCache("getSettingsPrint", function () {
|
||||
|
||||
$config_file = CONFIG_DIR . '/system/settings.print.php';
|
||||
if (!file_exists($config_file)) {
|
||||
// die("File not exist: config/system/settings.print.php");
|
||||
return [];
|
||||
}
|
||||
|
||||
return include $config_file;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function getRelationConfigForItem($item_type) {
|
||||
|
||||
return static::getCache("getRelationConfigForItem-".$item_type, function () use ($item_type) {
|
||||
|
||||
$config_relation_file = ROOT_DIR . '/config/client/config_relation.php';
|
||||
if(!file_exists($config_relation_file)) {
|
||||
die("config/client/config_relation.php does not exist!");
|
||||
}
|
||||
|
||||
$system_relation_file = ROOT_DIR . '/config/system/relation_config.php';
|
||||
if(!file_exists($system_relation_file)) {
|
||||
die("config/system/relation_config.php does not exist!");
|
||||
}
|
||||
|
||||
$config_relation = include $config_relation_file;
|
||||
$available_content = include $system_relation_file;
|
||||
|
||||
if(isset($config_relation[$item_type])) {
|
||||
$config = array();
|
||||
foreach ($config_relation[$item_type] as $type) {
|
||||
if(isset($available_content[$type])) $config[$type] = $available_content[$type];
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
|
||||
return [];
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static function getProductImageTypes(){
|
||||
$config_image_type_file = ROOT_DIR . "/config/client/config_product_image_types.php";
|
||||
if(!file_exists($config_image_type_file)){
|
||||
return [
|
||||
'standard' => 'Hình sản phẩm',
|
||||
];
|
||||
}
|
||||
|
||||
return include $config_image_type_file;
|
||||
}
|
||||
|
||||
|
||||
public static function getProductUnitList() {
|
||||
return static::getCache("getProductUnitList", function (){
|
||||
$config_file = CONFIG_DIR . "/client/product.unit.php";
|
||||
if(file_exists($config_file)) {
|
||||
return include $config_file;
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function getLanguageCount(){
|
||||
$language_list = static::getLanguageConfig();
|
||||
$count = sizeof($language_list);
|
||||
return ($count > 1) ? $count : 1; //always have at least 1 language
|
||||
}
|
||||
|
||||
|
||||
public static function getLanguageConfig(){
|
||||
return static::getCache('getLanguageConfig', function (){
|
||||
$config_file = CONFIG_DIR . "/client/language_enable.php";
|
||||
if(!file_exists($config_file)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return include $config_file;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function getClientBuildConfig() {
|
||||
return static::getCache("getClientBuildConfig", function (){
|
||||
$config_file = CONFIG_DIR . '/build/store.config.php';
|
||||
if(file_exists($config_file)) {
|
||||
return include $config_file;
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
106
inc/Hura8/System/Constant.php
Normal file
106
inc/Hura8/System/Constant.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
|
||||
use Hura8\Traits\ClassCacheTrait;
|
||||
use ReflectionClass;
|
||||
|
||||
class Constant
|
||||
{
|
||||
|
||||
use ClassCacheTrait;
|
||||
|
||||
|
||||
private static $constant_dir = CONFIG_DIR . '/system';
|
||||
|
||||
const LANGUAGE_ID = "_l"; // to identify the language setting on URL or in FORM
|
||||
|
||||
|
||||
public static function getAllEntityTypes() {
|
||||
|
||||
return static::getCache('getAllEntityTypes', function () {
|
||||
|
||||
$extend_type_config = CONFIG_DIR . DIRECTORY_SEPARATOR . "/client/config_extend_entity_type.php";
|
||||
$extended_types = [];
|
||||
if(file_exists($extend_type_config)) {
|
||||
$extend_types = include_once $extend_type_config;
|
||||
foreach ($extend_types as $key => $title) {
|
||||
$extended_types[] = preg_replace("/[^a-z0-9_\-]/i", "", $key);
|
||||
}
|
||||
}
|
||||
|
||||
$list = [];
|
||||
try {
|
||||
$reflectionClass = new \ReflectionClass(new \Hura8\Interfaces\EntityType());
|
||||
foreach ($reflectionClass->getConstants() as $handle => $value) {
|
||||
$list[] = $value;
|
||||
}
|
||||
|
||||
// add extend
|
||||
foreach ($extended_types as $type) {
|
||||
if(!in_array($type, $list)) {
|
||||
$list[] = $type;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\ReflectionException $e) {
|
||||
//
|
||||
}
|
||||
|
||||
return $list;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// list of languages can be enabled
|
||||
public static function languagePermitList() {
|
||||
return [
|
||||
"vi" => "Tiếng Việt", // default
|
||||
"en" => "English",
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public static function mobileProviderPrefixList() {
|
||||
return static::getCache('mobileProviderPrefixList', function () {
|
||||
return include static::$constant_dir. '/mobile_provider.php';
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function customerStageList() {
|
||||
return static::getCache('customerStageList', function () {
|
||||
return include static::$constant_dir. '/customer_stage_list.php';
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function saluteList() {
|
||||
return static::getCache('genderList', function () {
|
||||
return include static::$constant_dir. '/salute_list.php';
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function genderList() {
|
||||
return static::getCache('genderList', function () {
|
||||
return include static::$constant_dir. '/gender_list.php';
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function industryList() {
|
||||
return static::getCache('industryList', function () {
|
||||
return include static::$constant_dir. '/industry_list.php';
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function maritalStatusList() {
|
||||
return static::getCache('maritalStatusList', function () {
|
||||
return include static::$constant_dir. '/marital_status_list.php';
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
124
inc/Hura8/System/Controller/DomainController.php
Normal file
124
inc/Hura8/System/Controller/DomainController.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Controller;
|
||||
|
||||
use Hura8\System\Model\DomainModel;
|
||||
|
||||
class DomainController
|
||||
{
|
||||
protected $objDomainModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->objDomainModel = new DomainModel();
|
||||
}
|
||||
|
||||
protected $layout_options = [
|
||||
"pc" => "Chỉ cho PC",
|
||||
"mobile" => "Chỉ cho Mobile",
|
||||
//"amp" => "Chỉ cho AMP",
|
||||
"all" => "Cả PC & Mobile",
|
||||
];
|
||||
|
||||
|
||||
public function buildDomainConfig() {
|
||||
$domain_per_languages = $this->getList('');
|
||||
|
||||
$config_domain_list = []; //all domains and attributes, so we can know the info of currently visited domain
|
||||
$config_domain_languages = []; //domains per language, so we can redirect to main domain of a specific language
|
||||
|
||||
foreach ($domain_per_languages as $lang => $list_domains) {
|
||||
foreach ($list_domains as $_item) {
|
||||
$config_domain_languages[$lang][] = [
|
||||
"domain" => $_item['domain'],
|
||||
"is_main" => $_item['isMain'],
|
||||
"layout" => ($_item['layout']) ? $_item['layout'] : 'pc',
|
||||
];
|
||||
|
||||
$config_domain_list[$_item['domain']] = [
|
||||
"lang" => $_item['lang'],
|
||||
"is_main" => $_item['isMain'],
|
||||
"layout" => ($_item['layout']) ? $_item['layout'] : 'pc',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
"language" => $config_domain_languages,
|
||||
"list" => $config_domain_list,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function getLayoutOption(){
|
||||
return $this->layout_options;
|
||||
}
|
||||
|
||||
|
||||
public function getList($language = '') {
|
||||
|
||||
$item_list = $this->objDomainModel->getList([
|
||||
"language" => $language,
|
||||
"numPerPage" => 100,
|
||||
]);
|
||||
|
||||
$result = array();
|
||||
foreach ( $item_list as $rs ) {
|
||||
if(!$rs['lang']) $rs['lang'] = DEFAULT_LANGUAGE;
|
||||
$result[$rs['lang']][] = $rs;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
public function addNewDomain($domain, $language = DEFAULT_LANGUAGE){
|
||||
$this->objDomainModel->addNewDomain($this->cleanDomain($domain), $language);
|
||||
|
||||
$this->rebuildConfigFile();
|
||||
}
|
||||
|
||||
|
||||
public function deleteDomain($domain){
|
||||
$this->objDomainModel->deleteDomain($this->cleanDomain($domain));
|
||||
|
||||
$this->rebuildConfigFile();
|
||||
}
|
||||
|
||||
|
||||
public function setDomainMain($domain, $language){
|
||||
$this->objDomainModel->setDomainMain($this->cleanDomain($domain), $language);
|
||||
|
||||
$this->rebuildConfigFile();
|
||||
}
|
||||
|
||||
|
||||
public function setDomainLayout($domain, $layout = 'pc'){
|
||||
$layout_option = $this->getLayoutOption();
|
||||
if(!isset($layout_option[$layout])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->objDomainModel->setDomainLayout($this->cleanDomain($domain), $layout);
|
||||
$this->rebuildConfigFile();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected function cleanDomain($domain) {
|
||||
$domain_element = parse_url($domain);
|
||||
|
||||
$scheme = isset($domain_element['scheme']) ? $domain_element['scheme'] . '://' : '';
|
||||
$host = $domain_element['host'] ?? '';
|
||||
$port = isset($domain_element['port']) ? ':' . $domain_element['port'] : '';
|
||||
|
||||
return strtolower(trim($scheme . $host . $port));
|
||||
}
|
||||
|
||||
|
||||
protected function rebuildConfigFile() {
|
||||
$objSettingController = new SettingController();
|
||||
$objSettingController->create_config_file_n_upload();
|
||||
}
|
||||
}
|
||||
43
inc/Hura8/System/Controller/EntityPermissionController.php
Normal file
43
inc/Hura8/System/Controller/EntityPermissionController.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Controller;
|
||||
|
||||
use Hura8\Interfaces\iEntityPermission;
|
||||
|
||||
class EntityPermissionController implements iEntityPermission
|
||||
{
|
||||
|
||||
public function __construct($entity_type) {
|
||||
|
||||
}
|
||||
|
||||
public function canCreate(): bool
|
||||
{
|
||||
// TODO: Implement canCreate() method.
|
||||
return true;
|
||||
}
|
||||
|
||||
public function canUpdate(): bool
|
||||
{
|
||||
// TODO: Implement canUpdate() method.
|
||||
return true;
|
||||
}
|
||||
|
||||
public function canDelete(): bool
|
||||
{
|
||||
// TODO: Implement canDelete() method.
|
||||
return true;
|
||||
}
|
||||
|
||||
public function canView(): bool
|
||||
{
|
||||
// TODO: Implement canView() method.
|
||||
return true;
|
||||
}
|
||||
|
||||
public function canApprove(): bool
|
||||
{
|
||||
// TODO: Implement canApprove() method.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
82
inc/Hura8/System/Controller/RelationController.php
Normal file
82
inc/Hura8/System/Controller/RelationController.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Controller;
|
||||
|
||||
use Hura8\System\Model\RelationModel;
|
||||
use Hura8\Traits\ClassCacheTrait;
|
||||
|
||||
class RelationController
|
||||
{
|
||||
use ClassCacheTrait;
|
||||
|
||||
protected $objRelationModel;
|
||||
|
||||
private $main_item_type = '';
|
||||
private $main_item_id = 0;
|
||||
|
||||
public function __construct($main_item_type, $main_item_id)
|
||||
{
|
||||
$this->objRelationModel = new RelationModel($main_item_type, $main_item_id);
|
||||
$this->main_item_type = $main_item_type;
|
||||
$this->main_item_id = $main_item_id;
|
||||
}
|
||||
|
||||
|
||||
public function updateOrdering($related_item_id, $new_order) {
|
||||
return $this->objRelationModel->updateOrdering($related_item_id, $new_order);
|
||||
}
|
||||
|
||||
|
||||
//@warn: this does not check if records exist.
|
||||
public function create(array $related_items, $both_way_relation = true) {
|
||||
return $this->objRelationModel->create($related_items, $both_way_relation);
|
||||
}
|
||||
|
||||
|
||||
public function checkExist($main_item_type, $main_item_id, $related_item_type, $related_item_id){
|
||||
return $this->objRelationModel->checkExist($main_item_type, $main_item_id, $related_item_type, $related_item_id);
|
||||
}
|
||||
|
||||
//remove a related-item
|
||||
public function remove($related_item_type, $related_item_id, $remove_both_way = true) {
|
||||
return $this->objRelationModel->remove($related_item_type, $related_item_id, $remove_both_way);
|
||||
}
|
||||
|
||||
|
||||
//remove all relate items
|
||||
public function truncate() {
|
||||
$this->objRelationModel->truncate();
|
||||
}
|
||||
|
||||
|
||||
public function getRelatedItems(array $related_item_types = []) {
|
||||
return $this->objRelationModel->getRelatedItems($related_item_types);
|
||||
}
|
||||
|
||||
public function getRelatedItemsForList(array $main_item_list_ids, array $related_item_types = []) {
|
||||
return $this->objRelationModel->getRelatedItemsForList($main_item_list_ids, $related_item_types);
|
||||
}
|
||||
|
||||
//count related items
|
||||
public function getRelatedItemCount() {
|
||||
return $this->objRelationModel->getRelatedItemCount();
|
||||
}
|
||||
|
||||
public static function findItemUrl($item_type, $item_id) {
|
||||
$url_config = array(
|
||||
"product" => "/admin/?opt=product&view=form&id=".$item_id."&part=relation&l=vi&popup=".POPUP,
|
||||
"product-category" => "?opt=product&view=category-form&id=".$item_id."",
|
||||
|
||||
"article-article" => "?opt=article&view=form&id=".$item_id."&l=&popup=".POPUP,
|
||||
"article-category" => "?opt=article&view=category-form&id=".$item_id."&l=&popup=".POPUP,
|
||||
|
||||
"album" => "?opt=album&view=form&id=".$item_id,
|
||||
"banner" => "?opt=banner&view=upload&id=".$item_id,
|
||||
"page" => "?opt=page&view=form&id=".$item_id,
|
||||
);
|
||||
|
||||
return (isset($url_config[$item_type])) ? $url_config[$item_type] : null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
144
inc/Hura8/System/Controller/SettingController.php
Normal file
144
inc/Hura8/System/Controller/SettingController.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Controller;
|
||||
|
||||
use Hura8\Components\Template\AdminController\ATemplateSetController;
|
||||
use Hura8\System\Model\SettingModel;
|
||||
|
||||
class SettingController
|
||||
{
|
||||
|
||||
static $file_folder = "media/settings";
|
||||
|
||||
protected $objSettingModel;
|
||||
|
||||
// reserved special keys which should not be created by admin (but the system)
|
||||
protected $special_keys = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->objSettingModel = new SettingModel();
|
||||
$this->special_keys = include CONFIG_DIR . "/system/special_settings_keys.php";
|
||||
}
|
||||
|
||||
|
||||
public function getAll(){
|
||||
return $this->objSettingModel->getAll();
|
||||
}
|
||||
|
||||
|
||||
public function getSpecialKeys() {
|
||||
$group_keys = $this->special_keys;
|
||||
return array_merge($group_keys['design'], $group_keys['system']);
|
||||
}
|
||||
|
||||
|
||||
public function get($key, $default =null){
|
||||
return $this->objSettingModel->get($key, $default);
|
||||
}
|
||||
|
||||
|
||||
public function delete($key){
|
||||
return $this->objSettingModel->delete($key);
|
||||
}
|
||||
|
||||
|
||||
// update bulk key-values
|
||||
public function updateBulk(array $key_values){
|
||||
foreach ($key_values as $key => $value) {
|
||||
$this->objSettingModel->updateOrCreate($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function updateOrCreate($key, $value, $comment = '') {
|
||||
return $this->objSettingModel->updateOrCreate($key, $value, $comment );
|
||||
}
|
||||
|
||||
|
||||
public function getList(array $keys) {
|
||||
return $this->objSettingModel->getList($keys);
|
||||
}
|
||||
|
||||
|
||||
public function populateSpecialKeys() {
|
||||
$keys = [];
|
||||
foreach ($this->special_keys as $group => $_list) {
|
||||
$keys = array_merge($keys, $_list);
|
||||
}
|
||||
|
||||
return $this->objSettingModel->populateKeys($keys);
|
||||
}
|
||||
|
||||
|
||||
//tao config file tu database va upload vao thu muc website
|
||||
// Global config variables available to the whole website
|
||||
// includes:
|
||||
/*
|
||||
* - domain setting
|
||||
* - main domain
|
||||
* - template_set
|
||||
* - exchange rate
|
||||
* - password to unlock website
|
||||
* - google analytic verification
|
||||
* - google webmaster tool verification
|
||||
* - number of products to display
|
||||
* - default product display type: list|grid
|
||||
*
|
||||
* */
|
||||
public function create_config_file_n_upload(){
|
||||
|
||||
$objDomainController = new DomainController();
|
||||
$config_domains = $objDomainController->buildDomainConfig();
|
||||
|
||||
$config = [];
|
||||
|
||||
// * - domain setting
|
||||
$config['domains'] = $config_domains;
|
||||
|
||||
// * - template_set
|
||||
$objATemplateSetController = new ATemplateSetController();
|
||||
$config['template_set'] = $objATemplateSetController->getActivatedSet();
|
||||
|
||||
// * - exchange rate
|
||||
// * - password to unlock website
|
||||
// * - google analytic verification
|
||||
// * - google webmaster tool verification
|
||||
// * - number of products to display
|
||||
// * - default product display type: list|grid
|
||||
$config['setup'] = $this->getList(array(
|
||||
"web_close_pass" ,
|
||||
"web_close_message" ,
|
||||
"exchange_rate" ,
|
||||
"google_domain_verify" ,
|
||||
"product_per_page" ,
|
||||
"product_default_order",
|
||||
"site_manager" ,
|
||||
"site_manager_access_key",
|
||||
));
|
||||
|
||||
$config_file = CONFIG_DIR . "/build/store.config.php";
|
||||
|
||||
$config_head = "<?php
|
||||
/**
|
||||
* Author: HuraSoft
|
||||
* Generated time : ".date("d-m-Y, g:ia")."
|
||||
*/
|
||||
";
|
||||
$config_content = "return ".var_export($config, true) .";";
|
||||
|
||||
@file_put_contents($config_file, $config_head. $this->_minifyCode($config_content));
|
||||
}
|
||||
|
||||
|
||||
//this is a greatly simplified version
|
||||
protected function _minifyCode($text) {
|
||||
//remove line break
|
||||
$text = str_replace(["\n", "\r", "\t"], " ", $text);
|
||||
//remove double spacings
|
||||
$text = preg_replace("/(\s+)/i", " ", $text);
|
||||
|
||||
return trim($text);
|
||||
}
|
||||
|
||||
}
|
||||
242
inc/Hura8/System/Controller/UrlManagerController.php
Normal file
242
inc/Hura8/System/Controller/UrlManagerController.php
Normal file
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Controller;
|
||||
|
||||
use Hura8\Interfaces\AppResponse;
|
||||
use Hura8\System\IDGenerator;
|
||||
use Hura8\System\Language;
|
||||
use Hura8\System\Model\UrlModel;
|
||||
use Hura8\System\Url;
|
||||
use Hura8\Traits\ClassCacheTrait;
|
||||
|
||||
|
||||
class UrlManagerController
|
||||
{
|
||||
|
||||
use ClassCacheTrait;
|
||||
|
||||
/* @var UrlModel $objUrlModel */
|
||||
protected $objUrlModel;
|
||||
|
||||
public function __construct() {
|
||||
$this->objUrlModel = new UrlModel();
|
||||
}
|
||||
|
||||
|
||||
public function createRedirect($info) {
|
||||
$request_path = $info['request_path'] ?? '';
|
||||
$redirect_code = $info['redirect_code'] ?? 0;
|
||||
$redirect_url = $info['redirect_url'] ?? '';
|
||||
|
||||
if(!$request_path || !$redirect_url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$request_path_element = Url::parse($request_path);
|
||||
|
||||
$request_path_path = $request_path_element['path'];
|
||||
|
||||
// home page or itself is forbidden
|
||||
if($request_path_path == '/' || $request_path_path == $redirect_url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return $this->objUrlModel->create([
|
||||
"url_type" => "redirect",
|
||||
"request_path" => $request_path_path,
|
||||
"redirect_code" => $redirect_code,
|
||||
"redirect_url" => $redirect_url,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function getInfo($id) : ?array {
|
||||
return $this->objUrlModel->getInfo($id);
|
||||
}
|
||||
|
||||
|
||||
public function getEmptyInfo($addition_field_value = []) : array {
|
||||
return $this->objUrlModel->getEmptyInfo($addition_field_value);
|
||||
}
|
||||
|
||||
|
||||
public function getList(array $condition) : array
|
||||
{
|
||||
return $this->objUrlModel->getList($condition);
|
||||
}
|
||||
|
||||
|
||||
public function getTotal(array $condition) : int
|
||||
{
|
||||
return $this->objUrlModel->getTotal($condition);
|
||||
}
|
||||
|
||||
|
||||
public function deleteByRequestPath($request_path) {
|
||||
$this->objUrlModel->deleteByRequestPath($request_path);
|
||||
}
|
||||
|
||||
|
||||
public static function translateRequestPathConfig(
|
||||
$request_path_config, // "/%extra_path%/%item_index%/ac%item_id%.html",/
|
||||
$item_id = '',
|
||||
$item_index = '',
|
||||
$extra_path = ''
|
||||
) {
|
||||
|
||||
$item_index = static::create_url_index($item_index); //reclean url index
|
||||
|
||||
$new_url = str_replace(
|
||||
array('%item_id%', '%item_index%', '%extra_path%',),
|
||||
array($item_id, $item_index, $extra_path),
|
||||
$request_path_config
|
||||
);
|
||||
|
||||
return str_replace("//","/",$new_url);
|
||||
}
|
||||
|
||||
|
||||
public static function create_url_index($name, $vietnamese=true){
|
||||
if($vietnamese) $name = Language::chuyenKhongdau($name);
|
||||
|
||||
$name = preg_replace("/[^a-z0-9\s_\-]/i", " ", $name);
|
||||
$name = preg_replace("/\s+/i", " ", $name);
|
||||
$name = str_replace(" ","-", trim($name));
|
||||
$name = preg_replace("/-+/","-", $name);
|
||||
|
||||
if (!defined("BUILD_URL_INDEX_LOWERCASE") || BUILD_URL_INDEX_LOWERCASE) {
|
||||
return strtolower($name);
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
|
||||
public function getUrlMetaInfo($request_path){
|
||||
return $this->objUrlModel->getUrlMetaInfo($request_path);
|
||||
}
|
||||
|
||||
|
||||
public function createUrlMeta(array $info){
|
||||
return $this->objUrlModel->createUrlMeta($info);
|
||||
}
|
||||
|
||||
|
||||
public function getUrlInfoByRequestPath($request_path) {
|
||||
$info = $this->objUrlModel->getUrlByRequestPath($request_path);
|
||||
if($info){
|
||||
|
||||
$id_path_content = $this->analyze_url_id_path($info['id_path']);
|
||||
|
||||
$id_path_content['option'] = $id_path_content['module'];
|
||||
//$id_path_content['redirect_code'] = $info['redirect_code'];
|
||||
//$id_path_content['request_path'] = $info['request_path'];
|
||||
|
||||
return array_merge($info, $id_path_content);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
///module:product/view:category/view_id:1/brand_id:55/type:hello-ther
|
||||
//return:
|
||||
/*
|
||||
[
|
||||
"module" => "product",
|
||||
"view" => "category",
|
||||
"view_id" => "1",
|
||||
'query' => [
|
||||
"brand_id" => 55,
|
||||
"type" => "hello-ther",
|
||||
],
|
||||
],
|
||||
* */
|
||||
protected function analyze_url_id_path($id_path) : array
|
||||
{
|
||||
$id_path_ele = explode("/", $id_path);
|
||||
|
||||
$result = array();
|
||||
$query_string = array();
|
||||
|
||||
foreach ( $id_path_ele as $ele ) {
|
||||
if(!$ele) continue;
|
||||
|
||||
$ele_part = explode(":", $ele);
|
||||
if(!in_array($ele_part[0], array('module', 'view', 'view_id'))) {
|
||||
$query_string[$ele_part[0]] = $ele_part[1];
|
||||
}else{
|
||||
$result[$ele_part[0]] = $ele_part[1];
|
||||
}
|
||||
}
|
||||
|
||||
$result['query'] = $query_string;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
public static function createIdPath($module, $view, $view_id, array $other_list = []): string
|
||||
{
|
||||
$parameter_constr = [
|
||||
"module:".$module,
|
||||
"view:".$view,
|
||||
"view_id:".$view_id,
|
||||
];
|
||||
foreach($other_list as $arg => $value) {
|
||||
$parameter_constr[] = $arg.":".$value;
|
||||
}
|
||||
|
||||
return "/".join("/", $parameter_constr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description create or update url's $request_path by checking $id_path. $new_request_path will return to be updated to item's entity
|
||||
* @param string $url_type $module:$view
|
||||
* @param string $wanted_request_path
|
||||
* @param string $id_path
|
||||
* @param string $redirect_code
|
||||
* @return ?string $wanted_request_path or new $request_path if the path already exists
|
||||
*/
|
||||
public function createUrl(string $url_type, string $wanted_request_path, string $id_path, $redirect_code=0) : ?string
|
||||
{
|
||||
$new_request_path = $this->createUniqueRequestPath($wanted_request_path, $id_path);
|
||||
|
||||
$check_id_path_exist = $this->objUrlModel->getUrlByIdPath($id_path);
|
||||
if($check_id_path_exist) {
|
||||
$res = $this->objUrlModel->update($check_id_path_exist['id'], [
|
||||
'request_path' => $new_request_path ,
|
||||
]);
|
||||
} else {
|
||||
$res = $this->objUrlModel->create([
|
||||
'url_type' => $url_type,
|
||||
'request_path' => $new_request_path ,
|
||||
'id_path' => $id_path,
|
||||
'redirect_code' => $redirect_code,
|
||||
]);
|
||||
}
|
||||
|
||||
return ($res->getStatus() == AppResponse::SUCCESS) ? $new_request_path : null;
|
||||
}
|
||||
|
||||
|
||||
//create a unique request-path
|
||||
protected function createUniqueRequestPath(string $wanted_request_path, string $id_path) : string
|
||||
{
|
||||
$check_exist = $this->objUrlModel->getUrlByRequestPath($wanted_request_path);
|
||||
|
||||
//ok, can use this one
|
||||
if(!$check_exist || $check_exist['id_path'] == $id_path ) {
|
||||
return $wanted_request_path;
|
||||
}
|
||||
|
||||
//other case, create new path and check again
|
||||
$random_suffix = IDGenerator::createStringId(4, true);
|
||||
$new_request_path = $wanted_request_path . '-'.$random_suffix;
|
||||
|
||||
return $this->createUniqueRequestPath($new_request_path, $id_path);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
44
inc/Hura8/System/Controller/ViewHistoryController.php
Normal file
44
inc/Hura8/System/Controller/ViewHistoryController.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Controller;
|
||||
|
||||
|
||||
use Hura8\System\Model\WebUserModel;
|
||||
|
||||
class ViewHistoryController
|
||||
{
|
||||
protected $history = []; //type => [id1, id2]
|
||||
|
||||
protected $objWebUserModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->objWebUserModel = new WebUserModel(WebUserController::getUserId());
|
||||
}
|
||||
|
||||
|
||||
public function addHistory($item_type, $item_id) {
|
||||
$current_list = $this->getHistory($item_type);
|
||||
|
||||
// if exist, remove it
|
||||
$search_key = array_search($item_id, $current_list, true);
|
||||
if($search_key !== false) {
|
||||
array_splice($current_list, $search_key, 1);
|
||||
}
|
||||
|
||||
// add to front
|
||||
array_unshift($current_list, $item_id);
|
||||
|
||||
$this->history[$item_type] = $current_list;
|
||||
|
||||
// save to db
|
||||
$this->objWebUserModel->setValue("view-history", $this->history);
|
||||
}
|
||||
|
||||
|
||||
public function getHistory($item_type) {
|
||||
$history = $this->objWebUserModel->getValue("view-history");
|
||||
return (isset($history[$item_type])) ? $history[$item_type] : [];
|
||||
}
|
||||
|
||||
}
|
||||
27
inc/Hura8/System/Controller/WebUserController.php
Normal file
27
inc/Hura8/System/Controller/WebUserController.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Controller;
|
||||
|
||||
use Hura8\System\Model\WebUserModel;
|
||||
|
||||
class WebUserController
|
||||
{
|
||||
const USER_BROWSER_COOKIE_NAME = 'uID';
|
||||
|
||||
protected $objWebUserModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->objWebUserModel = new WebUserModel(self::getUserId());
|
||||
}
|
||||
|
||||
// prefer cookie-id, if set ID in url parameter then use it
|
||||
public static function getUserId() {
|
||||
$url_user_id = getRequest("uid", "");
|
||||
$cookie_user_id = (isset($_COOKIE[self::USER_BROWSER_COOKIE_NAME])) ? $_COOKIE[self::USER_BROWSER_COOKIE_NAME] : '';
|
||||
|
||||
return ($url_user_id) ?: $cookie_user_id;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
78
inc/Hura8/System/Controller/aAdminEntityBaseController.php
Normal file
78
inc/Hura8/System/Controller/aAdminEntityBaseController.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Controller;
|
||||
|
||||
use Hura8\Interfaces\AppResponse;
|
||||
use Hura8\Interfaces\iEntityController;
|
||||
|
||||
abstract class aAdminEntityBaseController extends aEntityBaseController implements iEntityController
|
||||
{
|
||||
|
||||
public function updateFields($id, array $info): AppResponse
|
||||
{
|
||||
return $this->iEntityModel->updateFields($id, $info);
|
||||
}
|
||||
|
||||
|
||||
public function updateField($id, $field, $value): AppResponse
|
||||
{
|
||||
|
||||
$fields_list = [];
|
||||
$fields_list[$field] = $value;
|
||||
|
||||
return $this->iEntityModel->updateFields($id, $fields_list);
|
||||
}
|
||||
|
||||
|
||||
public function updateFeatured($id, $new_status): AppResponse
|
||||
{
|
||||
$status = ($new_status == 'on' || $new_status == 1) ? 1 : 0;
|
||||
|
||||
return $this->iEntityModel->updateFields($id, ['is_featured' => $status]);
|
||||
}
|
||||
|
||||
|
||||
public function updateStatus($id, $new_status): AppResponse
|
||||
{
|
||||
$status = ($new_status == 'on' || $new_status == 1) ? 1 : 0;
|
||||
|
||||
return $this->iEntityModel->updateFields($id, ['status' => $status]);
|
||||
}
|
||||
|
||||
|
||||
public function create(array $info): AppResponse
|
||||
{
|
||||
return $this->iEntityModel->create($info);
|
||||
}
|
||||
|
||||
|
||||
public function update($id, array $info): AppResponse
|
||||
{
|
||||
if($this->iEntityLanguageModel) {
|
||||
return $this->iEntityLanguageModel->update($id, $info);
|
||||
}
|
||||
|
||||
return $this->iEntityModel->update($id, $info);
|
||||
}
|
||||
|
||||
|
||||
abstract protected function deleteFileBeforeDeleteItem($item_id) : bool;
|
||||
|
||||
|
||||
public function delete($id): AppResponse
|
||||
{
|
||||
if($this->deleteFileBeforeDeleteItem($id)) {
|
||||
return $this->iEntityModel->delete($id);
|
||||
}
|
||||
|
||||
return new AppResponse('error', 'Cannot delete '.$id);
|
||||
}
|
||||
|
||||
|
||||
public function getEmptyInfo(array $additional_fields = []) : array
|
||||
{
|
||||
return $this->iEntityModel->getEmptyInfo($additional_fields);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
304
inc/Hura8/System/Controller/aCategoryBaseController.php
Normal file
304
inc/Hura8/System/Controller/aCategoryBaseController.php
Normal file
@@ -0,0 +1,304 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Controller;
|
||||
|
||||
use Hura8\Interfaces\AppResponse;
|
||||
use Hura8\Interfaces\iEntityCategoryModel;
|
||||
use Hura8\Interfaces\iEntityLanguageModel;
|
||||
use Hura8\Traits\ClassCacheTrait;
|
||||
|
||||
abstract class aCategoryBaseController
|
||||
{
|
||||
|
||||
use ClassCacheTrait;
|
||||
|
||||
/* @var iEntityCategoryModel $iEntityCategoryModel */
|
||||
protected $iEntityCategoryModel;
|
||||
|
||||
/* @var ?iEntityLanguageModel $iEntityLanguageModel */
|
||||
protected $iEntityLanguageModel = null;
|
||||
|
||||
protected $view_language = LANGUAGE;
|
||||
|
||||
public static $public_fields = [
|
||||
"id", "title", "cat_path", "request_path", "image", "url", "not_translated", "children"
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
iEntityCategoryModel $iEntityCategoryModel,
|
||||
?iEntityLanguageModel $iEntityLanguageModel = null
|
||||
) {
|
||||
$this->iEntityCategoryModel = $iEntityCategoryModel;
|
||||
|
||||
if(!$this->isDefaultLanguage() && $iEntityLanguageModel instanceof iEntityLanguageModel) {
|
||||
$this->iEntityLanguageModel = $iEntityLanguageModel;
|
||||
$this->iEntityLanguageModel->setLanguage($this->view_language);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function create(array $info) : AppResponse
|
||||
{
|
||||
return $this->iEntityCategoryModel->create($info);
|
||||
}
|
||||
|
||||
|
||||
public function update($id, array $info) : AppResponse
|
||||
{
|
||||
if($this->iEntityLanguageModel) {
|
||||
return $this->iEntityLanguageModel->update($id, $info, $info['title']);
|
||||
}
|
||||
|
||||
return $this->iEntityCategoryModel->update($id, $info);
|
||||
}
|
||||
|
||||
|
||||
public function delete($id) : AppResponse
|
||||
{
|
||||
return $this->iEntityCategoryModel->delete($id);
|
||||
}
|
||||
|
||||
|
||||
public function updateFields($id, array $info) : AppResponse
|
||||
{
|
||||
return $this->iEntityCategoryModel->updateFields($id, $info);
|
||||
}
|
||||
|
||||
|
||||
public function isDefaultLanguage() : bool
|
||||
{
|
||||
return IS_DEFAULT_LANGUAGE;
|
||||
}
|
||||
|
||||
|
||||
public function getListByIds(array $list_id, array $condition = array()) : array
|
||||
{
|
||||
$item_list = $this->iEntityCategoryModel->getListByIds($list_id, $condition);
|
||||
|
||||
if($this->iEntityLanguageModel) {
|
||||
$item_list_language_info = $this->iEntityLanguageModel->getListByIds($list_id);
|
||||
|
||||
$final_list = [];
|
||||
foreach ($item_list as $item) {
|
||||
$item_language_info = $item_list_language_info[$item['id']] ?? ["not_translated" => true];
|
||||
$final_list[] = $this->formatItemInList(array_merge($item, $item_language_info));
|
||||
}
|
||||
|
||||
return $final_list;
|
||||
}
|
||||
|
||||
return array_map(function ($item){
|
||||
return $this->formatItemInList($item);
|
||||
}, $item_list);
|
||||
}
|
||||
|
||||
|
||||
protected function formatItemInList(array $info): array
|
||||
{
|
||||
return $info;
|
||||
}
|
||||
|
||||
|
||||
public function getActualFilterCondition(array $raw_filter_condition) : array
|
||||
{
|
||||
return $raw_filter_condition;
|
||||
}
|
||||
|
||||
|
||||
public function getModelFilterCondition(array $raw_filter_condition) : array
|
||||
{
|
||||
return $this->iEntityCategoryModel->getQueryCondition( $raw_filter_condition);
|
||||
}
|
||||
|
||||
|
||||
public function getList(array $condition) : array
|
||||
{
|
||||
$item_list = $this->iEntityCategoryModel->getList($condition);
|
||||
|
||||
if($this->iEntityLanguageModel) {
|
||||
$list_ids = array_map(function ($item){ return $item['id'];}, $item_list);
|
||||
$item_list_language_info = $this->iEntityLanguageModel->getListByIds($list_ids);
|
||||
|
||||
$final_list = [];
|
||||
foreach ($item_list as $item) {
|
||||
$item_language_info = $item_list_language_info[$item['id']] ?? ["not_translated" => true];
|
||||
$final_list[] = $this->formatItemInList(array_merge($item, $item_language_info));
|
||||
}
|
||||
|
||||
return $final_list;
|
||||
}
|
||||
|
||||
return array_map(function ($item){
|
||||
return $this->formatItemInList($item);
|
||||
}, $item_list);
|
||||
}
|
||||
|
||||
|
||||
public function getTotal(array $condition) : int
|
||||
{
|
||||
return $this->iEntityCategoryModel->getTotal($condition);
|
||||
}
|
||||
|
||||
|
||||
protected function formatItemInfo(?array $info) : ?array
|
||||
{
|
||||
return $info;
|
||||
}
|
||||
|
||||
|
||||
public function getInfo($id) : ?array
|
||||
{
|
||||
if(!$id) return null;
|
||||
|
||||
return self::getCache("getInfo-".$id."-".$this->view_language, function () use ($id){
|
||||
|
||||
if($this->iEntityLanguageModel) {
|
||||
$info = $this->iEntityCategoryModel->getInfo($id);
|
||||
$item_language_info = $this->iEntityLanguageModel->getInfo($id);
|
||||
if($item_language_info) {
|
||||
return $this->formatItemInfo(array_merge($info, $item_language_info));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->formatItemInfo($this->iEntityCategoryModel->getInfo($id));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function getEmptyInfo(array $additional_fields = []) : array
|
||||
{
|
||||
return $this->iEntityCategoryModel->getEmptyInfo($additional_fields);
|
||||
}
|
||||
|
||||
|
||||
// all categories and nested by parent
|
||||
public function getNestedCategories($is_public = false) {
|
||||
|
||||
$cache_key = "getNestedCategories-".$this->iEntityCategoryModel->getEntityType()."-".($is_public ? 1: 0);
|
||||
|
||||
return self::getCache($cache_key, function () use ($is_public){
|
||||
$all_categories = $this->getAllParent([
|
||||
'status' => ($is_public) ? 1 : 0
|
||||
]);
|
||||
|
||||
$result = [];
|
||||
if(isset($all_categories[0])) {
|
||||
foreach ($all_categories[0] as $index => $info) {
|
||||
$info['children'] = ($info['is_parent']) ? $this->getChildren($info['id'], $all_categories, 1) : [];
|
||||
$result[] = $this->formatItemInList($info);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function getChildren($parent_id, $all_categories, $current_level = 1){
|
||||
// dont allow too many nested level
|
||||
$max_deep_level_allow = 5;
|
||||
if($current_level == $max_deep_level_allow) return [];
|
||||
|
||||
$result = [];
|
||||
if(isset($all_categories[$parent_id])) {
|
||||
foreach ($all_categories[$parent_id] as $id => $info) {
|
||||
$info['children'] = $this->getChildren($id, $all_categories, $current_level + 1);
|
||||
$result[] = $this->formatItemInList($info);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
public function showPath($catPathId, $p_category){
|
||||
|
||||
if(!$catPathId){
|
||||
$cat_info = $this->getInfo($p_category);
|
||||
$catPathId = $cat_info['cat_path'];
|
||||
}
|
||||
|
||||
$result = array();
|
||||
$path_build = array();
|
||||
|
||||
if(strlen($catPathId) > 0){
|
||||
$cat_id_list = array_filter(explode(":", $catPathId));
|
||||
|
||||
$cat_list_info = $this->getListByIds($cat_id_list);
|
||||
|
||||
//reverse cat id order because the parent in the right most
|
||||
krsort($cat_id_list);
|
||||
|
||||
$start_count = 0;
|
||||
$count_cat = sizeof($cat_id_list);
|
||||
|
||||
foreach($cat_id_list as $catId){
|
||||
|
||||
$_cat_info = $cat_list_info[$catId] ?? null;
|
||||
if(!$_cat_info) continue;
|
||||
|
||||
$start_count ++;
|
||||
$path_build[] = "<a href=\"".$_cat_info['url']."\">".$_cat_info['title']."</a> ";
|
||||
|
||||
$result[] = array(
|
||||
'id' => $catId,
|
||||
'url' => $_cat_info['url'],
|
||||
'title' => $_cat_info['title'],
|
||||
);
|
||||
|
||||
if($start_count < $count_cat) $path_build[] = " >> ";
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'path' => $result,
|
||||
'path_url' => join("", $path_build),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function getAll(array $condition = []) {
|
||||
return $this->iEntityCategoryModel->getAll($condition);
|
||||
}
|
||||
|
||||
|
||||
public function getAllParent(array $condition = []) {
|
||||
|
||||
if($this->iEntityLanguageModel) {
|
||||
|
||||
$all_categories = $this->getAll($condition);
|
||||
|
||||
$item_list_ids = array_map(function ($item){ return $item['id']; }, $all_categories);
|
||||
$item_list_language_info = $this->iEntityLanguageModel->getListByIds($item_list_ids);
|
||||
|
||||
$translated_list = [];
|
||||
foreach ($all_categories as $item) {
|
||||
$item_language_info = $item_list_language_info[$item['id']] ?? ["not_translated" => true];
|
||||
$item = array_merge($item, $item_language_info);
|
||||
|
||||
$translated_list[] = array_merge($item, $item_language_info);
|
||||
}
|
||||
|
||||
// get group by parent
|
||||
$final_list = [];
|
||||
foreach ( $translated_list as $item ) {
|
||||
$final_list[$item['parent_id']][$item['id']] = $this->formatItemInList($item);
|
||||
}
|
||||
|
||||
return $final_list;
|
||||
}
|
||||
|
||||
// else
|
||||
$all_categories = $this->getAll($condition);
|
||||
$final_list = [];
|
||||
foreach ( $all_categories as $item ) {
|
||||
$final_list[$item['parent_id']][$item['id']] = $this->formatItemInList($item);
|
||||
}
|
||||
|
||||
return $final_list;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
197
inc/Hura8/System/Controller/aERPController.php
Normal file
197
inc/Hura8/System/Controller/aERPController.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Hura8\System\Controller;
|
||||
|
||||
use Hura8\Database\ConnectDB;
|
||||
|
||||
abstract class aERPController implements iClientERP
|
||||
{
|
||||
|
||||
protected $provider;
|
||||
protected $erp_config;
|
||||
|
||||
protected $tb_product = TableName::PRODUCT;
|
||||
protected $tb_erp_product = "erp_product";
|
||||
|
||||
// this table is the exact copy of $tb_erp_product
|
||||
// it's used when call the syncProductToWeb method to make sure that no new products are added in the syncing process
|
||||
protected $tb_erp_product_copy = "erp_product_tmp_copy";
|
||||
|
||||
protected $tb_log = "erp_log";
|
||||
|
||||
/* @var $objERPProvider iERPProvider */
|
||||
protected $objERPProvider;
|
||||
|
||||
/* @var $db ConnectDB */
|
||||
protected $db;
|
||||
|
||||
protected $get_erp_product_options = [];
|
||||
|
||||
|
||||
public function __construct($provider)
|
||||
{
|
||||
$this->provider = $provider;
|
||||
|
||||
$provider_config_file = CONFIG_DIR . '/provider/'.$provider.'_config.php';
|
||||
if(file_exists($provider_config_file)) {
|
||||
$this->erp_config = include $provider_config_file;
|
||||
|
||||
// create an instance of provider
|
||||
$this->erpFactory();
|
||||
|
||||
$this->db = ConnectDB::getInstance('');
|
||||
}else{
|
||||
die("Cannot load /config/provider/".$provider."_config.php ");
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected function customSyncProductToWeb();
|
||||
abstract protected function formatProductListFromERP(array $product_list);
|
||||
|
||||
/**
|
||||
* @overwrite on implemeting class if needed
|
||||
*/
|
||||
public function getProductListPerPage($page, $debug = false) {
|
||||
return $this->getProductList(['page' => $page], $debug);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iERPProvider
|
||||
*/
|
||||
public function getERPInstance()
|
||||
{
|
||||
return $this->objERPProvider;
|
||||
}
|
||||
|
||||
public function createOrder(array $order_info)
|
||||
{
|
||||
return $this->objERPProvider->createOrder($order_info);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
*/
|
||||
public function syncProductToWeb(array $options = [])
|
||||
{
|
||||
$this->beforeSyncProductToWeb();
|
||||
$this->customSyncProductToWeb();
|
||||
$this->afterSyncProductToWeb();
|
||||
}
|
||||
|
||||
protected function beforeSyncProductToWeb() {
|
||||
/*$this->db->runQuery("CREATE TABLE `" . $this->tb_erp_product_copy . "` LIKE ".$this->tb_erp_product." ");
|
||||
$this->db->runQuery("INSERT INTO `" . $this->tb_erp_product_copy . "` SELECT * FROM ".$this->tb_erp_product." ");
|
||||
$this->db->runQuery("UPDATE `" . $this->tb_erp_product_copy . "` e, idv_sell_product_store p SET
|
||||
e.product_id = p.id
|
||||
WHERE e.sku = p.storeSKU AND LENGTH(e.sku) > 0 ");
|
||||
$this->db->runQuery("DELETE FROM `" . $this->tb_erp_product_copy . "` WHERE `product_id` = 0 ");*/
|
||||
|
||||
$this->db->multi_query([
|
||||
"CREATE TABLE IF NOT EXISTS `" . $this->tb_erp_product_copy . "` LIKE ".$this->tb_erp_product." ",
|
||||
"INSERT INTO `" . $this->tb_erp_product_copy . "` SELECT * FROM ".$this->tb_erp_product." ",
|
||||
"UPDATE `" . $this->tb_erp_product_copy . "` e, ".$this->tb_product." p SET
|
||||
e.product_id = p.id
|
||||
WHERE e.sku = p.storeSKU AND LENGTH(e.sku) > 0 ",
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
protected function afterSyncProductToWeb() {
|
||||
$this->db->runQuery("DROP TABLE `" . $this->tb_erp_product_copy . "` ");
|
||||
}
|
||||
|
||||
public function setERPProductOptions(array $options = []) {
|
||||
foreach ($options as $key => $value) {
|
||||
$this->get_erp_product_options[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: clean any existing data before populate new ones
|
||||
* @return void
|
||||
*/
|
||||
public function cleanExistingData()
|
||||
{
|
||||
$this->db->runQuery("TRUNCATE `" . $this->tb_erp_product . "` ");
|
||||
}
|
||||
|
||||
public function getAllStore()
|
||||
{
|
||||
return $this->objERPProvider->getAllStore();
|
||||
}
|
||||
|
||||
|
||||
public function getProductSummary()
|
||||
{
|
||||
return $this->objERPProvider->getProductSummary();
|
||||
}
|
||||
|
||||
|
||||
public function getProductList(array $options = [], $debug = false)
|
||||
{
|
||||
return $this->formatProductListFromERP($this->objERPProvider->getProductList($options, $debug));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get log data
|
||||
*/
|
||||
public function getLog($type, $limit = 50) {
|
||||
$query = $this->db->runQuery("SELECT `id`, `data`, `log_time` FROM `".$this->tb_log."` WHERE `type` = ? ORDER BY `id` DESC LIMIT ".$limit, ['s'], [$type]);
|
||||
|
||||
return array_map(function ($item){
|
||||
$copy = $item;
|
||||
$copy['data'] = $item['data'] ? \json_decode($item['data'], true) : [];
|
||||
|
||||
return $copy;
|
||||
}, $this->db->fetchAll($query));
|
||||
}
|
||||
|
||||
/**
|
||||
* log data
|
||||
*/
|
||||
public function updateLogData($id, $new_data) {
|
||||
|
||||
$query = $this->db->runQuery("SELECT `data` FROM `".$this->tb_log."` WHERE `id` = ? LIMIT 1", ['d'], [$id]);
|
||||
|
||||
if($item_info = $this->db->fetchAssoc($query)) {
|
||||
$current_data = $item_info['data'] ? \json_decode($item_info['data'], true) : [];
|
||||
$updated_info = array_merge($current_data, $new_data);
|
||||
|
||||
return $this->db->update($this->tb_log, ['data' => $updated_info], ['id' => $id]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* log data
|
||||
*/
|
||||
public function log($type, array $data) {
|
||||
$info = [];
|
||||
$info['type'] = $type;
|
||||
$info['data'] = $data;
|
||||
$info['log_time'] = CURRENT_TIME;
|
||||
|
||||
return $this->db->insert($this->tb_log, $info);
|
||||
}
|
||||
|
||||
protected function erpFactory()
|
||||
{
|
||||
if(!$this->provider) {
|
||||
die("No provider found!");
|
||||
}
|
||||
|
||||
$provider_class = 'Provider\\ERPProviders\\'.ucfirst($this->provider);
|
||||
|
||||
try {
|
||||
|
||||
$this->objERPProvider = (new \ReflectionClass($provider_class))->newInstance($this->erp_config);
|
||||
|
||||
} catch (\ReflectionException $e) {
|
||||
die("aClientERP/erpFactory: ".$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
262
inc/Hura8/System/Controller/aEntityBaseController.php
Normal file
262
inc/Hura8/System/Controller/aEntityBaseController.php
Normal file
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Controller;
|
||||
|
||||
use Hura8\Interfaces\iEntityLanguageModel;
|
||||
use Hura8\Interfaces\iEntityModel;
|
||||
use Hura8\System\Security\DataClean;
|
||||
use Hura8\System\Security\DataType;
|
||||
use Hura8\Traits\ClassCacheTrait;
|
||||
|
||||
|
||||
/**
|
||||
* @description shared controller for aAdminEntityBaseController and aPublicEntityBaseController
|
||||
* DO NOT EXTEND THIS CLASS from other classes
|
||||
*/
|
||||
abstract class aEntityBaseController
|
||||
{
|
||||
use ClassCacheTrait;
|
||||
|
||||
/* @var iEntityModel $iEntityModel */
|
||||
protected $iEntityModel;
|
||||
|
||||
/* @var ?iEntityLanguageModel $iEntityLanguageModel */
|
||||
protected $iEntityLanguageModel = null;
|
||||
|
||||
protected $view_language = LANGUAGE;
|
||||
|
||||
public function __construct(
|
||||
iEntityModel $iEntityModel,
|
||||
?iEntityLanguageModel $iEntityLanguageModel = null
|
||||
) {
|
||||
|
||||
$this->iEntityModel = $iEntityModel;
|
||||
|
||||
if(!$this->isDefaultLanguage() && $iEntityLanguageModel instanceof iEntityLanguageModel) {
|
||||
$this->iEntityLanguageModel = $iEntityLanguageModel;
|
||||
|
||||
// only controller allow to control the language for the model
|
||||
$this->iEntityLanguageModel->setLanguage($this->view_language);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getLanguage() {
|
||||
return $this->view_language;
|
||||
}
|
||||
|
||||
|
||||
public function isDefaultLanguage() {
|
||||
return IS_DEFAULT_LANGUAGE;
|
||||
}
|
||||
|
||||
|
||||
public function getFilterConditions() : array {
|
||||
return array_merge(
|
||||
$this->baseFilterConditions(),
|
||||
$this->extendFilterConditions()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// this is supposed to be overwritten by the extending class if required
|
||||
protected function extendFilterConditions() : array {
|
||||
return [];
|
||||
}
|
||||
|
||||
// just here to show the reserved keys. That's all
|
||||
protected function baseFilterConditions() {
|
||||
$base_filter = [
|
||||
'q' => getRequest("q", ''), // keyword search
|
||||
'q_options' => [
|
||||
'field_filters' => [],
|
||||
'fulltext_fields' => [],
|
||||
'limit_result' => 2000
|
||||
], // q_options as in iSearch->find($keyword, array $field_filters = [], array $fulltext_fields = ["keywords"], $limit_result = 2000)
|
||||
'featured' => getRequestInt("featured", 0), // 1|-1
|
||||
'status' => getRequestInt("status", 0), // 1|-1
|
||||
'excluded_ids' => null, // [id1, id2, ...]
|
||||
'included_ids' => null,// [id1, id2, ...]
|
||||
|
||||
// to special filters for language
|
||||
'translated' => getRequestInt("translated", 0), // 1|-1
|
||||
|
||||
// for sorting
|
||||
'sort_by' => getRequest('sort', ''),
|
||||
|
||||
// for pagination, not exactly for filter but put here to reserve the keys
|
||||
'numPerPage' => getRequestInt("show", 20),
|
||||
'page' => getPageId(),
|
||||
];
|
||||
|
||||
if(getRequest("excluded_ids", '') != '') {
|
||||
$base_filter['excluded_ids'] = explode("-", getRequest("excluded_ids", ''));
|
||||
}
|
||||
|
||||
if(getRequest("included_ids", '') != '') {
|
||||
$base_filter['included_ids'] = explode("-", getRequest("included_ids", ''));
|
||||
}
|
||||
|
||||
return $base_filter;
|
||||
}
|
||||
|
||||
|
||||
protected function formatItemInList(array $item_info)
|
||||
{
|
||||
return $item_info;
|
||||
}
|
||||
|
||||
|
||||
protected function formatItemInfo(array $item_info)
|
||||
{
|
||||
return $item_info;
|
||||
}
|
||||
|
||||
|
||||
public function getListByIds(array $list_id, array $condition = array()) : array
|
||||
{
|
||||
$item_list = array_map(function ($item){
|
||||
return $this->formatItemInList($item);
|
||||
}, $this->iEntityModel->getListByIds($list_id, $condition));
|
||||
|
||||
if($this->iEntityLanguageModel) {
|
||||
$item_list_language_info = $this->iEntityLanguageModel->getListByIds($list_id);
|
||||
|
||||
$final_list = [];
|
||||
foreach ($item_list as $item) {
|
||||
$item_language_info = $item_list_language_info[$item['id']] ?? ["not_translated" => true];
|
||||
$final_list[] = array_merge($item, $item_language_info);
|
||||
}
|
||||
|
||||
return $final_list;
|
||||
}
|
||||
|
||||
return $item_list;
|
||||
}
|
||||
|
||||
|
||||
// for extending controller class to validate and clean data in the $raw_filter_condition (ie. from URL)
|
||||
// before sending to model for data querying
|
||||
// extending controller must overwrite this
|
||||
protected function validateAndCleanFilterCondition(array $raw_filter_condition) : array {
|
||||
|
||||
$clean_values = [];
|
||||
|
||||
foreach ($raw_filter_condition as $key => $value) {
|
||||
// default
|
||||
if(is_array($value)) {
|
||||
$clean_values[$key] = DataClean::makeListOfInputSafe($value, DataType::ID);
|
||||
}else{
|
||||
$clean_values[$key] = DataClean::makeInputSafe($value, DataType::ID);
|
||||
}
|
||||
}
|
||||
|
||||
return $clean_values;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description utility to inspect the actual filters which will be used in getList
|
||||
* make sure to edit the ::_buildQueryConditionExtend method on the Model so the Model will parse the filters provided by controller here
|
||||
* @param array $raw_filter_condition
|
||||
* @return string[]
|
||||
*/
|
||||
public function getActualFilterCondition(array $raw_filter_condition) : array
|
||||
{
|
||||
return $this->buildFilterQuery($raw_filter_condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description utility to inspect the actual filters which will be used in getList by Model
|
||||
* @param array $raw_filter_condition
|
||||
* @return string[]
|
||||
*/
|
||||
public function getModelFilterCondition(array $raw_filter_condition) : array
|
||||
{
|
||||
return $this->iEntityModel->getQueryCondition($this->buildFilterQuery($raw_filter_condition));
|
||||
}
|
||||
|
||||
|
||||
public function getList(array $raw_filter_condition) : array
|
||||
{
|
||||
|
||||
$filter_condition = $this->buildFilterQuery($raw_filter_condition);
|
||||
//debug_var($filter_condition);
|
||||
|
||||
$item_list = array_map(function ($item){
|
||||
return $this->formatItemInList($item);
|
||||
}, $this->iEntityModel->getList($filter_condition));
|
||||
|
||||
|
||||
if($this->iEntityLanguageModel) {
|
||||
|
||||
$item_list_ids = array_map(function ($item){ return $item['id']; }, $item_list);
|
||||
$item_list_language_info = $this->iEntityLanguageModel->getListByIds($item_list_ids);
|
||||
|
||||
$final_list = [];
|
||||
foreach ($item_list as $item) {
|
||||
$item_language_info = $item_list_language_info[$item['id']] ?? ["not_translated" => true];
|
||||
$final_list[] = array_merge($item, $item_language_info);
|
||||
}
|
||||
|
||||
return $final_list;
|
||||
}
|
||||
|
||||
return $item_list;
|
||||
}
|
||||
|
||||
|
||||
public function getTotal(array $raw_filter_condition) : int
|
||||
{
|
||||
$filter_condition = $this->buildFilterQuery($raw_filter_condition);
|
||||
|
||||
return $this->iEntityModel->getTotal($filter_condition);
|
||||
}
|
||||
|
||||
|
||||
protected function buildFilterQuery(array $raw_filter_condition) : array
|
||||
{
|
||||
$filter_condition = $this->validateAndCleanFilterCondition($raw_filter_condition);
|
||||
|
||||
// special case to filter out which ids have not been translated
|
||||
if(isset($filter_condition['translated']) && $filter_condition['translated'] && $this->iEntityLanguageModel) {
|
||||
if($filter_condition['translated'] == 1) {
|
||||
$filter_condition['included_ids'] = $this->iEntityLanguageModel->getTranslatedIds();
|
||||
}else{
|
||||
$filter_condition['excluded_ids'] = $this->iEntityLanguageModel->getTranslatedIds();
|
||||
}
|
||||
}
|
||||
|
||||
return $filter_condition;
|
||||
}
|
||||
|
||||
|
||||
public function getInfo($id): ?array
|
||||
{
|
||||
if(!$id) return null;
|
||||
|
||||
return self::getCache("getInfo-".$id."-".$this->view_language, function () use ($id){
|
||||
|
||||
$info = $this->iEntityModel->getInfo($id);
|
||||
|
||||
if($this->iEntityLanguageModel && $info) {
|
||||
$item_language_info = $this->iEntityLanguageModel->getInfo($id);
|
||||
//debug_var($item_language_info);
|
||||
|
||||
if($item_language_info) {
|
||||
return $this->formatItemInfo(array_merge($info, $item_language_info));
|
||||
}else{
|
||||
$info["not_translated"] = true;
|
||||
|
||||
return $this->formatItemInfo($info);
|
||||
}
|
||||
}
|
||||
|
||||
return ($info) ? $this->formatItemInfo($info) : null;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
344
inc/Hura8/System/Controller/aExcelDownloadController.php
Normal file
344
inc/Hura8/System/Controller/aExcelDownloadController.php
Normal file
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by Glee Ltd.
|
||||
* User: Hieu
|
||||
* Date: 20-Aug-19
|
||||
* Time: 11:16 AM
|
||||
* Description:
|
||||
*/
|
||||
|
||||
namespace Hura8\System\Controller;
|
||||
|
||||
use Hura8\Interfaces\iExcelDownload;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
|
||||
|
||||
abstract class aExcelDownloadController implements iExcelDownload
|
||||
{
|
||||
protected static $column_names = [
|
||||
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
|
||||
'AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK','AL','AM',
|
||||
'AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ',
|
||||
//...
|
||||
];
|
||||
|
||||
protected $work_sheet_title = "Danh sách"; //
|
||||
protected $export_file_name = '';
|
||||
|
||||
/* @var $objExcel Spreadsheet */
|
||||
protected $objExcel;
|
||||
|
||||
/* @var $currentActiveSheet Worksheet */
|
||||
protected $currentActiveSheet;
|
||||
|
||||
protected $client_excel_col_config = [
|
||||
/*"A" => array(
|
||||
'name' => 'ID Sản phẩm Web',
|
||||
'width' => '10',
|
||||
'data_field_name' => 'id',
|
||||
),
|
||||
"B" => array(
|
||||
'name' => 'Mã kho (SKU)',
|
||||
'width' => '10',
|
||||
'data_field_name' => 'storeSKU',
|
||||
),*/
|
||||
//...
|
||||
];
|
||||
|
||||
protected $field_column_mappings = [
|
||||
//'name' => "A",
|
||||
//'price' => "B",
|
||||
];
|
||||
|
||||
private $header_row_index = 2;
|
||||
|
||||
// hold cach for some operations
|
||||
protected $cache = [];
|
||||
|
||||
protected $format_item_middlewares = []; // list of middleware object to format item
|
||||
|
||||
|
||||
public function __construct($client_config_file_name='', $export_file_name='', $work_sheet_title = '')
|
||||
{
|
||||
if($client_config_file_name) {
|
||||
$this->setColumnConfigUseConfigFile($client_config_file_name);
|
||||
}
|
||||
|
||||
$this->export_file_name = ($export_file_name) ?: "file_".CURRENT_TIME;
|
||||
$this->work_sheet_title = ($work_sheet_title) ?: "Danh sách";
|
||||
}
|
||||
|
||||
|
||||
protected function setColumnConfigUseConfigFile($client_config_file_name) {
|
||||
//this from a config file for each client
|
||||
$client_config_file = "config/client/excel/".$client_config_file_name;
|
||||
if( ! file_exists(ROOT_DIR .'/'. $client_config_file)) {
|
||||
die("Please create config file: ".$client_config_file);
|
||||
}
|
||||
$client_fields_config = include ROOT_DIR .'/'. $client_config_file;
|
||||
|
||||
// auto add excel column names based on fields' index
|
||||
$this->client_excel_col_config = $this->_make_columns(array_values($client_fields_config));
|
||||
|
||||
// create field-col map
|
||||
$this->createFieldColumnMappings();
|
||||
}
|
||||
|
||||
|
||||
private function createFieldColumnMappings() {
|
||||
$field_column_mappings = [];
|
||||
foreach ($this->client_excel_col_config as $column_name => $_prop) {
|
||||
$field_column_mappings[$_prop['data_field_name']] = $column_name;
|
||||
}
|
||||
$this->field_column_mappings = $field_column_mappings;
|
||||
}
|
||||
|
||||
|
||||
public function setColumnConfigManually(array $col_config) {
|
||||
$this->client_excel_col_config = $this->_make_columns($col_config);
|
||||
// create field-col map
|
||||
$this->createFieldColumnMappings();
|
||||
}
|
||||
|
||||
|
||||
protected function _make_columns($fields_config) {
|
||||
$new_array = [];
|
||||
$total_names = sizeof(static::$column_names);
|
||||
foreach ($fields_config as $index => $config) {
|
||||
if($index >= $total_names) break;
|
||||
$new_array[static::$column_names[$index]] = $config;
|
||||
}
|
||||
|
||||
return $new_array;
|
||||
}
|
||||
|
||||
|
||||
public function start(array $options = [
|
||||
"debug_mode" => '', // show-item-list
|
||||
"excelOption" => [],
|
||||
"sheetOption" => [],
|
||||
"sheetStartRowNumber" => 3,
|
||||
"sheetHeaderRowNumber" => 2,
|
||||
"getItemListOption" => [
|
||||
"brand" => [],
|
||||
"category" => [],
|
||||
"page" => 1,
|
||||
"limit" => 100,
|
||||
],
|
||||
"exportFileOption" => [],
|
||||
]) {
|
||||
|
||||
$debug_mode = $options['debug_mode'] ?? false;
|
||||
// debug mode
|
||||
if($debug_mode) {
|
||||
// show item list
|
||||
if($debug_mode == 'show-item-list') {
|
||||
$item_list = $this->getItemList($options['getItemListOption']);
|
||||
print_r($item_list);
|
||||
}
|
||||
|
||||
// show formatted list
|
||||
if($debug_mode == 'show-formatted-list') {
|
||||
$item_list = $this->formatItemList( $this->getItemList($options['getItemListOption']) );
|
||||
print_r($item_list);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// setup
|
||||
if(isset($options['sheetHeaderRowNumber']) && $options['sheetHeaderRowNumber']) {
|
||||
$this->header_row_index = $options['sheetHeaderRowNumber'];
|
||||
}
|
||||
|
||||
$this->createExcelObject($options['excelOption'] ?? []);
|
||||
$this->createActiveSheet(0, $options['sheetOption'] ?? []);
|
||||
|
||||
// num beforeWriteList
|
||||
$this->beforeWriteList();
|
||||
|
||||
// fetch all items and write till the end
|
||||
$has_fetch_all_items = false;
|
||||
$start_row = (isset($options["sheetStartRowNumber"])) ? $options["sheetStartRowNumber"] : 3;
|
||||
|
||||
$item_list = $this->formatItemList( $this->getItemList($options['getItemListOption']) );
|
||||
|
||||
//debug_var($item_list);
|
||||
//exit;
|
||||
|
||||
$has_fetch_all_items = true;
|
||||
$this->writeItemsToExcel($start_row, $item_list);
|
||||
|
||||
/*$getItemListOption = $options['getItemListOption'];
|
||||
$pageIndex = 0;
|
||||
|
||||
while (!$has_fetch_all_items) {
|
||||
// run from page 1->end
|
||||
$pageIndex += 1;
|
||||
$getItemListOption["page"] = $pageIndex;
|
||||
$item_list = $this->getItemList($getItemListOption);
|
||||
|
||||
// flag
|
||||
if(!sizeof($item_list)) {
|
||||
$has_fetch_all_items = true;
|
||||
}
|
||||
|
||||
// else, start write
|
||||
$last_row = $this->writeItemsToExcel($start_row, $item_list);
|
||||
|
||||
// update $start_row
|
||||
$start_row = $last_row;// + 1
|
||||
}*/
|
||||
|
||||
// export
|
||||
if($has_fetch_all_items) {
|
||||
$this->getExcelFile($options['exportFileOption']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// abstract methods
|
||||
protected function beforeWriteList() { }
|
||||
abstract protected function getItemList(array $options);
|
||||
abstract protected function defaultFormatItemInfo(array $item_info, $index =0);
|
||||
|
||||
protected function registerFormatItemInfoMiddleware($middleware_ojb) {
|
||||
$this->format_item_middlewares[] = $middleware_ojb;
|
||||
}
|
||||
|
||||
protected function formatItemInfo(array $item_info, $index = 0) {
|
||||
|
||||
// apply middleware
|
||||
if(sizeof($this->format_item_middlewares)) {
|
||||
foreach ($this->format_item_middlewares as $_middleware) {
|
||||
$item_info = call_user_func($_middleware, $item_info);
|
||||
}
|
||||
} else {
|
||||
$item_info = $this->defaultFormatItemInfo($item_info, $index);
|
||||
}
|
||||
|
||||
return $item_info;
|
||||
}
|
||||
|
||||
|
||||
protected function createExcelObject(array $options)
|
||||
{
|
||||
// Create new Spreadsheet object
|
||||
$this->objExcel = new Spreadsheet();
|
||||
|
||||
// Set properties
|
||||
$this->objExcel->getProperties()->setCreator("Hurasoft");
|
||||
$this->objExcel->getProperties()->setLastModifiedBy("Hurasoft");
|
||||
$this->objExcel->getProperties()->setTitle("Excel Document");
|
||||
$this->objExcel->getProperties()->setSubject("Excel Document");
|
||||
$this->objExcel->getProperties()->setDescription("Tao file excel");
|
||||
}
|
||||
|
||||
|
||||
protected function createActiveSheet($sheet_index = 0, array $options=[]) {
|
||||
// Create a first sheet, representing sales data
|
||||
$this->objExcel->setActiveSheetIndex($sheet_index);
|
||||
$this->currentActiveSheet = $this->objExcel->getActiveSheet();
|
||||
$this->currentActiveSheet->setCellValueExplicit('A1', $this->work_sheet_title, DataType::TYPE_STRING);
|
||||
$this->currentActiveSheet->getStyle('A1')->getFont()->setSize(16);
|
||||
$this->currentActiveSheet->getStyle('A1')->getFont()->setBold(true);
|
||||
|
||||
// Set header row
|
||||
$row_index = $this->header_row_index;
|
||||
foreach ($this->client_excel_col_config as $col_name => $_prop) {
|
||||
$col_width = (isset($_prop['width']) && intval($_prop['width']) > 0) ? intval($_prop['width']) : 15;
|
||||
$this->currentActiveSheet->getColumnDimension($col_name)->setWidth($col_width);
|
||||
$this->currentActiveSheet->setCellValueExplicit($col_name. $row_index, $_prop["name"], DataType::TYPE_STRING);
|
||||
$this->currentActiveSheet->getStyle($col_name . $row_index)->getFont()->setBold(true);
|
||||
}
|
||||
}
|
||||
|
||||
protected function formatItemList(array $item_list)
|
||||
{
|
||||
$new_list = [];
|
||||
foreach ( $item_list as $index => $item_info ) {
|
||||
$new_list[$index] = $this->formatItemInfo($item_info, $index);
|
||||
}
|
||||
|
||||
return $new_list;
|
||||
}
|
||||
|
||||
|
||||
protected function writeItemsToExcel($start_row = 1, array $item_list=[])
|
||||
{
|
||||
$write_row = $start_row;
|
||||
foreach ( $item_list as $index => $item_info ) {
|
||||
|
||||
// write each field to its corresponding columns
|
||||
foreach ($item_info as $_field => $_value) {
|
||||
if( !isset($this->field_column_mappings[$_field])) continue;
|
||||
|
||||
if(is_array($_value)) $_value = serialize($_value);
|
||||
|
||||
$write_column = $this->field_column_mappings[$_field];
|
||||
|
||||
$this->currentActiveSheet->setCellValueExplicit($write_column . $write_row, $_value, DataType::TYPE_STRING);
|
||||
$this->currentActiveSheet->getStyle($write_column . $write_row)->getAlignment()->setWrapText(true);
|
||||
}
|
||||
|
||||
// next rows
|
||||
$write_row += 1;
|
||||
}
|
||||
|
||||
// get the last row
|
||||
return $write_row;
|
||||
}
|
||||
|
||||
|
||||
protected function getExcelFile(array $options)
|
||||
{
|
||||
// write to a local file
|
||||
$local_file = $options['local_file'] ?? '';
|
||||
if($local_file) {
|
||||
$this->_save_to_file($local_file);
|
||||
return true;
|
||||
}
|
||||
|
||||
// default: export to browser to download
|
||||
$this->_export_to_browser();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function cleanUp(){
|
||||
// clean up
|
||||
$this->objExcel->disconnectWorksheets();
|
||||
unset($this->objExcel);
|
||||
}
|
||||
|
||||
protected function _save_to_file($file_path){
|
||||
|
||||
// delete old file if exist
|
||||
if(file_exists($file_path)) {
|
||||
@unlink($file_path);
|
||||
}
|
||||
|
||||
$writer = new Xlsx($this->objExcel);
|
||||
$writer->save($file_path);
|
||||
$this->cleanUp();
|
||||
}
|
||||
|
||||
protected function _export_to_browser(){
|
||||
// Rename sheet
|
||||
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
header('Content-Disposition: attachment;filename="'.$this->export_file_name.'.xlsx"');
|
||||
header('Cache-Control: max-age=0');
|
||||
|
||||
$writer = new Xlsx($this->objExcel);
|
||||
ob_end_clean();
|
||||
$writer->save('php://output');
|
||||
|
||||
$this->cleanUp();
|
||||
exit();
|
||||
}
|
||||
|
||||
}
|
||||
355
inc/Hura8/System/Controller/aExcelUploadController.php
Normal file
355
inc/Hura8/System/Controller/aExcelUploadController.php
Normal file
@@ -0,0 +1,355 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Controller;
|
||||
|
||||
use Hura8\System\ReadExcel;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
||||
|
||||
abstract class aExcelUploadController
|
||||
{
|
||||
|
||||
protected $update_option = [];
|
||||
|
||||
protected static $column_names = [
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
//...
|
||||
];
|
||||
|
||||
protected $file_input_name = "file_excel"; //
|
||||
|
||||
protected $client_excel_col_config = [
|
||||
/*"A" => array(
|
||||
'name' => 'ID Sản phẩm Web',
|
||||
'width' => '10',
|
||||
'data_field_name' => 'id',
|
||||
),
|
||||
"B" => array(
|
||||
'name' => 'Mã kho (SKU)',
|
||||
'width' => '10',
|
||||
'data_field_name' => 'storeSKU',
|
||||
),*/
|
||||
//...
|
||||
];
|
||||
|
||||
protected $field_column_mappings = [
|
||||
//'name' => "A",
|
||||
//'price' => "B",
|
||||
];
|
||||
|
||||
// hold cache for some operations
|
||||
protected $cache = [];
|
||||
|
||||
protected $format_item_middlewares = []; // list of middleware object to format item
|
||||
|
||||
|
||||
public function __construct($client_config_file_name = '', $file_input_name = '', array $update_option = [])
|
||||
{
|
||||
|
||||
if(!$client_config_file_name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//this from a config file for each client
|
||||
$client_config_file = "config/client/excel/" . $client_config_file_name;
|
||||
if (!file_exists(ROOT_DIR . '/' . $client_config_file)) {
|
||||
die("Please create config file: " . $client_config_file);
|
||||
}
|
||||
$client_fields_config = include ROOT_DIR . '/' . $client_config_file;
|
||||
|
||||
if($file_input_name) {
|
||||
$this->file_input_name = $file_input_name;
|
||||
}
|
||||
|
||||
// auto add excel column names based on fields' index
|
||||
$this->client_excel_col_config = $this->_make_columns(array_values($client_fields_config));
|
||||
|
||||
// create field-col map
|
||||
$field_column_mappings = [];
|
||||
foreach ($this->client_excel_col_config as $column_name => $_prop) {
|
||||
if(!$_prop['data_field_name']) continue;
|
||||
|
||||
// skip column which is not for upload
|
||||
if(isset($_prop['for_upload']) && !$_prop['for_upload']) continue;
|
||||
|
||||
$field_column_mappings[$_prop['data_field_name']] = $column_name;
|
||||
}
|
||||
|
||||
$this->field_column_mappings = $field_column_mappings;
|
||||
|
||||
$this->update_option = $update_option;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function updateUpdateOptions(array $update_option = []) {
|
||||
foreach ($update_option as $key => $value) {
|
||||
$this->update_option[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getColumnNotForUpload() {
|
||||
$result = [];
|
||||
foreach ($this->client_excel_col_config as $column_name => $_prop) {
|
||||
// skip column which is not for upload
|
||||
if(isset($_prop['for_upload']) && !$_prop['for_upload']) $result[$column_name] = $_prop['name'] ;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
public function getColumnCanUpdate() {
|
||||
$result = [];
|
||||
foreach ($this->client_excel_col_config as $column_name => $_prop) {
|
||||
// skip column which is not for upload
|
||||
if(isset($_prop['for_upload']) && !$_prop['for_upload']) continue;
|
||||
|
||||
// skip column which is not for update
|
||||
if(isset($_prop['can_update']) && !$_prop['can_update']) continue;
|
||||
|
||||
$result[] = $_prop ;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
protected function getRequiredFields() {
|
||||
$result = [];
|
||||
foreach ($this->client_excel_col_config as $column_name => $_prop) {
|
||||
// skip column which is not for upload
|
||||
if(isset($_prop['required']) && $_prop['required']) $result[] = $_prop['data_field_name'] ;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
protected function _make_columns($fields_config)
|
||||
{
|
||||
$new_array = [];
|
||||
$total_names = sizeof(static::$column_names);
|
||||
foreach ($fields_config as $index => $config) {
|
||||
if ($index >= $total_names) break;
|
||||
$new_array[static::$column_names[$index]] = $config;
|
||||
}
|
||||
|
||||
return $new_array;
|
||||
}
|
||||
|
||||
|
||||
protected function getExcelFileExt($excel_file){
|
||||
$ext = substr(strrchr($excel_file, "."), 1);
|
||||
return (in_array($ext, ["xls", "xlsx"])) ? $ext : false;
|
||||
}
|
||||
|
||||
|
||||
public function start($sheet_start_row = 3, $batch_mode = false, $batch_size=100)
|
||||
{
|
||||
$ext = $this->getExcelFileExt($_FILES[$this->file_input_name]["name"]);
|
||||
if(!$ext) {
|
||||
return [
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid excel file',
|
||||
];
|
||||
}
|
||||
|
||||
$objReadExcel = new ReadExcel($ext);
|
||||
|
||||
$all_rows = $objReadExcel->read(
|
||||
$_FILES[$this->file_input_name]["tmp_name"],
|
||||
$sheet_start_row,
|
||||
$this->field_column_mappings,
|
||||
'',
|
||||
true
|
||||
);
|
||||
|
||||
$this->beforeProcessRows($all_rows);
|
||||
|
||||
$success_row_counter = 0;
|
||||
|
||||
if($batch_mode) {
|
||||
// batch mode
|
||||
$small_batch = [];
|
||||
$counter = 0;
|
||||
foreach ($all_rows as $sheet_index => $sheet_content) {
|
||||
foreach ($sheet_content as $row_id => $row_content) {
|
||||
|
||||
$formatted_info = $this->formatItemInfo($this->convertColToField($row_content));
|
||||
if(!$formatted_info) continue;
|
||||
|
||||
$counter += 1;
|
||||
|
||||
$small_batch[] = $formatted_info;
|
||||
if($counter % $batch_size == 0) {
|
||||
$success_row_counter += $this->processBatchItems($small_batch);
|
||||
// reset
|
||||
$counter = 0;
|
||||
$small_batch = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process the remain
|
||||
if(sizeof($small_batch)) {
|
||||
$success_row_counter += $this->processBatchItems($small_batch);
|
||||
}
|
||||
|
||||
} else {
|
||||
// single item mode
|
||||
foreach ($all_rows as $sheet_index => $sheet_content) {
|
||||
foreach ($sheet_content as $row_index => $row_content) {
|
||||
|
||||
$formatted_info = $this->formatItemInfo($this->convertColToField($row_content));
|
||||
if(!$formatted_info) continue;
|
||||
|
||||
if($this->processItem($formatted_info, $row_index)){
|
||||
$success_row_counter += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//unset($all_rows);
|
||||
|
||||
return [
|
||||
'status' => 'success',
|
||||
'message' => '',
|
||||
'success_counter' => $success_row_counter,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
protected function convertExcelDateValue($value) {
|
||||
// Date value in Excel's cell can be displayed as text or date value, we need to check for both
|
||||
$format_value = $this->formatExcelUploadDate($value);
|
||||
|
||||
if(!$format_value) {
|
||||
// check if it's excel date
|
||||
try {
|
||||
$format_value = (is_numeric($value)) ? date("d-m-Y", Date::excelToTimestamp($value, date_default_timezone_get())) : '';
|
||||
}catch (\Exception $e) {
|
||||
$format_value = '';
|
||||
}
|
||||
}
|
||||
|
||||
return $format_value;
|
||||
}
|
||||
|
||||
|
||||
protected function formatExcelUploadDate($input_date) {
|
||||
$check_date_pattern = "/\d{1,2}-\d{1,2}-\d{4}/i";
|
||||
$format_date = str_replace("/", "-", $input_date);
|
||||
|
||||
if(preg_match($check_date_pattern, $format_date)) {
|
||||
return date("d-m-Y", strtotime($format_date));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function convertExcelHourMinuteValue($value) {
|
||||
$format_value = $this->formatExcelUploadHourMinute($value);
|
||||
|
||||
if(!$format_value) {
|
||||
// check if it's excel date
|
||||
try {
|
||||
$format_value = (is_numeric($value)) ? date("H:i", Date::excelToTimestamp($value, date_default_timezone_get())) : '';
|
||||
}catch (\Exception $e) {
|
||||
$format_value = '';
|
||||
}
|
||||
}
|
||||
|
||||
return $format_value;
|
||||
}
|
||||
|
||||
|
||||
protected function formatExcelUploadHourMinute($input_date) {
|
||||
$check_date_pattern = "/\d{1,2}:\d{1,2}/i";
|
||||
//$format_date = str_replace("/", "-", $input_date);
|
||||
|
||||
if(preg_match($check_date_pattern, $input_date)) {
|
||||
return date("H:i", strtotime($input_date));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ['A' => '12', 'B' => 'ten sp'] => ['id' => '12', 'product_name' => 'ten sp']
|
||||
protected function convertColToField(array $row_content ) {
|
||||
|
||||
if(!$this->field_column_mappings) {
|
||||
return array_values($row_content);
|
||||
}
|
||||
|
||||
$item_info = [];
|
||||
foreach ($this->field_column_mappings as $field => $col) {
|
||||
//if(!isset($row_content[$col])) continue;
|
||||
$item_info[$field] = $row_content[$col];
|
||||
}
|
||||
|
||||
return $item_info;
|
||||
}
|
||||
|
||||
// abstract methods
|
||||
protected function beforeProcessRows(array &$all_read_rows){
|
||||
// default nothing
|
||||
// derived class should overwrite this method
|
||||
}
|
||||
abstract protected function processItem(array $item_info, $row_index=0);
|
||||
abstract protected function processBatchItems(array $item_list);
|
||||
|
||||
/**
|
||||
* @param array $item_info
|
||||
* @return array|null
|
||||
*/
|
||||
abstract protected function formatItemInfoBeforeProcess(array $item_info);
|
||||
|
||||
public function registerFormatItemInfoMiddleware($middleware_ojb)
|
||||
{
|
||||
$this->format_item_middlewares[] = $middleware_ojb;
|
||||
}
|
||||
|
||||
|
||||
protected $date_fields = [];
|
||||
public function setDateFields(array $date_fields) {
|
||||
$this->date_fields = $date_fields;
|
||||
}
|
||||
|
||||
protected $hour_minute_fields = [];
|
||||
public function setHourMinuteFields(array $hour_minute_fields) {
|
||||
$this->hour_minute_fields = $hour_minute_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $item_info
|
||||
* @return array|null
|
||||
*/
|
||||
protected function formatItemInfo(array $item_info)
|
||||
{
|
||||
$copy = $item_info;
|
||||
|
||||
// apply middleware
|
||||
if (sizeof($this->format_item_middlewares)) {
|
||||
foreach ($this->format_item_middlewares as $_middleware) {
|
||||
$copy = call_user_func($_middleware, $copy);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->date_fields as $field) {
|
||||
$copy[$field] = $this->convertExcelDateValue($item_info[$field]);
|
||||
}
|
||||
|
||||
foreach ($this->hour_minute_fields as $field) {
|
||||
$copy[$field] = $this->convertExcelHourMinuteValue($item_info[$field]);
|
||||
}
|
||||
|
||||
return $this->formatItemInfoBeforeProcess($copy);
|
||||
}
|
||||
|
||||
}
|
||||
81
inc/Hura8/System/Controller/aPublicEntityBaseController.php
Normal file
81
inc/Hura8/System/Controller/aPublicEntityBaseController.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Controller;
|
||||
|
||||
use Hura8\Interfaces\iPublicEntityController;
|
||||
|
||||
abstract class aPublicEntityBaseController extends aEntityBaseController implements iPublicEntityController
|
||||
{
|
||||
|
||||
public function getListByIds(array $list_id, array $condition = array()) : array
|
||||
{
|
||||
$item_list = array_map(function ($item){
|
||||
return $this->formatItemInList($item);
|
||||
}, $this->iEntityModel->getListByIds($list_id, $condition));
|
||||
|
||||
if($this->iEntityLanguageModel) {
|
||||
$item_list_language_info = $this->iEntityLanguageModel->getListByIds($list_id);
|
||||
|
||||
$final_list = [];
|
||||
foreach ($item_list as $item) {
|
||||
$item_language_info = isset($item_list_language_info[$item['id']]) ? $item_list_language_info[$item['id']] : ["not_translated" => true];
|
||||
$final_list[] = array_merge($item, $item_language_info);
|
||||
}
|
||||
|
||||
return $final_list;
|
||||
}
|
||||
|
||||
return $item_list;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function getList(array $condition) : array
|
||||
{
|
||||
$copy = $condition;
|
||||
|
||||
// public items must have status=1
|
||||
$copy['status'] = 1;
|
||||
|
||||
return parent::getList($copy);
|
||||
}
|
||||
|
||||
|
||||
public function getTotal(array $condition) : int
|
||||
{
|
||||
$copy = $condition;
|
||||
|
||||
// public items must have status=1
|
||||
$copy['status'] = 1;
|
||||
|
||||
return parent::getTotal($copy);
|
||||
}
|
||||
|
||||
|
||||
public function getInfo($id): ?array
|
||||
{
|
||||
if(!$id) return null;
|
||||
|
||||
return self::getCache("getInfo-".$id."-".$this->view_language, function () use ($id){
|
||||
|
||||
$info = $this->iEntityModel->getInfo($id);
|
||||
|
||||
if($this->iEntityLanguageModel && $info) {
|
||||
$item_language_info = $this->iEntityLanguageModel->getInfo($id);
|
||||
if($item_language_info) {
|
||||
return $this->formatItemInfo(array_merge($info, $item_language_info));
|
||||
}else{
|
||||
$info["not_translated"] = true;
|
||||
|
||||
return $this->formatItemInfo($info);
|
||||
}
|
||||
}
|
||||
|
||||
return ($info) ? $this->formatItemInfo($info) : null;
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
270
inc/Hura8/System/Controller/bFileHandle.php
Normal file
270
inc/Hura8/System/Controller/bFileHandle.php
Normal file
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Controller;
|
||||
|
||||
use Hura8\Interfaces\AppResponse;
|
||||
use Hura8\Interfaces\FileHandleInfo;
|
||||
use Hura8\Interfaces\FileHandleResponse;
|
||||
use Hura8\System\FileSystem;
|
||||
use Hura8\System\HuraImage;
|
||||
use Hura8\System\IDGenerator;
|
||||
use League\MimeTypeDetection\FinfoMimeTypeDetector;
|
||||
|
||||
/**
|
||||
* @date 16-Jan-2024
|
||||
* @description based-class to handling files, this is used for :
|
||||
* - FileUpload class
|
||||
* - CopyFileFromUrl class
|
||||
* ...
|
||||
*
|
||||
*/
|
||||
|
||||
abstract class bFileHandle
|
||||
{
|
||||
|
||||
static $image_extensions = array(".jpg",".jpeg",".gif", ".png", ".webp", '.avif', ".ico");
|
||||
|
||||
protected $permit_file_extensions = array(
|
||||
".jpg",".jpeg",".gif", ".png", ".webp", '.avif',
|
||||
".doc", ".docx",".xls",".xlsx", ".ppt", ".pdf",
|
||||
".rar", ".zip",
|
||||
//".avi",".mov",".mpg", ".wmv", ".mpeg",
|
||||
//".mp3",".mp4", ".ogg", ".oga", ".wav", ".wma"
|
||||
);
|
||||
|
||||
protected $permit_mine_types = array(
|
||||
'image/jpeg', 'image/png','image/gif', 'image/webp', 'image/avif',
|
||||
|
||||
// zip files
|
||||
'application/zip', 'application/x-zip-compressed', 'multipart/x-zip', 'application/x-compressed',
|
||||
);
|
||||
|
||||
// full system's path where the file will be finally stored or it's currently there for processing
|
||||
// example: /var/www/html/domain.com/public_html/media/product/
|
||||
protected $target_dir = "";
|
||||
|
||||
// directory appears to the public
|
||||
// example: /media/product/
|
||||
protected $public_dir = "";
|
||||
|
||||
protected $tmp_dir = ROOT_DIR . "/var/tmp_upload/"; // system tmp dir to store uploaded file for security examination before moving to target_dir
|
||||
protected $tmp_folder = ""; // tmp folder per session in the $tmp_dir to hold user's files. This folder will be removed when operation is complete
|
||||
|
||||
protected $setup_success = false;
|
||||
|
||||
/**
|
||||
* @param string $target_dir will be created if not exist i.e. media/product/ or media/user_upload/date('d-m-Y')
|
||||
* @param ?array $permit_file_extensions null to allow all default file extensions
|
||||
*/
|
||||
public function __construct( string $target_dir, ?array $permit_file_extensions = null ) {
|
||||
|
||||
if(is_array($permit_file_extensions)) {
|
||||
$this->permit_file_extensions = $permit_file_extensions;
|
||||
}
|
||||
|
||||
if($target_dir) {
|
||||
$this->target_dir = PUBLIC_DIR . DIRECTORY_SEPARATOR. $target_dir;
|
||||
$this->public_dir = "/".$target_dir;
|
||||
}
|
||||
|
||||
$setup_res = $this->setUp();
|
||||
if($setup_res->getStatus() == AppResponse::SUCCESS) {
|
||||
$this->setup_success = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description (rename if set) and resize file
|
||||
* @param string $name
|
||||
* @param string $new_name
|
||||
* @param array $resized_sizes [] array('small' => ['width' => 100, 'height' => 100], 'large' => ['width' => 200, 'height' => 200] )
|
||||
* @return array|false
|
||||
*/
|
||||
public function resizeFile(string $name, string $new_name = '', array $resized_sizes = []) {
|
||||
|
||||
if($new_name) {
|
||||
$renamed = $this->renameFile($name, $new_name);
|
||||
if(!$renamed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//$file_name = $renamed['file_name'];
|
||||
//$public_path = $renamed['public_path'];
|
||||
$local_path = $renamed['local_path'];
|
||||
}else{
|
||||
//$file_name = $name;
|
||||
//$public_path = $this->public_dir . "/".$name;
|
||||
$local_path = $this->target_dir . "/" . $name;
|
||||
}
|
||||
|
||||
$objHuraImage = new HuraImage();
|
||||
|
||||
list(, $expected_files, ) = $objHuraImage->resize($local_path, $resized_sizes);
|
||||
|
||||
return $expected_files;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description we can rename the uploaded file to new file name, for example: we want product's image have the format [PRODUCT_ID]-name
|
||||
* @param string $name
|
||||
* @param string $new_name
|
||||
* @return array | false
|
||||
*/
|
||||
public function renameFile(string $name, string $new_name) {
|
||||
if(@rename($this->target_dir . "/" . $name, $this->target_dir . "/" . $new_name)){
|
||||
return [
|
||||
"file_name" => $new_name,
|
||||
"public_path" => $this->public_dir . "/".$new_name,
|
||||
"local_path" => $this->target_dir . "/" . $new_name,
|
||||
];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// public utility
|
||||
public static function getFileExtension($file_name) {
|
||||
return strtolower(strrchr($file_name,"."));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description run clean up after finish using the FileHandle instance
|
||||
*/
|
||||
public function cleanUp() {
|
||||
if($this->tmp_folder) {
|
||||
FileSystem::removeDir($this->tmp_folder);
|
||||
}
|
||||
}
|
||||
|
||||
protected function processFile(
|
||||
$original_file_name,
|
||||
$original_file_tmp_name, // temporary uploaded file as in case of $_FILES[$input_file_name]["tmp_name"]
|
||||
$fixed_file_name="",
|
||||
$max_file_size=0
|
||||
) : FileHandleResponse {
|
||||
|
||||
if(!$original_file_name) {
|
||||
return new FileHandleResponse(AppResponse::ERROR, 'no file', null);
|
||||
}
|
||||
|
||||
$file_size = filesize($original_file_tmp_name);
|
||||
if($max_file_size > 0 && $max_file_size < $file_size) {
|
||||
return new FileHandleResponse(AppResponse::ERROR, 'Size is too large: '.round($file_size/1000).'KB', null);
|
||||
}
|
||||
|
||||
//validate extension
|
||||
$file_ext = self::getFileExtension($original_file_name);
|
||||
|
||||
if(!in_array($file_ext, $this->permit_file_extensions)) {
|
||||
return new FileHandleResponse(AppResponse::ERROR, "Type ".$file_ext." is not allowed!", null);
|
||||
}
|
||||
|
||||
$file_name = substr($original_file_name, 0, strrpos($original_file_name,"."));
|
||||
$file_name = preg_replace("/[^a-z0-9_-]/i","", $file_name);
|
||||
$file_name = substr($file_name, 0, 50); // max- length
|
||||
$clean_file_name = ($fixed_file_name) ?: $file_name . $file_ext;
|
||||
|
||||
$tmp_file_path = $this->tmp_folder . "/". $clean_file_name;
|
||||
$return_data = null;
|
||||
|
||||
//debug_var([$original_data, $tmp_file_path]);
|
||||
if(@rename($original_file_tmp_name, $tmp_file_path)){
|
||||
|
||||
$is_file_image = (in_array($file_ext, static::$image_extensions ));
|
||||
|
||||
if($is_file_image) {
|
||||
list($width, $height) = getimagesize($tmp_file_path);
|
||||
}else{
|
||||
$width = 0;
|
||||
$height = 0;
|
||||
}
|
||||
|
||||
$detector = new FinfoMimeTypeDetector();
|
||||
$mimeType = $detector->detectMimeTypeFromPath($tmp_file_path);
|
||||
|
||||
// if image, we re-create and optimize it
|
||||
if($is_file_image) {
|
||||
if(in_array($mimeType, $this->permit_mine_types)) {
|
||||
$objHuraImage = new HuraImage();
|
||||
if($objHuraImage->create($tmp_file_path, $this->target_dir . DIRECTORY_SEPARATOR . $clean_file_name)){
|
||||
$return_data = new FileHandleInfo([
|
||||
"file_name" => $clean_file_name,
|
||||
"public_path" => $this->public_dir . "/".$clean_file_name,
|
||||
"local_path" => $this->target_dir . "/" . $clean_file_name,
|
||||
"mime_type" => $mimeType,
|
||||
"file_size" => $file_size,
|
||||
"file_ext" => $file_ext,
|
||||
"width" => $width,
|
||||
"height" => $height,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}elseif(@rename ($tmp_file_path, $this->target_dir . DIRECTORY_SEPARATOR . $clean_file_name )) {
|
||||
$return_data = new FileHandleInfo([
|
||||
"file_name" => $clean_file_name,
|
||||
"public_path" => $this->public_dir . "/".$clean_file_name,
|
||||
"local_path" => $this->target_dir . "/" . $clean_file_name,
|
||||
"mime_type" => $mimeType,
|
||||
"file_size" => $file_size,
|
||||
"file_ext" => $file_ext,
|
||||
"width" => 0,
|
||||
"height" => 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// delete tmp file on server
|
||||
if(file_exists($original_file_tmp_name)) {
|
||||
@unlink($original_file_tmp_name);
|
||||
}
|
||||
|
||||
if($return_data) {
|
||||
return new FileHandleResponse(AppResponse::SUCCESS, 'Success', $return_data);
|
||||
}
|
||||
|
||||
return new FileHandleResponse(AppResponse::ERROR, 'Unknown', null);
|
||||
}
|
||||
|
||||
|
||||
protected function setUp(): AppResponse
|
||||
{
|
||||
// check target dir
|
||||
if($this->target_dir && !is_dir($this->target_dir)) {
|
||||
@mkdir($this->target_dir, 0755, true);
|
||||
}
|
||||
|
||||
if(!file_exists($this->target_dir)) {
|
||||
return new AppResponse(AppResponse::ERROR, $this->target_dir.' not exists');
|
||||
}
|
||||
|
||||
// create tmp_folder to upload file to
|
||||
$this->tmp_folder = $this->create_tmp_folder();
|
||||
if(!$this->tmp_folder) {
|
||||
return new AppResponse(AppResponse::ERROR, "Check ".$this->tmp_dir." and make sure it exists and writable");
|
||||
}
|
||||
|
||||
return new AppResponse(AppResponse::SUCCESS);
|
||||
}
|
||||
|
||||
|
||||
protected function create_tmp_folder() : ?string {
|
||||
$tmp_folder = $this->tmp_dir . IDGenerator::createStringId(5);
|
||||
if(!@mkdir($tmp_folder, 0777, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// retest
|
||||
if(!$tmp_folder || !is_dir($tmp_folder)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $tmp_folder;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
56
inc/Hura8/System/CopyFileFromUrl.php
Normal file
56
inc/Hura8/System/CopyFileFromUrl.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
|
||||
use Hura8\Interfaces\AppResponse;
|
||||
use Hura8\Interfaces\FileHandleResponse;
|
||||
use Hura8\System\Controller\bFileHandle;
|
||||
|
||||
|
||||
class CopyFileFromUrl extends bFileHandle
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $file_url
|
||||
* @param int $max_file_size max file size in bytes accepts, default = 1MB
|
||||
* @param string $fixed_file_name set uploaded name to this fixed name (ie. [item_id].jpg) so old file will be replaced
|
||||
* @return FileHandleResponse
|
||||
*/
|
||||
public function start(string $file_url, string $fixed_file_name='', int $max_file_size = 1000000) : FileHandleResponse
|
||||
{
|
||||
|
||||
if(!Url::isUrlValid($file_url)) {
|
||||
return new FileHandleResponse(AppResponse::ERROR, $file_url." is not a valid url");
|
||||
}
|
||||
|
||||
if(!$this->setup_success) {
|
||||
return new FileHandleResponse(AppResponse::ERROR, "Fail to setup");
|
||||
}
|
||||
|
||||
$original_file_name = substr($file_url, strrpos($file_url, "/"));
|
||||
$original_file_path = $this->tmp_folder . "/". $original_file_name;
|
||||
|
||||
$url_content = Url::getUrlContent($file_url);
|
||||
if(!$url_content || !@file_put_contents($original_file_path, $url_content)) {
|
||||
return new FileHandleResponse(AppResponse::ERROR, "Fail to copy");
|
||||
}
|
||||
|
||||
/*if(!@copy($file_url, $original_file_path)) {
|
||||
return new FileHandleResponse(AppResponse::ERROR, "Fail to copy");
|
||||
}*/
|
||||
|
||||
$result = $this->processFile(
|
||||
$original_file_name,
|
||||
$original_file_path,
|
||||
$fixed_file_name,
|
||||
$max_file_size
|
||||
);
|
||||
|
||||
unset($url_content);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
66
inc/Hura8/System/CronProcess.php
Normal file
66
inc/Hura8/System/CronProcess.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
final class CronProcess
|
||||
{
|
||||
private $pid;
|
||||
|
||||
/* @var FileSystem $objFileSystem */
|
||||
private $objFileSystem;
|
||||
|
||||
const LOG_DIR = VAR_DIR . '/log/';
|
||||
private $max_allow_run_time = 1800; // in seconds - max cron run time.
|
||||
|
||||
public function __construct($pid, $max_allow_run_time = 1800) {
|
||||
$this->pid = $pid;
|
||||
$this->objFileSystem = new FileSystem(self::LOG_DIR);
|
||||
$this->max_allow_run_time = $max_allow_run_time;
|
||||
}
|
||||
|
||||
public function getPidFile() {
|
||||
return $this->objFileSystem->getFile($this->pid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the process is running. True if:
|
||||
* - has pid file
|
||||
* - the file's create-time within the time() and time() - $this->max_allow_run_time
|
||||
*
|
||||
* Else: False and remove pid file if exist
|
||||
* @return bool
|
||||
*/
|
||||
public function isRunning() : bool {
|
||||
|
||||
$file_exist = ($this->objFileSystem->getFile($this->pid));
|
||||
if(!$file_exist) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$last_modified_time = $this->objFileSystem->getFileLastModifiedTime($this->pid);
|
||||
|
||||
if($last_modified_time > 0 && $last_modified_time < time() - $this->max_allow_run_time) {
|
||||
|
||||
echo "Pid File exists but too old. Trying to auto-delete now". PHP_EOL;
|
||||
|
||||
if($this->objFileSystem->delete($this->pid)){
|
||||
echo "File is deleted successfully". PHP_EOL;
|
||||
}else{
|
||||
echo "Auto-delete fails. File needs to be removed manually. Path: ". $this->objFileSystem->getFilename($this->pid). PHP_EOL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function start() {
|
||||
return $this->objFileSystem->writeFile($this->pid, time());
|
||||
}
|
||||
|
||||
public function finish() {
|
||||
return $this->objFileSystem->delete($this->pid);
|
||||
}
|
||||
|
||||
}
|
||||
251
inc/Hura8/System/Email.php
Normal file
251
inc/Hura8/System/Email.php
Normal file
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
use Hura8\Components\Template\Controller\TemplateController;
|
||||
use Hura8\Interfaces\iEmail;
|
||||
use Hura8\System\Controller\SettingController;
|
||||
use Provider\Adman;
|
||||
use Swift_Mailer;
|
||||
use Swift_Message;
|
||||
use Swift_SmtpTransport;
|
||||
|
||||
|
||||
class Email implements iEmail {
|
||||
|
||||
protected $config = [
|
||||
"mail_method" => "",
|
||||
"from_name" => "",
|
||||
"from_email" => "",
|
||||
"reply_to" => "",
|
||||
"subject" => "",
|
||||
"content" => "",
|
||||
];
|
||||
|
||||
/* @var SettingController $objSettingController */
|
||||
protected $objSettingController;
|
||||
|
||||
//construct
|
||||
public function __construct() {
|
||||
// default
|
||||
if(defined("MAIL_METHOD")) $this->config["mail_method"] = MAIL_METHOD;
|
||||
if(defined("MAIL_NAME")) $this->config["from_name"] = MAIL_NAME;
|
||||
if(defined("MAIL_ADDRESS")) $this->config["from_email"] = MAIL_ADDRESS;
|
||||
|
||||
if(!$this->config["from_name"]) $this->config["from_name"] = str_replace('www.', '', $_SERVER['HTTP_HOST']);
|
||||
|
||||
$this->objSettingController = new SettingController();
|
||||
|
||||
$email_settings = $this->objSettingController->getList([
|
||||
'email_from_default',
|
||||
]);
|
||||
|
||||
$email_from = (isset($email_settings['email_from_default']) && $email_settings['email_from_default']) ? $email_settings['email_from_default'] : '' ;
|
||||
if($email_from && self::validate_email($email_from) && $email_from != MAIL_ADDRESS) {
|
||||
$this->setUp([
|
||||
"from_email" => $email_from,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function setUp(array $config)
|
||||
{
|
||||
// over-ride default config
|
||||
if(sizeof($config)) {
|
||||
foreach ($config as $key => $value) {
|
||||
$this->config[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function send(array $to_emails, $subject, $content, array $key_values = [])
|
||||
{
|
||||
if(!$this->checkConfig()) return [
|
||||
'status' => 'error',
|
||||
'message' => "Config errors: ".\json_encode($this->config),
|
||||
];
|
||||
|
||||
// send email using a template in email/
|
||||
$this->config['subject'] = TemplateController::parse(null, $subject, $key_values);
|
||||
$this->config['content'] = TemplateController::parse(null, $content, $key_values);
|
||||
|
||||
try {
|
||||
switch ($this->config["mail_method"]){
|
||||
case "huraserver";
|
||||
$result = $this->sendEmailFromHura($to_emails);
|
||||
break;
|
||||
case "queue";
|
||||
$result = $this->addQueue($to_emails);
|
||||
break;
|
||||
case "adman";
|
||||
$result = $this->sendFromAdman($to_emails);
|
||||
break;
|
||||
case "smtp";
|
||||
$result = $this->sendFromSmtp($to_emails);
|
||||
break;
|
||||
default;
|
||||
$result = null;
|
||||
}
|
||||
}catch (\Exception $exception) {
|
||||
$result = "Error: ".$exception->getMessage();
|
||||
}
|
||||
|
||||
if($result) {
|
||||
$this->log(array_merge($this->config, [
|
||||
'to_emails' => $to_emails,
|
||||
'result' => $result,
|
||||
]));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function checkConfig() {
|
||||
return (
|
||||
$this->config["mail_method"]
|
||||
&& $this->config["from_name"]
|
||||
&& $this->config["from_email"]
|
||||
&& self::validate_email($this->config["from_email"])
|
||||
);
|
||||
}
|
||||
|
||||
protected function addQueue(array $to_emails){
|
||||
// todo:
|
||||
/*foreach($to_email_list as $single_email){
|
||||
$this->createQueue(array(
|
||||
"message_id" => 0,
|
||||
"from_email" => $from_mail,
|
||||
"from_name" => $from_name,
|
||||
"reply_to" => $reply_to_email,
|
||||
"to_email" => $single_email,
|
||||
"to_name" => $to_name,
|
||||
"subject" => $title,
|
||||
"content" => $content,
|
||||
"source" => "",
|
||||
));
|
||||
}*/
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function sendFromAdman(array $to_emails){
|
||||
if(!defined("ADMAN_API_KEY") || ADMAN_API_KEY == '') return 'ADMAN_API_KEY not set';
|
||||
if(!defined("ADMAN_USER_DOMAIN") || ADMAN_USER_DOMAIN == '') return 'ADMAN_USER_DOMAIN not set';
|
||||
|
||||
$recipients = array_map(function ($email){
|
||||
return [
|
||||
'to_email' => $email,
|
||||
'to_name' => '',
|
||||
'personal_settings' => array(),
|
||||
];
|
||||
}, $to_emails);
|
||||
|
||||
$payload = array(
|
||||
'message' => array(
|
||||
'from_email' => $this->config['from_email'],
|
||||
'from_name' => $this->config['from_name'],
|
||||
'reply_to_email' => $this->config['reply_to'],
|
||||
'subject' => $this->config['subject'],
|
||||
'settings' => array(),
|
||||
'schedule_go_time_utc' => 0,
|
||||
'content_html' => $this->config['content'],
|
||||
'content_plain' => '',
|
||||
),
|
||||
'recipients' => $recipients
|
||||
);
|
||||
|
||||
return Adman::send(ADMAN_USER_DOMAIN, ADMAN_API_KEY, $payload);
|
||||
}
|
||||
|
||||
|
||||
protected function sendFromSmtp(array $to_emails){
|
||||
|
||||
if(!SMTP_HOST || !SMTP_USERNAME || !SMTP_PASSWORD) {
|
||||
return 'Smtp credentials not set';
|
||||
}
|
||||
|
||||
// https://swiftmailer.symfony.com/docs/introduction.html
|
||||
// Create the Transport
|
||||
$transport = (new Swift_SmtpTransport(SMTP_HOST, SMTP_PORT, SMTP_SECURE))
|
||||
->setUsername(SMTP_USERNAME)
|
||||
->setPassword(SMTP_PASSWORD)
|
||||
;
|
||||
$transport->setTimeout(10);
|
||||
|
||||
// Create the Mailer using your created Transport
|
||||
$mailer = new Swift_Mailer($transport);
|
||||
|
||||
// Create a message
|
||||
$message = (new Swift_Message())
|
||||
->setSubject($this->config['subject'])
|
||||
->setFrom($this->config['from_email'], $this->config['from_name'])
|
||||
->setTo($to_emails)
|
||||
->setBody($this->config['content'], 'text/html')
|
||||
;
|
||||
|
||||
// Send the message
|
||||
return $mailer->send($message);
|
||||
}
|
||||
|
||||
|
||||
protected function sendEmailFromHura(array $to_emails){
|
||||
$rebuild_to_emails = [];
|
||||
$max_email_allow = 3;
|
||||
$counter = 0;
|
||||
foreach ($to_emails as $email) {
|
||||
if(self::validate_email($email)) {
|
||||
$counter += 1;
|
||||
if($counter >= $max_email_allow) break;
|
||||
$rebuild_to_emails[] = ['email' => $email, 'name' => ''];
|
||||
}
|
||||
}
|
||||
|
||||
if(!sizeof($rebuild_to_emails)) {
|
||||
return [
|
||||
'status' => 'error',
|
||||
'message' => 'Error. No valid emails to be sent',
|
||||
];
|
||||
}
|
||||
|
||||
$data = [
|
||||
'title' => $this->config['subject'],
|
||||
'content' => $this->config['content'],
|
||||
'to_emails' => $rebuild_to_emails,
|
||||
'from_email' => $this->config['from_email'],
|
||||
'from_name' => $this->config['from_name'],
|
||||
'reply_to_email' => $this->config['reply_to'],
|
||||
'reply_to_name' => '',
|
||||
'client_id' => CLIENT_ID,
|
||||
'domain' => CURRENT_DOMAIN,
|
||||
];
|
||||
|
||||
// protect
|
||||
$msg_hash = sha1(serialize($data).'asdas32');
|
||||
$data['msg_hash'] = $msg_hash;
|
||||
|
||||
$headers = [];
|
||||
|
||||
return curl_post(HURAMAIL_RECEIVE_ENDPOINT, $data, $headers);
|
||||
}
|
||||
|
||||
protected function createQueue(array $config) {
|
||||
// todo:
|
||||
}
|
||||
|
||||
//email log after sending an email and remove from queue
|
||||
protected function log(array $log_content){
|
||||
/*$db = ConnectDB::getInstance('');
|
||||
$db->insert(
|
||||
'email_log',
|
||||
[
|
||||
'payload' => \json_encode($log_content),
|
||||
'create_time' => CURRENT_TIME,
|
||||
]
|
||||
);*/
|
||||
}
|
||||
|
||||
//validate email
|
||||
public static function validate_email($email){
|
||||
return filter_var($email, FILTER_VALIDATE_EMAIL);
|
||||
}
|
||||
|
||||
}
|
||||
47
inc/Hura8/System/Export.php
Normal file
47
inc/Hura8/System/Export.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
//used to export content to download
|
||||
namespace Hura8\System;
|
||||
|
||||
|
||||
class Export
|
||||
{
|
||||
public function export($content, $file_name, $type)
|
||||
{
|
||||
if($type=='xls' || $type=='xlsx') {
|
||||
$this->setHeaderXLS($file_name);
|
||||
}
|
||||
else if($type=='doc') {
|
||||
$this->setHeaderDoc($file_name);
|
||||
}
|
||||
else if($type=='csv') {
|
||||
$this->setHeaderCSV($file_name);
|
||||
}
|
||||
|
||||
echo $content;
|
||||
}
|
||||
|
||||
// method for Excel file
|
||||
protected function setHeaderXLS($file_name)
|
||||
{
|
||||
header("Content-type: application/ms-excel");
|
||||
header("Content-Disposition: attachment; filename=$file_name");
|
||||
header("Pragma: no-cache");
|
||||
header("Expires: 0");
|
||||
}
|
||||
|
||||
// method for Doc file
|
||||
protected function setHeaderDoc($file_name)
|
||||
{
|
||||
header("Content-type: application/x-ms-download");
|
||||
header("Content-Disposition: attachment; filename=$file_name");
|
||||
header('Cache-Control: public');
|
||||
}
|
||||
|
||||
// method for CSV file
|
||||
protected function setHeaderCSV($file_name)
|
||||
{
|
||||
header("Content-type: application/csv");
|
||||
header("Content-Disposition: inline; filename=$file_name");
|
||||
}
|
||||
}
|
||||
158
inc/Hura8/System/ExportExcelUseTemplate.php
Normal file
158
inc/Hura8/System/ExportExcelUseTemplate.php
Normal file
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataType;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Hyperlink;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
||||
|
||||
|
||||
class ExportExcelUseTemplate {
|
||||
|
||||
/* @var $spreadsheet Spreadsheet */
|
||||
private $spreadsheet = null;
|
||||
|
||||
/* @var $activeSheet Worksheet */
|
||||
private $activeSheet = null;
|
||||
|
||||
private $active_sheet_index = 0;
|
||||
|
||||
|
||||
public function __construct($tpl_file = '') {
|
||||
if(file_exists($tpl_file)) {
|
||||
$this->_createActiveSheet($tpl_file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function _createActiveSheet($tpl_file) {
|
||||
$this->spreadsheet = IOFactory::load($tpl_file);
|
||||
|
||||
$this->spreadsheet->setActiveSheetIndex($this->active_sheet_index);
|
||||
|
||||
$this->activeSheet = $this->spreadsheet->getActiveSheet();
|
||||
}
|
||||
|
||||
|
||||
public function setRowHeight($row_number, $height_pt) {
|
||||
$this->activeSheet->getRowDimension($row_number)->setRowHeight($height_pt, 'pt');
|
||||
}
|
||||
|
||||
|
||||
public function removeRow($row_id) {
|
||||
if(!$this->activeSheet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->activeSheet->removeRow($row_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set a cell value.
|
||||
*
|
||||
* @param string $cell Coordinate of the cell, eg: 'A1'
|
||||
* @param mixed $data Value of the cell
|
||||
* @param string $pDataType Explicit data type, see DataType::TYPE_*
|
||||
*
|
||||
*/
|
||||
public function writeToCell($cell, $data, $pDataType) {
|
||||
if(!$this->activeSheet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->activeSheet->setCellValueExplicit($cell, $data, $pDataType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int $start_row
|
||||
* @param array $row_list array( ['field-1' => value, 'field-2' => value, 'field-3' => value], ['field-1' => value, 'field-2' => value,],)
|
||||
* @param array $map_column_fields map excel column to row field . Example: array('A' => 'name', 'B' => 'price', ...)
|
||||
* @param array $number_fields list of data fields that their values will be write to cell as number. Example ['price', 'qty', ]
|
||||
* @param array $hyperlink_fields list of data fields that their values will be added hyperlink
|
||||
* @return false|int|mixed|string
|
||||
*/
|
||||
public function writeRowList($start_row = 1, array $row_list =[], array $map_column_fields = [], array $number_fields = [], array $hyperlink_fields = []) {
|
||||
|
||||
if(!$this->activeSheet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// add new rows to hold new data
|
||||
for($i = 0; $i< sizeof($row_list); $i++) {
|
||||
$this->activeSheet->insertNewRowBefore($start_row + 1);
|
||||
}
|
||||
|
||||
$last_insert_row_index = 0;
|
||||
|
||||
foreach ($row_list as $index => $item) {
|
||||
|
||||
$last_insert_row_index = $start_row + $index;
|
||||
|
||||
foreach ($map_column_fields as $column => $field) {
|
||||
|
||||
$cell_cor = $column . $last_insert_row_index;
|
||||
|
||||
if(array_key_exists($field, $number_fields)) {
|
||||
$this->activeSheet->setCellValueExplicit($cell_cor, $item[$field], DataType::TYPE_NUMERIC);
|
||||
}else{
|
||||
$this->activeSheet->setCellValueExplicit($cell_cor, $item[$field], DataType::TYPE_STRING);
|
||||
}
|
||||
|
||||
// set hyperlink
|
||||
if(array_key_exists($field, $hyperlink_fields)) {
|
||||
|
||||
// link can be a field of the item or a fixed value
|
||||
$hyperlink_url = (isset($item[$hyperlink_fields[$field]])) ? $item[$hyperlink_fields[$field]] : $hyperlink_fields[$field];
|
||||
|
||||
$this->activeSheet->setHyperlink($cell_cor, new Hyperlink($hyperlink_url, 'Mở link để xem tại website'));
|
||||
}
|
||||
|
||||
// format number
|
||||
if(array_key_exists($field, $number_fields)) {
|
||||
|
||||
// check format type
|
||||
if($number_fields[$field]) {
|
||||
$this->activeSheet->getStyle($cell_cor)->getNumberFormat()->setFormatCode($number_fields[$field]);
|
||||
}
|
||||
|
||||
}else{
|
||||
$this->activeSheet->getStyle($cell_cor)->getAlignment()->setWrapText(true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return $last_insert_row_index;
|
||||
}
|
||||
|
||||
|
||||
public function exportToBrowser($file_name='export') {
|
||||
if(!$this->spreadsheet) {
|
||||
die("No file");
|
||||
}
|
||||
|
||||
// export
|
||||
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
header('Content-Disposition: attachment;filename="'.$file_name.'-'.time().'.xlsx"');
|
||||
header('Cache-Control: max-age=0');
|
||||
|
||||
$writer = new Xlsx($this->spreadsheet);
|
||||
ob_end_clean();
|
||||
$writer->save('php://output');
|
||||
|
||||
// clean up
|
||||
$this->spreadsheet->disconnectWorksheets();
|
||||
$this->spreadsheet = null;
|
||||
}
|
||||
|
||||
}
|
||||
212
inc/Hura8/System/FileSystem.php
Normal file
212
inc/Hura8/System/FileSystem.php
Normal file
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
class FileSystem
|
||||
{
|
||||
/**
|
||||
* The cache directory.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $directory;
|
||||
|
||||
/** @var int */
|
||||
private $umask;
|
||||
|
||||
/**
|
||||
* @param string $directory The cache directory.
|
||||
* @param int $umask
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($directory, $umask = 0002)
|
||||
{
|
||||
// YES, this needs to be *before* createPathIfNeeded()
|
||||
if (! is_int($umask)) {
|
||||
throw new \Exception(sprintf(
|
||||
'The umask parameter is required to be integer, was: %s',
|
||||
gettype($umask)
|
||||
));
|
||||
}
|
||||
|
||||
$this->umask = $umask;
|
||||
|
||||
if (! $this->createPathIfNeeded($directory)) {
|
||||
throw new \Exception(sprintf(
|
||||
'The directory "%s" does not exist and could not be created.',
|
||||
$directory
|
||||
));
|
||||
}
|
||||
|
||||
if (! is_writable($directory)) {
|
||||
throw new \Exception(sprintf(
|
||||
'The directory "%s" is not writable.',
|
||||
$directory
|
||||
));
|
||||
}
|
||||
|
||||
// YES, this needs to be *after* createPathIfNeeded()
|
||||
$this->directory = realpath($directory);
|
||||
|
||||
//$this->directoryStringLength = strlen($this->directory);
|
||||
//$this->isRunningOnWindows = defined('PHP_WINDOWS_VERSION_BUILD');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description extract a zip file after it has been uploaded to the server
|
||||
* @param string $filename name of the zip file (abc.zip)
|
||||
* @param string $uploaded_zip_path path where it's uploaded to i.e /usr/.../tmp_upload/
|
||||
* @param string $extract_target_path path where the zip files will be extracted to , if not provided the $uploaded_zip_path will be used
|
||||
* @return array [extracted_folder, test_folder_exist]
|
||||
*/
|
||||
public static function unzip(string $filename, string $uploaded_zip_path, string $extract_target_path = '') {
|
||||
$extract_path = $extract_target_path ?: $uploaded_zip_path;
|
||||
|
||||
$zip = new \ZipArchive();
|
||||
$x = $zip->open($uploaded_zip_path . DIRECTORY_SEPARATOR . $filename);
|
||||
if ($x === true) {
|
||||
$zip->extractTo($extract_path); // change this to the correct site path
|
||||
$zip->close();
|
||||
}
|
||||
@unlink($uploaded_zip_path . DIRECTORY_SEPARATOR . $filename);
|
||||
|
||||
$expected_result = $extract_path . DIRECTORY_SEPARATOR . str_replace(".zip", "", $filename);
|
||||
|
||||
return array($expected_result, file_exists($expected_result));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description scan a folder recursively and return all files with sub-path
|
||||
* @param string $folder full path /usr/local/.../
|
||||
* @param array $file_list
|
||||
*/
|
||||
public static function scanDirRecursive(string $folder, array &$file_list = []) {
|
||||
$dir = opendir($folder);
|
||||
while(( $file = readdir($dir)) ) {
|
||||
if (( $file != '.' ) && ( $file != '..' )) {
|
||||
if ( is_dir($folder. '/' . $file) ) {
|
||||
FileSystem::scanDirRecursive($folder. '/' . $file, $file_list);
|
||||
}
|
||||
else {
|
||||
//$file_list[] = str_replace(PUBLIC_DIR . "/", "", $folder .'/'. $file);
|
||||
$file_list[] = $folder .'/'. $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($dir);
|
||||
}
|
||||
|
||||
//remove directory recursively
|
||||
public static function removeDir($dir) {
|
||||
if (is_dir($dir)) {
|
||||
$objects = scandir($dir);
|
||||
foreach ($objects as $object) {
|
||||
if ($object != "." && $object != "..") {
|
||||
if (is_dir($dir."/".$object)) {
|
||||
self::removeDir($dir."/".$object);
|
||||
}
|
||||
else {
|
||||
@unlink($dir."/".$object);
|
||||
}
|
||||
}
|
||||
}
|
||||
@rmdir($dir);
|
||||
}
|
||||
}
|
||||
|
||||
public function getFile(string $filename)
|
||||
{
|
||||
$full_filepath = $this->getFilename($filename);
|
||||
if(file_exists($full_filepath)) {
|
||||
return file_get_contents($full_filepath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getFileLastModifiedTime(string $filename)
|
||||
{
|
||||
$full_filepath = $this->getFilename($filename);
|
||||
if(file_exists($full_filepath)) {
|
||||
return filemtime($full_filepath);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a string content to file in an atomic way.
|
||||
*
|
||||
* @param string $filename Path to the file where to write the data.
|
||||
* @param string $content The content to write
|
||||
*
|
||||
* @return bool TRUE on success, FALSE if path cannot be created, if path is not writable or an any other error.
|
||||
*/
|
||||
public function writeFile(string $filename, string $content)
|
||||
{
|
||||
$full_filepath = $this->getFilename($filename);
|
||||
$filepath = pathinfo($full_filepath, PATHINFO_DIRNAME);
|
||||
|
||||
if (! $this->createPathIfNeeded($filepath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! is_writable($filepath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tmpFile = tempnam($filepath, 'swap');
|
||||
@chmod($tmpFile, 0666 & (~$this->umask));
|
||||
|
||||
if (file_put_contents($tmpFile, $content) !== false) {
|
||||
|
||||
@chmod($tmpFile, 0666 & (~$this->umask));
|
||||
if (@rename($tmpFile, $full_filepath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@unlink($tmpFile);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function delete($filename)
|
||||
{
|
||||
$full_filepath = $this->getFilename($filename);
|
||||
|
||||
return @unlink($full_filepath) || ! file_exists($full_filepath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFilename($filename)
|
||||
{
|
||||
return $this->directory
|
||||
. DIRECTORY_SEPARATOR
|
||||
. $filename ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create path if needed.
|
||||
*
|
||||
* @return bool TRUE on success or if path already exists, FALSE if path cannot be created.
|
||||
*/
|
||||
private function createPathIfNeeded(string $path)
|
||||
{
|
||||
if (! is_dir($path)) {
|
||||
if (@mkdir($path, 0755 & (~$this->umask), true) === false && ! is_dir($path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
110
inc/Hura8/System/FileUpload.php
Normal file
110
inc/Hura8/System/FileUpload.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
use Hura8\Interfaces\AppResponse;
|
||||
use Hura8\Interfaces\FileHandleResponse;
|
||||
use Hura8\System\Controller\bFileHandle;
|
||||
|
||||
/**
|
||||
* @date 25-Oct-2023
|
||||
* Single upload handle for whole application
|
||||
* - Manage security
|
||||
* - upload file to desired destination
|
||||
* - return the file path for other modules to use
|
||||
*
|
||||
* Security:
|
||||
* - restrict file extension and mine type
|
||||
*
|
||||
* Sample code:
|
||||
|
||||
$objFileUpload = new FileUpload(
|
||||
"media/banner", //.date("Y-m-d"),
|
||||
array(".jpg",".jpeg",".gif", ".png", ".webp", '.avif')
|
||||
);
|
||||
$upload_result = $objFileUpload->handleUpload("file");
|
||||
|
||||
if($upload_result->getStatus() == 'ok') {
|
||||
$file_info = $upload_result->getData();
|
||||
|
||||
$file_name = $file_info->file_name;
|
||||
$file_ext = $file_info->file_ext;
|
||||
$public_path = $file_info->public_path;
|
||||
|
||||
$renamed = $objFileUpload->renameUploadedFile($file_name, $item_id . $file_ext);
|
||||
|
||||
if($renamed) {
|
||||
$new_file_name = $renamed['file_name'];
|
||||
$new_public_path = $renamed['public_path'];
|
||||
}
|
||||
|
||||
}
|
||||
*
|
||||
*/
|
||||
class FileUpload extends bFileHandle
|
||||
{
|
||||
|
||||
/**
|
||||
* @description version of handleUpload to support multiple files using array input
|
||||
* example: <input type="file" size="30" name="multiple_file[]" /> instead of <input type="file" size="30" name="single_file" />
|
||||
*
|
||||
* @param string $input_file_name name of <input type="file" size="30" name="multiple_file[]" /> => use: multiple_file
|
||||
* @param int $max_file_size max file size in bytes accepts, default = 1MB
|
||||
* @return FileHandleResponse[]
|
||||
*/
|
||||
public function handleMultipleUpload(string $input_file_name, int $max_file_size = 1000000) : array
|
||||
{
|
||||
$upload_result = [];
|
||||
for($i = 0; $i < sizeof($_FILES[$input_file_name]['name']); $i++){
|
||||
$original_uploaded_file_name = $_FILES[$input_file_name]["name"][$i];
|
||||
$original_uploaded_data = $_FILES[$input_file_name]["tmp_name"][$i];
|
||||
|
||||
$upload_result[] = $this->processFile(
|
||||
$original_uploaded_file_name,
|
||||
$original_uploaded_data,
|
||||
'',
|
||||
$max_file_size
|
||||
);
|
||||
}
|
||||
|
||||
// clean up
|
||||
$this->cleanUp();
|
||||
|
||||
return $upload_result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $input_file_name name of <input type="file" size="30" name="upload_file" /> (which is upload_file)
|
||||
* @param int $max_file_size max file size in bytes accepts, default = 1MB
|
||||
* @param string $fixed_file_name set uploaded name to this fixed name (ie. [item_id].jpg) so old file will be replaced
|
||||
* @return FileHandleResponse
|
||||
*/
|
||||
public function handleUpload(string $input_file_name, string $fixed_file_name='', int $max_file_size = 1000000) : FileHandleResponse
|
||||
{
|
||||
if(!$this->setup_success) {
|
||||
return new FileHandleResponse(AppResponse::ERROR, "Fail to setup");
|
||||
}
|
||||
|
||||
if(!$input_file_name || !isset($_FILES[$input_file_name])) {
|
||||
return new FileHandleResponse('error', 'no file', null);
|
||||
}
|
||||
|
||||
$original_uploaded_file_name = $_FILES[$input_file_name]["name"] ?? "";
|
||||
$original_uploaded_data = $_FILES[$input_file_name]["tmp_name"];
|
||||
|
||||
$upload_result = $this->processFile(
|
||||
$original_uploaded_file_name,
|
||||
$original_uploaded_data,
|
||||
$fixed_file_name,
|
||||
$max_file_size
|
||||
);
|
||||
|
||||
// clean up
|
||||
$this->cleanUp();
|
||||
|
||||
return $upload_result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
190
inc/Hura8/System/Firewall.php
Normal file
190
inc/Hura8/System/Firewall.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
// Simple firewall to protect DDOS attack
|
||||
// It's better to use
|
||||
// https://github.com/terrylinooo/shieldon
|
||||
// https://github.com/antonioribeiro/firewall
|
||||
// https://packagist.org/packages/middlewares/firewall
|
||||
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
|
||||
use Jaybizzle\CrawlerDetect\CrawlerDetect;
|
||||
|
||||
class Firewall
|
||||
{
|
||||
|
||||
const MAX_CONCURRENT_VISIT_ALLOW = 5;
|
||||
const WHITE_LIST_IP = ROOT_DIR . "/config/system/white_list_ip.txt";
|
||||
const BAN_IP_LIST = ROOT_DIR . "/config/build/ban_ip_list.txt";
|
||||
const TEMPORARY_BAN_DURATION = 60; //seconds
|
||||
|
||||
/* @var $instance Firewall */
|
||||
protected static $instance;
|
||||
protected $log_dir = ROOT_DIR . '/cache/firewall/log/';
|
||||
protected $ban_dir = ROOT_DIR . '/cache/firewall/ban/';
|
||||
private $user_ip = '';
|
||||
protected $white_list = [
|
||||
// google bot ips
|
||||
// bing bots
|
||||
// others..
|
||||
];
|
||||
|
||||
protected function __construct() {
|
||||
// todo: use Redis for performance instead of directory
|
||||
/*$this->log_dir = ROOT_DIR . '/cache/firewall/log/';
|
||||
$this->ban_dir = ROOT_DIR . '/cache/firewall/ban/';
|
||||
if(!file_exists($this->log_dir)) {
|
||||
mkdir($this->log_dir, 0755, true);
|
||||
}
|
||||
if(!file_exists($this->ban_dir)) {
|
||||
mkdir($this->ban_dir, 0755, true);
|
||||
}*/
|
||||
$this->user_ip = getIpAddress();
|
||||
|
||||
if(file_exists(self::WHITE_LIST_IP)) {
|
||||
$this->white_list = array_filter(explode("\n", file_get_contents(self::WHITE_LIST_IP)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static function getInstance() {
|
||||
if(!static::$instance) {
|
||||
static::$instance = new self();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
// check if current user agent is an crawler
|
||||
public function isCrawler() : bool {
|
||||
$objCrawlerDetect = new CrawlerDetect();
|
||||
//echo $objCrawlerDetect->getUserAgent();
|
||||
// https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers
|
||||
return $objCrawlerDetect->isCrawler();
|
||||
}
|
||||
|
||||
public function monitor() {
|
||||
// todo:
|
||||
return false;
|
||||
|
||||
$total_concurrent_visit = $this->logIp($this->user_ip);
|
||||
if($total_concurrent_visit > self::MAX_CONCURRENT_VISIT_ALLOW) {
|
||||
// ban if not in whitelist
|
||||
if(!$this->isWhiteListed()) {
|
||||
$this->banTemporary($this->user_ip, self::TEMPORARY_BAN_DURATION);
|
||||
}
|
||||
|
||||
die("System overload");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function stopIfBanned() {
|
||||
if($this->isIPBanned()) {
|
||||
die("Perhaps, this website is not for you.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function isWhiteListed() {
|
||||
$filter = new IPFilter($this->white_list);
|
||||
return $filter->check($this->user_ip);
|
||||
}
|
||||
|
||||
protected function isIPBanned() {
|
||||
|
||||
// check whitelist
|
||||
if($this->isWhiteListed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check temporary ban
|
||||
/*$file_id = $this->ban_dir . $this->user_ip.'.data';
|
||||
if(file_exists($file_id)) {
|
||||
$lift_time = file_get_contents($file_id);
|
||||
// lift_time is not over yet
|
||||
if($lift_time > time()) {
|
||||
return true;
|
||||
}
|
||||
}*/
|
||||
|
||||
// permanent ban
|
||||
$config_file = static::BAN_IP_LIST;
|
||||
if(@file_exists( $config_file )) {
|
||||
$list_ips = @file_get_contents( $config_file );
|
||||
$list_ip_arr = array_filter(explode("\n", $list_ips));
|
||||
$filter = new IPFilter($list_ip_arr);
|
||||
|
||||
return $filter->check($this->user_ip);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ban an IP temporarily
|
||||
protected function banTemporary($ip, $time) {
|
||||
$file_id = $this->ban_dir . $ip.'.data';
|
||||
@file_put_contents($file_id, time() + $time);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// only allow 1 of these formats:
|
||||
// - Full IP: 127.0.0.1
|
||||
// - Wildcard: 172.0.0.*
|
||||
// - Mask: 125.0.0.1/24
|
||||
// - Range: 125.0.0.1-125.0.0.9
|
||||
public static function validateIP($ip) {
|
||||
// - Full IP: 127.0.0.1
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// - Wildcard: 172.0.0.*
|
||||
if (strpos($ip, "*") !== false && preg_match("/^([0-9]+)\.([0-9]+)\.(([0-9]+)|\*)\.(([0-9]+)|\*)$/i", $ip)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// - Mask: 125.0.0.1/24
|
||||
if (strpos($ip, "/") !== false && preg_match("/^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)\/([0-9]+)$/i", $ip)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// - Range: 125.0.0.1-125.0.0.9
|
||||
if (strpos($ip, "-") !== false ) {
|
||||
list($begin, $end) = explode('-', $ip);
|
||||
return filter_var($begin, FILTER_VALIDATE_IP) && filter_var($end, FILTER_VALIDATE_IP);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private function logIp($ip) {
|
||||
$file_id = $this->log_dir . $ip.'.data';
|
||||
$time_track = CURRENT_TIME - CURRENT_TIME % 5; // track within 5 seconds,
|
||||
$data = [];
|
||||
if(file_exists($file_id)) {
|
||||
$data = \json_decode(file_get_contents($file_id), true);
|
||||
}
|
||||
|
||||
if(isset($data[$time_track])) {
|
||||
$data[$time_track] = $data[$time_track] + 1;
|
||||
}else{
|
||||
$data[$time_track] = 1;
|
||||
}
|
||||
|
||||
// only log for current time to prevent large $data's size
|
||||
// our purpose is only to detect if this IP is brutally forceing the system (too many visits per time)
|
||||
|
||||
@file_put_contents($file_id, \json_encode($data));
|
||||
|
||||
return $data[$time_track];
|
||||
}
|
||||
|
||||
}
|
||||
245
inc/Hura8/System/HtmlParser.php
Normal file
245
inc/Hura8/System/HtmlParser.php
Normal file
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* Date: 11/29/17
|
||||
* Time: 10:48 PM
|
||||
*/
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
class HtmlParser
|
||||
{
|
||||
private $source_html = '';
|
||||
|
||||
public function __construct() {
|
||||
|
||||
}
|
||||
|
||||
public function setSource($source_html){
|
||||
$this->source_html = str_replace(array("\n", "\r", "\t")," ", $source_html);
|
||||
}
|
||||
|
||||
//@$boundary_pattern: pattern to find a smaller boundary-block within source_html so that our content does not wander around
|
||||
/**
|
||||
* @param $pattern_arr_or_str array|string ['pattern1', 'pattern2'] or 'pattern'
|
||||
* @param bool $match_once
|
||||
* @param bool $find_image
|
||||
* @param string $boundary_pattern
|
||||
* @return array|bool
|
||||
*/
|
||||
public function extract($pattern_arr_or_str, $match_once = true, $find_image = false, $boundary_pattern = "") {
|
||||
if(is_array($pattern_arr_or_str)) {
|
||||
foreach ($pattern_arr_or_str as $pattern){
|
||||
$result = $this->extractSinglePattern($pattern, $match_once, $find_image, $boundary_pattern);
|
||||
if(is_array($result) && isset($result['result'])) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// default is string
|
||||
return $this->extractSinglePattern($pattern_arr_or_str, $match_once, $find_image, $boundary_pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $pattern string
|
||||
* @param bool $match_once
|
||||
* @param bool $find_image
|
||||
* @param string $boundary_pattern
|
||||
* @return array|bool
|
||||
*/
|
||||
protected function extractSinglePattern($pattern, $match_once = true, $find_image = false, $boundary_pattern = "") {
|
||||
|
||||
$elements = $this->getCodeElement($pattern);
|
||||
if(!$elements["code"]) return false;
|
||||
|
||||
$match = array();
|
||||
$source_html = $this->source_html;
|
||||
//found boundary if pattern exist
|
||||
if($boundary_pattern && preg_match("@".$boundary_pattern."@i", $source_html, $match)) {
|
||||
$source_html = $match[1];
|
||||
}
|
||||
|
||||
if($match_once) {
|
||||
if(preg_match("@".$elements["code"]."@i", $source_html, $match)){
|
||||
//echo $match[1];
|
||||
return array(
|
||||
"result" => $this->cleanHtmlBlock($match[1], $elements),
|
||||
"images" => ($find_image) ? $this->extractImages($match[1]) : null
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$results = array();
|
||||
if(preg_match_all("@".$elements["code"]."@", $source_html, $match)){
|
||||
foreach ($match[1] as $html_block ) {
|
||||
$results[] = array(
|
||||
"result" => $this->cleanHtmlBlock($html_block, $elements),
|
||||
"images" => ($find_image) ? $this->extractImages($html_block) : null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function cleanHtmlBlock($html_block, $elements) {
|
||||
|
||||
if($elements["removed"]){
|
||||
$arrayRemover = array_filter(explode(";",$elements["removed"]));
|
||||
foreach($arrayRemover as $char_removed){
|
||||
$char_removed = stripslashes(trim($char_removed));
|
||||
$html_block = str_replace($char_removed.";","", $html_block); //sometimes
|
||||
$html_block = str_replace($char_removed,"", $html_block);
|
||||
$html_block = preg_replace("{".addslashes($char_removed)."}","", $html_block);
|
||||
}
|
||||
}
|
||||
|
||||
if($elements["sepa"]){
|
||||
$html_block = strrchr($html_block, $elements["sepa"]);
|
||||
$html_block = str_replace($elements["sepa"],"", $html_block);
|
||||
}
|
||||
|
||||
if($elements["extra_url"] && $html_block) $html_block = $elements["extra_url"] . trim($html_block);
|
||||
|
||||
//thay the cum tu source bang cum tu tuy chon
|
||||
if($elements["invalid"]){
|
||||
$arrayReplace = array_filter(explode(";", $elements["invalid"]));
|
||||
foreach($arrayReplace as $replace_group){
|
||||
$replace_group = stripslashes($replace_group);
|
||||
//echo $replace_group;
|
||||
$replace_group_a = explode("#",$replace_group);
|
||||
$html_block = str_replace(trim($replace_group_a[0]),trim($replace_group_a[1]), $html_block);
|
||||
$html_block = preg_replace("{".addslashes(trim($replace_group_a[0]))."}",trim($replace_group_a[1]), $html_block);
|
||||
}
|
||||
}
|
||||
|
||||
return trim($html_block);
|
||||
}
|
||||
|
||||
|
||||
private function extractImages($source_html){
|
||||
$img_match = array();
|
||||
if(preg_match_all("/img(.*?)?src\s*=\s*\\\\?[\'\"]?([+:%\/\?~=&;\(\),|!._a-zA-Z0-9-]*)[\'\"]?/i", $source_html, $img_match)){
|
||||
return array_unique($img_match[2]);
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
//$code_line =
|
||||
// singlePro | chandau(.*?)chancuoi | remove_word'];word2 | sepa | prepend_after | invalid_list
|
||||
// pagePro | chandau(.*?)chancuoi | remove_word'];word2 | sepa | prepend_after | invalid_list
|
||||
/**
|
||||
* @param $code_line string
|
||||
* @return array|false
|
||||
*/
|
||||
private function getCodeElement($code_line){
|
||||
if(!$code_line) return false;
|
||||
|
||||
$element = explode("|", trim($code_line));
|
||||
$result = array();
|
||||
if(array_key_exists(0, $element)){
|
||||
$result["type"] = trim($element[0]); //pagePro, singlePro
|
||||
}else $result["type"] = "";
|
||||
|
||||
if(array_key_exists(1, $element)){
|
||||
$result["code"] = trim($element[1]);
|
||||
}else $result["code"] = "";
|
||||
|
||||
if(array_key_exists(2,$element)){
|
||||
$result["removed"] = trim($element[2]);
|
||||
}else $result["removed"] = "";
|
||||
|
||||
if(array_key_exists(3,$element)){
|
||||
$result["sepa"] = trim($element[3]);
|
||||
}else $result["sepa"] = "";
|
||||
|
||||
if(array_key_exists(4,$element)){
|
||||
$result["extra_url"] = trim($element[4]);
|
||||
}else $result["extra_url"] = "";
|
||||
|
||||
if(array_key_exists(5,$element)){
|
||||
$result["invalid"] = trim($element[5]); //for images
|
||||
}else $result["invalid"] = "";
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
private function buildFullUrl($url, $base_url){
|
||||
if(!$base_url) return $url;
|
||||
if(strlen($url) < 2) return "";
|
||||
|
||||
if(preg_match("/(http|www.|javascript|mailto|ymsgr)/i",$url)){
|
||||
return $url;
|
||||
}else{
|
||||
return $this->convert_to_absolute( $base_url, $url );
|
||||
}
|
||||
}
|
||||
|
||||
private function convert_to_absolute($absolute, $relative) {
|
||||
$p = parse_url($relative);
|
||||
$first_letter = $relative[0];
|
||||
$last_letter = substr($relative, strlen($relative) -1, 1);
|
||||
|
||||
if(array_key_exists("scheme",$p) || strpos($relative,"www.") !== false || substr($relative, 0, 2) == '//') return $relative; //it's absolute
|
||||
|
||||
if(in_array($first_letter,array('?',';'))) return str_replace(strrchr($absolute,$first_letter),"",$absolute) . $relative;
|
||||
|
||||
if($first_letter == "#") return $absolute;//already crawled this page
|
||||
|
||||
extract(parse_url($absolute));
|
||||
|
||||
$path = (isset($path)) ? $path : "";
|
||||
$path = (strrchr($absolute,"/")!="/") ? ((dirname($path) != "\\") ? dirname($path) : "") : $path;
|
||||
|
||||
if($first_letter == '/') {
|
||||
$cparts = array_filter(explode("/", $relative));
|
||||
}
|
||||
else {
|
||||
|
||||
$aparts = array_filter(explode("/", $path));
|
||||
//print_r($aparts);
|
||||
$rparts = array_filter(explode("/", $relative));
|
||||
//print_r($rparts);
|
||||
$cparts = array_merge($aparts, $rparts);
|
||||
//print_r($cparts);
|
||||
if(!preg_match("/[a-z0-9]/i",$first_letter)){
|
||||
foreach($cparts as $i => $part) {
|
||||
if($part == '.') {
|
||||
$cparts[$i] = null;
|
||||
}
|
||||
if($part == '..') {
|
||||
$cparts[$i] = '';
|
||||
if(array_key_exists($i - 1,$cparts)){
|
||||
if($cparts[$i - 1] != null) $cparts[$i - 1] = null;
|
||||
else if(array_key_exists($i - 3,$cparts)) $cparts[$i - 3] = null; // in case ../../
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
$cparts = array_filter($cparts);
|
||||
}
|
||||
|
||||
$path = implode("/", $cparts);
|
||||
if($last_letter == '/') $path .= "/";
|
||||
|
||||
|
||||
$url = "";
|
||||
if($scheme) {
|
||||
$url = "$scheme://";
|
||||
}
|
||||
|
||||
if($host) {
|
||||
$url .= "$host/";
|
||||
}
|
||||
$url .= $path;
|
||||
return $url;
|
||||
}
|
||||
|
||||
}
|
||||
180
inc/Hura8/System/HuraImage.php
Normal file
180
inc/Hura8/System/HuraImage.php
Normal file
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
use Intervention\Image\ImageManager as InterventionImage;
|
||||
|
||||
// 12-Nov-2022
|
||||
// class to manipulate images: create/resize/convert/water-mark ...
|
||||
// reference guide:
|
||||
// https://image.intervention.io/v2/api/make
|
||||
//
|
||||
|
||||
final class HuraImage
|
||||
{
|
||||
|
||||
/* @var $objInterventionImage InterventionImage */
|
||||
private $objInterventionImage;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
$this->objInterventionImage = new InterventionImage();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* optimize file size of an image and resize its width to the max_width it
|
||||
*
|
||||
* @param string $file_dir __DIR__."/media/product/'
|
||||
* @param string $file_name file.jpg
|
||||
* @param int $max_width
|
||||
* @return bool
|
||||
*/
|
||||
public function optimizeFile(string $file_dir, string $file_name, int $max_width = 1200): bool
|
||||
{
|
||||
$file_path = $file_dir . DIRECTORY_SEPARATOR . $file_name;
|
||||
$random_str = IDGenerator::createStringId(10);
|
||||
$clone_file = $file_dir . DIRECTORY_SEPARATOR . $random_str. '-' . $file_name;
|
||||
|
||||
if(!copy($file_path, $clone_file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$img = $this->objInterventionImage->make($clone_file);
|
||||
|
||||
list ( $img_width ) = getimagesize($clone_file);
|
||||
if($img_width > $max_width) {
|
||||
$img->resize($max_width, null, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
})->save($file_path, 90);
|
||||
}else{
|
||||
$img->save($file_path, 90);
|
||||
}
|
||||
|
||||
// then delete the clone
|
||||
unlink($clone_file);
|
||||
|
||||
return file_exists($file_path);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* create image to local system
|
||||
* copy from https://image.intervention.io/v2/api/make
|
||||
* @param mixed $source
|
||||
* Source to create an image from. The method responds to the following input types:
|
||||
string - Path of the image in filesystem.
|
||||
string - URL of an image (allow_url_fopen must be enabled).
|
||||
string - Binary image data.
|
||||
string - Data-URL encoded image data.
|
||||
string - Base64 encoded image data.
|
||||
resource - PHP resource of type gd. (when using GD driver)
|
||||
object - Imagick instance (when using Imagick driver)
|
||||
object - Intervention\Image\Image instance
|
||||
object - SplFileInfo instance (To handle Laravel file uploads via Symfony\Component\HttpFoundation\File\UploadedFile)
|
||||
*
|
||||
* @param string $saved_file_path __DIR__."/media/product/file.jpg'
|
||||
* @return bool
|
||||
*/
|
||||
public function create($source, $saved_file_path) {
|
||||
|
||||
try {
|
||||
$this->objInterventionImage->make($source)->save($saved_file_path, 90);
|
||||
}catch (\Exception $e) {
|
||||
// NotReadableException: Unsupported image type. GD/PHP installation does not support WebP format
|
||||
@copy($source, $saved_file_path);
|
||||
}
|
||||
|
||||
return file_exists($saved_file_path);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* https://image.intervention.io/v2/api/encode
|
||||
* @param $local_file_path
|
||||
* @param string $format jpg | png | gif | webp | data-url
|
||||
* @return bool
|
||||
*/
|
||||
public function convertFileFormat($local_file_path, $format ) {
|
||||
|
||||
// if same format as local file, stop
|
||||
$file_ext = strtolower(substr(strrchr($local_file_path, '.'), 1));
|
||||
if($file_ext == $format) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$saved_file_path = substr($local_file_path, 0, strrpos($local_file_path,".") ) . ".". $format;
|
||||
|
||||
$this->objInterventionImage->make($local_file_path)
|
||||
->encode($format, 100)
|
||||
->save($saved_file_path, 90);
|
||||
|
||||
return file_exists($saved_file_path);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* guide: https://image.intervention.io/v2/api/resize
|
||||
* @param $local_file_path
|
||||
* @param array $size_key_dimension array('small' => ['width' => 100, 'height' => 100], 'large' => ['width' => 200, 'height' => 200] )
|
||||
* @param string $resized_file_directory
|
||||
* @return array
|
||||
*/
|
||||
public function resize($local_file_path, array $size_key_dimension, $resized_file_directory = '') {
|
||||
|
||||
$stored_directory = ($resized_file_directory) ?: substr($local_file_path, 0, strrpos($local_file_path,"/") ) ;
|
||||
//echo "<p>stored_directory: ".$stored_directory;
|
||||
|
||||
$file_name = substr(strrchr($local_file_path,"/"), 1);
|
||||
//echo "<p>file_name: ".$file_name;
|
||||
|
||||
$expected_files = [];
|
||||
foreach ($size_key_dimension as $key => $dimension) {
|
||||
|
||||
$resized_file_name = $key. IMAGE_FILE_SEPARATOR . $file_name;
|
||||
$expected_files[] = $resized_file_name;
|
||||
|
||||
$saved_file_path = $stored_directory . "/".$resized_file_name;
|
||||
$new_width = $dimension['width'] ?? null;
|
||||
$new_height = $dimension['height'] ?? null;
|
||||
|
||||
$img = $this->objInterventionImage->make($local_file_path);
|
||||
|
||||
if(!$new_width || !$new_height) {
|
||||
$img->resize($new_width, $new_height, function ($constraint) {
|
||||
$constraint->aspectRatio();
|
||||
})->save($saved_file_path, 90);
|
||||
}else{
|
||||
$img->resize($new_width, $new_height)->save($saved_file_path, 90);
|
||||
}
|
||||
|
||||
$img->destroy();
|
||||
}
|
||||
|
||||
//echo "<p>Expected files: ".\json_encode($expected_files);
|
||||
$exist_all = true;
|
||||
foreach ($expected_files as $_file) {
|
||||
if(!file_exists($stored_directory . "/". $_file)) {
|
||||
$exist_all = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return array($exist_all, $expected_files, $stored_directory);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $original_file_path
|
||||
* @param $img_watermark_path
|
||||
* @param string $position
|
||||
*/
|
||||
public function watermark($original_file_path, $img_watermark_path, $position='bottom-right') {
|
||||
$img = $this->objInterventionImage->make($original_file_path);
|
||||
$watermark = $this->objInterventionImage->make($img_watermark_path);
|
||||
$img->insert($watermark, $position, 10, 10);
|
||||
//$img->bac ($destinationPath.'/'.$fileName);
|
||||
$img->save($original_file_path, 100);
|
||||
}
|
||||
|
||||
}
|
||||
56
inc/Hura8/System/IDGenerator.php
Normal file
56
inc/Hura8/System/IDGenerator.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
use Hidehalo\Nanoid\Client;
|
||||
|
||||
class IDGenerator
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $prefix - S = sale-order, P = purchase-order
|
||||
* @param int $increment_id result of IncrementIDModel/getId
|
||||
* @param int $length max length for the result-id, if length=6, prefix=P, $increment_id=20 => final id = P000020
|
||||
* @return string
|
||||
*/
|
||||
public static function createItemId(string $prefix, int $increment_id, $length = 6): string
|
||||
{
|
||||
$num_zeros = $length - strlen($increment_id);
|
||||
if($num_zeros > 0) {
|
||||
return join("", [$prefix, str_repeat("0", $num_zeros), $increment_id]);
|
||||
}
|
||||
|
||||
return join("", [$prefix, $increment_id]);
|
||||
}
|
||||
|
||||
|
||||
private static $nanoClientCache;
|
||||
|
||||
|
||||
/**
|
||||
* @description create string id using Nano ID
|
||||
* Collision Calculator: https://zelark.github.io/nano-id-cc/
|
||||
* @param int $length
|
||||
* @param bool $lower_case
|
||||
* @return string
|
||||
*/
|
||||
public static function createStringId(int $length = 15, bool $lower_case = false) : string {
|
||||
|
||||
if($lower_case) {
|
||||
$alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||
} else {
|
||||
$alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
}
|
||||
|
||||
if(isset(static::$nanoClientCache)) {
|
||||
return static::$nanoClientCache->formattedId($alphabet, $length);
|
||||
}
|
||||
|
||||
// create one
|
||||
$client = new Client();
|
||||
static::$nanoClientCache = $client;
|
||||
|
||||
return $client->formattedId($alphabet, $length);
|
||||
}
|
||||
|
||||
}
|
||||
129
inc/Hura8/System/IPFilter.php
Normal file
129
inc/Hura8/System/IPFilter.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
// copy from: https://php.net/manual/en/function.ip2long.php
|
||||
//useage:
|
||||
/*$filter = new IPFilter(
|
||||
array(
|
||||
'127.0.0.1',
|
||||
'172.0.0.*',
|
||||
'173.0.*.*',
|
||||
'126.1.0.0/255.255.0.0',
|
||||
'125.0.0.1-125.0.0.9',
|
||||
));
|
||||
$filter -> check('126.1.0.2');*/
|
||||
|
||||
class IPFilter
|
||||
{
|
||||
private static $_IP_TYPE_SINGLE = 'single';
|
||||
private static $_IP_TYPE_WILDCARD = 'wildcard';
|
||||
private static $_IP_TYPE_MASK = 'mask';
|
||||
private static $_IP_TYPE_SECTION = 'section';
|
||||
private $_allowed_ips = array();
|
||||
|
||||
public function __construct($allowed_ips)
|
||||
{
|
||||
$this -> _allowed_ips = $allowed_ips;
|
||||
}
|
||||
|
||||
|
||||
public function check($ip, $allowed_ips = null)
|
||||
{
|
||||
$allowed_ips = $allowed_ips ? $allowed_ips : $this->_allowed_ips;
|
||||
|
||||
foreach($allowed_ips as $allowed_ip)
|
||||
{
|
||||
$type = $this->_judge_ip_type($allowed_ip);
|
||||
$sub_rst = call_user_func(array($this,'_sub_checker_' . $type), $allowed_ip, $ip);
|
||||
|
||||
if ($sub_rst)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private function _judge_ip_type($ip)
|
||||
{
|
||||
if (strpos($ip, '*'))
|
||||
{
|
||||
return self::$_IP_TYPE_WILDCARD;
|
||||
}
|
||||
|
||||
if (strpos($ip, '/'))
|
||||
{
|
||||
return self::$_IP_TYPE_MASK;
|
||||
}
|
||||
|
||||
if (strpos($ip, '-'))
|
||||
{
|
||||
return self::$_IP_TYPE_SECTION;
|
||||
}
|
||||
|
||||
return self::$_IP_TYPE_SINGLE;
|
||||
|
||||
/*if (ip2long($ip))
|
||||
{
|
||||
return self::$_IP_TYPE_SINGLE;
|
||||
}
|
||||
|
||||
return false;*/
|
||||
}
|
||||
|
||||
// can use for IPV4&6
|
||||
private function _sub_checker_single($allowed_ip, $ip)
|
||||
{
|
||||
return strval($allowed_ip) == strval($ip);
|
||||
//return (ip2long($allowed_ip) == ip2long($ip));
|
||||
}
|
||||
|
||||
// currently only IPV4: 172.0.0.*
|
||||
private function _sub_checker_wildcard($allowed_ip, $ip)
|
||||
{
|
||||
$allowed_ip_arr = explode('.', $allowed_ip);
|
||||
$ip_arr = explode('.', $ip);
|
||||
for($i = 0;$i < count($allowed_ip_arr);$i++)
|
||||
{
|
||||
if ($allowed_ip_arr[$i] == '*')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (false == ($allowed_ip_arr[$i] == $ip_arr[$i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// currently only IPV4: 125.0.0.1/24
|
||||
private function _sub_checker_mask($banned_range, $ip)
|
||||
{
|
||||
// $allowed_ip is in IP/CIDR format eg 127.0.0.1/24
|
||||
list( $range, $netmask ) = explode( '/', $banned_range, 2 );
|
||||
$range_decimal = ip2long( $range );
|
||||
$ip_decimal = ip2long( $ip );
|
||||
$wildcard_decimal = pow( 2, ( 32 - $netmask ) ) - 1;
|
||||
$netmask_decimal = ~ $wildcard_decimal;
|
||||
return !( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
|
||||
}
|
||||
|
||||
// currently only IPV4: 125.0.0.1-125.0.0.9
|
||||
private function _sub_checker_section($allowed_ip, $ip)
|
||||
{
|
||||
list($begin, $end) = explode('-', $allowed_ip);
|
||||
$begin = ip2long(trim($begin));
|
||||
$end = ip2long(trim($end));
|
||||
$ip = ip2long($ip);
|
||||
|
||||
return ($ip >= $begin && $ip <= $end);
|
||||
}
|
||||
}
|
||||
94
inc/Hura8/System/Language.php
Normal file
94
inc/Hura8/System/Language.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by Glee Ltd.
|
||||
* User: Hieu
|
||||
* Date: 05-Mar-18
|
||||
* Time: 4:25 PM
|
||||
* Description:
|
||||
*/
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
|
||||
class Language
|
||||
{
|
||||
//25-04-2013
|
||||
//convert text to lowever case, even with vietnamese
|
||||
public static function convert_lower($text){
|
||||
|
||||
$lower = array(
|
||||
"đ",
|
||||
"ó","ỏ","ò","ọ","õ","ô","ỗ","ổ","ồ","ố","ộ","ơ","ỡ","ớ","ờ","ở","ợ",
|
||||
"ì","í","ỉ","ì","ĩ","ị",
|
||||
"ê","ệ","ế","ể","ễ","ề","é","ẹ","ẽ","è","ẻ",
|
||||
"ả","á","ạ","ã","à","â","ẩ","ấ","ầ","ậ","ẫ","ă","ẳ","ắ","ằ","ặ","ẵ",
|
||||
"ũ","ụ","ú","ủ","ù","ư","ự","ứ","ử","ừ","ữ",
|
||||
"ỹ","ỵ","ý","ỷ","ỳ",
|
||||
"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
|
||||
);
|
||||
|
||||
$upper = array(
|
||||
"Đ",
|
||||
"Ó","Ỏ","Ò","Ọ","Õ","Ô","Ỗ","Ổ","Ồ","Ố","Ộ","Ơ","Ỡ","Ớ","Ờ","Ở","Ợ",
|
||||
"Ì","Í","Ỉ","Ì","Ĩ","Ị",
|
||||
"Ê","Ệ","Ế","Ể","Ễ","Ề","É","Ẹ","Ẽ","È","Ẻ",
|
||||
"Ả","Á","Ạ","Ã","À","Â","Ẩ","Ấ","Ầ","Ậ","Ẫ","Ă","Ẳ","Ắ","Ằ","Ặ","Ẵ",
|
||||
"Ũ","Ụ","Ú","Ủ","Ù","Ư","Ự","Ứ","Ử","Ừ","Ữ",
|
||||
"Ỹ","Ỵ","Ý","Ỷ","Ỳ",
|
||||
"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
|
||||
);
|
||||
|
||||
//convert
|
||||
return str_replace($upper, $lower, $text);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static function chuyenKhongdau($txt){
|
||||
$arraychar = array(
|
||||
array("đ","Đ"),
|
||||
array("ó","ỏ","ò","ọ","õ","ô","ỗ","ổ","ồ","ố","ộ","ơ","ỡ","ớ","ờ","ở","ợ","Ó","Ỏ","Ò","Ọ","Õ","Ô","Ỗ","Ổ","Ồ","Ố","Ộ","Ơ","Ỡ","Ớ","Ờ","Ở","Ợ"),
|
||||
array("ì","í","ỉ","ì","ĩ","ị","Ì","Í","Ỉ","Ì","Ĩ","Ị"),
|
||||
array("ê","ệ","ế","ể","ễ","ề","é","ẹ","ẽ","è","ẻ","Ê","Ệ","Ế","Ể","Ễ","Ề","É","Ẹ","Ẽ","È","Ẻ"),
|
||||
array("ả","á","ạ","ã","à","â","ẩ","ấ","ầ","ậ","ẫ","ă","ẳ","ắ","ằ","ặ","ẵ","Ả","Á","Ạ","Ã","À","Â","Ẩ","Ấ","Ầ","Ậ","Ẫ","Ă","Ẳ","Ắ","Ằ","Ặ","Ẵ"),
|
||||
array("ũ","ụ","ú","ủ","ù","ư","ữ","ự","ứ","ử","ừ","Ũ","Ụ","Ú","Ủ","Ù","Ư","Ũ","Ự","Ứ","Ử","Ừ"),
|
||||
array("ỹ","ỵ","ý","ỷ","ỳ","Ỹ","Ỵ","Ý","Ỷ","Ỳ")
|
||||
);
|
||||
|
||||
$arrayconvert = array("d","o","i","e","a","u","y");
|
||||
|
||||
$count = sizeof($arraychar);
|
||||
for($i=0; $i < $count; $i++) {
|
||||
$txt = str_replace($arraychar[$i], $arrayconvert[$i], $txt);
|
||||
}
|
||||
|
||||
return $txt;
|
||||
}
|
||||
|
||||
//convert vietnamese: Nguyễn Minh Hiếu => nguyeexn minh hieesu
|
||||
public static function convertText($vietnamese_txt){
|
||||
|
||||
$vietnamese_char = array(
|
||||
"đ",
|
||||
"ó","ỏ","ò","ọ","õ","ô","ỗ","ổ","ồ","ố","ộ","ơ","ỡ","ớ","ờ","ở","ợ",
|
||||
"ì","í","ỉ","ì","ĩ","ị",
|
||||
"ê","ệ","ế","ể","ễ","ề","é","ẹ","ẽ","è","ẻ",
|
||||
"ả","á","ạ","ã","à","â","ẩ","ấ","ầ","ậ","ẫ","ă","ẳ","ắ","ằ","ặ","ẵ",
|
||||
"ũ","ụ","ú","ủ","ù","ư","ữ","ự","ứ","ử","ừ",
|
||||
"ỹ","ỵ","ý","ỷ","ỳ",
|
||||
);
|
||||
|
||||
$equivalent_char = array(
|
||||
"dd",
|
||||
"os","or","of","oj","ox","oo","oox","oor","oof","oos","ooj","ow","owx","ows","owf","owr","owj",
|
||||
"if","is","ir","if","ix","ij",
|
||||
"ee","eej","ees","eef","eex","eer","es","ej","ex","ef","or",
|
||||
"ar","as","aj","ax","af","aa","aar","aas","aaf","aaj","aax","aw","awr","aws","awf","awj","aax",
|
||||
"ux","uj","us","ur","uf","uw","uwx","uwj","uws","uwr","uwf",
|
||||
"yx","yj","ys","yr","yf",
|
||||
);
|
||||
|
||||
return str_replace($vietnamese_char, $equivalent_char, static::convert_lower($vietnamese_txt));
|
||||
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
39
inc/Hura8/System/ModuleManager.php
Normal file
39
inc/Hura8/System/ModuleManager.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
use Hura8\Traits\ClassCacheTrait;
|
||||
|
||||
class ModuleManager
|
||||
{
|
||||
use ClassCacheTrait;
|
||||
|
||||
|
||||
public static function getModuleRouting($module) {
|
||||
$cache_key = "module-routing-".$module;
|
||||
|
||||
return static::getCache($cache_key, function () use ($module) {
|
||||
$config_file = CONFIG_DIR . '/modules/'.$module.'/routing.php';
|
||||
if (file_exists($config_file)) {
|
||||
return include $config_file;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function getModuleAdmin($module) {
|
||||
$cache_key = "module-admin-".$module;
|
||||
|
||||
return static::getCache($cache_key, function () use ($module) {
|
||||
$config_file = CONFIG_DIR . '/modules/'.$module.'/admin.php';
|
||||
if (file_exists($config_file)) {
|
||||
return include $config_file;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
437
inc/Hura8/System/Paging.php
Normal file
437
inc/Hura8/System/Paging.php
Normal file
@@ -0,0 +1,437 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by Glee Ltd.
|
||||
* User: Hieu
|
||||
* Date: 05-Mar-18
|
||||
* Time: 4:30 PM
|
||||
* Description:
|
||||
*/
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
|
||||
final class Paging
|
||||
{
|
||||
|
||||
protected static $cache = [];
|
||||
|
||||
//get last page
|
||||
public static function getLastPage($totalResults, $numPerPage) {
|
||||
//$current_url = curPageURL();
|
||||
$maxPage = ceil($totalResults / $numPerPage);
|
||||
|
||||
return Url::buildUrl($_SERVER['REQUEST_URI'], ["page" => $maxPage]);
|
||||
}
|
||||
|
||||
public static function pageSizeSelectBox($default_size = 15) {
|
||||
$html = ["<select class='hura-change-page-size' onchange='window.location=this.value'>"];
|
||||
|
||||
$final_list = static::getPageSizes();
|
||||
foreach ($final_list as $item) {
|
||||
$is_selected = ($default_size == $item['size']) ? "selected" : "";
|
||||
$html[] = "<option value='". $item['url'] ."' ".$is_selected.">". $item['name'] ."</option>";
|
||||
}
|
||||
|
||||
$html[] = "</select>";
|
||||
|
||||
return join("", $html);
|
||||
}
|
||||
|
||||
//allow to select pageSize
|
||||
public static function getPageSizes() {
|
||||
$sizes = [15, 30, 50];
|
||||
$final_list = [];
|
||||
foreach ($sizes as $size) {
|
||||
$final_list[] = [
|
||||
"size" => $size,
|
||||
"name" => $size . "/ trang",
|
||||
"url" => Url::buildUrl($_SERVER['REQUEST_URI'], ["page" => '', "pageSize" => $size])
|
||||
];
|
||||
}
|
||||
|
||||
return $final_list;
|
||||
}
|
||||
|
||||
//Function nhay trang paging
|
||||
public static function pagingAjax($totalResults,$numPerRow,$currentURL,$maxPageDisplay,$holder_id){
|
||||
$currentPage = isset($_GET["page"]) ? (int) $_GET["page"] : 1;
|
||||
$currentURL = str_replace(array("?page=".$currentPage, "&page=".$currentPage),"",$currentURL);
|
||||
if(strpos($currentURL, "?") !== false) $currentURL .= "&";
|
||||
else $currentURL .= "?";
|
||||
|
||||
$maxPage = ceil($totalResults/$numPerRow);
|
||||
//max page to see : 20
|
||||
$maxPage = ($maxPage > 50) ? 50 : $maxPage;
|
||||
|
||||
if($maxPage==1) return "";
|
||||
$farLeft ="<td class=pagingFarSide align=center>...</td>";
|
||||
$farRight ="<td class=pagingFarSide align=center>...</td>";
|
||||
$link ="<table cellpadding=0 cellspacing=0><tr>";
|
||||
//$maxPageDisplay la so page se hien ra, thuong lay 7
|
||||
if($currentPage > 1) $link .= "<td class=\"pagingIntact\"><a href=\"javascript:;\" onclick=\"loadAjaxContent('".$holder_id."', '".$currentURL."&page=".($currentPage-1)."')\"><<</a></td>";
|
||||
else $link .= "<td class=\"pagingIntact\"><a>Xem</a></td>";
|
||||
$link .= "<td class=pagingSpace></td>";
|
||||
|
||||
if($maxPage<=$maxPageDisplay){
|
||||
|
||||
for($i=1;$i<=$maxPage;$i++){
|
||||
if($i==$currentPage) $link .= "<td class=\"pagingViewed\">$i</td><td class=pagingSpace></td>";
|
||||
else $link .= "<td class=\"pagingIntact\"><a href=\"javascript:;\" onclick=\"loadAjaxContent('".$holder_id."', '".$currentURL."&page=$i')\">$i</a></td><td class=pagingSpace></td>";
|
||||
}
|
||||
|
||||
if($currentPage < $maxPage) $link .= "<td class=\"pagingIntact\"><a href=\"javascript:;\" onclick=\"loadAjaxContent('".$holder_id."', '".$currentURL."&page=".($currentPage+1)."')\">>></a></td>";
|
||||
|
||||
}else{
|
||||
//Vay la tong so trang nhieu hon so trang muon hien ra
|
||||
if($currentPage<=(ceil($maxPageDisplay/2))){
|
||||
|
||||
//Neu trang dang xem dang o muc 1,2,3,4
|
||||
for($i=1;$i<=$maxPageDisplay;$i++){
|
||||
if($i==$currentPage) $link .= "<td class=\"pagingViewed\">$i</td><td class=pagingSpace></td>";
|
||||
else $link .= "<td class=\"pagingIntact\"><a href=\"javascript:;\" onclick=\"loadAjaxContent('".$holder_id."', '".$currentURL."&page=$i')\">$i</a></td><td class=pagingSpace></td>";
|
||||
}
|
||||
$link .=$farRight;
|
||||
//$link .= "<td class=\"pagingIntact\"><a href=\"".$currentURL."&page=".$maxPage."\">".$maxPage."</a></td><td class=pagingSpace></td>";//the last page
|
||||
$link .= "<td class=\"pagingIntact\"><a href=\"javascript:;\" onclick=\"loadAjaxContent('".$holder_id."', '".$currentURL."&page=".($currentPage+1)."')\">>></a></td>";
|
||||
|
||||
}elseif($currentPage<=($maxPage-round($maxPageDisplay/2,0))){
|
||||
//Neu trang dang xem o muc cao hon 4
|
||||
$link .= "<td class=\"pagingIntact\"><a href=\"javascript:;\" onclick=\"loadAjaxContent('".$holder_id."', '".$currentURL."&page=1')\">1</a></td><td class=pagingSpace></td>";//the 1st page
|
||||
$link .=$farLeft;
|
||||
for($i=($currentPage-round($maxPageDisplay/2,0)+1);$i<($currentPage+round($maxPageDisplay/2,0));$i++){
|
||||
if($i==$currentPage) $link .= "<td class=\"pagingViewed\">$i</td><td class=pagingSpace></td>";
|
||||
else $link .= "<td class=\"pagingIntact\"><a href=\"javascript:;\" onclick=\"loadAjaxContent('".$holder_id."', '".$currentURL."&page=$i')\">$i</a></td><td class=pagingSpace></td>";
|
||||
}
|
||||
$link .=$farRight;
|
||||
//$link .= "<td class=\"pagingIntact\"><a href=\"".$currentURL."&page=".$maxPage."\">".$maxPage."</a></td><td class=pagingSpace></td>";//the last page
|
||||
$link .= "<td class=\"pagingIntact\"><a href=\"javascript:;\" onclick=\"loadAjaxContent('".$holder_id."', '".$currentURL."&page=".($currentPage+1)."')\">>></a></td>";
|
||||
}else{
|
||||
//May trang cuoi cung
|
||||
$link .= "<td class=\"pagingIntact\"><a href=\"javascript:;\" onclick=\"loadAjaxContent('".$holder_id."', '".$currentURL."&page=1')\">1</a></td><td class=pagingSpace></td>";//the 1st page
|
||||
$link .=$farLeft;
|
||||
for($i=($maxPage-$maxPageDisplay+1);$i<=$maxPage;$i++){
|
||||
if($i==$currentPage) $link .= "<td class=\"pagingViewed\">$i</td><td class=pagingSpace></td>";
|
||||
else $link .= "<td class=\"pagingIntact\"><a href=\"javascript:;\" onclick=\"loadAjaxContent('".$holder_id."', '".$currentURL."&page=$i')\">$i</a></td><td class=pagingSpace></td>";
|
||||
}
|
||||
}
|
||||
}
|
||||
$link .= "</tr></table>";
|
||||
//Lua chon hien thi, phai it nhat tu 2 trang tro len moi hien ra
|
||||
|
||||
if($maxPage>1){
|
||||
return $link;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 04-Sept-2020
|
||||
public static function paging_template($totalResults, $numPerRow, $currentURL = '', $maxPageDisplay = 7){
|
||||
// parameter variables
|
||||
if(!$currentURL) $currentURL = $_SERVER['REQUEST_URI'];
|
||||
$url_parts = self::destructUrlWithCustomPageParam($currentURL);
|
||||
/*[
|
||||
'path' => $url_path,
|
||||
'query' => $query_params,
|
||||
'page' => $current_page,
|
||||
];*/
|
||||
|
||||
$query_without_page = $url_parts['query'];
|
||||
$current_page = (isset($url_parts['page']) && $url_parts['page']) ? intval($url_parts['page']) : 1;
|
||||
$total_pages = ceil($totalResults / $numPerRow);
|
||||
$page_space = ceil(($maxPageDisplay - 1) / 2);
|
||||
if($current_page > $total_pages) $current_page = $total_pages;
|
||||
|
||||
/*
|
||||
* $current_page is expected to be always in the middle:
|
||||
* 3 = $page_space = ( $maxPageDisplay = 7-1/2)
|
||||
* if current_page = 4 -> expect start = 4-3 = 1, expect end = 4+3 = 7
|
||||
* if current_page = 2 -> expect start = 2-3 = -1, expect end = 2+3 = 5. Since -1 is not valid, we need to add 2 to make it at least 1, so the end need to add 2 as well, so the result is: start = 1, end = 7
|
||||
* if current_page = 6 -> expect start = 6-3 = 3, expect end = 6+3 = 9. if max page =8, the end need to offset 9-8 = 1, so the start also need to offset 1, the result: start=3-1 = 2, end=9-1=8
|
||||
* */
|
||||
|
||||
// expect result
|
||||
$start_index = $current_page - $page_space;
|
||||
$end_index = $current_page + $page_space;
|
||||
|
||||
//calculate offset
|
||||
if($start_index < 1) {
|
||||
$offset = 1 - $start_index;
|
||||
$start_index = 1;
|
||||
$end_index += $offset;
|
||||
if($end_index > $total_pages) $end_index = $total_pages;
|
||||
}else if($end_index > $total_pages) {
|
||||
$offset = $end_index - $total_pages;
|
||||
$end_index = $total_pages;
|
||||
$start_index -= $offset;
|
||||
if($start_index < 1) $start_index = 1;
|
||||
}
|
||||
|
||||
$page_collection = [];
|
||||
$build_table = [];
|
||||
|
||||
// add prev
|
||||
if($start_index > 1) {
|
||||
$query_without_page['page'] = $current_page-1;
|
||||
$url = self::buildUrlWithCustomPageParam($url_parts['path'], $query_without_page);
|
||||
|
||||
$build_table[] = "<td class=\"pagingIntact\"><a title='Back' href=\"".$url."\"> << </a></td><td class=pagingSpace></td>";
|
||||
$page_collection[] = [
|
||||
"name" => "prev",
|
||||
"url" => $url,
|
||||
"is_active" => 0,
|
||||
];
|
||||
}
|
||||
|
||||
for($i = $start_index; $i <= $end_index ; $i++) {
|
||||
$query_without_page['page'] = $i;
|
||||
$url = self::buildUrlWithCustomPageParam($url_parts['path'], $query_without_page);
|
||||
$is_active = ($i == $current_page) ? 1 : 0;
|
||||
|
||||
$page_collection[] = [
|
||||
"name" => $i,
|
||||
"url" => $url,
|
||||
"is_active" => $is_active,
|
||||
];
|
||||
|
||||
if($is_active) {
|
||||
$build_table[] = "<td class=\"pagingViewed\">".$i."</td><td class=pagingSpace></td>";
|
||||
}else{
|
||||
$build_table[] = "<td class=\"pagingIntact\"><a href=\"".$url."\">".$i."</a></td><td class=pagingSpace></td>";
|
||||
}
|
||||
}
|
||||
|
||||
// add forward
|
||||
if($end_index < $total_pages) {
|
||||
$query_without_page['page'] = $current_page + 1;
|
||||
$url = self::buildUrlWithCustomPageParam($url_parts['path'], $query_without_page);
|
||||
$build_table[] = "<td class=\"pagingIntact\"><a title='Next' href=\"".$url."\"> >> </a></td><td class=pagingSpace></td>";
|
||||
$page_collection[] = [
|
||||
"name" => "next",
|
||||
"url" => $url,
|
||||
"is_active" => 0,
|
||||
];
|
||||
}
|
||||
|
||||
//-------------------
|
||||
if($total_pages > 1 ){
|
||||
$tb_page = join("", [
|
||||
'<table cellpadding="0" cellspacing="0"><tr>',
|
||||
join('', $build_table) ,
|
||||
"</tr></table>"
|
||||
]) ;
|
||||
|
||||
return array($page_collection, $tb_page, $total_pages);
|
||||
}
|
||||
|
||||
return array([], '', 1);
|
||||
}
|
||||
|
||||
// Url with page parameter format
|
||||
// default: domain/path?key1=value1&key2=value2&page=1|2|3...
|
||||
// custom1: domain/path/1|2|3/?key1=value1&key2=value2
|
||||
public static function buildUrlWithCustomPageParam($url_path, array $params){
|
||||
$url_format_type = self::getUrlWithCustomPageFormat();
|
||||
|
||||
$params_without_page = $params;
|
||||
$page_number = 1;
|
||||
if(isset($params['page'])) {
|
||||
$page_number = intval($params['page']);
|
||||
unset($params_without_page['page']);
|
||||
}
|
||||
|
||||
// custom1 (use case: hanoicomputer.vn)
|
||||
if($url_format_type == 'custom1') {
|
||||
if($page_number > 1){
|
||||
$url_path = $url_path."/".$page_number."/";
|
||||
}
|
||||
}else{
|
||||
// default
|
||||
if($page_number > 1){
|
||||
// push page to the end, always
|
||||
$params_without_page['page'] = $page_number;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(sizeof($params_without_page)) {
|
||||
return join("", [$url_path, "?", http_build_query($params_without_page)]);
|
||||
}
|
||||
|
||||
return $url_path;
|
||||
}
|
||||
|
||||
// destruct Url to get url_path and query params
|
||||
public static function destructUrlWithCustomPageParam($full_url){
|
||||
$cache_key = md5($full_url);
|
||||
$cached_value = self::getCache($cache_key);
|
||||
// if in cache, get right away
|
||||
if($cached_value) return $cached_value;
|
||||
|
||||
// not in cache, build and set cache
|
||||
$url_format_type = self::getUrlWithCustomPageFormat();
|
||||
|
||||
// destruct
|
||||
$parsed_url = explode("?", $full_url);
|
||||
$url_path = isset($parsed_url[0]) ? $parsed_url[0] : '';
|
||||
$query_part = isset($parsed_url[1]) ? $parsed_url[1] : '';
|
||||
$query_params = [];
|
||||
$current_page = 1;
|
||||
|
||||
if($query_part) {
|
||||
parse_str($query_part, $query_params);
|
||||
}
|
||||
|
||||
// custom1: url format= domain/path/1|2|3/?key1=value1&key2=value2
|
||||
if($url_format_type == 'custom1') {
|
||||
$match = [];
|
||||
if(preg_match("{/(.*?)/([0-9]+)/$}", $url_path, $match)){
|
||||
if(isset($match[2]) && $match[2] > 1) $current_page = $match[2];
|
||||
$url_path = "/".$match[1];
|
||||
}
|
||||
|
||||
} else {
|
||||
// default
|
||||
if(isset($query_params['page'])) {
|
||||
$current_page = $query_params['page'];
|
||||
unset($query_params['page']);
|
||||
}
|
||||
}
|
||||
|
||||
$cached_value = [
|
||||
'path' => $url_path,
|
||||
'query' => $query_params,
|
||||
'page' => $current_page,
|
||||
];
|
||||
|
||||
self::setCache($cache_key, $cached_value);
|
||||
|
||||
return $cached_value;
|
||||
}
|
||||
|
||||
|
||||
// check if user in in the admin panel
|
||||
protected static function is_in_admin(){
|
||||
return substr($_SERVER['REQUEST_URI'], 0, 7) == '/admin/';
|
||||
}
|
||||
|
||||
// Url with page parameter format
|
||||
// default: domain/path?key1=value1&key2=value2&page=1|2|3...
|
||||
// custom1: domain/path/1|2|3/?key1=value1&key2=value2
|
||||
protected static function getUrlWithCustomPageFormat() {
|
||||
$url_format_type = "default";
|
||||
|
||||
// keep default for admin panel
|
||||
if(self::is_in_admin()) {
|
||||
return $url_format_type;
|
||||
}
|
||||
|
||||
// or search page
|
||||
if(substr($_SERVER['REQUEST_URI'], 0, 4) == '/tim') {
|
||||
return $url_format_type;
|
||||
}
|
||||
|
||||
if(defined("URL_PAGING_FORMAT") && URL_PAGING_FORMAT) {
|
||||
$url_format_type = URL_PAGING_FORMAT;
|
||||
}
|
||||
|
||||
return $url_format_type;
|
||||
}
|
||||
|
||||
protected static function getCache($key) {
|
||||
return isset(self::$cache[$key]) ? unserialize(self::$cache[$key]) : null;
|
||||
}
|
||||
|
||||
protected static function setCache($key, $value) {
|
||||
self::$cache[$key] = serialize($value);
|
||||
}
|
||||
|
||||
//Function nhay trang
|
||||
public static function paging($totalResults, $numPerRow, $currentURL, $maxPageDisplay, $limitPage = 50){
|
||||
$currentPage = isset($_GET["page"]) ? (int) $_GET["page"] : 1;
|
||||
$currentURL = str_replace(array("?page=".$currentPage, "&page=".$currentPage),"",$currentURL);
|
||||
//build_url($currentURL, array("page"=>''));
|
||||
|
||||
$first_url = $currentURL; //when $page=1
|
||||
|
||||
if(strpos($currentURL, "?") !== false) $currentURL .= "&";
|
||||
else $currentURL .= "?";
|
||||
|
||||
$maxPage = ceil($totalResults/$numPerRow);
|
||||
//max page to see : 20
|
||||
if($limitPage > 0){
|
||||
$maxPage = ($maxPage > $limitPage) ? $limitPage : $maxPage;
|
||||
}
|
||||
|
||||
if($maxPage==1) return "";
|
||||
$farLeft ="<td class=\"pagingFarSide\" align=\"center\">...</td>";
|
||||
$farRight ="<td class=\"pagingFarSide\" align=\"center\">...</td>";
|
||||
$link ="<table cellpadding=\"0\" cellspacing=\"0\"><tr>";
|
||||
//$maxPageDisplay la so page se hien ra, thuong lay 7
|
||||
if($currentPage == 2) $link .= "<td class=\"pagingIntact\"><a href=\"".$first_url."\">Quay lại</a></td>";
|
||||
else if($currentPage > 2) $link .= "<td class=\"pagingIntact\"><a href=\"".$currentURL."page=".($currentPage-1)."\">Quay lại</a></td>";
|
||||
else $link .= "<td class=\"pagingIntact\"><a>Xem trang</a></td>";
|
||||
|
||||
$link .= "<td class=\"pagingSpace\"></td>";
|
||||
|
||||
if($maxPage<=$maxPageDisplay){
|
||||
|
||||
for($i=1;$i<=$maxPage;$i++){
|
||||
if($i==$currentPage) $link .= "<td class=\"pagingViewed\">$i</td><td class=pagingSpace></td>";
|
||||
else {
|
||||
if($i== 1) $link .= "<td class=\"pagingIntact\"><a href=\"".$first_url."\">$i</a></td><td class=pagingSpace></td>";
|
||||
else $link .= "<td class=\"pagingIntact\"><a href=\"".$currentURL."page=$i\">$i</a></td><td class=pagingSpace></td>";
|
||||
}
|
||||
}
|
||||
|
||||
if($currentPage < $maxPage) $link .= "<td class=\"pagingIntact\"><a href=\"".$currentURL."page=".($currentPage+1)."\">Tiếp theo</a></td>";
|
||||
|
||||
}else{
|
||||
|
||||
//Vay la tong so trang nhieu hon so trang muon hien ra
|
||||
if($currentPage<=(ceil($maxPageDisplay/2))){
|
||||
|
||||
//Neu trang dang xem dang o muc 1,2,3,4
|
||||
for($i=1;$i<=$maxPageDisplay;$i++){
|
||||
if($i==$currentPage) $link .= "<td class=\"pagingViewed\">$i</td><td class=\"pagingSpace\"></td>";
|
||||
else {
|
||||
if($i== 1) $link .= "<td class=\"pagingIntact\"><a href=\"".$first_url."\">$i</a></td><td class=\"pagingSpace\"></td>";
|
||||
else $link .= "<td class=\"pagingIntact\"><a href=\"".$currentURL."page=$i\">$i</a></td><td class=\"pagingSpace\"></td>";
|
||||
}
|
||||
}
|
||||
$link .=$farRight;
|
||||
//$link .= "<td class=\"pagingIntact\"><a href=\"".$currentURL."page=".$maxPage."\">".$maxPage."</a></td><td class=pagingSpace></td>";//the last page
|
||||
$link .= "<td class=\"pagingIntact\"><a href=\"".$currentURL."page=".($currentPage+1)."\">Tiếp theo</a></td>";
|
||||
|
||||
}elseif($currentPage<=($maxPage-round($maxPageDisplay/2,0))){
|
||||
//Neu trang dang xem o muc cao hon 4
|
||||
$link .= "<td class=\"pagingIntact\"><a href=\"".$first_url."\">1</a></td><td class=\"pagingSpace\"></td>";//the 1st page
|
||||
$link .= $farLeft;
|
||||
for($i=($currentPage-round($maxPageDisplay/2,0)+1);$i<($currentPage+round($maxPageDisplay/2,0));$i++){
|
||||
if($i==$currentPage) $link .= "<td class=\"pagingViewed\">$i</td><td class=pagingSpace></td>";
|
||||
else $link .= "<td class=\"pagingIntact\"><a href=\"".$currentURL."page=$i\">$i</a></td><td class=\"pagingSpace\"></td>";
|
||||
}
|
||||
$link .= $farRight;
|
||||
//$link .= "<td class=\"pagingIntact\"><a href=\"".$currentURL."page=".$maxPage."\">".$maxPage."</a></td><td class=pagingSpace></td>";//the last page
|
||||
$link .= "<td class=\"pagingIntact\"><a href=\"".$currentURL."page=".($currentPage+1)."\">Tiếp theo</a></td>";
|
||||
}else{
|
||||
//May trang cuoi cung
|
||||
$link .= "<td class=\"pagingIntact\"><a href=\"".$first_url."\">1</a></td><td class=\"pagingSpace\"></td>";//the 1st page
|
||||
$link .= $farLeft;
|
||||
for($i=($maxPage-$maxPageDisplay+1);$i<=$maxPage;$i++){
|
||||
if($i==$currentPage) $link .= "<td class=\"pagingViewed\">$i</td><td class=\"pagingSpace\"></td>";
|
||||
else $link .= "<td class=\"pagingIntact\"><a href=\"".$currentURL."page=$i\">$i</a></td><td class=\"pagingSpace\"></td>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$link .= "</tr></table>";
|
||||
|
||||
//Lua chon hien thi, phai it nhat tu 2 trang tro len moi hien ra
|
||||
if($maxPage > 1){
|
||||
return $link;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
39
inc/Hura8/System/Permission.php
Normal file
39
inc/Hura8/System/Permission.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
use Hura8\Traits\ClassCacheTrait;
|
||||
|
||||
class Permission
|
||||
{
|
||||
|
||||
use ClassCacheTrait;
|
||||
|
||||
public static function getClientEntities() : array {
|
||||
return static::getCache("getClientEntities", function (){
|
||||
$config_file = CONFIG_DIR . "/client/config_entities.php";
|
||||
if(file_exists($config_file)) {
|
||||
return include $config_file;
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function getClientConfigPerModule() {
|
||||
|
||||
return static::getCache("getClientConfigPerModule", function (){
|
||||
$config_file = CONFIG_DIR . "/client/admin/config_per_module_disabled.php";
|
||||
if(file_exists($config_file)) {
|
||||
return include $config_file;
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
84
inc/Hura8/System/Plugin.php
Normal file
84
inc/Hura8/System/Plugin.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by Glee Ltd.
|
||||
* User: Hieu
|
||||
* Date: 3/6/18
|
||||
* Time: 6:55 PM
|
||||
* Description:
|
||||
*/
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
|
||||
class Plugin
|
||||
{
|
||||
|
||||
public static function getPagePlugin($module, $page) {
|
||||
$allowed_plugins = self::getPlugins();
|
||||
|
||||
if($module == 'global') {
|
||||
$page_config = $allowed_plugins['global'];
|
||||
}else{
|
||||
$page_config = (isset($allowed_plugins[$module]) && isset($allowed_plugins[$module][$page])) ? $allowed_plugins[$module][$page] : [];
|
||||
}
|
||||
|
||||
if(!sizeof($page_config)) return [];
|
||||
|
||||
$plugin_mapping_file = ROOT_DIR . "/config/system/plugin_mapping.php";
|
||||
|
||||
if(file_exists($plugin_mapping_file)) {
|
||||
|
||||
$plugin_mapping = include ($plugin_mapping_file);
|
||||
$page_plugin_data = [];
|
||||
|
||||
foreach ($page_config as $key => $is_enabled) {
|
||||
if($is_enabled && isset($plugin_mapping[$key]) && $plugin_mapping[$key]) {
|
||||
$file = "display_plugin/". $plugin_mapping[$key];
|
||||
if(file_exists($file)) {
|
||||
$page_plugin_data[$key] = include $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $page_plugin_data;
|
||||
|
||||
} else {
|
||||
|
||||
die($plugin_mapping_file." not exist");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected static function getPlugins() {
|
||||
|
||||
$config_file = ROOT_DIR . "/config/client/config_plugin.php";
|
||||
|
||||
if(file_exists($config_file)) {
|
||||
|
||||
$config_plugins = include $config_file;
|
||||
//global plugin should over-ride local one
|
||||
$allowed_plugins = $config_plugins;
|
||||
|
||||
foreach ($config_plugins as $module => $content) {
|
||||
if($module == "global") continue; //skip
|
||||
|
||||
foreach ($content as $option => $option_pages) {
|
||||
foreach ($option_pages as $page => $precate) {
|
||||
if($precate && array_key_exists($page, $allowed_plugins['global']) && $allowed_plugins['global'][$page] ) {
|
||||
$allowed_plugins[$module][$option][$page] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $allowed_plugins;
|
||||
|
||||
} else {
|
||||
|
||||
die($config_file." not exist");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
1023
inc/Hura8/System/RainTPL.php
Normal file
1023
inc/Hura8/System/RainTPL.php
Normal file
File diff suppressed because it is too large
Load Diff
1027
inc/Hura8/System/RainTPLChecker.php
Normal file
1027
inc/Hura8/System/RainTPLChecker.php
Normal file
File diff suppressed because it is too large
Load Diff
98
inc/Hura8/System/ReadExcel.php
Normal file
98
inc/Hura8/System/ReadExcel.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by Glee Ltd.
|
||||
* Date: 12-May-18
|
||||
* Time: 12:02 PM
|
||||
* Description: a class wrapper for reading an excel file and apply a callback on each rows
|
||||
*/
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
|
||||
class ReadExcel
|
||||
{
|
||||
protected static $default_cols = [
|
||||
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
|
||||
'AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK','AL','AM',
|
||||
'AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ',
|
||||
//...
|
||||
];
|
||||
|
||||
/* $objReader Reader\IReader */
|
||||
protected $objReader;
|
||||
|
||||
public function __construct($ext="xlsx"){
|
||||
// $inputFileType = 'Xls';
|
||||
// $inputFileType = 'Xlsx';
|
||||
// $inputFileType = 'Xml';
|
||||
// $inputFileType = 'Ods';
|
||||
// $inputFileType = 'Slk';
|
||||
// $inputFileType = 'Gnumeric';
|
||||
// $inputFileType = 'Csv';
|
||||
$inputFileType = ucfirst($ext);
|
||||
$this->objReader = IOFactory::createReader($inputFileType);
|
||||
}
|
||||
|
||||
/*return [
|
||||
'sheet_index' => [
|
||||
'row_id' => [
|
||||
//column name => content
|
||||
'A' => '',
|
||||
'B' => '',
|
||||
],
|
||||
],
|
||||
//...
|
||||
];*/
|
||||
public function read(
|
||||
$excel_file,
|
||||
$sheet_start_row = 2,
|
||||
array $column_list = ['A', 'B'],
|
||||
$callback_on_each_row = '',
|
||||
$return_content = false
|
||||
) {
|
||||
|
||||
//$objReader->setReadDataOnly(true);
|
||||
$objPHPExcel = $this->objReader->load($excel_file);
|
||||
$apply_callback_on_rows = ($callback_on_each_row && function_exists($callback_on_each_row));
|
||||
|
||||
$number_sheet = $objPHPExcel->getSheetCount();
|
||||
|
||||
$all_sheet = [];
|
||||
|
||||
$map_column_list = (sizeof($column_list)) ? $column_list : self::$default_cols;
|
||||
|
||||
for($i=0; $i < $number_sheet; $i++){
|
||||
|
||||
$objPHPExcel->setActiveSheetIndex($i);
|
||||
$current_sheet = $objPHPExcel->getActiveSheet();
|
||||
$number_row = $current_sheet->getHighestRow();
|
||||
|
||||
$all_sheet_rows = [];
|
||||
for($j = $sheet_start_row; $j <= $number_row; $j++){
|
||||
$rows_content = [];
|
||||
foreach ($map_column_list as $col) {
|
||||
//$cell_value = $current_sheet->getCell($col . $j)->getFormattedValue();
|
||||
$cell_value = $current_sheet->getCell($col . $j)->getValue();
|
||||
$rows_content[$col] = ($cell_value) ? trim($cell_value) : '';
|
||||
}
|
||||
|
||||
//check if we want to return data
|
||||
if($return_content) $all_sheet_rows[$j] = $rows_content;
|
||||
|
||||
//check if we want to apply callback right away
|
||||
if($apply_callback_on_rows) {
|
||||
call_user_func($callback_on_each_row, $rows_content);
|
||||
}
|
||||
}
|
||||
|
||||
if($return_content) $all_sheet[$i] = $all_sheet_rows;
|
||||
}
|
||||
|
||||
//free resource
|
||||
$objPHPExcel = null;
|
||||
|
||||
return ($return_content) ? $all_sheet : true;
|
||||
}
|
||||
}
|
||||
152
inc/Hura8/System/Registry.php
Normal file
152
inc/Hura8/System/Registry.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* Registry class
|
||||
* This class with hold shared/global variables needed for the entire system during each user request
|
||||
* Some common variables
|
||||
* + $user_info
|
||||
* + $company_info
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
final class Registry {
|
||||
/**
|
||||
* Hold all other class instances
|
||||
* Example:
|
||||
* array(
|
||||
'user' => $user;
|
||||
* )
|
||||
*
|
||||
* Usage: if we want to use the $user instance of User class, do this
|
||||
* $user = Registry::getInstance('user');
|
||||
*/
|
||||
private static $instances = [];
|
||||
|
||||
/**
|
||||
* Hold all shared variables used for views
|
||||
* Example:
|
||||
* array(
|
||||
'user_info' => $user_info;
|
||||
* )
|
||||
*
|
||||
* Usage: if we want to use the $user instance of User class, do this
|
||||
* $user_info = Registry::getVariable('user_info');
|
||||
*/
|
||||
private static $variables = [];
|
||||
|
||||
/**
|
||||
* hold template variables to render in the template
|
||||
* Example:
|
||||
* array(
|
||||
'user_info' => $user_info;
|
||||
* )
|
||||
*
|
||||
*/
|
||||
private static $tpl_vars = [];
|
||||
|
||||
/**
|
||||
* hold registered plugins for each template
|
||||
* Example:
|
||||
* array(
|
||||
'homepage/homepage' => [
|
||||
|
||||
];
|
||||
* )
|
||||
*
|
||||
*/
|
||||
private static $register_plugins = [];
|
||||
|
||||
|
||||
private function __construct(){
|
||||
//do nothing
|
||||
}
|
||||
|
||||
private function __clone(){
|
||||
//do nothing
|
||||
}
|
||||
|
||||
public static function setInstance($key, $value){
|
||||
self::$instances[$key] = $value;
|
||||
}
|
||||
|
||||
public static function getInstance($key){
|
||||
return isset( self::$instances[$key]) ? self::$instances[$key] : null;
|
||||
}
|
||||
|
||||
//02-11-2015 create an instance for a class and save it
|
||||
public static function createInstance($className, $class_constructor_args = array()){
|
||||
$key = strtolower($className);
|
||||
if(self::getInstance($key)) {
|
||||
return self::getInstance($key);
|
||||
}else{
|
||||
//check for additional arguments for __constructor
|
||||
if (sizeof($class_constructor_args)) {
|
||||
$class = new \ReflectionClass($className);
|
||||
$instance = $class->newInstanceArgs($class_constructor_args);
|
||||
//$instance = new $className();
|
||||
self::setInstance($key, $instance);
|
||||
}else{
|
||||
//$className = 'Adman\\'.$className;
|
||||
$instance = new $className();
|
||||
self::setInstance($key, $instance);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
||||
public static function setVariables($key, $value = ''){
|
||||
if(is_array($key)) {
|
||||
foreach ($key as $_k => $_v) {
|
||||
self::$variables[$_k] = $_v;
|
||||
}
|
||||
} else {
|
||||
self::$variables[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getVariable($key){
|
||||
return self::$variables[$key] ?? null;
|
||||
}
|
||||
|
||||
//set key=> value or array(key=>value)
|
||||
public static function setTplVar($key, $value = ''){
|
||||
if(is_array($key)) {
|
||||
foreach ($key as $_k => $_v) {
|
||||
self::$tpl_vars[$_k] = $_v;
|
||||
}
|
||||
} else {
|
||||
self::$tpl_vars[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getTplVar(){
|
||||
return self::$tpl_vars;
|
||||
}
|
||||
|
||||
//set key=> value or array(key=>value)
|
||||
public static function setPlugins($tpl, $plugin_name, callable $callable, $parameters){
|
||||
|
||||
if(!isset(self::$register_plugins[$tpl])) self::$register_plugins[$tpl] = [];
|
||||
|
||||
//not allow to overwrite
|
||||
if(isset(self::$register_plugins[$tpl][$plugin_name])) return false;
|
||||
|
||||
self::$register_plugins[$tpl][$plugin_name] = [
|
||||
"callable" => $callable,
|
||||
"para" => $parameters,
|
||||
];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function getPlugins($tpl){
|
||||
return (isset(self::$register_plugins[$tpl])) ? self::$register_plugins[$tpl] : false;
|
||||
}
|
||||
|
||||
public function __destruct(){
|
||||
self::$instances = null;
|
||||
self::$variables = null;
|
||||
}
|
||||
}
|
||||
94
inc/Hura8/System/Router.php
Normal file
94
inc/Hura8/System/Router.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
|
||||
use Hura8\System\Controller\UrlManagerController;
|
||||
|
||||
class Router {
|
||||
|
||||
private $path_config = [];
|
||||
|
||||
/* @var UrlManagerController $objUrlManagerController */
|
||||
private $objUrlManagerController;
|
||||
|
||||
public function __construct() {
|
||||
$this->path_config = Config::getRoutes();
|
||||
$this->objUrlManagerController = new UrlManagerController();
|
||||
}
|
||||
|
||||
// url: domain/abc/product.php?para1=value1
|
||||
public function getSiteRouting(string $request_uri = '') {
|
||||
|
||||
if(!$request_uri) $request_uri = $_SERVER['REQUEST_URI'];
|
||||
|
||||
$parsed = Url::parse($request_uri); //abc/product.php?param1=12¶m2=value2
|
||||
|
||||
$request_path = $parsed['path'];
|
||||
|
||||
// remove possible language prefix: i.e. /en/about-us
|
||||
$language_enabled = Config::getLanguageConfig();
|
||||
if(sizeof($language_enabled) > 1) {
|
||||
$test_language_prefix = substr($request_path, 0, 4); // /vi/ or /en/ ''
|
||||
foreach ($language_enabled as $_lang => $_v) {
|
||||
if($test_language_prefix == "/".$_lang."/") {
|
||||
$request_path = substr($request_path, 3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// home
|
||||
if($request_path == '/') {
|
||||
return array_merge($this->path_config['home'], ['query' => $parsed['query']]);
|
||||
}
|
||||
|
||||
// check match pattern in $this->path_config
|
||||
foreach ($this->path_config as $_config => $_route ) {
|
||||
if(preg_match("{^".$_config."$}", $request_path, $match )) {
|
||||
|
||||
if(isset($_route['query']) && is_array($_route['query'])) {
|
||||
$_route['query'] = array_merge($_route['query'], $parsed['query']);
|
||||
}else{
|
||||
$_route['query'] = $parsed['query'];
|
||||
}
|
||||
|
||||
return array_merge([
|
||||
'path' => $request_path,
|
||||
'match' => $match,
|
||||
], $_route);
|
||||
}
|
||||
}
|
||||
|
||||
// check in database's url table as the last resort
|
||||
$check_db = $this->objUrlManagerController->getUrlInfoByRequestPath($request_path);
|
||||
if($check_db) {
|
||||
|
||||
if($check_db['redirect_code']) {
|
||||
return [
|
||||
"redirect_code" => $check_db['redirect_code'],
|
||||
"redirect_url" => $check_db['redirect_url'],
|
||||
];
|
||||
}
|
||||
|
||||
if(sizeof($parsed['query'])) {
|
||||
if(isset($check_db['query']) && is_array($check_db['query'])) {
|
||||
$check_db['query'] = array_merge($check_db['query'], $parsed['query']);
|
||||
}else{
|
||||
$check_db['query'] = $parsed['query'];
|
||||
}
|
||||
}
|
||||
|
||||
return $check_db;
|
||||
}
|
||||
|
||||
// else error
|
||||
return [
|
||||
'module' => 'error',
|
||||
'view' => '404_not_found',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
606
inc/Hura8/System/Security/Cookie.php
Normal file
606
inc/Hura8/System/Security/Cookie.php
Normal file
@@ -0,0 +1,606 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* PHP-Cookie (https://github.com/delight-im/PHP-Cookie)
|
||||
* Copyright (c) delight.im (https://www.delight.im/)
|
||||
* Licensed under the MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
namespace Hura8\System\Security;
|
||||
|
||||
/**
|
||||
* Modern cookie management for PHP
|
||||
*
|
||||
* Cookies are a mechanism for storing data in the client's web browser and identifying returning clients on subsequent visits
|
||||
*
|
||||
* All cookies that have successfully been set will automatically be included in the global `$_COOKIE` array with future requests
|
||||
*
|
||||
* You can set a new cookie using the static method `Cookie::setcookie(...)` which is compatible to PHP's built-in `setcookie(...)` function
|
||||
*
|
||||
* Alternatively, you can construct an instance of this class, set properties individually, and finally call `save()`
|
||||
*
|
||||
* Note that cookies must always be set before the HTTP headers are sent to the client, i.e. before the actual output starts
|
||||
*/
|
||||
final class Cookie
|
||||
{
|
||||
|
||||
/** @var string name prefix indicating that the cookie must be from a secure origin (i.e. HTTPS) and the 'secure' attribute must be set */
|
||||
const PREFIX_SECURE = '__Secure-';
|
||||
/** @var string name prefix indicating that the 'domain' attribute must *not* be set, the 'path' attribute must be '/' and the effects of {@see PREFIX_SECURE} apply as well */
|
||||
const PREFIX_HOST = '__Host-';
|
||||
const HEADER_PREFIX = 'Set-Cookie: ';
|
||||
const SAME_SITE_RESTRICTION_NONE = 'None';
|
||||
const SAME_SITE_RESTRICTION_LAX = 'Lax';
|
||||
const SAME_SITE_RESTRICTION_STRICT = 'Strict';
|
||||
|
||||
/** @var string the name of the cookie which is also the key for future accesses via `$_COOKIE[...]` */
|
||||
private $name;
|
||||
/** @var mixed|null the value of the cookie that will be stored on the client's machine */
|
||||
private $value;
|
||||
/** @var int the Unix timestamp indicating the time that the cookie will expire at, i.e. usually `time() + $seconds` */
|
||||
private $expiryTime;
|
||||
/** @var string the path on the server that the cookie will be valid for (including all sub-directories), e.g. an empty string for the current directory or `/` for the root directory */
|
||||
private $path;
|
||||
/** @var string|null the domain that the cookie will be valid for (including subdomains) or `null` for the current host (excluding subdomains) */
|
||||
private $domain;
|
||||
/** @var bool indicates that the cookie should be accessible through the HTTP protocol only and not through scripting languages */
|
||||
private $httpOnly;
|
||||
/** @var bool indicates that the cookie should be sent back by the client over secure HTTPS connections only */
|
||||
private $secureOnly;
|
||||
/** @var string|null indicates that the cookie should not be sent along with cross-site requests (either `null`, `None`, `Lax` or `Strict`) */
|
||||
private $sameSiteRestriction;
|
||||
|
||||
/**
|
||||
* Prepares a new cookie
|
||||
*
|
||||
* @param string $name the name of the cookie which is also the key for future accesses via `$_COOKIE[...]`
|
||||
*/
|
||||
public function __construct($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->value = null;
|
||||
$this->expiryTime = 0;
|
||||
$this->path = '/';
|
||||
$this->domain = null;
|
||||
$this->httpOnly = true;
|
||||
$this->secureOnly = false;
|
||||
$this->sameSiteRestriction = self::SAME_SITE_RESTRICTION_LAX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the cookie
|
||||
*
|
||||
* @return string the name of the cookie which is also the key for future accesses via `$_COOKIE[...]`
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the cookie
|
||||
*
|
||||
* @return mixed|null the value of the cookie that will be stored on the client's machine
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value for the cookie
|
||||
*
|
||||
* @param mixed|null $value the value of the cookie that will be stored on the client's machine
|
||||
* @return static this instance for chaining
|
||||
*/
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the expiry time of the cookie
|
||||
*
|
||||
* @return int the Unix timestamp indicating the time that the cookie will expire at, i.e. usually `time() + $seconds`
|
||||
*/
|
||||
public function getExpiryTime()
|
||||
{
|
||||
return $this->expiryTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the expiry time for the cookie
|
||||
*
|
||||
* @param int $expiryTime the Unix timestamp indicating the time that the cookie will expire at, i.e. usually `time() + $seconds`
|
||||
* @return static this instance for chaining
|
||||
*/
|
||||
public function setExpiryTime($expiryTime)
|
||||
{
|
||||
$this->expiryTime = $expiryTime;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum age of the cookie (i.e. the remaining lifetime)
|
||||
*
|
||||
* @return int the maximum age of the cookie in seconds
|
||||
*/
|
||||
public function getMaxAge()
|
||||
{
|
||||
return $this->expiryTime - \time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the expiry time for the cookie based on the specified maximum age (i.e. the remaining lifetime)
|
||||
*
|
||||
* @param int $maxAge the maximum age for the cookie in seconds
|
||||
* @return static this instance for chaining
|
||||
*/
|
||||
public function setMaxAge($maxAge)
|
||||
{
|
||||
$this->expiryTime = \time() + $maxAge;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of the cookie
|
||||
*
|
||||
* @return string the path on the server that the cookie will be valid for (including all sub-directories), e.g. an empty string for the current directory or `/` for the root directory
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path for the cookie
|
||||
*
|
||||
* @param string $path the path on the server that the cookie will be valid for (including all sub-directories), e.g. an empty string for the current directory or `/` for the root directory
|
||||
* @return static this instance for chaining
|
||||
*/
|
||||
public function setPath($path)
|
||||
{
|
||||
$this->path = $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the domain of the cookie
|
||||
*
|
||||
* @return string|null the domain that the cookie will be valid for (including subdomains) or `null` for the current host (excluding subdomains)
|
||||
*/
|
||||
public function getDomain()
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the domain for the cookie
|
||||
*
|
||||
* @param string|null $domain the domain that the cookie will be valid for (including subdomains) or `null` for the current host (excluding subdomains)
|
||||
* @return static this instance for chaining
|
||||
*/
|
||||
public function setDomain($domain = null)
|
||||
{
|
||||
$this->domain = self::normalizeDomain($domain);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the cookie should be accessible through HTTP only
|
||||
*
|
||||
* @return bool whether the cookie should be accessible through the HTTP protocol only and not through scripting languages
|
||||
*/
|
||||
public function isHttpOnly()
|
||||
{
|
||||
return $this->httpOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the cookie should be accessible through HTTP only
|
||||
*
|
||||
* @param bool $httpOnly indicates that the cookie should be accessible through the HTTP protocol only and not through scripting languages
|
||||
* @return static this instance for chaining
|
||||
*/
|
||||
public function setHttpOnly($httpOnly)
|
||||
{
|
||||
$this->httpOnly = $httpOnly;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the cookie should be sent over HTTPS only
|
||||
*
|
||||
* @return bool whether the cookie should be sent back by the client over secure HTTPS connections only
|
||||
*/
|
||||
public function isSecureOnly()
|
||||
{
|
||||
return $this->secureOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the cookie should be sent over HTTPS only
|
||||
*
|
||||
* @param bool $secureOnly indicates that the cookie should be sent back by the client over secure HTTPS connections only
|
||||
* @return static this instance for chaining
|
||||
*/
|
||||
public function setSecureOnly($secureOnly)
|
||||
{
|
||||
$this->secureOnly = $secureOnly;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the same-site restriction of the cookie
|
||||
*
|
||||
* @return string|null whether the cookie should not be sent along with cross-site requests (either `null`, `None`, `Lax` or `Strict`)
|
||||
*/
|
||||
public function getSameSiteRestriction()
|
||||
{
|
||||
return $this->sameSiteRestriction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the same-site restriction for the cookie
|
||||
*
|
||||
* @param string|null $sameSiteRestriction indicates that the cookie should not be sent along with cross-site requests (either `null`, `None`, `Lax` or `Strict`)
|
||||
* @return static this instance for chaining
|
||||
*/
|
||||
public function setSameSiteRestriction($sameSiteRestriction)
|
||||
{
|
||||
$this->sameSiteRestriction = $sameSiteRestriction;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the cookie
|
||||
*
|
||||
* @return bool whether the cookie header has successfully been sent (and will *probably* cause the client to set the cookie)
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
return self::addHttpHeader((string)$this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the cookie and immediately creates the corresponding variable in the superglobal `$_COOKIE` array
|
||||
*
|
||||
* The variable would otherwise only be available starting from the next HTTP request
|
||||
*
|
||||
* @return bool whether the cookie header has successfully been sent (and will *probably* cause the client to set the cookie)
|
||||
*/
|
||||
public function saveAndSet()
|
||||
{
|
||||
$_COOKIE[$this->name] = $this->value;
|
||||
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the cookie
|
||||
*
|
||||
* @return bool whether the cookie header has successfully been sent (and will *probably* cause the client to delete the cookie)
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
// create a temporary copy of this cookie so that it isn't corrupted
|
||||
$copiedCookie = clone $this;
|
||||
// set the copied cookie's value to an empty string which internally sets the required options for a deletion
|
||||
$copiedCookie->setValue('');
|
||||
|
||||
// save the copied "deletion" cookie
|
||||
return $copiedCookie->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the cookie and immediately removes the corresponding variable from the superglobal `$_COOKIE` array
|
||||
*
|
||||
* The variable would otherwise only be deleted at the start of the next HTTP request
|
||||
*
|
||||
* @return bool whether the cookie header has successfully been sent (and will *probably* cause the client to delete the cookie)
|
||||
*/
|
||||
public function deleteAndUnset()
|
||||
{
|
||||
unset($_COOKIE[$this->name]);
|
||||
|
||||
return $this->delete();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return self::buildCookieHeader($this->name, $this->value, $this->expiryTime, $this->path, $this->domain, $this->secureOnly, $this->httpOnly, $this->sameSiteRestriction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new cookie in a way compatible to PHP's `setcookie(...)` function
|
||||
*
|
||||
* @param string $name the name of the cookie which is also the key for future accesses via `$_COOKIE[...]`
|
||||
* @param mixed|null $value the value of the cookie that will be stored on the client's machine
|
||||
* @param int $expiryTime the Unix timestamp indicating the time that the cookie will expire at, i.e. usually `time() + $seconds`
|
||||
* @param string|null $path the path on the server that the cookie will be valid for (including all sub-directories), e.g. an empty string for the current directory or `/` for the root directory
|
||||
* @param string|null $domain the domain that the cookie will be valid for (including subdomains) or `null` for the current host (excluding subdomains)
|
||||
* @param bool $secureOnly indicates that the cookie should be sent back by the client over secure HTTPS connections only
|
||||
* @param bool $httpOnly indicates that the cookie should be accessible through the HTTP protocol only and not through scripting languages
|
||||
* @param string|null $sameSiteRestriction indicates that the cookie should not be sent along with cross-site requests (either `null`, `None`, `Lax` or `Strict`)
|
||||
* @return bool whether the cookie header has successfully been sent (and will *probably* cause the client to set the cookie)
|
||||
*/
|
||||
public static function setcookie($name, $value = null, $expiryTime = 0, $path = null, $domain = null, $secureOnly = false, $httpOnly = false, $sameSiteRestriction = null)
|
||||
{
|
||||
return self::addHttpHeader(
|
||||
self::buildCookieHeader($name, $value, $expiryTime, $path, $domain, $secureOnly, $httpOnly, $sameSiteRestriction)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the HTTP header that can be used to set a cookie with the specified options
|
||||
*
|
||||
* @param string $name the name of the cookie which is also the key for future accesses via `$_COOKIE[...]`
|
||||
* @param mixed|null $value the value of the cookie that will be stored on the client's machine
|
||||
* @param int $expiryTime the Unix timestamp indicating the time that the cookie will expire at, i.e. usually `time() + $seconds`
|
||||
* @param string|null $path the path on the server that the cookie will be valid for (including all sub-directories), e.g. an empty string for the current directory or `/` for the root directory
|
||||
* @param string|null $domain the domain that the cookie will be valid for (including subdomains) or `null` for the current host (excluding subdomains)
|
||||
* @param bool $secureOnly indicates that the cookie should be sent back by the client over secure HTTPS connections only
|
||||
* @param bool $httpOnly indicates that the cookie should be accessible through the HTTP protocol only and not through scripting languages
|
||||
* @param string|null $sameSiteRestriction indicates that the cookie should not be sent along with cross-site requests (either `null`, `None`, `Lax` or `Strict`)
|
||||
* @return string the HTTP header
|
||||
*/
|
||||
public static function buildCookieHeader($name, $value = null, $expiryTime = 0, $path = null, $domain = null, $secureOnly = false, $httpOnly = false, $sameSiteRestriction = null)
|
||||
{
|
||||
if (self::isNameValid($name)) {
|
||||
$name = (string)$name;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (self::isExpiryTimeValid($expiryTime)) {
|
||||
$expiryTime = (int)$expiryTime;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
$forceShowExpiry = false;
|
||||
|
||||
if (\is_null($value) || $value === false || $value === '') {
|
||||
$value = 'deleted';
|
||||
$expiryTime = 0;
|
||||
$forceShowExpiry = true;
|
||||
}
|
||||
|
||||
$maxAgeStr = self::formatMaxAge($expiryTime, $forceShowExpiry);
|
||||
$expiryTimeStr = self::formatExpiryTime($expiryTime, $forceShowExpiry);
|
||||
|
||||
$headerStr = self::HEADER_PREFIX . $name . '=' . \urlencode($value);
|
||||
|
||||
if (!\is_null($expiryTimeStr)) {
|
||||
$headerStr .= '; expires=' . $expiryTimeStr;
|
||||
}
|
||||
|
||||
// The `Max-Age` property is supported on PHP 5.5+ only (https://bugs.php.net/bug.php?id=23955).
|
||||
if (\PHP_VERSION_ID >= 50500) {
|
||||
if (!\is_null($maxAgeStr)) {
|
||||
$headerStr .= '; Max-Age=' . $maxAgeStr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($path) || $path === 0) {
|
||||
$headerStr .= '; path=' . $path;
|
||||
}
|
||||
|
||||
if (!empty($domain) || $domain === 0) {
|
||||
$headerStr .= '; domain=' . $domain;
|
||||
}
|
||||
|
||||
if ($secureOnly) {
|
||||
$headerStr .= '; secure';
|
||||
}
|
||||
|
||||
if ($httpOnly) {
|
||||
$headerStr .= '; httponly';
|
||||
}
|
||||
|
||||
if ($sameSiteRestriction === self::SAME_SITE_RESTRICTION_NONE) {
|
||||
// if the 'secure' attribute is missing
|
||||
if (!$secureOnly) {
|
||||
\trigger_error('When the \'SameSite\' attribute is set to \'None\', the \'secure\' attribute should be set as well', \E_USER_WARNING);
|
||||
}
|
||||
|
||||
$headerStr .= '; SameSite=None';
|
||||
} elseif ($sameSiteRestriction === self::SAME_SITE_RESTRICTION_LAX) {
|
||||
$headerStr .= '; SameSite=Lax';
|
||||
} elseif ($sameSiteRestriction === self::SAME_SITE_RESTRICTION_STRICT) {
|
||||
$headerStr .= '; SameSite=Strict';
|
||||
}
|
||||
|
||||
return $headerStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given cookie header and returns an equivalent cookie instance
|
||||
*
|
||||
* @param string $cookieHeader the cookie header to parse
|
||||
* @return Cookie|null the cookie instance or `null`
|
||||
*/
|
||||
public static function parse($cookieHeader)
|
||||
{
|
||||
if (empty($cookieHeader)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (\preg_match('/^' . self::HEADER_PREFIX . '(.*?)=(.*?)(?:; (.*?))?$/i', $cookieHeader, $matches)) {
|
||||
$cookie = new self($matches[1]);
|
||||
$cookie->setPath(null);
|
||||
$cookie->setHttpOnly(false);
|
||||
$cookie->setValue(
|
||||
\urldecode($matches[2])
|
||||
);
|
||||
$cookie->setSameSiteRestriction(null);
|
||||
|
||||
if (\count($matches) >= 4) {
|
||||
$attributes = \explode('; ', $matches[3]);
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
if (\strcasecmp($attribute, 'HttpOnly') === 0) {
|
||||
$cookie->setHttpOnly(true);
|
||||
} elseif (\strcasecmp($attribute, 'Secure') === 0) {
|
||||
$cookie->setSecureOnly(true);
|
||||
} elseif (\stripos($attribute, 'Expires=') === 0) {
|
||||
$cookie->setExpiryTime((int)\strtotime(\substr($attribute, 8)));
|
||||
} elseif (\stripos($attribute, 'Domain=') === 0) {
|
||||
$cookie->setDomain(\substr($attribute, 7));
|
||||
} elseif (\stripos($attribute, 'Path=') === 0) {
|
||||
$cookie->setPath(\substr($attribute, 5));
|
||||
} elseif (\stripos($attribute, 'SameSite=') === 0) {
|
||||
$cookie->setSameSiteRestriction(\substr($attribute, 9));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $cookie;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a cookie with the specified name exists
|
||||
*
|
||||
* @param string $name the name of the cookie to check
|
||||
* @return bool whether there is a cookie with the specified name
|
||||
*/
|
||||
public static function exists($name)
|
||||
{
|
||||
return isset($_COOKIE[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value from the requested cookie or, if not found, the specified default value
|
||||
*
|
||||
* @param string $name the name of the cookie to retrieve the value from
|
||||
* @param mixed $defaultValue the default value to return if the requested cookie cannot be found
|
||||
* @return mixed the value from the requested cookie or the default value
|
||||
*/
|
||||
public static function get($name, $defaultValue = null)
|
||||
{
|
||||
if (isset($_COOKIE[$name])) {
|
||||
return $_COOKIE[$name];
|
||||
} else {
|
||||
return $defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private static function isNameValid($name)
|
||||
{
|
||||
$name = (string)$name;
|
||||
|
||||
// The name of a cookie must not be empty on PHP 7+ (https://bugs.php.net/bug.php?id=69523).
|
||||
if ($name !== '' || \PHP_VERSION_ID < 70000) {
|
||||
if (!\preg_match('/[=,; \\t\\r\\n\\013\\014]/', $name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function isExpiryTimeValid($expiryTime)
|
||||
{
|
||||
return \is_numeric($expiryTime) || \is_null($expiryTime) || \is_bool($expiryTime);
|
||||
}
|
||||
|
||||
private static function calculateMaxAge($expiryTime)
|
||||
{
|
||||
if ($expiryTime === 0) {
|
||||
return 0;
|
||||
} else {
|
||||
$maxAge = $expiryTime - \time();
|
||||
|
||||
// The value of the `Max-Age` property must not be negative on PHP 7.0.19+ (< 7.1) and
|
||||
// PHP 7.1.5+ (https://bugs.php.net/bug.php?id=72071).
|
||||
if ((\PHP_VERSION_ID >= 70019 && \PHP_VERSION_ID < 70100) || \PHP_VERSION_ID >= 70105) {
|
||||
if ($maxAge < 0) {
|
||||
$maxAge = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $maxAge;
|
||||
}
|
||||
}
|
||||
|
||||
private static function formatExpiryTime($expiryTime, $forceShow = false)
|
||||
{
|
||||
if ($expiryTime > 0 || $forceShow) {
|
||||
if ($forceShow) {
|
||||
$expiryTime = 1;
|
||||
}
|
||||
|
||||
return \gmdate('D, d-M-Y H:i:s T', $expiryTime);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static function formatMaxAge($expiryTime, $forceShow = false)
|
||||
{
|
||||
if ($expiryTime > 0 || $forceShow) {
|
||||
return (string)self::calculateMaxAge($expiryTime);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static function normalizeDomain($domain = null)
|
||||
{
|
||||
// make sure that the domain is a string
|
||||
$domain = (string)$domain;
|
||||
|
||||
// if the cookie should be valid for the current host only
|
||||
if ($domain === '') {
|
||||
// no need for further normalization
|
||||
return null;
|
||||
}
|
||||
|
||||
// if the provided domain is actually an IP address
|
||||
if (\filter_var($domain, \FILTER_VALIDATE_IP) !== false) {
|
||||
// let the cookie be valid for the current host
|
||||
return null;
|
||||
}
|
||||
|
||||
// for local hostnames (which either have no dot at all or a leading dot only)
|
||||
if (\strpos($domain, '.') === false || \strrpos($domain, '.') === 0) {
|
||||
// let the cookie be valid for the current host while ensuring maximum compatibility
|
||||
return null;
|
||||
}
|
||||
|
||||
// unless the domain already starts with a dot
|
||||
if ($domain[0] !== '.') {
|
||||
// prepend a dot for maximum compatibility (e.g. with RFC 2109)
|
||||
$domain = '.' . $domain;
|
||||
}
|
||||
|
||||
// return the normalized domain
|
||||
return $domain;
|
||||
}
|
||||
|
||||
private static function addHttpHeader($header)
|
||||
{
|
||||
if (!\headers_sent()) {
|
||||
if (!empty($header)) {
|
||||
\header($header, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
126
inc/Hura8/System/Security/DataClean.php
Normal file
126
inc/Hura8/System/Security/DataClean.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Security;
|
||||
|
||||
|
||||
class DataClean
|
||||
{
|
||||
|
||||
/**
|
||||
* @description limit max length. limitLength("toi ten nguyen", 10) => "toi ten ng"
|
||||
* @param string $text
|
||||
* @param int $max_length
|
||||
* @return string
|
||||
*/
|
||||
public static function limitLength(string $text, int $max_length = 100 ) : string
|
||||
{
|
||||
if(strlen($text) <= $max_length) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
return substr($text, 0, $max_length-1);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description limit max length but keep full words. limitLengthFullWords("toi ten nguyen", 10) => "toi ten"
|
||||
* @param string $text
|
||||
* @param int $max_length
|
||||
* @return string
|
||||
*/
|
||||
public static function limitLengthFullWords(string $text, int $max_length = 100 ) : string
|
||||
{
|
||||
|
||||
if(strlen($text) <= $max_length) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$char_at_max_length = $text[$max_length-1];
|
||||
if($char_at_max_length == ' ') {
|
||||
return substr($text, 0, $max_length-1);
|
||||
}
|
||||
|
||||
//else fall back to the last space
|
||||
$words = explode(" ", substr($text, 0, $max_length-1));
|
||||
array_pop($words);
|
||||
|
||||
return join(" ", $words);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $input_list array
|
||||
* @param $type string
|
||||
* @return array
|
||||
*/
|
||||
public static function makeListOfInputSafe(array $input_list, $type){
|
||||
if(!sizeof($input_list)) return [];
|
||||
|
||||
$result = [];
|
||||
foreach ($input_list as $key => $str) {
|
||||
$result[$key] = self::makeInputSafe($str, $type);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $input string data to process
|
||||
* @param $type string, type of data to validate against, enum : int|double|email|id|string|date|plain
|
||||
* @return mixed
|
||||
*/
|
||||
public static function makeInputSafe($input, $type){
|
||||
|
||||
if(is_array($input)) {
|
||||
return $input;
|
||||
}
|
||||
|
||||
if ( $type == DataType::ID ) {
|
||||
//is $input the database item_id ?
|
||||
return preg_replace('/[^a-z0-9_\-\.]/i', '', $input);
|
||||
}
|
||||
|
||||
if ( $type == DataType::EMAIL ) {
|
||||
return filter_var($input, FILTER_VALIDATE_EMAIL) ? $input : '';
|
||||
}
|
||||
|
||||
if ( $type == DataType::INTEGER ) {
|
||||
// support negative number
|
||||
return (int) preg_replace('/[^0-9\-]/', '', $input);
|
||||
}
|
||||
|
||||
if ( $type == DataType::DOUBLE ) {
|
||||
// support negative number
|
||||
$input = preg_replace('/[^0-9,\-]/', '', $input);
|
||||
//convert vietnamese style , to . for percentage
|
||||
$input = str_replace(",", ".", $input);
|
||||
|
||||
return (double) $input;
|
||||
}
|
||||
|
||||
if ( $type == DataType::DATE ) {
|
||||
// support pattern:
|
||||
// date = d-m-Y
|
||||
// datetime = d-m-Y H:i:a
|
||||
$pattern = "/([0-9]{2})-([0-9]{2})-([0-9]{2,4})(\s)?([0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?)?/i";
|
||||
return preg_replace($pattern, '', $input);
|
||||
}
|
||||
|
||||
if ( $type == DataType::PLAIN_TEXT || $type == DataType::STRING ) {
|
||||
return strip_tags($input);
|
||||
}
|
||||
|
||||
if ( $type == DataType::NON_VIETNAMESE ) {
|
||||
return preg_replace('/[^a-z0-9_\s\-]/i', '', $input);
|
||||
}
|
||||
|
||||
if ( $type == DataType::RICH_TEXT ) {
|
||||
return $input;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
22
inc/Hura8/System/Security/DataFormatter.php
Normal file
22
inc/Hura8/System/Security/DataFormatter.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Security;
|
||||
|
||||
class DataFormatter
|
||||
{
|
||||
|
||||
//remove space, ., ,
|
||||
//add 84 to 0923232123 -> 84923232123
|
||||
//dont change 84923232123 -> 84923232123
|
||||
public static function formatMobile($mobile) {
|
||||
$mobile = preg_replace("/[^0-9]/i", "", $mobile);
|
||||
$first_2 = substr($mobile, 0, 2);
|
||||
if( $first_2 != '84') {
|
||||
return '84'.substr($mobile, 1);
|
||||
}
|
||||
|
||||
return $mobile;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
17
inc/Hura8/System/Security/DataType.php
Normal file
17
inc/Hura8/System/Security/DataType.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Security;
|
||||
|
||||
class DataType
|
||||
{
|
||||
const ID = 'id';
|
||||
const EMAIL = 'email';
|
||||
const IP_ADDRESS = 'ip';
|
||||
const INTEGER = 'int';
|
||||
const DOUBLE = 'double';
|
||||
const DATE = 'date'; // date or date-time
|
||||
const STRING = 'plain'; // string without tags
|
||||
const PLAIN_TEXT = 'plain'; // same as STRING type, use any, prefer PLAIN_TEXT to make it clear
|
||||
const NON_VIETNAMESE = 'non_vi'; // same as PLAIN_TEXT and without vietnamese strokes (nguyen instead of Nguyễn)
|
||||
const RICH_TEXT = 'rich_text'; // with html tags
|
||||
}
|
||||
78
inc/Hura8/System/Security/DataValidator.php
Normal file
78
inc/Hura8/System/Security/DataValidator.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Security;
|
||||
|
||||
|
||||
use Hura8\System\Constant;
|
||||
use Hura8\Traits\ClassCacheTrait;
|
||||
|
||||
class DataValidator
|
||||
{
|
||||
|
||||
use ClassCacheTrait;
|
||||
|
||||
|
||||
public static function isValidLength($str, $max_length=50) : bool {
|
||||
if(!is_string($str)) return '';
|
||||
|
||||
return (strlen($str) <= $max_length);
|
||||
}
|
||||
|
||||
|
||||
public static function isIPAddress($input, $type = '') : bool {
|
||||
//validates IPv4
|
||||
if($type == '4') {
|
||||
return filter_var($input, FILTER_VALIDATE_IP,FILTER_FLAG_IPV4);
|
||||
}
|
||||
|
||||
//validates IPv6
|
||||
if($type == '6') {
|
||||
return filter_var($input, FILTER_VALIDATE_IP,FILTER_FLAG_IPV6);
|
||||
}
|
||||
|
||||
// all type
|
||||
// return filter_var($input, FILTER_VALIDATE_IP,FILTER_FLAG_IPV4);
|
||||
|
||||
//validates IPv4 and IPv6, excluding reserved and private ranges
|
||||
return filter_var($input, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function isDate($input) {
|
||||
// support pattern:
|
||||
// date = d-m-Y
|
||||
// datetime = d-m-Y H:i:a
|
||||
$pattern = "/([0-9]{2})-([0-9]{2})-([0-9]{2,4})(\s)?([0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?)?/i";
|
||||
return (preg_match($pattern, $input));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $input
|
||||
* @return string|int
|
||||
*/
|
||||
public static function isMobile(string $input) {
|
||||
$clean = preg_replace("/[^0-9]/", '', $input);
|
||||
return (preg_match('/[0-9]{8,14}$/', $clean));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $input
|
||||
* @return bool
|
||||
*/
|
||||
public static function isEmail($input): bool
|
||||
{
|
||||
return filter_var(trim($input), FILTER_VALIDATE_EMAIL);
|
||||
}
|
||||
|
||||
|
||||
public static function isGender($input) : bool {
|
||||
$system_list = Constant::genderList();
|
||||
|
||||
return (array_key_exists($input, $system_list));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
38
inc/Hura8/System/Security/Encryption.php
Normal file
38
inc/Hura8/System/Security/Encryption.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Security;
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
|
||||
class Encryption
|
||||
{
|
||||
|
||||
// list of algorithm supported in JWT::$supported_algs
|
||||
const JWT_ALG = 'HS256';
|
||||
|
||||
|
||||
public static function jwtDecode($jwt_code, $secret_key) {
|
||||
try {
|
||||
$decoded = (array) JWT::decode($jwt_code, $secret_key, array(Encryption::JWT_ALG));
|
||||
return (array) $decoded['data'];
|
||||
}catch (\Exception $exception) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static function jwtEncode(array $data, $secret_key, $expire_timestamp = 0) {
|
||||
$payload = array(
|
||||
"iss" => $_SERVER['HTTP_HOST'],
|
||||
"data" => $data,
|
||||
"iat" => time(),
|
||||
);
|
||||
|
||||
if($expire_timestamp) $payload['exp'] = $expire_timestamp;
|
||||
|
||||
return JWT::encode($payload, $secret_key, Encryption::JWT_ALG);
|
||||
}
|
||||
|
||||
}
|
||||
18
inc/Hura8/System/Security/Hash.php
Normal file
18
inc/Hura8/System/Security/Hash.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Security;
|
||||
|
||||
class Hash
|
||||
{
|
||||
|
||||
protected const CHECK_SUM_KEY = "as21321ASDAS"; // can change this key per project
|
||||
|
||||
public static function createCheckSum($txt) : string {
|
||||
return hash_hmac('sha256', $txt, static::CHECK_SUM_KEY);
|
||||
}
|
||||
|
||||
public static function verifyCheckSum($checksum, $txt) : bool {
|
||||
return ($checksum == static::createCheckSum($txt));
|
||||
}
|
||||
|
||||
}
|
||||
224
inc/Hura8/System/Security/Session.php
Normal file
224
inc/Hura8/System/Security/Session.php
Normal file
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System\Security;
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* PHP-Cookie (https://github.com/delight-im/PHP-Cookie)
|
||||
* Copyright (c) delight.im (https://www.delight.im/)
|
||||
* Licensed under the MIT License (https://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Session management with improved cookie handling
|
||||
*
|
||||
* You can start a session using the static method `Session::start(...)` which is compatible to PHP's built-in `session_start()` function
|
||||
*
|
||||
* Note that sessions must always be started before the HTTP headers are sent to the client, i.e. before the actual output starts
|
||||
*/
|
||||
final class Session
|
||||
{
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts or resumes a session in a way compatible to PHP's built-in `session_start()` function
|
||||
*
|
||||
* @param string|null $sameSiteRestriction indicates that the cookie should not be sent along with cross-site requests (either `null`, `None`, `Lax` or `Strict`)
|
||||
*/
|
||||
public static function start($sameSiteRestriction = Cookie::SAME_SITE_RESTRICTION_LAX)
|
||||
{
|
||||
// run PHP's built-in equivalent
|
||||
\session_start();
|
||||
|
||||
// intercept the cookie header (if any) and rewrite it
|
||||
self::rewriteCookieHeader($sameSiteRestriction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns or sets the ID of the current session
|
||||
*
|
||||
* In order to change the current session ID, pass the new ID as the only argument to this method
|
||||
*
|
||||
* Please note that there is rarely a need for the version of this method that *updates* the ID
|
||||
*
|
||||
* For most purposes, you may find the method `regenerate` from this same class more helpful
|
||||
*
|
||||
* @param string|null $newId (optional) a new session ID to replace the current session ID
|
||||
* @return string the (old) session ID or an empty string
|
||||
*/
|
||||
public static function id($newId = null)
|
||||
{
|
||||
if ($newId === null) {
|
||||
return \session_id();
|
||||
} else {
|
||||
return \session_id($newId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-generates the session ID in a way compatible to PHP's built-in `session_regenerate_id()` function
|
||||
*
|
||||
* @param bool $deleteOldSession whether to delete the old session or not
|
||||
* @param string|null $sameSiteRestriction indicates that the cookie should not be sent along with cross-site requests (either `null`, `None`, `Lax` or `Strict`)
|
||||
*/
|
||||
public static function regenerate($deleteOldSession = false, $sameSiteRestriction = Cookie::SAME_SITE_RESTRICTION_LAX)
|
||||
{
|
||||
// run PHP's built-in equivalent
|
||||
\session_regenerate_id($deleteOldSession);
|
||||
|
||||
// intercept the cookie header (if any) and rewrite it
|
||||
self::rewriteCookieHeader($sameSiteRestriction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a value for the specified key exists in the session
|
||||
*
|
||||
* @param string $key the key to check
|
||||
* @return bool whether there is a value for the specified key or not
|
||||
*/
|
||||
public static function has($key)
|
||||
{
|
||||
return isset($_SESSION[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requested value from the session or, if not found, the specified default value
|
||||
*
|
||||
* @param string $key the key to retrieve the value for
|
||||
* @param mixed $defaultValue the default value to return if the requested value cannot be found
|
||||
* @return mixed the requested value or the default value
|
||||
*/
|
||||
public static function get($key, $defaultValue = null)
|
||||
{
|
||||
if(isset($_SESSION[$key])) {
|
||||
return $_SESSION[$key];
|
||||
} else {
|
||||
return $defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requested value and removes it from the session
|
||||
*
|
||||
* This is identical to calling `get` first and then `remove` for the same key
|
||||
*
|
||||
* @param string $key the key to retrieve and remove the value for
|
||||
* @param mixed $defaultValue the default value to return if the requested value cannot be found
|
||||
* @return mixed the requested value or the default value
|
||||
*/
|
||||
public static function take($key, $defaultValue = null)
|
||||
{
|
||||
if (isset($_SESSION[$key])) {
|
||||
$value = $_SESSION[$key];
|
||||
|
||||
unset($_SESSION[$key]);
|
||||
|
||||
return $value;
|
||||
} else {
|
||||
return $defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value for the specified key to the given value
|
||||
*
|
||||
* Any data that already exists for the specified key will be overwritten
|
||||
*
|
||||
* @param string $key the key to set the value for
|
||||
* @param mixed $value the value to set
|
||||
*/
|
||||
public static function set($key, $value)
|
||||
{
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the value for the specified key from the session
|
||||
*
|
||||
* @param string $key the key to remove the value for
|
||||
*/
|
||||
public static function delete($key)
|
||||
{
|
||||
unset($_SESSION[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercepts and rewrites the session cookie header
|
||||
*
|
||||
* @param string|null $sameSiteRestriction indicates that the cookie should not be sent along with cross-site requests (either `null`, `None`, `Lax` or `Strict`)
|
||||
*/
|
||||
private static function rewriteCookieHeader($sameSiteRestriction = Cookie::SAME_SITE_RESTRICTION_LAX)
|
||||
{
|
||||
// get and remove the original cookie header set by PHP
|
||||
$originalCookieHeader = Session::takeHeaderCookie('Set-Cookie', \session_name() . '=');
|
||||
|
||||
// if a cookie header has been found
|
||||
if (isset($originalCookieHeader)) {
|
||||
// parse it into a cookie instance
|
||||
$parsedCookie = Cookie::parse($originalCookieHeader);
|
||||
|
||||
// if the cookie has successfully been parsed
|
||||
if (isset($parsedCookie)) {
|
||||
// apply the supplied same-site restriction
|
||||
$parsedCookie->setSameSiteRestriction($sameSiteRestriction);
|
||||
|
||||
if ($parsedCookie->getSameSiteRestriction() === Cookie::SAME_SITE_RESTRICTION_NONE && !$parsedCookie->isSecureOnly()) {
|
||||
\trigger_error('You may have to enable the \'session.cookie_secure\' directive in the configuration in \'php.ini\' or via the \'ini_set\' function', \E_USER_WARNING);
|
||||
}
|
||||
|
||||
// save the cookie
|
||||
$parsedCookie->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns and removes the header with the specified name (and optional value prefix)
|
||||
*
|
||||
* @param string $name the name of the header
|
||||
* @param string $valuePrefix the optional string to match at the beginning of the header's value
|
||||
* @return string|null the header (if found) or `null`
|
||||
*/
|
||||
private static function takeHeaderCookie($name, $valuePrefix = '') {
|
||||
if (empty($name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$nameLength = \strlen($name);
|
||||
$headers = \headers_list();
|
||||
|
||||
$first = null;
|
||||
$homonyms = [];
|
||||
|
||||
foreach ($headers as $header) {
|
||||
if (\strcasecmp(\substr($header, 0, $nameLength + 1), ($name . ':')) === 0) {
|
||||
$headerValue = \trim(\substr($header, $nameLength + 1), "\t ");
|
||||
|
||||
if ((empty($valuePrefix) || \substr($headerValue, 0, \strlen($valuePrefix)) === $valuePrefix) && $first === null) {
|
||||
$first = $header;
|
||||
}
|
||||
else {
|
||||
$homonyms[] = $header;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($first !== null) {
|
||||
\header_remove($name);
|
||||
|
||||
foreach ($homonyms as $homonym) {
|
||||
\header($homonym, false);
|
||||
}
|
||||
}
|
||||
|
||||
return $first;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
153
inc/Hura8/System/TimeManager.php
Normal file
153
inc/Hura8/System/TimeManager.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
|
||||
class TimeManager
|
||||
{
|
||||
|
||||
//convert date from javascript to database
|
||||
public static function convert_date_from_javascript($date) {
|
||||
//$date : dd-mm-yyyy or dd/mm/yyyy to yyyy-mm-dd
|
||||
if (!$date) return "0000-00-00";
|
||||
$date = str_replace('/', '-', $date);
|
||||
|
||||
if(!static::is_date($date)) return "0000-00-00";
|
||||
|
||||
list($day, $month, $year) = array_filter(explode("-", $date));
|
||||
|
||||
return join("-", [$year, $month, $day]); // $array[2] . "-" . $array[1] . "-" . $array[0];
|
||||
}
|
||||
|
||||
|
||||
// check format dd-mm-yyyy as date
|
||||
public static function is_date($date){
|
||||
return (preg_match('/[0-9]{1,2}-[0-9]{1,2}-[0-9]{4}/', $date));
|
||||
}
|
||||
|
||||
|
||||
public static function format_date($time) {
|
||||
if(!$time || $time == '0000-00-00') return '';
|
||||
|
||||
$today = date("d-m-Y");
|
||||
$date_update = date("d-m-Y", strtotime($time));
|
||||
if ($today == $date_update) {
|
||||
return "Hôm nay";
|
||||
}
|
||||
|
||||
return $date_update;
|
||||
}
|
||||
|
||||
public static function format_time($time) {
|
||||
$today = date("d-m-Y");
|
||||
$date_update = date("d-m-Y", strtotime($time));
|
||||
$hour_update = date("g:i a", strtotime($time));
|
||||
|
||||
if ($today == $date_update) {
|
||||
return "Hôm nay, " . $hour_update;
|
||||
}
|
||||
|
||||
return $date_update . ", " . $hour_update;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $from_date string dd-mm-YYYY
|
||||
* @param $to_date string dd-mm-YYYY
|
||||
* @return array [
|
||||
[
|
||||
"date" => 'd-m-Y',
|
||||
"day" => 'Mon|Tue|Wed|Thu|Fri|Sat|Sun',
|
||||
"date_in_year" => 'd-m',
|
||||
],
|
||||
// ...
|
||||
]
|
||||
*/
|
||||
public static function get_date_range($from_date, $to_date = ''){
|
||||
if(!$to_date) $to_date = $from_date;
|
||||
|
||||
$periods = new \DatePeriod(
|
||||
new \DateTime(static::reverse_date($from_date)),
|
||||
new \DateInterval('P1D'),
|
||||
new \DateTime(static::reverse_date($to_date))
|
||||
);
|
||||
|
||||
$range = [];
|
||||
foreach ($periods as $key => $value) {
|
||||
$range[] = [
|
||||
"date" => $value->format('d-m-Y'),
|
||||
"day" => $value->format('D'),
|
||||
"date_in_year" => $value->format('d-m'),
|
||||
];
|
||||
}
|
||||
|
||||
$to_date_time = strtotime(static::reverse_date($to_date));
|
||||
$range[] = [
|
||||
"date" => $to_date,
|
||||
"day" => date("D", $to_date_time),
|
||||
"date_in_year" => date("d-m", $to_date_time),
|
||||
];
|
||||
|
||||
return $range;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $from_date string dd-mm-YYYY
|
||||
* @param $to_date string dd-mm-YYYY
|
||||
* @return int number of days
|
||||
*/
|
||||
public static function get_day_diff($from_date, $to_date) {
|
||||
$now = strtotime(static::reverse_date($to_date));
|
||||
$your_date = strtotime(static::reverse_date($from_date));
|
||||
$datediff = $now - $your_date;
|
||||
|
||||
return round($datediff / (60 * 60 * 24));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $date string dd-mm-YYYY
|
||||
* @param $num_days int
|
||||
* @return string dd-mm-YYYY
|
||||
*/
|
||||
public static function add_day($date, $num_days = 1) {
|
||||
return date('d-m-Y', strtotime(static::reverse_date($date). ' + '.$num_days.' days'));
|
||||
}
|
||||
|
||||
|
||||
//Reverse date from yyyy-mm-dd to dd-mm-yyyy
|
||||
public static function reverse_date($p_date) {
|
||||
|
||||
list($year, $month, $day) = explode("-", $p_date);
|
||||
|
||||
if(!$year || $year == '0000') return '';
|
||||
|
||||
return join("-", [$day, $month, $year]);
|
||||
}
|
||||
|
||||
|
||||
public static function getDateTimeForMySQL() {
|
||||
return date("Y-m-d H:i:s");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description convert date pattern: dd-mm-YYYY H:i to timestamp
|
||||
* @param string $input_date_time dd-mm-YYYY H:i to timestamp
|
||||
* @return int
|
||||
*/
|
||||
public static function convertDateTimeToInt($input_date_time) {
|
||||
|
||||
$check_date_pattern = "/\d{2}-\d{2}-\d{4}(\s+)?\d{1,2}:\d{1,2}/i";
|
||||
$format_date_time = str_replace("/", "-", $input_date_time);
|
||||
|
||||
if(preg_match($check_date_pattern, $format_date_time)) {
|
||||
list($date, $time) = array_values(array_filter(explode(" ", $format_date_time)));
|
||||
|
||||
return strtotime($date. " ". $time);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
152
inc/Hura8/System/UploadZip.php
Normal file
152
inc/Hura8/System/UploadZip.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
|
||||
class UploadZip
|
||||
{
|
||||
protected $upload_folder;
|
||||
|
||||
protected $allow_file_extensions = array(
|
||||
".jpeg", ".jpg", ".gif", ".png", ".webp", ".ico",
|
||||
".html", ".htm",
|
||||
".js",
|
||||
".css",
|
||||
);
|
||||
|
||||
|
||||
public function __construct($upload_folder) {
|
||||
$this->upload_folder = PUBLIC_DIR . "/" . $upload_folder;
|
||||
}
|
||||
|
||||
|
||||
public function getFileList() {
|
||||
$file_list = [];
|
||||
$this->scanFileInDir( $this->upload_folder, $file_list);
|
||||
|
||||
return $file_list;
|
||||
}
|
||||
|
||||
|
||||
protected function scanFileInDir($folder, &$file_list = []) {
|
||||
$dir = opendir($folder);
|
||||
while(( $file = readdir($dir)) ) {
|
||||
if (( $file != '.' ) && ( $file != '..' )) {
|
||||
if ( is_dir($folder. '/' . $file) ) {
|
||||
$this->scanFileInDir($folder. '/' . $file, $file_list);
|
||||
}
|
||||
else {
|
||||
$file_list[] = str_replace(PUBLIC_DIR . "/", "", $folder .'/'. $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($dir);
|
||||
}
|
||||
|
||||
|
||||
public function handleUpload($input_file_name='file') {
|
||||
|
||||
if(!$this->check_upload_folder()) {
|
||||
return [
|
||||
'status' => 'error',
|
||||
'message' => "Target folder not exist or writeable. Folder: ". $this->upload_folder,
|
||||
];
|
||||
}
|
||||
|
||||
$filename = $_FILES[$input_file_name]["name"];
|
||||
$source = $_FILES[$input_file_name]["tmp_name"];
|
||||
$type = $_FILES[$input_file_name]["type"];
|
||||
|
||||
list($filename_no_ext, $file_ext) = explode(".", $filename);
|
||||
$filename_no_ext = preg_replace("/[^a-z0-9_]/i", "", $filename_no_ext);
|
||||
|
||||
if(!strtolower($file_ext) == 'zip') {
|
||||
return [
|
||||
'status' => 'error',
|
||||
'message' => "File này không phải file .zip, vui lòng sửa và upload lại",
|
||||
];
|
||||
}
|
||||
|
||||
//unzip to a temporary folder, test the content if ok then move to desired folder
|
||||
$target_path_tmp = $this->uploadToTmpFolder($source, $filename);
|
||||
$this->removeDangerousFiles($target_path_tmp);
|
||||
$this->transferFileFromTmpFolder($target_path_tmp);
|
||||
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'message' => "",
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
protected function uploadToTmpFolder($source, $filename ) {
|
||||
$target_path_tmp = $this->upload_folder . "/tmp_".CURRENT_TIME; // change this to the correct site path
|
||||
if(!file_exists($target_path_tmp)) @mkdir($target_path_tmp, 0755);
|
||||
|
||||
//$target_path = $this->upload_folder . "/".$filename_no_ext;
|
||||
//if(!file_exists($target_path)) mkdir($target_path, 0755);
|
||||
|
||||
if(move_uploaded_file($source, $target_path_tmp . "/".$filename)) {
|
||||
$zip = new \ZipArchive();
|
||||
$x = $zip->open($target_path_tmp . "/".$filename);
|
||||
if ($x === true) {
|
||||
$zip->extractTo($target_path_tmp); // change this to the correct site path
|
||||
$zip->close();
|
||||
}
|
||||
@unlink($target_path_tmp . "/".$filename);
|
||||
}
|
||||
|
||||
return $target_path_tmp;
|
||||
}
|
||||
|
||||
|
||||
protected function removeDangerousFiles($target_path_tmp) {
|
||||
foreach ( new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($target_path_tmp),
|
||||
RecursiveIteratorIterator::SELF_FIRST) as $item
|
||||
) {
|
||||
if(!$item->isDir()) {
|
||||
//only allow : image, html file
|
||||
$ext = strtolower(strrchr($item, "."));
|
||||
if(in_array($ext, $this->allow_file_extensions )){
|
||||
//copy to new folder
|
||||
$list_valid_file[] = (string) $item;
|
||||
|
||||
if(strpos($item, ".php") !== false) {
|
||||
//remove potetial harmful files like: r57.php.jpg, c100.php.html
|
||||
@unlink($item);
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
@unlink($item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function transferFileFromTmpFolder($target_path_tmp) {
|
||||
recursive_copy($target_path_tmp, $this->upload_folder);
|
||||
removeDir($target_path_tmp);
|
||||
}
|
||||
|
||||
protected function check_upload_folder() {
|
||||
if(!file_exists($this->upload_folder)) {
|
||||
@mkdir($this->upload_folder, 0755, true);
|
||||
}
|
||||
|
||||
return (file_exists($this->upload_folder) && is_writable($this->upload_folder)) ;
|
||||
}
|
||||
|
||||
|
||||
public function get_upload_size_limit() {
|
||||
$max_upload = (int)(ini_get('upload_max_filesize'));
|
||||
$max_post = (int)(ini_get('post_max_size'));
|
||||
$memory_limit = (int)(ini_get('memory_limit'));
|
||||
return min($max_upload, $max_post, $memory_limit);
|
||||
}
|
||||
|
||||
}
|
||||
123
inc/Hura8/System/Url.php
Normal file
123
inc/Hura8/System/Url.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
|
||||
class Url
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @return bool|string
|
||||
*/
|
||||
public static function getUrlContent(string $url) {
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1; rv:33.0) Gecko/20100101 Firefox/33.0");
|
||||
//curl_setopt($ch, CURLOPT_PROXY, $proxy); // $proxy is ip of proxy server
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
//$httpCode = curl_getinfo($ch , CURLINFO_HTTP_CODE); // this results 0 every time
|
||||
$response = curl_exec($ch);
|
||||
//if ($response === false) $response = curl_error($ch);
|
||||
//echo stripslashes($response);
|
||||
curl_close($ch);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
public static function isUrlValid($url) {
|
||||
if(!$url) return false;
|
||||
return (filter_var($url, FILTER_VALIDATE_URL) !== false);
|
||||
}
|
||||
|
||||
|
||||
public static function parse($url) {
|
||||
$data = parse_url($url);
|
||||
$default = [
|
||||
'scheme' => '',
|
||||
'host' => '',
|
||||
'port' => '',
|
||||
'user' => '',
|
||||
'pass' => '',
|
||||
'path' => '',
|
||||
'query' => [],
|
||||
'fragment' => '',
|
||||
];
|
||||
foreach ($data as $key => $value) {
|
||||
if(isset($default[$key])) {
|
||||
$default[$key] = ($key == 'query') ? self::parsedQuery($value) : $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
|
||||
public static function parsedQuery($query = '') {
|
||||
if(!$query) return [];
|
||||
$result = [];
|
||||
$parts = array_filter(explode("&", $query));
|
||||
|
||||
foreach ($parts as $part) {
|
||||
$el = explode("=", $part);
|
||||
if(sizeof($el) != 2) continue;
|
||||
|
||||
$cleaned_key = preg_replace("/[^a-z0-9_\-\.]/i", '', $el[0]);
|
||||
$cleaned_value = urldecode($el[1]); //preg_replace("/[^a-z0-9_\.\-;&,%]/i", '', urldecode($el[1]));
|
||||
|
||||
$result[$cleaned_key] = $cleaned_value;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
//2-12-2010 build url base on query parameters
|
||||
// $query_para = array(key=>value)
|
||||
public static function buildUrl($base_url, $query_para, $exclude_page_id = true){
|
||||
|
||||
$base_element = parse_url($base_url);
|
||||
|
||||
if(!isset($base_element['scheme'])) {
|
||||
$port = $_SERVER['SERVER_PORT'] ?? '80';
|
||||
|
||||
if((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || $port == 443 ) {
|
||||
$base_element['scheme'] = 'https';
|
||||
}
|
||||
else $base_element['scheme'] = 'http';
|
||||
}
|
||||
|
||||
$current_query_para = array();
|
||||
if(isset($base_element['query'])){
|
||||
parse_str($base_element['query'], $current_query_para);
|
||||
}
|
||||
|
||||
$new_query_para = array_merge($current_query_para, $query_para);
|
||||
$filter_query_para = [];
|
||||
foreach($new_query_para as $key => $val){
|
||||
if($val) $filter_query_para[$key] = $val; //urlencode($val); // http_build_query will encode value, no need for urlencode here
|
||||
}
|
||||
|
||||
if($exclude_page_id && isset($filter_query_para['page'])) unset($filter_query_para['page']);
|
||||
|
||||
ksort($filter_query_para);
|
||||
|
||||
$query_string = http_build_query($filter_query_para);
|
||||
|
||||
$host_name = (isset($base_element['host'])) ? $base_element['host'] : ($_SERVER['HTTP_HOST'] ?? '');
|
||||
$url_path = isset($base_element['path']) ? $base_element['path'] : '';
|
||||
|
||||
if(substr($url_path, 0, 1) !== '/') $url_path = '/'.$url_path;
|
||||
|
||||
if($query_string) return $base_element['scheme'] . "://". $host_name . $url_path. "?" . $query_string;
|
||||
|
||||
return $base_element['scheme'] . "://". $host_name . $url_path;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
86
inc/Hura8/System/Youtube.php
Normal file
86
inc/Hura8/System/Youtube.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by Glee Ltd.
|
||||
* User: Hieu
|
||||
* Date: 05-Mar-18
|
||||
* Time: 4:16 PM
|
||||
* Description:
|
||||
*/
|
||||
|
||||
namespace Hura8\System;
|
||||
|
||||
|
||||
class Youtube
|
||||
{
|
||||
|
||||
public static function buildVideoUrl($video_id) {
|
||||
return 'https://www.youtube.com/watch?v='.$video_id;
|
||||
}
|
||||
|
||||
/* 08-06-2016
|
||||
* @description: get youtube thumnail
|
||||
* @param url youtube's video url
|
||||
* @param size thumnail size: default|high|medium
|
||||
* @return url or ''
|
||||
*/
|
||||
public static function getYouTubeThumbnail($url){
|
||||
$id = static::getVideoId($url);
|
||||
if($id) {
|
||||
return "https://img.youtube.com/vi/".$id."/hqdefault.jpg";
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public static function getVideoId($url) {
|
||||
if(!$url) return '';
|
||||
|
||||
// direct video code
|
||||
if(!Url::isUrlValid($url)) return $url;
|
||||
|
||||
//$url = https://www.youtube.com/watch?v=9246msCh7x4
|
||||
$pattern =
|
||||
'%^# Match any youtube URL
|
||||
(?:https?://)? # Optional scheme. Either http or https
|
||||
(?:www\.)? # Optional www subdomain
|
||||
(?: # Group host alternatives
|
||||
youtu\.be/ # Either youtu.be,
|
||||
| youtube\.com # or youtube.com
|
||||
(?: # Group path alternatives
|
||||
/embed/ # Either /embed/
|
||||
| /v/ # or /v/
|
||||
| .*v= # or /watch\?v=
|
||||
) # End path alternatives.
|
||||
) # End host alternatives.
|
||||
([\w-]{10,12}) # Allow 10-12 for 11 char youtube id.
|
||||
($|&).* # if additional parameters are also in query string after video id.
|
||||
$%x'
|
||||
;
|
||||
|
||||
if (preg_match($pattern, $url, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
//21-12-2012
|
||||
//show Youtube Video code from given youtube url
|
||||
public static function buildHtml($url, $method='iframe', $video_width=560, $video_height=315, $auto_play=1) {
|
||||
$youtube_id = self::getVideoId($url);
|
||||
|
||||
if ($youtube_id) {
|
||||
switch ($method){
|
||||
case "iframe";
|
||||
if($auto_play == 1) {
|
||||
return "<iframe width=\"".$video_width."\" height=\"".$video_height."\" src=\"https://www.youtube.com/embed/".$youtube_id."?rel=0&autoplay=1\" frameborder=\"0\" allowfullscreen></iframe>";
|
||||
}else{
|
||||
return "<iframe width=\"".$video_width."\" height=\"".$video_height."\" src=\"https://www.youtube.com/embed/".$youtube_id."?rel=0\" frameborder=\"0\" allowfullscreen></iframe>";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
3
inc/Hura8/System/_readme.txt.txt
Normal file
3
inc/Hura8/System/_readme.txt.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
01-Dec-2023
|
||||
|
||||
Base system files
|
||||
Reference in New Issue
Block a user