Completed
Push — release/1.8.11 ( a764cd )
by Ravinder
893:07 queued 876:14
created

Give_API::get_forms()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 4
nop 1
dl 0
loc 38
rs 9.0008
c 0
b 0
f 0
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
	 * @access public
137
	 */
138
	public function __construct() {
139
140
		$this->versions = array(
141
			'v1' => 'GIVE_API_V1',
142
		);
143
144
		foreach ( $this->get_versions() as $version => $class ) {
0 ignored issues
show
Bug introduced by
The expression $this->get_versions() of type string is not traversable.
Loading history...
145
			require_once GIVE_PLUGIN_DIR . 'includes/api/class-give-api-' . $version . '.php';
146
		}
147
148
		add_action( 'init', array( $this, 'add_endpoint' ) );
149
		add_action( 'wp', array( $this, 'process_query' ), - 1 );
150
		add_filter( 'query_vars', array( $this, 'query_vars' ) );
151
		add_action( 'show_user_profile', array( $this, 'user_key_field' ) );
152
		add_action( 'edit_user_profile', array( $this, 'user_key_field' ) );
153
		add_action( 'personal_options_update', array( $this, '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
157
		// Setup a backwards compatibility check for user API Keys
158
		add_filter( 'get_user_metadata', array( $this, 'api_key_backwards_compat' ), 10, 4 );
159
160
		// Determine if JSON_PRETTY_PRINT is available
161
		$this->pretty_print = defined( 'JSON_PRETTY_PRINT' ) ? JSON_PRETTY_PRINT : null;
162
163
		// Allow API request logging to be turned off
164
		$this->log_requests = apply_filters( 'give_api_log_requests', $this->log_requests );
165
166
		// Setup Give_Payment_Stats instance
167
		$this->stats = new Give_Payment_Stats();
168
169
	}
170
171
	/**
172
	 * Registers a new rewrite endpoint for accessing the API
173
	 *
174
	 * @access public
175
	 *
176
	 * @param array $rewrite_rules WordPress Rewrite Rules
177
	 *
178
	 * @since  1.1
179
	 */
180
	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
	 * @return string[] $vars New query vars
193
	 */
194
	public function query_vars( $vars ) {
195
196
		$vars[] = 'token';
197
		$vars[] = 'key';
198
		$vars[] = 'query';
199
		$vars[] = 'type';
200
		$vars[] = 'form';
201
		$vars[] = 'number';
202
		$vars[] = 'date';
203
		$vars[] = 'startdate';
204
		$vars[] = 'enddate';
205
		$vars[] = 'donor';
206
		$vars[] = 'format';
207
		$vars[] = 'id';
208
		$vars[] = 'purchasekey';
209
		$vars[] = 'email';
210
211
		return $vars;
212
	}
213
214
	/**
215
	 * Retrieve the API versions
216
	 *
217
	 * @access public
218
	 * @since  1.1
219
	 * @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
	 */
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
	 * @return string
242
	 */
243
	public function get_default_version() {
244
245
		$version = get_option( 'give_default_api_version' );
246
247
		if ( defined( 'GIVE_API_VERSION' ) ) {
248
			$version = GIVE_API_VERSION;
249
		} elseif ( ! $version ) {
250
			$version = 'v1';
251
		}
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
	 *
354
	 * @param string $key   Public Key
355
	 *
356
	 * @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
361
		if ( empty( $key ) ) {
362
			$key = urldecode( $wp_query->query_vars['key'] );
363
		}
364
365
		if ( empty( $key ) ) {
366
			return false;
367
		}
368
369
		$user = Give_Cache::get( md5( 'give_api_user_' . $key ), true );
370
371
		if ( false === $user ) {
372
			$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
		}
375
376
		if ( $user != null ) {
377
			$this->user_id = $user;
378
379
			return $user;
380
		}
381
382
		return false;
383
	}
384
385
	/**
386
	 * Get user public key.
387
	 *
388
	 * @param int $user_id
389
	 *
390
	 * @return mixed|null|string
391
	 */
392
	public function get_user_public_key( $user_id = 0 ) {
393
		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
		if ( empty( $user_id ) ) {
396
			return '';
397
		}
398
399
		$cache_key       = md5( 'give_api_user_public_key' . $user_id );
400
		$user_public_key = Give_Cache::get( $cache_key, true );
401
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
		}
406
407
		return $user_public_key;
408
	}
409
410
	/**
411
	 * Get user secret key.
412
	 *
413
	 * @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
	 * @uses   Give_API::output()
491
	 * @return void
492
	 */
493
	private function invalid_version() {
494
		$error          = array();
495
		$error['error'] = esc_html__( 'Invalid API version.', 'give' );
496
497
		$this->data = $error;
498
		$this->output( 404 );
499
	}
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
			'forms',
633
			'donors',
634
			'donations',
635
		) );
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
			// 400 is Bad Request
648
			$this->output( 400 );
649
		}
650
651
		$this->endpoint = $query;
652
	}
653
654
	/**
655
	 * Get page number
656
	 *
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
					$dates['m_end']   = null;
853
					$dates['year']    = date( 'Y', $current_time ) - 1;
854
					break;
855
856
			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
		 * Returns the filters for the dates used to retrieve earnings.
861
		 *
862
		 * @since 1.2
863
		 *
864
		 * @param array $dates The dates used for retrieving earnings.
865
		 */
866
		return apply_filters( 'give_api_stat_dates', $dates );
867
	}
868
869
	/**
870
	 * Process Get Donors API Request.
871
	 *
872
	 * @access public
873
	 * @since  1.1
874
	 * @global WPDB $wpdb  Used to query the database using the WordPress Database API.
875
	 *
876
	 * @param int   $donor Donor ID
877
	 *
878
	 * @return array $donors Multidimensional array of the donors.
879
	 */
880
	public function get_donors( $donor = null ) {
881
882
		$donors = array();
883
		$error  = array();
884
		if ( ! user_can( $this->user_id, 'view_give_sensitive_data' ) && ! $this->override ) {
885
			return $donors;
886
		}
887
888
		$paged    = $this->get_paged();
889
		$per_page = $this->per_page();
890
		$offset   = $per_page * ( $paged - 1 );
891
892
		if ( is_numeric( $donor ) ) {
893
			$field = 'id';
894
		} else {
895
			$field = 'email';
896
		}
897
898
		$donor_query = Give()->donors->get_donors( array(
899
			'number' => $per_page,
900
			'offset' => $offset,
901
			$field   => $donor,
902
		) );
903
		$donor_count = 0;
904
905
		if ( $donor_query ) {
906
907
			foreach ( $donor_query as $donor_obj ) {
908
909
				$names      = explode( ' ', $donor_obj->name );
910
				$first_name = ! empty( $names[0] ) ? $names[0] : '';
911
				$last_name  = '';
912
				if ( ! empty( $names[1] ) ) {
913
					unset( $names[0] );
914
					$last_name = implode( ' ', $names );
915
				}
916
917
				$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
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
949
		} else {
950
951
			$error['error'] = esc_html__( 'No donors found.', 'give' );
952
953
			return $error;
954
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
957
		return $donors;
958
	}
959
960
	/**
961
	 * Process Get Donation Forms API Request
962
	 *
963
	 * @access public
964
	 * @since  1.1
965
	 *
966
	 * @param int $form Give Form ID.
967
	 *
968
	 * @return array $donors Multidimensional array of the forms.
969
	 */
970
	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
				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
				$error['error'] = sprintf( /* translators: %s: form */
1000
					esc_html__( 'Form %s not found.', 'give' ), $form );
1001
1002
				return $error;
1003
			}
1004
		}
1005
1006
		return $forms;
1007
	}
1008
1009
	/**
1010
	 * Given a give_forms post object, generate the data for the API output
1011
	 *
1012
	 * @since  1.1
1013
	 *
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
	 */
1018
	private function get_form_data( $form_info ) {
1019
1020
		$form = array();
1021
1022
		$form['info']['id']            = $form_info->ID;
1023
		$form['info']['slug']          = $form_info->post_name;
1024
		$form['info']['title']         = $form_info->post_title;
1025
		$form['info']['create_date']   = $form_info->post_date;
1026
		$form['info']['modified_date'] = $form_info->post_modified;
1027
		$form['info']['status']        = $form_info->post_status;
1028
		$form['info']['link']          = html_entity_decode( $form_info->guid );
1029
		$form['info']['content']       = give_get_meta( $form_info->ID, '_give_form_content', true );
1030
		$form['info']['thumbnail']     = wp_get_attachment_url( get_post_thumbnail_id( $form_info->ID ) );
1031
1032
		if ( give_is_setting_enabled( give_get_option( 'categories', 'disabled' ) ) ) {
1033
			$form['info']['category'] = get_the_terms( $form_info, 'give_forms_category' );
1034
			$form['info']['tags']     = get_the_terms( $form_info, 'give_forms_tag' );
1035
		}
1036
		if ( give_is_setting_enabled( give_get_option( 'tags', 'disabled' ) ) ) {
1037
			$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
		$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
			$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
			$form['goal']['amount']               = isset( $goal_amount ) ? $goal_amount : '';
1047
			$form['goal']['percentage_completed'] = isset( $goal_percentage_completed ) ? $goal_percentage_completed : '';
1048
		}
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
				}
1340
			} else {
1341
				if ( get_post_type( $args['form'] ) == 'give_forms' ) {
1342
					$form_info               = get_post( $args['form'] );
1343
					$earnings['earnings'][0] = array(
1344
						$form_info->post_name => give_get_form_earnings_stats( $args['form'] ),
1345
					);
1346
				} else {
1347
					$error['error'] = sprintf( /* translators: %s: form */
1348
						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
1352
			if ( ! empty( $error ) ) {
1353
				return $error;
1354
			}
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
1367
			return array(
1368
				'stats' => $stats,
1369
			);
1370
		}// 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
	}
1372
1373
	/**
1374
	 * Retrieves Recent Donations
1375
	 *
1376
	 * @access public
1377
	 * @since  1.1
1378
	 *
1379
	 * @param $args array
1380
	 *
1381
	 * @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
1386
		$defaults = array(
1387
			'date'      => null,
1388
			'startdate' => null,
1389
			'enddate'   => null,
1390
		);
1391
1392
		$args = wp_parse_args( $args, $defaults );
1393
1394
		$sales = array();
1395
1396
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1397
			return $sales;
1398
		}
1399
1400
		if ( isset( $wp_query->query_vars['id'] ) ) {
1401
			$query   = array();
1402
			$query[] = new Give_Payment( $wp_query->query_vars['id'] );
1403
		} elseif ( isset( $wp_query->query_vars['purchasekey'] ) ) {
1404
			$query   = array();
1405
			$query[] = give_get_payment_by( 'key', $wp_query->query_vars['purchasekey'] );
1406
		} elseif ( isset( $wp_query->query_vars['email'] ) ) {
1407
			$args  = array(
1408
				'fields'     => 'ids',
1409
				'meta_key'   => '_give_payment_user_email',
1410
				'meta_value' => $wp_query->query_vars['email'],
1411
				'number'     => $this->per_page(),
1412
				'page'       => $this->get_paged(),
1413
				'status'     => 'publish',
1414
			);
1415
			$query = give_get_payments( $args );
1416
		} elseif ( isset( $wp_query->query_vars['date'] ) ) {
1417
1418
			$current_time = current_time( 'timestamp' );
1419
			$dates        = $this->get_dates( $args );
1420
1421
			/**
1422
			 *  Switch case for date query argument
1423
			 *
1424
			 * @since  1.8.8
1425
			 *
1426
			 * @params text date | today, yesterday or range
1427
			 * @params date startdate | required when date = range and format to be YYYYMMDD (i.e. 20170524)
1428
			 * @params date enddate | required when date = range and format to be YYYYMMDD (i.e. 20170524)
1429
			 */
1430
			switch ( $wp_query->query_vars['date'] ) {
1431
1432
				case 'today':
1433
1434
					// Set and Format Start and End Date to be date of today.
1435
					$start_date = $end_date = date( 'Y/m/d', $current_time );
1436
1437
					break;
1438
1439
				case 'yesterday':
1440
1441
					// Set and Format Start and End Date to be date of yesterday.
1442
					$start_date = $end_date = date( 'Y/m', $current_time ) . '/' . ( date( 'd', $current_time ) - 1 );
1443
1444
					break;
1445
1446
				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
		}
1798
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
				esc_html__( 'You do not have permission to %s API keys for this user.', 'give' ), $process ), esc_html__( 'Error', 'give' ), array(
1815
				'response' => 403,
1816
			) );
1817
		}
1818
1819
		switch ( $process ) {
1820
			case 'generate':
1821
				if ( $this->generate_api_key( $user_id ) ) {
1822
					Give_Cache::delete( Give_Cache::get_key( 'give_total_api_keys' ) );
1823
					wp_redirect( add_query_arg( 'give-message', 'api-key-generated', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1824
					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
					wp_redirect( add_query_arg( 'give-message', 'api-key-failed', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1827
					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
				}
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
				$this->generate_api_key( $user_id, true );
1832
				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
		}
1845
	}
1846
1847
	/**
1848
	 * 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
			return false;
1862
		}
1863
1864
		$user = get_userdata( $user_id );
1865
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
	 *
1935
	 * Generates the key requested by user_key_field and stores it in the database
1936
	 *
1937
	 * @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