Completed
Push — branch-8.sync ( b15093 )
by Jeremy
07:09
created

Themes   F

Complexity

Total Complexity 104

Size/Duplication

Total Lines 842
Duplicated Lines 2.73 %

Coupling/Cohesion

Components 2
Dependencies 2

Importance

Changes 0
Metric Value
dl 23
loc 842
rs 1.758
c 0
b 0
f 0
wmc 104
lcom 2
cbo 2

27 Methods

Rating   Name   Duplication   Size   Complexity  
A name() 0 3 1
A init_listeners() 0 26 1
B sync_widget_edit() 0 27 6
A sync_network_allowed_themes_change() 0 37 3
A get_theme_details_for_slugs() 9 13 2
B detect_theme_edit() 0 37 6
F theme_edit_ajax() 0 106 21
A detect_theme_deletion() 0 26 2
C check_upgrader() 14 69 13
A init_full_sync_listeners() 0 3 1
A sync_theme_support() 0 14 1
A enqueue_full_sync_actions() 0 13 1
A send_full_sync_actions() 0 7 1
A estimate_full_sync_actions() 0 3 1
A init_before_send() 0 3 1
A get_full_sync_actions() 0 3 1
A expand_theme_data() 0 3 1
A get_widget_name() 0 4 2
A get_sidebar_name() 0 4 2
A sync_add_widgets_to_sidebar() 0 30 4
A sync_remove_widgets_from_sidebar() 0 32 5
A sync_widgets_reordered() 0 25 4
C sync_sidebar_widgets_actions() 0 60 14
A get_theme_support_info() 0 24 4
A get_delete_theme_call() 0 13 4
A is_theme_switch() 0 3 1
A total() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Themes often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Themes, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Themes sync module.
4
 *
5
 * @package automattic/jetpack-sync
6
 */
7
8
namespace Automattic\Jetpack\Sync\Modules;
9
10
use Automattic\Jetpack\Sync\Defaults;
11
12
/**
13
 * Class to handle sync for themes.
14
 */
15
class Themes extends Module {
16
	/**
17
	 * Sync module name.
18
	 *
19
	 * @access public
20
	 *
21
	 * @return string
22
	 */
23
	public function name() {
24
		return 'themes';
25
	}
26
27
	/**
28
	 * Initialize themes action listeners.
29
	 *
30
	 * @access public
31
	 *
32
	 * @param callable $callable Action handler callable.
33
	 */
34
	public function init_listeners( $callable ) {
35
		add_action( 'switch_theme', array( $this, 'sync_theme_support' ), 10, 3 );
36
		add_action( 'jetpack_sync_current_theme_support', $callable, 10, 2 );
37
		add_action( 'upgrader_process_complete', array( $this, 'check_upgrader' ), 10, 2 );
38
		add_action( 'jetpack_installed_theme', $callable, 10, 2 );
39
		add_action( 'jetpack_updated_themes', $callable, 10, 2 );
40
		add_action( 'delete_site_transient_update_themes', array( $this, 'detect_theme_deletion' ) );
41
		add_action( 'jetpack_deleted_theme', $callable, 10, 2 );
42
		add_filter( 'wp_redirect', array( $this, 'detect_theme_edit' ) );
43
		add_action( 'jetpack_edited_theme', $callable, 10, 2 );
44
		add_action( 'wp_ajax_edit-theme-plugin-file', array( $this, 'theme_edit_ajax' ), 0 );
45
		add_action( 'update_site_option_allowedthemes', array( $this, 'sync_network_allowed_themes_change' ), 10, 4 );
46
		add_action( 'jetpack_network_disabled_themes', $callable, 10, 2 );
47
		add_action( 'jetpack_network_enabled_themes', $callable, 10, 2 );
48
49
		// Sidebar updates.
50
		add_action( 'update_option_sidebars_widgets', array( $this, 'sync_sidebar_widgets_actions' ), 10, 2 );
51
52
		add_action( 'jetpack_widget_added', $callable, 10, 4 );
53
		add_action( 'jetpack_widget_removed', $callable, 10, 4 );
54
		add_action( 'jetpack_widget_moved_to_inactive', $callable, 10, 2 );
55
		add_action( 'jetpack_cleared_inactive_widgets', $callable );
56
		add_action( 'jetpack_widget_reordered', $callable, 10, 2 );
57
		add_filter( 'widget_update_callback', array( $this, 'sync_widget_edit' ), 10, 4 );
58
		add_action( 'jetpack_widget_edited', $callable );
59
	}
60
61
	/**
62
	 * Sync handler for a widget edit.
63
	 *
64
	 * @access public
65
	 *
66
	 * @todo Implement nonce verification
67
	 *
68
	 * @param array      $instance      The current widget instance's settings.
69
	 * @param array      $new_instance  Array of new widget settings.
70
	 * @param array      $old_instance  Array of old widget settings.
71
	 * @param \WP_Widget $widget_object The current widget instance.
72
	 * @return array The current widget instance's settings.
73
	 */
74
	public function sync_widget_edit( $instance, $new_instance, $old_instance, $widget_object ) {
75
		if ( empty( $old_instance ) ) {
76
			return $instance;
77
		}
78
79
		// Don't trigger sync action if this is an ajax request, because Customizer makes them during preview before saving changes.
80
		// phpcs:disable WordPress.Security.NonceVerification.Missing
81
		if ( defined( 'DOING_AJAX' ) && DOING_AJAX && isset( $_POST['customized'] ) ) {
82
			return $instance;
83
		}
84
85
		$widget = array(
86
			'name'  => $widget_object->name,
87
			'id'    => $widget_object->id,
88
			'title' => isset( $new_instance['title'] ) ? $new_instance['title'] : '',
89
		);
90
		/**
91
		 * Trigger action to alert $callable sync listener that a widget was edited.
92
		 *
93
		 * @since 5.0.0
94
		 *
95
		 * @param string $widget_name , Name of edited widget
96
		 */
97
		do_action( 'jetpack_widget_edited', $widget );
98
99
		return $instance;
100
	}
101
102
	/**
103
	 * Sync handler for network allowed themes change.
104
	 *
105
	 * @access public
106
	 *
107
	 * @param string $option     Name of the network option.
108
	 * @param mixed  $value      Current value of the network option.
109
	 * @param mixed  $old_value  Old value of the network option.
110
	 * @param int    $network_id ID of the network.
111
	 */
112
	public function sync_network_allowed_themes_change( $option, $value, $old_value, $network_id ) {
113
		$all_enabled_theme_slugs = array_keys( $value );
114
115
		if ( count( $old_value ) > count( $value ) ) {
116
117
			// Suppress jetpack_network_disabled_themes sync action when theme is deleted.
118
			$delete_theme_call = $this->get_delete_theme_call();
119
			if ( ! empty( $delete_theme_call ) ) {
120
				return;
121
			}
122
123
			$newly_disabled_theme_names = array_keys( array_diff_key( $old_value, $value ) );
124
			$newly_disabled_themes      = $this->get_theme_details_for_slugs( $newly_disabled_theme_names );
125
			/**
126
			 * Trigger action to alert $callable sync listener that network themes were disabled.
127
			 *
128
			 * @since 5.0.0
129
			 *
130
			 * @param mixed $newly_disabled_themes, Array of info about network disabled themes
131
			 * @param mixed $all_enabled_theme_slugs, Array of slugs of all enabled themes
132
			 */
133
			do_action( 'jetpack_network_disabled_themes', $newly_disabled_themes, $all_enabled_theme_slugs );
134
			return;
135
		}
136
137
		$newly_enabled_theme_names = array_keys( array_diff_key( $value, $old_value ) );
138
		$newly_enabled_themes      = $this->get_theme_details_for_slugs( $newly_enabled_theme_names );
139
		/**
140
		 * Trigger action to alert $callable sync listener that network themes were enabled
141
		 *
142
		 * @since 5.0.0
143
		 *
144
		 * @param mixed $newly_enabled_themes , Array of info about network enabled themes
145
		 * @param mixed $all_enabled_theme_slugs, Array of slugs of all enabled themes
146
		 */
147
		do_action( 'jetpack_network_enabled_themes', $newly_enabled_themes, $all_enabled_theme_slugs );
148
	}
149
150
	/**
151
	 * Retrieve details for one or more themes by their slugs.
152
	 *
153
	 * @access private
154
	 *
155
	 * @param array $theme_slugs Theme slugs.
156
	 * @return array Details for the themes.
157
	 */
158
	private function get_theme_details_for_slugs( $theme_slugs ) {
159
		$theme_data = array();
160 View Code Duplication
		foreach ( $theme_slugs as $slug ) {
161
			$theme               = wp_get_theme( $slug );
162
			$theme_data[ $slug ] = array(
163
				'name'    => $theme->get( 'Name' ),
164
				'version' => $theme->get( 'Version' ),
165
				'uri'     => $theme->get( 'ThemeURI' ),
166
				'slug'    => $slug,
167
			);
168
		}
169
		return $theme_data;
170
	}
171
172
	/**
173
	 * Detect a theme edit during a redirect.
174
	 *
175
	 * @access public
176
	 *
177
	 * @param string $redirect_url Redirect URL.
178
	 * @return string Redirect URL.
179
	 */
180
	public function detect_theme_edit( $redirect_url ) {
181
		$url              = wp_parse_url( admin_url( $redirect_url ) );
182
		$theme_editor_url = wp_parse_url( admin_url( 'theme-editor.php' ) );
183
184
		if ( $theme_editor_url['path'] !== $url['path'] ) {
185
			return $redirect_url;
186
		}
187
188
		$query_params = array();
189
		wp_parse_str( $url['query'], $query_params );
190
		if (
191
			! isset( $_POST['newcontent'] ) ||
192
			! isset( $query_params['file'] ) ||
193
			! isset( $query_params['theme'] ) ||
194
			! isset( $query_params['updated'] )
195
		) {
196
			return $redirect_url;
197
		}
198
		$theme      = wp_get_theme( $query_params['theme'] );
199
		$theme_data = array(
200
			'name'    => $theme->get( 'Name' ),
201
			'version' => $theme->get( 'Version' ),
202
			'uri'     => $theme->get( 'ThemeURI' ),
203
		);
204
205
		/**
206
		 * Trigger action to alert $callable sync listener that a theme was edited.
207
		 *
208
		 * @since 5.0.0
209
		 *
210
		 * @param string $query_params['theme'], Slug of edited theme
211
		 * @param string $theme_data, Information about edited them
212
		 */
213
		do_action( 'jetpack_edited_theme', $query_params['theme'], $theme_data );
214
215
		return $redirect_url;
216
	}
217
218
	/**
219
	 * Handler for AJAX theme editing.
220
	 *
221
	 * @todo Refactor to use WP_Filesystem instead of fopen()/fclose().
222
	 */
223
	public function theme_edit_ajax() {
224
		$args = wp_unslash( $_POST );
225
226
		if ( empty( $args['theme'] ) ) {
227
			return;
228
		}
229
230
		if ( empty( $args['file'] ) ) {
231
			return;
232
		}
233
		$file = $args['file'];
234
		if ( 0 !== validate_file( $file ) ) {
235
			return;
236
		}
237
238
		if ( ! isset( $args['newcontent'] ) ) {
239
			return;
240
		}
241
242
		if ( ! isset( $args['nonce'] ) ) {
243
			return;
244
		}
245
246
		$stylesheet = $args['theme'];
247
		if ( 0 !== validate_file( $stylesheet ) ) {
248
			return;
249
		}
250
251
		if ( ! current_user_can( 'edit_themes' ) ) {
252
			return;
253
		}
254
255
		$theme = wp_get_theme( $stylesheet );
256
		if ( ! $theme->exists() ) {
257
			return;
258
		}
259
260
		$real_file = $theme->get_stylesheet_directory() . '/' . $file;
261
		if ( ! wp_verify_nonce( $args['nonce'], 'edit-theme_' . $real_file . $stylesheet ) ) {
262
			return;
263
		}
264
265
		if ( $theme->errors() && 'theme_no_stylesheet' === $theme->errors()->get_error_code() ) {
266
			return;
267
		}
268
269
		$editable_extensions = wp_get_theme_file_editable_extensions( $theme );
270
271
		$allowed_files = array();
272
		foreach ( $editable_extensions as $type ) {
273
			switch ( $type ) {
274
				case 'php':
275
					$allowed_files = array_merge( $allowed_files, $theme->get_files( 'php', -1 ) );
276
					break;
277
				case 'css':
278
					$style_files                = $theme->get_files( 'css', -1 );
279
					$allowed_files['style.css'] = $style_files['style.css'];
280
					$allowed_files              = array_merge( $allowed_files, $style_files );
281
					break;
282
				default:
283
					$allowed_files = array_merge( $allowed_files, $theme->get_files( $type, -1 ) );
284
					break;
285
			}
286
		}
287
288
		if ( 0 !== validate_file( $real_file, $allowed_files ) ) {
289
			return;
290
		}
291
292
		// Ensure file is real.
293
		if ( ! is_file( $real_file ) ) {
294
			return;
295
		}
296
297
		// Ensure file extension is allowed.
298
		$extension = null;
0 ignored issues
show
Unused Code introduced by
$extension is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
299
		if ( preg_match( '/\.([^.]+)$/', $real_file, $matches ) ) {
300
			$extension = strtolower( $matches[1] );
301
			if ( ! in_array( $extension, $editable_extensions, true ) ) {
302
				return;
303
			}
304
		}
305
306
		if ( ! is_writeable( $real_file ) ) {
307
			return;
308
		}
309
310
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
311
		$file_pointer = fopen( $real_file, 'w+' );
312
		if ( false === $file_pointer ) {
313
			return;
314
		}
315
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
316
		fclose( $file_pointer );
317
318
		$theme_data = array(
319
			'name'    => $theme->get( 'Name' ),
320
			'version' => $theme->get( 'Version' ),
321
			'uri'     => $theme->get( 'ThemeURI' ),
322
		);
323
324
		/**
325
		 * This action is documented already in this file.
326
		 */
327
		do_action( 'jetpack_edited_theme', $stylesheet, $theme_data );
328
	}
329
330
	/**
331
	 * Detect a theme deletion.
332
	 *
333
	 * @access public
334
	 */
335
	public function detect_theme_deletion() {
336
		$delete_theme_call = $this->get_delete_theme_call();
337
		if ( empty( $delete_theme_call ) ) {
338
			return;
339
		}
340
341
		$slug       = $delete_theme_call['args'][0];
342
		$theme      = wp_get_theme( $slug );
343
		$theme_data = array(
344
			'name'    => $theme->get( 'Name' ),
345
			'version' => $theme->get( 'Version' ),
346
			'uri'     => $theme->get( 'ThemeURI' ),
347
			'slug'    => $slug,
348
		);
349
350
		/**
351
		 * Signals to the sync listener that a theme was deleted and a sync action
352
		 * reflecting the deletion and theme slug should be sent
353
		 *
354
		 * @since 5.0.0
355
		 *
356
		 * @param string $slug Theme slug
357
		 * @param array $theme_data Theme info Since 5.3
358
		 */
359
		do_action( 'jetpack_deleted_theme', $slug, $theme_data );
360
	}
361
362
	/**
363
	 * Handle an upgrader completion action.
364
	 *
365
	 * @access public
366
	 *
367
	 * @param \WP_Upgrader $upgrader The upgrader instance.
368
	 * @param array        $details  Array of bulk item update data.
369
	 */
370
	public function check_upgrader( $upgrader, $details ) {
371
		if ( ! isset( $details['type'] ) ||
372
			'theme' !== $details['type'] ||
373
			is_wp_error( $upgrader->skin->result ) ||
374
			! method_exists( $upgrader, 'theme_info' )
375
		) {
376
			return;
377
		}
378
379
		if ( 'install' === $details['action'] ) {
380
			$theme = $upgrader->theme_info();
381
			if ( ! $theme instanceof \WP_Theme ) {
0 ignored issues
show
Bug introduced by
The class WP_Theme does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
382
				return;
383
			}
384
			$theme_info = array(
385
				'name'    => $theme->get( 'Name' ),
386
				'version' => $theme->get( 'Version' ),
387
				'uri'     => $theme->get( 'ThemeURI' ),
388
			);
389
390
			/**
391
			 * Signals to the sync listener that a theme was installed and a sync action
392
			 * reflecting the installation and the theme info should be sent
393
			 *
394
			 * @since 4.9.0
395
			 *
396
			 * @param string $theme->theme_root Text domain of the theme
397
			 * @param mixed $theme_info Array of abbreviated theme info
398
			 */
399
			do_action( 'jetpack_installed_theme', $theme->stylesheet, $theme_info );
400
		}
401
402
		if ( 'update' === $details['action'] ) {
403
			$themes = array();
404
405
			if ( empty( $details['themes'] ) && isset( $details['theme'] ) ) {
406
				$details['themes'] = array( $details['theme'] );
407
			}
408
409 View Code Duplication
			foreach ( $details['themes'] as $theme_slug ) {
410
				$theme = wp_get_theme( $theme_slug );
411
412
				if ( ! $theme instanceof \WP_Theme ) {
0 ignored issues
show
Bug introduced by
The class WP_Theme does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
413
					continue;
414
				}
415
416
				$themes[ $theme_slug ] = array(
417
					'name'       => $theme->get( 'Name' ),
418
					'version'    => $theme->get( 'Version' ),
419
					'uri'        => $theme->get( 'ThemeURI' ),
420
					'stylesheet' => $theme->stylesheet,
421
				);
422
			}
423
424
			if ( empty( $themes ) ) {
425
				return;
426
			}
427
428
			/**
429
			 * Signals to the sync listener that one or more themes was updated and a sync action
430
			 * reflecting the update and the theme info should be sent
431
			 *
432
			 * @since 6.2.0
433
			 *
434
			 * @param mixed $themes Array of abbreviated theme info
435
			 */
436
			do_action( 'jetpack_updated_themes', $themes );
437
		}
438
	}
439
440
	/**
441
	 * Initialize themes action listeners for full sync.
442
	 *
443
	 * @access public
444
	 *
445
	 * @param callable $callable Action handler callable.
446
	 */
447
	public function init_full_sync_listeners( $callable ) {
448
		add_action( 'jetpack_full_sync_theme_data', $callable );
449
	}
450
451
	/**
452
	 * Handle a theme switch.
453
	 *
454
	 * @access public
455
	 *
456
	 * @param string    $new_name  Name of the new theme.
457
	 * @param \WP_Theme $new_theme The new theme.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $new_theme not be \WP_Theme|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
458
	 * @param \WP_Theme $old_theme The previous theme.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $old_theme not be \WP_Theme|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
459
	 */
460
	public function sync_theme_support( $new_name, $new_theme = null, $old_theme = null ) {
461
		$previous_theme = $this->get_theme_support_info( $old_theme );
462
463
		/**
464
		 * Fires when the client needs to sync theme support info
465
		 * Only sends theme support attributes whitelisted in Defaults::$default_theme_support_whitelist
466
		 *
467
		 * @since 4.2.0
468
		 *
469
		 * @param array the theme support array
470
		 * @param array the previous theme since Jetpack 6.5.0
471
		 */
472
		do_action( 'jetpack_sync_current_theme_support', $this->get_theme_support_info(), $previous_theme );
473
	}
474
475
	/**
476
	 * Enqueue the themes actions for full sync.
477
	 *
478
	 * @access public
479
	 *
480
	 * @param array   $config               Full sync configuration for this sync module.
481
	 * @param int     $max_items_to_enqueue Maximum number of items to enqueue.
482
	 * @param boolean $state                True if full sync has finished enqueueing this module, false otherwise.
483
	 * @return array  Number of actions enqueued, and next module state.
484
	 */
485
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
486
		/**
487
		 * Tells the client to sync all theme data to the server
488
		 *
489
		 * @since 4.2.0
490
		 *
491
		 * @param boolean Whether to expand theme data (should always be true)
492
		 */
493
		do_action( 'jetpack_full_sync_theme_data', true );
494
495
		// The number of actions enqueued, and next module state (true == done).
496
		return array( 1, true );
497
	}
498
499
	/**
500
	 * Send the themes actions for full sync.
501
	 *
502
	 * @access public
503
	 *
504
	 * @param array $config Full sync configuration for this sync module.
505
	 * @param int   $send_until The timestamp until the current request can send.
506
	 * @param array $state This module Full Sync status.
507
	 *
508
	 * @return array This module Full Sync status.
509
	 */
510
	public function send_full_sync_actions( $config, $send_until, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
511
		// we call this instead of do_action when sending immediately.
512
		$this->send_action( 'jetpack_full_sync_theme_data', array( true ) );
513
514
		// The number of actions enqueued, and next module state (true == done).
515
		return array( 'finished' => true );
516
	}
517
518
	/**
519
	 * Retrieve an estimated number of actions that will be enqueued.
520
	 *
521
	 * @access public
522
	 *
523
	 * @param array $config Full sync configuration for this sync module.
524
	 * @return array Number of items yet to be enqueued.
525
	 */
526
	public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
527
		return 1;
528
	}
529
530
	/**
531
	 * Initialize the module in the sender.
532
	 *
533
	 * @access public
534
	 */
535
	public function init_before_send() {
536
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_theme_data', array( $this, 'expand_theme_data' ) );
537
	}
538
539
	/**
540
	 * Retrieve the actions that will be sent for this module during a full sync.
541
	 *
542
	 * @access public
543
	 *
544
	 * @return array Full sync actions of this module.
545
	 */
546
	public function get_full_sync_actions() {
547
		return array( 'jetpack_full_sync_theme_data' );
548
	}
549
550
	/**
551
	 * Expand the theme within a hook before it is serialized and sent to the server.
552
	 *
553
	 * @access public
554
	 *
555
	 * @return array Theme data.
556
	 */
557
	public function expand_theme_data() {
558
		return array( $this->get_theme_support_info() );
559
	}
560
561
	/**
562
	 * Retrieve the name of the widget by the widget ID.
563
	 *
564
	 * @access public
565
	 * @global $wp_registered_widgets
566
	 *
567
	 * @param string $widget_id Widget ID.
568
	 * @return string Name of the widget, or null if not found.
569
	 */
570
	public function get_widget_name( $widget_id ) {
571
		global $wp_registered_widgets;
572
		return ( isset( $wp_registered_widgets[ $widget_id ] ) ? $wp_registered_widgets[ $widget_id ]['name'] : null );
573
	}
574
575
	/**
576
	 * Retrieve the name of the sidebar by the sidebar ID.
577
	 *
578
	 * @access public
579
	 * @global $wp_registered_sidebars
580
	 *
581
	 * @param string $sidebar_id Sidebar ID.
582
	 * @return string Name of the sidebar, or null if not found.
583
	 */
584
	public function get_sidebar_name( $sidebar_id ) {
585
		global $wp_registered_sidebars;
586
		return ( isset( $wp_registered_sidebars[ $sidebar_id ] ) ? $wp_registered_sidebars[ $sidebar_id ]['name'] : null );
587
	}
588
589
	/**
590
	 * Sync addition of widgets to a sidebar.
591
	 *
592
	 * @access public
593
	 *
594
	 * @param array  $new_widgets New widgets.
595
	 * @param array  $old_widgets Old widgets.
596
	 * @param string $sidebar     Sidebar ID.
597
	 * @return array All widgets that have been moved to the sidebar.
598
	 */
599
	public function sync_add_widgets_to_sidebar( $new_widgets, $old_widgets, $sidebar ) {
600
		$added_widgets = array_diff( $new_widgets, $old_widgets );
601
		if ( empty( $added_widgets ) ) {
602
			return array();
603
		}
604
		$moved_to_sidebar = array();
605
		$sidebar_name     = $this->get_sidebar_name( $sidebar );
606
607
		// Don't sync jetpack_widget_added if theme was switched.
608
		if ( $this->is_theme_switch() ) {
609
			return array();
610
		}
611
612
		foreach ( $added_widgets as $added_widget ) {
613
			$moved_to_sidebar[] = $added_widget;
614
			$added_widget_name  = $this->get_widget_name( $added_widget );
615
			/**
616
			 * Helps Sync log that a widget got added
617
			 *
618
			 * @since 4.9.0
619
			 *
620
			 * @param string $sidebar, Sidebar id got changed
621
			 * @param string $added_widget, Widget id got added
622
			 * @param string $sidebar_name, Sidebar id got changed Since 5.0.0
623
			 * @param string $added_widget_name, Widget id got added Since 5.0.0
624
			 */
625
			do_action( 'jetpack_widget_added', $sidebar, $added_widget, $sidebar_name, $added_widget_name );
626
		}
627
		return $moved_to_sidebar;
628
	}
629
630
	/**
631
	 * Sync removal of widgets from a sidebar.
632
	 *
633
	 * @access public
634
	 *
635
	 * @param array  $new_widgets      New widgets.
636
	 * @param array  $old_widgets      Old widgets.
637
	 * @param string $sidebar          Sidebar ID.
638
	 * @param array  $inactive_widgets Current inactive widgets.
639
	 * @return array All widgets that have been moved to inactive.
640
	 */
641
	public function sync_remove_widgets_from_sidebar( $new_widgets, $old_widgets, $sidebar, $inactive_widgets ) {
642
		$removed_widgets = array_diff( $old_widgets, $new_widgets );
643
644
		if ( empty( $removed_widgets ) ) {
645
			return array();
646
		}
647
648
		$moved_to_inactive = array();
649
		$sidebar_name      = $this->get_sidebar_name( $sidebar );
650
651
		foreach ( $removed_widgets as $removed_widget ) {
652
			// Lets check if we didn't move the widget to in_active_widgets.
653
			if ( isset( $inactive_widgets ) && ! in_array( $removed_widget, $inactive_widgets, true ) ) {
654
				$removed_widget_name = $this->get_widget_name( $removed_widget );
655
				/**
656
				 * Helps Sync log that a widgte got removed
657
				 *
658
				 * @since 4.9.0
659
				 *
660
				 * @param string $sidebar, Sidebar id got changed
661
				 * @param string $removed_widget, Widget id got removed
662
				 * @param string $sidebar_name, Name of the sidebar that changed  Since 5.0.0
663
				 * @param string $removed_widget_name, Name of the widget that got removed Since 5.0.0
664
				 */
665
				do_action( 'jetpack_widget_removed', $sidebar, $removed_widget, $sidebar_name, $removed_widget_name );
666
			} else {
667
				$moved_to_inactive[] = $removed_widget;
668
			}
669
		}
670
		return $moved_to_inactive;
671
672
	}
673
674
	/**
675
	 * Sync a reorder of widgets within a sidebar.
676
	 *
677
	 * @access public
678
	 *
679
	 * @todo Refactor serialize() to a json_encode().
680
	 *
681
	 * @param array  $new_widgets New widgets.
682
	 * @param array  $old_widgets Old widgets.
683
	 * @param string $sidebar     Sidebar ID.
684
	 */
685
	public function sync_widgets_reordered( $new_widgets, $old_widgets, $sidebar ) {
686
		$added_widgets = array_diff( $new_widgets, $old_widgets );
687
		if ( ! empty( $added_widgets ) ) {
688
			return;
689
		}
690
		$removed_widgets = array_diff( $old_widgets, $new_widgets );
691
		if ( ! empty( $removed_widgets ) ) {
692
			return;
693
		}
694
695
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
696
		if ( serialize( $old_widgets ) !== serialize( $new_widgets ) ) {
697
			$sidebar_name = $this->get_sidebar_name( $sidebar );
698
			/**
699
			 * Helps Sync log that a sidebar id got reordered
700
			 *
701
			 * @since 4.9.0
702
			 *
703
			 * @param string $sidebar, Sidebar id got changed
704
			 * @param string $sidebar_name, Name of the sidebar that changed  Since 5.0.0
705
			 */
706
			do_action( 'jetpack_widget_reordered', $sidebar, $sidebar_name );
707
		}
708
709
	}
710
711
	/**
712
	 * Handle the update of the sidebars and widgets mapping option.
713
	 *
714
	 * @access public
715
	 *
716
	 * @param mixed $old_value The old option value.
717
	 * @param mixed $new_value The new option value.
718
	 */
719
	public function sync_sidebar_widgets_actions( $old_value, $new_value ) {
720
		// Don't really know how to deal with different array_values yet.
721
		if (
722
			( isset( $old_value['array_version'] ) && 3 !== $old_value['array_version'] ) ||
723
			( isset( $new_value['array_version'] ) && 3 !== $new_value['array_version'] )
724
		) {
725
			return;
726
		}
727
728
		$moved_to_inactive_ids = array();
729
		$moved_to_sidebar      = array();
730
731
		foreach ( $new_value as $sidebar => $new_widgets ) {
732
			if ( in_array( $sidebar, array( 'array_version', 'wp_inactive_widgets' ), true ) ) {
733
				continue;
734
			}
735
			$old_widgets = isset( $old_value[ $sidebar ] )
736
				? $old_value[ $sidebar ]
737
				: array();
738
739
			if ( ! is_array( $new_widgets ) ) {
740
				$new_widgets = array();
741
			}
742
743
			$moved_to_inactive_recently = $this->sync_remove_widgets_from_sidebar( $new_widgets, $old_widgets, $sidebar, $new_value['wp_inactive_widgets'] );
744
			$moved_to_inactive_ids      = array_merge( $moved_to_inactive_ids, $moved_to_inactive_recently );
745
746
			$moved_to_sidebar_recently = $this->sync_add_widgets_to_sidebar( $new_widgets, $old_widgets, $sidebar );
747
			$moved_to_sidebar          = array_merge( $moved_to_sidebar, $moved_to_sidebar_recently );
748
749
			$this->sync_widgets_reordered( $new_widgets, $old_widgets, $sidebar );
750
751
		}
752
753
		// Don't sync either jetpack_widget_moved_to_inactive or jetpack_cleared_inactive_widgets if theme was switched.
754
		if ( $this->is_theme_switch() ) {
755
			return;
756
		}
757
758
		// Treat inactive sidebar a bit differently.
759
		if ( ! empty( $moved_to_inactive_ids ) ) {
760
			$moved_to_inactive_name = array_map( array( $this, 'get_widget_name' ), $moved_to_inactive_ids );
761
			/**
762
			 * Helps Sync log that a widgets IDs got moved to in active
763
			 *
764
			 * @since 4.9.0
765
			 *
766
			 * @param array $moved_to_inactive_ids, Array of widgets id that moved to inactive id got changed
767
			 * @param array $moved_to_inactive_names, Array of widgets names that moved to inactive id got changed Since 5.0.0
768
			 */
769
			do_action( 'jetpack_widget_moved_to_inactive', $moved_to_inactive_ids, $moved_to_inactive_name );
770
		} elseif ( empty( $moved_to_sidebar ) && empty( $new_value['wp_inactive_widgets'] ) && ! empty( $old_value['wp_inactive_widgets'] ) ) {
771
			/**
772
			 * Helps Sync log that a got cleared from inactive.
773
			 *
774
			 * @since 4.9.0
775
			 */
776
			do_action( 'jetpack_cleared_inactive_widgets' );
777
		}
778
	}
779
780
	/**
781
	 * Retrieve the theme data for the current or a specific theme.
782
	 *
783
	 * @access private
784
	 *
785
	 * @param \WP_Theme $theme Theme object. Optional, will default to the current theme.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $theme not be \WP_Theme|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
786
	 * @return array Theme data.
787
	 */
788
	private function get_theme_support_info( $theme = null ) {
789
		global $_wp_theme_features;
790
791
		$theme_support = array();
792
793
		// We are trying to get the current theme info.
794
		if ( null === $theme ) {
795
			$theme = wp_get_theme();
796
797
			foreach ( Defaults::$default_theme_support_whitelist as $theme_feature ) {
798
				$has_support = current_theme_supports( $theme_feature );
799
				if ( $has_support ) {
800
					$theme_support[ $theme_feature ] = $_wp_theme_features[ $theme_feature ];
801
				}
802
			}
803
		}
804
805
		$theme_support['name']    = $theme->get( 'Name' );
806
		$theme_support['version'] = $theme->get( 'Version' );
807
		$theme_support['slug']    = $theme->get_stylesheet();
808
		$theme_support['uri']     = $theme->get( 'ThemeURI' );
809
810
		return $theme_support;
811
	}
812
813
	/**
814
	 * Whether we've deleted a theme in the current request.
815
	 *
816
	 * @access private
817
	 *
818
	 * @return boolean True if this is a theme deletion request, false otherwise.
819
	 */
820
	private function get_delete_theme_call() {
821
		// Intentional usage of `debug_backtrace()` for production needs.
822
		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
823
		$backtrace         = debug_backtrace();
824
		$delete_theme_call = null;
825
		foreach ( $backtrace as $call ) {
826
			if ( isset( $call['function'] ) && 'delete_theme' === $call['function'] ) {
827
				$delete_theme_call = $call;
828
				break;
829
			}
830
		}
831
		return $delete_theme_call;
832
	}
833
834
	/**
835
	 * Whether we've switched to another theme in the current request.
836
	 *
837
	 * @access private
838
	 *
839
	 * @return boolean True if this is a theme switch request, false otherwise.
840
	 */
841
	private function is_theme_switch() {
842
		return did_action( 'after_switch_theme' );
843
	}
844
845
	/**
846
	 * Return Total number of objects.
847
	 *
848
	 * @param array $config Full Sync config.
849
	 *
850
	 * @return int total
851
	 */
852
	public function total( $config ) {
853
		return 1;
854
	}
855
856
}
857