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.

includes/wpinv-helper-functions.php (13 issues)

Code
1
<?php
2
/**
3
 * Contains helper functions.
4
 *
5
 * @since 1.0.0
6
 * @package Invoicing
7
 */
8
9
defined( 'ABSPATH' ) || exit;
10
11
/**
12
 * Are we supporting item quantities?
13
 */
14
function wpinv_item_quantities_enabled() {
15
    return true;
16
}
17
18
/**
19
 * Returns the user's ip address.
20
 */
21
function wpinv_get_ip() {
22
23
    if ( isset( $_SERVER['HTTP_X_REAL_IP'] ) ) {
24
        return sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_REAL_IP'] ) );
0 ignored issues
show
It seems like wp_unslash($_SERVER['HTTP_X_REAL_IP']) can also be of type array; however, parameter $str of sanitize_text_field() 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

24
        return sanitize_text_field( /** @scrutinizer ignore-type */ wp_unslash( $_SERVER['HTTP_X_REAL_IP'] ) );
Loading history...
25
    }
26
27
    if ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
28
        // Proxy servers can send through this header like this: X-Forwarded-For: client1, proxy1, proxy2
29
        // Make sure we always only send through the first IP in the list which should always be the client IP.
30
        return (string) rest_is_ip_address( trim( current( preg_split( '/,/', sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) ) ) ) );
31
    }
32
33
    if ( isset( $_SERVER['HTTP_CLIENT_IP'] ) ) {
34
        return sanitize_text_field( wp_unslash( $_SERVER['HTTP_CLIENT_IP'] ) );
35
    }
36
37
    if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
38
        return sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
39
    }
40
41
    return '';
42
}
43
44
function wpinv_get_user_agent() {
45
    if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
46
        $user_agent = sanitize_text_field( $_SERVER['HTTP_USER_AGENT'] );
47
    } else {
48
        $user_agent = '';
49
    }
50
51
    return apply_filters( 'wpinv_get_user_agent', $user_agent );
52
}
53
54
/**
55
 * Standardizes an amount for insterting into the database.
56
 *
57
 * @param string $amount The amount to sanitize.
58
 * @return float
59
 */
60
function getpaid_standardize_amount( $amount ) {
61
62
    $amount = str_replace( wpinv_thousands_separator(), '', $amount );
63
    $amount = str_replace( wpinv_decimal_separator(), '.', $amount );
64
    if ( is_numeric( $amount ) ) {
65
        return floatval( $amount );
66
    }
67
68
    // Cast the remaining to a float.
69
    return wpinv_round_amount( preg_replace( '/[^0-9\.\-]/', '', $amount ) );
0 ignored issues
show
preg_replace('/[^0-9\.\-]/', '', $amount) of type string is incompatible with the type double expected by parameter $amount of wpinv_round_amount(). ( Ignorable by Annotation )

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

69
    return wpinv_round_amount( /** @scrutinizer ignore-type */ preg_replace( '/[^0-9\.\-]/', '', $amount ) );
Loading history...
70
71
}
72
73
/**
74
 * Standardizes an amount that has been retrieved from the database.
75
 *
76
 * @param string $amount The amount to sanitize.
77
 */
78
function getpaid_unstandardize_amount( $amount ) {
79
    return str_replace( '.', wpinv_decimal_separator(), $amount );
80
}
81
82
/**
83
 * Sanitizes an amount.
84
 *
85
 * @param string $amount The amount to sanitize.
86
 */
87
function wpinv_sanitize_amount( $amount ) {
88
89
    if ( is_numeric( $amount ) ) {
90
        return floatval( $amount );
91
    }
92
93
    // Separate the decimals and thousands.
94
    $amount    = empty( $amount ) ? '0' : (string) $amount;
95
    $amount    = explode( wpinv_decimal_separator(), $amount );
96
97
    // Remove thousands.
98
    $amount[0] = str_replace( wpinv_thousands_separator(), '', $amount[0] );
99
100
    // Convert back to string.
101
    $amount = count( $amount ) > 1 ? "{$amount[0]}.{$amount[1]}" : $amount[0];
102
103
    // Cast the remaining to a float.
104
    return (float) preg_replace( '/[^0-9\.\-]/', '', $amount );
105
106
}
107
108
/**
109
 * Rounds an amount.
110
 *
111
 * @param float $amount
112
 * @param float|string|int|null $decimals
113
 */
114
function wpinv_round_amount( $amount, $decimals = null, $use_sprintf = false ) {
115
116
    if ( $decimals === null ) {
117
        $decimals = wpinv_decimals();
118
    }
119
120
    if ( $use_sprintf ) {
121
        $amount = sprintf( "%.{$decimals}f", (float) $amount );
122
    } else {
123
        $amount = round( (float) $amount, absint( $decimals ) );
124
    }
125
126
    return apply_filters( 'wpinv_round_amount', $amount, $decimals );
127
}
128
129
/**
130
 * Get all invoice statuses.
131
 *
132
 * @since 1.0.19
133
 * @param bool $draft Whether or not to include the draft status.
134
 * @param bool $trashed Whether or not to include the trash status.
135
 * @param string|WPInv_Invoice $invoice The invoice object|post type|type
136
 * @return array
137
 */
138
function wpinv_get_invoice_statuses( $draft = false, $trashed = false, $invoice = false ) {
139
140
	$invoice_statuses = array(
141
		'wpi-pending'    => _x( 'Pending payment', 'Invoice status', 'invoicing' ),
142
        'publish'        => _x( 'Paid', 'Invoice status', 'invoicing' ),
143
        'wpi-processing' => _x( 'Processing', 'Invoice status', 'invoicing' ),
144
		'wpi-onhold'     => _x( 'On hold', 'Invoice status', 'invoicing' ),
145
		'wpi-cancelled'  => _x( 'Cancelled', 'Invoice status', 'invoicing' ),
146
		'wpi-refunded'   => _x( 'Refunded', 'Invoice status', 'invoicing' ),
147
        'wpi-failed'     => _x( 'Failed', 'Invoice status', 'invoicing' ),
148
        'wpi-renewal'    => _x( 'Renewal Payment', 'Invoice status', 'invoicing' ),
149
    );
150
151
    if ( $draft ) {
152
        $invoice_statuses['draft'] = __( 'Draft', 'invoicing' );
153
    }
154
155
    if ( $trashed ) {
156
        $invoice_statuses['trash'] = __( 'Trash', 'invoicing' );
157
    }
158
159
    if ( $invoice instanceof WPInv_Invoice ) {
160
        $invoice = $invoice->get_post_type();
161
    }
162
163
	return apply_filters( 'wpinv_statuses', $invoice_statuses, $invoice );
164
}
165
166
/**
167
 * Returns the formated invoice status.
168
 *
169
 * @param string $status The raw status
170
 * @param string|WPInv_Invoice $invoice The invoice object|post type|type
171
 */
172
function wpinv_status_nicename( $status, $invoice = false ) {
173
    $statuses = wpinv_get_invoice_statuses( true, true, $invoice );
0 ignored issues
show
It seems like $invoice can also be of type false; however, parameter $invoice of wpinv_get_invoice_statuses() does only seem to accept WPInv_Invoice|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

173
    $statuses = wpinv_get_invoice_statuses( true, true, /** @scrutinizer ignore-type */ $invoice );
Loading history...
174
    $status   = isset( $statuses[ $status ] ) ? $statuses[ $status ] : $status;
175
176
    return sanitize_text_field( $status );
177
}
178
179
/**
180
 * Retrieves the default currency code.
181
 *
182
 * @param string $current
183
 */
184
function wpinv_get_currency( $current = '' ) {
185
186
    if ( empty( $current ) ) {
187
        $current = apply_filters( 'wpinv_currency', wpinv_get_option( 'currency', 'USD' ) );
188
    }
189
190
    return trim( strtoupper( $current ) );
191
}
192
193
/**
194
 * Given a currency, it returns a currency symbol.
195
 *
196
 * @param string|null $currency The currency code. Defaults to the default currency.
197
 */
198
function wpinv_currency_symbol( $currency = null ) {
199
200
    // Prepare the currency.
201
    $currency = empty( $currency ) ? wpinv_get_currency() : wpinv_clean( $currency );
202
203
    // Fetch all symbols.
204
    $symbols = wpinv_get_currency_symbols();
205
206
    // Fetch this currencies symbol.
207
    $currency_symbol = isset( $symbols[ $currency ] ) ? $symbols[ $currency ] : $currency;
208
209
    // Filter the symbol.
210
    return apply_filters( 'wpinv_currency_symbol', $currency_symbol, $currency );
211
}
212
213
function wpinv_currency_position() {
214
    $position = wpinv_get_option( 'currency_position', 'left' );
215
216
    return apply_filters( 'wpinv_currency_position', $position );
217
}
218
219
/**
220
 * Returns the thousands separator for a currency.
221
 *
222
 * @param $string|null $current
0 ignored issues
show
Documentation Bug introduced by
The doc comment $string|null at position 0 could not be parsed: Unknown type name '$string' at position 0 in $string|null.
Loading history...
223
 */
224
function wpinv_thousands_separator( $current = null ) {
225
226
    if ( null == $current ) {
227
        $current = wpinv_get_option( 'thousands_separator', ',' );
228
    }
229
230
    return trim( $current );
231
}
232
233
/**
234
 * Returns the decimal separator for a currency.
235
 *
236
 * @param $string|null $current
0 ignored issues
show
Documentation Bug introduced by
The doc comment $string|null at position 0 could not be parsed: Unknown type name '$string' at position 0 in $string|null.
Loading history...
237
 */
238
function wpinv_decimal_separator( $current = null ) {
239
240
    if ( null == $current ) {
241
        $current = wpinv_get_option( 'decimal_separator', '.' );
242
    }
243
244
    return trim( $current );
245
}
246
247
/**
248
 * Returns the number of decimals to use.
249
 *
250
 * @param $string|null $current
0 ignored issues
show
Documentation Bug introduced by
The doc comment $string|null at position 0 could not be parsed: Unknown type name '$string' at position 0 in $string|null.
Loading history...
251
 */
252
function wpinv_decimals( $current = null ) {
253
254
    if ( null == $current ) {
255
        $current = wpinv_get_option( 'decimals', 2 );
256
    }
257
258
    return absint( $current );
259
}
260
261
/**
262
 * Retrieves a list of all supported currencies.
263
 */
264
function wpinv_get_currencies() {
265
    return apply_filters( 'wpinv_currencies', wpinv_get_data( 'currencies' ) );
266
}
267
268
/**
269
 * Retrieves a list of all currency symbols.
270
 */
271
function wpinv_get_currency_symbols() {
272
    return apply_filters( 'wpinv_currency_symbols', wpinv_get_data( 'currency-symbols' ) );
273
}
274
275
/**
276
 * Get the price format depending on the currency position.
277
 *
278
 * @return string
279
 */
280
function getpaid_get_price_format() {
281
	$currency_pos = wpinv_currency_position();
282
	$format       = '%1$s%2$s';
283
284
	switch ( $currency_pos ) {
285
		case 'left':
286
			$format = '%1$s%2$s';
287
			break;
288
		case 'right':
289
			$format = '%2$s%1$s';
290
			break;
291
		case 'left_space':
292
			$format = '%1$s&nbsp;%2$s';
293
			break;
294
		case 'right_space':
295
			$format = '%2$s&nbsp;%1$s';
296
			break;
297
	}
298
299
	return apply_filters( 'getpaid_price_format', $format, $currency_pos );
300
}
301
302
/**
303
 * Prints an amount with the correct format.
304
 *
305
 * @param  float  $amount Raw price.
306
 * @param  string $currency Currency.
307
 * @return string
308
 */
309
function wpinv_the_price( $amount = 0, $currency = '' ) {
310
    echo wp_kses_post( wpinv_price( $amount, $currency ) );
311
}
312
313
/**
314
 * Format the amount with a currency symbol.
315
 *
316
 * @param  float  $amount Raw price.
317
 * @param  string $currency Currency.
318
 * @return string
319
 */
320
function wpinv_price( $amount = 0, $currency = '' ) {
321
322
    // Backwards compatibility.
323
    $amount             = wpinv_sanitize_amount( $amount );
324
325
    // Prepare variables.
326
    $currency           = wpinv_get_currency( $currency );
327
    $amount             = (float) $amount;
328
    $unformatted_amount = $amount;
329
    $negative           = $amount < 0;
330
    $amount             = apply_filters( 'getpaid_raw_amount', floatval( $negative ? $amount * -1 : $amount ) );
331
    $amount             = wpinv_format_amount( $amount );
332
333
    // Format the amount.
334
    $format             = getpaid_get_price_format();
335
    $formatted_amount   = ( $negative ? '-' : '' ) . sprintf( $format, '<span class="getpaid-currency__symbol">' . wpinv_currency_symbol( $currency ) . '</span>', $amount );
336
337
    // Filter the formatting.
338
    return apply_filters( 'wpinv_price', $formatted_amount, $amount, $currency, $unformatted_amount );
339
}
340
341
/**
342
 * Format an amount with separators.
343
 *
344
 * @param  float    $amount Raw amount.
345
 * @param  null|int $decimals Number of decimals to use.
346
 * @param  bool     $calculate Whether or not to apply separators.
347
 * @return string
348
 */
349
function wpinv_format_amount( $amount, $decimals = null, $calculate = false ) {
350
    $thousands_sep = wpinv_thousands_separator();
351
    $decimal_sep   = wpinv_decimal_separator();
352
    $decimals      = wpinv_decimals( $decimals );
353
    $amount        = wpinv_sanitize_amount( $amount );
354
355
    if ( $calculate ) {
356
        return $amount;
357
    }
358
359
    // Fomart the amount.
360
    return number_format( $amount, $decimals, $decimal_sep, $thousands_sep );
361
}
362
363
function wpinv_sanitize_key( $key ) {
364
    $raw_key = $key;
365
    $key = preg_replace( '/[^a-zA-Z0-9_\-\.\:\/]/', '', $key );
366
367
    return apply_filters( 'wpinv_sanitize_key', $key, $raw_key );
368
}
369
370
/**
371
 * Returns a file extesion.
372
 *
373
 * @param $str the file whose extension should be retrieved.
374
 */
375
function wpinv_get_file_extension( $str ) {
376
    $filetype = wp_check_filetype( $str );
377
    return $filetype['ext'];
378
}
379
380
/**
381
 * Checks if a given string is an image URL.
382
 *
383
 * @param string $string
384
 */
385
function wpinv_string_is_image_url( $string ) {
386
    $extension = strtolower( wpinv_get_file_extension( $string ) );
387
    return in_array( $extension, array( 'jpeg', 'jpg', 'png', 'gif', 'ico' ), true );
388
}
389
390
/**
391
 * Returns the current URL.
392
 */
393
function wpinv_get_current_page_url() {
394
    return esc_url( add_query_arg( array() ) );
395
}
396
397
/**
398
 * Define a constant if it is not already defined.
399
 *
400
 * @since 1.0.19
401
 * @param string $name  Constant name.
402
 * @param mixed  $value Value.
403
 */
404
function getpaid_maybe_define_constant( $name, $value ) {
405
	if ( ! defined( $name ) ) {
406
		define( $name, $value );
407
	}
408
}
409
410
function wpinv_get_php_arg_separator_output() {
411
	return ini_get( 'arg_separator.output' );
412
}
413
414
function wpinv_rgb_from_hex( $color ) {
415
    $color = str_replace( '#', '', $color );
416
417
    // Convert shorthand colors to full format, e.g. "FFF" -> "FFFFFF"
418
    $color = preg_replace( '~^(.)(.)(.)$~', '$1$1$2$2$3$3', $color );
419
    if ( empty( $color ) ) {
420
        return null;
421
    }
422
423
    $color = str_split( $color );
424
425
    $rgb      = array();
426
    $rgb['R'] = hexdec( $color[0] . $color[1] );
427
    $rgb['G'] = hexdec( $color[2] . $color[3] );
428
    $rgb['B'] = hexdec( $color[4] . $color[5] );
429
430
    return $rgb;
431
}
432
433
function wpinv_hex_darker( $color, $factor = 30 ) {
434
    $base  = wpinv_rgb_from_hex( $color );
435
    $color = '#';
436
437
    foreach ( $base as $k => $v ) {
438
        $amount      = $v / 100;
439
        $amount      = round( $amount * $factor );
440
        $new_decimal = $v - $amount;
441
442
        $new_hex_component = dechex( $new_decimal );
0 ignored issues
show
$new_decimal of type double is incompatible with the type integer expected by parameter $num of dechex(). ( Ignorable by Annotation )

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

442
        $new_hex_component = dechex( /** @scrutinizer ignore-type */ $new_decimal );
Loading history...
443
        if ( strlen( $new_hex_component ) < 2 ) {
444
            $new_hex_component = '0' . $new_hex_component;
445
        }
446
        $color .= $new_hex_component;
447
    }
448
449
    return $color;
450
}
451
452
function wpinv_hex_lighter( $color, $factor = 30 ) {
453
    $base  = wpinv_rgb_from_hex( $color );
454
    $color = '#';
455
456
    foreach ( $base as $k => $v ) {
457
        $amount      = 255 - $v;
458
        $amount      = $amount / 100;
459
        $amount      = round( $amount * $factor );
460
        $new_decimal = $v + $amount;
461
462
        $new_hex_component = dechex( $new_decimal );
0 ignored issues
show
$new_decimal of type double is incompatible with the type integer expected by parameter $num of dechex(). ( Ignorable by Annotation )

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

462
        $new_hex_component = dechex( /** @scrutinizer ignore-type */ $new_decimal );
Loading history...
463
        if ( strlen( $new_hex_component ) < 2 ) {
464
            $new_hex_component = '0' . $new_hex_component;
465
        }
466
        $color .= $new_hex_component;
467
    }
468
469
    return $color;
470
}
471
472
function wpinv_light_or_dark( $color, $dark = '#000000', $light = '#FFFFFF' ) {
473
    $hex = str_replace( '#', '', $color );
474
475
    $c_r = hexdec( substr( $hex, 0, 2 ) );
476
    $c_g = hexdec( substr( $hex, 2, 2 ) );
477
    $c_b = hexdec( substr( $hex, 4, 2 ) );
478
479
    $brightness = ( ( $c_r * 299 ) + ( $c_g * 587 ) + ( $c_b * 114 ) ) / 1000;
480
481
    return $brightness > 155 ? $dark : $light;
482
}
483
484
function wpinv_format_hex( $hex ) {
485
    $hex = trim( str_replace( '#', '', $hex ) );
486
487
    if ( strlen( $hex ) == 3 ) {
488
        $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
489
    }
490
491
    return $hex ? '#' . $hex : null;
492
}
493
494
/**
495
 * Get truncated string with specified width.
496
 *
497
 * @since 1.0.0
498
 *
499
 * @param string $str The string being decoded.
500
 * @param int $start The start position offset. Number of characters from the beginning of string.
501
 *                      For negative value, number of characters from the end of the string.
502
 * @param int $width The width of the desired trim. Negative widths count from the end of the string.
503
 * @param string $trimmaker A string that is added to the end of string when string is truncated. Ex: "...".
504
 * @param string $encoding The encoding parameter is the character encoding. Default "UTF-8".
505
 * @return string
506
 */
507
function wpinv_utf8_strimwidth( $str, $start, $width, $trimmaker = '', $encoding = 'UTF-8' ) {
508
    if ( function_exists( 'mb_strimwidth' ) ) {
509
        return mb_strimwidth( $str, $start, $width, $trimmaker, $encoding );
510
    }
511
512
    return wpinv_utf8_substr( $str, $start, $width, $encoding ) . $trimmaker;
513
}
514
515
/**
516
 * Get the string length.
517
 *
518
 * @since 1.0.0
519
 *
520
 * @param string $str The string being checked for length.
521
 * @param string $encoding The encoding parameter is the character encoding. Default "UTF-8".
522
 * @return int Returns the number of characters in string.
523
 */
524
function wpinv_utf8_strlen( $str, $encoding = 'UTF-8' ) {
525
    if ( function_exists( 'mb_strlen' ) ) {
526
        return mb_strlen( $str, $encoding );
527
    }
528
529
    return strlen( $str );
530
}
531
532
function wpinv_utf8_strtolower( $str, $encoding = 'UTF-8' ) {
533
    if ( function_exists( 'mb_strtolower' ) ) {
534
        return mb_strtolower( $str, $encoding );
535
    }
536
537
    return strtolower( $str );
538
}
539
540
function wpinv_utf8_strtoupper( $str, $encoding = 'UTF-8' ) {
541
    if ( function_exists( 'mb_strtoupper' ) ) {
542
        return mb_strtoupper( $str, $encoding );
543
    }
544
545
    return strtoupper( $str );
546
}
547
548
/**
549
 * Find position of first occurrence of string in a string
550
 *
551
 * @since 1.0.0
552
 *
553
 * @param string $str The string being checked.
554
 * @param string $find The string to find in input string.
555
 * @param int $offset The search offset. Default "0". A negative offset counts from the end of the string.
556
 * @param string $encoding The encoding parameter is the character encoding. Default "UTF-8".
557
 * @return int Returns the position of the first occurrence of search in the string.
558
 */
559
function wpinv_utf8_strpos( $str, $find, $offset = 0, $encoding = 'UTF-8' ) {
560
    if ( function_exists( 'mb_strpos' ) ) {
561
        return mb_strpos( $str, $find, $offset, $encoding );
562
    }
563
564
    return strpos( $str, $find, $offset );
565
}
566
567
/**
568
 * Find position of last occurrence of a string in a string.
569
 *
570
 * @since 1.0.0
571
 *
572
 * @param string $str The string being checked, for the last occurrence of search.
573
 * @param string $find The string to find in input string.
574
 * @param int $offset Specifies begin searching an arbitrary number of characters into the string.
575
 * @param string $encoding The encoding parameter is the character encoding. Default "UTF-8".
576
 * @return int Returns the position of the last occurrence of search.
577
 */
578
function wpinv_utf8_strrpos( $str, $find, $offset = 0, $encoding = 'UTF-8' ) {
579
    if ( function_exists( 'mb_strrpos' ) ) {
580
        return mb_strrpos( $str, $find, $offset, $encoding );
581
    }
582
583
    return strrpos( $str, $find, $offset );
584
}
585
586
/**
587
 * Get the part of string.
588
 *
589
 * @since 1.0.0
590
 *
591
 * @param string $str The string to extract the substring from.
592
 * @param int $start If start is non-negative, the returned string will start at the entered position in string, counting from zero.
593
 *                      If start is negative, the returned string will start at the entered position from the end of string.
594
 * @param int|null $length Maximum number of characters to use from string.
595
 * @param string $encoding The encoding parameter is the character encoding. Default "UTF-8".
596
 * @return string
597
 */
598
function wpinv_utf8_substr( $str, $start, $length = null, $encoding = 'UTF-8' ) {
599
    if ( function_exists( 'mb_substr' ) ) {
600
        if ( null === $length ) {
601
            return mb_substr( $str, $start, wpinv_utf8_strlen( $str, $encoding ), $encoding );
602
        } else {
603
            return mb_substr( $str, $start, $length, $encoding );
604
        }
605
    }
606
607
    return substr( $str, $start, $length );
608
}
609
610
/**
611
 * Get the width of string.
612
 *
613
 * @since 1.0.0
614
 *
615
 * @param string $str The string being decoded.
616
 * @param string $encoding The encoding parameter is the character encoding. Default "UTF-8".
617
 * @return string The width of string.
618
 */
619
function wpinv_utf8_strwidth( $str, $encoding = 'UTF-8' ) {
620
    if ( function_exists( 'mb_strwidth' ) ) {
621
        return mb_strwidth( $str, $encoding );
622
    }
623
624
    return wpinv_utf8_strlen( $str, $encoding );
625
}
626
627
function wpinv_utf8_ucfirst( $str, $lower_str_end = false, $encoding = 'UTF-8' ) {
628
    if ( function_exists( 'mb_strlen' ) ) {
629
        $first_letter = wpinv_utf8_strtoupper( wpinv_utf8_substr( $str, 0, 1, $encoding ), $encoding );
630
        $str_end = '';
631
632
        if ( $lower_str_end ) {
633
            $str_end = wpinv_utf8_strtolower( wpinv_utf8_substr( $str, 1, wpinv_utf8_strlen( $str, $encoding ), $encoding ), $encoding );
634
        } else {
635
            $str_end = wpinv_utf8_substr( $str, 1, wpinv_utf8_strlen( $str, $encoding ), $encoding );
636
        }
637
638
        return $first_letter . $str_end;
639
    }
640
641
    return ucfirst( $str );
642
}
643
644
function wpinv_utf8_ucwords( $str, $encoding = 'UTF-8' ) {
645
    if ( function_exists( 'mb_convert_case' ) ) {
646
        return mb_convert_case( $str, MB_CASE_TITLE, $encoding );
647
    }
648
649
    return ucwords( $str );
650
}
651
652
function wpinv_period_in_days( $period, $unit ) {
653
    $period = absint( $period );
654
655
    if ( $period > 0 ) {
656
        if ( in_array( strtolower( $unit ), array( 'w', 'week', 'weeks' ) ) ) {
657
            $period = $period * 7;
658
        } elseif ( in_array( strtolower( $unit ), array( 'm', 'month', 'months' ) ) ) {
659
            $period = $period * 30;
660
        } elseif ( in_array( strtolower( $unit ), array( 'y', 'year', 'years' ) ) ) {
661
            $period = $period * 365;
662
        }
663
    }
664
665
    return $period;
666
}
667
668
function wpinv_cal_days_in_month( $calendar, $month, $year ) {
669
    if ( function_exists( 'cal_days_in_month' ) ) {
670
        return cal_days_in_month( $calendar, $month, $year );
671
    }
672
673
    // Fallback in case the calendar extension is not loaded in PHP
674
    // Only supports Gregorian calendar
675
    return gmdate( 't', mktime( 0, 0, 0, $month, 1, $year ) );
676
}
677
678
/**
679
 * Display a help tip for settings.
680
 *
681
 * @param  string $tip Help tip text
682
 * @param  bool $allow_html Allow sanitized HTML if true or escape
683
 *
684
 * @return string
685
 */
686
function wpi_help_tip( $tip, $allow_html = false, $is_vue = false, $echo = false ) {
687
688
    if ( $allow_html ) {
689
        $tip = wpi_sanitize_tooltip( $tip );
690
    } else {
691
        $tip = strip_tags( $tip );
692
    }
693
694
    if ( $is_vue ) {
695
696
        if ( $echo ) {
697
            echo '<span class="dashicons dashicons-editor-help" title="' . esc_attr( $tip ) . '"></span>';
698
        } else {
699
            return '<span class="dashicons dashicons-editor-help" title="' . esc_attr( $tip ) . '"></span>';
700
        }
701
}
702
703
    if ( $echo ) {
704
        echo '<span class="wpi-help-tip dashicons dashicons-editor-help" title="' . esc_attr( $tip ) . '"></span>';
705
    } else {
706
        return '<span class="wpi-help-tip dashicons dashicons-editor-help" title="' . esc_attr( $tip ) . '"></span>';
707
    }
708
}
709
710
/**
711
 * Sanitize a string destined to be a tooltip.
712
 *
713
 * Tooltips are encoded with htmlspecialchars to prevent XSS. Should not be used in conjunction with esc_attr()
714
 *
715
 * @param string $var
716
 * @return string
717
 */
718
function wpi_sanitize_tooltip( $var ) {
719
    return wp_kses(
720
        html_entity_decode( $var ),
721
        array(
722
			'br'     => array(),
723
			'em'     => array(),
724
			'strong' => array(),
725
			'b'      => array(),
726
			'small'  => array(),
727
			'span'   => array(),
728
			'ul'     => array(),
729
			'li'     => array(),
730
			'ol'     => array(),
731
			'p'      => array(),
732
        )
733
    );
734
}
735
736
/**
737
 * Get all WPI screen ids.
738
 *
739
 * @return array
740
 */
741
function wpinv_get_screen_ids() {
742
743
    $screen_id = sanitize_title( __( 'Invoicing', 'invoicing' ) );
744
745
    $screen_ids = array(
746
        'toplevel_page_' . $screen_id,
747
        'wpi_invoice',
748
        'wpi_item',
749
        'wpi_quote',
750
        'wpi_discount',
751
        'wpi_payment_form',
752
        'edit-wpi_invoice',
753
        'edit-wpi_item',
754
        'edit-wpi_discount',
755
        'edit-wpi_quote',
756
        'edit-wpi_payment_form',
757
        'getpaid_page_wpinv-settings',
758
        'getpaid_page_wpinv-subscriptions',
759
        'getpaid_page_wpinv-reports',
760
        'getpaid_page_wpi-addons',
761
        'getpaid_page_wpinv-customers',
762
        'gp-setup', // setup wizard
763
    );
764
765
    return apply_filters( 'wpinv_screen_ids', $screen_ids );
766
}
767
768
/**
769
 * Cleans up an array, comma- or space-separated list of scalar values.
770
 *
771
 * @since 1.0.13
772
 *
773
 * @param array|string $list List of values.
774
 * @return array Sanitized array of values.
775
 */
776
function wpinv_parse_list( $list ) {
777
778
    if ( empty( $list ) ) {
779
        $list = array();
780
    }
781
782
	if ( ! is_array( $list ) ) {
783
		return preg_split( '/[\s,]+/', $list, -1, PREG_SPLIT_NO_EMPTY );
784
	}
785
786
	return $list;
787
}
788
789
/**
790
 * Fetches data stored on disk.
791
 *
792
 * @since 1.0.14
793
 *
794
 * @param string $key Type of data to fetch.
795
 * @return mixed Fetched data.
796
 */
797
function wpinv_get_data( $key ) {
798
    return apply_filters( "wpinv_get_$key", include WPINV_PLUGIN_DIR . "includes/data/$key.php" );
799
}
800
801
/**
802
 * (Maybe) Adds an empty option to an array of options.
803
 *
804
 * @since 1.0.14
805
 *
806
 * @param array $options
807
 * @param bool $first_empty Whether or not the first item in the list should be empty
808
 * @return mixed Fetched data.
809
 */
810
function wpinv_maybe_add_empty_option( $options, $first_empty ) {
811
812
    if ( ! empty( $options ) && $first_empty ) {
813
        return array_merge( array( '' => '' ), $options );
814
    }
815
    return $options;
816
817
}
818
819
/**
820
 * Clean variables using sanitize_text_field.
821
 *
822
 * @param mixed $var Data to sanitize.
823
 * @return string|array
824
 */
825
function wpinv_clean( $var ) {
826
827
	if ( is_array( $var ) ) {
828
		return array_map( 'wpinv_clean', $var );
829
    }
830
831
    if ( is_object( $var ) ) {
832
		$object_vars = get_object_vars( $var );
833
		foreach ( $object_vars as $property_name => $property_value ) {
834
			$var->$property_name = wpinv_clean( $property_value );
835
        }
836
        return $var;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $var returns the type object which is incompatible with the documented return type array|string.
Loading history...
837
	}
838
839
    return is_string( $var ) ? sanitize_text_field( stripslashes( $var ) ) : $var;
840
}
841
842
/**
843
 * Converts a price string into an options array.
844
 *
845
 * @param string $str Data to convert.
846
 * @return string|array
847
 */
848
function getpaid_convert_price_string_to_options( $str ) {
849
850
	$raw_options = array_map( 'trim', explode( ',', $str ) );
851
    $options     = array();
852
853
    foreach ( $raw_options as $option ) {
854
855
        if ( '' == $option ) {
856
            continue;
857
        }
858
859
        $option = array_map( 'trim', explode( '|', $option ) );
860
861
        $price = null;
862
        $label = null;
863
864
        if ( isset( $option[0] ) && '' != $option[0] ) {
865
            $label  = $option[0];
866
        }
867
868
        if ( isset( $option[1] ) && '' != $option[1] ) {
869
            $price = $option[1];
870
        }
871
872
        if ( ! isset( $price ) ) {
873
            $price = $label;
874
        }
875
876
        if ( ! isset( $price ) || ! is_numeric( $price ) ) {
877
            continue;
878
        }
879
880
        if ( ! isset( $label ) ) {
881
            $label = $price;
882
        }
883
884
        $options[ "$label|$price" ] = $label;
885
    }
886
887
    return $options;
888
}
889
890
/**
891
 * Returns the help tip.
892
 */
893
function getpaid_get_help_tip( $tip, $additional_classes = '', $echo = false ) {
894
    $classes = 'wpi-help-tip dashicons dashicons-editor-help ' . $additional_classes;
895
    $tip     = esc_attr( $tip );
896
897
    if ( $echo ) {
898
        echo '<span class="' . esc_attr( $classes ) . '" data-tip="' . esc_attr( $tip ) . '"></span>';
899
    } else {
900
        return '<span class="' . esc_attr( $classes ) . '" data-tip="' . esc_attr( $tip ) . '"></span>';
901
    }
902
903
}
904
905
/**
906
 * Formats a date
907
 */
908
function getpaid_format_date( $date, $with_time = false ) {
909
910
    if ( empty( $date ) || $date == '0000-00-00 00:00:00' ) {
911
        return '';
912
    }
913
914
    $format = getpaid_date_format();
915
916
    if ( $with_time ) {
917
        $format .= ' ' . getpaid_time_format();
918
    }
919
    return date_i18n( $format, strtotime( $date ) );
920
921
}
922
923
/**
924
 * Formats a date into the website's date setting.
925
 *
926
 * @return string
927
 */
928
function getpaid_format_date_value( $date, $default = '&mdash;', $with_time = false ) {
929
    $date = getpaid_format_date( $date, $with_time );
930
    return empty( $date ) ? $default : $date;
931
}
932
933
/**
934
 * Get the date format used all over the plugin.
935
 *
936
 * @return string
937
 */
938
function getpaid_date_format() {
939
	return apply_filters( 'getpaid_date_format', get_option( 'date_format' ) );
0 ignored issues
show
Bug Best Practice introduced by
The expression return apply_filters('ge..._option('date_format')) could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
940
}
941
942
/**
943
 * Get the time format used all over the plugin.
944
 *
945
 * @return string
946
 */
947
function getpaid_time_format() {
948
	return apply_filters( 'getpaid_time_format', get_option( 'time_format' ) );
0 ignored issues
show
Bug Best Practice introduced by
The expression return apply_filters('ge..._option('time_format')) could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
949
}
950
951
/**
952
 * Limit length of a string.
953
 *
954
 * @param  string  $string string to limit.
955
 * @param  integer $limit Limit size in characters.
956
 * @return string
957
 */
958
function getpaid_limit_length( $string, $limit ) {
959
    $str_limit = $limit - 3;
960
961
	if ( function_exists( 'mb_strimwidth' ) ) {
962
		if ( mb_strlen( $string ) > $limit ) {
963
			$string = mb_strimwidth( $string, 0, $str_limit ) . '...';
964
		}
965
	} else {
966
		if ( strlen( $string ) > $limit ) {
967
			$string = substr( $string, 0, $str_limit ) . '...';
968
		}
969
	}
970
    return $string;
971
972
}
973
974
/**
975
 * Returns the REST API handler.
976
 *
977
 * @return WPInv_API
978
 * @since 1.0.19
979
 */
980
function getpaid_api() {
981
    return getpaid()->get( 'api' );
982
}
983
984
/**
985
 * Returns the post types object.
986
 *
987
 * @return GetPaid_Post_Types
988
 * @since 1.0.19
989
 */
990
function getpaid_post_types() {
991
    return getpaid()->get( 'post_types' );
992
}
993
994
/**
995
 * Returns the session handler.
996
 *
997
 * @return WPInv_Session_Handler
998
 * @since 1.0.19
999
 */
1000
function getpaid_session() {
1001
    return getpaid()->get( 'session' );
1002
}
1003
1004
/**
1005
 * Returns the notes handler.
1006
 *
1007
 * @return WPInv_Notes
1008
 * @since 1.0.19
1009
 */
1010
function getpaid_notes() {
1011
    return getpaid()->get( 'notes' );
1012
}
1013
1014
/**
1015
 * Returns the main admin class.
1016
 *
1017
 * @return GetPaid_Admin
1018
 */
1019
function getpaid_admin() {
1020
    return getpaid()->get( 'admin' );
1021
}
1022
1023
/**
1024
 * Retrieves a URL to an authenticated action
1025
 *
1026
 * @param string $action
1027
 * @param string $base the base url
1028
 * @return string
1029
 */
1030
function getpaid_get_authenticated_action_url( $action, $base = false ) {
1031
    return wp_nonce_url( add_query_arg( 'getpaid-action', $action, $base ), 'getpaid-nonce', 'getpaid-nonce' );
1032
}
1033
1034
/**
1035
 * Returns a post type label.
1036
 *
1037
 * @return string
1038
 */
1039
function getpaid_get_post_type_label( $post_type, $plural = true ) {
1040
1041
    $post_type = get_post_type_object( $post_type );
1042
1043
    if ( ! is_object( $post_type ) ) {
1044
        return null;
1045
    }
1046
1047
    return $plural ? $post_type->labels->name : $post_type->labels->singular_name;
1048
1049
}
1050
1051
/**
1052
 * Retrieves an array
1053
 *
1054
 * @return mixed|null
1055
 */
1056
function getpaid_get_array_field( $array, $key, $secondary_key = null ) {
1057
1058
    if ( ! is_array( $array ) ) {
1059
        return null;
1060
    }
1061
1062
    if ( ! empty( $secondary_key ) ) {
1063
        $array = isset( $array[ $secondary_key ] ) ? $array[ $secondary_key ] : array();
1064
        return getpaid_get_array_field( $array, $key );
0 ignored issues
show
Are you sure the usage of getpaid_get_array_field($array, $key) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1065
    }
1066
1067
    return isset( $array[ $key ] ) ? $array[ $key ] : null;
1068
1069
}
1070
1071
/**
1072
 * Merges an empty array
1073
 *
1074
 * @return array
1075
 */
1076
function getpaid_array_merge_if_empty( $args, $defaults ) {
1077
1078
    foreach ( $defaults as $key => $value ) {
1079
1080
        if ( empty( $args[ $key ] ) ) {
1081
            $args[ $key ] = $value;
1082
        }
1083
    }
1084
1085
    return $args;
1086
1087
}
1088
1089
/**
1090
 * Returns allowed file types.
1091
 *
1092
 * @return array
1093
 */
1094
function getpaid_get_allowed_mime_types() {
1095
1096
    $types = get_allowed_mime_types();
1097
1098
    if ( isset( $types['htm|html'] ) ) {
1099
		unset( $types['htm|html'] );
1100
	}
1101
1102
    if ( isset( $types['js'] ) ) {
1103
		unset( $types['js'] );
1104
	}
1105
1106
    return $types;
1107
1108
}
1109
1110
1111
function getpaid_user_delete_invoice( $data ) {
1112
1113
    // Ensure there is an invoice to delete.
1114
    if ( empty( $data['invoice_id'] ) ) {
1115
        return;
1116
    }
1117
1118
    $invoice = new WPInv_Invoice( (int) $data['invoice_id'] );
1119
1120
    // Ensure that it exists and that it belongs to the current user.
1121
    if ( ! $invoice->exists() || $invoice->get_customer_id() != get_current_user_id() ) {
1122
        $notice = 'perm_delete_invoice';
1123
1124
    // Can it be deleted?
1125
    } elseif ( ! $invoice->needs_payment() ) {
1126
        $notice = 'cannot_delete_invoice';
1127
1128
    // Delete it.
1129
    } else {
1130
1131
        $invoice->delete();
1132
        $notice = 'deleted_invoice';
1133
    }
1134
1135
    $redirect = add_query_arg(
1136
        array(
1137
            'wpinv-notice'   => $notice,
1138
            'getpaid-action' => false,
1139
            'getpaid-nonce'  => false,
1140
            'invoice_id'     => false,
1141
        )
1142
    );
1143
1144
    wp_safe_redirect( $redirect );
1145
    exit;
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1146
1147
}
1148
add_action( 'getpaid_authenticated_action_delete_invoice', 'getpaid_user_delete_invoice' );
1149
1150
/**
1151
 * Converts string to lower case.
1152
 *
1153
 * @since 2.8.23
1154
 *
1155
 * @param string $string String to convert.
1156
 * @param string $charset Character set to use for conversion.
1157
 * @return string Returns converted string.
1158
 */
1159
function getpaid_strtolower( $string, $charset = 'UTF-8' ) {
1160
	if ( function_exists( 'mb_convert_case' ) ) {
1161
		return mb_convert_case( $string, MB_CASE_LOWER, $charset );
1162
	} else {
1163
		return strtolower( $string );
1164
	}
1165
}
1166
1167
/**
1168
 * Converts string to upper case.
1169
 *
1170
 * @since 2.8.23
1171
 *
1172
 * @param string $string String to convert.
1173
 * @param string $charset Character set to use for conversion.
1174
 * @return string Returns converted string.
1175
 */
1176
function getpaid_strtoupper( $string, $charset = 'UTF-8' ) {
1177
	if ( function_exists( 'mb_convert_case' ) ) {
1178
		return mb_convert_case( $string, MB_CASE_UPPER, $charset );
1179
	} else {
1180
		return strtoupper( $string );
1181
	}
1182
}