| @@ -12,13 +12,13 @@ | ||
| 12 | 12 | /** |
| 13 | 13 | * Duplicator Class |
| 14 | 14 | */ |
| 15 | 15 | |
| 16 | -if (!class_exists('BdThemes_Duplicator')) : | |
| 16 | +if (!class_exists(__NAMESPACE__ . '\\BdThemes_Duplicator')) : | |
| 17 | 17 | class BdThemes_Duplicator { |
| 18 | 18 | |
| 19 | 19 | public function __construct() { |
| 20 | - add_action('admin_action_bdt_duplicate_as_draft', [$this, 'bdt_duplicate_as_draft']); | |
| 20 | + add_action('admin_action_ultimate_post_kit_duplicate_as_draft', [$this, 'bdt_duplicate_as_draft']); | |
| 21 | 21 | add_filter('post_row_actions', [$this, 'bdt_duplicate_post_link'], 10, 2); |
| 22 | 22 | add_filter('page_row_actions', [$this, 'bdt_duplicate_post_link'], 10, 2); |
| 23 | 23 | } |
| 24 | 24 | |
| @@ -27,40 +27,61 @@ | ||
| 27 | 27 | if (!current_user_can('edit_posts')) { |
| 28 | 28 | wp_die('You don\'t have permission to duplicate it; please go back!'); |
| 29 | 29 | } |
| 30 | 30 | |
| 31 | - if (!(isset($_GET['post']) || isset($_POST['post']) || (isset($_REQUEST['action']) && 'bdt_duplicate_as_draft' == $_REQUEST['action']))) { | |
| 31 | + if (!(isset($_GET['post']) || isset($_POST['post']) || (isset($_REQUEST['action']) && 'ultimate_post_kit_duplicate_as_draft' == $_REQUEST['action']))) { | |
| 32 | 32 | wp_die('No post to duplicate has been supplied!'); |
| 33 | 33 | } |
| 34 | 34 | |
| 35 | 35 | /** |
| 36 | - * Nonce verification | |
| 36 | + * get the original post id | |
| 37 | + * | |
| 38 | + * This has to be read before the nonce check because the nonce action is bound | |
| 39 | + * to the post being duplicated (see bdt_duplicate_post_link()). The value is | |
| 40 | + * cast to an integer and used only to build that action string; nothing is read | |
| 41 | + * or written with it until the nonce and capability checks below have passed. | |
| 37 | 42 | */ |
| 38 | - if (!isset($_GET['duplicate_nonce']) || !wp_verify_nonce($_GET['duplicate_nonce'], basename(__FILE__))) { | |
| 43 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing -- only used to construct the nonce action, which is verified on the next statement. | |
| 44 | + $post_id = isset($_GET['post']) ? absint($_GET['post']) : absint($_POST['post'] ?? 0); | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * Nonce verification. | |
| 48 | + * | |
| 49 | + * The nonce is bound to the post being duplicated, so one nonce cannot be | |
| 50 | + * replayed against every other post (and post type) on the site. | |
| 51 | + */ | |
| 52 | + if (!isset($_GET['duplicate_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['duplicate_nonce'])), 'upk_duplicate_post_' . $post_id)) { | |
| 39 | 53 | return; |
| 40 | 54 | } |
| 41 | 55 | |
| 42 | 56 | /** |
| 43 | - * get the original post id | |
| 44 | - */ | |
| 45 | - $post_id = (isset($_GET['post']) ? absint($_GET['post']) : absint($_POST['post'])); | |
| 46 | - /** | |
| 47 | 57 | * and all the original post data then |
| 48 | 58 | */ |
| 49 | 59 | $post = get_post($post_id); |
| 50 | 60 | |
| 61 | + if (!$post) { | |
| 62 | + wp_die(esc_html('Failed. Not Found Post: ' . $post_id)); | |
| 63 | + } | |
| 64 | + | |
| 51 | 65 | /** |
| 52 | - * if you don't want current user to be the new post author, | |
| 66 | + * Authorise against THIS post, not against a generic role capability. | |
| 67 | + * | |
| 68 | + * edit_others_posts is only the capability for the built-in 'post' type; it | |
| 69 | + * grants nothing over a post type registered with its own capability set. Use | |
| 70 | + * the meta capability so WordPress maps it through the target post type, and | |
| 71 | + * check create_posts separately because duplicating creates a new object. | |
| 53 | 72 | */ |
| 54 | - $current_user_id = get_current_user_id(); | |
| 73 | + if (!current_user_can('edit_post', $post_id)) { | |
| 74 | + wp_die('You don\'t have permission to duplicate it; please go back!'); | |
| 75 | + } | |
| 55 | 76 | |
| 56 | - if (current_user_can('manage_options') || current_user_can('edit_others_posts')) { | |
| 57 | - $this->duplicate_edit_post($post_id); | |
| 58 | - } else if (current_user_can('edit_posts') && $post->post_author == $current_user_id) { | |
| 59 | - $this->duplicate_edit_post($post_id); | |
| 60 | - } else { | |
| 77 | + $post_type_object = get_post_type_object($post->post_type); | |
| 78 | + | |
| 79 | + if (!$post_type_object || !current_user_can($post_type_object->cap->create_posts)) { | |
| 61 | 80 | wp_die('You don\'t have permission to duplicate it; please go back!'); |
| 62 | 81 | } |
| 82 | + | |
| 83 | + $this->duplicate_edit_post($post_id); | |
| 63 | 84 | } |
| 64 | 85 | |
| 65 | 86 | /** |
| 66 | 87 | * duplicate edit post |
| @@ -123,21 +144,27 @@ | ||
| 123 | 144 | |
| 124 | 145 | /** |
| 125 | 146 | * duplicate all post meta just in two SQL queries |
| 126 | 147 | */ |
| 127 | - $bdt_post_meta_infos = $wpdb->get_results("SELECT meta_key, meta_value FROM $wpdb->postmeta WHERE post_id=$bdt_post_id"); | |
| 148 | + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- one-off admin duplicate action, caching not applicable. | |
| 149 | + $bdt_post_meta_infos = $wpdb->get_results($wpdb->prepare("SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d", $post_id)); | |
| 128 | 150 | |
| 129 | 151 | if (is_array($bdt_post_meta_infos)) { |
| 130 | - $bdt_sql_query = "INSERT INTO {$wpdb->postmeta} ( post_id, meta_key, meta_value ) VALUES "; | |
| 131 | 152 | $bdt_sql_query_sel = []; |
| 153 | + $bdt_sql_values = []; | |
| 132 | 154 | |
| 133 | 155 | foreach ($bdt_post_meta_infos as $bdt_meta_info) { |
| 134 | - $bdt_meta_value = wp_slash($bdt_meta_info->meta_value); | |
| 135 | - $bdt_sql_query_sel[] = "( $bdt_new_post_id, '{$bdt_meta_info->meta_key}', '{$bdt_meta_value}' )"; | |
| 156 | + $bdt_sql_query_sel[] = '( %d, %s, %s )'; | |
| 157 | + $bdt_sql_values[] = $bdt_new_post_id; | |
| 158 | + $bdt_sql_values[] = $bdt_meta_info->meta_key; | |
| 159 | + $bdt_sql_values[] = wp_slash($bdt_meta_info->meta_value); | |
| 136 | 160 | } |
| 137 | 161 | |
| 138 | - $bdt_sql_query .= implode(', ', $bdt_sql_query_sel) . ';'; | |
| 139 | - $wpdb->query($bdt_sql_query); | |
| 162 | + if (!empty($bdt_sql_query_sel)) { | |
| 163 | + $bdt_sql_query = "INSERT INTO {$wpdb->postmeta} ( post_id, meta_key, meta_value ) VALUES " . implode(', ', $bdt_sql_query_sel); | |
| 164 | + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query is built only from static "%d, %s, %s" placeholders and the trusted {$wpdb->postmeta} table name; all values are bound via $wpdb->prepare(). | |
| 165 | + $wpdb->query($wpdb->prepare($bdt_sql_query, $bdt_sql_values)); | |
| 166 | + } | |
| 140 | 167 | |
| 141 | 168 | /** |
| 142 | 169 | * fix template type issues |
| 143 | 170 | */ |
| @@ -161,14 +188,15 @@ | ||
| 161 | 188 | |
| 162 | 189 | $current_post_type = get_post_type($post_id); |
| 163 | 190 | |
| 164 | 191 | if (is_array($bdt_names) && in_array($current_post_type, $bdt_names)) { |
| 165 | - wp_redirect(admin_url('edit.php?post_type=' . $current_post_type)); | |
| 192 | + wp_safe_redirect(admin_url('edit.php?post_type=' . $current_post_type)); | |
| 193 | + exit; | |
| 166 | 194 | } |
| 167 | 195 | |
| 168 | 196 | exit; |
| 169 | 197 | } else { |
| 170 | - wp_die('Failed. Not Found Post: ' . $post_id); | |
| 198 | + wp_die(esc_html('Failed. Not Found Post: ' . $post_id)); | |
| 171 | 199 | } |
| 172 | 200 | } |
| 173 | 201 | |
| 174 | 202 | |
| @@ -173,15 +201,15 @@ | ||
| 173 | 201 | |
| 174 | 202 | |
| 175 | 203 | public function bdt_duplicate_post_link($actions, $post) { |
| 176 | 204 | |
| 177 | - if (current_user_can('manage_options') || current_user_can('edit_others_posts')) { | |
| 205 | + if (current_user_can('edit_post', $post->ID)) { | |
| 178 | 206 | if ($post->post_type == 'post') { |
| 179 | - $actions['duplicate'] = '<a href="' . wp_nonce_url('admin.php?action=bdt_duplicate_as_draft&post=' . $post->ID, basename(__FILE__), 'duplicate_nonce') . '" title="Duplicate this post" rel="permalink">' . esc_html_x("Duplicate Post", "Admin String", "ultimate-post-kit") . '</a>'; | |
| 207 | + $actions['duplicate'] = '<a href="' . wp_nonce_url('admin.php?action=ultimate_post_kit_duplicate_as_draft&post=' . $post->ID, 'upk_duplicate_post_' . $post->ID, 'duplicate_nonce') . '" title="Duplicate this post" rel="permalink">' . esc_html_x("Duplicate Post", "Admin String", "ultimate-post-kit") . '</a>'; | |
| 180 | 208 | } elseif ($post->post_type == 'page') { |
| 181 | - $actions['duplicate'] = '<a href="' . wp_nonce_url('admin.php?action=bdt_duplicate_as_draft&post=' . $post->ID, basename(__FILE__), 'duplicate_nonce') . '" title="Duplicate this page" rel="permalink">' . esc_html_x("Duplicate Page", "Admin String", "ultimate-post-kit") . '</a>'; | |
| 209 | + $actions['duplicate'] = '<a href="' . wp_nonce_url('admin.php?action=ultimate_post_kit_duplicate_as_draft&post=' . $post->ID, 'upk_duplicate_post_' . $post->ID, 'duplicate_nonce') . '" title="Duplicate this page" rel="permalink">' . esc_html_x("Duplicate Page", "Admin String", "ultimate-post-kit") . '</a>'; | |
| 182 | 210 | } elseif ($post->post_type == 'elementor_library') { |
| 183 | - $actions['duplicate'] = '<a href="' . wp_nonce_url('admin.php?action=bdt_duplicate_as_draft&post=' . $post->ID, basename(__FILE__), 'duplicate_nonce') . '" title="Duplicate this template" rel="permalink">' . esc_html_x("Duplicate Template", "Admin String", "ultimate-post-kit") . '</a>'; | |
| 211 | + $actions['duplicate'] = '<a href="' . wp_nonce_url('admin.php?action=ultimate_post_kit_duplicate_as_draft&post=' . $post->ID, 'upk_duplicate_post_' . $post->ID, 'duplicate_nonce') . '" title="Duplicate this template" rel="permalink">' . esc_html_x("Duplicate Template", "Admin String", "ultimate-post-kit") . '</a>'; | |
| 184 | 212 | } |
| 185 | 213 | } |
| 186 | 214 | return $actions; |
| 187 | 215 | } |
| @@ -187,6 +215,15 @@ | ||
| 187 | 215 | } |
| 188 | 216 | } |
| 189 | 217 | endif; |
| 190 | 218 | |
| 191 | - | |
| 219 | +/** | |
| 220 | + * Instantiate the namespaced class. | |
| 221 | + * | |
| 222 | + * The guard above and this statement must resolve to the same class. An | |
| 223 | + * unqualified class_exists() string is always resolved against the global | |
| 224 | + * namespace, so a sibling plugin declaring a global \BdThemes_Duplicator | |
| 225 | + * (Live Copy Paste does) used to satisfy the old guard and skip the | |
| 226 | + * declaration, while this line still asked for | |
| 227 | + * UltimatePostKit\Includes\BdThemes_Duplicator -- a fatal error. | |
| 228 | + */ | |
| 192 | 229 | new BdThemes_Duplicator(); |