post->ID ); if ( ! $original_post ) { return; } $this->republish( $post, $original_post ); // Trigger the redirect in the Classic Editor. if ( $this->is_classic_editor_post_request() ) { $this->redirect( $original_post->ID, $post->ID ); } } /** * Republishes the original post with the passed post, when using the Block Editor. * * @param WP_Post $post The copy's post object. * * @return void */ public function republish_after_rest_api_request( $post ) { $this->republish_request( $post ); } /** * Republishes the original post with the passed post, when using the Classic Editor. * * Runs also in the Block Editor to save the custom meta data only when there * are custom meta boxes. * * @param int $post_id The copy's post ID. * @param WP_Post $post The copy's post object. * * @return void */ public function republish_after_post_request( $post_id, $post ) { if ( $this->is_rest_request() ) { return; } $this->republish_request( $post ); } /** * Republishes the scheduled Rewrited and Republish post. * * @param WP_Post $copy The scheduled copy. * * @return void */ public function republish_scheduled_post( $copy ) { if ( ! $copy instanceof WP_Post || ! $this->permissions_helper->is_rewrite_and_republish_copy( $copy ) ) { return; } $original_post = Utils::get_original( $copy->ID ); // If the original post was permanently deleted, we don't want to republish, so trash instead. if ( ! $original_post ) { $this->delete_copy( $copy->ID, null, false ); return; } \kses_remove_filters(); $this->republish( $copy, $original_post ); \kses_init_filters(); $this->delete_copy( $copy->ID, $original_post->ID ); } /** * Cleans up the copied post and temporary metadata after the user has been redirected. * * @return void */ public function clean_up_after_redirect() { if ( ! empty( $_GET['dprepublished'] ) && ! empty( $_GET['dpcopy'] ) && ! empty( $_GET['post'] ) ) { $copy_id = \intval( \wp_unslash( $_GET['dpcopy'] ) ); $post_id = \intval( \wp_unslash( $_GET['post'] ) ); \check_admin_referer( 'dp-republish', 'dpnonce' ); if ( \intval( \get_post_meta( $copy_id, '_dp_has_been_republished', true ) ) === 1 ) { $this->delete_copy( $copy_id, $post_id ); } else { \wp_die( \esc_html__( 'An error occurred while deleting the Rewrite & Republish copy.', 'duplicate-post' ) ); } } } /** * Checks whether a request is the Classic Editor POST request. * * @return bool Whether the request is the Classic Editor POST request. */ public function is_classic_editor_post_request() { if ( $this->is_rest_request() || \wp_doing_ajax() ) { return false; } return isset( $_GET['meta-box-loader'] ) === false; } /** * Determines whether the current request is a REST request. * * @return bool Whether or not the request is a REST request. */ public function is_rest_request() { return \defined( 'REST_REQUEST' ) && \REST_REQUEST; } /** * Republishes the post by overwriting the original post. * * @param WP_Post $post The Rewrite & Republish copy. * @param WP_Post $original_post The original post. * * @return void */ public function republish( WP_Post $post, WP_Post $original_post ) { // Remove WordPress default filter so a new revision is not created on republish. \remove_action( 'post_updated', 'wp_save_post_revision', 10 ); // Republish taxonomies and meta. $this->republish_post_taxonomies( $post ); $this->republish_post_meta( $post ); // Republish the post. $this->republish_post_elements( $post, $original_post ); // Mark the copy as already published. \update_post_meta( $post->ID, '_dp_has_been_republished', '1' ); // Re-enable the creation of a new revision. \add_action( 'post_updated', 'wp_save_post_revision', 10, 1 ); } /** * Deletes the copy and associated post meta, if applicable. * * @param int $copy_id The copy's ID. * @param int|null $post_id The original post's ID. Optional. * @param bool $permanently_delete Whether to permanently delete the copy. Defaults to true. * * @return void */ public function delete_copy( $copy_id, $post_id = null, $permanently_delete = true ) { /** * Fires before deleting a Rewrite & Republish copy. * * @param int $copy_id The copy's ID. * @param int $post_id The original post's ID.. */ \do_action( 'duplicate_post_after_rewriting', $copy_id, $post_id ); // Delete the copy bypassing the trash so it also deletes the copy post meta. \wp_delete_post( $copy_id, $permanently_delete ); if ( ! \is_null( $post_id ) ) { // Delete the meta that marks the original post has having a copy. \delete_post_meta( $post_id, '_dp_has_rewrite_republish_copy' ); } } /** * Republishes the post elements overwriting the original post. * * @param WP_Post $post The post object. * @param WP_Post $original_post The original post. * * @return void */ protected function republish_post_elements( $post, $original_post ) { // Cast to array and not alter the copy's original object. $post_to_be_rewritten = clone $post; // Prepare post data for republishing. $post_to_be_rewritten->ID = $original_post->ID; $post_to_be_rewritten->post_name = $original_post->post_name; $post_to_be_rewritten->post_status = $this->determine_post_status( $post, $original_post ); /** * Yoast SEO and other plugins prevent from accidentally updating another post's * data (e.g. the Yoast SEO metadata by checking the $_POST data ID with the post object ID. * We need to overwrite the $_POST data ID to allow updating the original post. */ $_POST['ID'] = $original_post->ID; // Republish the original post. $rewritten_post_id = \wp_update_post( $post_to_be_rewritten ); if ( $rewritten_post_id === 0 ) { \wp_die( \esc_html__( 'An error occurred while republishing the post.', 'duplicate-post' ) ); } } /** * Republishes the post taxonomies overwriting the ones of the original post. * * @param WP_Post $post The copy's post object. * * @return void */ protected function republish_post_taxonomies( $post ) { $original_post_id = Utils::get_original_post_id( $post->ID ); $copy_taxonomies_options = [ 'taxonomies_excludelist' => [], 'use_filters' => false, 'copy_format' => true, ]; $this->post_duplicator->copy_post_taxonomies( $original_post_id, $post, $copy_taxonomies_options ); } /** * Republishes the post meta overwriting the ones of the original post. * * @param WP_Post $post The copy's post object. * * @return void */ protected function republish_post_meta( $post ) { $original_post_id = Utils::get_original_post_id( $post->ID ); $copy_meta_options = [ 'meta_excludelist' => Utils::get_default_filtered_meta_names(), 'use_filters' => false, 'copy_thumbnail' => true, 'copy_template' => true, ]; $this->post_duplicator->copy_post_meta_info( $original_post_id, $post, $copy_meta_options ); } /** * Redirects the user to the original post. * * @param int $original_post_id The ID of the original post to redirect to. * @param int $copy_id The ID of the copy post. * * @return void */ protected function redirect( $original_post_id, $copy_id ) { \wp_safe_redirect( \add_query_arg( [ 'dprepublished' => 1, 'dpcopy' => $copy_id, 'dpnonce' => \wp_create_nonce( 'dp-republish' ), ], \admin_url( 'post.php?action=edit&post=' . $original_post_id ) ) ); exit(); } /** * Determines the post status to use when publishing the Rewrite & Republish copy. * * @param WP_Post $post The post object. * @param WP_Post $original_post The original post object. * * @return string The post status to use. */ protected function determine_post_status( $post, $original_post ) { if ( $original_post->post_status === 'trash' ) { return 'trash'; } if ( $post->post_status === 'private' ) { return 'private'; } return 'publish'; } /** * Deletes the original post meta that flags it as having a copy when the copy is manually deleted. * * @param int $post_id Post ID of a post that is going to be deleted. * * @return void */ public function clean_up_when_copy_manually_deleted( $post_id ) { $post = \get_post( $post_id ); if ( ! $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) { return; } $original_post_id = Utils::get_original_post_id( $post_id ); \delete_post_meta( $original_post_id, '_dp_has_rewrite_republish_copy' ); } }