Completed
Push — add/jetpack-assistant-ui ( a6f776...33ce41 )
by Jeremy
202:03 queued 191:10
created

Jetpack_Beta::is_network_active()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 4
nop 0
dl 0
loc 15
rs 9.4555
c 0
b 0
f 0
1
<?php
2
/**
3
 * Primary class file for the Jetpack Beta plugin.
4
 *
5
 * @package 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 View Code Duplication
		if ( 'stable' === $section &&
882
		file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . JETPACK_PLUGIN_FILE ) ) {
883
			self::replace_active_plugin( JETPACK_DEV_PLUGIN_FILE, JETPACK_PLUGIN_FILE, true );
884
			self::update_option( $branch, $section );
885
			return;
886
		}
887
888 View Code Duplication
		if ( self::get_branch_and_section_dev() === array( $branch, $section )
889
		&& file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . JETPACK_DEV_PLUGIN_FILE ) ) {
890
			self::replace_active_plugin( JETPACK_PLUGIN_FILE, JETPACK_DEV_PLUGIN_FILE, true );
891
			self::update_option( $branch, $section );
892
			return;
893
		}
894
895
		self::proceed_to_install_and_activate(
896
			self::get_install_url( $branch, $section ),
897
			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...
898
			$section
899
		);
900
		self::update_option( $branch, $section );
901
	}
902
903
	/**
904
	 * Update to the latest version.
905
	 *
906
	 * @param string $branch  - Branch.
907
	 * @param string $section - Section.
908
	 */
909
	public static function update_plugin( $branch, $section ) {
910
		self::proceed_to_install(
911
			self::get_install_url( $branch, $section ),
912
			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...
913
			$section
914
		);
915
916 View Code Duplication
		if ( 'stable' !== $section ) {
917
			update_option( self::$option_dev_installed, array( $branch, $section, self::get_manifest_data( $branch, $section ) ) );
918
		}
919
	}
920
921
	/**
922
	 * Helper function to update installed version option.
923
	 *
924
	 * @param string $branch  - Branch.
925
	 * @param string $section - Section.
926
	 */
927
	public static function update_option( $branch, $section ) {
928 View Code Duplication
		if ( 'stable' !== $section ) {
929
			update_option( self::$option_dev_installed, array( $branch, $section, self::get_manifest_data( $branch, $section ) ) );
930
		}
931
		update_option( self::$option, array( $branch, $section ) );
932
	}
933
934
	/**
935
	 * Return manifest info for specififed branch/section.
936
	 *
937
	 * @param string $branch  - Branch.
938
	 * @param string $section - Section.
939
	 */
940
	public static function get_manifest_data( $branch, $section ) {
941
		$installed             = get_option( self::$option_dev_installed );
942
		$current_manifest_data = isset( $installed[2] ) ? $installed[2] : false;
943
944
		$manifest_data = self::get_beta_manifest();
945
946
		if ( ! isset( $manifest_data->{$section} ) ) {
947
			return $current_manifest_data;
948
		}
949
950
		if ( 'master' === $section ) {
951
			return $manifest_data->{$section};
952
		}
953
954
		if ( 'rc' === $section ) {
955
			return $manifest_data->{$section};
956
		}
957
958
		if ( isset( $manifest_data->{$section}->{$branch} ) ) {
959
			return $manifest_data->{$section}->{$branch};
960
		}
961
962
		return $current_manifest_data;
963
	}
964
965
	/**
966
	 * Install specified plugin version.
967
	 *
968
	 * @param string $url           - Url for plugin version.
969
	 * @param string $plugin_folder - Path JP or JP Dev plugin folder.
970
	 * @param string $section       - Section.
971
	 */
972
	public static function proceed_to_install_and_activate( $url, $plugin_folder, $section ) {
973
		self::proceed_to_install( $url, $plugin_folder, $section );
974
975
		if ( 'stable' === $section || 'tags' === $section ) {
976
			self::replace_active_plugin( JETPACK_DEV_PLUGIN_FILE, JETPACK_PLUGIN_FILE, true );
977
		} else {
978
			self::replace_active_plugin( JETPACK_PLUGIN_FILE, JETPACK_DEV_PLUGIN_FILE, true );
979
		}
980
	}
981
982
	/**
983
	 * Download plugin files.
984
	 *
985
	 * @param string $url           - Url for plugin version.
986
	 * @param string $plugin_folder - Path JP or JP Dev plugin folder.
987
	 * @param string $section       - Section.
988
	 */
989
	public static function proceed_to_install( $url, $plugin_folder, $section ) {
990
		$temp_path = download_url( $url );
991
992 View Code Duplication
		if ( is_wp_error( $temp_path ) ) {
993
			// translators: %1$s: download url, %2$s: error message.
994
			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() ) ) );
995
		}
996
		require_once ABSPATH . 'wp-admin/includes/file.php';
997
		$creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, array() );
998
		/* initialize the API */
999
		if ( ! WP_Filesystem( $creds ) ) {
1000
			/* any problems and we exit */
1001
			wp_die( esc_html( __( 'Jetpack Beta: No File System access', 'jetpack-beta' ) ) );
1002
		}
1003
1004
		global $wp_filesystem;
1005
		if ( 'stable' === $section || 'tags' === $section ) {
1006
			$plugin_path = WP_PLUGIN_DIR;
1007
		} else {
1008
			$plugin_path = str_replace( ABSPATH, $wp_filesystem->abspath(), WP_PLUGIN_DIR );
1009
		}
1010
1011
		$result = unzip_file( $temp_path, $plugin_path );
1012
1013 View Code Duplication
		if ( is_wp_error( $result ) ) {
1014
			// translators: %1$s: error message.
1015
			wp_die( esc_html( sprintf( __( 'Error Unziping file: Error: %1$s', 'jetpack-beta' ), $result->get_error_message() ) ) );
1016
		}
1017
	}
1018
1019
	/**
1020
	 * Check if plugin is network activated.
1021
	 */
1022
	public static function is_network_active() {
1023
		if ( ! is_multisite() ) {
1024
			return false;
1025
		}
1026
1027
		if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
1028
			return false;
1029
		}
1030
1031
		if ( is_plugin_active_for_network( JETPACK_PLUGIN_FILE ) || is_plugin_active_for_network( JETPACK_DEV_PLUGIN_FILE ) ) {
1032
			return true;
1033
		}
1034
1035
		return false;
1036
	}
1037
1038
	/**
1039
	 * Swap plugin files.
1040
	 *
1041
	 * @param string $current_plugin      - Current plugin path.
1042
	 * @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...
1043
	 * @param bool   $force_activate      - Whether to force activate plguin.
1044
	 */
1045
	public static function replace_active_plugin( $current_plugin, $replace_with_plugin = null, $force_activate = false ) {
1046
		// The autoloader sets the cache in a shutdown hook. Clear it after the autoloader sets it.
1047
		add_action( 'shutdown', array( __CLASS__, 'clear_autoloader_plugin_cache' ), 99 );
1048
1049
		if ( self::is_network_active() ) {
1050
			$new_active_plugins     = array();
1051
			$network_active_plugins = get_site_option( 'active_sitewide_plugins' );
1052
			foreach ( $network_active_plugins as $plugin => $date ) {
1053
				$key                        = ( $plugin === $current_plugin ? $replace_with_plugin : $plugin );
1054
				$new_active_plugins[ $key ] = $date;
1055
			}
1056
			update_site_option( 'active_sitewide_plugins', $new_active_plugins );
1057
			return;
1058
		}
1059
1060
		$active_plugins     = (array) get_option( 'active_plugins', array() );
1061
		$new_active_plugins = array();
1062
1063
		if ( empty( $replace_with_plugin ) ) {
1064
			$new_active_plugins = array_diff( $active_plugins, array( $current_plugin ) );
1065
		} else {
1066
			foreach ( $active_plugins as $plugin ) {
1067
				$new_active_plugins[] = ( $plugin === $current_plugin ? $replace_with_plugin : $plugin );
1068
			}
1069
		}
1070
1071
		if ( $force_activate && ! in_array( $replace_with_plugin, $new_active_plugins, true ) ) {
1072
			$new_active_plugins[] = $replace_with_plugin;
1073
		}
1074
		update_option( 'active_plugins', $new_active_plugins );
1075
	}
1076
1077
	/**
1078
	 * Check if `stable` should be updated.
1079
	 *
1080
	 * @return bool
1081
	 */
1082
	public static function should_update_stable_version() {
1083
		// Pressable Jetpack version is manage via Pressable.
1084
		if ( defined( 'IS_PRESSABLE' ) && IS_PRESSABLE ) {
1085
			return false;
1086
		}
1087
		// Check if we are Jetpack plugin is installed via git.
1088
		if ( file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'jetpack/.git' ) ) {
1089
			return false;
1090
		}
1091
1092
		// Check if running a tag directly from svn.
1093
		if ( self::is_on_tag() ) {
1094
			return false;
1095
		}
1096
1097
		$updates = get_site_transient( 'update_plugins' );
1098
1099
		if ( isset( $updates->response, $updates->response[ JETPACK_PLUGIN_FILE ] ) ) {
1100
			return true;
1101
		}
1102
		$org_data    = self::get_org_data();
1103
		$plugin_data = self::get_jetpack_plugin_info_stable();
1104
1105
		return ( isset( $plugin_data['Version'], $org_data->version )
1106
			&& $org_data->version !== $plugin_data['Version'] );
1107
	}
1108
1109
	/**
1110
	 * Here we are checking if the DEV branch that we are currenly on is not something that is available in the manifest
1111
	 * Meaning that the DEV branch was merged into master and so we need to update it.
1112
	 *
1113
	 * @return bool
1114
	 */
1115
	public static function should_update_dev_to_master() {
1116
		list( $branch, $section ) = self::get_branch_and_section_dev();
1117
1118
		if ( false === $branch || 'master' === $section || 'rc' === $section || 'tags' === $section ) {
1119
			return false;
1120
		}
1121
		$manifest = self::get_beta_manifest();
1122
		return ! isset( $manifest->{$section}->{$branch} );
1123
	}
1124
1125
	/**
1126
	 * Get WP Option: jp_beta_autoupdate
1127
	 */
1128
	public static function is_set_to_autoupdate() {
1129
		return get_option( self::$option_autoupdate, false );
1130
	}
1131
1132
	/**
1133
	 * Get WP Option: jp_beta_email_notifications
1134
	 */
1135
	public static function is_set_to_email_notifications() {
1136
		return get_option( self::$option_email_notif, true );
1137
	}
1138
1139
	/**
1140
	 * Clear scheduled WP-Cron jobs on plugin deactivation.
1141
	 */
1142
	public static function clear_autoupdate_cron() {
1143
		if ( ! is_main_site() ) {
1144
			return;
1145
		}
1146
		wp_clear_scheduled_hook( self::$auto_update_cron_hook );
1147
1148
		if ( function_exists( 'wp_unschedule_hook' ) ) { // New in WP `4.9`.
1149
			wp_unschedule_hook( self::$auto_update_cron_hook );
1150
		}
1151
	}
1152
1153
	/**
1154
	 * Schedule plugin update jobs.
1155
	 */
1156
	public static function schedule_hourly_autoupdate() {
1157
		wp_clear_scheduled_hook( self::$auto_update_cron_hook );
1158
		wp_schedule_event( time(), 'hourly', self::$auto_update_cron_hook );
1159
	}
1160
1161
	/**
1162
	 * Determine if plugin update jobs should be scheduled.
1163
	 */
1164
	public static function maybe_schedule_autoupdate() {
1165
		if ( ! self::is_set_to_autoupdate() ) {
1166
			return;
1167
		}
1168
1169
		if ( ! is_main_site() ) {
1170
			return;
1171
		}
1172
		$has_schedule_already = wp_get_schedule( self::$auto_update_cron_hook );
1173
		if ( ! $has_schedule_already ) {
1174
			self::schedule_hourly_autoupdate();
1175
		}
1176
	}
1177
1178
	/**
1179
	 * Get "What changed" info for display.
1180
	 *
1181
	 * @return string|false
1182
	 */
1183
	public static function what_changed() {
1184
		$commit = self::get_version_commit();
1185
		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...
1186
			$html        = '';
1187
			$commit_data = self::get_commit_data_from_github( $commit );
1188
			if ( isset( $commit_data->commit->message ) ) {
1189
				$html .= sprintf(
1190
					__( "\n %1\$s \n\n[Commit](%2\$s)", 'jetpack-beta' ),
1191
					esc_html( $commit_data->commit->message ),
1192
					esc_url( $commit_data->html_url )
1193
				);
1194
				"\n\n";
1195
			}
1196
			if ( ! empty( $commit_data->files ) ) {
1197
				$html .= "\n\n";
1198
				// translators: %d: number of files changed.
1199
				$html .= sprintf( _n( '%d file changed ', '%d files changed', count( $commit_data->files ), 'jetpack-beta' ) );
1200
				$html .= "\n";
1201
				foreach ( $commit_data->files as $file ) {
1202
					$added_deleted_changed = array();
1203
					if ( $file->additions ) {
1204
						$added_deleted_changed[] = '+' . $file->additions;
1205
					}
1206
					if ( $file->deletions ) {
1207
						$added_deleted_changed[] = '-' . $file->deletions;
1208
					}
1209
					$html .= sprintf( "- %s ... (%s %s) \n", esc_html( $file->filename ), esc_html( $file->status ), implode( ' ', $added_deleted_changed ) );
1210
				}
1211
				$html .= "\n\n";
1212
			}
1213
			if ( ! empty( $html ) ) {
1214
				return $html;
1215
			}
1216
		}
1217
		return false;
1218
	}
1219
1220
	/**
1221
	 * Get version commit if available.
1222
	 *
1223
	 * @return string|false
1224
	 */
1225
	public static function get_version_commit() {
1226
		$split_version = explode( '-', self::get_jetpack_plugin_version() );
1227
		if ( isset( $split_version[3] ) ) {
1228
			return $split_version[3];
1229
		}
1230
		return false;
1231
	}
1232
1233
	/**
1234
	 * Fetch commit data from GitHub.
1235
	 *
1236
	 * @param string $commit - The commit to fetch.
1237
	 */
1238
	public static function get_commit_data_from_github( $commit ) {
1239
		return self::get_remote_data( JETPACK_GITHUB_API_URL . 'commits/' . $commit, 'github_commits_' . $commit );
1240
	}
1241
1242
	/**
1243
	 * The jetpack_beta_autoupdate_hourly_cron job.
1244
	 */
1245
	public static function run_autoupdate() {
1246
		if ( ! self::is_set_to_autoupdate() ) {
1247
			return;
1248
		}
1249
1250
		if ( ! is_main_site() ) {
1251
			return;
1252
		}
1253
1254
		require_once ABSPATH . 'wp-admin/includes/file.php';
1255
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
1256
		require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1257
		wp_clean_plugins_cache();
1258
		ob_start();
1259
		wp_update_plugins(); // Check for Plugin updates.
1260
		ob_end_clean();
1261
		$plugins = array();
1262
		if (
1263
		! self::is_on_stable() &&
1264
		( self::should_update_dev_to_master() || self::should_update_dev_version() )
1265
		) {
1266
			add_filter( 'upgrader_source_selection', array( 'Jetpack_Beta', 'check_for_main_files' ), 10, 2 );
1267
1268
			// If response is false, don't alter the transient.
1269
			$plugins[] = JETPACK_DEV_PLUGIN_FILE;
1270
		}
1271
		$autupdate = Jetpack_Beta_Autoupdate_Self::instance();
1272
		if ( $autupdate->has_never_version() ) {
1273
			$plugins[] = JPBETA__PLUGIN_FOLDER . '/jetpack-beta.php';
1274
		}
1275
1276
		if ( empty( $plugins ) ) {
1277
			return;
1278
		}
1279
1280
		// Unhook this functions that output things before we send our response header.
1281
		remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
1282
		remove_action( 'upgrader_process_complete', 'wp_version_check' );
1283
		remove_action( 'upgrader_process_complete', 'wp_update_themes' );
1284
1285
		$skin = new WP_Ajax_Upgrader_Skin();
1286
		// The Automatic_Upgrader_Skin skin shouldn't output anything.
1287
		$upgrader = new Plugin_Upgrader( $skin );
1288
		$upgrader->init();
1289
		// This avoids the plugin to be deactivated.
1290
		// Using bulk upgrade puts the site into maintenance mode during the upgrades.
1291
		$result = $upgrader->bulk_upgrade( $plugins );
1292
		$errors = $upgrader->skin->get_errors();
1293
		$log    = $upgrader->skin->get_upgrade_messages();
1294
1295
		if ( is_wp_error( $errors ) && $errors->get_error_code() ) {
1296
			return $errors;
1297
		}
1298
1299
		if ( $result && ! defined( 'JETPACK_BETA_SKIP_EMAIL' ) && self::is_set_to_email_notifications() ) {
1300
			self::send_autoupdate_email( $plugins, $log );
1301
		}
1302
	}
1303
1304
	/**
1305
	 * Builds and sends an email about succesfull plugin autoupdate.
1306
	 *
1307
	 * @param Array  $plugins - List of plugins that were updated.
1308
	 * @param String $log     - Upgrade message from core's plugin upgrader.
1309
	 */
1310
	private static function send_autoupdate_email( $plugins, $log ) {
1311
		$admin_email = get_site_option( 'admin_email' );
1312
1313
		if ( empty( $admin_email ) ) {
1314
			return;
1315
		}
1316
1317
		// In case the code is called in a scope different from wp-admin.
1318
		require_once JPBETA__PLUGIN_DIR . 'class-jetpack-beta-admin.php';
1319
1320
		// Calling empty() on a function return value crashes in PHP < 5.5.
1321
		// Thus we assign the return value explicitly and then check with empty().
1322
		$bloginfo_name = get_bloginfo( 'name' );
1323
		$site_title    = ! empty( $bloginfo_name ) ? get_bloginfo( 'name' ) : get_site_url();
1324
		$what_updated  = 'Jetpack Beta Tester Plugin';
1325
		// translators: %s: The site title.
1326
		$subject = sprintf( __( '[%s] Autoupdated Jetpack Beta Tester', 'jetpack-beta' ), $site_title );
1327
1328
		if ( in_array( JETPACK_DEV_PLUGIN_FILE, $plugins, true ) ) {
1329
			$subject = sprintf(
1330
				// translators: %1$s: site title, %2$s: pretty plugin version (eg 9.3).
1331
				__( '[%1$s] Autoupdated Jetpack %2$s ', 'jetpack-beta' ),
1332
				$site_title,
1333
				self::get_jetpack_plugin_pretty_version()
1334
			);
1335
1336
			$what_updated = sprintf(
1337
				// translators: $1$s: pretty plugin version, $2$s: raw plugin version (eg 9.3.2-beta).
1338
				__( 'Jetpack %1$s (%2$s)', 'jetpack-beta' ),
1339
				self::get_jetpack_plugin_pretty_version(),
1340
				self::get_jetpack_plugin_version()
1341
			);
1342
1343
			if ( count( $plugins ) > 1 ) {
1344
				$subject = sprintf(
1345
					// translators: %1$s: site title, %2$s: pretty plugin version.
1346
					__( '[%1$s] Autoupdated Jetpack %2$s and the Jetpack Beta Tester', '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.
1353
					__( 'Jetpack %1$s (%2$s) and the Jetpack Beta Tester', 'jetpack-beta' ),
1354
					self::get_jetpack_plugin_pretty_version(),
1355
					self::get_jetpack_plugin_version()
1356
				);
1357
			}
1358
		}
1359
1360
		$message = sprintf(
1361
			// translators: %1$s: site url, $2$s: text of what has updated.
1362
			__( 'Howdy! Your site at %1$s has autoupdated %2$s.', 'jetpack-beta' ),
1363
			home_url(),
1364
			$what_updated
1365
		);
1366
		$message .= "\n\n";
1367
1368
		$what_changed = self::what_changed();
1369
		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...
1370
			$message .= __( 'What changed?', 'jetpack-beta' );
1371
			$message .= wp_strip_all_tags( $what_changed );
1372
		}
1373
1374
		$message .= __( 'During the autoupdate the following happened:', 'jetpack-beta' );
1375
		$message .= "\n\n";
1376
		// Can only reference the About screen if their update was successful.
1377
		$log      = array_map( 'html_entity_decode', $log );
1378
		$message .= ' - ' . implode( "\n - ", $log );
1379
		$message .= "\n\n";
1380
1381
		// Adds To test section. for PR's it's a PR description, for master/RC - it's a to_test.md file contents.
1382
		$message .= Jetpack_Beta_Admin::to_test_content();
1383
		$message .= "\n\n";
1384
1385
		wp_mail( $admin_email, $subject, $message );
1386
	}
1387
1388
	/**
1389
	 * This checks intends to fix errors in our build server when Jetpack.
1390
	 *
1391
	 * @param string $source        - Source path.
1392
	 * @param string $remote_source - Remote path.
1393
	 *
1394
	 * @return WP_Error
1395
	 */
1396
	public static function check_for_main_files( $source, $remote_source ) {
1397
		if ( $source === $remote_source . '/jetpack-dev/' ) {
1398
			if ( ! file_exists( $source . 'jetpack.php' ) ) {
1399
				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...
1400
			}
1401
			if ( ! file_exists( $source . '_inc/build/static.html' ) ) {
1402
				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...
1403
			}
1404
			if ( ! file_exists( $source . '_inc/build/admin.js' ) ) {
1405
				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...
1406
			}
1407
			// It has happened that sometimes a generated bundle from the master branch ends up with an empty
1408
			// vendor directory. Used to be a problem in the beta building process.
1409
			if ( self::is_dir_empty( $source . 'vendor' ) ) {
1410
				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...
1411
			}
1412
		}
1413
1414
		return $source;
1415
	}
1416
1417
	/**
1418
	 * Checks if a dir is empty.
1419
	 *
1420
	 * @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...
1421
	 * @return boolean
1422
	 */
1423
	public static function is_dir_empty( $dir ) {
1424
		return ( count( scandir( $dir ) ) === 2 );
1425
	}
1426
1427
	/**
1428
	 * Callback function to include Jetpack beta options into Jetpack sync whitelist.
1429
	 *
1430
	 * @param Array $whitelist List of whitelisted options to sync.
1431
	 */
1432
	public function add_to_options_whitelist( $whitelist ) {
1433
		$whitelist[] = self::$option;
1434
		$whitelist[] = self::$option_dev_installed;
1435
		$whitelist[] = self::$option_autoupdate;
1436
		$whitelist[] = self::$option_email_notif;
1437
		return $whitelist;
1438
	}
1439
1440
	/**
1441
	 * Custom error handler to intercept errors and log them using Jetpack's own logger.
1442
	 *
1443
	 * @param int    $errno   - Error code.
1444
	 * @param string $errstr  - Error message.
1445
	 * @param string $errfile - File name where the error happened.
1446
	 * @param int    $errline - Line in the code.
1447
	 *
1448
	 * @return bool Whether to make the default handler handle the error as well.
1449
	 */
1450
	public static function custom_error_handler( $errno, $errstr, $errfile, $errline ) {
1451
1452
		if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'log' ) ) {
1453
			$error_string = sprintf( '%s, %s:%d', $errstr, $errfile, $errline );
1454
1455
			// Only adding to log if the message is related to Jetpack.
1456
			if ( false !== stripos( $error_string, 'jetpack' ) ) {
1457
				Jetpack::log( $errno, $error_string );
1458
			}
1459
		}
1460
1461
		/**
1462
		 * The error_reporting call returns current error reporting level as an integer. Bitwise
1463
		 * AND lets us determine whether the current error is included in the current error
1464
		 * reporting level
1465
		 */
1466
		if ( ! ( error_reporting() & $errno ) ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting,WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting
1467
1468
			// If this error is not being reported in the current settings, stop reporting here by returning true.
1469
			return true;
1470
		}
1471
1472
		// Returning false makes the error go through the standard error handler as well.
1473
		return false;
1474
	}
1475
1476
	/**
1477
	 * Clears the autoloader transient.
1478
	 */
1479
	public static function clear_autoloader_plugin_cache() {
1480
		delete_transient( 'jetpack_autoloader_plugin_paths' );
1481
	}
1482
}
1483