PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 7.4.3
Jetpack – WP Security, Backup, Speed, & Growth v7.4.3
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 14.4.2 All 500 releases
jetpack / sync / class.jetpack-sync-module-posts.php
class.jetpack-sync-module-posts.php
432 lines 14.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 require_once dirname( __FILE__ ) . '/class.jetpack-sync-settings.php';
4
5 class Jetpack_Sync_Module_Posts extends Jetpack_Sync_Module {
6
7 private $just_published = array();
8 private $previous_status = array();
9 private $action_handler;
10 private $import_end = false;
11
12 const DEFAULT_PREVIOUS_STATE = 'new';
13
14 public function name() {
15 return 'posts';
16 }
17
18 public function get_object_by_id( $object_type, $id ) {
19 if ( $object_type === 'post' && $post = get_post( intval( $id ) ) ) {
20 return $this->filter_post_content_and_add_links( $post );
21 }
22
23 return false;
24 }
25
26 public function init_listeners( $callable ) {
27 $this->action_handler = $callable;
28
29 add_action( 'wp_insert_post', array( $this, 'wp_insert_post' ), 11, 3 );
30 add_action( 'jetpack_sync_save_post', $callable, 10, 4 );
31
32 add_action( 'deleted_post', $callable, 10 );
33 add_action( 'jetpack_published_post', $callable, 10, 2 );
34
35 add_action( 'transition_post_status', array( $this, 'save_published' ), 10, 3 );
36 add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_post', array( $this, 'filter_blacklisted_post_types' ) );
37
38 // listen for meta changes
39 $this->init_listeners_for_meta_type( 'post', $callable );
40 $this->init_meta_whitelist_handler( 'post', array( $this, 'filter_meta' ) );
41
42 add_action( 'jetpack_daily_akismet_meta_cleanup_before', array( $this, 'daily_akismet_meta_cleanup_before' ) );
43 add_action( 'jetpack_daily_akismet_meta_cleanup_after', array( $this, 'daily_akismet_meta_cleanup_after' ) );
44 add_action( 'jetpack_post_meta_batch_delete', $callable, 10, 2 );
45
46 }
47
48
49 public function daily_akismet_meta_cleanup_before( $feedback_ids ) {
50 remove_action( 'deleted_post_meta', $this->action_handler );
51 /**
52 * Used for syncing deletion of batch post meta
53 *
54 * @since 6.1.0
55 *
56 * @module sync
57 *
58 * @param array $feedback_ids feedback post IDs
59 * @param string $meta_key to be deleted
60 */
61 do_action( 'jetpack_post_meta_batch_delete', $feedback_ids, '_feedback_akismet_values' );
62 }
63
64 public function daily_akismet_meta_cleanup_after( $feedback_ids ) {
65 add_action( 'deleted_post_meta', $this->action_handler );
66 }
67
68 public function init_full_sync_listeners( $callable ) {
69 add_action( 'jetpack_full_sync_posts', $callable ); // also sends post meta
70 }
71
72 public function init_before_send() {
73 add_filter( 'jetpack_sync_before_send_jetpack_sync_save_post', array( $this, 'expand_jetpack_sync_save_post' ) );
74
75 // full sync
76 add_filter( 'jetpack_sync_before_send_jetpack_full_sync_posts', array( $this, 'expand_post_ids' ) );
77 }
78
79 public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
80 global $wpdb;
81
82 return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_posts', $wpdb->posts, 'ID', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
83 }
84
85 public function estimate_full_sync_actions( $config ) {
86 global $wpdb;
87
88 $query = "SELECT count(*) FROM $wpdb->posts WHERE " . $this->get_where_sql( $config );
89 $count = $wpdb->get_var( $query );
90
91 return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
92 }
93
94 private function get_where_sql( $config ) {
95 $where_sql = Jetpack_Sync_Settings::get_blacklisted_post_types_sql();
96
97 // config is a list of post IDs to sync
98 if ( is_array( $config ) ) {
99 $where_sql .= ' AND ID IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
100 }
101
102 return $where_sql;
103 }
104
105 function get_full_sync_actions() {
106 return array( 'jetpack_full_sync_posts' );
107 }
108
109 /**
110 * Process content before send
111 *
112 * @param array $args wp_insert_post arguments
113 *
114 * @return array
115 */
116 function expand_jetpack_sync_save_post( $args ) {
117 list( $post_id, $post, $update, $previous_state ) = $args;
118 return array( $post_id, $this->filter_post_content_and_add_links( $post ), $update, $previous_state );
119 }
120
121 function filter_blacklisted_post_types( $args ) {
122 $post = $args[1];
123
124 if ( in_array( $post->post_type, Jetpack_Sync_Settings::get_setting( 'post_types_blacklist' ) ) ) {
125 return false;
126 }
127
128 return $args;
129 }
130
131 // Meta
132 function filter_meta( $args ) {
133 if ( $this->is_post_type_allowed( $args[1] ) && $this->is_whitelisted_post_meta( $args[2] ) ) {
134 return $args;
135 }
136
137 return false;
138 }
139
140 function is_whitelisted_post_meta( $meta_key ) {
141 // _wpas_skip_ is used by publicize
142 return in_array( $meta_key, Jetpack_Sync_Settings::get_setting( 'post_meta_whitelist' ) ) || wp_startswith( $meta_key, '_wpas_skip_' );
143 }
144
145 function is_post_type_allowed( $post_id ) {
146 $post = get_post( intval( $post_id ) );
147 if ( $post->post_type ) {
148 return ! in_array( $post->post_type, Jetpack_Sync_Settings::get_setting( 'post_types_blacklist' ) );
149 }
150 return false;
151 }
152
153 function remove_embed() {
154 global $wp_embed;
155 remove_filter( 'the_content', array( $wp_embed, 'run_shortcode' ), 8 );
156 // remove the embed shortcode since we would do the part later.
157 remove_shortcode( 'embed' );
158 // Attempts to embed all URLs in a post
159 remove_filter( 'the_content', array( $wp_embed, 'autoembed' ), 8 );
160 }
161
162 function add_embed() {
163 global $wp_embed;
164 add_filter( 'the_content', array( $wp_embed, 'run_shortcode' ), 8 );
165 // Shortcode placeholder for strip_shortcodes()
166 add_shortcode( 'embed', '__return_false' );
167 // Attempts to embed all URLs in a post
168 add_filter( 'the_content', array( $wp_embed, 'autoembed' ), 8 );
169 }
170
171 // Expands wp_insert_post to include filtered content
172 function filter_post_content_and_add_links( $post_object ) {
173 global $post;
174 $post = $post_object;
175
176 // return non existant post
177 $post_type = get_post_type_object( $post->post_type );
178 if ( empty( $post_type ) || ! is_object( $post_type ) ) {
179 $non_existant_post = new stdClass();
180 $non_existant_post->ID = $post->ID;
181 $non_existant_post->post_modified = $post->post_modified;
182 $non_existant_post->post_modified_gmt = $post->post_modified_gmt;
183 $non_existant_post->post_status = 'jetpack_sync_non_registered_post_type';
184 $non_existant_post->post_type = $post->post_type;
185
186 return $non_existant_post;
187 }
188 /**
189 * Filters whether to prevent sending post data to .com
190 *
191 * Passing true to the filter will prevent the post data from being sent
192 * to the WordPress.com.
193 * Instead we pass data that will still enable us to do a checksum against the
194 * Jetpacks data but will prevent us from displaying the data on in the API as well as
195 * other services.
196 *
197 * @since 4.2.0
198 *
199 * @param boolean false prevent post data from being synced to WordPress.com
200 * @param mixed $post WP_POST object
201 */
202 if ( apply_filters( 'jetpack_sync_prevent_sending_post_data', false, $post ) ) {
203 // We only send the bare necessary object to be able to create a checksum.
204 $blocked_post = new stdClass();
205 $blocked_post->ID = $post->ID;
206 $blocked_post->post_modified = $post->post_modified;
207 $blocked_post->post_modified_gmt = $post->post_modified_gmt;
208 $blocked_post->post_status = 'jetpack_sync_blocked';
209 $blocked_post->post_type = $post->post_type;
210
211 return $blocked_post;
212 }
213
214 // lets not do oembed just yet.
215 $this->remove_embed();
216
217 if ( 0 < strlen( $post->post_password ) ) {
218 $post->post_password = 'auto-' . wp_generate_password( 10, false );
219 }
220
221 /** This filter is already documented in core. wp-includes/post-template.php */
222 if ( Jetpack_Sync_Settings::get_setting( 'render_filtered_content' ) && $post_type->public ) {
223 global $shortcode_tags;
224 /**
225 * Filter prevents some shortcodes from expanding.
226 *
227 * Since we can can expand some type of shortcode better on the .com side and make the
228 * expansion more relevant to contexts. For example [galleries] and subscription emails
229 *
230 * @since 4.5.0
231 *
232 * @param array of shortcode tags to remove.
233 */
234 $shortcodes_to_remove = apply_filters(
235 'jetpack_sync_do_not_expand_shortcodes',
236 array(
237 'gallery',
238 'slideshow',
239 )
240 );
241 $removed_shortcode_callbacks = array();
242 foreach ( $shortcodes_to_remove as $shortcode ) {
243 if ( isset( $shortcode_tags[ $shortcode ] ) ) {
244 $removed_shortcode_callbacks[ $shortcode ] = $shortcode_tags[ $shortcode ];
245 }
246 }
247
248 array_map( 'remove_shortcode', array_keys( $removed_shortcode_callbacks ) );
249
250 $post->post_content_filtered = apply_filters( 'the_content', $post->post_content );
251 $post->post_excerpt_filtered = apply_filters( 'the_excerpt', $post->post_excerpt );
252
253 foreach ( $removed_shortcode_callbacks as $shortcode => $callback ) {
254 add_shortcode( $shortcode, $callback );
255 }
256 }
257
258 $this->add_embed();
259
260 if ( has_post_thumbnail( $post->ID ) ) {
261 $image_attributes = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'full' );
262 if ( is_array( $image_attributes ) && isset( $image_attributes[0] ) ) {
263 $post->featured_image = $image_attributes[0];
264 }
265 }
266
267 $post->permalink = get_permalink( $post->ID );
268 $post->shortlink = wp_get_shortlink( $post->ID );
269
270 if ( function_exists( 'amp_get_permalink' ) ) {
271 $post->amp_permalink = amp_get_permalink( $post->ID );
272 }
273
274 return $post;
275 }
276
277 public function save_published( $new_status, $old_status, $post ) {
278 if ( 'publish' === $new_status && 'publish' !== $old_status ) {
279 $this->just_published[ $post->ID ] = true;
280 }
281
282 $this->previous_status[ $post->ID ] = $old_status;
283 }
284
285 /*
286 * When publishing or updating a post, the Gutenberg editor sends two requests:
287 * 1. sent to WP REST API endpoint `wp-json/wp/v2/posts/$id`
288 * 2. sent to wp-admin/post.php `?post=$id&action=edit&classic-editor=1&meta_box=1`
289 *
290 * The 2nd request is to update post meta, which is not supported on WP REST API.
291 * When syncing post data, we will include if this was a meta box update.
292 */
293 public function is_gutenberg_meta_box_update() {
294 return (
295 isset( $_POST['action'], $_GET['classic-editor'], $_GET['meta_box'] ) &&
296 'editpost' === $_POST['action'] &&
297 '1' === $_GET['classic-editor'] &&
298 '1' === $_GET['meta_box']
299 );
300 }
301
302 public function wp_insert_post( $post_ID, $post = null, $update = null ) {
303 if ( ! is_numeric( $post_ID ) || is_null( $post ) ) {
304 return;
305 }
306
307 // workaround for https://github.com/woocommerce/woocommerce/issues/18007
308 if ( $post && 'shop_order' === $post->post_type ) {
309 $post = get_post( $post_ID );
310 }
311
312 $previous_status = isset( $this->previous_status[ $post_ID ] ) ?
313 $this->previous_status[ $post_ID ] :
314 self::DEFAULT_PREVIOUS_STATE;
315
316 $just_published = isset( $this->just_published[ $post_ID ] ) ?
317 $this->just_published[ $post_ID ] :
318 false;
319
320 $state = array(
321 'is_auto_save' => (bool) Jetpack_Constants::get_constant( 'DOING_AUTOSAVE' ),
322 'previous_status' => $previous_status,
323 'just_published' => $just_published,
324 'is_gutenberg_meta_box_update' => $this->is_gutenberg_meta_box_update(),
325 );
326 /**
327 * Filter that is used to add to the post flags ( meta data ) when a post gets published
328 *
329 * @since 5.8.0
330 *
331 * @param int $post_ID the post ID
332 * @param mixed $post WP_POST object
333 * @param bool $update Whether this is an existing post being updated or not.
334 * @param mixed $state state
335 *
336 * @module sync
337 */
338 do_action( 'jetpack_sync_save_post', $post_ID, $post, $update, $state );
339 unset( $this->previous_status[ $post_ID ] );
340 $this->send_published( $post_ID, $post );
341 }
342
343 public function send_published( $post_ID, $post ) {
344 if ( ! isset( $this->just_published[ $post_ID ] ) ) {
345 return;
346 }
347
348 // Post revisions cause race conditions where this send_published add the action before the actual post gets synced
349 if ( wp_is_post_autosave( $post ) || wp_is_post_revision( $post ) ) {
350 return;
351 }
352
353 $post_flags = array(
354 'post_type' => $post->post_type,
355 );
356
357 $author_user_object = get_user_by( 'id', $post->post_author );
358 if ( $author_user_object ) {
359 $post_flags['author'] = array(
360 'id' => $post->post_author,
361 'wpcom_user_id' => get_user_meta( $post->post_author, 'wpcom_user_id', true ),
362 'display_name' => $author_user_object->display_name,
363 'email' => $author_user_object->user_email,
364 'translated_role' => Jetpack::translate_user_to_role( $author_user_object ),
365 );
366 }
367
368 /**
369 * Filter that is used to add to the post flags ( meta data ) when a post gets published
370 *
371 * @since 4.4.0
372 *
373 * @param mixed array post flags that are added to the post
374 * @param mixed $post WP_POST object
375 */
376 $flags = apply_filters( 'jetpack_published_post_flags', $post_flags, $post );
377
378 /**
379 * Action that gets synced when a post type gets published.
380 *
381 * @since 4.4.0
382 *
383 * @param int $post_ID
384 * @param mixed array $flags post flags that are added to the post
385 */
386 do_action( 'jetpack_published_post', $post_ID, $flags );
387 unset( $this->just_published[ $post_ID ] );
388
389 /**
390 * Send additional sync action for Activity Log when post is a Customizer publish
391 */
392 if ( 'customize_changeset' == $post->post_type ) {
393 $post_content = json_decode( $post->post_content, true );
394 foreach ( $post_content as $key => $value ) {
395 // Skip if it isn't a widget
396 if ( 'widget_' != substr( $key, 0, strlen( 'widget_' ) ) ) {
397 continue;
398 }
399 // Change key from "widget_archives[2]" to "archives-2"
400 $key = str_replace( 'widget_', '', $key );
401 $key = str_replace( '[', '-', $key );
402 $key = str_replace( ']', '', $key );
403
404 global $wp_registered_widgets;
405 if ( isset( $wp_registered_widgets[ $key ] ) ) {
406 $widget_data = array(
407 'name' => $wp_registered_widgets[ $key ]['name'],
408 'id' => $key,
409 'title' => $value['value']['title'],
410 );
411 do_action( 'jetpack_widget_edited', $widget_data );
412 }
413 }
414 }
415 }
416
417 public function expand_post_ids( $args ) {
418 list( $post_ids, $previous_interval_end) = $args;
419
420 $posts = array_filter( array_map( array( 'WP_Post', 'get_instance' ), $post_ids ) );
421 $posts = array_map( array( $this, 'filter_post_content_and_add_links' ), $posts );
422 $posts = array_values( $posts ); // reindex in case posts were deleted
423
424 return array(
425 $posts,
426 $this->get_metadata( $post_ids, 'post', Jetpack_Sync_Settings::get_setting( 'post_meta_whitelist' ) ),
427 $this->get_term_relationships( $post_ids ),
428 $previous_interval_end
429 );
430 }
431 }
432