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
andRewrite & Republish
links.
Download this release
Release Info
Developer | lopo |
Plugin | Duplicate Post |
Version | 4.0 |
Comparing to | |
See all releases |
Code changes from version 3.2.6 to 4.0
- compat/duplicate-post-gutenberg.php +39 -0
- compat/duplicate-post-jetpack.php +37 -13
- compat/duplicate-post-wpml.php +66 -35
- duplicate-post-options.css → css/duplicate-post-options.css +1 -0
- css/duplicate-post.css +91 -0
- duplicate-post-admin.php +555 -712
- duplicate-post-common.php +66 -213
- duplicate-post-options.php +18 -793
- duplicate-post.css +0 -46
- duplicate-post.php +54 -24
- duplicate_post_admin_script.js +0 -35
- js/dist/duplicate-post-edit-400.js +1 -0
- js/dist/duplicate-post-options-400.js +1 -0
- js/dist/duplicate-post-quick-edit-400.js +1 -0
- js/dist/duplicate-post-strings-400.js +1 -0
- readme.txt +18 -10
- src/admin/class-options-form-generator.php +306 -0
- src/admin/class-options-inputs.php +78 -0
- src/admin/class-options-page.php +179 -0
- src/admin/class-options.php +317 -0
- src/admin/views/options.php +252 -0
- src/class-duplicate-post.php +84 -0
- src/class-permissions-helper.php +296 -0
- src/class-post-duplicator.php +384 -0
- src/class-post-republisher.php +425 -0
- src/class-revisions-migrator.php +71 -0
- src/class-utils.php +196 -0
- src/handlers/class-bulk-handler.php +138 -0
- src/handlers/class-check-changes-handler.php +166 -0
- src/handlers/class-handler.php +81 -0
- src/handlers/class-link-handler.php +238 -0
- src/handlers/class-save-post-handler.php +68 -0
- src/ui/class-admin-bar.php +190 -0
- src/ui/class-asset-manager.php +160 -0
- src/ui/class-block-editor.php +193 -0
- src/ui/class-bulk-actions.php +86 -0
- src/ui/class-classic-editor.php +337 -0
- src/ui/class-column.php +171 -0
- src/ui/class-link-builder.php +101 -0
- src/ui/class-metabox.php +121 -0
- src/ui/class-post-list.php +137 -0
- src/ui/class-post-states.php +71 -0
- src/ui/class-row-actions.php +147 -0
- src/ui/class-user-interface.php +132 -0
- src/watchers/class-bulk-actions-watcher.php +93 -0
- src/watchers/class-copied-post-watcher.php +129 -0
- src/watchers/class-link-actions-watcher.php +132 -0
- src/watchers/class-original-post-watcher.php +110 -0
- src/watchers/class-republished-post-watcher.php +111 -0
- src/watchers/class-watchers.php +72 -0
- vendor/autoload.php +7 -0
- vendor/composer/ClassLoader.php +445 -0
- vendor/composer/InstalledVersions.php +281 -0
- vendor/composer/LICENSE +21 -0
- vendor/composer/autoload_classmap.php +42 -0
- vendor/composer/autoload_namespaces.php +9 -0
- vendor/composer/autoload_psr4.php +9 -0
- vendor/composer/autoload_real.php +55 -0
- vendor/composer/autoload_static.php +52 -0
- 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 .= '&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 |
-
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
15 |
-
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
-
$
|
19 |
|
20 |
-
return $
|
21 |
}
|
22 |
|
23 |
-
|
24 |
-
|
|
|
|
|
|
|
|
|
25 |
WPCom_Markdown::get_instance()->unload_markdown_for_posts();
|
26 |
}
|
27 |
|
28 |
-
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
24 |
-
if (!empty($trid)) {
|
25 |
-
$translations = $sitepress->get_element_translations($trid);
|
26 |
-
$new_trid
|
27 |
-
foreach ($translations as $code => $details) {
|
28 |
-
if ($code
|
29 |
if ( $details->element_id ) {
|
30 |
$translation = get_post( $details->element_id );
|
31 |
-
if (
|
|
|
|
|
32 |
$new_post_id = duplicate_post_create_duplicate( $translation, $status );
|
33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
}
|
35 |
}
|
36 |
}
|
37 |
-
$duplicated_posts[ $post->ID ] = $post_id;
|
38 |
}
|
39 |
}
|
40 |
|
41 |
-
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
return;
|
|
|
5 |
|
6 |
-
require_once
|
7 |
|
8 |
-
|
9 |
-
|
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 |
-
|
27 |
-
|
28 |
-
|
|
|
29 |
duplicate_post_plugin_upgrade();
|
30 |
|
31 |
-
if (
|
32 |
-
|
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 |
-
|
46 |
-
|
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 |
-
|
73 |
-
|
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')
|
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')
|
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 |
-
|
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
|
106 |
return;
|
|
|
107 |
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
|
|
114 |
);
|
115 |
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
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 |
-
|
145 |
-
|
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 |
-
|
|
|
|
|
|
|
202 |
update_option( 'duplicate_post_version', duplicate_post_get_current_version() );
|
203 |
}
|
204 |
|
205 |
/**
|
206 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
207 |
*/
|
208 |
function duplicate_post_show_update_notice() {
|
209 |
-
if(!current_user_can( 'manage_options'))
|
|
|
|
|
210 |
|
211 |
$current_screen = get_current_screen();
|
212 |
-
if (
|
|
|
213 |
empty( $current_screen->base ) ||
|
214 |
-
( $current_screen->base !==
|
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 |
-
|
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="'
|
246 |
-
|
247 |
-
<div style="margin: 0.5em">'
|
248 |
-
|
|
|
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 |
-
*
|
270 |
*
|
271 |
-
* @return
|
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&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 “%s”', '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 “%s”', '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 |
-
*
|
|
|
|
|
|
|
|
|
|
|
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,
|
577 |
|
578 |
-
$post_taxonomies = get_object_taxonomies($post->post_type);
|
579 |
-
//
|
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 (
|
586 |
-
|
|
|
|
|
587 |
$taxonomies_blacklist[] = 'post_format';
|
588 |
}
|
589 |
-
|
590 |
-
|
591 |
-
|
592 |
-
|
593 |
-
|
594 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
595 |
}
|
596 |
-
wp_set_object_terms($new_id, $terms, $taxonomy);
|
597 |
}
|
598 |
}
|
599 |
}
|
600 |
|
601 |
/**
|
602 |
-
*
|
603 |
-
|
604 |
-
|
605 |
-
|
606 |
-
|
607 |
-
|
608 |
-
|
|
|
|
|
|
|
|
|
|
|
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'; //
|
616 |
-
$meta_blacklist[] = '_edit_last'; //
|
617 |
-
if(get_option('duplicate_post_copytemplate')
|
618 |
$meta_blacklist[] = '_wp_page_template';
|
619 |
}
|
620 |
-
if(get_option('duplicate_post_copythumbnail')
|
621 |
$meta_blacklist[] = '_thumbnail_id';
|
622 |
}
|
623 |
|
624 |
-
$meta_blacklist = apply_filters_deprecated( 'duplicate_post_blacklist_filter'
|
625 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
626 |
|
627 |
-
$meta_blacklist_string = '('.implode(')|('
|
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('#^'
|
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 |
-
*
|
675 |
-
|
676 |
-
|
677 |
-
|
678 |
-
|
679 |
-
|
680 |
-
|
681 |
-
|
682 |
-
|
683 |
-
|
684 |
-
|
685 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
695 |
-
$file_array['name']
|
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 |
-
|
702 |
continue;
|
703 |
}
|
704 |
$new_post_author = wp_get_current_user();
|
705 |
-
$cloned_child
|
706 |
-
|
707 |
-
|
708 |
-
|
709 |
-
|
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 |
-
|
718 |
-
if(
|
719 |
-
|
720 |
}
|
721 |
|
|
|
|
|
|
|
|
|
722 |
}
|
723 |
}
|
724 |
|
725 |
/**
|
726 |
-
*
|
|
|
|
|
|
|
|
|
727 |
*/
|
728 |
-
function duplicate_post_copy_children($new_id, $post, $status = ''){
|
729 |
-
//
|
730 |
-
$children = get_posts(
|
731 |
-
|
732 |
-
|
733 |
-
|
734 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
735 |
}
|
736 |
}
|
737 |
|
738 |
/**
|
739 |
-
*
|
|
|
|
|
|
|
740 |
*/
|
741 |
-
function duplicate_post_copy_comments($new_id, $post){
|
742 |
-
$comments = get_comments(
|
743 |
-
|
744 |
-
|
745 |
-
|
746 |
-
|
|
|
|
|
747 |
|
748 |
$old_id_to_new = array();
|
749 |
-
foreach ($comments as $comment){
|
750 |
-
//
|
751 |
-
if( $comment->comment_type ===
|
752 |
-
|
|
|
|
|
753 |
$commentdata = array(
|
754 |
-
'comment_post_ID'
|
755 |
-
'comment_author'
|
756 |
'comment_author_email' => $comment->comment_author_email,
|
757 |
-
'comment_author_url'
|
758 |
-
'comment_content'
|
759 |
-
'comment_type'
|
760 |
-
'comment_parent'
|
761 |
-
'user_id'
|
762 |
-
'comment_author_IP'
|
763 |
-
'comment_agent'
|
764 |
-
'comment_karma'
|
765 |
-
'comment_approved'
|
766 |
);
|
767 |
-
if(get_option('duplicate_post_copydate')
|
768 |
-
$commentdata['comment_date']
|
769 |
-
$commentdata['comment_date_gmt'] = get_gmt_from_date($comment->comment_date);
|
|
|
|
|
|
|
|
|
|
|
770 |
}
|
771 |
-
$
|
772 |
-
$old_id_to_new[$comment->comment_ID] = $new_comment_id;
|
773 |
}
|
774 |
}
|
775 |
|
776 |
/**
|
777 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
778 |
*/
|
779 |
-
function duplicate_post_create_duplicate($post, $status = '', $parent_id = '') {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
780 |
|
781 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
782 |
|
783 |
-
if (!duplicate_post_is_post_type_enabled($post->post_type) && $post->post_type
|
784 |
-
wp_die(
|
|
|
|
|
|
|
|
|
|
|
|
|
785 |
|
786 |
-
$new_post_status = (empty($status))? $post->post_status: $status;
|
|
|
787 |
|
788 |
-
if ($post->post_type
|
789 |
-
$prefix = sanitize_text_field(get_option('duplicate_post_title_prefix'));
|
790 |
-
$suffix = sanitize_text_field(get_option('duplicate_post_title_suffix'));
|
791 |
-
|
792 |
-
if (get_option('duplicate_post_copytitle') == 1) {
|
793 |
$title = $post->post_title;
|
794 |
-
if (!empty($prefix))
|
795 |
-
|
796 |
-
}
|
|
|
|
|
|
|
|
|
797 |
$title = ' ';
|
798 |
}
|
799 |
-
$title = trim($prefix
|
800 |
|
801 |
/*
|
802 |
* Not sure we should force a title. Instead, we should respect what WP does.
|
803 |
-
*
|
804 |
-
*
|
805 |
-
*
|
806 |
-
*
|
807 |
*/
|
808 |
-
|
|
|
809 |
$new_post_status = 'draft';
|
810 |
} else {
|
811 |
-
if ( 'publish'
|
812 |
-
//
|
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
|
827 |
$new_post_author_id = $new_post_author->ID;
|
828 |
-
if ( get_option('duplicate_post_copyauthor')
|
829 |
-
//
|
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
|
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')
|
849 |
$post_name = '';
|
850 |
}
|
|
|
851 |
|
852 |
$new_post = array(
|
853 |
-
|
854 |
-
|
855 |
-
|
856 |
-
|
857 |
-
|
858 |
-
|
859 |
-
|
860 |
-
|
861 |
-
|
862 |
-
|
863 |
-
|
864 |
-
|
865 |
-
|
866 |
-
|
867 |
);
|
868 |
|
869 |
-
if(get_option('duplicate_post_copydate')
|
870 |
-
$
|
871 |
-
$new_post['
|
|
|
872 |
}
|
873 |
|
874 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
897 |
-
|
898 |
-
|
899 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
900 |
}
|
901 |
return $links;
|
902 |
}
|
903 |
|
904 |
-
|
905 |
-
|
906 |
-
|
907 |
-
|
908 |
-
|
909 |
-
|
910 |
-
|
911 |
-
|
912 |
-
|
913 |
-
|
914 |
-
|
915 |
-
|
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 |
-
|
939 |
-
$bulk_actions['duplicate_post_clone'] = esc_html__( 'Clone', 'duplicate-post');
|
940 |
-
return $bulk_actions;
|
941 |
-
}
|
942 |
|
943 |
-
|
944 |
-
|
945 |
-
|
946 |
-
|
947 |
-
|
948 |
-
|
949 |
-
|
950 |
-
|
951 |
-
|
952 |
-
|
953 |
-
|
954 |
-
|
955 |
-
|
956 |
-
|
957 |
-
|
958 |
-
|
959 |
-
|
960 |
-
|
961 |
-
|
962 |
-
|
|
|
963 |
|
964 |
-
|
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&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 |
-
*
|
|
|
|
|
|
|
5 |
*/
|
6 |
-
function duplicate_post_is_current_user_allowed_to_copy() {
|
7 |
-
return current_user_can('copy_posts');
|
8 |
-
}
|
9 |
|
10 |
-
|
11 |
-
|
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 |
-
*
|
|
|
|
|
|
|
21 |
*/
|
22 |
-
function
|
23 |
-
|
|
|
|
|
|
|
|
|
24 |
}
|
25 |
|
26 |
-
// Template tag
|
27 |
/**
|
28 |
-
*
|
29 |
-
*
|
30 |
*
|
31 |
-
* @param int
|
32 |
* @param string $context Optional, default to display. How to write the '&', defaults to '&'.
|
33 |
-
* @param
|
34 |
* @return string
|
35 |
*/
|
36 |
function duplicate_post_get_clone_post_link( $id = 0, $context = 'display', $draft = true ) {
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
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.'&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 |
-
$
|
105 |
-
$
|
106 |
-
$post_type_object = get_post_type_object( $post->post_type );
|
107 |
|
108 |
-
if ( $
|
109 |
-
return
|
110 |
-
'<a href="%s" aria-label="%s">%s</a>',
|
111 |
-
get_edit_post_link( $post->ID ),
|
112 |
-
esc_attr( sprintf( __( 'Edit “%s”', '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 “%s”', '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 “%s”', 'default' ), $title ) ),
|
132 |
-
$title
|
133 |
-
);
|
134 |
-
}
|
135 |
}
|
136 |
-
return $title;
|
137 |
-
}
|
138 |
|
139 |
-
|
140 |
-
|
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 |
-
|
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 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
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 |
-
|
194 |
-
if(!
|
195 |
-
|
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 |
-
|
217 |
-
|
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 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
|
|
|
|
240 |
}
|
241 |
|
242 |
/**
|
243 |
-
*
|
|
|
|
|
|
|
|
|
244 |
*/
|
245 |
-
function
|
246 |
-
return ($
|
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 '&'.
|
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 |
-
*
|
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 |
-
* @
|
|
|
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 |
-
|
172 |
-
tabs[x].addEventListener('focus', focusEventHandler);
|
173 |
-
};
|
174 |
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
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 |
-
|
192 |
-
|
193 |
-
|
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 |
-
|
592 |
-
|
593 |
-
|
594 |
-
|
595 |
-
|
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 |
-
|
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:
|
7 |
* Author: Enrico Battocchi & Team Yoast
|
8 |
* Author URI: https://yoast.com
|
9 |
* Text Domain: duplicate-post
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
*/
|
11 |
|
12 |
-
|
13 |
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
(at your option) any later version.
|
18 |
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
GNU General Public License for more details.
|
23 |
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
*/
|
28 |
|
29 |
-
|
30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
}
|
32 |
|
33 |
-
|
34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
|
36 |
/**
|
37 |
-
*
|
38 |
*/
|
39 |
function duplicate_post_load_plugin_textdomain() {
|
40 |
-
|
41 |
}
|
42 |
add_action( 'plugins_loaded', 'duplicate_post_load_plugin_textdomain' );
|
43 |
|
44 |
-
add_filter(
|
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
|
70 |
|
71 |
-
if (is_admin()){
|
72 |
-
|
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:
|
6 |
-
Tested up to: 5.
|
7 |
-
Stable tag:
|
8 |
-
Requires PHP: 5.
|
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.
|
66 |
-
2.
|
67 |
-
3.
|
68 |
-
4.
|
69 |
-
5.
|
70 |
-
6.
|
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 “%s”', '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 “%s”', '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 “%s”', '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 (“%s”)', '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( '← 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 . '&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 “%s”', '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 “%s”', '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 “%s”', '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 |
+
);
|