PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.1
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 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 All 503 releases
jetpack / _inc / lib / class-jetpack-recommendations.php

class-jetpack-recommendations.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.1, at _inc/lib/class-jetpack-recommendations.php

451 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Utilities related to the Jetpack Recommendations
4 *
5 * @package automattic/jetpack
6 */
7
8 use Automattic\Jetpack\Current_Plan as Jetpack_Plan;
9 use Automattic\Jetpack\Plugins_Installer;
10 use Automattic\Jetpack\Status;
11 use Automattic\Jetpack\Status\Host;
12
13 /**
14 * Contains utilities related to the Jetpack Recommendations.
15 *
16 * @package automattic/jetpack
17 */
18
19 /**
20 * Jetpack_Recommendations class
21 */
22 class Jetpack_Recommendations {
23 const PUBLICIZE_RECOMMENDATION = 'publicize';
24 const PROTECT_RECOMMENDATION = 'protect';
25 const ANTI_SPAM_RECOMMENDATION = 'anti-spam';
26 const VIDEOPRESS_RECOMMENDATION = 'videopress';
27 const BACKUP_PLAN_RECOMMENDATION = 'backup-plan';
28 const BOOST_RECOMMENDATION = 'boost';
29
30 const CONDITIONAL_RECOMMENDATIONS_OPTION = 'recommendations_conditional';
31 const CONDITIONAL_RECOMMENDATIONS = array(
32 self::PUBLICIZE_RECOMMENDATION,
33 self::PROTECT_RECOMMENDATION,
34 self::ANTI_SPAM_RECOMMENDATION,
35 self::VIDEOPRESS_RECOMMENDATION,
36 self::BACKUP_PLAN_RECOMMENDATION,
37 self::BOOST_RECOMMENDATION,
38 );
39
40 const VIDEOPRESS_TIMED_ACTION = 'jetpack_recommend_videopress';
41
42 /**
43 * Returns a boolean indicating if the Jetpack Recommendations are enabled.
44 *
45 * @since 9.3.0
46 *
47 * @return bool
48 */
49 public static function is_enabled() {
50 // Shortcircuit early if Jetpack is not active or we are in offline mode.
51 if ( ! Jetpack::is_connection_ready() || ( new Status() )->is_offline_mode() ) {
52 return false;
53 }
54
55 // No recommendations for Atomic sites, they already get onboarded in Calypso.
56 if ( ( new Host() )->is_woa_site() ) {
57 return false;
58 }
59
60 self::initialize_jetpack_recommendations();
61
62 return true;
63 }
64
65 /**
66 * Returns a boolean indicating if the Jetpack Banner is enabled.
67 *
68 * @since 9.3.0
69 *
70 * @deprecated 13.2
71 *
72 * @return bool
73 */
74 public static function is_banner_enabled() {
75 _deprecated_function( __METHOD__, 'jetpack-13.2' );
76 return false;
77 }
78
79 /**
80 * Set up actions to monitor for things that trigger a recommendation.
81 *
82 * @return false|void
83 */
84 public static function init_conditional_recommendation_actions() {
85 // Check to make sure that recommendations are enabled.
86 if ( ! self::is_enabled() ) {
87 return false;
88 }
89
90 // Monitor for the publishing of a new post.
91 add_action( 'transition_post_status', array( static::class, 'post_transition' ), 10, 3 );
92 add_action( 'jetpack_activate_module', array( static::class, 'jetpack_module_activated' ), 10, 2 );
93
94 // Monitor for activating a new plugin.
95 add_action( 'activated_plugin', array( static::class, 'plugin_activated' ), 10 );
96
97 // Monitor for the addition of a new comment.
98 add_action( 'comment_post', array( static::class, 'comment_added' ), 10, 3 );
99
100 // Monitor for Jetpack connection success.
101 add_action( 'jetpack_authorize_ending_authorized', array( static::class, 'jetpack_connected' ) );
102 add_action( self::VIDEOPRESS_TIMED_ACTION, array( static::class, 'recommend_videopress' ) );
103
104 // Monitor for changes in plugins that have auto-updates enabled
105 add_action( 'update_site_option_auto_update_plugins', array( static::class, 'plugin_auto_update_settings_changed' ), 10, 3 );
106 }
107
108 /**
109 * Check when Jetpack modules are activated if some recommendations should be skipped.
110 *
111 * @param string $module Name of the module activated.
112 * @param bool $success Whether the module activation was successful.
113 */
114 public static function jetpack_module_activated( $module, $success ) {
115 if ( 'publicize' === $module && $success ) {
116 self::disable_conditional_recommendation( self::PUBLICIZE_RECOMMENDATION );
117 } elseif ( 'videopress' === $module && $success ) {
118 // If VideoPress is enabled and a recommendation for it is scheduled, cancel that recommendation.
119 $recommendation_timestamp = wp_next_scheduled( self::VIDEOPRESS_TIMED_ACTION );
120 if ( false !== $recommendation_timestamp ) {
121 wp_unschedule_event( $recommendation_timestamp, self::VIDEOPRESS_TIMED_ACTION );
122 }
123 }
124 }
125
126 /**
127 * Hook for transition_post_status that checks for the publishing of a new post or page.
128 * Used to enable the publicize and boost recommendations.
129 *
130 * @param string $new_status new status of post.
131 * @param string $old_status old status of post.
132 * @param WP_Post $post the post object being updated.
133 */
134 public static function post_transition( $new_status, $old_status, $post ) {
135 // Check for condition when post has been published.
136 if ( 'post' === $post->post_type && 'publish' === $new_status && 'publish' !== $old_status && ! Jetpack::is_module_active( 'publicize' ) ) {
137 // Set the publicize recommendation to have met criteria to be shown.
138 self::enable_conditional_recommendation( self::PUBLICIZE_RECOMMENDATION );
139 return;
140 }
141 // A new page has been published
142 // Check to see if the boost plugin is active
143 if (
144 'page' === $post->post_type &&
145 'publish' === $new_status &&
146 'publish' !== $old_status &&
147 ! Plugins_Installer::is_plugin_active( 'boost/jetpack-boost.php' ) &&
148 ! Plugins_Installer::is_plugin_active( 'jetpack-boost/jetpack-boost.php' )
149 ) {
150 self::enable_conditional_recommendation( self::BOOST_RECOMMENDATION );
151 }
152 }
153
154 /**
155 * Runs when a plugin gets activated
156 *
157 * @param string $plugin Path to the plugins file relative to the plugins directory.
158 */
159 public static function plugin_activated( $plugin ) {
160 // If the plugin is in this list, don't enable the recommendation.
161 $plugin_whitelist = array(
162 'jetpack.php',
163 'akismet.php',
164 'creative-mail.php',
165 'jetpack-backup.php',
166 'jetpack-boost.php',
167 'jetpack-protect.php',
168 'crowdsignal.php',
169 'vaultpress.php',
170 'woocommerce.php',
171 );
172
173 $path_parts = explode( '/', $plugin );
174 $plugin_file = array_pop( $path_parts );
175
176 if ( ! in_array( $plugin_file, $plugin_whitelist, true ) ) {
177 $products = array_column( Jetpack_Plan::get_products(), 'product_slug' );
178
179 // Check for a plan or product that enables scan.
180 $plan_supports_scan = Jetpack_Plan::supports( 'scan' );
181 $has_scan_product = count( array_intersect( array( 'jetpack_scan', 'jetpack_scan_monthly' ), $products ) ) > 0;
182 $has_scan = $plan_supports_scan || $has_scan_product;
183
184 // Check if Jetpack Protect plugin is already active.
185 $has_protect = Plugins_Installer::is_plugin_active( 'jetpack-protect/jetpack-protect.php' ) || Plugins_Installer::is_plugin_active( 'protect/jetpack-protect.php' );
186
187 if ( ! $has_scan && ! $has_protect ) {
188 self::enable_conditional_recommendation( self::PROTECT_RECOMMENDATION );
189 }
190 }
191 }
192
193 /**
194 * Runs when the auto_update_plugins option has been changed
195 *
196 * @param string $option_name - the name of the option updated ( always auto_update_plugins ).
197 * @param array $new_auto_update_plugins - plugins that have auto update enabled following the change.
198 * @param array $old_auto_update_plugins - plugins that had auto update enabled before the most recent change.
199 * @return void
200 */
201 public static function plugin_auto_update_settings_changed( $option_name, $new_auto_update_plugins, $old_auto_update_plugins ) {
202 if (
203 is_multisite() ||
204 self::is_conditional_recommendation_enabled( self::BACKUP_PLAN_RECOMMENDATION )
205 ) {
206 return;
207 }
208
209 // Look for plugins that have had auto-update enabled in this most recent update.
210 $enabled_auto_updates = array_diff( $new_auto_update_plugins, $old_auto_update_plugins );
211 if ( ! empty( $enabled_auto_updates ) ) {
212 // Check the backup state.
213 $rewind_state = get_transient( 'jetpack_rewind_state' );
214 $has_backup = $rewind_state && in_array( $rewind_state->state, array( 'awaiting_credentials', 'provisioning', 'active' ), true );
215
216 if ( ! $has_backup ) {
217 self::enable_conditional_recommendation( self::BACKUP_PLAN_RECOMMENDATION );
218 }
219 }
220 }
221
222 /**
223 * Runs when a new comment is added.
224 *
225 * @param integer $comment_id The ID of the comment that was added.
226 * @param bool $comment_approved Whether or not the comment is approved.
227 * @param array $commentdata Comment data.
228 */
229 public static function comment_added( $comment_id, $comment_approved, $commentdata ) {
230 if ( self::is_conditional_recommendation_enabled( self::ANTI_SPAM_RECOMMENDATION ) ) {
231 return;
232 }
233
234 if ( Plugins_Installer::is_plugin_active( 'akismet/akismet.php' ) ) {
235 return;
236 }
237
238 // The site has anti-spam features already.
239 $site_products = array_column( Jetpack_Plan::get_products(), 'product_slug' );
240 $has_anti_spam_product = count( array_intersect( array( 'jetpack_anti_spam', 'jetpack_anti_spam_monthly' ), $site_products ) ) > 0;
241
242 if ( Jetpack_Plan::supports( 'akismet' ) || Jetpack_Plan::supports( 'antispam' ) || $has_anti_spam_product ) {
243 return;
244 }
245
246 if ( isset( $commentdata['comment_post_ID'] ) ) {
247 $post_id = $commentdata['comment_post_ID'];
248 } else {
249 $comment = get_comment( $comment_id );
250 $post_id = $comment->comment_post_ID;
251 }
252 $comment_count = get_comments_number( $post_id );
253
254 if ( intval( $comment_count ) >= 5 ) {
255 self::enable_conditional_recommendation( self::ANTI_SPAM_RECOMMENDATION );
256 }
257 }
258
259 /**
260 * Runs after a successful connection is made.
261 */
262 public static function jetpack_connected() {
263 // Schedule a recommendation for VideoPress in 2 weeks.
264 if ( false === wp_next_scheduled( self::VIDEOPRESS_TIMED_ACTION ) ) {
265 $date = new DateTime();
266 $date->add( new DateInterval( 'P14D' ) );
267 wp_schedule_single_event( $date->getTimestamp(), self::VIDEOPRESS_TIMED_ACTION );
268 }
269 }
270
271 /**
272 * Enable a recommendation for VideoPress.
273 */
274 public static function recommend_videopress() {
275 // Check to see if the VideoPress recommendation is already enabled.
276 if ( self::is_conditional_recommendation_enabled( self::VIDEOPRESS_RECOMMENDATION ) ) {
277 return;
278 }
279
280 $site_plan = Jetpack_Plan::get();
281 $site_products = array_column( Jetpack_Plan::get_products(), 'product_slug' );
282
283 if ( self::should_recommend_videopress( $site_plan, $site_products ) ) {
284 self::enable_conditional_recommendation( self::VIDEOPRESS_RECOMMENDATION );
285 }
286 }
287
288 /**
289 * Should we provide a recommendation for videopress?
290 * This method exists to facilitate unit testing
291 *
292 * @param array $site_plan A representation of the site's plan.
293 * @param array $site_products An array of product slugs.
294 * @return boolean
295 */
296 public static function should_recommend_videopress( $site_plan, $site_products ) {
297 // Does the site have the VideoPress module enabled?
298 if ( Jetpack::is_module_active( 'videopress' ) ) {
299 return false;
300 }
301
302 // Does the site plan have upgraded videopress features?
303 // For now, this just checks to see if the site has a free plan.
304 // Jetpack_Plan::supports('videopress') returns true for all plans, since there is a free tier.
305 $is_free_plan = 'free' === $site_plan['class'];
306 if ( ! $is_free_plan ) {
307 return false;
308 }
309
310 // Does this site already have a VideoPress product?
311 $has_videopress_product = count( array_intersect( array( 'jetpack_videopress', 'jetpack_videopress_monthly' ), $site_products ) ) > 0;
312 if ( $has_videopress_product ) {
313 return false;
314 }
315
316 return true;
317 }
318
319 /**
320 * Enable a recommendation.
321 *
322 * @param string $recommendation_name The name of the recommendation to enable.
323 * @return false|void
324 */
325 public static function enable_conditional_recommendation( $recommendation_name ) {
326 if ( ! in_array( $recommendation_name, self::CONDITIONAL_RECOMMENDATIONS, true ) ) {
327 return false;
328 }
329
330 $conditional_recommendations = Jetpack_Options::get_option( self::CONDITIONAL_RECOMMENDATIONS_OPTION, array() );
331 if ( ! in_array( $recommendation_name, $conditional_recommendations, true ) ) {
332 $conditional_recommendations[] = $recommendation_name;
333 Jetpack_Options::update_option( self::CONDITIONAL_RECOMMENDATIONS_OPTION, $conditional_recommendations );
334 }
335 }
336
337 /**
338 * Disable a recommendation.
339 *
340 * @param string $recommendation_name The name of the recommendation to disable.
341 * @return false|void
342 */
343 public static function disable_conditional_recommendation( $recommendation_name ) {
344 if ( ! in_array( $recommendation_name, self::CONDITIONAL_RECOMMENDATIONS, true ) ) {
345 return false;
346 }
347
348 $conditional_recommendations = Jetpack_Options::get_option( self::CONDITIONAL_RECOMMENDATIONS_OPTION, array() );
349 $recommendation_index = array_search( $recommendation_name, $conditional_recommendations, true );
350
351 if ( false !== $recommendation_index ) {
352 array_splice( $conditional_recommendations, $recommendation_index, 1 );
353 Jetpack_Options::update_option( self::CONDITIONAL_RECOMMENDATIONS_OPTION, $conditional_recommendations );
354 }
355 }
356
357 /**
358 * Check to see if a recommendation is enabled or not.
359 *
360 * @param string $recommendation_name The name of the recommendation to check for.
361 * @return bool
362 */
363 public static function is_conditional_recommendation_enabled( $recommendation_name ) {
364 $conditional_recommendations = Jetpack_Options::get_option( self::CONDITIONAL_RECOMMENDATIONS_OPTION, array() );
365 return in_array( $recommendation_name, $conditional_recommendations, true );
366 }
367
368 /**
369 * Gets data for all conditional recommendations.
370 *
371 * @return mixed
372 */
373 public static function get_conditional_recommendations() {
374 return Jetpack_Options::get_option( self::CONDITIONAL_RECOMMENDATIONS_OPTION, array() );
375 }
376
377 /**
378 * Get an array of new conditional recommendations that have not been viewed.
379 *
380 * @return array
381 */
382 public static function get_new_conditional_recommendations() {
383 $conditional_recommendations = self::get_conditional_recommendations();
384 $recommendations_data = Jetpack_Options::get_option( 'recommendations_data', array() );
385 $viewed_recommendations = $recommendations_data['viewedRecommendations'] ?? array();
386
387 // array_diff returns a keyed array - reduce to unique values.
388 return array_unique( array_values( array_diff( $conditional_recommendations, $viewed_recommendations ) ) );
389 }
390
391 /**
392 * Initializes the Recommendations step according to the Setup Wizard state.
393 */
394 private static function initialize_jetpack_recommendations() {
395 if ( Jetpack_Options::get_option( 'recommendations_step' ) ) {
396 return;
397 }
398
399 $setup_wizard_status = Jetpack_Options::get_option( 'setup_wizard_status' );
400 if ( 'completed' === $setup_wizard_status ) {
401 Jetpack_Options::update_option( 'recommendations_step', 'setup-wizard-completed' );
402 }
403 }
404
405 /**
406 * Get the data for the recommendations
407 *
408 * @return array Recommendations data
409 */
410 public static function get_recommendations_data() {
411 self::initialize_jetpack_recommendations();
412
413 return Jetpack_Options::get_option( 'recommendations_data', array() );
414 }
415
416 /**
417 * Update the data for the recommendations
418 *
419 * @param WP_REST_Request $data The data.
420 */
421 public static function update_recommendations_data( $data ) {
422 if ( ! empty( $data ) ) {
423 Jetpack_Options::update_option( 'recommendations_data', $data );
424 }
425 }
426
427 /**
428 * Get the data for the recommendations
429 *
430 * @return array Recommendations data
431 */
432 public static function get_recommendations_step() {
433 self::initialize_jetpack_recommendations();
434
435 return array(
436 'step' => Jetpack_Options::get_option( 'recommendations_step', 'not-started' ),
437 );
438 }
439
440 /**
441 * Update the step for the recommendations
442 *
443 * @param WP_REST_Request $step The step.
444 */
445 public static function update_recommendations_step( $step ) {
446 if ( ! empty( $step ) ) {
447 Jetpack_Options::update_option( 'recommendations_step', $step );
448 }
449 }
450 }
451