Completed
Push — update/pre_connection_jitms_pl... ( 9bf136...a14e59 )
by
unknown
214:58 queued 207:06
created

Themes::detect_theme_deletion()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 26
rs 9.504
c 0
b 0
f 0
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
use Automattic\Jetpack\Sync\Functions;
12
13
/**
14
 * Class to handle sync for themes.
15
 */
16
class Themes extends Module {
17
	/**
18
	 * Sync module name.
19
	 *
20
	 * @access public
21
	 *
22
	 * @return string
23
	 */
24
	public function name() {
25
		return 'themes';
26
	}
27
28
	/**
29
	 * Initialize themes action listeners.
30
	 *
31
	 * @access public
32
	 *
33
	 * @param callable $callable Action handler callable.
34
	 */
35
	public function init_listeners( $callable ) {
36
		add_action( 'switch_theme', array( $this, 'sync_theme_support' ), 10, 3 );
37
		add_action( 'jetpack_sync_current_theme_support', $callable, 10, 2 );
38
		add_action( 'upgrader_process_complete', array( $this, 'check_upgrader' ), 10, 2 );
39
		add_action( 'jetpack_installed_theme', $callable, 10, 2 );
40
		add_action( 'jetpack_updated_themes', $callable, 10, 2 );
41
		add_action( 'delete_site_transient_update_themes', array( $this, 'detect_theme_deletion' ) );
42
		add_action( 'jetpack_deleted_theme', $callable, 10, 2 );
43
		add_filter( 'wp_redirect', array( $this, 'detect_theme_edit' ) );
44
		add_action( 'jetpack_edited_theme', $callable, 10, 2 );
45
		add_action( 'wp_ajax_edit-theme-plugin-file', array( $this, 'theme_edit_ajax' ), 0 );
46
		add_action( 'update_site_option_allowedthemes', array( $this, 'sync_network_allowed_themes_change' ), 10, 4 );
47
		add_action( 'jetpack_network_disabled_themes', $callable, 10, 2 );
48
		add_action( 'jetpack_network_enabled_themes', $callable, 10, 2 );
49
50
		// Sidebar updates.
51
		add_action( 'update_option_sidebars_widgets', array( $this, 'sync_sidebar_widgets_actions' ), 10, 2 );
52
53
		add_action( 'jetpack_widget_added', $callable, 10, 4 );
54
		add_action( 'jetpack_widget_removed', $callable, 10, 4 );
55
		add_action( 'jetpack_widget_moved_to_inactive', $callable, 10, 2 );
56
		add_action( 'jetpack_cleared_inactive_widgets', $callable );
57
		add_action( 'jetpack_widget_reordered', $callable, 10, 2 );
58
		add_filter( 'widget_update_callback', array( $this, 'sync_widget_edit' ), 10, 4 );
59
		add_action( 'jetpack_widget_edited', $callable );
60
	}
61
62
	/**
63
	 * Sync handler for a widget edit.
64
	 *
65
	 * @access public
66
	 *
67
	 * @todo Implement nonce verification
68
	 *
69
	 * @param array      $instance      The current widget instance's settings.
70
	 * @param array      $new_instance  Array of new widget settings.
71
	 * @param array      $old_instance  Array of old widget settings.
72
	 * @param \WP_Widget $widget_object The current widget instance.
73
	 * @return array The current widget instance's settings.
74
	 */
75
	public function sync_widget_edit( $instance, $new_instance, $old_instance, $widget_object ) {
76
		if ( empty( $old_instance ) ) {
77
			return $instance;
78
		}
79
80
		// Don't trigger sync action if this is an ajax request, because Customizer makes them during preview before saving changes.
81
		// phpcs:disable WordPress.Security.NonceVerification.Missing
82
		if ( defined( 'DOING_AJAX' ) && DOING_AJAX && isset( $_POST['customized'] ) ) {
83
			return $instance;
84
		}
85
86
		$widget = array(
87
			'name'  => $widget_object->name,
88
			'id'    => $widget_object->id,
89
			'title' => isset( $new_instance['title'] ) ? $new_instance['title'] : '',
90
		);
91
		/**
92
		 * Trigger action to alert $callable sync listener that a widget was edited.
93
		 *
94
		 * @since 5.0.0
95
		 *
96
		 * @param string $widget_name , Name of edited widget
97
		 */
98
		do_action( 'jetpack_widget_edited', $widget );
99
100
		return $instance;
101
	}
102
103
	/**
104
	 * Sync handler for network allowed themes change.
105
	 *
106
	 * @access public
107
	 *
108
	 * @param string $option     Name of the network option.
109
	 * @param mixed  $value      Current value of the network option.
110
	 * @param mixed  $old_value  Old value of the network option.
111
	 * @param int    $network_id ID of the network.
112
	 */
113
	public function sync_network_allowed_themes_change( $option, $value, $old_value, $network_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
114
		$all_enabled_theme_slugs = array_keys( $value );
115
116
		if ( count( $old_value ) > count( $value ) ) {
117
118
			// Suppress jetpack_network_disabled_themes sync action when theme is deleted.
119
			$delete_theme_call = $this->get_delete_theme_call();
120
			if ( ! empty( $delete_theme_call ) ) {
121
				return;
122
			}
123
124
			$newly_disabled_theme_names = array_keys( array_diff_key( $old_value, $value ) );
125
			$newly_disabled_themes      = $this->get_theme_details_for_slugs( $newly_disabled_theme_names );
126
			/**
127
			 * Trigger action to alert $callable sync listener that network themes were disabled.
128
			 *
129
			 * @since 5.0.0
130
			 *
131
			 * @param mixed $newly_disabled_themes, Array of info about network disabled themes
132
			 * @param mixed $all_enabled_theme_slugs, Array of slugs of all enabled themes
133
			 */
134
			do_action( 'jetpack_network_disabled_themes', $newly_disabled_themes, $all_enabled_theme_slugs );
135
			return;
136
		}
137
138
		$newly_enabled_theme_names = array_keys( array_diff_key( $value, $old_value ) );
139
		$newly_enabled_themes      = $this->get_theme_details_for_slugs( $newly_enabled_theme_names );
140
		/**
141
		 * Trigger action to alert $callable sync listener that network themes were enabled
142
		 *
143
		 * @since 5.0.0
144
		 *
145
		 * @param mixed $newly_enabled_themes , Array of info about network enabled themes
146
		 * @param mixed $all_enabled_theme_slugs, Array of slugs of all enabled themes
147
		 */
148
		do_action( 'jetpack_network_enabled_themes', $newly_enabled_themes, $all_enabled_theme_slugs );
149
	}
150
151
	/**
152
	 * Retrieve details for one or more themes by their slugs.
153
	 *
154
	 * @access private
155
	 *
156
	 * @param array $theme_slugs Theme slugs.
157
	 * @return array Details for the themes.
158
	 */
159
	private function get_theme_details_for_slugs( $theme_slugs ) {
160
		$theme_data = array();
161 View Code Duplication
		foreach ( $theme_slugs as $slug ) {
162
			$theme               = wp_get_theme( $slug );
163
			$theme_data[ $slug ] = array(
164
				'name'    => $theme->get( 'Name' ),
165
				'version' => $theme->get( 'Version' ),
166
				'uri'     => $theme->get( 'ThemeURI' ),
167
				'slug'    => $slug,
168
			);
169
		}
170
		return $theme_data;
171
	}
172
173
	/**
174
	 * Detect a theme edit during a redirect.
175
	 *
176
	 * @access public
177
	 *
178
	 * @param string $redirect_url Redirect URL.
179
	 * @return string Redirect URL.
180
	 */
181
	public function detect_theme_edit( $redirect_url ) {
182
		$url              = wp_parse_url( admin_url( $redirect_url ) );
183
		$theme_editor_url = wp_parse_url( admin_url( 'theme-editor.php' ) );
184
185
		if ( $theme_editor_url['path'] !== $url['path'] ) {
186
			return $redirect_url;
187
		}
188
189
		$query_params = array();
190
		wp_parse_str( $url['query'], $query_params );
191
		if (
192
			! isset( $_POST['newcontent'] ) ||
193
			! isset( $query_params['file'] ) ||
194
			! isset( $query_params['theme'] ) ||
195
			! isset( $query_params['updated'] )
196
		) {
197
			return $redirect_url;
198
		}
199
		$theme      = wp_get_theme( $query_params['theme'] );
200
		$theme_data = array(
201
			'name'    => $theme->get( 'Name' ),
202
			'version' => $theme->get( 'Version' ),
203
			'uri'     => $theme->get( 'ThemeURI' ),
204
		);
205
206
		/**
207
		 * Trigger action to alert $callable sync listener that a theme was edited.
208
		 *
209
		 * @since 5.0.0
210
		 *
211
		 * @param string $query_params['theme'], Slug of edited theme
212
		 * @param string $theme_data, Information about edited them
213
		 */
214
		do_action( 'jetpack_edited_theme', $query_params['theme'], $theme_data );
215
216
		return $redirect_url;
217
	}
218
219
	/**
220
	 * Handler for AJAX theme editing.
221
	 *
222
	 * @todo Refactor to use WP_Filesystem instead of fopen()/fclose().
223
	 */
224
	public function theme_edit_ajax() {
225
		$args = wp_unslash( $_POST );
226
227
		if ( empty( $args['theme'] ) ) {
228
			return;
229
		}
230
231
		if ( empty( $args['file'] ) ) {
232
			return;
233
		}
234
		$file = $args['file'];
235
		if ( 0 !== validate_file( $file ) ) {
236
			return;
237
		}
238
239
		if ( ! isset( $args['newcontent'] ) ) {
240
			return;
241
		}
242
243
		if ( ! isset( $args['nonce'] ) ) {
244
			return;
245
		}
246
247
		$stylesheet = $args['theme'];
248
		if ( 0 !== validate_file( $stylesheet ) ) {
249
			return;
250
		}
251
252
		if ( ! current_user_can( 'edit_themes' ) ) {
253
			return;
254
		}
255
256
		$theme = wp_get_theme( $stylesheet );
257
		if ( ! $theme->exists() ) {
258
			return;
259
		}
260
261
		if ( ! wp_verify_nonce( $args['nonce'], 'edit-theme_' . $stylesheet . '_' . $file ) ) {
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
		$real_file = $theme->get_stylesheet_directory() . '/' . $file;
289
		if ( 0 !== validate_file( $real_file, $allowed_files ) ) {
290
			return;
291
		}
292
293
		// Ensure file is real.
294
		if ( ! is_file( $real_file ) ) {
295
			return;
296
		}
297
298
		// Ensure file extension is allowed.
299
		$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...
300
		if ( preg_match( '/\.([^.]+)$/', $real_file, $matches ) ) {
301
			$extension = strtolower( $matches[1] );
302
			if ( ! in_array( $extension, $editable_extensions, true ) ) {
303
				return;
304
			}
305
		}
306
307
		if ( ! is_writeable( $real_file ) ) {
308
			return;
309
		}
310
311
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen
312
		$file_pointer = fopen( $real_file, 'w+' );
313
		if ( false === $file_pointer ) {
314
			return;
315
		}
316
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fclose
317
		fclose( $file_pointer );
318
319
		$theme_data = array(
320
			'name'    => $theme->get( 'Name' ),
321
			'version' => $theme->get( 'Version' ),
322
			'uri'     => $theme->get( 'ThemeURI' ),
323
		);
324
325
		/**
326
		 * This action is documented already in this file.
327
		 */
328
		do_action( 'jetpack_edited_theme', $stylesheet, $theme_data );
329
	}
330
331
	/**
332
	 * Detect a theme deletion.
333
	 *
334
	 * @access public
335
	 */
336
	public function detect_theme_deletion() {
337
		$delete_theme_call = $this->get_delete_theme_call();
338
		if ( empty( $delete_theme_call ) ) {
339
			return;
340
		}
341
342
		$slug       = $delete_theme_call['args'][0];
343
		$theme      = wp_get_theme( $slug );
344
		$theme_data = array(
345
			'name'    => $theme->get( 'Name' ),
346
			'version' => $theme->get( 'Version' ),
347
			'uri'     => $theme->get( 'ThemeURI' ),
348
			'slug'    => $slug,
349
		);
350
351
		/**
352
		 * Signals to the sync listener that a theme was deleted and a sync action
353
		 * reflecting the deletion and theme slug should be sent
354
		 *
355
		 * @since 5.0.0
356
		 *
357
		 * @param string $slug Theme slug
358
		 * @param array $theme_data Theme info Since 5.3
359
		 */
360
		do_action( 'jetpack_deleted_theme', $slug, $theme_data );
361
	}
362
363
	/**
364
	 * Handle an upgrader completion action.
365
	 *
366
	 * @access public
367
	 *
368
	 * @param \WP_Upgrader $upgrader The upgrader instance.
369
	 * @param array        $details  Array of bulk item update data.
370
	 */
371
	public function check_upgrader( $upgrader, $details ) {
372
		if ( ! isset( $details['type'] ) ||
373
			'theme' !== $details['type'] ||
374
			is_wp_error( $upgrader->skin->result ) ||
375
			! method_exists( $upgrader, 'theme_info' )
376
		) {
377
			return;
378
		}
379
380
		if ( 'install' === $details['action'] ) {
381
			$theme = $upgrader->theme_info();
382
			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...
383
				return;
384
			}
385
			$theme_info = array(
386
				'name'    => $theme->get( 'Name' ),
387
				'version' => $theme->get( 'Version' ),
388
				'uri'     => $theme->get( 'ThemeURI' ),
389
			);
390
391
			/**
392
			 * Signals to the sync listener that a theme was installed and a sync action
393
			 * reflecting the installation and the theme info should be sent
394
			 *
395
			 * @since 4.9.0
396
			 *
397
			 * @param string $theme->theme_root Text domain of the theme
398
			 * @param mixed $theme_info Array of abbreviated theme info
399
			 */
400
			do_action( 'jetpack_installed_theme', $theme->stylesheet, $theme_info );
401
		}
402
403
		if ( 'update' === $details['action'] ) {
404
			$themes = array();
405
406
			if ( empty( $details['themes'] ) && isset( $details['theme'] ) ) {
407
				$details['themes'] = array( $details['theme'] );
408
			}
409
410 View Code Duplication
			foreach ( $details['themes'] as $theme_slug ) {
411
				$theme = wp_get_theme( $theme_slug );
412
413
				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...
414
					continue;
415
				}
416
417
				$themes[ $theme_slug ] = array(
418
					'name'       => $theme->get( 'Name' ),
419
					'version'    => $theme->get( 'Version' ),
420
					'uri'        => $theme->get( 'ThemeURI' ),
421
					'stylesheet' => $theme->stylesheet,
422
				);
423
			}
424
425
			if ( empty( $themes ) ) {
426
				return;
427
			}
428
429
			/**
430
			 * Signals to the sync listener that one or more themes was updated and a sync action
431
			 * reflecting the update and the theme info should be sent
432
			 *
433
			 * @since 6.2.0
434
			 *
435
			 * @param mixed $themes Array of abbreviated theme info
436
			 */
437
			do_action( 'jetpack_updated_themes', $themes );
438
		}
439
	}
440
441
	/**
442
	 * Initialize themes action listeners for full sync.
443
	 *
444
	 * @access public
445
	 *
446
	 * @param callable $callable Action handler callable.
447
	 */
448
	public function init_full_sync_listeners( $callable ) {
449
		add_action( 'jetpack_full_sync_theme_data', $callable );
450
	}
451
452
	/**
453
	 * Handle a theme switch.
454
	 *
455
	 * @access public
456
	 *
457
	 * @param string    $new_name  Name of the new theme.
458
	 * @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...
459
	 * @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...
460
	 */
461
	public function sync_theme_support( $new_name, $new_theme = null, $old_theme = null ) {
462
		$previous_theme = $this->get_theme_info( $old_theme );
463
464
		/**
465
		 * Fires when the client needs to sync theme support info
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_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_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
	 *
787
	 * @return array Theme data.
788
	 */
789
	private function get_theme_info( $theme = null ) {
790
		$theme_support = array();
791
792
		// We are trying to get the current theme info.
793
		if ( null === $theme ) {
794
			$theme = wp_get_theme();
795
		}
796
797
		$theme_support['name']    = $theme->get( 'Name' );
798
		$theme_support['version'] = $theme->get( 'Version' );
799
		$theme_support['slug']    = $theme->get_stylesheet();
800
		$theme_support['uri']     = $theme->get( 'ThemeURI' );
801
802
		return $theme_support;
803
	}
804
805
	/**
806
	 * Whether we've deleted a theme in the current request.
807
	 *
808
	 * @access private
809
	 *
810
	 * @return boolean True if this is a theme deletion request, false otherwise.
811
	 */
812
	private function get_delete_theme_call() {
813
		// Intentional usage of `debug_backtrace()` for production needs.
814
		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
815
		$backtrace         = debug_backtrace();
816
		$delete_theme_call = null;
817
		foreach ( $backtrace as $call ) {
818
			if ( isset( $call['function'] ) && 'delete_theme' === $call['function'] ) {
819
				$delete_theme_call = $call;
820
				break;
821
			}
822
		}
823
		return $delete_theme_call;
824
	}
825
826
	/**
827
	 * Whether we've switched to another theme in the current request.
828
	 *
829
	 * @access private
830
	 *
831
	 * @return boolean True if this is a theme switch request, false otherwise.
832
	 */
833
	private function is_theme_switch() {
834
		return did_action( 'after_switch_theme' );
835
	}
836
837
	/**
838
	 * Return Total number of objects.
839
	 *
840
	 * @param array $config Full Sync config.
841
	 *
842
	 * @return int total
843
	 */
844
	public function total( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
845
		return 1;
846
	}
847
848
}
849