diff --git a/kernel/App.php b/kernel/App.php index 9447b4b..7ce9803 100644 --- a/kernel/App.php +++ b/kernel/App.php @@ -42,7 +42,6 @@ class App public function load(): static { $this->moduleService = new ModuleService(); -// $this->themeService = new ThemeService(); App::$collector = new CgRouteCollector(); $this->setRouting(); @@ -57,10 +56,6 @@ class App foreach ($modules_routs as $rout){ include "$rout"; } -// $activeTheme = getConst($this->themeService->getActiveTheme()); -// if (!empty($activeTheme)){ -// include $activeTheme . "/" . $this->themeService->getThemeRout($activeTheme); -// } $themeService = new ThemeService(); $activeTheme = getConst($themeService->getActiveTheme()); diff --git a/kernel/admin_themes/default/manifest.json b/kernel/admin_themes/default/manifest.json index 8bce176..2da3b4f 100644 --- a/kernel/admin_themes/default/manifest.json +++ b/kernel/admin_themes/default/manifest.json @@ -3,6 +3,7 @@ "version": "0.1", "author": "ItGuild", "slug": "default", + "type": "admin_theme", "description": "Default admin theme", "preview": "preview.png", "resource": "/resources/admin_themes/default", diff --git a/kernel/console/controllers/ThemeController.php b/kernel/console/controllers/ThemeController.php new file mode 100644 index 0000000..1c3d762 --- /dev/null +++ b/kernel/console/controllers/ThemeController.php @@ -0,0 +1,73 @@ +argv['path'])) { + throw new \Exception('Missing theme path "--path" specified'); + } + + if (file_exists(ROOT_DIR . $this->argv['path'])) { + $themeService = new ThemeService(); + if ($themeService->install($this->argv['path'])) { + $this->out->r("Тема сайта установлена", 'green'); + } else { + $this->out->r("Ошибка установки темы сайта", 'red'); + } + } else { + $this->out->r("Тема сайта не найдена", 'red'); + } + } + + /** + * @throws \Exception + */ + public function actionUninstallTheme(): void + { + if (!isset($this->argv['path'])) { + throw new \Exception('Missing theme path "--path" specified'); + } + + if (file_exists(ROOT_DIR . $this->argv['path'])) { + $themeService = new ThemeService(); + $themeService->uninstall($this->argv['path']); + $this->out->r("Тема сайта удалена", 'green'); + } else { + $this->out->r("Тема сайта не найдена", 'red'); + } + } + + /** + * @throws \Exception + */ + public function actionPackTheme(): void + { + if (!isset($this->argv['path'])) { + throw new \Exception('Missing theme path "--path" specified'); + } + + if (file_exists(ROOT_DIR . $this->argv['path'])) { + $themeService = new ThemeService(); + $themeService->pack($this->argv['path']); + $this->out->r("Тема сайта заархивирована", 'green'); + } else { + $this->out->r("Тема сайта не найдена", 'red'); + } + } + +} \ No newline at end of file diff --git a/kernel/console/routs/cli.php b/kernel/console/routs/cli.php index 4676cc7..68f06c7 100644 --- a/kernel/console/routs/cli.php +++ b/kernel/console/routs/cli.php @@ -41,6 +41,21 @@ App::$collector->group(["prefix" => "admin-theme"], callback: function (RouteCol ); }); +App::$collector->group(["prefix" => "theme"], callback: function (RouteCollector $router){ + App::$collector->console('install', + [\kernel\console\controllers\ThemeController::class, 'actionInstallTheme'], + additionalInfo: ['description' => 'Установить тему сайта', 'params' => ['--path' => 'Путь к устанавливаемой теме']] + ); + App::$collector->console('uninstall', + [\kernel\console\controllers\ThemeController::class, 'actionUninstallTheme'], + additionalInfo: ['description' => 'Удалить тему сайта', 'params' => ['--path' => 'Путь к удаляемой теме']] + ); + App::$collector->console('pack', + [\kernel\console\controllers\ThemeController::class, 'actionPackTheme'], + additionalInfo: ['description' => 'Заархивировать тему сайта', 'params' => ['--path' => 'Путь к теме, которую нужно заархивировать']] + ); +}); + App::$collector->group(["prefix" => "secure"], callback: function (RouteCollector $router){ App::$collector->console('create-secret-key', [\kernel\console\controllers\SecureController::class, 'actionCreateSecretKey'], diff --git a/kernel/modules/module_shop_client/controllers/ModuleShopClientController.php b/kernel/modules/module_shop_client/controllers/ModuleShopClientController.php index e6a7a75..e856224 100644 --- a/kernel/modules/module_shop_client/controllers/ModuleShopClientController.php +++ b/kernel/modules/module_shop_client/controllers/ModuleShopClientController.php @@ -17,6 +17,7 @@ use kernel\services\AdminThemeService; use kernel\services\KernelService; use kernel\services\ModuleService; use kernel\services\ModuleShopService; +use kernel\services\ThemeService; use PHPMailer\PHPMailer\Exception; class ModuleShopClientController extends AdminController @@ -60,6 +61,7 @@ class ModuleShopClientController extends AdminController 'per_page' => $per_page, 'kernelService' => new KernelService(), 'adminThemeService' => new AdminThemeService(), + 'themeService' => new ThemeService(), ]); } else { $this->cgView->render("module_shop_error_connection.php"); @@ -295,4 +297,56 @@ class ModuleShopClientController extends AdminController $this->redirect('/admin/module_shop_client', 302); } + #[NoReturn] public function actionThemeInstall(): void + { + $request = new Request(); + $id = $request->get("id"); + $themeInfo = RESTClient::request($_ENV['MODULE_SHOP_URL'] . '/api/module_shop/install/' . $id); + + $themeInfo = json_decode($themeInfo->getBody()->getContents(), true); + Files::uploadByUrl($_ENV['MODULE_SHOP_URL'] . $themeInfo['path_to_archive'], RESOURCES_DIR . "/tmp/themes"); + if ($this->themeService->install('/resources/tmp/themes/' . basename($themeInfo['path_to_archive']))) { + Flash::setMessage("success", "Тема сайта успешно установлена."); + } else { + Session::start(); + Session::set("error", implode(";", $this->themeService->getErrors())); + } + + $this->redirect('/admin/module_shop_client', 302); + } + + #[NoReturn] public function actionThemeUpdate(): void + { + $request = new Request(); + $slug = $request->get("slug"); + $modules_info = RESTClient::request($_ENV['MODULE_SHOP_URL'] . '/api/module_shop/gb_slug'); + + $modules_info = json_decode($modules_info->getBody()->getContents(), true); + foreach ($modules_info as $module) { + if ($module['slug'] === $slug) { + $path = $module['path_to_archive']; + } + } + if (isset($path)) { + Files::uploadByUrl($_ENV['MODULE_SHOP_URL'] . $path, RESOURCES_DIR . "/tmp/themes"); + $this->themeService->update('/resources/tmp/themes/' . basename($path)); + Flash::setMessage("success", "Тема сайта успешно обновлена."); + } else { + Flash::setMessage("error", "Ошибка обновления темы сайта."); + } + + $this->redirect('/admin/module_shop_client', 302); + } + + #[NoReturn] public function actionThemeDelete(): void + { + $request = new Request(); + $slug = $request->get("slug"); + $themeInfo = $this->themeService->getThemeInfoBySlug($slug); + $this->themeService->uninstall($themeInfo['path']); + + Flash::setMessage("success", "Тема сайта успешно удалена."); + $this->redirect('/admin/module_shop_client', 302); + } + } \ No newline at end of file diff --git a/kernel/modules/module_shop_client/routs/module_shop_client.php b/kernel/modules/module_shop_client/routs/module_shop_client.php index 52647d2..9c98e9a 100644 --- a/kernel/modules/module_shop_client/routs/module_shop_client.php +++ b/kernel/modules/module_shop_client/routs/module_shop_client.php @@ -26,7 +26,11 @@ App::$collector->group(["prefix" => "admin"], function (RouteCollector $router){ App::$collector->get('/install', [\kernel\modules\module_shop_client\controllers\ModuleShopClientController::class, 'actionAdminThemeInstall']); App::$collector->get('/update', [\kernel\modules\module_shop_client\controllers\ModuleShopClientController::class, 'actionAdminThemeUpdate']); App::$collector->get('/delete', [\kernel\modules\module_shop_client\controllers\ModuleShopClientController::class, 'actionAdminThemeDelete']); - + }); + App::$collector->group(["prefix" => "theme"], function (RouteCollector $router) { + App::$collector->get('/install', [\kernel\modules\module_shop_client\controllers\ModuleShopClientController::class, 'actionThemeInstall']); + App::$collector->get('/update', [\kernel\modules\module_shop_client\controllers\ModuleShopClientController::class, 'actionThemeUpdate']); + App::$collector->get('/delete', [\kernel\modules\module_shop_client\controllers\ModuleShopClientController::class, 'actionThemeDelete']); }); }); }); diff --git a/kernel/modules/module_shop_client/views/index.php b/kernel/modules/module_shop_client/views/index.php index e0d1c8c..010e158 100644 --- a/kernel/modules/module_shop_client/views/index.php +++ b/kernel/modules/module_shop_client/views/index.php @@ -7,6 +7,7 @@ * @var \kernel\services\ModuleService $moduleService * @var \kernel\services\KernelService $kernelService * @var \kernel\services\AdminThemeService $adminThemeService + * @var \kernel\services\ThemeService $themeService * @var array $filterValues */ @@ -130,6 +131,37 @@ $table->addAction(function ($row, $url) use ($adminThemeService) { return false; }); +$table->addAction(function ($row, $url) use ($themeService) { + if ($row['type'] === 'theme') { + $slug = $row['slug']; + if ($themeService->isInstall($slug)) { + if (!$themeService->isLastVersion($slug)) { + $url = "$url/theme/update/?slug=" . $row['slug']; + + return \kernel\widgets\IconBtn\IconBtnUpdateWidget::create(['url' => $url])->run(); + } + } + } + + return false; +}); + +$table->addAction(function ($row, $url) use ($themeService) { + if ($row['type'] === 'theme') { + if ($themeService->isInstall($row['slug'])) { + $url = "$url/theme/delete/?slug=" . $row['slug']; + + return \kernel\widgets\IconBtn\IconBtnDeleteWidget::create(['url' => $url])->run(); + } else { + $url = "$url/theme/install/?id=" . $row['id']; + + return \kernel\widgets\IconBtn\IconBtnInstallWidget::create(['url' => $url])->run(); + } + } + + return false; +}); + $table->afterPrint(function () { return \kernel\IGTabel\btn\PrimaryBtn::create('Сбросить все фильтры', '/admin/module_shop_client')->fetch(); }); diff --git a/kernel/services/AdminThemeService.php b/kernel/services/AdminThemeService.php index 0d41286..e01ef1c 100644 --- a/kernel/services/AdminThemeService.php +++ b/kernel/services/AdminThemeService.php @@ -16,6 +16,7 @@ class AdminThemeService protected Option $option; protected string $active_theme; + protected ModuleService $moduleService; @@ -151,7 +152,7 @@ class AdminThemeService $fileHelper = new Files(); $fileHelper->copy_folder(ROOT_DIR . $path, $tmpThemeDirFull . 'meta/'); - $fileHelper->copy_folder(RESOURCES_DIR . '/' . $themeName, $tmpThemeDirFull . 'resources/'); + $fileHelper->copy_folder(RESOURCES_DIR . '/admin_themes/' . $themeName, $tmpThemeDirFull . 'resources/'); if (!is_dir(RESOURCES_DIR . '/tmp/admin_themes')) { $old_mask = umask(0); @@ -193,7 +194,7 @@ class AdminThemeService if (isset($manifest['resource_path'])) { $fileHelper->copy_folder($tmpThemeDirFull . "resources", $manifest['resource_path']); } else { - $fileHelper->copy_folder($tmpThemeDirFull . "resources", RESOURCES_DIR . '/' . $manifest['slug']); + $fileHelper->copy_folder($tmpThemeDirFull . "resources", RESOURCES_DIR . '/admin_themes/' . $manifest['slug']); } $fileHelper->recursiveRemoveDir($tmpThemeDirFull); @@ -213,8 +214,8 @@ class AdminThemeService if (file_exists($path)) { $fileHelper->recursiveRemoveDir($path); } - if (file_exists(RESOURCES_DIR . '/' . $themeInfo['slug'])) { - $fileHelper->recursiveRemoveDir(RESOURCES_DIR . '/' . $themeInfo['slug']); + if (file_exists(RESOURCES_DIR . '/admin_themes/' . $themeInfo['slug'])) { + $fileHelper->recursiveRemoveDir(RESOURCES_DIR . '/admin_themes/' . $themeInfo['slug']); } } diff --git a/kernel/services/ThemeService.php b/kernel/services/ThemeService.php index 598e3cd..5bfaa9d 100644 --- a/kernel/services/ThemeService.php +++ b/kernel/services/ThemeService.php @@ -3,6 +3,7 @@ namespace kernel\services; use DirectoryIterator; +use kernel\helpers\Files; use kernel\helpers\Manifest; use kernel\helpers\RESTClient; use kernel\models\Option; @@ -14,10 +15,14 @@ class ThemeService protected Option $option; protected string $active_theme = ""; + protected ModuleService $moduleService; + + public function __construct() { $this->option = new Option(); $this->findActiveTheme(); + $this->moduleService = new ModuleService(); } /** @@ -103,6 +108,123 @@ class ThemeService return $dirs; } + public function isInstall(string $slug): bool + { + $dirs = $this->getThemeDirs(); + foreach ($dirs as $dir) { + foreach (new DirectoryIterator($dir) as $fileInfo) { + if ($fileInfo->isDot()) continue; + if ($this->getThemeInfo($fileInfo->getPathname())['slug'] === $slug) { + return true; + } + } + } + + return false; + } + + public function isLastVersion(string $slug): bool + { + if ($this->moduleService->isServerAvailable()) { + $modulesInfo = RESTClient::request($_ENV['MODULE_SHOP_URL'] . '/api/module_shop/gb_slug'); + + $modulesInfo = json_decode($modulesInfo->getBody()->getContents(), true); + + $themeInfo = $this->getThemeInfoBySlug($slug); + foreach ($modulesInfo as $mod) { + if ($mod['slug'] === $themeInfo['slug'] && $mod['version'] === $themeInfo['version']) { + return true; + } + } + } + + return false; + } + + public function pack(string $path): void + { + $themeName = basename($path); + + $tmpThemeDirFull = RESOURCES_DIR . '/tmp/ad/' . $themeName . "/"; + + $fileHelper = new Files(); + $fileHelper->copy_folder(ROOT_DIR . $path, $tmpThemeDirFull . 'meta/'); + $fileHelper->copy_folder(RESOURCES_DIR . '/themes/' . $themeName, $tmpThemeDirFull . 'resources/'); + + if (!is_dir(RESOURCES_DIR . '/tmp/themes')) { + $old_mask = umask(0); + mkdir(RESOURCES_DIR . '/tmp/themes', 0775, true); + umask($old_mask); + } + $fileHelper->pack($tmpThemeDirFull, RESOURCES_DIR . '/tmp/themes/' . $themeName . '.igt'); + } + + public function install(string $path): bool + { + $zip = new ZipArchive; + $tmpThemeDir = md5(time()); + $res = $zip->open(ROOT_DIR . $path); + if ($res === TRUE) { + $tmpThemeDirFull = RESOURCES_DIR . '/tmp/ad/' . $tmpThemeDir . "/"; + $zip->extractTo($tmpThemeDirFull); + $zip->close(); + } else { + $this->addError('unable to open zip archive'); + return false; + } + + if (!file_exists($tmpThemeDirFull . "meta/manifest.json")){ + $this->addError('manifest.json not found'); + return false; + } + + $manifestJson = getConst(file_get_contents($tmpThemeDirFull . "meta/manifest.json")); + $manifest = Manifest::getWithVars($manifestJson); + + $fileHelper = new Files(); + if (isset($manifest['theme_path'])) { + $fileHelper->copy_folder($tmpThemeDirFull . "meta", $manifest['theme_path']); + } else { + $fileHelper->copy_folder($tmpThemeDirFull . "meta", APP_DIR . '/themes/' . $manifest['slug']); + } + + if (isset($manifest['resource_path'])) { + $fileHelper->copy_folder($tmpThemeDirFull . "resources", $manifest['resource_path']); + } else { + $fileHelper->copy_folder($tmpThemeDirFull . "resources", RESOURCES_DIR . '/themes/' . $manifest['slug']); + } + + $fileHelper->recursiveRemoveDir($tmpThemeDirFull); + unlink(ROOT_DIR . $path); + return true; + } + + public function uninstall(string $path): void + { + $themeInfo = $this->getThemeInfo(APP_DIR . '/themes/' . basename($path)); + + $active_theme = $this->option::where("key", "active_theme")->first(); + if ($active_theme->value === ROOT_DIR . $path) { + $this->setActiveTheme(KERNEL_DIR . '/themes/default'); + } + $fileHelper = new Files(); + if (file_exists($path)) { + $fileHelper->recursiveRemoveDir($path); + } + if (file_exists(RESOURCES_DIR . '/themes/' . $themeInfo['slug'])) { + $fileHelper->recursiveRemoveDir(RESOURCES_DIR . '/themes/' . $themeInfo['slug']); + } + } + + public function update(string $path): bool + { + if ($this->install($path)) { + return true; + } + + return false; + } + public function getThemeRout(string $path) { if (file_exists($path . "/manifest.json")){ diff --git a/kernel/themes/default/manifest.json b/kernel/themes/default/manifest.json index 04a2fec..05f98bc 100644 --- a/kernel/themes/default/manifest.json +++ b/kernel/themes/default/manifest.json @@ -1,10 +1,11 @@ { "name": "Default", - "slug": "default", "version": "0.1", "author": "ItGuild", - "preview": "preview.png", + "slug": "default", + "type": "theme", "description": "Default theme", + "preview": "preview.png", "resource": "/resources/themes/default", "resource_path": "{RESOURCES}/themes/default", "routs": "routs/default.php" diff --git a/kernel/themes/default/views/layout/main.php b/kernel/themes/default/views/layout/main.php index 3ca6288..824482e 100644 --- a/kernel/themes/default/views/layout/main.php +++ b/kernel/themes/default/views/layout/main.php @@ -31,7 +31,7 @@ $assets = new \kernel\themes\default\assets\DefaultThemesAssets($resources);