This commit is contained in:
Thomas Spohr
2025-08-18 14:55:06 +02:00
parent 7396cf9f51
commit 381b203e85
14 changed files with 374 additions and 34 deletions

BIN
com_eis/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -6,3 +6,4 @@ COM_EIS_MAIN="EIS Hauptansicht"
COM_EIS_MENU="EIS" COM_EIS_MENU="EIS"
COM_EIS_CONFIG="Einstellungen" COM_EIS_CONFIG="Einstellungen"
COM_EIS_MAINTENANCE="Wartung" COM_EIS_MAINTENANCE="Wartung"
COM_EIS_PDF_TREE="Verzeichnisbaum"

View File

@@ -23,7 +23,7 @@ return new class implements ServiceProviderInterface
$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 // Optional: DownloadController explizit registrieren (kann auch weggelassen werden)
$container->set( $container->set(
DownloadController::class, DownloadController::class,
fn(Container $c) => new DownloadController() fn(Container $c) => new DownloadController()

BIN
com_eis/site/.DS_Store vendored Normal file

Binary file not shown.

BIN
com_eis/site/src/.DS_Store vendored Normal file

Binary file not shown.

BIN
com_eis/src/.DS_Store vendored

Binary file not shown.

View File

@@ -1,4 +1,5 @@
<?php <?php
namespace EIS\Component\EIS\Administrator\Controller; namespace EIS\Component\EIS\Administrator\Controller;
\defined('_JEXEC') or die; \defined('_JEXEC') or die;
@@ -106,4 +107,35 @@ class DisplayController extends BaseController
} }
} }
} }
public function rename(): void
{
// CSRF prüfen
\Joomla\CMS\Session\Session::checkToken() or jexit(\JText::_('JINVALID_TOKEN'));
$app = \Joomla\CMS\Factory::getApplication();
$id = (int) $app->input->get('id');
$title = trim((string) $app->input->get('title', '', 'STRING'));
if ($id <= 0) {
$app->enqueueMessage('Ungültige ID', 'error');
$this->setRedirect('index.php?option=com_eis&view=main');
return;
}
/** @var \Joomla\Database\DatabaseDriver $db */
$db = \Joomla\CMS\Factory::getDbo();
$query = $db->getQuery(true)
->update($db->quoteName('#__eis_documents'))
->set($db->quoteName('title') . ' = ' . ($title === '' ? 'NULL' : $db->quote($title)))
->where($db->quoteName('id') . ' = ' . (int) $id);
try {
$db->setQuery($query)->execute();
$app->enqueueMessage('Anzeigename gespeichert', 'message');
} catch (\Throwable $e) {
$app->enqueueMessage('Fehler beim Speichern: ' . $e->getMessage(), 'error');
}
$this->setRedirect('index.php?option=com_eis&view=main');
}
} }

View File

@@ -0,0 +1,112 @@
<?php
namespace EIS\Component\EIS\Administrator\Helper;
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseDriver;
class TreeHelper
{
/** Lädt #__eis_documents, gruppiert nach parent_id und sortiert */
public static function getItems(): array
{
/** @var DatabaseDriver $db */
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__eis_documents'))
->order($db->quoteName('ordering') . ' ASC');
$rows = $db->setQuery($query)->loadAssocList();
$grouped = [];
foreach ($rows as $row) {
$pid = $row['parent_id'] === null ? null : (int) $row['parent_id'];
$grouped[$pid][] = $row;
}
// alphabetisch nach name
foreach ($grouped as &$group) {
usort($group, fn($a, $b) => strcasecmp($a['name'], $b['name']));
}
return $grouped;
}
/** Baum rendern (nutzt title als alternativen Anzeigenamen) */
public static function renderTree(array $items, ?int $parentId = null): string
{
if (!isset($items[$parentId])) {
return '';
}
$html = '<ul class="pdf-tree">';
foreach ($items[$parentId] as $item) {
$isFolder = (bool) $item['is_folder'];
$rawName = $item['title'] ?: $item['name'];
$display = preg_replace('/\.pdf$/i', '', (string) $rawName);
$display = htmlspecialchars($display, ENT_QUOTES, 'UTF-8');
$id = (int) $item['id'];
if ($isFolder) {
$fileCount = self::countFilesRecursive($items, $id);
$html .= '<li class="folder"'
. ' data-id="' . $id . '"'
. ' data-title="' . htmlspecialchars((string)($item['title'] ?? ''), ENT_QUOTES, 'UTF-8') . '"'
. ' data-name="' . htmlspecialchars((string)$item['name'], ENT_QUOTES, 'UTF-8') . '"'
. '>';
$html .= '<span class="toggle" role="button" aria-label="Ordner umschalten" tabindex="0">▶</span> ';
$html .= '<span class="folder-label">📁 ' . $display . ' <small class="count">(' . (int)$fileCount . ')</small></span>';
$html .= self::renderTree($items, $id);
$html .= '</li>';
} else {
$sizeStr = '';
if (!empty($item['path']) && is_file($item['path'])) {
$size = @filesize($item['path']);
if ($size !== false) {
$sizeStr = ' <small class="meta">(' . self::formatFileSize((int) $size) . ')</small>';
}
}
$html .= '<li class="file"'
. ' data-id="' . $id . '"'
. ' data-title="' . htmlspecialchars((string)($item['title'] ?? ''), ENT_QUOTES, 'UTF-8') . '"'
. ' data-name="' . htmlspecialchars((string)$item['name'], ENT_QUOTES, 'UTF-8') . '"'
. '>';
$html .= '<span class="file-label">📄 ' . $display . $sizeStr . '</span>';
$html .= '</li>';
}
}
$html .= '</ul>';
return $html;
}
private static function countFilesRecursive(array $items, int $parentId): int
{
$count = 0;
if (!isset($items[$parentId])) {
return 0;
}
foreach ($items[$parentId] as $item) {
if ((bool) $item['is_folder']) {
$count += self::countFilesRecursive($items, (int)$item['id']);
} else {
$count++;
}
}
return $count;
}
private static function formatFileSize(int $bytes): string
{
if ($bytes >= 1073741824) return number_format($bytes / 1073741824, 2) . ' GB';
if ($bytes >= 1048576) return number_format($bytes / 1048576, 1) . ' MB';
if ($bytes >= 1024) return number_format($bytes / 1024, 0) . ' KB';
return $bytes . ' B';
}
}

Binary file not shown.

View File

@@ -1,15 +1,20 @@
<?php <?php
namespace EIS\Component\EIS\Administrator\View\Main; namespace EIS\Component\EIS\Administrator\View\Main;
\defined('_JEXEC') or die; \defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView; use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use EIS\Component\EIS\Administrator\Helper\TreeHelper;
class HtmlView extends BaseHtmlView class HtmlView extends BaseHtmlView
{ {
public function display($tpl = null): void public function display($tpl = null): void
{ {
echo "<h2>Willkommen bei EIS</h2>"; // Baumdaten laden und fürs Template bereitstellen
$this->data = Factory::getApplication()->getUserState('com_eis.pdfdata'); $items = TreeHelper::getItems();
$this->treeHtml = TreeHelper::renderTree($items);
parent::display($tpl); parent::display($tpl);
} }
} }

BIN
com_eis/tmpl/.DS_Store vendored

Binary file not shown.

View File

@@ -4,41 +4,231 @@
use Joomla\CMS\HTML\HTMLHelper; use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Router\Route; use Joomla\CMS\Router\Route;
use Joomla\CMS\Language\Text; use Joomla\CMS\Language\Text;
use Joomla\CMS\Session\Session;
?> ?>
<!-- Scan-Formular -->
<form action="<?php echo Route::_('index.php?option=com_eis&task=display.scan'); ?>" method="post" name="adminForm" id="adminForm"> <form action="<?php echo Route::_('index.php?option=com_eis&task=display.scan'); ?>" method="post" name="adminForm" id="adminForm">
<div class="form-horizontal"> <div class="form-horizontal">
<fieldset class="adminform"> <fieldset class="adminform">
<legend><?php echo Text::_('COM_EIS_DOCUMENT_PATH'); ?></legend> <legend><?php echo Text::_('COM_EIS_DOCUMENT_PATH'); ?></legend>
<!-- Optional: Hier später Pfad aus Einstellungen anzeigen -->
</fieldset> </fieldset>
<div>
<button class="btn btn-primary" type="submit"><?php echo Text::_('COM_EIS_SCAN_DOCUMENTS'); ?></button> <div class="mt-2">
<button class="btn btn-primary" type="submit">
<?php echo Text::_('COM_EIS_SCAN_DOCUMENTS'); ?>
</button>
</div> </div>
</div> </div>
<?php echo HTMLHelper::_('form.token'); ?> <?php echo HTMLHelper::_('form.token'); ?>
</form> </form>
<?php if (!empty($this->data)) : ?>
<hr>
<h3><?php echo Text::_('COM_EIS_PDF_TREE'); ?></h3>
<ul>
<?php echo renderTree($this->data); ?>
</ul>
<?php endif; ?>
<?php <hr>
function renderTree($items)
{ <?php $hasTree = !empty($this->treeHtml); ?>
$html = '';
foreach ($items as $item) { <?php if (!$hasTree): ?>
if (isset($item['children'])) { <p class="text-muted">
$html .= '<li><strong>' . htmlspecialchars($item['name']) . '</strong><ul>' . renderTree($item['children']) . '</ul></li>'; <?php echo Text::_('COM_EIS_NO_DOCUMENTS_FOUND') ?: 'Noch keine Dokumente in der Datenbank. Bitte zuerst „Dokumente einlesen“ ausführen.'; ?>
} else { </p>
$html .= '<li>' . htmlspecialchars($item['name']) . '</li>'; <?php else: ?>
<style>
/* Layout */
.eis-flex {
display: flex;
gap: 1rem;
align-items: flex-start;
}
.eis-tree-wrap {
flex: 1 1 auto;
background: #f9f9f9;
border: 1px solid #ddd;
border-radius: 6px;
padding: 1em;
}
.eis-edit {
flex: 0 0 360px;
background: #fff;
border: 1px solid #ddd;
border-radius: 6px;
padding: 1em;
}
.eis-edit h4 {
margin-top: 0;
}
/* Baum */
.pdf-tree {
list-style: none;
margin: 0;
padding: 0;
}
.pdf-tree ul {
list-style: none;
margin-left: 1.5em;
padding: 0;
display: none;
}
/* Unterebenen eingeklappt */
.pdf-tree li {
margin: .3em 0;
}
.folder>.toggle {
cursor: pointer;
width: 1em;
display: inline-block;
user-select: none;
}
.folder>.folder-label,
.file>.file-label {
cursor: pointer;
user-select: none;
}
.pdf-tree .meta {
color: #666;
}
.pdf-tree .count {
color: #999;
}
/* Steuerleiste */
.controls {
margin-bottom: .75em;
}
.controls button {
margin-right: .5em;
}
</style>
<h3 class="mt-3"><?php echo Text::_('COM_EIS_PDF_TREE') ?: 'PDF-Baum'; ?></h3>
<div class="eis-flex">
<!-- Linke Seite: Baum -->
<div class="eis-tree-wrap">
<div class="controls">
<button id="eis-expand-all" type="button" class="btn btn-sm btn-secondary">
<?php echo Text::_('JGLOBAL_EXPAND_ALL') ?: 'Ausklappen'; ?>
</button>
<button id="eis-collapse-all" type="button" class="btn btn-sm btn-secondary">
<?php echo Text::_('JGLOBAL_COLLAPSE_ALL') ?: 'Einklappen'; ?>
</button>
</div>
<div class="tree">
<?php echo $this->treeHtml; ?>
</div>
</div>
<!-- Rechte Seite: Inline-Bearbeitungsformular -->
<div class="eis-edit">
<h4><?php echo Text::_('COM_EIS_EDIT_TITLE') ?: 'Anzeigename bearbeiten'; ?></h4>
<form action="<?php echo Route::_('index.php?option=com_eis&task=display.rename'); ?>" method="post" id="eis-rename-form">
<div class="control-group">
<label for="eis-current-name"><?php echo Text::_('COM_EIS_FIELD_NAME') ?: 'Originalname'; ?></label>
<input type="text" id="eis-current-name" class="form-control" value="" readonly>
</div>
<div class="control-group" style="margin-top:.5rem;">
<label for="eis-title"><?php echo Text::_('COM_EIS_FIELD_TITLE') ?: 'Anzeigename (optional)'; ?></label>
<input type="text" name="title" id="eis-title" class="form-control" value="" placeholder="<?php echo Text::_('COM_EIS_PLACEHOLDER_TITLE') ?: 'Leer lassen = automatisch aus Dateiname'; ?>">
</div>
<input type="hidden" name="id" id="eis-id" value="">
<div style="margin-top:.75rem;">
<button type="submit" class="btn btn-success">
<?php echo Text::_('JSAVE') ?: 'Speichern'; ?>
</button>
<button type="button" id="eis-clear-title" class="btn btn-light">
<?php echo Text::_('JDEFAULT') ?: 'Zurücksetzen'; ?>
</button>
</div>
<?php echo HTMLHelper::_('form.token'); ?>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const wrap = document.querySelector('.eis-tree-wrap');
const form = document.getElementById('eis-rename-form');
const idField = document.getElementById('eis-id');
const nameField = document.getElementById('eis-current-name');
const titleField = document.getElementById('eis-title');
const clearBtn = document.getElementById('eis-clear-title');
if (!wrap || !form) return;
// Einzelnes Toggle: Caret ODER Label
wrap.addEventListener('click', (e) => {
const t = e.target;
// Toggle (Caret oder Label für Ordner)
if (t.classList.contains('toggle') || t.classList.contains('folder-label')) {
const li = t.closest('li.folder');
if (li) {
// direkte Kind-UL finden
let childUl = null;
for (const el of li.children) {
if (el.tagName === 'UL') {
childUl = el;
break;
} }
} }
return $html; if (childUl) {
const open = childUl.style.display === 'block';
childUl.style.display = open ? 'none' : 'block';
const caret = li.querySelector(':scope > .toggle') || li.querySelector('.toggle');
if (caret) caret.textContent = open ? '▶' : '▼';
} }
?> }
}
// Auswahl fürs Bearbeiten: Klick auf Ordner- oder Datei-Label
if (t.classList.contains('folder-label') || t.classList.contains('file-label')) {
const li = t.closest('li');
if (!li) return;
const id = li.getAttribute('data-id') || '';
const title = li.getAttribute('data-title') || '';
const name = li.getAttribute('data-name') || '';
idField.value = id;
nameField.value = name;
titleField.value = title;
titleField.focus();
}
});
// "Zurücksetzen" = Anzeigename löschen (fällt auf Dateiname zurück)
clearBtn?.addEventListener('click', () => {
titleField.value = '';
titleField.focus();
});
// Global: Alle aus-/einklappen
document.getElementById('eis-expand-all')?.addEventListener('click', () => {
document.querySelectorAll('.pdf-tree ul').forEach(ul => ul.style.display = 'block');
document.querySelectorAll('.pdf-tree .toggle').forEach(t => t.textContent = '▼');
});
document.getElementById('eis-collapse-all')?.addEventListener('click', () => {
document.querySelectorAll('.pdf-tree ul').forEach(ul => ul.style.display = 'none');
document.querySelectorAll('.pdf-tree .toggle').forEach(t => t.textContent = '▶');
});
});
</script>
<?php endif; ?>