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