PluginProbe
Ultimate Post Kit / trunk
Ultimate Post Kit vtrunk
4.2.1 4.2.2 4.2.3 4.5.0 4.5.2 4.5.3 4.2.0 4.1.18 4.1.17 4.1.16 4.1.15 4.1.14 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.0.9 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.1.5 All 145 releases
ultimate-post-kit / includes / class-duplicator.php

class-duplicator.php in Ultimate Post Kit trunk, at includes/class-duplicator.php

230 lines 10.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace UltimatePostKit\Includes;
4
5 use Elementor\Core\Files\CSS\Post as Post_CSS;
6
7 if (!defined('ABSPATH')) {
8 exit;
9 }
10 // Exit if accessed directly
11
12 /**
13 * Duplicator Class
14 */
15
16 if (!class_exists(__NAMESPACE__ . '\\BdThemes_Duplicator')) :
17 class BdThemes_Duplicator {
18
19 public function __construct() {
20 add_action('admin_action_ultimate_post_kit_duplicate_as_draft', [$this, 'bdt_duplicate_as_draft']);
21 add_filter('post_row_actions', [$this, 'bdt_duplicate_post_link'], 10, 2);
22 add_filter('page_row_actions', [$this, 'bdt_duplicate_post_link'], 10, 2);
23 }
24
25 public function bdt_duplicate_as_draft() {
26
27 if (!current_user_can('edit_posts')) {
28 wp_die('You don\'t have permission to duplicate it; please go back!');
29 }
30
31 if (!(isset($_GET['post']) || isset($_POST['post']) || (isset($_REQUEST['action']) && 'ultimate_post_kit_duplicate_as_draft' == $_REQUEST['action']))) {
32 wp_die('No post to duplicate has been supplied!');
33 }
34
35 /**
36 * get the original post id
37 *
38 * This has to be read before the nonce check because the nonce action is bound
39 * to the post being duplicated (see bdt_duplicate_post_link()). The value is
40 * cast to an integer and used only to build that action string; nothing is read
41 * or written with it until the nonce and capability checks below have passed.
42 */
43 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing -- only used to construct the nonce action, which is verified on the next statement.
44 $post_id = isset($_GET['post']) ? absint($_GET['post']) : absint($_POST['post'] ?? 0);
45
46 /**
47 * Nonce verification.
48 *
49 * The nonce is bound to the post being duplicated, so one nonce cannot be
50 * replayed against every other post (and post type) on the site.
51 */
52 if (!isset($_GET['duplicate_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['duplicate_nonce'])), 'upk_duplicate_post_' . $post_id)) {
53 return;
54 }
55
56 /**
57 * and all the original post data then
58 */
59 $post = get_post($post_id);
60
61 if (!$post) {
62 wp_die(esc_html('Failed. Not Found Post: ' . $post_id));
63 }
64
65 /**
66 * Authorise against THIS post, not against a generic role capability.
67 *
68 * edit_others_posts is only the capability for the built-in 'post' type; it
69 * grants nothing over a post type registered with its own capability set. Use
70 * the meta capability so WordPress maps it through the target post type, and
71 * check create_posts separately because duplicating creates a new object.
72 */
73 if (!current_user_can('edit_post', $post_id)) {
74 wp_die('You don\'t have permission to duplicate it; please go back!');
75 }
76
77 $post_type_object = get_post_type_object($post->post_type);
78
79 if (!$post_type_object || !current_user_can($post_type_object->cap->create_posts)) {
80 wp_die('You don\'t have permission to duplicate it; please go back!');
81 }
82
83 $this->duplicate_edit_post($post_id);
84 }
85
86 /**
87 * duplicate edit post
88 */
89 public function duplicate_edit_post($post_id) {
90 global $wpdb;
91 /**
92 * and all the original post data then
93 */
94 $bdt_post = get_post($post_id);
95 /**
96 * if you don't want current user to be the new post author,
97 * then change next couple of lines to this: $new_post_author = $post->post_author;
98 */
99 $bdt_current_user = wp_get_current_user();
100 $bdt_new_post_author = $bdt_current_user->ID;
101
102 /**
103 * if post data exists, create the post duplicate
104 */
105 if (isset($bdt_post) && $bdt_post != null) {
106 /**
107 * new post data array
108 */
109 $bdt_args = [
110 'post_status' => 'draft',
111 /* translators: %1$s post title */
112 'post_title' => sprintf(__('%1$s - [Duplicated]', 'ultimate-post-kit'), $bdt_post->post_title),
113 'post_type' => $bdt_post->post_type,
114 'post_name' => $bdt_post->post_name,
115 'post_content' => $bdt_post->post_content,
116 'post_excerpt' => $bdt_post->post_excerpt,
117 'post_author' => $bdt_new_post_author,
118 'post_parent' => $bdt_post->post_parent,
119 'post_password' => $bdt_post->post_password,
120 'comment_status' => $bdt_post->comment_status,
121 'ping_status' => $bdt_post->ping_status,
122 'menu_order' => $bdt_post->menu_order,
123 'to_ping' => $bdt_post->to_ping,
124 ];
125
126 /**
127 * insert the post by wp_insert_post() function
128 */
129 $bdt_new_post_id = wp_insert_post($bdt_args);
130
131 /**
132 * get all current post terms ad set them to the new post draft
133 */
134 $bdt_taxonomies = get_object_taxonomies($bdt_post->post_type);
135
136 /**
137 * returns array of taxonomy names for post type, ex array("category", "post_tag");
138 */
139
140 foreach ($bdt_taxonomies as $bdt_taxonomy) {
141 $bdt_post_terms = wp_get_object_terms($post_id, $bdt_taxonomy, ['fields' => 'slugs']);
142 wp_set_object_terms($bdt_new_post_id, $bdt_post_terms, $bdt_taxonomy, false);
143 }
144
145 /**
146 * duplicate all post meta just in two SQL queries
147 */
148 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- one-off admin duplicate action, caching not applicable.
149 $bdt_post_meta_infos = $wpdb->get_results($wpdb->prepare("SELECT meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d", $post_id));
150
151 if (is_array($bdt_post_meta_infos)) {
152 $bdt_sql_query_sel = [];
153 $bdt_sql_values = [];
154
155 foreach ($bdt_post_meta_infos as $bdt_meta_info) {
156 $bdt_sql_query_sel[] = '( %d, %s, %s )';
157 $bdt_sql_values[] = $bdt_new_post_id;
158 $bdt_sql_values[] = $bdt_meta_info->meta_key;
159 $bdt_sql_values[] = wp_slash($bdt_meta_info->meta_value);
160 }
161
162 if (!empty($bdt_sql_query_sel)) {
163 $bdt_sql_query = "INSERT INTO {$wpdb->postmeta} ( post_id, meta_key, meta_value ) VALUES " . implode(', ', $bdt_sql_query_sel);
164 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Query is built only from static "%d, %s, %s" placeholders and the trusted {$wpdb->postmeta} table name; all values are bound via $wpdb->prepare().
165 $wpdb->query($wpdb->prepare($bdt_sql_query, $bdt_sql_values));
166 }
167
168 /**
169 * fix template type issues
170 */
171 $source_type = get_post_meta($post_id, '_elementor_template_type', true);
172 delete_post_meta($bdt_new_post_id, '_elementor_template_type');
173 update_post_meta($bdt_new_post_id, '_elementor_template_type', $source_type);
174 }
175
176 $css = Post_CSS::create($bdt_new_post_id);
177 $css->update();
178
179 /**
180 * finally, redirect to the edit post screen for the new draft
181 */
182
183 $bdt_all_post_types = get_post_types([], 'names');
184
185 foreach ($bdt_all_post_types as $bdt_key => $bdt_value) {
186 $bdt_names[] = $bdt_key;
187 }
188
189 $current_post_type = get_post_type($post_id);
190
191 if (is_array($bdt_names) && in_array($current_post_type, $bdt_names)) {
192 wp_safe_redirect(admin_url('edit.php?post_type=' . $current_post_type));
193 exit;
194 }
195
196 exit;
197 } else {
198 wp_die(esc_html('Failed. Not Found Post: ' . $post_id));
199 }
200 }
201
202
203 public function bdt_duplicate_post_link($actions, $post) {
204
205 if (current_user_can('edit_post', $post->ID)) {
206 if ($post->post_type == 'post') {
207 $actions['duplicate'] = '<a href="' . wp_nonce_url('admin.php?action=ultimate_post_kit_duplicate_as_draft&post=' . $post->ID, 'upk_duplicate_post_' . $post->ID, 'duplicate_nonce') . '" title="Duplicate this post" rel="permalink">' . esc_html_x("Duplicate Post", "Admin String", "ultimate-post-kit") . '</a>';
208 } elseif ($post->post_type == 'page') {
209 $actions['duplicate'] = '<a href="' . wp_nonce_url('admin.php?action=ultimate_post_kit_duplicate_as_draft&post=' . $post->ID, 'upk_duplicate_post_' . $post->ID, 'duplicate_nonce') . '" title="Duplicate this page" rel="permalink">' . esc_html_x("Duplicate Page", "Admin String", "ultimate-post-kit") . '</a>';
210 } elseif ($post->post_type == 'elementor_library') {
211 $actions['duplicate'] = '<a href="' . wp_nonce_url('admin.php?action=ultimate_post_kit_duplicate_as_draft&post=' . $post->ID, 'upk_duplicate_post_' . $post->ID, 'duplicate_nonce') . '" title="Duplicate this template" rel="permalink">' . esc_html_x("Duplicate Template", "Admin String", "ultimate-post-kit") . '</a>';
212 }
213 }
214 return $actions;
215 }
216 }
217 endif;
218
219 /**
220 * Instantiate the namespaced class.
221 *
222 * The guard above and this statement must resolve to the same class. An
223 * unqualified class_exists() string is always resolved against the global
224 * namespace, so a sibling plugin declaring a global \BdThemes_Duplicator
225 * (Live Copy Paste does) used to satisfy the old guard and skip the
226 * declaration, while this line still asked for
227 * UltimatePostKit\Includes\BdThemes_Duplicator -- a fatal error.
228 */
229 new BdThemes_Duplicator();
230