. */ /* ────────────────────────────────────────────────────────────────── */ /** Stamp the platform fix id on every head block stored under $title. */ function citami_tag_head_blocks($option, $title, $fix_id) { $fix_id = (string) $fix_id; if ($fix_id === '') { return 0; } $blocks = get_option($option, array()); if (!is_array($blocks)) { return 0; } $tagged = 0; foreach ($blocks as $idx => $b) { if (is_array($b) && isset($b['title']) && $b['title'] === $title) { $blocks[$idx]['fix_id'] = $fix_id; $tagged++; } } if ($tagged > 0) { update_option($option, array_values($blocks), false); } return $tagged; } /** Remove head blocks by fix id (falls back to title for pre-2.13 blocks). */ function citami_purge_head_blocks_by_fix($option, $fix_id, $title = '') { $blocks = get_option($option, array()); if (!is_array($blocks)) { return 0; } $removed = 0; foreach ($blocks as $idx => $b) { if (!is_array($b)) { continue; } $match = ($fix_id !== '' && isset($b['fix_id']) && $b['fix_id'] === $fix_id) || ($title !== '' && !isset($b['fix_id']) && isset($b['title']) && $b['title'] === $title); if ($match) { unset($blocks[$idx]); $removed++; } } if ($removed > 0) { update_option($option, array_values($blocks), false); } return $removed; } /** Robots segments (fix-keyed). Legacy flat string becomes one untagged segment. */ function citami_robots_segments() { $segments = get_option(CITAMI_ROBOTS_SEGMENTS_OPT, null); if (is_array($segments)) { return $segments; } $legacy = get_option(CITAMI_ROBOTS_OPT, ''); $segments = array(); if (is_string($legacy) && trim($legacy) !== '') { $segments[] = array('fix_id' => '', 'title' => '', 'content' => trim($legacy)); } return $segments; } /** Rebuild the flat string the robots_txt filter prints. */ function citami_rebuild_robots_rules(array $segments) { $parts = array(); foreach ($segments as $s) { if (is_array($s) && isset($s['content']) && trim((string) $s['content']) !== '') { $parts[] = trim((string) $s['content']); } } update_option(CITAMI_ROBOTS_SEGMENTS_OPT, array_values($segments), false); update_option(CITAMI_ROBOTS_OPT, implode("\n", $parts), false); } /** * Undo everything a fix wrote. Returns counts per surface. Never throws: * a rollback must always leave the site in a consistent state. */ function citami_rollback_fix($fix_id, $title = '') { $fix_id = (string) $fix_id; $title = (string) $title; $out = array('schema' => 0, 'meta' => 0, 'robots' => 0, 'llms' => 0, 'faq_page' => 0, 'queue' => 0); $out['schema'] = citami_purge_head_blocks_by_fix(CITAMI_SCHEMA_OPT, $fix_id, $title); $out['meta'] = citami_purge_head_blocks_by_fix(CITAMI_META_OPT, $fix_id, $title); // robots.txt segments $segments = citami_robots_segments(); $kept = array(); foreach ($segments as $s) { $match = ($fix_id !== '' && isset($s['fix_id']) && $s['fix_id'] === $fix_id) || ($title !== '' && isset($s['title']) && $s['title'] === $title && (!isset($s['fix_id']) || $s['fix_id'] === '')); if ($match) { $out['robots']++; } else { $kept[] = $s; } } if ($out['robots'] > 0) { citami_rebuild_robots_rules($kept); } // llms.txt — only the file WE wrote for this fix (marker present). $llms = get_option(CITAMI_LLMS_FIX_OPT, array()); if (is_array($llms) && ( ($fix_id !== '' && isset($llms['fix_id']) && $llms['fix_id'] === $fix_id) || ($title !== '' && isset($llms['title']) && $llms['title'] === $title) )) { $path = ABSPATH . 'llms.txt'; if (is_file($path)) { $current = (string) @file_get_contents($path); if (citami_llms_marker_pos($current) !== false) { wp_delete_file($path); $out['llms'] = 1; } } delete_option(CITAMI_LLMS_FIX_OPT); } // FAQ page we created for this fix → trash (never a customer page). if ($fix_id !== '') { $pages = get_posts(array( 'post_type' => 'page', 'post_status' => 'any', 'meta_key' => '_citami_fix_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- one fix id, one page 'meta_value' => $fix_id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value 'numberposts' => 5, )); foreach ($pages as $pg) { if (get_post_meta($pg->ID, '_citami_faq_page', true) !== '1') { continue; } if (get_post_meta($pg->ID, '_citami_fix_id', true) !== $fix_id) { continue; } wp_trash_post($pg->ID); $out['faq_page']++; } delete_transient(CITAMI_DETECTED_SLUGS_TRANSIENT); } // Queue bookkeeping — every entry of this fix becomes 'rolled_back'. $queue = get_option(CITAMI_FIX_QUEUE_OPT, array()); if (is_array($queue)) { $changed = false; foreach ($queue as $idx => $entry) { if (!is_array($entry)) { continue; } $match = ($fix_id !== '' && isset($entry['fix_id']) && $entry['fix_id'] === $fix_id) || ($title !== '' && (!isset($entry['fix_id']) || $entry['fix_id'] === '') && isset($entry['title']) && $entry['title'] === $title); if ($match && isset($entry['status']) && $entry['status'] === 'applied') { $queue[$idx]['status'] = 'rolled_back'; $queue[$idx]['actioned_at'] = gmdate('c'); $queue[$idx]['action_message'] = citami_t('Rimosso dal sito.', 'Removed from the site.'); $out['queue']++; $changed = true; } } if ($changed) { update_option(CITAMI_FIX_QUEUE_OPT, $queue, false); } } return $out; } /** DELETE /wp-json/citami/v1/apply-fix/{fix_id}?title=… */ function citami_rest_rollback_fix(WP_REST_Request $request) { $fix_id = sanitize_text_field((string) $request->get_param('fix_id')); $title = sanitize_text_field((string) $request->get_param('title')); if ($fix_id === '' && $title === '') { return new WP_Error('citami_bad_payload', 'fix_id o title obbligatorio.', array('status' => 400)); } $removed = citami_rollback_fix($fix_id, $title); $total = array_sum($removed) - $removed['queue']; return rest_ensure_response(array( 'success' => true, 'removed' => $removed, 'message' => $total > 0 ? citami_t('Fix rimosso dal sito.', 'Fix removed from the site.') : citami_t('Nulla da rimuovere per questo fix.', 'Nothing to remove for this fix.'), )); } add_action('rest_api_init', function () { register_rest_route('citami/v1', '/apply-fix/(?P[A-Za-z0-9_\-]+)', array( 'methods' => 'DELETE', 'callback' => 'citami_rest_rollback_fix', 'permission_callback' => 'citami_check_permission', )); });