Compare commits

..

18 Commits

Author SHA1 Message Date
Thomas Spohr
fd88be717e Ohne Zugriffsebenen 2026-02-14 13:07:19 +01:00
Thomas Spohr
c744863094 Test 2026-02-01 12:59:54 +01:00
Thomas Spohr
530ff9f3a0 Voe Umbau mit Joomla-Cron 2026-02-01 12:46:12 +01:00
Thomas Spohr
31af9b27e7 Jetzt mit einem Ordner für neue Dokumente 2025-08-27 14:12:48 +02:00
Thomas Spohr
103fea5c48 Mit neuen Manfest 2025-08-26 20:28:35 +02:00
Thomas Spohr
dda6869030 Mit deutscher Sprache 2025-08-24 10:08:36 +02:00
Thomas Spohr
bcc180fa6f DE-Sprache 2025-08-21 16:27:27 +02:00
Thomas Spohr
87666dde70 Jetzt über pdf.js 2025-08-21 15:56:29 +02:00
Thomas Spohr
fc1de065c9 Mit Formular 2025-08-20 14:08:20 +02:00
Thomas Spohr
381b203e85 OK 2025-08-18 14:55:06 +02:00
Thomas Spohr
7396cf9f51 Änderungen im Modul. Ausklappen desd Baumes 2025-08-18 11:58:50 +02:00
Thomas Spohr
bd7e720c47 Version mit sicherem Download 2025-08-18 11:34:09 +02:00
Thomas Spohr
33fa9c8f94 Letzte Version 2025-08-13 08:35:35 +02:00
Thomas Spohr
266fd69afb Dokumenteninfo wird jetzt in DB geschrieben. 2025-07-31 12:22:10 +02:00
Thomas Spohr
9c9d422380 1. Version mit DB 2025-07-30 14:19:44 +02:00
Thomas Spohr
a481866b66 erste Lauffähige Fassung 2025-07-29 10:33:26 +02:00
Thomas Spohr
af2ebf4ab6 Erstes Joomla-Modul 2025-07-16 10:03:59 +02:00
Thomas Spohr
519f9e16b3 Neuer Kommentar 2025-07-15 13:34:26 +02:00
429 changed files with 162189 additions and 12 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
EIS.docx Normal file

Binary file not shown.

View File

@@ -1,4 +0,0 @@
# EIS
Joomla-Modul für die Ansicht von PDF's
Das kommt alles noch

View File

@@ -0,0 +1,48 @@
; Joomla Backend UI-Texte
; Allgemein
COM_EIS_TITLE="EIS"
COM_EIS_SUBTITLE="Dokumentenverwaltung"
COM_EIS_BACK="Zurück"
COM_EIS_SAVE="Speichern"
COM_EIS_CANCEL="Abbrechen"
COM_EIS_APPLY="Anwenden"
COM_EIS_CLOSE="Schließen"
; Main View (Baum & Scan)
COM_EIS_PDF_TREE="PDF-Baum"
COM_EIS_SCAN_DOCUMENTS="Dokumente einlesen"
COM_EIS_DOCUMENT_PATH="Dokumenten-Pfad"
COM_EIS_NO_DOCUMENTS_FOUND="Noch keine Dokumente in der Datenbank. Bitte zuerst „Dokumente einlesen“ ausführen."
COM_EIS_EXPAND_ALL="Ausklappen"
COM_EIS_COLLAPSE_ALL="Einklappen"
COM_EIS_PREVIEW_OPEN_NEWTAB="Im neuen Tab öffnen"
COM_EIS_PREVIEW_TITLE="PDF-Vorschau"
COM_EIS_FILESIZE="Dateigröße"
COM_EIS_VIRTUAL_NEW="Neue Dokumente"
; Config View (Einstellungen)
COM_EIS_SETTINGS_TITLE="Einstellungen"
COM_EIS_FIELDSET_GENERAL="Allgemein"
COM_EIS_FIELD_DOCUMENT_ROOT_LABEL="Basis-Verzeichnis für PDFs"
COM_EIS_FIELD_DOCUMENT_ROOT_DESC="Absoluter Serverpfad, in dem die PDF-Dateien liegen (z. B. /var/www/pdf)."
; Meldungen
COM_EIS_MSG_SAVED_SUCCESS="Einstellungen wurden gespeichert."
COM_EIS_MSG_SAVED_ERROR="Einstellungen konnten nicht gespeichert werden."
COM_EIS_MSG_SCAN_STARTED="Einlesen gestartet."
COM_EIS_MSG_SCAN_DONE="Einlesen abgeschlossen."
COM_EIS_MSG_INVALID_PATH="Der angegebene Pfad ist ungültig oder nicht lesbar."
; Toolbar/Buttons (falls eigene Toolbars)
COM_EIS_TOOLBAR_SAVE="Speichern"
COM_EIS_TOOLBAR_APPLY="Übernehmen"
COM_EIS_TOOLBAR_CLOSE="Schließen"
COM_EIS_MSG_PATH_NOT_EXISTS="Hinweis: Das Verzeichnis „%s“ existiert nicht."
COM_EIS_MSG_PATH_NOT_READABLE="Hinweis: Das Verzeichnis „%s“ ist nicht lesbar."
COM_EIS_ACCESS_TITLE="Zugriff (Top-Level-Ordner)"
COM_EIS_FIELD_VIEWLEVEL="Zugriffsebene"
COM_EIS_ACCESS_HELP="Nur für Ordner der ersten Ebene. Unterordner und Dateien erben die Einstellung."

View File

@@ -0,0 +1,6 @@
; Joomla Backend Systemtexte (Menü, Erweiterungsname, Kurzbeschreibung)
COM_EIS="EIS"
COM_EIS_MENU="EIS"
COM_EIS_MAIN="EIS-Dokumente"
COM_EIS_CONFIG="Einstellungen"
COM_EIS_XML_DESCRIPTION="EIS Komponente zum Verwalten und Anzeigen von PDFs."

View File

@@ -0,0 +1,32 @@
COM_EIS_TITLE="EIS"
COM_EIS_SUBTITLE="Document Management"
COM_EIS_BACK="Back"
COM_EIS_SAVE="Save"
COM_EIS_CANCEL="Cancel"
COM_EIS_APPLY="Apply"
COM_EIS_CLOSE="Close"
COM_EIS_PDF_TREE="PDF Tree"
COM_EIS_SCAN_DOCUMENTS="Scan Documents"
COM_EIS_DOCUMENT_PATH="Document Path"
COM_EIS_NO_DOCUMENTS_FOUND="No documents in the database yet. Please run “Scan Documents” first."
COM_EIS_EXPAND_ALL="Expand All"
COM_EIS_COLLAPSE_ALL="Collapse All"
COM_EIS_PREVIEW_OPEN_NEWTAB="Open in new tab"
COM_EIS_PREVIEW_TITLE="PDF Preview"
COM_EIS_FILESIZE="File size"
COM_EIS_SETTINGS_TITLE="Settings"
COM_EIS_FIELDSET_GENERAL="General"
COM_EIS_FIELD_DOCUMENT_ROOT_LABEL="Base directory for PDFs"
COM_EIS_FIELD_DOCUMENT_ROOT_DESC="Absolute server path containing the PDF files (e.g. /var/www/pdf)."
COM_EIS_MSG_SAVED_SUCCESS="Settings saved."
COM_EIS_MSG_SAVED_ERROR="Settings could not be saved."
COM_EIS_MSG_SCAN_STARTED="Scan started."
COM_EIS_MSG_SCAN_DONE="Scan finished."
COM_EIS_MSG_INVALID_PATH="The specified path is invalid or not readable."
COM_EIS_TOOLBAR_SAVE="Save"
COM_EIS_TOOLBAR_APPLY="Apply"
COM_EIS_TOOLBAR_CLOSE="Close"

View File

@@ -0,0 +1,5 @@
COM_EIS="EIS"
COM_EIS_MENU="EIS"
COM_EIS_MAIN="EIS Documents"
COM_EIS_CONFIG="Settings"
COM_EIS_XML_DESCRIPTION="EIS component for managing and viewing PDFs."

View File

@@ -0,0 +1,40 @@
<?php
namespace EIS\Component\EIS;
\defined('_JEXEC') or die;
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
use Joomla\CMS\Extension\Service\Provider\RouterFactory;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use EIS\Component\EIS\Site\Controller\DownloadController;
return new class implements ServiceProviderInterface
{
public function register(Container $container): void
{
// MVC-Basisregistrierung
$container->registerServiceProvider(new ComponentDispatcherFactory('\\EIS\\Component\\EIS'));
$container->registerServiceProvider(new MVCFactory('\\EIS\\Component\\EIS'));
$container->registerServiceProvider(new RouterFactory('\\EIS\\Component\\EIS'));
// Optional: DownloadController explizit registrieren (kann auch weggelassen werden)
$container->set(
DownloadController::class,
fn(Container $c) => new DownloadController()
);
// Komponentenschnittstelle
$container->set(
ComponentInterface::class,
static fn(Container $c) => new MVCComponent(
$c->get(ComponentDispatcherFactoryInterface::class)
)
);
}
};

View File

@@ -0,0 +1,43 @@
-- Documents
CREATE TABLE IF NOT EXISTS `#__eis_documents` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`path` TEXT NOT NULL,
`parent_id` INT UNSIGNED DEFAULT NULL,
`is_folder` TINYINT(1) NOT NULL DEFAULT 0,
`title` VARCHAR(255) DEFAULT NULL,
`description` TEXT DEFAULT NULL,
`ordering` INT NOT NULL DEFAULT 0,
`created` DATETIME DEFAULT NULL,
`modified` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_eis_docs_parent` (`parent_id`),
KEY `idx_eis_docs_ordering` (`ordering`),
KEY `idx_eis_docs_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Settings (Key/Value)
CREATE TABLE IF NOT EXISTS `#__eis_settings` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`param` VARCHAR(191) NOT NULL,
`value` TEXT NULL,
`created` DATETIME DEFAULT NULL,
`modified` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_param` (`param`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__eis_folder_access` (
`folder_id` INT UNSIGNED NOT NULL,
`viewlevel_id` INT UNSIGNED NOT NULL,
`modified` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`folder_id`),
KEY `idx_viewlevel` (`viewlevel_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Standardwert für den PDF-Pfad (leer = noch nicht gesetzt)
INSERT IGNORE INTO `#__eis_settings` (`param`, `value`, `created`, `modified`)
VALUES ('document_root', '/var/www/pdf', NOW(), NOW());

View File

@@ -0,0 +1,2 @@
DROP TABLE IF EXISTS `#__eis_settings`;
DROP TABLE IF EXISTS `#__eis_documents`;

View File

@@ -0,0 +1,70 @@
-- UTF-8, kein BOM
-- EIS Schema-Update 1.1.1
-- Ziel: fehlende Tabellen anlegen, fehlende Spalten/Indizes ergänzen.
-- ==========================================================
-- 1) Einstellungen (neu in 1.1.1)
-- ==========================================================
CREATE TABLE IF NOT EXISTS `#__eis_settings` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`param` VARCHAR(191) NOT NULL,
`value` TEXT NULL,
`created` DATETIME NULL DEFAULT NULL,
`modified` DATETIME NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_param` (`param`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT IGNORE INTO `#__eis_settings` (`param`, `value`, `created`, `modified`)
VALUES ('document_root', '', NOW(), NOW());
-- ==========================================================
-- 2) Dokumente (Bestand absichern)
-- Falls ältere Installationen die Tabelle nicht haben.
-- ==========================================================
CREATE TABLE IF NOT EXISTS `#__eis_documents` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`path` TEXT NOT NULL,
`parent_id` INT UNSIGNED DEFAULT NULL,
`is_folder` TINYINT(1) NOT NULL DEFAULT 0,
`title` VARCHAR(255) DEFAULT NULL,
`description` TEXT DEFAULT NULL,
`ordering` INT NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `#__eis_folder_access` (
`folder_id` INT UNSIGNED NOT NULL,
`viewlevel_id` INT UNSIGNED NOT NULL,
`modified` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`folder_id`),
KEY `idx_viewlevel` (`viewlevel_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Falls es Altbestände ohne neue Spalten gab, Spalten idempotent ergänzen
ALTER TABLE `#__eis_documents`
ADD COLUMN IF NOT EXISTS `title` VARCHAR(255) DEFAULT NULL,
ADD COLUMN IF NOT EXISTS `description` TEXT DEFAULT NULL,
ADD COLUMN IF NOT EXISTS `ordering` INT NOT NULL DEFAULT 0,
MODIFY COLUMN `is_folder` TINYINT(1) NOT NULL DEFAULT 0;
-- Nützliche Indizes (idempotent)
CREATE INDEX IF NOT EXISTS `idx_eis_docs_parent` ON `#__eis_documents` (`parent_id`);
CREATE INDEX IF NOT EXISTS `idx_eis_docs_ordering` ON `#__eis_documents` (`ordering`);
CREATE INDEX IF NOT EXISTS `idx_eis_docs_name` ON `#__eis_documents` (`name`);
-- Optional: Selbst-Referenz als FK (nur wenn du ON DELETE CASCADE willst)
-- Achtung: MySQL verlangt gleiche Kollation/Engine; Namen idempotent prüfen:
-- (MySQL kennt kein "ADD CONSTRAINT IF NOT EXISTS", daher defensiv erst droppen)
-- SET @fk_exists := (
-- SELECT COUNT(*)
-- FROM information_schema.REFERENTIAL_CONSTRAINTS
-- WHERE CONSTRAINT_SCHEMA = DATABASE()
-- AND CONSTRAINT_NAME = 'fk_eis_docs_parent'
-- );
-- SET @sql := IF(@fk_exists = 0,
-- 'ALTER TABLE `#__eis_documents` ADD CONSTRAINT `fk_eis_docs_parent` FOREIGN KEY (`parent_id`) REFERENCES `#__eis_documents`(`id`) ON DELETE CASCADE;',
-- 'SELECT 1');
-- PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

View File

@@ -0,0 +1,58 @@
<?php
namespace EIS\Component\EIS\Administrator\Controller;
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
use EIS\Component\EIS\Administrator\Helper\SettingsHelper;
class ConfigController extends BaseController
{
public function save(): void
{
// CSRF
if (!Session::checkToken('post')) {
throw new \RuntimeException(Text::_('JINVALID_TOKEN'), 403);
}
$app = Factory::getApplication();
$input = $app->getInput();
// Feldname aus dem Config-Template
$path = (string) $input->post->get('document_root', '', 'string');
$path = trim($path);
// Minimal-Validierung & Normalisierung
// (Hier kein striktes is_readable(), damit man den Pfad auch erst konfigurieren kann,
// wenn das Verzeichnis später angelegt/umgehängt wird. Warnen ist aber hilfreich.)
if ($path === '') {
$app->enqueueMessage(Text::_('COM_EIS_MSG_INVALID_PATH') ?: 'Bitte einen gültigen Pfad angeben.', 'warning');
$this->setRedirect(Route::_('index.php?option=com_eis&view=config', false));
return;
}
// Doppelte Slashes und trailing Slash bereinigen
$path = preg_replace('#/+#', '/', $path);
// Lass den Root-Slash stehen, entferne sonst trailing Slash
if ($path !== '/' && str_ends_with($path, '/')) {
$path = rtrim($path, '/');
}
// Optional: Hinweise zur Existenz/Lesbarkeit (nur Hinweis, kein Hard-Error)
if (!is_dir($path)) {
$app->enqueueMessage(Text::sprintf('COM_EIS_MSG_PATH_NOT_EXISTS', $path) ?: "Hinweis: Verzeichnis existiert nicht: {$path}", 'notice');
} elseif (!is_readable($path)) {
$app->enqueueMessage(Text::sprintf('COM_EIS_MSG_PATH_NOT_READABLE', $path) ?: "Hinweis: Verzeichnis nicht lesbar: {$path}", 'notice');
}
// Persistieren
SettingsHelper::setSetting('document_root', $path);
$app->enqueueMessage(Text::_('COM_EIS_MSG_SAVED_SUCCESS') ?: 'Einstellungen wurden gespeichert.', 'message');
$this->setRedirect(Route::_('index.php?option=com_eis&view=config', false));
}
}

View File

@@ -0,0 +1,218 @@
<?php
namespace EIS\Component\EIS\Administrator\Controller;
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Session\Session;
use Joomla\Database\DatabaseDriver;
use EIS\Component\EIS\Administrator\Helper\SettingsHelper;
use EIS\Component\EIS\Administrator\Service\DocumentScanner;
class DisplayController extends BaseController
{
protected $default_view = 'main';
/**
* Button-Aktion: PDF-Verzeichnis scannen und in Datenbank speichern
*/
public function scan(): void
{
// CSRF prüfen (Form hat Token)
if (!Session::checkToken('post')) {
throw new \RuntimeException(Text::_('JINVALID_TOKEN'), 403);
}
$app = Factory::getApplication();
try {
$newIds = DocumentScanner::run();
// Wie bisher: New IDs für die View (virtueller Ordner "Neue Dokumente")
$app->setUserState('com_eis.new_ids', $newIds);
$app->enqueueMessage(Text::_('COM_EIS_MSG_SCAN_DONE') ?: 'PDF-Struktur erfolgreich gespeichert.', 'message');
} catch (\Throwable $e) {
$app->enqueueMessage($e->getMessage(), 'error');
}
$this->setRedirect(Route::_('index.php?option=com_eis&view=main', false));
}
/**
* Rekursive Verzeichnisanalyse
*/
private function scanFolder(string $dir): array
{
$result = [];
if (!is_dir($dir)) {
return $result;
}
$entries = @scandir($dir);
if ($entries === false) {
return $result;
}
foreach ($entries as $file) {
if ($file === '.' || $file === '..') {
continue;
}
$fullPath = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($fullPath)) {
$result[] = [
'name' => $file,
'children' => $this->scanFolder($fullPath)
];
} elseif (is_file($fullPath) && strtolower(pathinfo($file, PATHINFO_EXTENSION)) === 'pdf') {
$result[] = [
'name' => $file,
'path' => $fullPath
];
}
}
// Alphabetisch stabil sortieren
usort($result, static function ($a, $b) {
return strcasecmp((string)$a['name'], (string)$b['name']);
});
return $result;
}
/**
* Struktur rekursiv in die Datenbank schreiben
* @return int[] Insert-IDs aller neu eingefügten Dateien (für „Neue Dokumente“)
*/
private function saveToDb(array $items, ?int $parentId, DatabaseDriver $db): array
{
$insertedFileIds = [];
foreach ($items as $item) {
$name = $db->quote($item['name']);
$path = $db->quote($item['path'] ?? ''); // leer statt NULL
$parent = $parentId !== null ? (int)$parentId : 'NULL';
$isFolder = isset($item['children']) ? 1 : 0;
$query = $db->getQuery(true)
->insert($db->quoteName('#__eis_documents'))
->columns(['name', 'path', 'parent_id', 'is_folder'])
->values("$name, $path, $parent, $isFolder");
$db->setQuery($query)->execute();
$insertedId = (int)$db->insertid();
if ($isFolder && !empty($item['children'])) {
// Kinder speichern
$childIds = $this->saveToDb($item['children'], $insertedId, $db);
// Nur Datei-IDs sammeln
$insertedFileIds = array_merge($insertedFileIds, $childIds);
} else {
// Datei -> ID merken
$insertedFileIds[] = $insertedId;
}
}
return $insertedFileIds;
}
public function rename(): void
{
// CSRF prüfen
if (!Session::checkToken('post')) {
throw new \RuntimeException(Text::_('JINVALID_TOKEN'), 403);
}
$app = Factory::getApplication();
$id = (int) $app->input->post->get('id', 0);
$title = trim((string) $app->input->post->get('title', '', 'string'));
if ($id <= 0) {
$app->enqueueMessage(Text::_('JERROR_AN_ERROR_HAS_OCCURRED') ?: 'Ungültige ID', 'error');
$this->setRedirect(Route::_('index.php?option=com_eis&view=main', false));
return;
}
/** @var DatabaseDriver $db */
$db = Factory::getDbo();
$query = $db->getQuery(true)
->update($db->quoteName('#__eis_documents'))
->set($db->quoteName('title') . ' = ' . ($title === '' ? 'NULL' : $db->quote($title)))
->set($db->quoteName('modified') . ' = ' . $db->quote(\Joomla\CMS\Factory::getDate()->toSql()))
->where($db->quoteName('id') . ' = ' . (int)$id);
try {
$db->setQuery($query)->execute();
$app->enqueueMessage(Text::_('COM_EIS_MSG_SAVED_SUCCESS') ?: 'Anzeigename gespeichert', 'message');
} catch (\Throwable $e) {
$app->enqueueMessage((Text::_('COM_EIS_MSG_SAVED_ERROR') ?: 'Fehler beim Speichern: ') . $e->getMessage(), 'error');
}
$this->setRedirect(Route::_('index.php?option=com_eis&view=main', false));
}
public function saveAccess(): void
{
if (!\Joomla\CMS\Session\Session::checkToken('post')) {
throw new \RuntimeException(\JText::_('JINVALID_TOKEN'), 403);
}
$app = \Joomla\CMS\Factory::getApplication();
$db = \Joomla\CMS\Factory::getDbo();
$folderId = (int) $app->input->post->get('folder_id', 0);
$viewlevelId = (int) $app->input->post->get('viewlevel_id', 0);
// Validierung: folderId muss Top-Level-Ordner sein
if ($folderId <= 0) {
$app->enqueueMessage('Ungültiger Ordner.', 'error');
$this->setRedirect(\Joomla\CMS\Router\Route::_('index.php?option=com_eis&view=main', false));
return;
}
// prüfen, ob Top-Level-Ordner
$q = $db->getQuery(true)
->select($db->quoteName(['id','is_folder','parent_id']))
->from($db->quoteName('#__eis_documents'))
->where($db->quoteName('id') . ' = ' . (int)$folderId);
$db->setQuery($q);
$row = $db->loadAssoc();
if (!$row || (int)$row['is_folder'] !== 1 || $row['parent_id'] !== null) {
$app->enqueueMessage('Nur Top-Level-Ordner können berechtigt werden.', 'warning');
$this->setRedirect(\Joomla\CMS\Router\Route::_('index.php?option=com_eis&view=main', false));
return;
}
// Viewlevel validieren
if ($viewlevelId <= 0) {
// löschen (öffentlich)
$db->setQuery(
$db->getQuery(true)
->delete($db->quoteName('#__eis_folder_access'))
->where($db->quoteName('folder_id') . ' = ' . (int)$folderId)
)->execute();
$app->enqueueMessage('Berechtigung entfernt (öffentlich).', 'message');
$this->setRedirect(\Joomla\CMS\Router\Route::_('index.php?option=com_eis&view=main', false));
return;
}
// upsert
$sql = 'INSERT INTO ' . $db->quoteName('#__eis_folder_access')
. ' (' . $db->quoteName('folder_id') . ', ' . $db->quoteName('viewlevel_id') . ') VALUES ('
. (int)$folderId . ', ' . (int)$viewlevelId . ')'
. ' ON DUPLICATE KEY UPDATE ' . $db->quoteName('viewlevel_id') . ' = VALUES(' . $db->quoteName('viewlevel_id') . ')';
$db->setQuery($sql)->execute();
$app->enqueueMessage('Berechtigung gespeichert.', 'message');
$this->setRedirect(\Joomla\CMS\Router\Route::_('index.php?option=com_eis&view=main', false));
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace EIS\Component\EIS\Administrator\Extension;
\defined('_JEXEC') or die;
use Joomla\CMS\Extension\MVCComponent;
final class EISComponent extends MVCComponent
{
}

View File

@@ -0,0 +1,45 @@
<?php
namespace EIS\Component\EIS\Administrator\Helper;
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
final class SettingsHelper
{
public static function getSetting(string $param, $default = '')
{
$db = Factory::getDbo();
$q = $db->getQuery(true)
->select($db->quoteName('value'))
->from($db->quoteName('#__eis_settings'))
->where($db->quoteName('param') . ' = ' . $db->quote($param));
$db->setQuery($q);
$val = $db->loadResult();
return ($val !== null && $val !== '') ? $val : $default;
}
public static function setSetting(string $param, $value): void
{
$db = Factory::getDbo();
// Basis-INSERT per Query-Builder zusammenstellen …
$qb = $db->getQuery(true)
->insert($db->quoteName('#__eis_settings'))
->columns([$db->quoteName('param'), $db->quoteName('value'), $db->quoteName('modified')])
->values(
$db->quote($param) . ', ' .
$db->quote((string) $value) . ', ' .
$db->quote(date('Y-m-d H:i:s'))
);
// … und dann als String um "ON DUPLICATE KEY UPDATE" erweitern.
$sql = (string) $qb
. ' ON DUPLICATE KEY UPDATE '
. $db->quoteName('value') . ' = VALUES(' . $db->quoteName('value') . '), '
. $db->quoteName('modified') . ' = VALUES(' . $db->quoteName('modified') . ')';
$db->setQuery($sql)->execute();
}
}

View File

@@ -0,0 +1,193 @@
<?php
namespace EIS\Component\EIS\Administrator\Helper;
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
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, static fn($a, $b) => strcasecmp((string)$a['name'], (string)$b['name']));
}
return $grouped;
}
/**
* Kombiniert: Virtueller Ordner "Neue Dokumente" (falls $newIds vorhanden) + normaler Baum
*/
public static function renderTreeWithNew(array $items, array $newIds = []): string
{
$html = '';
// 1) Virtueller Ordner "Neue Dokumente"
$newFiles = self::collectFilesByIds($items, $newIds);
if (!empty($newFiles)) {
$label = Text::_('COM_EIS_VIRTUAL_NEW') ?: 'Neue Dokumente';
$html .= '<ul class="pdf-tree">';
$html .= '<li class="folder" data-id="new" data-title="' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '" data-name="' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '" data-count="' . count($newFiles) . '">';
$html .= '<span class="toggle" role="button" aria-label="Ordner umschalten" tabindex="0">▼</span> ';
$html .= '<span class="folder-label">🆕 ' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . ' <small class="count">(' . (int) count($newFiles) . ')</small></span>';
// Kinder sichtbar
$html .= '<ul style="display:block">';
foreach ($newFiles as $row) {
$html .= self::renderSingleFileLi($row);
}
$html .= '</ul>';
$html .= '</li>';
$html .= '</ul>';
}
// 2) Normaler Baum
$html .= self::renderTree($items, null);
return $html;
}
/** 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 = !empty($item['is_folder']);
$rawName = (string)($item['title'] ?? '') !== '' ? (string)$item['title'] : (string)$item['name'];
$display = preg_replace('/\.pdf$/i', '', $rawName);
$display = htmlspecialchars((string)$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') . '"'
. ' data-count="' . (int)$fileCount . '"'
. '>'
. '<span class="toggle" role="button" aria-label="Ordner umschalten" tabindex="0">▶</span> '
. '<span class="folder-label">📁 ' . $display . ' <small class="count">(' . (int)$fileCount . ')</small></span>'
. self::renderTree($items, $id)
. '</li>';
} else {
$html .= self::renderSingleFileLi($item, $display);
}
}
$html .= '</ul>';
return $html;
}
/** Einzelnes <li> für Datei (wird auch vom virtuellen Ordner genutzt) */
private static function renderSingleFileLi(array $item, ?string $displayAlreadyEscaped = null): string
{
$rawName = (string)($item['title'] ?? '') !== '' ? (string)$item['title'] : (string)$item['name'];
$display = $displayAlreadyEscaped ?? htmlspecialchars(preg_replace('/\.pdf$/i', '', $rawName), ENT_QUOTES, 'UTF-8');
$id = (int)$item['id'];
// Dateigröße ermitteln (optional)
$sizeStr = '';
$sizeBytes = '';
if (!empty($item['path']) && is_file((string)$item['path'])) {
$bytes = @filesize((string)$item['path']); // @ unterdrückt Warnungen bei Zugriffsproblemen
if ($bytes !== false) {
$sizeBytes = (string)(int)$bytes;
$sizeStr = self::formatFileSize((int)$bytes);
}
}
$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') . '"'
. ' data-size="' . htmlspecialchars($sizeStr, ENT_QUOTES, 'UTF-8') . '"'
. ' data-bytes="' . htmlspecialchars($sizeBytes, ENT_QUOTES, 'UTF-8') . '"'
. '>'
. '<span class="file-label">📄 ' . $display
. ($sizeStr !== '' ? ' <small class="meta">(' . $sizeStr . ')</small>' : '')
. '</span>'
. '</li>';
return $html;
}
/** Dateien zu gegebenen IDs einsammeln (nur is_folder = 0) */
private static function collectFilesByIds(array $items, array $ids): array
{
if (empty($ids)) {
return [];
}
$ids = array_map('intval', $ids);
// Flat-Index aller Rows
$idx = [];
foreach ($items as $group) {
foreach ($group as $row) {
$idx[(int)$row['id']] = $row;
}
}
$out = [];
foreach ($ids as $id) {
if (isset($idx[$id]) && empty($idx[$id]['is_folder'])) {
$out[] = $idx[$id];
}
}
return $out;
}
private static function countFilesRecursive(array $items, int $parentId): int
{
$count = 0;
if (!isset($items[$parentId])) {
return 0;
}
foreach ($items[$parentId] as $item) {
if (!empty($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';
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace EIS\Component\EIS\Administrator\Service;
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseDriver;
use EIS\Component\EIS\Administrator\Helper\SettingsHelper;
class DocumentScanner
{
/**
* Führt den Scan aus.
* @return int[] IDs der eingefügten Dateien
*/
public static function run(?string $path = null, ?DatabaseDriver $db = null): array
{
$db = $db ?: Factory::getDbo();
$app = Factory::getApplication();
// Pfad laden, wenn nicht übergeben
$path = $path ?: SettingsHelper::getSetting('document_root', '/var/www/pdf');
if (!$path || !is_dir($path)) {
// Kein enqueueMessage, damit es auch im Task sauber läuft
throw new \RuntimeException('Pfad ungültig oder nicht gesetzt: ' . (string)$path);
}
$data = self::scanFolder($path);
// Achtung: du löscht aktuell immer alles -> danach ist alles "neu"
$db->truncateTable('#__eis_documents');
$newIds = self::saveToDb($data, null, $db);
// Dauerhaft speichern (Frontend-Modul nutzt das ja schon)
SettingsHelper::setSetting('last_new_ids', json_encode($newIds, JSON_UNESCAPED_SLASHES));
SettingsHelper::setSetting('last_scan_at', date('Y-m-d H:i:s'));
return $newIds;
}
private static function scanFolder(string $dir): array
{
$result = [];
if (!is_dir($dir)) {
return $result;
}
$entries = @scandir($dir);
if ($entries === false) {
return $result;
}
foreach ($entries as $file) {
if ($file === '.' || $file === '..') {
continue;
}
$fullPath = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($fullPath)) {
$result[] = [
'name' => $file,
'children' => self::scanFolder($fullPath)
];
} elseif (is_file($fullPath) && strtolower(pathinfo($file, PATHINFO_EXTENSION)) === 'pdf') {
$result[] = [
'name' => $file,
'path' => $fullPath
];
}
}
usort($result, static fn($a, $b) => strcasecmp((string)$a['name'], (string)$b['name']));
return $result;
}
private static function saveToDb(array $items, ?int $parentId, DatabaseDriver $db): array
{
$insertedFileIds = [];
foreach ($items as $item) {
$name = $db->quote($item['name']);
$path = $db->quote($item['path'] ?? '');
$parent = $parentId !== null ? (int)$parentId : 'NULL';
$isFolder = isset($item['children']) ? 1 : 0;
$query = $db->getQuery(true)
->insert($db->quoteName('#__eis_documents'))
->columns(['name', 'path', 'parent_id', 'is_folder'])
->values("$name, $path, $parent, $isFolder");
$db->setQuery($query)->execute();
$insertedId = (int) $db->insertid();
if ($isFolder && !empty($item['children'])) {
$childIds = self::saveToDb($item['children'], $insertedId, $db);
$insertedFileIds = array_merge($insertedFileIds, $childIds);
} else {
$insertedFileIds[] = $insertedId;
}
}
return $insertedFileIds;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace EIS\Component\EIS\Administrator\View\Config;
\defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use EIS\Component\EIS\Administrator\Helper\SettingsHelper;
class HtmlView extends BaseHtmlView
{
protected $pdfPath = '';
public function display($tpl = null): void
{
$this->pdfPath = SettingsHelper::getSetting('document_root', '/var/www/pdf');
parent::display($tpl);
}
public function getPdfPath(): string
{
return (string)$this->pdfPath;
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace EIS\Component\EIS\Administrator\View\Main;
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\Database\DatabaseDriver;
use EIS\Component\EIS\Administrator\Helper\TreeHelper;
class HtmlView extends BaseHtmlView
{
/** HTML des Baums (inkl. virtuellem Ordner „Neue Dokumente“, falls vorhanden) */
protected string $treeHtml = '';
/** Liste aller Viewlevels: [ ['id'=>int, 'title'=>string], ... ] */
protected array $viewLevels = [];
/**
* Map der gesetzten ACLs für Top-Level-Ordner:
* [ folder_id (int) => viewlevel_id (int), ... ]
*/
protected array $folderAccess = [];
public function display($tpl = null): void
{
$app = Factory::getApplication();
/** @var DatabaseDriver $db */
$db = Factory::getDbo();
// 1) Baum-Daten
$items = TreeHelper::getItems();
// neu hinzugekommene IDs aus letztem Scan (können leer sein)
$newIds = (array) $app->getUserState('com_eis.new_ids', []);
// Baum rendern (virtueller Ordner „Neue Dokumente“ + regulärer Baum)
$this->treeHtml = TreeHelper::renderTreeWithNew($items, $newIds);
// 2) Viewlevels laden
$this->viewLevels = $this->loadViewLevels($db);
// 3) gesetzte ACLs für Top-Level-Ordner laden
$this->folderAccess = $this->loadFolderAccess($db);
// Template ausgeben
parent::display($tpl);
// „Neue Dokumente“ einmalig zeigen → State leeren
$app->setUserState('com_eis.new_ids', []);
}
/** Für Template: HTML des Baums */
public function getTreeHtml(): string
{
return $this->treeHtml;
}
/** Für Template: Liste der Viewlevels */
public function getViewLevels(): array
{
return $this->viewLevels;
}
/**
* Für Template/JS: Map folder_id => viewlevel_id (nur Top-Level-Ordner)
* Beispiel: [ 12 => 3, 15 => 5 ]
*/
public function getFolderAccess(): array
{
return $this->folderAccess;
}
/** Lädt alle Viewlevels sortiert nach Titel */
private function loadViewLevels(DatabaseDriver $db): array
{
$q = $db->getQuery(true)
->select($db->quoteName(['id', 'title']))
->from($db->quoteName('#__viewlevels'))
->order($db->quoteName('title') . ' ASC');
$db->setQuery($q);
$rows = (array) $db->loadAssocList();
// Safety: Normalisieren der Typen
foreach ($rows as &$r) {
$r['id'] = (int) ($r['id'] ?? 0);
$r['title'] = (string) ($r['title'] ?? '');
}
return $rows;
}
/**
* Lädt ACL-Zuweisungen aus #__eis_folder_access.
* Es werden nur Top-Level-Ordner-IDs erwartet (per Controller validiert).
*/
private function loadFolderAccess(DatabaseDriver $db): array
{
$q = $db->getQuery(true)
->select($db->quoteName(['folder_id', 'viewlevel_id']))
->from($db->quoteName('#__eis_folder_access'));
$db->setQuery($q);
$rows = (array) $db->loadAssocList();
$map = [];
foreach ($rows as $r) {
$fid = (int) ($r['folder_id'] ?? 0);
$vid = (int) ($r['viewlevel_id'] ?? 0);
if ($fid > 0 && $vid > 0) {
$map[$fid] = $vid;
}
}
return $map;
}
}

View File

@@ -0,0 +1,72 @@
<?php
\defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\CMS\HTML\HTMLHelper;
// Wert aus der View (HtmlView::getPdfPath()), Fallback leer
$pdfPath = method_exists($this, 'getPdfPath') ? (string) $this->getPdfPath() : '';
?>
<h2 class="mb-3">
<?php echo Text::_('COM_EIS_SETTINGS_TITLE') ?: 'Einstellungen'; ?>
</h2>
<form action="<?php echo Route::_('index.php?option=com_eis&task=config.save'); ?>"
method="post"
name="adminForm"
id="adminForm"
novalidate>
<div class="form-horizontal">
<fieldset class="adminform">
<legend><?php echo Text::_('COM_EIS_FIELDSET_GENERAL') ?: 'Allgemein'; ?></legend>
<div class="control-group">
<label class="control-label" for="document_root">
<?php echo Text::_('COM_EIS_FIELD_DOCUMENT_ROOT_LABEL') ?: 'Basis-Verzeichnis für PDFs'; ?>
</label>
<div class="controls">
<input
type="text"
class="form-control"
name="document_root"
id="document_root"
value="<?php echo htmlspecialchars($pdfPath, ENT_QUOTES, 'UTF-8'); ?>"
placeholder="/var/www/pdf"
size="60"
required
/>
<p class="help-block" style="margin-top:.35rem;">
<?php echo Text::_('COM_EIS_FIELD_DOCUMENT_ROOT_DESC')
?: 'Absoluter Serverpfad, in dem die PDF-Dateien liegen (z. B. /var/www/pdf).'; ?>
</p>
</div>
</div>
</fieldset>
</div>
<div class="mt-2">
<button type="submit" class="btn btn-primary">
<?php echo Text::_('JSAVE') ?: 'Speichern'; ?>
</button>
</div>
<?php echo HTMLHelper::_('form.token'); ?>
</form>
<script>
document.addEventListener('DOMContentLoaded', function () {
const form = document.getElementById('adminForm');
if (!form) return;
form.addEventListener('submit', function (e) {
const input = document.getElementById('document_root');
if (!input || !input.value.trim()) {
e.preventDefault();
alert('<?php echo Text::_('COM_EIS_MSG_INVALID_PATH') ?: 'Bitte einen gültigen Pfad angeben.'; ?>');
input?.focus();
}
});
});
</script>

View File

@@ -0,0 +1,263 @@
<?php
\defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Language\Text;
$hasTree = !empty($this->treeHtml);
// ACL-Daten aus der View (für Vorbelegung im Formular)
$viewLevels = method_exists($this, 'getViewLevels') ? (array) $this->getViewLevels() : [];
$aclMap = method_exists($this, 'getFolderAccess') ? (array) $this->getFolderAccess() : [];
?>
<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:.2rem 0 1rem}
/* Baum */
.pdf-tree{list-style:none;margin:0;padding:0}
.pdf-tree ul{list-style:none;margin-left:1.25em;padding:0;display:none}
.pdf-tree li{margin:.35em 0}
.folder>.toggle{cursor:pointer;width:1.25em;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}
.pdf-tree .is-selected{background:rgba(0,0,0,.06);border-radius:3px}
/* Steuerleiste */
.controls{margin-bottom:.75em}
.controls button{margin-right:.5em}
/* Form */
.control-group{margin-bottom:.6rem}
.form-help{color:#666;font-size:.9em}
</style>
<!-- Scan-Formular -->
<form action="<?php echo Route::_('index.php?option=com_eis&task=display.scan'); ?>" method="post" id="eis-scan-form">
<fieldset class="adminform">
<legend><?php echo Text::_('COM_EIS_DOCUMENT_PATH'); ?></legend>
<div class="form-help"><?php echo Text::_('COM_EIS_SETTINGS_TITLE'); ?> · <?php echo Text::_('COM_EIS_FIELD_DOCUMENT_ROOT_LABEL'); ?></div>
</fieldset>
<div class="mt-2">
<button class="btn btn-primary" type="submit">
<?php echo Text::_('COM_EIS_SCAN_DOCUMENTS'); ?>
</button>
</div>
<?php echo HTMLHelper::_('form.token'); ?>
</form>
<hr>
<?php if (!$hasTree): ?>
<p class="text-muted">
<?php echo Text::_('COM_EIS_NO_DOCUMENTS_FOUND'); ?>
</p>
<?php else: ?>
<h3 class="mt-3"><?php echo Text::_('COM_EIS_PDF_TREE'); ?></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::_('COM_EIS_EXPAND_ALL'); ?>
</button>
<button id="eis-collapse-all" type="button" class="btn btn-sm btn-secondary">
<?php echo Text::_('COM_EIS_COLLAPSE_ALL'); ?>
</button>
</div>
<div class="tree" id="eis-tree" role="tree" aria-label="<?php echo Text::_('COM_EIS_PDF_TREE'); ?>">
<?php echo $this->treeHtml; ?>
</div>
</div>
<!-- Rechte Seite: Inline-Bearbeitung + ACL -->
<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" novalidate>
<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">
<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 class="form-help"><?php echo Text::_('COM_EIS_FILESIZE'); ?>: <span id="eis-filesize"></span></div>
</div>
<input type="hidden" name="id" id="eis-id" value="">
<div class="mt-2">
<button type="submit" class="btn btn-success" id="eis-save" disabled>
<?php echo Text::_('JSAVE'); ?>
</button>
<button type="button" id="eis-clear-title" class="btn btn-light" disabled>
<?php echo Text::_('JDEFAULT') ?: 'Zurücksetzen'; ?>
</button>
</div>
<?php echo HTMLHelper::_('form.token'); ?>
</form>
<!-- ===== ACL: Zugriff (Top-Level-Ordner) ===== -->
<hr style="margin:1rem 0">
<h4><?php echo Text::_('COM_EIS_ACCESS_TITLE') ?: 'Zugriff (Top-Level-Ordner)'; ?></h4>
<form action="<?php echo Route::_('index.php?option=com_eis&task=display.saveAccess'); ?>"
method="post" id="eis-access-form">
<div class="control-group">
<label for="eis-folder-viewlevel">
<?php echo Text::_('COM_EIS_FIELD_VIEWLEVEL') ?: 'Zugriffsebene'; ?>
</label>
<select name="viewlevel_id" id="eis-folder-viewlevel" class="form-control">
<option value="0">(Öffentlich)</option>
<?php foreach ($viewLevels as $vl): ?>
<option value="<?php echo (int)$vl['id']; ?>">
<?php echo htmlspecialchars($vl['title'], ENT_QUOTES, 'UTF-8'); ?>
</option>
<?php endforeach; ?>
</select>
<div class="form-help">
<?php echo Text::_('COM_EIS_ACCESS_HELP') ?: 'Nur für Ordner der ersten Ebene. Unterordner und Dateien erben die Einstellung.'; ?>
</div>
</div>
<input type="hidden" name="folder_id" id="eis-folder-id" value="">
<?php echo HTMLHelper::_('form.token'); ?>
<div class="mt-2">
<button type="submit" class="btn btn-secondary" id="eis-access-save" disabled>
<?php echo Text::_('JSAVE'); ?>
</button>
</div>
</form>
<!-- ===== /ACL ===== -->
</div>
</div>
<script>
(function(){
const tree = document.getElementById('eis-tree');
const form = document.getElementById('eis-rename-form');
const idFld = document.getElementById('eis-id');
const nameFld = document.getElementById('eis-current-name');
const titleFld= document.getElementById('eis-title');
const sizeOut = document.getElementById('eis-filesize');
const btnSave = document.getElementById('eis-save');
const btnClr = document.getElementById('eis-clear-title');
// ACL-Form Elemente
const aclForm = document.getElementById('eis-access-form');
const aclFolderId = document.getElementById('eis-folder-id');
const aclSelect = document.getElementById('eis-folder-viewlevel');
const aclBtn = document.getElementById('eis-access-save');
// PHP-Map folder_id => viewlevel_id an JS übergeben
const ACL_MAP = <?php echo json_encode($aclMap, JSON_UNESCAPED_SLASHES); ?>;
if (!tree) return;
let lastSelected;
// Helper: prüfen, ob li.folder Top-Level ist
function isTopLevelFolder(li){
if (!li) return false;
const parentLi = li.parentElement ? li.parentElement.closest('li.folder') : null;
return !parentLi; // kein übergeordneter <li.folder>
}
// Toggle (Caret) & Auswahl
tree.addEventListener('click', (e) => {
const t = e.target;
// Ein-/Ausklappen
if (t.classList.contains('toggle') || t.classList.contains('folder-label')) {
const li = t.closest('li.folder');
if (li) {
const childUl = li.querySelector(':scope > ul');
if (childUl) {
const open = childUl.style.display === 'block';
childUl.style.display = open ? 'none' : 'block';
const caret = li.querySelector(':scope > .toggle');
if (caret) caret.textContent = open ? '▶' : '▼';
}
}
}
// Auswahl für Bearbeitung (Ordner- oder Datei-Label)
if (t.classList.contains('folder-label') || t.classList.contains('file-label')) {
const li = t.closest('li');
if (!li) return;
// Visuelles Highlight
if (lastSelected) lastSelected.classList.remove('is-selected');
t.classList.add('is-selected');
lastSelected = t;
// Daten aus data-Attributen
const id = li.getAttribute('data-id') || '';
const title = li.getAttribute('data-title') || '';
const name = li.getAttribute('data-name') || '';
const size = li.getAttribute('data-size') || ''; // optional
idFld.value = id;
nameFld.value = name;
titleFld.value= title;
sizeOut.textContent = size || '';
btnSave.disabled = !id;
btnClr.disabled = !id;
// ACL-Form: nur bei Top-Level-Ordner aktivieren und vorbelegen
if (li.classList.contains('folder') && isTopLevelFolder(li)) {
aclFolderId.value = id || '';
aclBtn.disabled = !aclFolderId.value;
// vorwählen
const current = (id && ACL_MAP[id]) ? String(ACL_MAP[id]) : '0';
if (aclSelect) {
for (const opt of aclSelect.options) {
opt.selected = (opt.value === current);
}
}
} else {
// kein Top-Level -> ACL-Form deaktivieren
aclFolderId.value = '';
aclBtn.disabled = true;
}
// Fokus ins Titel-Feld
titleFld.focus();
}
});
// „Zurücksetzen“ = Titel leeren
btnClr?.addEventListener('click', () => {
titleFld.value = '';
titleFld.focus();
});
// Global: 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; ?>

52
com_eis/eis.xml Normal file
View File

@@ -0,0 +1,52 @@
<extension type="component" method="upgrade">
<name>com_eis</name>
<version>1.1.3</version>
<namespace path="src">EIS\Component\EIS</namespace>
<files folder="site">
<folder>src</folder>
</files>
<administration>
<menu>COM_EIS_MENU</menu>
<submenu>
<menu link="option=com_eis&amp;view=main">COM_EIS_MAIN</menu>
<menu link="option=com_eis&amp;view=config">COM_EIS_CONFIG</menu>
</submenu>
<files folder="administrator">
<folder>src</folder>
<folder>tmpl</folder>
<folder>sql</folder>
<folder>language</folder>
<folder>services</folder>
</files>
<languages folder="administrator/language/en-GB">
<language tag="en-GB">en-GB.com_eis.ini</language>
<language tag="en-GB">en-GB.com_eis.sys.ini</language>
</languages>
<languages folder="administrator/language/de-DE">
<language tag="de-DE">de-DE.com_eis.ini</language>
<language tag="de-DE">de-DE.com_eis.sys.ini</language>
</languages>
</administration>
<install>
<sql>
<file driver="mysql" charset="utf8">sql/install.mysql.utf8.sql</file>
</sql>
</install>
<uninstall>
<sql>
<file driver="mysql" charset="utf8">sql/uninstall.mysql.utf8.sql</file>
</sql>
</uninstall>
<schemas>
<schemapath type="mysql">sql/updates/mysql</schemapath>
</schemas>
<media folder="media" destination="com_eis">
<folder>pdfjs</folder>
</media>
</extension>

BIN
com_eis/eis.zip Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,3 @@
àRCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSEáCNS2-H

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,3 @@
àRCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSEá ETen-B5-H` ^

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,4 @@
àRCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE!!<21>º]aX!!]`<60>21<32>> <09>p <0B>z<EFBFBD>$]<06>"Rd<E2809A>-Uƒ7<C692>*
4„%<25>+ „Z „{<7B>/%…<<3C>9K…b<E280A6>1]†.<2E>" ‰`]‡,<2C>"]ˆ

Some files were not shown because too many files have changed in this diff Show More