diff --git a/app/index.php b/app/index.php
index 5a08ad7f..846160f4 100755
--- a/app/index.php
+++ b/app/index.php
@@ -5,7 +5,7 @@ if(!file_exists('config.php')){
}
include ('config.php');
if(!isset($_REQUEST['g']) || !isset($_REQUEST['n'])){
-header("Location:".CLIENT_BASE_URL."login.php");
+header("Location:".CLIENT_BASE_URL."login.php");
exit();
}
$group = $_REQUEST['g'];
@@ -14,9 +14,16 @@ $name= $_REQUEST['n'];
$groups = array('admin','modules');
if($group == 'admin' || $group == 'modules'){
- $name = str_replace("..","",$name);
+ $name = str_replace("..","",$name);
$name = str_replace("/","",$name);
include APP_BASE_PATH.'/'.$group.'/'.$name.'/index.php';
+}else if ($group == 'extension'){
+ $name = str_replace("..","",$name);
+ $name = str_replace("/","",$name);
+ $moduleName = $name;
+ $moduleGroup = 'extensions';
+ $extensionIndex = APP_BASE_PATH.'/../extensions/'.$name.'/web/index.php';
+ include APP_BASE_PATH.'extensions/wrapper.php';
}else{
exit();
-}
\ No newline at end of file
+}
diff --git a/core/config.base.php b/core/config.base.php
index 6a712edd..d0ba6dda 100644
--- a/core/config.base.php
+++ b/core/config.base.php
@@ -36,3 +36,7 @@ define('ALL_CLIENT_BASE_PATH', '/var/www/icehrm.app/icehrmapp/');
define('LDAP_ENABLED', true);
define('RECRUITMENT_ENABLED', false);
define('APP_WEB_URL', 'https://icehrm.com');
+
+if (!defined('EXTENSIONS_URL')) {
+ define('EXTENSIONS_URL', str_replace('/web/', '/extensions/', BASE_URL));
+}
diff --git a/core/extensions/wrapper.php b/core/extensions/wrapper.php
new file mode 100644
index 00000000..30efd38b
--- /dev/null
+++ b/core/extensions/wrapper.php
@@ -0,0 +1,31 @@
+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();
+}
+?>
+
+
+
+
+
+
+
diff --git a/core/header.php b/core/header.php
index 45e43479..6bb0c921 100644
--- a/core/header.php
+++ b/core/header.php
@@ -215,6 +215,26 @@ if (defined('SYM_CLIENT')) {
+
+
+
">
+
+ "> =\Classes\LanguageManager::tran($menu['name'])?>
+
+
+
+
+
+
+
setupExtensions();
+$extensionIcons = $extensionData[0];
+$extensionTemp = $extensionData[1];
+$extensionMenus = array_keys($extensionIcons);
+
foreach ($adminModulesTemp as $k => $v) {
ksort($adminModulesTemp[$k]);
}
@@ -299,6 +305,10 @@ foreach ($userModulesTemp as $k => $v) {
ksort($userModulesTemp[$k]);
}
+foreach ($extensionTemp as $k => $v) {
+ ksort($extensionTemp[$k]);
+}
+
$adminIcons = json_decode(file_get_contents(CLIENT_PATH.'/admin/meta.json'), true);
$adminMenus = array_keys($adminIcons);
@@ -332,8 +342,6 @@ foreach ($userMenus as $menu) {
}
}
-$mainIcons = array_merge($adminIcons, $userIcons);
-
foreach ($userModulesTemp as $k => $v) {
if (!in_array($k, $added)) {
$arr = array("name"=>$k,"menu"=>$userModulesTemp[$k]);
@@ -341,6 +349,25 @@ foreach ($userModulesTemp as $k => $v) {
}
}
+$extensions = array();
+foreach ($extensionMenus as $menu) {
+ if (isset($extensionTemp[$menu])) {
+ $arr = array("name"=>$menu,"menu"=>$extensionTemp[$menu]);
+ $extensions[] = $arr;
+ $added[] = $menu;
+ }
+}
+
+foreach ($extensionTemp as $k => $v) {
+ if (!in_array($k, $added)) {
+ $arr = array("name"=>$k,"menu"=>$extensionTemp[$k]);
+ $extensions[] = $arr;
+ }
+}
+
+// Merge icons
+$mainIcons = array_merge($adminIcons, $userIcons, $extensionIcons);
+
//Remove modules having no permissions
if (!empty($user)) {
if (!empty($user->user_roles)) {
@@ -393,4 +420,24 @@ if (!empty($user)) {
}
}
}
+
+ foreach ($extensions as $fk => $menu) {
+ foreach ($menu['menu'] as $key => $item) {
+ // If the user's once of the user roles are blacklisted for the module
+ $commonRoles = array_intersect($item['user_roles_blacklist'], $userRoles);
+ if (!empty($commonRoles)) {
+ unset($extensions[$fk]['menu'][$key]);
+ }
+ if (!in_array($user->user_level, $item['user_levels'])) {
+ if (!empty($userRoles)) {
+ $commonRoles = array_intersect($item['user_roles'], $userRoles);
+ if (empty($commonRoles)) {
+ unset($extensions[$fk]['menu'][$key]);
+ }
+ } else {
+ unset($extensions[$fk]['menu'][$key]);
+ }
+ }
+ }
+ }
}
diff --git a/core/src/Classes/AbstractModuleManager.php b/core/src/Classes/AbstractModuleManager.php
index 2270f251..f38922a8 100644
--- a/core/src/Classes/AbstractModuleManager.php
+++ b/core/src/Classes/AbstractModuleManager.php
@@ -278,4 +278,12 @@ abstract class AbstractModuleManager
{
BaseService::getInstance()->addCalculationHook($code, $name, $class, $method);
}
+
+ public function install() {
+
+ }
+
+ public function uninstall() {
+
+ }
}
diff --git a/core/src/Classes/ExtensionManager.php b/core/src/Classes/ExtensionManager.php
new file mode 100644
index 00000000..2ba38c10
--- /dev/null
+++ b/core/src/Classes/ExtensionManager.php
@@ -0,0 +1,143 @@
+Find("mod_group = ?", array(self::GROUP));
+
+ $extensionsInDB = [];
+ foreach ($extensions as $dbm) {
+ $extensionsInDB[$dbm->name] = $dbm;
+ ModuleAccessService::getInstance()->setModule($dbm->name, self::GROUP, $dbm);
+ }
+
+ return $extensionsInDB;
+ }
+
+ public function getExtensionsPath() {
+ return APP_BASE_PATH.'../extensions/';
+ }
+
+ public function getExtensionMetaData($extensionName)
+ {
+ return json_decode(file_get_contents($this->getExtensionsPath().$extensionName.'/meta.json'));
+ }
+
+ public function setupExtensions() {
+ $menu = [];
+ $extensions = [];
+ $extensionDirs = scandir($this->getExtensionsPath());
+ $currentLocation = 0;
+
+ $extensionsInDB = $this->processExtensionInDB();
+
+ $needToInstall = false;
+
+ foreach ($extensionDirs as $extensionDir) {
+ if (is_dir($this->getExtensionsPath().$extensionDir) && $extensionDir != '.' && $extensionDir != '..') {
+ $meta = $this->getExtensionMetaData($extensionDir);
+
+ $arr = [];
+ $arr['name'] = $extensionDir;
+ $arr['label'] = $meta->label;
+ $arr['icon'] = $meta->icon;
+ $arr['menu'] = $meta->menu[0];
+ $arr['order'] = 0;
+ $arr['status'] = 'Enabled';
+ $arr['user_levels'] = $meta->user_levels;
+ $arr['user_roles'] = isset($meta->user_roles)?$meta->user_roles:"";
+ $arr['model_namespace'] = $meta->model_namespace;
+ $arr['manager'] = $meta->manager;
+
+ // Add menu
+ $menu[$meta->menu[0]] = $meta->menu[1];
+
+ //Check in admin dbmodules
+ if (isset($extensionsInDB[$arr['name']])) {
+ $dbModule = $extensionsInDB[$arr['name']];
+
+ $arr['name'] = $dbModule->name;
+ $arr['label'] = $dbModule->label;
+ $arr['icon'] = $dbModule->icon;
+ $arr['menu'] = $dbModule->menu;
+ $arr['status'] = $dbModule->status;
+ $arr['user_levels'] = json_decode($dbModule->user_levels);
+ $arr['user_roles'] = empty($dbModule->user_roles)
+ ? [] : json_decode($dbModule->user_roles);
+ $arr['user_roles_blacklist'] = empty($dbModule->user_roles_blacklist)
+ ? [] : json_decode($dbModule->user_roles_blacklist);
+ } else {
+ $dbModule = new \Modules\Common\Model\Module();
+ $dbModule->menu = $arr['menu'];
+ $dbModule->name = $arr['name'];
+ $dbModule->label = $arr['label'];
+ $dbModule->icon = $arr['icon'];
+ $dbModule->mod_group = self::GROUP;
+ $dbModule->mod_order = $arr['order'];
+ $dbModule->status = "Enabled";
+ $dbModule->version = isset($meta->version)?$meta->version:"";
+ $dbModule->update_path = self::GROUP.">".$extensionDir;
+ $dbModule->user_levels = isset($meta->user_levels)?json_encode($meta->user_levels):"";
+ $dbModule->user_roles = isset($meta->user_roles)?json_encode($meta->user_roles):"";
+
+ $ok = $dbModule->Save();
+ if (!$ok) {
+ LogManager::getInstance()->error('Error saving module: '.$dbModule->name);
+ }
+ $needToInstall = $ok;
+ }
+
+ /* @var \Classes\AbstractModuleManager */
+ $manager = $this->includeModuleManager($extensionDir, $arr);
+ if ($dbModule->status == 'Disabled') {
+ continue;
+ }
+
+ if ($needToInstall) {
+ $manager->install();
+ }
+
+ $menuName = $arr['menu'];
+ if (!isset($extensions[$menuName])) {
+ $extensions[$menuName] = array();
+ }
+
+ if (!$meta->headless) {
+ if ($arr['order'] == '0' || $arr['order'] == '') {
+ $extensions[$menuName]["Z".$currentLocation] = $arr;
+ $currentLocation++;
+ } else {
+ $extensions[$arr['menu']]["A".$arr['order']] = $arr;
+ }
+ }
+
+ $initializer = $manager->getInitializer();
+ if ($initializer !== null) {
+ $initializer->setBaseService(BaseService::getInstance());
+ $initializer->init();
+ }
+ }
+ }
+
+ return [$menu, $extensions];
+ }
+
+ public function includeModuleManager($name, $data)
+ {
+ include($this->getExtensionsPath().$name.'/'.$name.'.php');
+ $moduleManagerClass = $data['manager'];
+ /* @var \Classes\AbstractModuleManager $moduleManagerObj*/
+ $moduleManagerObj = new $moduleManagerClass();
+ $moduleManagerObj->setModuleObject($data);
+ $moduleManagerObj->setModuleType(self::GROUP);
+ $moduleManagerObj->setModulePath(CLIENT_PATH.'/'.self::GROUP.'/'.$name);
+ \Classes\BaseService::getInstance()->addModuleManager($moduleManagerObj);
+ return $moduleManagerObj;
+ }
+}
diff --git a/core/src/Classes/IceExtension.php b/core/src/Classes/IceExtension.php
new file mode 100644
index 00000000..21983d3a
--- /dev/null
+++ b/core/src/Classes/IceExtension.php
@@ -0,0 +1,20 @@
+file = $file;
}
@@ -50,6 +52,7 @@ abstract class AbstractMigration
$ret = $this->db()->Execute($sql);
if (!$ret) {
$this->lastError = $this->db()->ErrorMsg();
+ LogManager::getInstance()->error('Error in migration: '.$this->lastError);
}
return $ret;
}
diff --git a/core/src/Classes/ModuleAccess.php b/core/src/Classes/ModuleAccess.php
index b1a4ee83..07b8911b 100644
--- a/core/src/Classes/ModuleAccess.php
+++ b/core/src/Classes/ModuleAccess.php
@@ -12,7 +12,7 @@ class ModuleAccess
* @param $name
* @param $group
*/
- public function __construct($name, $group)
+ public function __construct($name, $group = 'extension')
{
$this->name = $name;
$this->group = $group;
diff --git a/extensions/gitkeep b/extensions/gitkeep
new file mode 100755
index 00000000..52803082
--- /dev/null
+++ b/extensions/gitkeep
@@ -0,0 +1 @@
+git keep
diff --git a/gulpfile.js b/gulpfile.js
index ca2c37c6..295a8f54 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -359,6 +359,48 @@ gulp.task('modules-js', (done) => {
.pipe(gulp.dest('./web/dist'));
});
+gulp.task('extension-js', (done) => {
+ let extension = process.argv.filter((item) => item.substr(0, 3) === '--x');
+ if (extension.length === 1) {
+ extension = extension[0].substr(3);
+ }
+
+ // map them to our stream function
+ return browserify({
+ entries: [`extensions/${extension}/web/js/index.js`],
+ basedir: '.',
+ debug: true,
+ cache: {},
+ packageCache: {},
+ })
+ .external(vendorsFlat)
+ .transform('babelify', {
+ plugins: [
+ ['@babel/plugin-proposal-class-properties', { loose: true }],
+ ],
+ presets: ['@babel/preset-env', '@babel/preset-react'],
+ extensions: ['.js', '.jsx'],
+ })
+ .transform(require('browserify-css'))
+ .bundle()
+ .pipe(source(`${extension}.js`))
+ .pipe(buffer())
+ .pipe(ifElse(!isProduction, () => sourcemaps.init({ loadMaps: true })))
+ .pipe(ifElse(isProduction, () => uglifyes(
+ {
+ compress: true,
+ mangle: {
+ reserved: [],
+ },
+ },
+ )))
+ .pipe(ifElse(isProduction, () => javascriptObfuscator({
+ compact: true,
+ })))
+ .pipe(ifElse(!isProduction, () => sourcemaps.write('./')))
+ .pipe(gulp.dest(`./extensions/${extension}/dist`));
+});
+
gulp.task('watch', () => {
gulp.watch('web/admin/src/*/*.js', gulp.series('admin-js'));
diff --git a/web/css/style.css b/web/css/style.css
index a41aeac9..44bfcbcb 100644
--- a/web/css/style.css
+++ b/web/css/style.css
@@ -943,3 +943,7 @@ table.dataTable{
.table-row-dark {
background-color: #fbfbfb;
}
+
+.mod-tab {
+ margin-bottom:0px;margin-left:5px;border-bottom: none;
+}