Completed
Push — develop ( d6e54c...488d60 )
by Zack
17:10
created

EDD_SL_Plugin_Updater::show_changelog()   D

Complexity

Conditions 17
Paths 267

Size

Total Lines 67
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 38
nc 267
nop 0
dl 0
loc 67
rs 4.3163
c 0
b 0
f 0

How to fix   Long Method    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
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 13 and the first side effect is on line 5.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
namespace GV;
3
4
// Exit if accessed directly
5
if ( ! defined( 'ABSPATH' ) ) exit;
0 ignored issues
show
Coding Style Best Practice introduced by
It is generally a best practice to always use braces with control structures.

Adding braces to control structures avoids accidental mistakes as your code changes:

// Without braces (not recommended)
if (true)
    doSomething();

// Recommended
if (true) {
    doSomething();
}
Loading history...
6
7
/**
8
 * Allows plugins to use their own update API.
9
 *
10
 * @author Easy Digital Downloads
11
 * @version 1.6.14
12
 */
13
class EDD_SL_Plugin_Updater {
14
15
	private $api_url     = '';
16
	private $api_data    = array();
17
	private $name        = '';
18
	private $slug        = '';
19
	private $version     = '';
20
	private $wp_override = false;
21
	private $cache_key   = '';
22
23
	/**
24
	 * Class constructor.
25
	 *
26
	 * @uses plugin_basename()
27
	 * @uses hook()
28
	 *
29
	 * @param string  $_api_url     The URL pointing to the custom API endpoint.
30
	 * @param string  $_plugin_file Path to the plugin file.
31
	 * @param array   $_api_data    Optional data to send with API calls.
32
	 */
33
	public function __construct( $_api_url, $_plugin_file, $_api_data = null ) {
34
35
		global $edd_plugin_data;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
36
37
		$this->api_url     = trailingslashit( $_api_url );
38
		$this->api_data    = $_api_data;
39
		$this->name        = plugin_basename( $_plugin_file );
40
		$this->slug        = basename( $_plugin_file, '.php' );
41
		$this->version     = $_api_data['version'];
42
		$this->wp_override = isset( $_api_data['wp_override'] ) ? (bool) $_api_data['wp_override'] : false;
43
		$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...
44
		$this->cache_key   = md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
45
46
		$edd_plugin_data[ $this->slug ] = $this->api_data;
47
48
		// Set up hooks.
49
		$this->init();
50
51
	}
52
53
	/**
54
	 * Set up WordPress filters to hook into WP's update process.
55
	 *
56
	 * @uses add_filter()
57
	 *
58
	 * @return void
59
	 */
60
	public function init() {
61
62
		add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
63
		add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
64
		remove_action( 'after_plugin_row_' . $this->name, 'wp_plugin_update_row', 10 );
65
		add_action( 'after_plugin_row_' . $this->name, array( $this, 'show_update_notification' ), 10, 2 );
66
		add_action( 'admin_init', array( $this, 'show_changelog' ) );
67
68
	}
69
70
	/**
71
	 * Check for Updates at the defined API endpoint and modify the update array.
72
	 *
73
	 * This function dives into the update API just when WordPress creates its update array,
74
	 * then adds a custom API call and injects the custom plugin data retrieved from the API.
75
	 * It is reassembled from parts of the native WordPress plugin update code.
76
	 * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
77
	 *
78
	 * @uses api_request()
79
	 *
80
	 * @param array   $_transient_data Update array build by WordPress.
81
	 * @return array Modified update array with custom plugin data.
82
	 */
83
	public function check_update( $_transient_data ) {
84
85
		global $pagenow;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
86
87
		if ( ! is_object( $_transient_data ) ) {
88
			$_transient_data = new stdClass;
89
		}
90
91
		if ( 'plugins.php' == $pagenow && is_multisite() ) {
92
			return $_transient_data;
93
		}
94
95
		if ( ! empty( $_transient_data->response ) && ! empty( $_transient_data->response[ $this->name ] ) && false === $this->wp_override ) {
96
			return $_transient_data;
97
		}
98
99
		$version_info = $this->get_cached_version_info();
100
101
		if ( false === $version_info ) {
102
			$version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
103
104
			$this->set_version_info_cache( $version_info );
105
106
		}
107
108
		if ( false !== $version_info && is_object( $version_info ) && isset( $version_info->new_version ) ) {
109
110
			if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
111
112
				$_transient_data->response[ $this->name ] = $version_info;
113
114
			}
115
116
			$_transient_data->last_checked           = current_time( 'timestamp' );
117
			$_transient_data->checked[ $this->name ] = $this->version;
118
119
		}
120
121
		return $_transient_data;
122
	}
123
124
	/**
125
	 * show update nofication row -- needed for multisite subsites, because WP won't tell you otherwise!
126
	 *
127
	 * @param string  $file
128
	 * @param array   $plugin
129
	 */
130
	public function show_update_notification( $file, $plugin ) {
131
132
		if ( is_network_admin() ) {
133
			return;
134
		}
135
136
		if( ! current_user_can( 'update_plugins' ) ) {
137
			return;
138
		}
139
140
		if( ! is_multisite() ) {
141
			return;
142
		}
143
144
		if ( $this->name != $file ) {
145
			return;
146
		}
147
148
		// Remove our filter on the site transient
149
		remove_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ), 10 );
150
151
		$update_cache = get_site_transient( 'update_plugins' );
152
153
		$update_cache = is_object( $update_cache ) ? $update_cache : new stdClass();
154
155
		if ( empty( $update_cache->response ) || empty( $update_cache->response[ $this->name ] ) ) {
156
157
			$version_info = $this->get_cached_version_info();
158
159
			if ( false === $version_info ) {
160
				$version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug, 'beta' => $this->beta ) );
161
162
				$this->set_version_info_cache( $version_info );
163
			}
164
165
			if ( ! is_object( $version_info ) ) {
166
				return;
167
			}
168
169
			if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
170
171
				$update_cache->response[ $this->name ] = $version_info;
172
173
			}
174
175
			$update_cache->last_checked = current_time( 'timestamp' );
176
			$update_cache->checked[ $this->name ] = $this->version;
177
178
			set_site_transient( 'update_plugins', $update_cache );
179
180
		} else {
181
182
			$version_info = $update_cache->response[ $this->name ];
183
184
		}
185
186
		// Restore our filter
187
		add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
188
189
		if ( ! empty( $update_cache->response[ $this->name ] ) && version_compare( $this->version, $version_info->new_version, '<' ) ) {
190
191
			// build a plugin list row, with update notification
192
			$wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
193
			# <tr class="plugin-update-tr"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange">
194
			echo '<tr class="plugin-update-tr" id="' . $this->slug . '-update" data-slug="' . $this->slug . '" data-plugin="' . $this->slug . '/' . $file . '">';
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '$this'
Loading history...
introduced by
Expected next thing to be a escaping function, not '$file'
Loading history...
195
			echo '<td colspan="3" class="plugin-update colspanchange">';
196
			echo '<div class="update-message notice inline notice-warning notice-alt">';
197
198
			$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' );
199
200
			if ( empty( $version_info->download_link ) ) {
201
				printf(
202
					__( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s.', 'gravityview' ),
203
					esc_html( $version_info->name ),
204
					'<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
205
					esc_html( $version_info->new_version ),
206
					'</a>'
207
				);
208
			} else {
209
				printf(
210
					__( 'There is a new version of %1$s available. %2$sView version %3$s details%4$s or %5$supdate now%6$s.', 'gravityview' ),
211
					esc_html( $version_info->name ),
212
					'<a target="_blank" class="thickbox" href="' . esc_url( $changelog_link ) . '">',
213
					esc_html( $version_info->new_version ),
214
					'</a>',
215
					'<a href="' . esc_url( wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $this->name, 'upgrade-plugin_' . $this->name ) ) .'">',
216
					'</a>'
217
				);
218
			}
219
220
			do_action( "in_plugin_update_message-{$file}", $plugin, $version_info );
221
222
			echo '</div></td></tr>';
223
		}
224
	}
225
226
	/**
227
	 * Updates information on the "View version x.x details" page with custom data.
228
	 *
229
	 * @uses api_request()
230
	 *
231
	 * @param mixed   $_data
232
	 * @param string  $_action
233
	 * @param object  $_args
234
	 * @return object $_data
235
	 */
236
	public function plugins_api_filter( $_data, $_action = '', $_args = null ) {
237
238
		if ( $_action != 'plugin_information' ) {
0 ignored issues
show
introduced by
Found "!= '". Use Yoda Condition checks, you must
Loading history...
239
240
			return $_data;
241
242
		}
243
244
		if ( ! isset( $_args->slug ) || ( $_args->slug != $this->slug ) ) {
245
246
			return $_data;
247
248
		}
249
250
		$to_send = array(
251
			'slug'   => $this->slug,
252
			'is_ssl' => is_ssl(),
253
			'fields' => array(
254
				'banners' => array(),
255
				'reviews' => false
0 ignored issues
show
introduced by
Each line in an array declaration must end in a comma
Loading history...
256
			)
257
		);
258
259
		$cache_key = 'edd_api_request_' . md5( serialize( $this->slug . $this->api_data['license'] . $this->beta ) );
260
261
		// Get the transient where we store the api request for this plugin for 24 hours
262
		$edd_api_request_transient = $this->get_cached_version_info( $cache_key );
263
264
		//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.
265
		if ( empty( $edd_api_request_transient ) ) {
266
267
			$api_response = $this->api_request( 'plugin_information', $to_send );
268
269
			// Expires in 3 hours
270
			$this->set_version_info_cache( $api_response, $cache_key );
271
272
			if ( false !== $api_response ) {
273
				$_data = $api_response;
274
			}
275
276
		} else {
277
			$_data = $edd_api_request_transient;
278
		}
279
280
		// Convert sections into an associative array, since we're getting an object, but Core expects an array.
281
		if ( isset( $_data->sections ) && ! is_array( $_data->sections ) ) {
282
			$new_sections = array();
283
			foreach ( $_data->sections as $key => $value ) {
284
				$new_sections[ $key ] = $value;
285
			}
286
287
			$_data->sections = $new_sections;
288
		}
289
290
		// Convert banners into an associative array, since we're getting an object, but Core expects an array.
291
		if ( isset( $_data->banners ) && ! is_array( $_data->banners ) ) {
292
			$new_banners = array();
293
			foreach ( $_data->banners as $key => $value ) {
294
				$new_banners[ $key ] = $value;
295
			}
296
297
			$_data->banners = $new_banners;
298
		}
299
300
		return $_data;
301
	}
302
303
	/**
304
	 * Disable SSL verification in order to prevent download update failures
305
	 *
306
	 * @param array   $args
307
	 * @param string  $url
308
	 * @return object $array
309
	 */
310
	public function http_request_args( $args, $url ) {
311
312
		$verify_ssl = $this->verify_ssl();
313
		if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
0 ignored issues
show
introduced by
Found "!== false". Use Yoda Condition checks, you must
Loading history...
314
			$args['sslverify'] = $verify_ssl;
315
		}
316
		return $args;
317
318
	}
319
320
	/**
321
	 * Calls the API and, if successfull, returns the object delivered by the API.
322
	 *
323
	 * @uses get_bloginfo()
324
	 * @uses wp_remote_post()
325
	 * @uses is_wp_error()
326
	 *
327
	 * @param string  $_action The requested action.
328
	 * @param array   $_data   Parameters for the API action.
329
	 * @return false|object
330
	 */
331
	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...
332
333
		global $wp_version;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
334
335
		$data = array_merge( $this->api_data, $_data );
336
337
		if ( $data['slug'] != $this->slug ) {
338
			return;
339
		}
340
341
		if( $this->api_url == trailingslashit (home_url() ) ) {
0 ignored issues
show
Coding Style introduced by
Space before opening parenthesis of function call prohibited
Loading history...
Coding Style introduced by
Expected 1 spaces after opening bracket; 0 found
Loading history...
342
			return false; // Don't allow a plugin to ping itself
343
		}
344
345
		$api_params = array(
346
			'edd_action' => 'get_version',
347
			'license'    => ! empty( $data['license'] ) ? $data['license'] : '',
348
			'item_name'  => isset( $data['item_name'] ) ? $data['item_name'] : false,
349
			'item_id'    => isset( $data['item_id'] ) ? $data['item_id'] : false,
350
			'version'    => isset( $data['version'] ) ? $data['version'] : false,
351
			'slug'       => $data['slug'],
352
			'author'     => $data['author'],
353
			'url'        => home_url(),
354
			'beta'       => ! empty( $data['beta'] ),
355
		);
356
357
		$verify_ssl = $this->verify_ssl();
358
		$request    = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
359
360
		if ( ! is_wp_error( $request ) ) {
361
			$request = json_decode( wp_remote_retrieve_body( $request ) );
362
		}
363
364
		if ( $request && isset( $request->sections ) ) {
365
			$request->sections = maybe_unserialize( $request->sections );
366
		} else {
367
			$request = false;
368
		}
369
370
		if ( $request && isset( $request->banners ) ) {
371
			$request->banners = maybe_unserialize( $request->banners );
372
		}
373
374
		if( ! empty( $request->sections ) ) {
375
			foreach( $request->sections as $key => $section ) {
376
				$request->$key = (array) $section;
377
			}
378
		}
379
380
		return $request;
381
	}
382
383
	public function show_changelog() {
384
385
		global $edd_plugin_data;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
386
387
		if( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action'] ) {
0 ignored issues
show
introduced by
Detected access of super global var $_REQUEST, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_REQUEST
Loading history...
388
			return;
389
		}
390
391
		if( empty( $_REQUEST['plugin'] ) ) {
0 ignored issues
show
introduced by
Detected access of super global var $_REQUEST, probably need manual inspection.
Loading history...
392
			return;
393
		}
394
395
		if( empty( $_REQUEST['slug'] ) ) {
0 ignored issues
show
introduced by
Detected access of super global var $_REQUEST, probably need manual inspection.
Loading history...
396
			return;
397
		}
398
399
		if( ! current_user_can( 'update_plugins' ) ) {
400
			wp_die( __( 'You do not have permission to install plugin updates', 'gravityview' ), __( 'Error', 'gravityview' ), array( 'response' => 403 ) );
401
		}
402
403
		$data         = $edd_plugin_data[ $_REQUEST['slug'] ];
0 ignored issues
show
introduced by
Detected access of super global var $_REQUEST, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_REQUEST
Loading history...
404
		$beta         = ! empty( $data['beta'] ) ? true : false;
405
		$cache_key    = md5( 'edd_plugin_' . sanitize_key( $_REQUEST['plugin'] ) . '_' . $beta . '_version_info' );
0 ignored issues
show
introduced by
Detected access of super global var $_REQUEST, probably need manual inspection.
Loading history...
406
		$version_info = $this->get_cached_version_info( $cache_key );
407
408
		if( false === $version_info ) {
409
410
			$api_params = array(
411
				'edd_action' => 'get_version',
412
				'item_name'  => isset( $data['item_name'] ) ? $data['item_name'] : false,
413
				'item_id'    => isset( $data['item_id'] ) ? $data['item_id'] : false,
414
				'slug'       => $_REQUEST['slug'],
0 ignored issues
show
introduced by
Detected access of super global var $_REQUEST, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_REQUEST
Loading history...
415
				'author'     => $data['author'],
416
				'url'        => home_url(),
417
				'beta'       => ! empty( $data['beta'] )
418
			);
419
420
			$verify_ssl = $this->verify_ssl();
421
			$request    = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => $verify_ssl, 'body' => $api_params ) );
422
423
			if ( ! is_wp_error( $request ) ) {
424
				$version_info = json_decode( wp_remote_retrieve_body( $request ) );
425
			}
426
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
427
428
			if ( ! empty( $version_info ) && isset( $version_info->sections ) ) {
429
				$version_info->sections = maybe_unserialize( $version_info->sections );
430
			} else {
431
				$version_info = false;
432
			}
433
434
			if( ! empty( $version_info ) ) {
435
				foreach( $version_info->sections as $key => $section ) {
436
					$version_info->$key = (array) $section;
437
				}
438
			}
439
440
			$this->set_version_info_cache( $version_info, $cache_key );
441
442
		}
443
444
		if( ! empty( $version_info ) && isset( $version_info->sections['changelog'] ) ) {
445
			echo '<div style="background:#fff;padding:10px;">' . $version_info->sections['changelog'] . '</div>';
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '$version_info'
Loading history...
446
		}
447
448
		exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method show_changelog() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
449
	}
450
451
	public function get_cached_version_info( $cache_key = '' ) {
452
453
		if( empty( $cache_key ) ) {
454
			$cache_key = $this->cache_key;
455
		}
456
457
		$cache = get_option( $cache_key );
458
459
		if( empty( $cache['timeout'] ) || current_time( 'timestamp' ) > $cache['timeout'] ) {
460
			return false; // Cache is expired
461
		}
462
463
		return json_decode( $cache['value'] );
464
465
	}
466
467
	public function set_version_info_cache( $value = '', $cache_key = '' ) {
468
469
		if( empty( $cache_key ) ) {
470
			$cache_key = $this->cache_key;
471
		}
472
473
		$data = array(
474
			'timeout' => strtotime( '+3 hours', current_time( 'timestamp' ) ),
475
			'value'   => json_encode( $value )
476
		);
477
478
		update_option( $cache_key, $data, 'no' );
479
480
	}
481
482
	/**
483
	 * Returns if the SSL of the store should be verified.
484
	 *
485
	 * @since  1.6.13
486
	 * @return bool
487
	 */
488
	private function verify_ssl() {
489
		return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this );
490
	}
491
492
}
493