| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357 |
- <?php
- /**
- * Module Name: Copy Post
- * Module Description: Enable the option to copy entire posts and pages, including tags and settings
- * Sort Order: 15
- * First Introduced: 7.0
- * Requires Connection: No
- * Auto Activate: No
- * Module Tags: Writing
- * Feature: Writing
- * Additional Search Queries: copy, duplicate
- */
- /**
- * Copy Post class.
- */
- class Jetpack_Copy_Post {
- /**
- * Jetpack_Copy_Post_By_Param constructor.
- * Add row actions to post/page/CPT listing screens.
- * Process any `?copy` param if on a create new post/page/CPT screen.
- *
- * @return void
- */
- public function __construct() {
- if ( 'edit.php' === $GLOBALS['pagenow'] ) {
- add_filter( 'post_row_actions', array( $this, 'add_row_action' ), 10, 2 );
- add_filter( 'page_row_actions', array( $this, 'add_row_action' ), 10, 2 );
- return;
- }
- if ( ! empty( $_GET['jetpack-copy'] ) && 'post-new.php' === $GLOBALS['pagenow'] ) {
- add_action( 'wp_insert_post', array( $this, 'update_post_data' ), 10, 3 );
- add_filter( 'pre_option_default_post_format', '__return_empty_string' );
- }
- }
- /**
- * Update the new (target) post data with the source post data.
- *
- * @param int $target_post_id Target post ID.
- * @param WP_Post $post Target post object (not used).
- * @param bool $update Whether this is an existing post being updated or not.
- * @return void
- */
- public function update_post_data( $target_post_id, $post, $update ) {
- global $wp_version;
- // This `$update` check avoids infinite loops of trying to update our updated post.
- if ( $update ) {
- return;
- }
- $source_post = get_post( $_GET['jetpack-copy'] );
- if ( ! $source_post instanceof WP_Post ||
- ! $this->user_can_access_post( $source_post->ID ) ||
- ! $this->validate_post_type( $source_post ) ) {
- return;
- }
- $update_results = array(
- 'update_content' => $this->update_content( $source_post, $target_post_id ),
- 'update_featured_image' => $this->update_featured_image( $source_post, $target_post_id ),
- 'update_post_format' => $this->update_post_format( $source_post, $target_post_id ),
- 'update_likes_sharing' => $this->update_likes_sharing( $source_post, $target_post_id ),
- 'update_post_type_terms' => $this->update_post_type_terms( $source_post, $target_post_id ),
- );
- // Required to satisfy get_default_post_to_edit(), which has these filters after post creation.
- add_filter( 'default_title', array( $this, 'filter_title' ), 10, 2 );
- add_filter( 'default_content', array( $this, 'filter_content' ), 10, 2 );
- add_filter( 'default_excerpt', array( $this, 'filter_excerpt' ), 10, 2 );
- /*
- * Required to avoid the block editor from adding default blocks according to post format.
- * @todo: simplify once WordPress 5.8 is the minimum required version.
- */
- if ( version_compare( $wp_version, '5.8', '>=' ) ) {
- add_filter( 'block_editor_settings_all', array( $this, 'remove_post_format_template' ) );
- } else {
- add_filter( 'block_editor_settings', array( $this, 'remove_post_format_template' ) );
- }
- /**
- * Fires after all updates have been performed, and default content filters have been added.
- * Allows for any cleanup or post operations, and default content filters can be removed or modified.
- *
- * @module copy-post
- *
- * @since 7.0.0
- *
- * @param WP_Post $source_post Post object that was copied.
- * @param int $target_post_id Target post ID.
- * @param array $update_results Results of all update operations, allowing action to be taken.
- */
- do_action( 'jetpack_copy_post', $source_post, $target_post_id, $update_results );
- }
- /**
- * Determine if the current user has edit access to the source post.
- *
- * @param int $post_id Source post ID (the post being copied).
- * @return bool True if user has the meta cap of `edit_post` for the given post ID, false otherwise.
- */
- protected function user_can_access_post( $post_id ) {
- return current_user_can( 'edit_post', $post_id );
- }
- /**
- * Update the target post's title, content, excerpt, categories, and tags.
- *
- * @param WP_Post $source_post Post object to be copied.
- * @param int $target_post_id Target post ID.
- * @return int 0 on failure, or the updated post ID on success.
- */
- protected function update_content( $source_post, $target_post_id ) {
- $data = array(
- 'ID' => $target_post_id,
- 'post_title' => $source_post->post_title,
- 'post_content' => $source_post->post_content,
- 'post_excerpt' => $source_post->post_excerpt,
- 'comment_status' => $source_post->comment_status,
- 'ping_status' => $source_post->ping_status,
- 'post_category' => wp_get_post_categories( $source_post->ID ),
- 'post_password' => $source_post->post_password,
- 'tags_input' => $source_post->tags_input,
- );
- /**
- * Fires just before the target post is updated with its new data.
- * Allows for final data adjustments before updating the target post.
- *
- * @module copy-post
- *
- * @since 7.0.0
- *
- * @param array $data Post data with which to update the target (new) post.
- * @param WP_Post $source_post Post object being copied.
- * @param int $target_post_id Target post ID.
- */
- $data = apply_filters( 'jetpack_copy_post_data', $data, $source_post, $target_post_id );
- return wp_update_post( $data );
- }
- /**
- * Update terms for post types.
- *
- * @param WP_Post $source_post Post object to be copied.
- * @param int $target_post_id Target post ID.
- * @return array Results of attempts to set each term to the target (new) post.
- */
- protected function update_post_type_terms( $source_post, $target_post_id ) {
- $results = array();
- $bypassed_post_types = apply_filters( 'jetpack_copy_post_bypassed_post_types', array( 'post', 'page' ), $source_post, $target_post_id );
- if ( in_array( $source_post->post_type, $bypassed_post_types, true ) ) {
- return $results;
- }
- $taxonomies = get_object_taxonomies( $source_post, 'objects' );
- foreach ( $taxonomies as $taxonomy ) {
- $terms = wp_get_post_terms( $source_post->ID, $taxonomy->name, array( 'fields' => 'ids' ) );
- $results[] = wp_set_post_terms( $target_post_id, $terms, $taxonomy->name );
- }
- return $results;
- }
- /**
- * Update the target post's featured image.
- *
- * @param WP_Post $source_post Post object to be copied.
- * @param int $target_post_id Target post ID.
- * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
- */
- protected function update_featured_image( $source_post, $target_post_id ) {
- $featured_image_id = get_post_thumbnail_id( $source_post );
- return update_post_meta( $target_post_id, '_thumbnail_id', $featured_image_id );
- }
- /**
- * Update the target post's post format.
- *
- * @param WP_Post $source_post Post object to be copied.
- * @param int $target_post_id Target post ID.
- * @return array|WP_Error|false WP_Error on error, array of affected term IDs on success.
- */
- protected function update_post_format( $source_post, $target_post_id ) {
- $post_format = get_post_format( $source_post );
- return set_post_format( $target_post_id, $post_format );
- }
- /**
- * Ensure the block editor doesn't modify the source post content for non-standard post formats.
- *
- * @param array $settings Settings to be passed into the block editor.
- * @return array Settings with any `template` key removed.
- */
- public function remove_post_format_template( $settings ) {
- unset( $settings['template'] );
- return $settings;
- }
- /**
- * Update the target post's Likes and Sharing statuses.
- *
- * @param WP_Post $source_post Post object to be copied.
- * @param int $target_post_id Target post ID.
- * @return array Array with the results of each update action.
- */
- protected function update_likes_sharing( $source_post, $target_post_id ) {
- $likes = get_post_meta( $source_post->ID, 'switch_like_status', true );
- $sharing = get_post_meta( $source_post->ID, 'sharing_disabled', true );
- if ( '' !== $likes ) {
- $likes_result = update_post_meta( $target_post_id, 'switch_like_status', $likes );
- } else {
- $likes_result = null;
- }
- if ( '' !== $sharing ) {
- $sharing_result = update_post_meta( $target_post_id, 'sharing_disabled', $sharing );
- } else {
- $sharing_result = null;
- }
- return array(
- 'likes' => $likes_result,
- 'sharing' => $sharing_result,
- );
- }
- /**
- * Update the target post's title.
- *
- * @param string $post_title Post title determined by `get_default_post_to_edit()`.
- * @param WP_Post $post Post object of newly-inserted post.
- * @return string Updated post title from source post.
- */
- public function filter_title( $post_title, $post ) {
- return $post->post_title;
- }
- /**
- * Update the target post's content (`post_content`).
- *
- * @param string $post_content Post content determined by `get_default_post_to_edit()`.
- * @param WP_Post $post Post object of newly-inserted post.
- * @return string Updated post content from source post.
- */
- public function filter_content( $post_content, $post ) {
- return $post->post_content;
- }
- /**
- * Update the target post's excerpt.
- *
- * @param string $post_excerpt Post excerpt determined by `get_default_post_to_edit()`.
- * @param WP_Post $post Post object of newly-inserted post.
- * @return string Updated post excerpt from source post.
- */
- public function filter_excerpt( $post_excerpt, $post ) {
- return $post->post_excerpt;
- }
- /**
- * Validate the post type to be used for the target post.
- *
- * @param WP_Post $post Post object of current post in listing.
- * @return bool True if the post type is in a list of supported psot types; false otherwise.
- */
- protected function validate_post_type( $post ) {
- /**
- * Fires when determining if the "Copy" row action should be made available.
- * Allows overriding supported post types.
- *
- * @module copy-post
- *
- * @since 7.0.0
- *
- * @param array Post types supported by default.
- * @param WP_Post $post Post object of current post in listing.
- */
- $valid_post_types = apply_filters(
- 'jetpack_copy_post_post_types',
- array(
- 'post',
- 'page',
- 'jetpack-testimonial',
- 'jetpack-portfolio',
- ),
- $post
- );
- return in_array( $post->post_type, $valid_post_types, true );
- }
- /**
- * Add a "Copy" row action to supported posts/pages/CPTs on list views.
- *
- * @param array $actions Existing actions.
- * @param WP_Post $post Post object of current post in list.
- * @return array Array of updated row actions.
- */
- public function add_row_action( $actions, $post ) {
- if ( ! $this->user_can_access_post( $post->ID ) ||
- ! $post instanceof WP_Post ||
- ! $this->validate_post_type( $post ) ) {
- return $actions;
- }
- $edit_url = add_query_arg(
- array(
- 'post_type' => $post->post_type,
- 'jetpack-copy' => $post->ID,
- ),
- admin_url( 'post-new.php' )
- );
- $edit_action = array(
- 'jetpack-copy' => sprintf(
- '<a href="%s" aria-label="%s">%s</a>',
- esc_url( $edit_url ),
- esc_attr__( 'Copy this post.', 'jetpack' ),
- esc_html__( 'Copy', 'jetpack' )
- ),
- );
- // Insert the Copy action before the Trash action.
- $edit_offset = array_search( 'trash', array_keys( $actions ), true );
- $updated_actions = array_merge(
- array_slice( $actions, 0, $edit_offset ),
- $edit_action,
- array_slice( $actions, $edit_offset )
- );
- /**
- * Fires after the new Copy action has been added to the row actions.
- * Allows changes to the action presentation, or other final checks.
- *
- * @module copy-post
- *
- * @since 7.0.0
- *
- * @param array $updated_actions Updated row actions with the Copy Post action.
- * @param array $actions Original row actions passed to this filter.
- * @param WP_Post $post Post object of current post in listing.
- */
- return apply_filters( 'jetpack_copy_post_row_actions', $updated_actions, $actions, $post );
- }
- }
- /**
- * Instantiate an instance of Jetpack_Copy_Post on the `admin_init` hook.
- */
- function jetpack_copy_post_init() {
- new Jetpack_Copy_Post();
- }
- add_action( 'admin_init', 'jetpack_copy_post_init' );
|