duplicate-post
Last commit date
compat
3 months ago
css
3 months ago
js
3 months ago
src
3 months ago
vendor
2 months ago
admin-functions.php
3 months ago
common-functions.php
3 months ago
duplicate-post.php
2 months ago
duplicate_post_yoast_icon-125x125.png
5 years ago
gpl-2.0.txt
14 years ago
options.php
3 months ago
readme.txt
2 weeks ago
admin-functions.php
783 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Backend functions. |
| 4 | * |
| 5 | * @package Yoast\WP\Duplicate_Post |
| 6 | * @since 2.0 |
| 7 | */ |
| 8 | |
| 9 | if ( ! is_admin() ) { |
| 10 | return; |
| 11 | } |
| 12 | |
| 13 | use Yoast\WP\Duplicate_Post\UI\Newsletter; |
| 14 | use Yoast\WP\Duplicate_Post\Utils; |
| 15 | |
| 16 | require_once DUPLICATE_POST_PATH . 'options.php'; |
| 17 | |
| 18 | require_once DUPLICATE_POST_PATH . 'compat/wpml-functions.php'; |
| 19 | require_once DUPLICATE_POST_PATH . 'compat/jetpack-functions.php'; |
| 20 | |
| 21 | /** |
| 22 | * Wrapper for the option 'duplicate_post_version'. |
| 23 | * |
| 24 | * @return mixed |
| 25 | */ |
| 26 | function duplicate_post_get_installed_version() { |
| 27 | return get_option( 'duplicate_post_version' ); |
| 28 | } |
| 29 | |
| 30 | /** |
| 31 | * Wrapper for the defined constant DUPLICATE_POST_CURRENT_VERSION. |
| 32 | * |
| 33 | * @return string |
| 34 | */ |
| 35 | function duplicate_post_get_current_version() { |
| 36 | return DUPLICATE_POST_CURRENT_VERSION; |
| 37 | } |
| 38 | |
| 39 | add_action( 'admin_init', 'duplicate_post_admin_init' ); |
| 40 | |
| 41 | /** |
| 42 | * Adds handlers depending on the options. |
| 43 | * |
| 44 | * @return void |
| 45 | */ |
| 46 | function duplicate_post_admin_init() { |
| 47 | duplicate_post_plugin_upgrade(); |
| 48 | |
| 49 | if ( intval( get_site_option( 'duplicate_post_show_notice' ) ) === 1 ) { |
| 50 | if ( is_multisite() ) { |
| 51 | add_action( 'network_admin_notices', 'duplicate_post_show_update_notice' ); |
| 52 | } |
| 53 | else { |
| 54 | add_action( 'admin_notices', 'duplicate_post_show_update_notice' ); |
| 55 | } |
| 56 | add_action( 'wp_ajax_duplicate_post_dismiss_notice', 'duplicate_post_dismiss_notice' ); |
| 57 | } |
| 58 | |
| 59 | add_action( 'duplicate_post_after_duplicated', 'duplicate_post_copy_post_meta_info', 10, 2 ); |
| 60 | |
| 61 | if ( intval( get_option( 'duplicate_post_copychildren' ) ) === 1 ) { |
| 62 | add_action( 'duplicate_post_after_duplicated', 'duplicate_post_copy_children', 20, 3 ); |
| 63 | } |
| 64 | |
| 65 | if ( intval( get_option( 'duplicate_post_copyattachments' ) ) === 1 ) { |
| 66 | add_action( 'duplicate_post_after_duplicated', 'duplicate_post_copy_attachments', 30, 2 ); |
| 67 | } |
| 68 | |
| 69 | if ( intval( get_option( 'duplicate_post_copycomments' ) ) === 1 ) { |
| 70 | add_action( 'duplicate_post_after_duplicated', 'duplicate_post_copy_comments', 40, 2 ); |
| 71 | } |
| 72 | |
| 73 | add_action( 'duplicate_post_after_duplicated', 'duplicate_post_copy_post_taxonomies', 50, 2 ); |
| 74 | |
| 75 | add_filter( 'plugin_row_meta', 'duplicate_post_add_plugin_links', 10, 2 ); |
| 76 | } |
| 77 | |
| 78 | /** |
| 79 | * Plugin upgrade. |
| 80 | * |
| 81 | * @return void |
| 82 | */ |
| 83 | function duplicate_post_plugin_upgrade() { |
| 84 | $installed_version = duplicate_post_get_installed_version(); |
| 85 | |
| 86 | if ( duplicate_post_get_current_version() === $installed_version ) { |
| 87 | return; |
| 88 | } |
| 89 | |
| 90 | if ( empty( $installed_version ) ) { |
| 91 | // Get default roles. |
| 92 | $default_roles = [ |
| 93 | 'editor', |
| 94 | 'administrator', |
| 95 | 'wpseo_manager', |
| 96 | 'wpseo_editor', |
| 97 | ]; |
| 98 | |
| 99 | foreach ( $default_roles as $name ) { |
| 100 | $role = get_role( $name ); |
| 101 | if ( ! empty( $role ) ) { |
| 102 | $role->add_cap( 'copy_posts' ); |
| 103 | } |
| 104 | } |
| 105 | add_option( 'duplicate_post_show_notice', 1 ); |
| 106 | } |
| 107 | else { |
| 108 | update_option( 'duplicate_post_show_notice', 0 ); |
| 109 | } |
| 110 | |
| 111 | $show_links_in_defaults = [ |
| 112 | 'row' => '1', |
| 113 | 'adminbar' => '1', |
| 114 | 'submitbox' => '1', |
| 115 | 'bulkactions' => '1', |
| 116 | ]; |
| 117 | |
| 118 | add_option( 'duplicate_post_copytitle', '1' ); |
| 119 | add_option( 'duplicate_post_copydate', '0' ); |
| 120 | add_option( 'duplicate_post_copystatus', '0' ); |
| 121 | add_option( 'duplicate_post_copyslug', '0' ); |
| 122 | add_option( 'duplicate_post_copyexcerpt', '1' ); |
| 123 | add_option( 'duplicate_post_copycontent', '1' ); |
| 124 | add_option( 'duplicate_post_copythumbnail', '1' ); |
| 125 | add_option( 'duplicate_post_copytemplate', '1' ); |
| 126 | add_option( 'duplicate_post_copyformat', '1' ); |
| 127 | add_option( 'duplicate_post_copyauthor', '0' ); |
| 128 | add_option( 'duplicate_post_copypassword', '0' ); |
| 129 | add_option( 'duplicate_post_copyattachments', '0' ); |
| 130 | add_option( 'duplicate_post_copychildren', '0' ); |
| 131 | add_option( 'duplicate_post_copycomments', '0' ); |
| 132 | add_option( 'duplicate_post_copymenuorder', '1' ); |
| 133 | add_option( 'duplicate_post_taxonomies_blacklist', [] ); |
| 134 | add_option( 'duplicate_post_blacklist', '' ); |
| 135 | add_option( 'duplicate_post_types_enabled', [ 'post', 'page' ] ); |
| 136 | add_option( 'duplicate_post_show_original_column', '0' ); |
| 137 | add_option( 'duplicate_post_show_original_in_post_states', '0' ); |
| 138 | add_option( 'duplicate_post_show_original_meta_box', '0' ); |
| 139 | add_option( |
| 140 | 'duplicate_post_show_link', |
| 141 | [ |
| 142 | 'new_draft' => '1', |
| 143 | 'clone' => '1', |
| 144 | 'rewrite_republish' => '1', |
| 145 | ], |
| 146 | ); |
| 147 | add_option( 'duplicate_post_show_link_in', $show_links_in_defaults ); |
| 148 | |
| 149 | $taxonomies_blacklist = get_option( 'duplicate_post_taxonomies_blacklist', [] ); |
| 150 | if ( empty( $taxonomies_blacklist ) ) { |
| 151 | $taxonomies_blacklist = []; |
| 152 | } |
| 153 | elseif ( ! is_array( $taxonomies_blacklist ) ) { |
| 154 | $taxonomies_blacklist = [ $taxonomies_blacklist ]; |
| 155 | } |
| 156 | if ( in_array( 'post_format', $taxonomies_blacklist, true ) ) { |
| 157 | update_option( 'duplicate_post_copyformat', 0 ); |
| 158 | $taxonomies_blacklist = array_diff( $taxonomies_blacklist, [ 'post_format' ] ); |
| 159 | update_option( 'duplicate_post_taxonomies_blacklist', $taxonomies_blacklist ); |
| 160 | } |
| 161 | |
| 162 | $meta_blacklist = explode( ',', get_option( 'duplicate_post_blacklist' ) ); |
| 163 | $meta_blacklist = array_map( 'trim', $meta_blacklist ); |
| 164 | if ( in_array( '_wp_page_template', $meta_blacklist, true ) ) { |
| 165 | update_option( 'duplicate_post_copytemplate', 0 ); |
| 166 | $meta_blacklist = array_diff( $meta_blacklist, [ '_wp_page_template' ] ); |
| 167 | } |
| 168 | if ( in_array( '_thumbnail_id', $meta_blacklist, true ) ) { |
| 169 | update_option( 'duplicate_post_copythumbnail', 0 ); |
| 170 | $meta_blacklist = array_diff( $meta_blacklist, [ '_thumbnail_id' ] ); |
| 171 | } |
| 172 | update_option( 'duplicate_post_blacklist', implode( ',', $meta_blacklist ) ); |
| 173 | |
| 174 | if ( version_compare( $installed_version, '4.0.0' ) < 0 ) { |
| 175 | // Migrate the 'Show links in' options to the new array-based structure. |
| 176 | duplicate_post_migrate_show_links_in_options( $show_links_in_defaults ); |
| 177 | } |
| 178 | |
| 179 | delete_site_option( 'duplicate_post_version' ); |
| 180 | update_option( 'duplicate_post_version', duplicate_post_get_current_version() ); |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * Runs the upgrade routine for version 4.0 to update the options in the database. |
| 185 | * |
| 186 | * @param array $defaults The default options to fall back on. |
| 187 | * |
| 188 | * @return void |
| 189 | */ |
| 190 | function duplicate_post_migrate_show_links_in_options( $defaults ) { |
| 191 | $options_to_migrate = [ |
| 192 | 'duplicate_post_show_row' => 'row', |
| 193 | 'duplicate_post_show_adminbar' => 'adminbar', |
| 194 | 'duplicate_post_show_submitbox' => 'submitbox', |
| 195 | 'duplicate_post_show_bulkactions' => 'bulkactions', |
| 196 | ]; |
| 197 | |
| 198 | $new_options = []; |
| 199 | foreach ( $options_to_migrate as $old => $new ) { |
| 200 | $new_options[ $new ] = get_option( $old, $defaults[ $new ] ); |
| 201 | |
| 202 | delete_option( $old ); |
| 203 | } |
| 204 | |
| 205 | update_option( 'duplicate_post_show_link_in', $new_options ); |
| 206 | } |
| 207 | |
| 208 | /** |
| 209 | * Shows the welcome notice. |
| 210 | * |
| 211 | * @global string $wp_version The WordPress version string. |
| 212 | * |
| 213 | * @return void |
| 214 | */ |
| 215 | function duplicate_post_show_update_notice() { |
| 216 | if ( ! current_user_can( 'manage_options' ) ) { |
| 217 | return; |
| 218 | } |
| 219 | |
| 220 | $current_screen = get_current_screen(); |
| 221 | if ( empty( $current_screen ) |
| 222 | || empty( $current_screen->base ) |
| 223 | || ( $current_screen->base !== 'dashboard' && $current_screen->base !== 'plugins' ) |
| 224 | ) { |
| 225 | return; |
| 226 | } |
| 227 | |
| 228 | $title = sprintf( |
| 229 | /* translators: %s: Yoast Duplicate Post. */ |
| 230 | esc_html__( 'You\'ve successfully installed %s!', 'duplicate-post' ), |
| 231 | 'Yoast Duplicate Post', |
| 232 | ); |
| 233 | |
| 234 | $img_path = plugins_url( '/duplicate_post_yoast_icon-125x125.png', __FILE__ ); |
| 235 | |
| 236 | echo '<div id="duplicate-post-notice" class="notice is-dismissible" style="display: flex; align-items: flex-start;"> |
| 237 | <img src="' . esc_url( $img_path ) . '" alt="" style="margin: 1em 1em 1em 0; width: 130px; align-self: center;"/> |
| 238 | <div style="margin: 0.5em"> |
| 239 | <h1 style="font-size: 14px; color: #a4286a; font-weight: 600; margin-top: 8px;">' . $title . '</h1>' // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: escaped properly above. |
| 240 | . Newsletter::newsletter_signup_form() // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: escaped in newsletter.php. |
| 241 | . '</div> |
| 242 | </div>'; |
| 243 | |
| 244 | echo "<script> |
| 245 | function duplicate_post_dismiss_notice(){ |
| 246 | var data = { |
| 247 | 'action': 'duplicate_post_dismiss_notice', |
| 248 | }; |
| 249 | |
| 250 | jQuery.post(ajaxurl, data, function(response) { |
| 251 | jQuery('#duplicate-post-notice').hide(); |
| 252 | }); |
| 253 | } |
| 254 | |
| 255 | jQuery(document).ready(function(){ |
| 256 | jQuery('body').on('click', '.notice-dismiss', function(){ |
| 257 | duplicate_post_dismiss_notice(); |
| 258 | }); |
| 259 | }); |
| 260 | </script>"; |
| 261 | } |
| 262 | |
| 263 | /** |
| 264 | * Dismisses the notice. |
| 265 | * |
| 266 | * @return bool |
| 267 | */ |
| 268 | function duplicate_post_dismiss_notice() { |
| 269 | return update_site_option( 'duplicate_post_show_notice', 0 ); |
| 270 | } |
| 271 | |
| 272 | /** |
| 273 | * Copies the taxonomies of a post to another post. |
| 274 | * |
| 275 | * @global wpdb $wpdb WordPress database abstraction object. |
| 276 | * |
| 277 | * @param int $new_id New post ID. |
| 278 | * @param WP_Post $post The original post object. |
| 279 | * |
| 280 | * @return void |
| 281 | */ |
| 282 | function duplicate_post_copy_post_taxonomies( $new_id, $post ) { |
| 283 | global $wpdb; |
| 284 | if ( isset( $wpdb->terms ) ) { |
| 285 | // Clear default category (added by wp_insert_post). |
| 286 | wp_set_object_terms( $new_id, [], 'category' ); |
| 287 | |
| 288 | $post_taxonomies = get_object_taxonomies( $post->post_type ); |
| 289 | // Several plugins just add support to post-formats but don't register post_format taxonomy. |
| 290 | if ( post_type_supports( $post->post_type, 'post-formats' ) && ! in_array( 'post_format', $post_taxonomies, true ) ) { |
| 291 | $post_taxonomies[] = 'post_format'; |
| 292 | } |
| 293 | |
| 294 | $taxonomies_blacklist = get_option( 'duplicate_post_taxonomies_blacklist', [] ); |
| 295 | if ( empty( $taxonomies_blacklist ) ) { |
| 296 | $taxonomies_blacklist = []; |
| 297 | } |
| 298 | elseif ( ! is_array( $taxonomies_blacklist ) ) { |
| 299 | $taxonomies_blacklist = [ $taxonomies_blacklist ]; |
| 300 | } |
| 301 | if ( intval( get_option( 'duplicate_post_copyformat' ) ) === 0 ) { |
| 302 | $taxonomies_blacklist[] = 'post_format'; |
| 303 | } |
| 304 | |
| 305 | /** |
| 306 | * Filters the taxonomy excludelist when copying a post. |
| 307 | * |
| 308 | * @param array $taxonomies_blacklist The taxonomy excludelist from the options. |
| 309 | * |
| 310 | * @return array |
| 311 | */ |
| 312 | $taxonomies_blacklist = apply_filters( 'duplicate_post_taxonomies_excludelist_filter', $taxonomies_blacklist ); |
| 313 | |
| 314 | $taxonomies = array_diff( $post_taxonomies, $taxonomies_blacklist ); |
| 315 | foreach ( $taxonomies as $taxonomy ) { |
| 316 | $post_terms = wp_get_object_terms( $post->ID, $taxonomy, [ 'orderby' => 'term_order' ] ); |
| 317 | $terms = []; |
| 318 | $num_terms = count( $post_terms ); |
| 319 | for ( $i = 0; $i < $num_terms; $i++ ) { |
| 320 | $terms[] = $post_terms[ $i ]->slug; |
| 321 | } |
| 322 | wp_set_object_terms( $new_id, $terms, $taxonomy ); |
| 323 | } |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | /** |
| 328 | * Copies the meta information of a post to another post |
| 329 | * |
| 330 | * @param int $new_id The new post ID. |
| 331 | * @param WP_Post $post The original post object. |
| 332 | * |
| 333 | * @return void |
| 334 | */ |
| 335 | function duplicate_post_copy_post_meta_info( $new_id, $post ) { |
| 336 | $post_meta_keys = get_post_custom_keys( $post->ID ); |
| 337 | if ( empty( $post_meta_keys ) ) { |
| 338 | return; |
| 339 | } |
| 340 | $meta_blacklist = get_option( 'duplicate_post_blacklist' ); |
| 341 | if ( $meta_blacklist === '' ) { |
| 342 | $meta_blacklist = []; |
| 343 | } |
| 344 | else { |
| 345 | $meta_blacklist = explode( ',', $meta_blacklist ); |
| 346 | $meta_blacklist = array_filter( $meta_blacklist ); |
| 347 | $meta_blacklist = array_map( 'trim', $meta_blacklist ); |
| 348 | } |
| 349 | $meta_blacklist[] = '_edit_lock'; // Edit lock. |
| 350 | $meta_blacklist[] = '_edit_last'; // Edit lock. |
| 351 | $meta_blacklist[] = '_dp_is_rewrite_republish_copy'; |
| 352 | $meta_blacklist[] = '_dp_has_rewrite_republish_copy'; |
| 353 | if ( intval( get_option( 'duplicate_post_copytemplate' ) ) === 0 ) { |
| 354 | $meta_blacklist[] = '_wp_page_template'; |
| 355 | } |
| 356 | if ( intval( get_option( 'duplicate_post_copythumbnail' ) ) === 0 ) { |
| 357 | $meta_blacklist[] = '_thumbnail_id'; |
| 358 | } |
| 359 | |
| 360 | /** |
| 361 | * Filters the meta fields excludelist when copying a post. |
| 362 | * |
| 363 | * @param array $meta_blacklist The meta fields excludelist from the options. |
| 364 | * |
| 365 | * @return array |
| 366 | */ |
| 367 | $meta_blacklist = apply_filters( 'duplicate_post_excludelist_filter', $meta_blacklist ); |
| 368 | |
| 369 | $meta_blacklist_string = '(' . implode( ')|(', $meta_blacklist ) . ')'; |
| 370 | if ( strpos( $meta_blacklist_string, '*' ) !== false ) { |
| 371 | $meta_blacklist_string = str_replace( [ '*' ], [ '[a-zA-Z0-9_]*' ], $meta_blacklist_string ); |
| 372 | |
| 373 | $meta_keys = []; |
| 374 | foreach ( $post_meta_keys as $meta_key ) { |
| 375 | if ( ! preg_match( '#^(' . $meta_blacklist_string . ')$#', $meta_key ) ) { |
| 376 | $meta_keys[] = $meta_key; |
| 377 | } |
| 378 | } |
| 379 | } |
| 380 | else { |
| 381 | $meta_keys = array_diff( $post_meta_keys, $meta_blacklist ); |
| 382 | } |
| 383 | |
| 384 | /** |
| 385 | * Filters the list of meta fields names when copying a post. |
| 386 | * |
| 387 | * @param array $meta_keys The list of meta fields name, with the ones in the excludelist already removed. |
| 388 | * |
| 389 | * @return array |
| 390 | */ |
| 391 | $meta_keys = apply_filters( 'duplicate_post_meta_keys_filter', $meta_keys ); |
| 392 | |
| 393 | foreach ( $meta_keys as $meta_key ) { |
| 394 | $meta_values = get_post_custom_values( $meta_key, $post->ID ); |
| 395 | foreach ( $meta_values as $meta_value ) { |
| 396 | $meta_value = maybe_unserialize( $meta_value ); |
| 397 | add_post_meta( $new_id, $meta_key, duplicate_post_wp_slash( $meta_value ) ); |
| 398 | } |
| 399 | } |
| 400 | } |
| 401 | |
| 402 | /** |
| 403 | * Workaround for inconsistent wp_slash. |
| 404 | * Works only with WP 4.4+ (map_deep) |
| 405 | * |
| 406 | * @param mixed $value Array or object to be recursively slashed. |
| 407 | * @return string|mixed |
| 408 | */ |
| 409 | function duplicate_post_addslashes_deep( $value ) { |
| 410 | if ( function_exists( 'map_deep' ) ) { |
| 411 | return map_deep( $value, 'duplicate_post_addslashes_to_strings_only' ); |
| 412 | } |
| 413 | else { |
| 414 | return wp_slash( $value ); |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | /** |
| 419 | * Adds slashes only to strings. |
| 420 | * |
| 421 | * @param mixed $value Value to slash only if string. |
| 422 | * @return string|mixed |
| 423 | */ |
| 424 | function duplicate_post_addslashes_to_strings_only( $value ) { |
| 425 | return Utils::addslashes_to_strings_only( $value ); |
| 426 | } |
| 427 | |
| 428 | /** |
| 429 | * Replacement function for faulty core wp_slash(). |
| 430 | * |
| 431 | * @param mixed $value What to add slash to. |
| 432 | * @return mixed |
| 433 | */ |
| 434 | function duplicate_post_wp_slash( $value ) { |
| 435 | return duplicate_post_addslashes_deep( $value ); |
| 436 | } |
| 437 | |
| 438 | /** |
| 439 | * Copies attachments, including physical files. |
| 440 | * |
| 441 | * @param int $new_id The new post ID. |
| 442 | * @param WP_Post $post The original post object. |
| 443 | * |
| 444 | * @return void |
| 445 | */ |
| 446 | function duplicate_post_copy_attachments( $new_id, $post ) { |
| 447 | // Get thumbnail ID. |
| 448 | $old_thumbnail_id = get_post_thumbnail_id( $post->ID ); |
| 449 | // Get children. |
| 450 | $children = get_posts( |
| 451 | [ |
| 452 | 'post_type' => 'any', |
| 453 | 'numberposts' => -1, |
| 454 | 'post_status' => 'any', |
| 455 | 'post_parent' => $post->ID, |
| 456 | ], |
| 457 | ); |
| 458 | // Clone old attachments. |
| 459 | foreach ( $children as $child ) { |
| 460 | if ( $child->post_type !== 'attachment' ) { |
| 461 | continue; |
| 462 | } |
| 463 | $url = wp_get_attachment_url( $child->ID ); |
| 464 | // Let's copy the actual file. |
| 465 | $tmp = download_url( $url ); |
| 466 | if ( is_wp_error( $tmp ) ) { |
| 467 | continue; |
| 468 | } |
| 469 | |
| 470 | $desc = wp_slash( $child->post_content ); |
| 471 | |
| 472 | $file_array = []; |
| 473 | $file_array['name'] = basename( $url ); |
| 474 | $file_array['tmp_name'] = $tmp; |
| 475 | // "Upload" to the media collection |
| 476 | $new_attachment_id = media_handle_sideload( $file_array, $new_id, $desc ); |
| 477 | |
| 478 | if ( is_wp_error( $new_attachment_id ) ) { |
| 479 | wp_delete_file( $file_array['tmp_name'] ); |
| 480 | continue; |
| 481 | } |
| 482 | $new_post_author = wp_get_current_user(); |
| 483 | $cloned_child = [ |
| 484 | 'ID' => $new_attachment_id, |
| 485 | 'post_title' => $child->post_title, |
| 486 | 'post_excerpt' => $child->post_excerpt, // Caption. |
| 487 | 'post_content' => $child->post_content, // Description. |
| 488 | 'post_author' => $new_post_author->ID, |
| 489 | ]; |
| 490 | wp_update_post( wp_slash( $cloned_child ) ); |
| 491 | |
| 492 | $alt_title = get_post_meta( $child->ID, '_wp_attachment_image_alt', true ); |
| 493 | if ( $alt_title ) { |
| 494 | update_post_meta( $new_attachment_id, '_wp_attachment_image_alt', wp_slash( $alt_title ) ); |
| 495 | } |
| 496 | |
| 497 | // If we have cloned the post thumbnail, set the copy as the thumbnail for the new post. |
| 498 | if ( intval( get_option( 'duplicate_post_copythumbnail' ) ) === 1 && $old_thumbnail_id === $child->ID ) { |
| 499 | set_post_thumbnail( $new_id, $new_attachment_id ); |
| 500 | } |
| 501 | } |
| 502 | } |
| 503 | |
| 504 | /** |
| 505 | * Copies child posts. |
| 506 | * |
| 507 | * @param int $new_id The new post ID. |
| 508 | * @param WP_Post $post The original post object. |
| 509 | * @param string $status Optional. The destination status. |
| 510 | * |
| 511 | * @return void |
| 512 | */ |
| 513 | function duplicate_post_copy_children( $new_id, $post, $status = '' ) { |
| 514 | // Get children. |
| 515 | $children = get_posts( |
| 516 | [ |
| 517 | 'post_type' => 'any', |
| 518 | 'numberposts' => -1, |
| 519 | 'post_status' => 'any', |
| 520 | 'post_parent' => $post->ID, |
| 521 | ], |
| 522 | ); |
| 523 | |
| 524 | foreach ( $children as $child ) { |
| 525 | if ( $child->post_type === 'attachment' ) { |
| 526 | continue; |
| 527 | } |
| 528 | duplicate_post_create_duplicate( $child, $status, $new_id ); |
| 529 | } |
| 530 | } |
| 531 | |
| 532 | /** |
| 533 | * Copies comments. |
| 534 | * |
| 535 | * @param int $new_id The new post ID. |
| 536 | * @param WP_Post $post The original post object. |
| 537 | * |
| 538 | * @return void |
| 539 | */ |
| 540 | function duplicate_post_copy_comments( $new_id, $post ) { |
| 541 | $comments = get_comments( |
| 542 | [ |
| 543 | 'post_id' => $post->ID, |
| 544 | 'order' => 'ASC', |
| 545 | 'orderby' => 'comment_date_gmt', |
| 546 | ], |
| 547 | ); |
| 548 | |
| 549 | $old_id_to_new = []; |
| 550 | foreach ( $comments as $comment ) { |
| 551 | // Do not copy pingbacks or trackbacks. |
| 552 | if ( $comment->comment_type === 'pingback' || $comment->comment_type === 'trackback' ) { |
| 553 | continue; |
| 554 | } |
| 555 | $parent = ( $comment->comment_parent && $old_id_to_new[ $comment->comment_parent ] ) ? $old_id_to_new[ $comment->comment_parent ] : 0; |
| 556 | $commentdata = [ |
| 557 | 'comment_post_ID' => $new_id, |
| 558 | 'comment_author' => $comment->comment_author, |
| 559 | 'comment_author_email' => $comment->comment_author_email, |
| 560 | 'comment_author_url' => $comment->comment_author_url, |
| 561 | 'comment_content' => $comment->comment_content, |
| 562 | 'comment_type' => $comment->comment_type, |
| 563 | 'comment_parent' => $parent, |
| 564 | 'user_id' => $comment->user_id, |
| 565 | 'comment_author_IP' => $comment->comment_author_IP, |
| 566 | 'comment_agent' => $comment->comment_agent, |
| 567 | 'comment_karma' => $comment->comment_karma, |
| 568 | 'comment_approved' => $comment->comment_approved, |
| 569 | ]; |
| 570 | if ( intval( get_option( 'duplicate_post_copydate' ) ) === 1 ) { |
| 571 | $commentdata['comment_date'] = $comment->comment_date; |
| 572 | $commentdata['comment_date_gmt'] = get_gmt_from_date( $comment->comment_date ); |
| 573 | } |
| 574 | $new_comment_id = wp_insert_comment( $commentdata ); |
| 575 | $commentmeta = get_comment_meta( $new_comment_id ); |
| 576 | foreach ( $commentmeta as $meta_key => $meta_value ) { |
| 577 | add_comment_meta( $new_comment_id, $meta_key, duplicate_post_wp_slash( $meta_value ) ); |
| 578 | } |
| 579 | $old_id_to_new[ $comment->comment_ID ] = $new_comment_id; |
| 580 | } |
| 581 | } |
| 582 | |
| 583 | /** |
| 584 | * Creates a duplicate from a post. |
| 585 | * |
| 586 | * This is the main functions that does the cloning. |
| 587 | * |
| 588 | * @param WP_Post $post The original post object. |
| 589 | * @param string $status Optional. The intended destination status. |
| 590 | * @param string $parent_id Optional. The parent post ID if we are calling this recursively. |
| 591 | * @return int|WP_Error |
| 592 | */ |
| 593 | function duplicate_post_create_duplicate( $post, $status = '', $parent_id = '' ) { |
| 594 | /** |
| 595 | * Fires before duplicating a post. |
| 596 | * |
| 597 | * @param WP_Post $post The original post object. |
| 598 | * @param bool $status The intended destination status. |
| 599 | * @param int $parent_id The parent post ID if we are calling this recursively. |
| 600 | */ |
| 601 | do_action( 'duplicate_post_pre_copy', $post, $status, $parent_id ); |
| 602 | |
| 603 | /** |
| 604 | * Filter allowing to copy post. |
| 605 | * |
| 606 | * @param bool $can_duplicate Default to `true`. |
| 607 | * @param WP_Post $post The original post object. |
| 608 | * @param bool $status The intended destination status. |
| 609 | * @param int $parent_id The parent post ID if we are calling this recursively. |
| 610 | * |
| 611 | * @return bool |
| 612 | */ |
| 613 | $can_duplicate = apply_filters( 'duplicate_post_allow', true, $post, $status, $parent_id ); |
| 614 | if ( ! $can_duplicate ) { |
| 615 | wp_die( esc_html( __( 'You aren\'t allowed to duplicate this post', 'duplicate-post' ) ) ); |
| 616 | } |
| 617 | |
| 618 | if ( ! duplicate_post_is_post_type_enabled( $post->post_type ) && $post->post_type !== 'attachment' ) { |
| 619 | wp_die( |
| 620 | esc_html( |
| 621 | __( 'Copy features for this post type are not enabled in options page', 'duplicate-post' ) . ': ' |
| 622 | . $post->post_type, |
| 623 | ), |
| 624 | ); |
| 625 | } |
| 626 | |
| 627 | $new_post_status = ( empty( $status ) ) ? $post->post_status : $status; |
| 628 | $title = ' '; |
| 629 | |
| 630 | if ( $post->post_type !== 'attachment' ) { |
| 631 | $prefix = sanitize_text_field( get_option( 'duplicate_post_title_prefix' ) ); |
| 632 | $suffix = sanitize_text_field( get_option( 'duplicate_post_title_suffix' ) ); |
| 633 | if ( intval( get_option( 'duplicate_post_copytitle' ) ) === 1 ) { |
| 634 | $title = $post->post_title; |
| 635 | if ( ! empty( $prefix ) ) { |
| 636 | $prefix .= ' '; |
| 637 | } |
| 638 | if ( ! empty( $suffix ) ) { |
| 639 | $suffix = ' ' . $suffix; |
| 640 | } |
| 641 | } |
| 642 | else { |
| 643 | $title = ' '; |
| 644 | } |
| 645 | $title = trim( $prefix . $title . $suffix ); |
| 646 | |
| 647 | /* |
| 648 | * Not sure we should force a title. Instead, we should respect what WP does. |
| 649 | * if ( '' === $title ) { |
| 650 | * // empty title. |
| 651 | * $title = __( 'Untitled', 'default' ); |
| 652 | * } |
| 653 | */ |
| 654 | |
| 655 | if ( intval( get_option( 'duplicate_post_copystatus' ) ) === 0 ) { |
| 656 | $new_post_status = 'draft'; |
| 657 | } |
| 658 | elseif ( $new_post_status === 'publish' || $new_post_status === 'future' ) { |
| 659 | // Check if the user has the right capability. |
| 660 | if ( is_post_type_hierarchical( $post->post_type ) ) { |
| 661 | if ( ! current_user_can( 'publish_pages' ) ) { |
| 662 | $new_post_status = 'pending'; |
| 663 | } |
| 664 | } |
| 665 | elseif ( ! current_user_can( 'publish_posts' ) ) { |
| 666 | $new_post_status = 'pending'; |
| 667 | } |
| 668 | } |
| 669 | } |
| 670 | |
| 671 | $new_post_author = wp_get_current_user(); |
| 672 | $new_post_author_id = $new_post_author->ID; |
| 673 | if ( intval( get_option( 'duplicate_post_copyauthor' ) ) === 1 ) { |
| 674 | // Check if the user has the right capability. |
| 675 | if ( is_post_type_hierarchical( $post->post_type ) ) { |
| 676 | if ( current_user_can( 'edit_others_pages' ) ) { |
| 677 | $new_post_author_id = $post->post_author; |
| 678 | } |
| 679 | } |
| 680 | elseif ( current_user_can( 'edit_others_posts' ) ) { |
| 681 | $new_post_author_id = $post->post_author; |
| 682 | } |
| 683 | } |
| 684 | |
| 685 | $menu_order = ( intval( get_option( 'duplicate_post_copymenuorder' ) ) === 1 ) ? $post->menu_order : 0; |
| 686 | $increase_menu_order_by = get_option( 'duplicate_post_increase_menu_order_by' ); |
| 687 | if ( ! empty( $increase_menu_order_by ) && is_numeric( $increase_menu_order_by ) ) { |
| 688 | $menu_order += intval( $increase_menu_order_by ); |
| 689 | } |
| 690 | |
| 691 | $post_name = $post->post_name; |
| 692 | if ( intval( get_option( 'duplicate_post_copyslug' ) ) !== 1 ) { |
| 693 | $post_name = ''; |
| 694 | } |
| 695 | $new_post_parent = empty( $parent_id ) ? $post->post_parent : $parent_id; |
| 696 | |
| 697 | $new_post = [ |
| 698 | 'menu_order' => $menu_order, |
| 699 | 'comment_status' => $post->comment_status, |
| 700 | 'ping_status' => $post->ping_status, |
| 701 | 'post_author' => $new_post_author_id, |
| 702 | 'post_content' => ( intval( get_option( 'duplicate_post_copycontent' ) ) === 1 ) ? $post->post_content : '', |
| 703 | 'post_content_filtered' => ( intval( get_option( 'duplicate_post_copycontent' ) ) === 1 ) ? $post->post_content_filtered : '', |
| 704 | 'post_excerpt' => ( intval( get_option( 'duplicate_post_copyexcerpt' ) ) === 1 ) ? $post->post_excerpt : '', |
| 705 | 'post_mime_type' => $post->post_mime_type, |
| 706 | 'post_parent' => $new_post_parent, |
| 707 | 'post_password' => ( intval( get_option( 'duplicate_post_copypassword' ) ) === 1 ) ? $post->post_password : '', |
| 708 | 'post_status' => $new_post_status, |
| 709 | 'post_title' => $title, |
| 710 | 'post_type' => $post->post_type, |
| 711 | 'post_name' => $post_name, |
| 712 | ]; |
| 713 | |
| 714 | if ( intval( get_option( 'duplicate_post_copydate' ) ) === 1 ) { |
| 715 | $new_post_date = $post->post_date; |
| 716 | $new_post['post_date'] = $new_post_date; |
| 717 | $new_post['post_date_gmt'] = get_gmt_from_date( $new_post_date ); |
| 718 | } |
| 719 | |
| 720 | /** |
| 721 | * Filter new post values. |
| 722 | * |
| 723 | * @param array $new_post New post values. |
| 724 | * @param WP_Post $post Original post object. |
| 725 | * |
| 726 | * @return array |
| 727 | */ |
| 728 | $new_post = apply_filters( 'duplicate_post_new_post', $new_post, $post ); |
| 729 | $new_post_id = wp_insert_post( wp_slash( $new_post ), true ); |
| 730 | |
| 731 | // If you have written a plugin which uses non-WP database tables to save |
| 732 | // information about a post you can hook this action to dupe that data. |
| 733 | if ( $new_post_id !== 0 && ! is_wp_error( $new_post_id ) ) { |
| 734 | |
| 735 | /** |
| 736 | * Fires after a post has been duplicated. |
| 737 | * |
| 738 | * @param int $new_post_id The ID of the new post. |
| 739 | * @param WP_Post $post The original post object. |
| 740 | * @param string $status The status of the new post. |
| 741 | * @param string $post_type The post type of the duplicated post. |
| 742 | */ |
| 743 | do_action( 'duplicate_post_after_duplicated', $new_post_id, $post, $status, $post->post_type ); |
| 744 | |
| 745 | // Deprecated hooks for backward compatibility. |
| 746 | if ( $post->post_type === 'page' || is_post_type_hierarchical( $post->post_type ) ) { |
| 747 | do_action_deprecated( 'dp_duplicate_page', [ $new_post_id, $post, $status ], 'Yoast Duplicate Post 4.6', 'duplicate_post_after_duplicated' ); |
| 748 | } |
| 749 | else { |
| 750 | do_action_deprecated( 'dp_duplicate_post', [ $new_post_id, $post, $status ], 'Yoast Duplicate Post 4.6', 'duplicate_post_after_duplicated' ); |
| 751 | } |
| 752 | |
| 753 | delete_post_meta( $new_post_id, '_dp_original' ); |
| 754 | add_post_meta( $new_post_id, '_dp_original', $post->ID ); |
| 755 | } |
| 756 | |
| 757 | /** |
| 758 | * Fires after duplicating a post. |
| 759 | * |
| 760 | * @param int|WP_Error $new_post_id The new post id or WP_Error object on error. |
| 761 | * @param WP_Post $post The original post object. |
| 762 | * @param bool $status The intended destination status. |
| 763 | * @param int $parent_id The parent post ID if we are calling this recursively. |
| 764 | */ |
| 765 | do_action( 'duplicate_post_post_copy', $new_post_id, $post, $status, $parent_id ); |
| 766 | |
| 767 | return $new_post_id; |
| 768 | } |
| 769 | |
| 770 | /** |
| 771 | * Adds some links on the plugin page. |
| 772 | * |
| 773 | * @param array<string> $links The links array. |
| 774 | * @param string $file The file name. |
| 775 | * @return array<string> |
| 776 | */ |
| 777 | function duplicate_post_add_plugin_links( $links, $file ) { |
| 778 | if ( plugin_basename( __DIR__ . '/duplicate-post.php' ) === $file ) { |
| 779 | $links[] = '<a href="https://yoa.st/4jr">' . esc_html__( 'Documentation', 'duplicate-post' ) . '</a>'; |
| 780 | } |
| 781 | return $links; |
| 782 | } |
| 783 |