Test Failed
Pull Request — master (#2054)
by Devin
05:04
created

Give_API   D

Complexity

Total Complexity 269

Size/Duplication

Total Lines 2059
Duplicated Lines 18.8 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 29.77%

Importance

Changes 0
Metric Value
dl 387
loc 2059
rs 4.4102
c 0
b 0
f 0
ccs 276
cts 927
cp 0.2977
wmc 269
lcom 1
cbo 6

41 Methods

Rating   Name   Duplication   Size   Complexity  
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 __construct() 0 32 3
A add_endpoint() 0 3 1
B set_queried_version() 0 29 3
D validate_request() 0 41 10
B get_user() 0 26 5
A get_user_public_key() 17 17 3
A get_user_secret_key() 17 17 3
A missing_auth() 7 7 1
A invalid_auth() 7 7 1
A invalid_key() 7 7 1
A invalid_version() 7 7 1
D process_query() 0 97 19
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() 115 169 22
C get_donors() 4 79 10
B get_forms() 0 38 5
D get_form_data() 0 66 15
D get_stats() 161 276 54
D get_recent_donations() 0 167 22
A get_output_format() 0 7 2
F log_request() 0 48 17
A get_output() 0 3 1
B output() 0 67 4
B user_key_field() 0 42 5
D process_api_key() 23 61 14
C generate_api_key() 0 56 13
B revoke_api_key() 0 26 4
A get_version() 0 3 1
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() 11 11 1
A get_default_earnings_stats() 11 11 1
B api_key_backwards_compat() 0 24 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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
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     https://opensource.org/licenses/gpl-license 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 public
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 public
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
	 * Whether or not to override api key validation.
89
	 *
90
	 * @var bool
91
	 * @access public
92
	 * @since  1.1
93
	 */
94
	public $override = true;
95
96
	/**
97
	 * Version of the API queried
98
	 *
99
	 * @var string
100
	 * @access public
101
	 * @since  1.1
102
	 */
103
	private $queried_version;
104
105
	/**
106
	 * All versions of the API
107
	 *
108
	 * @var string
109
	 * @access protected
110
	 * @since  1.1
111
	 */
112
	protected $versions = array();
113
114
	/**
115
	 * Queried endpoint
116
	 *
117
	 * @var string
118
	 * @access private
119
	 * @since  1.1
120
	 */
121
	private $endpoint;
122
123
	/**
124
	 * Endpoints routes
125
	 *
126
	 * @var object
127
	 * @access private
128
	 * @since  1.1
129
	 */
130
	private $routes;
131
132
	/**
133
	 * Setup the Give API
134
	 *
135
	 * @since  1.1
136 13
	 * @access public
137
	 */
138 13
	public function __construct() {
139 13
140
		$this->versions = array(
0 ignored issues
show
Documentation Bug introduced by
It seems like array('v1' => 'GIVE_API_V1') of type array<string,string,{"v1":"string"}> is incompatible with the declared type string of property $versions.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
141
			'v1' => 'GIVE_API_V1',
142 13
		);
143 13
144 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...
145
			require_once GIVE_PLUGIN_DIR . 'includes/api/class-give-api-' . $version . '.php';
146 13
		}
147 13
148 13
		add_action( 'init', array( $this, 'add_endpoint' ) );
149 13
		add_action( 'wp', array( $this, 'process_query' ), - 1 );
150 13
		add_filter( 'query_vars', array( $this, 'query_vars' ) );
151 13
		add_action( 'show_user_profile', array( $this, 'user_key_field' ) );
152 13
		add_action( 'edit_user_profile', array( $this, 'user_key_field' ) );
153 13
		add_action( 'personal_options_update', array( $this, 'generate_api_key' ) );
154
		add_action( 'edit_user_profile_update', array( $this, 'generate_api_key' ) );
155
		add_action( 'give_process_api_key', array( $this, 'process_api_key' ) );
156 13
157
		// Setup a backwards compatibility check for user API Keys
158
		add_filter( 'get_user_metadata', array( $this, 'api_key_backwards_compat' ), 10, 4 );
159 13
160
		// Determine if JSON_PRETTY_PRINT is available
161
		$this->pretty_print = defined( 'JSON_PRETTY_PRINT' ) ? JSON_PRETTY_PRINT : null;
0 ignored issues
show
Documentation Bug introduced by
It seems like defined('JSON_PRETTY_PRI...SON_PRETTY_PRINT : null can also be of type integer. However, the property $pretty_print is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
162 13
163
		// Allow API request logging to be turned off
164
		$this->log_requests = apply_filters( 'give_api_log_requests', $this->log_requests );
165 13
166
		// Setup Give_Payment_Stats instance
167 13
		$this->stats = new Give_Payment_Stats();
168
169
	}
170
171
	/**
172
	 * Registers a new rewrite endpoint for accessing the API
173
	 *
174
	 * @access public
175
	 *
176
	 * @since  1.1
177
	 */
178 11
	public function add_endpoint() {
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 public
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;
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
		} else {
285
286
			$this->queried_version = $this->get_default_version();
287
288
		}
289
290
	}
291
292
	/**
293
	 * Validate the API request
294
	 *
295
	 * Checks for the user's public key and token against the secret key.
296
	 *
297
	 * @access private
298
	 * @global object $wp_query WordPress Query
299
	 * @uses   Give_API::get_user()
300
	 * @uses   Give_API::invalid_key()
301
	 * @uses   Give_API::invalid_auth()
302
	 * @since  1.1
303
	 * @return bool
304
	 */
305
	private function validate_request() {
306
		global $wp_query;
307
308
		$this->override = false;
309
310
		// Make sure we have both user and api key
311
		if ( ! empty( $wp_query->query_vars['give-api'] ) && ( $wp_query->query_vars['give-api'] !== 'forms' || ! empty( $wp_query->query_vars['token'] ) ) ) {
0 ignored issues
show
introduced by
Found "!== '". Use Yoda Condition checks, you must
Loading history...
312
313
			if ( empty( $wp_query->query_vars['token'] ) || empty( $wp_query->query_vars['key'] ) ) {
314
				$this->missing_auth();
315
316
				return false;
317
			}
318
319
			// Retrieve the user by public API key and ensure they exist
320
			if ( ! ( $user = $this->get_user( $wp_query->query_vars['key'] ) ) ) {
321
322
				$this->invalid_key();
323
324
				return false;
325
326
			} else {
327
328
				$token  = urldecode( $wp_query->query_vars['token'] );
329
				$secret = $this->get_user_secret_key( $user );
0 ignored issues
show
Documentation introduced by
$user is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
330
				$public = urldecode( $wp_query->query_vars['key'] );
331
332
				if ( hash_equals( md5( $secret . $public ), $token ) ) {
333
					$this->is_valid_request = true;
334
				} else {
335
					$this->invalid_auth();
336
337
					return false;
338
				}
0 ignored issues
show
introduced by
Blank line found after control structure
Loading history...
339
340
			}
341
		} elseif ( ! empty( $wp_query->query_vars['give-api'] ) && $wp_query->query_vars['give-api'] === 'forms' ) {
0 ignored issues
show
introduced by
Found "=== '". Use Yoda Condition checks, you must
Loading history...
342
			$this->is_valid_request = true;
343
			$wp_query->set( 'key', 'public' );
344
		}
345
	}
346
347
	/**
348
	 * Retrieve the user ID based on the public key provided
349
	 *
350
	 * @access public
351
	 * @since  1.1
352
	 * @global WPDB  $wpdb  Used to query the database using the WordPress
353 1
	 *                      Database API
354 1
	 *
355
	 * @param string $key   Public Key
356 1
	 *
357
	 * @return bool if user ID is found, false otherwise
358
	 */
359
	public function get_user( $key = '' ) {
360 1
		global $wpdb, $wp_query;
361
362
		if ( empty( $key ) ) {
363
			$key = urldecode( $wp_query->query_vars['key'] );
364 1
		}
365
366 1
		if ( empty( $key ) ) {
367 1
			return false;
368 1
		}
369 1
370
		$user = Give_Cache::get( md5( 'give_api_user_' . $key ), true );
371 1
372 1
		if ( false === $user ) {
373
			$user = $wpdb->get_var( $wpdb->prepare( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = %s LIMIT 1", $key ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
introduced by
Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.
Loading history...
374 1
			Give_Cache::set( md5( 'give_api_user_' . $key ), $user, DAY_IN_SECONDS, true );
375
		}
376
377
		if ( $user != null ) {
378
			$this->user_id = $user;
379
380 11
			return $user;
381 2
		}
382
383 2
		return false;
384
	}
385
386
	/**
387 2
	 * Get user public key.
388 2
	 *
389
	 * @param int $user_id
390 2
	 *
391 2
	 * @return mixed|null|string
392 2
	 */
393 2 View Code Duplication
	public function get_user_public_key( $user_id = 0 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
394
		global $wpdb;
395 11
396
		if ( empty( $user_id ) ) {
397
			return '';
398 11
		}
399 2
400
		$cache_key       = md5( 'give_api_user_public_key' . $user_id );
401 2
		$user_public_key = Give_Cache::get( $cache_key, true );
402
403
		if ( empty( $user_public_key ) ) {
404
			$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 ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
introduced by
Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.
Loading history...
405 2
			Give_Cache::set( $cache_key, $user_public_key, HOUR_IN_SECONDS, true );
406 2
		}
407
408 2
		return $user_public_key;
409 2
	}
410 2
411 11
	/**
412
	 * Get user secret key.
413 2
	 *
414
	 * @param int $user_id
415
	 *
416
	 * @return mixed|null|string
417
	 */
418 View Code Duplication
	public function get_user_secret_key( $user_id = 0 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
419
		global $wpdb;
420
421
		if ( empty( $user_id ) ) {
422
			return '';
423
		}
424
425
		$cache_key       = md5( 'give_api_user_secret_key' . $user_id );
426
		$user_secret_key = Give_Cache::get( $cache_key, true );
427
428
		if ( empty( $user_secret_key ) ) {
429
			$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 ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
introduced by
Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.
Loading history...
430
			Give_Cache::set( $cache_key, $user_secret_key, HOUR_IN_SECONDS, true );
431
		}
432
433
		return $user_secret_key;
434
	}
435
436
	/**
437
	 * Displays a missing authentication error if all the parameters are not met.
438
	 * provided
439
	 *
440
	 * @access private
441
	 * @uses   Give_API::output()
442
	 * @since  1.1
443
	 */
444 View Code Duplication
	private function missing_auth() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
445
		$error          = array();
446
		$error['error'] = __( 'You must specify both a token and API key.', 'give' );
447
448
		$this->data = $error;
449
		$this->output( 401 );
450
	}
451
452
	/**
453
	 * Displays an authentication failed error if the user failed to provide valid
454
	 * credentials
455
	 *
456
	 * @access private
457
	 * @since  1.1
458
	 * @uses   Give_API::output()
459
	 * @return void
460
	 */
461 View Code Duplication
	private function invalid_auth() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
462
		$error          = array();
463
		$error['error'] = __( 'Your request could not be authenticated.', 'give' );
464
465
		$this->data = $error;
466
		$this->output( 403 );
467
	}
468
469
	/**
470
	 * Displays an invalid API key error if the API key provided couldn't be
471
	 * validated
472
	 *
473
	 * @access private
474
	 * @since  1.1
475
	 * @uses   Give_API::output()
476
	 * @return void
477
	 */
478 View Code Duplication
	private function invalid_key() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
479
		$error          = array();
480
		$error['error'] = __( 'Invalid API key.', 'give' );
481
482
		$this->data = $error;
483
		$this->output( 403 );
484
	}
485
486
	/**
487
	 * Displays an invalid version error if the version number passed isn't valid
488
	 *
489
	 * @access private
490 3
	 * @since  1.1
491
	 * @uses   Give_API::output()
492 3
	 * @return void
493
	 */
494 View Code Duplication
	private function invalid_version() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
495 3
		$error          = array();
496
		$error['error'] = __( 'Invalid API version.', 'give' );
497
498 3
		$this->data = $error;
499 3
		$this->output( 404 );
500
	}
501
502
	/**
503
	 * Listens for the API and then processes the API requests
504
	 *
505
	 * @access public
506
	 * @global $wp_query
507
	 * @since  1.1
508
	 * @return void
509
	 */
510
	public function process_query() {
511
512
		global $wp_query;
513
514
		// Start logging how long the request takes for logging
515
		$before = microtime( true );
516
517
		// Check for give-api var. Get out if not present
518
		if ( empty( $wp_query->query_vars['give-api'] ) ) {
519
			return;
520
		}
521
522
		// Determine which version was queried
523
		$this->set_queried_version();
524
525
		// Determine the kind of query
526
		$this->set_query_mode();
527
528
		// Check for a valid user and set errors if necessary
529
		$this->validate_request();
530
531
		// Only proceed if no errors have been noted
532
		if ( ! $this->is_valid_request ) {
533
			return;
534
		}
535
536
		if ( ! defined( 'GIVE_DOING_API' ) ) {
537
			define( 'GIVE_DOING_API', true );
538
		}
539
540
		$data         = array();
541
		$this->routes = new $this->versions[$this->get_queried_version()];
0 ignored issues
show
introduced by
Array keys should be surrounded by spaces unless they contain a string or an integer.
Loading history...
542
		$this->routes->validate_request();
543
544
		switch ( $this->endpoint ) :
545
546
			case 'stats' :
547
548
				$data = $this->routes->get_stats( array(
549
					'type'      => isset( $wp_query->query_vars['type'] ) ? $wp_query->query_vars['type'] : null,
550
					'form'      => isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null,
551
					'date'      => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null,
552
					'startdate' => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null,
553
					'enddate'   => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null,
554
				) );
555
556
				break;
557
558
			case 'forms' :
559
560
				$form = isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null;
561
562
				$data = $this->routes->get_forms( $form );
563
564
				break;
565
566
			case 'donors' :
567
568
				$donor = isset( $wp_query->query_vars['donor'] ) ? $wp_query->query_vars['donor'] : null;
569
570
				$data = $this->routes->get_donors( $donor );
571
572
				break;
573
574
			case 'donations' :
575
576
				/**
577
				 *  Call to get recent donations
578
				 *
579
				 * @params text date | today, yesterday or range
580
				 * @params date startdate | required when date = range and format to be YYYYMMDD (i.e. 20170524)
581
				 * @params date enddate | required when date = range and format to be YYYYMMDD (i.e. 20170524)
582
				 */
583
				$data = $this->routes->get_recent_donations( array(
584
					'id'        => isset( $wp_query->query_vars['id'] ) ? $wp_query->query_vars['id'] : null,
585
					'date'      => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null,
586
					'startdate' => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null,
587
					'enddate'   => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null,
588
				) );
589
590
				break;
591
592
		endswitch;
593
594
		// Allow extensions to setup their own return data
595
		$this->data = apply_filters( 'give_api_output_data', $data, $this->endpoint, $this );
596
597
		$after                       = microtime( true );
598
		$request_time                = ( $after - $before );
599
		$this->data['request_speed'] = $request_time;
600
601
		// Log this API request, if enabled. We log it here because we have access to errors.
602
		$this->log_request( $this->data );
603
604
		// Send out data to the output function
605
		$this->output();
606
	}
607
608
	/**
609
	 * Returns the API endpoint requested
610
	 *
611
	 * @access public
612
	 * @since  1.1
613
	 * @return string $query Query mode
614
	 */
615
	public function get_query_mode() {
616
617
		return $this->endpoint;
618
	}
619
620
	/**
621
	 * Determines the kind of query requested and also ensure it is a valid query
622
	 *
623
	 * @access public
624
	 * @since  1.1
625
	 * @global $wp_query
626
	 */
627
	public function set_query_mode() {
628
629
		global $wp_query;
630
631
		// Whitelist our query options
632 11
		$accepted = apply_filters( 'give_api_valid_query_modes', array(
633 11
			'stats',
634
			'forms',
635 11
			'donors',
636
			'donations',
637
		) );
638
639
		$query = isset( $wp_query->query_vars['give-api'] ) ? $wp_query->query_vars['give-api'] : null;
640
		$query = str_replace( $this->queried_version . '/', '', $query );
641
642
		$error = array();
643
644
		// Make sure our query is valid
645
		if ( ! in_array( $query, $accepted ) ) {
646
			$error['error'] = __( 'Invalid query.', 'give' );
647 11
648 11
			$this->data = $error;
649
			// 400 is Bad Request
650 11
			$this->output( 400 );
651
		}
652 11
653
		$this->endpoint = $query;
654
	}
655
656 11
	/**
657
	 * Get page number
658
	 *
659
	 * @access public
660
	 * @since  1.1
661
	 * @global $wp_query
662
	 * @return int $wp_query->query_vars['page'] if page number returned (default: 1)
663
	 */
664
	public function get_paged() {
665
		global $wp_query;
666
667
		return isset( $wp_query->query_vars['page'] ) ? $wp_query->query_vars['page'] : 1;
668
	}
669
670
671
	/**
672
	 * Number of results to display per page
673
	 *
674
	 * @access public
675
	 * @since  1.1
676
	 * @global $wp_query
677
	 * @return int $per_page Results to display per page (default: 10)
678
	 */
679
	public function per_page() {
680
		global $wp_query;
681
682
		$per_page = isset( $wp_query->query_vars['number'] ) ? $wp_query->query_vars['number'] : 10;
683
684
		if ( $per_page < 0 && $this->get_query_mode() == 'donors' ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
685
			$per_page = 99999999;
686
		} // End if().
687
688
		return apply_filters( 'give_api_results_per_page', $per_page );
689
	}
690
691
	/**
692
	 * Sets up the dates used to retrieve earnings/donations
693
	 *
694
	 * @access public
695
	 * @since  1.2
696
	 *
697
	 * @param array $args Arguments to override defaults
698
	 *
699
	 * @return array $dates
700
	 */
701
	public function get_dates( $args = array() ) {
702
		$dates = array();
703
704
		$defaults = array(
705
			'type'      => '',
706
			'form'      => null,
707
			'date'      => null,
708
			'startdate' => null,
709
			'enddate'   => null,
710
		);
711
712
		$args = wp_parse_args( $args, $defaults );
713
714
		$current_time = current_time( 'timestamp' );
715
716
		if ( 'range' === $args['date'] ) {
717
			$startdate          = strtotime( $args['startdate'] );
718
			$enddate            = strtotime( $args['enddate'] );
719
			$dates['day_start'] = date( 'd', $startdate );
720
			$dates['day_end']   = date( 'd', $enddate );
721
			$dates['m_start']   = date( 'n', $startdate );
722
			$dates['m_end']     = date( 'n', $enddate );
723
			$dates['year']      = date( 'Y', $startdate );
724
			$dates['year_end']  = date( 'Y', $enddate );
725
		} else {
726
			// Modify dates based on predefined ranges
727
			switch ( $args['date'] ) :
728
729 View Code Duplication
				case 'this_month' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
730
					$dates['day']     = null;
731
					$dates['m_start'] = date( 'n', $current_time );
732
					$dates['m_end']   = date( 'n', $current_time );
733
					$dates['year']    = date( 'Y', $current_time );
734
					break;
735
736
				case 'last_month' :
737
					$dates['day']     = null;
738
					$dates['m_start'] = date( 'n', $current_time ) == 1 ? 12 : date( 'n', $current_time ) - 1;
739
					$dates['m_end']   = $dates['m_start'];
740
					$dates['year']    = date( 'n', $current_time ) == 1 ? date( 'Y', $current_time ) - 1 : date( 'Y', $current_time );
741
					break;
742
743 View Code Duplication
				case 'today' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
744
					$dates['day']     = date( 'd', $current_time );
745
					$dates['m_start'] = date( 'n', $current_time );
746
					$dates['m_end']   = date( 'n', $current_time );
747
					$dates['year']    = date( 'Y', $current_time );
748
					break;
749
750 View Code Duplication
				case 'yesterday' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
751
752
					$year  = date( 'Y', $current_time );
753
					$month = date( 'n', $current_time );
754
					$day   = date( 'd', $current_time );
755
756
					if ( $month == 1 && $day == 1 ) {
0 ignored issues
show
introduced by
Found "== 1". Use Yoda Condition checks, you must
Loading history...
757
758
						$year  -= 1;
759
						$month = 12;
760
						$day   = cal_days_in_month( CAL_GREGORIAN, $month, $year );
761
762
					} elseif ( $month > 1 && $day == 1 ) {
0 ignored issues
show
introduced by
Found "== 1". Use Yoda Condition checks, you must
Loading history...
763
764
						$month -= 1;
765
						$day   = cal_days_in_month( CAL_GREGORIAN, $month, $year );
766
767
					} else {
768
769
						$day -= 1;
770
771
					}
772
773
					$dates['day']     = $day;
774
					$dates['m_start'] = $month;
775
					$dates['m_end']   = $month;
776
					$dates['year']    = $year;
777
778
					break;
779
780 View Code Duplication
				case 'this_quarter' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
781
					$month_now = date( 'n', $current_time );
782
783
					$dates['day'] = null;
784
785
					if ( $month_now <= 3 ) {
786
787
						$dates['m_start'] = 1;
788
						$dates['m_end']   = 3;
789
						$dates['year']    = date( 'Y', $current_time );
790
791
					} elseif ( $month_now <= 6 ) {
792
793
						$dates['m_start'] = 4;
794
						$dates['m_end']   = 6;
795
						$dates['year']    = date( 'Y', $current_time );
796
797
					} elseif ( $month_now <= 9 ) {
798
799
						$dates['m_start'] = 7;
800
						$dates['m_end']   = 9;
801
						$dates['year']    = date( 'Y', $current_time );
802
803
					} else {
804
805
						$dates['m_start'] = 10;
806
						$dates['m_end']   = 12;
807
						$dates['year']    = date( 'Y', $current_time );
808
809
					}
810
					break;
811
812 View Code Duplication
				case 'last_quarter' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
813
					$month_now = date( 'n', $current_time );
814
815
					$dates['day'] = null;
816
817
					if ( $month_now <= 3 ) {
818
819
						$dates['m_start'] = 10;
820
						$dates['m_end']   = 12;
821
						$dates['year']    = date( 'Y', $current_time ) - 1; // Previous year
822
823
					} elseif ( $month_now <= 6 ) {
824
825
						$dates['m_start'] = 1;
826
						$dates['m_end']   = 3;
827
						$dates['year']    = date( 'Y', $current_time );
828
829
					} elseif ( $month_now <= 9 ) {
830
831
						$dates['m_start'] = 4;
832
						$dates['m_end']   = 6;
833
						$dates['year']    = date( 'Y', $current_time );
834
835
					} else {
836
837
						$dates['m_start'] = 7;
838
						$dates['m_end']   = 9;
839
						$dates['year']    = date( 'Y', $current_time );
840
841
					}
842
					break;
843
844 View Code Duplication
				case 'this_year' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
845
					$dates['day']     = null;
846
					$dates['m_start'] = null;
847
					$dates['m_end']   = null;
848
					$dates['year']    = date( 'Y', $current_time );
849
					break;
850
851 View Code Duplication
				case 'last_year' :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
852 1
					$dates['day']     = null;
853
					$dates['m_start'] = null;
854 1
					$dates['m_end']   = null;
855 1
					$dates['year']    = date( 'Y', $current_time ) - 1;
856 1
					break;
857
858
			endswitch;
859
		}// End if().
860 1
861
		/**
862 1
		 * Returns the filters for the dates used to retrieve earnings.
863 1
		 *
864 1
		 * @since 1.2
865
		 *
866 1
		 * @param array $dates The dates used for retrieving earnings.
867
		 */
868
		return apply_filters( 'give_api_stat_dates', $dates );
869 1
	}
870
871
	/**
872 1
	 * Process Get Donors API Request.
873 1
	 *
874 1
	 * @access public
875
	 * @since  1.1
876 1
	 * @global WPDB $wpdb  Used to query the database using the WordPress Database API.
877 1
	 *
878
	 * @param int   $donor Donor ID
879 1
	 *
880
	 * @return array $donors Multidimensional array of the donors.
881 1
	 */
882
	public function get_donors( $donor = null ) {
883 1
884 1
		$donors = array();
885 1
		$error  = array();
886 1
		if ( ! user_can( $this->user_id, 'view_give_sensitive_data' ) && ! $this->override ) {
887 1
			return $donors;
888 1
		}
889 1
890
		$paged    = $this->get_paged();
891 1
		$per_page = $this->per_page();
892 1
		$offset   = $per_page * ( $paged - 1 );
893 1
894 1
		if ( is_numeric( $donor ) ) {
895 1
			$field = 'id';
896 1
		} else {
897 1
			$field = 'email';
898
		}
899 1
900
		$donor_query = Give()->donors->get_donors( array(
901 1
			'number' => $per_page,
902
			'offset' => $offset,
903
			$field   => $donor,
904 1
		) );
905 1
		$donor_count = 0;
906 1
907
		if ( $donor_query ) {
908 1
909
			foreach ( $donor_query as $donor_obj ) {
910 1
911 1
				$names      = explode( ' ', $donor_obj->name );
912
				$first_name = ! empty( $names[0] ) ? $names[0] : '';
913 1
				$last_name  = '';
914 View Code Duplication
				if ( ! empty( $names[1] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
915 1
					unset( $names[0] );
916
					$last_name = implode( ' ', $names );
917 1
				}
918
919
				$donors['donors'][ $donor_count ]['info']['user_id']      = '';
920
				$donors['donors'][ $donor_count ]['info']['username']     = '';
921
				$donors['donors'][ $donor_count ]['info']['display_name'] = '';
922
				$donors['donors'][ $donor_count ]['info']['donor_id']     = $donor_obj->id;
923
				$donors['donors'][ $donor_count ]['info']['first_name']   = $first_name;
924
				$donors['donors'][ $donor_count ]['info']['last_name']    = $last_name;
925
				$donors['donors'][ $donor_count ]['info']['email']        = $donor_obj->email;
926
927
				if ( ! empty( $donor_obj->user_id ) ) {
928
929
					$user_data = get_userdata( $donor_obj->user_id );
930
931
					// Donor with registered account.
932
					$donors['donors'][ $donor_count ]['info']['user_id']      = $donor_obj->user_id;
933
					$donors['donors'][ $donor_count ]['info']['username']     = $user_data->user_login;
934
					$donors['donors'][ $donor_count ]['info']['display_name'] = $user_data->display_name;
935 1
936
				}
937
938
				$donors['donors'][ $donor_count ]['stats']['total_donations'] = $donor_obj->purchase_count;
939
				$donors['donors'][ $donor_count ]['stats']['total_spent']     = $donor_obj->purchase_value;
940
941
				$donor_count ++;
942
943
			}
944
		} elseif ( $donor ) {
945
946
			$error['error'] = sprintf( /* translators: %s: donor */
947
				__( 'Donor %s not found.', 'give' ), $donor );
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 12 spaces, but found 16.
Loading history...
948 11
949
			return $error;
950 11
951 11
		} else {
952
953 11
			$error['error'] = __( 'No donors found.', 'give' );
954 11
955
			return $error;
956 11
957 11
		}// End if().
958 11
959 11
		return $donors;
960 11
	}
961 11
962
	/**
963 11
	 * Process Get Donation Forms API Request
964 11
	 *
965 11
	 * @access public
966 11
	 * @since  1.1
967 11
	 *
968 11
	 * @param int $form Give Form ID.
969 11
	 *
970 11
	 * @return array $donors Multidimensional array of the forms.
971
	 */
972
	public function get_forms( $form = null ) {
973
974
		$forms = array();
975
		$error = array();
976
977
		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...
978
			$forms['forms'] = array();
979
980
			$form_list = get_posts( array(
981
				'post_type'        => 'give_forms',
982
				'posts_per_page'   => $this->per_page(),
983
				'suppress_filters' => true,
984
				'paged'            => $this->get_paged(),
985
			) );
986
987 11
			if ( $form_list ) {
988
				$i = 0;
989
				foreach ( $form_list as $form_info ) {
990
					$forms['forms'][ $i ] = $this->get_form_data( $form_info );
991
					$i ++;
992
				}
993
			}
994
		} else {
995
			if ( get_post_type( $form ) == 'give_forms' ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
996
				$form_info = get_post( $form );
997
998
				$forms['forms'][0] = $this->get_form_data( $form_info );
999 11
1000
			} else {
1001 11
				$error['error'] = sprintf( /* translators: %s: form */
1002
					__( 'Form %s not found.', 'give' ), $form );
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 16 spaces, but found 20.
Loading history...
1003 11
1004 11
				return $error;
1005 11
			}
1006 11
		}
1007 11
1008 11
		return $forms;
1009 11
	}
1010 11
1011 11
	/**
1012
	 * Given a give_forms post object, generate the data for the API output
1013 11
	 *
1014
	 * @since  1.1
1015
	 *
1016
	 * @param  object $form_info The Give Form's Post Object.
1017 11
	 *
1018
	 * @return array                Array of post data to return back in the API.
1019
	 */
1020
	private function get_form_data( $form_info ) {
1021 11
1022 11
		$form = array();
1023 11
1024 11
		$form['info']['id']            = $form_info->ID;
1025 11
		$form['info']['slug']          = $form_info->post_name;
1026 11
		$form['info']['title']         = $form_info->post_title;
1027
		$form['info']['create_date']   = $form_info->post_date;
1028 11
		$form['info']['modified_date'] = $form_info->post_modified;
1029 11
		$form['info']['status']        = $form_info->post_status;
1030 11
		$form['info']['link']          = html_entity_decode( $form_info->guid );
1031 11
		$form['info']['content']       = give_get_meta( $form_info->ID, '_give_form_content', true );
1032
		$form['info']['thumbnail']     = wp_get_attachment_url( get_post_thumbnail_id( $form_info->ID ) );
1033 11
1034 11
		if ( give_is_setting_enabled( give_get_option( 'categories', 'disabled' ) ) ) {
1035
			$form['info']['category'] = get_the_terms( $form_info, 'give_forms_category' );
1036 11
			$form['info']['tags']     = get_the_terms( $form_info, 'give_forms_tag' );
1037 11
		}
1038
		if ( give_is_setting_enabled( give_get_option( 'tags', 'disabled' ) ) ) {
1039
			$form['info']['tags'] = get_the_terms( $form_info, 'give_forms_tag' );
1040
		}
1041 11
1042
		// Check whether any goal is to be achieved for the donation form.
1043
		$goal_option = give_get_meta( $form_info->ID, '_give_goal_option', true );
1044 11
		$goal_amount = give_get_meta( $form_info->ID, '_give_set_goal', true );
1045
		if ( give_is_setting_enabled( $goal_option ) && $goal_amount ) {
1046 11
			$total_income                         = give_get_form_earnings_stats( $form_info->ID );
1047
			$goal_percentage_completed            = ( $total_income < $goal_amount ) ? round( ( $total_income / $goal_amount ) * 100, 2 ) : 100;
1048 11
			$form['goal']['amount']               = isset( $goal_amount ) ? $goal_amount : '';
1049
			$form['goal']['percentage_completed'] = isset( $goal_percentage_completed ) ? $goal_percentage_completed : '';
1050
		}
1051
1052
		if ( user_can( $this->user_id, 'view_give_reports' ) || $this->override ) {
1053
			$form['stats']['total']['donations']           = give_get_form_sales_stats( $form_info->ID );
1054
			$form['stats']['total']['earnings']            = give_get_form_earnings_stats( $form_info->ID );
1055
			$form['stats']['monthly_average']['donations'] = give_get_average_monthly_form_sales( $form_info->ID );
1056
			$form['stats']['monthly_average']['earnings']  = give_get_average_monthly_form_earnings( $form_info->ID );
1057
		}
1058
1059
		$counter = 0;
1060
		if ( give_has_variable_prices( $form_info->ID ) ) {
1061
			foreach ( give_get_variable_prices( $form_info->ID ) as $price ) {
0 ignored issues
show
Bug introduced by
The expression give_get_variable_prices($form_info->ID) of type false|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1062
				$counter ++;
1063
				// multi-level item
1064
				$level                                     = isset( $price['_give_text'] ) ? $price['_give_text'] : 'level-' . $counter;
1065
				$form['pricing'][ sanitize_key( $level ) ] = $price['_give_amount'];
1066
1067
			}
1068
		} else {
1069
			$form['pricing']['amount'] = give_get_form_price( $form_info->ID );
1070
		}
1071
1072
		if ( user_can( $this->user_id, 'view_give_sensitive_data' ) || $this->override ) {
1073
1074
			/**
1075
			 * Fires when generating API sensitive data.
1076
			 *
1077
			 * @since 1.1
1078
			 */
1079
			do_action( 'give_api_sensitive_data' );
1080
1081
		}
1082
1083
		return apply_filters( 'give_api_forms_form', $form );
1084
1085
	}
1086
1087
	/**
1088
	 * Process Get Stats API Request
1089
	 *
1090
	 * @since 1.1
1091
	 *
1092
	 * @global WPDB $wpdb Used to query the database using the WordPress.
1093
	 *
1094
	 * @param array $args Arguments provided by API Request.
1095
	 *
1096
	 * @return array
1097
	 */
1098
	public function get_stats( $args = array() ) {
1099
		$defaults = array(
1100
			'type'      => null,
1101
			'form'      => null,
1102
			'date'      => null,
1103
			'startdate' => null,
1104
			'enddate'   => null,
1105
		);
1106
1107
		$args = wp_parse_args( $args, $defaults );
1108
1109
		$dates = $this->get_dates( $args );
1110
1111
		$stats     = array();
1112
		$earnings  = array(
1113
			'earnings' => array(),
1114
		);
1115
		$donations = array(
1116
			'donations' => array(),
1117
		);
1118
		$error     = array();
1119
1120
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1121
			return $stats;
1122
		}
1123
1124
		if ( $args['type'] == 'donations' ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
1125
1126
			if ( $args['form'] == null ) {
1127
				if ( $args['date'] == null ) {
1128
					$donations = $this->get_default_sales_stats();
1129
				} elseif ( $args['date'] === 'range' ) {
0 ignored issues
show
introduced by
Found "=== '". Use Yoda Condition checks, you must
Loading history...
1130
					// Return donations for a date range.
1131
					// Ensure the end date is later than the start date.
1132 View Code Duplication
					if ( $args['enddate'] < $args['startdate'] ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1133
						$error['error'] = __( 'The end date must be later than the start date.', 'give' );
1134
					}
1135
1136
					// Ensure both the start and end date are specified
1137 View Code Duplication
					if ( empty( $args['startdate'] ) || empty( $args['enddate'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1138
						$error['error'] = __( 'Invalid or no date range specified.', 'give' );
1139
					}
1140
1141
					$total = 0;
1142
1143
					// Loop through the years
1144
					$y = $dates['year'];
1145 View Code Duplication
					while ( $y <= $dates['year_end'] ) :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1146
1147
						if ( $dates['year'] == $dates['year_end'] ) {
1148
							$month_start = $dates['m_start'];
1149
							$month_end   = $dates['m_end'];
1150
						} elseif ( $y == $dates['year'] && $dates['year_end'] > $dates['year'] ) {
1151
							$month_start = $dates['m_start'];
1152
							$month_end   = 12;
1153
						} elseif ( $y == $dates['year_end'] ) {
1154
							$month_start = 1;
1155
							$month_end   = $dates['m_end'];
1156
						} else {
1157
							$month_start = 1;
1158
							$month_end   = 12;
1159
						}
1160
1161
						$i = $month_start;
1162
						while ( $i <= $month_end ) :
1163
1164
							if ( $i == $dates['m_start'] ) {
1165
								$d = $dates['day_start'];
1166
							} else {
1167
								$d = 1;
1168
							}
1169
1170
							if ( $i == $dates['m_end'] ) {
1171
								$num_of_days = $dates['day_end'];
1172
							} else {
1173
								$num_of_days = cal_days_in_month( CAL_GREGORIAN, $i, $y );
1174
							}
1175
1176
							while ( $d <= $num_of_days ) :
1177
								$sale_count = give_get_sales_by_date( $d, $i, $y );
1178
								$date_key   = date( 'Ymd', strtotime( $y . '/' . $i . '/' . $d ) );
1179
								if ( ! isset( $donations['sales'][ $date_key ] ) ) {
1180
									$donations['sales'][ $date_key ] = 0;
1181
								}
1182
								$donations['sales'][ $date_key ] += $sale_count;
1183
								$total                           += $sale_count;
1184
								$d ++;
1185
							endwhile;
1186
							$i ++;
1187
						endwhile;
1188
1189
						$y ++;
1190
					endwhile;
1191
1192
					$donations['totals'] = $total;
1193 View Code Duplication
				} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1194
					if ( $args['date'] == 'this_quarter' || $args['date'] == 'last_quarter' ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
1195
						$donations_count = 0;
1196
1197
						// Loop through the months
1198
						$month = $dates['m_start'];
1199
1200
						while ( $month <= $dates['m_end'] ) :
1201
							$donations_count += give_get_sales_by_date( null, $month, $dates['year'] );
1202
							$month ++;
1203
						endwhile;
1204
1205
						$donations['donations'][ $args['date'] ] = $donations_count;
1206
					} else {
1207
						$donations['donations'][ $args['date'] ] = give_get_sales_by_date( $dates['day'], $dates['m_start'], $dates['year'] );
1208
					}
1209
				}// End if().
1210
			} elseif ( $args['form'] == 'all' ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
1211
				$forms = get_posts( array(
1212
					'post_type' => 'give_forms',
1213
					'nopaging'  => true,
0 ignored issues
show
introduced by
Disabling pagination is prohibited in VIP context, do not set nopaging to true ever.
Loading history...
1214
				) );
1215
				$i     = 0;
1216
				foreach ( $forms as $form_info ) {
1217
					$donations['donations'][ $i ] = array(
1218
						$form_info->post_name => give_get_form_sales_stats( $form_info->ID ),
1219
					);
1220
					$i ++;
1221
				}
1222 View Code Duplication
			} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1223
				if ( get_post_type( $args['form'] ) == 'give_forms' ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
1224
					$form_info                 = get_post( $args['form'] );
1225
					$donations['donations'][0] = array(
1226
						$form_info->post_name => give_get_form_sales_stats( $args['form'] ),
1227
					);
1228
				} else {
1229
					$error['error'] = sprintf( /* translators: %s: form */
1230
						__( 'Form %s not found.', 'give' ), $args['form'] );
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 20 spaces, but found 24.
Loading history...
1231
				}
1232
			}// End if().
1233
1234
			if ( ! empty( $error ) ) {
1235
				return $error;
1236
			}
1237
1238
			return $donations;
1239
1240
		} elseif ( $args['type'] == 'earnings' ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
1241
			if ( $args['form'] == null ) {
1242
				if ( $args['date'] == null ) {
1243
					$earnings = $this->get_default_earnings_stats();
1244
				} elseif ( $args['date'] === 'range' ) {
0 ignored issues
show
introduced by
Found "=== '". Use Yoda Condition checks, you must
Loading history...
1245
					// Return sales for a date range
1246
					// Ensure the end date is later than the start date
1247 View Code Duplication
					if ( $args['enddate'] < $args['startdate'] ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1248
						$error['error'] = __( 'The end date must be later than the start date.', 'give' );
1249
					}
1250
1251
					// Ensure both the start and end date are specified
1252 View Code Duplication
					if ( empty( $args['startdate'] ) || empty( $args['enddate'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1253
						$error['error'] = __( 'Invalid or no date range specified.', 'give' );
1254
					}
1255
1256
					$total = (float) 0.00;
1257
1258
					// Loop through the years
1259
					$y = $dates['year'];
1260
					if ( ! isset( $earnings['earnings'] ) ) {
1261
						$earnings['earnings'] = array();
1262
					}
1263 View Code Duplication
					while ( $y <= $dates['year_end'] ) :
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1264
1265
						if ( $dates['year'] == $dates['year_end'] ) {
1266
							$month_start = $dates['m_start'];
1267
							$month_end   = $dates['m_end'];
1268
						} elseif ( $y == $dates['year'] && $dates['year_end'] > $dates['year'] ) {
1269
							$month_start = $dates['m_start'];
1270
							$month_end   = 12;
1271
						} elseif ( $y == $dates['year_end'] ) {
1272
							$month_start = 1;
1273
							$month_end   = $dates['m_end'];
1274
						} else {
1275
							$month_start = 1;
1276
							$month_end   = 12;
1277
						}
1278
1279
						$i = $month_start;
1280
						while ( $i <= $month_end ) :
1281
1282
							if ( $i == $dates['m_start'] ) {
1283
								$d = $dates['day_start'];
1284
							} else {
1285
								$d = 1;
1286
							}
1287
1288
							if ( $i == $dates['m_end'] ) {
1289
								$num_of_days = $dates['day_end'];
1290
							} else {
1291
								$num_of_days = cal_days_in_month( CAL_GREGORIAN, $i, $y );
1292
							}
1293
1294
							while ( $d <= $num_of_days ) :
1295
								$earnings_stat = give_get_earnings_by_date( $d, $i, $y );
1296
								$date_key      = date( 'Ymd', strtotime( $y . '/' . $i . '/' . $d ) );
1297
								if ( ! isset( $earnings['earnings'][ $date_key ] ) ) {
1298
									$earnings['earnings'][ $date_key ] = 0;
1299
								}
1300
								$earnings['earnings'][ $date_key ] += $earnings_stat;
1301
								$total                             += $earnings_stat;
1302
								$d ++;
1303
							endwhile;
1304
1305
							$i ++;
1306
						endwhile;
1307
1308
						$y ++;
1309
					endwhile;
1310
1311
					$earnings['totals'] = $total;
1312 View Code Duplication
				} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1313
					if ( $args['date'] == 'this_quarter' || $args['date'] == 'last_quarter' ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
1314
						$earnings_count = (float) 0.00;
1315
1316
						// Loop through the months
1317
						$month = $dates['m_start'];
1318
1319
						while ( $month <= $dates['m_end'] ) :
1320
							$earnings_count += give_get_earnings_by_date( null, $month, $dates['year'] );
1321
							$month ++;
1322
						endwhile;
1323
1324
						$earnings['earnings'][ $args['date'] ] = $earnings_count;
1325
					} else {
1326
						$earnings['earnings'][ $args['date'] ] = give_get_earnings_by_date( $dates['day'], $dates['m_start'], $dates['year'] );
1327
					}
1328
				}// End if().
1329
			} elseif ( $args['form'] == 'all' ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
1330
				$forms = get_posts( array(
1331
					'post_type' => 'give_forms',
1332
					'nopaging'  => true,
0 ignored issues
show
introduced by
Disabling pagination is prohibited in VIP context, do not set nopaging to true ever.
Loading history...
1333
				) );
1334
1335
				$i = 0;
1336
				foreach ( $forms as $form_info ) {
1337
					$earnings['earnings'][ $i ] = array(
1338
						$form_info->post_name => give_get_form_earnings_stats( $form_info->ID ),
1339 11
					);
1340 11
					$i ++;
1341
				}
1342 11 View Code Duplication
			} else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1343
				if ( get_post_type( $args['form'] ) == 'give_forms' ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
1344 11
					$form_info               = get_post( $args['form'] );
1345
					$earnings['earnings'][0] = array(
1346
						$form_info->post_name => give_get_form_earnings_stats( $args['form'] ),
1347
					);
1348 11
				} else {
1349
					$error['error'] = sprintf( /* translators: %s: form */
1350
						__( 'Form %s not found.', 'give' ), $args['form'] );
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 20 spaces, but found 24.
Loading history...
1351 11
				}
1352
			}// End if().
1353
1354 11
			if ( ! empty( $error ) ) {
1355
				return $error;
1356
			}
1357
1358
			return $earnings;
1359
		} elseif ( $args['type'] == 'donors' ) {
0 ignored issues
show
introduced by
Found "== '". Use Yoda Condition checks, you must
Loading history...
1360
			$donors                             = new Give_DB_Donors();
1361
			$stats['donations']['total_donors'] = $donors->count();
1362
1363
			return $stats;
1364
1365
		} elseif ( empty( $args['type'] ) ) {
1366 11
			$stats = array_merge( $stats, $this->get_default_sales_stats() );
1367 11
			$stats = array_merge( $stats, $this->get_default_earnings_stats() );
1368 11
1369
			return array(
1370 11
				'stats' => $stats,
1371 11
			);
1372
		}// End if().
1373 11
	}
1374 11
1375 11
	/**
1376
	 * Retrieves Recent Donations
1377 11
	 *
1378 11
	 * @access public
1379 11
	 * @since  1.1
1380 11
	 *
1381 11
	 * @param $args array
1382
	 *
1383
	 * @return array
1384
	 */
1385 11
	public function get_recent_donations( $args = array() ) {
1386 11
		global $wp_query;
1387
1388 11
		$defaults = array(
1389 11
			'id'        => null,
1390
			'date'      => null,
1391 11
			'startdate' => null,
1392 11
			'enddate'   => null,
1393 11
		);
1394 11
1395 11
		$args = wp_parse_args( $args, $defaults );
1396 11
1397 11
		$donations = array();
1398 11
1399 11
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1400 11
			return $donations;
1401
		}
1402 11
1403 11
		if ( isset( $wp_query->query_vars['id'] ) ) {
1404 11
			$query   = array();
1405
			$query[] = new Give_Payment( $wp_query->query_vars['id'] );
1406 11
		} elseif ( isset( $wp_query->query_vars['purchasekey'] ) ) {
1407 11
			$query   = array();
1408 11
			$query[] = give_get_payment_by( 'key', $wp_query->query_vars['purchasekey'] );
1409
		} elseif ( isset( $wp_query->query_vars['email'] ) ) {
1410 11
			$args  = array(
1411 11
				'fields'     => 'ids',
1412 11
				'meta_key'   => '_give_payment_user_email',
0 ignored issues
show
introduced by
Detected usage of meta_key, possible slow query.
Loading history...
1413 11
				'meta_value' => $wp_query->query_vars['email'],
0 ignored issues
show
introduced by
Detected usage of meta_value, possible slow query.
Loading history...
1414 11
				'number'     => $this->per_page(),
1415 11
				'page'       => $this->get_paged(),
1416
			);
1417 11
			$query = give_get_payments( $args );
1418 11
		} elseif ( isset( $wp_query->query_vars['date'] ) ) {
1419
1420
			$current_time = current_time( 'timestamp' );
1421 11
			$dates        = $this->get_dates( $args );
1422
			$start_date   = '';
1423
			$end_date     = '';
1424 11
1425 11
			/**
1426 11
			 *  Switch case for date query argument
1427 11
			 *
1428 11
			 * @since  1.8.8
1429 11
			 *
1430 11
			 * @params text date | today, yesterday or range
1431 11
			 * @params date startdate | required when date = range and format to be YYYYMMDD (i.e. 20170524)
1432
			 * @params date enddate | required when date = range and format to be YYYYMMDD (i.e. 20170524)
1433
			 */
1434 11
			switch ( $wp_query->query_vars['date'] ) {
1435 11
1436
				case 'today':
1437
1438 11
					// Set and Format Start and End Date to be date of today.
1439
					$start_date = $end_date = date( 'Y/m/d', $current_time );
1440 11
1441
					break;
1442 11
1443 11
				case 'yesterday':
1444 11
1445
					// Set and Format Start and End Date to be date of yesterday.
1446 11
					$start_date = $end_date = date( 'Y/m', $current_time ) . '/' . ( date( 'd', $current_time ) - 1 );
1447
1448
					break;
1449
1450
				case 'range':
1451
1452
					// Format Start Date and End Date for filtering payment based on date range.
1453
					$start_date = $dates['year'] . '/' . $dates['m_start'] . '/' . $dates['day_start'];
1454
					$end_date   = $dates['year_end'] . '/' . $dates['m_end'] . '/' . $dates['day_end'];
1455
1456
					break;
1457
1458
			}
1459
1460
			$args = array(
1461
				'fields'     => 'ids',
1462
				'start_date' => $start_date,
1463
				'end_date'   => $end_date,
1464
				'number'     => $this->per_page(),
1465
				'page'       => $this->get_paged(),
1466
			);
1467
1468
			$query = give_get_payments( $args );
1469
		} else {
1470
			$args  = array(
1471
				'fields' => 'ids',
1472
				'number' => $this->per_page(),
1473
				'page'   => $this->get_paged(),
1474
			);
1475
			$query = give_get_payments( $args );
1476
		}// End if().
1477
1478
		if ( $query ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $query of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1479
			$i = 0;
1480
			foreach ( $query as $payment ) {
1481
1482
				if ( is_numeric( $payment ) ) {
1483
					$payment      = new Give_Payment( $payment );
1484
					$payment_meta = $payment->get_meta();
0 ignored issues
show
Unused Code introduced by
$payment_meta is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1485
					$user_info    = $payment->user_info;
0 ignored issues
show
Unused Code introduced by
$user_info is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1486
				}
1487
1488
				$payment_meta = $payment->get_meta();
1489
				$user_info    = $payment->user_info;
1490
1491
				$first_name = isset( $user_info['first_name'] ) ? $user_info['first_name'] : '';
1492
				$last_name  = isset( $user_info['last_name'] ) ? $user_info['last_name'] : '';
1493
1494
				$donations['donations'][ $i ]['ID']             = $payment->number;
1495
				$donations['donations'][ $i ]['transaction_id'] = $payment->transaction_id;
1496
				$donations['donations'][ $i ]['key']            = $payment->key;
1497
				$donations['donations'][ $i ]['total']          = $payment->total;
1498
				$donations['donations'][ $i ]['status']         = give_get_payment_status( $payment, true );
1499
				$donations['donations'][ $i ]['gateway']        = $payment->gateway;
1500
				$donations['donations'][ $i ]['name']           = $first_name . ' ' . $last_name;
1501
				$donations['donations'][ $i ]['fname']          = $first_name;
1502
				$donations['donations'][ $i ]['lname']          = $last_name;
1503
				$donations['donations'][ $i ]['email']          = $payment->email;
1504
				$donations['donations'][ $i ]['date']           = $payment->date;
1505
1506
				$form_id  = isset( $payment_meta['form_id'] ) ? $payment_meta['form_id'] : $payment_meta;
1507
				$price    = isset( $payment_meta['form_id'] ) ? give_get_form_price( $payment_meta['form_id'] ) : false;
1508
				$price_id = isset( $payment_meta['price_id'] ) ? $payment_meta['price_id'] : null;
1509
1510
				$donations['donations'][ $i ]['form']['id']    = $form_id;
1511
				$donations['donations'][ $i ]['form']['name']  = get_the_title( $payment_meta['form_id'] );
1512
				$donations['donations'][ $i ]['form']['price'] = $price;
1513
1514
				if ( give_has_variable_prices( $form_id ) ) {
1515
					if ( isset( $payment_meta['price_id'] ) ) {
1516
						$price_name                                         = give_get_price_option_name( $form_id, $payment_meta['price_id'], $payment->ID );
1517
						$donations['donations'][ $i ]['form']['price_name'] = $price_name;
1518
						$donations['donations'][ $i ]['form']['price_id']   = $price_id;
1519
						$donations['donations'][ $i ]['form']['price']      = give_get_price_option_amount( $form_id, $price_id );
1520
1521
					}
1522
				}
1523
1524
				// Add custom meta to API
1525
				foreach ( $payment_meta as $meta_key => $meta_value ) {
1526
1527
					$exceptions = array(
1528
						'form_title',
1529
						'form_id',
1530
						'price_id',
1531
						'user_info',
1532
						'key',
1533
						'email',
1534
						'date',
1535
					);
1536
1537
					// Don't clutter up results with dupes
1538
					if ( in_array( $meta_key, $exceptions ) ) {
1539
						continue;
1540
					}
1541
1542
					$donations['donations'][ $i ]['payment_meta'][ $meta_key ] = $meta_value;
1543
1544
				}
1545
1546
				$i ++;
1547
			}// End foreach().
1548
		}// End if().
1549
1550
		return apply_filters( 'give_api_donations_endpoint', $donations );
1551
	}
1552
1553
	/**
1554
	 * Retrieve the output format.
1555
	 *
1556
	 * Determines whether results should be displayed in XML or JSON.
1557
	 *
1558
	 * @since  1.1
1559
	 * @access public
1560
	 *
1561
	 * @return mixed
1562
	 */
1563
	public function get_output_format() {
1564
		global $wp_query;
1565
1566
		$format = isset( $wp_query->query_vars['format'] ) ? $wp_query->query_vars['format'] : 'json';
1567
1568
		return apply_filters( 'give_api_output_format', $format );
1569
	}
1570
1571
1572
	/**
1573
	 * Log each API request, if enabled.
1574
	 *
1575
	 * @access private
1576
	 * @since  1.1
1577
	 *
1578
	 * @global Give_Logging $give_logs
1579
	 * @global WP_Query     $wp_query
1580
	 *
1581
	 * @param array         $data
1582
	 *
1583
	 * @return void
1584
	 */
1585
	private function log_request( $data = array() ) {
1586
		if ( ! $this->log_requests ) {
1587
			return;
1588
		}
1589
1590
		/**
1591
		 * @var Give_Logging $give_logs
1592
		 */
1593
		global $give_logs;
1594
1595
		/**
1596
		 * @var WP_Query $wp_query
1597
		 */
1598
		global $wp_query;
1599
1600
		$query = array(
1601
			'give-api'    => $wp_query->query_vars['give-api'],
1602
			'key'         => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null,
1603
			'token'       => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null,
1604
			'query'       => isset( $wp_query->query_vars['query'] ) ? $wp_query->query_vars['query'] : null,
1605
			'type'        => isset( $wp_query->query_vars['type'] ) ? $wp_query->query_vars['type'] : null,
1606
			'form'        => isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null,
1607
			'donor'       => isset( $wp_query->query_vars['donor'] ) ? $wp_query->query_vars['donor'] : null,
1608
			'date'        => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null,
1609
			'startdate'   => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null,
1610
			'enddate'     => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null,
1611
			'id'          => isset( $wp_query->query_vars['id'] ) ? $wp_query->query_vars['id'] : null,
1612
			'purchasekey' => isset( $wp_query->query_vars['purchasekey'] ) ? $wp_query->query_vars['purchasekey'] : null,
1613
			'email'       => isset( $wp_query->query_vars['email'] ) ? $wp_query->query_vars['email'] : null,
1614
		);
1615
1616
		$log_data = array(
1617
			'log_type'     => 'api_request',
1618
			'post_excerpt' => http_build_query( $query ),
1619
			'post_content' => ! empty( $data['error'] ) ? $data['error'] : '',
1620
		);
1621
1622
		$log_meta = array(
1623
			'request_ip' => give_get_ip(),
1624
			'user'       => $this->user_id,
1625
			'key'        => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null,
1626
			'token'      => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null,
1627
			'time'       => $data['request_speed'],
1628
			'version'    => $this->get_queried_version(),
1629
		);
1630
1631
		$give_logs->insert_log( $log_data, $log_meta );
1632
	}
1633
1634
1635
	/**
1636
	 * Retrieve the output data.
1637
	 *
1638
	 * @access public
1639
	 * @since  1.1
1640
	 * @return array
1641
	 */
1642
	public function get_output() {
1643
		return $this->data;
1644
	}
1645
1646
	/**
1647
	 * Output Query in either JSON/XML.
1648
	 * The query data is outputted as JSON by default.
1649
	 *
1650
	 * @since 1.1
1651
	 * @global WP_Query $wp_query
1652
	 *
1653
	 * @param int       $status_code
1654
	 */
1655
	public function output( $status_code = 200 ) {
1656
1657
		$format = $this->get_output_format();
1658
1659
		status_header( $status_code );
1660
1661
		/**
1662
		 * Fires before outputting the API.
1663
		 *
1664
		 * @since 1.1
1665
		 *
1666
		 * @param array    $data   Response data to return.
1667
		 * @param Give_API $this   The Give_API object.
1668
		 * @param string   $format Output format, XML or JSON. Default is JSON.
1669
		 */
1670
		do_action( 'give_api_output_before', $this->data, $this, $format );
1671
1672
		switch ( $format ) :
1673
1674
			case 'xml' :
1675
1676
				require_once GIVE_PLUGIN_DIR . 'includes/libraries/array2xml.php';
1677
				$xml = Array2XML::createXML( 'give', $this->data );
1678
				echo $xml->saveXML();
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '$xml'
Loading history...
1679
1680
				break;
1681
1682
			case 'json' :
1683
1684
				header( 'Content-Type: application/json' );
1685
				if ( ! empty( $this->pretty_print ) ) {
1686
					echo json_encode( $this->data, $this->pretty_print );
1687
				} else {
1688
					echo json_encode( $this->data );
1689
				}
1690
1691
				break;
1692
1693
			default :
1694
1695
				/**
1696
				 * Fires by the API while outputting other formats.
1697
				 *
1698
				 * @since 1.1
1699
				 *
1700
				 * @param array    $data Response data to return.
1701
				 * @param Give_API $this The Give_API object.
1702
				 */
1703
				do_action( "give_api_output_{$format}", $this->data, $this );
1704
1705
				break;
1706
1707
		endswitch;
1708
1709
		/**
1710
		 * Fires after outputting the API.
1711
		 *
1712
		 * @since 1.1
1713
		 *
1714
		 * @param array    $data   Response data to return.
1715
		 * @param Give_API $this   The Give_API object.
1716
		 * @param string   $format Output format, XML or JSON. Default is JSON.
1717
		 */
1718
		do_action( 'give_api_output_after', $this->data, $this, $format );
1719
1720
		give_die();
1721
	}
1722
1723
	/**
1724
	 * Modify User Profile
1725
	 *
1726
	 * Modifies the output of profile.php to add key generation/revocation.
1727
	 *
1728
	 * @access public
1729
	 * @since  1.1
1730
	 *
1731
	 * @param object $user Current user info
1732
	 *
1733
	 * @return void
1734
	 */
1735
	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...
1736
1737
		if ( ( give_get_option( 'api_allow_user_keys', false ) || current_user_can( 'manage_give_settings' ) ) && current_user_can( 'edit_user', $user->ID ) ) {
1738
1739
			$user = get_userdata( $user->ID );
1740
			?>
1741
			<table class="form-table">
1742
				<tbody>
1743
				<tr>
1744
					<th>
1745
						<?php _e( 'Give API Keys', 'give' ); ?>
1746
					</th>
1747
					<td>
1748
						<?php
1749
						$public_key = $this->get_user_public_key( $user->ID );
1750
						$secret_key = $this->get_user_secret_key( $user->ID );
1751
						?>
1752
						<?php if ( empty( $user->give_user_public_key ) ) { ?>
1753
							<input name="give_set_api_key" type="checkbox" id="give_set_api_key" />
1754
							<span class="description"><?php _e( 'Generate API Key', 'give' ); ?></span>
1755
						<?php } else { ?>
1756
							<strong style="display:inline-block; width: 125px;"><?php _e( 'Public key:', 'give' ); ?>
1757
								&nbsp;</strong>
1758
							<input type="text" disabled="disabled" class="regular-text" id="publickey" value="<?php echo esc_attr( $public_key ); ?>" />
1759
							<br />
1760
							<strong style="display:inline-block; width: 125px;"><?php _e( 'Secret key:', 'give' ); ?>
1761
								&nbsp;</strong>
1762
							<input type="text" disabled="disabled" class="regular-text" id="privatekey" value="<?php echo esc_attr( $secret_key ); ?>" />
1763
							<br />
1764
							<strong style="display:inline-block; width: 125px;"><?php _e( 'Token:', 'give' ); ?>
1765
								&nbsp;</strong>
1766
							<input type="text" disabled="disabled" class="regular-text" id="token" value="<?php echo esc_attr( $this->get_token( $user->ID ) ); ?>" />
1767
							<br />
1768
							<input name="give_revoke_api_key" type="checkbox" id="give_revoke_api_key" />
1769
							<span class="description"><label for="give_revoke_api_key"><?php _e( 'Revoke API Keys', 'give' ); ?></label></span>
1770
						<?php } ?>
1771
					</td>
1772
				</tr>
1773
				</tbody>
1774
			</table>
1775
		<?php }// End if().
1776
	}
1777
1778
	/**
1779
	 * Process an API key generation/revocation
1780
	 *
1781
	 * @access public
1782
	 * @since  1.1
1783
	 *
1784
	 * @param array $args
1785
	 *
1786
	 * @return void
1787
	 */
1788
	public function process_api_key( $args ) {
1789
1790 View Code Duplication
		if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'give-api-nonce' ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
introduced by
Detected access of super global var $_REQUEST, probably need manual inspection.
Loading history...
introduced by
Detected usage of a non-validated input variable: $_REQUEST
Loading history...
introduced by
Detected usage of a non-sanitized input variable: $_REQUEST
Loading history...
1791
			wp_die( __( 'Nonce verification failed.', 'give' ), __( 'Error', 'give' ), array(
1792
				'response' => 403,
1793
			) );
1794
		}
1795
1796
		if ( empty( $args['user_id'] ) ) {
1797 2
			wp_die( __( 'User ID Required.', 'give' ), __( 'Error', 'give' ), array(
1798 2
				'response' => 401,
1799
			) );
1800
		}
1801
1802
		if ( is_numeric( $args['user_id'] ) ) {
1803
			$user_id = isset( $args['user_id'] ) ? absint( $args['user_id'] ) : get_current_user_id();
1804
		} else {
1805
			$userdata = get_user_by( 'login', $args['user_id'] );
1806
			$user_id  = $userdata->ID;
1807
		}
1808
		$process = isset( $args['give_api_process'] ) ? strtolower( $args['give_api_process'] ) : false;
1809
1810
		if ( $user_id == get_current_user_id() && ! give_get_option( 'allow_user_api_keys' ) && ! current_user_can( 'manage_give_settings' ) ) {
1811
			wp_die( sprintf( /* translators: %s: process */
1812
				__( 'You do not have permission to %s API keys for this user.', 'give' ), $process ), __( 'Error', 'give' ), array(
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 12 spaces, but found 16.
Loading history...
1813
				'response' => 403,
1814 2
			) );
1815 2 View Code Duplication
		} elseif ( ! current_user_can( 'manage_give_settings' ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1816
			wp_die( sprintf( /* translators: %s: process */
1817 2
				__( 'You do not have permission to %s API keys for this user.', 'give' ), $process ), __( 'Error', 'give' ), array(
0 ignored issues
show
Coding Style introduced by
This line of the multi-line function call does not seem to be indented correctly. Expected 12 spaces, but found 16.
Loading history...
1818
				'response' => 403,
1819 2
			) );
1820 2
		}
1821
1822 2
		switch ( $process ) {
1823 2
			case 'generate':
1824 2
				if ( $this->generate_api_key( $user_id ) ) {
1825
					Give_Cache::delete( Give_Cache::get_key( 'give_total_api_keys' ) );
1826 2
					wp_redirect( add_query_arg( 'give-message', 'api-key-generated', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1827 2
					exit();
1828 2
				} else {
1829
					wp_redirect( add_query_arg( 'give-message', 'api-key-failed', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1830
					exit();
1831 2
				}
1832 2
				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...
1833 View Code Duplication
			case 'regenerate':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1834
				$this->generate_api_key( $user_id, true );
1835
				Give_Cache::delete( Give_Cache::get_key( 'give_total_api_keys' ) );
1836
				wp_redirect( add_query_arg( 'give-message', 'api-key-regenerated', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1837
				exit();
1838
				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...
1839 View Code Duplication
			case 'revoke':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1840
				$this->revoke_api_key( $user_id );
1841
				Give_Cache::delete( Give_Cache::get_key( 'give_total_api_keys' ) );
1842
				wp_redirect( add_query_arg( 'give-message', 'api-key-revoked', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1843
				exit();
1844 2
				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...
1845 2
			default;
1846 2
				break;
1847
		}
1848 2
	}
1849
1850
	/**
1851
	 * Generate new API keys for a user
1852
	 *
1853
	 * @param int     $user_id    User ID the key is being generated for.
1854
	 * @param boolean $regenerate Regenerate the key for the user.
1855
	 *
1856
	 * @access public
1857
	 * @since  1.1
1858
	 *
1859
	 * @return boolean True if (re)generated successfully, false otherwise.
1860
	 */
1861 2
	public function generate_api_key( $user_id = 0, $regenerate = false ) {
1862 2
1863 2
		// Bail out, if user doesn't exists.
1864
		if ( empty( $user_id ) ) {
1865 2
			return false;
1866
		}
1867
1868
		$user = get_userdata( $user_id );
1869
1870
		// Bail Out, if user object doesn't exists.
1871
		if ( ! $user ) {
1872
			return false;
1873
		}
1874
1875
		$new_public_key = '';
1876
		$new_secret_key = '';
1877
1878
		if( ! empty( $_POST['from'] ) && 'profile' === $_POST['from'] ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1879
			// For User Profile Page.
1880
			if( ! empty( $_POST['give_set_api_key'] ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1881
				// Generate API Key from User Profile page.
1882
				$new_public_key = $this->generate_public_key( $user->user_email );
1883
				$new_secret_key = $this->generate_private_key( $user->ID );
1884
			} elseif ( ! empty( $_POST['give_revoke_api_key'] ) ) {
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
1885
				// Revoke API Key from User Profile page.
1886
				$this->revoke_api_key( $user->ID );
1887
			} else {
1888
				return false;
1889
			}
1890
		} else {
1891
			// For Tools > API page.
1892
			$public_key = $this->get_user_public_key( $user_id );
1893
1894
			if ( empty( $public_key ) && ! $regenerate ) {
1895
				// Generating API for first time.
1896
				$new_public_key = $this->generate_public_key( $user->user_email );
1897
				$new_secret_key = $this->generate_private_key( $user->ID );
1898
			} elseif ( $public_key && $regenerate ) {
1899
				// API Key already exists and Regenerating API Key.
1900
				$this->revoke_api_key( $user->ID );
1901
				$new_public_key = $this->generate_public_key( $user->user_email );
1902
				$new_secret_key = $this->generate_private_key( $user->ID );
1903
			} elseif ( ! empty( $public_key ) && ! $regenerate ) {
1904
				// Doing nothing, when API Key exists but still try to generate again instead of regenerating.
1905
				return false;
1906
			} else {
1907
				// Revoke API Key.
1908
				$this->revoke_api_key( $user->ID );
1909
			}
1910
		}
1911
1912
		update_user_meta( $user_id, $new_public_key, 'give_user_public_key' );
0 ignored issues
show
introduced by
update_user_meta() usage is highly discouraged, check VIP documentation on "Working with wp_users"
Loading history...
1913
		update_user_meta( $user_id, $new_secret_key, 'give_user_secret_key' );
0 ignored issues
show
introduced by
update_user_meta() usage is highly discouraged, check VIP documentation on "Working with wp_users"
Loading history...
1914
1915
		return true;
1916
	}
1917
1918
	/**
1919
	 * Revoke a users API keys
1920
	 *
1921
	 * @access public
1922
	 * @since  1.1
1923
	 *
1924
	 * @param int $user_id User ID of user to revoke key for
1925
	 *
1926
	 * @return bool
1927
	 */
1928
	public function revoke_api_key( $user_id = 0 ) {
1929
1930
		if ( empty( $user_id ) ) {
1931
			return false;
1932
		}
1933
1934 56
		$user = get_userdata( $user_id );
1935
1936 56
		if ( ! $user ) {
1937 56
			return false;
1938
		}
1939
1940
		$public_key = $this->get_user_public_key( $user_id );
1941
		$secret_key = $this->get_user_secret_key( $user_id );
1942
		if ( ! empty( $public_key ) ) {
1943
			Give_Cache::delete( Give_Cache::get_key( md5( 'give_api_user_' . $public_key ) ) );
1944
			Give_Cache::delete( Give_Cache::get_key( md5( 'give_api_user_public_key' . $user_id ) ) );
1945
			Give_Cache::delete( Give_Cache::get_key( md5( 'give_api_user_secret_key' . $user_id ) ) );
1946
			delete_user_meta( $user_id, $public_key );
0 ignored issues
show
introduced by
delete_user_meta() usage is highly discouraged, check VIP documentation on "Working with wp_users"
Loading history...
1947
			delete_user_meta( $user_id, $secret_key );
0 ignored issues
show
introduced by
delete_user_meta() usage is highly discouraged, check VIP documentation on "Working with wp_users"
Loading history...
1948
		} else {
1949
			return false;
1950
		}
1951
1952
		return true;
1953
	}
1954
1955
	public function get_version() {
1956
		return self::VERSION;
1957
	}
1958
1959
	/**
1960
	 * Generate the public key for a user
1961
	 *
1962
	 * @access private
1963
	 * @since  1.1
1964
	 *
1965
	 * @param string $user_email
1966
	 *
1967
	 * @return string
1968
	 */
1969
	private function generate_public_key( $user_email = '' ) {
1970
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
1971
		$public   = hash( 'md5', $user_email . $auth_key . date( 'U' ) );
1972
1973
		return $public;
1974
	}
1975
1976
	/**
1977
	 * Generate the secret key for a user
1978
	 *
1979
	 * @access private
1980
	 * @since  1.1
1981
	 *
1982
	 * @param int $user_id
1983
	 *
1984
	 * @return string
1985
	 */
1986
	private function generate_private_key( $user_id = 0 ) {
1987
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
1988
		$secret   = hash( 'md5', $user_id . $auth_key . date( 'U' ) );
1989
1990
		return $secret;
1991
	}
1992
1993
	/**
1994
	 * Retrieve the user's token
1995
	 *
1996
	 * @access private
1997
	 * @since  1.1
1998
	 *
1999
	 * @param int $user_id
2000
	 *
2001
	 * @return string
2002
	 */
2003
	public function get_token( $user_id = 0 ) {
2004
		return hash( 'md5', $this->get_user_secret_key( $user_id ) . $this->get_user_public_key( $user_id ) );
2005
	}
2006
2007
	/**
2008
	 * Generate the default donation stats returned by the 'stats' endpoint
2009
	 *
2010
	 * @access private
2011
	 * @since  1.1
2012
	 * @return array default sales statistics
2013
	 */
2014 View Code Duplication
	private function get_default_sales_stats() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2015
2016
		// Default sales return
2017
		$donations                               = array();
2018
		$donations['donations']['today']         = $this->stats->get_sales( 0, 'today' );
2019
		$donations['donations']['current_month'] = $this->stats->get_sales( 0, 'this_month' );
2020
		$donations['donations']['last_month']    = $this->stats->get_sales( 0, 'last_month' );
2021
		$donations['donations']['totals']        = give_get_total_donations();
2022
2023
		return $donations;
2024
	}
2025
2026
	/**
2027
	 * Generate the default earnings stats returned by the 'stats' endpoint
2028
	 *
2029
	 * @access private
2030
	 * @since  1.1
2031
	 * @return array default earnings statistics
2032
	 */
2033 View Code Duplication
	private function get_default_earnings_stats() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2034
2035
		// Default earnings return
2036
		$earnings                              = array();
2037
		$earnings['earnings']['today']         = $this->stats->get_earnings( 0, 'today' );
2038
		$earnings['earnings']['current_month'] = $this->stats->get_earnings( 0, 'this_month' );
2039
		$earnings['earnings']['last_month']    = $this->stats->get_earnings( 0, 'last_month' );
2040
		$earnings['earnings']['totals']        = give_get_total_earnings();
2041
2042
		return $earnings;
2043
	}
2044
2045
	/**
2046
	 * API Key Backwards Compatibility
2047
	 *
2048
	 * A Backwards Compatibility call for the change of meta_key/value for users API Keys.
2049
	 *
2050
	 * @since  1.3.6
2051
	 *
2052
	 * @param  string $check     Whether to check the cache or not
2053
	 * @param  int    $object_id The User ID being passed
2054
	 * @param  string $meta_key  The user meta key
2055
	 * @param  bool   $single    If it should return a single value or array
2056
	 *
2057
	 * @return string            The API key/secret for the user supplied
2058
	 */
2059
	public function api_key_backwards_compat( $check, $object_id, $meta_key, $single ) {
2060
2061
		if ( $meta_key !== 'give_user_public_key' && $meta_key !== 'give_user_secret_key' ) {
0 ignored issues
show
introduced by
Found "!== '". Use Yoda Condition checks, you must
Loading history...
2062
			return $check;
2063
		}
2064
2065
		$return = $check;
2066
2067
		switch ( $meta_key ) {
2068
			case 'give_user_public_key':
2069
				$return = Give()->api->get_user_public_key( $object_id );
2070
				break;
2071
			case 'give_user_secret_key':
2072
				$return = Give()->api->get_user_secret_key( $object_id );
2073
				break;
2074
		}
2075
2076
		if ( ! $single ) {
2077
			$return = array( $return );
2078
		}
2079
2080
		return $return;
2081
2082
	}
2083
2084
}
2085