Completed
Push — issues-2036 ( 6dd3d7 )
by Ravinder
1267:25 queued 1262:37
created

Give_API::update_key()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 1
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A Give_API::generate_public_key() 0 6 2
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
	 * @access public
137
	 */
138
	public function __construct() {
139
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
		);
143
144
		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
		}
147
148
		add_action( 'init', array( $this, 'add_endpoint' ) );
149
		add_action( 'wp', array( $this, 'process_query' ), - 1 );
150
		add_filter( 'query_vars', array( $this, 'query_vars' ) );
151
		add_action( 'show_user_profile', array( $this, 'user_key_field' ) );
152
		add_action( 'edit_user_profile', array( $this, 'user_key_field' ) );
153
		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
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
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
163
		// Allow API request logging to be turned off
164
		$this->log_requests = apply_filters( 'give_api_log_requests', $this->log_requests );
165
166
		// Setup Give_Payment_Stats instance
167
		$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
	public function add_endpoint() {
179
		add_rewrite_endpoint( 'give-api', EP_ALL );
180
	}
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
	public function query_vars( $vars ) {
193
194
		$vars[] = 'token';
195
		$vars[] = 'key';
196
		$vars[] = 'query';
197
		$vars[] = 'type';
198
		$vars[] = 'form';
199
		$vars[] = 'number';
200
		$vars[] = 'date';
201
		$vars[] = 'startdate';
202
		$vars[] = 'enddate';
203
		$vars[] = 'donor';
204
		$vars[] = 'format';
205
		$vars[] = 'id';
206
		$vars[] = 'purchasekey';
207
		$vars[] = 'email';
208
209
		return $vars;
210
	}
211
212
	/**
213
	 * Retrieve the API versions
214
	 *
215
	 * @access public
216
	 * @since  1.1
217
	 * @return array
218
	 */
219
	public function get_versions() {
220
		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
	public function get_default_version() {
242
243
		$version = get_option( 'give_default_api_version' );
244
245
		if ( defined( 'GIVE_API_VERSION' ) ) {
246
			$version = GIVE_API_VERSION;
247
		} elseif ( ! $version ) {
248
			$version = 'v1';
249
		}
250
251
		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
	 *                      Database API
354
	 *
355
	 * @param string $key   Public Key
356
	 *
357
	 * @return bool if user ID is found, false otherwise
358
	 */
359
	public function get_user( $key = '' ) {
360
		global $wpdb, $wp_query;
361
362
		if ( empty( $key ) ) {
363
			$key = urldecode( $wp_query->query_vars['key'] );
364
		}
365
366
		if ( empty( $key ) ) {
367
			return false;
368
		}
369
370
		$user = Give_Cache::get( md5( 'give_api_user_' . $key ), true );
371
372
		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
			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
			return $user;
381
		}
382
383
		return false;
384
	}
385
386
	/**
387
	 * Get user public key.
388
	 *
389
	 * @param int $user_id
390
	 *
391
	 * @return mixed|null|string
392
	 */
393 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
396
		if ( empty( $user_id ) ) {
397
			return '';
398
		}
399
400
		$cache_key       = md5( 'give_api_user_public_key' . $user_id );
401
		$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
			Give_Cache::set( $cache_key, $user_public_key, HOUR_IN_SECONDS, true );
406
		}
407
408
		return $user_public_key;
409
	}
410
411
	/**
412
	 * Get user secret key.
413
	 *
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'] = esc_html__( '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'] = esc_html__( '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'] = esc_html__( '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
	 * @since  1.1
491
	 * @uses   Give_API::output()
492
	 * @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
		$error          = array();
496
		$error['error'] = esc_html__( 'Invalid API version.', 'give' );
497
498
		$this->data = $error;
499
		$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
		$accepted = apply_filters( 'give_api_valid_query_modes', array(
633
			'stats',
634
			'forms',
635
			'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'] = esc_html__( 'Invalid query.', 'give' );
647
648
			$this->data = $error;
649
			// 400 is Bad Request
650
			$this->output( 400 );
651
		}
652
653
		$this->endpoint = $query;
654
	}
655
656
	/**
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
					$dates['day']     = null;
853
					$dates['m_start'] = null;
854
					$dates['m_end']   = null;
855
					$dates['year']    = date( 'Y', $current_time ) - 1;
856
					break;
857
858
			endswitch;
859
		}// End if().
860
861
		/**
862
		 * Returns the filters for the dates used to retrieve earnings.
863
		 *
864
		 * @since 1.2
865
		 *
866
		 * @param array $dates The dates used for retrieving earnings.
867
		 */
868
		return apply_filters( 'give_api_stat_dates', $dates );
869
	}
870
871
	/**
872
	 * Process Get Donors API Request.
873
	 *
874
	 * @access public
875
	 * @since  1.1
876
	 * @global WPDB $wpdb  Used to query the database using the WordPress Database API.
877
	 *
878
	 * @param int   $donor Donor ID
879
	 *
880
	 * @return array $donors Multidimensional array of the donors.
881
	 */
882
	public function get_donors( $donor = null ) {
883
884
		$donors = array();
885
		$error  = array();
886
		if ( ! user_can( $this->user_id, 'view_give_sensitive_data' ) && ! $this->override ) {
887
			return $donors;
888
		}
889
890
		$paged    = $this->get_paged();
891
		$per_page = $this->per_page();
892
		$offset   = $per_page * ( $paged - 1 );
893
894
		if ( is_numeric( $donor ) ) {
895
			$field = 'id';
896
		} else {
897
			$field = 'email';
898
		}
899
900
		$donor_query = Give()->donors->get_donors( array(
901
			'number' => $per_page,
902
			'offset' => $offset,
903
			$field   => $donor,
904
		) );
905
		$donor_count = 0;
906
907
		if ( $donor_query ) {
908
909
			foreach ( $donor_query as $donor_obj ) {
910
911
				$names      = explode( ' ', $donor_obj->name );
912
				$first_name = ! empty( $names[0] ) ? $names[0] : '';
913
				$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
					unset( $names[0] );
916
					$last_name = implode( ' ', $names );
917
				}
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
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
				esc_html__( '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
949
			return $error;
950
951
		} else {
952
953
			$error['error'] = esc_html__( 'No donors found.', 'give' );
954
955
			return $error;
956
957
		}// End if().
958
959
		return $donors;
960
	}
961
962
	/**
963
	 * Process Get Donation Forms API Request
964
	 *
965
	 * @access public
966
	 * @since  1.1
967
	 *
968
	 * @param int $form Give Form ID.
969
	 *
970
	 * @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
			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
1000
			} else {
1001
				$error['error'] = sprintf( /* translators: %s: form */
1002
					esc_html__( '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
1004
				return $error;
1005
			}
1006
		}
1007
1008
		return $forms;
1009
	}
1010
1011
	/**
1012
	 * Given a give_forms post object, generate the data for the API output
1013
	 *
1014
	 * @since  1.1
1015
	 *
1016
	 * @param  object $form_info The Give Form's Post Object.
1017
	 *
1018
	 * @return array                Array of post data to return back in the API.
1019
	 */
1020
	private function get_form_data( $form_info ) {
1021
1022
		$form = array();
1023
1024
		$form['info']['id']            = $form_info->ID;
1025
		$form['info']['slug']          = $form_info->post_name;
1026
		$form['info']['title']         = $form_info->post_title;
1027
		$form['info']['create_date']   = $form_info->post_date;
1028
		$form['info']['modified_date'] = $form_info->post_modified;
1029
		$form['info']['status']        = $form_info->post_status;
1030
		$form['info']['link']          = html_entity_decode( $form_info->guid );
1031
		$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
1034
		if ( give_is_setting_enabled( give_get_option( 'categories', 'disabled' ) ) ) {
1035
			$form['info']['category'] = get_the_terms( $form_info, 'give_forms_category' );
1036
			$form['info']['tags']     = get_the_terms( $form_info, 'give_forms_tag' );
1037
		}
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
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
		$goal_amount = give_get_meta( $form_info->ID, '_give_set_goal', true );
1045
		if ( give_is_setting_enabled( $goal_option ) && $goal_amount ) {
1046
			$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
			$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'] = esc_html__( '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'] = esc_html__( '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
						esc_html__( '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'] = esc_html__( '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'] = esc_html__( '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
					);
1340
					$i ++;
1341
				}
1342 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
					$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
				} else {
1349
					$error['error'] = sprintf( /* translators: %s: form */
1350
						esc_html__( '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
				}
1352
			}// End if().
1353
1354
			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
			$stats = array_merge( $stats, $this->get_default_sales_stats() );
1367
			$stats = array_merge( $stats, $this->get_default_earnings_stats() );
1368
1369
			return array(
1370
				'stats' => $stats,
1371
			);
1372
		}// End if().
1373
	}
1374
1375
	/**
1376
	 * Retrieves Recent Donations
1377
	 *
1378
	 * @access public
1379
	 * @since  1.1
1380
	 *
1381
	 * @param $args array
1382
	 *
1383
	 * @return array
1384
	 */
1385
	public function get_recent_donations( $args = array() ) {
1386
		global $wp_query;
1387
1388
		$defaults = array(
1389
			'id'        => null,
1390
			'date'      => null,
1391
			'startdate' => null,
1392
			'enddate'   => null,
1393
		);
1394
1395
		$args = wp_parse_args( $args, $defaults );
1396
1397
		$donations = array();
1398
1399
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1400
			return $donations;
1401
		}
1402
1403
		if ( isset( $wp_query->query_vars['id'] ) ) {
1404
			$query   = array();
1405
			$query[] = new Give_Payment( $wp_query->query_vars['id'] );
1406
		} elseif ( isset( $wp_query->query_vars['purchasekey'] ) ) {
1407
			$query   = array();
1408
			$query[] = give_get_payment_by( 'key', $wp_query->query_vars['purchasekey'] );
1409
		} elseif ( isset( $wp_query->query_vars['email'] ) ) {
1410
			$args  = array(
1411
				'fields'     => 'ids',
1412
				'meta_key'   => '_give_payment_user_email',
0 ignored issues
show
introduced by
Detected usage of meta_key, possible slow query.
Loading history...
1413
				'meta_value' => $wp_query->query_vars['email'],
0 ignored issues
show
introduced by
Detected usage of meta_value, possible slow query.
Loading history...
1414
				'number'     => $this->per_page(),
1415
				'page'       => $this->get_paged(),
1416
			);
1417
			$query = give_get_payments( $args );
1418
		} elseif ( isset( $wp_query->query_vars['date'] ) ) {
1419
1420
			$current_time = current_time( 'timestamp' );
1421
			$dates        = $this->get_dates( $args );
1422
1423
			/**
1424
			 *  Switch case for date query argument
1425
			 *
1426
			 * @since  1.8.8
1427
			 *
1428
			 * @params text date | today, yesterday or range
1429
			 * @params date startdate | required when date = range and format to be YYYYMMDD (i.e. 20170524)
1430
			 * @params date enddate | required when date = range and format to be YYYYMMDD (i.e. 20170524)
1431
			 */
1432
			switch ( $wp_query->query_vars['date'] ) {
1433
1434
				case 'today':
1435
1436
					// Set and Format Start and End Date to be date of today.
1437
					$start_date = $end_date = date( 'Y/m/d', $current_time );
1438
1439
					break;
1440
1441
				case 'yesterday':
1442
1443
					// Set and Format Start and End Date to be date of yesterday.
1444
					$start_date = $end_date = date( 'Y/m', $current_time ) . '/' . ( date( 'd', $current_time ) - 1 );
1445
1446
					break;
1447
1448
				case 'range':
1449
1450
					// Format Start Date and End Date for filtering payment based on date range.
1451
					$start_date = $dates['year'] . '/' . $dates['m_start'] . '/' . $dates['day_start'];
1452
					$end_date   = $dates['year_end'] . '/' . $dates['m_end'] . '/' . $dates['day_end'];
1453
1454
					break;
1455
1456
			}
1457
1458
			$args = array(
1459
				'fields'     => 'ids',
1460
				'start_date' => $start_date,
1461
				'end_date'   => $end_date,
1462
				'number'     => $this->per_page(),
1463
				'page'       => $this->get_paged(),
1464
			);
1465
1466
			$query = give_get_payments( $args );
1467
		} else {
1468
			$args  = array(
1469
				'fields' => 'ids',
1470
				'number' => $this->per_page(),
1471
				'page'   => $this->get_paged(),
1472
			);
1473
			$query = give_get_payments( $args );
1474
		}// End if().
1475
1476
		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...
1477
			$i = 0;
1478
			foreach ( $query as $payment ) {
1479
1480
				if ( is_numeric( $payment ) ) {
1481
					$payment      = new Give_Payment( $payment );
1482
					$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...
1483
					$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...
1484
				}
1485
1486
				$payment_meta = $payment->get_meta();
1487
				$user_info    = $payment->user_info;
1488
1489
				$first_name = isset( $user_info['first_name'] ) ? $user_info['first_name'] : '';
1490
				$last_name  = isset( $user_info['last_name'] ) ? $user_info['last_name'] : '';
1491
1492
				$donations['donations'][ $i ]['ID']             = $payment->number;
1493
				$donations['donations'][ $i ]['transaction_id'] = $payment->transaction_id;
1494
				$donations['donations'][ $i ]['key']            = $payment->key;
1495
				$donations['donations'][ $i ]['total']          = $payment->total;
1496
				$donations['donations'][ $i ]['status']         = give_get_payment_status( $payment, true );
1497
				$donations['donations'][ $i ]['gateway']        = $payment->gateway;
1498
				$donations['donations'][ $i ]['name']           = $first_name . ' ' . $last_name;
1499
				$donations['donations'][ $i ]['fname']          = $first_name;
1500
				$donations['donations'][ $i ]['lname']          = $last_name;
1501
				$donations['donations'][ $i ]['email']          = $payment->email;
1502
				$donations['donations'][ $i ]['date']           = $payment->date;
1503
1504
				$form_id  = isset( $payment_meta['form_id'] ) ? $payment_meta['form_id'] : $payment_meta;
1505
				$price    = isset( $payment_meta['form_id'] ) ? give_get_form_price( $payment_meta['form_id'] ) : false;
1506
				$price_id = isset( $payment_meta['price_id'] ) ? $payment_meta['price_id'] : null;
1507
1508
				$donations['donations'][ $i ]['form']['id']    = $form_id;
1509
				$donations['donations'][ $i ]['form']['name']  = get_the_title( $payment_meta['form_id'] );
1510
				$donations['donations'][ $i ]['form']['price'] = $price;
1511
1512
				if ( give_has_variable_prices( $form_id ) ) {
1513
					if ( isset( $payment_meta['price_id'] ) ) {
1514
						$price_name                                         = give_get_price_option_name( $form_id, $payment_meta['price_id'], $payment->ID );
1515
						$donations['donations'][ $i ]['form']['price_name'] = $price_name;
1516
						$donations['donations'][ $i ]['form']['price_id']   = $price_id;
1517
						$donations['donations'][ $i ]['form']['price']      = give_get_price_option_amount( $form_id, $price_id );
1518
1519
					}
1520
				}
1521
1522
				// Add custom meta to API
1523
				foreach ( $payment_meta as $meta_key => $meta_value ) {
1524
1525
					$exceptions = array(
1526
						'form_title',
1527
						'form_id',
1528
						'price_id',
1529
						'user_info',
1530
						'key',
1531
						'email',
1532
						'date',
1533
					);
1534
1535
					// Don't clutter up results with dupes
1536
					if ( in_array( $meta_key, $exceptions ) ) {
1537
						continue;
1538
					}
1539
1540
					$donations['donations'][ $i ]['payment_meta'][ $meta_key ] = $meta_value;
1541
1542
				}
1543
1544
				$i ++;
1545
			}// End foreach().
1546
		}// End if().
1547
1548
		return apply_filters( 'give_api_donations_endpoint', $donations );
1549
	}
1550
1551
	/**
1552
	 * Retrieve the output format.
1553
	 *
1554
	 * Determines whether results should be displayed in XML or JSON.
1555
	 *
1556
	 * @since  1.1
1557
	 * @access public
1558
	 *
1559
	 * @return mixed
1560
	 */
1561
	public function get_output_format() {
1562
		global $wp_query;
1563
1564
		$format = isset( $wp_query->query_vars['format'] ) ? $wp_query->query_vars['format'] : 'json';
1565
1566
		return apply_filters( 'give_api_output_format', $format );
1567
	}
1568
1569
1570
	/**
1571
	 * Log each API request, if enabled.
1572
	 *
1573
	 * @access private
1574
	 * @since  1.1
1575
	 *
1576
	 * @global Give_Logging $give_logs
1577
	 * @global WP_Query     $wp_query
1578
	 *
1579
	 * @param array         $data
1580
	 *
1581
	 * @return void
1582
	 */
1583
	private function log_request( $data = array() ) {
1584
		if ( ! $this->log_requests ) {
1585
			return;
1586
		}
1587
1588
		/**
1589
		 * @var Give_Logging $give_logs
1590
		 */
1591
		global $give_logs;
1592
1593
		/**
1594
		 * @var WP_Query $wp_query
1595
		 */
1596
		global $wp_query;
1597
1598
		$query = array(
1599
			'give-api'    => $wp_query->query_vars['give-api'],
1600
			'key'         => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null,
1601
			'token'       => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null,
1602
			'query'       => isset( $wp_query->query_vars['query'] ) ? $wp_query->query_vars['query'] : null,
1603
			'type'        => isset( $wp_query->query_vars['type'] ) ? $wp_query->query_vars['type'] : null,
1604
			'form'        => isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null,
1605
			'donor'       => isset( $wp_query->query_vars['donor'] ) ? $wp_query->query_vars['donor'] : null,
1606
			'date'        => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null,
1607
			'startdate'   => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null,
1608
			'enddate'     => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null,
1609
			'id'          => isset( $wp_query->query_vars['id'] ) ? $wp_query->query_vars['id'] : null,
1610
			'purchasekey' => isset( $wp_query->query_vars['purchasekey'] ) ? $wp_query->query_vars['purchasekey'] : null,
1611
			'email'       => isset( $wp_query->query_vars['email'] ) ? $wp_query->query_vars['email'] : null,
1612
		);
1613
1614
		$log_data = array(
1615
			'log_type'     => 'api_request',
1616
			'post_excerpt' => http_build_query( $query ),
1617
			'post_content' => ! empty( $data['error'] ) ? $data['error'] : '',
1618
		);
1619
1620
		$log_meta = array(
1621
			'request_ip' => give_get_ip(),
1622
			'user'       => $this->user_id,
1623
			'key'        => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null,
1624
			'token'      => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null,
1625
			'time'       => $data['request_speed'],
1626
			'version'    => $this->get_queried_version(),
1627
		);
1628
1629
		$give_logs->insert_log( $log_data, $log_meta );
1630
	}
1631
1632
1633
	/**
1634
	 * Retrieve the output data.
1635
	 *
1636
	 * @access public
1637
	 * @since  1.1
1638
	 * @return array
1639
	 */
1640
	public function get_output() {
1641
		return $this->data;
1642
	}
1643
1644
	/**
1645
	 * Output Query in either JSON/XML.
1646
	 * The query data is outputted as JSON by default.
1647
	 *
1648
	 * @since 1.1
1649
	 * @global WP_Query $wp_query
1650
	 *
1651
	 * @param int       $status_code
1652
	 */
1653
	public function output( $status_code = 200 ) {
1654
1655
		$format = $this->get_output_format();
1656
1657
		status_header( $status_code );
1658
1659
		/**
1660
		 * Fires before outputting the API.
1661
		 *
1662
		 * @since 1.1
1663
		 *
1664
		 * @param array    $data   Response data to return.
1665
		 * @param Give_API $this   The Give_API object.
1666
		 * @param string   $format Output format, XML or JSON. Default is JSON.
1667
		 */
1668
		do_action( 'give_api_output_before', $this->data, $this, $format );
1669
1670
		switch ( $format ) :
1671
1672
			case 'xml' :
1673
1674
				require_once GIVE_PLUGIN_DIR . 'includes/libraries/array2xml.php';
1675
				$xml = Array2XML::createXML( 'give', $this->data );
1676
				echo $xml->saveXML();
0 ignored issues
show
introduced by
Expected next thing to be a escaping function, not '$xml'
Loading history...
1677
1678
				break;
1679
1680
			case 'json' :
1681
1682
				header( 'Content-Type: application/json' );
1683
				if ( ! empty( $this->pretty_print ) ) {
1684
					echo json_encode( $this->data, $this->pretty_print );
1685
				} else {
1686
					echo json_encode( $this->data );
1687
				}
1688
1689
				break;
1690
1691
			default :
1692
1693
				/**
1694
				 * Fires by the API while outputting other formats.
1695
				 *
1696
				 * @since 1.1
1697
				 *
1698
				 * @param array    $data Response data to return.
1699
				 * @param Give_API $this The Give_API object.
1700
				 */
1701
				do_action( "give_api_output_{$format}", $this->data, $this );
1702
1703
				break;
1704
1705
		endswitch;
1706
1707
		/**
1708
		 * Fires after outputting the API.
1709
		 *
1710
		 * @since 1.1
1711
		 *
1712
		 * @param array    $data   Response data to return.
1713
		 * @param Give_API $this   The Give_API object.
1714
		 * @param string   $format Output format, XML or JSON. Default is JSON.
1715
		 */
1716
		do_action( 'give_api_output_after', $this->data, $this, $format );
1717
1718
		give_die();
1719
	}
1720
1721
	/**
1722
	 * Modify User Profile
1723
	 *
1724
	 * Modifies the output of profile.php to add key generation/revocation.
1725
	 *
1726
	 * @access public
1727
	 * @since  1.1
1728
	 *
1729
	 * @param object $user Current user info
1730
	 *
1731
	 * @return void
1732
	 */
1733
	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...
1734
1735
		if ( ( give_get_option( 'api_allow_user_keys', false ) || current_user_can( 'manage_give_settings' ) ) && current_user_can( 'edit_user', $user->ID ) ) {
1736
1737
			$user = get_userdata( $user->ID );
1738
			?>
1739
			<table class="form-table">
1740
				<tbody>
1741
				<tr>
1742
					<th>
1743
						<?php esc_html_e( 'Give API Keys', 'give' ); ?>
1744
					</th>
1745
					<td>
1746
						<?php
1747
						$public_key = $this->get_user_public_key( $user->ID );
1748
						$secret_key = $this->get_user_secret_key( $user->ID );
1749
						?>
1750
						<?php if ( empty( $user->give_user_public_key ) ) { ?>
1751
							<input name="give_set_api_key" type="checkbox" id="give_set_api_key" value="0" />
1752
							<span class="description"><?php esc_html_e( 'Generate API Key', 'give' ); ?></span>
1753
						<?php } else { ?>
1754
							<strong style="display:inline-block; width: 125px;"><?php esc_html_e( 'Public key:', 'give' ); ?>
1755
								&nbsp;</strong>
1756
							<input type="text" disabled="disabled" class="regular-text" id="publickey" value="<?php echo esc_attr( $public_key ); ?>" />
1757
							<br />
1758
							<strong style="display:inline-block; width: 125px;"><?php esc_html_e( 'Secret key:', 'give' ); ?>
1759
								&nbsp;</strong>
1760
							<input type="text" disabled="disabled" class="regular-text" id="privatekey" value="<?php echo esc_attr( $secret_key ); ?>" />
1761
							<br />
1762
							<strong style="display:inline-block; width: 125px;"><?php esc_html_e( 'Token:', 'give' ); ?>
1763
								&nbsp;</strong>
1764
							<input type="text" disabled="disabled" class="regular-text" id="token" value="<?php echo esc_attr( $this->get_token( $user->ID ) ); ?>" />
1765
							<br />
1766
							<input name="give_set_api_key" type="checkbox" id="give_set_api_key" value="0" />
1767
							<span class="description"><label for="give_set_api_key"><?php esc_html_e( 'Revoke API Keys', 'give' ); ?></label></span>
1768
						<?php } ?>
1769
					</td>
1770
				</tr>
1771
				</tbody>
1772
			</table>
1773
		<?php }// End if().
1774
	}
1775
1776
	/**
1777
	 * Process an API key generation/revocation
1778
	 *
1779
	 * @access public
1780
	 * @since  1.1
1781
	 *
1782
	 * @param array $args
1783
	 *
1784
	 * @return void
1785
	 */
1786
	public function process_api_key( $args ) {
1787
1788 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...
1789
			wp_die( __( 'Nonce verification failed.', 'give' ), __( 'Error', 'give' ), array(
1790
				'response' => 403,
1791
			) );
1792
		}
1793
1794
		if ( empty( $args['user_id'] ) ) {
1795
			wp_die( __( 'User ID Required.', 'give' ), __( 'Error', 'give' ), array(
1796
				'response' => 401,
1797
			) );
1798
		}
1799
1800
		if ( is_numeric( $args['user_id'] ) ) {
1801
			$user_id = isset( $args['user_id'] ) ? absint( $args['user_id'] ) : get_current_user_id();
1802
		} else {
1803
			$userdata = get_user_by( 'login', $args['user_id'] );
1804
			$user_id  = $userdata->ID;
1805
		}
1806
		$process = isset( $args['give_api_process'] ) ? strtolower( $args['give_api_process'] ) : false;
1807
1808
		if ( $user_id == get_current_user_id() && ! give_get_option( 'allow_user_api_keys' ) && ! current_user_can( 'manage_give_settings' ) ) {
1809
			wp_die( sprintf( /* translators: %s: process */
1810
				esc_html__( 'You do not have permission to %s API keys for this user.', 'give' ), $process ), esc_html__( '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...
1811
				'response' => 403,
1812
			) );
1813
		} elseif ( ! current_user_can( 'manage_give_settings' ) ) {
1814
			wp_die( sprintf( /* translators: %s: process */
1815
				esc_html__( 'You do not have permission to %s API keys for this user.', 'give' ), $process ), esc_html__( '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...
1816
				'response' => 403,
1817
			) );
1818
		}
1819
1820
		switch ( $process ) {
1821
			case 'generate':
1822
				if ( $this->generate_api_key( $user_id ) ) {
1823
					Give_Cache::delete( Give_Cache::get_key( 'give_total_api_keys' ) );
1824
					wp_redirect( add_query_arg( 'give-message', 'api-key-generated', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1825
					exit();
1826
				} else {
1827
					wp_redirect( add_query_arg( 'give-message', 'api-key-failed', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1828
					exit();
1829
				}
1830
				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...
1831 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...
1832
				$this->generate_api_key( $user_id, true );
1833
				Give_Cache::delete( Give_Cache::get_key( 'give_total_api_keys' ) );
1834
				wp_redirect( add_query_arg( 'give-message', 'api-key-regenerated', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1835
				exit();
1836
				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...
1837 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...
1838
				$this->revoke_api_key( $user_id );
1839
				Give_Cache::delete( Give_Cache::get_key( 'give_total_api_keys' ) );
1840
				wp_redirect( add_query_arg( 'give-message', 'api-key-revoked', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1841
				exit();
1842
				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...
1843
			default;
1844
				break;
1845
		}
1846
	}
1847
1848
	/**
1849
	 * Generate new API keys for a user
1850
	 *
1851
	 * @param int     $user_id    User ID the key is being generated for.
1852
	 * @param boolean $regenerate Regenerate the key for the user.
1853
	 *
1854
	 * @access public
1855
	 * @since  1.1
1856
	 *
1857
	 * @return boolean True if (re)generated succesfully, false otherwise.
1858
	 */
1859
	public function generate_api_key( $user_id = 0, $regenerate = false ) {
1860
1861
		// Bail out, if user doesn't exists.
1862
		if ( empty( $user_id ) ) {
1863
			return false;
1864
		}
1865
1866
		$user = get_userdata( $user_id );
1867
1868
		// Bail Out, if user object doesn't exists.
1869
		if ( ! $user ) {
1870
			return false;
1871
		}
1872
1873
		$public_key = $this->get_user_public_key( $user_id );
1874
		$secret_key = $this->get_user_secret_key( $user_id );
0 ignored issues
show
Unused Code introduced by
$secret_key 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...
1875
1876
		if ( empty( $public_key ) ) {
1877
			$new_public_key = $this->generate_public_key( $user->user_email );
1878
			$new_secret_key = $this->generate_private_key( $user->ID );
1879
		} else if ( ! empty( $public_key ) ) {
1880
			// Regenerate API keys, if regenerate is true.
1881
			if( true === $regenerate ) {
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...
1882
				$this->revoke_api_key( $user->ID );
1883
				$new_public_key = $this->generate_public_key( $user->user_email );
1884
				$new_secret_key = $this->generate_private_key( $user->ID );
1885
			} elseif ( current_user_can( 'edit_user', $user_id ) && isset( $_POST['give_set_api_key'] ) ) {
0 ignored issues
show
introduced by
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
1886
				// Revoke API Key, if Public key exists and updating user profile.
1887
				$this->revoke_api_key( $user->ID );
1888
			}
1889
		} else {
1890
			return false;
1891
		}
1892
1893
		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...
1894
		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...
1895
1896
		return true;
1897
	}
1898
1899
	/**
1900
	 * Revoke a users API keys
1901
	 *
1902
	 * @access public
1903
	 * @since  1.1
1904
	 *
1905
	 * @param int $user_id User ID of user to revoke key for
1906
	 *
1907
	 * @return bool
1908
	 */
1909
	public function revoke_api_key( $user_id = 0 ) {
1910
1911
		if ( empty( $user_id ) ) {
1912
			return false;
1913
		}
1914
1915
		$user = get_userdata( $user_id );
1916
1917
		if ( ! $user ) {
1918
			return false;
1919
		}
1920
1921
		$public_key = $this->get_user_public_key( $user_id );
1922
		$secret_key = $this->get_user_secret_key( $user_id );
1923
		if ( ! empty( $public_key ) ) {
1924
			Give_Cache::delete( Give_Cache::get_key( md5( 'give_api_user_' . $public_key ) ) );
1925
			Give_Cache::delete( Give_Cache::get_key( md5( 'give_api_user_public_key' . $user_id ) ) );
1926
			Give_Cache::delete( Give_Cache::get_key( md5( 'give_api_user_secret_key' . $user_id ) ) );
1927
			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...
1928
			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...
1929
		} else {
1930
			return false;
1931
		}
1932
1933
		return true;
1934
	}
1935
1936
	public function get_version() {
1937
		return self::VERSION;
1938
	}
1939
1940
	/**
1941
	 * Generate the public key for a user
1942
	 *
1943
	 * @access private
1944
	 * @since  1.1
1945
	 *
1946
	 * @param string $user_email
1947
	 *
1948
	 * @return string
1949
	 */
1950
	private function generate_public_key( $user_email = '' ) {
1951
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
1952
		$public   = hash( 'md5', $user_email . $auth_key . date( 'U' ) );
1953
1954
		return $public;
1955
	}
1956
1957
	/**
1958
	 * Generate the secret key for a user
1959
	 *
1960
	 * @access private
1961
	 * @since  1.1
1962
	 *
1963
	 * @param int $user_id
1964
	 *
1965
	 * @return string
1966
	 */
1967
	private function generate_private_key( $user_id = 0 ) {
1968
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
1969
		$secret   = hash( 'md5', $user_id . $auth_key . date( 'U' ) );
1970
1971
		return $secret;
1972
	}
1973
1974
	/**
1975
	 * Retrieve the user's token
1976
	 *
1977
	 * @access private
1978
	 * @since  1.1
1979
	 *
1980
	 * @param int $user_id
1981
	 *
1982
	 * @return string
1983
	 */
1984
	public function get_token( $user_id = 0 ) {
1985
		return hash( 'md5', $this->get_user_secret_key( $user_id ) . $this->get_user_public_key( $user_id ) );
1986
	}
1987
1988
	/**
1989
	 * Generate the default donation stats returned by the 'stats' endpoint
1990
	 *
1991
	 * @access private
1992
	 * @since  1.1
1993
	 * @return array default sales statistics
1994
	 */
1995 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...
1996
1997
		// Default sales return
1998
		$donations                               = array();
1999
		$donations['donations']['today']         = $this->stats->get_sales( 0, 'today' );
2000
		$donations['donations']['current_month'] = $this->stats->get_sales( 0, 'this_month' );
2001
		$donations['donations']['last_month']    = $this->stats->get_sales( 0, 'last_month' );
2002
		$donations['donations']['totals']        = give_get_total_donations();
2003
2004
		return $donations;
2005
	}
2006
2007
	/**
2008
	 * Generate the default earnings stats returned by the 'stats' endpoint
2009
	 *
2010
	 * @access private
2011
	 * @since  1.1
2012
	 * @return array default earnings statistics
2013
	 */
2014 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...
2015
2016
		// Default earnings return
2017
		$earnings                              = array();
2018
		$earnings['earnings']['today']         = $this->stats->get_earnings( 0, 'today' );
2019
		$earnings['earnings']['current_month'] = $this->stats->get_earnings( 0, 'this_month' );
2020
		$earnings['earnings']['last_month']    = $this->stats->get_earnings( 0, 'last_month' );
2021
		$earnings['earnings']['totals']        = give_get_total_earnings();
2022
2023
		return $earnings;
2024
	}
2025
2026
	/**
2027
	 * API Key Backwards Compatibility
2028
	 *
2029
	 * A Backwards Compatibility call for the change of meta_key/value for users API Keys.
2030
	 *
2031
	 * @since  1.3.6
2032
	 *
2033
	 * @param  string $check     Whether to check the cache or not
2034
	 * @param  int    $object_id The User ID being passed
2035
	 * @param  string $meta_key  The user meta key
2036
	 * @param  bool   $single    If it should return a single value or array
2037
	 *
2038
	 * @return string            The API key/secret for the user supplied
2039
	 */
2040
	public function api_key_backwards_compat( $check, $object_id, $meta_key, $single ) {
2041
2042
		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...
2043
			return $check;
2044
		}
2045
2046
		$return = $check;
2047
2048
		switch ( $meta_key ) {
2049
			case 'give_user_public_key':
2050
				$return = Give()->api->get_user_public_key( $object_id );
2051
				break;
2052
			case 'give_user_secret_key':
2053
				$return = Give()->api->get_user_secret_key( $object_id );
2054
				break;
2055
		}
2056
2057
		if ( ! $single ) {
2058
			$return = array( $return );
2059
		}
2060
2061
		return $return;
2062
2063
	}
2064
2065
}
2066