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')) { + + +
  • "> + + "> + + + + +
  • + + 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; +}