Compare commits
15 Commits
feature/cu
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37583bad35 | ||
|
|
ea93d4604c | ||
|
|
caf41de755 | ||
|
|
45d80e9440 | ||
|
|
bb8f11963a | ||
|
|
6581d1424e | ||
|
|
253b298b0d | ||
|
|
df554680c4 | ||
|
|
92032cf1eb | ||
|
|
22cd81611d | ||
|
|
1a3e468458 | ||
|
|
88962d4380 | ||
|
|
3b1285aeaf | ||
|
|
b73e244865 | ||
|
|
5f050282f0 |
@@ -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,7 +14,7 @@ $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'){
|
||||
@@ -23,7 +23,7 @@ if($group == 'admin' || $group == 'modules'){
|
||||
$moduleName = $name;
|
||||
$moduleGroup = 'extensions';
|
||||
$extensionIndex = APP_BASE_PATH.'/../extensions/'.$name.'/web/index.php';
|
||||
include APP_BASE_PATH.'extensions/wrapper.php';
|
||||
include $extensionIndex;
|
||||
}else{
|
||||
exit();
|
||||
}
|
||||
|
||||
@@ -4,48 +4,48 @@
|
||||
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
|
||||
*/
|
||||
|
||||
use Classes\PermissionManager;
|
||||
use Clients\Common\Model\Client;
|
||||
|
||||
$moduleName = 'clients';
|
||||
$moduleGroup = 'admin';
|
||||
define('MODULE_PATH',dirname(__FILE__));
|
||||
include APP_BASE_PATH.'header.php';
|
||||
include APP_BASE_PATH.'modulejslibs.inc.php';
|
||||
define('MODULE_PATH', dirname(__FILE__));
|
||||
include APP_BASE_PATH . 'header.php';
|
||||
include APP_BASE_PATH . 'modulejslibs.inc.php';
|
||||
?><div class="span9">
|
||||
|
||||
<ul class="nav nav-tabs" id="modTab" style="margin-bottom:0px;margin-left:5px;border-bottom: none;">
|
||||
<li class="active"><a id="tabClient" href="#tabPageClient"><?=t('Clients')?></a></li>
|
||||
</ul>
|
||||
<ul class="nav nav-tabs" id="modTab" style="margin-bottom:0px;margin-left:5px;border-bottom: none;">
|
||||
<li class="active"><a id="tabClient" href="#tabPageClient"><?= t('Clients') ?></a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="tabPageClient">
|
||||
<div id="Client" class="reviewBlock" data-content="List" style="padding-left:5px;">
|
||||
|
||||
</div>
|
||||
<div id="ClientForm" class="reviewBlock" data-content="Form" style="padding-left:5px;display:none;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="tabPageClient">
|
||||
<div id="ClientTable" class="reviewBlock" data-content="List" style="padding-left:5px;"></div>
|
||||
<div id="ClientForm"></div>
|
||||
<div id="ClientFilterForm"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
var modJsList = [];
|
||||
<?php
|
||||
$moduleData = [
|
||||
'user_level' => $user->user_level,
|
||||
'permissions' => [
|
||||
'Client' => PermissionManager::checkGeneralAccess(new Client()),
|
||||
]
|
||||
];
|
||||
?>
|
||||
<script>
|
||||
var data = <?= json_encode($moduleData) ?>;
|
||||
var modJsList = [];
|
||||
|
||||
modJsList['tabClient'] = new ClientAdapter('Client','Client');
|
||||
modJsList['tabClient'] = new ClientAdapter('Client');
|
||||
|
||||
<?php if(isset($modulePermissions['perm']['Add Clients']) && $modulePermissions['perm']['Add Clients'] == "No"){?>
|
||||
modJsList['tabClient'].setShowAddNew(false);
|
||||
<?php }?>
|
||||
modJsList.tabClient.setObjectTypeName('Client');
|
||||
modJsList.tabClient.setAccess(data.permissions.Client);
|
||||
modJsList.tabClient.setDataPipe(new IceDataPipe(modJsList.tabClient));
|
||||
modJsList.tabClient.setRemoteTable(true);
|
||||
|
||||
<?php if(isset($modulePermissions['perm']['Delete Clients']) && $modulePermissions['perm']['Delete Clients'] == "No"){?>
|
||||
modJsList['tabClient'].setShowDelete(false);
|
||||
<?php }?>
|
||||
|
||||
<?php if(isset($modulePermissions['perm']['Edit Clients']) && $modulePermissions['perm']['Edit Clients'] == "No"){?>
|
||||
modJsList['tabClient'].setShowSave(false);
|
||||
<?php }?>
|
||||
|
||||
|
||||
var modJs = modJsList['tabClient'];
|
||||
|
||||
</script>
|
||||
<?php include APP_BASE_PATH.'footer.php';?>
|
||||
var modJs = modJsList['tabClient'];
|
||||
</script>
|
||||
<?php include APP_BASE_PATH . 'footer.php'; ?>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
|
||||
*/
|
||||
|
||||
use Classes\BaseService;
|
||||
use Classes\PermissionManager;
|
||||
use Company\Common\Model\CompanyStructure;
|
||||
|
||||
@@ -60,6 +61,7 @@ path.link {
|
||||
<?php
|
||||
$moduleData = [
|
||||
'user_level' => $user->user_level,
|
||||
'customFields' => BaseService::getInstance()->getCustomFields("CompanyStructure"),
|
||||
'permissions' => [
|
||||
'CompanyStructure' => PermissionManager::checkGeneralAccess(new CompanyStructure()),
|
||||
]
|
||||
|
||||
42
core/admin/custom_fields/index.php
Normal file
42
core/admin/custom_fields/index.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use Classes\BaseService;
|
||||
use Classes\PermissionManager;
|
||||
use FieldNames\Common\Model\CustomField;
|
||||
|
||||
$moduleName = 'assets';
|
||||
$moduleGroup = 'admin';
|
||||
define('MODULE_PATH',dirname(__FILE__));
|
||||
include APP_BASE_PATH.'header.php';
|
||||
include APP_BASE_PATH.'modulejslibs.inc.php';
|
||||
$modelClasses = BaseService::getInstance()->getCustomFieldClassMap();
|
||||
?><div class="span9">
|
||||
|
||||
<ul class="nav nav-tabs" id="modTab" style="margin-bottom:0px;margin-left:5px;border-bottom: none;">
|
||||
<li class="active"><a id="tabCustomField" href="#tabPageCustomField"><?=t('Custom Fields')?></a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="tabPageCustomField">
|
||||
<div id="CustomFieldTable" class="reviewBlock" data-content="List"></div>
|
||||
<div id="CustomFieldForm"></div>
|
||||
<div id="CustomFieldFilterForm"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="dataGroup"></div>
|
||||
<?php
|
||||
$moduleData = [
|
||||
'user_level' => $user->user_level,
|
||||
'types' => $modelClasses,
|
||||
'permissions' => [
|
||||
'CustomField' => PermissionManager::checkGeneralAccess(new CustomField()),
|
||||
]
|
||||
];
|
||||
?>
|
||||
<script>
|
||||
initAdminCustomFields(<?=json_encode($moduleData)?>);
|
||||
</script>
|
||||
<?php include APP_BASE_PATH.'footer.php';?>
|
||||
|
||||
12
core/admin/custom_fields/meta.json
Normal file
12
core/admin/custom_fields/meta.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"label": "Custom Fields",
|
||||
"menu": "Admin",
|
||||
"order": "892",
|
||||
"icon": "fa-code",
|
||||
"user_levels": [
|
||||
"Admin"
|
||||
],
|
||||
"permissions": [],
|
||||
"model_namespace": "\\FieldNames\\Common\\Model",
|
||||
"manager": "\\CustomField\\Admin\\Api\\CustomFieldAdminManager"
|
||||
}
|
||||
@@ -13,41 +13,25 @@ include APP_BASE_PATH.'modulejslibs.inc.php';
|
||||
|
||||
<ul class="nav nav-tabs" id="modTab" style="margin-bottom:0px;margin-left:5px;border-bottom: none;">
|
||||
<li class="active"><a id="tabEmployeeFieldName" href="#tabPageEmployeeFieldName"><?=t('Employee Field Names')?></a></li>
|
||||
<li><a id="tabEmployeeCustomField" href="#tabPageEmployeeCustomField"><?=t('Employee Custom Fields')?></a></li>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="tabPageEmployeeFieldName">
|
||||
<div id="EmployeeFieldName" class="reviewBlock" data-content="List" style="padding-left:5px;">
|
||||
|
||||
</div>
|
||||
<div id="EmployeeFieldNameForm" class="reviewBlock" data-content="Form" style="padding-left:5px;display:none;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="tabPageEmployeeCustomField">
|
||||
<div id="EmployeeCustomField" class="reviewBlock" data-content="List" style="padding-left:5px;">
|
||||
|
||||
</div>
|
||||
<div id="EmployeeCustomFieldForm" class="reviewBlock" data-content="Form" style="padding-left:5px;display:none;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
var modJsList = new Array();
|
||||
var modJsList = [];
|
||||
|
||||
modJsList['tabEmployeeFieldName'] = new FieldNameAdapter('FieldNameMapping','EmployeeFieldName',{"type":"Employee"});
|
||||
modJsList['tabEmployeeFieldName'].setRemoteTable(true);
|
||||
modJsList['tabEmployeeFieldName'].setShowDelete(false);
|
||||
modJsList['tabEmployeeFieldName'].setShowAddNew(false);
|
||||
|
||||
modJsList['tabEmployeeCustomField'] = new CustomFieldAdapter('CustomField','EmployeeCustomField',{"type":"Employee"},"display_order desc");
|
||||
modJsList['tabEmployeeCustomField'].setRemoteTable(true);
|
||||
modJsList['tabEmployeeCustomField'].setTableType("Employee");
|
||||
|
||||
|
||||
var modJs = modJsList['tabEmployeeFieldName'];
|
||||
|
||||
|
||||
@@ -4,71 +4,65 @@
|
||||
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
|
||||
*/
|
||||
|
||||
use Classes\PermissionManager;
|
||||
use Projects\Common\Model\Project;
|
||||
use Projects\Common\Model\EmployeeProject;
|
||||
|
||||
$moduleName = 'projects';
|
||||
$moduleGroup = 'admin';
|
||||
define('MODULE_PATH',dirname(__FILE__));
|
||||
include APP_BASE_PATH.'header.php';
|
||||
include APP_BASE_PATH.'modulejslibs.inc.php';
|
||||
define('MODULE_PATH', dirname(__FILE__));
|
||||
include APP_BASE_PATH . 'header.php';
|
||||
include APP_BASE_PATH . 'modulejslibs.inc.php';
|
||||
|
||||
?><div class="span9">
|
||||
|
||||
<ul class="nav nav-tabs" id="modTab" style="margin-bottom:0px;margin-left:5px;border-bottom: none;">
|
||||
<li class="active"><a id="tabProject" href="#tabPageProject"><?=t('Projects')?></a></li>
|
||||
<li><a id="tabEmployeeProject" href="#tabPageEmployeeProject"><?=t('Employee Projects')?></a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="tabPageProject">
|
||||
<div id="Project" class="reviewBlock" data-content="List" style="padding-left:5px;">
|
||||
<ul class="nav nav-tabs" id="modTab" style="margin-bottom:0px;margin-left:5px;border-bottom: none;">
|
||||
<li class="active"><a id="tabProject" href="#tabPageProject"><?= t('Projects') ?></a></li>
|
||||
<li><a id="tabEmployeeProject" href="#tabPageEmployeeProject"><?= t('Employee Projects') ?></a></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<div id="ProjectForm" class="reviewBlock" data-content="Form" style="padding-left:5px;display:none;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="tabPageEmployeeProject">
|
||||
<div id="EmployeeProject" class="reviewBlock" data-content="List" style="padding-left:5px;">
|
||||
|
||||
</div>
|
||||
<div id="EmployeeProjectForm" class="reviewBlock" data-content="Form" style="padding-left:5px;display:none;">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="tabPageProject">
|
||||
<div id="ProjectTable" class="reviewBlock" data-content="List" style="padding-left:5px;"></div>
|
||||
<div id="ProjectForm"></div>
|
||||
<div id="ProjectFilterForm"></div>
|
||||
</div>
|
||||
<div class="tab-pane" id="tabPageEmployeeProject">
|
||||
<div id="EmployeeProjectTable" class="reviewBlock" data-content="List" style="padding-left:5px;"></div>
|
||||
<div id="EmployeeProjectForm"></div>
|
||||
<div id="EmployeeProjectFilterForm"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<?php
|
||||
$moduleData = [
|
||||
'user_level' => $user->user_level,
|
||||
'permissions' => [
|
||||
'Project' => PermissionManager::checkGeneralAccess(new Project()),
|
||||
'EmployeeProject' => PermissionManager::checkGeneralAccess(new EmployeeProject()),
|
||||
]
|
||||
];
|
||||
?>
|
||||
<script>
|
||||
var modJsList = [];
|
||||
var modJsList = [];
|
||||
var data = <?= json_encode($moduleData) ?>;
|
||||
modJsList['tabProject'] = new ProjectAdapter('Project', 'Project');
|
||||
modJsList.tabProject.setObjectTypeName('Project');
|
||||
modJsList.tabProject.setAccess(data.permissions.Project);
|
||||
modJsList.tabProject.setDataPipe(new IceDataPipe(modJsList.tabProject));
|
||||
modJsList.tabProject.setRemoteTable(true);
|
||||
|
||||
modJsList['tabProject'] = new ProjectAdapter('Project','Project');
|
||||
|
||||
<?php if(isset($modulePermissions['perm']['Add Projects']) && $modulePermissions['perm']['Add Projects'] == "No"){?>
|
||||
modJsList['tabProject'].setShowAddNew(false);
|
||||
<?php }?>
|
||||
|
||||
<?php if(isset($modulePermissions['perm']['Delete Projects']) && $modulePermissions['perm']['Delete Projects'] == "No"){?>
|
||||
modJsList['tabProject'].setShowDelete(false);
|
||||
<?php }?>
|
||||
|
||||
<?php if(isset($modulePermissions['perm']['Edit Projects']) && $modulePermissions['perm']['Edit Projects'] == "No"){?>
|
||||
modJsList['tabProject'].setShowSave(false);
|
||||
<?php }?>
|
||||
modJsList['tabEmployeeProject'] = new EmployeeProjectAdapter('EmployeeProject', 'EmployeeProject');
|
||||
|
||||
|
||||
modJsList['tabEmployeeProject'] = new EmployeeProjectAdapter('EmployeeProject','EmployeeProject');
|
||||
modJsList['tabEmployeeProject'].setRemoteTable(true);
|
||||
modJsList.tabEmployeeProject.setObjectTypeName('Employee Project');
|
||||
modJsList.tabEmployeeProject.setAccess(data.permissions.EmployeeProject);
|
||||
modJsList.tabEmployeeProject.setDataPipe(new IceDataPipe(modJsList.tabEmployeeProject));
|
||||
modJsList.tabEmployeeProject.setRemoteTable(true);
|
||||
|
||||
<?php if(isset($modulePermissions['perm']['Add Projects']) && $modulePermissions['perm']['Add Projects'] == "No"){?>
|
||||
modJsList['tabEmployeeProject'].setShowAddNew(false);
|
||||
<?php }?>
|
||||
<?php if(isset($modulePermissions['perm']['Delete Projects']) && $modulePermissions['perm']['Delete Projects'] == "No"){?>
|
||||
modJsList['tabEmployeeProject'].setShowDelete(false);
|
||||
<?php }?>
|
||||
<?php if(isset($modulePermissions['perm']['Edit Projects']) && $modulePermissions['perm']['Edit Projects'] == "No"){?>
|
||||
modJsList['tabEmployeeProject'].setShowEdit(false);
|
||||
<?php }?>
|
||||
|
||||
|
||||
var modJs = modJsList['tabProject'];
|
||||
var modJs = modJsList['tabProject'];
|
||||
|
||||
</script>
|
||||
<?php include APP_BASE_PATH.'footer.php';?>
|
||||
<?php include APP_BASE_PATH . 'footer.php'; ?>
|
||||
|
||||
@@ -19,28 +19,42 @@ $options1 = array();
|
||||
$options1['setShowAddNew'] = 'false';
|
||||
$options1['setRemoteTable'] = 'true';
|
||||
|
||||
$notCloud = !defined('IS_CLOUD') || IS_CLOUD == false;
|
||||
|
||||
$moduleBuilder->addModuleOrGroup(new ModuleTab(
|
||||
'CompanySetting','Setting','Company','SettingAdapter','{"category":"Company"}','name',true,$options1
|
||||
));
|
||||
$moduleBuilder->addModuleOrGroup(new ModuleTab(
|
||||
'SystemSetting','Setting','System','SettingAdapter','{"category":"System"}','name',false,$options1
|
||||
));
|
||||
if (!defined('CLOUD_INSTALLATION')) {
|
||||
if ( $notCloud ) {
|
||||
$moduleBuilder->addModuleOrGroup(new ModuleTab(
|
||||
'EmailSetting', 'Setting', 'Email', 'SettingAdapter', '{"category":"Email"}', 'name', false, $options1
|
||||
));
|
||||
}
|
||||
if(!defined('LEAVE_ENABLED') || LDAP_ENABLED == true) {
|
||||
$moduleBuilder->addModuleOrGroup(new ModuleTab(
|
||||
'LeaveSetting', 'Setting', 'Leave', 'SettingAdapter', '{"category":"Leave"}', 'name', false, $options1
|
||||
));
|
||||
}
|
||||
|
||||
$moduleBuilder->addModuleOrGroup(new ModuleTab(
|
||||
'LeaveSetting','Setting','Leave / PTO','SettingAdapter','{"category":"Leave"}','name',false,$options1
|
||||
'AttendanceSetting','Setting','Attendance','SettingAdapter','{"category":"Attendance"}','name',false,$options1
|
||||
));
|
||||
|
||||
if(!defined('LDAP_ENABLED') || LDAP_ENABLED == true){
|
||||
$moduleBuilder->addModuleOrGroup(new ModuleTab(
|
||||
'LDAPSetting','Setting','LDAP','SettingAdapter','{"category":"LDAP"}','name',false,$options1
|
||||
));
|
||||
}
|
||||
$moduleBuilder->addModuleOrGroup(new ModuleTab(
|
||||
'AttendanceSetting','Setting','Attendance','SettingAdapter','{"category":"Attendance"}','name',false,$options1
|
||||
));
|
||||
|
||||
if(!defined('SAML_ENABLED') || SAML_ENABLED == true){
|
||||
$moduleBuilder->addModuleOrGroup(new ModuleTab(
|
||||
'SAMLSetting','Setting','SAML','SettingAdapter','{"category":"SAML"}','name',false,$options1
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
$moduleBuilder->addModuleOrGroup(new ModuleTab(
|
||||
'OtherSetting','Setting','Other','SettingAdapter','{"category":["Projects","Recruitment","Notifications","Expense","Travel","Api","Overtime"]}','name',false,$options1
|
||||
));
|
||||
|
||||
@@ -31,22 +31,6 @@ $moduleBuilder->addModuleOrGroup(new \Classes\ModuleBuilder\ModuleTab(
|
||||
$travelRequestOptions
|
||||
));
|
||||
|
||||
if ($user->user_level === 'Admin') {
|
||||
$travelCustomFieldOptions = [];
|
||||
$travelCustomFieldOptions['setRemoteTable'] = 'true';
|
||||
$travelCustomFieldOptions['setTableType'] = '\'EmployeeTravelRecord\'';
|
||||
|
||||
$moduleBuilder->addModuleOrGroup(new \Classes\ModuleBuilder\ModuleTab(
|
||||
'TravelCustomField',
|
||||
'CustomField',
|
||||
'Custom Fields',
|
||||
'CustomFieldAdapter',
|
||||
'{"type":"EmployeeTravelRecord"}',
|
||||
'',
|
||||
false,
|
||||
$travelCustomFieldOptions
|
||||
));
|
||||
}
|
||||
|
||||
echo \Classes\UIManager::getInstance()->renderModule($moduleBuilder);
|
||||
|
||||
|
||||
@@ -13,14 +13,13 @@ if(!defined('HOME_LINK_OTHERS')){
|
||||
}
|
||||
|
||||
//Version
|
||||
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');
|
||||
define('VERSION', '29.0.0.OS');
|
||||
define('CACHE_VALUE', '29.0.0.OS.2020-04021509');
|
||||
define('VERSION_NUMBER', '290000');
|
||||
define('VERSION_DATE', '02/04/2021');
|
||||
|
||||
if(!defined('CONTACT_EMAIL')){define('CONTACT_EMAIL','icehrm@gamonoid.com');}
|
||||
if(!defined('KEY_PREFIX')){define('KEY_PREFIX','IceHrm');}
|
||||
if(!defined('APP_SEC')){define('APP_SEC','dbcs234d2saaqw');}
|
||||
|
||||
define('UI_SHOW_SWITCH_PROFILE', true);
|
||||
define('CRON_LOG', ini_get('error_log'));
|
||||
@@ -33,7 +32,10 @@ if(!defined('WK_HTML_PATH')){
|
||||
}
|
||||
define('ALL_CLIENT_BASE_PATH', '/var/www/icehrm.app/icehrmapp/');
|
||||
|
||||
define('IS_CLOUD', false);
|
||||
define('LDAP_ENABLED', true);
|
||||
define('SAML_ENABLED', true);
|
||||
define('LEAVE_ENABLED', true);
|
||||
define('RECRUITMENT_ENABLED', false);
|
||||
define('APP_WEB_URL', 'https://icehrm.com');
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
<?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';
|
||||
?>
|
||||
1354
core/lib/saml2/Assertion.php
Normal file
1354
core/lib/saml2/Assertion.php
Normal file
File diff suppressed because it is too large
Load Diff
185
core/lib/saml2/MetadataReader.php
Normal file
185
core/lib/saml2/MetadataReader.php
Normal file
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
include_once 'Utilities.php';
|
||||
class IDPMetadataReader{
|
||||
|
||||
private $identityProviders;
|
||||
private $serviceProviders;
|
||||
|
||||
public function __construct(DOMNode $xml = NULL){
|
||||
|
||||
$this->identityProviders = array();
|
||||
$this->serviceProviders = array();
|
||||
|
||||
$entitiesDescriptor = Utilities::xpQuery($xml, './saml_metadata:EntitiesDescriptor');
|
||||
|
||||
if(!empty($entitiesDescriptor))
|
||||
$entityDescriptors = Utilities::xpQuery($entitiesDescriptor[0], './saml_metadata:EntityDescriptor');
|
||||
else
|
||||
$entityDescriptors = Utilities::xpQuery($xml, './saml_metadata:EntityDescriptor');
|
||||
|
||||
foreach ($entityDescriptors as $entityDescriptor) {
|
||||
$idpSSODescriptor = Utilities::xpQuery($entityDescriptor, './saml_metadata:IDPSSODescriptor');
|
||||
|
||||
if(isset($idpSSODescriptor) && !empty($idpSSODescriptor)){
|
||||
array_push($this->identityProviders,new IdentityProviders($entityDescriptor));
|
||||
}
|
||||
//TODO: add sp descriptor
|
||||
}
|
||||
}
|
||||
|
||||
public function getIdentityProviders(){
|
||||
return $this->identityProviders;
|
||||
}
|
||||
|
||||
public function getServiceProviders(){
|
||||
return $this->serviceProviders;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class IdentityProviders{
|
||||
|
||||
private $idpName;
|
||||
private $entityID;
|
||||
private $loginDetails;
|
||||
private $logoutDetails;
|
||||
private $signingCertificate;
|
||||
private $encryptionCertificate;
|
||||
private $signedRequest;
|
||||
|
||||
public function __construct(DOMElement $xml = NULL){
|
||||
|
||||
$this->idpName = '';
|
||||
$this->loginDetails = array();
|
||||
$this->logoutDetails = array();
|
||||
$this->signingCertificate = array();
|
||||
$this->encryptionCertificate = array();
|
||||
|
||||
if ($xml->hasAttribute('entityID')) {
|
||||
$this->entityID = $xml->getAttribute('entityID');
|
||||
}
|
||||
|
||||
if($xml->hasAttribute('WantAuthnRequestsSigned')){
|
||||
$this->signedRequest = $xml->getAttribute('WantAuthnRequestsSigned');
|
||||
}
|
||||
|
||||
$idpSSODescriptor = Utilities::xpQuery($xml, './saml_metadata:IDPSSODescriptor');
|
||||
|
||||
if (count($idpSSODescriptor) > 1) {
|
||||
throw new Exception('More than one <IDPSSODescriptor> in <EntityDescriptor>.');
|
||||
} elseif (empty($idpSSODescriptor)) {
|
||||
throw new Exception('Missing required <IDPSSODescriptor> in <EntityDescriptor>.');
|
||||
}
|
||||
$idpSSODescriptorEL = $idpSSODescriptor[0];
|
||||
|
||||
$info = Utilities::xpQuery($xml, './saml_metadata:Extensions');
|
||||
|
||||
if($info)
|
||||
$this->parseInfo($idpSSODescriptorEL);
|
||||
$this->parseSSOService($idpSSODescriptorEL);
|
||||
$this->parseSLOService($idpSSODescriptorEL);
|
||||
$this->parsex509Certificate($idpSSODescriptorEL);
|
||||
|
||||
}
|
||||
|
||||
private function parseInfo($xml){
|
||||
$displayNames = Utilities::xpQuery($xml, './mdui:UIInfo/mdui:DisplayName');
|
||||
foreach ($displayNames as $name) {
|
||||
if($name->hasAttribute('xml:lang') && $name->getAttribute('xml:lang')=="en"){
|
||||
$this->idpName = $name->textContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parseSSOService($xml){
|
||||
$ssoServices = Utilities::xpQuery($xml, './saml_metadata:SingleSignOnService');
|
||||
foreach ($ssoServices as $ssoService) {
|
||||
$binding = str_replace("urn:oasis:names:tc:SAML:2.0:bindings:","",$ssoService->getAttribute('Binding'));
|
||||
$this->loginDetails = array_merge(
|
||||
$this->loginDetails,
|
||||
array($binding => $ssoService->getAttribute('Location'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function parseSLOService($xml){
|
||||
$sloServices = Utilities::xpQuery($xml, './saml_metadata:SingleLogoutService');
|
||||
foreach ($sloServices as $sloService) {
|
||||
$binding = str_replace("urn:oasis:names:tc:SAML:2.0:bindings:","",$sloService->getAttribute('Binding'));
|
||||
$this->logoutDetails = array_merge(
|
||||
$this->logoutDetails,
|
||||
array($binding => $sloService->getAttribute('Location'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function parsex509Certificate($xml){
|
||||
foreach ( Utilities::xpQuery($xml, './saml_metadata:KeyDescriptor') as $KeyDescriptorNode ) {
|
||||
if($KeyDescriptorNode->hasAttribute('use')){
|
||||
if($KeyDescriptorNode->getAttribute('use')=='encryption'){
|
||||
$this->parseEncryptionCertificate($KeyDescriptorNode);
|
||||
}else{
|
||||
$this->parseSigningCertificate($KeyDescriptorNode);
|
||||
}
|
||||
}else{
|
||||
$this->parseSigningCertificate($KeyDescriptorNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parseSigningCertificate($xml){
|
||||
$certNode = Utilities::xpQuery($xml, './ds:KeyInfo/ds:X509Data/ds:X509Certificate');
|
||||
$certData = trim($certNode[0]->textContent);
|
||||
$certData = str_replace(array ( "\r", "\n", "\t", ' '), '', $certData);
|
||||
if(!empty($certNode))
|
||||
array_push($this->signingCertificate, Utilities::sanitize_certificate( $certData ));
|
||||
}
|
||||
|
||||
|
||||
private function parseEncryptionCertificate($xml){
|
||||
$certNode = Utilities::xpQuery($xml, './ds:KeyInfo/ds:X509Data/ds:X509Certificate');
|
||||
$certData = trim($certNode[0]->textContent);
|
||||
$certData = str_replace(array ( "\r", "\n", "\t", ' '), '', $certData);
|
||||
if(!empty($certNode))
|
||||
array_push($this->encryptionCertificate, $certData);
|
||||
}
|
||||
|
||||
public function getIdpName(){
|
||||
return "";
|
||||
}
|
||||
|
||||
public function getEntityID(){
|
||||
return $this->entityID;
|
||||
}
|
||||
|
||||
public function getLoginURL($binding){
|
||||
return $this->loginDetails[$binding];
|
||||
}
|
||||
|
||||
public function getLogoutURL($binding){
|
||||
return $this->logoutDetails[$binding];
|
||||
}
|
||||
|
||||
public function getLoginDetails(){
|
||||
return $this->loginDetails;
|
||||
}
|
||||
|
||||
public function getLogoutDetails(){
|
||||
return $this->logoutDetails;
|
||||
}
|
||||
|
||||
public function getSigningCertificate(){
|
||||
return $this->signingCertificate;
|
||||
}
|
||||
|
||||
public function getEncryptionCertificate(){
|
||||
return $this->encryptionCertificate[0];
|
||||
}
|
||||
|
||||
public function isRequestSigned(){
|
||||
return $this->signedRequest;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
33
core/lib/saml2/MoSAMLBasicEnum.php
Normal file
33
core/lib/saml2/MoSAMLBasicEnum.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
abstract class MoSAMLBasicEnum {
|
||||
private static $constCacheArray = NULL;
|
||||
|
||||
public static function getConstants() {
|
||||
if (self::$constCacheArray == NULL) {
|
||||
self::$constCacheArray = array();
|
||||
}
|
||||
$calledClass = get_called_class();
|
||||
if (!array_key_exists($calledClass, self::$constCacheArray)) {
|
||||
$reflect = new ReflectionClass($calledClass);
|
||||
self::$constCacheArray[$calledClass] = $reflect->getConstants();
|
||||
}
|
||||
return self::$constCacheArray[$calledClass];
|
||||
}
|
||||
|
||||
public static function isValidName($name, $strict = false) {
|
||||
$constants = self::getConstants();
|
||||
|
||||
if ($strict) {
|
||||
return array_key_exists($name, $constants);
|
||||
}
|
||||
|
||||
$keys = array_map('strtolower', array_keys($constants));
|
||||
return in_array(strtolower($name), $keys);
|
||||
}
|
||||
|
||||
public static function isValidValue($value, $strict = true) {
|
||||
$values = array_values(self::getConstants());
|
||||
return in_array($value, $values, $strict);
|
||||
}
|
||||
}
|
||||
109
core/lib/saml2/MoSAMLPointer.php
Normal file
109
core/lib/saml2/MoSAMLPointer.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: HP
|
||||
* Date: 9/11/2018
|
||||
* Time: 9:48 AM
|
||||
*/
|
||||
|
||||
class MoSAMLPointer
|
||||
{
|
||||
|
||||
private $content,$anchor_id,$edge,$align,$active,$pointer_name;
|
||||
|
||||
function __construct($header,$body,$anchor_id,$edge,$align,$active,$prefix){
|
||||
|
||||
$this->content = '<h3>'.$header.'</h3>';
|
||||
$this->content .= '<p id="'.$prefix.'" style="font-size: initial;">' .$body . '</p>';
|
||||
$this-> anchor_id = $anchor_id;
|
||||
$this->edge = $edge;
|
||||
$this->align = $align;
|
||||
$this->active = $active;
|
||||
$this->pointer_name = 'miniorange_admin_pointer_'.$prefix;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
function return_array(){
|
||||
return array(
|
||||
// The content needs to point to what we created above in the $new_pointer_content variable
|
||||
'content' => $this->content,
|
||||
|
||||
// In order for the custom pointer to appear in the right location we need to specify the ID
|
||||
// of the element we want it to appear next to
|
||||
'anchor_id' => $this->anchor_id,
|
||||
|
||||
// On what edge do we want the pointer to appear. Options are 'top', 'left', 'right', 'bottom'
|
||||
'edge' => $this->edge,
|
||||
|
||||
// How do we want out custom pointer to align to the element it is attached to. Options are
|
||||
// 'left', 'right', 'center'
|
||||
'align' => $this->align,
|
||||
|
||||
// This is how we tell the pointer to be dismissed or not. Make sure that the 'new_items'
|
||||
// string matches the string at the beginning of the array item
|
||||
'active' => $this->active
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $content
|
||||
*/
|
||||
public function setContent($content)
|
||||
{
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAnchorId()
|
||||
{
|
||||
return $this->anchor_id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getEdge()
|
||||
{
|
||||
return $this->edge;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getActive()
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $active
|
||||
*/
|
||||
public function setActive($active)
|
||||
{
|
||||
$this->active = $active;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getPointerName()
|
||||
{
|
||||
return $this->pointer_name;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
51
core/lib/saml2/PointersManager.php
Normal file
51
core/lib/saml2/PointersManager.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
class MoSAMLPointersManager {
|
||||
|
||||
private $pfile;
|
||||
private $version;
|
||||
private $prefix;
|
||||
private $pointers = array();
|
||||
|
||||
public function __construct( $file, $version, $prefix ) {
|
||||
$this->pfile = file_exists( $file ) ? $file : FALSE;
|
||||
$this->version = str_replace( '.', '_', $version );
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
public function parse() {
|
||||
if ( empty( $this->pfile ) ) return;
|
||||
$pointers = (array) require_once $this->pfile;
|
||||
if ( empty($pointers) ) return;
|
||||
foreach ( $pointers as $i => $pointer ) {
|
||||
if(is_array($pointer)){
|
||||
$pointer['id'] = "{$this->prefix}{$this->version}_{$i}";
|
||||
$this->pointers[$pointer['id']] = (object) $pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function filter( $page ) {
|
||||
if ( empty( $this->pointers ) ) return array();
|
||||
$uid = get_current_user_id();
|
||||
$no = explode( ',', (string) get_user_meta( $uid, 'dismissed_wp_pointers', TRUE ) );
|
||||
$active_ids = array_diff( array_keys( $this->pointers ), $no );
|
||||
$good = array();
|
||||
foreach( $this->pointers as $i => $pointer ) {
|
||||
if (
|
||||
in_array( $i, $active_ids, TRUE ) // is active
|
||||
&& isset( $pointer->where ) // has where
|
||||
&& in_array( $page, (array) $pointer->where, TRUE ) // current page is in where
|
||||
) {
|
||||
$good[] = $pointer;
|
||||
}
|
||||
}
|
||||
$count = count( $good );
|
||||
if ( $good === 0 ) return array();
|
||||
foreach( array_values( $good ) as $i => $pointer ) {
|
||||
$good[$i]->next = $i+1 < $count ? $good[$i+1]->id : '';
|
||||
}
|
||||
|
||||
return $good;
|
||||
}
|
||||
}
|
||||
114
core/lib/saml2/Response.php
Normal file
114
core/lib/saml2/Response.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of miniOrange SAML plugin.
|
||||
*
|
||||
* miniOrange SAML plugin is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* miniOrange SAML plugin is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with miniOrange SAML plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
include 'Assertion.php';
|
||||
|
||||
/**
|
||||
* Class for SAML2 Response messages.
|
||||
*
|
||||
*/
|
||||
class SAML2_Response
|
||||
{
|
||||
/**
|
||||
* The assertions in this response.
|
||||
*/
|
||||
private $assertions;
|
||||
|
||||
/**
|
||||
* The destination URL in this response.
|
||||
*/
|
||||
private $destination;
|
||||
|
||||
private $certificates;
|
||||
private $signatureData;
|
||||
|
||||
/**
|
||||
* Constructor for SAML 2 response messages.
|
||||
*
|
||||
* @param DOMElement|NULL $xml The input message.
|
||||
*/
|
||||
public function __construct(DOMElement $xml = NULL)
|
||||
{
|
||||
//parent::__construct('Response', $xml);
|
||||
|
||||
$this->assertions = array();
|
||||
$this->certificates = array();
|
||||
|
||||
if ($xml === NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sig = Utilities::validateElement($xml);
|
||||
if ($sig !== FALSE) {
|
||||
$this->certificates = $sig['Certificates'];
|
||||
$this->signatureData = $sig;
|
||||
}
|
||||
|
||||
/* set the destination from saml response */
|
||||
if ($xml->hasAttribute('Destination')) {
|
||||
$this->destination = $xml->getAttribute('Destination');
|
||||
}
|
||||
|
||||
for ($node = $xml->firstChild; $node !== NULL; $node = $node->nextSibling) {
|
||||
if ($node->namespaceURI !== 'urn:oasis:names:tc:SAML:2.0:assertion') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($node->localName === 'Assertion' || $node->localName === 'EncryptedAssertion') {
|
||||
$this->assertions[] = new SAML2_Assertion($node);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the assertions in this response.
|
||||
*
|
||||
* @return SAML2_Assertion[]|SAML2_EncryptedAssertion[]
|
||||
*/
|
||||
public function getAssertions()
|
||||
{
|
||||
return $this->assertions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the assertions that should be included in this response.
|
||||
*
|
||||
* @param SAML2_Assertion[]|SAML2_EncryptedAssertion[] The assertions.
|
||||
*/
|
||||
public function setAssertions(array $assertions)
|
||||
{
|
||||
$this->assertions = $assertions;
|
||||
}
|
||||
|
||||
public function getDestination()
|
||||
{
|
||||
return $this->destination;
|
||||
}
|
||||
|
||||
|
||||
public function getCertificates()
|
||||
{
|
||||
return $this->certificates;
|
||||
}
|
||||
|
||||
public function getSignatureData()
|
||||
{
|
||||
return $this->signatureData;
|
||||
}
|
||||
}
|
||||
511
core/lib/saml2/SAML2Core/MoXMLSecEnc.php
Normal file
511
core/lib/saml2/SAML2Core/MoXMLSecEnc.php
Normal file
@@ -0,0 +1,511 @@
|
||||
<?php
|
||||
namespace RobRichards\XMLSecLibs;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use DOMNode;
|
||||
use DOMXPath;
|
||||
use Exception;
|
||||
use RobRichards\XMLSecLibs\Utils\MoXPath as XPath;
|
||||
|
||||
/**
|
||||
* xmlseclibs.php
|
||||
*
|
||||
* Copyright (c) 2007-2020, Robert Richards <rrichards@cdatazone.org>.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* * Neither the name of Robert Richards nor the names of his
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @author Robert Richards <rrichards@cdatazone.org>
|
||||
* @copyright 2007-2020 Robert Richards <rrichards@cdatazone.org>
|
||||
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
|
||||
*/
|
||||
|
||||
class MoXMLSecEnc
|
||||
{
|
||||
const template = "<xenc:EncryptedData xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'>
|
||||
<xenc:CipherData>
|
||||
<xenc:CipherValue></xenc:CipherValue>
|
||||
</xenc:CipherData>
|
||||
</xenc:EncryptedData>";
|
||||
|
||||
const Element = 'http://www.w3.org/2001/04/xmlenc#Element';
|
||||
const Content = 'http://www.w3.org/2001/04/xmlenc#Content';
|
||||
const URI = 3;
|
||||
const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#';
|
||||
|
||||
/** @var null|DOMDocument */
|
||||
private $encdoc = null;
|
||||
|
||||
/** @var null|DOMNode */
|
||||
private $rawNode = null;
|
||||
|
||||
/** @var null|string */
|
||||
public $type = null;
|
||||
|
||||
/** @var null|DOMElement */
|
||||
public $encKey = null;
|
||||
|
||||
/** @var array */
|
||||
private $references = array();
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->_resetTemplate();
|
||||
}
|
||||
|
||||
private function _resetTemplate()
|
||||
{
|
||||
$this->encdoc = new DOMDocument();
|
||||
$this->encdoc->loadXML(self::template);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param DOMNode $node
|
||||
* @param string $type
|
||||
* @throws Exception
|
||||
*/
|
||||
public function addReference($name, $node, $type)
|
||||
{
|
||||
if (! $node instanceOf DOMNode) {
|
||||
throw new Exception('$node is not of type DOMNode');
|
||||
}
|
||||
$curencdoc = $this->encdoc;
|
||||
$this->_resetTemplate();
|
||||
$encdoc = $this->encdoc;
|
||||
$this->encdoc = $curencdoc;
|
||||
$refuri = MoXMLSecurityDSig::generateGUID();
|
||||
$element = $encdoc->documentElement;
|
||||
$element->setAttribute("Id", $refuri);
|
||||
$this->references[$name] = array("node" => $node, "type" => $type, "encnode" => $encdoc, "refuri" => $refuri);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DOMNode $node
|
||||
*/
|
||||
public function setNode($node)
|
||||
{
|
||||
$this->rawNode = $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the selected node with the given key.
|
||||
*
|
||||
* @param MoXMLSecurityKey $objKey The encryption key and algorithm.
|
||||
* @param bool $replace Whether the encrypted node should be replaced in the original tree. Default is true.
|
||||
* @return DOMElement The <xenc:EncryptedData>-element.
|
||||
*@throws Exception
|
||||
*
|
||||
*/
|
||||
public function encryptNode($objKey, $replace = true)
|
||||
{
|
||||
$data = '';
|
||||
if (empty($this->rawNode)) {
|
||||
throw new Exception('Node to encrypt has not been set');
|
||||
}
|
||||
if (! $objKey instanceof MoXMLSecurityKey) {
|
||||
throw new Exception('Invalid Key');
|
||||
}
|
||||
$doc = $this->rawNode->ownerDocument;
|
||||
$xPath = new DOMXPath($this->encdoc);
|
||||
$objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue');
|
||||
$cipherValue = $objList->item(0);
|
||||
if ($cipherValue == null) {
|
||||
throw new Exception('Error locating CipherValue element within template');
|
||||
}
|
||||
switch ($this->type) {
|
||||
case (self::Element):
|
||||
$data = $doc->saveXML($this->rawNode);
|
||||
$this->encdoc->documentElement->setAttribute('Type', self::Element);
|
||||
break;
|
||||
case (self::Content):
|
||||
$children = $this->rawNode->childNodes;
|
||||
foreach ($children AS $child) {
|
||||
$data .= $doc->saveXML($child);
|
||||
}
|
||||
$this->encdoc->documentElement->setAttribute('Type', self::Content);
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Type is currently not supported');
|
||||
}
|
||||
|
||||
$encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod'));
|
||||
$encMethod->setAttribute('Algorithm', $objKey->getAlgorithm());
|
||||
$cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode->parentNode->firstChild);
|
||||
|
||||
$strEncrypt = base64_encode($objKey->encryptData($data));
|
||||
$value = $this->encdoc->createTextNode($strEncrypt);
|
||||
$cipherValue->appendChild($value);
|
||||
|
||||
if ($replace) {
|
||||
switch ($this->type) {
|
||||
case (self::Element):
|
||||
if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
|
||||
return $this->encdoc;
|
||||
}
|
||||
$importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true);
|
||||
$this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
|
||||
return $importEnc;
|
||||
case (self::Content):
|
||||
$importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true);
|
||||
while ($this->rawNode->firstChild) {
|
||||
$this->rawNode->removeChild($this->rawNode->firstChild);
|
||||
}
|
||||
$this->rawNode->appendChild($importEnc);
|
||||
return $importEnc;
|
||||
}
|
||||
} else {
|
||||
return $this->encdoc->documentElement;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MoXMLSecurityKey $objKey
|
||||
* @throws Exception
|
||||
*/
|
||||
public function encryptReferences($objKey)
|
||||
{
|
||||
$curRawNode = $this->rawNode;
|
||||
$curType = $this->type;
|
||||
foreach ($this->references AS $name => $reference) {
|
||||
$this->encdoc = $reference["encnode"];
|
||||
$this->rawNode = $reference["node"];
|
||||
$this->type = $reference["type"];
|
||||
try {
|
||||
$encNode = $this->encryptNode($objKey);
|
||||
$this->references[$name]["encnode"] = $encNode;
|
||||
} catch (Exception $e) {
|
||||
$this->rawNode = $curRawNode;
|
||||
$this->type = $curType;
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
$this->rawNode = $curRawNode;
|
||||
$this->type = $curType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the CipherValue text from this encrypted node.
|
||||
*
|
||||
* @throws Exception
|
||||
* @return string|null The Ciphervalue text, or null if no CipherValue is found.
|
||||
*/
|
||||
public function getCipherValue()
|
||||
{
|
||||
if (empty($this->rawNode)) {
|
||||
throw new Exception('Node to decrypt has not been set');
|
||||
}
|
||||
|
||||
$doc = $this->rawNode->ownerDocument;
|
||||
$xPath = new DOMXPath($doc);
|
||||
$xPath->registerNamespace('xmlencr', self::XMLENCNS);
|
||||
/* Only handles embedded content right now and not a reference */
|
||||
$query = "./xmlencr:CipherData/xmlencr:CipherValue";
|
||||
$nodeset = $xPath->query($query, $this->rawNode);
|
||||
$node = $nodeset->item(0);
|
||||
|
||||
if (!$node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return base64_decode($node->nodeValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt this encrypted node.
|
||||
*
|
||||
* The behaviour of this function depends on the value of $replace.
|
||||
* If $replace is false, we will return the decrypted data as a string.
|
||||
* If $replace is true, we will insert the decrypted element(s) into the
|
||||
* document, and return the decrypted element(s).
|
||||
*
|
||||
* @param MoXMLSecurityKey $objKey The decryption key that should be used when decrypting the node.
|
||||
* @param boolean $replace Whether we should replace the encrypted node in the XML document with the decrypted data. The default is true.
|
||||
*
|
||||
* @return string|DOMElement The decrypted data.
|
||||
*/
|
||||
public function decryptNode($objKey, $replace=true)
|
||||
{
|
||||
if (! $objKey instanceof MoXMLSecurityKey) {
|
||||
throw new Exception('Invalid Key');
|
||||
}
|
||||
|
||||
$encryptedData = $this->getCipherValue();
|
||||
if ($encryptedData) {
|
||||
$decrypted = $objKey->decryptData($encryptedData);
|
||||
if ($replace) {
|
||||
switch ($this->type) {
|
||||
case (self::Element):
|
||||
$newdoc = new DOMDocument();
|
||||
$newdoc->loadXML($decrypted);
|
||||
if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
|
||||
return $newdoc;
|
||||
}
|
||||
$importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, true);
|
||||
$this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode);
|
||||
return $importEnc;
|
||||
case (self::Content):
|
||||
if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) {
|
||||
$doc = $this->rawNode;
|
||||
} else {
|
||||
$doc = $this->rawNode->ownerDocument;
|
||||
}
|
||||
$newFrag = $doc->createDocumentFragment();
|
||||
$newFrag->appendXML($decrypted);
|
||||
$parent = $this->rawNode->parentNode;
|
||||
$parent->replaceChild($newFrag, $this->rawNode);
|
||||
return $parent;
|
||||
default:
|
||||
return $decrypted;
|
||||
}
|
||||
} else {
|
||||
return $decrypted;
|
||||
}
|
||||
} else {
|
||||
throw new Exception("Cannot locate encrypted data");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the XMLSecurityKey
|
||||
*
|
||||
* @param MoXMLSecurityKey $srcKey
|
||||
* @param MoXMLSecurityKey $rawKey
|
||||
* @param bool $append
|
||||
* @throws Exception
|
||||
*/
|
||||
public function encryptKey($srcKey, $rawKey, $append=true)
|
||||
{
|
||||
if ((! $srcKey instanceof MoXMLSecurityKey) || (! $rawKey instanceof MoXMLSecurityKey)) {
|
||||
throw new Exception('Invalid Key');
|
||||
}
|
||||
$strEncKey = base64_encode($srcKey->encryptData($rawKey->key));
|
||||
$root = $this->encdoc->documentElement;
|
||||
$encKey = $this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptedKey');
|
||||
if ($append) {
|
||||
$keyInfo = $root->insertBefore($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'), $root->firstChild);
|
||||
$keyInfo->appendChild($encKey);
|
||||
} else {
|
||||
$this->encKey = $encKey;
|
||||
}
|
||||
$encMethod = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod'));
|
||||
$encMethod->setAttribute('Algorithm', $srcKey->getAlgorith());
|
||||
if (! empty($srcKey->name)) {
|
||||
$keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'));
|
||||
$keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name));
|
||||
}
|
||||
$cipherData = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherData'));
|
||||
$cipherData->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherValue', $strEncKey));
|
||||
if (is_array($this->references) && count($this->references) > 0) {
|
||||
$refList = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:ReferenceList'));
|
||||
foreach ($this->references AS $name => $reference) {
|
||||
$refuri = $reference["refuri"];
|
||||
$dataRef = $refList->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:DataReference'));
|
||||
$dataRef->setAttribute("URI", '#' . $refuri);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param MoXMLSecurityKey $encKey
|
||||
* @return DOMElement|string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function decryptKey($encKey)
|
||||
{
|
||||
if (! $encKey->isEncrypted) {
|
||||
throw new Exception("Key is not Encrypted");
|
||||
}
|
||||
if (empty($encKey->key)) {
|
||||
throw new Exception("Key is missing data to perform the decryption");
|
||||
}
|
||||
return $this->decryptNode($encKey, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DOMDocument $element
|
||||
* @return DOMNode|null
|
||||
*/
|
||||
public function locateEncryptedData($element)
|
||||
{
|
||||
if ($element instanceof DOMDocument) {
|
||||
$doc = $element;
|
||||
} else {
|
||||
$doc = $element->ownerDocument;
|
||||
}
|
||||
if ($doc) {
|
||||
$xpath = new DOMXPath($doc);
|
||||
$query = "//*[local-name()='EncryptedData' and namespace-uri()='".self::XMLENCNS."']";
|
||||
$nodeset = $xpath->query($query);
|
||||
return $nodeset->item(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key from the DOM
|
||||
* @param null|DOMNode $node
|
||||
* @return null|MoXMLSecurityKey
|
||||
*/
|
||||
public function locateKey($node=null)
|
||||
{
|
||||
if (empty($node)) {
|
||||
$node = $this->rawNode;
|
||||
}
|
||||
if (! $node instanceof DOMNode) {
|
||||
return null;
|
||||
}
|
||||
if ($doc = $node->ownerDocument) {
|
||||
$xpath = new DOMXPath($doc);
|
||||
$xpath->registerNamespace('xmlsecenc', self::XMLENCNS);
|
||||
$query = ".//xmlsecenc:EncryptionMethod";
|
||||
$nodeset = $xpath->query($query, $node);
|
||||
if ($encmeth = $nodeset->item(0)) {
|
||||
$attrAlgorithm = $encmeth->getAttribute("Algorithm");
|
||||
try {
|
||||
$objKey = new MoXMLSecurityKey($attrAlgorithm, array('type' => 'private'));
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
return $objKey;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|MoXMLSecurityKey $objBaseKey
|
||||
* @param null|DOMNode $node
|
||||
* @return null|MoXMLSecurityKey
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function staticLocateKeyInfo($objBaseKey=null, $node=null)
|
||||
{
|
||||
if (empty($node) || (! $node instanceof DOMNode)) {
|
||||
return null;
|
||||
}
|
||||
$doc = $node->ownerDocument;
|
||||
if (!$doc) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$xpath = new DOMXPath($doc);
|
||||
$xpath->registerNamespace('xmlsecenc', self::XMLENCNS);
|
||||
$xpath->registerNamespace('xmlsecdsig', MoXMLSecurityDSig::XMLDSIGNS);
|
||||
$query = "./xmlsecdsig:KeyInfo";
|
||||
$nodeset = $xpath->query($query, $node);
|
||||
$encmeth = $nodeset->item(0);
|
||||
if (!$encmeth) {
|
||||
/* No KeyInfo in EncryptedData / EncryptedKey. */
|
||||
return $objBaseKey;
|
||||
}
|
||||
|
||||
foreach ($encmeth->childNodes AS $child) {
|
||||
switch ($child->localName) {
|
||||
case 'KeyName':
|
||||
if (! empty($objBaseKey)) {
|
||||
$objBaseKey->name = $child->nodeValue;
|
||||
}
|
||||
break;
|
||||
case 'KeyValue':
|
||||
foreach ($child->childNodes AS $keyval) {
|
||||
switch ($keyval->localName) {
|
||||
case 'DSAKeyValue':
|
||||
throw new Exception("DSAKeyValue currently not supported");
|
||||
case 'RSAKeyValue':
|
||||
$modulus = null;
|
||||
$exponent = null;
|
||||
if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) {
|
||||
$modulus = base64_decode($modulusNode->nodeValue);
|
||||
}
|
||||
if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) {
|
||||
$exponent = base64_decode($exponentNode->nodeValue);
|
||||
}
|
||||
if (empty($modulus) || empty($exponent)) {
|
||||
throw new Exception("Missing Modulus or Exponent");
|
||||
}
|
||||
$publicKey = MoXMLSecurityKey::convertRSA($modulus, $exponent);
|
||||
$objBaseKey->loadKey($publicKey);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'RetrievalMethod':
|
||||
$type = $child->getAttribute('Type');
|
||||
if ($type !== 'http://www.w3.org/2001/04/xmlenc#EncryptedKey') {
|
||||
/* Unsupported key type. */
|
||||
break;
|
||||
}
|
||||
$uri = $child->getAttribute('URI');
|
||||
if ($uri[0] !== '#') {
|
||||
/* URI not a reference - unsupported. */
|
||||
break;
|
||||
}
|
||||
$id = substr($uri, 1);
|
||||
|
||||
$query = '//xmlsecenc:EncryptedKey[@Id="'.MoXPath::filterAttrValue($id, MoXPath::DOUBLE_QUOTE).'"]';
|
||||
$keyElement = $xpath->query($query)->item(0);
|
||||
if (!$keyElement) {
|
||||
throw new Exception("Unable to locate EncryptedKey with @Id='$id'.");
|
||||
}
|
||||
|
||||
return MoXMLSecurityKey::fromEncryptedKeyElement($keyElement);
|
||||
case 'EncryptedKey':
|
||||
return MoXMLSecurityKey::fromEncryptedKeyElement($child);
|
||||
case 'X509Data':
|
||||
if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) {
|
||||
if ($x509certNodes->length > 0) {
|
||||
$x509cert = $x509certNodes->item(0)->textContent;
|
||||
$x509cert = str_replace(array("\r", "\n", " "), "", $x509cert);
|
||||
$x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n";
|
||||
$objBaseKey->loadKey($x509cert, false, true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $objBaseKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|MoXMLSecurityKey $objBaseKey
|
||||
* @param null|DOMNode $node
|
||||
* @return null|MoXMLSecurityKey
|
||||
*/
|
||||
public function locateKeyInfo($objBaseKey=null, $node=null)
|
||||
{
|
||||
if (empty($node)) {
|
||||
$node = $this->rawNode;
|
||||
}
|
||||
return self::staticLocateKeyInfo($objBaseKey, $node);
|
||||
}
|
||||
}
|
||||
1162
core/lib/saml2/SAML2Core/MoXMLSecurityDSig.php
Normal file
1162
core/lib/saml2/SAML2Core/MoXMLSecurityDSig.php
Normal file
File diff suppressed because it is too large
Load Diff
800
core/lib/saml2/SAML2Core/MoXMLSecurityKey.php
Normal file
800
core/lib/saml2/SAML2Core/MoXMLSecurityKey.php
Normal file
@@ -0,0 +1,800 @@
|
||||
<?php
|
||||
namespace RobRichards\XMLSecLibs;
|
||||
|
||||
use DOMElement;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* xmlseclibs.php
|
||||
*
|
||||
* Copyright (c) 2007-2020, Robert Richards <rrichards@cdatazone.org>.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* * Neither the name of Robert Richards nor the names of his
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @author Robert Richards <rrichards@cdatazone.org>
|
||||
* @copyright 2007-2020 Robert Richards <rrichards@cdatazone.org>
|
||||
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
|
||||
*/
|
||||
|
||||
class MoXMLSecurityKey
|
||||
{
|
||||
const TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
|
||||
const AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
|
||||
const AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
|
||||
const AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
|
||||
const AES128_GCM = 'http://www.w3.org/2009/xmlenc11#aes128-gcm';
|
||||
const AES192_GCM = 'http://www.w3.org/2009/xmlenc11#aes192-gcm';
|
||||
const AES256_GCM = 'http://www.w3.org/2009/xmlenc11#aes256-gcm';
|
||||
const RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
|
||||
const RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
|
||||
const DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1';
|
||||
const RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
|
||||
const RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
|
||||
const RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
|
||||
const RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
|
||||
const HMAC_SHA1 = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1';
|
||||
const AUTHTAG_LENGTH = 16;
|
||||
|
||||
/** @var array */
|
||||
private $cryptParams = array();
|
||||
|
||||
/** @var int|string */
|
||||
public $type = 0;
|
||||
|
||||
/** @var mixed|null */
|
||||
public $key = null;
|
||||
|
||||
/** @var string */
|
||||
public $passphrase = "";
|
||||
|
||||
/** @var string|null */
|
||||
public $iv = null;
|
||||
|
||||
/** @var string|null */
|
||||
public $name = null;
|
||||
|
||||
/** @var mixed|null */
|
||||
public $keyChain = null;
|
||||
|
||||
/** @var bool */
|
||||
public $isEncrypted = false;
|
||||
|
||||
/** @var MoXMLSecEnc|null */
|
||||
public $encryptedCtx = null;
|
||||
|
||||
/** @var mixed|null */
|
||||
public $guid = null;
|
||||
|
||||
/**
|
||||
* This variable contains the certificate as a string if this key represents an X509-certificate.
|
||||
* If this key doesn't represent a certificate, this will be null.
|
||||
* @var string|null
|
||||
*/
|
||||
private $x509Certificate = null;
|
||||
|
||||
/**
|
||||
* This variable contains the certificate thumbprint if we have loaded an X509-certificate.
|
||||
* @var string|null
|
||||
*/
|
||||
private $X509Thumbprint = null;
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param null|array $params
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct($type, $params=null)
|
||||
{
|
||||
switch ($type) {
|
||||
case (self::TRIPLEDES_CBC):
|
||||
$this->cryptParams['library'] = 'openssl';
|
||||
$this->cryptParams['cipher'] = 'des-ede3-cbc';
|
||||
$this->cryptParams['type'] = 'symmetric';
|
||||
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc';
|
||||
$this->cryptParams['keysize'] = 24;
|
||||
$this->cryptParams['blocksize'] = 8;
|
||||
break;
|
||||
case (self::AES128_CBC):
|
||||
$this->cryptParams['library'] = 'openssl';
|
||||
$this->cryptParams['cipher'] = 'aes-128-cbc';
|
||||
$this->cryptParams['type'] = 'symmetric';
|
||||
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc';
|
||||
$this->cryptParams['keysize'] = 16;
|
||||
$this->cryptParams['blocksize'] = 16;
|
||||
break;
|
||||
case (self::AES192_CBC):
|
||||
$this->cryptParams['library'] = 'openssl';
|
||||
$this->cryptParams['cipher'] = 'aes-192-cbc';
|
||||
$this->cryptParams['type'] = 'symmetric';
|
||||
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc';
|
||||
$this->cryptParams['keysize'] = 24;
|
||||
$this->cryptParams['blocksize'] = 16;
|
||||
break;
|
||||
case (self::AES256_CBC):
|
||||
$this->cryptParams['library'] = 'openssl';
|
||||
$this->cryptParams['cipher'] = 'aes-256-cbc';
|
||||
$this->cryptParams['type'] = 'symmetric';
|
||||
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc';
|
||||
$this->cryptParams['keysize'] = 32;
|
||||
$this->cryptParams['blocksize'] = 16;
|
||||
break;
|
||||
case (self::AES128_GCM):
|
||||
$this->cryptParams['library'] = 'openssl';
|
||||
$this->cryptParams['cipher'] = 'aes-128-gcm';
|
||||
$this->cryptParams['type'] = 'symmetric';
|
||||
$this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes128-gcm';
|
||||
$this->cryptParams['keysize'] = 32;
|
||||
$this->cryptParams['blocksize'] = 16;
|
||||
break;
|
||||
case (self::AES192_GCM):
|
||||
$this->cryptParams['library'] = 'openssl';
|
||||
$this->cryptParams['cipher'] = 'aes-192-gcm';
|
||||
$this->cryptParams['type'] = 'symmetric';
|
||||
$this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes192-gcm';
|
||||
$this->cryptParams['keysize'] = 32;
|
||||
$this->cryptParams['blocksize'] = 16;
|
||||
break;
|
||||
case (self::AES256_GCM):
|
||||
$this->cryptParams['library'] = 'openssl';
|
||||
$this->cryptParams['cipher'] = 'aes-256-gcm';
|
||||
$this->cryptParams['type'] = 'symmetric';
|
||||
$this->cryptParams['method'] = 'http://www.w3.org/2009/xmlenc11#aes256-gcm';
|
||||
$this->cryptParams['keysize'] = 32;
|
||||
$this->cryptParams['blocksize'] = 16;
|
||||
break;
|
||||
case (self::RSA_1_5):
|
||||
$this->cryptParams['library'] = 'openssl';
|
||||
$this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
|
||||
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5';
|
||||
if (is_array($params) && ! empty($params['type'])) {
|
||||
if ($params['type'] == 'public' || $params['type'] == 'private') {
|
||||
$this->cryptParams['type'] = $params['type'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
|
||||
case (self::RSA_OAEP_MGF1P):
|
||||
$this->cryptParams['library'] = 'openssl';
|
||||
$this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING;
|
||||
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p';
|
||||
$this->cryptParams['hash'] = null;
|
||||
if (is_array($params) && ! empty($params['type'])) {
|
||||
if ($params['type'] == 'public' || $params['type'] == 'private') {
|
||||
$this->cryptParams['type'] = $params['type'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
|
||||
case (self::RSA_SHA1):
|
||||
$this->cryptParams['library'] = 'openssl';
|
||||
$this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
|
||||
$this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
|
||||
if (is_array($params) && ! empty($params['type'])) {
|
||||
if ($params['type'] == 'public' || $params['type'] == 'private') {
|
||||
$this->cryptParams['type'] = $params['type'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
|
||||
case (self::RSA_SHA256):
|
||||
$this->cryptParams['library'] = 'openssl';
|
||||
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
|
||||
$this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
|
||||
$this->cryptParams['digest'] = 'SHA256';
|
||||
if (is_array($params) && ! empty($params['type'])) {
|
||||
if ($params['type'] == 'public' || $params['type'] == 'private') {
|
||||
$this->cryptParams['type'] = $params['type'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
|
||||
case (self::RSA_SHA384):
|
||||
$this->cryptParams['library'] = 'openssl';
|
||||
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384';
|
||||
$this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
|
||||
$this->cryptParams['digest'] = 'SHA384';
|
||||
if (is_array($params) && ! empty($params['type'])) {
|
||||
if ($params['type'] == 'public' || $params['type'] == 'private') {
|
||||
$this->cryptParams['type'] = $params['type'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
|
||||
case (self::RSA_SHA512):
|
||||
$this->cryptParams['library'] = 'openssl';
|
||||
$this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512';
|
||||
$this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
|
||||
$this->cryptParams['digest'] = 'SHA512';
|
||||
if (is_array($params) && ! empty($params['type'])) {
|
||||
if ($params['type'] == 'public' || $params['type'] == 'private') {
|
||||
$this->cryptParams['type'] = $params['type'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new Exception('Certificate "type" (private/public) must be passed via parameters');
|
||||
case (self::HMAC_SHA1):
|
||||
$this->cryptParams['library'] = $type;
|
||||
$this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1';
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Invalid Key Type');
|
||||
}
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the key size for the symmetric encryption algorithm..
|
||||
*
|
||||
* If the key size is unknown, or this isn't a symmetric encryption algorithm,
|
||||
* null is returned.
|
||||
*
|
||||
* @return int|null The number of bytes in the key.
|
||||
*/
|
||||
public function getSymmetricKeySize()
|
||||
{
|
||||
if (! isset($this->cryptParams['keysize'])) {
|
||||
return null;
|
||||
}
|
||||
return $this->cryptParams['keysize'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a session key using the openssl-extension.
|
||||
* In case of using DES3-CBC the key is checked for a proper parity bits set.
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function generateSessionKey()
|
||||
{
|
||||
if (!isset($this->cryptParams['keysize'])) {
|
||||
throw new Exception('Unknown key size for type "' . $this->type . '".');
|
||||
}
|
||||
$keysize = $this->cryptParams['keysize'];
|
||||
|
||||
$key = openssl_random_pseudo_bytes($keysize);
|
||||
|
||||
if ($this->type === self::TRIPLEDES_CBC) {
|
||||
/* Make sure that the generated key has the proper parity bits set.
|
||||
* Mcrypt doesn't care about the parity bits, but others may care.
|
||||
*/
|
||||
for ($i = 0; $i < strlen($key); $i++) {
|
||||
$byte = ord($key[$i]) & 0xfe;
|
||||
$parity = 1;
|
||||
for ($j = 1; $j < 8; $j++) {
|
||||
$parity ^= ($byte >> $j) & 1;
|
||||
}
|
||||
$byte |= $parity;
|
||||
$key[$i] = chr($byte);
|
||||
}
|
||||
}
|
||||
|
||||
$this->key = $key;
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw thumbprint of a certificate
|
||||
*
|
||||
* @param string $cert
|
||||
* @return null|string
|
||||
*/
|
||||
public static function getRawThumbprint($cert)
|
||||
{
|
||||
|
||||
$arCert = explode("\n", $cert);
|
||||
$data = '';
|
||||
$inData = false;
|
||||
|
||||
foreach ($arCert AS $curData) {
|
||||
if (! $inData) {
|
||||
if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) {
|
||||
$inData = true;
|
||||
}
|
||||
} else {
|
||||
if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) {
|
||||
break;
|
||||
}
|
||||
$data .= trim($curData);
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($data)) {
|
||||
return strtolower(sha1(base64_decode($data)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the given key, or - with isFile set true - the key from the keyfile.
|
||||
*
|
||||
* @param string $key
|
||||
* @param bool $isFile
|
||||
* @param bool $isCert
|
||||
* @throws Exception
|
||||
*/
|
||||
public function loadKey($key, $isFile=false, $isCert = false)
|
||||
{
|
||||
if ($isFile) {
|
||||
$this->key = file_get_contents($key);
|
||||
} else {
|
||||
$this->key = $key;
|
||||
}
|
||||
if ($isCert) {
|
||||
$this->key = openssl_x509_read($this->key);
|
||||
openssl_x509_export($this->key, $str_cert);
|
||||
$this->x509Certificate = $str_cert;
|
||||
$this->key = $str_cert;
|
||||
} else {
|
||||
$this->x509Certificate = null;
|
||||
}
|
||||
if ($this->cryptParams['library'] == 'openssl') {
|
||||
switch ($this->cryptParams['type']) {
|
||||
case 'public':
|
||||
if ($isCert) {
|
||||
/* Load the thumbprint if this is an X509 certificate. */
|
||||
$this->X509Thumbprint = self::getRawThumbprint($this->key);
|
||||
}
|
||||
$this->key = openssl_get_publickey($this->key);
|
||||
if (! $this->key) {
|
||||
throw new Exception('Unable to extract public key');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'private':
|
||||
$this->key = openssl_get_privatekey($this->key, $this->passphrase);
|
||||
break;
|
||||
|
||||
case'symmetric':
|
||||
if (strlen($this->key) < $this->cryptParams['keysize']) {
|
||||
throw new Exception('Key must contain at least 25 characters for this cipher');
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception('Unknown type');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ISO 10126 Padding
|
||||
*
|
||||
* @param string $data
|
||||
* @param integer $blockSize
|
||||
* @throws Exception
|
||||
* @return string
|
||||
*/
|
||||
private function padISO10126($data, $blockSize)
|
||||
{
|
||||
if ($blockSize > 256) {
|
||||
throw new Exception('Block size higher than 256 not allowed');
|
||||
}
|
||||
$padChr = $blockSize - (strlen($data) % $blockSize);
|
||||
$pattern = chr($padChr);
|
||||
return $data . str_repeat($pattern, $padChr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove ISO 10126 Padding
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
private function unpadISO10126($data)
|
||||
{
|
||||
$padChr = substr($data, -1);
|
||||
$padLen = ord($padChr);
|
||||
return substr($data, 0, -$padLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts the given data (string) using the openssl-extension
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
private function encryptSymmetric($data)
|
||||
{
|
||||
$this->iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->cryptParams['cipher']));
|
||||
$authTag = null;
|
||||
if(in_array($this->cryptParams['cipher'], ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'])) {
|
||||
if (version_compare(PHP_VERSION, '7.1.0') < 0) {
|
||||
throw new Exception('PHP 7.1.0 is required to use AES GCM algorithms');
|
||||
}
|
||||
$authTag = openssl_random_pseudo_bytes(self::AUTHTAG_LENGTH);
|
||||
$encrypted = openssl_encrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA, $this->iv, $authTag);
|
||||
} else {
|
||||
$data = $this->padISO10126($data, $this->cryptParams['blocksize']);
|
||||
$encrypted = openssl_encrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv);
|
||||
}
|
||||
|
||||
if (false === $encrypted) {
|
||||
throw new Exception('Failure encrypting Data (openssl symmetric) - ' . openssl_error_string());
|
||||
}
|
||||
return $this->iv . $encrypted . $authTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the given data (string) using the openssl-extension
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
*/
|
||||
private function decryptSymmetric($data)
|
||||
{
|
||||
$iv_length = openssl_cipher_iv_length($this->cryptParams['cipher']);
|
||||
$this->iv = substr($data, 0, $iv_length);
|
||||
$data = substr($data, $iv_length);
|
||||
$authTag = null;
|
||||
if(in_array($this->cryptParams['cipher'], ['aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm'])) {
|
||||
if (version_compare(PHP_VERSION, '7.1.0') < 0) {
|
||||
throw new Exception('PHP 7.1.0 is required to use AES GCM algorithms');
|
||||
}
|
||||
// obtain and remove the authentication tag
|
||||
$offset = 0 - self::AUTHTAG_LENGTH;
|
||||
$authTag = substr($data, $offset);
|
||||
$data = substr($data, 0, $offset);
|
||||
$decrypted = openssl_decrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA, $this->iv, $authTag);
|
||||
} else {
|
||||
$decrypted = openssl_decrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv);
|
||||
}
|
||||
|
||||
if (false === $decrypted) {
|
||||
throw new Exception('Failure decrypting Data (openssl symmetric) - ' . openssl_error_string());
|
||||
}
|
||||
return null !== $authTag ? $decrypted : $this->unpadISO10126($decrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts the given public data (string) using the openssl-extension
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private function encryptPublic($data)
|
||||
{
|
||||
if (! openssl_public_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) {
|
||||
throw new Exception('Failure encrypting Data (openssl public) - ' . openssl_error_string());
|
||||
}
|
||||
return $encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the given public data (string) using the openssl-extension
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private function decryptPublic($data)
|
||||
{
|
||||
if (! openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
|
||||
throw new Exception('Failure decrypting Data (openssl public) - ' . openssl_error_string());
|
||||
}
|
||||
return $decrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts the given private data (string) using the openssl-extension
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private function encryptPrivate($data)
|
||||
{
|
||||
if (! openssl_private_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) {
|
||||
throw new Exception('Failure encrypting Data (openssl private) - ' . openssl_error_string());
|
||||
}
|
||||
return $encrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the given private data (string) using the openssl-extension
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private function decryptPrivate($data)
|
||||
{
|
||||
if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) {
|
||||
throw new Exception('Failure decrypting Data (openssl private) - ' . openssl_error_string());
|
||||
}
|
||||
return $decrypted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the given data (string) using the openssl-extension
|
||||
*
|
||||
* @param string $data
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private function signOpenSSL($data)
|
||||
{
|
||||
$algo = OPENSSL_ALGO_SHA1;
|
||||
if (! empty($this->cryptParams['digest'])) {
|
||||
$algo = $this->cryptParams['digest'];
|
||||
}
|
||||
if (! openssl_sign($data, $signature, $this->key, $algo)) {
|
||||
throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo);
|
||||
}
|
||||
return $signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the given data (string) belonging to the given signature using the openssl-extension
|
||||
*
|
||||
* Returns:
|
||||
* 1 on succesful signature verification,
|
||||
* 0 when signature verification failed,
|
||||
* -1 if an error occurred during processing.
|
||||
*
|
||||
* NOTE: be very careful when checking the return value, because in PHP,
|
||||
* -1 will be cast to True when in boolean context. So always check the
|
||||
* return value in a strictly typed way, e.g. "$obj->verify(...) === 1".
|
||||
*
|
||||
* @param string $data
|
||||
* @param string $signature
|
||||
* @return int
|
||||
*/
|
||||
private function verifyOpenSSL($data, $signature)
|
||||
{
|
||||
$algo = OPENSSL_ALGO_SHA1;
|
||||
if (! empty($this->cryptParams['digest'])) {
|
||||
$algo = $this->cryptParams['digest'];
|
||||
}
|
||||
return openssl_verify($data, $signature, $this->key, $algo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor.
|
||||
*
|
||||
* @param string $data
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function encryptData($data)
|
||||
{
|
||||
if ($this->cryptParams['library'] === 'openssl') {
|
||||
switch ($this->cryptParams['type']) {
|
||||
case 'symmetric':
|
||||
return $this->encryptSymmetric($data);
|
||||
case 'public':
|
||||
return $this->encryptPublic($data);
|
||||
case 'private':
|
||||
return $this->encryptPrivate($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor.
|
||||
*
|
||||
* @param string $data
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function decryptData($data)
|
||||
{
|
||||
if ($this->cryptParams['library'] === 'openssl') {
|
||||
switch ($this->cryptParams['type']) {
|
||||
case 'symmetric':
|
||||
return $this->decryptSymmetric($data);
|
||||
case 'public':
|
||||
return $this->decryptPublic($data);
|
||||
case 'private':
|
||||
return $this->decryptPrivate($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs the data (string) using the extension assigned to the type in the constructor.
|
||||
*
|
||||
* @param string $data
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function signData($data)
|
||||
{
|
||||
switch ($this->cryptParams['library']) {
|
||||
case 'openssl':
|
||||
return $this->signOpenSSL($data);
|
||||
case (self::HMAC_SHA1):
|
||||
return hash_hmac("sha1", $data, $this->key, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the data (string) against the given signature using the extension assigned to the type in the constructor.
|
||||
*
|
||||
* Returns in case of openSSL:
|
||||
* 1 on succesful signature verification,
|
||||
* 0 when signature verification failed,
|
||||
* -1 if an error occurred during processing.
|
||||
*
|
||||
* NOTE: be very careful when checking the return value, because in PHP,
|
||||
* -1 will be cast to True when in boolean context. So always check the
|
||||
* return value in a strictly typed way, e.g. "$obj->verify(...) === 1".
|
||||
*
|
||||
* @param string $data
|
||||
* @param string $signature
|
||||
* @return bool|int
|
||||
*/
|
||||
public function verifySignature($data, $signature)
|
||||
{
|
||||
switch ($this->cryptParams['library']) {
|
||||
case 'openssl':
|
||||
return $this->verifyOpenSSL($data, $signature);
|
||||
case (self::HMAC_SHA1):
|
||||
$expectedSignature = hash_hmac("sha1", $data, $this->key, true);
|
||||
return strcmp($signature, $expectedSignature) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @see getAlgorithm()
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAlgorith()
|
||||
{
|
||||
return $this->getAlgorithm();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAlgorithm()
|
||||
{
|
||||
return $this->cryptParams['method'];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param int $type
|
||||
* @param string $string
|
||||
* @return null|string
|
||||
*/
|
||||
public static function makeAsnSegment($type, $string)
|
||||
{
|
||||
switch ($type) {
|
||||
case 0x02:
|
||||
if (ord($string) > 0x7f)
|
||||
$string = chr(0).$string;
|
||||
break;
|
||||
case 0x03:
|
||||
$string = chr(0).$string;
|
||||
break;
|
||||
}
|
||||
|
||||
$length = strlen($string);
|
||||
|
||||
if ($length < 128) {
|
||||
$output = sprintf("%c%c%s", $type, $length, $string);
|
||||
} else if ($length < 0x0100) {
|
||||
$output = sprintf("%c%c%c%s", $type, 0x81, $length, $string);
|
||||
} else if ($length < 0x010000) {
|
||||
$output = sprintf("%c%c%c%c%s", $type, 0x82, $length / 0x0100, $length % 0x0100, $string);
|
||||
} else {
|
||||
$output = null;
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Hint: Modulus and Exponent must already be base64 decoded
|
||||
* @param string $modulus
|
||||
* @param string $exponent
|
||||
* @return string
|
||||
*/
|
||||
public static function convertRSA($modulus, $exponent)
|
||||
{
|
||||
/* make an ASN publicKeyInfo */
|
||||
$exponentEncoding = self::makeAsnSegment(0x02, $exponent);
|
||||
$modulusEncoding = self::makeAsnSegment(0x02, $modulus);
|
||||
$sequenceEncoding = self::makeAsnSegment(0x30, $modulusEncoding.$exponentEncoding);
|
||||
$bitstringEncoding = self::makeAsnSegment(0x03, $sequenceEncoding);
|
||||
$rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500");
|
||||
$publicKeyInfo = self::makeAsnSegment(0x30, $rsaAlgorithmIdentifier.$bitstringEncoding);
|
||||
|
||||
/* encode the publicKeyInfo in base64 and add PEM brackets */
|
||||
$publicKeyInfoBase64 = base64_encode($publicKeyInfo);
|
||||
$encoding = "-----BEGIN PUBLIC KEY-----\n";
|
||||
$offset = 0;
|
||||
while ($segment = substr($publicKeyInfoBase64, $offset, 64)) {
|
||||
$encoding = $encoding.$segment."\n";
|
||||
$offset += 64;
|
||||
}
|
||||
return $encoding."-----END PUBLIC KEY-----\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $parent
|
||||
*/
|
||||
public function serializeKey($parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the X509 certificate this key represents.
|
||||
*
|
||||
* Will return the X509 certificate in PEM-format if this key represents
|
||||
* an X509 certificate.
|
||||
*
|
||||
* @return string The X509 certificate or null if this key doesn't represent an X509-certificate.
|
||||
*/
|
||||
public function getX509Certificate()
|
||||
{
|
||||
return $this->x509Certificate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the thumbprint of this X509 certificate.
|
||||
*
|
||||
* Returns:
|
||||
* The thumbprint as a lowercase 40-character hexadecimal number, or null
|
||||
* if this isn't a X509 certificate.
|
||||
*
|
||||
* @return string Lowercase 40-character hexadecimal number of thumbprint
|
||||
*/
|
||||
public function getX509Thumbprint()
|
||||
{
|
||||
return $this->X509Thumbprint;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create key from an EncryptedKey-element.
|
||||
*
|
||||
* @param DOMElement $element The EncryptedKey-element.
|
||||
* @return MoXMLSecurityKey The new key.
|
||||
* @throws Exception
|
||||
*
|
||||
*/
|
||||
public static function fromEncryptedKeyElement(DOMElement $element)
|
||||
{
|
||||
|
||||
$objenc = new MoXMLSecEnc();
|
||||
$objenc->setNode($element);
|
||||
if (! $objKey = $objenc->locateKey()) {
|
||||
throw new Exception("Unable to locate algorithm for this Encrypted Key");
|
||||
}
|
||||
$objKey->isEncrypted = true;
|
||||
$objKey->encryptedCtx = $objenc;
|
||||
MoXMLSecEnc::staticLocateKeyInfo($objKey, $element);
|
||||
return $objKey;
|
||||
}
|
||||
|
||||
}
|
||||
44
core/lib/saml2/SAML2Core/Utils/MoXPath.php
Normal file
44
core/lib/saml2/SAML2Core/Utils/MoXPath.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace RobRichards\XMLSecLibs\Utils;
|
||||
|
||||
class MoXPath
|
||||
{
|
||||
const ALPHANUMERIC = '\w\d';
|
||||
const NUMERIC = '\d';
|
||||
const LETTERS = '\w';
|
||||
const EXTENDED_ALPHANUMERIC = '\w\d\s\-_:\.';
|
||||
|
||||
const SINGLE_QUOTE = '\'';
|
||||
const DOUBLE_QUOTE = '"';
|
||||
const ALL_QUOTES = '[\'"]';
|
||||
|
||||
|
||||
/**
|
||||
* Filter an attribute value for save inclusion in an XPath query.
|
||||
*
|
||||
* @param string $value The value to filter.
|
||||
* @param string $quotes The quotes used to delimit the value in the XPath query.
|
||||
*
|
||||
* @return string The filtered attribute value.
|
||||
*/
|
||||
public static function filterAttrValue($value, $quotes = self::ALL_QUOTES)
|
||||
{
|
||||
return preg_replace('#'.$quotes.'#', '', $value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter an attribute name for save inclusion in an XPath query.
|
||||
*
|
||||
* @param string $name The attribute name to filter.
|
||||
* @param mixed $allow The set of characters to allow. Can be one of the constants provided by this class, or a
|
||||
* custom regex excluding the '#' character (used as delimiter).
|
||||
*
|
||||
* @return string The filtered attribute name.
|
||||
*/
|
||||
public static function filterAttrName($name, $allow = self::EXTENDED_ALPHANUMERIC)
|
||||
{
|
||||
return preg_replace('#[^'.$allow.']#', '', $name);
|
||||
}
|
||||
}
|
||||
830
core/lib/saml2/Utilities.php
Normal file
830
core/lib/saml2/Utilities.php
Normal file
@@ -0,0 +1,830 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of miniOrange SAML plugin.
|
||||
*
|
||||
* miniOrange SAML plugin is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* miniOrange SAML plugin is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with miniOrange SAML plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
include_once 'xmlseclibs.php';
|
||||
use \RobRichards\XMLSecLibs\MoXMLSecurityKey;
|
||||
use \RobRichards\XMLSecLibs\MoXMLSecurityDSig;
|
||||
use \RobRichards\XMLSecLibs\MoXMLSecEnc;
|
||||
|
||||
class Utilities {
|
||||
|
||||
public static function generateID() {
|
||||
return '_' . self::stringToHex(self::generateRandomBytes(21));
|
||||
}
|
||||
|
||||
public static function stringToHex($bytes) {
|
||||
$ret = '';
|
||||
for($i = 0; $i < strlen($bytes); $i++) {
|
||||
$ret .= sprintf('%02x', ord($bytes[$i]));
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public static function generateRandomBytes($length, $fallback = TRUE) {
|
||||
|
||||
return openssl_random_pseudo_bytes($length);
|
||||
}
|
||||
|
||||
public static function createAuthnRequest($acsUrl, $issuer, $force_authn = 'false') {
|
||||
$requestXmlStr = '<?xml version="1.0" encoding="UTF-8"?>' .
|
||||
'<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="' . self::generateID() .
|
||||
'" Version="2.0" IssueInstant="' . self::generateTimestamp() . '"';
|
||||
if( $force_authn == 'true') {
|
||||
$requestXmlStr .= ' ForceAuthn="true"';
|
||||
}
|
||||
$requestXmlStr .= ' ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="' . $acsUrl .
|
||||
'" ><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">' . $issuer . '</saml:Issuer></samlp:AuthnRequest>';
|
||||
$deflatedStr = gzdeflate($requestXmlStr);
|
||||
$base64EncodedStr = base64_encode($deflatedStr);
|
||||
$urlEncoded = urlencode($base64EncodedStr);
|
||||
update_option('MO_SAML_REQUEST',$base64EncodedStr);
|
||||
|
||||
return $urlEncoded;
|
||||
}
|
||||
|
||||
public static function createSAMLRequest($acsUrl, $issuer, $destination, $force_authn = 'false') {
|
||||
|
||||
$requestXmlStr = '<?xml version="1.0" encoding="UTF-8"?>' .
|
||||
'<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="' . self::generateID() .
|
||||
'" Version="2.0" IssueInstant="' . self::generateTimestamp() . '"';
|
||||
if( $force_authn == 'true') {
|
||||
$requestXmlStr .= ' ForceAuthn="true"';
|
||||
}
|
||||
$requestXmlStr .= ' ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="' . $acsUrl .
|
||||
'" Destination="' . htmlspecialchars($destination) . '"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">' . $issuer . '</saml:Issuer><samlp:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
|
||||
/></samlp:AuthnRequest>';
|
||||
$samlRequest = base64_encode($requestXmlStr);
|
||||
update_option('MO_SAML_REQUEST',$samlRequest);
|
||||
return $requestXmlStr;
|
||||
}
|
||||
|
||||
public static function generateTimestamp($instant = NULL) {
|
||||
if($instant === NULL) {
|
||||
$instant = time();
|
||||
}
|
||||
return gmdate('Y-m-d\TH:i:s\Z', $instant);
|
||||
}
|
||||
|
||||
public static function xpQuery(DOMNode $node, $query)
|
||||
{
|
||||
|
||||
static $xpCache = NULL;
|
||||
|
||||
if ($node instanceof DOMDocument) {
|
||||
$doc = $node;
|
||||
} else {
|
||||
$doc = $node->ownerDocument;
|
||||
}
|
||||
|
||||
if ($xpCache === NULL || !$xpCache->document->isSameNode($doc)) {
|
||||
$xpCache = new DOMXPath($doc);
|
||||
$xpCache->registerNamespace('soap-env', 'http://schemas.xmlsoap.org/soap/envelope/');
|
||||
$xpCache->registerNamespace('saml_protocol', 'urn:oasis:names:tc:SAML:2.0:protocol');
|
||||
$xpCache->registerNamespace('saml_assertion', 'urn:oasis:names:tc:SAML:2.0:assertion');
|
||||
$xpCache->registerNamespace('saml_metadata', 'urn:oasis:names:tc:SAML:2.0:metadata');
|
||||
$xpCache->registerNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#');
|
||||
$xpCache->registerNamespace('xenc', 'http://www.w3.org/2001/04/xmlenc#');
|
||||
}
|
||||
|
||||
$results = $xpCache->query($query, $node);
|
||||
$ret = array();
|
||||
for ($i = 0; $i < $results->length; $i++) {
|
||||
$ret[$i] = $results->item($i);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public static function parseNameId(DOMElement $xml)
|
||||
{
|
||||
$ret = array('Value' => trim($xml->textContent));
|
||||
|
||||
foreach (array('NameQualifier', 'SPNameQualifier', 'Format') as $attr) {
|
||||
if ($xml->hasAttribute($attr)) {
|
||||
$ret[$attr] = $xml->getAttribute($attr);
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public static function xsDateTimeToTimestamp($time)
|
||||
{
|
||||
$matches = array();
|
||||
|
||||
// We use a very strict regex to parse the timestamp.
|
||||
$regex = '/^(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)T(\\d\\d):(\\d\\d):(\\d\\d)(?:\\.\\d+)?Z$/D';
|
||||
if (preg_match($regex, $time, $matches) == 0) {
|
||||
echo sprintf("Invalid SAML2 timestamp passed to xsDateTimeToTimestamp: ". htmlspecialchars($time));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Extract the different components of the time from the matches in the regex.
|
||||
// intval will ignore leading zeroes in the string.
|
||||
$year = intval($matches[1]);
|
||||
$month = intval($matches[2]);
|
||||
$day = intval($matches[3]);
|
||||
$hour = intval($matches[4]);
|
||||
$minute = intval($matches[5]);
|
||||
$second = intval($matches[6]);
|
||||
|
||||
// We use gmmktime because the timestamp will always be given
|
||||
//in UTC.
|
||||
$ts = gmmktime($hour, $minute, $second, $month, $day, $year);
|
||||
|
||||
return $ts;
|
||||
}
|
||||
|
||||
public static function extractStrings(DOMElement $parent, $namespaceURI, $localName)
|
||||
{
|
||||
|
||||
|
||||
|
||||
$ret = array();
|
||||
for ($node = $parent->firstChild; $node !== NULL; $node = $node->nextSibling) {
|
||||
if ($node->namespaceURI !== $namespaceURI || $node->localName !== $localName) {
|
||||
continue;
|
||||
}
|
||||
$ret[] = trim($node->textContent);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public static function validateElement(DOMElement $root)
|
||||
{
|
||||
//$data = $root->ownerDocument->saveXML($root);
|
||||
//echo htmlspecialchars($data);
|
||||
|
||||
/* Create an XML security object. */
|
||||
$objXMLSecDSig = new MoXMLSecurityDSig();
|
||||
|
||||
/* Both SAML messages and SAML assertions use the 'ID' attribute. */
|
||||
$objXMLSecDSig->idKeys[] = 'ID';
|
||||
|
||||
|
||||
/* Locate the XMLDSig Signature element to be used. */
|
||||
$signatureElement = self::xpQuery($root, './ds:Signature');
|
||||
//print_r($signatureElement);
|
||||
|
||||
if (count($signatureElement) === 0) {
|
||||
/* We don't have a signature element to validate. */
|
||||
return FALSE;
|
||||
} elseif (count($signatureElement) > 1) {
|
||||
echo sprintf("XMLSec: more than one signature element in root.");
|
||||
exit;
|
||||
}/* elseif ((in_array('Response', $signatureElement) && $ocurrence['Response'] > 1) ||
|
||||
(in_array('Assertion', $signatureElement) && $ocurrence['Assertion'] > 1) ||
|
||||
!in_array('Response', $signatureElement) && !in_array('Assertion', $signatureElement)
|
||||
) {
|
||||
return false;
|
||||
} */
|
||||
|
||||
$signatureElement = $signatureElement[0];
|
||||
$objXMLSecDSig->sigNode = $signatureElement;
|
||||
|
||||
/* Canonicalize the XMLDSig SignedInfo element in the message. */
|
||||
$objXMLSecDSig->canonicalizeSignedInfo();
|
||||
|
||||
/* Validate referenced xml nodes. */
|
||||
if (!$objXMLSecDSig->validateReference()) {
|
||||
echo sprintf("XMLSec: digest validation failed");
|
||||
exit;
|
||||
}
|
||||
|
||||
/* Check that $root is one of the signed nodes. */
|
||||
$rootSigned = FALSE;
|
||||
/** @var DOMNode $signedNode */
|
||||
foreach ($objXMLSecDSig->getValidatedNodes() as $signedNode) {
|
||||
if ($signedNode->isSameNode($root)) {
|
||||
$rootSigned = TRUE;
|
||||
break;
|
||||
} elseif ($root->parentNode instanceof DOMDocument && $signedNode->isSameNode($root->ownerDocument)) {
|
||||
/* $root is the root element of a signed document. */
|
||||
$rootSigned = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$rootSigned) {
|
||||
echo sprintf("XMLSec: The root element is not signed.");
|
||||
exit;
|
||||
}
|
||||
|
||||
/* Now we extract all available X509 certificates in the signature element. */
|
||||
$certificates = array();
|
||||
foreach (self::xpQuery($signatureElement, './ds:KeyInfo/ds:X509Data/ds:X509Certificate') as $certNode) {
|
||||
$certData = trim($certNode->textContent);
|
||||
$certData = str_replace(array("\r", "\n", "\t", ' '), '', $certData);
|
||||
$certificates[] = $certData;
|
||||
//echo "CertDate: " . $certData . "<br />";
|
||||
}
|
||||
|
||||
$ret = array(
|
||||
'Signature' => $objXMLSecDSig,
|
||||
'Certificates' => $certificates,
|
||||
);
|
||||
|
||||
//echo "Signature validated";
|
||||
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function validateSignature(array $info, MoXMLSecurityKey $key)
|
||||
{
|
||||
|
||||
|
||||
/** @var MoXMLSecurityDSig $objXMLSecDSig */
|
||||
$objXMLSecDSig = $info['Signature'];
|
||||
|
||||
$sigMethod = self::xpQuery($objXMLSecDSig->sigNode, './ds:SignedInfo/ds:SignatureMethod');
|
||||
if (empty($sigMethod)) {
|
||||
echo sprintf('Missing SignatureMethod element');
|
||||
exit();
|
||||
}
|
||||
$sigMethod = $sigMethod[0];
|
||||
if (!$sigMethod->hasAttribute('Algorithm')) {
|
||||
echo sprintf('Missing Algorithm-attribute on SignatureMethod element.');
|
||||
exit;
|
||||
}
|
||||
$algo = $sigMethod->getAttribute('Algorithm');
|
||||
|
||||
if ($key->type === MoXMLSecurityKey::RSA_SHA1 && $algo !== $key->type) {
|
||||
$key = self::castKey($key, $algo);
|
||||
}
|
||||
|
||||
/* Check the signature. */
|
||||
if (! $objXMLSecDSig->verify($key)) {
|
||||
echo sprintf('Unable to validate Signature');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
public static function castKey(MoXMLSecurityKey $key, $algorithm, $type = 'public')
|
||||
{
|
||||
|
||||
// do nothing if algorithm is already the type of the key
|
||||
if ($key->type === $algorithm) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$keyInfo = openssl_pkey_get_details($key->key);
|
||||
if ($keyInfo === FALSE) {
|
||||
echo sprintf('Unable to get key details from XMLSecurityKey.');
|
||||
exit;
|
||||
}
|
||||
if (!isset($keyInfo['key'])) {
|
||||
echo sprintf('Missing key in public key details.');
|
||||
exit;
|
||||
}
|
||||
|
||||
$newKey = new MoXMLSecurityKey($algorithm, array('type'=>$type));
|
||||
$newKey->loadKey($keyInfo['key']);
|
||||
|
||||
return $newKey;
|
||||
}
|
||||
|
||||
public static function processResponse($currentURL, $certFingerprint, $signatureData,
|
||||
SAML2_Response $response, $certNumber,$relayState) {
|
||||
|
||||
$assertion = current($response->getAssertions());
|
||||
|
||||
$notBefore = $assertion->getNotBefore();
|
||||
if ($notBefore !== NULL && $notBefore > time() + 60) {
|
||||
error_log('Received an assertion that is valid in the future. Check clock synchronization on IdP and SP.');
|
||||
return false;
|
||||
}
|
||||
|
||||
$notOnOrAfter = $assertion->getNotOnOrAfter();
|
||||
if ($notOnOrAfter !== NULL && $notOnOrAfter <= time() - 60) {
|
||||
error_log('Received an assertion that has expired. Check clock synchronization on IdP and SP.');
|
||||
return false;
|
||||
}
|
||||
|
||||
$sessionNotOnOrAfter = $assertion->getSessionNotOnOrAfter();
|
||||
if ($sessionNotOnOrAfter !== NULL && $sessionNotOnOrAfter <= time() - 60) {
|
||||
error_log('Received an assertion with a session that has expired. Check clock synchronization on IdP and SP.');
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Validate Response-element destination. */
|
||||
$msgDestination = $response->getDestination();
|
||||
if(substr($msgDestination, -1) == '/') {
|
||||
$msgDestination = substr($msgDestination, 0, -1);
|
||||
}
|
||||
if(substr($currentURL, -1) == '/') {
|
||||
$currentURL = substr($currentURL, 0, -1);
|
||||
}
|
||||
|
||||
if ($msgDestination !== NULL && $msgDestination !== $currentURL) {
|
||||
echo sprintf('Destination in response doesn\'t match the current URL. Destination is "' .
|
||||
htmlspecialchars($msgDestination) . '", current URL is "' . htmlspecialchars($currentURL) . '".');
|
||||
exit;
|
||||
}
|
||||
|
||||
$responseSigned = self::checkSign($certFingerprint, $signatureData, $certNumber,$relayState);
|
||||
|
||||
if (!$responseSigned) {
|
||||
error_log('SAML: Responses is not signed');
|
||||
}
|
||||
|
||||
/* Returning boolean $responseSigned */
|
||||
return $responseSigned;
|
||||
}
|
||||
|
||||
|
||||
public static function checkSign($certFingerprint, $signatureData, $certNumber, $relayState) {
|
||||
$certificates = $signatureData['Certificates'];
|
||||
error_log('SAML: certificate count = '.count($certificates));
|
||||
if (count($certificates) === 0) {
|
||||
$storedCerts = maybe_unserialize(get_option('saml_x509_certificate'));
|
||||
$pemCert = $storedCerts[$certNumber];
|
||||
}else{
|
||||
$fpArray = array();
|
||||
$fpArray[] = $certFingerprint;
|
||||
$pemCert = self::findCertificate($fpArray, $certificates, $relayState);
|
||||
if($pemCert==false)
|
||||
return false;
|
||||
}
|
||||
|
||||
$lastException = NULL;
|
||||
|
||||
$key = new MoXMLSecurityKey(MoXMLSecurityKey::RSA_SHA1, array('type'=>'public'));
|
||||
$key->loadKey($pemCert);
|
||||
|
||||
try {
|
||||
/*
|
||||
* Make sure that we have a valid signature
|
||||
*/
|
||||
self::validateSignature($signatureData, $key);
|
||||
return TRUE;
|
||||
} catch (Exception $e) {
|
||||
$lastException = $e;
|
||||
}
|
||||
|
||||
|
||||
/* We were unable to validate the signature with any of our keys. */
|
||||
if ($lastException !== NULL) {
|
||||
throw $lastException;
|
||||
} else {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static function validateIssuerAndAudience($samlResponse, $spEntityId, $issuerToValidateAgainst, $relayState) {
|
||||
$issuer = current($samlResponse->getAssertions())->getIssuer();
|
||||
$assertion = current($samlResponse->getAssertions());
|
||||
$audiences = $assertion->getValidAudiences();
|
||||
if(strcmp($issuerToValidateAgainst, $issuer) === 0) {
|
||||
if(!empty($audiences)) {
|
||||
if(in_array($spEntityId, $audiences, TRUE)) {
|
||||
return TRUE;
|
||||
} else {
|
||||
if($relayState=='testValidate'){
|
||||
$Error_message=mo_saml_options_error_constants::Error_invalid_audience;
|
||||
$Cause_message = mo_saml_options_error_constants::Cause_invalid_audience;
|
||||
echo '<div style="font-family:Calibri;padding:0 3%;">';
|
||||
echo '<div style="color: #a94442;background-color: #f2dede;padding: 15px;margin-bottom: 20px;text-align:center;border:1px solid #E6B3B2;font-size:18pt;"> ' . __('ERROR','miniorange-saml-20-single-sign-on') . '</div>
|
||||
<div style="color: #a94442;font-size:14pt; margin-bottom:20px;"><p><strong>' . __('Error','miniorange-saml-20-single-sign-on') . ': </strong>'.$Error_message.'</p>
|
||||
|
||||
<p><strong>' . __('Possible Cause','miniorange-saml-20-single-sign-on'). ': </strong>'.$Cause_message.'</p>
|
||||
<p>' . __('Expected one of the Audiences to be','miniorange-saml-20-single-sign-on'). ': '.$spEntityId.'<p>
|
||||
</div>';
|
||||
mo_saml_download_logs($Error_message,$Cause_message);
|
||||
exit;
|
||||
}
|
||||
else
|
||||
{
|
||||
wp_die(__("We could not sign you in. Please contact your administrator",'miniorange-saml-20-single-sign-on'),"Error: Invalid Audience URI");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if($relayState=='testValidate'){
|
||||
|
||||
$Error_message=mo_saml_options_error_constants::Error_issuer_not_verfied;
|
||||
$Cause_message = mo_saml_options_error_constants::Cause_issuer_not_verfied;
|
||||
update_option('mo_saml_required_issuer',$issuer);
|
||||
echo '<div style="font-family:Calibri;padding:0 3%;">';
|
||||
echo '<div style="color: #a94442;background-color: #f2dede;padding: 15px;margin-bottom: 20px;text-align:center;border:1px solid #E6B3B2;font-size:18pt;">' . __('ERROR','miniorange-saml-20-single-sign-on') . '</div>
|
||||
<div style="color: #a94442;font-size:14pt; margin-bottom:20px;text-align: justify"><p><strong>' . __('Error','miniorange-saml-20-single-sign-on'). ':'.$Error_message.' </strong></p>
|
||||
|
||||
<p><strong>' . __('Possible Cause','miniorange-saml-20-single-sign-on') . ':'.$Cause_message.' </strong></p>
|
||||
<div>
|
||||
<ol style="text-align: center">
|
||||
<form method="post" action="" name="mo_fix_entityid" id="mo_fix_certificate">';
|
||||
wp_nonce_field('mo_fix_entity_id');
|
||||
echo '<input type="hidden" name="option" value="mo_fix_entity_id" />
|
||||
<input type="submit" class="miniorange-button" style="width: 55%" value="' . __('Fix Issue','miniorange-saml-20-single-sign-on' ) .'">
|
||||
</form>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>';
|
||||
|
||||
mo_saml_download_logs($Error_message,$Cause_message);
|
||||
exit;
|
||||
}
|
||||
else
|
||||
{
|
||||
wp_die(__("We could not sign you in. Please contact your administrator",'miniorange-saml-20-single-sign-on'),"Error: Issuer cannot be verified");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function findCertificate(array $certFingerprints, array $certificates, $relayState) {
|
||||
|
||||
$candidates = array();
|
||||
|
||||
//foreach ($certificates as $cert) {
|
||||
$fp = strtolower(sha1(base64_decode($certificates[0])));
|
||||
if (!in_array($fp, $certFingerprints, TRUE)) {
|
||||
$candidates[] = $fp;
|
||||
return false;
|
||||
//continue;
|
||||
}
|
||||
|
||||
/* We have found a matching fingerprint. */
|
||||
$pem = "-----BEGIN CERTIFICATE-----\n" .
|
||||
chunk_split($certificates[0], 64) .
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
return $pem;
|
||||
// }
|
||||
|
||||
// if($relayState=='testValidate'){
|
||||
// $pem = "-----BEGIN CERTIFICATE-----<br>" .
|
||||
// chunk_split($cert, 64) .
|
||||
// "<br>-----END CERTIFICATE-----";
|
||||
|
||||
// echo '<div style="font-family:Calibri;padding:0 3%;">';
|
||||
// echo '<div style="color: #a94442;background-color: #f2dede;padding: 15px;margin-bottom: 20px;text-align:center;border:1px solid #E6B3B2;font-size:18pt;"> ERROR</div>
|
||||
// <div style="color: #a94442;font-size:14pt; margin-bottom:20px;"><p><strong>Error: </strong>Unable to find a certificate matching the configured fingerprint.</p>
|
||||
// <p>Please contact your administrator and report the following error:</p>
|
||||
// <p><strong>Possible Cause: </strong>Content of \'X.509 Certificate\' field in Service Provider Settings is incorrect. Please replace it with certificate given below.</p>
|
||||
// <p><strong>Certificate found in SAML Response: </strong><br><br>'.$pem.'</p>
|
||||
// </div>
|
||||
// <div style="margin:3%;display:block;text-align:center;">
|
||||
// <form action="index.php">
|
||||
// <div style="margin:3%;display:block;text-align:center;"><input style="padding:1%;width:100px;background: #0091CD none repeat scroll 0% 0%;cursor: pointer;font-size:15px;border-width: 1px;border-style: solid;border-radius: 3px;white-space: nowrap;box-sizing: border-box;border-color: #0073AA;box-shadow: 0px 1px 0px rgba(120, 200, 230, 0.6) inset;color: #FFF;"type="button" value="Done" onClick="self.close();"></div>';
|
||||
|
||||
// exit;
|
||||
// }
|
||||
// else{
|
||||
// wp_die("We could not sign you in. Please contact your administrator","Error: Invalid Certificate");
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt an encrypted element.
|
||||
*
|
||||
* This is an internal helper function.
|
||||
*
|
||||
* @param DOMElement $encryptedData The encrypted data.
|
||||
* @param MoXMLSecurityKey $inputKey The decryption key.
|
||||
* @param array &$blacklist Blacklisted decryption algorithms.
|
||||
* @return DOMElement The decrypted element.
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function doDecryptElement(DOMElement $encryptedData, MoXMLSecurityKey $inputKey, array &$blacklist)
|
||||
{
|
||||
$enc = new MoXMLSecEnc();
|
||||
$enc->setNode($encryptedData);
|
||||
|
||||
$enc->type = $encryptedData->getAttribute("Type");
|
||||
$symmetricKey = $enc->locateKey($encryptedData);
|
||||
if (!$symmetricKey) {
|
||||
echo sprintf(__('Could not locate key algorithm in encrypted data.','miniorange-saml-20-single-sign-on'));
|
||||
exit;
|
||||
}
|
||||
|
||||
$symmetricKeyInfo = $enc->locateKeyInfo($symmetricKey);
|
||||
if (!$symmetricKeyInfo) {
|
||||
echo sprintf(__('Could not locate <dsig:KeyInfo> for the encrypted key.','miniorange-saml-20-single-sign-on'));
|
||||
exit;
|
||||
}
|
||||
$inputKeyAlgo = $inputKey->getAlgorith();
|
||||
if ($symmetricKeyInfo->isEncrypted) {
|
||||
$symKeyInfoAlgo = $symmetricKeyInfo->getAlgorith();
|
||||
if (in_array($symKeyInfoAlgo, $blacklist, TRUE)) {
|
||||
echo sprintf('Algorithm disabled: ' . var_export($symKeyInfoAlgo, TRUE));
|
||||
exit;
|
||||
}
|
||||
if ($symKeyInfoAlgo === MoXMLSecurityKey::RSA_OAEP_MGF1P && $inputKeyAlgo === MoXMLSecurityKey::RSA_1_5) {
|
||||
/*
|
||||
* The RSA key formats are equal, so loading an RSA_1_5 key
|
||||
* into an RSA_OAEP_MGF1P key can be done without problems.
|
||||
* We therefore pretend that the input key is an
|
||||
* RSA_OAEP_MGF1P key.
|
||||
*/
|
||||
$inputKeyAlgo = MoXMLSecurityKey::RSA_OAEP_MGF1P;
|
||||
}
|
||||
/* Make sure that the input key format is the same as the one used to encrypt the key. */
|
||||
if ($inputKeyAlgo !== $symKeyInfoAlgo) {
|
||||
echo sprintf( 'Algorithm mismatch between input key and key used to encrypt ' .
|
||||
' the symmetric key for the message. Key was: ' .
|
||||
var_export($inputKeyAlgo, TRUE) . '; message was: ' .
|
||||
var_export($symKeyInfoAlgo, TRUE));
|
||||
exit;
|
||||
}
|
||||
/** @var MoXMLSecEnc $encKey */
|
||||
$encKey = $symmetricKeyInfo->encryptedCtx;
|
||||
$symmetricKeyInfo->key = $inputKey->key;
|
||||
$keySize = $symmetricKey->getSymmetricKeySize();
|
||||
if ($keySize === NULL) {
|
||||
/* To protect against "key oracle" attacks, we need to be able to create a
|
||||
* symmetric key, and for that we need to know the key size.
|
||||
*/
|
||||
echo sprintf('Unknown key size for encryption algorithm: ' . var_export($symmetricKey->type, TRUE));
|
||||
exit;
|
||||
}
|
||||
try {
|
||||
$key = $encKey->decryptKey($symmetricKeyInfo);
|
||||
if (strlen($key) != $keySize) {
|
||||
echo sprintf('Unexpected key size (' . strlen($key) * 8 . 'bits) for encryption algorithm: ' .
|
||||
var_export($symmetricKey->type, TRUE));
|
||||
exit;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
/* We failed to decrypt this key. Log it, and substitute a "random" key. */
|
||||
|
||||
/* Create a replacement key, so that it looks like we fail in the same way as if the key was correctly padded. */
|
||||
/* We base the symmetric key on the encrypted key and private key, so that we always behave the
|
||||
* same way for a given input key.
|
||||
*/
|
||||
$encryptedKey = $encKey->getCipherValue();
|
||||
$pkey = openssl_pkey_get_details($symmetricKeyInfo->key);
|
||||
$pkey = sha1(serialize($pkey), TRUE);
|
||||
$key = sha1($encryptedKey . $pkey, TRUE);
|
||||
/* Make sure that the key has the correct length. */
|
||||
if (strlen($key) > $keySize) {
|
||||
$key = substr($key, 0, $keySize);
|
||||
} elseif (strlen($key) < $keySize) {
|
||||
$key = str_pad($key, $keySize);
|
||||
}
|
||||
}
|
||||
$symmetricKey->loadkey($key);
|
||||
} else {
|
||||
$symKeyAlgo = $symmetricKey->getAlgorith();
|
||||
/* Make sure that the input key has the correct format. */
|
||||
if ($inputKeyAlgo !== $symKeyAlgo) {
|
||||
echo sprintf( 'Algorithm mismatch between input key and key in message. ' .
|
||||
'Key was: ' . var_export($inputKeyAlgo, TRUE) . '; message was: ' .
|
||||
var_export($symKeyAlgo, TRUE));
|
||||
exit;
|
||||
}
|
||||
$symmetricKey = $inputKey;
|
||||
}
|
||||
$algorithm = $symmetricKey->getAlgorith();
|
||||
if (in_array($algorithm, $blacklist, TRUE)) {
|
||||
echo sprintf('Algorithm disabled: ' . var_export($algorithm, TRUE));
|
||||
exit;
|
||||
}
|
||||
/** @var string $decrypted */
|
||||
$decrypted = $enc->decryptNode($symmetricKey, FALSE);
|
||||
/*
|
||||
* This is a workaround for the case where only a subset of the XML
|
||||
* tree was serialized for encryption. In that case, we may miss the
|
||||
* namespaces needed to parse the XML.
|
||||
*/
|
||||
$xml = '<root xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" '.
|
||||
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' .
|
||||
$decrypted .
|
||||
'</root>';
|
||||
$newDoc = new DOMDocument();
|
||||
if (!@$newDoc->loadXML($xml)) {
|
||||
echo sprintf('Failed to parse decrypted XML. Maybe the wrong sharedkey was used?');
|
||||
throw new Exception('Failed to parse decrypted XML. Maybe the wrong sharedkey was used?');
|
||||
}
|
||||
$decryptedElement = $newDoc->firstChild->firstChild;
|
||||
if ($decryptedElement === NULL) {
|
||||
echo sprintf('Missing encrypted element.');
|
||||
throw new Exception('Missing encrypted element.');
|
||||
}
|
||||
|
||||
if (!($decryptedElement instanceof DOMElement)) {
|
||||
echo sprintf('Decrypted element was not actually a DOMElement.');
|
||||
}
|
||||
|
||||
return $decryptedElement;
|
||||
}
|
||||
/**
|
||||
* Decrypt an encrypted element.
|
||||
*
|
||||
* @param DOMElement $encryptedData The encrypted data.
|
||||
* @param MoXMLSecurityKey $inputKey The decryption key.
|
||||
* @param array $blacklist Blacklisted decryption algorithms.
|
||||
* @return DOMElement The decrypted element.
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function decryptElement(DOMElement $encryptedData, MoXMLSecurityKey $inputKey, array $blacklist = array(), MoXMLSecurityKey $alternateKey = NULL)
|
||||
{
|
||||
try {
|
||||
return self::doDecryptElement($encryptedData, $inputKey, $blacklist);
|
||||
} catch (Exception $e) {
|
||||
//Try with alternate key
|
||||
try {
|
||||
return self::doDecryptElement($encryptedData, $alternateKey, $blacklist);
|
||||
} catch(Exception $t) {
|
||||
|
||||
}
|
||||
/*
|
||||
* Something went wrong during decryption, but for security
|
||||
* reasons we cannot tell the user what failed.
|
||||
*/
|
||||
//print_r($e->getMessage());
|
||||
echo sprintf('Failed to decrypt XML element.');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the metadata of the SP based on the settings
|
||||
*
|
||||
* @param string $sp The SP data
|
||||
* @param string $authnsign authnRequestsSigned attribute
|
||||
* @param string $wsign wantAssertionsSigned attribute
|
||||
* @param DateTime $validUntil Metadata's valid time
|
||||
* @param Timestamp $cacheDuration Duration of the cache in seconds
|
||||
* @param array $contacts Contacts info
|
||||
* @param array $organization Organization ingo
|
||||
*
|
||||
* @return string SAML Metadata XML
|
||||
*/
|
||||
public static function metadata_builder($siteUrl)
|
||||
{
|
||||
$xml = new DOMDocument();
|
||||
$url = plugins_url().'/miniorange-saml-20-single-sign-on/sp-metadata.xml';
|
||||
|
||||
$xml->load($url);
|
||||
|
||||
$xpath = new DOMXPath($xml);
|
||||
$elements = $xpath->query('//md:EntityDescriptor[@entityID="http://{path-to-your-site}/wp-content/plugins/miniorange-saml-20-single-sign-on/"]');
|
||||
|
||||
if ($elements->length >= 1) {
|
||||
$element = $elements->item(0);
|
||||
$element->setAttribute('entityID', $siteUrl.'/wp-content/plugins/miniorange-saml-20-single-sign-on/');
|
||||
}
|
||||
|
||||
$elements = $xpath->query('//md:AssertionConsumerService[@Location="http://{path-to-your-site}"]');
|
||||
if ($elements->length >= 1) {
|
||||
$element = $elements->item(0);
|
||||
$element->setAttribute('Location', $siteUrl.'/');
|
||||
}
|
||||
|
||||
//re-save
|
||||
$xml->save(plugins_url()."/miniorange-saml-20-single-sign-on/sp-metadata.xml");
|
||||
}
|
||||
|
||||
public static function get_mapped_groups($saml_params, $saml_groups)
|
||||
{
|
||||
$groups = array();
|
||||
|
||||
if (!empty($saml_groups)) {
|
||||
$saml_mapped_groups = array();
|
||||
$i=1;
|
||||
while ($i < 10) {
|
||||
$saml_mapped_groups_value = $saml_params->get('group'.$i.'_map');
|
||||
|
||||
$saml_mapped_groups[$i] = explode(';', $saml_mapped_groups_value);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($saml_groups as $saml_group) {
|
||||
if (!empty($saml_group)) {
|
||||
$i = 0;
|
||||
$found = false;
|
||||
|
||||
while ($i < 9 && !$found) {
|
||||
if (!empty($saml_mapped_groups[$i]) && in_array($saml_group, $saml_mapped_groups[$i], TRUE)) {
|
||||
$groups[] = $saml_params->get('group'.$i);
|
||||
$found = true;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($groups);
|
||||
}
|
||||
|
||||
|
||||
public static function getEncryptionAlgorithm($method){
|
||||
switch($method){
|
||||
case 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc':
|
||||
return MoXMLSecurityKey::TRIPLEDES_CBC;
|
||||
break;
|
||||
|
||||
case 'http://www.w3.org/2001/04/xmlenc#aes128-cbc':
|
||||
return MoXMLSecurityKey::AES128_CBC;
|
||||
|
||||
case 'http://www.w3.org/2001/04/xmlenc#aes192-cbc':
|
||||
return MoXMLSecurityKey::AES192_CBC;
|
||||
break;
|
||||
|
||||
case 'http://www.w3.org/2001/04/xmlenc#aes256-cbc':
|
||||
return MoXMLSecurityKey::AES256_CBC;
|
||||
break;
|
||||
|
||||
case 'http://www.w3.org/2001/04/xmlenc#rsa-1_5':
|
||||
return MoXMLSecurityKey::RSA_1_5;
|
||||
break;
|
||||
|
||||
case 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p':
|
||||
return MoXMLSecurityKey::RSA_OAEP_MGF1P;
|
||||
break;
|
||||
|
||||
case 'http://www.w3.org/2000/09/xmldsig#dsa-sha1':
|
||||
return MoXMLSecurityKey::DSA_SHA1;
|
||||
break;
|
||||
|
||||
case 'http://www.w3.org/2000/09/xmldsig#rsa-sha1':
|
||||
return MoXMLSecurityKey::RSA_SHA1;
|
||||
break;
|
||||
|
||||
case 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256':
|
||||
return MoXMLSecurityKey::RSA_SHA256;
|
||||
break;
|
||||
|
||||
case 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384':
|
||||
return MoXMLSecurityKey::RSA_SHA384;
|
||||
break;
|
||||
|
||||
case 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512':
|
||||
return MoXMLSecurityKey::RSA_SHA512;
|
||||
break;
|
||||
|
||||
default:
|
||||
echo sprintf('Invalid Encryption Method: '. htmlspecialchars($method));
|
||||
exit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static function sanitize_certificate( $certificate ) {
|
||||
$certificate = preg_replace("/[\r\n]+/", "", $certificate);
|
||||
$certificate = str_replace( "-", "", $certificate );
|
||||
$certificate = str_replace( "BEGIN CERTIFICATE", "", $certificate );
|
||||
$certificate = str_replace( "END CERTIFICATE", "", $certificate );
|
||||
$certificate = str_replace( " ", "", $certificate );
|
||||
$certificate = chunk_split($certificate, 64, "\r\n");
|
||||
$certificate = "-----BEGIN CERTIFICATE-----\r\n" . $certificate . "-----END CERTIFICATE-----";
|
||||
return $certificate;
|
||||
}
|
||||
|
||||
public static function desanitize_certificate( $certificate ) {
|
||||
$certificate = preg_replace("/[\r\n]+/", "", $certificate);
|
||||
//$certificate = str_replace( "-", "", $certificate );
|
||||
$certificate = str_replace( "-----BEGIN CERTIFICATE-----", "", $certificate );
|
||||
$certificate = str_replace( "-----END CERTIFICATE-----", "", $certificate );
|
||||
$certificate = str_replace( " ", "", $certificate );
|
||||
//$certificate = chunk_split($certificate, 64, "\r\n");
|
||||
//$certificate = "-----BEGIN CERTIFICATE-----\r\n" . $certificate . "-----END CERTIFICATE-----";
|
||||
return $certificate;
|
||||
}
|
||||
|
||||
public static function mo_saml_wp_remote_post($url, $args = array()){
|
||||
$response = wp_remote_post($url, $args);
|
||||
if(!is_wp_error($response)){
|
||||
return $response;
|
||||
} else {
|
||||
$show_message = new saml_mo_login();
|
||||
update_option('mo_saml_message', __('Unable to connect to the Internet. Please try again.','miniorange-saml-20-single-sign-on'));
|
||||
$show_message->mo_saml_show_error_message();
|
||||
}
|
||||
}
|
||||
|
||||
public static function mo_saml_wp_remote_get($url, $args = array()){
|
||||
$response = wp_remote_get($url, $args);
|
||||
if(!is_wp_error($response)){
|
||||
return $response;
|
||||
} else {
|
||||
$show_message = new saml_mo_login();
|
||||
update_option('mo_saml_message', __('Unable to connect to the Internet. Please try again.','miniorange-saml-20-single-sign-on'));
|
||||
$show_message->mo_saml_show_error_message();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
44
core/lib/saml2/encryption.php
Normal file
44
core/lib/saml2/encryption.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* @package miniOrange
|
||||
* @author miniOrange Security Software Pvt. Ltd.
|
||||
* @license GNU/GPLv3
|
||||
* @copyright Copyright 2015 miniOrange. All Rights Reserved.
|
||||
*
|
||||
*
|
||||
* This file is part of miniOrange SAML plugin.
|
||||
*/
|
||||
|
||||
class AESEncryption {
|
||||
/**
|
||||
* @param string $data - the key=value pairs separated with &
|
||||
* @return string
|
||||
*/
|
||||
public static function encrypt_data($data, $key) {
|
||||
$key = openssl_digest($key, 'sha256');
|
||||
$method = 'AES-128-ECB';
|
||||
$ivSize = openssl_cipher_iv_length($method);
|
||||
$iv = openssl_random_pseudo_bytes($ivSize);
|
||||
$strCrypt = openssl_encrypt ($data, $method, $key,OPENSSL_RAW_DATA||OPENSSL_ZERO_PADDING, $iv);
|
||||
return base64_encode($iv.$strCrypt);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $data - crypt response from Sagepay
|
||||
* @return string
|
||||
*/
|
||||
public static function decrypt_data($data, $key) {
|
||||
$strIn = base64_decode($data);
|
||||
$key = openssl_digest($key, 'sha256');
|
||||
$method = 'AES-128-ECB';
|
||||
$ivSize = openssl_cipher_iv_length($method);
|
||||
$iv = substr($strIn,0,$ivSize);
|
||||
$data = substr($strIn,$ivSize);
|
||||
$clear = openssl_decrypt ($data, $method, $key, OPENSSL_RAW_DATA||OPENSSL_ZERO_PADDING, $iv);
|
||||
|
||||
return $clear;
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
640
core/lib/saml2/mo-saml-options-enum.php
Normal file
640
core/lib/saml2/mo-saml-options-enum.php
Normal file
@@ -0,0 +1,640 @@
|
||||
<?php
|
||||
include "MoSAMLBasicEnum.php";
|
||||
class mo_saml_options_enum_sso_loginMoSAML extends MoSAMLBasicEnum {
|
||||
const Relay_state = 'mo_saml_relay_state';
|
||||
const Redirect_Idp = 'mo_saml_registered_only_access';
|
||||
const Force_authentication = 'mo_saml_force_authentication';
|
||||
const Enable_access_RSS = 'mo_saml_enable_rss_access';
|
||||
const Auto_redirect = 'mo_saml_enable_login_redirect';
|
||||
}
|
||||
|
||||
class mo_saml_options_enum_identity_providerMoSAML extends MoSAMLBasicEnum{
|
||||
const Broker_service ='mo_saml_enable_cloud_broker';
|
||||
const SP_Base_Url='mo_saml_sp_base_url';
|
||||
const SP_Entity_ID = 'mo_saml_sp_entity_id';
|
||||
|
||||
}
|
||||
|
||||
class mo_saml_options_tab_names extends MoSAMLBasicEnum{
|
||||
|
||||
const Service_provider_settings = 'service-provider-setup';
|
||||
const Identity_provider_settting = 'identity-provider-setup';
|
||||
const Redirection_sso_links = 'redirection-sso-links';
|
||||
const Entire_plugin_tour = 'entire-plugin-tour';
|
||||
const Attribute_role_mapping = 'attribute-role-mapping';
|
||||
|
||||
}
|
||||
|
||||
class mo_saml_options_enum_pointersMoSAML extends MoSAMLBasicEnum{
|
||||
public static
|
||||
$DEFAULT = array(
|
||||
'custom_admin_pointers4_8_52_default-miniorange-sp-metadata-url',
|
||||
'custom_admin_pointers4_8_52_default-miniorange-select-your-idp',
|
||||
'custom_admin_pointers4_8_52_default-miniorange-upload-metadata',
|
||||
'custom_admin_pointers4_8_52_default-miniorange-test-configuration',
|
||||
'custom_admin_pointers4_8_52_default-miniorange-attribute-mapping',
|
||||
'custom_admin_pointers4_8_52_default-miniorange-role-mapping',
|
||||
'custom_admin_pointers4_8_52_default-minorange-use-widget',
|
||||
'custom_admin_pointers4_8_52_default-miniorange-addons',
|
||||
'custom_admin_pointers4_8_52_default-miniorange-support-pointer'
|
||||
);
|
||||
public static $DEFAULT_SKIP = array(
|
||||
'custom_admin_pointers4_8_52_default-miniorange-sp-metadata-url',
|
||||
'custom_admin_pointers4_8_52_default-miniorange-select-your-idp',
|
||||
'custom_admin_pointers4_8_52_default-miniorange-upload-metadata',
|
||||
'custom_admin_pointers4_8_52_default-miniorange-test-configuration',
|
||||
'custom_admin_pointers4_8_52_default-miniorange-attribute-mapping',
|
||||
'custom_admin_pointers4_8_52_default-miniorange-role-mapping',
|
||||
'custom_admin_pointers4_8_52_default-minorange-use-widget',
|
||||
'custom_admin_pointers4_8_52_default-miniorange-addons',
|
||||
);
|
||||
public static $SERVICE_PROVIDER = array(
|
||||
'custom_admin_pointers4_8_52_miniorange-select-your-idp',
|
||||
'custom_admin_pointers4_8_52_miniorange-upload-metadata',
|
||||
'custom_admin_pointers4_8_52_miniorange-test-configuration',
|
||||
'custom_admin_pointers4_8_52_miniorange-import-config',
|
||||
'custom_admin_pointers4_8_52_export-import-config',
|
||||
'custom_admin_pointers4_8_52_configure-service-restart-tour');
|
||||
public static $IDENTITY_PROVIDER = array(
|
||||
'custom_admin_pointers4_8_52_metadata_manual',
|
||||
'custom_admin_pointers4_8_52_miniorange-sp-metadata-url',
|
||||
'custom_admin_pointers4_8_52_identity-provider-restart-tour'
|
||||
);
|
||||
public static $ATTRIBUTE_MAPPING = array(
|
||||
'custom_admin_pointers4_8_52_miniorange-attribute-mapping',
|
||||
'custom_admin_pointers4_8_52_miniorange-role-mapping',
|
||||
'custom_admin_pointers4_8_52_attribute-mapping-restart-tour');
|
||||
public static $REDIRECTION_LINK = array(
|
||||
'custom_admin_pointers4_8_52_minorange-use-widget',
|
||||
'custom_admin_pointers4_8_52_miniorange-auto-redirect',
|
||||
'custom_admin_pointers4_8_52_miniorange-auto-redirect-login-page',
|
||||
'custom_admin_pointers4_8_52_miniorange-short-code',
|
||||
'custom_admin_pointers4_8_52_miniorange-redirection-sso-restart-tour'
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
class mo_saml_options_enum_service_providerMoSAML extends MoSAMLBasicEnum{
|
||||
const Identity_name ='saml_identity_name';
|
||||
const Login_binding_type='saml_login_binding_type';
|
||||
const Login_URL = 'saml_login_url';
|
||||
const Logout_binding_type = 'saml_logout_binding_type';
|
||||
const Logout_URL = 'saml_logout_url';
|
||||
const Issuer = 'saml_issuer';
|
||||
const X509_certificate = 'saml_x509_certificate';
|
||||
const Request_signed = 'saml_request_signed';
|
||||
const Guide_name = 'saml_identity_provider_guide_name';
|
||||
const Is_encoding_enabled = 'mo_saml_encoding_enabled';
|
||||
|
||||
|
||||
}
|
||||
|
||||
class mo_saml_options_test_configuration extends MoSAMLBasicEnum{
|
||||
const SAML_REQUEST = 'MO_SAML_REQUEST';
|
||||
const SAML_RESPONSE = 'MO_SAML_RESPONSE';
|
||||
const TEST_CONFIG_ERROR_LOG = 'MO_SAML_TEST';
|
||||
}
|
||||
|
||||
class mo_saml_options_enum_attribute_mappingMoSAML extends MoSAMLBasicEnum{
|
||||
const Attribute_Username ='saml_am_username';
|
||||
const Attribute_Email = 'saml_am_email';
|
||||
const Attribute_First_name ='saml_am_first_name';
|
||||
const Attribute_Last_name = 'saml_am_last_name';
|
||||
const Attribute_Group_name ='saml_am_group_name';
|
||||
const Attribute_Custom_mapping = 'mo_saml_custom_attrs_mapping';
|
||||
const Attribute_Account_matcher = 'saml_am_account_matcher';
|
||||
|
||||
}
|
||||
|
||||
class mo_saml_options_enum_role_mappingMoSAML extends MoSAMLBasicEnum{
|
||||
const Role_do_not_auto_create_users = 'mo_saml_dont_create_user_if_role_not_mapped';
|
||||
const Role_do_not_assign_role_unlisted = 'saml_am_dont_allow_unlisted_user_role';
|
||||
const Role_do_not_update_existing_user = 'saml_am_dont_update_existing_user_role';
|
||||
const Role_default_role ='saml_am_default_user_role';
|
||||
}
|
||||
|
||||
|
||||
class mo_saml_options_error_constants extends MoSAMLBasicEnum{
|
||||
const Error_no_certificate = "Unable to find a certificate .";
|
||||
const Cause_no_certificate = "No signature found in SAML Response or Assertion. Please sign at least one of them.";
|
||||
const Error_wrong_certificate = "Unable to find a certificate matching the configured fingerprint.";
|
||||
const Cause_wrong_certificate = "X.509 Certificate field in plugin does not match the certificate found in SAML Response.";
|
||||
const Error_invalid_audience = "Invalid Audience URI.";
|
||||
const Cause_invalid_audience = "The value of 'Audience URI' field on Identity Provider's side is incorrect";
|
||||
const Error_issuer_not_verfied = "Issuer cannot be verified.";
|
||||
const Cause_issuer_not_verfied = "IdP Entity ID configured and the one found in SAML Response do not match";
|
||||
}
|
||||
|
||||
|
||||
class mo_saml_options_plugin_constants extends MoSAMLBasicEnum{
|
||||
const CMS_Name = "WP";
|
||||
const Application_Name = "WP miniOrange SAML 2.0 SSO Plugin";
|
||||
const Application_type = "SAML";
|
||||
const Version = "4.9.05";
|
||||
const HOSTNAME = "https://login.xecurify.com";
|
||||
}
|
||||
|
||||
class mo_saml_options_plugin_idp extends MoSAMLBasicEnum{
|
||||
public static $IDP_GUIDES = array(
|
||||
"Azure AD" => "azure-ad",
|
||||
"Azure B2C" => "azure-b2c",
|
||||
"ADFS" => "adfs",
|
||||
"Okta" => "okta",
|
||||
"SalesForce" => "salesforce",
|
||||
"Google Apps" => "google-apps",
|
||||
"OneLogin" => "onelogin",
|
||||
"MiniOrange" => "miniorange",
|
||||
"Keycloak" => "jboss-keycloak",
|
||||
"AbsorbLMS" => "absorb-lms",
|
||||
"Degreed" => "degreed",
|
||||
"JumpCloud" => "jumpcloud",
|
||||
"PingFederate" => "pingfederate",
|
||||
"PingOne" => "pingone",
|
||||
"Centrify" => "centrify",
|
||||
"Oracle" => "oracle-enterprise-manager",
|
||||
"Bitium" => "bitium",
|
||||
"Shibboleth 2" => "shibboleth2",
|
||||
"Shibboleth 3" => "shibboleth3",
|
||||
"Gluu Server" => "gluu-server",
|
||||
"SimpleSAMLphp" => "simplesaml",
|
||||
"OpenAM" => "openam",
|
||||
"Authanvil"=>"authanvil",
|
||||
"Auth0"=>"auth0",
|
||||
"CA Identity"=>"ca-identity",
|
||||
"WSO2"=>"wso2",
|
||||
"RSA SecureID"=>"rsa-secureid",
|
||||
"Custom IDP"=>"custom-idp"
|
||||
);
|
||||
}
|
||||
|
||||
class mo_saml_options_plugin_idp_videos extends MoSAMLBasicEnum{
|
||||
public static $IDP_VIDEOS = array(
|
||||
"azure-ad"=> "eHen4aiflFU",
|
||||
"azure-b2c"=> "",
|
||||
"adfs"=> "rLBHbRbrY5E",
|
||||
"okta"=> "YHE8iYojUqM",
|
||||
"salesforce"=> "LRQrmgr255Q",
|
||||
"google-apps"=> "5BwzEjgZiu4",
|
||||
"onelogin"=> "_Hsot_RG9YY",
|
||||
"miniorange"=> "eamf9s6JpbA",
|
||||
"jboss-keycloak"=> "Io6x1fTNWHI",
|
||||
"absorb-lms"=> "",
|
||||
"degreed"=> "",
|
||||
"jumpcloud"=> "",
|
||||
"pingfederate"=> "",
|
||||
"pingone"=> "",
|
||||
"centrify"=> "",
|
||||
"oracle-enterprise-manager"=> "",
|
||||
"bitium"=> "",
|
||||
"shibboleth2"=> "",
|
||||
"shibboleth3"=> "",
|
||||
"gluu-server"=> "",
|
||||
"simplesaml"=> "",
|
||||
"openam"=> "",
|
||||
"authanvil"=> "",
|
||||
"auth0"=> "54pz6m5h9mk",
|
||||
"ca-identity" => "",
|
||||
"wso2" => "",
|
||||
"rsa-secureid" => "",
|
||||
"custom-idp" => "gilfhNFYsgc"
|
||||
);
|
||||
}
|
||||
|
||||
class mo_saml_options_addons extends MoSAMLBasicEnum{
|
||||
|
||||
public static $ADDON_URL = array(
|
||||
|
||||
'scim' => 'https://plugins.miniorange.com/wordpress-user-provisioning',
|
||||
'page_restriction' => 'https://plugins.miniorange.com/wordpress-page-restriction',
|
||||
'file_prevention' => 'https://plugins.miniorange.com/wordpress-media-restriction',
|
||||
'ssologin' => 'https://plugins.miniorange.com/wordpress-sso-login-audit',
|
||||
'buddypress' => 'https://plugins.miniorange.com/wordpress-buddypress-integrator',
|
||||
'learndash' => 'https://plugins.miniorange.com/wordpress-learndash-integrator',
|
||||
'attribute_based_redirection' => 'https://plugins.miniorange.com/wordpress-attribute-based-redirection-restriction',
|
||||
'ssosession' => 'https://plugins.miniorange.com/sso-session-management',
|
||||
'fsso' => 'https://plugins.miniorange.com/incommon-federation-single-sign-on-sso',
|
||||
'paid_mem_pro' => 'https://plugins.miniorange.com/paid-membership-pro-integrator',
|
||||
'memberpress' => 'https://plugins.miniorange.com/wordpress-memberpress-integrator',
|
||||
'wp_members' => 'https://plugins.miniorange.com/wordpress-members-integrator',
|
||||
'woocommerce' => 'https://plugins.miniorange.com/wordpress-woocommerce-integrator',
|
||||
'guest_login' => 'https://plugins.miniorange.com/guest-user-login',
|
||||
'profile_picture_add_on' => 'https://plugins.miniorange.com/wordpress-profile-picture-map'
|
||||
|
||||
);
|
||||
|
||||
public static $WP_ADDON_URL = array(
|
||||
'page-restriction' => 'https://wordpress.org/plugins/page-and-post-restriction/embed/',
|
||||
'scim-user-sync'=> 'https://wordpress.org/plugins/scim-user-provisioning/embed/'
|
||||
);
|
||||
|
||||
public static $ADDON_TITLE = array(
|
||||
|
||||
'scim' => 'SCIM User Provisioning',
|
||||
'page_restriction' => 'Page and Post Restriction',
|
||||
'file_prevention' => 'Prevent File Access',
|
||||
'ssologin' => 'SSO Login Audit',
|
||||
'buddypress' => 'BuddyPress Integrator',
|
||||
'learndash' => 'Learndash Integrator',
|
||||
'attribute_based_redirection' => 'Attribute Based Redirection',
|
||||
'ssosession' => 'SSO Session Management',
|
||||
'fsso' => 'Federation Single Sign-On',
|
||||
'memberpress' => 'MemberPress Integrator',
|
||||
'wp_members' => 'WP-Members Integrator',
|
||||
'woocommerce' => 'WooCommerce Integrator',
|
||||
'guest_login' => 'Guest Login',
|
||||
'profile_picture_add_on' => 'Profile Picture Add-on',
|
||||
'paid_mem_pro' => 'PaidMembership Pro Integrator'
|
||||
);
|
||||
|
||||
public static $RECOMMENDED_ADDONS_PATH = array(
|
||||
|
||||
"learndash" => "sfwd-lms/sfwd_lms.php",
|
||||
"buddypress" => "buddypress/bp-loader.php",
|
||||
"paid_mem_pro" => "paid-memberships-pro/paid-memberships-pro.php",
|
||||
"memberpress" => "memberpress/memberpress.php",
|
||||
"wp_members" => "wp-members/wp-members.php",
|
||||
"woocommerce" => "woocommerce/woocommerce.php"
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
class mo_saml_license_plans extends MoSAMLBasicEnum {
|
||||
|
||||
public static $license_plans = array (
|
||||
'standard' => 'WP SAML SSO Standard Plan',
|
||||
'premium' => 'WP SAML SSO Premium Plan',
|
||||
'enterprise' => 'WP SAML SSO Enterprise Plan',
|
||||
'enterprise-multiple-idp' => 'WP SAML SSO Enterprise Multiple-IDP Plan',
|
||||
'all-inclusive' => 'WP SAML SSO All Inclusive Plan',
|
||||
'premium-multisite' => 'WP SAML SSO Premium Multisite Plan',
|
||||
'enterprise-multisite' => 'WP SAML SSO Enterprise Multisite Plan',
|
||||
'all-inclusive-multisite' => 'WP SAML SSO All Inclusive Multisite Plan',
|
||||
'help' => 'Not Sure'
|
||||
);
|
||||
|
||||
public static $license_plans_slug = array (
|
||||
'standard' => '16.0.2@16.0.2',
|
||||
'premium' => '12.0.2@12.0.2',
|
||||
'enterprise' => '12.0.2@12.0.2',
|
||||
'enterprise-multiple-idp' => '25.0.1@25.0.1',
|
||||
'all-inclusive' => '25.0.1@25.0.1',
|
||||
);
|
||||
}
|
||||
|
||||
class mo_saml_time_zones extends MoSAMLBasicEnum {
|
||||
|
||||
public static $time_zones = array(
|
||||
"(GMT-11:00) Niue Time" => "Pacific/Niue",
|
||||
"(GMT-11:00) Samoa Standard Time" => "Pacific/Pago_Pago",
|
||||
"(GMT-10:00) Cook Islands Standard Time" => "Pacific/Rarotonga",
|
||||
"(GMT-10:00) Hawaii-Aleutian Standard Time" => "Pacific/Honolulu",
|
||||
"(GMT-10:00) Tahiti Time" => "Pacific/Tahiti",
|
||||
"(GMT-09:30) Marquesas Time" => "Pacific/Marquesas",
|
||||
"(GMT-09:00) Gambier Time" => "Pacific/Gambier",
|
||||
"(GMT-09:00) Hawaii-Aleutian Time (Adak)" => "America/Adak",
|
||||
"(GMT-08:00) Alaska Time - Anchorage" => "America/Anchorage",
|
||||
"(GMT-08:00) Alaska Time - Juneau" => "America/Juneau",
|
||||
"(GMT-08:00) Alaska Time - Metlakatla" => "America/Metlakatla",
|
||||
"(GMT-08:00) Alaska Time - Nome" => "America/Nome",
|
||||
"(GMT-08:00) Alaska Time - Sitka" => "America/Sitka",
|
||||
"(GMT-08:00) Alaska Time - Yakutat" => "America/Yakutat",
|
||||
"(GMT-08:00) Pitcairn Time" => "Pacific/Pitcairn",
|
||||
"(GMT-07:00) Mexican Pacific Standard Time" => "America/Hermosillo",
|
||||
"(GMT-07:00) Mountain Standard Time - Creston" => "America/Creston",
|
||||
"(GMT-07:00) Mountain Standard Time - Dawson" => "America/Dawson",
|
||||
"(GMT-07:00) Mountain Standard Time - Dawson Creek" => "America/Dawson_Creek",
|
||||
"(GMT-07:00) Mountain Standard Time - Fort Nelson" => "America/Fort_Nelson",
|
||||
"(GMT-07:00) Mountain Standard Time - Phoenix" => "America/Phoenix",
|
||||
"(GMT-07:00) Mountain Standard Time - Whitehorse" => "America/Whitehorse",
|
||||
"(GMT-07:00) Pacific Time - Los Angeles" => "America/Los_Angeles",
|
||||
"(GMT-07:00) Pacific Time - Tijuana" => "America/Tijuana",
|
||||
"(GMT-07:00) Pacific Time - Vancouver" => "America/Vancouver",
|
||||
"(GMT-06:00) Central Standard Time - Belize" => "America/Belize",
|
||||
"(GMT-06:00) Central Standard Time - Costa Rica" => "America/Costa_Rica",
|
||||
"(GMT-06:00) Central Standard Time - El Salvador" => "America/El_Salvador",
|
||||
"(GMT-06:00) Central Standard Time - Guatemala" => "America/Guatemala",
|
||||
"(GMT-06:00) Central Standard Time - Managua" => "America/Managua",
|
||||
"(GMT-06:00) Central Standard Time - Regina" => "America/Regina",
|
||||
"(GMT-06:00) Central Standard Time - Swift Current" => "America/Swift_Current",
|
||||
"(GMT-06:00) Central Standard Time - Tegucigalpa" => "America/Tegucigalpa",
|
||||
"(GMT-06:00) Easter Island Time" => "Pacific/Easter",
|
||||
"(GMT-06:00) Galapagos Time" => "Pacific/Galapagos",
|
||||
"(GMT-06:00) Mexican Pacific Time - Chihuahua" => "America/Chihuahua",
|
||||
"(GMT-06:00) Mexican Pacific Time - Mazatlan" => "America/Mazatlan",
|
||||
"(GMT-06:00) Mountain Time - Boise" => "America/Boise",
|
||||
"(GMT-06:00) Mountain Time - Cambridge Bay" => "America/Cambridge_Bay",
|
||||
"(GMT-06:00) Mountain Time - Denver" => "America/Denver",
|
||||
"(GMT-06:00) Mountain Time - Edmonton" => "America/Edmonton",
|
||||
"(GMT-06:00) Mountain Time - Inuvik" => "America/Inuvik",
|
||||
"(GMT-06:00) Mountain Time - Ojinaga" => "America/Ojinaga",
|
||||
"(GMT-06:00) Mountain Time - Yellowknife" => "America/Yellowknife",
|
||||
"(GMT-05:00) Acre Standard Time - Eirunepe" => "America/Eirunepe",
|
||||
"(GMT-05:00) Acre Standard Time - Rio Branco" => "America/Rio_Branco",
|
||||
"(GMT-05:00) Central Time - Bahia Banderas" => "America/Bahia_Banderas",
|
||||
"(GMT-05:00) Central Time - Beulah, North Dakota" => "America/North_Dakota/Beulah",
|
||||
"(GMT-05:00) Central Time - Center, North Dakota" => "America/North_Dakota/Center",
|
||||
"(GMT-05:00) Central Time - Chicago" => "America/Chicago",
|
||||
"(GMT-05:00) Central Time - Knox, Indiana" => "America/Indiana/Knox",
|
||||
"(GMT-05:00) Central Time - Matamoros" => "America/Matamoros",
|
||||
"(GMT-05:00) Central Time - Menominee" => "America/Menominee",
|
||||
"(GMT-05:00) Central Time - Merida" => "America/Merida",
|
||||
"(GMT-05:00) Central Time - Mexico City" => "America/Mexico_City",
|
||||
"(GMT-05:00) Central Time - Monterrey" => "America/Monterrey",
|
||||
"(GMT-05:00) Central Time - New Salem, North Dakota" => "America/North_Dakota/New_Salem",
|
||||
"(GMT-05:00) Central Time - Rainy River" => "America/Rainy_River",
|
||||
"(GMT-05:00) Central Time - Rankin Inlet" => "America/Rankin_Inlet",
|
||||
"(GMT-05:00) Central Time - Resolute" => "America/Resolute",
|
||||
"(GMT-05:00) Central Time - Tell City, Indiana" => "America/Indiana/Tell_City",
|
||||
"(GMT-05:00) Central Time - Winnipeg" => "America/Winnipeg",
|
||||
"(GMT-05:00) Colombia Standard Time" => "America/Bogota",
|
||||
"(GMT-05:00) Eastern Standard Time - Atikokan" => "America/Atikokan",
|
||||
"(GMT-05:00) Eastern Standard Time - Cancun" => "America/Cancun",
|
||||
"(GMT-05:00) Eastern Standard Time - Jamaica" => "America/Jamaica",
|
||||
"(GMT-05:00) Eastern Standard Time - Panama" => "America/Panama",
|
||||
"(GMT-05:00) Ecuador Time" => "America/Guayaquil",
|
||||
"(GMT-05:00) Peru Standard Time" => "America/Lima",
|
||||
"(GMT-04:00) Amazon Standard Time - Boa Vista" => "America/Boa_Vista",
|
||||
"(GMT-04:00) Amazon Standard Time - Campo Grande" => "America/Campo_Grande",
|
||||
"(GMT-04:00) Amazon Standard Time - Cuiaba" => "America/Cuiaba",
|
||||
"(GMT-04:00) Amazon Standard Time - Manaus" => "America/Manaus",
|
||||
"(GMT-04:00) Amazon Standard Time - Porto Velho" => "America/Porto_Velho",
|
||||
"(GMT-04:00) Atlantic Standard Time - Barbados" => "America/Barbados",
|
||||
"(GMT-04:00) Atlantic Standard Time - Blanc-Sablon" => "America/Blanc-Sablon",
|
||||
"(GMT-04:00) Atlantic Standard Time - Curaçao" => "America/Curacao",
|
||||
"(GMT-04:00) Atlantic Standard Time - Martinique" => "America/Martinique",
|
||||
"(GMT-04:00) Atlantic Standard Time - Port of Spain" => "America/Port_of_Spain",
|
||||
"(GMT-04:00) Atlantic Standard Time - Puerto Rico" => "America/Puerto_Rico",
|
||||
"(GMT-04:00) Atlantic Standard Time - Santo Domingo" => "America/Santo_Domingo",
|
||||
"(GMT-04:00) Bolivia Time" => "America/La_Paz",
|
||||
"(GMT-04:00) Chile Time" => "America/Santiago",
|
||||
"(GMT-04:00) Cuba Time" => "America/Havana",
|
||||
"(GMT-04:00) Eastern Time - Detroit" => "America/Detroit",
|
||||
"(GMT-04:00) Eastern Time - Grand Turk" => "America/Grand_Turk",
|
||||
"(GMT-04:00) Eastern Time - Indianapolis" => "America/Indiana/Indianapolis",
|
||||
"(GMT-04:00) Eastern Time - Iqaluit" => "America/Iqaluit",
|
||||
"(GMT-04:00) Eastern Time - Louisville" => "America/Kentucky/Louisville",
|
||||
"(GMT-04:00) Eastern Time - Marengo, Indiana" => "America/Indiana/Marengo",
|
||||
"(GMT-04:00) Eastern Time - Monticello, Kentucky" => "America/Kentucky/Monticello",
|
||||
"(GMT-04:00) Eastern Time - Nassau" => "America/Nassau",
|
||||
"(GMT-04:00) Eastern Time - New York" => "America/New_York",
|
||||
"(GMT-04:00) Eastern Time - Nipigon" => "America/Nipigon",
|
||||
"(GMT-04:00) Eastern Time - Pangnirtung" => "America/Pangnirtung",
|
||||
"(GMT-04:00) Eastern Time - Petersburg, Indiana" => "America/Indiana/Petersburg",
|
||||
"(GMT-04:00) Eastern Time - Port-au-Prince" => "America/Port-au-Prince",
|
||||
"(GMT-04:00) Eastern Time - Thunder Bay" => "America/Thunder_Bay",
|
||||
"(GMT-04:00) Eastern Time - Toronto" => "America/Toronto",
|
||||
"(GMT-04:00) Eastern Time - Vevay, Indiana" => "America/Indiana/Vevay",
|
||||
"(GMT-04:00) Eastern Time - Vincennes, Indiana" => "America/Indiana/Vincennes",
|
||||
"(GMT-04:00) Eastern Time - Winamac, Indiana" => "America/Indiana/Winamac",
|
||||
"(GMT-04:00) Guyana Time" => "America/Guyana",
|
||||
"(GMT-04:00) Paraguay Time" => "America/Asuncion",
|
||||
"(GMT-04:00) Venezuela Time" => "America/Caracas",
|
||||
"(GMT-03:00) Argentina Standard Time - Buenos Aires" => "America/Argentina/Buenos_Aires",
|
||||
"(GMT-03:00) Argentina Standard Time - Catamarca" => "America/Argentina/Catamarca",
|
||||
"(GMT-03:00) Argentina Standard Time - Cordoba" => "America/Argentina/Cordoba",
|
||||
"(GMT-03:00) Argentina Standard Time - Jujuy" => "America/Argentina/Jujuy",
|
||||
"(GMT-03:00) Argentina Standard Time - La Rioja" => "America/Argentina/La_Rioja",
|
||||
"(GMT-03:00) Argentina Standard Time - Mendoza" => "America/Argentina/Mendoza",
|
||||
"(GMT-03:00) Argentina Standard Time - Rio Gallegos" => "America/Argentina/Rio_Gallegos",
|
||||
"(GMT-03:00) Argentina Standard Time - Salta" => "America/Argentina/Salta",
|
||||
"(GMT-03:00) Argentina Standard Time - San Juan" => "America/Argentina/San_Juan",
|
||||
"(GMT-03:00) Argentina Standard Time - San Luis" => "America/Argentina/San_Luis",
|
||||
"(GMT-03:00) Argentina Standard Time - Tucuman" => "America/Argentina/Tucuman",
|
||||
"(GMT-03:00) Argentina Standard Time - Ushuaia" => "America/Argentina/Ushuaia",
|
||||
"(GMT-03:00) Atlantic Time - Bermuda" => "Atlantic/Bermuda",
|
||||
"(GMT-03:00) Atlantic Time - Glace Bay" => "America/Glace_Bay",
|
||||
"(GMT-03:00) Atlantic Time - Goose Bay" => "America/Goose_Bay",
|
||||
"(GMT-03:00) Atlantic Time - Halifax" => "America/Halifax",
|
||||
"(GMT-03:00) Atlantic Time - Moncton" => "America/Moncton",
|
||||
"(GMT-03:00) Atlantic Time - Thule" => "America/Thule",
|
||||
"(GMT-03:00) Brasilia Standard Time - Araguaina" => "America/Araguaina",
|
||||
"(GMT-03:00) Brasilia Standard Time - Bahia" => "America/Bahia",
|
||||
"(GMT-03:00) Brasilia Standard Time - Belem" => "America/Belem",
|
||||
"(GMT-03:00) Brasilia Standard Time - Fortaleza" => "America/Fortaleza",
|
||||
"(GMT-03:00) Brasilia Standard Time - Maceio" => "America/Maceio",
|
||||
"(GMT-03:00) Brasilia Standard Time - Recife" => "America/Recife",
|
||||
"(GMT-03:00) Brasilia Standard Time - Santarem" => "America/Santarem",
|
||||
"(GMT-03:00) Brasilia Standard Time - Sao Paulo" => "America/Sao_Paulo",
|
||||
"(GMT-03:00) Chile Time" => "America/Santiago",
|
||||
"(GMT-03:00) Falkland Islands Standard Time" => "Atlantic/Stanley",
|
||||
"(GMT-03:00) French Guiana Time" => "America/Cayenne",
|
||||
"(GMT-03:00) Palmer Time" => "Antarctica/Palmer",
|
||||
"(GMT-03:00) Punta Arenas Time" => "America/Punta_Arenas",
|
||||
"(GMT-03:00) Rothera Time" => "Antarctica/Rothera",
|
||||
"(GMT-03:00) Suriname Time" => "America/Paramaribo",
|
||||
"(GMT-03:00) Uruguay Standard Time" => "America/Montevideo",
|
||||
"(GMT-02:30) Newfoundland Time" => "America/St_Johns",
|
||||
"(GMT-02:00) Fernando de Noronha Standard Time" => "America/Noronha",
|
||||
"(GMT-02:00) South Georgia Time" => "Atlantic/South_Georgia",
|
||||
"(GMT-02:00) St. Pierre & Miquelon Time" => "America/Miquelon",
|
||||
"(GMT-02:00) West Greenland Time" => "America/Nuuk",
|
||||
"(GMT-01:00) Cape Verde Standard Time" => "Atlantic/Cape_Verde",
|
||||
"(GMT+00:00) Azores Time" => "Atlantic/Azores",
|
||||
"(GMT+00:00) Coordinated Universal Time" => "UTC",
|
||||
"(GMT+00:00) East Greenland Time" => "America/Scoresbysund",
|
||||
"(GMT+00:00) Greenwich Mean Time" => "Etc/GMT",
|
||||
"(GMT+00:00) Greenwich Mean Time - Abidjan" => "Africa/Abidjan",
|
||||
"(GMT+00:00) Greenwich Mean Time - Accra" => "Africa/Accra",
|
||||
"(GMT+00:00) Greenwich Mean Time - Bissau" => "Africa/Bissau",
|
||||
"(GMT+00:00) Greenwich Mean Time - Danmarkshavn" => "America/Danmarkshavn",
|
||||
"(GMT+00:00) Greenwich Mean Time - Monrovia" => "Africa/Monrovia",
|
||||
"(GMT+00:00) Greenwich Mean Time - Reykjavik" => "Atlantic/Reykjavik",
|
||||
"(GMT+00:00) Greenwich Mean Time - São Tomé" => "Africa/Sao_Tome",
|
||||
"(GMT+01:00) Central European Standard Time - Algiers" => "Africa/Algiers",
|
||||
"(GMT+01:00) Central European Standard Time - Tunis" => "Africa/Tunis",
|
||||
"(GMT+01:00) Ireland Time" => "Europe/Dublin",
|
||||
"(GMT+01:00) Morocco Time" => "Africa/Casablanca",
|
||||
"(GMT+01:00) United Kingdom Time" => "Europe/London",
|
||||
"(GMT+01:00) West Africa Standard Time - Lagos" => "Africa/Lagos",
|
||||
"(GMT+01:00) West Africa Standard Time - Ndjamena" => "Africa/Ndjamena",
|
||||
"(GMT+01:00) Western European Time - Canary" => "Atlantic/Canary",
|
||||
"(GMT+01:00) Western European Time - Faroe" => "Atlantic/Faroe",
|
||||
"(GMT+01:00) Western European Time - Lisbon" => "Europe/Lisbon",
|
||||
"(GMT+01:00) Western European Time - Madeira" => "Atlantic/Madeira",
|
||||
"(GMT+01:00) Western Sahara Time" => "Africa/El_Aaiun",
|
||||
"(GMT+02:00) Central Africa Time - Khartoum" => "Africa/Khartoum",
|
||||
"(GMT+02:00) Central Africa Time - Maputo" => "Africa/Maputo",
|
||||
"(GMT+02:00) Central Africa Time - Windhoek" => "Africa/Windhoek",
|
||||
"(GMT+02:00) Central European Time - Amsterdam" => "Europe/Amsterdam",
|
||||
"(GMT+02:00) Central European Time - Andorra" => "Europe/Andorra",
|
||||
"(GMT+02:00) Central European Time - Belgrade" => "Europe/Belgrade",
|
||||
"(GMT+02:00) Central European Time - Berlin" => "Europe/Berlin",
|
||||
"(GMT+02:00) Central European Time - Brussels" => "Europe/Brussels",
|
||||
"(GMT+02:00) Central European Time - Budapest" => "Europe/Budapest",
|
||||
"(GMT+02:00) Central European Time - Ceuta" => "Africa/Ceuta",
|
||||
"(GMT+02:00) Central European Time - Copenhagen" => "Europe/Copenhagen",
|
||||
"(GMT+02:00) Central European Time - Gibraltar" => "Europe/Gibraltar",
|
||||
"(GMT+02:00) Central European Time - Luxembourg" => "Europe/Luxembourg",
|
||||
"(GMT+02:00) Central European Time - Madrid" => "Europe/Madrid",
|
||||
"(GMT+02:00) Central European Time - Malta" => "Europe/Malta",
|
||||
"(GMT+02:00) Central European Time - Monaco" => "Europe/Monaco",
|
||||
"(GMT+02:00) Central European Time - Oslo" => "Europe/Oslo",
|
||||
"(GMT+02:00) Central European Time - Paris" => "Europe/Paris",
|
||||
"(GMT+02:00) Central European Time - Prague" => "Europe/Prague",
|
||||
"(GMT+02:00) Central European Time - Rome" => "Europe/Rome",
|
||||
"(GMT+02:00) Central European Time - Stockholm" => "Europe/Stockholm",
|
||||
"(GMT+02:00) Central European Time - Tirane" => "Europe/Tirane",
|
||||
"(GMT+02:00) Central European Time - Vienna" => "Europe/Vienna",
|
||||
"(GMT+02:00) Central European Time - Warsaw" => "Europe/Warsaw",
|
||||
"(GMT+02:00) Central European Time - Zurich" => "Europe/Zurich",
|
||||
"(GMT+02:00) Eastern European Standard Time - Cairo" => "Africa/Cairo",
|
||||
"(GMT+02:00) Eastern European Standard Time - Kaliningrad" => "Europe/Kaliningrad",
|
||||
"(GMT+02:00) Eastern European Standard Time - Tripoli" => "Africa/Tripoli",
|
||||
"(GMT+02:00) South Africa Standard Time" => "Africa/Johannesburg",
|
||||
"(GMT+02:00) Troll Time" => "Antarctica/Troll",
|
||||
"(GMT+03:00) Arabian Standard Time - Baghdad" => "Asia/Baghdad",
|
||||
"(GMT+03:00) Arabian Standard Time - Qatar" => "Asia/Qatar",
|
||||
"(GMT+03:00) Arabian Standard Time - Riyadh" => "Asia/Riyadh",
|
||||
"(GMT+03:00) East Africa Time - Juba" => "Africa/Juba",
|
||||
"(GMT+03:00) East Africa Time - Nairobi" => "Africa/Nairobi",
|
||||
"(GMT+03:00) Eastern European Time - Amman" => "Asia/Amman",
|
||||
"(GMT+03:00) Eastern European Time - Athens" => "Europe/Athens",
|
||||
"(GMT+03:00) Eastern European Time - Beirut" => "Asia/Beirut",
|
||||
"(GMT+03:00) Eastern European Time - Bucharest" => "Europe/Bucharest",
|
||||
"(GMT+03:00) Eastern European Time - Chisinau" => "Europe/Chisinau",
|
||||
"(GMT+03:00) Eastern European Time - Damascus" => "Asia/Damascus",
|
||||
"(GMT+03:00) Eastern European Time - Gaza" => "Asia/Gaza",
|
||||
"(GMT+03:00) Eastern European Time - Hebron" => "Asia/Hebron",
|
||||
"(GMT+03:00) Eastern European Time - Helsinki" => "Europe/Helsinki",
|
||||
"(GMT+03:00) Eastern European Time - Kiev" => "Europe/Kiev",
|
||||
"(GMT+03:00) Eastern European Time - Nicosia" => "Asia/Nicosia",
|
||||
"(GMT+03:00) Eastern European Time - Riga" => "Europe/Riga",
|
||||
"(GMT+03:00) Eastern European Time - Sofia" => "Europe/Sofia",
|
||||
"(GMT+03:00) Eastern European Time - Tallinn" => "Europe/Tallinn",
|
||||
"(GMT+03:00) Eastern European Time - Uzhhorod" => "Europe/Uzhgorod",
|
||||
"(GMT+03:00) Eastern European Time - Vilnius" => "Europe/Vilnius",
|
||||
"(GMT+03:00) Eastern European Time - Zaporozhye" => "Europe/Zaporozhye",
|
||||
"(GMT+03:00) Famagusta Time" => "Asia/Famagusta",
|
||||
"(GMT+03:00) Israel Time" => "Asia/Jerusalem",
|
||||
"(GMT+03:00) Kirov Time" => "Europe/Kirov",
|
||||
"(GMT+03:00) Moscow Standard Time - Minsk" => "Europe/Minsk",
|
||||
"(GMT+03:00) Moscow Standard Time - Moscow" => "Europe/Moscow",
|
||||
"(GMT+03:00) Moscow Standard Time - Simferopol" => "Europe/Simferopol",
|
||||
"(GMT+03:00) Syowa Time" => "Antarctica/Syowa",
|
||||
"(GMT+03:00) Turkey Time" => "Europe/Istanbul",
|
||||
"(GMT+04:00) Armenia Standard Time" => "Asia/Yerevan",
|
||||
"(GMT+04:00) Astrakhan Time" => "Europe/Astrakhan",
|
||||
"(GMT+04:00) Azerbaijan Standard Time" => "Asia/Baku",
|
||||
"(GMT+04:00) Georgia Standard Time" => "Asia/Tbilisi",
|
||||
"(GMT+04:00) Gulf Standard Time" => "Asia/Dubai",
|
||||
"(GMT+04:00) Mauritius Standard Time" => "Indian/Mauritius",
|
||||
"(GMT+04:00) Réunion Time" => "Indian/Reunion",
|
||||
"(GMT+04:00) Samara Standard Time" => "Europe/Samara",
|
||||
"(GMT+04:00) Saratov Time" => "Europe/Saratov",
|
||||
"(GMT+04:00) Seychelles Time" => "Indian/Mahe",
|
||||
"(GMT+04:00) Ulyanovsk Time" => "Europe/Ulyanovsk",
|
||||
"(GMT+04:00) Volgograd Standard Time" => "Europe/Volgograd",
|
||||
"(GMT+04:30) Afghanistan Time" => "Asia/Kabul",
|
||||
"(GMT+04:30) Iran Time" => "Asia/Tehran",
|
||||
"(GMT+05:00) French Southern & Antarctic Time" => "Indian/Kerguelen",
|
||||
"(GMT+05:00) Maldives Time" => "Indian/Maldives",
|
||||
"(GMT+05:00) Mawson Time" => "Antarctica/Mawson",
|
||||
"(GMT+05:00) Pakistan Standard Time" => "Asia/Karachi",
|
||||
"(GMT+05:00) Tajikistan Time" => "Asia/Dushanbe",
|
||||
"(GMT+05:00) Turkmenistan Standard Time" => "Asia/Ashgabat",
|
||||
"(GMT+05:00) Uzbekistan Standard Time - Samarkand" => "Asia/Samarkand",
|
||||
"(GMT+05:00) Uzbekistan Standard Time - Tashkent" => "Asia/Tashkent",
|
||||
"(GMT+05:00) West Kazakhstan Time - Aqtau" => "Asia/Aqtau",
|
||||
"(GMT+05:00) West Kazakhstan Time - Aqtobe" => "Asia/Aqtobe",
|
||||
"(GMT+05:00) West Kazakhstan Time - Atyrau" => "Asia/Atyrau",
|
||||
"(GMT+05:00) West Kazakhstan Time - Oral" => "Asia/Oral",
|
||||
"(GMT+05:00) West Kazakhstan Time - Qyzylorda" => "Asia/Qyzylorda",
|
||||
"(GMT+05:00) Yekaterinburg Standard Time" => "Asia/Yekaterinburg",
|
||||
"(GMT+05:30) Indian Standard Time - Colombo" => "Asia/Colombo",
|
||||
"(GMT+05:30) Indian Standard Time - Kolkata" => "Asia/Kolkata",
|
||||
"(GMT+05:45) Nepal Time" => "Asia/Kathmandu",
|
||||
"(GMT+06:00) Bangladesh Standard Time" => "Asia/Dhaka",
|
||||
"(GMT+06:00) Bhutan Time" => "Asia/Thimphu",
|
||||
"(GMT+06:00) East Kazakhstan Time - Almaty" => "Asia/Almaty",
|
||||
"(GMT+06:00) East Kazakhstan Time - Kostanay" => "Asia/Qostanay",
|
||||
"(GMT+06:00) Indian Ocean Time" => "Indian/Chagos",
|
||||
"(GMT+06:00) Kyrgyzstan Time" => "Asia/Bishkek",
|
||||
"(GMT+06:00) Omsk Standard Time" => "Asia/Omsk",
|
||||
"(GMT+06:00) Urumqi Time" => "Asia/Urumqi",
|
||||
"(GMT+06:00) Vostok Time" => "Antarctica/Vostok",
|
||||
"(GMT+06:30) Cocos Islands Time" => "Indian/Cocos",
|
||||
"(GMT+06:30) Myanmar Time" => "Asia/Yangon",
|
||||
"(GMT+07:00) Barnaul Time" => "Asia/Barnaul",
|
||||
"(GMT+07:00) Christmas Island Time" => "Indian/Christmas",
|
||||
"(GMT+07:00) Davis Time" => "Antarctica/Davis",
|
||||
"(GMT+07:00) Hovd Standard Time" => "Asia/Hovd",
|
||||
"(GMT+07:00) Indochina Time - Bangkok" => "Asia/Bangkok",
|
||||
"(GMT+07:00) Indochina Time - Ho Chi Minh City" => "Asia/Ho_Chi_Minh",
|
||||
"(GMT+07:00) Krasnoyarsk Standard Time - Krasnoyarsk" => "Asia/Krasnoyarsk",
|
||||
"(GMT+07:00) Krasnoyarsk Standard Time - Novokuznetsk" => "Asia/Novokuznetsk",
|
||||
"(GMT+07:00) Novosibirsk Standard Time" => "Asia/Novosibirsk",
|
||||
"(GMT+07:00) Tomsk Time" => "Asia/Tomsk",
|
||||
"(GMT+07:00) Western Indonesia Time - Jakarta" => "Asia/Jakarta",
|
||||
"(GMT+07:00) Western Indonesia Time - Pontianak" => "Asia/Pontianak",
|
||||
"(GMT+08:00) Australian Western Standard Time - Casey" => "Antarctica/Casey",
|
||||
"(GMT+08:00) Australian Western Standard Time - Perth" => "Australia/Perth",
|
||||
"(GMT+08:00) Brunei Darussalam Time" => "Asia/Brunei",
|
||||
"(GMT+08:00) Central Indonesia Time" => "Asia/Makassar",
|
||||
"(GMT+08:00) China Standard Time - Macao" => "Asia/Macau",
|
||||
"(GMT+08:00) China Standard Time - Shanghai" => "Asia/Shanghai",
|
||||
"(GMT+08:00) Hong Kong Standard Time" => "Asia/Hong_Kong",
|
||||
"(GMT+08:00) Irkutsk Standard Time" => "Asia/Irkutsk",
|
||||
"(GMT+08:00) Malaysia Time - Kuala Lumpur" => "Asia/Kuala_Lumpur",
|
||||
"(GMT+08:00) Malaysia Time - Kuching" => "Asia/Kuching",
|
||||
"(GMT+08:00) Philippine Standard Time" => "Asia/Manila",
|
||||
"(GMT+08:00) Singapore Standard Time" => "Asia/Singapore",
|
||||
"(GMT+08:00) Taipei Standard Time" => "Asia/Taipei",
|
||||
"(GMT+08:00) Ulaanbaatar Standard Time - Choibalsan" => "Asia/Choibalsan",
|
||||
"(GMT+08:00) Ulaanbaatar Standard Time - Ulaanbaatar" => "Asia/Ulaanbaatar",
|
||||
"(GMT+08:45) Australian Central Western Standard Time" => "Australia/Eucla",
|
||||
"(GMT+09:00) East Timor Time" => "Asia/Dili",
|
||||
"(GMT+09:00) Eastern Indonesia Time" => "Asia/Jayapura",
|
||||
"(GMT+09:00) Japan Standard Time" => "Asia/Tokyo",
|
||||
"(GMT+09:00) Korean Standard Time - Pyongyang" => "Asia/Pyongyang",
|
||||
"(GMT+09:00) Korean Standard Time - Seoul" => "Asia/Seoul",
|
||||
"(GMT+09:00) Palau Time" => "Pacific/Palau",
|
||||
"(GMT+09:00) Yakutsk Standard Time - Chita" => "Asia/Chita",
|
||||
"(GMT+09:00) Yakutsk Standard Time - Khandyga" => "Asia/Khandyga",
|
||||
"(GMT+09:00) Yakutsk Standard Time - Yakutsk" => "Asia/Yakutsk",
|
||||
"(GMT+09:30) Australian Central Standard Time" => "Australia/Darwin",
|
||||
"(GMT+09:30) Central Australia Time - Adelaide" => "Australia/Adelaide",
|
||||
"(GMT+09:30) Central Australia Time - Broken Hill" => "Australia/Broken_Hill",
|
||||
"(GMT+10:00) Australian Eastern Standard Time - Brisbane" => "Australia/Brisbane",
|
||||
"(GMT+10:00) Australian Eastern Standard Time - Lindeman" => "Australia/Lindeman",
|
||||
"(GMT+10:00) Chamorro Standard Time" => "Pacific/Guam",
|
||||
"(GMT+10:00) Chuuk Time" => "Pacific/Chuuk",
|
||||
"(GMT+10:00) Dumont-d’Urville Time" => "Antarctica/DumontDUrville",
|
||||
"(GMT+10:00) Eastern Australia Time - Currie" => "Australia/Currie",
|
||||
"(GMT+10:00) Eastern Australia Time - Hobart" => "Australia/Hobart",
|
||||
"(GMT+10:00) Eastern Australia Time - Melbourne" => "Australia/Melbourne",
|
||||
"(GMT+10:00) Eastern Australia Time - Sydney" => "Australia/Sydney",
|
||||
"(GMT+10:00) Papua New Guinea Time" => "Pacific/Port_Moresby",
|
||||
"(GMT+10:00) Vladivostok Standard Time - Ust-Nera" => "Asia/Ust-Nera",
|
||||
"(GMT+10:00) Vladivostok Standard Time - Vladivostok" => "Asia/Vladivostok",
|
||||
"(GMT+10:30) Lord Howe Time" => "Australia/Lord_Howe",
|
||||
"(GMT+11:00) Bougainville Time" => "Pacific/Bougainville",
|
||||
"(GMT+11:00) Kosrae Time" => "Pacific/Kosrae",
|
||||
"(GMT+11:00) Macquarie Island Time" => "Antarctica/Macquarie",
|
||||
"(GMT+11:00) Magadan Standard Time" => "Asia/Magadan",
|
||||
"(GMT+11:00) New Caledonia Standard Time" => "Pacific/Noumea",
|
||||
"(GMT+11:00) Norfolk Island Time" => "Pacific/Norfolk",
|
||||
"(GMT+11:00) Ponape Time" => "Pacific/Pohnpei",
|
||||
"(GMT+11:00) Sakhalin Standard Time" => "Asia/Sakhalin",
|
||||
"(GMT+11:00) Solomon Islands Time" => "Pacific/Guadalcanal",
|
||||
"(GMT+11:00) Srednekolymsk Time" => "Asia/Srednekolymsk",
|
||||
"(GMT+11:00) Vanuatu Standard Time" => "Pacific/Efate",
|
||||
"(GMT+12:00) Anadyr Standard Time" => "Asia/Anadyr",
|
||||
"(GMT+12:00) Fiji Time" => "Pacific/Fiji",
|
||||
"(GMT+12:00) Gilbert Islands Time" => "Pacific/Tarawa",
|
||||
"(GMT+12:00) Marshall Islands Time - Kwajalein" => "Pacific/Kwajalein",
|
||||
"(GMT+12:00) Marshall Islands Time - Majuro" => "Pacific/Majuro",
|
||||
"(GMT+12:00) Nauru Time" => "Pacific/Nauru",
|
||||
"(GMT+12:00) New Zealand Time" => "Pacific/Auckland",
|
||||
"(GMT+12:00) Petropavlovsk-Kamchatski Standard Time" => "Asia/Kamchatka",
|
||||
"(GMT+12:00) Tuvalu Time" => "Pacific/Funafuti",
|
||||
"(GMT+12:00) Wake Island Time" => "Pacific/Wake",
|
||||
"(GMT+12:00) Wallis & Futuna Time" => "Pacific/Wallis",
|
||||
"(GMT+12:45) Chatham Time" => "Pacific/Chatham",
|
||||
"(GMT+13:00) Apia Time" => "Pacific/Apia",
|
||||
"(GMT+13:00) Phoenix Islands Time" => "Pacific/Enderbury",
|
||||
"(GMT+13:00) Tokelau Time" => "Pacific/Fakaofo",
|
||||
"(GMT+13:00) Tonga Standard Time" => "Pacific/Tongatapu",
|
||||
"(GMT+14:00) Line Islands Time" => "Pacific/Kiritimati"
|
||||
);
|
||||
}
|
||||
47
core/lib/saml2/xmlseclibs.php
Normal file
47
core/lib/saml2/xmlseclibs.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* xmlseclibs.php
|
||||
*
|
||||
* Copyright (c) 2007-2016, Robert Richards <rrichards@cdatazone.org>.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* * Neither the name of Robert Richards nor the names of his
|
||||
* contributors may be used to endorse or promote products derived
|
||||
* from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @author Robert Richards <rrichards@cdatazone.org>
|
||||
* @copyright 2007-2017 Robert Richards <rrichards@cdatazone.org>
|
||||
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
|
||||
* @version 3.0.1
|
||||
*/
|
||||
|
||||
$xmlseclibs_srcdir = dirname(__FILE__) . '/SAML2Core';
|
||||
require $xmlseclibs_srcdir . '/MoXMLSecurityKey.php';
|
||||
require $xmlseclibs_srcdir . '/MoXMLSecurityDSig.php';
|
||||
require $xmlseclibs_srcdir . '/MoXMLSecEnc.php';
|
||||
require $xmlseclibs_srcdir . '/Utils/MoXPath.php';
|
||||
@@ -1,11 +1,14 @@
|
||||
<?php
|
||||
define('CLIENT_PATH', dirname(__FILE__));
|
||||
use Classes\SAMLManager;use Classes\SettingsManager;use Utils\LogManager;define('CLIENT_PATH', dirname(__FILE__));
|
||||
include("config.base.php");
|
||||
include("include.common.php");
|
||||
include("server.includes.inc.php");
|
||||
|
||||
$companyName = \Classes\SettingsManager::getInstance()->getSetting('Company: Name');
|
||||
$gsuiteEnabled = \Classes\SettingsManager::getInstance()->getSetting('System: G Suite Enabled');
|
||||
$gsuiteEnabled = SettingsManager::getInstance()->getSetting('System: G Suite Enabled');
|
||||
$companyName = SettingsManager::getInstance()->getSetting('Company: Name');
|
||||
$SAMLAutoLogin = SettingsManager::getInstance()->getSetting('SAML: Auto Login') === "1";
|
||||
$SAMLEnabled = SettingsManager::getInstance()->getSetting("SAML: Enabled") == "1";
|
||||
$SAMLUserLoaded = false;
|
||||
|
||||
if (isset($_REQUEST['logout'])) {
|
||||
\Utils\SessionUtils::unsetClientSession();
|
||||
@@ -14,12 +17,19 @@ if (isset($_REQUEST['logout'])) {
|
||||
|
||||
if (empty($user) || empty($user->email)) {
|
||||
|
||||
if (!empty($_REQUEST['username']) && !empty($_REQUEST['password'])) {
|
||||
if (!isset($_REQUEST['logout']) && !isset($_POST['SAMLResponse']) && $SAMLAutoLogin && $SAMLEnabled && !empty(SettingsManager::getInstance()->getSetting("SAML: IDP SSO Url"))) {
|
||||
header("Location:" . SettingsManager::getInstance()->getSetting("SAML: IDP SSO Url"));
|
||||
exit();
|
||||
}
|
||||
|
||||
if ((!empty($_REQUEST['username']) && !empty($_REQUEST['password']))
|
||||
|| isset($_POST['SAMLResponse'])
|
||||
) {
|
||||
$suser = null;
|
||||
$ssoUserLoaded = false;
|
||||
|
||||
if($_REQUEST['username'] != "admin") {
|
||||
if (\Classes\SettingsManager::getInstance()->getSetting("LDAP: Enabled") == "1") {
|
||||
if (SettingsManager::getInstance()->getSetting("LDAP: Enabled") === "1") {
|
||||
$ldapResp = \Classes\LDAPManager::getInstance()->checkLDAPLogin($_REQUEST['username'], $_REQUEST['password']);
|
||||
if ($ldapResp->getStatus() == \Classes\IceResponse::ERROR) {
|
||||
header("Location:" . CLIENT_BASE_URL . "login.php?f=1");
|
||||
@@ -36,6 +46,39 @@ if (empty($user) || empty($user->email)) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($SAMLEnabled && isset($_POST['SAMLResponse'])) {
|
||||
$samlData = $_POST['SAMLResponse'];
|
||||
|
||||
if(array_key_exists('RelayState', $_POST) && !empty( $_POST['RelayState'] ) && $_POST['RelayState'] !== '/') {
|
||||
$relayState = htmlspecialchars($_POST['RelayState']);
|
||||
} else {
|
||||
$relayState = '';
|
||||
}
|
||||
|
||||
$ssoUserEmail = (new SAMLManager())->getSSOEmail($samlData, $relayState);
|
||||
LogManager::getInstance()->info('SSO SAML User Email:'.$ssoUserEmail);
|
||||
if (false === $ssoUserEmail) {
|
||||
header("Location:" . CLIENT_BASE_URL . "login.php?f=1");
|
||||
exit();
|
||||
} else {
|
||||
$mapping = SettingsManager::getInstance()->getSetting('SAML: Name ID Mapping');
|
||||
$suser = new \Users\Common\Model\User();
|
||||
if ($mapping === 'username') {
|
||||
$suser->Load("username = ?", array($ssoUserEmail));
|
||||
} else {
|
||||
$suser->Load("email = ?", array($ssoUserEmail));
|
||||
}
|
||||
|
||||
LogManager::getInstance()->info('SSO SAML User:'.print_r($suser, true));
|
||||
if (empty($suser)) {
|
||||
header("Location:" . CLIENT_BASE_URL . "login.php?f=1");
|
||||
exit();
|
||||
}
|
||||
$ssoUserLoaded = true;
|
||||
$SAMLUserLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($suser)) {
|
||||
$suser = new \Users\Common\Model\User();
|
||||
$suser->Load(
|
||||
@@ -59,7 +102,7 @@ if (empty($user) || empty($user->email)) {
|
||||
|
||||
$loginCsrf = \Utils\SessionUtils::getSessionObject('csrf-login');
|
||||
|
||||
if ($_REQUEST['csrf'] != $loginCsrf || empty($_REQUEST['csrf'])) {
|
||||
if (!$SAMLUserLoaded && ($_REQUEST['csrf'] != $loginCsrf || empty($_REQUEST['csrf']))) {
|
||||
$next = !empty($_REQUEST['next'])?'&next='.$_REQUEST['next']:'';
|
||||
header("Location:".CLIENT_BASE_URL."login.php?f=1".$next);
|
||||
exit();
|
||||
@@ -277,7 +320,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?>" style="max-width:100%;max-height:280px;"/>
|
||||
<img style="max-width: 100%;" src="<?=$logoFileUrl?>"/>
|
||||
</div>
|
||||
<hr/>
|
||||
<?php if ($gsuiteEnabled) {?>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?php
|
||||
$migrationList = [];
|
||||
$migrationList[] = 'v20210402_280006_modify_attendance_rep';
|
||||
$migrationList[] = 'v20210327_280005_saml_settings';
|
||||
$migrationList[] = 'v20210228_280003_add_share_with_employee';
|
||||
$migrationList[] = 'v20210228_280004_add_visible_to_document';
|
||||
$migrationList[] = 'v20201028_280002_update_gender';
|
||||
$migrationList[] = 'v20201028_280001_update_module_names';
|
||||
$migrationList[] = 'v20201017_271101_switch_off_photo_att';
|
||||
|
||||
24
core/migrations/v20210228_280003_add_share_with_employee.php
Normal file
24
core/migrations/v20210228_280003_add_share_with_employee.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Classes\Migration;
|
||||
|
||||
class v20210228_280003_add_share_with_employee extends AbstractMigration
|
||||
{
|
||||
|
||||
public function up()
|
||||
{
|
||||
|
||||
$sql = <<<'SQL'
|
||||
ALTER TABLE `Documents`
|
||||
ADD COLUMN `share_with_employee` enum('Yes','No') NULL DEFAULT 'Yes' AFTER `updated`;
|
||||
SQL;
|
||||
$this->executeQuery($sql);
|
||||
|
||||
return $this->executeQuery($sql);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
24
core/migrations/v20210228_280004_add_visible_to_document.php
Normal file
24
core/migrations/v20210228_280004_add_visible_to_document.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Classes\Migration;
|
||||
|
||||
class v20210228_280004_add_visible_to_document extends AbstractMigration
|
||||
{
|
||||
|
||||
public function up()
|
||||
{
|
||||
|
||||
$sql = <<<'SQL'
|
||||
ALTER TABLE `EmployeeDocuments`
|
||||
ADD COLUMN `visible_to` enum('Owner','Manager','Admin') NULL DEFAULT 'Owner' AFTER `expire_notification_last`;
|
||||
SQL;
|
||||
$this->executeQuery($sql);
|
||||
|
||||
return $this->executeQuery($sql);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
85
core/migrations/v20210327_280005_saml_settings.php
Normal file
85
core/migrations/v20210327_280005_saml_settings.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace Classes\Migration;
|
||||
|
||||
class v20210327_280005_saml_settings extends AbstractMigration {
|
||||
|
||||
public function up(){
|
||||
|
||||
$sql[] = <<<'SQL'
|
||||
REPLACE INTO `Settings` (`name`, `value`, `description`, `meta`, `category`)
|
||||
VALUES (
|
||||
'SAML: Enabled',
|
||||
'0',
|
||||
'Enable SAML Login',
|
||||
'["value", {"label":"Value","type":"select","source":[["1","Yes"],["0","No"]]}]',
|
||||
'SAML'
|
||||
);
|
||||
SQL;
|
||||
$sql[] = <<<'SQL'
|
||||
REPLACE INTO `Settings` (`name`, `value`, `description`, `meta`, `category`)
|
||||
VALUES (
|
||||
'SAML: Auto Login',
|
||||
'0',
|
||||
'Try to login via SAML by redirecting the user to SSO Url',
|
||||
'["value", {"label":"Value","type":"select","source":[["1","Yes"],["0","No"]]}]',
|
||||
'SAML'
|
||||
);
|
||||
SQL;
|
||||
|
||||
$sql[] = <<<'SQL'
|
||||
REPLACE INTO `Settings` (`name`, `value`, `description`, `meta`, `category`)
|
||||
VALUES (
|
||||
'SAML: IDP SSO Url',
|
||||
'',
|
||||
'Identity Provider Single Sign-On URL. Users will be redirected to this URL for authentication',
|
||||
'',
|
||||
'SAML'
|
||||
);
|
||||
SQL;
|
||||
|
||||
$sql[] = <<<'SQL'
|
||||
REPLACE INTO `Settings` (`name`, `value`, `description`, `meta`, `category`)
|
||||
VALUES (
|
||||
'SAML: IDP Issuer',
|
||||
'',
|
||||
'Identity Provider Issuer',
|
||||
'',
|
||||
'SAML'
|
||||
);
|
||||
SQL;
|
||||
|
||||
$sql[] = <<<'SQL'
|
||||
REPLACE INTO `Settings` (`name`, `value`, `description`, `meta`, `category`)
|
||||
VALUES (
|
||||
'SAML: X.509 Certificate',
|
||||
'',
|
||||
'X.509 Certificate provided by the Identity Provider. This certificate will be encrypted',
|
||||
'["value", {"label":"Value","type":"textarea"}]',
|
||||
'SAML'
|
||||
);
|
||||
SQL;
|
||||
|
||||
$sql[] = <<<'SQL'
|
||||
REPLACE INTO `Settings` (`name`, `value`, `description`, `meta`, `category`)
|
||||
VALUES (
|
||||
'SAML: Name ID Mapping',
|
||||
'email',
|
||||
'SAML Name id mapped to can be mapped to icehrm user email or the username',
|
||||
'["value", {"label":"Value","type":"select","source":[["email","Email"],["username","Username"]]}]',
|
||||
'SAML'
|
||||
);
|
||||
SQL;
|
||||
|
||||
$result = true;
|
||||
foreach ($sql as $query) {
|
||||
$result = $result && $this->executeQuery($query);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function down(){
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
32
core/migrations/v20210402_280006_modify_attendance_rep.php
Normal file
32
core/migrations/v20210402_280006_modify_attendance_rep.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
namespace Classes\Migration;
|
||||
|
||||
use Model\Report;
|
||||
|
||||
class v20210402_280006_modify_attendance_rep extends AbstractMigration {
|
||||
|
||||
public function up(){
|
||||
|
||||
$report = new Report();
|
||||
$report->Load('name = ?', ['Employee Time Tracking Report']);
|
||||
$report->parameters = <<<'JSON'
|
||||
[
|
||||
[ "employee", {"label":"Employee","type":"select2","allow-null":false,"remote-source":["Employee","id","first_name+last_name"]}],
|
||||
[ "date_start", {"label":"Start Date","type":"date", "validation":"none"}],
|
||||
[ "date_end", {"label":"End Date","type":"date","validation":"none"}],
|
||||
["period", { "label": "Period", "type": "select", "source": [["Current Month", "Current Month"], ["Last Month", "Last Month"], ["Last Week", "Last Week"], ["Last 2 Weeks", "Last 2 Weeks"]], "validation":"none" }]
|
||||
]
|
||||
JSON;
|
||||
$report->paramOrder = <<<'JSON'
|
||||
["employee","date_start","date_end","period"]
|
||||
JSON;
|
||||
|
||||
$report->Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function down(){
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -67,6 +67,8 @@ modJsList['tabEmployeeDocument'].setShowDelete(false);
|
||||
modJsList['tabEmployeeDocument'].setShowEdit(false);
|
||||
<?php }?>
|
||||
|
||||
modJsList['tabEmployeeDocument'].setRemoteTable(true);
|
||||
|
||||
|
||||
modJsList['tabEmployeeCompanyDocument'] = new EmployeeCompanyDocumentAdapter('CompanyDocument','EmployeeCompanyDocument');
|
||||
modJsList['tabEmployeeCompanyDocument'].setLoadMoreButton($("#loadMoreEmployeeCompanyDocument"));
|
||||
|
||||
@@ -7,6 +7,7 @@ use Classes\Migration\MigrationManager;
|
||||
use Classes\NotificationManager;
|
||||
use Classes\ReportHandler;
|
||||
use Classes\SettingsManager;
|
||||
use Model\BaseModel;
|
||||
use Utils\LogManager;
|
||||
|
||||
if (!defined("AWS_REGION")) {
|
||||
@@ -79,6 +80,18 @@ if (defined('REDIS_SERVER_URI')
|
||||
);
|
||||
}
|
||||
|
||||
$samlEnabled = SettingsManager::getInstance()->getSetting("SAML: Enabled");
|
||||
if ($samlEnabled === '1') {
|
||||
include APP_BASE_PATH . 'lib/saml2/Utilities.php';
|
||||
include APP_BASE_PATH . 'lib/saml2/Response.php';
|
||||
include APP_BASE_PATH . 'lib/saml2/encryption.php';
|
||||
include APP_BASE_PATH . 'lib/saml2/mo-saml-options-enum.php';
|
||||
}
|
||||
|
||||
$instanceId = SettingsManager::getInstance()->getSetting("Instance : ID");
|
||||
$instanceKey = SettingsManager::getInstance()->getSetting("Instance: Key");
|
||||
if(!defined('APP_SEC')){define('APP_SEC',sha1($instanceId.$instanceKey));}
|
||||
|
||||
$noJSONRequests = SettingsManager::getInstance()->getSetting("System: Do not pass JSON in request");
|
||||
|
||||
$debugMode = SettingsManager::getInstance()->getSetting("System: Debug Mode");
|
||||
@@ -123,10 +136,12 @@ if (defined('CLIENT_PATH')) {
|
||||
|
||||
$modelClassList = $moduleManagerObj->getModelClasses();
|
||||
$metaData = $moduleManagerObj->getModuleObject();
|
||||
/** @var BaseModel $modelClass */
|
||||
foreach ($modelClassList as $modelClass) {
|
||||
$modelClassWithNameSpace = $metaData['model_namespace']."\\".$modelClass;
|
||||
$modelClassWithNameSpace::SetDatabaseAdapter($dbLocal);
|
||||
$baseService->addModelClass($modelClass, $modelClassWithNameSpace);
|
||||
$modelClassObject = new $modelClassWithNameSpace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,7 @@ class AttendanceActionManager extends SubActionManager
|
||||
//Find any open punch
|
||||
$attendance = new Attendance();
|
||||
$attendance->Load(
|
||||
"employee = ? and DATE_FORMAT( in_time, '%Y-%m-%d' ) = ? and (out_time is NULL
|
||||
or out_time = '0000-00-00 00:00:00')",
|
||||
"employee = ? and DATE_FORMAT( in_time, '%Y-%m-%d' ) = ? and out_time is NULL",
|
||||
array($employee->id,$date)
|
||||
);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
namespace Classes;
|
||||
|
||||
use Model\BaseModel;
|
||||
use Utils\LogManager;
|
||||
|
||||
abstract class AbstractModuleManager
|
||||
@@ -254,7 +255,17 @@ abstract class AbstractModuleManager
|
||||
protected function addModelClass($className)
|
||||
{
|
||||
$this->modelClasses[] = $className;
|
||||
BaseService::getInstance()->addModelClass($className, $this->moduleObject['model_namespace']."\\".$className);
|
||||
$classWithNamespace = $this->moduleObject['model_namespace']."\\".$className;
|
||||
BaseService::getInstance()->addModelClass($className, $classWithNamespace);
|
||||
/** @var BaseModel $modelClass */
|
||||
$modelClass = new $classWithNamespace();
|
||||
if ($modelClass->isCustomFieldsEnabled()) {
|
||||
$objectName = $modelClass->getObjectName();
|
||||
BaseService::getInstance()->addCustomFieldClass(
|
||||
$className,
|
||||
(null === $objectName)? $className : $objectName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function addHistoryGeneric($type, $table, $refName, $refId, $field, $oldValue, $newValue)
|
||||
@@ -279,11 +290,11 @@ abstract class AbstractModuleManager
|
||||
BaseService::getInstance()->addCalculationHook($code, $name, $class, $method);
|
||||
}
|
||||
|
||||
public function install() {
|
||||
|
||||
public function install()
|
||||
{
|
||||
}
|
||||
|
||||
public function uninstall() {
|
||||
|
||||
public function uninstall()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ use Employees\Common\Model\EmployeeApproval;
|
||||
use FieldNames\Common\Model\CustomField;
|
||||
use FieldNames\Common\Model\FieldNameMapping;
|
||||
use Metadata\Common\Model\CalculationHook;
|
||||
use Model\BaseModel;
|
||||
use Model\DataEntryBackup;
|
||||
use Model\Setting;
|
||||
use Modules\Common\Model\Module;
|
||||
@@ -51,7 +52,8 @@ class BaseService
|
||||
public $calculationHooks = array();
|
||||
public $customFieldManager = null;
|
||||
public $migrationManager = null;
|
||||
public $modelClassMap = array();
|
||||
public $modelClassMap = [];
|
||||
public $customFieldsClassMap = [];
|
||||
public $currentProfileId = false;
|
||||
|
||||
protected $cacheService = null;
|
||||
@@ -167,6 +169,20 @@ class BaseService
|
||||
$this->modelClassMap[$modelClass] = $fullQualifiedName;
|
||||
}
|
||||
|
||||
public function getCustomFieldClassMap()
|
||||
{
|
||||
$map = [];
|
||||
foreach ($this->customFieldsClassMap as $key => $val) {
|
||||
$map[] = [$key, $val];
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
public function addCustomFieldClass($customFieldsClass, $objectName)
|
||||
{
|
||||
$this->customFieldsClassMap[$customFieldsClass] = $objectName;
|
||||
}
|
||||
|
||||
public function getModelClassName($name)
|
||||
{
|
||||
return $this->getFullQualifiedModelClassName($name);
|
||||
@@ -637,7 +653,10 @@ class BaseService
|
||||
|
||||
$processedList = array();
|
||||
foreach ($list as $obj) {
|
||||
$processedList[] = $this->cleanUpAdoDB($obj->postProcessGetData($obj));
|
||||
$processedObj = $this->cleanUpAdoDB($obj->postProcessGetData($obj));
|
||||
if (null !== $processedObj) {
|
||||
$processedList[] = $processedObj;
|
||||
}
|
||||
}
|
||||
|
||||
$list = $processedList;
|
||||
@@ -749,7 +768,7 @@ class BaseService
|
||||
$obj = $this->enrichObjectCustomFields($table, $obj);
|
||||
|
||||
$obj = $obj->postProcessGetElement($obj);
|
||||
return $this->cleanUpAdoDB($obj->postProcessGetData($obj));
|
||||
return $this->cleanUpAdoDB($obj);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -928,6 +947,7 @@ class BaseService
|
||||
{
|
||||
$fileFields = $this->fileFields;
|
||||
$nsTable = $this->getFullQualifiedModelClassName($table);
|
||||
/** @var BaseModel $ele */
|
||||
$ele = new $nsTable();
|
||||
|
||||
$ele->Load('id = ?', array($id));
|
||||
@@ -985,7 +1005,7 @@ class BaseService
|
||||
$dataEntryBackup->data = json_encode($newObj);
|
||||
$dataEntryBackup->Save();
|
||||
}
|
||||
|
||||
$ele->executePostDeleteActions($ele);
|
||||
$this->audit(IceConstants::AUDIT_DELETE, "Deleted an object in ".$table." [id:".$ele->id."]");
|
||||
}
|
||||
|
||||
@@ -998,6 +1018,7 @@ class BaseService
|
||||
}
|
||||
|
||||
$cfs = $this->customFieldManager->getCustomFields($table, $id);
|
||||
/** @var CustomField $cf */
|
||||
foreach ($cfs as $cf) {
|
||||
$cf->Delete();
|
||||
}
|
||||
@@ -1804,7 +1825,7 @@ END;
|
||||
$companyStructure->Load('id = ?', array($parentCompanyStructure));
|
||||
}
|
||||
} while (!empty($companyStructure->id)
|
||||
&& !empty($parentCompanyStructure)
|
||||
&& !empty($parentCompanyStructure)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,14 @@ class DomainAwareInputCleaner
|
||||
|
||||
private function isValidFilterValue($input)
|
||||
{
|
||||
if (is_array($input)) {
|
||||
$isValid = true;
|
||||
foreach ($input as $val) {
|
||||
$isValid = $isValid && !!preg_match('/^[-_: \d\p{L}]+$/u', $val);
|
||||
}
|
||||
|
||||
return $isValid;
|
||||
}
|
||||
return !!preg_match('/^[-_: \d\p{L}]+$/u', $input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ use Utils\LogManager;
|
||||
class ExtensionManager
|
||||
{
|
||||
const GROUP = 'extension';
|
||||
protected function processExtensionInDB() {
|
||||
protected function processExtensionInDB()
|
||||
{
|
||||
$dbModule = new \Modules\Common\Model\Module();
|
||||
$extensions = $dbModule->Find("mod_group = ?", array(self::GROUP));
|
||||
|
||||
@@ -20,7 +21,8 @@ class ExtensionManager
|
||||
return $extensionsInDB;
|
||||
}
|
||||
|
||||
public function getExtensionsPath() {
|
||||
public function getExtensionsPath()
|
||||
{
|
||||
return APP_BASE_PATH.'../extensions/';
|
||||
}
|
||||
|
||||
@@ -29,7 +31,8 @@ class ExtensionManager
|
||||
return json_decode(file_get_contents($this->getExtensionsPath().$extensionName.'/meta.json'));
|
||||
}
|
||||
|
||||
public function setupExtensions() {
|
||||
public function setupExtensions()
|
||||
{
|
||||
$menu = [];
|
||||
$extensions = [];
|
||||
$extensionDirs = scandir($this->getExtensionsPath());
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Classes;
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\SignatureInvalidException;
|
||||
|
||||
class JwtTokenService
|
||||
{
|
||||
@@ -22,7 +23,11 @@ class JwtTokenService
|
||||
public function getBaseToken($jwtToken)
|
||||
{
|
||||
$secret = APP_SEC.APP_PASSWORD;
|
||||
$jwt = JWT::decode($jwtToken, $secret, array('HS256'));
|
||||
try {
|
||||
$jwt = JWT::decode($jwtToken, $secret, array('HS256'));
|
||||
} catch (SignatureInvalidException $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (time() > intval($jwt->expire)) {
|
||||
return null;
|
||||
|
||||
@@ -15,6 +15,8 @@ class MemcacheService
|
||||
public static $openConnections = array();
|
||||
private static $me = null;
|
||||
|
||||
protected $inMemoryStore = [];
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
@@ -56,6 +58,15 @@ class MemcacheService
|
||||
}
|
||||
|
||||
public function set($key, $value, $expiry = 3600)
|
||||
{
|
||||
if (!$this->setInServer($key, $value, $expiry)) {
|
||||
$this->inMemoryStore[$this->compressKey($key)] = $value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setInServer($key, $value, $expiry = 3600)
|
||||
{
|
||||
if (!class_exists('\\Memcached')) {
|
||||
return false;
|
||||
@@ -74,6 +85,19 @@ class MemcacheService
|
||||
}
|
||||
|
||||
public function get($key)
|
||||
{
|
||||
$data = $this->getFromServer($key);
|
||||
if ($data) {
|
||||
return $data;
|
||||
}
|
||||
if (isset($this->inMemoryStore[$this->compressKey($key)])) {
|
||||
return $this->inMemoryStore[$this->compressKey($key)];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getFromServer($key)
|
||||
{
|
||||
if (!class_exists('\\Memcached')) {
|
||||
return false;
|
||||
|
||||
98
core/src/Classes/ModuleBuilderV2/ModuleBuilder.php
Normal file
98
core/src/Classes/ModuleBuilderV2/ModuleBuilder.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Thilina
|
||||
* Date: 8/20/17
|
||||
* Time: 9:47 AM
|
||||
*/
|
||||
|
||||
namespace Classes\ModuleBuilderV2;
|
||||
|
||||
use Classes\PermissionManager;
|
||||
|
||||
class ModuleBuilder
|
||||
{
|
||||
public $modules = array();
|
||||
public $user = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->user = \Classes\BaseService::getInstance()->getCurrentUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ModuleTab $module
|
||||
*/
|
||||
public function addModuleOrGroup($module)
|
||||
{
|
||||
$this->modules[] = $module;
|
||||
}
|
||||
|
||||
public function getTabHeadersHTML()
|
||||
{
|
||||
$html = "";
|
||||
foreach ($this->modules as $module) {
|
||||
$html .= $module->getHTML()."\r\n";
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function getTabPagesHTML()
|
||||
{
|
||||
$html = "";
|
||||
/* @var ModuleTab $module */
|
||||
foreach ($this->modules as $module) {
|
||||
if (get_class($module) === ModuleTab::class) {
|
||||
$html .= $module->getPageHTML()."\r\n";
|
||||
} else {
|
||||
/* @var ModuleTab $mod */
|
||||
foreach ($module->modules as $mod) {
|
||||
$html .= $mod->getPageHTML()."\r\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function getModJsHTML()
|
||||
{
|
||||
$moduleData = [
|
||||
'user_level' => $this->user->user_level,
|
||||
'permissions' => [
|
||||
]
|
||||
];
|
||||
|
||||
$html = "var modJsList = new Array();\r\n";
|
||||
$activeModule = "";
|
||||
/* @var ModuleTab $module */
|
||||
foreach ($this->modules as $module) {
|
||||
if (get_class($module) == ModuleTab::class) {
|
||||
$html .= $module->getJSObjectCode()."\r\n";
|
||||
|
||||
$modelClass = $module->modelPath;
|
||||
$moduleData['permissions'][$module->name] = PermissionManager::checkGeneralAccess(new $modelClass());
|
||||
|
||||
if ($module->isActive) {
|
||||
$activeModule = $module->name;
|
||||
}
|
||||
} else {
|
||||
/* @var ModuleTab $mod */
|
||||
foreach ($module->modules as $mod) {
|
||||
$modelClass = $mod->modelPath;
|
||||
$moduleData['permissions'][$mod->name] = PermissionManager::checkGeneralAccess(new $modelClass());
|
||||
|
||||
if ($module->isActive && $activeModule == "") {
|
||||
$activeModule = $mod->name;
|
||||
}
|
||||
$html .= $mod->getJSObjectCode()."\r\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$html .= "var modJs = modJsList['tab".$activeModule."'];\r\n";
|
||||
|
||||
$html = "var data = ".json_encode($moduleData).";\r\n".$html;
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
98
core/src/Classes/ModuleBuilderV2/ModuleTab.php
Normal file
98
core/src/Classes/ModuleBuilderV2/ModuleTab.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Thilina
|
||||
* Date: 8/20/17
|
||||
* Time: 9:47 AM
|
||||
*/
|
||||
|
||||
namespace Classes\ModuleBuilderV2;
|
||||
|
||||
class ModuleTab
|
||||
{
|
||||
public $modelPath;
|
||||
public $name;
|
||||
public $class;
|
||||
public $label;
|
||||
public $adapterName;
|
||||
public $filter;
|
||||
public $orderBy;
|
||||
public $isActive = false;
|
||||
public $isInsideGroup = false;
|
||||
public $options = array();
|
||||
|
||||
public function __construct(
|
||||
$modelPath,
|
||||
$name,
|
||||
$class,
|
||||
$label,
|
||||
$adapterName,
|
||||
$filter,
|
||||
$orderBy,
|
||||
$isActive = false,
|
||||
$options = array()
|
||||
) {
|
||||
|
||||
$this->modelPath = $modelPath;
|
||||
$this->name = $name;
|
||||
$this->class = $class;
|
||||
$this->label = $label;
|
||||
$this->adapterName = $adapterName;
|
||||
$this->filter = $filter;
|
||||
$this->orderBy = $orderBy;
|
||||
$this->isActive = $isActive;
|
||||
|
||||
$this->options = array_merge(
|
||||
$options,
|
||||
[
|
||||
"setObjectTypeName" => "'{$this->name}'",
|
||||
"setAccess" => "data.permissions.{$this->name} ? data.permissions.{$this->name} : {}",
|
||||
"setDataPipe" => 'new IceDataPipe(modJsList.tab' . $this->name . ')',
|
||||
"setRemoteTable" => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function getHTML()
|
||||
{
|
||||
$active = ($this->isActive)?"active":"";
|
||||
if (!$this->isInsideGroup) {
|
||||
return '<li class="' . $active . '"><a id="tab' . $this->name
|
||||
. '" href="#tabPage' . $this->name . '">' . t($this->label) . '</a></li>';
|
||||
} else {
|
||||
return '<li class="' . $active . '"><a id="tab' . $this->name
|
||||
. '" href="#tabPage' . $this->name . '">' . t($this->label) . '</a></li>';
|
||||
}
|
||||
}
|
||||
|
||||
public function getPageHTML()
|
||||
{
|
||||
$active = ($this->isActive)?" active":"";
|
||||
$html = '<div class="tab-pane'.$active.'" id="tabPage'.$this->name.'">'.
|
||||
'<div id="'.$this->name.'Table" class="reviewBlock" data-content="List" style="padding-left:5px;"></div>'.
|
||||
'<div id="'.$this->name.'Form"></div>'.
|
||||
'<div id="'.$this->name.'FilterForm"></div>'.
|
||||
'</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function getJSObjectCode()
|
||||
{
|
||||
$js = "";
|
||||
if (empty($this->filter)) {
|
||||
$js.= "modJsList['tab" . $this->name . "'] = new " .
|
||||
$this->adapterName . "('" . $this->class . "','" . $this->name . "','','".$this->orderBy. "');\r\n";
|
||||
} else {
|
||||
$js.= "modJsList['tab" . $this->name . "'] = new " .
|
||||
$this->adapterName . "('" . $this->class . "','" . $this->name . "'," .
|
||||
$this->filter . ",'".$this->orderBy. "');\r\n";
|
||||
}
|
||||
|
||||
foreach ($this->options as $key => $val) {
|
||||
$js.= "modJsList['tab" . $this->name . "'].".$key."(".$val. ");\r\n";
|
||||
}
|
||||
|
||||
return $js;
|
||||
}
|
||||
}
|
||||
53
core/src/Classes/ModuleBuilderV2/ModuleTabGroup.php
Normal file
53
core/src/Classes/ModuleBuilderV2/ModuleTabGroup.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: Thilina
|
||||
* Date: 8/20/17
|
||||
* Time: 9:48 AM
|
||||
*/
|
||||
|
||||
namespace Classes\ModuleBuilderV2;
|
||||
|
||||
class ModuleTabGroup
|
||||
{
|
||||
public $name;
|
||||
public $label;
|
||||
public $isActive = false;
|
||||
public $modules = array();
|
||||
|
||||
public function __construct($name, $label)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->label = $label;
|
||||
}
|
||||
|
||||
public function addModuleTab($moduleTab)
|
||||
{
|
||||
if ($moduleTab->isActive) {
|
||||
$this->isActive = true;
|
||||
$moduleTab->isActive = false;
|
||||
}
|
||||
$moduleTab->isInsideGroup = true;
|
||||
$this->modules[] = $moduleTab;
|
||||
}
|
||||
|
||||
public function getHTML()
|
||||
{
|
||||
$html = "";
|
||||
$active = ($this->isActive)?" active":"";
|
||||
|
||||
$html.= '<li class="dropdown'.$active.'">'."\r\n".
|
||||
'<a href="#" id="'.$this->name.
|
||||
'" class="dropdown-toggle" data-toggle="dropdown" aria-controls="'.$this->name.
|
||||
'-contents">'.$this->label.' <span class="caret"></span></a>'."\r\n".
|
||||
'<ul class="dropdown-menu" role="menu" aria-labelledby="'.$this->name.'" id="'.$this->name.'-contents">';
|
||||
|
||||
foreach ($this->modules as $module) {
|
||||
$html.= $module->getHTML();
|
||||
}
|
||||
|
||||
$html .= "</ul></li>";
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,7 @@ class PasswordManager
|
||||
return new IceResponse(IceResponse::ERROR, $error);
|
||||
}
|
||||
|
||||
if (strlen($password) > 20) {
|
||||
if (strlen($password) > 30) {
|
||||
$error = "Password too long";
|
||||
|
||||
return new IceResponse(IceResponse::ERROR, $error);
|
||||
|
||||
@@ -424,10 +424,8 @@ class RestEndPoint
|
||||
$token = $_GET['token'];
|
||||
}
|
||||
|
||||
if (strlen($token) > 32) {
|
||||
$tokenService = new JwtTokenService();
|
||||
$token = $tokenService->getBaseToken($token);
|
||||
}
|
||||
$tokenService = new JwtTokenService();
|
||||
$token = $tokenService->getBaseToken($token);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
100
core/src/Classes/SAMLManager.php
Normal file
100
core/src/Classes/SAMLManager.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Classes;
|
||||
|
||||
use \RobRichards\XMLSecLibs\MoXMLSecurityKey;
|
||||
use Utils\LogManager;
|
||||
|
||||
class SAMLManager
|
||||
{
|
||||
public function getSSOEmail($samlData, $relayState)
|
||||
{
|
||||
// Service Providers Assertion Consumer Service (ACS) URL
|
||||
$acsUrl = CLIENT_BASE_URL.'login.php';
|
||||
$samlResponse = htmlspecialchars($samlData);
|
||||
|
||||
$samlResponse = base64_decode($samlResponse);
|
||||
|
||||
$document = new \DOMDocument();
|
||||
$document->loadXML($samlResponse);
|
||||
$samlResponseXml = $document->firstChild;
|
||||
|
||||
$doc = $document->documentElement;
|
||||
$xpath = new \DOMXpath($document);
|
||||
$xpath->registerNamespace('samlp', 'urn:oasis:names:tc:SAML:2.0:protocol');
|
||||
$xpath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:2.0:assertion');
|
||||
|
||||
$status = $xpath->query('/samlp:Response/samlp:Status/samlp:StatusCode', $doc);
|
||||
$statusString = $status->item(0)->getAttribute('Value');
|
||||
|
||||
|
||||
$statusArray = explode(':', $statusString);
|
||||
if (array_key_exists(7, $statusArray)) {
|
||||
$status = $statusArray[7];
|
||||
}
|
||||
|
||||
if ('Success' !== $status) {
|
||||
$StatusMessage = $xpath->query('/samlp:Response/samlp:Status/samlp:StatusMessage', $doc)->item(0);
|
||||
LogManager::getInstance()->error('SAML login failed: status = '. $status);
|
||||
if (!empty($StatusMessage)) {
|
||||
$StatusMessage = $StatusMessage->nodeValue;
|
||||
LogManager::getInstance()->error('SAML login failed: status message = '. $StatusMessage);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$x509cert = SettingsManager::getInstance()->getSetting('SAML: X.509 Certificate');
|
||||
|
||||
$samlResponse = new \SAML2_Response($samlResponseXml);
|
||||
$responseSignatureData = $samlResponse->getSignatureData();
|
||||
$assertionSignatureData = current($samlResponse->getAssertions())->getSignatureData();
|
||||
|
||||
$certFingerPrint = MoXMLSecurityKey::getRawThumbprint($x509cert);
|
||||
$certFingerPrint = preg_replace('/\s+/', '', $certFingerPrint);
|
||||
$validSignature = false;
|
||||
if (!empty($responseSignatureData)) {
|
||||
$validSignature = \Utilities::processResponse(
|
||||
$acsUrl,
|
||||
$certFingerPrint,
|
||||
$responseSignatureData,
|
||||
$samlResponse,
|
||||
0,
|
||||
$relayState
|
||||
);
|
||||
LogManager::getInstance()->error('SAML: response signature validity :'.$validSignature);
|
||||
}
|
||||
|
||||
if (!empty($assertionSignatureData)) {
|
||||
$validSignature = \Utilities::processResponse(
|
||||
$acsUrl,
|
||||
$certFingerPrint,
|
||||
$assertionSignatureData,
|
||||
$samlResponse,
|
||||
0,
|
||||
$relayState
|
||||
);
|
||||
LogManager::getInstance()->error('SAML: response signature validity :'.$validSignature);
|
||||
}
|
||||
|
||||
if (!$validSignature) {
|
||||
LogManager::getInstance()->error('Invalid response or assertion signature');
|
||||
return false;
|
||||
}
|
||||
|
||||
$issuer = current($samlResponse->getAssertions())->getIssuer();
|
||||
$assertion = current($samlResponse->getAssertions());
|
||||
$audiences = $assertion->getValidAudiences();
|
||||
$expectedIssuer = SettingsManager::getInstance()->getSetting('SAML: IDP Issuer');
|
||||
if ($issuer !== $expectedIssuer) {
|
||||
LogManager::getInstance()->error('SAML Invalid Issuer :'.$issuer.' expected :'.$expectedIssuer);
|
||||
return false;
|
||||
}
|
||||
|
||||
$ssoEmail = current(current($samlResponse->getAssertions())->getNameId());
|
||||
if (!$ssoEmail) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $ssoEmail;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,22 @@
|
||||
<?php
|
||||
namespace Classes;
|
||||
|
||||
use Classes\Crypt\AesCtr;
|
||||
use Model\Setting;
|
||||
|
||||
class SettingsManager
|
||||
{
|
||||
|
||||
const ENCRYPTED_PREFIX = 'iceenc_';
|
||||
|
||||
private static $me = null;
|
||||
|
||||
private $encryptedSettings = [];
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
$this->addEncryptedSetting('SAML: X.509 Certificate');
|
||||
$this->addEncryptedSetting('LDAP: Manager Password');
|
||||
}
|
||||
|
||||
public static function getInstance()
|
||||
@@ -21,9 +28,67 @@ class SettingsManager
|
||||
return self::$me;
|
||||
}
|
||||
|
||||
public function addEncryptedSetting($name)
|
||||
{
|
||||
if (!$this->isEncryptedSetting($name)) {
|
||||
$this->encryptedSettings[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
public function isEncryptedSetting($name)
|
||||
{
|
||||
return in_array($name, $this->encryptedSettings);
|
||||
}
|
||||
|
||||
public function getInstanceKey()
|
||||
{
|
||||
$settings = new Setting();
|
||||
$settings->Load("name = ?", array("Instance: Key"));
|
||||
if ($settings->name != "Instance: Key") {
|
||||
return null;
|
||||
}
|
||||
return $settings->value;
|
||||
}
|
||||
|
||||
private function encrypt($value)
|
||||
{
|
||||
$id = BaseService::getInstance()->getInstanceId();
|
||||
$key = $this->getInstanceKey();
|
||||
return AesCtr::encrypt($value, $id.$key, 256);
|
||||
}
|
||||
|
||||
public function encryptSetting($name, $value)
|
||||
{
|
||||
// check the existence of prefix and encrypt only if need to avoid double encryption
|
||||
if ($this->isEncryptedSetting($name)
|
||||
&& substr($value, 0, strlen(self::ENCRYPTED_PREFIX)) !== self::ENCRYPTED_PREFIX
|
||||
) {
|
||||
$value = self::ENCRYPTED_PREFIX.$this->encrypt($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function decrypt($value)
|
||||
{
|
||||
$id = BaseService::getInstance()->getInstanceId();
|
||||
$key = $this->getInstanceKey();
|
||||
return AesCtr::decrypt($value, $id.$key, 256);
|
||||
}
|
||||
|
||||
public function decryptSetting($name, $value)
|
||||
{
|
||||
if ($this->isEncryptedSetting($name)
|
||||
&& substr($value, 0, strlen(self::ENCRYPTED_PREFIX)) === self::ENCRYPTED_PREFIX
|
||||
) {
|
||||
$value = $this->decrypt(substr($value, strlen(self::ENCRYPTED_PREFIX)));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getSetting($name)
|
||||
{
|
||||
|
||||
if (class_exists("\\Classes\\ProVersion")) {
|
||||
$pro = new ProVersion();
|
||||
$val = $pro->getSetting($name);
|
||||
@@ -34,20 +99,28 @@ class SettingsManager
|
||||
|
||||
$setting = new Setting();
|
||||
$setting->Load("name = ?", array($name));
|
||||
$value = null;
|
||||
if ($setting->name == $name) {
|
||||
return $setting->value;
|
||||
$value = $setting->value;
|
||||
}
|
||||
return null;
|
||||
|
||||
if (null === $value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->decryptSetting($name, $value);
|
||||
}
|
||||
|
||||
public function setSetting($name, $value)
|
||||
{
|
||||
$setting = new Setting();
|
||||
$setting->Load("name = ?", array($name));
|
||||
if ($setting->name == $name) {
|
||||
$setting->value = $value;
|
||||
$setting->Save();
|
||||
if ($setting->name !== $name) {
|
||||
return;
|
||||
}
|
||||
|
||||
$setting->value = $this->encryptSetting($name, $value);
|
||||
$setting->Save();
|
||||
}
|
||||
|
||||
public function addSetting($name, $value)
|
||||
@@ -55,14 +128,22 @@ class SettingsManager
|
||||
$setting = new Setting();
|
||||
$setting->Load("name = ?", array($name));
|
||||
if ($setting->name == $name) {
|
||||
$setting->value = $value;
|
||||
$setting->value = $this->encryptSetting($name, $value);
|
||||
$setting->Save();
|
||||
} else {
|
||||
$setting->name = $name;
|
||||
$setting->value = $value;
|
||||
$setting->description = $value;
|
||||
$setting->value = $this->encryptSetting($name, $value);
|
||||
$setting->description = '';
|
||||
$setting->meta = '';
|
||||
$setting->Save();
|
||||
}
|
||||
}
|
||||
|
||||
public function getDeprecatedSettings()
|
||||
{
|
||||
return [
|
||||
'Attendance: Work Week Start Day',
|
||||
'Attendance: Overtime Calculation Class'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,14 @@ use Classes\IceResponse;
|
||||
use Classes\ModuleAccess;
|
||||
use Employees\Common\Model\Employee;
|
||||
use Model\BaseModel;
|
||||
use Model\CustomFieldTrait;
|
||||
|
||||
class CompanyStructure extends BaseModel
|
||||
{
|
||||
use CustomFieldTrait;
|
||||
public $objectName = 'Company Structures';
|
||||
protected $allowCustomFields = true;
|
||||
|
||||
public $table = 'CompanyStructures';
|
||||
|
||||
public function getAdminAccess()
|
||||
|
||||
25
core/src/CustomField/Admin/Api/CustomFieldAdminManager.php
Normal file
25
core/src/CustomField/Admin/Api/CustomFieldAdminManager.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace CustomField\Admin\Api;
|
||||
|
||||
use Classes\AbstractModuleManager;
|
||||
|
||||
class CustomFieldAdminManager extends AbstractModuleManager
|
||||
{
|
||||
public function initializeUserClasses()
|
||||
{
|
||||
}
|
||||
|
||||
public function initializeFieldMappings()
|
||||
{
|
||||
}
|
||||
|
||||
public function initializeDatabaseErrorMappings()
|
||||
{
|
||||
}
|
||||
|
||||
public function setupModuleClassDefinitions()
|
||||
{
|
||||
$this->addModelClass('CustomField');
|
||||
}
|
||||
}
|
||||
@@ -45,10 +45,12 @@ class DocumentTaskCreator implements TaskCreator
|
||||
return 0;
|
||||
}
|
||||
|
||||
$query = "select count(id) as c from EmployeeDocuments where employee = ? and valid_until < ?";
|
||||
$query
|
||||
= "select count(id) as c from EmployeeDocuments where employee = ? and valid_until < ? and visible_to = ?";
|
||||
|
||||
$user->DB()->SetFetchMode(ADODB_FETCH_ASSOC);
|
||||
$rs = $user->DB()->Execute($query, [$employee->id, date('Y-m-d')]);
|
||||
// TODO - sending notifications only for Owner documents, this need to be extended later
|
||||
$rs = $user->DB()->Execute($query, [$employee->id, date('Y-m-d'), 'Owner']);
|
||||
$count = $rs->fields['c'];
|
||||
|
||||
return $count;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace Documents\Common\Model;
|
||||
|
||||
use Classes\BaseService;
|
||||
use Classes\ModuleAccess;
|
||||
use Model\BaseModel;
|
||||
|
||||
@@ -26,4 +27,23 @@ class Document extends BaseModel
|
||||
new ModuleAccess('documents', 'user'),
|
||||
];
|
||||
}
|
||||
|
||||
public function fieldValueMethods()
|
||||
{
|
||||
return ['getDocumentTypesForUser'];
|
||||
}
|
||||
|
||||
public function getDocumentTypesForUser()
|
||||
{
|
||||
$documents = new Document();
|
||||
if (BaseService::getInstance()->currentUser->user_level === 'Employee'
|
||||
|| BaseService::getInstance()->currentUser->user_level === 'Restricted Employee'
|
||||
) {
|
||||
$documents = $documents->Find('share_with_employee = ?', ['Yes']);
|
||||
} else {
|
||||
$documents = $documents->Find('1 = 1');
|
||||
}
|
||||
|
||||
return $documents;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,65 @@
|
||||
|
||||
namespace Documents\Common\Model;
|
||||
|
||||
use Classes\BaseService;
|
||||
use Classes\IceResponse;
|
||||
use Classes\ModuleAccess;
|
||||
use Employees\Common\Model\Employee;
|
||||
use Model\BaseModel;
|
||||
|
||||
class EmployeeDocument extends BaseModel
|
||||
{
|
||||
public $table = 'EmployeeDocuments';
|
||||
|
||||
private function getHiddenDocumentTypeIds()
|
||||
{
|
||||
}
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
public function Find($whereOrderBy, $bindarr = false, $cache = false, $pkeysArr = false, $extra = array())
|
||||
{
|
||||
$find = '';
|
||||
$user = BaseService::getInstance()->getCurrentUser();
|
||||
|
||||
if ($user->user_level == 'Employee') {
|
||||
$find = ' visible_to = \'Owner\' AND ';
|
||||
$document = new Document();
|
||||
$hiddenDocumentTypes = $document->Find(
|
||||
"share_with_employee = ?",
|
||||
['No']
|
||||
);
|
||||
|
||||
$hiddenTypeIds = [];
|
||||
foreach ($hiddenDocumentTypes as $hiddenDocumentType) {
|
||||
$hiddenTypeIds[] = $hiddenDocumentType->id;
|
||||
}
|
||||
|
||||
if(count($hiddenTypeIds) > 0) {
|
||||
$find .= ' document NOT IN (\''.implode('\',\'', $hiddenTypeIds).'\') AND ';
|
||||
}
|
||||
|
||||
return parent::Find($find.$whereOrderBy, $bindarr, $pkeysArr, $extra);
|
||||
|
||||
} else if ($user->user_level == 'Manager') {
|
||||
// Original $whereOrderBy already contain employee selection
|
||||
// So here if isSubOrdinates is true if the query coming from Employee -> Document Management
|
||||
// In that case we need to show documents from sub ordinates
|
||||
// These docs can can be owner and manager both
|
||||
if (isset($isSubOrdinates) && $isSubOrdinates) {
|
||||
$find .= ' visible_to in (\'Owner\', \'Manager\') AND ';
|
||||
} else {
|
||||
// Here we are showing the documents for the manager
|
||||
// If someone upload a document for this manager and make it visible to manager,
|
||||
// that means only the manager of this manager can see the document
|
||||
// So it should not be visible to this manager
|
||||
$find .= ' visible_to in (\'Owner\') AND ';
|
||||
}
|
||||
}
|
||||
|
||||
return parent::Find($find.$whereOrderBy, $bindarr, $pkeysArr, $extra);
|
||||
}
|
||||
// @codingStandardsIgnoreEnd
|
||||
|
||||
public function getAdminAccess()
|
||||
{
|
||||
return array("get","element","save","delete");
|
||||
@@ -26,6 +77,8 @@ class EmployeeDocument extends BaseModel
|
||||
return array("get","element","save","delete");
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getUserAccess()
|
||||
{
|
||||
return array("get");
|
||||
|
||||
@@ -8,6 +8,7 @@ use Classes\ModuleAccess;
|
||||
use Company\Common\Model\CompanyStructure;
|
||||
use Metadata\Common\Model\Country;
|
||||
use Model\BaseModel;
|
||||
use Model\CustomFieldTrait;
|
||||
|
||||
class Employee extends BaseModel
|
||||
{
|
||||
@@ -90,6 +91,11 @@ class Employee extends BaseModel
|
||||
$this->oldObj = BaseService::getInstance()->getElement('Employee', $obj->id, $mapping, true);
|
||||
}
|
||||
|
||||
public function isCustomFieldsEnabled()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private function saveHistory($obj)
|
||||
{
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ use Classes\BaseService;
|
||||
use Classes\IceResponse;
|
||||
use Classes\ModuleAccess;
|
||||
use Model\BaseModel;
|
||||
use Utils\LogManager;
|
||||
|
||||
class CustomField extends BaseModel
|
||||
{
|
||||
@@ -58,6 +59,22 @@ class CustomField extends BaseModel
|
||||
return new IceResponse(IceResponse::SUCCESS, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param CustomField $obj
|
||||
*/
|
||||
public function executePostDeleteActions($obj)
|
||||
{
|
||||
$ret = $this->DB()->Execute(
|
||||
'DELETE FROM CustomFieldValues where type = ? and name = ?',
|
||||
[$obj->type, $obj->name]
|
||||
);
|
||||
|
||||
if (!$ret) {
|
||||
$this->lastError = $this->db()->ErrorMsg();
|
||||
LogManager::getInstance()->error('Error deleting custom field values: '.$this->DB()->ErrorMsg());
|
||||
}
|
||||
}
|
||||
|
||||
public function getModuleAccess()
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -11,6 +11,8 @@ use Utils\LogManager;
|
||||
|
||||
class BaseModel extends \ADOdb_Active_Record
|
||||
{
|
||||
public $objectName = null;
|
||||
protected $allowCustomFields = false;
|
||||
|
||||
public $keysToIgnore = array(
|
||||
"_table",
|
||||
@@ -195,6 +197,16 @@ class BaseModel extends \ADOdb_Active_Record
|
||||
{
|
||||
}
|
||||
|
||||
public function executePostDeleteActions($obj)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* If null is returned the object wont be included in the response
|
||||
*
|
||||
* @param $obj
|
||||
* @return mixed
|
||||
*/
|
||||
public function postProcessGetData($obj)
|
||||
{
|
||||
return $obj;
|
||||
@@ -340,4 +352,14 @@ class BaseModel extends \ADOdb_Active_Record
|
||||
return $ok;
|
||||
}
|
||||
// @codingStandardsIgnoreEnd
|
||||
|
||||
public function getObjectName()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isCustomFieldsEnabled()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
16
core/src/Model/CustomFieldTrait.php
Normal file
16
core/src/Model/CustomFieldTrait.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Model;
|
||||
|
||||
trait CustomFieldTrait
|
||||
{
|
||||
public function getObjectName()
|
||||
{
|
||||
return $this->objectName;
|
||||
}
|
||||
|
||||
public function isCustomFieldsEnabled()
|
||||
{
|
||||
return $this->allowCustomFields;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ use Classes\BaseService;
|
||||
use Classes\IceResponse;
|
||||
use Classes\ModuleAccess;
|
||||
use Classes\RestApiManager;
|
||||
use Classes\SettingsManager;
|
||||
use Users\Common\Model\User;
|
||||
|
||||
class Setting extends BaseModel
|
||||
@@ -74,6 +75,12 @@ class Setting extends BaseModel
|
||||
return new IceResponse(IceResponse::SUCCESS, "");
|
||||
}
|
||||
|
||||
public function executePreSaveActions($obj)
|
||||
{
|
||||
$obj->value = SettingsManager::getInstance()->encryptSetting($obj->name, $obj->value);
|
||||
return new IceResponse(IceResponse::SUCCESS, $obj);
|
||||
}
|
||||
|
||||
public function executePreUpdateActions($obj)
|
||||
{
|
||||
return $this->executePreSaveActions($obj);
|
||||
@@ -87,5 +94,17 @@ class Setting extends BaseModel
|
||||
{
|
||||
}
|
||||
|
||||
public function postProcessGetData($obj)
|
||||
{
|
||||
if (in_array($obj->name, SettingsManager::getInstance()->getDeprecatedSettings())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (strlen($obj->value) > 30) {
|
||||
$obj->value = substr($obj->value, 0, 30).'...';
|
||||
}
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public $table = 'Settings';
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace Reports\Admin\Reports;
|
||||
|
||||
use Attendance\Common\Model\Attendance;
|
||||
use Company\Common\Model\CompanyStructure;
|
||||
use Employees\Common\Model\Employee;
|
||||
use Reports\Admin\Api\ClassBasedReportBuilder;
|
||||
use Reports\Admin\Api\ReportBuilderInterface;
|
||||
@@ -16,6 +17,17 @@ class EmployeeTimeTrackReport extends ClassBasedReportBuilder implements ReportB
|
||||
LogManager::getInstance()->info(json_encode($report));
|
||||
LogManager::getInstance()->info(json_encode($req));
|
||||
|
||||
if (empty($req['period'])
|
||||
&& (
|
||||
empty($req['date_start'])
|
||||
|| 'NULL' === $req['date_start']
|
||||
|| empty($req['date_end'])
|
||||
|| 'NULL' === $req['date_end']
|
||||
)
|
||||
) {
|
||||
$req['period'] = 'Current Month';
|
||||
}
|
||||
|
||||
$employeeTimeEntry = new EmployeeTimeEntry();
|
||||
|
||||
$timeEntryList = $employeeTimeEntry->Find(
|
||||
@@ -38,6 +50,8 @@ class EmployeeTimeTrackReport extends ClassBasedReportBuilder implements ReportB
|
||||
//$minutes = (int)($seconds/60);
|
||||
//Find Attendance Entries
|
||||
|
||||
$req = $this->setRequestDatesBasedOnThePeriod($req);
|
||||
|
||||
$attendance = new Attendance();
|
||||
$atteandanceList = $attendance->Find(
|
||||
"employee = ? and date(in_time) >= ? and date(out_time) <= ? and in_time < out_time",
|
||||
@@ -66,21 +80,44 @@ class EmployeeTimeTrackReport extends ClassBasedReportBuilder implements ReportB
|
||||
$employeeObject = new Employee();
|
||||
$employeeObject->Load("id = ?", array($req['employee']));
|
||||
|
||||
$company = new CompanyStructure();
|
||||
$company->Load('id = ?', [$employeeObject->department]);
|
||||
|
||||
$reportData = array();
|
||||
//$reportData[] = array($employeeObject->first_name." ".$employeeObject->last_name,"","","","");
|
||||
$reportData[] = array("Date","First Punch-In Time","Last Punch-Out Time","Time in Office","Time in Timesheets");
|
||||
$reportData = [];
|
||||
$reportData[] = [
|
||||
"Date",
|
||||
"First Punch-In Time",
|
||||
"Last Punch-Out Time",
|
||||
"Time in Attendance (Hours)",
|
||||
"Time in Time-sheets (Hours)",
|
||||
];
|
||||
$reportData[] = ["Employee:",$employeeObject->first_name." ".$employeeObject->last_name,"","",""];
|
||||
$reportData[] = ["Department:",$company->title,"","",""];
|
||||
$reportData[] = ["Total Days:","","","",""];
|
||||
|
||||
|
||||
//Iterate date range
|
||||
|
||||
$interval = \DateInterval::createFromDateString('1 day');
|
||||
$period = new \DatePeriod(new \DateTime($req['date_start']), $interval, new \DateTime($req['date_end']));
|
||||
$period = new \DatePeriod(
|
||||
new \DateTime($req['date_start']),
|
||||
$interval,
|
||||
(new \DateTime($req['date_end']))->modify('+1 day')
|
||||
);
|
||||
|
||||
$totalHoursOffice = 0;
|
||||
$totalHoursTimeSheets = 0;
|
||||
$totalDaysForThePeriod = 0;
|
||||
|
||||
foreach ($period as $dt) {
|
||||
$dataRow = array();
|
||||
$key = $dt->format("Y-m-d");
|
||||
|
||||
if (!isset($firstTimeInArray[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$totalDaysForThePeriod++;
|
||||
$dataRow[] = $key;
|
||||
|
||||
if (isset($firstTimeInArray[$key])) {
|
||||
@@ -107,8 +144,45 @@ class EmployeeTimeTrackReport extends ClassBasedReportBuilder implements ReportB
|
||||
$dataRow[] = 0;
|
||||
}
|
||||
|
||||
$totalHoursOffice += $dataRow[3];
|
||||
$totalHoursTimeSheets += $dataRow[4];
|
||||
|
||||
$dataRow[3] = number_format($dataRow[3], 2, '.', '');
|
||||
$dataRow[4] = number_format($dataRow[4], 2, '.', '');
|
||||
|
||||
$reportData[] = $dataRow;
|
||||
}
|
||||
|
||||
$reportData[3][1] = $totalDaysForThePeriod;
|
||||
|
||||
$totalHoursOffice = number_format($totalHoursOffice, 2, '.', '');
|
||||
$totalHoursTimeSheets = number_format($totalHoursTimeSheets, 2, '.', '');
|
||||
|
||||
$reportData[] = ["Total","","",$totalHoursOffice,$totalHoursTimeSheets];
|
||||
|
||||
return $reportData;
|
||||
}
|
||||
|
||||
private function setRequestDatesBasedOnThePeriod($req)
|
||||
{
|
||||
if (empty($req['period'])) {
|
||||
return $req;
|
||||
}
|
||||
|
||||
if ($req['period'] === 'Current Month') {
|
||||
$req['date_start'] = date('Y-m-01', strtotime('now'));
|
||||
$req['date_end'] = date('Y-m-d', strtotime('now'));
|
||||
} elseif ($req['period'] === 'Last Month') {
|
||||
$req['date_start'] = date('Y-m-d', strtotime('first day of last month'));
|
||||
$req['date_end'] = date('Y-m-d', strtotime('last day of last month'));
|
||||
} elseif ($req['period'] === 'Last Week') {
|
||||
$req['date_start'] = date("Y-m-d", strtotime("-7 days"));
|
||||
$req['date_end'] = date('Y-m-d', strtotime('now'));
|
||||
} elseif ($req['period'] === 'Last 2 Weeks') {
|
||||
$req['date_start'] = date("Y-m-d", strtotime("-14 days"));
|
||||
$req['date_end'] = date('Y-m-d', strtotime('now'));
|
||||
}
|
||||
|
||||
return $req;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,9 @@ class StaffDirectory extends Employee
|
||||
foreach ($res as $entry) {
|
||||
$emp = new BaseModel();
|
||||
$emp->id = $entry->id;
|
||||
$emp = FileService::getInstance()->updateProfileImage($emp);
|
||||
//$emp->image = str_replace("_img_",$emp->image,$img);
|
||||
$emp->first_name = $entry->first_name;
|
||||
$emp->last_name = $entry->last_name;
|
||||
$emp = FileService::getInstance()->updateSmallProfileImage($emp);
|
||||
$emp->job_title = $entry->job_title;
|
||||
$emp->department = $entry->department;
|
||||
$emp->work_phone = $entry->work_phone;
|
||||
@@ -41,6 +40,11 @@ class StaffDirectory extends Employee
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function isCustomFieldsEnabled()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
public function Insert()
|
||||
{
|
||||
|
||||
@@ -11,10 +11,15 @@ namespace Travel\Common\Model;
|
||||
use Classes\ModuleAccess;
|
||||
use Classes\SettingsManager;
|
||||
use Model\ApproveModel;
|
||||
use Model\CustomFieldTrait;
|
||||
|
||||
class EmployeeTravelRecord extends ApproveModel
|
||||
{
|
||||
use CustomFieldTrait;
|
||||
|
||||
public $table = 'EmployeeTravelRecords';
|
||||
public $objectName = 'Travel Request';
|
||||
protected $allowCustomFields = true;
|
||||
|
||||
public $notificationModuleName = "Travel Management";
|
||||
public $notificationUnitName = "TravelRequest";
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Travel\Common\Model;
|
||||
|
||||
class EmployeeTravelRecordApproval extends EmployeeTravelRecord
|
||||
{
|
||||
protected $allowCustomFields = false;
|
||||
|
||||
// @codingStandardsIgnoreStart
|
||||
public function Find($whereOrderBy, $bindarr = false, $cache = false, $pkeysArr = false, $extra = array())
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
git keep
|
||||
43
gulpfile.js
43
gulpfile.js
@@ -243,6 +243,7 @@ gulp.task('admin-js', (done) => {
|
||||
const files = [
|
||||
'attendance',
|
||||
'company_structure',
|
||||
'custom_fields',
|
||||
'clients',
|
||||
'charts',
|
||||
'dashboard',
|
||||
@@ -359,48 +360,6 @@ 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'));
|
||||
|
||||
125
package-lock.json
generated
125
package-lock.json
generated
@@ -2233,15 +2233,36 @@
|
||||
}
|
||||
},
|
||||
"browserslist": {
|
||||
"version": "4.11.1",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.11.1.tgz",
|
||||
"integrity": "sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g==",
|
||||
"version": "4.16.6",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz",
|
||||
"integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"caniuse-lite": "^1.0.30001038",
|
||||
"electron-to-chromium": "^1.3.390",
|
||||
"node-releases": "^1.1.53",
|
||||
"pkg-up": "^2.0.0"
|
||||
"caniuse-lite": "^1.0.30001219",
|
||||
"colorette": "^1.2.2",
|
||||
"electron-to-chromium": "^1.3.723",
|
||||
"escalade": "^3.1.1",
|
||||
"node-releases": "^1.1.71"
|
||||
},
|
||||
"dependencies": {
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001230",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz",
|
||||
"integrity": "sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ==",
|
||||
"dev": true
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.739",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.739.tgz",
|
||||
"integrity": "sha512-+LPJVRsN7hGZ9EIUUiWCpO7l4E3qBYHNadazlucBfsXBbccDFNKUBAgzE68FnkWGJPwD/AfKhSzL+G+Iqb8A4A==",
|
||||
"dev": true
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "1.1.72",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz",
|
||||
"integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
@@ -2347,12 +2368,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001124",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001124.tgz",
|
||||
"integrity": "sha512-zQW8V3CdND7GHRH6rxm6s59Ww4g/qGWTheoboW9nfeMg7sUoopIfKCcNZUjwYRCOrvereh3kwDpZj4VLQ7zGtA==",
|
||||
"dev": true
|
||||
},
|
||||
"caseless": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||
@@ -2612,6 +2627,12 @@
|
||||
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
|
||||
"dev": true
|
||||
},
|
||||
"colorette": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
|
||||
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
|
||||
"dev": true
|
||||
},
|
||||
"combine-source-map": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz",
|
||||
@@ -3498,12 +3519,6 @@
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.397",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.397.tgz",
|
||||
"integrity": "sha512-zcUd1p/7yzTSdWkCTrqGvbnEOASy96d0RJL/lc5BDJoO23Z3G/VHd0yIPbguDU9n8QNUTCigLO7oEdtOb7fp2A==",
|
||||
"dev": true
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||
@@ -3625,6 +3640,12 @@
|
||||
"es6-symbol": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
||||
"dev": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
@@ -3824,7 +3845,7 @@
|
||||
},
|
||||
"doctrine": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "http://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
|
||||
"integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -6988,7 +7009,7 @@
|
||||
},
|
||||
"is-accessor-descriptor": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
|
||||
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
|
||||
"integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -7034,7 +7055,7 @@
|
||||
},
|
||||
"is-data-descriptor": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
|
||||
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
|
||||
"integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -8504,12 +8525,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "1.1.53",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.53.tgz",
|
||||
"integrity": "sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==",
|
||||
"dev": true
|
||||
},
|
||||
"normalize-package-data": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
||||
@@ -9097,60 +9112,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"pkg-up": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz",
|
||||
"integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"find-up": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"find-up": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
|
||||
"integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"locate-path": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
|
||||
"integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-locate": "^2.0.0",
|
||||
"path-exists": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
|
||||
"integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-try": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"p-locate": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
|
||||
"integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"p-limit": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"p-try": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
|
||||
"integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugin-error": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz",
|
||||
|
||||
831
release.md
831
release.md
@@ -1,3 +1,41 @@
|
||||
# Release Notes IceHrm Open Source
|
||||
|
||||
##Release note v29.0.0.PRO
|
||||
|
||||
### 🧲 New features
|
||||
|
||||
* SAML support [https://icehrm.gitbook.io/icehrm/api-and-single-sign-on/sign-in-with-saml-okta](https://icehrm.gitbook.io/icehrm/api-and-single-sign-on/sign-in-with-saml-okta)
|
||||
* Ability to control who can see employee documents
|
||||
* New custom field module. With this module users can manage all the custom fields via a single module.
|
||||
* Adding custom field support for company structure.
|
||||
* UI improvements for client module.
|
||||
* Introducing encrypted settings.
|
||||
* Adding additional fields such as total time to employee time tracking report.
|
||||
* Improvements to icehrm custom extension development [https://icehrm.gitbook.io/icehrm/developer-guide/creating-first-extension](https://icehrm.gitbook.io/icehrm/developer-guide/creating-first-extension)
|
||||
|
||||
### 🛡️ Security improvements
|
||||
|
||||
* More restrictive criteria for user passwords.
|
||||
* Removing support for legacy API tokens. (if you are using the mobile app your users will need to re authorize)
|
||||
* Removing unused custom field values.
|
||||
|
||||
### 🐛 Bug fixes
|
||||
* Only relevant settings are displayed, under the `Other` tab on settings module
|
||||
|
||||
## Release note v28.2.0.OS
|
||||
|
||||
### New features
|
||||
|
||||
* 🦠 💉 Custom extensions [https://icehrm.gitbook.io/icehrm/developer-guide/creating-first-extension](https://icehrm.gitbook.io/icehrm/developer-guide/creating-first-extension)
|
||||
|
||||
## Release note v28.1.1.OS
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
* Fixing inability to filter employee documents
|
||||
* Fixed the issue with selecting projects when adding timesheets details
|
||||
* Fix issues occurred due to incorrectly configured API
|
||||
|
||||
## Release note v28.1.0.OS
|
||||
|
||||
### 🧲 New features
|
||||
@@ -16,441 +54,477 @@
|
||||
* New REST endpoints for employee qualifications
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
* Fixed, issue with managers being able to create performance reviews for employees who are not their direct reports
|
||||
* Fixed, issues related to using full profile image instead of using smaller version of profile image
|
||||
* Changing third gender to other
|
||||
* Improvements and fixes for internal frontend data caching
|
||||
|
||||
## Release note v27.0.2.OS
|
||||
|
||||
This fixes some major issues found in v27.0.1.OS
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
* Filtering across whole application was broken and now it's fixed
|
||||
* Fixed the issue related to photo not being shown to the admin when photo attendance is enabled
|
||||
|
||||
### 🧑🏻💻 For developers
|
||||
|
||||
* We have added support for vagrant development environment based on Debian 10 / PHP 7.3 \(with Xdebug\) / Nginx / MySQL
|
||||
|
||||
## Release note v27.0.0.OS
|
||||
|
||||
### New features
|
||||
|
||||
* Employee document management is now available for open-source version
|
||||
* UI/UX improvements (new fonts / better spacing)
|
||||
* UI/UX improvements \(new fonts / better spacing\)
|
||||
* Payroll module improvements
|
||||
* Security improvements to password policy
|
||||
* Albanian language is now available
|
||||
* Ability to deploy using docker
|
||||
|
||||
### For developers
|
||||
* Developer environment based on docker [https://www.youtube.com/watch?v=sz8OV_ON6S8](https://www.youtube.com/watch?v=sz8OV_ON6S8)
|
||||
|
||||
* Developer environment based on docker [https://www.youtube.com/watch?v=sz8OV\_ON6S8](https://www.youtube.com/watch?v=sz8OV_ON6S8)
|
||||
* [Developer guide](https://icehrm.gitbook.io/icehrm/developer-guide/create-new-module)
|
||||
* Fully supports all php versions >= 5.6 upto v7.3 (php 5.6 support is deprecated and not recommended)
|
||||
* Fully supports all php versions >= 5.6 upto v7.3 \(php 5.6 support is deprecated and not recommended\)
|
||||
|
||||
### Bug fixes
|
||||
* Fixes to newly found vulnerabilities (https://github.com/gamonoid/icehrm/issues/213): credits to: [Talos](https://talosintelligence.com/)
|
||||
|
||||
* Fixes to newly found vulnerabilities \([https://github.com/gamonoid/icehrm/issues/213](https://github.com/gamonoid/icehrm/issues/213)\): credits to: [Talos](https://talosintelligence.com/)
|
||||
* Fixed the travel request approval for managers
|
||||
* Fixed the issue with attendance source IP display
|
||||
* Fixing Api issues in PHP 7.3
|
||||
|
||||
|
||||
Release note v26.6.0.OS
|
||||
------------------------
|
||||
## Release note v26.6.0.OS
|
||||
|
||||
### Features
|
||||
* Some Improvements to UI such as updating Icons and upgrading font-awesome to its latest version
|
||||
* Tracking IP and location of the employee when marking attendance, this is done when updating attendance via mobile
|
||||
* Ability to control location tracking via mobile using server side settings
|
||||
* Improvements to translations
|
||||
* Compatible with location tracking with icehrm mobile app
|
||||
|
||||
|
||||
* Some Improvements to UI such as updating Icons and upgrading font-awesome to its latest version
|
||||
* Tracking IP and location of the employee when marking attendance, this is done when updating attendance via mobile
|
||||
* Ability to control location tracking via mobile using server side settings
|
||||
* Improvements to translations
|
||||
|
||||
### Mobile App
|
||||
* This release is coupled with mobile application release on AppStore (https://apple.co/2Yrtxoy) and Google Play (http://bit.ly/2OkMmKe)
|
||||
|
||||
* This release is coupled with mobile application release on AppStore \([https://apple.co/2Yrtxoy](https://apple.co/2Yrtxoy)\) and Google Play \([http://bit.ly/2OkMmKe](http://bit.ly/2OkMmKe)\)
|
||||
|
||||
### Fixes
|
||||
* Order projects by name on Timesheet project listing (This is to make it easier to edit timesheets with many projects)
|
||||
* Link home page user profile to employee profile update page
|
||||
* Fix issues related to configuring Api with mobile app
|
||||
|
||||
|
||||
* Order projects by name on Timesheet project listing \(This is to make it easier to edit timesheets with many projects\)
|
||||
* Link home page user profile to employee profile update page
|
||||
* Fix issues related to configuring Api with mobile app
|
||||
|
||||
### Security Improvements
|
||||
* Upgrade npm missing dependencies
|
||||
|
||||
|
||||
Release note v26.2.0.OS
|
||||
------------------------
|
||||
* Upgrade npm missing dependencies
|
||||
|
||||
## Release note v26.2.0.OS
|
||||
|
||||
### Features
|
||||
* Add staff directory module
|
||||
* Update client-side js to ES6
|
||||
* Compatible with IceHrm Mobile App
|
||||
* Use npm libraries when possible
|
||||
* Add gulp build for frontend assets
|
||||
* Allow generating QR code with rest api key (https://github.com/gamonoid/icehrm/issues/169)
|
||||
* Updated readme for development setup with vagrant
|
||||
|
||||
|
||||
* Add staff directory module
|
||||
* Update client-side js to ES6
|
||||
* Compatible with IceHrm Mobile App
|
||||
* Use npm libraries when possible
|
||||
* Add gulp build for frontend assets
|
||||
* Allow generating QR code with rest api key \([https://github.com/gamonoid/icehrm/issues/169](https://github.com/gamonoid/icehrm/issues/169)\)
|
||||
* Updated readme for development setup with vagrant
|
||||
|
||||
### Fixes
|
||||
* Add missing employee details report
|
||||
* Fix: Labels of 'Employee Custom Fields' not displayed (https://github.com/gamonoid/icehrm/issues/146)
|
||||
* Fix: Work week for all counties can not be edited
|
||||
* Fix: Custom fields are not shown under employee profile (https://github.com/gamonoid/icehrm/issues/159)
|
||||
* Fix: Additional buttons shown below timesheet list (https://github.com/gamonoid/icehrm/issues/171)
|
||||
* Updates to Italian translations (https://github.com/gamonoid/icehrm/pull/166) by https://github.com/nightwatch75
|
||||
|
||||
|
||||
Release note v24.0.0.OS
|
||||
------------------------
|
||||
* Add missing employee details report
|
||||
* Fix: Labels of 'Employee Custom Fields' not displayed \([https://github.com/gamonoid/icehrm/issues/146](https://github.com/gamonoid/icehrm/issues/146)\)
|
||||
* Fix: Work week for all counties can not be edited
|
||||
* Fix: Custom fields are not shown under employee profile \([https://github.com/gamonoid/icehrm/issues/159](https://github.com/gamonoid/icehrm/issues/159)\)
|
||||
* Fix: Additional buttons shown below timesheet list \([https://github.com/gamonoid/icehrm/issues/171](https://github.com/gamonoid/icehrm/issues/171)\)
|
||||
* Updates to Italian translations \([https://github.com/gamonoid/icehrm/pull/166](https://github.com/gamonoid/icehrm/pull/166)\) by [https://github.com/nightwatch75](https://github.com/nightwatch75)
|
||||
|
||||
## Release note v24.0.0.OS
|
||||
|
||||
### Features
|
||||
* Allow passing additional parameters to payroll predefined methods
|
||||
* Pass leave type name in function field to get leave count for a given type
|
||||
* Add employee name to payroll report
|
||||
* Show supervisor name on employee profile
|
||||
* Add custom fields to employee report
|
||||
* Add filter by status feature to subordinate time sheets
|
||||
|
||||
* Allow passing additional parameters to payroll predefined methods
|
||||
* Pass leave type name in function field to get leave count for a given type
|
||||
* Add employee name to payroll report
|
||||
* Show supervisor name on employee profile
|
||||
* Add custom fields to employee report
|
||||
* Add filter by status feature to subordinate time sheets
|
||||
|
||||
### Security Fixes
|
||||
* Fix missing login form CSRF token
|
||||
* Fix risky usage of hashed password in request
|
||||
* Fixing permission issues on module access for each user level
|
||||
* Prevent manager from accessing sensitive user records
|
||||
|
||||
|
||||
* Fix missing login form CSRF token
|
||||
* Fix risky usage of hashed password in request
|
||||
* Fixing permission issues on module access for each user level
|
||||
* Prevent manager from accessing sensitive user records
|
||||
|
||||
### Other Fixes
|
||||
* Hide employee salary from managers
|
||||
* Prevent manager from accessing audit, cron and notifications
|
||||
* Prevent managers from deleting employees
|
||||
* Validate overtime start and end times
|
||||
* Fix issue: employee can download draft payroll
|
||||
|
||||
Release note v23.0.1.OS
|
||||
------------------------
|
||||
|
||||
* Hide employee salary from managers
|
||||
* Prevent manager from accessing audit, cron and notifications
|
||||
* Prevent managers from deleting employees
|
||||
* Validate overtime start and end times
|
||||
* Fix issue: employee can download draft payroll
|
||||
|
||||
## Release note v23.0.1.OS
|
||||
|
||||
This release include some very critical security fixes. We recommend upgrading your installation to latest release.
|
||||
|
||||
### Fixes
|
||||
* Fix missing login form CSRF token
|
||||
* Fix risky usage of hashed password in request
|
||||
|
||||
Release note v23.0.0.OS
|
||||
------------------------
|
||||
### Features
|
||||
* Loading last used module when revisiting application
|
||||
* Finnish language support (Beta)
|
||||
* Improvements to German, Italian and Chinese language translations
|
||||
* Allow quickly switching languages
|
||||
* Improvements to security for preventing possible LFI attacks
|
||||
* Allow manual date inputs
|
||||
* Custom fields for travel requests
|
||||
* Allow importing approved overtime hours into payroll
|
||||
* Add date and time masks
|
||||
|
||||
|
||||
### Fixes
|
||||
* Fix logout cookie issue, by clearing remember me cookie when logging out
|
||||
* Improve privacy for GDPR
|
||||
* Improvements to file upload field
|
||||
* Fix issue: attendance rest end point not working on php 5.6
|
||||
|
||||
Release note v22.0.0.OS
|
||||
------------------------
|
||||
### Features
|
||||
* Improvements to module naming
|
||||
|
||||
### Fixes
|
||||
* Fix issue: filter dialog default values are not selected
|
||||
* Fix issue: department head can be an employee outside the department
|
||||
* Fix issue: department head or supervisor (who has manager leave access) can't use switch employee feature
|
||||
* Fix issue: employee name is not visible on report if middle name is empty
|
||||
* Fix missing login form CSRF token
|
||||
* Fix risky usage of hashed password in request
|
||||
|
||||
Release note v21.1.0.OS
|
||||
------------------------
|
||||
### Features
|
||||
* UI improvements (help button and error messages)
|
||||
* Allow adding placeholders to text fields
|
||||
* Improvements to German Translations
|
||||
|
||||
### Fixes
|
||||
* Fixing notification issues
|
||||
|
||||
Release note v21.0.0.OS
|
||||
------------------------
|
||||
### Features
|
||||
* Fully compatible with php 7.1
|
||||
* Add Net_SMTP via composer (no pear installation needed)
|
||||
|
||||
### Fixes
|
||||
* Fixes for web servers not supporting JSON in GET request
|
||||
|
||||
Release note v21.0.0.OS
|
||||
------------------------
|
||||
### Features
|
||||
* Fully compatible with php 7.1
|
||||
* Add Net_SMTP via composer (no pear installation needed)
|
||||
|
||||
### Fixes
|
||||
* Fixes for web servers not supporting JSON in GET request
|
||||
|
||||
Release note v20.3.0.PRO
|
||||
------------------------
|
||||
### Features
|
||||
* Employee and Attendance REST Api Released
|
||||
* Import/Export for Payroll Configurations
|
||||
* Ability to import employee approved time sheet hours to payroll
|
||||
* Swift Mailer based SMTP support (no need to install Net_SMTP anymore)
|
||||
* Add direct Edit button on employee list
|
||||
|
||||
### Fixes
|
||||
* Fix DB connection issues due to special characters in password
|
||||
* Fixes for custom field saving issues in mysql v5.7.x
|
||||
|
||||
Release note v20.2
|
||||
------------------
|
||||
### Fixes
|
||||
* Fix for resetting modules
|
||||
|
||||
Release note v20.1
|
||||
------------------
|
||||
### Features
|
||||
* Compatible with MySQL 5.7 Strict Mode
|
||||
* PSR-2 compatible code
|
||||
* Employee History Module
|
||||
* Staff Directory
|
||||
|
||||
### Fixes
|
||||
* Fix: password reset not working
|
||||
* Fix: limiting selectable countries via Settings
|
||||
* Fix for resetting modules
|
||||
|
||||
Release note v20.0
|
||||
------------------
|
||||
### Features
|
||||
* Payroll Module
|
||||
* Compatible with MySQL 5.7 Strict Mode
|
||||
* Namespaced Classes
|
||||
* LDAP Module
|
||||
|
||||
### Fixes
|
||||
* Fix: limiting selectable countries via Settings
|
||||
|
||||
Release note v19.0
|
||||
------------------
|
||||
### Features
|
||||
* Development environment
|
||||
* Overtime module
|
||||
* Department heads who can manage all employees attached to a company structure
|
||||
|
||||
|
||||
Release note v18.0
|
||||
------------------
|
||||
### Features
|
||||
* Translations (beta) for German, French, Polish, Italian, Sinhala, Chinese, Japanese, Hindi and Spanish
|
||||
* PDF Reports
|
||||
* Ability to specify department heads
|
||||
* Add advanced custom fields to employees via UI
|
||||
* Allow indirect admins to approve travel requests
|
||||
* Adding more languages to Language meta data table
|
||||
* Improvements to report module
|
||||
* Ability to select sections for placing custom fields on employee detail view screen
|
||||
* Introducing clone button
|
||||
* Unlimited custom fields for employees
|
||||
* PDF report for monitoring time employee spent on projects
|
||||
* Report files module - Allow downloading all previously generated reports
|
||||
|
||||
### Fixes
|
||||
* Fix: subordinates are not showing beyond first page issue.
|
||||
|
||||
|
||||
Release note v16.1
|
||||
------------------
|
||||
|
||||
### Fixes
|
||||
* Fix LDAP user login issue
|
||||
* Allow creating users with username having dot and dash
|
||||
|
||||
Release note v16.0
|
||||
------------------
|
||||
### Features
|
||||
* Advanced Employee Management Module is now included in IceHrm Open Source Edition
|
||||
* LDAP Module which was only available in IceHrm Enterprise is now included in open source also
|
||||
* Initial implementation of icehrm REST Api for reading employee details
|
||||
* Improvements to data filtering
|
||||
* Multiple tabs for settings module
|
||||
* Overtime reports - now its possible to calculate overtime for employees.compatible with US overtime rules
|
||||
* Logout the user if tried accessing an unauthorized module
|
||||
* Setting for updating module names
|
||||
|
||||
### Fixes
|
||||
* Fix issue: classes should be loaded even the module is disabled
|
||||
* Deleting the only Admin user is not allowed
|
||||
* Fixes for handling non UTF-8
|
||||
* Fix for non-mandatory select boxes are shown as mandatory
|
||||
|
||||
Release note v15.2
|
||||
------------------
|
||||
## Release note v23.0.0.OS
|
||||
|
||||
### Features
|
||||
* Overtime Reports
|
||||
* Overtime calculation for california
|
||||
|
||||
### Fixes
|
||||
* Fix issue: uncaught error when placeholder value is empty
|
||||
* Log email sending success status
|
||||
* Fix broken longer company name issue
|
||||
* Make the application accessible when client on an intranet with no internet connection
|
||||
* Fix issue: when a module is disabled other modules depend on it stops working
|
||||
|
||||
|
||||
Release note v15.0
|
||||
------------------
|
||||
|
||||
### Features
|
||||
* Clear HTML5 local storage when logging in and switching users
|
||||
* Showing a loading message while getting data from server
|
||||
* Adding a new field to show total time of each time sheet
|
||||
* New report added for listing Employee Time Sheets
|
||||
* Company logo uploaded via settings will be used for all email headers
|
||||
|
||||
### Fixes
|
||||
* Fix issue: default module URL is incorrect for Employees
|
||||
* Fix date parsing issue in time sheets
|
||||
* AWS phar is included only when required
|
||||
|
||||
Release note v14.1
|
||||
------------------
|
||||
|
||||
### Features
|
||||
* Add Quick access menu
|
||||
* Loading last used module when revisiting application
|
||||
* Finnish language support \(Beta\)
|
||||
* Improvements to German, Italian and Chinese language translations
|
||||
* Allow quickly switching languages
|
||||
* Improvements to security for preventing possible LFI attacks
|
||||
* Allow manual date inputs
|
||||
* Custom fields for travel requests
|
||||
* Allow importing approved overtime hours into payroll
|
||||
* Add date and time masks
|
||||
|
||||
### Fixes
|
||||
* Fix issue: salary module not loading
|
||||
* Add travel report
|
||||
|
||||
Release note v14.0
|
||||
------------------
|
||||
* Fix logout cookie issue, by clearing remember me cookie when logging out
|
||||
* Improve privacy for GDPR
|
||||
* Improvements to file upload field
|
||||
* Fix issue: attendance rest end point not working on php 5.6
|
||||
|
||||
## Release note v22.0.0.OS
|
||||
|
||||
### Features
|
||||
* IceHrm is now fully compatible with PHP 7
|
||||
* Improvements to travel management module to change the process of applying for travel requests
|
||||
* New report add for getting travel requests
|
||||
* Improvements to user interface
|
||||
* Bunch of UI improvements including changing menu order and font sizes
|
||||
* Add a setting to use server time for time zone defined on department that a user is attached to create new attendance records
|
||||
* Improvements to admin/manager and user dashboard
|
||||
* Managers allowed to view/add/edit employee documents
|
||||
* New reports added for employee expenses and travel
|
||||
|
||||
### Fixes
|
||||
* Fix unavailable help links
|
||||
|
||||
|
||||
Release note v13.4
|
||||
-----------------
|
||||
|
||||
### Features
|
||||
|
||||
### Fixes
|
||||
* Fix employee leave report leave type field
|
||||
|
||||
Release note v13.0
|
||||
-----------------
|
||||
|
||||
### Features
|
||||
* Recruitment module
|
||||
* Allow managers to edit attendance of direct report employees
|
||||
|
||||
### Fixes
|
||||
* Employee switching issue fixed
|
||||
* Fix terminated employee labels
|
||||
* Fix issue with punch-in
|
||||
|
||||
Release note v12.6
|
||||
-----------------
|
||||
|
||||
### Features
|
||||
* Charts module
|
||||
* Code level security improvements
|
||||
|
||||
### Fixes
|
||||
* Employee switching issue fixed
|
||||
|
||||
|
||||
Release note v11.1
|
||||
-----------------
|
||||
|
||||
### Features
|
||||
* Add/Edit or remove employee fields
|
||||
|
||||
|
||||
Release note v11.0
|
||||
-----------------
|
||||
|
||||
### Features
|
||||
* Employee data archiving
|
||||
* Leave cancellation requests
|
||||
* Adding view employee feature
|
||||
* Improvements to module naming
|
||||
|
||||
### Fixes
|
||||
* Improvements to date time pickers
|
||||
|
||||
* Fix issue: filter dialog default values are not selected
|
||||
* Fix issue: department head can be an employee outside the department
|
||||
* Fix issue: department head or supervisor \(who has manager leave access\) can't use switch employee feature
|
||||
* Fix issue: employee name is not visible on report if middle name is empty
|
||||
|
||||
Release note v10.1
|
||||
-----------------
|
||||
## Release note v21.1.0.OS
|
||||
|
||||
### Features
|
||||
* Integration with ice-framework (http://githun.com/thilinah/ice-framework)
|
||||
* Option for only allow users to add an entry to a timesheet only if they have marked atteandance for the selected period
|
||||
* Restricting availability of leave types to employees using leave groups
|
||||
* Admins and add notes to employees
|
||||
|
||||
Release note v9.1
|
||||
-----------------
|
||||
* UI improvements \(help button and error messages\)
|
||||
* Allow adding placeholders to text fields
|
||||
* Improvements to German Translations
|
||||
|
||||
### Fixes
|
||||
* Add missing S3FileSystem class
|
||||
* Fix issue: passing result of a method call directly into empty method is not supported in php v5.3
|
||||
|
||||
* Fixing notification issues
|
||||
|
||||
Release note v9.0
|
||||
-----------------
|
||||
## Release note v21.0.0.OS
|
||||
|
||||
### Features
|
||||
* New user interface
|
||||
* Decimal leave counts supported
|
||||
|
||||
Update icehrm v8.4 to v9.0
|
||||
--------------------------
|
||||
|
||||
* Make a backup of your icehrm db
|
||||
* Run db script "icehrmdb_update_v8.4_to_v9.0.sql" which can be found inside script folder of icehrm_v9.0
|
||||
* remove all folders except app folder in icehrm root folder
|
||||
* copy all folders except app folder from new installation to icehrm root folder
|
||||
|
||||
|
||||
Release note v8.4
|
||||
-----------------
|
||||
* Fully compatible with php 7.1
|
||||
* Add Net\_SMTP via composer \(no pear installation needed\)
|
||||
|
||||
### Fixes
|
||||
* Fix leave carry forward rounding issues
|
||||
* Fix issue: select2 default value not getting set for select2
|
||||
* Fix issue: email not sent when admin changing leave status
|
||||
|
||||
Release note v8.3
|
||||
-----------------
|
||||
* Fixes for web servers not supporting JSON in GET request
|
||||
|
||||
### Fixes
|
||||
* Fix user table issue on windows, this will resolve errors such as: (Note that this fix has no effect on unix based installations)
|
||||
* Admin not able to view user uploaded documents
|
||||
* Admin not able to upload documants for users
|
||||
* Admin can not view employee attendance records
|
||||
* Employee projects can not be added
|
||||
|
||||
|
||||
Release note v8.2
|
||||
-----------------
|
||||
## Release note v21.0.0.OS
|
||||
|
||||
### Features
|
||||
|
||||
* Fully compatible with php 7.1
|
||||
* Add Net\_SMTP via composer \(no pear installation needed\)
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fixes for web servers not supporting JSON in GET request
|
||||
|
||||
## Release note v20.3.0.PRO
|
||||
|
||||
### Features
|
||||
|
||||
* Employee and Attendance REST Api Released
|
||||
* Import/Export for Payroll Configurations
|
||||
* Ability to import employee approved time sheet hours to payroll
|
||||
* Swift Mailer based SMTP support \(no need to install Net\_SMTP anymore\)
|
||||
* Add direct Edit button on employee list
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix DB connection issues due to special characters in password
|
||||
* Fixes for custom field saving issues in mysql v5.7.x
|
||||
|
||||
## Release note v20.2
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix for resetting modules
|
||||
|
||||
## Release note v20.1
|
||||
|
||||
### Features
|
||||
|
||||
* Compatible with MySQL 5.7 Strict Mode
|
||||
* PSR-2 compatible code
|
||||
* Employee History Module
|
||||
* Staff Directory
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix: password reset not working
|
||||
* Fix: limiting selectable countries via Settings
|
||||
* Fix for resetting modules
|
||||
|
||||
## Release note v20.0
|
||||
|
||||
### Features
|
||||
|
||||
* Payroll Module
|
||||
* Compatible with MySQL 5.7 Strict Mode
|
||||
* Namespaced Classes
|
||||
* LDAP Module
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix: limiting selectable countries via Settings
|
||||
|
||||
## Release note v19.0
|
||||
|
||||
### Features
|
||||
|
||||
* Development environment
|
||||
* Overtime module
|
||||
* Department heads who can manage all employees attached to a company structure
|
||||
|
||||
## Release note v18.0
|
||||
|
||||
### Features
|
||||
|
||||
* Translations \(beta\) for German, French, Polish, Italian, Sinhala, Chinese, Japanese, Hindi and Spanish
|
||||
* PDF Reports
|
||||
* Ability to specify department heads
|
||||
* Add advanced custom fields to employees via UI
|
||||
* Allow indirect admins to approve travel requests
|
||||
* Adding more languages to Language meta data table
|
||||
* Improvements to report module
|
||||
* Ability to select sections for placing custom fields on employee detail view screen
|
||||
* Introducing clone button
|
||||
* Unlimited custom fields for employees
|
||||
* PDF report for monitoring time employee spent on projects
|
||||
* Report files module - Allow downloading all previously generated reports
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix: subordinates are not showing beyond first page issue.
|
||||
|
||||
## Release note v16.1
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix LDAP user login issue
|
||||
* Allow creating users with username having dot and dash
|
||||
|
||||
## Release note v16.0
|
||||
|
||||
### Features
|
||||
|
||||
* Advanced Employee Management Module is now included in IceHrm Open Source Edition
|
||||
* LDAP Module which was only available in IceHrm Enterprise is now included in open source also
|
||||
* Initial implementation of icehrm REST Api for reading employee details
|
||||
* Improvements to data filtering
|
||||
* Multiple tabs for settings module
|
||||
* Overtime reports - now its possible to calculate overtime for employees.compatible with US overtime rules
|
||||
* Logout the user if tried accessing an unauthorized module
|
||||
* Setting for updating module names
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix issue: classes should be loaded even the module is disabled
|
||||
* Deleting the only Admin user is not allowed
|
||||
* Fixes for handling non UTF-8
|
||||
* Fix for non-mandatory select boxes are shown as mandatory
|
||||
|
||||
## Release note v15.2
|
||||
|
||||
### Features
|
||||
|
||||
* Overtime Reports
|
||||
* Overtime calculation for california
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix issue: uncaught error when placeholder value is empty
|
||||
* Log email sending success status
|
||||
* Fix broken longer company name issue
|
||||
* Make the application accessible when client on an intranet with no internet connection
|
||||
* Fix issue: when a module is disabled other modules depend on it stops working
|
||||
|
||||
## Release note v15.0
|
||||
|
||||
### Features
|
||||
|
||||
* Clear HTML5 local storage when logging in and switching users
|
||||
* Showing a loading message while getting data from server
|
||||
* Adding a new field to show total time of each time sheet
|
||||
* New report added for listing Employee Time Sheets
|
||||
* Company logo uploaded via settings will be used for all email headers
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix issue: default module URL is incorrect for Employees
|
||||
* Fix date parsing issue in time sheets
|
||||
* AWS phar is included only when required
|
||||
|
||||
## Release note v14.1
|
||||
|
||||
### Features
|
||||
|
||||
* Add Quick access menu
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix issue: salary module not loading
|
||||
* Add travel report
|
||||
|
||||
## Release note v14.0
|
||||
|
||||
### Features
|
||||
|
||||
* IceHrm is now fully compatible with PHP 7
|
||||
* Improvements to travel management module to change the process of applying for travel requests
|
||||
* New report add for getting travel requests
|
||||
* Improvements to user interface
|
||||
* Bunch of UI improvements including changing menu order and font sizes
|
||||
* Add a setting to use server time for time zone defined on department that a user is attached to create new attendance records
|
||||
* Improvements to admin/manager and user dashboard
|
||||
* Managers allowed to view/add/edit employee documents
|
||||
* New reports added for employee expenses and travel
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix unavailable help links
|
||||
|
||||
## Release note v13.4
|
||||
|
||||
### Features
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix employee leave report leave type field
|
||||
|
||||
## Release note v13.0
|
||||
|
||||
### Features
|
||||
|
||||
* Recruitment module
|
||||
* Allow managers to edit attendance of direct report employees
|
||||
|
||||
### Fixes
|
||||
|
||||
* Employee switching issue fixed
|
||||
* Fix terminated employee labels
|
||||
* Fix issue with punch-in
|
||||
|
||||
## Release note v12.6
|
||||
|
||||
### Features
|
||||
|
||||
* Charts module
|
||||
* Code level security improvements
|
||||
|
||||
### Fixes
|
||||
|
||||
* Employee switching issue fixed
|
||||
|
||||
## Release note v11.1
|
||||
|
||||
### Features
|
||||
|
||||
* Add/Edit or remove employee fields
|
||||
|
||||
## Release note v11.0
|
||||
|
||||
### Features
|
||||
|
||||
* Employee data archiving
|
||||
* Leave cancellation requests
|
||||
* Adding view employee feature
|
||||
|
||||
### Fixes
|
||||
|
||||
* Improvements to date time pickers
|
||||
|
||||
## Release note v10.1
|
||||
|
||||
### Features
|
||||
|
||||
* Integration with ice-framework \([http://githun.com/thilinah/ice-framework](http://githun.com/thilinah/ice-framework)\)
|
||||
* Option for only allow users to add an entry to a timesheet only if they have marked atteandance for the selected period
|
||||
* Restricting availability of leave types to employees using leave groups
|
||||
* Admins and add notes to employees
|
||||
|
||||
## Release note v9.1
|
||||
|
||||
### Fixes
|
||||
|
||||
* Add missing S3FileSystem class
|
||||
* Fix issue: passing result of a method call directly into empty method is not supported in php v5.3
|
||||
|
||||
## Release note v9.0
|
||||
|
||||
### Features
|
||||
|
||||
* New user interface
|
||||
* Decimal leave counts supported
|
||||
|
||||
## Update icehrm v8.4 to v9.0
|
||||
|
||||
* Make a backup of your icehrm db
|
||||
* Run db script "icehrmdb\_update\_v8.4\_to\_v9.0.sql" which can be found inside script folder of icehrm\_v9.0
|
||||
* remove all folders except app folder in icehrm root folder
|
||||
* copy all folders except app folder from new installation to icehrm root folder
|
||||
|
||||
## Release note v8.4
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix leave carry forward rounding issues
|
||||
* Fix issue: select2 default value not getting set for select2
|
||||
* Fix issue: email not sent when admin changing leave status
|
||||
|
||||
## Release note v8.3
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fix user table issue on windows, this will resolve errors such as: \(Note that this fix has no effect on unix based installations\)
|
||||
* Admin not able to view user uploaded documents
|
||||
* Admin not able to upload documants for users
|
||||
* Admin can not view employee attendance records
|
||||
* Employee projects can not be added
|
||||
|
||||
## Release note v8.2
|
||||
|
||||
### Features
|
||||
|
||||
* Instance verification added
|
||||
|
||||
Release note v8.1
|
||||
-----------------
|
||||
## Release note v8.1
|
||||
|
||||
### Fixes
|
||||
|
||||
* Fixed bug that caused a fatal error in php v5.4
|
||||
* aws2.7.11 phar file replaced by a aws2.7.11 extracted files
|
||||
* old aws sdk removed
|
||||
|
||||
Release note v8.0
|
||||
-----------------
|
||||
## Release note v8.0
|
||||
|
||||
### Features
|
||||
|
||||
* Admin dashbord module
|
||||
* If the employee joined in current leave period, his leave entitlement is calculated proportional to joined date
|
||||
* Improvements to reporting module
|
||||
@@ -462,22 +536,22 @@ Release note v8.0
|
||||
* Upgrade aws sdk to v2.7.11
|
||||
* Allow employees to change password
|
||||
* Use only the email address defined under user for sending mails
|
||||
* Making work_email and private_email fields optional
|
||||
|
||||
* Making work\_email and private\_email fields optional
|
||||
|
||||
### Fixes
|
||||
|
||||
* Upload dialog close button issue fixed
|
||||
|
||||
Release note v7.2
|
||||
-----------------
|
||||
## Release note v7.2
|
||||
|
||||
### Fixes
|
||||
* Some critical vulnerabilities are fixed as recommend by http://zeroscience.mk/en/
|
||||
|
||||
Release note v7.1
|
||||
-----------------
|
||||
* Some critical vulnerabilities are fixed as recommend by [http://zeroscience.mk/en/](http://zeroscience.mk/en/)
|
||||
|
||||
## Release note v7.1
|
||||
|
||||
### Features
|
||||
|
||||
* Improved company structure graph
|
||||
* Leave notes implementation <20> Supervisor can add a note when approving or rejecting leaves
|
||||
* Filtering support
|
||||
@@ -486,20 +560,20 @@ Release note v7.1
|
||||
* Add ability to disable employee information editing
|
||||
|
||||
### Fixes
|
||||
|
||||
* Make loans editable only by admin
|
||||
* Fix: permissions not getting applied to employee documents
|
||||
* Fix error adding employee documents when no user assigned to the admin
|
||||
|
||||
### Code Quality
|
||||
|
||||
* Moving all module related code and data into module folders
|
||||
|
||||
Release note v6.1
|
||||
-----------------
|
||||
## Release note v6.1
|
||||
|
||||
Leave carry forwared related isue fixed
|
||||
|
||||
Release note v6.0
|
||||
-----------------
|
||||
## Release note v6.0
|
||||
|
||||
* Features
|
||||
* Notifications for leaves and timesheets
|
||||
@@ -513,21 +587,18 @@ Release note v6.0
|
||||
* Admin can make all projects available to employees or just the set of prjects assigned to them using Setting "Projects: Make All Projects Available to Employees"
|
||||
* Employee document, date added field can not be changed by the employee anymore
|
||||
* About dialog added for admins
|
||||
|
||||
* Fixes
|
||||
* Fix default employee delete issue (when the default employee is deleted the admin user attached to it also get deleted)
|
||||
* Fix default employee delete issue \(when the default employee is deleted the admin user attached to it also get deleted\)
|
||||
* Fix user duplicate email issue
|
||||
* Fix manager can not logout from switched employee
|
||||
* Remove admin guide from non admin users
|
||||
|
||||
Release note v5.3
|
||||
-----------------
|
||||
## Release note v5.3
|
||||
|
||||
* Fixes
|
||||
* Fix missing employee name in employee details report
|
||||
|
||||
Release note v5.2
|
||||
-----------------
|
||||
## Release note v5.2
|
||||
|
||||
* Fixes
|
||||
* Remove unwanted error logs
|
||||
@@ -536,38 +607,33 @@ Release note v5.2
|
||||
* Remove add new button from subordinates module
|
||||
* Adding administrators' guide
|
||||
|
||||
Release note v5.1
|
||||
-----------------
|
||||
## Release note v5.1
|
||||
|
||||
* Fixes
|
||||
* Fixing for non updating null fields
|
||||
* https://bitbucket.org/thilina/icehrm-opensource/commits/df57308b53484a2e43ef5c72967ed1cd0dc756cc
|
||||
* [https://bitbucket.org/thilina/icehrm-opensource/commits/df57308b53484a2e43ef5c72967ed1cd0dc756cc](https://bitbucket.org/thilina/icehrm-opensource/commits/df57308b53484a2e43ef5c72967ed1cd0dc756cc)
|
||||
|
||||
Release note v5.0
|
||||
-----------------
|
||||
## Release note v5.0
|
||||
|
||||
* Features
|
||||
* New user permission implementation
|
||||
* Adding new user level - Manager
|
||||
|
||||
* Fixes
|
||||
* Fixing remote table loading issue
|
||||
|
||||
Release note v4.2
|
||||
-----------------
|
||||
## Release note v4.2
|
||||
|
||||
### Fixes
|
||||
* https://bitbucket.org/thilina/icehrm-opensource/issue/23/subordinate-leaves-pagination-not-working
|
||||
* https://bitbucket.org/thilina/icehrm-opensource/issue/20/error-occured-while-time-punch
|
||||
|
||||
* [https://bitbucket.org/thilina/icehrm-opensource/issue/23/subordinate-leaves-pagination-not-working](https://bitbucket.org/thilina/icehrm-opensource/issue/23/subordinate-leaves-pagination-not-working)
|
||||
* [https://bitbucket.org/thilina/icehrm-opensource/issue/20/error-occured-while-time-punch](https://bitbucket.org/thilina/icehrm-opensource/issue/20/error-occured-while-time-punch)
|
||||
|
||||
Release note v4.1
|
||||
-----------------
|
||||
## Release note v4.1
|
||||
|
||||
### Features
|
||||
|
||||
* Better email format for notifications
|
||||
* Convert upload dialog to a bootstrp model
|
||||
|
||||
* Fixes
|
||||
* Fix error sending emails with amazon SES
|
||||
* Fix errors related to XAMPP and WAMPP servers
|
||||
@@ -576,3 +642,4 @@ Release note v4.1
|
||||
* Allow icehrm client to work without an internet connection
|
||||
* Fix installer incorrect base url issue
|
||||
* Fix empty user creation issue
|
||||
|
||||
|
||||
@@ -3,13 +3,21 @@
|
||||
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
|
||||
*/
|
||||
|
||||
import AdapterBase from '../../../api/AdapterBase';
|
||||
import ReactModalAdapterBase from '../../../api/ReactModalAdapterBase';
|
||||
|
||||
/**
|
||||
* ClientAdapter
|
||||
*/
|
||||
|
||||
class ClientAdapter extends AdapterBase {
|
||||
class ClientAdapter extends ReactModalAdapterBase {
|
||||
constructor(endPoint, tab, filter, orderBy) {
|
||||
super(endPoint, tab, filter, orderBy);
|
||||
this.fieldNameMap = {};
|
||||
this.hiddenFields = {};
|
||||
this.tableFields = {};
|
||||
this.formOnlyFields = {};
|
||||
}
|
||||
|
||||
getDataMapping() {
|
||||
return [
|
||||
'id',
|
||||
@@ -30,6 +38,32 @@ class ClientAdapter extends AdapterBase {
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
getTableColumns() {
|
||||
return [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: 'Details',
|
||||
dataIndex: 'details',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: 'Address',
|
||||
dataIndex: 'address',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: 'Contact Number',
|
||||
dataIndex: 'contact_number',
|
||||
sorter: true,
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
getFormFields() {
|
||||
if (this.showSave) {
|
||||
return [
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CompanyStructureAdapter, CompanyGraphAdapter } from './lib';
|
||||
import IceDataPipe from "../../../api/IceDataPipe";
|
||||
import CustomFieldAdapter from "../../../api/ReactCustomFieldAdapter";
|
||||
|
||||
|
||||
function init(data) {
|
||||
@@ -9,6 +8,7 @@ function init(data) {
|
||||
modJsList.tabCompanyStructure.setObjectTypeName('Company Structure');
|
||||
modJsList.tabCompanyStructure.setDataPipe(new IceDataPipe(modJsList.tabCompanyStructure));
|
||||
modJsList.tabCompanyStructure.setAccess(data.permissions.CompanyStructure);
|
||||
modJsList.tabCompanyStructure.setCustomFields(data.customFields);
|
||||
|
||||
modJsList.tabCompanyGraph = new CompanyGraphAdapter('CompanyStructure');
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ class CompanyStructureAdapter extends ReactModalAdapterBase {
|
||||
}
|
||||
|
||||
getFormFields() {
|
||||
return [
|
||||
return this.addCustomFields([
|
||||
['id', { label: 'ID', type: 'hidden', validation: '' }],
|
||||
['title', { label: 'Name', type: 'text', validation: '' }],
|
||||
['description', { label: 'Details', type: 'textarea', validation: '' }],
|
||||
@@ -80,7 +80,7 @@ class CompanyStructureAdapter extends ReactModalAdapterBase {
|
||||
['heads', {
|
||||
label: 'Heads', type: 'select2multi', 'allow-null': true, 'remote-source': ['Employee', 'id', 'first_name+last_name'],
|
||||
}],
|
||||
];
|
||||
]);
|
||||
}
|
||||
|
||||
postRenderForm(object, $tempDomObj) {
|
||||
|
||||
18
web/admin/src/custom_fields/index.js
Normal file
18
web/admin/src/custom_fields/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { CommonCustomFieldAdapter } from './lib';
|
||||
import IceDataPipe from '../../../api/IceDataPipe';
|
||||
|
||||
function init(data) {
|
||||
const modJsList = {};
|
||||
|
||||
modJsList.tabCustomField = new CommonCustomFieldAdapter('CustomField', 'CustomField', {}, '');
|
||||
modJsList.tabCustomField.setRemoteTable(true);
|
||||
modJsList.tabCustomField.setObjectTypeName('Custom Field');
|
||||
modJsList.tabCustomField.setDataPipe(new IceDataPipe(modJsList.tabCustomField));
|
||||
modJsList.tabCustomField.setAccess(data.permissions.CustomField);
|
||||
modJsList.tabCustomField.setTypes(data.types);
|
||||
|
||||
window.modJs = modJsList.tabCustomField;
|
||||
window.modJsList = modJsList;
|
||||
}
|
||||
|
||||
window.initAdminCustomFields = init;
|
||||
166
web/admin/src/custom_fields/lib.js
Normal file
166
web/admin/src/custom_fields/lib.js
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Author: Thilina Hasantha
|
||||
*/
|
||||
import ReactCustomFieldAdapter from '../../../api/ReactCustomFieldAdapter';
|
||||
|
||||
/**
|
||||
* AssetTypeAdapter
|
||||
*/
|
||||
|
||||
class CommonCustomFieldAdapter extends ReactCustomFieldAdapter {
|
||||
getDataMapping() {
|
||||
return [
|
||||
'id',
|
||||
'name',
|
||||
'type',
|
||||
'field_type',
|
||||
'field_label',
|
||||
'display',
|
||||
'display_order',
|
||||
];
|
||||
}
|
||||
|
||||
getHeaders() {
|
||||
return [
|
||||
{ sTitle: 'ID', bVisible: false },
|
||||
{ sTitle: 'Name' },
|
||||
{ sTitle: 'Object Type' },
|
||||
{ sTitle: 'Field Type' },
|
||||
{ sTitle: 'Field Label' },
|
||||
{ sTitle: 'Display Status' },
|
||||
{ sTitle: 'Priority' },
|
||||
];
|
||||
}
|
||||
|
||||
getTableColumns() {
|
||||
return [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: 'Object Type',
|
||||
dataIndex: 'type',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: 'Field Label',
|
||||
dataIndex: 'field_label',
|
||||
},
|
||||
{
|
||||
title: 'Field Type',
|
||||
dataIndex: 'field_type',
|
||||
},
|
||||
{
|
||||
title: 'Display Status',
|
||||
dataIndex: 'display',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: 'Priority',
|
||||
dataIndex: 'display_order',
|
||||
sorter: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
setTypes(tables) {
|
||||
this.types = tables;
|
||||
}
|
||||
|
||||
getFormFields() {
|
||||
return [
|
||||
['id', { label: 'ID', type: 'hidden' }],
|
||||
['field_label', { label: 'Field Label', type: 'text', validation: '' }],
|
||||
['type',
|
||||
{
|
||||
label: 'Object Type',
|
||||
type: 'select2',
|
||||
source: this.types,
|
||||
},
|
||||
],
|
||||
['field_type', { label: 'Field Type', type: 'select', source: [['text', 'Text Field'], ['textarea', 'Text Area'], ['select', 'Select'], ['select2', 'Select2'], ['select2multi', 'Multi Select'], ['fileupload', 'File Upload'], ['date', 'Date'], ['datetime', 'Date Time'], ['time', 'Time'], ['signature', 'Signature']] }],
|
||||
['field_validation', {
|
||||
label: 'Validation', type: 'select2', validation: 'none', sort: 'none', 'null-label': 'Required', 'allow-null': true, source: [['none', 'None'], ['number', 'Number'], ['numberOrEmpty', 'Number or Empty'], ['float', 'Decimal'], ['email', 'Email'], ['emailOrEmpty', 'Email or Empty']],
|
||||
}],
|
||||
['field_options', {
|
||||
label: 'Field Options',
|
||||
type: 'datagroup',
|
||||
form: [
|
||||
['label', { label: 'Label', type: 'text', validation: '' }],
|
||||
['value', { label: 'Value', type: 'text', validation: 'none' }],
|
||||
],
|
||||
html: '<div id="#_id_#" class="panel panel-default"><div class="panel-body">#_delete_##_edit_#<span style="color:#999;font-size:13px;font-weight:bold">#_label_#</span>:#_value_#</div></div>',
|
||||
columns: [
|
||||
{
|
||||
title: 'Label',
|
||||
dataIndex: 'label',
|
||||
key: 'label',
|
||||
},
|
||||
{
|
||||
title: 'Option Value',
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
},
|
||||
],
|
||||
validation: 'none',
|
||||
}],
|
||||
['display_order', { label: 'Priority', type: 'text', validation: 'none' }],
|
||||
];
|
||||
}
|
||||
|
||||
getFilters() {
|
||||
return [
|
||||
['type',
|
||||
{
|
||||
label: 'Object Type',
|
||||
type: 'select2',
|
||||
'allow-null': true,
|
||||
source: this.types,
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
forceInjectValuesBeforeSave(params) {
|
||||
const data = ['', {}];
|
||||
const options = [];
|
||||
let optionsData;
|
||||
|
||||
data[1].label = params.field_label;
|
||||
data[1].type = params.field_type;
|
||||
data[1].validation = params.field_validation;
|
||||
if (['select', 'select2', 'select2multi'].indexOf(params.field_type) >= 0) {
|
||||
optionsData = (params.field_options === '' || params.field_options === undefined)
|
||||
? [] : JSON.parse(params.field_options);
|
||||
for (const index in optionsData) {
|
||||
options.push([optionsData[index].value, optionsData[index].label]);
|
||||
}
|
||||
data[1].source = options;
|
||||
}
|
||||
if (params.field_validation == null || params.field_validation === undefined) {
|
||||
params.field_validation = '';
|
||||
}
|
||||
if (this.currentElement == null || this.currentElement.name == null || this.currentElement.name === '') {
|
||||
params.name = this.getNameFromFieldName(params.field_label);
|
||||
} else {
|
||||
params.name = this.currentElement.name;
|
||||
}
|
||||
|
||||
data[0] = params.name;
|
||||
params.data = JSON.stringify(data);
|
||||
|
||||
params.display = 'Form';
|
||||
|
||||
params.display_order = parseInt(params.display_order);
|
||||
if (!Number.isInteger(params.display_order)) {
|
||||
params.display_order = 1;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = { CommonCustomFieldAdapter };
|
||||
@@ -98,6 +98,7 @@ class DashboardAdapter extends AdapterBase {
|
||||
}
|
||||
|
||||
drawEmployeeDistributionChart() {
|
||||
const that = this;
|
||||
document.getElementById('EmployeeDistributionChart').style.display = 'none';
|
||||
ReactDOM.render(
|
||||
this.getSpinner(),
|
||||
@@ -115,7 +116,7 @@ class DashboardAdapter extends AdapterBase {
|
||||
forceFit: true,
|
||||
title: {
|
||||
visible: true,
|
||||
text: 'Employee Distribution',
|
||||
text: that.gt('Employee Distribution'),
|
||||
},
|
||||
description: {
|
||||
visible: false,
|
||||
@@ -125,7 +126,7 @@ class DashboardAdapter extends AdapterBase {
|
||||
visible: true,
|
||||
content: {
|
||||
value: chartData.reduce((acc, item) => acc + item.value, 0),
|
||||
name: 'Total',
|
||||
name: that.gt('Total'),
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
@@ -151,6 +152,7 @@ class DashboardAdapter extends AdapterBase {
|
||||
}
|
||||
|
||||
drawOnlineOfflineEmployeeChart() {
|
||||
const that = this;
|
||||
document.getElementById('EmployeeOnlineOfflineChart').style.display = 'none';
|
||||
ReactDOM.render(
|
||||
this.getSpinner(),
|
||||
@@ -168,7 +170,7 @@ class DashboardAdapter extends AdapterBase {
|
||||
forceFit: true,
|
||||
title: {
|
||||
visible: true,
|
||||
text: 'Employee Check-Ins',
|
||||
text: that.gt('Employee Check-Ins'),
|
||||
},
|
||||
description: {
|
||||
visible: false,
|
||||
@@ -178,7 +180,7 @@ class DashboardAdapter extends AdapterBase {
|
||||
visible: true,
|
||||
content: {
|
||||
value: chartData.reduce((acc, item) => acc + item.value, 0),
|
||||
name: 'Total',
|
||||
name: that.gt('Total'),
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
@@ -199,6 +201,7 @@ class DashboardAdapter extends AdapterBase {
|
||||
}
|
||||
|
||||
drawCompanyLeaveEntitlementChart() {
|
||||
const that = this;
|
||||
document.getElementById('CompanyLeaveEntitlementChart').style.display = 'none';
|
||||
ReactDOM.render(
|
||||
this.getSpinner(),
|
||||
@@ -216,7 +219,7 @@ class DashboardAdapter extends AdapterBase {
|
||||
forceFit: true,
|
||||
title: {
|
||||
visible: true,
|
||||
text: 'Company Vacation Usage',
|
||||
text: that.gt('Company Vacation Usage'),
|
||||
},
|
||||
description: {
|
||||
visible: false,
|
||||
@@ -226,7 +229,7 @@ class DashboardAdapter extends AdapterBase {
|
||||
visible: true,
|
||||
content: {
|
||||
value: chartData.reduce((acc, item) => acc + item.value, 0),
|
||||
name: 'Total',
|
||||
name: that.gt('Total'),
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
|
||||
@@ -34,6 +34,8 @@ class DocumentAdapter extends AdapterBase {
|
||||
['expire_notification_month', { label: 'Notify Expiry Before One Month', type: 'select', source: [['Yes', 'Yes'], ['No', 'No']] }],
|
||||
['expire_notification_week', { label: 'Notify Expiry Before One Week', type: 'select', source: [['Yes', 'Yes'], ['No', 'No']] }],
|
||||
['expire_notification_day', { label: 'Notify Expiry Before One Day', type: 'select', source: [['Yes', 'Yes'], ['No', 'No']] }],
|
||||
['share_with_employee', { label: 'Share with Employee', type: 'select', source: [['Yes', 'Yes'], ['No', 'No']] }],
|
||||
// [ "sign", {"label":"Require Signature","type":"select","source":[["Yes","Yes"],["No","No"]]}],
|
||||
// [ "sign", {"label":"Require Signature","type":"select","source":[["Yes","Yes"],["No","No"]]}],
|
||||
// [ "sign_label", {"label":"Signature Description","type":"textarea","validation":"none"}],
|
||||
['details', { label: 'Details', type: 'textarea', validation: 'none' }],
|
||||
@@ -146,6 +148,7 @@ class EmployeeDocumentAdapter extends AdapterBase {
|
||||
['date_added', { label: 'Date Added', type: 'date', validation: '' }],
|
||||
['valid_until', { label: 'Valid Until', type: 'date', validation: 'none' }],
|
||||
['status', { label: 'Status', type: 'select', source: [['Active', 'Active'], ['Inactive', 'Inactive'], ['Draft', 'Draft']] }],
|
||||
['visible_to', { label: 'Visible To', type: 'select', source: [['Owner', 'Owner'], ['Manager', 'Manager'], ['Admin', 'Admin']] }],
|
||||
['details', { label: 'Details', type: 'textarea', validation: 'none' }],
|
||||
['attachment', { label: 'Attachment', type: 'fileupload', validation: '' }],
|
||||
];
|
||||
|
||||
@@ -363,28 +363,7 @@ class EmployeeAdapter extends ReactModalAdapterBase {
|
||||
});
|
||||
}
|
||||
|
||||
return this.addActualFields(steps, fields);
|
||||
}
|
||||
|
||||
addActualFields(steps, fields) {
|
||||
return steps.map((item) => {
|
||||
item.fields = item.fields.reduce((acc, fieldName) => {
|
||||
const field = fields.find(([name]) => name === fieldName);
|
||||
if (field) {
|
||||
acc.push(field);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
getFormOptions() {
|
||||
return {
|
||||
width: 1024,
|
||||
twoColumnLayout: false,
|
||||
};
|
||||
return this.addActualFieldsForStepModal(steps, fields);
|
||||
}
|
||||
|
||||
getFilters() {
|
||||
@@ -426,7 +405,7 @@ class EmployeeAdapter extends ReactModalAdapterBase {
|
||||
&& (
|
||||
<Tag color="volcano" onClick={() => modJs.terminateEmployee(record.id)} style={{ cursor: 'pointer' }}>
|
||||
<DeleteOutlined />
|
||||
{` ${adapter.gt('Delete')}`}
|
||||
{` ${adapter.gt('Deactivate')}`}
|
||||
</Tag>
|
||||
)}
|
||||
{adapter.hasAccess('save')
|
||||
|
||||
@@ -344,7 +344,7 @@ class PayrollColumnAdapter extends AdapterBase {
|
||||
['enabled', { label: 'Enabled', type: 'select', source: [['Yes', 'Yes'], ['No', 'No']] }],
|
||||
['default_value', { label: 'Default Value', type: 'text', validation: '' }],
|
||||
fucntionColumnList,
|
||||
['function_type', { label: 'Function Type', type: 'select', source: [['Advanced', 'Advanced'], ['Simple', 'Simple']] }],
|
||||
['function_type', { label: 'Function Type', type: 'select', source: [['Simple', 'Simple']] }],
|
||||
['calculation_function', { label: 'Function', type: 'code', validation: 'none' }],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -3,13 +3,21 @@
|
||||
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
|
||||
*/
|
||||
|
||||
import AdapterBase from '../../../api/AdapterBase';
|
||||
import ReactModalAdapterBase from '../../../api/ReactModalAdapterBase';
|
||||
|
||||
/**
|
||||
* ProjectAdapter
|
||||
*/
|
||||
|
||||
class ProjectAdapter extends AdapterBase {
|
||||
class ProjectAdapter extends ReactModalAdapterBase {
|
||||
constructor(endPoint, tab, filter, orderBy) {
|
||||
super(endPoint, tab, filter, orderBy);
|
||||
this.fieldNameMap = {};
|
||||
this.hiddenFields = {};
|
||||
this.tableFields = {};
|
||||
this.formOnlyFields = {};
|
||||
}
|
||||
|
||||
getDataMapping() {
|
||||
return [
|
||||
'id',
|
||||
@@ -26,6 +34,21 @@ class ProjectAdapter extends AdapterBase {
|
||||
];
|
||||
}
|
||||
|
||||
getTableColumns() {
|
||||
return [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: 'Client',
|
||||
dataIndex: 'client',
|
||||
sorter: true,
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
getFormFields() {
|
||||
if (this.showSave) {
|
||||
return [
|
||||
@@ -60,7 +83,15 @@ class ProjectAdapter extends AdapterBase {
|
||||
*/
|
||||
|
||||
|
||||
class EmployeeProjectAdapter extends AdapterBase {
|
||||
class EmployeeProjectAdapter extends ReactModalAdapterBase {
|
||||
constructor(endPoint, tab, filter, orderBy) {
|
||||
super(endPoint, tab, filter, orderBy);
|
||||
this.fieldNameMap = {};
|
||||
this.hiddenFields = {};
|
||||
this.tableFields = {};
|
||||
this.formOnlyFields = {};
|
||||
}
|
||||
|
||||
getDataMapping() {
|
||||
return [
|
||||
'id',
|
||||
@@ -77,6 +108,22 @@ class EmployeeProjectAdapter extends AdapterBase {
|
||||
];
|
||||
}
|
||||
|
||||
getTableColumns() {
|
||||
return [
|
||||
{
|
||||
title: 'Employee',
|
||||
dataIndex: 'employee',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: 'Project',
|
||||
dataIndex: 'project',
|
||||
sorter: true,
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
getFormFields() {
|
||||
return [
|
||||
['id', { label: 'ID', type: 'hidden' }],
|
||||
|
||||
@@ -9,8 +9,6 @@ import TimeUtils from './TimeUtils';
|
||||
import RequestCache from './RequestCache';
|
||||
import SocialShare from './SocialShare';
|
||||
|
||||
const Aes = require('./Aes');
|
||||
|
||||
window.RequestCache = RequestCache;
|
||||
window.SocialShare = SocialShare;
|
||||
|
||||
|
||||
@@ -104,16 +104,16 @@ class ApproveAdminAdapter extends LogViewAdapter {
|
||||
|
||||
getStatusOptionsData(currentStatus) {
|
||||
const data = {};
|
||||
if (currentStatus == 'Approved') {
|
||||
if (currentStatus === 'Approved') {
|
||||
|
||||
} else if (currentStatus == 'Pending') {
|
||||
} else if (currentStatus === 'Pending') {
|
||||
data.Approved = 'Approved';
|
||||
data.Rejected = 'Rejected';
|
||||
} else if (currentStatus == 'Rejected') {
|
||||
} else if (currentStatus === 'Rejected') {
|
||||
|
||||
} else if (currentStatus == 'Cancelled') {
|
||||
} else if (currentStatus === 'Cancelled') {
|
||||
|
||||
} else if (currentStatus == 'Processing') {
|
||||
} else if (currentStatus === 'Processing') {
|
||||
|
||||
} else {
|
||||
data['Cancellation Requested'] = 'Cancellation Requested';
|
||||
|
||||
@@ -37,7 +37,7 @@ class CustomFieldAdapter extends AdapterBase {
|
||||
['id', { label: 'ID', type: 'hidden' }],
|
||||
['name', { label: 'Name', type: 'text', validation: '' }],
|
||||
['display', { label: 'Display Status', type: 'select', source: [['Form', 'Show'], ['Hidden', 'Hidden']] }],
|
||||
['field_type', { label: 'Field Type', type: 'select', source: [['text', 'Text Field'], ['textarea', 'Text Area'], ['select', 'Select'], ['select2', 'Select2'], ['select2multi', 'Multi Select'], ['fileupload', 'File Upload'], ['date', 'Date'], ['datetime', 'Date Time'], ['time', 'Time']] }],
|
||||
['field_type', { label: 'Field Type', type: 'select', source: [['text', 'Text Field'], ['textarea', 'Text Area'], ['select', 'Select'], ['select2', 'Select2'], ['select2multi', 'Multi Select'], ['fileupload', 'File Upload'], ['date', 'Date'], ['datetime', 'Date Time'], ['time', 'Time'], ['signature', 'Signature']] }],
|
||||
['field_label', { label: 'Field Label', type: 'text', validation: '' }],
|
||||
['field_validation', {
|
||||
label: 'Validation', type: 'select2', validation: 'none', sort: 'none', 'null-label': 'Required', 'allow-null': true, source: [['none', 'None'], ['number', 'Number'], ['numberOrEmpty', 'Number or Empty'], ['float', 'Decimal'], ['email', 'Email'], ['emailOrEmpty', 'Email or Empty']],
|
||||
|
||||
@@ -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';
|
||||
@@ -143,6 +143,7 @@ class ModuleBase {
|
||||
|
||||
gt(key) {
|
||||
if (this.translations[key] === undefined || this.translations[key] === null) {
|
||||
console.log("Tr:" + key);
|
||||
return key;
|
||||
}
|
||||
return this.translations[key][0];
|
||||
@@ -168,15 +169,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 +202,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 = {};
|
||||
@@ -321,26 +322,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;
|
||||
@@ -373,14 +374,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();
|
||||
}
|
||||
@@ -598,10 +599,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;
|
||||
@@ -679,10 +680,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;
|
||||
@@ -749,10 +750,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" },
|
||||
@@ -760,17 +761,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",
|
||||
@@ -778,23 +779,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() {
|
||||
|
||||
}
|
||||
@@ -808,26 +809,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, []);
|
||||
@@ -891,10 +892,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;
|
||||
@@ -903,17 +904,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 = '';
|
||||
@@ -1006,11 +1007,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' });
|
||||
@@ -1041,7 +1042,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 {
|
||||
@@ -1057,7 +1058,7 @@ class ModuleBase {
|
||||
return this.gt('Password too short');
|
||||
}
|
||||
|
||||
if (password.length > 20) {
|
||||
if (password.length > 30) {
|
||||
return this.gt('Password too long');
|
||||
}
|
||||
|
||||
@@ -1085,23 +1086,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']);
|
||||
@@ -1115,7 +1116,7 @@ class ModuleBase {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
*/
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
doCustomValidation(params) {
|
||||
return null;
|
||||
@@ -1237,20 +1238,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;
|
||||
@@ -1351,20 +1352,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 = [];
|
||||
@@ -1588,28 +1589,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);
|
||||
@@ -1664,10 +1665,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('');
|
||||
@@ -2061,12 +2062,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;
|
||||
@@ -2180,7 +2181,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') {
|
||||
@@ -2197,9 +2198,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();
|
||||
@@ -2422,49 +2423,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) {
|
||||
@@ -2473,20 +2474,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() {
|
||||
@@ -2499,12 +2500,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>';
|
||||
@@ -2537,11 +2538,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();
|
||||
@@ -2602,10 +2603,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;
|
||||
|
||||
134
web/api/ReactApproveAdminAdapter.js
Normal file
134
web/api/ReactApproveAdminAdapter.js
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
Copyright (c) 2018 [Glacies UG, Berlin, Germany] (http://glacies.de)
|
||||
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
|
||||
*/
|
||||
|
||||
/**
|
||||
* ReactApproveAdminAdapter
|
||||
*/
|
||||
import React from 'react';
|
||||
import ReactLogViewAdapter from './ReactLogViewAdapter';
|
||||
import {Space, Tag} from "antd";
|
||||
import {CopyOutlined, DeleteOutlined, EditOutlined, MonitorOutlined} from "@ant-design/icons";
|
||||
|
||||
class ReactApproveAdminAdapter extends ReactLogViewAdapter {
|
||||
constructor(endPoint, tab, filter, orderBy) {
|
||||
super(endPoint, tab, filter, orderBy);
|
||||
}
|
||||
|
||||
getStatusFieldPosition() {
|
||||
const dm = this.getDataMapping();
|
||||
return dm.length - 1;
|
||||
}
|
||||
|
||||
openStatus(id, status) {
|
||||
$(`#${this.itemNameLower}StatusModel`).modal('show');
|
||||
$(`#${this.itemNameLower}_status`).html(this.getStatusOptions(status));
|
||||
$(`#${this.itemNameLower}_status`).val(status);
|
||||
this.statusChangeId = id;
|
||||
}
|
||||
|
||||
closeDialog() {
|
||||
$(`#${this.itemNameLower}StatusModel`).modal('hide');
|
||||
}
|
||||
|
||||
changeStatus() {
|
||||
const status = $(`#${this.itemNameLower}_status`).val();
|
||||
const reason = $(`#${this.itemNameLower}_reason`).val();
|
||||
|
||||
if (status == undefined || status == null || status == '') {
|
||||
this.showMessage('Error', `Please select ${this.itemNameLower} status`);
|
||||
return;
|
||||
}
|
||||
|
||||
const object = { id: this.statusChangeId, status, reason };
|
||||
|
||||
const reqJson = JSON.stringify(object);
|
||||
|
||||
const callBackData = [];
|
||||
callBackData.callBackData = [];
|
||||
callBackData.callBackSuccess = 'changeStatusSuccessCallBack';
|
||||
callBackData.callBackFail = 'changeStatusFailCallBack';
|
||||
|
||||
this.customAction('changeStatus', `admin=${this.modulePathName}`, reqJson, callBackData);
|
||||
|
||||
this.closeDialog();
|
||||
this.statusChangeId = null;
|
||||
}
|
||||
|
||||
changeStatusSuccessCallBack(callBackData) {
|
||||
this.showMessage('Successful', `${this.itemName} Request status changed successfully`);
|
||||
this.get([]);
|
||||
}
|
||||
|
||||
changeStatusFailCallBack(callBackData) {
|
||||
this.showMessage('Error', `Error occurred while changing ${this.itemName} request status`);
|
||||
}
|
||||
|
||||
getTableActionButtonJsx(adapter) {
|
||||
return (text, record) => (
|
||||
<Space size="middle">
|
||||
{adapter.hasAccess('save') && adapter.showEdit
|
||||
&& (
|
||||
<Tag color="green" onClick={() => modJs.edit(record.id)} style={{ cursor: 'pointer' }}>
|
||||
<EditOutlined />
|
||||
{` ${adapter.gt('Edit')}`}
|
||||
</Tag>
|
||||
)}
|
||||
{adapter.hasAccess('delete') && adapter.showDelete
|
||||
&& (
|
||||
<Tag color="volcano" onClick={() => modJs.deleteRow(record.id)} style={{ cursor: 'pointer' }}>
|
||||
<DeleteOutlined />
|
||||
{` ${adapter.gt('Delete')}`}
|
||||
</Tag>
|
||||
)}
|
||||
{Object.keys(this.getStatusOptionsData(record.status)).length > 0
|
||||
&& (
|
||||
<Tag color="blue" onClick={() => modJs.openStatus(record.id, record.status)} style={{ cursor: 'pointer' }}>
|
||||
<MonitorOutlined />
|
||||
{` ${adapter.gt('Change Status')}`}
|
||||
</Tag>
|
||||
)}
|
||||
<Tag color="cyan" onClick={() => modJs.getLogs(record.id)} style={{ cursor: 'pointer' }}>
|
||||
<CopyOutlined />
|
||||
{` ${adapter.gt('View Logs')}`}
|
||||
</Tag>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
hasCustomButtons() {
|
||||
return true;
|
||||
}
|
||||
|
||||
isSubProfileTable() {
|
||||
return this.user.user_level !== 'Admin' && this.user.user_level !== 'Restricted Admin';
|
||||
}
|
||||
|
||||
getStatusOptionsData(currentStatus) {
|
||||
const data = {};
|
||||
if (currentStatus === 'Approved') {
|
||||
|
||||
} else if (currentStatus === 'Pending') {
|
||||
data.Approved = 'Approved';
|
||||
data.Rejected = 'Rejected';
|
||||
} else if (currentStatus === 'Rejected') {
|
||||
|
||||
} else if (currentStatus === 'Cancelled') {
|
||||
|
||||
} else if (currentStatus === 'Processing') {
|
||||
|
||||
} else {
|
||||
data['Cancellation Requested'] = 'Cancellation Requested';
|
||||
data.Cancelled = 'Cancelled';
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
getStatusOptions(currentStatus) {
|
||||
return this.generateOptions(this.getStatusOptionsData(currentStatus));
|
||||
}
|
||||
}
|
||||
|
||||
export default ReactApproveAdminAdapter;
|
||||
71
web/api/ReactApproveModuleAdapter.js
Normal file
71
web/api/ReactApproveModuleAdapter.js
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright (c) 2018 [Glacies UG, Berlin, Germany] (http://glacies.de)
|
||||
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Space, Tag } from 'antd';
|
||||
import {
|
||||
CopyOutlined, DeleteOutlined, EditOutlined, MonitorOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import ReactLogViewAdapter from './ReactLogViewAdapter';
|
||||
|
||||
class ReactApproveModuleAdapter extends ReactLogViewAdapter {
|
||||
cancelRequest(id) {
|
||||
const object = {};
|
||||
object.id = id;
|
||||
|
||||
const reqJson = JSON.stringify(object);
|
||||
|
||||
const callBackData = [];
|
||||
callBackData.callBackData = [];
|
||||
callBackData.callBackSuccess = 'cancelSuccessCallBack';
|
||||
callBackData.callBackFail = 'cancelFailCallBack';
|
||||
|
||||
this.customAction('cancel', `modules=${this.modulePathName}`, reqJson, callBackData);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
cancelSuccessCallBack(callBackData) {
|
||||
this.showMessage('Successful', `${this.itemName} cancellation request sent`);
|
||||
this.get([]);
|
||||
}
|
||||
|
||||
cancelFailCallBack(callBackData) {
|
||||
this.showMessage(`Error Occurred while cancelling ${this.itemName}`, callBackData);
|
||||
}
|
||||
|
||||
getTableActionButtonJsx(adapter) {
|
||||
return (text, record) => (
|
||||
<Space size="middle">
|
||||
{adapter.hasAccess('save') && adapter.showEdit
|
||||
&& (
|
||||
<Tag color="green" onClick={() => modJs.edit(record.id)} style={{ cursor: 'pointer' }}>
|
||||
<EditOutlined />
|
||||
{` ${adapter.gt('Edit')}`}
|
||||
</Tag>
|
||||
)}
|
||||
{adapter.hasAccess('delete') && adapter.showDelete && record.status === 'Approved'
|
||||
&& (
|
||||
<Tag color="volcano" onClick={() => modJs.cancelRequest(record.id)} style={{ cursor: 'pointer' }}>
|
||||
<DeleteOutlined />
|
||||
{` ${adapter.gt('Cancel')}`}
|
||||
</Tag>
|
||||
)}
|
||||
{adapter.hasAccess('delete') && adapter.showDelete && record.status === 'Pending'
|
||||
&& this.user.user_level === 'Admin'
|
||||
&& (
|
||||
<Tag color="volcano" onClick={() => modJs.deleteRow(record.id)} style={{ cursor: 'pointer' }}>
|
||||
<DeleteOutlined />
|
||||
{` ${adapter.gt('Delete')}`}
|
||||
</Tag>
|
||||
)}
|
||||
<Tag color="cyan" onClick={() => modJs.getLogs(record.id)} style={{ cursor: 'pointer' }}>
|
||||
<CopyOutlined />
|
||||
{` ${adapter.gt('View Logs')}`}
|
||||
</Tag>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ReactApproveModuleAdapter;
|
||||
@@ -20,6 +20,8 @@ class ReactCustomFieldAdapter extends AdapterBase {
|
||||
return [
|
||||
'id',
|
||||
'name',
|
||||
'field_type',
|
||||
'field_label',
|
||||
'display',
|
||||
'display_order',
|
||||
];
|
||||
@@ -29,6 +31,8 @@ class ReactCustomFieldAdapter extends AdapterBase {
|
||||
return [
|
||||
{ sTitle: 'ID', bVisible: false },
|
||||
{ sTitle: 'Name' },
|
||||
{ sTitle: 'Field Type' },
|
||||
{ sTitle: 'Field Label' },
|
||||
{ sTitle: 'Display Status' },
|
||||
{ sTitle: 'Priority' },
|
||||
];
|
||||
@@ -40,17 +44,24 @@ class ReactCustomFieldAdapter extends AdapterBase {
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
sorter: true,
|
||||
width: '25%',
|
||||
},
|
||||
{
|
||||
title: 'Field Label',
|
||||
dataIndex: 'field_label',
|
||||
},
|
||||
{
|
||||
title: 'Field Type',
|
||||
dataIndex: 'field_type',
|
||||
},
|
||||
{
|
||||
title: 'Display Status',
|
||||
dataIndex: 'display',
|
||||
width: '35%',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: 'Priority',
|
||||
dataIndex: 'display_order',
|
||||
width: '10%',
|
||||
sorter: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -58,10 +69,8 @@ class ReactCustomFieldAdapter extends AdapterBase {
|
||||
getFormFields() {
|
||||
return [
|
||||
['id', { label: 'ID', type: 'hidden' }],
|
||||
['name', { label: 'Name', type: 'text', validation: '' }],
|
||||
['display', { label: 'Display Status', type: 'select', source: [['Form', 'Show'], ['Hidden', 'Hidden']] }],
|
||||
['field_type', { label: 'Field Type', type: 'select', source: [['text', 'Text Field'], ['textarea', 'Text Area'], ['select', 'Select'], ['select2', 'Select2'], ['select2multi', 'Multi Select'], ['fileupload', 'File Upload'], ['date', 'Date'], ['datetime', 'Date Time'], ['time', 'Time']] }],
|
||||
['field_label', { label: 'Field Label', type: 'text', validation: '' }],
|
||||
['field_type', { label: 'Field Type', type: 'select', source: [['text', 'Text Field'], ['textarea', 'Text Area'], ['select', 'Select'], ['select2', 'Select2'], ['select2multi', 'Multi Select'], ['fileupload', 'File Upload'], ['date', 'Date'], ['datetime', 'Date Time'], ['time', 'Time'], ['signature', 'Signature']] }],
|
||||
['field_validation', {
|
||||
label: 'Validation', type: 'select2', validation: 'none', sort: 'none', 'null-label': 'Required', 'allow-null': true, source: [['none', 'None'], ['number', 'Number'], ['numberOrEmpty', 'Number or Empty'], ['float', 'Decimal'], ['email', 'Email'], ['emailOrEmpty', 'Email or Empty']],
|
||||
}],
|
||||
@@ -87,11 +96,14 @@ class ReactCustomFieldAdapter extends AdapterBase {
|
||||
],
|
||||
validation: 'none',
|
||||
}],
|
||||
['display_order', { label: 'Priority', type: 'text', validation: 'number' }],
|
||||
['display_section', { label: 'Display Section', type: 'text', validation: 'none' }],
|
||||
['display_order', { label: 'Priority', type: 'text', validation: 'none' }],
|
||||
];
|
||||
}
|
||||
|
||||
getNameFromFieldName(fieldName) {
|
||||
return fieldName.replace(/[^a-z0-9+]+/gi, '').toLowerCase();
|
||||
}
|
||||
|
||||
setTableType(type) {
|
||||
this.tableType = type;
|
||||
}
|
||||
@@ -102,18 +114,26 @@ class ReactCustomFieldAdapter extends AdapterBase {
|
||||
return str != null && name.test(str);
|
||||
};
|
||||
|
||||
if (this.currentElement == null || this.currentElement.name == null || this.currentElement.name === '') {
|
||||
params.name = this.getNameFromFieldName(params.field_label);
|
||||
if (!validateName(params.name)) {
|
||||
return 'Invalid field label for custom field';
|
||||
}
|
||||
} else {
|
||||
params.name = this.currentElement.name;
|
||||
}
|
||||
|
||||
if (!validateName(params.name)) {
|
||||
return 'Invalid name for custom field';
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
forceInjectValuesBeforeSave(params) {
|
||||
const data = [params.name]; const options = []; let
|
||||
optionsData;
|
||||
data.push({});
|
||||
const data = ['', {}];
|
||||
const options = [];
|
||||
let optionsData;
|
||||
|
||||
data[1].label = params.field_label;
|
||||
data[1].type = params.field_type;
|
||||
data[1].validation = params.field_validation;
|
||||
@@ -128,8 +148,23 @@ class ReactCustomFieldAdapter extends AdapterBase {
|
||||
if (params.field_validation == null || params.field_validation === undefined) {
|
||||
params.field_validation = '';
|
||||
}
|
||||
params.data = JSON.stringify(data);
|
||||
|
||||
params.type = this.tableType;
|
||||
if (this.currentElement == null || this.currentElement.name == null || this.currentElement.name === '') {
|
||||
params.name = this.getNameFromFieldName(params.field_label);
|
||||
} else {
|
||||
params.name = this.currentElement.name;
|
||||
}
|
||||
|
||||
data[0] = params.name;
|
||||
params.data = JSON.stringify(data);
|
||||
|
||||
params.display = 'Form';
|
||||
params.display_order = parseInt(params.display_order);
|
||||
if (!Number.isInteger(params.display_order)) {
|
||||
params.display_order = 1;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
||||
45
web/api/ReactIdNameAdapter.js
Normal file
45
web/api/ReactIdNameAdapter.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import ReactModalAdapterBase from './ReactModalAdapterBase';
|
||||
|
||||
class ReactIdNameAdapter extends ReactModalAdapterBase {
|
||||
constructor(endPoint, tab, filter, orderBy) {
|
||||
super(endPoint, tab, filter, orderBy);
|
||||
}
|
||||
|
||||
getDataMapping() {
|
||||
return [
|
||||
'id',
|
||||
'name',
|
||||
];
|
||||
}
|
||||
|
||||
getHeaders() {
|
||||
return [
|
||||
{ sTitle: 'ID', bVisible: false },
|
||||
{ sTitle: 'Name' },
|
||||
];
|
||||
}
|
||||
|
||||
getFormFields() {
|
||||
return [
|
||||
['id', { label: 'ID', type: 'hidden' }],
|
||||
['name', { label: 'Name', type: 'text', validation: '' }],
|
||||
];
|
||||
}
|
||||
|
||||
getTableColumns() {
|
||||
return [
|
||||
{
|
||||
title: 'Id',
|
||||
dataIndex: 'id',
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
sorter: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export default ReactIdNameAdapter;
|
||||
58
web/api/ReactLogViewAdapter.js
Normal file
58
web/api/ReactLogViewAdapter.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright (c) 2018 [Glacies UG, Berlin, Germany] (http://glacies.de)
|
||||
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
|
||||
*/
|
||||
/* global timeUtils */
|
||||
/**
|
||||
* ReactLogViewAdapter
|
||||
*/
|
||||
|
||||
import ReactModalAdapterBase from './ReactModalAdapterBase';
|
||||
|
||||
class ReactLogViewAdapter extends ReactModalAdapterBase {
|
||||
getLogs(id) {
|
||||
const object = { id };
|
||||
const reqJson = JSON.stringify(object);
|
||||
|
||||
const callBackData = [];
|
||||
callBackData.callBackData = [];
|
||||
callBackData.callBackSuccess = 'getLogsSuccessCallBack';
|
||||
callBackData.callBackFail = 'getLogsFailCallBack';
|
||||
|
||||
this.customAction('getLogs', `admin=${this.modulePathName}`, reqJson, callBackData);
|
||||
}
|
||||
|
||||
getLogsSuccessCallBack(callBackData) {
|
||||
let tableLog = '<table class="table table-condensed table-bordered table-striped" style="font-size:14px;"><thead><tr><th>Notes</th></tr></thead><tbody>_days_</tbody></table> ';
|
||||
const rowLog = '<tr><td><span class="logTime label label-default">_date_</span> <b>_status_</b><br/>_note_</td></tr>';
|
||||
|
||||
const logs = callBackData.data;
|
||||
let html = '';
|
||||
let rowsLogs = '';
|
||||
|
||||
|
||||
for (let i = 0; i < logs.length; i++) {
|
||||
let trow = rowLog;
|
||||
trow = trow.replace(/_date_/g, logs[i].time);
|
||||
trow = trow.replace(/_status_/g, `${logs[i].status_from} -> ${logs[i].status_to}`);
|
||||
trow = trow.replace(/_note_/g, logs[i].note);
|
||||
rowsLogs += trow;
|
||||
}
|
||||
|
||||
if (rowsLogs !== '') {
|
||||
tableLog = tableLog.replace('_days_', rowsLogs);
|
||||
html += tableLog;
|
||||
}
|
||||
|
||||
this.showMessage('Logs', html);
|
||||
|
||||
timeUtils.convertToRelativeTime($('.logTime'));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
getLogsFailCallBack(callBackData) {
|
||||
this.showMessage('Error', 'Error occured while getting data');
|
||||
}
|
||||
}
|
||||
|
||||
export default ReactLogViewAdapter;
|
||||
@@ -48,6 +48,10 @@ class ReactModalAdapterBase extends AdapterBase {
|
||||
return this.access.indexOf(type) > 0;
|
||||
}
|
||||
|
||||
hasCustomButtons() {
|
||||
return false;
|
||||
}
|
||||
|
||||
initTable() {
|
||||
if (this.tableInitialized) {
|
||||
return false;
|
||||
@@ -56,7 +60,11 @@ class ReactModalAdapterBase extends AdapterBase {
|
||||
if (tableDom) {
|
||||
this.tableContainer = React.createRef();
|
||||
let columns = this.getTableColumns();
|
||||
if (this.hasAccess('save') || this.hasAccess('delete') || this.hasAccess('element')) {
|
||||
if (this.hasAccess('save')
|
||||
|| this.hasAccess('delete')
|
||||
|| this.hasAccess('element')
|
||||
|| this.hasCustomButtons()
|
||||
) {
|
||||
columns.push({
|
||||
title: 'Actions',
|
||||
key: 'actions',
|
||||
@@ -257,6 +265,27 @@ class ReactModalAdapterBase extends AdapterBase {
|
||||
showLoader() {
|
||||
// $('#iceloader').show();
|
||||
}
|
||||
|
||||
addActualFieldsForStepModal(steps, fields) {
|
||||
return steps.map((item) => {
|
||||
item.fields = item.fields.reduce((acc, fieldName) => {
|
||||
const field = fields.find(([name]) => name === fieldName);
|
||||
if (field) {
|
||||
acc.push(field);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
getFormOptions() {
|
||||
return {
|
||||
width: 1024,
|
||||
twoColumnLayout: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default ReactModalAdapterBase;
|
||||
|
||||
70
web/components/IceColorPick.js
Normal file
70
web/components/IceColorPick.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { SketchPicker } from 'react-color';
|
||||
|
||||
function useComponentVisible(initialIsVisible) {
|
||||
const [isComponentVisible, setIsComponentVisible] = useState(initialIsVisible);
|
||||
const ref = useRef(null);
|
||||
|
||||
const handleClickOutside = (event) => {
|
||||
if (ref.current && !ref.current.contains(event.target)) {
|
||||
setIsComponentVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('click', handleClickOutside, true);
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClickOutside, true);
|
||||
};
|
||||
});
|
||||
|
||||
return { ref, isComponentVisible, setIsComponentVisible };
|
||||
}
|
||||
|
||||
function IceColorPick(props) {
|
||||
const { value, onChange, readOnly } = props;
|
||||
|
||||
const { ref, isComponentVisible, setIsComponentVisible } = useComponentVisible(true);
|
||||
|
||||
const [color, setColor] = useState(value || '#FFF');
|
||||
const [showPicker, setShowPicker] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isComponentVisible) {
|
||||
setShowPicker(false);
|
||||
}
|
||||
}, [isComponentVisible])
|
||||
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
setColor(value);
|
||||
}
|
||||
|
||||
}, [value]);
|
||||
|
||||
return <div className="colorpicker-container">
|
||||
<div
|
||||
className="colorpicker-preview"
|
||||
onClick={() => {
|
||||
if (!showPicker) {
|
||||
setIsComponentVisible(true);
|
||||
}
|
||||
setShowPicker(!showPicker);
|
||||
}}
|
||||
style={ { backgroundColor: color} }
|
||||
/>
|
||||
<div ref={ref} className={`colorpicker-component ${(readOnly || !showPicker) ? 'hidden': ''}` }>
|
||||
<SketchPicker
|
||||
color={color}
|
||||
disableAlpha
|
||||
presetColors={[]}
|
||||
onChangeComplete={({ hex }) => {
|
||||
onChange(hex);
|
||||
setColor(hex);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default IceColorPick;
|
||||
@@ -21,6 +21,7 @@ class IceDataGroup extends React.Component {
|
||||
value = this.parseValue(value);
|
||||
value = value.map(item => ({ ...item, key:item.id } ));
|
||||
const columns = JSON.parse(JSON.stringify(field[1].columns));
|
||||
|
||||
if (!this.props.readOnly) {
|
||||
columns.push({
|
||||
title: 'Action',
|
||||
@@ -30,6 +31,7 @@ class IceDataGroup extends React.Component {
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!this.props.readOnly &&
|
||||
|
||||
@@ -7,6 +7,8 @@ import IceUpload from './IceUpload';
|
||||
import IceDataGroup from './IceDataGroup';
|
||||
import IceSelect from './IceSelect';
|
||||
import IceLabel from './IceLabel';
|
||||
import IceColorPick from './IceColorPick';
|
||||
import IceSignature from './IceSignature';
|
||||
|
||||
|
||||
const ValidationRules = {
|
||||
@@ -86,24 +88,36 @@ class IceForm extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { fields, twoColumnLayout } = this.props;
|
||||
const { fields, twoColumnLayout, adapter } = this.props;
|
||||
let formInputs = [];
|
||||
const formInputs1 = [];
|
||||
const formInputs2 = [];
|
||||
const columns = !twoColumnLayout ? 1 : 2;
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const formInput = this.createFromField(fields[i], this.props.viewOnly);
|
||||
if (formInput != null) {
|
||||
formInputs.push(
|
||||
adapter.beforeRenderFieldHook(
|
||||
fields[i][0],
|
||||
this.createFromField(fields[i], this.props.viewOnly),
|
||||
fields[i][1]
|
||||
)
|
||||
);
|
||||
}
|
||||
formInputs = formInputs.filter(input => !!input);
|
||||
|
||||
for (let i = 0; i < formInputs.length; i++) {
|
||||
|
||||
if (formInputs[i] != null) {
|
||||
if (columns === 1) {
|
||||
formInputs1.push(formInput);
|
||||
formInputs1.push(formInputs[i]);
|
||||
} else if (i % 2 === 0) {
|
||||
formInputs1.push(formInput);
|
||||
formInputs1.push(formInputs[i]);
|
||||
} else {
|
||||
formInputs2.push(formInput);
|
||||
formInputs2.push(formInputs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onFormLayoutChange = () => {};
|
||||
const onFormLayoutChange = () => { };
|
||||
|
||||
return (
|
||||
<Form
|
||||
@@ -116,12 +130,12 @@ class IceForm extends React.Component {
|
||||
size="middle"
|
||||
>
|
||||
{this.state.errorMsg
|
||||
&& (
|
||||
<>
|
||||
<Alert message={this.state.errorMsg} type="error" showIcon />
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
&& (
|
||||
<>
|
||||
<Alert message={this.state.errorMsg} type="error" showIcon />
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
{columns === 1 && formInputs1}
|
||||
{columns === 2 && (
|
||||
<Row gutter={16}>
|
||||
@@ -239,6 +253,9 @@ class IceForm extends React.Component {
|
||||
</Form.Item>
|
||||
);
|
||||
} if (data.type === 'textarea') {
|
||||
if (!data.rows) {
|
||||
data.rows = 4;
|
||||
}
|
||||
return (
|
||||
<Form.Item
|
||||
labelCol={labelSpan}
|
||||
@@ -249,7 +266,7 @@ class IceForm extends React.Component {
|
||||
>
|
||||
{viewOnly
|
||||
? <IceLabel />
|
||||
: <Input.TextArea />}
|
||||
: <Input.TextArea rows={data.rows} />}
|
||||
</Form.Item>
|
||||
);
|
||||
} if (data.type === 'date') {
|
||||
@@ -355,6 +372,34 @@ class IceForm extends React.Component {
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
} if (data.type === 'colorpick') {
|
||||
return (
|
||||
<Form.Item
|
||||
labelCol={labelSpan}
|
||||
name={name}
|
||||
key={name}
|
||||
label={data.label}
|
||||
>
|
||||
<IceColorPick
|
||||
adapter={adapter}
|
||||
field={field}
|
||||
title={data.label}
|
||||
readOnly={viewOnly}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
} if (data.type === 'signature') {
|
||||
return (
|
||||
<Form.Item
|
||||
labelCol={labelSpan}
|
||||
label={data.label}
|
||||
key={name}
|
||||
name={name}
|
||||
rules={rules}
|
||||
>
|
||||
<IceSignature readOnly={viewOnly} />
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ class IceFormModal extends React.Component {
|
||||
}
|
||||
|
||||
show(data) {
|
||||
this.props.adapter.beforeRenderFieldHook = this.props.adapter.beforeRenderField ? this.props.adapter.beforeRenderField(data) : (fieldName, field) => field;
|
||||
if (!data) {
|
||||
this.setState({ visible: true });
|
||||
if (this.iceFormReference.current) {
|
||||
|
||||
83
web/components/IceSignature.js
Normal file
83
web/components/IceSignature.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
import SignatureCanvas from 'react-signature-canvas';
|
||||
import { Button, Modal, Tag } from 'antd';
|
||||
import { VerifiedOutlined } from '@ant-design/icons';
|
||||
|
||||
class IceSignature extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onChange = props.onChange;
|
||||
this.state = {
|
||||
visible: false,
|
||||
};
|
||||
this.signature = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
}
|
||||
|
||||
show() {
|
||||
this.setState({ visible: true });
|
||||
}
|
||||
|
||||
setSignature(ref) {
|
||||
if (ref == null) {
|
||||
return;
|
||||
}
|
||||
const { value } = this.props;
|
||||
if (value != null && value.length > 10) {
|
||||
ref.fromDataURL(value);
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.setState({ visible: false });
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.signature.clear();
|
||||
}
|
||||
|
||||
save() {
|
||||
const data = this.signature.toDataURL('image/png');
|
||||
this.onChange(data);
|
||||
this.setState({ visible: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { readOnly } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
visible={this.state.visible}
|
||||
title="Signature"
|
||||
maskClosable={false}
|
||||
centered
|
||||
width={300}
|
||||
onCancel={() => { this.hide(); }}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={() => { this.hide(); }}>
|
||||
Cancel
|
||||
</Button>,
|
||||
<Button key="clear" disabled={readOnly} type="dashed" onClick={() => { if (!readOnly) { this.clear(); } }}>
|
||||
Clear
|
||||
</Button>,
|
||||
<Button key="ok" disabled={readOnly} type="primary" onClick={() => { if (!readOnly) { this.save(); } }}>
|
||||
Submit
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<SignatureCanvas ref={(ref) => { this.signature = ref; this.setSignature(ref); }} canvasProps={{ width: 250, height: 200, className: 'sigCanvas', ...( readOnly ? { readOnly } : {}), }} />
|
||||
</Modal>
|
||||
<Tag color="blue" style={{ cursor: 'pointer' }} onClick={() => { this.show(); }}>
|
||||
<VerifiedOutlined />
|
||||
{' '}
|
||||
Sign
|
||||
</Tag>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default IceSignature;
|
||||
@@ -10,6 +10,7 @@ class IceStepFormModal extends IceFormModal {
|
||||
}
|
||||
|
||||
show(data) {
|
||||
this.props.adapter.beforeRenderFieldHook = this.props.adapter.beforeRenderField ? this.props.adapter.beforeRenderField(data) : (fieldName, field) => field;
|
||||
if (!data) {
|
||||
this.setState({ visible: true });
|
||||
if (this.iceFormReference.current) {
|
||||
|
||||
@@ -69,7 +69,6 @@ class IceTable extends React.Component {
|
||||
};
|
||||
|
||||
search = (value) => {
|
||||
console.log('search table:' + value);
|
||||
this.setState({ search: value });
|
||||
const fetchConfig = this.state.fetchConfig;
|
||||
console.log(fetchConfig);
|
||||
@@ -105,12 +104,10 @@ class IceTable extends React.Component {
|
||||
}
|
||||
|
||||
fetch = (params = {}) => {
|
||||
console.log('params:', params);
|
||||
//this.setState({ loading: this.state.showLoading });
|
||||
this.setState({ loading: true });
|
||||
//const hideMessage = message.loading({ content: 'Loading Latest Data ...', key: 'loadingTable', duration: 1});
|
||||
const pagination = { ...this.state.pagination };
|
||||
console.log('pagination:', pagination);
|
||||
|
||||
if (this.props.adapter.localStorageEnabled) {
|
||||
try {
|
||||
|
||||
@@ -150,7 +150,7 @@ class UpdatePasswordModal extends React.Component {
|
||||
return this.props.adapter.gt('Password too short');
|
||||
}
|
||||
|
||||
if (password.length > 20) {
|
||||
if (password.length > 30) {
|
||||
return this.props.adapter.gt('Password too long');
|
||||
}
|
||||
|
||||
|
||||
@@ -947,3 +947,69 @@ table.dataTable{
|
||||
.mod-tab {
|
||||
margin-bottom:0px;margin-left:5px;border-bottom: none;
|
||||
}
|
||||
|
||||
.row {
|
||||
margin-right: 0px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.colorpicker-container {
|
||||
|
||||
}
|
||||
|
||||
.colorpicker-preview {
|
||||
display: block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 5px #FFF solid;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 6px 1px #c7c7c78f;
|
||||
}
|
||||
|
||||
.colorpicker-component {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: 44px;
|
||||
}
|
||||
|
||||
.dropdown-menu>li>a:hover {
|
||||
background-color: #5ca4e7 i !important;
|
||||
}
|
||||
.sigCanvas {
|
||||
border: 1px dashed;
|
||||
}
|
||||
|
||||
.sigContainer {
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.sigPad {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sigImage {
|
||||
background-size: 200px 50px;
|
||||
width: 200px;
|
||||
height: 50px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.dropdown-menu>li>a:hover {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.ant-form-item-label>label {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: auto !important;
|
||||
color: rgba(0,0,0,.85);
|
||||
font-size: 14px;
|
||||
word-break: break-word;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
|
||||
23
web/dist/admin-bundle.js
vendored
23
web/dist/admin-bundle.js
vendored
File diff suppressed because one or more lines are too long
8
web/dist/common.js
vendored
8
web/dist/common.js
vendored
File diff suppressed because one or more lines are too long
25
web/dist/modules-bundle.js
vendored
25
web/dist/modules-bundle.js
vendored
File diff suppressed because one or more lines are too long
2
web/dist/third-party.css
vendored
2
web/dist/third-party.css
vendored
File diff suppressed because one or more lines are too long
98
web/dist/third-party.js
vendored
98
web/dist/third-party.js
vendored
@@ -19430,16 +19430,16 @@ unlimitRow:function(a){var b=this.rowStructs[a];b.moreEls&&(b.moreEls.remove(),b
|
||||
* Licensed MIT © Zeno Rocha
|
||||
*/
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Clipboard = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
var matches = require('matches-selector')
|
||||
|
||||
module.exports = function (element, selector, checkYoSelf) {
|
||||
var parent = checkYoSelf ? element : element.parentNode
|
||||
|
||||
while (parent && parent !== document) {
|
||||
if (matches(parent, selector)) return parent;
|
||||
parent = parent.parentNode
|
||||
}
|
||||
}
|
||||
var matches = require('matches-selector')
|
||||
|
||||
module.exports = function (element, selector, checkYoSelf) {
|
||||
var parent = checkYoSelf ? element : element.parentNode
|
||||
|
||||
while (parent && parent !== document) {
|
||||
if (matches(parent, selector)) return parent;
|
||||
parent = parent.parentNode
|
||||
}
|
||||
}
|
||||
|
||||
},{"matches-selector":5}],2:[function(require,module,exports){
|
||||
var closest = require('closest');
|
||||
@@ -19636,45 +19636,45 @@ function listenSelector(selector, type, callback) {
|
||||
module.exports = listen;
|
||||
|
||||
},{"./is":3,"delegate":2}],5:[function(require,module,exports){
|
||||
|
||||
/**
|
||||
* Element prototype.
|
||||
*/
|
||||
|
||||
var proto = Element.prototype;
|
||||
|
||||
/**
|
||||
* Vendor function.
|
||||
*/
|
||||
|
||||
var vendor = proto.matchesSelector
|
||||
|| proto.webkitMatchesSelector
|
||||
|| proto.mozMatchesSelector
|
||||
|| proto.msMatchesSelector
|
||||
|| proto.oMatchesSelector;
|
||||
|
||||
/**
|
||||
* Expose `match()`.
|
||||
*/
|
||||
|
||||
module.exports = match;
|
||||
|
||||
/**
|
||||
* Match `el` to `selector`.
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {String} selector
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function match(el, selector) {
|
||||
if (vendor) return vendor.call(el, selector);
|
||||
var nodes = el.parentNode.querySelectorAll(selector);
|
||||
for (var i = 0; i < nodes.length; ++i) {
|
||||
if (nodes[i] == el) return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
/**
|
||||
* Element prototype.
|
||||
*/
|
||||
|
||||
var proto = Element.prototype;
|
||||
|
||||
/**
|
||||
* Vendor function.
|
||||
*/
|
||||
|
||||
var vendor = proto.matchesSelector
|
||||
|| proto.webkitMatchesSelector
|
||||
|| proto.mozMatchesSelector
|
||||
|| proto.msMatchesSelector
|
||||
|| proto.oMatchesSelector;
|
||||
|
||||
/**
|
||||
* Expose `match()`.
|
||||
*/
|
||||
|
||||
module.exports = match;
|
||||
|
||||
/**
|
||||
* Match `el` to `selector`.
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {String} selector
|
||||
* @return {Boolean}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function match(el, selector) {
|
||||
if (vendor) return vendor.call(el, selector);
|
||||
var nodes = el.parentNode.querySelectorAll(selector);
|
||||
for (var i = 0; i < nodes.length; ++i) {
|
||||
if (nodes[i] == el) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},{}],6:[function(require,module,exports){
|
||||
function select(element) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user