' . esc_html(citami_t('Impostazioni salvate.', 'Settings saved.')) . '
%s
'string', 'sanitize_callback' => 'citami_sanitize_api_key', 'default' => '', )); register_setting('citami_settings', 'citami_endpoint', array( 'type' => 'string', 'sanitize_callback' => 'citami_sanitize_endpoint', 'default' => citami_default_endpoint(), )); register_setting('citami_settings', CITAMI_REVIEW_ONLY_OPT, array( 'type' => 'string', 'sanitize_callback' => 'citami_sanitize_review_only', 'default' => '1', )); // v2.11.0 — CONFIGURABLE-SLUGS (gap M2): slug extra per il // rilevamento pagine about/faq, fusi con i default hardcoded. register_setting('citami_settings', CITAMI_ABOUT_SLUGS_OPT, array( 'type' => 'string', 'sanitize_callback' => 'citami_sanitize_slug_list', 'default' => '', )); register_setting('citami_settings', CITAMI_FAQ_SLUGS_OPT, array( 'type' => 'string', 'sanitize_callback' => 'citami_sanitize_slug_list', 'default' => '', )); }); function citami_settings_page() { if (!current_user_can('manage_options')) return; // ?tab=… picks the screen; default = overview (2.38). $active_tab = isset($_GET['tab']) ? sanitize_key((string) $_GET['tab']) : 'overview'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only: which screen to show if (!in_array($active_tab, citami_ui_tab_ids(), true)) { $active_tab = 'overview'; } $queue = get_option(CITAMI_FIX_QUEUE_OPT, array()); $queue_array = is_array($queue) ? $queue : array(); $pending_count = 0; foreach ($queue_array as $fix) { $status = is_array($fix) && isset($fix['status']) ? (string) $fix['status'] : 'pending'; if ($status === 'pending') $pending_count++; } // Connection state at a glance (2.46 — one state, with the reason as tooltip and a link to fix it). $conn = citami_connection_state(); $pill_class = 'citami-pill--' . $conn['class']; $pill_text = $conn['label']; $nav = citami_ui_nav_items($pending_count); $labels = array(); foreach ($nav as $it) { $labels[$it['id']] = $it['label']; } $labels['setup'] = citami_t('Primo avvio guidato', 'Guided first run'); $dashboard_url = citami_lang_url(citami_brand_site() . '/dashboard'); ?>
' . esc_html(citami_t('Impostazioni salvate.', 'Settings saved.')) . '
%s
Impostazioni → Plugin → bottone «Mostra API key» → Copia.", 'Connect this site to your Citami dashboard. If you downloaded the ZIP from the dashboard, the key is already preloaded — no need to paste it. Otherwise: Settings → Plugin → "Show API key" button → Copy.' )); ?>
' . esc_html(citami_t('Nessuna domanda/risposta leggibile nel codice: l\'approvazione non andrebbe a buon fine.', 'No readable question/answer in the code: the approval would fail.')) . '
'; } break; default: $line = esc_html(citami_t('Non cambia nulla da solo: va fatto a mano, seguendo il codice qui sotto.', 'Changes nothing on its own: it is done by hand, following the code below.')); } return '' . esc_html(citami_t('Cosa cambia:', 'What changes:')) . ' ' . $line . '
' . $extra; } /** 2.60 — HTML id of a queue row (queue ids look like fix_6523….1234). */ function citami_fix_anchor($id) { return 'citami-fix-' . preg_replace('/[^A-Za-z0-9_\-]/', '-', (string) $id); } /** 2.60 — transient holding the one-shot outcome shown inside a queue row. */ function citami_fix_notice_key($id) { return 'citami_fix_notice_' . md5((string) $id); } /** * Render the Pending Fixes review table. Approve/Reject submit to * admin-post.php?action=citami_fix_action which routes to * citami_handle_fix_action(); every action comes back to its row. */ function citami_render_pending_fixes_tab(array $queue) { $review_only = citami_is_review_only(); if (empty($queue) && citami_get_api_key() === '') { echo wp_kses(citami_connect_card_html('pending'), citami_kses_allowed()); return; } if (empty($queue) && citami_is_free_account()) { echo wp_kses('' . esc_html(citami_upgrade_line('fixes')) . '
' . citami_upgrade_link_html('pending_fixes', citami_t('Vedi i piani', 'See the plans')) . '
Approva per applicarlo al sito (JSON-LD, meta tag, llms.txt, robots.txt) o Rifiuta per scartarlo. Un fix applicato si toglie dal sito con Revoca. La coda mantiene gli ultimi 50.', 'Every fix received from Citami is queued here. Click Approve to apply it to your site (JSON-LD, meta tags, llms.txt, robots.txt) or Reject to discard it. An applied fix comes off the site with Revoke. The queue keeps the latest 50.' )); ?>
Auto-apply attivo: i fix arrivano già applicati al sito e questa lista ne è lo storico. Per toglierne uno dal sito usa Revoca sulla sua riga: rimuove quello che il fix aveva aggiunto (blocco nel <head>, regole di robots.txt, llms.txt, pagina FAQ).",
'Auto-apply enabled: fixes arrive already applied to your site and this list is their history. To take one off the site use Revoke on its row: it removes what the fix had added (<head> block, robots.txt rules, llms.txt, FAQ page).'
)); ?>
|
|
|
' . esc_html($pretty) . ''; } return '
' . esc_html($code) . '
' . esc_html(citami_t('JSON non leggibile: così com\'è non si può applicare.', 'Unreadable JSON: it cannot be applied as it is.')) . '
'; case 'meta_tags': case 'llms_txt': case 'robots_txt': default: return '' . esc_html($code) . ''; } } /** * Apply a queued fix's code to the site. Mirrors the auto-apply branches * of citami_rest_apply_fix but operates on a stored queue entry. Returns * array('ok' => bool, 'message' => string). */ function citami_apply_queued_fix(array $fix) { $fix_type = isset($fix['fix_type']) ? (string) $fix['fix_type'] : ''; $title = isset($fix['title']) ? (string) $fix['title'] : ''; $code = isset($fix['code_snippet']) ? (string) $fix['code_snippet'] : ''; $fix_id = isset($fix['fix_id']) ? (string) $fix['fix_id'] : ''; if (!in_array($fix_type, array('llms_txt', 'robots_txt', 'schema_injection', 'schema_markup', 'meta_tags', 'faq_page'), true)) { // Manual-only fix types (bio_author, content_restructure, etc.) // — record approval but no auto-application is possible. Operator must // edit the site manually using the code preview. return array( 'ok' => true, 'manual' => true, 'message' => citami_t('Da fare a mano: questo tipo di fix non si applica da solo. Usa il codice qui sotto.', 'To do by hand: this fix type does not apply itself. Use the code below.'), ); } // 2.59 — the same routine as the platform push, title and fix id included, // so Revoke finds what this approval wrote (robots rules, llms.txt, FAQ page). $res = citami_apply_fix_now($fix_type, $title, $code, $fix_id); if (is_wp_error($res)) { return array('ok' => false, 'message' => $res->get_error_message()); } $data = (array) $res->get_data(); return array('ok' => !empty($data['success']), 'message' => isset($data['message']) ? (string) $data['message'] : ''); } /** * 2.60 — back to the queue row that was acted on, with its outcome inside * the row (shown once) and the row highlighted. */ function citami_fix_redirect_to_row($fix_id, array $notice) { set_transient(citami_fix_notice_key($fix_id), $notice, 120); wp_safe_redirect(citami_admin_url('pending', array('focus' => (string) $fix_id)) . '#' . citami_fix_anchor($fix_id)); exit; } /** * Admin-post handler for Approve/Reject/Revoke. Validates capability + * nonce, routes to citami_apply_queued_fix on approve, marks rejected on * reject, rolls back on revoke. Comes back to the row (2.60). */ function citami_handle_fix_action() { if (!current_user_can('manage_options')) { wp_die(esc_html(citami_t('Permesso negato.', 'Permission denied.')), '', array('response' => 403)); } $fix_id = isset($_POST['fix_id']) ? sanitize_text_field(wp_unslash($_POST['fix_id'])) : ''; $decision = isset($_POST['decision']) ? sanitize_key((string) $_POST['decision']) : ''; $nonce = isset($_POST['_citami_fix_nonce']) ? sanitize_text_field(wp_unslash($_POST['_citami_fix_nonce'])) : ''; if ($fix_id === '' || !in_array($decision, array('approve', 'reject', 'revoke'), true)) { wp_die(esc_html(citami_t('Parametri mancanti.', 'Missing parameters.')), '', array('response' => 400)); } if (!wp_verify_nonce($nonce, 'citami_fix_action_' . $fix_id)) { wp_die(esc_html(citami_t('Nonce non valido o scaduto. Ricarica la pagina e riprova.', 'Invalid or expired nonce. Reload the page and try again.')), '', array('response' => 403)); } $queue = get_option(CITAMI_FIX_QUEUE_OPT, array()); if (!is_array($queue)) $queue = array(); $target = null; foreach ($queue as $fix) { if (is_array($fix) && isset($fix['id']) && $fix['id'] === $fix_id) { $target = $fix; break; } } if (!$target) { // No row to come back to: the message goes on top of the tab. set_transient('citami_action_notice', array( 'type' => 'error', 'message' => citami_t('Fix non trovato (potrebbe essere uscito dalla coda dei 50 più recenti).', 'Fix not found (it may have dropped out of the 50 most recent).'), ), 30); wp_safe_redirect(citami_admin_url('pending')); exit; } $current_status = isset($target['status']) ? (string) $target['status'] : 'pending'; $title = isset($target['title']) && (string) $target['title'] !== '' ? (string) $target['title'] : citami_t('(senza titolo)', '(untitled)'); // v2.13.0 — 'revoke' undoes an APPLIED fix (rollback by fix id / title). if ($decision === 'revoke') { if ($current_status !== 'applied') { citami_fix_redirect_to_row($fix_id, array( 'type' => 'warning', 'message' => citami_t('Solo un fix applicato può essere revocato.', 'Only an applied fix can be revoked.'), )); } $removed = citami_rollback_fix( isset($target['fix_id']) ? (string) $target['fix_id'] : '', isset($target['title']) ? (string) $target['title'] : '' ); $n_removed = array_sum($removed) - $removed['queue']; if ($n_removed === 0) { // 2.59 — nothing found to remove: say so instead of a green "(0)". citami_fix_redirect_to_row($fix_id, array( 'type' => 'warning', 'message' => citami_t('Non ho trovato sul sito niente da rimuovere per questo fix (forse è già stato tolto a mano). Il fix resta segnato come applicato.', 'Nothing to remove was found on the site for this fix (it may have been removed by hand). The fix stays marked as applied.'), )); } citami_update_fix_in_queue($fix_id, array( 'status' => 'rolled_back', 'actioned_at' => gmdate('c'), 'actioned_by' => wp_get_current_user()->user_login ?? 'unknown', 'action_message' => citami_t('Revocato dall\'admin: rimosso dal sito.', 'Revoked by admin: removed from the site.'), )); // WP25 — the platform learns the fix is no longer live. citami_sync_report(isset($target['fix_id']) ? (string) $target['fix_id'] : $fix_id, false, citami_t('Revocato dall\'admin in WordPress: rimosso dal sito.', 'Revoked by the admin in WordPress: removed from the site.'), 'revoked'); citami_fix_redirect_to_row($fix_id, array( 'type' => 'success', 'message' => sprintf(citami_t('Fix rimosso dal sito: %d elementi tolti.', 'Fix removed from the site: %d items taken out.'), $n_removed), )); } if ($current_status !== 'pending') { list(, $status_label) = citami_fix_status_label($current_status); citami_fix_redirect_to_row($fix_id, array( 'type' => 'warning', 'message' => sprintf(citami_t('Fix già gestito (stato: %s).', 'Fix already handled (status: %s).'), $status_label), )); } $user = wp_get_current_user(); $actor = $user && !empty($user->ID) && isset($user->user_login) ? (string) $user->user_login : 'unknown'; if ($decision === 'reject') { citami_update_fix_in_queue($fix_id, array( 'status' => 'rejected', 'actioned_at' => gmdate('c'), 'actioned_by' => $actor, 'action_message' => citami_t("Rifiutato dall'admin.", 'Rejected by admin.'), )); // WP25 — the platform stops waiting for it. citami_sync_report(isset($target['fix_id']) ? (string) $target['fix_id'] : $fix_id, false, citami_t("Rifiutato dall'admin in WordPress.", 'Rejected by the admin in WordPress.'), 'rejected'); citami_fix_redirect_to_row($fix_id, array( 'type' => 'success', 'message' => sprintf(citami_t('Fix rifiutato: «%s». Sul sito non è cambiato nulla.', 'Fix rejected: "%s". Nothing changed on the site.'), $title), )); } $result = citami_apply_queued_fix($target); $manual = !empty($result['manual']); citami_update_fix_in_queue($fix_id, array( 'status' => $manual ? 'manual' : ($result['ok'] ? 'applied' : 'failed'), 'actioned_at' => gmdate('c'), 'actioned_by' => $actor, 'action_message' => $result['message'], )); // WP25 — approving in wp-admin is an application too: report it like the sync pull does. // 2.65 — a manual-only fix approved here is not live: nothing to report. if (!$manual) { citami_sync_report(isset($target['fix_id']) ? (string) $target['fix_id'] : $fix_id, (bool) $result['ok'], (string) $result['message'], $result['ok'] ? 'applied' : 'failed'); } if ($manual) { $notice = array('type' => 'warning', 'message' => (string) $result['message']); } elseif ($result['ok']) { $notice = array('type' => 'success', 'message' => citami_t('Fix applicato: ', 'Fix applied: ') . $result['message']); } else { $notice = array('type' => 'error', 'message' => citami_t('Errore applicazione: ', 'Could not apply: ') . $result['message']); } citami_fix_redirect_to_row($fix_id, $notice); } add_action('admin_post_citami_fix_action', 'citami_handle_fix_action'); /** * 2.60 — Settings tab actions. do=check runs the connection check on * demand (the page itself only reads the last stored result) and comes * back to the Status line with the outcome there. */ function citami_handle_settings_action() { if (!current_user_can('manage_options')) { wp_die(esc_html(citami_t('Permesso negato.', 'Permission denied.')), '', array('response' => 403)); } $nonce = isset($_POST['_citami_settings_nonce']) ? sanitize_text_field(wp_unslash($_POST['_citami_settings_nonce'])) : ''; if (!wp_verify_nonce($nonce, 'citami_settings_action')) { wp_die(esc_html(citami_t('Nonce non valido o scaduto. Ricarica la pagina e riprova.', 'Invalid or expired nonce. Reload the page and try again.')), '', array('response' => 403)); } $do = isset($_POST['do']) ? sanitize_key((string) $_POST['do']) : ''; if ($do === 'check') { if (citami_get_api_key() === '') { $notice = array('type' => 'warning', 'message' => citami_t('Prima inserisci e salva la API key, poi verifica la connessione.', 'Enter and save the API key first, then check the connection.')); } else { $health = citami_health_ping(); $ok = is_array($health) && !empty($health['ok']); $msg = is_array($health) && isset($health['message']) ? (string) $health['message'] : ''; $notice = $ok ? array('type' => 'success', 'message' => sprintf(citami_t('Connessione verificata ora: %s.', 'Connection checked just now: %s.'), $msg)) : array('type' => 'error', 'message' => sprintf(citami_t('Connessione non riuscita: %s. Controlla chiave ed endpoint qui sopra.', 'Connection failed: %s. Check the key and endpoint above.'), $msg)); } set_transient('citami_health_check_notice', $notice, 120); wp_safe_redirect(citami_admin_url('general', array('focus' => 'health')) . '#citami-health'); exit; } wp_safe_redirect(citami_admin_url('general')); exit; } add_action('admin_post_citami_settings_action', 'citami_handle_settings_action'); /** * 2.56.1 — links on the Plugins screen: Settings always, "Get started" * (the guided first run) until the site is connected. */ function citami_plugin_action_links($links) { $links = is_array($links) ? $links : array(); $own = array('' . esc_html(citami_t('Impostazioni', 'Settings')) . ''); if (citami_get_api_key() === '' && function_exists('citami_setup_url')) { $own[] = '' . esc_html(citami_t('Inizia', 'Get started')) . ''; } return array_merge($own, $links); } if (defined('CITAMI_PLUGIN_FILE') && function_exists('plugin_basename')) { add_filter('plugin_action_links_' . plugin_basename(CITAMI_PLUGIN_FILE), 'citami_plugin_action_links'); }