Completed
Push — update/beta_remove_autoload_de... ( f76915 )
by
unknown
26:38 queued 16:10
created

Jetpack_Beta::update_autoload_dev_constant()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Primary class file for the Jetpack Beta plugin.
4
 *
5
 * @package automattic/jetpack-beta
6
 */
7
8
// Check that the file is not accessed directly.
9
if ( ! defined( 'ABSPATH' ) ) {
10
	exit;
11
}
12
13
/**
14
 * Class Jetpack_Beta
15
 */
16
class Jetpack_Beta {
17
18
	/**
19
	 * Singleton Jetpack_Beta class instance.
20
	 *
21
	 * @var Jetpack_Beta
22
	 */
23
	protected static $instance = null;
24
25
	/**
26
	 * WP Options string: jetpack_beta_active
27
	 *
28
	 * @var string
29
	 */
30
	protected static $option = 'jetpack_beta_active';
31
32
	/**
33
	 * WP Options string: jetpack_beta_dev_currently_installed
34
	 *
35
	 * @var string
36
	 */
37
	protected static $option_dev_installed = 'jetpack_beta_dev_currently_installed';
38
39
	/**
40
	 * WP Options string: jp_beta_autoupdate
41
	 *
42
	 * @var string
43
	 */
44
	protected static $option_autoupdate = 'jp_beta_autoupdate';
45
46
	/**
47
	 * WP Options string: jp_beta_email_notifications
48
	 *
49
	 * @var string
50
	 */
51
	protected static $option_email_notif = 'jp_beta_email_notifications';
52
53
	/**
54
	 * WP-Cron string: jetpack_beta_autoupdate_hourly_cron
55
	 *
56
	 * @var string
57
	 */
58
	protected static $auto_update_cron_hook = 'jetpack_beta_autoupdate_hourly_cron';
59
60
	/**
61
	 * Main Instance
62
	 */
63
	public static function instance() {
64
		if ( null === self::$instance ) {
65
			self::$instance = new self();
66
		}
67
68
		return self::$instance;
69
	}
70
71
	/**
72
	 * Constructor
73
	 */
74
	public function __construct() {
75
		add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'maybe_plugins_update_transient' ) );
76
		add_filter( 'upgrader_post_install', array( $this, 'upgrader_post_install' ), 10, 3 );
77
78
		add_action( 'admin_bar_menu', array( $this, 'admin_bar_menu' ) );
79
		add_action( 'deactivate_plugin', array( $this, 'plugin_deactivated' ), 10, 2 );
80
81
		add_action( 'upgrader_process_complete', array( $this, 'upgrader_process_complete' ), 10, 2 );
82
83
		add_filter( 'plugin_action_links_' . JETPACK_PLUGIN_FILE, array( $this, 'remove_activate_stable' ) );
84
		add_filter( 'plugin_action_links_' . JETPACK_DEV_PLUGIN_FILE, array( $this, 'remove_activate_dev' ) );
85
86
		add_filter( 'network_admin_plugin_action_links_' . JETPACK_PLUGIN_FILE, array( $this, 'remove_activate_stable' ) );
87
		add_filter( 'network_admin_plugin_action_links_' . JETPACK_DEV_PLUGIN_FILE, array( $this, 'remove_activate_dev' ) );
88
89
		add_filter( 'all_plugins', array( $this, 'update_all_plugins' ) );
90
91
		add_filter( 'plugins_api', array( $this, 'get_plugin_info' ), 10, 3 );
92
93
		add_action( 'jetpack_beta_autoupdate_hourly_cron', array( 'Jetpack_Beta', 'run_autoupdate' ) );
94
95
		add_filter( 'jetpack_options_whitelist', array( $this, 'add_to_options_whitelist' ) );
96
97
		if ( is_admin() ) {
98
			require JPBETA__PLUGIN_DIR . 'class-jetpack-beta-admin.php';
99
			self::maybe_schedule_autoupdate();
100
			Jetpack_Beta_Admin::init();
101
		}
102
	}
103
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 ( ! function_exists( 'is_plugin_active' ) ) {
546
				require_once ABSPATH . 'wp-admin/includes/plugin.php';
547
			}
548
			if ( is_plugin_active( JETPACK_PLUGIN_FILE ) ) {
549
				return array( 'stable', 'stable' );
550
			}
551
			return array( false, false );
552
		}
553
		// Branch and section.
554
		return $option;
555
	}
556
557
	/**
558
	 * Check if Jetpack version is 'stable' version.
559
	 */
560 View Code Duplication
	public static function is_on_stable() {
561
		$branch_and_section = self::get_branch_and_section();
562
		if ( empty( $branch_and_section[0] ) || 'stable' === $branch_and_section[0] ) {
563
			return true;
564
		}
565
		return false;
566
	}
567
568
	/**
569
	 * Check if Jetpack active version is a tag version.
570
	 */
571
	public static function is_on_tag() {
572
		$option = (array) self::get_option();
573
		if ( isset( $option[1] ) && 'tags' === $option[1] ) {
574
			return true;
575
		}
576
		return false;
577
	}
578
579
	/**
580
	 * Get active Jetpack Dev branch/section.
581
	 */
582
	public static function get_branch_and_section_dev() {
583
		$option = (array) self::get_dev_installed();
584
		if ( false !== $option[0] && isset( $option[1] ) ) {
585
			return array( $option[0], $option[1] );
586
		}
587
		if ( is_plugin_active( JETPACK_DEV_PLUGIN_FILE ) ) {
588
			return array( 'stable', 'stable' );
589
		}
590
		return array( false, false );
591
	}
592
593
	/**
594
	 * Massage JP plugin version string.
595
	 *
596
	 * @param bool $is_dev_version - If JP Dev version is being queried.
597
	 */
598
	public static function get_jetpack_plugin_pretty_version( $is_dev_version = false ) {
599
		if ( $is_dev_version ) {
600
			list( $branch, $section ) = self::get_branch_and_section_dev();
601
		} else {
602
			list( $branch, $section ) = self::get_branch_and_section();
603
		}
604
605
		if ( ! $section ) {
606
			return '';
607
		}
608
609
		if ( 'master' === $section ) {
610
			return 'Bleeding Edge';
611
		}
612
613
		if ( 'stable' === $section ) {
614
			return 'Latest Stable';
615
		}
616
617
		if ( 'tags' === $section ) {
618
			return sprintf(
619
				// translators: %1$s: a tagged Jetpack plugin version.
620
				__( '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' ),
621
				esc_attr( $branch )
622
			);
623
		}
624
625
		if ( 'rc' === $section ) {
626
			return 'Release Candidate';
627
		}
628
629
		if ( 'pr' === $section ) {
630
			$branch = str_replace( '-', ' ', $branch );
631
			return 'Feature Branch: ' . str_replace( '_', ' / ', $branch );
632
		}
633
634
		return self::get_jetpack_plugin_version();
635
	}
636
637
	/**
638
	 * Fetch latest Jetpack version.
639
	 *
640
	 * @param bool $is_dev_version - If JP Dev version is being queried.
641
	 */
642
	public static function get_new_jetpack_version( $is_dev_version = false ) {
643
		$manifest = self::get_beta_manifest();
644
		if ( $is_dev_version ) {
645
			list( $branch, $section ) = self::get_branch_and_section_dev();
646
		} else {
647
			list( $branch, $section ) = self::get_branch_and_section();
648
		}
649
650
		if ( 'master' === $section && isset( $manifest->{$section}->version ) ) {
651
			return $manifest->{$section}->version;
652
		}
653
654
		if ( 'rc' === $section && isset( $manifest->{$section}->version ) ) {
655
			return $manifest->{$section}->version;
656
		}
657
658
		if ( isset( $manifest->{$section} ) &&
659
		isset( $manifest->{$section}->{$branch} ) &&
660
		isset( $manifest->{$section}->{$branch}->version )
661
		) {
662
			return $manifest->{$section}->{$branch}->version;
663
		}
664
		return 0;
665
	}
666
667
	/**
668
	 * Get JP Dev plugin repo URL.
669
	 */
670
	public static function get_url_dev() {
671
		list( $branch, $section ) = self::get_branch_and_section_dev();
672
		return self::get_url( $branch, $section );
673
	}
674
675
	/**
676
	 * Get JP plugin repo URL.
677
	 *
678
	 * @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...
679
	 * @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...
680
	 */
681
	public static function get_url( $branch = null, $section = null ) {
682
		if ( is_null( $section ) ) {
683
			list( $branch, $section ) = self::get_branch_and_section();
684
		}
685
686
		if ( 'master' === $section ) {
687
			return JETPACK_GITHUB_URL . '/tree/master-build';
688
		}
689
690
		if ( 'rc' === $section ) {
691
			return JETPACK_GITHUB_URL . '/tree/' . $section . '-build';
692
		}
693
694
		if ( 'pr' === $section ) {
695
			$manifest = self::get_beta_manifest();
696
			return isset( $manifest->{$section}->{$branch}->pr )
697
			? JETPACK_GITHUB_URL . '/pull/' . $manifest->{$section}->{$branch}->pr
698
			: JETPACK_DEFAULT_URL;
699
		}
700
		return JETPACK_DEFAULT_URL;
701
	}
702
703
	/**
704
	 * Get install URL for JP dev.
705
	 */
706
	public static function get_install_url_dev() {
707
		list( $branch, $section ) = self::get_branch_and_section_dev();
708
		return self::get_install_url( $branch, $section );
709
	}
710
711
	/**
712
	 * Get install URL for JP.
713
	 *
714
	 * @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...
715
	 * @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...
716
	 */
717
	public static function get_install_url( $branch = null, $section = null ) {
718
		if ( is_null( $section ) ) {
719
			list( $branch, $section ) = self::get_branch_and_section();
720
		}
721
722
		if ( 'stable' === $section ) {
723
			$org_data = self::get_org_data();
724
			return $org_data->download_link;
725
		} elseif ( 'tags' === $section ) {
726
			$org_data = self::get_org_data();
727
			return $org_data->versions->{$branch} ? $org_data->versions->{$branch} : false;
728
		}
729
		$manifest = self::get_beta_manifest( true );
730
731
		if ( 'master' === $section && isset( $manifest->{$section}->download_url ) ) {
732
			return $manifest->{$section}->download_url;
733
		}
734
735
		if ( 'rc' === $section ) {
736
			if ( isset( $manifest->{$section}->download_url ) ) {
737
				return $manifest->{$section}->download_url;
738
			}
739
			$branches = array_keys( (array) $manifest->{$section} );
740
			foreach ( $branches as $branch ) {
741
				if ( isset( $manifest->{$section}->{$branch}->download_url ) ) {
742
					return $manifest->{$section}->{$branch}->download_url;
743
				}
744
			}
745
			return null;
746
		}
747
748
		if ( isset( $manifest->{$section}->{$branch}->download_url ) ) {
749
			return $manifest->{$section}->{$branch}->download_url;
750
		}
751
		return null;
752
	}
753
754
	/**
755
	 * Get stable JP version plugin data.
756
	 */
757
	public static function get_jetpack_plugin_info_stable() {
758
		return self::get_jetpack_plugin_info( JETPACK_PLUGIN_FILE );
759
	}
760
761
	/**
762
	 * Get dev JP version plugin data.
763
	 */
764
	public static function get_jetpack_plugin_info_dev() {
765
		return self::get_jetpack_plugin_info( JETPACK_DEV_PLUGIN_FILE );
766
	}
767
768
	/**
769
	 * Get JP plugin data.
770
	 *
771
	 * @param mixed $plugin_file - JP or JP Dev plugin path.
772
	 */
773
	public static function get_jetpack_plugin_info( $plugin_file = null ) {
774
775
		if ( is_null( $plugin_file ) ) {
776
			$plugin_file = self::get_plugin_file();
777
		}
778
779
		if ( ! function_exists( 'get_plugin_data' ) ) {
780
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
781
		}
782
		$plugin_file_path = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $plugin_file;
783
784
		if ( file_exists( $plugin_file_path ) ) {
785
			return get_plugin_data( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $plugin_file );
786
		}
787
788
		return null;
789
	}
790
791
	/**
792
	 * Switch active JP plugin version when JP Beta plugin is deactivated.
793
	 * This needs to happen on `shutdown`, otherwise it doesn't work.
794
	 */
795
	public static function switch_active() {
796
		self::replace_active_plugin( JETPACK_DEV_PLUGIN_FILE, JETPACK_PLUGIN_FILE );
797
	}
798
799
	/**
800
	 * Fetch the Jetpack beta manifest.
801
	 *
802
	 * @param bool $force_refresh - Whether to bypass cached response.
803
	 */
804
	public static function get_beta_manifest( $force_refresh = false ) {
805
		return self::get_remote_data( JETPACK_BETA_MANIFEST_URL, 'manifest', $force_refresh );
806
	}
807
808
	/**
809
	 * Fetch WordPress.org Jetpack plugin info.
810
	 */
811
	public static function get_org_data() {
812
		return self::get_remote_data( JETPACK_ORG_API_URL, 'org_data' );
813
	}
814
815
	/**
816
	 * Helper function used to fetch remote data from WordPress.org, GitHub, and betadownload.jetpack.me
817
	 *
818
	 * @param string $url       - Url being fetched.
819
	 * @param string $transient - Transient name (manifest|org_data|github_commits_).
820
	 * @param bool   $bypass    - Whether to bypass cached response.
821
	 */
822
	public static function get_remote_data( $url, $transient, $bypass = false ) {
823
		$prefix = 'jetpack_beta_';
824
		$cache  = get_site_transient( $prefix . $transient );
825
		if ( $cache && ! $bypass ) {
826
			return $cache;
827
		}
828
829
		$remote_manifest = wp_remote_get( $url );
830
831
		if ( is_wp_error( $remote_manifest ) ) {
832
			return false;
833
		}
834
835
		$cache = json_decode( wp_remote_retrieve_body( $remote_manifest ) );
836
		set_site_transient( $prefix . $transient, $cache, MINUTE_IN_SECONDS * 15 );
837
838
		return $cache;
839
	}
840
841
	/**
842
	 * Delete set transients when plugin is deactivated.
843
	 */
844
	public static function delete_all_transiants() {
845
		$prefix = 'jetpack_beta_';
846
847
		delete_site_transient( $prefix . 'org_data' );
848
		delete_site_transient( $prefix . 'manifest' );
849
850
		delete_site_transient( Jetpack_Beta_Autoupdate_Self::TRANSIENT_NAME );
851
852
	}
853
854
	/**
855
	 * Install & activate JP for the given branch/section.
856
	 *
857
	 * @param string $branch  - Branch.
858
	 * @param string $section - Section.
859
	 */
860
	public static function install_and_activate( $branch, $section ) {
861
		// Cleanup previous version of the beta plugin.
862
		if ( file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'jetpack-pressable-beta' ) ) {
863
			// Delete the Jetpack dev plugin.
864
			require_once ABSPATH . 'wp-admin/includes/file.php';
865
			$creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, array() );
866
			if ( ! WP_Filesystem( $creds ) ) {
867
				// Any problems and we exit.
868
				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...
869
			}
870
			global $wp_filesystem;
871
			if ( ! $wp_filesystem ) {
872
				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...
873
			}
874
875
			$working_dir = WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'jetpack-pressable-beta';
876
			// Delete the folder `JETPACK_BETA_PLUGIN_FOLDER`.
877
			if ( $wp_filesystem->is_dir( $working_dir ) ) {
878
				$wp_filesystem->delete( $working_dir, true );
879
			}
880
			// Deactivate the plugin.
881
			self::replace_active_plugin( 'jetpack-pressable-beta/jetpack.php' );
882
		}
883
884 View Code Duplication
		if ( 'stable' === $section &&
885
		file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . JETPACK_PLUGIN_FILE ) ) {
886
			self::replace_active_plugin( JETPACK_DEV_PLUGIN_FILE, JETPACK_PLUGIN_FILE, true );
887
			self::update_option( $branch, $section );
888
			return;
889
		}
890
891 View Code Duplication
		if ( self::get_branch_and_section_dev() === array( $branch, $section )
892
		&& file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . JETPACK_DEV_PLUGIN_FILE ) ) {
893
			self::replace_active_plugin( JETPACK_PLUGIN_FILE, JETPACK_DEV_PLUGIN_FILE, true );
894
			self::update_option( $branch, $section );
895
			return;
896
		}
897
898
		self::proceed_to_install_and_activate(
899
			self::get_install_url( $branch, $section ),
900
			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...
901
			$section
902
		);
903
		self::update_option( $branch, $section );
904
	}
905
906
	/**
907
	 * Update to the latest version.
908
	 *
909
	 * @param string $branch  - Branch.
910
	 * @param string $section - Section.
911
	 */
912
	public static function update_plugin( $branch, $section ) {
913
		self::proceed_to_install(
914
			self::get_install_url( $branch, $section ),
915
			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...
916
			$section
917
		);
918
919 View Code Duplication
		if ( 'stable' !== $section ) {
920
			update_option( self::$option_dev_installed, array( $branch, $section, self::get_manifest_data( $branch, $section ) ) );
921
		}
922
	}
923
924
	/**
925
	 * Helper function to update installed version option.
926
	 *
927
	 * @param string $branch  - Branch.
928
	 * @param string $section - Section.
929
	 */
930
	public static function update_option( $branch, $section ) {
931 View Code Duplication
		if ( 'stable' !== $section ) {
932
			update_option( self::$option_dev_installed, array( $branch, $section, self::get_manifest_data( $branch, $section ) ) );
933
		}
934
		update_option( self::$option, array( $branch, $section ) );
935
	}
936
937
	/**
938
	 * Return manifest info for specififed branch/section.
939
	 *
940
	 * @param string $branch  - Branch.
941
	 * @param string $section - Section.
942
	 */
943
	public static function get_manifest_data( $branch, $section ) {
944
		$installed             = get_option( self::$option_dev_installed );
945
		$current_manifest_data = isset( $installed[2] ) ? $installed[2] : false;
946
947
		$manifest_data = self::get_beta_manifest();
948
949
		if ( ! isset( $manifest_data->{$section} ) ) {
950
			return $current_manifest_data;
951
		}
952
953
		if ( 'master' === $section ) {
954
			return $manifest_data->{$section};
955
		}
956
957
		if ( 'rc' === $section ) {
958
			return $manifest_data->{$section};
959
		}
960
961
		if ( isset( $manifest_data->{$section}->{$branch} ) ) {
962
			return $manifest_data->{$section}->{$branch};
963
		}
964
965
		return $current_manifest_data;
966
	}
967
968
	/**
969
	 * Install specified plugin version.
970
	 *
971
	 * @param string $url           - Url for plugin version.
972
	 * @param string $plugin_folder - Path JP or JP Dev plugin folder.
973
	 * @param string $section       - Section.
974
	 */
975
	public static function proceed_to_install_and_activate( $url, $plugin_folder, $section ) {
976
		self::proceed_to_install( $url, $plugin_folder, $section );
977
978
		if ( 'stable' === $section || 'tags' === $section ) {
979
			self::replace_active_plugin( JETPACK_DEV_PLUGIN_FILE, JETPACK_PLUGIN_FILE, true );
980
		} else {
981
			self::replace_active_plugin( JETPACK_PLUGIN_FILE, JETPACK_DEV_PLUGIN_FILE, true );
982
		}
983
	}
984
985
	/**
986
	 * Download plugin files.
987
	 *
988
	 * @param string $url           - Url for plugin version.
989
	 * @param string $plugin_folder - Path JP or JP Dev plugin folder.
990
	 * @param string $section       - Section.
991
	 */
992
	public static function proceed_to_install( $url, $plugin_folder, $section ) {
993
		$temp_path = download_url( $url );
994
995 View Code Duplication
		if ( is_wp_error( $temp_path ) ) {
996
			// translators: %1$s: download url, %2$s: error message.
997
			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() ) ) );
998
		}
999
		require_once ABSPATH . 'wp-admin/includes/file.php';
1000
		$creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, array() );
1001
		/* initialize the API */
1002
		if ( ! WP_Filesystem( $creds ) ) {
1003
			/* any problems and we exit */
1004
			wp_die( esc_html( __( 'Jetpack Beta: No File System access', 'jetpack-beta' ) ) );
1005
		}
1006
1007
		global $wp_filesystem;
1008
		if ( 'stable' === $section || 'tags' === $section ) {
1009
			$plugin_path = WP_PLUGIN_DIR;
1010
		} else {
1011
			$plugin_path = str_replace( ABSPATH, $wp_filesystem->abspath(), WP_PLUGIN_DIR );
1012
		}
1013
1014
		$result = unzip_file( $temp_path, $plugin_path );
1015
1016 View Code Duplication
		if ( is_wp_error( $result ) ) {
1017
			// translators: %1$s: error message.
1018
			wp_die( esc_html( sprintf( __( 'Error Unziping file: Error: %1$s', 'jetpack-beta' ), $result->get_error_message() ) ) );
1019
		}
1020
	}
1021
1022
	/**
1023
	 * Check if plugin is network activated.
1024
	 */
1025
	public static function is_network_active() {
1026
		if ( ! is_multisite() ) {
1027
			return false;
1028
		}
1029
1030
		if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
1031
			return false;
1032
		}
1033
1034
		if ( is_plugin_active_for_network( JETPACK_PLUGIN_FILE ) || is_plugin_active_for_network( JETPACK_DEV_PLUGIN_FILE ) ) {
1035
			return true;
1036
		}
1037
1038
		return false;
1039
	}
1040
1041
	/**
1042
	 * Swap plugin files.
1043
	 *
1044
	 * @param string $current_plugin      - Current plugin path.
1045
	 * @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...
1046
	 * @param bool   $force_activate      - Whether to force activate plguin.
1047
	 */
1048
	public static function replace_active_plugin( $current_plugin, $replace_with_plugin = null, $force_activate = false ) {
1049
		// The autoloader sets the cache in a shutdown hook. Clear it after the autoloader sets it.
1050
		add_action( 'shutdown', array( __CLASS__, 'clear_autoloader_plugin_cache' ), 99 );
1051
1052
		if ( self::is_network_active() ) {
1053
			$new_active_plugins     = array();
1054
			$network_active_plugins = get_site_option( 'active_sitewide_plugins' );
1055
			foreach ( $network_active_plugins as $plugin => $date ) {
1056
				$key                        = ( $plugin === $current_plugin ? $replace_with_plugin : $plugin );
1057
				$new_active_plugins[ $key ] = $date;
1058
			}
1059
			update_site_option( 'active_sitewide_plugins', $new_active_plugins );
1060
			return;
1061
		}
1062
1063
		$active_plugins     = (array) get_option( 'active_plugins', array() );
1064
		$new_active_plugins = array();
1065
1066
		if ( empty( $replace_with_plugin ) ) {
1067
			$new_active_plugins = array_diff( $active_plugins, array( $current_plugin ) );
1068
		} else {
1069
			foreach ( $active_plugins as $plugin ) {
1070
				$new_active_plugins[] = ( $plugin === $current_plugin ? $replace_with_plugin : $plugin );
1071
			}
1072
		}
1073
1074
		if ( $force_activate && ! in_array( $replace_with_plugin, $new_active_plugins, true ) ) {
1075
			$new_active_plugins[] = $replace_with_plugin;
1076
		}
1077
		update_option( 'active_plugins', $new_active_plugins );
1078
	}
1079
1080
	/**
1081
	 * Check if `stable` should be updated.
1082
	 *
1083
	 * @return bool
1084
	 */
1085
	public static function should_update_stable_version() {
1086
		// Pressable Jetpack version is manage via Pressable.
1087
		if ( defined( 'IS_PRESSABLE' ) && IS_PRESSABLE ) {
1088
			return false;
1089
		}
1090
1091
		// Check if running in a docker instance.
1092
		if ( defined( 'JETPACK_DOCKER_ENV' ) && JETPACK_DOCKER_ENV ) {
1093
			return false;
1094
		}
1095
1096
		// Check if we are Jetpack plugin is installed via git.
1097
		if ( file_exists( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . 'jetpack/.git' ) ) {
1098
			return false;
1099
		}
1100
1101
		// Check if running a tag directly from svn.
1102
		if ( self::is_on_tag() ) {
1103
			return false;
1104
		}
1105
1106
		$updates = get_site_transient( 'update_plugins' );
1107
1108
		if ( isset( $updates->response, $updates->response[ JETPACK_PLUGIN_FILE ] ) ) {
1109
			return true;
1110
		}
1111
		$org_data    = self::get_org_data();
1112
		$plugin_data = self::get_jetpack_plugin_info_stable();
1113
1114
		return ( isset( $plugin_data['Version'], $org_data->version )
1115
			&& $org_data->version !== $plugin_data['Version'] );
1116
	}
1117
1118
	/**
1119
	 * Here we are checking if the DEV branch that we are currenly on is not something that is available in the manifest
1120
	 * Meaning that the DEV branch was merged into master and so we need to update it.
1121
	 *
1122
	 * @return bool
1123
	 */
1124
	public static function should_update_dev_to_master() {
1125
		list( $branch, $section ) = self::get_branch_and_section_dev();
1126
1127
		if ( false === $branch || 'master' === $section || 'rc' === $section || 'tags' === $section ) {
1128
			return false;
1129
		}
1130
		$manifest = self::get_beta_manifest();
1131
		return ! isset( $manifest->{$section}->{$branch} );
1132
	}
1133
1134
	/**
1135
	 * Get WP Option: jp_beta_autoupdate
1136
	 */
1137
	public static function is_set_to_autoupdate() {
1138
		return get_option( self::$option_autoupdate, false );
1139
	}
1140
1141
	/**
1142
	 * Get WP Option: jp_beta_email_notifications
1143
	 */
1144
	public static function is_set_to_email_notifications() {
1145
		return get_option( self::$option_email_notif, true );
1146
	}
1147
1148
	/**
1149
	 * Clear scheduled WP-Cron jobs on plugin deactivation.
1150
	 */
1151
	public static function clear_autoupdate_cron() {
1152
		if ( ! is_main_site() ) {
1153
			return;
1154
		}
1155
		wp_clear_scheduled_hook( self::$auto_update_cron_hook );
1156
1157
		if ( function_exists( 'wp_unschedule_hook' ) ) { // New in WP `4.9`.
1158
			wp_unschedule_hook( self::$auto_update_cron_hook );
1159
		}
1160
	}
1161
1162
	/**
1163
	 * Schedule plugin update jobs.
1164
	 */
1165
	public static function schedule_hourly_autoupdate() {
1166
		wp_clear_scheduled_hook( self::$auto_update_cron_hook );
1167
		wp_schedule_event( time(), 'hourly', self::$auto_update_cron_hook );
1168
	}
1169
1170
	/**
1171
	 * Determine if plugin update jobs should be scheduled.
1172
	 */
1173
	public static function maybe_schedule_autoupdate() {
1174
		if ( ! self::is_set_to_autoupdate() ) {
1175
			return;
1176
		}
1177
1178
		if ( ! is_main_site() ) {
1179
			return;
1180
		}
1181
		$has_schedule_already = wp_get_schedule( self::$auto_update_cron_hook );
1182
		if ( ! $has_schedule_already ) {
1183
			self::schedule_hourly_autoupdate();
1184
		}
1185
	}
1186
1187
	/**
1188
	 * Get "What changed" info for display.
1189
	 *
1190
	 * @return string|false
1191
	 */
1192
	public static function what_changed() {
1193
		$commit = self::get_version_commit();
1194
		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...
1195
			$html        = '';
1196
			$commit_data = self::get_commit_data_from_github( $commit );
1197
			if ( isset( $commit_data->commit->message ) ) {
1198
				$html .= sprintf(
1199
					__( "\n %1\$s \n\n[Commit](%2\$s)", 'jetpack-beta' ),
1200
					esc_html( $commit_data->commit->message ),
1201
					esc_url( $commit_data->html_url )
1202
				);
1203
				"\n\n";
1204
			}
1205
			if ( ! empty( $commit_data->files ) ) {
1206
				$html .= "\n\n";
1207
				// translators: %d: number of files changed.
1208
				$html .= sprintf( _n( '%d file changed ', '%d files changed', count( $commit_data->files ), 'jetpack-beta' ) );
1209
				$html .= "\n";
1210
				foreach ( $commit_data->files as $file ) {
1211
					$added_deleted_changed = array();
1212
					if ( $file->additions ) {
1213
						$added_deleted_changed[] = '+' . $file->additions;
1214
					}
1215
					if ( $file->deletions ) {
1216
						$added_deleted_changed[] = '-' . $file->deletions;
1217
					}
1218
					$html .= sprintf( "- %s ... (%s %s) \n", esc_html( $file->filename ), esc_html( $file->status ), implode( ' ', $added_deleted_changed ) );
1219
				}
1220
				$html .= "\n\n";
1221
			}
1222
			if ( ! empty( $html ) ) {
1223
				return $html;
1224
			}
1225
		}
1226
		return false;
1227
	}
1228
1229
	/**
1230
	 * Get version commit if available.
1231
	 *
1232
	 * @return string|false
1233
	 */
1234
	public static function get_version_commit() {
1235
		$split_version = explode( '-', self::get_jetpack_plugin_version() );
1236
		if ( isset( $split_version[3] ) ) {
1237
			return $split_version[3];
1238
		}
1239
		return false;
1240
	}
1241
1242
	/**
1243
	 * Fetch commit data from GitHub.
1244
	 *
1245
	 * @param string $commit - The commit to fetch.
1246
	 */
1247
	public static function get_commit_data_from_github( $commit ) {
1248
		return self::get_remote_data( JETPACK_GITHUB_API_URL . 'commits/' . $commit, 'github_commits_' . $commit );
1249
	}
1250
1251
	/**
1252
	 * The jetpack_beta_autoupdate_hourly_cron job - does not update Stable.
1253
	 */
1254
	public static function run_autoupdate() {
1255
		if ( ! self::is_set_to_autoupdate() ) {
1256
			return;
1257
		}
1258
1259
		if ( ! is_main_site() ) {
1260
			return;
1261
		}
1262
1263
		require_once ABSPATH . 'wp-admin/includes/file.php';
1264
		require_once ABSPATH . 'wp-admin/includes/plugin.php';
1265
		require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
1266
		wp_clean_plugins_cache();
1267
		ob_start();
1268
		wp_update_plugins(); // Check for Plugin updates.
1269
		ob_end_clean();
1270
		$plugins = array();
1271
		if (
1272
		! self::is_on_stable() &&
1273
		( self::should_update_dev_to_master() || self::should_update_dev_version() )
1274
		) {
1275
			add_filter( 'upgrader_source_selection', array( 'Jetpack_Beta', 'check_for_main_files' ), 10, 2 );
1276
1277
			// If response is false, don't alter the transient.
1278
			$plugins[] = JETPACK_DEV_PLUGIN_FILE;
1279
		}
1280
		$autupdate = Jetpack_Beta_Autoupdate_Self::instance();
1281
		if ( $autupdate->has_never_version() ) {
1282
			$plugins[] = JPBETA__PLUGIN_FOLDER . '/jetpack-beta.php';
1283
		}
1284
1285
		if ( empty( $plugins ) ) {
1286
			return;
1287
		}
1288
1289
		// Unhook this functions that output things before we send our response header.
1290
		remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 );
1291
		remove_action( 'upgrader_process_complete', 'wp_version_check' );
1292
		remove_action( 'upgrader_process_complete', 'wp_update_themes' );
1293
1294
		$skin = new WP_Ajax_Upgrader_Skin();
1295
		// The Automatic_Upgrader_Skin skin shouldn't output anything.
1296
		$upgrader = new Plugin_Upgrader( $skin );
1297
		$upgrader->init();
1298
		// This avoids the plugin to be deactivated.
1299
		// Using bulk upgrade puts the site into maintenance mode during the upgrades.
1300
		$result = $upgrader->bulk_upgrade( $plugins );
1301
		$errors = $upgrader->skin->get_errors();
1302
		$log    = $upgrader->skin->get_upgrade_messages();
1303
1304
		if ( is_wp_error( $errors ) && $errors->get_error_code() ) {
1305
			return $errors;
1306
		}
1307
1308
		if ( $result && ! defined( 'JETPACK_BETA_SKIP_EMAIL' ) && self::is_set_to_email_notifications() ) {
1309
			self::send_autoupdate_email( $plugins, $log );
1310
		}
1311
	}
1312
1313
	/**
1314
	 * Builds and sends an email about succesfull plugin autoupdate.
1315
	 *
1316
	 * @param Array  $plugins - List of plugins that were updated.
1317
	 * @param String $log     - Upgrade message from core's plugin upgrader.
1318
	 */
1319
	private static function send_autoupdate_email( $plugins, $log ) {
1320
		$admin_email = get_site_option( 'admin_email' );
1321
1322
		if ( empty( $admin_email ) ) {
1323
			return;
1324
		}
1325
1326
		// In case the code is called in a scope different from wp-admin.
1327
		require_once JPBETA__PLUGIN_DIR . 'class-jetpack-beta-admin.php';
1328
1329
		// Calling empty() on a function return value crashes in PHP < 5.5.
1330
		// Thus we assign the return value explicitly and then check with empty().
1331
		$bloginfo_name = get_bloginfo( 'name' );
1332
		$site_title    = ! empty( $bloginfo_name ) ? get_bloginfo( 'name' ) : get_site_url();
1333
		$what_updated  = 'Jetpack Beta Tester Plugin';
1334
		// translators: %s: The site title.
1335
		$subject = sprintf( __( '[%s] Autoupdated Jetpack Beta Tester', 'jetpack-beta' ), $site_title );
1336
1337
		if ( in_array( JETPACK_DEV_PLUGIN_FILE, $plugins, true ) ) {
1338
			$subject = sprintf(
1339
				// translators: %1$s: site title, %2$s: pretty plugin version (eg 9.3).
1340
				__( '[%1$s] Autoupdated Jetpack %2$s ', 'jetpack-beta' ),
1341
				$site_title,
1342
				self::get_jetpack_plugin_pretty_version()
1343
			);
1344
1345
			$what_updated = sprintf(
1346
				// translators: $1$s: pretty plugin version, $2$s: raw plugin version (eg 9.3.2-beta).
1347
				__( 'Jetpack %1$s (%2$s)', 'jetpack-beta' ),
1348
				self::get_jetpack_plugin_pretty_version(),
1349
				self::get_jetpack_plugin_version()
1350
			);
1351
1352
			if ( count( $plugins ) > 1 ) {
1353
				$subject = sprintf(
1354
					// translators: %1$s: site title, %2$s: pretty plugin version.
1355
					__( '[%1$s] Autoupdated Jetpack %2$s and the Jetpack Beta Tester', 'jetpack-beta' ),
1356
					$site_title,
1357
					self::get_jetpack_plugin_pretty_version()
1358
				);
1359
1360
				$what_updated = sprintf(
1361
					// translators: $1$s: pretty plugin version, $2$s: raw plugin version.
1362
					__( 'Jetpack %1$s (%2$s) and the Jetpack Beta Tester', 'jetpack-beta' ),
1363
					self::get_jetpack_plugin_pretty_version(),
1364
					self::get_jetpack_plugin_version()
1365
				);
1366
			}
1367
		}
1368
1369
		$message = sprintf(
1370
			// translators: %1$s: site url, $2$s: text of what has updated.
1371
			__( 'Howdy! Your site at %1$s has autoupdated %2$s.', 'jetpack-beta' ),
1372
			home_url(),
1373
			$what_updated
1374
		);
1375
		$message .= "\n\n";
1376
1377
		$what_changed = self::what_changed();
1378
		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...
1379
			$message .= __( 'What changed?', 'jetpack-beta' );
1380
			$message .= wp_strip_all_tags( $what_changed );
1381
		}
1382
1383
		$message .= __( 'During the autoupdate the following happened:', 'jetpack-beta' );
1384
		$message .= "\n\n";
1385
		// Can only reference the About screen if their update was successful.
1386
		$log      = array_map( 'html_entity_decode', $log );
1387
		$message .= ' - ' . implode( "\n - ", $log );
1388
		$message .= "\n\n";
1389
1390
		// Adds To test section. for PR's it's a PR description, for master/RC - it's a to_test.md file contents.
1391
		$message .= Jetpack_Beta_Admin::to_test_content();
1392
		$message .= "\n\n";
1393
1394
		wp_mail( $admin_email, $subject, $message );
1395
	}
1396
1397
	/**
1398
	 * This checks intends to fix errors in our build server when Jetpack.
1399
	 *
1400
	 * @param string $source        - Source path.
1401
	 * @param string $remote_source - Remote path.
1402
	 *
1403
	 * @return WP_Error
1404
	 */
1405
	public static function check_for_main_files( $source, $remote_source ) {
1406
		if ( $source === $remote_source . '/jetpack-dev/' ) {
1407
			if ( ! file_exists( $source . 'jetpack.php' ) ) {
1408
				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...
1409
			}
1410
			if ( ! file_exists( $source . '_inc/build/static.html' ) ) {
1411
				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...
1412
			}
1413
			if ( ! file_exists( $source . '_inc/build/admin.js' ) ) {
1414
				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...
1415
			}
1416
			// It has happened that sometimes a generated bundle from the master branch ends up with an empty
1417
			// vendor directory. Used to be a problem in the beta building process.
1418
			if ( self::is_dir_empty( $source . 'vendor' ) ) {
1419
				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...
1420
			}
1421
		}
1422
1423
		return $source;
1424
	}
1425
1426
	/**
1427
	 * Checks if a dir is empty.
1428
	 *
1429
	 * @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...
1430
	 * @return boolean
1431
	 */
1432
	public static function is_dir_empty( $dir ) {
1433
		return ( count( scandir( $dir ) ) === 2 );
1434
	}
1435
1436
	/**
1437
	 * Callback function to include Jetpack beta options into Jetpack sync whitelist.
1438
	 *
1439
	 * @param Array $whitelist List of whitelisted options to sync.
1440
	 */
1441
	public function add_to_options_whitelist( $whitelist ) {
1442
		$whitelist[] = self::$option;
1443
		$whitelist[] = self::$option_dev_installed;
1444
		$whitelist[] = self::$option_autoupdate;
1445
		$whitelist[] = self::$option_email_notif;
1446
		return $whitelist;
1447
	}
1448
1449
	/**
1450
	 * Custom error handler to intercept errors and log them using Jetpack's own logger.
1451
	 *
1452
	 * @param int    $errno   - Error code.
1453
	 * @param string $errstr  - Error message.
1454
	 * @param string $errfile - File name where the error happened.
1455
	 * @param int    $errline - Line in the code.
1456
	 *
1457
	 * @return bool Whether to make the default handler handle the error as well.
1458
	 */
1459
	public static function custom_error_handler( $errno, $errstr, $errfile, $errline ) {
1460
1461
		if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'log' ) ) {
1462
			$error_string = sprintf( '%s, %s:%d', $errstr, $errfile, $errline );
1463
1464
			// Only adding to log if the message is related to Jetpack.
1465
			if ( false !== stripos( $error_string, 'jetpack' ) ) {
1466
				Jetpack::log( $errno, $error_string );
1467
			}
1468
		}
1469
1470
		/**
1471
		 * The error_reporting call returns current error reporting level as an integer. Bitwise
1472
		 * AND lets us determine whether the current error is included in the current error
1473
		 * reporting level
1474
		 */
1475
		if ( ! ( error_reporting() & $errno ) ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting,WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_error_reporting
1476
1477
			// If this error is not being reported in the current settings, stop reporting here by returning true.
1478
			return true;
1479
		}
1480
1481
		// Returning false makes the error go through the standard error handler as well.
1482
		return false;
1483
	}
1484
1485
	/**
1486
	 * Clears the autoloader transient.
1487
	 */
1488
	public static function clear_autoloader_plugin_cache() {
1489
		delete_transient( 'jetpack_autoloader_plugin_paths' );
1490
	}
1491
}
1492