*/ 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 $input * @return array */ 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> $apps * @return array> */ 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> $widgets * @return array> */ 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 */ 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 $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 = "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); } }