Completed
Pull Request — master (#703)
by
unknown
17:23
created

Give_API::update_key()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4.0058

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 3
nop 1
dl 0
loc 19
ccs 13
cts 14
cp 0.9286
crap 4.0058
rs 9.2
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 26 and the first side effect is on line 16.

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

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

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

Loading history...
2
/**
3
 * Give API
4
 *
5
 * A front-facing JSON/XML API that makes it possible to query donation data.
6
 *
7
 * @package     Give
8
 * @subpackage  Classes/API
9
 * @copyright   Copyright (c) 2016, WordImpress
10
 * @license     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 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
	 *
89
	 * @var bool
90
	 * @access public
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 protected
109
	 * @since  1.1
110
	 */
111
	protected $versions = array();
112
113
	/**
114
	 * Queried endpoint
115
	 *
116
	 * @var string
117
	 * @access private
118
	 * @since  1.1
119
	 */
120
	private $endpoint;
121
122
	/**
123
	 * Endpoints routes
124
	 *
125
	 * @var object
126
	 * @access private
127
	 * @since  1.1
128
	 */
129
	private $routes;
130
131
	/**
132
	 * Setup the Give API
133
	 *
134
	 * @since 1.1
135
	 * @access public
136
	 */
137 13
	public function __construct() {
138
139 13
		$this->versions = array(
140 13
			'v1' => 'GIVE_API_V1',
141
		);
142
143 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...
144 13
			require_once GIVE_PLUGIN_DIR . 'includes/api/class-give-api-' . $version . '.php';
145 13
		}
146
147 13
		add_action( 'init', array( $this, 'add_endpoint' ) );
148 13
		add_action( 'wp', array( $this, 'process_query' ), - 1 );
149 13
		add_filter( 'query_vars', array( $this, 'query_vars' ) );
150 13
		add_action( 'show_user_profile', array( $this, 'user_key_field' ) );
151 13
		add_action( 'edit_user_profile', array( $this, 'user_key_field' ) );
152 13
		add_action( 'personal_options_update', array( $this, 'update_key' ) );
153 13
		add_action( 'edit_user_profile_update', array( $this, 'update_key' ) );
154 13
		add_action( 'give_process_api_key', array( $this, 'process_api_key' ) );
155
156
		// Setup a backwards compatibility check for user API Keys
157 13
		add_filter( 'get_user_metadata', array( $this, 'api_key_backwards_compat' ), 10, 4 );
158
159
		// Determine if JSON_PRETTY_PRINT is available
160 13
		$this->pretty_print = defined( 'JSON_PRETTY_PRINT' ) ? JSON_PRETTY_PRINT : null;
161
162
		// Allow API request logging to be turned off
163 13
		$this->log_requests = apply_filters( 'give_api_log_requests', $this->log_requests );
164
165
		// Setup Give_Payment_Stats instance
166 13
		$this->stats = new Give_Payment_Stats;
167
168 13
	}
169
170
	/**
171
	 * Registers a new rewrite endpoint for accessing the API
172
	 *
173
	 * @access public
174
	 *
175
	 * @param array $rewrite_rules WordPress Rewrite Rules
176
	 *
177
	 * @since  1.1
178
	 */
179 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...
180 11
		add_rewrite_endpoint( 'give-api', EP_ALL );
181 11
	}
182
183
	/**
184
	 * Registers query vars for API access
185
	 *
186
	 * @access public
187
	 * @since  1.1
188
	 *
189
	 * @param array $vars Query vars
190
	 *
191
	 * @return string[] $vars New query vars
192
	 */
193 4
	public function query_vars( $vars ) {
194
195 4
		$vars[] = 'token';
196 4
		$vars[] = 'key';
197 4
		$vars[] = 'query';
198 4
		$vars[] = 'type';
199 4
		$vars[] = 'form';
200 4
		$vars[] = 'number';
201 4
		$vars[] = 'date';
202 4
		$vars[] = 'startdate';
203 4
		$vars[] = 'enddate';
204 4
		$vars[] = 'donor';
205 4
		$vars[] = 'format';
206 4
		$vars[] = 'id';
207 4
		$vars[] = 'purchasekey';
208 4
		$vars[] = 'email';
209
210 4
		return $vars;
211
	}
212
213
	/**
214
	 * Retrieve the API versions
215
	 *
216
	 * @access public
217
	 * @since  1.1
218
	 * @return array
219
	 */
220 13
	public function get_versions() {
221 13
		return $this->versions;
222
	}
223
224
	/**
225
	 * Retrieve the API version that was queried
226
	 *
227
	 * @access public
228
	 * @since  1.1
229
	 * @return string
230
	 */
231
	public function get_queried_version() {
232
		return $this->queried_version;
233
	}
234
235
	/**
236
	 * Retrieves the default version of the API to use
237
	 *
238
	 * @access public
239
	 * @since  1.1
240
	 * @return string
241
	 */
242 1
	public function get_default_version() {
243
244 1
		$version = get_option( 'give_default_api_version' );
245
246 1
		if ( defined( 'GIVE_API_VERSION' ) ) {
247 1
			$version = GIVE_API_VERSION;
248 1
		} elseif ( ! $version ) {
249
			$version = 'v1';
250
		}
251
252 1
		return $version;
253
	}
254
255
	/**
256
	 * Sets the version of the API that was queried.
257
	 *
258
	 * Falls back to the default version if no version is specified
259
	 *
260
	 * @access private
261
	 * @since  1.1
262
	 */
263
	private function set_queried_version() {
264
265
		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...
266
267
		$version = $wp_query->query_vars['give-api'];
268
269
		if ( strpos( $version, '/' ) ) {
270
271
			$version = explode( '/', $version );
272
			$version = strtolower( $version[0] );
273
274
			$wp_query->query_vars['give-api'] = str_replace( $version . '/', '', $wp_query->query_vars['give-api'] );
275
276
			if ( array_key_exists( $version, $this->versions ) ) {
277
278
				$this->queried_version = $version;
279
280
			} else {
281
282
				$this->is_valid_request = false;
283
				$this->invalid_version();
284
			}
285
286
		} else {
287
288
			$this->queried_version = $this->get_default_version();
289
290
		}
291
292
	}
293
294
	/**
295
	 * Validate the API request
296
	 *
297
	 * Checks for the user's public key and token against the secret key
298
	 *
299
	 * @access private
300
	 * @global object $wp_query WordPress Query
301
	 * @uses   Give_API::get_user()
302
	 * @uses   Give_API::invalid_key()
303
	 * @uses   Give_API::invalid_auth()
304
	 * @since  1.1
305
	 * @return void
306
	 */
307
	private function validate_request() {
308
		global $wp_query;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
309
310
		$this->override = false;
311
312
		// Make sure we have both user and api key
313
		if ( ! empty( $wp_query->query_vars['give-api'] ) && ( $wp_query->query_vars['give-api'] != 'forms' || ! empty( $wp_query->query_vars['token'] ) ) ) {
314
315
			if ( empty( $wp_query->query_vars['token'] ) || empty( $wp_query->query_vars['key'] ) ) {
316
				$this->missing_auth();
317
			}
318
319
			// Retrieve the user by public API key and ensure they exist
320
			if ( ! ( $user = $this->get_user( $wp_query->query_vars['key'] ) ) ) {
321
322
				$this->invalid_key();
323
324
			} else {
325
326
				$token  = urldecode( $wp_query->query_vars['token'] );
327
				$secret = $this->get_user_secret_key( $user );
328
				$public = urldecode( $wp_query->query_vars['key'] );
329
330
				if ( hash_equals( md5( $secret . $public ), $token ) ) {
331
					$this->is_valid_request = true;
332
				} else {
333
					$this->invalid_auth();
334
				}
335
			}
336
		} elseif ( ! empty( $wp_query->query_vars['give-api'] ) && $wp_query->query_vars['give-api'] == 'forms' ) {
337
			$this->is_valid_request = true;
338
			$wp_query->set( 'key', 'public' );
339
		}
340
	}
341
342
	/**
343
	 * Retrieve the user ID based on the public key provided
344
	 *
345
	 * @access public
346
	 * @since  1.1
347
	 * @global WPDB $wpdb Used to query the database using the WordPress
348
	 *                      Database API
349
	 *
350
	 * @param string $key Public Key
351
	 *
352
	 * @return bool if user ID is found, false otherwise
353
	 */
354 1
	public function get_user( $key = '' ) {
355 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...
356
357 1
		if ( empty( $key ) ) {
358
			$key = urldecode( $wp_query->query_vars['key'] );
359
		}
360
361 1
		if ( empty( $key ) ) {
362
			return false;
363
		}
364
365 1
		$user = get_transient( md5( 'give_api_user_' . $key ) );
366
367 1
		if ( false === $user ) {
368 1
			$user = $wpdb->get_var( $wpdb->prepare( "SELECT user_id FROM $wpdb->usermeta WHERE meta_key = %s LIMIT 1", $key ) );
369 1
			set_transient( md5( 'give_api_user_' . $key ), $user, DAY_IN_SECONDS );
370 1
		}
371
372 1
		if ( $user != null ) {
373 1
			$this->user_id = $user;
374
375 1
			return $user;
376
		}
377
378
		return false;
379
	}
380
381 2
	public function get_user_public_key( $user_id = 0 ) {
382 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...
383
384 2
		if ( empty( $user_id ) ) {
385
			return '';
386
		}
387
388 2
		$cache_key       = md5( 'give_api_user_public_key' . $user_id );
389 2
		$user_public_key = get_transient( $cache_key );
390
391 2
		if ( empty( $user_public_key ) ) {
392 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 ) );
393 2
			set_transient( $cache_key, $user_public_key, HOUR_IN_SECONDS );
394 2
		}
395
396 2
		return $user_public_key;
397
	}
398
399 11
	public function get_user_secret_key( $user_id = 0 ) {
400 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...
401
402 2
		if ( empty( $user_id ) ) {
403
			return '';
404
		}
405
406 2
		$cache_key       = md5( 'give_api_user_secret_key' . $user_id );
407 2
		$user_secret_key = get_transient( $cache_key );
408
409 2
		if ( empty( $user_secret_key ) ) {
410 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 ) );
411 11
			set_transient( $cache_key, $user_secret_key, HOUR_IN_SECONDS );
412 2
		}
413
414 2
		return $user_secret_key;
415
	}
416
417
	/**
418
	 * Displays a missing authentication error if all the parameters aren't
419
	 * provided
420
	 *
421
	 * @access private
422
	 * @uses   Give_API::output()
423
	 * @since  1.1
424
	 */
425
	private function missing_auth() {
426
		$error          = array();
427
		$error['error'] = esc_attr__( 'You must specify both a token and API key!', 'give' );
428
429
		$this->data = $error;
430
		$this->output( 401 );
431
	}
432
433
	/**
434
	 * Displays an authentication failed error if the user failed to provide valid
435
	 * credentials
436
	 *
437
	 * @access private
438
	 * @since  1.1
439
	 * @uses   Give_API::output()
440
	 * @return void
441
	 */
442
	private function invalid_auth() {
443
		$error          = array();
444
		$error['error'] = esc_attr__( 'Your request could not be authenticated!', 'give' );
445
446
		$this->data = $error;
447
		$this->output( 403 );
448
	}
449
450
	/**
451
	 * Displays an invalid API key error if the API key provided couldn't be
452
	 * validated
453
	 *
454
	 * @access private
455
	 * @since  1.1
456
	 * @uses   Give_API::output()
457
	 * @return void
458
	 */
459
	private function invalid_key() {
460
		$error          = array();
461
		$error['error'] = esc_attr__( 'Invalid API key!', 'give' );
462
463
		$this->data = $error;
464
		$this->output( 403 );
465
	}
466
467
	/**
468
	 * Displays an invalid version error if the version number passed isn't valid
469
	 *
470
	 * @access private
471
	 * @since  1.1
472
	 * @uses   Give_API::output()
473
	 * @return void
474
	 */
475
	private function invalid_version() {
476
		$error          = array();
477
		$error['error'] = esc_attr__( 'Invalid API version!', 'give' );
478
479
		$this->data = $error;
480
		$this->output( 404 );
481
	}
482
483
	/**
484
	 * Listens for the API and then processes the API requests
485
	 *
486
	 * @access public
487
	 * @global $wp_query
488
	 * @since  1.1
489
	 * @return void
490
	 */
491 3
	public function process_query() {
492
493 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...
494
495
		// Start logging how long the request takes for logging
496 3
		$before = microtime( true );
497
498
		// Check for give-api var. Get out if not present
499 3
		if ( empty( $wp_query->query_vars['give-api'] ) ) {
500 3
			return;
501
		}
502
503
		// Determine which version was queried
504
		$this->set_queried_version();
505
506
		// Determine the kind of query
507
		$this->set_query_mode();
508
509
		// Check for a valid user and set errors if necessary
510
		$this->validate_request();
511
512
		// Only proceed if no errors have been noted
513
		if ( ! $this->is_valid_request ) {
514
			return;
515
		}
516
517
		if ( ! defined( 'GIVE_DOING_API' ) ) {
518
			define( 'GIVE_DOING_API', true );
519
		}
520
521
		$data         = array();
522
		$this->routes = new $this->versions[$this->get_queried_version()];
523
		$this->routes->validate_request();
524
525
		switch ( $this->endpoint ) :
526
527
			case 'stats' :
528
529
				$data = $this->routes->get_stats( array(
530
					'type'      => isset( $wp_query->query_vars['type'] ) ? $wp_query->query_vars['type'] : null,
531
					'form'      => isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null,
532
					'date'      => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null,
533
					'startdate' => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null,
534
					'enddate'   => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null
535
				) );
536
537
				break;
538
539
			case 'forms' :
540
541
				$form = isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null;
542
543
				$data = $this->routes->get_forms( $form );
544
545
				break;
546
547
			case 'donors' :
548
549
				$customer = isset( $wp_query->query_vars['donor'] ) ? $wp_query->query_vars['donor'] : null;
550
551
				$data = $this->routes->get_customers( $customer );
552
553
				break;
554
555
			case 'donations' :
556
557
				$data = $this->routes->get_recent_donations();
558
559
				break;
560
561
		endswitch;
562
563
		// Allow extensions to setup their own return data
564
		$this->data = apply_filters( 'give_api_output_data', $data, $this->endpoint, $this );
565
566
		$after                       = microtime( true );
567
		$request_time                = ( $after - $before );
568
		$this->data['request_speed'] = $request_time;
569
570
		// Log this API request, if enabled. We log it here because we have access to errors.
571
		$this->log_request( $this->data );
572
573
		// Send out data to the output function
574
		$this->output();
575
	}
576
577
	/**
578
	 * Returns the API endpoint requested
579
	 *
580
	 * @access public
581
	 * @since  1.1
582
	 * @return string $query Query mode
583
	 */
584
	public function get_query_mode() {
585
586
		return $this->endpoint;
587
	}
588
589
	/**
590
	 * Determines the kind of query requested and also ensure it is a valid query
591
	 *
592
	 * @access public
593
	 * @since  1.1
594
	 * @global $wp_query
595
	 */
596
	public function set_query_mode() {
597
598
		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...
599
600
		// Whitelist our query options
601
		$accepted = apply_filters( 'give_api_valid_query_modes', array(
602
			'stats',
603
			'forms',
604
			'donors',
605
			'donations'
606
		) );
607
608
		$query = isset( $wp_query->query_vars['give-api'] ) ? $wp_query->query_vars['give-api'] : null;
609
		$query = str_replace( $this->queried_version . '/', '', $query );
610
611
		$error = array();
612
613
		// Make sure our query is valid
614
		if ( ! in_array( $query, $accepted ) ) {
615
			$error['error'] = __( 'Invalid query!', 'give' );
616
617
			$this->data = $error;
618
			// 400 is Bad Request
619
			$this->output( 400 );
620
		}
621
622
		$this->endpoint = $query;
623
	}
624
625
	/**
626
	 * Get page number
627
	 *
628
	 * @access public
629
	 * @since  1.1
630
	 * @global $wp_query
631
	 * @return int $wp_query->query_vars['page'] if page number returned (default: 1)
632
	 */
633 11
	public function get_paged() {
634 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...
635
636 11
		return isset( $wp_query->query_vars['page'] ) ? $wp_query->query_vars['page'] : 1;
637
	}
638
639
640
	/**
641
	 * Number of results to display per page
642
	 *
643
	 * @access public
644
	 * @since  1.1
645
	 * @global $wp_query
646
	 * @return int $per_page Results to display per page (default: 10)
647
	 */
648 11
	public function per_page() {
649 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...
650
651 11
		$per_page = isset( $wp_query->query_vars['number'] ) ? $wp_query->query_vars['number'] : 10;
652
653 11
		if ( $per_page < 0 && $this->get_query_mode() == 'donors' ) {
654
			$per_page = 99999999;
655
		} // Customers query doesn't support -1
656
657 11
		return apply_filters( 'give_api_results_per_page', $per_page );
658
	}
659
660
	/**
661
	 * Sets up the dates used to retrieve earnings/donations
662
	 *
663
	 * @access public
664
	 * @since  1.2
665
	 *
666
	 * @param array $args Arguments to override defaults
667
	 *
668
	 * @return array $dates
669
	 */
670
	public function get_dates( $args = array() ) {
671
		$dates = array();
672
673
		$defaults = array(
674
			'type'      => '',
675
			'form'      => null,
676
			'date'      => null,
677
			'startdate' => null,
678
			'enddate'   => null
679
		);
680
681
		$args = wp_parse_args( $args, $defaults );
682
683
		$current_time = current_time( 'timestamp' );
684
685
		if ( 'range' === $args['date'] ) {
686
			$startdate          = strtotime( $args['startdate'] );
687
			$enddate            = strtotime( $args['enddate'] );
688
			$dates['day_start'] = date( 'd', $startdate );
689
			$dates['day_end']   = date( 'd', $enddate );
690
			$dates['m_start']   = date( 'n', $startdate );
691
			$dates['m_end']     = date( 'n', $enddate );
692
			$dates['year']      = date( 'Y', $startdate );
693
			$dates['year_end']  = date( 'Y', $enddate );
694
		} else {
695
			// Modify dates based on predefined ranges
696
			switch ( $args['date'] ) :
697
698
				case 'this_month' :
699
					$dates['day']     = null;
700
					$dates['m_start'] = date( 'n', $current_time );
701
					$dates['m_end']   = date( 'n', $current_time );
702
					$dates['year']    = date( 'Y', $current_time );
703
					break;
704
705
				case 'last_month' :
706
					$dates['day']     = null;
707
					$dates['m_start'] = date( 'n', $current_time ) == 1 ? 12 : date( 'n', $current_time ) - 1;
708
					$dates['m_end']   = $dates['m_start'];
709
					$dates['year']    = date( 'n', $current_time ) == 1 ? date( 'Y', $current_time ) - 1 : date( 'Y', $current_time );
710
					break;
711
712
				case 'today' :
713
					$dates['day']     = date( 'd', $current_time );
714
					$dates['m_start'] = date( 'n', $current_time );
715
					$dates['m_end']   = date( 'n', $current_time );
716
					$dates['year']    = date( 'Y', $current_time );
717
					break;
718
719
				case 'yesterday' :
720
721
					$year  = date( 'Y', $current_time );
722
					$month = date( 'n', $current_time );
723
					$day   = date( 'd', $current_time );
724
725
					if ( $month == 1 && $day == 1 ) {
726
727
						$year -= 1;
728
						$month = 12;
729
						$day   = cal_days_in_month( CAL_GREGORIAN, $month, $year );
730
731
					} elseif ( $month > 1 && $day == 1 ) {
732
733
						$month -= 1;
734
						$day = cal_days_in_month( CAL_GREGORIAN, $month, $year );
735
736
					} else {
737
738
						$day -= 1;
739
740
					}
741
742
					$dates['day']     = $day;
743
					$dates['m_start'] = $month;
744
					$dates['m_end']   = $month;
745
					$dates['year']    = $year;
746
747
					break;
748
749
				case 'this_quarter' :
750
					$month_now = date( 'n', $current_time );
751
752
					$dates['day'] = null;
753
754
					if ( $month_now <= 3 ) {
755
756
						$dates['m_start'] = 1;
757
						$dates['m_end']   = 3;
758
						$dates['year']    = date( 'Y', $current_time );
759
760
					} else if ( $month_now <= 6 ) {
761
762
						$dates['m_start'] = 4;
763
						$dates['m_end']   = 6;
764
						$dates['year']    = date( 'Y', $current_time );
765
766
					} else if ( $month_now <= 9 ) {
767
768
						$dates['m_start'] = 7;
769
						$dates['m_end']   = 9;
770
						$dates['year']    = date( 'Y', $current_time );
771
772
					} else {
773
774
						$dates['m_start'] = 10;
775
						$dates['m_end']   = 12;
776
						$dates['year']    = date( 'Y', $current_time );
777
778
					}
779
					break;
780
781
				case 'last_quarter' :
782
					$month_now = date( 'n', $current_time );
783
784
					$dates['day'] = null;
785
786
					if ( $month_now <= 3 ) {
787
788
						$dates['m_start'] = 10;
789
						$dates['m_end']   = 12;
790
						$dates['year']    = date( 'Y', $current_time ) - 1; // Previous year
791
792
					} else if ( $month_now <= 6 ) {
793
794
						$dates['m_start'] = 1;
795
						$dates['m_end']   = 3;
796
						$dates['year']    = date( 'Y', $current_time );
797
798
					} else if ( $month_now <= 9 ) {
799
800
						$dates['m_start'] = 4;
801
						$dates['m_end']   = 6;
802
						$dates['year']    = date( 'Y', $current_time );
803
804
					} else {
805
806
						$dates['m_start'] = 7;
807
						$dates['m_end']   = 9;
808
						$dates['year']    = date( 'Y', $current_time );
809
810
					}
811
					break;
812
813
				case 'this_year' :
814
					$dates['day']     = null;
815
					$dates['m_start'] = null;
816
					$dates['m_end']   = null;
817
					$dates['year']    = date( 'Y', $current_time );
818
					break;
819
820
				case 'last_year' :
821
					$dates['day']     = null;
822
					$dates['m_start'] = null;
823
					$dates['m_end']   = null;
824
					$dates['year']    = date( 'Y', $current_time ) - 1;
825
					break;
826
827
			endswitch;
828
		}
829
830
		/**
831
		 * Returns the filters for the dates used to retrieve earnings/donations
832
		 *
833
		 * @since 1.2
834
		 *
835
		 * @param object $dates The dates used for retrieving earnings/donations
836
		 */
837
838
		return apply_filters( 'give_api_stat_dates', $dates );
839
	}
840
841
	/**
842
	 * Process Get Customers API Request
843
	 *
844
	 * @access public
845
	 * @since  1.1
846
	 * @global WPDB $wpdb Used to query the database using the WordPress
847
	 *                          Database API
848
	 *
849
	 * @param int $customer Customer ID
850
	 *
851
	 * @return array $customers Multidimensional array of the customers
852
	 */
853 1
	public function get_customers( $customer = null ) {
854
855 1
		$customers = array();
856 1
		$error     = array();
857 1
		if ( ! user_can( $this->user_id, 'view_give_sensitive_data' ) && ! $this->override ) {
858
			return $customers;
859
		}
860
861 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...
862
863 1
		$paged    = $this->get_paged();
864 1
		$per_page = $this->per_page();
865 1
		$offset   = $per_page * ( $paged - 1 );
866
867 1
		if ( is_numeric( $customer ) ) {
868
			$field = 'id';
869
		} else {
870 1
			$field = 'email';
871
		}
872
873 1
		$customer_query = Give()->customers->get_customers( array(
874 1
			'number' => $per_page,
875 1
			'offset' => $offset,
876
			$field   => $customer
877 1
		) );
878 1
		$customer_count = 0;
879
880 1
		if ( $customer_query ) {
881
882 1
			foreach ( $customer_query as $customer_obj ) {
883
884 1
				$names      = explode( ' ', $customer_obj->name );
885 1
				$first_name = ! empty( $names[0] ) ? $names[0] : '';
886 1
				$last_name  = '';
887 1
				if ( ! empty( $names[1] ) ) {
888 1
					unset( $names[0] );
889 1
					$last_name = implode( ' ', $names );
890 1
				}
891
892 1
				$customers['donors'][ $customer_count ]['info']['user_id']      = '';
893 1
				$customers['donors'][ $customer_count ]['info']['username']     = '';
894 1
				$customers['donors'][ $customer_count ]['info']['display_name'] = '';
895 1
				$customers['donors'][ $customer_count ]['info']['customer_id']  = $customer_obj->id;
896 1
				$customers['donors'][ $customer_count ]['info']['first_name']   = $first_name;
897 1
				$customers['donors'][ $customer_count ]['info']['last_name']    = $last_name;
898 1
				$customers['donors'][ $customer_count ]['info']['email']        = $customer_obj->email;
899
900 1
				if ( ! empty( $customer_obj->user_id ) ) {
901
902 1
					$user_data = get_userdata( $customer_obj->user_id );
903
904
					// Customer with registered account
905 1
					$customers['donors'][ $customer_count ]['info']['user_id']      = $customer_obj->user_id;
906 1
					$customers['donors'][ $customer_count ]['info']['username']     = $user_data->user_login;
907 1
					$customers['donors'][ $customer_count ]['info']['display_name'] = $user_data->display_name;
908
909 1
				}
910
911 1
				$customers['donors'][ $customer_count ]['stats']['total_donations'] = $customer_obj->purchase_count;
912 1
				$customers['donors'][ $customer_count ]['stats']['total_spent']     = $customer_obj->purchase_value;
913
914 1
				$customer_count ++;
915
916 1
			}
917
918 1
		} elseif ( $customer ) {
919
920
			$error['error'] = sprintf( __( 'Donor %s not found!', 'give' ), $customer );
921
922
			return $error;
923
924
		} else {
925
926
			$error['error'] = __( 'No donors found!', 'give' );
927
928
			return $error;
929
930
		}
931
932 1
		return $customers;
933
	}
934
935
	/**
936
	 * Process Get Products API Request
937
	 *
938
	 * @access public
939
	 * @since  1.1
940
	 *
941
	 * @param int $form Give Form ID
942
	 *
943
	 * @return array $customers Multidimensional array of the forms
944
	 */
945 11
	public function get_forms( $form = null ) {
946
947 11
		$forms = array();
948 11
		$error = array();
949
950 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...
951 11
			$forms['forms'] = array();
952
953 11
			$form_list = get_posts( array(
954 11
				'post_type'        => 'give_forms',
955 11
				'posts_per_page'   => $this->per_page(),
956 11
				'suppress_filters' => true,
957 11
				'paged'            => $this->get_paged()
958 11
			) );
959
960 11
			if ( $form_list ) {
961 11
				$i = 0;
962 11
				foreach ( $form_list as $form_info ) {
963 11
					$forms['forms'][ $i ] = $this->get_form_data( $form_info );
964 11
					$i ++;
965 11
				}
966 11
			}
967 11
		} else {
968
			if ( get_post_type( $form ) == 'give_forms' ) {
969
				$form_info = get_post( $form );
970
971
				$forms['forms'][0] = $this->get_form_data( $form_info );
972
973
			} else {
974
				$error['error'] = sprintf( __( 'Form %s not found!', 'give' ), $form );
975
976
				return $error;
977
			}
978
		}
979
980 11
		return $forms;
981
	}
982
983
	/**
984
	 * Given a give_forms post object, generate the data for the API output
985
	 *
986
	 * @since  1.1
987
	 *
988
	 * @param  object $form_info The Download Post Object
989
	 *
990
	 * @return array                Array of post data to return back in the API
991
	 */
992 11
	private function get_form_data( $form_info ) {
993
994 11
		$form = array();
995
996 11
		$form['info']['id']            = $form_info->ID;
997 11
		$form['info']['slug']          = $form_info->post_name;
998 11
		$form['info']['title']         = $form_info->post_title;
999 11
		$form['info']['create_date']   = $form_info->post_date;
1000 11
		$form['info']['modified_date'] = $form_info->post_modified;
1001 11
		$form['info']['status']        = $form_info->post_status;
1002 11
		$form['info']['link']          = html_entity_decode( $form_info->guid );
1003 11
		$form['info']['content']       = get_post_meta( $form_info->ID, '_give_form_content', true );
1004 11
		$form['info']['thumbnail']     = wp_get_attachment_url( get_post_thumbnail_id( $form_info->ID ) );
1005
1006 11
		if ( give_get_option( 'enable_categories' ) == 'on' ) {
1007
			$form['info']['category'] = get_the_terms( $form_info, 'give_forms_category' );
1008
			$form['info']['tags']     = get_the_terms( $form_info, 'give_forms_tag' );
1009
		}
1010 11
		if ( give_get_option( 'enable_tags' ) == 'on' ) {
1011
			$form['info']['tags'] = get_the_terms( $form_info, 'give_forms_tag' );
1012
		}
1013
1014 11
		if ( user_can( $this->user_id, 'view_give_reports' ) || $this->override ) {
1015 11
			$form['stats']['total']['donations']           = give_get_form_sales_stats( $form_info->ID );
1016 11
			$form['stats']['total']['earnings']            = give_get_form_earnings_stats( $form_info->ID );
1017 11
			$form['stats']['monthly_average']['donations'] = give_get_average_monthly_form_sales( $form_info->ID );
1018 11
			$form['stats']['monthly_average']['earnings']  = give_get_average_monthly_form_earnings( $form_info->ID );
1019 11
		}
1020
1021 11
		$counter = 0;
1022 11
		if ( give_has_variable_prices( $form_info->ID ) ) {
1023 11
			foreach ( give_get_variable_prices( $form_info->ID ) as $price ) {
1024 11
				$counter ++;
1025
				//muli-level item
1026 11
				$level                                     = isset( $price['_give_text'] ) ? $price['_give_text'] : 'level-' . $counter;
1027 11
				$form['pricing'][ sanitize_key( $level ) ] = $price['_give_amount'];
1028
1029 11
			}
1030 11
		} else {
1031
			$form['pricing']['amount'] = give_get_form_price( $form_info->ID );
1032
		}
1033
1034 11
		if ( user_can( $this->user_id, 'view_give_sensitive_data' ) || $this->override ) {
1035
1036
			//Sensitive data here
1037 11
			do_action( 'give_api_sensitive_data' );
1038
1039 11
		}
1040
1041 11
		return apply_filters( 'give_api_forms_form', $form );
1042
1043
	}
1044
1045
	/**
1046
	 * Process Get Stats API Request
1047
	 *
1048
	 * @since 1.1
1049
	 *
1050
	 * @global WPDB $wpdb Used to query the database using the WordPress
1051
	 *
1052
	 * @param array $args Arguments provided by API Request
1053
	 *
1054
	 * @return array
1055
	 */
1056
	public function get_stats( $args = array() ) {
1057
		$defaults = array(
1058
			'type'      => null,
1059
			'form'      => null,
1060
			'date'      => null,
1061
			'startdate' => null,
1062
			'enddate'   => null
1063
		);
1064
1065
		$args = wp_parse_args( $args, $defaults );
1066
1067
		$dates = $this->get_dates( $args );
1068
1069
		$stats    = array();
1070
		$earnings = array(
1071
			'earnings' => array()
1072
		);
1073
		$sales    = array(
1074
			'donations' => array()
1075
		);
1076
		$error    = array();
1077
1078
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1079
			return $stats;
1080
		}
1081
1082
		if ( $args['type'] == 'donations' ) {
1083
1084
			if ( $args['form'] == null ) {
1085
				if ( $args['date'] == null ) {
1086
					$sales = $this->get_default_sales_stats();
1087
				} elseif ( $args['date'] === 'range' ) {
1088
					// Return sales for a date range
1089
1090
					// Ensure the end date is later than the start date
1091
					if ( $args['enddate'] < $args['startdate'] ) {
1092
						$error['error'] = __( 'The end date must be later than the start date!', 'give' );
1093
					}
1094
1095
					// Ensure both the start and end date are specified
1096
					if ( empty( $args['startdate'] ) || empty( $args['enddate'] ) ) {
1097
						$error['error'] = __( 'Invalid or no date range specified!', 'give' );
1098
					}
1099
1100
					$total = 0;
1101
1102
					// Loop through the years
1103
					$y = $dates['year'];
1104
					while ( $y <= $dates['year_end'] ) :
1105
1106
						if ( $dates['year'] == $dates['year_end'] ) {
1107
							$month_start = $dates['m_start'];
1108
							$month_end   = $dates['m_end'];
1109
						} elseif ( $y == $dates['year'] && $dates['year_end'] > $dates['year'] ) {
1110
							$month_start = $dates['m_start'];
1111
							$month_end   = 12;
1112
						} elseif ( $y == $dates['year_end'] ) {
1113
							$month_start = 1;
1114
							$month_end   = $dates['m_end'];
1115
						} else {
1116
							$month_start = 1;
1117
							$month_end   = 12;
1118
						}
1119
1120
						$i = $month_start;
1121
						while ( $i <= $month_end ) :
1122
1123
							if ( $i == $dates['m_start'] ) {
1124
								$d = $dates['day_start'];
1125
							} else {
1126
								$d = 1;
1127
							}
1128
1129
							if ( $i == $dates['m_end'] ) {
1130
								$num_of_days = $dates['day_end'];
1131
							} else {
1132
								$num_of_days = cal_days_in_month( CAL_GREGORIAN, $i, $y );
1133
							}
1134
1135
							while ( $d <= $num_of_days ) :
1136
								$sale_count = give_get_sales_by_date( $d, $i, $y );
1137
								$date_key   = date( 'Ymd', strtotime( $y . '/' . $i . '/' . $d ) );
1138
								if ( ! isset( $sales['sales'][ $date_key ] ) ) {
1139
									$sales['sales'][ $date_key ] = 0;
1140
								}
1141
								$sales['sales'][ $date_key ] += $sale_count;
1142
								$total += $sale_count;
1143
								$d ++;
1144
							endwhile;
1145
							$i ++;
1146
						endwhile;
1147
1148
						$y ++;
1149
					endwhile;
1150
1151
					$sales['totals'] = $total;
1152
				} else {
1153
					if ( $args['date'] == 'this_quarter' || $args['date'] == 'last_quarter' ) {
1154
						$sales_count = 0;
1155
1156
						// Loop through the months
1157
						$month = $dates['m_start'];
1158
1159
						while ( $month <= $dates['m_end'] ) :
1160
							$sales_count += give_get_sales_by_date( null, $month, $dates['year'] );
1161
							$month ++;
1162
						endwhile;
1163
1164
						$sales['donations'][ $args['date'] ] = $sales_count;
1165
					} else {
1166
						$sales['donations'][ $args['date'] ] = give_get_sales_by_date( $dates['day'], $dates['m_start'], $dates['year'] );
1167
					}
1168
				}
1169
			} elseif ( $args['form'] == 'all' ) {
1170
				$forms = get_posts( array( 'post_type' => 'give_forms', 'nopaging' => true ) );
1171
				$i     = 0;
1172
				foreach ( $forms as $form_info ) {
1173
					$sales['donations'][ $i ] = array( $form_info->post_name => give_get_form_sales_stats( $form_info->ID ) );
1174
					$i ++;
1175
				}
1176
			} else {
1177
				if ( get_post_type( $args['form'] ) == 'give_forms' ) {
1178
					$form_info             = get_post( $args['form'] );
1179
					$sales['donations'][0] = array( $form_info->post_name => give_get_form_sales_stats( $args['form'] ) );
1180
				} else {
1181
					$error['error'] = sprintf( __( 'Product %s not found!', 'give' ), $args['form'] );
1182
				}
1183
			}
1184
1185
			if ( ! empty( $error ) ) {
1186
				return $error;
1187
			}
1188
1189
			return $sales;
1190
1191
		} elseif ( $args['type'] == 'earnings' ) {
1192
			if ( $args['form'] == null ) {
1193
				if ( $args['date'] == null ) {
1194
					$earnings = $this->get_default_earnings_stats();
1195
				} elseif ( $args['date'] === 'range' ) {
1196
					// Return sales for a date range
1197
1198
					// Ensure the end date is later than the start date
1199
					if ( $args['enddate'] < $args['startdate'] ) {
1200
						$error['error'] = __( 'The end date must be later than the start date!', 'give' );
1201
					}
1202
1203
					// Ensure both the start and end date are specified
1204
					if ( empty( $args['startdate'] ) || empty( $args['enddate'] ) ) {
1205
						$error['error'] = __( 'Invalid or no date range specified!', 'give' );
1206
					}
1207
1208
					$total = (float) 0.00;
1209
1210
					// Loop through the years
1211
					$y = $dates['year'];
1212
					if ( ! isset( $earnings['earnings'] ) ) {
1213
						$earnings['earnings'] = array();
1214
					}
1215
					while ( $y <= $dates['year_end'] ) :
1216
1217
						if ( $dates['year'] == $dates['year_end'] ) {
1218
							$month_start = $dates['m_start'];
1219
							$month_end   = $dates['m_end'];
1220
						} elseif ( $y == $dates['year'] && $dates['year_end'] > $dates['year'] ) {
1221
							$month_start = $dates['m_start'];
1222
							$month_end   = 12;
1223
						} elseif ( $y == $dates['year_end'] ) {
1224
							$month_start = 1;
1225
							$month_end   = $dates['m_end'];
1226
						} else {
1227
							$month_start = 1;
1228
							$month_end   = 12;
1229
						}
1230
1231
						$i = $month_start;
1232
						while ( $i <= $month_end ) :
1233
1234
							if ( $i == $dates['m_start'] ) {
1235
								$d = $dates['day_start'];
1236
							} else {
1237
								$d = 1;
1238
							}
1239
1240
							if ( $i == $dates['m_end'] ) {
1241
								$num_of_days = $dates['day_end'];
1242
							} else {
1243
								$num_of_days = cal_days_in_month( CAL_GREGORIAN, $i, $y );
1244
							}
1245
1246
							while ( $d <= $num_of_days ) :
1247
								$earnings_stat = give_get_earnings_by_date( $d, $i, $y );
1248
								$date_key      = date( 'Ymd', strtotime( $y . '/' . $i . '/' . $d ) );
1249
								if ( ! isset( $earnings['earnings'][ $date_key ] ) ) {
1250
									$earnings['earnings'][ $date_key ] = 0;
1251
								}
1252
								$earnings['earnings'][ $date_key ] += $earnings_stat;
1253
								$total += $earnings_stat;
1254
								$d ++;
1255
							endwhile;
1256
1257
							$i ++;
1258
						endwhile;
1259
1260
						$y ++;
1261
					endwhile;
1262
1263
					$earnings['totals'] = $total;
1264
				} else {
1265
					if ( $args['date'] == 'this_quarter' || $args['date'] == 'last_quarter' ) {
1266
						$earnings_count = (float) 0.00;
1267
1268
						// Loop through the months
1269
						$month = $dates['m_start'];
1270
1271
						while ( $month <= $dates['m_end'] ) :
1272
							$earnings_count += give_get_earnings_by_date( null, $month, $dates['year'] );
1273
							$month ++;
1274
						endwhile;
1275
1276
						$earnings['earnings'][ $args['date'] ] = $earnings_count;
1277
					} else {
1278
						$earnings['earnings'][ $args['date'] ] = give_get_earnings_by_date( $dates['day'], $dates['m_start'], $dates['year'] );
1279
					}
1280
				}
1281
			} elseif ( $args['form'] == 'all' ) {
1282
				$forms = get_posts( array( 'post_type' => 'give_forms', 'nopaging' => true ) );
1283
1284
				$i = 0;
1285
				foreach ( $forms as $form_info ) {
1286
					$earnings['earnings'][ $i ] = array( $form_info->post_name => give_get_form_earnings_stats( $form_info->ID ) );
1287
					$i ++;
1288
				}
1289
			} else {
1290
				if ( get_post_type( $args['form'] ) == 'give_forms' ) {
1291
					$form_info               = get_post( $args['form'] );
1292
					$earnings['earnings'][0] = array( $form_info->post_name => give_get_form_earnings_stats( $args['form'] ) );
1293
				} else {
1294
					$error['error'] = sprintf( __( 'Form %s not found!', 'give' ), $args['form'] );
1295
				}
1296
			}
1297
1298
			if ( ! empty( $error ) ) {
1299
				return $error;
1300
			}
1301
1302
			return $earnings;
1303
		} elseif ( $args['type'] == 'donors' ) {
1304
			$customers                          = new Give_DB_Customers();
1305
			$stats['donations']['total_donors'] = $customers->count();
1306
1307
			return $stats;
1308
1309
		} elseif ( empty( $args['type'] ) ) {
1310
			$stats = array_merge( $stats, $this->get_default_sales_stats() );
1311
			$stats = array_merge( $stats, $this->get_default_earnings_stats() );
1312
1313
			return array( 'stats' => $stats );
1314
		}
1315
	}
1316
1317
	/**
1318
	 * Retrieves Recent Donations
1319
	 *
1320
	 * @access public
1321
	 * @since  1.1
1322
	 * @return array
1323
	 */
1324 11
	public function get_recent_donations() {
1325 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...
1326
1327 11
		$sales = array();
1328
1329 11
		if ( ! user_can( $this->user_id, 'view_give_reports' ) && ! $this->override ) {
1330
			return $sales;
1331
		}
1332
1333 11
		if ( isset( $wp_query->query_vars['id'] ) ) {
1334
			$query   = array();
1335
			$query[] = new Give_Payment( $wp_query->query_vars['id'] );
1336 11
		} elseif ( isset( $wp_query->query_vars['purchasekey'] ) ) {
1337
			$query   = array();
1338
			$query[] = give_get_payment_by( 'key', $wp_query->query_vars['purchasekey'] );
1339 11
		} elseif ( isset( $wp_query->query_vars['email'] ) ) {
1340
			$args  = array(
1341
				'fields'     => 'ids',
1342
				'meta_key'   => '_give_payment_user_email',
1343
				'meta_value' => $wp_query->query_vars['email'],
1344
				'number'     => $this->per_page(),
1345
				'page'       => $this->get_paged(),
1346
				'status'     => 'publish'
1347
			);
1348
			$query = give_get_payments( $args );
1349
		} else {
1350
			$args  = array(
1351 11
				'fields' => 'ids',
1352 11
				'number' => $this->per_page(),
1353 11
				'page'   => $this->get_paged(),
1354
				'status' => 'publish'
1355 11
			);
1356 11
			$query = give_get_payments( $args );
1357
		}
1358 11
		if ( $query ) {
1359 11
			$i = 0;
1360 11
			foreach ( $query as $payment ) {
1361
1362 11
				if ( is_numeric( $payment ) ) {
1363 11
					$payment      = new Give_Payment( $payment );
1364 11
					$payment_meta = $payment->get_meta();
1365 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...
1366 11
				} else {
1367
					continue;
1368
				}
1369
1370 11
				$payment_meta = $payment->get_meta();
1371 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...
1372
1373 11
				$first_name = isset( $user_info['first_name'] ) ? $user_info['first_name'] : '';
1374 11
				$last_name  = isset( $user_info['last_name'] ) ? $user_info['last_name'] : '';
1375
1376 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...
1377 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...
1378 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...
1379 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...
1380 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...
1381 11
				$sales['donations'][ $i ]['name']           = $first_name . ' ' . $last_name;
1382 11
				$sales['donations'][ $i ]['fname']          = $first_name;
1383 11
				$sales['donations'][ $i ]['lname']          = $last_name;
1384 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...
1385 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...
1386
1387 11
				$form_id  = isset( $payment_meta['form_id'] ) ? $payment_meta['form_id'] : $payment_meta;
1388 11
				$price    = isset( $payment_meta['form_id'] ) ? give_get_form_price( $payment_meta['form_id'] ) : false;
1389 11
				$price_id = isset( $payment_meta['price_id'] ) ? $payment_meta['price_id'] : null;
1390
1391 11
				$sales['donations'][ $i ]['form']['id']    = $form_id;
1392 11
				$sales['donations'][ $i ]['form']['name']  = get_the_title( $payment_meta['form_id'] );
1393 11
				$sales['donations'][ $i ]['form']['price'] = $price;
1394
1395 11
				if ( give_has_variable_prices( $form_id ) ) {
1396 11
					if ( isset( $payment_meta['price_id'] ) ) {
1397 11
						$price_name                                     = give_get_price_option_name( $form_id, $payment_meta['price_id'], $payment->ID );
1398 11
						$sales['donations'][ $i ]['form']['price_name'] = $price_name;
1399 11
						$sales['donations'][ $i ]['form']['price_id']   = $price_id;
1400 11
						$sales['donations'][ $i ]['form']['price']      = give_get_price_option_amount( $form_id, $price_id );
1401
1402 11
					}
1403 11
				}
1404
1405
				//Add custom meta to API
1406 11
				foreach ( $payment_meta as $meta_key => $meta_value ) {
1407
1408
					$exceptions = array(
1409 11
						'form_title',
1410 11
						'form_id',
1411 11
						'price_id',
1412 11
						'user_info',
1413 11
						'key',
1414 11
						'email',
1415 11
						'date',
1416 11
					);
1417
1418
					//Don't clutter up results with dupes
1419 11
					if ( in_array( $meta_key, $exceptions ) ) {
1420 11
						continue;
1421
					}
1422
1423 11
					$sales['donations'][ $i ]['payment_meta'][ $meta_key ] = $meta_value;
1424
1425 11
				}
1426
1427 11
				$i ++;
1428 11
			}
1429 11
		}
1430
1431 11
		return apply_filters( 'give_api_donations_endpoint', $sales );
1432
	}
1433
1434
	/**
1435
	 * Retrieve the output format
1436
	 *
1437
	 * Determines whether results should be displayed in XML or JSON
1438
	 *
1439
	 * @since 1.1
1440
     * @access public
1441
	 *
1442
	 * @return mixed|void
1443
	 */
1444
	public function get_output_format() {
1445
		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...
1446
1447
		$format = isset( $wp_query->query_vars['format'] ) ? $wp_query->query_vars['format'] : 'json';
1448
1449
		return apply_filters( 'give_api_output_format', $format );
1450
	}
1451
1452
1453
	/**
1454
	 * Log each API request, if enabled
1455
	 *
1456
	 * @access private
1457
	 * @since  1.1
1458
     *
1459
	 * @global Give_Logging $give_logs
1460
	 * @global WP_Query     $wp_query
1461
	 *
1462
	 * @param array $data
1463
	 *
1464
	 * @return void
1465
	 */
1466
	private function log_request( $data = array() ) {
1467
		if ( ! $this->log_requests ) {
1468
			return;
1469
		}
1470
1471
        /**
1472
         * @var Give_Logging $give_logs
1473
         */
1474
		global $give_logs;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
1475
1476
        /**
1477
         * @var WP_Query $wp_query
1478
         */
1479
        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...
1480
1481
		$query = array(
1482
			'give-api'    => $wp_query->query_vars['give-api'],
1483
			'key'         => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null,
1484
			'token'       => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null,
1485
			'query'       => isset( $wp_query->query_vars['query'] ) ? $wp_query->query_vars['query'] : null,
1486
			'type'        => isset( $wp_query->query_vars['type'] ) ? $wp_query->query_vars['type'] : null,
1487
			'form'        => isset( $wp_query->query_vars['form'] ) ? $wp_query->query_vars['form'] : null,
1488
			'customer'    => isset( $wp_query->query_vars['customer'] ) ? $wp_query->query_vars['customer'] : null,
1489
			'date'        => isset( $wp_query->query_vars['date'] ) ? $wp_query->query_vars['date'] : null,
1490
			'startdate'   => isset( $wp_query->query_vars['startdate'] ) ? $wp_query->query_vars['startdate'] : null,
1491
			'enddate'     => isset( $wp_query->query_vars['enddate'] ) ? $wp_query->query_vars['enddate'] : null,
1492
			'id'          => isset( $wp_query->query_vars['id'] ) ? $wp_query->query_vars['id'] : null,
1493
			'purchasekey' => isset( $wp_query->query_vars['purchasekey'] ) ? $wp_query->query_vars['purchasekey'] : null,
1494
			'email'       => isset( $wp_query->query_vars['email'] ) ? $wp_query->query_vars['email'] : null,
1495
		);
1496
1497
		$log_data = array(
1498
			'log_type'     => 'api_request',
1499
			'post_excerpt' => http_build_query( $query ),
1500
			'post_content' => ! empty( $data['error'] ) ? $data['error'] : '',
1501
		);
1502
1503
		$log_meta = array(
1504
			'request_ip' => give_get_ip(),
1505
			'user'       => $this->user_id,
1506
			'key'        => isset( $wp_query->query_vars['key'] ) ? $wp_query->query_vars['key'] : null,
1507
			'token'      => isset( $wp_query->query_vars['token'] ) ? $wp_query->query_vars['token'] : null,
1508
			'time'       => $data['request_speed'],
1509
			'version'    => $this->get_queried_version()
1510
		);
1511
1512
		$give_logs->insert_log( $log_data, $log_meta );
1513
	}
1514
1515
1516
	/**
1517
	 * Retrieve the output data
1518
	 *
1519
	 * @access public
1520
	 * @since  1.1
1521
	 * @return array
1522
	 */
1523
	public function get_output() {
1524
		return $this->data;
1525
	}
1526
1527
	/**
1528
	 * Output Query in either JSON/XML. The query data is outputted as JSON
1529
	 * by default
1530
	 *
1531
	 * @since 1.1
1532
	 * @global WP_Query $wp_query
1533
	 *
1534
	 * @param int $status_code
1535
	 */
1536
	public function output( $status_code = 200 ) {
1537
        /**
1538
         * @var WP_Query $wp_query
1539
         */
1540
		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...
1541
1542
		$format = $this->get_output_format();
1543
1544
		status_header( $status_code );
1545
1546
		do_action( 'give_api_output_before', $this->data, $this, $format );
1547
1548
		switch ( $format ) :
1549
1550
			case 'xml' :
1551
1552
				require_once GIVE_PLUGIN_DIR . 'includes/libraries/array2xml.php';
1553
				$xml = Array2XML::createXML( 'give', $this->data );
1554
				echo $xml->saveXML();
1555
1556
				break;
1557
1558
			case 'json' :
1559
1560
				header( 'Content-Type: application/json' );
1561
				if ( ! empty( $this->pretty_print ) ) {
1562
					echo json_encode( $this->data, $this->pretty_print );
1563
				} else {
1564
					echo json_encode( $this->data );
1565
				}
1566
1567
				break;
1568
1569
1570
			default :
1571
1572
				// Allow other formats to be added via extensions
1573
				do_action( 'give_api_output_' . $format, $this->data, $this );
1574
1575
				break;
1576
1577
		endswitch;
1578
1579
		do_action( 'give_api_output_after', $this->data, $this, $format );
1580
1581
		give_die();
1582
	}
1583
1584
	/**
1585
	 * Modify User Profile
1586
	 *
1587
	 * Modifies the output of profile.php to add key generation/revocation
1588
	 *
1589
	 * @access public
1590
	 * @since  1.1
1591
	 *
1592
	 * @param object $user Current user info
1593
	 *
1594
	 * @return void
1595
	 */
1596
	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...
1597
1598
		if ( ( give_get_option( 'api_allow_user_keys', false ) || current_user_can( 'manage_give_settings' ) ) && current_user_can( 'edit_user', $user->ID ) ) {
1599
			$user = get_userdata( $user->ID );
1600
			?>
1601
			<table class="form-table">
1602
				<tbody>
1603
				<tr>
1604
					<th>
1605
						<?php esc_attr_e( 'Give API Keys', 'give' ); ?>
1606
					</th>
1607
					<td>
1608
						<?php
1609
						$public_key = $this->get_user_public_key( $user->ID );
1610
						$secret_key = $this->get_user_secret_key( $user->ID );
1611
						?>
1612
						<?php if ( empty( $user->give_user_public_key ) ) { ?>
1613
							<input name="give_set_api_key" type="checkbox" id="give_set_api_key" value="0"/>
1614
							<span class="description"><?php esc_attr_e( 'Generate API Key', 'give' ); ?></span>
1615
						<?php } else { ?>
1616
							<strong style="display:inline-block; width: 125px;"><?php _e( 'Public key:', 'give' ); ?>&nbsp;</strong>
1617
							<input type="text" disabled="disabled" class="regular-text" id="publickey" value="<?php echo esc_attr( $public_key ); ?>"/>
1618
							<br/>
1619
							<strong style="display:inline-block; width: 125px;"><?php esc_attr_e( 'Secret key:', 'give' ); ?>&nbsp;</strong>
1620
							<input type="text" disabled="disabled" class="regular-text" id="privatekey" value="<?php echo esc_attr( $secret_key ); ?>"/>
1621
							<br/>
1622
							<strong style="display:inline-block; width: 125px;"><?php esc_attr_e( 'Token:', 'give' ); ?>&nbsp;</strong>
1623
							<input type="text" disabled="disabled" class="regular-text" id="token" value="<?php echo esc_attr( $this->get_token( $user->ID ) ); ?>"/>
1624
							<br/>
1625
							<input name="give_set_api_key" type="checkbox" id="give_set_api_key" value="0"/>
1626
							<span class="description"><label for="give_set_api_key"><?php esc_attr_e( 'Revoke API Keys', 'give' ); ?></label></span>
1627
						<?php } ?>
1628
					</td>
1629
				</tr>
1630
				</tbody>
1631
			</table>
1632
		<?php }
1633
	}
1634
1635
	/**
1636
	 * Process an API key generation/revocation
1637
	 *
1638
	 * @access public
1639
	 * @since  1.1
1640
	 *
1641
	 * @param array $args
1642
	 *
1643
	 * @return void
1644
	 */
1645
	public function process_api_key( $args ) {
1646
1647
		if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'give-api-nonce' ) ) {
1648
1649
			wp_die( esc_attr__( 'Nonce verification failed', 'give' ), __( 'Error', 'give' ), array( 'response' => 403 ) );
1650
1651
		}
1652
1653
		if ( empty( $args['user_id'] ) ) {
1654
			wp_die( esc_attr__( 'User ID Required', 'give' ), esc_attr__( 'Error', 'give' ), array( 'response' => 401 ) );
1655
		}
1656
1657
		if ( is_numeric( $args['user_id'] ) ) {
1658
			$user_id = isset( $args['user_id'] ) ? absint( $args['user_id'] ) : get_current_user_id();
1659
		} else {
1660
			$userdata = get_user_by( 'login', $args['user_id'] );
1661
			$user_id  = $userdata->ID;
1662
		}
1663
		$process = isset( $args['give_api_process'] ) ? strtolower( $args['give_api_process'] ) : false;
1664
1665
		if ( $user_id == get_current_user_id() && ! give_get_option( 'allow_user_api_keys' ) && ! current_user_can( 'manage_give_settings' ) ) {
1666
			wp_die( sprintf( __( 'You do not have permission to %s API keys for this user', 'give' ), $process ), __( 'Error', 'give' ), array( 'response' => 403 ) );
1667
		} elseif ( ! current_user_can( 'manage_give_settings' ) ) {
1668
			wp_die( sprintf( __( 'You do not have permission to %s API keys for this user', 'give' ), $process ), __( 'Error', 'give' ), array( 'response' => 403 ) );
1669
		}
1670
1671
		switch ( $process ) {
1672
			case 'generate':
1673
				if ( $this->generate_api_key( $user_id ) ) {
1674
					delete_transient( 'give-total-api-keys' );
1675
					wp_redirect( add_query_arg( 'give-message', 'api-key-generated', 'edit.php?post_type=give_forms&page=give-settings&tab=api' ) );
1676
					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...
1677
				} else {
1678
					wp_redirect( add_query_arg( 'give-message', 'api-key-failed', 'edit.php?post_type=give_forms&page=give-settings&tab=api' ) );
1679
					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...
1680
				}
1681
				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...
1682
			case 'regenerate':
1683
				$this->generate_api_key( $user_id, true );
1684
				delete_transient( 'give-total-api-keys' );
1685
				wp_redirect( add_query_arg( 'give-message', 'api-key-regenerated', 'edit.php?post_type=give_forms&page=give-settings&tab=api' ) );
1686
				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...
1687
				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...
1688
			case 'revoke':
1689
				$this->revoke_api_key( $user_id );
1690
				delete_transient( 'give-total-api-keys' );
1691
				wp_redirect( add_query_arg( 'give-message', 'api-key-revoked', 'edit.php?post_type=give_forms&page=give-settings&tab=api' ) );
1692
				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...
1693
				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...
1694
			default;
1695
				break;
1696
		}
1697
	}
1698
1699
	/**
1700
	 * Generate new API keys for a user
1701
	 *
1702
	 * @access public
1703
	 * @since  1.1
1704
	 *
1705
	 * @param int $user_id User ID the key is being generated for
1706
	 * @param boolean $regenerate Regenerate the key for the user
1707
	 *
1708
	 * @return boolean True if (re)generated succesfully, false otherwise.
1709
	 */
1710
	public function generate_api_key( $user_id = 0, $regenerate = false ) {
1711
1712
		if ( empty( $user_id ) ) {
1713
			return false;
1714
		}
1715
1716
		$user = get_userdata( $user_id );
1717
1718
		if ( ! $user ) {
1719
			return false;
1720
		}
1721
1722
		$public_key = $this->get_user_public_key( $user_id );
1723
		$secret_key = $this->get_user_secret_key( $user_id );
1724
1725
		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...
1726
			$new_public_key = $this->generate_public_key( $user->user_email );
1727
			$new_secret_key = $this->generate_private_key( $user->ID );
1728
		} else {
1729
			return false;
1730
		}
1731
1732
		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...
1733
			$this->revoke_api_key( $user->ID );
1734
		}
1735
1736
		update_user_meta( $user_id, $new_public_key, 'give_user_public_key' );
1737
		update_user_meta( $user_id, $new_secret_key, 'give_user_secret_key' );
1738
1739
		return true;
1740
	}
1741
1742
	/**
1743
	 * Revoke a users API keys
1744
	 *
1745
	 * @access public
1746
	 * @since  1.1
1747
	 *
1748
	 * @param int $user_id User ID of user to revoke key for
1749
	 *
1750
	 * @return string
1751
	 */
1752
	public function revoke_api_key( $user_id = 0 ) {
1753
1754
		if ( empty( $user_id ) ) {
1755
			return false;
1756
		}
1757
1758
		$user = get_userdata( $user_id );
1759
1760
		if ( ! $user ) {
1761
			return false;
1762
		}
1763
1764
		$public_key = $this->get_user_public_key( $user_id );
1765
		$secret_key = $this->get_user_secret_key( $user_id );
1766
		if ( ! empty( $public_key ) ) {
1767
			delete_transient( md5( 'give_api_user_' . $public_key ) );
1768
			delete_transient( md5( 'give_api_user_public_key' . $user_id ) );
1769
			delete_transient( md5( 'give_api_user_secret_key' . $user_id ) );
1770
			delete_user_meta( $user_id, $public_key );
1771
			delete_user_meta( $user_id, $secret_key );
1772
		} else {
1773
			return false;
1774
		}
1775
1776
		return true;
1777
	}
1778
1779 2
	public function get_version() {
1780 2
		return self::VERSION;
1781
	}
1782
1783
1784
	/**
1785
	 * Generate and Save API key
1786
	 *
1787
	 * Generates the key requested by user_key_field and stores it in the database
1788
	 *
1789
	 * @access public
1790
	 * @since  1.1
1791
	 *
1792
	 * @param int $user_id
1793
	 *
1794
	 * @return void
1795
	 */
1796 2
	public function update_key( $user_id ) {
1797 2
		if ( current_user_can( 'edit_user', $user_id ) && isset( $_POST['give_set_api_key'] ) ) {
1798
1799 2
			$user = get_userdata( $user_id );
1800
1801 2
			$public_key = $this->get_user_public_key( $user_id );
1802 2
			$secret_key = $this->get_user_secret_key( $user_id );
1803
1804 2
			if ( empty( $public_key ) ) {
1805 2
				$new_public_key = $this->generate_public_key( $user->user_email );
1806 2
				$new_secret_key = $this->generate_private_key( $user->ID );
1807
1808 2
				update_user_meta( $user_id, $new_public_key, 'give_user_public_key' );
1809 2
				update_user_meta( $user_id, $new_secret_key, 'give_user_secret_key' );
1810 2
			} else {
1811
				$this->revoke_api_key( $user_id );
1812
			}
1813 2
		}
1814 2
	}
1815
1816
	/**
1817
	 * Generate the public key for a user
1818
	 *
1819
	 * @access private
1820
	 * @since  1.1
1821
	 *
1822
	 * @param string $user_email
1823
	 *
1824
	 * @return string
1825
	 */
1826 2
	private function generate_public_key( $user_email = '' ) {
1827 2
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
1828 2
		$public   = hash( 'md5', $user_email . $auth_key . date( 'U' ) );
1829
1830 2
		return $public;
1831
	}
1832
1833
	/**
1834
	 * Generate the secret key for a user
1835
	 *
1836
	 * @access private
1837
	 * @since  1.1
1838
	 *
1839
	 * @param int $user_id
1840
	 *
1841
	 * @return string
1842
	 */
1843 2
	private function generate_private_key( $user_id = 0 ) {
1844 2
		$auth_key = defined( 'AUTH_KEY' ) ? AUTH_KEY : '';
1845 2
		$secret   = hash( 'md5', $user_id . $auth_key . date( 'U' ) );
1846
1847 2
		return $secret;
1848
	}
1849
1850
	/**
1851
	 * Retrieve the user's token
1852
	 *
1853
	 * @access private
1854
	 * @since  1.1
1855
	 *
1856
	 * @param int $user_id
1857
	 *
1858
	 * @return string
1859
	 */
1860
	public function get_token( $user_id = 0 ) {
1861
		return hash( 'md5', $this->get_user_secret_key( $user_id ) . $this->get_user_public_key( $user_id ) );
1862
	}
1863
1864
	/**
1865
	 * Generate the default sales stats returned by the 'stats' endpoint
1866
	 *
1867
	 * @access private
1868
	 * @since  1.1
1869
	 * @return array default sales statistics
1870
	 */
1871
	private function get_default_sales_stats() {
1872
1873
		// Default sales return
1874
		$sales                               = array();
1875
		$sales['donations']['today']         = $this->stats->get_sales( 0, 'today' );
1876
		$sales['donations']['current_month'] = $this->stats->get_sales( 0, 'this_month' );
1877
		$sales['donations']['last_month']    = $this->stats->get_sales( 0, 'last_month' );
1878
		$sales['donations']['totals']        = give_get_total_sales();
1879
1880
		return $sales;
1881
	}
1882
1883
	/**
1884
	 * Generate the default earnings stats returned by the 'stats' endpoint
1885
	 *
1886
	 * @access private
1887
	 * @since  1.1
1888
	 * @return array default earnings statistics
1889
	 */
1890
	private function get_default_earnings_stats() {
1891
1892
		// Default earnings return
1893
		$earnings                              = array();
1894
		$earnings['earnings']['today']         = $this->stats->get_earnings( 0, 'today' );
1895
		$earnings['earnings']['current_month'] = $this->stats->get_earnings( 0, 'this_month' );
1896
		$earnings['earnings']['last_month']    = $this->stats->get_earnings( 0, 'last_month' );
1897
		$earnings['earnings']['totals']        = give_get_total_earnings();
1898
1899
		return $earnings;
1900
	}
1901
1902
	/**
1903
	 * API Key Backwards Compatibility
1904
	 *
1905
	 * @description A Backwards Compatibility call for the change of meta_key/value for users API Keys
1906
	 *
1907
	 * @since  1.3.6
1908
	 *
1909
	 * @param  string $check     Whether to check the cache or not
1910
	 * @param  int    $object_id The User ID being passed
1911
	 * @param  string $meta_key  The user meta key
1912
	 * @param  bool   $single    If it should return a single value or array
1913
	 *
1914
	 * @return string            The API key/secret for the user supplied
1915
	 */
1916 56
	public function api_key_backwards_compat( $check, $object_id, $meta_key, $single ) {
1917
1918 56
		if ( $meta_key !== 'give_user_public_key' && $meta_key !== 'give_user_secret_key' ) {
1919 56
			return $check;
1920
		}
1921
1922
		$return = $check;
1923
1924
		switch ( $meta_key ) {
1925
			case 'give_user_public_key':
1926
				$return = Give()->api->get_user_public_key( $object_id );
1927
				break;
1928
			case 'give_user_secret_key':
1929
				$return = Give()->api->get_user_secret_key( $object_id );
1930
				break;
1931
		}
1932
1933
		if ( ! $single ) {
1934
			$return = array( $return );
1935
		}
1936
1937
		return $return;
1938
1939
	}
1940
1941
}
1942