832 lines
29 KiB
PHP

<?php
namespace Aiko;
use Aiko\Database\Connections;
use Aiko\Http;
use Aiko\Token;
use modules\rule\model\Rule;
use Predis\Client;
use Predis\Session\Handler;
use Aiko\SessionRedis;
class Registry
{
private $vars = array();
public function __set($index, $value)
{
$this->vars[$index] = $value;
}
public function __get($index)
{
return $this->vars[$index];
}
}
abstract class Controller
{
protected $registry;
public $ActionAjaxOff;
protected $methodAccess;
protected $apiAction;
protected $apiParams;
protected $apiModule;
protected $publicAction = array();
private $allowJwt = array();
protected $appID;
protected $tokenID;
protected $generalActions=array();
protected $isFile=false;
private $allowedMimeType = [
'image/jpeg',
'image/png',
'image/jpg',
'video/mp4',
'application/pdf',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheetapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'text/plain',
'application/octet-stream',
'application/zip',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'application/vnd.ms-word.document.macroEnabled.12',
'application/vnd.ms-word.template.macroEnabled.12',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'application/vnd.ms-excel.sheet.macroEnabled.12',
'application/vnd.ms-excel.template.macroEnabled.12',
'application/vnd.ms-excel.addin.macroEnabled.12',
'application/vnd.ms-excel.sheet.binary.macroEnabled.12'
];
public function __construct($registry)
{
// session_id('hcportal-session-id');
// session_start();
// Http::enabledCors();
$this->registry = $registry;
$this->methodAccess = $_SERVER['REQUEST_METHOD'];
$this->allowJwt = array('dologin', 'dologout', 'refreshToken', 'generateNewToken', 'loginauth');
// connect main DB
// $this->registry = $registry;
// $this->registry->db = Connections::getInstance(
// $this->registry->config->host,
// $this->registry->config->db,
// $this->registry->config->socket,
// $this->registry->config->user,
// $this->registry->config->password,
// $this->registry->config->dbms
// );
if($this->registry->config->dbMainConType!=='local')
{
$this->registry->db = Connections::getInstance($this->registry->config->dbMainConType);
}else {
$this->registry->db = Connections::getInstance(
$this->registry->config->dbMainConType,
$this->registry->config->host,
$this->registry->config->socket,
$this->registry->config->user,
$this->registry->config->password
);
}
// $handler = new Session($registry);
// $result= session_set_save_handler($handler,true);
// session_start();
// session_start();
// if (!interface_exists('SessionHandlerInterface')) {
// exit('ATTENTION: the session handler implemented by Predis requires PHP >= 5.4.0 ' .
// "or a polyfill for SessionHandlerInterface provided by an external package.\n");
// }
// $single_server=[
// 'scheme' => 'tcp',
// 'host' => '10.1.200.218',
// 'port' => 6388,
// ];
// $client = new Client($single_server, ['prefix' => 'sessions:']);
// // Set `gc_maxlifetime` to specify a time-to-live of 5 seconds for session keys.
// $handler = new Handler($client, ['gc_maxlifetime' => get_cfg_var("session.gc_maxlifetime")]);
// // Register the session handler.
// $handler->register();
// // We just set a fixed session ID only for the sake of our example.
// session_id('hcportalsessionid');
if(!isset($_SESSION))
{
session_start();
}
// check mime_type
$this->checkContentFile();
}
abstract public function index();
protected function checkToken()
{
try {
$token = Http::getTokenJWT();
// get token ID
$tokenPart = explode('.', $token);
if (count($tokenPart) != 4) {
throw new \ErrorException('token part invalid');
}
$stmt = $this->registry->db->prepare('select appID,tokenID,chipper,data,expired from jwt where id=:id');
$stmt->bindValue(':id', $tokenPart[3], \PDO::PARAM_INT);
$stmt->execute();
$rs = $stmt->fetchAll(\PDO::FETCH_ASSOC);
if (count($rs) == 0) {
throw new \ErrorException('token jwt not exist');
}
$this->appID=$rs[0]['appID'];
$this->tokenID=$rs[0]['tokenID'];
$now = time();
if ($rs[0]['expired'] < $now) {
throw new \Exception('Time Token refresh Exceded');
}
// update expired
$stmt = $this->registry->db->prepare('update jwt set expired=:expired where id=:id');
$stmt->bindValue(':id', $tokenPart[3], \PDO::PARAM_INT);
$stmt->bindValue(':expired', time() + __LIFETIMEJWT, \PDO::PARAM_INT);
$stmt->execute();
$newToken = $tokenPart[0] . '.' . $tokenPart[1] . '.' . $tokenPart[2];
$data = Token::decodeJWTNew($newToken, $rs[0]['chipper']);
if (is_numeric($data)) {
if ($data === 8) // expired token
{
$this->registry->log->customError('tokenError', 'Module Controller / check Token : expired token , token :' . $data);
Http::tokenExpired(array('message' => 'Token need refresh'));
} else {
throw new \ErrorException('decode Error token :' . $data);
}
}
$rData = json_decode(json_encode($data->data), true);
\Helper::setSession($rData);
return true;
} catch (\ErrorException $e) {
$this->registry->log->customError('tokenError', 'Module Controller / check Token :' . $e->getMessage());
Http::UnauthorizedResponseJson(array('message' => 'Wrong Token'));
//return false;
} catch (\Exception $e) {
$this->registry->log->customError('tokenError', 'Module Controller / check Token :' . $e->getMessage());
Http::UnauthorizedResponseJson(array('message' => $e->getMessage()));
} catch (\PDOException $e) {
$this->registry->log->customError('tokenError', 'Module Controller / check Token :' . $e->getMessage());
Http::UnauthorizedResponseJson(array('message' => 'query error '));
}
}
protected function checkTokenOld()
{
try {
$token = Http::getTokenJWT();
$data = Token::decodeJWTNew($token);
if (is_numeric($data)) {
if ($data === 8) // expired token
{
$this->registry->log->customError('tokenError', 'Module Controller / check Token : expired token , token :' . $data);
Http::tokenExpired(array('message' => 'Wrong Token'));
} else {
throw new \ErrorException('decode Error token :' . $data);
}
}
$rData = json_decode(json_encode($data->data), true);
\Helper::setSession($rData);
return true;
} catch (\ErrorException $e) {
$this->registry->log->customError('tokenError', 'Module Controller / check Token :' . $e->getMessage());
Http::UnauthorizedResponseJson(array('message' => 'Wrong Token'));
//return false;
} catch (\Exception $e) {
$this->registry->log->customError('tokenError', 'Module Controller / check Token :' . $e->getMessage());
Http::UnauthorizedResponseJson(array('message' => $e->getMessage()));
} catch (\PDOException $e) {
$this->registry->log->customError('tokenError', 'Module Controller / check Token :' . $e->getMessage());
Http::UnauthorizedResponseJson(array('message' => 'query error '));
}
}
protected function checkRulesAccess()
{
$rule = new Rule($this->registry);
if (!in_array($this->apiAction, $this->publicAction)) {
$hasAccess = $rule->hasAccess($this->apiModule, $this->apiAction);
if ($hasAccess == false) {
Http::ErrorQueryResponse('operation not permit', 'json');
}
}
}
protected function checkAPIAccess()
{
/* check method access */
$this->allowOptionMethod();
if (!in_array($this->methodAccess, array('POST', 'GET', 'DELETE'))) {
Http::UnauthorizedResponseJson(array('message' => 'Method Not allowed'));
}
$this->apiAction = '';
switch ($this->methodAccess) {
case 'POST':
/* check and get action */
$this->apiAction = Http::GetvarData('action');
if (!isset($this->apiAction)) {
$jtext = Http::GetBodyRequest();
$this->apiParams = \Firebase\JWT\JWT::jsonDecode($jtext);
if (!isset($this->apiParams->action)) {
Http::UnauthorizedResponseJson(array('message' => 'Action not set'));
}
$this->apiAction = $this->apiParams->action;
}
break;
default:
// GET // DELETE
$this->apiAction = Http::GetvarData('action');
if (strlen($this->apiAction) === 0) {
Http::UnauthorizedResponseJson(array('message' => 'Action not set'));
}
break;
}
/* check token */
$isAllowed = $this->checkToken();
if (!$isAllowed) {
Http::UnauthorizedResponseJson(array('message' => 'Wrong Token'));
}
/* check rule */
$this->checkRulesAccess();
}
protected function isAuthorized()
{
/* check method access */
$this->allowOptionMethod();
if (!in_array($this->methodAccess, array('POST', 'GET', 'DELETE'))) {
Http::UnauthorizedResponseJson(array('message' => 'Method Not allowed'));
}
$this->apiAction = '';
// var_dump($this->methodAccess);
switch ($this->methodAccess) {
case 'POST':
/* check and get action */
if($this->isFile){
$aText['action']=Http::GetVarData('action','post');
$this->apiParams=\Firebase\JWT\JWT::jsonDecode(\Firebase\JWT\JWT::jsonEncode($aText));
$this->apiAction = Http::GetVarData('action','post');
}else{
$jtext = Http::GetBodyRequest();
$this->apiParams = \Firebase\JWT\JWT::jsonDecode($jtext);
if (!isset($this->apiParams->action)) {
Http::UnauthorizedResponseJson(array('message' => 'Action not set'));
}
$this->apiAction = $this->apiParams->action;
}
break;
default:
// GET // DELETE
$this->apiAction = Http::GetvarData('action');
$this->apiParams = json_decode(json_encode(Http::getAllRequest()));
if (strlen($this->apiAction) === 0) {
Http::UnauthorizedResponseJson(array('message' => 'Action not set'));
}
break;
}
if (!in_array($this->apiAction, $this->allowJwt)) {
/* check token */
$isAllowed = $this->checkToken();
if (!$isAllowed) {
Http::UnauthorizedResponseJson(array('message' => 'Wrong Token'));
}
}
if (is_array($this->generalActions)) {
/* check rule */
if(!in_array($this->apiAction,$this->generalActions)){
$this->checkRulesAccess();
}
}
/* process request */
$this->prosesRequest();
}
protected function checkAPIAccessEvaluation()
{
if ($this->methodAccess == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
Http::ResponseJson('ok', '0', '1');
}
}
$isAllowed = $this->checkTokenEvaluation();
if (!$isAllowed) {
Http::UnauthorizedResponseJson(array('message' => 'Wrong Token'));
}
}
protected function checkTokenEvaluation()
{
try {
$token = Http::getTokenJWT();
$data = Token::decodeJWT($token);
if (!isset($data->data)) {
throw new \ErrorException('decode Error token :' . $token);
}
$_SESSION = array();
session_destroy();
$_SESSION['group'] = $data->data->group;
$_SESSION['username'] = $data->data->username;
$_SESSION['name'] = isset($data->data->name) ? $data->data->name : $data->data->nama;
$_SESSION['section'] = isset($data->data->section) ? $data->data->section : $data->data->secion;
$_SESSION['userID'] = $data->data->userID;
$_SESSION['empNo'] = isset($data->data->empNo) ? $data->data->empNo : '';
$_SESSION['empSite'] = $data->data->empSite;
$_SESSION['empSubArea'] = isset($data->data->empSubArea) ? $data->data->empSubArea : '';
$_SESSION['flagApp'] = isset($data->data->flagApp) ? $data->data->flagApp : '';
$_SESSION['nationality'] = isset($data->data->nationality) ? $data->data->nationality : '';
$_SESSION['role'] = isset($data->data->role) ? $data->data->role : '';
// if jwt valid set session var
return true;
} catch (\ErrorException $e) {
$this->registry->log->error('Module Controller / check Token Eval :' . $e->getMessage());
return false;
}
}
protected function allowOptionMethod()
{
if ($this->methodAccess == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
Http::ResponseJson(array('ok'), '0', '1');
}
}
}
private function prosesRequest()
{
switch ($this->methodAccess) {
case 'POST':
$this->executePost();
break;
case 'GET':
$this->executeGet();
break;
case 'DELETE':
$this->executeDelete();
break;
default:
Http::ErrorQueryResponse('method not permit');
break;
}
}
protected function executePost()
{
$act = $this->apiAction;
if (method_exists($this, $act)) {
$this->$act();
} else {
Http::ErrorQueryResponse('Action not registered');
}
}
private function executeGet()
{
$act = $this->apiAction;
if (method_exists($this, $act)) {
$this->$act();
} else {
Http::ErrorQueryResponse('Action not registered');
}
}
protected function executeDelete()
{
}
protected function extendAllowJwt(array $extended)
{
foreach ($extended as $value) {
array_push($this->allowJwt, $value);
}
}
/**
* fungsi ini untuk convert message dari api.
* untuk keperluan migrasi FE ke framework yang baru
* karena fokus utama migrasi FE dulu jadi BE yang menyesuaikan
*
* @param message
* @return $result : string
*/
protected function convertMessages($message)
{
$result = $message;
switch($message){
case 'PAYROLL.MESSAGE.SUCCMESINS':
$result = 'MESSAGE.SUCCMESINS';
break;
case 'PAYROLL.MESSAGE.FAILMESEXIST':
$result = 'MESSAGE.DATA_ALREADY_EXIST';
break;
case 'PAYROLL.MESSAGE.FAILMESUNKNOWN':
$result = 'MESSAGE.FAILMESUNKNOWN';
break;
case 'PAYROLL.MESSAGE.FAILMESERRREQ':
$result = 'MESSAGE.FAILMESERRREQ';
break;
case 'PAYROLL.MESSAGE.SUCCMESDEL':
$result = 'MESSAGE.SUCCMESDEL';
break;
case 'PAYROLL.MESSAGE.SUCCMESUPD':
$result = 'MESSAGE.SUCCMESUPD';
break;
case 'PAYROLL.MESSAGE.FAILMESQUERY':
$result = 'MESSAGE.FAILMESQUERY';
break;
case 'MENU.MASTER_DATA.ADMINISTRATIVE_AREA.MAIN.CANTDELETE':
$result = 'MESSAGE.CANTDELETE';
break;
}
return $result;
}
/**
* fungsi ini untuk convert response menjadi format pagination.
* untuk keperluan migrasi FE ke framework yang baru
* karena fokus utama migrasi FE dulu jadi BE yang menyesuaikan
*
* @param array
* @return array
*/
protected function convertToPaginationFormat($array)
{
$total = count($array);
$aData['iTotalDisplayRecords'] = $total;
$aData['iTotalRecords'] = $total;
$aData['aData'] = $array;
return $aData;
}
private function checkContentFile(){
$this->isFile=Http::isMultipartFormData();
if($this->isFile){
if (!empty($_FILES) && is_array($_FILES) && count($_FILES) > 0) {
foreach ($_FILES as $file) {
$filepath = $file['tmp_name'];
// $filesize = filesize($filepath);
$fileinfo = finfo_open(FILEINFO_MIME_TYPE);
$filetype = finfo_file($fileinfo, $filepath);
finfo_close($fileinfo);
if (!in_array($filetype, $this->allowedMimeType)) {
Http::ErrorQueryResponse(array('name' => $file['name'], 'message' =>'15220-failed'), 'json');
}
}
}
}
}
protected function setSession($token)
{
// get token ID
$tokenPart = explode('.', $token);
\Helper::dump($tokenPart);
if (count($tokenPart) != 4) {
throw new \ErrorException('token part invalid');
}
$stmt = $this->registry->db->prepare('select appID,tokenID,chipper,data,expired from jwt where id=:id');
$stmt->bindValue(':id', $tokenPart[3], \PDO::PARAM_INT);
$stmt->execute();
$rs = $stmt->fetchAll(\PDO::FETCH_ASSOC);
if (count($rs) == 0) {
throw new \ErrorException('token jwt not exist');
}
$this->appID=$rs[0]['appID'];
$this->tokenID=$rs[0]['tokenID'];
$now = time();
if ($rs[0]['expired'] < $now) {
throw new \Exception('Time Token refresh Exceded');
}
// update expired
$stmt = $this->registry->db->prepare('update jwt set expired=:expired where id=:id');
$stmt->bindValue(':id', $tokenPart[3], \PDO::PARAM_INT);
$stmt->bindValue(':expired', time() + __LIFETIMEJWT, \PDO::PARAM_INT);
$stmt->execute();
$newToken = $tokenPart[0] . '.' . $tokenPart[1] . '.' . $tokenPart[2];
$data = Token::decodeJWTNew($newToken, $rs[0]['chipper']);
if (is_numeric($data)) {
if ($data === 8) // expired token
{
$this->registry->log->customError('tokenError', 'Module Controller / check Token : expired token , token :' . $data);
Http::tokenExpired(array('message' => 'Token need refresh'));
} else {
throw new \ErrorException('decode Error token :' . $data);
}
}
$rData = json_decode(json_encode($data->data), true);
\Helper::setSession($rData);
}
}
class Router
{
private $registry;
private $path;
private $args = array();
public $file;
public $controller;
public $action;
public $parts;
private $controllerPath;
private $prefix;
public function __construct($registry,$prefix='')
{
$this->registry = $registry;
$this->prefix=$prefix;
}
public function loader()
{
try {
/*** a new controller class instance , pembuatan controller object***/
$class = $this->controller;
$this->registry->controller = $class;
$this->registry->action = $this->action;
$ClassName = ucfirst($class);
$mod = strtolower($class);
$aModules = explode('/', $this->controllerPath);
$jumModules = count($aModules);
//$mod1 = substr($this->controllerPath, 1);
$mod1 = $this->controllerPath;
$strslash = substr($this->controllerPath, 0, 1);
if ($strslash == '/' || $strslash == '\\') {
$mod1 = substr($this->controllerPath, 1);
}
$newmod = str_replace('/', '\\', $mod1);
$namespaces = "\\modules\\{$newmod}\\controller\\{$ClassName}Controller";
$this->registry->ContPath = $mod1;
$controller = new $namespaces($this->registry);
/*** check if the action is callable ***/
if (is_callable(array($controller, $this->action)) == false) {
$action = 'index';
} else {
$action = $this->action;
}
/*** run the action , ini sama kayak execute function yang ada pada controller pada mvc sebelumnya
* ***/
if ($this->registry->config->ajax == 'on') {
if (!empty($controller->ActionAjaxOff)) {
if (!in_array($action, $controller->ActionAjaxOff)) {
// if true
if (!$this->registry->isAjax) {
exit('ajax request required');
}
}
} else {
if (!$this->registry->isAjax) {
exit('ajax request required');
}
}
} else {
if ($this->registry->isAjax) {
exit('please set ajax config to "on" if request ajax required');
}
}
$controller->$action();
} catch (\Exception $e) {
$this->registry->log->error('Core / Loader :' . $e->getMessage() . ' Line :' . $e->getLine() . ' ' . $e->getFile());
Http::InternalServerError('error loader');
}
}
private function getController()
{
try {
/* get variable*/
$this->controller = $this->getControllerName();
$j = 0;
if (!(empty($this->parts[2]) or $this->parts[2] == '-')) {
for ($i = 2; $i < count($this->parts); $i++) {
$this->args[$j] = $this->parts[$i];
$j++;
}
$this->registry->vars = $this->args;
} else {
$this->registry->vars = 'null';
}
/*** set the file path ***/
return $this->controller;
} catch (\Exception $e) {
$this->registry->log->error('Core / Loader :' . $e->getMessage() . ' Line :' . $e->getLine() . ' ' . $e->getFile());
\Aiko\Http::InternalServerError('Error loader');
}
}
public function getControllerName()
{
try {
$restrict = '';
if ($this->registry->config->restrict == 'yes') {
if (isset($this->registry->config->ipconfig)) {
$ip = $this->getRealIpAddr();
$register = in_array($ip, $this->registry->config->ipconfig);
if ($ip != '127.0.0.1') {
if (!$register) {
$restrict = 'restrict';
}
}
} else {
$restrict = 'restrict';
}
}
$this->getName($restrict);
$this->Request_check();
return $this->controller;
} catch (\Exception $e) {
$this->registry->log->error('Core / Loader :' . $e->getMessage() . ' Line :' . $e->getLine());
\Aiko\Http::InternalServerError('Error loader');
}
}
private function getName($restrict)
{
try {
if ($restrict == 'restrict') {
$this->controller = 'restrict';
$this->controllerPath = 'restrict';
} else {
$route = (empty($_GET['rt'])) ? '' : $_GET['rt'];
if (empty($route)) {
// jika route tidak ada / pada awal page
$route = 'index';
} else {
// clean root with prefix
$route= $this->cleanRoute($route);
/*** get the parts of the route ***/
$this->parts = explode('/', $route);
// set controller name
// cek apakan part yang pertama memiliki controller kalau tidak ditemukan return 404
if (!is_dir(__SITE_PATH . '/src/modules/' . $this->parts[0])) {
$this->controller = 'error404';
$this->controllerPath = 'error404';
} else {
$i = 0;
$path = '';
$found = false;
do {
$path .= '/' . $this->parts[$i];
$dir = __SITE_PATH . '/src/modules' . $path;
if (file_exists($dir . '/controller')) {
$found = true;
break;
}
$i++;
} while ($i < count($this->parts));
if ($found) {
$this->controller = $this->parts[$i];
$this->controllerPath = $path;
} else {
$this->controller = 'error404';
$this->controllerPath = 'error404';
}
if (isset($this->parts[$i + 1])) {
// set action name
$this->action = $this->parts[$i + 1];
}
}
}
// cek apakah controller kosong, jika kosong set ke index
if (empty($this->controller)) {
$this->controller = 'index';
$this->controllerPath = 'index';
}
/*** Get action ***/
if (empty($this->action)) {
$this->action = 'index';
}
}
} catch (\Exception $e) {
$this->registry->log->error('Core / Loader :' . $e->getMessage() . ' Line :' . $e->getLine());
\Aiko\Http::InternalServerError('Error loader');
}
}
private function Request_check()
{
$this->registry->isAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) and
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest';
}
public function getControllerPath()
{
return $this->controllerPath;
}
private function getRealIpAddr()
{
if (!empty($_SERVER['HTTP_CLIENT_IP'])) { //check ip from share internet
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { //to check ip is pass from proxy
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
return $ip;
}
private function cleanRoute($route):string{
$prefixLength=strlen($this->prefix);
if ($prefixLength==0){
return $route;
}
$routePrefix=substr($route,0,$prefixLength);
if($this->prefix!==$routePrefix){
Http::InternalServerError('failed route');
}
$newRoute= substr($route,$prefixLength);
if(strlen($newRoute)==0 || $newRoute=='/'){
$newRoute='index';
}
// check apakah string pertama route / ?
if(substr($newRoute,0,1)=='/'){
$newRoute=substr($newRoute,1);
}
return $newRoute;
}
}