Compare commits

...

24 Commits

Author SHA1 Message Date
Alan Cell
5df15d39c1 Example usage of IceHrm model classes 2020-11-13 04:47:50 +01:00
Alan Cell
143bcac1f9 Custom tasks extension, basic implementation 2020-11-12 08:06:36 +01:00
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
58 changed files with 1378 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'

12
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
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
systemctl restart sshd.service
sudo service nginx restart
sudo chmod 755 -R /var/log
SHELL
config.vm.hostname = "icehrm.os"

View File

@@ -17,6 +17,13 @@ if($group == 'admin' || $group == 'modules'){
$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();
}

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

@@ -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

View File

@@ -0,0 +1,13 @@
{
"label": "My Tasks",
"menu": ["Tasks", "fa-list"],
"icon": "fa-tasks",
"user_levels": [
"Admin",
"Manager",
"User"
],
"model_namespace": "\\Tasks\\Model",
"manager": "\\Tasks\\Extension",
"headless": false
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Tasks;
use Classes\IceExtension;
class Extension extends IceExtension
{
public function install() {
$migration = new Migration();
return $migration->up();
}
public function uninstall() {
$migration = new Migration();
return $migration->down();
}
public function setupModuleClassDefinitions()
{
}
public function setupRestEndPoints()
{
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Tasks;
use Classes\Migration\AbstractMigration;
class Migration extends AbstractMigration
{
public function up()
{
$sql = <<<'SQL'
create table `Tasks` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`employee` bigint(20) NULL,
`name` varchar(250) NOT NULL,
`description` TEXT NULL,
`attachment` varchar(100) NULL,
`created` DATETIME default NULL,
`updated` DATETIME default NULL,
primary key (`id`),
CONSTRAINT `Fk_EmployeeTasks_Employees` FOREIGN KEY (`employee`) REFERENCES `Employees` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) engine=innodb default charset=utf8;
SQL;
return $this->executeQuery($sql);
}
public function down()
{
$sql = <<<'SQL'
DROP TABLE IF EXISTS `Tasks`;
SQL;
return $this->executeQuery($sql);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Tasks\Model;
use Classes\ModuleAccess;
use Model\BaseModel;
class Task extends BaseModel
{
public $table = 'Tasks';
public function getAdminAccess()
{
return ["get","element","save","delete"];
}
public function getManagerAccess()
{
return ["get","element"];
}
public function getUserAccess()
{
return [];
}
public function getAnonymousAccess()
{
return [];
}
public function getModuleAccess()
{
return [
new ModuleAccess('tasks'),
];
}
}

View File

@@ -0,0 +1,4 @@
<?php
require_once __DIR__.'/src/Tasks/Extension.php';
require_once __DIR__.'/src/Tasks/Migration.php';
require_once __DIR__.'/src/Tasks/Model/Task.php';

View File

@@ -0,0 +1,33 @@
<?php
$user = \Classes\BaseService::getInstance()->getCurrentUser();
echo "Welcome ".$user->username."<br/>";
echo "Creating a task <br/>";
$task = new \Tasks\Model\Task();
$taskName = 'Task-'.rand(rand(0, 100), 50000);
$task->name = $taskName;
$task->employee = $user->employee;
$task->description = $taskName.' description';
$task->created = date('Y-m-d H:i:s');
$task->updated = date('Y-m-d H:i:s');
/**
* Saving the task, $ok will be false if there were any error during the creation
*/
$ok = $task->Save();
if (!$ok) {
echo "Error: ".$task->ErrorMsg()." <br/>";
}
echo "Find last task <br/>";
$taskFromDB = new \Tasks\Model\Task();
/**
* You can use load method to load the first matching task into an empty model
*/
$taskFromDB->Load('name = ?', [$taskName]);
var_dump($taskFromDB);

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

@@ -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 = {};
@@ -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 {
@@ -1332,7 +1338,8 @@ class ModuleBase {
try {
modJs.filterQuery();
} catch (err) {
// Do Nothing
console.log(err);
console.log(err.message);
}
return false;
});
@@ -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]];
@@ -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') {

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 = '';