Completed
Pull Request — master (#753)
by
unknown
32:33 queued 27:43
created

Give_API::get_customers()   C

Complexity

Conditions 10
Paths 23

Size

Total Lines 85
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 43
CRAP Score 10.5186

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 10
eloc 50
c 1
b 0
f 1
nc 23
nop 1
dl 0
loc 85
ccs 43
cts 52
cp 0.8269
crap 10.5186
rs 5.3454

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

Loading history...
2
/**
3
 * Give API
4
 *
5
 * A front-facing JSON/XML API that makes it possible to query donation data.
6
 *
7
 * @package     Give
8
 * @subpackage  Classes/API
9
 * @copyright   Copyright (c) 2016, WordImpress
10
 * @license     http://opensource.org/licenses/gpl-2.0.php 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 private
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 private
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
	 *
89
	 * @var bool
90
	 * @access private
91
	 * @since  1.1
92
	 */
93
	public $override = true;
94
95
	/**
96
	 * Version of the API queried
97
	 *
98
	 * @var string
99
	 * @access public
100
	 * @since  1.1
101
	 */
102
	private $queried_version;
103
104
	/**
105
	 * All versions of the API
106
	 *
107
	 * @var string
108
	 * @access public
109
	 * @since  1.1
110
	 */
111
	protected $versions = array();
112
113
	/**
114
	 * Queried endpoint
115
	 *
116
	 * @var string
117
	 * @access public
118
	 * @since  1.1
119
	 */
120
	private $endpoint;
121
122
	/**
123
	 * Endpoints routes
124
	 *
125
	 * @var object
126
	 * @access public
127
	 * @since  1.1
128
	 */
129
	private $routes;
130
131
	/**
132
	 * Setup the Give API
133
	 *
134
	 * @since 1.1
135
	 */
136 13
	public function __construct() {
137
138 13
		$this->versions = array(
139 13
			'v1' => 'GIVE_API_V1',
140
		);
141
142 13
		foreach ( $this->get_versions() as $version => $class ) {
0 ignored issues
show
Bug introduced by
The expression $this->get_versions() of type string is not traversable.
Loading history...
143 13
			require_once GIVE_PLUGIN_DIR . 'includes/api/class-give-api-' . $version . '.php';
144 13
		}
145
146 13
		add_action( 'init', array( $this, 'add_endpoint' ) );
147 13
		add_action( 'wp', array( $this, 'process_query' ), - 1 );
148 13
		add_filter( 'query_vars', array( $this, 'query_vars' ) );
149 13
		add_action( 'show_user_profile', array( $this, 'user_key_field' ) );
150 13
		add_action( 'edit_user_profile', array( $this, 'user_key_field' ) );
151 13
		add_action( 'personal_options_update', array( $this, 'update_key' ) );
152 13
		add_action( 'edit_user_profile_update', array( $this, 'update_key' ) );
153 13
		add_action( 'give_process_api_key', array( $this, 'process_api_key' ) );
154
155
		// Setup a backwards compatibility check for user API Keys
156 13
		add_filter( 'get_user_metadata', array( $this, 'api_key_backwards_compat' ), 10, 4 );
157
158
		// Determine if JSON_PRETTY_PRINT is available
159 13
		$this->pretty_print = defined( 'JSON_PRETTY_PRINT' ) ? JSON_PRETTY_PRINT : null;
160
161
		// Allow API request logging to be turned off
162 13
		$this->log_requests = apply_filters( 'give_api_log_requests', $this->log_requests );
163
164
		// Setup Give_Payment_Stats instance
165 13
		$this->stats = new Give_Payment_Stats;
166
167 13
	}
168
169
	/**
170
	 * Registers a new rewrite endpoint for accessing the API
171
	 *
172
	 * @access public
173
	 *
174
	 * @param array $rewrite_rules WordPress Rewrite Rules
175
	 *
176
	 * @since  1.1
177
	 */
178 11
	public function add_endpoint( $rewrite_rules ) {
0 ignored issues
show
Unused Code introduced by
The parameter $rewrite_rules is not used and could be removed.

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

Loading history...
179 11
		add_rewrite_endpoint( 'give-api', EP_ALL );
180 11
	}
181
182
	/**
183
	 * Registers query vars for API access
184
	 *
185
	 * @access public
186
	 * @since  1.1
187
	 *
188
	 * @param array $vars Query vars
189
	 *
190
	 * @return string[] $vars New query vars
191
	 */
192 4
	public function query_vars( $vars ) {
193
194 4
		$vars[] = 'token';
195 4
		$vars[] = 'key';
196 4
		$vars[] = 'query';
197 4
		$vars[] = 'type';
198 4
		$vars[] = 'form';
199 4
		$vars[] = 'number';
200 4
		$vars[] = 'date';
201 4
		$vars[] = 'startdate';
202 4
		$vars[] = 'enddate';
203 4
		$vars[] = 'donor';
204 4
		$vars[] = 'format';
205 4
		$vars[] = 'id';
206 4
		$vars[] = 'purchasekey';
207 4
		$vars[] = 'email';
208
209 4
		return $vars;
210
	}
211
212
	/**
213
	 * Retrieve the API versions
214
	 *
215
	 * @access public
216
	 * @since  1.1
217
	 * @return array
218
	 */
219 13
	public function get_versions() {
220 13
		return $this->versions;
221
	}
222
223
	/**
224
	 * Retrieve the API version that was queried
225
	 *
226
	 * @access public
227
	 * @since  1.1
228
	 * @return string
229
	 */
230
	public function get_queried_version() {
231
		return $this->queried_version;
232
	}
233
234
	/**
235
	 * Retrieves the default version of the API to use
236
	 *
237
	 * @access private
238
	 * @since  1.1
239
	 * @return string
240
	 */
241 1
	public function get_default_version() {
242
243 1
		$version = get_option( 'give_default_api_version' );
244
245 1
		if ( defined( 'GIVE_API_VERSION' ) ) {
246 1
			$version = GIVE_API_VERSION;
247 1
		} elseif ( ! $version ) {
248
			$version = 'v1';
249
		}
250
251 1
		return $version;
252
	}
253
254
	/**
255
	 * Sets the version of the API that was queried.
256
	 *
257
	 * Falls back to the default version if no version is specified
258
	 *
259
	 * @access private
260
	 * @since  1.1
261
	 */
262
	private function set_queried_version() {
263
264
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
265
266
		$version = $wp_query->query_vars['give-api'];
267
268
		if ( strpos( $version, '/' ) ) {
269
270
			$version = explode( '/', $version );
271
			$version = strtolower( $version[0] );
272
273
			$wp_query->query_vars['give-api'] = str_replace( $version . '/', '', $wp_query->query_vars['give-api'] );
274
275
			if ( array_key_exists( $version, $this->versions ) ) {
276
277
				$this->queried_version = $version;
278
279
			} else {
280
281
				$this->is_valid_request = false;
282
				$this->invalid_version();
283
			}
284
285
		} else {
286
287
			$this->queried_version = $this->get_default_version();
288
289
		}
290
291
	}
292
293
	/**
294
	 * Validate the API request
295
	 *
296
	 * Checks for the user's public key and token against the secret key
297
	 *
298
	 * @access private
299
	 * @global object $wp_query WordPress Query
300
	 * @uses   Give_API::get_user()
301
	 * @uses   Give_API::invalid_key()
302
	 * @uses   Give_API::invalid_auth()
303
	 * @since  1.1
304
	 * @return void
305
	 */
306
	private function validate_request() {
307
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
355
356 1
		if ( empty( $key ) ) {
357
			$key = urldecode( $wp_query->query_vars['key'] );
358
		}
359
360 1
		if ( empty( $key ) ) {
361
			return false;
362
		}
363
364 1
		$user = get_transient( md5( 'give_api_user_' . $key ) );
365
366 1
		if ( false === $user ) {
367 1
			$user = $wpdb->get_var( $wpdb->prepare( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = %s LIMIT 1", $key ) );
368 1
			set_transient( md5( 'give_api_user_' . $key ), $user, DAY_IN_SECONDS );
369 1
		}
370
371 1
		if ( $user != null ) {
372 1
			$this->user_id = $user;
373
374 1
			return $user;
375
		}
376
377
		return false;
378
	}
379
380 11
	public function get_user_public_key( $user_id = 0 ) {
381 2
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
382
383 2
		if ( empty( $user_id ) ) {
384
			return '';
385
		}
386
387 2
		$cache_key       = md5( 'give_api_user_public_key' . $user_id );
388 2
		$user_public_key = get_transient( $cache_key );
389
390 2
		if ( empty( $user_public_key ) ) {
391 2
			$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 ) );
392 2
			set_transient( $cache_key, $user_public_key, HOUR_IN_SECONDS );
393 2
		}
394
395 11
		return $user_public_key;
396
	}
397
398 11
	public function get_user_secret_key( $user_id = 0 ) {
399 2
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
493
494
		// Start logging how long the request takes for logging
495 3
		$before = microtime( true );
496
497
		// Check for give-api var. Get out if not present
498 3
		if ( empty( $wp_query->query_vars['give-api'] ) ) {
499 3
			return;
500
		}
501
502
		// Determine which version was queried
503
		$this->set_queried_version();
504
505
		// Determine the kind of query
506
		$this->set_query_mode();
507
508
		// Check for a valid user and set errors if necessary
509
		$this->validate_request();
510
511
		// Only proceed if no errors have been noted
512
		if ( ! $this->is_valid_request ) {
513
			return;
514
		}
515
516
		if ( ! defined( 'GIVE_DOING_API' ) ) {
517
			define( 'GIVE_DOING_API', true );
518
		}
519
520
		$data         = array();
521
		$this->routes = new $this->versions[$this->get_queried_version()];
522
		$this->routes->validate_request();
523
524
		switch ( $this->endpoint ) :
525
526
			case 'stats' :
527
528
				$data = $this->routes->get_stats( array(
529
					'type'      => isset( $wp_query->query_vars['type'] ) ? $wp_query->query_vars['type'] : null,
530
					'form'      => isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null,
531
					'date'      => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null,
532
					'startdate' => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null,
533
					'enddate'   => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null
534
				) );
535
536
				break;
537
538
			case 'forms' :
539
540
				$form = isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null;
541
542
				$data = $this->routes->get_forms( $form );
543
544
				break;
545
546
			case 'donors' :
547
548
				$customer = isset( $wp_query->query_vars['donor'] ) ? $wp_query->query_vars['donor'] : null;
549
550
				$data = $this->routes->get_customers( $customer );
551
552
				break;
553
554
			case 'donations' :
555
556
				$data = $this->routes->get_recent_donations();
557
558
				break;
559
560
		endswitch;
561
562
		// Allow extensions to setup their own return data
563
		$this->data = apply_filters( 'give_api_output_data', $data, $this->endpoint, $this );
564
565
		$after                       = microtime( true );
566
		$request_time                = ( $after - $before );
567
		$this->data['request_speed'] = $request_time;
568
569
		// Log this API request, if enabled. We log it here because we have access to errors.
570
		$this->log_request( $this->data );
571
572
		// Send out data to the output function
573
		$this->output();
574
	}
575
576
	/**
577
	 * Returns the API endpoint requested
578
	 *
579
	 * @access private
580
	 * @since  1.1
581
	 * @return string $query Query mode
582
	 */
583
	public function get_query_mode() {
584
585
		return $this->endpoint;
586
	}
587
588
	/**
589
	 * Determines the kind of query requested and also ensure it is a valid query
590
	 *
591
	 * @access private
592
	 * @since  1.1
593
	 * @global $wp_query
594
	 */
595
	public function set_query_mode() {
596
597
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
598
599
		// Whitelist our query options
600
		$accepted = apply_filters( 'give_api_valid_query_modes', array(
601
			'stats',
602
			'forms',
603
			'donors',
604
			'donations'
605
		) );
606
607
		$query = isset( $wp_query->query_vars['give-api'] ) ? $wp_query->query_vars['give-api'] : null;
608
		$query = str_replace( $this->queried_version . '/', '', $query );
609
610
		$error = array();
611
612
		// Make sure our query is valid
613
		if ( ! in_array( $query, $accepted ) ) {
614
			$error['error'] = __( 'Invalid query!', 'give' );
615
616
			$this->data = $error;
617
			// 400 is Bad Request
618
			$this->output( 400 );
619
		}
620
621
		$this->endpoint = $query;
622
	}
623
624
	/**
625
	 * Get page number
626
	 *
627
	 * @access private
628
	 * @since  1.1
629
	 * @global $wp_query
630
	 * @return int $wp_query->query_vars['page'] if page number returned (default: 1)
631
	 */
632 11
	public function get_paged() {
633 11
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
634
635 11
		return isset( $wp_query->query_vars['page'] ) ? $wp_query->query_vars['page'] : 1;
636
	}
637
638
639
	/**
640
	 * Number of results to display per page
641
	 *
642
	 * @access private
643
	 * @since  1.1
644
	 * @global $wp_query
645
	 * @return int $per_page Results to display per page (default: 10)
646
	 */
647 11
	public function per_page() {
648 11
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
649
650 11
		$per_page = isset( $wp_query->query_vars['number'] ) ? $wp_query->query_vars['number'] : 10;
651
652 11
		if ( $per_page < 0 && $this->get_query_mode() == 'donors' ) {
653
			$per_page = 99999999;
654
		} // Customers query doesn't support -1
655
656 11
		return apply_filters( 'give_api_results_per_page', $per_page );
657
	}
658
659
	/**
660
	 * Sets up the dates used to retrieve earnings/donations
661
	 *
662
	 * @access public
663
	 * @since  1.2
664
	 *
665
	 * @param array $args Arguments to override defaults
666
	 *
667
	 * @return array $dates
668
	 */
669
	public function get_dates( $args = array() ) {
670
		$dates = array();
671
672
		$defaults = array(
673
			'type'      => '',
674
			'form'      => null,
675
			'date'      => null,
676
			'startdate' => null,
677
			'enddate'   => null
678
		);
679
680
		$args = wp_parse_args( $args, $defaults );
681
682
		$current_time = current_time( 'timestamp' );
683
684
		if ( 'range' === $args['date'] ) {
685
			$startdate          = strtotime( $args['startdate'] );
686
			$enddate            = strtotime( $args['enddate'] );
687
			$dates['day_start'] = date( 'd', $startdate );
688
			$dates['day_end']   = date( 'd', $enddate );
689
			$dates['m_start']   = date( 'n', $startdate );
690
			$dates['m_end']     = date( 'n', $enddate );
691
			$dates['year']      = date( 'Y', $startdate );
692
			$dates['year_end']  = date( 'Y', $enddate );
693
		} else {
694
			// Modify dates based on predefined ranges
695
			switch ( $args['date'] ) :
696
697
				case 'this_month' :
698
					$dates['day']     = null;
699
					$dates['m_start'] = date( 'n', $current_time );
700
					$dates['m_end']   = date( 'n', $current_time );
701
					$dates['year']    = date( 'Y', $current_time );
702
					break;
703
704
				case 'last_month' :
705
					$dates['day']     = null;
706
					$dates['m_start'] = date( 'n', $current_time ) == 1 ? 12 : date( 'n', $current_time ) - 1;
707
					$dates['m_end']   = $dates['m_start'];
708
					$dates['year']    = date( 'n', $current_time ) == 1 ? date( 'Y', $current_time ) - 1 : date( 'Y', $current_time );
709
					break;
710
711
				case 'today' :
712
					$dates['day']     = date( 'd', $current_time );
713
					$dates['m_start'] = date( 'n', $current_time );
714
					$dates['m_end']   = date( 'n', $current_time );
715
					$dates['year']    = date( 'Y', $current_time );
716
					break;
717
718
				case 'yesterday' :
719
720
					$year  = date( 'Y', $current_time );
721
					$month = date( 'n', $current_time );
722
					$day   = date( 'd', $current_time );
723
724
					if ( $month == 1 && $day == 1 ) {
725
726
						$year -= 1;
727
						$month = 12;
728
						$day   = cal_days_in_month( CAL_GREGORIAN, $month, $year );
729
730
					} elseif ( $month > 1 && $day == 1 ) {
731
732
						$month -= 1;
733
						$day = cal_days_in_month( CAL_GREGORIAN, $month, $year );
734
735
					} else {
736
737
						$day -= 1;
738
739
					}
740
741
					$dates['day']     = $day;
742
					$dates['m_start'] = $month;
743
					$dates['m_end']   = $month;
744
					$dates['year']    = $year;
745
746
					break;
747
748
				case 'this_quarter' :
749
					$month_now = date( 'n', $current_time );
750
751
					$dates['day'] = null;
752
753
					if ( $month_now <= 3 ) {
754
755
						$dates['m_start'] = 1;
756
						$dates['m_end']   = 3;
757
						$dates['year']    = date( 'Y', $current_time );
758
759
					} else if ( $month_now <= 6 ) {
760
761
						$dates['m_start'] = 4;
762
						$dates['m_end']   = 6;
763
						$dates['year']    = date( 'Y', $current_time );
764
765
					} else if ( $month_now <= 9 ) {
766
767
						$dates['m_start'] = 7;
768
						$dates['m_end']   = 9;
769
						$dates['year']    = date( 'Y', $current_time );
770
771
					} else {
772
773
						$dates['m_start'] = 10;
774
						$dates['m_end']   = 12;
775
						$dates['year']    = date( 'Y', $current_time );
776
777
					}
778
					break;
779
780
				case 'last_quarter' :
781
					$month_now = date( 'n', $current_time );
782
783
					$dates['day'] = null;
784
785
					if ( $month_now <= 3 ) {
786
787
						$dates['m_start'] = 10;
788
						$dates['m_end']   = 12;
789
						$dates['year']    = date( 'Y', $current_time ) - 1; // Previous year
790
791
					} else if ( $month_now <= 6 ) {
792
793
						$dates['m_start'] = 1;
794
						$dates['m_end']   = 3;
795
						$dates['year']    = date( 'Y', $current_time );
796
797
					} else if ( $month_now <= 9 ) {
798
799
						$dates['m_start'] = 4;
800
						$dates['m_end']   = 6;
801
						$dates['year']    = date( 'Y', $current_time );
802
803
					} else {
804
805
						$dates['m_start'] = 7;
806
						$dates['m_end']   = 9;
807
						$dates['year']    = date( 'Y', $current_time );
808
809
					}
810
					break;
811
812
				case 'this_year' :
813
					$dates['day']     = null;
814
					$dates['m_start'] = null;
815
					$dates['m_end']   = null;
816
					$dates['year']    = date( 'Y', $current_time );
817
					break;
818
819
				case 'last_year' :
820
					$dates['day']     = null;
821
					$dates['m_start'] = null;
822
					$dates['m_end']   = null;
823
					$dates['year']    = date( 'Y', $current_time ) - 1;
824
					break;
825
826
			endswitch;
827
		}
828
829
		/**
830
		 * Returns the filters for the dates used to retreive earnings/donations
831
		 *
832
		 * @since 1.2
833
		 *
834
		 * @param object $dates The dates used for retreiving earnings/donations
835
		 */
836
837
		return apply_filters( 'give_api_stat_dates', $dates );
838
	}
839
840
	/**
841
	 * Process Get Customers API Request
842
	 *
843
	 * @access public
844
	 * @since  1.1
845
	 * @global object $wpdb Used to query the database using the WordPress
846
	 *                          Database API
847
	 *
848
	 * @param int $customer Customer ID
849
	 *
850
	 * @return array $customers Multidimensional array of the customers
851
	 */
852 1
	public function get_customers( $customer = null ) {
853
854 1
		$customers = array();
855 1
		$error     = array();
856 1
		if ( ! user_can( $this->user_id, 'view_give_sensitive_data' ) && ! $this->override ) {
857
			return $customers;
858
		}
859
860 1
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
861
862 1
		$paged    = $this->get_paged();
863 1
		$per_page = $this->per_page();
864 1
		$offset   = $per_page * ( $paged - 1 );
865
866 1
		if ( is_numeric( $customer ) ) {
867
			$field = 'id';
868
		} else {
869 1
			$field = 'email';
870
		}
871
872 1
		$customer_query = Give()->customers->get_customers( array(
873 1
			'number' => $per_page,
874 1
			'offset' => $offset,
875
			$field   => $customer
876 1
		) );
877 1
		$customer_count = 0;
878
879 1
		if ( $customer_query ) {
880
881 1
			foreach ( $customer_query as $customer_obj ) {
882
883 1
				$names      = explode( ' ', $customer_obj->name );
884 1
				$first_name = ! empty( $names[0] ) ? $names[0] : '';
885 1
				$last_name  = '';
886 1
				if ( ! empty( $names[1] ) ) {
887 1
					unset( $names[0] );
888 1
					$last_name = implode( ' ', $names );
889 1
				}
890
891 1
				$customers['donors'][ $customer_count ]['info']['user_id']      = '';
892 1
				$customers['donors'][ $customer_count ]['info']['username']     = '';
893 1
				$customers['donors'][ $customer_count ]['info']['display_name'] = '';
894 1
				$customers['donors'][ $customer_count ]['info']['customer_id']  = $customer_obj->id;
895 1
				$customers['donors'][ $customer_count ]['info']['first_name']   = $first_name;
896 1
				$customers['donors'][ $customer_count ]['info']['last_name']    = $last_name;
897 1
				$customers['donors'][ $customer_count ]['info']['email']        = $customer_obj->email;
898
899 1
				if ( ! empty( $customer_obj->user_id ) ) {
900
901 1
					$user_data = get_userdata( $customer_obj->user_id );
902
903
					// Customer with registered account
904 1
					$customers['donors'][ $customer_count ]['info']['user_id']      = $customer_obj->user_id;
905 1
					$customers['donors'][ $customer_count ]['info']['username']     = $user_data->user_login;
906 1
					$customers['donors'][ $customer_count ]['info']['display_name'] = $user_data->display_name;
907
908 1
				}
909
910 1
				$customers['donors'][ $customer_count ]['stats']['total_donations'] = $customer_obj->purchase_count;
911 1
				$customers['donors'][ $customer_count ]['stats']['total_spent']     = $customer_obj->purchase_value;
912
913 1
				$customer_count ++;
914
915 1
			}
916
917 1
		} elseif ( $customer ) {
918
919
			$error['error'] = sprintf(
920
				/* translators: %s: customer */
921
				__( 'Donor %s not found!', 'give' ),
922
				$customer
923
			);
924
925
			return $error;
926
927
		} else {
928
929
			$error['error'] = __( 'No donors found!', 'give' );
930
931
			return $error;
932
933
		}
934
935 1
		return $customers;
936
	}
937
938
	/**
939
	 * Process Get Products API Request
940
	 *
941
	 * @access public
942
	 * @since  1.1
943
	 *
944
	 * @param int $form Give Form ID
945
	 *
946
	 * @return array $customers Multidimensional array of the forms
947
	 */
948 11
	public function get_forms( $form = null ) {
949
950 11
		$forms = array();
951 11
		$error = array();
952
953 11
		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...
954 11
			$forms['forms'] = array();
955
956 11
			$form_list = get_posts( array(
957 11
				'post_type'        => 'give_forms',
958 11
				'posts_per_page'   => $this->per_page(),
959 11
				'suppress_filters' => true,
960 11
				'paged'            => $this->get_paged()
961 11
			) );
962
963 11
			if ( $form_list ) {
964 11
				$i = 0;
965 11
				foreach ( $form_list as $form_info ) {
966 11
					$forms['forms'][ $i ] = $this->get_form_data( $form_info );
967 11
					$i ++;
968 11
				}
969 11
			}
970 11
		} else {
971
			if ( get_post_type( $form ) == 'give_forms' ) {
972
				$form_info = get_post( $form );
973
974
				$forms['forms'][0] = $this->get_form_data( $form_info );
975
976
			} else {
977
				$error['error'] = sprintf(
978
					/* translators: %s: form */
979
					__( 'Form %s not found!', 'give' ),
980
					$form
981
				);
982
983
				return $error;
984
			}
985
		}
986
987 11
		return $forms;
988
	}
989
990
	/**
991
	 * Given a give_forms post object, generate the data for the API output
992
	 *
993
	 * @since  1.1
994
	 *
995
	 * @param  object $form_info The Download Post Object
996
	 *
997
	 * @return array                Array of post data to return back in the API
998
	 */
999 11
	private function get_form_data( $form_info ) {
1000
1001 11
		$form = array();
1002
1003 11
		$form['info']['id']            = $form_info->ID;
1004 11
		$form['info']['slug']          = $form_info->post_name;
1005 11
		$form['info']['title']         = $form_info->post_title;
1006 11
		$form['info']['create_date']   = $form_info->post_date;
1007 11
		$form['info']['modified_date'] = $form_info->post_modified;
1008 11
		$form['info']['status']        = $form_info->post_status;
1009 11
		$form['info']['link']          = html_entity_decode( $form_info->guid );
1010 11
		$form['info']['content']       = get_post_meta( $form_info->ID, '_give_form_content', true );
1011 11
		$form['info']['thumbnail']     = wp_get_attachment_url( get_post_thumbnail_id( $form_info->ID ) );
1012
1013 11
		if ( give_get_option( 'enable_categories' ) == 'on' ) {
1014
			$form['info']['category'] = get_the_terms( $form_info, 'give_forms_category' );
1015
			$form['info']['tags']     = get_the_terms( $form_info, 'give_forms_tag' );
1016
		}
1017 11
		if ( give_get_option( 'enable_tags' ) == 'on' ) {
1018
			$form['info']['tags'] = get_the_terms( $form_info, 'give_forms_tag' );
1019
		}
1020
1021 11
		if ( user_can( $this->user_id, 'view_give_reports' ) || $this->override ) {
1022 11
			$form['stats']['total']['donations']           = give_get_form_sales_stats( $form_info->ID );
1023 11
			$form['stats']['total']['earnings']            = give_get_form_earnings_stats( $form_info->ID );
1024 11
			$form['stats']['monthly_average']['donations'] = give_get_average_monthly_form_sales( $form_info->ID );
1025 11
			$form['stats']['monthly_average']['earnings']  = give_get_average_monthly_form_earnings( $form_info->ID );
1026 11
		}
1027
1028 11
		$counter = 0;
1029 11
		if ( give_has_variable_prices( $form_info->ID ) ) {
1030 11
			foreach ( give_get_variable_prices( $form_info->ID ) as $price ) {
1031 11
				$counter ++;
1032
				//muli-level item
1033 11
				$level                                     = isset( $price['_give_text'] ) ? $price['_give_text'] : 'level-' . $counter;
1034 11
				$form['pricing'][ sanitize_key( $level ) ] = $price['_give_amount'];
1035
1036 11
			}
1037 11
		} else {
1038
			$form['pricing']['amount'] = give_get_form_price( $form_info->ID );
1039
		}
1040
1041 11
		if ( user_can( $this->user_id, 'view_give_sensitive_data' ) || $this->override ) {
1042
1043
			//Sensitive data here
1044 11
			do_action( 'give_api_sensitive_data' );
1045
1046 11
		}
1047
1048 11
		return apply_filters( 'give_api_forms_form', $form );
1049
1050
	}
1051
1052
	/**
1053
	 * Process Get Stats API Request
1054
	 *
1055
	 * @since 1.1
1056
	 *
1057
	 * @global object $wpdb Used to query the database using the WordPress
1058
	 *
1059
	 * @param array $args Arguments provided by API Request
1060
	 *
1061
	 * @return array
1062
	 */
1063
	public function get_stats( $args = array() ) {
1064
		$defaults = array(
1065
			'type'      => null,
1066
			'form'      => null,
1067
			'date'      => null,
1068
			'startdate' => null,
1069
			'enddate'   => null
1070
		);
1071
1072
		$args = wp_parse_args( $args, $defaults );
1073
1074
		$dates = $this->get_dates( $args );
1075
1076
		$stats    = array();
1077
		$earnings = array(
1078
			'earnings' => array()
1079
		);
1080
		$sales    = array(
1081
			'donations' => array()
1082
		);
1083
		$error    = array();
1084
1085
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1086
			return $stats;
1087
		}
1088
1089
		if ( $args['type'] == 'donations' ) {
1090
1091
			if ( $args['form'] == null ) {
1092
				if ( $args['date'] == null ) {
1093
					$sales = $this->get_default_sales_stats();
1094
				} elseif ( $args['date'] === 'range' ) {
1095
					// Return sales for a date range
1096
1097
					// Ensure the end date is later than the start date
1098
					if ( $args['enddate'] < $args['startdate'] ) {
1099
						$error['error'] = __( 'The end date must be later than the start date!', 'give' );
1100
					}
1101
1102
					// Ensure both the start and end date are specified
1103
					if ( empty( $args['startdate'] ) || empty( $args['enddate'] ) ) {
1104
						$error['error'] = __( 'Invalid or no date range specified!', 'give' );
1105
					}
1106
1107
					$total = 0;
1108
1109
					// Loop through the years
1110
					$y = $dates['year'];
1111
					while ( $y <= $dates['year_end'] ) :
1112
1113
						if ( $dates['year'] == $dates['year_end'] ) {
1114
							$month_start = $dates['m_start'];
1115
							$month_end   = $dates['m_end'];
1116
						} elseif ( $y == $dates['year'] && $dates['year_end'] > $dates['year'] ) {
1117
							$month_start = $dates['m_start'];
1118
							$month_end   = 12;
1119
						} elseif ( $y == $dates['year_end'] ) {
1120
							$month_start = 1;
1121
							$month_end   = $dates['m_end'];
1122
						} else {
1123
							$month_start = 1;
1124
							$month_end   = 12;
1125
						}
1126
1127
						$i = $month_start;
1128
						while ( $i <= $month_end ) :
1129
1130
							if ( $i == $dates['m_start'] ) {
1131
								$d = $dates['day_start'];
1132
							} else {
1133
								$d = 1;
1134
							}
1135
1136
							if ( $i == $dates['m_end'] ) {
1137
								$num_of_days = $dates['day_end'];
1138
							} else {
1139
								$num_of_days = cal_days_in_month( CAL_GREGORIAN, $i, $y );
1140
							}
1141
1142
							while ( $d <= $num_of_days ) :
1143
								$sale_count = give_get_sales_by_date( $d, $i, $y );
1144
								$date_key   = date( 'Ymd', strtotime( $y . '/' . $i . '/' . $d ) );
1145
								if ( ! isset( $sales['sales'][ $date_key ] ) ) {
1146
									$sales['sales'][ $date_key ] = 0;
1147
								}
1148
								$sales['sales'][ $date_key ] += $sale_count;
1149
								$total += $sale_count;
1150
								$d ++;
1151
							endwhile;
1152
							$i ++;
1153
						endwhile;
1154
1155
						$y ++;
1156
					endwhile;
1157
1158
					$sales['totals'] = $total;
1159
				} else {
1160
					if ( $args['date'] == 'this_quarter' || $args['date'] == 'last_quarter' ) {
1161
						$sales_count = 0;
1162
1163
						// Loop through the months
1164
						$month = $dates['m_start'];
1165
1166
						while ( $month <= $dates['m_end'] ) :
1167
							$sales_count += give_get_sales_by_date( null, $month, $dates['year'] );
1168
							$month ++;
1169
						endwhile;
1170
1171
						$sales['donations'][ $args['date'] ] = $sales_count;
1172
					} else {
1173
						$sales['donations'][ $args['date'] ] = give_get_sales_by_date( $dates['day'], $dates['m_start'], $dates['year'] );
1174
					}
1175
				}
1176
			} elseif ( $args['form'] == 'all' ) {
1177
				$forms = get_posts( array( 'post_type' => 'give_forms', 'nopaging' => true ) );
1178
				$i     = 0;
1179
				foreach ( $forms as $form_info ) {
1180
					$sales['donations'][ $i ] = array( $form_info->post_name => give_get_form_sales_stats( $form_info->ID ) );
1181
					$i ++;
1182
				}
1183
			} else {
1184
				if ( get_post_type( $args['form'] ) == 'give_forms' ) {
1185
					$form_info             = get_post( $args['form'] );
1186
					$sales['donations'][0] = array( $form_info->post_name => give_get_form_sales_stats( $args['form'] ) );
1187
				} else {
1188
					$error['error'] = sprintf(
1189
						/* translators: %s: form */
1190
						__( 'Product %s not found!', 'give' ),
1191
						$args['form']
1192
					);
1193
				}
1194
			}
1195
1196
			if ( ! empty( $error ) ) {
1197
				return $error;
1198
			}
1199
1200
			return $sales;
1201
1202
		} elseif ( $args['type'] == 'earnings' ) {
1203
			if ( $args['form'] == null ) {
1204
				if ( $args['date'] == null ) {
1205
					$earnings = $this->get_default_earnings_stats();
1206
				} elseif ( $args['date'] === 'range' ) {
1207
					// Return sales for a date range
1208
1209
					// Ensure the end date is later than the start date
1210
					if ( $args['enddate'] < $args['startdate'] ) {
1211
						$error['error'] = __( 'The end date must be later than the start date!', 'give' );
1212
					}
1213
1214
					// Ensure both the start and end date are specified
1215
					if ( empty( $args['startdate'] ) || empty( $args['enddate'] ) ) {
1216
						$error['error'] = __( 'Invalid or no date range specified!', 'give' );
1217
					}
1218
1219
					$total = (float) 0.00;
1220
1221
					// Loop through the years
1222
					$y = $dates['year'];
1223
					if ( ! isset( $earnings['earnings'] ) ) {
1224
						$earnings['earnings'] = array();
1225
					}
1226
					while ( $y <= $dates['year_end'] ) :
1227
1228
						if ( $dates['year'] == $dates['year_end'] ) {
1229
							$month_start = $dates['m_start'];
1230
							$month_end   = $dates['m_end'];
1231
						} elseif ( $y == $dates['year'] && $dates['year_end'] > $dates['year'] ) {
1232
							$month_start = $dates['m_start'];
1233
							$month_end   = 12;
1234
						} elseif ( $y == $dates['year_end'] ) {
1235
							$month_start = 1;
1236
							$month_end   = $dates['m_end'];
1237
						} else {
1238
							$month_start = 1;
1239
							$month_end   = 12;
1240
						}
1241
1242
						$i = $month_start;
1243
						while ( $i <= $month_end ) :
1244
1245
							if ( $i == $dates['m_start'] ) {
1246
								$d = $dates['day_start'];
1247
							} else {
1248
								$d = 1;
1249
							}
1250
1251
							if ( $i == $dates['m_end'] ) {
1252
								$num_of_days = $dates['day_end'];
1253
							} else {
1254
								$num_of_days = cal_days_in_month( CAL_GREGORIAN, $i, $y );
1255
							}
1256
1257
							while ( $d <= $num_of_days ) :
1258
								$earnings_stat = give_get_earnings_by_date( $d, $i, $y );
1259
								$date_key      = date( 'Ymd', strtotime( $y . '/' . $i . '/' . $d ) );
1260
								if ( ! isset( $earnings['earnings'][ $date_key ] ) ) {
1261
									$earnings['earnings'][ $date_key ] = 0;
1262
								}
1263
								$earnings['earnings'][ $date_key ] += $earnings_stat;
1264
								$total += $earnings_stat;
1265
								$d ++;
1266
							endwhile;
1267
1268
							$i ++;
1269
						endwhile;
1270
1271
						$y ++;
1272
					endwhile;
1273
1274
					$earnings['totals'] = $total;
1275
				} else {
1276
					if ( $args['date'] == 'this_quarter' || $args['date'] == 'last_quarter' ) {
1277
						$earnings_count = (float) 0.00;
1278
1279
						// Loop through the months
1280
						$month = $dates['m_start'];
1281
1282
						while ( $month <= $dates['m_end'] ) :
1283
							$earnings_count += give_get_earnings_by_date( null, $month, $dates['year'] );
1284
							$month ++;
1285
						endwhile;
1286
1287
						$earnings['earnings'][ $args['date'] ] = $earnings_count;
1288
					} else {
1289
						$earnings['earnings'][ $args['date'] ] = give_get_earnings_by_date( $dates['day'], $dates['m_start'], $dates['year'] );
1290
					}
1291
				}
1292
			} elseif ( $args['form'] == 'all' ) {
1293
				$forms = get_posts( array( 'post_type' => 'give_forms', 'nopaging' => true ) );
1294
1295
				$i = 0;
1296
				foreach ( $forms as $form_info ) {
1297
					$earnings['earnings'][ $i ] = array( $form_info->post_name => give_get_form_earnings_stats( $form_info->ID ) );
1298
					$i ++;
1299
				}
1300
			} else {
1301
				if ( get_post_type( $args['form'] ) == 'give_forms' ) {
1302
					$form_info               = get_post( $args['form'] );
1303
					$earnings['earnings'][0] = array( $form_info->post_name => give_get_form_earnings_stats( $args['form'] ) );
1304
				} else {
1305
					$error['error'] = sprintf(
1306
						/* translators: %s: form */
1307
						__( 'Form %s not found!', 'give' ),
1308
						$args['form']
1309
					);
1310
				}
1311
			}
1312
1313
			if ( ! empty( $error ) ) {
1314
				return $error;
1315
			}
1316
1317
			return $earnings;
1318
		} elseif ( $args['type'] == 'donors' ) {
1319
			$customers                          = new Give_DB_Customers();
1320
			$stats['donations']['total_donors'] = $customers->count();
1321
1322
			return $stats;
1323
1324
		} elseif ( empty( $args['type'] ) ) {
1325
			$stats = array_merge( $stats, $this->get_default_sales_stats() );
1326
			$stats = array_merge( $stats, $this->get_default_earnings_stats() );
1327
1328
			return array( 'stats' => $stats );
1329
		}
1330
	}
1331
1332
	/**
1333
	 * Retrieves Recent Donations
1334
	 *
1335
	 * @access public
1336
	 * @since  1.1
1337
	 * @return array
1338
	 */
1339 11
	public function get_recent_donations() {
1340 11
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
1341
1342 11
		$sales = array();
1343
1344 11
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1345
			return $sales;
1346
		}
1347
1348 11
		if ( isset( $wp_query->query_vars['id'] ) ) {
1349
			$query   = array();
1350
			$query[] = new Give_Payment( $wp_query->query_vars['id'] );
1351 11
		} elseif ( isset( $wp_query->query_vars['purchasekey'] ) ) {
1352
			$query   = array();
1353
			$query[] = give_get_payment_by( 'key', $wp_query->query_vars['purchasekey'] );
1354 11
		} elseif ( isset( $wp_query->query_vars['email'] ) ) {
1355
			$args  = array(
1356
				'fields'     => 'ids',
1357
				'meta_key'   => '_give_payment_user_email',
1358
				'meta_value' => $wp_query->query_vars['email'],
1359
				'number'     => $this->per_page(),
1360
				'page'       => $this->get_paged(),
1361
				'status'     => 'publish'
1362
			);
1363
			$query = give_get_payments( $args );
1364
		} else {
1365
			$args  = array(
1366 11
				'fields' => 'ids',
1367 11
				'number' => $this->per_page(),
1368 11
				'page'   => $this->get_paged(),
1369
				'status' => 'publish'
1370 11
			);
1371 11
			$query = give_get_payments( $args );
1372
		}
1373 11
		if ( $query ) {
1374 11
			$i = 0;
1375 11
			foreach ( $query as $payment ) {
1376
1377 11
				if ( is_numeric( $payment ) ) {
1378 11
					$payment      = new Give_Payment( $payment );
1379 11
					$payment_meta = $payment->get_meta();
1380 11
					$user_info    = $payment->user_info;
0 ignored issues
show
Documentation introduced by
The property $user_info is declared private in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1381 11
				} else {
1382
					continue;
1383
				}
1384
1385 11
				$payment_meta = $payment->get_meta();
1386 11
				$user_info    = $payment->user_info;
0 ignored issues
show
Documentation introduced by
The property $user_info is declared private in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1387
1388 11
				$first_name = isset( $user_info['first_name'] ) ? $user_info['first_name'] : '';
1389 11
				$last_name  = isset( $user_info['last_name'] ) ? $user_info['last_name'] : '';
1390
1391 11
				$sales['donations'][ $i ]['ID']             = $payment->number;
0 ignored issues
show
Documentation introduced by
The property $number is declared protected in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1392 11
				$sales['donations'][ $i ]['transaction_id'] = $payment->transaction_id;
0 ignored issues
show
Documentation introduced by
The property $transaction_id is declared protected in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1393 11
				$sales['donations'][ $i ]['key']            = $payment->key;
0 ignored issues
show
Documentation introduced by
The property $key is declared protected in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1394 11
				$sales['donations'][ $i ]['total']          = $payment->total;
0 ignored issues
show
Documentation introduced by
The property $total is declared protected in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1395 11
				$sales['donations'][ $i ]['gateway']        = $payment->gateway;
0 ignored issues
show
Documentation introduced by
The property $gateway is declared protected in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1396 11
				$sales['donations'][ $i ]['name']           = $first_name . ' ' . $last_name;
1397 11
				$sales['donations'][ $i ]['fname']          = $first_name;
1398 11
				$sales['donations'][ $i ]['lname']          = $last_name;
1399 11
				$sales['donations'][ $i ]['email']          = $payment->email;
0 ignored issues
show
Documentation introduced by
The property $email is declared protected in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1400 11
				$sales['donations'][ $i ]['date']           = $payment->date;
0 ignored issues
show
Documentation introduced by
The property $date is declared protected in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1401
1402 11
				$form_id  = isset( $payment_meta['form_id'] ) ? $payment_meta['form_id'] : $payment_meta;
1403 11
				$price    = isset( $payment_meta['form_id'] ) ? give_get_form_price( $payment_meta['form_id'] ) : false;
1404 11
				$price_id = isset( $payment_meta['price_id'] ) ? $payment_meta['price_id'] : null;
1405
1406 11
				$sales['donations'][ $i ]['form']['id']    = $form_id;
1407 11
				$sales['donations'][ $i ]['form']['name']  = get_the_title( $payment_meta['form_id'] );
1408 11
				$sales['donations'][ $i ]['form']['price'] = $price;
1409
1410 11
				if ( give_has_variable_prices( $form_id ) ) {
1411 11
					if ( isset( $payment_meta['price_id'] ) ) {
1412 11
						$price_name                                     = give_get_price_option_name( $form_id, $payment_meta['price_id'], $payment->ID );
1413 11
						$sales['donations'][ $i ]['form']['price_name'] = $price_name;
1414 11
						$sales['donations'][ $i ]['form']['price_id']   = $price_id;
1415 11
						$sales['donations'][ $i ]['form']['price']      = give_get_price_option_amount( $form_id, $price_id );
1416
1417 11
					}
1418 11
				}
1419
1420
				//Add custom meta to API
1421 11
				foreach ( $payment_meta as $meta_key => $meta_value ) {
1422
1423
					$exceptions = array(
1424 11
						'form_title',
1425 11
						'form_id',
1426 11
						'price_id',
1427 11
						'user_info',
1428 11
						'key',
1429 11
						'email',
1430 11
						'date',
1431 11
					);
1432
1433
					//Don't clutter up results with dupes
1434 11
					if ( in_array( $meta_key, $exceptions ) ) {
1435 11
						continue;
1436
					}
1437
1438 11
					$sales['donations'][ $i ]['payment_meta'][ $meta_key ] = $meta_value;
1439
1440 11
				}
1441
1442 11
				$i ++;
1443 11
			}
1444 11
		}
1445
1446 11
		return apply_filters( 'give_api_donations_endpoint', $sales );
1447
	}
1448
1449
	/**
1450
	 * Retrieve the output format
1451
	 *
1452
	 * Determines whether results should be displayed in XML or JSON
1453
	 *
1454
	 * @since 1.1
1455
	 *
1456
	 * @return mixed|void
1457
	 */
1458
	public function get_output_format() {
1459
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
1460
1461
		$format = isset( $wp_query->query_vars['format'] ) ? $wp_query->query_vars['format'] : 'json';
1462
1463
		return apply_filters( 'give_api_output_format', $format );
1464
	}
1465
1466
1467
	/**
1468
	 * Log each API request, if enabled
1469
	 *
1470
	 * @access private
1471
	 * @since  1.1
1472
	 * @global      $give_logs
1473
	 * @global      $wp_query
1474
	 *
1475
	 * @param array $data
1476
	 *
1477
	 * @return void
1478
	 */
1479
	private function log_request( $data = array() ) {
1480
		if ( ! $this->log_requests ) {
1481
			return;
1482
		}
1483
1484
		global $give_logs, $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
1485
1486
		$query = array(
1487
			'give-api'    => $wp_query->query_vars['give-api'],
1488
			'key'         => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null,
1489
			'token'       => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null,
1490
			'query'       => isset( $wp_query->query_vars['query'] ) ? $wp_query->query_vars['query'] : null,
1491
			'type'        => isset( $wp_query->query_vars['type'] ) ? $wp_query->query_vars['type'] : null,
1492
			'form'        => isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null,
1493
			'customer'    => isset( $wp_query->query_vars['customer'] ) ? $wp_query->query_vars['customer'] : null,
1494
			'date'        => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null,
1495
			'startdate'   => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null,
1496
			'enddate'     => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null,
1497
			'id'          => isset( $wp_query->query_vars['id'] ) ? $wp_query->query_vars['id'] : null,
1498
			'purchasekey' => isset( $wp_query->query_vars['purchasekey'] ) ? $wp_query->query_vars['purchasekey'] : null,
1499
			'email'       => isset( $wp_query->query_vars['email'] ) ? $wp_query->query_vars['email'] : null,
1500
		);
1501
1502
		$log_data = array(
1503
			'log_type'     => 'api_request',
1504
			'post_excerpt' => http_build_query( $query ),
1505
			'post_content' => ! empty( $data['error'] ) ? $data['error'] : '',
1506
		);
1507
1508
		$log_meta = array(
1509
			'request_ip' => give_get_ip(),
1510
			'user'       => $this->user_id,
1511
			'key'        => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null,
1512
			'token'      => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null,
1513
			'time'       => $data['request_speed'],
1514
			'version'    => $this->get_queried_version()
1515
		);
1516
1517
		$give_logs->insert_log( $log_data, $log_meta );
1518
	}
1519
1520
1521
	/**
1522
	 * Retrieve the output data
1523
	 *
1524
	 * @access public
1525
	 * @since  1.1
1526
	 * @return array
1527
	 */
1528
	public function get_output() {
1529
		return $this->data;
1530
	}
1531
1532
	/**
1533
	 * Output Query in either JSON/XML. The query data is outputted as JSON
1534
	 * by default
1535
	 *
1536
	 * @since 1.1
1537
	 * @global    $wp_query
1538
	 *
1539
	 * @param int $status_code
1540
	 */
1541
	public function output( $status_code = 200 ) {
1542
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
1543
1544
		$format = $this->get_output_format();
1545
1546
		status_header( $status_code );
1547
1548
		do_action( 'give_api_output_before', $this->data, $this, $format );
1549
1550
		switch ( $format ) :
1551
1552
			case 'xml' :
1553
1554
				require_once GIVE_PLUGIN_DIR . 'includes/libraries/array2xml.php';
1555
				$xml = Array2XML::createXML( 'give', $this->data );
1556
				echo $xml->saveXML();
1557
1558
				break;
1559
1560
			case 'json' :
1561
1562
				header( 'Content-Type: application/json' );
1563
				if ( ! empty( $this->pretty_print ) ) {
1564
					echo json_encode( $this->data, $this->pretty_print );
1565
				} else {
1566
					echo json_encode( $this->data );
1567
				}
1568
1569
				break;
1570
1571
1572
			default :
1573
1574
				// Allow other formats to be added via extensions
1575
				do_action( 'give_api_output_' . $format, $this->data, $this );
1576
1577
				break;
1578
1579
		endswitch;
1580
1581
		do_action( 'give_api_output_after', $this->data, $this, $format );
1582
1583
		give_die();
1584
	}
1585
1586
	/**
1587
	 * Modify User Profile
1588
	 *
1589
	 * Modifies the output of profile.php to add key generation/revocation
1590
	 *
1591
	 * @access public
1592
	 * @since  1.1
1593
	 *
1594
	 * @param object $user Current user info
1595
	 *
1596
	 * @return void
1597
	 */
1598
	function user_key_field( $user ) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1599
1600
		if ( ( give_get_option( 'api_allow_user_keys', false ) || current_user_can( 'manage_give_settings' ) ) && current_user_can( 'edit_user', $user->ID ) ) {
1601
			$user = get_userdata( $user->ID );
1602
			?>
1603
			<table class="form-table">
1604
				<tbody>
1605
				<tr>
1606
					<th>
1607
						<?php esc_attr_e( 'Give API Keys', 'give' ); ?>
1608
					</th>
1609
					<td>
1610
						<?php
1611
						$public_key = $this->get_user_public_key( $user->ID );
1612
						$secret_key = $this->get_user_secret_key( $user->ID );
1613
						?>
1614
						<?php if ( empty( $user->give_user_public_key ) ) { ?>
1615
							<input name="give_set_api_key" type="checkbox" id="give_set_api_key" value="0"/>
1616
							<span class="description"><?php esc_attr_e( 'Generate API Key', 'give' ); ?></span>
1617
						<?php } else { ?>
1618
							<strong style="display:inline-block; width: 125px;"><?php _e( 'Public key:', 'give' ); ?>&nbsp;</strong>
1619
							<input type="text" disabled="disabled" class="regular-text" id="publickey" value="<?php echo esc_attr( $public_key ); ?>"/>
1620
							<br/>
1621
							<strong style="display:inline-block; width: 125px;"><?php esc_attr_e( 'Secret key:', 'give' ); ?>&nbsp;</strong>
1622
							<input type="text" disabled="disabled" class="regular-text" id="privatekey" value="<?php echo esc_attr( $secret_key ); ?>"/>
1623
							<br/>
1624
							<strong style="display:inline-block; width: 125px;"><?php esc_attr_e( 'Token:', 'give' ); ?>&nbsp;</strong>
1625
							<input type="text" disabled="disabled" class="regular-text" id="token" value="<?php echo esc_attr( $this->get_token( $user->ID ) ); ?>"/>
1626
							<br/>
1627
							<input name="give_set_api_key" type="checkbox" id="give_set_api_key" value="0"/>
1628
							<span class="description"><label for="give_set_api_key"><?php esc_attr_e( 'Revoke API Keys', 'give' ); ?></label></span>
1629
						<?php } ?>
1630
					</td>
1631
				</tr>
1632
				</tbody>
1633
			</table>
1634
		<?php }
1635
	}
1636
1637
	/**
1638
	 * Process an API key generation/revocation
1639
	 *
1640
	 * @access public
1641
	 * @since  1.1
1642
	 *
1643
	 * @param array $args
1644
	 *
1645
	 * @return void
1646
	 */
1647
	public function process_api_key( $args ) {
1648
1649
		if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'give-api-nonce' ) ) {
1650
1651
			wp_die( esc_attr__( 'Nonce verification failed.', 'give' ), __( 'Error', 'give' ), array( 'response' => 403 ) );
1652
1653
		}
1654
1655
		if ( empty( $args['user_id'] ) ) {
1656
			wp_die( esc_attr__( 'User ID Required.', 'give' ), esc_attr__( 'Error', 'give' ), array( 'response' => 401 ) );
1657
		}
1658
1659
		if ( is_numeric( $args['user_id'] ) ) {
1660
			$user_id = isset( $args['user_id'] ) ? absint( $args['user_id'] ) : get_current_user_id();
1661
		} else {
1662
			$userdata = get_user_by( 'login', $args['user_id'] );
1663
			$user_id  = $userdata->ID;
1664
		}
1665
		$process = isset( $args['give_api_process'] ) ? strtolower( $args['give_api_process'] ) : false;
1666
1667
		if ( $user_id == get_current_user_id() && ! give_get_option( 'allow_user_api_keys' ) && ! current_user_can( 'manage_give_settings' ) ) {
1668
			wp_die(
1669
				sprintf(
1670
					/* translators: %s: process */
1671
					__( 'You do not have permission to %s API keys for this user.', 'give' ),
1672
					$process
1673
				),
1674
				__( 'Error', 'give' ),
1675
				array( 'response' => 403 )
1676
			);
1677
		} elseif ( ! current_user_can( 'manage_give_settings' ) ) {
1678
			wp_die(
1679
				sprintf(
1680
					/* translators: %s: process */
1681
					__( 'You do not have permission to %s API keys for this user.', 'give' ),
1682
					$process
1683
				),
1684
				__( 'Error', 'give' ),
1685
				array( 'response' => 403 )
1686
			);
1687
		}
1688
1689
		switch ( $process ) {
1690
			case 'generate':
1691
				if ( $this->generate_api_key( $user_id ) ) {
1692
					delete_transient( 'give-total-api-keys' );
1693
					wp_redirect( add_query_arg( 'give-message', 'api-key-generated', 'edit.php?post_type=give_forms&page=give-settings&tab=api' ) );
1694
					exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method process_api_key() contains an exit expression.

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

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

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

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

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

Loading history...
1698
				}
1699
				break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1700
			case 'regenerate':
1701
				$this->generate_api_key( $user_id, true );
1702
				delete_transient( 'give-total-api-keys' );
1703
				wp_redirect( add_query_arg( 'give-message', 'api-key-regenerated', 'edit.php?post_type=give_forms&page=give-settings&tab=api' ) );
1704
				exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method process_api_key() contains an exit expression.

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

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

Loading history...
1705
				break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1706
			case 'revoke':
1707
				$this->revoke_api_key( $user_id );
1708
				delete_transient( 'give-total-api-keys' );
1709
				wp_redirect( add_query_arg( 'give-message', 'api-key-revoked', 'edit.php?post_type=give_forms&page=give-settings&tab=api' ) );
1710
				exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method process_api_key() contains an exit expression.

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

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

Loading history...
1711
				break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1712
			default;
1713
				break;
1714
		}
1715
	}
1716
1717
	/**
1718
	 * Generate new API keys for a user
1719
	 *
1720
	 * @access public
1721
	 * @since  1.1
1722
	 *
1723
	 * @param int $user_id User ID the key is being generated for
1724
	 * @param boolean $regenerate Regenerate the key for the user
1725
	 *
1726
	 * @return boolean True if (re)generated succesfully, false otherwise.
1727
	 */
1728
	public function generate_api_key( $user_id = 0, $regenerate = false ) {
1729
1730
		if ( empty( $user_id ) ) {
1731
			return false;
1732
		}
1733
1734
		$user = get_userdata( $user_id );
1735
1736
		if ( ! $user ) {
1737
			return false;
1738
		}
1739
1740
		$public_key = $this->get_user_public_key( $user_id );
1741
		$secret_key = $this->get_user_secret_key( $user_id );
1742
1743
		if ( empty( $public_key ) || $regenerate == true ) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

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

Loading history...
1744
			$new_public_key = $this->generate_public_key( $user->user_email );
1745
			$new_secret_key = $this->generate_private_key( $user->ID );
1746
		} else {
1747
			return false;
1748
		}
1749
1750
		if ( $regenerate == true ) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

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

Loading history...
1751
			$this->revoke_api_key( $user->ID );
1752
		}
1753
1754
		update_user_meta( $user_id, $new_public_key, 'give_user_public_key' );
1755
		update_user_meta( $user_id, $new_secret_key, 'give_user_secret_key' );
1756
1757
		return true;
1758
	}
1759
1760
	/**
1761
	 * Revoke a users API keys
1762
	 *
1763
	 * @access public
1764
	 * @since  1.1
1765
	 *
1766
	 * @param int $user_id User ID of user to revoke key for
1767
	 *
1768
	 * @return string
1769
	 */
1770
	public function revoke_api_key( $user_id = 0 ) {
1771
1772
		if ( empty( $user_id ) ) {
1773
			return false;
1774
		}
1775
1776
		$user = get_userdata( $user_id );
1777
1778
		if ( ! $user ) {
1779
			return false;
1780
		}
1781
1782
		$public_key = $this->get_user_public_key( $user_id );
1783
		$secret_key = $this->get_user_secret_key( $user_id );
1784
		if ( ! empty( $public_key ) ) {
1785
			delete_transient( md5( 'give_api_user_' . $public_key ) );
1786
			delete_transient( md5( 'give_api_user_public_key' . $user_id ) );
1787
			delete_transient( md5( 'give_api_user_secret_key' . $user_id ) );
1788
			delete_user_meta( $user_id, $public_key );
1789
			delete_user_meta( $user_id, $secret_key );
1790
		} else {
1791
			return false;
1792
		}
1793
1794
		return true;
1795
	}
1796
1797 2
	public function get_version() {
1798 2
		return self::VERSION;
1799
	}
1800
1801
1802
	/**
1803
	 * Generate and Save API key
1804
	 *
1805
	 * Generates the key requested by user_key_field and stores it in the database
1806
	 *
1807
	 * @access public
1808
	 * @since  1.1
1809
	 *
1810
	 * @param int $user_id
1811
	 *
1812
	 * @return void
1813
	 */
1814 2
	public function update_key( $user_id ) {
1815 2
		if ( current_user_can( 'edit_user', $user_id ) && isset( $_POST['give_set_api_key'] ) ) {
1816
1817 2
			$user = get_userdata( $user_id );
1818
1819 2
			$public_key = $this->get_user_public_key( $user_id );
1820 2
			$secret_key = $this->get_user_secret_key( $user_id );
1821
1822 2
			if ( empty( $public_key ) ) {
1823 2
				$new_public_key = $this->generate_public_key( $user->user_email );
1824 2
				$new_secret_key = $this->generate_private_key( $user->ID );
1825
1826 2
				update_user_meta( $user_id, $new_public_key, 'give_user_public_key' );
1827 2
				update_user_meta( $user_id, $new_secret_key, 'give_user_secret_key' );
1828 2
			} else {
1829
				$this->revoke_api_key( $user_id );
1830
			}
1831 2
		}
1832 2
	}
1833
1834
	/**
1835
	 * Generate the public key for a user
1836
	 *
1837
	 * @access private
1838
	 * @since  1.1
1839
	 *
1840
	 * @param string $user_email
1841
	 *
1842
	 * @return string
1843
	 */
1844 2
	private function generate_public_key( $user_email = '' ) {
1845 2
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
1846 2
		$public   = hash( 'md5', $user_email . $auth_key . date( 'U' ) );
1847
1848 2
		return $public;
1849
	}
1850
1851
	/**
1852
	 * Generate the secret key for a user
1853
	 *
1854
	 * @access private
1855
	 * @since  1.1
1856
	 *
1857
	 * @param int $user_id
1858
	 *
1859
	 * @return string
1860
	 */
1861 2
	private function generate_private_key( $user_id = 0 ) {
1862 2
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
1863 2
		$secret   = hash( 'md5', $user_id . $auth_key . date( 'U' ) );
1864
1865 2
		return $secret;
1866
	}
1867
1868
	/**
1869
	 * Retrieve the user's token
1870
	 *
1871
	 * @access private
1872
	 * @since  1.1
1873
	 *
1874
	 * @param int $user_id
1875
	 *
1876
	 * @return string
1877
	 */
1878
	public function get_token( $user_id = 0 ) {
1879
		return hash( 'md5', $this->get_user_secret_key( $user_id ) . $this->get_user_public_key( $user_id ) );
1880
	}
1881
1882
	/**
1883
	 * Generate the default sales stats returned by the 'stats' endpoint
1884
	 *
1885
	 * @access private
1886
	 * @since  1.1
1887
	 * @return array default sales statistics
1888
	 */
1889
	private function get_default_sales_stats() {
1890
1891
		// Default sales return
1892
		$sales                               = array();
1893
		$sales['donations']['today']         = $this->stats->get_sales( 0, 'today' );
1894
		$sales['donations']['current_month'] = $this->stats->get_sales( 0, 'this_month' );
1895
		$sales['donations']['last_month']    = $this->stats->get_sales( 0, 'last_month' );
1896
		$sales['donations']['totals']        = give_get_total_sales();
1897
1898
		return $sales;
1899
	}
1900
1901
	/**
1902
	 * Generate the default earnings stats returned by the 'stats' endpoint
1903
	 *
1904
	 * @access private
1905
	 * @since  1.1
1906
	 * @return array default earnings statistics
1907
	 */
1908
	private function get_default_earnings_stats() {
1909
1910
		// Default earnings return
1911
		$earnings                              = array();
1912
		$earnings['earnings']['today']         = $this->stats->get_earnings( 0, 'today' );
1913
		$earnings['earnings']['current_month'] = $this->stats->get_earnings( 0, 'this_month' );
1914
		$earnings['earnings']['last_month']    = $this->stats->get_earnings( 0, 'last_month' );
1915
		$earnings['earnings']['totals']        = give_get_total_earnings();
1916
1917
		return $earnings;
1918
	}
1919
1920
	/**
1921
	 * API Key Backwards Compatibility
1922
	 *
1923
	 * @description A Backwards Compatibility call for the change of meta_key/value for users API Keys
1924
	 *
1925
	 * @since       1.3.6
1926
	 *
1927
	 * @param  string $check Whether to check the cache or not
1928
	 * @param  int $object_id The User ID being passed
1929
	 * @param  string $meta_key The user meta key
1930
	 * @param  bool $single If it should return a single value or array
1931
	 *
1932
	 * @return string            The API key/secret for the user supplied
1933
	 */
1934 56
	public function api_key_backwards_compat( $check, $object_id, $meta_key, $single ) {
1935
1936 56
		if ( $meta_key !== 'give_user_public_key' && $meta_key !== 'give_user_secret_key' ) {
1937 56
			return $check;
1938
		}
1939
1940
		$return = $check;
1941
1942
		switch ( $meta_key ) {
1943
			case 'give_user_public_key':
1944
				$return = Give()->api->get_user_public_key( $object_id );
1945
				break;
1946
			case 'give_user_secret_key':
1947
				$return = Give()->api->get_user_secret_key( $object_id );
1948
				break;
1949
		}
1950
1951
		if ( ! $single ) {
1952
			$return = array( $return );
1953
		}
1954
1955
		return $return;
1956
1957
	}
1958
1959
}
1960