OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

edit.php

25 KB
<?php
$isEdit = isset($page) && $page !== null;
$pageTitle = $isEdit ? 'Seite bearbeiten' : 'Neue Seite';

$_wurzel = dirname(__DIR__, 2);

$this->layout('layouts/main', [
    'page_title'  => $pageTitle,
    'breadcrumbs' => [],
    'full_width'  => true,
    'extra_css'   => ['/assets/css/page-editor.css?v=' . filemtime($_wurzel . '/public/assets/css/page-editor.css')],
    'extra_js'    => ['/assets/js/page-editor.js?v='  . filemtime($_wurzel . '/public/assets/js/page-editor.js')],
]);
?>

<div class="page-editor">
    <!-- Custom Breadcrumb -->
    <nav class="editor-breadcrumb">
        <a href="/"><?= $this->te('Start') ?></a>
        <span class="sep">/</span>
        <a href="/topics"><?= $this->te('Themengebiete') ?></a>
        <?php if (isset($topic)): ?>
            <span class="sep">/</span>
            <a href="/topics/<?= $topic['id'] ?>"><?= $this->e($topic['name']) ?></a>
        <?php endif; ?>
        <?php if ($isEdit): ?>
            <span class="sep">/</span>
            <a href="/pages/<?= $page['id'] ?>"><?= $this->e($page['title']) ?></a>
            <span class="sep">/</span>
            <span class="current"><?= $this->te('Bearbeiten') ?></span>
        <?php else: ?>
            <span class="sep">/</span>
            <span class="current"><?= $this->te('Neue Seite') ?></span>
        <?php endif; ?>
    </nav>

    <form action="<?= $isEdit ? '/pages/' . $page['id'] : '/pages' ?>" method="POST"
          class="editor-form" id="page-form" enctype="multipart/form-data">
        <input type="hidden" name="_csrf" value="<?= $this->csrf() ?>">
        <?php if ($isEdit): ?>
            <input type="hidden" name="_method" value="PUT">
        <?php endif; ?>

        <!-- Header Bar -->
        <header class="editor-topbar">
            <div class="topbar-left">
                <a href="<?= $isEdit ? '/pages/' . $page['id'] : '/topics/' . ($topic['id'] ?? '') ?>" class="btn-back" title="<?= $this->te('Zurueck') ?>">
                    &larr;
                </a>
                <div class="page-title-wrapper">
                    <input type="text" name="title" id="title" class="page-title-input"
                           value="<?= $this->e($page['title'] ?? '') ?>"
                           placeholder="<?= $this->te('Seitentitel eingeben...') ?>"
                           required maxlength="500" autofocus>
                </div>
            </div>
            <div class="topbar-right">
                <?php if ($isEdit): ?>
                    <span id="autosave-status" class="autosave-status autosave-status-idle" style="display:none;"></span>
                    <span id="change-reason-hint" class="change-reason-hint" style="display:none;">
                        <i class="fas fa-pen"></i> <?= $this->te('Änderungsgrund wird abgefragt') ?>
                    </span>
                    <span class="version-indicator">v<?= $page['version_number'] ?? 1 ?></span>
                <?php endif; ?>
                <button type="submit" name="action" value="save" class="btn btn-primary btn-save">
                    <i class="fas fa-save"></i> <?= $isEdit ? 'Speichern' : 'Erstellen' ?>
                </button>
            </div>
        </header>

        <!-- Main Editor Area -->
        <div class="editor-layout">
            <!-- Editor Main -->
            <div class="editor-main">
                <!-- TinyMCE Editor -->
                <div class="editor-wrapper">
                    <textarea name="content" id="content-editor"><?= $this->e($this->fixRelativeUrls($page['content_html'] ?? ($template_content ?? ''))) ?></textarea>
                </div>

                <?php if ($isEdit): ?>
                <!-- Verstecktes Feld – wird per Modal befüllt -->
                <input type="hidden" id="change_reason" name="change_reason" value="<?= $this->e($pending_change_reason ?? '') ?>">

                <?php endif; ?>
            </div>

            <!-- Sidebar -->
            <aside class="editor-sidebar" id="editorSidebar">
                <div class="sidebar-toggle">
                    <button type="button" class="sidebar-toggle-btn" id="sidebarToggleBtn" title="<?= $this->te('Sidebar ein-/ausklappen') ?>">
                        <span class="toggle-icon">&raquo;</span>
                    </button>
                </div>
                <!-- Vorlagen-Auswahl (nur bei neuer Seite) -->
                <?php if (!$isEdit && !empty($page_templates ?? [])): ?>
                <div class="sidebar-section">
                    <div class="section-header">
                        <span class="section-icon"><i class="fas fa-clipboard-list" aria-hidden="true"></i></span>
                        <?= $this->te('Vorlage') ?>
                    </div>
                    <div class="section-content">
                        <select id="template-select" class="form-select">
                            <option value=""><?= $this->te('Keine Vorlage') ?></option>
                            <?php foreach ($page_templates as $category => $tpls): ?>
                            <optgroup label="<?= $this->e($category) ?>">
                                <?php foreach ($tpls as $tpl): ?>
                                <option value="<?= $tpl['id'] ?>" data-icon="<?= $this->e($tpl['icon']) ?>" title="<?= $this->e($tpl['description'] ?? '') ?>">
                                    <?= $tpl['icon'] ?> <?= $this->e($tpl['name']) ?>
                                </option>
                                <?php endforeach; ?>
                            </optgroup>
                            <?php endforeach; ?>
                        </select>
                        <small class="form-help"><?= $this->te('Vorlage als Startinhalt laden.') ?></small>
                    </div>
                </div>
                <?php endif; ?>

                <!-- Topic Selection -->
                <div class="sidebar-section">
                    <div class="section-header">
                        <span class="section-icon"><i class="fas fa-folder" aria-hidden="true"></i></span>
                        <?= $this->te('Themengebiet') ?>
                    </div>
                    <?php if ($isEdit): ?>
                        <div class="section-content">
                            <select name="topic_id" class="form-select">
                                <?php foreach ($topics ?? [] as $t): ?>
                                    <?php
                                    $indent = str_repeat('-- ', $t['depth'] ?? 0);
                                    $selected = ($t['id'] == ($topic['id'] ?? '')) ? 'selected' : '';
                                    ?>
                                    <option value="<?= $t['id'] ?>" <?= $selected ?>>
                                        <?= $indent ?><?= $this->e($t['name']) ?>
                                    </option>
                                <?php endforeach; ?>
                            </select>
                        </div>
                    <?php else: ?>
                        <div class="section-content">
                            <select name="topic_id" id="topic_id" class="form-select" required>
                                <option value=""><?= $this->te('Themengebiet waehlen...') ?></option>
                                <?php foreach ($topics ?? [] as $t): ?>
                                    <?php
                                    $indent = str_repeat('-- ', $t['depth'] ?? 0);
                                    $selected = ($t['id'] == ($topic['id'] ?? (isset($_GET['topic_id']) ? (int)$_GET['topic_id'] : null))) ? 'selected' : '';
                                    ?>
                                    <option value="<?= $t['id'] ?>" <?= $selected ?>>
                                        <?= $indent ?><?= $this->e($t['name']) ?>
                                    </option>
                                <?php endforeach; ?>
                            </select>
                        </div>
                    <?php endif; ?>
                </div>

                <!-- Self-Service-Portal: Freigabe je Einzelseite (keine Vererbung) -->
                <?php if (!empty($portal_enabled)): ?>
                <div class="sidebar-section">
                    <div class="section-header">
                        <span class="section-icon"><i class="fas fa-home" aria-hidden="true"></i></span>
                        <?= $this->te('Self-Service-Portal') ?>
                    </div>
                    <div class="section-content">
                        <label style="display:flex;gap:.5rem;align-items:flex-start;font-weight:normal;cursor:pointer;">
                            <input type="checkbox" name="portal_published" value="1" style="margin-top:.2rem;"
                                   <?= !empty($page['portal_published']) ? 'checked' : '' ?>>
                            <span><?= $this->te('Als Anleitung veröffentlichen') ?>
                                <small style="display:block;color:var(--color-text-muted);"><?= $this->te('Für alle Portal-Nutzer sichtbar. Gilt nur für diese Seite – Unterseiten bleiben intern.') ?></small>
                            </span>
                        </label>
                    </div>
                </div>
                <?php endif; ?>

                <!-- Ansprechpartner -->
                <?php if ($this->hasCapability('view_contacts')): ?>
                <div class="sidebar-section">
                    <div class="section-header">
                        <span class="section-icon"><i class="fas fa-user" aria-hidden="true"></i></span>
                        <?= $this->te('Ansprechpartner') ?>
                    </div>
                    <div class="section-content">
                        <input type="hidden" name="ansprechpartner_type" id="ap-type"
                               value="<?= $this->e(($ansprechpartner['_type'] ?? '') ?: '') ?>">
                        <input type="hidden" name="ansprechpartner_id" id="ap-id"
                               value="<?= (int)($ansprechpartner['id'] ?? 0) ?>">

                        <!-- Aktuelle Auswahl -->
                        <div id="ap-current" style="<?= empty($ansprechpartner) ? 'display:none' : '' ?>">
                            <div class="ap-selected">
                                <span class="ap-selected-name" id="ap-selected-name">
                                    <?php if (!empty($ansprechpartner)): ?>
                                        <?php if (($ansprechpartner['_type'] ?? '') === 'contact'): ?>
                                            <?= $this->e($ansprechpartner['first_name'] . ' ' . $ansprechpartner['last_name']) ?>
                                            <?php if (!empty($ansprechpartner['company_name'])): ?>
                                                <small>(<?= $this->e($ansprechpartner['company_name']) ?>)</small>
                                            <?php endif; ?>
                                        <?php elseif (($ansprechpartner['_type'] ?? '') === 'user'): ?>
                                            <?= $this->e($ansprechpartner['display_name'] ?? $ansprechpartner['name'] ?? '') ?>
                                            <small style="color:var(--gray-500);"><?= $this->te('(Intern)') ?></small>
                                        <?php else: ?>
                                            <?= $this->e($ansprechpartner['name'] ?? '') ?>
                                        <?php endif; ?>
                                    <?php endif; ?>
                                </span>
                                <button type="button" class="ap-remove-btn" id="ap-remove" title="<?= $this->te('Entfernen') ?>">&times;</button>
                            </div>
                        </div>

                        <!-- Suchfeld -->
                        <div id="ap-search-wrapper" style="<?= !empty($ansprechpartner) ? 'display:none' : '' ?>">
                            <input type="text" id="ap-search" placeholder="<?= $this->te('Kontakt oder Firma suchen...') ?>"
                                   class="ap-search-input" autocomplete="off">
                            <div id="ap-search-results" class="ap-search-results" style="display:none;"></div>
                        </div>
                    </div>
                </div>
                <?php endif; ?>

                <!-- Tags -->
                <div class="sidebar-section">
                    <div class="section-header">
                        <span class="section-icon"><i class="fas fa-tags" aria-hidden="true"></i></span>
                        <?= $this->te('Tags') ?>
                    </div>
                    <div class="section-content">
                        <div class="tag-select-wrapper" id="tagSelectWrapper">
                            <div class="tag-selected-list" id="tagSelectedList">
                                <?php foreach ($page_tags ?? [] as $tag): ?>
                                <?php $tagColor = $this->safeHexColor($tag['color'] ?? null); ?>
                                <span class="tag-chip" data-id="<?= $tag['id'] ?>" style="background:<?= $tagColor ?>20;color:<?= $tagColor ?>;border:1px solid <?= $tagColor ?>40;">
                                    <?= $this->e($tag['name']) ?>
                                    <button aria-label="<?= $this->te('Löschen') ?>" type="button" class="tag-chip-remove" data-id="<?= $tag['id'] ?>">&times;</button>
                                    <input type="hidden" name="tags[]" value="<?= $tag['id'] ?>">
                                </span>
                                <?php endforeach; ?>
                            </div>
                            <input type="text" id="tagSearchInput" class="tag-search-input"
                                   placeholder="<?= $this->te('Tag suchen oder erstellen...') ?>" autocomplete="off">
                            <div class="tag-dropdown" id="tagDropdown" style="display:none;">
                                <?php foreach ($all_tags ?? [] as $tag): ?>
                                <?php $tagColor = $this->safeHexColor($tag['color'] ?? null); ?>
                                <div class="tag-dropdown-item" data-id="<?= $tag['id'] ?>" data-name="<?= $this->e($tag['name']) ?>" data-color="<?= $tagColor ?>">
                                    <span class="tag-dropdown-color" style="background:<?= $tagColor ?>;"></span>
                                    <?= $this->e($tag['name']) ?>
                                    <span class="tag-dropdown-count"><?= $tag['usage_count'] ?? 0 ?></span>
                                </div>
                                <?php endforeach; ?>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Attachments -->
                <div class="sidebar-section">
                    <div class="section-header">
                        <span class="section-icon"><i class="fas fa-paperclip" aria-hidden="true"></i></span>
                        <?= $this->te('Anhaenge') ?> <span class="file-counter" id="file-counter">(<?= count($files ?? []) ?>)</span>
                    </div>
                    <div class="section-content">
                        <ul class="file-list" id="file-list">
                            <?php if (!empty($files)): ?>
                                <?php foreach ($files as $file): ?>
                                <li class="file-item" data-file-id="<?= $file['id'] ?>">
                                    <span class="file-icon"><i class="fas fa-file" aria-hidden="true"></i></span>
                                    <a href="/files/<?= $file['id'] ?>" class="file-name" target="_blank"><?= $this->e($file['original_filename']) ?></a>
                                    <span class="file-size"><?= number_format($file['file_size'] / 1024, 1) ?> KB</span>
                                    <button type="button" class="file-delete" data-file-id="<?= $file['id'] ?>" title="<?= $this->te('Loeschen') ?>">&times;</button>
                                </li>
                                <?php endforeach; ?>
                            <?php endif; ?>
                        </ul>
                        <?php if ($isEdit): ?>
                        <div class="file-dropzone" id="file-dropzone">
                            <input type="file" id="file-input" multiple class="file-input">
                            <label for="file-input" class="dropzone-label">
                                <span class="dropzone-icon"><i class="fas fa-upload" aria-hidden="true"></i></span>
                                <span><?= $this->te('Dateien hochladen') ?></span>
                            </label>
                        </div>
                        <div class="upload-progress" id="upload-progress" style="display: none;">
                            <div class="progress-bar"><div class="progress-fill" id="progress-fill"></div></div>
                            <span class="progress-text" id="progress-text"><?= $this->te('Hochladen...') ?></span>
                        </div>
                        <?php else: ?>
                        <p class="upload-hint"><?= $this->te('Speichern Sie die Seite zuerst, um Dateien hochzuladen.') ?></p>
                        <?php endif; ?>
                    </div>
                </div>

                <!-- Version History -->
                <?php if ($isEdit): ?>
                <div class="sidebar-section">
                    <div class="section-header">
                        <span class="section-icon"><i class="fas fa-history" aria-hidden="true"></i></span>
                        <?= $this->te('Versionen') ?>
                    </div>
                    <div class="section-content">
                        <a href="/pages/<?= $page['id'] ?>/versions" class="btn btn-secondary btn-sm btn-block">
                            <?= $this->te('Versionshistorie anzeigen') ?>
                        </a>
                    </div>
                </div>
                <?php endif; ?>

                <!-- Seite löschen (via JS, nicht als nested form!) -->
                <?php if ($isEdit && ($can_delete ?? false)): ?>
                <div class="sidebar-section sidebar-section-danger">
                    <div class="section-header section-header-danger">
                        <span class="section-icon"><i class="fas fa-exclamation-triangle" aria-hidden="true"></i></span>
                        <?= $this->te('Gefahrenzone') ?>
                    </div>
                    <div class="section-content">
                        <button type="button" class="btn btn-danger btn-sm btn-block" onclick="deletePageAction()">
                            <i class="fas fa-trash" aria-hidden="true"></i> <?= $this->te('Seite l&ouml;schen') ?>
                        </button>
                    </div>
                </div>
                <?php endif; ?>
            </aside>
        </div>
    </form>

    <?php if ($isEdit && !empty($draft_content) && !empty($draft_saved_at)): ?>
    <!-- Entwurf-Wiederherstellungs-Modal (öffnet automatisch beim Laden) -->
    <div id="draft-modal" class="modal-overlay cr-modal" role="dialog" aria-modal="true">
        <div class="modal-card">
            <div class="modal-header">
                <h2><i class="fas fa-history"></i> <?= $this->te('Ungespeicherter Entwurf gefunden') ?></h2>
            </div>
            <div class="modal-body">
                <p class="cr-modal-desc">
                    <?= $this->t('Es gibt noch einen automatisch gespeicherten Entwurf vom<br> <strong>{zeitpunkt}</strong>.', ['zeitpunkt' => date('d.m.Y \u\m H:i \U\h\r', strtotime($draft_saved_at))]) ?>
                </p>
                <p class="cr-modal-desc" style="color:var(--gray-500);font-size:0.82rem;margin-bottom:0;">
                    <?= $this->te('Möchten Sie diesen Entwurf wiederherstellen oder mit dem aktuell gespeicherten Inhalt weiterarbeiten?') ?>
                </p>
            </div>
            <div class="modal-footer">
                <button type="button" id="draft-discard-btn" class="btn btn-default">
                    <i class="fas fa-times"></i> <?= $this->te('Nein, verwerfen') ?>
                </button>
                <button type="button" id="draft-restore-btn" class="btn btn-warning">
                    <i class="fas fa-undo"></i> <?= $this->te('Ja, wiederherstellen') ?>
                </button>
            </div>
        </div>
    </div>
    <?php endif; ?>

    <?php if ($isEdit): ?>
    <!-- Änderungsgrund-Modal -->
    <div id="change-reason-modal" class="modal-overlay cr-modal" style="display:none;" role="dialog" aria-modal="true" aria-labelledby="cr-modal-title">
        <div class="modal-card">
            <div class="modal-header">
                <h2 id="cr-modal-title"><i class="fas fa-edit"></i> <?= $this->te('Was wurde geändert?') ?></h2>
            </div>
            <div class="modal-body">
                <p class="cr-modal-desc"><?= $this->te('Kurze Beschreibung der Änderungen für die Versionshistorie:') ?></p>
                <label for="cr-modal-input"><?= $this->te('Änderungsgrund') ?> <span class="text-danger">*</span></label>
                <input type="text" id="cr-modal-input" class="form-control"
                       placeholder="<?= $this->te('z.B. Tippfehler korrigiert, Abschnitt ergänzt…') ?>"
                       minlength="3" maxlength="500" autocomplete="off">
                <div id="cr-modal-error" class="cr-modal-error" style="display:none;">
                    <i class="fas fa-exclamation-circle"></i> <?= $this->te('Bitte einen Änderungsgrund mit mindestens 3 Zeichen angeben.') ?>
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" id="cr-modal-cancel" class="btn btn-default"><?= $this->te('Abbrechen') ?></button>
                <button type="button" id="cr-modal-confirm" class="btn btn-primary">
                    <i class="fas fa-save"></i> <?= $this->te('Speichern') ?>
                </button>
            </div>
        </div>
    </div>
    <?php endif; ?>
</div>

<?php if ($topics_ai_enabled ?? false): ?>
<!-- KI-Assistent Modal -->
<div id="ai-editor-modal" class="modal-overlay ai-modal" style="display:none;" role="dialog" aria-modal="true" aria-labelledby="ai-modal-title">
    <div class="modal-card">
        <div class="modal-header">
            <h2 id="ai-modal-title"><?= $this->te('✨ KI-Assistent') ?></h2>
            <button type="button" class="modal-close" id="ai-modal-close" title="<?= $this->te('Schließen') ?>">&times;</button>
        </div>
        <div class="modal-body">
            <!-- Prompt -->
            <div class="form-group">
                <label for="ai-prompt"><?= $this->te('Anweisung') ?></label>
                <p class="form-hint" style="margin:0 0 0.5rem;"><?= $this->te('Beschreibe was du möchten – Inhalt erstellen, bearbeiten oder ergänzen') ?></p>
                <textarea id="ai-prompt" class="form-control"
                          placeholder="<?= $this->te('z.B.: Erstelle eine Schritt-für-Schritt-Anleitung für das Backup-Verfahren&#10;z.B.: Ergänze einen Abschnitt über Troubleshooting&#10;z.B.: Formuliere den Text professioneller und kürzer') ?>"
                          rows="5" maxlength="1000"></textarea>
                <small class="form-hint"><?= $this->te('Ctrl+Enter zum Absenden &middot; Max. 1000 Zeichen &middot; Der gesamte Editor-Inhalt wird als Kontext übergeben') ?></small>
            </div>

            <!-- Fehler -->
            <div id="ai-modal-error" class="alert alert-danger" style="display:none;margin:0;"></div>
        </div>
        <div class="modal-footer">
            <button type="button" class="btn btn-default" id="ai-modal-cancel"><?= $this->te('Abbrechen') ?></button>
            <button type="button" class="btn btn-primary" id="ai-modal-submit">
                <span id="ai-modal-submit-text"><?= $this->te('✨ Generieren') ?></span>
                <span id="ai-modal-loading" style="display:none;">
                    <span class="ai-spinner"></span> <?= $this->te('KI arbeitet&hellip;') ?>
                </span>
            </button>
        </div>
    </div>
</div>
<?php endif; // topics_ai_enabled ?>

<?php
/**
 * Angaben, die das Editor-Skript vom Server braucht. Als JSON im Markup, damit
 * das Skript selbst statisch ausgeliefert und zwischengespeichert werden kann.
 */
$_editorDaten = [
    'isEdit'       => $isEdit,
    'tinymce'      => TINYMCE_INSTALLED,
    'aiEnabled'    => (bool)($topics_ai_enabled ?? false),
    'canDelete'    => $isEdit && (bool)($can_delete ?? false),
    'pageId'       => $isEdit ? (int)$page['id'] : 0,
    'draftContent' => $draft_content ?? null,
];
?>
<script type="application/json" id="page-editor-data">
<?= json_encode($_editorDaten, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>
</script>

<?php if (TINYMCE_INSTALLED): ?>
<!-- TinyMCE -->
<script src="<?= $this->asset('/assets/tinymce/tinymce.min.js') ?>"></script>
<?php else: ?>
<!-- wysiwyg.js Fallback -->
<link rel="stylesheet" href="<?= $this->asset('/assets/css/wysiwyg.css') ?>">
<script src="<?= $this->asset('/assets/js/wysiwyg.js') ?>"></script>
<?php endif; ?>



<?php if ($isEdit): ?>
<div class="lock-banner" id="lock-banner" style="display:none">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="16" height="16"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
    <span id="lock-banner-text"><?= $this->te('Seite wird gesperrt…') ?></span>
</div>


<?php endif; ?>