sync_model = &$polylang->sync_post_model; $this->model = &$polylang->model; // Create buttons in the language metabox. if ( $polylang instanceof PLL_Admin && ! wp_doing_cron() && ! ( defined( 'WP_CLI' ) && WP_CLI ) ) { // Loaded with `PLL_Admin_Loader`: we're on `admin_init` or `use_block_editor_for_post`. $this->add_metabox_buttons(); } add_action( 'pll_bulk_translate_options_init', array( $this, 'add_bulk_translate_options' ) ); add_filter( 'update_translation_group', array( $this, 'unsync_post' ), 10, 3 ); add_action( 'pll_save_post', array( $this, 'sync_posts' ), 5 ); // Before PLL_Admin_Sync, Before PLL_ACF, Before PLLWC. } /** * Registers the buttons. * * @since 3.6.5 * * @return void */ public function add_metabox_buttons(): void { foreach ( $this->model->get_languages_list() as $language ) { $this->buttons[ $language->slug ] = new PLL_Sync_Post_Button( $this->sync_model, $language ); } } /** * Registers options of the Translate bulk action. * * @since 3.6.5 * * @param PLL_Bulk_Translate $bulk_translate Instance of `PLL_Bulk_Translate`. * @return void */ public function add_bulk_translate_options( $bulk_translate ): void { $bulk_translate->register_options( array( new PLL_Sync_Post_Bulk_Option( array( 'name' => 'pll_sync_post', 'description' => __( 'Synchronize translations in selected languages with the original items', 'polylang-pro' ), 'do_synchronize' => true, 'priority' => 10, ), $this->model, $this->sync_model ), new PLL_Sync_Post_Bulk_Option( array( 'name' => 'pll_copy_post', 'description' => __( 'Copy original items to selected languages', 'polylang-pro' ), 'do_synchronize' => false, 'priority' => 5, ), $this->model, $this->sync_model ), ) ); } /** * Duplicates the post and saves the synchronization group * * @since 2.1 * * @param int $post_id The post id. * @return void */ public function sync_posts( $post_id ) { static $avoid_recursion = false; if ( $avoid_recursion ) { return; } if ( ! empty( $_POST['post_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification // We are editing the post from post.php (only place where we can change the option to sync). if ( ! empty( $_POST['pll_sync_post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification $sync_post = array_intersect( array_map( 'sanitize_key', $_POST['pll_sync_post'] ), array( 'true' ) ); // phpcs:ignore WordPress.Security.NonceVerification } if ( empty( $sync_post ) ) { $this->sync_model->save_group( $post_id, array() ); return; } } else { // Quick edit or bulk edit or any place where the Languages metabox is not displayed. $sync_post = array_diff( $this->sync_model->get( $post_id ), array( $post_id ) ); // Just remove this post from the list. } $avoid_recursion = true; $languages = array_keys( $sync_post ); foreach ( $languages as $k => $lang ) { if ( $this->sync_model->current_user_can_synchronize( $post_id, $lang ) ) { add_filter( 'is_sticky', array( $this, 'handle_sticky_post' ) ); $this->sync_model->copy_post( $post_id, $lang, false ); // Don't save the group inside the loop. remove_filter( 'is_sticky', array( $this, 'handle_sticky_post' ) ); } else { unset( $languages[ $k ] ); } } // Save group if the languages metabox is displayed. if ( ! empty( $_POST['post_lang_choice'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification $this->sync_model->save_group( $post_id, $languages ); } $avoid_recursion = false; } /** * * Unsynchronize a post from the post translations group. * * @since 3.2 * * @param (int|string[])[] $tr List of translations with language codes as array keys and post IDs as array values. * An optional element defined by the 'sync' key includes an array of synchronized translations * with target language code as array keys and source language code as values. * @param string $old_slug The old language slug. * @param string $new_slug The new language slug. * @return (int|string[])[] */ public function unsync_post( $tr, $old_slug, $new_slug ) { if ( ! is_array( $tr ) || empty( $tr['sync'] ) || ! is_array( $tr['sync'] ) ) { return $tr; } // Delete sync between translations when deleting a language or update slug. // Search old slug in array keys. if ( array_key_exists( $old_slug, $tr['sync'] ) ) { if ( ! empty( $new_slug ) && ! empty( $tr['sync'][ $old_slug ] ) ) { $tr['sync'][ $new_slug ] = $tr['sync'][ $old_slug ]; } unset( $tr['sync'][ $old_slug ] ); } // Search old slug in array values. if ( in_array( $old_slug, $tr['sync'] ) ) { foreach ( $tr['sync'] as $key => $value ) { if ( $value !== $old_slug ) { continue; } // If new slug then replace it. if ( $new_slug ) { $tr['sync'][ $key ] = $new_slug; } else { // Otherwise unset the old slug. unset( $tr['sync'][ $key ] ); } } } return $tr; } /** * Some backward compatibility with Polylang < 2.6 * allows for example to call PLL()->sync_post->are_synchronized() used in Polylang for WooCommerce * * @since 2.6 * * @param string $func Function name. * @param array $args Function arguments. * @return mixed|void */ public function __call( $func, $args ) { if ( method_exists( $this->sync_model, $func ) ) { if ( WP_DEBUG ) { $debug = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions sprintf( '%1$s() was called incorrectly in %2$s on line %3$s: the call to PLL()->sync_post->%1$s() has been deprecated in Polylang 2.6, use PLL()->sync_post->sync_model->%1$s() instead.' . "\nError handler", esc_html( $func ), esc_html( $debug[0]['file'] ), absint( $debug[0]['line'] ) ) ); } return call_user_func_array( array( $this->sync_model, $func ), $args ); } $debug = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions trigger_error( // phpcs:ignore WordPress.PHP.DevelopmentFunctions sprintf( 'Call to undefined function PLL()->sync_post->%1$s() in %2$s on line %3$s' . "\nError handler", esc_html( $func ), esc_html( $debug[0]['file'] ), absint( $debug[0]['line'] ) ), E_USER_ERROR ); } /** * Filter the sticky status of a post. * * @since 3.2 * * @param bool $is_sticky Whether or not the source post is sticky. * @return bool */ public function handle_sticky_post( $is_sticky ) { if ( isset( $_POST['_inline_edit'], $_POST['action'] ) && wp_verify_nonce( $_POST['_inline_edit'], 'inlineeditnonce' ) && 'inline-save' === $_POST['action'] ) { // For quick edit. $is_sticky = isset( $_POST['sticky'] ) && 'sticky' === $_POST['sticky']; } elseif ( isset( $_POST['_pll_nonce'], $_POST['action'] ) && wp_verify_nonce( $_POST['_pll_nonce'], 'pll_language' ) && 'editpost' === $_POST['action'] ) { // For the classic editor. $is_sticky = isset( $_POST['sticky'] ) && 'sticky' === $_POST['sticky']; } elseif ( isset( $_GET['_wpnonce'], $_GET['action'], $_GET['sticky'] ) && wp_verify_nonce( $_GET['_wpnonce'], 'bulk-posts' ) && 'edit' === $_GET['action'] ) { // For bulk edit (note that 'sticky' can take 3 different values here). switch ( $_GET['sticky'] ) { case '-1': break; case 'unsticky': $is_sticky = false; break; case 'sticky': $is_sticky = true; break; } } return $is_sticky; } }