Issues (4296)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/api/class-give-api.php (97 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
162 13
163
		// Allow API request logging to be turned off
164
		$this->log_requests = apply_filters( 'give_api_log_requests', $this->log_requests );
165 13
166
		// Setup Give_Payment_Stats instance
167 13
		$this->stats = new Give_Payment_Stats();
168
169
	}
170
171
	/**
172
	 * There are certain responsibility of this function:
173
	 *  1. handle backward compatibility for deprecated functions
174
	 *
175
	 * @since 2.0
176
	 *
177
	 * @param $name
178 11
	 * @param $arguments
179 11
	 *
180 11
	 * @return mixed
181
	 */
182
	public function __call( $name, $arguments ) {
183
		$deprecated_function_arr = array(
184
			'get_customers',
185
		);
186
187
		if ( in_array( $name, $deprecated_function_arr, true ) ) {
188
			switch ( $name ) {
189
				case 'get_customers':
190
					$args = ! empty( $arguments[0] ) ? $arguments[0] : array();
191
192 4
					return $this->get_donors( $args );
193
			}
194 4
		}
195 4
	}
196 4
197 4
	/**
198 4
	 * Registers a new rewrite endpoint for accessing the API
199 4
	 *
200 4
	 * @access public
201 4
	 *
202 4
	 * @since  1.1
203 4
	 */
204 4
	public function add_endpoint() {
205 4
		add_rewrite_endpoint( 'give-api', EP_ALL );
206 4
	}
207 4
208
	/**
209 4
	 * Registers query vars for API access
210
	 *
211
	 * @access public
212
	 * @since  1.1
213
	 *
214
	 * @param array $vars Query vars
215
	 *
216
	 * @return string[] $vars New query vars
217
	 */
218
	public function query_vars( $vars ) {
219 13
220 13
		$vars[] = 'token';
221
		$vars[] = 'key';
222
		$vars[] = 'query';
223
		$vars[] = 'type';
224
		$vars[] = 'form';
225
		$vars[] = 'number';
226
		$vars[] = 'date';
227
		$vars[] = 'startdate';
228
		$vars[] = 'enddate';
229
		$vars[] = 'donor';
230
		$vars[] = 'format';
231
		$vars[] = 'id';
232
		$vars[] = 'purchasekey';
233
		$vars[] = 'email';
234
235
		return $vars;
236
	}
237
238
	/**
239
	 * Retrieve the API versions
240
	 *
241 1
	 * @access public
242
	 * @since  1.1
243 1
	 * @return array
244
	 */
245 1
	public function get_versions() {
246 1
		return $this->versions;
247 1
	}
248
249
	/**
250
	 * Retrieve the API version that was queried
251 1
	 *
252
	 * @access public
253
	 * @since  1.1
254
	 * @return string
255
	 */
256
	public function get_queried_version() {
257
		return $this->queried_version;
258
	}
259
260
	/**
261
	 * Retrieves the default version of the API to use
262
	 *
263
	 * @access public
264
	 * @since  1.1
265
	 * @return string
266
	 */
267
	public function get_default_version() {
268
269
		$version = get_option( 'give_default_api_version' );
270
271
		if ( defined( 'GIVE_API_VERSION' ) ) {
272
			$version = GIVE_API_VERSION;
273
		} elseif ( ! $version ) {
274
			$version = 'v1';
275
		}
276
277
		return $version;
278
	}
279
280
	/**
281
	 * Sets the version of the API that was queried.
282
	 *
283
	 * Falls back to the default version if no version is specified
284
	 *
285
	 * @access private
286
	 * @since  1.1
287
	 */
288
	private function set_queried_version() {
289
290
		global $wp_query;
291
292
		$version = $wp_query->query_vars['give-api'];
293
294
		if ( strpos( $version, '/' ) ) {
295
296
			$version = explode( '/', $version );
297
			$version = strtolower( $version[0] );
298
299
			$wp_query->query_vars['give-api'] = str_replace( $version . '/', '', $wp_query->query_vars['give-api'] );
300
301
			if ( array_key_exists( $version, $this->versions ) ) {
302
303
				$this->queried_version = $version;
304
305
			} else {
306
307
				$this->is_valid_request = false;
308
				$this->invalid_version();
309
			}
310
		} else {
311
312
			$this->queried_version = $this->get_default_version();
313
314
		}
315
316
	}
317
318
	/**
319
	 * Validate the API request
320
	 *
321
	 * Checks for the user's public key and token against the secret key.
322
	 *
323
	 * @access private
324
	 * @global object $wp_query WordPress Query
325
	 * @uses   Give_API::get_user()
326
	 * @uses   Give_API::invalid_key()
327
	 * @uses   Give_API::invalid_auth()
328
	 * @since  1.1
329
	 * @return bool
330
	 */
331
	private function validate_request() {
332
		global $wp_query;
333
334
		$this->override = false;
335
336
		// Make sure we have both user and api key
337
		if ( ! empty( $wp_query->query_vars['give-api'] ) && ( $wp_query->query_vars['give-api'] !== 'forms' || ! empty( $wp_query->query_vars['token'] ) ) ) {
0 ignored issues
show
Found "!== '". Use Yoda Condition checks, you must
Loading history...
338
339
			if ( empty( $wp_query->query_vars['token'] ) || empty( $wp_query->query_vars['key'] ) ) {
340
				$this->missing_auth();
341
342
				return false;
343
			}
344
345
			// Retrieve the user by public API key and ensure they exist
346
			if ( ! ( $user = $this->get_user( $wp_query->query_vars['key'] ) ) ) {
347
348
				$this->invalid_key();
349
350
				return false;
351
352
			} else {
353 1
354 1
				$token  = urldecode( $wp_query->query_vars['token'] );
355
				$secret = $this->get_user_secret_key( $user );
0 ignored issues
show
$user is of type boolean, but the function expects a integer.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
356 1
				$public = urldecode( $wp_query->query_vars['key'] );
357
358
				if ( hash_equals( md5( $secret . $public ), $token ) ) {
359
					$this->is_valid_request = true;
360 1
				} else {
361
					$this->invalid_auth();
362
363
					return false;
364 1
				}
0 ignored issues
show
Blank line found after control structure
Loading history...
365
366 1
			}
367 1
		} elseif ( ! empty( $wp_query->query_vars['give-api'] ) && $wp_query->query_vars['give-api'] === 'forms' ) {
0 ignored issues
show
Found "=== '". Use Yoda Condition checks, you must
Loading history...
368 1
			$this->is_valid_request = true;
369 1
			$wp_query->set( 'key', 'public' );
370
		}
371 1
	}
372 1
373
	/**
374 1
	 * Retrieve the user ID based on the public key provided
375
	 *
376
	 * @access public
377
	 * @since  1.1
378
	 * @global WPDB  $wpdb  Used to query the database using the WordPress
379
	 *                      Database API
380 11
	 *
381 2
	 * @param string $key   Public Key
382
	 *
383 2
	 * @return bool if user ID is found, false otherwise
384
	 */
385
	public function get_user( $key = '' ) {
386
		global $wpdb, $wp_query;
387 2
388 2
		if ( empty( $key ) ) {
389
			$key = urldecode( $wp_query->query_vars['key'] );
390 2
		}
391 2
392 2
		if ( empty( $key ) ) {
393 2
			return false;
394
		}
395 11
396
		$user = Give_Cache::get( md5( 'give_api_user_' . $key ), true );
397
398 11
		if ( false === $user ) {
399 2
			$user = $wpdb->get_var( $wpdb->prepare( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = %s LIMIT 1", $key ) );
0 ignored issues
show
Usage of a direct database call is discouraged.
Loading history...
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.
Loading history...
400
			Give_Cache::set( md5( 'give_api_user_' . $key ), $user, DAY_IN_SECONDS, true );
401 2
		}
402
403
		if ( $user != null ) {
404
			$this->user_id = $user;
405 2
406 2
			return $user;
407
		}
408 2
409 2
		return false;
410 2
	}
411 11
412
	/**
413 2
	 * Get user public key.
414
	 *
415
	 * @param int $user_id
416
	 *
417
	 * @return mixed|null|string
418
	 */
419 View Code Duplication
	public function get_user_public_key( $user_id = 0 ) {
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

Loading history...
420
		global $wpdb;
421
422
		if ( empty( $user_id ) ) {
423
			return '';
424
		}
425
426
		$cache_key       = md5( 'give_api_user_public_key' . $user_id );
427
		$user_public_key = Give_Cache::get( $cache_key, true );
428
429
		if ( empty( $user_public_key ) ) {
430
			$user_public_key = $wpdb->get_var( $wpdb->prepare( "SELECT meta_key FROM $wpdb->usermeta WHERE meta_value = 'give_user_public_key' AND user_id = %d", $user_id ) );
0 ignored issues
show
Usage of a direct database call is discouraged.
Loading history...
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.
Loading history...
431
			Give_Cache::set( $cache_key, $user_public_key, HOUR_IN_SECONDS, true );
432
		}
433
434
		return $user_public_key;
435
	}
436
437
	/**
438
	 * Get user secret key.
439
	 *
440
	 * @param int $user_id
441
	 *
442
	 * @return mixed|null|string
443
	 */
444 View Code Duplication
	public function get_user_secret_key( $user_id = 0 ) {
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

Loading history...
445
		global $wpdb;
446
447
		if ( empty( $user_id ) ) {
448
			return '';
449
		}
450
451
		$cache_key       = md5( 'give_api_user_secret_key' . $user_id );
452
		$user_secret_key = Give_Cache::get( $cache_key, true );
453
454
		if ( empty( $user_secret_key ) ) {
455
			$user_secret_key = $wpdb->get_var( $wpdb->prepare( "SELECT meta_key FROM $wpdb->usermeta WHERE meta_value = 'give_user_secret_key' AND user_id = %d", $user_id ) );
0 ignored issues
show
Usage of a direct database call is discouraged.
Loading history...
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
Usage of users/usermeta tables is highly discouraged in VIP context, For storing user additional user metadata, you should look at User Attributes.
Loading history...
456
			Give_Cache::set( $cache_key, $user_secret_key, HOUR_IN_SECONDS, true );
457
		}
458
459
		return $user_secret_key;
460
	}
461
462
	/**
463
	 * Displays a missing authentication error if all the parameters are not met.
464
	 * provided
465
	 *
466
	 * @access private
467
	 * @uses   Give_API::output()
468
	 * @since  1.1
469
	 */
470 View Code Duplication
	private function missing_auth() {
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

Loading history...
471
		$error          = array();
472
		$error['error'] = __( 'You must specify both a token and API key.', 'give' );
473
474
		$this->data = $error;
475
		$this->output( 401 );
476
	}
477
478
	/**
479
	 * Displays an authentication failed error if the user failed to provide valid
480
	 * credentials
481
	 *
482
	 * @access private
483
	 * @since  1.1
484
	 * @uses   Give_API::output()
485
	 * @return void
486
	 */
487 View Code Duplication
	private function invalid_auth() {
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

Loading history...
488
		$error          = array();
489
		$error['error'] = __( 'Your request could not be authenticated.', 'give' );
490 3
491
		$this->data = $error;
492 3
		$this->output( 403 );
493
	}
494
495 3
	/**
496
	 * Displays an invalid API key error if the API key provided couldn't be
497
	 * validated
498 3
	 *
499 3
	 * @access private
500
	 * @since  1.1
501
	 * @uses   Give_API::output()
502
	 * @return void
503
	 */
504 View Code Duplication
	private function invalid_key() {
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

Loading history...
505
		$error          = array();
506
		$error['error'] = __( 'Invalid API key.', 'give' );
507
508
		$this->data = $error;
509
		$this->output( 403 );
510
	}
511
512
	/**
513
	 * Displays an invalid version error if the version number passed isn't valid
514
	 *
515
	 * @access private
516
	 * @since  1.1
517
	 * @uses   Give_API::output()
518
	 * @return void
519
	 */
520 View Code Duplication
	private function invalid_version() {
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

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

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

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

Loading history...
756
					$dates['day']     = null;
757
					$dates['m_start'] = date( 'n', $current_time );
758
					$dates['m_end']   = date( 'n', $current_time );
759
					$dates['year']    = date( 'Y', $current_time );
760
					break;
761
762
				case 'last_month' :
763
					$dates['day']     = null;
764
					$dates['m_start'] = date( 'n', $current_time ) == 1 ? 12 : date( 'n', $current_time ) - 1;
765
					$dates['m_end']   = $dates['m_start'];
766
					$dates['year']    = date( 'n', $current_time ) == 1 ? date( 'Y', $current_time ) - 1 : date( 'Y', $current_time );
767
					break;
768
769 View Code Duplication
				case 'today' :
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
770
					$dates['day']     = date( 'd', $current_time );
771
					$dates['m_start'] = date( 'n', $current_time );
772
					$dates['m_end']   = date( 'n', $current_time );
773
					$dates['year']    = date( 'Y', $current_time );
774
					break;
775
776 View Code Duplication
				case 'yesterday' :
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
777
778
					$year  = date( 'Y', $current_time );
779
					$month = date( 'n', $current_time );
780
					$day   = date( 'd', $current_time );
781
782
					if ( $month == 1 && $day == 1 ) {
0 ignored issues
show
Found "== 1". Use Yoda Condition checks, you must
Loading history...
783
784
						$year  -= 1;
785
						$month = 12;
786
						$day   = cal_days_in_month( CAL_GREGORIAN, $month, $year );
787
788
					} elseif ( $month > 1 && $day == 1 ) {
0 ignored issues
show
Found "== 1". Use Yoda Condition checks, you must
Loading history...
789
790
						$month -= 1;
791
						$day   = cal_days_in_month( CAL_GREGORIAN, $month, $year );
792
793
					} else {
794
795
						$day -= 1;
796
797
					}
798
799
					$dates['day']     = $day;
800
					$dates['m_start'] = $month;
801
					$dates['m_end']   = $month;
802
					$dates['year']    = $year;
803
804
					break;
805
806 View Code Duplication
				case 'this_quarter' :
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

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

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

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

Loading history...
839
					$month_now = date( 'n', $current_time );
840
841
					$dates['day'] = null;
842
843
					if ( $month_now <= 3 ) {
844
845
						$dates['m_start'] = 10;
846
						$dates['m_end']   = 12;
847
						$dates['year']    = date( 'Y', $current_time ) - 1; // Previous year
848
849
					} elseif ( $month_now <= 6 ) {
850
851
						$dates['m_start'] = 1;
852 1
						$dates['m_end']   = 3;
853
						$dates['year']    = date( 'Y', $current_time );
854 1
855 1
					} elseif ( $month_now <= 9 ) {
856 1
857
						$dates['m_start'] = 4;
858
						$dates['m_end']   = 6;
859
						$dates['year']    = date( 'Y', $current_time );
860 1
861
					} else {
862 1
863 1
						$dates['m_start'] = 7;
864 1
						$dates['m_end']   = 9;
865
						$dates['year']    = date( 'Y', $current_time );
866 1
867
					}
868
					break;
869 1
870 View Code Duplication
				case 'this_year' :
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
871
					$dates['day']     = null;
872 1
					$dates['m_start'] = null;
873 1
					$dates['m_end']   = null;
874 1
					$dates['year']    = date( 'Y', $current_time );
875
					break;
876 1
877 1 View Code Duplication
				case 'last_year' :
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
878
					$dates['day']     = null;
879 1
					$dates['m_start'] = null;
880
					$dates['m_end']   = null;
881 1
					$dates['year']    = date( 'Y', $current_time ) - 1;
882
					break;
883 1
884 1
			endswitch;
885 1
		}// End if().
886 1
887 1
		/**
888 1
		 * Returns the filters for the dates used to retrieve earnings.
889 1
		 *
890
		 * @since 1.2
891 1
		 *
892 1
		 * @param array $dates The dates used for retrieving earnings.
893 1
		 */
894 1
		return apply_filters( 'give_api_stat_dates', $dates );
895 1
	}
896 1
897 1
	/**
898
	 * Process Get Donors API Request.
899 1
	 *
900
	 * @access public
901 1
	 * @since  1.1
902
	 * @global WPDB $wpdb  Used to query the database using the WordPress Database API.
903
	 *
904 1
	 * @param int $donor Donor ID.
905 1
	 *
906 1
	 * @return array $donors Multidimensional array of the donors.
907
	 */
908 1
	public function get_donors( $donor = null ) {
909
910 1
		$donors = array();
911 1
		$error  = array();
912
		if ( ! user_can( $this->user_id, 'view_give_sensitive_data' ) && ! $this->override ) {
913 1
			return $donors;
914
		}
915 1
916
		$paged    = $this->get_paged();
917 1
		$per_page = $this->per_page();
918
		$offset   = $per_page * ( $paged - 1 );
919
920
		if ( is_numeric( $donor ) ) {
921
			$field = 'id';
922
		} else {
923
			$field = 'email';
924
		}
925
926
		$donor_query = Give()->donors->get_donors( array(
927
			'number' => $per_page,
928
			'offset' => $offset,
929
			$field   => $donor,
930
		) );
931
		$donor_count = 0;
932
933
		if ( $donor_query ) {
934
935 1
			foreach ( $donor_query as $donor_obj ) {
936
937
				$names      = explode( ' ', $donor_obj->name );
938
				$first_name = ! empty( $names[0] ) ? $names[0] : '';
939
				$last_name  = '';
940
				if ( ! empty( $names[1] ) ) {
941
					unset( $names[0] );
942
					$last_name = implode( ' ', $names );
943
				}
944
945
				$title_prefix = Give()->donor_meta->get_meta( $donor_obj->id, '_give_donor_title_prefix', true );
946
947
				// Set title prefix empty, if not available in db.
948 11
				if ( empty( $title_prefix ) ) {
949
					$title_prefix = '';
950 11
				}
951 11
952
				$donors['donors'][ $donor_count ]['info']['user_id']      = '';
953 11
				$donors['donors'][ $donor_count ]['info']['username']     = '';
954 11
				$donors['donors'][ $donor_count ]['info']['display_name'] = '';
955
				$donors['donors'][ $donor_count ]['info']['donor_id']     = $donor_obj->id;
956 11
				$donors['donors'][ $donor_count ]['info']['title_prefix'] = $title_prefix;
957 11
				$donors['donors'][ $donor_count ]['info']['first_name']   = $first_name;
958 11
				$donors['donors'][ $donor_count ]['info']['last_name']    = $last_name;
959 11
				$donors['donors'][ $donor_count ]['info']['email']        = $donor_obj->email;
960 11
961 11
				if ( ! empty( $donor_obj->user_id ) ) {
962
963 11
					$user_data = get_userdata( $donor_obj->user_id );
964 11
965 11
					// Donor with registered account.
966 11
					$donors['donors'][ $donor_count ]['info']['user_id']      = $donor_obj->user_id;
967 11
					$donors['donors'][ $donor_count ]['info']['username']     = $user_data->user_login;
968 11
					$donors['donors'][ $donor_count ]['info']['display_name'] = $user_data->display_name;
969 11
970 11
				}
971
972
				$donors['donors'][ $donor_count ]['stats']['total_donations'] = $donor_obj->purchase_count;
973
				$donors['donors'][ $donor_count ]['stats']['total_spent']     = $donor_obj->purchase_value;
974
975
				$donor = new Give_Donor( $donor_obj->id );
976
977
				// Get donor's addresses.
978
				$donors['donors'][ $donor_count ]['address'] = $donor->address;
979
980
				$donor_count ++;
981
982
			} // End foreach().
983
		} elseif ( $donor ) {
984
985
			$error['error'] = sprintf(
986
				/* translators: %s: donor */
987 11
				__( 'Donor %s not found.', 'give' ),
988
				$donor
989
			);
990
991
			return $error;
992
993
		} else {
994
995
			$error['error'] = __( 'No donors found.', 'give' );
996
997
			return $error;
998
999 11
		} // End if().
1000
1001 11
		return $donors;
1002
	}
1003 11
1004 11
	/**
1005 11
	 * Process Get Donation Forms API Request
1006 11
	 *
1007 11
	 * @access public
1008 11
	 * @since  1.1
1009 11
	 *
1010 11
	 * @param int $form Give Form ID.
1011 11
	 *
1012
	 * @return array $donors Multidimensional array of the forms.
1013 11
	 */
1014
	public function get_forms( $form = null ) {
1015
1016
		$forms = array();
1017 11
		$error = array();
1018
1019
		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...
1020
			$forms['forms'] = array();
1021 11
1022 11
			$form_list = get_posts( array(
1023 11
				'post_type'        => 'give_forms',
1024 11
				'posts_per_page'   => $this->per_page(),
1025 11
				'suppress_filters' => true,
1026 11
				'paged'            => $this->get_paged(),
1027
			) );
1028 11
1029 11
			if ( $form_list ) {
1030 11
				$i = 0;
1031 11
				foreach ( $form_list as $form_info ) {
1032
					$forms['forms'][ $i ] = $this->get_form_data( $form_info );
1033 11
					$i ++;
1034 11
				}
1035
			}
1036 11
		} else {
1037 11
			if ( get_post_type( $form ) == 'give_forms' ) {
0 ignored issues
show
Found "== '". Use Yoda Condition checks, you must
Loading history...
1038
				$form_info = get_post( $form );
1039
1040
				$forms['forms'][0] = $this->get_form_data( $form_info );
1041 11
1042
			} else {
1043
				$error['error'] = sprintf( /* translators: %s: form */
1044 11
					__( 'Form %s not found.', 'give' ), $form );
0 ignored issues
show
This line of the multi-line function call does not seem to be indented correctly. Expected 16 spaces, but found 20.
Loading history...
1045
1046 11
				return $error;
1047
			}
1048 11
		}
1049
1050
		return $forms;
1051
	}
1052
1053
	/**
1054
	 * Given a give_forms post object, generate the data for the API output
1055
	 *
1056
	 * @since  1.1
1057
	 *
1058
	 * @param  object $form_info The Give Form's Post Object.
1059
	 *
1060
	 * @return array                Array of post data to return back in the API.
1061
	 */
1062
	private function get_form_data( $form_info ) {
1063
1064
		$form = array();
1065
1066
		$form['info']['id']            = $form_info->ID;
1067
		$form['info']['slug']          = $form_info->post_name;
1068
		$form['info']['title']         = $form_info->post_title;
1069
		$form['info']['create_date']   = $form_info->post_date;
1070
		$form['info']['modified_date'] = $form_info->post_modified;
1071
		$form['info']['status']        = $form_info->post_status;
1072
		$form['info']['link']          = html_entity_decode( $form_info->guid );
1073
		$form['info']['content']       = give_get_meta( $form_info->ID, '_give_form_content', true );
1074
		$form['info']['thumbnail']     = wp_get_attachment_url( get_post_thumbnail_id( $form_info->ID ) );
1075
1076
		if ( give_is_setting_enabled( give_get_option( 'categories', 'disabled' ) ) ) {
1077
			$form['info']['category'] = get_the_terms( $form_info, 'give_forms_category' );
1078
			$form['info']['tags']     = get_the_terms( $form_info, 'give_forms_tag' );
1079
		}
1080
		if ( give_is_setting_enabled( give_get_option( 'tags', 'disabled' ) ) ) {
1081
			$form['info']['tags'] = get_the_terms( $form_info, 'give_forms_tag' );
1082
		}
1083
1084
		// Check whether any goal is to be achieved for the donation form.
1085
		$goal_option = give_get_meta( $form_info->ID, '_give_goal_option', true );
1086
		$goal_amount = give_get_meta( $form_info->ID, '_give_set_goal', true );
1087
		if ( give_is_setting_enabled( $goal_option ) && $goal_amount ) {
1088
			$total_income                         = give_get_form_earnings_stats( $form_info->ID );
1089
			$goal_percentage_completed            = ( $total_income < $goal_amount ) ? round( ( $total_income / $goal_amount ) * 100, 2 ) : 100;
1090
			$form['goal']['amount']               = isset( $goal_amount ) ? $goal_amount : '';
1091
			$form['goal']['percentage_completed'] = isset( $goal_percentage_completed ) ? $goal_percentage_completed : '';
1092
		}
1093
1094
		if ( user_can( $this->user_id, 'view_give_reports' ) || $this->override ) {
1095
			$form['stats']['total']['donations']           = give_get_form_sales_stats( $form_info->ID );
1096
			$form['stats']['total']['earnings']            = give_get_form_earnings_stats( $form_info->ID );
1097
			$form['stats']['monthly_average']['donations'] = give_get_average_monthly_form_sales( $form_info->ID );
1098
			$form['stats']['monthly_average']['earnings']  = give_get_average_monthly_form_earnings( $form_info->ID );
1099
		}
1100
1101
		$counter = 0;
1102
		if ( give_has_variable_prices( $form_info->ID ) ) {
1103
			foreach ( give_get_variable_prices( $form_info->ID ) as $price ) {
0 ignored issues
show
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...
1104
				$counter ++;
1105
				// multi-level item
1106
				$level                                     = isset( $price['_give_text'] ) ? $price['_give_text'] : 'level-' . $counter;
1107
				$form['pricing'][ sanitize_key( $level ) ] = $price['_give_amount'];
1108
1109
			}
1110
		} else {
1111
			$form['pricing']['amount'] = give_get_form_price( $form_info->ID );
1112
		}
1113
1114
		if ( user_can( $this->user_id, 'view_give_sensitive_data' ) || $this->override ) {
1115
1116
			/**
1117
			 * Fires when generating API sensitive data.
1118
			 *
1119
			 * @since 1.1
1120
			 */
1121
			do_action( 'give_api_sensitive_data' );
1122
1123
		}
1124
1125
		return apply_filters( 'give_api_forms_form', $form );
1126
1127
	}
1128
1129
	/**
1130
	 * Process Get Stats API Request
1131
	 *
1132
	 * @since 1.1
1133
	 *
1134
	 * @global WPDB $wpdb Used to query the database using the WordPress.
1135
	 *
1136
	 * @param array $args Arguments provided by API Request.
1137
	 *
1138
	 * @return array
1139
	 */
1140
	public function get_stats( $args = array() ) {
1141
		$defaults = array(
1142
			'type'      => null,
1143
			'form'      => null,
1144
			'date'      => null,
1145
			'startdate' => null,
1146
			'enddate'   => null,
1147
		);
1148
1149
		$args = wp_parse_args( $args, $defaults );
1150
1151
		$dates = $this->get_dates( $args );
1152
1153
		$stats     = array();
1154
		$earnings  = array(
1155
			'earnings' => array(),
1156
		);
1157
		$donations = array(
1158
			'donations' => array(),
1159
		);
1160
		$error     = array();
1161
1162
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1163
			return $stats;
1164
		}
1165
1166
		if ( $args['type'] == 'donations' ) {
0 ignored issues
show
Found "== '". Use Yoda Condition checks, you must
Loading history...
1167
1168
			if ( $args['form'] == null ) {
1169
				if ( $args['date'] == null ) {
1170
					$donations = $this->get_default_sales_stats();
1171
				} elseif ( $args['date'] === 'range' ) {
0 ignored issues
show
Found "=== '". Use Yoda Condition checks, you must
Loading history...
1172
					// Return donations for a date range.
1173
					// Ensure the end date is later than the start date.
1174 View Code Duplication
					if ( $args['enddate'] < $args['startdate'] ) {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

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

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

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

Loading history...
1180
						$error['error'] = __( 'Invalid or no date range specified.', 'give' );
1181
					}
1182
1183
					$total = 0;
1184
1185
					// Loop through the years
1186
					$y = $dates['year'];
1187 View Code Duplication
					while ( $y <= $dates['year_end'] ) :
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
1188
1189
						if ( $dates['year'] == $dates['year_end'] ) {
1190
							$month_start = $dates['m_start'];
1191
							$month_end   = $dates['m_end'];
1192
						} elseif ( $y == $dates['year'] && $dates['year_end'] > $dates['year'] ) {
1193
							$month_start = $dates['m_start'];
1194
							$month_end   = 12;
1195
						} elseif ( $y == $dates['year_end'] ) {
1196
							$month_start = 1;
1197
							$month_end   = $dates['m_end'];
1198
						} else {
1199
							$month_start = 1;
1200
							$month_end   = 12;
1201
						}
1202
1203
						$i = $month_start;
1204
						while ( $i <= $month_end ) :
1205
1206
							if ( $i == $dates['m_start'] ) {
1207
								$d = $dates['day_start'];
1208
							} else {
1209
								$d = 1;
1210
							}
1211
1212
							if ( $i == $dates['m_end'] ) {
1213
								$num_of_days = $dates['day_end'];
1214
							} else {
1215
								$num_of_days = cal_days_in_month( CAL_GREGORIAN, $i, $y );
1216
							}
1217
1218
							while ( $d <= $num_of_days ) :
1219
								$sale_count = give_get_sales_by_date( $d, $i, $y );
1220
								$date_key   = date( 'Ymd', strtotime( $y . '/' . $i . '/' . $d ) );
1221
								if ( ! isset( $donations['sales'][ $date_key ] ) ) {
1222
									$donations['sales'][ $date_key ] = 0;
1223
								}
1224
								$donations['sales'][ $date_key ] += $sale_count;
1225
								$total                           += $sale_count;
1226
								$d ++;
1227
							endwhile;
1228
							$i ++;
1229
						endwhile;
1230
1231
						$y ++;
1232
					endwhile;
1233
1234
					$donations['totals'] = $total;
1235 View Code Duplication
				} else {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
1236
					if ( $args['date'] == 'this_quarter' || $args['date'] == 'last_quarter' ) {
0 ignored issues
show
Found "== '". Use Yoda Condition checks, you must
Loading history...
1237
						$donations_count = 0;
1238
1239
						// Loop through the months
1240
						$month = $dates['m_start'];
1241
1242
						while ( $month <= $dates['m_end'] ) :
1243
							$donations_count += give_get_sales_by_date( null, $month, $dates['year'] );
1244
							$month ++;
1245
						endwhile;
1246
1247
						$donations['donations'][ $args['date'] ] = $donations_count;
1248
					} else {
1249
						$donations['donations'][ $args['date'] ] = give_get_sales_by_date( $dates['day'], $dates['m_start'], $dates['year'] );
1250
					}
1251
				}// End if().
1252
			} elseif ( $args['form'] == 'all' ) {
0 ignored issues
show
Found "== '". Use Yoda Condition checks, you must
Loading history...
1253
				$forms = get_posts( array(
1254
					'post_type' => 'give_forms',
1255
					'nopaging'  => true,
0 ignored issues
show
Disabling pagination is prohibited in VIP context, do not set nopaging to true ever.
Loading history...
1256
				) );
1257
				$i     = 0;
1258
				foreach ( $forms as $form_info ) {
1259
					$donations['donations'][ $i ] = array(
1260
						$form_info->post_name => $this->stats->get_sales(
1261
							$form_info->ID,
1262
							is_numeric( $args['startdate'] )
1263
								? strtotime( $args['startdate'] )
1264
								: $args['startdate'],
1265
							is_numeric( $args['enddate'] )
1266
								? strtotime( $args['enddate'] )
1267
								: $args['enddate']
1268
						),
1269
					);
1270
					$i ++;
1271
				}
1272 View Code Duplication
			} else {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
1273
				if ( get_post_type( $args['form'] ) == 'give_forms' ) {
0 ignored issues
show
Found "== '". Use Yoda Condition checks, you must
Loading history...
1274
					$form_info                 = get_post( $args['form'] );
1275
					$donations['donations'][0] = array(
1276
						$form_info->post_name => $this->stats->get_sales(
1277
							$args['form'],
1278
							is_numeric( $args['startdate'] )
1279
								? strtotime( $args['startdate'] )
1280
								: $args['startdate'],
1281
							is_numeric( $args['enddate'] )
1282
								? strtotime( $args['enddate'] )
1283
								: $args['enddate']
1284
						),
1285
					);
1286
				} else {
1287
					$error['error'] = sprintf( /* translators: %s: form */
1288
						__( 'Form %s not found.', 'give' ), $args['form'] );
0 ignored issues
show
This line of the multi-line function call does not seem to be indented correctly. Expected 20 spaces, but found 24.
Loading history...
1289
				}
1290
			}// End if().
1291
1292
			if ( ! empty( $error ) ) {
1293
				return $error;
1294
			}
1295
1296
			return $donations;
1297
1298
		} elseif ( $args['type'] == 'earnings' ) {
0 ignored issues
show
Found "== '". Use Yoda Condition checks, you must
Loading history...
1299
			if ( $args['form'] == null ) {
1300
				if ( $args['date'] == null ) {
1301
					$earnings = $this->get_default_earnings_stats();
1302
				} elseif ( $args['date'] === 'range' ) {
0 ignored issues
show
Found "=== '". Use Yoda Condition checks, you must
Loading history...
1303
					// Return sales for a date range
1304
					// Ensure the end date is later than the start date
1305 View Code Duplication
					if ( $args['enddate'] < $args['startdate'] ) {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

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

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

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

Loading history...
1311
						$error['error'] = __( 'Invalid or no date range specified.', 'give' );
1312
					}
1313
1314
					$total = (float) 0.00;
1315
1316
					// Loop through the years
1317
					$y = $dates['year'];
1318
					if ( ! isset( $earnings['earnings'] ) ) {
1319
						$earnings['earnings'] = array();
1320
					}
1321 View Code Duplication
					while ( $y <= $dates['year_end'] ) :
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
1322
1323
						if ( $dates['year'] == $dates['year_end'] ) {
1324
							$month_start = $dates['m_start'];
1325
							$month_end   = $dates['m_end'];
1326
						} elseif ( $y == $dates['year'] && $dates['year_end'] > $dates['year'] ) {
1327
							$month_start = $dates['m_start'];
1328
							$month_end   = 12;
1329
						} elseif ( $y == $dates['year_end'] ) {
1330
							$month_start = 1;
1331
							$month_end   = $dates['m_end'];
1332
						} else {
1333
							$month_start = 1;
1334
							$month_end   = 12;
1335
						}
1336
1337
						$i = $month_start;
1338
						while ( $i <= $month_end ) :
1339 11
1340 11
							if ( $i == $dates['m_start'] ) {
1341
								$d = $dates['day_start'];
1342 11
							} else {
1343
								$d = 1;
1344 11
							}
1345
1346
							if ( $i == $dates['m_end'] ) {
1347
								$num_of_days = $dates['day_end'];
1348 11
							} else {
1349
								$num_of_days = cal_days_in_month( CAL_GREGORIAN, $i, $y );
1350
							}
1351 11
1352
							while ( $d <= $num_of_days ) :
1353
								$earnings_stat = give_get_earnings_by_date( $d, $i, $y );
1354 11
								$date_key      = date( 'Ymd', strtotime( $y . '/' . $i . '/' . $d ) );
1355
								if ( ! isset( $earnings['earnings'][ $date_key ] ) ) {
1356
									$earnings['earnings'][ $date_key ] = 0;
1357
								}
1358
								$earnings['earnings'][ $date_key ] += $earnings_stat;
1359
								$total                             += $earnings_stat;
1360
								$d ++;
1361
							endwhile;
1362
1363
							$i ++;
1364
						endwhile;
1365
1366 11
						$y ++;
1367 11
					endwhile;
1368 11
1369
					$earnings['totals'] = $total;
1370 11 View Code Duplication
				} else {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
1371 11
					if ( $args['date'] == 'this_quarter' || $args['date'] == 'last_quarter' ) {
0 ignored issues
show
Found "== '". Use Yoda Condition checks, you must
Loading history...
1372
						$earnings_count = (float) 0.00;
1373 11
1374 11
						// Loop through the months
1375 11
						$month = $dates['m_start'];
1376
1377 11
						while ( $month <= $dates['m_end'] ) :
1378 11
							$earnings_count += give_get_earnings_by_date( null, $month, $dates['year'] );
1379 11
							$month ++;
1380 11
						endwhile;
1381 11
1382
						$earnings['earnings'][ $args['date'] ] = $earnings_count;
1383
					} else {
1384
						$earnings['earnings'][ $args['date'] ] = give_get_earnings_by_date( $dates['day'], $dates['m_start'], $dates['year'] );
1385 11
					}
1386 11
				}// End if().
1387
			} elseif ( $args['form'] == 'all' ) {
0 ignored issues
show
Found "== '". Use Yoda Condition checks, you must
Loading history...
1388 11
				$forms = get_posts( array(
1389 11
					'post_type' => 'give_forms',
1390
					'nopaging'  => true,
0 ignored issues
show
Disabling pagination is prohibited in VIP context, do not set nopaging to true ever.
Loading history...
1391 11
				) );
1392 11
1393 11
				$i = 0;
1394 11
				foreach ( $forms as $form_info ) {
1395 11
					$earnings['earnings'][ $i ] = array(
1396 11
						$form_info->post_name => give_get_form_earnings_stats( $form_info->ID ),
1397 11
					);
1398 11
					$i ++;
1399 11
				}
1400 11 View Code Duplication
			} else {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
1401
				if ( get_post_type( $args['form'] ) == 'give_forms' ) {
0 ignored issues
show
Found "== '". Use Yoda Condition checks, you must
Loading history...
1402 11
					$form_info               = get_post( $args['form'] );
1403 11
					$earnings['earnings'][0] = array(
1404 11
						$form_info->post_name => $this->stats->get_earnings(
1405
								$args['form'],
0 ignored issues
show
This line of the multi-line function call does not seem to be indented correctly. Expected 28 spaces, but found 32.
Loading history...
1406 11
								is_numeric( $args['startdate'] )
0 ignored issues
show
This line of the multi-line function call does not seem to be indented correctly. Expected 28 spaces, but found 32.
Loading history...
1407 11
									? strtotime( $args['startdate'] )
1408 11
									: $args['startdate'],
1409
								is_numeric( $args['enddate'] )
0 ignored issues
show
This line of the multi-line function call does not seem to be indented correctly. Expected 28 spaces, but found 32.
Loading history...
1410 11
									? strtotime( $args['enddate'] )
1411 11
									: $args['enddate']
1412 11
						),
1413 11
					);
1414 11
				} else {
1415 11
					$error['error'] = sprintf( /* translators: %s: form */
1416
						__( 'Form %s not found.', 'give' ), $args['form'] );
0 ignored issues
show
This line of the multi-line function call does not seem to be indented correctly. Expected 20 spaces, but found 24.
Loading history...
1417 11
				}
1418 11
			}// End if().
1419
1420
			if ( ! empty( $error ) ) {
1421 11
				return $error;
1422
			}
1423
1424 11
			return $earnings;
1425 11
		} elseif ( $args['type'] == 'donors' ) {
0 ignored issues
show
Found "== '". Use Yoda Condition checks, you must
Loading history...
1426 11
			$donors                             = new Give_DB_Donors();
1427 11
			$stats['donations']['total_donors'] = $donors->count();
1428 11
1429 11
			return $stats;
1430 11
1431 11
		} elseif ( empty( $args['type'] ) ) {
1432
			$stats = array_merge( $stats, $this->get_default_sales_stats() );
1433
			$stats = array_merge( $stats, $this->get_default_earnings_stats() );
1434 11
1435 11
			return array(
1436
				'stats' => $stats,
1437
			);
1438 11
		}// End if().
1439
	}
1440 11
1441
	/**
1442 11
	 * Retrieves Recent Donations
1443 11
	 *
1444 11
	 * @access public
1445
	 * @since  1.1
1446 11
	 *
1447
	 * @param $args array
1448
	 *
1449
	 * @return array
1450
	 */
1451
	public function get_recent_donations( $args = array() ) {
1452
		global $wp_query;
1453
1454
		$defaults = array(
1455
			'id'        => null,
1456
			'date'      => null,
1457
			'startdate' => null,
1458
			'enddate'   => null,
1459
		);
1460
1461
		$args = wp_parse_args( $args, $defaults );
1462
1463
		$donations = array();
1464
1465
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1466
			return $donations;
1467
		}
1468
1469
		if ( isset( $wp_query->query_vars['id'] ) ) {
1470
			$query   = array();
1471
			$query[] = new Give_Payment( $wp_query->query_vars['id'] );
1472
		} elseif ( isset( $wp_query->query_vars['purchasekey'] ) ) {
1473
			$query   = array();
1474
			$query[] = give_get_payment_by( 'key', $wp_query->query_vars['purchasekey'] );
1475
		} elseif ( isset( $wp_query->query_vars['email'] ) ) {
1476
			$args  = array(
1477
				'fields'     => 'ids',
1478
				'meta_key'   => '_give_payment_donor_email',
0 ignored issues
show
Detected usage of meta_key, possible slow query.
Loading history...
1479
				'meta_value' => $wp_query->query_vars['email'],
0 ignored issues
show
Detected usage of meta_value, possible slow query.
Loading history...
1480
				'number'     => $this->per_page(),
1481
				'page'       => $this->get_paged(),
1482
			);
1483
			$query = give_get_payments( $args );
1484
		} elseif ( isset( $wp_query->query_vars['date'] ) ) {
1485
1486
			$current_time = current_time( 'timestamp' );
1487
			$dates        = $this->get_dates( $args );
1488
			$start_date   = '';
1489
			$end_date     = '';
1490
1491
			/**
1492
			 *  Switch case for date query argument
1493
			 *
1494
			 * @since  1.8.8
1495
			 *
1496
			 * @params text date | today, yesterday or range
1497
			 * @params date startdate | required when date = range and format to be YYYYMMDD (i.e. 20170524)
1498
			 * @params date enddate | required when date = range and format to be YYYYMMDD (i.e. 20170524)
1499
			 */
1500
			switch ( $wp_query->query_vars['date'] ) {
1501
1502
				case 'today':
1503
1504
					// Set and Format Start and End Date to be date of today.
1505
					$start_date = $end_date = date( 'Y/m/d', $current_time );
1506
1507
					break;
1508
1509
				case 'yesterday':
1510
1511
					// Set and Format Start and End Date to be date of yesterday.
1512
					$start_date = $end_date = date( 'Y/m', $current_time ) . '/' . ( date( 'd', $current_time ) - 1 );
1513
1514
					break;
1515
1516
				case 'range':
1517
1518
					// Format Start Date and End Date for filtering payment based on date range.
1519
					$start_date = $dates['year'] . '/' . $dates['m_start'] . '/' . $dates['day_start'];
1520
					$end_date   = $dates['year_end'] . '/' . $dates['m_end'] . '/' . $dates['day_end'];
1521
1522
					break;
1523
1524
			}
1525
1526
			$args = array(
1527
				'fields'     => 'ids',
1528
				'start_date' => $start_date,
1529
				'end_date'   => $end_date,
1530
				'number'     => $this->per_page(),
1531
				'page'       => $this->get_paged(),
1532
			);
1533
1534
			$query = give_get_payments( $args );
1535
		} else {
1536
			$args  = array(
1537
				'fields' => 'ids',
1538
				'number' => $this->per_page(),
1539
				'page'   => $this->get_paged(),
1540
			);
1541
			$query = give_get_payments( $args );
1542
		}// End if().
1543
1544
		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...
1545
			$i = 0;
1546
			foreach ( $query as $payment ) {
1547
1548
				if ( is_numeric( $payment ) ) {
1549
					$payment      = new Give_Payment( $payment );
1550
					$payment_meta = $payment->get_meta();
0 ignored issues
show
$payment_meta is not used, you could remove the assignment.

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

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

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

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

Loading history...
1551
					$user_info    = $payment->user_info;
0 ignored issues
show
$user_info is not used, you could remove the assignment.

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

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

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

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

Loading history...
1552
				}
1553
1554
				$payment_meta = $payment->get_meta();
1555
				$user_info    = $payment->user_info;
1556
1557
				$first_name = isset( $user_info['first_name'] ) ? $user_info['first_name'] : '';
1558
				$last_name  = isset( $user_info['last_name'] ) ? $user_info['last_name'] : '';
1559
1560
				$donations['donations'][ $i ]['ID']             = $payment->ID;
1561
				$donations['donations'][ $i ]['number']         = $payment->number;
1562
				$donations['donations'][ $i ]['transaction_id'] = $payment->transaction_id;
1563
				$donations['donations'][ $i ]['key']            = $payment->key;
1564
				$donations['donations'][ $i ]['total']          = $payment->total;
1565
				$donations['donations'][ $i ]['status']         = give_get_payment_status( $payment, true );
1566
				$donations['donations'][ $i ]['gateway']        = $payment->gateway;
1567
				$donations['donations'][ $i ]['name']           = $first_name . ' ' . $last_name;
1568
				$donations['donations'][ $i ]['fname']          = $first_name;
1569
				$donations['donations'][ $i ]['lname']          = $last_name;
1570
				$donations['donations'][ $i ]['email']          = $payment->email;
1571
				$donations['donations'][ $i ]['date']           = $payment->date;
1572
				$donations['donations'][ $i ]['payment_meta']   = array();
1573
1574
				$form_id  = isset( $payment_meta['form_id'] ) ? $payment_meta['form_id'] : $payment_meta;
1575
				$price    = isset( $payment_meta['form_id'] ) ? give_get_form_price( $payment_meta['form_id'] ) : false;
1576
				$price_id = isset( $payment_meta['price_id'] ) ? $payment_meta['price_id'] : null;
1577
1578
				$donations['donations'][ $i ]['form']['id']    = $form_id;
1579
				$donations['donations'][ $i ]['form']['name']  = get_the_title( $payment_meta['form_id'] );
1580
				$donations['donations'][ $i ]['form']['price'] = $price;
1581
1582
				if ( give_has_variable_prices( $form_id ) ) {
1583
					if ( isset( $payment_meta['price_id'] ) ) {
1584
						$price_name                                         = give_get_price_option_name( $form_id, $payment_meta['price_id'], $payment->ID );
1585
						$donations['donations'][ $i ]['form']['price_name'] = $price_name;
1586
						$donations['donations'][ $i ]['form']['price_id']   = $price_id;
1587
						$donations['donations'][ $i ]['form']['price']      = give_get_price_option_amount( $form_id, $price_id );
1588
1589
					}
1590
				}
1591
1592
				if( ! empty( $payment_meta ) ) {
0 ignored issues
show
Space after opening control structure is required
Loading history...
No space before opening parenthesis is prohibited
Loading history...
1593
					// Add custom meta to API
1594
					foreach ( $payment_meta as $meta_key => $meta_value ) {
1595
1596
						$exceptions = array(
1597
							'form_title',
1598
							'form_id',
1599
							'price_id',
1600
							'user_info',
1601
							'key',
1602
							'email',
1603
							'date',
1604
						);
1605
1606
						// Don't clutter up results with dupes
1607
						if ( in_array( $meta_key, $exceptions ) ) {
1608
							continue;
1609
						}
1610
1611
						$donations['donations'][ $i ]['payment_meta'][ $meta_key ] = $meta_value;
1612
1613
					}
1614
				}
1615
1616
				$i ++;
1617
			}// End foreach().
1618
		}// End if().
1619
1620
		return apply_filters( 'give_api_donations_endpoint', $donations );
1621
	}
1622
1623
	/**
1624
	 * Retrieve the output format.
1625
	 *
1626
	 * Determines whether results should be displayed in XML or JSON.
1627
	 *
1628
	 * @since  1.1
1629
	 * @access public
1630
	 *
1631
	 * @return mixed
1632
	 */
1633
	public function get_output_format() {
1634
		global $wp_query;
1635
1636
		$format = isset( $wp_query->query_vars['format'] ) ? $wp_query->query_vars['format'] : 'json';
1637
1638
		return apply_filters( 'give_api_output_format', $format );
1639
	}
1640
1641
1642
	/**
1643
	 * Log each API request, if enabled.
1644
	 *
1645
	 * @access private
1646
	 * @since  1.1
1647
	 *
1648
	 * @global WP_Query     $wp_query
1649
	 *
1650
	 * @param array         $data
1651
	 *
1652
	 * @return void
1653
	 */
1654
	private function log_request( $data = array() ) {
1655
		if ( ! $this->log_requests ) {
1656
			return;
1657
		}
1658
1659
		/**
1660
		 * @var WP_Query $wp_query
1661
		 */
1662
		global $wp_query;
1663
1664
		$query = array(
1665
			'give-api'    => $wp_query->query_vars['give-api'],
1666
			'key'         => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null,
1667
			'token'       => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null,
1668
			'query'       => isset( $wp_query->query_vars['query'] ) ? $wp_query->query_vars['query'] : null,
1669
			'type'        => isset( $wp_query->query_vars['type'] ) ? $wp_query->query_vars['type'] : null,
1670
			'form'        => isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null,
1671
			'donor'       => isset( $wp_query->query_vars['donor'] ) ? $wp_query->query_vars['donor'] : null,
1672
			'date'        => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null,
1673
			'startdate'   => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null,
1674
			'enddate'     => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null,
1675
			'id'          => isset( $wp_query->query_vars['id'] ) ? $wp_query->query_vars['id'] : null,
1676
			'purchasekey' => isset( $wp_query->query_vars['purchasekey'] ) ? $wp_query->query_vars['purchasekey'] : null,
1677
			'email'       => isset( $wp_query->query_vars['email'] ) ? $wp_query->query_vars['email'] : null,
1678
		);
1679
1680
		$log_data = array(
1681
			'log_type'     => 'api_request',
1682
			'post_excerpt' => http_build_query( $query ),
1683
			'post_content' => ! empty( $data['error'] ) ? $data['error'] : '',
1684
		);
1685
1686
		$log_meta = array(
1687
			'api_query'  => http_build_query( $query ),
1688
			'request_ip' => give_get_ip(),
1689
			'user'       => $this->user_id,
1690
			'key'        => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null,
1691
			'token'      => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null,
1692
			'time'       => $data['request_speed'],
1693
			'version'    => $this->get_queried_version(),
1694
		);
1695
1696
		Give()->logs->insert_log( $log_data, $log_meta );
1697
	}
1698
1699
1700
	/**
1701
	 * Retrieve the output data.
1702
	 *
1703
	 * @access public
1704
	 * @since  1.1
1705
	 * @return array
1706
	 */
1707
	public function get_output() {
1708
		return $this->data;
1709
	}
1710
1711
	/**
1712
	 * Output Query in either JSON/XML.
1713
	 * The query data is outputted as JSON by default.
1714
	 *
1715
	 * @since 1.1
1716
	 * @global WP_Query $wp_query
1717
	 *
1718
	 * @param int       $status_code
1719
	 */
1720
	public function output( $status_code = 200 ) {
1721
1722
		$format = $this->get_output_format();
1723
1724
		status_header( $status_code );
1725
1726
		/**
1727
		 * Fires before outputting the API.
1728
		 *
1729
		 * @since 1.1
1730
		 *
1731
		 * @param array    $data   Response data to return.
1732
		 * @param Give_API $this   The Give_API object.
1733
		 * @param string   $format Output format, XML or JSON. Default is JSON.
1734
		 */
1735
		do_action( 'give_api_output_before', $this->data, $this, $format );
1736
1737
		switch ( $format ) :
1738
1739
			case 'xml' :
1740
1741
				require_once GIVE_PLUGIN_DIR . 'includes/libraries/array2xml.php';
1742
				$xml = Array2XML::createXML( 'give', $this->data );
1743
				echo $xml->saveXML();
0 ignored issues
show
Expected next thing to be a escaping function, not '$xml'
Loading history...
1744
1745
				break;
1746
1747
			case 'json' :
1748
1749
				header( 'Content-Type: application/json' );
1750
				if ( ! empty( $this->pretty_print ) ) {
1751
					echo json_encode( $this->data, $this->pretty_print );
1752
				} else {
1753
					echo json_encode( $this->data );
1754
				}
1755
1756
				break;
1757
1758
			default :
1759
1760
				/**
1761
				 * Fires by the API while outputting other formats.
1762
				 *
1763
				 * @since 1.1
1764
				 *
1765
				 * @param array    $data Response data to return.
1766
				 * @param Give_API $this The Give_API object.
1767
				 */
1768
				do_action( "give_api_output_{$format}", $this->data, $this );
1769
1770
				break;
1771
1772
		endswitch;
1773
1774
		/**
1775
		 * Fires after outputting the API.
1776
		 *
1777
		 * @since 1.1
1778
		 *
1779
		 * @param array    $data   Response data to return.
1780
		 * @param Give_API $this   The Give_API object.
1781
		 * @param string   $format Output format, XML or JSON. Default is JSON.
1782
		 */
1783
		do_action( 'give_api_output_after', $this->data, $this, $format );
1784
1785
		give_die();
1786
	}
1787
1788
	/**
1789
	 * Modify User Profile
1790
	 *
1791
	 * Modifies the output of profile.php to add key generation/revocation.
1792
	 *
1793
	 * @access public
1794
	 * @since  1.1
1795
	 *
1796
	 * @param object $user Current user info
1797 2
	 *
1798 2
	 * @return void
1799
	 */
1800
	function user_key_field( $user ) {
0 ignored issues
show
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...
1801
1802
		if ( ( give_get_option( 'api_allow_user_keys', false ) || current_user_can( 'manage_give_settings' ) ) && current_user_can( 'edit_user', $user->ID ) ) {
1803
1804
			$user = get_userdata( $user->ID );
1805
			?>
1806
			<table class="form-table">
1807
				<tbody>
1808
				<tr>
1809
					<th>
1810
						<?php _e( 'Give API Keys', 'give' ); ?>
1811
					</th>
1812
					<td>
1813
						<?php
1814 2
						$public_key = $this->get_user_public_key( $user->ID );
1815 2
						$secret_key = $this->get_user_secret_key( $user->ID );
1816
						?>
1817 2
						<?php if ( empty( $user->give_user_public_key ) ) { ?>
1818
							<input name="give_set_api_key" type="checkbox" id="give_set_api_key" />
1819 2
							<span class="description"><?php _e( 'Generate API Key', 'give' ); ?></span>
1820 2
						<?php } else { ?>
1821
							<strong style="display:inline-block; width: 125px;"><?php _e( 'Public key:', 'give' ); ?>
1822 2
								&nbsp;</strong>
1823 2
							<input type="text" disabled="disabled" class="regular-text" id="publickey" value="<?php echo esc_attr( $public_key ); ?>" />
1824 2
							<br />
1825
							<strong style="display:inline-block; width: 125px;"><?php _e( 'Secret key:', 'give' ); ?>
1826 2
								&nbsp;</strong>
1827 2
							<input type="text" disabled="disabled" class="regular-text" id="privatekey" value="<?php echo esc_attr( $secret_key ); ?>" />
1828 2
							<br />
1829
							<strong style="display:inline-block; width: 125px;"><?php _e( 'Token:', 'give' ); ?>
1830
								&nbsp;</strong>
1831 2
							<input type="text" disabled="disabled" class="regular-text" id="token" value="<?php echo esc_attr( $this->get_token( $user->ID ) ); ?>" />
1832 2
							<br />
1833
							<input name="give_revoke_api_key" type="checkbox" id="give_revoke_api_key" />
1834
							<span class="description"><label for="give_revoke_api_key"><?php _e( 'Revoke API Keys', 'give' ); ?></label></span>
1835
						<?php } ?>
1836
					</td>
1837
				</tr>
1838
				</tbody>
1839
			</table>
1840
		<?php }// End if().
1841
	}
1842
1843
	/**
1844 2
	 * Process an API key generation/revocation
1845 2
	 *
1846 2
	 * @access public
1847
	 * @since  1.1
1848 2
	 *
1849
	 * @param array $args
1850
	 *
1851
	 * @return void
1852
	 */
1853
	public function process_api_key( $args ) {
1854
1855 View Code Duplication
		if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'give-api-nonce' ) ) {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
Detected access of super global var $_REQUEST, probably need manual inspection.
Loading history...
Detected usage of a non-validated input variable: $_REQUEST
Loading history...
Detected usage of a non-sanitized input variable: $_REQUEST
Loading history...
1856
			wp_die( __( 'Nonce verification failed.', 'give' ), __( 'Error', 'give' ), array(
1857
				'response' => 403,
1858
			) );
1859
		}
1860
1861 2 View Code Duplication
		if ( empty( $args['user_id'] ) ) {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
1862 2
			wp_die( __( 'User ID Required.', 'give' ), __( 'Error', 'give' ), array(
1863 2
				'response' => 401,
1864
			) );
1865 2
		}
1866
1867
		if ( is_numeric( $args['user_id'] ) ) {
1868
			$user_id = isset( $args['user_id'] ) ? absint( $args['user_id'] ) : get_current_user_id();
1869
		} else {
1870
			$userdata = get_user_by( 'login', $args['user_id'] );
1871
			$user_id  = $userdata->ID;
1872
		}
1873
		$process = isset( $args['give_api_process'] ) ? strtolower( $args['give_api_process'] ) : false;
1874
1875
		if ( $user_id == get_current_user_id() && ! give_get_option( 'allow_user_api_keys' ) && ! current_user_can( 'manage_give_settings' ) ) {
1876
			wp_die( sprintf( /* translators: %s: process */
1877
				__( 'You do not have permission to %s API keys for this user.', 'give' ), $process ), __( 'Error', 'give' ), array(
0 ignored issues
show
This line of the multi-line function call does not seem to be indented correctly. Expected 12 spaces, but found 16.
Loading history...
1878
				'response' => 403,
1879
			) );
1880 View Code Duplication
		} elseif ( ! current_user_can( 'manage_give_settings' ) ) {
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
1881
			wp_die( sprintf( /* translators: %s: process */
1882
				__( 'You do not have permission to %s API keys for this user.', 'give' ), $process ), __( 'Error', 'give' ), array(
0 ignored issues
show
This line of the multi-line function call does not seem to be indented correctly. Expected 12 spaces, but found 16.
Loading history...
1883
				'response' => 403,
1884
			) );
1885
		}
1886
1887
		switch ( $process ) {
1888
			case 'generate':
1889
				if ( $this->generate_api_key( $user_id ) ) {
1890
					Give_Cache::delete( Give_Cache::get_key( 'give_total_api_keys' ) );
1891
					wp_redirect( add_query_arg( 'give-messages[]', 'api-key-generated', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1892
					exit();
1893
				} else {
1894
					wp_redirect( add_query_arg( 'give-messages[]', 'api-key-failed', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1895
					exit();
1896
				}
1897
				break;
0 ignored issues
show
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...
1898 View Code Duplication
			case 'regenerate':
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
1899
				$this->generate_api_key( $user_id, true );
1900
				Give_Cache::delete( Give_Cache::get_key( 'give_total_api_keys' ) );
1901
				wp_redirect( add_query_arg( 'give-messages[]', 'api-key-regenerated', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1902
				exit();
1903
				break;
0 ignored issues
show
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...
1904 View Code Duplication
			case 'revoke':
0 ignored issues
show
This code seems to be duplicated across your project.

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

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

Loading history...
1905
				$this->revoke_api_key( $user_id );
1906
				Give_Cache::delete( Give_Cache::get_key( 'give_total_api_keys' ) );
1907
				wp_redirect( add_query_arg( 'give-messages[]', 'api-key-revoked', 'edit.php?post_type=give_forms&page=give-tools&tab=api' ) );
1908
				exit();
1909
				break;
0 ignored issues
show
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...
1910
			default;
1911
				break;
1912
		}
1913
	}
1914
1915
	/**
1916
	 * Generate new API keys for a user
1917
	 *
1918
	 * @param int     $user_id    User ID the key is being generated for.
1919
	 * @param boolean $regenerate Regenerate the key for the user.
1920
	 *
1921
	 * @access public
1922
	 * @since  1.1
1923
	 *
1924
	 * @return boolean True if (re)generated successfully, false otherwise.
1925
	 */
1926
	public function generate_api_key( $user_id = 0, $regenerate = false ) {
1927
1928
		// Bail out, if user doesn't exists.
1929
		if ( empty( $user_id ) ) {
1930
			return false;
1931
		}
1932
1933
		$user = get_userdata( $user_id );
1934 56
1935
		// Bail Out, if user object doesn't exists.
1936 56
		if ( ! $user ) {
1937 56
			return false;
1938
		}
1939
1940
		$new_public_key = '';
1941
		$new_secret_key = '';
1942
1943
		if( ! empty( $_POST['from'] ) && 'profile' === $_POST['from'] ) {
0 ignored issues
show
Space after opening control structure is required
Loading history...
No space before opening parenthesis is prohibited
Loading history...
1944
			// For User Profile Page.
1945
			if( ! empty( $_POST['give_set_api_key'] ) ) {
0 ignored issues
show
Space after opening control structure is required
Loading history...
No space before opening parenthesis is prohibited
Loading history...
1946
				// Generate API Key from User Profile page.
1947
				$new_public_key = $this->generate_public_key( $user->user_email );
1948
				$new_secret_key = $this->generate_private_key( $user->ID );
1949
			} elseif ( ! empty( $_POST['give_revoke_api_key'] ) ) {
0 ignored issues
show
Detected access of super global var $_POST, probably need manual inspection.
Loading history...
1950
				// Revoke API Key from User Profile page.
1951
				$this->revoke_api_key( $user->ID );
1952
			} else {
1953
				return false;
1954
			}
1955
		} else {
1956
			// For Tools > API page.
1957
			$public_key = $this->get_user_public_key( $user_id );
1958
1959
			if ( empty( $public_key ) && ! $regenerate ) {
1960
				// Generating API for first time.
1961
				$new_public_key = $this->generate_public_key( $user->user_email );
1962
				$new_secret_key = $this->generate_private_key( $user->ID );
1963
			} elseif ( $public_key && $regenerate ) {
1964
				// API Key already exists and Regenerating API Key.
1965
				$this->revoke_api_key( $user->ID );
1966
				$new_public_key = $this->generate_public_key( $user->user_email );
1967
				$new_secret_key = $this->generate_private_key( $user->ID );
1968
			} elseif ( ! empty( $public_key ) && ! $regenerate ) {
1969
				// Doing nothing, when API Key exists but still try to generate again instead of regenerating.
1970
				return false;
1971
			} else {
1972
				// Revoke API Key.
1973
				$this->revoke_api_key( $user->ID );
1974
			}
1975
		}
1976
1977
		update_user_meta( $user_id, $new_public_key, 'give_user_public_key' );
0 ignored issues
show
update_user_meta() usage is highly discouraged, check VIP documentation on "Working with wp_users"
Loading history...
1978
		update_user_meta( $user_id, $new_secret_key, 'give_user_secret_key' );
0 ignored issues
show
update_user_meta() usage is highly discouraged, check VIP documentation on "Working with wp_users"
Loading history...
1979
1980
		return true;
1981
	}
1982
1983
	/**
1984
	 * Revoke a users API keys
1985
	 *
1986
	 * @access public
1987
	 * @since  1.1
1988
	 *
1989
	 * @param int $user_id User ID of user to revoke key for
1990
	 *
1991
	 * @return bool
1992
	 */
1993
	public function revoke_api_key( $user_id = 0 ) {
1994
1995
		if ( empty( $user_id ) ) {
1996
			return false;
1997
		}
1998
1999
		$user = get_userdata( $user_id );
2000
2001
		if ( ! $user ) {
2002
			return false;
2003
		}
2004
2005
		$public_key = $this->get_user_public_key( $user_id );
2006
		$secret_key = $this->get_user_secret_key( $user_id );
2007
		if ( ! empty( $public_key ) ) {
2008
			Give_Cache::delete( Give_Cache::get_key( md5( 'give_api_user_' . $public_key ) ) );
2009
			Give_Cache::delete( Give_Cache::get_key( md5( 'give_api_user_public_key' . $user_id ) ) );
2010
			Give_Cache::delete( Give_Cache::get_key( md5( 'give_api_user_secret_key' . $user_id ) ) );
2011
			delete_user_meta( $user_id, $public_key );
0 ignored issues
show
delete_user_meta() usage is highly discouraged, check VIP documentation on "Working with wp_users"
Loading history...
2012
			delete_user_meta( $user_id, $secret_key );
0 ignored issues
show
delete_user_meta() usage is highly discouraged, check VIP documentation on "Working with wp_users"
Loading history...
2013
		} else {
2014
			return false;
2015
		}
2016
2017
		return true;
2018
	}
2019
2020
	public function get_version() {
2021
		return self::VERSION;
2022
	}
2023
2024
	/**
2025
	 * Generate the public key for a user
2026
	 *
2027
	 * @access private
2028
	 * @since  1.1
2029
	 *
2030
	 * @param string $user_email
2031
	 *
2032
	 * @return string
2033
	 */
2034
	private function generate_public_key( $user_email = '' ) {
2035
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
2036
		$public   = hash( 'md5', $user_email . $auth_key . date( 'U' ) );
2037
2038
		return $public;
2039
	}
2040
2041
	/**
2042
	 * Generate the secret key for a user
2043
	 *
2044
	 * @access private
2045
	 * @since  1.1
2046
	 *
2047
	 * @param int $user_id
2048
	 *
2049
	 * @return string
2050
	 */
2051
	private function generate_private_key( $user_id = 0 ) {
2052
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
2053
		$secret   = hash( 'md5', $user_id . $auth_key . date( 'U' ) );
2054
2055
		return $secret;
2056
	}
2057
2058
	/**
2059
	 * Retrieve the user's token
2060
	 *
2061
	 * @access private
2062
	 * @since  1.1
2063
	 *
2064
	 * @param int $user_id
2065
	 *
2066
	 * @return string
2067
	 */
2068
	public function get_token( $user_id = 0 ) {
2069
		return hash( 'md5', $this->get_user_secret_key( $user_id ) . $this->get_user_public_key( $user_id ) );
2070
	}
2071
2072
	/**
2073
	 * Generate the default donation stats returned by the 'stats' endpoint
2074
	 *
2075
	 * @access private
2076
	 * @since  1.1
2077
	 * @return array default sales statistics
2078
	 */
2079 View Code Duplication
	private function get_default_sales_stats() {
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

Loading history...
2080
2081
		// Default sales return
2082
		$donations                               = array();
2083
		$donations['donations']['today']         = $this->stats->get_sales( 0, 'today' );
2084
		$donations['donations']['current_month'] = $this->stats->get_sales( 0, 'this_month' );
2085
		$donations['donations']['last_month']    = $this->stats->get_sales( 0, 'last_month' );
2086
		$donations['donations']['totals']        = give_get_total_donations();
2087
2088
		return $donations;
2089
	}
2090
2091
	/**
2092
	 * Generate the default earnings stats returned by the 'stats' endpoint
2093
	 *
2094
	 * @access private
2095
	 * @since  1.1
2096
	 * @return array default earnings statistics
2097
	 */
2098 View Code Duplication
	private function get_default_earnings_stats() {
0 ignored issues
show
This method seems to be duplicated in your project.

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

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

Loading history...
2099
2100
		// Default earnings return
2101
		$earnings                              = array();
2102
		$earnings['earnings']['today']         = $this->stats->get_earnings( 0, 'today' );
2103
		$earnings['earnings']['current_month'] = $this->stats->get_earnings( 0, 'this_month' );
2104
		$earnings['earnings']['last_month']    = $this->stats->get_earnings( 0, 'last_month' );
2105
		$earnings['earnings']['totals']        = give_get_total_earnings();
2106
2107
		return $earnings;
2108
	}
2109
2110
	/**
2111
	 * API Key Backwards Compatibility
2112
	 *
2113
	 * A Backwards Compatibility call for the change of meta_key/value for users API Keys.
2114
	 *
2115
	 * @since  1.3.6
2116
	 *
2117
	 * @param  string $check     Whether to check the cache or not
2118
	 * @param  int    $object_id The User ID being passed
2119
	 * @param  string $meta_key  The user meta key
2120
	 * @param  bool   $single    If it should return a single value or array
2121
	 *
2122
	 * @return string            The API key/secret for the user supplied
2123
	 */
2124
	public function api_key_backwards_compat( $check, $object_id, $meta_key, $single ) {
2125
2126
		if ( $meta_key !== 'give_user_public_key' && $meta_key !== 'give_user_secret_key' ) {
0 ignored issues
show
Found "!== '". Use Yoda Condition checks, you must
Loading history...
2127
			return $check;
2128
		}
2129
2130
		$return = $check;
2131
2132
		switch ( $meta_key ) {
2133
			case 'give_user_public_key':
2134
				$return = Give()->api->get_user_public_key( $object_id );
2135
				break;
2136
			case 'give_user_secret_key':
2137
				$return = Give()->api->get_user_secret_key( $object_id );
2138
				break;
2139
		}
2140
2141
		if ( ! $single ) {
2142
			$return = array( $return );
2143
		}
2144
2145
		return $return;
2146
2147
	}
2148
2149
}
2150