PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 7.2.4
Jetpack – WP Security, Backup, Speed, & Growth v7.2.4
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
429 lines 13.9 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
185 return $non_existant_post;
186 }
187 /**
188 * Filters whether to prevent sending post data to .com
189 *
190 * Passing true to the filter will prevent the post data from being sent
191 * to the WordPress.com.
192 * Instead we pass data that will still enable us to do a checksum against the
193 * Jetpacks data but will prevent us from displaying the data on in the API as well as
194 * other services.
195 *
196 * @since 4.2.0
197 *
198 * @param boolean false prevent post data from being synced to WordPress.com
199 * @param mixed $post WP_POST object
200 */
201 if ( apply_filters( 'jetpack_sync_prevent_sending_post_data', false, $post ) ) {
202 // We only send the bare necessary object to be able to create a checksum.
203 $blocked_post = new stdClass();
204 $blocked_post->ID = $post->ID;
205 $blocked_post->post_modified = $post->post_modified;
206 $blocked_post->post_modified_gmt = $post->post_modified_gmt;
207 $blocked_post->post_status = 'jetpack_sync_blocked';
208
209 return $blocked_post;
210 }
211
212 // lets not do oembed just yet.
213 $this->remove_embed();
214
215 if ( 0 < strlen( $post->post_password ) ) {
216 $post->post_password = 'auto-' . wp_generate_password( 10, false );
217 }
218
219 /** This filter is already documented in core. wp-includes/post-template.php */
220 if ( Jetpack_Sync_Settings::get_setting( 'render_filtered_content' ) && $post_type->public ) {
221 global $shortcode_tags;
222 /**
223 * Filter prevents some shortcodes from expanding.
224 *
225 * Since we can can expand some type of shortcode better on the .com side and make the
226 * expansion more relevant to contexts. For example [galleries] and subscription emails
227 *
228 * @since 4.5.0
229 *
230 * @param array of shortcode tags to remove.
231 */
232 $shortcodes_to_remove = apply_filters(
233 'jetpack_sync_do_not_expand_shortcodes',
234 array(
235 'gallery',
236 'slideshow',
237 )
238 );
239 $removed_shortcode_callbacks = array();
240 foreach ( $shortcodes_to_remove as $shortcode ) {
241 if ( isset( $shortcode_tags[ $shortcode ] ) ) {
242 $removed_shortcode_callbacks[ $shortcode ] = $shortcode_tags[ $shortcode ];
243 }
244 }
245
246 array_map( 'remove_shortcode', array_keys( $removed_shortcode_callbacks ) );
247
248 $post->post_content_filtered = apply_filters( 'the_content', $post->post_content );
249 $post->post_excerpt_filtered = apply_filters( 'the_excerpt', $post->post_excerpt );
250
251 foreach ( $removed_shortcode_callbacks as $shortcode => $callback ) {
252 add_shortcode( $shortcode, $callback );
253 }
254 }
255
256 $this->add_embed();
257
258 if ( has_post_thumbnail( $post->ID ) ) {
259 $image_attributes = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'full' );
260 if ( is_array( $image_attributes ) && isset( $image_attributes[0] ) ) {
261 $post->featured_image = $image_attributes[0];
262 }
263 }
264
265 $post->permalink = get_permalink( $post->ID );
266 $post->shortlink = wp_get_shortlink( $post->ID );
267
268 if ( function_exists( 'amp_get_permalink' ) ) {
269 $post->amp_permalink = amp_get_permalink( $post->ID );
270 }
271
272 return $post;
273 }
274
275 public function save_published( $new_status, $old_status, $post ) {
276 if ( 'publish' === $new_status && 'publish' !== $old_status ) {
277 $this->just_published[ $post->ID ] = true;
278 }
279
280 $this->previous_status[ $post->ID ] = $old_status;
281 }
282
283 /*
284 * When publishing or updating a post, the Gutenberg editor sends two requests:
285 * 1. sent to WP REST API endpoint `wp-json/wp/v2/posts/$id`
286 * 2. sent to wp-admin/post.php `?post=$id&action=edit&classic-editor=1&meta_box=1`
287 *
288 * The 2nd request is to update post meta, which is not supported on WP REST API.
289 * When syncing post data, we will include if this was a meta box update.
290 */
291 public function is_gutenberg_meta_box_update() {
292 return (
293 isset( $_POST['action'], $_GET['classic-editor'], $_GET['meta_box'] ) &&
294 'editpost' === $_POST['action'] &&
295 '1' === $_GET['classic-editor'] &&
296 '1' === $_GET['meta_box']
297 );
298 }
299
300 public function wp_insert_post( $post_ID, $post = null, $update = null ) {
301 if ( ! is_numeric( $post_ID ) || is_null( $post ) ) {
302 return;
303 }
304
305 // workaround for https://github.com/woocommerce/woocommerce/issues/18007
306 if ( $post && 'shop_order' === $post->post_type ) {
307 $post = get_post( $post_ID );
308 }
309
310 $previous_status = isset( $this->previous_status[ $post_ID ] ) ?
311 $this->previous_status[ $post_ID ] :
312 self::DEFAULT_PREVIOUS_STATE;
313
314 $just_published = isset( $this->just_published[ $post_ID ] ) ?
315 $this->just_published[ $post_ID ] :
316 false;
317
318 $state = array(
319 'is_auto_save' => (bool) Jetpack_Constants::get_constant( 'DOING_AUTOSAVE' ),
320 'previous_status' => $previous_status,
321 'just_published' => $just_published,
322 'is_gutenberg_meta_box_update' => $this->is_gutenberg_meta_box_update(),
323 );
324 /**
325 * Filter that is used to add to the post flags ( meta data ) when a post gets published
326 *
327 * @since 5.8.0
328 *
329 * @param int $post_ID the post ID
330 * @param mixed $post WP_POST object
331 * @param bool $update Whether this is an existing post being updated or not.
332 * @param mixed $state state
333 *
334 * @module sync
335 */
336 do_action( 'jetpack_sync_save_post', $post_ID, $post, $update, $state );
337 unset( $this->previous_status[ $post_ID ] );
338 $this->send_published( $post_ID, $post );
339 }
340
341 public function send_published( $post_ID, $post ) {
342 if ( ! isset( $this->just_published[ $post_ID ] ) ) {
343 return;
344 }
345
346 // Post revisions cause race conditions where this send_published add the action before the actual post gets synced
347 if ( wp_is_post_autosave( $post ) || wp_is_post_revision( $post ) ) {
348 return;
349 }
350
351 $post_flags = array(
352 'post_type' => $post->post_type,
353 );
354
355 $author_user_object = get_user_by( 'id', $post->post_author );
356 if ( $author_user_object ) {
357 $post_flags['author'] = array(
358 'id' => $post->post_author,
359 'wpcom_user_id' => get_user_meta( $post->post_author, 'wpcom_user_id', true ),
360 'display_name' => $author_user_object->display_name,
361 'email' => $author_user_object->user_email,
362 'translated_role' => Jetpack::translate_user_to_role( $author_user_object ),
363 );
364 }
365
366 /**
367 * Filter that is used to add to the post flags ( meta data ) when a post gets published
368 *
369 * @since 4.4.0
370 *
371 * @param mixed array post flags that are added to the post
372 * @param mixed $post WP_POST object
373 */
374 $flags = apply_filters( 'jetpack_published_post_flags', $post_flags, $post );
375
376 /**
377 * Action that gets synced when a post type gets published.
378 *
379 * @since 4.4.0
380 *
381 * @param int $post_ID
382 * @param mixed array $flags post flags that are added to the post
383 */
384 do_action( 'jetpack_published_post', $post_ID, $flags );
385 unset( $this->just_published[ $post_ID ] );
386
387 /**
388 * Send additional sync action for Activity Log when post is a Customizer publish
389 */
390 if ( 'customize_changeset' == $post->post_type ) {
391 $post_content = json_decode( $post->post_content, true );
392 foreach ( $post_content as $key => $value ) {
393 // Skip if it isn't a widget
394 if ( 'widget_' != substr( $key, 0, strlen( 'widget_' ) ) ) {
395 continue;
396 }
397 // Change key from "widget_archives[2]" to "archives-2"
398 $key = str_replace( 'widget_', '', $key );
399 $key = str_replace( '[', '-', $key );
400 $key = str_replace( ']', '', $key );
401
402 global $wp_registered_widgets;
403 if ( isset( $wp_registered_widgets[ $key ] ) ) {
404 $widget_data = array(
405 'name' => $wp_registered_widgets[ $key ]['name'],
406 'id' => $key,
407 'title' => $value['value']['title'],
408 );
409 do_action( 'jetpack_widget_edited', $widget_data );
410 }
411 }
412 }
413 }
414
415 public function expand_post_ids( $args ) {
416 $post_ids = $args[0];
417
418 $posts = array_filter( array_map( array( 'WP_Post', 'get_instance' ), $post_ids ) );
419 $posts = array_map( array( $this, 'filter_post_content_and_add_links' ), $posts );
420 $posts = array_values( $posts ); // reindex in case posts were deleted
421
422 return array(
423 $posts,
424 $this->get_metadata( $post_ids, 'post', Jetpack_Sync_Settings::get_setting( 'post_meta_whitelist' ) ),
425 $this->get_term_relationships( $post_ids ),
426 );
427 }
428 }
429