Completed
Push — master ( 647547...13dcfc )
by Devin
17:56
created

Give_API   D

Complexity

Total Complexity 253

Size/Duplication

Total Lines 1902
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 30.5%

Importance

Changes 5
Bugs 1 Features 0
Metric Value
dl 0
loc 1902
ccs 276
cts 905
cp 0.305
rs 4.4102
c 5
b 1
f 0
wmc 253
lcom 1
cbo 4

42 Methods

Rating   Name   Duplication   Size   Complexity  
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
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
D process_query() 0 85 15
A get_query_mode() 0 4 1
B set_query_mode() 0 28 3
A get_paged() 0 5 2
A per_page() 0 11 4
D get_dates() 0 170 22
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
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 6 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_recent_donations() 0 109 18

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 13
	public function __construct() {
137
138 13
		$this->versions = array(
139 13
			'v1' => 'GIVE_API_V1',
140
		);
141
142 13
		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 13
			require_once GIVE_PLUGIN_DIR . 'includes/api/class-give-api-' . $version . '.php';
144 13
		}
145
146 13
		add_action( 'init', array( $this, 'add_endpoint' ) );
147 13
		add_action( 'wp', array( $this, 'process_query' ), - 1 );
148 13
		add_filter( 'query_vars', array( $this, 'query_vars' ) );
149 13
		add_action( 'show_user_profile', array( $this, 'user_key_field' ) );
150 13
		add_action( 'edit_user_profile', array( $this, 'user_key_field' ) );
151 13
		add_action( 'personal_options_update', array( $this, 'update_key' ) );
152 13
		add_action( 'edit_user_profile_update', array( $this, 'update_key' ) );
153 13
		add_action( 'give_process_api_key', array( $this, 'process_api_key' ) );
154
155
		// Setup a backwards compatibility check for user API Keys
156 13
		add_filter( 'get_user_metadata', array( $this, 'api_key_backwards_compat' ), 10, 4 );
157
158
		// Determine if JSON_PRETTY_PRINT is available
159 13
		$this->pretty_print = defined( 'JSON_PRETTY_PRINT' ) ? JSON_PRETTY_PRINT : null;
160
161
		// Allow API request logging to be turned off
162 13
		$this->log_requests = apply_filters( 'give_api_log_requests', $this->log_requests );
163
164
		// Setup Give_Payment_Stats instance
165 13
		$this->stats = new Give_Payment_Stats;
166
167 13
	}
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 11
	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 11
		add_rewrite_endpoint( 'give-api', EP_ALL );
180 11
	}
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 13
	public function get_versions() {
220 13
		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 11
	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 11
		return $user_public_key;
396
	}
397
398 11
	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 11
		}
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
			// 400 is Bad Request
618
			$this->output( 400 );
619
		}
620
621
		$this->endpoint = $query;
622
	}
623
624
	/**
625
	 * Get page number
626
	 *
627
	 * @access private
628
	 * @since  1.1
629
	 * @global $wp_query
630
	 * @return int $wp_query->query_vars['page'] if page number returned (default: 1)
631
	 */
632 11
	public function get_paged() {
633 11
		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...
634
635 11
		return isset( $wp_query->query_vars['page'] ) ? $wp_query->query_vars['page'] : 1;
636
	}
637
638
639
	/**
640
	 * Number of results to display per page
641
	 *
642
	 * @access private
643
	 * @since  1.1
644
	 * @global $wp_query
645
	 * @return int $per_page Results to display per page (default: 10)
646
	 */
647 11
	public function per_page() {
648 11
		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...
649
650 11
		$per_page = isset( $wp_query->query_vars['number'] ) ? $wp_query->query_vars['number'] : 10;
651
652 11
		if ( $per_page < 0 && $this->get_query_mode() == 'donors' ) {
653
			$per_page = 99999999;
654
		} // Customers query doesn't support -1
655
656 11
		return apply_filters( 'give_api_results_per_page', $per_page );
657
	}
658
659
	/**
660
	 * Sets up the dates used to retrieve earnings/donations
661
	 *
662
	 * @access public
663
	 * @since  1.2
664
	 *
665
	 * @param array $args Arguments to override defaults
666
	 *
667
	 * @return array $dates
668
	 */
669
	public function get_dates( $args = array() ) {
670
		$dates = array();
671
672
		$defaults = array(
673
			'type'      => '',
674
			'form'      => null,
675
			'date'      => null,
676
			'startdate' => null,
677
			'enddate'   => null
678
		);
679
680
		$args = wp_parse_args( $args, $defaults );
681
682
		$current_time = current_time( 'timestamp' );
683
684
		if ( 'range' === $args['date'] ) {
685
			$startdate          = strtotime( $args['startdate'] );
686
			$enddate            = strtotime( $args['enddate'] );
687
			$dates['day_start'] = date( 'd', $startdate );
688
			$dates['day_end']   = date( 'd', $enddate );
689
			$dates['m_start']   = date( 'n', $startdate );
690
			$dates['m_end']     = date( 'n', $enddate );
691
			$dates['year']      = date( 'Y', $startdate );
692
			$dates['year_end']  = date( 'Y', $enddate );
693
		} else {
694
			// Modify dates based on predefined ranges
695
			switch ( $args['date'] ) :
696
697
				case 'this_month' :
698
					$dates['day']     = null;
699
					$dates['m_start'] = date( 'n', $current_time );
700
					$dates['m_end']   = date( 'n', $current_time );
701
					$dates['year']    = date( 'Y', $current_time );
702
					break;
703
704
				case 'last_month' :
705
					$dates['day']     = null;
706
					$dates['m_start'] = date( 'n', $current_time ) == 1 ? 12 : date( 'n', $current_time ) - 1;
707
					$dates['m_end']   = $dates['m_start'];
708
					$dates['year']    = date( 'n', $current_time ) == 1 ? date( 'Y', $current_time ) - 1 : date( 'Y', $current_time );
709
					break;
710
711
				case 'today' :
712
					$dates['day']     = date( 'd', $current_time );
713
					$dates['m_start'] = date( 'n', $current_time );
714
					$dates['m_end']   = date( 'n', $current_time );
715
					$dates['year']    = date( 'Y', $current_time );
716
					break;
717
718
				case 'yesterday' :
719
720
					$year  = date( 'Y', $current_time );
721
					$month = date( 'n', $current_time );
722
					$day   = date( 'd', $current_time );
723
724
					if ( $month == 1 && $day == 1 ) {
725
726
						$year -= 1;
727
						$month = 12;
728
						$day   = cal_days_in_month( CAL_GREGORIAN, $month, $year );
729
730
					} elseif ( $month > 1 && $day == 1 ) {
731
732
						$month -= 1;
733
						$day = cal_days_in_month( CAL_GREGORIAN, $month, $year );
734
735
					} else {
736
737
						$day -= 1;
738
739
					}
740
741
					$dates['day']     = $day;
742
					$dates['m_start'] = $month;
743
					$dates['m_end']   = $month;
744
					$dates['year']    = $year;
745
746
					break;
747
748
				case 'this_quarter' :
749
					$month_now = date( 'n', $current_time );
750
751
					$dates['day'] = null;
752
753
					if ( $month_now <= 3 ) {
754
755
						$dates['m_start'] = 1;
756
						$dates['m_end']   = 3;
757
						$dates['year']    = date( 'Y', $current_time );
758
759
					} else if ( $month_now <= 6 ) {
760
761
						$dates['m_start'] = 4;
762
						$dates['m_end']   = 6;
763
						$dates['year']    = date( 'Y', $current_time );
764
765
					} else if ( $month_now <= 9 ) {
766
767
						$dates['m_start'] = 7;
768
						$dates['m_end']   = 9;
769
						$dates['year']    = date( 'Y', $current_time );
770
771
					} else {
772
773
						$dates['m_start'] = 10;
774
						$dates['m_end']   = 12;
775
						$dates['year']    = date( 'Y', $current_time );
776
777
					}
778
					break;
779
780
				case 'last_quarter' :
781
					$month_now = date( 'n', $current_time );
782
783
					$dates['day'] = null;
784
785
					if ( $month_now <= 3 ) {
786
787
						$dates['m_start'] = 10;
788
						$dates['m_end']   = 12;
789
						$dates['year']    = date( 'Y', $current_time ) - 1; // Previous year
790
791
					} else if ( $month_now <= 6 ) {
792
793
						$dates['m_start'] = 1;
794
						$dates['m_end']   = 3;
795
						$dates['year']    = date( 'Y', $current_time );
796
797
					} else if ( $month_now <= 9 ) {
798
799
						$dates['m_start'] = 4;
800
						$dates['m_end']   = 6;
801
						$dates['year']    = date( 'Y', $current_time );
802
803
					} else {
804
805
						$dates['m_start'] = 7;
806
						$dates['m_end']   = 9;
807
						$dates['year']    = date( 'Y', $current_time );
808
809
					}
810
					break;
811
812
				case 'this_year' :
813
					$dates['day']     = null;
814
					$dates['m_start'] = null;
815
					$dates['m_end']   = null;
816
					$dates['year']    = date( 'Y', $current_time );
817
					break;
818
819
				case 'last_year' :
820
					$dates['day']     = null;
821
					$dates['m_start'] = null;
822
					$dates['m_end']   = null;
823
					$dates['year']    = date( 'Y', $current_time ) - 1;
824
					break;
825
826
			endswitch;
827
		}
828
829
		/**
830
		 * Returns the filters for the dates used to retreive earnings/donations
831
		 *
832
		 * @since 1.2
833
		 *
834
		 * @param object $dates The dates used for retreiving earnings/donations
835
		 */
836
837
		return apply_filters( 'give_api_stat_dates', $dates );
838
	}
839
840
	/**
841
	 * Process Get Customers API Request
842
	 *
843
	 * @access public
844
	 * @since  1.1
845
	 * @global object $wpdb Used to query the database using the WordPress
846
	 *                          Database API
847
	 *
848
	 * @param int $customer Customer ID
849
	 *
850
	 * @return array $customers Multidimensional array of the customers
851
	 */
852 1
	public function get_customers( $customer = null ) {
853
854 1
		$customers = array();
855 1
		$error     = array();
856 1
		if ( ! user_can( $this->user_id, 'view_give_sensitive_data' ) && ! $this->override ) {
857
			return $customers;
858
		}
859
860 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...
861
862 1
		$paged    = $this->get_paged();
863 1
		$per_page = $this->per_page();
864 1
		$offset   = $per_page * ( $paged - 1 );
865
866 1
		if ( is_numeric( $customer ) ) {
867
			$field = 'id';
868
		} else {
869 1
			$field = 'email';
870
		}
871
872 1
		$customer_query = Give()->customers->get_customers( array(
873 1
			'number' => $per_page,
874 1
			'offset' => $offset,
875
			$field   => $customer
876 1
		) );
877 1
		$customer_count = 0;
878
879 1
		if ( $customer_query ) {
880
881 1
			foreach ( $customer_query as $customer_obj ) {
882
883 1
				$names      = explode( ' ', $customer_obj->name );
884 1
				$first_name = ! empty( $names[0] ) ? $names[0] : '';
885 1
				$last_name  = '';
886 1
				if ( ! empty( $names[1] ) ) {
887 1
					unset( $names[0] );
888 1
					$last_name = implode( ' ', $names );
889 1
				}
890
891 1
				$customers['donors'][ $customer_count ]['info']['user_id']      = '';
892 1
				$customers['donors'][ $customer_count ]['info']['username']     = '';
893 1
				$customers['donors'][ $customer_count ]['info']['display_name'] = '';
894 1
				$customers['donors'][ $customer_count ]['info']['customer_id']  = $customer_obj->id;
895 1
				$customers['donors'][ $customer_count ]['info']['first_name']   = $first_name;
896 1
				$customers['donors'][ $customer_count ]['info']['last_name']    = $last_name;
897 1
				$customers['donors'][ $customer_count ]['info']['email']        = $customer_obj->email;
898
899 1
				if ( ! empty( $customer_obj->user_id ) ) {
900
901 1
					$user_data = get_userdata( $customer_obj->user_id );
902
903
					// Customer with registered account
904 1
					$customers['donors'][ $customer_count ]['info']['user_id']      = $customer_obj->user_id;
905 1
					$customers['donors'][ $customer_count ]['info']['username']     = $user_data->user_login;
906 1
					$customers['donors'][ $customer_count ]['info']['display_name'] = $user_data->display_name;
907
908 1
				}
909
910 1
				$customers['donors'][ $customer_count ]['stats']['total_donations'] = $customer_obj->purchase_count;
911 1
				$customers['donors'][ $customer_count ]['stats']['total_spent']     = $customer_obj->purchase_value;
912
913 1
				$customer_count ++;
914
915 1
			}
916
917 1
		} elseif ( $customer ) {
918
919
			$error['error'] = sprintf( __( 'Donor %s not found!', 'give' ), $customer );
920
921
			return $error;
922
923
		} else {
924
925
			$error['error'] = __( 'No donors found!', 'give' );
926
927
			return $error;
928
929
		}
930
931 1
		return $customers;
932
	}
933
934
	/**
935
	 * Process Get Products API Request
936
	 *
937
	 * @access public
938
	 * @since  1.1
939
	 *
940
	 * @param int $form Give Form ID
941
	 *
942
	 * @return array $customers Multidimensional array of the forms
943
	 */
944 11
	public function get_forms( $form = null ) {
945
946 11
		$forms = array();
947 11
		$error = array();
948
949 11
		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...
950 11
			$forms['forms'] = array();
951
952 11
			$form_list = get_posts( array(
953 11
				'post_type'        => 'give_forms',
954 11
				'posts_per_page'   => $this->per_page(),
955 11
				'suppress_filters' => true,
956 11
				'paged'            => $this->get_paged()
957 11
			) );
958
959 11
			if ( $form_list ) {
960 11
				$i = 0;
961 11
				foreach ( $form_list as $form_info ) {
962 11
					$forms['forms'][ $i ] = $this->get_form_data( $form_info );
963 11
					$i ++;
964 11
				}
965 11
			}
966 11
		} else {
967
			if ( get_post_type( $form ) == 'give_forms' ) {
968
				$form_info = get_post( $form );
969
970
				$forms['forms'][0] = $this->get_form_data( $form_info );
971
972
			} else {
973
				$error['error'] = sprintf( __( 'Form %s not found!', 'give' ), $form );
974
975
				return $error;
976
			}
977
		}
978
979 11
		return $forms;
980
	}
981
982
	/**
983
	 * Given a give_forms post object, generate the data for the API output
984
	 *
985
	 * @since  1.1
986
	 *
987
	 * @param  object $form_info The Download Post Object
988
	 *
989
	 * @return array                Array of post data to return back in the API
990
	 */
991 11
	private function get_form_data( $form_info ) {
992
993 11
		$form = array();
994
995 11
		$form['info']['id']            = $form_info->ID;
996 11
		$form['info']['slug']          = $form_info->post_name;
997 11
		$form['info']['title']         = $form_info->post_title;
998 11
		$form['info']['create_date']   = $form_info->post_date;
999 11
		$form['info']['modified_date'] = $form_info->post_modified;
1000 11
		$form['info']['status']        = $form_info->post_status;
1001 11
		$form['info']['link']          = html_entity_decode( $form_info->guid );
1002 11
		$form['info']['content']       = get_post_meta( $form_info->ID, '_give_form_content', true );
1003 11
		$form['info']['thumbnail']     = wp_get_attachment_url( get_post_thumbnail_id( $form_info->ID ) );
1004
1005 11
		if ( give_get_option( 'enable_categories' ) == 'on' ) {
1006
			$form['info']['category'] = get_the_terms( $form_info, 'give_forms_category' );
1007
			$form['info']['tags']     = get_the_terms( $form_info, 'give_forms_tag' );
1008
		}
1009 11
		if ( give_get_option( 'enable_tags' ) == 'on' ) {
1010
			$form['info']['tags'] = get_the_terms( $form_info, 'give_forms_tag' );
1011
		}
1012
1013 11
		if ( user_can( $this->user_id, 'view_give_reports' ) || $this->override ) {
1014 11
			$form['stats']['total']['donations']           = give_get_form_sales_stats( $form_info->ID );
1015 11
			$form['stats']['total']['earnings']            = give_get_form_earnings_stats( $form_info->ID );
1016 11
			$form['stats']['monthly_average']['donations'] = give_get_average_monthly_form_sales( $form_info->ID );
1017 11
			$form['stats']['monthly_average']['earnings']  = give_get_average_monthly_form_earnings( $form_info->ID );
1018 11
		}
1019
1020 11
		$counter = 0;
1021 11
		if ( give_has_variable_prices( $form_info->ID ) ) {
1022 11
			foreach ( give_get_variable_prices( $form_info->ID ) as $price ) {
1023 11
				$counter ++;
1024
				//muli-level item
1025 11
				$level                                     = isset( $price['_give_text'] ) ? $price['_give_text'] : 'level-' . $counter;
1026 11
				$form['pricing'][ sanitize_key( $level ) ] = $price['_give_amount'];
1027
1028 11
			}
1029 11
		} else {
1030
			$form['pricing']['amount'] = give_get_form_price( $form_info->ID );
1031
		}
1032
1033 11
		if ( user_can( $this->user_id, 'view_give_sensitive_data' ) || $this->override ) {
1034
1035
			//Sensitive data here
1036 11
			do_action( 'give_api_sensitive_data' );
1037
1038 11
		}
1039
1040 11
		return apply_filters( 'give_api_forms_form', $form );
1041
1042
	}
1043
1044
	/**
1045
	 * Process Get Stats API Request
1046
	 *
1047
	 * @since 1.1
1048
	 *
1049
	 * @global object $wpdb Used to query the database using the WordPress
1050
	 *
1051
	 * @param array $args Arguments provided by API Request
1052
	 *
1053
	 * @return array
1054
	 */
1055
	public function get_stats( $args = array() ) {
1056
		$defaults = array(
1057
			'type'      => null,
1058
			'form'      => null,
1059
			'date'      => null,
1060
			'startdate' => null,
1061
			'enddate'   => null
1062
		);
1063
1064
		$args = wp_parse_args( $args, $defaults );
1065
1066
		$dates = $this->get_dates( $args );
1067
1068
		$stats    = array();
1069
		$earnings = array(
1070
			'earnings' => array()
1071
		);
1072
		$sales    = array(
1073
			'donations' => array()
1074
		);
1075
		$error    = array();
1076
1077
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1078
			return $stats;
1079
		}
1080
1081
		if ( $args['type'] == 'donations' ) {
1082
1083
			if ( $args['form'] == null ) {
1084
				if ( $args['date'] == null ) {
1085
					$sales = $this->get_default_sales_stats();
1086
				} elseif ( $args['date'] === 'range' ) {
1087
					// Return sales for a date range
1088
1089
					// Ensure the end date is later than the start date
1090
					if ( $args['enddate'] < $args['startdate'] ) {
1091
						$error['error'] = __( 'The end date must be later than the start date!', 'give' );
1092
					}
1093
1094
					// Ensure both the start and end date are specified
1095
					if ( empty( $args['startdate'] ) || empty( $args['enddate'] ) ) {
1096
						$error['error'] = __( 'Invalid or no date range specified!', 'give' );
1097
					}
1098
1099
					$total = 0;
1100
1101
					// Loop through the years
1102
					$y = $dates['year'];
1103
					while ( $y <= $dates['year_end'] ) :
1104
1105
						if ( $dates['year'] == $dates['year_end'] ) {
1106
							$month_start = $dates['m_start'];
1107
							$month_end   = $dates['m_end'];
1108
						} elseif ( $y == $dates['year'] && $dates['year_end'] > $dates['year'] ) {
1109
							$month_start = $dates['m_start'];
1110
							$month_end   = 12;
1111
						} elseif ( $y == $dates['year_end'] ) {
1112
							$month_start = 1;
1113
							$month_end   = $dates['m_end'];
1114
						} else {
1115
							$month_start = 1;
1116
							$month_end   = 12;
1117
						}
1118
1119
						$i = $month_start;
1120
						while ( $i <= $month_end ) :
1121
1122
							if ( $i == $dates['m_start'] ) {
1123
								$d = $dates['day_start'];
1124
							} else {
1125
								$d = 1;
1126
							}
1127
1128
							if ( $i == $dates['m_end'] ) {
1129
								$num_of_days = $dates['day_end'];
1130
							} else {
1131
								$num_of_days = cal_days_in_month( CAL_GREGORIAN, $i, $y );
1132
							}
1133
1134
							while ( $d <= $num_of_days ) :
1135
								$sale_count = give_get_sales_by_date( $d, $i, $y );
1136
								$date_key   = date( 'Ymd', strtotime( $y . '/' . $i . '/' . $d ) );
1137
								if ( ! isset( $sales['sales'][ $date_key ] ) ) {
1138
									$sales['sales'][ $date_key ] = 0;
1139
								}
1140
								$sales['sales'][ $date_key ] += $sale_count;
1141
								$total += $sale_count;
1142
								$d ++;
1143
							endwhile;
1144
							$i ++;
1145
						endwhile;
1146
1147
						$y ++;
1148
					endwhile;
1149
1150
					$sales['totals'] = $total;
1151
				} else {
1152
					if ( $args['date'] == 'this_quarter' || $args['date'] == 'last_quarter' ) {
1153
						$sales_count = 0;
1154
1155
						// Loop through the months
1156
						$month = $dates['m_start'];
1157
1158
						while ( $month <= $dates['m_end'] ) :
1159
							$sales_count += give_get_sales_by_date( null, $month, $dates['year'] );
1160
							$month ++;
1161
						endwhile;
1162
1163
						$sales['donations'][ $args['date'] ] = $sales_count;
1164
					} else {
1165
						$sales['donations'][ $args['date'] ] = give_get_sales_by_date( $dates['day'], $dates['m_start'], $dates['year'] );
1166
					}
1167
				}
1168
			} elseif ( $args['form'] == 'all' ) {
1169
				$forms = get_posts( array( 'post_type' => 'give_forms', 'nopaging' => true ) );
1170
				$i     = 0;
1171
				foreach ( $forms as $form_info ) {
1172
					$sales['donations'][ $i ] = array( $form_info->post_name => give_get_form_sales_stats( $form_info->ID ) );
1173
					$i ++;
1174
				}
1175
			} else {
1176
				if ( get_post_type( $args['form'] ) == 'give_forms' ) {
1177
					$form_info             = get_post( $args['form'] );
1178
					$sales['donations'][0] = array( $form_info->post_name => give_get_form_sales_stats( $args['form'] ) );
1179
				} else {
1180
					$error['error'] = sprintf( __( 'Product %s not found!', 'give' ), $args['form'] );
1181
				}
1182
			}
1183
1184
			if ( ! empty( $error ) ) {
1185
				return $error;
1186
			}
1187
1188
			return $sales;
1189
1190
		} elseif ( $args['type'] == 'earnings' ) {
1191
			if ( $args['form'] == null ) {
1192
				if ( $args['date'] == null ) {
1193
					$earnings = $this->get_default_earnings_stats();
1194
				} elseif ( $args['date'] === 'range' ) {
1195
					// Return sales for a date range
1196
1197
					// Ensure the end date is later than the start date
1198
					if ( $args['enddate'] < $args['startdate'] ) {
1199
						$error['error'] = __( 'The end date must be later than the start date!', 'give' );
1200
					}
1201
1202
					// Ensure both the start and end date are specified
1203
					if ( empty( $args['startdate'] ) || empty( $args['enddate'] ) ) {
1204
						$error['error'] = __( 'Invalid or no date range specified!', 'give' );
1205
					}
1206
1207
					$total = (float) 0.00;
1208
1209
					// Loop through the years
1210
					$y = $dates['year'];
1211
					if ( ! isset( $earnings['earnings'] ) ) {
1212
						$earnings['earnings'] = array();
1213
					}
1214
					while ( $y <= $dates['year_end'] ) :
1215
1216
						if ( $dates['year'] == $dates['year_end'] ) {
1217
							$month_start = $dates['m_start'];
1218
							$month_end   = $dates['m_end'];
1219
						} elseif ( $y == $dates['year'] && $dates['year_end'] > $dates['year'] ) {
1220
							$month_start = $dates['m_start'];
1221
							$month_end   = 12;
1222
						} elseif ( $y == $dates['year_end'] ) {
1223
							$month_start = 1;
1224
							$month_end   = $dates['m_end'];
1225
						} else {
1226
							$month_start = 1;
1227
							$month_end   = 12;
1228
						}
1229
1230
						$i = $month_start;
1231
						while ( $i <= $month_end ) :
1232
1233
							if ( $i == $dates['m_start'] ) {
1234
								$d = $dates['day_start'];
1235
							} else {
1236
								$d = 1;
1237
							}
1238
1239
							if ( $i == $dates['m_end'] ) {
1240
								$num_of_days = $dates['day_end'];
1241
							} else {
1242
								$num_of_days = cal_days_in_month( CAL_GREGORIAN, $i, $y );
1243
							}
1244
1245
							while ( $d <= $num_of_days ) :
1246
								$earnings_stat = give_get_earnings_by_date( $d, $i, $y );
1247
								$date_key      = date( 'Ymd', strtotime( $y . '/' . $i . '/' . $d ) );
1248
								if ( ! isset( $earnings['earnings'][ $date_key ] ) ) {
1249
									$earnings['earnings'][ $date_key ] = 0;
1250
								}
1251
								$earnings['earnings'][ $date_key ] += $earnings_stat;
1252
								$total += $earnings_stat;
1253
								$d ++;
1254
							endwhile;
1255
1256
							$i ++;
1257
						endwhile;
1258
1259
						$y ++;
1260
					endwhile;
1261
1262
					$earnings['totals'] = $total;
1263
				} else {
1264
					if ( $args['date'] == 'this_quarter' || $args['date'] == 'last_quarter' ) {
1265
						$earnings_count = (float) 0.00;
1266
1267
						// Loop through the months
1268
						$month = $dates['m_start'];
1269
1270
						while ( $month <= $dates['m_end'] ) :
1271
							$earnings_count += give_get_earnings_by_date( null, $month, $dates['year'] );
1272
							$month ++;
1273
						endwhile;
1274
1275
						$earnings['earnings'][ $args['date'] ] = $earnings_count;
1276
					} else {
1277
						$earnings['earnings'][ $args['date'] ] = give_get_earnings_by_date( $dates['day'], $dates['m_start'], $dates['year'] );
1278
					}
1279
				}
1280
			} elseif ( $args['form'] == 'all' ) {
1281
				$forms = get_posts( array( 'post_type' => 'give_forms', 'nopaging' => true ) );
1282
1283
				$i = 0;
1284
				foreach ( $forms as $form_info ) {
1285
					$earnings['earnings'][ $i ] = array( $form_info->post_name => give_get_form_earnings_stats( $form_info->ID ) );
1286
					$i ++;
1287
				}
1288
			} else {
1289
				if ( get_post_type( $args['form'] ) == 'give_forms' ) {
1290
					$form_info               = get_post( $args['form'] );
1291
					$earnings['earnings'][0] = array( $form_info->post_name => give_get_form_earnings_stats( $args['form'] ) );
1292
				} else {
1293
					$error['error'] = sprintf( __( 'Form %s not found!', 'give' ), $args['form'] );
1294
				}
1295
			}
1296
1297
			if ( ! empty( $error ) ) {
1298
				return $error;
1299
			}
1300
1301
			return $earnings;
1302
		} elseif ( $args['type'] == 'donors' ) {
1303
			$customers                          = new Give_DB_Customers();
1304
			$stats['donations']['total_donors'] = $customers->count();
1305
1306
			return $stats;
1307
1308
		} elseif ( empty( $args['type'] ) ) {
1309
			$stats = array_merge( $stats, $this->get_default_sales_stats() );
1310
			$stats = array_merge( $stats, $this->get_default_earnings_stats() );
1311
1312
			return array( 'stats' => $stats );
1313
		}
1314
	}
1315
1316
	/**
1317
	 * Retrieves Recent Donations
1318
	 *
1319
	 * @access public
1320
	 * @since  1.1
1321
	 * @return array
1322
	 */
1323 11
	public function get_recent_donations() {
1324 11
		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...
1325
1326 11
		$sales = array();
1327
1328 11
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1329
			return $sales;
1330
		}
1331
1332 11
		if ( isset( $wp_query->query_vars['id'] ) ) {
1333
			$query   = array();
1334
			$query[] = new Give_Payment( $wp_query->query_vars['id'] );
1335 11
		} elseif ( isset( $wp_query->query_vars['purchasekey'] ) ) {
1336
			$query   = array();
1337
			$query[] = give_get_payment_by( 'key', $wp_query->query_vars['purchasekey'] );
1338 11
		} elseif ( isset( $wp_query->query_vars['email'] ) ) {
1339
			$args  = array(
1340
				'fields'     => 'ids',
1341
				'meta_key'   => '_give_payment_user_email',
1342
				'meta_value' => $wp_query->query_vars['email'],
1343
				'number'     => $this->per_page(),
1344
				'page'       => $this->get_paged(),
1345
				'status'     => 'publish'
1346
			);
1347
			$query = give_get_payments( $args );
1348
		} else {
1349
			$args  = array(
1350 11
				'fields' => 'ids',
1351 11
				'number' => $this->per_page(),
1352 11
				'page'   => $this->get_paged(),
1353
				'status' => 'publish'
1354 11
			);
1355 11
			$query = give_get_payments( $args );
1356
		}
1357 11
		if ( $query ) {
1358 11
			$i = 0;
1359 11
			foreach ( $query as $payment ) {
1360
1361 11
				if ( is_numeric( $payment ) ) {
1362 11
					$payment      = new Give_Payment( $payment );
1363 11
					$payment_meta = $payment->get_meta();
1364 11
					$user_info    = $payment->user_info;
0 ignored issues
show
Documentation introduced by
The property $user_info is declared private in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1365 11
				} else {
1366
					continue;
1367
				}
1368
1369 11
				$payment_meta = $payment->get_meta();
1370 11
				$user_info    = $payment->user_info;
0 ignored issues
show
Documentation introduced by
The property $user_info is declared private in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1371
1372 11
				$first_name = isset( $user_info['first_name'] ) ? $user_info['first_name'] : '';
1373 11
				$last_name  = isset( $user_info['last_name'] ) ? $user_info['last_name'] : '';
1374
1375 11
				$sales['donations'][ $i ]['ID']             = $payment->number;
0 ignored issues
show
Documentation introduced by
The property $number is declared protected in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1376 11
				$sales['donations'][ $i ]['transaction_id'] = $payment->transaction_id;
0 ignored issues
show
Documentation introduced by
The property $transaction_id is declared protected in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1377 11
				$sales['donations'][ $i ]['key']            = $payment->key;
0 ignored issues
show
Documentation introduced by
The property $key is declared protected in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1378 11
				$sales['donations'][ $i ]['total']          = $payment->total;
0 ignored issues
show
Documentation introduced by
The property $total is declared protected in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1379 11
				$sales['donations'][ $i ]['gateway']        = $payment->gateway;
0 ignored issues
show
Documentation introduced by
The property $gateway is declared protected in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1380 11
				$sales['donations'][ $i ]['name']           = $first_name . ' ' . $last_name;
1381 11
				$sales['donations'][ $i ]['fname']          = $first_name;
1382 11
				$sales['donations'][ $i ]['lname']          = $last_name;
1383 11
				$sales['donations'][ $i ]['email']          = $payment->email;
0 ignored issues
show
Documentation introduced by
The property $email is declared protected in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1384 11
				$sales['donations'][ $i ]['date']           = $payment->date;
0 ignored issues
show
Documentation introduced by
The property $date is declared protected in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1385
1386 11
				$form_id  = isset( $payment_meta['form_id'] ) ? $payment_meta['form_id'] : $payment_meta;
1387 11
				$price    = isset( $payment_meta['form_id'] ) ? give_get_form_price( $payment_meta['form_id'] ) : false;
1388 11
				$price_id = isset( $payment_meta['price_id'] ) ? $payment_meta['price_id'] : null;
1389
1390 11
				$sales['donations'][ $i ]['form']['id']    = $form_id;
1391 11
				$sales['donations'][ $i ]['form']['name']  = get_the_title( $payment_meta['form_id'] );
1392 11
				$sales['donations'][ $i ]['form']['price'] = $price;
1393
1394 11
				if ( give_has_variable_prices( $form_id ) ) {
1395 11
					if ( isset( $payment_meta['price_id'] ) ) {
1396 11
						$price_name                                     = give_get_price_option_name( $form_id, $payment_meta['price_id'], $payment->ID );
1397 11
						$sales['donations'][ $i ]['form']['price_name'] = $price_name;
1398 11
						$sales['donations'][ $i ]['form']['price_id']   = $price_id;
1399 11
						$sales['donations'][ $i ]['form']['price']      = give_get_price_option_amount( $form_id, $price_id );
1400
1401 11
					}
1402 11
				}
1403
1404
				//Add custom meta to API
1405 11
				foreach ( $payment_meta as $meta_key => $meta_value ) {
1406
1407
					$exceptions = array(
1408 11
						'form_title',
1409 11
						'form_id',
1410 11
						'price_id',
1411 11
						'user_info',
1412 11
						'key',
1413 11
						'email',
1414 11
						'date',
1415 11
					);
1416
1417
					//Don't clutter up results with dupes
1418 11
					if ( in_array( $meta_key, $exceptions ) ) {
1419 11
						continue;
1420
					}
1421
1422 11
					$sales['donations'][ $i ]['payment_meta'][ $meta_key ] = $meta_value;
1423
1424 11
				}
1425
1426 11
				$i ++;
1427 11
			}
1428 11
		}
1429
1430 11
		return apply_filters( 'give_api_donations_endpoint', $sales );
1431
	}
1432
1433
	/**
1434
	 * Retrieve the output format
1435
	 *
1436
	 * Determines whether results should be displayed in XML or JSON
1437
	 *
1438
	 * @since 1.1
1439
	 *
1440
	 * @return mixed|void
1441
	 */
1442
	public function get_output_format() {
1443
		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...
1444
1445
		$format = isset( $wp_query->query_vars['format'] ) ? $wp_query->query_vars['format'] : 'json';
1446
1447
		return apply_filters( 'give_api_output_format', $format );
1448
	}
1449
1450
1451
	/**
1452
	 * Log each API request, if enabled
1453
	 *
1454
	 * @access private
1455
	 * @since  1.1
1456
	 * @global      $give_logs
1457
	 * @global      $wp_query
1458
	 *
1459
	 * @param array $data
1460
	 *
1461
	 * @return void
1462
	 */
1463
	private function log_request( $data = array() ) {
1464
		if ( ! $this->log_requests ) {
1465
			return;
1466
		}
1467
1468
		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...
1469
1470
		$query = array(
1471
			'give-api'    => $wp_query->query_vars['give-api'],
1472
			'key'         => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null,
1473
			'token'       => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null,
1474
			'query'       => isset( $wp_query->query_vars['query'] ) ? $wp_query->query_vars['query'] : null,
1475
			'type'        => isset( $wp_query->query_vars['type'] ) ? $wp_query->query_vars['type'] : null,
1476
			'form'        => isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null,
1477
			'customer'    => isset( $wp_query->query_vars['customer'] ) ? $wp_query->query_vars['customer'] : null,
1478
			'date'        => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null,
1479
			'startdate'   => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null,
1480
			'enddate'     => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null,
1481
			'id'          => isset( $wp_query->query_vars['id'] ) ? $wp_query->query_vars['id'] : null,
1482
			'purchasekey' => isset( $wp_query->query_vars['purchasekey'] ) ? $wp_query->query_vars['purchasekey'] : null,
1483
			'email'       => isset( $wp_query->query_vars['email'] ) ? $wp_query->query_vars['email'] : null,
1484
		);
1485
1486
		$log_data = array(
1487
			'log_type'     => 'api_request',
1488
			'post_excerpt' => http_build_query( $query ),
1489
			'post_content' => ! empty( $data['error'] ) ? $data['error'] : '',
1490
		);
1491
1492
		$log_meta = array(
1493
			'request_ip' => give_get_ip(),
1494
			'user'       => $this->user_id,
1495
			'key'        => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null,
1496
			'token'      => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null,
1497
			'time'       => $data['request_speed'],
1498
			'version'    => $this->get_queried_version()
1499
		);
1500
1501
		$give_logs->insert_log( $log_data, $log_meta );
1502
	}
1503
1504
1505
	/**
1506
	 * Retrieve the output data
1507
	 *
1508
	 * @access public
1509
	 * @since  1.1
1510
	 * @return array
1511
	 */
1512
	public function get_output() {
1513
		return $this->data;
1514
	}
1515
1516
	/**
1517
	 * Output Query in either JSON/XML. The query data is outputted as JSON
1518
	 * by default
1519
	 *
1520
	 * @since 1.1
1521
	 * @global    $wp_query
1522
	 *
1523
	 * @param int $status_code
1524
	 */
1525
	public function output( $status_code = 200 ) {
1526
		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...
1527
1528
		$format = $this->get_output_format();
1529
1530
		status_header( $status_code );
1531
1532
		do_action( 'give_api_output_before', $this->data, $this, $format );
1533
1534
		switch ( $format ) :
1535
1536
			case 'xml' :
1537
1538
				require_once GIVE_PLUGIN_DIR . 'includes/libraries/array2xml.php';
1539
				$xml = Array2XML::createXML( 'give', $this->data );
1540
				echo $xml->saveXML();
1541
1542
				break;
1543
1544
			case 'json' :
1545
1546
				header( 'Content-Type: application/json' );
1547
				if ( ! empty( $this->pretty_print ) ) {
1548
					echo json_encode( $this->data, $this->pretty_print );
1549
				} else {
1550
					echo json_encode( $this->data );
1551
				}
1552
1553
				break;
1554
1555
1556
			default :
1557
1558
				// Allow other formats to be added via extensions
1559
				do_action( 'give_api_output_' . $format, $this->data, $this );
1560
1561
				break;
1562
1563
		endswitch;
1564
1565
		do_action( 'give_api_output_after', $this->data, $this, $format );
1566
1567
		give_die();
1568
	}
1569
1570
	/**
1571
	 * Modify User Profile
1572
	 *
1573
	 * Modifies the output of profile.php to add key generation/revocation
1574
	 *
1575
	 * @access public
1576
	 * @since  1.1
1577
	 *
1578
	 * @param object $user Current user info
1579
	 *
1580
	 * @return void
1581
	 */
1582
	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...
1583
1584
		if ( ( give_get_option( 'api_allow_user_keys', false ) || current_user_can( 'manage_give_settings' ) ) && current_user_can( 'edit_user', $user->ID ) ) {
1585
			$user = get_userdata( $user->ID );
1586
			?>
1587
			<table class="form-table">
1588
				<tbody>
1589
				<tr>
1590
					<th>
1591
						<?php esc_attr_e( 'Give API Keys', 'give' ); ?>
1592
					</th>
1593
					<td>
1594
						<?php
1595
						$public_key = $this->get_user_public_key( $user->ID );
1596
						$secret_key = $this->get_user_secret_key( $user->ID );
1597
						?>
1598
						<?php if ( empty( $user->give_user_public_key ) ) { ?>
1599
							<input name="give_set_api_key" type="checkbox" id="give_set_api_key" value="0"/>
1600
							<span class="description"><?php esc_attr_e( 'Generate API Key', 'give' ); ?></span>
1601
						<?php } else { ?>
1602
							<strong style="display:inline-block; width: 125px;"><?php _e( 'Public key:', 'give' ); ?>&nbsp;</strong>
1603
							<input type="text" disabled="disabled" class="regular-text" id="publickey" value="<?php echo esc_attr( $public_key ); ?>"/>
1604
							<br/>
1605
							<strong style="display:inline-block; width: 125px;"><?php esc_attr_e( 'Secret key:', 'give' ); ?>&nbsp;</strong>
1606
							<input type="text" disabled="disabled" class="regular-text" id="privatekey" value="<?php echo esc_attr( $secret_key ); ?>"/>
1607
							<br/>
1608
							<strong style="display:inline-block; width: 125px;"><?php esc_attr_e( 'Token:', 'give' ); ?>&nbsp;</strong>
1609
							<input type="text" disabled="disabled" class="regular-text" id="token" value="<?php echo esc_attr( $this->get_token( $user->ID ) ); ?>"/>
1610
							<br/>
1611
							<input name="give_set_api_key" type="checkbox" id="give_set_api_key" value="0"/>
1612
							<span class="description"><label for="give_set_api_key"><?php esc_attr_e( 'Revoke API Keys', 'give' ); ?></label></span>
1613
						<?php } ?>
1614
					</td>
1615
				</tr>
1616
				</tbody>
1617
			</table>
1618
		<?php }
1619
	}
1620
1621
	/**
1622
	 * Process an API key generation/revocation
1623
	 *
1624
	 * @access public
1625
	 * @since  1.1
1626
	 *
1627
	 * @param array $args
1628
	 *
1629
	 * @return void
1630
	 */
1631
	public function process_api_key( $args ) {
1632
1633
		if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'give-api-nonce' ) ) {
1634
1635
			wp_die( esc_attr__( 'Nonce verification failed', 'give' ), __( 'Error', 'give' ), array( 'response' => 403 ) );
1636
1637
		}
1638
1639
		if ( empty( $args['user_id'] ) ) {
1640
			wp_die( esc_attr__( 'User ID Required', 'give' ), esc_attr__( 'Error', 'give' ), array( 'response' => 401 ) );
1641
		}
1642
1643
		if ( is_numeric( $args['user_id'] ) ) {
1644
			$user_id = isset( $args['user_id'] ) ? absint( $args['user_id'] ) : get_current_user_id();
1645
		} else {
1646
			$userdata = get_user_by( 'login', $args['user_id'] );
1647
			$user_id  = $userdata->ID;
1648
		}
1649
		$process = isset( $args['give_api_process'] ) ? strtolower( $args['give_api_process'] ) : false;
1650
1651
		if ( $user_id == get_current_user_id() && ! give_get_option( 'allow_user_api_keys' ) && ! current_user_can( 'manage_give_settings' ) ) {
1652
			wp_die( sprintf( __( 'You do not have permission to %s API keys for this user', 'give' ), $process ), __( 'Error', 'give' ), array( 'response' => 403 ) );
1653
		} elseif ( ! current_user_can( 'manage_give_settings' ) ) {
1654
			wp_die( sprintf( __( 'You do not have permission to %s API keys for this user', 'give' ), $process ), __( 'Error', 'give' ), array( 'response' => 403 ) );
1655
		}
1656
1657
		switch ( $process ) {
1658
			case 'generate':
1659
				if ( $this->generate_api_key( $user_id ) ) {
1660
					delete_transient( 'give-total-api-keys' );
1661
					wp_redirect( add_query_arg( 'give-message', 'api-key-generated', 'edit.php?post_type=give_forms&page=give-settings&tab=api' ) );
1662
					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...
1663
				} else {
1664
					wp_redirect( add_query_arg( 'give-message', 'api-key-failed', 'edit.php?post_type=give_forms&page=give-settings&tab=api' ) );
1665
					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...
1666
				}
1667
				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...
1668
			case 'regenerate':
1669
				$this->generate_api_key( $user_id, true );
1670
				delete_transient( 'give-total-api-keys' );
1671
				wp_redirect( add_query_arg( 'give-message', 'api-key-regenerated', 'edit.php?post_type=give_forms&page=give-settings&tab=api' ) );
1672
				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...
1673
				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...
1674
			case 'revoke':
1675
				$this->revoke_api_key( $user_id );
1676
				delete_transient( 'give-total-api-keys' );
1677
				wp_redirect( add_query_arg( 'give-message', 'api-key-revoked', 'edit.php?post_type=give_forms&page=give-settings&tab=api' ) );
1678
				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...
1679
				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...
1680
			default;
1681
				break;
1682
		}
1683
	}
1684
1685
	/**
1686
	 * Generate new API keys for a user
1687
	 *
1688
	 * @access public
1689
	 * @since  1.1
1690
	 *
1691
	 * @param int $user_id User ID the key is being generated for
1692
	 * @param boolean $regenerate Regenerate the key for the user
1693
	 *
1694
	 * @return boolean True if (re)generated succesfully, false otherwise.
1695
	 */
1696
	public function generate_api_key( $user_id = 0, $regenerate = false ) {
1697
1698
		if ( empty( $user_id ) ) {
1699
			return false;
1700
		}
1701
1702
		$user = get_userdata( $user_id );
1703
1704
		if ( ! $user ) {
1705
			return false;
1706
		}
1707
1708
		$public_key = $this->get_user_public_key( $user_id );
1709
		$secret_key = $this->get_user_secret_key( $user_id );
1710
1711
		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...
1712
			$new_public_key = $this->generate_public_key( $user->user_email );
1713
			$new_secret_key = $this->generate_private_key( $user->ID );
1714
		} else {
1715
			return false;
1716
		}
1717
1718
		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...
1719
			$this->revoke_api_key( $user->ID );
1720
		}
1721
1722
		update_user_meta( $user_id, $new_public_key, 'give_user_public_key' );
1723
		update_user_meta( $user_id, $new_secret_key, 'give_user_secret_key' );
1724
1725
		return true;
1726
	}
1727
1728
	/**
1729
	 * Revoke a users API keys
1730
	 *
1731
	 * @access public
1732
	 * @since  1.1
1733
	 *
1734
	 * @param int $user_id User ID of user to revoke key for
1735
	 *
1736
	 * @return string
1737
	 */
1738
	public function revoke_api_key( $user_id = 0 ) {
1739
1740
		if ( empty( $user_id ) ) {
1741
			return false;
1742
		}
1743
1744
		$user = get_userdata( $user_id );
1745
1746
		if ( ! $user ) {
1747
			return false;
1748
		}
1749
1750
		$public_key = $this->get_user_public_key( $user_id );
1751
		$secret_key = $this->get_user_secret_key( $user_id );
1752
		if ( ! empty( $public_key ) ) {
1753
			delete_transient( md5( 'give_api_user_' . $public_key ) );
1754
			delete_transient( md5( 'give_api_user_public_key' . $user_id ) );
1755
			delete_transient( md5( 'give_api_user_secret_key' . $user_id ) );
1756
			delete_user_meta( $user_id, $public_key );
1757
			delete_user_meta( $user_id, $secret_key );
1758
		} else {
1759
			return false;
1760
		}
1761
1762
		return true;
1763
	}
1764
1765 2
	public function get_version() {
1766 2
		return self::VERSION;
1767
	}
1768
1769
1770
	/**
1771
	 * Generate and Save API key
1772
	 *
1773
	 * Generates the key requested by user_key_field and stores it in the database
1774
	 *
1775
	 * @access public
1776
	 * @since  1.1
1777
	 *
1778
	 * @param int $user_id
1779
	 *
1780
	 * @return void
1781
	 */
1782 2
	public function update_key( $user_id ) {
1783 2
		if ( current_user_can( 'edit_user', $user_id ) && isset( $_POST['give_set_api_key'] ) ) {
1784
1785 2
			$user = get_userdata( $user_id );
1786
1787 2
			$public_key = $this->get_user_public_key( $user_id );
1788 2
			$secret_key = $this->get_user_secret_key( $user_id );
1789
1790 2
			if ( empty( $public_key ) ) {
1791 2
				$new_public_key = $this->generate_public_key( $user->user_email );
1792 2
				$new_secret_key = $this->generate_private_key( $user->ID );
1793
1794 2
				update_user_meta( $user_id, $new_public_key, 'give_user_public_key' );
1795 2
				update_user_meta( $user_id, $new_secret_key, 'give_user_secret_key' );
1796 2
			} else {
1797
				$this->revoke_api_key( $user_id );
1798
			}
1799 2
		}
1800 2
	}
1801
1802
	/**
1803
	 * Generate the public key for a user
1804
	 *
1805
	 * @access private
1806
	 * @since  1.1
1807
	 *
1808
	 * @param string $user_email
1809
	 *
1810
	 * @return string
1811
	 */
1812 2
	private function generate_public_key( $user_email = '' ) {
1813 2
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
1814 2
		$public   = hash( 'md5', $user_email . $auth_key . date( 'U' ) );
1815
1816 2
		return $public;
1817
	}
1818
1819
	/**
1820
	 * Generate the secret key for a user
1821
	 *
1822
	 * @access private
1823
	 * @since  1.1
1824
	 *
1825
	 * @param int $user_id
1826
	 *
1827
	 * @return string
1828
	 */
1829 2
	private function generate_private_key( $user_id = 0 ) {
1830 2
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
1831 2
		$secret   = hash( 'md5', $user_id . $auth_key . date( 'U' ) );
1832
1833 2
		return $secret;
1834
	}
1835
1836
	/**
1837
	 * Retrieve the user's token
1838
	 *
1839
	 * @access private
1840
	 * @since  1.1
1841
	 *
1842
	 * @param int $user_id
1843
	 *
1844
	 * @return string
1845
	 */
1846
	public function get_token( $user_id = 0 ) {
1847
		return hash( 'md5', $this->get_user_secret_key( $user_id ) . $this->get_user_public_key( $user_id ) );
1848
	}
1849
1850
	/**
1851
	 * Generate the default sales stats returned by the 'stats' endpoint
1852
	 *
1853
	 * @access private
1854
	 * @since  1.1
1855
	 * @return array default sales statistics
1856
	 */
1857
	private function get_default_sales_stats() {
1858
1859
		// Default sales return
1860
		$sales                               = array();
1861
		$sales['donations']['today']         = $this->stats->get_sales( 0, 'today' );
1862
		$sales['donations']['current_month'] = $this->stats->get_sales( 0, 'this_month' );
1863
		$sales['donations']['last_month']    = $this->stats->get_sales( 0, 'last_month' );
1864
		$sales['donations']['totals']        = give_get_total_sales();
1865
1866
		return $sales;
1867
	}
1868
1869
	/**
1870
	 * Generate the default earnings stats returned by the 'stats' endpoint
1871
	 *
1872
	 * @access private
1873
	 * @since  1.1
1874
	 * @return array default earnings statistics
1875
	 */
1876
	private function get_default_earnings_stats() {
1877
1878
		// Default earnings return
1879
		$earnings                              = array();
1880
		$earnings['earnings']['today']         = $this->stats->get_earnings( 0, 'today' );
1881
		$earnings['earnings']['current_month'] = $this->stats->get_earnings( 0, 'this_month' );
1882
		$earnings['earnings']['last_month']    = $this->stats->get_earnings( 0, 'last_month' );
1883
		$earnings['earnings']['totals']        = give_get_total_earnings();
1884
1885
		return $earnings;
1886
	}
1887
1888
	/**
1889
	 * API Key Backwards Compatibility
1890
	 *
1891
	 * @description A Backwards Compatibility call for the change of meta_key/value for users API Keys
1892
	 *
1893
	 * @since       1.3.6
1894
	 *
1895
	 * @param  string $check Whether to check the cache or not
1896
	 * @param  int $object_id The User ID being passed
1897
	 * @param  string $meta_key The user meta key
1898
	 * @param  bool $single If it should return a single value or array
1899
	 *
1900
	 * @return string            The API key/secret for the user supplied
1901
	 */
1902 56
	public function api_key_backwards_compat( $check, $object_id, $meta_key, $single ) {
1903
1904 56
		if ( $meta_key !== 'give_user_public_key' && $meta_key !== 'give_user_secret_key' ) {
1905 56
			return $check;
1906
		}
1907
1908
		$return = $check;
1909
1910
		switch ( $meta_key ) {
1911
			case 'give_user_public_key':
1912
				$return = Give()->api->get_user_public_key( $object_id );
1913
				break;
1914
			case 'give_user_secret_key':
1915
				$return = Give()->api->get_user_secret_key( $object_id );
1916
				break;
1917
		}
1918
1919
		if ( ! $single ) {
1920
			$return = array( $return );
1921
		}
1922
1923
		return $return;
1924
1925
	}
1926
1927
}
1928