Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 241
0.00% covered (danger)
0.00%
0 / 29
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_Stats_Upgrade_Nudges
0.00% covered (danger)
0.00%
0 / 241
0.00% covered (danger)
0.00%
0 / 29
5700
0.00% covered (danger)
0.00%
0 / 1
 init
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 unset_nudges_setting
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 is_backup_active
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 is_plugin_installed
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 is_plugin_active
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 is_scan_active
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 is_search_active
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 has_security_plan
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 has_complete_plan
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 has_pro_plan
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 has_product
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 is_akismet_active
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 print_header
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
 print_footer
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 print_styles
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 1
2
 print_script
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
2
 get_upgrade_link
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 get_product_description_link
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 track_event
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 print_item
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
20
 print_security
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 print_backup
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 print_scan
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 print_akismet
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 print_search
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 print_videopress
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 get_boost_output
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 get_crm_output
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 render
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
240
1<?php
2/**
3 * Adds a section with Upgrade nudges to the Status Report page
4 *
5 * @package jetpack
6 */
7
8use Automattic\Jetpack\Connection\Manager;
9use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
10use Automattic\Jetpack\Jetpack_CRM_Data;
11use Automattic\Jetpack\Plugins_Installer;
12use Automattic\Jetpack\Redirect;
13use Automattic\Jetpack\Tracking;
14
15/**
16 * Class that adds a new section to the Stats Report page
17 */
18class Jetpack_Stats_Upgrade_Nudges {
19
20    /**
21     * Indicates whether the class initialized or not
22     *
23     * @var bool
24     */
25    private static $initialized = false;
26
27    /**
28     * Initialize the class by registering the action
29     *
30     * @return void
31     */
32    public static function init() {
33        if ( ! self::$initialized ) {
34            self::$initialized = true;
35            add_action( 'jetpack_admin_pages_wrap_ui_after_callback', array( __CLASS__, 'render' ) );
36        }
37    }
38
39    /**
40     * Unsets the collapse nudges setting.
41     */
42    public static function unset_nudges_setting() {
43        $stats_options = get_option( 'stats_options' );
44
45        if ( $stats_options ) {
46            unset( $stats_options['collapse_nudges'] );
47            update_option( 'stats_options', $stats_options );
48        }
49    }
50
51    /**
52     * Determines whether Backup is active
53     *
54     * @return boolean
55     */
56    private static function is_backup_active() {
57        $rewind_data = Jetpack_Core_Json_Api_Endpoints::rewind_data();
58        return is_object( $rewind_data ) && isset( $rewind_data->state ) && 'unavailable' !== $rewind_data->state;
59    }
60
61    /**
62     * Checks if a plugin is installed.
63     *
64     * @param string $plugin_file The plugin filename.
65     * @return boolean
66     */
67    private static function is_plugin_installed( $plugin_file ) {
68        $plugins = Plugins_Installer::get_plugins();
69        return isset( $plugins[ $plugin_file ] );
70    }
71
72    /**
73     * Checks if a plugin is active.
74     *
75     * @param string $plugin_file The plugin filename.
76     * @return boolean
77     */
78    private static function is_plugin_active( $plugin_file ) {
79        $plugins = Plugins_Installer::get_plugins();
80        return isset( $plugins[ $plugin_file ] ) && isset( $plugins[ $plugin_file ]['active'] ) && $plugins[ $plugin_file ]['active'];
81    }
82
83    /**
84     * Determines whether Scan is active
85     *
86     * @return boolean
87     */
88    private static function is_scan_active() {
89        $scan_data = Jetpack_Core_Json_Api_Endpoints::scan_state();
90        return is_object( $scan_data ) && isset( $scan_data->state ) && 'unavailable' !== $scan_data->state;
91    }
92
93    /**
94     * Determines whether Search module is active
95     *
96     * @return boolean
97     */
98    private static function is_search_active() {
99        return Jetpack::is_module_active( 'search' );
100    }
101
102    /**
103     * Determines whether the Site is on a Security Plan. It will also return true if site has backup, scan and akismet.
104     *
105     * @return boolean
106     */
107    private static function has_security_plan() {
108        $plan_data = Jetpack_Plan::get();
109        if ( is_array( $plan_data ) && isset( $plan_data['product_slug'] ) ) {
110            $has_plan = wp_startswith( $plan_data['product_slug'], 'jetpack_security' );
111            return (
112                $has_plan || (
113                    self::is_backup_active() &&
114                    self::is_scan_active() &&
115                    self::is_akismet_active()
116                )
117            );
118        }
119        return false;
120    }
121
122    /**
123     * Determines whether the Site is on the Complete Plan.
124     *
125     * @return boolean
126     */
127    private static function has_complete_plan() {
128        $plan_data = Jetpack_Plan::get();
129        if ( is_array( $plan_data ) && isset( $plan_data['product_slug'] ) ) {
130            return wp_startswith( $plan_data['product_slug'], 'jetpack_complete' );
131        }
132        return false;
133    }
134
135    /**
136     * Determines whether the Site is on the Pro Plan, which also includes every product nudged here.
137     *
138     * @return boolean
139     */
140    private static function has_pro_plan() {
141        $plan_data = Jetpack_Plan::get();
142        if ( is_array( $plan_data ) && isset( $plan_data['product_slug'] ) ) {
143            return wp_startswith( $plan_data['product_slug'], 'jetpack_pro_' );
144        }
145        return false;
146    }
147
148    /**
149     * Determines whether the Site has a specific product.
150     *
151     * @param string $target_product_slug The product slug we are looking for.
152     *
153     * @return boolean
154     */
155    private static function has_product( $target_product_slug ) {
156        $site_products_slugs = array_column( Jetpack_Plan::get_products(), 'product_slug' );
157
158        foreach ( $site_products_slugs as $product_slug ) {
159            if ( wp_startswith( $product_slug, $target_product_slug ) ) {
160                return true;
161            }
162        }
163
164        return false;
165    }
166
167    /**
168     * Determines whether Akismet is active
169     *
170     * @return boolean
171     */
172    private static function is_akismet_active() {
173        return Jetpack::is_akismet_active();
174    }
175
176    /**
177     * Outputs the header of the Upgrade Secion
178     *
179     * @return void
180     */
181    private static function print_header() {
182        if ( self::has_security_plan() ) {
183            // translators: %s is the Site Name.
184            $title = __( 'Performance and growth tools for %s', 'jetpack' );
185        } else {
186            // translators: %s is the Site Name.
187            $title = __( 'Security, performance, and growth tools for %s', 'jetpack' );
188        }
189        $title = sprintf( $title, get_bloginfo( 'site_name' ) );
190
191        $aria_expanded  = 'true';
192        $postbox_closed = '';
193
194        $stats_options = get_option( 'stats_options' );
195
196        if ( isset( $stats_options['collapse_nudges'] ) && $stats_options['collapse_nudges'] ) {
197            $aria_expanded  = 'false';
198            $postbox_closed = ' closed';
199        }
200
201        ?>
202        <div id="jp-stats-report-upgrade-wrap">
203            <div class="meta-box-sortables">
204                <div class="postbox<?php echo esc_attr( $postbox_closed ); ?>">
205                    <div class="dops-card dops-section-header is-compact jp-stats-report-upgrade-header">
206                        <div class="dops-section-header__label">
207                            <span class="dops-section-header__label-text">
208                                <?php echo esc_html( $title ); ?>
209                            </span>
210                        </div>
211                        <div class="dops-section-header__actions handle-actions hide-if-no-js">
212                            <button type="button" id="stats_nudges_toggle" class="handlediv" aria-expanded="<?php echo esc_attr( $aria_expanded ); ?>">
213                                <span class="screen-reader-text">Toggle Upgrade Nudges</span>
214                                <span class="toggle-indicator" aria-hidden="true"></span>
215                            </button>
216                        </div>
217                    </div>
218                    <div class="inside">
219        <?php
220    }
221
222    /**
223     * Outputs the footer of the Upgrade Section
224     *
225     * @return void
226     */
227    private static function print_footer() {
228        ?>
229                    </div>
230                </div>
231            </div>
232        </div>
233        <?php
234    }
235
236    /**
237     * Outputs the custom css rules of the Upgrade Section
238     *
239     * @return void
240     */
241    private static function print_styles() {
242        ?>
243        <style>
244            .dops-section-header.dops-card.jp-stats-report-upgrade-header {
245                font-weight: bold;
246                box-shadow: none;
247                flex-wrap: nowrap;
248            }
249            #jp-stats-report-upgrade-wrap .dops-section-header__label-text {
250                white-space: normal;
251            }
252            #stats_nudges_toggle {
253                height: 100%;
254            }
255            .dops-banner.dops-card.is-product.jp-stats-report-upgrade-item {
256                margin-bottom: 0;
257                border-left: 0;
258                box-shadow: none;
259                border-top: 1px solid #c3c4c7;
260                padding: 12px 24px;
261            }
262            .dops-banner.dops-card.jp-stats-report-upgrade-item.jp-stats-report-upgrade-subitem {
263                margin-left: 72px;
264                padding-left: 0;
265            }
266            .jp-stats-report-upgrade-item .dops-banner__action {
267                margin-right: 0;
268            }
269            #jp-stats-report-upgrade-wrap .dops-card::after {
270                content: "";
271            }
272            .jp-stats-report-upgrade-item .dops-banner__title p {
273                margin: 5px 0 0 0;
274                font-weight: normal;
275            }
276            #jp-stats-report-upgrade-wrap .postbox {
277                background-color: white;
278                border: 1px solid #c3c4c7;
279                margin-bottom: 0;
280            }
281            #jp-stats-report-upgrade-wrap .postbox .inside {
282                padding: 0;
283            }
284        </style>
285        <?php
286    }
287    /**
288     * Add a script which handles collapse/expansion of the nudge UI.
289     */
290    private static function print_script() {
291        ?>
292        <script>
293            (function(window, document, undefined){
294                window.onload = set_up_click_handler;
295
296                const stats_wrap = document.getElementById( 'jp-stats-wrap' );
297                stats_wrap.addEventListener( 'stats-loaded', function () {
298                    const stat_chart = document.getElementById( 'statchart' );
299                    if ( stat_chart === null ) {
300                        document.getElementById( 'stats_nudges_toggle' ).style.display = 'none';
301                    }
302                });
303
304                function set_up_click_handler(){
305                    document.getElementById( 'stats_nudges_toggle' ).onclick = function () {
306                        const collapseValue = 'true' === this.getAttribute( 'aria-expanded' );
307
308                        fetch( '/wp-json/jetpack/v4/settings', {
309                            method: 'post',
310                            body: JSON.stringify( { collapse_nudges: collapseValue } ),
311                            headers: {
312                                'X-WP-Nonce': <?php echo wp_json_encode( wp_create_nonce( 'wp_rest' ), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?>,
313                                'Content-type': 'application/json' }
314                        } );
315                    };
316                }
317            })(window, document, undefined);
318        </script>
319        <?php
320    }
321
322    /**
323     * Gets the upgrade Redirect link
324     *
325     * @param string $source The source of the redirect link.
326     * @return string
327     */
328    private static function get_upgrade_link( $source ) {
329        $args = array();
330        if ( ! ( new Manager( 'jetpack' ) )->has_connected_owner() ) {
331            $args['query'] = 'unlinked=1';
332        }
333        return Redirect::get_url( $source, $args );
334    }
335
336    /**
337     * Gets the product description link.
338     *
339     * @param string $product_key The product key of the product we wish to display.
340     * @return string
341     */
342    private static function get_product_description_link( $product_key ) {
343        return Jetpack::admin_url( array( 'page' => sprintf( 'jetpack#/product/%s', $product_key ) ) );
344    }
345
346    /**
347     * Tracks an event in Tracks
348     *
349     * @param string $event_name The event name.
350     * @return void
351     */
352    private static function track_event( $event_name ) {
353        $connection_manager = new Manager( 'jetpack' );
354        $tracking           = new Tracking( 'jetpack', $connection_manager );
355        $tracking->record_user_event(
356            $event_name,
357            array(
358                'has_connected_owner' => $connection_manager->has_connected_owner(),
359            )
360        );
361    }
362
363    /**
364     * Outputs one Upgrade item
365     *
366     * @param string  $title The title of the item.
367     * @param string  $text The description of the item.
368     * @param string  $icon The path of the icon, relative to Jetpack images folder.
369     * @param string  $link The link of the button.
370     * @param string  $tracks_id The id used to identify the tracks events. Automatically prefixed with "jetpack_stats_nudges_{view|click|learn_more}_".
371     * @param string  $learn_more_link The target of the "Learn more" link.
372     * @param boolean $subitem Whether it is a subitem or not.
373     * @param string  $button_label The button label.
374     * @return void
375     */
376    private static function print_item( $title, $text, $icon, $link, $tracks_id, $learn_more_link, $subitem = false, $button_label = null ) {
377        $additional_classes = $subitem ? 'jp-stats-report-upgrade-subitem' : '';
378        $button_class       = $subitem ? 'is-secondary' : 'is-primary';
379        $icon_url           = plugins_url( '', JETPACK__PLUGIN_FILE ) . '/images/products/' . $icon;
380        $button_label       = $button_label === null ? _x( 'Upgrade', 'Call to action to buy a new plan', 'jetpack' ) : $button_label;
381        $view_event         = "stats_nudges_view_$tracks_id";
382        $click_event        = "stats_nudges_click_$tracks_id";
383        $learn_more_event   = "stats_nudges_learn_more_$tracks_id";
384        self::track_event( $view_event );
385
386        ?>
387            <div class="dops-card dops-banner has-call-to-action is-product jp-stats-report-upgrade-item <?php echo esc_attr( $additional_classes ); ?>">
388                <div class="dops-banner__icon-plan">
389                    <img src="<?php echo esc_attr( $icon_url ); ?>" alt="" width="32" height="32">
390                </div>
391                <div class="dops-banner__content">
392                    <div class="dops-banner__info">
393                        <div class="dops-banner__title">
394                            <?php echo esc_html( $title ); ?>
395                            <p>
396                                <?php echo esc_html( $text ); ?>
397                                <a href="<?php echo esc_attr( $learn_more_link ); ?>" class="jptracks" target="_blank" rel="noopener noreferrer" data-jptracks-name="<?php echo esc_attr( $learn_more_event ); ?>">
398                                    <?php esc_html_e( 'Learn more', 'jetpack' ); ?>
399                                </a>
400                            </p>
401                        </div>
402                    </div>
403                    <div class="dops-banner__action">
404                        <a href="<?php echo esc_attr( $link ); ?>" type="button" class="jptracks dops-button is-compact <?php echo esc_attr( $button_class ); ?>" data-jptracks-name="<?php echo esc_attr( $click_event ); ?>">
405                        <?php echo esc_html( $button_label ); ?>
406                        </a>
407                    </div>
408                </div>
409            </div>
410        <?php
411    }
412
413    /**
414     * Prints the Security item
415     *
416     * @return void
417     */
418    private static function print_security() {
419        $link       = self::get_product_description_link( 'security' );
420        $learn_link = self::get_upgrade_link( 'stats-nudges-security-learn' );
421        $text       = __( 'Comprehensive protection for your site, including Backup, Scan, and Anti-spam.', 'jetpack' );
422        self::print_item( _x( 'Security', 'Jetpack product name', 'jetpack' ), $text, 'product-jetpack.svg', $link, 'security', $learn_link );
423    }
424
425    /**
426     * Prints the Backup item
427     *
428     * @return void
429     */
430    private static function print_backup() {
431        $link       = self::get_product_description_link( 'backup' );
432        $learn_link = self::get_upgrade_link( 'stats-nudges-backup-learn' );
433        $text       = __( 'Save every change and get back online quickly with one-click restores.', 'jetpack' );
434        self::print_item( __( 'VaultPress Backup', 'jetpack' ), $text, 'product-jetpack-backup.svg', $link, 'backup', $learn_link, true );
435    }
436
437    /**
438     * Prints the Scan item
439     *
440     * @since 10.1
441     * @since 10.3 The scan nudge has been removed, but leaving this here in case we reverse course.
442     *
443     * @todo Remove this function is not used ~6 months.
444     *
445     * @return void
446     */
447    private static function print_scan() {
448        $link       = self::get_product_description_link( 'scan' );
449        $learn_link = self::get_upgrade_link( 'stats-nudges-scan-learn' );
450        $text       = __( 'Stay ahead of security threats with automated scanning and one-click fixes.', 'jetpack' );
451        self::print_item( __( 'Scan', 'jetpack' ), $text, 'product-jetpack-scan.svg', $link, 'scan', $learn_link, true );
452    }
453
454    /**
455     * Prints the Akismet item
456     *
457     * @since 10.1
458     * @since 10.3 The anti-spam nudge has been removed, but leaving this here in case we reverse course.
459     *
460     * @todo Remove this function is not used ~6 months.
461     *
462     * @return void
463     */
464    private static function print_akismet() {
465        $link       = self::get_product_description_link( 'akismet' );
466        $learn_link = self::get_upgrade_link( 'stats-nudges-akismet-learn' );
467        $text       = __( 'Automatically clear spam from comments and forms.', 'jetpack' );
468        self::print_item( __( 'Akismet Anti-spam', 'jetpack' ), $text, 'product-jetpack-anti-spam.svg', $link, 'akismet', $learn_link, true );
469    }
470
471    /**
472     * Prints the Search item
473     *
474     * @return void
475     */
476    private static function print_search() {
477        $link       = self::get_product_description_link( 'search' );
478        $learn_link = self::get_upgrade_link( 'stats-nudges-search-learn' );
479        $text       = __( 'Help your site visitors instantly find what they\'re looking for so they read and buy more.', 'jetpack' );
480        self::print_item( __( 'Search', 'jetpack' ), $text, 'product-jetpack-search.svg', $link, 'search', $learn_link );
481    }
482
483    /**
484     * Prints the VideoPress item
485     *
486     * @return void
487     */
488    private static function print_videopress() {
489        $link       = self::get_product_description_link( 'videopress' );
490        $learn_link = self::get_upgrade_link( 'stats-nudges-videopress-learn' );
491        $text       = __( 'Engage your visitors with high-quality, ad-free videos built specifically for WordPress.', 'jetpack' );
492        self::print_item( __( 'VideoPress', 'jetpack' ), $text, 'product-jetpack-videopress.svg', $link, 'videopress', $learn_link );
493    }
494
495    /**
496     * Prints the Boost item
497     *
498     * @param bool $print Whether to print the item output or just check whether it would be printed or not.
499     *
500     * @return bool
501     */
502    private static function get_boost_output( $print = true ) {
503        $plugin_file = 'jetpack-boost/jetpack-boost.php';
504        $plugin_slug = 'jetpack-boost';
505        if ( self::is_plugin_active( $plugin_file ) ) {
506            return false;
507        } elseif ( self::is_plugin_installed( $plugin_file ) ) {
508            $label = __( 'Activate Boost', 'jetpack' );
509            $link  = wp_nonce_url( 'plugins.php?action=activate&amp;plugin=' . rawurlencode( $plugin_file ) . '&amp;plugin_status=all&amp;paged=1', 'activate-plugin_' . $plugin_file );
510        } else {
511            $label = __( 'Install Boost', 'jetpack' );
512            $link  = wp_nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=' . $plugin_slug ), 'install-plugin_' . $plugin_slug );
513        }
514        if ( $print ) {
515            $learn_link = self::get_upgrade_link( 'stats-nudges-boost-learn' );
516            $text       = __( 'Improve your site\'s performance and SEO in a few clicks with the free Jetpack Boost plugin.', 'jetpack' );
517            self::print_item( __( 'Boost', 'jetpack' ), $text, 'product-jetpack-boost.svg', $link, 'boost', $learn_link, false, $label );
518        }
519        return true;
520    }
521
522    /**
523     * Prints the CRM item
524     *
525     * @param bool $print Whether to print the item output or just check whether it would be printed or not.
526     *
527     * @since 10.1
528     *
529     * @return bool
530     */
531    private static function get_crm_output( $print = true ) {
532        $plugin_file = Jetpack_CRM_Data::JETPACK_CRM_PLUGIN_SLUG;
533        $plugin_slug = substr( $plugin_file, 0, strpos( $plugin_file, '/' ) );
534        if ( self::is_plugin_active( $plugin_file ) ) {
535            return false;
536        } elseif ( self::is_plugin_installed( $plugin_file ) ) {
537            $link  = wp_nonce_url( 'plugins.php?action=activate&amp;plugin=' . rawurlencode( $plugin_file ) . '&amp;plugin_status=all&amp;paged=1', 'activate-plugin_' . $plugin_file );
538            $label = __( 'Activate CRM', 'jetpack' );
539        } else {
540            $link  = wp_nonce_url( self_admin_url( 'update.php?action=install-plugin&plugin=' . $plugin_slug ), 'install-plugin_' . $plugin_slug );
541            $label = __( 'Install CRM', 'jetpack' );
542        }
543        if ( $print ) {
544            $learn_link = self::get_upgrade_link( 'stats-nudges-crm-learn' );
545            $text       = __( 'Sell more and get more leads with the free Jetpack CRM plugin built specifically for WordPress.', 'jetpack' );
546            self::print_item( __( 'CRM', 'jetpack' ), $text, 'product-jetpack-crm.svg', $link, 'crm', $learn_link, false, $label );
547        }
548        return true;
549    }
550
551    /**
552     * Outputs the section to the Stats Report page
553     *
554     * @param string $callback The callback passed to the jetpack admin page.
555     * @return void
556     */
557    public static function render( $callback ) {
558        /** This filter is documented in _inc/lib/admin-pages/class.jetpack-react-page.php */
559        if ( 'stats_reports_page' !== $callback || ! apply_filters( 'jetpack_show_promotions', true ) || ! current_user_can( 'manage_options' ) ) {
560            return;
561        }
562
563        if ( self::has_complete_plan() || self::has_pro_plan() ) {
564            return;
565        }
566
567        if (
568            self::has_security_plan() &&
569            self::is_backup_active() &&
570            self::is_search_active() &&
571            ! self::get_boost_output( false ) &&
572            ! self::get_crm_output( false )
573        ) {
574            return;
575        }
576
577        self::print_styles();
578        self::print_script();
579        self::print_header();
580        if ( ! self::has_security_plan() ) {
581            self::print_security();
582            if ( ! self::is_backup_active() ) {
583                self::print_backup();
584            }
585        }
586        if ( ! self::is_search_active() ) {
587            self::print_search();
588        }
589        if ( ! self::has_product( 'jetpack_videopress' ) ) {
590            self::print_videopress();
591        }
592        self::get_boost_output();
593        self::get_crm_output();
594        self::print_footer();
595    }
596}