Completed
Push — master ( 6650f3...84c2e6 )
by Devin
16:35
created

Give_API   D

Complexity

Total Complexity 252

Size/Duplication

Total Lines 1887
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 29.77%
Metric Value
wmc 252
lcom 1
cbo 3
dl 0
loc 1887
ccs 267
cts 897
cp 0.2977
rs 4.4102

42 Methods

Rating   Name   Duplication   Size   Complexity  
B get_user() 0 26 5
A get_user_public_key() 0 17 3
A get_user_secret_key() 0 17 3
A missing_auth() 0 7 1
A invalid_auth() 0 7 1
A invalid_key() 0 7 1
A invalid_version() 0 7 1
B __construct() 0 32 3
A add_endpoint() 0 3 1
A query_vars() 0 19 1
A get_versions() 0 3 1
A get_queried_version() 0 3 1
A get_default_version() 0 12 3
B set_queried_version() 0 30 3
D validate_request() 0 34 10
A get_query_mode() 0 4 1
B set_query_mode() 0 27 3
A get_paged() 0 5 2
A per_page() 0 11 4
C get_customers() 0 81 10
B get_forms() 0 37 5
C get_form_data() 0 52 10
D get_stats() 0 260 54
D get_recent_donations() 0 96 17
A get_output_format() 0 7 2
F log_request() 0 40 17
A get_output() 0 3 1
B output() 0 44 4
B user_key_field() 0 38 5
D process_api_key() 0 53 14
B generate_api_key() 0 31 6
B revoke_api_key() 0 26 4
A get_version() 0 3 1
A update_key() 0 19 4
A generate_public_key() 0 5 2
A generate_private_key() 0 6 2
A get_token() 0 3 1
A get_default_sales_stats() 0 11 1
A get_default_earnings_stats() 0 11 1
B api_key_backwards_compat() 0 24 6
D get_dates() 0 170 22
D process_query() 0 85 15

How to fix   Complexity   

Complex Class

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

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 26 and the first side effect is on line 16.

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
/**
3
 * Give API
4
 *
5
 * A front-facing JSON/XML API that makes it possible to query donation data.
6
 *
7
 * @package     Give
8
 * @subpackage  Classes/API
9
 * @copyright   Copyright (c) 2016, WordImpress
10
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
11
 * @since       1.1
12
 */
13
14
// Exit if accessed directly
15
if ( ! defined( 'ABSPATH' ) ) {
16
	exit;
17
}
18
19
/**
20
 * Give_API Class
21
 *
22
 * Renders API returns as a JSON/XML array
23
 *
24
 * @since  1.1
25
 */
26
class Give_API {
27
28
	/**
29
	 * Latest API Version
30
	 */
31
	const VERSION = 1;
32
33
	/**
34
	 * Pretty Print?
35
	 *
36
	 * @var bool
37
	 * @access private
38
	 * @since  1.1
39
	 */
40
	private $pretty_print = false;
41
42
	/**
43
	 * Log API requests?
44
	 *
45
	 * @var bool
46
	 * @access private
47
	 * @since  1.1
48
	 */
49
	public $log_requests = true;
50
51
	/**
52
	 * Is this a valid request?
53
	 *
54
	 * @var bool
55
	 * @access private
56
	 * @since  1.1
57
	 */
58
	private $is_valid_request = false;
59
60
	/**
61
	 * User ID Performing the API Request
62
	 *
63
	 * @var int
64
	 * @access private
65
	 * @since  1.1
66
	 */
67
	public $user_id = 0;
68
69
	/**
70
	 * Instance of Give Stats class
71
	 *
72
	 * @var object
73
	 * @access private
74
	 * @since  1.1
75
	 */
76
	private $stats;
77
78
	/**
79
	 * Response data to return
80
	 *
81
	 * @var array
82
	 * @access private
83
	 * @since  1.1
84
	 */
85
	private $data = array();
86
87
	/**
88
	 *
89
	 * @var bool
90
	 * @access private
91
	 * @since  1.1
92
	 */
93
	public $override = true;
94
95
	/**
96
	 * Version of the API queried
97
	 *
98
	 * @var string
99
	 * @access public
100
	 * @since  1.1
101
	 */
102
	private $queried_version;
103
104
	/**
105
	 * All versions of the API
106
	 *
107
	 * @var string
108
	 * @access public
109
	 * @since  1.1
110
	 */
111
	protected $versions = array();
112
113
	/**
114
	 * Queried endpoint
115
	 *
116
	 * @var string
117
	 * @access public
118
	 * @since  1.1
119
	 */
120
	private $endpoint;
121
122
	/**
123
	 * Endpoints routes
124
	 *
125
	 * @var object
126
	 * @access public
127
	 * @since  1.1
128
	 */
129
	private $routes;
130
131
	/**
132
	 * Setup the Give API
133
	 *
134
	 * @since 1.1
135
	 */
136 12
	public function __construct() {
137
138 12
		$this->versions = array(
139 12
			'v1' => 'GIVE_API_V1',
140
		);
141
142 12
		foreach ( $this->get_versions() as $version => $class ) {
0 ignored issues
show
Bug introduced by
The expression $this->get_versions() of type string is not traversable.
Loading history...
143 12
			require_once GIVE_PLUGIN_DIR . 'includes/api/class-give-api-' . $version . '.php';
144 12
		}
145
146 12
		add_action( 'init', array( $this, 'add_endpoint' ) );
147 12
		add_action( 'wp', array( $this, 'process_query' ), - 1 );
148 12
		add_filter( 'query_vars', array( $this, 'query_vars' ) );
149 12
		add_action( 'show_user_profile', array( $this, 'user_key_field' ) );
150 12
		add_action( 'edit_user_profile', array( $this, 'user_key_field' ) );
151 12
		add_action( 'personal_options_update', array( $this, 'update_key' ) );
152 12
		add_action( 'edit_user_profile_update', array( $this, 'update_key' ) );
153 12
		add_action( 'give_process_api_key', array( $this, 'process_api_key' ) );
154
155
		// Setup a backwards compatibility check for user API Keys
156 12
		add_filter( 'get_user_metadata', array( $this, 'api_key_backwards_compat' ), 10, 4 );
157
158
		// Determine if JSON_PRETTY_PRINT is available
159 12
		$this->pretty_print = defined( 'JSON_PRETTY_PRINT' ) ? JSON_PRETTY_PRINT : null;
160
161
		// Allow API request logging to be turned off
162 12
		$this->log_requests = apply_filters( 'give_api_log_requests', $this->log_requests );
163
164
		// Setup Give_Payment_Stats instance
165 12
		$this->stats = new Give_Payment_Stats;
166
167 12
	}
168
169
	/**
170
	 * Registers a new rewrite endpoint for accessing the API
171
	 *
172
	 * @access public
173
	 *
174
	 * @param array $rewrite_rules WordPress Rewrite Rules
175
	 *
176
	 * @since  1.1
177
	 */
178 10
	public function add_endpoint( $rewrite_rules ) {
0 ignored issues
show
Unused Code introduced by
The parameter $rewrite_rules 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...
179 10
		add_rewrite_endpoint( 'give-api', EP_ALL );
180 10
	}
181
182
	/**
183
	 * Registers query vars for API access
184
	 *
185
	 * @access public
186
	 * @since  1.1
187
	 *
188
	 * @param array $vars Query vars
189
	 *
190
	 * @return string[] $vars New query vars
191
	 */
192 4
	public function query_vars( $vars ) {
193
194 4
		$vars[] = 'token';
195 4
		$vars[] = 'key';
196 4
		$vars[] = 'query';
197 4
		$vars[] = 'type';
198 4
		$vars[] = 'form';
199 4
		$vars[] = 'number';
200 4
		$vars[] = 'date';
201 4
		$vars[] = 'startdate';
202 4
		$vars[] = 'enddate';
203 4
		$vars[] = 'donor';
204 4
		$vars[] = 'format';
205 4
		$vars[] = 'id';
206 4
		$vars[] = 'purchasekey';
207 4
		$vars[] = 'email';
208
209 4
		return $vars;
210
	}
211
212
	/**
213
	 * Retrieve the API versions
214
	 *
215
	 * @access public
216
	 * @since  1.1
217
	 * @return array
218
	 */
219 12
	public function get_versions() {
220 12
		return $this->versions;
221
	}
222
223
	/**
224
	 * Retrieve the API version that was queried
225
	 *
226
	 * @access public
227
	 * @since  1.1
228
	 * @return string
229
	 */
230
	public function get_queried_version() {
231
		return $this->queried_version;
232
	}
233
234
	/**
235
	 * Retrieves the default version of the API to use
236
	 *
237
	 * @access private
238
	 * @since  1.1
239
	 * @return string
240
	 */
241 1
	public function get_default_version() {
242
243 1
		$version = get_option( 'give_default_api_version' );
244
245 1
		if ( defined( 'GIVE_API_VERSION' ) ) {
246 1
			$version = GIVE_API_VERSION;
247 1
		} elseif ( ! $version ) {
248
			$version = 'v1';
249
		}
250
251 1
		return $version;
252
	}
253
254
	/**
255
	 * Sets the version of the API that was queried.
256
	 *
257
	 * Falls back to the default version if no version is specified
258
	 *
259
	 * @access private
260
	 * @since  1.1
261
	 */
262
	private function set_queried_version() {
263
264
		global $wp_query;
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...
265
266
		$version = $wp_query->query_vars['give-api'];
267
268
		if ( strpos( $version, '/' ) ) {
269
270
			$version = explode( '/', $version );
271
			$version = strtolower( $version[0] );
272
273
			$wp_query->query_vars['give-api'] = str_replace( $version . '/', '', $wp_query->query_vars['give-api'] );
274
275
			if ( array_key_exists( $version, $this->versions ) ) {
276
277
				$this->queried_version = $version;
278
279
			} else {
280
281
				$this->is_valid_request = false;
282
				$this->invalid_version();
283
			}
284
285
		} else {
286
287
			$this->queried_version = $this->get_default_version();
288
289
		}
290
291
	}
292
293
	/**
294
	 * Validate the API request
295
	 *
296
	 * Checks for the user's public key and token against the secret key
297
	 *
298
	 * @access private
299
	 * @global object $wp_query WordPress Query
300
	 * @uses   Give_API::get_user()
301
	 * @uses   Give_API::invalid_key()
302
	 * @uses   Give_API::invalid_auth()
303
	 * @since  1.1
304
	 * @return void
305
	 */
306
	private function validate_request() {
307
		global $wp_query;
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...
308
309
		$this->override = false;
310
311
		// Make sure we have both user and api key
312
		if ( ! empty( $wp_query->query_vars['give-api'] ) && ( $wp_query->query_vars['give-api'] != 'forms' || ! empty( $wp_query->query_vars['token'] ) ) ) {
313
314
			if ( empty( $wp_query->query_vars['token'] ) || empty( $wp_query->query_vars['key'] ) ) {
315
				$this->missing_auth();
316
			}
317
318
			// Retrieve the user by public API key and ensure they exist
319
			if ( ! ( $user = $this->get_user( $wp_query->query_vars['key'] ) ) ) {
320
321
				$this->invalid_key();
322
323
			} else {
324
325
				$token  = urldecode( $wp_query->query_vars['token'] );
326
				$secret = $this->get_user_secret_key( $user );
327
				$public = urldecode( $wp_query->query_vars['key'] );
328
329
				if ( hash_equals( md5( $secret . $public ), $token ) ) {
330
					$this->is_valid_request = true;
331
				} else {
332
					$this->invalid_auth();
333
				}
334
			}
335
		} elseif ( ! empty( $wp_query->query_vars['give-api'] ) && $wp_query->query_vars['give-api'] == 'forms' ) {
336
			$this->is_valid_request = true;
337
			$wp_query->set( 'key', 'public' );
338
		}
339
	}
340
341
	/**
342
	 * Retrieve the user ID based on the public key provided
343
	 *
344
	 * @access public
345
	 * @since  1.1
346
	 * @global object $wpdb Used to query the database using the WordPress
347
	 *                      Database API
348
	 *
349
	 * @param string  $key  Public Key
350
	 *
351
	 * @return bool if user ID is found, false otherwise
352
	 */
353 1
	public function get_user( $key = '' ) {
354 1
		global $wpdb, $wp_query;
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...
355
356 1
		if ( empty( $key ) ) {
357
			$key = urldecode( $wp_query->query_vars['key'] );
358
		}
359
360 1
		if ( empty( $key ) ) {
361
			return false;
362
		}
363
364 1
		$user = get_transient( md5( 'give_api_user_' . $key ) );
365
366 1
		if ( false === $user ) {
367 1
			$user = $wpdb->get_var( $wpdb->prepare( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = %s LIMIT 1", $key ) );
368 1
			set_transient( md5( 'give_api_user_' . $key ), $user, DAY_IN_SECONDS );
369 1
		}
370
371 1
		if ( $user != null ) {
372 1
			$this->user_id = $user;
373
374 1
			return $user;
375
		}
376
377
		return false;
378
	}
379
380 2
	public function get_user_public_key( $user_id = 0 ) {
381 2
		global $wpdb;
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...
382
383 2
		if ( empty( $user_id ) ) {
384
			return '';
385
		}
386
387 2
		$cache_key       = md5( 'give_api_user_public_key' . $user_id );
388 2
		$user_public_key = get_transient( $cache_key );
389
390 2
		if ( empty( $user_public_key ) ) {
391 2
			$user_public_key = $wpdb->get_var( $wpdb->prepare( "SELECT meta_key FROM $wpdb->usermeta WHERE meta_value = 'give_user_public_key' AND user_id = %d", $user_id ) );
392 2
			set_transient( $cache_key, $user_public_key, HOUR_IN_SECONDS );
393 2
		}
394
395 2
		return $user_public_key;
396
	}
397
398 2
	public function get_user_secret_key( $user_id = 0 ) {
399 2
		global $wpdb;
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...
400
401 2
		if ( empty( $user_id ) ) {
402
			return '';
403
		}
404
405 2
		$cache_key       = md5( 'give_api_user_secret_key' . $user_id );
406 2
		$user_secret_key = get_transient( $cache_key );
407
408 2
		if ( empty( $user_secret_key ) ) {
409 2
			$user_secret_key = $wpdb->get_var( $wpdb->prepare( "SELECT meta_key FROM $wpdb->usermeta WHERE meta_value = 'give_user_secret_key' AND user_id = %d", $user_id ) );
410 2
			set_transient( $cache_key, $user_secret_key, HOUR_IN_SECONDS );
411 2
		}
412
413 2
		return $user_secret_key;
414
	}
415
416
	/**
417
	 * Displays a missing authentication error if all the parameters aren't
418
	 * provided
419
	 *
420
	 * @access private
421
	 * @uses   Give_API::output()
422
	 * @since  1.1
423
	 */
424
	private function missing_auth() {
425
		$error          = array();
426
		$error['error'] = esc_attr__( 'You must specify both a token and API key!', 'give' );
427
428
		$this->data = $error;
429
		$this->output( 401 );
430
	}
431
432
	/**
433
	 * Displays an authentication failed error if the user failed to provide valid
434
	 * credentials
435
	 *
436
	 * @access private
437
	 * @since  1.1
438
	 * @uses   Give_API::output()
439
	 * @return void
440
	 */
441
	private function invalid_auth() {
442
		$error          = array();
443
		$error['error'] = esc_attr__( 'Your request could not be authenticated!', 'give' );
444
445
		$this->data = $error;
446
		$this->output( 403 );
447
	}
448
449
	/**
450
	 * Displays an invalid API key error if the API key provided couldn't be
451
	 * validated
452
	 *
453
	 * @access private
454
	 * @since  1.1
455
	 * @uses   Give_API::output()
456
	 * @return void
457
	 */
458
	private function invalid_key() {
459
		$error          = array();
460
		$error['error'] = esc_attr__( 'Invalid API key!', 'give' );
461
462
		$this->data = $error;
463
		$this->output( 403 );
464
	}
465
466
	/**
467
	 * Displays an invalid version error if the version number passed isn't valid
468
	 *
469
	 * @access private
470
	 * @since  1.1
471
	 * @uses   Give_API::output()
472
	 * @return void
473
	 */
474
	private function invalid_version() {
475
		$error          = array();
476
		$error['error'] = esc_attr__( 'Invalid API version!', 'give' );
477
478
		$this->data = $error;
479
		$this->output( 404 );
480
	}
481
482
	/**
483
	 * Listens for the API and then processes the API requests
484
	 *
485
	 * @access public
486
	 * @global $wp_query
487
	 * @since  1.1
488
	 * @return void
489
	 */
490 3
	public function process_query() {
491
492 3
		global $wp_query;
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...
493
494
		// Start logging how long the request takes for logging
495 3
		$before = microtime( true );
496
497
		// Check for give-api var. Get out if not present
498 3
		if ( empty( $wp_query->query_vars['give-api'] ) ) {
499 3
			return;
500
		}
501
502
		// Determine which version was queried
503
		$this->set_queried_version();
504
505
		// Determine the kind of query
506
		$this->set_query_mode();
507
508
		// Check for a valid user and set errors if necessary
509
		$this->validate_request();
510
511
		// Only proceed if no errors have been noted
512
		if ( ! $this->is_valid_request ) {
513
			return;
514
		}
515
516
		if ( ! defined( 'GIVE_DOING_API' ) ) {
517
			define( 'GIVE_DOING_API', true );
518
		}
519
520
		$data         = array();
521
		$this->routes = new $this->versions[$this->get_queried_version()];
522
		$this->routes->validate_request();
523
524
		switch ( $this->endpoint ) :
525
526
			case 'stats' :
527
528
				$data = $this->routes->get_stats( array(
529
					'type'      => isset( $wp_query->query_vars['type'] ) ? $wp_query->query_vars['type'] : null,
530
					'form'      => isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null,
531
					'date'      => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null,
532
					'startdate' => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null,
533
					'enddate'   => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null
534
				) );
535
536
				break;
537
538
			case 'forms' :
539
540
				$form = isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null;
541
542
				$data = $this->routes->get_forms( $form );
543
544
				break;
545
546
			case 'donors' :
547
548
				$customer = isset( $wp_query->query_vars['donor'] ) ? $wp_query->query_vars['donor'] : null;
549
550
				$data = $this->routes->get_customers( $customer );
551
552
				break;
553
554
			case 'donations' :
555
556
				$data = $this->routes->get_recent_donations();
557
558
				break;
559
560
		endswitch;
561
562
		// Allow extensions to setup their own return data
563
		$this->data = apply_filters( 'give_api_output_data', $data, $this->endpoint, $this );
564
565
		$after                       = microtime( true );
566
		$request_time                = ( $after - $before );
567
		$this->data['request_speed'] = $request_time;
568
569
		// Log this API request, if enabled. We log it here because we have access to errors.
570
		$this->log_request( $this->data );
571
572
		// Send out data to the output function
573
		$this->output();
574
	}
575
576
	/**
577
	 * Returns the API endpoint requested
578
	 *
579
	 * @access private
580
	 * @since  1.1
581
	 * @return string $query Query mode
582
	 */
583
	public function get_query_mode() {
584
585
		return $this->endpoint;
586
	}
587
588
	/**
589
	 * Determines the kind of query requested and also ensure it is a valid query
590
	 *
591
	 * @access private
592
	 * @since  1.1
593
	 * @global $wp_query
594
	 */
595
	public function set_query_mode() {
596
597
		global $wp_query;
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...
598
599
		// Whitelist our query options
600
		$accepted = apply_filters( 'give_api_valid_query_modes', array(
601
			'stats',
602
			'forms',
603
			'donors',
604
			'donations'
605
		) );
606
607
		$query = isset( $wp_query->query_vars['give-api'] ) ? $wp_query->query_vars['give-api'] : null;
608
		$query = str_replace( $this->queried_version . '/', '', $query );
609
610
		$error = array();
611
612
		// Make sure our query is valid
613
		if ( ! in_array( $query, $accepted ) ) {
614
			$error['error'] = __( 'Invalid query!', 'give' );
615
616
			$this->data = $error;
617
			$this->output();
618
		}
619
620
		$this->endpoint = $query;
621
	}
622
623
	/**
624
	 * Get page number
625
	 *
626
	 * @access private
627
	 * @since  1.1
628
	 * @global $wp_query
629
	 * @return int $wp_query->query_vars['page'] if page number returned (default: 1)
630
	 */
631 10
	public function get_paged() {
632 10
		global $wp_query;
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...
633
634 10
		return isset( $wp_query->query_vars['page'] ) ? $wp_query->query_vars['page'] : 1;
635
	}
636
637
638
	/**
639
	 * Number of results to display per page
640
	 *
641
	 * @access private
642
	 * @since  1.1
643
	 * @global $wp_query
644
	 * @return int $per_page Results to display per page (default: 10)
645
	 */
646 10
	public function per_page() {
647 10
		global $wp_query;
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...
648
649 10
		$per_page = isset( $wp_query->query_vars['number'] ) ? $wp_query->query_vars['number'] : 10;
650
651 10
		if ( $per_page < 0 && $this->get_query_mode() == 'donors' ) {
652
			$per_page = 99999999;
653
		} // Customers query doesn't support -1
654
655 10
		return apply_filters( 'give_api_results_per_page', $per_page );
656
	}
657
658
	/**
659
	 * Sets up the dates used to retrieve earnings/donations
660
	 *
661
	 * @access public
662
	 * @since  1.2
663
	 *
664
	 * @param array $args Arguments to override defaults
665
	 *
666
	 * @return array $dates
667
	 */
668
	public function get_dates( $args = array() ) {
669
		$dates = array();
670
671
		$defaults = array(
672
			'type'      => '',
673
			'form'      => null,
674
			'date'      => null,
675
			'startdate' => null,
676
			'enddate'   => null
677
		);
678
679
		$args = wp_parse_args( $args, $defaults );
680
681
		$current_time = current_time( 'timestamp' );
682
683
		if ( 'range' === $args['date'] ) {
684
			$startdate          = strtotime( $args['startdate'] );
685
			$enddate            = strtotime( $args['enddate'] );
686
			$dates['day_start'] = date( 'd', $startdate );
687
			$dates['day_end']   = date( 'd', $enddate );
688
			$dates['m_start']   = date( 'n', $startdate );
689
			$dates['m_end']     = date( 'n', $enddate );
690
			$dates['year']      = date( 'Y', $startdate );
691
			$dates['year_end']  = date( 'Y', $enddate );
692
		} else {
693
			// Modify dates based on predefined ranges
694
			switch ( $args['date'] ) :
695
696
				case 'this_month' :
697
					$dates['day']     = null;
698
					$dates['m_start'] = date( 'n', $current_time );
699
					$dates['m_end']   = date( 'n', $current_time );
700
					$dates['year']    = date( 'Y', $current_time );
701
					break;
702
703
				case 'last_month' :
704
					$dates['day']     = null;
705
					$dates['m_start'] = date( 'n', $current_time ) == 1 ? 12 : date( 'n', $current_time ) - 1;
706
					$dates['m_end']   = $dates['m_start'];
707
					$dates['year']    = date( 'n', $current_time ) == 1 ? date( 'Y', $current_time ) - 1 : date( 'Y', $current_time );
708
					break;
709
710
				case 'today' :
711
					$dates['day']     = date( 'd', $current_time );
712
					$dates['m_start'] = date( 'n', $current_time );
713
					$dates['m_end']   = date( 'n', $current_time );
714
					$dates['year']    = date( 'Y', $current_time );
715
					break;
716
717
				case 'yesterday' :
718
719
					$year  = date( 'Y', $current_time );
720
					$month = date( 'n', $current_time );
721
					$day   = date( 'd', $current_time );
722
723
					if ( $month == 1 && $day == 1 ) {
724
725
						$year -= 1;
726
						$month = 12;
727
						$day   = cal_days_in_month( CAL_GREGORIAN, $month, $year );
728
729
					} elseif ( $month > 1 && $day == 1 ) {
730
731
						$month -= 1;
732
						$day = cal_days_in_month( CAL_GREGORIAN, $month, $year );
733
734
					} else {
735
736
						$day -= 1;
737
738
					}
739
740
					$dates['day']     = $day;
741
					$dates['m_start'] = $month;
742
					$dates['m_end']   = $month;
743
					$dates['year']    = $year;
744
745
					break;
746
747
				case 'this_quarter' :
748
					$month_now = date( 'n', $current_time );
749
750
					$dates['day'] = null;
751
752
					if ( $month_now <= 3 ) {
753
754
						$dates['m_start'] = 1;
755
						$dates['m_end']   = 3;
756
						$dates['year']    = date( 'Y', $current_time );
757
758
					} else if ( $month_now <= 6 ) {
759
760
						$dates['m_start'] = 4;
761
						$dates['m_end']   = 6;
762
						$dates['year']    = date( 'Y', $current_time );
763
764
					} else if ( $month_now <= 9 ) {
765
766
						$dates['m_start'] = 7;
767
						$dates['m_end']   = 9;
768
						$dates['year']    = date( 'Y', $current_time );
769
770
					} else {
771
772
						$dates['m_start'] = 10;
773
						$dates['m_end']   = 12;
774
						$dates['year']    = date( 'Y', $current_time );
775
776
					}
777
					break;
778
779
				case 'last_quarter' :
780
					$month_now = date( 'n', $current_time );
781
782
					$dates['day'] = null;
783
784
					if ( $month_now <= 3 ) {
785
786
						$dates['m_start'] = 10;
787
						$dates['m_end']   = 12;
788
						$dates['year']    = date( 'Y', $current_time ) - 1; // Previous year
789
790
					} else if ( $month_now <= 6 ) {
791
792
						$dates['m_start'] = 1;
793
						$dates['m_end']   = 3;
794
						$dates['year']    = date( 'Y', $current_time );
795
796
					} else if ( $month_now <= 9 ) {
797
798
						$dates['m_start'] = 4;
799
						$dates['m_end']   = 6;
800
						$dates['year']    = date( 'Y', $current_time );
801
802
					} else {
803
804
						$dates['m_start'] = 7;
805
						$dates['m_end']   = 9;
806
						$dates['year']    = date( 'Y', $current_time );
807
808
					}
809
					break;
810
811
				case 'this_year' :
812
					$dates['day']     = null;
813
					$dates['m_start'] = null;
814
					$dates['m_end']   = null;
815
					$dates['year']    = date( 'Y', $current_time );
816
					break;
817
818
				case 'last_year' :
819
					$dates['day']     = null;
820
					$dates['m_start'] = null;
821
					$dates['m_end']   = null;
822
					$dates['year']    = date( 'Y', $current_time ) - 1;
823
					break;
824
825
			endswitch;
826
		}
827
828
		/**
829
		 * Returns the filters for the dates used to retreive earnings/donations
830
		 *
831
		 * @since 1.2
832
		 *
833
		 * @param object $dates The dates used for retreiving earnings/donations
834
		 */
835
836
		return apply_filters( 'give_api_stat_dates', $dates );
837
	}
838
839
	/**
840
	 * Process Get Customers API Request
841
	 *
842
	 * @access public
843
	 * @since  1.1
844
	 * @global object $wpdb     Used to query the database using the WordPress
845
	 *                          Database API
846
	 *
847
	 * @param int     $customer Customer ID
848
	 *
849
	 * @return array $customers Multidimensional array of the customers
850
	 */
851 1
	public function get_customers( $customer = null ) {
852
853 1
		$customers = array();
854 1
		$error     = array();
855 1
		if ( ! user_can( $this->user_id, 'view_give_sensitive_data' ) && ! $this->override ) {
856
			return $customers;
857
		}
858
859 1
		global $wpdb;
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...
860
861 1
		$paged    = $this->get_paged();
862 1
		$per_page = $this->per_page();
863 1
		$offset   = $per_page * ( $paged - 1 );
864
865 1
		if ( is_numeric( $customer ) ) {
866
			$field = 'id';
867
		} else {
868 1
			$field = 'email';
869
		}
870
871 1
		$customer_query = Give()->customers->get_customers( array(
0 ignored issues
show
Bug introduced by
The method get_customers() does not seem to exist on object<Give_Customer>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
872 1
			'number' => $per_page,
873 1
			'offset' => $offset,
874
			$field   => $customer
875 1
		) );
876 1
		$customer_count = 0;
877
878 1
		if ( $customer_query ) {
879
880 1
			foreach ( $customer_query as $customer_obj ) {
881
882 1
				$names      = explode( ' ', $customer_obj->name );
883 1
				$first_name = ! empty( $names[0] ) ? $names[0] : '';
884 1
				$last_name  = '';
885 1
				if ( ! empty( $names[1] ) ) {
886
					unset( $names[0] );
887
					$last_name = implode( ' ', $names );
888
				}
889
890 1
				$customers['donors'][ $customer_count ]['info']['user_id']      = '';
891 1
				$customers['donors'][ $customer_count ]['info']['username']     = '';
892 1
				$customers['donors'][ $customer_count ]['info']['display_name'] = '';
893 1
				$customers['donors'][ $customer_count ]['info']['customer_id']  = $customer_obj->id;
894 1
				$customers['donors'][ $customer_count ]['info']['first_name']   = $first_name;
895 1
				$customers['donors'][ $customer_count ]['info']['last_name']    = $last_name;
896 1
				$customers['donors'][ $customer_count ]['info']['email']        = $customer_obj->email;
897
898 1
				if ( ! empty( $customer_obj->user_id ) ) {
899
900 1
					$user_data = get_userdata( $customer_obj->user_id );
901
902
					// Customer with registered account
903 1
					$customers['donors'][ $customer_count ]['info']['user_id']      = $customer_obj->user_id;
904 1
					$customers['donors'][ $customer_count ]['info']['username']     = $user_data->user_login;
905 1
					$customers['donors'][ $customer_count ]['info']['display_name'] = $user_data->display_name;
906
907 1
				}
908
909 1
				$customers['donors'][ $customer_count ]['stats']['total_donations'] = $customer_obj->purchase_count;
910 1
				$customers['donors'][ $customer_count ]['stats']['total_spent']     = $customer_obj->purchase_value;
911
912 1
				$customer_count ++;
913
914 1
			}
915
916 1
		} elseif ( $customer ) {
917
918
			$error['error'] = sprintf( __( 'Donor %s not found!', 'give' ), $customer );
919
920
			return $error;
921
922
		} else {
923
924
			$error['error'] = __( 'No donors found!', 'give' );
925
926
			return $error;
927
928
		}
929
930 1
		return $customers;
931
	}
932
933
	/**
934
	 * Process Get Products API Request
935
	 *
936
	 * @access public
937
	 * @since  1.1
938
	 *
939
	 * @param int $form Give Form ID
940
	 *
941
	 * @return array $customers Multidimensional array of the forms
942
	 */
943 10
	public function get_forms( $form = null ) {
944
945 10
		$forms = array();
946 10
		$error = array();
947
948 10
		if ( $form == null ) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $form of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
949 10
			$forms['forms'] = array();
950
951 10
			$form_list = get_posts( array(
952 10
				'post_type'        => 'give_forms',
953 10
				'posts_per_page'   => $this->per_page(),
954 10
				'suppress_filters' => true,
955 10
				'paged'            => $this->get_paged()
956 10
			) );
957
958 10
			if ( $form_list ) {
959 10
				$i = 0;
960 10
				foreach ( $form_list as $form_info ) {
961 10
					$forms['forms'][ $i ] = $this->get_form_data( $form_info );
962 10
					$i ++;
963 10
				}
964 10
			}
965 10
		} else {
966
			if ( get_post_type( $form ) == 'give_forms' ) {
967
				$form_info = get_post( $form );
968
969
				$forms['forms'][0] = $this->get_form_data( $form_info );
970
971
			} else {
972
				$error['error'] = sprintf( __( 'Form %s not found!', 'give' ), $form );
973
974
				return $error;
975
			}
976
		}
977
978 10
		return $forms;
979
	}
980
981
	/**
982
	 * Given a give_forms post object, generate the data for the API output
983
	 *
984
	 * @since  1.1
985
	 *
986
	 * @param  object $form_info The Download Post Object
987
	 *
988
	 * @return array                Array of post data to return back in the API
989
	 */
990 10
	private function get_form_data( $form_info ) {
991
992 10
		$form = array();
993
994 10
		$form['info']['id']            = $form_info->ID;
995 10
		$form['info']['slug']          = $form_info->post_name;
996 10
		$form['info']['title']         = $form_info->post_title;
997 10
		$form['info']['create_date']   = $form_info->post_date;
998 10
		$form['info']['modified_date'] = $form_info->post_modified;
999 10
		$form['info']['status']        = $form_info->post_status;
1000 10
		$form['info']['link']          = html_entity_decode( $form_info->guid );
1001 10
		$form['info']['content']       = get_post_meta( $form_info->ID, '_give_form_content', true );
1002 10
		$form['info']['thumbnail']     = wp_get_attachment_url( get_post_thumbnail_id( $form_info->ID ) );
1003
1004 10
		if ( give_get_option( 'enable_categories' ) == 'on' ) {
1005
			$form['info']['category'] = get_the_terms( $form_info, 'give_forms_category' );
1006
			$form['info']['tags']     = get_the_terms( $form_info, 'give_forms_tag' );
1007
		}
1008 10
		if ( give_get_option( 'enable_tags' ) == 'on' ) {
1009
			$form['info']['tags'] = get_the_terms( $form_info, 'give_forms_tag' );
1010
		}
1011
1012 10
		if ( user_can( $this->user_id, 'view_give_reports' ) || $this->override ) {
1013 10
			$form['stats']['total']['donations']           = give_get_form_sales_stats( $form_info->ID );
1014 10
			$form['stats']['total']['earnings']            = give_get_form_earnings_stats( $form_info->ID );
1015 10
			$form['stats']['monthly_average']['donations'] = give_get_average_monthly_form_sales( $form_info->ID );
1016 10
			$form['stats']['monthly_average']['earnings']  = give_get_average_monthly_form_earnings( $form_info->ID );
1017 10
		}
1018
1019 10
		$counter = 0;
1020 10
		if ( give_has_variable_prices( $form_info->ID ) ) {
1021 10
			foreach ( give_get_variable_prices( $form_info->ID ) as $price ) {
1022 10
				$counter ++;
1023
				//muli-level item
1024 10
				$level                                     = isset( $price['_give_text'] ) ? $price['_give_text'] : 'level-' . $counter;
1025 10
				$form['pricing'][ sanitize_key( $level ) ] = $price['_give_amount'];
1026
1027 10
			}
1028 10
		} else {
1029
			$form['pricing']['amount'] = give_get_form_price( $form_info->ID );
1030
		}
1031
1032 10
		if ( user_can( $this->user_id, 'view_give_sensitive_data' ) || $this->override ) {
1033
1034
			//Sensitive data here
1035 10
			do_action( 'give_api_sensitive_data' );
1036
1037 10
		}
1038
1039 10
		return apply_filters( 'give_api_forms_form', $form );
1040
1041
	}
1042
1043
	/**
1044
	 * Process Get Stats API Request
1045
	 *
1046
	 * @since 1.1
1047
	 *
1048
	 * @global object $wpdb Used to query the database using the WordPress
1049
	 *
1050
	 * @param array   $args Arguments provided by API Request
1051
	 *
1052
	 * @return array
1053
	 */
1054
	public function get_stats( $args = array() ) {
1055
		$defaults = array(
1056
			'type'      => null,
1057
			'form'      => null,
1058
			'date'      => null,
1059
			'startdate' => null,
1060
			'enddate'   => null
1061
		);
1062
1063
		$args = wp_parse_args( $args, $defaults );
1064
1065
		$dates = $this->get_dates( $args );
1066
1067
		$stats     = array();
1068
		$earnings  = array(
1069
			'earnings' => array()
1070
		);
1071
		$sales = array(
1072
			'donations' => array()
1073
		);
1074
		$error     = array();
1075
1076
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1077
			return $stats;
1078
		}
1079
1080
		if ( $args['type'] == 'donations' ) {
1081
1082
			if ( $args['form'] == null ) {
1083
				if ( $args['date'] == null ) {
1084
					$sales = $this->get_default_sales_stats();
1085
				} elseif ( $args['date'] === 'range' ) {
1086
					// Return sales for a date range
1087
1088
					// Ensure the end date is later than the start date
1089
					if ( $args['enddate'] < $args['startdate'] ) {
1090
						$error['error'] = __( 'The end date must be later than the start date!', 'give' );
1091
					}
1092
1093
					// Ensure both the start and end date are specified
1094
					if ( empty( $args['startdate'] ) || empty( $args['enddate'] ) ) {
1095
						$error['error'] = __( 'Invalid or no date range specified!', 'give' );
1096
					}
1097
1098
					$total = 0;
1099
1100
					// Loop through the years
1101
					$y = $dates['year'];
1102
					while ( $y <= $dates['year_end'] ) :
1103
1104
						if ( $dates['year'] == $dates['year_end'] ) {
1105
							$month_start = $dates['m_start'];
1106
							$month_end   = $dates['m_end'];
1107
						} elseif ( $y == $dates['year'] && $dates['year_end'] > $dates['year'] ) {
1108
							$month_start = $dates['m_start'];
1109
							$month_end   = 12;
1110
						} elseif ( $y == $dates['year_end'] ) {
1111
							$month_start = 1;
1112
							$month_end   = $dates['m_end'];
1113
						} else {
1114
							$month_start = 1;
1115
							$month_end   = 12;
1116
						}
1117
1118
						$i = $month_start;
1119
						while ( $i <= $month_end ) :
1120
1121
							if ( $i == $dates['m_start'] ) {
1122
								$d = $dates['day_start'];
1123
							} else {
1124
								$d = 1;
1125
							}
1126
1127
							if ( $i == $dates['m_end'] ) {
1128
								$num_of_days = $dates['day_end'];
1129
							} else {
1130
								$num_of_days = cal_days_in_month( CAL_GREGORIAN, $i, $y );
1131
							}
1132
1133
							while ( $d <= $num_of_days ) :
1134
								$sale_count = give_get_sales_by_date( $d, $i, $y );
1135
								$date_key   = date( 'Ymd', strtotime( $y . '/' . $i . '/' . $d ) );
1136
								if ( ! isset( $sales['sales'][ $date_key ] ) ) {
1137
									$sales['sales'][ $date_key ] = 0;
1138
								}
1139
								$sales['sales'][ $date_key ] += $sale_count;
1140
								$total += $sale_count;
1141
								$d ++;
1142
							endwhile;
1143
							$i ++;
1144
						endwhile;
1145
1146
						$y ++;
1147
					endwhile;
1148
1149
					$sales['totals'] = $total;
1150
				} else {
1151
					if ( $args['date'] == 'this_quarter' || $args['date'] == 'last_quarter' ) {
1152
						$sales_count = 0;
1153
1154
						// Loop through the months
1155
						$month = $dates['m_start'];
1156
1157
						while ( $month <= $dates['m_end'] ) :
1158
							$sales_count += give_get_sales_by_date( null, $month, $dates['year'] );
1159
							$month ++;
1160
						endwhile;
1161
1162
						$sales['donations'][ $args['date'] ] = $sales_count;
1163
					} else {
1164
						$sales['donations'][ $args['date'] ] = give_get_sales_by_date( $dates['day'], $dates['m_start'], $dates['year'] );
1165
					}
1166
				}
1167
			} elseif ( $args['form'] == 'all' ) {
1168
				$forms = get_posts( array( 'post_type' => 'give_forms', 'nopaging' => true ) );
1169
				$i     = 0;
1170
				foreach ( $forms as $form_info ) {
1171
					$sales['donations'][ $i ] = array( $form_info->post_name => give_get_form_sales_stats( $form_info->ID ) );
1172
					$i ++;
1173
				}
1174
			} else {
1175
				if ( get_post_type( $args['form'] ) == 'give_forms' ) {
1176
					$form_info             = get_post( $args['form'] );
1177
					$sales['donations'][0] = array( $form_info->post_name => give_get_form_sales_stats( $args['form'] ) );
1178
				} else {
1179
					$error['error'] = sprintf( __( 'Product %s not found!', 'give' ), $args['form'] );
1180
				}
1181
			}
1182
1183
			if ( ! empty( $error ) ) {
1184
				return $error;
1185
			}
1186
1187
			return $sales;
1188
1189
		} elseif ( $args['type'] == 'earnings' ) {
1190
			if ( $args['form'] == null ) {
1191
				if ( $args['date'] == null ) {
1192
					$earnings = $this->get_default_earnings_stats();
1193
				} elseif ( $args['date'] === 'range' ) {
1194
					// Return sales for a date range
1195
1196
					// Ensure the end date is later than the start date
1197
					if ( $args['enddate'] < $args['startdate'] ) {
1198
						$error['error'] = __( 'The end date must be later than the start date!', 'give' );
1199
					}
1200
1201
					// Ensure both the start and end date are specified
1202
					if ( empty( $args['startdate'] ) || empty( $args['enddate'] ) ) {
1203
						$error['error'] = __( 'Invalid or no date range specified!', 'give' );
1204
					}
1205
1206
					$total = (float) 0.00;
1207
1208
					// Loop through the years
1209
					$y = $dates['year'];
1210
					if ( ! isset( $earnings['earnings'] ) ) {
1211
						$earnings['earnings'] = array();
1212
					}
1213
					while ( $y <= $dates['year_end'] ) :
1214
1215
						if ( $dates['year'] == $dates['year_end'] ) {
1216
							$month_start = $dates['m_start'];
1217
							$month_end   = $dates['m_end'];
1218
						} elseif ( $y == $dates['year'] && $dates['year_end'] > $dates['year'] ) {
1219
							$month_start = $dates['m_start'];
1220
							$month_end   = 12;
1221
						} elseif ( $y == $dates['year_end'] ) {
1222
							$month_start = 1;
1223
							$month_end   = $dates['m_end'];
1224
						} else {
1225
							$month_start = 1;
1226
							$month_end   = 12;
1227
						}
1228
1229
						$i = $month_start;
1230
						while ( $i <= $month_end ) :
1231
1232
							if ( $i == $dates['m_start'] ) {
1233
								$d = $dates['day_start'];
1234
							} else {
1235
								$d = 1;
1236
							}
1237
1238
							if ( $i == $dates['m_end'] ) {
1239
								$num_of_days = $dates['day_end'];
1240
							} else {
1241
								$num_of_days = cal_days_in_month( CAL_GREGORIAN, $i, $y );
1242
							}
1243
1244
							while ( $d <= $num_of_days ) :
1245
								$earnings_stat = give_get_earnings_by_date( $d, $i, $y );
1246
								$date_key      = date( 'Ymd', strtotime( $y . '/' . $i . '/' . $d ) );
1247
								if ( ! isset( $earnings['earnings'][ $date_key ] ) ) {
1248
									$earnings['earnings'][ $date_key ] = 0;
1249
								}
1250
								$earnings['earnings'][ $date_key ] += $earnings_stat;
1251
								$total += $earnings_stat;
1252
								$d ++;
1253
							endwhile;
1254
1255
							$i ++;
1256
						endwhile;
1257
1258
						$y ++;
1259
					endwhile;
1260
1261
					$earnings['totals'] = $total;
1262
				} else {
1263
					if ( $args['date'] == 'this_quarter' || $args['date'] == 'last_quarter' ) {
1264
						$earnings_count = (float) 0.00;
1265
1266
						// Loop through the months
1267
						$month = $dates['m_start'];
1268
1269
						while ( $month <= $dates['m_end'] ) :
1270
							$earnings_count += give_get_earnings_by_date( null, $month, $dates['year'] );
1271
							$month ++;
1272
						endwhile;
1273
1274
						$earnings['earnings'][ $args['date'] ] = $earnings_count;
1275
					} else {
1276
						$earnings['earnings'][ $args['date'] ] = give_get_earnings_by_date( $dates['day'], $dates['m_start'], $dates['year'] );
1277
					}
1278
				}
1279
			} elseif ( $args['form'] == 'all' ) {
1280
				$forms = get_posts( array( 'post_type' => 'give_forms', 'nopaging' => true ) );
1281
1282
				$i = 0;
1283
				foreach ( $forms as $form_info ) {
1284
					$earnings['earnings'][ $i ] = array( $form_info->post_name => give_get_form_earnings_stats( $form_info->ID ) );
1285
					$i ++;
1286
				}
1287
			} else {
1288
				if ( get_post_type( $args['form'] ) == 'give_forms' ) {
1289
					$form_info               = get_post( $args['form'] );
1290
					$earnings['earnings'][0] = array( $form_info->post_name => give_get_form_earnings_stats( $args['form'] ) );
1291
				} else {
1292
					$error['error'] = sprintf( __( 'Form %s not found!', 'give' ), $args['form'] );
1293
				}
1294
			}
1295
1296
			if ( ! empty( $error ) ) {
1297
				return $error;
1298
			}
1299
1300
			return $earnings;
1301
		} elseif ( $args['type'] == 'donors' ) {
1302
			$customers                          = new Give_DB_Customers();
1303
			$stats['donations']['total_donors'] = $customers->count();
1304
1305
			return $stats;
1306
1307
		} elseif ( empty( $args['type'] ) ) {
1308
			$stats = array_merge( $stats, $this->get_default_sales_stats() );
1309
			$stats = array_merge( $stats, $this->get_default_earnings_stats() );
1310
1311
			return array( 'stats' => $stats );
1312
		}
1313
	}
1314
1315
	/**
1316
	 * Retrieves Recent Sales
1317
	 *
1318
	 * @access public
1319
	 * @since  1.1
1320
	 * @return array
1321
	 */
1322 10
	public function get_recent_donations() {
1323 10
		global $wp_query;
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...
1324
1325 10
		$sales = array();
1326
1327 10
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1328
			return $sales;
1329
		}
1330
1331 10
		if ( isset( $wp_query->query_vars['id'] ) ) {
1332
			$query   = array();
1333
			$query[] = give_get_payment_by( 'id', $wp_query->query_vars['id'] );
1334 10
		} elseif ( isset( $wp_query->query_vars['purchasekey'] ) ) {
1335
			$query   = array();
1336
			$query[] = give_get_payment_by( 'key', $wp_query->query_vars['purchasekey'] );
1337 10
		} elseif ( isset( $wp_query->query_vars['email'] ) ) {
1338
			$query = give_get_payments( array(
1339
				'meta_key'   => '_give_payment_user_email',
1340
				'meta_value' => $wp_query->query_vars['email'],
1341
				'number'     => $this->per_page(),
1342
				'page'       => $this->get_paged(),
1343
				'status'     => 'publish'
1344
			) );
1345
		} else {
1346 10
			$query = give_get_payments( array(
1347 10
				'number' => $this->per_page(),
1348 10
				'page'   => $this->get_paged(),
1349
				'status' => 'publish'
1350 10
			) );
1351
		}
1352
1353 10
		if ( $query ) {
1354 10
			$i = 0;
1355 10
			foreach ( $query as $payment ) {
1356 10
				$payment_meta = give_get_payment_meta( $payment->ID );
1357 10
				$user_info    = give_get_payment_meta_user_info( $payment->ID );
1358 10
				$first_name   = isset( $user_info['first_name'] ) ? $user_info['first_name'] : '';
1359 10
				$last_name    = isset( $user_info['last_name'] ) ? $user_info['last_name'] : '';
1360
1361 10
				$sales['donations'][ $i ]['ID']             = give_get_payment_number( $payment->ID );
1362 10
				$sales['donations'][ $i ]['transaction_id'] = give_get_payment_transaction_id( $payment->ID );
1363 10
				$sales['donations'][ $i ]['key']            = give_get_payment_key( $payment->ID );
1364 10
				$sales['donations'][ $i ]['total']          = give_get_payment_amount( $payment->ID );
1365 10
				$sales['donations'][ $i ]['gateway']        = give_get_payment_gateway( $payment->ID );
1366 10
				$sales['donations'][ $i ]['name']           = $first_name . ' ' . $last_name;
1367 10
				$sales['donations'][ $i ]['fname']          = $first_name;
1368 10
				$sales['donations'][ $i ]['lname']          = $last_name;
1369 10
				$sales['donations'][ $i ]['email']          = give_get_payment_user_email( $payment->ID );
1370 10
				$sales['donations'][ $i ]['date']           = $payment->post_date;
1371
1372 10
				$form_id  = isset( $payment_meta['form_id'] ) ? $payment_meta['form_id'] : $payment_meta;
1373 10
				$price    = isset( $payment_meta['form_id'] ) ? give_get_form_price( $payment_meta['form_id'] ) : false;
1374 10
				$price_id = isset( $payment_meta['price_id'] ) ? $payment_meta['price_id'] : null;
1375
1376 10
				$sales['donations'][ $i ]['form']['id']    = $form_id;
1377 10
				$sales['donations'][ $i ]['form']['name']  = get_the_title( $payment_meta['form_id'] );
1378 10
				$sales['donations'][ $i ]['form']['price'] = $price;
1379
1380 10
				if ( give_has_variable_prices( $form_id ) ) {
1381 10
					if ( isset( $payment_meta['price_id'] ) ) {
1382 10
						$price_name                                     = give_get_price_option_name( $form_id, $payment_meta['price_id'], $payment->ID );
1383 10
						$sales['donations'][ $i ]['form']['price_name'] = $price_name;
1384 10
						$sales['donations'][ $i ]['form']['price_id']   = $price_id;
1385 10
						$sales['donations'][ $i ]['form']['price']      = give_get_price_option_amount( $form_id, $price_id );
1386
1387 10
					}
1388 10
				}
1389
1390
				//Add custom meta to API
1391 10
				foreach ( $payment_meta as $meta_key => $meta_value ) {
1392
1393
					$exceptions = array(
1394 10
						'form_title',
1395 10
						'form_id',
1396 10
						'price_id',
1397 10
						'user_info',
1398 10
						'key',
1399 10
						'email',
1400 10
						'date',
1401 10
					);
1402
1403
					//Don't clutter up results with dupes
1404 10
					if ( in_array( $meta_key, $exceptions ) ) {
1405 10
						continue;
1406
					}
1407
1408 10
					$sales['donations'][ $i ]['payment_meta'][ $meta_key ] = $meta_value;
1409
1410 10
				}
1411
1412 10
				$i ++;
1413 10
			}
1414 10
		}
1415
1416 10
		return apply_filters( 'give_api_donations_endpoint', $sales );
1417
	}
1418
1419
	/**
1420
	 * Retrieve the output format
1421
	 *
1422
	 * Determines whether results should be displayed in XML or JSON
1423
	 *
1424
	 * @since 1.1
1425
	 *
1426
	 * @return mixed|void
1427
	 */
1428
	public function get_output_format() {
1429
		global $wp_query;
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...
1430
1431
		$format = isset( $wp_query->query_vars['format'] ) ? $wp_query->query_vars['format'] : 'json';
1432
1433
		return apply_filters( 'give_api_output_format', $format );
1434
	}
1435
1436
1437
	/**
1438
	 * Log each API request, if enabled
1439
	 *
1440
	 * @access private
1441
	 * @since  1.1
1442
	 * @global      $give_logs
1443
	 * @global      $wp_query
1444
	 *
1445
	 * @param array $data
1446
	 *
1447
	 * @return void
1448
	 */
1449
	private function log_request( $data = array() ) {
1450
		if ( ! $this->log_requests ) {
1451
			return;
1452
		}
1453
1454
		global $give_logs, $wp_query;
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...
1455
1456
		$query = array(
1457
			'give-api'    => $wp_query->query_vars['give-api'],
1458
			'key'         => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null,
1459
			'token'       => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null,
1460
			'query'       => isset( $wp_query->query_vars['query'] ) ? $wp_query->query_vars['query'] : null,
1461
			'type'        => isset( $wp_query->query_vars['type'] ) ? $wp_query->query_vars['type'] : null,
1462
			'form'        => isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null,
1463
			'customer'    => isset( $wp_query->query_vars['customer'] ) ? $wp_query->query_vars['customer'] : null,
1464
			'date'        => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null,
1465
			'startdate'   => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null,
1466
			'enddate'     => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null,
1467
			'id'          => isset( $wp_query->query_vars['id'] ) ? $wp_query->query_vars['id'] : null,
1468
			'purchasekey' => isset( $wp_query->query_vars['purchasekey'] ) ? $wp_query->query_vars['purchasekey'] : null,
1469
			'email'       => isset( $wp_query->query_vars['email'] ) ? $wp_query->query_vars['email'] : null,
1470
		);
1471
1472
		$log_data = array(
1473
			'log_type'     => 'api_request',
1474
			'post_excerpt' => http_build_query( $query ),
1475
			'post_content' => ! empty( $data['error'] ) ? $data['error'] : '',
1476
		);
1477
1478
		$log_meta = array(
1479
			'request_ip' => give_get_ip(),
1480
			'user'       => $this->user_id,
1481
			'key'        => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null,
1482
			'token'      => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null,
1483
			'time'       => $data['request_speed'],
1484
			'version'    => $this->get_queried_version()
1485
		);
1486
1487
		$give_logs->insert_log( $log_data, $log_meta );
1488
	}
1489
1490
1491
	/**
1492
	 * Retrieve the output data
1493
	 *
1494
	 * @access public
1495
	 * @since  1.1
1496
	 * @return array
1497
	 */
1498
	public function get_output() {
1499
		return $this->data;
1500
	}
1501
1502
	/**
1503
	 * Output Query in either JSON/XML. The query data is outputted as JSON
1504
	 * by default
1505
	 *
1506
	 * @since 1.1
1507
	 * @global    $wp_query
1508
	 *
1509
	 * @param int $status_code
1510
	 */
1511
	public function output( $status_code = 200 ) {
1512
		global $wp_query;
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...
1513
1514
		$format = $this->get_output_format();
1515
1516
		status_header( $status_code );
1517
1518
		do_action( 'give_api_output_before', $this->data, $this, $format );
1519
1520
		switch ( $format ) :
1521
1522
			case 'xml' :
1523
1524
				require_once GIVE_PLUGIN_DIR . 'includes/libraries/array2xml.php';
1525
				$xml = Array2XML::createXML( 'give', $this->data );
1526
				echo $xml->saveXML();
1527
1528
				break;
1529
1530
			case 'json' :
1531
1532
				header( 'Content-Type: application/json' );
1533
				if ( ! empty( $this->pretty_print ) ) {
1534
					echo json_encode( $this->data, $this->pretty_print );
1535
				} else {
1536
					echo json_encode( $this->data );
1537
				}
1538
1539
				break;
1540
1541
1542
			default :
1543
1544
				// Allow other formats to be added via extensions
1545
				do_action( 'give_api_output_' . $format, $this->data, $this );
1546
1547
				break;
1548
1549
		endswitch;
1550
1551
		do_action( 'give_api_output_after', $this->data, $this, $format );
1552
1553
		give_die();
1554
	}
1555
1556
	/**
1557
	 * Modify User Profile
1558
	 *
1559
	 * Modifies the output of profile.php to add key generation/revocation
1560
	 *
1561
	 * @access public
1562
	 * @since  1.1
1563
	 *
1564
	 * @param object $user Current user info
1565
	 *
1566
	 * @return void
1567
	 */
1568
	function user_key_field( $user ) {
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...
1569
1570
		if ( ( give_get_option( 'api_allow_user_keys', false ) || current_user_can( 'manage_give_settings' ) ) && current_user_can( 'edit_user', $user->ID ) ) {
1571
			$user = get_userdata( $user->ID );
1572
			?>
1573
			<table class="form-table">
1574
				<tbody>
1575
				<tr>
1576
					<th>
1577
						<?php esc_attr_e( 'Give API Keys', 'give' ); ?>
1578
					</th>
1579
					<td>
1580
						<?php
1581
						$public_key = $this->get_user_public_key( $user->ID );
1582
						$secret_key = $this->get_user_secret_key( $user->ID );
1583
						?>
1584
						<?php if ( empty( $user->give_user_public_key ) ) { ?>
1585
							<input name="give_set_api_key" type="checkbox" id="give_set_api_key" value="0" />
1586
							<span class="description"><?php esc_attr_e( 'Generate API Key', 'give' ); ?></span>
1587
						<?php } else { ?>
1588
							<strong style="display:inline-block; width: 125px;"><?php _e( 'Public key:', 'give' ); ?>&nbsp;</strong>
1589
							<input type="text" disabled="disabled" class="regular-text" id="publickey" value="<?php echo esc_attr( $public_key ); ?>" />
1590
							<br />
1591
							<strong style="display:inline-block; width: 125px;"><?php esc_attr_e( 'Secret key:', 'give' ); ?>&nbsp;</strong>
1592
							<input type="text" disabled="disabled" class="regular-text" id="privatekey" value="<?php echo esc_attr( $secret_key ); ?>" />
1593
							<br />
1594
							<strong style="display:inline-block; width: 125px;"><?php esc_attr_e( 'Token:', 'give' ); ?>&nbsp;</strong>
1595
							<input type="text" disabled="disabled" class="regular-text" id="token" value="<?php echo esc_attr( $this->get_token( $user->ID ) ); ?>" />
1596
							<br />
1597
							<input name="give_set_api_key" type="checkbox" id="give_set_api_key" value="0" />
1598
							<span class="description"><label for="give_set_api_key"><?php esc_attr_e( 'Revoke API Keys', 'give' ); ?></label></span>
1599
						<?php } ?>
1600
					</td>
1601
				</tr>
1602
				</tbody>
1603
			</table>
1604
		<?php }
1605
	}
1606
1607
	/**
1608
	 * Process an API key generation/revocation
1609
	 *
1610
	 * @access public
1611
	 * @since  1.1
1612
	 *
1613
	 * @param array $args
1614
	 *
1615
	 * @return void
1616
	 */
1617
	public function process_api_key( $args ) {
1618
1619
		if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'give-api-nonce' ) ) {
1620
1621
			wp_die( esc_attr__( 'Nonce verification failed', 'give' ), __( 'Error', 'give' ), array( 'response' => 403 ) );
1622
1623
		}
1624
1625
		if ( empty( $args['user_id'] ) ) {
1626
			wp_die(  esc_attr__( 'User ID Required', 'give' ), esc_attr__( 'Error', 'give' ), array( 'response' => 401 ) );
1627
		}
1628
1629
		if ( is_numeric( $args['user_id'] ) ) {
1630
			$user_id = isset( $args['user_id'] ) ? absint( $args['user_id'] ) : get_current_user_id();
1631
		} else {
1632
			$userdata = get_user_by( 'login', $args['user_id'] );
1633
			$user_id  = $userdata->ID;
1634
		}
1635
		$process = isset( $args['give_api_process'] ) ? strtolower( $args['give_api_process'] ) : false;
1636
1637
		if ( $user_id == get_current_user_id() && ! give_get_option( 'allow_user_api_keys' ) && ! current_user_can( 'manage_give_settings' ) ) {
1638
			wp_die( sprintf( __( 'You do not have permission to %s API keys for this user', 'give' ), $process ), __( 'Error', 'give' ), array( 'response' => 403 ) );
1639
		} elseif ( ! current_user_can( 'manage_give_settings' ) ) {
1640
			wp_die( sprintf( __( 'You do not have permission to %s API keys for this user', 'give' ), $process ), __( 'Error', 'give' ), array( 'response' => 403 ) );
1641
		}
1642
1643
		switch ( $process ) {
1644
			case 'generate':
1645
				if ( $this->generate_api_key( $user_id ) ) {
1646
					delete_transient( 'give-total-api-keys' );
1647
					wp_redirect( add_query_arg( 'give-message', 'api-key-generated', 'edit.php?post_type=give_forms&page=give-settings&tab=api' ) );
1648
					exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method process_api_key() 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...
1649
				} else {
1650
					wp_redirect( add_query_arg( 'give-message', 'api-key-failed', 'edit.php?post_type=give_forms&page=give-settings&tab=api' ) );
1651
					exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method process_api_key() 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...
1652
				}
1653
				break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1654
			case 'regenerate':
1655
				$this->generate_api_key( $user_id, true );
1656
				delete_transient( 'give-total-api-keys' );
1657
				wp_redirect( add_query_arg( 'give-message', 'api-key-regenerated', 'edit.php?post_type=give_forms&page=give-settings&tab=api' ) );
1658
				exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method process_api_key() 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...
1659
				break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1660
			case 'revoke':
1661
				$this->revoke_api_key( $user_id );
1662
				delete_transient( 'give-total-api-keys' );
1663
				wp_redirect( add_query_arg( 'give-message', 'api-key-revoked', 'edit.php?post_type=give_forms&page=give-settings&tab=api' ) );
1664
				exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method process_api_key() 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...
1665
				break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1666
			default;
1667
				break;
1668
		}
1669
	}
1670
1671
	/**
1672
	 * Generate new API keys for a user
1673
	 *
1674
	 * @access public
1675
	 * @since  1.1
1676
	 *
1677
	 * @param int     $user_id    User ID the key is being generated for
1678
	 * @param boolean $regenerate Regenerate the key for the user
1679
	 *
1680
	 * @return boolean True if (re)generated succesfully, false otherwise.
1681
	 */
1682
	public function generate_api_key( $user_id = 0, $regenerate = false ) {
1683
1684
		if ( empty( $user_id ) ) {
1685
			return false;
1686
		}
1687
1688
		$user = get_userdata( $user_id );
1689
1690
		if ( ! $user ) {
1691
			return false;
1692
		}
1693
1694
		$public_key = $this->get_user_public_key( $user_id );
1695
		$secret_key = $this->get_user_secret_key( $user_id );
1696
1697
		if ( empty( $public_key ) || $regenerate == true ) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1698
			$new_public_key = $this->generate_public_key( $user->user_email );
1699
			$new_secret_key = $this->generate_private_key( $user->ID );
1700
		} else {
1701
			return false;
1702
		}
1703
1704
		if ( $regenerate == true ) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1705
			$this->revoke_api_key( $user->ID );
1706
		}
1707
1708
		update_user_meta( $user_id, $new_public_key, 'give_user_public_key' );
1709
		update_user_meta( $user_id, $new_secret_key, 'give_user_secret_key' );
1710
1711
		return true;
1712
	}
1713
1714
	/**
1715
	 * Revoke a users API keys
1716
	 *
1717
	 * @access public
1718
	 * @since  1.1
1719
	 *
1720
	 * @param int $user_id User ID of user to revoke key for
1721
	 *
1722
	 * @return string
1723
	 */
1724
	public function revoke_api_key( $user_id = 0 ) {
1725
1726
		if ( empty( $user_id ) ) {
1727
			return false;
1728
		}
1729
1730
		$user = get_userdata( $user_id );
1731
1732
		if ( ! $user ) {
1733
			return false;
1734
		}
1735
1736
		$public_key = $this->get_user_public_key( $user_id );
1737
		$secret_key = $this->get_user_secret_key( $user_id );
1738
		if ( ! empty( $public_key ) ) {
1739
			delete_transient( md5( 'give_api_user_' . $public_key ) );
1740
			delete_transient( md5( 'give_api_user_public_key' . $user_id ) );
1741
			delete_transient( md5( 'give_api_user_secret_key' . $user_id ) );
1742
			delete_user_meta( $user_id, $public_key );
1743
			delete_user_meta( $user_id, $secret_key );
1744
		} else {
1745
			return false;
1746
		}
1747
1748
		return true;
1749
	}
1750
1751 2
	public function get_version() {
1752 2
		return self::VERSION;
1753
	}
1754
1755
1756
	/**
1757
	 * Generate and Save API key
1758
	 *
1759
	 * Generates the key requested by user_key_field and stores it in the database
1760
	 *
1761
	 * @access public
1762
	 * @since  1.1
1763
	 *
1764
	 * @param int $user_id
1765
	 *
1766
	 * @return void
1767
	 */
1768 2
	public function update_key( $user_id ) {
1769 2
		if ( current_user_can( 'edit_user', $user_id ) && isset( $_POST['give_set_api_key'] ) ) {
1770
1771 2
			$user = get_userdata( $user_id );
1772
1773 2
			$public_key = $this->get_user_public_key( $user_id );
1774 2
			$secret_key = $this->get_user_secret_key( $user_id );
1775
1776 2
			if ( empty( $public_key ) ) {
1777 2
				$new_public_key = $this->generate_public_key( $user->user_email );
1778 2
				$new_secret_key = $this->generate_private_key( $user->ID );
1779
1780 2
				update_user_meta( $user_id, $new_public_key, 'give_user_public_key' );
1781 2
				update_user_meta( $user_id, $new_secret_key, 'give_user_secret_key' );
1782 2
			} else {
1783
				$this->revoke_api_key( $user_id );
1784
			}
1785 2
		}
1786 2
	}
1787
1788
	/**
1789
	 * Generate the public key for a user
1790
	 *
1791
	 * @access private
1792
	 * @since  1.1
1793
	 *
1794
	 * @param string $user_email
1795
	 *
1796
	 * @return string
1797
	 */
1798 2
	private function generate_public_key( $user_email = '' ) {
1799 2
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
1800 2
		$public   = hash( 'md5', $user_email . $auth_key . date( 'U' ) );
1801 2
		return $public;
1802
	}
1803
1804
	/**
1805
	 * Generate the secret key for a user
1806
	 *
1807
	 * @access private
1808
	 * @since  1.1
1809
	 *
1810
	 * @param int $user_id
1811
	 *
1812
	 * @return string
1813
	 */
1814 2
	private function generate_private_key( $user_id = 0 ) {
1815 2
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
1816 2
		$secret   = hash( 'md5', $user_id . $auth_key . date( 'U' ) );
1817
1818 2
		return $secret;
1819
	}
1820
1821
	/**
1822
	 * Retrieve the user's token
1823
	 *
1824
	 * @access private
1825
	 * @since  1.1
1826
	 *
1827
	 * @param int $user_id
1828
	 *
1829
	 * @return string
1830
	 */
1831
	public function get_token( $user_id = 0 ) {
1832
		return hash( 'md5', $this->get_user_secret_key( $user_id ) . $this->get_user_public_key( $user_id ) );
1833
	}
1834
1835
	/**
1836
	 * Generate the default sales stats returned by the 'stats' endpoint
1837
	 *
1838
	 * @access private
1839
	 * @since  1.1
1840
	 * @return array default sales statistics
1841
	 */
1842
	private function get_default_sales_stats() {
1843
1844
		// Default sales return
1845
		$sales                               = array();
1846
		$sales['donations']['today']         = $this->stats->get_sales( 0, 'today' );
1847
		$sales['donations']['current_month'] = $this->stats->get_sales( 0, 'this_month' );
1848
		$sales['donations']['last_month']    = $this->stats->get_sales( 0, 'last_month' );
1849
		$sales['donations']['totals']        = give_get_total_sales();
1850
1851
		return $sales;
1852
	}
1853
1854
	/**
1855
	 * Generate the default earnings stats returned by the 'stats' endpoint
1856
	 *
1857
	 * @access private
1858
	 * @since  1.1
1859
	 * @return array default earnings statistics
1860
	 */
1861
	private function get_default_earnings_stats() {
1862
1863
		// Default earnings return
1864
		$earnings                              = array();
1865
		$earnings['earnings']['today']         = $this->stats->get_earnings( 0, 'today' );
1866
		$earnings['earnings']['current_month'] = $this->stats->get_earnings( 0, 'this_month' );
1867
		$earnings['earnings']['last_month']    = $this->stats->get_earnings( 0, 'last_month' );
1868
		$earnings['earnings']['totals']        = give_get_total_earnings();
1869
1870
		return $earnings;
1871
	}
1872
1873
	/**
1874
	 * API Key Backwards Compatibility
1875
	 *
1876
	 * @description A Backwards Compatibility call for the change of meta_key/value for users API Keys
1877
	 *
1878
	 * @since       1.3.6
1879
	 *
1880
	 * @param  string $check     Whether to check the cache or not
1881
	 * @param  int    $object_id The User ID being passed
1882
	 * @param  string $meta_key  The user meta key
1883
	 * @param  bool   $single    If it should return a single value or array
1884
	 *
1885
	 * @return string            The API key/secret for the user supplied
1886
	 */
1887 38
	public function api_key_backwards_compat( $check, $object_id, $meta_key, $single ) {
1888
1889 38
		if ( $meta_key !== 'give_user_public_key' && $meta_key !== 'give_user_secret_key' ) {
1890 38
			return $check;
1891
		}
1892
1893
		$return = $check;
1894
1895
		switch ( $meta_key ) {
1896
			case 'give_user_public_key':
1897
				$return = Give()->api->get_user_public_key( $object_id );
1898
				break;
1899
			case 'give_user_secret_key':
1900
				$return = Give()->api->get_user_secret_key( $object_id );
1901
				break;
1902
		}
1903
1904
		if ( ! $single ) {
1905
			$return = array( $return );
1906
		}
1907
1908
		return $return;
1909
1910
	}
1911
1912
}
1913