OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

versions.php

18 KB
<?php
// Standardwerte setzen
$topic = $topic ?? null;
$topic_ancestors = $topic_ancestors ?? [];
$versions = $versions ?? [];
$can_restore = $can_restore ?? false;

$this->layout('layouts/main', [
    'page_title' => 'Versionen: ' . $page['title'],
    'breadcrumbs' => [],
    'full_width' => true
]);
?>

<div class="versions-fullwidth">
    <div class="content-header">
        <div>
            <h1><span aria-hidden="true">&#128197;</span> <?= $this->te('Versionshistorie') ?></h1>
            <ol class="breadcrumb">
                <li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
                <li class="breadcrumb-item"><a href="/topics"><?= $this->te('Themengebiete') ?></a></li>
                <?php foreach ($topic_ancestors as $ancestor): ?>
                    <li class="breadcrumb-item"><a href="/topics/<?= $ancestor['id'] ?>"><?= $this->e($ancestor['name']) ?></a></li>
                <?php endforeach; ?>
                <?php if ($topic): ?>
                    <li class="breadcrumb-item"><a href="/topics/<?= $topic['id'] ?>"><?= $this->e($topic['name']) ?></a></li>
                <?php endif; ?>
                <li class="breadcrumb-item"><a href="/pages/<?= $page['id'] ?>"><?= $this->e($page['title']) ?></a></li>
                <li class="breadcrumb-item active"><?= $this->te('Versionen') ?></li>
            </ol>
        </div>
        <div class="content-header-actions">
            <a href="/pages/<?= $page['id'] ?>" class="btn btn-default btn-sm"><?= $this->te('&larr; Zurück zur Seite') ?></a>
        </div>
    </div>

    <div class="versions-body">
        <div class="versions-layout">
            <!-- Versionsliste -->
            <main class="versions-main">
                <div class="versions-list">
                    <?php foreach ($versions as $index => $version): ?>
                        <?php $isCurrent = $version['id'] == $page['current_version_id']; ?>
                        <article class="version-card <?= $isCurrent ? 'current' : '' ?>">
                            <div class="version-header">
                                <div class="version-info">
                                    <span class="version-number">Version <?= $version['version_number'] ?></span>
                                    <?php if ($isCurrent): ?>
                                        <span class="status-pill status-pill--success"><?= $this->te('Aktuell') ?></span>
                                    <?php else: ?>
                                        <span class="status-pill status-pill--neutral"><?= $this->te('Historisch') ?></span>
                                    <?php endif; ?>
                                </div>
                                <div class="version-meta">
                                    <span class="meta-date">
                                        <span aria-hidden="true">&#128197;</span> <?= date('d.m.Y H:i', strtotime($version['created_at'])) ?>
                                    </span>
                                    <span class="meta-author">
                                        <span aria-hidden="true">&#128100;</span> <?= $this->e($version['created_by_name'] ?? 'Unbekannt') ?>
                                    </span>
                                </div>
                            </div>

                            <?php if (!empty($version['change_reason'])): ?>
                                <div class="version-reason">
                                    <strong><?= $this->te('Änderung:') ?></strong> <?= $this->e($version['change_reason']) ?>
                                </div>
                            <?php endif; ?>

                            <div class="version-actions">
                                <a href="/pages/<?= $page['id'] ?>/versions/<?= $version['id'] ?>" class="btn btn-default btn-sm"
                                   aria-label="Version <?= $version['version_number'] ?> anzeigen">
                                    <span aria-hidden="true">&#128065;</span> <?= $this->te('Anzeigen') ?>
                                </a>

                                <?php if (!$isCurrent && $can_restore): ?>
                                    <form action="/pages/<?= $page['id'] ?>/versions/<?= $version['id'] ?>/restore" method="POST"
                                          class="inline-form js-restore-form" data-version="<?= $version['version_number'] ?>">
                                        <input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
                                        <button type="submit" class="btn btn-warning btn-sm"
                                                aria-label="Version <?= $version['version_number'] ?> wiederherstellen">
                                            <span aria-hidden="true">&#8635;</span> <?= $this->te('Wiederherstellen') ?>
                                        </button>
                                    </form>
                                <?php endif; ?>

                                <?php if ($index > 0): ?>
                                    <a href="/pages/<?= $page['id'] ?>/versions/compare?v1=<?= $version['id'] ?>&v2=<?= $versions[$index - 1]['id'] ?>"
                                       class="btn btn-default btn-sm"
                                       aria-label="Version <?= $version['version_number'] ?> mit Version <?= $versions[$index - 1]['version_number'] ?> vergleichen">
                                        <span aria-hidden="true">&#8596;</span> <?= $this->te('Vergleichen') ?>
                                    </a>
                                <?php endif; ?>
                            </div>
                        </article>
                    <?php endforeach; ?>

                    <?php if (empty($versions)): ?>
                        <div class="empty-state">
                            <span class="empty-icon" aria-hidden="true">&#128197;</span>
                            <h3><?= $this->te('Keine Versionen') ?></h3>
                            <p><?= $this->te('Für diese Seite wurden noch keine Versionen gespeichert.') ?></p>
                        </div>
                    <?php endif; ?>
                </div>
            </main>

            <!-- Sidebar -->
            <aside class="versions-sidebar">
                <!-- Statistik -->
                <div class="sidebar-card">
                    <h3><span aria-hidden="true">&#128202;</span> <?= $this->te('Übersicht') ?></h3>
                    <dl class="stats-list">
                        <dt><?= $this->te('Versionen') ?></dt>
                        <dd><?= count($versions) ?></dd>
                        <dt><?= $this->te('Aktuelle Version') ?></dt>
                        <dd>v<?= $page['version_number'] ?? 1 ?></dd>
                        <dt><?= $this->te('Erstellt') ?></dt>
                        <dd><?= date('d.m.Y', strtotime($page['created_at'])) ?></dd>
                    </dl>
                </div>

                <!-- Vergleichs-Tool -->
                <?php if (count($versions) >= 2): ?>
                <div class="sidebar-card">
                    <h3><span aria-hidden="true">&#8596;</span> <?= $this->te('Versionen vergleichen') ?></h3>
                    <form action="/pages/<?= $page['id'] ?>/versions/compare" method="GET" class="compare-form" id="compareForm" novalidate>
                        <div class="form-group">
                            <label for="v1"><?= $this->te('Aeltere Version:') ?></label>
                            <select name="v1" id="v1" class="form-control">
                                <?php foreach ($versions as $v): ?>
                                    <option value="<?= $v['id'] ?>">
                                        v<?= $v['version_number'] ?> (<?= date('d.m.Y', strtotime($v['created_at'])) ?>)
                                    </option>
                                <?php endforeach; ?>
                            </select>
                        </div>

                        <div class="form-group">
                            <label for="v2"><?= $this->te('Neuere Version:') ?></label>
                            <select name="v2" id="v2" class="form-control">
                                <?php foreach ($versions as $v): ?>
                                    <option value="<?= $v['id'] ?>" <?= $v['id'] == ($page['current_version_id'] ?? 0) ? 'selected' : '' ?>>
                                        v<?= $v['version_number'] ?> (<?= date('d.m.Y', strtotime($v['created_at'])) ?>)
                                    </option>
                                <?php endforeach; ?>
                            </select>
                        </div>

                        <p class="compare-error" id="compareError" role="alert" hidden></p>

                        <button type="submit" class="btn btn-primary btn-block">
                            <?= $this->te('Vergleichen') ?>
                        </button>
                    </form>
                </div>
                <?php endif; ?>

                <!-- Zurück zur Seite -->
                <div class="sidebar-card">
                    <h3><span aria-hidden="true">&#128196;</span> <?= $this->te('Seite') ?></h3>
                    <a href="/pages/<?= $page['id'] ?>" class="page-link">
                        <span class="page-icon" aria-hidden="true">&#128196;</span>
                        <span class="page-title"><?= $this->e($page['title']) ?></span>
                    </a>
                </div>
            </aside>
        </div>
    </div>
</div>

<!-- Bestaetigung fuer die Wiederherstellung -->
<div class="modal-overlay" id="restoreModal" style="display:none;" role="dialog" aria-modal="true" aria-labelledby="restoreModalTitle">
    <div class="modal-card">
        <div class="modal-header">
            <h2 id="restoreModalTitle"><?= $this->te('Version wiederherstellen') ?></h2>
            <button type="button" class="modal-close" id="restoreModalClose" aria-label="<?= $this->te('Schließen') ?>">&times;</button>
        </div>
        <div class="modal-body">
            <p id="restoreModalText"><?= $this->te('Möchten Sie diese Version wiederherstellen?') ?></p>
            <p><?= $this->t('Beim Wiederherstellen wird eine <strong>neue Version</strong> mit diesem Inhalt angelegt. Alle bisherigen Versionen bleiben erhalten.') ?></p>
        </div>
        <div class="modal-footer">
            <button type="button" class="btn btn-default" id="restoreModalCancel"><?= $this->te('Abbrechen') ?></button>
            <button type="button" class="btn btn-primary" id="restoreModalConfirm"><?= $this->te('Wiederherstellen') ?></button>
        </div>
    </div>
</div>

<style>
.versions-fullwidth {
}


/* Body */
.versions-body {
    padding: 2rem;
    background: var(--slate-50);
    min-height: calc(100vh - 300px);
}

.versions-layout {
    display: grid;
    grid-template-columns: 1fr 320px;
    gap: 2rem;
    max-width: 1200px;
    margin: 0 auto;
}

/* Main */
.versions-main {
    min-width: 0;
}

.versions-list {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

/* Version Card */
.version-card {
    background: var(--color-bg);
    border-radius: 0.75rem;
    padding: 1.25rem;
    box-shadow: 0 1px 3px rgba(0,0,0,0.05);
    border-left: 4px solid var(--gray-200);
    transition: all 0.2s;
}

.version-card:hover {
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

.version-card.current {
    border-left-color: var(--green-500);
    background: linear-gradient(135deg, var(--green-50) 0%, var(--green-100) 100%);
}

.version-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    margin-bottom: 0.75rem;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.version-info {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.version-number {
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--gray-900);
}

.version-meta {
    display: flex;
    gap: 1rem;
    font-size: 0.85rem;
    color: var(--gray-500);
}

.version-reason {
    padding: 0.75rem;
    background: var(--gray-50);
    border-radius: 0.5rem;
    font-size: 0.9rem;
    color: var(--gray-700);
    margin-bottom: 0.75rem;
}

.version-actions {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.inline-form {
    display: inline;
}

/* Sidebar */
.versions-sidebar {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.sidebar-card {
    background: var(--color-bg);
    border-radius: 0.75rem;
    padding: 1.25rem;
    box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}

.sidebar-card h3 {
    margin: 0 0 1rem 0;
    font-size: 0.9rem;
    font-weight: 600;
    color: var(--gray-700);
}

/* Stats List */
.stats-list {
    margin: 0;
}

.stats-list dt {
    font-size: 0.75rem;
    color: var(--gray-500);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin-top: 0.75rem;
}

.stats-list dt:first-child {
    margin-top: 0;
}

.stats-list dd {
    margin: 0.25rem 0 0 0;
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--gray-900);
}


.compare-form label {
    display: block;
    font-size: 0.85rem;
    color: var(--gray-700);
    margin-bottom: 0.375rem;
}

/* Hinweis bei ungueltiger Versionsauswahl */
.compare-error {
    margin: 0 0 0.75rem 0;
    font-size: 0.85rem;
    color: var(--red-600);
}

.compare-error[hidden] {
    display: none;
}

/* Page Link */
.page-link {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem;
    background: var(--gray-50);
    border-radius: 0.5rem;
    text-decoration: none;
    transition: all 0.15s;
}

.page-link:hover {
    background: var(--blue-50);
    text-decoration: none;
}

.page-icon {
    font-size: 1.5rem;
}

.page-title {
    font-weight: 500;
    color: var(--gray-900);
}
.empty-state h3 {
    margin: 0 0 0.5rem 0;
    font-size: 1.1rem;
    color: var(--gray-700);
}
/* Responsive */
@media (max-width: 1024px) {
    .versions-layout {
        grid-template-columns: 1fr;
    }

    .versions-sidebar {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    }
}

@media (max-width: 768px) {
    .version-header {
        flex-direction: column;
    }

    .version-meta {
        flex-direction: column;
        gap: 0.25rem;
    }

    .versions-sidebar {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 480px) {
    .version-card {
        padding: 1rem;
    }

    .version-number {
        font-size: 1rem;
    }

    .version-actions .btn-sm {
        font-size: 0.75rem;
        padding: 0.3rem 0.5rem;
    }

    .sidebar-card {
        padding: 1rem;
    }
}
</style>

<script>
(function () {
    /* Vergleichs-Formular: zwei verschiedene Versionen erzwingen und
       die Auswahl vor dem Absenden auf das Paar aelter -> neuer normalisieren.
       Beide Selects listen die Versionen in derselben Reihenfolge (neueste zuerst),
       daher gilt: groesserer Options-Index = aeltere Version. */
    var compareForm = document.getElementById('compareForm');
    if (compareForm) {
        var v1 = document.getElementById('v1');
        var v2 = document.getElementById('v2');
        var compareError = document.getElementById('compareError');

        var clearError = function () {
            if (compareError) {
                compareError.hidden = true;
                compareError.textContent = '';
            }
        };

        if (v1 && v2) {
            v1.addEventListener('change', clearError);
            v2.addEventListener('change', clearError);
        }

        compareForm.addEventListener('submit', function (event) {
            if (!v1 || !v2) return;

            if (v1.value === v2.value) {
                event.preventDefault();
                if (compareError) {
                    compareError.textContent = 'Bitte zwei unterschiedliche Versionen auswählen.';
                    compareError.hidden = false;
                }
                v1.focus();
                return;
            }

            clearError();

            if (v1.selectedIndex < v2.selectedIndex) {
                var older = v2.value;
                var newer = v1.value;
                v1.value = older;
                v2.value = newer;
            }
        });
    }

    /* Wiederherstellen: Bestaetigung ueber das zentrale Modal */
    var restoreModal = document.getElementById('restoreModal');
    if (!restoreModal) return;

    var restoreText = document.getElementById('restoreModalText');
    var confirmBtn = document.getElementById('restoreModalConfirm');
    var cancelBtn = document.getElementById('restoreModalCancel');
    var closeBtn = document.getElementById('restoreModalClose');
    var pendingForm = null;

    function openModal(form) {
        pendingForm = form;
        if (restoreText) {
            var versionNumber = form.getAttribute('data-version');
            restoreText.textContent = versionNumber
                ? 'Möchten Sie Version ' + versionNumber + ' wiederherstellen?'
                : 'Möchten Sie diese Version wiederherstellen?';
        }
        restoreModal.style.display = 'flex';
        if (confirmBtn) confirmBtn.focus();
    }

    function closeModal() {
        restoreModal.style.display = 'none';
        pendingForm = null;
    }

    Array.prototype.forEach.call(document.querySelectorAll('.js-restore-form'), function (form) {
        form.addEventListener('submit', function (event) {
            event.preventDefault();
            openModal(form);
        });
    });

    if (confirmBtn) {
        confirmBtn.addEventListener('click', function () {
            if (!pendingForm) return;
            var form = pendingForm;
            closeModal();
            form.submit();
        });
    }

    if (cancelBtn) cancelBtn.addEventListener('click', closeModal);
    if (closeBtn) closeBtn.addEventListener('click', closeModal);

    restoreModal.addEventListener('click', function (event) {
        if (event.target === restoreModal) closeModal();
    });

    document.addEventListener('keydown', function (event) {
        if (event.key === 'Escape' && restoreModal.style.display === 'flex') closeModal();
    });
})();
</script>