Completed
Push — update/beta_plugin_set_autoloa... ( 14adbe )
by
unknown
394:45 queued 384:59
created

Jetpack_Beta::get_plugin_info()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 3
dl 0
loc 27
rs 9.488
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
104
	/**
105
	 * Fired when the upgrader process is complete; sets option jetpack_beta_dev_currently_installed
106
	 *
107
	 * @param WP_Upgrader $upgrader          - An upgrader instance.
108
	 * @param array       $updates_completed - Array of bulk item update data.
109
	 */
110
	public function upgrader_process_complete( $upgrader, $updates_completed ) {
111
		if ( ! isset( $updates_completed['plugins'] ) ) {
112
			return;
113
		}
114
115
		if ( 'update' === $updates_completed['action'] &&
116
			'plugin' === $updates_completed['type'] &&
117
		in_array( JETPACK_DEV_PLUGIN_FILE, $updates_completed['plugins'], true ) ) {
118
			list( $branch, $section ) = self::get_branch_and_section_dev();
119
			if ( self::should_update_dev_to_master() ) {
120
				list( $branch, $section ) = array( 'master', 'master' );
121
			}
122
			update_option( self::$option_dev_installed, array( $branch, $section, self::get_manifest_data( $branch, $section ) ) );
123
		}
124
	}
125
126
	/**
127
	 * If Jetpack or JP Dev plugin is network activated, update active_plugins option.
128
	 */
129
	public static function is_network_enabled() {
130
		if ( self::is_network_active() ) {
131
			add_filter( 'option_active_plugins', array( 'Jetpack_Beta', 'override_active_plugins' ) );
132
		}
133
	}
134
135
	/**
136
	 * This filter is only applied if Jetpack is network activated,
137
	 * makes sure that you can't have Jetpack or Jetpack Dev plugins versions loaded.
138
	 *
139
	 * @param array $active_plugins - Currently activated plugins.
140
	 *
141
	 * @return array Updated array of active plugins.
142
	 */
143
	public static function override_active_plugins( $active_plugins ) {
144
		$new_active_plugins = array();
145
		foreach ( $active_plugins as $active_plugin ) {
146
			if ( ! self::is_jetpack_plugin( $active_plugin ) ) {
147
				$new_active_plugins[] = $active_plugin;
148
			}
149
		}
150
		return $new_active_plugins;
151
	}
152
153
	/**
154
	 * Actions taken when the Jetpack Beta plugin is deactivated.
155
	 *
156
	 * @param string $plugin       - Plugin path being deactivated.
157
	 */
158
	public function plugin_deactivated( $plugin ) {
159
		if ( ! self::is_jetpack_plugin( $plugin ) ) {
160
			return;
161
		}
162
163
		delete_option( self::$option );
164
	}
165
166
	/**
167
	 * Checks if passed plugin matches JP or JP Dev paths.
168
	 *
169
	 * @param string $plugin - A plugin path.
170
	 */
171
	public static function is_jetpack_plugin( $plugin ) {
172
		return in_array( $plugin, array( JETPACK_PLUGIN_FILE, JETPACK_DEV_PLUGIN_FILE ), true );
173
	}
174
175
	/**
176
	 * Filter JP Dev plugin action links.
177
	 *
178
	 * @param array $actions - Array of plugin action links.
179
	 */
180
	public function remove_activate_dev( $actions ) {
181 View Code Duplication
		if ( is_plugin_active( JETPACK_PLUGIN_FILE ) || self::is_network_active() ) {
182
			$actions['activate'] = __( 'Plugin Already Active', 'jetpack-beta' );
183
		}
184
		return $actions;
185
	}
186
187
	/**
188
	 * Filter JP Stable plugin action links.
189
	 *
190
	 * @param array $actions - Array of plugin action links.
191
	 */
192
	public function remove_activate_stable( $actions ) {
193 View Code Duplication
		if ( is_plugin_active( JETPACK_DEV_PLUGIN_FILE ) || self::is_network_active() ) {
194
			$actions['activate'] = __( 'Plugin Already Active', 'jetpack-beta' );
195
		}
196
		return $actions;
197
	}
198
199
	/**
200
	 * Filters plugins to list in the Plugins list table.
201
	 *
202
	 * @param array $plugins - Array of arrays of plugin data.
203
	 *
204
	 * @return array Updated array of plugin data.
205
	 */
206
	public function update_all_plugins( $plugins ) {
207
		// WP.com requests away show regular plugin.
208
		if ( defined( 'REST_API_REQUEST' ) && REST_API_REQUEST ) {
209
			// Ensure that Jetpack reports the version it's using on account of the Jetpack Beta plugin to Calypso.
210
			if ( is_plugin_active( JETPACK_DEV_PLUGIN_FILE ) ) {
211
				$plugins[ JETPACK_PLUGIN_FILE ]['Version'] = $plugins[ JETPACK_DEV_PLUGIN_FILE ]['Version'];
212
			}
213
			unset( $plugins[ JETPACK_DEV_PLUGIN_FILE ] );
214
			return $plugins;
215
		}
216
217
		if ( is_plugin_active( JETPACK_DEV_PLUGIN_FILE ) ) {
218
			unset( $plugins[ JETPACK_PLUGIN_FILE ] );
219
		} else {
220
			unset( $plugins[ JETPACK_DEV_PLUGIN_FILE ] );
221
		}
222
		return $plugins;
223
	}
224
225
	/**
226
	 * Filter WordPress.org Plugins API results.
227
	 *
228
	 * @param false|object|array $false    - The result object or array. Default false.
229
	 * @param string             $action   - The type of information being requested from the Plugin Installation API.
230
	 * @param object             $response - Plugin API arguments.
231
	 */
232
	public function get_plugin_info( $false, $action, $response ) {
233
234
		// Check if this call API is for the right plugin.
235
		if ( ! isset( $response->slug ) || JETPACK_DEV_PLUGIN_SLUG !== $response->slug ) {
236
			return false;
237
		}
238
		$update_date  = null;
239
		$download_zip = null;
240
		$dev_data     = self::get_dev_installed();
241
		if ( isset( $dev_data[2] ) ) {
242
			$update_date  = $dev_data[2]->update_date;
243
			$download_zip = $dev_data[2]->download_url;
244
		}
245
		// Update tags.
246
		$response->slug          = JETPACK_DEV_PLUGIN_SLUG;
247
		$response->plugin        = JETPACK_DEV_PLUGIN_SLUG;
248
		$response->name          = 'Jetpack | ' . self::get_jetpack_plugin_pretty_version( true );
249
		$response->plugin_name   = 'Jetpack | ' . self::get_jetpack_plugin_pretty_version( true );
250
		$response->version       = self::get_jetpack_plugin_version( true );
251
		$response->author        = 'Automattic';
252
		$response->homepage      = 'https://jetpack.com/contact-support/beta-group/';
253
		$response->downloaded    = false;
254
		$response->last_updated  = $update_date;
255
		$response->sections      = array( 'description' => Jetpack_Beta_Admin::to_test_content() );
256
		$response->download_link = $download_zip;
257
		return $response;
258
	}
259
260
	/**
261
	 * Run on activation to flush update cache.
262
	 */
263
	public static function activate() {
264
		// Don't do anyting funnly.
265
		if ( defined( 'DOING_CRON' ) ) {
266
			return;
267
		}
268
		delete_site_transient( 'update_plugins' );
269
	}
270
271
	/**
272
	 * Returns active Jetpack plugin file partial path string (jetpack/jetpack.php|jetpack-dev/jetpack.php).
273
	 */
274
	public static function get_plugin_file() {
275
		return self::get_plugin_slug() . '/jetpack.php';
276
	}
277
278
	/**
279
	 * Returns active plugin slug string (jetpack|jetpack-dev).
280
	 */
281 View Code Duplication
	public static function get_plugin_slug() {
282
		$installed = self::get_branch_and_section();
283
		if ( empty( $installed ) || 'stable' === $installed[1] || 'tags' === $installed[1] ) {
284
			return 'jetpack';
285
		}
286
		return JETPACK_DEV_PLUGIN_SLUG;
287
	}
288
289
	/**
290
	 * Handler ran for Jetpack Beta plugin deactivation hook.
291
	 */
292
	public static function deactivate() {
293
		// Don't do anyting funnly.
294
		if ( defined( 'DOING_CRON' ) ) {
295
			return;
296
		}
297
298
		self::clear_autoupdate_cron();
299
		self::delete_all_transiants();
300
		add_action( 'shutdown', array( __CLASS__, 'switch_active' ), 5 );
301
		add_action( 'shutdown', array( __CLASS__, 'remove_dev_plugin' ), 20 );
302
		delete_option( self::$option );
303
	}
304
305
	/**
306
	 * When Jetpack Beta plugin is deactivated, remove the jetpack-dev plugin directory and cleanup.
307
	 */
308
	public static function remove_dev_plugin() {
309
		if ( is_multisite() ) {
310
			return;
311
		}
312
313
		// Delete the jetpack dev plugin.
314
		require_once ABSPATH . 'wp-admin/includes/file.php';
315
		$creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, array() );
316
		if ( ! WP_Filesystem( $creds ) ) {
317
			// Any problems and we exit.
318
			return;
319
		}
320
		global $wp_filesystem;
321
		if ( ! $wp_filesystem ) {
322
			return;
323
		}
324
325
		$working_dir = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . JETPACK_DEV_PLUGIN_SLUG;
326
		// Delete the folder JETPACK_BETA_PLUGIN_FOLDER.
327
		if ( $wp_filesystem->is_dir( $working_dir ) ) {
328
			$wp_filesystem->delete( $working_dir, true );
329
		}
330
		// Since we are removing this dev plugin we should also clean up this data.
331
		delete_option( self::$option_dev_installed );
332
	}
333
334
	/**
335
	 * Builds URL to the admin area for the current site and specified query param.
336
	 *
337
	 * @param string $query - Path relative to the admin URL.
338
	 */
339
	public static function admin_url( $query = '?page=jetpack-beta' ) {
340
		return ( self::is_network_active() )
341
		? network_admin_url( 'admin.php' . $query )
342
		: admin_url( 'admin.php' . $query );
343
	}
344
345
	/**
346
	 * Build the "Jetpack Beta" admin bar menu items.
347
	 */
348
	public function admin_bar_menu() {
349
		global $wp_admin_bar;
350
351
		if ( ! is_object( $wp_admin_bar ) ) {
352
			return;
353
		}
354
355
		// Nothing got activated yet.
356
		if ( ! self::get_option() ) {
357
			return;
358
		}
359
360
		$args = array(
361
			'id'     => 'jetpack-beta_admin_bar',
362
			'title'  => 'Jetpack Beta',
363
			'parent' => 'top-secondary',
364
			'href'   => current_user_can( 'update_plugins' ) ? self::admin_url() : '',
365
		);
366
		$wp_admin_bar->add_node( $args );
367
368
		// Add a child item to our parent item.
369
		$args = array(
370
			'id'     => 'jetpack-beta_version',
371
			// translators: %s: active Jetpack plugin branch/tag.
372
			'title'  => sprintf( __( 'Running %s', 'jetpack-beta' ), self::get_jetpack_plugin_pretty_version() ),
373
			'parent' => 'jetpack-beta_admin_bar',
374
		);
375
376
		$wp_admin_bar->add_node( $args );
377
378
		if ( self::get_plugin_slug() === JETPACK_DEV_PLUGIN_SLUG ) {
379
			// Highlight the menu if you are running the BETA Versions..
380
			echo sprintf( '<style>#wpadminbar #wp-admin-bar-jetpack-beta_admin_bar { background: %s; }</style>', esc_attr( JETPACK_GREEN ) );
381
		}
382
383
		$args = array(
384
			'id'     => 'jetpack-beta_report',
385
			'title'  => __( 'Report Bug', 'jetpack-beta' ),
386
			'href'   => JETPACK_BETA_REPORT_URL,
387
			'parent' => 'jetpack-beta_admin_bar',
388
		);
389
		$wp_admin_bar->add_node( $args );
390
391
		list( $branch, $section ) = self::get_branch_and_section();
392
		if ( 'pr' === $section ) {
393
			$args = array(
394
				'id'     => 'jetpack-beta_report_more_info',
395
				'title'  => __( 'More Info ', 'jetpack-beta' ),
396
				'href'   => self::get_url( $branch, $section ),
397
				'parent' => 'jetpack-beta_admin_bar',
398
			);
399
			$wp_admin_bar->add_node( $args );
400
		}
401
	}
402
403
	/**
404
	 * Filters `update_plugins` transient.
405
	 *
406
	 * @param object $transient - Plugin update data.
407
	 */
408
	public function maybe_plugins_update_transient( $transient ) {
409
		if ( ! isset( $transient->no_update ) ) {
410
			return $transient;
411
		}
412
413
		// Do not try to update things that do not exist.
414
		if ( ! file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . JETPACK_DEV_PLUGIN_FILE ) ) {
415
			return $transient;
416
		}
417
418
		// Do not look for update if we are stable branch.
419
		if ( self::is_on_stable() ) {
420
			return $transient;
421
		}
422
423
		// Lets always grab the latest.
424
		delete_site_transient( 'jetpack_beta_manifest' );
425
426
		// Check if there is a new version.
427
		if ( self::should_update_dev_to_master() ) {
428
			// If response is false, don't alter the transient.
429
			$transient->response[ JETPACK_DEV_PLUGIN_FILE ] = self::get_jepack_dev_master_update_response();
430
			// Unset the that it doesn't need an update.
431
			unset( $transient->no_update[ JETPACK_DEV_PLUGIN_FILE ] );
432
		} elseif ( self::should_update_dev_version() ) {
433
			// If response is false, don't alter the transient.
434
			$transient->response[ JETPACK_DEV_PLUGIN_FILE ] = self::get_jepack_dev_update_response();
435
			// Unset the that it doesn't need an update.
436
			unset( $transient->no_update[ JETPACK_DEV_PLUGIN_FILE ] );
437
		} else {
438
			unset( $transient->response[ JETPACK_DEV_PLUGIN_FILE ] );
439
			if ( isset( $transient->no_update ) ) {
440
				$transient->no_update[ JETPACK_DEV_PLUGIN_FILE ] = self::get_jepack_dev_update_response();
441
			}
442
		}
443
444
		return $transient;
445
	}
446
447
	/**
448
	 * Determine if JP dev version should be updated.
449
	 */
450
	public static function should_update_dev_version() {
451
		return version_compare( self::get_new_jetpack_version( true ), self::get_jetpack_plugin_version( true ), '>' );
452
	}
453
454
	/**
455
	 * Build plugin update data response for dev plugin.
456
	 */
457
	public static function get_jepack_dev_update_response() {
458
		$response              = new stdClass();
459
		$response->id          = JETPACK_DEV_PLUGIN_SLUG;
460
		$response->plugin      = JETPACK_DEV_PLUGIN_SLUG;
461
		$response->new_version = self::get_new_jetpack_version( true );
462
		$response->slug        = JETPACK_DEV_PLUGIN_SLUG;
463
		$response->url         = self::get_url_dev();
464
		$response->package     = self::get_install_url_dev();
465
		return $response;
466
	}
467
468
	/**
469
	 * Build plugin update data response for JP dev master.
470
	 */
471
	public static function get_jepack_dev_master_update_response() {
472
		$response = self::get_jepack_dev_update_response();
473
474
		$master_manifest       = self::get_manifest_data( 'master', 'master' );
475
		$response->new_version = $master_manifest->version;
476
		$response->url         = self::get_url( 'master', 'master' );
477
		$response->package     = $master_manifest->download_url;
478
		return $response;
479
	}
480
481
	/**
482
	 * Moves the newly downloaded folder into jetpack-dev.
483
	 *
484
	 * @param bool  $worked      - Installation response.
485
	 * @param array $hook_extras - Extra args passed to hooked filters.
486
	 * @param array $result      - Installation result data.
487
	 *
488
	 * @return WP_Error
489
	 */
490
	public function upgrader_post_install( $worked, $hook_extras, $result ) {
491
		global $wp_filesystem;
492
493
		if (
494
		! isset( $hook_extras['plugin'] )
495
		|| JETPACK_DEV_PLUGIN_FILE !== $hook_extras['plugin']
496
		) {
497
			return $worked;
498
		}
499
500
		if ( $wp_filesystem->move( $result['destination'], WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . JETPACK_DEV_PLUGIN_SLUG, true ) ) {
501
			return $worked;
502
		} else {
503
			return new WP_Error();
504
		}
505
	}
506
507
	/**
508
	 * Get the active JP or JP Dev plugin version.
509
	 *
510
	 * @param bool $is_dev_version - If dev plugin version is being queried.
511
	 *
512
	 * @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...
513
	 */
514
	public static function get_jetpack_plugin_version( $is_dev_version = false ) {
515
		if ( $is_dev_version ) {
516
			$info = self::get_jetpack_plugin_info_dev();
517
		} else {
518
			$info = self::get_jetpack_plugin_info();
519
		}
520
521
		return isset( $info['Version'] ) ? $info['Version'] : 0;
522
	}
523
524
	/**
525
	 * Get WP Option: jetpack_beta_active
526
	 */
527
	public static function get_option() {
528
		return get_option( self::$option );
529
	}
530
531
	/**
532
	 * Get WP Option: jetpack_beta_dev_currently_installed
533
	 */
534
	public static function get_dev_installed() {
535
		return get_option( self::$option_dev_installed );
536
	}
537
538
	/**
539
	 * Get active Jetpack branch/section.
540
	 */
541
	public static function get_branch_and_section() {
542
		$option = (array) self::get_option();
543
		if ( false === $option[0] ) {
544
			// See if the Jetpack plugin is enabled.
545
			if ( is_plugin_active( JETPACK_PLUGIN_FILE ) ) {
546
				return array( 'stable', 'stable' );
547
			}
548
			return array( false, false );
549
		}
550
		// Branch and section.
551
		return $option;
552
	}
553
554
	/**
555
	 * Check if Jetpack version is 'stable' version.
556
	 */
557 View Code Duplication
	public static function is_on_stable() {
558
		$branch_and_section = self::get_branch_and_section();
559
		if ( empty( $branch_and_section[0] ) || 'stable' === $branch_and_section[0] ) {
560
			return true;
561
		}
562
		return false;
563
	}
564
565
	/**
566
	 * Check if Jetpack active version is a tag version.
567
	 */
568
	public static function is_on_tag() {
569
		$option = (array) self::get_option();
570
		if ( isset( $option[1] ) && 'tags' === $option[1] ) {
571
			return true;
572
		}
573
		return false;
574
	}
575
576
	/**
577
	 * Get active Jetpack Dev branch/section.
578
	 */
579
	public static function get_branch_and_section_dev() {
580
		$option = (array) self::get_dev_installed();
581
		if ( false !== $option[0] && isset( $option[1] ) ) {
582
			return array( $option[0], $option[1] );
583
		}
584
		if ( is_plugin_active( JETPACK_DEV_PLUGIN_FILE ) ) {
585
			return array( 'stable', 'stable' );
586
		}
587
		return array( false, false );
588
	}
589
590
	/**
591
	 * Massage JP plugin version string.
592
	 *
593
	 * @param bool $is_dev_version - If JP Dev version is being queried.
594
	 */
595
	public static function get_jetpack_plugin_pretty_version( $is_dev_version = false ) {
596
		if ( $is_dev_version ) {
597
			list( $branch, $section ) = self::get_branch_and_section_dev();
598
		} else {
599
			list( $branch, $section ) = self::get_branch_and_section();
600
		}
601
602
		if ( ! $section ) {
603
			return '';
604
		}
605
606
		if ( 'master' === $section ) {
607
			return 'Bleeding Edge';
608
		}
609
610
		if ( 'stable' === $section ) {
611
			return 'Latest Stable';
612
		}
613
614
		if ( 'tags' === $section ) {
615
			return sprintf(
616
				// translators: %1$s: a tagged Jetpack plugin version.
617
				__( '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' ),
618
				esc_attr( $branch )
619
			);
620
		}
621
622
		if ( 'rc' === $section ) {
623
			return 'Release Candidate';
624
		}
625
626
		if ( 'pr' === $section ) {
627
			$branch = str_replace( '-', ' ', $branch );
628
			return 'Feature Branch: ' . str_replace( '_', ' / ', $branch );
629
		}
630
631
		return self::get_jetpack_plugin_version();
632
	}
633
634
	/**
635
	 * Fetch latest Jetpack version.
636
	 *
637
	 * @param bool $is_dev_version - If JP Dev version is being queried.
638
	 */
639
	public static function get_new_jetpack_version( $is_dev_version = false ) {
640
		$manifest = self::get_beta_manifest();
641
		if ( $is_dev_version ) {
642
			list( $branch, $section ) = self::get_branch_and_section_dev();
643
		} else {
644
			list( $branch, $section ) = self::get_branch_and_section();
645
		}
646
647
		if ( 'master' === $section && isset( $manifest->{$section}->version ) ) {
648
			return $manifest->{$section}->version;
649
		}
650
651
		if ( 'rc' === $section && isset( $manifest->{$section}->version ) ) {
652
			return $manifest->{$section}->version;
653
		}
654
655
		if ( isset( $manifest->{$section} ) &&
656
		isset( $manifest->{$section}->{$branch} ) &&
657
		isset( $manifest->{$section}->{$branch}->version )
658
		) {
659
			return $manifest->{$section}->{$branch}->version;
660
		}
661
		return 0;
662
	}
663
664
	/**
665
	 * Get JP Dev plugin repo URL.
666
	 */
667
	public static function get_url_dev() {
668
		list( $branch, $section ) = self::get_branch_and_section_dev();
669
		return self::get_url( $branch, $section );
670
	}
671
672
	/**
673
	 * Get JP plugin repo URL.
674
	 *
675
	 * @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...
676
	 * @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...
677
	 */
678
	public static function get_url( $branch = null, $section = null ) {
679
		if ( is_null( $section ) ) {
680
			list( $branch, $section ) = self::get_branch_and_section();
681
		}
682
683
		if ( 'master' === $section ) {
684
			return JETPACK_GITHUB_URL . '/tree/master-build';
685
		}
686
687
		if ( 'rc' === $section ) {
688
			return JETPACK_GITHUB_URL . '/tree/' . $section . '-build';
689
		}
690
691
		if ( 'pr' === $section ) {
692
			$manifest = self::get_beta_manifest();
693
			return isset( $manifest->{$section}->{$branch}->pr )
694
			? JETPACK_GITHUB_URL . '/pull/' . $manifest->{$section}->{$branch}->pr
695
			: JETPACK_DEFAULT_URL;
696
		}
697
		return JETPACK_DEFAULT_URL;
698
	}
699
700
	/**
701
	 * Get install URL for JP dev.
702
	 */
703
	public static function get_install_url_dev() {
704
		list( $branch, $section ) = self::get_branch_and_section_dev();
705
		return self::get_install_url( $branch, $section );
706
	}
707
708
	/**
709
	 * Get install URL for JP.
710
	 *
711
	 * @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...
712
	 * @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...
713
	 */
714
	public static function get_install_url( $branch = null, $section = null ) {
715
		if ( is_null( $section ) ) {
716
			list( $branch, $section ) = self::get_branch_and_section();
717
		}
718
719
		if ( 'stable' === $section ) {
720
			$org_data = self::get_org_data();
721
			return $org_data->download_link;
722
		} elseif ( 'tags' === $section ) {
723
			$org_data = self::get_org_data();
724
			return $org_data->versions->{$branch} ? $org_data->versions->{$branch} : false;
725
		}
726
		$manifest = self::get_beta_manifest( true );
727
728
		if ( 'master' === $section && isset( $manifest->{$section}->download_url ) ) {
729
			return $manifest->{$section}->download_url;
730
		}
731
732
		if ( 'rc' === $section ) {
733
			if ( isset( $manifest->{$section}->download_url ) ) {
734
				return $manifest->{$section}->download_url;
735
			}
736
			$branches = array_keys( (array) $manifest->{$section} );
737
			foreach ( $branches as $branch ) {
738
				if ( isset( $manifest->{$section}->{$branch}->download_url ) ) {
739
					return $manifest->{$section}->{$branch}->download_url;
740
				}
741
			}
742
			return null;
743
		}
744
745
		if ( isset( $manifest->{$section}->{$branch}->download_url ) ) {
746
			return $manifest->{$section}->{$branch}->download_url;
747
		}
748
		return null;
749
	}
750
751
	/**
752
	 * Get stable JP version plugin data.
753
	 */
754
	public static function get_jetpack_plugin_info_stable() {
755
		return self::get_jetpack_plugin_info( JETPACK_PLUGIN_FILE );
756
	}
757
758
	/**
759
	 * Get dev JP version plugin data.
760
	 */
761
	public static function get_jetpack_plugin_info_dev() {
762
		return self::get_jetpack_plugin_info( JETPACK_DEV_PLUGIN_FILE );
763
	}
764
765
	/**
766
	 * Get JP plugin data.
767
	 *
768
	 * @param mixed $plugin_file - JP or JP Dev plugin path.
769
	 */
770
	public static function get_jetpack_plugin_info( $plugin_file = null ) {
771
772
		if ( is_null( $plugin_file ) ) {
773
			$plugin_file = self::get_plugin_file();
774
		}
775
776
		if ( ! function_exists( 'get_plugin_data' ) ) {
777
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
778
		}
779
		$plugin_file_path = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $plugin_file;
780
781
		if ( file_exists( $plugin_file_path ) ) {
782
			return get_plugin_data( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $plugin_file );
783
		}
784
785
		return null;
786
	}
787
788
	/**
789
	 * Switch active JP plugin version when JP Beta plugin is deactivated.
790
	 * This needs to happen on `shutdown`, otherwise it doesn't work.
791
	 */
792
	public static function switch_active() {
793
		self::replace_active_plugin( JETPACK_DEV_PLUGIN_FILE, JETPACK_PLUGIN_FILE );
794
	}
795
796
	/**
797
	 * Fetch the Jetpack beta manifest.
798
	 *
799
	 * @param bool $force_refresh - Whether to bypass cached response.
800
	 */
801
	public static function get_beta_manifest( $force_refresh = false ) {
802
		return self::get_remote_data( JETPACK_BETA_MANIFEST_URL, 'manifest', $force_refresh );
803
	}
804
805
	/**
806
	 * Fetch WordPress.org Jetpack plugin info.
807
	 */
808
	public static function get_org_data() {
809
		return self::get_remote_data( JETPACK_ORG_API_URL, 'org_data' );
810
	}
811
812
	/**
813
	 * Helper function used to fetch remote data from WordPress.org, GitHub, and betadownload.jetpack.me
814
	 *
815
	 * @param string $url       - Url being fetched.
816
	 * @param string $transient - Transient name (manifest|org_data|github_commits_).
817
	 * @param bool   $bypass    - Whether to bypass cached response.
818
	 */
819
	public static function get_remote_data( $url, $transient, $bypass = false ) {
820
		$prefix = 'jetpack_beta_';
821
		$cache  = get_site_transient( $prefix . $transient );
822
		if ( $cache && ! $bypass ) {
823
			return $cache;
824
		}
825
826
		$remote_manifest = wp_remote_get( $url );
827
828
		if ( is_wp_error( $remote_manifest ) ) {
829
			return false;
830
		}
831
832
		$cache = json_decode( wp_remote_retrieve_body( $remote_manifest ) );
833
		set_site_transient( $prefix . $transient, $cache, MINUTE_IN_SECONDS * 15 );
834
835
		return $cache;
836
	}
837
838
	/**
839
	 * Delete set transients when plugin is deactivated.
840
	 */
841
	public static function delete_all_transiants() {
842
		$prefix = 'jetpack_beta_';
843
844
		delete_site_transient( $prefix . 'org_data' );
845
		delete_site_transient( $prefix . 'manifest' );
846
847
		delete_site_transient( Jetpack_Beta_Autoupdate_Self::TRANSIENT_NAME );
848
849
	}
850
851
	/**
852
	 * Install & activate JP for the given branch/section.
853
	 *
854
	 * @param string $branch  - Branch.
855
	 * @param string $section - Section.
856
	 */
857
	public static function install_and_activate( $branch, $section ) {
858
		// Cleanup previous version of the beta plugin.
859
		if ( file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'jetpack-pressable-beta' ) ) {
860
			// Delete the Jetpack dev plugin.
861
			require_once ABSPATH . 'wp-admin/includes/file.php';
862
			$creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, array() );
863
			if ( ! WP_Filesystem( $creds ) ) {
864
				// Any problems and we exit.
865
				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...
866
			}
867
			global $wp_filesystem;
868
			if ( ! $wp_filesystem ) {
869
				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...
870
			}
871
872
			$working_dir = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'jetpack-pressable-beta';
873
			// Delete the folder `JETPACK_BETA_PLUGIN_FOLDER`.
874
			if ( $wp_filesystem->is_dir( $working_dir ) ) {
875
				$wp_filesystem->delete( $working_dir, true );
876
			}
877
			// Deactivate the plugin.
878
			self::replace_active_plugin( 'jetpack-pressable-beta/jetpack.php' );
879
		}
880
881
		self::update_autoload_dev_constant( $section );
882
883 View Code Duplication
		if ( 'stable' === $section &&
884
		file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . JETPACK_PLUGIN_FILE ) ) {
885
			self::replace_active_plugin( JETPACK_DEV_PLUGIN_FILE, JETPACK_PLUGIN_FILE, true );
886
			self::update_option( $branch, $section );
887
			return;
888
		}
889
890 View Code Duplication
		if ( self::get_branch_and_section_dev() === array( $branch, $section )
891
		&& file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . JETPACK_DEV_PLUGIN_FILE ) ) {
892
			self::replace_active_plugin( JETPACK_PLUGIN_FILE, JETPACK_DEV_PLUGIN_FILE, true );
893
			self::update_option( $branch, $section );
894
			return;
895
		}
896
897
		self::proceed_to_install_and_activate(
898
			self::get_install_url( $branch, $section ),
899
			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...
900
			$section
901
		);
902
		self::update_option( $branch, $section );
903
	}
904
905
	/**
906
	 * Update to the latest version.
907
	 *
908
	 * @param string $branch  - Branch.
909
	 * @param string $section - Section.
910
	 */
911
	public static function update_plugin( $branch, $section ) {
912
		self::proceed_to_install(
913
			self::get_install_url( $branch, $section ),
914
			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...
915
			$section
916
		);
917
918 View Code Duplication
		if ( 'stable' !== $section ) {
919
			update_option( self::$option_dev_installed, array( $branch, $section, self::get_manifest_data( $branch, $section ) ) );
920
		}
921
	}
922
923
	/**
924
	 * Helper function to update installed version option.
925
	 *
926
	 * @param string $branch  - Branch.
927
	 * @param string $section - Section.
928
	 */
929
	public static function update_option( $branch, $section ) {
930 View Code Duplication
		if ( 'stable' !== $section ) {
931
			update_option( self::$option_dev_installed, array( $branch, $section, self::get_manifest_data( $branch, $section ) ) );
932
		}
933
		update_option( self::$option, array( $branch, $section ) );
934
	}
935
936
	/**
937
	 * Return manifest info for specififed branch/section.
938
	 *
939
	 * @param string $branch  - Branch.
940
	 * @param string $section - Section.
941
	 */
942
	public static function get_manifest_data( $branch, $section ) {
943
		$installed             = get_option( self::$option_dev_installed );
944
		$current_manifest_data = isset( $installed[2] ) ? $installed[2] : false;
945
946
		$manifest_data = self::get_beta_manifest();
947
948
		if ( ! isset( $manifest_data->{$section} ) ) {
949
			return $current_manifest_data;
950
		}
951
952
		if ( 'master' === $section ) {
953
			return $manifest_data->{$section};
954
		}
955
956
		if ( 'rc' === $section ) {
957
			return $manifest_data->{$section};
958
		}
959
960
		if ( isset( $manifest_data->{$section}->{$branch} ) ) {
961
			return $manifest_data->{$section}->{$branch};
962
		}
963
964
		return $current_manifest_data;
965
	}
966
967
	/**
968
	 * Install specified plugin version.
969
	 *
970
	 * @param string $url           - Url for plugin version.
971
	 * @param string $plugin_folder - Path JP or JP Dev plugin folder.
972
	 * @param string $section       - Section.
973
	 */
974
	public static function proceed_to_install_and_activate( $url, $plugin_folder, $section ) {
975
		self::proceed_to_install( $url, $plugin_folder, $section );
976
977
		if ( 'stable' === $section || 'tags' === $section ) {
978
			self::replace_active_plugin( JETPACK_DEV_PLUGIN_FILE, JETPACK_PLUGIN_FILE, true );
979
		} else {
980
			self::replace_active_plugin( JETPACK_PLUGIN_FILE, JETPACK_DEV_PLUGIN_FILE, true );
981
		}
982
	}
983
984
	/**
985
	 * Download plugin files.
986
	 *
987
	 * @param string $url           - Url for plugin version.
988
	 * @param string $plugin_folder - Path JP or JP Dev plugin folder.
989
	 * @param string $section       - Section.
990
	 */
991
	public static function proceed_to_install( $url, $plugin_folder, $section ) {
992
		$temp_path = download_url( $url );
993
994 View Code Duplication
		if ( is_wp_error( $temp_path ) ) {
995
			// translators: %1$s: download url, %2$s: error message.
996
			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() ) ) );
997
		}
998
		require_once ABSPATH . 'wp-admin/includes/file.php';
999
		$creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, array() );
1000
		/* initialize the API */
1001
		if ( ! WP_Filesystem( $creds ) ) {
1002
			/* any problems and we exit */
1003
			wp_die( esc_html( __( 'Jetpack Beta: No File System access', 'jetpack-beta' ) ) );
1004
		}
1005
1006
		global $wp_filesystem;
1007
		if ( 'stable' === $section || 'tags' === $section ) {
1008
			$plugin_path = WP_PLUGIN_DIR;
1009
		} else {
1010
			$plugin_path = str_replace( ABSPATH, $wp_filesystem->abspath(), WP_PLUGIN_DIR );
1011
		}
1012
1013
		$result = unzip_file( $temp_path, $plugin_path );
1014
1015 View Code Duplication
		if ( is_wp_error( $result ) ) {
1016
			// translators: %1$s: error message.
1017
			wp_die( esc_html( sprintf( __( 'Error Unziping file: Error: %1$s', 'jetpack-beta' ), $result->get_error_message() ) ) );
1018
		}
1019
	}
1020
1021
	/**
1022
	 * Check if plugin is network activated.
1023
	 */
1024
	public static function is_network_active() {
1025
		if ( ! is_multisite() ) {
1026
			return false;
1027
		}
1028
1029
		if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
1030
			return false;
1031
		}
1032
1033
		if ( is_plugin_active_for_network( JETPACK_PLUGIN_FILE ) || is_plugin_active_for_network( JETPACK_DEV_PLUGIN_FILE ) ) {
1034
			return true;
1035
		}
1036
1037
		return false;
1038
	}
1039
1040
	/**
1041
	 * Swap plugin files.
1042
	 *
1043
	 * @param string $current_plugin      - Current plugin path.
1044
	 * @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...
1045
	 * @param bool   $force_activate      - Whether to force activate plguin.
1046
	 */
1047
	public static function replace_active_plugin( $current_plugin, $replace_with_plugin = null, $force_activate = false ) {
1048
		// The autoloader sets the cache in a shutdown hook. Clear it after the autoloader sets it.
1049
		add_action( 'shutdown', array( __CLASS__, 'clear_autoloader_plugin_cache' ), 99 );
1050
1051
		if ( self::is_network_active() ) {
1052
			$new_active_plugins     = array();
1053
			$network_active_plugins = get_site_option( 'active_sitewide_plugins' );
1054
			foreach ( $network_active_plugins as $plugin => $date ) {
1055
				$key                        = ( $plugin === $current_plugin ? $replace_with_plugin : $plugin );
1056
				$new_active_plugins[ $key ] = $date;
1057
			}
1058
			update_site_option( 'active_sitewide_plugins', $new_active_plugins );
1059
			return;
1060
		}
1061
1062
		$active_plugins     = (array) get_option( 'active_plugins', array() );
1063
		$new_active_plugins = array();
1064
1065
		if ( empty( $replace_with_plugin ) ) {
1066
			$new_active_plugins = array_diff( $active_plugins, array( $current_plugin ) );
1067
		} else {
1068
			foreach ( $active_plugins as $plugin ) {
1069
				$new_active_plugins[] = ( $plugin === $current_plugin ? $replace_with_plugin : $plugin );
1070
			}
1071
		}
1072
1073
		if ( $force_activate && ! in_array( $replace_with_plugin, $new_active_plugins, true ) ) {
1074
			$new_active_plugins[] = $replace_with_plugin;
1075
		}
1076
		update_option( 'active_plugins', $new_active_plugins );
1077
	}
1078
1079
	/**
1080
	 * Check if `stable` should be updated.
1081
	 *
1082
	 * @return bool
1083
	 */
1084
	public static function should_update_stable_version() {
1085
		// Pressable Jetpack version is manage via Pressable.
1086
		if ( defined( 'IS_PRESSABLE' ) && IS_PRESSABLE ) {
1087
			return false;
1088
		}
1089
		// Check if we are Jetpack plugin is installed via git.
1090
		if ( file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'jetpack/.git' ) ) {
1091
			return false;
1092
		}
1093
1094
		// Check if running a tag directly from svn.
1095
		if ( self::is_on_tag() ) {
1096
			return false;
1097
		}
1098
1099
		$updates = get_site_transient( 'update_plugins' );
1100
1101
		if ( isset( $updates->response, $updates->response[ JETPACK_PLUGIN_FILE ] ) ) {
1102
			return true;
1103
		}
1104
		$org_data    = self::get_org_data();
1105
		$plugin_data = self::get_jetpack_plugin_info_stable();
1106
1107
		return ( isset( $plugin_data['Version'], $org_data->version )
1108
			&& $org_data->version !== $plugin_data['Version'] );
1109
	}
1110
1111
	/**
1112
	 * Here we are checking if the DEV branch that we are currenly on is not something that is available in the manifest
1113
	 * Meaning that the DEV branch was merged into master and so we need to update it.
1114
	 *
1115
	 * @return bool
1116
	 */
1117
	public static function should_update_dev_to_master() {
1118
		list( $branch, $section ) = self::get_branch_and_section_dev();
1119
1120
		if ( false === $branch || 'master' === $section || 'rc' === $section || 'tags' === $section ) {
1121
			return false;
1122
		}
1123
		$manifest = self::get_beta_manifest();
1124
		return ! isset( $manifest->{$section}->{$branch} );
1125
	}
1126
1127
	/**
1128
	 * Get WP Option: jp_beta_autoupdate
1129
	 */
1130
	public static function is_set_to_autoupdate() {
1131
		return get_option( self::$option_autoupdate, false );
1132
	}
1133
1134
	/**
1135
	 * Get WP Option: jp_beta_email_notifications
1136
	 */
1137
	public static function is_set_to_email_notifications() {
1138
		return get_option( self::$option_email_notif, true );
1139
	}
1140
1141
	/**
1142
	 * Clear scheduled WP-Cron jobs on plugin deactivation.
1143
	 */
1144
	public static function clear_autoupdate_cron() {
1145
		if ( ! is_main_site() ) {
1146
			return;
1147
		}
1148
		wp_clear_scheduled_hook( self::$auto_update_cron_hook );
1149
1150
		if ( function_exists( 'wp_unschedule_hook' ) ) { // New in WP `4.9`.
1151
			wp_unschedule_hook( self::$auto_update_cron_hook );
1152
		}
1153
	}
1154
1155
	/**
1156
	 * Schedule plugin update jobs.
1157
	 */
1158
	public static function schedule_hourly_autoupdate() {
1159
		wp_clear_scheduled_hook( self::$auto_update_cron_hook );
1160
		wp_schedule_event( time(), 'hourly', self::$auto_update_cron_hook );
1161
	}
1162
1163
	/**
1164
	 * Determine if plugin update jobs should be scheduled.
1165
	 */
1166
	public static function maybe_schedule_autoupdate() {
1167
		if ( ! self::is_set_to_autoupdate() ) {
1168
			return;
1169
		}
1170
1171
		if ( ! is_main_site() ) {
1172
			return;
1173
		}
1174
		$has_schedule_already = wp_get_schedule( self::$auto_update_cron_hook );
1175
		if ( ! $has_schedule_already ) {
1176
			self::schedule_hourly_autoupdate();
1177
		}
1178
	}
1179
1180
	/**
1181
	 * Get "What changed" info for display.
1182
	 *
1183
	 * @return string|false
1184
	 */
1185
	public static function what_changed() {
1186
		$commit = self::get_version_commit();
1187
		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...
1188
			$html        = '';
1189
			$commit_data = self::get_commit_data_from_github( $commit );
1190
			if ( isset( $commit_data->commit->message ) ) {
1191
				$html .= sprintf(
1192
					__( "\n %1\$s \n\n[Commit](%2\$s)", 'jetpack-beta' ),
1193
					esc_html( $commit_data->commit->message ),
1194
					esc_url( $commit_data->html_url )
1195
				);
1196
				"\n\n";
1197
			}
1198
			if ( ! empty( $commit_data->files ) ) {
1199
				$html .= "\n\n";
1200
				// translators: %d: number of files changed.
1201
				$html .= sprintf( _n( '%d file changed ', '%d files changed', count( $commit_data->files ), 'jetpack-beta' ) );
1202
				$html .= "\n";
1203
				foreach ( $commit_data->files as $file ) {
1204
					$added_deleted_changed = array();
1205
					if ( $file->additions ) {
1206
						$added_deleted_changed[] = '+' . $file->additions;
1207
					}
1208
					if ( $file->deletions ) {
1209
						$added_deleted_changed[] = '-' . $file->deletions;
1210
					}
1211
					$html .= sprintf( "- %s ... (%s %s) \n", esc_html( $file->filename ), esc_html( $file->status ), implode( ' ', $added_deleted_changed ) );
1212
				}
1213
				$html .= "\n\n";
1214
			}
1215
			if ( ! empty( $html ) ) {
1216
				return $html;
1217
			}
1218
		}
1219
		return false;
1220
	}
1221
1222
	/**
1223
	 * Get version commit if available.
1224
	 *
1225
	 * @return string|false
1226
	 */
1227
	public static function get_version_commit() {
1228
		$split_version = explode( '-', self::get_jetpack_plugin_version() );
1229
		if ( isset( $split_version[3] ) ) {
1230
			return $split_version[3];
1231
		}
1232
		return false;
1233
	}
1234
1235
	/**
1236
	 * Fetch commit data from GitHub.
1237
	 *
1238
	 * @param string $commit - The commit to fetch.
1239
	 */
1240
	public static function get_commit_data_from_github( $commit ) {
1241
		return self::get_remote_data( JETPACK_GITHUB_API_URL . 'commits/' . $commit, 'github_commits_' . $commit );
1242
	}
1243
1244
	/**
1245
	 * The jetpack_beta_autoupdate_hourly_cron job.
1246
	 */
1247
	public static function run_autoupdate() {
1248
		if ( ! self::is_set_to_autoupdate() ) {
1249
			return;
1250
		}
1251
1252
		if ( ! is_main_site() ) {
1253
			return;
1254
		}
1255
1256
		require_once ABSPATH . 'wp-admin/includes/file.php';
1257
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
1258
		require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1259
		wp_clean_plugins_cache();
1260
		ob_start();
1261
		wp_update_plugins(); // Check for Plugin updates.
1262
		ob_end_clean();
1263
		$plugins = array();
1264
		if (
1265
		! self::is_on_stable() &&
1266
		( self::should_update_dev_to_master() || self::should_update_dev_version() )
1267
		) {
1268
			add_filter( 'upgrader_source_selection', array( 'Jetpack_Beta', 'check_for_main_files' ), 10, 2 );
1269
1270
			// If response is false, don't alter the transient.
1271
			$plugins[] = JETPACK_DEV_PLUGIN_FILE;
1272
		}
1273
		$autupdate = Jetpack_Beta_Autoupdate_Self::instance();
1274
		if ( $autupdate->has_never_version() ) {
1275
			$plugins[] = JPBETA__PLUGIN_FOLDER . '/jetpack-beta.php';
1276
		}
1277
1278
		if ( empty( $plugins ) ) {
1279
			return;
1280
		}
1281
1282
		// Unhook this functions that output things before we send our response header.
1283
		remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
1284
		remove_action( 'upgrader_process_complete', 'wp_version_check' );
1285
		remove_action( 'upgrader_process_complete', 'wp_update_themes' );
1286
1287
		$skin = new WP_Ajax_Upgrader_Skin();
1288
		// The Automatic_Upgrader_Skin skin shouldn't output anything.
1289
		$upgrader = new Plugin_Upgrader( $skin );
1290
		$upgrader->init();
1291
		// This avoids the plugin to be deactivated.
1292
		// Using bulk upgrade puts the site into maintenance mode during the upgrades.
1293
		$result = $upgrader->bulk_upgrade( $plugins );
1294
		$errors = $upgrader->skin->get_errors();
1295
		$log    = $upgrader->skin->get_upgrade_messages();
1296
1297
		if ( is_wp_error( $errors ) && $errors->get_error_code() ) {
1298
			return $errors;
1299
		}
1300
1301
		if ( $result && ! defined( 'JETPACK_BETA_SKIP_EMAIL' ) && self::is_set_to_email_notifications() ) {
1302
			self::send_autoupdate_email( $plugins, $log );
1303
		}
1304
	}
1305
1306
	/**
1307
	 * Builds and sends an email about succesfull plugin autoupdate.
1308
	 *
1309
	 * @param Array  $plugins - List of plugins that were updated.
1310
	 * @param String $log     - Upgrade message from core's plugin upgrader.
1311
	 */
1312
	private static function send_autoupdate_email( $plugins, $log ) {
1313
		$admin_email = get_site_option( 'admin_email' );
1314
1315
		if ( empty( $admin_email ) ) {
1316
			return;
1317
		}
1318
1319
		// In case the code is called in a scope different from wp-admin.
1320
		require_once JPBETA__PLUGIN_DIR . 'class-jetpack-beta-admin.php';
1321
1322
		// Calling empty() on a function return value crashes in PHP < 5.5.
1323
		// Thus we assign the return value explicitly and then check with empty().
1324
		$bloginfo_name = get_bloginfo( 'name' );
1325
		$site_title    = ! empty( $bloginfo_name ) ? get_bloginfo( 'name' ) : get_site_url();
1326
		$what_updated  = 'Jetpack Beta Tester Plugin';
1327
		// translators: %s: The site title.
1328
		$subject = sprintf( __( '[%s] Autoupdated Jetpack Beta Tester', 'jetpack-beta' ), $site_title );
1329
1330
		if ( in_array( JETPACK_DEV_PLUGIN_FILE, $plugins, true ) ) {
1331
			$subject = sprintf(
1332
				// translators: %1$s: site title, %2$s: pretty plugin version (eg 9.3).
1333
				__( '[%1$s] Autoupdated Jetpack %2$s ', 'jetpack-beta' ),
1334
				$site_title,
1335
				self::get_jetpack_plugin_pretty_version()
1336
			);
1337
1338
			$what_updated = sprintf(
1339
				// translators: $1$s: pretty plugin version, $2$s: raw plugin version (eg 9.3.2-beta).
1340
				__( 'Jetpack %1$s (%2$s)', 'jetpack-beta' ),
1341
				self::get_jetpack_plugin_pretty_version(),
1342
				self::get_jetpack_plugin_version()
1343
			);
1344
1345
			if ( count( $plugins ) > 1 ) {
1346
				$subject = sprintf(
1347
					// translators: %1$s: site title, %2$s: pretty plugin version.
1348
					__( '[%1$s] Autoupdated Jetpack %2$s and the Jetpack Beta Tester', 'jetpack-beta' ),
1349
					$site_title,
1350
					self::get_jetpack_plugin_pretty_version()
1351
				);
1352
1353
				$what_updated = sprintf(
1354
					// translators: $1$s: pretty plugin version, $2$s: raw plugin version.
1355
					__( 'Jetpack %1$s (%2$s) and the Jetpack Beta Tester', 'jetpack-beta' ),
1356
					self::get_jetpack_plugin_pretty_version(),
1357
					self::get_jetpack_plugin_version()
1358
				);
1359
			}
1360
		}
1361
1362
		$message = sprintf(
1363
			// translators: %1$s: site url, $2$s: text of what has updated.
1364
			__( 'Howdy! Your site at %1$s has autoupdated %2$s.', 'jetpack-beta' ),
1365
			home_url(),
1366
			$what_updated
1367
		);
1368
		$message .= "\n\n";
1369
1370
		$what_changed = self::what_changed();
1371
		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...
1372
			$message .= __( 'What changed?', 'jetpack-beta' );
1373
			$message .= wp_strip_all_tags( $what_changed );
1374
		}
1375
1376
		$message .= __( 'During the autoupdate the following happened:', 'jetpack-beta' );
1377
		$message .= "\n\n";
1378
		// Can only reference the About screen if their update was successful.
1379
		$log      = array_map( 'html_entity_decode', $log );
1380
		$message .= ' - ' . implode( "\n - ", $log );
1381
		$message .= "\n\n";
1382
1383
		// Adds To test section. for PR's it's a PR description, for master/RC - it's a to_test.md file contents.
1384
		$message .= Jetpack_Beta_Admin::to_test_content();
1385
		$message .= "\n\n";
1386
1387
		wp_mail( $admin_email, $subject, $message );
1388
	}
1389
1390
	/**
1391
	 * This checks intends to fix errors in our build server when Jetpack.
1392
	 *
1393
	 * @param string $source        - Source path.
1394
	 * @param string $remote_source - Remote path.
1395
	 *
1396
	 * @return WP_Error
1397
	 */
1398
	public static function check_for_main_files( $source, $remote_source ) {
1399
		if ( $source === $remote_source . '/jetpack-dev/' ) {
1400
			if ( ! file_exists( $source . 'jetpack.php' ) ) {
1401
				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...
1402
			}
1403
			if ( ! file_exists( $source . '_inc/build/static.html' ) ) {
1404
				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...
1405
			}
1406
			if ( ! file_exists( $source . '_inc/build/admin.js' ) ) {
1407
				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...
1408
			}
1409
			// It has happened that sometimes a generated bundle from the master branch ends up with an empty
1410
			// vendor directory. Used to be a problem in the beta building process.
1411
			if ( self::is_dir_empty( $source . 'vendor' ) ) {
1412
				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...
1413
			}
1414
		}
1415
1416
		return $source;
1417
	}
1418
1419
	/**
1420
	 * Checks if a dir is empty.
1421
	 *
1422
	 * @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...
1423
	 * @return boolean
1424
	 */
1425
	public static function is_dir_empty( $dir ) {
1426
		return ( count( scandir( $dir ) ) === 2 );
1427
	}
1428
1429
	/**
1430
	 * Callback function to include Jetpack beta options into Jetpack sync whitelist.
1431
	 *
1432
	 * @param Array $whitelist List of whitelisted options to sync.
1433
	 */
1434
	public function add_to_options_whitelist( $whitelist ) {
1435
		$whitelist[] = self::$option;
1436
		$whitelist[] = self::$option_dev_installed;
1437
		$whitelist[] = self::$option_autoupdate;
1438
		$whitelist[] = self::$option_email_notif;
1439
		return $whitelist;
1440
	}
1441
1442
	/**
1443
	 * Custom error handler to intercept errors and log them using Jetpack's own logger.
1444
	 *
1445
	 * @param int    $errno   - Error code.
1446
	 * @param string $errstr  - Error message.
1447
	 * @param string $errfile - File name where the error happened.
1448
	 * @param int    $errline - Line in the code.
1449
	 *
1450
	 * @return bool Whether to make the default handler handle the error as well.
1451
	 */
1452
	public static function custom_error_handler( $errno, $errstr, $errfile, $errline ) {
1453
1454
		if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'log' ) ) {
1455
			$error_string = sprintf( '%s, %s:%d', $errstr, $errfile, $errline );
1456
1457
			// Only adding to log if the message is related to Jetpack.
1458
			if ( false !== stripos( $error_string, 'jetpack' ) ) {
1459
				Jetpack::log( $errno, $error_string );
1460
			}
1461
		}
1462
1463
		/**
1464
		 * The error_reporting call returns current error reporting level as an integer. Bitwise
1465
		 * AND lets us determine whether the current error is included in the current error
1466
		 * reporting level
1467
		 */
1468
		if ( ! ( error_reporting() & $errno ) ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting,WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting
1469
1470
			// If this error is not being reported in the current settings, stop reporting here by returning true.
1471
			return true;
1472
		}
1473
1474
		// Returning false makes the error go through the standard error handler as well.
1475
		return false;
1476
	}
1477
1478
	/**
1479
	 * Clears the autoloader transient.
1480
	 */
1481
	public static function clear_autoloader_plugin_cache() {
1482
		delete_transient( 'jetpack_autoloader_plugin_paths' );
1483
	}
1484
1485
	/**
1486
	 * Sets the 'jetpack_autoload_dev' option when a Jetpack version is activated:
1487
	 *   - Sets the option to true when a development version of Jetpack is activated.
1488
	 *   - Sets the option to false when the stable or release candidate version is  activated.
1489
	 *
1490
	 * This option is used to set the JETPACK_AUTOLOAD_DEV constant in jetpack-beta.php. The constant
1491
	 * is used by the Jetpack autoloader to determine whether stable or development versions of
1492
	 * packages should be preferred.
1493
	 *
1494
	 * @param string $section The section value for the activating Jetpack version.
1495
	 */
1496
	public static function update_autoload_dev_constant( $section ) {
1497
		if ( in_array( $section, array( 'stable', 'rc' ), true ) ) {
1498
				// The stable and rc versions use stable package versions.
1499
				update_option( 'jetpack_autoload_dev', false );
1500
		} else {
1501
				update_option( 'jetpack_autoload_dev', true );
1502
		}
1503
	}
1504
}
1505