Compare commits

..

15 Commits

Author SHA1 Message Date
dependabot[bot]
07a5ca4ffb Bump hosted-git-info from 2.8.8 to 2.8.9
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-09 22:16:32 +00:00
Alan Cell
ea93d4604c Release notes 2021-04-05 20:36:47 +02:00
Alan Cell
caf41de755 Merge tag 'v29.0.0.OS' into develop
v29.0.0.OS
2021-04-05 19:12:59 +02:00
Alan Cell
45d80e9440 Merge branch 'release/v29.0.0.OS' 2021-04-05 19:12:59 +02:00
Alan Cell
bb8f11963a Fix code style 2021-04-05 19:01:58 +02:00
Alan Cell
6581d1424e Merge tag 'v29.0.0.OS' into develop
v29.0.0.OS
2021-04-05 18:53:55 +02:00
Alan Cell
253b298b0d Merge branch 'release/v29.0.0.OS' 2021-04-05 18:53:54 +02:00
Alan Cell
df554680c4 Sync changes v29.0.0 from IceHrmPro (https://icehrm.com/purchase-icehrmpro) 2021-04-05 18:52:23 +02:00
Gamonoid
92032cf1eb Update readme.md 2021-01-12 19:55:00 +01:00
Gamonoid
22cd81611d Add applity 2020-12-20 20:32:30 +01:00
Alan Cell
1a3e468458 Fix displaying wring generated profile image for in staff directory 2020-11-14 19:37:18 +01:00
Alan Cell
88962d4380 Merge branch 'release/v28.2.0.OS' 2020-11-13 02:46:51 +01:00
Alan Cell
3b1285aeaf Merge tag 'v28.2.0.OS' into develop
v28.2.0.OS
2020-11-13 02:46:51 +01:00
Alan Cell
b73e244865 Production build, release notes and version update for v28.2.0 2020-11-13 02:45:48 +01:00
Alan Cell
5f050282f0 Update readme and change delete label for employees 2020-11-13 02:43:40 +01:00
110 changed files with 8968 additions and 1189 deletions

View File

@@ -5,7 +5,7 @@ if(!file_exists('config.php')){
}
include ('config.php');
if(!isset($_REQUEST['g']) || !isset($_REQUEST['n'])){
header("Location:".CLIENT_BASE_URL."login.php");
header("Location:".CLIENT_BASE_URL."login.php");
exit();
}
$group = $_REQUEST['g'];
@@ -14,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();
}

View File

@@ -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'; ?>

View File

@@ -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()),
]

View 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';?>

View 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"
}

View File

@@ -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'];

View File

@@ -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'; ?>

View File

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

View File

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

View File

@@ -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');

View File

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

File diff suppressed because it is too large Load Diff

View 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;
}
}

View 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);
}
}

View 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;
}
}

View 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
View 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;
}
}

View 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);
}
}

File diff suppressed because it is too large Load Diff

View 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;
}
}

View 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);
}
}

View 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();
}
}
}
?>

View 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;
}
}
?>

View 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-dUrville 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"
);
}

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

View File

@@ -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) {?>

View File

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

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -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"));

View File

@@ -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();
}
}
}

View File

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

View File

@@ -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()
{
}
}

View File

@@ -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)
);
}

View File

@@ -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);
}
}

View File

@@ -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());

View File

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

View File

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

View 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;
}
}

View 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;
}
}

View 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;
}
}

View File

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

View File

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

View 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;
}
}

View File

@@ -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'
];
}
}

View File

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

View 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');
}
}

View File

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

View File

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

View File

@@ -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");

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
<?php
namespace Model;
trait CustomFieldTrait
{
public function getObjectName()
{
return $this->objectName;
}
public function isCustomFieldsEnabled()
{
return $this->allowCustomFields;
}
}

View File

@@ -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';
}

View File

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

View File

@@ -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()
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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'));

12
package-lock.json generated
View File

@@ -3824,7 +3824,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": {
@@ -6681,9 +6681,9 @@
}
},
"hosted-git-info": {
"version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
"version": "2.8.9",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
"dev": true
},
"htmlescape": {
@@ -6988,7 +6988,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 +7034,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": {

View File

@@ -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 &gt;= 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

View File

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

View File

@@ -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');

View File

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

View 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;

View 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 };

View File

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

View File

@@ -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: '' }],
];

View File

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

View File

@@ -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' }],
];
}

View File

@@ -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' }],

View File

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

View File

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

View File

@@ -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']],

View File

@@ -61,10 +61,10 @@ class ModuleBase {
}
/**
* Some browsers do not support sending JSON in get parameters. Set this to true to avoid sending JSON
* @method setNoJSONRequests
* @param val {Boolean}
*/
* Some browsers do not support sending JSON in get parameters. Set this to true to avoid sending JSON
* @method setNoJSONRequests
* @param val {Boolean}
*/
setNoJSONRequests(val) {
this.noJSONRequests = val;
}
@@ -79,12 +79,12 @@ class ModuleBase {
}
/**
* Check if the current user has a permission
* @method checkPermission
* @param permission {String}
* @example
* this.checkPermission("Upload/Delete Profile Image")
*/
* Check if the current user has a permission
* @method checkPermission
* @param permission {String}
* @example
* this.checkPermission("Upload/Delete Profile Image")
*/
checkPermission(permission) {
if (this.permissions[permission] === undefined || this.permissions[permission] == null || this.permissions[permission] === 'Yes') {
return 'Yes';
@@ -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;

View 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;

View 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;

View File

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

View 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;

View 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>&nbsp;&nbsp;<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;

View File

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

View 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;

View File

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

View File

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

View File

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

View 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;

View File

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

View File

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

View File

@@ -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');
}

Some files were not shown because too many files have changed in this diff Show More