Completed
Pull Request — master (#2223)
by Justin
06:02
created

WPSC_Tracking   D

Complexity

Total Complexity 80

Size/Duplication

Total Lines 528
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
dl 0
loc 528
rs 4.8717
c 0
b 0
f 0
wmc 80
lcom 1
cbo 2

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
B schedule_send() 0 13 5
A tracking_allowed() 0 5 1
A get_last_send() 0 3 1
D send_data() 0 44 9
A check_for_optin() 0 10 1
A check_for_optout() 0 12 2
C admin_notice() 0 32 7
B setup_data() 0 41 1
A get_theme_info() 0 5 2
B get_wordpress_info() 0 18 5
D get_server_info() 0 30 9
C get_all_plugins() 0 43 8
A get_wpec_info() 0 9 1
A get_user_counts() 0 13 2
A get_product_counts() 0 8 1
B get_order_counts() 0 26 4
C get_active_payment_gateways() 0 28 7
B get_active_shipping_methods() 0 16 5
A get_all_template_overrides() 0 11 2
B wpsc_let_to_num() 0 17 6

How to fix   Complexity   

Complex Class

Complex classes like WPSC_Tracking 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 WPSC_Tracking, and based on these observations, apply Extract Interface, too.

1
<?php
0 ignored issues
show
Coding Style introduced by
End of line character is invalid; expected "\n" but found "\r\n"
Loading history...
2
/**
3
 * Tracking functions for reporting plugin usage to the WPEC site for users that have opted in
4
 *
5
 * @package     WPEC
6
 * @subpackage  Admin
7
 * @copyright   Copyright (c) 2017
8
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
 * @since       3.12
10
*/
11
12
// Exit if accessed directly
13
if ( ! defined( 'ABSPATH' ) ) exit;
14
15
/**
16
 * Usage tracking
17
 *
18
 * @access public
19
 * @since  3.12
20
 * @return void
21
 */
22
class WPSC_Tracking {
23
24
	/**
25
	 * The data to send to the WPEC site
26
	 *
27
	 * @access private
28
	 */
29
	private $data;
30
	
31
	/**
32
	 * Where we are sending the data to
33
	 *
34
	 * @access private
35
	 */	
36
	private $api_url = 'https://wpecommerce.org/';
37
	
38
	/**
39
	 * Get things going
40
	 *
41
	 * @access public
42
	 */
43
	public function __construct() {
44
		add_action( 'init', array( $this, 'schedule_send' ) );
45
		//add_action( 'edd_settings_general_sanitize', array( $this, 'check_for_settings_optin' ) );
46
		add_action( 'wpec_opt_into_tracking', array( $this, 'check_for_optin' ) );
47
		add_action( 'wpec_opt_out_of_tracking', array( $this, 'check_for_optout' ) );
48
		add_action( 'admin_notices', array( $this, 'admin_notice' ) );
49
	}
50
	
51
	/**
52
	 * Schedule a weekly checkin
53
	 *
54
	 * @access public
55
	 * @return void
56
	 */
57
	public function schedule_send() {
58
		
59
		if ( isset( $_REQUEST['wpec_tracking_action'] ) && ( $_REQUEST['wpec_tracking_action'] == 'opt_into_tracking' ) ) {
60
			do_action( 'wpec_opt_into_tracking' );		
61
		}
62
		
63
		if ( isset( $_REQUEST['wpec_tracking_action'] ) && ( $_REQUEST['wpec_tracking_action'] == 'opt_out_of_tracking' ) ) {
64
			do_action( 'wpec_opt_out_of_tracking' );		
65
		}
66
				
67
		// We send once a week (while tracking is allowed) to check in, which can be used to determine active sites
68
		add_action( 'wpsc_weekly_cron_task', array( $this, 'send_data' ) );
69
	}
70
	
71
	/**
72
	 * Check if the user has opted into tracking
73
	 *
74
	 * @access private
75
	 * @return bool
76
	 */
77
	private function tracking_allowed() {
78
		$allow_tracking = get_option( 'wpsc_usage_tracking', false );
79
		
80
		return (bool) $allow_tracking;
81
	}
82
	
83
	/**
84
	 * Get the last time a checkin was sent
85
	 *
86
	 * @access private
87
	 * @return false|string
88
	 */
89
	private function get_last_send() {
90
		return get_option( 'wpsc_usage_tracking_last_send' );
91
	}
92
	
93
	/**
94
	 * Send the data to the WPEC server
95
	 *
96
	 * @access private
97
	 * @return void
98
	 */	
99
	public function send_data( $override = false ) {
100
		
101
		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
102
			return;
103
		}
104
		
105
		$home_url = trailingslashit( home_url() );
106
		
107
		// Allows us to stop our own site from checking in, and a filter for our additional sites
108
		if ( true === apply_filters( 'wpec_disable_tracking_checkin', false ) ) {
109
			return false;
110
		}
111
		
112
		if( ! $this->tracking_allowed() && ! $override ) {
113
			return false;
114
		}
115
116
		// Send a maximum of once per week
117
		$last_send = $this->get_last_send();
118
		
119
		if( is_numeric( $last_send ) && $last_send > strtotime( '-1 week' ) ) {
120
			return false;
121
		}
122
		
123
		$this->setup_data();
124
		
125
		$request = wp_remote_post( $this->api_url . '?wpec_tracking_action=checkin', array(
126
			'method'      => 'POST',
127
			'timeout'     => 20,
128
			'redirection' => 5,
129
			'httpversion' => '1.1',
130
			'blocking'    => true,
131
			'body'        => $this->data,
132
			'user-agent'  => 'WPEC/' . WPSC_VERSION . '; ' . get_bloginfo( 'url' )
133
		) );
134
		
135
		if( is_wp_error( $request ) ) {
136
			return $request;
137
		}
138
		
139
		update_option( 'wpsc_usage_tracking_last_send', time() );
140
		
141
		return true;
142
	}
143
	
144
	/**
145
	 * Check for a new opt-in via the admin notice
146
	 *
147
	 * @access public
148
	 * @return void
149
	 */
150
	public function check_for_optin( $data ) {
0 ignored issues
show
Unused Code introduced by
The parameter $data 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...
151
				
152
		update_option( 'wpsc_usage_tracking', '1' );
153
		
154
		$this->send_data( true );
155
		
156
		update_option( 'wpsc_usage_tracking_notice', '1' );
157
		
158
		wp_redirect( remove_query_arg( 'wpec_tracking_action' ) ); exit;
159
	}
160
	/**
161
	 * Check for opt-out via the admin notice
162
	 *
163
	 * @access public
164
	 * @return void
165
	 */
166
	public function check_for_optout( $data ) {
0 ignored issues
show
Unused Code introduced by
The parameter $data 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...
167
168
		$allowed = get_option( 'wpsc_usage_tracking', false );
169
		
170
		if( $allowed ) {
171
			delete_option( 'wpsc_usage_tracking' );
172
		}
173
		
174
		update_option( 'wpsc_usage_tracking_notice', '1' );
175
		
176
		wp_redirect( remove_query_arg( 'wpec_tracking_action' ) ); exit;
177
	}
178
	
179
	/**
180
	 * Display the admin notice to users that have not opted-in or out
181
	 *
182
	 * @access public
183
	 * @return void
184
	 */
185
	public function admin_notice() {
186
		$hide_notice = get_option( 'wpsc_usage_tracking_notice' );
187
		
188
		if ( $hide_notice ) {
189
			return;
190
		}
191
		
192
		if ( get_option( 'wpsc_usage_tracking', false ) ) {
193
			return;
194
		}
195
		
196
		if ( ! current_user_can( 'manage_options' ) ) {
197
			return;
198
		}
199
		
200
		if (
201
			stristr( network_site_url( '/' ), 'dev'       ) !== false ||
202
			stristr( network_site_url( '/' ), 'localhost' ) !== false ||
203
			stristr( network_site_url( '/' ), ':8888'     ) !== false // This is common with MAMP on OS X
204
		) {
205
			update_option( 'wpsc_usage_tracking_notice', '1' );
206
		} else {
207
			$optin_url  = add_query_arg( 'wpec_tracking_action', 'opt_into_tracking' );
208
			$optout_url = add_query_arg( 'wpec_tracking_action', 'opt_out_of_tracking' );
209
			$extensions_url = $this->api_url . 'store/';
210
			echo '<div class="updated"><p>';
211
				echo sprintf( __( 'Allow WP eCommerce to track plugin usage? Opt-in to tracking and our newsletter and immediately be emailed a 20%s discount to the WPEC shop, valid towards the <a href="%s" target="_blank">purchase of extensions</a>. No sensitive data is tracked.', 'wp-e-commerce' ), '%', $extensions_url );
212
				echo '&nbsp;<a href="' . esc_url( $optin_url ) . '" class="button-secondary">' . __( 'Allow', 'wp-e-commerce' ) . '</a>';
213
				echo '&nbsp;<a href="' . esc_url( $optout_url ) . '" class="button-secondary">' . __( 'Do not allow', 'wp-e-commerce' ) . '</a>';
214
			echo '</p></div>';
215
		}
216
	}
217
	
218
	/**
219
	 * Setup the data that is going to be tracked
220
	 *
221
	 * @access private
222
	 * @return void
223
	 */
224
	private function setup_data() {
225
		
226
		$data = array();
227
		
228
		// General site info
229
		$data['url']                = home_url();
230
		$data['email']              = get_option( 'admin_email' );
231
		
232
		// Theme info
233
		$data['theme']              = self::get_theme_info();
234
235
		// WordPress Info
236
		$data['wp']                 = self::get_wordpress_info();
237
		
238
		// Server Info
239
		$data['server']             = self::get_server_info();
240
241
		// Plugin info
242
		$all_plugins                = self::get_all_plugins();
243
		$data['active_plugins']     = $all_plugins['active_plugins'];
244
		$data['inactive_plugins']   = $all_plugins['inactive_plugins'];
245
246
		// WPEC Related Section
247
		$data['wpec']               = self::get_wpec_info();
248
249
		// Store count info
250
		$data['users']              = self::get_user_counts();
251
		$data['products']           = self::get_product_counts();
252
		$data['orders']             = self::get_order_counts();
253
		
254
		// Payment gateway info
255
		$data['gateways']           = self::get_active_payment_gateways();
256
257
		// Shipping method info
258
		$data['shipping_methods']   = self::get_active_shipping_methods();
259
		
260
		// Template overrides
261
		$data['template_overrides'] = self::get_all_template_overrides();
262
		
263
		$this->data = $data;
264
	}
265
	
266
	/**
267
	 * Get the current theme info, theme name and version.
268
	 * @return array
269
	 */
270
	public static function get_theme_info() {
271
		$theme_data        = wp_get_theme();
272
		$theme_child_theme = is_child_theme() ? 'Yes' : 'No';
273
		return array( 'name' => $theme_data->Name, 'version' => $theme_data->Version, 'child_theme' => $theme_child_theme );
274
	}	
275
276
	/**
277
	 * Get WordPress related data.
278
	 * @return array
279
	 */
280
	private static function get_wordpress_info() {
281
		$wp_data = array();
282
		
283
		$memory = self::wpsc_let_to_num( WP_MEMORY_LIMIT );
284
		
285
		if ( function_exists( 'memory_get_usage' ) ) {
286
			$system_memory = self::wpsc_let_to_num( @ini_get( 'memory_limit' ) );
0 ignored issues
show
Coding Style introduced by
Silencing errors is discouraged
Loading history...
287
			$memory        = max( $memory, $system_memory );
288
		}
289
		
290
		$wp_data['memory_limit'] = size_format( $memory );
291
		$wp_data['debug_mode']   = ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ? 'Yes' : 'No';
292
		$wp_data['locale']       = get_locale();
293
		$wp_data['version']      = get_bloginfo( 'version' );
294
		$wp_data['multisite']    = is_multisite() ? 'Yes' : 'No';
295
		
296
		return $wp_data;
297
	}
298
299
	/**
300
	 * Get server related info.
301
	 * @return array
302
	 */
303
	private static function get_server_info() {
304
		$server_data = array();
305
306
		if ( isset( $_SERVER['SERVER_SOFTWARE'] ) && ! empty( $_SERVER['SERVER_SOFTWARE'] ) ) {
307
			$server_data['software'] = $_SERVER['SERVER_SOFTWARE'];
308
		}
309
310
		if ( function_exists( 'phpversion' ) ) {
311
			$server_data['php_version'] = phpversion();
312
		}
313
314
		if ( function_exists( 'ini_get' ) ) {
315
			$server_data['php_post_max_size'] = size_format( self::wpsc_let_to_num( ini_get( 'post_max_size' ) ) );
316
			$server_data['php_time_limt'] = ini_get( 'max_execution_time' );
317
			$server_data['php_max_input_vars'] = ini_get( 'max_input_vars' );
318
			$server_data['php_suhosin'] = extension_loaded( 'suhosin' ) ? 'Yes' : 'No';
319
		}
320
321
		global $wpdb;
322
		
323
		$server_data['mysql_version'] = $wpdb->db_version();
324
325
		$server_data['php_max_upload_size'] = size_format( wp_max_upload_size() );
326
		$server_data['php_default_timezone'] = date_default_timezone_get();
327
		$server_data['php_soap'] = class_exists( 'SoapClient' ) ? 'Yes' : 'No';
328
		$server_data['php_fsockopen'] = function_exists( 'fsockopen' ) ? 'Yes' : 'No';
329
		$server_data['php_curl'] = function_exists( 'curl_init' ) ? 'Yes' : 'No';
330
331
		return $server_data;
332
	}
333
334
	/**
335
	 * Get all plugins grouped into activated or not.
336
	 * @return array
337
	 */
338
	private static function get_all_plugins() {
339
		
340
		// Ensure get_plugins function is loaded
341
		if( ! function_exists( 'get_plugins' ) ) {
342
			include ABSPATH . '/wp-admin/includes/plugin.php';
343
		}
344
345
		$plugins        	 = get_plugins();
346
		$active_plugins_keys = get_option( 'active_plugins', array() );
347
		$active_plugins 	 = array();
348
349
		foreach ( $plugins as $k => $v ) {
350
			// Take care of formatting the data how we want it.
351
			$formatted = array();
352
			$formatted['name'] = strip_tags( $v['Name'] );
353
			
354
			if ( isset( $v['Version'] ) ) {
355
				$formatted['version'] = strip_tags( $v['Version'] );
356
			}
357
			
358
			if ( isset( $v['Author'] ) ) {
359
				$formatted['author'] = strip_tags( $v['Author'] );
360
			}
361
			
362
			if ( isset( $v['Network'] ) ) {
363
				$formatted['network'] = strip_tags( $v['Network'] );
364
			}
365
			
366
			if ( isset( $v['PluginURI'] ) ) {
367
				$formatted['plugin_uri'] = strip_tags( $v['PluginURI'] );
368
			}
369
			
370
			if ( in_array( $k, $active_plugins_keys ) ) {
371
				// Remove active plugins from list so we can show active and inactive separately
372
				unset( $plugins[$k] );
373
				$active_plugins[$k] = $formatted;
374
			} else {
375
				$plugins[$k] = $formatted;
376
			}
377
		}
378
379
		return array( 'active_plugins' => $active_plugins, 'inactive_plugins' => $plugins );
380
	}
381
382
	private static function get_wpec_info() {
383
		$wpec_data = array();
384
		
385
		$wpec_data['version']      = WPSC_VERSION;
386
		$wpec_data['url']          = WPSC_URL;
387
		$wpec_data['debug']        = WPSC_DEBUG;
388
		
389
		return $wpec_data;
390
	}
391
	
392
	/**
393
	 * Get user totals based on user role.
394
	 * @return array
395
	 */
396
	private static function get_user_counts() {
397
		$user_count          = array();
398
		
399
		$user_count_data     = count_users();
400
		$user_count['total'] = $user_count_data['total_users'];
401
402
		// Get user count based on user role
403
		foreach ( $user_count_data['avail_roles'] as $role => $count ) {
404
			$user_count[ $role ] = $count;
405
		}
406
407
		return $user_count;
408
	}
409
	
410
	/**
411
	 * Get product totals based on product type.
412
	 * @return array
413
	 */
414
	private static function get_product_counts() {
415
		$product_count          = array();
416
		
417
		$product_count_data     = wp_count_posts( 'wpsc-product' );
418
		$product_count['total'] = $product_count_data->publish;
419
420
		return $product_count;
421
	}	
422
	
423
	/**
424
	 * Get order counts based on order status.
425
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
426
	 */
427
	private static function get_order_counts() {
428
		global $wpdb;
429
		
430
		$order_count      = array();
431
		$curr_year        = date('Y');
432
		
433
		$result = $wpdb->get_row( "SELECT FROM_UNIXTIME( DATE,  '%Y' ) AS year FROM `".WPSC_TABLE_PURCHASE_LOGS."` WHERE FROM_UNIXTIME( DATE,  '%Y' ) > 2000 AND processed IN ( 3, 4, 5 ) ORDER BY date ASC LIMIT 1 ", ARRAY_A );
434
		$start_year = $result['year'];
435
436
		if( $start_year ) {
437
			while ( $start_year <= $curr_year ) {
438
				$sql = $wpdb->prepare( "SELECT SUM(`totalprice`) as total, COUNT(*) as cnt FROM `".WPSC_TABLE_PURCHASE_LOGS."` WHERE `processed` IN (3,4,5) AND `date` BETWEEN %s AND %s", mktime( 0, 0, 0, 1, 1, $start_year ), mktime( 23, 59, 59, 12, 31, $start_year ) );
439
				$orders = $wpdb->get_row( $sql, ARRAY_A );
440
				
441
				if( $orders ) {
442
					$order_count["{$start_year}"] = array( 'orders' => $orders['cnt'], 'total' => $orders['total'] );
443
				}
444
				$start_year ++;
445
			}
446
		}
447
		
448
		$currency_data = WPSC_Countries::get_currency_data( get_option( 'currency_type' ), true );
449
		$order_count['currency'] = $currency_data['code'];
450
451
		return $order_count;
452
	}
453
	
454
	/**
455
	 * Get a list of all active payment gateways.
456
	 * @return array
457
	 */
458
	private static function get_active_payment_gateways() {
459
		$active_gateways = array();
460
		
461
		// First get merchant V2 gateways
462
		if ( _wpsc_is_merchant_v2_active() ) {
463
			$gateways = _wpsc_merchant_v2_get_active_gateways();
464
			
465
			if( $gateways ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $gateways of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
466
				foreach( $gateways as $id => $gateway ) {
467
					$active_gateways['mv2'][ $id ] = array( 'title' => $gateway['name'] );
468
				}
469
			}
470
		}
471
		
472
		// Merchant V3 gateways if any
473
		$gateways = WPSC_Payment_Gateways::get_active_gateways();
474
		
475
		if( $gateways ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $gateways of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
476
			foreach( $gateways as $id => $gateway ) {
477
				$meta = WPSC_Payment_Gateways::get_meta( $gateway );
478
				$name = isset( $meta['name'] ) ? $meta['name'] : $meta['class'];
479
				
480
				$active_gateways['mv3'][ $id ] = array( 'title' => $name );
481
			}
482
		}
483
		
484
		return $active_gateways;
485
	}
486
	
487
	/**
488
	 * Get a list of all active shipping methods.
489
	 * @return array
490
	 */
491
	private static function get_active_shipping_methods() {
492
		$active_methods   = array();
493
		
494
		if ( wpsc_is_shipping_enabled() ) {
495
			global $wpsc_shipping_modules;
496
497
			$custom_shipping = get_option( 'custom_shipping_options' );
498
499
			foreach ( (array) $custom_shipping as $id => $shipping ) {
500
				$module_title = isset( $wpsc_shipping_modules[$shipping] ) &&  is_callable( array( $wpsc_shipping_modules[ $shipping ], 'getName' ) ) ? $wpsc_shipping_modules[ $shipping ]->getName() : '';
501
				$active_methods[ $id ] = array( 'name' => $shipping, 'title' => $module_title );
502
			}
503
		}
504
505
		return $active_methods;
506
	}
507
508
	/**
509
	 * Look for any template override and return filenames.
510
	 * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|false? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
511
	 */
512
	private static function get_all_template_overrides() {
513
		$override_data  = array();
514
		
515
		$te = get_option( 'wpsc_get_active_theme_engine', '1.0' );
516
		
517
		if( '1.0' == $te ) {
518
			$override_data = wpsc_check_theme_location();
519
		}
520
521
		return $override_data;
522
	}
523
	
524
	/**
525
	 * let_to_num function.
526
	 *
527
	 * This function transforms the php.ini notation for numbers (like '2M') to an integer.
528
	 *
529
	 * @param $size
530
	 * @return int
531
	 */
532
	private static function wpsc_let_to_num( $size ) {
533
		$l   = substr( $size, -1 );
534
		$ret = substr( $size, 0, -1 );
535
		switch ( strtoupper( $l ) ) {
536
			case 'P':
537
				$ret *= 1024;
538
			case 'T':
539
				$ret *= 1024;
540
			case 'G':
541
				$ret *= 1024;
542
			case 'M':
543
				$ret *= 1024;
544
			case 'K':
545
				$ret *= 1024;
546
		}
547
		return $ret;
548
	}
549
}
550
551
$wpsc_tracking = new WPSC_Tracking;
552