PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 9.7.2
Jetpack – WP Security, Backup, Speed, & Growth v9.7.2
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / modules / copy-post.php

copy-post.php in Jetpack – WP Security, Backup, Speed, & Growth 9.7.2, at modules/copy-post.php

349 lines 11.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Module Name: Copy Post
4 * Module Description: Enable the option to copy entire posts and pages, including tags and settings
5 * Sort Order: 15
6 * First Introduced: 7.0
7 * Requires Connection: No
8 * Auto Activate: No
9 * Module Tags: Writing
10 * Feature: Writing
11 * Additional Search Queries: copy, duplicate
12 */
13
14 /**
15 * Copy Post class.
16 */
17 class Jetpack_Copy_Post {
18 /**
19 * Jetpack_Copy_Post_By_Param constructor.
20 * Add row actions to post/page/CPT listing screens.
21 * Process any `?copy` param if on a create new post/page/CPT screen.
22 *
23 * @return void
24 */
25 public function __construct() {
26 if ( 'edit.php' === $GLOBALS['pagenow'] ) {
27 add_filter( 'post_row_actions', array( $this, 'add_row_action' ), 10, 2 );
28 add_filter( 'page_row_actions', array( $this, 'add_row_action' ), 10, 2 );
29 return;
30 }
31
32 if ( ! empty( $_GET['jetpack-copy'] ) && 'post-new.php' === $GLOBALS['pagenow'] ) {
33 add_action( 'wp_insert_post', array( $this, 'update_post_data' ), 10, 3 );
34 add_filter( 'pre_option_default_post_format', '__return_empty_string' );
35 }
36 }
37
38 /**
39 * Update the new (target) post data with the source post data.
40 *
41 * @param int $target_post_id Target post ID.
42 * @param WP_Post $post Target post object (not used).
43 * @param bool $update Whether this is an existing post being updated or not.
44 * @return void
45 */
46 public function update_post_data( $target_post_id, $post, $update ) {
47 // This `$update` check avoids infinite loops of trying to update our updated post.
48 if ( $update ) {
49 return;
50 }
51
52 $source_post = get_post( $_GET['jetpack-copy'] );
53 if ( ! $source_post instanceof WP_Post ||
54 ! $this->user_can_access_post( $source_post->ID ) ||
55 ! $this->validate_post_type( $source_post ) ) {
56 return;
57 }
58
59 $update_results = array(
60 'update_content' => $this->update_content( $source_post, $target_post_id ),
61 'update_featured_image' => $this->update_featured_image( $source_post, $target_post_id ),
62 'update_post_format' => $this->update_post_format( $source_post, $target_post_id ),
63 'update_likes_sharing' => $this->update_likes_sharing( $source_post, $target_post_id ),
64 'update_post_type_terms' => $this->update_post_type_terms( $source_post, $target_post_id ),
65 );
66
67 // Required to satisfy get_default_post_to_edit(), which has these filters after post creation.
68 add_filter( 'default_title', array( $this, 'filter_title' ), 10, 2 );
69 add_filter( 'default_content', array( $this, 'filter_content' ), 10, 2 );
70 add_filter( 'default_excerpt', array( $this, 'filter_excerpt' ), 10, 2 );
71
72 // Required to avoid the block editor from adding default blocks according to post format.
73 add_filter( 'block_editor_settings', array( $this, 'remove_post_format_template' ) );
74
75 /**
76 * Fires after all updates have been performed, and default content filters have been added.
77 * Allows for any cleanup or post operations, and default content filters can be removed or modified.
78 *
79 * @module copy-post
80 *
81 * @since 7.0.0
82 *
83 * @param WP_Post $source_post Post object that was copied.
84 * @param int $target_post_id Target post ID.
85 * @param array $update_results Results of all update operations, allowing action to be taken.
86 */
87 do_action( 'jetpack_copy_post', $source_post, $target_post_id, $update_results );
88 }
89
90 /**
91 * Determine if the current user has edit access to the source post.
92 *
93 * @param int $post_id Source post ID (the post being copied).
94 * @return bool True if user has the meta cap of `edit_post` for the given post ID, false otherwise.
95 */
96 protected function user_can_access_post( $post_id ) {
97 return current_user_can( 'edit_post', $post_id );
98 }
99
100 /**
101 * Update the target post's title, content, excerpt, categories, and tags.
102 *
103 * @param WP_Post $source_post Post object to be copied.
104 * @param int $target_post_id Target post ID.
105 * @return int 0 on failure, or the updated post ID on success.
106 */
107 protected function update_content( $source_post, $target_post_id ) {
108 $data = array(
109 'ID' => $target_post_id,
110 'post_title' => $source_post->post_title,
111 'post_content' => $source_post->post_content,
112 'post_excerpt' => $source_post->post_excerpt,
113 'comment_status' => $source_post->comment_status,
114 'ping_status' => $source_post->ping_status,
115 'post_category' => wp_get_post_categories( $source_post->ID ),
116 'post_password' => $source_post->post_password,
117 'tags_input' => $source_post->tags_input,
118 );
119
120 /**
121 * Fires just before the target post is updated with its new data.
122 * Allows for final data adjustments before updating the target post.
123 *
124 * @module copy-post
125 *
126 * @since 7.0.0
127 *
128 * @param array $data Post data with which to update the target (new) post.
129 * @param WP_Post $source_post Post object being copied.
130 * @param int $target_post_id Target post ID.
131 */
132 $data = apply_filters( 'jetpack_copy_post_data', $data, $source_post, $target_post_id );
133 return wp_update_post( $data );
134 }
135
136 /**
137 * Update terms for post types.
138 *
139 * @param WP_Post $source_post Post object to be copied.
140 * @param int $target_post_id Target post ID.
141 * @return array Results of attempts to set each term to the target (new) post.
142 */
143 protected function update_post_type_terms( $source_post, $target_post_id ) {
144 $results = array();
145
146 $bypassed_post_types = apply_filters( 'jetpack_copy_post_bypassed_post_types', array( 'post', 'page' ), $source_post, $target_post_id );
147 if ( in_array( $source_post->post_type, $bypassed_post_types, true ) ) {
148 return $results;
149 }
150
151 $taxonomies = get_object_taxonomies( $source_post, 'objects' );
152 foreach ( $taxonomies as $taxonomy ) {
153 $terms = wp_get_post_terms( $source_post->ID, $taxonomy->name, array( 'fields' => 'ids' ) );
154 $results[] = wp_set_post_terms( $target_post_id, $terms, $taxonomy->name );
155 }
156
157 return $results;
158 }
159
160 /**
161 * Update the target post's featured image.
162 *
163 * @param WP_Post $source_post Post object to be copied.
164 * @param int $target_post_id Target post ID.
165 * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
166 */
167 protected function update_featured_image( $source_post, $target_post_id ) {
168 $featured_image_id = get_post_thumbnail_id( $source_post );
169 return update_post_meta( $target_post_id, '_thumbnail_id', $featured_image_id );
170 }
171
172 /**
173 * Update the target post's post format.
174 *
175 * @param WP_Post $source_post Post object to be copied.
176 * @param int $target_post_id Target post ID.
177 * @return array|WP_Error|false WP_Error on error, array of affected term IDs on success.
178 */
179 protected function update_post_format( $source_post, $target_post_id ) {
180 $post_format = get_post_format( $source_post );
181 return set_post_format( $target_post_id, $post_format );
182 }
183
184 /**
185 * Ensure the block editor doesn't modify the source post content for non-standard post formats.
186 *
187 * @param array $settings Settings to be passed into the block editor.
188 * @return array Settings with any `template` key removed.
189 */
190 public function remove_post_format_template( $settings ) {
191 unset( $settings['template'] );
192 return $settings;
193 }
194
195 /**
196 * Update the target post's Likes and Sharing statuses.
197 *
198 * @param WP_Post $source_post Post object to be copied.
199 * @param int $target_post_id Target post ID.
200 * @return array Array with the results of each update action.
201 */
202 protected function update_likes_sharing( $source_post, $target_post_id ) {
203 $likes = get_post_meta( $source_post->ID, 'switch_like_status', true );
204 $sharing = get_post_meta( $source_post->ID, 'sharing_disabled', true );
205
206 if ( '' !== $likes ) {
207 $likes_result = update_post_meta( $target_post_id, 'switch_like_status', $likes );
208 } else {
209 $likes_result = null;
210 }
211
212 if ( '' !== $sharing ) {
213 $sharing_result = update_post_meta( $target_post_id, 'sharing_disabled', $sharing );
214 } else {
215 $sharing_result = null;
216 }
217
218 return array(
219 'likes' => $likes_result,
220 'sharing' => $sharing_result,
221 );
222 }
223
224 /**
225 * Update the target post's title.
226 *
227 * @param string $post_title Post title determined by `get_default_post_to_edit()`.
228 * @param WP_Post $post Post object of newly-inserted post.
229 * @return string Updated post title from source post.
230 */
231 public function filter_title( $post_title, $post ) {
232 return $post->post_title;
233 }
234
235 /**
236 * Update the target post's content (`post_content`).
237 *
238 * @param string $post_content Post content determined by `get_default_post_to_edit()`.
239 * @param WP_Post $post Post object of newly-inserted post.
240 * @return string Updated post content from source post.
241 */
242 public function filter_content( $post_content, $post ) {
243 return $post->post_content;
244 }
245
246 /**
247 * Update the target post's excerpt.
248 *
249 * @param string $post_excerpt Post excerpt determined by `get_default_post_to_edit()`.
250 * @param WP_Post $post Post object of newly-inserted post.
251 * @return string Updated post excerpt from source post.
252 */
253 public function filter_excerpt( $post_excerpt, $post ) {
254 return $post->post_excerpt;
255 }
256
257 /**
258 * Validate the post type to be used for the target post.
259 *
260 * @param WP_Post $post Post object of current post in listing.
261 * @return bool True if the post type is in a list of supported psot types; false otherwise.
262 */
263 protected function validate_post_type( $post ) {
264 /**
265 * Fires when determining if the "Copy" row action should be made available.
266 * Allows overriding supported post types.
267 *
268 * @module copy-post
269 *
270 * @since 7.0.0
271 *
272 * @param array Post types supported by default.
273 * @param WP_Post $post Post object of current post in listing.
274 */
275 $valid_post_types = apply_filters(
276 'jetpack_copy_post_post_types',
277 array(
278 'post',
279 'page',
280 'jetpack-testimonial',
281 'jetpack-portfolio',
282 ),
283 $post
284 );
285 return in_array( $post->post_type, $valid_post_types, true );
286 }
287
288 /**
289 * Add a "Copy" row action to supported posts/pages/CPTs on list views.
290 *
291 * @param array $actions Existing actions.
292 * @param WP_Post $post Post object of current post in list.
293 * @return array Array of updated row actions.
294 */
295 public function add_row_action( $actions, $post ) {
296 if ( ! $this->user_can_access_post( $post->ID ) ||
297 ! $post instanceof WP_Post ||
298 ! $this->validate_post_type( $post ) ) {
299 return $actions;
300 }
301
302 $edit_url = add_query_arg(
303 array(
304 'post_type' => $post->post_type,
305 'jetpack-copy' => $post->ID,
306 ),
307 admin_url( 'post-new.php' )
308 );
309 $edit_action = array(
310 'jetpack-copy' => sprintf(
311 '<a href="%s" aria-label="%s">%s</a>',
312 esc_url( $edit_url ),
313 esc_attr__( 'Copy this post.', 'jetpack' ),
314 esc_html__( 'Copy', 'jetpack' )
315 ),
316 );
317
318 // Insert the Copy action before the Trash action.
319 $edit_offset = array_search( 'trash', array_keys( $actions ), true );
320 $updated_actions = array_merge(
321 array_slice( $actions, 0, $edit_offset ),
322 $edit_action,
323 array_slice( $actions, $edit_offset )
324 );
325
326 /**
327 * Fires after the new Copy action has been added to the row actions.
328 * Allows changes to the action presentation, or other final checks.
329 *
330 * @module copy-post
331 *
332 * @since 7.0.0
333 *
334 * @param array $updated_actions Updated row actions with the Copy Post action.
335 * @param array $actions Original row actions passed to this filter.
336 * @param WP_Post $post Post object of current post in listing.
337 */
338 return apply_filters( 'jetpack_copy_post_row_actions', $updated_actions, $actions, $post );
339 }
340 }
341
342 /**
343 * Instantiate an instance of Jetpack_Copy_Post on the `admin_init` hook.
344 */
345 function jetpack_copy_post_init() {
346 new Jetpack_Copy_Post();
347 }
348 add_action( 'admin_init', 'jetpack_copy_post_init' );
349