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-themes.php
class.jetpack-sync-module-themes.php
613 lines 18.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 class Jetpack_Sync_Module_Themes extends Jetpack_Sync_Module {
4 function name() {
5 return 'themes';
6 }
7
8 public function init_listeners( $callable ) {
9 add_action( 'switch_theme', array( $this, 'sync_theme_support' ), 10, 3 );
10 add_action( 'jetpack_sync_current_theme_support', $callable, 10, 2 );
11 add_action( 'upgrader_process_complete', array( $this, 'check_upgrader' ), 10, 2 );
12 add_action( 'jetpack_installed_theme', $callable, 10, 2 );
13 add_action( 'jetpack_updated_themes', $callable, 10, 2 );
14 add_action( 'delete_site_transient_update_themes', array( $this, 'detect_theme_deletion' ) );
15 add_action( 'jetpack_deleted_theme', $callable, 10, 2 );
16 add_filter( 'wp_redirect', array( $this, 'detect_theme_edit' ) );
17 add_action( 'jetpack_edited_theme', $callable, 10, 2 );
18 add_action( 'wp_ajax_edit-theme-plugin-file', array( $this, 'theme_edit_ajax' ), 0 );
19 add_action( 'update_site_option_allowedthemes', array( $this, 'sync_network_allowed_themes_change' ), 10, 4 );
20 add_action( 'jetpack_network_disabled_themes', $callable, 10, 2 );
21 add_action( 'jetpack_network_enabled_themes', $callable, 10, 2 );
22
23 // Sidebar updates.
24 add_action( 'update_option_sidebars_widgets', array( $this, 'sync_sidebar_widgets_actions' ), 10, 2 );
25
26 add_action( 'jetpack_widget_added', $callable, 10, 4 );
27 add_action( 'jetpack_widget_removed', $callable, 10, 4 );
28 add_action( 'jetpack_widget_moved_to_inactive', $callable, 10, 2 );
29 add_action( 'jetpack_cleared_inactive_widgets', $callable );
30 add_action( 'jetpack_widget_reordered', $callable, 10, 2 );
31 add_filter( 'widget_update_callback', array( $this, 'sync_widget_edit' ), 10, 4 );
32 add_action( 'jetpack_widget_edited', $callable );
33 }
34
35 public function sync_widget_edit( $instance, $new_instance, $old_instance, $widget_object ) {
36 if ( empty( $old_instance ) ) {
37 return $instance;
38 }
39
40 // Don't trigger sync action if this is an ajax request, because Customizer makes them during preview before saving changes
41 if ( defined( 'DOING_AJAX' ) && DOING_AJAX && isset( $_POST['customized'] ) ) {
42 return $instance;
43 }
44
45 $widget = array(
46 'name' => $widget_object->name,
47 'id' => $widget_object->id,
48 'title' => isset( $new_instance['title'] ) ? $new_instance['title'] : '',
49 );
50 /**
51 * Trigger action to alert $callable sync listener that a widget was edited
52 *
53 * @since 5.0.0
54 *
55 * @param string $widget_name , Name of edited widget
56 */
57 do_action( 'jetpack_widget_edited', $widget );
58
59 return $instance;
60 }
61
62 public function sync_network_allowed_themes_change( $option, $value, $old_value, $network_id ) {
63 $all_enabled_theme_slugs = array_keys( $value );
64
65 if ( count( $old_value ) > count( $value ) ) {
66
67 // Suppress jetpack_network_disabled_themes sync action when theme is deleted
68 $delete_theme_call = $this->get_delete_theme_call();
69 if ( ! empty( $delete_theme_call ) ) {
70 return;
71 }
72
73 $newly_disabled_theme_names = array_keys( array_diff_key( $old_value, $value ) );
74 $newly_disabled_themes = $this->get_theme_details_for_slugs( $newly_disabled_theme_names );
75 /**
76 * Trigger action to alert $callable sync listener that network themes were disabled
77 *
78 * @since 5.0.0
79 *
80 * @param mixed $newly_disabled_themes, Array of info about network disabled themes
81 * @param mixed $all_enabled_theme_slugs, Array of slugs of all enabled themes
82 */
83 do_action( 'jetpack_network_disabled_themes', $newly_disabled_themes, $all_enabled_theme_slugs );
84 return;
85 }
86
87 $newly_enabled_theme_names = array_keys( array_diff_key( $value, $old_value ) );
88 $newly_enabled_themes = $this->get_theme_details_for_slugs( $newly_enabled_theme_names );
89 /**
90 * Trigger action to alert $callable sync listener that network themes were enabled
91 *
92 * @since 5.0.0
93 *
94 * @param mixed $newly_enabled_themes , Array of info about network enabled themes
95 * @param mixed $all_enabled_theme_slugs, Array of slugs of all enabled themes
96 */
97 do_action( 'jetpack_network_enabled_themes', $newly_enabled_themes, $all_enabled_theme_slugs );
98 }
99
100 private function get_theme_details_for_slugs( $theme_slugs ) {
101 $theme_data = array();
102 foreach ( $theme_slugs as $slug ) {
103 $theme = wp_get_theme( $slug );
104 $theme_data[ $slug ] = array(
105 'name' => $theme->get( 'Name' ),
106 'version' => $theme->get( 'Version' ),
107 'uri' => $theme->get( 'ThemeURI' ),
108 'slug' => $slug,
109 );
110 }
111 return $theme_data;
112 }
113
114 public function detect_theme_edit( $redirect_url ) {
115 $url = wp_parse_url( admin_url( $redirect_url ) );
116 $theme_editor_url = wp_parse_url( admin_url( 'theme-editor.php' ) );
117
118 if ( $theme_editor_url['path'] !== $url['path'] ) {
119 return $redirect_url;
120 }
121
122 $query_params = array();
123 wp_parse_str( $url['query'], $query_params );
124 if (
125 ! isset( $_POST['newcontent'] ) ||
126 ! isset( $query_params['file'] ) ||
127 ! isset( $query_params['theme'] ) ||
128 ! isset( $query_params['updated'] )
129 ) {
130 return $redirect_url;
131 }
132 $theme = wp_get_theme( $query_params['theme'] );
133 $theme_data = array(
134 'name' => $theme->get( 'Name' ),
135 'version' => $theme->get( 'Version' ),
136 'uri' => $theme->get( 'ThemeURI' ),
137 );
138
139 /**
140 * Trigger action to alert $callable sync listener that a theme was edited
141 *
142 * @since 5.0.0
143 *
144 * @param string $query_params['theme'], Slug of edited theme
145 * @param string $theme_data, Information about edited them
146 */
147 do_action( 'jetpack_edited_theme', $query_params['theme'], $theme_data );
148
149 return $redirect_url;
150 }
151
152 public function theme_edit_ajax() {
153 $args = wp_unslash( $_POST );
154
155 if ( empty( $args['theme'] ) ) {
156 return;
157 }
158
159 if ( empty( $args['file'] ) ) {
160 return;
161 }
162 $file = $args['file'];
163 if ( 0 !== validate_file( $file ) ) {
164 return;
165 }
166
167 if ( ! isset( $args['newcontent'] ) ) {
168 return;
169 }
170
171 if ( ! isset( $args['nonce'] ) ) {
172 return;
173 }
174
175 $stylesheet = $args['theme'];
176 if ( 0 !== validate_file( $stylesheet ) ) {
177 return;
178 }
179
180 if ( ! current_user_can( 'edit_themes' ) ) {
181 return;
182 }
183
184 $theme = wp_get_theme( $stylesheet );
185 if ( ! $theme->exists() ) {
186 return;
187 }
188
189 $real_file = $theme->get_stylesheet_directory() . '/' . $file;
190 if ( ! wp_verify_nonce( $args['nonce'], 'edit-theme_' . $real_file . $stylesheet ) ) {
191 return;
192 }
193
194 if ( $theme->errors() && 'theme_no_stylesheet' === $theme->errors()->get_error_code() ) {
195 return;
196 }
197
198 $editable_extensions = wp_get_theme_file_editable_extensions( $theme );
199
200 $allowed_files = array();
201 foreach ( $editable_extensions as $type ) {
202 switch ( $type ) {
203 case 'php':
204 $allowed_files = array_merge( $allowed_files, $theme->get_files( 'php', -1 ) );
205 break;
206 case 'css':
207 $style_files = $theme->get_files( 'css', -1 );
208 $allowed_files['style.css'] = $style_files['style.css'];
209 $allowed_files = array_merge( $allowed_files, $style_files );
210 break;
211 default:
212 $allowed_files = array_merge( $allowed_files, $theme->get_files( $type, -1 ) );
213 break;
214 }
215 }
216
217 if ( 0 !== validate_file( $real_file, $allowed_files ) ) {
218 return;
219 }
220
221 // Ensure file is real.
222 if ( ! is_file( $real_file ) ) {
223 return;
224 }
225
226 // Ensure file extension is allowed.
227 $extension = null;
228 if ( preg_match( '/\.([^.]+)$/', $real_file, $matches ) ) {
229 $extension = strtolower( $matches[1] );
230 if ( ! in_array( $extension, $editable_extensions, true ) ) {
231 return;
232 }
233 }
234
235 if ( ! is_writeable( $real_file ) ) {
236 return;
237 }
238
239 $file_pointer = fopen( $real_file, 'w+' );
240 if ( false === $file_pointer ) {
241 return;
242 }
243 fclose( $file_pointer );
244
245 $theme_data = array(
246 'name' => $theme->get( 'Name' ),
247 'version' => $theme->get( 'Version' ),
248 'uri' => $theme->get( 'ThemeURI' ),
249 );
250
251 /**
252 * This action is documented already in this file
253 */
254 do_action( 'jetpack_edited_theme', $stylesheet, $theme_data );
255
256 }
257
258 public function detect_theme_deletion() {
259 $delete_theme_call = $this->get_delete_theme_call();
260 if ( empty( $delete_theme_call ) ) {
261 return;
262 }
263
264 $slug = $delete_theme_call['args'][0];
265 $theme = wp_get_theme( $slug );
266 $theme_data = array(
267 'name' => $theme->get( 'Name' ),
268 'version' => $theme->get( 'Version' ),
269 'uri' => $theme->get( 'ThemeURI' ),
270 'slug' => $slug,
271 );
272
273 /**
274 * Signals to the sync listener that a theme was deleted and a sync action
275 * reflecting the deletion and theme slug should be sent
276 *
277 * @since 5.0.0
278 *
279 * @param string $slug Theme slug
280 * @param array $theme_data Theme info Since 5.3
281 */
282 do_action( 'jetpack_deleted_theme', $slug, $theme_data );
283 }
284
285 public function check_upgrader( $upgrader, $details ) {
286 if ( ! isset( $details['type'] ) ||
287 'theme' !== $details['type'] ||
288 is_wp_error( $upgrader->skin->result ) ||
289 ! method_exists( $upgrader, 'theme_info' )
290 ) {
291 return;
292 }
293
294 if ( 'install' === $details['action'] ) {
295 $theme = $upgrader->theme_info();
296 if ( ! $theme instanceof WP_Theme ) {
297 return;
298 }
299 $theme_info = array(
300 'name' => $theme->get( 'Name' ),
301 'version' => $theme->get( 'Version' ),
302 'uri' => $theme->get( 'ThemeURI' ),
303 );
304
305 /**
306 * Signals to the sync listener that a theme was installed and a sync action
307 * reflecting the installation and the theme info should be sent
308 *
309 * @since 4.9.0
310 *
311 * @param string $theme->theme_root Text domain of the theme
312 * @param mixed $theme_info Array of abbreviated theme info
313 */
314 do_action( 'jetpack_installed_theme', $theme->stylesheet, $theme_info );
315 }
316
317 if ( 'update' === $details['action'] ) {
318 $themes = array();
319
320 if ( empty( $details['themes'] ) && isset( $details['theme'] ) ) {
321 $details['themes'] = array( $details['theme'] );
322 }
323
324 foreach ( $details['themes'] as $theme_slug ) {
325 $theme = wp_get_theme( $theme_slug );
326
327 if ( ! $theme instanceof WP_Theme ) {
328 continue;
329 }
330
331 $themes[ $theme_slug ] = array(
332 'name' => $theme->get( 'Name' ),
333 'version' => $theme->get( 'Version' ),
334 'uri' => $theme->get( 'ThemeURI' ),
335 'stylesheet' => $theme->stylesheet,
336 );
337 }
338
339 if ( empty( $themes ) ) {
340 return;
341 }
342
343 /**
344 * Signals to the sync listener that one or more themes was updated and a sync action
345 * reflecting the update and the theme info should be sent
346 *
347 * @since 6.2.0
348 *
349 * @param mixed $themes Array of abbreviated theme info
350 */
351 do_action( 'jetpack_updated_themes', $themes );
352 }
353
354 }
355
356 public function init_full_sync_listeners( $callable ) {
357 add_action( 'jetpack_full_sync_theme_data', $callable );
358 }
359
360 public function sync_theme_support( $new_name, $new_theme = null, $old_theme = null ) {
361 $previous_theme = $this->get_theme_support_info( $old_theme );
362
363 /**
364 * Fires when the client needs to sync theme support info
365 * Only sends theme support attributes whitelisted in Jetpack_Sync_Defaults::$default_theme_support_whitelist
366 *
367 * @since 4.2.0
368 *
369 * @param array the theme support array
370 * @param array the previous theme since Jetpack 6.5.0
371 */
372 do_action( 'jetpack_sync_current_theme_support', $this->get_theme_support_info(), $previous_theme );
373 }
374
375 public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
376 /**
377 * Tells the client to sync all theme data to the server
378 *
379 * @since 4.2.0
380 *
381 * @param boolean Whether to expand theme data (should always be true)
382 */
383 do_action( 'jetpack_full_sync_theme_data', true );
384
385 // The number of actions enqueued, and next module state (true == done)
386 return array( 1, true );
387 }
388
389 public function estimate_full_sync_actions( $config ) {
390 return 1;
391 }
392
393 public function init_before_send() {
394 add_filter( 'jetpack_sync_before_send_jetpack_full_sync_theme_data', array( $this, 'expand_theme_data' ) );
395 }
396
397 function get_full_sync_actions() {
398 return array( 'jetpack_full_sync_theme_data' );
399 }
400
401 function expand_theme_data() {
402 return array( $this->get_theme_support_info() );
403 }
404
405 function get_widget_name( $widget_id ) {
406 global $wp_registered_widgets;
407 return ( isset( $wp_registered_widgets[ $widget_id ] ) ? $wp_registered_widgets[ $widget_id ]['name'] : null );
408 }
409
410 function get_sidebar_name( $sidebar_id ) {
411 global $wp_registered_sidebars;
412 return ( isset( $wp_registered_sidebars[ $sidebar_id ] ) ? $wp_registered_sidebars[ $sidebar_id ]['name'] : null );
413 }
414
415 function sync_add_widgets_to_sidebar( $new_widgets, $old_widgets, $sidebar ) {
416 $added_widgets = array_diff( $new_widgets, $old_widgets );
417 if ( empty( $added_widgets ) ) {
418 return array();
419 }
420 $moved_to_sidebar = array();
421 $sidebar_name = $this->get_sidebar_name( $sidebar );
422
423 // Don't sync jetpack_widget_added if theme was switched
424 if ( $this->is_theme_switch() ) {
425 return array();
426 }
427
428 foreach ( $added_widgets as $added_widget ) {
429 $moved_to_sidebar[] = $added_widget;
430 $added_widget_name = $this->get_widget_name( $added_widget );
431 /**
432 * Helps Sync log that a widget got added
433 *
434 * @since 4.9.0
435 *
436 * @param string $sidebar, Sidebar id got changed
437 * @param string $added_widget, Widget id got added
438 * @param string $sidebar_name, Sidebar id got changed Since 5.0.0
439 * @param string $added_widget_name, Widget id got added Since 5.0.0
440 */
441 do_action( 'jetpack_widget_added', $sidebar, $added_widget, $sidebar_name, $added_widget_name );
442 }
443 return $moved_to_sidebar;
444 }
445
446 function sync_remove_widgets_from_sidebar( $new_widgets, $old_widgets, $sidebar, $inactive_widgets ) {
447 $removed_widgets = array_diff( $old_widgets, $new_widgets );
448
449 if ( empty( $removed_widgets ) ) {
450 return array();
451 }
452
453 $moved_to_inactive = array();
454 $sidebar_name = $this->get_sidebar_name( $sidebar );
455
456 foreach ( $removed_widgets as $removed_widget ) {
457 // Lets check if we didn't move the widget to in_active_widgets
458 if ( isset( $inactive_widgets ) && ! in_array( $removed_widget, $inactive_widgets ) ) {
459 $removed_widget_name = $this->get_widget_name( $removed_widget );
460 /**
461 * Helps Sync log that a widgte got removed
462 *
463 * @since 4.9.0
464 *
465 * @param string $sidebar, Sidebar id got changed
466 * @param string $removed_widget, Widget id got removed
467 * @param string $sidebar_name, Name of the sidebar that changed Since 5.0.0
468 * @param string $removed_widget_name, Name of the widget that got removed Since 5.0.0
469 */
470 do_action( 'jetpack_widget_removed', $sidebar, $removed_widget, $sidebar_name, $removed_widget_name );
471 } else {
472 $moved_to_inactive[] = $removed_widget;
473 }
474 }
475 return $moved_to_inactive;
476
477 }
478
479 function sync_widgets_reordered( $new_widgets, $old_widgets, $sidebar ) {
480 $added_widgets = array_diff( $new_widgets, $old_widgets );
481 if ( ! empty( $added_widgets ) ) {
482 return;
483 }
484 $removed_widgets = array_diff( $old_widgets, $new_widgets );
485 if ( ! empty( $removed_widgets ) ) {
486 return;
487 }
488
489 if ( serialize( $old_widgets ) !== serialize( $new_widgets ) ) {
490 $sidebar_name = $this->get_sidebar_name( $sidebar );
491 /**
492 * Helps Sync log that a sidebar id got reordered
493 *
494 * @since 4.9.0
495 *
496 * @param string $sidebar, Sidebar id got changed
497 * @param string $sidebar_name, Name of the sidebar that changed Since 5.0.0
498 */
499 do_action( 'jetpack_widget_reordered', $sidebar, $sidebar_name );
500 }
501
502 }
503
504 function sync_sidebar_widgets_actions( $old_value, $new_value ) {
505 // Don't really know how to deal with different array_values yet.
506 if (
507 ( isset( $old_value['array_version'] ) && $old_value['array_version'] !== 3 ) ||
508 ( isset( $new_value['array_version'] ) && $new_value['array_version'] !== 3 )
509 ) {
510 return;
511 }
512
513 $moved_to_inactive_ids = array();
514 $moved_to_sidebar = array();
515
516 foreach ( $new_value as $sidebar => $new_widgets ) {
517 if ( in_array( $sidebar, array( 'array_version', 'wp_inactive_widgets' ) ) ) {
518 continue;
519 }
520 $old_widgets = isset( $old_value[ $sidebar ] )
521 ? $old_value[ $sidebar ]
522 : array();
523
524 if ( ! is_array( $new_widgets ) ) {
525 $new_widgets = array();
526 }
527
528 $moved_to_inactive_recently = $this->sync_remove_widgets_from_sidebar( $new_widgets, $old_widgets, $sidebar, $new_value['wp_inactive_widgets'] );
529 $moved_to_inactive_ids = array_merge( $moved_to_inactive_ids, $moved_to_inactive_recently );
530
531 $moved_to_sidebar_recently = $this->sync_add_widgets_to_sidebar( $new_widgets, $old_widgets, $sidebar );
532 $moved_to_sidebar = array_merge( $moved_to_sidebar, $moved_to_sidebar_recently );
533
534 $this->sync_widgets_reordered( $new_widgets, $old_widgets, $sidebar );
535
536 }
537
538 // Don't sync either jetpack_widget_moved_to_inactive or jetpack_cleared_inactive_widgets if theme was switched
539 if ( $this->is_theme_switch() ) {
540 return;
541 }
542
543 // Treat inactive sidebar a bit differently
544 if ( ! empty( $moved_to_inactive_ids ) ) {
545 $moved_to_inactive_name = array_map( array( $this, 'get_widget_name' ), $moved_to_inactive_ids );
546 /**
547 * Helps Sync log that a widgets IDs got moved to in active
548 *
549 * @since 4.9.0
550 *
551 * @param array $moved_to_inactive_ids, Array of widgets id that moved to inactive id got changed
552 * @param array $moved_to_inactive_names, Array of widgets names that moved to inactive id got changed Since 5.0.0
553 */
554 do_action( 'jetpack_widget_moved_to_inactive', $moved_to_inactive_ids, $moved_to_inactive_name );
555 } elseif ( empty( $moved_to_sidebar ) &&
556 empty( $new_value['wp_inactive_widgets'] ) &&
557 ! empty( $old_value['wp_inactive_widgets'] ) ) {
558 /**
559 * Helps Sync log that a got cleared from inactive.
560 *
561 * @since 4.9.0
562 */
563 do_action( 'jetpack_cleared_inactive_widgets' );
564 }
565 }
566
567 /**
568 * @param null $theme or the theme object
569 *
570 * @return array
571 */
572 private function get_theme_support_info( $theme = null ) {
573 global $_wp_theme_features;
574
575 $theme_support = array();
576
577 // We are trying to get the current theme info.
578 if ( $theme === null ) {
579 $theme = wp_get_theme();
580
581 foreach ( Jetpack_Sync_Defaults::$default_theme_support_whitelist as $theme_feature ) {
582 $has_support = current_theme_supports( $theme_feature );
583 if ( $has_support ) {
584 $theme_support[ $theme_feature ] = $_wp_theme_features[ $theme_feature ];
585 }
586 }
587 }
588
589 $theme_support['name'] = $theme->get( 'Name' );
590 $theme_support['version'] = $theme->get( 'Version' );
591 $theme_support['slug'] = $theme->get_stylesheet();
592 $theme_support['uri'] = $theme->get( 'ThemeURI' );
593
594 return $theme_support;
595 }
596
597 private function get_delete_theme_call() {
598 $backtrace = debug_backtrace();
599 $delete_theme_call = null;
600 foreach ( $backtrace as $call ) {
601 if ( isset( $call['function'] ) && 'delete_theme' === $call['function'] ) {
602 $delete_theme_call = $call;
603 break;
604 }
605 }
606 return $delete_theme_call;
607 }
608
609 private function is_theme_switch() {
610 return did_action( 'after_switch_theme' );
611 }
612 }
613