can_copy( $user, $post, 'republish_posts', 'refresh_roles' ); } /** Create a draft tied to an original post. */ public function clone_for_refresh( int $source_id ) { return $this->copy_post( $source_id, [ 'title_suffix' => '', 'extra_meta' => [ '_republish_post' => [ 1 ], '_republish_post_parent' => [ $source_id ], ], 'filter' => 'adminoptim_clone_republish_post_meta', 'action' => 'adminoptim_after_clone_republish', ] ); } /** Whether a post is a refresh copy (as opposed to an original). */ public function is_refresh_copy( int $post_id ): bool { return (bool) get_post_meta( $post_id, '_republish_post', true ); } /** Get the original post ID a refresh copy was cloned from. */ public function get_refresh_parent( int $post_id ): int { return absint( get_post_meta( $post_id, '_republish_post_parent', true ) ); } /** * Replace the original with a refresh copy. * * @param int|\WP_Post $refresh_item Refresh copy or ID. * @return int|\WP_Error */ public function refresh_original( $refresh_item ) { $refresh = $refresh_item instanceof \WP_Post ? $refresh_item : get_post( absint( $refresh_item ) ); if ( ! $refresh instanceof \WP_Post || ! $this->is_refresh_copy( $refresh->ID ) ) { return new \WP_Error( 'adminoptim_invalid_refresh_copy', __( 'The refresh copy is invalid.', 'admin-optimizer' ) ); } $parent_id = $this->get_refresh_parent( $refresh->ID ); $original = get_post( $parent_id ); if ( ! $original instanceof \WP_Post ) { return new \WP_Error( 'adminoptim_missing_refresh_parent', __( 'The original post no longer exists.', 'admin-optimizer' ) ); } $update = [ 'ID' => $parent_id, 'post_title' => $refresh->post_title, 'post_content' => $refresh->post_content, 'post_content_filtered' => $refresh->post_content_filtered, 'post_excerpt' => $refresh->post_excerpt, 'post_parent' => $refresh->post_parent, 'menu_order' => $refresh->menu_order, ]; if ( $this->replacement_enabled( $refresh->ID, 'replace_author', '_republish_replace_author' ) ) { $update['post_author'] = $refresh->post_author; } if ( $this->replacement_enabled( $refresh->ID, 'replace_publish_date', '_republish_replace_publish_date' ) ) { $update['post_date'] = $this->effective_local_date( $refresh ); $update['post_date_gmt'] = get_gmt_from_date( $update['post_date'] ); } if ( $this->replacement_enabled( $refresh->ID, 'replace_modified_date', '_republish_replace_modified_date' ) ) { $update['post_modified'] = $this->effective_local_date( $refresh ); $update['post_modified_gmt'] = get_gmt_from_date( $update['post_modified'] ); } $updated_id = wp_update_post( wp_slash( $update ), true, false ); if ( is_wp_error( $updated_id ) ) { return $updated_id; } /** * Fires after the core fields of a refresh replacement are written, * before cleanup. Pro attaches here to replace taxonomies/custom * fields, delete comments, and update the GUID — Free has no * canonical implementation for those steps on its own. * * @param \WP_Post $refresh The refresh copy. * @param int $parent_id The original post ID being replaced. */ do_action( 'adminoptim_content_refresher_replace', $refresh, $parent_id ); $this->reparent_attachments( $refresh->ID, $parent_id ); update_post_meta( $parent_id, '_republish_last_updated', current_time( 'mysql' ) ); do_action( 'adminoptim_after_republish', $refresh->ID, $parent_id ); $status_result = wp_update_post( [ 'ID' => $refresh->ID, 'post_status' => 'republish-trash', ], true, false ); if ( is_wp_error( $status_result ) ) { return $status_result; } update_post_meta( $refresh->ID, '_republish_status', 'pending-delete' ); return $parent_id; } /** Delete or trash a refresh copy, per the module's configured after-republish action. */ public function delete_refresh_copy( int $refresh_id ): void { if ( 'delete' === ( $this->options['after_republish_action'] ?? 'trash' ) ) { wp_delete_post( $refresh_id, true ); } else { wp_trash_post( $refresh_id ); } } /** Resolve a per-copy yes/no override before the module default. */ private function replacement_enabled( int $refresh_id, string $option, string $meta_key ): bool { $override = get_post_meta( $refresh_id, $meta_key, true ); if ( 'yes' === $override ) { return true; } if ( 'no' === $override ) { return false; } return ! empty( $this->options[ $option ] ); } /** Resolve the effective local publish date/time for a refresh copy. */ private function effective_local_date( \WP_Post $refresh ): string { return get_post_timestamp( $refresh ) <= time() ? $refresh->post_date : current_time( 'mysql' ); } /** Reassign attachments from the refresh copy to the original post. */ private function reparent_attachments( int $refresh_id, int $parent_id ): void { $attachments = get_posts( [ 'post_type' => 'attachment', 'post_parent' => $refresh_id, 'posts_per_page' => -1, 'fields' => 'ids', ] ); foreach ( $attachments as $attachment_id ) { wp_update_post( [ 'ID' => $attachment_id, 'post_parent' => $parent_id, ] ); } } }