/** * 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 Napoli https://www.cronachedellacampania.it/tag/calcio-napoli/ Le ultime notizie LIVE dalla Campania Sat, 01 Aug 2026 16:56:14 +0000 it-IT hourly 1 https://www.cronachedellacampania.it/wp-content/uploads/2026/04/cropped-cronache_logo_rotondo_2026-dimensioni-medie-32x32.png Calcio Napoli https://www.cronachedellacampania.it/tag/calcio-napoli/ 32 32 112696088 Il Ministro Abodi celebra il centenario del Napoli: un secolo di emozioni e storia https://www.cronachedellacampania.it/2026/08/il-ministro-abodi-celebra-il-centenario-del-napoli-un-secolo-di-emozioni-e-storia/ https://www.cronachedellacampania.it/2026/08/il-ministro-abodi-celebra-il-centenario-del-napoli-un-secolo-di-emozioni-e-storia/#respond Sat, 01 Aug 2026 16:55:10 +0000 https://www.cronachedellacampania.it/?p=876381 Andrea Abodi, ministro dello Sport, rende omaggio al Napoli nel giorno del suo centenario, ricordando la passione e le emozioni che la squadra ha regalato a milioni di tifosi in tutto il mondo.]]>

Roma -Il centenario del Napoli è stato celebrato con parole cariche di emozione dal ministro dello Sport, Andrea Abodi. In un messaggio pubblico, Abodi ha sottolineato come il Napoli rappresenti molto più di una squadra di calcio: è un simbolo di cultura, passione e identità per la città e per i suoi tifosi sparsi nel mondo.

“Un secolo di storia e di storie, di musica e teatro che sono diventati lo stadio, di passione infinita e qualche patimento, di lacrime dal diverso sapore, da piccole sofferenze a infinite gioie, di cuore e amore della gente”, ha scritto il ministro. Abodi ha anche condiviso un ricordo personale, raccontando come il Napoli sia stata la sua prima grande passione sportiva fin dall’infanzia, evocando nomi leggendari come Juliano, Zoff e Maradona, icona indimenticabile per tutti gli amanti del calcio.

“Auguro al Napoli un meraviglioso secolo di vita, che ha riempito la vita di tanti e continuerà a farlo, attraversando generazioni e distanze”, ha concluso. Il messaggio sottolinea il valore del club non solo come realtà sportiva, ma come patrimonio culturale e sociale, capace di unire comunità e accendere passioni in ogni angolo del pianeta.

]]>
https://www.cronachedellacampania.it/2026/08/il-ministro-abodi-celebra-il-centenario-del-napoli-un-secolo-di-emozioni-e-storia/feed/ 0 876381
Mattia Esposito, il giovane napoletano che conquista il Calcio Napoli: un sogno che diventa realtà https://www.cronachedellacampania.it/2026/08/mattia-esposito-il-giovane-napoletano-che-conquista-il-calcio-napoli-un-sogno-che-diventa-realta/ https://www.cronachedellacampania.it/2026/08/mattia-esposito-il-giovane-napoletano-che-conquista-il-calcio-napoli-un-sogno-che-diventa-realta/#respond Sat, 01 Aug 2026 10:58:29 +0000 https://www.cronachedellacampania.it/?p=876235 Tra le strade di Napoli nasce una storia di sacrificio e determinazione: Mattia Esposito, con umiltà e impegno, indossa la maglia del Calcio Napoli, diventando simbolo di speranza e orgoglio per la città.]]>

Napoli celebra un nuovo talento che ha saputo trasformare un sogno in realtà grazie a sacrifici e tanta dedizione. Mattia Esposito, giovane promessa del calcio partenopeo, ha raggiunto un traguardo importante indossando la maglia del Calcio Napoli, suscitando emozione e orgoglio in tutta la comunità.

L’immagine commovente dell’abbraccio tra Mattia e sua madre racconta anni di rinunce, speranze e amore incondizionato.

Il Vice Presidente di Soccorso Napoli Sanità ODV, Esposito Nuzzo Rosario, ha voluto esprimere un messaggio speciale alla famiglia: “Siate fieri di Mattia, un esempio di come educazione, sacrificio e determinazione portino a risultati straordinari. Napoli ha bisogno di giovani come lui, capaci di rappresentare la città con onore.” A nome di tutta l’organizzazione, sono state rivolte a Mattia e alla sua famiglia le più sentite congratulazioni, accompagnate dall’augurio di un percorso ricco di soddisfazioni e successi dentro e fuori dal campo.

La città tutta sostiene e incoraggia questo piccolo campione a continuare a inseguire i propri sogni con la stessa forza e umiltà che lo hanno contraddistinto finora.

]]>
https://www.cronachedellacampania.it/2026/08/mattia-esposito-il-giovane-napoletano-che-conquista-il-calcio-napoli-un-sogno-che-diventa-realta/feed/ 0 876235
Napoli Calcio festeggia i 100 anni con uno speciale su Rai3 https://www.cronachedellacampania.it/2026/08/napoli-calcio-festeggia-i-100-anni-con-uno-speciale-su-rai3/ https://www.cronachedellacampania.it/2026/08/napoli-calcio-festeggia-i-100-anni-con-uno-speciale-su-rai3/#respond Sat, 01 Aug 2026 06:26:35 +0000 https://www.cronachedellacampania.it/?p=876130 In occasione del centenario della Società Sportiva Calcio Napoli, Rai3 trasmette lo speciale 'Napoli 100', un viaggio emozionante tra storia, immagini e testimonianze che raccontano un secolo di passione azzurra.]]>

Oggi, 1° agosto 2026, Rai3 celebra il centenario della Società Sportiva Calcio Napoli con uno speciale dedicato intitolato “Napoli 100”. La trasmissione, realizzata dalla TGR Campania sotto la direzione del caporedattore centrale Oreste Lo Pomo, racconta i cento anni di storia del club partenopeo attraverso un ricco archivio di documenti, immagini e testimonianze.

Il programma offre un viaggio emozionante che intreccia la storia della squadra con quella della città di Napoli, arricchito da contributi esclusivi come le interviste all’attore Alessandro Siani e al presidente del Napoli, Aurelio De Laurentiis. Lo speciale è stato curato dal caporedattore vicario Gianfranco Coppola e coordinato dal vicecaporedattore Fabrizio Cappella, con il supporto della redazione giornalistica, delle Teche Rai e del lavoro di montaggio.

La scelta del 1° agosto come data di messa in onda non è casuale: proprio in questo giorno, cento anni fa, la storia ufficiale del Napoli Calcio ebbe inizio. “Napoli 100” rientra nel palinsesto estivo della Testata Giornalistica Regionale, diretta da Roberto Pacchetti con il condirettore Antonello Perillo, e sarà trasmesso anche su Rai Italia, il canale satellitare rivolto agli italiani all’estero. Un appuntamento imperdibile per tutti gli appassionati di calcio e per chi vuole scoprire le radici di una delle squadre più amate d’Italia.

]]>
https://www.cronachedellacampania.it/2026/08/napoli-calcio-festeggia-i-100-anni-con-uno-speciale-su-rai3/feed/ 0 876130
Manfredi: «Sequestro al Maradona? Fraintesa la delibera comunale sugli spazi» https://www.cronachedellacampania.it/2026/05/manfredi-sequestro-al-maradona-fraintesa-delibera-comunale/ https://www.cronachedellacampania.it/2026/05/manfredi-sequestro-al-maradona-fraintesa-delibera-comunale/#comments Thu, 28 May 2026 09:24:07 +0000 https://www.cronachedellacampania.it/?p=855816 Il sindaco di Napoli Gaetano Manfredi interviene sul sequestro di una struttura realizzata dal Calcio Napoli allo stadio Maradona, sottolineando un'interpretazione errata della delibera comunale sulle concessioni degli spazi.]]>

Napoli– Un malinteso interpretativo della delibera comunale sulla concessione degli spazi allo stadio Maradona ha portato al sequestro di una struttura realizzata dal Calcio Napoli nella tribuna autorità.

A chiarire la situazione è stato il sindaco di Napoli, Gaetano Manfredi, che ha spiegato come la società abbia considerato la delibera anche come un’autorizzazione edilizia, mentre in realtà era necessario ottenere un ulteriore permesso per eseguire i lavori.

“Dalle informazioni in mio possesso, credo ci sia stata un’interpretazione contrastante della delibera, che ha generato questa problematica”, ha dichiarato Manfredi a margine di un convegno. L’intervento della Polizia locale è scaturito da un esposto presentato da un privato, che ha portato all’accertamento della discrepanza procedurale. Il sindaco ha espresso la speranza che la questione possa essere risolta rapidamente, evitando ulteriori complicazioni per il club e per la città.

]]>
https://www.cronachedellacampania.it/2026/05/manfredi-sequestro-al-maradona-fraintesa-delibera-comunale/feed/ 1 855816
Il Napoli degli Anni ’60: Sfide, Passione e Crescita di un Club Ambizioso https://www.cronachedellacampania.it/2026/04/il-napoli-degli-anni-60-sfide-passione-e-crescita-di-un/ https://www.cronachedellacampania.it/2026/04/il-napoli-degli-anni-60-sfide-passione-e-crescita-di-un/#comments Fri, 17 Apr 2026 07:30:00 +0000 https://www.cronachedellacampania.it/?p=843103 Gli anni ’60 segnarono per il Napoli una decade di trasformazioni intense tra sfide sportive e fermenti sociali, gettando le basi per un futuro glorioso.]]>

Un ricordo d’infanzia sul San Paolo

Ricordo ancora, da bambino, le lunghe passeggiate con mio nonno verso lo Stadio San Paolo, allora ben diversa dalla struttura moderna che conosciamo oggi. L’aria vibrava di speranze e di una passione autentica che pervadeva ogni angolo del quartiere Vomero, dove vivevamo. Quegli anni Sessanta rappresentarono un decennio cruciale per il Napoli, non solo sul campo ma anche nella vita sociale della città.

Contesto socio-economico di Napoli negli anni ’60

Gli anni ’60 a Napoli furono un periodo di grandi cambiamenti. La città, ancora segnata dalle conseguenze della guerra, stava lentamente cercando di risollevarsi. Le difficoltà economiche e sociali erano palpabili, ma proprio queste creavano un terreno fertile per la nascita di una passione collettiva che si rifletteva nello sport. Il calcio rappresentava per molti un’occasione di riscatto e di orgoglio cittadino.

Le sfide sportive tra Serie A e Serie B

Per il Napoli, gli anni ’60 furono un continuo alternarsi di speranze e delusioni. La squadra affrontava periodi di difficoltà, con retrocessioni e promozioni che misero alla prova la tenuta del club e dei suoi tifosi. L’obiettivo era chiaro: affermarsi stabilmente in Serie A e competere con i grandi del calcio italiano. Ogni partita era una battaglia, ogni punto conquistato un traguardo fondamentale.

Giocatori simbolo di quegli anni

Tra le fila del Napoli emersero figure di spicco che iniziarono a lasciare un segno indelebile. Calciatori dall’animo combattivo e dal talento cristallino, capaci di trascinare la squadra con le loro giocate e la loro dedizione. Nomi come Ottavio Bianchi e Dino Zoff iniziarono a brillare, rappresentando il cuore pulsante di un Napoli ambizioso e in crescita.

Partite memorabili e momenti decisivi

La storia del Napoli negli anni ’60 è costellata di partite che ancora oggi i tifosi ricordano con emozione. Scontri intensi contro avversari storici, vittorie sorprendenti e gare al cardiopalma che plasmarono l’identità della squadra. Questi momenti non furono solo successi sportivi, ma vere e proprie tappe di un percorso di maturazione e affermazione.

Il ruolo dei tifosi e della città

Il sostegno dei tifosi napoletani fu un elemento chiave in questo decennio. La passione della città per il Napoli non aveva eguali, e il pubblico del San Paolo era il dodicesimo uomo in campo. La capacità dei tifosi di unire la città intorno alla squadra faceva del calcio un fenomeno sociale capace di superare divisioni e difficoltà.

Cambiamenti strutturali nel club e nello stadio

Negli anni ’60, il Napoli cominciò a pianificare un futuro più solido. Cambiamenti nella gestione societaria e investimenti infrastrutturali furono fondamentali per la crescita del club. Lo Stadio San Paolo, pur nella sua vecchia struttura, divenne un simbolo di questa ambizione, pronto ad accogliere una tifoseria sempre più numerosa e appassionata.

Racconti e testimonianze dell’epoca

I cronisti sportivi e i tifosi dell’epoca ci hanno lasciato racconti vibranti, ricchi di emozioni e dettagli che restituiscono il clima di quegli anni. «Quegli anni furono una prova di carattere per il Napoli, dove il coraggio in campo rifletteva la tenacia della città tutta», così ricordava un vecchio giornalista di Cronache della Campania. Queste testimonianze sono la memoria viva di un’epoca che ha forgiato l’identità del club.

Un’opinione sul valore di quegli anni

Analizzando a posteriori, è evidente come gli anni ’60 rappresentarono un crocevia fondamentale per il Napoli. Non si trattò solo di risultati sportivi, ma di una crescita culturale e sociale del club. La perseveranza e la passione mostrate in quel decennio prepararono il terreno per i successi futuri, dimostrando che la forza della squadra nasce anche dall’amore e dalla resilienza della sua gente.

Collegamento con la crescita nei decenni successivi

Il percorso iniziato negli anni ’60 portò il Napoli a consolidarsi nelle stagioni successive, fino a raggiungere traguardi storici negli anni ’80 e oltre. La base di tifosi, la struttura organizzativa e la tradizione sportiva costruita in quegli anni furono fondamentali per le vittorie di campionato e le imprese europee. La storia del club non può prescindere da quel decennio di lotte e speranze.

Il quartiere Vomero e la città nel cuore del tifo

Il quartiere Vomero, con le sue strade animate e le piazze gremite, fu uno dei luoghi dove la passione per il Napoli si manifestava con maggiore intensità. I bar, le radio accese e le discussioni animate ricreavano un’atmosfera unica, fatta di attesa e di entusiasmo. La città intera si identificava con la squadra, rendendo ogni partita un evento sociale di grande rilievo.

Conclusione: un decennio che ha fatto la storia

Gli anni ’60 furono per il Napoli un periodo di formazione, di sfide e di sogni. La squadra e la città insieme scrissero un capitolo fondamentale della storia del calcio napoletano, aprendo la strada a un futuro luminoso. La memoria di quei giorni ci ricorda che il valore di un club si costruisce anche nei momenti più difficili, attraverso la passione, il coraggio e la determinazione.

Per approfondire altre tappe della storia del Napoli, visita i nostri approfondimenti su la storia del Napoli e scopri i volti dei grandi campioni attraverso le nostre schede dettagliate su i campioni del Napoli.

Serie: Calcio Napoli la Storia

Un viaggio tra le radici, le sfide e le vittorie del club partenopeo, raccontato dai suoi protagonisti e testimoni unici.

Questa storia fa parte della serie: Calcio Napoli la Storia
storia del calcio napoli, le origini, i campioni, i trionfi
Altri episodi nella serie
Non ci sono ancora altri episodi pubblicati.
]]>
https://www.cronachedellacampania.it/2026/04/il-napoli-degli-anni-60-sfide-passione-e-crescita-di-un/feed/ 1 843103
Le Origini del Calcio Napoli: Da Umili Radici a Orgoglio della Città https://www.cronachedellacampania.it/2026/04/le-origini-del-calcio-napoli-da-umili-radici-a-orgoglio/ https://www.cronachedellacampania.it/2026/04/le-origini-del-calcio-napoli-da-umili-radici-a-orgoglio/#comments Thu, 16 Apr 2026 07:30:00 +0000 https://www.cronachedellacampania.it/?p=842702 Scopri come il Calcio Napoli nacque nel 1926, tra storia, passione e il legame indissolubile con la città partenopea.]]>

Nel cuore di Napoli nasce una leggenda

Nel cuore pulsante di Napoli, tra vicoli stretti, il profumo del mare e la vivacità delle piazze, nacque una passione destinata a cambiare il volto dello sport cittadino. Era il 1926 quando il Calcio Napoli prese vita, tra grandi speranze e sfide inaspettate. Ricordo ancora, da bambino, le chiacchiere sulla prima partita ufficiale, l’entusiasmo nei volti di chi sognava un futuro diverso per il calcio a Napoli, lontano dalle grandi metropoli del nord Italia.

Fondazione nel 1926: un contesto storico e sociale delicato

Il Napoli vide la luce in un periodo storico complesso: l’Italia degli anni Venti, con le sue tensioni politiche, economiche e sociali, determinava anche l’identità di una città come Napoli. Una città vibrante ma segnata da grandi disparità, dove il calcio iniziava a diventare un’occasione di riscatto e aggregazione. La nascita del club rappresentava per molti un simbolo di speranza e orgoglio civico.

I fondatori e i primi dirigenti: gli artefici di un sogno

Il merito di aver dato vita al Calcio Napoli va attribuito a un gruppo di appassionati dirigenti e sportivi locali che, con tenacia, riuscirono a mettere insieme risorse e volontà. Tra questi, spiccano i nomi di Giorgio Ascarelli, che non solo fu un imprenditore lungimirante ma anche una figura centrale nel finanziare il progetto, e altri personaggi che credettero fermamente nella nascita di una squadra che potesse rappresentare tutta la città.

Lo stadio e le prime sfide sul campo

Le prime partite ufficiali si disputarono in impianti modesti, ben lontani dagli attuali stadi moderni. Inizialmente il Napoli si cimentò nei campionati regionali, facendo fatica ma guadagnandosi lentamente il rispetto degli avversari. Lo stadio, allora semplice e popolare, divenne ben presto un punto di riferimento per i tifosi, un luogo dove la città poteva riunirsi e sognare insieme.

Il legame con Napoli e i tifosi nelle prime stagioni

Sin dall’inizio, il Calcio Napoli si radicò profondamente nel tessuto sociale della città. Il club non era solo una squadra di calcio, ma un simbolo di identità per i napoletani. Le prime stagioni videro una partecipazione calorosa del pubblico, che riempiva gli spalti con entusiasmo e orgoglio, facendo emergere quel legame unico tra squadra e città che ancora oggi caratterizza il Napoli.

I primi campioni e il loro impatto nel calcio partenopeo

Tra i primi giocatori che fecero la storia del Napoli figurano atleti che, pur lontani dalla fama odierna, rappresentarono le fondamenta su cui costruire il futuro. Questi campioni, con passione e sacrificio, contribuirono a plasmare un’identità calcistica ben definita, facendo sì che il Napoli fosse percepito non solo come una squadra, ma come un vero e proprio orgoglio cittadino.

Il calcio come strumento di identità cittadina

Il calcio a Napoli iniziò presto a diventare molto più di uno sport: era uno strumento di comunicazione sociale, un modo per aggregare una popolazione spesso divisa da differenze economiche e culturali. La squadra divenne un luogo dove la città si riconosceva e si esprimeva, un modo per affermare un’identità forte e resistente, pronta a lottare sul campo e nella vita.

Curiosità e aneddoti dalle prime partite

Tra le tante storie che hanno caratterizzato i primi passi del Napoli, un aneddoto curioso riguarda la prima partita ufficiale: giocata in condizioni climatiche difficili, con un pubblico che sfidava la pioggia e il freddo pur di sostenere la squadra. È in queste immagini, fatte di passione e semplicità, che si coglie il vero spirito del Napoli, fatto di dedizione e amore incondizionato.

Il ruolo dei quartieri e dello stadio Diego Armando Maradona

Quartieri storici come la Sanità hanno sempre rappresentato un cuore pulsante della tifoseria napoletana, contribuendo a costruire quel senso di appartenenza che lega il club ai suoi sostenitori. Oggi, lo Stadio Diego Armando Maradona – un monumento moderno che porta il nome del più grande campione della città – continua a essere il luogo sacro dove si celebra questa storia lunga quasi un secolo.

Come il Napoli moderno riflette ancora quelle origini

Il Napoli odierno, con la sua squadra competitiva e la sua tifoseria appassionata, riflette ancora profondamente quelle origini umili e popolari. La filosofia del club, fatta di coraggio, umiltà e cuore, nasce proprio da quei primi anni difficili ma ricchi di speranza, confermando una continuità storica e culturale che rende il Napoli unico nel panorama calcistico nazionale e internazionale.

Un legame indissolubile tra squadra e città

“Il Napoli non è solo una squadra, è l’anima stessa della città che batte forte in ogni vittoria e sconfitta.” Questa frase riassume il sentimento che lega da sempre il club al suo pubblico, un rapporto fatto di emozioni forti e di un’identità condivisa, nata e cresciuta proprio nel contesto unico di Napoli.

Per approfondire

Vuoi scoprire di più sulle epiche sfide e i campioni che hanno fatto grande il Napoli? Visita il nostro approfondimento sulla storia dei campioni del Napoli o il racconto delle grandi trionfi del club.


Serie: Calcio Napoli la Storia

Seguici per scoprire insieme le tappe fondamentali, i protagonisti e le emozioni di un viaggio lungo quasi un secolo, dentro e fuori dal campo.

Questa storia fa parte della serie: Calcio Napoli la Storia
storia del calcio napoli, le origini, i campioni, i trionfi
Altri episodi nella serie
Non ci sono ancora altri episodi pubblicati.
]]>
https://www.cronachedellacampania.it/2026/04/le-origini-del-calcio-napoli-da-umili-radici-a-orgoglio/feed/ 1 842702
Beukema carica il Napoli: «Vogliamo chiudere bene prima della sosta» https://www.cronachedellacampania.it/2026/03/beukema-carica-il-napoli-vogliamo-chiudere-bene-prima-della-sosta/ https://www.cronachedellacampania.it/2026/03/beukema-carica-il-napoli-vogliamo-chiudere-bene-prima-della-sosta/#comments Tue, 17 Mar 2026 18:17:41 +0000 https://www.cronachedellacampania.it/?p=834178 Il Napoli guarda avanti senza distrazioni e con un obiettivo ben chiaro: chiudere al meglio il ciclo prima della sosta per le Nazionali. Alla vigilia della trasferta contro il Cagliari, in programma venerdì sera, è Sam Beukema a suonare la carica, indicando la direzione della squadra. «Sappiamo l’importanza di questa...]]>

Il Napoli guarda avanti senza distrazioni e con un obiettivo ben chiaro: chiudere al meglio il ciclo prima della sosta per le Nazionali. Alla vigilia della trasferta contro il Cagliari, in programma venerdì sera, è Sam Beukema a suonare la carica, indicando la direzione della squadra.

«Sappiamo l’importanza di questa partita e vogliamo chiudere bene questo ciclo prima della sosta», spiega il difensore, sottolineando un approccio pragmatico. «Ragioniamo partita dopo partita ed il nostro obiettivo è andare in Champions League: al termine di queste nove partite poi vedremo dove saremo». Un messaggio chiaro, che riflette ambizione ma anche la volontà di mantenere alta la concentrazione senza fare calcoli prematuri.

Per Beukema, arrivato in una realtà esigente, il peso della maglia non è un problema, ma uno stimolo. «Quando vai in un big club come il Napoli c’è sempre pressione. A Bologna avevo già esperienza internazionale, ma qui devi provare a vincere sempre», racconta. «A me queste pressioni piacciono molto, è per questo che sono diventato un calciatore».

Il difensore olandese torna poi sulla vittoria contro il Lecce, analizzando con lucidità luci e ombre della prestazione. «Sapevamo che giocano molto con le palle lunghe, era importante vincere le seconde palle. Nel primo tempo dovevamo fare meglio, anche perché loro hanno trovato il gol e dobbiamo crescere sulle palle inattive». Un aspetto su cui la squadra ha già lavorato: «Abbiamo analizzato il gol subito e sappiamo dove migliorare».

La reazione, però, è arrivata nella ripresa. «Nel secondo tempo si è vista la differenza: abbiamo vinto più seconde palle e trovato più spazi. Potevamo segnare ancora di più, ma abbiamo fatto bene e siamo contenti della vittoria».

Infine, un passaggio su Politano, tornato al gol dopo un periodo di attesa. «Era contentissimo e lo eravamo anche noi. Si è sempre allenato bene, era solo questione di tempo». E un dato che racconta la forza collettiva: «Abbiamo già 19 giocatori andati a segno, significa che siamo una squadra vera».

]]>
https://www.cronachedellacampania.it/2026/03/beukema-carica-il-napoli-vogliamo-chiudere-bene-prima-della-sosta/feed/ 1 834178
Napoli, Conte: obiettivo resta la Champions https://www.cronachedellacampania.it/2026/03/napoli-conte-obiettivo-resta-la-champions/ https://www.cronachedellacampania.it/2026/03/napoli-conte-obiettivo-resta-la-champions/#comments Sat, 14 Mar 2026 19:45:18 +0000 https://www.cronachedellacampania.it/?p=833493 Dopo la vittoria per 2-1 del Napoli sul Lecce, il tecnico azzurro Antonio Conte ha analizzato la partita ai microfoni di DAZN, soffermandosi soprattutto sulla gestione dei giocatori rientrati da lunghi infortuni. “Stiamo recuperando calciatori che sono rimasti fuori per diversi mesi – ha spiegato l’allenatore – e non possiamo...]]>

Dopo la vittoria per 2-1 del Napoli sul Lecce, il tecnico azzurro Antonio Conte ha analizzato la partita ai microfoni di DAZN, soffermandosi soprattutto sulla gestione dei giocatori rientrati da lunghi infortuni.

“Stiamo recuperando calciatori che sono rimasti fuori per diversi mesi – ha spiegato l’allenatore – e non possiamo pensare che tornino immediatamente con la condizione migliore”. Conte ha citato anche la scelta iniziale di schierare André‑Frank Zambo Anguissa, poi sostituito nel corso del match.

“Oggi ho deciso di partire con Anguissa, ma quando eravamo sotto nel punteggio ho fatto un cambio. È normale: chi rientra ha bisogno di tempo per riabituarsi all’intensità”.

“I ragazzi devono fidarsi delle mie sensazioni”

Nel suo intervento, Conte ha sottolineato il ruolo delle decisioni tecniche nella gestione del gruppo.

“I ragazzi devono avere fiducia nelle sensazioni che ho. Io mi prendo le responsabilità delle scelte, ma tutti devono lavorare per il bene comune, che è il Napoli”.

Il tecnico ha poi ribadito la priorità della stagione: “Dobbiamo pensare a qualificarci in Champions League”.

La soddisfazione per la media punti

Conte ha anche commentato i numeri della sua gestione sulla panchina partenopea.

“Non sono uno che guarda molto le statistiche, preferisco concentrarmi sul lavoro quotidiano. Però scoprire di avere una media punti di 2,21 non può che farmi piacere”.

Il momento di paura per Banda

Nel finale della gara non è mancato un momento di forte apprensione legato alle condizioni dell’attaccante del Lecce Lameck Banda.

“Stavo preparando la barriera su una punizione quando ho visto che si stava accasciando – ha raccontato Conte –. Mi sono preoccupato quando ho notato che aveva la mano sul petto e mi sono precipitato in campo per chiamare i soccorsi”.

Il tecnico ha concluso con un augurio: “Spero che ora stia bene e che possa riprendersi al più presto”.

]]>
https://www.cronachedellacampania.it/2026/03/napoli-conte-obiettivo-resta-la-champions/feed/ 1 833493
Napoli e i suoi simboli: dal Vesuvio al tifo azzurro https://www.cronachedellacampania.it/2026/03/__trashed-34/ https://www.cronachedellacampania.it/2026/03/__trashed-34/#comments Sat, 14 Mar 2026 13:30:47 +0000 Napoli non è solo una città: è un universo simbolico unico, dove storia millenaria, tradizioni popolari e passioni collettive si intrecciano quotidianamente. Parlare di simboli Napoli significa attraversare un patrimonio culturale ricco e stratificato che va dal Vesuvio, emblema naturale della città, ai riti e alle iconografie legate alla squadra...]]>

Napoli non è solo una città: è un universo simbolico unico, dove storia millenaria, tradizioni popolari e passioni collettive si intrecciano quotidianamente. Parlare di simboli Napoli significa attraversare un patrimonio culturale ricco e stratificato che va dal Vesuvio, emblema naturale della città, ai riti e alle iconografie legate alla squadra locale e al tifo Napoli, che trasforma ogni partita in una festa di identità e appartenenza.

Il Vesuvio, le tradizioni popolari e altri segni di identità

Il Vesuvio è indubbiamente il primo dei simboli Napoli. La sua sagoma è diventata emblema della città stessa: maestoso, silenzioso e carico di significato, è presente su cartoline, quadri e souvenir come espressione della tensione tra bellezza naturale e potenziale catastrofe che ha segnato la storia del golfo.

Accanto alla presenza fisica del Vesuvio, altri simboli popolari raccontano Napoli attraverso mito e superstizione. Ad esempio, il cornicello rosso, un amuleto portafortuna tradizionale, è considerato un potente antidoto contro il malocchio e rappresenta la fusione tra cultura greca-romana e credenze popolari partenopee.

All’interno del tessuto urbano, simboli minori ma profondamente radicati, come la fontana della Sirena Parthenope, figura mitica legata alla fondazione leggendaria della città, incarnano l’identità collettiva nei luoghi quotidiani.

Infine, iniziative recenti come l’installazione della scritta monumentale “Napoli” in piazza Municipio contribuiscono a rafforzare e mostrare all’esterno simboli Napoli in chiave contemporanea, celebrando il patrimonio culturale attraverso linguaggi visivi moderni.

Il calcio come simbolo collettivo: storia, colori e tifo Napoli

Quando si parla di tifo Napoli, si entra nel cuore pulsante dell’identità cittadina contemporanea. La squadra di calcio SSC Napoli non è soltanto un club sportivo, ma un simbolo sociale in cui confluiscono senso di appartenenza, memoria storica e passione condivisa.

Il club ha radici profonde nel territorio: i colori azzurri evocano il mare del golfo e il cielo partenopeo, mentre il semplice emblematico “N” sullo stemma, rinnovato nel 2024 per una versione più moderna e riconoscibile, parla di continuità identitaria radicata nei valori napoletani. La storia dei simboli del club, compresi soprannomi come I Partenopei o I Ciucci (quest’ultimo nato da una battuta di spirito e trasformato dai tifosi in emblema di resilienza) racconta l’evoluzione di un popolo che sa reinventare la propria immagine in chiave ironica e affettuosa.

Il tifo Napoli vero e proprio è un fenomeno collettivo che va oltre le gradinate dello stadio. Gruppi organizzati di ultras, cori come “‘O surdato ‘nnammurato”, e riti pre-partita nelle strade, nei bar e nei quartieri, trasformano ogni match in un evento di coesione sociale. Questa passione è spesso centrata anche sulla figura di Diego Maradona, il calciatore che ha segnato un’epoca e rappresenta per molti tifosi un simbolo d’appartenenza e orgoglio identitario.

Simboli e narrazioni nella Napoli contemporanea

I simboli Napoli non restano confinati alla storia o alla tifoseria: essi si intrecciano nella narrazione culturale globale della città. Le immagini della pizza, del mare, dei murales dedicati a Maradona, di San Gennaro e di Pulcinella, compongono un mosaico simbolico che comunica a turisti e residenti l’essenza di Napoli, complessa, ironica, passionale.

In epoca recente, anche la moda e il merchandising sportivo incorporano simboli tradizionali (ad esempio elementi folkloristici nascosti nelle divise), creando nuove connessioni tra cultura pop e identità storica.

Questi segni, antichi e moderni, mostrano come Napoli riesca a trasmettere la sua unicità attraverso un linguaggio simbolico dinamico, capace di rinnovarsi pur mantenendo forti legami con il passato.

Simboli Napoli: dal Vesuvio al tifo Napoli, identità e passione

Scopri i simboli Napoli più iconici: dal Vesuvio al tifo Napoli, tra storia, cultura popolare e passione calcistica che unisce la città.

Simboli Napoli

Tifo Napoli

]]>
https://www.cronachedellacampania.it/2026/03/__trashed-34/feed/ 1 820688
Le partite che hanno fatto la storia del Napoli https://www.cronachedellacampania.it/2026/03/__trashed-33/ https://www.cronachedellacampania.it/2026/03/__trashed-33/#comments Fri, 13 Mar 2026 13:30:45 +0000 https://www.cronachedellacampania.it/?p=820689 Il Napoli è una delle squadre più affascinanti e amate del calcio italiano. Fondata nel 1926, ha vissuto una storia fatta di sofferenze, rinascite e momenti indimenticabili che hanno segnato per sempre la memoria dei tifosi. In questo articolo ripercorriamo alcune delle Napoli partite storiche che hanno scritto pagine indelebili...]]>

Il Napoli è una delle squadre più affascinanti e amate del calcio italiano. Fondata nel 1926, ha vissuto una storia fatta di sofferenze, rinascite e momenti indimenticabili che hanno segnato per sempre la memoria dei tifosi. In questo articolo ripercorriamo alcune delle Napoli partite storiche che hanno scritto pagine indelebili nel libro della società, con un focus su grandi vittorie Napoli che hanno segnato epoche diverse del club.

Il primo scudetto (1986‑87): l’impresa che cambiò la storia

Tra le Napoli partite storiche più celebri della storia del club c’è senza dubbio la stagione 1986‑87, quella del primo scudetto nella storia del Napoli. Dopo anni di lotte nel massimo campionato italiano, la squadra guidata da Diego Armando Maradona centrò uno storico successo, diventando la prima squadra del Sud Italia a vincere il titolo nazionale.

In un’annata caratterizzata da grande continuità di risultati, il Napoli ottenne 42 punti (all’epoca si assegnavano 2 punti per vittoria) e mantenne una posizione di vertice per tutta la stagione. La festa dei tifosi esplose quando, con una giornata d’anticipo, il Napoli conquistò matematicamente lo scudetto tra gli applausi del pubblico del San Paolo.

Quell’anno gli azzurri non solo vinsero lo scudetto, ma conquistarono anche la Coppa Italia, realizzando un memorabile double, impresa rara e storica per una squadra italiana in quegli anni, suggellata da un 4‑0 complessivo nella finale contro l’Atalanta.

La Coppa UEFA 1989: Napoli conquista l’Europa

Una delle grandi vittorie Napoli è certamente la conquista della Coppa UEFA nella stagione 1988‑89, il primo grande titolo internazionale della società. In una competizione molto competitiva, il Napoli superò numerosi avversari prima di arrivare alla doppia finale contro il VfB Stuttgart.

La finale fu giocata su due partite: il Napoli vinse 2‑1 al San Paolo davanti a oltre 80.000 tifosi, mentre al ritorno riuscì a ottenere un 3‑3 al Neckarstadion di Stoccarda, sufficiente per un aggregate di 5‑4 che consegnò la coppa all’Italia.

Quella vittoria non rappresentò solo un successo sportivo, ma un simbolo dell’affermazione europea della squadra partenopea: gli azzurri divennero la prima italiana a vincere la UEFA Cup dopo Juventus nel 1977 e aprirono un’era di prestigio per le squadre italiane in competizioni internazionali.

La Coppa Italia 2014: una finale dominata

Un’altra grande vittoria Napoli degna di entrare tra le Napoli partite storiche è la finale di Coppa Italia del 3 maggio 2014, giocata allo Stadio Olimpico di Roma. In quell’occasione il Napoli affrontò la Fiorentina e si impose con un netto 3‑1, grazie a una prestazione convincente e alla doppietta di Lorenzo Insigne nei primi minuti.

Nonostante l’espulsione di Gökhan Inler nella ripresa, gli azzurri gestirono il vantaggio fino al triplice fischio, con un gol nel finale anche di Dries Mertens, suggellando una delle vittorie più importanti in Coppa Italia degli ultimi anni.

Questa affermazione non solo aggiunse un titolo nazionale al palmarès, ma consolidò il rendimento competitivo del Napoli in finale, confermando la capacità della squadra di imporsi nei momenti chiave delle competizioni italiane.

Napoli partite storiche

Grandi vittorie Napoli

Le partite che hanno fatto la storia del Napoli | Grandi vittorie Napoli

Scopri le Napoli partite storiche che hanno segnato la storia del club azzurro: dallo scudetto del 1987 alla Coppa UEFA del 1989 e alla Coppa Italia 2014, le grandi vittorie Napoli che hanno emozionato i tifosi.

]]>
https://www.cronachedellacampania.it/2026/03/__trashed-33/feed/ 1 820689
Come si è evoluto nel tempo lo stemma del Napoli, dal cavallo rampante alla N stilizzata https://www.cronachedellacampania.it/2025/09/storia-stemma-napoli-calcio-dal-cavallo-rampante-alla-n-stilizzata/ https://www.cronachedellacampania.it/2025/09/storia-stemma-napoli-calcio-dal-cavallo-rampante-alla-n-stilizzata/#comments Thu, 04 Sep 2025 09:16:25 +0000 https://www.cronachedellacampania.it/?p=789779 La storia dello stemma del Napoli calcio è un viaggio attraverso l’identità visiva e sentimentale della città: un cammino che inizia con un cavallo rampante, simbolo evocativo del Regno borbonico, e arriva alla “N” stilizzata, icona moderna del club. Nell’annata 1926/1927, la neonata Associazione Calcio Napoli adottò uno stemma in...]]>

La storia dello stemma del Napoli calcio è un viaggio attraverso l’identità visiva e sentimentale della città: un cammino che inizia con un cavallo rampante, simbolo evocativo del Regno borbonico, e arriva alla “N” stilizzata, icona moderna del club. Nell’annata 1926/1927, la neonata Associazione Calcio Napoli adottò uno stemma in cui un fiero cavallo rampante, detto “Corsiero del Sole”, era sospeso su un pallone da calcio, incorniciato da un ovale azzurro e aleggiava il blasone della nobiltà partenopea.

Tuttavia, la squadra partì con un campionato disastroso, appena un punto e 61 gol subiti in 18 partite, situazione che spinse i tifosi a ironizzare comparando il cavallo ad un povero asinello sofferente, il celebre “ciuccio” di don Domenico Ascione. Da quel momento, il simbolo perse immediatamente la sua nobiltà e perse il suo fascino, diventando figura di dileggio popolare.

Nel giro di pochi anni, l’emblema cambiò radicalmente: nel 1927 sparirono cavallo e ovale, e nel 1928 venne introdotto un design più essenziale, ovvero un cerchio azzurro bordato di oro con una grande N borbonica al centro, riferimento alla città e al suo patrimonio artistico, come le pregiate porcellane di Capodimonte.

Dal cavallo rampante alla nascita della “N”

L’adozione del cavallo rampante, simbolo storico della resistenza napoletana già dai tempi svevi (come racconta la leggenda legata al “Corsiero del Sole”), conferiva originalmente allo stemma una solennità quasi araldica. Tuttavia, il suo utilizzo durò appena un anno: la débâcle sportiva della squadra indusse la tifoseria a raccontare che quel nobile destriero fosse più simile “’o ciuccio ‘e Fichella”, venendo deriso come un povero asino malconcio.

Nel 1928, per distanziarsi definitivamente dal passato umiliante, la società adottò un nuovo logo: un cerchio azzurro con bordo dorato e una N centrale, che divenne simbolo distintivo della squadra e della città. Questo è il momento in cui il logo Napoli inizia a delinearsi.

Seguendo gli sviluppi successivi: negli anni ’60, con la trasformazione in SSC Napoli, si rielaborò il simbolo riprendendo in parte lo stile araldico del cavallo del 1926 (in uno stemma più articolato), ma nel 1964 fu di nuovo adottata la N inserita in ovale o cerchio, con l’aggiunta della scritta “S.S.C. Napoli” e talvolta i tricolori

L’evoluzione post-bellica: scudi, ovali e ritorno alla “N”

Gli anni successivi alla Seconda Guerra Mondiale segnarono per il logo Napoli una fase grafica intensamente sperimentale, in cui il club cercò di riflettere attraverso il proprio emblema il rinnovamento societario e l’aspirazione a una riconoscibilità immediata.

All’inizio degli anni ’40, il semplice cerchio con la “N” gialla fu sostituito da una forma a scudo che esplicitava un ritorno a uno stile più forte e corporativo, il celebre cerchio fu abbandonato, mentre il simbolo prescelto divenne un “N” bianca su fondo azzurro in un contorno giallo piuttosto marcato. In quel periodo, si iniziò anche a sperimentare varie tonalità di azzurro e bianco per adattarsi alle divise di gioco, fino ad arrivare a versioni semplificate e geometriche del carattere tipografico della lettera.

Negli anni ’50 e ’60 il design del logo Napoli si fece ancora più pulito e minimal, spesso inserendo la scritta “S.S.C. Napoli” lungo il bordo inferiore del cerchio o dello scudo, e applicando praticità di riproduzione su merchandising, maglie e materiali promozionali. Sotto l’era Maradona (anni ’80), il logo assunse una valenza mitica: tonalità più accese, cerchio più marcato e inserimento del nome completo della società ne amplificarono la presenza iconica all’interno dell’identità sportiva.

Restyling moderni: semplificazione e tipografia di oggi

L’avvento del nuovo millennio segnò una fase di restyling moderni dove il design del logo diventa più contemporaneo e flessibile, rispecchiando la crescente attenzione all’immagine del brand.

Nel 2002 e nei successivi anni, la “N” bianca fu nuovamente valorizzata: si impiegò uno schema circolare con tonalità blu scuro o blu notte, accompagnato da scritte minimaliste come “Napoli Soccer” o la denominazione “S.s.c. Napoli”.

Ma il vero cambiamento arrivò tra gli anni 2010 e 2020: si assistette al diffondersi di versioni “outline” del logo, semplici contorni su maglie, packaging e materiali digitali, senza riempimenti cromatici.

Nel 2024, il club ufficializzò un nuovo logo: doppio cerchio concentrici con la “N” al centro, completamente minimal e intercambiabile nei colori, adattabile a qualsiasi sfondo (bianco, azzurro o blu navy). Questa evoluzione trova compimento nelle divise della stagione 2025-2026, dove il logo è declinato anche in effetti 3D e integrato nel design generale della maglia, a sottolineare l’identità moderna del club.

]]>
https://www.cronachedellacampania.it/2025/09/storia-stemma-napoli-calcio-dal-cavallo-rampante-alla-n-stilizzata/feed/ 1 789779
Calcio Napoli, Tommaso Bianchini nominato direttore generale dell’Area Business https://www.cronachedellacampania.it/2025/07/calcio-napoli-tommaso-bianchini-nominato-direttore-generale-dellarea-business/ https://www.cronachedellacampania.it/2025/07/calcio-napoli-tommaso-bianchini-nominato-direttore-generale-dellarea-business/#respond Mon, 14 Jul 2025 17:49:09 +0000 https://www.cronachedellacampania.it/?p=782756 Napoli – Il Napoli ha annunciato oggi la nomina di Tommaso Bianchini a direttore generale dell’Area Business . Lo comunica il club azzurro attraverso una nota ufficiale firmata dal presidente Aurelio De Laurentiis. Bianchini, che risponderà direttamente a De Laurentiis e all’amministratore delegato Andrea Chiavelli, avrà la responsabilità delle aree...]]>

Napoli – Il Napoli ha annunciato oggi la nomina di Tommaso Bianchini a direttore generale dell’Area Business

. Lo comunica il club azzurro attraverso una nota ufficiale firmata dal presidente Aurelio De Laurentiis. Bianchini, che risponderà direttamente a De Laurentiis e all’amministratore delegato Andrea Chiavelli, avrà la responsabilità delle aree Marketing, Commerciale, Merchandising e Stadium Innovation. T

ra i suoi obiettivi principali, quello di innovare e ottimizzare l’utilizzo dello stadio, puntando a nuove opportunità di sviluppo per il club.

“Il presidente De Laurentiis è lieto di dare il benvenuto a Bianchini, che porterà la sua esperienza per rafforzare le strategie di crescita dell’Area Business”, si legge nel comunicato ufficiale del Napoli.

]]>
https://www.cronachedellacampania.it/2025/07/calcio-napoli-tommaso-bianchini-nominato-direttore-generale-dellarea-business/feed/ 0 782756
Il Napoli presenta la nuova maglia: c’è il colletto, si rinnova anche il logo https://www.cronachedellacampania.it/2024/07/napoli-nuova-maglia/ https://www.cronachedellacampania.it/2024/07/napoli-nuova-maglia/#respond Fri, 05 Jul 2024 13:13:05 +0000 https://www.cronachedellacampania.it/?p=670582 La SSC Napoli ha presentato ufficialmente la sua nuova maglia e un rinnovato logo attraverso uno spettacolare video manifesto pubblicato su YouTube.Il club ha dichiarato: “La SSC Napoli rivela la sua nuova identità con un video manifesto che celebra l’orgoglio di appartenere a Napoli. Partendo dalla rivisitazione del logo, con...]]>

La SSC Napoli ha presentato ufficialmente la sua nuova maglia e un rinnovato logo attraverso uno spettacolare video manifesto pubblicato su YouTube.Il club ha dichiarato: “La SSC Napoli rivela la sua nuova identità con un video manifesto che celebra l’orgoglio di appartenere a Napoli.

Partendo dalla rivisitazione del logo, con la sua tradizionale N napoleonica resa in una versione più minimalista e contemporanea.L’essenza della nuova grafica punta a un design più pulito ed elegante, dominato da tonalità monocromatiche”.

La vera novità, però, è rappresentata dalla tipografia.

Per la prima volta, il club introduce una font proprietaria, denominata “BE NAPOLI”, che incarna perfettamente le caratteristiche del marchio e della città stessa.Napoli è una città multiculturale in continua evoluzione, un melting pot di stili e culture che si riflette in questa nuova tipografia unica e dinamica, ispirata alla ricca eredità culturale partenopea.

“La tipografia BE NAPOLI è un omaggio alla città e alla sua vibrante energia”, ha commentato il club. “Con linee sfuggenti e diagonali audaci, richiama le visioni dinamiche di Napoli.

I caratteri spessi e compatti simboleggiano la determinazione e l’unità della squadra, mentre gli angoli acuti riflettono grinta e velocità”.

Il video manifesto, ispirato all’orgoglio napoletano, esplora le meraviglie della città, dai quartieri più iconici come i Quartieri Spagnoli e la Sanità, fino a luoghi meno conosciuti ma altrettanto significativi come l’Ipogeo dei Cristallini e l’Ospedale delle Bambole.Un racconto visivo che celebra la diversità e l’inclusività, fondendo tradizione e innovazione, superstizione e razionalità.

La presentazione della nuova maglia Home si è svolta nell’incantevole scenario del Parco archeologico del Pausilypon e nella suggestiva Grotta di Seiano.L’azzurro Napoli, iconico e amato dai tifosi, continua a essere il colore dominante della divisa per la stagione 2024/2025.

La maglia, frutto della collaborazione con EA7, Global Technical Partner del club, si distingue per il suo design elegante e l’attenzione ai dettagli retrò, inclusi il collo a V e il patch del logo SSC Napoli in 3D.

“La nuova maglia presenta un pattern in poliestere sport performance, con un design a caldo dell’iconica ‘N’ in tutto il tessuto”, ha spiegato il club. “Dettagli in contrasto e materiali stretch assicurano comfort e ergonomia, mentre uno scudetto speciale con ologramma digitale completa il look autentico della maglia”. La maglia Home sarà disponibile per l’acquisto sull’official online store del club a partire dalle 19:26 di oggi.

]]>
https://www.cronachedellacampania.it/2024/07/napoli-nuova-maglia/feed/ 0 670582
A Napoli oggi ricorda Diego Armando Maradona nel suo stadio https://www.cronachedellacampania.it/2023/11/napoli-maradona/ https://www.cronachedellacampania.it/2023/11/napoli-maradona/#respond Sat, 25 Nov 2023 07:43:06 +0000 https://www.cronachedellacampania.it/?p=616329 Atalanta-Napoli non sarà una gara normale per il popolo azzurro: il match di oggi assume, infatti, un significato particolare per ogni amante della squadra partenopea.Esattamente 3 anni fa si spegneva Diego Armando Maradona, il più grande calciatore di tutti i tempi, idolo incontrastato di Napoli. La città partenopea si appresta...]]>

Atalanta-Napoli non sarà una gara normale per il popolo azzurro: il match di oggi assume, infatti, un significato particolare per ogni amante della squadra partenopea.Esattamente 3 anni fa si spegneva Diego Armando Maradona, il più grande calciatore di tutti i tempi, idolo incontrastato di Napoli.

La città partenopea si appresta a ricordare il proprio eroe e simbolo.Il suo ricordo è indelebile e batte fortissimo nel cuore di tutti i tifosi napoletani.

A tre anni dalla scomparsa, Napoli celebra ancora una volta Diego Armando Maradona.Appuntamento allo stadio intitolato al Pibe de Oro.

A partire dalle ore 21 è in programma la celebrazione dell’ex campione argentino con una ‘torciata’ in suo ricordo.

“Un evento che richiamerà migliaia di tifosi del Napoli come ogni anno subito dopo il match che gli azzurri disputeranno a Bergamo”.A comunicarlo sono gli ultras partenopei, promotori dell’iniziativa per il secondo anno consecutivo, che hanno annunciato una grande flashmob con torce alla mano per illuminare la notte di Napoli e per commemorare la scomparsa di Diego Armando Maradona.

Gli ultras invitano tutti i tifosi a presentarsi all’esterno dello stadio che oggi porta il nome del “Pibe de Oro”. “L’iniziativa vuole essere un tributo vibrante e passionale a colui che ha scritto pagine indelebili nella storia del calcio e nel cuore di Napoli.La torcia, simbolo di passione e amore per il calcio, sarà accesa per illuminare il cammino dei ricordi, sottolineando che il numero 10 non è solo un numero di maglia, ma un’eredità del popolo napoletano.

Il Napoli, insieme ai suoi tifosi, continuerà a onorare la memoria di Diego Armando Maradona, celebrando non solo il giocatore straordinario, ma anche l’uomo che ha lasciato un segno indelebile nella storia del calcio mondiale”

]]>
https://www.cronachedellacampania.it/2023/11/napoli-maradona/feed/ 0 616329
Napoli, apertura speciale della Cappella San Severo per la vittoria dello Scudetto https://www.cronachedellacampania.it/2023/05/cappella-san-severo-scudetto/ https://www.cronachedellacampania.it/2023/05/cappella-san-severo-scudetto/#respond Mon, 22 May 2023 12:30:22 +0000 https://www.cronachedellacampania.it/?p=579010 Apertura straordinaria del Museo Cappella Sansevero in occasione della vittoria del terzo scudetto del Napoli. Martedì 6 giugno, due giorni dopo l’ultima giornata del campionato di Serie A concluso con la vittoria degli azzurri, la Cappella Sansevero sarà eccezionalmente aperta al pubblico dalle ore 9 alle 19 (ultimo ingresso alle...]]>

Apertura straordinaria del Museo Cappella Sansevero in occasione della vittoria del terzo scudetto del Napoli. Martedì 6 giugno, due giorni dopo l’ultima giornata del campionato di Serie A concluso con la vittoria degli azzurri, la Cappella Sansevero sarà eccezionalmente aperta al pubblico dalle ore 9 alle 19 (ultimo ingresso alle 18.30) e non rispetterà quindi il consueto giorno di chiusura settimanale.

L’ingresso sarà consentito con tariffa ridotta a 3 euro per tutti i visitatori a partire dai 4 anni compiuti e sarà possibile scattare foto all’interno di tutti gli spazi museali. Le visite guidate non saranno consentite, ma si potrà noleggiare l’audioguida. Per accedere al Museo è obbligatoria la prenotazione, che si effettua esclusivamente online dal sito www.museosansevero.it, collegandosi alla piattaforma di prenotazione dedicata all’evento.

Il ricavato dalla vendita dei ticket andrà interamente devoluto all’associazione Asd Spartak San Gennaro, scuola calcio popolare e gratuita che da anni lavora con i bambini e i ragazzi dei quartieri del centro antico di Napoli. Il museo invita “i gentili visitatori che verranno in visita il 6 giugno a indossare un accessorio del Napoli (sciarpa, cappello, spilla o altro)”.

]]>
https://www.cronachedellacampania.it/2023/05/cappella-san-severo-scudetto/feed/ 0 579010
Spalletti: “Il Napoli può aprire un ciclo, tutto ok con Dela, ne parlerà lui” https://www.cronachedellacampania.it/2023/05/spalletti-napoli-ciclo/ https://www.cronachedellacampania.it/2023/05/spalletti-napoli-ciclo/#respond Sat, 13 May 2023 11:43:52 +0000 https://www.cronachedellacampania.it/?p=577332 “Fare riunione nei ristoranti di Napoli non è facile, si è sempre con la bocca piena di cose bellissime (ride, ndr), ma quello che è venuto fuori è tutto molto chiaro ma i dettagli è giusto li esponga la società quando riterrà opportuno.Non posso passare avanti, tutte le domande su...]]>

“Fare riunione nei ristoranti di Napoli non è facile, si è sempre con la bocca piena di cose bellissime (ride, ndr), ma quello che è venuto fuori è tutto molto chiaro ma i dettagli è giusto li esponga la società quando riterrà opportuno.Non posso passare avanti, tutte le domande su questa cosa avranno la stessa risposta.

Ne parlerà la società, quando vorrà, è corretto così.

Io posso solo continuare a lavorare ed a finire nel migliore dei modi il lavoro”. Luciano Spalletti, alla vigilia della trasferta di Monza, archivia in una battuta la questione contratto.Tutto chiaro, ne parlerà la società.

Dopo Udine e la partita con la Fiorentina c’è timore di un calo di concentrazione.Obiettivo i 91 punti di Sarri: “Bisogna giocarsi le partite, è lì che ritroviamo il nostro lavoro, l’affetto di tutti gli sportivi, ci saranno stadi pieni da tutte le parti con pezzi di cuore azzurro dove noi passeremo per cui bisogna far bene.

Dobbiamo alternare festeggiamenti, come giusto che sia, ma anche giocare un buon calcio come giusto fare anche se abbiamo chiuso in anticipo questo discorso.

Ho trovato perfetto quanto fatto con la Fiorentina dopo due notti di festeggiamenti, tutti si aspettavano una squadra più scarica”. Qual è il momento preciso in cui ha avuto la consapevolezza del tricolore? “Ne sono tanti, si parla sempre di costruzione da quando siamo partiti.

“Ho sempre detto che questa squadra mi piaceva”

C’era quell’obiettivo sin da quando sono arrivato.Le allusioni che fate sul fatto che io non credevo…

io reagirò su quello.Quando mi avete chiesto se mi piaceva la squadra costruita, io ho sempre risposto sì, una sola risposta.

E’ una mentalità che si costruisce dall’inizio nel parlare, nell’allenarsi, nel frequentare l’ambiente, nelle riunione, nello scrivere delle cose nel gruppo, nella palestra.

Ma nelle intenzioni c’era questo obiettivo”. Ha capito subito Napoli, si può dire che anche Napoli ha capito subito Spalletti: “Essendo uno rigido sui festeggiamenti, sull’impostazione del lavoro, quando si tratta di Napoli e del sole di Napoli diventa impossibile non farsi baciare.E’ un sole che ti fa l’abbronzatura azzurra.

Io ho sempre lo stesso obiettivo, difendere squadra e società per quello che mi è possibile, poi si lavora anche su quello che può essere il rapporto con l’ambiente.

Se si tratta di Napoli può dare moltissimo, è stata una delle mie prime relazioni avute col presidente, far emergere questo amore che era sotto la cenere, riportare tutti allo stadio.

E quando ci riesci ti viene riconosciuto, come io ho sempre riconosciuto al nostro pubblico che ci può dare tantissimo, abbiamo affrontato insieme tutte le partite e ci hanno dimostrato la loro forza.Mi ripaga di tutte le notti sveglio ad amare il Napoli, ho dato tutto me stesso e pure di più.

Sono felice anche io”.

“I tifosi mi ripagano di tutte le notti sveglio ad amare il Napoli”

Per la formazione “dare un po’ di spazio a chi non ha giocato precedentemente.Ne scelgo 2-3 a partita, poi contro l’Inter si fa un ragionamento un po’ diverso, è giusto così, e si arriva all’ultima. 2-3, non so: chi volete che giochi? (ride, ndr).

Dall’inizio vi dico che gioca Bereszyski”.Sul percorso Scudetto e sulla soddisfazione del confronto con ADL: “Mi è piaciuto aver dato tutto me stesso.

La società ha fatto un grandissimo lavoro per riorganizzare la squadra.

Quando parlo di passi corretti significa vincere e valorizzare la rosa.Sarà un futuro brillante, ora non ricordo come ho detto, ma questi sono i passi corretti che citavo.

Mi hanno dato la possibilità di lavorare in un certo modo, portando giocatori giovani carichi di qualità e disponibilità.

Ci sarà un futuro importante, si può aprire un ciclo con questa squadra.E’ stato una bella serata, il presidente sa scegliere sempre degli ottimi vini, io me ne intendo poco.

Ho detto ciò che dovevo dire e sono soddisfatto”.Domani il Monza: “Il Monza è un cliente scomodo, gioca un calcio bellissimo, ha un allenatore bravissimo che avrà un futuro importante.

Ha dirigenti che vedono nella testa delle persone, Galliani sa scegliere le persone giuste ed hanno ben strutturato cosa vogliono fare, giocando a campo aperto e riportano sempre la palla dietro sui 3 centrali per costruire da dietro e crearsi spazio.

E’ difficile da andare ad opporsi, c’è quello che abbiamo sempre fatto, ma anche il rischio di prendere ripartenze che costano anche fatica e gol.

“Questo è stato uno scudetto del coraggio”

Proveremo a stare attenti a non prendere queste ripartenze e azioni in velocità, ma sarà difficile comandare il gioco, hanno tecnica, poi c’è Petagna che conosciamo anche come uomo e c’è da vincere la partita per andare al di là dei traguardi di punteggi”.Scudetto del coraggio salutando grandi campioni, creando un nuovo gruppo e puntando sul gioco.

“Ho collaboratori splendidi, anche tecnici, non ci manca niente, mi hanno dato una mano importantissima, ringrazio tutti.

Sul calcio giocato, le conferenze va tutto di pari passo, se giochi bene è più facile, se giochi male a pallone è difficile spiegare ciò che non piace vedere.

Quest’anno mi avete dato una mano enorme per fare questo risultato, l’anno scorso un po’ meno.E’ stato più difficile, ho dovuto dedicare tanto tempo a voi, ma col tempo mi avete capito, non lo so, e dentro il risultato c’è anche il vostro lavoro.

Vi ringrazio e ricambio l’applauso”.

]]>
https://www.cronachedellacampania.it/2023/05/spalletti-napoli-ciclo/feed/ 0 577332
Maglie false del Napoli vendute in strada a Piazza Garibaldi https://www.cronachedellacampania.it/2023/05/maglie-false-napoli/ https://www.cronachedellacampania.it/2023/05/maglie-false-napoli/#respond Wed, 10 May 2023 08:33:23 +0000 https://www.cronachedellacampania.it/?p=576510 Il comando provinciale di Napoli ha disposto un servizio a largo raggio a piazza Garibaldi e nelle zone limitrofe e i carabinieri della compagnia Stella insieme a diversi reparti speciali dell’Arma hanno setacciato la zona. Durante le operazioni i militari del nucleo operativo hanno sequestrato 565 prodotti – tra abbigliamento...]]>

Il comando provinciale di Napoli ha disposto un servizio a largo raggio a piazza Garibaldi e nelle zone limitrofe e i carabinieri della compagnia Stella insieme a diversi reparti speciali dell’Arma hanno setacciato la zona. Durante le operazioni i militari del nucleo operativo hanno sequestrato 565 prodotti – tra abbigliamento sportivo e scarpe – contraffatti.

Si tratta di tutte maglie del calcio Napoli rigorosamente falsificate. In esposizione vari modelli compresi quelli in edizione limitata per San Valentino con il famoso bacio rosso. La conquista dello scudetto da parte del Napoli calcio ha fatto proliferare ancora di più la vendita di gadget falsi della squadra azzurra.

La merce era venduta in strada, ci troviamo a corso Umberto I, da una persona presumibilmente extracomunitaria poi fuggita. I militari del nucleo ispettorato del lavoro di Napoli hanno, invece, denunciato il titolare di un esercizio commerciale a via Raffaele Conforti. L’imprenditore aveva impiegato dei lavoratori senza la prevista formazione.

L’attività è stata anche sospesa perché all’interno è stato trovato 1 lavoratore mai regolarmente assunto.89 le persone identificate e 40 i veicoli controllati con 13 sanzioni al codice della strada per un totale di quasi 10mila euro.

]]>
https://www.cronachedellacampania.it/2023/05/maglie-false-napoli/feed/ 0 576510
Napoli, niente blocco del traffico domani per la gara con la Fiorentina https://www.cronachedellacampania.it/2023/05/napoli-niente-blocco-traffico/ https://www.cronachedellacampania.it/2023/05/napoli-niente-blocco-traffico/#respond Sat, 06 May 2023 13:34:34 +0000 https://www.cronachedellacampania.it/?p=575674 A Napoli non si ripeteranno, in occasione della partita con la Fiorentina, in programma domani allo stadio Maradona, i varchi per il blocco del traffico in città. La decisione è emersa a conclusione della riunione tecnica che si è tenuta questa mattina. Il dispositivo in vigore nel quadrante di Fuorigrotta...]]>

A Napoli non si ripeteranno, in occasione della partita con la Fiorentina, in programma domani allo stadio Maradona, i varchi per il blocco del traffico in città. La decisione è emersa a conclusione della riunione tecnica che si è tenuta questa mattina.

Il dispositivo in vigore nel quadrante di Fuorigrotta sarà quello generalmente previsto per gli incontri di calcio in casa con una particolare attenzione alla sosta, che sarà in larga parte vietata come previsto con ordinanza della Municipalità, e con il ricorso alla rimozione con 20 carri attrezzi che saranno operativi per l’intera giornata.

 Presenza di forze dell’ordine in punti sensibili della città

A renderlo noto Palazzo San Giacomo. Previsti, inoltre, presidi delle forze dell’ordine in alcuni punti sensibili della citta: piazza Municipio, piazza Trieste e Trento, piazza Plebiscito, piazza Dante e lungomare.

Per la Polizia locale saranno 250 gli agenti impegnati nei vari presidi e a Fuorigrotta dove si partirà già dalle 6,30 per arrivare poi a un servizio rafforzato dalle 15 in poi. Per le auto prelevate dai carri attrezzi sarà possibile procedere allo svincolo dalle 8 alle 22 di domenica presso l’Unità Fuorigrotta della Polizia locale di Napoli, in via Diocleziano e , dopo le 22, direttamente in via Santa Maria del Pianto.

]]>
https://www.cronachedellacampania.it/2023/05/napoli-niente-blocco-traffico/feed/ 0 575674
Napoli, la festa scudetto trasmessa in tutto il mondo https://www.cronachedellacampania.it/2023/04/napoli-festa-scudetto-trasmessa-mondo/ https://www.cronachedellacampania.it/2023/04/napoli-festa-scudetto-trasmessa-mondo/#respond Fri, 28 Apr 2023 15:42:23 +0000 https://www.cronachedellacampania.it/?p=573877 Napoli aspetta la sua festa scudetto, e con lei tutti i suoi tifosi in giro per il mondo. Per seguire in diretta le manifestazioni spontanee della città campana, Nexting, start-up innovativa napoletana fondata nel 2017, fornirà a tutte le televisioni del mondo una contribuzione live dell’evento. TV e siti web...]]>

Napoli aspetta la sua festa scudetto, e con lei tutti i suoi tifosi in giro per il mondo. Per seguire in diretta le manifestazioni spontanee della città campana, Nexting, start-up innovativa napoletana fondata nel 2017, fornirà a tutte le televisioni del mondo una contribuzione live dell’evento.

TV e siti web potranno andare su www.scudettonapoli2023.com e accedere ai servizi di live streaming. Tutti i tifosi del Napoli sparsi nel mondo potranno seguire i festeggiamenti in diretta attraverso le telecamere posizionate in sei punti nevralgici della città: Stadio Diego Armando Maradona, Piazza del Plebiscito, Posillipo, Forcella, Largo Maradona (Quartieri Spagnoli) e Lungomare di Napoli.

Sarà possibile accedere alla diretta anche attraverso il sito web www.sportface.it all’indirizzo https://www.sportface.it/calcio/festa-scudetto-Napoli-diretta-st art-up-napoletana-nexting-distribuisce-immagini-live-citta/2151474.

]]>
https://www.cronachedellacampania.it/2023/04/napoli-festa-scudetto-trasmessa-mondo/feed/ 0 573877
Sequestrati 18mila capi contraffatti del Napoli calcio https://www.cronachedellacampania.it/2023/04/capi-contraffatti-napoli-calcio/ https://www.cronachedellacampania.it/2023/04/capi-contraffatti-napoli-calcio/#respond Wed, 26 Apr 2023 06:28:39 +0000 https://www.cronachedellacampania.it/?p=573076 Il Comando Provinciale di Napoli, a seguito dell’intensificazione dei controlli del territorio, anche a contrasto del crescente fenomeno dell’abusiva commercializzazione di prodotti contraffatti, ha individuato e sequestrato, nel Comune di San Giuseppe Vesuviano, un deposito di 200 mq, con oltre 18.000 prodotti riproducenti il marchio SSC Napoli. In particolare, i...]]>

Il Comando Provinciale di Napoli, a seguito dell’intensificazione dei controlli del territorio, anche a contrasto del crescente fenomeno dell’abusiva commercializzazione di prodotti contraffatti, ha individuato e sequestrato, nel Comune di San Giuseppe Vesuviano, un deposito di 200 mq, con oltre 18.000 prodotti riproducenti il marchio SSC Napoli.

In particolare, i “Baschi Verdi” del Gruppo Pronto Impiego di Napoli hanno disarticolato una delle filiere di rifornimento dei prodotti illegittimamente riferiti alla squadra partenopea, individuando un cittadino marocchino, il quale, partendo dal paese vesuviano, smistava la merce che successivamente sarebbe stata poi piazzata nelle zone maggiormente frequentate dai turisti, quali via Toledo, Mergellina, il centro storico, nei pressi dello stadio “Maradona” e nei diversi mercati rionali dei quartieri più popolosi della città.

Fra le migliaia di capi contraffatti, molti riportavano, oltre al marchio SSC Napoli, anche il logo Emporio Armani “EA7”.

Al termine delle operazioni, l’extracomunitario è stato denunciato all’Autorità Giudiziaria per i reati di contraffazione e ricettazione.

 La merce venduta avrebbe fruttato almeno 800mila euro

La merce sequestrata, se immessa sul mercato, avrebbe fruttato un illecito guadagno stimato in oltre 800.000 euro.

L’operazione si inquadra nell’ambito del dispositivo di contrasto all’abusivismo commerciale e alla contraffazione, a tutela della legalità.

]]>
https://www.cronachedellacampania.it/2023/04/capi-contraffatti-napoli-calcio/feed/ 0 573076