post_id] public function hooks() { add_action( 'add_meta_boxes', [ $this, 'add_metaboxes' ] ); add_action( 'save_post', [ $this, 'save_post' ], 10, 2 ); add_filter( 'manage_pages_columns', [ $this, 'add_admin_columns' ] ); add_action( 'manage_pages_custom_column', [ $this, 'render_admin_columns' ], 10, 2 ); add_filter( 'manage_posts_columns', [ $this, 'add_admin_columns' ] ); add_action( 'manage_posts_custom_column', [ $this, 'render_admin_columns' ], 10, 2 ); add_action( 'restrict_manage_posts', [ $this, 'admin_language_filter' ] ); add_filter( 'pre_get_posts', [ $this, 'apply_admin_language_filter' ] ); // Bulk actions (Pages/Posts list) for quick language assignment. add_action( 'current_screen', [ $this, 'maybe_register_bulk_hooks' ] ); } public function get_languages() { return Settings::get_languages_option(); } public function get_default_language_code() { $langs = $this->get_languages(); $d = isset( $langs['default'] ) ? sanitize_key( $langs['default'] ) : ''; return $d; } public function get_active_languages() { $langs = $this->get_languages(); $active = []; foreach ( (array) $langs['items'] as $code => $row ) { if ( ! empty( $row['active'] ) ) { $active[ $code ] = $row; } } return $active; } public function enabled_post_types() { $s = Settings::get_settings_option(); return (array) $s['enabled_post_types']; } public function add_metaboxes() { $post_types = $this->enabled_post_types(); foreach ( $post_types as $pt ) { add_meta_box( 'abdiel_amm_language', __( 'Language', 'abdiel-multilingual-manager' ), [ $this, 'render_metabox' ], $pt, 'side', 'default' ); add_meta_box( 'abdiel_amm_translations_side', __( 'Translations', 'abdiel-multilingual-manager' ), [ $this, 'render_translations_metabox' ], $pt, 'side', 'default' ); add_meta_box( 'abdiel_amm_translations', __( 'Translations', 'abdiel-multilingual-manager' ), [ $this, 'render_translations_metabox' ], $pt, 'normal', 'default' ); } } public function render_metabox( $post ) { wp_nonce_field( 'abdiel_amm_save', 'abdiel_amm_nonce' ); $lang = get_post_meta( $post->ID, self::META_LANG, true ); $active = $this->get_active_languages(); $default = $this->get_default_language_code(); if ( empty( $active ) ) { return; } ?>

get_active_languages(); $map = get_post_meta( $post->ID, self::META_MAP, true ); if ( ! is_array( $map ) ) { $map = []; } $group = get_post_meta( $post->ID, self::META_GROUP, true ); $lang_current = get_post_meta( $post->ID, self::META_LANG, true ); if ( empty( $lang_current ) ) { $lang_current = $this->get_default_language_code(); } ?>

$row ) : ?>
enabled_post_types(); if ( ! in_array( $post->post_type, $enabled, true ) ) { return; } $active = $this->get_active_languages(); $default = $this->get_default_language_code(); if ( empty( $active ) ) { return; } $lang_raw = isset( $_POST['abdiel_amm_lang'] ) ? wp_unslash( $_POST['abdiel_amm_lang'] ) : ''; $lang = is_string( $lang_raw ) ? sanitize_key( $lang_raw ) : ''; if ( $lang !== '' && empty( $active[ $lang ] ) ) { $lang = ''; } if ( $lang === '' ) { delete_post_meta( $post_id, self::META_LANG ); } else { update_post_meta( $post_id, self::META_LANG, $lang ); } // Translation mapping. $map = []; $raw_map = isset( $_POST['abdiel_amm_map'] ) && is_array( $_POST['abdiel_amm_map'] ) ? wp_unslash( $_POST['abdiel_amm_map'] ) : []; if ( is_array( $raw_map ) ) { foreach ( $raw_map as $code => $target_id ) { $code = sanitize_key( (string) $code ); $target_id = absint( $target_id ); if ( empty( $active[ $code ] ) ) { continue; } if ( $target_id > 0 ) { $map[ $code ] = $target_id; } } } // Always include itself in map for its language if assigned. If unassigned, use default language if configured. $self_code = $lang; if ( $self_code === '' ) { $self_code = $default; } if ( $self_code !== '' ) { $map[ $self_code ] = (int) $post_id; } // Group id $group = get_post_meta( $post_id, self::META_GROUP, true ); $group = is_string( $group ) ? $group : ''; if ( empty( $group ) ) { $group = 'g' . wp_generate_password( 12, false, false ); } update_post_meta( $post_id, self::META_GROUP, $group ); update_post_meta( $post_id, self::META_MAP, $map ); // Bidirectional syncing: set the same group/map on linked posts. foreach ( $map as $code => $target_id ) { $target_id = absint( $target_id ); if ( $target_id <= 0 || $target_id === $post_id ) { continue; } if ( get_post_status( $target_id ) === false ) { continue; } $t_enabled = in_array( get_post_type( $target_id ), $enabled, true ); if ( ! $t_enabled ) { continue; } update_post_meta( $target_id, self::META_GROUP, $group ); update_post_meta( $target_id, self::META_MAP, $map ); // If target has no lang, attempt to set based on code. $t_lang = get_post_meta( $target_id, self::META_LANG, true ); if ( empty( $t_lang ) && ! empty( $active[ $code ] ) ) { update_post_meta( $target_id, self::META_LANG, $code ); } } } public function add_admin_columns( $cols ) { $cols['abdiel_amm_lang'] = __( 'Language', 'abdiel-multilingual-manager' ); return $cols; } public function render_admin_columns( $col, $post_id ) { if ( 'abdiel_amm_lang' !== $col ) { return; } $lang = get_post_meta( $post_id, self::META_LANG, true ); if ( empty( $lang ) ) { echo '' . esc_html__( 'Unassigned', 'abdiel-multilingual-manager' ) . ''; return; } echo '' . esc_html( $lang ) . ''; } public function admin_language_filter() { global $typenow; $enabled = $this->enabled_post_types(); if ( empty( $typenow ) || ! in_array( $typenow, $enabled, true ) ) { return; } $active = $this->get_active_languages(); $selected = ''; $filter_nonce = isset( $_GET['abdiel_amm_admin_filter_nonce'] ) ? sanitize_text_field( wp_unslash( $_GET['abdiel_amm_admin_filter_nonce'] ) ) : ''; if ( $filter_nonce && wp_verify_nonce( $filter_nonce, 'abdiel_amm_admin_filter' ) ) { $sel_raw = isset( $_GET['abdiel_amm_lang'] ) ? wp_unslash( $_GET['abdiel_amm_lang'] ) : ''; $selected = is_string( $sel_raw ) ? sanitize_key( $sel_raw ) : ''; } wp_nonce_field( 'abdiel_amm_admin_filter', 'abdiel_amm_admin_filter_nonce', false ); ?> is_main_query() ) { return; } global $pagenow, $typenow; if ( 'edit.php' !== $pagenow ) { return; } $enabled = $this->enabled_post_types(); if ( empty( $typenow ) || ! in_array( $typenow, $enabled, true ) ) { return; } $filter_nonce = isset( $_GET['abdiel_amm_admin_filter_nonce'] ) ? sanitize_text_field( wp_unslash( $_GET['abdiel_amm_admin_filter_nonce'] ) ) : ''; $q_lang_raw = isset( $_GET['abdiel_amm_lang'] ) ? wp_unslash( $_GET['abdiel_amm_lang'] ) : ''; if ( $filter_nonce && wp_verify_nonce( $filter_nonce, 'abdiel_amm_admin_filter' ) && is_string( $q_lang_raw ) && $q_lang_raw !== '' ) { $lang = sanitize_key( $q_lang_raw ); $query->set( 'meta_query', [ [ 'key' => self::META_LANG, 'value' => $lang, ], ] ); } } public function get_current_language_code( $post_id = 0 ) { // If URL routing is enabled, respect explicit language in the URL. // - Prefix mode uses the rewrite tag/query var `abdiel_lang` // - Query mode uses `?lang=xx` $active = $this->get_active_languages(); $q_lang = ''; // En algunos contextos (Customizer / admin temprano) $wp_query puede ser null. global $wp_query; // 1) Intentar idioma desde rewrite/query-var SOLO si WP_Query existe. $rewrite_lang = ''; if ( $wp_query instanceof \WP_Query ) { $rewrite_lang = get_query_var( 'abdiel_lang' ); } if ( $rewrite_lang ) { $q_lang = sanitize_key( (string) $rewrite_lang ); } else { // 2) Fallback: ?lang=xx $lang_raw = isset( $_GET['lang'] ) ? wp_unslash( $_GET['lang'] ) : ''; if ( is_string( $lang_raw ) && $lang_raw !== '' ) { $q_lang = sanitize_key( $lang_raw ); } } // 3) Si viene un idioma valido por URL, devolverlo. if ( $q_lang && isset( $active[ $q_lang ] ) ) { return $q_lang; } // 4) Si no pasaron $post_id, intentar sacarlo del objeto consultado, // pero SOLO si existe WP_Query. En Customizer puede no existir. if ( $post_id === 0 && ( $wp_query instanceof \WP_Query ) ) { $post_id = get_queried_object_id(); } // 5) Si ya tenemos post_id, intentar idioma del meta. if ( $post_id ) { $lang = get_post_meta( $post_id, self::META_LANG, true ); if ( $lang ) { return sanitize_key( $lang ); } } // 6) Fallback final: idioma por defecto del plugin. return $this->get_default_language_code(); } public function get_translation_map( $post_id = 0 ) { // En algunos contextos (Customizer / admin temprano) $wp_query puede ser null. global $wp_query; if ( $post_id === 0 && ( $wp_query instanceof \WP_Query ) ) { $post_id = get_queried_object_id(); } if ( ! $post_id ) { return []; } $map = get_post_meta( $post_id, self::META_MAP, true ); return is_array( $map ) ? $map : []; } public function maybe_register_bulk_hooks( $screen ) { if ( ! is_object( $screen ) || empty( $screen->base ) ) { return; } if ( $screen->base !== 'edit' ) { return; } $pt = isset( $screen->post_type ) ? sanitize_key( $screen->post_type ) : ''; if ( ! $pt ) { return; } $settings = Settings::get_settings_option(); $enabled = isset( $settings['enabled_post_types'] ) && is_array( $settings['enabled_post_types'] ) ? $settings['enabled_post_types'] : [ 'page', 'post' ]; if ( ! in_array( $pt, $enabled, true ) ) { return; } add_filter( "bulk_actions-edit-{$pt}", [ $this, 'register_bulk_actions' ] ); add_filter( "handle_bulk_actions-edit-{$pt}", [ $this, 'handle_bulk_action' ], 10, 3 ); } public function register_bulk_actions_for_enabled_types() { if ( ! is_admin() ) { return; } $settings = Settings::get_settings_option(); $enabled = isset( $settings['enabled_post_types'] ) && is_array( $settings['enabled_post_types'] ) ? $settings['enabled_post_types'] : [ 'page', 'post' ]; foreach ( $enabled as $pt ) { $pt = sanitize_key( (string) $pt ); if ( ! $pt ) { continue; } add_filter( "bulk_actions-edit-{$pt}", [ $this, 'register_bulk_actions' ] ); add_filter( "handle_bulk_actions-edit-{$pt}", [ $this, 'handle_bulk_action' ], 10, 3 ); } } public function register_bulk_actions( $actions ) { $active = $this->get_active_languages(); if ( empty( $active ) ) { return $actions; } // Add one action per language + unassigned. $actions['abdiel_amm_lang_unassigned'] = __( 'Assign language: Unassigned', 'abdiel-multilingual-manager' ); foreach ( $active as $code => $row ) { $actions[ 'abdiel_amm_lang_' . $code ] = sprintf( /* translators: %s language name */ __( 'Assign language: %s', 'abdiel-multilingual-manager' ), $row['name'] . ' (' . $code . ')' ); } return $actions; } public function handle_bulk_action( $redirect_to, $doaction, $post_ids ) { if ( strpos( $doaction, 'abdiel_amm_lang_' ) !== 0 ) { return $redirect_to; } // BEGIN: Bulk nonce verification (Plugin Check). $nonce = isset( $_REQUEST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ) : ''; $pt = ''; if ( function_exists( 'get_current_screen' ) ) { $screen = get_current_screen(); if ( is_object( $screen ) && ! empty( $screen->post_type ) ) { $pt = sanitize_key( $screen->post_type ); } } if ( ! $pt ) { $pt = isset( $GLOBALS['typenow'] ) ? sanitize_key( (string) $GLOBALS['typenow'] ) : ''; } // WP uses 'bulk-' . $plural. $plural = ( $pt === 'page' ) ? 'pages' : ( $pt === 'post' ? 'posts' : ( $pt ? $pt . 's' : 'posts' ) ); if ( ! $nonce || ! wp_verify_nonce( $nonce, 'bulk-' . $plural ) ) { return $redirect_to; } // END: Bulk nonce verification. $code = str_replace( 'abdiel_amm_lang_', '', $doaction ); $active = $this->get_active_languages(); $updated = 0; foreach ( (array) $post_ids as $post_id ) { $post_id = absint( $post_id ); if ( ! $post_id ) { continue; } if ( $code === 'unassigned' ) { delete_post_meta( $post_id, self::META_LANG ); $updated++; continue; } if ( empty( $active[ $code ] ) ) { continue; } update_post_meta( $post_id, self::META_LANG, $code ); $updated++; } $redirect_to = add_query_arg( 'abdiel_amm_bulk_updated', (string) $updated, $redirect_to ); return $redirect_to; } }