Completed
Push — master ( 6395bc...9c520e )
by Julien
02:45
created

TITAN_EDD_SL_Plugin_Updater   B

Complexity

Total Complexity 54

Size/Duplication

Total Lines 357
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 1
Bugs 0 Features 1
Metric Value
wmc 54
c 1
b 0
f 1
lcom 1
cbo 0
dl 0
loc 357
rs 7.0642

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 1
A init() 0 9 1
C check_update() 0 29 8
D show_update_notification() 0 87 13
B plugins_api_filter() 0 32 5
A http_request_args() 0 7 3
D api_request() 0 38 9
C show_changelog() 0 57 14

How to fix   Complexity   

Complex Class

Complex classes like TITAN_EDD_SL_Plugin_Updater often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TITAN_EDD_SL_Plugin_Updater, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
// uncomment this line for testing
4
//set_site_transient( 'update_plugins', null );
5
6
// Exit if accessed directly
7
if ( ! defined( 'ABSPATH' ) ) exit;
8
9
/**
10
 * Allows plugins to use their own update API.
11
 *
12
 * @author Pippin Williamson
13
 * @version 1.6.3
14
 */
15
class TITAN_EDD_SL_Plugin_Updater {
16
	private $api_url   = '';
17
	private $api_data  = array();
18
	private $name      = '';
19
	private $slug      = '';
20
	private $version   = '';
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
	function __construct( $_api_url, $_plugin_file, $_api_data = null ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
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
42
		$edd_plugin_data[ $this->slug ] = $this->api_data;
43
44
		// Set up hooks.
45
		$this->init();
46
47
	}
48
49
	/**
50
	 * Set up WordPress filters to hook into WP's update process.
51
	 *
52
	 * @uses add_filter()
53
	 *
54
	 * @return void
55
	 */
56
	public function init() {
57
58
		add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
59
		add_filter( 'plugins_api', array( $this, 'plugins_api_filter' ), 10, 3 );
60
		remove_action( 'after_plugin_row_' . $this->name, 'wp_plugin_update_row', 10, 2 );
61
		add_action( 'after_plugin_row_' . $this->name, array( $this, 'show_update_notification' ), 10, 2 );
62
		add_action( 'admin_init', array( $this, 'show_changelog' ) );
63
64
	}
65
66
	/**
67
	 * Check for Updates at the defined API endpoint and modify the update array.
68
	 *
69
	 * This function dives into the update API just when WordPress creates its update array,
70
	 * then adds a custom API call and injects the custom plugin data retrieved from the API.
71
	 * It is reassembled from parts of the native WordPress plugin update code.
72
	 * See wp-includes/update.php line 121 for the original wp_update_plugins() function.
73
	 *
74
	 * @uses api_request()
75
	 *
76
	 * @param array   $_transient_data Update array build by WordPress.
77
	 * @return array Modified update array with custom plugin data.
78
	 */
79
	function check_update( $_transient_data ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
80
81
		global $pagenow;
82
83
		if ( ! is_object( $_transient_data ) ) {
84
			$_transient_data = new stdClass;
85
		}
86
87
		if ( 'plugins.php' == $pagenow && is_multisite() ) {
88
			return $_transient_data;
89
		}
90
91
		$version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug ) );
92
93
		if ( false !== $version_info && is_object( $version_info ) && isset( $version_info->new_version ) ) {
94
95
			if ( version_compare( $this->version, $version_info->new_version, '<' ) ) {
96
97
				$_transient_data->response[ $this->name ] = $version_info;
98
99
			}
100
101
			$_transient_data->last_checked           = time();
102
			$_transient_data->checked[ $this->name ] = $this->version;
103
104
		}
105
106
		return $_transient_data;
107
	}
108
109
	/**
110
	 * show update nofication row -- needed for multisite subsites, because WP won't tell you otherwise!
111
	 *
112
	 * @param string  $file
113
	 * @param array   $plugin
114
	 */
115
	public function show_update_notification( $file, $plugin ) {
116
117
		if( ! current_user_can( 'update_plugins' ) ) {
118
			return;
119
		}
120
121
		if( ! is_multisite() ) {
122
			return;
123
		}
124
125
		if ( $this->name != $file ) {
126
			return;
127
		}
128
129
		// Remove our filter on the site transient
130
		remove_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ), 10 );
131
132
		$update_cache = get_site_transient( 'update_plugins' );
133
		
134
		$update_cache = is_object( $update_cache ) ? $update_cache : new stdClass();
135
136
		if ( empty( $update_cache->response ) || empty( $update_cache->response[ $this->name ] ) ) {
137
138
			$cache_key    = md5( 'edd_plugin_' . sanitize_key( $this->name ) . '_version_info' );
139
			$version_info = get_transient( $cache_key );
140
141
			if( false === $version_info ) {
142
143
				$version_info = $this->api_request( 'plugin_latest_version', array( 'slug' => $this->slug ) );
144
145
				set_transient( $cache_key, $version_info, 3600 );
146
			}
147
148
			if( ! is_object( $version_info ) ) {
149
				return;
150
			}
151
152
			if( version_compare( $this->version, $version_info->new_version, '<' ) ) {
153
154
				$update_cache->response[ $this->name ] = $version_info;
155
156
			}
157
158
			$update_cache->last_checked = time();
159
			$update_cache->checked[ $this->name ] = $this->version;
160
161
			set_site_transient( 'update_plugins', $update_cache );
162
163
		} else {
164
165
			$version_info = $update_cache->response[ $this->name ];
166
167
		}
168
169
		// Restore our filter
170
		add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'check_update' ) );
171
172
		if ( ! empty( $update_cache->response[ $this->name ] ) && version_compare( $this->version, $version_info->new_version, '<' ) ) {
173
174
			// build a plugin list row, with update notification
175
			$wp_list_table = _get_list_table( 'WP_Plugins_List_Table' );
176
			echo '<tr class="plugin-update-tr"><td colspan="' . $wp_list_table->get_column_count() . '" class="plugin-update colspanchange"><div class="update-message">';
177
178
			$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' );
179
180
			if ( empty( $version_info->download_link ) ) {
181
				printf(
182
					__( 'There is a new version of %1$s available. <a target="_blank" class="thickbox" href="%2$s">View version %3$s details</a>.', 'easy-digital-downloads' ),
183
					esc_html( $version_info->name ),
184
					esc_url( $changelog_link ),
185
					esc_html( $version_info->new_version )
186
				);
187
			} else {
188
				printf(
189
					__( 'There is a new version of %1$s available. <a target="_blank" class="thickbox" href="%2$s">View version %3$s details</a> or <a href="%4$s">update now</a>.', 'easy-digital-downloads' ),
190
					esc_html( $version_info->name ),
191
					esc_url( $changelog_link ),
192
					esc_html( $version_info->new_version ),
193
					esc_url( wp_nonce_url( self_admin_url( 'update.php?action=upgrade-plugin&plugin=' ) . $this->name, 'upgrade-plugin_' . $this->name ) )
194
				);
195
			}
196
197
			do_action( "in_plugin_update_message-{$file}", $plugin, $version_info );
198
199
			echo '</div></td></tr>';
200
		}
201
	}
202
203
204
	/**
205
	 * Updates information on the "View version x.x details" page with custom data.
206
	 *
207
	 * @uses api_request()
208
	 *
209
	 * @param mixed   $_data
210
	 * @param string  $_action
211
	 * @param object  $_args
212
	 * @return object $_data
213
	 */
214
	function plugins_api_filter( $_data, $_action = '', $_args = null ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
215
216
217
		if ( $_action != 'plugin_information' ) {
218
219
			return $_data;
220
221
		}
222
223
		if ( ! isset( $_args->slug ) || ( $_args->slug != $this->slug ) ) {
224
225
			return $_data;
226
227
		}
228
229
		$to_send = array(
230
			'slug'   => $this->slug,
231
			'is_ssl' => is_ssl(),
232
			'fields' => array(
233
				'banners' => false, // These will be supported soon hopefully
234
				'reviews' => false
235
			)
236
		);
237
238
		$api_response = $this->api_request( 'plugin_information', $to_send );
239
240
		if ( false !== $api_response ) {
241
			$_data = $api_response;
242
		}
243
244
		return $_data;
245
	}
246
247
248
	/**
249
	 * Disable SSL verification in order to prevent download update failures
250
	 *
251
	 * @param array   $args
252
	 * @param string  $url
253
	 * @return object $array
254
	 */
255
	function http_request_args( $args, $url ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
256
		// If it is an https request and we are performing a package download, disable ssl verification
257
		if ( strpos( $url, 'https://' ) !== false && strpos( $url, 'edd_action=package_download' ) ) {
258
			$args['sslverify'] = false;
259
		}
260
		return $args;
261
	}
262
263
	/**
264
	 * Calls the API and, if successfull, returns the object delivered by the API.
265
	 *
266
	 * @uses get_bloginfo()
267
	 * @uses wp_remote_post()
268
	 * @uses is_wp_error()
269
	 *
270
	 * @param string  $_action The requested action.
271
	 * @param array   $_data   Parameters for the API action.
272
	 * @return false|object
273
	 */
274
	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...
275
276
		global $wp_version;
277
278
		$data = array_merge( $this->api_data, $_data );
279
280
		if ( $data['slug'] != $this->slug ) {
281
			return;
282
		}
283
284
		if( $this->api_url == home_url() ) {
285
			return false; // Don't allow a plugin to ping itself
286
		}
287
288
		$api_params = array(
289
			'edd_action' => 'get_version',
290
			'license'    => ! empty( $data['license'] ) ? $data['license'] : '',
291
			'item_name'  => isset( $data['item_name'] ) ? $data['item_name'] : false,
292
			'item_id'    => isset( $data['item_id'] ) ? $data['item_id'] : false,
293
			'slug'       => $data['slug'],
294
			'author'     => $data['author'],
295
			'url'        => home_url()
296
		);
297
298
		$request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => false, 'body' => $api_params ) );
299
300
		if ( ! is_wp_error( $request ) ) {
301
			$request = json_decode( wp_remote_retrieve_body( $request ) );
302
		}
303
304
		if ( $request && isset( $request->sections ) ) {
305
			$request->sections = maybe_unserialize( $request->sections );
306
		} else {
307
			$request = false;
308
		}
309
310
		return $request;
311
	}
312
313
	public function show_changelog() {
314
315
		global $edd_plugin_data;
316
317
		if( empty( $_REQUEST['edd_sl_action'] ) || 'view_plugin_changelog' != $_REQUEST['edd_sl_action'] ) {
318
			return;
319
		}
320
321
		if( empty( $_REQUEST['plugin'] ) ) {
322
			return;
323
		}
324
325
		if( empty( $_REQUEST['slug'] ) ) {
326
			return;
327
		}
328
329
		if( ! current_user_can( 'update_plugins' ) ) {
330
			wp_die( __( 'You do not have permission to install plugin updates', 'easy-digital-downloads' ), __( 'Error', 'easy-digital-downloads' ), array( 'response' => 403 ) );
331
		}
332
333
		$data         = $edd_plugin_data[ $_REQUEST['slug'] ];
334
		$cache_key    = md5( 'edd_plugin_' . sanitize_key( $_REQUEST['plugin'] ) . '_version_info' );
335
		$version_info = get_transient( $cache_key );
336
337
		if( false === $version_info ) {
338
339
			$api_params = array(
340
				'edd_action' => 'get_version',
341
				'item_name'  => isset( $data['item_name'] ) ? $data['item_name'] : false,
342
				'item_id'    => isset( $data['item_id'] ) ? $data['item_id'] : false,
343
				'slug'       => $_REQUEST['slug'],
344
				'author'     => $data['author'],
345
				'url'        => home_url()
346
			);
347
348
			$request = wp_remote_post( $this->api_url, array( 'timeout' => 15, 'sslverify' => false, 'body' => $api_params ) );
349
350
			if ( ! is_wp_error( $request ) ) {
351
				$version_info = json_decode( wp_remote_retrieve_body( $request ) );
352
			}
353
354
			if ( ! empty( $version_info ) && isset( $version_info->sections ) ) {
355
				$version_info->sections = maybe_unserialize( $version_info->sections );
356
			} else {
357
				$version_info = false;
358
			}
359
360
			set_transient( $cache_key, $version_info, 3600 );
361
362
		}
363
364
		if( ! empty( $version_info ) && isset( $version_info->sections['changelog'] ) ) {
365
			echo '<div style="background:#fff;padding:10px;">' . $version_info->sections['changelog'] . '</div>';
366
		}
367
368
		exit;
369
	}
370
371
}