Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
/*
* Function for post duplication. Dups appear as drafts. User is redirected to the edit screen
*/
function rd_duplicate_post_as_draft(){
global $wpdb;
if (! ( isset( $_GET['post']) || isset( $_POST['post']) || ( isset($_REQUEST['action']) && 'rd_duplicate_post_as_draft' == $_REQUEST['action'] ) ) ) {
wp_die('No post to duplicate has been supplied!');
}
/*
* Nonce verification
*/
if ( !isset( $_GET['duplicate_nonce'] ) || !wp_verify_nonce( $_GET['duplicate_nonce'], basename( __FILE__ ) ) )
return;
/*
* get the original post id
*/
$post_id = (isset($_GET['post']) ? absint( $_GET['post'] ) : absint( $_POST['post'] ) );
/*
* and all the original post data then
*/
$post = get_post( $post_id );
/*
* if you don't want current user to be the new post author,
* then change next couple of lines to this: $new_post_author = $post->post_author;
*/
$current_user = wp_get_current_user();
$new_post_author = $current_user->ID;
/*
* if post data exists, create the post duplicate
*/
if (isset( $post ) && $post != null) {
/*
* new post data array
*/
$args = array(
'comment_status' => $post->comment_status,
'ping_status' => $post->ping_status,
'post_author' => $new_post_author,
'post_content' => $post->post_content,
'post_excerpt' => $post->post_excerpt,
'post_name' => $post->post_name,
'post_parent' => $post->post_parent,
'post_password' => $post->post_password,
'post_status' => 'draft',
'post_title' => $post->post_title,
'post_type' => $post->post_type,
'to_ping' => $post->to_ping,
'menu_order' => $post->menu_order
);
/*
* insert the post by wp_insert_post() function
*/
$new_post_id = wp_insert_post( $args );
/*
* get all current post terms ad set them to the new post draft
*/
$taxonomies = get_object_taxonomies($post->post_type); // returns array of taxonomy names for post type, ex array("category", "post_tag");
foreach ($taxonomies as $taxonomy) {
$post_terms = wp_get_object_terms($post_id, $taxonomy, array('fields' => 'slugs'));
wp_set_object_terms($new_post_id, $post_terms, $taxonomy, false);
}
/*
* duplicate all post meta just in two SQL queries
*/
$post_meta_infos = $wpdb->get_results("SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE post_id=$post_id");
if (count($post_meta_infos)!=0) {
$sql_query = "INSERT INTO $wpdb->postmeta (post_id, meta_key, meta_value) ";
foreach ($post_meta_infos as $meta_info) {
$meta_key = $meta_info->meta_key;
if( $meta_key == '_wp_old_slug' ) continue;
$meta_value = addslashes($meta_info->meta_value);
$sql_query_sel[]= "SELECT $new_post_id, '$meta_key', '$meta_value'";
}
$sql_query.= implode(" UNION ALL ", $sql_query_sel);
$wpdb->query($sql_query);
}
/*
* finally, redirect to the edit post screen for the new draft
*/
wp_redirect( admin_url( 'post.php?action=edit&post=' . $new_post_id ) );
exit;
} else {
wp_die('Post creation failed, could not find original post: ' . $post_id);
}
}
add_action( 'admin_action_rd_duplicate_post_as_draft', 'rd_duplicate_post_as_draft' );
/*
* Add the duplicate link to action list for post_row_actions
*/
function rd_duplicate_post_link( $actions, $post ) {
if (current_user_can('edit_posts')) {
$actions['duplicate'] = '<a href="' . wp_nonce_url('admin.php?action=rd_duplicate_post_as_draft&post=' . $post->ID, basename(__FILE__), 'duplicate_nonce' ) . '" title="Duplicate this item" rel="permalink">Duplicate</a>';
}
return $actions;
}
add_filter( 'post_row_actions', 'rd_duplicate_post_link', 10, 2 );
@mark2090

This comment has been minimized.

Copy link

@mark2090 mark2090 commented Sep 17, 2020

WordPress will throw an error if you repeat this code for pages, because rd_duplicate_post_as_draft cannot be called twice.

What worked for me, to also do this for pages, is to change all instances of rd_duplicate_post syntax to rd_duplicate_page.

Also you have to remove the last line and replace it with:

add_filter('page_row_actions', 'rd_duplicate_post_link', 10, 2);

Then the "duplicate" link is seen under all posts or pages listed in WordPress when you hover each one. Plus, it copies all categories, tags, and even all the meta data you entered if you use "All in one SEO" for a page or post. Quite convenient!

@Tysss

This comment has been minimized.

Copy link

@Tysss Tysss commented Apr 27, 2021

Thank you for sharing the code! Are you the original writer of this code?

There is a slight security/capability issue in this - you are not checking which post the user is duplicating before executing the process.
i.e, if User A has admin role, and User B has author role, user B could also duplicate posts he does not have capabilities to (for example pages or custom post types). All they would need to do is change the post ID in the link URL.

This problem could be solved by verifying the nonce with post ID in the action. Example below:
Row 103:
$actions['duplicate'] = '<a href="' . wp_nonce_url('admin.php?action=rd_duplicate_post_as_draft&post=' . $post->ID, basename(__FILE__). $post->ID, 'duplicate_nonce' ) . '" title="Duplicate this item" rel="permalink">Duplicate</a>';
Row 13:
if ( !isset( $_GET['duplicate_nonce'] ) || !wp_verify_nonce( $_GET['duplicate_nonce'], basename( __FILE__ ). $_GET['post'] ) )

(I did not test either of the codes - original or modified, so I could be mistaken.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment