GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

EDD_SL_Plugin_Updater::check_update()   C
last analyzed

Complexity

Conditions 12
Paths 16

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 156

Importance

Changes 0
Metric Value
cc 12
nc 16
nop 1
dl 0
loc 40
rs 6.9666
c 0
b 0
f 0
ccs 0
cts 22
cp 0
crap 156

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
// Exit if accessed directly
4
if ( ! defined( 'ABSPATH' ) ) exit;
5
6
/**
7
 * Allows plugins to use their own update API.
8
 *
9
 * @author Easy Digital Downloads
10
 * @version 1.6.11
11
 */
12
class EDD_SL_Plugin_Updater {
13
14
	private $api_url     = '';
15
	private $api_data    = array();
16
	private $name        = '';
17
	private $slug        = '';
18
	private $version     = '';
19
	private $wp_override = false;
20
	private $cache_key   = '';
21
22
	/**
23
	 * Class constructor.
24
	 *
25
	 * @uses plugin_basename()
26
	 * @uses hook()
27
	 *
28
	 * @param string  $_api_url     The URL pointing to the custom API endpoint.
29
	 * @param string  $_plugin_file Path to the plugin file.
30
	 * @param array   $_api_data    Optional data to send with API calls.
31
	 */
32
	public function __construct( $_api_url, $_plugin_file, $_api_data = null ) {
33
34
		global $edd_plugin_data;
35
36
		$this->api_url     = trailingslashit( $_api_url );
37
		$this->api_data    = $_api_data;
0 ignored issues
show
Documentation Bug introduced by
It seems like $_api_data can be null. However, the property $api_data is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
38
		$this->name        = plugin_basename( $_plugin_file );
39
		$this->slug        = basename( $_plugin_file, '.php' );
40
		$this->version     = $_api_data['version'];
41
		$this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false;
42
		$this->beta        = ! empty( $this->api_data['beta'] ) ? true : false;
0 ignored issues
show
Bug introduced by
The property beta does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
43
		$this->cache_key   = md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
44
45
		$edd_plugin_data[ $this->slug ] = $this->api_data;
46
47
		// Set up hooks.
48
		$this->init();
49
50
	}
51
52
	/**
53
	 * Set up WordPress filters to hook into WP's update process.
54
	 *
55
	 * @uses add_filter()
56
	 *
57
	 * @return void
58
	 */
59
	public function init() {
60
61
		add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
62
		add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
63
		remove_action( 'after_plugin_row_' . $this->name, 'wp_plugin_update_row', 10 );
64
		add_action( 'after_plugin_row_' . $this->name, array( $this, 'show_update_notification' ), 10, 2 );
65
		add_action( 'admin_init', array( $this, 'show_changelog' ) );
66
67
	}
68
69
	/**
70
	 * Check for Updates at the defined API endpoint and modify the update array.
71
	 *
72
	 * This function dives into the update API just when WordPress creates its update array,
73
	 * then adds a custom API call and injects the custom plugin data retrieved from the API.
74
	 * It is reassembled from parts of the native WordPress plugin update code.
75
	 * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
76
	 *
77
	 * @uses api_request()
78
	 *
79
	 * @param array   $_transient_data Update array build by WordPress.
80
	 * @return array Modified update array with custom plugin data.
81
	 */
82
	public function check_update( $_transient_data ) {
83
84
		global $pagenow;
85
86
		if ( ! is_object( $_transient_data ) ) {
87
			$_transient_data = new stdClass;
88
		}
89
90
		if ( 'plugins.php' == $pagenow && is_multisite() ) {
91
			return $_transient_data;
92
		}
93
94
		if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) {
95
			return $_transient_data;
96
		}
97
98
		$version_info = $this->get_cached_version_info();
99
100
		if ( false === $version_info ) {
101
			$version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
102
103
			$this->set_version_info_cache( $version_info );
0 ignored issues
show
Documentation introduced by
$version_info is of type false|object, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
104
105
		}
106
107
		if ( false !== $version_info && is_object( $version_info ) && isset( $version_info->new_version ) ) {
108
109
			if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
110
111
				$_transient_data->response[ $this->name ] = $version_info;
112
113
			}
114
115
			$_transient_data->last_checked           = current_time( 'timestamp' );
116
			$_transient_data->checked[ $this->name ] = $this->version;
117
118
		}
119
120
		return $_transient_data;
121
	}
122
123
	/**
124
	 * show update nofication row -- needed for multisite subsites, because WP won't tell you otherwise!
125
	 *
126
	 * @param string  $file
127
	 * @param array   $plugin
128
	 */
129
	public function show_update_notification( $file, $plugin ) {
130
131
		if ( is_network_admin() ) {
132
			return;
133
		}
134
135
		if( ! current_user_can( 'update_plugins' ) ) {
136
			return;
137
		}
138
139
		if( ! is_multisite() ) {
140
			return;
141
		}
142
143
		if ( $this->name != $file ) {
144
			return;
145
		}
146
147
		// Remove our filter on the site transient
148
		remove_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ), 10 );
149
150
		$update_cache = get_site_transient( 'update_plugins' );
151
152
		$update_cache = is_object( $update_cache ) ? $update_cache : new stdClass();
153
154
		if ( empty( $update_cache->response ) || empty( $update_cache->response[ $this->name ] ) ) {
155
156
			$version_info = $this->get_cached_version_info();
157
158
			if ( false === $version_info ) {
159
				$version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
160
161
				$this->set_version_info_cache( $version_info );
0 ignored issues
show
Documentation introduced by
$version_info is of type false|object, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
162
			}
163
164
			if ( ! is_object( $version_info ) ) {
165
				return;
166
			}
167
168
			if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
169
170
				$update_cache->response[ $this->name ] = $version_info;
171
172
			}
173
174
			$update_cache->last_checked = current_time( 'timestamp' );
175
			$update_cache->checked[ $this->name ] = $this->version;
176
177
			set_site_transient( 'update_plugins', $update_cache );
178
179
		} else {
180
181
			$version_info = $update_cache->response[ $this->name ];
182
183
		}
184
185
		// Restore our filter
186
		add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
187
188
		if ( ! empty( $update_cache->response[ $this->name ] ) && version_compare( $this->version, $version_info->new_version, '<' ) ) {
189
190
			// build a plugin list row, with update notification
191
			$wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
0 ignored issues
show
Unused Code introduced by
$wp_list_table is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
192
			# <tr class="plugin-update-tr"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange">
193
			echo '<tr class="plugin-update-tr" id="' . $this->slug . '-update" data-slug="' . $this->slug . '" data-plugin="' . $this->slug . '/' . $file . '">';
194
			echo '<td colspan="3" class="plugin-update colspanchange">';
195
			echo '<div class="update-message notice inline notice-warning notice-alt">';
196
197
			$changelog_link = self_admin_url( 'index.php?edd_sl_action=view_plugin_changelog&plugin=' . $this->name . '&slug=' . $this->slug . '&TB_iframe=true&width=772&height=911' );
198
199
			if ( empty( $version_info->download_link ) ) {
200
				printf(
201
					__( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s.', 'easy-digital-downloads' ),
202
					esc_html( $version_info->name ),
203
					'<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
204
					esc_html( $version_info->new_version ),
205
					'</a>'
206
				);
207
			} else {
208
				printf(
209
					__( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s or %5$supdate now%6$s.', 'easy-digital-downloads' ),
210
					esc_html( $version_info->name ),
211
					'<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
212
					esc_html( $version_info->new_version ),
213
					'</a>',
214
					'<a href="' . esc_url( wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $this->name, 'upgrade-plugin_' . $this->name ) ) .'">',
215
					'</a>'
216
				);
217
			}
218
219
			do_action( "in_plugin_update_message-{$file}", $plugin, $version_info );
220
221
			echo '</div></td></tr>';
222
		}
223
	}
224
225
	/**
226
	 * Updates information on the "View version x.x details" page with custom data.
227
	 *
228
	 * @uses api_request()
229
	 *
230
	 * @param mixed   $_data
231
	 * @param string  $_action
232
	 * @param object  $_args
233
	 * @return object $_data
234
	 */
235
	public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
236
237
		if ( $_action != 'plugin_information' ) {
238
239
			return $_data;
240
241
		}
242
243
		if ( ! isset( $_args->slug ) || ( $_args->slug != $this->slug ) ) {
244
245
			return $_data;
246
247
		}
248
249
		$to_send = array(
250
			'slug'   => $this->slug,
251
			'is_ssl' => is_ssl(),
252
			'fields' => array(
253
				'banners' => array(),
254
				'reviews' => false
255
			)
256
		);
257
258
		$cache_key = 'edd_api_request_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
259
260
		// Get the transient where we store the api request for this plugin for 24 hours
261
		$edd_api_request_transient = $this->get_cached_version_info( $cache_key );
262
263
		//If we have no transient-saved value, run the API, set a fresh transient with the API value, and return that value too right now.
264
		if ( empty( $edd_api_request_transient ) ) {
265
266
			$api_response = $this->api_request( 'plugin_information', $to_send );
267
268
			// Expires in 3 hours
269
			$this->set_version_info_cache( $api_response, $cache_key );
0 ignored issues
show
Documentation introduced by
$api_response is of type false|object, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
270
271
			if ( false !== $api_response ) {
272
				$_data = $api_response;
273
			}
274
275
		} else {
276
			$_data = $edd_api_request_transient;
277
		}
278
279
		// Convert sections into an associative array, since we're getting an object, but Core expects an array.
280
		if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) {
281
			$new_sections = array();
282
			foreach ( $_data->sections as $key => $key ) {
283
				$new_sections[ $key ] = $key;
284
			}
285
286
			$_data->sections = $new_sections;
287
		}
288
289
		// Convert banners into an associative array, since we're getting an object, but Core expects an array.
290
		if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) {
291
			$new_banners = array();
292
			foreach ( $_data->banners as $key => $key ) {
293
				$new_banners[ $key ] = $key;
294
			}
295
296
			$_data->banners = $new_banners;
297
		}
298
299
		return $_data;
300
	}
301
302
	/**
303
	 * Disable SSL verification in order to prevent download update failures
304
	 *
305
	 * @param array   $args
306
	 * @param string  $url
307
	 * @return object $array
308
	 */
309
	public function http_request_args( $args, $url ) {
310
		// If it is an https request and we are performing a package download, disable ssl verification
311
		if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
312
			$args['sslverify'] = false;
313
		}
314
		return $args;
315
	}
316
317
	/**
318
	 * Calls the API and, if successfull, returns the object delivered by the API.
319
	 *
320
	 * @uses get_bloginfo()
321
	 * @uses wp_remote_post()
322
	 * @uses is_wp_error()
323
	 *
324
	 * @param string  $_action The requested action.
325
	 * @param array   $_data   Parameters for the API action.
326
	 * @return false|object
327
	 */
328
	private function api_request( $_action, $_data ) {
0 ignored issues
show
Unused Code introduced by
The parameter $_action is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
329
330
		global $wp_version;
331
332
		$data = array_merge( $this->api_data, $_data );
333
334
		if ( $data['slug'] != $this->slug ) {
335
			return;
336
		}
337
338
		if( $this->api_url == trailingslashit (home_url() ) ) {
339
			return false; // Don't allow a plugin to ping itself
340
		}
341
342
		$api_params = array(
343
			'edd_action' => 'get_version',
344
			'license'    => ! empty( $data['license'] ) ? $data['license'] : '',
345
			'item_name'  => isset( $data['item_name'] ) ? $data['item_name'] : false,
346
			'item_id'    => isset( $data['item_id'] ) ? $data['item_id'] : false,
347
			'version'    => isset( $data['version'] ) ? $data['version'] : false,
348
			'slug'       => $data['slug'],
349
			'author'     => $data['author'],
350
			'url'        => home_url(),
351
			'beta'       => ! empty( $data['beta'] ),
352
		);
353
354
		$request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => false, 'body' => $api_params ) );
355
356
		if ( ! is_wp_error( $request ) ) {
357
			$request = json_decode( wp_remote_retrieve_body( $request ) );
358
		}
359
360
		if ( $request && isset( $request->sections ) ) {
361
			$request->sections = maybe_unserialize( $request->sections );
362
		} else {
363
			$request = false;
364
		}
365
366
		if ( $request && isset( $request->banners ) ) {
367
			$request->banners = maybe_unserialize( $request->banners );
368
		}
369
370
		if( ! empty( $request->sections ) ) {
371
			foreach( $request->sections as $key => $section ) {
372
				$request->$key = (array) $section;
373
			}
374
		}
375
376
		return $request;
377
	}
378
379
	public function show_changelog() {
380
381
		global $edd_plugin_data;
382
383
		if( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action'] ) {
384
			return;
385
		}
386
387
		if( empty( $_REQUEST['plugin'] ) ) {
388
			return;
389
		}
390
391
		if( empty( $_REQUEST['slug'] ) ) {
392
			return;
393
		}
394
395
		if( ! current_user_can( 'update_plugins' ) ) {
396
			wp_die( __( 'You do not have permission to install plugin updates', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) );
397
		}
398
399
		$data         = $edd_plugin_data[ $_REQUEST['slug'] ];
400
		$beta         = ! empty( $data['beta'] ) ? true : false;
401
		$cache_key    = md5( 'edd_plugin_' . sanitize_key( $_REQUEST['plugin'] ) . '_' . $beta . '_version_info' );
402
		$version_info = $this->get_cached_version_info( $cache_key );
403
404
		if( false === $version_info ) {
405
406
			$api_params = array(
407
				'edd_action' => 'get_version',
408
				'item_name'  => isset( $data['item_name'] ) ? $data['item_name'] : false,
409
				'item_id'    => isset( $data['item_id'] ) ? $data['item_id'] : false,
410
				'slug'       => $_REQUEST['slug'],
411
				'author'     => $data['author'],
412
				'url'        => home_url(),
413
				'beta'       => ! empty( $data['beta'] )
414
			);
415
416
			$request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => false, 'body' => $api_params ) );
417
418
			if ( ! is_wp_error( $request ) ) {
419
				$version_info = json_decode( wp_remote_retrieve_body( $request ) );
420
			}
421
422
423
			if ( ! empty( $version_info ) && isset( $version_info->sections ) ) {
424
				$version_info->sections = maybe_unserialize( $version_info->sections );
425
			} else {
426
				$version_info = false;
427
			}
428
429
			if( ! empty( $version_info ) ) {
430
				foreach( $version_info->sections as $key => $section ) {
431
					$version_info->$key = (array) $section;
432
				}
433
			}
434
435
			$this->set_version_info_cache( $version_info, $cache_key );
436
437
		}
438
439
		if( ! empty( $version_info ) && isset( $version_info->sections['changelog'] ) ) {
440
			echo '<div style="background:#fff;padding:10px;">' . $version_info->sections['changelog'] . '</div>';
441
		}
442
443
		exit;
444
	}
445
446
	public function get_cached_version_info( $cache_key = '' ) {
447
448
		if( empty( $cache_key ) ) {
449
			$cache_key = $this->cache_key;
450
		}
451
452
		$cache = get_option( $cache_key );
453
454
		if( empty( $cache['timeout'] ) || current_time( 'timestamp' ) > $cache['timeout'] ) {
455
			return false; // Cache is expired
456
		}
457
458
		return json_decode( $cache['value'] );
459
460
	}
461
462
	public function set_version_info_cache( $value = '', $cache_key = '' ) {
463
464
		if( empty( $cache_key ) ) {
465
			$cache_key = $this->cache_key;
466
		}
467
468
		$data = array(
469
			'timeout' => strtotime( '+3 hours', current_time( 'timestamp' ) ),
470
			'value'   => json_encode( $value )
471
		);
472
473
		update_option( $cache_key, $data );
474
475
	}
476
477
}