236 lines
9.4 KiB
PHP
236 lines
9.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App;
|
|
|
|
use Desktop\AppRegistry;
|
|
use Desktop\WidgetRegistry;
|
|
use ModulesCore\ModuleRegistry;
|
|
use SystemAddons\GiteaDeployStatus\GiteaDeployStatusService;
|
|
|
|
final class AdminAppsService
|
|
{
|
|
public function __construct(
|
|
private readonly string $projectRoot,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function bootstrap(bool $isAdmin): array
|
|
{
|
|
require_once AppPaths::systemAddonPath($this->projectRoot, 'gitea-deploy-status') . '/GiteaDeployStatusService.php';
|
|
|
|
$moduleRegistry = new ModuleRegistry(AppPaths::customAppsRoot($this->projectRoot));
|
|
$appRegistry = new AppRegistry($this->projectRoot . '/config/apps.php', $moduleRegistry->desktopApps());
|
|
$widgetRegistry = new WidgetRegistry($this->projectRoot . '/config/widgets.php', $moduleRegistry->widgets());
|
|
|
|
$apps = $this->normalizeApps($appRegistry->all());
|
|
$widgets = $this->normalizeWidgets($widgetRegistry->all());
|
|
$giteaConfig = ConfigLoader::load($this->projectRoot, 'gitea');
|
|
$giteaService = new GiteaDeployStatusService($giteaConfig);
|
|
$currentEnvironment = ConfigLoader::currentEnvironment();
|
|
|
|
return [
|
|
'meta' => [
|
|
'environment' => $currentEnvironment,
|
|
'gitea_config_path' => $this->environmentConfigPath('gitea', $currentEnvironment),
|
|
],
|
|
'stats' => [
|
|
'apps_total' => count($apps),
|
|
'installable_apps' => count(array_filter($apps, static fn (array $app): bool => !empty($app['installable']))),
|
|
'system_tools' => count(array_filter($apps, static fn (array $app): bool => ($app['app_scope'] ?? '') === 'system_tool')),
|
|
'widgets_total' => count($widgets),
|
|
'admin_only_apps' => count(array_filter($apps, static fn (array $app): bool => !empty($app['admin_only']))),
|
|
],
|
|
'apps' => $apps,
|
|
'widgets' => $widgets,
|
|
'gitea' => [
|
|
'config' => [
|
|
'base_url' => (string) ($giteaConfig['base_url'] ?? ''),
|
|
'token' => (string) ($giteaConfig['token'] ?? ''),
|
|
'owner' => (string) ($giteaConfig['owner'] ?? ''),
|
|
'repositories' => array_values(array_map('strval', (array) ($giteaConfig['repositories'] ?? []))),
|
|
'poll_idle_seconds' => max(1, (int) ($giteaConfig['poll_idle_seconds'] ?? 5)),
|
|
'poll_running_seconds' => max(1, (int) ($giteaConfig['poll_running_seconds'] ?? 3)),
|
|
'visibility' => (string) ($giteaConfig['visibility'] ?? 'admins'),
|
|
],
|
|
'diagnostics' => $giteaService->diagnostics(),
|
|
'tray_visible_for_current_user' => $giteaService->shouldShowInTray($isAdmin),
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $input
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function saveGiteaConfig(array $input): array
|
|
{
|
|
$environment = ConfigLoader::currentEnvironment();
|
|
$config = [
|
|
'base_url' => $this->sanitizeBaseUrl((string) ($input['base_url'] ?? '')),
|
|
'token' => trim((string) ($input['token'] ?? '')),
|
|
'owner' => trim((string) ($input['owner'] ?? '')),
|
|
'repositories' => $this->sanitizeRepositories((string) ($input['repositories'] ?? '')),
|
|
'poll_idle_seconds' => $this->sanitizePositiveInt($input['poll_idle_seconds'] ?? 5, 5),
|
|
'poll_running_seconds' => $this->sanitizePositiveInt($input['poll_running_seconds'] ?? 3, 3),
|
|
'visibility' => $this->sanitizeVisibility((string) ($input['visibility'] ?? 'admins')),
|
|
];
|
|
|
|
$this->writeEnvironmentConfig('gitea', $environment, $config);
|
|
|
|
return $config;
|
|
}
|
|
|
|
/**
|
|
* @param array<int, array<string, mixed>> $apps
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function normalizeApps(array $apps): array
|
|
{
|
|
$normalized = array_map(static function (array $app): array {
|
|
$requiredGroups = array_values(array_map('strval', (array) ($app['required_groups'] ?? [])));
|
|
|
|
return [
|
|
'app_id' => (string) ($app['app_id'] ?? ''),
|
|
'title' => (string) ($app['title'] ?? ''),
|
|
'summary' => trim((string) ($app['summary'] ?? '')),
|
|
'entry_route' => (string) ($app['entry_route'] ?? ''),
|
|
'app_scope' => (string) ($app['app_scope'] ?? 'module'),
|
|
'module_name' => (string) ($app['module_name'] ?? ''),
|
|
'installable' => !array_key_exists('installable', $app) || (bool) $app['installable'],
|
|
'show_on_desktop' => !empty($app['show_on_desktop']),
|
|
'supports_widget' => !empty($app['supports_widget']),
|
|
'supports_tray' => !empty($app['supports_tray']),
|
|
'enabled_by_default' => !empty($app['enabled_by_default']),
|
|
'required_groups' => $requiredGroups,
|
|
'admin_only' => in_array('administrators', array_map(
|
|
static fn (string $group): string => strtolower(trim($group, '/')),
|
|
$requiredGroups
|
|
), true),
|
|
'source' => isset($app['module_id']) ? 'custom_app' : 'system',
|
|
];
|
|
}, $apps);
|
|
|
|
usort($normalized, static fn (array $left, array $right): int => [$left['title'], $left['app_id']] <=> [$right['title'], $right['app_id']]);
|
|
|
|
return $normalized;
|
|
}
|
|
|
|
/**
|
|
* @param array<int, array<string, mixed>> $widgets
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function normalizeWidgets(array $widgets): array
|
|
{
|
|
$normalized = array_map(static function (array $widget): array {
|
|
return [
|
|
'widget_id' => (string) ($widget['widget_id'] ?? ''),
|
|
'title' => (string) ($widget['title'] ?? ''),
|
|
'summary' => trim((string) ($widget['summary'] ?? '')),
|
|
'zone' => (string) ($widget['zone'] ?? 'sidebar'),
|
|
'default_enabled' => !empty($widget['default_enabled']),
|
|
'source_app_id' => (string) ($widget['source_app_id'] ?? ''),
|
|
'source_module_id' => (string) ($widget['source_module_id'] ?? ''),
|
|
'launch_app_id' => (string) ($widget['launch_app_id'] ?? ''),
|
|
'status_api' => isset($widget['status_api']) ? (string) $widget['status_api'] : '',
|
|
];
|
|
}, $widgets);
|
|
|
|
usort($normalized, static fn (array $left, array $right): int => [$left['title'], $left['widget_id']] <=> [$right['title'], $right['widget_id']]);
|
|
|
|
return $normalized;
|
|
}
|
|
|
|
private function sanitizeBaseUrl(string $value): string
|
|
{
|
|
return rtrim(trim($value), '/');
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
private function sanitizeRepositories(string $value): array
|
|
{
|
|
$entries = preg_split('/[\r\n,;]+/', $value) ?: [];
|
|
$result = [];
|
|
|
|
foreach ($entries as $entry) {
|
|
$normalized = trim($entry);
|
|
if ($normalized === '' || !preg_match('/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/', $normalized)) {
|
|
continue;
|
|
}
|
|
|
|
$result[$normalized] = $normalized;
|
|
}
|
|
|
|
return array_values($result);
|
|
}
|
|
|
|
private function sanitizePositiveInt(mixed $value, int $fallback): int
|
|
{
|
|
$normalized = (int) $value;
|
|
|
|
return $normalized > 0 ? $normalized : $fallback;
|
|
}
|
|
|
|
private function sanitizeVisibility(string $value): string
|
|
{
|
|
$normalized = strtolower(trim($value));
|
|
|
|
return in_array($normalized, ['all', 'admins', 'disabled'], true) ? $normalized : 'admins';
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $config
|
|
*/
|
|
private function writeEnvironmentConfig(string $name, string $environment, array $config): void
|
|
{
|
|
$directory = $this->projectRoot . '/config/' . $environment;
|
|
if (!is_dir($directory) && !mkdir($directory, 0775, true) && !is_dir($directory)) {
|
|
throw new \RuntimeException('Konfigurationsverzeichnis konnte nicht angelegt werden.');
|
|
}
|
|
|
|
$path = $this->environmentConfigPath($name, $environment);
|
|
$content = "<?php\n\n";
|
|
$content .= "declare(strict_types=1);\n\n";
|
|
$content .= 'return ' . $this->exportPhpValue($config) . ";\n";
|
|
|
|
if (file_put_contents($path, $content) === false) {
|
|
throw new \RuntimeException('Konfigurationsdatei konnte nicht geschrieben werden.');
|
|
}
|
|
}
|
|
|
|
private function environmentConfigPath(string $name, string $environment): string
|
|
{
|
|
return $this->projectRoot . '/config/' . $environment . '/' . $name . '.php';
|
|
}
|
|
|
|
private function exportPhpValue(mixed $value, int $depth = 0): string
|
|
{
|
|
if (is_array($value)) {
|
|
if ($value === []) {
|
|
return '[]';
|
|
}
|
|
|
|
$indent = str_repeat(' ', $depth);
|
|
$childIndent = str_repeat(' ', $depth + 1);
|
|
$lines = ['['];
|
|
|
|
foreach ($value as $key => $item) {
|
|
$prefix = is_int($key) ? '' : $this->exportPhpValue($key) . ' => ';
|
|
$lines[] = $childIndent . $prefix . $this->exportPhpValue($item, $depth + 1) . ',';
|
|
}
|
|
|
|
$lines[] = $indent . ']';
|
|
|
|
return implode("\n", $lines);
|
|
}
|
|
|
|
return var_export($value, true);
|
|
}
|
|
}
|