diff --git a/plugin/itcca-allievi/assets/css/form.css b/plugin/itcca-allievi/assets/css/form.css index 85fe9c1..25af131 100644 --- a/plugin/itcca-allievi/assets/css/form.css +++ b/plugin/itcca-allievi/assets/css/form.css @@ -2,6 +2,10 @@ :root { --itcca-primary: var(--wp--preset--color--primary, currentColor); + --itcca-font-family: var( + --wp--preset--font-family--body, + var(--global--font-family-body, inherit) + ); --itcca-error: #b3261e; --itcca-success: #1e6b3a; --itcca-border: rgba(0, 0, 0, 0.18); @@ -10,6 +14,13 @@ --itcca-gap: 1rem; } +.itcca-form, +.itcca-form-success, +.itcca-form-error, +.itcca-form-summary { + font-family: var(--itcca-font-family), sans-serif; +} + .itcca-form { display: flex; flex-direction: column; @@ -26,19 +37,68 @@ opacity: 0 !important; } +.itcca-form-notice-wrap { + max-width: 760px; + margin: 1.25rem auto; + padding: 0 1rem; +} + +.itcca-form-success, +.itcca-form-error, +.itcca-form-summary { + max-width: 760px; + margin: 0 auto 1.25rem; + padding: 1rem 1.25rem; + border-radius: var(--itcca-radius); +} + +.itcca-alert-success, .itcca-form-success { border-left: 4px solid var(--itcca-success); background: rgba(30, 107, 58, 0.08); - padding: 0.875rem 1rem; - border-radius: var(--itcca-radius); - margin-bottom: 1.25rem; } +.itcca-alert-error, .itcca-form-error { border-left: 4px solid var(--itcca-error); background: rgba(179, 38, 30, 0.08); - padding: 0.875rem 1rem; - border-radius: var(--itcca-radius); +} + +.itcca-alert-title { + font-weight: 700; + font-size: 1.05rem; + margin: 0 0 0.35rem; +} + +.itcca-alert-success .itcca-alert-title::before { + content: "✓ "; + color: var(--itcca-success); +} + +.itcca-alert-error .itcca-alert-title::before { + content: "✕ "; + color: var(--itcca-error); +} + +.itcca-form-success p, +.itcca-form-error p, +.itcca-form-summary p { + margin: 0; +} + +.itcca-form-success p + p, +.itcca-form-error p + p, +.itcca-form-summary p + p { + margin-top: 0.35rem; +} + +.itcca-error-list { + margin: 0.5rem 0 0; + padding-left: 1.25rem; +} + +.itcca-error-list li { + margin: 0.15rem 0; } .itcca-section { @@ -68,7 +128,7 @@ .itcca-field .itcca-label, .itcca-field label { - font-size: 0.92rem; + font-size: calc(0.92rem + 4pt); font-weight: 500; } @@ -76,6 +136,12 @@ color: var(--itcca-error); } +.itcca-form input:invalid, +.itcca-form select:invalid { + box-shadow: none; + border-color: var(--itcca-border); +} + .itcca-field input[type="text"], .itcca-field input[type="email"], .itcca-field input[type="tel"], @@ -101,7 +167,7 @@ .itcca-field-error { color: var(--itcca-error); - font-size: 0.85rem; + font-size: calc(0.85rem + 4pt); margin: 0; } @@ -126,7 +192,7 @@ .itcca-checkbox { align-items: flex-start; - font-size: 0.92rem; + font-size: calc(0.92rem + 4pt); } .itcca-checkbox input { diff --git a/plugin/itcca-allievi/assets/js/form.js b/plugin/itcca-allievi/assets/js/form.js index bf6a896..010665b 100644 --- a/plugin/itcca-allievi/assets/js/form.js +++ b/plugin/itcca-allievi/assets/js/form.js @@ -33,51 +33,145 @@ return (v || '').replace(/[\s\-]/g, ''); } - function validateInput(input) { - const field = input.closest('.itcca-field'); - if (!field) return true; + function shouldShowErrors(field, form) { + return ( + field.classList.contains('itcca-touched') || + form.classList.contains('itcca-submit-attempted') + ); + } + + function markTouched(field) { + field.classList.add('itcca-touched'); + } + + function getInputValidation(input) { const type = input.dataset.type || ''; const value = (input.value || '').trim(); + if (input.required && value === '') { - showError(field, 'Campo obbligatorio.'); - return false; + return { valid: false, message: 'Campo obbligatorio.' }; } if (value === '') { - clearError(field); - return true; + return { valid: true, message: '' }; } if (type === 'cf') { if (!ITCCA_CF_RE.test(value)) { - showError(field, 'Codice fiscale non valido.'); - return false; + return { valid: false, message: 'Codice fiscale non valido.' }; } } else if (type === 'email') { const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/; if (!emailRe.test(value)) { - showError(field, 'Indirizzo email non valido.'); - return false; + return { valid: false, message: 'Indirizzo email non valido.' }; } } else if (type === 'cap') { if (!ITCCA_CAP_RE.test(value)) { - showError(field, 'CAP deve essere di 5 cifre.'); - return false; + return { valid: false, message: 'CAP deve essere di 5 cifre.' }; } } else if (type === 'tel') { if (!ITCCA_PHONE_RE.test(normalizePhone(value))) { - showError(field, 'Numero non valido.'); - return false; + return { valid: false, message: 'Numero non valido.' }; } } else if (type === 'date') { const d = new Date(value); if (isNaN(d.getTime()) || d > new Date()) { - showError(field, 'Data non valida.'); - return false; + return { valid: false, message: 'Data non valida.' }; } } + return { valid: true, message: '' }; + } + + function validateInput(input, form) { + const field = input.closest('.itcca-field'); + if (!field) { + return true; + } + + const result = getInputValidation(input); + if (!shouldShowErrors(field, form)) { + return result.valid; + } + + if (!result.valid) { + showError(field, result.message); + return false; + } + clearError(field); return true; } + function validateRadioGroup(field, form) { + const radios = field.querySelectorAll('input[type="radio"]'); + if (radios.length === 0) { + return true; + } + + const required = Array.from(radios).some((radio) => radio.required); + const checked = Array.from(radios).some((radio) => radio.checked); + const valid = !required || checked; + + if (!shouldShowErrors(field, form)) { + return valid; + } + + if (!valid) { + showError(field, 'Campo obbligatorio.'); + return false; + } + + clearError(field); + return true; + } + + function validatePrivacy(field, form) { + const checkbox = field.querySelector('input[name="privacy"]'); + if (!checkbox) { + return true; + } + + const valid = checkbox.checked; + if (!shouldShowErrors(field, form)) { + return valid; + } + + if (!valid) { + showError(field, 'Devi accettare l\'informativa.'); + return false; + } + + clearError(field); + return true; + } + + function showClientSubmitSummary(form) { + let summary = form.querySelector('.itcca-form-summary-client'); + if (!summary) { + summary = document.createElement('div'); + summary.className = 'itcca-form-error itcca-form-summary itcca-form-summary-client itcca-alert-error'; + summary.setAttribute('role', 'alert'); + summary.setAttribute('aria-live', 'assertive'); + form.insertBefore(summary, form.firstChild); + } + + summary.innerHTML = + '

Controlla i campi

' + + '

Correggi i campi evidenziati prima di inviare.

'; + } + + function clearClientSubmitSummary(form) { + const summary = form.querySelector('.itcca-form-summary-client'); + if (summary) { + summary.remove(); + } + } + + function scrollToAlert() { + const alert = document.querySelector('.itcca-form-success, .itcca-form-error, .itcca-form-summary'); + if (alert) { + alert.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + } + function init() { const forms = document.querySelectorAll('form.itcca-form'); forms.forEach((form) => { @@ -86,33 +180,89 @@ cfInput.addEventListener('input', uppercaseCF); } - const inputs = form.querySelectorAll('input[data-type], input[required]'); - inputs.forEach((input) => { - input.addEventListener('blur', () => validateInput(input)); + form.querySelectorAll('.itcca-field-has-error').forEach((field) => { + markTouched(field); + }); + + const textInputs = form.querySelectorAll('input[data-type], input[required]:not([type="radio"]):not([type="checkbox"])'); + textInputs.forEach((input) => { + input.addEventListener('blur', () => { + const field = input.closest('.itcca-field'); + if (field) { + markTouched(field); + } + validateInput(input, form); + }); + }); + + form.querySelectorAll('.itcca-field').forEach((field) => { + const radios = field.querySelectorAll('input[type="radio"]'); + if (radios.length > 0) { + const radioGroup = field.querySelector('.itcca-radio-group'); + const validateGroup = () => validateRadioGroup(field, form); + + radios.forEach((radio) => { + radio.addEventListener('change', () => { + markTouched(field); + validateGroup(); + }); + }); + + if (radioGroup) { + radioGroup.addEventListener('blur', () => { + markTouched(field); + validateGroup(); + }); + } + } + + const privacy = field.querySelector('input[name="privacy"]'); + if (privacy) { + privacy.addEventListener('change', () => { + markTouched(field); + validatePrivacy(field, form); + }); + privacy.addEventListener('blur', () => { + markTouched(field); + validatePrivacy(field, form); + }); + } }); form.addEventListener('submit', (e) => { + form.classList.add('itcca-submit-attempted'); let ok = true; - inputs.forEach((input) => { - if (!validateInput(input)) { + + textInputs.forEach((input) => { + if (!validateInput(input, form)) { ok = false; } }); - const privacy = form.querySelector('input[name="privacy"]'); - if (privacy && !privacy.checked) { - const field = privacy.closest('.itcca-field'); - if (field) showError(field, 'Devi accettare l\'informativa.'); - ok = false; - } + + form.querySelectorAll('.itcca-field').forEach((field) => { + if (field.querySelector('input[type="radio"]') && !validateRadioGroup(field, form)) { + ok = false; + } + if (field.querySelector('input[name="privacy"]') && !validatePrivacy(field, form)) { + ok = false; + } + }); + if (!ok) { e.preventDefault(); + showClientSubmitSummary(form); const firstErr = form.querySelector('.itcca-field-has-error'); if (firstErr) { firstErr.scrollIntoView({ behavior: 'smooth', block: 'center' }); } + return; } + + clearClientSubmitSummary(form); }); }); + + scrollToAlert(); } if (document.readyState === 'loading') { diff --git a/plugin/itcca-allievi/includes/class-allievi-list-table.php b/plugin/itcca-allievi/includes/class-allievi-list-table.php index 44dede9..8806dca 100644 --- a/plugin/itcca-allievi/includes/class-allievi-list-table.php +++ b/plugin/itcca-allievi/includes/class-allievi-list-table.php @@ -124,6 +124,7 @@ 'sync' => __('Sincronizza con Google Sheet', 'itcca-allievi'), 'deactivate' => __('Imposta A = D (disattivo)', 'itcca-allievi'), 'activate' => __('Imposta A = A (attivo)', 'itcca-allievi'), + 'delete' => __('Elimina definitivamente', 'itcca-allievi'), ]; } @@ -289,13 +290,30 @@ admin_url('admin-post.php?action=itcca_sync_user&user_id=' . (int) $item->ID), 'itcca_sync_user_' . (int) $item->ID ); - return sprintf( + $delete_url = wp_nonce_url( + admin_url('admin-post.php?action=itcca_delete_allievo&user_id=' . (int) $item->ID), + 'itcca_delete_allievo_' . (int) $item->ID + ); + $confirm = esc_js(__('Eliminare definitivamente questo allievo? L\'operazione non è reversibile.', 'itcca-allievi')); + + $links = sprintf( '%s | %s', esc_url($edit), esc_html__('Modifica', 'itcca-allievi'), esc_url($sync_url), esc_html__('Pusha su foglio', 'itcca-allievi') ); + + if (current_user_can('delete_users')) { + $links .= sprintf( + ' | %s', + esc_url($delete_url), + $confirm, + esc_html__('Elimina', 'itcca-allievi') + ); + } + + return $links; } protected function extra_tablenav($which): void diff --git a/plugin/itcca-allievi/includes/class-allievi-list.php b/plugin/itcca-allievi/includes/class-allievi-list.php index 6ff2f1c..bbe07e8 100644 --- a/plugin/itcca-allievi/includes/class-allievi-list.php +++ b/plugin/itcca-allievi/includes/class-allievi-list.php @@ -18,6 +18,7 @@ { add_action('admin_menu', [self::class, 'add_menu']); add_action('admin_post_itcca_sync_user', [self::class, 'handle_sync_single']); + add_action('admin_post_itcca_delete_allievo', [self::class, 'handle_delete_single']); add_action('admin_post_itcca_export_csv', [ExportCsv::class, 'handle_export']); } @@ -150,10 +151,12 @@ 'synced' => __('Sincronizzazione eseguita.', 'itcca-allievi'), 'sync_failed' => __('Sincronizzazione fallita. Controlla i log nelle impostazioni.', 'itcca-allievi'), 'updated' => __('Operazione eseguita.', 'itcca-allievi'), + 'deleted' => __('Allievo eliminato.', 'itcca-allievi'), + 'delete_failed' => __('Impossibile eliminare l\'allievo selezionato.', 'itcca-allievi'), ]; $msg = $messages[$notice] ?? ''; if ($msg !== '') { - $class = $notice === 'sync_failed' ? 'notice-error' : 'notice-success'; + $class = in_array($notice, ['sync_failed', 'delete_failed'], true) ? 'notice-error' : 'notice-success'; printf('

%s

', esc_attr($class), esc_html($msg)); } } @@ -174,13 +177,26 @@ if ($action === '-1' || $action === '') { $action = sanitize_key($_REQUEST['action2'] ?? ''); } - if (!in_array($action, ['sync', 'deactivate', 'activate'], true)) { + if (!in_array($action, ['sync', 'deactivate', 'activate', 'delete'], true)) { return; } + if ($action === 'delete' && !current_user_can('delete_users')) { + wp_die(__('Permessi insufficienti per eliminare allievi.', 'itcca-allievi')); + } + $ids = array_map('intval', (array) $_REQUEST['users']); + $deleted = 0; foreach ($ids as $id) { - if ($id <= 0) continue; + if ($id <= 0) { + continue; + } + if ($action === 'delete') { + if (self::delete_allievo($id)) { + $deleted++; + } + continue; + } if ($action === 'deactivate') { update_user_meta($id, ITCCA_META_PREFIX . 'a', 'D'); } elseif ($action === 'activate') { @@ -190,10 +206,52 @@ } } + if ($action === 'delete') { + wp_safe_redirect(add_query_arg( + 'itcca_notice', + $deleted > 0 ? 'deleted' : 'delete_failed', + admin_url('admin.php?page=' . self::MENU_SLUG) + )); + exit; + } + wp_safe_redirect(add_query_arg('itcca_notice', 'updated', admin_url('admin.php?page=' . self::MENU_SLUG))); exit; } + public static function handle_delete_single(): void + { + if (!current_user_can('delete_users')) { + wp_die(__('Permessi insufficienti.', 'itcca-allievi')); + } + $user_id = isset($_GET['user_id']) ? (int) $_GET['user_id'] : 0; + if ($user_id <= 0) { + wp_die(__('Utente non valido.', 'itcca-allievi')); + } + check_admin_referer('itcca_delete_allievo_' . $user_id); + + $ok = self::delete_allievo($user_id); + + wp_safe_redirect(add_query_arg( + 'itcca_notice', + $ok ? 'deleted' : 'delete_failed', + admin_url('admin.php?page=' . self::MENU_SLUG) + )); + exit; + } + + private static function delete_allievo(int $user_id): bool + { + $user = get_user_by('id', $user_id); + if (!$user instanceof \WP_User || !in_array(ITCCA_ROLE, (array) $user->roles, true)) { + return false; + } + if (!function_exists('wp_delete_user')) { + require_once ABSPATH . 'wp-admin/includes/user.php'; + } + return (bool) wp_delete_user($user_id); + } + public static function handle_sync_single(): void { if (!current_user_can('edit_users')) { diff --git a/plugin/itcca-allievi/includes/class-form-shortcode.php b/plugin/itcca-allievi/includes/class-form-shortcode.php index bd5fc7f..b575f83 100644 --- a/plugin/itcca-allievi/includes/class-form-shortcode.php +++ b/plugin/itcca-allievi/includes/class-form-shortcode.php @@ -17,21 +17,32 @@ public const RATE_LIMIT_MAX = 5; public const RATE_LIMIT_WINDOW = 3600; + private static bool $success_banner_shown = false; + + /** @var 'yes'|'no'|null */ + private static ?string $success_resolved = null; + public static function register(): void { add_shortcode(self::SHORTCODE, [self::class, 'render']); add_action('admin_post_nopriv_' . self::ACTION, [self::class, 'handle_submit']); add_action('admin_post_' . self::ACTION, [self::class, 'handle_submit']); add_action('wp_enqueue_scripts', [self::class, 'enqueue_assets']); + add_action('wp', [self::class, 'maybe_show_global_success']); } public static function enqueue_assets(): void { global $post; - $should_enqueue = is_a($post, 'WP_Post') && has_shortcode((string) $post->post_content, self::SHORTCODE); - if (!$should_enqueue) { + $has_shortcode = is_a($post, 'WP_Post') && has_shortcode((string) $post->post_content, self::SHORTCODE); + if (!$has_shortcode && !self::is_success_pending()) { return; } + self::enqueue_form_assets(); + } + + private static function enqueue_form_assets(): void + { wp_enqueue_style( 'itcca-form', ITCCA_URL . 'assets/css/form.css', @@ -52,23 +63,56 @@ } } + public static function maybe_show_global_success(): void + { + if (is_admin() || !self::resolve_success()) { + return; + } + add_action('wp_body_open', [self::class, 'render_success_banner_once'], 5); + add_action('wp_footer', [self::class, 'render_success_banner_once'], 5); + } + + public static function render_success_banner_once(): void + { + if (self::$success_banner_shown) { + return; + } + self::$success_banner_shown = true; + echo '
'; + self::render_success_banner(); + echo '
'; + } + public static function render(array $atts = [], ?string $content = null): string { + if (self::resolve_success()) { + ob_start(); + if (!self::$success_banner_shown) { + self::render_success_banner_once(); + } + return (string) ob_get_clean(); + } + $state = self::pop_state(); $errors = is_array($state['errors'] ?? null) ? $state['errors'] : []; $values = is_array($state['values'] ?? null) ? $state['values'] : []; - $success = !empty($state['success']); ob_start(); - if ($success) { - echo '
' - . esc_html__('Iscrizione inviata correttamente. A breve riceverai una email per impostare la tua password.', 'itcca-allievi') - . '
'; - } include ITCCA_PATH . 'templates/form-iscrizione.php'; return (string) ob_get_clean(); } + private static function render_success_banner(): void + { + echo '
'; + echo '

' . esc_html__('Iscrizione completata', 'itcca-allievi') . '

'; + echo '

' . esc_html__( + 'La tua richiesta è stata registrata correttamente. A breve riceverai una email con le istruzioni per impostare la password e accedere al tuo profilo.', + 'itcca-allievi' + ) . '

'; + echo '
'; + } + public static function handle_submit(): void { $referer = wp_get_referer() ?: home_url('/'); @@ -120,6 +164,14 @@ } self::save_meta((int) $user_id, $values); + + $user = get_user_by('id', (int) $user_id); + if ($user instanceof \WP_User) { + $reset_key = get_password_reset_key($user); + $reset_key = is_wp_error($reset_key) ? null : $reset_key; + self::send_welcome_email((int) $user_id, $user->user_login, $reset_key); + } + self::notify_admin((int) $user_id, $values); do_action('itcca_user_registered', (int) $user_id, $values); @@ -127,11 +179,8 @@ self::store_state(['success' => true]); $redirect = get_option('itcca_redirect_after_submit', ''); - if ($redirect !== '') { - wp_safe_redirect(esc_url_raw($redirect)); - } else { - wp_safe_redirect($referer); - } + $target = $redirect !== '' ? $redirect : $referer; + wp_safe_redirect(esc_url_raw(add_query_arg('itcca_success', '1', $target))); exit; } @@ -216,12 +265,6 @@ 'role' => ITCCA_ROLE, ]); - if (!is_wp_error($user_id) && $email !== '') { - $key = get_password_reset_key(get_user_by('id', (int) $user_id)); - if (!is_wp_error($key)) { - self::send_welcome_email((int) $user_id, $login, $key); - } - } return $user_id; } @@ -260,22 +303,32 @@ } } - private static function send_welcome_email(int $user_id, string $login, string $reset_key): void + private static function send_welcome_email(int $user_id, string $login, ?string $reset_key): void { $user = get_user_by('id', $user_id); if (!$user instanceof \WP_User) { return; } - $reset_url = network_site_url(sprintf('wp-login.php?action=rp&key=%s&login=%s', $reset_key, rawurlencode($login)), 'login'); - $site = wp_specialchars_decode((string) get_option('blogname'), ENT_QUOTES); + $site = wp_specialchars_decode((string) get_option('blogname'), ENT_QUOTES); $subject = sprintf(__('[%s] Conferma iscrizione e imposta la password', 'itcca-allievi'), $site); $message = sprintf(__('Ciao %s,', 'itcca-allievi'), $user->first_name) . "\n\n"; - $message .= __('La tua iscrizione è stata registrata correttamente.', 'itcca-allievi') . "\n\n"; - $message .= __('Per accedere al tuo profilo imposta una password cliccando qui:', 'itcca-allievi') . "\n"; - $message .= $reset_url . "\n\n"; + $message .= __('La tua richiesta di iscrizione è stata registrata correttamente.', 'itcca-allievi') . "\n\n"; + + if ($reset_key !== null) { + $reset_url = network_site_url( + sprintf('wp-login.php?action=rp&key=%s&login=%s', $reset_key, rawurlencode($login)), + 'login' + ); + $message .= __('Per accedere al tuo profilo imposta una password cliccando qui:', 'itcca-allievi') . "\n"; + $message .= $reset_url . "\n\n"; + } else { + $message .= __('Per impostare la password visita:', 'itcca-allievi') . "\n"; + $message .= wp_lostpassword_url() . "\n\n"; + } + $message .= __('Il tuo username è:', 'itcca-allievi') . ' ' . $login . "\n"; - wp_mail($user->user_email, $subject, $message); + self::send_mail($user->user_email, $subject, $message); } /** @@ -298,9 +351,35 @@ $message .= ($values['cognome'] ?? '') . ' ' . ($values['nome'] ?? '') . "\n"; $message .= 'Email: ' . ($values['user_email'] ?? '') . "\n"; $message .= 'CF: ' . ($values['cf'] ?? '') . "\n"; - $message .= 'Cellulare: ' . ($values['cellulare'] ?? '') . "\n\n"; + $message .= 'Cellulare: ' . ($values['cellulare'] ?? '') . "\n"; + $message .= __('Data iscrizione:', 'itcca-allievi') . ' ' . current_time('mysql') . "\n\n"; $message .= __('Scheda allievo:', 'itcca-allievi') . ' ' . $url . "\n"; - wp_mail($admin, $subject, $message); + + $headers = []; + $user_email = sanitize_email($values['user_email'] ?? ''); + if ($user_email !== '') { + $headers[] = 'Reply-To: ' . $user_email; + } + + self::send_mail($admin, $subject, $message, $headers); + } + + /** + * @param array $headers + */ + private static function send_mail(string $to, string $subject, string $message, array $headers = []): bool + { + $all_headers = array_merge(['Content-Type: text/plain; charset=UTF-8'], $headers); + $sent = wp_mail($to, $subject, $message, $all_headers); + if (!$sent) { + error_log(sprintf('[ITCCA Allievi] wp_mail failed: to=%s subject=%s', $to, $subject)); + set_transient( + 'itcca_mail_last_error', + ['to' => $to, 'subject' => $subject, 'time' => time()], + HOUR_IN_SECONDS + ); + } + return $sent; } private static function slug(string $s): string @@ -347,19 +426,70 @@ /** * @return array{errors?: array, values?: array, success?: bool} */ - public static function pop_state(): array + private static function read_state(): array { $token = sanitize_key($_COOKIE[self::COOKIE] ?? ''); if ($token === '') { return []; } $state = get_transient('itcca_state_' . $token); - delete_transient('itcca_state_' . $token); + return is_array($state) ? $state : []; + } + + private static function clear_state_cookie(): void + { + $token = sanitize_key($_COOKIE[self::COOKIE] ?? ''); + if ($token !== '') { + delete_transient('itcca_state_' . $token); + } setcookie(self::COOKIE, '', [ 'expires' => time() - 3600, 'path' => '/', + 'secure' => is_ssl(), + 'httponly' => true, 'samesite' => 'Lax', ]); - return is_array($state) ? $state : []; + } + + private static function is_success_pending(): bool + { + if (isset($_GET['itcca_success']) && sanitize_text_field(wp_unslash((string) $_GET['itcca_success'])) === '1') { + return true; + } + $state = self::read_state(); + return !empty($state['success']); + } + + private static function resolve_success(): bool + { + if (self::$success_resolved === 'yes') { + return true; + } + if (self::$success_resolved === 'no') { + return false; + } + if (isset($_GET['itcca_success']) && sanitize_text_field(wp_unslash((string) $_GET['itcca_success'])) === '1') { + self::clear_state_cookie(); + self::$success_resolved = 'yes'; + return true; + } + $state = self::read_state(); + if (!empty($state['success'])) { + self::clear_state_cookie(); + self::$success_resolved = 'yes'; + return true; + } + self::$success_resolved = 'no'; + return false; + } + + /** + * @return array{errors?: array, values?: array, success?: bool} + */ + public static function pop_state(): array + { + $state = self::read_state(); + self::clear_state_cookie(); + return $state; } } diff --git a/plugin/itcca-allievi/itcca-allievi.php b/plugin/itcca-allievi/itcca-allievi.php index 0742359..cc00dbf 100644 --- a/plugin/itcca-allievi/itcca-allievi.php +++ b/plugin/itcca-allievi/itcca-allievi.php @@ -3,7 +3,7 @@ * Plugin Name: ITCCA Allievi * Plugin URI: https://example.com * Description: Gestione iscrizioni allievi ai corsi di Tai Chi: estende l'utente WordPress con i campi del registro INSARRI, espone un form pubblico di iscrizione e sincronizza i dati con un Google Sheet privato selezionabile da Drive. - * Version: 1.0.0 + * Version: 1.2.0 * Requires at least: 6.0 * Requires PHP: 8.1 * Author: Fabio Arrigoni @@ -21,7 +21,7 @@ exit; } -define('ITCCA_VERSION', '1.0.0'); +define('ITCCA_VERSION', '1.2.0'); define('ITCCA_FILE', __FILE__); define('ITCCA_PATH', plugin_dir_path(__FILE__)); define('ITCCA_URL', plugin_dir_url(__FILE__)); diff --git a/plugin/itcca-allievi/templates/form-iscrizione.php b/plugin/itcca-allievi/templates/form-iscrizione.php index 19f7d19..2b8c093 100644 --- a/plugin/itcca-allievi/templates/form-iscrizione.php +++ b/plugin/itcca-allievi/templates/form-iscrizione.php @@ -35,6 +35,11 @@ }; $general_error = $form_errors['_form'] ?? ''; +$field_errors = array_filter( + $form_errors, + static fn ($key) => $key !== '_form', + ARRAY_FILTER_USE_KEY +); ?>
@@ -42,7 +47,20 @@ - + + + $section_label) : @@ -67,10 +85,10 @@ $req_attr = $required ? ' required' : ''; $req_mark = $required ? ' ' : ''; ?> -
+
-
+
$opt_label) : ?>