diff --git a/lang/de_de.lang b/lang/de_de.lang index ea454cf..653b0b1 100644 --- a/lang/de_de.lang +++ b/lang/de_de.lang @@ -133,11 +133,24 @@ settings_saved = Einstellungen erfolgreich gespeichert upload = Upload upload_module = Modul "{0}" hochladen upload_template = Template "{0}" hochladen +upload_class = Klasse hochladen upload_success = "{0}" wurde erfolgreich zu "{1}" hochgeladen upload_error = Fehler beim Hochladen upload_missing_data = Bitte alle Pflichtfelder ausfüllen no_local_modules = Keine lokalen Module gefunden no_local_templates = Keine lokalen Templates gefunden +no_local_classes = Keine lokalen Klassen gefunden +upload_no_repos = Keine Repositories konfiguriert. Bitte fügen Sie zuerst Repositories hinzu. +upload_select_repo = Repository auswählen +upload_choose_repo = -- Repository wählen -- +upload_select_class = Klasse auswählen +upload_choose_class = -- Klasse wählen -- +upload_commit_message = Commit-Nachricht +upload_commit_placeholder = Beschreibung der Änderungen... +upload_class_to_github = Klasse zu GitHub hochladen +local_classes_overview = Lokale Klassen-Übersicht +class_files = Dateien +class_installed = Installiert github_owner = GitHub Besitzer github_repo = Repository Name github_branch = Branch @@ -164,6 +177,30 @@ upload_author = Standard Autor upload_author_help = Dein Name für die Metadaten upload_settings_missing = Upload-Einstellungen sind nicht vollständig konfiguriert. Bitte konfiguriere Owner und Repository in den +# Overview +overview_title = GitHub Installer - Übersicht +overview_repositories = Repositories +overview_repositories_desc = Verwalten Sie GitHub Repositories für Module, Templates und Klassen. +overview_manage_repositories = Repositories verwalten +overview_install = Installieren +overview_install_desc = Installieren Sie Module, Templates und Klassen von GitHub. +overview_upload = Upload +overview_upload_desc = Laden Sie Ihre eigenen Inhalte zu GitHub hoch. +overview_modules_templates = Module/Templates +overview_classes = Klassen +overview_class_management = Klassen-Management +overview_installation = Installation +overview_class_install_desc = Klassen werden automatisch im Verzeichnis project/lib/gitClasses/KlassenName/ installiert. +overview_structure = Struktur +overview_class_structure_1 = Jede Klasse erhält einen eigenen Ordner +overview_class_structure_2 = Metadaten werden in config.yml gespeichert +overview_class_structure_3 = Zusätzliche Dateien werden mit übertragen +overview_class_upload_desc = Lokale Klassen können zu GitHub Repositories hochgeladen werden. +overview_management = Verwaltung +overview_cache_desc = Repository-Daten werden gecacht für bessere Performance. +overview_manage_cache = Cache verwalten +overview_settings_desc = GitHub Token und Cache-Einstellungen konfigurieren. + # General loading = Lade... error_occurred = Ein Fehler ist aufgetreten @@ -178,3 +215,6 @@ save = Speichern edit = Bearbeiten delete = Löschen back = Zurück +delete_class_confirm = Möchten Sie die Klasse "%s" wirklich löschen? Ein Backup wird erstellt. +class_deleted = Klasse wurde erfolgreich gelöscht +delete_failed = Löschen fehlgeschlagen diff --git a/lang/en_gb.lang b/lang/en_gb.lang index 6bd8e54..9f6e588 100644 --- a/lang/en_gb.lang +++ b/lang/en_gb.lang @@ -49,6 +49,32 @@ module_actions = Actions module_details = Details module_info = Module Information +# Classes +classes_title = Classes +classes_select_repo = Select Repository +classes_choose_repo = -- Choose Repository -- +classes_no_repos = No repositories configured. Please add repositories first. +classes_loading = Loading classes... +classes_no_classes = No classes found in this repository +classes_install = Install +classes_install_confirm = Install class "{0}"? +classes_installed_success = Class installed successfully +classes_install_error = Error installing class +classes_update = Reload +classes_update_confirm = Reload class "{0}"? +classes_updated_success = Class successfully reloaded +classes_update_error = Error reloading class +class_name = Name +class_title = Title +class_description = Description +class_version = Version +class_author = Author +class_namespace = Namespace +class_target_directory = Target Directory +class_actions = Actions +class_details = Details +class_info = Class Information + # Templates templates_title = Templates templates_select_repo = Select Repository @@ -60,6 +86,10 @@ templates_install = Install templates_install_confirm = Install template "{0}"? templates_installed_success = Template installed successfully templates_install_error = Error installing template +templates_update = Reload +templates_update_confirm = Reload template "{0}"? +templates_updated_success = Template successfully reloaded +templates_update_error = Error reloading template template_name = Name template_title = Title template_description = Description @@ -97,11 +127,24 @@ settings_saved = Settings saved successfully upload = Upload upload_module = Upload module "{0}" upload_template = Upload template "{0}" +upload_class = Upload class upload_success = "{0}" was successfully uploaded to "{1}" upload_error = Upload error upload_missing_data = Please fill all required fields no_local_modules = No local modules found no_local_templates = No local templates found +no_local_classes = No local classes found +upload_no_repos = No repositories configured. Please add repositories first. +upload_select_repo = Select Repository +upload_choose_repo = -- Choose Repository -- +upload_select_class = Select Class +upload_choose_class = -- Choose Class -- +upload_commit_message = Commit Message +upload_commit_placeholder = Description of changes... +upload_class_to_github = Upload Class to GitHub +local_classes_overview = Local Classes Overview +class_files = Files +class_installed = Installed github_owner = GitHub Owner github_repo = Repository Name github_branch = Branch @@ -128,6 +171,30 @@ upload_author = Default Author upload_author_help = Your name for metadata upload_settings_missing = Upload settings are not fully configured. Please configure Owner and Repository in the +# Overview +overview_title = GitHub Installer - Overview +overview_repositories = Repositories +overview_repositories_desc = Manage GitHub repositories for modules, templates and classes. +overview_manage_repositories = Manage Repositories +overview_install = Install +overview_install_desc = Install modules, templates and classes from GitHub. +overview_upload = Upload +overview_upload_desc = Upload your own content to GitHub. +overview_modules_templates = Modules/Templates +overview_classes = Classes +overview_class_management = Class Management +overview_installation = Installation +overview_class_install_desc = Classes are automatically installed to the project/lib/gitClasses/ClassName/ directory. +overview_structure = Structure +overview_class_structure_1 = Each class gets its own folder +overview_class_structure_2 = Metadata is stored in config.yml +overview_class_structure_3 = Additional files are transferred as well +overview_class_upload_desc = Local classes can be uploaded to GitHub repositories. +overview_management = Management +overview_cache_desc = Repository data is cached for better performance. +overview_manage_cache = Manage Cache +overview_settings_desc = Configure GitHub token and cache settings. + # General loading = Loading... error_occurred = An error occurred @@ -142,3 +209,6 @@ save = Save edit = Edit delete = Delete back = Back +delete_class_confirm = Do you really want to delete the class "%s"? A backup will be created. +class_deleted = Class was successfully deleted +delete_failed = Delete failed diff --git a/lib/ClassManager.php b/lib/ClassManager.php new file mode 100644 index 0000000..25f6c3b --- /dev/null +++ b/lib/ClassManager.php @@ -0,0 +1,415 @@ +github = new GitHubApi(); + $this->repoManager = new RepositoryManager(); + $this->projectLibPath = rex_path::addon('project') . 'lib/gitClasses/'; + } + + /** + * Klasse installieren + */ + public function installClass(string $repoKey, string $className): bool + { + $repositories = $this->repoManager->getRepositories(); + + if (!isset($repositories[$repoKey])) { + throw new \Exception('Repository not found'); + } + + $repo = $repositories[$repoKey]; + $classes = $this->repoManager->getClasses($repoKey); + + if (!isset($classes[$className])) { + throw new \Exception('Class not found in repository'); + } + + $classData = $classes[$className]; + $targetDir = $this->projectLibPath . $className . '/'; + + // Zielverzeichnis erstellen + if (!rex_dir::create($targetDir)) { + throw new \Exception('Cannot create target directory: ' . $targetDir); + } + + // Hauptklassen-Datei installieren + $filename = $classData['filename'] ?? $className . '.php'; + $targetFile = $targetDir . $filename; + + if (!rex_file::put($targetFile, $classData['content'])) { + throw new \Exception('Cannot write class file: ' . $targetFile); + } + + // Zusätzliche Dateien installieren (falls vorhanden) + $this->installAdditionalFiles($repo, $className, $targetDir); + + // Config-Datei erstellen für Metadaten + $this->createConfigFile($targetDir, $classData); + + return true; + } + + /** + * Klasse aktualisieren + */ + public function updateClass(string $repoKey, string $className): bool + { + $targetDir = $this->projectLibPath . $className . '/'; + + if (!is_dir($targetDir)) { + throw new \Exception('Class not installed locally'); + } + + // Backup erstellen + $backupDir = $targetDir . '.backup_' . date('Y-m-d_H-i-s') . '/'; + if (!rex_dir::copy($targetDir, $backupDir)) { + throw new \Exception('Cannot create backup'); + } + + try { + // Klasse neu installieren + $result = $this->installClass($repoKey, $className); + + // Backup löschen bei Erfolg + rex_dir::delete($backupDir); + + return $result; + + } catch (\Exception $e) { + // Bei Fehler: Backup wiederherstellen + rex_dir::delete($targetDir); + rex_dir::copy($backupDir, $targetDir); + rex_dir::delete($backupDir); + + throw $e; + } + } + + /** + * Klasse zu GitHub hochladen + */ + public function uploadClass(string $repoKey, string $className, string $commitMessage = ''): bool + { + $repositories = $this->repoManager->getRepositories(); + + if (!isset($repositories[$repoKey])) { + throw new \Exception('Repository not found'); + } + + $repo = $repositories[$repoKey]; + $classDir = $this->projectLibPath . $className . '/'; + + if (!is_dir($classDir)) { + throw new \Exception('Class directory not found: ' . $classDir); + } + + // Alle Dateien im Klassen-Verzeichnis hochladen + $files = $this->getClassFiles($classDir); + + if (empty($files)) { + throw new \Exception('No files found in class directory'); + } + + $commitMessage = $commitMessage ?: "Update class {$className}"; + + foreach ($files as $relativePath => $content) { + $githubPath = "classes/{$className}/{$relativePath}"; + + $this->github->uploadFile( + $repo['owner'], + $repo['repo'], + $githubPath, + $content, + $commitMessage, + $repo['branch'] ?? 'main' + ); + } + + return true; + } + + + + /** + * Alle lokalen Klassen abrufen + */ + public function getLocalClasses(): array + { + $classes = []; + + if (!is_dir($this->projectLibPath)) { + return $classes; + } + + $directories = scandir($this->projectLibPath); + + foreach ($directories as $dir) { + if ($dir === '.' || $dir === '..') { + continue; + } + + $classDir = $this->projectLibPath . $dir . '/'; + + if (!is_dir($classDir)) { + continue; + } + + $classData = $this->analyzeLocalClass($dir, $classDir); + if ($classData) { + $classes[$dir] = $classData; + } + } + + return $classes; + } + + /** + * Zusätzliche Dateien aus Repository installieren + */ + private function installAdditionalFiles(array $repo, string $className, string $targetDir): void + { + try { + $contents = $this->github->getRepositoryContents( + $repo['owner'], + $repo['repo'], + "classes/{$className}", + $repo['branch'] ?? 'main' + ); + + foreach ($contents as $item) { + if ($item['type'] === 'file' && $item['name'] !== ($className . '.php')) { + try { + $content = $this->github->getFileContent( + $repo['owner'], + $repo['repo'], + "classes/{$className}/{$item['name']}", + $repo['branch'] ?? 'main' + ); + + rex_file::put($targetDir . $item['name'], $content); + + } catch (\Exception $e) { + // Einzelne Datei konnte nicht geladen werden - weiter machen + error_log("Could not install additional file {$item['name']}: " . $e->getMessage()); + } + } + } + + } catch (\Exception $e) { + // Keine zusätzlichen Dateien vorhanden oder Fehler - kein Problem + } + } + + /** + * Config-Datei für Metadaten erstellen + */ + private function createConfigFile(string $targetDir, array $classData): void + { + $config = [ + 'title' => $classData['title'] ?? '', + 'description' => $classData['description'] ?? '', + 'version' => $classData['version'] ?? '1.0.0', + 'author' => $classData['author'] ?? '', + 'filename' => $classData['filename'] ?? '', + 'namespace' => $classData['namespace'] ?? '', + 'installed_at' => date('Y-m-d H:i:s'), + 'source_path' => $classData['path'] ?? '' + ]; + + $yamlContent = "# Class Configuration\n"; + foreach ($config as $key => $value) { + if ($value !== '' && $value !== null) { + $yamlContent .= "{$key}: " . (is_string($value) ? '"' . addslashes($value) . '"' : $value) . "\n"; + } + } + + rex_file::put($targetDir . 'config.yml', $yamlContent); + } + + /** + * Alle Dateien in einem Klassen-Verzeichnis abrufen + */ + private function getClassFiles(string $classDir): array + { + $files = []; + $iterator = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($classDir) + ); + + foreach ($iterator as $file) { + if ($file->isFile()) { + $relativePath = str_replace($classDir, '', $file->getPathname()); + $relativePath = str_replace('\\', '/', $relativePath); // Windows compatibility + + $content = rex_file::get($file->getPathname()); + if ($content !== false) { + $files[$relativePath] = $content; + } + } + } + + return $files; + } + + /** + * Lokale Klasse analysieren + */ + private function analyzeLocalClass(string $className, string $classDir): ?array + { + // Config-Datei suchen + $configFile = $classDir . 'config.yml'; + $config = []; + + if (file_exists($configFile)) { + $configContent = rex_file::get($configFile); + if ($configContent) { + $config = $this->parseSimpleYaml($configContent); + } + } + + // Hauptklassen-Datei finden + $phpFiles = glob($classDir . '*.php'); + $mainFile = null; + + if (!empty($config['filename'])) { + $mainFile = $classDir . $config['filename']; + } elseif (file_exists($classDir . $className . '.php')) { + $mainFile = $classDir . $className . '.php'; + } elseif (!empty($phpFiles)) { + $mainFile = $phpFiles[0]; + } + + if (!$mainFile || !file_exists($mainFile)) { + return null; + } + + $content = rex_file::get($mainFile); + if (!$content) { + return null; + } + + return [ + 'title' => $config['title'] ?? $className, + 'description' => $config['description'] ?? $this->extractClassDescription($content), + 'version' => $config['version'] ?? $this->extractClassVersion($content) ?? '1.0.0', + 'author' => $config['author'] ?? $this->extractClassAuthor($content), + 'filename' => basename($mainFile), + 'namespace' => $config['namespace'] ?? $this->extractNamespace($content), + 'installed_at' => $config['installed_at'] ?? (file_exists($configFile) ? date('Y-m-d H:i:s', filemtime($configFile)) : ''), + 'source_path' => $config['source_path'] ?? '', + 'local_path' => $classDir, + 'has_config' => file_exists($configFile), + 'file_count' => count(glob($classDir . '*')) + ]; + } + + /** + * Einfacher YAML-Parser + */ + private function parseSimpleYaml(string $content): array + { + $result = []; + $lines = explode("\n", $content); + + foreach ($lines as $line) { + $line = trim($line); + if (empty($line) || strpos($line, '#') === 0) { + continue; + } + + if (strpos($line, ':') !== false) { + [$key, $value] = explode(':', $line, 2); + $key = trim($key); + $value = trim($value, ' "\''); + + if ($value === 'true') { + $value = true; + } elseif ($value === 'false') { + $value = false; + } elseif (is_numeric($value)) { + $value = is_float($value) ? (float)$value : (int)$value; + } + + $result[$key] = $value; + } + } + + return $result; + } + + /** + * Namespace aus PHP-Code extrahieren + */ + private function extractNamespace(string $content): string + { + if (preg_match('/namespace\s+([^;]+);/', $content, $matches)) { + return trim($matches[1]); + } + + return ''; + } + + /** + * Beschreibung aus PHP-Kommentaren extrahieren + */ + private function extractClassDescription(string $content): string + { + if (preg_match('/@description\s+(.+)/i', $content, $matches)) { + return trim($matches[1]); + } + + if (preg_match('/\/\*\*(.*?)\*\//s', $content, $matches)) { + $comment = $matches[1]; + $lines = explode("\n", $comment); + + foreach ($lines as $line) { + $line = trim($line, " \t*"); + if (!empty($line) && !preg_match('/@\w+/', $line)) { + return substr($line, 0, 200); + } + } + } + + return ''; + } + + /** + * Version aus PHP-Kommentaren extrahieren + */ + private function extractClassVersion(string $content): string + { + if (preg_match('/@version\s+(.+)/i', $content, $matches)) { + return trim($matches[1]); + } + + return ''; + } + + /** + * Autor aus PHP-Kommentaren extrahieren + */ + private function extractClassAuthor(string $content): string + { + if (preg_match('/@author\s+(.+)/i', $content, $matches)) { + return trim($matches[1]); + } + + return ''; + } +} \ No newline at end of file diff --git a/lib/GitHubApi.php b/lib/GitHubApi.php index ead1d73..c51c8e0 100644 --- a/lib/GitHubApi.php +++ b/lib/GitHubApi.php @@ -240,4 +240,12 @@ public function createOrUpdateFile(string $owner, string $repo, string $path, st return $this->makeRequest($endpoint, 'PUT', $data); } + + /** + * Alias für createOrUpdateFile + */ + public function uploadFile(string $owner, string $repo, string $path, string $content, string $message, string $branch = 'main'): array + { + return $this->createOrUpdateFile($owner, $repo, $path, $content, $message, $branch); + } } diff --git a/lib/NewInstallManager.php b/lib/NewInstallManager.php index 47207af..174097a 100644 --- a/lib/NewInstallManager.php +++ b/lib/NewInstallManager.php @@ -252,22 +252,11 @@ public function installClass(string $repoKey, string $className): bool $targetDirectory = $classData['target_directory'] ?? 'lib'; $filename = $classData['filename'] ?? $className . '.php'; - // Ziel-Pfad bestimmen mit Verzeichnis-Struktur - if ($targetDirectory === 'lib') { - $basePath = \rex_path::addon('project') . 'lib/'; - } else { - $basePath = \rex_path::addon('project') . $targetDirectory . '/'; - } + // Ziel-Pfad bestimmen - IMMER in gitClasses Unterordner + $basePath = \rex_path::addon('project') . 'lib/gitClasses/'; - // Wenn die Klasse aus einem Verzeichnis stammt, Verzeichnis-Struktur beibehalten - if (str_contains($classData['path'], '/')) { - // z.B. "classes/DemoHelper" -> "DemoHelper/" - $classDirName = basename($classData['path']); - $targetPath = $basePath . $classDirName . '/'; - } else { - // Einzelne Datei direkt in lib/ - $targetPath = $basePath; - } + // Jede Klasse bekommt ihren eigenen Ordner im gitClasses Verzeichnis + $targetPath = $basePath . $className . '/'; // Verzeichnis erstellen falls nicht vorhanden if (!is_dir($targetPath)) { diff --git a/lib/RepositoryManager.php b/lib/RepositoryManager.php index 3652e98..5c05ed9 100644 --- a/lib/RepositoryManager.php +++ b/lib/RepositoryManager.php @@ -591,20 +591,13 @@ private function hasKeyField(string $table = 'module'): bool public function getClassesWithStatus(string $repoKey): array { $classes = $this->getClasses($repoKey); - $projectLibPath = \rex_path::addon('project') . 'lib/'; + $gitClassesPath = \rex_path::addon('project') . 'lib/gitClasses/'; foreach ($classes as $className => &$classData) { $filename = $classData['filename'] ?? $className . '.php'; - // Prüfung mit Verzeichnis-Struktur - if (str_contains($classData['path'], '/')) { - // z.B. "classes/DemoHelper" -> "lib/DemoHelper/DemoHelper.php" - $classDirName = basename($classData['path']); - $targetFile = $projectLibPath . $classDirName . '/' . $filename; - } else { - // Einzelne Datei direkt in lib/ - $targetFile = $projectLibPath . $filename; - } + // Jede Klasse bekommt ihren eigenen Ordner im gitClasses Verzeichnis + $targetFile = $gitClassesPath . $className . '/' . $filename; $classData['status'] = [ 'installed' => file_exists($targetFile), diff --git a/lib/UpdateManager.php b/lib/UpdateManager.php index 552b65d..80c4347 100644 --- a/lib/UpdateManager.php +++ b/lib/UpdateManager.php @@ -314,22 +314,11 @@ public function updateClass(string $repoKey, string $className): bool $targetDirectory = $classData['target_directory'] ?? 'lib'; $filename = $classData['filename'] ?? $className . '.php'; - // Ziel-Pfad bestimmen mit Verzeichnis-Struktur - if ($targetDirectory === 'lib') { - $basePath = \rex_path::addon('project') . 'lib/'; - } else { - $basePath = \rex_path::addon('project') . $targetDirectory . '/'; - } + // Ziel-Pfad bestimmen - IMMER in gitClasses Unterordner + $basePath = \rex_path::addon('project') . 'lib/gitClasses/'; - // Wenn die Klasse aus einem Verzeichnis stammt, Verzeichnis-Struktur beibehalten - if (str_contains($classData['path'], '/')) { - // z.B. "classes/DemoHelper" -> "DemoHelper/" - $classDirName = basename($classData['path']); - $targetPath = $basePath . $classDirName . '/'; - } else { - // Einzelne Datei direkt in lib/ - $targetPath = $basePath; - } + // Jede Klasse bekommt ihren eigenen Ordner im gitClasses Verzeichnis + $targetPath = $basePath . $className . '/'; $targetFile = $targetPath . $filename; diff --git a/package.yml b/package.yml index b2a03c1..6be520f 100644 --- a/package.yml +++ b/package.yml @@ -11,6 +11,7 @@ page: icon: rex-icon fa-github pjax: false subpages: + overview: {title: 'translate:overview_title', perm: 'github_installer[install]', icon: 'rex-icon fa-dashboard'} repositories: {title: 'translate:repositories', perm: 'github_installer[config]', icon: 'rex-icon fa-github'} modules: {title: 'translate:modules', perm: 'github_installer[install]', icon: 'rex-icon fa-puzzle-piece'} templates: {title: 'translate:templates', perm: 'github_installer[install]', icon: 'rex-icon fa-file-code-o'} diff --git a/pages/classes.php b/pages/classes.php index 9efde8b..271b8f9 100644 --- a/pages/classes.php +++ b/pages/classes.php @@ -82,14 +82,15 @@ $isInstalled = $classData['status']['installed']; $statusBadge = $isInstalled ? 'Installiert' : 'Neu'; - // Action Button + // Action Buttons + $actionButtons = ''; if ($isInstalled) { - $actionButton = ''; } else { - $actionButton = ''; @@ -109,7 +110,7 @@ ' . rex_escape($classData['author'] ?? $addon->i18n('unknown')) . ' ' . $infoLinks . ' ' . $statusBadge . ' - ' . $actionButton . ' + ' . $actionButtons . ' '; } @@ -222,6 +223,8 @@ function updateClassHandler(button) { $('.update-class-btn').on('click', function() { updateClassHandler($(this)); }); + + }); diff --git a/pages/overview.php b/pages/overview.php new file mode 100644 index 0000000..02114ee --- /dev/null +++ b/pages/overview.php @@ -0,0 +1,96 @@ + +
+
+
+

' . $addon->i18n('overview_repositories') . '

+
+
+

' . $addon->i18n('overview_repositories_desc') . '

+ ' . $addon->i18n('overview_manage_repositories') . ' +
+
+
+ +
+
+
+

' . $addon->i18n('overview_install') . '

+
+ +
+
+ +
+
+
+

' . $addon->i18n('overview_upload') . '

+
+ +
+
+ + +
+
+
+
+

' . $addon->i18n('overview_class_management') . '

+
+
+

' . $addon->i18n('overview_installation') . '

+

' . $addon->i18n('overview_class_install_desc') . '

+ +

' . $addon->i18n('overview_structure') . '

+
    +
  • ' . $addon->i18n('overview_class_structure_1') . '
  • +
  • ' . $addon->i18n('overview_class_structure_2') . '
  • +
  • ' . $addon->i18n('overview_class_structure_3') . '
  • +
+ +

' . $addon->i18n('overview_upload') . '

+

' . $addon->i18n('overview_class_upload_desc') . '

+
+
+
+ +
+
+
+

' . $addon->i18n('overview_management') . '

+
+
+

' . $addon->i18n('cache') . '

+

' . $addon->i18n('overview_cache_desc') . '

+ ' . $addon->i18n('overview_manage_cache') . ' + +

' . $addon->i18n('settings') . '

+

' . $addon->i18n('overview_settings_desc') . '

+ ' . $addon->i18n('settings') . ' +
+
+
+
+'; + +echo $content; \ No newline at end of file diff --git a/pages/upload.php b/pages/upload.php index 146d76f..298d29d 100644 --- a/pages/upload.php +++ b/pages/upload.php @@ -1,94 +1,197 @@ getConfig('upload_owner', ''); -$uploadRepo = $addon->getConfig('upload_repo', ''); -$uploadBranch = $addon->getConfig('upload_branch', 'main'); -$uploadAuthor = $addon->getConfig('upload_author', ''); +// Repository-Manager für Repository-Auswahl +$repoManager = new RepositoryManager(); +$repositories = $repoManager->getRepositories(); -if (empty($uploadOwner) || empty($uploadRepo)) { - echo rex_view::warning($this->i18n('upload_settings_missing') . ' ' . $this->i18n('settings') . ''); +if (empty($repositories)) { + echo rex_view::warning($addon->i18n('upload_no_repos') . ' ' . $addon->i18n('repositories') . ''); return; } // Upload durchführen if ($func === 'upload' && rex_post('upload', 'bool')) { - $itemId = (int) rex_post('item_id', 'int'); - $description = rex_post('description', 'string', ''); - $version = rex_post('version', 'string', '1.0.0'); - - if (!$itemId) { - echo rex_view::error($this->i18n('upload_missing_data')); - } else { - try { - $github = new GitHubApi(); - - // Repository testen - if (!$github->testRepository($uploadOwner, $uploadRepo, $uploadBranch)) { - throw new Exception("Repository {$uploadOwner}/{$uploadRepo} nicht erreichbar"); + if ($type === 'class') { + // Klassen-Upload + $className = rex_post('class_name', 'string'); + $targetRepo = rex_post('target_repo', 'string'); + $commitMessage = rex_post('commit_message', 'string') ?: "Upload class {$className}"; + + if (!$className || !$targetRepo) { + echo rex_view::error($addon->i18n('upload_missing_data')); + } else { + try { + if (!isset($repositories[$targetRepo])) { + throw new Exception('Repository nicht gefunden'); + } + + $classManager = new ClassManager(); + $result = $classManager->uploadClass($targetRepo, $className, $commitMessage); + + if ($result) { + $repo = $repositories[$targetRepo]; + echo rex_view::success($addon->i18n('upload_success', $className, $repo['owner'] . '/' . $repo['repo'])); + } + + } catch (Exception $e) { + echo rex_view::error($addon->i18n('upload_error') . ': ' . $e->getMessage()); } - - if ($type === 'module') { - $result = uploadModule($itemId, $github, $uploadOwner, $uploadRepo, $uploadBranch, $uploadAuthor, $description, $version); - } else { - $result = uploadTemplate($itemId, $github, $uploadOwner, $uploadRepo, $uploadBranch, $uploadAuthor, $description, $version); + } + } else { + // Module/Template Upload + $itemId = (int) rex_post('item_id', 'int'); + $targetRepo = rex_post('target_repo', 'string'); + $description = rex_post('description', 'string', ''); + $version = rex_post('version', 'string', '1.0.0'); + + if (!$itemId || !$targetRepo) { + echo rex_view::error($addon->i18n('upload_missing_data')); + } else { + try { + if (!isset($repositories[$targetRepo])) { + throw new Exception('Repository nicht gefunden'); + } + + $repo = $repositories[$targetRepo]; + $github = new GitHubApi(); + + // Repository testen + if (!$github->testRepository($repo['owner'], $repo['repo'], $repo['branch'])) { + throw new Exception("Repository {$repo['owner']}/{$repo['repo']} nicht erreichbar"); + } + + if ($type === 'module') { + $result = uploadModule($itemId, $github, $repo['owner'], $repo['repo'], $repo['branch'], $addon->getConfig('upload_author', ''), $description, $version); + } else { + $result = uploadTemplate($itemId, $github, $repo['owner'], $repo['repo'], $repo['branch'], $addon->getConfig('upload_author', ''), $description, $version); + } + + echo rex_view::success($addon->i18n('upload_success', $result['name'], $repo['owner'] . '/' . $repo['repo'])); + + } catch (Exception $e) { + echo rex_view::error($addon->i18n('upload_error') . ': ' . $e->getMessage()); } - - echo rex_view::success($this->i18n('upload_success', $result['name'], $uploadOwner . '/' . $uploadRepo)); - - } catch (Exception $e) { - echo rex_view::error($this->i18n('upload_error') . ': ' . $e->getMessage()); } } } // Upload-Formular anzeigen if ($func === 'upload' && !rex_post('upload', 'bool')) { - $itemId = (int) rex_request('item_id', 'int'); - - if ($type === 'module') { - $sql = rex_sql::factory(); - $sql->setQuery('SELECT id AS id, name AS name, `key` AS `key` FROM rex_module WHERE id = ?', [$itemId]); - $itemLabel = 'Modul'; - } else { - $sql = rex_sql::factory(); - $sql->setQuery('SELECT id AS id, name AS name, `key` AS `key` FROM rex_template WHERE id = ?', [$itemId]); - $itemLabel = 'Template'; + // Standard-Repository bestimmen + $defaultRepo = ''; + $uploadRepo = $addon->getConfig('upload_owner', '') . '/' . $addon->getConfig('upload_repo', ''); + foreach ($repositories as $repoKey => $repoData) { + if ($repoKey === $uploadRepo) { + $defaultRepo = $repoKey; + break; + } } - - if ($sql->getRows() === 0) { - echo rex_view::error($this->i18n('item_not_found')); - return; + if (!$defaultRepo && !empty($repositories)) { + $defaultRepo = array_key_first($repositories); } - // Werte direkt über rex_sql Methoden abrufen - $itemName = $sql->getValue('name'); - $itemKey = $sql->getValue('key'); - - $content = '
'; - $content .= '' . $itemLabel . ' hochladen: ' . rex_escape($itemName ?: 'Unbekannt') . ''; - - $content .= '
'; - $content .= 'Ziel-Repository: ' . rex_escape($uploadOwner . '/' . $uploadRepo) . ' (Branch: ' . rex_escape($uploadBranch) . ')'; - $content .= '
'; - - $formElements = []; - - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $formElements[] = $n; - - $n = []; - $n['label'] = ''; - $n['field'] = ''; - $formElements[] = $n; + if ($type === 'class') { + // Klassen-Upload Formular + $className = rex_request('class_name', 'string'); + $classManager = new ClassManager(); + $localClasses = $classManager->getLocalClasses(); + + if (!isset($localClasses[$className])) { + echo rex_view::error($addon->i18n('item_not_found')); + return; + } + + $classData = $localClasses[$className]; + + $content = '
'; + $content .= 'Klasse hochladen: ' . rex_escape($classData['title'] ?? $className) . ''; + + $formElements = []; + + // Repository-Auswahl + $n = []; + $n['label'] = ''; + $select = ''; + $n['field'] = $select; + $formElements[] = $n; + + // Commit-Message + $n = []; + $n['label'] = ''; + $n['field'] = ''; + $formElements[] = $n; + + // Hidden Class Name + $content .= ''; + + } else { + // Module/Template Upload Formular + $itemId = (int) rex_request('item_id', 'int'); + + if ($type === 'module') { + $sql = rex_sql::factory(); + $sql->setQuery('SELECT id AS id, name AS name, `key` AS `key` FROM rex_module WHERE id = ?', [$itemId]); + $itemLabel = 'Modul'; + } else { + $sql = rex_sql::factory(); + $sql->setQuery('SELECT id AS id, name AS name, `key` AS `key` FROM rex_template WHERE id = ?', [$itemId]); + $itemLabel = 'Template'; + } + + if ($sql->getRows() === 0) { + echo rex_view::error($addon->i18n('item_not_found')); + return; + } + + // Werte direkt über rex_sql Methoden abrufen + $itemName = $sql->getValue('name'); + $itemKey = $sql->getValue('key'); + + $content = '
'; + $content .= '' . $itemLabel . ' hochladen: ' . rex_escape($itemName ?: 'Unbekannt') . ''; + + $formElements = []; + + // Repository-Auswahl + $n = []; + $n['label'] = ''; + $select = ''; + $n['field'] = $select; + $formElements[] = $n; + + $n = []; + $n['label'] = ''; + $n['field'] = ''; + $formElements[] = $n; + + $n = []; + $n['label'] = ''; + $n['field'] = ''; + $formElements[] = $n; + + // Hidden Item ID + $content .= ''; + } $fragment = new rex_fragment(); $fragment->setVar('elements', $formElements, false); @@ -114,7 +217,6 @@ $output = $fragment->parse('core/page/section.php'); echo '
'; - echo ''; echo $output; echo '
'; @@ -124,7 +226,8 @@ // Tab-Navigation $tabs = [ 'module' => $this->i18n('modules'), - 'template' => $this->i18n('templates') + 'template' => $this->i18n('templates'), + 'class' => $this->i18n('classes_title') ]; $tabContent = ''; @@ -136,39 +239,79 @@ echo ''; // Item-Liste anzeigen -$list = rex_list::factory("SELECT id, name, `key`, createdate, updatedate FROM rex_{$type} ORDER BY name"); -$list->addTableAttribute('class', 'table-striped'); +if ($type === 'class') { + // Klassen-Liste + $classManager = new ClassManager(); + $localClasses = $classManager->getLocalClasses(); + + if (empty($localClasses)) { + echo rex_view::info($addon->i18n('no_local_classes')); + } else { + $tableContent = '
+ + + + + + + + + + + + '; + + foreach ($localClasses as $className => $classData) { + $uploadUrl = rex_url::currentBackendPage(['func' => 'upload', 'class_name' => $className, 'type' => 'class']); + $tableContent .= ' + + + + + + + '; + } + + $tableContent .= '
' . $addon->i18n('class_name') . '' . $addon->i18n('class_title') . '' . $addon->i18n('class_version') . '' . $addon->i18n('class_author') . '' . $addon->i18n('class_installed') . '' . $addon->i18n('upload') . '
' . rex_escape($className) . '' . rex_escape($classData['title'] ?? 'Unnamed Class') . '' . rex_escape($classData['version'] ?? '1.0.0') . '' . rex_escape($classData['author'] ?? $addon->i18n('unknown')) . '' . rex_escape($classData['installed_at'] ?? $addon->i18n('unknown')) . '' . $addon->i18n('upload') . '
'; + echo $tableContent; + } +} else { + // Module/Template Liste + $list = rex_list::factory("SELECT id, name, `key`, createdate, updatedate FROM rex_{$type} ORDER BY name"); + $list->addTableAttribute('class', 'table-striped'); -$list->setColumnLabel('name', $this->i18n('name')); -$list->setColumnLabel('key', 'Key'); -$list->setColumnLabel('createdate', $this->i18n('created')); -$list->setColumnLabel('updatedate', $this->i18n('updated')); + $list->setColumnLabel('name', $addon->i18n('name')); + $list->setColumnLabel('key', 'Key'); + $list->setColumnLabel('createdate', $addon->i18n('created')); + $list->setColumnLabel('updatedate', $addon->i18n('updated')); -$list->setColumnFormat('createdate', 'custom', function ($params) { - $value = $params['list']->getValue('createdate'); - if (!$value || $value === '0000-00-00 00:00:00') { - return '-'; - } - return rex_formatter::intlDateTime($value, [\IntlDateFormatter::SHORT, \IntlDateFormatter::SHORT]); -}); -$list->setColumnFormat('updatedate', 'custom', function ($params) { - $value = $params['list']->getValue('updatedate'); - if (!$value || $value === '0000-00-00 00:00:00') { - return '-'; - } - return rex_formatter::intlDateTime($value, [\IntlDateFormatter::SHORT, \IntlDateFormatter::SHORT]); -}); + $list->setColumnFormat('createdate', 'custom', function ($params) { + $value = $params['list']->getValue('createdate'); + if (!$value || $value === '0000-00-00 00:00:00') { + return '-'; + } + return rex_formatter::intlDateTime($value, [\IntlDateFormatter::SHORT, \IntlDateFormatter::SHORT]); + }); + $list->setColumnFormat('updatedate', 'custom', function ($params) { + $value = $params['list']->getValue('updatedate'); + if (!$value || $value === '0000-00-00 00:00:00') { + return '-'; + } + return rex_formatter::intlDateTime($value, [\IntlDateFormatter::SHORT, \IntlDateFormatter::SHORT]); + }); -// Upload-Button hinzufügen -$list->addColumn('upload', $this->i18n('upload'), -1, ['###VALUE###', '###VALUE###']); -$list->setColumnFormat('upload', 'custom', function ($params) use ($type) { - $itemId = $params['list']->getValue('id'); - $url = rex_url::currentBackendPage(['func' => 'upload', 'item_id' => $itemId, 'type' => $type]); - return '' . - rex_i18n::msg('upload') . ''; -}); + // Upload-Button hinzufügen + $list->addColumn('upload', $addon->i18n('upload'), -1, ['###VALUE###', '###VALUE###']); + $list->setColumnFormat('upload', 'custom', function ($params) use ($type) { + $itemId = $params['list']->getValue('id'); + $url = rex_url::currentBackendPage(['func' => 'upload', 'item_id' => $itemId, 'type' => $type]); + return '' . + rex_i18n::msg('upload') . ''; + }); -echo $list->get(); + echo $list->get(); +} // Helper Functions function uploadModule($moduleId, $github, $owner, $repo, $branch, $author, $description, $version) {