Completed
Push — renovate/webpack-cli-4.x ( d6eb3b...d1283c )
by
unknown
172:37 queued 163:05
created

Jetpack_Beta::should_update_dev_version()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Primary class file for the Jetpack Beta plugin.
4
 *
5
 * @package automattic/jetpack-beta
6
 */
7
8
// Check that the file is not accessed directly.
9
if ( ! defined( 'ABSPATH' ) ) {
10
	exit;
11
}
12
13
/**
14
 * Class Jetpack_Beta
15
 */
16
class Jetpack_Beta {
17
18
	/**
19
	 * Singleton Jetpack_Beta class instance.
20
	 *
21
	 * @var Jetpack_Beta
22
	 */
23
	protected static $instance = null;
24
25
	/**
26
	 * WP Options string: jetpack_beta_active
27
	 *
28
	 * @var string
29
	 */
30
	protected static $option = 'jetpack_beta_active';
31
32
	/**
33
	 * WP Options string: jetpack_beta_dev_currently_installed
34
	 *
35
	 * @var string
36
	 */
37
	protected static $option_dev_installed = 'jetpack_beta_dev_currently_installed';
38
39
	/**
40
	 * WP Options string: jp_beta_autoupdate
41
	 *
42
	 * @var string
43
	 */
44
	protected static $option_autoupdate = 'jp_beta_autoupdate';
45
46
	/**
47
	 * WP Options string: jp_beta_email_notifications
48
	 *
49
	 * @var string
50
	 */
51
	protected static $option_email_notif = 'jp_beta_email_notifications';
52
53
	/**
54
	 * WP-Cron string: jetpack_beta_autoupdate_hourly_cron
55
	 *
56
	 * @var string
57
	 */
58
	protected static $auto_update_cron_hook = 'jetpack_beta_autoupdate_hourly_cron';
59
60
	/**
61
	 * Main Instance
62
	 */
63
	public static function instance() {
64
		if ( null === self::$instance ) {
65
			self::$instance = new self();
66
		}
67
68
		return self::$instance;
69
	}
70
71
	/**
72
	 * Constructor
73
	 */
74
	public function __construct() {
75
		add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'maybe_plugins_update_transient' ) );
76
		add_filter( 'upgrader_post_install', array( $this, 'upgrader_post_install' ), 10, 3 );
77
78
		add_action( 'admin_bar_menu', array( $this, 'admin_bar_menu' ) );
79
		add_action( 'deactivate_plugin', array( $this, 'plugin_deactivated' ), 10, 2 );
80
81
		add_action( 'upgrader_process_complete', array( $this, 'upgrader_process_complete' ), 10, 2 );
82
83
		add_filter( 'plugin_action_links_' . JETPACK_PLUGIN_FILE, array( $this, 'remove_activate_stable' ) );
84
		add_filter( 'plugin_action_links_' . JETPACK_DEV_PLUGIN_FILE, array( $this, 'remove_activate_dev' ) );
85
86
		add_filter( 'network_admin_plugin_action_links_' . JETPACK_PLUGIN_FILE, array( $this, 'remove_activate_stable' ) );
87
		add_filter( 'network_admin_plugin_action_links_' . JETPACK_DEV_PLUGIN_FILE, array( $this, 'remove_activate_dev' ) );
88
89
		add_filter( 'all_plugins', array( $this, 'update_all_plugins' ) );
90
91
		add_filter( 'plugins_api', array( $this, 'get_plugin_info' ), 10, 3 );
92
93
		add_action( 'jetpack_beta_autoupdate_hourly_cron', array( 'Jetpack_Beta', 'run_autoupdate' ) );
94
95
		add_filter( 'jetpack_options_whitelist', array( $this, 'add_to_options_whitelist' ) );
96
97
		if ( is_admin() ) {
98
			require JPBETA__PLUGIN_DIR . 'class-jetpack-beta-admin.php';
99
			self::maybe_schedule_autoupdate();
100
			Jetpack_Beta_Admin::init();
101
		}
102
103
		$current_version = $this->get_branch_and_section();
104
		self::update_autoload_dev_constant( $current_version[1] );
105
	}
106
107
	/**
108
	 * Fired when the upgrader process is complete; sets option jetpack_beta_dev_currently_installed
109
	 *
110
	 * @param WP_Upgrader $upgrader          - An upgrader instance.
111
	 * @param array       $updates_completed - Array of bulk item update data.
112
	 */
113
	public function upgrader_process_complete( $upgrader, $updates_completed ) {
114
		if ( ! isset( $updates_completed['plugins'] ) ) {
115
			return;
116
		}
117
118
		if ( 'update' === $updates_completed['action'] &&
119
			'plugin' === $updates_completed['type'] &&
120
		in_array( JETPACK_DEV_PLUGIN_FILE, $updates_completed['plugins'], true ) ) {
121
			list( $branch, $section ) = self::get_branch_and_section_dev();
122
			if ( self::should_update_dev_to_master() ) {
123
				list( $branch, $section ) = array( 'master', 'master' );
124
			}
125
			update_option( self::$option_dev_installed, array( $branch, $section, self::get_manifest_data( $branch, $section ) ) );
126
		}
127
	}
128
129
	/**
130
	 * If Jetpack or JP Dev plugin is network activated, update active_plugins option.
131
	 */
132
	public static function is_network_enabled() {
133
		if ( self::is_network_active() ) {
134
			add_filter( 'option_active_plugins', array( 'Jetpack_Beta', 'override_active_plugins' ) );
135
		}
136
	}
137
138
	/**
139
	 * This filter is only applied if Jetpack is network activated,
140
	 * makes sure that you can't have Jetpack or Jetpack Dev plugins versions loaded.
141
	 *
142
	 * @param array $active_plugins - Currently activated plugins.
143
	 *
144
	 * @return array Updated array of active plugins.
145
	 */
146
	public static function override_active_plugins( $active_plugins ) {
147
		$new_active_plugins = array();
148
		foreach ( $active_plugins as $active_plugin ) {
149
			if ( ! self::is_jetpack_plugin( $active_plugin ) ) {
150
				$new_active_plugins[] = $active_plugin;
151
			}
152
		}
153
		return $new_active_plugins;
154
	}
155
156
	/**
157
	 * Actions taken when the Jetpack Beta plugin is deactivated.
158
	 *
159
	 * @param string $plugin       - Plugin path being deactivated.
160
	 */
161
	public function plugin_deactivated( $plugin ) {
162
		if ( ! self::is_jetpack_plugin( $plugin ) ) {
163
			return;
164
		}
165
166
		delete_option( self::$option );
167
	}
168
169
	/**
170
	 * Checks if passed plugin matches JP or JP Dev paths.
171
	 *
172
	 * @param string $plugin - A plugin path.
173
	 */
174
	public static function is_jetpack_plugin( $plugin ) {
175
		return in_array( $plugin, array( JETPACK_PLUGIN_FILE, JETPACK_DEV_PLUGIN_FILE ), true );
176
	}
177
178
	/**
179
	 * Filter JP Dev plugin action links.
180
	 *
181
	 * @param array $actions - Array of plugin action links.
182
	 */
183
	public function remove_activate_dev( $actions ) {
184 View Code Duplication
		if ( is_plugin_active( JETPACK_PLUGIN_FILE ) || self::is_network_active() ) {
185
			$actions['activate'] = __( 'Plugin Already Active', 'jetpack-beta' );
186
		}
187
		return $actions;
188
	}
189
190
	/**
191
	 * Filter JP Stable plugin action links.
192
	 *
193
	 * @param array $actions - Array of plugin action links.
194
	 */
195
	public function remove_activate_stable( $actions ) {
196 View Code Duplication
		if ( is_plugin_active( JETPACK_DEV_PLUGIN_FILE ) || self::is_network_active() ) {
197
			$actions['activate'] = __( 'Plugin Already Active', 'jetpack-beta' );
198
		}
199
		return $actions;
200
	}
201
202
	/**
203
	 * Filters plugins to list in the Plugins list table.
204
	 *
205
	 * @param array $plugins - Array of arrays of plugin data.
206
	 *
207
	 * @return array Updated array of plugin data.
208
	 */
209
	public function update_all_plugins( $plugins ) {
210
		// WP.com requests away show regular plugin.
211
		if ( defined( 'REST_API_REQUEST' ) && REST_API_REQUEST ) {
212
			// Ensure that Jetpack reports the version it's using on account of the Jetpack Beta plugin to Calypso.
213
			if ( is_plugin_active( JETPACK_DEV_PLUGIN_FILE ) ) {
214
				$plugins[ JETPACK_PLUGIN_FILE ]['Version'] = $plugins[ JETPACK_DEV_PLUGIN_FILE ]['Version'];
215
			}
216
			unset( $plugins[ JETPACK_DEV_PLUGIN_FILE ] );
217
			return $plugins;
218
		}
219
220
		if ( is_plugin_active( JETPACK_DEV_PLUGIN_FILE ) ) {
221
			unset( $plugins[ JETPACK_PLUGIN_FILE ] );
222
		} else {
223
			unset( $plugins[ JETPACK_DEV_PLUGIN_FILE ] );
224
		}
225
		return $plugins;
226
	}
227
228
	/**
229
	 * Filter WordPress.org Plugins API results.
230
	 *
231
	 * @param false|object|array $false    - The result object or array. Default false.
232
	 * @param string             $action   - The type of information being requested from the Plugin Installation API.
233
	 * @param object             $response - Plugin API arguments.
234
	 */
235
	public function get_plugin_info( $false, $action, $response ) {
236
237
		// Check if this call API is for the right plugin.
238
		if ( ! isset( $response->slug ) || JETPACK_DEV_PLUGIN_SLUG !== $response->slug ) {
239
			return false;
240
		}
241
		$update_date  = null;
242
		$download_zip = null;
243
		$dev_data     = self::get_dev_installed();
244
		if ( isset( $dev_data[2] ) ) {
245
			$update_date  = $dev_data[2]->update_date;
246
			$download_zip = $dev_data[2]->download_url;
247
		}
248
		// Update tags.
249
		$response->slug          = JETPACK_DEV_PLUGIN_SLUG;
250
		$response->plugin        = JETPACK_DEV_PLUGIN_SLUG;
251
		$response->name          = 'Jetpack | ' . self::get_jetpack_plugin_pretty_version( true );
252
		$response->plugin_name   = 'Jetpack | ' . self::get_jetpack_plugin_pretty_version( true );
253
		$response->version       = self::get_jetpack_plugin_version( true );
254
		$response->author        = 'Automattic';
255
		$response->homepage      = 'https://jetpack.com/contact-support/beta-group/';
256
		$response->downloaded    = false;
257
		$response->last_updated  = $update_date;
258
		$response->sections      = array( 'description' => Jetpack_Beta_Admin::to_test_content() );
259
		$response->download_link = $download_zip;
260
		return $response;
261
	}
262
263
	/**
264
	 * Run on activation to flush update cache.
265
	 */
266
	public static function activate() {
267
		// Don't do anyting funnly.
268
		if ( defined( 'DOING_CRON' ) ) {
269
			return;
270
		}
271
		delete_site_transient( 'update_plugins' );
272
	}
273
274
	/**
275
	 * Returns active Jetpack plugin file partial path string (jetpack/jetpack.php|jetpack-dev/jetpack.php).
276
	 */
277
	public static function get_plugin_file() {
278
		return self::get_plugin_slug() . '/jetpack.php';
279
	}
280
281
	/**
282
	 * Returns active plugin slug string (jetpack|jetpack-dev).
283
	 */
284 View Code Duplication
	public static function get_plugin_slug() {
285
		$installed = self::get_branch_and_section();
286
		if ( empty( $installed ) || 'stable' === $installed[1] || 'tags' === $installed[1] ) {
287
			return 'jetpack';
288
		}
289
		return JETPACK_DEV_PLUGIN_SLUG;
290
	}
291
292
	/**
293
	 * Handler ran for Jetpack Beta plugin deactivation hook.
294
	 */
295
	public static function deactivate() {
296
		// Don't do anyting funnly.
297
		if ( defined( 'DOING_CRON' ) ) {
298
			return;
299
		}
300
301
		self::clear_autoupdate_cron();
302
		self::delete_all_transiants();
303
		add_action( 'shutdown', array( __CLASS__, 'switch_active' ), 5 );
304
		add_action( 'shutdown', array( __CLASS__, 'remove_dev_plugin' ), 20 );
305
		delete_option( self::$option );
306
		delete_option( 'jetpack_autoload_dev' );
307
	}
308
309
	/**
310
	 * When Jetpack Beta plugin is deactivated, remove the jetpack-dev plugin directory and cleanup.
311
	 */
312
	public static function remove_dev_plugin() {
313
		if ( is_multisite() ) {
314
			return;
315
		}
316
317
		// Delete the jetpack dev plugin.
318
		require_once ABSPATH . 'wp-admin/includes/file.php';
319
		$creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, array() );
320
		if ( ! WP_Filesystem( $creds ) ) {
321
			// Any problems and we exit.
322
			return;
323
		}
324
		global $wp_filesystem;
325
		if ( ! $wp_filesystem ) {
326
			return;
327
		}
328
329
		$working_dir = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . JETPACK_DEV_PLUGIN_SLUG;
330
		// Delete the folder JETPACK_BETA_PLUGIN_FOLDER.
331
		if ( $wp_filesystem->is_dir( $working_dir ) ) {
332
			$wp_filesystem->delete( $working_dir, true );
333
		}
334
		// Since we are removing this dev plugin we should also clean up this data.
335
		delete_option( self::$option_dev_installed );
336
	}
337
338
	/**
339
	 * Builds URL to the admin area for the current site and specified query param.
340
	 *
341
	 * @param string $query - Path relative to the admin URL.
342
	 */
343
	public static function admin_url( $query = '?page=jetpack-beta' ) {
344
		return ( self::is_network_active() )
345
		? network_admin_url( 'admin.php' . $query )
346
		: admin_url( 'admin.php' . $query );
347
	}
348
349
	/**
350
	 * Build the "Jetpack Beta" admin bar menu items.
351
	 */
352
	public function admin_bar_menu() {
353
		global $wp_admin_bar;
354
355
		if ( ! is_object( $wp_admin_bar ) ) {
356
			return;
357
		}
358
359
		// Nothing got activated yet.
360
		if ( ! self::get_option() ) {
361
			return;
362
		}
363
364
		$args = array(
365
			'id'     => 'jetpack-beta_admin_bar',
366
			'title'  => 'Jetpack Beta',
367
			'parent' => 'top-secondary',
368
			'href'   => current_user_can( 'update_plugins' ) ? self::admin_url() : '',
369
		);
370
		$wp_admin_bar->add_node( $args );
371
372
		// Add a child item to our parent item.
373
		$args = array(
374
			'id'     => 'jetpack-beta_version',
375
			// translators: %s: active Jetpack plugin branch/tag.
376
			'title'  => sprintf( __( 'Running %s', 'jetpack-beta' ), self::get_jetpack_plugin_pretty_version() ),
377
			'parent' => 'jetpack-beta_admin_bar',
378
		);
379
380
		$wp_admin_bar->add_node( $args );
381
382
		if ( self::get_plugin_slug() === JETPACK_DEV_PLUGIN_SLUG ) {
383
			// Highlight the menu if you are running the BETA Versions..
384
			echo sprintf( '<style>#wpadminbar #wp-admin-bar-jetpack-beta_admin_bar { background: %s; }</style>', esc_attr( JETPACK_GREEN ) );
385
		}
386
387
		$args = array(
388
			'id'     => 'jetpack-beta_report',
389
			'title'  => __( 'Report Bug', 'jetpack-beta' ),
390
			'href'   => JETPACK_BETA_REPORT_URL,
391
			'parent' => 'jetpack-beta_admin_bar',
392
		);
393
		$wp_admin_bar->add_node( $args );
394
395
		list( $branch, $section ) = self::get_branch_and_section();
396
		if ( 'pr' === $section ) {
397
			$args = array(
398
				'id'     => 'jetpack-beta_report_more_info',
399
				'title'  => __( 'More Info ', 'jetpack-beta' ),
400
				'href'   => self::get_url( $branch, $section ),
401
				'parent' => 'jetpack-beta_admin_bar',
402
			);
403
			$wp_admin_bar->add_node( $args );
404
		}
405
	}
406
407
	/**
408
	 * Filters `update_plugins` transient.
409
	 *
410
	 * @param object $transient - Plugin update data.
411
	 */
412
	public function maybe_plugins_update_transient( $transient ) {
413
		if ( ! isset( $transient->no_update ) ) {
414
			return $transient;
415
		}
416
417
		// Do not try to update things that do not exist.
418
		if ( ! file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . JETPACK_DEV_PLUGIN_FILE ) ) {
419
			return $transient;
420
		}
421
422
		// Do not look for update if we are stable branch.
423
		if ( self::is_on_stable() ) {
424
			return $transient;
425
		}
426
427
		// Lets always grab the latest.
428
		delete_site_transient( 'jetpack_beta_manifest' );
429
430
		// Check if there is a new version.
431
		if ( self::should_update_dev_to_master() ) {
432
			// If response is false, don't alter the transient.
433
			$transient->response[ JETPACK_DEV_PLUGIN_FILE ] = self::get_jepack_dev_master_update_response();
434
			// Unset the that it doesn't need an update.
435
			unset( $transient->no_update[ JETPACK_DEV_PLUGIN_FILE ] );
436
		} elseif ( self::should_update_dev_version() ) {
437
			// If response is false, don't alter the transient.
438
			$transient->response[ JETPACK_DEV_PLUGIN_FILE ] = self::get_jepack_dev_update_response();
439
			// Unset the that it doesn't need an update.
440
			unset( $transient->no_update[ JETPACK_DEV_PLUGIN_FILE ] );
441
		} else {
442
			unset( $transient->response[ JETPACK_DEV_PLUGIN_FILE ] );
443
			if ( isset( $transient->no_update ) ) {
444
				$transient->no_update[ JETPACK_DEV_PLUGIN_FILE ] = self::get_jepack_dev_update_response();
445
			}
446
		}
447
448
		return $transient;
449
	}
450
451
	/**
452
	 * Determine if JP dev version should be updated.
453
	 */
454
	public static function should_update_dev_version() {
455
		return version_compare( self::get_new_jetpack_version( true ), self::get_jetpack_plugin_version( true ), '>' );
456
	}
457
458
	/**
459
	 * Build plugin update data response for dev plugin.
460
	 */
461
	public static function get_jepack_dev_update_response() {
462
		$response              = new stdClass();
463
		$response->id          = JETPACK_DEV_PLUGIN_SLUG;
464
		$response->plugin      = JETPACK_DEV_PLUGIN_SLUG;
465
		$response->new_version = self::get_new_jetpack_version( true );
466
		$response->slug        = JETPACK_DEV_PLUGIN_SLUG;
467
		$response->url         = self::get_url_dev();
468
		$response->package     = self::get_install_url_dev();
469
		return $response;
470
	}
471
472
	/**
473
	 * Build plugin update data response for JP dev master.
474
	 */
475
	public static function get_jepack_dev_master_update_response() {
476
		$response = self::get_jepack_dev_update_response();
477
478
		$master_manifest       = self::get_manifest_data( 'master', 'master' );
479
		$response->new_version = $master_manifest->version;
480
		$response->url         = self::get_url( 'master', 'master' );
481
		$response->package     = $master_manifest->download_url;
482
		return $response;
483
	}
484
485
	/**
486
	 * Moves the newly downloaded folder into jetpack-dev.
487
	 *
488
	 * @param bool  $worked      - Installation response.
489
	 * @param array $hook_extras - Extra args passed to hooked filters.
490
	 * @param array $result      - Installation result data.
491
	 *
492
	 * @return WP_Error
493
	 */
494
	public function upgrader_post_install( $worked, $hook_extras, $result ) {
495
		global $wp_filesystem;
496
497
		if (
498
		! isset( $hook_extras['plugin'] )
499
		|| JETPACK_DEV_PLUGIN_FILE !== $hook_extras['plugin']
500
		) {
501
			return $worked;
502
		}
503
504
		if ( $wp_filesystem->move( $result['destination'], WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . JETPACK_DEV_PLUGIN_SLUG, true ) ) {
505
			return $worked;
506
		} else {
507
			return new WP_Error();
508
		}
509
	}
510
511
	/**
512
	 * Get the active JP or JP Dev plugin version.
513
	 *
514
	 * @param bool $is_dev_version - If dev plugin version is being queried.
515
	 *
516
	 * @return string|0 Plugin version.
0 ignored issues
show
Documentation introduced by
The doc-type string|0 could not be parsed: Unknown type name "0" at position 7. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
517
	 */
518
	public static function get_jetpack_plugin_version( $is_dev_version = false ) {
519
		if ( $is_dev_version ) {
520
			$info = self::get_jetpack_plugin_info_dev();
521
		} else {
522
			$info = self::get_jetpack_plugin_info();
523
		}
524
525
		return isset( $info['Version'] ) ? $info['Version'] : 0;
526
	}
527
528
	/**
529
	 * Get WP Option: jetpack_beta_active
530
	 */
531
	public static function get_option() {
532
		return get_option( self::$option );
533
	}
534
535
	/**
536
	 * Get WP Option: jetpack_beta_dev_currently_installed
537
	 */
538
	public static function get_dev_installed() {
539
		return get_option( self::$option_dev_installed );
540
	}
541
542
	/**
543
	 * Get active Jetpack branch/section.
544
	 */
545
	public static function get_branch_and_section() {
546
		$option = (array) self::get_option();
547
		if ( false === $option[0] ) {
548
			// See if the Jetpack plugin is enabled.
549
			if ( ! function_exists( 'is_plugin_active' ) ) {
550
				require_once ABSPATH . 'wp-admin/includes/plugin.php';
551
			}
552
			if ( is_plugin_active( JETPACK_PLUGIN_FILE ) ) {
553
				return array( 'stable', 'stable' );
554
			}
555
			return array( false, false );
556
		}
557
		// Branch and section.
558
		return $option;
559
	}
560
561
	/**
562
	 * Check if Jetpack version is 'stable' version.
563
	 */
564 View Code Duplication
	public static function is_on_stable() {
565
		$branch_and_section = self::get_branch_and_section();
566
		if ( empty( $branch_and_section[0] ) || 'stable' === $branch_and_section[0] ) {
567
			return true;
568
		}
569
		return false;
570
	}
571
572
	/**
573
	 * Check if Jetpack active version is a tag version.
574
	 */
575
	public static function is_on_tag() {
576
		$option = (array) self::get_option();
577
		if ( isset( $option[1] ) && 'tags' === $option[1] ) {
578
			return true;
579
		}
580
		return false;
581
	}
582
583
	/**
584
	 * Get active Jetpack Dev branch/section.
585
	 */
586
	public static function get_branch_and_section_dev() {
587
		$option = (array) self::get_dev_installed();
588
		if ( false !== $option[0] && isset( $option[1] ) ) {
589
			return array( $option[0], $option[1] );
590
		}
591
		if ( is_plugin_active( JETPACK_DEV_PLUGIN_FILE ) ) {
592
			return array( 'stable', 'stable' );
593
		}
594
		return array( false, false );
595
	}
596
597
	/**
598
	 * Massage JP plugin version string.
599
	 *
600
	 * @param bool $is_dev_version - If JP Dev version is being queried.
601
	 */
602
	public static function get_jetpack_plugin_pretty_version( $is_dev_version = false ) {
603
		if ( $is_dev_version ) {
604
			list( $branch, $section ) = self::get_branch_and_section_dev();
605
		} else {
606
			list( $branch, $section ) = self::get_branch_and_section();
607
		}
608
609
		if ( ! $section ) {
610
			return '';
611
		}
612
613
		if ( 'master' === $section ) {
614
			return 'Bleeding Edge';
615
		}
616
617
		if ( 'stable' === $section ) {
618
			return 'Latest Stable';
619
		}
620
621
		if ( 'tags' === $section ) {
622
			return sprintf(
623
				// translators: %1$s: a tagged Jetpack plugin version.
624
				__( 'Public release (<a href="https://plugins.trac.wordpress.org/browser/jetpack/tags/%1$s" target="_blank" rel="noopener noreferrer">available on WordPress.org</a>)', 'jetpack-beta' ),
625
				esc_attr( $branch )
626
			);
627
		}
628
629
		if ( 'rc' === $section ) {
630
			return 'Release Candidate';
631
		}
632
633
		if ( 'pr' === $section ) {
634
			$branch = str_replace( '-', ' ', $branch );
635
			return 'Feature Branch: ' . str_replace( '_', ' / ', $branch );
636
		}
637
638
		return self::get_jetpack_plugin_version();
639
	}
640
641
	/**
642
	 * Fetch latest Jetpack version.
643
	 *
644
	 * @param bool $is_dev_version - If JP Dev version is being queried.
645
	 */
646
	public static function get_new_jetpack_version( $is_dev_version = false ) {
647
		$manifest = self::get_beta_manifest();
648
		if ( $is_dev_version ) {
649
			list( $branch, $section ) = self::get_branch_and_section_dev();
650
		} else {
651
			list( $branch, $section ) = self::get_branch_and_section();
652
		}
653
654
		if ( 'master' === $section && isset( $manifest->{$section}->version ) ) {
655
			return $manifest->{$section}->version;
656
		}
657
658
		if ( 'rc' === $section && isset( $manifest->{$section}->version ) ) {
659
			return $manifest->{$section}->version;
660
		}
661
662
		if ( isset( $manifest->{$section} ) &&
663
		isset( $manifest->{$section}->{$branch} ) &&
664
		isset( $manifest->{$section}->{$branch}->version )
665
		) {
666
			return $manifest->{$section}->{$branch}->version;
667
		}
668
		return 0;
669
	}
670
671
	/**
672
	 * Get JP Dev plugin repo URL.
673
	 */
674
	public static function get_url_dev() {
675
		list( $branch, $section ) = self::get_branch_and_section_dev();
676
		return self::get_url( $branch, $section );
677
	}
678
679
	/**
680
	 * Get JP plugin repo URL.
681
	 *
682
	 * @param string $branch  - Branch.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $branch not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
683
	 * @param string $section - Section.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $section not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
684
	 */
685
	public static function get_url( $branch = null, $section = null ) {
686
		if ( is_null( $section ) ) {
687
			list( $branch, $section ) = self::get_branch_and_section();
688
		}
689
690
		if ( 'master' === $section ) {
691
			return JETPACK_GITHUB_URL . '/tree/master-build';
692
		}
693
694
		if ( 'rc' === $section ) {
695
			return JETPACK_GITHUB_URL . '/tree/' . $section . '-build';
696
		}
697
698
		if ( 'pr' === $section ) {
699
			$manifest = self::get_beta_manifest();
700
			return isset( $manifest->{$section}->{$branch}->pr )
701
			? JETPACK_GITHUB_URL . '/pull/' . $manifest->{$section}->{$branch}->pr
702
			: JETPACK_DEFAULT_URL;
703
		}
704
		return JETPACK_DEFAULT_URL;
705
	}
706
707
	/**
708
	 * Get install URL for JP dev.
709
	 */
710
	public static function get_install_url_dev() {
711
		list( $branch, $section ) = self::get_branch_and_section_dev();
712
		return self::get_install_url( $branch, $section );
713
	}
714
715
	/**
716
	 * Get install URL for JP.
717
	 *
718
	 * @param string $branch  - Branch.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $branch not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
719
	 * @param string $section - Section.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $section not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
720
	 */
721
	public static function get_install_url( $branch = null, $section = null ) {
722
		if ( is_null( $section ) ) {
723
			list( $branch, $section ) = self::get_branch_and_section();
724
		}
725
726
		if ( 'stable' === $section ) {
727
			$org_data = self::get_org_data();
728
			return $org_data->download_link;
729
		} elseif ( 'tags' === $section ) {
730
			$org_data = self::get_org_data();
731
			return $org_data->versions->{$branch} ? $org_data->versions->{$branch} : false;
732
		}
733
		$manifest = self::get_beta_manifest( true );
734
735
		if ( 'master' === $section && isset( $manifest->{$section}->download_url ) ) {
736
			return $manifest->{$section}->download_url;
737
		}
738
739
		if ( 'rc' === $section ) {
740
			if ( isset( $manifest->{$section}->download_url ) ) {
741
				return $manifest->{$section}->download_url;
742
			}
743
			$branches = array_keys( (array) $manifest->{$section} );
744
			foreach ( $branches as $branch ) {
745
				if ( isset( $manifest->{$section}->{$branch}->download_url ) ) {
746
					return $manifest->{$section}->{$branch}->download_url;
747
				}
748
			}
749
			return null;
750
		}
751
752
		if ( isset( $manifest->{$section}->{$branch}->download_url ) ) {
753
			return $manifest->{$section}->{$branch}->download_url;
754
		}
755
		return null;
756
	}
757
758
	/**
759
	 * Get stable JP version plugin data.
760
	 */
761
	public static function get_jetpack_plugin_info_stable() {
762
		return self::get_jetpack_plugin_info( JETPACK_PLUGIN_FILE );
763
	}
764
765
	/**
766
	 * Get dev JP version plugin data.
767
	 */
768
	public static function get_jetpack_plugin_info_dev() {
769
		return self::get_jetpack_plugin_info( JETPACK_DEV_PLUGIN_FILE );
770
	}
771
772
	/**
773
	 * Get JP plugin data.
774
	 *
775
	 * @param mixed $plugin_file - JP or JP Dev plugin path.
776
	 */
777
	public static function get_jetpack_plugin_info( $plugin_file = null ) {
778
779
		if ( is_null( $plugin_file ) ) {
780
			$plugin_file = self::get_plugin_file();
781
		}
782
783
		if ( ! function_exists( 'get_plugin_data' ) ) {
784
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
785
		}
786
		$plugin_file_path = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $plugin_file;
787
788
		if ( file_exists( $plugin_file_path ) ) {
789
			return get_plugin_data( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $plugin_file );
790
		}
791
792
		return null;
793
	}
794
795
	/**
796
	 * Switch active JP plugin version when JP Beta plugin is deactivated.
797
	 * This needs to happen on `shutdown`, otherwise it doesn't work.
798
	 */
799
	public static function switch_active() {
800
		self::replace_active_plugin( JETPACK_DEV_PLUGIN_FILE, JETPACK_PLUGIN_FILE );
801
	}
802
803
	/**
804
	 * Fetch the Jetpack beta manifest.
805
	 *
806
	 * @param bool $force_refresh - Whether to bypass cached response.
807
	 */
808
	public static function get_beta_manifest( $force_refresh = false ) {
809
		return self::get_remote_data( JETPACK_BETA_MANIFEST_URL, 'manifest', $force_refresh );
810
	}
811
812
	/**
813
	 * Fetch WordPress.org Jetpack plugin info.
814
	 */
815
	public static function get_org_data() {
816
		return self::get_remote_data( JETPACK_ORG_API_URL, 'org_data' );
817
	}
818
819
	/**
820
	 * Helper function used to fetch remote data from WordPress.org, GitHub, and betadownload.jetpack.me
821
	 *
822
	 * @param string $url       - Url being fetched.
823
	 * @param string $transient - Transient name (manifest|org_data|github_commits_).
824
	 * @param bool   $bypass    - Whether to bypass cached response.
825
	 */
826
	public static function get_remote_data( $url, $transient, $bypass = false ) {
827
		$prefix = 'jetpack_beta_';
828
		$cache  = get_site_transient( $prefix . $transient );
829
		if ( $cache && ! $bypass ) {
830
			return $cache;
831
		}
832
833
		$remote_manifest = wp_remote_get( $url );
834
835
		if ( is_wp_error( $remote_manifest ) ) {
836
			return false;
837
		}
838
839
		$cache = json_decode( wp_remote_retrieve_body( $remote_manifest ) );
840
		set_site_transient( $prefix . $transient, $cache, MINUTE_IN_SECONDS * 15 );
841
842
		return $cache;
843
	}
844
845
	/**
846
	 * Delete set transients when plugin is deactivated.
847
	 */
848
	public static function delete_all_transiants() {
849
		$prefix = 'jetpack_beta_';
850
851
		delete_site_transient( $prefix . 'org_data' );
852
		delete_site_transient( $prefix . 'manifest' );
853
854
		delete_site_transient( Jetpack_Beta_Autoupdate_Self::TRANSIENT_NAME );
855
856
	}
857
858
	/**
859
	 * Install & activate JP for the given branch/section.
860
	 *
861
	 * @param string $branch  - Branch.
862
	 * @param string $section - Section.
863
	 */
864
	public static function install_and_activate( $branch, $section ) {
865
		// Cleanup previous version of the beta plugin.
866
		if ( file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'jetpack-pressable-beta' ) ) {
867
			// Delete the Jetpack dev plugin.
868
			require_once ABSPATH . 'wp-admin/includes/file.php';
869
			$creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, array() );
870
			if ( ! WP_Filesystem( $creds ) ) {
871
				// Any problems and we exit.
872
				return new WP_error( 'Filesystem Problem' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'Filesystem Problem'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
873
			}
874
			global $wp_filesystem;
875
			if ( ! $wp_filesystem ) {
876
				return new WP_error( '$wp_filesystem is not global' );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with '$wp_filesystem is not global'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
877
			}
878
879
			$working_dir = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'jetpack-pressable-beta';
880
			// Delete the folder `JETPACK_BETA_PLUGIN_FOLDER`.
881
			if ( $wp_filesystem->is_dir( $working_dir ) ) {
882
				$wp_filesystem->delete( $working_dir, true );
883
			}
884
			// Deactivate the plugin.
885
			self::replace_active_plugin( 'jetpack-pressable-beta/jetpack.php' );
886
		}
887
888
		self::update_autoload_dev_constant( $section );
889
890 View Code Duplication
		if ( 'stable' === $section &&
891
		file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . JETPACK_PLUGIN_FILE ) ) {
892
			self::replace_active_plugin( JETPACK_DEV_PLUGIN_FILE, JETPACK_PLUGIN_FILE, true );
893
			self::update_option( $branch, $section );
894
			return;
895
		}
896
897 View Code Duplication
		if ( self::get_branch_and_section_dev() === array( $branch, $section )
898
		&& file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . JETPACK_DEV_PLUGIN_FILE ) ) {
899
			self::replace_active_plugin( JETPACK_PLUGIN_FILE, JETPACK_DEV_PLUGIN_FILE, true );
900
			self::update_option( $branch, $section );
901
			return;
902
		}
903
904
		self::proceed_to_install_and_activate(
905
			self::get_install_url( $branch, $section ),
906
			self::get_plugin_slug( $section ),
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Beta::get_plugin_slug() has too many arguments starting with $section.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
907
			$section
908
		);
909
		self::update_option( $branch, $section );
910
	}
911
912
	/**
913
	 * Update to the latest version.
914
	 *
915
	 * @param string $branch  - Branch.
916
	 * @param string $section - Section.
917
	 */
918
	public static function update_plugin( $branch, $section ) {
919
		self::proceed_to_install(
920
			self::get_install_url( $branch, $section ),
921
			self::get_plugin_slug( $section ),
0 ignored issues
show
Unused Code introduced by
The call to Jetpack_Beta::get_plugin_slug() has too many arguments starting with $section.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
922
			$section
923
		);
924
925 View Code Duplication
		if ( 'stable' !== $section ) {
926
			update_option( self::$option_dev_installed, array( $branch, $section, self::get_manifest_data( $branch, $section ) ) );
927
		}
928
	}
929
930
	/**
931
	 * Helper function to update installed version option.
932
	 *
933
	 * @param string $branch  - Branch.
934
	 * @param string $section - Section.
935
	 */
936
	public static function update_option( $branch, $section ) {
937 View Code Duplication
		if ( 'stable' !== $section ) {
938
			update_option( self::$option_dev_installed, array( $branch, $section, self::get_manifest_data( $branch, $section ) ) );
939
		}
940
		update_option( self::$option, array( $branch, $section ) );
941
	}
942
943
	/**
944
	 * Return manifest info for specififed branch/section.
945
	 *
946
	 * @param string $branch  - Branch.
947
	 * @param string $section - Section.
948
	 */
949
	public static function get_manifest_data( $branch, $section ) {
950
		$installed             = get_option( self::$option_dev_installed );
951
		$current_manifest_data = isset( $installed[2] ) ? $installed[2] : false;
952
953
		$manifest_data = self::get_beta_manifest();
954
955
		if ( ! isset( $manifest_data->{$section} ) ) {
956
			return $current_manifest_data;
957
		}
958
959
		if ( 'master' === $section ) {
960
			return $manifest_data->{$section};
961
		}
962
963
		if ( 'rc' === $section ) {
964
			return $manifest_data->{$section};
965
		}
966
967
		if ( isset( $manifest_data->{$section}->{$branch} ) ) {
968
			return $manifest_data->{$section}->{$branch};
969
		}
970
971
		return $current_manifest_data;
972
	}
973
974
	/**
975
	 * Install specified plugin version.
976
	 *
977
	 * @param string $url           - Url for plugin version.
978
	 * @param string $plugin_folder - Path JP or JP Dev plugin folder.
979
	 * @param string $section       - Section.
980
	 */
981
	public static function proceed_to_install_and_activate( $url, $plugin_folder, $section ) {
982
		self::proceed_to_install( $url, $plugin_folder, $section );
983
984
		if ( 'stable' === $section || 'tags' === $section ) {
985
			self::replace_active_plugin( JETPACK_DEV_PLUGIN_FILE, JETPACK_PLUGIN_FILE, true );
986
		} else {
987
			self::replace_active_plugin( JETPACK_PLUGIN_FILE, JETPACK_DEV_PLUGIN_FILE, true );
988
		}
989
	}
990
991
	/**
992
	 * Download plugin files.
993
	 *
994
	 * @param string $url           - Url for plugin version.
995
	 * @param string $plugin_folder - Path JP or JP Dev plugin folder.
996
	 * @param string $section       - Section.
997
	 */
998
	public static function proceed_to_install( $url, $plugin_folder, $section ) {
999
		$temp_path = download_url( $url );
1000
1001 View Code Duplication
		if ( is_wp_error( $temp_path ) ) {
1002
			// translators: %1$s: download url, %2$s: error message.
1003
			wp_die( wp_kses_post( sprintf( __( 'Error Downloading: <a href="%1$s">%1$s</a> - Error: %2$s', 'jetpack-beta' ), $url, $temp_path->get_error_message() ) ) );
1004
		}
1005
		require_once ABSPATH . 'wp-admin/includes/file.php';
1006
		$creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, array() );
1007
		/* initialize the API */
1008
		if ( ! WP_Filesystem( $creds ) ) {
1009
			/* any problems and we exit */
1010
			wp_die( esc_html( __( 'Jetpack Beta: No File System access', 'jetpack-beta' ) ) );
1011
		}
1012
1013
		global $wp_filesystem;
1014
		if ( 'stable' === $section || 'tags' === $section ) {
1015
			$plugin_path = WP_PLUGIN_DIR;
1016
		} else {
1017
			$plugin_path = str_replace( ABSPATH, $wp_filesystem->abspath(), WP_PLUGIN_DIR );
1018
		}
1019
1020
		$result = unzip_file( $temp_path, $plugin_path );
1021
1022 View Code Duplication
		if ( is_wp_error( $result ) ) {
1023
			// translators: %1$s: error message.
1024
			wp_die( esc_html( sprintf( __( 'Error Unziping file: Error: %1$s', 'jetpack-beta' ), $result->get_error_message() ) ) );
1025
		}
1026
	}
1027
1028
	/**
1029
	 * Check if plugin is network activated.
1030
	 */
1031
	public static function is_network_active() {
1032
		if ( ! is_multisite() ) {
1033
			return false;
1034
		}
1035
1036
		if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
1037
			return false;
1038
		}
1039
1040
		if ( is_plugin_active_for_network( JETPACK_PLUGIN_FILE ) || is_plugin_active_for_network( JETPACK_DEV_PLUGIN_FILE ) ) {
1041
			return true;
1042
		}
1043
1044
		return false;
1045
	}
1046
1047
	/**
1048
	 * Swap plugin files.
1049
	 *
1050
	 * @param string $current_plugin      - Current plugin path.
1051
	 * @param string $replace_with_plugin - Plugin path to replace with.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $replace_with_plugin not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1052
	 * @param bool   $force_activate      - Whether to force activate plguin.
1053
	 */
1054
	public static function replace_active_plugin( $current_plugin, $replace_with_plugin = null, $force_activate = false ) {
1055
		// The autoloader sets the cache in a shutdown hook. Clear it after the autoloader sets it.
1056
		add_action( 'shutdown', array( __CLASS__, 'clear_autoloader_plugin_cache' ), 99 );
1057
1058
		if ( self::is_network_active() ) {
1059
			$new_active_plugins     = array();
1060
			$network_active_plugins = get_site_option( 'active_sitewide_plugins' );
1061
			foreach ( $network_active_plugins as $plugin => $date ) {
1062
				$key                        = ( $plugin === $current_plugin ? $replace_with_plugin : $plugin );
1063
				$new_active_plugins[ $key ] = $date;
1064
			}
1065
			update_site_option( 'active_sitewide_plugins', $new_active_plugins );
1066
			return;
1067
		}
1068
1069
		$active_plugins     = (array) get_option( 'active_plugins', array() );
1070
		$new_active_plugins = array();
1071
1072
		if ( empty( $replace_with_plugin ) ) {
1073
			$new_active_plugins = array_diff( $active_plugins, array( $current_plugin ) );
1074
		} else {
1075
			foreach ( $active_plugins as $plugin ) {
1076
				$new_active_plugins[] = ( $plugin === $current_plugin ? $replace_with_plugin : $plugin );
1077
			}
1078
		}
1079
1080
		if ( $force_activate && ! in_array( $replace_with_plugin, $new_active_plugins, true ) ) {
1081
			$new_active_plugins[] = $replace_with_plugin;
1082
		}
1083
		update_option( 'active_plugins', $new_active_plugins );
1084
	}
1085
1086
	/**
1087
	 * Check if `stable` should be updated.
1088
	 *
1089
	 * @return bool
1090
	 */
1091
	public static function should_update_stable_version() {
1092
		// Pressable Jetpack version is manage via Pressable.
1093
		if ( defined( 'IS_PRESSABLE' ) && IS_PRESSABLE ) {
1094
			return false;
1095
		}
1096
1097
		// Check if running in a docker instance.
1098
		if ( defined( 'JETPACK_DOCKER_ENV' ) && JETPACK_DOCKER_ENV ) {
1099
			return false;
1100
		}
1101
1102
		// Check if we are Jetpack plugin is installed via git.
1103
		if ( file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'jetpack/.git' ) ) {
1104
			return false;
1105
		}
1106
1107
		// Check if running a tag directly from svn.
1108
		if ( self::is_on_tag() ) {
1109
			return false;
1110
		}
1111
1112
		$updates = get_site_transient( 'update_plugins' );
1113
1114
		if ( isset( $updates->response, $updates->response[ JETPACK_PLUGIN_FILE ] ) ) {
1115
			return true;
1116
		}
1117
		$org_data    = self::get_org_data();
1118
		$plugin_data = self::get_jetpack_plugin_info_stable();
1119
1120
		return ( isset( $plugin_data['Version'], $org_data->version )
1121
			&& $org_data->version !== $plugin_data['Version'] );
1122
	}
1123
1124
	/**
1125
	 * Here we are checking if the DEV branch that we are currenly on is not something that is available in the manifest
1126
	 * Meaning that the DEV branch was merged into master and so we need to update it.
1127
	 *
1128
	 * @return bool
1129
	 */
1130
	public static function should_update_dev_to_master() {
1131
		list( $branch, $section ) = self::get_branch_and_section_dev();
1132
1133
		if ( false === $branch || 'master' === $section || 'rc' === $section || 'tags' === $section ) {
1134
			return false;
1135
		}
1136
		$manifest = self::get_beta_manifest();
1137
		return ! isset( $manifest->{$section}->{$branch} );
1138
	}
1139
1140
	/**
1141
	 * Get WP Option: jp_beta_autoupdate
1142
	 */
1143
	public static function is_set_to_autoupdate() {
1144
		return get_option( self::$option_autoupdate, false );
1145
	}
1146
1147
	/**
1148
	 * Get WP Option: jp_beta_email_notifications
1149
	 */
1150
	public static function is_set_to_email_notifications() {
1151
		return get_option( self::$option_email_notif, true );
1152
	}
1153
1154
	/**
1155
	 * Clear scheduled WP-Cron jobs on plugin deactivation.
1156
	 */
1157
	public static function clear_autoupdate_cron() {
1158
		if ( ! is_main_site() ) {
1159
			return;
1160
		}
1161
		wp_clear_scheduled_hook( self::$auto_update_cron_hook );
1162
1163
		if ( function_exists( 'wp_unschedule_hook' ) ) { // New in WP `4.9`.
1164
			wp_unschedule_hook( self::$auto_update_cron_hook );
1165
		}
1166
	}
1167
1168
	/**
1169
	 * Schedule plugin update jobs.
1170
	 */
1171
	public static function schedule_hourly_autoupdate() {
1172
		wp_clear_scheduled_hook( self::$auto_update_cron_hook );
1173
		wp_schedule_event( time(), 'hourly', self::$auto_update_cron_hook );
1174
	}
1175
1176
	/**
1177
	 * Determine if plugin update jobs should be scheduled.
1178
	 */
1179
	public static function maybe_schedule_autoupdate() {
1180
		if ( ! self::is_set_to_autoupdate() ) {
1181
			return;
1182
		}
1183
1184
		if ( ! is_main_site() ) {
1185
			return;
1186
		}
1187
		$has_schedule_already = wp_get_schedule( self::$auto_update_cron_hook );
1188
		if ( ! $has_schedule_already ) {
1189
			self::schedule_hourly_autoupdate();
1190
		}
1191
	}
1192
1193
	/**
1194
	 * Get "What changed" info for display.
1195
	 *
1196
	 * @return string|false
1197
	 */
1198
	public static function what_changed() {
1199
		$commit = self::get_version_commit();
1200
		if ( $commit ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $commit of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1201
			$html        = '';
1202
			$commit_data = self::get_commit_data_from_github( $commit );
1203
			if ( isset( $commit_data->commit->message ) ) {
1204
				$html .= sprintf(
1205
					__( "\n %1\$s \n\n[Commit](%2\$s)", 'jetpack-beta' ),
1206
					esc_html( $commit_data->commit->message ),
1207
					esc_url( $commit_data->html_url )
1208
				);
1209
				"\n\n";
1210
			}
1211
			if ( ! empty( $commit_data->files ) ) {
1212
				$html .= "\n\n";
1213
				// translators: %d: number of files changed.
1214
				$html .= sprintf( _n( '%d file changed ', '%d files changed', count( $commit_data->files ), 'jetpack-beta' ) );
1215
				$html .= "\n";
1216
				foreach ( $commit_data->files as $file ) {
1217
					$added_deleted_changed = array();
1218
					if ( $file->additions ) {
1219
						$added_deleted_changed[] = '+' . $file->additions;
1220
					}
1221
					if ( $file->deletions ) {
1222
						$added_deleted_changed[] = '-' . $file->deletions;
1223
					}
1224
					$html .= sprintf( "- %s ... (%s %s) \n", esc_html( $file->filename ), esc_html( $file->status ), implode( ' ', $added_deleted_changed ) );
1225
				}
1226
				$html .= "\n\n";
1227
			}
1228
			if ( ! empty( $html ) ) {
1229
				return $html;
1230
			}
1231
		}
1232
		return false;
1233
	}
1234
1235
	/**
1236
	 * Get version commit if available.
1237
	 *
1238
	 * @return string|false
1239
	 */
1240
	public static function get_version_commit() {
1241
		$split_version = explode( '-', self::get_jetpack_plugin_version() );
1242
		if ( isset( $split_version[3] ) ) {
1243
			return $split_version[3];
1244
		}
1245
		return false;
1246
	}
1247
1248
	/**
1249
	 * Fetch commit data from GitHub.
1250
	 *
1251
	 * @param string $commit - The commit to fetch.
1252
	 */
1253
	public static function get_commit_data_from_github( $commit ) {
1254
		return self::get_remote_data( JETPACK_GITHUB_API_URL . 'commits/' . $commit, 'github_commits_' . $commit );
1255
	}
1256
1257
	/**
1258
	 * The jetpack_beta_autoupdate_hourly_cron job - does not update Stable.
1259
	 */
1260
	public static function run_autoupdate() {
1261
		if ( ! self::is_set_to_autoupdate() ) {
1262
			return;
1263
		}
1264
1265
		if ( ! is_main_site() ) {
1266
			return;
1267
		}
1268
1269
		require_once ABSPATH . 'wp-admin/includes/file.php';
1270
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
1271
		require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1272
		wp_clean_plugins_cache();
1273
		ob_start();
1274
		wp_update_plugins(); // Check for Plugin updates.
1275
		ob_end_clean();
1276
		$plugins = array();
1277
		if (
1278
		! self::is_on_stable() &&
1279
		( self::should_update_dev_to_master() || self::should_update_dev_version() )
1280
		) {
1281
			add_filter( 'upgrader_source_selection', array( 'Jetpack_Beta', 'check_for_main_files' ), 10, 2 );
1282
1283
			// If response is false, don't alter the transient.
1284
			$plugins[] = JETPACK_DEV_PLUGIN_FILE;
1285
		}
1286
		$autupdate = Jetpack_Beta_Autoupdate_Self::instance();
1287
		if ( $autupdate->has_never_version() ) {
1288
			$plugins[] = JPBETA__PLUGIN_FOLDER . '/jetpack-beta.php';
1289
		}
1290
1291
		if ( empty( $plugins ) ) {
1292
			return;
1293
		}
1294
1295
		// Unhook this functions that output things before we send our response header.
1296
		remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
1297
		remove_action( 'upgrader_process_complete', 'wp_version_check' );
1298
		remove_action( 'upgrader_process_complete', 'wp_update_themes' );
1299
1300
		$skin = new WP_Ajax_Upgrader_Skin();
1301
		// The Automatic_Upgrader_Skin skin shouldn't output anything.
1302
		$upgrader = new Plugin_Upgrader( $skin );
1303
		$upgrader->init();
1304
		// This avoids the plugin to be deactivated.
1305
		// Using bulk upgrade puts the site into maintenance mode during the upgrades.
1306
		$result = $upgrader->bulk_upgrade( $plugins );
1307
		$errors = $upgrader->skin->get_errors();
1308
		$log    = $upgrader->skin->get_upgrade_messages();
1309
1310
		if ( is_wp_error( $errors ) && $errors->get_error_code() ) {
1311
			return $errors;
1312
		}
1313
1314
		if ( $result && ! defined( 'JETPACK_BETA_SKIP_EMAIL' ) && self::is_set_to_email_notifications() ) {
1315
			self::send_autoupdate_email( $plugins, $log );
1316
		}
1317
	}
1318
1319
	/**
1320
	 * Builds and sends an email about succesfull plugin autoupdate.
1321
	 *
1322
	 * @param Array  $plugins - List of plugins that were updated.
1323
	 * @param String $log     - Upgrade message from core's plugin upgrader.
1324
	 */
1325
	private static function send_autoupdate_email( $plugins, $log ) {
1326
		$admin_email = get_site_option( 'admin_email' );
1327
1328
		if ( empty( $admin_email ) ) {
1329
			return;
1330
		}
1331
1332
		// In case the code is called in a scope different from wp-admin.
1333
		require_once JPBETA__PLUGIN_DIR . 'class-jetpack-beta-admin.php';
1334
1335
		// Calling empty() on a function return value crashes in PHP < 5.5.
1336
		// Thus we assign the return value explicitly and then check with empty().
1337
		$bloginfo_name = get_bloginfo( 'name' );
1338
		$site_title    = ! empty( $bloginfo_name ) ? get_bloginfo( 'name' ) : get_site_url();
1339
		$what_updated  = 'Jetpack Beta Tester Plugin';
1340
		// translators: %s: The site title.
1341
		$subject = sprintf( __( '[%s] Autoupdated Jetpack Beta Tester', 'jetpack-beta' ), $site_title );
1342
1343
		if ( in_array( JETPACK_DEV_PLUGIN_FILE, $plugins, true ) ) {
1344
			$subject = sprintf(
1345
				// translators: %1$s: site title, %2$s: pretty plugin version (eg 9.3).
1346
				__( '[%1$s] Autoupdated Jetpack %2$s ', 'jetpack-beta' ),
1347
				$site_title,
1348
				self::get_jetpack_plugin_pretty_version()
1349
			);
1350
1351
			$what_updated = sprintf(
1352
				// translators: $1$s: pretty plugin version, $2$s: raw plugin version (eg 9.3.2-beta).
1353
				__( 'Jetpack %1$s (%2$s)', 'jetpack-beta' ),
1354
				self::get_jetpack_plugin_pretty_version(),
1355
				self::get_jetpack_plugin_version()
1356
			);
1357
1358
			if ( count( $plugins ) > 1 ) {
1359
				$subject = sprintf(
1360
					// translators: %1$s: site title, %2$s: pretty plugin version.
1361
					__( '[%1$s] Autoupdated Jetpack %2$s and the Jetpack Beta Tester', 'jetpack-beta' ),
1362
					$site_title,
1363
					self::get_jetpack_plugin_pretty_version()
1364
				);
1365
1366
				$what_updated = sprintf(
1367
					// translators: $1$s: pretty plugin version, $2$s: raw plugin version.
1368
					__( 'Jetpack %1$s (%2$s) and the Jetpack Beta Tester', 'jetpack-beta' ),
1369
					self::get_jetpack_plugin_pretty_version(),
1370
					self::get_jetpack_plugin_version()
1371
				);
1372
			}
1373
		}
1374
1375
		$message = sprintf(
1376
			// translators: %1$s: site url, $2$s: text of what has updated.
1377
			__( 'Howdy! Your site at %1$s has autoupdated %2$s.', 'jetpack-beta' ),
1378
			home_url(),
1379
			$what_updated
1380
		);
1381
		$message .= "\n\n";
1382
1383
		$what_changed = self::what_changed();
1384
		if ( $what_changed ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $what_changed of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1385
			$message .= __( 'What changed?', 'jetpack-beta' );
1386
			$message .= wp_strip_all_tags( $what_changed );
1387
		}
1388
1389
		$message .= __( 'During the autoupdate the following happened:', 'jetpack-beta' );
1390
		$message .= "\n\n";
1391
		// Can only reference the About screen if their update was successful.
1392
		$log      = array_map( 'html_entity_decode', $log );
1393
		$message .= ' - ' . implode( "\n - ", $log );
1394
		$message .= "\n\n";
1395
1396
		// Adds To test section. for PR's it's a PR description, for master/RC - it's a to_test.md file contents.
1397
		$message .= Jetpack_Beta_Admin::to_test_content();
1398
		$message .= "\n\n";
1399
1400
		wp_mail( $admin_email, $subject, $message );
1401
	}
1402
1403
	/**
1404
	 * This checks intends to fix errors in our build server when Jetpack.
1405
	 *
1406
	 * @param string $source        - Source path.
1407
	 * @param string $remote_source - Remote path.
1408
	 *
1409
	 * @return WP_Error
1410
	 */
1411
	public static function check_for_main_files( $source, $remote_source ) {
1412
		if ( $source === $remote_source . '/jetpack-dev/' ) {
1413
			if ( ! file_exists( $source . 'jetpack.php' ) ) {
1414
				return new WP_Error( 'plugin_file_does_not_exist', __( 'Main Plugin File does not exist', 'jetpack-beta' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'plugin_file_does_not_exist'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1415
			}
1416
			if ( ! file_exists( $source . '_inc/build/static.html' ) ) {
1417
				return new WP_Error( 'static_admin_page_does_not_exist', __( 'Static Admin Page File does not exist', 'jetpack-beta' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'static_admin_page_does_not_exist'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1418
			}
1419
			if ( ! file_exists( $source . '_inc/build/admin.js' ) ) {
1420
				return new WP_Error( 'admin_page_does_not_exist', __( 'Admin Page File does not exist', 'jetpack-beta' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'admin_page_does_not_exist'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1421
			}
1422
			// It has happened that sometimes a generated bundle from the master branch ends up with an empty
1423
			// vendor directory. Used to be a problem in the beta building process.
1424
			if ( self::is_dir_empty( $source . 'vendor' ) ) {
1425
				return new WP_Error( 'vendor_dir_is_empty', __( 'The dependencies dir (vendor) is empty', 'jetpack-beta' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'vendor_dir_is_empty'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1426
			}
1427
		}
1428
1429
		return $source;
1430
	}
1431
1432
	/**
1433
	 * Checks if a dir is empty.
1434
	 *
1435
	 * @param [type] $dir The absolute directory path to check.
0 ignored issues
show
Documentation introduced by
The doc-type [type] could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1436
	 * @return boolean
1437
	 */
1438
	public static function is_dir_empty( $dir ) {
1439
		return ( count( scandir( $dir ) ) === 2 );
1440
	}
1441
1442
	/**
1443
	 * Callback function to include Jetpack beta options into Jetpack sync whitelist.
1444
	 *
1445
	 * @param Array $whitelist List of whitelisted options to sync.
1446
	 */
1447
	public function add_to_options_whitelist( $whitelist ) {
1448
		$whitelist[] = self::$option;
1449
		$whitelist[] = self::$option_dev_installed;
1450
		$whitelist[] = self::$option_autoupdate;
1451
		$whitelist[] = self::$option_email_notif;
1452
		return $whitelist;
1453
	}
1454
1455
	/**
1456
	 * Custom error handler to intercept errors and log them using Jetpack's own logger.
1457
	 *
1458
	 * @param int    $errno   - Error code.
1459
	 * @param string $errstr  - Error message.
1460
	 * @param string $errfile - File name where the error happened.
1461
	 * @param int    $errline - Line in the code.
1462
	 *
1463
	 * @return bool Whether to make the default handler handle the error as well.
1464
	 */
1465
	public static function custom_error_handler( $errno, $errstr, $errfile, $errline ) {
1466
1467
		if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'log' ) ) {
1468
			$error_string = sprintf( '%s, %s:%d', $errstr, $errfile, $errline );
1469
1470
			// Only adding to log if the message is related to Jetpack.
1471
			if ( false !== stripos( $error_string, 'jetpack' ) ) {
1472
				Jetpack::log( $errno, $error_string );
1473
			}
1474
		}
1475
1476
		/**
1477
		 * The error_reporting call returns current error reporting level as an integer. Bitwise
1478
		 * AND lets us determine whether the current error is included in the current error
1479
		 * reporting level
1480
		 */
1481
		if ( ! ( error_reporting() & $errno ) ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting,WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting
1482
1483
			// If this error is not being reported in the current settings, stop reporting here by returning true.
1484
			return true;
1485
		}
1486
1487
		// Returning false makes the error go through the standard error handler as well.
1488
		return false;
1489
	}
1490
1491
	/**
1492
	 * Clears the autoloader transient.
1493
	 */
1494
	public static function clear_autoloader_plugin_cache() {
1495
		delete_transient( 'jetpack_autoloader_plugin_paths' );
1496
	}
1497
1498
	/**
1499
	 * Sets the 'jetpack_autoload_dev' option when a Jetpack version is activated:
1500
	 *   - Sets the option to true when a development version of Jetpack is activated.
1501
	 *   - Sets the option to false when the stable or release candidate version is  activated.
1502
	 *
1503
	 * This option is used to set the JETPACK_AUTOLOAD_DEV constant in jetpack-beta.php. The constant
1504
	 * is used by the Jetpack autoloader to determine whether stable or development versions of
1505
	 * packages should be preferred.
1506
	 *
1507
	 * @param string $section The section value for the activating Jetpack version.
1508
	 */
1509
	public static function update_autoload_dev_constant( $section ) {
1510
		if ( in_array( $section, array( 'stable', 'rc' ), true ) ) {
1511
				// The stable and rc versions use stable package versions.
1512
				update_option( 'jetpack_autoload_dev', 0 );
1513
		} else {
1514
				update_option( 'jetpack_autoload_dev', 1 );
1515
		}
1516
	}
1517
}
1518