forked from fa/breadcrumb-the-shire
260 lines
14 KiB
PHTML
260 lines
14 KiB
PHTML
<?php
|
|
|
|
use MintyPHP\Module\Security\Service\SecurityCheckProcess;
|
|
|
|
$check = is_array($check ?? null) ? $check : [];
|
|
$steps = is_array($steps ?? null) ? $steps : [];
|
|
$techItems = is_array($techItems ?? null) ? $techItems : [];
|
|
$processState = is_array($processState ?? null) ? $processState : [];
|
|
$techState = is_array($techState ?? null) ? $techState : [];
|
|
$progress = is_array($progress ?? null) ? $progress : [];
|
|
$userNames = is_array($userNames ?? null) ? $userNames : [];
|
|
$canEdit = $canEdit ?? false;
|
|
$canManage = $canManage ?? false;
|
|
$canGenerateReport = $canGenerateReport ?? false;
|
|
$reportPdfUrl = (string) ($reportPdfUrl ?? '');
|
|
$status = (string) ($status ?? SecurityCheckProcess::STATUS_OPEN);
|
|
$readonly = !$canEdit;
|
|
|
|
$percent = (int) ($progress['percent'] ?? 0);
|
|
$doneCount = (int) ($progress['done'] ?? 0);
|
|
$totalCount = (int) ($progress['total'] ?? 0);
|
|
|
|
$completionMeta = static function (array $entry) use ($userNames): string {
|
|
if (empty($entry['done'])) {
|
|
return '';
|
|
}
|
|
$parts = [];
|
|
$by = (int) ($entry['by'] ?? 0);
|
|
if ($by > 0 && isset($userNames[$by]) && $userNames[$by] !== '') {
|
|
$parts[] = $userNames[$by];
|
|
}
|
|
$at = trim((string) ($entry['at'] ?? ''));
|
|
if ($at !== '') {
|
|
$parts[] = $at;
|
|
}
|
|
return $parts === [] ? '' : implode(' · ', $parts);
|
|
};
|
|
?>
|
|
<div class="app-details-container">
|
|
<section data-tab-panel data-tab-panel-wide>
|
|
<?php
|
|
$titlebar = [
|
|
'title' => t('Security check'),
|
|
'backHref' => lurl($closeTarget ?? 'security/checks'),
|
|
];
|
|
if (!$readonly) {
|
|
$titlebar['actions'] = [
|
|
[
|
|
'label' => t('Save'),
|
|
'type' => 'submit',
|
|
'form' => 'security-check-form',
|
|
'name' => 'action',
|
|
'value' => 'save',
|
|
'class' => 'primary',
|
|
'tone' => 'success',
|
|
],
|
|
];
|
|
}
|
|
require templatePath('partials/app-details-titlebar.phtml');
|
|
?>
|
|
|
|
<!-- Summary -->
|
|
<div class="security-check-summary">
|
|
<dl class="security-check-meta">
|
|
<div><dt><?php e(t('Customer')); ?></dt><dd><?php e((string) ($check['debitor_name'] ?? '')); ?> <span class="app-muted">(<?php e((string) ($check['debitor_no'] ?? '')); ?>)</span></dd></div>
|
|
<div><dt><?php e(t('Domain')); ?></dt><dd><?php e((string) ($check['domain_url'] ?? ($check['domain_no'] ?? ''))); ?></dd></div>
|
|
<div><dt><?php e(t('Software product')); ?></dt><dd><?php e((string) ($check['product_name'] ?? ($check['product_code'] ?? ''))); ?></dd></div>
|
|
<div><dt><?php e(t('Owner')); ?></dt><dd><?php e((string) ($check['owner_name'] ?? '—')); ?></dd></div>
|
|
<div><dt><?php e(t('Status')); ?></dt><dd><span class="badge" data-variant="<?php e(SecurityCheckProcess::statusVariant($status)); ?>"><?php e(t(SecurityCheckProcess::statusLabel($status))); ?></span></dd></div>
|
|
</dl>
|
|
</div>
|
|
<div class="security-check-progress">
|
|
<div class="security-check-progress-bar" role="progressbar" aria-label="<?php e(t('Progress')); ?>" aria-valuenow="<?php e((string) $percent); ?>" aria-valuemin="0" aria-valuemax="100">
|
|
<div class="security-check-progress-fill" style="width: <?php e((string) $percent); ?>%"></div>
|
|
</div>
|
|
<span class="security-check-progress-label"><?php e(t('%d of %d completed', $doneCount, $totalCount)); ?> (<?php e((string) $percent); ?>%)</span>
|
|
</div>
|
|
|
|
<?php if ($readonly && $status === SecurityCheckProcess::STATUS_ARCHIVED): ?>
|
|
<div class="notice" data-variant="info" role="status">
|
|
<p><?php e(t('This security check is archived and read-only.')); ?></p>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Checklist -->
|
|
<form method="post" id="security-check-form" data-app-component="security-check-workspace">
|
|
<div class="security-steps">
|
|
<?php foreach ($steps as $index => $step):
|
|
$key = (string) ($step['key'] ?? '');
|
|
$kind = (string) ($step['kind'] ?? SecurityCheckProcess::KIND_STEP);
|
|
$stepNumber = $index + 1;
|
|
?>
|
|
<?php if ($kind === SecurityCheckProcess::KIND_TECH_CHECKLIST): ?>
|
|
<!-- Step: technical checklist (per product) -->
|
|
<article class="security-step security-step-tech">
|
|
<header class="security-step-head">
|
|
<span class="security-step-number"><?php e((string) $stepNumber); ?></span>
|
|
<div>
|
|
<h3><?php e(t((string) ($step['label'] ?? ''))); ?></h3>
|
|
<p class="app-muted"><?php e(t((string) ($step['description'] ?? ''))); ?></p>
|
|
</div>
|
|
<?php if (!empty($progress['step6_done'])): ?>
|
|
<span class="badge" data-variant="success"><?php e(t('Complete')); ?></span>
|
|
<?php endif; ?>
|
|
</header>
|
|
<?php if ($techItems === []): ?>
|
|
<p class="app-muted security-step-empty"><?php e(t('This product has no technical checklist items defined yet.')); ?></p>
|
|
<?php else: ?>
|
|
<ul class="security-tech-list">
|
|
<?php
|
|
$snapshotItems = is_array($check['tech_schema_snapshot']['items'] ?? null) ? $check['tech_schema_snapshot']['items'] : [];
|
|
foreach ($snapshotItems as $item):
|
|
if (!is_array($item)) { continue; }
|
|
$itemType = (string) ($item['type'] ?? 'check');
|
|
if ($itemType === 'section'): ?>
|
|
<li class="security-tech-section"><?php e((string) ($item['label'] ?? '')); ?></li>
|
|
<?php else:
|
|
$itemKey = (string) ($item['key'] ?? '');
|
|
if ($itemKey === '') { continue; }
|
|
$entry = is_array($techState[$itemKey] ?? null) ? $techState[$itemKey] : [];
|
|
$done = !empty($entry['done']);
|
|
$note = (string) ($entry['note'] ?? '');
|
|
$meta = $completionMeta($entry);
|
|
?>
|
|
<li class="security-tech-item<?php if ($done): ?> is-done<?php endif; ?>">
|
|
<label class="security-check-toggle">
|
|
<input type="checkbox" name="tech[<?php e($itemKey); ?>][done]" value="1"<?php if ($done): ?> checked<?php endif; ?><?php if ($readonly): ?> disabled<?php endif; ?>>
|
|
<span class="security-tech-label">
|
|
<?php e((string) ($item['label'] ?? '')); ?>
|
|
<?php if (trim((string) ($item['hint'] ?? '')) !== ''): ?>
|
|
<small class="app-muted security-tech-hint"><?php e((string) $item['hint']); ?></small>
|
|
<?php endif; ?>
|
|
</span>
|
|
</label>
|
|
<?php if ($meta !== ''): ?><span class="security-completion-meta"><i class="bi bi-check2-circle"></i> <?php e($meta); ?></span><?php endif; ?>
|
|
<?php if (trim((string) ($item['markdown_html'] ?? '')) !== ''):
|
|
$markdownHtml = (string) $item['markdown_html'];
|
|
require __DIR__ . '/../../../templates/tech-markdown.phtml';
|
|
endif; ?>
|
|
<input type="text" class="security-item-note" name="tech[<?php e($itemKey); ?>][note]" value="<?php e($note); ?>" placeholder="<?php e(t('Note (optional)')); ?>"<?php if ($readonly): ?> disabled<?php endif; ?>>
|
|
</li>
|
|
<?php endif; ?>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
<?php endif; ?>
|
|
</article>
|
|
<?php else:
|
|
$entry = is_array($processState[$key] ?? null) ? $processState[$key] : [];
|
|
$done = !empty($entry['done']);
|
|
$note = (string) ($entry['note'] ?? '');
|
|
$meta = $completionMeta($entry);
|
|
?>
|
|
<article class="security-step<?php if ($done): ?> is-done<?php endif; ?>">
|
|
<header class="security-step-head">
|
|
<span class="security-step-number"><?php e((string) $stepNumber); ?></span>
|
|
<label class="security-check-toggle security-step-toggle">
|
|
<input type="checkbox" name="step[<?php e($key); ?>][done]" value="1"<?php if ($done): ?> checked<?php endif; ?><?php if ($readonly): ?> disabled<?php endif; ?>>
|
|
<span>
|
|
<strong><?php e(t((string) ($step['label'] ?? ''))); ?></strong>
|
|
<small class="app-muted"><?php e(t((string) ($step['description'] ?? ''))); ?></small>
|
|
</span>
|
|
</label>
|
|
<?php if ($meta !== ''): ?><span class="security-completion-meta"><i class="bi bi-check2-circle"></i> <?php e($meta); ?></span><?php endif; ?>
|
|
</header>
|
|
|
|
<?php
|
|
$stepFields = is_array($step['fields'] ?? null) ? $step['fields'] : [];
|
|
if ($stepFields !== []):
|
|
$fieldValues = is_array($entry['fields'] ?? null) ? $entry['fields'] : [];
|
|
$hasRequired = false;
|
|
?>
|
|
<div class="security-step-fields">
|
|
<?php foreach ($stepFields as $field):
|
|
$fkey = (string) ($field['key'] ?? '');
|
|
if ($fkey === '') { continue; }
|
|
$ftype = (string) ($field['type'] ?? SecurityCheckProcess::FIELD_TEXTAREA);
|
|
$isDatetime = $ftype === SecurityCheckProcess::FIELD_DATETIME;
|
|
$fieldRequired = ($field['required'] ?? false) === true;
|
|
if ($fieldRequired) { $hasRequired = true; }
|
|
$fval = (string) ($fieldValues[$fkey] ?? '');
|
|
$fname = 'step[' . $key . '][fields][' . $fkey . ']';
|
|
$fid = 'security-field-' . $key . '-' . $fkey;
|
|
?>
|
|
<div class="security-step-field<?php if ($isDatetime): ?> security-step-field-datetime<?php endif; ?>">
|
|
<label for="<?php e($fid); ?>"><?php e(t((string) ($field['label'] ?? ''))); ?><?php if ($fieldRequired): ?> <span class="security-form-required" aria-hidden="true">*</span><?php endif; ?></label>
|
|
<?php if ($isDatetime): ?>
|
|
<input type="datetime-local" id="<?php e($fid); ?>" name="<?php e($fname); ?>" value="<?php e($fval); ?>"<?php if ($fieldRequired): ?> data-security-required="1"<?php endif; ?><?php if ($readonly): ?> disabled<?php endif; ?>>
|
|
<?php else: ?>
|
|
<textarea id="<?php e($fid); ?>" name="<?php e($fname); ?>" rows="2"<?php if ($fieldRequired): ?> data-security-required="1"<?php endif; ?><?php if ($readonly): ?> disabled<?php endif; ?>><?php e($fval); ?></textarea>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php if ($hasRequired): ?>
|
|
<p class="security-step-gate-hint" data-security-gate-hint hidden>
|
|
<i class="bi bi-info-circle"></i> <?php e(t('Fill in all required fields to complete this step.')); ?>
|
|
</p>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
|
|
<input type="text" class="security-item-note" name="step[<?php e($key); ?>][note]" value="<?php e($note); ?>" placeholder="<?php e(t('Note (optional)')); ?>"<?php if ($readonly): ?> disabled<?php endif; ?>>
|
|
|
|
<?php if ($key === SecurityCheckProcess::STEP_REPORT): ?>
|
|
<div class="security-report-action">
|
|
<?php if ($canGenerateReport && $reportPdfUrl !== ''): ?>
|
|
<a href="<?php e($reportPdfUrl); ?>" class="secondary outline small" target="_blank" rel="noopener">
|
|
<i class="bi bi-file-earmark-pdf" aria-hidden="true"></i> <?php e(t('Generate PDF customer report')); ?>
|
|
</a>
|
|
<p class="app-muted security-report-hint"><?php e(t('Opens in a new tab and reflects the last saved state.')); ?></p>
|
|
<?php else: ?>
|
|
<button type="button" class="secondary outline small" disabled>
|
|
<i class="bi bi-file-earmark-pdf" aria-hidden="true"></i> <?php e(t('Generate PDF customer report')); ?>
|
|
</button>
|
|
<p class="app-muted security-report-hint"><i class="bi bi-lock" aria-hidden="true"></i> <?php e(t('Complete and save all previous steps to enable the customer report.')); ?></p>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</article>
|
|
<?php endif; ?>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<?php if (!$readonly): ?>
|
|
<div class="security-form-actions">
|
|
<button type="submit" name="action" value="save" class="primary app-action-success"><?php e(t('Save')); ?></button>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php \MintyPHP\Session::getCsrfInput(); ?>
|
|
</form>
|
|
|
|
<?php if ($canManage): ?>
|
|
<!-- Danger zone -->
|
|
<div class="security-danger-zone">
|
|
<div class="security-danger-zone-inner">
|
|
<h3><?php e(t('Danger zone')); ?></h3>
|
|
<div class="security-danger-actions">
|
|
<form method="post">
|
|
<?php \MintyPHP\Session::getCsrfInput(); ?>
|
|
<?php if ($status === SecurityCheckProcess::STATUS_ARCHIVED): ?>
|
|
<button type="submit" name="action" value="unarchive" class="secondary outline small"><i class="bi bi-arrow-counterclockwise"></i> <?php e(t('Reopen check')); ?></button>
|
|
<?php else: ?>
|
|
<button type="submit" name="action" value="archive" class="secondary outline small"
|
|
data-confirm-message="<?php e(t('Archive this security check? It will become read-only.')); ?>"
|
|
data-confirm-variant="warning"><i class="bi bi-archive"></i> <?php e(t('Archive check')); ?></button>
|
|
<?php endif; ?>
|
|
</form>
|
|
<form method="post">
|
|
<?php \MintyPHP\Session::getCsrfInput(); ?>
|
|
<button type="submit" name="action" value="delete" class="danger outline small"
|
|
data-confirm-message="<?php e(t('Delete this security check? This cannot be undone.')); ?>"
|
|
data-confirm-variant="danger"><i class="bi bi-trash3"></i> <?php e(t('Delete check')); ?></button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</section>
|
|
</div>
|