Quelltext des aktuellen Standes
<?php
$this->layout('layouts/main', [
'page_title' => $page_title,
'current_route' => $current_route,
'full_width' => true,
'extra_css' => ['/assets/css/isms.css?v=' . filemtime(dirname(__DIR__, 2) . '/public/assets/css/isms.css')],
]);
use OpenNIT\Isms\IsmsManager;
$controls = $controls ?? [];
$stats = $stats ?? ['total' => 0, 'applicable' => 0, 'implemented' => 0, 'in_progress' => 0, 'coverage' => 0];
$activeTheme = $active_theme ?? '';
$users = $users ?? [];
$pages = $pages ?? [];
$canManage = $can_manage ?? false;
$view = $this;
$statusClass = [
'not_implemented' => 'status-pill--danger',
'in_progress' => 'status-pill--warning',
'implemented' => 'status-pill--success',
'not_applicable' => 'status-pill--neutral',
];
?>
<div class="ism-wrap">
<div class="content-header">
<div>
<h1><?= $this->te('Statement of Applicability') ?></h1>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/"><?= $this->te('Home') ?></a></li>
<li class="breadcrumb-item"><a href="/isms">ISMS</a></li>
<li class="breadcrumb-item active"><?= $this->te('Annex A (SoA)') ?></li>
</ol>
</div>
<div class="content-header-actions">
<a class="btn btn-default btn-sm" href="/isms/export/soa.csv" title="<?= $this->te('SoA als CSV') ?>"><i class="fas fa-file-csv"></i> <?= $this->te('CSV-Export') ?></a>
</div>
</div>
<div class="page-body">
<div class="kpi-row">
<div class="kpi-col">
<div class="small-box bg-info">
<div class="inner"><h3><?= (int)$stats['total'] ?></h3><p><?= $this->te('Controls (ISO 27001:2022)') ?></p></div>
<div class="icon"><i class="fas fa-tasks"></i></div>
</div>
</div>
<div class="kpi-col">
<div class="small-box bg-primary">
<div class="inner"><h3><?= (int)$stats['applicable'] ?></h3><p><?= $this->te('Anwendbar') ?></p></div>
<div class="icon"><i class="fas fa-check-double"></i></div>
</div>
</div>
<div class="kpi-col">
<div class="small-box bg-warning">
<div class="inner"><h3><?= (int)$stats['in_progress'] ?></h3><p><?= $this->te('In Umsetzung') ?></p></div>
<div class="icon"><i class="fas fa-spinner"></i></div>
</div>
</div>
<div class="kpi-col">
<div class="small-box bg-success">
<div class="inner"><h3><?= (int)$stats['coverage'] ?>%</h3><p><?= $this->te('Umgesetzt') ?></p></div>
<div class="icon"><i class="fas fa-shield-alt"></i></div>
</div>
</div>
</div>
<div class="subnav">
<a href="/isms" class="subnav-tab"><?= $this->te('Risikoregister') ?></a>
<a href="/isms/controls" class="subnav-tab active"><?= $this->te('Statement of Applicability (Annex A)') ?></a>
<a href="/isms/reviews" class="subnav-tab"><?= $this->te('Management-Review') ?></a>
<a href="/isms/guide" class="subnav-tab"><?= $this->te('Anleitung') ?></a>
</div>
<!-- Themen-Filter (Styleguide § 8) -->
<div class="card card-outline card-primary mb-3">
<div class="filter-card-body ism-toolbar">
<a href="/isms/controls" class="ism-tag-chip <?= $activeTheme === '' ? 'active' : '' ?>"><?= $this->te('Alle Themen') ?></a>
<?php foreach (IsmsManager::THEMES as $key => $label): ?>
<a href="/isms/controls?theme=<?= urlencode($key) ?>" class="ism-tag-chip <?= $activeTheme === $key ? 'active' : '' ?>"><?= $view->e($label) ?></a>
<?php endforeach; ?>
</div>
</div>
<div class="table-wrapper">
<table class="data-table">
<thead>
<tr>
<th scope="col"><?= $this->te('Ref') ?></th>
<th scope="col"><?= $this->te('Control') ?></th>
<th scope="col"><?= $this->te('Thema') ?></th>
<th scope="col"><?= $this->te('Anwendbar') ?></th>
<th scope="col"><?= $this->te('Status') ?></th>
<th scope="col"><?= $this->te('Owner') ?></th>
<?php if ($canManage): ?><th scope="col"></th><?php endif; ?>
</tr>
</thead>
<tbody>
<?php foreach ($controls as $c): ?>
<tr id="<?= $view->e($c['ref']) ?>">
<td><strong><?= $view->e($c['ref']) ?></strong></td>
<td>
<?= $view->e($c['title']) ?>
<?php if (!empty($c['justification'])): ?><div class="ism-host-sub" title="<?= $this->te('Begründung') ?>"><?= $view->e($c['justification']) ?></div><?php endif; ?>
<?php if (!empty($c['policy_page_id'])): ?><div class="ism-host-sub"><i class="fas fa-file-alt"></i> <a href="/pages/<?= (int)$c['policy_page_id'] ?>" class="table-link"><?= $view->e($c['policy_page_title'] ?: 'Richtlinie') ?></a></div><?php endif; ?>
</td>
<td><span class="ism-host-sub"><?= $view->e(IsmsManager::THEMES[$c['theme']] ?? $c['theme']) ?></span></td>
<td><?= (int)$c['applicable'] === 1 ? '<span class="status-pill status-pill--success">Ja</span>' : '<span class="status-pill status-pill--neutral">Nein</span>' ?></td>
<td><span class="status-pill <?= $statusClass[$c['status']] ?? 'status-pill--neutral' ?>"><?= $view->e(IsmsManager::CONTROL_STATUS[$c['status']] ?? $c['status']) ?></span></td>
<td><?= $view->e($c['owner_name'] ?: '—') ?></td>
<?php if ($canManage): ?>
<td>
<button type="button" class="btn btn-default btn-xs"
onclick='ismEditControl(<?= json_encode([
"id" => (int)$c["id"],
"ref" => $c["ref"],
"title" => $c["title"],
"applicable" => (int)$c["applicable"],
"status" => $c["status"],
"justification" => $c["justification"] ?? "",
"notes" => $c["notes"] ?? "",
"owner_user_id" => (int)($c["owner_user_id"] ?? 0),
"policy_page_id" => (int)($c["policy_page_id"] ?? 0),
], JSON_HEX_APOS | JSON_HEX_QUOT) ?>)'><?= $this->te('Bearbeiten') ?></button>
</td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<?php if ($canManage): ?>
<!-- Shared Edit-Modal -->
<div id="ismControlModal" class="modal-overlay" role="dialog" aria-modal="true" style="display:none;" data-modal>
<div class="modal-card">
<div class="modal-header">
<h2 id="ismControlTitle"><?= $this->te('Control bearbeiten') ?></h2>
<button type="button" class="modal-close" onclick="document.getElementById('ismControlModal').style.display='none'" aria-label="<?= $this->te('Schließen') ?>">×</button>
</div>
<form method="post" id="ismControlForm">
<input type="hidden" name="_csrf" value="<?= $view->e($csrf_token) ?>">
<div class="modal-body">
<div class="ism-form-row">
<div class="form-group ism-form-col">
<label for="c-applicable"><?= $this->te('Anwendbar (SoA)') ?></label>
<select id="c-applicable" name="applicable" class="form-control">
<option value="1"><?= $this->te('Ja') ?></option>
<option value="0"><?= $this->te('Nein') ?></option>
</select>
</div>
<div class="form-group ism-form-col">
<label for="c-status"><?= $this->te('Umsetzungsstatus') ?></label>
<select id="c-status" name="status" class="form-control">
<?php foreach (IsmsManager::CONTROL_STATUS as $key => $lbl): ?>
<option value="<?= $view->e($key) ?>"><?= $view->e($lbl) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="form-group">
<label for="c-owner"><?= $this->te('Verantwortlich') ?></label>
<select id="c-owner" name="owner_user_id" class="form-control">
<option value=""><?= $this->te('— Owner —') ?></option>
<?php foreach ($users as $u): ?>
<option value="<?= (int)$u['id'] ?>"><?= $view->e($u['display_name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="c-just"><?= $this->te('Begründung (SoA)') ?></label>
<textarea id="c-just" name="justification" class="form-control" rows="2" placeholder="<?= $this->te('Warum ist dieses Control (nicht) anwendbar?') ?>"></textarea>
</div>
<div class="form-group">
<label for="c-notes"><?= $this->te('Umsetzungsnachweis / Notiz') ?></label>
<textarea id="c-notes" name="notes" class="form-control" rows="2"></textarea>
</div>
<?php if (!empty($pages)): ?>
<div class="form-group">
<label for="c-page"><?= $this->te('Richtlinien-Seite (Nachweis)') ?></label>
<select id="c-page" name="policy_page_id" class="form-control">
<option value=""><?= $this->te('— keine —') ?></option>
<?php foreach ($pages as $p): ?>
<option value="<?= (int)$p['id'] ?>"><?= $view->e($p['title']) ?></option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="document.getElementById('ismControlModal').style.display='none'"><?= $this->te('Abbrechen') ?></button>
<button type="submit" class="btn btn-primary"><?= $this->te('Speichern') ?></button>
</div>
</form>
</div>
</div>
<script>
function ismEditControl(c) {
var form = document.getElementById('ismControlForm');
form.action = '/isms/controls/' + c.id;
document.getElementById('ismControlTitle').textContent = c.ref + ' · ' + c.title;
document.getElementById('c-applicable').value = String(c.applicable);
document.getElementById('c-status').value = c.status;
document.getElementById('c-owner').value = c.owner_user_id ? String(c.owner_user_id) : '';
var pageSel = document.getElementById('c-page');
if (pageSel) pageSel.value = c.policy_page_id ? String(c.policy_page_id) : '';
document.getElementById('c-just').value = c.justification || '';
document.getElementById('c-notes').value = c.notes || '';
document.getElementById('ismControlModal').style.display = 'flex';
}
// Modal schließen bei Klick auf den Backdrop oder Escape
(function () {
var modal = document.getElementById('ismControlModal');
if (!modal) return;
modal.addEventListener('click', function (e) { if (e.target === modal) modal.style.display = 'none'; });
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') modal.style.display = 'none'; });
})();
</script>
<?php endif; ?>