Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 354
0.00% covered (danger)
0.00%
0 / 28
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_Mu_Wpcom
0.00% covered (danger)
0.00%
0 / 353
0.00% covered (danger)
0.00%
0 / 28
13572
0.00% covered (danger)
0.00%
0 / 1
 init
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
42
 schedule_translation_updates
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 maybe_update_translations
0.00% covered (danger)
0.00%
0 / 61
0.00% covered (danger)
0.00%
0 / 1
420
 clear_translation_destination
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 get_all_active_locales
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 load_features
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 1
6
 load_wpcom_user_features
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
56
 load_wpcom_sites_features
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 load_etk_features_flags
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
20
 load_etk_features
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
90
 load_newspack_blocks
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 load_coming_soon
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
90
 load_launchpad
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 load_wpcom_rest_api_endpoints
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 load_jetpack_mu_wpcom_settings
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
2
 load_map_block_settings
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 load_newsletter_categories_settings
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 unbind_focusout_on_wp_admin_bar_menu_toggle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 should_disable_comment_experience
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
90
 load_verbum_comments
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 load_verbum_comments_admin
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 load_verbum_moderate
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 load_wpcom_simple_odyssey_stats
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 load_custom_css
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 load_wpcom_random_redirect
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 load_social_links
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 set_wpcom_blog_id_script_data
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 add_jetpack_script_data_for_p2
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2/**
3 * Enhances your site with features powered by WordPress.com
4 * This package is intended for internal use on WordPress.com sites only (simple and Atomic).
5 * Internal PT Reference: p9dueE-6jY-p2
6 *
7 * @package automattic/jetpack-mu-wpcom
8 */
9
10namespace Automattic\Jetpack;
11
12define( 'WPCOM_ADMIN_BAR_UNIFICATION', true );
13/**
14 * Jetpack_Mu_Wpcom main class.
15 */
16class Jetpack_Mu_Wpcom {
17    const PACKAGE_VERSION = '6.10.1';
18    const PKG_DIR         = __DIR__ . '/../';
19    const BASE_DIR        = __DIR__ . '/';
20    const BASE_FILE       = __FILE__;
21
22    /**
23     * Initialize the class.
24     */
25    public static function init() {
26        if ( did_action( 'jetpack_mu_wpcom_initialized' ) ) {
27            return;
28        }
29
30        // Shared code for src/features.
31        require_once self::PKG_DIR . 'src/common/index.php'; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.NotAbsolutePath
32        require_once __DIR__ . '/utils.php';
33        require_once __DIR__ . '/lib/load.php';
34
35        // Load features that don't need any special loading considerations.
36        add_action( 'plugins_loaded', array( __CLASS__, 'load_features' ) );
37
38        // Load features that only apply to WordPress.com-connected users.
39        add_action( 'plugins_loaded', array( __CLASS__, 'load_wpcom_user_features' ) );
40        add_action( 'plugins_loaded', array( __CLASS__, 'load_etk_features' ) );
41
42        // Load features that only apply to WordPress.com sites, regardless of whether the users are connected.
43        add_action( 'plugins_loaded', array( __CLASS__, 'load_wpcom_sites_features' ) );
44
45        // Load ETK features flag to turn off the features in the ETK plugin.
46        // It needs higher priority than the ETK plugin.
47        add_action( 'plugins_loaded', array( __CLASS__, 'load_etk_features_flags' ), 0 );
48
49        /*
50         * Please double-check whether you really need to load your feature separately.
51         * Chances are you can just add it to the `load_features` method.
52         */
53        add_action( 'plugins_loaded', array( __CLASS__, 'load_launchpad' ), 0 );
54        add_action( 'plugins_loaded', array( __CLASS__, 'load_coming_soon' ) );
55        add_action( 'plugins_loaded', array( __CLASS__, 'load_wpcom_rest_api_endpoints' ) );
56        add_action( 'plugins_loaded', array( __CLASS__, 'load_newspack_blocks' ) );
57
58        // These features run only on simple sites.
59        if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
60            add_action( 'plugins_loaded', array( __CLASS__, 'load_verbum_comments' ) );
61            add_action( 'plugins_loaded', array( __CLASS__, 'load_verbum_moderate' ) );
62            add_action( 'wp_loaded', array( __CLASS__, 'load_verbum_comments_admin' ) );
63            add_action( 'admin_menu', array( __CLASS__, 'load_wpcom_simple_odyssey_stats' ) );
64            add_action( 'plugins_loaded', array( __CLASS__, 'load_wpcom_random_redirect' ) );
65        }
66
67        // These features run only on atomic sites.
68        if ( defined( 'IS_ATOMIC' ) && IS_ATOMIC ) {
69            add_action( 'plugins_loaded', array( __CLASS__, 'load_custom_css' ) );
70            add_action( 'init', array( __CLASS__, 'schedule_translation_updates' ) );
71        }
72
73        // Unified navigation fix for changes in WordPress 6.2.
74        add_action( 'admin_enqueue_scripts', array( __CLASS__, 'unbind_focusout_on_wp_admin_bar_menu_toggle' ) );
75
76        // Load the Map block settings.
77        add_action( 'enqueue_block_assets', array( __CLASS__, 'load_jetpack_mu_wpcom_settings' ), 999 );
78
79        // Load the Map block settings.
80        add_action( 'enqueue_block_assets', array( __CLASS__, 'load_map_block_settings' ), 999 );
81
82        // Load the Newsletter category settings.
83        add_action( 'enqueue_block_assets', array( __CLASS__, 'load_newsletter_categories_settings' ), 999 );
84
85        // Load the Social Links feature.
86        add_action( 'init', array( __CLASS__, 'load_social_links' ), 30 );
87
88        // Filter to ensure JetpackScriptData.site.host and is_wpcom_platform is set, to ensure Jetpack blocks work as expected via P2.
89        add_filter( 'jetpack_public_js_script_data', array( __CLASS__, 'add_jetpack_script_data_for_p2' ), 10, 1 );
90
91        // Filter to populate JetpackScriptData.site.wpcom.blog_id with the actual WP.com blog ID.
92        add_filter( 'jetpack_admin_js_script_data', array( __CLASS__, 'set_wpcom_blog_id_script_data' ), 10, 1 );
93
94        /**
95         * Runs right after the Jetpack_Mu_Wpcom package is initialized.
96         *
97         * @since 0.1.2
98         */
99        do_action( 'jetpack_mu_wpcom_initialized' );
100    }
101
102    /**
103     * Schedules translation updates for Jetpack MU WPCOM.
104     *
105     * This function sets up the necessary cron jobs to ensure that translation files
106     * are regularly updated.
107     *
108     * @return void
109     */
110    public static function schedule_translation_updates() {
111        add_action( 'wpcomsh_translation_update', array( __CLASS__, 'maybe_update_translations' ) );
112
113        if ( ! wp_next_scheduled( 'wpcomsh_translation_update' ) ) {
114            wp_schedule_event( time(), 'twicedaily', 'wpcomsh_translation_update' );
115        }
116    }
117
118    /**
119     * Fetches and installs Jetpack-mu-wpcom package translations when needed.
120     */
121    public static function maybe_update_translations() {
122        global $wp_filesystem;
123        if ( ! $wp_filesystem ) {
124            require_once ABSPATH . 'wp-admin/includes/file.php';
125            WP_Filesystem();
126        }
127
128        $locales = self::get_all_active_locales();
129        if ( empty( $locales ) ) {
130            return;
131        }
132
133        $plugins_request_data              = array();
134        $plugin_language_pack_destinations = array(
135            'jetpack-mu-wpcom' => WP_LANG_DIR . '/mu-plugins/',
136            'wpcomsh'          => WP_LANG_DIR . '/mu-plugins/',
137        );
138
139        foreach ( array_keys( $plugin_language_pack_destinations ) as $plugin_slug ) {
140            $plugins_request_data[ $plugin_slug ] = array( 'version' => 'latest' );
141        }
142
143        $response = wp_remote_post(
144            'https://translate.wordpress.com/api/translations-updates/wpcom/plugins',
145            array(
146                'body'    => wp_json_encode(
147                    array(
148                        'locales' => $locales,
149                        'plugins' => $plugins_request_data,
150                    ),
151                    JSON_UNESCAPED_SLASHES
152                ),
153                'headers' => array( 'Content-Type' => 'application/json' ),
154                'timeout' => 10,
155            )
156        );
157
158        if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
159            return;
160        }
161
162        $data = json_decode( wp_remote_retrieve_body( $response ), true );
163
164        // API error, api returned but something was wrong.
165        if ( array_key_exists( 'success', $data ) && false === $data['success'] ) {
166            return;
167        }
168
169        if ( ! is_array( $data ) || ! is_array( $data['data'] ) ) {
170            return;
171        }
172
173        foreach ( $data['data'] as $plugin_name => $language_packs ) {
174            if ( ! isset( $plugin_language_pack_destinations[ $plugin_name ] ) ) {
175                continue;
176            }
177
178            $destination = $plugin_language_pack_destinations[ $plugin_name ];
179
180            foreach ( $language_packs as $translation ) {
181                $locale        = $translation['wp_locale'] ?? '';
182                $package_url   = $translation['package'] ?? '';
183                $last_modified = $translation['last_modified'] ?? '';
184
185                if ( ! $locale || ! $package_url || ! $last_modified ) {
186                    continue;
187                }
188
189                $local_po_file = "{$destination}/$plugin_name-{$locale}.po";
190                if ( file_exists( $local_po_file ) ) {
191                    $local_po_data                       = wp_get_pomo_file_data( $local_po_file );
192                    $installed_translation_revision_time = new \DateTime( $local_po_data['PO-Revision-Date'] );
193                    $new_translation_revision_time       = new \DateTime( $last_modified );
194
195                    // Skip if translation language pack is not newer than what is installed already.
196                    if ( $new_translation_revision_time <= $installed_translation_revision_time ) {
197                        continue;
198                    }
199                }
200
201                $translation_zip_file = download_url( $package_url );
202                if ( is_wp_error( $translation_zip_file ) ) {
203                    continue;
204                }
205
206                static::clear_translation_destination( $destination, $plugin_name, $locale );
207
208                $unzip_result = unzip_file( $translation_zip_file, $destination );
209                if ( is_wp_error( $unzip_result ) ) {
210                    wp_delete_file( $translation_zip_file );
211                    continue;
212                }
213
214                wp_delete_file( $translation_zip_file );
215
216            }
217        }
218    }
219
220    /**
221     * Clears the translation destination by deleting existing translation files.
222     *
223     * @param string $local_destination The local destination path.
224     * @param string $plugin_slug The plugin slug.
225     * @param string $locale The locale.
226     */
227    public static function clear_translation_destination( $local_destination, $plugin_slug, $locale ) {
228        global $wp_filesystem;
229
230        if ( ! $wp_filesystem ) {
231            require_once ABSPATH . 'wp-admin/includes/file.php';
232            WP_Filesystem();
233        }
234
235        $files = array(
236            "{$local_destination}{$plugin_slug}-{$locale}.po",
237            "{$local_destination}{$plugin_slug}-{$locale}.mo",
238            "{$local_destination}{$plugin_slug}-{$locale}.l10n.php",
239        );
240
241        $json_files = glob( "{$local_destination}{$plugin_slug}-{$locale}-*.json" );
242        if ( $json_files ) {
243            $files = array_merge( $files, $json_files );
244        }
245
246        foreach ( $files as $file ) {
247            if ( $wp_filesystem->exists( $file ) ) {
248                $wp_filesystem->delete( $file );
249            }
250        }
251    }
252
253    /**
254     * Retrieves all active locales for the site.
255     */
256    public static function get_all_active_locales() {
257        $locales = array( get_locale() );
258
259        $available_languages = get_available_languages();
260        if ( ! empty( $available_languages ) ) {
261            $locales = array_merge( $locales, $available_languages );
262        }
263        return array_values( array_unique( $locales ) );
264    }
265
266    /**
267     * Load features that don't need any special loading considerations.
268     */
269    public static function load_features() {
270        \Automattic\Jetpack\ExPlat::init();
271
272        // Please keep the features in alphabetical order.
273        require_once __DIR__ . '/features/100-year-plan/enhanced-ownership.php';
274        require_once __DIR__ . '/features/100-year-plan/locked-mode.php';
275        require_once __DIR__ . '/features/admin-color-schemes/admin-color-schemes.php';
276        require_once __DIR__ . '/features/block-patterns/block-patterns.php';
277        require_once __DIR__ . '/features/blog-privacy/blog-privacy.php';
278        require_once __DIR__ . '/features/cloudflare-analytics/cloudflare-analytics.php';
279        require_once __DIR__ . '/features/code-editor/class-code-editor.php';
280        require_once __DIR__ . '/features/wpcom-blocks/code/class-code-block.php';
281        require_once __DIR__ . '/features/css-monkey-patches/index.php';
282        require_once __DIR__ . '/features/error-reporting/error-reporting.php';
283        require_once __DIR__ . '/features/first-posts-stream/first-posts-stream-helpers.php';
284        require_once __DIR__ . '/features/font-smoothing-antialiased/font-smoothing-antialiased.php';
285        require_once __DIR__ . '/features/google-analytics/google-analytics.php';
286        require_once __DIR__ . '/features/holiday-snow/class-holiday-snow.php';
287        require_once __DIR__ . '/features/launch-button/index.php';
288        require_once __DIR__ . '/features/logo-tool/logo-tool.php';
289        require_once __DIR__ . '/features/marketplace-products-updater/class-marketplace-products-updater.php';
290        require_once __DIR__ . '/features/media/heif-support.php';
291        require_once __DIR__ . '/features/post-categories/quick-actions.php';
292        require_once __DIR__ . '/features/post-like-from-email/post-like-from-email.php';
293        require_once __DIR__ . '/features/site-editor-dashboard-link/site-editor-dashboard-link.php';
294        require_once __DIR__ . '/features/wpcom-admin-dashboard/wpcom-admin-dashboard.php';
295        require_once __DIR__ . '/features/wpcom-attachment-pages/wpcom-attachment-pages.php';
296        require_once __DIR__ . '/features/wpcom-block-editor/class-jetpack-wpcom-block-editor.php';
297        require_once __DIR__ . '/features/wpcom-block-editor/functions.editor-type.php';
298        require_once __DIR__ . '/features/wpcom-dashboard/class-wpcom-dashboard.php';
299        require_once __DIR__ . '/features/wpcom-hotfixes/wpcom-hotfixes.php';
300        require_once __DIR__ . '/features/wpcom-logout/wpcom-logout.php';
301        require_once __DIR__ . '/features/wpcom-themes/wpcom-theme-fixes.php';
302        require_once __DIR__ . '/features/wpcom-post-list/wpcom-post-types-tracking.php';
303        require_once __DIR__ . '/features/wpcom-widgets/wpcom-widgets.php';
304        require_once __DIR__ . '/features/wpcom-wpadmin-page-view/wpcom-wpadmin-page-view.php';
305
306        /*
307         * Temporarily disable client-side media processing.
308         *
309         * Client-side media processing enables cross-origin isolation (COEP/COOP headers)
310         * which can break authenticated API requests. This should be removed once client-side
311         * media processing is compatible with Dotcom's infrastructure.
312         *
313         * @see gutenberg_set_up_cross_origin_isolation() in Gutenberg's lib/media/load.php
314         * @see https://a8c.slack.com/archives/CBTN58FTJ/p1771950744814189
315         */
316        add_filter( 'wp_client_side_media_processing_enabled', '__return_false' );
317
318        // Initializers, if needed.
319        \Marketplace_Products_Updater::init();
320        \Automattic\Jetpack\Code_Editor::setup();
321        \Automattic\Jetpack\Code_Block::setup();
322        \Automattic\Jetpack\Classic_Theme_Helper\Main::init();
323        \Automattic\Jetpack\Classic_Theme_Helper\Featured_Content::setup();
324
325        \Automattic\Jetpack\Jetpack_Mu_Wpcom\Holiday_Snow::init();
326        \Automattic\Jetpack\Jetpack_Mu_Wpcom\Wpcom_Dashboard::init();
327
328        // Gets autoloaded from the Scheduled_Updates package.
329        if ( class_exists( 'Automattic\Jetpack\Scheduled_Updates' ) ) {
330            Scheduled_Updates::init();
331        }
332    }
333
334    /**
335     * Load features that only apply to WordPress.com-connected users.
336     */
337    public static function load_wpcom_user_features() {
338        // To avoid potential collisions with ETK.
339        if ( ! class_exists( 'A8C\FSE\Help_Center' ) ) {
340            require_once __DIR__ . '/features/help-center/class-help-center.php';
341        }
342
343        if ( ! is_wpcom_user() ) {
344            require_once __DIR__ . '/features/replace-site-visibility/hide-site-visibility.php';
345            return;
346        }
347        if ( ! class_exists( 'A8C\FSE\Agents_Manager' ) ) {
348            require_once __DIR__ . '/features/agents-manager/class-agents-manager.php';
349        }
350        if ( ! class_exists( 'A8C\FSE\Survicate' ) ) {
351            require_once __DIR__ . '/features/survicate/class-survicate.php';
352        }
353        require_once __DIR__ . '/features/ai-assistant-banner/ai-assistant-banner.php';
354        require_once __DIR__ . '/features/html-block-restricted-tags/html-block-restricted-tags.php';
355        require_once __DIR__ . '/features/marketing/marketing.php';
356        require_once __DIR__ . '/features/pages/pages.php';
357        require_once __DIR__ . '/features/replace-site-visibility/replace-site-visibility.php';
358        require_once __DIR__ . '/features/stats/stats.php';
359        require_once __DIR__ . '/features/wpcom-admin-bar/wpcom-admin-bar.php';
360        require_once __DIR__ . '/features/wpcom-admin-interface/wpcom-admin-interface.php';
361        require_once __DIR__ . '/features/wpcom-admin-menu/wpcom-admin-menu.php';
362        require_once __DIR__ . '/features/wpcom-colourlovers-deprecate/wpcom-colourlovers-deprecate.php';
363        require_once __DIR__ . '/features/wpcom-comments/wpcom-comments.php';
364        require_once __DIR__ . '/features/wpcom-dashboard-widgets/wpcom-dashboard-widgets.php';
365        require_once __DIR__ . '/features/wpcom-imports/wpcom-imports.php';
366        require_once __DIR__ . '/features/wpcom-locale/sync-locale-from-calypso-to-atomic.php';
367        require_once __DIR__ . '/features/wpcom-media/wpcom-media-url-upload.php';
368        require_once __DIR__ . '/features/wpcom-media/wpcom-export-media-files.php';
369        require_once __DIR__ . '/features/wpcom-options-general/options-general.php';
370        require_once __DIR__ . '/features/wpcom-plugins/wpcom-plugins.php';
371        require_once __DIR__ . '/features/wpcom-profile-settings/profile-settings-link-to-wpcom.php';
372        require_once __DIR__ . '/features/wpcom-profile-settings/profile-settings-notices.php';
373        require_once __DIR__ . '/features/wpcom-sidebar-notice/wpcom-sidebar-notice.php';
374        require_once __DIR__ . '/features/wpcom-themes/wpcom-theme-tracking.php';
375        require_once __DIR__ . '/features/wpcom-themes/wpcom-themes.php';
376        require_once __DIR__ . '/features/wpcom-user-edit/wpcom-user-edit.php';
377
378        // Enable newsletter settings for sites with the newsletter-package-202603 sticker.
379        add_filter( 'jetpack_wp_admin_newsletter_settings_enabled', 'wpcom_maybe_enable_newsletter_settings' );
380
381        // Initialize Newsletter Settings so hooks like the Reading page notice
382        // are registered on Simple sites (where load-jetpack.php doesn't run).
383        \Automattic\Jetpack\Newsletter\Settings::init();
384
385        // Only load the Masterbar features on WoA sites.
386        if ( class_exists( '\Automattic\Jetpack\Status\Host' ) && ( new \Automattic\Jetpack\Status\Host() )->is_woa_site() ) {
387            // This is temporary. After we cleanup Masterbar on WPCOM we should load Masterbar for Simple sites too.
388            \Automattic\Jetpack\Masterbar\Main::init();
389        }
390    }
391
392    /**
393     * Load features that only apply to WordPress.com sites, regardless of whether the users are connected.
394     */
395    public static function load_wpcom_sites_features() {
396        if ( is_fully_managed_agency_site() ) {
397            return;
398        }
399
400        require_once __DIR__ . '/features/gutenberg-rtc/gutenberg-rtc.php';
401        require_once __DIR__ . '/features/wpcom-contact-form-flags/wpcom-contact-form-flags.php';
402    }
403
404    /**
405     * Define the flags to turn off features in the ETK plugin.
406     * Can be removed once the feature no longer exists in the ETK plugin.
407     */
408    public static function load_etk_features_flags() {
409        // Don't load on agency sites.
410        if ( is_fully_managed_agency_site() ) {
411            return;
412        }
413
414        // Don't load if the user is not a wpcom user on WP Admin.
415        // The features is still required on the frontend page regardless of the user.
416        if ( is_admin() && ! is_wpcom_user() ) {
417            return;
418        }
419
420        define( 'MU_WPCOM_COBLOCKS_GALLERY', true );
421        define( 'MU_WPCOM_CUSTOM_LINE_HEIGHT', true );
422        define( 'MU_WPCOM_BLOCK_INSERTER_MODIFICATIONS', true );
423        define( 'MU_WPCOM_HOMEPAGE_TITLE_HIDDEN', true );
424        define( 'MU_WPCOM_JETPACK_GLOBAL_STYLES', true );
425        define( 'A8C_USE_FONT_SMOOTHING_ANTIALIASED', false );
426        define( 'MU_WPCOM_MAILERLITE_WIDGET', true );
427        define( 'MU_WPCOM_OVERRIDE_PREVIEW_BUTTON_URL', true );
428        define( 'MU_WPCOM_PARAGRAPH_BLOCK', true );
429        define( 'MU_WPCOM_STARTER_PAGE_TEMPLATES', true );
430        define( 'MU_WPCOM_TAGS_EDUCATION', true );
431        define( 'MU_WPCOM_BLOCK_DESCRIPTION_LINKS', true );
432        define( 'MU_WPCOM_BLOCK_EDITOR_NUX', true );
433        define( 'MU_WPCOM_POSTS_LIST_BLOCK', true );
434        define( 'MU_WPCOM_JETPACK_COUNTDOWN_BLOCK', true );
435        define( 'MU_WPCOM_JETPACK_TIMELINE_BLOCK', true );
436        define( 'MU_WPCOM_DOCUMENTATION_LINKS', true );
437        define( 'MU_WPCOM_GLOBAL_STYLES', true );
438        define( 'MU_WPCOM_FSE', true );
439        define( 'MU_WPCOM_TEMPLATE_INSERTER', true );
440        define( 'MU_WPCOM_WHATS_NEW', true );
441    }
442
443    /**
444     * Load ETK features.
445     * Can be moved back to load_features() once the feature no longer exists in the ETK plugin.
446     */
447    public static function load_etk_features() {
448        // Don't load on agency sites.
449        if ( is_fully_managed_agency_site() ) {
450            return;
451        }
452
453        // Don't load if the user is not a wpcom user on WP Admin.
454        // The features is still required on the frontend page regardless of the user.
455        if ( is_admin() && ! is_wpcom_user() ) {
456            return;
457        }
458
459        require_once __DIR__ . '/features/jetpack-global-styles/class-global-styles.php';
460        require_once __DIR__ . '/features/mailerlite/subscriber-popup.php';
461        require_once __DIR__ . '/features/wpcom-fse/wpcom-fse.php';
462
463        /**
464         * Load features for the editor and the frontend pages.
465         */
466        global $pagenow;
467        $allowed_pages = array( 'post.php', 'post-new.php', 'site-editor.php' );
468        if ( ( isset( $pagenow ) && in_array( $pagenow, $allowed_pages, true ) ) || ! is_admin() ) {
469            require_once __DIR__ . '/features/block-editor/custom-line-height.php';
470            require_once __DIR__ . '/features/block-inserter-modifications/block-inserter-modifications.php';
471            require_once __DIR__ . '/features/hide-homepage-title/hide-homepage-title.php';
472            require_once __DIR__ . '/features/override-preview-button-url/override-preview-button-url.php';
473            require_once __DIR__ . '/features/paragraph-block-placeholder/paragraph-block-placeholder.php';
474            require_once __DIR__ . '/features/tags-education/tags-education.php';
475            require_once __DIR__ . '/features/wpcom-block-description-links/wpcom-block-description-links.php';
476            require_once __DIR__ . '/features/wpcom-block-editor-nux/class-wpcom-block-editor-nux.php';
477            require_once __DIR__ . '/features/wpcom-blocks/a8c-posts-list/a8c-posts-list.php';
478            require_once __DIR__ . '/features/wpcom-blocks/event-countdown/event-countdown.php';
479            require_once __DIR__ . '/features/wpcom-blocks/timeline/timeline.php';
480            require_once __DIR__ . '/features/wpcom-documentation-links/wpcom-documentation-links.php';
481            require_once __DIR__ . '/features/wpcom-global-styles/index.php';
482            require_once __DIR__ . '/features/wpcom-legacy-fse/wpcom-legacy-fse.php';
483        } elseif ( isset( $pagenow ) && 'customize.php' === $pagenow ) {
484            // Load wpcom-global-styles on the customizer so access to additional css can be checked there.
485            require_once __DIR__ . '/features/wpcom-global-styles/index.php';
486        }
487    }
488
489    /**
490     * Load the newspack blocks feature for the editor and the frontend pages.
491     */
492    public static function load_newspack_blocks() {
493        /**
494         * Avoid potential collisions with newspack-blocks plugin.
495         */
496        if ( class_exists( '\Newspack_Blocks', false ) ) {
497            return;
498        }
499
500        global $pagenow;
501        $allowed_pages = array( 'post.php', 'post-new.php', 'site-editor.php' );
502        if ( ( isset( $pagenow ) && in_array( $pagenow, $allowed_pages, true ) ) || ! is_admin() ) {
503            define( 'MU_WPCOM_NEWSPACK_BLOCKS', true );
504            require_once __DIR__ . '/features/newspack-blocks/index.php';
505        }
506    }
507
508    /**
509     * Load the Coming Soon feature.
510     */
511    public static function load_coming_soon() {
512        /**
513         * On WoA sites, users may be using non-symlinked older versions of the FSE plugin.
514         * If they are, check the active version to avoid redeclaration errors.
515         */
516        if ( ! function_exists( 'is_plugin_active' ) ) {
517            require_once ABSPATH . 'wp-admin/includes/plugin.php';
518        }
519
520        /**
521         * Explicitly pass $markup = false in get_plugin_data to avoid indirectly calling wptexturize that could cause unintended side effects.
522         * See: https://developer.wordpress.org/reference/functions/get_plugin_data/
523         */
524        $fse_plugin                 = 'full-site-editing/full-site-editing-plugin.php';
525        $fse_plugin_path            = WP_PLUGIN_DIR . '/' . $fse_plugin;
526        $invalid_fse_version_active =
527            file_exists( $fse_plugin_path ) &&
528            is_file( $fse_plugin_path ) &&
529            is_plugin_active( $fse_plugin ) &&
530            version_compare( get_plugin_data( $fse_plugin_path, false )['Version'], '3.56084', '<' );
531
532        if ( $invalid_fse_version_active ) {
533            return;
534        }
535
536        if (
537            ( defined( 'WPCOM_PUBLIC_COMING_SOON' ) && WPCOM_PUBLIC_COMING_SOON ) ||
538            apply_filters( 'a8c_enable_public_coming_soon', false )
539        ) {
540            require_once __DIR__ . '/features/coming-soon/coming-soon.php';
541        }
542    }
543
544    /**
545     * Load the Launchpad feature.
546     */
547    public static function load_launchpad() {
548        require_once __DIR__ . '/features/launchpad/launchpad.php';
549    }
550
551    /**
552     * Load WP REST API plugins for wpcom.
553     */
554    public static function load_wpcom_rest_api_endpoints() {
555        if ( ! function_exists( 'wpcom_rest_api_v2_load_plugin' ) ) {
556            return;
557        }
558
559        // We don't use `wpcom_rest_api_v2_load_plugin_files` because it operates inconsisently.
560        $plugins = glob( __DIR__ . '/features/wpcom-endpoints/*.php' );
561
562        if ( ! is_array( $plugins ) ) {
563            return;
564        }
565
566        foreach ( array_filter( $plugins, 'is_file' ) as $plugin ) {
567            require_once $plugin;
568        }
569    }
570
571    /**
572     * Adds a global variable containing the config of the plugin to the window object.
573     */
574    public static function load_jetpack_mu_wpcom_settings() {
575        $handle = 'jetpack-mu-wpcom-settings';
576
577        // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter
578        wp_register_script(
579            $handle,
580            false,
581            array(),
582            true
583        );
584
585        $data = wp_json_encode(
586            array(
587                'assetsUrl' => plugins_url( 'build/', self::BASE_FILE ),
588            ),
589            JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP
590        );
591
592        wp_add_inline_script(
593            $handle,
594            "var JETPACK_MU_WPCOM_SETTINGS = $data;",
595            'before'
596        );
597
598        wp_enqueue_script( $handle );
599    }
600
601    /**
602     * Adds a global variable containing the map provider in a map_block_settings object to the window object.
603     */
604    public static function load_map_block_settings() {
605        if (
606            ! function_exists( 'get_current_screen' )
607            || \get_current_screen() === null
608        ) {
609            return;
610        }
611
612        // Return early if we are not in the block editor.
613        if ( ! wp_should_load_block_editor_scripts_and_styles() ) {
614            return;
615        }
616
617        $map_provider = apply_filters( 'wpcom_map_block_map_provider', 'mapbox' );
618        wp_localize_script( 'jetpack-blocks-editor', 'Jetpack_Maps', array( 'provider' => $map_provider ) );
619    }
620
621    /**
622     * Adds a global variable containing where the newsletter categories should be shown.
623     */
624    public static function load_newsletter_categories_settings() {
625        if (
626            ! function_exists( 'get_current_screen' )
627            || \get_current_screen() === null
628        ) {
629            return;
630        }
631
632        // Return early if we are not in the block editor.
633        if ( ! wp_should_load_block_editor_scripts_and_styles() ) {
634            return;
635        }
636
637        $newsletter_categories_location = apply_filters( 'wpcom_newsletter_categories_location', 'block' );
638        wp_localize_script( 'jetpack-blocks-editor', 'Jetpack_Subscriptions', array( 'newsletter_categories_location' => $newsletter_categories_location ) );
639    }
640
641    /**
642     * Unbinds focusout event handler on #wp-admin-bar-menu-toggle introduced in WordPress 6.2.
643     *
644     * The focusout event handler is preventing the unified navigation from being closed on mobile.
645     */
646    public static function unbind_focusout_on_wp_admin_bar_menu_toggle() {
647        wp_add_inline_script( 'common', '(function($){ $(document).on("wp-responsive-activate", function(){ $(".is-nav-unification #wp-admin-bar-menu-toggle, .is-nav-unification #adminmenumain").off("focusout"); } ); }(jQuery) );' );
648    }
649
650    /**
651     * Determine whether to disable the comment experience.
652     *
653     * @param int $blog_id The blog ID.
654     * @return boolean
655     */
656    private static function should_disable_comment_experience( $blog_id ) {
657        $path_wp_for_teams = WP_CONTENT_DIR . '/lib/wpforteams/functions.php';
658
659        if ( file_exists( $path_wp_for_teams ) ) {
660            require_once $path_wp_for_teams;
661        }
662
663        // This covers both P2 and P2020 themes.
664        $is_p2     = str_contains( get_stylesheet(), 'pub/p2' ) || function_exists( '\WPForTeams\is_wpforteams_site' ) && is_wpforteams_site( $blog_id );
665        $is_forums = str_contains( get_stylesheet(), 'a8c/supportforums' ); // Not in /forums.
666
667        $verbum_option_enabled = get_blog_option( $blog_id, 'enable_verbum_commenting', true );
668
669        if ( empty( $verbum_option_enabled ) ) {
670            return true;
671        }
672
673        // Don't load any comment experience in the Reader, GlotPress, wp-admin, or P2.
674        return ( 1 === $blog_id || TRANSLATE_BLOG_ID === $blog_id || is_admin() || $is_p2 || $is_forums );
675    }
676
677    /**
678     * Load Verbum Comments.
679     */
680    public static function load_verbum_comments() {
681        if ( class_exists( 'Verbum_Comments' ) ) {
682            return;
683        } else {
684            $blog_id = get_current_blog_id();
685            // Jetpack loads Verbum though an iframe from jetpack.wordpress.com.
686            // So we need to check the GET request for the blogid.
687            // phpcs:ignore WordPress.Security.NonceVerification.Recommended
688            if ( isset( $_GET['blogid'] ) ) {
689                $blog_id = intval( $_GET['blogid'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
690            }
691            if ( self::should_disable_comment_experience( $blog_id ) ) {
692                return;
693            }
694            require_once __DIR__ . '/features/verbum-comments/class-verbum-comments.php';
695            new \Automattic\Jetpack\Verbum_Comments();
696        }
697    }
698
699    /**
700     * Load Verbum Comments Settings.
701     */
702    public static function load_verbum_comments_admin() {
703        require_once __DIR__ . '/features/verbum-comments/assets/class-verbum-admin.php';
704        new \Automattic\Jetpack\Verbum_Admin();
705    }
706
707    /**
708     * Load Verbum Moderate.
709     */
710    public static function load_verbum_moderate() {
711        require_once __DIR__ . '/features/verbum-comments/assets/class-verbum-moderate.php';
712        new \Automattic\Jetpack\Verbum_Moderate();
713    }
714
715    /**
716     * Load Odyssey Stats in Simple sites.
717     */
718    public static function load_wpcom_simple_odyssey_stats() {
719        require_once __DIR__ . '/features/wpcom-simple-odyssey-stats/wpcom-simple-odyssey-stats.php';
720    }
721
722    /**
723     * Load the Jetpack Custom CSS feature.
724     */
725    public static function load_custom_css() {
726        require_once __DIR__ . '/features/custom-css/custom-css/preprocessors.php';
727        require_once __DIR__ . '/features/custom-css/custom-css.php';
728    }
729
730    /**
731     * Load the Random Redirect feature.
732     */
733    public static function load_wpcom_random_redirect() {
734        require_once __DIR__ . '/features/random-redirect/random-redirect.php';
735    }
736
737    /**
738     * Load the Social Links feature.
739     */
740    public static function load_social_links() {
741        if ( class_exists( 'Automattic\Jetpack\Classic_Theme_Helper\Social_Links' ) ) {
742            new \Automattic\Jetpack\Classic_Theme_Helper\Social_Links();
743        }
744    }
745
746    /**
747     * Populate JetpackScriptData.site.wpcom.blog_id with the actual WP.com blog ID.
748     *
749     * @param array $data The script data.
750     * @return array
751     */
752    public static function set_wpcom_blog_id_script_data( $data ) {
753        $blog_id = get_wpcom_blog_id();
754        if ( $blog_id ) {
755            $data['site']['wpcom']['blog_id'] = $blog_id;
756        }
757        return $data;
758    }
759
760    /**
761     * Add Jetpack script data with host information on P2
762     *
763     * @param array $data - The Jetpack script data.
764     * @return array - The modified Jetpack script data.
765     */
766    public static function add_jetpack_script_data_for_p2( $data ) {
767        if (
768        str_contains( get_stylesheet(), 'pub/p2' ) ||
769        ( function_exists( '\WPForTeams\is_wpforteams_site' ) && is_wpforteams_site( get_current_blog_id() ) )
770        ) {
771            $host = new \Automattic\Jetpack\Status\Host();
772            if ( ! isset( $data['site']['host'] ) ) {
773                $data['site']['host'] = $host->get_known_host_guess();
774            }
775            if ( ! isset( $data['site']['is_wpcom_platform'] ) ) {
776                $data['site']['is_wpcom_platform'] = $host->is_wpcom_platform();
777            }
778        }
779        return $data;
780    }
781}