PluginProbe ʕ •ᴥ•ʔ
Yoast Duplicate Post / 4.6
Yoast Duplicate Post v4.6
trunk 0.3 0.4 0.5 0.6 0.6.1 1.0 1.1 1.1.1 1.1.2 2.0 2.0.1 2.0.2 2.1 2.1.1 2.2 2.3 2.4 2.4.1 2.5 2.6 3.0 3.0.1 3.0.2 3.0.3 3.1 3.1.1 3.1.2 3.2 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 4.0 4.0.1 4.0.2 4.1 4.1.1 4.1.2 4.2 4.3 4.4 4.5 4.6
duplicate-post / admin-functions.php
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