Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
20.99% covered (danger)
20.99%
68 / 324
14.29% covered (danger)
14.29%
3 / 21
CRAP
n/a
0 / 0
wpcomsh_get_attachment_url
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
wpcomsh_jetpack_sso_auth_cookie_expiration
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
wpcomsh_bypass_jetpack_sso_login
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
wpcomsh_modify_jetpack_sso_allowed_actions
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
wpcomsh_admin_enqueue_style
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
wpcomsh_allow_custom_wp_options
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
check_site_has_pending_automated_transfer
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
wpcomsh_jetpack_api_fix_unserializable_track_number
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_filter_outgoing_user_agent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
wpcomsh_allowed_redirect_hosts
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_make_content_clickable
73.33% covered (warning)
73.33%
11 / 15
0.00% covered (danger)
0.00%
0 / 1
6.68
wpcomsh_hide_scan_threats_from_transients
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_remove_threats_from_toolbar
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
wpcom_hide_scan_threats_from_api
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
56
wpcomsh_stats_timezone_string
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
wpcomsh_footer_rum_js
80.49% covered (warning)
80.49%
33 / 41
0.00% covered (danger)
0.00%
0 / 1
11.90
wpcomsh_get_woo_rum_data
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
wpcomsh_woocommerce_tracker_data
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
wpcomsh_record_tracks_event
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
wpcomsh_jetpack_filter_tos_for_tracking
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
wpcomsh_avoid_proxied_v2_banner
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Plugin Name: WordPress.com Site Helper
4 * Description: A helper for connecting WordPress.com sites to external host infrastructure.
5 * Version: 9.0.0
6 * Author: Automattic
7 * Author URI: http://automattic.com/
8 *
9 * @package wpcomsh
10 */
11
12define( 'WPCOMSH_VERSION', '9.0.0' );
13
14// Loaded first: fatal-error screen filter + one-shot plugin-deactivation endpoint.
15// The deactivator also needs to load before any regular plugin, so in production
16// a stub in wp-content/mu-plugins/ should re-include fatal-plugin-deactivator.php
17// directly (see wpcom-fatal-error/mu-plugin-stub.php).
18require_once __DIR__ . '/wpcom-fatal-error/load.php';
19
20// If true, Typekit fonts will be available in addition to Google fonts
21add_filter( 'jetpack_fonts_enable_typekit', '__return_true' );
22
23// This exists only on the Atomic platform. Blank if migrated elsewhere, so it doesn't fatal.
24if ( ! class_exists( 'Atomic_Persistent_Data' ) ) {
25    require_once __DIR__ . '/class-atomic-persistent-data.php';
26}
27
28require_once __DIR__ . '/constants.php';
29require_once __DIR__ . '/wpcom-features/functions-wpcom-features.php';
30require_once __DIR__ . '/wpcom-marketplace/software/class-marketplace-software-manager.php';
31require_once __DIR__ . '/functions.php';
32require_once __DIR__ . '/i18n.php';
33require_once __DIR__ . '/lib/require-lib.php';
34
35require_once __DIR__ . '/plugin-hotfixes.php';
36
37require_once __DIR__ . '/footer-credit/footer-credit.php';
38require_once __DIR__ . '/storefront/storefront.php';
39require_once __DIR__ . '/custom-colors/colors.php';
40require_once __DIR__ . '/storage/storage.php';
41require_once __DIR__ . '/imports/class-backup-import-manager.php';
42
43// Interoperability with the core WordPress data privacy functionality (See also "GDPR")
44require_once __DIR__ . '/privacy/class-wp-privacy-participating-plugins.php';
45
46// Functionality to make sites private and only accessible to members with appropriate capabilities
47require_once __DIR__ . '/private-site/private-site.php';
48
49// Updates customizer Save/Publish labels to avoid confusion on launching vs saving changes on a site.
50require_once __DIR__ . '/customizer-fixes/customizer-fixes.php';
51
52require_once __DIR__ . '/class-wpcomsh-log.php';
53require_once __DIR__ . '/safeguard/plugins.php';
54require_once __DIR__ . '/plugin-conflicts-guardian-rollout.php';
55require_once __DIR__ . '/jetpack-token-error-header/class-atomic-record-jetpack-token-errors.php';
56
57/**
58 * WP.com Widgets (in alphabetical order)
59 */
60require_once __DIR__ . '/widgets/class-gravatar-widget.php';
61require_once __DIR__ . '/widgets/class-jetpack-posts-i-like-widget.php';
62require_once __DIR__ . '/widgets/class-music-player-widget.php';
63require_once __DIR__ . '/widgets/class-widget-authors-grid.php';
64require_once __DIR__ . '/widgets/class-wpcom-freshly-pressed-widget.php';
65require_once __DIR__ . '/widgets/class-wpcom-widget-recent-comments.php';
66require_once __DIR__ . '/widgets/class-wpcom-widget-reservations.php';
67
68// WP.com Category Cloud widget
69require_once __DIR__ . '/widgets/class-wpcom-category-cloud-widget.php';
70// Override core tag cloud widget to add a settable `limit` parameter
71require_once __DIR__ . '/widgets/class-wpcom-tag-cloud-widget.php';
72
73require_once __DIR__ . '/widgets/tlkio/class-tlkio-widget.php';
74require_once __DIR__ . '/widgets/class-widget-top-clicks.php';
75require_once __DIR__ . '/widgets/class-pd-top-rated.php';
76require_once __DIR__ . '/widgets/class-jetpack-widget-twitter.php';
77
78/*
79 * Autoloader check: This ensures the plugin doesn't fatal if activated before
80 * `composer install` has been run. This is a common oversight during development
81 * setup. The admin notice helps developers quickly identify the issue.
82 */
83$jetpack_autoloader = __DIR__ . '/vendor/autoload_packages.php';
84if ( is_readable( $jetpack_autoloader ) ) {
85    require_once $jetpack_autoloader;
86} else {
87    if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
88        error_log( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
89            __( 'Error loading autoloader file for WordPress.com Site Helper plugin', 'wpcomsh' )
90        );
91    }
92
93    add_action(
94        'admin_notices',
95        function () {
96            if ( get_current_screen()->id !== 'plugins' ) {
97                return;
98            }
99
100            $message = sprintf(
101                wp_kses(
102                    /* translators: Placeholder is a link to a support document. */
103                    __( 'Your installation of WordPress.com Site Helper is incomplete. If you installed WordPress.com Site Helper from GitHub, please refer to <a href="%1$s" target="_blank" rel="noopener noreferrer">this document</a> to set up your development environment. WordPress.com Site Helper must have Composer dependencies installed and built via the build command.', 'wpcomsh' ),
104                    array(
105                        'a' => array(
106                            'href'   => array(),
107                            'target' => array(),
108                            'rel'    => array(),
109                        ),
110                    )
111                ),
112                'https://github.com/Automattic/jetpack/blob/trunk/docs/development-environment.md#building-your-project'
113            );
114            wp_admin_notice(
115                $message,
116                array(
117                    'type'        => 'error',
118                    'dismissible' => true,
119                )
120            );
121        }
122    );
123
124    return;
125}
126require_once __DIR__ . '/vendor/automattic/custom-fonts/custom-fonts.php';
127require_once __DIR__ . '/vendor/automattic/custom-fonts-typekit/custom-fonts-typekit.php';
128require_once __DIR__ . '/vendor/automattic/text-media-widget-styles/text-media-widget-styles.php';
129
130// REST API
131require_once __DIR__ . '/endpoints/rest-api.php';
132
133// Load feature plugins.
134require_once __DIR__ . '/feature-plugins/activitypub.php';
135require_once __DIR__ . '/feature-plugins/additional-css.php';
136require_once __DIR__ . '/feature-plugins/ai-launchpad.php';
137require_once __DIR__ . '/feature-plugins/autosave-revision.php';
138require_once __DIR__ . '/feature-plugins/blaze.php';
139require_once __DIR__ . '/feature-plugins/coblocks-mods.php';
140require_once __DIR__ . '/feature-plugins/crowdsignal.php';
141require_once __DIR__ . '/feature-plugins/full-site-editing.php';
142require_once __DIR__ . '/feature-plugins/google-fonts.php';
143require_once __DIR__ . '/feature-plugins/gutenberg-mods.php';
144require_once __DIR__ . '/feature-plugins/headstart-util.php';
145require_once __DIR__ . '/feature-plugins/headstart-woocommerce-terms.php';
146require_once __DIR__ . '/feature-plugins/hooks.php';
147require_once __DIR__ . '/feature-plugins/jetpack-ai-module-seed.php';
148require_once __DIR__ . '/feature-plugins/managed-plugins.php';
149require_once __DIR__ . '/feature-plugins/managed-themes.php';
150require_once __DIR__ . '/feature-plugins/marketplace.php';
151require_once __DIR__ . '/feature-plugins/masterbar.php';
152require_once __DIR__ . '/feature-plugins/migrate-guru-canary.php';
153require_once __DIR__ . '/feature-plugins/nav-redesign.php';
154require_once __DIR__ . '/feature-plugins/post-list.php';
155require_once __DIR__ . '/feature-plugins/class-wpcomsh-recovery-mode-sync.php';
156require_once __DIR__ . '/feature-plugins/sensei-pro-mods.php';
157require_once __DIR__ . '/feature-plugins/smtp-email-priority.php';
158require_once __DIR__ . '/feature-plugins/staging-sites.php';
159require_once __DIR__ . '/feature-plugins/stats.php';
160require_once __DIR__ . '/feature-plugins/woocommerce.php';
161require_once __DIR__ . '/feature-plugins/wordpress-mods.php';
162require_once __DIR__ . '/feature-plugins/wpcom-reader-link.php';
163require_once __DIR__ . '/feature-plugins/featured-image-in-email.php';
164
165/**
166 * Conditionally load the jetpack-mu-wpcom package.
167 *
168 * JETPACK_MU_WPCOM_LOAD_VIA_BETA_PLUGIN=true will load the package via the Jetpack Beta Tester plugin, not wpcomsh.
169 */
170if ( ! defined( 'JETPACK_MU_WPCOM_LOAD_VIA_BETA_PLUGIN' ) || ! JETPACK_MU_WPCOM_LOAD_VIA_BETA_PLUGIN ) {
171    if ( class_exists( 'Automattic\Jetpack\Jetpack_Mu_Wpcom' ) ) {
172        Automattic\Jetpack\Jetpack_Mu_Wpcom::init();
173    }
174}
175
176if ( ! class_exists( 'Jetpack_Data' ) ) {
177    require_once __DIR__ . '/feature-plugins/class-jetpack-data.php';
178}
179
180// Front end notices.
181require_once __DIR__ . '/frontend-notices/wpcomsh-frontend-notices.php';
182
183// wp-admin Notices
184require_once __DIR__ . '/notices/plan-notices.php';
185require_once __DIR__ . '/notices/storage-notices.php';
186require_once __DIR__ . '/notices/php-version-notices.php';
187require_once __DIR__ . '/notices/media-library-private-site-cdn-notice.php';
188require_once __DIR__ . '/notices/anyone-can-register-notice.php';
189require_once __DIR__ . '/notices/feature-moved-to-jetpack-notices.php';
190
191// Performance Profiler
192require_once __DIR__ . '/performance-profiler/performance-profiler.php';
193
194if ( defined( 'WP_CLI' ) && WP_CLI ) {
195    require_once __DIR__ . '/class-wpcomsh-cli-commands.php';
196    require_once __DIR__ . '/woa.php';
197}
198
199require_once __DIR__ . '/wpcom-migration-helpers/site-migration-helpers.php';
200
201// We include WPCom Themes results and installation on non-WP_CLI context.
202if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
203    require_once __DIR__ . '/wpcom-themes/themes.php';
204}
205
206require_once __DIR__ . '/class-jetpack-plugin-compatibility.php';
207Jetpack_Plugin_Compatibility::get_instance();
208
209require_once __DIR__ . '/support-session.php';
210
211// Adds fallback behavior for non-Gutenframed sites to be able to use the 'Share Post' functionality from WPCOM Reader.
212require_once __DIR__ . '/share-post/share-post.php';
213
214// Jetpack Connection Handlers (external storage and protected owner).
215require_once __DIR__ . '/connection/connection-handlers.php';
216
217// Require a Jetpack Connection Owner.
218require_once __DIR__ . '/jetpack-require-connection-owner/class-wpcomsh-require-connection-owner.php';
219
220// Enable MailPoet subscriber stats reports
221require_once __DIR__ . '/mailpoet/class-wpcomsh-mailpoet-subscribers-stats-report.php';
222
223// Force Jetpack to update plugins one-at-a-time to avoid a site-breaking core concurrent update bug
224// https://core.trac.wordpress.org/ticket/53705
225if (
226    ! defined( 'JETPACK_PLUGIN_AUTOUPDATE' ) &&
227    0 === strncmp( $_SERVER['REQUEST_URI'], '/xmlrpc.php?', strlen( '/xmlrpc.php?' ) ) ) { //phpcs:ignore WordPress.Security.ValidatedSanitizedInput
228    define( 'JETPACK_PLUGIN_AUTOUPDATE', true );
229}
230
231/**
232 * Filter attachment URLs if the 'wpcom_attachment_subdomain' option is present.
233 * Local image files will be unaffected, as they will pass a file_exists check.
234 * Files stored remotely will be filtered to have the correct URL.
235 *
236 * Once the files have been transferred, the 'wpcom_attachment_subdomain' will
237 * be removed, preventing further stats.
238 *
239 * @param string $url The attachment URL.
240 * @param int    $post_id The post id.
241 * @return string The filtered attachment URL.
242 */
243function wpcomsh_get_attachment_url( $url, $post_id ) {
244    $attachment_subdomain = get_option( 'wpcom_attachment_subdomain' );
245    if ( $attachment_subdomain ) {
246        $file = get_post_meta( $post_id, '_wp_attached_file', true );
247
248        if ( $file ) {
249            $local_file = WP_CONTENT_DIR . '/uploads/' . $file;
250            if ( ! file_exists( $local_file ) ) {
251                return esc_url( 'https://' . $attachment_subdomain . '/' . $file );
252            }
253        }
254    }
255    return $url;
256}
257add_filter( 'wp_get_attachment_url', 'wpcomsh_get_attachment_url', 11, 2 );
258
259/**
260 * When WordPress.com passes along an expiration for auth cookies and it is smaller
261 * than the value set by Jetpack by default (YEAR_IN_SECONDS), use the smaller value.
262 *
263 * @param int $seconds The cookie expiration in seconds.
264 * @return int The filtered cookie expiration in seconds
265 */
266function wpcomsh_jetpack_sso_auth_cookie_expiration( $seconds ) {
267    if ( isset( $_GET['expires'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
268        $expires = absint( $_GET['expires'] ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
269
270        if ( ! empty( $expires ) && $expires < $seconds ) {
271            $seconds = $expires;
272        }
273    }
274    return intval( $seconds );
275}
276add_filter( 'jetpack_sso_auth_cookie_expiration', 'wpcomsh_jetpack_sso_auth_cookie_expiration' );
277
278/**
279 * Determine if users should be enforced to log in with their WP.com account.
280 *
281 * Sites without local users:
282 * - WP.com login, always.
283 *
284 * Sites with local users:
285 * - If user comes from Calypso: WP.com login
286 * - Otherwise: Jetpack SSO login, so they can decide whether to use a WP.com account or a local account.
287 */
288function wpcomsh_bypass_jetpack_sso_login() {
289    $calypso_domains = array(
290        'https://wordpress.com/',
291        'https://horizon.wordpress.com/',
292        'https://wpcalypso.wordpress.com/',
293        'http://calypso.localhost:3000/',
294        'http://127.0.0.1:41050/', // Desktop App.
295    );
296    if ( in_array( wp_get_referer(), $calypso_domains, true ) ) {
297        return true;
298    }
299
300    if ( class_exists( '\Automattic\Jetpack\Connection\Manager' ) ) {
301        $connection_manager = new \Automattic\Jetpack\Connection\Manager( 'jetpack' );
302
303        // Fetching an extra field to overcome the caching bug: https://core.trac.wordpress.org/ticket/62003
304        $users = get_users( array( 'fields' => array( 'ID', 'user_login' ) ) );
305        foreach ( $users as $user ) {
306            if ( ! $connection_manager->is_user_connected( $user->ID ) ) {
307                return false;
308            }
309        }
310    }
311
312    return true;
313}
314add_filter( 'jetpack_sso_bypass_login_forward_wpcom', 'wpcomsh_bypass_jetpack_sso_login' );
315
316/**
317 * Add 'loggedout' to the list of actions that allow the wpcom login form to be used.
318 *
319 * This means that the login screen the user sees immediately after logging out is consistent
320 * with the login screen the user sees when they are not logged in: the wpcom login form.
321 *
322 * @param array $allowed_actions The allowed actions.
323 * @return array The modified allowed actions.
324 */
325function wpcomsh_modify_jetpack_sso_allowed_actions( $allowed_actions ) {
326    $allowed_actions[] = 'loggedout';
327    return $allowed_actions;
328}
329add_filter( 'jetpack_sso_allowed_actions', 'wpcomsh_modify_jetpack_sso_allowed_actions' );
330
331/**
332 * Overwrite the default value of SSO "Match by Email" setting.
333 * p9o2xV-2zY-p2
334 */
335add_filter( 'default_option_jetpack_sso_match_by_email', '__return_true' );
336
337/**
338 * Admin enqueue style
339 */
340function wpcomsh_admin_enqueue_style() {
341    wp_enqueue_style(
342        'wpcomsh-admin-style',
343        plugins_url( 'assets/admin-style.css', __FILE__ ),
344        array(),
345        WPCOMSH_VERSION
346    );
347}
348add_action( 'admin_enqueue_scripts', 'wpcomsh_admin_enqueue_style', 999 );
349
350/**
351 * Allow custom wp options
352 *
353 * @param array $options The options.
354 *
355 * @return array
356 */
357function wpcomsh_allow_custom_wp_options( $options ) {
358    // For storing AT options.
359    $options[] = 'at_options';
360    $options[] = 'at_options_logging_on';
361    $options[] = 'at_wpcom_premium_theme';
362    $options[] = 'jetpack_fonts';
363    $options[] = 'site_logo';
364    $options[] = 'footercredit';
365    $options[] = 'wpcomsh_at_managed_plugins';
366
367    return $options;
368}
369add_filter( 'jetpack_options_whitelist', 'wpcomsh_allow_custom_wp_options' );
370
371add_filter( 'jetpack_site_automated_transfer', '__return_true' );
372
373/**
374 * Check site has pending automated transfer
375 *
376 * @return bool
377 */
378function check_site_has_pending_automated_transfer() {
379    return get_option( 'has_pending_automated_transfer' );
380}
381
382add_filter( 'jetpack_site_pending_automated_transfer', 'check_site_has_pending_automated_transfer' );
383
384/**
385 * We have some instances where `track_number` of an audio attachment is `??0` and shows up as type string.
386 * However the problem is, that if post has nested property attachments with this track_number, `json_serialize` fails silently.
387 * Of course, this should be fixed during audio upload, but we need this fix until we can clean this up properly.
388 * More detail here: https://github.com/Automattic/automated-transfer/issues/235
389 *
390 * @param array $exif_data The file exif data.
391 *
392 * @return array
393 */
394function wpcomsh_jetpack_api_fix_unserializable_track_number( $exif_data ) {
395    if ( isset( $exif_data['track_number'] ) ) {
396        $exif_data['track_number'] = intval( $exif_data['track_number'] );
397    }
398    return $exif_data;
399}
400add_filter( 'wp_get_attachment_metadata', 'wpcomsh_jetpack_api_fix_unserializable_track_number' );
401
402// Jetpack for Atomic sites are always production version.
403add_filter( 'jetpack_development_version', '__return_false' );
404
405/**
406 * Make User Agent consistent with the rest of WordPress.com.
407 *
408 * @param mixed $agent The agent.
409 */
410function wpcomsh_filter_outgoing_user_agent( $agent ) {
411    global $wp_version;
412
413    return str_replace( "WordPress/$wp_version", 'WordPress.com', $agent );
414}
415add_filter( 'http_headers_useragent', 'wpcomsh_filter_outgoing_user_agent', 999 );
416
417/**
418 * Allow redirects to WordPress.com from Customizer.
419 *
420 * @param array $hosts The hosts.
421 */
422function wpcomsh_allowed_redirect_hosts( $hosts ) {
423    if ( is_array( $hosts ) ) {
424        $hosts[] = 'wordpress.com';
425        $hosts[] = 'calypso.localhost';
426        $hosts   = array_unique( $hosts );
427    }
428    return $hosts;
429}
430add_filter( 'allowed_redirect_hosts', 'wpcomsh_allowed_redirect_hosts', 11 );
431
432/**
433 * WP.com make clickable
434 *
435 * Converts all plain-text HTTP URLs in post_content to links on display.
436 * Uses WP_HTML_Tag_Processor for proper HTML tokenization that won't be confused by
437 * content inside tags (e.g., JavaScript comparison operators in script tags).
438 *
439 * @param string $content The content.
440 * @return string Modified content with linkified URLs.
441 * @uses make_clickable()
442 * @since 20121125
443 */
444function wpcomsh_make_content_clickable( $content ) {
445    // Fast path: no URL-shaped substring, no work to do. Avoids loading the
446    // linkifier and walking the tokenizer for the common case.
447    if ( false === stripos( $content, 'http' ) && false === stripos( $content, 'www.' ) ) {
448        return $content;
449    }
450
451    if ( ! method_exists( 'WP_HTML_Tag_Processor', 'next_token' ) ) {
452        if ( function_exists( 'bump_stats_extras' ) ) {
453            bump_stats_extras( 'wpcomsh-make-content-clickable', 'skipped-no-html-api' );
454        }
455        return $content;
456    }
457
458    require_once __DIR__ . '/class-wpcomsh-html-linkifier.php';
459
460    return Wpcomsh_HTML_Linkifier::modify_raw_text_nodes(
461        $content,
462        static function ( $raw_text ) {
463            return 1 === preg_match( '~https?://|www\.~', $raw_text )
464                ? make_clickable( $raw_text )
465                : $raw_text;
466        }
467    );
468}
469add_filter( 'the_content', 'wpcomsh_make_content_clickable', 120 );
470add_filter( 'the_excerpt', 'wpcomsh_make_content_clickable', 120 );
471
472/**
473 * Hide scan threats from transients
474 *
475 * @param mixed $response The response.
476 *
477 * @return mixed
478 */
479function wpcomsh_hide_scan_threats_from_transients( $response ) {
480    if ( ! empty( $response->threats ) ) {
481        $response->threats = array();
482    }
483    return $response;
484}
485add_filter( 'transient_jetpack_scan_state', 'wpcomsh_hide_scan_threats_from_transients' );
486
487/**
488 * Unhook Jetpack Scan Admin Notice
489 *
490 * @return void
491 */
492function wpcomsh_remove_threats_from_toolbar() {
493    global $wp_admin_bar;
494    $wp_admin_bar->remove_node( 'jetpack-scan-notice' );
495}
496add_action( 'wp_before_admin_bar_render', 'wpcomsh_remove_threats_from_toolbar', 999999 );
497
498/**
499 * Hide scan threats from api
500 *
501 * @param mixed $response The reponse.
502 *
503 * @return mixed
504 */
505function wpcom_hide_scan_threats_from_api( $response ) {
506    if (
507        ! ( $response instanceof WP_REST_Response )
508        || $response->get_matched_route() !== '/jetpack/v4/scan'
509    ) {
510        return $response;
511    }
512    $response_data = $response->get_data();
513    if ( empty( $response_data['data'] ) || ! is_string( $response_data['data'] ) ) {
514        return $response;
515    }
516
517    $json_body = json_decode( $response_data['data'], true );
518    if ( null === $json_body || empty( $json_body['threats'] ) ) {
519        return $response;
520    }
521
522    $json_body['threats']  = array();
523    $response_data['data'] = wp_json_encode( $json_body, JSON_UNESCAPED_SLASHES );
524    $response->set_data( $response_data );
525
526    return $response;
527}
528add_filter( 'rest_post_dispatch', 'wpcom_hide_scan_threats_from_api' );
529
530/**
531 * Returns a standardized timezone string.
532 *
533 * `wp_timezone_string()` sometimes returns offsets (e.g. "-07:00"), which are
534 * a non-standard representation of a UTC offset that only works in PHP.
535 * This function returns a standardized timezone string instead, of the form
536 * "Etc/GMT+7" for integer hour offsets, or a matching "<Area>/<City>" form for
537 * fractional hour offsets (used e.g. in India).
538 */
539function wpcomsh_stats_timezone_string() {
540    $wp_tz = wp_timezone_string();
541
542    // Did we get back an offset?
543    if ( preg_match( '/^([+-])?(\d{1,2}):(\d{2})$/', $wp_tz, $matches ) ) {
544        $sign    = $matches[1] === '-' ? -1 : 1;
545        $hours   = intval( $matches[2], 10 );
546        $minutes = intval( $matches[3], 10 );
547
548        // For fractional hour offsets, use `timezone_name_from_abbr` to get a
549        // matching "<Area>/<City>" timezone.
550        if ( $minutes > 0 ) {
551            $offset  = $sign * ( $hours * 3600 + $minutes * 60 );
552            $city_tz = timezone_name_from_abbr( '', $offset, 0 );
553
554            if ( ! empty( $city_tz ) ) {
555                return $city_tz;
556            }
557        }
558
559        // For integer hour offsets, use "Etc/GMT(+|-)<offset>".
560        // The sign is flipped, to match how the `Etc` area is specced.
561        //
562        // This codepath is also followed if no city exists to match a
563        // fractional offset, by simply discarding the fractional part.
564        // This isn't ideal, but there's no standard way of describing
565        // these offsets, and is likely to be an extreme edge case.
566        return 'Etc/GMT' . ( $sign === -1 ? '+' : '-' ) . $hours;
567    }
568
569    // For anything that's not an offset, return the string we got from WP.
570    return $wp_tz;
571}
572
573/**
574 * Collect RUM performance data
575 * p9o2xV-XY-p2
576 */
577function wpcomsh_footer_rum_js() {
578    $service      = 'atomic';
579    $allow_iframe = '';
580    if ( 'admin_footer' === current_action() ) {
581        $service = 'atomic-wpadmin';
582
583        $block_editor = \Automattic\Jetpack\Jetpack_Mu_Wpcom\WPCOM_Block_Editor\Jetpack_WPCOM_Block_Editor::init();
584        if ( $block_editor->is_iframed_block_editor() ) {
585            $service      = 'atomic-gutenframe';
586            $allow_iframe = 'data-allow-iframe="true"';
587        }
588    }
589
590    $rum_kv = array();
591    $rum_kv = apply_filters( 'wpcomsh_rum_kv', $rum_kv, $service );
592    if ( ! is_array( $rum_kv ) ) {
593        $rum_kv = array();
594    }
595
596    $rum_kv = wpcomsh_get_woo_rum_data( $rum_kv );
597    // Add user login and theme info.
598    $rum_kv['logged_in']        = is_user_logged_in() ? '1' : '0';
599    $rum_kv['wptheme']          = get_stylesheet();
600    $rum_kv['wptheme_is_block'] = wp_is_block_theme() ? '1' : '0';
601
602    if ( count( $rum_kv ) > 0 ) {
603        $rum_kv = wp_json_encode( $rum_kv, JSON_FORCE_OBJECT | JSON_UNESCAPED_SLASHES | JSON_HEX_AMP );
604        if ( is_string( $rum_kv ) ) {
605            $rum_kv = 'data-customproperties="' . esc_attr( $rum_kv ) . '"';
606        } else {
607            $rum_kv = '';
608        }
609    } else {
610        $rum_kv = '';
611    }
612
613    $site_v = apply_filters( 'wpcomsh_bilmur_site_v', null );
614
615    if ( true === $site_v ) {
616        $site_v = md5( (string) wp_parse_url( home_url(), PHP_URL_HOST ) );
617    }
618
619    $data_site_tz = 'data-site-tz="' . esc_attr( wpcomsh_stats_timezone_string() ) . '"';
620
621    $data_site_v = ( is_string( $site_v ) && '' !== $site_v )
622            ? 'data-site-v="' . esc_attr( $site_v ) . '"'
623            : '';
624
625    printf(
626        '<meta id="bilmur" property="bilmur:data" content="" %1$s data-provider="wordpress.com" data-service="%2$s" %3$s %4$s %5$s >' . "\n",
627        $rum_kv, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
628        esc_attr( $service ),
629        wp_kses_post( $allow_iframe ),
630        $data_site_tz, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
631        $data_site_v // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
632    );
633    printf(
634        '<script defer src="%s"></script>' . "\n", //phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
635        esc_url( 'https://s0.wp.com/wp-content/js/bilmur.min.js?m=' . gmdate( 'YW' ) )
636    );
637}
638
639/**
640 * Adds WooCommerce-related data to the Real User Monitoring (RUM) array.
641 *
642 * This function checks if WooCommerce is active on the site and adds
643 * this information to the provided RUM data array. It's designed to be
644 * used as part of the RUM data collection process for Atomic sites.
645 *
646 * @param array $rum_kv An array of existing RUM key-value pairs.
647 *                      If not provided, an empty array will be used.
648 *
649 * @return array The input array with added WooCommerce data.
650 *               The 'woo_active' key will be added with a boolean value
651 *               indicating whether WooCommerce is active.
652 */
653function wpcomsh_get_woo_rum_data( $rum_kv = array() ) {
654    $woo_active           = class_exists( 'WooCommerce' ) ? '1' : '0';
655    $rum_kv['woo_active'] = $woo_active;
656    return $rum_kv;
657}
658
659add_action( 'wp_footer', 'wpcomsh_footer_rum_js' );
660add_action( 'admin_footer', 'wpcomsh_footer_rum_js' );
661
662/**
663 * Adds Atomic site ID to WooCommerce tracker data.
664 *
665 * @param array $data The WooCommerce tracker data.
666 *
667 * @return array The WooCommerce tracker data with Atomic site ID added.
668 */
669function wpcomsh_woocommerce_tracker_data( $data ) {
670    $data['atomic_site_id'] = wpcomsh_get_atomic_site_id();
671    return $data;
672}
673
674add_filter( 'woocommerce_tracker_data', 'wpcomsh_woocommerce_tracker_data' );
675
676add_filter( 'amp_dev_tools_user_default_enabled', '__return_false' );
677
678/**
679 * Tracks helper. Filters Jetpack TOS option if class exists.
680 *
681 * @param mixed $event The event.
682 * @param mixed $event_properties The event property.
683 *
684 * @return void
685 */
686function wpcomsh_record_tracks_event( $event, $event_properties ) {
687    if ( class_exists( '\Automattic\Jetpack\Tracking' ) ) {
688        // User has to agree to ToS for tracking. Thing is, on initial Simple -> Atomic we never set the ToS option.
689        // And since they agreed to WP.com ToS, we can track but in a roundabout way. :).
690        add_filter( 'jetpack_options', 'wpcomsh_jetpack_filter_tos_for_tracking', 10, 2 );
691
692        $jetpack_tracks = new \Automattic\Jetpack\Tracking( 'atomic' );
693        $jetpack_tracks->tracks_record_event(
694            wp_get_current_user(),
695            $event,
696            $event_properties
697        );
698
699        remove_filter( 'jetpack_options', 'wpcomsh_jetpack_filter_tos_for_tracking', 10 );
700    }
701}
702
703/**
704 * Helper for filtering tos_agreed for tracking purposes.
705 * Explicit function so it can be removed afterwards.
706 *
707 * @param mixed $value The value.
708 * @param mixed $name Name.
709 *
710 * @return mixed
711 */
712function wpcomsh_jetpack_filter_tos_for_tracking( $value, $name ) {
713    if ( 'tos_agreed' === $name ) {
714        return true;
715    }
716
717    return $value;
718}
719
720/**
721 * Avoid proxied v2 banner
722 *
723 * @return void
724 */
725function wpcomsh_avoid_proxied_v2_banner() {
726    $priority = has_action( 'wp_footer', 'atomic_proxy_bar' );
727    if ( false !== $priority ) {
728        remove_action( 'wp_footer', 'atomic_proxy_bar', $priority );
729    }
730
731    $priority = has_action( 'admin_footer', 'atomic_proxy_bar' );
732    if ( false !== $priority ) {
733        remove_action( 'admin_footer', 'atomic_proxy_bar', $priority );
734    }
735}
736
737// We don't want to show a "PROXIED V2" banner for legacy widget previews
738// which are normally embedded within another page.
739if (
740    defined( 'AT_PROXIED_REQUEST' ) && AT_PROXIED_REQUEST &&
741    isset( $_GET['legacy-widget-preview'] ) && //phpcs:ignore WordPress.Security.NonceVerification
742    0 === strncmp( $_SERVER['REQUEST_URI'], '/wp-admin/widgets.php?', strlen( '/wp-admin/widgets.php?' ) ) ) { //phpcs:ignore WordPress.Security.ValidatedSanitizedInput
743    add_action( 'plugins_loaded', 'wpcomsh_avoid_proxied_v2_banner' );
744}