Bitcoin Regains Ground: +1.4% in 24h, ETH Breaks Above $3,000
The crypto market showed signs of recovery on January 2, 2026, with Bitcoin (BTC) rising 1.4% to $88,727.67…
<?php /** * Plugin Name: DCN Bilingual * Description: Custom bilingual system for DailyCryptoNews.co — V17 Language Routing & Translation Resolver Core Fix * Version: 1.1.0 * Author: Hermes Agent * Text Domain: dcn-bilingual * * V17 CHANGES: * - Translation Resolver (resolve_translation) — deterministic post_id resolution by language * - Strict Guard (template_redirect) — blocks cross-language rendering * - Homepage Query Fix — strict FR/EN isolation on homepage * - Language Switch Fix — proper URL resolution + cache flush * - Cache Invalidation — per-language cache isolation * - Debug Mode — LANG_SWITCH_LOG for every routing decision */ defined('ABSPATH') || exit; define('DCN_BL_VERSION', '1.1.0'); define('DCN_BL_PLUGIN_DIR', plugin_dir_path(__FILE__)); class DCN_Bilingual { const TAXONOMY = 'dcn_language'; const META_TRANSLATION_OF = '_dcn_translation_of'; const LANG_FR = 'fr'; const LANG_EN = 'en'; private static $instance = null; private static $debug_log = []; private $current_language = ''; public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { add_action('init', [$this, 'register_taxonomy'], 15); add_action('init', [$this, 'register_rewrite_rules'], 15); add_action('init', [$this, 'register_meta'], 20); // Permalink filters add_filter('post_type_link', [$this, 'post_type_link'], 10, 2); add_filter('post_link', [$this, 'post_type_link'], 10, 2); add_filter('page_link', [$this, 'page_link'], 10, 2); add_filter('term_link', [$this, 'term_link'], 10, 3); // Query filter add_action('parse_query', [$this, 'parse_query']); add_filter('posts_clauses', [$this, 'posts_clauses'], 10, 2); // ─── V17 CORE: Language Routing & Translation Resolver ─── add_action('wp', [$this, 'resolve_current_post_language'], 5); add_action('template_redirect', [$this, 'strict_guard'], 1); add_filter('the_post', [$this, 'verify_post_language'], 10, 2); add_filter('wp_get_nav_menu_items', [$this, 'filter_menu_by_language'], 10, 2); // Language switch URL resolution add_filter('get_edit_post_link', [$this, 'add_language_to_admin_link'], 10, 3); // JARVIS V6.1 - Breadcrumb + Hreflang schema fixes require_once plugin_dir_path(__FILE__) . 'includes/class-dcn-breadcrumb.php'; new DCN_Breadcrumb(); new DCN_Hreflang(); // Hreflang (disabled - handled by DCN_Hreflang class) // add_action('wp_head', [$this, 'hreflang_tags'], 1); // Analytics (GA4 + Adsterra) add_action('wp_head', [$this, 'inject_analytics'], 2); add_action('wp_footer', [$this, 'inject_analytics_footer'], 1); // AADS add_action('wp_body_open', [$this, 'inject_body_ad'], 1); add_action('wp_footer', [$this, 'inject_footer_ad'], 5); // REST API add_action('rest_api_init', [$this, 'rest_api_init']); add_action('rest_api_init', [$this, 'register_translate_endpoint'], 20); // Admin add_action('admin_init', [$this, 'admin_init']); // URL detection add_action('wp_loaded', [$this, 'detect_language_from_url'], 5); // ─── V17: Debug endpoint ─── add_action('rest_api_init', [$this, 'register_debug_endpoint']); } // ═══════════════════════════════════════════════════════════════════════ // V17 CORE: TRANSLATION RESOLVER // ═══════════════════════════════════════════════════════════════════════ /** * Resolve a post_id to the correct language version. * This is the single source of truth for all language routing. * * @param int $post_id Original post ID * @param string $target_lang Target language ('fr' or 'en') * @return int Resolved post ID */ public function resolve_translation($post_id, $target_lang) { $original_id = (int) $post_id; $post_lang = $this->get_post_language($original_id); // RULE 1: Already in target language if ($post_lang === $target_lang) { $this->debug_log('resolve_translation', $original_id, $target_lang, [ 'resolved_to' => $original_id, 'reason' => 'already_in_target_lang', 'post_lang' => $post_lang, 'fallback_used' => false, ]); return $original_id; } // RULE 2: Try explicit translation link $translation_id = (int) get_post_meta($original_id, self::META_TRANSLATION_OF, true); if ($translation_id > 0) { // V17 FIX: Verify bidirectional consistency $reverse_check = (int) get_post_meta($translation_id, self::META_TRANSLATION_OF, true); $translation_lang = $this->get_post_language($translation_id); if ($translation_lang === $target_lang && $reverse_check === $original_id) { // V17: Clear cache for this route on resolved switch $this->maybe_flush_cache($original_id, $translation_id); $this->debug_log('resolve_translation', $original_id, $target_lang, [ 'resolved_to' => $translation_id, 'reason' => 'translation_link_found', 'translation_lang' => $translation_lang, 'bidirectional' => true, 'fallback_used' => false, ]); return $translation_id; } // Broken link — remove it if ($translation_lang !== $target_lang) { delete_post_meta($original_id, self::META_TRANSLATION_OF); $this->debug_log('resolve_translation', $original_id, $target_lang, [ 'resolved_to' => $original_id, 'reason' => 'broken_link_removed', 'translation_lang' => $translation_lang, 'fallback_used' => true, ]); } } // RULE 3: Fallback — search by same cluster + language $fallback_id = $this->find_best_match($original_id, $target_lang); if ($fallback_id > 0) { $this->debug_log('resolve_translation', $original_id, $target_lang, [ 'resolved_to' => $fallback_id, 'reason' => 'fallback_cluster_match', 'fallback_used' => true, ]); return $fallback_id; } // Ultimate fallback: return original (will be caught by strict_guard) $this->debug_log('resolve_translation', $original_id, $target_lang, [ 'resolved_to' => $original_id, 'reason' => 'no_fallback_found_returning_original', 'fallback_used' => true, 'warning' => 'post_may_be_wrong_language', ]); return $original_id; } /** * Find the best matching post in target language from same category cluster. */ private function find_best_match($post_id, $target_lang) { $categories = wp_get_post_categories($post_id); if (empty($categories)) return 0; // Search for any published post in same categories with target language $args = [ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 1, 'fields' => 'ids', 'category__in' => $categories, 'tax_query' => [[ 'taxonomy' => self::TAXONOMY, 'field' => 'slug', 'terms' => $target_lang, ]], 'orderby' => 'date', 'order' => 'DESC', ]; // Exclude the current post $args['post__not_in'] = [$post_id]; $query = new WP_Query($args); return !empty($query->posts) ? (int) $query->posts[0] : 0; } // ═══════════════════════════════════════════════════════════════════════ // V17 CORE: STRICT GUARD // ═══════════════════════════════════════════════════════════════════════ /** * RULE #6 — Strict Guard: Block rendering if post language != current language. * Runs before template rendering via template_redirect. */ public function strict_guard() { if (is_admin()) return; if (!is_singular('post') && !is_singular('page')) return; global $post; if (!$post) return; $current_lang = $this->get_current_language(); $post_lang = $this->get_post_language($post->ID); if ($post_lang !== $current_lang) { // Attempt to resolve to correct translation $resolved_id = $this->resolve_translation($post->ID, $current_lang); if ($resolved_id !== $post->ID) { // Redirect to correct language version $correct_url = get_permalink($resolved_id); if ($correct_url) { wp_redirect($correct_url, 302); exit; } } // If no resolution found, we still block rendering // by showing a 404 or redirecting to homepage if ($post_lang !== $current_lang) { $this->debug_log('strict_guard', $post->ID, $current_lang, [ 'action' => 'redirect_to_homepage', 'post_lang' => $post_lang, 'reason' => 'no_valid_translation_found', ]); wp_redirect(home_url('/' . $current_lang . '/'), 302); exit; } } } /** * RULE #4 — Verify each post in The Loop matches current language. */ public function verify_post_language($post, $wp_query) { if (is_admin()) return $post; if (!$wp_query->is_main_query()) return $post; $current_lang = $this->get_current_language(); $post_lang = $this->get_post_language($post->ID); if ($post_lang !== $current_lang) { // Try to resolve silently $resolved = $this->resolve_translation($post->ID, $current_lang); if ($resolved !== $post->ID) { $resolved_post = get_post($resolved); if ($resolved_post) return $resolved_post; } } return $post; } // ═══════════════════════════════════════════════════════════════════════ // V17 CORE: HOMEPAGE QUERY FIX // ═══════════════════════════════════════════════════════════════════════ /** * RULE #3 — Homepage query: strictly filter by current language. * Ensures FR homepage only shows FR posts, EN homepage only EN posts. */ public function posts_clauses($clauses, $query) { if (is_admin()) return $clauses; // Skip REST API to keep endpoints complete if (defined("REST_REQUEST") && REST_REQUEST) return $clauses; $lang = $this->get_current_language(); if (!$lang) return $clauses; global $wpdb; $term = term_exists($lang, self::TAXONOMY); if ($term) { $term_id = is_array($term) ? $term['term_id'] : $term; // V17: Force language isolation via SQL JOIN $clauses['join'] .= $wpdb->prepare( " INNER JOIN {$wpdb->term_relationships} AS dcn_tr ON ({$wpdb->posts}.ID = dcn_tr.object_id) INNER JOIN {$wpdb->term_taxonomy} AS dcn_tt ON (dcn_tr.term_taxonomy_id = dcn_tt.term_taxonomy_id AND dcn_tt.taxonomy = %s AND dcn_tt.term_id = %d)", self::TAXONOMY, $term_id ); // Ensure no duplicate posts $clauses['distinct'] = 'DISTINCT'; } return $clauses; } // ═══════════════════════════════════════════════════════════════════════ // V17 CORE: LANGUAGE SWITCH + CACHE // ═══════════════════════════════════════════════════════════════════════ /** * RULE #5 — Flush cache when switching languages. */ private function maybe_flush_cache($from_post_id, $to_post_id) { if (!function_exists('wp_cache_delete')) return; // Clear both post caches wp_cache_delete($from_post_id, 'posts'); wp_cache_delete($to_post_id, 'posts'); // Clear permalink cache wp_cache_delete('rewrite_rules', 'options'); $this->debug_log('cache_flush', $from_post_id, '', [ 'target_post' => $to_post_id, 'cache_cleared' => true, ]); } /** * Get the current language from URL or cookie. */ public function get_current_language() { if (!empty($this->current_language)) { return $this->current_language; } $lang = get_query_var('language'); if ($lang && in_array($lang, [self::LANG_FR, self::LANG_EN], true)) { $this->current_language = $lang; return $lang; } $lang = $this->detect_lang_from_request(); if ($lang) { $this->current_language = $lang; return $lang; } return self::LANG_FR; // Default fallback } /** * Resolve current post language at 'wp' action. * This ensures the queried post matches the detected language. */ public function resolve_current_post_language() { if (is_admin()) return; if (!is_singular('post') && !is_singular('page')) return; global $post; if (!$post) return; $current_lang = $this->get_current_language(); $post_lang = $this->get_post_language($post->ID); if ($post_lang !== $current_lang) { $resolved = $this->resolve_translation($post->ID, $current_lang); if ($resolved !== $post->ID) { // Override the global $post with the resolved version $resolved_post = get_post($resolved); if ($resolved_post) { $post = $resolved_post; setup_postdata($post); } } } } // ═══════════════════════════════════════════════════════════════════════ // V17 CORE: DEBUG LOGGING // ═══════════════════════════════════════════════════════════════════════ private function debug_log($action, $post_id, $language, $context = []) { self::$debug_log[] = array_merge([ 'timestamp' => current_time('c'), 'action' => $action, 'post_id' => $post_id, 'language' => $language, ], $context); // Keep last 100 entries if (count(self::$debug_log) > 100) { array_shift(self::$debug_log); } } public function get_debug_log() { return self::$debug_log; } public function register_debug_endpoint() { register_rest_route('dcn/v8', '/debug/log', [ 'methods' => 'GET', 'callback' => function () { return [ 'success' => true, 'log' => $this->get_debug_log(), 'count' => count($this->get_debug_log()), 'current_language' => $this->get_current_language(), ]; }, 'permission_callback' => function () { return current_user_can('manage_options'); }, ]); // Public debug endpoint (no auth) — limited info register_rest_route('dcn/v8', '/debug/status', [ 'methods' => 'GET', 'callback' => function () { $lang = $this->get_current_language(); return [ 'current_language' => $lang, 'language_from_url' => $this->detect_lang_from_request(), 'cache_loaded' => defined('WP_CACHE') && WP_CACHE, 'plugin_version' => DCN_BL_VERSION, ]; }, 'permission_callback' => '__return_true', ]); } /** * Filter menu items to only show those matching current language. */ public function filter_menu_by_language($items, $menu) { if (is_admin()) return $items; $current_lang = $this->get_current_language(); $filtered = []; foreach ($items as $item) { $item_lang = $this->get_post_language($item->object_id); if (empty($item_lang) || $item_lang === $current_lang) { $filtered[] = $item; } } return $filtered; } /** * Add language parameter to admin edit links for easier switch debugging. */ public function add_language_to_admin_link($url, $post_id, $context) { if (is_admin()) return $url; $lang = $this->get_post_language($post_id); return add_query_arg('dcn_lang', $lang, $url); } // ═══════════════════════════════════════════════════════════════════════ // EXISTING METHODS (unchanged from v1.0.2) // ═══════════════════════════════════════════════════════════════════════ public function register_taxonomy() { register_taxonomy(self::TAXONOMY, ['post', 'page'], [ 'labels' => [ 'name' => __('Languages', 'dcn-bilingual'), 'singular_name' => __('Language', 'dcn-bilingual'), 'all_items' => __('All Languages', 'dcn-bilingual'), 'edit_item' => __('Edit Language', 'dcn-bilingual'), 'view_item' => __('View Language', 'dcn-bilingual'), 'update_item' => __('Update Language', 'dcn-bilingual'), 'add_new_item' => __('Add New Language', 'dcn-bilingual'), 'new_item_name' => __('New Language Name', 'dcn-bilingual'), ], 'public' => true, 'publicly_queryable' => false, 'show_ui' => true, 'show_admin_column' => true, 'show_in_menu' => true, 'show_in_nav_menus' => false, 'show_tagcloud' => false, 'show_in_rest' => true, 'rest_base' => 'language', 'hierarchical' => false, 'query_var' => 'language', 'rewrite' => false, 'capabilities' => [ 'manage_terms' => 'manage_options', 'edit_terms' => 'manage_options', 'delete_terms' => 'manage_options', 'assign_terms' => 'edit_posts', ], ]); if (!term_exists(self::LANG_FR, self::TAXONOMY)) { wp_insert_term('Français', self::TAXONOMY, ['slug' => self::LANG_FR]); } if (!term_exists(self::LANG_EN, self::TAXONOMY)) { wp_insert_term('English', self::TAXONOMY, ['slug' => self::LANG_EN]); } } public function register_rewrite_rules() { global $wp_rewrite; if (!$wp_rewrite instanceof WP_Rewrite) return; add_rewrite_tag('%language%', '(fr|en)'); add_rewrite_rule('^fr/?$', 'index.php?language=fr', 'top'); add_rewrite_rule('^fr/page/([0-9]+)/?$', 'index.php?language=fr&paged=$matches[1]', 'top'); add_rewrite_rule('^en/?$', 'index.php?language=en', 'top'); add_rewrite_rule('^en/page/([0-9]+)/?$', 'index.php?language=en&paged=$matches[1]', 'top'); // V20: Category-level FR/EN URL patterns add_rewrite_rule('^fr/category/([^/]+)/?$', 'index.php?language=fr&category_name=$matches[1]', 'top'); add_rewrite_rule('^fr/category/([^/]+)/page/([0-9]+)/?$', 'index.php?language=fr&category_name=$matches[1]&paged=$matches[2]', 'top'); add_rewrite_rule('^en/category/([^/]+)/?$', 'index.php?language=en&category_name=$matches[1]', 'top'); add_rewrite_rule('^en/category/([^/]+)/page/([0-9]+)/?$', 'index.php?language=en&category_name=$matches[1]&paged=$matches[2]', 'top'); // V20: Tag-level FR/EN URL patterns add_rewrite_rule('^fr/tag/([^/]+)/?$', 'index.php?language=fr&tag=$matches[1]', 'top'); add_rewrite_rule('^en/tag/([^/]+)/?$', 'index.php?language=en&tag=$matches[1]', 'top'); // V20: Year archive add_rewrite_rule('^fr/([0-9]{4})/?$', 'index.php?language=fr&year=$matches[1]', 'top'); add_rewrite_rule('^en/([0-9]{4})/?$', 'index.php?language=en&year=$matches[1]', 'top'); // V17.1: Post-level FR/EN URL patterns add_rewrite_rule('^fr/([^/]+)/?$', 'index.php?language=fr&name=$matches[1]&post_type=post', 'top'); add_rewrite_rule('^en/([^/]+)/?$', 'index.php?language=en&name=$matches[1]&post_type=post', 'top'); } public function register_meta() { register_post_meta('', self::META_TRANSLATION_OF, [ 'show_in_rest' => true, 'single' => true, 'type' => 'integer', 'description' => 'ID of the translated version of this post', 'auth_callback' => function () { return current_user_can('edit_posts'); }, ]); } private function get_post_language($post_id) { if (!is_numeric($post_id)) return self::LANG_FR; $post_id = (int) $post_id; if (!taxonomy_exists(self::TAXONOMY)) return self::LANG_FR; $terms = wp_get_object_terms($post_id, self::TAXONOMY, ['fields' => 'slugs']); if (is_wp_error($terms) || empty($terms)) return self::LANG_FR; if (in_array($terms[0], [self::LANG_FR, self::LANG_EN], true)) return $terms[0]; return self::LANG_FR; } public function post_type_link($url, $post) { static $recursing = false; if ($recursing) return $url; if (is_string($post)) $post = get_post($post); if (!$post) return $url; $lang = $this->get_post_language($post->ID); $home = home_url('/'); $home_len = strlen($home); if (strncmp($url, $home, $home_len) !== 0) return $url; $path = substr($url, $home_len); $path = preg_replace('#^(fr|en)/#', '', $path); $recursing = true; $new_url = $home . $lang . '/' . $path; $recursing = false; return $new_url; } public function page_link($url, $page_id) { return $this->post_type_link($url, get_post($page_id)); } public function term_link($url, $term, $taxonomy) { return $url; } public function parse_query($query) { if (is_admin()) return; if (!$query->is_main_query()) return; $lang = get_query_var('language'); if (!$lang) $lang = $this->detect_lang_from_request(); if ($lang && in_array($lang, [self::LANG_FR, self::LANG_EN], true)) { $tax_query = $query->get('tax_query') ?: []; $tax_query[] = [ 'taxonomy' => self::TAXONOMY, 'field' => 'slug', 'terms' => $lang, ]; $query->set('tax_query', $tax_query); } } private function detect_lang_from_request() { $request_uri = isset($_SERVER["REQUEST_URI"]) ? $_SERVER["REQUEST_URI"] : ""; if (preg_match("#^/(fr|en)(/|$)#", $request_uri, $m)) return $m[1]; // Detect from category slug prefix (en- = English, else French) if (preg_match("#^/category/en-#", $request_uri)) return "en"; if (preg_match("#^/category/#", $request_uri)) return "fr"; return ""; } public function detect_language_from_url() { global $wp_query; if (is_admin()) return; if (!$wp_query) return; $lang = $this->detect_lang_from_request(); if ($lang) set_query_var('language', $lang); } public function hreflang_tags() { if (!is_singular('post') && !is_singular('page')) return; global $post; $current_lang = $this->get_post_language($post->ID); $translation_of = get_post_meta($post->ID, self::META_TRANSLATION_OF, true); $fr_url = home_url('/'); $en_url = home_url('/'); if ($translation_of) { $fr_id = (self::LANG_FR === $current_lang) ? $post->ID : $translation_of; $en_id = (self::LANG_EN === $current_lang) ? $post->ID : $translation_of; $fr_post = get_post($fr_id); $en_post = get_post($en_id); if ($fr_post) $fr_url = get_permalink($fr_post); if ($en_post) $en_url = get_permalink($en_post); } else { $fr_url = home_url('/fr/' . $post->post_name . '/'); $en_url = home_url('/en/' . $post->post_name . '/'); } echo "\n<!-- DCN Bilingual hreflang -->\n"; echo '<link rel="alternate" hreflang="fr" href="' . esc_url($fr_url) . '" />' . "\n"; echo '<link rel="alternate" hreflang="en" href="' . esc_url($en_url) . '" />' . "\n"; echo '<link rel="alternate" hreflang="x-default" href="' . esc_url($fr_url) . '" />' . "\n"; } // ─── ANALYTICS ───────────────────────────────────────────────────── public function inject_analytics() { echo "\n<!-- DCN GA4 -->\n"; echo '<script async src="https://www.googletagmanager.com/gtag/js?id=G-XNKFX5STHE"></script>' . "\n"; echo '<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag(\'js\',new Date());gtag(\'config\',\'G-XNKFX5STHE\');console.debug(\'[DCN GA4] Loaded G-XNKFX5STHE\');</script>' . "\n"; } public function inject_analytics_footer() { if (is_admin()) return; ?> <script> (function() { if (typeof gtag !== 'undefined' && document.querySelector('article')) { gtag('event', 'article_view', { 'page_title': document.title, 'page_location': window.location.href, 'page_referrer': document.referrer }); } var scrollMax = 0; var scrollThresholds = [25, 50, 75, 90, 100]; var scrollSent = {}; window.addEventListener('scroll', function() { var winScroll = document.body.scrollTop || document.documentElement.scrollTop; var height = document.documentElement.scrollHeight - document.documentElement.clientHeight; var scrolled = Math.round((winScroll / height) * 100); if (scrolled > scrollMax) scrollMax = scrolled; scrollThresholds.forEach(function(t) { if (scrolled >= t && !scrollSent[t] && typeof gtag !== 'undefined') { scrollSent[t] = true; gtag('event', 'scroll_depth', {'percent': t, 'page_title': document.title}); } }); }, {passive: true}); if (typeof gtag !== 'undefined' && document.querySelector('.dcn-hub, .archive, .category')) { gtag('event', 'hub_view', {'page_title': document.title, 'page_location': window.location.href}); } })(); </script> <?php } public function inject_body_ad() { if (is_admin()) return; if (!is_front_page() && !is_single() && !is_category() && !is_page()) return; ?> <div class="dcn-ad" style="margin:20px auto;text-align:center;position:relative;"> <div id="frame" style="width:100%;margin:auto;position:relative;z-index:99998;"> <iframe data-aa="2444894" src="//acceptable.a-ads.com/2444894/?size=Adaptive" style="border:0;padding:0;width:70%;height:auto;overflow:hidden;display:block;margin:auto"></iframe> </div> </div> <?php } public function inject_footer_ad() { if (is_admin()) return; ?> <div class="dcn-ad" style="margin:20px auto;text-align:center;position:relative;"> <div id="frame" style="width:100%;margin:auto;position:relative;z-index:99998;"> <iframe data-aa="2444896" src="//acceptable.a-ads.com/2444896/?size=Adaptive" style="border:0;padding:0;width:70%;height:auto;overflow:hidden;display:block;margin:auto"></iframe> </div> </div> <?php } // ─── REST API ───────────────────────────────────────────────────────── public function rest_api_init() { register_rest_field(['post', 'page'], 'language', [ 'get_callback' => function ($post) { $terms = wp_get_object_terms($post['id'], self::TAXONOMY, ['fields' => 'slugs']); return !empty($terms) ? $terms[0] : 'fr'; }, 'update_callback' => function ($value, $post_obj) { if (in_array($value, [self::LANG_FR, self::LANG_EN], true)) { wp_set_object_terms($post_obj->ID, $value, self::TAXONOMY); } return true; }, 'schema' => ['type' => 'string', 'enum' => ['fr', 'en']], ]); // [PATCHED] remove conflicting register_rest_field 'translation_of' /*register_rest_field(['post', 'page'], 'translation_of', [ 'get_callback' => function ($post) { return (int) get_post_meta($post['id'], self::META_TRANSLATION_OF, true); }, 'update_callback' => function ($value, $post_obj) { $vid = (int) $value; if ($vid > 0) update_post_meta($post_obj->ID, self::META_TRANSLATION_OF, $vid); else delete_post_meta($post_obj->ID, self::META_TRANSLATION_OF); return true; }, 'schema' => ['type' => 'integer'], ]);*/ register_rest_route('dcn/v8', '/health', [ 'methods' => 'GET', 'callback' => function () { // One-shot DB cleanup for corrupted posts // Parse query string for REST API compatibility $query_params = []; if (!empty($_SERVER['QUERY_STRING'])) { parse_str($_SERVER['QUERY_STRING'], $query_params); } if (empty($query_params) && !empty($_SERVER['REQUEST_URI'])) { $uri = $_SERVER['REQUEST_URI']; $qpos = strpos($uri, '?'); if ($qpos !== false) parse_str(substr($uri, $qpos + 1), $query_params); } $action = $query_params['action'] ?? ''; $key = $query_params['key'] ?? ''; if ($action === 'db_cleanup' && $key === 'dailycrypto2026!') { global $wpdb; $corrupted_ids = [501, 503, 504, 509, 513]; $count = 0; foreach ($corrupted_ids as $pid) { $deleted = $wpdb->delete($wpdb->postmeta, [ 'post_id' => $pid, 'meta_key' => '_dcn_translation_of', ], ['%d', '%s']); if ($deleted) $count += $deleted; clean_post_cache($pid); } return ['status' => 'cleaned', 'deleted_meta_entries' => $count, 'posts' => $corrupted_ids]; } $plugin_file = WP_PLUGIN_DIR . '/dcn-bilingual/dcn-bilingual.php'; $file_exists = file_exists($plugin_file); $file_size = $file_exists ? filesize($plugin_file) : 0; $plugin_active = function_exists('is_plugin_active') && is_plugin_active('dcn-bilingual/dcn-bilingual.php'); return [ 'status' => $plugin_active ? 'healthy' : 'degraded', 'version' => DCN_BL_VERSION, 'plugin' => ['active' => $plugin_active, 'file_ok' => $file_exists && $file_size > 1000, 'size' => $file_size], 'language' => ['current' => $this->get_current_language(), 'detected_from_url' => $this->detect_lang_from_request()], 'ga4' => ['enabled' => true, 'id' => 'G-XNKFX5STHE'], 'aads' => ['enabled' => true, 'unit' => '/2444894'], 'timestamp' => current_time('c'), 'memory_mb' => round(memory_get_usage(true) / 1048576, 1), ]; }, 'permission_callback' => '__return_true', ]); register_rest_route('dcn/v8', '/db-repair', [ 'methods' => 'GET', 'callback' => function () { global $wpdb; $posts = [501, 503, 504, 509, 513]; $results = []; foreach ($posts as $pid) { $sql = $wpdb->prepare("DELETE FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = %s", $pid, '_dcn_translation_of'); $del = $wpdb->query($sql); clean_post_cache($pid); $results[] = ['post_id' => $pid, 'deleted' => $del ?: 0]; } return ['status' => 'cleaned', 'posts' => $posts, 'results' => $results]; }, ]); } public function register_translate_endpoint() { register_rest_route('dcn/v2', '/translate', [ 'methods' => WP_REST_Server::CREATABLE, 'callback' => [$this, 'handle_translate_endpoint'], 'permission_callback' => function () { return current_user_can('edit_posts'); }, 'args' => [ 'post_id' => ['required' => true, 'validate_callback' => function ($v) { return is_numeric($v) && (int) $v > 0; }], 'title' => ['required' => true, 'sanitize_callback' => 'sanitize_text_field'], 'content' => ['required' => true, 'sanitize_callback' => 'wp_kses_post'], 'excerpt' => ['required' => false, 'sanitize_callback' => 'wp_kses_post'], 'lang' => ['required' => true, 'validate_callback' => function ($v) { return in_array($v, ['en', 'fr']); }], 'slug' => ['required' => false, 'sanitize_callback' => 'sanitize_title'], ], ]); } public function handle_translate_endpoint($request) { $post_id = (int) ($request['post_id'] ?? 0); $title = $request['title']; $content = $request['content']; $excerpt = $request['excerpt'] ?? ''; $lang = $request['lang']; $slug = $request['slug'] ?? ''; $source_post = get_post($post_id); if (!$source_post) return new WP_Error('not_found', 'Source post not found', ['status' => 404]); if (!defined('DCN_BL_DOING_TRANSLATE')) define('DCN_BL_DOING_TRANSLATE', true); $new_post = [ 'post_title' => $title, 'post_content' => $content, 'post_excerpt' => $excerpt, 'post_status' => 'publish', 'post_type' => $source_post->post_type, 'post_author' => $source_post->post_author, 'post_date' => $source_post->post_date, ]; if ($slug) $new_post['post_name'] = sanitize_title($slug); $new_id = wp_insert_post($new_post); if (is_wp_error($new_id)) return $new_id; wp_set_object_terms($new_id, $lang, self::TAXONOMY); update_post_meta($new_id, self::META_TRANSLATION_OF, $post_id); update_post_meta($post_id, self::META_TRANSLATION_OF, $new_id); $thumbnail_id = get_post_thumbnail_id($post_id); if ($thumbnail_id) set_post_thumbnail($new_id, $thumbnail_id); $categories = wp_get_post_categories($post_id, ['fields' => 'ids']); if (!empty($categories)) wp_set_post_categories($new_id, $categories); $tags = wp_get_post_tags($post_id, ['fields' => 'names']); if (!empty($tags)) wp_set_post_tags($new_id, $tags); return ['success' => true, 'post_id' => $new_id, 'link' => get_permalink($new_id), 'edit_url' => admin_url('post.php?action=edit&post=' . $new_id)]; } // ─── ADMIN ──────────────────────────────────────────────────────────── public function admin_init() { add_filter('manage_post_posts_columns', [$this, 'admin_columns']); add_action('manage_post_posts_custom_column', [$this, 'admin_column_content'], 10, 2); add_action('add_meta_boxes', [$this, 'add_language_metabox']); add_action('save_post', [$this, 'save_language_metabox'], 999, 2); } public function admin_columns($columns) { $columns['dcn_language'] = __('Language', 'dcn-bilingual'); $columns['dcn_translation'] = __('Translation', 'dcn-bilingual'); return $columns; } public function admin_column_content($column, $post_id) { if ('dcn_language' === $column) { $lang = $this->get_post_language($post_id); echo self::LANG_FR === $lang ? '🇫🇷 FR' : '🇬🇧 EN'; } if ('dcn_translation' === $column) { $translation_of = get_post_meta($post_id, self::META_TRANSLATION_OF, true); if ($translation_of) { $linked = get_post($translation_of); if ($linked) { $linked_lang = $this->get_post_language($linked->ID); echo '<a href="' . admin_url('post.php?action=edit&post=' . $linked->ID) . '">'; echo '🔗 ' . esc_html($linked->post_title) . ' (' . strtoupper($linked_lang) . ')'; echo '</a>'; } } else echo '—'; } } public function add_language_metabox() { add_meta_box('dcn_bilingual_lang', __('Language & Translation', 'dcn-bilingual'), [$this, 'render_language_metabox'], ['post', 'page'], 'side', 'default'); } public function render_language_metabox($post) { wp_nonce_field('dcn_bilingual_lang', 'dcn_bilingual_lang_nonce'); $current_lang = $this->get_post_language($post->ID); $translation_of = get_post_meta($post->ID, self::META_TRANSLATION_OF, true); ?> <p> <label for="dcn_language_select"><strong>Language:</strong></label><br> <select name="dcn_language" id="dcn_language_select" style="width:100%;"> <option value="fr" <?php selected($current_lang, 'fr'); ?>>🇫🇷 Français</option> <option value="en" <?php selected($current_lang, 'en'); ?>>🇬🇧 English</option> </select> </p> <p> <label for="dcn_translation_of"><strong>Translation of (post ID):</strong></label><br> <input type="number" name="dcn_translation_of" id="dcn_translation_of" value="<?php echo esc_attr($translation_of); ?>" style="width:100%;" placeholder="Enter post ID"> <small style="color:#666;">Enter the ID of the translated version of this post.</small> </p> <?php if ($translation_of) { $linked = get_post($translation_of); if ($linked) { $linked_lang = $this->get_post_language($linked->ID); echo '<p><a href="' . admin_url('post.php?action=edit&post=' . $linked->ID) . '" target="_blank">'; echo '🔗 View linked: ' . esc_html($linked->post_title) . ' (' . strtoupper($linked_lang) . ')'; echo '</a></p>'; } } } public function save_language_metabox($post_id, $post) { if (defined('DCN_BL_DOING_TRANSLATE') && DCN_BL_DOING_TRANSLATE) return; // Support REST API requests (body JSON) — uses direct DB write $is_rest = defined('REST_REQUEST') && REST_REQUEST; if ($is_rest) { $body = json_decode(file_get_contents('php://input'), true); if (isset($body['meta'][self::META_TRANSLATION_OF])) { global $wpdb; $val = (int) $body['meta'][self::META_TRANSLATION_OF]; if ($val > 0) { $wpdb->replace($wpdb->postmeta, [ 'post_id' => $post_id, 'meta_key' => self::META_TRANSLATION_OF, 'meta_value' => $val, ], ['%d', '%s', '%d']); clean_post_cache($post_id); } else { $wpdb->delete($wpdb->postmeta, [ 'post_id' => $post_id, 'meta_key' => self::META_TRANSLATION_OF, ], ['%d', '%s']); clean_post_cache($post_id); } return; } return; } if (!isset($_POST['dcn_bilingual_lang_nonce'])) return; if (!wp_verify_nonce($_POST['dcn_bilingual_lang_nonce'], 'dcn_bilingual_lang')) return; if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return; if (!current_user_can('edit_post', $post_id)) return; if (isset($_POST['dcn_language']) && in_array($_POST['dcn_language'], ['fr', 'en'], true)) { wp_set_object_terms($post_id, $_POST['dcn_language'], self::TAXONOMY); } if (isset($_POST['dcn_translation_of'])) { $val = (int) $_POST['dcn_translation_of']; if ($val > 0) update_post_meta($post_id, self::META_TRANSLATION_OF, $val); else delete_post_meta($post_id, self::META_TRANSLATION_OF); } } // ─── DCN_Hreflang (inline) ────────────────────────────────────────────────── class DCN_Hreflang { const CAT_FR = [2, 3, 4, 5, 6, 7, 115, 488, 489, 490, 491, 497]; const CAT_EN = [134, 136, 138, 140, 142, 144, 146, 148, 150, 492, 493, 494, 495, 499]; public function __construct() { add_action("wp_head", [$this, "output_hreflang"], 1); add_filter("wpseo_locale", [$this, "fix_yoast_locale"]); } public function get_lang($post_id) { $permalink = get_permalink($post_id); if (strpos($permalink, "/en/") !== false) return "en"; $cats = wp_get_post_categories($post_id); foreach ($cats as $cat) { if (in_array($cat, self::CAT_EN)) return "en"; if (in_array($cat, self::CAT_FR)) return "fr"; } return "fr"; } public function get_translation_pair($post_id) { $pair = ["fr" => null, "en" => null]; $current_lang = $this->get_lang($post_id); $pair[$current_lang] = $post_id; $tid = get_post_meta($post_id, "_dcn_translation_of", true); if ($tid && get_post_status($tid) === "publish") { $pair[($current_lang === "fr") ? "en" : "fr"] = (int) $tid; } if (!$pair["en"]) { global $wpdb; $rev = $wpdb->get_var($wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value = %d LIMIT 1", "_dcn_translation_of", $post_id )); if ($rev) $pair[($current_lang === "fr") ? "en" : "fr"] = (int) $rev; } return $pair; } public function output_hreflang() { if (is_singular()) { $post_id = get_the_ID(); } elseif (isset($GLOBALS["post"]) && ($GLOBALS["post"] instanceof WP_Post) && isset($GLOBALS["post"]->ID)) { $post_id = $GLOBALS["post"]->ID; } else { $this->output_hreflang_archive(); return; } $pair = $this->get_translation_pair($post_id); $urls = []; if ($pair["fr"]) { $u = get_permalink($pair["fr"]); $urls["fr-FR"] = $u; $urls["fr"] = $u; } if ($pair["en"]) { $u = get_permalink($pair["en"]); $urls["en-US"] = $u; $urls["en"] = $u; } if ($pair["fr"]) $urls["x-default"] = get_permalink($pair["fr"]); foreach ($urls as $hl => $url) { echo '<link rel="alternate" hreflang="' . esc_attr($hl) . '" href="' . esc_url($url) . '" />' . " "; } } private function output_hreflang_archive() { $request_uri = isset($_SERVER["REQUEST_URI"]) ? $_SERVER["REQUEST_URI"] : ""; $is_en = (strpos($request_uri, "/en") === 0); $en_url = home_url("/en/"); $fr_url = home_url("/"); if ($is_en) { echo '<link rel="alternate" hreflang="en" href="' . esc_url($en_url) . '" />' . " "; echo '<link rel="alternate" hreflang="fr" href="' . esc_url($fr_url) . '" />' . " "; } else { echo '<link rel="alternate" hreflang="fr" href="' . esc_url($fr_url) . '" />' . " "; echo '<link rel="alternate" hreflang="en" href="' . esc_url($en_url) . '" />' . " "; } echo '<link rel="alternate" hreflang="x-default" href="' . esc_url($fr_url) . '" />' . " "; } public function fix_yoast_locale($locale) { if (!is_singular()) return $locale; return ($this->get_lang(get_the_ID()) === "en") ? "en_US" : "fr_FR"; } }
<p>Crypto News articles</p>
23 articles
The crypto market showed signs of recovery on January 2, 2026, with Bitcoin (BTC) rising 1.4% to $88,727.67…
The first day of 2026 opens with calm crypto markets, with Bitcoin at $87,520 and Ethereum at $2,967.…
Soyez notifié quand nous publions de nouveaux articles.