Letzte Version

This commit is contained in:
Thomas Spohr
2025-08-13 08:35:35 +02:00
parent 266fd69afb
commit 33fa9c8f94
10 changed files with 149 additions and 84 deletions

Binary file not shown.

View File

@@ -5,17 +5,23 @@
<file driver="mysql" charset="utf8">install/sql/mysql/install.utf8.sql</file> <file driver="mysql" charset="utf8">install/sql/mysql/install.utf8.sql</file>
</sql> </sql>
</install> </install>
<uninstall> <uninstall>
<sql> <sql>
<file driver="mysql" charset="utf8">install/sql/mysql/uninstall.mysql.utf8.sql</file> <file driver="mysql" charset="utf8">install/sql/mysql/uninstall.mysql.utf8.sql</file>
</sql> </sql>
</uninstall> </uninstall>
<name>com_eis</name> <name>com_eis</name>
<creationDate>2025-07-23</creationDate> <creationDate>2025-07-23</creationDate>
<author>Thomas Spohr powert by OpenAI</author> <author>Thomas Spohr powert by OpenAI</author>
<version>1.0.0</version> <version>1.0.0</version>
<description>EIS Minimal-Komponente</description> <description>EIS Minimal-Komponente</description>
<namespace path="src">EIS\Component\EIS</namespace> <namespace path="src">EIS\Component\EIS</namespace>
<files folder="site">
<folder>src</folder>
</files>
<administration> <administration>
<menu>COM_EIS_MENU</menu> <menu>COM_EIS_MENU</menu>
<submenu> <submenu>

BIN
com_eis/eis.zip Normal file

Binary file not shown.

View File

@@ -1,4 +1,5 @@
<?php <?php
namespace EIS\Component\EIS; namespace EIS\Component\EIS;
\defined('_JEXEC') or die; \defined('_JEXEC') or die;
@@ -11,15 +12,24 @@ use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface; use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\DI\Container; use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface; use Joomla\DI\ServiceProviderInterface;
use EIS\Component\EIS\Site\Controller\DownloadController;
return new class implements ServiceProviderInterface return new class implements ServiceProviderInterface
{ {
public function register(Container $container): void public function register(Container $container): void
{ {
// MVC-Basisregistrierung
$container->registerServiceProvider(new ComponentDispatcherFactory('\\EIS\\Component\\EIS')); $container->registerServiceProvider(new ComponentDispatcherFactory('\\EIS\\Component\\EIS'));
$container->registerServiceProvider(new MVCFactory('\\EIS\\Component\\EIS')); $container->registerServiceProvider(new MVCFactory('\\EIS\\Component\\EIS'));
$container->registerServiceProvider(new RouterFactory('\\EIS\\Component\\EIS')); $container->registerServiceProvider(new RouterFactory('\\EIS\\Component\\EIS'));
// DownloadController explizit registrieren
$container->set(
DownloadController::class,
fn(Container $c) => new DownloadController()
);
// Komponentenschnittstelle
$container->set( $container->set(
ComponentInterface::class, ComponentInterface::class,
static fn(Container $c) => new MVCComponent( static fn(Container $c) => new MVCComponent(

View File

@@ -0,0 +1,62 @@
<?php
namespace EIS\Component\EIS\Site\Controller;
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Router\Route;
use Joomla\Database\DatabaseDriver;
class DownloadController extends BaseController
{
public function download()
{
$app = Factory::getApplication();
$user = Factory::getUser();
$input = $app->input;
// Nur für eingeloggte Benutzer
if ($user->guest) {
$app->enqueueMessage('Bitte zuerst einloggen.', 'warning');
$app->redirect(Route::_('index.php?option=com_users&view=login', false));
return;
}
// ID aus URL lesen
$id = $input->getInt('id');
if (!$id) {
throw new \RuntimeException("Keine Dokument-ID übergeben.");
}
// Datenbankabfrage
/** @var DatabaseDriver $db */
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName(['path', 'name']))
->from($db->quoteName('#__eis_documents'))
->where($db->quoteName('id') . ' = ' . (int) $id)
->where($db->quoteName('is_folder') . ' = 0');
$row = $db->setQuery($query)->loadAssoc();
if (!$row) {
throw new \RuntimeException("PDF nicht gefunden oder kein gültiges Dokument.");
}
$filePath = $row['path'];
$fileName = $row['name'];
if (!file_exists($filePath)) {
throw new \RuntimeException("Datei existiert nicht auf dem Server.");
}
// PDF-Datei ausgeben
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="' . basename($fileName) . '"');
header('Content-Length: ' . filesize($filePath));
readfile($filePath);
exit;
}
}

View File

@@ -4,7 +4,6 @@ namespace EIS\Component\EIS\Administrator\View\Main;
\defined('_JEXEC') or die; \defined('_JEXEC') or die;
use Joomla\CMS\Factory; use Joomla\CMS\Factory;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView; use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
class HtmlView extends BaseHtmlView class HtmlView extends BaseHtmlView
{ {
public function display($tpl = null): void public function display($tpl = null): void

Binary file not shown.

View File

@@ -9,7 +9,6 @@ class ModEisAnzeigeHelper
/** /**
* Holt alle PDF-Elemente aus der Datenbank * Holt alle PDF-Elemente aus der Datenbank
*/ */
public static function getItems(): array public static function getItems(): array
{ {
/** @var DatabaseDriver $db */ /** @var DatabaseDriver $db */
@@ -35,7 +34,6 @@ class ModEisAnzeigeHelper
/** /**
* Hauptfunktion zum Rendern des Baums * Hauptfunktion zum Rendern des Baums
*/ */
public static function renderTree(array $items, int $parentId = null): string public static function renderTree(array $items, int $parentId = null): string
{ {
if (!isset($items[$parentId])) { if (!isset($items[$parentId])) {
@@ -47,19 +45,21 @@ class ModEisAnzeigeHelper
foreach ($items[$parentId] as $item) { foreach ($items[$parentId] as $item) {
$isFolder = (bool) $item['is_folder']; $isFolder = (bool) $item['is_folder'];
$title = htmlspecialchars($item['title'] ?: $item['name']); $title = htmlspecialchars($item['title'] ?: $item['name']);
// $path = htmlspecialchars($item['path'] ?? ''); $path = self::convertToRelativeUrl($item['path'] ?? '');
$path = htmlspecialchars(self::convertToRelativeUrl($item['path'] ?? ''));
// Erweiterung .pdf im Titel entfernen (nur Anzeige, nicht Link)
$displayTitle = preg_replace('/\.pdf$/i', '', $title);
if ($isFolder) { if ($isFolder) {
$fileCount = self::countFilesRecursive($items, $item['id']); $fileCount = self::countFilesRecursive($items, $item['id']);
$html .= '<li class="folder">'; $html .= '<li class="folder">';
$html .= '<span class="toggle">▶</span>'; $html .= '<span class="toggle">▶</span>';
$html .= '<span class="folder-icon"></span>' . $title . ' <small>(' . $fileCount . ')</small>'; $html .= '<span class="folder-icon">📁</span> ' . $displayTitle . ' <small>(' . $fileCount . ')</small>';
$html .= self::renderTree($items, $item['id']); $html .= self::renderTree($items, $item['id']);
$html .= '</li>'; $html .= '</li>';
} else { } else {
$html .= '<li class="file">'; $html .= '<li class="file">';
$html .= '<span class="file-icon"></span><a class="file-link" href="' . $path . '">' . $title . '</a>'; $html .= '<span class="file-icon">📄</span><a class="file-link" href="' . htmlspecialchars($path) . '" title="' . $title . '">' . $displayTitle . '</a>';
$html .= '</li>'; $html .= '</li>';
} }
} }
@@ -68,22 +68,25 @@ class ModEisAnzeigeHelper
return $html; return $html;
} }
/** /**
* Konvertiert absoluten Serverpfad in URL * Konvertiert absoluten Serverpfad in URL
*/ */
private static function convertToRelativeUrl(string $fullPath): string private static function convertToRelativeUrl(string $fullPath): string
{ {
// <== HIER den absoluten Pfad zu deinem Webroot anpassen // Absoluter Serverpfad zum Joomla-Root
$webRoot = '/var/www/vhosts/ts-it24.net/stbv.ts-it24.net'; $webRoot = '/var/www/joomla';
$webBase = ''; // ggf. '/subdir' falls Joomla in Unterordner $webBase = ''; // ggf. '/unterordner', wenn Joomla in Subdir
if (str_starts_with($fullPath, $webRoot)) { if (str_starts_with($fullPath, $webRoot)) {
return $webBase . str_replace($webRoot, '', $fullPath); $relativePath = str_replace($webRoot, '', $fullPath);
// Leerzeichen und Sonderzeichen escapen
$relativePath = implode('/', array_map('rawurlencode', explode('/', $relativePath)));
return rtrim(\JUri::root(), '/') . $webBase . $relativePath;
} }
return $fullPath; // Fallback: Original verwenden return $fullPath; // Fallback
} }
/** /**
* Zählt alle PDF-Dateien unterhalb eines Ordners rekursiv * Zählt alle PDF-Dateien unterhalb eines Ordners rekursiv
*/ */
@@ -96,7 +99,7 @@ class ModEisAnzeigeHelper
} }
foreach ($items[$parentId] as $item) { foreach ($items[$parentId] as $item) {
if ((bool)$item['is_folder']) { if ((bool) $item['is_folder']) {
$count += self::countFilesRecursive($items, $item['id']); $count += self::countFilesRecursive($items, $item['id']);
} else { } else {
$count++; $count++;

Binary file not shown.

View File

@@ -1,8 +1,6 @@
<?php defined('_JEXEC') or die; ?> <?php defined('_JEXEC') or die; ?>
<!-- Styles für Struktur, Vorschau und Icons -->
<style> <style>
/* Baum und Vorschau nebeneinander */
.pdf-tree-wrapper { .pdf-tree-wrapper {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
@@ -12,16 +10,16 @@
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 6px; border-radius: 6px;
box-shadow: 0 4px 10px rgba(0,0,0,0.05); box-shadow: 0 4px 10px rgba(0,0,0,0.05);
width: 100%;
max-width: 100%; /* Oder: max-width: 1400px; */
} }
/* Linke Spalte: Verzeichnisbaum */
.pdf-tree-container { .pdf-tree-container {
flex: 0 0 30%; flex: 0 0 30%;
height: 95vh; max-height: 95vh;
overflow: auto; overflow: auto;
} }
/* Vorschaufenster rechts */
#pdf-preview { #pdf-preview {
flex: 1; flex: 1;
min-width: 800px; min-width: 800px;
@@ -31,50 +29,46 @@
display: none; display: none;
} }
/* Vorschau-Inhalt (PDF) */
#pdf-preview iframe { #pdf-preview iframe {
width: 100%; width: 100%;
height: 100%; height: 100%;
border: none; border: none;
padding: 0;
margin: 0;
display: block; display: block;
} }
/* Verzeichnisliste zurücksetzen */ .pdf-tree {
.pdf-tree, .pdf-tree ul {
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.pdf-tree ul {
list-style: none;
margin-left: 1.5em;
padding: 0;
display: none;
}
.pdf-tree li { .pdf-tree li {
margin: 0.3em 0; margin: 0.3em 0;
} }
/* Ordner-Icons & Pfeile */
.folder > .toggle { .folder > .toggle {
cursor: pointer; cursor: pointer;
display: inline-block;
width: 1em; width: 1em;
} display: inline-block;
.folder-icon::before { user-select: none;
content: "📁";
margin-right: 4px;
} }
/* Datei-Icon */ /* Optionale visuelle Trennung bei Dateien */
.file-icon::before { .file-link {
content: "📄"; text-decoration: none;
margin-right: 4px; color: #0366d6;
}
.file-link:hover {
text-decoration: underline;
} }
/* Standard: Unterverzeichnisse einklappen */
.pdf-tree ul {
display: none;
margin-left: 1.5em;
}
/* Buttons oben */
.expand-collapse { .expand-collapse {
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
@@ -83,69 +77,60 @@
} }
</style> </style>
<!-- Steuerbuttons oben --> <!-- Buttons: Aus-/Einklappen -->
<div class="expand-collapse"> <div class="expand-collapse">
<button id="expand-all">ausklappen</button> <button id="expand-all">ausklappen</button>
<button id="collapse-all">einklappen</button> <button id="collapse-all">einklappen</button>
</div> </div>
<!-- PDF-Baum und Vorschau -->
<div class="pdf-tree-wrapper"> <div class="pdf-tree-wrapper">
<!-- Links: Verzeichnisbaum -->
<div class="pdf-tree-container"> <div class="pdf-tree-container">
<?php echo ModEisAnzeigeHelper::renderTree($items); ?> <?= ModEisAnzeigeHelper::renderTree($items); ?>
</div> </div>
<!-- Rechts: Vorschau-Bereich -->
<div id="pdf-preview"></div> <div id="pdf-preview"></div>
</div> </div>
<!-- Interaktivität: Vorschau, Toggle, Buttons -->
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function () {
const preview = document.getElementById('pdf-preview');
if (!preview) return;
// (1) Verzeichnisse auf-/zuklappen per Klick // (1) Verzeichnisse toggeln
document.querySelectorAll('.pdf-tree .toggle').forEach(function(toggle) { document.querySelectorAll('.pdf-tree .toggle').forEach(toggle => {
toggle.addEventListener('click', function() { toggle.addEventListener('click', () => {
var li = this.parentNode; const ul = toggle.closest('li').querySelector('ul');
var ul = li.querySelector('ul'); if (ul) {
if (!ul) return; const visible = ul.style.display === 'block';
if (ul.style.display === 'block') { ul.style.display = visible ? 'none' : 'block';
ul.style.display = 'none'; this.textContent = '▶'; toggle.textContent = visible ? '▶' : '▼';
} else {
ul.style.display = 'block'; this.textContent = '▼';
} }
}); });
}); });
// (2) Alle ausklappen // (2) Alle ausklappen
document.getElementById('expand-all').addEventListener('click', function() { document.getElementById('expand-all')?.addEventListener('click', () => {
document.querySelectorAll('.pdf-tree ul').forEach(function(ul) { document.querySelectorAll('.pdf-tree ul').forEach(ul => ul.style.display = 'block');
ul.style.display = 'block'; document.querySelectorAll('.pdf-tree .toggle').forEach(t => t.textContent = '▼');
});
document.querySelectorAll('.pdf-tree .toggle').forEach(function(toggle) {
toggle.textContent = '▼';
});
}); });
// (3) Alle einklappen // (3) Alle einklappen
document.getElementById('collapse-all').addEventListener('click', function() { document.getElementById('collapse-all')?.addEventListener('click', () => {
document.querySelectorAll('.pdf-tree ul').forEach(function(ul) { document.querySelectorAll('.pdf-tree ul').forEach(ul => ul.style.display = 'none');
ul.style.display = 'none'; document.querySelectorAll('.pdf-tree .toggle').forEach(t => t.textContent = '▶');
});
document.querySelectorAll('.pdf-tree .toggle').forEach(function(toggle) {
toggle.textContent = '▶';
});
}); });
// (4) Vorschau anzeigen beim Klick // (4) PDF-Vorschau
var preview = document.getElementById('pdf-preview'); document.querySelectorAll('.pdf-tree .file-link').forEach(link => {
document.querySelectorAll('.pdf-tree .file-link').forEach(function(link) { link.addEventListener('click', e => {
link.addEventListener('click', function(e) {
e.preventDefault(); e.preventDefault();
preview.style.display = 'block'; preview.style.display = 'block';
preview.innerHTML = '<iframe src="' + this.href + '" loading="lazy"></iframe>'; preview.innerHTML = ''; // Vorherige Vorschau entfernen
const iframe = document.createElement('iframe');
iframe.src = link.href;
iframe.loading = 'lazy';
preview.appendChild(iframe);
}); });
}); });
}); });
</script> </script>