Compare commits

..

22 Commits

Author SHA1 Message Date
Alan Cell
84548c4f63 Ability to add custom modules 2020-11-12 05:51:31 +01:00
Alan Cell
e5eccf32a7 Merge tag 'v28.1.1.OS' into develop
v28.1.1.OS
2020-11-07 12:52:02 +01:00
Alan Cell
f1dcc6b6a0 Merge branch 'release/v28.1.1.OS' 2020-11-07 12:52:02 +01:00
Alan Cell
b6c0256b49 Update production build 2020-11-07 12:37:11 +01:00
Alan Cell
e31bf5a4b9 Fix api issues and production build 2020-11-07 12:25:12 +01:00
Alan Cell
06a3172a38 Merge branch 'release/v28.1.1.OS' 2020-11-07 11:46:23 +01:00
Alan Cell
e74ca00902 Merge tag 'v28.1.1.OS' into develop
v28.1.1.OS
2020-11-07 11:46:23 +01:00
Alan Cell
28aa16f35c Update version 2020-11-07 11:45:05 +01:00
Alan Cell
fb3b5b562e Merge branch 'release/v28.1.1.OS' 2020-11-07 11:41:24 +01:00
Alan Cell
8663a7aff1 Merge tag 'v28.1.1.OS' into develop
v28.1.1.OS
2020-11-07 11:41:24 +01:00
Alan Cell
e9baf45d7c Production build + fixing code style issues 2020-11-06 20:08:07 +01:00
Alan Cell
2abe52963f Imitate the REST api using url parameter based implementation
Reason for this implementation is some clients having trouble configuring the rest api either due to not having proper access to the webserver in a shared hosting environment or security restrictions. But still from icehrm frontend we need to consume the backend rest api.
2020-11-06 20:00:53 +01:00
Alan Cell
da55c7a2d2 Ability to delete files from S3 2020-11-06 19:41:56 +01:00
Alan Cell
5cd7963f6f Implement password change for employee profile 2020-11-06 19:38:48 +01:00
Alan Cell
d986a2b5bb Fix issue: employee not be selected when filtering employee documents 2020-11-06 19:32:52 +01:00
Alan Cell
1ee4fb4ba1 Update vagrant file 2020-11-06 19:23:05 +01:00
Alan Cell
b06780c466 Fix issue with time-sheets module nit being able to load projects 2020-11-06 18:51:46 +01:00
Alan Cell
5ec497e11d Upgrade vagrant config 2020-11-06 18:41:39 +01:00
Alan Cell
df3b6e968a Update vagrant 2020-11-06 09:46:20 +01:00
Thilina Pituwala
3ceb427479 Fix ci build 2020-11-01 10:17:04 +01:00
Thilina Pituwala
d3b4748cba Remove in lage in readme 2020-11-01 02:37:33 +01:00
Thilina Pituwala
2a9e65d8a8 Remove php5.6 from travis 2020-11-01 02:31:57 +01:00
52 changed files with 1229 additions and 31302 deletions

View File

@@ -14,7 +14,6 @@ install:
script: ant build-ci
language: php
php:
- '5.6'
- '7.0'
- '7.1'
- '7.2'

16
Vagrantfile vendored
View File

@@ -3,8 +3,6 @@ Vagrant.configure("2") do |config|
config.vm.box_version = "1.0.0"
config.vm.network "private_network", ip: "192.168.10.12"
config.vm.synced_folder ".", "/vagrant", type: "nfs"
config.vm.synced_folder "./deployment/vagrant/sites-available", "/etc/nginx/sites-enabled", type: "nfs"
config.vm.synced_folder "./deployment/vagrant/ssl", "/etc/nginx/ssl", type: "nfs"
config.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
@@ -13,9 +11,17 @@ Vagrant.configure("2") do |config|
end
config.vm.provision "shell", inline: <<-SHELL
sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config
systemctl restart sshd.service
sudo service nginx restart
sudo rm /etc/nginx/ssl/icehrm.*
sudo ln -s /vagrant/deployment/vagrant/ssl/icehrm.crt /etc/nginx/ssl/icehrm.crt
sudo ln -s /vagrant/deployment/vagrant/ssl/icehrm.key /etc/nginx/ssl/icehrm.key
sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /vagrant/deployment/vagrant/sites-available/default /etc/nginx/sites-enabled/default
sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/g' /etc/ssh/sshd_config
sudo service nginx restart
sudo chmod 755 -R /var/log
SHELL
config.vm.hostname = "icehrm.os"

View File

@@ -5,7 +5,7 @@ if(!file_exists('config.php')){
}
include ('config.php');
if(!isset($_REQUEST['g']) || !isset($_REQUEST['n'])){
header("Location:".CLIENT_BASE_URL."login.php");
header("Location:".CLIENT_BASE_URL."login.php");
exit();
}
$group = $_REQUEST['g'];
@@ -14,9 +14,16 @@ $name= $_REQUEST['n'];
$groups = array('admin','modules');
if($group == 'admin' || $group == 'modules'){
$name = str_replace("..","",$name);
$name = str_replace("..","",$name);
$name = str_replace("/","",$name);
include APP_BASE_PATH.'/'.$group.'/'.$name.'/index.php';
}else if ($group == 'extension'){
$name = str_replace("..","",$name);
$name = str_replace("/","",$name);
$moduleName = $name;
$moduleGroup = 'extensions';
$extensionIndex = APP_BASE_PATH.'/../extensions/'.$name.'/web/index.php';
include APP_BASE_PATH.'extensions/wrapper.php';
}else{
exit();
}
}

View File

@@ -1,3 +1,3 @@
<?php
include ('config.php');
include (APP_BASE_PATH.'rest.php');
include (APP_BASE_PATH.'rest.php');

1
bin/export-plural-rules Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/gettext/languages/bin/export-plural-rules

1
bin/markdown Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/cebe/markdown/bin/markdown

1
bin/pdepend Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/pdepend/pdepend/src/bin/pdepend

1
bin/phpcb Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/mayflower/php-codebrowser/bin/phpcb

1
bin/phpcbf Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/squizlabs/php_codesniffer/scripts/phpcbf

1
bin/phpcpd Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/sebastian/phpcpd/composer/bin/phpcpd

1
bin/phpcs Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/squizlabs/php_codesniffer/scripts/phpcs

1
bin/phploc Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/phploc/phploc/phploc

1
bin/phpmd Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/phpmd/phpmd/src/bin/phpmd

1
bin/phpunit Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/phpunit/phpunit/phpunit

1
bin/release Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/consolidation/self-update/scripts/release

1
bin/robo Symbolic link
View File

@@ -0,0 +1 @@
../core/lib/composer/vendor/consolidation/robo/robo

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="icehrm" default="build">
<!-- By default, we assume all tools to be on the $PATH -->
<property name="toolsdir" value="${basedir}/tools/"/>
<property name="toolsdir" value="${basedir}/bin/"/>
<property name="destination" value="${basedir}/build/app"/>
<property name="testdir" value="${basedir}/build/test"/>
<property name="origin" value="${basedir}"/>

47
core/api-rest.php Normal file
View File

@@ -0,0 +1,47 @@
<?php
define('CLIENT_PATH',dirname(__FILE__));
include ("config.base.php");
include ("include.common.php");
include("server.includes.inc.php");
if(\Classes\SettingsManager::getInstance()->getSetting('Api: REST Api Enabled') == '1') {
if (defined('SYM_CLIENT')) {
define('REST_API_PATH', '/'.SYM_CLIENT.'/');
} else if (!defined('REST_API_PATH')){
define('REST_API_PATH', '/');
}
\Utils\LogManager::getInstance()->info("Request: " . $_REQUEST);
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
$echoRoute = \Classes\Macaw::get(REST_API_PATH . 'echo', function () {
echo "Echo " . rand();
});
\Utils\LogManager::getInstance()->debug('Api registered URI: '.$echoRoute);
$moduleManagers = \Classes\BaseService::getInstance()->getModuleManagers();
foreach ($moduleManagers as $moduleManagerObj) {
$moduleManagerObj->setupRestEndPoints();
}
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
\Utils\LogManager::getInstance()->debug('Api dispatch URI: '.$uri);
\Utils\LogManager::getInstance()->debug('Api dispatch method: '.$uri);
if (!defined('SYM_CLIENT')) {
//For hosted installations, dispatch will be done in app/index
\Classes\Macaw::dispatch();
}
}else{
echo "REST Api is not enabled. Please set 'Api: REST Api Enabled' setting to true";
}

41
core/api-url-based.php Normal file
View File

@@ -0,0 +1,41 @@
<?php
define('CLIENT_PATH',dirname(__FILE__));
include ("config.base.php");
include ("include.common.php");
include("server.includes.inc.php");
if(\Classes\SettingsManager::getInstance()->getSetting('Api: REST Api Enabled') == '1') {
\Utils\LogManager::getInstance()->info("Request: " . $_REQUEST);
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
define('REST_API_PATH', '/');
$echoRoute = \Classes\Macaw::get(REST_API_PATH . 'echo', function () {
echo "Echo " . rand();
});
\Utils\LogManager::getInstance()->debug('Api registered URI: '.$echoRoute);
$moduleManagers = \Classes\BaseService::getInstance()->getModuleManagers();
foreach ($moduleManagers as $moduleManagerObj) {
$moduleManagerObj->setupRestEndPoints();
}
$method = $_SERVER['REQUEST_METHOD'];
if (strtoupper($method) === 'GET') {
\Classes\IceRoute::dispatch($_GET['url'], $method);
} else {
$method = strtoupper($_REQUEST['method']);
\Classes\IceRoute::dispatch($_REQUEST['url'], $method);
}
}else{
echo "REST Api is not enabled. Please set 'Api: REST Api Enabled' setting to true";
}

View File

@@ -13,10 +13,10 @@ if(!defined('HOME_LINK_OTHERS')){
}
//Version
define('VERSION', '28.1.0.OS');
define('CACHE_VALUE', '28.1.0.OS.2020-10311445');
define('VERSION_NUMBER', '280100');
define('VERSION_DATE', '31/10/2020');
define('VERSION', '28.1.1.OS');
define('CACHE_VALUE', '28.1.1.OS.2020-11071143');
define('VERSION_NUMBER', '280101');
define('VERSION_DATE', '07/11/2020');
if(!defined('CONTACT_EMAIL')){define('CONTACT_EMAIL','icehrm@gamonoid.com');}
if(!defined('KEY_PREFIX')){define('KEY_PREFIX','IceHrm');}
@@ -36,3 +36,7 @@ define('ALL_CLIENT_BASE_PATH', '/var/www/icehrm.app/icehrmapp/');
define('LDAP_ENABLED', true);
define('RECRUITMENT_ENABLED', false);
define('APP_WEB_URL', 'https://icehrm.com');
if (!defined('EXTENSIONS_URL')) {
define('EXTENSIONS_URL', str_replace('/web/', '/extensions/', BASE_URL));
}

View File

@@ -0,0 +1,31 @@
<?php
use Classes\ExtensionManager;
use Utils\LogManager;
if (!isset($extensionIndex)) {
exit();
}
define('MODULE_PATH',APP_BASE_PATH.'extensions/'.$moduleName);
include APP_BASE_PATH.'header.php';
$extensionManager = new ExtensionManager();
$meta = $extensionManager->getExtensionMetaData($moduleName);
if (!$meta) {
LogManager::getInstance()->error("Extension metadata.json not found for $moduleName");
exit();
}
if ($meta->headless) {
LogManager::getInstance()->error("Extension running in headless mode for $moduleName");
exit();
}
?>
<script type="text/javascript" src="<?=BASE_URL.'dist/vendorReact.js'?>?v=<?=$jsVersion?>"></script>
<script type="text/javascript" src="<?=BASE_URL.'dist/vendorAntd.js'?>?v=<?=$jsVersion?>"></script>
<script type="text/javascript" src="<?=BASE_URL.'dist/vendorAntdIcons.js'?>?v=<?=$jsVersion?>"></script>
<script type="text/javascript" src="<?=BASE_URL.'dist/vendorAntv.js'?>?v=<?=$jsVersion?>"></script>
<script type="text/javascript" src="<?=BASE_URL.'dist/vendorOther.js'?>?v=<?=$jsVersion?>"></script>
<script type="text/javascript" src="<?=EXTENSIONS_URL.$moduleName.'/dist/'.$moduleName.'.js'?>?v=<?=$jsVersion?>"></script>
<?php
include $extensionIndex;
include APP_BASE_PATH.'footer.php';
?>

View File

@@ -215,6 +215,26 @@ if (defined('SYM_CLIENT')) {
<?php }?>
<?php foreach($extensions as $menu){?>
<?php if(count($menu['menu']) == 0){continue;}?>
<li class="treeview" ref="<?="extension_".str_replace(" ", "_", $menu['name'])?>">
<a href="#">
<i class="fa <?=!isset($mainIcons[$menu['name']])?"fa-th":$mainIcons[$menu['name']];?>"></i></i> <span><?=\Classes\LanguageManager::tran($menu['name'])?></span>
<i class="fa fa-angle-left pull-right"></i>
</a>
<ul class="treeview-menu" id="<?="extension_".str_replace(" ", "_", $menu['name'])?>">
<?php foreach ($menu['menu'] as $item){?>
<li>
<a data-turbolinks="true" href="<?=CLIENT_BASE_URL?>?g=extension&n=<?=$item['name']?>&m=<?="extension_".str_replace(" ", "_", $menu['name'])?>">
<i class="fa <?=!isset($item['icon'])?"fa-angle-double-right":$item['icon']?>"></i> <?=\Classes\LanguageManager::tran($item['label'])?>
</a>
</li>
<?php }?>
</ul>
</li>
<?php }?>
<?php
if(file_exists(CLIENT_PATH.'/third_party_meta.json')){
$tpModules = json_decode(file_get_contents(CLIENT_PATH.'/third_party_meta.json'),true);

View File

@@ -277,7 +277,7 @@ $csrfToken = sha1(rand(4500, 100000) . time(). CLIENT_BASE_URL);
<div class="col-lg-6 col-md-8 col-xs-10">
<div class="bg-white-2 h-100 px-11 pt-11 pb-7">
<div class="row d-flex justify-content-center">
<img src="<?=$logoFileUrl?>"/>
<img src="<?=$logoFileUrl?>" style="max-width:100%;max-height:280px;"/>
</div>
<hr/>
<?php if ($gsuiteEnabled) {?>

View File

@@ -291,6 +291,12 @@ foreach ($ams as $am) {
}
}
$extensionManager = new \Classes\ExtensionManager();
$extensionData = $extensionManager->setupExtensions();
$extensionIcons = $extensionData[0];
$extensionTemp = $extensionData[1];
$extensionMenus = array_keys($extensionIcons);
foreach ($adminModulesTemp as $k => $v) {
ksort($adminModulesTemp[$k]);
}
@@ -299,6 +305,10 @@ foreach ($userModulesTemp as $k => $v) {
ksort($userModulesTemp[$k]);
}
foreach ($extensionTemp as $k => $v) {
ksort($extensionTemp[$k]);
}
$adminIcons = json_decode(file_get_contents(CLIENT_PATH.'/admin/meta.json'), true);
$adminMenus = array_keys($adminIcons);
@@ -332,8 +342,6 @@ foreach ($userMenus as $menu) {
}
}
$mainIcons = array_merge($adminIcons, $userIcons);
foreach ($userModulesTemp as $k => $v) {
if (!in_array($k, $added)) {
$arr = array("name"=>$k,"menu"=>$userModulesTemp[$k]);
@@ -341,6 +349,25 @@ foreach ($userModulesTemp as $k => $v) {
}
}
$extensions = array();
foreach ($extensionMenus as $menu) {
if (isset($extensionTemp[$menu])) {
$arr = array("name"=>$menu,"menu"=>$extensionTemp[$menu]);
$extensions[] = $arr;
$added[] = $menu;
}
}
foreach ($extensionTemp as $k => $v) {
if (!in_array($k, $added)) {
$arr = array("name"=>$k,"menu"=>$extensionTemp[$k]);
$extensions[] = $arr;
}
}
// Merge icons
$mainIcons = array_merge($adminIcons, $userIcons, $extensionIcons);
//Remove modules having no permissions
if (!empty($user)) {
if (!empty($user->user_roles)) {
@@ -393,4 +420,24 @@ if (!empty($user)) {
}
}
}
foreach ($extensions as $fk => $menu) {
foreach ($menu['menu'] as $key => $item) {
// If the user's once of the user roles are blacklisted for the module
$commonRoles = array_intersect($item['user_roles_blacklist'], $userRoles);
if (!empty($commonRoles)) {
unset($extensions[$fk]['menu'][$key]);
}
if (!in_array($user->user_level, $item['user_levels'])) {
if (!empty($userRoles)) {
$commonRoles = array_intersect($item['user_roles'], $userRoles);
if (empty($commonRoles)) {
unset($extensions[$fk]['menu'][$key]);
}
} else {
unset($extensions[$fk]['menu'][$key]);
}
}
}
}
}

View File

@@ -3,49 +3,8 @@ header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: DELETE, POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With');
header('Content-Type: application/json');
define('CLIENT_PATH',dirname(__FILE__));
include ("config.base.php");
include ("include.common.php");
include("server.includes.inc.php");
if(\Classes\SettingsManager::getInstance()->getSetting('Api: REST Api Enabled') == '1') {
if (defined('SYM_CLIENT')) {
define('REST_API_PATH', '/'.SYM_CLIENT.'/');
} else if (!defined('REST_API_PATH')){
define('REST_API_PATH', '/');
}
\Utils\LogManager::getInstance()->info("Request: " . $_REQUEST);
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
$echoRoute = \Classes\Macaw::get(REST_API_PATH . 'echo', function () {
echo "Echo " . rand();
});
\Utils\LogManager::getInstance()->debug('Api registered URI: '.$echoRoute);
$moduleManagers = \Classes\BaseService::getInstance()->getModuleManagers();
foreach ($moduleManagers as $moduleManagerObj) {
$moduleManagerObj->setupRestEndPoints();
}
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
\Utils\LogManager::getInstance()->debug('Api dispatch URI: '.$uri);
\Utils\LogManager::getInstance()->debug('Api dispatch method: '.$uri);
if (!defined('SYM_CLIENT')) {
//For hosted installations, dispatch will be done in app/index
\Classes\Macaw::dispatch();
}
}else{
echo "REST Api is not enabled. Please set 'Api: REST Api Enabled' setting to true";
if (isset($_REQUEST['method']) && isset($_REQUEST['url'])) {
include(APP_BASE_PATH . 'api-url-based.php');
} else {
include(APP_BASE_PATH . 'api-rest.php');
}

View File

@@ -278,4 +278,12 @@ abstract class AbstractModuleManager
{
BaseService::getInstance()->addCalculationHook($code, $name, $class, $method);
}
public function install() {
}
public function uninstall() {
}
}

View File

@@ -292,8 +292,8 @@ class BaseService
$childCompaniesIds = array();
if (\Classes\SettingsManager::getInstance()->getSetting(
'System: Child Company Structure Managers Enabled'
) == '1'
'System: Child Company Structure Managers Enabled'
) == '1'
) {
$childCompaniesResp = \Company\Common\Model\CompanyStructure::getAllChildCompanyStructures(
$cempObj->department
@@ -486,8 +486,8 @@ class BaseService
$childCompaniesIds = array();
if (SettingsManager::getInstance()->getSetting(
'System: Child Company Structure Managers Enabled'
) == '1'
'System: Child Company Structure Managers Enabled'
) == '1'
) {
$childCompaniesResp = CompanyStructure::getAllChildCompanyStructures($cempObj->department);
$childCompanies = $childCompaniesResp->getObject();
@@ -567,8 +567,8 @@ class BaseService
$childCompaniesIds = array();
if (SettingsManager::getInstance()->getSetting(
'System: Child Company Structure Managers Enabled'
) == '1'
'System: Child Company Structure Managers Enabled'
) == '1'
) {
$childCompaniesResp = CompanyStructure::getAllChildCompanyStructures($cempObj->department);
$childCompanies = $childCompaniesResp->getObject();
@@ -1783,8 +1783,8 @@ END;
) {
$departmentHeadFound = true;
} elseif (SettingsManager::getInstance()->getSetting(
'System: Child Company Structure Managers Enabled'
) == '1'
'System: Child Company Structure Managers Enabled'
) == '1'
) {
$companyStructure = new CompanyStructure();
$companyStructure->Load('id = ?', array($subordinate->department));

View File

@@ -0,0 +1,143 @@
<?php
namespace Classes;
use Utils\LogManager;
class ExtensionManager
{
const GROUP = 'extension';
protected function processExtensionInDB() {
$dbModule = new \Modules\Common\Model\Module();
$extensions = $dbModule->Find("mod_group = ?", array(self::GROUP));
$extensionsInDB = [];
foreach ($extensions as $dbm) {
$extensionsInDB[$dbm->name] = $dbm;
ModuleAccessService::getInstance()->setModule($dbm->name, self::GROUP, $dbm);
}
return $extensionsInDB;
}
public function getExtensionsPath() {
return APP_BASE_PATH.'../extensions/';
}
public function getExtensionMetaData($extensionName)
{
return json_decode(file_get_contents($this->getExtensionsPath().$extensionName.'/meta.json'));
}
public function setupExtensions() {
$menu = [];
$extensions = [];
$extensionDirs = scandir($this->getExtensionsPath());
$currentLocation = 0;
$extensionsInDB = $this->processExtensionInDB();
$needToInstall = false;
foreach ($extensionDirs as $extensionDir) {
if (is_dir($this->getExtensionsPath().$extensionDir) && $extensionDir != '.' && $extensionDir != '..') {
$meta = $this->getExtensionMetaData($extensionDir);
$arr = [];
$arr['name'] = $extensionDir;
$arr['label'] = $meta->label;
$arr['icon'] = $meta->icon;
$arr['menu'] = $meta->menu[0];
$arr['order'] = 0;
$arr['status'] = 'Enabled';
$arr['user_levels'] = $meta->user_levels;
$arr['user_roles'] = isset($meta->user_roles)?$meta->user_roles:"";
$arr['model_namespace'] = $meta->model_namespace;
$arr['manager'] = $meta->manager;
// Add menu
$menu[$meta->menu[0]] = $meta->menu[1];
//Check in admin dbmodules
if (isset($extensionsInDB[$arr['name']])) {
$dbModule = $extensionsInDB[$arr['name']];
$arr['name'] = $dbModule->name;
$arr['label'] = $dbModule->label;
$arr['icon'] = $dbModule->icon;
$arr['menu'] = $dbModule->menu;
$arr['status'] = $dbModule->status;
$arr['user_levels'] = json_decode($dbModule->user_levels);
$arr['user_roles'] = empty($dbModule->user_roles)
? [] : json_decode($dbModule->user_roles);
$arr['user_roles_blacklist'] = empty($dbModule->user_roles_blacklist)
? [] : json_decode($dbModule->user_roles_blacklist);
} else {
$dbModule = new \Modules\Common\Model\Module();
$dbModule->menu = $arr['menu'];
$dbModule->name = $arr['name'];
$dbModule->label = $arr['label'];
$dbModule->icon = $arr['icon'];
$dbModule->mod_group = self::GROUP;
$dbModule->mod_order = $arr['order'];
$dbModule->status = "Enabled";
$dbModule->version = isset($meta->version)?$meta->version:"";
$dbModule->update_path = self::GROUP.">".$extensionDir;
$dbModule->user_levels = isset($meta->user_levels)?json_encode($meta->user_levels):"";
$dbModule->user_roles = isset($meta->user_roles)?json_encode($meta->user_roles):"";
$ok = $dbModule->Save();
if (!$ok) {
LogManager::getInstance()->error('Error saving module: '.$dbModule->name);
}
$needToInstall = $ok;
}
/* @var \Classes\AbstractModuleManager */
$manager = $this->includeModuleManager($extensionDir, $arr);
if ($dbModule->status == 'Disabled') {
continue;
}
if ($needToInstall) {
$manager->install();
}
$menuName = $arr['menu'];
if (!isset($extensions[$menuName])) {
$extensions[$menuName] = array();
}
if (!$meta->headless) {
if ($arr['order'] == '0' || $arr['order'] == '') {
$extensions[$menuName]["Z".$currentLocation] = $arr;
$currentLocation++;
} else {
$extensions[$arr['menu']]["A".$arr['order']] = $arr;
}
}
$initializer = $manager->getInitializer();
if ($initializer !== null) {
$initializer->setBaseService(BaseService::getInstance());
$initializer->init();
}
}
}
return [$menu, $extensions];
}
public function includeModuleManager($name, $data)
{
include($this->getExtensionsPath().$name.'/'.$name.'.php');
$moduleManagerClass = $data['manager'];
/* @var \Classes\AbstractModuleManager $moduleManagerObj*/
$moduleManagerObj = new $moduleManagerClass();
$moduleManagerObj->setModuleObject($data);
$moduleManagerObj->setModuleType(self::GROUP);
$moduleManagerObj->setModulePath(CLIENT_PATH.'/'.self::GROUP.'/'.$name);
\Classes\BaseService::getInstance()->addModuleManager($moduleManagerObj);
return $moduleManagerObj;
}
}

View File

@@ -94,6 +94,8 @@ class FileService
$s3FileSys = new S3FileSystem($uploadFilesToS3Key, $uploadFilesToS3Secret);
$result = $s3FileSys->putObject($s3Bucket, $uploadname, $localFile, 'authenticated-read');
$file->size = filesize($localFile);
unlink("/tmp/".$file->filename);
unlink("/tmp/".$file->filename."_orig");
@@ -101,7 +103,6 @@ class FileService
$file->employee = $profileImage->employee;
$file->file_group = 'profile_image_small';
$file->size = filesize(CLIENT_BASE_PATH.'data/'.$file->filename);
$file->size_text = $this->getReadableSize($file->size);
if (!empty($result)) {
@@ -293,8 +294,7 @@ class FileService
if ($file->employee == $profileId) {
$ok = $file->Delete();
if ($ok) {
LogManager::getInstance()->info("Delete File:".CLIENT_BASE_PATH.$file->filename);
unlink(CLIENT_BASE_PATH.'data/'.$file->filename);
$this->deleteFileFromDisk($file);
} else {
return false;
}
@@ -306,8 +306,7 @@ class FileService
if ($file->employee == $profileId) {
$ok = $file->Delete();
if ($ok) {
LogManager::getInstance()->info("Delete File:".CLIENT_BASE_PATH.$file->filename);
unlink(CLIENT_BASE_PATH.'data/'.$file->filename);
$this->deleteFileFromDisk($file);
} else {
return false;
}
@@ -317,6 +316,30 @@ class FileService
return true;
}
public function deleteFileFromDisk($file)
{
$uploadFilesToS3 = SettingsManager::getInstance()->getSetting("Files: Upload Files to S3");
if ($uploadFilesToS3 == "1") {
$uploadFilesToS3Key = SettingsManager::getInstance()->getSetting(
"Files: Amazon S3 Key for File Upload"
);
$uploadFilesToS3Secret = SettingsManager::getInstance()->getSetting(
"Files: Amazone S3 Secret for File Upload"
);
$s3Bucket = SettingsManager::getInstance()->getSetting("Files: S3 Bucket");
$uploadname = CLIENT_NAME."/".$file->filename;
LogManager::getInstance()->info("Delete from S3:".$uploadname);
$s3FileSys = new S3FileSystem($uploadFilesToS3Key, $uploadFilesToS3Secret);
$s3FileSys->deleteObject($s3Bucket, $uploadname);
} else {
LogManager::getInstance()->info("Delete:".CLIENT_BASE_PATH.'data/'.$file->filename);
unlink(CLIENT_BASE_PATH.'data/'.$file->filename);
}
}
public function deleteFileByField($value, $field)
{
LogManager::getInstance()->info("Delete file by field: $field / value: $value");

View File

@@ -0,0 +1,20 @@
<?php
namespace Classes;
abstract class IceExtension extends AbstractModuleManager
{
public function initializeUserClasses()
{
// TODO: Implement initializeUserClasses() method.
}
public function initializeFieldMappings()
{
// TODO: Implement initializeFieldMappings() method.
}
public function initializeDatabaseErrorMappings()
{
// TODO: Implement initializeDatabaseErrorMappings() method.
}
}

177
core/src/Classes/IceRoute.php Executable file
View File

@@ -0,0 +1,177 @@
<?php
namespace Classes;
/**
* @method static IceRoute get(string $route, Callable $callback)
* @method static IceRoute post(string $route, Callable $callback)
* @method static IceRoute put(string $route, Callable $callback)
* @method static IceRoute delete(string $route, Callable $callback)
* @method static IceRoute options(string $route, Callable $callback)
* @method static IceRoute head(string $route, Callable $callback)
*/
class IceRoute
{
public static $halts = false;
public static $routes = array();
public static $methods = array();
public static $callbacks = array();
public static $patterns = array(
':any' => '[^/]+',
':num' => '[0-9]+',
':all' => '.*'
);
public static $error_callback;
/**
* Defines a route w/ callback and method
*/
public static function __callstatic($method, $params)
{
$uri = $params[0][0];
$callback = $params[0][1];
array_push(self::$routes, $uri);
array_push(self::$methods, strtoupper($method));
array_push(self::$callbacks, $callback);
return $uri;
}
/**
* Defines callback if route is not found
*/
public static function error($callback)
{
self::$error_callback = $callback;
}
public static function haltOnMatch($flag = true)
{
self::$halts = $flag;
}
/**
* Runs the callback for the given request
*/
public static function dispatch($uri, $method)
{
$searches = array_keys(static::$patterns);
$replaces = array_values(static::$patterns);
$found_route = false;
self::$routes = str_replace('//', '/', self::$routes);
// check if route is defined without regex
if (in_array($uri, self::$routes)) {
$route_pos = array_keys(self::$routes, $uri);
foreach ($route_pos as $route) {
//using an ANY option to match both GET and POST requests
if (self::$methods[$route] == $method || self::$methods[$route] == 'ANY') {
$found_route = true;
//if route is not an object
if (!is_object(self::$callbacks[$route])) {
//grab all parts based on a / separator
$parts = explode('/', self::$callbacks[$route]);
//collect the last index of the array
$last = end($parts);
//grab the controller name and method call
$segments = explode('@', $last);
//instanitate controller
$controller = new $segments[0]();
//call method
$controller->$segments[1]();
if (self::$halts) {
return;
}
} else {
//call closure
call_user_func(self::$callbacks[$route]);
if (self::$halts) {
return;
}
}
}
}
} else {
// check if defined with regex
$pos = 0;
foreach (self::$routes as $route) {
if (strpos($route, ':') !== false) {
$route = str_replace($searches, $replaces, $route);
}
if (preg_match('#^' . $route . '$#', $uri, $matched)) {
if (self::$methods[$pos] == $method) {
$found_route = true;
array_shift($matched); //remove $matched[0] as [1] is the first parameter.
if (!is_object(self::$callbacks[$pos])) {
//grab all parts based on a / separator
$parts = explode('/', self::$callbacks[$pos]);
//collect the last index of the array
$last = end($parts);
//grab the controller name and method call
$segments = explode('@', $last);
//instanitate controller
$controller = new $segments[0]();
//fix multi parameters
if (!method_exists($controller, $segments[1])) {
echo "controller and action not found";
} else {
call_user_func_array(array($controller, $segments[1]), $matched);
}
//call method and pass any extra parameters to the method
// $controller->$segments[1](implode(",", $matched));
if (self::$halts) {
return;
}
} else {
call_user_func_array(self::$callbacks[$pos], $matched);
if (self::$halts) {
return;
}
}
}
}
$pos++;
}
}
// run the error callback if the route was not found
if ($found_route == false) {
if (!self::$error_callback) {
self::$error_callback = function () {
header($_SERVER['SERVER_PROTOCOL']." 404 Not Found");
echo '404';
};
}
call_user_func(self::$error_callback);
}
}
}

View File

@@ -42,6 +42,8 @@ class Macaw
array_push(self::$methods, strtoupper($method));
array_push(self::$callbacks, $callback);
call_user_func('\Classes\IceRoute::'.$method, $params);
return $uri;
}

View File

@@ -8,6 +8,8 @@
namespace Classes\Migration;
use Utils\LogManager;
abstract class AbstractMigration
{
protected $file;
@@ -16,7 +18,7 @@ abstract class AbstractMigration
protected $lastError;
public function __construct($file)
public function __construct($file = null)
{
$this->file = $file;
}
@@ -50,6 +52,7 @@ abstract class AbstractMigration
$ret = $this->db()->Execute($sql);
if (!$ret) {
$this->lastError = $this->db()->ErrorMsg();
LogManager::getInstance()->error('Error in migration: '.$this->lastError);
}
return $ret;
}

View File

@@ -12,7 +12,7 @@ class ModuleAccess
* @param $name
* @param $group
*/
public function __construct($name, $group)
public function __construct($name, $group = 'extension')
{
$this->name = $name;
$this->group = $group;

View File

@@ -420,6 +420,8 @@ class RestEndPoint
if (preg_match('/Bearer\s(\S+)/', $headers, $matches)) {
$token = $matches[1];
}
} else {
$token = $_GET['token'];
}
if (strlen($token) > 32) {

View File

@@ -147,6 +147,10 @@ class EmployeesActionManager extends SubActionManager
return new IceResponse(IceResponse::ERROR, "Error occurred while changing password");
}
if (!PasswordManager::verifyPassword($req->current, $user->password)) {
return new IceResponse(IceResponse::ERROR, "Current password is incorrect");
}
$passwordStrengthResponse = PasswordManager::isQualifiedPassword($req->pwd);
if ($passwordStrengthResponse->getStatus() === IceResponse::ERROR) {
return $passwordStrengthResponse;
@@ -158,6 +162,6 @@ class EmployeesActionManager extends SubActionManager
return new IceResponse(IceResponse::ERROR, $user->ErrorMsg());
}
return new IceResponse(IceResponse::SUCCESS, $user);
return new IceResponse(IceResponse::SUCCESS, []);
}
}

1
extensions/gitkeep Executable file
View File

@@ -0,0 +1 @@
git keep

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -359,6 +359,48 @@ gulp.task('modules-js', (done) => {
.pipe(gulp.dest('./web/dist'));
});
gulp.task('extension-js', (done) => {
let extension = process.argv.filter((item) => item.substr(0, 3) === '--x');
if (extension.length === 1) {
extension = extension[0].substr(3);
}
// map them to our stream function
return browserify({
entries: [`extensions/${extension}/web/js/index.js`],
basedir: '.',
debug: true,
cache: {},
packageCache: {},
})
.external(vendorsFlat)
.transform('babelify', {
plugins: [
['@babel/plugin-proposal-class-properties', { loose: true }],
],
presets: ['@babel/preset-env', '@babel/preset-react'],
extensions: ['.js', '.jsx'],
})
.transform(require('browserify-css'))
.bundle()
.pipe(source(`${extension}.js`))
.pipe(buffer())
.pipe(ifElse(!isProduction, () => sourcemaps.init({ loadMaps: true })))
.pipe(ifElse(isProduction, () => uglifyes(
{
compress: true,
mangle: {
reserved: [],
},
},
)))
.pipe(ifElse(isProduction, () => javascriptObfuscator({
compact: true,
})))
.pipe(ifElse(!isProduction, () => sourcemaps.write('./')))
.pipe(gulp.dest(`./extensions/${extension}/dist`));
});
gulp.task('watch', () => {
gulp.watch('web/admin/src/*/*.js', gulp.series('admin-js'));

View File

@@ -1,5 +1,3 @@
<img src="web/images/logo-sq.png" align="right" />
IceHrm
===========
[![Build Status](https://travis-ci.org/gamonoid/icehrm.svg?branch=master)](https://travis-ci.org/gamonoid/icehrm)

View File

@@ -154,7 +154,7 @@ class EmployeeDocumentAdapter extends AdapterBase {
getFilters() {
return [
['employee', { label: 'Employee', type: 'select2', 'remote-source': ['Employee', 'id', 'first_name+last_name'] }],
['employee', { label: 'Employee', type: 'select2', 'remote-source': ['Employee', 'id', 'first_name+last_name', 'getActiveSubordinateEmployees'] }],
];
}

View File

@@ -55,7 +55,7 @@ class AdapterBase extends ModuleBase {
}
setupApiClient(token) {
this.apiClient = new IceApiClient(this.apiUrl, token);
this.apiClient = new IceApiClient(this.apiUrl, token, window.CLIENT_BASE_URL, true);
}
setApiUrl(apiUrl) {

26
web/api/CustomAction.js Normal file
View File

@@ -0,0 +1,26 @@
const axios = require('axios');
class CustomAction {
constructor(adapter) {
this.adapter = adapter;
}
execute(subAction, module, request, isPost) {
if (!isPost) {
return axios.get(
this.adapter.moduleRelativeURL,
{
params: {
t: this.adapter.table, a: 'ca', sa: subAction, mod: module, req: request,
},
},
);
}
return axios.post(this.moduleRelativeURL, {
t: this.adapter.table, a: 'ca', sa: subAction, mod: module, req: request,
});
}
}
export default CustomAction;

View File

@@ -1,12 +1,19 @@
const axios = require('axios');
class IceApiClient {
constructor(baseUrl, token) {
constructor(baseUrl, token, clientBaseUrl, legacyApiWrapper = true) {
this.baseUrl = baseUrl;
this.token = token;
this.clientBaseUrl = clientBaseUrl;
this.legacyApiWrapper = legacyApiWrapper;
}
get(endpoint) {
if (this.legacyApiWrapper) {
const url = `${this.clientBaseUrl}api/index.php?token=${this.token}&method=get&url=/${endpoint}`;
return axios.get(url);
}
return axios.get(this.baseUrl + endpoint, {
headers: {
Authorization: `Bearer ${this.token}`,

View File

@@ -61,10 +61,10 @@ class ModuleBase {
}
/**
* Some browsers do not support sending JSON in get parameters. Set this to true to avoid sending JSON
* @method setNoJSONRequests
* @param val {Boolean}
*/
* Some browsers do not support sending JSON in get parameters. Set this to true to avoid sending JSON
* @method setNoJSONRequests
* @param val {Boolean}
*/
setNoJSONRequests(val) {
this.noJSONRequests = val;
}
@@ -79,12 +79,12 @@ class ModuleBase {
}
/**
* Check if the current user has a permission
* @method checkPermission
* @param permission {String}
* @example
* this.checkPermission("Upload/Delete Profile Image")
*/
* Check if the current user has a permission
* @method checkPermission
* @param permission {String}
* @example
* this.checkPermission("Upload/Delete Profile Image")
*/
checkPermission(permission) {
if (this.permissions[permission] === undefined || this.permissions[permission] == null || this.permissions[permission] === 'Yes') {
return 'Yes';
@@ -168,15 +168,15 @@ class ModuleBase {
}
/**
* If this method returned false the action buttons in data table for modules will not be displayed.
* Override this method in module lib.js to hide action buttons
* @method showActionButtons
* @param permission {String}
* @example
* EmployeeLeaveEntitlementAdapter.method('showActionButtons() {
* return false;
* }
*/
* If this method returned false the action buttons in data table for modules will not be displayed.
* Override this method in module lib.js to hide action buttons
* @method showActionButtons
* @param permission {String}
* @example
* EmployeeLeaveEntitlementAdapter.method('showActionButtons() {
* return false;
* }
*/
showActionButtons() {
return true;
}
@@ -201,30 +201,30 @@ class ModuleBase {
}
/**
* Get the current profile
* @method getCurrentProfile
* @returns Profile of the current user if the profile is not switched if not switched profile
*/
* Get the current profile
* @method getCurrentProfile
* @returns Profile of the current user if the profile is not switched if not switched profile
*/
getCurrentProfile() {
return this.currentProfile;
}
/**
* Retrive data required to create select boxes for add new /edit forms for a given module. This is called when loading the module
* @method initFieldMasterData
* @param callback {Function} call this once loading completed
* @param callback {Function} call this once all field loading completed. This indicate that the form can be displayed saftly
* @example
* ReportAdapter.method('renderForm(object) {
* var that = this;
* this.processFormFieldsWithObject(object);
* var cb = function(){
* that.super.renderForm(object);
* };
* this.initFieldMasterData(cb);
* }
*/
* Retrive data required to create select boxes for add new /edit forms for a given module. This is called when loading the module
* @method initFieldMasterData
* @param callback {Function} call this once loading completed
* @param callback {Function} call this once all field loading completed. This indicate that the form can be displayed saftly
* @example
* ReportAdapter.method('renderForm(object) {
* var that = this;
* this.processFormFieldsWithObject(object);
* var cb = function(){
* that.super.renderForm(object);
* };
* this.initFieldMasterData(cb);
* }
*/
initFieldMasterData(callback, loadAllCallback, loadAllCallbackData) {
this.fieldMasterData = {};
this.fieldMasterDataKeys = {};
@@ -236,10 +236,11 @@ class ModuleBase {
for (let i = 0; i < remoteSourceFields.length; i++) {
const fieldRemote = remoteSourceFields[i];
if (fieldRemote[1]['remote-source'] !== undefined && fieldRemote[1]['remote-source'] != null) {
let key = `${fieldRemote[1]['remote-source'][0]}_${fieldRemote[1]['remote-source'][1]}_${fieldRemote[1]['remote-source'][2]}`;
if (fieldRemote[1]['remote-source'].length === 4) {
key = `${key}_${fieldRemote[1]['remote-source'][3]}`;
}
// let key = `${fieldRemote[1]['remote-source'][0]}_${fieldRemote[1]['remote-source'][1]}_${fieldRemote[1]['remote-source'][2]}`;
// if (fieldRemote[1]['remote-source'].length === 4) {
// key = `${key}_${fieldRemote[1]['remote-source'][3]}`;
// }
const key = this.getRemoteSourceKey(fieldRemote);
this.fieldMasterDataKeys[key] = false;
const callBackData = {};
@@ -320,26 +321,26 @@ class ModuleBase {
}
/**
* Pass true to this method after creating module JS object to open new/edit entry form for the module on a popup.
* @method setShowFormOnPopup
* @param val {Boolean}
* @example
* modJs.subModJsList['tabCandidateApplication'] = new CandidateApplicationAdapter('Application','CandidateApplication',{"candidate":data.id}
* modJs.subModJsList['tabCandidateApplication'].setShowFormOnPopup(true);
*/
* Pass true to this method after creating module JS object to open new/edit entry form for the module on a popup.
* @method setShowFormOnPopup
* @param val {Boolean}
* @example
* modJs.subModJsList['tabCandidateApplication'] = new CandidateApplicationAdapter('Application','CandidateApplication',{"candidate":data.id}
* modJs.subModJsList['tabCandidateApplication'].setShowFormOnPopup(true);
*/
setShowFormOnPopup(val) {
this.showFormOnPopup = val;
}
/**
* Set this to true to if you need the datatable to load data page by page instead of loading all data at once.
* @method setRemoteTable
* @param val {Boolean}
* @example
* modJs.subModJsList['tabCandidateApplication'] = new CandidateApplicationAdapter('Application','CandidateApplication',{"candidate":data.id}
* modJs.subModJsList['tabCandidateApplication'].setRemoteTable(true);
*/
* Set this to true to if you need the datatable to load data page by page instead of loading all data at once.
* @method setRemoteTable
* @param val {Boolean}
* @example
* modJs.subModJsList['tabCandidateApplication'] = new CandidateApplicationAdapter('Application','CandidateApplication',{"candidate":data.id}
* modJs.subModJsList['tabCandidateApplication'].setRemoteTable(true);
*/
setRemoteTable(val) {
this.createRemoteTable = val;
@@ -372,14 +373,14 @@ class ModuleBase {
}
if (this.fieldMasterDataCallback !== null
&& this.fieldMasterDataCallback !== undefined
&& this.isAllLoaded(this.fieldMasterDataKeys)
&& (this.fieldMasterDataCallbackData !== null && this.fieldMasterDataCallbackData !== undefined)
&& this.fieldMasterDataCallback !== undefined
&& this.isAllLoaded(this.fieldMasterDataKeys)
&& (this.fieldMasterDataCallbackData !== null && this.fieldMasterDataCallbackData !== undefined)
) {
this.fieldMasterDataCallback(this.fieldMasterDataCallbackData);
} else if (this.fieldMasterDataCallback !== null
&& this.fieldMasterDataCallback !== undefined
&& this.isAllLoaded(this.fieldMasterDataKeys)
&& this.fieldMasterDataCallback !== undefined
&& this.isAllLoaded(this.fieldMasterDataKeys)
) {
this.fieldMasterDataCallback();
}
@@ -597,10 +598,10 @@ class ModuleBase {
}
/**
* Create the data table on provided element id
* @method createTable
* @param val {Boolean}
*/
* Create the data table on provided element id
* @method createTable
* @param val {Boolean}
*/
createTable(elementId) {
const that = this;
@@ -678,10 +679,10 @@ class ModuleBase {
}
/**
* Create a data table on provided element id which loads data page by page
* @method createTableServer
* @param val {Boolean}
*/
* Create a data table on provided element id which loads data page by page
* @method createTableServer
* @param val {Boolean}
*/
createTableServer(elementId) {
const that = this;
@@ -748,10 +749,10 @@ class ModuleBase {
}
/**
* This should be overridden in module lib.js classes to return module headers which are used to create the data table.
* @method getHeaders
* @example
SettingAdapter.method('getHeaders() {
* This should be overridden in module lib.js classes to return module headers which are used to create the data table.
* @method getHeaders
* @example
SettingAdapter.method('getHeaders() {
return [
{ "sTitle": "ID" ,"bVisible":false},
{ "sTitle": "Name" },
@@ -759,17 +760,17 @@ class ModuleBase {
{ "sTitle": "Details"}
];
}
*/
*/
getHeaders() {
}
/**
* This should be overridden in module lib.js classes to return module field values which are used to create the data table.
* @method getDataMapping
* @example
SettingAdapter.method('getDataMapping() {
* This should be overridden in module lib.js classes to return module field values which are used to create the data table.
* @method getDataMapping
* @example
SettingAdapter.method('getDataMapping() {
return [
"id",
"name",
@@ -777,23 +778,23 @@ class ModuleBase {
"description"
];
}
*/
*/
getDataMapping() {
}
/**
* This should be overridden in module lib.js classes to return module from fields which are used to create the add/edit form and also used for initializing select box values in form.
* @method getFormFields
* @example
SettingAdapter.method('getFormFields() {
* This should be overridden in module lib.js classes to return module from fields which are used to create the add/edit form and also used for initializing select box values in form.
* @method getFormFields
* @example
SettingAdapter.method('getFormFields() {
return [
[ "id", {"label":"ID","type":"hidden"}],
[ "value", {"label":"Value","type":"text","validation":"none"}]
];
}
*/
*/
getFormFields() {
}
@@ -807,26 +808,26 @@ class ModuleBase {
}
/**
* This can be overridden in module lib.js classes inorder to show a filter form
* @method getFilters
* @example
EmployeeAdapter.method('getFilters() {
* This can be overridden in module lib.js classes inorder to show a filter form
* @method getFilters
* @example
EmployeeAdapter.method('getFilters() {
return [
[ "job_title", {"label":"Job Title","type":"select2","allow-null":true,"null-label":"All Job Titles","remote-source":["JobTitle","id","name"]}],
[ "department", {"label":"Department","type":"select2","allow-null":true,"null-label":"All Departments","remote-source":["CompanyStructure","id","title"]}],
[ "supervisor", {"label":"Supervisor","type":"select2","allow-null":true,"null-label":"Anyone","remote-source":["Employee","id","first_name+last_name"]}]
];
}
*/
*/
getFilters() {
return null;
}
/**
* Show the edit form for an item
* @method edit
* @param id {int} id of the item to edit
*/
* Show the edit form for an item
* @method edit
* @param id {int} id of the item to edit
*/
edit(id) {
this.currentId = id;
this.getElement(id, []);
@@ -890,10 +891,10 @@ class ModuleBase {
}
/**
* Delete an item
* @method deleteRow
* @param id {int} id of the item to edit
*/
* Delete an item
* @method deleteRow
* @param id {int} id of the item to edit
*/
deleteRow(id) {
this.deleteParams.id = id;
@@ -902,17 +903,17 @@ class ModuleBase {
}
/**
* Show a popup with message
* @method showMessage
* @param title {String} title of the message box
* @param message {String} message
* @param closeCallback {Function} this will be called once the dialog is closed (optional)
* @param closeCallback {Function} data to pass to close callback (optional)
* @param closeCallbackData
* @param isPlain {Boolean} if true buttons are not shown (optional / default = true)
* @example
* this.showMessage("Error Occured while Applying Leave", callBackData);
*/
* Show a popup with message
* @method showMessage
* @param title {String} title of the message box
* @param message {String} message
* @param closeCallback {Function} this will be called once the dialog is closed (optional)
* @param closeCallback {Function} data to pass to close callback (optional)
* @param closeCallbackData
* @param isPlain {Boolean} if true buttons are not shown (optional / default = true)
* @example
* this.showMessage("Error Occured while Applying Leave", callBackData);
*/
showMessage(title, message, closeCallback = null, closeCallbackData = null, isPlain = false) {
const that = this;
let modelId = '';
@@ -1005,11 +1006,11 @@ class ModuleBase {
/**
* Create or edit an element
* @method save
* @param getFunctionCallBackData {Array} once a success is returned call get() function for this module with these parameters
* @param successCallback {Function} this will get called after success response
*/
* Create or edit an element
* @method save
* @param getFunctionCallBackData {Array} once a success is returned call get() function for this module with these parameters
* @param successCallback {Function} this will get called after success response
*/
save(callGetFunction, successCallback) {
const validator = new FormValidation(`${this.getTableName()}_submit`, true, { ShowPopup: false, LabelErrorClass: 'error' });
@@ -1040,7 +1041,7 @@ class ModuleBase {
const fields = this.getFormFields();
fields.forEach((field) => {
if ((field[1].type === 'date' || field[1].type === 'datetime')
&& (params[field[0]] === '' || params[field[0]] === '0000-00-00' || params[field[0]] === '0000-00-00 00:00:00')) {
&& (params[field[0]] === '' || params[field[0]] === '0000-00-00' || params[field[0]] === '0000-00-00 00:00:00')) {
if (field[1].validation === 'none') {
params[field[0]] = 'NULL';
} else {
@@ -1084,23 +1085,23 @@ class ModuleBase {
}
/**
* Override this method to inject attitional parameters or modify existing parameters retrived from
* add/edit form before sending to the server
* @method forceInjectValuesBeforeSave
* @param params {Array} keys and values in form
* @returns {Array} modified parameters
*/
* Override this method to inject attitional parameters or modify existing parameters retrived from
* add/edit form before sending to the server
* @method forceInjectValuesBeforeSave
* @param params {Array} keys and values in form
* @returns {Array} modified parameters
*/
forceInjectValuesBeforeSave(params) {
return params;
}
/**
* Override this method to do custom validations at client side
* @method doCustomValidation
* @param params {Array} keys and values in form
* @returns {Null or String} return null if validation success, returns error message if unsuccessful
* @example
EmployeeLeaveAdapter.method('doCustomValidation(params) {
* Override this method to do custom validations at client side
* @method doCustomValidation
* @param params {Array} keys and values in form
* @returns {Null or String} return null if validation success, returns error message if unsuccessful
* @example
EmployeeLeaveAdapter.method('doCustomValidation(params) {
try{
if(params['date_start'] != params['date_end']){
var ds = new Date(params['date_start']);
@@ -1114,7 +1115,7 @@ class ModuleBase {
}
return null;
}
*/
*/
// eslint-disable-next-line no-unused-vars
doCustomValidation(params) {
return null;
@@ -1177,7 +1178,12 @@ class ModuleBase {
value = 'Not Selected';
}
} else {
value = this.fieldMasterData[`${rmf[0]}_${rmf[1]}_${rmf[2]}`][filters[prop]];
let key = `${rmf[0]}_${rmf[1]}_${rmf[2]}`;
if (rmf.length > 3) {
key = `${key}_${rmf[3]}`;
}
//value = this.fieldMasterData[`${rmf[0]}_${rmf[1]}_${rmf[2]}`][filters[prop]];
value = this.fieldMasterData[key][filters[prop]];
valueOrig = value;
}
} else {
@@ -1231,20 +1237,20 @@ class ModuleBase {
}
/**
* Override this method to do custom validations at client side for values selected in filters
* @method doCustomFilterValidation
* @param params {Array} keys and values in form
* @returns {Null or String} return null if validation success, returns error message if unsuccessful
*/
* Override this method to do custom validations at client side for values selected in filters
* @method doCustomFilterValidation
* @param params {Array} keys and values in form
* @returns {Null or String} return null if validation success, returns error message if unsuccessful
*/
doCustomFilterValidation(params) {
return true;
}
/**
* Reset selected filters
* @method resetFilters
*/
* Reset selected filters
* @method resetFilters
*/
resetFilters() {
this.filter = this.origFilter;
@@ -1332,7 +1338,8 @@ class ModuleBase {
try {
modJs.filterQuery();
} catch (err) {
// Do Nothing
console.log(err);
console.log(err.message);
}
return false;
});
@@ -1344,20 +1351,20 @@ class ModuleBase {
/**
* Override this method in your module class to make changes to data fo the form before showing the form
* @method preRenderForm
* @param object {Array} keys value list for populating form
*/
* Override this method in your module class to make changes to data fo the form before showing the form
* @method preRenderForm
* @param object {Array} keys value list for populating form
*/
preRenderForm(object) {
}
/**
* Create the form
* @method renderForm
* @param object {Array} keys value list for populating form
*/
* Create the form
* @method renderForm
* @param object {Array} keys value list for populating form
*/
renderForm(object) {
const signatureIds = [];
@@ -1581,28 +1588,28 @@ class ModuleBase {
}
/**
* Override this method in your module class to make changes to data fo the form after showing it
* @method postRenderForm
* @param object {Array} keys value list for populating form
* @param $tempDomObj {DOM} a DOM element for the form
* @example
* UserAdapter.method('postRenderForm(object, $tempDomObj) {
* Override this method in your module class to make changes to data fo the form after showing it
* @method postRenderForm
* @param object {Array} keys value list for populating form
* @param $tempDomObj {DOM} a DOM element for the form
* @example
* UserAdapter.method('postRenderForm(object, $tempDomObj) {
if(object == null || object == undefined){
$tempDomObj.find("#changePasswordBtn").remove();
}
}
*/
*/
postRenderForm(object, $tempDomObj) {
}
/**
* Convert data group field to HTML
* @method dataGroupToHtml
* @param val {String} value in the field
* @param field {Array} field meta data
*/
* Convert data group field to HTML
* @method dataGroupToHtml
* @param val {String} value in the field
* @param field {Array} field meta data
*/
dataGroupToHtml(val, field) {
const data = JSON.parse(val);
@@ -1657,10 +1664,10 @@ class ModuleBase {
}
/**
* Reset the DataGroup for a given field
* @method resetDataGroup
* @param field {Array} field meta data
*/
* Reset the DataGroup for a given field
* @method resetDataGroup
* @param field {Array} field meta data
*/
resetDataGroup(field) {
$(`#${field[0]}`).val('');
$(`#${field[0]}_div`).html('');
@@ -2054,12 +2061,12 @@ class ModuleBase {
/**
* Fill a form with required values after showing it
* @method fillForm
* @param object {Array} form data
* @param formId {String} id of the form
* @param formId {Array} field meta data
*/
* Fill a form with required values after showing it
* @method fillForm
* @param object {Array} form data
* @param formId {String} id of the form
* @param formId {Array} field meta data
*/
fillForm(object, formId, fields) {
let placeHolderVal;
@@ -2094,7 +2101,8 @@ class ModuleBase {
$(`${formId} #${fields[i][0]}`).html(object[fields[i][0]]);
} else if (fields[i][1].type === 'placeholder') {
if (fields[i][1]['remote-source'] !== undefined && fields[i][1]['remote-source'] != null) {
const key = `${fields[i][1]['remote-source'][0]}_${fields[i][1]['remote-source'][1]}_${fields[i][1]['remote-source'][2]}`;
//const key = `${fields[i][1]['remote-source'][0]}_${fields[i][1]['remote-source'][1]}_${fields[i][1]['remote-source'][2]}`;
const key = this.getRemoteSourceKey(fields[i]);
placeHolderVal = this.fieldMasterData[key][object[fields[i][0]]];
} else {
placeHolderVal = object[fields[i][0]];
@@ -2172,7 +2180,7 @@ class ModuleBase {
}
} else if (fields[i][1].type === 'signature') {
if (object[fields[i][0]] !== '' || object[fields[i][0]] !== undefined
|| object[fields[i][0]] != null) {
|| object[fields[i][0]] != null) {
$(`${formId} #${fields[i][0]}`).data('signaturePad').fromDataURL(object[fields[i][0]]);
}
} else if (fields[i][1].type === 'simplemde') {
@@ -2189,9 +2197,9 @@ class ModuleBase {
}
/**
* Cancel edit or add new on modules
* @method cancel
*/
* Cancel edit or add new on modules
* @method cancel
*/
cancel() {
$(`#${this.getTableName()}Form`).hide();
@@ -2218,10 +2226,11 @@ class ModuleBase {
if (field[1].source !== undefined && field[1].source != null) {
t = t.replace('_options_', this.renderFormSelectOptions(field[1].source, field));
} else if (field[1]['remote-source'] !== undefined && field[1]['remote-source'] != null) {
let key = `${field[1]['remote-source'][0]}_${field[1]['remote-source'][1]}_${field[1]['remote-source'][2]}`;
if (field[1]['remote-source'].length === 4) {
key = `${key}_${field[1]['remote-source'][3]}`;
}
// let key = `${field[1]['remote-source'][0]}_${field[1]['remote-source'][1]}_${field[1]['remote-source'][2]}`;
// if (field[1]['remote-source'].length === 4) {
// key = `${key}_${field[1]['remote-source'][3]}`;
// }
const key = this.getRemoteSourceKey(field);
t = t.replace('_options_', this.renderFormSelectOptionsRemote(this.fieldMasterData[key], field));
}
} else if (field[1].type === 'colorpick') {
@@ -2413,49 +2422,49 @@ class ModuleBase {
}
/**
* Override this method to change add new button label
* @method getAddNewLabel
*/
* Override this method to change add new button label
* @method getAddNewLabel
*/
getAddNewLabel() {
return 'Add New';
}
/**
* Used to set whether to show the add new button for a module
* @method setShowAddNew
* @param showAddNew {Boolean} value
*/
* Used to set whether to show the add new button for a module
* @method setShowAddNew
* @param showAddNew {Boolean} value
*/
setShowAddNew(showAddNew) {
this.showAddNew = showAddNew;
}
/**
* Used to set whether to show delete button for each entry in module
* @method setShowDelete
* @param val {Boolean} value
*/
* Used to set whether to show delete button for each entry in module
* @method setShowDelete
* @param val {Boolean} value
*/
setShowDelete(val) {
this.showDelete = val;
}
/**
* Used to set whether to show edit button for each entry in module
* @method setShowEdit
* @param val {Boolean} value
*/
* Used to set whether to show edit button for each entry in module
* @method setShowEdit
* @param val {Boolean} value
*/
setShowEdit(val) {
this.showEdit = val;
}
/**
* Used to set whether to show save button in form
* @method setShowSave
* @param val {Boolean} value
*/
* Used to set whether to show save button in form
* @method setShowSave
* @param val {Boolean} value
*/
setShowSave(val) {
@@ -2464,20 +2473,20 @@ class ModuleBase {
/**
* Used to set whether to show cancel button in form
* @method setShowCancel
* @param val {Boolean} value
*/
* Used to set whether to show cancel button in form
* @method setShowCancel
* @param val {Boolean} value
*/
setShowCancel(val) {
this.showCancel = val;
}
/**
* Datatable option array will be extended with associative array provided here
* @method getCustomTableParams
* @param val {Boolean} value
*/
* Datatable option array will be extended with associative array provided here
* @method getCustomTableParams
* @param val {Boolean} value
*/
getCustomTableParams() {
@@ -2490,12 +2499,12 @@ class ModuleBase {
/**
* This return html for action buttons in each row. Override this method if you need to make changes to action buttons.
* @method getActionButtonsHtml
* @param id {int} id of the row
* @param data {Array} data for the row
* @returns {String} html for action buttons
*/
* This return html for action buttons in each row. Override this method if you need to make changes to action buttons.
* @method getActionButtonsHtml
* @param id {int} id of the row
* @param data {Array} data for the row
* @returns {String} html for action buttons
*/
getActionButtonsHtml(id, data) {
const editButton = '<img class="tableActionButton" src="_BASE_images/edit.png" style="cursor:pointer;" rel="tooltip" title="Edit" onclick="modJs.edit(_id_);return false;"></img>';
@@ -2528,11 +2537,11 @@ class ModuleBase {
/**
* Generates a random string
* @method generateRandom
* @param length {int} required length of the string
* @returns {String} random string
*/
* Generates a random string
* @method generateRandom
* @param length {int} required length of the string
* @returns {String} random string
*/
generateRandom(length) {
const d = new Date();
@@ -2593,10 +2602,10 @@ class ModuleBase {
}
/**
* Override this method in a module to provide the help link for the module. Help link of the module on frontend will get updated with this.
* @method getHelpLink
* @returns {String} help link
*/
* Override this method in a module to provide the help link for the module. Help link of the module on frontend will get updated with this.
* @method getHelpLink
* @returns {String} help link
*/
getHelpLink() {
return null;

View File

@@ -0,0 +1,232 @@
import React, {Component} from 'react';
import {
Form,
Modal,
Input,
Button,
message,
} from 'antd';
import CustomAction from '../api/CustomAction';
class UpdatePasswordModal extends React.Component {
state = {
loading: false,
passwordHasError: false,
passwordState: { hasFeedback: false, validateStatus:'', help:'Password must include at least one number, one lowercase letter, one uppercase letter and a symbol' },
confirmationHasError: false,
confirmationState: { hasFeedback: false, validateStatus:'', help:'' },
};
constructor(props) {
super(props);
this.formRef = React.createRef();
this.customAction = new CustomAction(this.props.adapter);
}
componentDidMount() {
message.config({
top: 40,
});
}
clearConfirmFeedback = () => {
this.setState({confirmationHasError: false});
this.setState({
confirmationState: {
hasFeedback : false,
validateStatus:'',
help:'',
}
});
}
updatePasswordState(value) {
const passwordValidationResult = this.validatePassword(value);
if (passwordValidationResult !== null) {
this.setState({passwordHasError: true});
this.setState({
passwordState: {
hasFeedback : true,
validateStatus:'error',
help:passwordValidationResult,
}
});
return false;
} else {
this.setState({passwordHasError: false});
this.setState({
passwordState: {
hasFeedback : true,
validateStatus:'success',
help:'',
}
});
}
return true;
}
updateConfirmPasswordState(values) {
if (values.confirm !== values.new) {
this.setState({confirmationHasError: true});
this.setState({
confirmationState: {
hasFeedback : true,
validateStatus:'error',
help:'Passwords don\'t match',
}
});
return false;
} else {
this.setState({confirmationHasError: false});
this.setState({
confirmationState: {
hasFeedback : false,
validateStatus:'',
help:'',
}
});
}
return true;
}
handleOk = () => {
const from = this.formRef.current;
from
.validateFields()
.then((values) => {
if (this.updatePasswordState(values.new) && this.updateConfirmPasswordState(values)) {
this.updatePassword(values.current, values.new)
.then((response) => {
const data = response.data;
console.log(data);
if (data.status === 'SUCCESS') {
this.handleCancel();
message.success(this.props.adapter.gt('Password updated'));
} else {
message.error(
`${this.props.adapter.gt('Error updating password')}: ${this.props.adapter.gt(data.data)}`
);
}
}).catch((error) => {
message.error(
`${this.props.adapter.gt('Error updating password')}`
);
console.log(error.message);
});
}
})
.catch((info) => {
this.setState({ loading: false });
});
}
handleCancel = () => {
if (this.formRef.current) {
this.formRef.current.resetFields();
}
this.props.closeModal();
}
updatePassword = (oldPassword, newPassword) => {
const req = { current: oldPassword ? oldPassword : '', pwd: newPassword };
const reqJson = JSON.stringify(req);
const callBackData = [];
callBackData.callBackData = [];
callBackData.callBackSuccess = 'changePasswordSuccessCallBack';
callBackData.callBackFail = 'changePasswordFailCallBack';
return this.customAction.execute('changePassword', 'modules=employees', reqJson);
}
validatePassword = (password) => {
if (password.length < 8) {
return this.props.adapter.gt('Password too short');
}
if (password.length > 20) {
return this.props.adapter.gt('Password too long');
}
const numberTester = /.*[0-9]+.*$/;
if (!password.match(numberTester)) {
return this.props.adapter.gt('Password must include at least one number');
}
const lowerTester = /.*[a-z]+.*$/;
if (!password.match(lowerTester)) {
return this.props.adapter.gt('Password must include at least one lowercase letter');
}
const upperTester = /.*[A-Z]+.*$/;
if (!password.match(upperTester)) {
return this.props.adapter.gt('Password must include at least one uppercase letter');
}
const symbolTester = /.*[\W]+.*$/;
if (!password.match(symbolTester)) {
return this.props.adapter.gt('Password must include at least one symbol');
}
return null;
}
render() {
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};
return (
<Modal
visible={this.props.visible}
title="Update Password"
onOk={this.handleOk}
onCancel={this.handleCancel}
footer={[
<Button key="back" onClick={this.handleCancel}>
{this.props.adapter.gt('Cancel')}
</Button>,
<Button key="submit" type="primary" loading={this.state.loading} onClick={this.handleOk}>
{this.props.adapter.gt('Update')}
</Button>,
]}
>
<Form {...layout} ref={this.formRef}>
<Form.Item label="Current Password" key="current" name="current" >
<Input.Password placeholder="current password"/>
</Form.Item>
{ this.state.passwordHasError &&
<Form.Item label="New Password" key="new" name="new" {...this.state.passwordState}>
<Input.Password placeholder="new password" onChange={(event) => this.updatePasswordState(event.target.value)}/>
</Form.Item>
}
{ !this.state.passwordHasError &&
<Form.Item label="New Password" key="new" name="new" {...this.state.passwordState}>
<Input.Password placeholder="new password" onChange={(event) => this.updatePasswordState(event.target.value)}/>
</Form.Item>
}
{ this.state.confirmationHasError &&
<Form.Item label="Confirm Password" key="confirm" name="confirm" {...this.state.confirmationState}>
<Input.Password placeholder="confirm password" onChange={(event) => this.clearConfirmFeedback()}/>
</Form.Item>
}
{ !this.state.confirmationHasError &&
<Form.Item label="Confirm Password" key="confirm" name="confirm" >
<Input.Password placeholder="confirm password" onChange={(event) => this.clearConfirmFeedback()}/>
</Form.Item>
}
</Form>
</Modal>
)
}
}
export default UpdatePasswordModal;

View File

@@ -943,3 +943,7 @@ table.dataTable{
.table-row-dark {
background-color: #fbfbfb;
}
.mod-tab {
margin-bottom:0px;margin-left:5px;border-bottom: none;
}

30987
web/dist/admin-bundle.js vendored

File diff suppressed because one or more lines are too long

4
web/dist/common.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,37 +1,32 @@
import React, {Component} from 'react';
import React from 'react';
import {
Col,
Card,
Badge,
Avatar,
Input,
Row,
Descriptions,
Typography,
Table,
Space,
Button,
Tag,
message,
Tabs,
Spin,
Skeleton
} from 'antd';
import {
FilterOutlined,
EditOutlined,
PhoneTwoTone,
MailTwoTone,
SyncOutlined,
LockOutlined,
} from '@ant-design/icons';
import TagList from "../../../../components/TagList";
const { Search } = Input;
import UpdatePasswordModal from "../../../../components/UpdatePasswordModal";
const { Title, Text } = Typography;
const { TabPane } = Tabs;
class EmployeeProfile extends React.Component {
state = {
loading: true,
showPasswordResetModal: false,
};
constructor(props) {
@@ -42,6 +37,10 @@ class EmployeeProfile extends React.Component {
this.setState({ loading: value });
}
setShowPasswordUpdate(value) {
this.setState({ showPasswordResetModal: value });
}
updateProfileImage() {
showUploadDialog(
`profile_image_${this.props.element.id}_${(new Date()).getTime()}`,
@@ -71,6 +70,33 @@ class EmployeeProfile extends React.Component {
</>);
}
getEditButtonJsxWithPassword() {
return (<>
{this.state.loading &&
<Tag icon={<SyncOutlined spin/>} color="processing">
{this.props.adapter.gt('Edit')}
</Tag>
}
{!this.state.loading &&
<Tag icon={<EditOutlined/>} color="processing"
onClick={() => modJs.edit(this.props.element.id)}>
{this.props.adapter.gt('Edit')}
</Tag>
}
<Tag icon={<LockOutlined/>} color="volcano" onClick={() => this.setShowPasswordUpdate(true)}>
{this.props.adapter.gt('Update Password')}
</Tag>
</>);
}
getUpdatePasswordButtonJsx() {
return (<>
<Tag icon={<SyncOutlined spin/>} color="processing">
{this.props.adapter.gt('Update Password')}
</Tag>
</>);
}
getTabViewEmployeeFilterButtonJsx(tab) {
return (
<Tag icon={<EditOutlined/>} color="processing"
@@ -86,10 +112,15 @@ class EmployeeProfile extends React.Component {
}
return (
<>
<UpdatePasswordModal
visible={this.state.showPasswordResetModal}
closeModal={() => {this.setState({ showPasswordResetModal: false })}}
adapter={this.props.adapter}
/>
<Row direction="vertical" style={{width: '100%', padding: '10px'}} gutter={24}>
<Col span={24}>
<Card title={this.props.adapter.gt('Employee Profile')}
extra={this.getEditButtonJsx()}
extra={this.getEditButtonJsxWithPassword()}
style={{ width: '100%' }}
>
<Space size={'large'}>

View File

@@ -613,8 +613,15 @@ class EmployeeTimeEntryAdapter extends AdapterBase {
return dateArray;
}
renderForm(object) {
this.initMasterDataReader();
this.masterDataReader.updateAllMasterData()
.then(() => {
this._renderForm(object);
});
}
_renderForm(object) {
let formHtml = this.getCustomTemplate('time_entry_form.html');
formHtml = formHtml.replace(/modJs/g, "modJsList['tabEmployeeTimeEntry']");
let html = '';