1/**
2 * Duplicate WordPress Posts, Pages, and Custom Post Types as Drafts
3 *
4 * This code snippet enables the duplication of WordPress posts, pages, and all registered custom post types (CPTs).
5 * It adds a 'Duplicate' link to the row actions for each item in the admin dashboard. When clicked, this link
6 * triggers the duplication of the selected item, creating a new draft with the same content, custom fields,
7 * and taxonomies.
8 *
9 * Functions:
10 * 1. `duplicate_post_as_draft`: Handles the duplication process. It copies the post data, including title,
11 * content, excerpt, and custom fields, and creates a new post with a 'draft' status. It also maintains
12 * the taxonomy terms (like categories and tags) from the original post.
13 * 2. `duplicate_post_link`: Adds the 'Duplicate' action link to the WordPress admin interface for each post,
14 * page, and custom post type. This link uses WordPress's built-in nonce functionality for security.
15 * 3. `apply_duplicate_post_link_to_cpts`: Dynamically applies the duplicate post link function to all public
16 * post types, ensuring the 'Duplicate' link appears for any custom post types registered on the site.
17 *
18 * Author: Mark Harris
19 * URI: https://www.christchurchwebsolutions.co.uk
20 */
21
22function duplicate_post_as_draft() {
23 global $wpdb;
24
25 // Verify the nonce for security
26 $nonce_action = 'duplicate_post_as_draft';
27 $nonce_name = 'duplicate_nonce';
28 if (!isset($_GET[$nonce_name]) || !wp_verify_nonce($_GET[$nonce_name], $nonce_action)) {
29 wp_die(esc_html__('Security check failed.', 'wpturbo'));
30 }
31
32 // Check if the 'post' parameter is set in either GET or POST request
33 $post_id = filter_input(INPUT_GET, 'post', FILTER_SANITIZE_NUMBER_INT) ?: filter_input(INPUT_POST, 'post', FILTER_SANITIZE_NUMBER_INT);
34
35 if (!$post_id) {
36 wp_die(esc_html__('No post to duplicate has been supplied!', 'wpturbo'));
37 }
38
39 // Check if the post exists
40 $post = get_post($post_id);
41 if (!$post) {
42 wp_die(esc_html(sprintf(__('Post creation failed, could not find original post: %s', 'wpturbo'), $post_id)));
43 }
44
45 $current_user = wp_get_current_user();
46 $new_post_author = $current_user->ID;
47
48 $args = [
49 "comment_status" => $post->comment_status,
50 "ping_status" => $post->ping_status,
51 "post_author" => $new_post_author,
52 "post_content" => $post->post_content,
53 "post_excerpt" => $post->post_excerpt,
54 "post_name" => $post->post_name,
55 "post_parent" => $post->post_parent,
56 "post_password" => $post->post_password,
57 "post_status" => "draft",
58 "post_title" => $post->post_title . " (Copy)",
59 "post_type" => $post->post_type,
60 "to_ping" => $post->to_ping,
61 "menu_order" => $post->menu_order
62 ];
63
64 $new_post_id = wp_insert_post($args);
65
66 $taxonomies = get_object_taxonomies($post->post_type);
67 foreach ($taxonomies as $taxonomy) {
68 $post_terms = wp_get_object_terms($post_id, $taxonomy, ["fields" => "slugs"]);
69 wp_set_object_terms($new_post_id, $post_terms, $taxonomy, false);
70 }
71
72 $post_meta_infos = $wpdb->get_results(
73 $wpdb->prepare("SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE post_id = %d", $post_id)
74 );
75 if (count($post_meta_infos) != 0) {
76 foreach ($post_meta_infos as $meta_info) {
77 $meta_key = $meta_info->meta_key;
78 $meta_value = sanitize_meta($meta_info->meta_key, $meta_info->meta_value, "post");
79 $wpdb->insert($wpdb->postmeta, [
80 "post_id" => $new_post_id,
81 "meta_key" => $meta_key,
82 "meta_value" => $meta_value
83 ]);
84 }
85 }
86
87 // Redirect to the post list screen and show a success message
88 $redirect_url = admin_url("edit.php?post_type=" . $post->post_type);
89 wp_redirect(add_query_arg("message", "101", $redirect_url));
90 exit();
91}
92
93add_action("admin_action_duplicate_post_as_draft", "duplicate_post_as_draft");
94
95function duplicate_post_link($actions, $post) {
96 if (current_user_can('edit_posts')) {
97 $actions['duplicate'] = '<a href="' .
98 wp_nonce_url(
99 admin_url("admin.php?action=duplicate_post_as_draft&post=" . $post->ID),
100 'duplicate_post_as_draft',
101 'duplicate_nonce'
102 ) .
103 '" title="' . esc_attr__('Duplicate this item', 'wpturbo') .
104 '" rel="permalink">' . esc_html__('Duplicate', 'wpturbo') . '</a>';
105 }
106 return $actions;
107}
108
109add_filter("post_row_actions", "duplicate_post_link", 10, 2);
110add_filter("page_row_actions", "duplicate_post_link", 10, 2);
111
112function apply_duplicate_post_link_to_cpts() {
113 $post_types = get_post_types(["public" => true], "names");
114 foreach ($post_types as $post_type) {
115 add_filter("{$post_type}_row_actions", "duplicate_post_link", 10, 2);
116 }
117}
118
119add_action("admin_init", "apply_duplicate_post_link_to_cpts");
120
121function show_duplicate_admin_notice() {
122 if (isset($_GET['message']) && $_GET['message'] === '101') {
123 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html('Post duplicated successfully.') . '</p></div>';
124 }
125}
126
127add_action('admin_notices', 'show_duplicate_admin_notice');