ProShell v2.0
Dashboard
Server Info
Server: 158.106.128.192
PHP: 8.2.33
home
planet5
public_html
bansibaba.com
wp-includes
2026-09-14 06:40:18
Editing: post-republisher.php
Cancel
Save Changes
<?php namespace Yoast\WP\Duplicate_Post; use WP_Post; /** * Duplicate Post class to republish a rewritten post. * * @since 4.0 */ class Post_Republisher { /** * Post_Duplicator object. * * @var Post_Duplicator */ protected $post_duplicator; /** * Holds the permissions helper. * * @var Permissions_Helper */ protected $permissions_helper; /** * Initializes the class. * * @param Post_Duplicator $post_duplicator The Post_Duplicator object. * @param Permissions_Helper $permissions_helper The Permissions Helper object. */ public function __construct( Post_Duplicator $post_duplicator, Permissions_Helper $permissions_helper ) { $this->post_duplicator = $post_duplicator; $this->permissions_helper = $permissions_helper; } /** * Adds hooks to integrate with WordPress. * * @return void */ public function register_hooks() { \add_action( 'init', [ $this, 'register_post_statuses' ] ); \add_filter( 'wp_insert_post_data', [ $this, 'change_post_copy_status' ], 1, 2 ); $enabled_post_types = $this->permissions_helper->get_enabled_post_types(); foreach ( $enabled_post_types as $enabled_post_type ) { /** * Called in the REST API when submitting the post copy in the Block Editor. * Runs the republishing of the copy onto the original. */ \add_action( "rest_after_insert_{$enabled_post_type}", [ $this, 'republish_after_rest_api_request' ] ); } /** * Called by `wp_insert_post()` when submitting the post copy, which runs in two cases: * - In the Classic Editor, where there's only one request that updates everything. * - In the Block Editor, only when there are custom meta boxes. */ \add_action( 'wp_insert_post', [ $this, 'republish_after_post_request' ], \PHP_INT_MAX, 2 ); // Clean up after the redirect to the original post. \add_action( 'load-post.php', [ $this, 'clean_up_after_redirect' ] ); // Clean up orphaned R&R copies when opening a post for editing. \add_action( 'load-post.php', [ $this, 'clean_up_orphaned_copy' ], 11 ); // Clean up the original when the copy is manually deleted from the trash. \add_action( 'before_delete_post', [ $this, 'clean_up_when_copy_manually_deleted' ] ); // Ensure scheduled Rewrite and Republish posts are properly handled. \add_action( 'future_to_publish', [ $this, 'republish_scheduled_post' ] ); } /** * Adds custom post statuses. * * These post statuses are meant for internal use. However, we can't use the * `internal` status because the REST API posts controller allows all registered * statuses but the `internal` one. * * @return void */ public function register_post_statuses() { $options = [ 'label' => \__( 'Republish', 'duplicate-post' ), 'exclude_from_search' => false, 'show_in_admin_all_list' => false, 'show_in_admin_status_list' => false, ]; \register_post_status( 'dp-rewrite-republish', $options ); } /** * Changes the post copy status. * * Runs on the `wp_insert_post_data` hook in `wp_insert_post()` when * submitting the post copy. * * @param array $data An array of slashed, sanitized, and processed post data. * @param array $postarr An array of sanitized (and slashed) but otherwise unmodified post data. * * @return array An array of slashed, sanitized, and processed attachment post data. */ public function change_post_copy_status( $data, $postarr ) { if ( ! \array_key_exists( 'ID', $postarr ) || empty( $postarr['ID'] ) ) { return $data; } $post = \get_post( $postarr['ID'] ); if ( ! $post || ! $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) { return $data; } if ( $data['post_status'] === 'publish' ) { $data['post_status'] = 'dp-rewrite-republish'; } return $data; } /** * Executes the republish request. * * @param WP_Post $post The copy's post object. * * @return void */ public function republish_request( $post ) { if ( ! $post instanceof WP_Post || ! $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) { return; } $is_republishing = $this->permissions_helper->is_copy_allowed_to_be_republished( $post ); $is_scheduling = ( $post->post_status === 'future' ); // Only act on a republish submission or the scheduling of a republish. if ( ! $is_republishing && ! $is_scheduling ) { return; } $original_post = Utils::get_original( $post->ID ); if ( ! $original_post ) { return; } if ( ! \current_user_can( 'edit_post', $original_post->ID ) ) { // Undo the republish or scheduling but keep the copy's content, so the original is never overwritten. $this->revert_unauthorized_copy( $post ); \wp_die( \esc_html__( 'You are not allowed to republish this post.', 'duplicate-post' ), \esc_html__( 'Permission denied', 'duplicate-post' ), [ 'response' => 403 ], ); } // A scheduled copy is republished by cron at the scheduled time, not now. if ( $is_republishing ) { $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 orphaned Rewrite & Republish copies when opening a post for editing. * * This ensures that if a copy is stuck in the dp-rewrite-republish status, * it gets deleted automatically to unblock the R&R functionality. * * @return void */ public function clean_up_orphaned_copy() { if ( empty( $_GET['post'] ) || empty( $_GET['action'] ) || $_GET['action'] !== 'edit' ) { return; } $post_id = \intval( \wp_unslash( $_GET['post'] ) ); $post = \get_post( $post_id ); if ( ! $post || $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) { return; } // Check if this post has an orphaned R&R copy. $copy = $this->permissions_helper->get_rewrite_and_republish_copy( $post ); if ( ! $copy ) { return; } // If the copy is in dp-rewrite-republish status, it's orphaned and should be deleted. if ( $copy->post_status === 'dp-rewrite-republish' ) { $this->delete_copy( $copy->ID, $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 ( (int) \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 ) { /** * Fires before the Rewrite & Republish copy is republished to the original post. * * This action runs before any content, taxonomies, or meta are copied from the * Rewrite & Republish copy to the original post. Use this hook to perform actions * or modifications before the republishing process begins. * * @since 4.6 * * @param WP_Post $post The Rewrite & Republish copy. * @param WP_Post $original_post The original post that will be overwritten. */ \do_action( 'duplicate_post_before_republish', $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 ); /** * Fires after the Rewrite & Republish copy has been republished to the original post. * * This action runs after all content, taxonomies, and meta have been copied from * the Rewrite & Republish copy to the original post. The copy is marked as republished * but has not yet been deleted. Use this hook to perform cleanup or additional * processing after the republishing is complete. * * @since 4.6 * * @param WP_Post $post The Rewrite & Republish copy. * @param WP_Post $original_post The original post that has been updated. */ \do_action( 'duplicate_post_after_republish', $post, $original_post ); } /** * 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 ( $post_id !== null ) { // 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(); } /** * Reverts a copy that the current user is not allowed to republish back to draft. * * Preserves the copy's content while undoing the republish or scheduling, so the original * is never overwritten and the copy is not left published or in a republish-pending state. * * @param WP_Post $post The copy's post object. * * @return void */ protected function revert_unauthorized_copy( WP_Post $post ) { \wp_update_post( [ 'ID' => $post->ID, 'post_status' => 'draft', ], ); } /** * 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' ); } }
Upload Files
Cancel
Upload
Create New
Cancel
Create
Change Permissions
Cancel
Save
Change Date
Cancel
Save
Rename Item
Cancel
Save
Confirm Delete
Are you sure you want to delete the selected items?
Cancel
Delete