PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-sync / src / modules / class-themes.php
jetpack / jetpack_vendor / automattic / jetpack-sync / src / modules Last commit date
class-attachments.php 8 months ago class-callables.php 1 month ago class-comments.php 2 months ago class-constants.php 8 months ago class-full-sync-immediately.php 3 months ago class-full-sync.php 2 months ago class-import.php 8 months ago class-menus.php 2 months ago class-meta.php 2 months ago class-module.php 5 months ago class-network-options.php 8 months ago class-options.php 2 months ago class-plugins.php 2 months ago class-posts.php 2 months ago class-protect.php 8 months ago class-search.php 2 months ago class-stats.php 6 months ago class-term-relationships.php 8 months ago class-terms.php 2 months ago class-themes.php 2 months ago class-updates.php 8 months ago class-users.php 1 month ago class-woocommerce-hpos-orders.php 1 month ago class-woocommerce-products.php 8 months ago class-woocommerce.php 1 month ago class-wp-super-cache.php 8 months ago
class-themes.php
883 lines
1 <?php
2 /**
3 * Themes sync module.
4 *
5 * @package automattic/jetpack-sync
6 */
7
8 namespace Automattic\Jetpack\Sync\Modules;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit( 0 );
12 }
13
14 /**
15 * Class to handle sync for themes.
16 */
17 class Themes extends Module {
18 /**
19 * Sync module name.
20 *
21 * @access public
22 *
23 * @return string
24 */
25 public function name() {
26 return 'themes';
27 }
28
29 /**
30 * Initialize themes action listeners.
31 *
32 * @access public
33 *
34 * @param callable $callable Action handler callable.
35 */
36 public function init_listeners( $callable ) {
37 add_action( 'switch_theme', array( $this, 'sync_theme_support' ), 10, 3 );
38 add_action( 'jetpack_sync_current_theme_support', $callable, 10, 2 );
39 add_action( 'upgrader_process_complete', array( $this, 'check_upgrader' ), 10, 2 );
40 add_action( 'jetpack_installed_theme', $callable, 10, 2 );
41 add_action( 'jetpack_updated_themes', $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 // Theme deletions.
50 add_action( 'deleted_theme', array( $this, 'detect_theme_deletion' ), 10, 2 );
51 add_action( 'jetpack_deleted_theme', $callable, 10, 2 );
52
53 // Sidebar updates.
54 add_action( 'update_option_sidebars_widgets', array( $this, 'sync_sidebar_widgets_actions' ), 10, 2 );
55
56 add_action( 'jetpack_widget_added', $callable, 10, 4 );
57 add_action( 'jetpack_widget_removed', $callable, 10, 4 );
58 add_action( 'jetpack_widget_moved_to_inactive', $callable, 10, 2 );
59 add_action( 'jetpack_cleared_inactive_widgets', $callable );
60 add_action( 'jetpack_widget_reordered', $callable, 10, 2 );
61 add_filter( 'widget_update_callback', array( $this, 'sync_widget_edit' ), 10, 4 );
62 add_action( 'jetpack_widget_edited', $callable );
63 }
64
65 /**
66 * Sync handler for a widget edit.
67 *
68 * @access public
69 *
70 * @param array $instance The current widget instance's settings.
71 * @param array $new_instance Array of new widget settings.
72 * @param array $old_instance Array of old widget settings.
73 * @param \WP_Widget $widget_object The current widget instance.
74 * @return array The current widget instance's settings.
75 */
76 public function sync_widget_edit( $instance, $new_instance, $old_instance, $widget_object ) {
77 if ( empty( $old_instance ) ) {
78 return $instance;
79 }
80
81 // Don't trigger sync action if this is an ajax request, because Customizer makes them during preview before saving changes.
82 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Not doing anything with or in response to the $_POST data. We're only using $_POST['customized'] to early return if this is an ajax request from Customizer.
83 if ( defined( 'DOING_AJAX' ) && DOING_AJAX && isset( $_POST['customized'] ) ) {
84 return $instance;
85 }
86
87 $widget = array(
88 'name' => $widget_object->name,
89 'id' => $widget_object->id,
90 'title' => $new_instance['title'] ?? '',
91 );
92 /**
93 * Trigger action to alert $callable sync listener that a widget was edited.
94 *
95 * @since 1.6.3
96 * @since-jetpack 5.0.0
97 *
98 * @param string $widget_name , Name of edited widget
99 */
100 do_action( 'jetpack_widget_edited', $widget );
101
102 return $instance;
103 }
104
105 /**
106 * Sync handler for network allowed themes change.
107 *
108 * @access public
109 *
110 * @param string $option Name of the network option.
111 * @param mixed $value Current value of the network option.
112 * @param mixed $old_value Old value of the network option.
113 * @param int $network_id ID of the network.
114 */
115 public function sync_network_allowed_themes_change( $option, $value, $old_value, $network_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
116 $all_enabled_theme_slugs = array_keys( $value );
117 $old_value_count = is_countable( $old_value ) ? count( $old_value ) : 0;
118 $value_count = is_countable( $value ) ? count( $value ) : 0;
119
120 if ( $old_value_count > $value_count ) {
121 // Suppress jetpack_network_disabled_themes sync action when theme is deleted.
122 $delete_theme_call = $this->get_delete_theme_call();
123 if ( ! empty( $delete_theme_call ) ) {
124 return;
125 }
126
127 $newly_disabled_theme_names = array_keys( array_diff_key( $old_value, $value ) );
128 $newly_disabled_themes = $this->get_theme_details_for_slugs( $newly_disabled_theme_names );
129 /**
130 * Trigger action to alert $callable sync listener that network themes were disabled.
131 *
132 * @since 1.6.3
133 * @since-jetpack 5.0.0
134 *
135 * @param mixed $newly_disabled_themes, Array of info about network disabled themes
136 * @param mixed $all_enabled_theme_slugs, Array of slugs of all enabled themes
137 */
138 do_action( 'jetpack_network_disabled_themes', $newly_disabled_themes, $all_enabled_theme_slugs );
139 return;
140 }
141
142 $newly_enabled_theme_names = array_keys( array_diff_key( $value, $old_value ) );
143 $newly_enabled_themes = $this->get_theme_details_for_slugs( $newly_enabled_theme_names );
144 /**
145 * Trigger action to alert $callable sync listener that network themes were enabled
146 *
147 * @since 1.6.3
148 * @since-jetpack 5.0.0
149 *
150 * @param mixed $newly_enabled_themes , Array of info about network enabled themes
151 * @param mixed $all_enabled_theme_slugs, Array of slugs of all enabled themes
152 */
153 do_action( 'jetpack_network_enabled_themes', $newly_enabled_themes, $all_enabled_theme_slugs );
154 }
155
156 /**
157 * Retrieve details for one or more themes by their slugs.
158 *
159 * @access private
160 *
161 * @param array $theme_slugs Theme slugs.
162 * @return array Details for the themes.
163 */
164 private function get_theme_details_for_slugs( $theme_slugs ) {
165 $theme_data = array();
166 foreach ( $theme_slugs as $slug ) {
167 $theme = wp_get_theme( $slug );
168 $theme_data[ $slug ] = array(
169 'name' => $theme->get( 'Name' ),
170 'version' => $theme->get( 'Version' ),
171 'uri' => $theme->get( 'ThemeURI' ),
172 'slug' => $slug,
173 );
174 }
175 return $theme_data;
176 }
177
178 /**
179 * Detect a theme edit during a redirect.
180 *
181 * @access public
182 *
183 * @param string $redirect_url Redirect URL.
184 * @return string Redirect URL.
185 */
186 public function detect_theme_edit( $redirect_url ) {
187 $url = wp_parse_url( admin_url( $redirect_url ) );
188 $theme_editor_url = wp_parse_url( admin_url( 'theme-editor.php' ) );
189
190 if ( ! isset( $url['query'] ) || $theme_editor_url['path'] !== $url['path'] ) {
191 return $redirect_url;
192 }
193
194 $query_params = array();
195 wp_parse_str( $url['query'], $query_params );
196 if (
197 ! isset( $_POST['newcontent'] ) || // phpcs:ignore WordPress.Security.NonceVerification.Missing -- 'wp_redirect' gets fired for a lot of things. We're only using $_POST['newcontent'] to limit action to redirects from theme edits, we're not doing anything with or in response to the post itself.
198 ! isset( $query_params['file'] ) ||
199 ! isset( $query_params['theme'] ) ||
200 ! isset( $query_params['updated'] )
201 ) {
202 return $redirect_url;
203 }
204 $theme = wp_get_theme( $query_params['theme'] );
205 $theme_data = array(
206 'name' => $theme->get( 'Name' ),
207 'version' => $theme->get( 'Version' ),
208 'uri' => $theme->get( 'ThemeURI' ),
209 );
210
211 /**
212 * Trigger action to alert $callable sync listener that a theme was edited.
213 *
214 * @since 1.6.3
215 * @since-jetpack 5.0.0
216 *
217 * @param string $query_params['theme'], Slug of edited theme
218 * @param string $theme_data, Information about edited them
219 */
220 do_action( 'jetpack_edited_theme', $query_params['theme'], $theme_data );
221
222 return $redirect_url;
223 }
224
225 /**
226 * Handler for AJAX theme editing.
227 *
228 * @todo Refactor to use WP_Filesystem instead of fopen()/fclose().
229 */
230 public function theme_edit_ajax() {
231 // This validation is based on wp_edit_theme_plugin_file().
232 if ( empty( $_POST['theme'] ) ) {
233 return;
234 }
235
236 if ( empty( $_POST['file'] ) ) {
237 return;
238 }
239 $file = wp_unslash( $_POST['file'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated manually just after.
240 if ( 0 !== validate_file( $file ) ) {
241 return;
242 }
243
244 if ( ! isset( $_POST['newcontent'] ) ) {
245 return;
246 }
247
248 if ( ! isset( $_POST['nonce'] ) ) {
249 return;
250 }
251
252 $stylesheet = wp_unslash( $_POST['theme'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated manually just after.
253 if ( 0 !== validate_file( $stylesheet ) ) {
254 return;
255 }
256
257 if ( ! current_user_can( 'edit_themes' ) ) {
258 return;
259 }
260
261 $theme = wp_get_theme( $stylesheet );
262 if ( ! $theme->exists() ) {
263 return;
264 }
265
266 if ( ! wp_verify_nonce( $_POST['nonce'], 'edit-theme_' . $stylesheet . '_' . $file ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- WP core doesn't pre-sanitize nonces either.
267 return;
268 }
269
270 if ( $theme->errors() && 'theme_no_stylesheet' === $theme->errors()->get_error_code() ) {
271 return;
272 }
273
274 $editable_extensions = wp_get_theme_file_editable_extensions( $theme );
275
276 $allowed_files = array();
277 foreach ( $editable_extensions as $type ) {
278 switch ( $type ) {
279 case 'php':
280 $allowed_files = array_merge( $allowed_files, $theme->get_files( 'php', -1 ) );
281 break;
282 case 'css':
283 $style_files = $theme->get_files( 'css', -1 );
284 $allowed_files['style.css'] = $style_files['style.css'];
285 $allowed_files = array_merge( $allowed_files, $style_files );
286 break;
287 default:
288 $allowed_files = array_merge( $allowed_files, $theme->get_files( $type, -1 ) );
289 break;
290 }
291 }
292
293 $real_file = $theme->get_stylesheet_directory() . '/' . $file;
294 if ( 0 !== validate_file( $real_file, $allowed_files ) ) {
295 return;
296 }
297
298 // Ensure file is real.
299 if ( ! is_file( $real_file ) ) {
300 return;
301 }
302
303 // Ensure file extension is allowed.
304 $extension = null;
305 if ( preg_match( '/\.([^.]+)$/', $real_file, $matches ) ) {
306 $extension = strtolower( $matches[1] );
307 if ( ! in_array( $extension, $editable_extensions, true ) ) {
308 return;
309 }
310 }
311
312 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable
313 if ( ! is_writable( $real_file ) ) {
314 return;
315 }
316
317 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
318 $file_pointer = fopen( $real_file, 'w+' );
319 if ( false === $file_pointer ) {
320 return;
321 }
322 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
323 fclose( $file_pointer );
324
325 $theme_data = array(
326 'name' => $theme->get( 'Name' ),
327 'version' => $theme->get( 'Version' ),
328 'uri' => $theme->get( 'ThemeURI' ),
329 );
330
331 /**
332 * This action is documented already in this file.
333 */
334 do_action( 'jetpack_edited_theme', $stylesheet, $theme_data );
335 }
336
337 /**
338 * Detect a theme deletion.
339 *
340 * @access public
341 *
342 * @param string $stylesheet Stylesheet of the theme to delete.
343 * @param bool $deleted Whether the theme deletion was successful.
344 */
345 public function detect_theme_deletion( $stylesheet, $deleted ) {
346 $theme = wp_get_theme( $stylesheet );
347 $theme_data = array(
348 'name' => $theme->get( 'Name' ),
349 'version' => $theme->get( 'Version' ),
350 'uri' => $theme->get( 'ThemeURI' ),
351 'slug' => $stylesheet,
352 );
353
354 if ( $deleted ) {
355 /**
356 * Signals to the sync listener that a theme was deleted and a sync action
357 * reflecting the deletion and theme slug should be sent
358 *
359 * @since 1.6.3
360 * @since-jetpack 5.0.0
361 *
362 * @param string $stylesheet Theme slug
363 * @param array $theme_data Theme info Since 5.3
364 */
365 do_action( 'jetpack_deleted_theme', $stylesheet, $theme_data );
366 }
367 }
368
369 /**
370 * Handle an upgrader completion action.
371 *
372 * @access public
373 *
374 * @param \WP_Upgrader $upgrader The upgrader instance.
375 * @param array $details Array of bulk item update data.
376 */
377 public function check_upgrader( $upgrader, $details ) {
378 if ( ! isset( $details['type'] ) ||
379 'theme' !== $details['type'] ||
380 is_wp_error( $upgrader->skin->result ) ||
381 ! method_exists( $upgrader, 'theme_info' )
382 ) {
383 return;
384 }
385
386 if ( 'install' === $details['action'] ) {
387 // @phan-suppress-next-line PhanUndeclaredMethod -- Checked above. See also https://github.com/phan/phan/issues/1204.
388 $theme = $upgrader->theme_info();
389 if ( ! $theme instanceof \WP_Theme ) {
390 return;
391 }
392 $theme_info = array(
393 'name' => $theme->get( 'Name' ),
394 'version' => $theme->get( 'Version' ),
395 'uri' => $theme->get( 'ThemeURI' ),
396 );
397
398 /**
399 * Signals to the sync listener that a theme was installed and a sync action
400 * reflecting the installation and the theme info should be sent
401 *
402 * @since 1.6.3
403 * @since-jetpack 4.9.0
404 *
405 * @param string $theme->theme_root Text domain of the theme
406 * @param mixed $theme_info Array of abbreviated theme info
407 */
408 do_action( 'jetpack_installed_theme', $theme->stylesheet, $theme_info );
409 }
410
411 if ( 'update' === $details['action'] ) {
412 $themes = array();
413
414 if ( empty( $details['themes'] ) && isset( $details['theme'] ) ) {
415 $details['themes'] = array( $details['theme'] );
416 }
417
418 foreach ( $details['themes'] as $theme_slug ) {
419 $theme = wp_get_theme( $theme_slug );
420
421 if ( ! $theme instanceof \WP_Theme ) {
422 continue;
423 }
424
425 $themes[ $theme_slug ] = array(
426 'name' => $theme->get( 'Name' ),
427 'version' => $theme->get( 'Version' ),
428 'uri' => $theme->get( 'ThemeURI' ),
429 'stylesheet' => $theme->stylesheet,
430 );
431 }
432
433 if ( empty( $themes ) ) {
434 return;
435 }
436
437 /**
438 * Signals to the sync listener that one or more themes was updated and a sync action
439 * reflecting the update and the theme info should be sent
440 *
441 * @since 1.6.3
442 * @since-jetpack 6.2.0
443 *
444 * @param mixed $themes Array of abbreviated theme info
445 */
446 do_action( 'jetpack_updated_themes', $themes );
447 }
448 }
449
450 /**
451 * Initialize themes action listeners for full sync.
452 *
453 * @access public
454 *
455 * @param callable $callable Action handler callable.
456 */
457 public function init_full_sync_listeners( $callable ) {
458 add_action( 'jetpack_full_sync_theme_data', $callable );
459 }
460
461 /**
462 * Handle a theme switch.
463 *
464 * @access public
465 *
466 * @param string $new_name Name of the new theme.
467 * @param \WP_Theme $new_theme The new theme.
468 * @param \WP_Theme $old_theme The previous theme.
469 */
470 public function sync_theme_support( $new_name, $new_theme = null, $old_theme = null ) {
471 $previous_theme = $this->get_theme_info( $old_theme );
472
473 /**
474 * Fires when the client needs to sync theme support info
475 *
476 * @since 1.6.3
477 * @since-jetpack 4.2.0
478 *
479 * @param array the theme support array
480 * @param array the previous theme since Jetpack 6.5.0
481 */
482 do_action( 'jetpack_sync_current_theme_support', $this->get_theme_info(), $previous_theme );
483 }
484
485 /**
486 * Enqueue the themes actions for full sync.
487 *
488 * @access public
489 *
490 * @param array $config Full sync configuration for this sync module.
491 * @param int $max_items_to_enqueue Maximum number of items to enqueue.
492 * @param boolean $state True if full sync has finished enqueueing this module, false otherwise.
493 * @return array Number of actions enqueued, and next module state.
494 */
495 public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
496 /**
497 * Tells the client to sync all theme data to the server
498 *
499 * @since 1.6.3
500 * @since-jetpack 4.2.0
501 *
502 * @param boolean Whether to expand theme data (should always be true)
503 */
504 do_action( 'jetpack_full_sync_theme_data', true );
505
506 // The number of actions enqueued, and next module state (true == done).
507 return array( 1, true );
508 }
509
510 /**
511 * Send the themes actions for full sync.
512 *
513 * @access public
514 *
515 * @param array $config Full sync configuration for this sync module.
516 * @param array $status This module Full Sync status.
517 * @param int $send_until The timestamp until the current request can send.
518 * @param int $started The timestamp when the full sync started.
519 *
520 * @return array This module Full Sync status.
521 */
522 public function send_full_sync_actions( $config, $status, $send_until, $started ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
523 // we call this instead of do_action when sending immediately.
524 $result = $this->send_action( 'jetpack_full_sync_theme_data', array( true ) );
525
526 if ( is_wp_error( $result ) ) {
527 $status['error'] = true;
528 return $status;
529 }
530 $status['finished'] = true;
531 $status['sent'] = $status['total'];
532 return $status;
533 }
534
535 /**
536 * Retrieve an estimated number of actions that will be enqueued.
537 *
538 * @access public
539 *
540 * @param array $config Full sync configuration for this sync module.
541 * @return int Number of items yet to be enqueued.
542 */
543 public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
544 return 1;
545 }
546
547 /**
548 * Initialize the module in the sender.
549 *
550 * @access public
551 */
552 public function init_before_send() {
553 add_filter( 'jetpack_sync_before_send_jetpack_full_sync_theme_data', array( $this, 'expand_theme_data' ) );
554 }
555
556 /**
557 * Retrieve the actions that will be sent for this module during a full sync.
558 *
559 * @access public
560 *
561 * @return array Full sync actions of this module.
562 */
563 public function get_full_sync_actions() {
564 return array( 'jetpack_full_sync_theme_data' );
565 }
566
567 /**
568 * Expand the theme within a hook before it is serialized and sent to the server.
569 *
570 * @access public
571 *
572 * @return array Theme data.
573 */
574 public function expand_theme_data() {
575 return array( $this->get_theme_info() );
576 }
577
578 /**
579 * Retrieve the name of the widget by the widget ID.
580 *
581 * @access public
582 * @global $wp_registered_widgets
583 *
584 * @param string $widget_id Widget ID.
585 * @return string Name of the widget, or null if not found.
586 */
587 public function get_widget_name( $widget_id ) {
588 global $wp_registered_widgets;
589 return ( isset( $wp_registered_widgets[ $widget_id ] ) ? $wp_registered_widgets[ $widget_id ]['name'] : null );
590 }
591
592 /**
593 * Retrieve the name of the sidebar by the sidebar ID.
594 *
595 * @access public
596 * @global $wp_registered_sidebars
597 *
598 * @param string $sidebar_id Sidebar ID.
599 * @return string Name of the sidebar, or null if not found.
600 */
601 public function get_sidebar_name( $sidebar_id ) {
602 global $wp_registered_sidebars;
603 return ( isset( $wp_registered_sidebars[ $sidebar_id ] ) ? $wp_registered_sidebars[ $sidebar_id ]['name'] : null );
604 }
605
606 /**
607 * Sync addition of widgets to a sidebar.
608 *
609 * @access public
610 *
611 * @param array $new_widgets New widgets.
612 * @param array $old_widgets Old widgets.
613 * @param string $sidebar Sidebar ID.
614 * @return array All widgets that have been moved to the sidebar.
615 */
616 public function sync_add_widgets_to_sidebar( $new_widgets, $old_widgets, $sidebar ) {
617 $added_widgets = array_diff( $new_widgets, $old_widgets );
618 if ( empty( $added_widgets ) ) {
619 return array();
620 }
621 $moved_to_sidebar = array();
622 $sidebar_name = $this->get_sidebar_name( $sidebar );
623
624 // Don't sync jetpack_widget_added if theme was switched.
625 if ( $this->is_theme_switch() ) {
626 return array();
627 }
628
629 foreach ( $added_widgets as $added_widget ) {
630 $moved_to_sidebar[] = $added_widget;
631 $added_widget_name = $this->get_widget_name( $added_widget );
632 /**
633 * Helps Sync log that a widget got added
634 *
635 * @since 1.6.3
636 * @since-jetpack 4.9.0
637 *
638 * @param string $sidebar, Sidebar id got changed
639 * @param string $added_widget, Widget id got added
640 * @param string $sidebar_name, Sidebar id got changed Since 5.0.0
641 * @param string $added_widget_name, Widget id got added Since 5.0.0
642 */
643 do_action( 'jetpack_widget_added', $sidebar, $added_widget, $sidebar_name, $added_widget_name );
644 }
645 return $moved_to_sidebar;
646 }
647
648 /**
649 * Sync removal of widgets from a sidebar.
650 *
651 * @access public
652 *
653 * @param array $new_widgets New widgets.
654 * @param array $old_widgets Old widgets.
655 * @param string $sidebar Sidebar ID.
656 * @param array $inactive_widgets Current inactive widgets.
657 * @return array All widgets that have been moved to inactive.
658 */
659 public function sync_remove_widgets_from_sidebar( $new_widgets, $old_widgets, $sidebar, $inactive_widgets ) {
660 $removed_widgets = array_diff( $old_widgets, $new_widgets );
661
662 if ( empty( $removed_widgets ) ) {
663 return array();
664 }
665
666 $moved_to_inactive = array();
667 $sidebar_name = $this->get_sidebar_name( $sidebar );
668
669 foreach ( $removed_widgets as $removed_widget ) {
670 // Lets check if we didn't move the widget to in_active_widgets.
671 if ( isset( $inactive_widgets ) && ! in_array( $removed_widget, $inactive_widgets, true ) ) {
672 $removed_widget_name = $this->get_widget_name( $removed_widget );
673 /**
674 * Helps Sync log that a widgte got removed
675 *
676 * @since 1.6.3
677 * @since-jetpack 4.9.0
678 *
679 * @param string $sidebar, Sidebar id got changed
680 * @param string $removed_widget, Widget id got removed
681 * @param string $sidebar_name, Name of the sidebar that changed Since 5.0.0
682 * @param string $removed_widget_name, Name of the widget that got removed Since 5.0.0
683 */
684 do_action( 'jetpack_widget_removed', $sidebar, $removed_widget, $sidebar_name, $removed_widget_name );
685 } else {
686 $moved_to_inactive[] = $removed_widget;
687 }
688 }
689 return $moved_to_inactive;
690 }
691
692 /**
693 * Sync a reorder of widgets within a sidebar.
694 *
695 * @access public
696 *
697 * @todo Refactor serialize() to a json_encode().
698 *
699 * @param array $new_widgets New widgets.
700 * @param array $old_widgets Old widgets.
701 * @param string $sidebar Sidebar ID.
702 */
703 public function sync_widgets_reordered( $new_widgets, $old_widgets, $sidebar ) {
704 $added_widgets = array_diff( $new_widgets, $old_widgets );
705 if ( ! empty( $added_widgets ) ) {
706 return;
707 }
708 $removed_widgets = array_diff( $old_widgets, $new_widgets );
709 if ( ! empty( $removed_widgets ) ) {
710 return;
711 }
712
713 // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
714 if ( serialize( $old_widgets ) !== serialize( $new_widgets ) ) {
715 $sidebar_name = $this->get_sidebar_name( $sidebar );
716 /**
717 * Helps Sync log that a sidebar id got reordered
718 *
719 * @since 1.6.3
720 * @since-jetpack 4.9.0
721 *
722 * @param string $sidebar, Sidebar id got changed
723 * @param string $sidebar_name, Name of the sidebar that changed Since 5.0.0
724 */
725 do_action( 'jetpack_widget_reordered', $sidebar, $sidebar_name );
726 }
727 }
728
729 /**
730 * Handle the update of the sidebars and widgets mapping option.
731 *
732 * @access public
733 *
734 * @param mixed $old_value The old option value.
735 * @param mixed $new_value The new option value.
736 */
737 public function sync_sidebar_widgets_actions( $old_value, $new_value ) {
738 // Don't really know how to deal with different array_values yet.
739 if (
740 ( isset( $old_value['array_version'] ) && 3 !== $old_value['array_version'] ) ||
741 ( isset( $new_value['array_version'] ) && 3 !== $new_value['array_version'] )
742 ) {
743 return;
744 }
745
746 $moved_to_inactive_ids = array();
747 $moved_to_sidebar = array();
748 $new_inactive_widgets = $new_value['wp_inactive_widgets'] ?? array();
749
750 foreach ( $new_value as $sidebar => $new_widgets ) {
751 if ( in_array( $sidebar, array( 'array_version', 'wp_inactive_widgets' ), true ) ) {
752 continue;
753 }
754 $old_widgets = $old_value[ $sidebar ] ?? array();
755
756 if ( ! is_array( $new_widgets ) ) {
757 $new_widgets = array();
758 }
759 $moved_to_inactive_recently = $this->sync_remove_widgets_from_sidebar( $new_widgets, $old_widgets, $sidebar, $new_inactive_widgets );
760 $moved_to_inactive_ids = array_merge( $moved_to_inactive_ids, $moved_to_inactive_recently );
761
762 $moved_to_sidebar_recently = $this->sync_add_widgets_to_sidebar( $new_widgets, $old_widgets, $sidebar );
763 $moved_to_sidebar = array_merge( $moved_to_sidebar, $moved_to_sidebar_recently );
764
765 $this->sync_widgets_reordered( $new_widgets, $old_widgets, $sidebar );
766
767 }
768
769 // Don't sync either jetpack_widget_moved_to_inactive or jetpack_cleared_inactive_widgets if theme was switched.
770 if ( $this->is_theme_switch() ) {
771 return;
772 }
773
774 // Treat inactive sidebar a bit differently.
775 if ( ! empty( $moved_to_inactive_ids ) ) {
776 $moved_to_inactive_name = array_map( array( $this, 'get_widget_name' ), $moved_to_inactive_ids );
777 /**
778 * Helps Sync log that a widgets IDs got moved to in active
779 *
780 * @since 1.6.3
781 * @since-jetpack 4.9.0
782 *
783 * @param array $moved_to_inactive_ids, Array of widgets id that moved to inactive id got changed
784 * @param array $moved_to_inactive_names, Array of widgets names that moved to inactive id got changed Since 5.0.0
785 */
786 do_action( 'jetpack_widget_moved_to_inactive', $moved_to_inactive_ids, $moved_to_inactive_name );
787 } elseif ( empty( $moved_to_sidebar ) && empty( $new_value['wp_inactive_widgets'] ) && ! empty( $old_value['wp_inactive_widgets'] ) ) {
788 /**
789 * Helps Sync log that a got cleared from inactive.
790 *
791 * @since 1.6.3
792 * @since-jetpack 4.9.0
793 */
794 do_action( 'jetpack_cleared_inactive_widgets' );
795 }
796 }
797
798 /**
799 * Retrieve the theme data for the current or a specific theme.
800 *
801 * @access private
802 *
803 * @param \WP_Theme $theme Theme object. Optional, will default to the current theme.
804 *
805 * @return array Theme data.
806 */
807 private function get_theme_info( $theme = null ) {
808 $theme_support = array();
809
810 // We are trying to get the current theme info.
811 if ( null === $theme ) {
812 $theme = wp_get_theme();
813 }
814
815 $theme_support['name'] = $theme->get( 'Name' );
816 $theme_support['version'] = $theme->get( 'Version' );
817 $theme_support['slug'] = $theme->get_stylesheet();
818 $theme_support['uri'] = $theme->get( 'ThemeURI' );
819
820 return $theme_support;
821 }
822
823 /**
824 * Whether we've deleted a theme in the current request.
825 *
826 * @access private
827 *
828 * @return boolean True if this is a theme deletion request, false otherwise.
829 */
830 private function get_delete_theme_call() {
831 // Intentional usage of `debug_backtrace()` for production needs.
832 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
833 $backtrace = debug_backtrace();
834 $delete_theme_call = null;
835 foreach ( $backtrace as $call ) {
836 if ( isset( $call['function'] ) && 'delete_theme' === $call['function'] ) {
837 $delete_theme_call = $call;
838 break;
839 }
840 }
841 return $delete_theme_call;
842 }
843
844 /**
845 * Whether we've switched to another theme in the current request.
846 *
847 * @access private
848 *
849 * @return boolean True if this is a theme switch request, false otherwise.
850 */
851 private function is_theme_switch() {
852 return did_action( 'after_switch_theme' );
853 }
854
855 /**
856 * Return Total number of objects.
857 *
858 * @param array $config Full Sync config.
859 *
860 * @return int total
861 */
862 public function total( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
863 return 1;
864 }
865
866 /**
867 * Retrieve a set of constants by their IDs.
868 *
869 * @access public
870 *
871 * @param string $object_type Object type.
872 * @param array $ids Object IDs.
873 * @return array Array of objects.
874 */
875 public function get_objects_by_id( $object_type, $ids ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
876 if ( 'theme-info' !== $object_type ) {
877 return array();
878 }
879
880 return array( $this->get_theme_info() );
881 }
882 }
883