Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 405
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_About_Page
0.00% covered (danger)
0.00%
0 / 402
0.00% covered (danger)
0.00%
0 / 9
2652
0.00% covered (danger)
0.00%
0 / 1
 get_page_hook
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 add_page_actions
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 page_admin_scripts
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 additional_styles
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 render
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 page_render
0.00% covered (danger)
0.00%
0 / 117
0.00% covered (danger)
0.00%
0 / 1
2
 display_plugins
0.00% covered (danger)
0.00%
0 / 216
0.00% covered (danger)
0.00%
0 / 1
1482
 fetch_a8c_data
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
20
 display_gravatars
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Class for the Jetpack About Page within the wp-admin.
4 *
5 * @package automattic/jetpack
6 */
7
8/**
9 * Disable direct access and execution.
10 */
11if ( ! defined( 'ABSPATH' ) ) {
12    exit( 0 );
13}
14
15require_once __DIR__ . '/class.jetpack-admin-page.php';
16
17/**
18 * Builds the landing page and its menu.
19 */
20class Jetpack_About_Page extends Jetpack_Admin_Page {
21
22    /**
23     * Show the settings page only when Jetpack is connected or in dev mode.
24     *
25     * @var bool If the page should be shown.
26     */
27    protected $dont_show_if_not_active = true;
28
29    /**
30     * Anonymous info about a12s. The method fetch_a8c_data() stores the response from wpcom here.
31     *
32     * @var array
33     */
34    private $a8c_data = null;
35
36    /**
37     * Add a submenu item to the Jetpack admin menu.
38     *
39     * @return string
40     */
41    public function get_page_hook() {
42        // Add the main admin Jetpack menu.
43        return add_submenu_page(
44            '',
45            esc_html__( 'About Jetpack', 'jetpack' ),
46            '',
47            'jetpack_admin_page',
48            'jetpack_about',
49            array( $this, 'render' )
50        );
51    }
52
53    /**
54     * Add page action
55     *
56     * @param string $hook Hook of current page.
57     */
58    public function add_page_actions( $hook ) {
59        if ( 'admin_page_jetpack_about' === $hook ) {
60            $this->a8c_data = $this->fetch_a8c_data();
61        }
62    }
63
64    /**
65     * Enqueues scripts and styles for the admin page.
66     */
67    public function page_admin_scripts() {
68        wp_enqueue_style( 'plugin-install' );
69        wp_enqueue_script( 'plugin-install' );
70        // required for plugin modal action button functionality.
71        wp_enqueue_script( 'updates' );
72        // required for modal popup JS and styling.
73        wp_enqueue_style( 'thickbox' );
74        wp_enqueue_script( 'thickbox' );
75    }
76
77    /**
78     * Load styles for static page.
79     */
80    public function additional_styles() {
81        Jetpack_Admin_Page::load_wrapper_styles();
82    }
83
84    /**
85     * Render the page with a common top and bottom part, and page specific content
86     */
87    public function render() {
88        Jetpack_Admin_Page::wrap_ui( array( $this, 'page_render' ), array( 'show-nav' => false ) );
89    }
90
91    /**
92     * Render the page content
93     */
94    public function page_render() {
95        ?>
96        <div class="jp-lower">
97            <h1 class="screen-reader-text"><?php esc_html_e( 'About Jetpack', 'jetpack' ); ?></h1>
98            <div class="jetpack-about__link-back">
99                <a href="<?php echo esc_url( admin_url( 'admin.php?page=jetpack' ) ); ?>">
100                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect x="0" fill="none" width="24" height="24"/><g><path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></g></svg>
101                    <?php esc_html_e( 'Back to Jetpack Dashboard', 'jetpack' ); ?>
102                </a>
103            </div>
104            <div class="jetpack-about__main">
105                <div class="jetpack-about__logo">
106                    <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 800 96" style="enable-background:new 0 0 800 96;" xml:space="preserve">
107                    <g>
108                        <path style="fill: #39c;" d="M292.922,78c-19.777,0-32.598-14.245-32.598-29.078V47.08c0-15.086,12.821-29.08,32.598-29.08
109                            c19.861,0,32.682,13.994,32.682,29.08v1.843C325.604,63.755,312.783,78,292.922,78z M315.044,47.245
110                            c0-10.808-7.877-20.447-22.122-20.447s-22.04,9.639-22.04,20.447v1.341c0,10.811,7.795,20.614,22.04,20.614
111                            s22.122-9.803,22.122-20.614V47.245z"/>
112                        <path d="M69.602,75.821l-7.374-13.826H29.463l-7.124,13.826H11.277l30.167-55.81h8.715l30.671,55.81H69.602z M45.552,30.906
113                            L33.401,54.369h24.72L45.552,30.906z"/>
114                        <path d="M128.427,78c-20.028,0-29.329-10.894-29.329-25.391V20.012h10.391v32.765c0,10.308,6.788,16.424,19.692,16.424
115                            c13.242,0,18.687-6.116,18.687-16.424V20.012h10.475v32.598C158.342,66.436,149.46,78,128.427,78z"/>
116                        <path d="M216.667,28.727v47.094h-10.475V28.727h-24.386v-8.715h59.245v8.715H216.667z"/>
117                        <path d="M418.955,75.821V31.659l-2.766,4.861l-23.379,39.301h-5.112L364.569,36.52l-2.765-4.861v44.162h-10.224v-55.81h14.497
118                            l22.038,38.296L390.713,63l2.599-4.692l21.786-38.296h14.331v55.81H418.955z"/>
119                        <path d="M508.619,75.821l-7.374-13.826H468.48l-7.123,13.826h-11.061l30.167-55.81h8.715l30.669,55.81H508.619z M484.569,30.906
120                            l-12.151,23.464h24.72L484.569,30.906z"/>
121                        <path d="M562.081,28.727v47.094h-10.474V28.727h-24.386v-8.715h59.245v8.715H562.081z"/>
122                        <path d="M638.924,28.727v47.094H628.45V28.727h-24.386v-8.715h59.245v8.715H638.924z"/>
123                        <path d="M689.118,75.821v-50.53c4.19,0,5.866-2.263,5.866-5.28h4.442v55.81H689.118z"/>
124                        <path d="M781.464,35.765c-5.028-4.609-12.402-8.967-22.374-8.967c-14.916,0-23.296,10.225-23.296,20.867v1.089
125                            c0,10.558,8.464,20.445,24.05,20.445c9.303,0,17.012-4.441,21.872-8.965L788,66.854C781.883,72.887,771.492,78,759.174,78
126                            c-21.118,0-33.939-13.743-33.939-28.828v-1.843c0-15.084,13.993-29.329,34.44-29.329c11.816,0,22.541,4.944,28.324,11.146
127                            L781.464,35.765z"/>
128                        <path d="M299.82,37.417c1.889,1.218,2.418,3.749,1.192,5.648l-9.553,14.797c-1.226,1.901-3.752,2.452-5.637,1.234l0,0
129                            c-1.886-1.22-2.421-3.745-1.192-5.647l9.553-14.797C295.41,36.753,297.935,36.201,299.82,37.417L299.82,37.417z"/>
130                    </g>
131                    </svg>
132                </div>
133                <div class="jetpack-about__content">
134                    <div class="jetpack-about__images">
135                        <ul class="jetpack-about__gravatars">
136                            <?php $this->display_gravatars(); ?>
137                        </ul>
138                        <p class="meet-the-team">
139                            <a href="https://automattic.com/about/" target="_blank" rel="noopener noreferrer" class="jptracks" data-jptracks-name="jetpack_about_meet_the_team"><?php esc_html_e( 'Meet the Automattic team', 'jetpack' ); ?></a>
140                            <svg class="gridicons-external" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M19 13v6c0 1.105-.895 2-2 2H5c-1.105 0-2-.895-2-2V7c0-1.105.895-2 2-2h6v2H5v12h12v-6h2zM13 3v2h4.586l-7.793 7.793 1.414 1.414L19 6.414V11h2V3h-8z"></path></g></svg>
141                        </p>
142                    </div>
143
144                    <div class="jetpack-about__text">
145                        <p>
146                            <?php esc_html_e( 'We are the people behind WordPress.com, WooCommerce, Jetpack, Simplenote, Longreads, VaultPress, Akismet, Gravatar, Crowdsignal, Cloudup, and more. We believe in making the web a better place.', 'jetpack' ); ?>
147                            <a href="https://automattic.com/" target="_blank" rel="noopener noreferrer" class="jptracks" data-jptracks-name="jetpack_about_learn_more">
148                                <?php esc_html_e( 'Learn more about us', 'jetpack' ); ?>
149                            </a>
150                            <svg class="gridicons-external" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M19 13v6c0 1.105-.895 2-2 2H5c-1.105 0-2-.895-2-2V7c0-1.105.895-2 2-2h6v2H5v12h12v-6h2zM13 3v2h4.586l-7.793 7.793 1.414 1.414L19 6.414V11h2V3h-8z"></path></g></svg>
151                        </p>
152                        <p>
153                            <?php
154                            echo esc_html(
155                                sprintf(
156                                    /* translators: first placeholder is the number of Automattic employees. The second is the number of countries of origin*/
157                                    __( 'We’re a distributed company with over %1$s Automatticians in more than %2$s countries speaking at least %3$s different languages. Our common goal is to democratize publishing so that anyone with a story can tell it, regardless of income, gender, politics, language, or where they live in the world.', 'jetpack' ),
158                                    $this->a8c_data['a12s'],
159                                    $this->a8c_data['countries'],
160                                    $this->a8c_data['languages']
161                                )
162                            );
163                            ?>
164                        </p>
165                        <p>
166                            <?php esc_html_e( 'We believe in Open Source and the vast majority of our work is available under the GPL.', 'jetpack' ); ?>
167                        </p>
168                        <p>
169                            <?php
170                                // Maybe use printf() because we'll want to escape the string but still allow for the link, so we can't use esc_html_e().
171                                echo wp_kses(
172                                    __( 'We strive to live by the <a href="https://automattic.com/creed/" target="_blank" class="jptracks" data-jptracks-name="jetpack_about_creed" rel="noopener noreferrer">Automattic Creed</a><svg class="gridicons-external" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M19 13v6c0 1.105-.895 2-2 2H5c-1.105 0-2-.895-2-2V7c0-1.105.895-2 2-2h6v2H5v12h12v-6h2zM13 3v2h4.586l-7.793 7.793 1.414 1.414L19 6.414V11h2V3h-8z"></path></g></svg>', 'jetpack' ),
173                                    array(
174                                        'a'    => array(
175                                            'href'   => array(),
176                                            'class'  => array(),
177                                            'target' => array(),
178                                            'rel'    => array(),
179                                            'data-jptracks-name' => array(),
180                                        ),
181                                        'svg'  => array(
182                                            'class'   => array(),
183                                            'height'  => array(),
184                                            'width'   => array(),
185                                            'xmlns'   => array(),
186                                            'viewbox' => array(),
187                                        ),
188                                        'g'    => array(),
189                                        'path' => array(
190                                            'd' => array(),
191                                        ),
192                                    )
193                                );
194                            ?>
195                        </p>
196                        <p>
197                            <a href="https://automattic.com/work-with-us" target="_blank" rel="noopener noreferrer" class="jptracks" data-jptracks-name="jetpack_about_work_with_us"><?php esc_html_e( 'Come work with us', 'jetpack' ); ?>
198                            </a><svg class="gridicons-external" height="24" width="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><path d="M19 13v6c0 1.105-.895 2-2 2H5c-1.105 0-2-.895-2-2V7c0-1.105.895-2 2-2h6v2H5v12h12v-6h2zM13 3v2h4.586l-7.793 7.793 1.414 1.414L19 6.414V11h2V3h-8z"></path></g></svg>
199                        </p>
200                    </div>
201                </div>
202            </div>
203
204            <div class="jetpack-about__colophon">
205                <h3><?php esc_html_e( 'Popular WordPress services by Automattic', 'jetpack' ); ?></h3>
206                <ul class="jetpack-about__services">
207                <?php $this->display_plugins(); ?>
208                </ul>
209
210                <p class="jetpack-about__services-more">
211                <?php
212                echo wp_kses(
213                    __( 'For even more of our WordPress plugins, please take a look at <a href="https://profiles.wordpress.org/automattic/#content-plugins" target="_blank" rel="noopener noreferrer" class="jptracks" data-jptracks-name="jetpack_about_wporg_profile">our WordPress.org profile</a>.', 'jetpack' ),
214                    array(
215                        'a' => array(
216                            'href'               => array(),
217                            'target'             => array(),
218                            'rel'                => array(),
219                            'class'              => array(),
220                            'data-jptracks-name' => array(),
221                        ),
222                    )
223                );
224                ?>
225                                                        </p>
226            </div>
227        </div>
228        <?php
229    }
230
231    /**
232     * Add information cards for a8c plugins.
233     */
234    public function display_plugins() {
235        $plugins_allowedtags = array(
236            'a'       => array(
237                'href'   => array(),
238                'title'  => array(),
239                'target' => array(),
240            ),
241            'abbr'    => array( 'title' => array() ),
242            'acronym' => array( 'title' => array() ),
243            'code'    => array(),
244            'pre'     => array(),
245            'em'      => array(),
246            'strong'  => array(),
247            'ul'      => array(),
248            'ol'      => array(),
249            'li'      => array(),
250            'p'       => array(),
251            'br'      => array(),
252        );
253
254        // slugs for plugins we want to display.
255        $a8c_plugins = $this->a8c_data['featured_plugins'];
256
257        // need this to access the plugins_api() function.
258        include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
259
260        $plugins = array();
261        foreach ( $a8c_plugins as $slug ) {
262            $args = array(
263                'slug'   => $slug,
264                'fields' => array(
265                    'added'                    => false,
266                    'author'                   => false,
267                    'author_profile'           => false,
268                    'banners'                  => false,
269                    'contributors'             => false,
270                    'donate_link'              => false,
271                    'homepage'                 => false,
272                    'reviews'                  => false,
273                    'screenshots'              => false,
274                    'support_threads'          => false,
275                    'support_threads_resolved' => false,
276                    'sections'                 => false,
277                    'tags'                     => false,
278                    'versions'                 => false,
279
280                    'compatibility'            => true,
281                    'downloaded'               => true,
282                    'downloadlink'             => true,
283                    'icons'                    => true,
284                    'last_updated'             => true,
285                    'num_ratings'              => true,
286                    'rating'                   => true,
287                    'requires'                 => true,
288                    'requires_php'             => true,
289                    'short_description'        => true,
290                    'tested'                   => true,
291                ),
292            );
293
294            // should probably add some error checking here too.
295            $api       = plugins_api( 'plugin_information', $args );
296            $plugins[] = $api;
297        }
298
299        foreach ( $plugins as $plugin ) {
300            if ( is_object( $plugin ) ) {
301                $plugin = (array) $plugin;
302            }
303
304            $title   = wp_kses( $plugin['name'], $plugins_allowedtags );
305            $version = wp_kses( $plugin['version'], $plugins_allowedtags );
306
307            $name = wp_strip_all_tags( $title . ' ' . $version );
308
309            // Remove any HTML from the description.
310            $description = wp_strip_all_tags( $plugin['short_description'] );
311
312            $wp_version = get_bloginfo( 'version' );
313
314            $compatible_php = ( empty( $plugin['requires_php'] ) || version_compare( phpversion(), $plugin['requires_php'], '>=' ) );
315            $compatible_wp  = ( empty( $plugin['requires'] ) || version_compare( $wp_version, $plugin['requires'], '>=' ) );
316
317            $action_links = array();
318
319            // install button.
320            if ( current_user_can( 'install_plugins' ) || current_user_can( 'update_plugins' ) ) {
321                $status = install_plugin_install_status( $plugin );
322                switch ( $status['status'] ) {
323                    case 'install':
324                        if ( $status['url'] ) {
325                            if ( $compatible_php && $compatible_wp ) {
326                                $action_links[] = sprintf(
327                                    '<a class="install-now button jptracks is-rna" data-slug="%1$s" href="%2$s" aria-label="%3$s" data-name="%4$s" data-jptracks-name="jetpack_about_install_button" data-jptracks-prop="%4$s">%5$s</a>',
328                                    esc_attr( $plugin['slug'] ),
329                                    esc_url( $status['url'] ),
330                                    /* translators: %s: plugin name and version */
331                                    esc_attr( sprintf( __( 'Install %s now', 'jetpack' ), $name ) ),
332                                    esc_attr( $name ),
333                                    esc_html__( 'Install Now', 'jetpack' )
334                                );
335                            } else {
336                                $action_links[] = sprintf(
337                                    '<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
338                                    _x( 'Cannot Install', 'plugin', 'jetpack' )
339                                );
340                            }
341                        }
342                        break;
343
344                    case 'update_available':
345                        if ( $status['url'] ) {
346                            $action_links[] = sprintf(
347                                '<a class="update-now button aria-button-if-js jptracks" data-plugin="%1$s" data-slug="%2$s" href="%3$s" aria-label="%4$s" data-name="%5$s" data-jptracks-name="jetpack_about_update_button" data-jptracks-prop="%5$s">%6$s</a>',
348                                esc_attr( $status['file'] ),
349                                esc_attr( $plugin['slug'] ),
350                                esc_url( $status['url'] ),
351                                /* translators: %s: plugin name and version */
352                                esc_attr( sprintf( __( 'Update %s now', 'jetpack' ), $name ) ),
353                                esc_attr( $name ),
354                                __( 'Update Now', 'jetpack' )
355                            );
356                        }
357                        break;
358
359                    case 'latest_installed':
360                    case 'newer_installed':
361                        if ( is_plugin_active( $status['file'] ) ) {
362                            $action_links[] = sprintf(
363                                '<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
364                                _x( 'Active', 'plugin', 'jetpack' )
365                            );
366                        } elseif ( current_user_can( 'activate_plugin', $status['file'] ) ) {
367                            $button_text = __( 'Activate', 'jetpack' );
368                            /* translators: %s: plugin name */
369                            $button_label = _x( 'Activate %s', 'plugin', 'jetpack' );
370                            $activate_url = add_query_arg(
371                                array(
372                                    '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $status['file'] ),
373                                    'action'   => 'activate',
374                                    'plugin'   => $status['file'],
375                                ),
376                                network_admin_url( 'plugins.php' )
377                            );
378
379                            if ( is_network_admin() ) {
380                                $button_text = __( 'Network Activate', 'jetpack' );
381                                /* translators: %s: plugin name */
382                                $button_label = _x( 'Network Activate %s', 'plugin', 'jetpack' );
383                                $activate_url = add_query_arg( array( 'networkwide' => 1 ), $activate_url );
384                            }
385
386                            $action_links[] = sprintf(
387                                '<a href="%1$s" class="button activate-now" aria-label="%2$s" data-jptracks-name="jetpack_about_activate_button" data-jptracks-prop="%3$s">%4$s</a>',
388                                esc_url( $activate_url ),
389                                esc_attr( sprintf( $button_label, $plugin['name'] ) ),
390                                esc_attr( $plugin['name'] ),
391                                $button_text
392                            );
393                        } else {
394                            $action_links[] = sprintf(
395                                '<button type="button" class="button button-disabled" disabled="disabled">%s</button>',
396                                _x( 'Installed', 'plugin', 'jetpack' )
397                            );
398                        }
399                        break;
400                }
401            }
402
403            $plugin_install = "plugin-install.php?tab=plugin-information&amp;plugin={$plugin['slug']}&amp;TB_iframe=true&amp;width=600&amp;height=550";
404            $details_link   = is_multisite()
405                ? network_admin_url( $plugin_install )
406                : admin_url( $plugin_install );
407
408            if ( ! empty( $plugin['icons']['svg'] ) ) {
409                $plugin_icon_url = $plugin['icons']['svg'];
410            } elseif ( ! empty( $plugin['icons']['2x'] ) ) {
411                $plugin_icon_url = $plugin['icons']['2x'];
412            } elseif ( ! empty( $plugin['icons']['1x'] ) ) {
413                $plugin_icon_url = $plugin['icons']['1x'];
414            } else {
415                $plugin_icon_url = $plugin['icons']['default'];
416            }
417            ?>
418
419        <li class="jetpack-about__plugin plugin-card-<?php echo sanitize_html_class( $plugin['slug'] ); ?>">
420            <?php
421            if ( ! $compatible_php || ! $compatible_wp ) {
422                echo '<div class="notice inline notice-error notice-alt"><p>';
423                if ( ! $compatible_php && ! $compatible_wp ) {
424                    esc_html_e( 'This plugin doesn&#8217;t work with your versions of WordPress and PHP.', 'jetpack' );
425                    if ( current_user_can( 'update_core' ) && current_user_can( 'update_php' ) ) {
426                        printf(
427                            /* translators: 1: "Update WordPress" screen URL, 2: "Update PHP" page URL */
428                            ' ' . wp_kses( __( '<a href="%1$s">Please update WordPress</a>, and then <a href="%2$s">learn more about updating PHP</a>.', 'jetpack' ), array( 'a' => array( 'href' => true ) ) ),
429                            esc_url( self_admin_url( 'update-core.php' ) ),
430                            esc_url( wp_get_update_php_url() )
431                        );
432                        wp_update_php_annotation();
433                    } elseif ( current_user_can( 'update_core' ) ) {
434                        printf(
435                            /* translators: %s: "Update WordPress" screen URL */
436                            ' ' . wp_kses( __( '<a href="%s">Please update WordPress</a>.', 'jetpack' ), array( 'a' => array( 'href' => true ) ) ),
437                            esc_url( self_admin_url( 'update-core.php' ) )
438                        );
439                    } elseif ( current_user_can( 'update_php' ) ) {
440                        printf(
441                            /* translators: %s: "Update PHP" page URL */
442                            ' ' . wp_kses( __( '<a href="%s">Learn more about updating PHP</a>.', 'jetpack' ), array( 'a' => array( 'href' => true ) ) ),
443                            esc_url( wp_get_update_php_url() )
444                        );
445                        wp_update_php_annotation();
446                    }
447                } elseif ( ! $compatible_wp ) {
448                    esc_html_e( 'This plugin doesn&#8217;t work with your version of WordPress.', 'jetpack' );
449                    if ( current_user_can( 'update_core' ) ) {
450                        printf(
451                            /* translators: %s: "Update WordPress" screen URL */
452                            ' ' . wp_kses( __( '<a href="%s">Please update WordPress</a>.', 'jetpack' ), array( 'a' => array( 'href' => true ) ) ),
453                            esc_url( self_admin_url( 'update-core.php' ) )
454                        );
455                    }
456                } elseif ( ! $compatible_php ) {
457                    esc_html_e( 'This plugin doesn&#8217;t work with your version of PHP.', 'jetpack' );
458                    if ( current_user_can( 'update_php' ) ) {
459                        printf(
460                            /* translators: %s: "Update PHP" page URL */
461                            ' ' . wp_kses( __( '<a href="%s">Learn more about updating PHP</a>.', 'jetpack' ), array( 'a' => array( 'href' => true ) ) ),
462                            esc_url( wp_get_update_php_url() )
463                        );
464                        wp_update_php_annotation();
465                    }
466                }
467                echo '</p></div>';
468            }
469            ?>
470
471            <div class="plugin-card-top">
472                <div class="name column-name">
473                    <h3>
474                        <a href="<?php echo esc_url( $details_link ); ?>" class="jptracks thickbox open-plugin-details-modal" data-jptracks-name="jetpack_about_plugin_modal" data-jptracks-prop="<?php echo esc_attr( $plugin['slug'] ); ?>">
475                        <?php echo esc_html( $title ); ?>
476                        <img src="<?php echo esc_url( $plugin_icon_url ); ?>" class="plugin-icon" alt="<?php esc_attr_e( 'Plugin icon', 'jetpack' ); ?>" aria-hidden="true">
477                        </a>
478                    </h3>
479                </div>
480                <div class="desc column-description">
481                    <p><?php echo esc_html( $description ); ?></p>
482                </div>
483
484                <div class="details-link">
485                    <a class="jptracks thickbox open-plugin-details-modal" href="<?php echo esc_url( $details_link ); ?>" data-jptracks-name="jetpack_about_plugin_details_modal" data-jptracks-prop="<?php echo esc_attr( $plugin['slug'] ); ?>"><?php esc_html_e( 'More Details', 'jetpack' ); ?></a>
486                </div>
487            </div>
488
489            <div class="plugin-card-bottom">
490                <div class="meta">
491                    <?php
492                    wp_star_rating(
493                        array(
494                            'rating' => $plugin['rating'],
495                            'type'   => 'percent',
496                            'number' => $plugin['num_ratings'],
497                        )
498                    );
499                    ?>
500                    <span class="num-ratings" aria-hidden="true">(<?php echo esc_html( number_format_i18n( $plugin['num_ratings'] ) ); ?> <?php esc_html_e( 'ratings', 'jetpack' ); ?>)</span>
501                    <div class="downloaded">
502                        <?php
503                        if ( $plugin['active_installs'] >= 1000000 ) {
504                            $active_installs_millions = floor( $plugin['active_installs'] / 1000000 );
505                            $active_installs_text     = sprintf(
506                                /* translators: number of millions of installs. */
507                                _nx( '%s+ Million', '%s+ Million', $active_installs_millions, 'Active plugin installations', 'jetpack' ),
508                                number_format_i18n( $active_installs_millions )
509                            );
510                        } elseif ( 0 === $plugin['active_installs'] ) {
511                            $active_installs_text = _x( 'Less Than 10', 'Active plugin installations', 'jetpack' );
512                        } else {
513                            $active_installs_text = number_format_i18n( $plugin['active_installs'] ) . '+';
514                        }
515                        /* translators: number of active installs */
516                        printf( esc_html__( '%s Active Installations', 'jetpack' ), esc_html( $active_installs_text ) );
517                        ?>
518                    </div>
519                </div>
520
521                <div class="action-links">
522                    <?php
523                    if ( $action_links ) {
524                        // The var simply collects strings that have already been sanitized.
525                        // phpcs:ignore WordPress.Security.EscapeOutput
526                        echo '<ul class="action-buttons"><li>' . implode( '</li><li>', $action_links ) . '</li></ul>';
527                    }
528                    ?>
529                </div>
530            </div>
531        </li>
532            <?php
533
534        }
535    }
536
537    /**
538     * Fetch anonymous data about A12s from wpcom: total count, number of countries, languages spoken.
539     *
540     * @since 7.4
541     *
542     * @return array $data
543     */
544    private function fetch_a8c_data() {
545        $data = get_transient( 'jetpack_a8c_data' );
546        if ( false === $data ) {
547            $data = json_decode(
548                wp_remote_retrieve_body(
549                    wp_remote_get( 'https://public-api.wordpress.com/wpcom/v2/jetpack-about' )
550                ),
551                true
552            );
553            if ( ! empty( $data ) && is_array( $data ) ) {
554                set_transient( 'jetpack_a8c_data', $data, WEEK_IN_SECONDS );
555            } else {
556                // Fallback if everything fails.
557                $data = array(
558                    'a12s'             => 888,
559                    'countries'        => 69,
560                    'languages'        => 83,
561                    'featured_plugins' => array(
562                        'woocommerce',
563                        'activitypub',
564                        'wp-job-manager',
565                        'wp-super-cache',
566                    ),
567                );
568            }
569        }
570        return $data;
571    }
572
573    /**
574     * Compile and display a list of avatars for A12s that gave their permission.
575     *
576     * @since 7.3
577     */
578    public function display_gravatars() {
579        $hashes = array(
580            'https://1.gravatar.com/avatar/d2ab03dbab0c97740be75f290a2e3190',
581            'https://2.gravatar.com/avatar/b0b357b291ac72bc7da81b4d74430fe6',
582            'https://2.gravatar.com/avatar/9e149207a0e0818abed0edbb1fb2d0bf',
583            'https://2.gravatar.com/avatar/9f376366854d750124dffe057dda99c9',
584            'https://1.gravatar.com/avatar/1c75d26ad0d38624f02b15accc1f20cd',
585            'https://1.gravatar.com/avatar/c510e69d83c7d10be4df64feeff4e46a',
586            'https://0.gravatar.com/avatar/88ec0dcadea38adf5f30a17e54e9b248',
587            'https://1.gravatar.com/avatar/1ec3571e0201a990ceca5e365e780efa',
588            'https://0.gravatar.com/avatar/0619d4de8aef78c81b2194ff1d164d85',
589            'https://0.gravatar.com/avatar/7fdcad31a04def0ab9583af475c9036c',
590            'https://0.gravatar.com/avatar/b3618d70c63bbc5cc7caee0beded5ff0',
591            'https://1.gravatar.com/avatar/4d346581a3340e32cf93703c9ce46bd4',
592            'https://2.gravatar.com/avatar/9c2f6b95a00dfccfadc6a912a2b859ba',
593            'https://1.gravatar.com/avatar/1a33e7a69df4f675fcd799edca088ac2',
594            'https://2.gravatar.com/avatar/d5dc443845c134f365519568d5d80e62',
595            'https://0.gravatar.com/avatar/c0ccdd53794779bcc07fcae7b79c4d80',
596        );
597        $output = '';
598        foreach ( $hashes as $hash ) {
599            $output .= '<li><img src="' . esc_url( $hash ) . '?s=150"></li>' . "\n";
600        }
601        echo wp_kses(
602            $output,
603            array(
604                'li'  => true,
605                'img' => array(
606                    'src' => true,
607                ),
608            )
609        );
610    }
611}