- Nowa klasa \Shared\Security\CsrfToken (generate/validate/regenerate) - Token CSRF we wszystkich formularzach edycji (form-edit.php) - Walidacja CSRF w FormRequestHandler::handleSubmit() - Token CSRF w formularzu logowania i formularzach 2FA - Walidacja CSRF w App::special_actions() dla żądań POST - Regeneracja tokenu po udanym logowaniu (bezpośrednia i przez 2FA) - Fix XSS: htmlspecialchars na $alert w unlogged-layout.php - 7 nowych testów CsrfTokenTest (817 testów łącznie) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
284 lines
9.8 KiB
PHP
284 lines
9.8 KiB
PHP
<?php
|
||
/**
|
||
* Uniwersalny szablon formularza edycji
|
||
*
|
||
* @var FormEditViewModel $form
|
||
*/
|
||
use admin\Support\Forms\FormFieldRenderer;
|
||
use admin\ViewModels\Forms\FormFieldType;
|
||
|
||
$form = $this->form;
|
||
$renderer = new FormFieldRenderer($form);
|
||
|
||
// Przygotuj filemanager key
|
||
\Shared\Helpers\Helpers::set_session('admin', true);
|
||
if (
|
||
empty($_SESSION['rfm_akey']) ||
|
||
(($_SESSION['rfm_akey_expires'] ?? 0) < time())
|
||
) {
|
||
$_SESSION['rfm_akey'] = bin2hex(random_bytes(16));
|
||
}
|
||
$_SESSION['rfm_akey_expires'] = time() + 20 * 60;
|
||
$_SESSION['can_use_rfm'] = true;
|
||
?>
|
||
|
||
<script type="text/javascript" src="/libraries/ckeditor/ckeditor.js"></script>
|
||
<script type="text/javascript" src="/libraries/ckeditor/adapters/jquery.js"></script>
|
||
|
||
<!-- iCheck -->
|
||
<link rel="stylesheet" type="text/css" href="/libraries/grid/plugins/icheck/skins/minimal/minimal.css" />
|
||
<link rel="stylesheet" type="text/css" href="/libraries/grid/plugins/icheck/skins/minimal/blue.css" />
|
||
<script type="text/javascript" src="/libraries/grid/plugins/icheck/icheck.min.js"></script>
|
||
<style>
|
||
.icheckbox_minimal-blue { margin-top: 10px; }
|
||
</style>
|
||
|
||
<div class="row">
|
||
<div class="col col-xs-12">
|
||
<div class="g-container" data="table:<?= $form->formId ?>">
|
||
<div class="panel panel-info panel-border top">
|
||
<div class="panel-heading">
|
||
<span class="panel-title"><?= htmlspecialchars($form->title) ?></span>
|
||
</div>
|
||
<div class="panel-heading p10 pl15" id="g-menu" style="height: auto;">
|
||
<?php foreach ($form->actions as $action): ?>
|
||
<?php if ($action->name === 'save'): ?>
|
||
<?php if ($form->persist): ?>
|
||
<a href="#" id="g-edit-save-close" class="btn btn-system btn-sm"
|
||
persist_edit="0"
|
||
back_url="<?= htmlspecialchars($action->backUrl ?? '') ?>"
|
||
url="<?= htmlspecialchars($action->url) ?>">
|
||
<i class="fa fa-check-circle mr5"></i>Zatwierdź i zamknij
|
||
</a>
|
||
<?php endif; ?>
|
||
<a href="#" id="g-edit-save" class="btn btn-success btn-sm"
|
||
persist_edit="<?= $form->persist ? '1' : '0' ?>"
|
||
back_url="<?= htmlspecialchars($action->backUrl ?? '') ?>"
|
||
url="<?= htmlspecialchars($action->url) ?>">
|
||
<i class="fa fa-check-circle mr5"></i>Zatwierdź
|
||
</a>
|
||
<?php elseif ($action->name === 'cancel'): ?>
|
||
<a href="<?= htmlspecialchars($action->url) ?>" class="btn btn-dark btn-sm" id="g-edit-cancel">
|
||
<i class="fa fa-reply mr5"></i>Wstecz
|
||
</a>
|
||
<?php elseif ($action->name === 'preview'): ?>
|
||
<a href="<?= htmlspecialchars($action->url) ?>" class="btn btn-info btn-sm" target="_blank">
|
||
<i class="fa fa-eye mr5"></i><?= htmlspecialchars($action->label) ?>
|
||
</a>
|
||
<?php else: ?>
|
||
<a href="<?= htmlspecialchars($action->url) ?>" class="btn <?= htmlspecialchars($action->cssClass) ?> btn-sm">
|
||
<?= htmlspecialchars($action->label) ?>
|
||
</a>
|
||
<?php endif; ?>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
|
||
<div class="panel-body">
|
||
<form method="<?= $form->method ?>" id="fg-<?= $form->formId ?>" class="g-form form-horizontal"
|
||
action="<?= htmlspecialchars($form->action) ?>" enctype="multipart/form-data">
|
||
|
||
<input type="hidden" name="_form_id" value="<?= htmlspecialchars($form->formId) ?>">
|
||
<input type="hidden" name="_csrf_token" value="<?= htmlspecialchars(\Shared\Security\CsrfToken::getToken()) ?>">
|
||
|
||
<?php foreach ($form->hiddenFields as $name => $value): ?>
|
||
<input type="hidden" name="<?= htmlspecialchars($name) ?>" value="<?= htmlspecialchars($value ?? '') ?>">
|
||
<?php endforeach; ?>
|
||
|
||
<?php if ($form->hasTabs()): ?>
|
||
<!-- Formularz z zakładkami -->
|
||
<div id="form-tabs-<?= $form->formId ?>">
|
||
<ul class="resp-tabs-list form-tabs">
|
||
<?php foreach ($form->tabs as $tab): ?>
|
||
<li><i class="fa <?= htmlspecialchars($tab->icon) ?>"></i><?= htmlspecialchars($tab->label) ?></li>
|
||
<?php endforeach; ?>
|
||
</ul>
|
||
<div class="resp-tabs-container form-tabs">
|
||
<?php foreach ($form->tabs as $tab): ?>
|
||
<div>
|
||
<?php
|
||
$tabFields = $form->getFieldsForTab($tab->id);
|
||
$langSections = $form->getLangSectionsForTab($tab->id);
|
||
?>
|
||
|
||
<?php foreach ($tabFields as $field): ?>
|
||
<?= $renderer->renderField($field) ?>
|
||
<?php endforeach; ?>
|
||
|
||
<?php foreach ($langSections as $section): ?>
|
||
<?= $renderer->renderLangSection($section) ?>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
<?php else: ?>
|
||
<!-- Formularz bez zakładek -->
|
||
<?php foreach ($form->fields as $field): ?>
|
||
<?php if ($field->type === FormFieldType::LANG_SECTION): ?>
|
||
<?= $renderer->renderLangSection($field) ?>
|
||
<?php else: ?>
|
||
<?= $renderer->renderField($field) ?>
|
||
<?php endif; ?>
|
||
<?php endforeach; ?>
|
||
<?php endif; ?>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script type="text/javascript">
|
||
$(function() {
|
||
// Inicjalizacja datepickerów
|
||
$('input.date').datetimepicker({
|
||
format: "YYYY-MM-DD",
|
||
pickTime: false
|
||
});
|
||
|
||
$('input.datetime').datetimepicker({
|
||
format: "YYYY-MM-DD HH:mm"
|
||
});
|
||
|
||
// Inicjalizacja zakładek
|
||
<?php if ($form->hasTabs()): ?>
|
||
$('#form-tabs-<?= $form->formId ?>').easyResponsiveTabs({
|
||
width: 'auto',
|
||
fit: true,
|
||
tabidentify: 'form-tabs',
|
||
type: 'vertical'
|
||
});
|
||
<?php endif; ?>
|
||
|
||
// Inicjalizacja iCheck
|
||
$('.icheck').iCheck({
|
||
checkboxClass: 'icheckbox_minimal-blue',
|
||
radioClass: 'iradio_minimal-blue'
|
||
});
|
||
|
||
function showFormMessage(type, text) {
|
||
var safeText = $('<div/>').text(text || '').html();
|
||
var alertClass = type === 'error' ? 'alert-danger' : 'alert-primary';
|
||
var html = '' +
|
||
'<div class="row js-form-message">' +
|
||
'<div class="col col-xs-12">' +
|
||
'<div class="alert ' + alertClass + ' alert-dismissable">' +
|
||
'<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' +
|
||
'<i class="fa fa-info pr10"></i>' + safeText +
|
||
'</div>' +
|
||
'</div>' +
|
||
'</div>';
|
||
|
||
$('#content .js-form-message').remove();
|
||
$('#content').prepend(html);
|
||
}
|
||
// Obsługa przycisków zapisu
|
||
$('#g-edit-save, #g-edit-save-close').on('click', function(e) {
|
||
e.preventDefault();
|
||
var $btn = $(this);
|
||
var url = $btn.attr('url');
|
||
var backUrl = $btn.attr('back_url');
|
||
var persist = $btn.attr('persist_edit');
|
||
var formId = 'fg-<?= $form->formId ?>';
|
||
|
||
// Synchronizuj zawartosc CKEditor do textarea przed serializacja
|
||
if (typeof CKEDITOR !== 'undefined' && CKEDITOR.instances) {
|
||
for (var instanceName in CKEDITOR.instances) {
|
||
if (Object.prototype.hasOwnProperty.call(CKEDITOR.instances, instanceName)) {
|
||
CKEDITOR.instances[instanceName].updateElement();
|
||
}
|
||
}
|
||
}
|
||
|
||
// Zbierz dane formularza
|
||
var formData = $('#' + formId).serialize();
|
||
|
||
$.ajax({
|
||
url: url,
|
||
type: 'POST',
|
||
data: formData,
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
if (response.success) {
|
||
var successMessage = response.message || 'Zmiany zostały zapisane.';
|
||
if (backUrl && persist === '0') {
|
||
window.location.href = backUrl;
|
||
} else {
|
||
showFormMessage('success', successMessage);
|
||
}
|
||
} else {
|
||
var errors = response.errors ? Object.values(response.errors).join(', ') : 'Błąd walidacji';
|
||
showFormMessage('error', 'Błąd: ' + errors);
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
// Fallback - tradycyjne submit formularza
|
||
$('#' + formId).attr('action', url).submit();
|
||
}
|
||
});
|
||
});
|
||
|
||
disable_menu();
|
||
});
|
||
</script>
|
||
|
||
<?php
|
||
// Renderowanie CKEditor dla pól edytora (zwykłych)
|
||
foreach ($form->fields as $field):
|
||
if ($field->type === FormFieldType::EDITOR):
|
||
?>
|
||
<script type="text/javascript">
|
||
$(function() {
|
||
$('#<?= $field->id ?>').ckeditor({
|
||
toolbar: '<?= $field->editorToolbar ?>',
|
||
height: '<?= $field->editorHeight ?>'
|
||
});
|
||
});
|
||
</script>
|
||
<?php
|
||
endif;
|
||
endforeach;
|
||
?>
|
||
|
||
<?php if ($form->hasLangSections()): ?>
|
||
<script type="text/javascript">
|
||
$(function() {
|
||
// Inicjalizacja zakładek językowych
|
||
$('[id^="languages-"].languages-tabs').each(function() {
|
||
$(this).easyResponsiveTabs({
|
||
width: 'auto',
|
||
fit: true,
|
||
tabidentify: 'languages-tabs',
|
||
type: 'horizontal'
|
||
});
|
||
});
|
||
|
||
// Inicjalizacja CKEditor dla pól w sekcjach językowych
|
||
<?php
|
||
foreach ($form->fields as $section):
|
||
if ($section->type === FormFieldType::LANG_SECTION && $section->langFields):
|
||
foreach ($section->langFields as $langField):
|
||
if ($langField->type === FormFieldType::EDITOR && $form->languages):
|
||
foreach ($form->languages as $lang):
|
||
if ($lang['status']):
|
||
?>
|
||
$('#<?= $langField->getLocalizedId($lang['id']) ?>').ckeditor({
|
||
toolbar: '<?= $langField->editorToolbar ?>',
|
||
height: '<?= $langField->editorHeight ?>'
|
||
});
|
||
<?php
|
||
endif;
|
||
endforeach;
|
||
endif;
|
||
endforeach;
|
||
endif;
|
||
endforeach;
|
||
?>
|
||
});
|
||
</script>
|
||
<?php endif; ?>
|
||
|
||
<script>CKEDITOR.dtd.$removeEmpty['span'] = false;</script>
|
||
|
||
|