Completed
Pull Request — master (#1832)
by Devin
04:50
created

Give_API::get_form_data()   D

Complexity

Conditions 15
Paths 288

Size

Total Lines 66
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 40.7802

Importance

Changes 0
Metric Value
cc 15
eloc 39
nc 288
nop 1
dl 0
loc 66
ccs 18
cts 35
cp 0.5143
crap 40.7802
rs 4.3644
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 26 and the first side effect is on line 16.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * Give API
4
 *
5
 * A front-facing JSON/XML API that makes it possible to query donation data.
6
 *
7
 * @package     Give
8
 * @subpackage  Classes/API
9
 * @copyright   Copyright (c) 2016, WordImpress
10
 * @license     https://opensource.org/licenses/gpl-license GNU Public License
11
 * @since       1.1
12
 */
13
14
// Exit if accessed directly.
15
if ( ! defined( 'ABSPATH' ) ) {
16
	exit;
17
}
18
19
/**
20
 * Give_API Class
21
 *
22
 * Renders API returns as a JSON/XML array
23
 *
24
 * @since  1.1
25
 */
26
class Give_API {
27
28
	/**
29
	 * Latest API Version
30
	 */
31
	const VERSION = 1;
32
33
	/**
34
	 * Pretty Print?
35
	 *
36
	 * @var bool
37
	 * @access private
38
	 * @since  1.1
39
	 */
40
	private $pretty_print = false;
41
42
	/**
43
	 * Log API requests?
44
	 *
45
	 * @var bool
46
	 * @access public
47
	 * @since  1.1
48
	 */
49
	public $log_requests = true;
50
51
	/**
52
	 * Is this a valid request?
53
	 *
54
	 * @var bool
55
	 * @access private
56
	 * @since  1.1
57
	 */
58
	private $is_valid_request = false;
59
60
	/**
61
	 * User ID Performing the API Request
62
	 *
63
	 * @var int
64
	 * @access public
65
	 * @since  1.1
66
	 */
67
	public $user_id = 0;
68
69
	/**
70
	 * Instance of Give Stats class
71
	 *
72
	 * @var object
73
	 * @access private
74
	 * @since  1.1
75
	 */
76
	private $stats;
77
78
	/**
79
	 * Response data to return
80
	 *
81
	 * @var array
82
	 * @access private
83
	 * @since  1.1
84
	 */
85
	private $data = array();
86
87
	/**
88
	 * Whether or not to override api key validation.
89
	 *
90
	 * @var bool
91
	 * @access public
92
	 * @since  1.1
93
	 */
94
	public $override = true;
95
96
	/**
97
	 * Version of the API queried
98
	 *
99
	 * @var string
100
	 * @access public
101
	 * @since  1.1
102
	 */
103
	private $queried_version;
104
105
	/**
106
	 * All versions of the API
107
	 *
108
	 * @var string
109
	 * @access protected
110
	 * @since  1.1
111
	 */
112
	protected $versions = array();
113
114
	/**
115
	 * Queried endpoint
116
	 *
117
	 * @var string
118
	 * @access private
119
	 * @since  1.1
120
	 */
121
	private $endpoint;
122
123
	/**
124
	 * Endpoints routes
125
	 *
126
	 * @var object
127
	 * @access private
128
	 * @since  1.1
129
	 */
130
	private $routes;
131
132
	/**
133
	 * Setup the Give API
134
	 *
135
	 * @since  1.1
136 13
	 * @access public
137
	 */
138 13
	public function __construct() {
139 13
140
		$this->versions = array(
141
			'v1' => 'GIVE_API_V1',
142 13
		);
143 13
144 13
		foreach ( $this->get_versions() as $version => $class ) {
0 ignored issues
show
Bug introduced by
The expression $this->get_versions() of type string is not traversable.
Loading history...
145
			require_once GIVE_PLUGIN_DIR . 'includes/api/class-give-api-' . $version . '.php';
146 13
		}
147 13
148 13
		add_action( 'init', array( $this, 'add_endpoint' ) );
149 13
		add_action( 'wp', array( $this, 'process_query' ), - 1 );
150 13
		add_filter( 'query_vars', array( $this, 'query_vars' ) );
151 13
		add_action( 'show_user_profile', array( $this, 'user_key_field' ) );
152 13
		add_action( 'edit_user_profile', array( $this, 'user_key_field' ) );
153 13
		add_action( 'personal_options_update', array( $this, 'update_key' ) );
154
		add_action( 'edit_user_profile_update', array( $this, 'update_key' ) );
155
		add_action( 'give_process_api_key', array( $this, 'process_api_key' ) );
156 13
157
		// Setup a backwards compatibility check for user API Keys
158
		add_filter( 'get_user_metadata', array( $this, 'api_key_backwards_compat' ), 10, 4 );
159 13
160
		// Determine if JSON_PRETTY_PRINT is available
161
		$this->pretty_print = defined( 'JSON_PRETTY_PRINT' ) ? JSON_PRETTY_PRINT : null;
162 13
163
		// Allow API request logging to be turned off
164
		$this->log_requests = apply_filters( 'give_api_log_requests', $this->log_requests );
165 13
166
		// Setup Give_Payment_Stats instance
167 13
		$this->stats = new Give_Payment_Stats();
168
169
	}
170
171
	/**
172
	 * Registers a new rewrite endpoint for accessing the API
173
	 *
174
	 * @access public
175
	 *
176
	 * @param array $rewrite_rules WordPress Rewrite Rules
177
	 *
178 11
	 * @since  1.1
179 11
	 */
180 11
	public function add_endpoint( $rewrite_rules ) {
0 ignored issues
show
Unused Code introduced by
The parameter $rewrite_rules is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
181
		add_rewrite_endpoint( 'give-api', EP_ALL );
182
	}
183
184
	/**
185
	 * Registers query vars for API access
186
	 *
187
	 * @access public
188
	 * @since  1.1
189
	 *
190
	 * @param array $vars Query vars
191
	 *
192 4
	 * @return string[] $vars New query vars
193
	 */
194 4
	public function query_vars( $vars ) {
195 4
196 4
		$vars[] = 'token';
197 4
		$vars[] = 'key';
198 4
		$vars[] = 'query';
199 4
		$vars[] = 'type';
200 4
		$vars[] = 'form';
201 4
		$vars[] = 'number';
202 4
		$vars[] = 'date';
203 4
		$vars[] = 'startdate';
204 4
		$vars[] = 'enddate';
205 4
		$vars[] = 'donor';
206 4
		$vars[] = 'format';
207 4
		$vars[] = 'id';
208
		$vars[] = 'purchasekey';
209 4
		$vars[] = 'email';
210
211
		return $vars;
212
	}
213
214
	/**
215
	 * Retrieve the API versions
216
	 *
217
	 * @access public
218
	 * @since  1.1
219 13
	 * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

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

Loading history...
220 13
	 */
221
	public function get_versions() {
222
		return $this->versions;
223
	}
224
225
	/**
226
	 * Retrieve the API version that was queried
227
	 *
228
	 * @access public
229
	 * @since  1.1
230
	 * @return string
231
	 */
232
	public function get_queried_version() {
233
		return $this->queried_version;
234
	}
235
236
	/**
237
	 * Retrieves the default version of the API to use
238
	 *
239
	 * @access public
240
	 * @since  1.1
241 1
	 * @return string
242
	 */
243 1
	public function get_default_version() {
244
245 1
		$version = get_option( 'give_default_api_version' );
246 1
247 1
		if ( defined( 'GIVE_API_VERSION' ) ) {
248
			$version = GIVE_API_VERSION;
249
		} elseif ( ! $version ) {
250
			$version = 'v1';
251 1
		}
252
253
		return $version;
254
	}
255
256
	/**
257
	 * Sets the version of the API that was queried.
258
	 *
259
	 * Falls back to the default version if no version is specified
260
	 *
261
	 * @access private
262
	 * @since  1.1
263
	 */
264
	private function set_queried_version() {
265
266
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
267
268
		$version = $wp_query->query_vars['give-api'];
269
270
		if ( strpos( $version, '/' ) ) {
271
272
			$version = explode( '/', $version );
273
			$version = strtolower( $version[0] );
274
275
			$wp_query->query_vars['give-api'] = str_replace( $version . '/', '', $wp_query->query_vars['give-api'] );
276
277
			if ( array_key_exists( $version, $this->versions ) ) {
278
279
				$this->queried_version = $version;
280
281
			} else {
282
283
				$this->is_valid_request = false;
284
				$this->invalid_version();
285
			}
286
		} else {
287
288
			$this->queried_version = $this->get_default_version();
289
290
		}
291
292
	}
293
294
	/**
295
	 * Validate the API request
296
	 *
297
	 * Checks for the user's public key and token against the secret key.
298
	 *
299
	 * @access private
300
	 * @global object $wp_query WordPress Query
301
	 * @uses   Give_API::get_user()
302
	 * @uses   Give_API::invalid_key()
303
	 * @uses   Give_API::invalid_auth()
304
	 * @since  1.1
305
	 * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be false|null?

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

Loading history...
306
	 */
307
	private function validate_request() {
308
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
309
310
		$this->override = false;
311
312
		// Make sure we have both user and api key
313
		if ( ! empty( $wp_query->query_vars['give-api'] ) && ( $wp_query->query_vars['give-api'] !== 'forms' || ! empty( $wp_query->query_vars['token'] ) ) ) {
314
315
			if ( empty( $wp_query->query_vars['token'] ) || empty( $wp_query->query_vars['key'] ) ) {
316
				$this->missing_auth();
317
				return false;
318
			}
319
320
			// Retrieve the user by public API key and ensure they exist
321
			if ( ! ( $user = $this->get_user( $wp_query->query_vars['key'] ) ) ) {
322
323
				$this->invalid_key();
324
				return false;
325
326
			} else {
327
328
				$token  = urldecode( $wp_query->query_vars['token'] );
329
				$secret = $this->get_user_secret_key( $user );
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
					return false;
337
				}
338
339
			}
340
		} elseif ( ! empty( $wp_query->query_vars['give-api'] ) && $wp_query->query_vars['give-api'] === 'forms' ) {
341
			$this->is_valid_request = true;
342
			$wp_query->set( 'key', 'public' );
343
		}
344
	}
345
346
	/**
347
	 * Retrieve the user ID based on the public key provided
348
	 *
349
	 * @access public
350
	 * @since  1.1
351
	 * @global WPDB  $wpdb  Used to query the database using the WordPress
352
	 *                      Database API
353 1
	 *
354 1
	 * @param string $key   Public Key
355
	 *
356 1
	 * @return bool if user ID is found, false otherwise
357
	 */
358
	public function get_user( $key = '' ) {
359
		global $wpdb, $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
360 1
361
		if ( empty( $key ) ) {
362
			$key = urldecode( $wp_query->query_vars['key'] );
363
		}
364 1
365
		if ( empty( $key ) ) {
366 1
			return false;
367 1
		}
368 1
369 1
		$user = Give_Cache::get( md5( 'give_api_user_' . $key ), true );
370
371 1
		if ( false === $user ) {
372 1
			$user = $wpdb->get_var( $wpdb->prepare( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = %s LIMIT 1", $key ) );
373
			Give_Cache::set( md5( 'give_api_user_' . $key ), $user, DAY_IN_SECONDS, true );
374 1
		}
375
376
		if ( $user != null ) {
377
			$this->user_id = $user;
378
379
			return $user;
380 11
		}
381 2
382
		return false;
383 2
	}
384
385
	/**
386
	 * Get user public key.
387 2
	 *
388 2
	 * @param int $user_id
389
	 *
390 2
	 * @return mixed|null|string
391 2
	 */
392 2
	public function get_user_public_key( $user_id = 0 ) {
393 2
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
394
395 11
		if ( empty( $user_id ) ) {
396
			return '';
397
		}
398 11
399 2
		$cache_key       = md5( 'give_api_user_public_key' . $user_id );
400
		$user_public_key = Give_Cache::get( $cache_key, true );
401 2
402
		if ( empty( $user_public_key ) ) {
403
			$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 ) );
404
			Give_Cache::set( $cache_key, $user_public_key, HOUR_IN_SECONDS, true );
405 2
		}
406 2
407
		return $user_public_key;
408 2
	}
409 2
410 2
	/**
411 11
	 * Get user secret key.
412
	 *
413 2
	 * @param int $user_id
414
	 *
415
	 * @return mixed|null|string
416
	 */
417
	public function get_user_secret_key( $user_id = 0 ) {
418
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
419
420
		if ( empty( $user_id ) ) {
421
			return '';
422
		}
423
424
		$cache_key       = md5( 'give_api_user_secret_key' . $user_id );
425
		$user_secret_key = Give_Cache::get( $cache_key, true );
426
427
		if ( empty( $user_secret_key ) ) {
428
			$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 ) );
429
			Give_Cache::set( $cache_key, $user_secret_key, HOUR_IN_SECONDS, true );
430
		}
431
432
		return $user_secret_key;
433
	}
434
435
	/**
436
	 * Displays a missing authentication error if all the parameters are not met.
437
	 * provided
438
	 *
439
	 * @access private
440
	 * @uses   Give_API::output()
441
	 * @since  1.1
442
	 */
443
	private function missing_auth() {
444
		$error          = array();
445
		$error['error'] = esc_html__( 'You must specify both a token and API key.', 'give' );
446
447
		$this->data = $error;
448
		$this->output( 401 );
449
	}
450
451
	/**
452
	 * Displays an authentication failed error if the user failed to provide valid
453
	 * credentials
454
	 *
455
	 * @access private
456
	 * @since  1.1
457
	 * @uses   Give_API::output()
458
	 * @return void
459
	 */
460
	private function invalid_auth() {
461
		$error          = array();
462
		$error['error'] = esc_html__( 'Your request could not be authenticated.', 'give' );
463
464
		$this->data = $error;
465
		$this->output( 403 );
466
	}
467
468
	/**
469
	 * Displays an invalid API key error if the API key provided couldn't be
470
	 * validated
471
	 *
472
	 * @access private
473
	 * @since  1.1
474
	 * @uses   Give_API::output()
475
	 * @return void
476
	 */
477
	private function invalid_key() {
478
		$error          = array();
479
		$error['error'] = esc_html__( 'Invalid API key.', 'give' );
480
481
		$this->data = $error;
482
		$this->output( 403 );
483
	}
484
485
	/**
486
	 * Displays an invalid version error if the version number passed isn't valid
487
	 *
488
	 * @access private
489
	 * @since  1.1
490 3
	 * @uses   Give_API::output()
491
	 * @return void
492 3
	 */
493
	private function invalid_version() {
494
		$error          = array();
495 3
		$error['error'] = esc_html__( 'Invalid API version.', 'give' );
496
497
		$this->data = $error;
498 3
		$this->output( 404 );
499 3
	}
500
501
	/**
502
	 * Listens for the API and then processes the API requests
503
	 *
504
	 * @access public
505
	 * @global $wp_query
506
	 * @since  1.1
507
	 * @return void
508
	 */
509
	public function process_query() {
510
511
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
512
513
		// Start logging how long the request takes for logging
514
		$before = microtime( true );
515
516
		// Check for give-api var. Get out if not present
517
		if ( empty( $wp_query->query_vars['give-api'] ) ) {
518
			return;
519
		}
520
521
		// Determine which version was queried
522
		$this->set_queried_version();
523
524
		// Determine the kind of query
525
		$this->set_query_mode();
526
527
		// Check for a valid user and set errors if necessary
528
		$this->validate_request();
529
530
		// Only proceed if no errors have been noted
531
		if ( ! $this->is_valid_request ) {
532
			return;
533
		}
534
535
		if ( ! defined( 'GIVE_DOING_API' ) ) {
536
			define( 'GIVE_DOING_API', true );
537
		}
538
539
		$data         = array();
540
		$this->routes = new $this->versions[$this->get_queried_version()];
541
		$this->routes->validate_request();
542
543
		switch ( $this->endpoint ) :
544
545
			case 'stats' :
546
547
				$data = $this->routes->get_stats( array(
548
					'type'      => isset( $wp_query->query_vars['type'] ) ? $wp_query->query_vars['type'] : null,
549
					'form'      => isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null,
550
					'date'      => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null,
551
					'startdate' => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null,
552
					'enddate'   => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null,
553
				) );
554
555
				break;
556
557
			case 'forms' :
558
559
				$form = isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null;
560
561
				$data = $this->routes->get_forms( $form );
562
563
				break;
564
565
			case 'donors' :
566
567
				$donor = isset( $wp_query->query_vars['donor'] ) ? $wp_query->query_vars['donor'] : null;
568
569
				$data = $this->routes->get_donors( $donor );
570
571
				break;
572
573
			case 'donations' :
574
575
				/**
576
				 *  Call to get recent donations
577
				 *
578
				 * @params text date | today, yesterday or range
579
				 * @params date startdate | required when date = range and format to be YYYYMMDD (i.e. 20170524)
580
				 * @params date enddate | required when date = range and format to be YYYYMMDD (i.e. 20170524)
581
				 */
582
				$data = $this->routes->get_recent_donations( array(
583
					'date'      => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null,
584
					'startdate' => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null,
585
					'enddate'   => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null,
586
				) );
587
588
				break;
589
590
		endswitch;
591
592
		// Allow extensions to setup their own return data
593
		$this->data = apply_filters( 'give_api_output_data', $data, $this->endpoint, $this );
594
595
		$after                       = microtime( true );
596
		$request_time                = ( $after - $before );
597
		$this->data['request_speed'] = $request_time;
598
599
		// Log this API request, if enabled. We log it here because we have access to errors.
600
		$this->log_request( $this->data );
601
602
		// Send out data to the output function
603
		$this->output();
604
	}
605
606
	/**
607
	 * Returns the API endpoint requested
608
	 *
609
	 * @access public
610
	 * @since  1.1
611
	 * @return string $query Query mode
612
	 */
613
	public function get_query_mode() {
614
615
		return $this->endpoint;
616
	}
617
618
	/**
619
	 * Determines the kind of query requested and also ensure it is a valid query
620
	 *
621
	 * @access public
622
	 * @since  1.1
623
	 * @global $wp_query
624
	 */
625
	public function set_query_mode() {
626
627
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
628
629
		// Whitelist our query options
630
		$accepted = apply_filters( 'give_api_valid_query_modes', array(
631
			'stats',
632 11
			'forms',
633 11
			'donors',
634
			'donations',
635 11
		) );
636
637
		$query = isset( $wp_query->query_vars['give-api'] ) ? $wp_query->query_vars['give-api'] : null;
638
		$query = str_replace( $this->queried_version . '/', '', $query );
639
640
		$error = array();
641
642
		// Make sure our query is valid
643
		if ( ! in_array( $query, $accepted ) ) {
644
			$error['error'] = esc_html__( 'Invalid query.', 'give' );
645
646
			$this->data = $error;
647 11
			// 400 is Bad Request
648 11
			$this->output( 400 );
649
		}
650 11
651
		$this->endpoint = $query;
652 11
	}
653
654
	/**
655
	 * Get page number
656 11
	 *
657
	 * @access public
658
	 * @since  1.1
659
	 * @global $wp_query
660
	 * @return int $wp_query->query_vars['page'] if page number returned (default: 1)
661
	 */
662
	public function get_paged() {
663
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
664
665
		return isset( $wp_query->query_vars['page'] ) ? $wp_query->query_vars['page'] : 1;
666
	}
667
668
669
	/**
670
	 * Number of results to display per page
671
	 *
672
	 * @access public
673
	 * @since  1.1
674
	 * @global $wp_query
675
	 * @return int $per_page Results to display per page (default: 10)
676
	 */
677
	public function per_page() {
678
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
679
680
		$per_page = isset( $wp_query->query_vars['number'] ) ? $wp_query->query_vars['number'] : 10;
681
682
		if ( $per_page < 0 && $this->get_query_mode() == 'donors' ) {
683
			$per_page = 99999999;
684
		} // End if().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
685
686
		return apply_filters( 'give_api_results_per_page', $per_page );
687
	}
688
689
	/**
690
	 * Sets up the dates used to retrieve earnings/donations
691
	 *
692
	 * @access public
693
	 * @since  1.2
694
	 *
695
	 * @param array $args Arguments to override defaults
696
	 *
697
	 * @return array $dates
698
	 */
699
	public function get_dates( $args = array() ) {
700
		$dates = array();
701
702
		$defaults = array(
703
			'type'      => '',
704
			'form'      => null,
705
			'date'      => null,
706
			'startdate' => null,
707
			'enddate'   => null,
708
		);
709
710
		$args = wp_parse_args( $args, $defaults );
711
712
		$current_time = current_time( 'timestamp' );
713
714
		if ( 'range' === $args['date'] ) {
715
			$startdate          = strtotime( $args['startdate'] );
716
			$enddate            = strtotime( $args['enddate'] );
717
			$dates['day_start'] = date( 'd', $startdate );
718
			$dates['day_end']   = date( 'd', $enddate );
719
			$dates['m_start']   = date( 'n', $startdate );
720
			$dates['m_end']     = date( 'n', $enddate );
721
			$dates['year']      = date( 'Y', $startdate );
722
			$dates['year_end']  = date( 'Y', $enddate );
723
		} else {
724
			// Modify dates based on predefined ranges
725
			switch ( $args['date'] ) :
726
727
				case 'this_month' :
728
					$dates['day']     = null;
729
					$dates['m_start'] = date( 'n', $current_time );
730
					$dates['m_end']   = date( 'n', $current_time );
731
					$dates['year']    = date( 'Y', $current_time );
732
					break;
733
734
				case 'last_month' :
735
					$dates['day']     = null;
736
					$dates['m_start'] = date( 'n', $current_time ) == 1 ? 12 : date( 'n', $current_time ) - 1;
737
					$dates['m_end']   = $dates['m_start'];
738
					$dates['year']    = date( 'n', $current_time ) == 1 ? date( 'Y', $current_time ) - 1 : date( 'Y', $current_time );
739
					break;
740
741
				case 'today' :
742
					$dates['day']     = date( 'd', $current_time );
743
					$dates['m_start'] = date( 'n', $current_time );
744
					$dates['m_end']   = date( 'n', $current_time );
745
					$dates['year']    = date( 'Y', $current_time );
746
					break;
747
748
				case 'yesterday' :
749
750
					$year  = date( 'Y', $current_time );
751
					$month = date( 'n', $current_time );
752
					$day   = date( 'd', $current_time );
753
754
					if ( $month == 1 && $day == 1 ) {
755
756
						$year  -= 1;
757
						$month = 12;
758
						$day   = cal_days_in_month( CAL_GREGORIAN, $month, $year );
759
760
					} elseif ( $month > 1 && $day == 1 ) {
761
762
						$month -= 1;
763
						$day   = cal_days_in_month( CAL_GREGORIAN, $month, $year );
764
765
					} else {
766
767
						$day -= 1;
768
769
					}
770
771
					$dates['day']     = $day;
772
					$dates['m_start'] = $month;
773
					$dates['m_end']   = $month;
774
					$dates['year']    = $year;
775
776
					break;
777
778
				case 'this_quarter' :
779
					$month_now = date( 'n', $current_time );
780
781
					$dates['day'] = null;
782
783
					if ( $month_now <= 3 ) {
784
785
						$dates['m_start'] = 1;
786
						$dates['m_end']   = 3;
787
						$dates['year']    = date( 'Y', $current_time );
788
789
					} elseif ( $month_now <= 6 ) {
790
791
						$dates['m_start'] = 4;
792
						$dates['m_end']   = 6;
793
						$dates['year']    = date( 'Y', $current_time );
794
795
					} elseif ( $month_now <= 9 ) {
796
797
						$dates['m_start'] = 7;
798
						$dates['m_end']   = 9;
799
						$dates['year']    = date( 'Y', $current_time );
800
801
					} else {
802
803
						$dates['m_start'] = 10;
804
						$dates['m_end']   = 12;
805
						$dates['year']    = date( 'Y', $current_time );
806
807
					}
808
					break;
809
810
				case 'last_quarter' :
811
					$month_now = date( 'n', $current_time );
812
813
					$dates['day'] = null;
814
815
					if ( $month_now <= 3 ) {
816
817
						$dates['m_start'] = 10;
818
						$dates['m_end']   = 12;
819
						$dates['year']    = date( 'Y', $current_time ) - 1; // Previous year
820
821
					} elseif ( $month_now <= 6 ) {
822
823
						$dates['m_start'] = 1;
824
						$dates['m_end']   = 3;
825
						$dates['year']    = date( 'Y', $current_time );
826
827
					} elseif ( $month_now <= 9 ) {
828
829
						$dates['m_start'] = 4;
830
						$dates['m_end']   = 6;
831
						$dates['year']    = date( 'Y', $current_time );
832
833
					} else {
834
835
						$dates['m_start'] = 7;
836
						$dates['m_end']   = 9;
837
						$dates['year']    = date( 'Y', $current_time );
838
839
					}
840
					break;
841
842
				case 'this_year' :
843
					$dates['day']     = null;
844
					$dates['m_start'] = null;
845
					$dates['m_end']   = null;
846
					$dates['year']    = date( 'Y', $current_time );
847
					break;
848
849
				case 'last_year' :
850
					$dates['day']     = null;
851
					$dates['m_start'] = null;
852 1
					$dates['m_end']   = null;
853
					$dates['year']    = date( 'Y', $current_time ) - 1;
854 1
					break;
855 1
856 1
			endswitch;
857
		}// End if().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
858
859
		/**
860 1
		 * Returns the filters for the dates used to retrieve earnings.
861
		 *
862 1
		 * @since 1.2
863 1
		 *
864 1
		 * @param array $dates The dates used for retrieving earnings.
865
		 */
866 1
		return apply_filters( 'give_api_stat_dates', $dates );
867
	}
868
869 1
	/**
870
	 * Process Get Donors API Request.
871
	 *
872 1
	 * @access public
873 1
	 * @since  1.1
874 1
	 * @global WPDB $wpdb  Used to query the database using the WordPress Database API.
875
	 *
876 1
	 * @param int   $donor Donor ID
877 1
	 *
878
	 * @return array $donors Multidimensional array of the donors.
879 1
	 */
880
	public function get_donors( $donor = null ) {
881 1
882
		$donors = array();
883 1
		$error  = array();
884 1
		if ( ! user_can( $this->user_id, 'view_give_sensitive_data' ) && ! $this->override ) {
885 1
			return $donors;
886 1
		}
887 1
888 1
		$paged    = $this->get_paged();
889 1
		$per_page = $this->per_page();
890
		$offset   = $per_page * ( $paged - 1 );
891 1
892 1
		if ( is_numeric( $donor ) ) {
893 1
			$field = 'id';
894 1
		} else {
895 1
			$field = 'email';
896 1
		}
897 1
898
		$donor_query = Give()->donors->get_donors( array(
899 1
			'number' => $per_page,
900
			'offset' => $offset,
901 1
			$field   => $donor,
902
		) );
903
		$donor_count = 0;
904 1
905 1
		if ( $donor_query ) {
906 1
907
			foreach ( $donor_query as $donor_obj ) {
908 1
909
				$names      = explode( ' ', $donor_obj->name );
910 1
				$first_name = ! empty( $names[0] ) ? $names[0] : '';
911 1
				$last_name  = '';
912
				if ( ! empty( $names[1] ) ) {
913 1
					unset( $names[0] );
914
					$last_name = implode( ' ', $names );
915 1
				}
916
917 1
				$donors['donors'][ $donor_count ]['info']['user_id']      = '';
918
				$donors['donors'][ $donor_count ]['info']['username']     = '';
919
				$donors['donors'][ $donor_count ]['info']['display_name'] = '';
920
				$donors['donors'][ $donor_count ]['info']['donor_id']     = $donor_obj->id;
921
				$donors['donors'][ $donor_count ]['info']['first_name']   = $first_name;
922
				$donors['donors'][ $donor_count ]['info']['last_name']    = $last_name;
923
				$donors['donors'][ $donor_count ]['info']['email']        = $donor_obj->email;
924
925
				if ( ! empty( $donor_obj->user_id ) ) {
926
927
					$user_data = get_userdata( $donor_obj->user_id );
928
929
					// Donor with registered account.
930
					$donors['donors'][ $donor_count ]['info']['user_id']      = $donor_obj->user_id;
931
					$donors['donors'][ $donor_count ]['info']['username']     = $user_data->user_login;
932
					$donors['donors'][ $donor_count ]['info']['display_name'] = $user_data->display_name;
933
934
				}
935 1
936
				$donors['donors'][ $donor_count ]['stats']['total_donations'] = $donor_obj->purchase_count;
937
				$donors['donors'][ $donor_count ]['stats']['total_spent']     = $donor_obj->purchase_value;
938
939
				$donor_count ++;
940
941
			}
942
		} elseif ( $donor ) {
943
944
			$error['error'] = sprintf( /* translators: %s: donor */
945
				esc_html__( 'Donor %s not found.', 'give' ), $donor );
946
947
			return $error;
948 11
949
		} else {
950 11
951 11
			$error['error'] = esc_html__( 'No donors found.', 'give' );
952
953 11
			return $error;
954 11
955
		}// End if().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
956 11
957 11
		return $donors;
958 11
	}
959 11
960 11
	/**
961 11
	 * Process Get Donation Forms API Request
962
	 *
963 11
	 * @access public
964 11
	 * @since  1.1
965 11
	 *
966 11
	 * @param int $form Give Form ID.
967 11
	 *
968 11
	 * @return array $donors Multidimensional array of the forms.
969 11
	 */
970 11
	public function get_forms( $form = null ) {
971
972
		$forms = array();
973
		$error = array();
974
975
		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...
976
			$forms['forms'] = array();
977
978
			$form_list = get_posts( array(
979
				'post_type'        => 'give_forms',
980
				'posts_per_page'   => $this->per_page(),
981
				'suppress_filters' => true,
982
				'paged'            => $this->get_paged(),
983
			) );
984
985
			if ( $form_list ) {
986
				$i = 0;
987 11
				foreach ( $form_list as $form_info ) {
988
					$forms['forms'][ $i ] = $this->get_form_data( $form_info );
989
					$i ++;
990
				}
991
			}
992
		} else {
993
			if ( get_post_type( $form ) == 'give_forms' ) {
994
				$form_info = get_post( $form );
995
996
				$forms['forms'][0] = $this->get_form_data( $form_info );
997
998
			} else {
999 11
				$error['error'] = sprintf( /* translators: %s: form */
1000
					esc_html__( 'Form %s not found.', 'give' ), $form );
1001 11
1002
				return $error;
1003 11
			}
1004 11
		}
1005 11
1006 11
		return $forms;
1007 11
	}
1008 11
1009 11
	/**
1010 11
	 * Given a give_forms post object, generate the data for the API output
1011 11
	 *
1012
	 * @since  1.1
1013 11
	 *
1014
	 * @param  object $form_info The Give Form's Post Object.
1015
	 *
1016
	 * @return array                Array of post data to return back in the API.
1017 11
	 */
1018
	private function get_form_data( $form_info ) {
1019
1020
		$form = array();
1021 11
1022 11
		$form['info']['id']            = $form_info->ID;
1023 11
		$form['info']['slug']          = $form_info->post_name;
1024 11
		$form['info']['title']         = $form_info->post_title;
1025 11
		$form['info']['create_date']   = $form_info->post_date;
1026 11
		$form['info']['modified_date'] = $form_info->post_modified;
1027
		$form['info']['status']        = $form_info->post_status;
1028 11
		$form['info']['link']          = html_entity_decode( $form_info->guid );
1029 11
		$form['info']['content']       = give_get_meta( $form_info->ID, '_give_form_content', true );
1030 11
		$form['info']['thumbnail']     = wp_get_attachment_url( get_post_thumbnail_id( $form_info->ID ) );
1031 11
1032
		if ( give_is_setting_enabled( give_get_option( 'categories', 'disabled' ) ) ) {
1033 11
			$form['info']['category'] = get_the_terms( $form_info, 'give_forms_category' );
1034 11
			$form['info']['tags']     = get_the_terms( $form_info, 'give_forms_tag' );
1035
		}
1036 11
		if ( give_is_setting_enabled( give_get_option( 'tags', 'disabled' ) ) ) {
1037 11
			$form['info']['tags'] = get_the_terms( $form_info, 'give_forms_tag' );
1038
		}
1039
1040
		// Check whether any goal is to be achieved for the donation form.
1041 11
		$goal_option = give_get_meta( $form_info->ID, '_give_goal_option', true );
1042
		$goal_amount = give_get_meta( $form_info->ID, '_give_set_goal', true );
1043
		if ( give_is_setting_enabled( $goal_option ) && $goal_amount ) {
1044 11
			$total_income = give_get_form_earnings_stats( $form_info->ID );
1045
			$goal_percentage_completed = ( $total_income < $goal_amount ) ? round( ( $total_income / $goal_amount ) * 100, 2 ) : 100;
1046 11
			$form['goal']['amount']               = isset( $goal_amount ) ? $goal_amount : '';
1047
			$form['goal']['percentage_completed'] = isset( $goal_percentage_completed ) ? $goal_percentage_completed : '';
1048 11
		}
1049
1050
		if ( user_can( $this->user_id, 'view_give_reports' ) || $this->override ) {
1051
			$form['stats']['total']['donations']           = give_get_form_sales_stats( $form_info->ID );
1052
			$form['stats']['total']['earnings']            = give_get_form_earnings_stats( $form_info->ID );
1053
			$form['stats']['monthly_average']['donations'] = give_get_average_monthly_form_sales( $form_info->ID );
1054
			$form['stats']['monthly_average']['earnings']  = give_get_average_monthly_form_earnings( $form_info->ID );
1055
		}
1056
1057
		$counter = 0;
1058
		if ( give_has_variable_prices( $form_info->ID ) ) {
1059
			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...
1060
				$counter ++;
1061
				// multi-level item
1062
				$level                                     = isset( $price['_give_text'] ) ? $price['_give_text'] : 'level-' . $counter;
1063
				$form['pricing'][ sanitize_key( $level ) ] = $price['_give_amount'];
1064
1065
			}
1066
		} else {
1067
			$form['pricing']['amount'] = give_get_form_price( $form_info->ID );
1068
		}
1069
1070
		if ( user_can( $this->user_id, 'view_give_sensitive_data' ) || $this->override ) {
1071
1072
			/**
1073
			 * Fires when generating API sensitive data.
1074
			 *
1075
			 * @since 1.1
1076
			 */
1077
			do_action( 'give_api_sensitive_data' );
1078
1079
		}
1080
1081
		return apply_filters( 'give_api_forms_form', $form );
1082
1083
	}
1084
1085
	/**
1086
	 * Process Get Stats API Request
1087
	 *
1088
	 * @since 1.1
1089
	 *
1090
	 * @global WPDB $wpdb Used to query the database using the WordPress.
1091
	 *
1092
	 * @param array $args Arguments provided by API Request.
1093
	 *
1094
	 * @return array
0 ignored issues
show
Documentation introduced by
Should the return type not be array|null?

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

Loading history...
1095
	 */
1096
	public function get_stats( $args = array() ) {
1097
		$defaults = array(
1098
			'type'      => null,
1099
			'form'      => null,
1100
			'date'      => null,
1101
			'startdate' => null,
1102
			'enddate'   => null,
1103
		);
1104
1105
		$args = wp_parse_args( $args, $defaults );
1106
1107
		$dates = $this->get_dates( $args );
1108
1109
		$stats    = array();
1110
		$earnings = array(
1111
			'earnings' => array(),
1112
		);
1113
		$sales    = array(
1114
			'donations' => array(),
1115
		);
1116
		$error    = array();
1117
1118
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1119
			return $stats;
1120
		}
1121
1122
		if ( $args['type'] == 'donations' ) {
1123
1124
			if ( $args['form'] == null ) {
1125
				if ( $args['date'] == null ) {
1126
					$sales = $this->get_default_sales_stats();
1127
				} elseif ( $args['date'] === 'range' ) {
1128
					// Return donations for a date range.
1129
					// Ensure the end date is later than the start date.
1130
					if ( $args['enddate'] < $args['startdate'] ) {
1131
						$error['error'] = esc_html__( 'The end date must be later than the start date.', 'give' );
1132
					}
1133
1134
					// Ensure both the start and end date are specified
1135
					if ( empty( $args['startdate'] ) || empty( $args['enddate'] ) ) {
1136
						$error['error'] = esc_html__( 'Invalid or no date range specified.', 'give' );
1137
					}
1138
1139
					$total = 0;
1140
1141
					// Loop through the years
1142
					$y = $dates['year'];
1143
					while ( $y <= $dates['year_end'] ) :
1144
1145
						if ( $dates['year'] == $dates['year_end'] ) {
1146
							$month_start = $dates['m_start'];
1147
							$month_end   = $dates['m_end'];
1148
						} elseif ( $y == $dates['year'] && $dates['year_end'] > $dates['year'] ) {
1149
							$month_start = $dates['m_start'];
1150
							$month_end   = 12;
1151
						} elseif ( $y == $dates['year_end'] ) {
1152
							$month_start = 1;
1153
							$month_end   = $dates['m_end'];
1154
						} else {
1155
							$month_start = 1;
1156
							$month_end   = 12;
1157
						}
1158
1159
						$i = $month_start;
1160
						while ( $i <= $month_end ) :
1161
1162
							if ( $i == $dates['m_start'] ) {
1163
								$d = $dates['day_start'];
1164
							} else {
1165
								$d = 1;
1166
							}
1167
1168
							if ( $i == $dates['m_end'] ) {
1169
								$num_of_days = $dates['day_end'];
1170
							} else {
1171
								$num_of_days = cal_days_in_month( CAL_GREGORIAN, $i, $y );
1172
							}
1173
1174
							while ( $d <= $num_of_days ) :
1175
								$sale_count = give_get_sales_by_date( $d, $i, $y );
1176
								$date_key   = date( 'Ymd', strtotime( $y . '/' . $i . '/' . $d ) );
1177
								if ( ! isset( $sales['sales'][ $date_key ] ) ) {
1178
									$sales['sales'][ $date_key ] = 0;
1179
								}
1180
								$sales['sales'][ $date_key ] += $sale_count;
1181
								$total                       += $sale_count;
1182
								$d ++;
1183
							endwhile;
1184
							$i ++;
1185
						endwhile;
1186
1187
						$y ++;
1188
					endwhile;
1189
1190
					$sales['totals'] = $total;
1191
				} else {
1192
					if ( $args['date'] == 'this_quarter' || $args['date'] == 'last_quarter' ) {
1193
						$sales_count = 0;
1194
1195
						// Loop through the months
1196
						$month = $dates['m_start'];
1197
1198
						while ( $month <= $dates['m_end'] ) :
1199
							$sales_count += give_get_sales_by_date( null, $month, $dates['year'] );
1200
							$month ++;
1201
						endwhile;
1202
1203
						$sales['donations'][ $args['date'] ] = $sales_count;
1204
					} else {
1205
						$sales['donations'][ $args['date'] ] = give_get_sales_by_date( $dates['day'], $dates['m_start'], $dates['year'] );
1206
					}
1207
				}// End if().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1208
			} elseif ( $args['form'] == 'all' ) {
1209
				$forms = get_posts( array(
1210
					'post_type' => 'give_forms',
1211
					'nopaging'  => true,
1212
				) );
1213
				$i     = 0;
1214
				foreach ( $forms as $form_info ) {
1215
					$sales['donations'][ $i ] = array(
1216
						$form_info->post_name => give_get_form_sales_stats( $form_info->ID ),
1217
					);
1218
					$i ++;
1219
				}
1220
			} else {
1221
				if ( get_post_type( $args['form'] ) == 'give_forms' ) {
1222
					$form_info             = get_post( $args['form'] );
1223
					$sales['donations'][0] = array(
1224
						$form_info->post_name => give_get_form_sales_stats( $args['form'] ),
1225
					);
1226
				} else {
1227
					$error['error'] = sprintf( /* translators: %s: form */
1228
						esc_html__( 'Form %s not found.', 'give' ), $args['form'] );
1229
				}
1230
			}// End if().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1231
1232
			if ( ! empty( $error ) ) {
1233
				return $error;
1234
			}
1235
1236
			return $sales;
1237
1238
		} elseif ( $args['type'] == 'earnings' ) {
1239
			if ( $args['form'] == null ) {
1240
				if ( $args['date'] == null ) {
1241
					$earnings = $this->get_default_earnings_stats();
1242
				} elseif ( $args['date'] === 'range' ) {
1243
					// Return sales for a date range
1244
					// Ensure the end date is later than the start date
1245
					if ( $args['enddate'] < $args['startdate'] ) {
1246
						$error['error'] = esc_html__( 'The end date must be later than the start date.', 'give' );
1247
					}
1248
1249
					// Ensure both the start and end date are specified
1250
					if ( empty( $args['startdate'] ) || empty( $args['enddate'] ) ) {
1251
						$error['error'] = esc_html__( 'Invalid or no date range specified.', 'give' );
1252
					}
1253
1254
					$total = (float) 0.00;
1255
1256
					// Loop through the years
1257
					$y = $dates['year'];
1258
					if ( ! isset( $earnings['earnings'] ) ) {
1259
						$earnings['earnings'] = array();
1260
					}
1261
					while ( $y <= $dates['year_end'] ) :
1262
1263
						if ( $dates['year'] == $dates['year_end'] ) {
1264
							$month_start = $dates['m_start'];
1265
							$month_end   = $dates['m_end'];
1266
						} elseif ( $y == $dates['year'] && $dates['year_end'] > $dates['year'] ) {
1267
							$month_start = $dates['m_start'];
1268
							$month_end   = 12;
1269
						} elseif ( $y == $dates['year_end'] ) {
1270
							$month_start = 1;
1271
							$month_end   = $dates['m_end'];
1272
						} else {
1273
							$month_start = 1;
1274
							$month_end   = 12;
1275
						}
1276
1277
						$i = $month_start;
1278
						while ( $i <= $month_end ) :
1279
1280
							if ( $i == $dates['m_start'] ) {
1281
								$d = $dates['day_start'];
1282
							} else {
1283
								$d = 1;
1284
							}
1285
1286
							if ( $i == $dates['m_end'] ) {
1287
								$num_of_days = $dates['day_end'];
1288
							} else {
1289
								$num_of_days = cal_days_in_month( CAL_GREGORIAN, $i, $y );
1290
							}
1291
1292
							while ( $d <= $num_of_days ) :
1293
								$earnings_stat = give_get_earnings_by_date( $d, $i, $y );
1294
								$date_key      = date( 'Ymd', strtotime( $y . '/' . $i . '/' . $d ) );
1295
								if ( ! isset( $earnings['earnings'][ $date_key ] ) ) {
1296
									$earnings['earnings'][ $date_key ] = 0;
1297
								}
1298
								$earnings['earnings'][ $date_key ] += $earnings_stat;
1299
								$total                             += $earnings_stat;
1300
								$d ++;
1301
							endwhile;
1302
1303
							$i ++;
1304
						endwhile;
1305
1306
						$y ++;
1307
					endwhile;
1308
1309
					$earnings['totals'] = $total;
1310
				} else {
1311
					if ( $args['date'] == 'this_quarter' || $args['date'] == 'last_quarter' ) {
1312
						$earnings_count = (float) 0.00;
1313
1314
						// Loop through the months
1315
						$month = $dates['m_start'];
1316
1317
						while ( $month <= $dates['m_end'] ) :
1318
							$earnings_count += give_get_earnings_by_date( null, $month, $dates['year'] );
1319
							$month ++;
1320
						endwhile;
1321
1322
						$earnings['earnings'][ $args['date'] ] = $earnings_count;
1323
					} else {
1324
						$earnings['earnings'][ $args['date'] ] = give_get_earnings_by_date( $dates['day'], $dates['m_start'], $dates['year'] );
1325
					}
1326
				}// End if().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1327
			} elseif ( $args['form'] == 'all' ) {
1328
				$forms = get_posts( array(
1329
					'post_type' => 'give_forms',
1330
					'nopaging'  => true,
1331
				) );
1332
1333
				$i = 0;
1334
				foreach ( $forms as $form_info ) {
1335
					$earnings['earnings'][ $i ] = array(
1336
						$form_info->post_name => give_get_form_earnings_stats( $form_info->ID ),
1337
					);
1338
					$i ++;
1339 11
				}
1340 11
			} else {
1341
				if ( get_post_type( $args['form'] ) == 'give_forms' ) {
1342 11
					$form_info               = get_post( $args['form'] );
1343
					$earnings['earnings'][0] = array(
1344 11
						$form_info->post_name => give_get_form_earnings_stats( $args['form'] ),
1345
					);
1346
				} else {
1347
					$error['error'] = sprintf( /* translators: %s: form */
1348 11
						esc_html__( 'Form %s not found.', 'give' ), $args['form'] );
1349
				}
1350
			}// End if().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1351 11
1352
			if ( ! empty( $error ) ) {
1353
				return $error;
1354 11
			}
1355
1356
			return $earnings;
1357
		} elseif ( $args['type'] == 'donors' ) {
1358
			$donors                             = new Give_DB_Donors();
1359
			$stats['donations']['total_donors'] = $donors->count();
1360
1361
			return $stats;
1362
1363
		} elseif ( empty( $args['type'] ) ) {
1364
			$stats = array_merge( $stats, $this->get_default_sales_stats() );
1365
			$stats = array_merge( $stats, $this->get_default_earnings_stats() );
1366 11
1367 11
			return array(
1368 11
				'stats' => $stats,
1369
			);
1370 11
		}// End if().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1371 11
	}
1372
1373 11
	/**
1374 11
	 * Retrieves Recent Donations
1375 11
	 *
1376
	 * @access public
1377 11
	 * @since  1.1
1378 11
	 *
1379 11
	 * @param $args array
1380 11
	 *
1381 11
	 * @return array
1382
	 */
1383
	public function get_recent_donations( $args = array() ) {
1384
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1385 11
1386 11
		$defaults = array(
1387
			'date'      => null,
1388 11
			'startdate' => null,
1389 11
			'enddate'   => null,
1390
		);
1391 11
1392 11
		$args = wp_parse_args( $args, $defaults );
1393 11
1394 11
		$sales = array();
1395 11
1396 11
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1397 11
			return $sales;
1398 11
		}
1399 11
1400 11
		if ( isset( $wp_query->query_vars['id'] ) ) {
1401
			$query   = array();
1402 11
			$query[] = new Give_Payment( $wp_query->query_vars['id'] );
1403 11
		} elseif ( isset( $wp_query->query_vars['purchasekey'] ) ) {
1404 11
			$query   = array();
1405
			$query[] = give_get_payment_by( 'key', $wp_query->query_vars['purchasekey'] );
1406 11
		} elseif ( isset( $wp_query->query_vars['email'] ) ) {
1407 11
			$args  = array(
1408 11
				'fields'     => 'ids',
1409
				'meta_key'   => '_give_payment_user_email',
1410 11
				'meta_value' => $wp_query->query_vars['email'],
1411 11
				'number'     => $this->per_page(),
1412 11
				'page'       => $this->get_paged(),
1413 11
				'status'     => 'publish',
1414 11
			);
1415 11
			$query = give_get_payments( $args );
1416
		} elseif ( isset( $wp_query->query_vars['date'] ) ) {
1417 11
1418 11
			$current_time = current_time( 'timestamp' );
1419
			$dates        = $this->get_dates( $args );
1420
1421 11
			/**
1422
			 *  Switch case for date query argument
1423
			 *
1424 11
			 * @since  1.8.8
1425 11
			 *
1426 11
			 * @params text date | today, yesterday or range
1427 11
			 * @params date startdate | required when date = range and format to be YYYYMMDD (i.e. 20170524)
1428 11
			 * @params date enddate | required when date = range and format to be YYYYMMDD (i.e. 20170524)
1429 11
			 */
1430 11
			switch ( $wp_query->query_vars['date'] ) {
1431 11
1432
				case 'today':
1433
1434 11
					// Set and Format Start and End Date to be date of today.
1435 11
					$start_date = $end_date = date( 'Y/m/d', $current_time );
1436
1437
					break;
1438 11
1439
				case 'yesterday':
1440 11
1441
					// Set and Format Start and End Date to be date of yesterday.
1442 11
					$start_date = $end_date = date( 'Y/m', $current_time ) . '/' . ( date( 'd', $current_time ) - 1 );
1443 11
1444 11
					break;
1445
1446 11
				case 'range':
1447
1448
					// Format Start Date and End Date for filtering payment based on date range.
1449
					$start_date = $dates['year'] . '/' . $dates['m_start'] . '/' . $dates['day_start'];
1450
					$end_date   = $dates['year_end'] . '/' . $dates['m_end'] . '/' . $dates['day_end'];
1451
1452
					break;
1453
1454
			}
1455
1456
			$args = array(
1457
				'fields'     => 'ids',
1458
				'start_date' => $start_date,
0 ignored issues
show
Bug introduced by
The variable $start_date does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1459
				'end_date'   => $end_date,
0 ignored issues
show
Bug introduced by
The variable $end_date does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1460
				'number'     => $this->per_page(),
1461
				'page'       => $this->get_paged(),
1462
				'status'     => 'publish',
1463
			);
1464
1465
			$query = give_get_payments( $args );
1466
		} else {
1467
			$args  = array(
1468
				'fields' => 'ids',
1469
				'number' => $this->per_page(),
1470
				'page'   => $this->get_paged(),
1471
				'status' => 'publish',
1472
			);
1473
			$query = give_get_payments( $args );
1474
		}// End if().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1475
		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...
1476
			$i = 0;
1477
			foreach ( $query as $payment ) {
1478
1479
				if ( is_numeric( $payment ) ) {
1480
					$payment      = new Give_Payment( $payment );
1481
					$payment_meta = $payment->get_meta();
1482
					$user_info    = $payment->user_info;
1483
				} else {
1484
					continue;
1485
				}
1486
1487
				$payment_meta = $payment->get_meta();
1488
				$user_info    = $payment->user_info;
1489
1490
				$first_name = isset( $user_info['first_name'] ) ? $user_info['first_name'] : '';
1491
				$last_name  = isset( $user_info['last_name'] ) ? $user_info['last_name'] : '';
1492
1493
				$sales['donations'][ $i ]['ID']             = $payment->number;
1494
				$sales['donations'][ $i ]['transaction_id'] = $payment->transaction_id;
1495
				$sales['donations'][ $i ]['key']            = $payment->key;
1496
				$sales['donations'][ $i ]['total']          = $payment->total;
1497
				$sales['donations'][ $i ]['gateway']        = $payment->gateway;
1498
				$sales['donations'][ $i ]['name']           = $first_name . ' ' . $last_name;
1499
				$sales['donations'][ $i ]['fname']          = $first_name;
1500
				$sales['donations'][ $i ]['lname']          = $last_name;
1501
				$sales['donations'][ $i ]['email']          = $payment->email;
1502
				$sales['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
				$sales['donations'][ $i ]['form']['id']    = $form_id;
1509
				$sales['donations'][ $i ]['form']['name']  = get_the_title( $payment_meta['form_id'] );
1510
				$sales['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
						$sales['donations'][ $i ]['form']['price_name'] = $price_name;
1516
						$sales['donations'][ $i ]['form']['price_id']   = $price_id;
1517
						$sales['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
					$sales['donations'][ $i ]['payment_meta'][ $meta_key ] = $meta_value;
1541
1542
				}
1543
1544
				$i ++;
1545
			}// End foreach().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1546
		}// End if().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1547
1548
		return apply_filters( 'give_api_donations_endpoint', $sales );
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;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
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;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1592
1593
		/**
1594
		 * @var WP_Query $wp_query
1595
		 */
1596
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
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();
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
			$user = get_userdata( $user->ID );
1737
			?>
1738
			<table class="form-table">
1739
				<tbody>
1740
				<tr>
1741
					<th>
1742
						<?php esc_html_e( 'Give API Keys', 'give' ); ?>
1743
					</th>
1744
					<td>
1745
						<?php
1746
						$public_key = $this->get_user_public_key( $user->ID );
1747
						$secret_key = $this->get_user_secret_key( $user->ID );
1748
						?>
1749
						<?php if ( empty( $user->give_user_public_key ) ) { ?>
1750
							<input name="give_set_api_key" type="checkbox" id="give_set_api_key" value="0"/>
1751
							<span class="description"><?php esc_html_e( 'Generate API Key', 'give' ); ?></span>
1752
						<?php } else { ?>
1753
							<strong style="display:inline-block; width: 125px;"><?php esc_html_e( 'Public key:', 'give' ); ?>
1754
								&nbsp;</strong>
1755
							<input type="text" disabled="disabled" class="regular-text" id="publickey" value="<?php echo esc_attr( $public_key ); ?>"/>
1756
							<br/>
1757
							<strong style="display:inline-block; width: 125px;"><?php esc_html_e( 'Secret key:', 'give' ); ?>
1758
								&nbsp;</strong>
1759
							<input type="text" disabled="disabled" class="regular-text" id="privatekey" value="<?php echo esc_attr( $secret_key ); ?>"/>
1760
							<br/>
1761
							<strong style="display:inline-block; width: 125px;"><?php esc_html_e( 'Token:', 'give' ); ?>
1762
								&nbsp;</strong>
1763
							<input type="text" disabled="disabled" class="regular-text" id="token" value="<?php echo esc_attr( $this->get_token( $user->ID ) ); ?>"/>
1764
							<br/>
1765
							<input name="give_set_api_key" type="checkbox" id="give_set_api_key" value="0"/>
1766
							<span class="description"><label for="give_set_api_key"><?php esc_html_e( 'Revoke API Keys', 'give' ); ?></label></span>
1767
						<?php } ?>
1768
					</td>
1769
				</tr>
1770
				</tbody>
1771
			</table>
1772
		<?php }// End if().
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1773
	}
1774
1775
	/**
1776
	 * Process an API key generation/revocation
1777
	 *
1778
	 * @access public
1779
	 * @since  1.1
1780
	 *
1781
	 * @param array $args
1782
	 *
1783
	 * @return void
1784
	 */
1785
	public function process_api_key( $args ) {
1786
1787
		if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'give-api-nonce' ) ) {
1788
			wp_die( __( 'Nonce verification failed.', 'give' ), __( 'Error', 'give' ), array(
1789
				'response' => 403,
1790
			) );
1791
		}
1792
1793
		if ( empty( $args['user_id'] ) ) {
1794
			wp_die( __( 'User ID Required.', 'give' ), __( 'Error', 'give' ), array(
1795
				'response' => 401,
1796
			) );
1797 2
		}
1798 2
1799
		if ( is_numeric( $args['user_id'] ) ) {
1800
			$user_id = isset( $args['user_id'] ) ? absint( $args['user_id'] ) : get_current_user_id();
1801
		} else {
1802
			$userdata = get_user_by( 'login', $args['user_id'] );
1803
			$user_id  = $userdata->ID;
1804
		}
1805
		$process = isset( $args['give_api_process'] ) ? strtolower( $args['give_api_process'] ) : false;
1806
1807
		if ( $user_id == get_current_user_id() && ! give_get_option( 'allow_user_api_keys' ) && ! current_user_can( 'manage_give_settings' ) ) {
1808
			wp_die( sprintf( /* translators: %s: process */
1809
				esc_html__( 'You do not have permission to %s API keys for this user.', 'give' ), $process ), esc_html__( 'Error', 'give' ), array(
1810
				'response' => 403,
1811
			) );
1812
		} elseif ( ! current_user_can( 'manage_give_settings' ) ) {
1813
			wp_die( sprintf( /* translators: %s: process */
1814 2
				esc_html__( 'You do not have permission to %s API keys for this user.', 'give' ), $process ), esc_html__( 'Error', 'give' ), array(
1815 2
				'response' => 403,
1816
			) );
1817 2
		}
1818
1819 2
		switch ( $process ) {
1820 2
			case 'generate':
1821
				if ( $this->generate_api_key( $user_id ) ) {
1822 2
					Give_Cache::delete( Give_Cache::get_key( 'give_total_api_keys' ) );
1823 2
					wp_redirect( add_query_arg( 'give-message', 'api-key-generated', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1824 2
					exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method process_api_key() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1825
				} else {
1826 2
					wp_redirect( add_query_arg( 'give-message', 'api-key-failed', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1827 2
					exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method process_api_key() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1828 2
				}
1829
				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...
1830
			case 'regenerate':
1831 2
				$this->generate_api_key( $user_id, true );
1832 2
				Give_Cache::delete( Give_Cache::get_key( 'give_total_api_keys' ) );
1833
				wp_redirect( add_query_arg( 'give-message', 'api-key-regenerated', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1834
				exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method process_api_key() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1835
				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...
1836
			case 'revoke':
1837
				$this->revoke_api_key( $user_id );
1838
				Give_Cache::delete( Give_Cache::get_key( 'give_total_api_keys' ) );
1839
				wp_redirect( add_query_arg( 'give-message', 'api-key-revoked', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1840
				exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method process_api_key() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1841
				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...
1842
			default;
1843
				break;
1844 2
		}
1845 2
	}
1846 2
1847
	/**
1848 2
	 * Generate new API keys for a user
1849
	 *
1850
	 * @access public
1851
	 * @since  1.1
1852
	 *
1853
	 * @param int     $user_id    User ID the key is being generated for
1854
	 * @param boolean $regenerate Regenerate the key for the user
1855
	 *
1856
	 * @return boolean True if (re)generated succesfully, false otherwise.
1857
	 */
1858
	public function generate_api_key( $user_id = 0, $regenerate = false ) {
1859
1860
		if ( empty( $user_id ) ) {
1861 2
			return false;
1862 2
		}
1863 2
1864
		$user = get_userdata( $user_id );
1865 2
1866
		if ( ! $user ) {
1867
			return false;
1868
		}
1869
1870
		$public_key = $this->get_user_public_key( $user_id );
1871
		$secret_key = $this->get_user_secret_key( $user_id );
1872
1873
		if ( empty( $public_key ) || $regenerate == true ) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

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

Loading history...
1874
			$new_public_key = $this->generate_public_key( $user->user_email );
1875
			$new_secret_key = $this->generate_private_key( $user->ID );
1876
		} else {
1877
			return false;
1878
		}
1879
1880
		if ( $regenerate == true ) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

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

Loading history...
1881
			$this->revoke_api_key( $user->ID );
1882
		}
1883
1884
		update_user_meta( $user_id, $new_public_key, 'give_user_public_key' );
1885
		update_user_meta( $user_id, $new_secret_key, 'give_user_secret_key' );
1886
1887
		return true;
1888
	}
1889
1890
	/**
1891
	 * Revoke a users API keys
1892
	 *
1893
	 * @access public
1894
	 * @since  1.1
1895
	 *
1896
	 * @param int $user_id User ID of user to revoke key for
1897
	 *
1898
	 * @return bool
1899
	 */
1900
	public function revoke_api_key( $user_id = 0 ) {
1901
1902
		if ( empty( $user_id ) ) {
1903
			return false;
1904
		}
1905
1906
		$user = get_userdata( $user_id );
1907
1908
		if ( ! $user ) {
1909
			return false;
1910
		}
1911
1912
		$public_key = $this->get_user_public_key( $user_id );
1913
		$secret_key = $this->get_user_secret_key( $user_id );
1914
		if ( ! empty( $public_key ) ) {
1915
			Give_Cache::delete( Give_Cache::get_key( md5( 'give_api_user_' . $public_key ) ) );
1916
			Give_Cache::delete( Give_Cache::get_key( md5( 'give_api_user_public_key' . $user_id ) ) );
1917
			Give_Cache::delete( Give_Cache::get_key( md5( 'give_api_user_secret_key' . $user_id ) ) );
1918
			delete_user_meta( $user_id, $public_key );
1919
			delete_user_meta( $user_id, $secret_key );
1920
		} else {
1921
			return false;
1922
		}
1923
1924
		return true;
1925
	}
1926
1927
	public function get_version() {
1928
		return self::VERSION;
1929
	}
1930
1931
1932
	/**
1933
	 * Generate and Save API key
1934 56
	 *
1935
	 * Generates the key requested by user_key_field and stores it in the database
1936 56
	 *
1937 56
	 * @access public
1938
	 * @since  1.1
1939
	 *
1940
	 * @param int $user_id
1941
	 *
1942
	 * @return void
1943
	 */
1944
	public function update_key( $user_id ) {
1945
		if ( current_user_can( 'edit_user', $user_id ) && isset( $_POST['give_set_api_key'] ) ) {
1946
1947
			$user = get_userdata( $user_id );
1948
1949
			$public_key = $this->get_user_public_key( $user_id );
1950
			$secret_key = $this->get_user_secret_key( $user_id );
1951
1952
			if ( empty( $public_key ) ) {
1953
				$new_public_key = $this->generate_public_key( $user->user_email );
1954
				$new_secret_key = $this->generate_private_key( $user->ID );
1955
1956
				update_user_meta( $user_id, $new_public_key, 'give_user_public_key' );
1957
				update_user_meta( $user_id, $new_secret_key, 'give_user_secret_key' );
1958
			} else {
1959
				$this->revoke_api_key( $user_id );
1960
			}
1961
		}
1962
	}
1963
1964
	/**
1965
	 * Generate the public key for a user
1966
	 *
1967
	 * @access private
1968
	 * @since  1.1
1969
	 *
1970
	 * @param string $user_email
1971
	 *
1972
	 * @return string
1973
	 */
1974
	private function generate_public_key( $user_email = '' ) {
1975
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
1976
		$public   = hash( 'md5', $user_email . $auth_key . date( 'U' ) );
1977
1978
		return $public;
1979
	}
1980
1981
	/**
1982
	 * Generate the secret key for a user
1983
	 *
1984
	 * @access private
1985
	 * @since  1.1
1986
	 *
1987
	 * @param int $user_id
1988
	 *
1989
	 * @return string
1990
	 */
1991
	private function generate_private_key( $user_id = 0 ) {
1992
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
1993
		$secret   = hash( 'md5', $user_id . $auth_key . date( 'U' ) );
1994
1995
		return $secret;
1996
	}
1997
1998
	/**
1999
	 * Retrieve the user's token
2000
	 *
2001
	 * @access private
2002
	 * @since  1.1
2003
	 *
2004
	 * @param int $user_id
2005
	 *
2006
	 * @return string
2007
	 */
2008
	public function get_token( $user_id = 0 ) {
2009
		return hash( 'md5', $this->get_user_secret_key( $user_id ) . $this->get_user_public_key( $user_id ) );
2010
	}
2011
2012
	/**
2013
	 * Generate the default donation stats returned by the 'stats' endpoint
2014
	 *
2015
	 * @access private
2016
	 * @since  1.1
2017
	 * @return array default sales statistics
2018
	 */
2019
	private function get_default_sales_stats() {
2020
2021
		// Default sales return
2022
		$sales                               = array();
2023
		$sales['donations']['today']         = $this->stats->get_sales( 0, 'today' );
2024
		$sales['donations']['current_month'] = $this->stats->get_sales( 0, 'this_month' );
2025
		$sales['donations']['last_month']    = $this->stats->get_sales( 0, 'last_month' );
2026
		$sales['donations']['totals']        = give_get_total_donations();
2027
2028
		return $sales;
2029
	}
2030
2031
	/**
2032
	 * Generate the default earnings stats returned by the 'stats' endpoint
2033
	 *
2034
	 * @access private
2035
	 * @since  1.1
2036
	 * @return array default earnings statistics
2037
	 */
2038
	private function get_default_earnings_stats() {
2039
2040
		// Default earnings return
2041
		$earnings                              = array();
2042
		$earnings['earnings']['today']         = $this->stats->get_earnings( 0, 'today' );
2043
		$earnings['earnings']['current_month'] = $this->stats->get_earnings( 0, 'this_month' );
2044
		$earnings['earnings']['last_month']    = $this->stats->get_earnings( 0, 'last_month' );
2045
		$earnings['earnings']['totals']        = give_get_total_earnings();
2046
2047
		return $earnings;
2048
	}
2049
2050
	/**
2051
	 * API Key Backwards Compatibility
2052
	 *
2053
	 * A Backwards Compatibility call for the change of meta_key/value for users API Keys.
2054
	 *
2055
	 * @since  1.3.6
2056
	 *
2057
	 * @param  string $check     Whether to check the cache or not
2058
	 * @param  int    $object_id The User ID being passed
2059
	 * @param  string $meta_key  The user meta key
2060
	 * @param  bool   $single    If it should return a single value or array
2061
	 *
2062
	 * @return string            The API key/secret for the user supplied
2063
	 */
2064
	public function api_key_backwards_compat( $check, $object_id, $meta_key, $single ) {
2065
2066
		if ( $meta_key !== 'give_user_public_key' && $meta_key !== 'give_user_secret_key' ) {
2067
			return $check;
2068
		}
2069
2070
		$return = $check;
2071
2072
		switch ( $meta_key ) {
2073
			case 'give_user_public_key':
2074
				$return = Give()->api->get_user_public_key( $object_id );
2075
				break;
2076
			case 'give_user_secret_key':
2077
				$return = Give()->api->get_user_secret_key( $object_id );
2078
				break;
2079
		}
2080
2081
		if ( ! $single ) {
2082
			$return = array( $return );
2083
		}
2084
2085
		return $return;
2086
2087
	}
2088
2089
}
2090