Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
22.62% covered (danger)
22.62%
69 / 305
21.05% covered (danger)
21.05%
4 / 19
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
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
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_remove_threats_from_toolbar
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
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: 10.0.0
6 * Author: Automattic
7 * Author URI: http://automattic.com/
8 *
9 * @package wpcomsh
10 */
11
12define( 'WPCOMSH_VERSION', '10.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/managed-plugins.php';
148require_once __DIR__ . '/feature-plugins/managed-themes.php';
149require_once __DIR__ . '/feature-plugins/marketplace.php';
150require_once __DIR__ . '/feature-plugins/masterbar.php';
151require_once __DIR__ . '/feature-plugins/migrate-guru-canary.php';
152require_once __DIR__ . '/feature-plugins/nav-redesign.php';
153require_once __DIR__ . '/feature-plugins/post-list.php';
154require_once __DIR__ . '/feature-plugins/class-wpcomsh-recovery-mode-sync.php';
155require_once __DIR__ . '/feature-plugins/sensei-pro-mods.php';
156require_once __DIR__ . '/feature-plugins/smtp-email-priority.php';
157require_once __DIR__ . '/feature-plugins/staging-sites.php';
158require_once __DIR__ . '/feature-plugins/stats.php';
159require_once __DIR__ . '/feature-plugins/woocommerce.php';
160require_once __DIR__ . '/feature-plugins/wordpress-mods.php';
161require_once __DIR__ . '/feature-plugins/wpcom-reader-link.php';
162require_once __DIR__ . '/feature-plugins/featured-image-in-email.php';
163
164/**
165 * Conditionally load the jetpack-mu-wpcom package.
166 *
167 * JETPACK_MU_WPCOM_LOAD_VIA_BETA_PLUGIN=true will load the package via the Jetpack Beta Tester plugin, not wpcomsh.
168 */
169if ( ! defined( 'JETPACK_MU_WPCOM_LOAD_VIA_BETA_PLUGIN' ) || ! JETPACK_MU_WPCOM_LOAD_VIA_BETA_PLUGIN ) {
170    if ( class_exists( 'Automattic\Jetpack\Jetpack_Mu_Wpcom' ) ) {
171        Automattic\Jetpack\Jetpack_Mu_Wpcom::init();
172    }
173}
174
175if ( ! class_exists( 'Jetpack_Data' ) ) {
176    require_once __DIR__ . '/feature-plugins/class-jetpack-data.php';
177}
178
179// Front end notices.
180require_once __DIR__ . '/frontend-notices/wpcomsh-frontend-notices.php';
181
182// wp-admin Notices
183require_once __DIR__ . '/notices/plan-notices.php';
184require_once __DIR__ . '/notices/storage-notices.php';
185require_once __DIR__ . '/notices/php-version-notices.php';
186require_once __DIR__ . '/notices/media-library-private-site-cdn-notice.php';
187require_once __DIR__ . '/notices/anyone-can-register-notice.php';
188require_once __DIR__ . '/notices/feature-moved-to-jetpack-notices.php';
189
190// Performance Profiler
191require_once __DIR__ . '/performance-profiler/performance-profiler.php';
192
193if ( defined( 'WP_CLI' ) && WP_CLI ) {
194    require_once __DIR__ . '/class-wpcomsh-cli-commands.php';
195    require_once __DIR__ . '/woa.php';
196}
197
198require_once __DIR__ . '/wpcom-migration-helpers/site-migration-helpers.php';
199
200// We include WPCom Themes results and installation on non-WP_CLI context.
201if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
202    require_once __DIR__ . '/wpcom-themes/themes.php';
203}
204
205require_once __DIR__ . '/class-jetpack-plugin-compatibility.php';
206Jetpack_Plugin_Compatibility::get_instance();
207
208require_once __DIR__ . '/support-session.php';
209
210// Adds fallback behavior for non-Gutenframed sites to be able to use the 'Share Post' functionality from WPCOM Reader.
211require_once __DIR__ . '/share-post/share-post.php';
212
213// Jetpack Connection Handlers (external storage and protected owner).
214require_once __DIR__ . '/connection/connection-handlers.php';
215
216// Require a Jetpack Connection Owner.
217require_once __DIR__ . '/jetpack-require-connection-owner/class-wpcomsh-require-connection-owner.php';
218
219// Enable MailPoet subscriber stats reports
220require_once __DIR__ . '/mailpoet/class-wpcomsh-mailpoet-subscribers-stats-report.php';
221
222// Force Jetpack to update plugins one-at-a-time to avoid a site-breaking core concurrent update bug
223// https://core.trac.wordpress.org/ticket/53705
224if (
225    ! defined( 'JETPACK_PLUGIN_AUTOUPDATE' ) &&
226    0 === strncmp( $_SERVER['REQUEST_URI'], '/xmlrpc.php?', strlen( '/xmlrpc.php?' ) ) ) { //phpcs:ignore WordPress.Security.ValidatedSanitizedInput
227    define( 'JETPACK_PLUGIN_AUTOUPDATE', true );
228}
229
230/**
231 * Filter attachment URLs if the 'wpcom_attachment_subdomain' option is present.
232 * Local image files will be unaffected, as they will pass a file_exists check.
233 * Files stored remotely will be filtered to have the correct URL.
234 *
235 * Once the files have been transferred, the 'wpcom_attachment_subdomain' will
236 * be removed, preventing further stats.
237 *
238 * @param string $url The attachment URL.
239 * @param int    $post_id The post id.
240 * @return string The filtered attachment URL.
241 */
242function wpcomsh_get_attachment_url( $url, $post_id ) {
243    $attachment_subdomain = get_option( 'wpcom_attachment_subdomain' );
244    if ( $attachment_subdomain ) {
245        $file = get_post_meta( $post_id, '_wp_attached_file', true );
246
247        if ( $file ) {
248            $local_file = WP_CONTENT_DIR . '/uploads/' . $file;
249            if ( ! file_exists( $local_file ) ) {
250                return esc_url( 'https://' . $attachment_subdomain . '/' . $file );
251            }
252        }
253    }
254    return $url;
255}
256add_filter( 'wp_get_attachment_url', 'wpcomsh_get_attachment_url', 11, 2 );
257
258/**
259 * When WordPress.com passes along an expiration for auth cookies and it is smaller
260 * than the value set by Jetpack by default (YEAR_IN_SECONDS), use the smaller value.
261 *
262 * @param int $seconds The cookie expiration in seconds.
263 * @return int The filtered cookie expiration in seconds
264 */
265function wpcomsh_jetpack_sso_auth_cookie_expiration( $seconds ) {
266    if ( isset( $_GET['expires'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended
267        $expires = absint( $_GET['expires'] ); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
268
269        if ( ! empty( $expires ) && $expires < $seconds ) {
270            $seconds = $expires;
271        }
272    }
273    return intval( $seconds );
274}
275add_filter( 'jetpack_sso_auth_cookie_expiration', 'wpcomsh_jetpack_sso_auth_cookie_expiration' );
276
277/**
278 * Determine if users should be enforced to log in with their WP.com account.
279 *
280 * Sites without local users:
281 * - WP.com login, always.
282 *
283 * Sites with local users:
284 * - If user comes from Calypso: WP.com login
285 * - Otherwise: Jetpack SSO login, so they can decide whether to use a WP.com account or a local account.
286 */
287function wpcomsh_bypass_jetpack_sso_login() {
288    $calypso_domains = array(
289        'https://wordpress.com/',
290        'https://horizon.wordpress.com/',
291        'https://wpcalypso.wordpress.com/',
292        'http://calypso.localhost:3000/',
293        'http://127.0.0.1:41050/', // Desktop App.
294    );
295    if ( in_array( wp_get_referer(), $calypso_domains, true ) ) {
296        return true;
297    }
298
299    if ( class_exists( '\Automattic\Jetpack\Connection\Manager' ) ) {
300        $connection_manager = new \Automattic\Jetpack\Connection\Manager( 'jetpack' );
301
302        // Fetching an extra field to overcome the caching bug: https://core.trac.wordpress.org/ticket/62003
303        $users = get_users( array( 'fields' => array( 'ID', 'user_login' ) ) );
304        foreach ( $users as $user ) {
305            if ( ! $connection_manager->is_user_connected( $user->ID ) ) {
306                return false;
307            }
308        }
309    }
310
311    return true;
312}
313add_filter( 'jetpack_sso_bypass_login_forward_wpcom', 'wpcomsh_bypass_jetpack_sso_login' );
314
315/**
316 * Add 'loggedout' to the list of actions that allow the wpcom login form to be used.
317 *
318 * This means that the login screen the user sees immediately after logging out is consistent
319 * with the login screen the user sees when they are not logged in: the wpcom login form.
320 *
321 * @param array $allowed_actions The allowed actions.
322 * @return array The modified allowed actions.
323 */
324function wpcomsh_modify_jetpack_sso_allowed_actions( $allowed_actions ) {
325    $allowed_actions[] = 'loggedout';
326    return $allowed_actions;
327}
328add_filter( 'jetpack_sso_allowed_actions', 'wpcomsh_modify_jetpack_sso_allowed_actions' );
329
330/**
331 * Overwrite the default value of SSO "Match by Email" setting.
332 * p9o2xV-2zY-p2
333 */
334add_filter( 'default_option_jetpack_sso_match_by_email', '__return_true' );
335
336/**
337 * Admin enqueue style
338 */
339function wpcomsh_admin_enqueue_style() {
340    wp_enqueue_style(
341        'wpcomsh-admin-style',
342        plugins_url( 'assets/admin-style.css', __FILE__ ),
343        array(),
344        WPCOMSH_VERSION
345    );
346}
347add_action( 'admin_enqueue_scripts', 'wpcomsh_admin_enqueue_style', 999 );
348
349/**
350 * Allow custom wp options
351 *
352 * @param array $options The options.
353 *
354 * @return array
355 */
356function wpcomsh_allow_custom_wp_options( $options ) {
357    // For storing AT options.
358    $options[] = 'at_options';
359    $options[] = 'at_options_logging_on';
360    $options[] = 'at_wpcom_premium_theme';
361    $options[] = 'jetpack_fonts';
362    $options[] = 'site_logo';
363    $options[] = 'footercredit';
364    $options[] = 'wpcomsh_at_managed_plugins';
365
366    return $options;
367}
368add_filter( 'jetpack_options_whitelist', 'wpcomsh_allow_custom_wp_options' );
369
370add_filter( 'jetpack_site_automated_transfer', '__return_true' );
371
372/**
373 * Check site has pending automated transfer
374 *
375 * @return bool
376 */
377function check_site_has_pending_automated_transfer() {
378    return get_option( 'has_pending_automated_transfer' );
379}
380
381add_filter( 'jetpack_site_pending_automated_transfer', 'check_site_has_pending_automated_transfer' );
382
383/**
384 * We have some instances where `track_number` of an audio attachment is `??0` and shows up as type string.
385 * However the problem is, that if post has nested property attachments with this track_number, `json_serialize` fails silently.
386 * Of course, this should be fixed during audio upload, but we need this fix until we can clean this up properly.
387 * More detail here: https://github.com/Automattic/automated-transfer/issues/235
388 *
389 * @param array $exif_data The file exif data.
390 *
391 * @return array
392 */
393function wpcomsh_jetpack_api_fix_unserializable_track_number( $exif_data ) {
394    if ( isset( $exif_data['track_number'] ) ) {
395        $exif_data['track_number'] = intval( $exif_data['track_number'] );
396    }
397    return $exif_data;
398}
399add_filter( 'wp_get_attachment_metadata', 'wpcomsh_jetpack_api_fix_unserializable_track_number' );
400
401// Jetpack for Atomic sites are always production version.
402add_filter( 'jetpack_development_version', '__return_false' );
403
404/**
405 * Make User Agent consistent with the rest of WordPress.com.
406 *
407 * @param mixed $agent The agent.
408 */
409function wpcomsh_filter_outgoing_user_agent( $agent ) {
410    global $wp_version;
411
412    return str_replace( "WordPress/$wp_version", 'WordPress.com', $agent );
413}
414add_filter( 'http_headers_useragent', 'wpcomsh_filter_outgoing_user_agent', 999 );
415
416/**
417 * Allow redirects to WordPress.com from Customizer.
418 *
419 * @param array $hosts The hosts.
420 */
421function wpcomsh_allowed_redirect_hosts( $hosts ) {
422    if ( is_array( $hosts ) ) {
423        $hosts[] = 'wordpress.com';
424        $hosts[] = 'calypso.localhost';
425        $hosts   = array_unique( $hosts );
426    }
427    return $hosts;
428}
429add_filter( 'allowed_redirect_hosts', 'wpcomsh_allowed_redirect_hosts', 11 );
430
431/**
432 * WP.com make clickable
433 *
434 * Converts all plain-text HTTP URLs in post_content to links on display.
435 * Uses WP_HTML_Tag_Processor for proper HTML tokenization that won't be confused by
436 * content inside tags (e.g., JavaScript comparison operators in script tags).
437 *
438 * @param string $content The content.
439 * @return string Modified content with linkified URLs.
440 * @uses make_clickable()
441 * @since 20121125
442 */
443function wpcomsh_make_content_clickable( $content ) {
444    // Fast path: no URL-shaped substring, no work to do. Avoids loading the
445    // linkifier and walking the tokenizer for the common case.
446    if ( false === stripos( $content, 'http' ) && false === stripos( $content, 'www.' ) ) {
447        return $content;
448    }
449
450    if ( ! method_exists( 'WP_HTML_Tag_Processor', 'next_token' ) ) {
451        if ( function_exists( 'bump_stats_extras' ) ) {
452            bump_stats_extras( 'wpcomsh-make-content-clickable', 'skipped-no-html-api' );
453        }
454        return $content;
455    }
456
457    require_once __DIR__ . '/class-wpcomsh-html-linkifier.php';
458
459    return Wpcomsh_HTML_Linkifier::modify_raw_text_nodes(
460        $content,
461        static function ( $raw_text ) {
462            return 1 === preg_match( '~https?://|www\.~', $raw_text )
463                ? make_clickable( $raw_text )
464                : $raw_text;
465        }
466    );
467}
468add_filter( 'the_content', 'wpcomsh_make_content_clickable', 120 );
469add_filter( 'the_excerpt', 'wpcomsh_make_content_clickable', 120 );
470
471/**
472 * Unhook Jetpack Scan Admin Notice
473 *
474 * Remove once Atomic only runs Jetpack builds that check `jetpack_protect_threat_notifications_enabled`.
475 *
476 * @return void
477 */
478function wpcomsh_remove_threats_from_toolbar() {
479    global $wp_admin_bar;
480    $wp_admin_bar->remove_node( 'jetpack-scan-notice' );
481}
482add_action( 'wp_before_admin_bar_render', 'wpcomsh_remove_threats_from_toolbar', 999999 );
483
484/**
485 * Returns a standardized timezone string.
486 *
487 * `wp_timezone_string()` sometimes returns offsets (e.g. "-07:00"), which are
488 * a non-standard representation of a UTC offset that only works in PHP.
489 * This function returns a standardized timezone string instead, of the form
490 * "Etc/GMT+7" for integer hour offsets, or a matching "<Area>/<City>" form for
491 * fractional hour offsets (used e.g. in India).
492 */
493function wpcomsh_stats_timezone_string() {
494    $wp_tz = wp_timezone_string();
495
496    // Did we get back an offset?
497    if ( preg_match( '/^([+-])?(\d{1,2}):(\d{2})$/', $wp_tz, $matches ) ) {
498        $sign    = $matches[1] === '-' ? -1 : 1;
499        $hours   = intval( $matches[2], 10 );
500        $minutes = intval( $matches[3], 10 );
501
502        // For fractional hour offsets, use `timezone_name_from_abbr` to get a
503        // matching "<Area>/<City>" timezone.
504        if ( $minutes > 0 ) {
505            $offset  = $sign * ( $hours * 3600 + $minutes * 60 );
506            $city_tz = timezone_name_from_abbr( '', $offset, 0 );
507
508            if ( ! empty( $city_tz ) ) {
509                return $city_tz;
510            }
511        }
512
513        // For integer hour offsets, use "Etc/GMT(+|-)<offset>".
514        // The sign is flipped, to match how the `Etc` area is specced.
515        //
516        // This codepath is also followed if no city exists to match a
517        // fractional offset, by simply discarding the fractional part.
518        // This isn't ideal, but there's no standard way of describing
519        // these offsets, and is likely to be an extreme edge case.
520        return 'Etc/GMT' . ( $sign === -1 ? '+' : '-' ) . $hours;
521    }
522
523    // For anything that's not an offset, return the string we got from WP.
524    return $wp_tz;
525}
526
527/**
528 * Collect RUM performance data
529 * p9o2xV-XY-p2
530 */
531function wpcomsh_footer_rum_js() {
532    $service      = 'atomic';
533    $allow_iframe = '';
534    if ( 'admin_footer' === current_action() ) {
535        $service = 'atomic-wpadmin';
536
537        $block_editor = \Automattic\Jetpack\Jetpack_Mu_Wpcom\WPCOM_Block_Editor\Jetpack_WPCOM_Block_Editor::init();
538        if ( $block_editor->is_iframed_block_editor() ) {
539            $service      = 'atomic-gutenframe';
540            $allow_iframe = 'data-allow-iframe="true"';
541        }
542    }
543
544    $rum_kv = array();
545    $rum_kv = apply_filters( 'wpcomsh_rum_kv', $rum_kv, $service );
546    if ( ! is_array( $rum_kv ) ) {
547        $rum_kv = array();
548    }
549
550    $rum_kv = wpcomsh_get_woo_rum_data( $rum_kv );
551    // Add user login and theme info.
552    $rum_kv['logged_in']        = is_user_logged_in() ? '1' : '0';
553    $rum_kv['wptheme']          = get_stylesheet();
554    $rum_kv['wptheme_is_block'] = wp_is_block_theme() ? '1' : '0';
555
556    if ( count( $rum_kv ) > 0 ) {
557        $rum_kv = wp_json_encode( $rum_kv, JSON_FORCE_OBJECT | JSON_UNESCAPED_SLASHES | JSON_HEX_AMP );
558        if ( is_string( $rum_kv ) ) {
559            $rum_kv = 'data-customproperties="' . esc_attr( $rum_kv ) . '"';
560        } else {
561            $rum_kv = '';
562        }
563    } else {
564        $rum_kv = '';
565    }
566
567    $site_v = apply_filters( 'wpcomsh_bilmur_site_v', null );
568
569    if ( true === $site_v ) {
570        $site_v = md5( (string) wp_parse_url( home_url(), PHP_URL_HOST ) );
571    }
572
573    $data_site_tz = 'data-site-tz="' . esc_attr( wpcomsh_stats_timezone_string() ) . '"';
574
575    $data_site_v = ( is_string( $site_v ) && '' !== $site_v )
576            ? 'data-site-v="' . esc_attr( $site_v ) . '"'
577            : '';
578
579    printf(
580        '<meta id="bilmur" property="bilmur:data" content="" %1$s data-provider="wordpress.com" data-service="%2$s" %3$s %4$s %5$s >' . "\n",
581        $rum_kv, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
582        esc_attr( $service ),
583        wp_kses_post( $allow_iframe ),
584        $data_site_tz, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
585        $data_site_v // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
586    );
587    printf(
588        '<script defer src="%s"></script>' . "\n", //phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
589        esc_url( 'https://s0.wp.com/wp-content/js/bilmur.min.js?m=' . gmdate( 'YW' ) )
590    );
591}
592
593/**
594 * Adds WooCommerce-related data to the Real User Monitoring (RUM) array.
595 *
596 * This function checks if WooCommerce is active on the site and adds
597 * this information to the provided RUM data array. It's designed to be
598 * used as part of the RUM data collection process for Atomic sites.
599 *
600 * @param array $rum_kv An array of existing RUM key-value pairs.
601 *                      If not provided, an empty array will be used.
602 *
603 * @return array The input array with added WooCommerce data.
604 *               The 'woo_active' key will be added with a boolean value
605 *               indicating whether WooCommerce is active.
606 */
607function wpcomsh_get_woo_rum_data( $rum_kv = array() ) {
608    $woo_active           = class_exists( 'WooCommerce' ) ? '1' : '0';
609    $rum_kv['woo_active'] = $woo_active;
610    return $rum_kv;
611}
612
613add_action( 'wp_footer', 'wpcomsh_footer_rum_js' );
614add_action( 'admin_footer', 'wpcomsh_footer_rum_js' );
615
616/**
617 * Adds Atomic site ID to WooCommerce tracker data.
618 *
619 * @param array $data The WooCommerce tracker data.
620 *
621 * @return array The WooCommerce tracker data with Atomic site ID added.
622 */
623function wpcomsh_woocommerce_tracker_data( $data ) {
624    $data['atomic_site_id'] = wpcomsh_get_atomic_site_id();
625    return $data;
626}
627
628add_filter( 'woocommerce_tracker_data', 'wpcomsh_woocommerce_tracker_data' );
629
630add_filter( 'amp_dev_tools_user_default_enabled', '__return_false' );
631
632/**
633 * Tracks helper. Filters Jetpack TOS option if class exists.
634 *
635 * @param mixed $event The event.
636 * @param mixed $event_properties The event property.
637 *
638 * @return void
639 */
640function wpcomsh_record_tracks_event( $event, $event_properties ) {
641    if ( class_exists( '\Automattic\Jetpack\Tracking' ) ) {
642        // User has to agree to ToS for tracking. Thing is, on initial Simple -> Atomic we never set the ToS option.
643        // And since they agreed to WP.com ToS, we can track but in a roundabout way. :).
644        add_filter( 'jetpack_options', 'wpcomsh_jetpack_filter_tos_for_tracking', 10, 2 );
645
646        $jetpack_tracks = new \Automattic\Jetpack\Tracking( 'atomic' );
647        $jetpack_tracks->tracks_record_event(
648            wp_get_current_user(),
649            $event,
650            $event_properties
651        );
652
653        remove_filter( 'jetpack_options', 'wpcomsh_jetpack_filter_tos_for_tracking', 10 );
654    }
655}
656
657/**
658 * Helper for filtering tos_agreed for tracking purposes.
659 * Explicit function so it can be removed afterwards.
660 *
661 * @param mixed $value The value.
662 * @param mixed $name Name.
663 *
664 * @return mixed
665 */
666function wpcomsh_jetpack_filter_tos_for_tracking( $value, $name ) {
667    if ( 'tos_agreed' === $name ) {
668        return true;
669    }
670
671    return $value;
672}
673
674/**
675 * Avoid proxied v2 banner
676 *
677 * @return void
678 */
679function wpcomsh_avoid_proxied_v2_banner() {
680    $priority = has_action( 'wp_footer', 'atomic_proxy_bar' );
681    if ( false !== $priority ) {
682        remove_action( 'wp_footer', 'atomic_proxy_bar', $priority );
683    }
684
685    $priority = has_action( 'admin_footer', 'atomic_proxy_bar' );
686    if ( false !== $priority ) {
687        remove_action( 'admin_footer', 'atomic_proxy_bar', $priority );
688    }
689}
690
691// We don't want to show a "PROXIED V2" banner for legacy widget previews
692// which are normally embedded within another page.
693if (
694    defined( 'AT_PROXIED_REQUEST' ) && AT_PROXIED_REQUEST &&
695    isset( $_GET['legacy-widget-preview'] ) && //phpcs:ignore WordPress.Security.NonceVerification
696    0 === strncmp( $_SERVER['REQUEST_URI'], '/wp-admin/widgets.php?', strlen( '/wp-admin/widgets.php?' ) ) ) { //phpcs:ignore WordPress.Security.ValidatedSanitizedInput
697    add_action( 'plugins_loaded', 'wpcomsh_avoid_proxied_v2_banner' );
698}