Issues (850)

Security Analysis    4 potential vulnerabilities

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

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

api/class-getpaid-rest-date-based-controller.php (2 issues)

Labels
Severity
1
<?php
2
/**
3
 * Helper for the date based controllers.
4
 *
5
 * @package GetPaid
6
 * @subpackage REST API
7
 * @since   2.0.0
8
 */
9
10
defined( 'ABSPATH' ) || exit;
11
12
/**
13
 * GetPaid REST date based controller class.
14
 *
15
 * @package Invoicing
16
 */
17
class GetPaid_REST_Date_Based_Controller extends GetPaid_REST_Controller {
18
19
	/**
20
	 * Group response items by day or month.
21
	 *
22
	 * @var string
23
	 */
24
	public $groupby = 'day';
25
26
	/**
27
	 * Returns an array with arguments to request the previous report.
28
	 *
29
	 * @var array
30
	 */
31
	public $previous_range = array();
32
33
	/**
34
	 * The period interval.
35
	 *
36
	 * @var int
37
	 */
38
	public $interval;
39
40
	/**
41
	 * Retrieves the before and after dates.
42
	 *
43
	 * @param WP_REST_Request $request Request object.
44
	 * @return array The appropriate date range.
45
	 */
46
	public function get_date_range( $request ) {
47
48
		// Check if the period is x_days.
49
		if ( preg_match( '/^(\d+)_days$/', $request['period'], $matches ) ) {
0 ignored issues
show
It seems like $request['period'] can also be of type null; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

49
		if ( preg_match( '/^(\d+)_days$/', /** @scrutinizer ignore-type */ $request['period'], $matches ) ) {
Loading history...
50
			$date_range = $this->get_x_days_date_range( absint( $matches[1] ) );
51
		} elseif ( is_callable( array( $this, 'get_' . $request['period'] . '_date_range' ) ) ) {
52
			$date_range = call_user_func( array( $this, 'get_' . $request['period'] . '_date_range' ), $request );
53
		} else {
54
			$request['period'] = '7_days';
55
			$date_range        = $this->get_x_days_date_range();
56
		}
57
58
		// 3 months max for day view.
59
		$before = strtotime( $date_range['before'] );
60
		$after  = strtotime( $date_range['after'] );
61
		if ( floor( ( $before - $after ) / MONTH_IN_SECONDS ) > 2 ) {
62
			$this->groupby = 'month';
63
		}
64
65
		$this->prepare_interval( $date_range );
66
67
		return $date_range;
68
69
	}
70
71
	/**
72
	 * Groups by month or days.
73
	 *
74
	 * @param array $range Date range.
75
	 * @return array The appropriate date range.
76
	 */
77
	public function prepare_interval( $range ) {
78
79
		$before = strtotime( $range['before'] );
80
		$after  = strtotime( $range['after'] );
81
		if ( 'day' === $this->groupby ) {
82
			$difference     = max( DAY_IN_SECONDS, ( DAY_IN_SECONDS + $before - $after ) ); // Prevent division by 0;
83
			$this->interval = absint( ceil( max( 1, $difference / DAY_IN_SECONDS ) ) );
84
			return;
85
		}
86
87
		$this->interval = 0;
88
		$min_date       = strtotime( gmdate( 'Y-m-01', $after ) );
89
90
		while ( $min_date <= $before ) {
91
			$this->interval ++;
92
			$min_date = strtotime( '+1 MONTH', $min_date );
93
		}
94
95
		$this->interval = max( 1, $this->interval );
96
97
	}
98
99
	/**
100
	 * Retrieves a custom date range.
101
	 *
102
	 * @param WP_REST_Request $request Request object.
103
	 * @return array The appropriate date range.
104
	 */
105
	public function get_custom_date_range( $request ) {
106
107
		$after  = max( strtotime( '-20 years' ), strtotime( sanitize_text_field( $request['after'] ) ) );
108
		$before = gmdate( 'Y-m-d' );
109
		
110
		if ( ! empty( $request['before'] ) ) {
111
			$before  = min( strtotime( $before ), strtotime( sanitize_text_field( $request['before'] ) ) );
112
		}
113
114
		// Set the previous date range.
115
		$difference           = $before - $after;
116
		$this->previous_range = array(
117
			'period' => 'custom',
118
			'before' => gmdate( 'Y-m-d', $before - $difference - DAY_IN_SECONDS ),
119
			'after'  => gmdate( 'Y-m-d', $after - $difference - DAY_IN_SECONDS ),
120
		);
121
122
		// Generate the report.
123
		return array(
124
			'before' => gmdate( 'Y-m-d', $before ),
0 ignored issues
show
It seems like $before can also be of type string; however, parameter $timestamp of gmdate() does only seem to accept integer|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

124
			'before' => gmdate( 'Y-m-d', /** @scrutinizer ignore-type */ $before ),
Loading history...
125
			'after'  => gmdate( 'Y-m-d', $after ),
126
		);
127
128
	}
129
130
	/**
131
	 * Retrieves todays date range.
132
	 *
133
	 * @return array The appropriate date range.
134
	 */
135
	public function get_today_date_range() {
136
137
		// Set the previous date range.
138
		$this->previous_range = array(
139
			'period' => 'yesterday',
140
		);
141
142
		// Generate the report.
143
		return array(
144
			'before' => gmdate( 'Y-m-d' ),
145
			'after'  => gmdate( 'Y-m-d' ),
146
		);
147
148
	}
149
150
	/**
151
	 * Retrieves yesterdays date range.
152
	 *
153
	 * @return array The appropriate date range.
154
	 */
155
	public function get_yesterday_date_range() {
156
157
		// Set the previous date range.
158
		$this->previous_range = array(
159
			'period' => 'custom',
160
			'before' => gmdate( 'Y-m-d', strtotime( '-2 days' ) ),
161
			'after'  => gmdate( 'Y-m-d', strtotime( '-2 days' ) ),
162
		);
163
164
		// Generate the report.
165
		return array(
166
			'before' => gmdate( 'Y-m-d', strtotime( '-1 day' ) ),
167
			'after'  => gmdate( 'Y-m-d', strtotime( '-1 day' ) ),
168
		);
169
170
	}
171
172
	/**
173
	 * Retrieves this week's date range.
174
	 *
175
	 * @return array The appropriate date range.
176
	 */
177
	public function get_week_date_range() {
178
179
		// Set the previous date range.
180
		$this->previous_range = array(
181
			'period' => 'last_week',
182
		);
183
184
		// Generate the report.
185
		$week_starts = absint( get_option( 'start_of_week' ) );
186
		return array(
187
			'before' => gmdate( 'Y-m-d' ),
188
			'after'  => gmdate( 'Y-m-d', strtotime( 'next Sunday -' . ( 7 - $week_starts ) . ' days' ) ),
189
		);
190
	}
191
192
	/**
193
	 * Retrieves last week's date range.
194
	 *
195
	 * @return array The appropriate date range.
196
	 */
197
	public function get_last_week_date_range() {
198
199
		$week_starts = absint( get_option( 'start_of_week' ) );
200
		$week_starts = strtotime( 'last Sunday -' . ( 7 - $week_starts ) . ' days' );
201
		$date_range  = array(
202
			'before' => gmdate( 'Y-m-d', $week_starts + 6 * DAY_IN_SECONDS ),
203
			'after'  => gmdate( 'Y-m-d', $week_starts ),
204
		);
205
206
		// Set the previous date range.
207
		$week_starts          = $week_starts - 7 * DAY_IN_SECONDS;
208
		$this->previous_range = array(
209
			'period' => 'custom',
210
			'before' => gmdate( 'Y-m-d', $week_starts + 6 * DAY_IN_SECONDS ),
211
			'after'  => gmdate( 'Y-m-d', $week_starts ),
212
		);
213
214
		// Generate the report.
215
		return $date_range;
216
	}
217
218
	/**
219
	 * Retrieves last x days date range.
220
	 *
221
	 * @return array The appropriate date range.
222
	 */
223
	public function get_x_days_date_range( $days = 7 ) {
224
225
		$days--;
226
227
		$date_range  = array(
228
			'before' => gmdate( 'Y-m-d' ),
229
			'after'  => gmdate( 'Y-m-d', strtotime( "-$days days" ) ),
230
		);
231
232
		$days++;
233
234
		// Set the previous date range.
235
		$this->previous_range = array(
236
			'period' => 'custom',
237
			'before' => gmdate( 'Y-m-d', strtotime( $date_range['before'] ) - $days * DAY_IN_SECONDS ),
238
			'after'  => gmdate( 'Y-m-d', strtotime( $date_range['after'] ) - $days * DAY_IN_SECONDS ),
239
		);
240
241
		// Generate the report.
242
		return $date_range;
243
	}
244
245
	/**
246
	 * Retrieves this month date range.
247
	 *
248
	 * @return array The appropriate date range.
249
	 */
250
	public function get_month_date_range() {
251
252
		// Set the previous date range.
253
		$this->previous_range = array(
254
			'period' => 'last_month',
255
		);
256
257
		// Generate the report.
258
		return array(
259
			'after'  => gmdate( 'Y-m-01' ),
260
			'before' => gmdate( 'Y-m-t' ),
261
		);
262
263
	}
264
265
	/**
266
	 * Retrieves last month's date range.
267
	 *
268
	 * @return array The appropriate date range.
269
	 */
270
	public function get_last_month_date_range() {
271
272
		// Set the previous date range.
273
		$this->previous_range = array(
274
			'period' => 'custom',
275
			'after'  => gmdate( 'Y-m-01', strtotime( '-2 months' ) ),
276
			'before' => gmdate( 'Y-m-t', strtotime( '-2 months' ) ),
277
		);
278
279
		// Generate the report.
280
		return array(
281
			'after'  => gmdate( 'Y-m-01', strtotime( 'last month' ) ),
282
			'before' => gmdate( 'Y-m-t', strtotime( 'last month' ) ),
283
		);
284
285
	}
286
287
	/**
288
	 * Retrieves this quarter date range.
289
	 *
290
	 * @return array The available quarters.
291
	 */
292
	public function get_quarters() {
293
294
		$year      = (int) gmdate( 'Y' );
295
		$last_year = (int) $year - 1;
296
		return array(
297
298
			// Third quarter of previous year: July 1st to September 30th
299
			array(
300
				'before' => "{$last_year}-09-30",
301
				'after'  => "{$last_year}-07-01",
302
			),
303
304
			// Last quarter of previous year: October 1st to December 31st
305
			array(
306
				'before' => "{$last_year}-12-31",
307
        		'after'  => "{$last_year}-10-01",
308
			),
309
310
			// First quarter: January 1st to March 31st
311
			array(
312
				'before' => "{$year}-03-31",
313
				'after'  => "{$year}-01-01",
314
			),
315
316
			// Second quarter: April 1st to June 30th
317
			array(
318
				'before' => "{$year}-06-30",
319
				'after'  => "{$year}-04-01",
320
			),
321
322
			// Third quarter: July 1st to September 30th
323
			array(
324
				'before' => "{$year}-09-30",
325
				'after'  => "{$year}-07-01",
326
			),
327
328
			// Fourth quarter: October 1st to December 31st
329
			array(
330
				'before' => "{$year}-12-31",
331
				'after'  => "{$year}-10-01",
332
			),
333
		);
334
	}
335
336
	/**
337
	 * Retrieves the current quater.
338
	 *
339
	 * @return int The current quarter.
340
	 */
341
	public function get_quarter() {
342
343
		$month    = (int) gmdate( 'n' );
344
		$quarters = array( 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4 );
345
		return $quarters[ $month - 1 ];
346
347
	}
348
349
	/**
350
	 * Retrieves this quarter date range.
351
	 *
352
	 * @return array The appropriate date range.
353
	 */
354
	public function get_quarter_date_range() {
355
356
		// Set the previous date range.
357
		$this->previous_range = array(
358
			'period' => 'last_quarter',
359
		);
360
361
		// Generate the report.
362
		$quarter  = $this->get_quarter();
363
		$quarters = $this->get_quarters();
364
		return $quarters[ $quarter + 1 ];
365
366
	}
367
368
	/**
369
	 * Retrieves last quarter's date range.
370
	 *
371
	 * @return array The appropriate date range.
372
	 */
373
	public function get_last_quarter_date_range() {
374
375
		$quarters = $this->get_quarters();
376
		$quarter  = $this->get_quarter();
377
378
		// Set the previous date range.
379
		$this->previous_range = array_merge(
380
			$quarters[ $quarter - 1 ],
381
			array( 'period' => 'custom' )
382
		);
383
384
		// Generate the report.
385
		return $quarters[ $quarter ];
386
387
	}
388
389
	/**
390
	 * Retrieves this year date range.
391
	 *
392
	 * @return array The appropriate date range.
393
	 */
394
	public function get_year_date_range() {
395
396
		// Set the previous date range.
397
		$this->previous_range = array(
398
			'period' => 'last_year',
399
		);
400
401
		// Generate the report.
402
		return array(
403
			'after'  => gmdate( 'Y-01-01' ),
404
			'before' => gmdate( 'Y-12-31' ),
405
		);
406
407
	}
408
409
	/**
410
	 * Retrieves last year date range.
411
	 *
412
	 * @return array The appropriate date range.
413
	 */
414
	public function get_last_year_date_range() {
415
416
		// Set the previous date range.
417
		$this->previous_range = array(
418
			'period' => 'custom',
419
			'after'  => gmdate( 'Y-01-01', strtotime( '-2 years' ) ),
420
			'before' => gmdate( 'Y-12-31', strtotime( '-2 years' ) ),
421
		);
422
423
		// Generate the report.
424
		return array(
425
			'after'  => gmdate( 'Y-01-01', strtotime( 'last year' ) ),
426
			'before' => gmdate( 'Y-12-31', strtotime( 'last year' ) ),
427
		);
428
429
	}
430
431
	/**
432
	 * Prepare a the request date for SQL usage.
433
	 *
434
	 * @param WP_REST_Request $request Request object.
435
	 * @param string $date_field The date field.
436
	 * @return string The appropriate SQL.
437
	 */
438
	public function get_date_range_sql( $request, $date_field ) {
439
		global $wpdb;
440
441
		$sql = '1=1';
442
		$range = $this->get_date_range( $request );
443
444
		if ( ! empty( $range['after'] ) ) {
445
			$sql .= ' AND ' . $wpdb->prepare(
446
				"$date_field >= %s",
447
				$range['after']
448
			);
449
		}
450
451
		if ( ! empty( $range['before'] ) ) {
452
			$sql .= ' AND ' . $wpdb->prepare(
453
				"$date_field <= %s",
454
				$range['before']
455
			);
456
		}
457
458
		return $sql;
459
460
	}
461
462
	/**
463
	 * Prepares a group by query.
464
	 *
465
	 * @param string $date_field The date field.
466
	 * @return string The appropriate SQL.
467
	 */
468
	public function get_group_by_sql( $date_field ) {
469
470
		if ( 'day' === $this->groupby ) {
471
			return "YEAR($date_field), MONTH($date_field), DAY($date_field)";
472
		}
473
474
		return "YEAR($date_field), MONTH($date_field)";
475
	}
476
477
	/**
478
	 * Get the query params for collections.
479
	 *
480
	 * @return array
481
	 */
482
	public function get_collection_params() {
483
		return array(
484
			'context' => $this->get_context_param( array( 'default' => 'view' ) ),
485
			'period'  => array(
486
				'description'       => __( 'Limit to results of a specific period.', 'invoicing' ),
487
				'type'              => 'string',
488
				'enum'              => array( 'custom', 'today', 'yesterday', 'week', 'last_week', '7_days', '30_days', '60_days', '90_days', '180_days', 'month', 'last_month', 'quarter', 'last_quarter', 'year', 'last_year', 'quarter', 'last_quarter' ),
489
				'validate_callback' => 'rest_validate_request_arg',
490
				'sanitize_callback' => 'sanitize_text_field',
491
				'default'           => '7_days',
492
			),
493
			'after'   => array(
494
				/* translators: %s: date format */
495
				'description'       => sprintf( __( 'Limit to results after a specific date, the date needs to be in the %s format.', 'invoicing' ), 'YYYY-MM-DD' ),
496
				'type'              => 'string',
497
				'validate_callback' => 'rest_validate_request_arg',
498
				'sanitize_callback' => 'sanitize_text_field',
499
				'default'           => gmdate( 'Y-m-d', strtotime( '-7 days' ) ),
500
			),
501
			'before'  => array(
502
				/* translators: %s: date format */
503
				'description'       => sprintf( __( 'Limit to results before a specific date, the date needs to be in the %s format.', 'invoicing' ), 'YYYY-MM-DD' ),
504
				'type'              => 'string',
505
				'validate_callback' => 'rest_validate_request_arg',
506
				'sanitize_callback' => 'sanitize_text_field',
507
				'default'           => gmdate( 'Y-m-d' ),
508
			),
509
		);
510
	}
511
}
512