finalize app installation draft

This commit is contained in:
Dennis Eichhorn 2021-09-20 20:15:15 +02:00
parent ca537eda92
commit e8cae23ca9
5 changed files with 659 additions and 43 deletions

View File

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

View File

@ -0,0 +1,34 @@
<?php
/**
* Orange Management
*
* PHP Version 8.0
*
* @package phpOMS\Application
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://orange-management.org
*/
declare(strict_types=1);
namespace phpOMS\Application;
use phpOMS\Stdlib\Base\Enum;
/**
* App status enum.
*
* @package phpOMS\Application
* @license OMS License 1.0
* @link https://orange-management.org
* @since 1.0.0
*/
abstract class ApplicationStatus extends Enum
{
public const NORMAL = 1;
public const READ_ONLY = 2;
public const DISABLED = 3;
}

View File

@ -0,0 +1,190 @@
<?php
/**
* Orange Management
*
* PHP Version 8.0
*
* @package phpOMS\Application
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://orange-management.org
*/
declare(strict_types=1);
namespace phpOMS\Application;
use phpOMS\Application\ApplicationInfo;
use phpOMS\Config\SettingsInterface;
use phpOMS\DataStorage\Database\DatabasePool;
use phpOMS\DataStorage\Database\Query\Builder;
use phpOMS\DataStorage\Database\Schema\Builder as SchemaBuilder;
/**
* Installer abstract class.
*
* @package phpOMS\Application
* @license OMS License 1.0
* @link https://orange-management.org
* @since 1.0.0
*/
abstract class InstallerAbstract
{
/**
* Install app.
*
* @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 install(DatabasePool $dbPool, ApplicationInfo $info, SettingsInterface $cfgHandler) : void
{
self::createTables($dbPool, $info);
self::registerInDatabase($dbPool, $info);
self::installSettings($dbPool, $info, $cfgHandler);
self::activate($dbPool, $info);
}
/**
* Register app in database.
*
* @param DatabasePool $dbPool Database instance
* @param ApplicationInfo $info App info
*
* @return void
*
* @since 1.0.0
*/
public static function registerInDatabase(DatabasePool $dbPool, ApplicationInfo $info) : void
{
$queryApp = new Builder($dbPool->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);
}
}

View File

@ -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, '<?php return [];');
}
if (!\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 = \array_merge_recursive($appRoutes, $moduleRoutes);
\file_put_contents($destRoutePath, '<?php return ' . ArrayParser::serializeArray($appRoutes) . ';', \LOCK_EX);
}
/**
* Install hooks.
*
* @param string $destHookPath Destination hook path
* @param string $srcHookPath Source hook path
*
* @return void
*
* @throws PathException This exception is thrown if the hook file doesn't exist
* @throws PermissionException This exception is thrown if the hook file couldn't be updated (no write permission)
*
* @since 1.0.0
*/
protected static function installHooks(string $destHookPath, string $srcHookPath) : void
{
if (!\is_file($destHookPath)) {
\file_put_contents($destHookPath, '<?php return [];');
}
if (!\is_file($srcHookPath)) {
return;
}
if (!\is_file($destHookPath)) {
throw new PathException($destHookPath);
}
if (!\is_writable($destHookPath)) {
throw new PermissionException($destHookPath);
}
/** @noinspection PhpIncludeInspection */
$appHooks = include $destHookPath;
/** @noinspection PhpIncludeInspection */
$moduleHooks = include $srcHookPath;
$appHooks = \array_merge_recursive($appHooks, $moduleHooks);
\file_put_contents($destHookPath, '<?php return ' . ArrayParser::serializeArray($appHooks) . ';', \LOCK_EX);
}
/**
* Deactivate routes.
*
* @param ApplicationInfo $appInfo Application info
*
* @return void
*
* @throws PermissionException
*
* @since 1.0.0
*/
public static function deactivateRoutes(ApplicationInfo $appInfo) : void
{
self::installRoutes(__DIR__ . '/../../Web/' . $appInfo->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, '<?php return ' . ArrayParser::serializeArray($appRoutes) . ';', \LOCK_EX);
}
/**
* Uninstall hooks.
*
* @param string $destHookPath Destination hook path
* @param string $srcHookPath Source hook path
*
* @return void
*
* @throws PermissionException
*
* @since 1.0.0
*/
protected static function uninstallHooks(string $destHookPath, string $srcHookPath) : void
{
if (!\is_file($destHookPath)
|| !\is_file($srcHookPath)
) {
return;
}
if (!\is_file($destHookPath)) {
throw new PathException($destHookPath);
}
if (!\is_writable($destHookPath)) {
throw new PermissionException($destHookPath);
}
/** @noinspection PhpIncludeInspection */
$appHooks = include $destHookPath;
/** @noinspection PhpIncludeInspection */
$moduleHooks = include $srcHookPath;
$appHooks = ArrayUtils::array_diff_assoc_recursive($appHooks, $moduleHooks);
\file_put_contents($destHookPath, '<?php return ' . ArrayParser::serializeArray($appHooks) . ';', \LOCK_EX);
}
/**
* Deactivate app in database.
*
* @param DatabasePool $dbPool Database instance
* @param ApplicationInfo $info Module info
*
* @return void
*
* @since 1.0.0
*/
public static function activateInDatabase(DatabasePool $dbPool, ApplicationInfo $info) : void
{
$query = new Builder($dbPool->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();
}
}

View File

@ -0,0 +1,116 @@
<?php
/**
* Orange Management
*
* PHP Version 8.0
*
* @package phpOMS\Application
* @copyright Dennis Eichhorn
* @license OMS License 1.0
* @version 1.0.0
* @link https://orange-management.org
*/
declare(strict_types=1);
namespace phpOMS\Application;
use phpOMS\DataStorage\Database\DatabasePool;
use phpOMS\DataStorage\Database\Query\Builder;
use phpOMS\DataStorage\Database\Schema\Builder as SchemaBuilder;
/**
* Uninstaller abstract class.
*
* @package phpOMS\Application
* @license OMS License 1.0
* @link https://orange-management.org
* @since 1.0.0
*/
abstract class UninstallerAbstract
{
/**
* Install module.
*
* @param DatabasePool $dbPool Database instance
* @param ApplicationInfo $info App info
*
* @return void
*
* @since 1.0.0
*/
public static function uninstall(DatabasePool $dbPool, ApplicationInfo $info) : void
{
self::deactivate($dbPool, $info);
self::dropTables($dbPool, $info);
self::unregisterFromDatabase($dbPool, $info);
}
/**
* Activate after install.
*
* @param DatabasePool $dbPool Database instance
* @param ApplicationInfo $info App info
*
* @return void
*
* @since 1.0.0
*/
protected static function deactivate(DatabasePool $dbPool, ApplicationInfo $info) : void
{
/** @var StatusAbstract $class */
$class = '\Web\\' . $info->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();
}
}