Compare commits

..

3 Commits

Author SHA1 Message Date
Alan Cell
5df15d39c1 Example usage of IceHrm model classes 2020-11-13 04:47:50 +01:00
Alan Cell
143bcac1f9 Custom tasks extension, basic implementation 2020-11-12 08:06:36 +01:00
Alan Cell
84548c4f63 Ability to add custom modules 2020-11-12 05:51:31 +01:00
110 changed files with 1153 additions and 8932 deletions

View File

@@ -23,7 +23,7 @@ if($group == 'admin' || $group == 'modules'){
$moduleName = $name; $moduleName = $name;
$moduleGroup = 'extensions'; $moduleGroup = 'extensions';
$extensionIndex = APP_BASE_PATH.'/../extensions/'.$name.'/web/index.php'; $extensionIndex = APP_BASE_PATH.'/../extensions/'.$name.'/web/index.php';
include $extensionIndex; include APP_BASE_PATH.'extensions/wrapper.php';
}else{ }else{
exit(); exit();
} }

View File

@@ -4,9 +4,6 @@
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah) Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
*/ */
use Classes\PermissionManager;
use Clients\Common\Model\Client;
$moduleName = 'clients'; $moduleName = 'clients';
$moduleGroup = 'admin'; $moduleGroup = 'admin';
define('MODULE_PATH',dirname(__FILE__)); define('MODULE_PATH',dirname(__FILE__));
@@ -20,32 +17,35 @@ include APP_BASE_PATH . 'modulejslibs.inc.php';
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane active" id="tabPageClient"> <div class="tab-pane active" id="tabPageClient">
<div id="ClientTable" class="reviewBlock" data-content="List" style="padding-left:5px;"></div> <div id="Client" class="reviewBlock" data-content="List" style="padding-left:5px;">
<div id="ClientForm"></div>
<div id="ClientFilterForm"></div> </div>
<div id="ClientForm" class="reviewBlock" data-content="Form" style="padding-left:5px;display:none;">
</div>
</div> </div>
</div> </div>
</div> </div>
<?php
$moduleData = [
'user_level' => $user->user_level,
'permissions' => [
'Client' => PermissionManager::checkGeneralAccess(new Client()),
]
];
?>
<script> <script>
var data = <?= json_encode($moduleData) ?>;
var modJsList = []; var modJsList = [];
modJsList['tabClient'] = new ClientAdapter('Client'); modJsList['tabClient'] = new ClientAdapter('Client','Client');
<?php if(isset($modulePermissions['perm']['Add Clients']) && $modulePermissions['perm']['Add Clients'] == "No"){?>
modJsList['tabClient'].setShowAddNew(false);
<?php }?>
<?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 }?>
modJsList.tabClient.setObjectTypeName('Client');
modJsList.tabClient.setAccess(data.permissions.Client);
modJsList.tabClient.setDataPipe(new IceDataPipe(modJsList.tabClient));
modJsList.tabClient.setRemoteTable(true);
var modJs = modJsList['tabClient']; var modJs = modJsList['tabClient'];
</script> </script>
<?php include APP_BASE_PATH.'footer.php';?> <?php include APP_BASE_PATH.'footer.php';?>

View File

@@ -4,7 +4,6 @@
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah) Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
*/ */
use Classes\BaseService;
use Classes\PermissionManager; use Classes\PermissionManager;
use Company\Common\Model\CompanyStructure; use Company\Common\Model\CompanyStructure;
@@ -61,7 +60,6 @@ path.link {
<?php <?php
$moduleData = [ $moduleData = [
'user_level' => $user->user_level, 'user_level' => $user->user_level,
'customFields' => BaseService::getInstance()->getCustomFields("CompanyStructure"),
'permissions' => [ 'permissions' => [
'CompanyStructure' => PermissionManager::checkGeneralAccess(new CompanyStructure()), 'CompanyStructure' => PermissionManager::checkGeneralAccess(new CompanyStructure()),
] ]

View File

@@ -1,42 +0,0 @@
<?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

@@ -1,12 +0,0 @@
{
"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,25 +13,41 @@ include APP_BASE_PATH.'modulejslibs.inc.php';
<ul class="nav nav-tabs" id="modTab" style="margin-bottom:0px;margin-left:5px;border-bottom: none;"> <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 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-content">
<div class="tab-pane active" id="tabPageEmployeeFieldName"> <div class="tab-pane active" id="tabPageEmployeeFieldName">
<div id="EmployeeFieldName" class="reviewBlock" data-content="List" style="padding-left:5px;"> <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>
</div> </div>
</div> </div>
<script> <script>
var modJsList = []; var modJsList = new Array();
modJsList['tabEmployeeFieldName'] = new FieldNameAdapter('FieldNameMapping','EmployeeFieldName',{"type":"Employee"}); modJsList['tabEmployeeFieldName'] = new FieldNameAdapter('FieldNameMapping','EmployeeFieldName',{"type":"Employee"});
modJsList['tabEmployeeFieldName'].setRemoteTable(true); modJsList['tabEmployeeFieldName'].setRemoteTable(true);
modJsList['tabEmployeeFieldName'].setShowDelete(false); modJsList['tabEmployeeFieldName'].setShowDelete(false);
modJsList['tabEmployeeFieldName'].setShowAddNew(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']; var modJs = modJsList['tabEmployeeFieldName'];

View File

@@ -4,19 +4,13 @@
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah) 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'; $moduleName = 'projects';
$moduleGroup = 'admin'; $moduleGroup = 'admin';
define('MODULE_PATH',dirname(__FILE__)); define('MODULE_PATH',dirname(__FILE__));
include APP_BASE_PATH.'header.php'; include APP_BASE_PATH.'header.php';
include APP_BASE_PATH.'modulejslibs.inc.php'; include APP_BASE_PATH.'modulejslibs.inc.php';
?><div class="span9"> ?><div class="span9">
<ul class="nav nav-tabs" id="modTab" style="margin-bottom:0px;margin-left:5px;border-bottom: none;"> <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 class="active"><a id="tabProject" href="#tabPageProject"><?=t('Projects')?></a></li>
<li><a id="tabEmployeeProject" href="#tabPageEmployeeProject"><?=t('Employee Projects')?></a></li> <li><a id="tabEmployeeProject" href="#tabPageEmployeeProject"><?=t('Employee Projects')?></a></li>
@@ -24,43 +18,55 @@ include APP_BASE_PATH . 'modulejslibs.inc.php';
<div class="tab-content"> <div class="tab-content">
<div class="tab-pane active" id="tabPageProject"> <div class="tab-pane active" id="tabPageProject">
<div id="ProjectTable" class="reviewBlock" data-content="List" style="padding-left:5px;"></div> <div id="Project" class="reviewBlock" data-content="List" style="padding-left:5px;">
<div id="ProjectForm"></div>
<div id="ProjectFilterForm"></div> </div>
<div id="ProjectForm" class="reviewBlock" data-content="Form" style="padding-left:5px;display:none;">
</div>
</div> </div>
<div class="tab-pane" id="tabPageEmployeeProject"> <div class="tab-pane" id="tabPageEmployeeProject">
<div id="EmployeeProjectTable" class="reviewBlock" data-content="List" style="padding-left:5px;"></div> <div id="EmployeeProject" class="reviewBlock" data-content="List" style="padding-left:5px;">
<div id="EmployeeProjectForm"></div>
<div id="EmployeeProjectFilterForm"></div> </div>
<div id="EmployeeProjectForm" class="reviewBlock" data-content="Form" style="padding-left:5px;display:none;">
</div>
</div> </div>
</div> </div>
</div> </div>
<?php
$moduleData = [
'user_level' => $user->user_level,
'permissions' => [
'Project' => PermissionManager::checkGeneralAccess(new Project()),
'EmployeeProject' => PermissionManager::checkGeneralAccess(new EmployeeProject()),
]
];
?>
<script> <script>
var modJsList = []; var modJsList = [];
var data = <?= json_encode($moduleData) ?>;
modJsList['tabProject'] = new ProjectAdapter('Project','Project'); modJsList['tabProject'] = new ProjectAdapter('Project','Project');
modJsList.tabProject.setObjectTypeName('Project');
modJsList.tabProject.setAccess(data.permissions.Project); <?php if(isset($modulePermissions['perm']['Add Projects']) && $modulePermissions['perm']['Add Projects'] == "No"){?>
modJsList.tabProject.setDataPipe(new IceDataPipe(modJsList.tabProject)); modJsList['tabProject'].setShowAddNew(false);
modJsList.tabProject.setRemoteTable(true); <?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);
<?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 }?>
modJsList.tabEmployeeProject.setObjectTypeName('Employee Project');
modJsList.tabEmployeeProject.setAccess(data.permissions.EmployeeProject);
modJsList.tabEmployeeProject.setDataPipe(new IceDataPipe(modJsList.tabEmployeeProject));
modJsList.tabEmployeeProject.setRemoteTable(true);
var modJs = modJsList['tabProject']; var modJs = modJsList['tabProject'];

View File

@@ -19,42 +19,28 @@ $options1 = array();
$options1['setShowAddNew'] = 'false'; $options1['setShowAddNew'] = 'false';
$options1['setRemoteTable'] = 'true'; $options1['setRemoteTable'] = 'true';
$notCloud = !defined('IS_CLOUD') || IS_CLOUD == false;
$moduleBuilder->addModuleOrGroup(new ModuleTab( $moduleBuilder->addModuleOrGroup(new ModuleTab(
'CompanySetting','Setting','Company','SettingAdapter','{"category":"Company"}','name',true,$options1 'CompanySetting','Setting','Company','SettingAdapter','{"category":"Company"}','name',true,$options1
)); ));
$moduleBuilder->addModuleOrGroup(new ModuleTab( $moduleBuilder->addModuleOrGroup(new ModuleTab(
'SystemSetting','Setting','System','SettingAdapter','{"category":"System"}','name',false,$options1 'SystemSetting','Setting','System','SettingAdapter','{"category":"System"}','name',false,$options1
)); ));
if ( $notCloud ) { if (!defined('CLOUD_INSTALLATION')) {
$moduleBuilder->addModuleOrGroup(new ModuleTab( $moduleBuilder->addModuleOrGroup(new ModuleTab(
'EmailSetting', 'Setting', 'Email', 'SettingAdapter', '{"category":"Email"}', 'name', false, $options1 'EmailSetting', 'Setting', 'Email', 'SettingAdapter', '{"category":"Email"}', 'name', false, $options1
)); ));
} }
if(!defined('LEAVE_ENABLED') || LDAP_ENABLED == true) {
$moduleBuilder->addModuleOrGroup(new ModuleTab( $moduleBuilder->addModuleOrGroup(new ModuleTab(
'LeaveSetting', 'Setting', 'Leave', 'SettingAdapter', '{"category":"Leave"}', 'name', false, $options1 'LeaveSetting','Setting','Leave / PTO','SettingAdapter','{"category":"Leave"}','name',false,$options1
)); ));
}
$moduleBuilder->addModuleOrGroup(new ModuleTab(
'AttendanceSetting','Setting','Attendance','SettingAdapter','{"category":"Attendance"}','name',false,$options1
));
if(!defined('LDAP_ENABLED') || LDAP_ENABLED == true){ if(!defined('LDAP_ENABLED') || LDAP_ENABLED == true){
$moduleBuilder->addModuleOrGroup(new ModuleTab( $moduleBuilder->addModuleOrGroup(new ModuleTab(
'LDAPSetting','Setting','LDAP','SettingAdapter','{"category":"LDAP"}','name',false,$options1 'LDAPSetting','Setting','LDAP','SettingAdapter','{"category":"LDAP"}','name',false,$options1
)); ));
} }
if(!defined('SAML_ENABLED') || SAML_ENABLED == true){
$moduleBuilder->addModuleOrGroup(new ModuleTab( $moduleBuilder->addModuleOrGroup(new ModuleTab(
'SAMLSetting','Setting','SAML','SettingAdapter','{"category":"SAML"}','name',false,$options1 'AttendanceSetting','Setting','Attendance','SettingAdapter','{"category":"Attendance"}','name',false,$options1
)); ));
}
$moduleBuilder->addModuleOrGroup(new ModuleTab( $moduleBuilder->addModuleOrGroup(new ModuleTab(
'OtherSetting','Setting','Other','SettingAdapter','{"category":["Projects","Recruitment","Notifications","Expense","Travel","Api","Overtime"]}','name',false,$options1 'OtherSetting','Setting','Other','SettingAdapter','{"category":["Projects","Recruitment","Notifications","Expense","Travel","Api","Overtime"]}','name',false,$options1
)); ));

View File

@@ -31,6 +31,22 @@ $moduleBuilder->addModuleOrGroup(new \Classes\ModuleBuilder\ModuleTab(
$travelRequestOptions $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); echo \Classes\UIManager::getInstance()->renderModule($moduleBuilder);

View File

@@ -13,13 +13,14 @@ if(!defined('HOME_LINK_OTHERS')){
} }
//Version //Version
define('VERSION', '29.0.0.OS'); define('VERSION', '28.1.1.OS');
define('CACHE_VALUE', '29.0.0.OS.2020-04021509'); define('CACHE_VALUE', '28.1.1.OS.2020-11071143');
define('VERSION_NUMBER', '290000'); define('VERSION_NUMBER', '280101');
define('VERSION_DATE', '02/04/2021'); define('VERSION_DATE', '07/11/2020');
if(!defined('CONTACT_EMAIL')){define('CONTACT_EMAIL','icehrm@gamonoid.com');} if(!defined('CONTACT_EMAIL')){define('CONTACT_EMAIL','icehrm@gamonoid.com');}
if(!defined('KEY_PREFIX')){define('KEY_PREFIX','IceHrm');} if(!defined('KEY_PREFIX')){define('KEY_PREFIX','IceHrm');}
if(!defined('APP_SEC')){define('APP_SEC','dbcs234d2saaqw');}
define('UI_SHOW_SWITCH_PROFILE', true); define('UI_SHOW_SWITCH_PROFILE', true);
define('CRON_LOG', ini_get('error_log')); define('CRON_LOG', ini_get('error_log'));
@@ -32,10 +33,7 @@ if(!defined('WK_HTML_PATH')){
} }
define('ALL_CLIENT_BASE_PATH', '/var/www/icehrm.app/icehrmapp/'); define('ALL_CLIENT_BASE_PATH', '/var/www/icehrm.app/icehrmapp/');
define('IS_CLOUD', false);
define('LDAP_ENABLED', true); define('LDAP_ENABLED', true);
define('SAML_ENABLED', true);
define('LEAVE_ENABLED', true);
define('RECRUITMENT_ENABLED', false); define('RECRUITMENT_ENABLED', false);
define('APP_WEB_URL', 'https://icehrm.com'); define('APP_WEB_URL', 'https://icehrm.com');

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,185 +0,0 @@
<?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

@@ -1,33 +0,0 @@
<?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

@@ -1,109 +0,0 @@
<?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

@@ -1,51 +0,0 @@
<?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;
}
}

View File

@@ -1,114 +0,0 @@
<?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

@@ -1,511 +0,0 @@
<?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

@@ -1,800 +0,0 @@
<?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

@@ -1,44 +0,0 @@
<?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

@@ -1,830 +0,0 @@
<?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

@@ -1,44 +0,0 @@
<?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

@@ -1,640 +0,0 @@
<?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

@@ -1,47 +0,0 @@
<?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,14 +1,11 @@
<?php <?php
use Classes\SAMLManager;use Classes\SettingsManager;use Utils\LogManager;define('CLIENT_PATH', dirname(__FILE__)); define('CLIENT_PATH', dirname(__FILE__));
include("config.base.php"); include("config.base.php");
include("include.common.php"); include("include.common.php");
include("server.includes.inc.php"); include("server.includes.inc.php");
$gsuiteEnabled = SettingsManager::getInstance()->getSetting('System: G Suite Enabled'); $companyName = \Classes\SettingsManager::getInstance()->getSetting('Company: Name');
$companyName = SettingsManager::getInstance()->getSetting('Company: Name'); $gsuiteEnabled = \Classes\SettingsManager::getInstance()->getSetting('System: G Suite Enabled');
$SAMLAutoLogin = SettingsManager::getInstance()->getSetting('SAML: Auto Login') === "1";
$SAMLEnabled = SettingsManager::getInstance()->getSetting("SAML: Enabled") == "1";
$SAMLUserLoaded = false;
if (isset($_REQUEST['logout'])) { if (isset($_REQUEST['logout'])) {
\Utils\SessionUtils::unsetClientSession(); \Utils\SessionUtils::unsetClientSession();
@@ -17,19 +14,12 @@ if (isset($_REQUEST['logout'])) {
if (empty($user) || empty($user->email)) { if (empty($user) || empty($user->email)) {
if (!isset($_REQUEST['logout']) && !isset($_POST['SAMLResponse']) && $SAMLAutoLogin && $SAMLEnabled && !empty(SettingsManager::getInstance()->getSetting("SAML: IDP SSO Url"))) { if (!empty($_REQUEST['username']) && !empty($_REQUEST['password'])) {
header("Location:" . SettingsManager::getInstance()->getSetting("SAML: IDP SSO Url"));
exit();
}
if ((!empty($_REQUEST['username']) && !empty($_REQUEST['password']))
|| isset($_POST['SAMLResponse'])
) {
$suser = null; $suser = null;
$ssoUserLoaded = false; $ssoUserLoaded = false;
if($_REQUEST['username'] != "admin") { if($_REQUEST['username'] != "admin") {
if (SettingsManager::getInstance()->getSetting("LDAP: Enabled") === "1") { if (\Classes\SettingsManager::getInstance()->getSetting("LDAP: Enabled") == "1") {
$ldapResp = \Classes\LDAPManager::getInstance()->checkLDAPLogin($_REQUEST['username'], $_REQUEST['password']); $ldapResp = \Classes\LDAPManager::getInstance()->checkLDAPLogin($_REQUEST['username'], $_REQUEST['password']);
if ($ldapResp->getStatus() == \Classes\IceResponse::ERROR) { if ($ldapResp->getStatus() == \Classes\IceResponse::ERROR) {
header("Location:" . CLIENT_BASE_URL . "login.php?f=1"); header("Location:" . CLIENT_BASE_URL . "login.php?f=1");
@@ -46,39 +36,6 @@ 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)) { if (empty($suser)) {
$suser = new \Users\Common\Model\User(); $suser = new \Users\Common\Model\User();
$suser->Load( $suser->Load(
@@ -102,7 +59,7 @@ if (empty($user) || empty($user->email)) {
$loginCsrf = \Utils\SessionUtils::getSessionObject('csrf-login'); $loginCsrf = \Utils\SessionUtils::getSessionObject('csrf-login');
if (!$SAMLUserLoaded && ($_REQUEST['csrf'] != $loginCsrf || empty($_REQUEST['csrf']))) { if ($_REQUEST['csrf'] != $loginCsrf || empty($_REQUEST['csrf'])) {
$next = !empty($_REQUEST['next'])?'&next='.$_REQUEST['next']:''; $next = !empty($_REQUEST['next'])?'&next='.$_REQUEST['next']:'';
header("Location:".CLIENT_BASE_URL."login.php?f=1".$next); header("Location:".CLIENT_BASE_URL."login.php?f=1".$next);
exit(); exit();
@@ -320,7 +277,7 @@ $csrfToken = sha1(rand(4500, 100000) . time(). CLIENT_BASE_URL);
<div class="col-lg-6 col-md-8 col-xs-10"> <div class="col-lg-6 col-md-8 col-xs-10">
<div class="bg-white-2 h-100 px-11 pt-11 pb-7"> <div class="bg-white-2 h-100 px-11 pt-11 pb-7">
<div class="row d-flex justify-content-center"> <div class="row d-flex justify-content-center">
<img style="max-width: 100%;" src="<?=$logoFileUrl?>"/> <img src="<?=$logoFileUrl?>" style="max-width:100%;max-height:280px;"/>
</div> </div>
<hr/> <hr/>
<?php if ($gsuiteEnabled) {?> <?php if ($gsuiteEnabled) {?>

View File

@@ -1,9 +1,5 @@
<?php <?php
$migrationList = []; $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_280002_update_gender';
$migrationList[] = 'v20201028_280001_update_module_names'; $migrationList[] = 'v20201028_280001_update_module_names';
$migrationList[] = 'v20201017_271101_switch_off_photo_att'; $migrationList[] = 'v20201017_271101_switch_off_photo_att';

View File

@@ -1,24 +0,0 @@
<?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

@@ -1,24 +0,0 @@
<?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

@@ -1,85 +0,0 @@
<?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

@@ -1,32 +0,0 @@
<?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,8 +67,6 @@ modJsList['tabEmployeeDocument'].setShowDelete(false);
modJsList['tabEmployeeDocument'].setShowEdit(false); modJsList['tabEmployeeDocument'].setShowEdit(false);
<?php }?> <?php }?>
modJsList['tabEmployeeDocument'].setRemoteTable(true);
modJsList['tabEmployeeCompanyDocument'] = new EmployeeCompanyDocumentAdapter('CompanyDocument','EmployeeCompanyDocument'); modJsList['tabEmployeeCompanyDocument'] = new EmployeeCompanyDocumentAdapter('CompanyDocument','EmployeeCompanyDocument');
modJsList['tabEmployeeCompanyDocument'].setLoadMoreButton($("#loadMoreEmployeeCompanyDocument")); modJsList['tabEmployeeCompanyDocument'].setLoadMoreButton($("#loadMoreEmployeeCompanyDocument"));

View File

@@ -7,7 +7,6 @@ use Classes\Migration\MigrationManager;
use Classes\NotificationManager; use Classes\NotificationManager;
use Classes\ReportHandler; use Classes\ReportHandler;
use Classes\SettingsManager; use Classes\SettingsManager;
use Model\BaseModel;
use Utils\LogManager; use Utils\LogManager;
if (!defined("AWS_REGION")) { if (!defined("AWS_REGION")) {
@@ -80,18 +79,6 @@ 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"); $noJSONRequests = SettingsManager::getInstance()->getSetting("System: Do not pass JSON in request");
$debugMode = SettingsManager::getInstance()->getSetting("System: Debug Mode"); $debugMode = SettingsManager::getInstance()->getSetting("System: Debug Mode");
@@ -136,12 +123,10 @@ if (defined('CLIENT_PATH')) {
$modelClassList = $moduleManagerObj->getModelClasses(); $modelClassList = $moduleManagerObj->getModelClasses();
$metaData = $moduleManagerObj->getModuleObject(); $metaData = $moduleManagerObj->getModuleObject();
/** @var BaseModel $modelClass */
foreach ($modelClassList as $modelClass) { foreach ($modelClassList as $modelClass) {
$modelClassWithNameSpace = $metaData['model_namespace']."\\".$modelClass; $modelClassWithNameSpace = $metaData['model_namespace']."\\".$modelClass;
$modelClassWithNameSpace::SetDatabaseAdapter($dbLocal); $modelClassWithNameSpace::SetDatabaseAdapter($dbLocal);
$baseService->addModelClass($modelClass, $modelClassWithNameSpace); $baseService->addModelClass($modelClass, $modelClassWithNameSpace);
$modelClassObject = new $modelClassWithNameSpace();
} }
} }
} }

View File

@@ -33,7 +33,8 @@ class AttendanceActionManager extends SubActionManager
//Find any open punch //Find any open punch
$attendance = new Attendance(); $attendance = new Attendance();
$attendance->Load( $attendance->Load(
"employee = ? and DATE_FORMAT( in_time, '%Y-%m-%d' ) = ? and out_time is NULL", "employee = ? and DATE_FORMAT( in_time, '%Y-%m-%d' ) = ? and (out_time is NULL
or out_time = '0000-00-00 00:00:00')",
array($employee->id,$date) array($employee->id,$date)
); );

View File

@@ -7,7 +7,6 @@
*/ */
namespace Classes; namespace Classes;
use Model\BaseModel;
use Utils\LogManager; use Utils\LogManager;
abstract class AbstractModuleManager abstract class AbstractModuleManager
@@ -255,17 +254,7 @@ abstract class AbstractModuleManager
protected function addModelClass($className) protected function addModelClass($className)
{ {
$this->modelClasses[] = $className; $this->modelClasses[] = $className;
$classWithNamespace = $this->moduleObject['model_namespace']."\\".$className; BaseService::getInstance()->addModelClass($className, $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) protected function addHistoryGeneric($type, $table, $refName, $refId, $field, $oldValue, $newValue)
@@ -290,11 +279,11 @@ abstract class AbstractModuleManager
BaseService::getInstance()->addCalculationHook($code, $name, $class, $method); BaseService::getInstance()->addCalculationHook($code, $name, $class, $method);
} }
public function install() public function install() {
{
} }
public function uninstall() public function uninstall() {
{
} }
} }

View File

@@ -19,7 +19,6 @@ use Employees\Common\Model\EmployeeApproval;
use FieldNames\Common\Model\CustomField; use FieldNames\Common\Model\CustomField;
use FieldNames\Common\Model\FieldNameMapping; use FieldNames\Common\Model\FieldNameMapping;
use Metadata\Common\Model\CalculationHook; use Metadata\Common\Model\CalculationHook;
use Model\BaseModel;
use Model\DataEntryBackup; use Model\DataEntryBackup;
use Model\Setting; use Model\Setting;
use Modules\Common\Model\Module; use Modules\Common\Model\Module;
@@ -52,8 +51,7 @@ class BaseService
public $calculationHooks = array(); public $calculationHooks = array();
public $customFieldManager = null; public $customFieldManager = null;
public $migrationManager = null; public $migrationManager = null;
public $modelClassMap = []; public $modelClassMap = array();
public $customFieldsClassMap = [];
public $currentProfileId = false; public $currentProfileId = false;
protected $cacheService = null; protected $cacheService = null;
@@ -169,20 +167,6 @@ class BaseService
$this->modelClassMap[$modelClass] = $fullQualifiedName; $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) public function getModelClassName($name)
{ {
return $this->getFullQualifiedModelClassName($name); return $this->getFullQualifiedModelClassName($name);
@@ -653,10 +637,7 @@ class BaseService
$processedList = array(); $processedList = array();
foreach ($list as $obj) { foreach ($list as $obj) {
$processedObj = $this->cleanUpAdoDB($obj->postProcessGetData($obj)); $processedList[] = $this->cleanUpAdoDB($obj->postProcessGetData($obj));
if (null !== $processedObj) {
$processedList[] = $processedObj;
}
} }
$list = $processedList; $list = $processedList;
@@ -768,7 +749,7 @@ class BaseService
$obj = $this->enrichObjectCustomFields($table, $obj); $obj = $this->enrichObjectCustomFields($table, $obj);
$obj = $obj->postProcessGetElement($obj); $obj = $obj->postProcessGetElement($obj);
return $this->cleanUpAdoDB($obj); return $this->cleanUpAdoDB($obj->postProcessGetData($obj));
} }
return null; return null;
} }
@@ -947,7 +928,6 @@ class BaseService
{ {
$fileFields = $this->fileFields; $fileFields = $this->fileFields;
$nsTable = $this->getFullQualifiedModelClassName($table); $nsTable = $this->getFullQualifiedModelClassName($table);
/** @var BaseModel $ele */
$ele = new $nsTable(); $ele = new $nsTable();
$ele->Load('id = ?', array($id)); $ele->Load('id = ?', array($id));
@@ -1005,7 +985,7 @@ class BaseService
$dataEntryBackup->data = json_encode($newObj); $dataEntryBackup->data = json_encode($newObj);
$dataEntryBackup->Save(); $dataEntryBackup->Save();
} }
$ele->executePostDeleteActions($ele);
$this->audit(IceConstants::AUDIT_DELETE, "Deleted an object in ".$table." [id:".$ele->id."]"); $this->audit(IceConstants::AUDIT_DELETE, "Deleted an object in ".$table." [id:".$ele->id."]");
} }
@@ -1018,7 +998,6 @@ class BaseService
} }
$cfs = $this->customFieldManager->getCustomFields($table, $id); $cfs = $this->customFieldManager->getCustomFields($table, $id);
/** @var CustomField $cf */
foreach ($cfs as $cf) { foreach ($cfs as $cf) {
$cf->Delete(); $cf->Delete();
} }

View File

@@ -90,14 +90,6 @@ class DomainAwareInputCleaner
private function isValidFilterValue($input) 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); return !!preg_match('/^[-_: \d\p{L}]+$/u', $input);
} }
} }

View File

@@ -7,8 +7,7 @@ use Utils\LogManager;
class ExtensionManager class ExtensionManager
{ {
const GROUP = 'extension'; const GROUP = 'extension';
protected function processExtensionInDB() protected function processExtensionInDB() {
{
$dbModule = new \Modules\Common\Model\Module(); $dbModule = new \Modules\Common\Model\Module();
$extensions = $dbModule->Find("mod_group = ?", array(self::GROUP)); $extensions = $dbModule->Find("mod_group = ?", array(self::GROUP));
@@ -21,8 +20,7 @@ class ExtensionManager
return $extensionsInDB; return $extensionsInDB;
} }
public function getExtensionsPath() public function getExtensionsPath() {
{
return APP_BASE_PATH.'../extensions/'; return APP_BASE_PATH.'../extensions/';
} }
@@ -31,8 +29,7 @@ class ExtensionManager
return json_decode(file_get_contents($this->getExtensionsPath().$extensionName.'/meta.json')); return json_decode(file_get_contents($this->getExtensionsPath().$extensionName.'/meta.json'));
} }
public function setupExtensions() public function setupExtensions() {
{
$menu = []; $menu = [];
$extensions = []; $extensions = [];
$extensionDirs = scandir($this->getExtensionsPath()); $extensionDirs = scandir($this->getExtensionsPath());

View File

@@ -3,7 +3,6 @@
namespace Classes; namespace Classes;
use Firebase\JWT\JWT; use Firebase\JWT\JWT;
use Firebase\JWT\SignatureInvalidException;
class JwtTokenService class JwtTokenService
{ {
@@ -23,11 +22,7 @@ class JwtTokenService
public function getBaseToken($jwtToken) public function getBaseToken($jwtToken)
{ {
$secret = APP_SEC.APP_PASSWORD; $secret = APP_SEC.APP_PASSWORD;
try {
$jwt = JWT::decode($jwtToken, $secret, array('HS256')); $jwt = JWT::decode($jwtToken, $secret, array('HS256'));
} catch (SignatureInvalidException $e) {
return null;
}
if (time() > intval($jwt->expire)) { if (time() > intval($jwt->expire)) {
return null; return null;

View File

@@ -15,8 +15,6 @@ class MemcacheService
public static $openConnections = array(); public static $openConnections = array();
private static $me = null; private static $me = null;
protected $inMemoryStore = [];
private function __construct() private function __construct()
{ {
} }
@@ -58,15 +56,6 @@ class MemcacheService
} }
public function set($key, $value, $expiry = 3600) 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')) { if (!class_exists('\\Memcached')) {
return false; return false;
@@ -85,19 +74,6 @@ class MemcacheService
} }
public function get($key) 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')) { if (!class_exists('\\Memcached')) {
return false; return false;

View File

@@ -1,98 +0,0 @@
<?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

@@ -1,98 +0,0 @@
<?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

@@ -1,53 +0,0 @@
<?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); return new IceResponse(IceResponse::ERROR, $error);
} }
if (strlen($password) > 30) { if (strlen($password) > 20) {
$error = "Password too long"; $error = "Password too long";
return new IceResponse(IceResponse::ERROR, $error); return new IceResponse(IceResponse::ERROR, $error);

View File

@@ -424,8 +424,10 @@ class RestEndPoint
$token = $_GET['token']; $token = $_GET['token'];
} }
if (strlen($token) > 32) {
$tokenService = new JwtTokenService(); $tokenService = new JwtTokenService();
$token = $tokenService->getBaseToken($token); $token = $tokenService->getBaseToken($token);
}
return $token; return $token;
} }

View File

@@ -1,100 +0,0 @@
<?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,22 +1,15 @@
<?php <?php
namespace Classes; namespace Classes;
use Classes\Crypt\AesCtr;
use Model\Setting; use Model\Setting;
class SettingsManager class SettingsManager
{ {
const ENCRYPTED_PREFIX = 'iceenc_';
private static $me = null; private static $me = null;
private $encryptedSettings = [];
private function __construct() private function __construct()
{ {
$this->addEncryptedSetting('SAML: X.509 Certificate');
$this->addEncryptedSetting('LDAP: Manager Password');
} }
public static function getInstance() public static function getInstance()
@@ -28,67 +21,9 @@ class SettingsManager
return self::$me; 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) public function getSetting($name)
{ {
if (class_exists("\\Classes\\ProVersion")) { if (class_exists("\\Classes\\ProVersion")) {
$pro = new ProVersion(); $pro = new ProVersion();
$val = $pro->getSetting($name); $val = $pro->getSetting($name);
@@ -99,51 +34,35 @@ class SettingsManager
$setting = new Setting(); $setting = new Setting();
$setting->Load("name = ?", array($name)); $setting->Load("name = ?", array($name));
$value = null;
if ($setting->name == $name) { if ($setting->name == $name) {
$value = $setting->value; return $setting->value;
} }
if (null === $value) {
return null; return null;
} }
return $this->decryptSetting($name, $value);
}
public function setSetting($name, $value) public function setSetting($name, $value)
{ {
$setting = new Setting(); $setting = new Setting();
$setting->Load("name = ?", array($name)); $setting->Load("name = ?", array($name));
if ($setting->name !== $name) { if ($setting->name == $name) {
return; $setting->value = $value;
}
$setting->value = $this->encryptSetting($name, $value);
$setting->Save(); $setting->Save();
} }
}
public function addSetting($name, $value) public function addSetting($name, $value)
{ {
$setting = new Setting(); $setting = new Setting();
$setting->Load("name = ?", array($name)); $setting->Load("name = ?", array($name));
if ($setting->name == $name) { if ($setting->name == $name) {
$setting->value = $this->encryptSetting($name, $value); $setting->value = $value;
$setting->Save(); $setting->Save();
} else { } else {
$setting->name = $name; $setting->name = $name;
$setting->value = $this->encryptSetting($name, $value); $setting->value = $value;
$setting->description = ''; $setting->description = $value;
$setting->meta = ''; $setting->meta = '';
$setting->Save(); $setting->Save();
} }
} }
public function getDeprecatedSettings()
{
return [
'Attendance: Work Week Start Day',
'Attendance: Overtime Calculation Class'
];
}
} }

View File

@@ -5,14 +5,9 @@ use Classes\IceResponse;
use Classes\ModuleAccess; use Classes\ModuleAccess;
use Employees\Common\Model\Employee; use Employees\Common\Model\Employee;
use Model\BaseModel; use Model\BaseModel;
use Model\CustomFieldTrait;
class CompanyStructure extends BaseModel class CompanyStructure extends BaseModel
{ {
use CustomFieldTrait;
public $objectName = 'Company Structures';
protected $allowCustomFields = true;
public $table = 'CompanyStructures'; public $table = 'CompanyStructures';
public function getAdminAccess() public function getAdminAccess()

View File

@@ -1,25 +0,0 @@
<?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,12 +45,10 @@ class DocumentTaskCreator implements TaskCreator
return 0; return 0;
} }
$query $query = "select count(id) as c from EmployeeDocuments where employee = ? and valid_until < ?";
= "select count(id) as c from EmployeeDocuments where employee = ? and valid_until < ? and visible_to = ?";
$user->DB()->SetFetchMode(ADODB_FETCH_ASSOC); $user->DB()->SetFetchMode(ADODB_FETCH_ASSOC);
// TODO - sending notifications only for Owner documents, this need to be extended later $rs = $user->DB()->Execute($query, [$employee->id, date('Y-m-d')]);
$rs = $user->DB()->Execute($query, [$employee->id, date('Y-m-d'), 'Owner']);
$count = $rs->fields['c']; $count = $rs->fields['c'];
return $count; return $count;

View File

@@ -1,7 +1,6 @@
<?php <?php
namespace Documents\Common\Model; namespace Documents\Common\Model;
use Classes\BaseService;
use Classes\ModuleAccess; use Classes\ModuleAccess;
use Model\BaseModel; use Model\BaseModel;
@@ -27,23 +26,4 @@ class Document extends BaseModel
new ModuleAccess('documents', 'user'), 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,65 +8,14 @@
namespace Documents\Common\Model; namespace Documents\Common\Model;
use Classes\BaseService;
use Classes\IceResponse; use Classes\IceResponse;
use Classes\ModuleAccess; use Classes\ModuleAccess;
use Employees\Common\Model\Employee;
use Model\BaseModel; use Model\BaseModel;
class EmployeeDocument extends BaseModel class EmployeeDocument extends BaseModel
{ {
public $table = 'EmployeeDocuments'; 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() public function getAdminAccess()
{ {
return array("get","element","save","delete"); return array("get","element","save","delete");
@@ -77,8 +26,6 @@ class EmployeeDocument extends BaseModel
return array("get","element","save","delete"); return array("get","element","save","delete");
} }
public function getUserAccess() public function getUserAccess()
{ {
return array("get"); return array("get");

View File

@@ -8,7 +8,6 @@ use Classes\ModuleAccess;
use Company\Common\Model\CompanyStructure; use Company\Common\Model\CompanyStructure;
use Metadata\Common\Model\Country; use Metadata\Common\Model\Country;
use Model\BaseModel; use Model\BaseModel;
use Model\CustomFieldTrait;
class Employee extends BaseModel class Employee extends BaseModel
{ {
@@ -91,11 +90,6 @@ class Employee extends BaseModel
$this->oldObj = BaseService::getInstance()->getElement('Employee', $obj->id, $mapping, true); $this->oldObj = BaseService::getInstance()->getElement('Employee', $obj->id, $mapping, true);
} }
public function isCustomFieldsEnabled()
{
return true;
}
private function saveHistory($obj) private function saveHistory($obj)
{ {

View File

@@ -12,7 +12,6 @@ use Classes\BaseService;
use Classes\IceResponse; use Classes\IceResponse;
use Classes\ModuleAccess; use Classes\ModuleAccess;
use Model\BaseModel; use Model\BaseModel;
use Utils\LogManager;
class CustomField extends BaseModel class CustomField extends BaseModel
{ {
@@ -59,22 +58,6 @@ class CustomField extends BaseModel
return new IceResponse(IceResponse::SUCCESS, ""); 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() public function getModuleAccess()
{ {
return [ return [

View File

@@ -11,8 +11,6 @@ use Utils\LogManager;
class BaseModel extends \ADOdb_Active_Record class BaseModel extends \ADOdb_Active_Record
{ {
public $objectName = null;
protected $allowCustomFields = false;
public $keysToIgnore = array( public $keysToIgnore = array(
"_table", "_table",
@@ -197,16 +195,6 @@ 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) public function postProcessGetData($obj)
{ {
return $obj; return $obj;
@@ -352,14 +340,4 @@ class BaseModel extends \ADOdb_Active_Record
return $ok; return $ok;
} }
// @codingStandardsIgnoreEnd // @codingStandardsIgnoreEnd
public function getObjectName()
{
return null;
}
public function isCustomFieldsEnabled()
{
return false;
}
} }

View File

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

View File

@@ -12,7 +12,6 @@ use Classes\BaseService;
use Classes\IceResponse; use Classes\IceResponse;
use Classes\ModuleAccess; use Classes\ModuleAccess;
use Classes\RestApiManager; use Classes\RestApiManager;
use Classes\SettingsManager;
use Users\Common\Model\User; use Users\Common\Model\User;
class Setting extends BaseModel class Setting extends BaseModel
@@ -75,12 +74,6 @@ class Setting extends BaseModel
return new IceResponse(IceResponse::SUCCESS, ""); 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) public function executePreUpdateActions($obj)
{ {
return $this->executePreSaveActions($obj); return $this->executePreSaveActions($obj);
@@ -94,17 +87,5 @@ 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'; public $table = 'Settings';
} }

View File

@@ -2,7 +2,6 @@
namespace Reports\Admin\Reports; namespace Reports\Admin\Reports;
use Attendance\Common\Model\Attendance; use Attendance\Common\Model\Attendance;
use Company\Common\Model\CompanyStructure;
use Employees\Common\Model\Employee; use Employees\Common\Model\Employee;
use Reports\Admin\Api\ClassBasedReportBuilder; use Reports\Admin\Api\ClassBasedReportBuilder;
use Reports\Admin\Api\ReportBuilderInterface; use Reports\Admin\Api\ReportBuilderInterface;
@@ -17,17 +16,6 @@ class EmployeeTimeTrackReport extends ClassBasedReportBuilder implements ReportB
LogManager::getInstance()->info(json_encode($report)); LogManager::getInstance()->info(json_encode($report));
LogManager::getInstance()->info(json_encode($req)); 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(); $employeeTimeEntry = new EmployeeTimeEntry();
$timeEntryList = $employeeTimeEntry->Find( $timeEntryList = $employeeTimeEntry->Find(
@@ -50,8 +38,6 @@ class EmployeeTimeTrackReport extends ClassBasedReportBuilder implements ReportB
//$minutes = (int)($seconds/60); //$minutes = (int)($seconds/60);
//Find Attendance Entries //Find Attendance Entries
$req = $this->setRequestDatesBasedOnThePeriod($req);
$attendance = new Attendance(); $attendance = new Attendance();
$atteandanceList = $attendance->Find( $atteandanceList = $attendance->Find(
"employee = ? and date(in_time) >= ? and date(out_time) <= ? and in_time < out_time", "employee = ? and date(in_time) >= ? and date(out_time) <= ? and in_time < out_time",
@@ -80,44 +66,21 @@ class EmployeeTimeTrackReport extends ClassBasedReportBuilder implements ReportB
$employeeObject = new Employee(); $employeeObject = new Employee();
$employeeObject->Load("id = ?", array($req['employee'])); $employeeObject->Load("id = ?", array($req['employee']));
$company = new CompanyStructure();
$company->Load('id = ?', [$employeeObject->department]);
$reportData = []; $reportData = array();
$reportData[] = [ //$reportData[] = array($employeeObject->first_name." ".$employeeObject->last_name,"","","","");
"Date", $reportData[] = array("Date","First Punch-In Time","Last Punch-Out Time","Time in Office","Time in Timesheets");
"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 //Iterate date range
$interval = \DateInterval::createFromDateString('1 day'); $interval = \DateInterval::createFromDateString('1 day');
$period = new \DatePeriod( $period = new \DatePeriod(new \DateTime($req['date_start']), $interval, new \DateTime($req['date_end']));
new \DateTime($req['date_start']),
$interval,
(new \DateTime($req['date_end']))->modify('+1 day')
);
$totalHoursOffice = 0;
$totalHoursTimeSheets = 0;
$totalDaysForThePeriod = 0;
foreach ($period as $dt) { foreach ($period as $dt) {
$dataRow = array(); $dataRow = array();
$key = $dt->format("Y-m-d"); $key = $dt->format("Y-m-d");
if (!isset($firstTimeInArray[$key])) {
continue;
}
$totalDaysForThePeriod++;
$dataRow[] = $key; $dataRow[] = $key;
if (isset($firstTimeInArray[$key])) { if (isset($firstTimeInArray[$key])) {
@@ -144,45 +107,8 @@ class EmployeeTimeTrackReport extends ClassBasedReportBuilder implements ReportB
$dataRow[] = 0; $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[] = $dataRow;
} }
$reportData[3][1] = $totalDaysForThePeriod;
$totalHoursOffice = number_format($totalHoursOffice, 2, '.', '');
$totalHoursTimeSheets = number_format($totalHoursTimeSheets, 2, '.', '');
$reportData[] = ["Total","","",$totalHoursOffice,$totalHoursTimeSheets];
return $reportData; 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,9 +24,10 @@ class StaffDirectory extends Employee
foreach ($res as $entry) { foreach ($res as $entry) {
$emp = new BaseModel(); $emp = new BaseModel();
$emp->id = $entry->id; $emp->id = $entry->id;
$emp = FileService::getInstance()->updateProfileImage($emp);
//$emp->image = str_replace("_img_",$emp->image,$img);
$emp->first_name = $entry->first_name; $emp->first_name = $entry->first_name;
$emp->last_name = $entry->last_name; $emp->last_name = $entry->last_name;
$emp = FileService::getInstance()->updateSmallProfileImage($emp);
$emp->job_title = $entry->job_title; $emp->job_title = $entry->job_title;
$emp->department = $entry->department; $emp->department = $entry->department;
$emp->work_phone = $entry->work_phone; $emp->work_phone = $entry->work_phone;
@@ -40,11 +41,6 @@ class StaffDirectory extends Employee
return $data; return $data;
} }
public function isCustomFieldsEnabled()
{
return false;
}
// @codingStandardsIgnoreStart // @codingStandardsIgnoreStart
public function Insert() public function Insert()
{ {

View File

@@ -11,15 +11,10 @@ namespace Travel\Common\Model;
use Classes\ModuleAccess; use Classes\ModuleAccess;
use Classes\SettingsManager; use Classes\SettingsManager;
use Model\ApproveModel; use Model\ApproveModel;
use Model\CustomFieldTrait;
class EmployeeTravelRecord extends ApproveModel class EmployeeTravelRecord extends ApproveModel
{ {
use CustomFieldTrait;
public $table = 'EmployeeTravelRecords'; public $table = 'EmployeeTravelRecords';
public $objectName = 'Travel Request';
protected $allowCustomFields = true;
public $notificationModuleName = "Travel Management"; public $notificationModuleName = "Travel Management";
public $notificationUnitName = "TravelRequest"; public $notificationUnitName = "TravelRequest";

View File

@@ -10,7 +10,6 @@ namespace Travel\Common\Model;
class EmployeeTravelRecordApproval extends EmployeeTravelRecord class EmployeeTravelRecordApproval extends EmployeeTravelRecord
{ {
protected $allowCustomFields = false;
// @codingStandardsIgnoreStart // @codingStandardsIgnoreStart
public function Find($whereOrderBy, $bindarr = false, $cache = false, $pkeysArr = false, $extra = array()) public function Find($whereOrderBy, $bindarr = false, $cache = false, $pkeysArr = false, $extra = array())

1
extensions/gitkeep Executable file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -243,7 +243,6 @@ gulp.task('admin-js', (done) => {
const files = [ const files = [
'attendance', 'attendance',
'company_structure', 'company_structure',
'custom_fields',
'clients', 'clients',
'charts', 'charts',
'dashboard', 'dashboard',
@@ -360,6 +359,48 @@ gulp.task('modules-js', (done) => {
.pipe(gulp.dest('./web/dist')); .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.task('watch', () => {
gulp.watch('web/admin/src/*/*.js', gulp.series('admin-js')); gulp.watch('web/admin/src/*/*.js', gulp.series('admin-js'));

12
package-lock.json generated
View File

@@ -3824,7 +3824,7 @@
}, },
"doctrine": { "doctrine": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", "resolved": "http://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
"integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -6681,9 +6681,9 @@
} }
}, },
"hosted-git-info": { "hosted-git-info": {
"version": "2.8.9", "version": "2.8.8",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
"dev": true "dev": true
}, },
"htmlescape": { "htmlescape": {
@@ -6988,7 +6988,7 @@
}, },
"is-accessor-descriptor": { "is-accessor-descriptor": {
"version": "0.1.6", "version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "resolved": "http://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
"integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
"dev": true, "dev": true,
"requires": { "requires": {
@@ -7034,7 +7034,7 @@
}, },
"is-data-descriptor": { "is-data-descriptor": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "resolved": "http://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
"integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
"dev": true, "dev": true,
"requires": { "requires": {

View File

@@ -1,41 +1,3 @@
# 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 ## Release note v28.1.0.OS
### 🧲 New features ### 🧲 New features
@@ -54,97 +16,81 @@
* New REST endpoints for employee qualifications * New REST endpoints for employee qualifications
### 🐛 Bug fixes ### 🐛 Bug fixes
* Fixed, issue with managers being able to create performance reviews for employees who are not their direct reports * 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 * Fixed, issues related to using full profile image instead of using smaller version of profile image
* Changing third gender to other * Changing third gender to other
* Improvements and fixes for internal frontend data caching * 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 ## Release note v27.0.0.OS
### New features ### New features
* Employee document management is now available for open-source version * 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 * Payroll module improvements
* Security improvements to password policy * Security improvements to password policy
* Albanian language is now available * Albanian language is now available
* Ability to deploy using docker * Ability to deploy using docker
### For developers ### 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) * [Developer guide](https://icehrm.gitbook.io/icehrm/developer-guide/create-new-module)
* Fully supports all php versions &gt;= 5.6 upto v7.3 \(php 5.6 support is deprecated and not recommended\) * Fully supports all php versions >= 5.6 upto v7.3 (php 5.6 support is deprecated and not recommended)
### Bug fixes ### 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 travel request approval for managers
* Fixed the issue with attendance source IP display * Fixed the issue with attendance source IP display
* Fixing Api issues in PHP 7.3 * Fixing Api issues in PHP 7.3
## Release note v26.6.0.OS
Release note v26.6.0.OS
------------------------
### Features ### Features
* Some Improvements to UI such as updating Icons and upgrading font-awesome to its latest version * 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 * 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 * Ability to control location tracking via mobile using server side settings
* Improvements to translations * Improvements to translations
* Compatible with location tracking with icehrm mobile app
### Mobile App ### 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 ### Fixes
* Order projects by name on Timesheet project listing (This is to make it easier to edit timesheets with many projects)
* 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 * Link home page user profile to employee profile update page
* Fix issues related to configuring Api with mobile app * Fix issues related to configuring Api with mobile app
### Security Improvements ### Security Improvements
* Upgrade npm missing dependencies * Upgrade npm missing dependencies
## Release note v26.2.0.OS
Release note v26.2.0.OS
------------------------
### Features ### Features
* Add staff directory module * Add staff directory module
* Update client-side js to ES6 * Update client-side js to ES6
* Compatible with IceHrm Mobile App * Compatible with IceHrm Mobile App
* Use npm libraries when possible * Use npm libraries when possible
* Add gulp build for frontend assets * 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)\) * Allow generating QR code with rest api key (https://github.com/gamonoid/icehrm/issues/169)
* Updated readme for development setup with vagrant * Updated readme for development setup with vagrant
### Fixes ### Fixes
* Add missing employee details report * 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: 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: 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: 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](https://github.com/gamonoid/icehrm/issues/171)\) * 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](https://github.com/gamonoid/icehrm/pull/166)\) by [https://github.com/nightwatch75](https://github.com/nightwatch75) * Updates to Italian translations (https://github.com/gamonoid/icehrm/pull/166) by https://github.com/nightwatch75
## Release note v24.0.0.OS
Release note v24.0.0.OS
------------------------
### Features ### Features
* Allow passing additional parameters to payroll predefined methods * Allow passing additional parameters to payroll predefined methods
* Pass leave type name in function field to get leave count for a given type * Pass leave type name in function field to get leave count for a given type
* Add employee name to payroll report * Add employee name to payroll report
@@ -153,35 +99,31 @@ This fixes some major issues found in v27.0.1.OS
* Add filter by status feature to subordinate time sheets * Add filter by status feature to subordinate time sheets
### Security Fixes ### Security Fixes
* Fix missing login form CSRF token * Fix missing login form CSRF token
* Fix risky usage of hashed password in request * Fix risky usage of hashed password in request
* Fixing permission issues on module access for each user level * Fixing permission issues on module access for each user level
* Prevent manager from accessing sensitive user records * Prevent manager from accessing sensitive user records
### Other Fixes ### Other Fixes
* Hide employee salary from managers * Hide employee salary from managers
* Prevent manager from accessing audit, cron and notifications * Prevent manager from accessing audit, cron and notifications
* Prevent managers from deleting employees * Prevent managers from deleting employees
* Validate overtime start and end times * Validate overtime start and end times
* Fix issue: employee can download draft payroll * Fix issue: employee can download draft payroll
## Release note v23.0.1.OS Release note v23.0.1.OS
------------------------
This release include some very critical security fixes. We recommend upgrading your installation to latest release. This release include some very critical security fixes. We recommend upgrading your installation to latest release.
### Fixes ### Fixes
* Fix missing login form CSRF token * Fix missing login form CSRF token
* Fix risky usage of hashed password in request * Fix risky usage of hashed password in request
## Release note v23.0.0.OS Release note v23.0.0.OS
------------------------
### Features ### Features
* Loading last used module when revisiting application * Loading last used module when revisiting application
* Finnish language support \(Beta\) * Finnish language support (Beta)
* Improvements to German, Italian and Chinese language translations * Improvements to German, Italian and Chinese language translations
* Allow quickly switching languages * Allow quickly switching languages
* Improvements to security for preventing possible LFI attacks * Improvements to security for preventing possible LFI attacks
@@ -190,122 +132,106 @@ This release include some very critical security fixes. We recommend upgrading y
* Allow importing approved overtime hours into payroll * Allow importing approved overtime hours into payroll
* Add date and time masks * Add date and time masks
### Fixes
### Fixes
* Fix logout cookie issue, by clearing remember me cookie when logging out * Fix logout cookie issue, by clearing remember me cookie when logging out
* Improve privacy for GDPR * Improve privacy for GDPR
* Improvements to file upload field * Improvements to file upload field
* Fix issue: attendance rest end point not working on php 5.6 * Fix issue: attendance rest end point not working on php 5.6
## Release note v22.0.0.OS Release note v22.0.0.OS
------------------------
### Features ### Features
* Improvements to module naming * Improvements to module naming
### Fixes ### Fixes
* Fix issue: filter dialog default values are not selected * Fix issue: filter dialog default values are not selected
* Fix issue: department head can be an employee outside the department * 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: 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 issue: employee name is not visible on report if middle name is empty
## Release note v21.1.0.OS Release note v21.1.0.OS
------------------------
### Features ### Features
* UI improvements (help button and error messages)
* UI improvements \(help button and error messages\)
* Allow adding placeholders to text fields * Allow adding placeholders to text fields
* Improvements to German Translations * Improvements to German Translations
### Fixes ### Fixes
* Fixing notification issues * Fixing notification issues
## Release note v21.0.0.OS Release note v21.0.0.OS
------------------------
### Features ### Features
* Fully compatible with php 7.1 * Fully compatible with php 7.1
* Add Net\_SMTP via composer \(no pear installation needed\) * Add Net_SMTP via composer (no pear installation needed)
### Fixes ### Fixes
* Fixes for web servers not supporting JSON in GET request * Fixes for web servers not supporting JSON in GET request
## Release note v21.0.0.OS Release note v21.0.0.OS
------------------------
### Features ### Features
* Fully compatible with php 7.1 * Fully compatible with php 7.1
* Add Net\_SMTP via composer \(no pear installation needed\) * Add Net_SMTP via composer (no pear installation needed)
### Fixes ### Fixes
* Fixes for web servers not supporting JSON in GET request * Fixes for web servers not supporting JSON in GET request
## Release note v20.3.0.PRO Release note v20.3.0.PRO
------------------------
### Features ### Features
* Employee and Attendance REST Api Released * Employee and Attendance REST Api Released
* Import/Export for Payroll Configurations * Import/Export for Payroll Configurations
* Ability to import employee approved time sheet hours to payroll * Ability to import employee approved time sheet hours to payroll
* Swift Mailer based SMTP support \(no need to install Net\_SMTP anymore\) * Swift Mailer based SMTP support (no need to install Net_SMTP anymore)
* Add direct Edit button on employee list * Add direct Edit button on employee list
### Fixes ### Fixes
* Fix DB connection issues due to special characters in password * Fix DB connection issues due to special characters in password
* Fixes for custom field saving issues in mysql v5.7.x * Fixes for custom field saving issues in mysql v5.7.x
## Release note v20.2 Release note v20.2
------------------
### Fixes ### Fixes
* Fix for resetting modules * Fix for resetting modules
## Release note v20.1 Release note v20.1
------------------
### Features ### Features
* Compatible with MySQL 5.7 Strict Mode * Compatible with MySQL 5.7 Strict Mode
* PSR-2 compatible code * PSR-2 compatible code
* Employee History Module * Employee History Module
* Staff Directory * Staff Directory
### Fixes ### Fixes
* Fix: password reset not working * Fix: password reset not working
* Fix: limiting selectable countries via Settings * Fix: limiting selectable countries via Settings
* Fix for resetting modules * Fix for resetting modules
## Release note v20.0 Release note v20.0
------------------
### Features ### Features
* Payroll Module * Payroll Module
* Compatible with MySQL 5.7 Strict Mode * Compatible with MySQL 5.7 Strict Mode
* Namespaced Classes * Namespaced Classes
* LDAP Module * LDAP Module
### Fixes ### Fixes
* Fix: limiting selectable countries via Settings * Fix: limiting selectable countries via Settings
## Release note v19.0 Release note v19.0
------------------
### Features ### Features
* Development environment * Development environment
* Overtime module * Overtime module
* Department heads who can manage all employees attached to a company structure * Department heads who can manage all employees attached to a company structure
## Release note v18.0
Release note v18.0
------------------
### Features ### Features
* Translations (beta) for German, French, Polish, Italian, Sinhala, Chinese, Japanese, Hindi and Spanish
* Translations \(beta\) for German, French, Polish, Italian, Sinhala, Chinese, Japanese, Hindi and Spanish
* PDF Reports * PDF Reports
* Ability to specify department heads * Ability to specify department heads
* Add advanced custom fields to employees via UI * Add advanced custom fields to employees via UI
@@ -319,20 +245,19 @@ This release include some very critical security fixes. We recommend upgrading y
* Report files module - Allow downloading all previously generated reports * Report files module - Allow downloading all previously generated reports
### Fixes ### Fixes
* Fix: subordinates are not showing beyond first page issue. * Fix: subordinates are not showing beyond first page issue.
## Release note v16.1
Release note v16.1
------------------
### Fixes ### Fixes
* Fix LDAP user login issue * Fix LDAP user login issue
* Allow creating users with username having dot and dash * Allow creating users with username having dot and dash
## Release note v16.0 Release note v16.0
------------------
### Features ### Features
* Advanced Employee Management Module is now included in IceHrm Open Source Edition * 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 * 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 * Initial implementation of icehrm REST Api for reading employee details
@@ -343,31 +268,30 @@ This release include some very critical security fixes. We recommend upgrading y
* Setting for updating module names * Setting for updating module names
### Fixes ### Fixes
* Fix issue: classes should be loaded even the module is disabled * Fix issue: classes should be loaded even the module is disabled
* Deleting the only Admin user is not allowed * Deleting the only Admin user is not allowed
* Fixes for handling non UTF-8 * Fixes for handling non UTF-8
* Fix for non-mandatory select boxes are shown as mandatory * Fix for non-mandatory select boxes are shown as mandatory
## Release note v15.2 Release note v15.2
------------------
### Features ### Features
* Overtime Reports * Overtime Reports
* Overtime calculation for california * Overtime calculation for california
### Fixes ### Fixes
* Fix issue: uncaught error when placeholder value is empty * Fix issue: uncaught error when placeholder value is empty
* Log email sending success status * Log email sending success status
* Fix broken longer company name issue * Fix broken longer company name issue
* Make the application accessible when client on an intranet with no internet connection * 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 * Fix issue: when a module is disabled other modules depend on it stops working
## Release note v15.0
Release note v15.0
------------------
### Features ### Features
* Clear HTML5 local storage when logging in and switching users * Clear HTML5 local storage when logging in and switching users
* Showing a loading message while getting data from server * Showing a loading message while getting data from server
* Adding a new field to show total time of each time sheet * Adding a new field to show total time of each time sheet
@@ -375,26 +299,24 @@ This release include some very critical security fixes. We recommend upgrading y
* Company logo uploaded via settings will be used for all email headers * Company logo uploaded via settings will be used for all email headers
### Fixes ### Fixes
* Fix issue: default module URL is incorrect for Employees * Fix issue: default module URL is incorrect for Employees
* Fix date parsing issue in time sheets * Fix date parsing issue in time sheets
* AWS phar is included only when required * AWS phar is included only when required
## Release note v14.1 Release note v14.1
------------------
### Features ### Features
* Add Quick access menu * Add Quick access menu
### Fixes ### Fixes
* Fix issue: salary module not loading * Fix issue: salary module not loading
* Add travel report * Add travel report
## Release note v14.0 Release note v14.0
------------------
### Features ### Features
* IceHrm is now fully compatible with PHP 7 * IceHrm is now fully compatible with PHP 7
* Improvements to travel management module to change the process of applying for travel requests * Improvements to travel management module to change the process of applying for travel requests
* New report add for getting travel requests * New report add for getting travel requests
@@ -406,125 +328,129 @@ This release include some very critical security fixes. We recommend upgrading y
* New reports added for employee expenses and travel * New reports added for employee expenses and travel
### Fixes ### Fixes
* Fix unavailable help links * Fix unavailable help links
## Release note v13.4
Release note v13.4
-----------------
### Features ### Features
### Fixes ### Fixes
* Fix employee leave report leave type field * Fix employee leave report leave type field
## Release note v13.0 Release note v13.0
-----------------
### Features ### Features
* Recruitment module * Recruitment module
* Allow managers to edit attendance of direct report employees * Allow managers to edit attendance of direct report employees
### Fixes ### Fixes
* Employee switching issue fixed * Employee switching issue fixed
* Fix terminated employee labels * Fix terminated employee labels
* Fix issue with punch-in * Fix issue with punch-in
## Release note v12.6 Release note v12.6
-----------------
### Features ### Features
* Charts module * Charts module
* Code level security improvements * Code level security improvements
### Fixes ### Fixes
* Employee switching issue fixed * Employee switching issue fixed
## Release note v11.1
Release note v11.1
-----------------
### Features ### Features
* Add/Edit or remove employee fields * Add/Edit or remove employee fields
## Release note v11.0
Release note v11.0
-----------------
### Features ### Features
* Employee data archiving * Employee data archiving
* Leave cancellation requests * Leave cancellation requests
* Adding view employee feature * Adding view employee feature
### Fixes ### Fixes
* Improvements to date time pickers * Improvements to date time pickers
## Release note v10.1
Release note v10.1
-----------------
### Features ### Features
* Integration with ice-framework (http://githun.com/thilinah/ice-framework)
* 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 * 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 * Restricting availability of leave types to employees using leave groups
* Admins and add notes to employees * Admins and add notes to employees
## Release note v9.1 Release note v9.1
-----------------
### Fixes ### Fixes
* Add missing S3FileSystem class * Add missing S3FileSystem class
* Fix issue: passing result of a method call directly into empty method is not supported in php v5.3 * Fix issue: passing result of a method call directly into empty method is not supported in php v5.3
## Release note v9.0
Release note v9.0
-----------------
### Features ### Features
* New user interface * New user interface
* Decimal leave counts supported * Decimal leave counts supported
## Update icehrm v8.4 to v9.0 Update icehrm v8.4 to v9.0
--------------------------
* Make a backup of your icehrm db * 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 * 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 * remove all folders except app folder in icehrm root folder
* copy all folders except app folder from new installation to icehrm root folder * copy all folders except app folder from new installation to icehrm root folder
## Release note v8.4
Release note v8.4
-----------------
### Fixes ### Fixes
* Fix leave carry forward rounding issues * Fix leave carry forward rounding issues
* Fix issue: select2 default value not getting set for select2 * Fix issue: select2 default value not getting set for select2
* Fix issue: email not sent when admin changing leave status * Fix issue: email not sent when admin changing leave status
## Release note v8.3 Release note v8.3
-----------------
### Fixes ### Fixes
* Fix user table issue on windows, this will resolve errors such as: (Note that this fix has no effect on unix based installations)
* 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 view user uploaded documents
* Admin not able to upload documants for users * Admin not able to upload documants for users
* Admin can not view employee attendance records * Admin can not view employee attendance records
* Employee projects can not be added * Employee projects can not be added
## Release note v8.2
Release note v8.2
-----------------
### Features ### Features
* Instance verification added * Instance verification added
## Release note v8.1 Release note v8.1
-----------------
### Fixes ### Fixes
* Fixed bug that caused a fatal error in php v5.4 * Fixed bug that caused a fatal error in php v5.4
* aws2.7.11 phar file replaced by a aws2.7.11 extracted files * aws2.7.11 phar file replaced by a aws2.7.11 extracted files
* old aws sdk removed * old aws sdk removed
## Release note v8.0 Release note v8.0
-----------------
### Features ### Features
* Admin dashbord module * Admin dashbord module
* If the employee joined in current leave period, his leave entitlement is calculated proportional to joined date * If the employee joined in current leave period, his leave entitlement is calculated proportional to joined date
* Improvements to reporting module * Improvements to reporting module
@@ -536,22 +462,22 @@ This release include some very critical security fixes. We recommend upgrading y
* Upgrade aws sdk to v2.7.11 * Upgrade aws sdk to v2.7.11
* Allow employees to change password * Allow employees to change password
* Use only the email address defined under user for sending mails * 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 ### Fixes
* Upload dialog close button issue fixed * Upload dialog close button issue fixed
## Release note v7.2 Release note v7.2
-----------------
### Fixes ### Fixes
* Some critical vulnerabilities are fixed as recommend by http://zeroscience.mk/en/
* Some critical vulnerabilities are fixed as recommend by [http://zeroscience.mk/en/](http://zeroscience.mk/en/) Release note v7.1
-----------------
## Release note v7.1
### Features ### Features
* Improved company structure graph * Improved company structure graph
* Leave notes implementation <20> Supervisor can add a note when approving or rejecting leaves * Leave notes implementation <20> Supervisor can add a note when approving or rejecting leaves
* Filtering support * Filtering support
@@ -560,20 +486,20 @@ This release include some very critical security fixes. We recommend upgrading y
* Add ability to disable employee information editing * Add ability to disable employee information editing
### Fixes ### Fixes
* Make loans editable only by admin * Make loans editable only by admin
* Fix: permissions not getting applied to employee documents * Fix: permissions not getting applied to employee documents
* Fix error adding employee documents when no user assigned to the admin * Fix error adding employee documents when no user assigned to the admin
### Code Quality ### Code Quality
* Moving all module related code and data into module folders * Moving all module related code and data into module folders
## Release note v6.1 Release note v6.1
-----------------
Leave carry forwared related isue fixed Leave carry forwared related isue fixed
## Release note v6.0 Release note v6.0
-----------------
* Features * Features
* Notifications for leaves and timesheets * Notifications for leaves and timesheets
@@ -587,18 +513,21 @@ Leave carry forwared related isue fixed
* 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" * 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 * Employee document, date added field can not be changed by the employee anymore
* About dialog added for admins * About dialog added for admins
* Fixes * 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 user duplicate email issue
* Fix manager can not logout from switched employee * Fix manager can not logout from switched employee
* Remove admin guide from non admin users * Remove admin guide from non admin users
## Release note v5.3 Release note v5.3
-----------------
* Fixes * Fixes
* Fix missing employee name in employee details report * Fix missing employee name in employee details report
## Release note v5.2 Release note v5.2
-----------------
* Fixes * Fixes
* Remove unwanted error logs * Remove unwanted error logs
@@ -607,33 +536,38 @@ Leave carry forwared related isue fixed
* Remove add new button from subordinates module * Remove add new button from subordinates module
* Adding administrators' guide * Adding administrators' guide
## Release note v5.1 Release note v5.1
-----------------
* Fixes * Fixes
* Fixing for non updating null fields * 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 * Features
* New user permission implementation * New user permission implementation
* Adding new user level - Manager * Adding new user level - Manager
* Fixes * Fixes
* Fixing remote table loading issue * Fixing remote table loading issue
## Release note v4.2 Release note v4.2
-----------------
### Fixes ### 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 ### Features
* Better email format for notifications * Better email format for notifications
* Convert upload dialog to a bootstrp model * Convert upload dialog to a bootstrp model
* Fixes * Fixes
* Fix error sending emails with amazon SES * Fix error sending emails with amazon SES
* Fix errors related to XAMPP and WAMPP servers * Fix errors related to XAMPP and WAMPP servers
@@ -642,4 +576,3 @@ Leave carry forwared related isue fixed
* Allow icehrm client to work without an internet connection * Allow icehrm client to work without an internet connection
* Fix installer incorrect base url issue * Fix installer incorrect base url issue
* Fix empty user creation issue * Fix empty user creation issue

View File

@@ -3,21 +3,13 @@
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah) Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
*/ */
import ReactModalAdapterBase from '../../../api/ReactModalAdapterBase'; import AdapterBase from '../../../api/AdapterBase';
/** /**
* ClientAdapter * ClientAdapter
*/ */
class ClientAdapter extends ReactModalAdapterBase { class ClientAdapter extends AdapterBase {
constructor(endPoint, tab, filter, orderBy) {
super(endPoint, tab, filter, orderBy);
this.fieldNameMap = {};
this.hiddenFields = {};
this.tableFields = {};
this.formOnlyFields = {};
}
getDataMapping() { getDataMapping() {
return [ return [
'id', 'id',
@@ -38,32 +30,6 @@ class ClientAdapter extends ReactModalAdapterBase {
]; ];
} }
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() { getFormFields() {
if (this.showSave) { if (this.showSave) {
return [ return [

View File

@@ -1,5 +1,6 @@
import { CompanyStructureAdapter, CompanyGraphAdapter } from './lib'; import { CompanyStructureAdapter, CompanyGraphAdapter } from './lib';
import IceDataPipe from "../../../api/IceDataPipe"; import IceDataPipe from "../../../api/IceDataPipe";
import CustomFieldAdapter from "../../../api/ReactCustomFieldAdapter";
function init(data) { function init(data) {
@@ -8,7 +9,6 @@ function init(data) {
modJsList.tabCompanyStructure.setObjectTypeName('Company Structure'); modJsList.tabCompanyStructure.setObjectTypeName('Company Structure');
modJsList.tabCompanyStructure.setDataPipe(new IceDataPipe(modJsList.tabCompanyStructure)); modJsList.tabCompanyStructure.setDataPipe(new IceDataPipe(modJsList.tabCompanyStructure));
modJsList.tabCompanyStructure.setAccess(data.permissions.CompanyStructure); modJsList.tabCompanyStructure.setAccess(data.permissions.CompanyStructure);
modJsList.tabCompanyStructure.setCustomFields(data.customFields);
modJsList.tabCompanyGraph = new CompanyGraphAdapter('CompanyStructure'); modJsList.tabCompanyGraph = new CompanyGraphAdapter('CompanyStructure');

View File

@@ -64,7 +64,7 @@ class CompanyStructureAdapter extends ReactModalAdapterBase {
} }
getFormFields() { getFormFields() {
return this.addCustomFields([ return [
['id', { label: 'ID', type: 'hidden', validation: '' }], ['id', { label: 'ID', type: 'hidden', validation: '' }],
['title', { label: 'Name', type: 'text', validation: '' }], ['title', { label: 'Name', type: 'text', validation: '' }],
['description', { label: 'Details', type: 'textarea', validation: '' }], ['description', { label: 'Details', type: 'textarea', validation: '' }],
@@ -80,7 +80,7 @@ class CompanyStructureAdapter extends ReactModalAdapterBase {
['heads', { ['heads', {
label: 'Heads', type: 'select2multi', 'allow-null': true, 'remote-source': ['Employee', 'id', 'first_name+last_name'], label: 'Heads', type: 'select2multi', 'allow-null': true, 'remote-source': ['Employee', 'id', 'first_name+last_name'],
}], }],
]); ];
} }
postRenderForm(object, $tempDomObj) { postRenderForm(object, $tempDomObj) {

View File

@@ -1,18 +0,0 @@
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

@@ -1,166 +0,0 @@
/**
* 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,7 +98,6 @@ class DashboardAdapter extends AdapterBase {
} }
drawEmployeeDistributionChart() { drawEmployeeDistributionChart() {
const that = this;
document.getElementById('EmployeeDistributionChart').style.display = 'none'; document.getElementById('EmployeeDistributionChart').style.display = 'none';
ReactDOM.render( ReactDOM.render(
this.getSpinner(), this.getSpinner(),
@@ -116,7 +115,7 @@ class DashboardAdapter extends AdapterBase {
forceFit: true, forceFit: true,
title: { title: {
visible: true, visible: true,
text: that.gt('Employee Distribution'), text: 'Employee Distribution',
}, },
description: { description: {
visible: false, visible: false,
@@ -126,7 +125,7 @@ class DashboardAdapter extends AdapterBase {
visible: true, visible: true,
content: { content: {
value: chartData.reduce((acc, item) => acc + item.value, 0), value: chartData.reduce((acc, item) => acc + item.value, 0),
name: that.gt('Total'), name: 'Total',
}, },
}, },
legend: { legend: {
@@ -152,7 +151,6 @@ class DashboardAdapter extends AdapterBase {
} }
drawOnlineOfflineEmployeeChart() { drawOnlineOfflineEmployeeChart() {
const that = this;
document.getElementById('EmployeeOnlineOfflineChart').style.display = 'none'; document.getElementById('EmployeeOnlineOfflineChart').style.display = 'none';
ReactDOM.render( ReactDOM.render(
this.getSpinner(), this.getSpinner(),
@@ -170,7 +168,7 @@ class DashboardAdapter extends AdapterBase {
forceFit: true, forceFit: true,
title: { title: {
visible: true, visible: true,
text: that.gt('Employee Check-Ins'), text: 'Employee Check-Ins',
}, },
description: { description: {
visible: false, visible: false,
@@ -180,7 +178,7 @@ class DashboardAdapter extends AdapterBase {
visible: true, visible: true,
content: { content: {
value: chartData.reduce((acc, item) => acc + item.value, 0), value: chartData.reduce((acc, item) => acc + item.value, 0),
name: that.gt('Total'), name: 'Total',
}, },
}, },
legend: { legend: {
@@ -201,7 +199,6 @@ class DashboardAdapter extends AdapterBase {
} }
drawCompanyLeaveEntitlementChart() { drawCompanyLeaveEntitlementChart() {
const that = this;
document.getElementById('CompanyLeaveEntitlementChart').style.display = 'none'; document.getElementById('CompanyLeaveEntitlementChart').style.display = 'none';
ReactDOM.render( ReactDOM.render(
this.getSpinner(), this.getSpinner(),
@@ -219,7 +216,7 @@ class DashboardAdapter extends AdapterBase {
forceFit: true, forceFit: true,
title: { title: {
visible: true, visible: true,
text: that.gt('Company Vacation Usage'), text: 'Company Vacation Usage',
}, },
description: { description: {
visible: false, visible: false,
@@ -229,7 +226,7 @@ class DashboardAdapter extends AdapterBase {
visible: true, visible: true,
content: { content: {
value: chartData.reduce((acc, item) => acc + item.value, 0), value: chartData.reduce((acc, item) => acc + item.value, 0),
name: that.gt('Total'), name: 'Total',
}, },
}, },
legend: { legend: {

View File

@@ -34,8 +34,6 @@ class DocumentAdapter extends AdapterBase {
['expire_notification_month', { label: 'Notify Expiry Before One Month', type: 'select', source: [['Yes', 'Yes'], ['No', 'No']] }], ['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_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']] }], ['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":"Require Signature","type":"select","source":[["Yes","Yes"],["No","No"]]}],
// [ "sign_label", {"label":"Signature Description","type":"textarea","validation":"none"}], // [ "sign_label", {"label":"Signature Description","type":"textarea","validation":"none"}],
['details', { label: 'Details', type: 'textarea', validation: 'none' }], ['details', { label: 'Details', type: 'textarea', validation: 'none' }],
@@ -148,7 +146,6 @@ class EmployeeDocumentAdapter extends AdapterBase {
['date_added', { label: 'Date Added', type: 'date', validation: '' }], ['date_added', { label: 'Date Added', type: 'date', validation: '' }],
['valid_until', { label: 'Valid Until', type: 'date', validation: 'none' }], ['valid_until', { label: 'Valid Until', type: 'date', validation: 'none' }],
['status', { label: 'Status', type: 'select', source: [['Active', 'Active'], ['Inactive', 'Inactive'], ['Draft', 'Draft']] }], ['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' }], ['details', { label: 'Details', type: 'textarea', validation: 'none' }],
['attachment', { label: 'Attachment', type: 'fileupload', validation: '' }], ['attachment', { label: 'Attachment', type: 'fileupload', validation: '' }],
]; ];

View File

@@ -363,7 +363,28 @@ class EmployeeAdapter extends ReactModalAdapterBase {
}); });
} }
return this.addActualFieldsForStepModal(steps, fields); 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,
};
} }
getFilters() { getFilters() {
@@ -405,7 +426,7 @@ class EmployeeAdapter extends ReactModalAdapterBase {
&& ( && (
<Tag color="volcano" onClick={() => modJs.terminateEmployee(record.id)} style={{ cursor: 'pointer' }}> <Tag color="volcano" onClick={() => modJs.terminateEmployee(record.id)} style={{ cursor: 'pointer' }}>
<DeleteOutlined /> <DeleteOutlined />
{` ${adapter.gt('Deactivate')}`} {` ${adapter.gt('Delete')}`}
</Tag> </Tag>
)} )}
{adapter.hasAccess('save') {adapter.hasAccess('save')

View File

@@ -344,7 +344,7 @@ class PayrollColumnAdapter extends AdapterBase {
['enabled', { label: 'Enabled', type: 'select', source: [['Yes', 'Yes'], ['No', 'No']] }], ['enabled', { label: 'Enabled', type: 'select', source: [['Yes', 'Yes'], ['No', 'No']] }],
['default_value', { label: 'Default Value', type: 'text', validation: '' }], ['default_value', { label: 'Default Value', type: 'text', validation: '' }],
fucntionColumnList, fucntionColumnList,
['function_type', { label: 'Function Type', type: 'select', source: [['Simple', 'Simple']] }], ['function_type', { label: 'Function Type', type: 'select', source: [['Advanced', 'Advanced'], ['Simple', 'Simple']] }],
['calculation_function', { label: 'Function', type: 'code', validation: 'none' }], ['calculation_function', { label: 'Function', type: 'code', validation: 'none' }],
]; ];
} }

View File

@@ -3,21 +3,13 @@
Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah) Developer: Thilina Hasantha (http://lk.linkedin.com/in/thilinah | https://github.com/thilinah)
*/ */
import ReactModalAdapterBase from '../../../api/ReactModalAdapterBase'; import AdapterBase from '../../../api/AdapterBase';
/** /**
* ProjectAdapter * ProjectAdapter
*/ */
class ProjectAdapter extends ReactModalAdapterBase { class ProjectAdapter extends AdapterBase {
constructor(endPoint, tab, filter, orderBy) {
super(endPoint, tab, filter, orderBy);
this.fieldNameMap = {};
this.hiddenFields = {};
this.tableFields = {};
this.formOnlyFields = {};
}
getDataMapping() { getDataMapping() {
return [ return [
'id', 'id',
@@ -34,21 +26,6 @@ class ProjectAdapter extends ReactModalAdapterBase {
]; ];
} }
getTableColumns() {
return [
{
title: 'Name',
dataIndex: 'name',
sorter: true,
},
{
title: 'Client',
dataIndex: 'client',
sorter: true,
}
];
}
getFormFields() { getFormFields() {
if (this.showSave) { if (this.showSave) {
return [ return [
@@ -83,15 +60,7 @@ class ProjectAdapter extends ReactModalAdapterBase {
*/ */
class EmployeeProjectAdapter extends ReactModalAdapterBase { class EmployeeProjectAdapter extends AdapterBase {
constructor(endPoint, tab, filter, orderBy) {
super(endPoint, tab, filter, orderBy);
this.fieldNameMap = {};
this.hiddenFields = {};
this.tableFields = {};
this.formOnlyFields = {};
}
getDataMapping() { getDataMapping() {
return [ return [
'id', 'id',
@@ -108,22 +77,6 @@ class EmployeeProjectAdapter extends ReactModalAdapterBase {
]; ];
} }
getTableColumns() {
return [
{
title: 'Employee',
dataIndex: 'employee',
sorter: true,
},
{
title: 'Project',
dataIndex: 'project',
sorter: true,
}
];
}
getFormFields() { getFormFields() {
return [ return [
['id', { label: 'ID', type: 'hidden' }], ['id', { label: 'ID', type: 'hidden' }],

View File

@@ -9,6 +9,8 @@ import TimeUtils from './TimeUtils';
import RequestCache from './RequestCache'; import RequestCache from './RequestCache';
import SocialShare from './SocialShare'; import SocialShare from './SocialShare';
const Aes = require('./Aes');
window.RequestCache = RequestCache; window.RequestCache = RequestCache;
window.SocialShare = SocialShare; window.SocialShare = SocialShare;

View File

@@ -104,16 +104,16 @@ class ApproveAdminAdapter extends LogViewAdapter {
getStatusOptionsData(currentStatus) { getStatusOptionsData(currentStatus) {
const data = {}; const data = {};
if (currentStatus === 'Approved') { if (currentStatus == 'Approved') {
} else if (currentStatus === 'Pending') { } else if (currentStatus == 'Pending') {
data.Approved = 'Approved'; data.Approved = 'Approved';
data.Rejected = 'Rejected'; 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 { } else {
data['Cancellation Requested'] = 'Cancellation Requested'; data['Cancellation Requested'] = 'Cancellation Requested';

View File

@@ -37,7 +37,7 @@ class CustomFieldAdapter extends AdapterBase {
['id', { label: 'ID', type: 'hidden' }], ['id', { label: 'ID', type: 'hidden' }],
['name', { label: 'Name', type: 'text', validation: '' }], ['name', { label: 'Name', type: 'text', validation: '' }],
['display', { label: 'Display Status', type: 'select', source: [['Form', 'Show'], ['Hidden', 'Hidden']] }], ['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'], ['signature', 'Signature']] }], ['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_label', { label: 'Field Label', type: 'text', validation: '' }],
['field_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']], 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

@@ -143,7 +143,6 @@ class ModuleBase {
gt(key) { gt(key) {
if (this.translations[key] === undefined || this.translations[key] === null) { if (this.translations[key] === undefined || this.translations[key] === null) {
console.log("Tr:" + key);
return key; return key;
} }
return this.translations[key][0]; return this.translations[key][0];
@@ -1058,7 +1057,7 @@ class ModuleBase {
return this.gt('Password too short'); return this.gt('Password too short');
} }
if (password.length > 30) { if (password.length > 20) {
return this.gt('Password too long'); return this.gt('Password too long');
} }

View File

@@ -1,134 +0,0 @@
/*
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

@@ -1,71 +0,0 @@
/*
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,8 +20,6 @@ class ReactCustomFieldAdapter extends AdapterBase {
return [ return [
'id', 'id',
'name', 'name',
'field_type',
'field_label',
'display', 'display',
'display_order', 'display_order',
]; ];
@@ -31,8 +29,6 @@ class ReactCustomFieldAdapter extends AdapterBase {
return [ return [
{ sTitle: 'ID', bVisible: false }, { sTitle: 'ID', bVisible: false },
{ sTitle: 'Name' }, { sTitle: 'Name' },
{ sTitle: 'Field Type' },
{ sTitle: 'Field Label' },
{ sTitle: 'Display Status' }, { sTitle: 'Display Status' },
{ sTitle: 'Priority' }, { sTitle: 'Priority' },
]; ];
@@ -44,24 +40,17 @@ class ReactCustomFieldAdapter extends AdapterBase {
title: 'Name', title: 'Name',
dataIndex: 'name', dataIndex: 'name',
sorter: true, sorter: true,
}, width: '25%',
{
title: 'Field Label',
dataIndex: 'field_label',
},
{
title: 'Field Type',
dataIndex: 'field_type',
}, },
{ {
title: 'Display Status', title: 'Display Status',
dataIndex: 'display', dataIndex: 'display',
sorter: true, width: '35%',
}, },
{ {
title: 'Priority', title: 'Priority',
dataIndex: 'display_order', dataIndex: 'display_order',
sorter: true, width: '10%',
}, },
]; ];
} }
@@ -69,8 +58,10 @@ class ReactCustomFieldAdapter extends AdapterBase {
getFormFields() { getFormFields() {
return [ return [
['id', { label: 'ID', type: 'hidden' }], ['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_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', { ['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']], 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']],
}], }],
@@ -96,14 +87,11 @@ class ReactCustomFieldAdapter extends AdapterBase {
], ],
validation: 'none', validation: 'none',
}], }],
['display_order', { label: 'Priority', type: 'text', validation: 'none' }], ['display_order', { label: 'Priority', type: 'text', validation: 'number' }],
['display_section', { label: 'Display Section', type: 'text', validation: 'none' }],
]; ];
} }
getNameFromFieldName(fieldName) {
return fieldName.replace(/[^a-z0-9+]+/gi, '').toLowerCase();
}
setTableType(type) { setTableType(type) {
this.tableType = type; this.tableType = type;
} }
@@ -114,26 +102,18 @@ class ReactCustomFieldAdapter extends AdapterBase {
return str != null && name.test(str); 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)) { if (!validateName(params.name)) {
return 'Invalid name for custom field'; return 'Invalid name for custom field';
} }
return null; return null;
} }
forceInjectValuesBeforeSave(params) { forceInjectValuesBeforeSave(params) {
const data = ['', {}]; const data = [params.name]; const options = []; let
const options = []; optionsData;
let optionsData; data.push({});
data[1].label = params.field_label; data[1].label = params.field_label;
data[1].type = params.field_type; data[1].type = params.field_type;
data[1].validation = params.field_validation; data[1].validation = params.field_validation;
@@ -148,23 +128,8 @@ class ReactCustomFieldAdapter extends AdapterBase {
if (params.field_validation == null || params.field_validation === undefined) { if (params.field_validation == null || params.field_validation === undefined) {
params.field_validation = ''; params.field_validation = '';
} }
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.data = JSON.stringify(data);
params.type = this.tableType;
params.display = 'Form';
params.display_order = parseInt(params.display_order);
if (!Number.isInteger(params.display_order)) {
params.display_order = 1;
}
return params; return params;
} }
} }

View File

@@ -1,45 +0,0 @@
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

@@ -1,58 +0,0 @@
/*
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,10 +48,6 @@ class ReactModalAdapterBase extends AdapterBase {
return this.access.indexOf(type) > 0; return this.access.indexOf(type) > 0;
} }
hasCustomButtons() {
return false;
}
initTable() { initTable() {
if (this.tableInitialized) { if (this.tableInitialized) {
return false; return false;
@@ -60,11 +56,7 @@ class ReactModalAdapterBase extends AdapterBase {
if (tableDom) { if (tableDom) {
this.tableContainer = React.createRef(); this.tableContainer = React.createRef();
let columns = this.getTableColumns(); let columns = this.getTableColumns();
if (this.hasAccess('save') if (this.hasAccess('save') || this.hasAccess('delete') || this.hasAccess('element')) {
|| this.hasAccess('delete')
|| this.hasAccess('element')
|| this.hasCustomButtons()
) {
columns.push({ columns.push({
title: 'Actions', title: 'Actions',
key: 'actions', key: 'actions',
@@ -265,27 +257,6 @@ class ReactModalAdapterBase extends AdapterBase {
showLoader() { showLoader() {
// $('#iceloader').show(); // $('#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; export default ReactModalAdapterBase;

View File

@@ -1,70 +0,0 @@
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,7 +21,6 @@ class IceDataGroup extends React.Component {
value = this.parseValue(value); value = this.parseValue(value);
value = value.map(item => ({ ...item, key:item.id } )); value = value.map(item => ({ ...item, key:item.id } ));
const columns = JSON.parse(JSON.stringify(field[1].columns)); const columns = JSON.parse(JSON.stringify(field[1].columns));
if (!this.props.readOnly) { if (!this.props.readOnly) {
columns.push({ columns.push({
title: 'Action', title: 'Action',
@@ -31,7 +30,6 @@ class IceDataGroup extends React.Component {
), ),
}); });
} }
return ( return (
<> <>
{!this.props.readOnly && {!this.props.readOnly &&

View File

@@ -7,8 +7,6 @@ import IceUpload from './IceUpload';
import IceDataGroup from './IceDataGroup'; import IceDataGroup from './IceDataGroup';
import IceSelect from './IceSelect'; import IceSelect from './IceSelect';
import IceLabel from './IceLabel'; import IceLabel from './IceLabel';
import IceColorPick from './IceColorPick';
import IceSignature from './IceSignature';
const ValidationRules = { const ValidationRules = {
@@ -88,31 +86,19 @@ class IceForm extends React.Component {
} }
render() { render() {
const { fields, twoColumnLayout, adapter } = this.props; const { fields, twoColumnLayout } = this.props;
let formInputs = [];
const formInputs1 = []; const formInputs1 = [];
const formInputs2 = []; const formInputs2 = [];
const columns = !twoColumnLayout ? 1 : 2; const columns = !twoColumnLayout ? 1 : 2;
for (let i = 0; i < fields.length; i++) { for (let i = 0; i < fields.length; i++) {
formInputs.push( const formInput = this.createFromField(fields[i], this.props.viewOnly);
adapter.beforeRenderFieldHook( if (formInput != null) {
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) { if (columns === 1) {
formInputs1.push(formInputs[i]); formInputs1.push(formInput);
} else if (i % 2 === 0) { } else if (i % 2 === 0) {
formInputs1.push(formInputs[i]); formInputs1.push(formInput);
} else { } else {
formInputs2.push(formInputs[i]); formInputs2.push(formInput);
} }
} }
} }
@@ -253,9 +239,6 @@ class IceForm extends React.Component {
</Form.Item> </Form.Item>
); );
} if (data.type === 'textarea') { } if (data.type === 'textarea') {
if (!data.rows) {
data.rows = 4;
}
return ( return (
<Form.Item <Form.Item
labelCol={labelSpan} labelCol={labelSpan}
@@ -266,7 +249,7 @@ class IceForm extends React.Component {
> >
{viewOnly {viewOnly
? <IceLabel /> ? <IceLabel />
: <Input.TextArea rows={data.rows} />} : <Input.TextArea />}
</Form.Item> </Form.Item>
); );
} if (data.type === 'date') { } if (data.type === 'date') {
@@ -372,34 +355,6 @@ class IceForm extends React.Component {
/> />
</Form.Item> </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; return null;
} }

View File

@@ -21,7 +21,6 @@ class IceFormModal extends React.Component {
} }
show(data) { show(data) {
this.props.adapter.beforeRenderFieldHook = this.props.adapter.beforeRenderField ? this.props.adapter.beforeRenderField(data) : (fieldName, field) => field;
if (!data) { if (!data) {
this.setState({ visible: true }); this.setState({ visible: true });
if (this.iceFormReference.current) { if (this.iceFormReference.current) {

View File

@@ -1,83 +0,0 @@
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,7 +10,6 @@ class IceStepFormModal extends IceFormModal {
} }
show(data) { show(data) {
this.props.adapter.beforeRenderFieldHook = this.props.adapter.beforeRenderField ? this.props.adapter.beforeRenderField(data) : (fieldName, field) => field;
if (!data) { if (!data) {
this.setState({ visible: true }); this.setState({ visible: true });
if (this.iceFormReference.current) { if (this.iceFormReference.current) {

View File

@@ -69,6 +69,7 @@ class IceTable extends React.Component {
}; };
search = (value) => { search = (value) => {
console.log('search table:' + value);
this.setState({ search: value }); this.setState({ search: value });
const fetchConfig = this.state.fetchConfig; const fetchConfig = this.state.fetchConfig;
console.log(fetchConfig); console.log(fetchConfig);
@@ -104,10 +105,12 @@ class IceTable extends React.Component {
} }
fetch = (params = {}) => { fetch = (params = {}) => {
console.log('params:', params);
//this.setState({ loading: this.state.showLoading }); //this.setState({ loading: this.state.showLoading });
this.setState({ loading: true }); this.setState({ loading: true });
//const hideMessage = message.loading({ content: 'Loading Latest Data ...', key: 'loadingTable', duration: 1}); //const hideMessage = message.loading({ content: 'Loading Latest Data ...', key: 'loadingTable', duration: 1});
const pagination = { ...this.state.pagination }; const pagination = { ...this.state.pagination };
console.log('pagination:', pagination);
if (this.props.adapter.localStorageEnabled) { if (this.props.adapter.localStorageEnabled) {
try { try {

View File

@@ -150,7 +150,7 @@ class UpdatePasswordModal extends React.Component {
return this.props.adapter.gt('Password too short'); return this.props.adapter.gt('Password too short');
} }
if (password.length > 30) { if (password.length > 20) {
return this.props.adapter.gt('Password too long'); return this.props.adapter.gt('Password too long');
} }

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