diff --git a/Application/ApplicationManager.php b/Application/ApplicationManager.php index 4f0e0528f..a43c08e8f 100644 --- a/Application/ApplicationManager.php +++ b/Application/ApplicationManager.php @@ -31,6 +31,14 @@ use phpOMS\System\File\PathException; */ final class ApplicationManager { + /** + * Application instance. + * + * @var ApplicationAbstract + * @since 1.0.0 + */ + private ApplicationAbstract $app; + /** * Applications * @@ -42,10 +50,13 @@ final class ApplicationManager /** * Constructor. * - * @since. 1.0.0 + * @param ApplicationAbstract $app Application + * + * @since 1.0.0 */ - public function __construct() + public function __construct(ApplicationAbstract $app) { + $this->app = $app; } /** @@ -85,31 +96,30 @@ final class ApplicationManager public function install(string $source, string $destination, string $theme = 'Default') : bool { $destination = \rtrim($destination, '\\/'); + $source = \rtrim($source, '/\\'); + if (!\is_dir($source) || \is_dir($destination)) { return false; } - $app = $this->loadInfo(\rtrim($source, '/\\') . '/info.json'); - $this->applications[$app->getInternalName()] = $app; - - $this->installFiles($source, $destination); - $this->installTheme($destination, $theme); - - $files = Directory::list($destination, '*', true); - foreach ($files as $file) { - if (!\is_file($destination . '/' . $file)) { - continue; - } - - $content = \file_get_contents($destination . '/' . $file); - if ($content === false) { - continue; // @codeCoverageIgnore - } - - \file_put_contents($destination . '/' . $file, \str_replace('{APPNAME}', \basename($destination), $content)); + if (!\is_file($source . '/Admin/Installer.php')) { + return false; } - return true; + try { + $info = $this->loadInfo($source . '/info.json'); + $this->applications[$info->getInternalName()] = $info; + + $this->installFiles($source, $destination); + $this->replacePlaceholder($destination); + + $class = '\\Web\\' . $info->getInternalName() . '\\Admin\\Installer'; + $class::install($this->app->dbPool, $info, $this->app->appSettings); + + return true; + } catch (\Throwable $t) { + return false; // @codeCoverageIgnore + } } /** @@ -128,36 +138,28 @@ final class ApplicationManager } /** - * Install the theme + * Replace placeholder string (application placeholder name) * * @param string $destination Destination of the application - * @param string $theme Theme name * * @return void * * @since 1.0.0 */ - private function installTheme(string $destination, string $theme) : void + private function replacePlaceholder(string $destination) : void { - if (!\is_dir($path = $destination . '/Themes/' . $theme)) { - return; - } - - $dirs = \scandir($path); - foreach ($dirs as $dir) { - if (!\is_dir($path. '/' . $dir) || $dir === '.' || $dir === '..') { + $files = Directory::list($destination, '*', true); + foreach ($files as $file) { + if (!\is_file($destination . '/' . $file)) { continue; } - if (\is_dir($destination . '/' . $dir)) { - Directory::delete($destination . '/' . $dir); + $content = \file_get_contents($destination . '/' . $file); + if ($content === false) { + continue; // @codeCoverageIgnore } - Directory::copy( - $destination . '/Themes/' . $theme . '/' . $dir, - $destination . '/' . $dir, - true - ); + \file_put_contents($destination . '/' . $file, \str_replace('{APPNAME}', \basename($destination), $content)); } } } diff --git a/Application/ApplicationStatus.php b/Application/ApplicationStatus.php new file mode 100644 index 000000000..9398277c6 --- /dev/null +++ b/Application/ApplicationStatus.php @@ -0,0 +1,34 @@ +get('insert')); + $queryApp->insert('app_name', 'app_theme', 'app_status') + ->into('app') + ->values($info->getInternalName(), 'Default', ApplicationStatus::NORMAL) + ->execute(); + } + + /** + * Install the theme + * + * @param string $destination Destination of the application + * @param string $theme Theme name + * + * @return void + * + * @since 1.0.0 + */ + public static function installTheme(string $destination, string $theme) : void + { + if (!\is_dir($path = $destination . '/Themes/' . $theme)) { + return; + } + + $dirs = \scandir($path); + foreach ($dirs as $dir) { + if (!\is_dir($path. '/' . $dir) || $dir === '.' || $dir === '..') { + continue; + } + + if (\is_dir($destination . '/' . $dir)) { + Directory::delete($destination . '/' . $dir); + } + + Directory::copy( + $destination . '/Themes/' . $theme . '/' . $dir, + $destination . '/' . $dir, + true + ); + } + } + + /** + * Install app settings. + * + * @param DatabasePool $dbPool Database instance + * @param ApplicationInfo $info App info + * @param SettingsInterface $cfgHandler Settings/Configuration handler + * + * @return void + * + * @since 1.0.0 + */ + public static function installSettings(DatabasePool $dbPool, ApplicationInfo $info, SettingsInterface $cfgHandler) : void + { + $path = __DIR__ . '/../../Web/' . $info->getInternalName() . '/Admin/Install/Settings.install.php'; + if (!\is_file($path)) { + return; + } + + $settings = include $path; + + foreach ($settings as $setting) { + $cfgHandler->create($setting); + } + } + + /** + * Create tables for app. + * + * @param DatabasePool $dbPool Database instance + * @param ApplicationInfo $info App info + * + * @return void + * + * @since 1.0.0 + */ + protected static function createTables(DatabasePool $dbPool, ApplicationInfo $info) : void + { + $path = __DIR__ . '/../../Web/' . $info->getInternalName() . '/Admin/Install/db.json'; + if (!\is_file($path)) { + return; + } + + $content = \file_get_contents($path); + if ($content === false) { + return; // @codeCoverageIgnore + } + + $definitions = \json_decode($content, true); + foreach ($definitions as $definition) { + SchemaBuilder::createFromSchema($definition, $dbPool->get('schema'))->execute(); + } + } + + /** + * Activate after install. + * + * @param DatabasePool $dbPool Database instance + * @param ApplicationInfo $info App info + * + * @return void + * + * @since 1.0.0 + */ + protected static function activate(DatabasePool $dbPool, ApplicationInfo $info) : void + { + /** @var StatusAbstract $class */ + $class = '\\Web\\' . $info->getInternalName() . '\\Admin\\Status'; + $class::activate($dbPool, $info); + } + + /** + * Re-init app. + * + * @param ApplicationInfo $info App info + * + * @return void + * + * @since 1.0.0 + */ + public static function reInit(ApplicationInfo $info) : void + { + $class = '\\Web\\' . $info->getInternalName() . '\\Admin\\Status'; + $class::activateRoutes($appInfo); + $class::activateHooks($appInfo); + } +} diff --git a/Application/StatusAbstract.php b/Application/StatusAbstract.php index 7b71ded43..c82533b71 100644 --- a/Application/StatusAbstract.php +++ b/Application/StatusAbstract.php @@ -30,21 +30,295 @@ use phpOMS\DataStorage\Database\Query\Builder; abstract class StatusAbstract { /** - * Deactivate module in database. + * Deactivate app. * * @param DatabasePool $dbPool Database instance - * @param ModuleInfo $info Module info + * @param ApplicationInfo $info Module info * * @return void * * @since 1.0.0 */ - public static function deactivateInDatabase(DatabasePool $dbPool, ModuleInfo $info) : void + public static function activate(DatabasePool $dbPool, ApplicationInfo $info) : void + { + self::activateRoutes($info); + self::activateHooks($info); + self::activateInDatabase($dbPool, $info); + } + + /** + * Init routes. + * + * @param ApplicationInfo $appInfo Application info + * + * @return void + * + * @throws PermissionException + * + * @since 1.0.0 + */ + public static function activateRoutes(ApplicationInfo $appInfo = null) : void + { + self::installRoutes(__DIR__ . '/../../Web/' . $appInfo->getInternalName() . '/Routes.php', __DIR__ . '/../../Web/' . $appInfo->getInternalName() . '/Admin/Install/Application/Routes.php'); + } + + /** + * Init hooks. + * + * @param ApplicationInfo $appInfo Application info + * + * @return void + * + * @throws PermissionException + * + * @since 1.0.0 + */ + public static function activateHooks(ApplicationInfo $appInfo = null) : void + { + self::installRoutes(__DIR__ . '/../../Web/' . $appInfo->getInternalName() . '/Hooks.php', __DIR__ . '/../../Web/' . $appInfo->getInternalName() . '/Admin/Install/Application/Hooks.php'); + } + + /** + * Install routes. + * + * @param string $destRoutePath Destination route path + * @param string $srcRoutePath Source route path + * + * @return void + * + * @throws PermissionException + * + * @since 1.0.0 + */ + protected static function installRoutes(string $destRoutePath, string $srcRoutePath) : void + { + if (!\is_file($destRoutePath)) { + \file_put_contents($destRoutePath, 'getInternalName() . '/Routes.php', __DIR__ . '/../../Web/' . $appInfo->getInternalName() . '/Admin/Install/Application/Routes.php'); + } + + /** + * Deactivate hooks. + * + * @param ApplicationInfo $appInfo Application info + * + * @return void + * + * @throws PermissionException + * + * @since 1.0.0 + */ + public static function deactivateHooks(ApplicationInfo $appInfo) : void + { + self::installRoutes(__DIR__ . '/../../Web/' . $appInfo->getInternalName() . '/Hooks.php', __DIR__ . '/../../Web/' . $appInfo->getInternalName() . '/Admin/Install/Application/Hooks.php'); + } + + /** + * Uninstall routes. + * + * @param string $destRoutePath Destination route path + * @param string $srcRoutePath Source route path + * + * @return void + * + * @throws PermissionException + * + * @since 1.0.0 + */ + public static function uninstallRoutes(string $destRoutePath, string $srcRoutePath) : void + { + if (!\is_file($destRoutePath) + || !\is_file($srcRoutePath) + ) { + return; + } + + if (!\is_file($destRoutePath)) { + throw new PathException($destRoutePath); + } + + if (!\is_writable($destRoutePath)) { + throw new PermissionException($destRoutePath); + } + + /** @noinspection PhpIncludeInspection */ + $appRoutes = include $destRoutePath; + /** @noinspection PhpIncludeInspection */ + $moduleRoutes = include $srcRoutePath; + + $appRoutes = ArrayUtils::array_diff_assoc_recursive($appRoutes, $moduleRoutes); + + \file_put_contents($destRoutePath, 'get('update')); $query->update('app') - ->sets('app.app_active', ModuleStatus::INACTIVE) - ->where('app.app_id', '=', $info->getInternalName()) + ->sets('app.app_status', ApplicationStatus::NORMAL) + ->where('app.app_name', '=', $info->getInternalName()) + ->execute(); + } + + /** + * Deactivate app. + * + * @param DatabasePool $dbPool Database instance + * @param ApplicationInfo $info Module info + * + * @return void + * + * @since 1.0.0 + */ + public static function deactivate(DatabasePool $dbPool, ApplicationInfo $info) : void + { + self::deactivateRoutes($info); + self::deactivateHooks($info); + self::deactivateInDatabase($dbPool, $info); + } + + /** + * Deactivate app in database. + * + * @param DatabasePool $dbPool Database instance + * @param ApplicationInfo $info Module info + * + * @return void + * + * @since 1.0.0 + */ + public static function deactivateInDatabase(DatabasePool $dbPool, ApplicationInfo $info) : void + { + $query = new Builder($dbPool->get('update')); + $query->update('app') + ->sets('app.app_status', ApplicationStatus::DISABLED) + ->where('app.app_name', '=', $info->getInternalName()) ->execute(); } } diff --git a/Application/UninstallerAbstract.php b/Application/UninstallerAbstract.php new file mode 100644 index 000000000..2f900ff79 --- /dev/null +++ b/Application/UninstallerAbstract.php @@ -0,0 +1,116 @@ +getInternalName() . '\Admin\Status'; + $class::deactivate($dbPool, $info); + } + + /** + * Drop tables of app. + * + * @param DatabasePool $dbPool Database instance + * @param ApplicationInfo $info App info + * + * @return void + * + * @since 1.0.0 + */ + public static function dropTables(DatabasePool $dbPool, ApplicationInfo $info) : void + { + $path = __DIR__ . '/../../Web/' . $info->getInternalName() . '/Admin/Install/db.json'; + + if (!\is_file($path)) { + return; + } + + $content = \file_get_contents($path); + if ($content === false) { + return; // @codeCoverageIgnore + } + + $definitions = \json_decode($content, true); + $builder = new SchemaBuilder($dbPool->get('schema')); + + foreach ($definitions as $definition) { + $builder->dropTable($definition['table'] ?? ''); + } + + $builder->execute(); + } + + /** + * Unregister app from database. + * + * @param DatabasePool $dbPool Database instance + * @param ApplicationInfo $info App info + * + * @return void + * + * @since 1.0.0 + */ + public static function unregisterFromDatabase(DatabasePool $dbPool, ApplicationInfo $info) : void + { + $queryApp = new Builder($dbPool->get('delete')); + $queryApp->delete() + ->from('app') + ->where('app_name', '=', $info->getInternalName()) + ->execute(); + } +}