/** * Plugin Name: SV Bridge BE Sender Optimized * Description: Invia dal backend al frontend post, meta, tassonomie e featured image con coda, hash payload, badge admin e mapping attachment backend/frontend, verifica reale hash contenuto FE, badge modifica confermata e ricezione modifiche FE -> BE solo se il post FE è publish. * Version: 5.7.0-state-truth-log24h */ if (!defined('ABSPATH')) exit; class SV_Bridge_BE_Sender_Optimized { protected $is_normalizing_backend_content = false; protected $is_receiving_frontend_sync = false; const QUEUE_OPTION = 'sv_bridge_be_queue'; const CRON_HOOK = 'sv_bridge_be_process_queue'; const META_LAST_HASH = '_sv_bridge_last_payload_hash'; const META_LAST_SENT_HASH = '_sv_bridge_last_sent_payload_hash'; const META_LAST_TRY = '_sv_bridge_last_try'; const META_LAST_ERROR = '_sv_bridge_last_error'; const META_LAST_RESPONSE_CODE = '_sv_bridge_last_response_code'; const META_LAST_RESPONSE_BODY = '_sv_bridge_last_response_body'; const META_LAST_DIAG = '_sv_bridge_last_diag'; const META_QUEUE_STATUS = '_sv_bridge_queue_status'; const META_QUEUE_AT = '_sv_bridge_queue_at'; const META_DELIVERED = '_sv_bridge_delivered_to_frontend'; const META_DELIVERED_AT = '_sv_bridge_delivered_to_frontend_at'; const META_FEATURED_FAIL = '_sv_bridge_featured_sync_failed'; const META_FEATURED_FAIL_MSG = '_sv_bridge_featured_sync_failed_msg'; const META_WAITING_FEATURED_SINCE = '_sv_bridge_waiting_featured_since'; const META_REMOTE_POST_ID = '_sv_bridge_remote_post_id'; const META_REMOTE_STATUS = '_sv_bridge_remote_status'; const META_REMOTE_TITLE = '_sv_bridge_remote_title'; const META_REMOTE_LAST_SYNC = '_sv_bridge_remote_last_sync'; const META_REMOTE_FEATURED_OK = '_sv_bridge_remote_featured_ok'; const META_REMOTE_LEGACY_LINKED_AT = '_sv_bridge_remote_legacy_linked_at'; const META_REMOTE_LAST_VERIFIED_AT = '_sv_bridge_remote_last_verified_at'; const META_REMOTE_CONTENT_VERIFIED = '_sv_bridge_remote_content_verified'; const META_REMOTE_CONTENT_HASH = '_sv_bridge_remote_content_hash'; const META_REMOTE_EXPECTED_CONTENT_HASH = '_sv_bridge_remote_expected_content_hash'; const META_REMOTE_TITLE_HASH = '_sv_bridge_remote_title_hash'; const META_REMOTE_EXCERPT_HASH = '_sv_bridge_remote_excerpt_hash'; const META_REMOTE_VERIFICATION_MSG = '_sv_bridge_remote_verification_msg'; const META_REMOTE_POST_TYPE = '_sv_bridge_remote_post_type'; const META_REMOTE_IS_ATTACHMENT = '_sv_bridge_remote_is_attachment'; const META_LAST_UPDATE_FROM_FE_AT = '_sv_bridge_last_update_from_fe_at'; const META_LAST_FE_SYNC_STATUS = '_sv_bridge_last_fe_sync_status'; const META_LAST_FE_SYNC_MSG = '_sv_bridge_last_fe_sync_msg'; const META_LAST_FE_POST_ID = '_sv_bridge_last_fe_post_id'; const META_LAST_FE_CHANGED_FIELDS = '_sv_bridge_last_fe_changed_fields'; const META_LAST_FE_SYNCED_META_KEYS = '_sv_bridge_last_fe_synced_meta_keys'; const META_LAST_FE_SYNCED_TAXONOMIES = '_sv_bridge_last_fe_synced_taxonomies'; const LOCK_PREFIX = 'sv_bridge_be_lock_'; const META_ARTICLE_LOG = '_sv_bridge_article_state_log'; const GLOBAL_LOG_OPTION = 'sv_bridge_be_global_log_24h'; const LOG_CLEANUP_TRANSIENT = 'sv_bridge_be_log_cleanup_lock'; const SCHEDULER_VERSION = '5.6.0'; const SCHEDULER_OPTION = 'sv_bridge_be_scheduler_version'; protected $frontend_uploads_base = 'https://www.cronachedellacampania.it/wp-content/uploads'; public function __construct() { add_filter('upload_dir', [$this, 'force_frontend_upload_urls'], 20); add_filter('wp_get_attachment_url', [$this, 'force_attachment_url_to_frontend'], 20, 2); add_filter('wp_calculate_image_srcset', [$this, 'force_srcset_to_frontend'], 20); add_action('rest_api_init', [$this, 'routes']); add_action('save_post', [$this, 'on_save_post'], 20, 3); add_action('added_post_meta', [$this, 'on_thumbnail_meta_changed'], 20, 4); add_action('updated_post_meta', [$this, 'on_thumbnail_meta_changed'], 20, 4); /* * Sync cancellazioni: * - wp_trash_post intercetta lo spostamento nel cestino dal backend. * - transition_post_status copre plugin/codici che impostano direttamente post_status=trash. * - before_delete_post resta come fallback per eliminazione definitiva. */ add_action('wp_trash_post', [$this, 'on_trash_post'], 20); add_action('transition_post_status', [$this, 'on_transition_to_trash'], 20, 3); add_action('transition_post_status', [$this, 'on_transition_from_auto_draft_to_real_status'], 25, 3); add_action('before_delete_post', [$this, 'on_delete_post'], 20); add_action(self::CRON_HOOK, [$this, 'process_queue']); add_action('init', [$this, 'ensure_cron']); add_action('init', [$this, 'cleanup_stale_delivered_queue_items'], 30); add_filter('cron_schedules', [$this, 'add_cron_interval']); add_action('admin_menu', [$this, 'add_settings_page']); add_action('admin_menu', [$this, 'add_observability_page'], 30); add_action('admin_post_sv_bridge_clear_be_log', [$this, 'handle_clear_global_log']); add_action('admin_post_sv_bridge_check_fe_post', [$this, 'handle_check_fe_post']); add_action('init', [$this, 'cleanup_global_log_24h'], 45); add_action('admin_init', [$this, 'maybe_kick_queue_on_admin'], 20); add_action('add_meta_boxes', [$this, 'add_metabox']); add_action('admin_post_sv_bridge_resend_post', [$this, 'handle_manual_resend']); // CSS compatto per la colonna Frontend in edit.php, così il badge non invade celle vicine. add_action('admin_head-edit.php', [$this, 'output_admin_list_css']); add_action('admin_head-post.php', [$this, 'output_admin_list_css']); add_action('admin_head-post-new.php', [$this, 'output_admin_list_css']); add_filter('manage_post_posts_columns', [$this, 'add_admin_column']); add_action('manage_post_posts_custom_column', [$this, 'render_admin_column'], 10, 2); add_filter('manage_page_posts_columns', [$this, 'add_admin_column']); add_action('manage_page_posts_custom_column', [$this, 'render_admin_column'], 10, 2); } protected function endpoint() { return defined('SV_BRIDGE_FE_ENDPOINT') ? SV_BRIDGE_FE_ENDPOINT : ''; } protected function status_endpoint() { $endpoint = $this->endpoint(); if (!$endpoint) return ''; return preg_replace('#/sync/?$#', '/status', $endpoint); } protected function secret() { return defined('SV_BRIDGE_SECRET') ? SV_BRIDGE_SECRET : ''; } public function routes() { register_rest_route('sv-bridge/v1', '/fe-sync', [ 'methods' => 'POST', 'callback' => [$this, 'handle_frontend_sync'], 'permission_callback' => '__return_true', ]); } protected function get_incoming_bridge_key(WP_REST_Request $request) { $headers = $request->get_headers(); $candidates = [ $headers['x-bridge-key'][0] ?? '', $headers['X-Bridge-Key'][0] ?? '', $_SERVER['HTTP_X_BRIDGE_KEY'] ?? '', $_SERVER['REDIRECT_HTTP_X_BRIDGE_KEY'] ?? '', ]; foreach ($candidates as $candidate) { $candidate = trim((string)$candidate); if ($candidate !== '') return $candidate; } return ''; } protected function auth_ok(WP_REST_Request $request) { $secret = $this->secret(); $incoming = $this->get_incoming_bridge_key($request); return ($secret !== '' && $incoming !== '' && hash_equals($secret, $incoming)); } protected function skip_frontend_to_backend_meta_key($key) { if ($this->skip_meta_key($key)) return true; /* * Non sincronizziamo ID media del frontend verso il backend: * i due siti possono avere ID attachment diversi anche se condividono uploads. */ $blocked = ['_thumbnail_id']; return in_array((string)$key, $blocked, true); } protected function sync_frontend_terms_to_backend($post_id, $terms_payload) { $post_id = (int)$post_id; $changed_taxonomies = []; if (!$post_id || !is_array($terms_payload)) return $changed_taxonomies; foreach ($terms_payload as $taxonomy => $terms) { $taxonomy = sanitize_key((string)$taxonomy); if (!$taxonomy || !taxonomy_exists($taxonomy) || !is_array($terms)) continue; $before = wp_get_object_terms($post_id, $taxonomy, ['fields' => 'ids']); if (is_wp_error($before)) $before = []; $before = array_values(array_unique(array_map('intval', (array)$before))); sort($before); $term_ids = []; foreach ($terms as $term_payload) { if (!is_array($term_payload)) continue; $slug = !empty($term_payload['slug']) ? sanitize_title((string)$term_payload['slug']) : ''; $name = !empty($term_payload['name']) ? wp_strip_all_tags((string)$term_payload['name']) : $slug; if ($name === '' && $slug === '') continue; $term = null; if ($slug) $term = get_term_by('slug', $slug, $taxonomy); if (!$term && $name) $term = get_term_by('name', $name, $taxonomy); if (!$term) { $args = []; if ($slug) $args['slug'] = $slug; if (!empty($term_payload['description'])) $args['description'] = wp_kses_post((string)$term_payload['description']); $created = wp_insert_term($name ?: $slug, $taxonomy, $args); if (is_wp_error($created)) continue; $term_ids[] = (int)$created['term_id']; } else { $term_ids[] = (int)$term->term_id; } } $term_ids = array_values(array_unique($term_ids)); $after = $term_ids; sort($after); if (maybe_serialize($before) !== maybe_serialize($after)) { wp_set_object_terms($post_id, $term_ids, $taxonomy, false); $changed_taxonomies[] = $taxonomy; } } return array_values(array_unique($changed_taxonomies)); } protected function sync_frontend_meta_to_backend($post_id, $incoming_meta) { $post_id = (int)$post_id; if (!$post_id || !is_array($incoming_meta)) return []; $synced = []; foreach ($incoming_meta as $key => $vals) { $key = (string)$key; if ($this->skip_frontend_to_backend_meta_key($key)) continue; $values = []; foreach ((array)$vals as $v) { $values[] = maybe_unserialize($v); } $current = get_post_meta($post_id, $key, false); if (maybe_serialize($current) === maybe_serialize($values)) { continue; } delete_post_meta($post_id, $key); foreach ($values as $v) { add_post_meta($post_id, $key, $v); } $synced[] = $key; } return $synced; } public function handle_frontend_sync(WP_REST_Request $request) { if (!$this->auth_ok($request)) { return new WP_REST_Response([ 'success' => false, 'message' => 'Unauthorized', ], 403); } $payload = $request->get_json_params(); if (!is_array($payload)) $payload = []; $entity = !empty($payload['entity']) ? sanitize_key((string)$payload['entity']) : ''; $action = !empty($payload['action']) ? sanitize_key((string)$payload['action']) : ''; $data = !empty($payload['data']) && is_array($payload['data']) ? $payload['data'] : []; if ($entity !== 'post' || $action !== 'frontend_upsert') { return new WP_REST_Response([ 'success' => false, 'message' => 'Azione FE -> BE non supportata', ], 400); } $incoming_post = !empty($data['post']) && is_array($data['post']) ? $data['post'] : []; $frontend_status = !empty($incoming_post['post_status']) ? sanitize_key((string)$incoming_post['post_status']) : ''; if ($frontend_status !== 'publish') { $blocked_backend_id = !empty($data['backend_post_id']) ? (int)$data['backend_post_id'] : 0; if ($blocked_backend_id) $this->add_article_log($blocked_backend_id, 'FE→BE', 'Sync bloccata: FE non publish', ['stato_fe' => $frontend_status ?: 'missing']); return new WP_REST_Response([ 'success' => false, 'message' => 'Sync FE -> BE bloccata: il post sul frontend non risulta publish.', 'frontend_post_id' => !empty($data['frontend_post_id']) ? (int)$data['frontend_post_id'] : 0, 'frontend_status' => $frontend_status ?: 'missing', ], 409); } $backend_post_id = !empty($data['backend_post_id']) ? (int)$data['backend_post_id'] : 0; if (!$backend_post_id && !empty($data['post']['ID'])) { $backend_post_id = (int)$data['post']['ID']; } $post = $backend_post_id ? get_post($backend_post_id) : null; if (!$post || !in_array($post->post_type, ['post', 'page', 'product'], true)) { return new WP_REST_Response([ 'success' => false, 'message' => 'Articolo backend non trovato o post type non valido', 'backend_post_id' => $backend_post_id, ], 404); } $changed_fields = []; $field_labels = [ 'post_title' => 'titolo', 'post_content' => 'contenuto', 'post_excerpt' => 'excerpt', 'post_name' => 'permalink/slug', 'comment_status' => 'stato commenti', 'ping_status' => 'stato ping', 'post_password' => 'password articolo', ]; foreach ($field_labels as $field => $label) { if (!array_key_exists($field, $incoming_post)) continue; $current_value = (string)get_post_field($field, $backend_post_id, 'raw'); if ((string)$incoming_post[$field] !== $current_value) { $changed_fields[] = $label; } } if (isset($incoming_post['menu_order']) && (int)$incoming_post['menu_order'] !== (int)get_post_field('menu_order', $backend_post_id, 'raw')) { $changed_fields[] = 'ordine menu'; } if (!empty($incoming_post['post_status']) && $incoming_post['post_status'] === 'publish' && get_post_status($backend_post_id) !== 'publish') { $changed_fields[] = 'stato articolo → publish'; } $update = ['ID' => $backend_post_id]; foreach (['post_title', 'post_content', 'post_excerpt', 'post_name', 'comment_status', 'ping_status', 'post_password'] as $field) { if (array_key_exists($field, $incoming_post)) { $update[$field] = wp_slash((string)$incoming_post[$field]); } } if (isset($incoming_post['menu_order'])) { $update['menu_order'] = (int)$incoming_post['menu_order']; } // FE -> BE può portare lo stato solo a publish. Stati FE diversi da publish sono bloccati prima. if (!empty($incoming_post['post_status']) && $incoming_post['post_status'] === 'publish') { $update['post_status'] = 'publish'; } $this->is_receiving_frontend_sync = true; $updated = wp_update_post($update, true); if (is_wp_error($updated)) { $this->is_receiving_frontend_sync = false; $this->add_article_log($backend_post_id, 'FE→BE', 'Errore aggiornamento BE da FE', ['errore' => $updated->get_error_message()]); return new WP_REST_Response([ 'success' => false, 'message' => $updated->get_error_message(), ], 500); } $changed_taxonomies = []; if (!empty($data['terms']) && is_array($data['terms'])) { $changed_taxonomies = $this->sync_frontend_terms_to_backend($backend_post_id, $data['terms']); } $synced_meta = []; if (!empty($data['meta']) && is_array($data['meta'])) { $synced_meta = $this->sync_frontend_meta_to_backend($backend_post_id, $data['meta']); } $this->is_receiving_frontend_sync = false; $payload_hash = !empty($data['frontend_hash']) ? sanitize_text_field((string)$data['frontend_hash']) : md5(wp_json_encode($data)); update_post_meta($backend_post_id, self::META_LAST_UPDATE_FROM_FE_AT, current_time('mysql')); update_post_meta($backend_post_id, self::META_LAST_FE_SYNC_STATUS, 'received'); update_post_meta($backend_post_id, self::META_LAST_FE_SYNC_MSG, 'Modifica ricevuta dal frontend e applicata al backend.'); if (!empty($data['frontend_post_id'])) { update_post_meta($backend_post_id, self::META_LAST_FE_POST_ID, (int)$data['frontend_post_id']); } update_post_meta($backend_post_id, self::META_LAST_FE_CHANGED_FIELDS, array_values(array_unique($changed_fields))); update_post_meta($backend_post_id, self::META_LAST_FE_SYNCED_META_KEYS, array_values(array_unique($synced_meta))); update_post_meta($backend_post_id, self::META_LAST_FE_SYNCED_TAXONOMIES, array_values(array_unique($changed_taxonomies))); $this->add_article_log($backend_post_id, 'FE→BE', 'Modifica FE applicata al BE', [ 'frontend_post_id' => !empty($data['frontend_post_id']) ? (int)$data['frontend_post_id'] : 0, 'campi' => implode(',', array_slice(array_values(array_unique($changed_fields)), 0, 8)), 'meta' => implode(',', array_slice(array_values(array_unique($synced_meta)), 0, 8)), 'tax' => implode(',', array_slice(array_values(array_unique($changed_taxonomies)), 0, 8)), ]); update_post_meta($backend_post_id, self::META_LAST_HASH, $payload_hash); update_post_meta($backend_post_id, self::META_LAST_SENT_HASH, $payload_hash); update_post_meta($backend_post_id, self::META_QUEUE_STATUS, 'synced_from_frontend'); update_post_meta($backend_post_id, self::META_LAST_DIAG, 'Modificato nel frontend e sincronizzato al backend. FE post ID: ' . (!empty($data['frontend_post_id']) ? (int)$data['frontend_post_id'] : 0) . '.'); clean_post_cache($backend_post_id); return new WP_REST_Response([ 'success' => true, 'message' => 'Modifica FE applicata al BE', 'backend_post_id' => $backend_post_id, 'frontend_post_id' => !empty($data['frontend_post_id']) ? (int)$data['frontend_post_id'] : 0, 'post_status' => get_post_status($backend_post_id), 'post_title_md5' => md5((string)get_post_field('post_title', $backend_post_id, 'raw')), 'post_content_md5' => md5((string)get_post_field('post_content', $backend_post_id, 'raw')), 'post_excerpt_md5' => md5((string)get_post_field('post_excerpt', $backend_post_id, 'raw')), 'changed_fields' => array_values(array_unique($changed_fields)), 'synced_meta_keys' => $synced_meta, 'synced_taxonomies' => $changed_taxonomies, 'synced_at' => current_time('mysql'), ], 200); } protected function be_to_fe_meta_blacklist_option_name() { return 'sv_bridge_be_to_fe_meta_blacklist'; } protected function normalize_meta_blacklist_input($raw) { if (is_array($raw)) { $parts = $raw; } else { $raw = (string)$raw; $parts = preg_split('/[\r\n,;]+/', $raw); } $out = []; foreach ((array)$parts as $key) { $key = trim((string)$key); if ($key === '') continue; // I metakey WordPress possono contenere underscore, trattini, punti e due punti. $key = preg_replace('/[^A-Za-z0-9_\-\.\:]/', '', $key); if ($key === '') continue; $out[$key] = true; } return array_keys($out); } protected function get_be_to_fe_meta_blacklist() { $list = get_option($this->be_to_fe_meta_blacklist_option_name(), []); return $this->normalize_meta_blacklist_input($list); } protected function is_be_to_fe_meta_blocked($key) { $key = (string)$key; if ($key === '') return false; return in_array($key, $this->get_be_to_fe_meta_blacklist(), true); } protected function be_to_fe_status_mode_option_name() { return 'sv_bridge_be_to_fe_status_mode'; } protected function be_to_fe_selected_statuses_option_name() { return 'sv_bridge_be_to_fe_selected_statuses'; } protected function allowed_bridge_post_statuses() { $statuses = get_post_stati([], 'names'); if (!is_array($statuses) || empty($statuses)) { $statuses = ['publish', 'future', 'draft', 'pending', 'private']; } // Stati tecnici/di sistema che non devono mai creare o aggiornare articoli sul FE. $blocked = ['auto-draft', 'inherit', 'trash']; $out = []; foreach ($statuses as $status) { $status = sanitize_key((string)$status); if ($status === '' || in_array($status, $blocked, true)) continue; $out[$status] = $status; } foreach (['publish', 'future', 'draft', 'pending', 'private'] as $default_status) { if (!isset($out[$default_status])) $out[$default_status] = $default_status; } return array_values($out); } protected function normalize_status_list_input($raw) { if (is_array($raw)) { $parts = $raw; } else { $parts = preg_split('/[\r\n,;]+/', (string)$raw); } $allowed = array_flip($this->allowed_bridge_post_statuses()); $out = []; foreach ((array)$parts as $status) { $status = sanitize_key((string)$status); if ($status === '' || !isset($allowed[$status])) continue; $out[$status] = true; } return array_keys($out); } protected function get_be_to_fe_status_mode() { $mode = get_option($this->be_to_fe_status_mode_option_name(), 'publish_only'); $mode = sanitize_key((string)$mode); if (!in_array($mode, ['all', 'publish_only', 'selected'], true)) $mode = 'all'; return $mode; } protected function get_be_to_fe_selected_statuses() { $list = get_option($this->be_to_fe_selected_statuses_option_name(), ['publish']); $list = $this->normalize_status_list_input($list); return !empty($list) ? $list : ['publish']; } protected function get_be_to_fe_syncable_statuses() { $mode = $this->get_be_to_fe_status_mode(); if ($mode === 'publish_only') return ['publish']; if ($mode === 'selected') return $this->get_be_to_fe_selected_statuses(); return $this->allowed_bridge_post_statuses(); } protected function is_post_status_allowed_for_be_to_fe($status) { $status = sanitize_key((string)$status); if ($status === '' || in_array($status, ['auto-draft', 'inherit', 'trash'], true)) return false; return in_array($status, $this->get_be_to_fe_syncable_statuses(), true); } protected function cleanup_be_queue_after_status_rule_change() { $removed = 0; $queue = $this->get_queue(); if (empty($queue)) return 0; $new_queue = []; foreach ((array)$queue as $item) { $post_id = !empty($item['post_id']) ? (int)$item['post_id'] : 0; if (!$post_id) continue; $post = get_post($post_id); if (!$post || !$this->is_syncable_post_for_frontend($post)) { update_post_meta($post_id, self::META_QUEUE_STATUS, 'blocked_status_removed_from_queue'); update_post_meta($post_id, self::META_LAST_DIAG, 'Coda BE → FE rimossa: lo stato articolo non è consentito dalle impostazioni di sync stati.'); $this->add_article_log($post_id, 'BE→FE', 'Coda rimossa per stato non consentito', [ 'post_status' => $post ? $post->post_status : 'missing', 'allowed_statuses' => implode(',', $this->get_be_to_fe_syncable_statuses()), ]); $removed++; continue; } $new_queue[] = $item; } if ($removed > 0) $this->save_queue($new_queue); return $removed; } public function add_settings_page() { add_options_page( 'SV Bridge Meta Blacklist', 'SV Bridge Meta', 'manage_options', 'sv-bridge-meta-blacklist', [$this, 'render_meta_blacklist_settings_page'] ); } protected function get_distinct_article_metakeys($limit = 300, $offset = 0, $search = '') { global $wpdb; $limit = max(50, min(1000, (int)$limit)); $offset = max(0, (int)$offset); $search = trim((string)$search); $post_types = ['post', 'page', 'product']; $placeholders = implode(',', array_fill(0, count($post_types), '%s')); $where = "WHERE p.post_type IN ($placeholders) AND pm.meta_key <> ''"; $params = $post_types; if ($search !== '') { $where .= " AND pm.meta_key LIKE %s"; $params[] = '%' . $wpdb->esc_like($search) . '%'; } $sql = "SELECT pm.meta_key, COUNT(*) AS total_rows, COUNT(DISTINCT pm.post_id) AS post_count FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id $where GROUP BY pm.meta_key ORDER BY pm.meta_key ASC LIMIT %d OFFSET %d"; $params[] = $limit; $params[] = $offset; $rows = $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A); return is_array($rows) ? $rows : []; } protected function count_distinct_article_metakeys($search = '') { global $wpdb; $search = trim((string)$search); $post_types = ['post', 'page', 'product']; $placeholders = implode(',', array_fill(0, count($post_types), '%s')); $where = "WHERE p.post_type IN ($placeholders) AND pm.meta_key <> ''"; $params = $post_types; if ($search !== '') { $where .= " AND pm.meta_key LIKE %s"; $params[] = '%' . $wpdb->esc_like($search) . '%'; } $sql = "SELECT COUNT(*) FROM ( SELECT pm.meta_key FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id $where GROUP BY pm.meta_key ) AS svb_keys"; return (int)$wpdb->get_var($wpdb->prepare($sql, $params)); } protected function cleanup_be_queue_after_meta_blacklist_change($old_list, $new_list) { $old_list = $this->normalize_meta_blacklist_input($old_list); $new_list = $this->normalize_meta_blacklist_input($new_list); $newly_blocked = array_values(array_diff($new_list, $old_list)); if (empty($newly_blocked)) return 0; $removed = 0; $queue = $this->get_queue(); $new_queue = []; foreach ((array)$queue as $item) { $post_id = !empty($item['post_id']) ? (int)$item['post_id'] : 0; if (!$post_id) continue; $keep = true; $post = get_post($post_id); if ($post) { $remote_title_hash = (string)get_post_meta($post_id, self::META_REMOTE_TITLE_HASH, true); $remote_content_hash = (string)get_post_meta($post_id, self::META_REMOTE_CONTENT_HASH, true); $remote_excerpt_hash = (string)get_post_meta($post_id, self::META_REMOTE_EXCERPT_HASH, true); $non_meta_diff = false; if ($remote_title_hash && !hash_equals($remote_title_hash, md5((string)get_post_field('post_title', $post_id, 'raw')))) $non_meta_diff = true; if ($remote_content_hash && !hash_equals($remote_content_hash, md5((string)get_post_field('post_content', $post_id, 'raw')))) $non_meta_diff = true; if ($remote_excerpt_hash && !hash_equals($remote_excerpt_hash, md5((string)get_post_field('post_excerpt', $post_id, 'raw')))) $non_meta_diff = true; if (!$non_meta_diff) { $payload = [ 'entity' => 'post', 'action' => 'upsert', 'data' => $this->build_post($post_id), ]; $filtered_hash = $this->build_payload_hash($payload); update_post_meta($post_id, self::META_LAST_HASH, $filtered_hash); update_post_meta($post_id, self::META_QUEUE_STATUS, 'blocked_meta_removed_from_queue'); update_post_meta($post_id, self::META_LAST_DIAG, 'Coda BE → FE rimossa dopo blacklist metakey: ' . implode(', ', array_slice($newly_blocked, 0, 12)) . '.'); $this->add_article_log($post_id, 'BE→FE', 'Coda rimossa per metakey bloccato', ['metakey' => implode(',', array_slice($newly_blocked, 0, 12))]); $removed++; $keep = false; } } if ($keep) $new_queue[] = $item; } if ($removed > 0) $this->save_queue($new_queue); return $removed; } public function render_meta_blacklist_settings_page() { if (!current_user_can('manage_options')) wp_die('Permessi insufficienti'); $option = $this->be_to_fe_meta_blacklist_option_name(); $saved = $this->get_be_to_fe_meta_blacklist(); $notice = ''; if (!empty($_POST['sv_bridge_save_meta_blacklist'])) { check_admin_referer('sv_bridge_save_be_meta_blacklist'); $old_saved = $saved; $manual = isset($_POST['sv_bridge_meta_blacklist_manual']) ? wp_unslash($_POST['sv_bridge_meta_blacklist_manual']) : ''; $checked = isset($_POST['sv_bridge_meta_blacklist_checked']) ? (array)wp_unslash($_POST['sv_bridge_meta_blacklist_checked']) : []; $new = array_merge($this->normalize_meta_blacklist_input($manual), $this->normalize_meta_blacklist_input($checked)); $new = array_values(array_unique($new)); sort($new, SORT_NATURAL | SORT_FLAG_CASE); update_option($option, $new, false); $saved = $new; $old_status_mode = $this->get_be_to_fe_status_mode(); $old_statuses = $this->get_be_to_fe_selected_statuses(); $status_mode = isset($_POST['sv_bridge_be_to_fe_status_mode']) ? sanitize_key(wp_unslash($_POST['sv_bridge_be_to_fe_status_mode'])) : 'all'; if (!in_array($status_mode, ['all', 'publish_only', 'selected'], true)) $status_mode = 'all'; $selected_statuses = isset($_POST['sv_bridge_be_to_fe_selected_statuses']) ? (array)wp_unslash($_POST['sv_bridge_be_to_fe_selected_statuses']) : []; $selected_statuses = $this->normalize_status_list_input($selected_statuses); if (empty($selected_statuses)) $selected_statuses = ['publish']; update_option($this->be_to_fe_status_mode_option_name(), $status_mode, false); update_option($this->be_to_fe_selected_statuses_option_name(), $selected_statuses, false); $removed = $this->cleanup_be_queue_after_meta_blacklist_change($old_saved, $new); $removed_status = 0; if ($old_status_mode !== $status_mode || maybe_serialize($old_statuses) !== maybe_serialize($selected_statuses)) { $removed_status = $this->cleanup_be_queue_after_status_rule_change(); } $notice = 'Impostazioni SV Bridge salvate.' . ($removed > 0 ? ' Code rimosse/fermate per metakey bloccati: ' . (int)$removed . '.' : '') . ($removed_status > 0 ? ' Code rimosse/fermate per stato non consentito: ' . (int)$removed_status . '.' : ''); } $show_scan = !empty($_GET['sv_bridge_show_metakeys']); $search = isset($_GET['sv_bridge_meta_search']) ? sanitize_text_field(wp_unslash($_GET['sv_bridge_meta_search'])) : ''; $per_page = 300; $paged = isset($_GET['sv_bridge_meta_paged']) ? max(1, (int)$_GET['sv_bridge_meta_paged']) : 1; $offset = ($paged - 1) * $per_page; $rows = $show_scan ? $this->get_distinct_article_metakeys($per_page, $offset, $search) : []; $total_keys = $show_scan ? $this->count_distinct_article_metakeys($search) : 0; $total_pages = $show_scan ? max(1, (int)ceil($total_keys / $per_page)) : 1; $page_url = admin_url('options-general.php?page=sv-bridge-meta-blacklist'); echo '
'; echo '

SV Bridge Meta Blacklist

'; if ($notice) echo '

' . esc_html($notice) . '

'; echo '

Da qui blocchi i metakey che questo backend non deve mandare al frontend durante la sync BE → FE.

'; echo '

Esempio: se inserisci post_views_count o _post_views_count, quei valori non verranno più inviati al FE dal BE.

'; echo '
'; wp_nonce_field('sv_bridge_save_be_meta_blacklist'); $status_mode = $this->get_be_to_fe_status_mode(); $selected_statuses = $this->get_be_to_fe_selected_statuses(); $all_statuses = $this->allowed_bridge_post_statuses(); echo '

Modalità invio articoli BE → FE: stati da sincronizzare

'; echo '

Decidi quali articoli del backend possono entrare in coda ed essere inviati al frontend. auto-draft, trash e inherit restano sempre esclusi per evitare bozze orfane o stati tecnici.

'; echo '
'; echo '

Impostazione attiva: ' . esc_html($status_mode === 'publish_only' ? 'solo publish' : ($status_mode === 'selected' ? 'solo stati selezionati' : 'tutti gli stati editoriali')) . ' — stati effettivi: ' . esc_html(implode(', ', $this->get_be_to_fe_syncable_statuses())) . '

'; echo '

'; echo '

'; echo '
'; foreach ($all_statuses as $status) { echo ''; } echo '
'; echo '

'; echo '

Se cambi questa impostazione, eventuali code BE → FE con stati non più consentiti vengono rimosse automaticamente.

'; echo '
'; echo '

Metakey bloccati BE → FE

'; echo ''; echo '

Puoi separarli anche con virgole. Quando salvi, le code BE → FE legate solo a metakey appena bloccati vengono fermate dove possibile.

'; echo '

Mostra tutti i metakey presenti negli articoli

'; if ($show_scan) { echo '

Metakey trovati negli articoli di questo sito

'; echo '

Lettura diretta da postmeta per post, pagine e prodotti. Totale metakey distinti trovati: ' . (int)$total_keys . '.

'; echo '

'; echo 'Cerca '; if ($search !== '') echo 'Reset ricerca'; echo '

'; if (empty($rows)) { echo '

Nessun metakey trovato.

'; } else { echo '
'; echo ''; foreach ($rows as $row) { $key = isset($row['meta_key']) ? (string)$row['meta_key'] : ''; if ($key === '') continue; $checked = in_array($key, $saved, true) ? ' checked' : ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
BloccaMetakeyArticoliRighe meta
' . esc_html($key) . '' . (int)($row['post_count'] ?? 0) . '' . (int)($row['total_rows'] ?? 0) . '
'; if ($total_pages > 1) { echo '

'; if ($paged > 1) echo '← Precedenti '; echo 'Pagina ' . (int)$paged . ' di ' . (int)$total_pages . ''; if ($paged < $total_pages) echo ' Successivi →'; echo '

'; } } } echo '

'; echo '
'; echo '
'; } public function add_cron_interval($schedules) { // Conservato per compatibilità. Non viene più creato un cron ricorrente ogni minuto. return $schedules; } public function ensure_cron() { /* * Migrazione automatica dal vecchio cron ricorrente ogni minuto. * Il worker BE -> FE viene ora creato solo quando la coda contiene lavoro. */ $installed = (string)get_option(self::SCHEDULER_OPTION, ''); if ($installed !== self::SCHEDULER_VERSION) { wp_clear_scheduled_hook(self::CRON_HOOK); update_option(self::SCHEDULER_OPTION, self::SCHEDULER_VERSION, false); } $queue = $this->get_queue(); if (!empty($queue) && !wp_next_scheduled(self::CRON_HOOK)) { wp_schedule_single_event(time() + 10, self::CRON_HOOK); } } public function force_frontend_upload_urls($dirs) { $dirs['baseurl'] = $this->frontend_uploads_base; $dirs['url'] = $this->frontend_uploads_base . $dirs['subdir']; return $dirs; } public function force_attachment_url_to_frontend($url, $post_id) { return preg_replace('#https?://cms\\.cronachedellacampania\\.it/wp-content/uploads#i', $this->frontend_uploads_base, $url); } public function force_srcset_to_frontend($sources) { if (!is_array($sources)) return $sources; foreach ($sources as &$source) { if (!empty($source['url'])) { $source['url'] = preg_replace('#https?://cms\\.cronachedellacampania\\.it/wp-content/uploads#i', $this->frontend_uploads_base, $source['url']); } } return $sources; } protected function skip_meta_key($key) { $blocked_exact = [ '_edit_lock', '_edit_last', '_wp_old_slug', '_wp_trash_meta_status', '_wp_trash_meta_time', self::META_LAST_HASH, self::META_LAST_SENT_HASH, self::META_LAST_TRY, self::META_LAST_ERROR, self::META_LAST_RESPONSE_CODE, self::META_LAST_RESPONSE_BODY, self::META_LAST_DIAG, self::META_QUEUE_STATUS, self::META_QUEUE_AT, self::META_DELIVERED, self::META_DELIVERED_AT, self::META_FEATURED_FAIL, self::META_FEATURED_FAIL_MSG, self::META_WAITING_FEATURED_SINCE, self::META_REMOTE_POST_ID, self::META_REMOTE_STATUS, self::META_REMOTE_TITLE, self::META_REMOTE_LAST_SYNC, self::META_REMOTE_FEATURED_OK, self::META_REMOTE_LEGACY_LINKED_AT, self::META_REMOTE_LAST_VERIFIED_AT, self::META_REMOTE_CONTENT_VERIFIED, self::META_REMOTE_CONTENT_HASH, self::META_REMOTE_EXPECTED_CONTENT_HASH, self::META_REMOTE_TITLE_HASH, self::META_REMOTE_EXCERPT_HASH, self::META_REMOTE_VERIFICATION_MSG, self::META_REMOTE_POST_TYPE, self::META_REMOTE_IS_ATTACHMENT, ]; $blocked_prefix = ['_oembed_', '_sv_bridge_']; if (in_array($key, $blocked_exact, true)) return true; foreach ($blocked_prefix as $prefix) { if (strpos($key, $prefix) === 0) return true; } return false; } protected function diagnose_response($code) { if ((int)$code === 200) return 'Sync riuscita correttamente.'; if ((int)$code === 403) return 'Frontend raggiunto ma richiesta rifiutata (403 Unauthorized).'; if ((int)$code === 404) return 'Endpoint frontend non trovato.'; if ((int)$code === 500) return 'Errore interno sul frontend.'; return 'Risposta inattesa: ' . (int)$code; } protected function build_user($user_id) { $u = get_userdata($user_id); if (!$u) return []; return [ 'ID' => (int)$u->ID, 'user_login' => $u->user_login, 'user_email' => $u->user_email, 'display_name' => $u->display_name, 'first_name' => get_user_meta($u->ID, 'first_name', true), 'last_name' => get_user_meta($u->ID, 'last_name', true), 'role' => !empty($u->roles[0]) ? $u->roles[0] : 'author', ]; } protected function build_term($term_id, $taxonomy) { $t = get_term($term_id, $taxonomy); if (!$t || is_wp_error($t)) return []; return [ 'term_id' => (int)$t->term_id, 'taxonomy' => $taxonomy, 'name' => $t->name, 'slug' => $t->slug, 'description' => $t->description, 'parent' => (int)$t->parent, 'meta' => get_term_meta($t->term_id), ]; } protected function normalize_videopack_shortcodes($content) { $content = (string)$content; if ($content == '') return $content; $content = preg_replace_callback('/\[videopack([^\]]*)\]/i', function ($m) { $attrs = isset($m[1]) ? (string)$m[1] : ''; $attrs = preg_replace('/\s+id\s*=\s*(["\']).*?\1/i', '', $attrs); $attrs = preg_replace('/\s+id\s*=\s*[^\s\]]+/i', '', $attrs); $attrs = trim(preg_replace('/\s+/', ' ', $attrs)); return $attrs !== '' ? '[videopack ' . $attrs . ']' : '[videopack]'; }, $content); $content = preg_replace('/\[videopack\s+\]/i', '[videopack]', $content); return $content; } protected function maybe_normalize_backend_content($post_id, $post) { if ($this->is_normalizing_backend_content || !$post) return; $normalized = $this->normalize_videopack_shortcodes($post->post_content); if ($normalized === (string)$post->post_content) return; $this->is_normalizing_backend_content = true; wp_update_post([ 'ID' => (int)$post_id, 'post_content' => $normalized, ]); $this->is_normalizing_backend_content = false; } protected function scalar_contains_attachment_reference($value, $attachment_ids) { if (is_array($value) || is_object($value)) { foreach ((array)$value as $sub) { if ($this->scalar_contains_attachment_reference($sub, $attachment_ids)) return true; } return false; } if (is_numeric($value) && in_array((int)$value, $attachment_ids, true)) return true; if (is_string($value) && $value !== '') { foreach ($attachment_ids as $aid) { if (preg_match('/(^|[^\d])' . preg_quote((string)$aid, '/') . '([^\d]|$)/', $value)) { return true; } } } return false; } protected function key_looks_media_related($key) { return (bool)preg_match('/(thumb|thumbnail|image|img|video|videopack|poster|cover|fifu|attachment|media|player)/i', (string)$key); } protected function detect_meta_reference_hints($clean_meta, $attachment_ids) { $hints = []; $attachment_ids = array_values(array_unique(array_map('intval', (array)$attachment_ids))); if (empty($attachment_ids)) return $hints; foreach ((array)$clean_meta as $key => $vals) { $reason = []; if ($this->key_looks_media_related($key)) { $reason[] = 'media_like_key'; } foreach ((array)$vals as $raw) { $value = maybe_unserialize($raw); if ($this->scalar_contains_attachment_reference($value, $attachment_ids)) { $reason[] = 'contains_backend_attachment_id'; break; } } if (!empty($reason)) { $hints[$key] = array_values(array_unique($reason)); } } return $hints; } protected function build_attachment($attachment_id) { $a = get_post($attachment_id); if (!$a || $a->post_type !== 'attachment') return []; $a_meta = get_post_meta($attachment_id); $clean_a_meta = []; foreach ($a_meta as $k => $vals) { if ($this->skip_meta_key($k)) continue; if ($this->is_be_to_fe_meta_blocked($k)) continue; $clean_a_meta[$k] = $vals; } $attached_file = get_post_meta($attachment_id, '_wp_attached_file', true); $url = wp_get_attachment_url($attachment_id); $basename = $attached_file ? wp_basename($attached_file) : ($url ? wp_basename(parse_url($url, PHP_URL_PATH)) : ''); return [ 'ID' => (int)$a->ID, 'post_type' => 'attachment', 'post_status' => $a->post_status, 'post_mime_type' => $a->post_mime_type, 'post_title' => $a->post_title, 'post_content' => $this->normalize_videopack_shortcodes($a->post_content), 'post_excerpt' => $a->post_excerpt, 'post_name' => $a->post_name, 'post_date' => $a->post_date, 'post_date_gmt' => $a->post_date_gmt, 'guid' => $url, 'attached_file' => $attached_file, 'file_basename' => $basename, 'meta' => $clean_a_meta, ]; } protected function build_post($post_id) { $p = get_post($post_id); if (!$p) return []; $meta = get_post_meta($post_id); $clean_meta = []; foreach ($meta as $k => $vals) { if ($this->skip_meta_key($k)) continue; if ($this->is_be_to_fe_meta_blocked($k)) continue; $clean_meta[$k] = $vals; } $terms_payload = []; $taxes = get_object_taxonomies($p->post_type, 'names'); foreach ($taxes as $taxonomy) { $terms = wp_get_object_terms($p->ID, $taxonomy, ['hide_empty' => false]); if (is_wp_error($terms) || empty($terms)) continue; $terms_payload[$taxonomy] = []; foreach ($terms as $t) { $terms_payload[$taxonomy][] = $this->build_term($t->term_id, $taxonomy); } } $attachments_payload = []; $attachment_ids = []; $thumb_id = get_post_thumbnail_id($p->ID); if ($thumb_id) $attachment_ids[] = (int)$thumb_id; $attached = get_attached_media('', $p->ID); if (!empty($attached)) { foreach ($attached as $att) { $attachment_ids[] = (int)$att->ID; } } $attachment_ids = array_values(array_unique(array_filter($attachment_ids))); foreach ($attachment_ids as $aid) { $built = $this->build_attachment($aid); if (!empty($built)) $attachments_payload[] = $built; } $featured_url = $thumb_id ? wp_get_attachment_url($thumb_id) : ''; $featured_attached_file = $thumb_id ? get_post_meta($thumb_id, '_wp_attached_file', true) : ''; $featured_basename = $featured_attached_file ? wp_basename($featured_attached_file) : ($featured_url ? wp_basename(parse_url($featured_url, PHP_URL_PATH)) : ''); $normalized_content = $this->normalize_videopack_shortcodes($p->post_content); $meta_reference_hints = $this->detect_meta_reference_hints($clean_meta, $attachment_ids); return [ 'post' => [ 'ID' => (int)$p->ID, 'post_type' => $p->post_type, 'post_title' => $p->post_title, 'post_content' => $normalized_content, 'post_excerpt' => $p->post_excerpt, 'post_status' => $p->post_status, 'post_name' => $p->post_name, 'post_date' => $p->post_date, 'post_date_gmt' => $p->post_date_gmt, 'post_modified' => $p->post_modified, 'post_modified_gmt' => $p->post_modified_gmt, 'menu_order' => (int)$p->menu_order, 'comment_status' => $p->comment_status, 'ping_status' => $p->ping_status, 'post_password' => $p->post_password, 'post_parent' => (int)$p->post_parent, '_thumbnail_id' => $thumb_id ? (int)$thumb_id : 0, '_featured_image_url' => $featured_url, '_featured_file_name' => $featured_basename, '_featured_attached_file' => $featured_attached_file, ], 'author' => $this->build_user($p->post_author), 'terms' => $terms_payload, 'attachments' => $attachments_payload, 'meta' => $clean_meta, 'meta_reference_hints' => $meta_reference_hints, ]; } protected function build_payload_hash($payload) { return md5(wp_json_encode($payload)); } protected function is_syncable_post_for_frontend($post) { if (!$post || !($post instanceof WP_Post)) return false; if (wp_is_post_revision($post->ID)) return false; if (!in_array($post->post_type, ['post', 'page', 'product'], true)) return false; /* * WordPress crea post provvisori con post_status=auto-draft quando si apre * l'editor. Questi NON sono vere bozze editoriali e non devono mai arrivare * sul frontend, altrimenti restano come bozze abbandonate/orfane. */ if (in_array($post->post_status, ['auto-draft', 'inherit', 'trash'], true)) return false; if (!$this->is_post_status_allowed_for_be_to_fe($post->post_status)) return false; $title = trim((string)$post->post_title); $content = trim((string)$post->post_content); $excerpt = trim((string)$post->post_excerpt); // Evita di sincronizzare finte bozze vuote o il titolo tecnico "Auto Draft". if ($post->post_status === 'draft' && $title === '' && $content === '' && $excerpt === '') return false; if ($post->post_status === 'draft' && preg_match('/^auto\s*draft$/i', $title) && $content === '' && $excerpt === '') return false; return true; } protected function cleanup_unsyncable_post_state($post_id, $reason = 'unsyncable') { $post_id = (int)$post_id; if (!$post_id) return; $this->remove_post_from_queue($post_id); update_post_meta($post_id, self::META_QUEUE_STATUS, 'ignored_' . sanitize_key($reason)); update_post_meta($post_id, self::META_LAST_DIAG, 'Sync FE ignorata: ' . $reason . '. Nessuna bozza automatica deve essere creata sul frontend.'); delete_post_meta($post_id, self::META_LAST_ERROR); } protected function should_queue_post($post_id, $post, $payload_hash) { if (!$this->is_syncable_post_for_frontend($post)) { $this->cleanup_unsyncable_post_state($post_id, 'auto_draft_or_empty_draft'); return false; } $last_hash = (string)get_post_meta($post_id, self::META_LAST_HASH, true); update_post_meta($post_id, self::META_LAST_HASH, $payload_hash); return $last_hash !== $payload_hash; } protected function get_queue() { $queue = get_option(self::QUEUE_OPTION, []); return is_array($queue) ? $queue : []; } protected function save_queue($queue) { $queue = array_values((array)$queue); update_option(self::QUEUE_OPTION, $queue, false); // Compatibilità object cache persistente: forza la lettura aggiornata della option. if (function_exists('wp_cache_delete')) { wp_cache_delete(self::QUEUE_OPTION, 'options'); } } protected function remove_post_from_queue($post_id) { $post_id = (int) $post_id; if (!$post_id) return 0; $queue = $this->get_queue(); $new_queue = []; $removed = 0; foreach ($queue as $item) { if (!empty($item['post_id']) && (int)$item['post_id'] === $post_id) { $removed++; continue; } $new_queue[] = $item; } if ($removed > 0) { $this->save_queue($new_queue); } return $removed; } protected function queue_post($post_id, $force = false) { $post_id = (int) $post_id; if (!$post_id) return; $post = get_post($post_id); if (!$this->is_syncable_post_for_frontend($post)) { $this->cleanup_unsyncable_post_state($post_id, 'auto_draft_or_empty_draft'); return; } $queue = $this->get_queue(); $replacement = ['post_id' => $post_id, 'queued_at' => time()]; $new_queue = []; $inserted = false; foreach ($queue as $item) { if (!empty($item['post_id']) && (int)$item['post_id'] === $post_id) { if (!$inserted) { $new_queue[] = $replacement; $inserted = true; } continue; } $new_queue[] = $item; } if (!$inserted) $new_queue[] = $replacement; $this->save_queue($new_queue); update_post_meta($post_id, self::META_QUEUE_STATUS, 'queued'); update_post_meta($post_id, self::META_QUEUE_AT, current_time('mysql')); delete_post_meta($post_id, self::META_LAST_ERROR); $this->add_article_log($post_id, 'BE→FE', 'Messa in coda modifica BE', ['stato' => get_post_status($post_id)]); // Worker one-shot: nessun polling quando la coda è vuota. if (!wp_next_scheduled(self::CRON_HOOK)) { wp_schedule_single_event(time() + 5, self::CRON_HOOK); } } protected function dequeue_first() { $queue = $this->get_queue(); if (empty($queue)) return null; $item = array_shift($queue); $this->save_queue($queue); return $item; } protected function acquire_lock($post_id, $ttl = 120) { $key = self::LOCK_PREFIX . (int)$post_id; if (get_transient($key)) return false; set_transient($key, 1, $ttl); return true; } protected function release_lock($post_id) { delete_transient(self::LOCK_PREFIX . (int)$post_id); } protected function expected_frontend_hashes_from_data($data) { $data = is_array($data) ? $data : []; return [ 'post_content_md5' => md5(isset($data['post_content']) ? (string)$data['post_content'] : ''), 'post_title_md5' => md5(isset($data['post_title']) ? (string)$data['post_title'] : ''), 'post_excerpt_md5' => md5(isset($data['post_excerpt']) ? (string)$data['post_excerpt'] : ''), ]; } protected function remote_hashes_match($remote, $expected) { if (!is_array($remote) || !is_array($expected)) return false; foreach (['post_content_md5', 'post_title_md5', 'post_excerpt_md5'] as $key) { if (!isset($remote[$key]) || !isset($expected[$key])) return false; if (!hash_equals((string)$expected[$key], (string)$remote[$key])) return false; } return true; } protected function remote_bridge_payload_was_received($remote, $expected) { if (!is_array($remote) || !is_array($expected)) return false; $map = [ 'post_content_md5' => ['bridge_expected_content_md5', 'incoming_content_md5', 'last_be_content_md5'], 'post_title_md5' => ['bridge_expected_title_md5', 'incoming_title_md5', 'last_be_title_md5'], 'post_excerpt_md5' => ['bridge_expected_excerpt_md5', 'incoming_excerpt_md5', 'last_be_excerpt_md5'], ]; foreach ($map as $expected_key => $remote_keys) { if (empty($expected[$expected_key])) return false; $matched = false; foreach ($remote_keys as $remote_key) { if (isset($remote[$remote_key]) && hash_equals((string)$expected[$expected_key], (string)$remote[$remote_key])) { $matched = true; break; } } if (!$matched) return false; } return true; } protected function save_remote_status_from_response($post_id, $remote = [], $context = 'sync', $expected_hashes = []) { $post_id = (int)$post_id; if (!$post_id || !is_array($remote)) return; if (isset($remote['post_id'])) update_post_meta($post_id, self::META_REMOTE_POST_ID, (int)$remote['post_id']); if (isset($remote['post_status'])) update_post_meta($post_id, self::META_REMOTE_STATUS, sanitize_key((string)$remote['post_status'])); if (isset($remote['status'])) update_post_meta($post_id, self::META_REMOTE_STATUS, sanitize_key((string)$remote['status'])); if (isset($remote['title'])) update_post_meta($post_id, self::META_REMOTE_TITLE, wp_strip_all_tags((string)$remote['title'])); if (isset($remote['post_type'])) update_post_meta($post_id, self::META_REMOTE_POST_TYPE, sanitize_key((string)$remote['post_type'])); if (isset($remote['is_attachment'])) update_post_meta($post_id, self::META_REMOTE_IS_ATTACHMENT, !empty($remote['is_attachment']) ? 1 : 0); if (isset($remote['post_content_md5'])) update_post_meta($post_id, self::META_REMOTE_CONTENT_HASH, sanitize_text_field((string)$remote['post_content_md5'])); if (isset($remote['post_title_md5'])) update_post_meta($post_id, self::META_REMOTE_TITLE_HASH, sanitize_text_field((string)$remote['post_title_md5'])); if (isset($remote['post_excerpt_md5'])) update_post_meta($post_id, self::META_REMOTE_EXCERPT_HASH, sanitize_text_field((string)$remote['post_excerpt_md5'])); if (!empty($expected_hashes['post_content_md5'])) update_post_meta($post_id, self::META_REMOTE_EXPECTED_CONTENT_HASH, sanitize_text_field((string)$expected_hashes['post_content_md5'])); if (!empty($expected_hashes)) { $verified = $this->remote_hashes_match($remote, $expected_hashes); $payload_received = $this->remote_bridge_payload_was_received($remote, $expected_hashes); /* * Differenza importante: * - verified = l'hash reale del post FE coincide al 100% con il payload BE. * - payload_received = il FE ha ricevuto e memorizzato il payload BE, ma qualche hook/plugin del FE * può avere normalizzato il contenuto salvato, producendo un hash finale diverso. * * In questo secondo caso NON dobbiamo lasciare l'articolo in coda infinita: la consegna BE -> FE è riuscita. */ update_post_meta($post_id, self::META_REMOTE_CONTENT_VERIFIED, ($verified || $payload_received) ? 1 : 0); if ($verified) { $msg = 'OK: titolo, contenuto ed excerpt salvati sul FE coincidono con il payload BE.'; } elseif ($payload_received) { $msg = 'OK: il FE ha ricevuto il payload BE. Nota: l\'hash finale del contenuto FE è diverso, probabilmente per normalizzazioni/hook del frontend, ma la sync non resta in coda.'; } else { $msg = 'AVVISO: il FE ha risposto HTTP 200, ma almeno un hash reale tra titolo/contenuto/excerpt non coincide e il FE non ha restituito gli hash di payload ricevuto.'; } update_post_meta($post_id, self::META_REMOTE_VERIFICATION_MSG, $msg); } if (isset($remote['last_sync'])) update_post_meta($post_id, self::META_REMOTE_LAST_SYNC, sanitize_text_field((string)$remote['last_sync'])); if (isset($remote['featured_ok'])) update_post_meta($post_id, self::META_REMOTE_FEATURED_OK, !empty($remote['featured_ok']) ? 1 : 0); if (isset($remote['featured_synced_ok'])) update_post_meta($post_id, self::META_REMOTE_FEATURED_OK, !empty($remote['featured_synced_ok']) ? 1 : 0); if (!empty($remote['legacy_linked_at'])) { update_post_meta($post_id, self::META_REMOTE_LEGACY_LINKED_AT, sanitize_text_field((string)$remote['legacy_linked_at'])); } elseif (!empty($remote['legacy_linked'])) { if (!get_post_meta($post_id, self::META_REMOTE_LEGACY_LINKED_AT, true)) { update_post_meta($post_id, self::META_REMOTE_LEGACY_LINKED_AT, current_time('mysql')); } } update_post_meta($post_id, self::META_REMOTE_LAST_VERIFIED_AT, current_time('mysql')); $remote_id = get_post_meta($post_id, self::META_REMOTE_POST_ID, true); $remote_sync = get_post_meta($post_id, self::META_REMOTE_LAST_SYNC, true); $legacy = get_post_meta($post_id, self::META_REMOTE_LEGACY_LINKED_AT, true); $diag = 'Frontend aggiornato correttamente (' . $context . ').'; if ($remote_id) $diag .= ' ID FE: ' . $remote_id . '.'; if ($remote_sync) $diag .= ' Ultimo sync FE: ' . $remote_sync . '.'; if ($legacy) $diag .= ' Articolo legacy agganciato: ' . $legacy . '.'; $verified = get_post_meta($post_id, self::META_REMOTE_CONTENT_VERIFIED, true); if ($verified !== '') $diag .= ((int)$verified ? ' Modifica contenuto verificata via hash.' : ' ATTENZIONE: modifica contenuto NON verificata via hash.'); if ((int)get_post_meta($post_id, self::META_REMOTE_IS_ATTACHMENT, true)) $diag .= ' ATTENZIONE: il FE ha risposto con un attachment, non con un articolo.'; update_post_meta($post_id, self::META_LAST_DIAG, $diag); } protected function mark_post_delivered($post_id, $context = 'remote_check') { $this->remove_post_from_queue($post_id); update_post_meta($post_id, self::META_DELIVERED, 1); if (!get_post_meta($post_id, self::META_DELIVERED_AT, true)) { update_post_meta($post_id, self::META_DELIVERED_AT, current_time('mysql')); } update_post_meta($post_id, self::META_QUEUE_STATUS, 'sent'); update_post_meta($post_id, self::META_REMOTE_LAST_VERIFIED_AT, current_time('mysql')); delete_post_meta($post_id, self::META_LAST_ERROR); update_post_meta($post_id, self::META_LAST_DIAG, 'Verifica frontend OK (' . $context . ').'); } protected function hard_close_queue_as_sent($post_id, $context = 'frontend_confirmed', $remote = []) { $post_id = (int) $post_id; if (!$post_id) return false; // Pulizia forte: elimina tutte le occorrenze residue nella option queue. $removed = $this->remove_post_from_queue($post_id); update_post_meta($post_id, self::META_DELIVERED, 1); if (!get_post_meta($post_id, self::META_DELIVERED_AT, true)) { update_post_meta($post_id, self::META_DELIVERED_AT, current_time('mysql')); } update_post_meta($post_id, self::META_QUEUE_STATUS, 'sent'); update_post_meta($post_id, self::META_REMOTE_LAST_VERIFIED_AT, current_time('mysql')); delete_post_meta($post_id, self::META_LAST_ERROR); if (is_array($remote)) { if (isset($remote['post_id'])) update_post_meta($post_id, self::META_REMOTE_POST_ID, (int)$remote['post_id']); if (isset($remote['post_status'])) update_post_meta($post_id, self::META_REMOTE_STATUS, sanitize_key((string)$remote['post_status'])); if (isset($remote['status'])) update_post_meta($post_id, self::META_REMOTE_STATUS, sanitize_key((string)$remote['status'])); if (isset($remote['last_sync'])) update_post_meta($post_id, self::META_REMOTE_LAST_SYNC, sanitize_text_field((string)$remote['last_sync'])); if (isset($remote['featured_ok'])) update_post_meta($post_id, self::META_REMOTE_FEATURED_OK, !empty($remote['featured_ok']) ? 1 : 0); if (isset($remote['featured_synced_ok'])) update_post_meta($post_id, self::META_REMOTE_FEATURED_OK, !empty($remote['featured_synced_ok']) ? 1 : 0); } $diag = 'Coda chiusa forzatamente: frontend confermato (' . sanitize_text_field((string)$context) . ').'; if ($removed > 0) $diag .= ' Rimossi ' . (int)$removed . ' residui dalla queue.'; update_post_meta($post_id, self::META_LAST_DIAG, $diag); return true; } public function cleanup_stale_delivered_queue_items() { if (get_transient('sv_bridge_be_cleanup_queue_gate')) return; set_transient('sv_bridge_be_cleanup_queue_gate', 1, 5 * MINUTE_IN_SECONDS); $queue = $this->get_queue(); if (empty($queue)) return; $changed = false; $new_queue = []; $seen = []; $limit = 200; $i = 0; foreach ($queue as $item) { $i++; if ($i > $limit) { $new_queue[] = $item; continue; } $pid = !empty($item['post_id']) ? (int)$item['post_id'] : 0; if (!$pid) { $changed = true; continue; } // Elimina duplicati nella queue. if (isset($seen[$pid])) { $changed = true; continue; } $seen[$pid] = true; $remote_id = (int)get_post_meta($pid, self::META_REMOTE_POST_ID, true); $delivered = (bool)get_post_meta($pid, self::META_DELIVERED, true); $http_code = (int)get_post_meta($pid, self::META_LAST_RESPONSE_CODE, true); $remote_status = (string)get_post_meta($pid, self::META_REMOTE_STATUS, true); $remote_sync = (string)get_post_meta($pid, self::META_REMOTE_LAST_SYNC, true); // Caso reale del tuo log: FE confermato, HTTP 200, ma meta queue rimasta queued. if ($remote_id && ($delivered || ($http_code >= 200 && $http_code < 300)) && ($remote_status || $remote_sync)) { $this->hard_close_queue_as_sent($pid, 'cleanup stale queue'); $changed = true; continue; } $new_queue[] = $item; } if ($changed) { $this->save_queue($new_queue); } } protected function verify_remote_post_exists($post_id, $post_type = 'post') { $status_endpoint = $this->status_endpoint(); $secret = $this->secret(); if (!$status_endpoint || !$secret || !$post_id) return false; $response = wp_remote_get(add_query_arg([ 'origin_post_id' => (int)$post_id, 'post_type' => sanitize_key($post_type ?: 'post'), 't' => time(), // evita cache aggressive/proxy su endpoint status ], $status_endpoint), [ 'timeout' => 15, 'headers' => [ 'x-bridge-key' => $secret, 'Accept' => 'application/json', 'Cache-Control' => 'no-cache', ], ]); if (is_wp_error($response)) { return false; } $code = (int) wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); $json = json_decode($body, true); if ($code < 200 || $code >= 300 || !is_array($json) || empty($json['success']) || empty($json['exists'])) { return false; } /* * Non blocchiamo più la riconciliazione solo perché il FE segnala featured_error: * l'articolo può essere già presente e pubblicato. L'errore immagine resta salvato * come diagnostica, ma non deve lasciare residui infiniti in coda. */ return [ 'post_id' => !empty($json['post_id']) ? (int)$json['post_id'] : 0, 'post_status' => !empty($json['post_status']) ? (string)$json['post_status'] : '', 'title' => !empty($json['title']) ? (string)$json['title'] : '', 'received_at' => !empty($json['received_at']) ? (string)$json['received_at'] : '', 'last_sync' => !empty($json['last_sync']) ? (string)$json['last_sync'] : '', 'featured_ok' => isset($json['featured_ok']) ? (bool)$json['featured_ok'] : false, 'featured_error' => !empty($json['featured_error']), 'featured_error_msg' => !empty($json['featured_error_msg']) ? (string)$json['featured_error_msg'] : '', 'legacy_linked' => !empty($json['legacy_linked']), 'legacy_linked_at' => !empty($json['legacy_linked_at']) ? (string)$json['legacy_linked_at'] : '', 'post_type' => !empty($json['post_type']) ? (string)$json['post_type'] : '', 'is_attachment' => !empty($json['is_attachment']), 'post_content_md5' => !empty($json['post_content_md5']) ? (string)$json['post_content_md5'] : '', 'post_title_md5' => !empty($json['post_title_md5']) ? (string)$json['post_title_md5'] : '', 'post_excerpt_md5' => !empty($json['post_excerpt_md5']) ? (string)$json['post_excerpt_md5'] : '', 'post_modified' => !empty($json['post_modified']) ? (string)$json['post_modified'] : '', ]; } protected function maybe_reconcile_with_frontend($post_id, $post_type = 'post') { $remote = $this->verify_remote_post_exists($post_id, $post_type); if (!$remote) return false; $payload = $this->build_post($post_id); $expected_hashes = !empty($payload['post']) ? $this->expected_frontend_hashes_from_data($payload['post']) : []; /* * La sola esistenza del post sul FE non basta per chiudere la coda: * lo stato remoto deve coincidere con quello reale del backend. * In particolare, un publish BE non può essere riconciliato come draft FE. * Il controllo avviene solo quando serve una riconciliazione, quindi non aggiunge scansioni. */ $expected_status = sanitize_key((string)get_post_status($post_id)); $remote_status = !empty($remote['post_status']) ? sanitize_key((string)$remote['post_status']) : ''; if ($expected_status && $remote_status && $expected_status !== $remote_status) { $this->save_remote_status_from_response($post_id, $remote, 'verifica status mismatch', $expected_hashes); update_post_meta($post_id, self::META_LAST_ERROR, 'Status mismatch FE: atteso ' . $expected_status . ', trovato ' . $remote_status); update_post_meta($post_id, self::META_QUEUE_STATUS, 'error'); update_post_meta($post_id, self::META_LAST_DIAG, 'Riconciliazione non chiusa: il frontend esiste ma ha stato ' . $remote_status . ', mentre il backend richiede ' . $expected_status . '. Verrà ritentato il normale invio BE → FE.'); $this->add_article_log($post_id, 'BE→FE', 'Riconciliazione rifiutata per stato diverso', [ 'stato_be' => $expected_status, 'stato_fe' => $remote_status, ]); return false; } $this->hard_close_queue_as_sent($post_id, 'remote_status_status_ok', $remote); $this->save_remote_status_from_response($post_id, $remote, 'verifica status coerente', $expected_hashes); if (!empty($remote['received_at'])) { update_post_meta($post_id, self::META_DELIVERED_AT, $remote['received_at']); } update_post_meta($post_id, self::META_LAST_RESPONSE_CODE, 209); update_post_meta($post_id, self::META_LAST_RESPONSE_BODY, wp_json_encode([ 'success' => true, 'reconciled' => true, 'remote_post_id' => $remote['post_id'], 'remote_status' => $remote['post_status'], 'featured_ok' => $remote['featured_ok'], 'featured_error' => !empty($remote['featured_error']), 'legacy_linked' => !empty($remote['legacy_linked']), 'legacy_linked_at' => !empty($remote['legacy_linked_at']) ? $remote['legacy_linked_at'] : '', ])); return true; } protected function send($entity, $action, $data, $extra = [], $log_post_id = 0) { $endpoint = $this->endpoint(); $secret = $this->secret(); if (!$endpoint || !$secret) { if ($log_post_id) { update_post_meta($log_post_id, self::META_LAST_ERROR, 'Endpoint o secret mancanti'); update_post_meta($log_post_id, self::META_LAST_DIAG, 'Controlla SV_BRIDGE_FE_ENDPOINT e SV_BRIDGE_SECRET'); update_post_meta($log_post_id, self::META_LAST_TRY, current_time('mysql')); update_post_meta($log_post_id, self::META_QUEUE_STATUS, 'error'); } return false; } $payload = array_merge(['entity' => $entity, 'action' => $action, 'data' => $data], $extra); if ($log_post_id) { $this->add_article_log($log_post_id, 'BE→FE', 'Invio HTTP al frontend', [ 'azione' => $action, 'stato_be_inviato' => isset($data['post_status']) ? (string)$data['post_status'] : '', 'titolo' => isset($data['post_title']) ? wp_trim_words(wp_strip_all_tags((string)$data['post_title']), 8, '…') : '', ]); } $expected_hashes = ($entity === 'post' && $action !== 'delete' && is_array($data)) ? $this->expected_frontend_hashes_from_data($data) : []; $response = wp_remote_post($endpoint, [ 'method' => 'POST', 'timeout' => 25, 'headers' => [ 'Content-Type' => 'application/json', 'x-bridge-key' => $secret, 'Cache-Control' => 'no-cache', ], 'body' => wp_json_encode($payload), ]); if (is_wp_error($response)) { if ($log_post_id) { update_post_meta($log_post_id, self::META_LAST_ERROR, $response->get_error_message()); update_post_meta($log_post_id, self::META_LAST_DIAG, 'wp_remote_post fallita'); update_post_meta($log_post_id, self::META_LAST_TRY, current_time('mysql')); update_post_meta($log_post_id, self::META_QUEUE_STATUS, 'error'); $post = get_post($log_post_id); if ($post && $this->maybe_reconcile_with_frontend($log_post_id, $post->post_type)) { return true; } } return false; } $code = (int)wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); $json = json_decode($body, true); if ($log_post_id) { $this->add_article_log($log_post_id, 'FE→BE', 'Risposta HTTP dal frontend', [ 'http' => $code, 'success' => is_array($json) && !empty($json['success']) ? '1' : '0', 'stato_fe' => is_array($json) ? (string)($json['status'] ?? ($json['post_status'] ?? '')) : '', 'desired_status_fe' => is_array($json) ? (string)($json['desired_status'] ?? '') : '', 'id_fe' => is_array($json) ? (string)($json['post_id'] ?? '') : '', ]); } if ($log_post_id) { update_post_meta($log_post_id, self::META_LAST_TRY, current_time('mysql')); update_post_meta($log_post_id, self::META_LAST_RESPONSE_CODE, $code); update_post_meta($log_post_id, self::META_LAST_RESPONSE_BODY, wp_strip_all_tags($body)); update_post_meta($log_post_id, self::META_LAST_DIAG, $this->diagnose_response($code)); } if ($code >= 200 && $code < 300) { if ($log_post_id && is_array($json) && !empty($expected_hashes) && !$this->remote_hashes_match($json, $expected_hashes)) { /* * Prima questo caso veniva trattato come errore e rimesso in coda. * È il motivo degli articoli "In coda" pur essendo già pubblicati sul frontend. * Da ora lo trattiamo come warning: salviamo la diagnostica, ma la consegna resta OK * se il FE ha restituito HTTP 200 e ID post valido. */ update_post_meta($log_post_id, self::META_LAST_DIAG, 'Frontend aggiornato correttamente con avviso hash: il FE ha ricevuto il post, ma l\'hash reale finale non coincide al 100%. La coda viene comunque chiusa per evitare retry infiniti.'); $this->save_remote_status_from_response($log_post_id, $json, 'sync hash warning', $expected_hashes); } if ($log_post_id && is_array($json) && $entity === 'post' && $action !== 'delete' && is_array($data)) { $expected_status = !empty($data['post_status']) ? sanitize_key((string)$data['post_status']) : ''; $remote_status = ''; if (isset($json['status'])) $remote_status = sanitize_key((string)$json['status']); if (!$remote_status && isset($json['post_status'])) $remote_status = sanitize_key((string)$json['post_status']); if ($expected_status && $remote_status && $expected_status !== $remote_status) { update_post_meta($log_post_id, self::META_LAST_ERROR, 'Status mismatch FE: atteso ' . $expected_status . ', ricevuto ' . $remote_status); update_post_meta($log_post_id, self::META_QUEUE_STATUS, 'status_mismatch'); update_post_meta($log_post_id, self::META_LAST_DIAG, 'Il frontend ha ricevuto il contenuto, ma lo stato non coincide. Atteso dal BE: ' . $expected_status . '. Stato reale FE: ' . $remote_status . '. La sync resta KO per evitare pubblicazioni o bozze errate.'); $this->save_remote_status_from_response($log_post_id, $json, 'sync status mismatch', $expected_hashes); return false; } } if ($log_post_id && is_array($json) && !empty($json['success']) && !empty($json['post_id'])) { // Il FE ha accettato e salvato il post: chiusura forte della coda, anche se gli hash sono warning. $this->hard_close_queue_as_sent($log_post_id, 'sync confirmed by FE', $json); } if ($log_post_id && is_array($json) && !empty($json['featured_required']) && empty($json['featured_synced_ok'])) { $local_post = get_post($log_post_id); update_post_meta($log_post_id, self::META_FEATURED_FAIL, 1); update_post_meta($log_post_id, self::META_FEATURED_FAIL_MSG, 'Il frontend non ha ancora una featured image valida. Lo stato editoriale del backend NON viene modificato.'); update_post_meta($log_post_id, self::META_QUEUE_STATUS, 'image_failed'); update_post_meta($log_post_id, self::META_LAST_DIAG, 'FE ricevuto ma featured non pronta. Il BE resta nello stato canonico ' . ($local_post ? $local_post->post_status : 'sconosciuto') . '; nessuna retrocessione automatica a draft.'); $this->add_article_log($log_post_id, 'BE→FE', 'Featured FE non pronta: stato BE preservato', [ 'stato_be' => $local_post ? $local_post->post_status : 'missing', 'stato_fe' => (string)($json['status'] ?? ''), ]); $this->save_remote_status_from_response($log_post_id, $json, 'sync image warning', $expected_hashes); return false; } if ($log_post_id) { $this->mark_post_delivered($log_post_id, 'sync'); if (is_array($json)) { $this->save_remote_status_from_response($log_post_id, $json, 'sync', $expected_hashes); } delete_post_meta($log_post_id, self::META_FEATURED_FAIL); delete_post_meta($log_post_id, self::META_FEATURED_FAIL_MSG); } return true; } if ($log_post_id) { update_post_meta($log_post_id, self::META_LAST_ERROR, 'HTTP ' . $code); update_post_meta($log_post_id, self::META_QUEUE_STATUS, 'error'); $post = get_post($log_post_id); if ($post && $this->maybe_reconcile_with_frontend($log_post_id, $post->post_type)) { return true; } } return false; } protected function send_full_post($post_id, $action = 'upsert') { $post = get_post($post_id); if (!$this->is_syncable_post_for_frontend($post)) { $this->cleanup_unsyncable_post_state($post_id, 'auto_draft_or_empty_draft'); return true; } $payload = $this->build_post($post_id); if (empty($payload['post'])) return false; $hash = $this->build_payload_hash($payload); $ok = $this->send('post', $action, $payload['post'], [ 'author' => $payload['author'], 'terms' => $payload['terms'], 'attachments' => $payload['attachments'], 'meta' => $payload['meta'], 'meta_reference_hints' => !empty($payload['meta_reference_hints']) ? $payload['meta_reference_hints'] : [], ], $post_id); if ($ok) { $this->remove_post_from_queue($post_id); update_post_meta($post_id, self::META_LAST_SENT_HASH, $hash); } return $ok; } protected function max_featured_wait_seconds() { if (defined('SV_BRIDGE_FEATURED_WAIT_SECONDS')) { return max(60, (int) SV_BRIDGE_FEATURED_WAIT_SECONDS); } return 10 * MINUTE_IN_SECONDS; } protected function require_featured_for_frontend($post) { if (!$post || $post->post_status !== 'publish') return false; if (!in_array($post->post_type, ['post'], true)) return false; if (defined('SV_BRIDGE_REQUIRE_FEATURED_IMAGE') && !SV_BRIDGE_REQUIRE_FEATURED_IMAGE) return false; return true; } protected function has_backend_featured_image_ready($post_id) { $thumb_id = (int) get_post_thumbnail_id($post_id); if ($thumb_id > 0) { return true; } // Fallback per eventuali plugin tipo FIFU: se c'è un URL esterno immagine, non blocchiamo il sync. $fifu_keys = ['fifu_image_url', '_fifu_image_url', '_external_featured_image', 'external_featured_image']; foreach ($fifu_keys as $key) { $url = trim((string) get_post_meta($post_id, $key, true)); if ($url !== '' && filter_var($url, FILTER_VALIDATE_URL)) { return true; } } return false; } protected function hold_post_waiting_for_featured($post_id, $post) { if (!$this->require_featured_for_frontend($post)) return false; if ($this->has_backend_featured_image_ready($post_id)) { delete_post_meta($post_id, self::META_WAITING_FEATURED_SINCE); return false; } $first_wait = (int) get_post_meta($post_id, self::META_WAITING_FEATURED_SINCE, true); if (!$first_wait) { $first_wait = time(); update_post_meta($post_id, self::META_WAITING_FEATURED_SINCE, $first_wait); } if ((time() - $first_wait) < $this->max_featured_wait_seconds()) { $this->queue_post($post_id, true); update_post_meta($post_id, self::META_QUEUE_STATUS, 'waiting_image'); update_post_meta($post_id, self::META_LAST_DIAG, 'In attesa immagine in evidenza prima di inviare al frontend.'); update_post_meta($post_id, self::META_FEATURED_FAIL_MSG, 'Il post è pubblicato sul backend ma la featured image non è ancora pronta. Sync FE rinviato.'); return true; } update_post_meta($post_id, self::META_QUEUE_STATUS, 'image_missing'); update_post_meta($post_id, self::META_FEATURED_FAIL, 1); update_post_meta($post_id, self::META_FEATURED_FAIL_MSG, 'Post pubblicato sul backend senza immagine in evidenza: invio al frontend bloccato.'); update_post_meta($post_id, self::META_LAST_DIAG, 'Invio FE bloccato: immagine in evidenza ancora assente dopo il tempo massimo di attesa.'); return true; } public function on_thumbnail_meta_changed($meta_id, $post_id, $meta_key, $meta_value) { if ($meta_key !== '_thumbnail_id') return; $post = get_post($post_id); if (!$post || wp_is_post_revision($post_id)) return; if (!in_array($post->post_type, ['post', 'page', 'product'], true)) return; delete_post_meta($post_id, self::META_WAITING_FEATURED_SINCE); delete_post_meta($post_id, self::META_FEATURED_FAIL); delete_post_meta($post_id, self::META_FEATURED_FAIL_MSG); delete_post_meta($post_id, self::META_LAST_SENT_HASH); delete_post_meta($post_id, self::META_LAST_HASH); $this->queue_post($post_id, true); $this->process_single_post($post_id); } public function on_save_post($post_id, $post, $update) { if (!$post || wp_is_post_revision($post_id)) return; if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return; if ($post->post_type === 'revision') return; if ($this->is_normalizing_backend_content) return; if ($this->is_receiving_frontend_sync) return; if (!$this->is_syncable_post_for_frontend($post)) { $this->cleanup_unsyncable_post_state($post_id, 'auto_draft_or_empty_draft'); return; } $this->maybe_normalize_backend_content($post_id, $post); $post = get_post($post_id); if (!$post) return; $payload = $this->build_post($post_id); if (empty($payload['post'])) return; $hash = $this->build_payload_hash($payload); $queue_status = (string)get_post_meta($post_id, self::META_QUEUE_STATUS, true); $delivered = (bool)get_post_meta($post_id, self::META_DELIVERED, true); $needs_force_retry = in_array($queue_status, ['queued', 'error', 'processing'], true) || !$delivered; if ($needs_force_retry) { delete_post_meta($post_id, self::META_LAST_SENT_HASH); $this->queue_post($post_id, true); return; } if ($this->should_queue_post($post_id, $post, $hash)) { $this->queue_post($post_id); } } protected function process_single_post($post_id) { $post_id = (int)$post_id; if (!$post_id) return false; $post = get_post($post_id); if (!$post) return false; if (!$this->is_syncable_post_for_frontend($post)) { $this->cleanup_unsyncable_post_state($post_id, 'auto_draft_or_empty_draft'); return true; } if ($this->hold_post_waiting_for_featured($post_id, $post)) { return false; } if (!$this->acquire_lock($post_id)) return false; update_post_meta($post_id, self::META_QUEUE_STATUS, 'processing'); $ok = $this->send_full_post($post_id, 'upsert'); if (!$ok) { $status = (string)get_post_meta($post_id, self::META_QUEUE_STATUS, true); if (in_array($status, ['error', 'processing'], true)) { $this->queue_post($post_id, true); } } $this->release_lock($post_id); return $ok; } public function process_queue() { if (get_transient('sv_bridge_be_queue_worker_lock')) return; set_transient('sv_bridge_be_queue_worker_lock', 1, 90); try { $item = $this->dequeue_first(); if (empty($item['post_id'])) return; $post_id = (int)$item['post_id']; $this->add_article_log($post_id, 'BE→FE', 'Processo coda BE→FE avviato'); $this->process_single_post($post_id); } finally { delete_transient('sv_bridge_be_queue_worker_lock'); } // Un articolo per processo PHP. Se resta coda, parte un processo nuovo. $queue = $this->get_queue(); if (!empty($queue) && !wp_next_scheduled(self::CRON_HOOK)) { wp_schedule_single_event(time() + 10, self::CRON_HOOK); } } protected function send_trash_to_frontend($post_id, $context = 'trash') { $post_id = (int) $post_id; $post = get_post($post_id); if (!$post || wp_is_post_revision($post_id)) return false; if (!in_array($post->post_type, ['post', 'page', 'product'], true)) return false; /* * Evita doppio invio nella stessa richiesta: wp_trash_post, transition_post_status * e before_delete_post possono scattare quasi insieme. */ $lock_key = 'sv_bridge_be_trash_sync_' . $post_id; if (get_transient($lock_key)) return false; set_transient($lock_key, 1, 60); return $this->send('post', 'delete', [ 'ID' => $post_id, 'post_type' => $post->post_type, 'post_status' => 'trash', 'delete_mode' => 'trash', 'source_status' => $post->post_status, 'deleted_at' => current_time('mysql'), 'delete_context' => sanitize_key((string) $context), ], [], $post_id); } public function on_transition_from_auto_draft_to_real_status($new_status, $old_status, $post) { if ($old_status !== 'auto-draft') return; if (!$post || empty($post->ID)) return; if (!in_array($new_status, ['draft', 'pending', 'private', 'future', 'publish'], true)) return; if (!$this->is_syncable_post_for_frontend($post)) return; /* * Quando WordPress trasforma l'auto-draft in una bozza reale, azzeriamo * gli hash tecnici creati nella fase provvisoria e sincronizziamo solo * la bozza vera, mantenendo lo stesso origin_post_id BE -> FE. */ delete_post_meta($post->ID, self::META_LAST_HASH); delete_post_meta($post->ID, self::META_LAST_SENT_HASH); delete_post_meta($post->ID, self::META_LAST_ERROR); $this->remove_post_from_queue($post->ID); $this->queue_post($post->ID, true); $this->process_single_post($post->ID); } public function on_trash_post($post_id) { $this->send_trash_to_frontend($post_id, 'wp_trash_post'); } public function on_transition_to_trash($new_status, $old_status, $post) { if ($new_status !== 'trash') return; if (!$post || empty($post->ID)) return; $this->send_trash_to_frontend((int) $post->ID, 'transition_post_status'); } public function on_delete_post($post_id) { /* * Fallback: anche se l'articolo viene eliminato definitivamente dal backend, * sul frontend lo spostiamo comunque nel cestino e non lo cancelliamo hard. */ $this->send_trash_to_frontend($post_id, 'before_delete_post'); } public function add_observability_page() { add_management_page('SV Bridge Log BE', 'SV Bridge Log BE', 'manage_options', 'sv-bridge-log-be', [$this, 'render_observability_page']); } protected function get_global_log_24h() { $rows = get_option(self::GLOBAL_LOG_OPTION, []); if (!is_array($rows)) $rows = []; $cutoff = time() - DAY_IN_SECONDS; $rows = array_values(array_filter($rows, function($row) use ($cutoff) { return !empty($row['ts']) && (int)$row['ts'] >= $cutoff; })); return array_slice($rows, 0, 500); } protected function append_global_log($post_id, $direction, $event, $details = []) { $rows = $this->get_global_log_24h(); array_unshift($rows, [ 'ts' => time(), 'time' => current_time('mysql'), 'post_id' => (int)$post_id, 'direction' => sanitize_text_field((string)$direction), 'event' => sanitize_text_field((string)$event), 'details' => is_array($details) ? $details : [], ]); $rows = array_slice($rows, 0, 500); if (get_option(self::GLOBAL_LOG_OPTION, null) === null) add_option(self::GLOBAL_LOG_OPTION, $rows, '', false); else update_option(self::GLOBAL_LOG_OPTION, $rows, false); } public function cleanup_global_log_24h() { if (get_transient(self::LOG_CLEANUP_TRANSIENT)) return; set_transient(self::LOG_CLEANUP_TRANSIENT, 1, 15 * MINUTE_IN_SECONDS); update_option(self::GLOBAL_LOG_OPTION, $this->get_global_log_24h(), false); } public function handle_clear_global_log() { if (!current_user_can('manage_options')) wp_die('Permessi insufficienti'); check_admin_referer('sv_bridge_clear_be_log'); update_option(self::GLOBAL_LOG_OPTION, [], false); wp_safe_redirect(admin_url('tools.php?page=sv-bridge-log-be&cleared=1')); exit; } public function maybe_kick_queue_on_admin() { if (!is_admin() || wp_doing_ajax()) return; if (get_transient('sv_bridge_be_admin_kick')) return; $queue = $this->get_queue(); if (empty($queue)) return; set_transient('sv_bridge_be_admin_kick', 1, 30); $next = wp_next_scheduled(self::CRON_HOOK); if (!$next || $next > time() + 30) wp_schedule_single_event(time() + 1, self::CRON_HOOK); if ((!defined('DISABLE_WP_CRON') || !DISABLE_WP_CRON) && function_exists('spawn_cron')) spawn_cron(time()); } protected function publication_badge_data($post_id) { $be = sanitize_key((string)get_post_status($post_id)); $fe = sanitize_key((string)get_post_meta($post_id, self::META_REMOTE_STATUS, true)); if ($fe === 'publish') return ['FE PUBBLICATO', '#00a32a']; if ($be === 'publish' && $fe === 'draft') return ['FE BOZZA ⚠', '#d63638']; if ($fe === 'draft') return ['FE BOZZA', '#646970']; if ($fe === 'future') return ['FE PROGRAMMATO', '#dba617']; if ($fe === 'pending') return ['FE PENDING', '#dba617']; if ($fe === 'private') return ['FE PRIVATO', '#7e57c2']; return ['FE STATO ?', '#646970']; } protected function render_remote_publication_badge($post_id) { list($label, $color) = $this->publication_badge_data($post_id); echo '' . esc_html($label) . ''; } public function handle_check_fe_post() { if (!current_user_can('edit_posts')) wp_die('Permessi insufficienti'); $post_id = isset($_GET['post_id']) ? (int)$_GET['post_id'] : 0; check_admin_referer('sv_bridge_check_fe_post_' . $post_id); $post = $post_id ? get_post($post_id) : null; if (!$post) wp_die('Articolo non trovato'); $remote = $this->verify_remote_post_exists($post_id, $post->post_type); if ($remote) { $payload = $this->build_post($post_id); $expected = !empty($payload['post']) ? $this->expected_frontend_hashes_from_data($payload['post']) : []; $this->save_remote_status_from_response($post_id, $remote, 'controllo manuale stato FE', $expected); } $remote_status = sanitize_key((string)get_post_meta($post_id, self::META_REMOTE_STATUS, true)); if ($remote_status !== sanitize_key((string)$post->post_status)) { delete_post_meta($post_id, self::META_LAST_SENT_HASH); $this->queue_post($post_id, true); $this->add_article_log($post_id, 'BE→FE', 'Controllo manuale: stato FE incoerente, reinvio accodato', ['stato_be'=>$post->post_status,'stato_fe'=>$remote_status]); } else { $this->add_article_log($post_id, 'BE→FE', 'Controllo manuale: stato FE coerente', ['stato'=>$remote_status]); } $url = get_edit_post_link($post_id, 'url'); wp_safe_redirect(add_query_arg('sv_bridge_checked', '1', $url ?: admin_url('edit.php'))); exit; } public function render_observability_page() { if (!current_user_can('manage_options')) return; $rows = $this->get_global_log_24h(); $queue = $this->get_queue(); echo '

SV Bridge · Log Backend → Frontend

'; echo '

Registro rolling delle ultime 24 ore, massimo 500 eventi. Nessuna scansione continua del database.

'; $next = wp_next_scheduled(self::CRON_HOOK); echo '

Coda attuale: ' . count($queue) . ' · Prossimo worker: ' . esc_html($next ? date_i18n('Y-m-d H:i:s',$next) : 'non pianificato') . '

'; if (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) echo '

DISABLE_WP_CRON è attivo: serve un cron server esterno oppure la coda può restare queued.

'; $clear = wp_nonce_url(admin_url('admin-post.php?action=sv_bridge_clear_be_log'), 'sv_bridge_clear_be_log'); echo '

Svuota log adesso

'; echo ''; foreach ($rows as $row) { $details=[]; foreach ((array)($row['details']??[]) as $k=>$v) if (is_scalar($v)) $details[]=$k . ': ' . $v; $id=(int)($row['post_id']??0); $link=$id?get_edit_post_link($id):''; echo ''; } if (empty($rows)) echo ''; echo '
OraIDFlussoEventoDettagli
' . esc_html($row['time']??'-') . '' . ($link?'#'.$id.'':'-') . '' . esc_html($row['direction']??'-') . '' . esc_html($row['event']??'-') . '' . esc_html(implode(' · ',$details)) . '
Nessun evento nelle ultime 24 ore.
'; } protected function add_article_log($post_id, $direction, $event, $details = []) { $post_id = (int)$post_id; if (!$post_id) return; $logs = get_post_meta($post_id, self::META_ARTICLE_LOG, true); if (!is_array($logs)) $logs = []; $clean_details = []; foreach ((array)$details as $k => $v) { if (is_scalar($v)) $clean_details[sanitize_key((string)$k)] = sanitize_text_field((string)$v); } array_unshift($logs, [ 'time' => current_time('mysql'), 'site' => 'BE', 'direction' => sanitize_text_field((string)$direction), 'event' => sanitize_text_field((string)$event), 'details' => $clean_details, ]); $cutoff = time() - DAY_IN_SECONDS; $logs = array_values(array_filter($logs, function($row) use ($cutoff) { $ts = !empty($row['time']) ? strtotime((string)$row['time']) : 0; return $ts && $ts >= $cutoff; })); $logs = array_slice($logs, 0, 80); update_post_meta($post_id, self::META_ARTICLE_LOG, $logs); $this->append_global_log($post_id, $direction, $event, $clean_details); } public function render_article_log_metabox($post) { if (!current_user_can('manage_options')) { echo '

Registro visibile solo agli amministratori.

'; return; } $logs = get_post_meta($post->ID, self::META_ARTICLE_LOG, true); if (!is_array($logs) || empty($logs)) { echo '

Nessuna operazione registrata per questo articolo.

'; return; } echo '
'; echo ''; echo ''; foreach ($logs as $row) { $details = []; if (!empty($row['details']) && is_array($row['details'])) { foreach ($row['details'] as $k => $v) $details[] = $k . ': ' . $v; } echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
OraSitoFlussoOperazioneDettagli
' . esc_html($row['time'] ?? '-') . '' . esc_html($row['site'] ?? 'BE') . '' . esc_html($row['direction'] ?? '-') . '' . esc_html($row['event'] ?? '-') . '' . esc_html(implode(' · ', $details)) . '
'; } public function add_metabox() { add_meta_box('sv-bridge-status', 'SV Bridge', [$this, 'render_metabox'], ['post', 'page', 'product'], 'side', 'high'); if (current_user_can('manage_options')) { add_meta_box('sv-bridge-article-log', 'Registro modifiche BE/FE', [$this, 'render_article_log_metabox'], ['post', 'page', 'product'], 'normal', 'default'); } } protected function get_badge_state($post_id) { $post = get_post($post_id); $queue_status = (string)get_post_meta($post_id, self::META_QUEUE_STATUS, true); $delivered = (bool)get_post_meta($post_id, self::META_DELIVERED, true); $img_fail = (bool)get_post_meta($post_id, self::META_FEATURED_FAIL, true); $http_code = (int)get_post_meta($post_id, self::META_LAST_RESPONSE_CODE, true); $last_try = (string)get_post_meta($post_id, self::META_LAST_TRY, true); $remote_status = (string)get_post_meta($post_id, self::META_REMOTE_STATUS, true); $remote_legacy = (string)get_post_meta($post_id, self::META_REMOTE_LEGACY_LINKED_AT, true); $last_update_from_fe = (string)get_post_meta($post_id, self::META_LAST_UPDATE_FROM_FE_AT, true); $verified = get_post_meta($post_id, self::META_REMOTE_CONTENT_VERIFIED, true); $is_attachment = (int)get_post_meta($post_id, self::META_REMOTE_IS_ATTACHMENT, true); if ($queue_status === 'synced_from_frontend' || $last_update_from_fe) return ['MOD FE', '#00a32a']; if ($img_fail || in_array($queue_status, ['image_missing', 'image_failed'], true)) return ['IMG KO', '#b32d2e']; if ($queue_status === 'waiting_image') return ['IMG', '#dba617']; if (in_array($queue_status, ['queued', 'processing'], true)) return ['MOD BE', '#dba617']; if ($is_attachment) return ['ERR', '#d63638']; if ($queue_status === 'status_mismatch') return ['ST KO', '#d63638']; if ($queue_status === 'content_mismatch' || $verified === '0') return ['KO', '#d63638']; if ($delivered && $remote_status === 'publish') return ['P su FE', '#00a32a']; if ($delivered && $remote_legacy) return ['LEG FE', '#7e57c2']; if ($delivered && (int)$verified === 1) return ['FE OK', '#00a32a']; if ($delivered && $http_code >= 200 && $http_code < 300) { if ($post && $post->post_status === 'draft') return ['B su FE', '#7e57c2']; return ['FE OK', '#00a32a']; } if ($last_try && ($http_code >= 400 || $queue_status === 'error')) return ['ERR', '#d63638']; if ($post && $post->post_status === 'draft' && $delivered) return ['BOZZA', '#646970']; return ['NO FE', '#646970']; } public function output_admin_list_css() { $screen = function_exists('get_current_screen') ? get_current_screen() : null; if (!$screen) return; $allowed = ['edit-post', 'edit-page', 'edit-product', 'post', 'page', 'product']; if (!in_array($screen->id, $allowed, true)) return; echo ''; } public function add_admin_column($columns) { $columns['sv_bridge_delivery'] = 'Frontend'; return $columns; } protected function get_backend_queue_item_for_post($post_id) { $post_id = (int)$post_id; foreach ($this->get_queue() as $item) { if (!empty($item['post_id']) && (int)$item['post_id'] === $post_id) return $item; } return []; } protected function get_backend_pending_change_rows($post_id) { $post_id = (int)$post_id; $post = get_post($post_id); $rows = []; if (!$post) return $rows; $queue_status = (string)get_post_meta($post_id, self::META_QUEUE_STATUS, true); $remote_status = (string)get_post_meta($post_id, self::META_REMOTE_STATUS, true); $remote_id = (string)get_post_meta($post_id, self::META_REMOTE_POST_ID, true); $remote_title_hash = (string)get_post_meta($post_id, self::META_REMOTE_TITLE_HASH, true); $remote_content_hash = (string)get_post_meta($post_id, self::META_REMOTE_CONTENT_HASH, true); $remote_excerpt_hash = (string)get_post_meta($post_id, self::META_REMOTE_EXCERPT_HASH, true); $last_error = (string)get_post_meta($post_id, self::META_LAST_ERROR, true); $diag = (string)get_post_meta($post_id, self::META_LAST_DIAG, true); $last_update_from_fe = (string)get_post_meta($post_id, self::META_LAST_UPDATE_FROM_FE_AT, true); $last_fe_post_id = (string)get_post_meta($post_id, self::META_LAST_FE_POST_ID, true); /* Caso MOD FE: il backend deve mostrare da edit.php cosa è stato ricevuto dal FE. */ if ($queue_status === 'synced_from_frontend' || $last_update_from_fe) { $rows[] = 'Direzione: frontend → backend'; $rows[] = 'Ultima modifica ricevuta dal FE: ' . ($last_update_from_fe ?: 'non disponibile'); if ($last_fe_post_id) $rows[] = 'Articolo FE sorgente: #' . $last_fe_post_id; $changed_fields = get_post_meta($post_id, self::META_LAST_FE_CHANGED_FIELDS, true); $changed_meta = get_post_meta($post_id, self::META_LAST_FE_SYNCED_META_KEYS, true); $changed_tax = get_post_meta($post_id, self::META_LAST_FE_SYNCED_TAXONOMIES, true); if (is_array($changed_fields) && !empty($changed_fields)) { $rows[] = 'Campi aggiornati sul BE: ' . implode(', ', array_slice($changed_fields, 0, 8)); } if (is_array($changed_tax) && !empty($changed_tax)) { $rows[] = 'Tassonomie aggiornate: ' . implode(', ', array_slice($changed_tax, 0, 8)); } if (is_array($changed_meta) && !empty($changed_meta)) { $rows[] = 'Metakey aggiornate: ' . implode(', ', array_slice($changed_meta, 0, 10)); } if (count($rows) <= 3) { $rows[] = 'Modifica FE applicata al BE. Nessuna azione necessaria, salvo nuova coda BE → FE.'; } if ($diag) $rows[] = wp_trim_words($diag, 18, '...'); return array_values(array_unique(array_filter($rows))); } $rows[] = 'Direzione: backend → frontend'; $rows[] = 'Stato coda: ' . ($queue_status ?: 'non impostato'); if ($remote_id) $rows[] = 'Articolo FE collegato: #' . $remote_id; if ($remote_status && $remote_status !== $post->post_status) $rows[] = 'Stato da aggiornare: ' . $remote_status . ' → ' . $post->post_status; if ($remote_title_hash && md5((string)$post->post_title) !== $remote_title_hash) $rows[] = 'Titolo da aggiornare sul FE'; if ($remote_content_hash && md5((string)$post->post_content) !== $remote_content_hash) $rows[] = 'Contenuto da aggiornare sul FE'; if ($remote_excerpt_hash && md5((string)$post->post_excerpt) !== $remote_excerpt_hash) $rows[] = 'Excerpt da aggiornare sul FE'; if (in_array($queue_status, ['waiting_image', 'image_missing', 'image_failed'], true)) { $rows[] = 'Immagine in evidenza/media da sincronizzare o correggere'; } $item = $this->get_backend_queue_item_for_post($post_id); if (!empty($item['queued_at'])) $rows[] = 'In coda dal: ' . date_i18n('Y-m-d H:i:s', (int)$item['queued_at']); if ($last_error) $rows[] = 'Ultimo errore: ' . wp_trim_words($last_error, 18, '...'); if ($diag && count($rows) <= 3) $rows[] = wp_trim_words($diag, 18, '...'); if (count($rows) <= 3 && in_array($queue_status, ['queued', 'processing'], true)) { $rows[] = 'Payload modificato: possibili modifiche a metakey, tassonomie, contenuto normalizzato o featured image.'; } return array_values(array_unique(array_filter($rows))); } protected function render_bridge_badge_popup($label, $color, $badge_class = '', $rows = [], $post_id = 0) { $class = 'sv-bridge-badge ' . sanitize_html_class((string)$badge_class); $style = 'background:' . esc_attr($color) . ';'; if (empty($rows)) { echo '' . esc_html($label) . ''; return; } echo '
'; echo '' . esc_html($label) . ''; echo '
'; echo '
Modifiche in coda
'; echo ''; if ($post_id && current_user_can('edit_post', $post_id)) { $queue_status = (string)get_post_meta((int)$post_id, self::META_QUEUE_STATUS, true); if (in_array($queue_status, ['queued', 'processing', 'waiting_image', 'content_mismatch', 'image_failed', 'image_missing', 'error'], true)) { $url = wp_nonce_url(admin_url('admin-post.php?action=sv_bridge_resend_post&post_id=' . (int)$post_id), 'sv_bridge_resend_post_' . (int)$post_id); echo '

Forza coda BE → FE

'; } } echo '
'; echo '
'; } public function render_admin_column($column, $post_id) { if ($column !== 'sv_bridge_delivery') return; list($label, $color) = $this->get_badge_state($post_id); $queue_status = (string) get_post_meta($post_id, self::META_QUEUE_STATUS, true); $delivered_at = (string) get_post_meta($post_id, self::META_DELIVERED_AT, true); $remote_id = (string) get_post_meta($post_id, self::META_REMOTE_POST_ID, true); $remote_sync = (string) get_post_meta($post_id, self::META_REMOTE_LAST_SYNC, true); $verified = get_post_meta($post_id, self::META_REMOTE_CONTENT_VERIFIED, true); $remote_legacy = (string) get_post_meta($post_id, self::META_REMOTE_LEGACY_LINKED_AT, true); $remote_type = (string) get_post_meta($post_id, self::META_REMOTE_POST_TYPE, true); $is_attachment = (int) get_post_meta($post_id, self::META_REMOTE_IS_ATTACHMENT, true); $diag = (string) get_post_meta($post_id, self::META_LAST_DIAG, true); $badge_class = 'neutral'; if ($is_attachment || $queue_status === 'content_mismatch' || $verified === '0' || in_array($queue_status, ['error', 'image_failed', 'image_missing'], true)) { $badge_class = 'err'; } elseif (in_array($queue_status, ['queued', 'processing', 'waiting_image'], true)) { $badge_class = 'warn'; } elseif ($remote_legacy) { $badge_class = 'legacy'; } elseif ($queue_status === 'sent' || $verified === '1') { $badge_class = 'ok'; } elseif ($queue_status) { $badge_class = 'info'; } echo '
'; $popup_rows = []; $last_update_from_fe = (string) get_post_meta($post_id, self::META_LAST_UPDATE_FROM_FE_AT, true); if (in_array($queue_status, ['queued', 'processing', 'waiting_image', 'content_mismatch', 'image_failed', 'image_missing', 'error', 'synced_from_frontend'], true) || $last_update_from_fe) { $popup_rows = $this->get_backend_pending_change_rows($post_id); } $this->render_bridge_badge_popup($label, $color, $badge_class, $popup_rows, $post_id); $this->render_remote_publication_badge($post_id); echo '
'; if ($remote_id) { echo '
FE #' . esc_html($remote_id) . '
'; } if ($remote_type) { echo '
Tipo: ' . esc_html($remote_type); if ($is_attachment) echo ' ATTACHMENT'; echo '
'; } if ($remote_sync) { echo '
Sync: ' . esc_html($remote_sync) . '
'; } elseif ($delivered_at) { echo '
Consegnato: ' . esc_html($delivered_at) . '
'; } if ($verified !== '') { echo '
Content: '; echo ((int) $verified === 1) ? 'OK' : 'NO'; echo '
'; } if ($remote_legacy) { echo '
Legacy: OK
'; } if (!$remote_id && $diag) { echo '
' . esc_html(wp_trim_words($diag, 12, '...')) . '
'; } echo '
'; echo '
'; } public function render_metabox($post) { $last_try = get_post_meta($post->ID, self::META_LAST_TRY, true); $http_code = get_post_meta($post->ID, self::META_LAST_RESPONSE_CODE, true); $error = get_post_meta($post->ID, self::META_LAST_ERROR, true); $response = get_post_meta($post->ID, self::META_LAST_RESPONSE_BODY, true); $diag = get_post_meta($post->ID, self::META_LAST_DIAG, true); $img_fail = get_post_meta($post->ID, self::META_FEATURED_FAIL, true); $img_msg = get_post_meta($post->ID, self::META_FEATURED_FAIL_MSG, true); $queue_status = get_post_meta($post->ID, self::META_QUEUE_STATUS, true); $delivered_at = get_post_meta($post->ID, self::META_DELIVERED_AT, true); $remote_id = get_post_meta($post->ID, self::META_REMOTE_POST_ID, true); $remote_status = get_post_meta($post->ID, self::META_REMOTE_STATUS, true); $remote_title = get_post_meta($post->ID, self::META_REMOTE_TITLE, true); $remote_last_sync = get_post_meta($post->ID, self::META_REMOTE_LAST_SYNC, true); $remote_featured_ok = get_post_meta($post->ID, self::META_REMOTE_FEATURED_OK, true); $remote_legacy = get_post_meta($post->ID, self::META_REMOTE_LEGACY_LINKED_AT, true); $remote_verified = get_post_meta($post->ID, self::META_REMOTE_LAST_VERIFIED_AT, true); $remote_content_verified = get_post_meta($post->ID, self::META_REMOTE_CONTENT_VERIFIED, true); $remote_verification_msg = get_post_meta($post->ID, self::META_REMOTE_VERIFICATION_MSG, true); $remote_content_hash = get_post_meta($post->ID, self::META_REMOTE_CONTENT_HASH, true); $remote_expected_hash = get_post_meta($post->ID, self::META_REMOTE_EXPECTED_CONTENT_HASH, true); $remote_post_type = get_post_meta($post->ID, self::META_REMOTE_POST_TYPE, true); $remote_is_attachment = get_post_meta($post->ID, self::META_REMOTE_IS_ATTACHMENT, true); $last_update_from_fe = get_post_meta($post->ID, self::META_LAST_UPDATE_FROM_FE_AT, true); $last_fe_sync_status = get_post_meta($post->ID, self::META_LAST_FE_SYNC_STATUS, true); $last_fe_post_id = get_post_meta($post->ID, self::META_LAST_FE_POST_ID, true); $endpoint = $this->endpoint(); $secret_present = $this->secret() ? 'Sì' : 'No'; list($badge, $color) = $this->get_badge_state($post->ID); echo '

Stato: ' . esc_html($badge) . '

'; echo '

Pubblicazione reale FE:
'; $this->render_remote_publication_badge($post->ID); echo '

'; echo '

Queue: ' . esc_html($queue_status ?: '-') . '

'; if ($last_update_from_fe) { echo '

Modifica ricevuta dal FE:
' . esc_html($last_update_from_fe) . '
Stato FE→BE: ' . esc_html($last_fe_sync_status ?: '-') . ' · ID FE: ' . esc_html($last_fe_post_id ?: '-') . '

'; } echo '

Consegnato al frontend:
' . esc_html($delivered_at ?: '-') . '

'; echo '
'; echo '

Conferma reale FE:
'; echo 'ID FE: ' . esc_html($remote_id ?: '-') . '
'; echo 'Tipo FE: ' . esc_html($remote_post_type ?: '-') . '' . ((int)$remote_is_attachment ? ' ATTACHMENT' : '') . '
'; echo 'Stato FE: ' . esc_html($remote_status ?: '-') . '
'; echo 'Ultimo sync FE: ' . esc_html($remote_last_sync ?: '-') . '
'; echo 'Verificato BE: ' . esc_html($remote_verified ?: '-') . '
'; echo 'Featured FE: ' . esc_html($remote_featured_ok !== '' ? ((int)$remote_featured_ok ? 'OK' : 'NO') : '-') . '
'; echo 'Verifica modifica content: ' . esc_html($remote_content_verified !== '' ? ((int)$remote_content_verified ? 'OK' : 'NO') : '-') . ''; echo '

'; echo '

Prova reale modifica:
' . esc_html($remote_verification_msg ?: '-') . '

'; echo '

Hash content BE/FE:
BE: ' . esc_html($remote_expected_hash ?: '-') . '
FE: ' . esc_html($remote_content_hash ?: '-') . '

'; if ($remote_legacy) { echo '

Articolo vecchio agganciato al FE
' . esc_html($remote_legacy) . '

'; } if ($remote_title) { echo '

Titolo FE ricevuto:
' . esc_html($remote_title) . '

'; } echo '

Endpoint FE:
' . esc_html($endpoint ?: 'NON DEFINITO') . '

'; echo '

Secret presente: ' . esc_html($secret_present) . '

'; echo '

Ultimo tentativo:
' . esc_html($last_try ?: 'Mai') . '

'; echo '

HTTP code:
' . esc_html($http_code ?: '-') . '

'; echo '

Errore tecnico:
' . esc_html($error ?: 'Nessuno') . '

'; echo '

Diagnosi:
' . nl2br(esc_html($diag ?: 'Nessuna diagnosi disponibile')) . '

'; echo '

Ultima risposta FE:
' . esc_html($response ?: '-') . '

'; if ($img_fail) { echo '

Problema immagine in evidenza:
' . esc_html($img_msg ?: 'Immagine non sincronizzata') . '

'; } $url = wp_nonce_url(admin_url('admin-post.php?action=sv_bridge_resend_post&post_id=' . (int)$post->ID), 'sv_bridge_resend_post_' . (int)$post->ID); echo '

Metti in coda / reinvia ora

'; $check_url = wp_nonce_url(admin_url('admin-post.php?action=sv_bridge_check_fe_post&post_id=' . (int)$post->ID), 'sv_bridge_check_fe_post_' . (int)$post->ID); echo '

Controlla stato FE / ripara coda

'; echo '

Il sistema evita invii duplicati quando il payload non è cambiato.

'; } public function handle_manual_resend() { if (!current_user_can('edit_posts')) wp_die('Permessi insufficienti'); $post_id = isset($_GET['post_id']) ? (int)$_GET['post_id'] : 0; check_admin_referer('sv_bridge_resend_post_' . $post_id); if (!$post_id || !get_post($post_id)) { wp_safe_redirect(admin_url('edit.php')); exit; } $post = get_post($post_id); if ($post && $this->maybe_reconcile_with_frontend($post_id, $post->post_type)) { $redirect = get_edit_post_link($post_id, 'url'); if (!$redirect) $redirect = admin_url('post.php?post=' . $post_id . '&action=edit'); $redirect = add_query_arg('sv_bridge_resend', 'verified', $redirect); wp_safe_redirect($redirect); exit; } delete_post_meta($post_id, self::META_LAST_SENT_HASH); $this->queue_post($post_id, true); $this->add_article_log($post_id, 'BE→FE', 'Forzatura manuale coda dal popup/metabox', ['utente' => wp_get_current_user()->user_login]); $redirect = get_edit_post_link($post_id, 'url'); if (!$redirect) $redirect = admin_url('post.php?post=' . $post_id . '&action=edit'); $redirect = add_query_arg('sv_bridge_resend', 'queued', $redirect); wp_safe_redirect($redirect); exit; } } if (!wp_next_scheduled('sv_bridge_be_process_queue') && function_exists('wp_get_schedules') && !isset(wp_get_schedules()['minute'])) { add_filter('cron_schedules', function($schedules) { $schedules['minute'] = ['interval' => 60, 'display' => 'Every Minute']; return $schedules; }); } new SV_Bridge_BE_Sender_Optimized(); Calcio mercato https://www.cronachedellacampania.it/tag/calcio-mercato/ Le ultime notizie LIVE dalla Campania Fri, 05 Jun 2026 08:07:26 +0000 it-IT hourly 1 https://www.cronachedellacampania.it/wp-content/uploads/2026/04/cropped-cronache_logo_rotondo_2026-dimensioni-medie-32x32.png Calcio mercato https://www.cronachedellacampania.it/tag/calcio-mercato/ 32 32 112696088 Allegri pronto a fare causa al Milano, Napoli punta su Rabiot e prepara la rivoluzione https://www.cronachedellacampania.it/2026/06/allegri-pronto-a-fare-causa-al-milano-napoli-punta-rabiot/ https://www.cronachedellacampania.it/2026/06/allegri-pronto-a-fare-causa-al-milano-napoli-punta-rabiot/#comments Mon, 01 Jun 2026 11:11:07 +0000 https://www.cronachedellacampania.it/?p=857102 Il divorzio tra Max Allegri e Milan si complica per questioni contrattuali e comunicative, mentre il Napoli accelera per il nuovo allenatore e cerca rinforzi come Rabiot.]]>

La separazione tra Max Allegri e il Milan si sta trasformando in una vera e propria battaglia legale. Dopo l’annuncio improvviso dell’esonero, comunicato ufficialmente dal club prima che l’allenatore ricevesse una notifica formale, si sono aperte tensioni sul fronte economico e sul modo in cui è stata gestita la comunicazione.

Allegri e il suo staff discutono ancora la buonuscita, e i suoi legali stanno valutando azioni legali per danno d’immagine. Il Milan, dal canto suo, sostiene che l’allenatore avesse già un accordo con il Napoli di Aurelio De Laurentiis prima della fine della stagione.

Questa disputa ha rallentato solo l’ufficialità dell’arrivo di Allegri al Napoli, dove il club partenopeo è già operativo per rafforzare la squadra. Il presidente De Laurentiis e il ds Giovanni Manna lavorano per costruire una rosa competitiva, con particolare attenzione al centrocampista Adrien Rabiot, ritenuto un elemento chiave per il nuovo progetto tecnico di Allegri. Nel mirino anche il portiere Matej Kovar e i difensori Mario Gila e Dodô.

Parallelamente, il Napoli deve affrontare la gestione dei rientri dai prestiti, decidendo quali giocatori confermare e quali cedere per finanziare le operazioni di mercato. Questo processo sarà fondamentale per garantire risorse e flessibilità al club, che punta a un rilancio ambizioso sotto la guida del nuovo allenatore.

]]>
https://www.cronachedellacampania.it/2026/06/allegri-pronto-a-fare-causa-al-milano-napoli-punta-rabiot/feed/ 1 857102
Calcio Napoli, fantamercato dalla Spagna: azzurri vicini a Cristiano Ronaldo https://www.cronachedellacampania.it/2022/07/calcio-napoli-cristiano-ronaldo/ https://www.cronachedellacampania.it/2022/07/calcio-napoli-cristiano-ronaldo/#respond Wed, 27 Jul 2022 18:55:27 +0000 https://www.cronachedellacampania.it/?p=510909 Cristiano Ronaldo potrebbe arrivare a Napoli. E’ la la solita notizia di fantamercato o c’è qualcosa di vero? I tifosi non sembrano credere tanto a una ipotesi del genere ma la stampa spagnola a proposito di CR7 spiega che il portoghese è pronto a lasciare il Manchester United, che pero’ vorrebbe...]]>

Cristiano Ronaldo potrebbe arrivare a Napoli. E’ la la solita notizia di fantamercato o c’è qualcosa di vero?

I tifosi non sembrano credere tanto a una ipotesi del genere ma la stampa spagnola a proposito di CR7 spiega che il portoghese è pronto a lasciare il Manchester United, che pero’ vorrebbe costringerlo a rispettare il contratto che lo lega ai Red Devils ancora per un anno.

Visto che CR7 sarebbe disposto, pur di giocare in un club che fa la Champions, ad abbassarsi l’ingaggio del 25%, e cosi’ il Napoli e’ sceso in campo, secondo ‘As’, con l’intenzione di portarlo in azzurro e costruire una nuova icona per il club dopo Maradona.

Il club ha avanzato la sua offerta contando anche sul rapporto di amicizia che c’e’ tra il presidente De Laurentiis e il manager di Ronaldo Jorge Mendes e potrebbe provare a fare una forte offerta per convincere la stella portoghese, per i diritti d’immagine del quale ci sarebbe comunque da discutere molto con De Laurentiis.

]]>
https://www.cronachedellacampania.it/2022/07/calcio-napoli-cristiano-ronaldo/feed/ 0 510909
CALCIOMERCATO. Lazio, arriva Sarri: accordo vicino. Juve, CR7 blocca il mercato https://www.cronachedellacampania.it/2021/06/calciomercato-lazio-sarri/ Fri, 04 Jun 2021 07:56:02 +0000 In casa di partenza ddi Ronaldo i bianconeri orientati su Icardi e Vlahovic. Intanto l'Atletico Madrid si è fatto avanti per Lautaro Martinez]]>

Questione di giorni, forse di ore, ma ormai ci siamo: Maurizio Sarri è ad un passo dalla panchina della Lazio.

L’ex tecnico di Chelsea e Juve ieri ha incontrato in un lungo vertice il patron Lotito e il ds Tare, come riporta il Corriere dello Sport. Sul piatto un biennale: 3 milioni di euro di stipendio netti più bonus in caso di qualificazione alla prossima Champions League.

TI POTREBBE INTERESSARE ANCHE: Vaccini, boom di prenotazioni per gli over 12 in Campania

Ore frenetiche anche in casa Juve. In attesa delle prime parole di Allegri si studiano i primi passi per il mercato: il nodo principale da sciogliere sarà quello del futuro di Cristiano Ronaldo.

Cristiano Ronaldo

Il portoghese è ad un bivio: ha ancora un anno di contratto, ma vorrebbe cambiare aria. Secondo il Corriere dello Sport toccherà a lui decidere: la società bianconera non si opporrà in alcun modo al suo eventuale addio. Proprio per questo motivo si studiano i successori: per Tuttosport in pole ci sono Icardi e Vlahovic.

L’Inter ha accolto Simone Inzaghi, ufficializzato nella giornata di ieri. Il mercato dei campioni d’Italia non sarà per niente semplice: attesa una cessione importante per motivi di bilancio. Il PSG punta Hakimi ma, come riporta oggi Tuttosport, l’Atletico Madrid si è fatto avanti per un altro big: Lautaro Martinez. In attesa di sviluppi, i nerazzurri studiano anche il mercato in entrata: nel mirino Emerson Palmieri, valutato 20 milioni dal Chelsea.

 Vincenzo Scarpa

]]>
429839
CalcioMercato: il Real su Dybala, Matuidi verso Miami https://www.cronachedellacampania.it/2020/08/calciomercato-il-real-su-dybala-matuidi-verso-miami/ Mon, 10 Aug 2020 18:44:02 +0000 Mercato: il Real su Dybala, Matuidi verso Miami Matuidi all’Inter Miami. Gattuso-Napoli c’e’ l’intesa 

In Spagna sono sicuri, il Real Madrid ha pronta una maxi-offerta per Paulo Dybala. Sul tavolo della Juve una valutazione complessiva di oltre 100 milioni di euro tra cash e una contropartita tecnica. Che la Juve la possa prendere in considerazione, poi, e’ un’altra questione. Due, secondo Marca che riprende rumors italiani, i giocatori che i merengues sarebbero disposti a sacrificare per raggiungere la Joya argentina: Isco o Toni Kroos, entrambi graditi al neo allenatore bianconero Andrea Pirlo. In effetti la Juventus non avrebbe intenzione di privarsi di Dybala, a maggior ragione se Cristiano Ronaldo decidesse di lasciare Torino. Il tormentone del calciomercato e’ dunque servito. Intanto il club bianconero dopo la delusione per l’eliminazione dalla Champions League ha deciso di voltare subito pagina e di iniziare immediatamente a programmare il futuro. Ai saluti il francese Blaise Matuidi, che giochera’ in America con l’Inter Miami. Intesa trovata con il club di David Beckham e, secondo i media francesi, visite mediche gia’ oggi a Parigi. L’obiettivo della Juve e’ quindi quello di ringiovanire la rosa, per cui l’addio di Matuidi non sara’ l’unico in casa bianconera. In partenza, infatti, ci sono anche Sami Khedira e Gonzalo Higuain. In casa Lazio prosegue la trattativa per David Silva. Se nei giorni scorsi l’affare aveva subito un’accelerazione – con tanto di conferme dal papa’ del giocatore -, nelle ultime ore i segnali sono sempre piu’ positivi. I contatti proseguono: si lavora su un triennale, ultimi dettagli da definire con il centrocampista spagnolo, 34 anni, in scadenza di contratto col Manchester City. Ieri ha giocato gli ultimi 9′ della sfida contro il Real Madrid, provera’ a salutare i Citizens con la Champions League.

Il Napoli e Gattuso insieme per tre anni. Il tecnico ha incontrato il presidente dei partenopei Aurelio De Laurentiis a Capri, un paio d’ore di colloqui e l’intesa per il futuro. Raggiunta l’intesa si e’ parlato di mercato. Tra i nomi sul taccuino dei dirigenti partenopei quello dell’esterno del Real Madrid, Reguilo’n (che piace anche al Chelsea), Boga, Under e Veretout. Dal punto di vista delle cessioni, Koulibaly e’ sempre in cima alla lista dei desideri del Manchester United che pero’ non e’ intenzionato ad assecondare la richiesta di De Laurentiis che valuta il difensore senegalese 100 milioni di euro. Vuole la Juve Arkadiusz Milik: il Napoli lo valuta 40 milioni. Salutata la Roma, potrebbe tornare in Italia, ma stavolta destinazione Inter, Chris Smalling. Il difensore inglese, infatti, non rientra nei piani dello United. (ANSA). MS 10-AGO-20 20:01 NNNN

]]>
367577
Calcio mercato: il Real Madrid piomba su Piatek https://www.cronachedellacampania.it/2019/01/calcio-mercato-il-real-madrid-piomba-su-piatek/ Mon, 14 Jan 2019 19:27:19 +0000 https://www.cronachedellacampania.it/?p=240354 alla Spagna sono sicuri: nel futuro di Krzysztof Piatek può esserci il Real Madrid. Secondo le indiscrezioni di ‘Cadena Ser’, i Blancos a caccia di un attaccante visti i recenti infortuni in rosa, hanno avviato i contatti con il Genoa: l’obiettivo è portare al Bernabeu il 23enne polacco, grande rivelazione...]]>

alla Spagna sono sicuri: nel futuro di Krzysztof Piatek può esserci il Real Madrid. Secondo le indiscrezioni di ‘Cadena Ser’, i Blancos a caccia di un attaccante visti i recenti infortuni in rosa, hanno avviato i contatti con il Genoa: l’obiettivo è portare al Bernabeu il 23enne polacco, grande rivelazione stagionale, già a gennaio. Il numero uno dei madrileni Perez non baderebbe a spese: sul piatto è disposto a mettere ben 60 milioni di euro. Negli ultimi giorni Piatek è stato anche accostato al Milan, sempre in attesa di conoscere il destino di Gonzalo Higuain: a Gedda, dove si trovano per la Supercoppa, i dirigenti rossoneri discuteranno con quelli della Juventus sul possibile trasferimento del ‘Pipita’ al Chelsea. Futuro incerto anche per l’altro gioiellino del Grifone, Christian Kouame. Il giovane attaccante ivoriano è da tempo nella lista dei desideri del Napoli, che continua a trattare con Preziosi: i partenopei offrono 15 milioni più bonus, il patron rossoblù ne vuole 25. Il ds azzurro Giuntoli potrebbe giocare la carta Rog, inserendolo come contropartita. La trattativa prosegue. L’altro grande obiettivo del Napoli è Hector Lozano del Psv: affare possibile solo in estate dal momento che gli olandesi non intendono lasciare partire il loro gioiello a gennaio. Ma gli azzurri, stando alla stampa spagnola, hanno due concorrenti temibili: Barcellona e Chelsea.La Juventus attende sempre Ramsey, tentando l’accelerata per avere il gallese già a gennaio e guarda con attenzione alle vicende di casa Barcellona, dove le trattative tra blaugrana e Jordi Alba, che piace molto al club bianconero, per il rinnovo si sono arenate. Juventus che intanto può sorridere per le dichiarazioni di Paulo Dybala: “Difficile sapere cosa riserva il futuro. In ogni caso, io sto molto bene alla Juventus. Sono dove ho sempre voluto essere”, ha assicurato ai media francesi la Joya, seguito con attenzione dalle big d’Europa. In casa Inter tiene sempre banco la questione del rinnovo di Mauro Icardi. Ci pensa l’ad Beppe Marotta a rassicurare i tifosi nerazzurri: “Non esiste alcun caso. Non credo che ci saranno problemi, entrambe le parti hanno intenzione di proseguire il rapporto”. Al lavoro anche la Lazio. Nel taccuino del ds Igli Tare è sempre inserito il nome dell’esterno del Chelsea ed ex Torino Davide Zappacosta. E un’altra vecchia conoscenza della Serie A è pronto al ritorno: Kucka è ormai da considerare un giocatore del Parma. Il centrocampista, ex Genoa e Milan, proveniente dal Trabzonspor, firmerà con gli emiliani un contratto fino al giugno 2022. A proposito dei rossoblù: nella lista dei loro obiettivi continua ad esserci il fantasista dell’Empoli Rade Krunic. E’ ufficiale invece il ritorno di Rigoni allo Zenit San Pietroburgo. Si chiude così dopo soli sei mesi l’avventura all’Atalanta. Potrebbe lasciare presto l’Italia Simone Scuffet: il portiere, chiuso da Musso ad Udine, è pronto a trasferirsi in prestito ai turchi del Kasimpasa fino alla fine della stagione. Anche un giocatore della Fiorentina si avvicina alla Turchia: Marko Pjaca. L’attaccante croato, di proprietà alla Juve, non sta trovando spazio nelle file dei viola: i bianconeri potrebbero riabbracciarlo e girarlo in prestito al Besiktas, che lo segue con grande interesse. Pjaca è seguito anche dal Parma e in Inghilterra dal Fulham. La Fiorentina invece continua a mantenere nel mirino Pedro Obiang del West Ham, corteggiato anche dal Lille. Uno sguardo all’estero: Mario Balotelli resta la priorità assoluta per l’Olympique Marsiglia, ma non ha ancora trovato un accordo con l’attaccante. Le discussioni con il giocatore in uscita dal Nizza, riferisce la stampa francese, sono attualmente in corso. Il direttore sportivo dell’OM Zubizarreta sta seguendo diverse punte, ma l’ex calciatore di Inter e Milan rimane la prima scelta. Leonardo Balerdi è un nuovo giocatore del Borussia Dortmund: il difensore 19enne, proveniente dal Boca Juniors e già considerato come uno dei giocatori più promettenti del Sud America, è stato pagato 12 milioni di euro più tre di bonus. L’argentino, seguito anche dal Barcellona, ha firmato un contratto fino al 2024.

]]>
240354
Speciale Mercato: tutte le trattative https://www.cronachedellacampania.it/2018/05/speciale-mercato-tutte-le-trattative/ Fri, 25 May 2018 18:24:03 +0000 https://www.cronachedellacampania.it/?p=190128 Il Napoli fa sognare. Dopo l’ingaggio di Carlo Ancelotti da parte del presidente Aurelio De Laurentiis, ora la dirigenza e’ al lavoro per allestire una squadra che possa davvero reggere la concorrenza della Juventus, e quindi primeggiare. Capitan HAMSIK, saputo il nome del nuovo tecnico, avrebbe deciso di rimanere declinando...]]>

Il Napoli fa sognare. Dopo l’ingaggio di Carlo Ancelotti da parte del presidente Aurelio De Laurentiis, ora la dirigenza e’ al lavoro per allestire una squadra che possa davvero reggere la concorrenza della Juventus, e quindi primeggiare. Capitan HAMSIK, saputo il nome del nuovo tecnico, avrebbe deciso di rimanere declinando le ricche offerte ricevute dalla Cina, mentre per rinforzare il centrocampo, che perdera’ JORGINHO, circolano i nomi dell’ex romanista PAREDES, che sarebbe stato espressamente richiesto da Ancelotti, e dell’ex bianconero VIDAL. Stellare il nome dell’attacco: si parla di BENZEMA, che potrebbe lasciare il Real Madrid. Girano anche i nomi di KEITA BALDE, CHIESA, FELIPE ANDERSON e SIRIGU, oltre ai soliti VERDI e POLITANO, mentre per SUSO il Milan pretenderebbe il pagamento dell’intera clusola di rescissione, ovvero 38 milioni, cifra non ritenuta congrua. Napoli fa pensare subito a Maurizio SARRI, i cui rappresentanti starebbero stringendo i tempi della trattativa con il Chelsea, deciso a liquidare Antonio Conte. Gli sviluppi di questa situazione potrebbero anche portare DAVID LUIZ alla corte di Ancelotti. Quanto a Sarri, vorrebbe con se’ ai Blues Gianfranco ZOLA come team manager. Come rinforzi per la squadra il nuovo tecnico avrebbe fatto ad Abramovich due soli nomi, quelli di KOULIBALY e HIGUAIN: quest’ultimo tornerebbe volentieri con il suo ex allenatore, e il Chelsea sarebbe disposto a pagare alla Juve una cifra adeguata per il Pipita. A quel punto verrebbe bloccata la cessione di MANDZUKIC, mentre e’ sempre in piedi il discorso per il ritorno in bianconero di MORATA. C’e’ anche il solito giro di portieri, perche’ da Liverpool ci sono conferme sull’interesse dei Reds per Gigio DONNARUMMA, anche se l’offerta di 40 milioni di euro e’ stata ritenuta insufficiente al Milan. Il Real Madrid punta sempre sul romanista ALISSON, al quale avrebbe offerto un quadriennale da 5 milioni a stagione. Settanta milioni sarebbe invece stata la proposta alla Roma, respinta dal d.s. Monchi che ne vuole cento. Questa situazione e’ seguita da Gigi BUFFON, il quale nonostante le offerte ricevute da Parigi sognerebbe di andare proprio al Real. E’ quello il grande sogno ma, attenzione, un sogno realizzabile. Il suo sponsor in seno alla societa’ pluricampione d’Europa e del mondo e’ l’amico Sergio Ramos, ma anche il tecnico Zinedine Zidane prenderebbe volentieri il suo ex compagno. Bisognera’ pero’ vedere come va la finale di Champions, perche’ in caso di Ko madridista Zizou potrebbe essere avvicendato. Mario BALOTELLI avrebbe trovato la sua nuova squadra: l’Olympique Marsiglia. Secondo quanto scrivono in Francia, sarebbe quasi tutto fatto tra i francesi e l’ex interista e ora il tecnico dell’OM Rudi Garcia avrebbe chiesto ai suoi dirigenti di fare un altro sforzo per prendere anche BENATIA dalla Juventus. Lavora in entrata anche la Lazio, che sogna il colpo CRISTANTE, e intanto lavora su LAPADULA, PAQUETA (centrocampista del Flamengo) e quell’EKAMBI, attaccante dell’Angers, che al d.s. Tare e’ stato consigliato da Samuel Eto’o. In piu’ il dirigente tiene sempre aperto il discorso per la stella iraniana AZMOUN. In casa giallorossa prende invece consistenza l’affare BERARDI, che vuole tornare a lavorare con Di Francesco. Si continua a trattare anche per KLUIVERT jr., mentre l’Inter ha fatto un sondaggio per NAINGGOLAN. Colpo anche sul mercato delle panchine: l’Udinese, che sta per prendere la ‘stella’ della serie D LORUSSO dal Trastevere, ripartira’ da Cesare PRANDELLI, mentre Pippo INZAGHI e’ sempre piu’ vicino al Bologna. Per la panchina del Sassuolo e’ sprint a due fra DE ZERBI e NICOLA.

]]>
190128
Calcio mercato, il Benevento chiede Sepe ma per il Napoli è incedibile https://www.cronachedellacampania.it/2018/01/calcio-mercato-il-benevento-chiede-sepe-ma-per-il-napoli-e-incedibile/ Thu, 11 Jan 2018 14:49:05 +0000 https://www.cronachedellacampania.it/?p=161067 Calcio mercato, il Benevento avrebbe chiesto Sepe al Napoli ma per Giuntoli e De Laurentiis è incedibile. “Vigorito mi chiamo’ un mese fa chiedendomi di Sepe, ma il Napoli non lo cede. Sono arrivate richieste da almeno altre 2 squadre di serie A, di buona classifica, ma il ragazzo restera’...]]>

Calcio mercato, il Benevento avrebbe chiesto Sepe al Napoli ma per Giuntoli e De Laurentiis è incedibile. “Vigorito mi chiamo’ un mese fa chiedendomi di Sepe, ma il Napoli non lo cede. Sono arrivate richieste da almeno altre 2 squadre di serie A, di buona classifica, ma il ragazzo restera’ a NAPOLI almeno fino a giugno e poi vedremo”. Cosi’, ai microfoni di Radio Crc, Mario Giuffredi, procuratore tra gli altri di Hysaj e Sepe. “Quanto vale Hysaj? Dicono 70 milioni, ma non credo anche perche’ se cosi’ fosse, De Laurentiis avrebbe sbagliato a mettere quella clausola – ha aggiunto Giuffredi -. Non credo che il Napoli prendera’ un terzino a gennaio, immagino che verra’ solo uno tra Verdi e Deulofeu. Poi, a giugno vedremo come andra’ a finire con Ghoulam e se dovesse andare via, il NAPOLI andra’ sul mercato”.

]]>
161067
Calcio mercato, l’Inter tratta per Rafinha, la Roma su Darmian https://www.cronachedellacampania.it/2018/01/calcio-mercato/ Wed, 10 Jan 2018 22:09:29 +0000 https://www.cronachedellacampania.it/?p=160883 Calcio mercato: Inter per Rafinha si tratta,la Roma su Darmian. Nerazzurri anche ipotesi Yanga-Mbiwa. Liverpool vuole il ‘Papu’. E’ l’Inter la societa’ piu’ attiva in questo calcio mercato di gennaio, anche se i vincoli del fair play finanziario e il freno imposto dal governo cinese agli investimenti nel calcio condizionano...]]>

Calcio mercato: Inter per Rafinha si tratta,la Roma su Darmian. Nerazzurri anche ipotesi Yanga-Mbiwa. Liverpool vuole il ‘Papu’. E’ l’Inter la societa’ piu’ attiva in questo calcio mercato di gennaio, anche se i vincoli del fair play finanziario e il freno imposto dal governo cinese agli investimenti nel calcio condizionano le mosse dei collaboratori del patron nerazzurro Zhang Jindong. In ogni caso, procede la trattativa con il Barcellona per il ‘figlio d’arte’ brasiliano RAFINHA, che i catalani sono disposti a cedere in prestito mentre proprio a causa della formula del possibile trasferimento si e’ arenato il discorso per DEULOFEU: il Barca lo cede solo a titolo definitivo, e per questo e’ rimasto in piedi solo il discorso con il Napoli. Intanto l’Inter valuta anche SCHUERRLE, in uscita dal Borussia Dortmund nonostante le smentite ‘di facciata’ provenienti dalla Germania. E’ poi sempre in piedi il discorso per il brasiliano RAMIRES, che vuole lasciare la Cina, mentre per la difesa e’ sempre attuale la pista del giovane atalantino BASTONI, ma circola anche il nome dell’ex romanista YANGA-MBIWA. C’era stata anche un’idea STURRIDGE, attaccante del Liverpool, ma non e’ stato superato l’ostacolo della formula: i Reds, infatti, vogliono un acquisto a titolo definitivo. Totalmente infondata la voce di ICARDI al Real Madrid (che punta anche SALAH del Liverpool): se ne riparlera’ in estate, quando la societa’ ‘merengue’ potrebbe pagare la clausola (valida solo per l’estero e solo dall’1 al 15 giugno) di 110 milioni di euro. Il Napoli continua a insistere per VERDI, che pero’ non e’ ancora convinto di andare alla corte di Sarri. Detto che i dirigenti azzurri sono disposti a girare al Bologna CICIRETTI, in arrivo dal Benevento, c’e’ da dire che il club felsineo avrebbe bussato alla porta del Bari, chiedendo GALANO, detto ‘il Robben del Tavoliere’: i pugliesi sono disposti a cederlo per 3 milioni di euro. Intanto il Napoli ha definito la trattativa con YOUNES. Micola Innocentin, agente dell’esterno offensivo tedesco dell’Ajax, e il d.s. del club azzurro Cristiano Giuntoli hanno raggiunto un accordo per un quinquennale da luglio. Per il calcio mercato estivo del Napoli gira anche il nome del brasiliano MAYCON, centrocampista mancino del Corinthians. In Inghilterra scrivono che il Liverpool starebbe pensando all’atalantino ‘Papu’ GOMEZ come sostituto di Coutinho nel caso non andasse in porto la trattativa con il Leicester per MAHREZ. L’Atalanta lo sostituirebbe con GIACCHERINI. Da Bergamo smentiscono, idem per i presunti contatti con la Juventus per CRISTANTE, che comunque piace ad Allegri. Fissate per domani le visite mediche a Cagliari di LEANDRO CASTAN. Il difensore brasiliano, che con la Roma ha un contratto valido fino a giugno 2019, va in Sardegna in prestito secco. I giallorossi hanno disponibilita’ limitate, ma cercano un terzino, e gli obiettivi sono sempre JUANFRAN o DARMIAN. L’Atletico Madrid si e’ informato su PELLEGRINI. Sulla sponda Lazio e’ invece tornato d’attualita’ il nome di RODRIGO CAIO in caso di addio in estate di DE VRIJ. La Spal prova a chiudere per CIONEK del Palermo e DODO’ dalla Sampdoria, mentre il Genoa potrebbe cedere ROSSETTINI al Sassuolo, che tratta anche il milanista PALETTA. DE MAIO puo’ tornare alla corte genoana lasciando quindi il Bologna (che cedera’ anche OIKONOMOU al Bari). Il Cagliari ha un appuntamento con il Werder Brema per CALDIROLA, mentre a Crotone si punta con decisione su RICCI e CRISETIG. BESSA sta per lasciare Verona con destinazione LA Torino granata, percorso che potrebbe essere fatto anche da PAZZINI, se accettera’ di ‘spalmare’ l’ingaggio. Alberto BRIGNOLI, eroe della partita contro il Milan, rimarra’ al Benevento. Il suo agente Polo Dattola ha detto infatti che “ad oggi non credo che quella dell’addio sia un’ipotesi percorribile”. Piuttosto i sanniti dopo aver sistemato l’attacco con BONGONDA del Trabzonspor (ma di proprieta’ del Celta Vigo) e DIABATE’ dell’Osmanlispor, ora pensano al centrocampo: l’ex Tottenham SANDRO, reduce da una esperienza non fortunata con l’Antalyaspor, ha superato i test medici e nei prossimi giorni si mettera’ a disposizione del tecnico De Zerbi. Infine MATOS, che lascia l’Udinese per andare a Verona in questa finestra di calcio mercato.

]]>
160883
Calcio mercato: il Napoli accelera per Ciciretti https://www.cronachedellacampania.it/2018/01/calcio-mercato-il-napoli-accelera-per-ciciretti/ Thu, 04 Jan 2018 20:27:06 +0000 http://www.cronachedellacampania.it/?p=159421 Il Napoli accelera per Amato Ciciretti. Da subito. Dopo l’eliminazione dalla Tim Cup, Maurizio Sarri ha chiesto rinforzi già in questo mercato. Dall’Inghilterra arrivano anche conferme sull’interesse del Manchester United per Jorginho del Napoli. Viste le difficolta’ ad arrivare a Simone Verdi e Matteo Politano (Bologna e Sassuolo non vogliono mollare...]]>

Il Napoli accelera per Amato Ciciretti. Da subito. Dopo l’eliminazione dalla Tim Cup, Maurizio Sarri ha chiesto rinforzi già in questo mercato. Dall’Inghilterra arrivano anche conferme sull’interesse del Manchester United per Jorginho del Napoli.

Viste le difficolta’ ad arrivare a Simone Verdi e Matteo Politano (Bologna e Sassuolo non vogliono mollare fino a giugno) e la fitta concorrenza per Gerard Deulofeu (non convocato dal Barcellona per il Celta Vigo, ma Inter e Siviglia sono in pressing) il fantasista del Benevento – gia’ ‘prenotato’ per giugno, quando potra’ svincolarsi – potrebbe essere l’uomo giusto. Dell’ipotesi ha parlato il presidente del Benevento, Oreste Vigorito: “Ho avuto ieri in sede la notifica da parte del NAPOLI, cosi’ come prevede il regolamento di Lega, dell’avvio della trattativa per Ciciretti, ma non mi e’ stato chiesto di partecipare alla negoziazione. Se poi il club azzurro vorra’ anticipare la cessione di Ciciretti a gennaio, non so rispondere perche’ nessuno me lo ha comunicato. Con De Laurentiis – aggiunge – c’e’ un buon rapporto, noi rappresentiamo una piccola realta’ e siamo felici di avere rapporti con tutti, anche col NAPOLI. Non ho mai ostacolato la trattativa tra Ciciretti e il NAPOLI e mai lo faro’ perche’ i giovani hanno il diritto di volare, ma non chiedero’ mai nulla al NAPOLI a gennaio per non perderlo a parametro zero”.

L’Inter sarebbe interessata a Juan Mata, il cui contratto con il Manchester United scade a fine stagione. La societa’ nerazzurra starebbe monitorando la situazione del calciatore spagnolo per poterlo prendere poi a giugno. A Luciano Spalletti, in realta’, servirebbe qualcuno da subito per puntare dritti al quarto posto. Se in difesa potrebbe essere anticipato l’arrivo di Alessandro Bastoni dall’Atalanta, “negli altri reparti ci vorrebbe uno piu’ forte di quelli che abbiamo. Ma chi prendiamo? Mkhitaryan? Pastore? Ma servono 30-40 milioni e mi hanno detto che non si possono spendere. Oppure potremmo ricorrere a uno scambio di prestiti ma ci vuole qualcuno che prende uno dei nostri giocatori e se non lo vogliono che fai?”. Mauro Icardi resta nel mirino del Real Madrid. I blancos stanno valutando la possibilita’, gia’ nel corso di questo mese, di intervenire in attacco viste le difficolta’ di Karim Benzema. Piace anche Timo Werner, 21enne bomber del Lipsia, mentre l’attaccante dell’Inter sarebbe disponibile da subito anche in Champions League. Per strappare Icardi al club nerazzurro servono i 100 milioni di euro della clausola rescissoria. Marko Pjaca puo’ ormai considerarsi un nuovo giocatore dello Schalke. La societa’ tedesca, che prelevera’ l’attaccante croato della Juventus con la formula del prestito secco fino a giugno, ha comunicato via Twitter che le visite mediche sono state superate e che a breve ci sara’ la firma sul contratto. Intanto, Stefano Sturaro sarebbe entrato nel mirino del Valencia: se al Mestalla non hanno ancora perso la speranza di riprendersi Joao Cancelo dall’Inter per mettere a posto la fascia destra, per il centrocampo la priorita’ e’ un giocatore dalle caratteristiche difensive e i nomi in ballo sono due: quello del centrocampista juventino e di Andre’ Simoes dell’Aek Atene. Sturaro avrebbe gia’ dato la propria disponibilita’.

E’ attiva soprattutto in uscita la Juventus in questa prima fase di calciomercato. I bianconeri, alla ricerca di una sistemazione in cui far crescere Marko Pjaca, reduce dal grave infortunio al ginocchio destro rimediato il marzo scorso con la nazionale, hanno trovato l’accordo con lo Schalke 04. E’ stato proprio il club tedesco in serata ad annunciare il buon esito della trattativa: l’attaccante croato ha superato le visite mediche e approda in prestito secco per sei mesi in Bundesliga dietro al corrispettivo di un milione di euro più bonus. Potrebbe lasciare Torino anche Stefano Sturaro, per il quale avrebbe fatto un sondaggio il Valencia, che non molla la pista Joao Cancelo, nonostante il portoghese nelle ultime settimane stia trovando più spazio con la maglia del’Inter.

La Roma continua a cercare un terzino (piace sempre JUANFRAN), e a parlare con il Benfica del possibile ‘sbarco’ in Portogallo di BRUNO PERES. Intanto e’ stato a Trigoria Alessandro Beltrami, agente di Radja NAINGGOLAN: si e’ discusso del momento particolare del belga ma anche di BARELLA del Cagliari, che e’ un altro degli assistiti di questo procuratore, che del giocatore ha parlato anche con la Juve e con l’Inter.

Il Genoa vuole cedere almeno cinque giocatori nel Mercato di gennaio: sono LANDRE, RODRIGUEZ, PALLADINO, CENTURION (che ritornera’ in Argentina) e RICCI. In bilico sono anche GENTILETTI, GALABINOV, LAZOVIC e COFIE, ma potrebbe partire anche LAXALT: il suo contratto e’ stato rinnovato fino al 2022 per evitare che andasse via a parametro zero, ma su di lui c’e’ da tempo il Galatasaray, che insiste per averlo. Tutto cio’ per dire che Preziosi potrebbe ripetere quanto gia’ fece l’anno scorso, ovvero rifare quasi completamente la squadra a gennaio. Fra i possibili arrivi c’e’ quello di LEANDRO CASTAN dalla Roma. SALIBUR e’ invece un obiettivo della Fiorentina. Ma il Guingamp vuole cedere il suo centrocampista solo a titolo definitivo, mentre la Viola vorrebbe il giocatore in prestito con diritto di riscatto. Corvino tratta anche VIVIANI con la Spal, ma per giugno. Per il Bologna c’e’ una richiesta del Crystal Palace per DE MAIO, ma ha chiesto di essere ceduto l’altro difensore MAIETTA, che piace a Cagliari, Verona ed Empoli. In Emilia si muove anche la Spal, che cerca un difensore: l’ultima pista porta al brasiliano RODRIGO ELY, ex milanista attualmente in Spagna all’Alaves. Con il Torino e’ sempre in piedi il discorso per ACQUAH e BONIFAZI. Il Crotone ha bisogno di un attaccante e provera’ a prendere l’interista PINAMONTI, ‘stella’ delle giovanili nerazzurre. Un altro giovane di talento, Alberto PICCHI dell’Empoli (pronipote di Armando, capitano della Grande Inter) e’ richiesto da Atalanta e Bologna. Il Verona ha chiesto BOYE’ al Torino e D’ALESSANDRO al Benevento. Il Sassuolo vuole l’attaccante ORSOLINI, attualmente all’Atalanta.

]]>
159421
Calcio mercato, Napoli: c’è il si di Younes https://www.cronachedellacampania.it/2018/01/calcio-mercato-napoli-younes/ Wed, 03 Jan 2018 19:02:58 +0000 http://www.cronachedellacampania.it/?p=159119 Il mercato e’ appena cominciato ufficialmente, ma i contatti sono tanti e l’impressione e’ che questa sessione invernale potrebbe essere meno povera di altre del passato. C’e’ anche chi, adesso, lavora in prospettiva giugno, perche’ per definire certi affari c’e’ bisogno di tempo. Il Napoli sta tentando di sciogliere il nodo...]]>

Il mercato e’ appena cominciato ufficialmente, ma i contatti sono tanti e l’impressione e’ che questa sessione invernale potrebbe essere meno povera di altre del passato. C’e’ anche chi, adesso, lavora in prospettiva giugno, perche’ per definire certi affari c’e’ bisogno di tempo. Il Napoli sta tentando di sciogliere il nodo di Simone VERDI, che vuole rimanere al Bologna fino a giugno. Invece c’e’ accordo totale con Amin YOUNES dell’Ajax per un trasferimento immediato (l’alternativa potrebbe essere Matteo POLITANO, o Amato CICIRETTI, ancora a Benevento ma gia’ messo sotto contratto) pero’ bisogna convincere gli olandesi. Per Roberto INGLESE ci sono dei problemi, perche’ il Chievo non vuole cederlo a gennaio, ma rispettare l’accordo che prevedeva giugno 2018. E non basta Dariusz MILIK in contropartita tecnica a far cambiare idea ai dirigenti veneti. La Juventus invece è fortemente interessata a due giovani italiani del calibro di Bryan CRISTANTE dell’Atalanta e Nicolo’ BARELLA del Cagliari. Quest’ultimo e’ stato proprio oggi ‘blindato’ dalla societa’ sarda, ma solo in vista di una remunerativa cessione. Ovviamente la dirigenza bianconera continua a lavorare anche su EMRE CAN, che lascera’ il Liverpool ma a giugno perche’ i Reds non lo lasceranno andare via prima della scadenza del contratto . In uscita la Juve lavora si Marko PJACA, per il quale la formula delle cessione allo Schalke 04 e’ quella del prestito oneroso per un milione di euro piu’ bonus di altri 500mila.

Vertice di mercato all’Inter: Spalletti ha chiesto di far venire subito da Bergamo il 18enne difensore Alessandro BASTONI, gia’ preso l’estate scorsa. Piacciono molto Gerard DEULOFEU del Barcellona (partira’ se in Catalogna arriva Philippe COUTINHO dal Liverpool per un affare da 150 milioni) e il laziale Stephan DE VRIJ, che pero’ fino a giugno non si muove. Javier PASTORE per ora rimane un sogno, perche’ a Parigi percepisce un ingaggio troppo elevato per le attuali possibilita’ nerazzurre. Cosi’ alla fine potrebbe arrivare il brasiliano RAMIRES dalla consorella cinese: il giocatore sta spingendo in tal senso, ed e’ disposto anche ad abbassarsi lo stipendio. Grzegorz KRYCHOWIAK torna nelle mire della Roma: il d.s. Monchi, che lo ha avuto con se’ a Siviglia, e’ convinto delle potenzialita’ di questo centrocampista di proprieta’ del Paris SG e ora al West Bromwich dove pero’ non ha convinto. Il polacco arriverebbe a Trigoria in prestito.

La Roma cerca anche un terzino, e in Spagna si scrive dell’interessamento per JUANFRAN dell’Atletico Madrid, che e’ in scadenza di contratto con il club della capitale spagnola. Arrivare a Matteo DARMIAN sembra invece piu’ difficile per via dell’elevata concorrenza. Poi c’e’ CENGIZ UNDER: potrebbe gia’ lasciare la Roma, perche’ sul turco ci sono Sassuolo e Verona, interessate a prenderlo in prestito. Oltretutto nel club neroverde c’e’ quel Domenico BERARDI che il tecnico Eusebio Di Francesco, con cui ha lavorato in Emilia, vorrebbe riavere a disposizione. Ecco perche’ Under puo’ diventare prezioso come pedina di scambio.  Da Verona, ma sponda Hellas, partira’ invece Martin CACERES, destinato alla Lazio, che a sua volta sta per cedere MAURICIO all’Atletico Mineiro. Il genoano Armando IZZO potrebbe andare al Milan per prendere il posto di Gustavo GOMEZ, che ha un accordo col Boca Juniors. Per i rossoneri c’e’ la concorrenza dello Schalke 04, disposto a offrire 12 milioni di euro per avere il difensore di Scampia da subito. Il Milan potrebbe cedere anche, dopo pochi mesi di permanenza in rossonero, Mateo MUSACCHIO, che ha estimatori in Spagna.

Ci sono tre nomi per la sostituzione di Paolo CANNAVARO, che lascia il Sassuolo per andare con il fratello Fabio in Cina. La dirigenza emiliana pensa in primis a Gabriel PALETTA, con Lorenzo TONELLI (che piace anche alla Spal) e l’ex juventino Frederik SORENSEN in alternativa. E a proposito di Sassuolo, si riparla della possibilita’ che Alessandro MATRI venga ceduto al Parma. L’Udinese pensa ancora di arrivare al difensore del Santos LUCAS VERISSIMO, nonostante la corte dello Spartak Mosca, visto che il giocatore preferirebbe la soluzione italiana anche se in Russia gli offrono un ingaggio piu’ alto.

]]>
159119
Calcio mercato, il Barca insiste per Koulibaly, Chiellini dice no alle sirene del Chelsea https://www.cronachedellacampania.it/2018/01/calcio-mercato-barca-insiste-koulibaly-chiellini-dice-no-alle-sirene-del-chelsea/ Tue, 02 Jan 2018 19:02:05 +0000 http://www.cronachedellacampania.it/?p=158913 Tutti pazzi per de Vrij. E’ il centrale olandese il pezzo pregiato del mercato di gennaio che domani aprira’ ufficialmente i battenti: in scadenza di contratto e ormai orientato a rifiutare l’offerta della Lazio, de Vrij e’ una grande opportunita’ low cost che Inter e Juventus non vorrebbero lasciarsi scappare,...]]>

Tutti pazzi per de Vrij. E’ il centrale olandese il pezzo pregiato del mercato di gennaio che domani aprira’ ufficialmente i battenti: in scadenza di contratto e ormai orientato a rifiutare l’offerta della Lazio, de Vrij e’ una grande opportunita’ low cost che Inter e Juventus non vorrebbero lasciarsi scappare, anche facendo (nel caso del club nerazzurro) un sacrificio gia’ in questa sessione. C’e’ pero’ da battere una concorrenza imponente, soprattutto dalla Spagna: il laziale piace al Valencia e, soprattutto, al Barcellona che, pero’, al momento ha la testa soltanto a strappar via Coutinho al Liverpool (per 150 milioni…). De Vrij avrebbe chiesto tempo all’Inter perche’ preferirebbe firmare a giugno per il Barça, dove prenderebbe il posto di Mascherano. Il club catalano in Italia segue anche il centrale del Napoli Koulibaly. Lotito intende cederlo per monetizzare. La Roma vuole sciogliere il nodo terzino destro con un investimento alla Kolarov: il profilo emerso nelle ultime ore e’ quello di Juanfran dell’Atletico Madrid, le alternative sono Aleix Vidal (Barcellona, piace anche all’Inter) e Darmian (Manchester United), ma qualsiasi operazione e’ legata a un eventuale addio di Bruno Peres, ormai fuori dalle preferenze di Di Francesco. Per l’ex granata sondaggi di Benfica e Galatasaray, ma va ricordato che la Roma in rosa, in quel ruolo, punta su Florenzi e sullo sfortunato Karsdorp, acquistato in estate dal Feyenoord ma praticamente mai a disposizione causa infortuni.

Dal Portogallo rilanciano a proposito di un interesse del Napoli per Grimaldo, ma la trattativa potrebbe aver sfogo soltanto giugno, considerato il costo del cartellino del mancino (circa 30 milioni) e il ritorno di Ghoulam che non appare poi cosi’ lontano. Per l’attacco, come alternativa a Callejon si lavora per Deulofeu, mentre Inglese dovrebbe vestire d’azzurro nel corso della sosta di A. Tornando all’Inter, Spalletti vuole un uomo di qualita’ dietro Icardi: Pastore e’ il sogno, ma l’ingaggio e’ troppo alto. E poi bisognerebbe cedere almeno uno tra Joao Mario e Brozovic, per i quali non c’e’ la fila. Fermo restando l’interesse per de Vrij, in casa nerazzurra tengono aperta la pista Yanga-Mbiwa, in un’operazione che coinvolgerebbe anche lo Jiangsu Suning. Piace Acerbi, ma il Sassuolo ha appena perso Cannavaro, volato in Cina per fare da vice al Guangzhou dal fratello Fabio: possibilita’ prossime allo zero. Dalla Francia: l’Inter segue con interesse Denys, 19enne terzino del Paris Fc che milita nel campionato di serie B transalpino. Il club nerazzurro vorrebbe ingaggiarlo a gennaio per poi darlo in prestito. Un nome spendibile per il Sassuolo sarebbe quello di Paletta: l’italo-argentino e’ scomparso dai radar sia con Montella che con Gattuso, potrebbe rilanciarsi con Iachini. Il Milan vuole sfoltire una rosa extralarge e in tal senso va anche la probabile cessione di Gustavo Gomez al Boca Juniors. Nessun ingresso previsto nella rosa rossonera dopo i fasti estivi.

Pjaca e’ pronto a lasciare, anche se solo per sei mesi, la Juventus: c’e’ lo Schalke in grande vantaggio per assicurarsi il talento croato, reduce da un grave infortunio al ginocchio. Chi alla Juve restera’, probabilmente per sempre, e’ Chiellini e la conferma arriva da Conte, cui non dispiacerebbe averlo al Chelsea: “Giorgio e’ un buon giocatore, ma intende chiudere la carriera alla Juve ed e’ giusto che sia cosi'”. Dopo aver ingaggiato Rossi, il Genoa potrebbe prendere un altro attaccante. Si tratta di Mesanovic, 25enne centravanti bosniaco che ha una valutazione di circa 2,5 milioni di euro e che milita nel Maribor. “Il Genoa? Adesso non penso alle voci, di queste cose se ne occupano il mio agente e la societa’ – afferma -. Io sono focalizzato al massimo sugli allenamenti. Poi e’ chiaro che sarebbe un grande passo avanti per la mia carriera trasferirmi in Italia”. Visite mediche per Sandro, 26enne centrocampista brasiliano dell’Antalyaspor pronto a indossare la maglia del Benevento.

]]>
158913
Calcio mercato, pronti per i botti: Napoli la nuova suggestione è Deulofeu https://www.cronachedellacampania.it/2018/01/calcio-mercato-pronti-per-i-botti-napoli-la-nuova-suggestione-e-deulofeu/ Mon, 01 Jan 2018 19:52:18 +0000 https://www.cronachedellacampania.it/?p=158680 Ancora due giorni e si riparte. Il mercato di gennaio sta per prendere il via, tra trattative concrete e difficolta’ oggettive. Se nel resto d’Europa, specie in Premier League, sono andati a segno gia’ colpi di grande peso e ‘irraggiungibili’ per i nostri club (basti pensare al passaggio di Van...]]>

Ancora due giorni e si riparte. Il mercato di gennaio sta per prendere il via, tra trattative concrete e difficolta’ oggettive. Se nel resto d’Europa, specie in Premier League, sono andati a segno gia’ colpi di grande peso e ‘irraggiungibili’ per i nostri club (basti pensare al passaggio di Van Dijk dal Southampton al Liverpool per 84 milioni di euro), in A si va a caccia di occasioni per rinforzare e puntellare le proprie rose, scoperte magari in ruoli chiave a causa di infortuni e scelte estive. Ci si aspettano movimenti di peso soprattutto da Inter e Napoli. I nerazzurri, seppure alle prese con i vincoli del Fair play finanziario e con l’austerita’ imposta dal Governo cinese a Suning, proveranno a mettere a disposizione di Spalletti un paio di elementi in grado di aiutare l’Inter a proseguire il cammino – accidentato negli ultimi tempi, ma comunque positivo – verso il ritorno in Champions League. I nomi piu’ caldi, al netto di una comunque necessaria cessione di Joao Mario, sono quelli di Pastore del Psg, Mkhytarian del Manchester United e Deulofeu del Barcellona.
Sull’ex Milan c’e’ pero’ anche la concorrenza del Napoli, con la coperta corta in avanti dopo il grave infortunio di Milik. L’arrivo di Inglese appare scontato anche se solo durante la sosta, De Laurentiis potrebbe tirar fuori l’asso dal mazzo: c’e’ anche Verdi del Bologna, autore di una prima parte di stagione d’altissimo livello, tra i desideri di Sarri. E’ difficile pero’ che Verdi, intoccabile al Bologna, possa fare un passo del genere durante questa finestra. Anche la suggestione Ciciretti, data per certa nelle scorse settimane, potrebbe concretizzarsi subito: il Benevento, anche se lontanissimo dalla zona salvezza, preferirebbe trattenere il suo talento fino a giugno, seppur a costo di perderlo a parametro zero. Altro nodo che verra’ risolto nei prossimi giorni e’ quello relativo ai giocatori in scadenza di contratto tra sei mesi: il fronte piu’ accesso e’ sicuramente quello di de Vrij. La Lazio ha da tempo sul piatto una proposta concreta, la trattativa e’ aperta e nei giorni scorso il ds Tare ha assicurato che a breve ci sara’ una “decisione finale”. In un senso o nell’altro.
L’Inter (che potrebbe anticipare l’arrivo di Bastoni dall’Atalanta e punta per la fascia anche Aleix Vidal) spinge, ma occhio al Barcellona. La Juventus ha messo le mani su Emre Can, ma il Liverpool (di nuovo alle prese con la grana Coutinho, dato dalla Nike, anche se per pochi minuti, oramai sicuro acquisto blaugrana) vuole trattenere il centrocampista fino al termine della stagione. Un altro prospetto di livello in scadenza come Goretzka, centrocampista dello Schalke, pare ormai destinato al Bayern Monaco. Pogba in versione “agente segreto” per cercare di convincere Dybala ad accettare le lusinghe del Manchester United. Sarebbe questa la nuova strategia della squadra di Mourinho per cercare di arrivare all’attaccante argentino, soprattutto dopo i contemporanei infortuni di Lukaku e Ibrahimovic. Pogba e Dybala sono amici e la loro intesa nasce ai tempi in cui entrambi (stagione 2015-2016) hanno giocato alla Juventus.
Lo United avrebbe “alzato” la posta in gioco, mettendo sul piatto 90 milioni di sterline (circa 100 milioni di euro) per arrivare alla Joya. L’Udinese non e’ sul genoano Lapadula. A mettere in chiaro le cose ci pensa il ds Gerolin: “Siamo pienamente soddisfatti del nostro parco attaccanti, non c’e’ alcuna trattativa di mercato per portare a Udine Lapadula, che non rientra nei nostri piani e nelle nostre strategie”. Il Torino cerca un vice Belotti: Pazzini (che potrebbe rientrare in uno scambio con Sadiq, che piace al Verona) resta in pole, nelle ultime ore si e’ ipotizzato l’ingaggio di Falcinelli dal Sassuolo. Pista complicata. Caceres, a segno due giorni fa con la Juventus, potrebbe accasarsi alla Lazio. Il Cagliari ha annunciato il prolungamento del contratto del portiere Rafael, che continuera’ a vestire i colori rossoblu’ sino al 30 giugno 2019. La Samp ha messo nel mirino Viviani della Spal, un’operazione che andrebbe in porto in estate, quando con ogni probabilita’ Torreira lascera’ il blucerchiato.

]]>
158680
Calcio mercato: pazza idea Dybala per lo United, il Napoli prova per Younes https://www.cronachedellacampania.it/2017/12/calcio-mercato-pazza-idea-dybala-lo-united-napoli-prova-younes/ Tue, 26 Dec 2017 19:56:54 +0000 https://www.cronachedellacampania.it/?p=157546 E’ Paulo DYBALA il protagonista dell’ultima voce di mercato, gia’ caldissimo nonostante la ‘finestra’ invernale si apra soltanto a gennaio. Dopo le tre panchine consecutive in campionato, secondo il ‘Daily Star’ Jose’ Mourinho sarebbe pronto a fare un’offerta per la ‘Joya’, a sua volta entrato nell’ordine d’idee, sempre secondo il...]]>

E’ Paulo DYBALA il protagonista dell’ultima voce di mercato, gia’ caldissimo nonostante la ‘finestra’ invernale si apra soltanto a gennaio. Dopo le tre panchine consecutive in campionato, secondo il ‘Daily Star’ Jose’ Mourinho sarebbe pronto a fare un’offerta per la ‘Joya’, a sua volta entrato nell’ordine d’idee, sempre secondo il tabloid, di lasciare la Juve, anche se solo in estate. Per il club bianconero sarebbe pronta un’offerta di 60 milioni di sterline, pari a circa 70 milioni di euro. Figura chiave di questa trattativa sarebbe quella di Javier Ribalta che da capo scout Juve e’ passato in estate allo United, il rapporto di quest’ultimo con Dybala faciliterebbe di molto l’operazione. Intanto Mourinho ha chiesto al suo club di effettuare un acquisto anche a gennaio, quello del giovane talento brasiliano MALCOM, 20 anni, dal Bordeaux. Per il club francese sarebbe pronta un’offerta di 50 milioni di dollari. Ma non e’ tutto, perche’ lo Special One starebbe corteggiando da alcune settimane Marco VERRATTI: l’ostacolo maggiore e’ la richiesta del Psg che per far partire il suo centrocampista, ma a giugno, chiede 120 milioni di euro.
Per la Roma, che secondo il d.s. Monchi non effettuera’ movimenti sul mercato invernale, spunta a sorpresa il nome di Marouane FELLAINI: sul centrocampista belga c’e’ da tempo il Besiktas ma, come scrive sempre il Daily Star, ora sul giocatore dello United c’e’ anche un deciso interessamento da parte del club giallorosso. Intanto il Man United ha proposto al calciatore un il rinnovo biennale, ma la proposta e’ stata, almeno per ora respinta. La Roma lavora anche su DARMIAN, seguito anche da Napoli e Inter, affare possibile solo se EMERSON PALMIERI dovesse essere ceduto alla Juventus. In Spagna scrivono che Diego Simeone ha convinto Sime VRSALJKO a rimanere nell’Atletico Madrid, per il quale sfuma quindi il passaggio al Napoli.
Per ‘As’ gli emissari del presidente Aurelio De Laurentiis avevano offerto 22 milioni per il laterale croato ex Sassuolo. E sempre a proposito di Napoli, in Spagna sono sicuri che Pepe REINA sara’ il nuovo portiere del Milan dal prossimo anno, quando Gigio DONNARUMMA lascera’ Milanello con destinazione Madrid (sponda Real) o Parigi. Ancora Napoli per dire che c’e’ l’accordo, gia’ per gennaio, con Amin YOUNES, esterno tedesco dell’Ajax. Manca pero’ quello con la societa’ di Amsterdam, che per lasciarlo libero fin da subito chiede 15 milioni, mentre il Napoli ne offre 5. La Lazio potrebbe prendere un rinforzo a centrocampo, e il nome che si fa con piu’ insistenza e’ quello di Raman CHIBSAH del Benevento. In realta’ il sogno del d.s. Igli Tare sarebbe Milan BADELJ, ma per il croato della Fiorentina c’e’ una concorrenza in prima fila, con i ‘cugini’ della Roma in prima fila. Potrebbe essere italiano il prossimo allenatore del Siviglia. La societa’ andalusa ha incontrato Walter MAZZARRI, ancora sotto contratto con il Watford, ma l’altro profilo valutato dagli spagnoli e’ quello di Vincenzo MONTELLA, fresco di esonero dal Milan ma comunque apprezzato dalla dirigenza sivigliana, che ha preso contatti con l’agente del tecnico, Alessandro Lucci.

]]>
157546