Duplicate Post - Version 4.0

Version Description

(2021-01-12) =

Enhancements:

  • Introduces the Rewrite & Republish feature, offering you the possibility to update a post/page without taking it offline or having to take extra steps. This feature is currently not available when Elementor is active on your site.
  • Introduces an integration with the Block Editor.
  • Introduces new settings to individually enable/disable the New Draft, Clone and Rewrite & Republish links.
Download this release

Release Info

Developer lopo
Plugin Icon 128x128 Duplicate Post
Version 4.0
Comparing to
See all releases

Code changes from version 3.2.6 to 4.0

Files changed (60) hide show
  1. compat/duplicate-post-gutenberg.php +39 -0
  2. compat/duplicate-post-jetpack.php +37 -13
  3. compat/duplicate-post-wpml.php +66 -35
  4. duplicate-post-options.css → css/duplicate-post-options.css +1 -0
  5. css/duplicate-post.css +91 -0
  6. duplicate-post-admin.php +555 -712
  7. duplicate-post-common.php +66 -213
  8. duplicate-post-options.php +18 -793
  9. duplicate-post.css +0 -46
  10. duplicate-post.php +54 -24
  11. duplicate_post_admin_script.js +0 -35
  12. js/dist/duplicate-post-edit-400.js +1 -0
  13. js/dist/duplicate-post-options-400.js +1 -0
  14. js/dist/duplicate-post-quick-edit-400.js +1 -0
  15. js/dist/duplicate-post-strings-400.js +1 -0
  16. readme.txt +18 -10
  17. src/admin/class-options-form-generator.php +306 -0
  18. src/admin/class-options-inputs.php +78 -0
  19. src/admin/class-options-page.php +179 -0
  20. src/admin/class-options.php +317 -0
  21. src/admin/views/options.php +252 -0
  22. src/class-duplicate-post.php +84 -0
  23. src/class-permissions-helper.php +296 -0
  24. src/class-post-duplicator.php +384 -0
  25. src/class-post-republisher.php +425 -0
  26. src/class-revisions-migrator.php +71 -0
  27. src/class-utils.php +196 -0
  28. src/handlers/class-bulk-handler.php +138 -0
  29. src/handlers/class-check-changes-handler.php +166 -0
  30. src/handlers/class-handler.php +81 -0
  31. src/handlers/class-link-handler.php +238 -0
  32. src/handlers/class-save-post-handler.php +68 -0
  33. src/ui/class-admin-bar.php +190 -0
  34. src/ui/class-asset-manager.php +160 -0
  35. src/ui/class-block-editor.php +193 -0
  36. src/ui/class-bulk-actions.php +86 -0
  37. src/ui/class-classic-editor.php +337 -0
  38. src/ui/class-column.php +171 -0
  39. src/ui/class-link-builder.php +101 -0
  40. src/ui/class-metabox.php +121 -0
  41. src/ui/class-post-list.php +137 -0
  42. src/ui/class-post-states.php +71 -0
  43. src/ui/class-row-actions.php +147 -0
  44. src/ui/class-user-interface.php +132 -0
  45. src/watchers/class-bulk-actions-watcher.php +93 -0
  46. src/watchers/class-copied-post-watcher.php +129 -0
  47. src/watchers/class-link-actions-watcher.php +132 -0
  48. src/watchers/class-original-post-watcher.php +110 -0
  49. src/watchers/class-republished-post-watcher.php +111 -0
  50. src/watchers/class-watchers.php +72 -0
  51. vendor/autoload.php +7 -0
  52. vendor/composer/ClassLoader.php +445 -0
  53. vendor/composer/InstalledVersions.php +281 -0
  54. vendor/composer/LICENSE +21 -0
  55. vendor/composer/autoload_classmap.php +42 -0
  56. vendor/composer/autoload_namespaces.php +9 -0
  57. vendor/composer/autoload_psr4.php +9 -0
  58. vendor/composer/autoload_real.php +55 -0
  59. vendor/composer/autoload_static.php +52 -0
  60. vendor/composer/installed.php +96 -0
compat/duplicate-post-gutenberg.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Gutenberg (Block editor)/Classic Editor compatibility functions
4
+ *
5
+ * @package Duplicate Post
6
+ * @since 4.0
7
+ */
8
+
9
+ add_filter( 'duplicate_post_get_clone_post_link', 'duplicate_post_classic_editor_clone_link', 10, 4 );
10
+
11
+ /**
12
+ * Edits the clone link URL to enforce Classic Editor legacy support.
13
+ *
14
+ * @see duplicate_post_get_clone_post_link()
15
+ *
16
+ * @param string $url The duplicate post link URL.
17
+ * @param int $post_id The original post ID.
18
+ * @param string $context The context in which the URL is used.
19
+ * @param bool $draft Whether the link is "New Draft" or "Clone".
20
+ *
21
+ * @return string
22
+ */
23
+ function duplicate_post_classic_editor_clone_link( $url, $post_id, $context, $draft ) {
24
+ $post = get_post( $post_id );
25
+ if ( ! $post ) {
26
+ return $url;
27
+ }
28
+
29
+ if ( isset( $_GET['classic-editor'] ) // phpcs:ignore WordPress.Security.NonceVerification
30
+ || ( $draft && function_exists( 'gutenberg_post_has_blocks' ) && ! gutenberg_post_has_blocks( $post ) )
31
+ || ( $draft && function_exists( 'has_blocks' ) && ! has_blocks( $post ) ) ) {
32
+ if ( 'display' === $context ) {
33
+ $url .= '&amp;classic-editor';
34
+ } else {
35
+ $url .= '&classic-editor';
36
+ }
37
+ }
38
+ return $url;
39
+ }
compat/duplicate-post-jetpack.php CHANGED
@@ -1,30 +1,54 @@
1
  <?php
2
- add_action( 'admin_init', 'duplicate_post_jetpack_init' );
 
 
 
 
 
3
 
 
4
 
 
 
 
5
  function duplicate_post_jetpack_init() {
6
- add_filter('duplicate_post_excludelist_filter', 'duplicate_post_jetpack_add_to_excludelist', 10, 1 );
7
 
8
- if (class_exists('WPCom_Markdown')){
9
- add_action('duplicate_post_pre_copy', 'duplicate_post_jetpack_disable_markdown', 10);
10
- add_action('duplicate_post_post_copy', 'duplicate_post_jetpack_enable_markdown', 10);
11
  }
12
  }
13
 
14
- function duplicate_post_jetpack_add_to_excludelist($meta_blacklist) {
15
- $meta_blacklist[] = '_wpas*'; //Jetpack Publicize
16
- $meta_blacklist[] = '_publicize*'; //Jetpack Publicize
 
 
 
 
 
 
17
 
18
- $meta_blacklist[] = '_jetpack*'; //Jetpack Subscriptions etc.
19
 
20
- return $meta_blacklist;
21
  }
22
 
23
- // Markdown
24
- function duplicate_post_jetpack_disable_markdown(){
 
 
 
 
25
  WPCom_Markdown::get_instance()->unload_markdown_for_posts();
26
  }
27
 
28
- function duplicate_post_jetpack_enable_markdown(){
 
 
 
 
 
29
  WPCom_Markdown::get_instance()->load_markdown_for_posts();
30
  }
1
  <?php
2
+ /**
3
+ * JetPack compatibility functions.
4
+ *
5
+ * @package Duplicate Post
6
+ * @since 3.2
7
+ */
8
 
9
+ add_action( 'admin_init', 'duplicate_post_jetpack_init' );
10
 
11
+ /**
12
+ * Add handlers for JetPack compatibility.
13
+ */
14
  function duplicate_post_jetpack_init() {
15
+ add_filter( 'duplicate_post_excludelist_filter', 'duplicate_post_jetpack_add_to_excludelist', 10, 1 );
16
 
17
+ if ( class_exists( 'WPCom_Markdown' ) ) {
18
+ add_action( 'duplicate_post_pre_copy', 'duplicate_post_jetpack_disable_markdown', 10 );
19
+ add_action( 'duplicate_post_post_copy', 'duplicate_post_jetpack_enable_markdown', 10 );
20
  }
21
  }
22
 
23
+ /**
24
+ * Add some JetPack custom field wildcards to be filtered out when cloning.
25
+ *
26
+ * @param array $meta_excludelist The array containing the blacklist of custom fields.
27
+ * @return array
28
+ */
29
+ function duplicate_post_jetpack_add_to_excludelist( $meta_excludelist ) {
30
+ $meta_excludelist[] = '_wpas*'; // Jetpack Publicize.
31
+ $meta_excludelist[] = '_publicize*'; // Jetpack Publicize.
32
 
33
+ $meta_excludelist[] = '_jetpack*'; // Jetpack Subscriptions etc.
34
 
35
+ return $meta_excludelist;
36
  }
37
 
38
+ /**
39
+ * Disable Markdown.
40
+ *
41
+ * To be called before copy.
42
+ */
43
+ function duplicate_post_jetpack_disable_markdown() {
44
  WPCom_Markdown::get_instance()->unload_markdown_for_posts();
45
  }
46
 
47
+ /**
48
+ * Enaable Markdown.
49
+ *
50
+ * To be called after copy.
51
+ */
52
+ function duplicate_post_jetpack_enable_markdown() {
53
  WPCom_Markdown::get_instance()->load_markdown_for_posts();
54
  }
compat/duplicate-post-wpml.php CHANGED
@@ -1,81 +1,112 @@
1
  <?php
 
 
 
 
 
 
 
 
 
2
  add_action( 'admin_init', 'duplicate_post_wpml_init' );
3
 
 
 
 
4
  function duplicate_post_wpml_init() {
5
  if ( defined( 'ICL_SITEPRESS_VERSION' ) ) {
6
- add_action('dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10, 3);
7
- add_action('dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10, 3);
8
- add_action('shutdown', 'duplicate_wpml_string_packages', 11 );
9
  }
10
  }
11
 
12
- global $duplicated_posts;
13
- $duplicated_posts = array();
14
 
15
- function duplicate_post_wpml_copy_translations($post_id, $post, $status = '') {
 
 
 
 
 
 
 
 
 
 
16
  global $sitepress;
17
  global $duplicated_posts;
18
-
19
- remove_action('dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10);
20
- remove_action('dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10);
21
-
22
  $current_language = $sitepress->get_current_language();
23
- $trid = $sitepress->get_element_trid($post->ID);
24
- if (!empty($trid)) {
25
- $translations = $sitepress->get_element_translations($trid);
26
- $new_trid = $sitepress->get_element_trid($post_id);
27
- foreach ($translations as $code => $details) {
28
- if ($code != $current_language) {
29
  if ( $details->element_id ) {
30
  $translation = get_post( $details->element_id );
31
- if (!$translation) continue;
 
 
32
  $new_post_id = duplicate_post_create_duplicate( $translation, $status );
33
- $sitepress->set_element_language_details( $new_post_id, 'post_' . $translation->post_type, $new_trid, $code, $current_language );
 
 
 
 
 
 
 
 
34
  }
35
  }
36
  }
37
- $duplicated_posts[ $post->ID ] = $post_id;
38
  }
39
  }
40
 
41
- function duplicate_wpml_string_packages() {
 
 
 
 
 
42
  global $duplicated_posts;
43
-
44
  foreach ( $duplicated_posts as $original_post_id => $duplicate_post_id ) {
45
-
46
- $original_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $original_post_id );
47
- $new_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $duplicate_post_id );
48
  if ( is_array( $original_string_packages ) ) {
49
  foreach ( $original_string_packages as $original_string_package ) {
50
  $translated_original_strings = $original_string_package->get_translated_strings( array() );
51
-
52
  foreach ( $new_string_packages as $new_string_package ) {
53
  $cache = new WPML_WP_Cache( 'WPML_Package' );
54
  $cache->flush_group_cache();
55
  $new_strings = $new_string_package->get_package_strings();
56
  foreach ( $new_strings as $new_string ) {
57
-
58
  if ( isset( $translated_original_strings[ $new_string->name ] ) ) {
59
  foreach ( $translated_original_strings[ $new_string->name ] as $language => $translated_string ) {
60
-
61
  do_action(
62
- 'wpml_add_string_translation',
63
  $new_string->id,
64
  $language,
65
  $translated_string['value'],
66
  $translated_string['status']
67
- );
68
-
69
  }
70
  }
71
-
72
  }
73
-
74
  }
75
-
76
  }
77
  }
78
  }
79
  }
80
-
81
- ?>
1
  <?php
2
+ /**
3
+ * WPML compatibility functions
4
+ *
5
+ * @global array $duplicated_posts Array to store the posts being duplicated.
6
+ *
7
+ * @package Duplicate Post
8
+ * @since 3.2
9
+ */
10
+
11
  add_action( 'admin_init', 'duplicate_post_wpml_init' );
12
 
13
+ /**
14
+ * Add handlers for WPML compatibility.
15
+ */
16
  function duplicate_post_wpml_init() {
17
  if ( defined( 'ICL_SITEPRESS_VERSION' ) ) {
18
+ add_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10, 3 );
19
+ add_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10, 3 );
20
+ add_action( 'shutdown', 'duplicate_wpml_string_packages', 11 );
21
  }
22
  }
23
 
24
+ global $duplicated_posts; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
25
+ $duplicated_posts = array(); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
26
 
27
+ /**
28
+ * Copy post translations.
29
+ *
30
+ * @global SitePress $sitepress Instance of the Main WPML class.
31
+ * @global array $duplicated_posts Array of duplicated posts.
32
+ *
33
+ * @param int $post_id ID of the copy.
34
+ * @param WP_Post $post Original post object.
35
+ * @param string $status Status of the new post.
36
+ */
37
+ function duplicate_post_wpml_copy_translations( $post_id, $post, $status = '' ) {
38
  global $sitepress;
39
  global $duplicated_posts;
40
+
41
+ remove_action( 'dp_duplicate_page', 'duplicate_post_wpml_copy_translations', 10 );
42
+ remove_action( 'dp_duplicate_post', 'duplicate_post_wpml_copy_translations', 10 );
43
+
44
  $current_language = $sitepress->get_current_language();
45
+ $trid = $sitepress->get_element_trid( $post->ID );
46
+ if ( ! empty( $trid ) ) {
47
+ $translations = $sitepress->get_element_translations( $trid );
48
+ $new_trid = $sitepress->get_element_trid( $post_id );
49
+ foreach ( $translations as $code => $details ) {
50
+ if ( $code !== $current_language ) {
51
  if ( $details->element_id ) {
52
  $translation = get_post( $details->element_id );
53
+ if ( ! $translation ) {
54
+ continue;
55
+ }
56
  $new_post_id = duplicate_post_create_duplicate( $translation, $status );
57
+ if ( ! is_wp_error( $new_post_id ) ) {
58
+ $sitepress->set_element_language_details(
59
+ $new_post_id,
60
+ 'post_' . $translation->post_type,
61
+ $new_trid,
62
+ $code,
63
+ $current_language
64
+ );
65
+ }
66
  }
67
  }
68
  }
69
+ $duplicated_posts[ $post->ID ] = $post_id; // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
70
  }
71
  }
72
 
73
+ /**
74
+ * Duplicate string packages.
75
+ *
76
+ * @global array() $duplicated_posts Array of duplicated posts.
77
+ */
78
+ function duplicate_wpml_string_packages() { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
79
  global $duplicated_posts;
80
+
81
  foreach ( $duplicated_posts as $original_post_id => $duplicate_post_id ) {
82
+
83
+ $original_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $original_post_id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
84
+ $new_string_packages = apply_filters( 'wpml_st_get_post_string_packages', false, $duplicate_post_id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
85
  if ( is_array( $original_string_packages ) ) {
86
  foreach ( $original_string_packages as $original_string_package ) {
87
  $translated_original_strings = $original_string_package->get_translated_strings( array() );
88
+
89
  foreach ( $new_string_packages as $new_string_package ) {
90
  $cache = new WPML_WP_Cache( 'WPML_Package' );
91
  $cache->flush_group_cache();
92
  $new_strings = $new_string_package->get_package_strings();
93
  foreach ( $new_strings as $new_string ) {
94
+
95
  if ( isset( $translated_original_strings[ $new_string->name ] ) ) {
96
  foreach ( $translated_original_strings[ $new_string->name ] as $language => $translated_string ) {
97
+
98
  do_action(
99
+ 'wpml_add_string_translation', // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals
100
  $new_string->id,
101
  $language,
102
  $translated_string['value'],
103
  $translated_string['status']
104
+ );
 
105
  }
106
  }
 
107
  }
 
108
  }
 
109
  }
110
  }
111
  }
112
  }
 
 
duplicate-post-options.css → css/duplicate-post-options.css RENAMED
@@ -7,6 +7,7 @@
7
  }
8
 
9
  #duplicate_post_settings_form header .nav-tab:focus {
 
10
  box-shadow: none;
11
  outline: 1px dotted #000000;
12
  }
7
  }
8
 
9
  #duplicate_post_settings_form header .nav-tab:focus {
10
+ color: #555;
11
  box-shadow: none;
12
  outline: 1px dotted #000000;
13
  }
css/duplicate-post.css ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #wp-admin-bar-root-default>#wp-admin-bar-duplicate-post>.ab-item .ab-icon::before,
2
+ #wp-admin-bar-root-default>#wp-admin-bar-new-draft>.ab-item .ab-icon::before,
3
+ #wp-admin-bar-root-default>#wp-admin-bar-rewrite-republish>.ab-item .ab-icon::before{
4
+ content:
5
+ url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'><path d='M18.9 4.3c0.6 0 1.1 0.5 1.1 1.1v13.6c0 0.6-0.5 1.1-1.1 1.1h-10.7c-0.6 0-1.1-0.5-1.1-1.1v-3.2h-6.1c-0.6 0-1.1-0.5-1.1-1.1v-7.5c0-0.6 0.3-1.4 0.8-1.8l4.6-4.6c0.4-0.4 1.2-0.8 1.8-0.8h4.6c0.6 0 1.1 0.5 1.1 1.1v3.7c0.4-0.3 1-0.4 1.4-0.4h4.6zM12.9 6.7l-3.3 3.3h3.3v-3.3zM5.7 2.4l-3.3 3.3h3.3v-3.3zM7.9 9.6l3.5-3.5v-4.6h-4.3v4.6c0 0.6-0.5 1.1-1.1 1.1h-4.6v7.1h5.7v-2.9c0-0.6 0.3-1.4 0.8-1.8zM18.6 18.6v-12.9h-4.3v4.6c0 0.6-0.5 1.1-1.1 1.1h-4.6v7.1h10z' fill='rgba(240,245,250,.6)'/></svg>");
6
+ top: 2px;
7
+ }
8
+
9
+ #wp-admin-bar-root-default>#wp-admin-bar-duplicate-post:hover>.ab-item .ab-icon::before,
10
+ #wp-admin-bar-root-default>#wp-admin-bar-new-draft:hover>.ab-item .ab-icon::before,
11
+ #wp-admin-bar-root-default>#wp-admin-bar-rewrite-republish:hover>.ab-item .ab-icon::before,
12
+ #wp-admin-bar-root-default>#wp-admin-bar-duplicate-post:focus>.ab-item .ab-icon::before,
13
+ #wp-admin-bar-root-default>#wp-admin-bar-new-draft:focus>.ab-item .ab-icon::before,
14
+ #wp-admin-bar-root-default>#wp-admin-bar-rewrite-republish:focus>.ab-item .ab-icon::before{
15
+ content:
16
+ url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'><path d='M18.9 4.3c0.6 0 1.1 0.5 1.1 1.1v13.6c0 0.6-0.5 1.1-1.1 1.1h-10.7c-0.6 0-1.1-0.5-1.1-1.1v-3.2h-6.1c-0.6 0-1.1-0.5-1.1-1.1v-7.5c0-0.6 0.3-1.4 0.8-1.8l4.6-4.6c0.4-0.4 1.2-0.8 1.8-0.8h4.6c0.6 0 1.1 0.5 1.1 1.1v3.7c0.4-0.3 1-0.4 1.4-0.4h4.6zM12.9 6.7l-3.3 3.3h3.3v-3.3zM5.7 2.4l-3.3 3.3h3.3v-3.3zM7.9 9.6l3.5-3.5v-4.6h-4.3v4.6c0 0.6-0.5 1.1-1.1 1.1h-4.6v7.1h5.7v-2.9c0-0.6 0.3-1.4 0.8-1.8zM18.6 18.6v-12.9h-4.3v4.6c0 0.6-0.5 1.1-1.1 1.1h-4.6v7.1h10z' fill='rgba(0, 185, 235, 1)'/></svg>");
17
+ }
18
+
19
+ /* Copy links in the classic editor. */
20
+ #duplicate-action {
21
+ margin-bottom: 12px;
22
+ }
23
+
24
+ #rewrite-republish-action {
25
+ margin-bottom: -2px;
26
+ }
27
+
28
+ #rewrite-republish-action + #delete-action {
29
+ margin-top: 8px;
30
+ }
31
+
32
+ /* Copy links in the block editor. */
33
+ .components-button.dp-editor-post-copy-to-draft,
34
+ .components-button.dp-editor-post-rewrite-republish {
35
+ margin-left: -6px;
36
+ text-decoration: underline;
37
+ }
38
+
39
+ #check-changes-action {
40
+ padding: 6px 10px 8px;
41
+ }
42
+
43
+ @media screen and (max-width: 782px){
44
+ #wp-admin-bar-root-default>#wp-admin-bar-duplicate-post,
45
+ #wp-admin-bar-root-default>#wp-admin-bar-new-draft,
46
+ #wp-admin-bar-root-default>#wp-admin-bar-rewrite-republish {
47
+ display: block;
48
+ position: static;
49
+ }
50
+ #wp-admin-bar-root-default>#wp-admin-bar-duplicate-post>.ab-item,
51
+ #wp-admin-bar-root-default>#wp-admin-bar-new-draft>.ab-item,
52
+ #wp-admin-bar-root-default>#wp-admin-bar-rewrite-republish>.ab-item {
53
+ text-indent: 100%;
54
+ white-space: nowrap;
55
+ overflow: hidden;
56
+ width: 52px;
57
+ padding: 0;
58
+ color: #999;
59
+ position: static;
60
+ }
61
+ #wp-admin-bar-root-default>#wp-admin-bar-duplicate-post>.ab-item .ab-icon::before,
62
+ #wp-admin-bar-root-default>#wp-admin-bar-new-draft>.ab-item .ab-icon::before,
63
+ #wp-admin-bar-root-default>#wp-admin-bar-rewrite-republish>.ab-item .ab-icon::before {
64
+ display: block;
65
+ text-indent: 0;
66
+ font: 400 32px/1 dashicons;
67
+ speak: none;
68
+ top: 0px;
69
+ width: 52px;
70
+ text-align: center;
71
+ -webkit-font-smoothing: antialiased;
72
+ -moz-osx-font-smoothing: grayscale;
73
+ }
74
+ #rewrite-republish-action + #delete-action {
75
+ margin-top: 0;
76
+ }
77
+ }
78
+
79
+ fieldset#duplicate_post_quick_edit_fieldset{
80
+ clear: both;
81
+ }
82
+
83
+ fieldset#duplicate_post_quick_edit_fieldset label{
84
+ display: inline;
85
+ margin: 0;
86
+ vertical-align: unset;
87
+ }
88
+
89
+ fieldset#duplicate_post_quick_edit_fieldset a{
90
+ text-decoration: underline;
91
+ }
duplicate-post-admin.php CHANGED
@@ -1,40 +1,44 @@
1
  <?php
2
- // Added by WarmStal
3
- if(!is_admin())
 
 
 
 
 
 
4
  return;
 
5
 
6
- require_once (dirname(__FILE__).'/duplicate-post-options.php');
7
 
8
- include_once (dirname(__FILE__).'/compat/duplicate-post-wpml.php');
9
- include_once (dirname(__FILE__).'/compat/duplicate-post-jetpack.php');
10
 
11
  /**
12
- * Wrapper for the option 'duplicate_post_version'
13
- */
14
  function duplicate_post_get_installed_version() {
15
  return get_option( 'duplicate_post_version' );
16
  }
17
 
18
  /**
19
- * Wrapper for the defined constant DUPLICATE_POST_CURRENT_VERSION
20
  */
21
  function duplicate_post_get_current_version() {
22
  return DUPLICATE_POST_CURRENT_VERSION;
23
  }
24
 
 
25
 
26
- add_action('admin_init','duplicate_post_admin_init');
27
-
28
- function duplicate_post_admin_init(){
 
29
  duplicate_post_plugin_upgrade();
30
 
31
- if (get_option('duplicate_post_show_row') == 1){
32
- add_filter('post_row_actions', 'duplicate_post_make_duplicate_link_row',10,2);
33
- add_filter('page_row_actions', 'duplicate_post_make_duplicate_link_row',10,2);
34
- }
35
-
36
- if (get_site_option('duplicate_post_show_notice') == 1){
37
- if(is_multisite()){
38
  add_action( 'network_admin_notices', 'duplicate_post_show_update_notice' );
39
  } else {
40
  add_action( 'admin_notices', 'duplicate_post_show_update_notice' );
@@ -42,198 +46,201 @@ function duplicate_post_admin_init(){
42
  add_action( 'wp_ajax_duplicate_post_dismiss_notice', 'duplicate_post_dismiss_notice' );
43
  }
44
 
45
- if (get_option('duplicate_post_show_submitbox') == 1){
46
- add_action( 'post_submitbox_start', 'duplicate_post_add_duplicate_post_button' );
47
- }
48
-
49
-
50
- if(get_option('duplicate_post_show_original_column') == 1){
51
- duplicate_post_show_original_column();
52
- }
53
-
54
- if(get_option('duplicate_post_show_original_in_post_states') == 1){
55
- add_filter( 'display_post_states', 'duplicate_post_show_original_in_post_states', 10, 2);
56
- }
57
-
58
- if(get_option('duplicate_post_show_original_meta_box') == 1){
59
- add_action('add_meta_boxes', 'duplicate_post_add_custom_box');
60
- add_action( 'save_post', 'duplicate_post_save_quick_edit_data' );
61
- }
62
- /**
63
- * Connect actions to functions
64
- */
65
- add_action('admin_action_duplicate_post_save_as_new_post', 'duplicate_post_save_as_new_post');
66
- add_action('admin_action_duplicate_post_save_as_new_post_draft', 'duplicate_post_save_as_new_post_draft');
67
-
68
- add_filter('removable_query_args', 'duplicate_post_add_removable_query_arg', 10, 1);
69
-
70
- // Using our action hooks
71
 
72
- add_action('dp_duplicate_post', 'duplicate_post_copy_post_meta_info', 10, 2);
73
- add_action('dp_duplicate_page', 'duplicate_post_copy_post_meta_info', 10, 2);
74
-
75
- if(get_option('duplicate_post_copychildren') == 1){
76
- add_action('dp_duplicate_post', 'duplicate_post_copy_children', 20, 3);
77
- add_action('dp_duplicate_page', 'duplicate_post_copy_children', 20, 3);
78
  }
79
 
80
- if(get_option('duplicate_post_copyattachments') == 1){
81
- add_action('dp_duplicate_post', 'duplicate_post_copy_attachments', 30, 2);
82
- add_action('dp_duplicate_page', 'duplicate_post_copy_attachments', 30, 2);
83
  }
84
 
85
- if(get_option('duplicate_post_copycomments') == 1){
86
- add_action('dp_duplicate_post', 'duplicate_post_copy_comments', 40, 2);
87
- add_action('dp_duplicate_page', 'duplicate_post_copy_comments', 40, 2);
88
  }
89
 
90
- add_action('dp_duplicate_post', 'duplicate_post_copy_post_taxonomies', 50, 2);
91
- add_action('dp_duplicate_page', 'duplicate_post_copy_post_taxonomies', 50, 2);
92
-
93
- add_filter('plugin_row_meta', 'duplicate_post_add_plugin_links', 10, 2);
94
 
95
- add_action( 'admin_notices', 'duplicate_post_action_admin_notice' );
96
  }
97
 
98
-
99
  /**
100
- * Plugin upgrade
101
  */
102
  function duplicate_post_plugin_upgrade() {
103
  $installed_version = duplicate_post_get_installed_version();
104
 
105
- if ( $installed_version == duplicate_post_get_current_version() )
106
  return;
 
107
 
108
-
109
- if (empty($installed_version)) {
110
- // Get default roles
111
- $default_roles = array(
112
- 3 => 'editor',
113
- 8 => 'administrator',
 
114
  );
115
 
116
- // Cycle all roles and assign capability if its level >= duplicate_post_copy_user_level
117
- foreach ($default_roles as $level => $name){
118
- $role = get_role($name);
119
- if(!empty($role)) $role->add_cap( 'copy_posts' );
120
- }
121
- } else {
122
- $min_user_level = get_option('duplicate_post_copy_user_level');
123
-
124
- if (!empty($min_user_level)){
125
- // Get default roles
126
- $default_roles = array(
127
- 1 => 'contributor',
128
- 2 => 'author',
129
- 3 => 'editor',
130
- 8 => 'administrator',
131
- );
132
-
133
- // Cycle all roles and assign capability if its level >= duplicate_post_copy_user_level
134
- foreach ($default_roles as $level => $name){
135
- $role = get_role($name);
136
- if ($role && $min_user_level <= $level)
137
- $role->add_cap( 'copy_posts' );
138
  }
139
- delete_option('duplicate_post_copy_user_level');
140
  }
141
  }
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
- add_option('duplicate_post_copytitle','1');
145
- add_option('duplicate_post_copydate','0');
146
- add_option('duplicate_post_copystatus','0');
147
- add_option('duplicate_post_copyslug','0');
148
- add_option('duplicate_post_copyexcerpt','1');
149
- add_option('duplicate_post_copycontent','1');
150
- add_option('duplicate_post_copythumbnail','1');
151
- add_option('duplicate_post_copytemplate','1');
152
- add_option('duplicate_post_copyformat','1');
153
- add_option('duplicate_post_copyauthor','0');
154
- add_option('duplicate_post_copypassword','0');
155
- add_option('duplicate_post_copyattachments','0');
156
- add_option('duplicate_post_copychildren','0');
157
- add_option('duplicate_post_copycomments','0');
158
- add_option('duplicate_post_copymenuorder','1');
159
- add_option('duplicate_post_taxonomies_blacklist',array());
160
- add_option('duplicate_post_blacklist','');
161
- add_option('duplicate_post_types_enabled',array('post', 'page'));
162
- add_option('duplicate_post_show_row','1');
163
- add_option('duplicate_post_show_adminbar','1');
164
- add_option('duplicate_post_show_submitbox','1');
165
- add_option('duplicate_post_show_bulkactions','1');
166
- add_option('duplicate_post_show_original_column','0');
167
- add_option('duplicate_post_show_original_in_post_states','0');
168
- add_option('duplicate_post_show_original_meta_box','0');
169
-
170
- $taxonomies_blacklist = get_option('duplicate_post_taxonomies_blacklist');
171
- if ($taxonomies_blacklist == "") $taxonomies_blacklist = array();
172
- if(in_array('post_format',$taxonomies_blacklist)){
173
- update_option('duplicate_post_copyformat', 0);
174
- $taxonomies_blacklist = array_diff($taxonomies_blacklist, array('post_format'));
175
- update_option('duplicate_post_taxonomies_blacklist', $taxonomies_blacklist);
176
- }
177
-
178
- $meta_blacklist = explode(",",get_option('duplicate_post_blacklist'));
179
- if ($meta_blacklist == "") $meta_blacklist = array();
180
- $meta_blacklist = array_map('trim', $meta_blacklist);
181
- if(in_array('_wp_page_template', $meta_blacklist)){
182
- update_option('duplicate_post_copytemplate', 0);
183
- $meta_blacklist = array_diff($meta_blacklist, array('_wp_page_template'));
184
- }
185
- if(in_array('_thumbnail_id', $meta_blacklist)){
186
- update_option('duplicate_post_copythumbnail', 0);
187
- $meta_blacklist = array_diff($meta_blacklist, array('_thumbnail_id'));
188
- }
189
- update_option('duplicate_post_blacklist', implode(',',$meta_blacklist));
190
-
191
- delete_option('duplicate_post_admin_user_level');
192
- delete_option('duplicate_post_create_user_level');
193
- delete_option('duplicate_post_view_user_level');
194
- delete_option('dp_notice');
195
-
196
- delete_option('duplicate_post_show_notice' );
197
- if ( version_compare( $installed_version, '3.2.5' ) < 0) {
198
  update_site_option( 'duplicate_post_show_notice', 1 );
199
  }
200
 
201
- delete_site_option('duplicate_post_version');
 
 
 
202
  update_option( 'duplicate_post_version', duplicate_post_get_current_version() );
203
  }
204
 
205
  /**
206
- * Shows the update notice
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  */
208
  function duplicate_post_show_update_notice() {
209
- if(!current_user_can( 'manage_options')) return;
 
 
210
 
211
  $current_screen = get_current_screen();
212
- if ( empty( $current_screen ) ||
 
213
  empty( $current_screen->base ) ||
214
- ( $current_screen->base !== "dashboard" && $current_screen->base !== "plugins" )
215
- ) {
216
  return;
217
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
- $class = 'notice is-dismissible';
220
- /* translators: %1$s: Yoast, %2$s: version number */
221
- $message = '<p style="margin: 0;"><strong>' . sprintf( __( 'What\'s new in %1$s Duplicate Post version %2$s:', 'duplicate-post' ), 'Yoast', DUPLICATE_POST_CURRENT_VERSION ) . '</strong> ';
222
- $message .= sprintf( __( 'Compatibility with WP 5.5 + various fixes', 'duplicate-post' ), 'Yoast' ) . '</p>';
223
  $message .= '<p>%%SIGNUP_FORM%%</p>';
224
- $message .= esc_html__('Serving the WordPress community since November 2007.', 'duplicate-post');
225
- global $wp_version;
226
- if( version_compare($wp_version, '4.2') < 0 ){
227
- $message .= '<a id="duplicate-post-dismiss-notice" href="javascript:duplicate_post_dismiss_notice();">'.__('Dismiss this notice.', 'default').'</a>';
228
- }
229
  $allowed_tags = array(
230
  'a' => array(
231
  'href' => array(),
232
- 'title' => array(),
233
  ),
234
  'br' => array(),
235
  'p' => array(),
236
- 'em' => array(),
237
  'strong' => array(),
238
  );
239
 
@@ -242,10 +249,11 @@ function duplicate_post_show_update_notice() {
242
 
243
  $img_path = plugins_url( '/duplicate_post_yoast_icon-125x125.png', __FILE__ );
244
 
245
- echo '<div id="duplicate-post-notice" class="'.$class.'" style="display: flex; align-items: center;">
246
- <img src="' . $img_path . '" alt=""/>
247
- <div style="margin: 0.5em">'.$sanitized_message.'</div>
248
- </div>';
 
249
  echo "<script>
250
  function duplicate_post_dismiss_notice(){
251
  var data = {
@@ -266,556 +274,396 @@ function duplicate_post_show_update_notice() {
266
  }
267
 
268
  /**
269
- * Renders the newsletter signup form.
270
  *
271
- * @return string The HTML of the newsletter signup form (escaped).
272
  */
273
- function duplicate_post_newsletter_signup_form() {
274
- /* translators: %1$s: Yoast */
275
- $copy = sprintf( __( 'If you want to stay up to date about all the exciting developments around Duplicate Post, subscribe to the %1$s newsletter!',
276
- 'duplicate-post' ), 'Yoast' );
277
-
278
- $email_label = __( 'Email Address', 'duplicate-post' );
279
-
280
- $html = '
281
- <!-- Begin Mailchimp Signup Form -->
282
- <div id="mc_embed_signup">
283
- <form action="https://yoast.us1.list-manage.com/subscribe/post?u=ffa93edfe21752c921f860358&amp;id=972f1c9122" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate>
284
- <div id="mc_embed_signup_scroll">
285
- ' . $copy . '
286
- <div class="mc-field-group" style="margin-top: 8px;">
287
- <label for="mce-EMAIL">' . $email_label . '</label>
288
- <input type="email" value="" name="EMAIL" class="required email" id="mce-EMAIL">
289
- <input type="submit" value="' . esc_attr__( 'Subscribe', 'duplicate-post' ) . '" name="subscribe" id="mc-embedded-subscribe" class="button">
290
- </div>
291
- <div id="mce-responses" class="clear">
292
- <div class="response" id="mce-error-response" style="display:none"></div>
293
- <div class="response" id="mce-success-response" style="display:none"></div>
294
- </div> <!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
295
- <div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_ffa93edfe21752c921f860358_972f1c9122" tabindex="-1" value=""></div>
296
- </div>
297
- </form>
298
- </div>
299
- <!--End mc_embed_signup-->
300
- ';
301
-
302
- return $html;
303
- }
304
-
305
-
306
  function duplicate_post_dismiss_notice() {
307
- $result = update_site_option('duplicate_post_show_notice', 0);
308
  return $result;
309
- wp_die();
310
- }
311
-
312
- function duplicate_post_show_original_column() {
313
- $duplicate_post_types_enabled = get_option( 'duplicate_post_types_enabled', array( 'post', 'page' ) );
314
- if ( ! is_array( $duplicate_post_types_enabled ) ) {
315
- $duplicate_post_types_enabled = array( $duplicate_post_types_enabled );
316
- }
317
-
318
- if ( count( $duplicate_post_types_enabled ) ) {
319
- foreach ( $duplicate_post_types_enabled as $enabled_post_type ) {
320
- add_filter( "manage_{$enabled_post_type}_posts_columns", 'duplicate_post_add_original_column' );
321
- add_action( "manage_{$enabled_post_type}_posts_custom_column", 'duplicate_post_show_original_item', 10, 2 );
322
- }
323
- add_action( 'quick_edit_custom_box', 'duplicate_post_quick_edit_remove_original', 10, 2 );
324
- add_action( 'save_post', 'duplicate_post_save_quick_edit_data' );
325
- add_action( 'admin_enqueue_scripts', 'duplicate_post_admin_enqueue_scripts' );
326
- }
327
- }
328
-
329
- function duplicate_post_add_original_column( $post_columns ) {
330
- $post_columns['duplicate_post_original_item'] = __( 'Original item', 'duplicate-post' );
331
- return $post_columns;
332
- }
333
-
334
- function duplicate_post_show_original_item( $column_name, $post_id ) {
335
- if ( 'duplicate_post_original_item' === $column_name ) {
336
- $column_value = '<span data-no_original>-</span>';
337
- $original_item = duplicate_post_get_original( $post_id );
338
- if ( $original_item ) {
339
- $column_value = duplicate_post_get_edit_or_view_link( $original_item );
340
- }
341
- echo $column_value;
342
- }
343
- }
344
-
345
- function duplicate_post_quick_edit_remove_original( $column_name, $post_type ) {
346
- if ( 'duplicate_post_original_item' != $column_name ) {
347
- return;
348
- }
349
-
350
- printf(
351
- '<fieldset class="inline-edit-col-left" id="duplicate_post_quick_edit_fieldset">
352
- <div class="inline-edit-col">
353
- <input type="checkbox"
354
- name="duplicate_post_remove_original"
355
- id="duplicate-post-remove-original"
356
- value="duplicate_post_remove_original"
357
- aria-describedby="duplicate-post-remove-original-description">
358
- <label for="duplicate-post-remove-original">
359
- <span class="checkbox-title">%s</span>
360
- </label>
361
- <span id="duplicate-post-remove-original-description" class="checkbox-title">%s</span>
362
- </div>
363
- </fieldset>',
364
- __(
365
- 'Delete reference to original item.',
366
- 'duplicate-post'
367
- ),
368
- __(
369
- 'The original item this was copied from is: <span class="duplicate_post_original_item_title_span"></span>',
370
- 'duplicate-post'
371
- )
372
- );
373
- }
374
-
375
- function duplicate_post_save_quick_edit_data( $post_id ) {
376
- if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
377
- return $post_id;
378
- }
379
-
380
- if ( ! current_user_can( 'edit_post', $post_id ) ) {
381
- return $post_id;
382
- }
383
-
384
- if ( ! empty( $_POST['duplicate_post_remove_original'] ) ) {
385
- delete_post_meta( $post_id, '_dp_original' );
386
- }
387
- }
388
-
389
- function duplicate_post_show_original_in_post_states( $post_states, $post ){
390
- $original_item = duplicate_post_get_original( $post->ID );
391
- if ( $original_item ) {
392
- // translators: Original item link (to view or edit) or title.
393
- $post_states['duplicate_post_original_item'] = sprintf( __( 'Original: %s', 'duplicate-post' ), duplicate_post_get_edit_or_view_link( $original_item ) );
394
- }
395
- return $post_states;
396
- }
397
-
398
- function duplicate_post_admin_enqueue_scripts( $hook ) {
399
- if ( 'edit.php' === $hook ) {
400
- wp_enqueue_script( 'duplicate_post_admin_script', plugins_url( 'duplicate_post_admin_script.js', __FILE__ ), false, DUPLICATE_POST_CURRENT_VERSION, true );
401
- }
402
- }
403
-
404
- function duplicate_post_add_custom_box(){
405
- $screens = get_option('duplicate_post_types_enabled');
406
- if(!is_array($screens)) $screens = array($screens);
407
- foreach ($screens as $screen) {
408
- add_meta_box(
409
- 'duplicate_post_show_original', // Unique ID
410
- 'Duplicate Post', // Box title
411
- 'duplicate_post_custom_box_html', // Content callback, must be of type callable
412
- $screen, // Post type
413
- 'side'
414
- );
415
- }
416
- }
417
-
418
- function duplicate_post_custom_box_html( $post ) {
419
- $original_item = duplicate_post_get_original( $post->ID );
420
- if ( $original_item ) {
421
- ?>
422
- <p>
423
- <input type="checkbox"
424
- name="duplicate_post_remove_original"
425
- id="duplicate-post-remove-original"
426
- value="duplicate_post_remove_original"
427
- aria-describedby="duplicate-post-remove-original-description">
428
- <label for="duplicate-post-remove-original">
429
- <?php esc_html_e( 'Delete reference to original item.', 'duplicate-post' ); ?>
430
- </label>
431
- </p>
432
- <p id="duplicate-post-remove-original-description">
433
- <?php
434
- /* translators: $s: link to edit or view the original item */
435
- printf( __( 'The original item this was copied from is: <span class="duplicate_post_original_item_title_span">%s</span>', 'duplicate-post' ), duplicate_post_get_edit_or_view_link( $original_item ) );
436
- ?>
437
- </p>
438
-
439
- <?php
440
- } else { ?>
441
- <script>
442
- (function(jQuery){
443
- jQuery('#duplicate_post_show_original').hide();
444
- })(jQuery);
445
- </script>
446
- <?php }
447
- }
448
-
449
- /**
450
- * Add the link to action list for post_row_actions
451
- */
452
- function duplicate_post_make_duplicate_link_row($actions, $post) {
453
- //$title = empty( $post->post_title ) ? __( '(no title)', 'duplicate-post' ) : $post->post_title;
454
- $title = _draft_or_post_title( $post );
455
-
456
- /**
457
- * Filter allowing displaying duplicate post link for current post.
458
- *
459
- * @param boolean $show_duplicate_link When to show duplicate link.
460
- * @param WP_Post $post The post object.
461
- *
462
- * @return boolean
463
- */
464
- if ( apply_filters( 'duplicate_post_show_link', duplicate_post_is_current_user_allowed_to_copy() && duplicate_post_is_post_type_enabled( $post->post_type ), $post ) ) {
465
- $actions['clone'] = '<a href="' . duplicate_post_get_clone_post_link( $post->ID, 'display', false ) .
466
- '" aria-label="' . esc_attr(
467
- /* translators: %s: Post title. */
468
- sprintf( __( 'Clone &#8220;%s&#8221;', 'duplicate-post' ), $title )
469
- ) . '">' .
470
- esc_html_x( 'Clone', 'verb', 'duplicate-post' ) . '</a>';
471
-
472
- $actions['edit_as_new_draft'] = '<a href="' . duplicate_post_get_clone_post_link( $post->ID ) .
473
- '" aria-label="' . esc_attr(
474
- /* translators: %s: Post title. */
475
- sprintf( __( 'New draft of &#8220;%s&#8221;', 'duplicate-post' ), $title )
476
- ) . '">' .
477
- esc_html__( 'New Draft', 'duplicate-post' ) .
478
- '</a>';
479
- }
480
- return $actions;
481
- }
482
-
483
- /**
484
- * Add a button in the post/page edit screen to create a clone
485
- */
486
- function duplicate_post_add_duplicate_post_button() {
487
- if ( isset( $_GET['post'] )){
488
- $id = $_GET['post'];
489
- $post = get_post($id);
490
- if(duplicate_post_is_current_user_allowed_to_copy() && duplicate_post_is_post_type_enabled($post->post_type)) {
491
- ?>
492
- <div id="duplicate-action">
493
- <a class="submitduplicate duplication"
494
- href="<?php echo esc_url( duplicate_post_get_clone_post_link( $id ) ); ?>"><?php esc_html_e('Copy to a new draft', 'duplicate-post'); ?>
495
- </a>
496
- </div>
497
- <?php
498
- }
499
- }
500
- }
501
-
502
- /*
503
- * This function calls the creation of a new copy of the selected post (as a draft)
504
- * then redirects to the edit post screen
505
- */
506
- function duplicate_post_save_as_new_post_draft(){
507
- duplicate_post_save_as_new_post('draft');
508
- }
509
-
510
- function duplicate_post_add_removable_query_arg( $removable_query_args ){
511
- $removable_query_args[] = 'cloned';
512
- return $removable_query_args;
513
- }
514
-
515
- /*
516
- * This function calls the creation of a new copy of the selected post (by default preserving the original publish status)
517
- * then redirects to the post list
518
- */
519
- function duplicate_post_save_as_new_post($status = ''){
520
- if(!duplicate_post_is_current_user_allowed_to_copy()){
521
- wp_die(esc_html__('Current user is not allowed to copy posts.', 'duplicate-post'));
522
- }
523
-
524
- if (! ( isset( $_GET['post']) || isset( $_POST['post']) || ( isset($_REQUEST['action']) && 'duplicate_post_save_as_new_post' == $_REQUEST['action'] ) ) ) {
525
- wp_die(esc_html__('No post to duplicate has been supplied!', 'duplicate-post'));
526
- }
527
-
528
- // Get the original post
529
- $id = (isset($_GET['post']) ? $_GET['post'] : $_POST['post']);
530
-
531
- check_admin_referer('duplicate-post_' . $id);
532
-
533
- $post = get_post($id);
534
-
535
- // Copy the post and insert it
536
- if (isset($post) && $post!=null) {
537
- $post_type = $post->post_type;
538
- $new_id = duplicate_post_create_duplicate($post, $status);
539
-
540
- if ($status == ''){
541
- $sendback = wp_get_referer();
542
- if ( ! $sendback ||
543
- strpos( $sendback, 'post.php' ) !== false ||
544
- strpos( $sendback, 'post-new.php' ) !== false ) {
545
- if ( 'attachment' == $post_type ) {
546
- $sendback = admin_url( 'upload.php' );
547
- } else {
548
- $sendback = admin_url( 'edit.php' );
549
- if ( ! empty( $post_type ) ) {
550
- $sendback = add_query_arg( 'post_type', $post_type, $sendback );
551
- }
552
- }
553
- } else {
554
- $sendback = remove_query_arg( array('trashed', 'untrashed', 'deleted', 'cloned', 'ids'), $sendback );
555
- }
556
- // Redirect to the post list screen
557
- wp_redirect( add_query_arg( array( 'cloned' => 1, 'ids' => $post->ID), $sendback ) );
558
- } else {
559
- // Redirect to the edit screen for the new draft post
560
- wp_redirect( add_query_arg( array( 'cloned' => 1, 'ids' => $post->ID), admin_url( 'post.php?action=edit&post=' . $new_id ) ) );
561
- }
562
- exit;
563
-
564
- } else {
565
- wp_die(esc_html__('Copy creation failed, could not find original:', 'duplicate-post') . ' ' . htmlspecialchars($id));
566
- }
567
  }
568
 
569
  /**
570
- * Copy the taxonomies of a post to another post
 
 
 
 
 
571
  */
572
- function duplicate_post_copy_post_taxonomies($new_id, $post) {
573
  global $wpdb;
574
- if (isset($wpdb->terms)) {
575
- // Clear default category (added by wp_insert_post)
576
- wp_set_object_terms( $new_id, NULL, 'category' );
577
 
578
- $post_taxonomies = get_object_taxonomies($post->post_type);
579
- // several plugins just add support to post-formats but don't register post_format taxonomy
580
- if(post_type_supports($post->post_type, 'post-formats') && !in_array('post_format', $post_taxonomies)){
581
  $post_taxonomies[] = 'post_format';
582
  }
583
 
584
- $taxonomies_blacklist = get_option('duplicate_post_taxonomies_blacklist');
585
- if ($taxonomies_blacklist == "") $taxonomies_blacklist = array();
586
- if(get_option('duplicate_post_copyformat') == 0){
 
 
587
  $taxonomies_blacklist[] = 'post_format';
588
  }
589
- $taxonomies = array_diff($post_taxonomies, $taxonomies_blacklist);
590
- foreach ($taxonomies as $taxonomy) {
591
- $post_terms = wp_get_object_terms($post->ID, $taxonomy, array( 'orderby' => 'term_order' ));
592
- $terms = array();
593
- for ($i=0; $i<count($post_terms); $i++) {
594
- $terms[] = $post_terms[$i]->slug;
 
 
 
 
 
 
 
 
 
 
 
595
  }
596
- wp_set_object_terms($new_id, $terms, $taxonomy);
597
  }
598
  }
599
  }
600
 
601
  /**
602
- * Copy the meta information of a post to another post
603
- */
604
- function duplicate_post_copy_post_meta_info($new_id, $post) {
605
- $post_meta_keys = get_post_custom_keys($post->ID);
606
- if (empty($post_meta_keys)) return;
607
- $meta_blacklist = get_option('duplicate_post_blacklist');
608
- if ($meta_blacklist == ""){
 
 
 
 
 
609
  $meta_blacklist = array();
610
  } else {
611
- $meta_blacklist = explode(',', $meta_blacklist);
612
- $meta_blacklist = array_filter($meta_blacklist);
613
- $meta_blacklist = array_map('trim', $meta_blacklist);
614
  }
615
- $meta_blacklist[] = '_edit_lock'; // edit lock
616
- $meta_blacklist[] = '_edit_last'; // edit lock
617
- if(get_option('duplicate_post_copytemplate') == 0){
618
  $meta_blacklist[] = '_wp_page_template';
619
  }
620
- if(get_option('duplicate_post_copythumbnail') == 0){
621
  $meta_blacklist[] = '_thumbnail_id';
622
  }
623
 
624
- $meta_blacklist = apply_filters_deprecated( 'duplicate_post_blacklist_filter' , array( $meta_blacklist ), '3.2.5', 'duplicate_post_excludelist_filter' );
625
- $meta_blacklist = apply_filters( 'duplicate_post_excludelist_filter' , $meta_blacklist );
 
 
 
 
 
 
 
626
 
627
- $meta_blacklist_string = '('.implode(')|(',$meta_blacklist).')';
628
- if(strpos($meta_blacklist_string, '*') !== false){
629
- $meta_blacklist_string = str_replace(array('*'), array('[a-zA-Z0-9_]*'), $meta_blacklist_string);
630
 
631
  $meta_keys = array();
632
- foreach($post_meta_keys as $meta_key){
633
- if(!preg_match('#^'.$meta_blacklist_string.'$#', $meta_key))
634
  $meta_keys[] = $meta_key;
 
635
  }
636
  } else {
637
- $meta_keys = array_diff($post_meta_keys, $meta_blacklist);
638
  }
639
 
 
 
 
 
 
 
 
640
  $meta_keys = apply_filters( 'duplicate_post_meta_keys_filter', $meta_keys );
641
 
642
- foreach ($meta_keys as $meta_key) {
643
- $meta_values = get_post_custom_values($meta_key, $post->ID);
644
- foreach ($meta_values as $meta_value) {
645
- $meta_value = maybe_unserialize($meta_value);
646
- add_post_meta($new_id, $meta_key, duplicate_post_wp_slash($meta_value));
647
  }
648
  }
649
  }
650
 
651
- /*
652
  * Workaround for inconsistent wp_slash.
653
  * Works only with WP 4.4+ (map_deep)
 
 
 
 
 
654
  */
655
  function duplicate_post_addslashes_deep( $value ) {
656
- if (function_exists('map_deep')){
657
  return map_deep( $value, 'duplicate_post_addslashes_to_strings_only' );
658
  } else {
659
  return wp_slash( $value );
660
  }
661
  }
662
 
 
 
 
 
 
 
 
 
663
  function duplicate_post_addslashes_to_strings_only( $value ) {
664
  return is_string( $value ) ? addslashes( $value ) : $value;
665
  }
666
 
 
 
 
 
 
 
 
 
667
  function duplicate_post_wp_slash( $value ) {
668
  return duplicate_post_addslashes_deep( $value );
669
  }
670
 
671
-
672
-
673
  /**
674
- * Copy the attachments
675
- */
676
- function duplicate_post_copy_attachments($new_id, $post){
677
- // get thumbnail ID
678
- $old_thumbnail_id = get_post_thumbnail_id($post->ID);
679
- // get children
680
- $children = get_posts(array( 'post_type' => 'any', 'numberposts' => -1, 'post_status' => 'any', 'post_parent' => $post->ID ));
681
- // clone old attachments
682
- foreach($children as $child){
683
- if ($child->post_type != 'attachment') continue;
684
- $url = wp_get_attachment_url($child->ID);
685
- // Let's copy the actual file
 
 
 
 
 
 
 
 
 
 
 
 
686
  $tmp = download_url( $url );
687
- if( is_wp_error( $tmp ) ) {
688
- @unlink($tmp);
689
  continue;
690
  }
691
 
692
- $desc = wp_slash($child->post_content);
693
 
694
- $file_array = array();
695
- $file_array['name'] = basename($url);
696
  $file_array['tmp_name'] = $tmp;
697
  // "Upload" to the media collection
698
  $new_attachment_id = media_handle_sideload( $file_array, $new_id, $desc );
699
 
700
- if ( is_wp_error($new_attachment_id) ) {
701
- @unlink($file_array['tmp_name']);
702
  continue;
703
  }
704
  $new_post_author = wp_get_current_user();
705
- $cloned_child = array(
706
- 'ID' => $new_attachment_id,
707
- 'post_title' => $child->post_title,
708
- 'post_excerpt' => $child->post_excerpt,
709
- 'post_content' => $child->post_content,
710
- 'post_author' => $new_post_author->ID
711
  );
712
- wp_update_post( wp_slash($cloned_child) );
713
-
714
- $alt_title = get_post_meta($child->ID, '_wp_attachment_image_alt', true);
715
- if($alt_title) update_post_meta($new_attachment_id, '_wp_attachment_image_alt', wp_slash($alt_title));
716
 
717
- // if we have cloned the post thumbnail, set the copy as the thumbnail for the new post
718
- if(get_option('duplicate_post_copythumbnail') == 1 && $old_thumbnail_id == $child->ID){
719
- set_post_thumbnail($new_id, $new_attachment_id);
720
  }
721
 
 
 
 
 
722
  }
723
  }
724
 
725
  /**
726
- * Copy children posts
 
 
 
 
727
  */
728
- function duplicate_post_copy_children($new_id, $post, $status = ''){
729
- // get children
730
- $children = get_posts(array( 'post_type' => 'any', 'numberposts' => -1, 'post_status' => 'any', 'post_parent' => $post->ID ));
731
- // clone old attachments
732
- foreach($children as $child){
733
- if ($child->post_type == 'attachment') continue;
734
- duplicate_post_create_duplicate($child, $status, $new_id);
 
 
 
 
 
 
 
 
 
735
  }
736
  }
737
 
738
  /**
739
- * Copy comments
 
 
 
740
  */
741
- function duplicate_post_copy_comments($new_id, $post){
742
- $comments = get_comments(array(
743
- 'post_id' => $post->ID,
744
- 'order' => 'ASC',
745
- 'orderby' => 'comment_date_gmt'
746
- ));
 
 
747
 
748
  $old_id_to_new = array();
749
- foreach ($comments as $comment){
750
- //do not copy pingbacks or trackbacks
751
- if( $comment->comment_type === "pingback" || $comment->comment_type === "trackback" ) continue;
752
- $parent = ($comment->comment_parent && $old_id_to_new[$comment->comment_parent])?$old_id_to_new[$comment->comment_parent]:0;
 
 
753
  $commentdata = array(
754
- 'comment_post_ID' => $new_id,
755
- 'comment_author' => $comment->comment_author,
756
  'comment_author_email' => $comment->comment_author_email,
757
- 'comment_author_url' => $comment->comment_author_url,
758
- 'comment_content' => $comment->comment_content,
759
- 'comment_type' => $comment->comment_type,
760
- 'comment_parent' => $parent,
761
- 'user_id' => $comment->user_id,
762
- 'comment_author_IP' => $comment->comment_author_IP,
763
- 'comment_agent' => $comment->comment_agent,
764
- 'comment_karma' => $comment->comment_karma,
765
- 'comment_approved' => $comment->comment_approved,
766
  );
767
- if(get_option('duplicate_post_copydate') == 1){
768
- $commentdata['comment_date'] = $comment->comment_date ;
769
- $commentdata['comment_date_gmt'] = get_gmt_from_date($comment->comment_date);
 
 
 
 
 
770
  }
771
- $new_comment_id = wp_insert_comment($commentdata);
772
- $old_id_to_new[$comment->comment_ID] = $new_comment_id;
773
  }
774
  }
775
 
776
  /**
777
- * Create a duplicate from a post
 
 
 
 
 
 
 
778
  */
779
- function duplicate_post_create_duplicate($post, $status = '', $parent_id = '') {
 
 
 
 
 
 
 
 
780
 
781
- do_action('duplicate_post_pre_copy');
 
 
 
 
 
 
 
 
 
 
 
 
 
782
 
783
- if (!duplicate_post_is_post_type_enabled($post->post_type) && $post->post_type != 'attachment')
784
- wp_die(esc_html__('Copy features for this post type are not enabled in options page', 'duplicate-post'));
 
 
 
 
 
 
785
 
786
- $new_post_status = (empty($status))? $post->post_status: $status;
 
787
 
788
- if ($post->post_type != 'attachment'){
789
- $prefix = sanitize_text_field(get_option('duplicate_post_title_prefix'));
790
- $suffix = sanitize_text_field(get_option('duplicate_post_title_suffix'));
791
- $title = ' ';
792
- if (get_option('duplicate_post_copytitle') == 1) {
793
  $title = $post->post_title;
794
- if (!empty($prefix)) $prefix.= " ";
795
- if (!empty($suffix)) $suffix = " ".$suffix;
796
- } else {
 
 
 
 
797
  $title = ' ';
798
  }
799
- $title = trim($prefix.$title.$suffix);
800
 
801
  /*
802
  * Not sure we should force a title. Instead, we should respect what WP does.
803
- * if ($title == ''){
804
- * // empty title
805
- * $title = __('Untitled', 'default');
806
- * }
807
  */
808
- if (get_option('duplicate_post_copystatus') == 0){
 
809
  $new_post_status = 'draft';
810
  } else {
811
- if ( 'publish' == $new_post_status || 'future' == $new_post_status ){
812
- // check if the user has the right capability
813
- if(is_post_type_hierarchical( $post->post_type )){
814
- if(!current_user_can('publish_pages')){
815
  $new_post_status = 'pending';
816
  }
817
  } else {
818
- if(!current_user_can('publish_posts')){
819
  $new_post_status = 'pending';
820
  }
821
  }
@@ -823,151 +671,146 @@ function duplicate_post_create_duplicate($post, $status = '', $parent_id = '') {
823
  }
824
  }
825
 
826
- $new_post_author = wp_get_current_user();
827
  $new_post_author_id = $new_post_author->ID;
828
- if ( get_option('duplicate_post_copyauthor') == '1' ){
829
- // check if the user has the right capability
830
- if(is_post_type_hierarchical( $post->post_type )){
831
- if(current_user_can('edit_others_pages')){
832
  $new_post_author_id = $post->post_author;
833
  }
834
  } else {
835
- if(current_user_can('edit_others_posts')){
836
  $new_post_author_id = $post->post_author;
837
  }
838
  }
839
  }
840
 
841
- $menu_order = (get_option('duplicate_post_copymenuorder') == '1') ? $post->menu_order : 0;
842
- $increase_menu_order_by = get_option('duplicate_post_increase_menu_order_by');
843
- if(!empty($increase_menu_order_by) && is_numeric($increase_menu_order_by)){
844
- $menu_order += intval($increase_menu_order_by);
845
  }
846
 
847
  $post_name = $post->post_name;
848
- if(get_option('duplicate_post_copyslug') != 1){
849
  $post_name = '';
850
  }
 
851
 
852
  $new_post = array(
853
- 'menu_order' => $menu_order,
854
- 'comment_status' => $post->comment_status,
855
- 'ping_status' => $post->ping_status,
856
- 'post_author' => $new_post_author_id,
857
- 'post_content' => (get_option('duplicate_post_copycontent') == '1') ? $post->post_content : "" ,
858
- 'post_content_filtered' => (get_option('duplicate_post_copycontent') == '1') ? $post->post_content_filtered : "" ,
859
- 'post_excerpt' => (get_option('duplicate_post_copyexcerpt') == '1') ? $post->post_excerpt : "",
860
- 'post_mime_type' => $post->post_mime_type,
861
- 'post_parent' => $new_post_parent = empty($parent_id)? $post->post_parent : $parent_id,
862
- 'post_password' => (get_option('duplicate_post_copypassword') == '1') ? $post->post_password: "",
863
- 'post_status' => $new_post_status,
864
- 'post_title' => $title,
865
- 'post_type' => $post->post_type,
866
- 'post_name' => $post_name
867
  );
868
 
869
- if(get_option('duplicate_post_copydate') == 1){
870
- $new_post['post_date'] = $new_post_date = $post->post_date ;
871
- $new_post['post_date_gmt'] = get_gmt_from_date($new_post_date);
 
872
  }
873
 
874
- $new_post_id = wp_insert_post(wp_slash($new_post));
 
 
 
 
 
 
 
 
 
875
 
876
  // If you have written a plugin which uses non-WP database tables to save
877
  // information about a post you can hook this action to dupe that data.
 
878
 
879
- if($new_post_id !== 0 && !is_wp_error($new_post_id)){
880
-
881
- if ($post->post_type == 'page' || is_post_type_hierarchical( $post->post_type ))
882
  do_action( 'dp_duplicate_page', $new_post_id, $post, $status );
883
- else
884
  do_action( 'dp_duplicate_post', $new_post_id, $post, $status );
 
885
 
886
- delete_post_meta($new_post_id, '_dp_original');
887
- add_post_meta($new_post_id, '_dp_original', $post->ID);
888
-
889
- do_action('duplicate_post_post_copy');
890
-
891
  }
892
 
 
 
 
 
 
 
 
 
 
 
893
  return $new_post_id;
894
  }
895
 
896
- //Add some links on the plugin page
897
- function duplicate_post_add_plugin_links($links, $file) {
898
- if ( $file == plugin_basename(dirname(__FILE__).'/duplicate-post.php') ) {
899
- $links[] = '<a href="https://yoast.com/wordpress/plugins/duplicate-post/" aria-label="' . esc_attr__('Documentation for Duplicate Post', 'duplicate-post') . '">' . esc_html__('Documentation', 'duplicate-post') . '</a>';
 
 
 
 
 
 
900
  }
901
  return $links;
902
  }
903
 
904
- /*** NOTICES ***/
905
- function duplicate_post_action_admin_notice() {
906
- if ( ! empty( $_REQUEST['cloned'] ) ) {
907
- $copied_posts = intval( $_REQUEST['cloned'] );
908
- printf( '<div id="message" class="notice notice-success fade"><p>' .
909
- /* translators: %s: number */
910
- _n( '%s item copied.',
911
- '%s items copied.',
912
- $copied_posts,
913
- 'duplicate-post'
914
- ) . '</p></div>', $copied_posts );
915
- remove_query_arg( 'cloned' );
916
- }
917
- }
918
-
919
-
920
- /*** BULK ACTIONS ***/
921
- add_action('admin_init', 'duplicate_post_add_bulk_filters');
922
-
923
- function duplicate_post_add_bulk_filters(){
924
- if(get_option('duplicate_post_show_bulkactions') != 1) return;
925
-
926
- if ( ! duplicate_post_is_current_user_allowed_to_copy() ) {
927
- return;
928
- }
929
-
930
- $duplicate_post_types_enabled = get_option('duplicate_post_types_enabled', array ('post', 'page'));
931
- if(!is_array($duplicate_post_types_enabled)) $duplicate_post_types_enabled = array($duplicate_post_types_enabled);
932
- foreach($duplicate_post_types_enabled as $duplicate_post_type_enabled){
933
- add_filter( "bulk_actions-edit-{$duplicate_post_type_enabled}", 'duplicate_post_register_bulk_action' );
934
- add_filter( "handle_bulk_actions-edit-{$duplicate_post_type_enabled}", 'duplicate_post_action_handler', 10, 3 );
935
- }
936
- }
937
 
938
- function duplicate_post_register_bulk_action($bulk_actions) {
939
- $bulk_actions['duplicate_post_clone'] = esc_html__( 'Clone', 'duplicate-post');
940
- return $bulk_actions;
941
- }
942
 
943
- function duplicate_post_action_handler( $redirect_to, $doaction, $post_ids ) {
944
- if ( $doaction !== 'duplicate_post_clone' ) {
945
- return $redirect_to;
946
- }
947
- $counter = 0;
948
- foreach ( $post_ids as $post_id ) {
949
- $post = get_post($post_id);
950
- if(!empty($post)){
951
- if( get_option('duplicate_post_copychildren') != 1
952
- || !is_post_type_hierarchical( $post->post_type )
953
- || (is_post_type_hierarchical( $post->post_type ) && !duplicate_post_has_ancestors_marked($post, $post_ids))){
954
- if(duplicate_post_create_duplicate($post)){
955
- $counter++;
956
- }
957
- }
958
- }
959
- }
960
- $redirect_to = add_query_arg( 'cloned', $counter, $redirect_to );
961
- return $redirect_to;
962
- }
 
963
 
964
- function duplicate_post_has_ancestors_marked($post, $post_ids){
965
- $ancestors_in_array = 0;
966
- $parent = $post->ID;
967
- while ($parent = wp_get_post_parent_id($parent)){
968
- if(in_array($parent, $post_ids)){
969
- $ancestors_in_array++;
970
- }
971
- }
972
- return ($ancestors_in_array !== 0);
973
  }
1
  <?php
2
+ /**
3
+ * Backend functions.
4
+ *
5
+ * @package Duplicate Post
6
+ * @since 2.0
7
+ */
8
+
9
+ if ( ! is_admin() ) {
10
  return;
11
+ }
12
 
13
+ require_once dirname( __FILE__ ) . '/duplicate-post-options.php';
14
 
15
+ require_once dirname( __FILE__ ) . '/compat/duplicate-post-wpml.php';
16
+ require_once dirname( __FILE__ ) . '/compat/duplicate-post-jetpack.php';
17
 
18
  /**
19
+ * Wrapper for the option 'duplicate_post_version'.
20
+ */
21
  function duplicate_post_get_installed_version() {
22
  return get_option( 'duplicate_post_version' );
23
  }
24
 
25
  /**
26
+ * Wrapper for the defined constant DUPLICATE_POST_CURRENT_VERSION.
27
  */
28
  function duplicate_post_get_current_version() {
29
  return DUPLICATE_POST_CURRENT_VERSION;
30
  }
31
 
32
+ add_action( 'admin_init', 'duplicate_post_admin_init' );
33
 
34
+ /**
35
+ * Adds handlers depending on the options.
36
+ */
37
+ function duplicate_post_admin_init() {
38
  duplicate_post_plugin_upgrade();
39
 
40
+ if ( intval( get_site_option( 'duplicate_post_show_notice' ) ) === 1 ) {
41
+ if ( is_multisite() ) {
 
 
 
 
 
42
  add_action( 'network_admin_notices', 'duplicate_post_show_update_notice' );
43
  } else {
44
  add_action( 'admin_notices', 'duplicate_post_show_update_notice' );
46
  add_action( 'wp_ajax_duplicate_post_dismiss_notice', 'duplicate_post_dismiss_notice' );
47
  }
48
 
49
+ add_action( 'dp_duplicate_post', 'duplicate_post_copy_post_meta_info', 10, 2 );
50
+ add_action( 'dp_duplicate_page', 'duplicate_post_copy_post_meta_info', 10, 2 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
+ if ( intval( get_option( 'duplicate_post_copychildren' ) ) === 1 ) {
53
+ add_action( 'dp_duplicate_post', 'duplicate_post_copy_children', 20, 3 );
54
+ add_action( 'dp_duplicate_page', 'duplicate_post_copy_children', 20, 3 );
 
 
 
55
  }
56
 
57
+ if ( intval( get_option( 'duplicate_post_copyattachments' ) ) === 1 ) {
58
+ add_action( 'dp_duplicate_post', 'duplicate_post_copy_attachments', 30, 2 );
59
+ add_action( 'dp_duplicate_page', 'duplicate_post_copy_attachments', 30, 2 );
60
  }
61
 
62
+ if ( intval( get_option( 'duplicate_post_copycomments' ) ) === 1 ) {
63
+ add_action( 'dp_duplicate_post', 'duplicate_post_copy_comments', 40, 2 );
64
+ add_action( 'dp_duplicate_page', 'duplicate_post_copy_comments', 40, 2 );
65
  }
66
 
67
+ add_action( 'dp_duplicate_post', 'duplicate_post_copy_post_taxonomies', 50, 2 );
68
+ add_action( 'dp_duplicate_page', 'duplicate_post_copy_post_taxonomies', 50, 2 );
 
 
69
 
70
+ add_filter( 'plugin_row_meta', 'duplicate_post_add_plugin_links', 10, 2 );
71
  }
72
 
 
73
  /**
74
+ * Plugin upgrade.
75
  */
76
  function duplicate_post_plugin_upgrade() {
77
  $installed_version = duplicate_post_get_installed_version();
78
 
79
+ if ( duplicate_post_get_current_version() === $installed_version ) {
80
  return;
81
+ }
82
 
83
+ if ( empty( $installed_version ) ) {
84
+ // Get default roles.
85
+ $default_roles = array(
86
+ 'editor',
87
+ 'administrator',
88
+ 'wpseo_manager',
89
+ 'wpseo_editor',
90
  );
91
 
92
+ foreach ( $default_roles as $name ) {
93
+ $role = get_role( $name );
94
+ if ( ! empty( $role ) ) {
95
+ $role->add_cap( 'copy_posts' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  }
 
97
  }
98
  }
99
 
100
+ $show_links_in_defaults = [
101
+ 'row' => '1',
102
+ 'adminbar' => '1',
103
+ 'submitbox' => '1',
104
+ 'bulkactions' => '1',
105
+ ];
106
+
107
+ add_option( 'duplicate_post_copytitle', '1' );
108
+ add_option( 'duplicate_post_copydate', '0' );
109
+ add_option( 'duplicate_post_copystatus', '0' );
110
+ add_option( 'duplicate_post_copyslug', '0' );
111
+ add_option( 'duplicate_post_copyexcerpt', '1' );
112
+ add_option( 'duplicate_post_copycontent', '1' );
113
+ add_option( 'duplicate_post_copythumbnail', '1' );
114
+ add_option( 'duplicate_post_copytemplate', '1' );
115
+ add_option( 'duplicate_post_copyformat', '1' );
116
+ add_option( 'duplicate_post_copyauthor', '0' );
117
+ add_option( 'duplicate_post_copypassword', '0' );
118
+ add_option( 'duplicate_post_copyattachments', '0' );
119
+ add_option( 'duplicate_post_copychildren', '0' );
120
+ add_option( 'duplicate_post_copycomments', '0' );
121
+ add_option( 'duplicate_post_copymenuorder', '1' );
122
+ add_option( 'duplicate_post_taxonomies_blacklist', array() );
123
+ add_option( 'duplicate_post_blacklist', '' );
124
+ add_option( 'duplicate_post_types_enabled', array( 'post', 'page' ) );
125
+ add_option( 'duplicate_post_show_original_column', '0' );
126
+ add_option( 'duplicate_post_show_original_in_post_states', '0' );
127
+ add_option( 'duplicate_post_show_original_meta_box', '0' );
128
+ add_option(
129
+ 'duplicate_post_show_link',
130
+ [
131
+ 'new_draft' => '1',
132
+ 'clone' => '1',
133
+ 'rewrite_republish' => '1',
134
+ ]
135
+ );
136
+ add_option( 'duplicate_post_show_link_in', $show_links_in_defaults );
137
+
138
+ $taxonomies_blacklist = get_option( 'duplicate_post_taxonomies_blacklist' );
139
+ if ( '' === $taxonomies_blacklist ) {
140
+ $taxonomies_blacklist = array();
141
+ }
142
+ if ( in_array( 'post_format', $taxonomies_blacklist, true ) ) {
143
+ update_option( 'duplicate_post_copyformat', 0 );
144
+ $taxonomies_blacklist = array_diff( $taxonomies_blacklist, array( 'post_format' ) );
145
+ update_option( 'duplicate_post_taxonomies_blacklist', $taxonomies_blacklist );
146
+ }
147
+
148
+ $meta_blacklist = explode( ',', get_option( 'duplicate_post_blacklist' ) );
149
+ if ( '' === $meta_blacklist ) {
150
+ $meta_blacklist = array();
151
+ }
152
+ $meta_blacklist = array_map( 'trim', $meta_blacklist );
153
+ if ( in_array( '_wp_page_template', $meta_blacklist, true ) ) {
154
+ update_option( 'duplicate_post_copytemplate', 0 );
155
+ $meta_blacklist = array_diff( $meta_blacklist, array( '_wp_page_template' ) );
156
+ }
157
+ if ( in_array( '_thumbnail_id', $meta_blacklist, true ) ) {
158
+ update_option( 'duplicate_post_copythumbnail', 0 );
159
+ $meta_blacklist = array_diff( $meta_blacklist, array( '_thumbnail_id' ) );
160
+ }
161
+ update_option( 'duplicate_post_blacklist', implode( ',', $meta_blacklist ) );
162
 
163
+ delete_option( 'duplicate_post_show_notice' );
164
+ if ( version_compare( $installed_version, '3.2.5' ) < 0 ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  update_site_option( 'duplicate_post_show_notice', 1 );
166
  }
167
 
168
+ // Migrate the 'Show links in' options to the new array-based structure.
169
+ duplicate_post_migrate_show_links_in_options( $show_links_in_defaults );
170
+
171
+ delete_site_option( 'duplicate_post_version' );
172
  update_option( 'duplicate_post_version', duplicate_post_get_current_version() );
173
  }
174
 
175
  /**
176
+ * Runs the upgrade routine for version 4.0 to update the options in the database.
177
+ *
178
+ * @param array $defaults The default options to fall back on.
179
+ *
180
+ * @return void
181
+ */
182
+ function duplicate_post_migrate_show_links_in_options( $defaults ) {
183
+ $options_to_migrate = [
184
+ 'duplicate_post_show_row' => 'row',
185
+ 'duplicate_post_show_adminbar' => 'adminbar',
186
+ 'duplicate_post_show_submitbox' => 'submitbox',
187
+ 'duplicate_post_show_bulkactions' => 'bulkactions',
188
+ ];
189
+
190
+ $new_options = [];
191
+ foreach ( $options_to_migrate as $old => $new ) {
192
+ $new_options[ $new ] = \get_option( $old, $defaults[ $new ] );
193
+
194
+ \delete_option( $old );
195
+ }
196
+
197
+ \update_option( 'duplicate_post_show_link_in', $new_options );
198
+ }
199
+
200
+ /**
201
+ * Shows the update notice.
202
+ *
203
+ * @global string $wp_version The WordPress version string.
204
  */
205
  function duplicate_post_show_update_notice() {
206
+ if ( ! current_user_can( 'manage_options' ) ) {
207
+ return;
208
+ }
209
 
210
  $current_screen = get_current_screen();
211
+ if (
212
+ empty( $current_screen ) ||
213
  empty( $current_screen->base ) ||
214
+ ( $current_screen->base !== 'dashboard' && $current_screen->base !== 'plugins' )
215
+ ) {
216
  return;
217
+ }
218
+
219
+ $class = 'notice is-dismissible';
220
+ $message = '<p><strong>' . sprintf(
221
+ /* translators: %s: Yoast Duplicate Post version. */
222
+ __( "What's new in Yoast Duplicate Post version %s:", 'duplicate-post' ),
223
+ DUPLICATE_POST_CURRENT_VERSION
224
+ ) . '</strong> ';
225
+ $message .= __( 'Meet the Rewrite & Republish feature! It makes it easy as ABC to update a post/page without taking it offline or having to take extra steps!', 'duplicate-post' )
226
+ . ' ';
227
+
228
+ $message .= '<a href="https://yoa.st/duplicate-post-4-0">'
229
+ . sprintf(
230
+ /* translators: %s: Yoast Duplicate Post version. */
231
+ __( 'Read more about what’s new in Yoast Duplicate Post %s!', 'duplicate-post' ),
232
+ DUPLICATE_POST_CURRENT_VERSION
233
+ )
234
+ . '</a></p>';
235
 
 
 
 
 
236
  $message .= '<p>%%SIGNUP_FORM%%</p>';
237
+
 
 
 
 
238
  $allowed_tags = array(
239
  'a' => array(
240
  'href' => array(),
 
241
  ),
242
  'br' => array(),
243
  'p' => array(),
 
244
  'strong' => array(),
245
  );
246
 
249
 
250
  $img_path = plugins_url( '/duplicate_post_yoast_icon-125x125.png', __FILE__ );
251
 
252
+ echo '<div id="duplicate-post-notice" class="' . esc_attr( $class ) . '" style="display: flex; align-items: center;">
253
+ <img src="' . esc_url( $img_path ) . '" alt=""/>
254
+ <div style="margin: 0.5em">' . $sanitized_message . // phpcs:ignore WordPress.Security.EscapeOutput
255
+ '</div></div>';
256
+
257
  echo "<script>
258
  function duplicate_post_dismiss_notice(){
259
  var data = {
274
  }
275
 
276
  /**
277
+ * Dismisses the notice.
278
  *
279
+ * @return bool
280
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  function duplicate_post_dismiss_notice() {
282
+ $result = update_site_option( 'duplicate_post_show_notice', 0 );
283
  return $result;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  }
285
 
286
  /**
287
+ * Copies the taxonomies of a post to another post.
288
+ *
289
+ * @global wpdb $wpdb WordPress database abstraction object.
290
+ *
291
+ * @param int $new_id New post ID.
292
+ * @param WP_Post $post The original post object.
293
  */
294
+ function duplicate_post_copy_post_taxonomies( $new_id, $post ) {
295
  global $wpdb;
296
+ if ( isset( $wpdb->terms ) ) {
297
+ // Clear default category (added by wp_insert_post).
298
+ wp_set_object_terms( $new_id, null, 'category' );
299
 
300
+ $post_taxonomies = get_object_taxonomies( $post->post_type );
301
+ // Several plugins just add support to post-formats but don't register post_format taxonomy.
302
+ if ( post_type_supports( $post->post_type, 'post-formats' ) && ! in_array( 'post_format', $post_taxonomies, true ) ) {
303
  $post_taxonomies[] = 'post_format';
304
  }
305
 
306
+ $taxonomies_blacklist = get_option( 'duplicate_post_taxonomies_blacklist' );
307
+ if ( '' === $taxonomies_blacklist ) {
308
+ $taxonomies_blacklist = array();
309
+ }
310
+ if ( intval( get_option( 'duplicate_post_copyformat' ) ) === 0 ) {
311
  $taxonomies_blacklist[] = 'post_format';
312
  }
313
+
314
+ /**
315
+ * Filters the taxonomy excludelist when copying a post.
316
+ *
317
+ * @param array $taxonomies_blacklist The taxonomy excludelist from the options.
318
+ *
319
+ * @return array
320
+ */
321
+ $taxonomies_blacklist = apply_filters( 'duplicate_post_taxonomies_excludelist_filter', $taxonomies_blacklist );
322
+
323
+ $taxonomies = array_diff( $post_taxonomies, $taxonomies_blacklist );
324
+ foreach ( $taxonomies as $taxonomy ) {
325
+ $post_terms = wp_get_object_terms( $post->ID, $taxonomy, array( 'orderby' => 'term_order' ) );
326
+ $terms = array();
327
+ $num_terms = count( $post_terms );
328
+ for ( $i = 0; $i < $num_terms; $i++ ) {
329
+ $terms[] = $post_terms[ $i ]->slug;
330
  }
331
+ wp_set_object_terms( $new_id, $terms, $taxonomy );
332
  }
333
  }
334
  }
335
 
336
  /**
337
+ * Copies the meta information of a post to another post
338
+ *
339
+ * @param int $new_id The new post ID.
340
+ * @param WP_Post $post The original post object.
341
+ */
342
+ function duplicate_post_copy_post_meta_info( $new_id, $post ) {
343
+ $post_meta_keys = get_post_custom_keys( $post->ID );
344
+ if ( empty( $post_meta_keys ) ) {
345
+ return;
346
+ }
347
+ $meta_blacklist = get_option( 'duplicate_post_blacklist' );
348
+ if ( '' === $meta_blacklist ) {
349
  $meta_blacklist = array();
350
  } else {
351
+ $meta_blacklist = explode( ',', $meta_blacklist );
352
+ $meta_blacklist = array_filter( $meta_blacklist );
353
+ $meta_blacklist = array_map( 'trim', $meta_blacklist );
354
  }
355
+ $meta_blacklist[] = '_edit_lock'; // Edit lock.
356
+ $meta_blacklist[] = '_edit_last'; // Edit lock.
357
+ if ( intval( get_option( 'duplicate_post_copytemplate' ) ) === 0 ) {
358
  $meta_blacklist[] = '_wp_page_template';
359
  }
360
+ if ( intval( get_option( 'duplicate_post_copythumbnail' ) ) === 0 ) {
361
  $meta_blacklist[] = '_thumbnail_id';
362
  }
363
 
364
+ $meta_blacklist = apply_filters_deprecated( 'duplicate_post_blacklist_filter', array( $meta_blacklist ), '3.2.5', 'duplicate_post_excludelist_filter' );
365
+ /**
366
+ * Filters the meta fields excludelist when copying a post.
367
+ *
368
+ * @param array $meta_blacklist The meta fields excludelist from the options.
369
+ *
370
+ * @return array
371
+ */
372
+ $meta_blacklist = apply_filters( 'duplicate_post_excludelist_filter', $meta_blacklist );
373
 
374
+ $meta_blacklist_string = '(' . implode( ')|(', $meta_blacklist ) . ')';
375
+ if ( strpos( $meta_blacklist_string, '*' ) !== false ) {
376
+ $meta_blacklist_string = str_replace( array( '*' ), array( '[a-zA-Z0-9_]*' ), $meta_blacklist_string );
377
 
378
  $meta_keys = array();
379
+ foreach ( $post_meta_keys as $meta_key ) {
380
+ if ( ! preg_match( '#^' . $meta_blacklist_string . '$#', $meta_key ) ) {
381
  $meta_keys[] = $meta_key;
382
+ }
383
  }
384
  } else {
385
+ $meta_keys = array_diff( $post_meta_keys, $meta_blacklist );
386
  }
387
 
388
+ /**
389
+ * Filters the list of meta fields names when copying a post.
390
+ *
391
+ * @param array $meta_keys The list of meta fields name, with the ones in the excludelist already removed.
392
+ *
393
+ * @return array
394
+ */
395
  $meta_keys = apply_filters( 'duplicate_post_meta_keys_filter', $meta_keys );
396
 
397
+ foreach ( $meta_keys as $meta_key ) {
398
+ $meta_values = get_post_custom_values( $meta_key, $post->ID );
399
+ foreach ( $meta_values as $meta_value ) {
400
+ $meta_value = maybe_unserialize( $meta_value );
401
+ add_post_meta( $new_id, $meta_key, duplicate_post_wp_slash( $meta_value ) );
402
  }
403
  }
404
  }
405
 
406
+ /**
407
  * Workaround for inconsistent wp_slash.
408
  * Works only with WP 4.4+ (map_deep)
409
+ *
410
+ * @ignore
411
+ *
412
+ * @param mixed $value Array or object to be recursively slashed.
413
+ * @return string|mixed
414
  */
415
  function duplicate_post_addslashes_deep( $value ) {
416
+ if ( function_exists( 'map_deep' ) ) {
417
  return map_deep( $value, 'duplicate_post_addslashes_to_strings_only' );
418
  } else {
419
  return wp_slash( $value );
420
  }
421
  }
422
 
423
+ /**
424
+ * Adds slashes only to strings.
425
+ *
426
+ * @ignore
427
+ *
428
+ * @param mixed $value Value to slash only if string.
429
+ * @return string|mixed
430
+ */
431
  function duplicate_post_addslashes_to_strings_only( $value ) {
432
  return is_string( $value ) ? addslashes( $value ) : $value;
433
  }
434
 
435
+ /**
436
+ * Replacement function for faulty core wp_slash().
437
+ *
438
+ * @ignore
439
+ *
440
+ * @param mixed $value What to add slash to.
441
+ * @return mixed
442
+ */
443
  function duplicate_post_wp_slash( $value ) {
444
  return duplicate_post_addslashes_deep( $value );
445
  }
446
 
 
 
447
  /**
448
+ * Copies attachments, including physical files.
449
+ *
450
+ * @param int $new_id The new post ID.
451
+ * @param WP_Post $post The original post object.
452
+ */
453
+ function duplicate_post_copy_attachments( $new_id, $post ) {
454
+ // Get thumbnail ID.
455
+ $old_thumbnail_id = get_post_thumbnail_id( $post->ID );
456
+ // Get children.
457
+ $children = get_posts(
458
+ array(
459
+ 'post_type' => 'any',
460
+ 'numberposts' => -1,
461
+ 'post_status' => 'any',
462
+ 'post_parent' => $post->ID,
463
+ )
464
+ );
465
+ // Clone old attachments.
466
+ foreach ( $children as $child ) {
467
+ if ( 'attachment' !== $child->post_type ) {
468
+ continue;
469
+ }
470
+ $url = wp_get_attachment_url( $child->ID );
471
+ // Let's copy the actual file.
472
  $tmp = download_url( $url );
473
+ if ( is_wp_error( $tmp ) ) {
 
474
  continue;
475
  }
476
 
477
+ $desc = wp_slash( $child->post_content );
478
 
479
+ $file_array = array();
480
+ $file_array['name'] = basename( $url );
481
  $file_array['tmp_name'] = $tmp;
482
  // "Upload" to the media collection
483
  $new_attachment_id = media_handle_sideload( $file_array, $new_id, $desc );
484
 
485
+ if ( is_wp_error( $new_attachment_id ) ) {
486
+ unlink( $file_array['tmp_name'] );
487
  continue;
488
  }
489
  $new_post_author = wp_get_current_user();
490
+ $cloned_child = array(
491
+ 'ID' => $new_attachment_id,
492
+ 'post_title' => $child->post_title,
493
+ 'post_exceprt' => $child->post_title,
494
+ 'post_author' => $new_post_author->ID,
 
495
  );
496
+ wp_update_post( wp_slash( $cloned_child ) );
 
 
 
497
 
498
+ $alt_title = get_post_meta( $child->ID, '_wp_attachment_image_alt', true );
499
+ if ( $alt_title ) {
500
+ update_post_meta( $new_attachment_id, '_wp_attachment_image_alt', wp_slash( $alt_title ) );
501
  }
502
 
503
+ // If we have cloned the post thumbnail, set the copy as the thumbnail for the new post.
504
+ if ( intval( get_option( 'duplicate_post_copythumbnail' ) ) === 1 && $old_thumbnail_id === $child->ID ) {
505
+ set_post_thumbnail( $new_id, $new_attachment_id );
506
+ }
507
  }
508
  }
509
 
510
  /**
511
+ * Copies child posts.
512
+ *
513
+ * @param int $new_id The new post ID.
514
+ * @param WP_Post $post The original post object.
515
+ * @param string $status Optional. The destination status.
516
  */
517
+ function duplicate_post_copy_children( $new_id, $post, $status = '' ) {
518
+ // Get children.
519
+ $children = get_posts(
520
+ array(
521
+ 'post_type' => 'any',
522
+ 'numberposts' => -1,
523
+ 'post_status' => 'any',
524
+ 'post_parent' => $post->ID,
525
+ )
526
+ );
527
+
528
+ foreach ( $children as $child ) {
529
+ if ( 'attachment' === $child->post_type ) {
530
+ continue;
531
+ }
532
+ duplicate_post_create_duplicate( $child, $status, $new_id );
533
  }
534
  }
535
 
536
  /**
537
+ * Copies comments.
538
+ *
539
+ * @param int $new_id The new post ID.
540
+ * @param WP_Post $post The original post object.
541
  */
542
+ function duplicate_post_copy_comments( $new_id, $post ) {
543
+ $comments = get_comments(
544
+ array(
545
+ 'post_id' => $post->ID,
546
+ 'order' => 'ASC',
547
+ 'orderby' => 'comment_date_gmt',
548
+ )
549
+ );
550
 
551
  $old_id_to_new = array();
552
+ foreach ( $comments as $comment ) {
553
+ // Do not copy pingbacks or trackbacks.
554
+ if ( $comment->comment_type === 'pingback' || $comment->comment_type === 'trackback' ) {
555
+ continue;
556
+ }
557
+ $parent = ( $comment->comment_parent && $old_id_to_new[ $comment->comment_parent ] ) ? $old_id_to_new[ $comment->comment_parent ] : 0;
558
  $commentdata = array(
559
+ 'comment_post_ID' => $new_id,
560
+ 'comment_author' => $comment->comment_author,
561
  'comment_author_email' => $comment->comment_author_email,
562
+ 'comment_author_url' => $comment->comment_author_url,
563
+ 'comment_content' => $comment->comment_content,
564
+ 'comment_type' => $comment->comment_type,
565
+ 'comment_parent' => $parent,
566
+ 'user_id' => $comment->user_id,
567
+ 'comment_author_IP' => $comment->comment_author_IP,
568
+ 'comment_agent' => $comment->comment_agent,
569
+ 'comment_karma' => $comment->comment_karma,
570
+ 'comment_approved' => $comment->comment_approved,
571
  );
572
+ if ( intval( get_option( 'duplicate_post_copydate' ) ) === 1 ) {
573
+ $commentdata['comment_date'] = $comment->comment_date;
574
+ $commentdata['comment_date_gmt'] = get_gmt_from_date( $comment->comment_date );
575
+ }
576
+ $new_comment_id = wp_insert_comment( $commentdata );
577
+ $commentmeta = get_comment_meta( $new_comment_id );
578
+ foreach ( $commentmeta as $meta_key => $meta_value ) {
579
+ add_comment_meta( $new_comment_id, $meta_key, duplicate_post_wp_slash( $meta_value ) );
580
  }
581
+ $old_id_to_new[ $comment->comment_ID ] = $new_comment_id;
 
582
  }
583
  }
584
 
585
  /**
586
+ * Creates a duplicate from a post.
587
+ *
588
+ * This is the main functions that does the cloning.
589
+ *
590
+ * @param WP_Post $post The original post object.
591
+ * @param string $status Optional. The intended destination status.
592
+ * @param string $parent_id Optional. The parent post ID if we are calling this recursively.
593
+ * @return int|WP_Error
594
  */
595
+ function duplicate_post_create_duplicate( $post, $status = '', $parent_id = '' ) {
596
+ /**
597
+ * Fires before duplicating a post.
598
+ *
599
+ * @param WP_Post $post The original post object.
600
+ * @param bool $status The intended destination status.
601
+ * @param int $parent_id The parent post ID if we are calling this recursively.
602
+ */
603
+ do_action( 'duplicate_post_pre_copy', $post, $status, $parent_id );
604
 
605
+ /**
606
+ * Filter allowing to copy post.
607
+ *
608
+ * @param bool $can_duplicate Default to `true`.
609
+ * @param WP_Post $post The original post object.
610
+ * @param bool $status The intended destination status.
611
+ * @param int $parent_id The parent post ID if we are calling this recursively.
612
+ *
613
+ * @return bool
614
+ */
615
+ $can_duplicate = apply_filters( 'duplicate_post_allow', true, $post, $status, $parent_id );
616
+ if ( ! $can_duplicate ) {
617
+ wp_die( esc_html( __( 'You aren\'t allowed to duplicate this post', 'duplicate-post' ) ) );
618
+ }
619
 
620
+ if ( ! duplicate_post_is_post_type_enabled( $post->post_type ) && 'attachment' !== $post->post_type ) {
621
+ wp_die(
622
+ esc_html(
623
+ __( 'Copy features for this post type are not enabled in options page', 'duplicate-post' ) . ': ' .
624
+ $post->post_type
625
+ )
626
+ );
627
+ }
628
 
629
+ $new_post_status = ( empty( $status ) ) ? $post->post_status : $status;
630
+ $title = ' ';
631
 
632
+ if ( 'attachment' !== $post->post_type ) {
633
+ $prefix = sanitize_text_field( get_option( 'duplicate_post_title_prefix' ) );
634
+ $suffix = sanitize_text_field( get_option( 'duplicate_post_title_suffix' ) );
635
+ if ( intval( get_option( 'duplicate_post_copytitle' ) ) === 1 ) {
 
636
  $title = $post->post_title;
637
+ if ( ! empty( $prefix ) ) {
638
+ $prefix .= ' ';
639
+ }
640
+ if ( ! empty( $suffix ) ) {
641
+ $suffix = ' ' . $suffix;
642
+ }
643
+ } else {
644
  $title = ' ';
645
  }
646
+ $title = trim( $prefix . $title . $suffix );
647
 
648
  /*
649
  * Not sure we should force a title. Instead, we should respect what WP does.
650
+ * if ( '' === $title ) {
651
+ * // empty title.
652
+ * $title = __( 'Untitled', 'default' );
653
+ * }
654
  */
655
+
656
+ if ( intval( get_option( 'duplicate_post_copystatus' ) ) === 0 ) {
657
  $new_post_status = 'draft';
658
  } else {
659
+ if ( 'publish' === $new_post_status || 'future' === $new_post_status ) {
660
+ // Check if the user has the right capability.
661
+ if ( is_post_type_hierarchical( $post->post_type ) ) {
662
+ if ( ! current_user_can( 'publish_pages' ) ) {
663
  $new_post_status = 'pending';
664
  }
665
  } else {
666
+ if ( ! current_user_can( 'publish_posts' ) ) {
667
  $new_post_status = 'pending';
668
  }
669
  }
671
  }
672
  }
673
 
674
+ $new_post_author = wp_get_current_user();
675
  $new_post_author_id = $new_post_author->ID;
676
+ if ( intval( get_option( 'duplicate_post_copyauthor' ) ) === 1 ) {
677
+ // Check if the user has the right capability.
678
+ if ( is_post_type_hierarchical( $post->post_type ) ) {
679
+ if ( current_user_can( 'edit_others_pages' ) ) {
680
  $new_post_author_id = $post->post_author;
681
  }
682
  } else {
683
+ if ( current_user_can( 'edit_others_posts' ) ) {
684
  $new_post_author_id = $post->post_author;
685
  }
686
  }
687
  }
688
 
689
+ $menu_order = ( intval( get_option( 'duplicate_post_copymenuorder' ) ) === 1 ) ? $post->menu_order : 0;
690
+ $increase_menu_order_by = get_option( 'duplicate_post_increase_menu_order_by' );
691
+ if ( ! empty( $increase_menu_order_by ) && is_numeric( $increase_menu_order_by ) ) {
692
+ $menu_order += intval( $increase_menu_order_by );
693
  }
694
 
695
  $post_name = $post->post_name;
696
+ if ( intval( get_option( 'duplicate_post_copyslug' ) ) !== 1 ) {
697
  $post_name = '';
698
  }
699
+ $new_post_parent = empty( $parent_id ) ? $post->post_parent : $parent_id;
700
 
701
  $new_post = array(
702
+ 'menu_order' => $menu_order,
703
+ 'comment_status' => $post->comment_status,
704
+ 'ping_status' => $post->ping_status,
705
+ 'post_author' => $new_post_author_id,
706
+ 'post_content' => ( intval( get_option( 'duplicate_post_copycontent' ) ) === 1 ) ? $post->post_content : '',
707
+ 'post_content_filtered' => ( intval( get_option( 'duplicate_post_copycontent' ) ) === 1 ) ? $post->post_content_filtered : '',
708
+ 'post_excerpt' => ( intval( get_option( 'duplicate_post_copyexcerpt' ) ) === 1 ) ? $post->post_excerpt : '',
709
+ 'post_mime_type' => $post->post_mime_type,
710
+ 'post_parent' => $new_post_parent,
711
+ 'post_password' => ( intval( get_option( 'duplicate_post_copypassword' ) ) === 1 ) ? $post->post_password : '',
712
+ 'post_status' => $new_post_status,
713
+ 'post_title' => $title,
714
+ 'post_type' => $post->post_type,
715
+ 'post_name' => $post_name,
716
  );
717
 
718
+ if ( intval( get_option( 'duplicate_post_copydate' ) ) === 1 ) {
719
+ $new_post_date = $post->post_date;
720
+ $new_post['post_date'] = $new_post_date;
721
+ $new_post['post_date_gmt'] = get_gmt_from_date( $new_post_date );
722
  }
723
 
724
+ /**
725
+ * Filter new post values.
726
+ *
727
+ * @param array $new_post New post values.
728
+ * @param WP_Post $post Original post object.
729
+ *
730
+ * @return array
731
+ */
732
+ $new_post = apply_filters( 'duplicate_post_new_post', $new_post, $post );
733
+ $new_post_id = wp_insert_post( wp_slash( $new_post ), true );
734
 
735
  // If you have written a plugin which uses non-WP database tables to save
736
  // information about a post you can hook this action to dupe that data.
737
+ if ( 0 !== $new_post_id && ! is_wp_error( $new_post_id ) ) {
738
 
739
+ if ( 'page' === $post->post_type || is_post_type_hierarchical( $post->post_type ) ) {
 
 
740
  do_action( 'dp_duplicate_page', $new_post_id, $post, $status );
741
+ } else {
742
  do_action( 'dp_duplicate_post', $new_post_id, $post, $status );
743
+ }
744
 
745
+ delete_post_meta( $new_post_id, '_dp_original' );
746
+ add_post_meta( $new_post_id, '_dp_original', $post->ID );
 
 
 
747
  }
748
 
749
+ /**
750
+ * Fires after duplicating a post.
751
+ *
752
+ * @param int|WP_Error $new_post_id The new post id or WP_Error object on error.
753
+ * @param WP_Post $post The original post object.
754
+ * @param bool $status The intended destination status.
755
+ * @param int $parent_id The parent post ID if we are calling this recursively.
756
+ */
757
+ do_action( 'duplicate_post_post_copy', $new_post_id, $post, $status, $parent_id );
758
+
759
  return $new_post_id;
760
  }
761
 
762
+ /**
763
+ * Adds some links on the plugin page.
764
+ *
765
+ * @param array $links The links array.
766
+ * @param string $file The file name.
767
+ * @return array
768
+ */
769
+ function duplicate_post_add_plugin_links( $links, $file ) {
770
+ if ( plugin_basename( dirname( __FILE__ ) . '/duplicate-post.php' ) === $file ) {
771
+ $links[] = '<a href="https://yoast.com/wordpress/plugins/duplicate-post">' . esc_html__( 'Documentation', 'duplicate-post' ) . '</a>';
772
  }
773
  return $links;
774
  }
775
 
776
+ /**
777
+ * Renders the newsletter signup form.
778
+ *
779
+ * @return string The HTML of the newsletter signup form (escaped).
780
+ */
781
+ function duplicate_post_newsletter_signup_form() {
782
+ $copy = sprintf(
783
+ /* translators: 1: Yoast */
784
+ __(
785
+ 'If you want to stay up to date about all the exciting developments around Duplicate Post, subscribe to the %1$s newsletter!',
786
+ 'duplicate-post'
787
+ ),
788
+ 'Yoast'
789
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
790
 
791
+ $email_label = __( 'Email Address', 'duplicate-post' );
 
 
 
792
 
793
+ $html = '
794
+ <!-- Begin Mailchimp Signup Form -->
795
+ <div id="mc_embed_signup">
796
+ <form action="https://yoast.us1.list-manage.com/subscribe/post?u=ffa93edfe21752c921f860358&amp;id=972f1c9122" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate>
797
+ <div id="mc_embed_signup_scroll">
798
+ ' . $copy . '
799
+ <div class="mc-field-group" style="margin-top: 8px;">
800
+ <label for="mce-EMAIL">' . $email_label . '</label>
801
+ <input type="email" value="" name="EMAIL" class="required email" id="mce-EMAIL">
802
+ <input type="submit" value="' . esc_attr__( 'Subscribe', 'duplicate-post' ) . '" name="subscribe" id="mc-embedded-subscribe" class="button">
803
+ </div>
804
+ <div id="mce-responses" class="clear">
805
+ <div class="response" id="mce-error-response" style="display:none"></div>
806
+ <div class="response" id="mce-success-response" style="display:none"></div>
807
+ </div> <!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
808
+ <div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_ffa93edfe21752c921f860358_972f1c9122" tabindex="-1" value=""></div>
809
+ </div>
810
+ </form>
811
+ </div>
812
+ <!--End mc_embed_signup-->
813
+ ';
814
 
815
+ return $html;
 
 
 
 
 
 
 
 
816
  }
duplicate-post-common.php CHANGED
@@ -1,247 +1,100 @@
1
  <?php
2
-
3
  /**
4
- * Test if the user is allowed to copy posts
 
 
 
5
  */
6
- function duplicate_post_is_current_user_allowed_to_copy() {
7
- return current_user_can('copy_posts');
8
- }
9
 
10
- /**
11
- * Test if post type is enable to be copied
12
- */
13
- function duplicate_post_is_post_type_enabled($post_type){
14
- $duplicate_post_types_enabled = get_option('duplicate_post_types_enabled', array ('post', 'page'));
15
- if(!is_array($duplicate_post_types_enabled)) $duplicate_post_types_enabled = array($duplicate_post_types_enabled);
16
- return in_array($post_type, $duplicate_post_types_enabled);
17
- }
18
 
19
  /**
20
- * Wrapper for the option 'duplicate_post_create_user_level'
 
 
 
21
  */
22
- function duplicate_post_get_copy_user_level() {
23
- return get_option( 'duplicate_post_copy_user_level' );
 
 
 
 
24
  }
25
 
26
- // Template tag
27
  /**
28
- * Retrieve duplicate post link for post.
29
- *
30
  *
31
- * @param int $id Optional. Post ID.
32
  * @param string $context Optional, default to display. How to write the '&', defaults to '&amp;'.
33
- * @param string $draft Optional, default to true
34
  * @return string
35
  */
36
  function duplicate_post_get_clone_post_link( $id = 0, $context = 'display', $draft = true ) {
37
- if ( !duplicate_post_is_current_user_allowed_to_copy() )
38
- return;
39
-
40
- if ( !$post = get_post( $id ) )
41
- return;
42
-
43
- if(!duplicate_post_is_post_type_enabled($post->post_type))
44
- return;
45
-
46
- if ($draft)
47
- $action_name = "duplicate_post_save_as_new_post_draft";
48
- else
49
- $action_name = "duplicate_post_save_as_new_post";
50
-
51
- if ( 'display' == $context )
52
- $action = '?action='.$action_name.'&amp;post='.$post->ID;
53
- else
54
- $action = '?action='.$action_name.'&post='.$post->ID;
55
-
56
- $post_type_object = get_post_type_object( $post->post_type );
57
- if ( !$post_type_object )
58
- return;
59
-
60
- return wp_nonce_url(apply_filters( 'duplicate_post_get_clone_post_link', admin_url( "admin.php". $action ), $post->ID, $context ), 'duplicate-post_' . $post->ID);
61
- }
62
- /**
63
- * Display duplicate post link for post.
64
- *
65
- * @param string $link Optional. Anchor text.
66
- * @param string $before Optional. Display before edit link.
67
- * @param string $after Optional. Display after edit link.
68
- * @param int $id Optional. Post ID.
69
- */
70
- function duplicate_post_clone_post_link( $link = null, $before = '', $after = '', $id = 0 ) {
71
- if ( !$post = get_post( $id ) )
72
- return;
73
-
74
- if ( !$url = duplicate_post_get_clone_post_link( $post->ID ) )
75
- return;
76
-
77
- if ( null === $link )
78
- $link = esc_html__('Copy to a new draft', 'duplicate-post');
79
-
80
- $link = '<a class="post-clone-link" href="' . $url . '">' . $link . '</a>';
81
- echo $before . apply_filters( 'duplicate_post_clone_post_link', $link, $post->ID ) . $after;
82
- }
83
- /**
84
- * Get original post .
85
- *
86
- * @param int $post Optional. Post ID or Post object.
87
- * @param string $output Optional, default is Object. Either OBJECT, ARRAY_A, or ARRAY_N.
88
- * @return mixed Post data
89
- */
90
- function duplicate_post_get_original($post = null , $output = OBJECT){
91
- if ( !$post = get_post( $post ) )
92
- return null;
93
- $original_ID = get_post_meta( $post->ID, '_dp_original');
94
- if (empty($original_ID)) return null;
95
- $original_post = get_post($original_ID[0], $output);
96
- return $original_post;
97
- }
98
-
99
- function duplicate_post_get_edit_or_view_link( $post ){
100
- $post = get_post( $post );
101
- if ( ! $post )
102
- return null;
103
 
104
- $can_edit_post = current_user_can( 'edit_post', $post->ID );
105
- $title = _draft_or_post_title( $post );
106
- $post_type_object = get_post_type_object( $post->post_type );
107
 
108
- if ( $can_edit_post && 'trash' != $post->post_status ) {
109
- return sprintf(
110
- '<a href="%s" aria-label="%s">%s</a>',
111
- get_edit_post_link( $post->ID ),
112
- esc_attr( sprintf( __( 'Edit &#8220;%s&#8221;', 'default' ), $title ) ),
113
- $title
114
- );
115
- } else if ( duplicate_post_is_post_type_viewable( $post_type_object ) ) {
116
- if ( in_array( $post->post_status, array( 'pending', 'draft', 'future' ) ) ) {
117
- if ( $can_edit_post ) {
118
- $preview_link = get_preview_post_link( $post );
119
- return sprintf(
120
- '<a href="%s" rel="bookmark" aria-label="%s">%s</a>',
121
- esc_url( $preview_link ),
122
- esc_attr( sprintf( __( 'Preview &#8220;%s&#8221;', 'default' ), $title ) ),
123
- $title
124
- );
125
- }
126
- } elseif ( 'trash' != $post->post_status ) {
127
- return sprintf(
128
- '<a href="%s" rel="bookmark" aria-label="%s">%s</a>',
129
- get_permalink( $post->ID ),
130
- /* translators: %s: post title */
131
- esc_attr( sprintf( __( 'View &#8220;%s&#8221;', 'default' ), $title ) ),
132
- $title
133
- );
134
- }
135
  }
136
- return $title;
137
- }
138
 
139
- /*
140
- * Workaround for is_post_type_viewable (introduced in WP 4.4)
141
- */
142
- function duplicate_post_is_post_type_viewable( $post_type ) {
143
- if ( function_exists( 'is_post_type_viewable' ) ){
144
- return is_post_type_viewable( $post_type );
145
  } else {
146
- if ( is_scalar( $post_type ) ) {
147
- $post_type = get_post_type_object( $post_type );
148
- if ( ! $post_type ) {
149
- return false;
150
- }
151
- }
152
- return $post_type->publicly_queryable || ( $post_type->_builtin && $post_type->public );
153
  }
154
  }
155
 
156
- // Admin bar
157
- function duplicate_post_admin_bar_render() {
158
- if(!is_admin_bar_showing()) return;
159
- global $wp_admin_bar;
160
- $current_object = get_queried_object();
161
- if ( !empty($current_object) ){
162
- if ( ! empty( $current_object->post_type )
163
- && ( $post_type_object = get_post_type_object( $current_object->post_type ) )
164
- && duplicate_post_is_current_user_allowed_to_copy()
165
- && ( $post_type_object->show_ui || 'attachment' == $current_object->post_type )
166
- && (duplicate_post_is_post_type_enabled($current_object->post_type) ) )
167
- {
168
- $wp_admin_bar->add_menu( array(
169
- 'id' => 'new_draft',
170
- 'title' => esc_attr__("Copy to a new draft", 'duplicate-post'),
171
- 'href' => duplicate_post_get_clone_post_link( $current_object->ID )
172
- ) );
173
- }
174
- } else if ( is_admin() && isset( $_GET['post'] )){
175
- $id = $_GET['post'];
176
- $post = get_post($id);
177
- if( !is_null($post)
178
- && duplicate_post_is_current_user_allowed_to_copy()
179
- && duplicate_post_is_post_type_enabled($post->post_type)) {
180
- $wp_admin_bar->add_menu( array(
181
- 'id' => 'new_draft',
182
- 'title' => esc_attr__("Copy to a new draft", 'duplicate-post'),
183
- 'href' => duplicate_post_get_clone_post_link( $id )
184
- ) );
185
- }
186
  }
187
- }
188
-
189
- function duplicate_post_enqueue_css() {
190
- wp_enqueue_style ( 'duplicate-post', plugins_url('/duplicate-post.css', __FILE__), array(), DUPLICATE_POST_CURRENT_VERSION );
191
- }
192
 
193
- function duplicate_post_add_css() {
194
- if(!is_admin_bar_showing()) return;
195
- $current_object = get_queried_object();
196
- if ( !empty($current_object) ){
197
- if ( ! empty( $current_object->post_type )
198
- && ( $post_type_object = get_post_type_object( $current_object->post_type ) )
199
- && duplicate_post_is_current_user_allowed_to_copy()
200
- && ( $post_type_object->show_ui || 'attachment' == $current_object->post_type )
201
- && (duplicate_post_is_post_type_enabled($current_object->post_type) ) )
202
- {
203
- duplicate_post_enqueue_css();
204
- }
205
- } else if ( is_admin() && isset( $_GET['post'] )){
206
- $id = $_GET['post'];
207
- $post = get_post($id);
208
- if( !is_null($post)
209
- && duplicate_post_is_current_user_allowed_to_copy()
210
- && duplicate_post_is_post_type_enabled($post->post_type)) {
211
- duplicate_post_enqueue_css();
212
- }
213
  }
214
- }
215
 
216
- function duplicate_post_add_css_to_post_list() {
217
- if ( is_admin() ) {
218
- $current_screen = get_current_screen();
219
- if ( ! is_null( $current_screen ) ) {
220
- if ( 'edit' === $current_screen->base ) {
221
- $post_type = $current_screen->post_type;
222
- if ( duplicate_post_is_current_user_allowed_to_copy()
223
- && duplicate_post_is_post_type_enabled( $post_type ) ) {
224
- duplicate_post_enqueue_css();
225
- }
226
- }
227
- }
228
  }
229
- }
230
 
231
- add_action('init', 'duplicate_post_init');
232
-
233
- function duplicate_post_init(){
234
- if (get_option ( 'duplicate_post_show_adminbar' ) == 1) {
235
- add_action ( 'wp_before_admin_bar_render', 'duplicate_post_admin_bar_render' );
236
- add_action ( 'wp_enqueue_scripts', 'duplicate_post_add_css' );
237
- add_action ( 'admin_enqueue_scripts', 'duplicate_post_add_css' );
238
- }
239
- add_action ( 'admin_enqueue_scripts', 'duplicate_post_add_css_to_post_list' );
 
 
240
  }
241
 
242
  /**
243
- * Sort taxonomy objects: first public, then private
 
 
 
 
244
  */
245
- function duplicate_post_tax_obj_cmp($a, $b) {
246
- return ($a->public < $b->public);
247
  }
1
  <?php
 
2
  /**
3
+ * Common functions.
4
+ *
5
+ * @package Duplicate Post
6
+ * @since 2.0
7
  */
 
 
 
8
 
9
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
10
+ use Yoast\WP\Duplicate_Post\UI\Link_Builder;
11
+ use Yoast\WP\Duplicate_Post\Utils;
 
 
 
 
 
12
 
13
  /**
14
+ * Tests if post type is enabled to be copied.
15
+ *
16
+ * @param string $post_type The post type to check.
17
+ * @return bool
18
  */
19
+ function duplicate_post_is_post_type_enabled( $post_type ) {
20
+ $duplicate_post_types_enabled = get_option( 'duplicate_post_types_enabled', array( 'post', 'page' ) );
21
+ if ( ! is_array( $duplicate_post_types_enabled ) ) {
22
+ $duplicate_post_types_enabled = array( $duplicate_post_types_enabled );
23
+ }
24
+ return in_array( $post_type, $duplicate_post_types_enabled, true );
25
  }
26
 
 
27
  /**
28
+ * Template tag to retrieve/display duplicate post link for post.
 
29
  *
30
+ * @param int $id Optional. Post ID.
31
  * @param string $context Optional, default to display. How to write the '&', defaults to '&amp;'.
32
+ * @param bool $draft Optional, default to true.
33
  * @return string
34
  */
35
  function duplicate_post_get_clone_post_link( $id = 0, $context = 'display', $draft = true ) {
36
+ $post = get_post( $id );
37
+ if ( ! $post ) {
38
+ return '';
39
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
+ $link_builder = new Link_Builder();
42
+ $permissions_helper = new Permissions_Helper();
 
43
 
44
+ if ( ! $permissions_helper->should_links_be_displayed( $post ) ) {
45
+ return '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  }
 
 
47
 
48
+ if ( $draft ) {
49
+ return $link_builder->build_new_draft_link( $post, $context );
 
 
 
 
50
  } else {
51
+ return $link_builder->build_clone_link( $post, $context );
 
 
 
 
 
 
52
  }
53
  }
54
 
55
+ /**
56
+ * Displays duplicate post link for post.
57
+ *
58
+ * @param string|null $link Optional. Anchor text.
59
+ * @param string $before Optional. Display before edit link.
60
+ * @param string $after Optional. Display after edit link.
61
+ * @param int $id Optional. Post ID.
62
+ */
63
+ function duplicate_post_clone_post_link( $link = null, $before = '', $after = '', $id = 0 ) {
64
+ $post = get_post( $id );
65
+ if ( ! $post ) {
66
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  }
 
 
 
 
 
68
 
69
+ $url = duplicate_post_get_clone_post_link( $post->ID );
70
+ if ( ! $url ) {
71
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  }
 
73
 
74
+ if ( null === $link ) {
75
+ $link = esc_html__( 'Copy to a new draft', 'duplicate-post' );
 
 
 
 
 
 
 
 
 
 
76
  }
 
77
 
78
+ $link = '<a class="post-clone-link" href="' . esc_url( $url ) . '">' . $link . '</a>';
79
+
80
+ /**
81
+ * Filter on the clone link HTML.
82
+ *
83
+ * @param string $link The full HTML tag of the link.
84
+ * @param int $ID The ID of the post.
85
+ *
86
+ * @return string
87
+ */
88
+ echo $before . apply_filters( 'duplicate_post_clone_post_link', $link, $post->ID ) . $after; // phpcs:ignore WordPress.Security.EscapeOutput
89
  }
90
 
91
  /**
92
+ * Gets the original post.
93
+ *
94
+ * @param int|null $post Optional. Post ID or Post object.
95
+ * @param string $output Optional, default is Object. Either OBJECT, ARRAY_A, or ARRAY_N.
96
+ * @return mixed Post data.
97
  */
98
+ function duplicate_post_get_original( $post = null, $output = OBJECT ) {
99
+ return Utils::get_original( $post, $output );
100
  }
duplicate-post-options.php CHANGED
@@ -1,802 +1,27 @@
1
  <?php
2
  /**
3
- * Add an option page
4
- */
5
- if ( !defined( 'ABSPATH' ) ) {
6
- exit;
7
- }
8
-
9
- if ( is_admin() ){ // admin actions
10
- add_action( 'admin_menu', 'duplicate_post_menu' );
11
- add_action( 'admin_init', 'duplicate_post_register_settings' );
12
- }
13
-
14
- function duplicate_post_register_settings() { // whitelist options
15
- register_setting( 'duplicate_post_group', 'duplicate_post_copytitle');
16
- register_setting( 'duplicate_post_group', 'duplicate_post_copydate');
17
- register_setting( 'duplicate_post_group', 'duplicate_post_copystatus');
18
- register_setting( 'duplicate_post_group', 'duplicate_post_copyslug');
19
- register_setting( 'duplicate_post_group', 'duplicate_post_copyexcerpt');
20
- register_setting( 'duplicate_post_group', 'duplicate_post_copycontent');
21
- register_setting( 'duplicate_post_group', 'duplicate_post_copythumbnail');
22
- register_setting( 'duplicate_post_group', 'duplicate_post_copytemplate');
23
- register_setting( 'duplicate_post_group', 'duplicate_post_copyformat');
24
- register_setting( 'duplicate_post_group', 'duplicate_post_copyauthor');
25
- register_setting( 'duplicate_post_group', 'duplicate_post_copypassword');
26
- register_setting( 'duplicate_post_group', 'duplicate_post_copyattachments');
27
- register_setting( 'duplicate_post_group', 'duplicate_post_copychildren');
28
- register_setting( 'duplicate_post_group', 'duplicate_post_copycomments');
29
- register_setting( 'duplicate_post_group', 'duplicate_post_copymenuorder');
30
- register_setting( 'duplicate_post_group', 'duplicate_post_blacklist');
31
- register_setting( 'duplicate_post_group', 'duplicate_post_taxonomies_blacklist');
32
- register_setting( 'duplicate_post_group', 'duplicate_post_title_prefix');
33
- register_setting( 'duplicate_post_group', 'duplicate_post_title_suffix');
34
- register_setting( 'duplicate_post_group', 'duplicate_post_increase_menu_order_by');
35
- register_setting( 'duplicate_post_group', 'duplicate_post_roles');
36
- register_setting( 'duplicate_post_group', 'duplicate_post_types_enabled');
37
- register_setting( 'duplicate_post_group', 'duplicate_post_show_row');
38
- register_setting( 'duplicate_post_group', 'duplicate_post_show_adminbar');
39
- register_setting( 'duplicate_post_group', 'duplicate_post_show_submitbox');
40
- register_setting( 'duplicate_post_group', 'duplicate_post_show_bulkactions');
41
- register_setting( 'duplicate_post_group', 'duplicate_post_show_original_column');
42
- register_setting( 'duplicate_post_group', 'duplicate_post_show_original_in_post_states');
43
- register_setting( 'duplicate_post_group', 'duplicate_post_show_original_meta_box');
44
- register_setting( 'duplicate_post_group', 'duplicate_post_show_notice');
45
- }
46
-
47
-
48
- function duplicate_post_menu() {
49
- $page_hook = add_options_page(
50
- /* translators: %s: Yoast */
51
- sprintf( __( '%s Duplicate Post settings', 'duplicate-post' ), 'Yoast' ),
52
- __( 'Duplicate Post', 'duplicate-post' ),
53
- 'manage_options',
54
- 'duplicatepost',
55
- 'duplicate_post_options'
56
- );
57
- add_action( $page_hook, 'duplicate_post_add_options_page_css' );
58
- }
59
-
60
- /**
61
- * Enqueues a CSS file with styles for the options page.
62
  *
63
- * @ignore
 
64
  */
65
- function duplicate_post_add_options_page_css() {
66
- wp_enqueue_style( 'duplicate-post-options', plugins_url( '/duplicate-post-options.css', __FILE__ ), array(), DUPLICATE_POST_CURRENT_VERSION );
67
- }
68
-
69
- function duplicate_post_options() {
70
- global $wp_roles, $wp_version;
71
-
72
- if ( current_user_can( 'promote_users' ) && (isset($_GET['settings-updated']) && $_GET['settings-updated'] == true)){
73
- $roles = $wp_roles->get_names();
74
-
75
- $dp_roles = get_option('duplicate_post_roles');
76
- if ( $dp_roles == "" ) $dp_roles = array();
77
-
78
- foreach ($roles as $name => $display_name){
79
- $role = get_role($name);
80
-
81
- /* If the role doesn't have the capability and it was selected, add it. */
82
- if ( !$role->has_cap( 'copy_posts' ) && in_array($name, $dp_roles) )
83
- $role->add_cap( 'copy_posts' );
84
-
85
- /* If the role has the capability and it wasn't selected, remove it. */
86
- elseif ( $role->has_cap( 'copy_posts' ) && !in_array($name, $dp_roles) )
87
- $role->remove_cap( 'copy_posts' );
88
- }
89
- }
90
- ?>
91
- <div class="wrap">
92
- <h1>
93
- <?php
94
- /* translators: %s: Yoast */
95
- echo esc_html(sprintf( __( '%s Duplicate Post settings', 'duplicate-post'), 'Yoast' ) );
96
- ?>
97
- </h1>
98
-
99
- <script>
100
- var tablist;
101
- var tabs;
102
- var panels;
103
-
104
- // For easy reference
105
- var keys = {
106
- end: 35,
107
- home: 36,
108
- left: 37,
109
- up: 38,
110
- right: 39,
111
- down: 40,
112
- delete: 46
113
- };
114
-
115
- // Add or substract depending on key pressed
116
- var direction = {
117
- 37: -1,
118
- 38: -1,
119
- 39: 1,
120
- 40: 1
121
- };
122
-
123
-
124
- function generateArrays () {
125
- tabs = document.querySelectorAll('#duplicate_post_settings_form [role="tab"]');
126
- panels = document.querySelectorAll('#duplicate_post_settings_form [role="tabpanel"]');
127
- };
128
-
129
- function addListeners (index) {
130
- tabs[index].addEventListener('click', function(event){
131
- var tab = event.target;
132
- activateTab(tab, false);
133
- });
134
- tabs[index].addEventListener('keydown', function(event) {
135
- var key = event.keyCode;
136
-
137
- switch (key) {
138
- case keys.end:
139
- event.preventDefault();
140
- // Activate last tab
141
- activateTab(tabs[tabs.length - 1]);
142
- break;
143
- case keys.home:
144
- event.preventDefault();
145
- // Activate first tab
146
- activateTab(tabs[0]);
147
- break;
148
- };
149
- });
150
- tabs[index].addEventListener('keyup', function(event) {
151
- var key = event.keyCode;
152
-
153
- switch (key) {
154
- case keys.left:
155
- case keys.right:
156
- switchTabOnArrowPress(event);
157
- break;
158
- };
159
- });
160
-
161
- // Build an array with all tabs (<button>s) in it
162
- tabs[index].index = index;
163
- };
164
-
165
-
166
- // Either focus the next, previous, first, or last tab
167
- // depening on key pressed
168
- function switchTabOnArrowPress (event) {
169
- var pressed = event.keyCode;
170
 
171
- for (x = 0; x < tabs.length; x++) {
172
- tabs[x].addEventListener('focus', focusEventHandler);
173
- };
174
 
175
- if (direction[pressed]) {
176
- var target = event.target;
177
- if (target.index !== undefined) {
178
- if (tabs[target.index + direction[pressed]]) {
179
- tabs[target.index + direction[pressed]].focus();
180
- }
181
- else if (pressed === keys.left || pressed === keys.up) {
182
- focusLastTab();
183
- }
184
- else if (pressed === keys.right || pressed == keys.down) {
185
- focusFirstTab();
186
- };
187
- };
188
- };
189
- };
190
 
191
- // Activates any given tab panel
192
- function activateTab (tab, setFocus) {
193
- setFocus = setFocus || true;
194
- // Deactivate all other tabs
195
- deactivateTabs();
196
-
197
- // Remove tabindex attribute
198
- tab.removeAttribute('tabindex');
199
-
200
- // Set the tab as selected
201
- tab.setAttribute('aria-selected', 'true');
202
-
203
- tab.classList.add('nav-tab-active');
204
-
205
- // Get the value of aria-controls (which is an ID)
206
- var controls = tab.getAttribute('aria-controls');
207
-
208
- // Remove hidden attribute from tab panel to make it visible
209
- document.getElementById(controls).removeAttribute('hidden');
210
-
211
- // Set focus when required
212
- if (setFocus) {
213
- tab.focus();
214
- };
215
- };
216
-
217
- // Deactivate all tabs and tab panels
218
- function deactivateTabs () {
219
- for (t = 0; t < tabs.length; t++) {
220
- tabs[t].setAttribute('tabindex', '-1');
221
- tabs[t].setAttribute('aria-selected', 'false');
222
- tabs[t].classList.remove('nav-tab-active');
223
- tabs[t].removeEventListener('focus', focusEventHandler);
224
- };
225
-
226
- for (p = 0; p < panels.length; p++) {
227
- panels[p].setAttribute('hidden', 'hidden');
228
- };
229
- };
230
-
231
- // Make a guess
232
- function focusFirstTab () {
233
- tabs[0].focus();
234
- };
235
-
236
- // Make a guess
237
- function focusLastTab () {
238
- tabs[tabs.length - 1].focus();
239
- };
240
-
241
- //
242
- function focusEventHandler (event) {
243
- var target = event.target;
244
-
245
- checkTabFocus(target);
246
- };
247
-
248
- // Only activate tab on focus if it still has focus after the delay
249
- function checkTabFocus (target) {
250
- focused = document.activeElement;
251
-
252
- if (target === focused) {
253
- activateTab(target, false);
254
- };
255
- };
256
-
257
- document.addEventListener("DOMContentLoaded", function () {
258
- tablist = document.querySelectorAll('#duplicate_post_settings_form [role="tablist"]')[0];
259
-
260
- generateArrays();
261
-
262
- // Bind listeners
263
- for (i = 0; i < tabs.length; ++i) {
264
- addListeners(i);
265
- };
266
-
267
-
268
- });
269
-
270
- jQuery(function(){
271
- jQuery('.taxonomy_private').hide();
272
-
273
- jQuery( '.toggle-private-taxonomies' )
274
- .on( 'click', function() {
275
- buttonElement = jQuery( this );
276
- jQuery( '.taxonomy_private' ).toggle( 300, function() {
277
- buttonElement.attr( 'aria-expanded', jQuery( this ).is( ":visible" ) );
278
- } );
279
- } );
280
- });
281
-
282
- </script>
283
-
284
- <form method="post" action="options.php" style="clear: both" id="duplicate_post_settings_form">
285
- <?php settings_fields('duplicate_post_group'); ?>
286
-
287
- <header role="tablist" aria-label="<?php esc_attr_e('Settings sections', 'duplicate-post'); ?>" class="nav-tab-wrapper">
288
- <button
289
- type="button"
290
- role="tab"
291
- class="nav-tab nav-tab-active"
292
- aria-selected="true"
293
- aria-controls="what-tab"
294
- id="what"><?php esc_html_e('What to copy', 'duplicate-post'); ?>
295
- </button>
296
- <button
297
- type="button"
298
- role="tab"
299
- class="nav-tab"
300
- aria-selected="false"
301
- aria-controls="who-tab"
302
- id="who"
303
- tabindex="-1"><?php esc_html_e('Permissions', 'duplicate-post'); ?>
304
- </button>
305
- <button
306
- type="button"
307
- role="tab"
308
- class="nav-tab"
309
- aria-selected="false"
310
- aria-controls="where-tab"
311
- id="where"
312
- tabindex="-1"><?php esc_html_e('Display', 'duplicate-post'); ?>
313
- </button>
314
- </header>
315
-
316
- <section
317
- tabindex="0"
318
- role="tabpanel"
319
- id="what-tab"
320
- aria-labelledby="what">
321
- <h2 class="hide-if-js"><?php esc_html_e( 'What to copy', 'duplicate-post' ); ?></h2>
322
- <table class="form-table" role="presentation">
323
- <tr>
324
- <th scope="row"><?php esc_html_e('Post/page elements to copy', 'duplicate-post'); ?></th>
325
- <td>
326
- <fieldset>
327
- <legend class="screen-reader-text"><?php esc_html_e( 'Post/page elements to copy', 'duplicate-post' ); ?></legend>
328
- <input type="checkbox"
329
- name="duplicate_post_copytitle" value="1"
330
- id="duplicate-post-copytitle"
331
- <?php
332
- if ( 1 === intval( get_option( 'duplicate_post_copytitle' ) ) ) {
333
- echo 'checked="checked"';}
334
- ?>
335
- />
336
- <label for="duplicate-post-copytitle"><?php esc_html_e( 'Title', 'default' ); ?></label><br />
337
- <input type="checkbox"
338
- name="duplicate_post_copydate" value="1"
339
- id="duplicate-post-copydate"
340
- <?php
341
- if ( 1 === intval( get_option( 'duplicate_post_copydate' ) ) ) {
342
- echo 'checked="checked"';}
343
- ?>
344
- />
345
- <label for="duplicate-post-copydate"><?php esc_html_e( 'Date', 'default' ); ?></label><br />
346
- <input type="checkbox"
347
- name="duplicate_post_copystatus" value="1"
348
- id="duplicate-post-copystatus"
349
- <?php
350
- if ( 1 === intval( get_option( 'duplicate_post_copystatus' ) ) ) {
351
- echo 'checked="checked"';}
352
- ?>
353
- />
354
- <label for="duplicate-post-copystatus"><?php esc_html_e( 'Status', 'default' ); ?></label><br />
355
- <input type="checkbox"
356
- name="duplicate_post_copyslug" value="1"
357
- id="duplicate-post-copyslug"
358
- <?php
359
- if ( 1 === intval( get_option( 'duplicate_post_copyslug' ) ) ) {
360
- echo 'checked="checked"';}
361
- ?>
362
- />
363
- <label for="duplicate-post-copyslug"><?php esc_html_e( 'Slug', 'default' ); ?></label><br />
364
- <input type="checkbox"
365
- name="duplicate_post_copyexcerpt" value="1"
366
- id="duplicate-post-copyexcerpt"
367
- <?php
368
- if ( 1 === intval( get_option( 'duplicate_post_copyexcerpt' ) ) ) {
369
- echo 'checked="checked"';}
370
- ?>
371
- />
372
- <label for="duplicate-post-copyexcerpt"><?php esc_html_e( 'Excerpt', 'default' ); ?></label><br />
373
- <input type="checkbox"
374
- name="duplicate_post_copycontent" value="1"
375
- id="duplicate-post-copycontent"
376
- <?php
377
- if ( 1 === intval( get_option( 'duplicate_post_copycontent' ) ) ) {
378
- echo 'checked="checked"';}
379
- ?>
380
- />
381
- <label for="duplicate-post-copycontent"><?php esc_html_e( 'Content', 'default' ); ?></label><br />
382
- <input type="checkbox"
383
- name="duplicate_post_copythumbnail" value="1"
384
- id="duplicate-post-copythumbnail"
385
- <?php
386
- if ( 1 === intval( get_option( 'duplicate_post_copythumbnail' ) ) ) {
387
- echo 'checked="checked"';}
388
- ?>
389
- />
390
- <label for="duplicate-post-copythumbnail"><?php esc_html_e( 'Featured Image', 'default' ); ?></label><br />
391
- <input type="checkbox"
392
- name="duplicate_post_copytemplate" value="1"
393
- id="duplicate-post-copytemplate"
394
- <?php
395
- if ( 1 === intval( get_option( 'duplicate_post_copytemplate' ) ) ) {
396
- echo 'checked="checked"';}
397
- ?>
398
- />
399
- <label for="duplicate-post-copytemplate"><?php esc_html_e( 'Template', 'default' ); ?></label><br />
400
- <input type="checkbox"
401
- name="duplicate_post_copyformat" value="1"
402
- id="duplicate-post-copyformat"
403
- <?php
404
- if ( 1 === intval( get_option( 'duplicate_post_copyformat' ) ) ) {
405
- echo 'checked="checked"';}
406
- ?>
407
- />
408
- <label for="duplicate-post-copyformat"><?php echo esc_html_x( 'Format', 'post format', 'default' ); ?></label><br />
409
- <input type="checkbox"
410
- name="duplicate_post_copyauthor" value="1"
411
- id="duplicate-post-copyauthor"
412
- <?php
413
- if ( 1 === intval( get_option( 'duplicate_post_copyauthor' ) ) ) {
414
- echo 'checked="checked"';}
415
- ?>
416
- />
417
- <label for="duplicate-post-copyauthor"><?php esc_html_e( 'Author', 'default' ); ?></label><br />
418
- <input type="checkbox"
419
- name="duplicate_post_copypassword" value="1"
420
- id="duplicate-post-copypassword"
421
- <?php
422
- if ( 1 === intval( get_option( 'duplicate_post_copypassword' ) ) ) {
423
- echo 'checked="checked"';}
424
- ?>
425
- />
426
- <label for="duplicate-post-copypassword"><?php esc_html_e( 'Password', 'default' ); ?></label><br />
427
- <input type="checkbox"
428
- name="duplicate_post_copyattachments" value="1"
429
- id="duplicate-post-copyattachments"
430
- aria-describedby="duplicate-post-copyattachments-description"
431
- <?php
432
- if ( 1 === intval( get_option( 'duplicate_post_copyattachments' ) ) ) {
433
- echo 'checked="checked"';}
434
- ?>
435
- />
436
- <label for="duplicate-post-copyattachments"><?php esc_html_e( 'Attachments', 'duplicate-post' ); ?></label>
437
- <span id="duplicate-post-copyattachments-description">(<?php esc_html_e( 'you probably want this unchecked, unless you have very special requirements', 'duplicate-post' ); ?>)</span><br />
438
- <input type="checkbox"
439
- name="duplicate_post_copychildren" value="1"
440
- id="duplicate-post-copychildren"
441
- <?php
442
- if ( 1 === intval( get_option( 'duplicate_post_copychildren' ) ) ) {
443
- echo 'checked="checked"';}
444
- ?>
445
- />
446
- <label for="duplicate-post-copychildren"><?php esc_html_e( 'Children', 'duplicate-post' ); ?></label><br />
447
- <input type="checkbox"
448
- name="duplicate_post_copycomments" value="1"
449
- id="duplicate-post-copycomments"
450
- aria-describedby="duplicate-post-copycomments-description"
451
- <?php
452
- if ( 1 === intval( get_option( 'duplicate_post_copycomments' ) ) ) {
453
- echo 'checked="checked"';}
454
- ?>
455
- />
456
- <label for="duplicate-post-copycomments"><?php esc_html_e( 'Comments', 'default' ); ?></label>
457
- <span id="duplicate-post-copycomments-description">(<?php esc_html_e( 'except pingbacks and trackbacks', 'duplicate-post' ); ?>)</span><br />
458
- <input type="checkbox"
459
- name="duplicate_post_copymenuorder" value="1"
460
- id="duplicate-post-copymenuorder"
461
- <?php
462
- if ( 1 === intval( get_option( 'duplicate_post_copymenuorder' ) ) ) {
463
- echo 'checked="checked"';}
464
- ?>
465
- />
466
- <label for="duplicate-post-copymenuorder"><?php esc_html_e( 'Menu order', 'default' ); ?></label>
467
- </fieldset>
468
- </td>
469
- </tr>
470
- <tr>
471
- <th scope="row">
472
- <label for="duplicate_post_title_prefix">
473
- <?php esc_html_e("Title prefix", 'duplicate-post'); ?>
474
- </label>
475
- </th>
476
- <td><input type="text" name="duplicate_post_title_prefix"
477
- id="duplicate_post_title_prefix"
478
- aria-describedby="duplicate-post-title-prefix-description"
479
- value="<?php form_option('duplicate_post_title_prefix'); ?>" />
480
- <p id="duplicate-post-title-prefix-description">
481
- <?php esc_html_e("Prefix to be added before the title, e.g. \"Copy of\" (blank for no prefix)", 'duplicate-post'); ?>
482
- </p>
483
- </td>
484
- </tr>
485
- <tr>
486
- <th scope="row">
487
- <label for="duplicate_post_title_suffix">
488
- <?php esc_html_e("Title suffix", 'duplicate-post'); ?>
489
- </label>
490
- </th>
491
- <td><input type="text" name="duplicate_post_title_suffix"
492
- id="duplicate_post_title_suffix"
493
- aria-describedby="duplicate-post-title-suffix-description"
494
- value="<?php form_option('duplicate_post_title_suffix'); ?>" />
495
- <p id="duplicate-post-title-suffix-description">
496
- <?php esc_html_e( 'Suffix to be added after the title, e.g. "(dup)" (blank for no suffix)', 'duplicate-post' ); ?>
497
- </p>
498
- </td>
499
- </tr>
500
- <tr>
501
- <th scope="row">
502
- <label for="duplicate_post_increase_menu_order_by">
503
- <?php esc_html_e("Increase menu order by", 'duplicate-post'); ?>
504
- </label>
505
- </th>
506
- <td><input type="number" min="0" step="1" name="duplicate_post_increase_menu_order_by"
507
- id="duplicate_post_increase_menu_order_by"
508
- aria-describedby="duplicate-post-increase-menu-order-by-description"
509
- value="<?php form_option('duplicate_post_increase_menu_order_by'); ?>" />
510
- <p id="duplicate-post-increase-menu-order-by-description">
511
- <?php esc_html_e( 'Add this number to the original menu order (blank or zero to retain the value)', 'duplicate-post' ); ?>
512
- </p>
513
- </td>
514
- </tr>
515
- <tr>
516
- <th scope="row">
517
- <label for="duplicate_post_blacklist">
518
- <?php esc_html_e("Do not copy these fields", 'duplicate-post'); ?>
519
- </label>
520
- </th>
521
- <td id="textfield"><input type="text"
522
- name="duplicate_post_blacklist"
523
- id="duplicate_post_blacklist"
524
- aria-describedby="duplicate-post-blacklist-description"
525
- value="<?php form_option('duplicate_post_blacklist'); ?>" />
526
- <p id="duplicate-post-blacklist-description">
527
- <?php esc_html_e( 'Comma-separated list of meta fields that must not be copied.', 'duplicate-post' ); ?>
528
- <?php esc_html_e( 'You can use * to match zero or more alphanumeric characters or underscores: e.g. field*', 'duplicate-post' ); ?>
529
- </p>
530
- </td>
531
- </tr>
532
- <tr>
533
- <th scope="row">
534
- <?php esc_html_e( 'Do not copy these taxonomies', 'duplicate-post' ); ?>
535
- </th>
536
- <td>
537
- <fieldset>
538
- <legend class="screen-reader-text"><?php esc_html_e( 'Do not copy these taxonomies', 'duplicate-post' ); ?></legend>
539
- <?php
540
- $taxonomies = get_taxonomies( array(), 'objects' );
541
- usort( $taxonomies, 'duplicate_post_tax_obj_cmp' );
542
- $taxonomies_blacklist = get_option( 'duplicate_post_taxonomies_blacklist' );
543
- if ( ! is_array( $taxonomies_blacklist ) ) {
544
- $taxonomies_blacklist = array();
545
- }
546
- foreach ( $taxonomies as $taxonomy ) :
547
- if ( 'post_format' === $taxonomy->name ) {
548
- continue;
549
- }
550
- ?>
551
- <div class="taxonomy_<?php echo ( $taxonomy->public ) ? 'public' : 'private'; ?>">
552
- <input type="checkbox"
553
- name="duplicate_post_taxonomies_blacklist[]"
554
- id="duplicate-post-<?php echo esc_attr( $taxonomy->name ); ?>"
555
- value="<?php echo esc_attr( $taxonomy->name ); ?>"
556
- <?php
557
- if ( in_array( $taxonomy->name, $taxonomies_blacklist, true ) ) {
558
- echo 'checked="checked"';
559
- }
560
- ?>
561
- />
562
- <label for="duplicate-post-<?php echo esc_attr( $taxonomy->name ); ?>">
563
- <?php echo esc_html( $taxonomy->labels->name . ' [' . $taxonomy->name . ']' ); ?>
564
- </label><br />
565
- </div>
566
- <?php endforeach; ?>
567
- <button type="button" class="button-link hide-if-no-js toggle-private-taxonomies" aria-expanded="false">
568
- <?php esc_html_e( 'Show/hide private taxonomies', 'duplicate-post' ); ?>
569
- </button>
570
- </fieldset>
571
- </td>
572
- </tr>
573
- </table>
574
- </section>
575
- <section
576
- tabindex="0"
577
- role="tabpanel"
578
- id="who-tab"
579
- aria-labelledby="who"
580
- hidden="hidden">
581
- <h2 class="hide-if-js"><?php esc_html_e( 'Permissions', 'duplicate-post' ); ?></h2>
582
- <table class="form-table" role="presentation">
583
- <?php if ( current_user_can( 'promote_users' ) ){ ?>
584
- <tr>
585
- <th scope="row"><?php esc_html_e("Roles allowed to copy", 'duplicate-post'); ?></th>
586
- <td>
587
- <fieldset>
588
- <legend class="screen-reader-text"><?php esc_html_e( 'Roles allowed to copy', 'duplicate-post' ); ?></legend>
589
- <?php
590
 
591
- $roles = $wp_roles->get_names();
592
- $post_types = get_post_types( array( 'show_ui' => true ), 'objects' );
593
- $edit_capabilities = array( 'edit_posts' => true );
594
- foreach ( $post_types as $post_type ) {
595
- $edit_capabilities[ $post_type->cap->edit_posts ] = true;
596
- }
597
- foreach ( $roles as $name => $display_name ) :
598
- $role = get_role( $name );
599
- if ( count( array_intersect_key( $role->capabilities, $edit_capabilities ) ) > 0 ) :
600
- ?>
601
- <input type="checkbox"
602
- name="duplicate_post_roles[]"
603
- id="duplicate-post-<?php echo esc_attr( $name ); ?>"
604
- value="<?php echo esc_attr( $name ); ?>"
605
- <?php
606
- if ( $role->has_cap( 'copy_posts' ) ) {
607
- echo 'checked="checked"';}
608
- ?>
609
- />
610
- <label for="duplicate-post-<?php echo esc_attr( $name ); ?>"><?php echo esc_html( translate_user_role( $display_name ) ); ?></label><br />
611
- <?php
612
- endif;
613
- endforeach;
614
- ?>
615
- <p>
616
- <?php esc_html_e( 'Warning: users will be able to copy all posts, even those of other users.', 'duplicate-post' ); ?><br />
617
- <?php esc_html_e( 'Passwords and contents of password-protected posts may become visible to undesired users and visitors.', 'duplicate-post' ); ?>
618
- </p>
619
- </fieldset>
620
- </td>
621
- </tr>
622
- <?php } ?>
623
- <tr>
624
- <th scope="row"><?php esc_html_e("Enable for these post types", 'duplicate-post'); ?></th>
625
- <td>
626
- <fieldset>
627
- <legend class="screen-reader-text"><?php esc_html_e( 'Enable for these post types', 'duplicate-post' ); ?></legend>
628
- <?php
629
- $post_types = get_post_types( array( 'show_ui' => true ), 'objects' );
630
- foreach ( $post_types as $post_type_object ) :
631
- if ( 'attachment' === $post_type_object->name ) {
632
- continue;
633
- }
634
- ?>
635
- <input type="checkbox"
636
- name="duplicate_post_types_enabled[]"
637
- id="duplicate-post-<?php echo esc_attr( $post_type_object->name ); ?>"
638
- value="<?php echo esc_attr( $post_type_object->name ); ?>"
639
- <?php
640
- if ( duplicate_post_is_post_type_enabled( $post_type_object->name ) ) {
641
- echo 'checked="checked"';}
642
- ?>
643
- />
644
- <label for="duplicate-post-<?php echo esc_attr( $post_type_object->name ); ?>"><?php echo esc_html( $post_type_object->labels->name ); ?></label><br />
645
- <?php endforeach; ?>
646
- <p>
647
- <?php esc_html_e( 'Select the post types you want the plugin to be enabled for.', 'duplicate-post' ); ?><br />
648
- <?php esc_html_e( 'Whether the links are displayed for custom post types registered by themes or plugins depends on their use of standard WordPress UI elements.', 'duplicate-post' ); ?>
649
- </p>
650
- </fieldset>
651
- </td>
652
- </tr>
653
- </table>
654
- </section>
655
- <section
656
- tabindex="0"
657
- role="tabpanel"
658
- id="where-tab"
659
- aria-labelledby="where"
660
- hidden="hidden">
661
- <h2 class="hide-if-js"><?php esc_html_e( 'Display', 'duplicate-post' ); ?></h2>
662
- <table class="form-table" role="presentation">
663
- <tr>
664
- <th scope="row"><?php esc_html_e( 'Show links in', 'duplicate-post' ); ?></th>
665
- <td>
666
- <fieldset>
667
- <legend class="screen-reader-text"><?php esc_html_e( 'Show links in', 'duplicate-post' ); ?></legend>
668
- <input
669
- type="checkbox"
670
- name="duplicate_post_show_row"
671
- id="duplicate-post-show-row"
672
- value="1"
673
- <?php
674
- if ( 1 === intval( get_option( 'duplicate_post_show_row' ) ) ) {
675
- echo 'checked="checked"';}
676
- ?>
677
- />
678
- <label for="duplicate-post-show-row"><?php esc_html_e( 'Post list', 'duplicate-post' ); ?></label><br />
679
- <input
680
- type="checkbox"
681
- name="duplicate_post_show_submitbox"
682
- id="duplicate-post-show-submitbox"
683
- value="1"
684
- <?php
685
- if ( 1 === intval( get_option( 'duplicate_post_show_submitbox' ) ) ) {
686
- echo 'checked="checked"';}
687
- ?>
688
- />
689
- <label for="duplicate-post-show-submitbox"><?php esc_html_e( 'Edit screen', 'duplicate-post' ); ?></label><br />
690
- <input
691
- type="checkbox"
692
- name="duplicate_post_show_adminbar"
693
- id="duplicate-post-show-adminbar"
694
- aria-describedby="duplicate-post-show-adminbar-description"
695
- value="1"
696
- <?php
697
- if ( 1 === intval( get_option( 'duplicate_post_show_adminbar' ) ) ) {
698
- echo 'checked="checked"';}
699
- ?>
700
- />
701
- <label for="duplicate-post-show-adminbar"><?php esc_html_e( 'Admin bar', 'duplicate-post' ); ?></label>
702
- <span id="duplicate-post-show-adminbar-description">(<?php esc_html_e( 'now works on Edit screen too - check this option to use with Gutenberg enabled', 'duplicate-post' ); ?>)</span><br />
703
- <?php
704
- if ( version_compare( $wp_version, '4.7' ) >= 0 ) {
705
- ?>
706
- <input
707
- type="checkbox"
708
- name="duplicate_post_show_bulkactions"
709
- id="duplicate-post-show-bulkactions"
710
- value="1"
711
- <?php
712
- if ( 1 === intval( get_option( 'duplicate_post_show_bulkactions' ) ) ) {
713
- echo 'checked="checked"';}
714
- ?>
715
- />
716
- <label for="duplicate-post-show-bulkactions"><?php esc_html_e( 'Bulk Actions', 'default' ); ?></label>
717
- <?php } ?>
718
- </fieldset>
719
- <p>
720
- <?php esc_html_e( 'Whether the links are displayed for custom post types registered by themes or plugins depends on their use of standard WordPress UI elements.', 'duplicate-post' ); ?>
721
- <br />
722
- <?php
723
- printf(
724
- /* translators: 1: Code start tag, 2: Code closing tag, 3: Link start tag to the template tag documentation, 4: Link closing tag. */
725
- esc_html__( 'You can also use the template tag %1$sduplicate_post_clone_post_link( $link, $before, $after, $id )%2$s. %3$sMore info on the template tag%4$s.', 'duplicate-post' ),
726
- '<code>',
727
- '</code>',
728
- '<a href="' . esc_url( 'https://developer.yoast.com/duplicate-post/functions-template-tags#duplicate_post_clone_post_link' ) . '">',
729
- '</a>'
730
- );
731
- ?>
732
- </p>
733
- </td>
734
- </tr>
735
- <tr>
736
- <th scope="row"><?php esc_html_e("Show original item:", 'duplicate-post'); ?></th>
737
- <td>
738
- <input
739
- type="checkbox"
740
- name="duplicate_post_show_original_meta_box"
741
- id="duplicate-post-show-original-meta-box"
742
- aria-describedby="duplicate-post-show-original-meta-box-description"
743
- value="1"
744
- <?php
745
- if( 1 === intval( get_option( 'duplicate_post_show_original_meta_box' ) ) ) {
746
- echo 'checked="checked"';
747
- } ?>/>
748
- <label for="duplicate-post-show-original-meta-box"><?php esc_html_e("In a metabox in the Edit screen [Classic editor]", 'duplicate-post'); ?></label>
749
- <p id="duplicate-post-show-original-meta-box-description">(<?php esc_html_e("you'll also be able to delete the reference to the original item with a checkbox", 'duplicate-post'); ?>)</p><br/>
750
- <input
751
- type="checkbox"
752
- name="duplicate_post_show_original_column"
753
- id="duplicate-post-show-original-column"
754
- aria-describedby="duplicate-post-show-original-column-description"
755
- value="1"
756
- <?php
757
- if( 1 === intval( get_option( 'duplicate_post_show_original_column' ) ) ) {
758
- echo 'checked="checked"';
759
- } ?>/>
760
- <label for="duplicate-post-show-original-column"><?php esc_html_e("In a column in the Post list", 'duplicate-post'); ?></label>
761
- <p id="duplicate-post-show-original-column-description">(<?php esc_html_e("you'll also be able to delete the reference to the original item with a checkbox in Quick Edit", 'duplicate-post'); ?>)</p><br/>
762
- <input
763
- type="checkbox"
764
- name="duplicate_post_show_original_in_post_states"
765
- id="duplicate-post-show-original-in-post-states"
766
- value="1"
767
- <?php
768
- if( 1 === intval( get_option( 'duplicate_post_show_original_in_post_states' ) ) ) {
769
- echo 'checked="checked"';
770
- } ?>/>
771
- <label for="duplicate-post-show-original-in-post-states"><?php esc_html_e("After the title in the Post list", 'duplicate-post'); ?></label>
772
- </td>
773
- </tr>
774
- <tr>
775
- <th scope="row"><?php esc_html_e( 'Update notice', 'duplicate-post' ); ?></th>
776
- <td>
777
- <input
778
- type="checkbox"
779
- name="duplicate_post_show_notice"
780
- id="duplicate-post-show-notice"
781
- value="1"
782
- <?php
783
- if ( 1 === intval( get_option( 'duplicate_post_show_notice' ) ) ) {
784
- echo 'checked="checked"';
785
- }
786
- ?>
787
- />
788
- <label for="duplicate-post-show-notice"><?php esc_html_e( 'Show update notice', 'duplicate-post' ); ?></label>
789
- </td>
790
- </tr>
791
- </table>
792
- </section>
793
- <p class="submit">
794
- <input type="submit" class="button button-primary"
795
- value="<?php esc_html_e('Save changes', 'duplicate-post') ?>" />
796
- </p>
797
 
798
- </form>
799
- </div>
800
- <?php
801
- }
802
- ?>
1
  <?php
2
  /**
3
+ * Options page
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  *
5
+ * @package Duplicate Post
6
+ * @since 2.0
7
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
+ namespace Yoast\WP\Duplicate_Post;
 
 
10
 
11
+ use Yoast\WP\Duplicate_Post\Admin\Options;
12
+ use Yoast\WP\Duplicate_Post\Admin\Options_Form_Generator;
13
+ use Yoast\WP\Duplicate_Post\Admin\Options_Inputs;
14
+ use Yoast\WP\Duplicate_Post\Admin\Options_Page;
15
+ use Yoast\WP\Duplicate_Post\UI\Asset_Manager;
 
 
 
 
 
 
 
 
 
 
16
 
17
+ if ( ! defined( 'ABSPATH' ) ) {
18
+ exit();
19
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
+ $duplicate_post_options_page = new Options_Page(
22
+ new Options(),
23
+ new Options_Form_Generator( new Options_Inputs() ),
24
+ new Asset_Manager()
25
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ $duplicate_post_options_page->register_hooks();
 
 
 
 
duplicate-post.css DELETED
@@ -1,46 +0,0 @@
1
- #wpadminbar #wp-admin-bar-new_draft > .ab-item::before {
2
- content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'><path d='M18.9 4.3c0.6 0 1.1 0.5 1.1 1.1v13.6c0 0.6-0.5 1.1-1.1 1.1h-10.7c-0.6 0-1.1-0.5-1.1-1.1v-3.2h-6.1c-0.6 0-1.1-0.5-1.1-1.1v-7.5c0-0.6 0.3-1.4 0.8-1.8l4.6-4.6c0.4-0.4 1.2-0.8 1.8-0.8h4.6c0.6 0 1.1 0.5 1.1 1.1v3.7c0.4-0.3 1-0.4 1.4-0.4h4.6zM12.9 6.7l-3.3 3.3h3.3v-3.3zM5.7 2.4l-3.3 3.3h3.3v-3.3zM7.9 9.6l3.5-3.5v-4.6h-4.3v4.6c0 0.6-0.5 1.1-1.1 1.1h-4.6v7.1h5.7v-2.9c0-0.6 0.3-1.4 0.8-1.8zM18.6 18.6v-12.9h-4.3v4.6c0 0.6-0.5 1.1-1.1 1.1h-4.6v7.1h10z' fill='rgba(240,245,250,.6)'/></svg>");
3
- top: 2px;
4
- }
5
-
6
- @media screen and (max-width: 782px){
7
- #wpadminbar li#wp-admin-bar-new_draft{
8
- display: block;
9
- }
10
-
11
- #wpadminbar #wp-admin-bar-new_draft > .ab-item {
12
- text-indent: 100%;
13
- white-space: nowrap;
14
- overflow: hidden;
15
- width: 52px;
16
- padding: 0;
17
- color: #999;
18
- position: relative;
19
- }
20
-
21
- #wpadminbar #wp-admin-bar-new_draft > .ab-item::before {
22
- display: block;
23
- text-indent: 0;
24
- font: 400 32px/1 dashicons;
25
- speak: none;
26
- top: 0px;
27
- width: 52px;
28
- text-align: center;
29
- -webkit-font-smoothing: antialiased;
30
- -moz-osx-font-smoothing: grayscale;
31
- }
32
- }
33
-
34
- fieldset#duplicate_post_quick_edit_fieldset{
35
- clear: both;
36
- }
37
-
38
- fieldset#duplicate_post_quick_edit_fieldset label{
39
- display: inline;
40
- margin: 0;
41
- vertical-align: unset;
42
- }
43
-
44
- fieldset#duplicate_post_quick_edit_fieldset a{
45
- text-decoration: underline;
46
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
duplicate-post.php CHANGED
@@ -3,45 +3,75 @@
3
  * Plugin Name: Yoast Duplicate Post
4
  * Plugin URI: https://yoast.com/wordpress/plugins/duplicate-post/
5
  * Description: Clone posts and pages.
6
- * Version: 3.2.6
7
  * Author: Enrico Battocchi & Team Yoast
8
  * Author URI: https://yoast.com
9
  * Text Domain: duplicate-post
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  */
11
 
12
- /* Copyright 2020 Yoast BV (email : info@yoast.com)
13
 
14
- This program is free software; you can redistribute it and/or modify
15
- it under the terms of the GNU General Public License as published by
16
- the Free Software Foundation; either version 2 of the License, or
17
- (at your option) any later version.
18
 
19
- This program is distributed in the hope that it will be useful,
20
- but WITHOUT ANY WARRANTY; without even the implied warranty of
21
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
- GNU General Public License for more details.
23
 
24
- You should have received a copy of the GNU General Public License
25
- along with this program; if not, write to the Free Software
26
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
27
- */
28
 
29
- if ( ! defined( 'ABSPATH' ) ) {
30
- exit;
 
 
 
 
 
 
 
31
  }
32
 
33
- // Version of the plugin
34
- define('DUPLICATE_POST_CURRENT_VERSION', '3.2.6' );
 
 
 
 
 
 
 
35
 
36
  /**
37
- * Initialise the internationalisation domain
38
  */
39
  function duplicate_post_load_plugin_textdomain() {
40
- load_plugin_textdomain( 'duplicate-post', FALSE, basename( dirname( __FILE__ ) ) . '/languages/' );
41
  }
42
  add_action( 'plugins_loaded', 'duplicate_post_load_plugin_textdomain' );
43
 
44
- add_filter( "plugin_action_links_" . plugin_basename(__FILE__), "duplicate_post_plugin_actions", 10);
45
 
46
  /**
47
  * Adds 'Settings' link to plugin entry in the Plugins list.
@@ -66,8 +96,8 @@ function duplicate_post_plugin_actions( $actions ) {
66
  return $actions;
67
  }
68
 
69
- require_once (dirname(__FILE__).'/duplicate-post-common.php');
70
 
71
- if (is_admin()){
72
- require_once (dirname(__FILE__).'/duplicate-post-admin.php');
73
  }
3
  * Plugin Name: Yoast Duplicate Post
4
  * Plugin URI: https://yoast.com/wordpress/plugins/duplicate-post/
5
  * Description: Clone posts and pages.
6
+ * Version: 4.0
7
  * Author: Enrico Battocchi & Team Yoast
8
  * Author URI: https://yoast.com
9
  * Text Domain: duplicate-post
10
+ *
11
+ * @package Duplicate Post
12
+ * @since 0.1
13
+ *
14
+ * Copyright 2020 Yoast BV (email : info@yoast.com)
15
+ *
16
+ * This program is free software; you can redistribute it and/or modify
17
+ * it under the terms of the GNU General Public License as published by
18
+ * the Free Software Foundation; either version 2 of the License, or
19
+ * (at your option) any later version.
20
+ *
21
+ * This program is distributed in the hope that it will be useful,
22
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
+ * GNU General Public License for more details.
25
+ *
26
+ * You should have received a copy of the GNU General Public License
27
+ * along with this program; if not, write to the Free Software
28
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
29
  */
30
 
31
+ use Yoast\WP\Duplicate_Post\Duplicate_Post;
32
 
33
+ if ( ! defined( 'ABSPATH' ) ) {
34
+ exit();
35
+ }
 
36
 
37
+ if ( ! defined( 'DUPLICATE_POST_FILE' ) ) {
38
+ define( 'DUPLICATE_POST_FILE', __FILE__ );
39
+ }
 
40
 
41
+ if ( ! defined( 'DUPLICATE_POST_PATH' ) ) {
42
+ define( 'DUPLICATE_POST_PATH', plugin_dir_path( __FILE__ ) );
43
+ }
 
44
 
45
+ define( 'DUPLICATE_POST_CURRENT_VERSION', '4.0' );
46
+
47
+ $duplicate_post_autoload_file = __DIR__ . '/vendor/autoload.php';
48
+
49
+ if ( is_readable( $duplicate_post_autoload_file ) ) {
50
+ require $duplicate_post_autoload_file;
51
+
52
+ // Initialize the main autoloaded class.
53
+ add_action( 'plugins_loaded', '__duplicate_post_main' );
54
  }
55
 
56
+ /**
57
+ * Loads the Duplicate Post main class.
58
+ *
59
+ * @phpcs:disable PHPCompatibility.FunctionNameRestrictions.ReservedFunctionNames.FunctionDoubleUnderscore,WordPress.NamingConventions.ValidFunctionName.FunctionDoubleUnderscore,WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Function name change would be BC-break.
60
+ */
61
+ function __duplicate_post_main() {
62
+ new Duplicate_Post();
63
+ }
64
+ // phpcs:enable
65
 
66
  /**
67
+ * Initialises the internationalisation domain.
68
  */
69
  function duplicate_post_load_plugin_textdomain() {
70
+ load_plugin_textdomain( 'duplicate-post', false, basename( dirname( __FILE__ ) ) . '/languages/' );
71
  }
72
  add_action( 'plugins_loaded', 'duplicate_post_load_plugin_textdomain' );
73
 
74
+ add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'duplicate_post_plugin_actions', 10 );
75
 
76
  /**
77
  * Adds 'Settings' link to plugin entry in the Plugins list.
96
  return $actions;
97
  }
98
 
99
+ require_once dirname( __FILE__ ) . '/duplicate-post-common.php';
100
 
101
+ if ( is_admin() ) {
102
+ include_once dirname( __FILE__ ) . '/duplicate-post-admin.php';
103
  }
duplicate_post_admin_script.js DELETED
@@ -1,35 +0,0 @@
1
- (function(jQuery) {
2
- // we create a copy of the WP inline edit post function
3
- var $wp_inline_edit = inlineEditPost.edit;
4
- // and then we overwrite the function with our own code
5
- inlineEditPost.edit = function( id ) {
6
- // "call" the original WP edit function
7
- // we don't want to leave WordPress hanging
8
- $wp_inline_edit.apply( this, arguments );
9
- // now we take care of our business
10
- // get the post ID
11
- var $post_id = 0;
12
- if ( typeof( id ) == 'object' ) {
13
- $post_id = parseInt( this.getId( id ) );
14
- }
15
- if ( $post_id > 0 ) {
16
- // define the edit row.
17
- var $edit_row = jQuery( '#edit-' + $post_id );
18
- var $post_row = jQuery( '#post-' + $post_id );
19
-
20
- // get the data.
21
- var has_original = ( jQuery( '.duplicate_post_original_item span[data-no_original]', $post_row ).length === 0 );
22
- var original = jQuery( '.duplicate_post_original_item', $post_row ).html();
23
-
24
- // populate the data.
25
- if ( has_original ) {
26
- jQuery( '.duplicate_post_original_item_title_span', $edit_row ).html( original );
27
- jQuery( '#duplicate_post_quick_edit_fieldset', $edit_row ).show();
28
- } else {
29
- jQuery( '#duplicate_post_quick_edit_fieldset', $edit_row ).hide();
30
- jQuery( '.duplicate_post_original_item_title_span', $edit_row ).html( '' );
31
- }
32
- }
33
- };
34
-
35
- })(jQuery);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/dist/duplicate-post-edit-400.js ADDED
@@ -0,0 +1 @@
 
1
+ !function(e){var t={};function r(i){if(t[i])return t[i].exports;var n=t[i]={i:i,l:!1,exports:{}};return e[i].call(n.exports,n,n.exports,r),n.l=!0,n.exports}r.m=e,r.c=t,r.d=function(e,t,i){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(r.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)r.d(i,n,function(t){return e[t]}.bind(null,n));return i},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=5)}([function(e,t){e.exports=window.wp.data},function(e,t){e.exports=window.wp.element},function(e,t){e.exports=window.wp.components},function(e,t){e.exports=window.wp.i18n},function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.redirectOnSaveCompletion=void 0;var i=r(0),n=function(e){var t=(0,i.select)("core/editor").getCurrentPostAttribute("status"),r=(0,i.select)("core/editor").getEditedPostAttribute("status");"dp-rewrite-republish"===t&&"publish"===r&&(0,i.dispatch)("core/editor").editPost({status:t}),window.location.assign(e)};t.redirectOnSaveCompletion=function(e,t){var r=(0,i.select)("core/editor").isSavingPost(),o=(0,i.select)("core/editor").isAutosavingPost(),s=(0,i.select)("core/edit-post").hasMetaBoxes(),a=(0,i.select)("core/edit-post").isSavingMetaBoxes();return s&&!a&&t.wasSavingMetaboxes&&n(e),s||r||!t.wasSavingPost||t.wasAutoSavingPost||n(e),{isSavingPost:r,isSavingMetaBoxes:a,isAutosavingPost:o}}},function(e,t,r){"use strict";var i=function(e,t){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return function(e,t){var r=[],i=!0,n=!1,o=void 0;try{for(var s,a=e[Symbol.iterator]();!(i=(s=a.next()).done)&&(r.push(s.value),!t||r.length!==t);i=!0);}catch(e){n=!0,o=e}finally{try{!i&&a.return&&a.return()}finally{if(n)throw o}}return r}(e,t);throw new TypeError("Invalid attempt to destructure non-iterable instance")},n=function(){function e(e,t){for(var r=0;r<t.length;r++){var i=t[r];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}return function(t,r,i){return r&&e(t.prototype,r),i&&e(t,i),t}}(),o=r(6),s=r(7),a=r(1),u=r(2),l=r(3),c=r(0),d=r(4);var p=new(function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.renderNotices(),this.removeSlugSidebarPanel()}return n(e,[{key:"handleRedirect",value:function(){var e=this;if(parseInt(duplicatePost.rewriting,10)){var t=!1,r=!1,i=!1;(0,c.subscribe)((function(){if(e.isSafeRedirectURL(duplicatePost.originalEditURL)&&e.isCopyAllowedToBeRepublished()){var n=(0,d.redirectOnSaveCompletion)(duplicatePost.originalEditURL,{wasSavingPost:t,wasSavingMetaboxes:r,wasAutoSavingPost:i});t=n.isSavingPost,r=n.isSavingMetaBoxes,i=n.isAutosavingPost}}))}}},{key:"isSafeRedirectURL",value:function(e){var t=document.createElement("a");return t.href=e,!!(/^https?:$/.test(t.protocol)&&/\/wp-admin\/post\.php$/.test(t.pathname)&&/\?action=edit&post=[0-9]+&dprepublished=1&dpcopy=[0-9]+&dpnonce=[a-z0-9]+/i.test(t.search))}},{key:"isCopyAllowedToBeRepublished",value:function(){var e=(0,c.select)("core/editor").getCurrentPostAttribute("status");return"dp-rewrite-republish"===e||"private"===e}},{key:"renderNotices",value:function(){if(duplicatePostNotices&&duplicatePostNotices instanceof Object){var e=!0,t=!1,r=void 0;try{for(var n,o=Object.entries(duplicatePostNotices)[Symbol.iterator]();!(e=(n=o.next()).done);e=!0){var s=n.value,a=i(s,2),u=(a[0],a[1]),l=JSON.parse(u);l.status&&l.text&&(0,c.dispatch)("core/notices").createNotice(l.status,l.text,{isDismissible:l.isDismissible||!0})}}catch(e){t=!0,r=e}finally{try{!e&&o.return&&o.return()}finally{if(t)throw r}}}}},{key:"removeSlugSidebarPanel",value:function(){parseInt(duplicatePost.rewriting,10)&&(0,c.dispatch)("core/edit-post").removeEditorPanel("post-link")}},{key:"render",value:function(){var e=(0,c.select)("core/editor").getEditedPostAttribute("status");return"1"===duplicatePost.showLinksIn.submitbox&&wp.element.createElement(a.Fragment,null,""!==duplicatePost.newDraftLink&&"1"===duplicatePost.showLinks.new_draft&&wp.element.createElement(s.PluginPostStatusInfo,null,wp.element.createElement(u.Button,{isTertiary:!0,className:"dp-editor-post-copy-to-draft",href:duplicatePost.newDraftLink},(0,l.__)("Copy to a new draft","duplicate-post"))),"publish"===e&&""!==duplicatePost.rewriteAndRepublishLink&&"1"===duplicatePost.showLinks.rewrite_republish&&wp.element.createElement(s.PluginPostStatusInfo,null,wp.element.createElement(u.Button,{isTertiary:!0,className:"dp-editor-post-rewrite-republish",href:duplicatePost.rewriteAndRepublishLink},(0,l.__)("Rewrite & Republish","duplicate-post"))))}}]),e}());p.handleRedirect(),(0,o.registerPlugin)("duplicate-post",{render:p.render})},function(e,t){e.exports=window.wp.plugins},function(e,t){e.exports=window.wp.editPost}]);
js/dist/duplicate-post-options-400.js ADDED
@@ -0,0 +1 @@
 
1
+ !function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=10)}({10:function(e,t,n){"use strict";var r=void 0,o=void 0,i=35,u=36,a=37,c=38,d=39,l=40,s={37:-1,38:-1,39:1,40:1};function f(e){r[e].addEventListener("click",(function(e){v(e.target,!1)})),r[e].addEventListener("keydown",(function(e){switch(e.keyCode){case i:e.preventDefault(),v(r[r.length-1]);break;case u:e.preventDefault(),v(r[0])}})),r[e].addEventListener("keyup",(function(e){switch(e.keyCode){case a:case d:!function(e){for(var t=e.keyCode,n=0;n<r.length;n++)r[n].addEventListener("focus",p);if(s[t]){var o=e.target;void 0!==o.index&&(r[o.index+s[t]]?r[o.index+s[t]].focus():t===a||t===c?r[r.length-1].focus():t!==d&&t!==l||r[0].focus())}}(e)}})),r[e].index=e}function v(e,t){t=t||!0,function(){for(var e=0;e<r.length;e++)r[e].setAttribute("tabindex","-1"),r[e].setAttribute("aria-selected","false"),r[e].classList.remove("nav-tab-active"),r[e].removeEventListener("focus",p);for(var t=0;t<o.length;t++)o[t].setAttribute("hidden","hidden")}(),e.removeAttribute("tabindex"),e.setAttribute("aria-selected","true"),e.classList.add("nav-tab-active");var n=e.getAttribute("aria-controls");document.getElementById(n).removeAttribute("hidden"),t&&e.focus()}function p(e){!function(e){var t=document.activeElement;e===t&&v(e,!1)}(e.target)}document.addEventListener("DOMContentLoaded",(function(){document.querySelectorAll('#duplicate_post_settings_form [role="tablist"]')[0],r=document.querySelectorAll('#duplicate_post_settings_form [role="tab"]'),o=document.querySelectorAll('#duplicate_post_settings_form [role="tabpanel"]');for(var e=0;e<r.length;++e)f(e)})),jQuery((function(){jQuery(".taxonomy_private").hide(),jQuery(".toggle-private-taxonomies").on("click",(function(){var e=jQuery(this);jQuery(".taxonomy_private").toggle(300,(function(){e.attr("aria-expanded",jQuery(this).is(":visible"))}))}))}))}});
js/dist/duplicate-post-quick-edit-400.js ADDED
@@ -0,0 +1 @@
 
1
+ !function(t){var e={};function n(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return t[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=t,n.c=e,n.d=function(t,e,i){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(i,o,function(e){return t[e]}.bind(null,o));return i},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=9)}({9:function(t,e,n){"use strict";var i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};!function(t){var e=inlineEditPost.edit;inlineEditPost.edit=function(n){e.apply(this,arguments);var o=0;if("object"==(void 0===n?"undefined":i(n))&&(o=parseInt(this.getId(n))),o>0){var r=t("#edit-"+o),u=t("#post-"+o),l=1!==t(".duplicate_post_original_item span",u).data("noOriginal"),a=t(".duplicate_post_original_item span",u).html(),p=1===t(".duplicate_post_original_item span",u).data("copyIsForRewriteAndRepublish");l&&!p?(t(".duplicate_post_original_item_title_span",r).html(a),t("#duplicate_post_quick_edit_fieldset",r).show()):(t("#duplicate_post_quick_edit_fieldset",r).hide(),t(".duplicate_post_original_item_title_span",r).html(""))}}}(jQuery)}});
js/dist/duplicate-post-strings-400.js ADDED
@@ -0,0 +1 @@
 
1
+ !function(e){var t={};function o(i){if(t[i])return t[i].exports;var r=t[i]={i:i,l:!1,exports:{}};return e[i].call(r.exports,r,r.exports,o),r.l=!0,r.exports}o.m=e,o.c=t,o.d=function(e,t,i){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},o.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.t=function(e,t){if(1&t&&(e=o(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(o.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var r in e)o.d(i,r,function(t){return e[t]}.bind(null,r));return i},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,"a",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p="",o(o.s=8)}([function(e,t){e.exports=window.wp.data},function(e,t){e.exports=window.wp.element},function(e,t){e.exports=window.wp.components},function(e,t){e.exports=window.wp.i18n},function(e,t,o){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.redirectOnSaveCompletion=void 0;var i=o(0),r=function(e){var t=(0,i.select)("core/editor").getCurrentPostAttribute("status"),o=(0,i.select)("core/editor").getEditedPostAttribute("status");"dp-rewrite-republish"===t&&"publish"===o&&(0,i.dispatch)("core/editor").editPost({status:t}),window.location.assign(e)};t.redirectOnSaveCompletion=function(e,t){var o=(0,i.select)("core/editor").isSavingPost(),n=(0,i.select)("core/editor").isAutosavingPost(),s=(0,i.select)("core/edit-post").hasMetaBoxes(),a=(0,i.select)("core/edit-post").isSavingMetaBoxes();return s&&!a&&t.wasSavingMetaboxes&&r(e),s||o||!t.wasSavingPost||t.wasAutoSavingPost||r(e),{isSavingPost:o,isSavingMetaBoxes:a,isAutosavingPost:n}}},,,,function(e,t,o){"use strict";var i=o(1),r=o(2),n=o(3),s=o(0),a=o(4);var u,l,c,p=function(){(0,s.dispatch)("core/editor").savePost();var e=!1,t=!1,o=!1;(0,s.subscribe)((function(){var i=(0,a.redirectOnSaveCompletion)(duplicatePostStrings.checkLink,{wasSavingPost:e,wasSavingMetaboxes:t,wasAutoSavingPost:o});e=i.isSavingPost,t=i.isSavingMetaBoxes,o=i.isAutosavingPost}))},d={Publish:(0,n.__)("Republish","duplicate-post"),"Publish:":(0,n.__)("Republish:","duplicate-post"),"Publish on:":(0,n.__)("Republish on:","duplicate-post"),"Are you ready to publish?":(0,n.__)("Are you ready to republish your post?","duplicate-post"),"Double-check your settings before publishing.":(0,i.createInterpolateElement)((0,n.__)("After republishing your changes will be merged into the original post and you'll be redirected there.<br /><br />Do you want to compare your changes with the original version before merging?<br /><br /><button>Save changes and compare</button>","duplicate-post"),{button:wp.element.createElement(r.Button,{isSecondary:!0,onClick:p}),br:wp.element.createElement("br",null)}),Schedule:(0,n.__)("Schedule republish","duplicate-post"),"Schedule…":(0,n.__)("Schedule republish…","duplicate-post"),"post action/button labelSchedule":(0,n.__)("Schedule republish","duplicate-post"),"Are you ready to schedule?":(0,n.__)("Are you ready to schedule the republishing of your post?","duplicate-post"),"Your work will be published at the specified date and time.":(0,i.createInterpolateElement)((0,n.__)("You're about to replace the original with this rewritten post at the specified date and time.<br /><br />Do you want to compare your changes with the original version before merging?<br /><br /><button>Save changes and compare</button>","duplicate-post"),{button:wp.element.createElement(r.Button,{isSecondary:!0,onClick:p}),br:wp.element.createElement("br",null)}),"is now scheduled. It will go live on":(0,n.__)(", the rewritten post, is now scheduled to replace the original post. It will be published on","duplicate-post")};for(var b in d)(0,n.setLocaleData)((c=[d[b],"duplicate-post"],(l=b)in(u={})?Object.defineProperty(u,l,{value:c,enumerable:!0,configurable:!0,writable:!0}):u[l]=c,u))}]);
readme.txt CHANGED
@@ -2,10 +2,10 @@
2
  Contributors: yoast, lopo
3
  Donate link: https://yoast.com/wordpress/plugins/duplicate-post/
4
  Tags: duplicate post, copy, clone
5
- Requires at least: 3.6
6
- Tested up to: 5.5
7
- Stable tag: 3.2.6
8
- Requires PHP: 5.2.4
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -62,12 +62,12 @@ If Duplicate Post is still in English, or if there are some untraslated strings,
62
 
63
  == Screenshots ==
64
 
65
- 1. Here you can copy the post you're editing to a new draft.
66
- 2. By clicking on "Clone" the post is cloned immediately. "New draft" leads to the edit screen.
67
- 3. The options page.
68
- 4. The template tag manually added to Twenty Ten theme. Click on the "Copy to a new draft" link and you're redirected to the edit screen for a new draft copy of your post.
69
- 5. The admin bar link.
70
- 6. Bulk clone action.
71
 
72
  == Upgrade Notice ==
73
 
@@ -151,6 +151,14 @@ New features and customization, WP 3.0 compatibility: you should upgrade if you
151
 
152
  == Changelog ==
153
 
 
 
 
 
 
 
 
 
154
  = 3.2.6 (2020-09-17) =
155
  * Compatibility with WordPress 5.5
156
  * Fixed bug about copying comments in WordPress 5.5
2
  Contributors: yoast, lopo
3
  Donate link: https://yoast.com/wordpress/plugins/duplicate-post/
4
  Tags: duplicate post, copy, clone
5
+ Requires at least: 5.5
6
+ Tested up to: 5.6
7
+ Stable tag: 4.0
8
+ Requires PHP: 5.6.20
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
62
 
63
  == Screenshots ==
64
 
65
+ 1. Classic editor.
66
+ 2. Block editor.
67
+ 3. Post list.
68
+ 4. Admin bar menu.
69
+ 5. Bulk actions.
70
+ 6. The options page.
71
 
72
  == Upgrade Notice ==
73
 
151
 
152
  == Changelog ==
153
 
154
+ = 4.0 (2021-01-12) =
155
+
156
+ Enhancements:
157
+
158
+ * Introduces the Rewrite & Republish feature, offering you the possibility to update a post/page without taking it offline or having to take extra steps. This feature is currently not available when Elementor is active on your site.
159
+ * Introduces an integration with the Block Editor.
160
+ * Introduces new settings to individually enable/disable the `New Draft`, `Clone` and `Rewrite & Republish` links.
161
+
162
  = 3.2.6 (2020-09-17) =
163
  * Compatibility with WordPress 5.5
164
  * Fixed bug about copying comments in WordPress 5.5
src/admin/class-options-form-generator.php ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post plugin file.
4
+ *
5
+ * @package Yoast\WP\Duplicate_Post\Admin
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\Admin;
9
+
10
+ use Yoast\WP\Duplicate_Post\Utils;
11
+
12
+ /**
13
+ * Class Options_Form_Generator
14
+ *
15
+ * @package Yoast\WP\Duplicate_Post\Admin
16
+ */
17
+ class Options_Form_Generator {
18
+
19
+ /**
20
+ * The Options_Inputs instance.
21
+ *
22
+ * @var Options_Inputs
23
+ */
24
+ protected $options_inputs;
25
+
26
+ /**
27
+ * Options_Form_Generator constructor.
28
+ *
29
+ * @param Options_Inputs $inputs The Options_Inputs instance.
30
+ */
31
+ public function __construct( Options_Inputs $inputs ) {
32
+ $this->options_inputs = $inputs;
33
+ }
34
+
35
+ /**
36
+ * Generates the HTML output of an input control, based on the passed options.
37
+ *
38
+ * @param array $options The options to base the input on.
39
+ * @param string $parent_option The parent option, used for grouped inputs. Optional.
40
+ *
41
+ * @return string The HTML output.
42
+ */
43
+ public function generate_options_input( array $options, $parent_option = '' ) {
44
+ $output = '';
45
+
46
+ foreach ( $options as $option => $option_values ) {
47
+ // Skip empty options.
48
+ if ( empty( $option_values ) ) {
49
+ continue;
50
+ }
51
+
52
+ // Check for support of the current WordPress version.
53
+ if ( \array_key_exists( 'version', $option_values ) && \version_compare( \get_bloginfo( 'version' ), $option_values['version'] ) < 0 ) {
54
+ continue;
55
+ }
56
+
57
+ if ( \array_key_exists( 'sub_options', $option_values ) ) {
58
+ $output .= $this->generate_options_input( $option_values['sub_options'], $option );
59
+
60
+ continue;
61
+ }
62
+
63
+ // If callback, call it.
64
+ if ( \array_key_exists( 'callback', $option_values ) ) {
65
+ $output .= $this->{$option_values['callback']}();
66
+
67
+ continue;
68
+ }
69
+
70
+ if ( ! \array_key_exists( 'type', $option_values ) ) {
71
+ continue;
72
+ }
73
+
74
+ $id = ( \array_key_exists( 'id', $option_values ) ? $option_values['id'] : $this->prepare_input_id( $option ) );
75
+
76
+ if ( $parent_option !== '' ) {
77
+ $id = \sprintf( '%s-%s', $this->prepare_input_id( $parent_option ), $id );
78
+ $option = \sprintf( '%s[%s]', $parent_option, $option );
79
+ }
80
+
81
+ switch ( $option_values['type'] ) {
82
+ case 'checkbox':
83
+ $output .= $this->options_inputs->checkbox(
84
+ $option,
85
+ $option_values['value'],
86
+ $id,
87
+ $this->is_checked( $option, $option_values, $parent_option )
88
+ );
89
+
90
+ $output .= \sprintf( '<label for="%s">%s</label>', $id, \esc_html( $option_values['label'] ) );
91
+ break;
92
+ case 'text':
93
+ $output .= $this->options_inputs->text( $option, $option_values['value'], $id );
94
+ break;
95
+ case 'number':
96
+ $output .= $this->options_inputs->number( $option, $option_values['value'], $id );
97
+
98
+ break;
99
+ }
100
+
101
+ if ( \array_key_exists( 'description', $option_values ) ) {
102
+ $output .= ' ' . $this->extract_description( $option_values['description'], $id );
103
+ }
104
+
105
+ $output .= '<br />';
106
+ }
107
+
108
+ return $output;
109
+ }
110
+
111
+ /**
112
+ * Sorts taxonomy objects based on being public, followed by being private.
113
+ *
114
+ * @param \WP_Taxonomy $taxonomy1 First taxonomy object.
115
+ * @param \WP_Taxonomy $taxonomy2 Second taxonomy object.
116
+ *
117
+ * @return bool True when the first taxonomy is public.
118
+ * @ignore
119
+ */
120
+ public function sort_taxonomy_objects( $taxonomy1, $taxonomy2 ) {
121
+ return ( $taxonomy1->public < $taxonomy2->public );
122
+ }
123
+
124
+ /**
125
+ * Extracts and formats the description associated with the input field.
126
+ *
127
+ * @param string|array $description The description string. Can be an array of strings.
128
+ * @param string $id The ID of the input field.
129
+ *
130
+ * @return string The description HTML for the input.
131
+ */
132
+ public function extract_description( $description, $id ) {
133
+ if ( ! \is_array( $description ) ) {
134
+ return \sprintf( '<span id="%s-description">(%s)</span>', $id, \esc_html( $description ) );
135
+ }
136
+
137
+ return \sprintf( '<p id="%s-description">%s</p>', $id, \implode( '<br />', \array_map( '\esc_html', $description ) ) );
138
+ }
139
+
140
+ /**
141
+ * Generates a list of checkboxes for registered taxonomies.
142
+ *
143
+ * @return string The generated taxonomies list.
144
+ */
145
+ public function generate_taxonomy_exclusion_list() {
146
+ $taxonomies = \get_taxonomies( [], 'objects' );
147
+
148
+ \usort( $taxonomies, [ $this, 'sort_taxonomy_objects' ] );
149
+
150
+ $taxonomies_blacklist = \get_option( 'duplicate_post_taxonomies_blacklist' );
151
+
152
+ if ( ! \is_array( $taxonomies_blacklist ) ) {
153
+ $taxonomies_blacklist = [];
154
+ }
155
+
156
+ $output = '';
157
+
158
+ foreach ( $taxonomies as $taxonomy ) {
159
+ if ( $taxonomy->name === 'post_format' ) {
160
+ continue;
161
+ }
162
+
163
+ $is_public = ( $taxonomy->public ) ? 'public' : 'private';
164
+ $name = \esc_attr( $taxonomy->name );
165
+
166
+ $output .= \sprintf( '<div class="taxonomy_%s">', $is_public );
167
+ $output .= $this->generate_options_input(
168
+ [
169
+ 'duplicate_post_taxonomies_blacklist[]' => [
170
+ 'type' => 'checkbox',
171
+ 'id' => 'duplicate-post-' . $this->prepare_input_id( $name ),
172
+ 'value' => $name,
173
+ 'checked' => \in_array( $taxonomy->name, $taxonomies_blacklist, true ),
174
+ 'label' => \esc_html( $taxonomy->labels->name . ' [' . $taxonomy->name . ']' ),
175
+ ],
176
+ ]
177
+ );
178
+ $output .= '</div>';
179
+ }
180
+
181
+ return $output;
182
+ }
183
+
184
+ /**
185
+ * Generates a list of checkboxes for registered roles.
186
+ *
187
+ * @return string The generated roles list.
188
+ */
189
+ public function generate_roles_permission_list() {
190
+ $post_types = \get_post_types( [ 'show_ui' => true ], 'objects' );
191
+ $edit_capabilities = [ 'edit_posts' => true ];
192
+
193
+ foreach ( $post_types as $post_type ) {
194
+ $edit_capabilities[ $post_type->cap->edit_posts ] = true;
195
+ }
196
+
197
+ $output = '';
198
+
199
+ foreach ( Utils::get_roles() as $name => $display_name ) {
200
+ $role = \get_role( $name );
201
+
202
+ if ( count( \array_intersect_key( $role->capabilities, $edit_capabilities ) ) > 0 ) {
203
+ $output .= $this->generate_options_input(
204
+ [
205
+ 'duplicate_post_roles[]' => [
206
+ 'type' => 'checkbox',
207
+ 'id' => 'duplicate-post-' . $this->prepare_input_id( $name ),
208
+ 'value' => $name,
209
+ 'checked' => $role->has_cap( 'copy_posts' ),
210
+ 'label' => \esc_html( \translate_user_role( $display_name ) ),
211
+ ],
212
+ ]
213
+ );
214
+ }
215
+ }
216
+
217
+ return $output;
218
+ }
219
+
220
+ /**
221
+ * Generates a list of checkboxes for registered post types.
222
+ *
223
+ * @return string The generated post types list.
224
+ */
225
+ public function generate_post_types_list() {
226
+ $post_types = \get_post_types( [ 'show_ui' => true ], 'objects' );
227
+ $output = '';
228
+
229
+ foreach ( $post_types as $post_type_object ) {
230
+ if ( $post_type_object->name === 'attachment'
231
+ || $post_type_object->name === 'wp_block' ) {
232
+ continue;
233
+ }
234
+
235
+ $name = \esc_attr( $post_type_object->name );
236
+
237
+ $output .= $this->generate_options_input(
238
+ [
239
+ 'duplicate_post_types_enabled[]' => [
240
+ 'type' => 'checkbox',
241
+ 'id' => 'duplicate-post-' . $this->prepare_input_id( $name ),
242
+ 'value' => $name,
243
+ 'checked' => $this->is_post_type_enabled( $post_type_object->name ),
244
+ 'label' => \esc_html( $post_type_object->labels->name ),
245
+ ],
246
+ ]
247
+ );
248
+ }
249
+
250
+ return $output;
251
+ }
252
+
253
+ /**
254
+ * Determines whether the passed option should result in a checked checkbox or not.
255
+ *
256
+ * @param string $option The option to search for.
257
+ * @param array $option_values The option's values.
258
+ * @param string $parent_option The parent option. Optional.
259
+ *
260
+ * @return bool Whether or not the checkbox should be checked.
261
+ */
262
+ public function is_checked( $option, $option_values, $parent_option = '' ) {
263
+ if ( \array_key_exists( 'checked', $option_values ) ) {
264
+ return $option_values['checked'];
265
+ }
266
+
267
+ // Check for serialized options.
268
+ $saved_option = ! empty( $parent_option ) ? \get_option( $parent_option ) : \get_option( $option );
269
+
270
+ if ( ! \is_array( $saved_option ) ) {
271
+ return (int) $saved_option === 1;
272
+ }
273
+
274
+ // Clean up the sub-option's name.
275
+ $cleaned_option = \trim( \str_replace( $parent_option, '', $option ), '[]' );
276
+
277
+ return \array_key_exists( $cleaned_option, $saved_option ) && (int) $saved_option[ $cleaned_option ] === 1;
278
+ }
279
+
280
+ /**
281
+ * Prepares the passed ID so it's properly formatted.
282
+ *
283
+ * @param string $id The ID to prepare.
284
+ *
285
+ * @return string The prepared input ID.
286
+ */
287
+ public function prepare_input_id( $id ) {
288
+ return \str_replace( '_', '-', $id );
289
+ }
290
+
291
+ /**
292
+ * Checks whether or not a post type is enabled.
293
+ *
294
+ * @param string $post_type The post type.
295
+ *
296
+ * @return bool Whether or not the post type is enabled.
297
+ * @codeCoverageIgnore As this is a simple wrapper for a function that is also being used elsewhere, we can skip testing for now.
298
+ */
299
+ public function is_post_type_enabled( $post_type ) {
300
+ $duplicate_post_types_enabled = \get_option( 'duplicate_post_types_enabled', [ 'post', 'page' ] );
301
+ if ( ! \is_array( $duplicate_post_types_enabled ) ) {
302
+ $duplicate_post_types_enabled = [ $duplicate_post_types_enabled ];
303
+ }
304
+ return \in_array( $post_type, $duplicate_post_types_enabled, true );
305
+ }
306
+ }
src/admin/class-options-inputs.php ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post plugin file.
4
+ *
5
+ * @package Yoast\WP\Duplicate_Post\Admin
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\Admin;
9
+
10
+ /**
11
+ * Class Options_Inputs
12
+ */
13
+ class Options_Inputs {
14
+
15
+ /**
16
+ * Creates a basic input based on the passed parameters.
17
+ *
18
+ * @param string $type The type of input.
19
+ * @param string $name The name of the input.
20
+ * @param string $value The value of the input.
21
+ * @param string $id The ID of the input.
22
+ * @param string $attributes The additional attributes to use. Optional.
23
+ *
24
+ * @return string The input's HTML output.
25
+ */
26
+ protected function input( $type, $name, $value, $id, $attributes = '' ) {
27
+ return \sprintf(
28
+ '<input type="%s" name="%s" id="%s" value="%s" %s />',
29
+ \esc_attr( $type ),
30
+ \esc_attr( $name ),
31
+ \esc_attr( $id ),
32
+ \esc_attr( $value ),
33
+ $attributes
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Creates a checkbox input.
39
+ *
40
+ * @param string $name The name of the checkbox.
41
+ * @param string $value The value of the checkbox.
42
+ * @param string $id The ID of the checkbox.
43
+ * @param bool $checked Whether or not the checkbox should be checked.
44
+ *
45
+ * @return string The checkbox' HTML output.
46
+ */
47
+ public function checkbox( $name, $value, $id, $checked = false ) {
48
+ $checked = $checked ? 'checked="checked"' : '';
49
+
50
+ return $this->input( 'checkbox', $name, $value, $id, $checked );
51
+ }
52
+
53
+ /**
54
+ * Creates a text field input.
55
+ *
56
+ * @param string $name The name of the text field.
57
+ * @param string $value The value of the text field.
58
+ * @param string $id The ID of the text field.
59
+ *
60
+ * @return string The text field's HTML output.
61
+ */
62
+ public function text( $name, $value, $id ) {
63
+ return $this->input( 'text', $name, $value, $id );
64
+ }
65
+
66
+ /**
67
+ * Creates a number input.
68
+ *
69
+ * @param string $name The name of the number input.
70
+ * @param string $value The value of the number input.
71
+ * @param string $id The ID of the number input.
72
+ *
73
+ * @return string The number input's HTML output.
74
+ */
75
+ public function number( $name, $value, $id ) {
76
+ return $this->input( 'number', $name, $value, $id, 'min="0" step="1"' );
77
+ }
78
+ }
src/admin/class-options-page.php ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post plugin file.
4
+ *
5
+ * @package Yoast\WP\Duplicate_Post\Admin
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\Admin;
9
+
10
+ use Yoast\WP\Duplicate_Post\UI\Asset_Manager;
11
+ use Yoast\WP\Duplicate_Post\Utils;
12
+
13
+ /**
14
+ * Class Options_Page
15
+ */
16
+ class Options_Page {
17
+ /**
18
+ * The Options instance.
19
+ *
20
+ * @var Options
21
+ */
22
+ protected $options;
23
+
24
+ /**
25
+ * The Options_Form_Generator instance.
26
+ *
27
+ * @var Options_Form_Generator
28
+ */
29
+ protected $generator;
30
+
31
+ /**
32
+ * Holds the asset manager.
33
+ *
34
+ * @var Asset_Manager
35
+ */
36
+ protected $asset_manager;
37
+
38
+ /**
39
+ * Options_Page constructor.
40
+ *
41
+ * @param Options $options The Options class instance.
42
+ * @param Options_Form_Generator $generator The Options_Form_Generator class instance.
43
+ * @param Asset_Manager $asset_manager The Asset_Manager class instance.
44
+ */
45
+ public function __construct( Options $options, Options_Form_Generator $generator, Asset_Manager $asset_manager ) {
46
+ $this->options = $options;
47
+ $this->generator = $generator;
48
+ $this->asset_manager = $asset_manager;
49
+ }
50
+
51
+ /**
52
+ * Registers the necessary hooks.
53
+ *
54
+ * @return void
55
+ */
56
+ public function register_hooks() {
57
+ if ( \is_admin() ) {
58
+ \add_action( 'admin_menu', [ $this, 'register_menu' ] );
59
+ \add_action( 'admin_init', [ $this->options, 'register_settings' ] );
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Enqueues the assets.
65
+ *
66
+ * @return void
67
+ */
68
+ public function enqueue_assets() {
69
+ $this->asset_manager->enqueue_options_styles();
70
+ $this->asset_manager->enqueue_options_script();
71
+ }
72
+
73
+ /**
74
+ * Registers the menu item.
75
+ *
76
+ * @return void
77
+ */
78
+ public function register_menu() {
79
+ $page_hook = \add_options_page(
80
+ \__( 'Duplicate Post Options', 'duplicate-post' ),
81
+ \__( 'Duplicate Post', 'duplicate-post' ),
82
+ 'manage_options',
83
+ 'duplicatepost',
84
+ [ $this, 'generate_page' ]
85
+ );
86
+
87
+ \add_action( $page_hook, [ $this, 'enqueue_assets' ] );
88
+ }
89
+
90
+ /**
91
+ * Generates the inputs for the specified tab / fieldset.
92
+ *
93
+ * @param string $tab The tab to get the configuration for.
94
+ * @param string $fieldset The fieldset to get the configuration for. Optional.
95
+ *
96
+ * @return string The HTML output for the controls present on the tab / fieldset.
97
+ * @codeCoverageIgnore As this is a simple wrapper for two functions that are already tested elsewhere, we can skip testing.
98
+ */
99
+ public function generate_tab_inputs( $tab, $fieldset = '' ) {
100
+ $options = $this->options->get_options_for_tab( $tab, $fieldset );
101
+
102
+ return $this->generator->generate_options_input( $options );
103
+ }
104
+
105
+ /**
106
+ * Generates an input for a single option.
107
+ *
108
+ * @param string $option The option configuration to base the input on.
109
+ *
110
+ * @return string The input HTML.
111
+ * @codeCoverageIgnore As this is a simple wrapper for two functions that are already tested elsewhere, we can skip testing.
112
+ */
113
+ public function generate_input( $option ) {
114
+ return $this->generator->generate_options_input( $this->options->get_option( $option ) );
115
+ }
116
+
117
+ /**
118
+ * Registers the proper capabilities.
119
+ *
120
+ * @return void
121
+ */
122
+ public function register_capabilities() {
123
+ if ( ! \current_user_can( 'promote_users' ) || ! $this->settings_updated() ) {
124
+ return;
125
+ }
126
+
127
+ $roles = $this->get_duplicate_post_roles();
128
+
129
+ foreach ( Utils::get_roles() as $name => $display_name ) {
130
+ $role = \get_role( $name );
131
+
132
+ if ( ! $role->has_cap( 'copy_posts' ) && \in_array( $name, $roles, true ) ) {
133
+ /* If the role doesn't have the capability and it was selected, add it. */
134
+ $role->add_cap( 'copy_posts' );
135
+ }
136
+
137
+ if ( $role->has_cap( 'copy_posts' ) && ! \in_array( $name, $roles, true ) ) {
138
+ /* If the role has the capability and it wasn't selected, remove it. */
139
+ $role->remove_cap( 'copy_posts' );
140
+ }
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Generates the options page.
146
+ *
147
+ * @return void
148
+ * @codeCoverageIgnore
149
+ */
150
+ public function generate_page() {
151
+ $this->register_capabilities();
152
+
153
+ require_once DUPLICATE_POST_PATH . 'src/admin/views/options.php';
154
+ }
155
+
156
+ /**
157
+ * Checks whether settings have been updated.
158
+ *
159
+ * @return bool Whether or not the settings have been updated.
160
+ */
161
+ protected function settings_updated() {
162
+ return isset( $_GET['settings-updated'] ) && $_GET['settings-updated'] === 'true'; // phpcs:ignore WordPress.Security.NonceVerification
163
+ }
164
+
165
+ /**
166
+ * Gets the registered custom roles.
167
+ *
168
+ * @return array The roles. Returns an empty array if there are none.
169
+ */
170
+ protected function get_duplicate_post_roles() {
171
+ $roles = \get_option( 'duplicate_post_roles' );
172
+
173
+ if ( empty( $roles ) ) {
174
+ $roles = [];
175
+ }
176
+
177
+ return $roles;
178
+ }
179
+ }
src/admin/class-options.php ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Options class
4
+ *
5
+ * @package Duplicate Post
6
+ * @since 4.0
7
+ */
8
+
9
+ namespace Yoast\WP\Duplicate_Post\Admin;
10
+
11
+ /**
12
+ * Class Options
13
+ */
14
+ class Options {
15
+
16
+ /**
17
+ * Registers the settings.
18
+ *
19
+ * @return void
20
+ */
21
+ public function register_settings() {
22
+ foreach ( \array_keys( $this->get_options() ) as $option ) {
23
+ \register_setting( 'duplicate_post_group', $option );
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Gets the options for the specified tab.
29
+ *
30
+ * Also allows filtering on a particular fieldset.
31
+ *
32
+ * @param string $tab The tab to get the options for.
33
+ * @param string $fieldset The fieldset to get the options for. Optional.
34
+ *
35
+ * @return array The options for the specified tab.
36
+ */
37
+ public function get_options_for_tab( $tab, $fieldset = '' ) {
38
+ $options = $this->get_options();
39
+
40
+ $options = \array_filter(
41
+ $options,
42
+ function ( $option ) use ( $tab ) {
43
+ return \array_key_exists( 'tab', $option ) && $option['tab'] === $tab;
44
+ }
45
+ );
46
+
47
+ if ( empty( $options ) ) {
48
+ return [];
49
+ }
50
+
51
+ // If a fieldset is specified, filter out the corresponding options.
52
+ if ( ! empty( $fieldset ) ) {
53
+ $options = \array_filter(
54
+ $options,
55
+ function ( $option ) use ( $fieldset ) {
56
+ return \array_key_exists( 'fieldset', $option ) && $option['fieldset'] === $fieldset;
57
+ }
58
+ );
59
+ }
60
+
61
+ return $options;
62
+ }
63
+
64
+ /**
65
+ * Gets an option from the options array, based on its name.
66
+ *
67
+ * @param string $name The name of the option to retrieve.
68
+ *
69
+ * @return array The option. Empty array if it does not exist.
70
+ */
71
+ public function get_option( $name ) {
72
+ $options = $this->get_options();
73
+
74
+ return \array_key_exists( $name, $options ) ? [ $name => $options[ $name ] ] : [];
75
+ }
76
+
77
+ /**
78
+ * Gets the list of registered options.
79
+ *
80
+ * @return array The options.
81
+ * @codeCoverageIgnore
82
+ */
83
+ public function get_options() {
84
+ return [
85
+ 'duplicate_post_copytitle' => [
86
+ 'tab' => 'what-to-copy',
87
+ 'fieldset' => 'elements-to-copy',
88
+ 'type' => 'checkbox',
89
+ 'label' => \__( 'Title', 'default' ),
90
+ 'value' => 1,
91
+ ],
92
+ 'duplicate_post_copydate' => [
93
+ 'tab' => 'what-to-copy',
94
+ 'fieldset' => 'elements-to-copy',
95
+ 'type' => 'checkbox',
96
+ 'label' => \__( 'Date', 'default' ),
97
+ 'value' => 1,
98
+ ],
99
+ 'duplicate_post_copystatus' => [
100
+ 'tab' => 'what-to-copy',
101
+ 'fieldset' => 'elements-to-copy',
102
+ 'type' => 'checkbox',
103
+ 'label' => \__( 'Status', 'default' ),
104
+ 'value' => 1,
105
+ ],
106
+ 'duplicate_post_copyslug' => [
107
+ 'tab' => 'what-to-copy',
108
+ 'fieldset' => 'elements-to-copy',
109
+ 'type' => 'checkbox',
110
+ 'label' => \__( 'Slug', 'default' ),
111
+ 'value' => 1,
112
+ ],
113
+ 'duplicate_post_copyexcerpt' => [
114
+ 'tab' => 'what-to-copy',
115
+ 'fieldset' => 'elements-to-copy',
116
+ 'type' => 'checkbox',
117
+ 'label' => \__( 'Excerpt', 'default' ),
118
+ 'value' => 1,
119
+ ],
120
+ 'duplicate_post_copycontent' => [
121
+ 'tab' => 'what-to-copy',
122
+ 'fieldset' => 'elements-to-copy',
123
+ 'type' => 'checkbox',
124
+ 'label' => \__( 'Content', 'default' ),
125
+ 'value' => 1,
126
+ ],
127
+ 'duplicate_post_copythumbnail' => [
128
+ 'tab' => 'what-to-copy',
129
+ 'fieldset' => 'elements-to-copy',
130
+ 'type' => 'checkbox',
131
+ 'label' => \__( 'Featured Image', 'default' ),
132
+ 'value' => 1,
133
+ ],
134
+ 'duplicate_post_copytemplate' => [
135
+ 'tab' => 'what-to-copy',
136
+ 'fieldset' => 'elements-to-copy',
137
+ 'type' => 'checkbox',
138
+ 'label' => \__( 'Template', 'default' ),
139
+ 'value' => 1,
140
+ ],
141
+ 'duplicate_post_copyformat' => [
142
+ 'tab' => 'what-to-copy',
143
+ 'fieldset' => 'elements-to-copy',
144
+ 'type' => 'checkbox',
145
+ 'label' => \__( 'Post format', 'default' ),
146
+ 'value' => 1,
147
+ ],
148
+ 'duplicate_post_copyauthor' => [
149
+ 'tab' => 'what-to-copy',
150
+ 'fieldset' => 'elements-to-copy',
151
+ 'type' => 'checkbox',
152
+ 'label' => \__( 'Author', 'default' ),
153
+ 'value' => 1,
154
+ ],
155
+ 'duplicate_post_copypassword' => [
156
+ 'tab' => 'what-to-copy',
157
+ 'fieldset' => 'elements-to-copy',
158
+ 'type' => 'checkbox',
159
+ 'label' => \__( 'Password', 'default' ),
160
+ 'value' => 1,
161
+ ],
162
+ 'duplicate_post_copyattachments' => [
163
+ 'tab' => 'what-to-copy',
164
+ 'fieldset' => 'elements-to-copy',
165
+ 'type' => 'checkbox',
166
+ 'label' => \__( 'Attachments', 'default' ),
167
+ 'value' => 1,
168
+ 'description' => \__( 'you probably want this unchecked, unless you have very special requirements', 'duplicate-post' ),
169
+ ],
170
+ 'duplicate_post_copychildren' => [
171
+ 'tab' => 'what-to-copy',
172
+ 'fieldset' => 'elements-to-copy',
173
+ 'type' => 'checkbox',
174
+ 'label' => \__( 'Children', 'default' ),
175
+ 'value' => 1,
176
+ ],
177
+ 'duplicate_post_copycomments' => [
178
+ 'tab' => 'what-to-copy',
179
+ 'fieldset' => 'elements-to-copy',
180
+ 'type' => 'checkbox',
181
+ 'label' => \__( 'Comments', 'default' ),
182
+ 'value' => 1,
183
+ 'description' => \__( 'except pingbacks and trackbacks', 'duplicate-post' ),
184
+ ],
185
+ 'duplicate_post_copymenuorder' => [
186
+ 'tab' => 'what-to-copy',
187
+ 'fieldset' => 'elements-to-copy',
188
+ 'type' => 'checkbox',
189
+ 'label' => \__( 'Menu order', 'default' ),
190
+ 'value' => 1,
191
+ ],
192
+ 'duplicate_post_title_prefix' => [
193
+ 'tab' => 'what-to-copy',
194
+ 'type' => 'text',
195
+ 'label' => \__( 'Title prefix', 'duplicate-post' ),
196
+ 'value' => \get_option( 'duplicate_post_title_prefix' ),
197
+ 'description' => [ \__( 'Prefix to be added before the title, e.g. "Copy of" (blank for no prefix)', 'duplicate-post' ) ],
198
+ ],
199
+ 'duplicate_post_title_suffix' => [
200
+ 'tab' => 'what-to-copy',
201
+ 'type' => 'text',
202
+ 'label' => \__( 'Title suffix', 'duplicate-post' ),
203
+ 'value' => \get_option( 'duplicate_post_title_suffix' ),
204
+ 'description' => [ \__( 'Suffix to be added after the title, e.g. "(dup)" (blank for no suffix)', 'duplicate-post' ) ],
205
+ ],
206
+ 'duplicate_post_increase_menu_order_by' => [
207
+ 'tab' => 'what-to-copy',
208
+ 'type' => 'number',
209
+ 'label' => \__( 'Increase menu order by', 'duplicate-post' ),
210
+ 'value' => \get_option( 'duplicate_post_increase_menu_order_by' ),
211
+ 'description' => [ \__( 'Add this number to the original menu order (blank or zero to retain the value)', 'duplicate-post' ) ],
212
+ ],
213
+ 'duplicate_post_blacklist' => [
214
+ 'tab' => 'what-to-copy',
215
+ 'type' => 'text',
216
+ 'label' => \__( 'Do not copy these fields', 'duplicate-post' ),
217
+ 'value' => \get_option( 'duplicate_post_blacklist' ),
218
+ 'description' => [
219
+ __( 'Comma-separated list of meta fields that must not be copied.', 'duplicate-post' ),
220
+ __( 'You can use * to match zero or more alphanumeric characters or underscores: e.g. field*', 'duplicate-post' ),
221
+ ],
222
+ ],
223
+ 'duplicate_post_taxonomies_blacklist' => [
224
+ 'tab' => 'what-to-copy',
225
+ 'callback' => 'generate_taxonomy_exclusion_list',
226
+ ],
227
+ 'duplicate_post_roles' => [
228
+ 'tab' => 'permissions',
229
+ 'callback' => 'generate_roles_permission_list',
230
+ ],
231
+ 'duplicate_post_types_enabled' => [
232
+ 'tab' => 'permissions',
233
+ 'callback' => 'generate_post_types_list',
234
+ ],
235
+ 'duplicate_post_show_original_meta_box' => [
236
+ 'tab' => 'display',
237
+ 'fieldset' => 'show-original',
238
+ 'type' => 'checkbox',
239
+ 'label' => \__( 'In a metabox in the Edit screen', 'duplicate-post' ),
240
+ 'value' => 1,
241
+ 'description' => [
242
+ __( "You'll also be able to delete the reference to the original item with a checkbox", 'duplicate-post' ),
243
+ ],
244
+ ],
245
+ 'duplicate_post_show_original_column' => [
246
+ 'tab' => 'display',
247
+ 'fieldset' => 'show-original',
248
+ 'type' => 'checkbox',
249
+ 'label' => \__( 'In a column in the Post list', 'duplicate-post' ),
250
+ 'value' => 1,
251
+ 'description' => [
252
+ __( "You'll also be able to delete the reference to the original item with a checkbox in Quick Edit", 'duplicate-post' ),
253
+ ],
254
+ ],
255
+ 'duplicate_post_show_original_in_post_states' => [
256
+ 'tab' => 'display',
257
+ 'fieldset' => 'show-original',
258
+ 'type' => 'checkbox',
259
+ 'label' => \__( 'After the title in the Post list', 'duplicate-post' ),
260
+ 'value' => 1,
261
+ ],
262
+ 'duplicate_post_show_notice' => [
263
+ 'tab' => 'display',
264
+ 'type' => 'checkbox',
265
+ 'label' => \__( 'Show update notice', 'duplicate-post' ),
266
+ 'value' => 1,
267
+ ],
268
+ 'duplicate_post_show_link' => [
269
+ 'tab' => 'display',
270
+ 'fieldset' => 'show-links',
271
+ 'sub_options' => [
272
+ 'new_draft' => [
273
+ 'type' => 'checkbox',
274
+ 'label' => \__( 'New Draft', 'duplicate-post' ),
275
+ 'value' => 1,
276
+ ],
277
+ 'clone' => [
278
+ 'type' => 'checkbox',
279
+ 'label' => \__( 'Clone', 'duplicate-post' ),
280
+ 'value' => 1,
281
+ ],
282
+ 'rewrite_republish' => [
283
+ 'type' => 'checkbox',
284
+ 'label' => \__( 'Rewrite & Republish', 'duplicate-post' ),
285
+ 'value' => 1,
286
+ ],
287
+ ],
288
+ ],
289
+ 'duplicate_post_show_link_in' => [
290
+ 'tab' => 'display',
291
+ 'fieldset' => 'show-links-in',
292
+ 'sub_options' => [
293
+ 'row' => [
294
+ 'type' => 'checkbox',
295
+ 'label' => \__( 'Post list', 'duplicate-post' ),
296
+ 'value' => 1,
297
+ ],
298
+ 'adminbar' => [
299
+ 'type' => 'checkbox',
300
+ 'label' => \__( 'Admin bar', 'duplicate-post' ),
301
+ 'value' => 1,
302
+ ],
303
+ 'submitbox' => [
304
+ 'type' => 'checkbox',
305
+ 'label' => \__( 'Edit screen', 'duplicate-post' ),
306
+ 'value' => 1,
307
+ ],
308
+ 'bulkactions' => [
309
+ 'type' => 'checkbox',
310
+ 'label' => \__( 'Bulk Actions', 'default' ),
311
+ 'value' => 1,
312
+ ],
313
+ ],
314
+ ],
315
+ ];
316
+ }
317
+ }
src/admin/views/options.php ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post plugin file.
4
+ *
5
+ * @package Yoast\WP\Duplicate_Post\Admin\Views
6
+ */
7
+
8
+ if ( ! defined( 'DUPLICATE_POST_CURRENT_VERSION' ) ) {
9
+ header( 'Status: 403 Forbidden' );
10
+ header( 'HTTP/1.1 403 Forbidden' );
11
+ exit();
12
+ }
13
+ ?>
14
+ <div class="wrap">
15
+ <h1>
16
+ <?php \esc_html_e( 'Duplicate Post Options', 'duplicate-post' ); ?>
17
+ </h1>
18
+
19
+ <form id="duplicate_post_settings_form" method="post" action="options.php" style="clear: both">
20
+ <?php \settings_fields( 'duplicate_post_group' ); ?>
21
+
22
+ <header role="tablist" aria-label="<?php \esc_attr_e( 'Settings sections', 'duplicate-post' ); ?>"
23
+ class="nav-tab-wrapper">
24
+ <button
25
+ type="button"
26
+ role="tab"
27
+ class="nav-tab nav-tab-active"
28
+ aria-selected="true"
29
+ aria-controls="what-tab"
30
+ id="what"><?php \esc_html_e( 'What to copy', 'duplicate-post' ); ?>
31
+ </button>
32
+ <button
33
+ type="button"
34
+ role="tab"
35
+ class="nav-tab"
36
+ aria-selected="false"
37
+ aria-controls="who-tab"
38
+ id="who"
39
+ tabindex="-1"><?php \esc_html_e( 'Permissions', 'duplicate-post' ); ?>
40
+ </button>
41
+ <button
42
+ type="button"
43
+ role="tab"
44
+ class="nav-tab"
45
+ aria-selected="false"
46
+ aria-controls="where-tab"
47
+ id="where"
48
+ tabindex="-1"><?php \esc_html_e( 'Display', 'duplicate-post' ); ?>
49
+ </button>
50
+ </header>
51
+
52
+ <section
53
+ tabindex="0"
54
+ role="tabpanel"
55
+ id="what-tab"
56
+ aria-labelledby="what">
57
+ <h2 class="hide-if-js"><?php \esc_html_e( 'What to copy', 'duplicate-post' ); ?></h2>
58
+ <table class="form-table" role="presentation">
59
+ <tr>
60
+ <th scope="row"><?php \esc_html_e( 'Post/page elements to copy', 'duplicate-post' ); ?></th>
61
+ <td>
62
+ <fieldset>
63
+ <legend class="screen-reader-text"><?php \esc_html_e( 'Post/page elements to copy', 'duplicate-post' ); ?></legend>
64
+ <?php
65
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Already escapes correctly.
66
+ echo $this->generate_tab_inputs( 'what-to-copy', 'elements-to-copy' );
67
+ ?>
68
+ </fieldset>
69
+ </td>
70
+ </tr>
71
+ <tr>
72
+ <th scope="row">
73
+ <label for="duplicate-post-title-prefix"><?php \esc_html_e( 'Title prefix', 'duplicate-post' ); ?></label>
74
+ </th>
75
+ <td>
76
+ <?php
77
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Already escapes correctly.
78
+ echo $this->generate_input( 'duplicate_post_title_prefix' );
79
+ ?>
80
+ </td>
81
+ </tr>
82
+ <tr>
83
+ <th scope="row">
84
+ <label for="duplicate-post-title-suffix"><?php \esc_html_e( 'Title suffix', 'duplicate-post' ); ?></label>
85
+ </th>
86
+ <td>
87
+ <?php
88
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Already escapes correctly.
89
+ echo $this->generate_input( 'duplicate_post_title_suffix' );
90
+ ?>
91
+ </td>
92
+ </tr>
93
+ <tr>
94
+ <th scope="row">
95
+ <label for="duplicate-post-increase-menu-order-by"><?php \esc_html_e( 'Increase menu order by', 'duplicate-post' ); ?></label>
96
+ </th>
97
+ <td>
98
+ <?php
99
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Already escapes correctly.
100
+ echo $this->generate_input( 'duplicate_post_increase_menu_order_by' );
101
+ ?>
102
+
103
+ </td>
104
+ </tr>
105
+ <tr>
106
+ <th scope="row">
107
+ <label for="duplicate-post-blacklist"><?php \esc_html_e( 'Do not copy these fields', 'duplicate-post' ); ?></label>
108
+ </th>
109
+ <td id="textfield">
110
+ <?php
111
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Already escapes correctly.
112
+ echo $this->generate_input( 'duplicate_post_blacklist' );
113
+ ?>
114
+ </td>
115
+ </tr>
116
+ <tr>
117
+ <th scope="row">
118
+ <?php \esc_html_e( 'Do not copy these taxonomies', 'duplicate-post' ); ?><br/>
119
+ </th>
120
+ <td>
121
+ <fieldset>
122
+ <legend class="screen-reader-text"><?php \esc_html_e( 'Do not copy these taxonomies', 'duplicate-post' ); ?></legend>
123
+ <?php
124
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Already escapes correctly.
125
+ echo $this->generate_input( 'duplicate_post_taxonomies_blacklist' );
126
+ ?>
127
+ <button type="button" class="button-link hide-if-no-js toggle-private-taxonomies"
128
+ aria-expanded="false">
129
+ <?php \esc_html_e( 'Show/hide private taxonomies', 'duplicate-post' ); ?>
130
+ </button>
131
+ </fieldset>
132
+ </td>
133
+ </tr>
134
+ </table>
135
+ </section>
136
+ <section
137
+ tabindex="0"
138
+ role="tabpanel"
139
+ id="who-tab"
140
+ aria-labelledby="who"
141
+ hidden="hidden">
142
+ <h2 class="hide-if-js"><?php \esc_html_e( 'Permissions', 'duplicate-post' ); ?></h2>
143
+ <table class="form-table" role="presentation">
144
+ <?php if ( \current_user_can( 'promote_users' ) ) { ?>
145
+ <tr>
146
+ <th scope="row"><?php \esc_html_e( 'Roles allowed to copy', 'duplicate-post' ); ?></th>
147
+ <td>
148
+ <fieldset>
149
+ <legend class="screen-reader-text"><?php \esc_html_e( 'Roles allowed to copy', 'duplicate-post' ); ?></legend>
150
+ <?php
151
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Already escapes correctly.
152
+ echo $this->generate_input( 'duplicate_post_roles' );
153
+ ?>
154
+ <p>
155
+ <?php \esc_html_e( 'Warning: users will be able to copy, rewrite and republish all posts, even those of other users.', 'duplicate-post' ); ?>
156
+ <br/>
157
+ <?php \esc_html_e( 'Passwords and contents of password-protected posts may become visible to undesired users and visitors.', 'duplicate-post' ); ?>
158
+ </p>
159
+ </fieldset>
160
+ </td>
161
+ </tr>
162
+ <?php } ?>
163
+ <tr>
164
+ <th scope="row"><?php \esc_html_e( 'Enable for these post types', 'duplicate-post' ); ?>
165
+ </th>
166
+ <td>
167
+ <fieldset>
168
+ <legend class="screen-reader-text"><?php \esc_html_e( 'Enable for these post types', 'duplicate-post' ); ?></legend>
169
+ <?php
170
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Already escapes correctly.
171
+ echo $this->generate_input( 'duplicate_post_types_enabled' );
172
+ ?>
173
+ <p>
174
+ <?php \esc_html_e( 'Select the post types you want the plugin to be enabled for.', 'duplicate-post' ); ?>
175
+ <br/>
176
+ <?php \esc_html_e( 'Whether the links are displayed for custom post types registered by themes or plugins depends on their use of standard WordPress UI elements.', 'duplicate-post' ); ?>
177
+ </p>
178
+ </fieldset>
179
+ </td>
180
+ </tr>
181
+ </table>
182
+ </section>
183
+ <section
184
+ tabindex="0"
185
+ role="tabpanel"
186
+ id="where-tab"
187
+ aria-labelledby="where"
188
+ hidden="hidden">
189
+ <h2 class="hide-if-js"><?php \esc_html_e( 'Display', 'duplicate-post' ); ?></h2>
190
+ <table class="form-table" role="presentation">
191
+ <tr>
192
+ <th scope="row"><?php \esc_html_e( 'Show these links', 'duplicate-post' ); ?></th>
193
+ <td>
194
+ <fieldset>
195
+ <?php
196
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Already escapes correctly.
197
+ echo $this->generate_tab_inputs( 'display', 'show-links' );
198
+ ?>
199
+ </fieldset>
200
+ </td>
201
+ </tr>
202
+
203
+ <tr>
204
+ <th scope="row"><?php \esc_html_e( 'Show links in', 'duplicate-post' ); ?></th>
205
+ <td>
206
+ <fieldset>
207
+ <?php
208
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Already escapes correctly.
209
+ echo $this->generate_tab_inputs( 'display', 'show-links-in' );
210
+ ?>
211
+ </fieldset>
212
+ <p>
213
+ <?php \esc_html_e( 'Whether the links are displayed for custom post types registered by themes or plugins depends on their use of standard WordPress UI elements.', 'duplicate-post' ); ?>
214
+ <br/>
215
+ <?php
216
+ \printf(
217
+ /* translators: 1: Code start tag, 2: Code closing tag, 3: Link start tag to the template tag documentation, 4: Link closing tag. */
218
+ \esc_html__( 'You can also use the template tag %1$sduplicate_post_clone_post_link( $link, $before, $after, $id )%2$s. %3$sMore info on the template tag%4$s.', 'duplicate-post' ),
219
+ '<code>',
220
+ '</code>',
221
+ '<a href="' . \esc_url( 'https://developer.yoast.com/duplicate-post/functions-template-tags#duplicate_post_clone_post_link' ) . '">',
222
+ '</a>'
223
+ );
224
+ ?>
225
+ </p>
226
+ </td>
227
+ </tr>
228
+ <tr>
229
+ <th scope="row"><?php \esc_html_e( 'Show original item:', 'duplicate-post' ); ?></th>
230
+ <td>
231
+ <?php
232
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Already escapes correctly.
233
+ echo $this->generate_tab_inputs( 'display', 'show-original' );
234
+ ?>
235
+ </td>
236
+ </tr>
237
+ <tr>
238
+ <th scope="row"><?php \esc_html_e( 'Update notice', 'duplicate-post' ); ?></th>
239
+ <td>
240
+ <?php
241
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Already escapes correctly.
242
+ echo $this->generate_input( 'duplicate_post_show_notice' );
243
+ ?>
244
+ </td>
245
+ </tr>
246
+ </table>
247
+ </section>
248
+ <p class="submit">
249
+ <input type="submit" class="button button-primary" value="<?php \esc_html_e( 'Save changes', 'duplicate-post' ); ?>"/>
250
+ </p>
251
+ </form>
252
+ </div>
src/class-duplicate-post.php ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post main class.
4
+ *
5
+ * @package Duplicate_Post
6
+ * @since 4.0
7
+ */
8
+
9
+ namespace Yoast\WP\Duplicate_Post;
10
+
11
+ use Yoast\WP\Duplicate_Post\Handlers\Handler;
12
+ use Yoast\WP\Duplicate_Post\UI\User_Interface;
13
+ use Yoast\WP\Duplicate_Post\Watchers\Watchers;
14
+
15
+ /**
16
+ * Represents the Duplicate Post main class.
17
+ */
18
+ class Duplicate_Post {
19
+
20
+ /**
21
+ * Permissions_Helper object.
22
+ *
23
+ * @var Permissions_Helper
24
+ */
25
+ protected $permissions_helper;
26
+
27
+ /**
28
+ * User_Interface object.
29
+ *
30
+ * @var User_Interface
31
+ */
32
+ protected $user_interface;
33
+
34
+ /**
35
+ * Post_Duplicator object.
36
+ *
37
+ * @var Post_Duplicator
38
+ */
39
+ protected $post_duplicator;
40
+
41
+ /**
42
+ * Handler object.
43
+ *
44
+ * @var Handler
45
+ */
46
+ protected $handler;
47
+
48
+ /**
49
+ * Post_Republisher object.
50
+ *
51
+ * @var Post_Republisher
52
+ */
53
+ protected $post_republisher;
54
+
55
+ /**
56
+ * Revisions_Migrator object.
57
+ *
58
+ * @var Revisions_Migrator
59
+ */
60
+ protected $revisions_migrator;
61
+
62
+ /**
63
+ * Watchers object.
64
+ *
65
+ * @var Watchers
66
+ */
67
+ protected $watchers;
68
+
69
+ /**
70
+ * Initializes the main class.
71
+ */
72
+ public function __construct() {
73
+ $this->permissions_helper = new Permissions_Helper();
74
+ $this->user_interface = new User_Interface( $this->permissions_helper );
75
+ $this->post_duplicator = new Post_Duplicator();
76
+ $this->handler = new Handler( $this->post_duplicator, $this->permissions_helper );
77
+ $this->post_republisher = new Post_Republisher( $this->post_duplicator, $this->permissions_helper );
78
+ $this->revisions_migrator = new Revisions_Migrator();
79
+ $this->watchers = new Watchers( $this->permissions_helper );
80
+
81
+ $this->post_republisher->register_hooks();
82
+ $this->revisions_migrator->register_hooks();
83
+ }
84
+ }
src/class-permissions-helper.php ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Permissions helper for Duplicate Post.
4
+ *
5
+ * @package Duplicate_Post
6
+ * @since 4.0
7
+ */
8
+
9
+ namespace Yoast\WP\Duplicate_Post;
10
+
11
+ /**
12
+ * Represents the Permissions_Helper class.
13
+ */
14
+ class Permissions_Helper {
15
+
16
+ /**
17
+ * Returns the array of the enabled post types.
18
+ *
19
+ * @return array The array of post types.
20
+ */
21
+ public function get_enabled_post_types() {
22
+ $duplicate_post_types_enabled = \get_option( 'duplicate_post_types_enabled', [ 'post', 'page' ] );
23
+ if ( ! \is_array( $duplicate_post_types_enabled ) ) {
24
+ $duplicate_post_types_enabled = [ $duplicate_post_types_enabled ];
25
+ }
26
+
27
+ return $duplicate_post_types_enabled;
28
+ }
29
+
30
+ /**
31
+ * Determines if post type is enabled to be copied.
32
+ *
33
+ * @param string $post_type The post type to check.
34
+ *
35
+ * @return bool Whether the post type is enabled to be copied.
36
+ */
37
+ public function is_post_type_enabled( $post_type ) {
38
+ return \in_array( $post_type, $this->get_enabled_post_types(), true );
39
+ }
40
+
41
+ /**
42
+ * Determines if the current user can copy posts.
43
+ *
44
+ * @return bool Whether the current user can copy posts.
45
+ */
46
+ public function is_current_user_allowed_to_copy() {
47
+ return \current_user_can( 'copy_posts' );
48
+ }
49
+
50
+ /**
51
+ * Determines if the post is a copy intended for Rewrite & Republish.
52
+ *
53
+ * @param \WP_Post $post The post object.
54
+ *
55
+ * @return bool Whether the post is a copy intended for Rewrite & Republish.
56
+ */
57
+ public function is_rewrite_and_republish_copy( \WP_Post $post ) {
58
+ return ( \intval( \get_post_meta( $post->ID, '_dp_is_rewrite_republish_copy', true ) ) === 1 );
59
+ }
60
+
61
+ /**
62
+ * Gets the Rewrite & Republish copy ID for the passed post.
63
+ *
64
+ * @param \WP_Post $post The post object.
65
+ *
66
+ * @return int The Rewrite & Republish copy ID.
67
+ */
68
+ public function get_rewrite_and_republish_copy_id( \WP_Post $post ) {
69
+ return \get_post_meta( $post->ID, '_dp_has_rewrite_republish_copy', true );
70
+ }
71
+
72
+ /**
73
+ * Gets the copy post object for the passed post.
74
+ *
75
+ * @param \WP_Post $post The post to get the copy for.
76
+ *
77
+ * @return \WP_Post|null The copy's post object or null if it doesn't exist.
78
+ */
79
+ public function get_rewrite_and_republish_copy( \WP_Post $post ) {
80
+ $copy_id = $this->get_rewrite_and_republish_copy_id( $post );
81
+
82
+ if ( empty( $copy_id ) ) {
83
+ return null;
84
+ }
85
+
86
+ return \get_post( $copy_id );
87
+ }
88
+
89
+ /**
90
+ * Determines if the post has a copy intended for Rewrite & Republish.
91
+ *
92
+ * @param \WP_Post $post The post object.
93
+ *
94
+ * @return bool Whether the post has a copy intended for Rewrite & Republish.
95
+ */
96
+ public function has_rewrite_and_republish_copy( \WP_Post $post ) {
97
+ return ( ! empty( $this->get_rewrite_and_republish_copy_id( $post ) ) );
98
+ }
99
+
100
+ /**
101
+ * Determines if the post has a copy intended for Rewrite & Republish which is scheduled to be published.
102
+ *
103
+ * @param \WP_Post $post The post object.
104
+ *
105
+ * @return bool|\WP_Post The scheduled copy if present, false if the post has no scheduled copy.
106
+ */
107
+ public function has_scheduled_rewrite_and_republish_copy( \WP_Post $post ) {
108
+ $copy = $this->get_rewrite_and_republish_copy( $post );
109
+
110
+ if ( ! empty( $copy ) && $copy->post_status === 'future' ) {
111
+ return $copy;
112
+ }
113
+
114
+ return false;
115
+ }
116
+
117
+ /**
118
+ * Determines whether the current screen is an edit post screen.
119
+ *
120
+ * @return bool Whether or not the current screen is editing an existing post.
121
+ */
122
+ public function is_edit_post_screen() {
123
+ if ( ! \is_admin() ) {
124
+ return false;
125
+ }
126
+
127
+ $current_screen = \get_current_screen();
128
+
129
+ return $current_screen->base === 'post' && $current_screen->action !== 'add';
130
+ }
131
+
132
+ /**
133
+ * Determines whether the current screen is an new post screen.
134
+ *
135
+ * @return bool Whether or not the current screen is editing an new post.
136
+ */
137
+ public function is_new_post_screen() {
138
+ if ( ! \is_admin() ) {
139
+ return false;
140
+ }
141
+
142
+ $current_screen = \get_current_screen();
143
+
144
+ return $current_screen->base === 'post' && $current_screen->action === 'add';
145
+ }
146
+
147
+ /**
148
+ * Determines if we are currently editing a post with Classic editor.
149
+ *
150
+ * @return bool Whether we are currently editing a post with Classic editor.
151
+ */
152
+ public function is_classic_editor() {
153
+ if ( ! $this->is_edit_post_screen() && ! $this->is_new_post_screen() ) {
154
+ return false;
155
+ }
156
+
157
+ $screen = \get_current_screen();
158
+ if ( $screen->is_block_editor() ) {
159
+ return false;
160
+ }
161
+
162
+ return true;
163
+ }
164
+
165
+ /**
166
+ * Determines if the original post has changed since the creation of the copy.
167
+ *
168
+ * @param \WP_Post $post The post object.
169
+ *
170
+ * @return bool Whether the original post has changed since the creation of the copy.
171
+ */
172
+ public function has_original_changed( \WP_Post $post ) {
173
+ if ( ! $this->is_rewrite_and_republish_copy( $post ) ) {
174
+ return false;
175
+ }
176
+
177
+ $original = Utils::get_original( $post );
178
+ $copy_creation_date_gmt = \get_post_meta( $post->ID, '_dp_creation_date_gmt', true );
179
+
180
+ if ( $original && $copy_creation_date_gmt ) {
181
+ if ( \strtotime( $original->post_modified_gmt ) > \strtotime( $copy_creation_date_gmt ) ) {
182
+ return true;
183
+ }
184
+ }
185
+
186
+ return false;
187
+ }
188
+
189
+ /**
190
+ * Determines if duplicate links for the post can be displayed.
191
+ *
192
+ * @param \WP_Post $post The post object.
193
+ *
194
+ * @return bool Whether the links can be displayed.
195
+ */
196
+ public function should_links_be_displayed( \WP_Post $post ) {
197
+ /**
198
+ * Filter allowing displaying duplicate post links for current post.
199
+ *
200
+ * @param bool $display_links Whether the duplicate links will be displayed.
201
+ * @param \WP_Post $post The post object.
202
+ *
203
+ * @return bool Whether or not to display the duplicate post links.
204
+ */
205
+ $display_links = \apply_filters( 'duplicate_post_show_link', $this->is_current_user_allowed_to_copy() && $this->is_post_type_enabled( $post->post_type ), $post );
206
+
207
+ return ! $this->is_rewrite_and_republish_copy( $post ) && $display_links;
208
+ }
209
+
210
+ /**
211
+ * Determines if the Rewrite & Republish link for the post should be displayed.
212
+ *
213
+ * @param \WP_Post $post The post object.
214
+ *
215
+ * @return bool Whether the links should be displayed.
216
+ */
217
+ public function should_rewrite_and_republish_be_allowed( \WP_Post $post ) {
218
+ return $post->post_status === 'publish'
219
+ && ! $this->is_rewrite_and_republish_copy( $post )
220
+ && ! $this->has_rewrite_and_republish_copy( $post )
221
+ && ! $this->is_elementor_active();
222
+ }
223
+
224
+ /**
225
+ * Determines whether the passed post type is public and shows an admin bar.
226
+ *
227
+ * @param string $post_type The post_type to copy.
228
+ *
229
+ * @return bool Whether or not the post can be copied to a new draft.
230
+ */
231
+ public function post_type_has_admin_bar( $post_type ) {
232
+ $post_type_object = \get_post_type_object( $post_type );
233
+
234
+ if ( empty( $post_type_object ) ) {
235
+ return false;
236
+ }
237
+
238
+ return $post_type_object->public && $post_type_object->show_in_admin_bar;
239
+ }
240
+
241
+ /**
242
+ * Determines whether a Rewrite & Republish copy can be republished.
243
+ *
244
+ * @param \WP_Post $post The post object.
245
+ *
246
+ * @return bool Whether the Rewrite & Republish copy can be republished.
247
+ */
248
+ public function is_copy_allowed_to_be_republished( \WP_Post $post ) {
249
+ return \in_array( $post->post_status, [ 'dp-rewrite-republish', 'private' ], true );
250
+ }
251
+
252
+ /**
253
+ * Determines if the post has a trashed copy intended for Rewrite & Republish.
254
+ *
255
+ * @param \WP_Post $post The post object.
256
+ *
257
+ * @return bool Whether the post has a trashed copy intended for Rewrite & Republish.
258
+ */
259
+ public function has_trashed_rewrite_and_republish_copy( \WP_Post $post ) {
260
+ $copy_id = \get_post_meta( $post->ID, '_dp_has_rewrite_republish_copy', true );
261
+
262
+ if ( ! $copy_id ) {
263
+ return false;
264
+ }
265
+
266
+ $copy = \get_post( $copy_id );
267
+
268
+ if ( $copy && $copy->post_status === 'trash' ) {
269
+ return true;
270
+ }
271
+
272
+ return false;
273
+ }
274
+
275
+ /**
276
+ * Determines if the Elementor plugin is active.
277
+ *
278
+ * We can't use is_plugin_active because this must be working on front end too.
279
+ *
280
+ * @return bool Whether the Elementor plugin is currently active.
281
+ */
282
+ public function is_elementor_active() {
283
+ $plugin = 'elementor/elementor.php';
284
+
285
+ if ( \in_array( $plugin, (array) \get_option( 'active_plugins', [] ), true ) ) {
286
+ return true;
287
+ }
288
+
289
+ if ( ! \is_multisite() ) {
290
+ return false;
291
+ }
292
+
293
+ $plugins = \get_site_option( 'active_sitewide_plugins' );
294
+ return isset( $plugins[ $plugin ] );
295
+ }
296
+ }
src/class-post-duplicator.php ADDED
@@ -0,0 +1,384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to create copies.
4
+ *
5
+ * @package Duplicate_Post
6
+ * @since 4.0
7
+ */
8
+
9
+ namespace Yoast\WP\Duplicate_Post;
10
+
11
+ /**
12
+ * Represents the Post Duplicator class.
13
+ */
14
+ class Post_Duplicator {
15
+
16
+ /**
17
+ * Returns an array with the default option values.
18
+ *
19
+ * @return array The default options values.
20
+ */
21
+ public function get_default_options() {
22
+ return [
23
+ 'copy_title' => true,
24
+ 'copy_date' => false,
25
+ 'copy_status' => false,
26
+ 'copy_name' => false,
27
+ 'copy_excerpt' => true,
28
+ 'copy_content' => true,
29
+ 'copy_thumbnail' => true,
30
+ 'copy_template' => true,
31
+ 'copy_format' => true,
32
+ 'copy_author' => false,
33
+ 'copy_password' => false,
34
+ 'copy_attachments' => false,
35
+ 'copy_children' => false,
36
+ 'copy_comments' => false,
37
+ 'copy_menu_order' => true,
38
+ 'title_prefix' => '',
39
+ 'title_suffix' => '',
40
+ 'increase_menu_order_by' => null,
41
+ 'parent_id' => null,
42
+ 'meta_excludelist' => [],
43
+ 'taxonomies_excludelist' => [],
44
+ 'use_filters' => true,
45
+ ];
46
+ }
47
+
48
+ /**
49
+ * Creates a copy of a post object, accordingly to an options array.
50
+ *
51
+ * @param \WP_Post $post The original post object.
52
+ * @param array $options The options overriding the default ones.
53
+ *
54
+ * @return int|\WP_Error The copy ID, or a WP_Error object on failure.
55
+ */
56
+ public function create_duplicate( \WP_Post $post, array $options = [] ) {
57
+ $defaults = $this->get_default_options();
58
+ $options = \wp_parse_args( $options, $defaults );
59
+
60
+ $title = '';
61
+ $new_post_status = $post->post_status;
62
+ if ( 'attachment' !== $post->post_type ) {
63
+ $title = $this->generate_copy_title( $post, $options );
64
+ $new_post_status = $this->generate_copy_status( $post, $options );
65
+ }
66
+
67
+ $new_post_author_id = $this->generate_copy_author( $post, $options );
68
+
69
+ $menu_order = $options['copy_menu_order'] ? $post->menu_order : 0;
70
+ if ( ! empty( $options['increase_menu_order_by'] ) && \is_numeric( $options['increase_menu_order_by'] ) ) {
71
+ $menu_order += \intval( $options['increase_menu_order_by'] );
72
+ }
73
+
74
+ $new_post = [
75
+ 'post_author' => $new_post_author_id,
76
+ 'post_content' => $options['copy_content'] ? $post->post_content : '',
77
+ 'post_content_filtered' => $options['copy_content'] ? $post->post_content_filtered : '',
78
+ 'post_title' => $title,
79
+ 'post_excerpt' => $options['copy_excerpt'] ? $post->post_excerpt : '',
80
+ 'post_status' => $new_post_status,
81
+ 'post_type' => $post->post_type,
82
+ 'comment_status' => $post->comment_status,
83
+ 'ping_status' => $post->ping_status,
84
+ 'post_password' => $options['copy_password'] ? $post->post_password : '',
85
+ 'post_name' => $options['copy_name'] ? $post->post_name : '',
86
+ 'post_parent' => empty( $options['parent_id'] ) ? $post->post_parent : $options['parent_id'],
87
+ 'menu_order' => $menu_order,
88
+ 'post_mime_type' => $post->post_mime_type,
89
+ ];
90
+
91
+ if ( $options['copy_date'] ) {
92
+ $new_post_date = $post->post_date;
93
+ $new_post['post_date'] = $new_post_date;
94
+ $new_post['post_date_gmt'] = \get_gmt_from_date( $new_post_date );
95
+ \add_filter( 'wp_insert_post_data', [ $this, 'set_modified' ], 1, 1 );
96
+ }
97
+
98
+ if ( $options['use_filters'] ) {
99
+ /**
100
+ * Filter new post values.
101
+ *
102
+ * @param array $new_post New post values.
103
+ * @param \WP_Post $post Original post object.
104
+ *
105
+ * @return array
106
+ */
107
+ $new_post = \apply_filters( 'duplicate_post_new_post', $new_post, $post );
108
+ }
109
+
110
+ $new_post_id = \wp_insert_post( \wp_slash( $new_post ), true );
111
+
112
+ if ( $options['copy_date'] ) {
113
+ \remove_filter( 'wp_insert_post_data', [ $this, 'set_modified' ], 1 );
114
+ }
115
+
116
+ if ( ! \is_wp_error( $new_post_id ) ) {
117
+ \delete_post_meta( $new_post_id, '_dp_original' );
118
+ \add_post_meta( $new_post_id, '_dp_original', $post->ID );
119
+ }
120
+
121
+ return $new_post_id;
122
+ }
123
+
124
+ /**
125
+ * Modifies the post data to set the modified date to now.
126
+ *
127
+ * This is needed for the Block editor when a post is copied with its date,
128
+ * so that the current publish date is shown instead of "Immediately".
129
+ *
130
+ * @param array $data The array of post data.
131
+ *
132
+ * @return array The updated array of post data.
133
+ */
134
+ public function set_modified( $data ) {
135
+ $data['post_modified'] = \current_time( 'mysql' );
136
+ $data['post_modified_gmt'] = \current_time( 'mysql', 1 );
137
+
138
+ return $data;
139
+ }
140
+
141
+ /**
142
+ * Wraps the function to create a copy for the Rewrite & Republish feature.
143
+ *
144
+ * @param \WP_Post $post The original post object.
145
+ *
146
+ * @return int|\WP_Error The copy ID, or a WP_Error object on failure.
147
+ */
148
+ public function create_duplicate_for_rewrite_and_republish( \WP_Post $post ) {
149
+ $options = [
150
+ 'copy_title' => true,
151
+ 'copy_date' => true,
152
+ 'copy_name' => false,
153
+ 'copy_content' => true,
154
+ 'copy_excerpt' => true,
155
+ 'copy_author' => true,
156
+ 'copy_menu_order' => true,
157
+ 'use_filters' => false,
158
+ ];
159
+ $defaults = $this->get_default_options();
160
+ $options = \wp_parse_args( $options, $defaults );
161
+
162
+ $new_post_id = $this->create_duplicate( $post, $options );
163
+
164
+ if ( ! \is_wp_error( $new_post_id ) ) {
165
+ $this->copy_post_taxonomies( $new_post_id, $post, $options );
166
+ $this->copy_post_meta_info( $new_post_id, $post, $options );
167
+
168
+ \update_post_meta( $new_post_id, '_dp_is_rewrite_republish_copy', 1 );
169
+ \update_post_meta( $post->ID, '_dp_has_rewrite_republish_copy', $new_post_id );
170
+ \update_post_meta( $new_post_id, '_dp_creation_date_gmt', \current_time( 'mysql', 1 ) );
171
+ }
172
+
173
+ return $new_post_id;
174
+ }
175
+
176
+ /**
177
+ * Copies the taxonomies of a post to another post.
178
+ *
179
+ * @param int $new_id New post ID.
180
+ * @param \WP_Post $post The original post object.
181
+ * @param array $options The options array.
182
+ *
183
+ * @return void
184
+ */
185
+ public function copy_post_taxonomies( $new_id, $post, $options ) {
186
+ // Clear default category (added by wp_insert_post).
187
+ \wp_set_object_terms( $new_id, null, 'category' );
188
+
189
+ $post_taxonomies = \get_object_taxonomies( $post->post_type );
190
+ // Several plugins just add support to post-formats but don't register post_format taxonomy.
191
+ if ( \post_type_supports( $post->post_type, 'post-formats' ) && ! \in_array( 'post_format', $post_taxonomies, true ) ) {
192
+ $post_taxonomies[] = 'post_format';
193
+ }
194
+
195
+ $taxonomies_excludelist = $options['taxonomies_excludelist'];
196
+ if ( ! \is_array( $taxonomies_excludelist ) ) {
197
+ $taxonomies_excludelist = [];
198
+ }
199
+
200
+ if ( ! $options['copy_format'] ) {
201
+ $taxonomies_excludelist[] = 'post_format';
202
+ }
203
+
204
+ if ( $options['use_filters'] ) {
205
+ /**
206
+ * Filters the taxonomy excludelist when copying a post.
207
+ *
208
+ * @param array $taxonomies_excludelist The taxonomy excludelist from the options.
209
+ *
210
+ * @return array
211
+ */
212
+ $taxonomies_excludelist = \apply_filters( 'duplicate_post_taxonomies_excludelist_filter', $taxonomies_excludelist );
213
+ }
214
+
215
+ $post_taxonomies = \array_diff( $post_taxonomies, $taxonomies_excludelist );
216
+
217
+ foreach ( $post_taxonomies as $taxonomy ) {
218
+ $post_terms = \wp_get_object_terms( $post->ID, $taxonomy, [ 'orderby' => 'term_order' ] );
219
+ $terms = [];
220
+ $num_terms = \count( $post_terms );
221
+ for ( $i = 0; $i < $num_terms; $i++ ) {
222
+ $terms[] = $post_terms[ $i ]->slug;
223
+ }
224
+ \wp_set_object_terms( $new_id, $terms, $taxonomy );
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Copies the meta information of a post to another post.
230
+ *
231
+ * @param int $new_id The new post ID.
232
+ * @param \WP_Post $post The original post object.
233
+ * @param array $options The options array.
234
+ *
235
+ * @return void
236
+ */
237
+ public function copy_post_meta_info( $new_id, $post, $options ) {
238
+ $post_meta_keys = \get_post_custom_keys( $post->ID );
239
+ if ( empty( $post_meta_keys ) ) {
240
+ return;
241
+ }
242
+ $meta_excludelist = $options['meta_excludelist'];
243
+ if ( ! \is_array( $meta_excludelist ) ) {
244
+ $meta_excludelist = [];
245
+ }
246
+ $meta_excludelist[] = '_edit_lock'; // Edit lock.
247
+ $meta_excludelist[] = '_edit_last'; // Edit lock.
248
+ $meta_excludelist[] = '_dp_original';
249
+ $meta_excludelist[] = '_dp_is_rewrite_republish_copy';
250
+ $meta_excludelist[] = '_dp_has_rewrite_republish_copy';
251
+ if ( ! $options['copy_template'] ) {
252
+ $meta_excludelist[] = '_wp_page_template';
253
+ }
254
+ if ( ! $options['copy_thumbnail'] ) {
255
+ $meta_excludelist[] = '_thumbnail_id';
256
+ }
257
+
258
+ if ( $options['use_filters'] ) {
259
+ /**
260
+ * Filters the meta fields excludelist when copying a post.
261
+ *
262
+ * @param array $meta_excludelist The meta fields excludelist from the options.
263
+ *
264
+ * @return array
265
+ */
266
+ $meta_excludelist = \apply_filters( 'duplicate_post_excludelist_filter', $meta_excludelist );
267
+ }
268
+
269
+ $meta_excludelist_string = '(' . \implode( ')|(', $meta_excludelist ) . ')';
270
+ if ( \strpos( $meta_excludelist_string, '*' ) !== false ) {
271
+ $meta_excludelist_string = \str_replace( [ '*' ], [ '[a-zA-Z0-9_]*' ], $meta_excludelist_string );
272
+
273
+ $meta_keys = [];
274
+ foreach ( $post_meta_keys as $meta_key ) {
275
+ if ( ! \preg_match( '#^' . $meta_excludelist_string . '$#', $meta_key ) ) {
276
+ $meta_keys[] = $meta_key;
277
+ }
278
+ }
279
+ } else {
280
+ $meta_keys = \array_diff( $post_meta_keys, $meta_excludelist );
281
+ }
282
+
283
+ if ( $options['use_filters'] ) {
284
+ /**
285
+ * Filters the list of meta fields names when copying a post.
286
+ *
287
+ * @param array $meta_keys The list of meta fields name, with the ones in the excludelist already removed.
288
+ *
289
+ * @return array
290
+ */
291
+ $meta_keys = \apply_filters( 'duplicate_post_meta_keys_filter', $meta_keys );
292
+ }
293
+
294
+ foreach ( $meta_keys as $meta_key ) {
295
+ $meta_values = \get_post_custom_values( $meta_key, $post->ID );
296
+ foreach ( $meta_values as $meta_value ) {
297
+ $meta_value = \maybe_unserialize( $meta_value );
298
+ \update_post_meta( $new_id, $meta_key, Utils::recursively_slash_strings( $meta_value ) );
299
+ }
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Generates and returns the title for the copy.
305
+ *
306
+ * @param \WP_Post $post The original post object.
307
+ * @param array $options The options array.
308
+ *
309
+ * @return string The calculated title for the copy.
310
+ */
311
+ public function generate_copy_title( \WP_Post $post, array $options ) {
312
+ $prefix = \sanitize_text_field( $options['title_prefix'] );
313
+ $suffix = \sanitize_text_field( $options['title_suffix'] );
314
+ if ( $options['copy_title'] ) {
315
+ $title = $post->post_title;
316
+ if ( ! empty( $prefix ) ) {
317
+ $prefix .= ' ';
318
+ }
319
+ if ( ! empty( $suffix ) ) {
320
+ $suffix = ' ' . $suffix;
321
+ }
322
+ } else {
323
+ $title = '';
324
+ }
325
+ return \trim( $prefix . $title . $suffix );
326
+ }
327
+
328
+ /**
329
+ * Generates and returns the status for the copy.
330
+ *
331
+ * @param \WP_Post $post The original post object.
332
+ * @param array $options The options array.
333
+ *
334
+ * @return string The calculated status for the copy.
335
+ */
336
+ public function generate_copy_status( \WP_Post $post, array $options ) {
337
+ $new_post_status = 'draft';
338
+
339
+ if ( $options['copy_status'] ) {
340
+ $new_post_status = $post->post_status;
341
+ if ( $new_post_status === 'publish' || $new_post_status === 'future' ) {
342
+ // check if the user has the right capability.
343
+ if ( \is_post_type_hierarchical( $post->post_type ) ) {
344
+ if ( ! \current_user_can( 'publish_pages' ) ) {
345
+ $new_post_status = 'pending';
346
+ }
347
+ } else {
348
+ if ( ! \current_user_can( 'publish_posts' ) ) {
349
+ $new_post_status = 'pending';
350
+ }
351
+ }
352
+ }
353
+ }
354
+
355
+ return $new_post_status;
356
+ }
357
+
358
+ /**
359
+ * Generates and returns the author ID for the copy.
360
+ *
361
+ * @param \WP_Post $post The original post object.
362
+ * @param array $options The options array.
363
+ *
364
+ * @return int|string The calculated author ID for the copy.
365
+ */
366
+ public function generate_copy_author( \WP_Post $post, array $options ) {
367
+ $new_post_author = \wp_get_current_user();
368
+ $new_post_author_id = $new_post_author->ID;
369
+ if ( $options['copy_author'] ) {
370
+ // check if the user has the right capability.
371
+ if ( \is_post_type_hierarchical( $post->post_type ) ) {
372
+ if ( \current_user_can( 'edit_others_pages' ) ) {
373
+ $new_post_author_id = $post->post_author;
374
+ }
375
+ } else {
376
+ if ( \current_user_can( 'edit_others_posts' ) ) {
377
+ $new_post_author_id = $post->post_author;
378
+ }
379
+ }
380
+ }
381
+
382
+ return $new_post_author_id;
383
+ }
384
+ }
src/class-post-republisher.php ADDED
@@ -0,0 +1,425 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to republish a rewritten post.
4
+ *
5
+ * @package Duplicate_Post
6
+ * @since 4.0
7
+ */
8
+
9
+ namespace Yoast\WP\Duplicate_Post;
10
+
11
+ /**
12
+ * Represents the Post Republisher class.
13
+ */
14
+ class Post_Republisher {
15
+
16
+ /**
17
+ * Post_Duplicator object.
18
+ *
19
+ * @var Post_Duplicator
20
+ */
21
+ protected $post_duplicator;
22
+
23
+ /**
24
+ * Holds the permissions helper.
25
+ *
26
+ * @var Permissions_Helper
27
+ */
28
+ protected $permissions_helper;
29
+
30
+ /**
31
+ * Initializes the class.
32
+ *
33
+ * @param Post_Duplicator $post_duplicator The Post_Duplicator object.
34
+ * @param Permissions_Helper $permissions_helper The Permissions Helper object.
35
+ */
36
+ public function __construct( Post_Duplicator $post_duplicator, Permissions_Helper $permissions_helper ) {
37
+ $this->post_duplicator = $post_duplicator;
38
+ $this->permissions_helper = $permissions_helper;
39
+ }
40
+
41
+ /**
42
+ * Adds hooks to integrate with WordPress.
43
+ *
44
+ * @return void
45
+ */
46
+ public function register_hooks() {
47
+ \add_action( 'init', [ $this, 'register_post_statuses' ] );
48
+ \add_filter( 'wp_insert_post_data', [ $this, 'change_post_copy_status' ], 1, 2 );
49
+
50
+ $enabled_post_types = $this->permissions_helper->get_enabled_post_types();
51
+ foreach ( $enabled_post_types as $enabled_post_type ) {
52
+ /**
53
+ * Called in the REST API when submitting the post copy in the Block Editor.
54
+ * Runs the republishing of the copy onto the original.
55
+ */
56
+ \add_action( "rest_after_insert_{$enabled_post_type}", [ $this, 'republish_after_rest_api_request' ] );
57
+ }
58
+
59
+ /**
60
+ * Called by `wp_insert_post()` when submitting the post copy, which runs in two cases:
61
+ * - In the Classic Editor, where there's only one request that updates everything.
62
+ * - In the Block Editor, only when there are custom meta boxes.
63
+ */
64
+ \add_action( 'wp_insert_post', [ $this, 'republish_after_post_request' ], \PHP_INT_MAX, 2 );
65
+
66
+ // Clean up after the redirect to the original post.
67
+ \add_action( 'load-post.php', [ $this, 'clean_up_after_redirect' ] );
68
+ // Clean up the original when the copy is manually deleted from the trash.
69
+ \add_action( 'before_delete_post', [ $this, 'clean_up_when_copy_manually_deleted' ] );
70
+ // Ensure scheduled Rewrite and Republish posts are properly handled.
71
+ \add_action( 'future_to_publish', [ $this, 'republish_scheduled_post' ] );
72
+ }
73
+
74
+ /**
75
+ * Adds custom post statuses.
76
+ *
77
+ * These post statuses are meant for internal use. However, we can't use the
78
+ * `internal` status because the REST API posts controller allows all registered
79
+ * statuses but the `internal` one.
80
+ *
81
+ * @return void
82
+ */
83
+ public function register_post_statuses() {
84
+ $options = [
85
+ 'label' => \__( 'Republish', 'duplicate-post' ),
86
+ 'public' => true,
87
+ 'exclude_from_search' => false,
88
+ 'show_in_admin_all_list' => false,
89
+ 'show_in_admin_status_list' => false,
90
+ ];
91
+
92
+ \register_post_status( 'dp-rewrite-republish', $options );
93
+ }
94
+
95
+ /**
96
+ * Changes the post copy status.
97
+ *
98
+ * Runs on the `wp_insert_post_data` hook in `wp_insert_post()` when
99
+ * submitting the post copy.
100
+ *
101
+ * @param array $data An array of slashed, sanitized, and processed post data.
102
+ * @param array $postarr An array of sanitized (and slashed) but otherwise unmodified post data.
103
+ *
104
+ * @return array An array of slashed, sanitized, and processed attachment post data.
105
+ */
106
+ public function change_post_copy_status( $data, $postarr ) {
107
+ $post = \get_post( $postarr['ID'] );
108
+
109
+ if ( ! $post || ! $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) {
110
+ return $data;
111
+ }
112
+
113
+ if ( $data['post_status'] === 'publish' ) {
114
+ $data['post_status'] = 'dp-rewrite-republish';
115
+ }
116
+
117
+ return $data;
118
+ }
119
+
120
+ /**
121
+ * Executes the republish request.
122
+ *
123
+ * @param \WP_Post $post The copy's post object.
124
+ *
125
+ * @return void
126
+ */
127
+ public function republish_request( $post ) {
128
+ if (
129
+ ! $this->permissions_helper->is_rewrite_and_republish_copy( $post )
130
+ || ! $this->permissions_helper->is_copy_allowed_to_be_republished( $post )
131
+ ) {
132
+ return;
133
+ }
134
+
135
+ $original_post = Utils::get_original( $post->ID );
136
+
137
+ if ( ! $original_post ) {
138
+ return;
139
+ }
140
+
141
+ $this->republish( $post, $original_post );
142
+
143
+ // Trigger the redirect in the Classic Editor.
144
+ if ( $this->is_classic_editor_post_request() ) {
145
+ $this->redirect( $original_post->ID, $post->ID );
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Republishes the original post with the passed post, when using the Block Editor.
151
+ *
152
+ * @param \WP_Post $post The copy's post object.
153
+ *
154
+ * @return void
155
+ */
156
+ public function republish_after_rest_api_request( $post ) {
157
+ $this->republish_request( $post );
158
+ }
159
+
160
+ /**
161
+ * Republishes the original post with the passed post, when using the Classic Editor.
162
+ *
163
+ * Runs also in the Block Editor to save the custom meta data only when there
164
+ * are custom meta boxes.
165
+ *
166
+ * @param int $post_id The copy's post ID.
167
+ * @param \WP_Post $post The copy's post object.
168
+ *
169
+ * @return void
170
+ */
171
+ public function republish_after_post_request( $post_id, $post ) {
172
+ if ( $this->is_rest_request() ) {
173
+ return;
174
+ }
175
+
176
+ $this->republish_request( $post );
177
+ }
178
+
179
+ /**
180
+ * Republishes the scheduled Rewrited and Republish post.
181
+ *
182
+ * @param \WP_Post $copy The scheduled copy.
183
+ *
184
+ * @return void
185
+ */
186
+ public function republish_scheduled_post( \WP_Post $copy ) {
187
+ if ( ! $this->permissions_helper->is_rewrite_and_republish_copy( $copy ) ) {
188
+ return;
189
+ }
190
+
191
+ $original_post = Utils::get_original( $copy->ID );
192
+
193
+ // If the original post was permanently deleted, we don't want to republish, so trash instead.
194
+ if ( ! $original_post ) {
195
+ $this->delete_copy( $copy->ID, null, false );
196
+
197
+ return;
198
+ }
199
+
200
+ $this->republish( $copy, $original_post );
201
+ $this->delete_copy( $copy->ID, $original_post->ID );
202
+ }
203
+
204
+ /**
205
+ * Cleans up the copied post and temporary metadata after the user has been redirected.
206
+ *
207
+ * @return void
208
+ */
209
+ public function clean_up_after_redirect() {
210
+ if ( ! empty( $_GET['dprepublished'] ) && ! empty( $_GET['dpcopy'] ) && ! empty( $_GET['post'] ) ) {
211
+ $copy_id = \intval( \wp_unslash( $_GET['dpcopy'] ) );
212
+ $post_id = \intval( \wp_unslash( $_GET['post'] ) );
213
+
214
+ \check_admin_referer( 'dp-republish', 'dpnonce' );
215
+
216
+ $this->delete_copy( $copy_id, $post_id );
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Checks whether a request is the Classic Editor POST request.
222
+ *
223
+ * @return bool Whether the request is the Classic Editor POST request.
224
+ */
225
+ public function is_classic_editor_post_request() {
226
+ if ( $this->is_rest_request() ) {
227
+ return false;
228
+ }
229
+
230
+ return isset( $_GET['meta-box-loader'] ) === false; // phpcs:ignore WordPress.Security.NonceVerification
231
+ }
232
+
233
+ /**
234
+ * Determines whether the current request is a REST request.
235
+ *
236
+ * @return bool Whether or not the request is a REST request.
237
+ */
238
+ public function is_rest_request() {
239
+ return defined( 'REST_REQUEST' ) && REST_REQUEST;
240
+ }
241
+
242
+ /**
243
+ * Republishes the post by overwriting the original post.
244
+ *
245
+ * @param \WP_Post $post The Rewrite & Republish copy.
246
+ * @param \WP_Post $original_post The original post.
247
+ *
248
+ * @return void
249
+ */
250
+ public function republish( \WP_Post $post, $original_post ) {
251
+ // Remove WordPress default filter so a new revision is not created on republish.
252
+ \remove_action( 'post_updated', 'wp_save_post_revision', 10 );
253
+
254
+ // Republish taxonomies and meta.
255
+ $this->republish_post_taxonomies( $post );
256
+ $this->republish_post_meta( $post );
257
+
258
+ // Republish the post.
259
+ $this->republish_post_elements( $post, $original_post );
260
+
261
+ // Re-enable the creation of a new revision.
262
+ \add_action( 'post_updated', 'wp_save_post_revision', 10, 1 );
263
+ }
264
+
265
+ /**
266
+ * Deletes the copy and associated post meta, if applicable.
267
+ *
268
+ * @param int $copy_id The copy's ID.
269
+ * @param int|null $post_id The original post's ID. Optional.
270
+ * @param bool $permanently_delete Whether to permanently delete the copy. Defaults to true.
271
+ *
272
+ * @return void
273
+ */
274
+ public function delete_copy( $copy_id, $post_id = null, $permanently_delete = true ) {
275
+ /**
276
+ * Fires before deleting a Rewrite & Republish copy.
277
+ *
278
+ * @param int $copy_id The copy's ID.
279
+ * @param int $post_id The original post's ID..
280
+ */
281
+ \do_action( 'duplicate_post_after_rewriting', $copy_id, $post_id );
282
+
283
+ // Delete the copy bypassing the trash so it also deletes the copy post meta.
284
+ \wp_delete_post( $copy_id, $permanently_delete );
285
+
286
+ if ( ! \is_null( $post_id ) ) {
287
+ // Delete the meta that marks the original post has having a copy.
288
+ \delete_post_meta( $post_id, '_dp_has_rewrite_republish_copy' );
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Republishes the post elements overwriting the original post.
294
+ *
295
+ * @param \WP_Post $post The post object.
296
+ * @param \WP_Post $original_post The original post.
297
+ *
298
+ * @return void
299
+ */
300
+ protected function republish_post_elements( $post, $original_post ) {
301
+ // Cast to array and not alter the copy's original object.
302
+ $post_to_be_rewritten = clone $post;
303
+
304
+ // Prepare post data for republishing.
305
+ $post_to_be_rewritten->ID = $original_post->ID;
306
+ $post_to_be_rewritten->post_name = $original_post->post_name;
307
+ $post_to_be_rewritten->post_status = $this->determine_post_status( $post, $original_post );
308
+
309
+ /**
310
+ * Yoast SEO and other plugins prevent from accidentally updating another post's
311
+ * data (e.g. the Yoast SEO metadata by checking the $_POST data ID with the post object ID.
312
+ * We need to overwrite the $_POST data ID to allow updating the original post.
313
+ */
314
+ $_POST['ID'] = $original_post->ID;
315
+
316
+ // Republish the original post.
317
+ $rewritten_post_id = \wp_update_post( \wp_slash( $post_to_be_rewritten ) );
318
+
319
+ if ( $rewritten_post_id === 0 ) {
320
+ \wp_die( \esc_html__( 'An error occurred while republishing the post.', 'duplicate-post' ) );
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Republishes the post taxonomies overwriting the ones of the original post.
326
+ *
327
+ * @param \WP_Post $post The copy's post object.
328
+ *
329
+ * @return void
330
+ */
331
+ protected function republish_post_taxonomies( $post ) {
332
+ $original_post_id = Utils::get_original_post_id( $post->ID );
333
+
334
+ $copy_taxonomies_options = [
335
+ 'taxonomies_excludelist' => [],
336
+ 'use_filters' => false,
337
+ 'copy_format' => true,
338
+ ];
339
+ $this->post_duplicator->copy_post_taxonomies( $original_post_id, $post, $copy_taxonomies_options );
340
+ }
341
+
342
+ /**
343
+ * Republishes the post meta overwriting the ones of the original post.
344
+ *
345
+ * @param \WP_Post $post The copy's post object.
346
+ *
347
+ * @return void
348
+ */
349
+ protected function republish_post_meta( $post ) {
350
+ $original_post_id = Utils::get_original_post_id( $post->ID );
351
+
352
+ $copy_meta_options = [
353
+ 'meta_excludelist' => [
354
+ '_edit_lock',
355
+ '_edit_last',
356
+ '_dp_original',
357
+ '_dp_is_rewrite_republish_copy',
358
+ ],
359
+ 'use_filters' => false,
360
+ 'copy_thumbnail' => true,
361
+ 'copy_template' => true,
362
+ ];
363
+ $this->post_duplicator->copy_post_meta_info( $original_post_id, $post, $copy_meta_options );
364
+ }
365
+
366
+ /**
367
+ * Redirects the user to the original post.
368
+ *
369
+ * @param int $original_post_id The ID of the original post to redirect to.
370
+ * @param int $copy_id The ID of the copy post.
371
+ *
372
+ * @return void
373
+ */
374
+ protected function redirect( $original_post_id, $copy_id ) {
375
+ \wp_safe_redirect(
376
+ \add_query_arg(
377
+ [
378
+ 'dprepublished' => 1,
379
+ 'dpcopy' => $copy_id,
380
+ 'dpnonce' => \wp_create_nonce( 'dp-republish' ),
381
+ ],
382
+ \admin_url( 'post.php?action=edit&post=' . $original_post_id )
383
+ )
384
+ );
385
+ exit();
386
+ }
387
+
388
+ /**
389
+ * Determines the post status to use when publishing the Rewrite & Republish copy.
390
+ *
391
+ * @param \WP_Post $post The post object.
392
+ * @param \WP_Post $original_post The original post object.
393
+ *
394
+ * @return string The post status to use.
395
+ */
396
+ protected function determine_post_status( $post, $original_post ) {
397
+ if ( $original_post->post_status === 'trash' ) {
398
+ return 'trash';
399
+ }
400
+
401
+ if ( $post->post_status === 'private' ) {
402
+ return 'private';
403
+ }
404
+
405
+ return 'publish';
406
+ }
407
+
408
+ /**
409
+ * Deletes the original post meta that flags it as having a copy when the copy is manually deleted.
410
+ *
411
+ * @param int $post_id Post ID of a post that is going to be deleted.
412
+ *
413
+ * @return void
414
+ */
415
+ public function clean_up_when_copy_manually_deleted( $post_id ) {
416
+ $post = \get_post( $post_id );
417
+
418
+ if ( ! $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) {
419
+ return;
420
+ }
421
+
422
+ $original_post_id = Utils::get_original_post_id( $post_id );
423
+ \delete_post_meta( $original_post_id, '_dp_has_rewrite_republish_copy' );
424
+ }
425
+ }
src/class-revisions-migrator.php ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to migrate revisions from the Rewrite & Republish copy to the original post.
4
+ *
5
+ * @package Duplicate_Post
6
+ * @since 4.0
7
+ */
8
+
9
+ namespace Yoast\WP\Duplicate_Post;
10
+
11
+ /**
12
+ * Represents the Revisions Migrator class.
13
+ */
14
+ class Revisions_Migrator {
15
+
16
+ /**
17
+ * Adds hooks to integrate with the Post Republisher class.
18
+ *
19
+ * @return void
20
+ */
21
+ public function register_hooks() {
22
+ \add_action( 'duplicate_post_after_rewriting', [ $this, 'migrate_revisions' ], 10, 2 );
23
+ }
24
+
25
+ /**
26
+ * Updates the revisions of the Rewrite & Republish copy to make them revisions of the original.
27
+ *
28
+ * It mimics the behaviour of wp_save_post_revision() in wp-includes/revision.php
29
+ * by deleting the revisions (except autosaves) exceeding the maximum allowed number.
30
+ *
31
+ * @param int $copy_id The copy's ID.
32
+ * @param int $original_id The post's ID.
33
+ *
34
+ * @return void
35
+ */
36
+ public function migrate_revisions( $copy_id, $original_id ) {
37
+ $copy = \get_post( $copy_id );
38
+ $original_post = \get_post( $original_id );
39
+
40
+ if ( \is_null( $copy ) || \is_null( $original_post ) || ! \wp_revisions_enabled( $original_post ) ) {
41
+ return;
42
+ }
43
+
44
+ $copy_revisions = \wp_get_post_revisions( $copy );
45
+ foreach ( $copy_revisions as $revision ) {
46
+ $revision->post_parent = $original_post->ID;
47
+ $revision->post_name = "$original_post->ID-revision-v1";
48
+ \wp_update_post( $revision );
49
+ }
50
+
51
+ $revisions_to_keep = \wp_revisions_to_keep( $original_post );
52
+ if ( $revisions_to_keep < 0 ) {
53
+ return;
54
+ }
55
+
56
+ $revisions = \wp_get_post_revisions( $original_post, [ 'order' => 'ASC' ] );
57
+ $delete = \count( $revisions ) - $revisions_to_keep;
58
+ if ( $delete < 1 ) {
59
+ return;
60
+ }
61
+
62
+ $revisions = \array_slice( $revisions, 0, $delete );
63
+
64
+ for ( $i = 0; isset( $revisions[ $i ] ); $i ++ ) {
65
+ if ( \strpos( $revisions[ $i ]->post_name, 'autosave' ) !== false ) {
66
+ continue;
67
+ }
68
+ \wp_delete_post_revision( $revisions[ $i ]->ID );
69
+ }
70
+ }
71
+ }
src/class-utils.php ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Utility methods for Duplicate Post.
4
+ *
5
+ * @package Duplicate_Post
6
+ * @since 4.0
7
+ */
8
+
9
+ namespace Yoast\WP\Duplicate_Post;
10
+
11
+ /**
12
+ * Represents the Utils class.
13
+ */
14
+ class Utils {
15
+
16
+ /**
17
+ * Flattens a version number for use in a filename.
18
+ *
19
+ * @param string $version The original version number.
20
+ *
21
+ * @return string The flattened version number.
22
+ */
23
+ public static function flatten_version( $version ) {
24
+ $parts = \explode( '.', $version );
25
+
26
+ if ( \count( $parts ) === 2 && \preg_match( '/^\d+$/', $parts[1] ) === 1 ) {
27
+ $parts[] = '0';
28
+ }
29
+
30
+ return \implode( '', $parts );
31
+ }
32
+
33
+ /**
34
+ * Adds slashes only to strings.
35
+ *
36
+ * @param mixed $value Value to slash only if string.
37
+ *
38
+ * @return string|mixed
39
+ */
40
+ public static function addslashes_to_strings_only( $value ) {
41
+ return \is_string( $value ) ? \addslashes( $value ) : $value;
42
+ }
43
+
44
+ /**
45
+ * Replaces faulty core wp_slash().
46
+ *
47
+ * Until WP 5.5 wp_slash() recursively added slashes not just to strings in array/objects, leading to errors.
48
+ *
49
+ * @param mixed $value What to add slashes to.
50
+ *
51
+ * @return mixed
52
+ */
53
+ public static function recursively_slash_strings( $value ) {
54
+ return \map_deep( $value, [ self::class, 'addslashes_to_strings_only' ] );
55
+ }
56
+
57
+ /**
58
+ * Gets the original post.
59
+ *
60
+ * @param int|\WP_Post|null $post Optional. Post ID or Post object.
61
+ * @param string $output Optional, default is Object. Either OBJECT, ARRAY_A, or ARRAY_N.
62
+ *
63
+ * @return \WP_Post|null Post data if successful, null otherwise.
64
+ */
65
+ public static function get_original( $post = null, $output = \OBJECT ) {
66
+ $post = \get_post( $post );
67
+ if ( ! $post ) {
68
+ return null;
69
+ }
70
+
71
+ $original_id = self::get_original_post_id( $post->ID );
72
+
73
+ if ( empty( $original_id ) ) {
74
+ return null;
75
+ }
76
+
77
+ return \get_post( $original_id, $output );
78
+ }
79
+
80
+ /**
81
+ * Determines if the post has ancestors marked for copy.
82
+ *
83
+ * If we are copying children, and the post has already an ancestor marked for copy, we have to filter it out.
84
+ *
85
+ * @param \WP_Post $post The post object.
86
+ * @param array $post_ids The array of marked post IDs.
87
+ *
88
+ * @return bool Whether the post has ancestors marked for copy.
89
+ */
90
+ public static function has_ancestors_marked( $post, $post_ids ) {
91
+ $ancestors_in_array = 0;
92
+ $parent = \wp_get_post_parent_id( $post->ID );
93
+ while ( $parent ) {
94
+ if ( \in_array( $parent, $post_ids, true ) ) {
95
+ $ancestors_in_array++;
96
+ }
97
+ $parent = \wp_get_post_parent_id( $parent );
98
+ }
99
+ return ( $ancestors_in_array !== 0 );
100
+ }
101
+
102
+ /**
103
+ * Returns a link to edit, preview or view a post, in accordance to user capabilities.
104
+ *
105
+ * @param \WP_Post $post Post ID or Post object.
106
+ *
107
+ * @return string|null The link to edit, preview or view a post.
108
+ */
109
+ public static function get_edit_or_view_link( $post ) {
110
+ $post = \get_post( $post );
111
+ if ( ! $post ) {
112
+ return null;
113
+ }
114
+
115
+ $can_edit_post = \current_user_can( 'edit_post', $post->ID );
116
+ $title = \_draft_or_post_title( $post );
117
+ $post_type_object = \get_post_type_object( $post->post_type );
118
+
119
+ if ( $can_edit_post && $post->post_status !== 'trash' ) {
120
+ return \sprintf(
121
+ '<a href="%s" aria-label="%s">%s</a>',
122
+ \get_edit_post_link( $post->ID ),
123
+ /* translators: %s: post title */
124
+ \esc_attr( \sprintf( \__( 'Edit &#8220;%s&#8221;', 'default' ), $title ) ),
125
+ $title
126
+ );
127
+ } elseif ( \is_post_type_viewable( $post_type_object ) ) {
128
+ if ( \in_array( $post->post_status, [ 'pending', 'draft', 'future' ], true ) ) {
129
+ if ( $can_edit_post ) {
130
+ $preview_link = \get_preview_post_link( $post );
131
+ return \sprintf(
132
+ '<a href="%s" rel="bookmark" aria-label="%s">%s</a>',
133
+ \esc_url( $preview_link ),
134
+ /* translators: %s: post title */
135
+ \esc_attr( \sprintf( \__( 'Preview &#8220;%s&#8221;', 'default' ), $title ) ),
136
+ $title
137
+ );
138
+ }
139
+ } elseif ( $post->post_status !== 'trash' ) {
140
+ return \sprintf(
141
+ '<a href="%s" rel="bookmark" aria-label="%s">%s</a>',
142
+ \get_permalink( $post->ID ),
143
+ /* translators: %s: post title */
144
+ \esc_attr( \sprintf( \__( 'View &#8220;%s&#8221;', 'default' ), $title ) ),
145
+ $title
146
+ );
147
+ }
148
+ }
149
+ return $title;
150
+ }
151
+
152
+ /**
153
+ * Gets the ID of the original post intended to be rewritten with the copy for Rewrite & Republish.
154
+ *
155
+ * @param int $post_id The copy post ID.
156
+ *
157
+ * @return int The original post id of a copy for Rewrite & Republish.
158
+ */
159
+ public static function get_original_post_id( $post_id ) {
160
+ return (int) \get_post_meta( $post_id, '_dp_original', true );
161
+ }
162
+
163
+ /**
164
+ * Gets the registered WordPress roles.
165
+ *
166
+ * @return array The roles.
167
+ * @codeCoverageIgnore As this is a simple wrapper method for a built-in WordPress method, we don't have to test it.
168
+ */
169
+ public static function get_roles() {
170
+ global $wp_roles;
171
+
172
+ return $wp_roles->get_names();
173
+ }
174
+
175
+ /**
176
+ * Gets a Duplicate Post option from the database.
177
+ *
178
+ * @param string $option The option to get.
179
+ * @param string $key The key to retrieve, if the option is an array.
180
+ *
181
+ * @return mixed The option.
182
+ */
183
+ public static function get_option( $option, $key = '' ) {
184
+ $option = \get_option( $option );
185
+
186
+ if ( ! \is_array( $option ) || empty( $key ) ) {
187
+ return $option;
188
+ }
189
+
190
+ if ( ! \array_key_exists( $key, $option ) ) {
191
+ return '';
192
+ }
193
+
194
+ return $option[ $key ];
195
+ }
196
+ }
src/handlers/class-bulk-handler.php ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post handler class for duplication bulk actions.
4
+ *
5
+ * @package Duplicate_Post
6
+ * @since 4.0
7
+ */
8
+
9
+ namespace Yoast\WP\Duplicate_Post\Handlers;
10
+
11
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
12
+ use Yoast\WP\Duplicate_Post\Post_Duplicator;
13
+ use Yoast\WP\Duplicate_Post\Utils;
14
+
15
+ /**
16
+ * Represents the handler for duplication bulk actions.
17
+ */
18
+ class Bulk_Handler {
19
+
20
+ /**
21
+ * Post_Duplicator object.
22
+ *
23
+ * @var Post_Duplicator
24
+ */
25
+ protected $post_duplicator;
26
+
27
+ /**
28
+ * Holds the permissions helper.
29
+ *
30
+ * @var Permissions_Helper
31
+ */
32
+ protected $permissions_helper;
33
+
34
+ /**
35
+ * Initializes the class.
36
+ *
37
+ * @param Post_Duplicator $post_duplicator The Post_Duplicator object.
38
+ * @param Permissions_Helper $permissions_helper The Permissions Helper object.
39
+ */
40
+ public function __construct( Post_Duplicator $post_duplicator, Permissions_Helper $permissions_helper ) {
41
+ $this->post_duplicator = $post_duplicator;
42
+ $this->permissions_helper = $permissions_helper;
43
+ }
44
+
45
+ /**
46
+ * Adds hooks to integrate with WordPress.
47
+ *
48
+ * @return void
49
+ */
50
+ public function register_hooks() {
51
+ \add_action( 'admin_init', [ $this, 'add_bulk_handlers' ] );
52
+ }
53
+
54
+ /**
55
+ * Hooks the handler for the Rewrite & Republish action for all the selected post types.
56
+ *
57
+ * @return void
58
+ */
59
+ public function add_bulk_handlers() {
60
+ $duplicate_post_types_enabled = $this->permissions_helper->get_enabled_post_types();
61
+
62
+ foreach ( $duplicate_post_types_enabled as $duplicate_post_type_enabled ) {
63
+ \add_filter( "handle_bulk_actions-edit-{$duplicate_post_type_enabled}", [ $this, 'bulk_action_handler' ], 10, 3 );
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Handles the bulk actions.
69
+ *
70
+ * @param string $redirect_to The URL to redirect to.
71
+ * @param string $doaction The action that has been called.
72
+ * @param array $post_ids The array of marked post IDs.
73
+ *
74
+ * @return string The URL to redirect to.
75
+ */
76
+ public function bulk_action_handler( $redirect_to, $doaction, array $post_ids ) {
77
+ $redirect_to = $this->clone_bulk_action_handler( $redirect_to, $doaction, $post_ids );
78
+ return $this->rewrite_bulk_action_handler( $redirect_to, $doaction, $post_ids );
79
+ }
80
+
81
+ /**
82
+ * Handles the bulk action for the Rewrite & Republish feature.
83
+ *
84
+ * @param string $redirect_to The URL to redirect to.
85
+ * @param string $doaction The action that has been called.
86
+ * @param array $post_ids The array of marked post IDs.
87
+ *
88
+ * @return string The URL to redirect to.
89
+ */
90
+ public function rewrite_bulk_action_handler( $redirect_to, $doaction, array $post_ids ) {
91
+ if ( $doaction !== 'duplicate_post_bulk_rewrite_republish' ) {
92
+ return $redirect_to;
93
+ }
94
+
95
+ $counter = 0;
96
+ foreach ( $post_ids as $post_id ) {
97
+ $post = \get_post( $post_id );
98
+ if ( ! empty( $post ) && $this->permissions_helper->should_rewrite_and_republish_be_allowed( $post ) ) {
99
+ $new_post_id = $this->post_duplicator->create_duplicate_for_rewrite_and_republish( $post );
100
+ if ( ! \is_wp_error( $new_post_id ) ) {
101
+ $counter++;
102
+ }
103
+ }
104
+ }
105
+ return \add_query_arg( 'bulk_rewriting', $counter, $redirect_to );
106
+ }
107
+
108
+ /**
109
+ * Handles the bulk action for the Clone feature.
110
+ *
111
+ * @param string $redirect_to The URL to redirect to.
112
+ * @param string $doaction The action that has been called.
113
+ * @param array $post_ids The array of marked post IDs.
114
+ *
115
+ * @return string The URL to redirect to.
116
+ */
117
+ public function clone_bulk_action_handler( $redirect_to, $doaction, $post_ids ) {
118
+ if ( $doaction !== 'duplicate_post_bulk_clone' ) {
119
+ return $redirect_to;
120
+ }
121
+
122
+ $counter = 0;
123
+ foreach ( $post_ids as $post_id ) {
124
+ $post = \get_post( $post_id );
125
+ if ( ! empty( $post ) && ! $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) {
126
+ if ( \intval( \get_option( 'duplicate_post_copychildren' ) !== 1 )
127
+ || ! \is_post_type_hierarchical( $post->post_type )
128
+ || ( \is_post_type_hierarchical( $post->post_type ) && ! Utils::has_ancestors_marked( $post, $post_ids ) )
129
+ ) {
130
+ if ( ! \is_wp_error( \duplicate_post_create_duplicate( $post ) ) ) {
131
+ $counter++;
132
+ }
133
+ }
134
+ }
135
+ }
136
+ return \add_query_arg( 'bulk_cloned', $counter, $redirect_to );
137
+ }
138
+ }
src/handlers/class-check-changes-handler.php ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post handler class for changes overview.
4
+ *
5
+ * @package Duplicate_Post
6
+ * @since 4.0
7
+ */
8
+
9
+ namespace Yoast\WP\Duplicate_Post\Handlers;
10
+
11
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
12
+ use Yoast\WP\Duplicate_Post\Utils;
13
+
14
+ /**
15
+ * Represents the handler for checking the changes between a copy and the original post.
16
+ */
17
+ class Check_Changes_Handler {
18
+
19
+ /**
20
+ * Holds the permissions helper.
21
+ *
22
+ * @var Permissions_Helper
23
+ */
24
+ protected $permissions_helper;
25
+
26
+ /**
27
+ * Initializes the class.
28
+ *
29
+ * @param Permissions_Helper $permissions_helper The Permissions Helper object.
30
+ */
31
+ public function __construct( Permissions_Helper $permissions_helper ) {
32
+ $this->permissions_helper = $permissions_helper;
33
+ }
34
+
35
+ /**
36
+ * Adds hooks to integrate with WordPress.
37
+ *
38
+ * @return void
39
+ */
40
+ public function register_hooks() {
41
+ \add_action( 'admin_action_duplicate_post_check_changes', [ $this, 'check_changes_action_handler' ] );
42
+ }
43
+
44
+ /**
45
+ * Handles the action for displaying the changes between a copy and the original.
46
+ *
47
+ * @return void
48
+ */
49
+ public function check_changes_action_handler() {
50
+ if ( ! ( isset( $_GET['post'] ) || isset( $_POST['post'] ) || // Input var okay.
51
+ ( isset( $_REQUEST['action'] ) && 'duplicate_post_check_changes' === $_REQUEST['action'] ) ) ) { // Input var okay.
52
+ \wp_die(
53
+ \esc_html__( 'No post has been supplied!', 'duplicate-post' )
54
+ );
55
+ return;
56
+ }
57
+
58
+ $id = ( isset( $_GET['post'] ) ? \intval( \wp_unslash( $_GET['post'] ) ) : \intval( \wp_unslash( $_POST['post'] ) ) ); // Input var okay.
59
+
60
+ \check_admin_referer( 'duplicate_post_check_changes_' . $id ); // Input var okay.
61
+
62
+ $post = \get_post( $id );
63
+
64
+ if ( ! $post ) {
65
+ \wp_die(
66
+ \esc_html(
67
+ \sprintf(
68
+ /* translators: %s: post ID. */
69
+ \__( 'Changes overview failed, could not find post with ID %s.', 'duplicate-post' ),
70
+ $id
71
+ )
72
+ )
73
+ );
74
+ return;
75
+ }
76
+
77
+ $original = Utils::get_original( $post );
78
+
79
+ if ( ! $original ) {
80
+ \wp_die(
81
+ \esc_html(
82
+ \__( 'Changes overview failed, could not find original post.', 'duplicate-post' )
83
+ )
84
+ );
85
+ return;
86
+ }
87
+ $post_edit_link = \get_edit_post_link( $post->ID );
88
+
89
+ $this->require_wordpress_header();
90
+ ?>
91
+ <div class="wrap">
92
+ <h1 class="long-header">
93
+ <?php
94
+ echo \sprintf(
95
+ /* translators: %s: original item link (to view or edit) or title. */
96
+ \esc_html__( 'Compare changes of duplicated post with the original (&#8220;%s&#8221;)', 'duplicate-post' ),
97
+ Utils::get_edit_or_view_link( $original ) // phpcs:ignore WordPress.Security.EscapeOutput
98
+ );
99
+ ?>
100
+ </h1>
101
+ <a href="<?php echo \esc_url( $post_edit_link ); ?>"><?php \esc_html_e( '&larr; Return to editor', 'default' ); ?></a>
102
+ <div class="revisions">
103
+ <div class="revisions-control-frame">
104
+ <div class="revisions-controls"></div>
105
+ </div>
106
+ <div class="revisions-diff-frame">
107
+ <div class="revisions-diff">
108
+ <div class="diff">
109
+ <?php
110
+ $fields = [
111
+ \__( 'Title', 'default' ) => 'post_title',
112
+ \__( 'Content', 'default' ) => 'post_content',
113
+ \__( 'Excerpt', 'default' ) => 'post_excerpt',
114
+ ];
115
+
116
+ foreach ( $fields as $name => $field ) {
117
+ $diff = \wp_text_diff( $original->$field, $post->$field );
118
+
119
+ if ( ! $diff && 'post_title' === $field ) {
120
+ // It's a better user experience to still show the Title, even if it didn't change.
121
+ $diff = '<table class="diff"><colgroup><col class="content diffsplit left"><col class="content diffsplit middle"><col class="content diffsplit right"></colgroup><tbody><tr>';
122
+ $diff .= '<td>' . \esc_html( $original->post_title ) . '</td><td></td><td>' . \esc_html( $post->post_title ) . '</td>';
123
+ $diff .= '</tr></tbody>';
124
+ $diff .= '</table>';
125
+ }
126
+
127
+ if ( $diff ) {
128
+ ?>
129
+ <h3><?php echo \esc_html( $name ); ?></h3>
130
+ <?php
131
+ echo $diff; // phpcs:ignore WordPress.Security.EscapeOutput
132
+ }
133
+ }
134
+ ?>
135
+
136
+ </div>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ </div>
141
+ <?php
142
+ $this->require_wordpress_footer();
143
+ }
144
+
145
+ /**
146
+ * Requires the WP admin header.
147
+ *
148
+ * @codeCoverageIgnore
149
+ *
150
+ * @return void
151
+ */
152
+ public function require_wordpress_header() {
153
+ require_once ABSPATH . 'wp-admin/admin-header.php';
154
+ }
155
+
156
+ /**
157
+ * Requires the WP admin footer.
158
+ *
159
+ * @codeCoverageIgnore
160
+ *
161
+ * @return void
162
+ */
163
+ public function require_wordpress_footer() {
164
+ require_once ABSPATH . 'wp-admin/admin-footer.php';
165
+ }
166
+ }
src/handlers/class-handler.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post handler class for duplication actions.
4
+ *
5
+ * @package Duplicate_Post
6
+ * @since 4.0
7
+ */
8
+
9
+ namespace Yoast\WP\Duplicate_Post\Handlers;
10
+
11
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
12
+ use Yoast\WP\Duplicate_Post\Post_Duplicator;
13
+
14
+ /**
15
+ * Represents the handler for duplication actions.
16
+ */
17
+ class Handler {
18
+
19
+ /**
20
+ * Post_Duplicator object.
21
+ *
22
+ * @var Post_Duplicator
23
+ */
24
+ protected $post_duplicator;
25
+
26
+ /**
27
+ * Holds the permissions helper.
28
+ *
29
+ * @var Permissions_Helper
30
+ */
31
+ protected $permissions_helper;
32
+
33
+ /**
34
+ * The bulk actions handler.
35
+ *
36
+ * @var Bulk_Handler
37
+ */
38
+ protected $bulk_handler;
39
+
40
+ /**
41
+ * The link actions handler.
42
+ *
43
+ * @var Link_Handler
44
+ */
45
+ protected $link_handler;
46
+
47
+ /**
48
+ * The save_post action handler.
49
+ *
50
+ * @var Save_Post_Handler
51
+ */
52
+ protected $save_post_handler;
53
+
54
+ /**
55
+ * The link actions handler.
56
+ *
57
+ * @var Check_Changes_Handler
58
+ */
59
+ protected $check_handler;
60
+
61
+ /**
62
+ * Initializes the class.
63
+ *
64
+ * @param Post_Duplicator $post_duplicator The Post_Duplicator object.
65
+ * @param Permissions_Helper $permissions_helper The Permissions Helper object.
66
+ */
67
+ public function __construct( Post_Duplicator $post_duplicator, Permissions_Helper $permissions_helper ) {
68
+ $this->post_duplicator = $post_duplicator;
69
+ $this->permissions_helper = $permissions_helper;
70
+
71
+ $this->bulk_handler = new Bulk_Handler( $this->post_duplicator, $this->permissions_helper );
72
+ $this->link_handler = new Link_Handler( $this->post_duplicator, $this->permissions_helper );
73
+ $this->check_handler = new Check_Changes_Handler( $this->permissions_helper );
74
+ $this->save_post_handler = new Save_Post_Handler( $this->permissions_helper );
75
+
76
+ $this->bulk_handler->register_hooks();
77
+ $this->link_handler->register_hooks();
78
+ $this->check_handler->register_hooks();
79
+ $this->save_post_handler->register_hooks();
80
+ }
81
+ }
src/handlers/class-link-handler.php ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post handler class for duplication actions from links.
4
+ *
5
+ * @package Duplicate_Post
6
+ * @since 4.0
7
+ */
8
+
9
+ namespace Yoast\WP\Duplicate_Post\Handlers;
10
+
11
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
12
+ use Yoast\WP\Duplicate_Post\Post_Duplicator;
13
+
14
+ /**
15
+ * Represents the handler for duplication actions from links.
16
+ */
17
+ class Link_Handler {
18
+
19
+ /**
20
+ * Post_Duplicator object.
21
+ *
22
+ * @var Post_Duplicator
23
+ */
24
+ protected $post_duplicator;
25
+
26
+ /**
27
+ * Holds the permissions helper.
28
+ *
29
+ * @var Permissions_Helper
30
+ */
31
+ protected $permissions_helper;
32
+
33
+ /**
34
+ * Initializes the class.
35
+ *
36
+ * @param Post_Duplicator $post_duplicator The Post_Duplicator object.
37
+ * @param Permissions_Helper $permissions_helper The Permissions Helper object.
38
+ */
39
+ public function __construct( Post_Duplicator $post_duplicator, Permissions_Helper $permissions_helper ) {
40
+ $this->post_duplicator = $post_duplicator;
41
+ $this->permissions_helper = $permissions_helper;
42
+ }
43
+
44
+ /**
45
+ * Adds hooks to integrate with WordPress.
46
+ *
47
+ * @return void
48
+ */
49
+ public function register_hooks() {
50
+ \add_action( 'admin_action_duplicate_post_rewrite', [ $this, 'rewrite_link_action_handler' ] );
51
+ \add_action( 'admin_action_duplicate_post_clone', [ $this, 'clone_link_action_handler' ] );
52
+ \add_action( 'admin_action_duplicate_post_new_draft', [ $this, 'new_draft_link_action_handler' ] );
53
+ }
54
+
55
+ /**
56
+ * Handles the action for copying a post to a new draft.
57
+ *
58
+ * @return void
59
+ */
60
+ public function new_draft_link_action_handler() {
61
+ if ( ! $this->permissions_helper->is_current_user_allowed_to_copy() ) {
62
+ \wp_die( \esc_html__( 'Current user is not allowed to copy posts.', 'duplicate-post' ) );
63
+ }
64
+
65
+ if ( ! ( isset( $_GET['post'] ) || isset( $_POST['post'] ) || // Input var okay.
66
+ ( isset( $_REQUEST['action'] ) && 'duplicate_post_new_draft' === $_REQUEST['action'] ) ) ) { // Input var okay.
67
+ \wp_die( \esc_html__( 'No post to duplicate has been supplied!', 'duplicate-post' ) );
68
+ }
69
+
70
+ $id = ( isset( $_GET['post'] ) ? \intval( \wp_unslash( $_GET['post'] ) ) : \intval( \wp_unslash( $_POST['post'] ) ) ); // Input var okay.
71
+
72
+ \check_admin_referer( 'duplicate_post_new_draft_' . $id ); // Input var okay.
73
+
74
+ $post = \get_post( $id );
75
+
76
+ if ( ! $post ) {
77
+ \wp_die(
78
+ \esc_html(
79
+ \__( 'Copy creation failed, could not find original:', 'duplicate-post' ) . ' '
80
+ . $id
81
+ )
82
+ );
83
+ }
84
+
85
+ if ( $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) {
86
+ \wp_die(
87
+ \esc_html__( 'You cannot create a copy of a post which is intended for Rewrite & Republish.', 'duplicate-post' )
88
+ );
89
+ }
90
+
91
+ $new_id = \duplicate_post_create_duplicate( $post, 'draft' );
92
+
93
+ if ( \is_wp_error( $new_id ) ) {
94
+ \wp_die(
95
+ \esc_html__( 'Copy creation failed, could not create a copy.', 'duplicate-post' )
96
+ );
97
+ }
98
+
99
+ \wp_safe_redirect(
100
+ \add_query_arg(
101
+ [
102
+ 'cloned' => 1,
103
+ 'ids' => $post->ID,
104
+ ],
105
+ \admin_url( 'post.php?action=edit&post=' . $new_id . ( isset( $_GET['classic-editor'] ) ? '&classic-editor' : '' ) )
106
+ )
107
+ );
108
+ exit();
109
+ }
110
+
111
+ /**
112
+ * Handles the action for copying a post and redirecting to the post list.
113
+ *
114
+ * @return void
115
+ */
116
+ public function clone_link_action_handler() {
117
+ if ( ! $this->permissions_helper->is_current_user_allowed_to_copy() ) {
118
+ \wp_die( \esc_html__( 'Current user is not allowed to copy posts.', 'duplicate-post' ) );
119
+ }
120
+
121
+ if ( ! ( isset( $_GET['post'] ) || isset( $_POST['post'] ) || // Input var okay.
122
+ ( isset( $_REQUEST['action'] ) && 'duplicate_post_clone' === $_REQUEST['action'] ) ) ) { // Input var okay.
123
+ \wp_die( \esc_html__( 'No post to duplicate has been supplied!', 'duplicate-post' ) );
124
+ }
125
+
126
+ $id = ( isset( $_GET['post'] ) ? \intval( \wp_unslash( $_GET['post'] ) ) : \intval( \wp_unslash( $_POST['post'] ) ) ); // Input var okay.
127
+
128
+ \check_admin_referer( 'duplicate_post_clone_' . $id ); // Input var okay.
129
+
130
+ $post = \get_post( $id );
131
+
132
+ if ( ! $post ) {
133
+ \wp_die(
134
+ \esc_html(
135
+ \__( 'Copy creation failed, could not find original:', 'duplicate-post' ) . ' '
136
+ . $id
137
+ )
138
+ );
139
+ }
140
+
141
+ if ( $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) {
142
+ \wp_die(
143
+ \esc_html__( 'You cannot create a copy of a post which is intended for Rewrite & Republish.', 'duplicate-post' )
144
+ );
145
+ }
146
+
147
+ $new_id = \duplicate_post_create_duplicate( $post );
148
+
149
+ if ( \is_wp_error( $new_id ) ) {
150
+ \wp_die(
151
+ \esc_html__( 'Copy creation failed, could not create a copy.', 'duplicate-post' )
152
+ );
153
+ }
154
+
155
+ $post_type = $post->post_type;
156
+ $sendback = \wp_get_referer();
157
+ if ( ! $sendback || strpos( $sendback, 'post.php' ) !== false || strpos( $sendback, 'post-new.php' ) !== false ) {
158
+ if ( 'attachment' === $post_type ) {
159
+ $sendback = \admin_url( 'upload.php' );
160
+ } else {
161
+ $sendback = \admin_url( 'edit.php' );
162
+ if ( ! empty( $post_type ) ) {
163
+ $sendback = \add_query_arg( 'post_type', $post_type, $sendback );
164
+ }
165
+ }
166
+ } else {
167
+ $sendback = \remove_query_arg( [ 'trashed', 'untrashed', 'deleted', 'cloned', 'ids' ], $sendback );
168
+ }
169
+
170
+ // Redirect to the post list screen.
171
+ \wp_safe_redirect(
172
+ \add_query_arg(
173
+ [
174
+ 'cloned' => 1,
175
+ 'ids' => $post->ID,
176
+ ],
177
+ $sendback
178
+ )
179
+ );
180
+ exit();
181
+ }
182
+
183
+ /**
184
+ * Handles the action for copying a post for the Rewrite & Republish feature.
185
+ *
186
+ * @return void
187
+ */
188
+ public function rewrite_link_action_handler() {
189
+ if ( ! $this->permissions_helper->is_current_user_allowed_to_copy() ) {
190
+ \wp_die( \esc_html__( 'Current user is not allowed to copy posts.', 'duplicate-post' ) );
191
+ }
192
+
193
+ if ( ! ( isset( $_GET['post'] ) || isset( $_POST['post'] ) || // Input var okay.
194
+ ( isset( $_REQUEST['action'] ) && 'duplicate_post_rewrite' === $_REQUEST['action'] ) ) ) { // Input var okay.
195
+ \wp_die( \esc_html__( 'No post to duplicate has been supplied!', 'duplicate-post' ) );
196
+ }
197
+
198
+ $id = ( isset( $_GET['post'] ) ? \intval( \wp_unslash( $_GET['post'] ) ) : \intval( \wp_unslash( $_POST['post'] ) ) ); // Input var okay.
199
+
200
+ \check_admin_referer( 'duplicate_post_rewrite_' . $id ); // Input var okay.
201
+
202
+ $post = \get_post( $id );
203
+
204
+ if ( ! $post ) {
205
+ \wp_die(
206
+ \esc_html(
207
+ \__( 'Copy creation failed, could not find original:', 'duplicate-post' ) . ' '
208
+ . $id
209
+ )
210
+ );
211
+ }
212
+
213
+ if ( ! $this->permissions_helper->should_rewrite_and_republish_be_allowed( $post ) ) {
214
+ \wp_die(
215
+ \esc_html__( 'You cannot create a copy for Rewrite & Republish if the original is not published or if it already has a copy.', 'duplicate-post' )
216
+ );
217
+ }
218
+
219
+ $new_id = $this->post_duplicator->create_duplicate_for_rewrite_and_republish( $post );
220
+
221
+ if ( \is_wp_error( $new_id ) ) {
222
+ \wp_die(
223
+ \esc_html__( 'Copy creation failed, could not create a copy.', 'duplicate-post' )
224
+ );
225
+ }
226
+
227
+ \wp_safe_redirect(
228
+ \add_query_arg(
229
+ [
230
+ 'rewriting' => 1,
231
+ 'ids' => $post->ID,
232
+ ],
233
+ \admin_url( 'post.php?action=edit&post=' . $new_id . ( isset( $_GET['classic-editor'] ) ? '&classic-editor' : '' ) )
234
+ )
235
+ );
236
+ exit();
237
+ }
238
+ }
src/handlers/class-save-post-handler.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post handler class for save_post action.
4
+ *
5
+ * @package Duplicate_Post
6
+ * @since 4.0
7
+ */
8
+
9
+ namespace Yoast\WP\Duplicate_Post\Handlers;
10
+
11
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
12
+
13
+ /**
14
+ * Represents the handler for save_post action.
15
+ */
16
+ class Save_Post_Handler {
17
+
18
+ /**
19
+ * Holds the permissions helper.
20
+ *
21
+ * @var Permissions_Helper
22
+ */
23
+ protected $permissions_helper;
24
+
25
+ /**
26
+ * Initializes the class.
27
+ *
28
+ * @param Permissions_Helper $permissions_helper The Permissions Helper object.
29
+ */
30
+ public function __construct( Permissions_Helper $permissions_helper ) {
31
+ $this->permissions_helper = $permissions_helper;
32
+ }
33
+
34
+ /**
35
+ * Adds hooks to integrate with WordPress.
36
+ *
37
+ * @return void
38
+ */
39
+ public function register_hooks() {
40
+ if ( \intval( \get_option( 'duplicate_post_show_original_meta_box' ) ) === 1
41
+ || \intval( \get_option( 'duplicate_post_show_original_column' ) ) === 1 ) {
42
+ \add_action( 'save_post', [ $this, 'delete_on_save_post' ] );
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Deletes the custom field with the ID of the original post.
48
+ *
49
+ * @param int $post_id The current post ID.
50
+ *
51
+ * @return void
52
+ */
53
+ public function delete_on_save_post( $post_id ) {
54
+ if ( ( \defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
55
+ || empty( $_POST['duplicate_post_remove_original'] ) // phpcs:ignore WordPress.Security.NonceVerification
56
+ || ! \current_user_can( 'edit_post', $post_id ) ) {
57
+ return;
58
+ }
59
+
60
+ $post = \get_post( $post_id );
61
+ if ( ! $post ) {
62
+ return;
63
+ }
64
+ if ( ! $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) {
65
+ \delete_post_meta( $post_id, '_dp_original' );
66
+ }
67
+ }
68
+ }
src/ui/class-admin-bar.php ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to manage the admin bar.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\UI;
9
+
10
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
11
+ use Yoast\WP\Duplicate_Post\Utils;
12
+
13
+ /**
14
+ * Represents the Admin_Bar class.
15
+ */
16
+ class Admin_Bar {
17
+
18
+ /**
19
+ * Holds the object to create the action link to duplicate.
20
+ *
21
+ * @var Link_Builder
22
+ */
23
+ protected $link_builder;
24
+
25
+ /**
26
+ * Holds the permissions helper.
27
+ *
28
+ * @var Permissions_Helper
29
+ */
30
+ protected $permissions_helper;
31
+
32
+ /**
33
+ * Holds the asset manager.
34
+ *
35
+ * @var Asset_Manager
36
+ */
37
+ protected $asset_manager;
38
+
39
+ /**
40
+ * Initializes the class.
41
+ *
42
+ * @param Link_Builder $link_builder The link builder.
43
+ * @param Permissions_Helper $permissions_helper The permissions helper.
44
+ * @param Asset_Manager $asset_manager The asset manager.
45
+ */
46
+ public function __construct( Link_Builder $link_builder, Permissions_Helper $permissions_helper, Asset_Manager $asset_manager ) {
47
+ $this->link_builder = $link_builder;
48
+ $this->permissions_helper = $permissions_helper;
49
+ $this->asset_manager = $asset_manager;
50
+ }
51
+
52
+ /**
53
+ * Adds hooks to integrate with WordPress.
54
+ *
55
+ * @return void
56
+ */
57
+ public function register_hooks() {
58
+ if ( \intval( Utils::get_option( 'duplicate_post_show_link_in', 'adminbar' ) ) === 1 ) {
59
+ \add_action( 'wp_before_admin_bar_render', [ $this, 'admin_bar_render' ] );
60
+ \add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_styles' ] );
61
+ \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_styles' ] );
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Shows Rewrite & Republish link in the Toolbar.
67
+ *
68
+ * @global \WP_Admin_Bar $wp_admin_bar WP_Admin_Bar instance.
69
+ *
70
+ * @return void
71
+ */
72
+ public function admin_bar_render() {
73
+ global $wp_admin_bar;
74
+
75
+ if ( ! \is_admin_bar_showing() ) {
76
+ return;
77
+ }
78
+
79
+ $post = $this->get_current_post();
80
+
81
+ if ( ! $post ) {
82
+ return;
83
+ }
84
+
85
+ $show_new_draft = ( \intval( Utils::get_option( 'duplicate_post_show_link', 'new_draft' ) ) === 1 );
86
+ $show_rewrite_and_republish = ( \intval( Utils::get_option( 'duplicate_post_show_link', 'rewrite_republish' ) ) === 1 )
87
+ && $this->permissions_helper->should_rewrite_and_republish_be_allowed( $post );
88
+
89
+ if ( $show_new_draft && $show_rewrite_and_republish ) {
90
+ $wp_admin_bar->add_menu(
91
+ [
92
+ 'id' => 'duplicate-post',
93
+ 'title' => '<span class="ab-icon"></span><span class="ab-label">' . \__( 'Duplicate Post', 'duplicate-post' ) . '</span>',
94
+ 'href' => $this->link_builder->build_new_draft_link( $post ),
95
+ ]
96
+ );
97
+ $wp_admin_bar->add_menu(
98
+ [
99
+ 'id' => 'new-draft',
100
+ 'parent' => 'duplicate-post',
101
+ 'title' => \__( 'Copy to a new draft', 'duplicate-post' ),
102
+ 'href' => $this->link_builder->build_new_draft_link( $post ),
103
+ ]
104
+ );
105
+ $wp_admin_bar->add_menu(
106
+ [
107
+ 'id' => 'rewrite-republish',
108
+ 'parent' => 'duplicate-post',
109
+ 'title' => \__( 'Rewrite & Republish', 'duplicate-post' ),
110
+ 'href' => $this->link_builder->build_rewrite_and_republish_link( $post ),
111
+ ]
112
+ );
113
+ } else {
114
+ if ( $show_new_draft ) {
115
+ $wp_admin_bar->add_menu(
116
+ [
117
+ 'id' => 'new-draft',
118
+ 'title' => '<span class="ab-icon"></span><span class="ab-label">' . \__( 'Copy to a new draft', 'duplicate-post' ) . '</span>',
119
+ 'href' => $this->link_builder->build_new_draft_link( $post ),
120
+ ]
121
+ );
122
+ }
123
+
124
+ if ( $show_rewrite_and_republish ) {
125
+ $wp_admin_bar->add_menu(
126
+ [
127
+ 'id' => 'rewrite-republish',
128
+ 'title' => '<span class="ab-icon"></span><span class="ab-label">' . \__( 'Rewrite & Republish', 'duplicate-post' ) . '</span>',
129
+ 'href' => $this->link_builder->build_rewrite_and_republish_link( $post ),
130
+ ]
131
+ );
132
+ }
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Links stylesheet for Toolbar link.
138
+ *
139
+ * @global \WP_Query $wp_the_query.
140
+ *
141
+ * @return void
142
+ */
143
+ public function enqueue_styles() {
144
+ if ( ! \is_admin_bar_showing() ) {
145
+ return;
146
+ }
147
+
148
+ $post = $this->get_current_post();
149
+
150
+ if ( ! $post ) {
151
+ return;
152
+ }
153
+
154
+ $this->asset_manager->enqueue_styles();
155
+ }
156
+
157
+ /**
158
+ * Returns the current post object (both if it's displayed or being edited).
159
+ *
160
+ * @global \WP_Query $wp_the_query
161
+ *
162
+ * @return false|\WP_Post The Post object, false if we are not on a post.
163
+ */
164
+ public function get_current_post() {
165
+ global $wp_the_query;
166
+
167
+ if ( \is_admin() ) {
168
+ $post = \get_post();
169
+ } else {
170
+ $post = $wp_the_query->get_queried_object();
171
+ }
172
+
173
+ if ( empty( $post ) || ! \is_a( $post, '\WP_Post' ) ) {
174
+ return false;
175
+ }
176
+
177
+ if (
178
+ ( ! $this->permissions_helper->is_edit_post_screen() && ! \is_singular( $post->post_type ) )
179
+ || ! $this->permissions_helper->post_type_has_admin_bar( $post->post_type )
180
+ ) {
181
+ return false;
182
+ }
183
+
184
+ if ( ! $this->permissions_helper->should_links_be_displayed( $post ) ) {
185
+ return false;
186
+ }
187
+
188
+ return $post;
189
+ }
190
+ }
src/ui/class-asset-manager.php ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to manage assets.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\UI;
9
+
10
+ use Yoast\WP\Duplicate_Post\Utils;
11
+
12
+ /**
13
+ * Represents the Duplicate Post Asset Manager class.
14
+ */
15
+ class Asset_Manager {
16
+
17
+ /**
18
+ * Adds hooks to integrate with WordPress.
19
+ *
20
+ * @return void
21
+ */
22
+ public function register_hooks() {
23
+ \add_action( 'init', [ $this, 'register_styles' ] );
24
+ \add_action( 'init', [ $this, 'register_scripts' ] );
25
+ }
26
+
27
+ /**
28
+ * Registers the styles.
29
+ *
30
+ * @return void
31
+ */
32
+ public function register_styles() {
33
+ \wp_register_style( 'duplicate-post', \plugins_url( '/css/duplicate-post.css', DUPLICATE_POST_FILE ), [], DUPLICATE_POST_CURRENT_VERSION );
34
+ \wp_register_style( 'duplicate-post-options', \plugins_url( '/css/duplicate-post-options.css', DUPLICATE_POST_FILE ), [], DUPLICATE_POST_CURRENT_VERSION );
35
+ }
36
+
37
+ /**
38
+ * Registers the scripts.
39
+ *
40
+ * @return void
41
+ */
42
+ public function register_scripts() {
43
+ $flattened_version = Utils::flatten_version( DUPLICATE_POST_CURRENT_VERSION );
44
+
45
+ \wp_register_script(
46
+ 'duplicate_post_edit_script',
47
+ \plugins_url( \sprintf( 'js/dist/duplicate-post-edit-%s.js', $flattened_version ), DUPLICATE_POST_FILE ),
48
+ [
49
+ 'wp-components',
50
+ 'wp-element',
51
+ 'wp-i18n',
52
+ ],
53
+ DUPLICATE_POST_CURRENT_VERSION,
54
+ true
55
+ );
56
+
57
+ \wp_register_script(
58
+ 'duplicate_post_strings',
59
+ \plugins_url( \sprintf( 'js/dist/duplicate-post-strings-%s.js', $flattened_version ), DUPLICATE_POST_FILE ),
60
+ [
61
+ 'wp-components',
62
+ 'wp-element',
63
+ 'wp-i18n',
64
+ ],
65
+ DUPLICATE_POST_CURRENT_VERSION,
66
+ true
67
+ );
68
+
69
+ \wp_register_script(
70
+ 'duplicate_post_quick_edit_script',
71
+ \plugins_url( \sprintf( 'js/dist/duplicate-post-quick-edit-%s.js', $flattened_version ), DUPLICATE_POST_FILE ),
72
+ [ 'jquery' ],
73
+ DUPLICATE_POST_CURRENT_VERSION,
74
+ true
75
+ );
76
+
77
+ \wp_register_script(
78
+ 'duplicate_post_options_script',
79
+ \plugins_url( \sprintf( 'js/dist/duplicate-post-options-%s.js', $flattened_version ), DUPLICATE_POST_FILE ),
80
+ [ 'jquery' ],
81
+ DUPLICATE_POST_CURRENT_VERSION,
82
+ true
83
+ );
84
+ }
85
+
86
+ /**
87
+ * Enqueues the styles.
88
+ *
89
+ * @return void
90
+ */
91
+ public function enqueue_styles() {
92
+ \wp_enqueue_style( 'duplicate-post' );
93
+ }
94
+
95
+ /**
96
+ * Enqueues the styles for the options page.
97
+ *
98
+ * @return void
99
+ */
100
+ public function enqueue_options_styles() {
101
+ \wp_enqueue_style( 'duplicate-post-options' );
102
+ }
103
+
104
+ /**
105
+ * Enqueues the script for the Block editor and passes object via localization.
106
+ *
107
+ * @param array $object The object to pass to the script.
108
+ *
109
+ * @return void
110
+ */
111
+ public function enqueue_edit_script( $object = [] ) {
112
+ $handle = 'duplicate_post_edit_script';
113
+ \wp_enqueue_script( $handle );
114
+ \wp_add_inline_script(
115
+ $handle,
116
+ 'let duplicatePostNotices = {};',
117
+ 'before'
118
+ );
119
+ \wp_localize_script(
120
+ $handle,
121
+ 'duplicatePost',
122
+ $object
123
+ );
124
+ }
125
+
126
+ /**
127
+ * Enqueues the script for the Javascript strings and passes object via localization.
128
+ *
129
+ * @param array $object The object to pass to the script.
130
+ *
131
+ * @return void
132
+ */
133
+ public function enqueue_strings_script( $object = [] ) {
134
+ $handle = 'duplicate_post_strings';
135
+ \wp_enqueue_script( $handle );
136
+ \wp_localize_script(
137
+ $handle,
138
+ 'duplicatePostStrings',
139
+ $object
140
+ );
141
+ }
142
+
143
+ /**
144
+ * Enqueues the script for the Quick Edit.
145
+ *
146
+ * @return void
147
+ */
148
+ public function enqueue_quick_edit_script() {
149
+ \wp_enqueue_script( 'duplicate_post_quick_edit_script' );
150
+ }
151
+
152
+ /**
153
+ * Enqueues the script for the Options page.
154
+ *
155
+ * @return void
156
+ */
157
+ public function enqueue_options_script() {
158
+ \wp_enqueue_script( 'duplicate_post_options_script' );
159
+ }
160
+ }
src/ui/class-block-editor.php ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to manage the block editor UI.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\UI;
9
+
10
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
11
+ use Yoast\WP\Duplicate_Post\Utils;
12
+
13
+ /**
14
+ * Represents the Block_Editor class.
15
+ */
16
+ class Block_Editor {
17
+
18
+ /**
19
+ * Holds the object to create the action link to duplicate.
20
+ *
21
+ * @var Link_Builder
22
+ */
23
+ protected $link_builder;
24
+
25
+ /**
26
+ * Holds the permissions helper.
27
+ *
28
+ * @var Permissions_Helper
29
+ */
30
+ protected $permissions_helper;
31
+
32
+ /**
33
+ * Holds the asset manager.
34
+ *
35
+ * @var Asset_Manager
36
+ */
37
+ protected $asset_manager;
38
+
39
+ /**
40
+ * Initializes the class.
41
+ *
42
+ * @param Link_Builder $link_builder The link builder.
43
+ * @param Permissions_Helper $permissions_helper The permissions helper.
44
+ * @param Asset_Manager $asset_manager The asset manager.
45
+ */
46
+ public function __construct( Link_Builder $link_builder, Permissions_Helper $permissions_helper, Asset_Manager $asset_manager ) {
47
+ $this->link_builder = $link_builder;
48
+ $this->permissions_helper = $permissions_helper;
49
+ $this->asset_manager = $asset_manager;
50
+ }
51
+
52
+ /**
53
+ * Adds hooks to integrate with WordPress.
54
+ *
55
+ * @return void
56
+ */
57
+ public function register_hooks() {
58
+ \add_action( 'admin_enqueue_scripts', [ $this, 'should_previously_used_keyword_assessment_run' ], 9 );
59
+ \add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_block_editor_scripts' ] );
60
+ }
61
+
62
+ /**
63
+ * Disables the Yoast SEO PreviouslyUsedKeyword assessment for Rewrite & Republish original and duplicate posts.
64
+ *
65
+ * @return void
66
+ */
67
+ public function should_previously_used_keyword_assessment_run() {
68
+ if ( $this->permissions_helper->is_edit_post_screen() || $this->permissions_helper->is_new_post_screen() ) {
69
+
70
+ $post = \get_post();
71
+
72
+ if (
73
+ ! \is_null( $post )
74
+ && (
75
+ $this->permissions_helper->is_rewrite_and_republish_copy( $post )
76
+ || $this->permissions_helper->has_rewrite_and_republish_copy( $post )
77
+ )
78
+ ) {
79
+ \add_filter( 'wpseo_previously_used_keyword_active', '__return_false' );
80
+ }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Enqueues the necessary JavaScript code for the block editor.
86
+ *
87
+ * @return void
88
+ */
89
+ public function enqueue_block_editor_scripts() {
90
+ $post = \get_post();
91
+
92
+ if ( ! $post ) {
93
+ return;
94
+ }
95
+
96
+ $is_rewrite_and_republish_copy = $this->permissions_helper->is_rewrite_and_republish_copy( $post );
97
+
98
+ $edit_js_object = [
99
+ 'newDraftLink' => $this->get_new_draft_permalink(),
100
+ 'rewriteAndRepublishLink' => $this->get_rewrite_republish_permalink(),
101
+ 'showLinks' => Utils::get_option( 'duplicate_post_show_link' ),
102
+ 'showLinksIn' => Utils::get_option( 'duplicate_post_show_link_in' ),
103
+ 'rewriting' => $is_rewrite_and_republish_copy ? 1 : 0,
104
+ 'originalEditURL' => $this->get_original_post_edit_url(),
105
+ ];
106
+ $this->asset_manager->enqueue_edit_script( $edit_js_object );
107
+
108
+ if ( $is_rewrite_and_republish_copy ) {
109
+ $string_js_object = [
110
+ 'checkLink' => $this->get_check_permalink(),
111
+ ];
112
+ $this->asset_manager->enqueue_strings_script( $string_js_object );
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Generates a New Draft permalink for the current post.
118
+ *
119
+ * @return string The permalink. Returns empty if the post can't be copied.
120
+ */
121
+ public function get_new_draft_permalink() {
122
+ $post = \get_post();
123
+
124
+ if ( ! $this->permissions_helper->should_links_be_displayed( $post ) ) {
125
+ return '';
126
+ }
127
+
128
+ return $this->link_builder->build_new_draft_link( $post );
129
+ }
130
+
131
+ /**
132
+ * Generates a Rewrite & Republish permalink for the current post.
133
+ *
134
+ * @return string The permalink. Returns empty if the post cannot be copied for Rewrite & Republish.
135
+ */
136
+ public function get_rewrite_republish_permalink() {
137
+ $post = \get_post();
138
+
139
+ if (
140
+ $this->permissions_helper->is_rewrite_and_republish_copy( $post )
141
+ || $this->permissions_helper->has_rewrite_and_republish_copy( $post )
142
+ || ! $this->permissions_helper->should_links_be_displayed( $post )
143
+ || $this->permissions_helper->is_elementor_active()
144
+ ) {
145
+ return '';
146
+ }
147
+
148
+ return $this->link_builder->build_rewrite_and_republish_link( $post );
149
+ }
150
+
151
+ /**
152
+ * Generates a Check Changes permalink for the current post, if it's intended for Rewrite & Republish.
153
+ *
154
+ * @return string The permalink. Returns empty if the post does not exist or it's not a Rewrite & Republish copy.
155
+ */
156
+ public function get_check_permalink() {
157
+ $post = \get_post();
158
+
159
+ if ( ! $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) {
160
+ return '';
161
+ }
162
+
163
+ return $this->link_builder->build_check_link( $post );
164
+ }
165
+
166
+ /**
167
+ * Generates a URL to the original post edit screen.
168
+ *
169
+ * @return string The URL. Empty if the copy post doesn't have an original.
170
+ */
171
+ public function get_original_post_edit_url() {
172
+ $post = \get_post();
173
+
174
+ if ( \is_null( $post ) ) {
175
+ return '';
176
+ }
177
+
178
+ $original_post_id = Utils::get_original_post_id( $post->ID );
179
+
180
+ if ( ! $original_post_id ) {
181
+ return '';
182
+ }
183
+
184
+ return \add_query_arg(
185
+ [
186
+ 'dprepublished' => 1,
187
+ 'dpcopy' => $post->ID,
188
+ 'dpnonce' => \wp_create_nonce( 'dp-republish' ),
189
+ ],
190
+ \admin_url( 'post.php?action=edit&post=' . $original_post_id )
191
+ );
192
+ }
193
+ }
src/ui/class-bulk-actions.php ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to manage the bulk actions menu.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\UI;
9
+
10
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
11
+ use Yoast\WP\Duplicate_Post\Utils;
12
+
13
+ /**
14
+ * Represents the Bulk_Actions class.
15
+ */
16
+ class Bulk_Actions {
17
+
18
+ /**
19
+ * Holds the permissions helper.
20
+ *
21
+ * @var Permissions_Helper
22
+ */
23
+ protected $permissions_helper;
24
+
25
+ /**
26
+ * Initializes the class.
27
+ *
28
+ * @param Permissions_Helper $permissions_helper The permissions helper.
29
+ */
30
+ public function __construct( Permissions_Helper $permissions_helper ) {
31
+ $this->permissions_helper = $permissions_helper;
32
+ }
33
+
34
+ /**
35
+ * Adds hooks to integrate with WordPress.
36
+ *
37
+ * @return void
38
+ */
39
+ public function register_hooks() {
40
+ if ( \intval( Utils::get_option( 'duplicate_post_show_link_in', 'bulkactions' ) ) === 0 ) {
41
+ return;
42
+ }
43
+
44
+ \add_action( 'admin_init', [ $this, 'add_bulk_filters' ] );
45
+ }
46
+
47
+ /**
48
+ * Hooks the function to add the Rewrite & Republish option in the bulk actions for the selected post types.
49
+ *
50
+ * @return void
51
+ */
52
+ public function add_bulk_filters() {
53
+ if ( ! $this->permissions_helper->is_current_user_allowed_to_copy() ) {
54
+ return;
55
+ }
56
+
57
+ $duplicate_post_types_enabled = $this->permissions_helper->get_enabled_post_types();
58
+ foreach ( $duplicate_post_types_enabled as $duplicate_post_type_enabled ) {
59
+ \add_filter( "bulk_actions-edit-{$duplicate_post_type_enabled}", [ $this, 'register_bulk_action' ] );
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Adds 'Rewrite & Republish' to the bulk action dropdown.
65
+ *
66
+ * @param array $bulk_actions The bulk actions array.
67
+ *
68
+ * @return array The bulk actions array.
69
+ */
70
+ public function register_bulk_action( $bulk_actions ) {
71
+ // phpcs:ignore WordPress.Security.NonceVerification
72
+ $is_draft_or_trash = isset( $_REQUEST['post_status'] ) && in_array( $_REQUEST['post_status'], array( 'draft', 'trash' ), true );
73
+
74
+ if ( \intval( Utils::get_option( 'duplicate_post_show_link', 'clone' ) ) === 1 ) {
75
+ $bulk_actions['duplicate_post_bulk_clone'] = \esc_html__( 'Clone', 'duplicate-post' );
76
+ }
77
+
78
+ if ( ! $is_draft_or_trash
79
+ && \intval( Utils::get_option( 'duplicate_post_show_link', 'rewrite_republish' ) ) === 1
80
+ && ! $this->permissions_helper->is_elementor_active() ) {
81
+ $bulk_actions['duplicate_post_bulk_rewrite_republish'] = \esc_html__( 'Rewrite & Republish', 'duplicate-post' );
82
+ }
83
+
84
+ return $bulk_actions;
85
+ }
86
+ }
src/ui/class-classic-editor.php ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to manage the classic editor UI.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\UI;
9
+
10
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
11
+ use Yoast\WP\Duplicate_Post\Utils;
12
+
13
+ /**
14
+ * Represents the Classic_Editor class.
15
+ */
16
+ class Classic_Editor {
17
+
18
+ /**
19
+ * Holds the object to create the action link to duplicate.
20
+ *
21
+ * @var Link_Builder
22
+ */
23
+ protected $link_builder;
24
+
25
+ /**
26
+ * Holds the permissions helper.
27
+ *
28
+ * @var Permissions_Helper
29
+ */
30
+ protected $permissions_helper;
31
+
32
+ /**
33
+ * Holds the asset manager.
34
+ *
35
+ * @var Asset_Manager
36
+ */
37
+ protected $asset_manager;
38
+
39
+ /**
40
+ * Initializes the class.
41
+ *
42
+ * @param Link_Builder $link_builder The link builder.
43
+ * @param Permissions_Helper $permissions_helper The permissions helper.
44
+ * @param Asset_Manager $asset_manager The asset manager.
45
+ */
46
+ public function __construct( Link_Builder $link_builder, Permissions_Helper $permissions_helper, Asset_Manager $asset_manager ) {
47
+ $this->link_builder = $link_builder;
48
+ $this->permissions_helper = $permissions_helper;
49
+ $this->asset_manager = $asset_manager;
50
+ }
51
+
52
+ /**
53
+ * Adds hooks to integrate with WordPress.
54
+ *
55
+ * @return void
56
+ */
57
+ public function register_hooks() {
58
+ \add_action( 'post_submitbox_misc_actions', [ $this, 'add_check_changes_link' ], 90 );
59
+
60
+ if ( \intval( Utils::get_option( 'duplicate_post_show_link_in', 'submitbox' ) ) === 1 ) {
61
+ if ( \intval( Utils::get_option( 'duplicate_post_show_link', 'new_draft' ) ) === 1 ) {
62
+ \add_action( 'post_submitbox_start', [ $this, 'add_new_draft_post_button' ] );
63
+ }
64
+
65
+ if ( \intval( Utils::get_option( 'duplicate_post_show_link', 'rewrite_republish' ) ) === 1 ) {
66
+ \add_action( 'post_submitbox_start', [ $this, 'add_rewrite_and_republish_post_button' ] );
67
+ }
68
+ }
69
+
70
+ \add_filter( 'gettext', [ $this, 'change_republish_strings_classic_editor' ], 10, 2 );
71
+ \add_filter( 'gettext_with_context', [ $this, 'change_schedule_strings_classic_editor' ], 10, 3 );
72
+ \add_filter( 'post_updated_messages', [ $this, 'change_scheduled_notice_classic_editor' ], 10, 1 );
73
+
74
+ \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_classic_editor_scripts' ] );
75
+ \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_classic_editor_styles' ] );
76
+
77
+ // Remove slug editing from Classic Editor.
78
+ \add_action( 'add_meta_boxes', [ $this, 'remove_slug_meta_box' ], 10, 2 );
79
+ \add_filter( 'get_sample_permalink_html', [ $this, 'remove_sample_permalink_slug_editor' ], 10, 5 );
80
+ }
81
+
82
+ /**
83
+ * Enqueues the necessary JavaScript code for the Classic editor.
84
+ *
85
+ * @return void
86
+ */
87
+ public function enqueue_classic_editor_scripts() {
88
+ if ( $this->permissions_helper->is_classic_editor() && isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
89
+ $id = \intval( \wp_unslash( $_GET['post'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
90
+ $post = \get_post( $id );
91
+
92
+ if ( ! \is_null( $post ) && $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) {
93
+ $this->asset_manager->enqueue_strings_script();
94
+ }
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Enqueues the necessary styles for the Classic editor.
100
+ *
101
+ * @return void
102
+ */
103
+ public function enqueue_classic_editor_styles() {
104
+ if ( $this->permissions_helper->is_classic_editor()
105
+ && isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
106
+ $id = \intval( \wp_unslash( $_GET['post'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
107
+ $post = \get_post( $id );
108
+
109
+ if ( ! \is_null( $post ) && $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) {
110
+ $this->asset_manager->enqueue_styles();
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Adds a button in the post/page edit screen to create a clone
117
+ *
118
+ * @param \WP_Post|null $post The post object that's being edited.
119
+ *
120
+ * @return void
121
+ */
122
+ public function add_new_draft_post_button( $post = null ) {
123
+ if ( \is_null( $post ) ) {
124
+ if ( isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
125
+ $id = \intval( \wp_unslash( $_GET['post'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
126
+ $post = \get_post( $id );
127
+ }
128
+ }
129
+
130
+ if (
131
+ ! \is_null( $post )
132
+ && $this->permissions_helper->should_links_be_displayed( $post )
133
+ ) {
134
+ ?>
135
+ <div id="duplicate-action">
136
+ <a class="submitduplicate duplication"
137
+ href="<?php echo \esc_url( $this->link_builder->build_new_draft_link( $post ) ); ?>"><?php \esc_html_e( 'Copy to a new draft', 'duplicate-post' ); ?>
138
+ </a>
139
+ </div>
140
+ <?php
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Adds a button in the post/page edit screen to create a clone for Rewrite & Republish.
146
+ *
147
+ * @param \WP_Post|null $post The post object that's being edited.
148
+ *
149
+ * @return void
150
+ */
151
+ public function add_rewrite_and_republish_post_button( $post = null ) {
152
+ if ( \is_null( $post ) ) {
153
+ if ( isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
154
+ $id = \intval( \wp_unslash( $_GET['post'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
155
+ $post = \get_post( $id );
156
+ }
157
+ }
158
+
159
+ if (
160
+ ! \is_null( $post )
161
+ && $this->permissions_helper->should_rewrite_and_republish_be_allowed( $post )
162
+ && $this->permissions_helper->should_links_be_displayed( $post )
163
+ ) {
164
+ ?>
165
+ <div id="rewrite-republish-action">
166
+ <a class="submitduplicate duplication" href="<?php echo \esc_url( $this->link_builder->build_rewrite_and_republish_link( $post ) ); ?>"><?php \esc_html_e( 'Rewrite & Republish', 'duplicate-post' ); ?>
167
+ </a>
168
+ </div>
169
+ <?php
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Adds a message in the post/page edit screen to create a clone for Rewrite & Republish.
175
+ *
176
+ * @param \WP_Post|null $post The post object that's being edited.
177
+ *
178
+ * @return void
179
+ */
180
+ public function add_check_changes_link( $post = null ) {
181
+ if ( \is_null( $post ) ) {
182
+ if ( isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
183
+ $id = \intval( \wp_unslash( $_GET['post'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
184
+ $post = \get_post( $id );
185
+ }
186
+ }
187
+
188
+ if ( ! \is_null( $post ) && $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) {
189
+ ?>
190
+ <div id="check-changes-action">
191
+ <?php \esc_html_e( 'Do you want to compare your changes with the original version before merging? Please save any changes first.', 'duplicate-post' ); ?>
192
+ <br><br>
193
+ <a class='button' href=<?php echo \esc_url( $this->link_builder->build_check_link( $post ) ); ?>>
194
+ <?php \esc_html_e( 'Compare', 'duplicate-post' ); ?>
195
+ </a>
196
+ </div>
197
+ <?php
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Changes the 'Publish' copies in the submitbox to 'Republish' if a post is intended for republishing.
203
+ *
204
+ * @param string $translation The translated text.
205
+ * @param string $text The text to translate.
206
+ *
207
+ * @return string The to-be-used copy of the text.
208
+ */
209
+ public function change_republish_strings_classic_editor( $translation, $text ) {
210
+ if ( $this->should_change_rewrite_republish_copy( \get_post() ) ) {
211
+ if ( $text === 'Publish' ) {
212
+ return \__( 'Republish', 'duplicate-post' );
213
+ }
214
+
215
+ if ( $text === 'Publish on: %s' ) {
216
+ /* translators: %s: Date on which the post is to be republished. */
217
+ return \__( 'Republish on: %s', 'duplicate-post' );
218
+ }
219
+ }
220
+
221
+ return $translation;
222
+ }
223
+
224
+ /**
225
+ * Changes the 'Schedule' copy in the submitbox to 'Schedule republish' if a post is intended for republishing.
226
+ *
227
+ * @param string $translation The translated text.
228
+ * @param string $text The text to translate.
229
+ *
230
+ * @return string The to-be-used copy of the text.
231
+ */
232
+ public function change_schedule_strings_classic_editor( $translation, $text ) {
233
+ if ( $this->should_change_rewrite_republish_copy( \get_post() ) && $text === 'Schedule' ) {
234
+ return \__( 'Schedule republish', 'duplicate-post' );
235
+ }
236
+
237
+ return $translation;
238
+ }
239
+
240
+ /**
241
+ * Changes the post-scheduled notice when a post or page intended for republishing is scheduled.
242
+ *
243
+ * @param array[] $messages Post updated messaged.
244
+ *
245
+ * @return array[] The to-be-used messages.
246
+ */
247
+ public function change_scheduled_notice_classic_editor( $messages ) {
248
+ $post = \get_post();
249
+ if ( ! $this->should_change_rewrite_republish_copy( $post ) ) {
250
+ return $messages;
251
+ }
252
+
253
+ $permalink = \get_permalink( $post->ID );
254
+ $scheduled_date = \get_the_time( \get_option( 'date_format' ), $post );
255
+ $scheduled_time = \get_the_time( \get_option( 'time_format' ), $post );
256
+
257
+ if ( $post->post_type === 'post' ) {
258
+ $messages['post'][9] = \sprintf(
259
+ /* translators: 1: The post title with a link to the frontend page, 2: The scheduled date and time. */
260
+ \esc_html__(
261
+ 'This rewritten post %1$s is now scheduled to replace the original post. It will be published on %2$s.',
262
+ 'duplicate-post'
263
+ ),
264
+ '<a href="' . $permalink . '">' . $post->post_title . '</a>',
265
+ '<strong>' . $scheduled_date . ' ' . $scheduled_time . '</strong>'
266
+ );
267
+ return $messages;
268
+ }
269
+
270
+ if ( $post->post_type === 'page' ) {
271
+ $messages['page'][9] = \sprintf(
272
+ /* translators: 1: The page title with a link to the frontend page, 2: The scheduled date and time. */
273
+ \esc_html__(
274
+ 'This rewritten page %1$s is now scheduled to replace the original page. It will be published on %2$s.',
275
+ 'duplicate-post'
276
+ ),
277
+ '<a href="' . $permalink . '">' . $post->post_title . '</a>',
278
+ '<strong>' . $scheduled_date . ' ' . $scheduled_time . '</strong>'
279
+ );
280
+ }
281
+
282
+ return $messages;
283
+ }
284
+
285
+ /**
286
+ * Determines if the Rewrite & Republish copies for the post should be used.
287
+ *
288
+ * @param \WP_Post $post The current post object.
289
+ *
290
+ * @return bool True if the Rewrite & Republish copies should be used.
291
+ */
292
+ public function should_change_rewrite_republish_copy( $post ) {
293
+ global $pagenow;
294
+ if ( ! \in_array( $pagenow, [ 'post.php', 'post-new.php' ], true ) ) {
295
+ return false;
296
+ }
297
+
298
+ if ( \is_null( $post ) ) {
299
+ return false;
300
+ }
301
+
302
+ return $this->permissions_helper->is_rewrite_and_republish_copy( $post );
303
+ }
304
+
305
+ /**
306
+ * Removes the slug meta box in the Classic Editor when the post is a Rewrite & Republish copy.
307
+ *
308
+ * @param string $post_type Post type.
309
+ * @param \WP_Post $post Post object.
310
+ *
311
+ * @return void
312
+ */
313
+ public function remove_slug_meta_box( $post_type, $post ) {
314
+ if ( $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) {
315
+ \remove_meta_box( 'slugdiv', $post_type, 'normal' );
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Removes the sample permalink slug editor in the Classic Editor when the post is a Rewrite & Republish copy.
321
+ *
322
+ * @param string $return Sample permalink HTML markup.
323
+ * @param int $post_id Post ID.
324
+ * @param string $new_title New sample permalink title.
325
+ * @param string $new_slug New sample permalink slug.
326
+ * @param \WP_Post $post Post object.
327
+ *
328
+ * @return string The filtered HTML of the sample permalink slug editor.
329
+ */
330
+ public function remove_sample_permalink_slug_editor( $return, $post_id, $new_title, $new_slug, $post ) {
331
+ if ( $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) {
332
+ return '';
333
+ }
334
+
335
+ return $return;
336
+ }
337
+ }
src/ui/class-column.php ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to manage the custom column + quick edit.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\UI;
9
+
10
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
11
+ use Yoast\WP\Duplicate_Post\Utils;
12
+
13
+ /**
14
+ * Represents the Column class.
15
+ */
16
+ class Column {
17
+
18
+ /**
19
+ * Holds the permissions helper.
20
+ *
21
+ * @var Permissions_Helper
22
+ */
23
+ protected $permissions_helper;
24
+
25
+ /**
26
+ * Holds the asset manager.
27
+ *
28
+ * @var Asset_Manager
29
+ */
30
+ protected $asset_manager;
31
+
32
+ /**
33
+ * Initializes the class.
34
+ *
35
+ * @param Permissions_Helper $permissions_helper The permissions helper.
36
+ * @param Asset_Manager $asset_manager The asset manager.
37
+ */
38
+ public function __construct( Permissions_Helper $permissions_helper, Asset_Manager $asset_manager ) {
39
+ $this->permissions_helper = $permissions_helper;
40
+ $this->asset_manager = $asset_manager;
41
+ }
42
+
43
+ /**
44
+ * Adds hooks to integrate with WordPress.
45
+ *
46
+ * @return void
47
+ */
48
+ public function register_hooks() {
49
+ if ( \intval( \get_option( 'duplicate_post_show_original_column' ) ) === 1 ) {
50
+ $enabled_post_types = $this->permissions_helper->get_enabled_post_types();
51
+ if ( \count( $enabled_post_types ) ) {
52
+ foreach ( $enabled_post_types as $enabled_post_type ) {
53
+ \add_filter( "manage_{$enabled_post_type}_posts_columns", [ $this, 'add_original_column' ] );
54
+ \add_action( "manage_{$enabled_post_type}_posts_custom_column", [ $this, 'show_original_item' ], 10, 2 );
55
+ }
56
+ \add_action( 'quick_edit_custom_box', [ $this, 'quick_edit_remove_original' ], 10, 2 );
57
+ \add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
58
+ \add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_styles' ] );
59
+ }
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Adds Original item column to the post list.
65
+ *
66
+ * @param array $post_columns The post columns array.
67
+ *
68
+ * @return array The updated array.
69
+ */
70
+ public function add_original_column( $post_columns ) {
71
+ $post_columns['duplicate_post_original_item'] = \__( 'Original item', 'duplicate-post' );
72
+ return $post_columns;
73
+ }
74
+
75
+ /**
76
+ * Sets the text to be displayed in the Original item column for the current post.
77
+ *
78
+ * @param string $column_name The name for the current column.
79
+ * @param int $post_id The ID for the current post.
80
+ *
81
+ * @return void
82
+ */
83
+ public function show_original_item( $column_name, $post_id ) {
84
+ if ( 'duplicate_post_original_item' === $column_name ) {
85
+ $column_content = '-';
86
+ $data_attr = ' data-no-original="1"';
87
+ $original_item = Utils::get_original( $post_id );
88
+ if ( $original_item ) {
89
+ $post = \get_post( $post_id );
90
+ $is_rewrite_and_republish_copy = \is_null( $post ) ? false : $this->permissions_helper->is_rewrite_and_republish_copy( $post );
91
+
92
+ $data_attr = $is_rewrite_and_republish_copy ? ' data-copy-is-for-rewrite-and-republish="1"' : '';
93
+ $column_content = Utils::get_edit_or_view_link( $original_item );
94
+ }
95
+ echo \sprintf(
96
+ '<span class="duplicate_post_original_link"%s>%s</span>',
97
+ $data_attr, // phpcs:ignore WordPress.Security.EscapeOutput
98
+ $column_content // phpcs:ignore WordPress.Security.EscapeOutput
99
+ );
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Adds original item checkbox + edit link in the Quick Edit.
105
+ *
106
+ * @param string $column_name The name for the current column.
107
+ *
108
+ * @return void
109
+ */
110
+ public function quick_edit_remove_original( $column_name ) {
111
+ if ( 'duplicate_post_original_item' !== $column_name ) {
112
+ return;
113
+ }
114
+ \printf(
115
+ '<fieldset class="inline-edit-col-left" id="duplicate_post_quick_edit_fieldset">
116
+ <div class="inline-edit-col">
117
+ <input type="checkbox"
118
+ name="duplicate_post_remove_original"
119
+ id="duplicate-post-remove-original"
120
+ value="duplicate_post_remove_original"
121
+ aria-describedby="duplicate-post-remove-original-description">
122
+ <label for="duplicate-post-remove-original">
123
+ <span class="checkbox-title">%s</span>
124
+ </label>
125
+ <span id="duplicate-post-remove-original-description" class="checkbox-title">%s</span>
126
+ </div>
127
+ </fieldset>',
128
+ \esc_html__(
129
+ 'Delete reference to original item.',
130
+ 'duplicate-post'
131
+ ),
132
+ \wp_kses(
133
+ \__(
134
+ 'The original item this was copied from is: <span class="duplicate_post_original_item_title_span"></span>',
135
+ 'duplicate-post'
136
+ ),
137
+ [
138
+ 'span' => [
139
+ 'class' => [],
140
+ ],
141
+ ]
142
+ )
143
+ );
144
+ }
145
+
146
+ /**
147
+ * Enqueues the Javascript file to inject column data into the Quick Edit.
148
+ *
149
+ * @param string $hook The current admin page.
150
+ *
151
+ * @return void
152
+ */
153
+ public function admin_enqueue_scripts( $hook ) {
154
+ if ( 'edit.php' === $hook ) {
155
+ $this->asset_manager->enqueue_quick_edit_script();
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Enqueues the CSS file to for the Quick edit
161
+ *
162
+ * @param string $hook The current admin page.
163
+ *
164
+ * @return void
165
+ */
166
+ public function admin_enqueue_styles( $hook ) {
167
+ if ( 'edit.php' === $hook ) {
168
+ $this->asset_manager->enqueue_styles();
169
+ }
170
+ }
171
+ }
src/ui/class-link-builder.php ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post link builder.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\UI;
9
+
10
+ /**
11
+ * Class Link_Builder
12
+ *
13
+ * @package Yoast\WP\Duplicate_Post
14
+ */
15
+ class Link_Builder {
16
+
17
+ /**
18
+ * Builds URL for duplication action for the Rewrite & Republish feature.
19
+ *
20
+ * @param int|\WP_Post $post The post object or ID.
21
+ * @param string $context The context in which the URL will be used.
22
+ *
23
+ * @return string The URL for the link.
24
+ */
25
+ public function build_rewrite_and_republish_link( $post, $context = 'display' ) {
26
+ return $this->build_link( $post, $context, 'duplicate_post_rewrite' );
27
+ }
28
+
29
+ /**
30
+ * Builds URL for the "Clone" action.
31
+ *
32
+ * @param int|\WP_Post $post The post object or ID.
33
+ * @param string $context The context in which the URL will be used.
34
+ *
35
+ * @return string The URL for the link.
36
+ */
37
+ public function build_clone_link( $post, $context = 'display' ) {
38
+ return $this->build_link( $post, $context, 'duplicate_post_clone' );
39
+ }
40
+
41
+ /**
42
+ * Builds URL for the "Copy to a new draft" action.
43
+ *
44
+ * @param int|\WP_Post $post The post object or ID.
45
+ * @param string $context The context in which the URL will be used.
46
+ *
47
+ * @return string The URL for the link.
48
+ */
49
+ public function build_new_draft_link( $post, $context = 'display' ) {
50
+ return $this->build_link( $post, $context, 'duplicate_post_new_draft' );
51
+ }
52
+
53
+ /**
54
+ * Builds URL for the "Check Changes" action.
55
+ *
56
+ * @param int|\WP_Post $post The post object or ID.
57
+ * @param string $context The context in which the URL will be used.
58
+ *
59
+ * @return string The URL for the link.
60
+ */
61
+ public function build_check_link( $post, $context = 'display' ) {
62
+ return $this->build_link( $post, $context, 'duplicate_post_check_changes' );
63
+ }
64
+
65
+ /**
66
+ * Builds URL for duplication action.
67
+ *
68
+ * @param int|\WP_Post $post The post object or ID.
69
+ * @param string $context The context in which the URL will be used.
70
+ * @param string $action_name The action for the URL.
71
+ *
72
+ * @return string The URL for the link.
73
+ */
74
+ public function build_link( $post, $context, $action_name ) {
75
+ $post = \get_post( $post );
76
+ if ( ! $post ) {
77
+ return '';
78
+ }
79
+
80
+ if ( 'display' === $context ) {
81
+ $action = '?action=' . $action_name . '&amp;post=' . $post->ID;
82
+ } else {
83
+ $action = '?action=' . $action_name . '&post=' . $post->ID;
84
+ }
85
+
86
+ return \wp_nonce_url(
87
+ /**
88
+ * Filter on the URL of the clone link
89
+ *
90
+ * @param string $url The URL of the clone link.
91
+ * @param int $ID The ID of the post
92
+ * @param string $context The context in which the URL is used.
93
+ * @param string $action_name The action name.
94
+ *
95
+ * @return string
96
+ */
97
+ \apply_filters( 'duplicate_post_get_clone_post_link', \admin_url( 'admin.php' . $action ), $post->ID, $context, $action_name ),
98
+ $action_name . '_' . $post->ID
99
+ );
100
+ }
101
+ }
src/ui/class-metabox.php ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to manage the metabox.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\UI;
9
+
10
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
11
+ use Yoast\WP\Duplicate_Post\Utils;
12
+
13
+ /**
14
+ * Represents the Metabox class.
15
+ */
16
+ class Metabox {
17
+
18
+ /**
19
+ * Holds the permissions helper.
20
+ *
21
+ * @var Permissions_Helper
22
+ */
23
+ protected $permissions_helper;
24
+
25
+ /**
26
+ * Initializes the class.
27
+ *
28
+ * @param Permissions_Helper $permissions_helper The permissions helper.
29
+ */
30
+ public function __construct( Permissions_Helper $permissions_helper ) {
31
+ $this->permissions_helper = $permissions_helper;
32
+ }
33
+
34
+ /**
35
+ * Adds hooks to integrate with WordPress.
36
+ *
37
+ * @return void
38
+ */
39
+ public function register_hooks() {
40
+ if ( \intval( \get_option( 'duplicate_post_show_original_meta_box' ) ) === 1 ) {
41
+ \add_action( 'add_meta_boxes', [ $this, 'add_custom_metabox' ] );
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Adds a metabox to Edit screen.
47
+ *
48
+ * @return void
49
+ */
50
+ public function add_custom_metabox() {
51
+ $screens = $this->permissions_helper->get_enabled_post_types();
52
+ if ( ! \is_array( $screens ) ) {
53
+ $screens = [ $screens ];
54
+ }
55
+ foreach ( $screens as $screen ) {
56
+ \add_meta_box(
57
+ 'duplicate_post_show_original',
58
+ \__( 'Duplicate Post', 'duplicate-post' ),
59
+ [ $this, 'custom_metabox_html' ],
60
+ $screen,
61
+ 'side'
62
+ );
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Outputs the HTML for the metabox.
68
+ *
69
+ * @param \WP_Post $post The current post.
70
+ *
71
+ * @return void
72
+ */
73
+ public function custom_metabox_html( $post ) {
74
+ $original_item = Utils::get_original( $post );
75
+ if ( $original_item ) {
76
+ if ( ! $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) {
77
+ ?>
78
+ <p>
79
+ <input type="checkbox"
80
+ name="duplicate_post_remove_original"
81
+ id="duplicate-post-remove-original"
82
+ value="duplicate_post_remove_original"
83
+ aria-describedby="duplicate-post-remove-original-description">
84
+ <label for="duplicate-post-remove-original">
85
+ <?php \esc_html_e( 'Delete reference to original item.', 'duplicate-post' ); ?>
86
+ </label>
87
+ </p>
88
+ <?php
89
+ }
90
+ ?>
91
+ <p id="duplicate-post-remove-original-description">
92
+ <?php
93
+ \printf(
94
+ \wp_kses(
95
+ /* translators: %s: post title */
96
+ \__(
97
+ 'The original item this was copied from is: <span class="duplicate_post_original_item_title_span">%s</span>',
98
+ 'duplicate-post'
99
+ ),
100
+ [
101
+ 'span' => [
102
+ 'class' => [],
103
+ ],
104
+ ]
105
+ ),
106
+ Utils::get_edit_or_view_link( $original_item ) // phpcs:ignore WordPress.Security.EscapeOutput
107
+ );
108
+ ?>
109
+ </p>
110
+ <?php
111
+ } else {
112
+ ?>
113
+ <script>
114
+ (function(jQuery){
115
+ jQuery('#duplicate_post_show_original').hide();
116
+ })(jQuery);
117
+ </script>
118
+ <?php
119
+ }
120
+ }
121
+ }
src/ui/class-post-list.php ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to manage the post list.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\UI;
9
+
10
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
11
+
12
+ /**
13
+ * Represents the Post_List class.
14
+ */
15
+ class Post_List {
16
+
17
+ /**
18
+ * Holds the permissions helper.
19
+ *
20
+ * @var Permissions_Helper
21
+ */
22
+ protected $permissions_helper;
23
+
24
+ /**
25
+ * Holds the array of copy IDs.
26
+ *
27
+ * @var array
28
+ */
29
+ protected $copy_ids = [];
30
+
31
+ /**
32
+ * Initializes the class.
33
+ *
34
+ * @param Permissions_Helper $permissions_helper The Permissions helper object.
35
+ */
36
+ public function __construct( Permissions_Helper $permissions_helper ) {
37
+ $this->permissions_helper = $permissions_helper;
38
+ }
39
+
40
+ /**
41
+ * Adds hooks to integrate with WordPress.
42
+ *
43
+ * @return void
44
+ */
45
+ public function register_hooks() {
46
+ \add_filter( 'parse_query', [ $this, 'filter_rewrite_and_republish_copies' ] );
47
+ \add_filter( 'wp_count_posts', [ $this, 'filter_rewrite_and_republish_counts' ], 10, 2 );
48
+ }
49
+
50
+ /**
51
+ * Filters out Rewrite & Republish copies from the post list when Elementor is active.
52
+ *
53
+ * @param \WP_Query $query The current WordPress query.
54
+ *
55
+ * @return \WP_Query The updated post WordPress query.
56
+ */
57
+ public function filter_rewrite_and_republish_copies( \WP_Query $query ) {
58
+ if ( ! $this->should_filter() ) {
59
+ return $query;
60
+ }
61
+
62
+ $post_not_in = $query->get( 'post__not_in', [] );
63
+ $post_not_in = array_merge( $post_not_in, \array_keys( $this->get_copy_ids( $query->get( 'post_type' ) ) ) );
64
+
65
+ $query->set( 'post__not_in', $post_not_in );
66
+
67
+ return $query;
68
+ }
69
+
70
+ /**
71
+ * Filters out the Rewrite and Republish posts from the post counts.
72
+ *
73
+ * @param object $counts The current post counts.
74
+ * @param string $post_type The post type.
75
+ *
76
+ * @return object The updated post counts.
77
+ */
78
+ public function filter_rewrite_and_republish_counts( $counts, $post_type ) {
79
+ if ( ! $this->should_filter() ) {
80
+ return $counts;
81
+ }
82
+
83
+ $copies = $this->get_copy_ids( $post_type );
84
+
85
+ foreach ( $copies as $item ) {
86
+ $status = $item->post_status;
87
+ if ( \property_exists( $counts, $status ) ) {
88
+ $counts->$status--;
89
+ }
90
+ }
91
+
92
+ return $counts;
93
+ }
94
+
95
+ /**
96
+ * Queries the database to get the IDs of all Rewrite and Republish copies.
97
+ *
98
+ * @param string $post_type The post type to fetch the copy IDs for.
99
+ *
100
+ * @return array The IDs of the copies.
101
+ */
102
+ protected function get_copy_ids( $post_type ) {
103
+ global $wpdb;
104
+
105
+ if ( \array_key_exists( $post_type, $this->copy_ids ) ) {
106
+ return $this->copy_ids[ $post_type ];
107
+ }
108
+
109
+ $this->copy_ids[ $post_type ] = $wpdb->get_results(
110
+ $wpdb->prepare(
111
+ 'SELECT post_id, post_status FROM ' . $wpdb->postmeta . ' AS pm ' .
112
+ 'JOIN ' . $wpdb->posts . ' AS p ON pm.post_id = p.ID ' .
113
+ 'WHERE meta_key = %s AND post_type = %s',
114
+ '_dp_is_rewrite_republish_copy',
115
+ $post_type
116
+ ),
117
+ OBJECT_K
118
+ );
119
+
120
+ return $this->copy_ids[ $post_type ];
121
+ }
122
+
123
+ /**
124
+ * Determines whether the filter should be applied.
125
+ *
126
+ * @return bool Whether the filter should be applied.
127
+ */
128
+ protected function should_filter() {
129
+ if ( ! \is_admin() || ! \function_exists( '\get_current_screen' ) ) {
130
+ return false;
131
+ }
132
+
133
+ $current_screen = \get_current_screen();
134
+
135
+ return ( $current_screen->base === 'edit' && $this->permissions_helper->is_elementor_active() );
136
+ }
137
+ }
src/ui/class-post-states.php ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to manage the post states display.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\UI;
9
+
10
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
11
+ use Yoast\WP\Duplicate_Post\Utils;
12
+
13
+ /**
14
+ * Represents the Post_States class.
15
+ */
16
+ class Post_States {
17
+
18
+ /**
19
+ * Holds the permissions helper.
20
+ *
21
+ * @var Permissions_Helper
22
+ */
23
+ protected $permissions_helper;
24
+
25
+ /**
26
+ * Initializes the class.
27
+ *
28
+ * @param Permissions_Helper $permissions_helper The Permissions helper object.
29
+ */
30
+ public function __construct( Permissions_Helper $permissions_helper ) {
31
+ $this->permissions_helper = $permissions_helper;
32
+ }
33
+
34
+ /**
35
+ * Adds hooks to integrate with WordPress.
36
+ *
37
+ * @return void
38
+ */
39
+ public function register_hooks() {
40
+ \add_filter( 'display_post_states', [ $this, 'show_original_in_post_states' ], 10, 2 );
41
+ }
42
+
43
+ /**
44
+ * Shows link to original post in the post states.
45
+ *
46
+ * @param array $post_states The array of post states.
47
+ * @param \WP_Post $post The current post.
48
+ *
49
+ * @return array The updated post states array.
50
+ */
51
+ public function show_original_in_post_states( $post_states, $post ) {
52
+ $original_item = Utils::get_original( $post );
53
+
54
+ if ( ! $original_item ) {
55
+ return $post_states;
56
+ }
57
+
58
+ if ( $this->permissions_helper->is_rewrite_and_republish_copy( $post ) ) {
59
+ /* translators: %s: Original item link (to view or edit) or title. */
60
+ $post_states['duplicate_post_original_item'] = \sprintf( \esc_html__( 'Rewrite & Republish of %s', 'duplicate-post' ), Utils::get_edit_or_view_link( $original_item ) );
61
+ return $post_states;
62
+ }
63
+
64
+ if ( \intval( \get_option( 'duplicate_post_show_original_in_post_states' ) ) === 1 ) {
65
+ /* translators: %s: Original item link (to view or edit) or title. */
66
+ $post_states['duplicate_post_original_item'] = \sprintf( \__( 'Original: %s', 'duplicate-post' ), Utils::get_edit_or_view_link( $original_item ) );
67
+ }
68
+
69
+ return $post_states;
70
+ }
71
+ }
src/ui/class-row-actions.php ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to manage the row actions.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\UI;
9
+
10
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
11
+ use Yoast\WP\Duplicate_Post\Utils;
12
+
13
+ /**
14
+ * Represents the Row_Action class.
15
+ */
16
+ class Row_Actions {
17
+
18
+ /**
19
+ * Holds the object to create the action link to duplicate.
20
+ *
21
+ * @var Link_Builder
22
+ */
23
+ protected $link_builder;
24
+
25
+ /**
26
+ * Holds the permissions helper.
27
+ *
28
+ * @var Permissions_Helper
29
+ */
30
+ protected $permissions_helper;
31
+
32
+ /**
33
+ * Initializes the class.
34
+ *
35
+ * @param Link_Builder $link_builder The link builder.
36
+ * @param Permissions_Helper $permissions_helper The permissions helper.
37
+ */
38
+ public function __construct( Link_Builder $link_builder, Permissions_Helper $permissions_helper ) {
39
+ $this->link_builder = $link_builder;
40
+ $this->permissions_helper = $permissions_helper;
41
+ }
42
+
43
+ /**
44
+ * Adds hooks to integrate with WordPress.
45
+ *
46
+ * @return void
47
+ */
48
+ public function register_hooks() {
49
+ if ( \intval( Utils::get_option( 'duplicate_post_show_link_in', 'row' ) ) === 0 ) {
50
+ return;
51
+ }
52
+
53
+ if ( \intval( Utils::get_option( 'duplicate_post_show_link', 'clone' ) ) === 1 ) {
54
+ \add_filter( 'post_row_actions', [ $this, 'add_clone_action_link' ], 10, 2 );
55
+ \add_filter( 'page_row_actions', [ $this, 'add_clone_action_link' ], 10, 2 );
56
+ }
57
+
58
+ if ( \intval( Utils::get_option( 'duplicate_post_show_link', 'new_draft' ) ) === 1 ) {
59
+ \add_filter( 'post_row_actions', [ $this, 'add_new_draft_action_link' ], 10, 2 );
60
+ \add_filter( 'page_row_actions', [ $this, 'add_new_draft_action_link' ], 10, 2 );
61
+ }
62
+
63
+ if ( \intval( Utils::get_option( 'duplicate_post_show_link', 'rewrite_republish' ) ) === 1 ) {
64
+ \add_filter( 'post_row_actions', [ $this, 'add_rewrite_and_republish_action_link' ], 10, 2 );
65
+ \add_filter( 'page_row_actions', [ $this, 'add_rewrite_and_republish_action_link' ], 10, 2 );
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Hooks in the `post_row_actions` and `page_row_actions` filters to add a 'Clone' link.
71
+ *
72
+ * @param array $actions The array of actions from the filter.
73
+ * @param \WP_Post $post The post object.
74
+ *
75
+ * @return array The updated array of actions.
76
+ */
77
+ public function add_clone_action_link( array $actions, \WP_Post $post ) {
78
+ if ( ! $this->permissions_helper->should_links_be_displayed( $post ) ) {
79
+ return $actions;
80
+ }
81
+
82
+ $title = \_draft_or_post_title( $post );
83
+
84
+ $actions['clone'] = '<a href="' . $this->link_builder->build_clone_link( $post->ID ) .
85
+ '" aria-label="' . \esc_attr(
86
+ /* translators: %s: Post title. */
87
+ \sprintf( \__( 'Clone &#8220;%s&#8221;', 'duplicate-post' ), $title )
88
+ ) . '">' .
89
+ \esc_html_x( 'Clone', 'verb', 'duplicate-post' ) . '</a>';
90
+
91
+ return $actions;
92
+ }
93
+
94
+ /**
95
+ * Hooks in the `post_row_actions` and `page_row_actions` filters to add a 'New Draft' link.
96
+ *
97
+ * @param array $actions The array of actions from the filter.
98
+ * @param \WP_Post $post The post object.
99
+ *
100
+ * @return array The updated array of actions.
101
+ */
102
+ public function add_new_draft_action_link( array $actions, \WP_Post $post ) {
103
+ if ( ! $this->permissions_helper->should_links_be_displayed( $post ) ) {
104
+ return $actions;
105
+ }
106
+
107
+ $title = \_draft_or_post_title( $post );
108
+
109
+ $actions['edit_as_new_draft'] = '<a href="' . $this->link_builder->build_new_draft_link( $post->ID ) .
110
+ '" aria-label="' . \esc_attr(
111
+ /* translators: %s: Post title. */
112
+ \sprintf( \__( 'New draft of &#8220;%s&#8221;', 'duplicate-post' ), $title )
113
+ ) . '">' .
114
+ \esc_html__( 'New Draft', 'duplicate-post' ) .
115
+ '</a>';
116
+
117
+ return $actions;
118
+ }
119
+
120
+ /**
121
+ * Hooks in the `post_row_actions` and `page_row_actions` filters to add a 'Rewrite & Republish' link.
122
+ *
123
+ * @param array $actions The array of actions from the filter.
124
+ * @param \WP_Post $post The post object.
125
+ *
126
+ * @return array The updated array of actions.
127
+ */
128
+ public function add_rewrite_and_republish_action_link( array $actions, \WP_Post $post ) {
129
+ if (
130
+ ! $this->permissions_helper->should_rewrite_and_republish_be_allowed( $post )
131
+ || ! $this->permissions_helper->should_links_be_displayed( $post )
132
+ ) {
133
+ return $actions;
134
+ }
135
+
136
+ $title = \_draft_or_post_title( $post );
137
+
138
+ $actions['rewrite'] = '<a href="' . $this->link_builder->build_rewrite_and_republish_link( $post->ID ) .
139
+ '" aria-label="' . \esc_attr(
140
+ /* translators: %s: Post title. */
141
+ \sprintf( \__( 'Rewrite & Republish &#8220;%s&#8221;', 'duplicate-post' ), $title )
142
+ ) . '">' .
143
+ \esc_html_x( 'Rewrite & Republish', 'verb', 'duplicate-post' ) . '</a>';
144
+
145
+ return $actions;
146
+ }
147
+ }
src/ui/class-user-interface.php ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post user interface.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\UI;
9
+
10
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
11
+
12
+ /**
13
+ * Represents the Duplicate Post User Interface class.
14
+ */
15
+ class User_Interface {
16
+
17
+ /**
18
+ * Holds the permissions helper.
19
+ *
20
+ * @var Permissions_Helper
21
+ */
22
+ protected $permissions_helper;
23
+
24
+ /**
25
+ * Holds the object to manage the row actions for the post.
26
+ *
27
+ * @var Row_Actions
28
+ */
29
+ protected $row_actions;
30
+
31
+ /**
32
+ * Holds the object to manage the classic editor UI.
33
+ *
34
+ * @var Classic_Editor
35
+ */
36
+ protected $classic_editor;
37
+
38
+ /**
39
+ * Holds the object to manage the block editor UI.
40
+ *
41
+ * @var Block_Editor
42
+ */
43
+ protected $block_editor;
44
+
45
+ /**
46
+ * Holds the object to manage the admin bar links.
47
+ *
48
+ * @var Admin_Bar
49
+ */
50
+ protected $admin_bar;
51
+
52
+ /**
53
+ * Holds the object to manage the bulk actions dropdown.
54
+ *
55
+ * @var Bulk_Actions
56
+ */
57
+ protected $bulk_actions;
58
+
59
+ /**
60
+ * Post states object.
61
+ *
62
+ * @var Post_States
63
+ */
64
+ protected $post_states;
65
+
66
+ /**
67
+ * Metabox object.
68
+ *
69
+ * @var Metabox
70
+ */
71
+ protected $metabox;
72
+
73
+ /**
74
+ * Column object.
75
+ *
76
+ * @var Column
77
+ */
78
+ protected $column;
79
+
80
+ /**
81
+ * Post_List object.
82
+ *
83
+ * @var Post_List
84
+ */
85
+ protected $post_list;
86
+
87
+ /**
88
+ * Holds the object to create the action link to duplicate.
89
+ *
90
+ * @var Link_Builder
91
+ */
92
+ protected $link_builder;
93
+
94
+ /**
95
+ * Holds the object to create the action link to duplicate.
96
+ *
97
+ * @var Asset_Manager
98
+ */
99
+ protected $asset_manager;
100
+
101
+ /**
102
+ * Initializes the class.
103
+ *
104
+ * @param Permissions_Helper $permissions_helper The permissions helper object.
105
+ */
106
+ public function __construct( Permissions_Helper $permissions_helper ) {
107
+ $this->permissions_helper = $permissions_helper;
108
+ $this->link_builder = new Link_Builder();
109
+ $this->asset_manager = new Asset_Manager();
110
+ $this->asset_manager->register_hooks();
111
+
112
+ $this->admin_bar = new Admin_Bar( $this->link_builder, $this->permissions_helper, $this->asset_manager );
113
+ $this->block_editor = new Block_Editor( $this->link_builder, $this->permissions_helper, $this->asset_manager );
114
+ $this->bulk_actions = new Bulk_Actions( $this->permissions_helper );
115
+ $this->column = new Column( $this->permissions_helper, $this->asset_manager );
116
+ $this->metabox = new Metabox( $this->permissions_helper );
117
+ $this->post_states = new Post_States( $this->permissions_helper );
118
+ $this->post_list = new Post_List( $this->permissions_helper );
119
+ $this->classic_editor = new Classic_Editor( $this->link_builder, $this->permissions_helper, $this->asset_manager );
120
+ $this->row_actions = new Row_Actions( $this->link_builder, $this->permissions_helper );
121
+
122
+ $this->admin_bar->register_hooks();
123
+ $this->block_editor->register_hooks();
124
+ $this->bulk_actions->register_hooks();
125
+ $this->column->register_hooks();
126
+ $this->metabox->register_hooks();
127
+ $this->post_states->register_hooks();
128
+ $this->post_list->register_hooks();
129
+ $this->classic_editor->register_hooks();
130
+ $this->row_actions->register_hooks();
131
+ }
132
+ }
src/watchers/class-bulk-actions-watcher.php ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to watch for the bulk actions and show notices.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\Watchers;
9
+
10
+ /**
11
+ * Represents the Bulk_Actions_Watcher class.
12
+ */
13
+ class Bulk_Actions_Watcher {
14
+
15
+ /**
16
+ * Initializes the class.
17
+ */
18
+ public function __construct() {
19
+ $this->register_hooks();
20
+ }
21
+
22
+ /**
23
+ * Adds hooks to integrate with WordPress.
24
+ *
25
+ * @return void
26
+ */
27
+ public function register_hooks() {
28
+ \add_filter( 'removable_query_args', [ $this, 'add_removable_query_args' ] );
29
+ \add_action( 'admin_notices', [ $this, 'add_bulk_clone_admin_notice' ] );
30
+ \add_action( 'admin_notices', [ $this, 'add_bulk_rewrite_and_republish_admin_notice' ] );
31
+ }
32
+
33
+ /**
34
+ * Adds vars to the removable query args.
35
+ *
36
+ * @param array $removable_query_args Array of query args keys.
37
+ *
38
+ * @return array The updated array of query args keys.
39
+ */
40
+ public function add_removable_query_args( $removable_query_args ) {
41
+ $removable_query_args[] = 'bulk_cloned';
42
+ $removable_query_args[] = 'bulk_rewriting';
43
+ return $removable_query_args;
44
+ }
45
+
46
+ /**
47
+ * Shows a notice after the Clone bulk action has succeeded.
48
+ *
49
+ * @return void
50
+ */
51
+ public function add_bulk_clone_admin_notice() {
52
+ if ( ! empty( $_REQUEST['bulk_cloned'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
53
+ $copied_posts = \intval( $_REQUEST['bulk_cloned'] ); // phpcs:ignore WordPress.Security.NonceVerification
54
+ \printf(
55
+ '<div id="message" class="notice notice-success fade"><p>' .
56
+ \esc_html(
57
+ /* translators: %s: Number of posts copied. */
58
+ \_n(
59
+ '%s item copied.',
60
+ '%s items copied.',
61
+ $copied_posts,
62
+ 'duplicate-post'
63
+ )
64
+ ) . '</p></div>',
65
+ \esc_html( $copied_posts )
66
+ );
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Shows a notice after the Rewrite & Republish bulk action has succeeded.
72
+ *
73
+ * @return void
74
+ */
75
+ public function add_bulk_rewrite_and_republish_admin_notice() {
76
+ if ( ! empty( $_REQUEST['bulk_rewriting'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
77
+ $copied_posts = \intval( $_REQUEST['bulk_rewriting'] ); // phpcs:ignore WordPress.Security.NonceVerification
78
+ \printf(
79
+ '<div id="message" class="notice notice-success fade"><p>' .
80
+ \esc_html(
81
+ /* translators: %s: Number of posts copied. */
82
+ \_n(
83
+ '%s post duplicated. You can now start rewriting your post in the duplicate of the original post. Once you choose to republish it your changes will be merged back into the original post.',
84
+ '%s posts duplicated. You can now start rewriting your posts in the duplicates of the original posts. Once you choose to republish them your changes will be merged back into the original post.',
85
+ $copied_posts,
86
+ 'duplicate-post'
87
+ )
88
+ ) . '</p></div>',
89
+ \esc_html( $copied_posts )
90
+ );
91
+ }
92
+ }
93
+ }
src/watchers/class-copied-post-watcher.php ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to watch if the current post has a Rewrite & Republish copy.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\Watchers;
9
+
10
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
11
+
12
+ /**
13
+ * Represents the Copied_Post_Watcher class.
14
+ */
15
+ class Copied_Post_Watcher {
16
+
17
+ /**
18
+ * Holds the permissions helper.
19
+ *
20
+ * @var Permissions_Helper
21
+ */
22
+ protected $permissions_helper;
23
+
24
+ /**
25
+ * Initializes the class.
26
+ *
27
+ * @param Permissions_Helper $permissions_helper The Permissions helper object.
28
+ */
29
+ public function __construct( $permissions_helper ) {
30
+ $this->permissions_helper = $permissions_helper;
31
+
32
+ $this->register_hooks();
33
+ }
34
+
35
+ /**
36
+ * Adds hooks to integrate with WordPress.
37
+ *
38
+ * @return void
39
+ */
40
+ public function register_hooks() {
41
+ \add_action( 'admin_notices', [ $this, 'add_admin_notice' ] );
42
+ \add_action( 'enqueue_block_editor_assets', [ $this, 'add_block_editor_notice' ], 11 );
43
+ }
44
+
45
+ /**
46
+ * Generates the translated text for the notice.
47
+ *
48
+ * @param \WP_Post $post The current post object.
49
+ *
50
+ * @return string The translated text for the notice.
51
+ */
52
+ public function get_notice_text( $post ) {
53
+ if ( $this->permissions_helper->has_trashed_rewrite_and_republish_copy( $post ) ) {
54
+ return \__(
55
+ 'You can only make one Rewrite & Republish duplicate at a time, and a duplicate of this post already exists in the trash. Permanently delete it if you want to make a new duplicate.',
56
+ 'duplicate-post'
57
+ );
58
+ }
59
+
60
+ $scheduled_copy = $this->permissions_helper->has_scheduled_rewrite_and_republish_copy( $post );
61
+ if ( ! $scheduled_copy ) {
62
+ return \__(
63
+ 'A duplicate of this post was made. Please note that any changes you make to this post will be replaced when the duplicated version is republished.',
64
+ 'duplicate-post'
65
+ );
66
+ }
67
+
68
+ return \sprintf(
69
+ /* translators: %1$s: scheduled date of the copy, %2$s: scheduled time of the copy. */
70
+ \__(
71
+ 'A duplicate of this post was made, which is scheduled to replace this post on %1$s at %2$s.',
72
+ 'duplicate-post'
73
+ ),
74
+ \get_the_time( \get_option( 'date_format' ), $scheduled_copy ),
75
+ \get_the_time( \get_option( 'time_format' ), $scheduled_copy )
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Shows a notice on the Classic editor.
81
+ *
82
+ * @return void
83
+ */
84
+ public function add_admin_notice() {
85
+ if ( ! $this->permissions_helper->is_classic_editor() ) {
86
+ return;
87
+ }
88
+
89
+ $post = \get_post();
90
+
91
+ if ( \is_null( $post ) ) {
92
+ return;
93
+ }
94
+
95
+ if ( $this->permissions_helper->has_rewrite_and_republish_copy( $post ) ) {
96
+ print '<div id="message" class="notice notice-warning is-dismissible fade"><p>' .
97
+ \esc_html( $this->get_notice_text( $post ) ) .
98
+ '</p></div>';
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Shows a notice on the Block editor.
104
+ *
105
+ * @return void
106
+ */
107
+ public function add_block_editor_notice() {
108
+ $post = \get_post();
109
+
110
+ if ( \is_null( $post ) ) {
111
+ return;
112
+ }
113
+
114
+ if ( $this->permissions_helper->has_rewrite_and_republish_copy( $post ) ) {
115
+
116
+ $notice = [
117
+ 'text' => \wp_slash( $this->get_notice_text( $post ) ),
118
+ 'status' => 'warning',
119
+ 'isDismissible' => true,
120
+ ];
121
+
122
+ \wp_add_inline_script(
123
+ 'duplicate_post_edit_script',
124
+ "duplicatePostNotices.has_rewrite_and_republish_notice = '" . \wp_json_encode( $notice ) . "';",
125
+ 'before'
126
+ );
127
+ }
128
+ }
129
+ }
src/watchers/class-link-actions-watcher.php ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to watch for the link actions and show notices.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\Watchers;
9
+
10
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
11
+
12
+ /**
13
+ * Represents the Link_Actions_Watcher class.
14
+ */
15
+ class Link_Actions_Watcher {
16
+
17
+ /**
18
+ * Holds the permissions helper.
19
+ *
20
+ * @var Permissions_Helper
21
+ */
22
+ protected $permissions_helper;
23
+
24
+ /**
25
+ * Initializes the class.
26
+ *
27
+ * @param Permissions_Helper $permissions_helper The Permissions helper object.
28
+ */
29
+ public function __construct( Permissions_Helper $permissions_helper ) {
30
+ $this->permissions_helper = $permissions_helper;
31
+
32
+ $this->register_hooks();
33
+ }
34
+
35
+ /**
36
+ * Adds hooks to integrate with WordPress.
37
+ *
38
+ * @return void
39
+ */
40
+ public function register_hooks() {
41
+ \add_filter( 'removable_query_args', [ $this, 'add_removable_query_args' ], 10, 1 );
42
+ \add_action( 'admin_notices', [ $this, 'add_clone_admin_notice' ] );
43
+ \add_action( 'admin_notices', [ $this, 'add_rewrite_and_republish_admin_notice' ] );
44
+ \add_action( 'enqueue_block_editor_assets', [ $this, 'add_rewrite_and_republish_block_editor_notice' ] );
45
+ }
46
+
47
+ /**
48
+ * Adds vars to the removable query args.
49
+ *
50
+ * @param array $removable_query_args Array of query args keys.
51
+ *
52
+ * @return array The updated array of query args keys.
53
+ */
54
+ public function add_removable_query_args( $removable_query_args ) {
55
+ $removable_query_args[] = 'cloned';
56
+ $removable_query_args[] = 'rewriting';
57
+ return $removable_query_args;
58
+ }
59
+
60
+ /**
61
+ * Shows a notice after the Clone link action has succeeded.
62
+ *
63
+ * @return void
64
+ */
65
+ public function add_clone_admin_notice() {
66
+ if ( ! empty( $_REQUEST['cloned'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
67
+ if ( ! $this->permissions_helper->is_classic_editor() ) {
68
+ return;
69
+ }
70
+
71
+ $copied_posts = \intval( $_REQUEST['cloned'] ); // phpcs:ignore WordPress.Security.NonceVerification
72
+ \printf(
73
+ '<div id="message" class="notice notice-success fade"><p>' .
74
+ \esc_html(
75
+ /* translators: %s: Number of posts copied. */
76
+ \_n(
77
+ '%s item copied.',
78
+ '%s items copied.',
79
+ $copied_posts,
80
+ 'duplicate-post'
81
+ )
82
+ ) . '</p></div>',
83
+ \esc_html( $copied_posts )
84
+ );
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Shows a notice in Classic editor after the Rewrite & Republish action via link has succeeded.
90
+ *
91
+ * @return void
92
+ */
93
+ public function add_rewrite_and_republish_admin_notice() {
94
+ if ( ! empty( $_REQUEST['rewriting'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
95
+ if ( ! $this->permissions_helper->is_classic_editor() ) {
96
+ return;
97
+ }
98
+
99
+ print '<div id="message" class="notice notice-warning is-dismissible fade"><p>' .
100
+ \esc_html__(
101
+ 'You can now start rewriting your post in this duplicate of the original post. If you click "Republish", your changes will be merged into the original post and you’ll be redirected there.',
102
+ 'duplicate-post'
103
+ ) . '</p></div>';
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Shows a notice on the Block editor after the Rewrite & Republish action via link has succeeded.
109
+ *
110
+ * @return void
111
+ */
112
+ public function add_rewrite_and_republish_block_editor_notice() {
113
+ if ( ! empty( $_REQUEST['rewriting'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
114
+ $notice = [
115
+ 'text' => \wp_slash(
116
+ __(
117
+ 'You can now start rewriting your post in this duplicate of the original post. If you click "Republish", this rewritten post will replace the original post.',
118
+ 'duplicate-post'
119
+ )
120
+ ),
121
+ 'status' => 'warning',
122
+ 'isDismissible' => true,
123
+ ];
124
+
125
+ \wp_add_inline_script(
126
+ 'duplicate_post_edit_script',
127
+ "duplicatePostNotices.rewriting_notice = '" . \wp_json_encode( $notice ) . "';",
128
+ 'before'
129
+ );
130
+ }
131
+ }
132
+ }
src/watchers/class-original-post-watcher.php ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post Original post watcher class.
4
+ *
5
+ * @package Duplicate_Post
6
+ * @since 4.0
7
+ */
8
+
9
+ namespace Yoast\WP\Duplicate_Post\Watchers;
10
+
11
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
12
+
13
+ /**
14
+ * Original post watcher.
15
+ *
16
+ * Watches the original post for changes.
17
+ */
18
+ class Original_Post_Watcher {
19
+
20
+ /**
21
+ * Holds the permissions helper.
22
+ *
23
+ * @var Permissions_Helper
24
+ */
25
+ protected $permissions_helper;
26
+
27
+ /**
28
+ * Initializes the class.
29
+ *
30
+ * @param Permissions_Helper $permissions_helper The Permissions helper object.
31
+ */
32
+ public function __construct( Permissions_Helper $permissions_helper ) {
33
+ $this->permissions_helper = $permissions_helper;
34
+
35
+ $this->register_hooks();
36
+ }
37
+
38
+ /**
39
+ * Registers the hooks.
40
+ *
41
+ * @return void
42
+ */
43
+ public function register_hooks() {
44
+ \add_action( 'admin_notices', [ $this, 'add_admin_notice' ] );
45
+ \add_action( 'enqueue_block_editor_assets', [ $this, 'add_block_editor_notice' ], 11 );
46
+ }
47
+
48
+ /**
49
+ * Generates the translated text for the notice.
50
+ *
51
+ * @return string The translated text for the notice.
52
+ */
53
+ public function get_notice_text() {
54
+ return \__(
55
+ 'The original post has been edited in the meantime. If you click "Republish", this rewritten post will replace the original post.',
56
+ 'duplicate-post'
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Shows a notice on the Classic editor.
62
+ *
63
+ * @return void
64
+ */
65
+ public function add_admin_notice() {
66
+ if ( ! $this->permissions_helper->is_classic_editor() ) {
67
+ return;
68
+ }
69
+
70
+ $post = \get_post();
71
+
72
+ if ( \is_null( $post ) ) {
73
+ return;
74
+ }
75
+
76
+ if ( $this->permissions_helper->has_original_changed( $post ) ) {
77
+ print '<div id="message" class="notice notice-warning is-dismissible fade"><p>' .
78
+ \esc_html( $this->get_notice_text() ) .
79
+ '</p></div>';
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Shows a notice on the Block editor.
85
+ *
86
+ * @return void
87
+ */
88
+ public function add_block_editor_notice() {
89
+ $post = \get_post();
90
+
91
+ if ( \is_null( $post ) ) {
92
+ return;
93
+ }
94
+
95
+ if ( $this->permissions_helper->has_original_changed( $post ) ) {
96
+
97
+ $notice = [
98
+ 'text' => \wp_slash( $this->get_notice_text() ),
99
+ 'status' => 'warning',
100
+ 'isDismissible' => true,
101
+ ];
102
+
103
+ \wp_add_inline_script(
104
+ 'duplicate_post_edit_script',
105
+ "duplicatePostNotices.has_original_changed_notice = '" . \wp_json_encode( $notice ) . "';",
106
+ 'before'
107
+ );
108
+ }
109
+ }
110
+ }
src/watchers/class-republished-post-watcher.php ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post class to watch if the post has been republished for Rewrite & Republish.
4
+ *
5
+ * @package Duplicate_Post
6
+ * @since 4.0
7
+ */
8
+
9
+ namespace Yoast\WP\Duplicate_Post\Watchers;
10
+
11
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
12
+
13
+ /**
14
+ * Represents the Republished_Post_Watcher class.
15
+ */
16
+ class Republished_Post_Watcher {
17
+
18
+ /**
19
+ * Holds the permissions helper.
20
+ *
21
+ * @var Permissions_Helper
22
+ */
23
+ protected $permissions_helper;
24
+
25
+ /**
26
+ * Initializes the class.
27
+ *
28
+ * @param Permissions_Helper $permissions_helper The Permissions helper object.
29
+ */
30
+ public function __construct( Permissions_Helper $permissions_helper ) {
31
+ $this->permissions_helper = $permissions_helper;
32
+
33
+ $this->register_hooks();
34
+ }
35
+
36
+ /**
37
+ * Adds hooks to integrate with WordPress.
38
+ *
39
+ * @return void
40
+ */
41
+ public function register_hooks() {
42
+ \add_filter( 'removable_query_args', [ $this, 'add_removable_query_args' ] );
43
+ \add_action( 'admin_notices', [ $this, 'add_admin_notice' ] );
44
+ \add_action( 'enqueue_block_editor_assets', [ $this, 'add_block_editor_notice' ], 11 );
45
+ }
46
+
47
+ /**
48
+ * Adds vars to the removable query args.
49
+ *
50
+ * @param array $removable_query_args Array of query args keys.
51
+ *
52
+ * @return array The updated array of query args keys.
53
+ */
54
+ public function add_removable_query_args( $removable_query_args ) {
55
+ $removable_query_args[] = 'dprepublished';
56
+ $removable_query_args[] = 'dpcopy';
57
+ $removable_query_args[] = 'dpnonce';
58
+
59
+ return $removable_query_args;
60
+ }
61
+
62
+ /**
63
+ * Generates the translated text for the republished notice.
64
+ *
65
+ * @return string The translated text for the republished notice.
66
+ */
67
+ public function get_notice_text() {
68
+ return \__(
69
+ 'Your original post has been replaced with the rewritten post. You are now viewing the (rewritten) original post.',
70
+ 'duplicate-post'
71
+ );
72
+ }
73
+
74
+ /**
75
+ * Shows a notice on the Classic editor.
76
+ *
77
+ * @return void
78
+ */
79
+ public function add_admin_notice() {
80
+ if ( ! $this->permissions_helper->is_classic_editor() ) {
81
+ return;
82
+ }
83
+
84
+ if ( ! empty( $_REQUEST['dprepublished'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
85
+ echo '<div id="message" class="notice notice-success is-dismissible"><p>' .
86
+ \esc_html( $this->get_notice_text() ) .
87
+ '</p></div>';
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Shows a notice on the Block editor.
93
+ *
94
+ * @return void
95
+ */
96
+ public function add_block_editor_notice() {
97
+ if ( ! empty( $_REQUEST['dprepublished'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
98
+ $notice = [
99
+ 'text' => \wp_slash( $this->get_notice_text() ),
100
+ 'status' => 'success',
101
+ 'isDismissible' => true,
102
+ ];
103
+
104
+ \wp_add_inline_script(
105
+ 'duplicate_post_edit_script',
106
+ "duplicatePostNotices.republished_notice = '" . \wp_json_encode( $notice ) . "';",
107
+ 'before'
108
+ );
109
+ }
110
+ }
111
+ }
src/watchers/class-watchers.php ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Duplicate Post user interface.
4
+ *
5
+ * @package Duplicate_Post
6
+ */
7
+
8
+ namespace Yoast\WP\Duplicate_Post\Watchers;
9
+
10
+ use Yoast\WP\Duplicate_Post\Permissions_Helper;
11
+
12
+ /**
13
+ * Represents the Duplicate Post User Interface class.
14
+ */
15
+ class Watchers {
16
+
17
+ /**
18
+ * Holds the permissions helper.
19
+ *
20
+ * @var Permissions_Helper
21
+ */
22
+ protected $permissions_helper;
23
+
24
+ /**
25
+ * Holds the original post watcher.
26
+ *
27
+ * @var Original_Post_Watcher
28
+ */
29
+ protected $original_post_watcher;
30
+
31
+ /**
32
+ * Holds the copied post watcher.
33
+ *
34
+ * @var Copied_Post_Watcher
35
+ */
36
+ protected $copied_post_watcher;
37
+
38
+ /**
39
+ * Holds the bulk actions watcher.
40
+ *
41
+ * @var Bulk_Actions_Watcher
42
+ */
43
+ protected $bulk_actions_watcher;
44
+
45
+ /**
46
+ * Holds the link actions watcher.
47
+ *
48
+ * @var Link_Actions_Watcher
49
+ */
50
+ protected $link_actions_watcher;
51
+
52
+ /**
53
+ * Holds the republished post watcher.
54
+ *
55
+ * @var Republished_Post_Watcher
56
+ */
57
+ protected $republished_post_watcher;
58
+
59
+ /**
60
+ * Initializes the class.
61
+ *
62
+ * @param Permissions_Helper $permissions_helper The permissions helper object.
63
+ */
64
+ public function __construct( Permissions_Helper $permissions_helper ) {
65
+ $this->permissions_helper = $permissions_helper;
66
+ $this->copied_post_watcher = new Copied_Post_Watcher( $this->permissions_helper );
67
+ $this->original_post_watcher = new Original_Post_Watcher( $this->permissions_helper );
68
+ $this->bulk_actions_watcher = new Bulk_Actions_Watcher();
69
+ $this->link_actions_watcher = new Link_Actions_Watcher( $this->permissions_helper );
70
+ $this->republished_post_watcher = new Republished_Post_Watcher( $this->permissions_helper );
71
+ }
72
+ }
vendor/autoload.php ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload.php @generated by Composer
4
+
5
+ require_once __DIR__ . '/composer/autoload_real.php';
6
+
7
+ return ComposerAutoloaderInitbac46342df44ff7c7344b8e79f379de2::getLoader();
vendor/composer/ClassLoader.php ADDED
@@ -0,0 +1,445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Composer.
5
+ *
6
+ * (c) Nils Adermann <naderman@naderman.de>
7
+ * Jordi Boggiano <j.boggiano@seld.be>
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ namespace Composer\Autoload;
14
+
15
+ /**
16
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
17
+ *
18
+ * $loader = new \Composer\Autoload\ClassLoader();
19
+ *
20
+ * // register classes with namespaces
21
+ * $loader->add('Symfony\Component', __DIR__.'/component');
22
+ * $loader->add('Symfony', __DIR__.'/framework');
23
+ *
24
+ * // activate the autoloader
25
+ * $loader->register();
26
+ *
27
+ * // to enable searching the include path (eg. for PEAR packages)
28
+ * $loader->setUseIncludePath(true);
29
+ *
30
+ * In this example, if you try to use a class in the Symfony\Component
31
+ * namespace or one of its children (Symfony\Component\Console for instance),
32
+ * the autoloader will first look for the class under the component/
33
+ * directory, and it will then fallback to the framework/ directory if not
34
+ * found before giving up.
35
+ *
36
+ * This class is loosely based on the Symfony UniversalClassLoader.
37
+ *
38
+ * @author Fabien Potencier <fabien@symfony.com>
39
+ * @author Jordi Boggiano <j.boggiano@seld.be>
40
+ * @see http://www.php-fig.org/psr/psr-0/
41
+ * @see http://www.php-fig.org/psr/psr-4/
42
+ */
43
+ class ClassLoader
44
+ {
45
+ // PSR-4
46
+ private $prefixLengthsPsr4 = array();
47
+ private $prefixDirsPsr4 = array();
48
+ private $fallbackDirsPsr4 = array();
49
+
50
+ // PSR-0
51
+ private $prefixesPsr0 = array();
52
+ private $fallbackDirsPsr0 = array();
53
+
54
+ private $useIncludePath = false;
55
+ private $classMap = array();
56
+ private $classMapAuthoritative = false;
57
+ private $missingClasses = array();
58
+ private $apcuPrefix;
59
+
60
+ public function getPrefixes()
61
+ {
62
+ if (!empty($this->prefixesPsr0)) {
63
+ return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
64
+ }
65
+
66
+ return array();
67
+ }
68
+
69
+ public function getPrefixesPsr4()
70
+ {
71
+ return $this->prefixDirsPsr4;
72
+ }
73
+
74
+ public function getFallbackDirs()
75
+ {
76
+ return $this->fallbackDirsPsr0;
77
+ }
78
+
79
+ public function getFallbackDirsPsr4()
80
+ {
81
+ return $this->fallbackDirsPsr4;
82
+ }
83
+
84
+ public function getClassMap()
85
+ {
86
+ return $this->classMap;
87
+ }
88
+
89
+ /**
90
+ * @param array $classMap Class to filename map
91
+ */
92
+ public function addClassMap(array $classMap)
93
+ {
94
+ if ($this->classMap) {
95
+ $this->classMap = array_merge($this->classMap, $classMap);
96
+ } else {
97
+ $this->classMap = $classMap;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Registers a set of PSR-0 directories for a given prefix, either
103
+ * appending or prepending to the ones previously set for this prefix.
104
+ *
105
+ * @param string $prefix The prefix
106
+ * @param array|string $paths The PSR-0 root directories
107
+ * @param bool $prepend Whether to prepend the directories
108
+ */
109
+ public function add($prefix, $paths, $prepend = false)
110
+ {
111
+ if (!$prefix) {
112
+ if ($prepend) {
113
+ $this->fallbackDirsPsr0 = array_merge(
114
+ (array) $paths,
115
+ $this->fallbackDirsPsr0
116
+ );
117
+ } else {
118
+ $this->fallbackDirsPsr0 = array_merge(
119
+ $this->fallbackDirsPsr0,
120
+ (array) $paths
121
+ );
122
+ }
123
+
124
+ return;
125
+ }
126
+
127
+ $first = $prefix[0];
128
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
129
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
130
+
131
+ return;
132
+ }
133
+ if ($prepend) {
134
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
135
+ (array) $paths,
136
+ $this->prefixesPsr0[$first][$prefix]
137
+ );
138
+ } else {
139
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
140
+ $this->prefixesPsr0[$first][$prefix],
141
+ (array) $paths
142
+ );
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Registers a set of PSR-4 directories for a given namespace, either
148
+ * appending or prepending to the ones previously set for this namespace.
149
+ *
150
+ * @param string $prefix The prefix/namespace, with trailing '\\'
151
+ * @param array|string $paths The PSR-4 base directories
152
+ * @param bool $prepend Whether to prepend the directories
153
+ *
154
+ * @throws \InvalidArgumentException
155
+ */
156
+ public function addPsr4($prefix, $paths, $prepend = false)
157
+ {
158
+ if (!$prefix) {
159
+ // Register directories for the root namespace.
160
+ if ($prepend) {
161
+ $this->fallbackDirsPsr4 = array_merge(
162
+ (array) $paths,
163
+ $this->fallbackDirsPsr4
164
+ );
165
+ } else {
166
+ $this->fallbackDirsPsr4 = array_merge(
167
+ $this->fallbackDirsPsr4,
168
+ (array) $paths
169
+ );
170
+ }
171
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
172
+ // Register directories for a new namespace.
173
+ $length = strlen($prefix);
174
+ if ('\\' !== $prefix[$length - 1]) {
175
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
176
+ }
177
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
178
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
179
+ } elseif ($prepend) {
180
+ // Prepend directories for an already registered namespace.
181
+ $this->prefixDirsPsr4[$prefix] = array_merge(
182
+ (array) $paths,
183
+ $this->prefixDirsPsr4[$prefix]
184
+ );
185
+ } else {
186
+ // Append directories for an already registered namespace.
187
+ $this->prefixDirsPsr4[$prefix] = array_merge(
188
+ $this->prefixDirsPsr4[$prefix],
189
+ (array) $paths
190
+ );
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Registers a set of PSR-0 directories for a given prefix,
196
+ * replacing any others previously set for this prefix.
197
+ *
198
+ * @param string $prefix The prefix
199
+ * @param array|string $paths The PSR-0 base directories
200
+ */
201
+ public function set($prefix, $paths)
202
+ {
203
+ if (!$prefix) {
204
+ $this->fallbackDirsPsr0 = (array) $paths;
205
+ } else {
206
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Registers a set of PSR-4 directories for a given namespace,
212
+ * replacing any others previously set for this namespace.
213
+ *
214
+ * @param string $prefix The prefix/namespace, with trailing '\\'
215
+ * @param array|string $paths The PSR-4 base directories
216
+ *
217
+ * @throws \InvalidArgumentException
218
+ */
219
+ public function setPsr4($prefix, $paths)
220
+ {
221
+ if (!$prefix) {
222
+ $this->fallbackDirsPsr4 = (array) $paths;
223
+ } else {
224
+ $length = strlen($prefix);
225
+ if ('\\' !== $prefix[$length - 1]) {
226
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
227
+ }
228
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
229
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Turns on searching the include path for class files.
235
+ *
236
+ * @param bool $useIncludePath
237
+ */
238
+ public function setUseIncludePath($useIncludePath)
239
+ {
240
+ $this->useIncludePath = $useIncludePath;
241
+ }
242
+
243
+ /**
244
+ * Can be used to check if the autoloader uses the include path to check
245
+ * for classes.
246
+ *
247
+ * @return bool
248
+ */
249
+ public function getUseIncludePath()
250
+ {
251
+ return $this->useIncludePath;
252
+ }
253
+
254
+ /**
255
+ * Turns off searching the prefix and fallback directories for classes
256
+ * that have not been registered with the class map.
257
+ *
258
+ * @param bool $classMapAuthoritative
259
+ */
260
+ public function setClassMapAuthoritative($classMapAuthoritative)
261
+ {
262
+ $this->classMapAuthoritative = $classMapAuthoritative;
263
+ }
264
+
265
+ /**
266
+ * Should class lookup fail if not found in the current class map?
267
+ *
268
+ * @return bool
269
+ */
270
+ public function isClassMapAuthoritative()
271
+ {
272
+ return $this->classMapAuthoritative;
273
+ }
274
+
275
+ /**
276
+ * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
277
+ *
278
+ * @param string|null $apcuPrefix
279
+ */
280
+ public function setApcuPrefix($apcuPrefix)
281
+ {
282
+ $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
283
+ }
284
+
285
+ /**
286
+ * The APCu prefix in use, or null if APCu caching is not enabled.
287
+ *
288
+ * @return string|null
289
+ */
290
+ public function getApcuPrefix()
291
+ {
292
+ return $this->apcuPrefix;
293
+ }
294
+
295
+ /**
296
+ * Registers this instance as an autoloader.
297
+ *
298
+ * @param bool $prepend Whether to prepend the autoloader or not
299
+ */
300
+ public function register($prepend = false)
301
+ {
302
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
303
+ }
304
+
305
+ /**
306
+ * Unregisters this instance as an autoloader.
307
+ */
308
+ public function unregister()
309
+ {
310
+ spl_autoload_unregister(array($this, 'loadClass'));
311
+ }
312
+
313
+ /**
314
+ * Loads the given class or interface.
315
+ *
316
+ * @param string $class The name of the class
317
+ * @return bool|null True if loaded, null otherwise
318
+ */
319
+ public function loadClass($class)
320
+ {
321
+ if ($file = $this->findFile($class)) {
322
+ includeFile($file);
323
+
324
+ return true;
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Finds the path to the file where the class is defined.
330
+ *
331
+ * @param string $class The name of the class
332
+ *
333
+ * @return string|false The path if found, false otherwise
334
+ */
335
+ public function findFile($class)
336
+ {
337
+ // class map lookup
338
+ if (isset($this->classMap[$class])) {
339
+ return $this->classMap[$class];
340
+ }
341
+ if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
342
+ return false;
343
+ }
344
+ if (null !== $this->apcuPrefix) {
345
+ $file = apcu_fetch($this->apcuPrefix.$class, $hit);
346
+ if ($hit) {
347
+ return $file;
348
+ }
349
+ }
350
+
351
+ $file = $this->findFileWithExtension($class, '.php');
352
+
353
+ // Search for Hack files if we are running on HHVM
354
+ if (false === $file && defined('HHVM_VERSION')) {
355
+ $file = $this->findFileWithExtension($class, '.hh');
356
+ }
357
+
358
+ if (null !== $this->apcuPrefix) {
359
+ apcu_add($this->apcuPrefix.$class, $file);
360
+ }
361
+
362
+ if (false === $file) {
363
+ // Remember that this class does not exist.
364
+ $this->missingClasses[$class] = true;
365
+ }
366
+
367
+ return $file;
368
+ }
369
+
370
+ private function findFileWithExtension($class, $ext)
371
+ {
372
+ // PSR-4 lookup
373
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
374
+
375
+ $first = $class[0];
376
+ if (isset($this->prefixLengthsPsr4[$first])) {
377
+ $subPath = $class;
378
+ while (false !== $lastPos = strrpos($subPath, '\\')) {
379
+ $subPath = substr($subPath, 0, $lastPos);
380
+ $search = $subPath . '\\';
381
+ if (isset($this->prefixDirsPsr4[$search])) {
382
+ $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
383
+ foreach ($this->prefixDirsPsr4[$search] as $dir) {
384
+ if (file_exists($file = $dir . $pathEnd)) {
385
+ return $file;
386
+ }
387
+ }
388
+ }
389
+ }
390
+ }
391
+
392
+ // PSR-4 fallback dirs
393
+ foreach ($this->fallbackDirsPsr4 as $dir) {
394
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
395
+ return $file;
396
+ }
397
+ }
398
+
399
+ // PSR-0 lookup
400
+ if (false !== $pos = strrpos($class, '\\')) {
401
+ // namespaced class name
402
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
403
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
404
+ } else {
405
+ // PEAR-like class name
406
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
407
+ }
408
+
409
+ if (isset($this->prefixesPsr0[$first])) {
410
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
411
+ if (0 === strpos($class, $prefix)) {
412
+ foreach ($dirs as $dir) {
413
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
414
+ return $file;
415
+ }
416
+ }
417
+ }
418
+ }
419
+ }
420
+
421
+ // PSR-0 fallback dirs
422
+ foreach ($this->fallbackDirsPsr0 as $dir) {
423
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
424
+ return $file;
425
+ }
426
+ }
427
+
428
+ // PSR-0 include paths.
429
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
430
+ return $file;
431
+ }
432
+
433
+ return false;
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Scope isolated include.
439
+ *
440
+ * Prevents access to $this/self from included files.
441
+ */
442
+ function includeFile($file)
443
+ {
444
+ include $file;
445
+ }
vendor/composer/InstalledVersions.php ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Composer;
4
+
5
+ use Composer\Semver\VersionParser;
6
+
7
+
8
+
9
+
10
+
11
+
12
+ class InstalledVersions
13
+ {
14
+ private static $installed = array (
15
+ 'root' =>
16
+ array (
17
+ 'pretty_version' => 'dev-develop',
18
+ 'version' => 'dev-develop',
19
+ 'aliases' =>
20
+ array (
21
+ ),
22
+ 'reference' => 'cb31584080dcfbce345ce9164a6d80beaf277ef8',
23
+ 'name' => '__root__',
24
+ ),
25
+ 'versions' =>
26
+ array (
27
+ '__root__' =>
28
+ array (
29
+ 'pretty_version' => 'dev-develop',
30
+ 'version' => 'dev-develop',
31
+ 'aliases' =>
32
+ array (
33
+ ),
34
+ 'reference' => 'cb31584080dcfbce345ce9164a6d80beaf277ef8',
35
+ ),
36
+ 'automattic/vipwpcs' =>
37
+ array (
38
+ 'pretty_version' => '2.2.0',
39
+ 'version' => '2.2.0.0',
40
+ 'aliases' =>
41
+ array (
42
+ ),
43
+ 'reference' => '4d0612461232b313d06321f1501c3989bd6aecf9',
44
+ ),
45
+ 'dealerdirect/phpcodesniffer-composer-installer' =>
46
+ array (
47
+ 'pretty_version' => 'v0.7.0',
48
+ 'version' => '0.7.0.0',
49
+ 'aliases' =>
50
+ array (
51
+ ),
52
+ 'reference' => 'e8d808670b8f882188368faaf1144448c169c0b7',
53
+ ),
54
+ 'phpcompatibility/php-compatibility' =>
55
+ array (
56
+ 'pretty_version' => '9.3.5',
57
+ 'version' => '9.3.5.0',
58
+ 'aliases' =>
59
+ array (
60
+ ),
61
+ 'reference' => '9fb324479acf6f39452e0655d2429cc0d3914243',
62
+ ),
63
+ 'phpcompatibility/phpcompatibility-paragonie' =>
64
+ array (
65
+ 'pretty_version' => '1.3.0',
66
+ 'version' => '1.3.0.0',
67
+ 'aliases' =>
68
+ array (
69
+ ),
70
+ 'reference' => 'b862bc32f7e860d0b164b199bd995e690b4b191c',
71
+ ),
72
+ 'phpcompatibility/phpcompatibility-wp' =>
73
+ array (
74
+ 'pretty_version' => '2.1.0',
75
+ 'version' => '2.1.0.0',
76
+ 'aliases' =>
77
+ array (
78
+ ),
79
+ 'reference' => '41bef18ba688af638b7310666db28e1ea9158b2f',
80
+ ),
81
+ 'sirbrillig/phpcs-variable-analysis' =>
82
+ array (
83
+ 'pretty_version' => 'v2.9.0',
84
+ 'version' => '2.9.0.0',
85
+ 'aliases' =>
86
+ array (
87
+ ),
88
+ 'reference' => 'ff54d4ec7f2bd152d526fdabfeff639aa9b8be01',
89
+ ),
90
+ 'squizlabs/php_codesniffer' =>
91
+ array (
92
+ 'pretty_version' => '3.5.8',
93
+ 'version' => '3.5.8.0',
94
+ 'aliases' =>
95
+ array (
96
+ ),
97
+ 'reference' => '9d583721a7157ee997f235f327de038e7ea6dac4',
98
+ ),
99
+ 'wp-coding-standards/wpcs' =>
100
+ array (
101
+ 'pretty_version' => '2.3.0',
102
+ 'version' => '2.3.0.0',
103
+ 'aliases' =>
104
+ array (
105
+ ),
106
+ 'reference' => '7da1894633f168fe244afc6de00d141f27517b62',
107
+ ),
108
+ ),
109
+ );
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+ public static function getInstalledPackages()
118
+ {
119
+ return array_keys(self::$installed['versions']);
120
+ }
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+ public static function isInstalled($packageName)
131
+ {
132
+ return isset(self::$installed['versions'][$packageName]);
133
+ }
134
+
135
+
136
+
137
+
138
+
139
+
140
+
141
+
142
+
143
+
144
+
145
+
146
+
147
+
148
+ public static function satisfies(VersionParser $parser, $packageName, $constraint)
149
+ {
150
+ $constraint = $parser->parseConstraints($constraint);
151
+ $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
152
+
153
+ return $provided->matches($constraint);
154
+ }
155
+
156
+
157
+
158
+
159
+
160
+
161
+
162
+
163
+
164
+
165
+ public static function getVersionRanges($packageName)
166
+ {
167
+ if (!isset(self::$installed['versions'][$packageName])) {
168
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
169
+ }
170
+
171
+ $ranges = array();
172
+ if (isset(self::$installed['versions'][$packageName]['pretty_version'])) {
173
+ $ranges[] = self::$installed['versions'][$packageName]['pretty_version'];
174
+ }
175
+ if (array_key_exists('aliases', self::$installed['versions'][$packageName])) {
176
+ $ranges = array_merge($ranges, self::$installed['versions'][$packageName]['aliases']);
177
+ }
178
+ if (array_key_exists('replaced', self::$installed['versions'][$packageName])) {
179
+ $ranges = array_merge($ranges, self::$installed['versions'][$packageName]['replaced']);
180
+ }
181
+ if (array_key_exists('provided', self::$installed['versions'][$packageName])) {
182
+ $ranges = array_merge($ranges, self::$installed['versions'][$packageName]['provided']);
183
+ }
184
+
185
+ return implode(' || ', $ranges);
186
+ }
187
+
188
+
189
+
190
+
191
+
192
+ public static function getVersion($packageName)
193
+ {
194
+ if (!isset(self::$installed['versions'][$packageName])) {
195
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
196
+ }
197
+
198
+ if (!isset(self::$installed['versions'][$packageName]['version'])) {
199
+ return null;
200
+ }
201
+
202
+ return self::$installed['versions'][$packageName]['version'];
203
+ }
204
+
205
+
206
+
207
+
208
+
209
+ public static function getPrettyVersion($packageName)
210
+ {
211
+ if (!isset(self::$installed['versions'][$packageName])) {
212
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
213
+ }
214
+
215
+ if (!isset(self::$installed['versions'][$packageName]['pretty_version'])) {
216
+ return null;
217
+ }
218
+
219
+ return self::$installed['versions'][$packageName]['pretty_version'];
220
+ }
221
+
222
+
223
+
224
+
225
+
226
+ public static function getReference($packageName)
227
+ {
228
+ if (!isset(self::$installed['versions'][$packageName])) {
229
+ throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
230
+ }
231
+
232
+ if (!isset(self::$installed['versions'][$packageName]['reference'])) {
233
+ return null;
234
+ }
235
+
236
+ return self::$installed['versions'][$packageName]['reference'];
237
+ }
238
+
239
+
240
+
241
+
242
+
243
+ public static function getRootPackage()
244
+ {
245
+ return self::$installed['root'];
246
+ }
247
+
248
+
249
+
250
+
251
+
252
+
253
+
254
+ public static function getRawData()
255
+ {
256
+ return self::$installed;
257
+ }
258
+
259
+
260
+
261
+
262
+
263
+
264
+
265
+
266
+
267
+
268
+
269
+
270
+
271
+
272
+
273
+
274
+
275
+
276
+
277
+ public static function reload($data)
278
+ {
279
+ self::$installed = $data;
280
+ }
281
+ }
vendor/composer/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Copyright (c) Nils Adermann, Jordi Boggiano
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished
9
+ to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+
vendor/composer/autoload_classmap.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_classmap.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ 'Yoast\\WP\\Duplicate_Post\\Admin\\Options' => $baseDir . '/src/admin/class-options.php',
10
+ 'Yoast\\WP\\Duplicate_Post\\Admin\\Options_Form_Generator' => $baseDir . '/src/admin/class-options-form-generator.php',
11
+ 'Yoast\\WP\\Duplicate_Post\\Admin\\Options_Inputs' => $baseDir . '/src/admin/class-options-inputs.php',
12
+ 'Yoast\\WP\\Duplicate_Post\\Admin\\Options_Page' => $baseDir . '/src/admin/class-options-page.php',
13
+ 'Yoast\\WP\\Duplicate_Post\\Duplicate_Post' => $baseDir . '/src/class-duplicate-post.php',
14
+ 'Yoast\\WP\\Duplicate_Post\\Handlers\\Bulk_Handler' => $baseDir . '/src/handlers/class-bulk-handler.php',
15
+ 'Yoast\\WP\\Duplicate_Post\\Handlers\\Check_Changes_Handler' => $baseDir . '/src/handlers/class-check-changes-handler.php',
16
+ 'Yoast\\WP\\Duplicate_Post\\Handlers\\Handler' => $baseDir . '/src/handlers/class-handler.php',
17
+ 'Yoast\\WP\\Duplicate_Post\\Handlers\\Link_Handler' => $baseDir . '/src/handlers/class-link-handler.php',
18
+ 'Yoast\\WP\\Duplicate_Post\\Handlers\\Save_Post_Handler' => $baseDir . '/src/handlers/class-save-post-handler.php',
19
+ 'Yoast\\WP\\Duplicate_Post\\Permissions_Helper' => $baseDir . '/src/class-permissions-helper.php',
20
+ 'Yoast\\WP\\Duplicate_Post\\Post_Duplicator' => $baseDir . '/src/class-post-duplicator.php',
21
+ 'Yoast\\WP\\Duplicate_Post\\Post_Republisher' => $baseDir . '/src/class-post-republisher.php',
22
+ 'Yoast\\WP\\Duplicate_Post\\Revisions_Migrator' => $baseDir . '/src/class-revisions-migrator.php',
23
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Admin_Bar' => $baseDir . '/src/ui/class-admin-bar.php',
24
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Asset_Manager' => $baseDir . '/src/ui/class-asset-manager.php',
25
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Block_Editor' => $baseDir . '/src/ui/class-block-editor.php',
26
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Bulk_Actions' => $baseDir . '/src/ui/class-bulk-actions.php',
27
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Classic_Editor' => $baseDir . '/src/ui/class-classic-editor.php',
28
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Column' => $baseDir . '/src/ui/class-column.php',
29
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Link_Builder' => $baseDir . '/src/ui/class-link-builder.php',
30
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Metabox' => $baseDir . '/src/ui/class-metabox.php',
31
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Post_List' => $baseDir . '/src/ui/class-post-list.php',
32
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Post_States' => $baseDir . '/src/ui/class-post-states.php',
33
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Row_Actions' => $baseDir . '/src/ui/class-row-actions.php',
34
+ 'Yoast\\WP\\Duplicate_Post\\UI\\User_Interface' => $baseDir . '/src/ui/class-user-interface.php',
35
+ 'Yoast\\WP\\Duplicate_Post\\Utils' => $baseDir . '/src/class-utils.php',
36
+ 'Yoast\\WP\\Duplicate_Post\\Watchers\\Bulk_Actions_Watcher' => $baseDir . '/src/watchers/class-bulk-actions-watcher.php',
37
+ 'Yoast\\WP\\Duplicate_Post\\Watchers\\Copied_Post_Watcher' => $baseDir . '/src/watchers/class-copied-post-watcher.php',
38
+ 'Yoast\\WP\\Duplicate_Post\\Watchers\\Link_Actions_Watcher' => $baseDir . '/src/watchers/class-link-actions-watcher.php',
39
+ 'Yoast\\WP\\Duplicate_Post\\Watchers\\Original_Post_Watcher' => $baseDir . '/src/watchers/class-original-post-watcher.php',
40
+ 'Yoast\\WP\\Duplicate_Post\\Watchers\\Republished_Post_Watcher' => $baseDir . '/src/watchers/class-republished-post-watcher.php',
41
+ 'Yoast\\WP\\Duplicate_Post\\Watchers\\Watchers' => $baseDir . '/src/watchers/class-watchers.php',
42
+ );
vendor/composer/autoload_namespaces.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_namespaces.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ );
vendor/composer/autoload_psr4.php ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_psr4.php @generated by Composer
4
+
5
+ $vendorDir = dirname(dirname(__FILE__));
6
+ $baseDir = dirname($vendorDir);
7
+
8
+ return array(
9
+ );
vendor/composer/autoload_real.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_real.php @generated by Composer
4
+
5
+ class ComposerAutoloaderInitbac46342df44ff7c7344b8e79f379de2
6
+ {
7
+ private static $loader;
8
+
9
+ public static function loadClassLoader($class)
10
+ {
11
+ if ('Composer\Autoload\ClassLoader' === $class) {
12
+ require __DIR__ . '/ClassLoader.php';
13
+ }
14
+ }
15
+
16
+ /**
17
+ * @return \Composer\Autoload\ClassLoader
18
+ */
19
+ public static function getLoader()
20
+ {
21
+ if (null !== self::$loader) {
22
+ return self::$loader;
23
+ }
24
+
25
+ spl_autoload_register(array('ComposerAutoloaderInitbac46342df44ff7c7344b8e79f379de2', 'loadClassLoader'), true, true);
26
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader();
27
+ spl_autoload_unregister(array('ComposerAutoloaderInitbac46342df44ff7c7344b8e79f379de2', 'loadClassLoader'));
28
+
29
+ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
30
+ if ($useStaticLoader) {
31
+ require_once __DIR__ . '/autoload_static.php';
32
+
33
+ call_user_func(\Composer\Autoload\ComposerStaticInitbac46342df44ff7c7344b8e79f379de2::getInitializer($loader));
34
+ } else {
35
+ $map = require __DIR__ . '/autoload_namespaces.php';
36
+ foreach ($map as $namespace => $path) {
37
+ $loader->set($namespace, $path);
38
+ }
39
+
40
+ $map = require __DIR__ . '/autoload_psr4.php';
41
+ foreach ($map as $namespace => $path) {
42
+ $loader->setPsr4($namespace, $path);
43
+ }
44
+
45
+ $classMap = require __DIR__ . '/autoload_classmap.php';
46
+ if ($classMap) {
47
+ $loader->addClassMap($classMap);
48
+ }
49
+ }
50
+
51
+ $loader->register(true);
52
+
53
+ return $loader;
54
+ }
55
+ }
vendor/composer/autoload_static.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // autoload_static.php @generated by Composer
4
+
5
+ namespace Composer\Autoload;
6
+
7
+ class ComposerStaticInitbac46342df44ff7c7344b8e79f379de2
8
+ {
9
+ public static $classMap = array (
10
+ 'Yoast\\WP\\Duplicate_Post\\Admin\\Options' => __DIR__ . '/../..' . '/src/admin/class-options.php',
11
+ 'Yoast\\WP\\Duplicate_Post\\Admin\\Options_Form_Generator' => __DIR__ . '/../..' . '/src/admin/class-options-form-generator.php',
12
+ 'Yoast\\WP\\Duplicate_Post\\Admin\\Options_Inputs' => __DIR__ . '/../..' . '/src/admin/class-options-inputs.php',
13
+ 'Yoast\\WP\\Duplicate_Post\\Admin\\Options_Page' => __DIR__ . '/../..' . '/src/admin/class-options-page.php',
14
+ 'Yoast\\WP\\Duplicate_Post\\Duplicate_Post' => __DIR__ . '/../..' . '/src/class-duplicate-post.php',
15
+ 'Yoast\\WP\\Duplicate_Post\\Handlers\\Bulk_Handler' => __DIR__ . '/../..' . '/src/handlers/class-bulk-handler.php',
16
+ 'Yoast\\WP\\Duplicate_Post\\Handlers\\Check_Changes_Handler' => __DIR__ . '/../..' . '/src/handlers/class-check-changes-handler.php',
17
+ 'Yoast\\WP\\Duplicate_Post\\Handlers\\Handler' => __DIR__ . '/../..' . '/src/handlers/class-handler.php',
18
+ 'Yoast\\WP\\Duplicate_Post\\Handlers\\Link_Handler' => __DIR__ . '/../..' . '/src/handlers/class-link-handler.php',
19
+ 'Yoast\\WP\\Duplicate_Post\\Handlers\\Save_Post_Handler' => __DIR__ . '/../..' . '/src/handlers/class-save-post-handler.php',
20
+ 'Yoast\\WP\\Duplicate_Post\\Permissions_Helper' => __DIR__ . '/../..' . '/src/class-permissions-helper.php',
21
+ 'Yoast\\WP\\Duplicate_Post\\Post_Duplicator' => __DIR__ . '/../..' . '/src/class-post-duplicator.php',
22
+ 'Yoast\\WP\\Duplicate_Post\\Post_Republisher' => __DIR__ . '/../..' . '/src/class-post-republisher.php',
23
+ 'Yoast\\WP\\Duplicate_Post\\Revisions_Migrator' => __DIR__ . '/../..' . '/src/class-revisions-migrator.php',
24
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Admin_Bar' => __DIR__ . '/../..' . '/src/ui/class-admin-bar.php',
25
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Asset_Manager' => __DIR__ . '/../..' . '/src/ui/class-asset-manager.php',
26
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Block_Editor' => __DIR__ . '/../..' . '/src/ui/class-block-editor.php',
27
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Bulk_Actions' => __DIR__ . '/../..' . '/src/ui/class-bulk-actions.php',
28
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Classic_Editor' => __DIR__ . '/../..' . '/src/ui/class-classic-editor.php',
29
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Column' => __DIR__ . '/../..' . '/src/ui/class-column.php',
30
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Link_Builder' => __DIR__ . '/../..' . '/src/ui/class-link-builder.php',
31
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Metabox' => __DIR__ . '/../..' . '/src/ui/class-metabox.php',
32
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Post_List' => __DIR__ . '/../..' . '/src/ui/class-post-list.php',
33
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Post_States' => __DIR__ . '/../..' . '/src/ui/class-post-states.php',
34
+ 'Yoast\\WP\\Duplicate_Post\\UI\\Row_Actions' => __DIR__ . '/../..' . '/src/ui/class-row-actions.php',
35
+ 'Yoast\\WP\\Duplicate_Post\\UI\\User_Interface' => __DIR__ . '/../..' . '/src/ui/class-user-interface.php',
36
+ 'Yoast\\WP\\Duplicate_Post\\Utils' => __DIR__ . '/../..' . '/src/class-utils.php',
37
+ 'Yoast\\WP\\Duplicate_Post\\Watchers\\Bulk_Actions_Watcher' => __DIR__ . '/../..' . '/src/watchers/class-bulk-actions-watcher.php',
38
+ 'Yoast\\WP\\Duplicate_Post\\Watchers\\Copied_Post_Watcher' => __DIR__ . '/../..' . '/src/watchers/class-copied-post-watcher.php',
39
+ 'Yoast\\WP\\Duplicate_Post\\Watchers\\Link_Actions_Watcher' => __DIR__ . '/../..' . '/src/watchers/class-link-actions-watcher.php',
40
+ 'Yoast\\WP\\Duplicate_Post\\Watchers\\Original_Post_Watcher' => __DIR__ . '/../..' . '/src/watchers/class-original-post-watcher.php',
41
+ 'Yoast\\WP\\Duplicate_Post\\Watchers\\Republished_Post_Watcher' => __DIR__ . '/../..' . '/src/watchers/class-republished-post-watcher.php',
42
+ 'Yoast\\WP\\Duplicate_Post\\Watchers\\Watchers' => __DIR__ . '/../..' . '/src/watchers/class-watchers.php',
43
+ );
44
+
45
+ public static function getInitializer(ClassLoader $loader)
46
+ {
47
+ return \Closure::bind(function () use ($loader) {
48
+ $loader->classMap = ComposerStaticInitbac46342df44ff7c7344b8e79f379de2::$classMap;
49
+
50
+ }, null, ClassLoader::class);
51
+ }
52
+ }
vendor/composer/installed.php ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php return array (
2
+ 'root' =>
3
+ array (
4
+ 'pretty_version' => 'dev-develop',
5
+ 'version' => 'dev-develop',
6
+ 'aliases' =>
7
+ array (
8
+ ),
9
+ 'reference' => 'cb31584080dcfbce345ce9164a6d80beaf277ef8',
10
+ 'name' => '__root__',
11
+ ),
12
+ 'versions' =>
13
+ array (
14
+ '__root__' =>
15
+ array (
16
+ 'pretty_version' => 'dev-develop',
17
+ 'version' => 'dev-develop',
18
+ 'aliases' =>
19
+ array (
20
+ ),
21
+ 'reference' => 'cb31584080dcfbce345ce9164a6d80beaf277ef8',
22
+ ),
23
+ 'automattic/vipwpcs' =>
24
+ array (
25
+ 'pretty_version' => '2.2.0',
26
+ 'version' => '2.2.0.0',
27
+ 'aliases' =>
28
+ array (
29
+ ),
30
+ 'reference' => '4d0612461232b313d06321f1501c3989bd6aecf9',
31
+ ),
32
+ 'dealerdirect/phpcodesniffer-composer-installer' =>
33
+ array (
34
+ 'pretty_version' => 'v0.7.0',
35
+ 'version' => '0.7.0.0',
36
+ 'aliases' =>
37
+ array (
38
+ ),
39
+ 'reference' => 'e8d808670b8f882188368faaf1144448c169c0b7',
40
+ ),
41
+ 'phpcompatibility/php-compatibility' =>
42
+ array (
43
+ 'pretty_version' => '9.3.5',
44
+ 'version' => '9.3.5.0',
45
+ 'aliases' =>
46
+ array (
47
+ ),
48
+ 'reference' => '9fb324479acf6f39452e0655d2429cc0d3914243',
49
+ ),
50
+ 'phpcompatibility/phpcompatibility-paragonie' =>
51
+ array (
52
+ 'pretty_version' => '1.3.0',
53
+ 'version' => '1.3.0.0',
54
+ 'aliases' =>
55
+ array (
56
+ ),
57
+ 'reference' => 'b862bc32f7e860d0b164b199bd995e690b4b191c',
58
+ ),
59
+ 'phpcompatibility/phpcompatibility-wp' =>
60
+ array (
61
+ 'pretty_version' => '2.1.0',
62
+ 'version' => '2.1.0.0',
63
+ 'aliases' =>
64
+ array (
65
+ ),
66
+ 'reference' => '41bef18ba688af638b7310666db28e1ea9158b2f',
67
+ ),
68
+ 'sirbrillig/phpcs-variable-analysis' =>
69
+ array (
70
+ 'pretty_version' => 'v2.9.0',
71
+ 'version' => '2.9.0.0',
72
+ 'aliases' =>
73
+ array (
74
+ ),
75
+ 'reference' => 'ff54d4ec7f2bd152d526fdabfeff639aa9b8be01',
76
+ ),
77
+ 'squizlabs/php_codesniffer' =>
78
+ array (
79
+ 'pretty_version' => '3.5.8',
80
+ 'version' => '3.5.8.0',
81
+ 'aliases' =>
82
+ array (
83
+ ),
84
+ 'reference' => '9d583721a7157ee997f235f327de038e7ea6dac4',
85
+ ),
86
+ 'wp-coding-standards/wpcs' =>
87
+ array (
88
+ 'pretty_version' => '2.3.0',
89
+ 'version' => '2.3.0.0',
90
+ 'aliases' =>
91
+ array (
92
+ ),
93
+ 'reference' => '7da1894633f168fe244afc6de00d141f27517b62',
94
+ ),
95
+ ),
96
+ );