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-template-functions.php (1 issue)

Code
1
<?php
2
/**
3
 * Template functions.
4
 *
5
 */
6
7
defined( 'ABSPATH' ) || exit;
8
9
/**
10
 * Displays an invoice.
11
 *
12
 * @param WPInv_Invoice $invoice.
13
 */
14
function getpaid_invoice( $invoice ) {
15
    if ( ! empty( $invoice ) ) {
16
        wpinv_get_template( 'invoice/invoice.php', compact( 'invoice' ) );
17
    }
18
}
19
add_action( 'getpaid_invoice', 'getpaid_invoice', 10 );
20
21
/**
22
 * Displays the invoice footer.
23
 */
24
function getpaid_invoice_footer( $invoice ) {
25
    if ( ! empty( $invoice ) ) {
26
        wpinv_get_template( 'invoice/footer.php', compact( 'invoice' ) );
27
    }
28
}
29
add_action( 'getpaid_invoice_footer', 'getpaid_invoice_footer', 10 );
30
31
/**
32
 * Displays the invoice top bar.
33
 */
34
function getpaid_invoice_header( $invoice ) {
35
    if ( ! empty( $invoice ) ) {
36
        wpinv_get_template( 'invoice/header.php', compact( 'invoice' ) );
37
    }
38
}
39
add_action( 'getpaid_invoice_header', 'getpaid_invoice_header', 10 );
40
41
/**
42
 * Displays actions on the left side of the header.
43
 */
44
function getpaid_invoice_header_left_actions( $invoice ) {
45
    if ( ! empty( $invoice ) ) {
46
        wpinv_get_template( 'invoice/header-left-actions.php', compact( 'invoice' ) );
47
    }
48
}
49
add_action( 'getpaid_invoice_header_left', 'getpaid_invoice_header_left_actions', 10 );
50
51
/**
52
 * Displays actions on the right side of the invoice top bar.
53
 */
54
function getpaid_invoice_header_right_actions( $invoice ) {
55
    if ( ! empty( $invoice ) ) {
56
        wpinv_get_template( 'invoice/header-right-actions.php', compact( 'invoice' ) );
57
    }
58
}
59
add_action( 'getpaid_invoice_header_right', 'getpaid_invoice_header_right_actions', 10 );
60
61
/**
62
 * Displays the invoice title, logo etc.
63
 */
64
function getpaid_invoice_details_top( $invoice ) {
65
    if ( ! empty( $invoice ) ) {
66
        wpinv_get_template( 'invoice/details-top.php', compact( 'invoice' ) );
67
    }
68
}
69
add_action( 'getpaid_invoice_details', 'getpaid_invoice_details_top', 10 );
70
71
/**
72
 * Displays the company logo.
73
 */
74
function getpaid_invoice_logo( $invoice ) {
75
    if ( ! empty( $invoice ) ) {
76
        wpinv_get_template( 'invoice/invoice-logo.php', compact( 'invoice' ) );
77
    }
78
}
79
add_action( 'getpaid_invoice_details_top_left', 'getpaid_invoice_logo' );
80
81
/**
82
 * Displays the type of invoice.
83
 */
84
function getpaid_invoice_type( $invoice ) {
85
    if ( ! empty( $invoice ) ) {
86
        wpinv_get_template( 'invoice/invoice-type.php', compact( 'invoice' ) );
87
    }
88
}
89
add_action( 'getpaid_invoice_details_top_right', 'getpaid_invoice_type' );
90
91
/**
92
 * Displays the invoice details.
93
 */
94
function getpaid_invoice_details_main( $invoice ) {
95
    if ( ! empty( $invoice ) ) {
96
        wpinv_get_template( 'invoice/details.php', compact( 'invoice' ) );
97
    }
98
}
99
add_action( 'getpaid_invoice_details', 'getpaid_invoice_details_main', 50 );
100
101
/**
102
 * Returns a path to the templates directory.
103
 *
104
 * @return string
105
 */
106
function wpinv_get_templates_dir() {
107
    return getpaid_template()->templates_dir;
108
}
109
110
/**
111
 * Returns a url to the templates directory.
112
 *
113
 * @return string
114
 */
115
function wpinv_get_templates_url() {
116
    return getpaid_template()->templates_url;
117
}
118
119
/**
120
 * Displays a template.
121
 *
122
 * First checks if there is a template overide, if not it loads the default template.
123
 *
124
 * @param string $template_name e.g payment-forms/cart.php The template to locate.
125
 * @param string $template_path The templates directory relative to the theme's root dir. Defaults to 'invoicing'.
126
 * @param string $default_path The root path to the default template. Defaults to invoicing/templates
127
 */
128
function wpinv_get_template( $template_name, $args = array(), $template_path = '', $default_path = '' ) {
129
    getpaid_template()->display_template( $template_name, $args, $template_path, $default_path );
130
}
131
132
/**
133
 * Retrieves a given template's html code.
134
 *
135
 * First checks if there is a template overide, if not it loads the default template.
136
 *
137
 * @param string $template_name e.g payment-forms/cart.php The template to locate.
138
 * @param array $args An array of args to pass to the template.
139
 * @param string $template_path The templates directory relative to the theme's root dir. Defaults to 'invoicing'.
140
 * @param string $default_path The root path to the default template. Defaults to invoicing/templates
141
 */
142
function wpinv_get_template_html( $template_name, $args = array(), $template_path = '', $default_path = '' ) {
143
	return getpaid_template()->get_template( $template_name, $args, $template_path, $default_path );
144
}
145
146
/**
147
 * Returns the default path from where to look for template overides.
148
 *
149
 * @return string
150
 */
151
function wpinv_template_path() {
152
    return apply_filters( 'wpinv_template_path', wpinv_get_theme_template_dir_name() );
153
}
154
155
/**
156
 * Returns the directory containing the template overides.
157
 *
158
 * @return string
159
 */
160
function wpinv_get_theme_template_dir_name() {
161
	return trailingslashit( apply_filters( 'wpinv_templates_dir', 'invoicing' ) );
162
}
163
164
/**
165
 * Locates a template path.
166
 *
167
 * First checks if there is a template overide, if not it loads the default template.
168
 *
169
 * @param string $template_name e.g payment-forms/cart.php The template to locate.
170
 * @param string $template_path The template path relative to the theme's root dir. Defaults to 'invoicing'.
171
 * @param string $default_path The root path to the default template. Defaults to invoicing/templates
172
 */
173
function wpinv_locate_template( $template_name, $template_path = '', $default_path = '' ) {
174
    return getpaid_template()->locate_template( $template_name, $template_path, $default_path );
175
}
176
177
function wpinv_get_template_part( $slug, $name = null, $load = true ) {
178
	do_action( 'get_template_part_' . $slug, $slug, $name );
179
180
	// Setup possible parts
181
	$templates = array();
182
	if ( isset( $name ) ) {
183
		$templates[] = $slug . '-' . $name . '.php';
184
    }
185
	$templates[] = $slug . '.php';
186
187
	// Allow template parts to be filtered
188
	$templates = apply_filters( 'wpinv_get_template_part', $templates, $slug, $name );
189
190
	// Return the part that is found
191
	return wpinv_locate_tmpl( $templates, $load, false );
192
}
193
194
function wpinv_locate_tmpl( $template_names, $load = false, $require_once = true ) {
195
	// No file found yet
196
	$located = false;
197
198
	// Try to find a template file
199
	foreach ( (array)$template_names as $template_name ) {
200
201
		// Continue if template is empty
202
		if ( empty( $template_name ) ) {
203
			continue;
204
        }
205
206
		// Trim off any slashes from the template name
207
		$template_name = ltrim( $template_name, '/' );
208
209
		// try locating this template file by looping through the template paths
210
		foreach ( wpinv_get_theme_template_paths() as $template_path ) {
211
212
			if ( file_exists( $template_path . $template_name ) ) {
213
				$located = $template_path . $template_name;
214
				break;
215
			}
216
		}
217
218
		if ( ! empty( $located ) ) {
219
			break;
220
		}
221
	}
222
223
	if ( ( true == $load ) && ! empty( $located ) ) {
224
		load_template( $located, $require_once );
225
    }
226
227
	return $located;
228
}
229
230
function wpinv_get_theme_template_paths() {
231
	$template_dir = wpinv_get_theme_template_dir_name();
232
233
	$file_paths = array(
234
		1   => trailingslashit( get_stylesheet_directory() ) . $template_dir,
235
		10  => trailingslashit( get_template_directory() ) . $template_dir,
236
		100 => wpinv_get_templates_dir(),
237
	);
238
239
	$file_paths = apply_filters( 'wpinv_template_paths', $file_paths );
240
241
	// sort the file paths based on priority
242
	ksort( $file_paths, SORT_NUMERIC );
243
244
	return array_map( 'trailingslashit', $file_paths );
245
}
246
247
function wpinv_checkout_meta_tags() {
248
249
	$pages   = array();
250
	$pages[] = wpinv_get_option( 'success_page' );
251
	$pages[] = wpinv_get_option( 'failure_page' );
252
	$pages[] = wpinv_get_option( 'invoice_history_page' );
253
	$pages[] = wpinv_get_option( 'invoice_subscription_page' );
254
255
	if ( ! wpinv_is_checkout() && ! is_page( $pages ) ) {
256
		return;
257
	}
258
259
	echo '<meta name="robots" content="noindex,nofollow" />' . "\n";
260
}
261
add_action( 'wp_head', 'wpinv_checkout_meta_tags' );
262
263
function wpinv_add_body_classes( $class ) {
264
	$classes = (array)$class;
265
266
	if ( wpinv_is_checkout() ) {
267
		$classes[] = 'wpinv-checkout';
268
		$classes[] = 'wpinv-page';
269
	}
270
271
	if ( wpinv_is_success_page() ) {
272
		$classes[] = 'wpinv-success';
273
		$classes[] = 'wpinv-page';
274
	}
275
276
	if ( wpinv_is_failed_transaction_page() ) {
277
		$classes[] = 'wpinv-failed-transaction';
278
		$classes[] = 'wpinv-page';
279
	}
280
281
	if ( wpinv_is_invoice_history_page() ) {
282
		$classes[] = 'wpinv-history';
283
		$classes[] = 'wpinv-page';
284
	}
285
286
	if ( wpinv_is_subscriptions_history_page() ) {
287
		$classes[] = 'wpinv-subscription';
288
		$classes[] = 'wpinv-page';
289
	}
290
291
	if ( wpinv_is_test_mode() ) {
292
		$classes[] = 'wpinv-test-mode';
293
		$classes[] = 'wpinv-page';
294
	}
295
296
	return array_unique( $classes );
297
}
298
add_filter( 'body_class', 'wpinv_add_body_classes' );
299
300
function wpinv_html_select( $args = array() ) {
301
    $defaults = array(
302
        'options'          => array(),
303
        'name'             => null,
304
        'class'            => '',
305
        'id'               => '',
306
        'selected'         => 0,
307
        'placeholder'      => null,
308
        'multiple'         => false,
309
        'show_option_all'  => _x( 'All', 'all dropdown items', 'invoicing' ),
310
        'show_option_none' => _x( 'None', 'no dropdown items', 'invoicing' ),
311
        'data'             => array(),
312
        'onchange'         => null,
313
        'required'         => false,
314
        'disabled'         => false,
315
        'readonly'         => false,
316
    );
317
318
    $args = wp_parse_args( $args, $defaults );
319
320
    $attrs = array(
321
        'name'     => $args['name'],
322
        'id'       => $args['id'],
323
        'class'    => 'wpinv-select ' . implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['class'] ) ) ),
324
        'multiple' => ! empty( $args['multiple'] ),
325
        'readonly' => ! empty( $args['readonly'] ),
326
        'disabled' => ! empty( $args['disabled'] ),
327
        'required' => ! empty( $args['required'] ),
328
        'onchange' => ! empty( $args['onchange'] ),
329
    );
330
331
    if ( $args['placeholder'] ) {
332
        $attrs['data-placeholder'] = $args['placeholder'];
333
    }
334
335
    if ( $args['onchange'] ) {
336
        $attrs['onchange'] = $args['onchange'];
337
    }
338
339
    foreach ( $args['data'] as $key => $value ) {
340
        $attrs["data-$key"] = $value;
341
    }
342
343
    echo '<select ';
344
345
    foreach ( $attrs as $attr => $value ) {
346
347
        if ( false === $value ) {
348
            continue;
349
        }
350
351
        if ( true === $value ) {
352
            echo ' ' . esc_attr( $attr );
353
        } else {
354
            echo ' ' . esc_attr( $attr ) . '="' . esc_attr( $value ) . '"';
355
        }
356
357
    }
358
359
    echo '>';
360
361
    if ( $args['show_option_all'] ) {
362
        if ( $args['multiple'] ) {
363
            $selected = in_array( 0, $args['selected'] );
364
        } else {
365
            $selected = empty( $args['selected'] );
366
        }
367
        echo '<option value="all"' . selected( $selected, true, false ) . '>' . esc_html( $args['show_option_all'] ) . '</option>';
368
    }
369
370
    if ( ! empty( $args['options'] ) ) {
371
372
        if ( $args['show_option_none'] ) {
373
            if ( $args['multiple'] ) {
374
                $selected = in_array( '', $args['selected'], true );
375
            } else {
376
                $selected = $args['selected'] === '';
377
            }
378
379
            echo '<option value=""' . selected( $selected, true, false ) . '>' . esc_html( $args['show_option_none'] ) . '</option>';
380
        }
381
382
        foreach ( $args['options'] as $key => $option ) {
383
384
            if ( $args['multiple'] && is_array( $args['selected'] ) ) {
385
                $selected = in_array( $key, $args['selected'], true );
386
            } else {
387
                $selected = $args['selected'] === $key;
388
            }
389
390
            echo '<option value="' . esc_attr( $key ) . '"' . selected( $selected, true, false ) . '>' . esc_html( $option ) . '</option>';
391
        }
392
    }
393
394
    echo '</select>';
395
396
}
397
398
function wpinv_item_dropdown( $args = array() ) {
399
    $defaults = array(
400
        'name'             => 'wpi_item',
401
        'id'               => 'wpi_item',
402
        'class'            => '',
403
        'multiple'         => false,
404
        'selected'         => 0,
405
        'number'           => -1,
406
        'placeholder'      => __( 'Choose a item', 'invoicing' ),
407
        'data'             => array( 'search-type' => 'item' ),
408
        'show_option_all'  => false,
409
        'show_option_none' => false,
410
        'show_recurring'   => false,
411
    );
412
413
    $args = wp_parse_args( $args, $defaults );
414
415
    $item_args = array(
416
        'post_type'      => 'wpi_item',
417
        'orderby'        => 'title',
418
        'order'          => 'ASC',
419
        'posts_per_page' => $args['number'],
420
421
        // Skip where _wpinv_one_time meta is yes.
422
        'meta_query'     => array(
423
            'relation' => 'OR',
424
            array(
425
                'key'     => '_wpinv_one_time',
426
                'compare' => 'NOT EXISTS',
427
            ),
428
        ),
429
    );
430
431
    $item_args  = apply_filters( 'wpinv_item_dropdown_query_args', $item_args, $args, $defaults );
432
433
    $items      = get_posts( $item_args );
434
    $options    = array();
435
    if ( $items ) {
436
        foreach ( $items as $item ) {
437
            $title = esc_html( $item->post_title );
438
439
            if ( ! empty( $args['show_recurring'] ) ) {
440
                $title .= wpinv_get_item_suffix( $item->ID, false );
441
            }
442
443
            $options[ absint( $item->ID ) ] = $title;
444
        }
445
    }
446
447
    // This ensures that any selected items are included in the drop down
448
    if ( is_array( $args['selected'] ) ) {
449
        foreach ( $args['selected'] as $item ) {
450
            if ( ! in_array( $item, $options ) ) {
451
                $title = get_the_title( $item );
452
                if ( ! empty( $args['show_recurring'] ) ) {
453
                    $title .= wpinv_get_item_suffix( $item, false );
454
                }
455
                $options[ $item ] = $title;
456
            }
457
        }
458
    } elseif ( is_numeric( $args['selected'] ) && $args['selected'] !== 0 ) {
459
        if ( ! in_array( $args['selected'], $options ) ) {
460
            $title = get_the_title( $args['selected'] );
461
            if ( ! empty( $args['show_recurring'] ) ) {
462
                $title .= wpinv_get_item_suffix( $args['selected'], false );
463
            }
464
            $options[ $args['selected'] ] = get_the_title( $args['selected'] );
465
        }
466
    }
467
468
    wpinv_html_select(
469
        array(
470
			'name'             => $args['name'],
471
			'selected'         => $args['selected'],
472
			'id'               => $args['id'],
473
			'class'            => $args['class'],
474
			'options'          => $options,
475
			'multiple'         => $args['multiple'],
476
			'placeholder'      => $args['placeholder'],
477
			'show_option_all'  => $args['show_option_all'],
478
			'show_option_none' => $args['show_option_none'],
479
			'data'             => $args['data'],
480
        )
481
    );
482
483
}
484
485
/**
486
 * Returns an array of published items.
487
 */
488
function wpinv_get_published_items_for_dropdown() {
489
490
    $items = get_posts(
491
        array(
492
            'post_type'      => 'wpi_item',
493
            'orderby'        => 'title',
494
            'order'          => 'ASC',
495
            'posts_per_page' => '-1',
496
        )
497
    );
498
499
    $options = array();
500
    if ( $items ) {
501
        foreach ( $items as $item ) {
502
            $options[ $item->ID ] = esc_html( $item->post_title ) . wpinv_get_item_suffix( $item->ID, false );
503
        }
504
    }
505
506
    return $options;
507
}
508
509
function wpinv_html_checkbox( $args = array() ) {
510
    $defaults = array(
511
        'name'    => null,
512
        'current' => null,
513
        'class'   => 'wpinv-checkbox',
514
        'options' => array(
515
            'disabled' => false,
516
            'readonly' => false,
517
        ),
518
    );
519
520
    $args = wp_parse_args( $args, $defaults );
521
522
    $class = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['class'] ) ) );
523
    $attr  = '';
524
    if ( ! empty( $args['options']['disabled'] ) ) {
525
        $attr .= ' disabled="disabled"';
526
    } elseif ( ! empty( $args['options']['readonly'] ) ) {
527
        $attr .= ' readonly';
528
    }
529
530
    $output = '<input type="checkbox"' . $attr . ' name="' . esc_attr( $args['name'] ) . '" id="' . esc_attr( $args['name'] ) . '" class="' . esc_attr( $class ) . ' ' . esc_attr( $args['name'] ) . '" ' . checked( 1, $args['current'], false ) . ' />';
531
532
    return $output;
533
}
534
535
/**
536
 * Displays a hidden field.
537
 */
538
function getpaid_hidden_field( $name, $value ) {
539
    echo "<input type='hidden' name='" . esc_attr( $name ) . "' value='" . esc_attr( $value ) . "' />";
540
}
541
542
/**
543
 * Displays a submit field.
544
 */
545
function getpaid_submit_field( $value, $name = 'submit', $class = 'btn-primary' ) {
546
    echo "<input type='submit' name='" . esc_attr( $name ) . "' value='" . esc_attr( $value ) . "' class='btn " . esc_attr( $class ) . "' />";
547
}
548
549
function wpinv_html_text( $args = array() ) {
550
    // Backwards compatibility
551
    if ( func_num_args() > 1 ) {
552
        $args = func_get_args();
553
554
        $name  = $args[0];
555
        $value = isset( $args[1] ) ? $args[1] : '';
556
        $label = isset( $args[2] ) ? $args[2] : '';
557
        $desc  = isset( $args[3] ) ? $args[3] : '';
558
    }
559
560
    $defaults = array(
561
        'id'           => '',
562
        'name'         => isset( $name ) ? $name : 'text',
563
        'value'        => isset( $value ) ? $value : null,
564
        'label'        => isset( $label ) ? $label : null,
565
        'desc'         => isset( $desc ) ? $desc : null,
566
        'placeholder'  => '',
567
        'class'        => 'regular-text',
568
        'disabled'     => false,
569
        'readonly'     => false,
570
        'required'     => false,
571
        'autocomplete' => '',
572
        'data'         => false,
573
    );
574
575
    $args = wp_parse_args( $args, $defaults );
576
577
    $class = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['class'] ) ) );
578
    $options = '';
579
    if ( $args['required'] ) {
580
        $options .= ' required="required"';
581
    }
582
    if ( $args['readonly'] ) {
583
        $options .= ' readonly';
584
    }
585
    if ( $args['readonly'] ) {
586
        $options .= ' readonly';
587
    }
588
589
    $data = '';
590
    if ( ! empty( $args['data'] ) ) {
591
        foreach ( $args['data'] as $key => $value ) {
592
            $data .= 'data-' . wpinv_sanitize_key( $key ) . '="' . esc_attr( $value ) . '" ';
593
        }
594
    }
595
596
    $output = '<span id="wpinv-' . wpinv_sanitize_key( $args['name'] ) . '-wrap">';
597
    $output .= '<label class="wpinv-label" for="' . wpinv_sanitize_key( $args['id'] ) . '">' . esc_html( $args['label'] ) . '</label>';
598
    if ( ! empty( $args['desc'] ) ) {
599
        $output .= '<span class="wpinv-description">' . esc_html( $args['desc'] ) . '</span>';
600
    }
601
602
    $output .= '<input type="text" name="' . esc_attr( $args['name'] ) . '" id="' . esc_attr( $args['id'] ) . '" autocomplete="' . esc_attr( $args['autocomplete'] ) . '" value="' . esc_attr( $args['value'] ) . '" placeholder="' . esc_attr( $args['placeholder'] ) . '" class="' . $class . '" ' . $data . ' ' . trim( $options ) . '/>';
603
604
    $output .= '</span>';
605
606
    return $output;
607
}
608
609
function wpinv_html_textarea( $args = array() ) {
610
    $defaults = array(
611
        'name'        => 'textarea',
612
        'value'       => null,
613
        'label'       => null,
614
        'desc'        => null,
615
        'class'       => 'large-text',
616
        'disabled'    => false,
617
        'placeholder' => '',
618
    );
619
620
    $args = wp_parse_args( $args, $defaults );
621
622
    $class = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['class'] ) ) );
623
    $disabled = '';
624
    if ( $args['disabled'] ) {
625
        $disabled = ' disabled="disabled"';
626
    }
627
628
    $output = '<span id="wpinv-' . wpinv_sanitize_key( $args['name'] ) . '-wrap">';
629
    $output .= '<label class="wpinv-label" for="' . wpinv_sanitize_key( $args['name'] ) . '">' . esc_html( $args['label'] ) . '</label>';
630
    $output .= '<textarea name="' . esc_attr( $args['name'] ) . '" placeholder="' . esc_attr( $args['placeholder'] ) . '" id="' . wpinv_sanitize_key( $args['name'] ) . '" class="' . $class . '"' . $disabled . '>' . esc_attr( $args['value'] ) . '</textarea>';
631
632
    if ( ! empty( $args['desc'] ) ) {
633
        $output .= '<span class="wpinv-description">' . esc_html( $args['desc'] ) . '</span>';
634
    }
635
    $output .= '</span>';
636
637
    return $output;
638
}
639
640
function wpinv_html_ajax_user_search( $args = array() ) {
641
    $defaults = array(
642
        'name'         => 'user_id',
643
        'value'        => null,
644
        'placeholder'  => __( 'Enter username', 'invoicing' ),
645
        'label'        => null,
646
        'desc'         => null,
647
        'class'        => '',
648
        'disabled'     => false,
649
        'autocomplete' => 'off',
650
        'data'         => false,
651
    );
652
653
    $args = wp_parse_args( $args, $defaults );
654
655
    $args['class'] = 'wpinv-ajax-user-search ' . $args['class'];
656
657
    $output  = '<span class="wpinv_user_search_wrap">';
658
        $output .= wpinv_html_text( $args );
659
        $output .= '<span class="wpinv_user_search_results hidden"><a class="wpinv-ajax-user-cancel" title="' . __( 'Cancel', 'invoicing' ) . '" aria-label="' . __( 'Cancel', 'invoicing' ) . '" href="#">x</a><span></span></span>';
660
    $output .= '</span>';
661
662
    return $output;
663
}
664
665
/**
666
 * Use our template to display invoices.
667
 *
668
 * @param string $template the template that is currently being used.
669
 */
670
function wpinv_template( $template ) {
671
    global $post;
672
673
    if ( ! is_admin() && ( is_single() || is_404() ) && ! empty( $post->ID ) && getpaid_is_invoice_post_type( get_post_type( $post->ID ) ) ) {
674
675
        // If the user can view this invoice, display it.
676
        if ( wpinv_user_can_view_invoice( $post->ID ) ) {
677
678
            return wpinv_get_template_part( 'wpinv-invoice-print', false, false );
679
680
        // Else display an error message.
681
        } else {
682
683
            return wpinv_get_template_part( 'wpinv-invalid-access', false, false );
684
685
        }
686
}
687
688
    return $template;
689
}
690
add_filter( 'template_include', 'wpinv_template', 1000, 1 );
691
692
function wpinv_get_business_address() {
693
    $business_address   = wpinv_store_address();
694
    $business_address   = ! empty( $business_address ) ? wp_kses_post( wpautop( $business_address ) ) : '';
695
696
    $business_address = $business_address ? '<div class="address">' . $business_address . '</div>' : '';
697
698
    return apply_filters( 'wpinv_get_business_address', $business_address );
699
}
700
701
/**
702
 * Displays the company address.
703
 */
704
function wpinv_display_from_address() {
705
    wpinv_get_template( 'invoice/company-address.php' );
706
}
707
add_action( 'getpaid_invoice_details_left', 'wpinv_display_from_address', 10 );
708
709
/**
710
 * Generates a watermark text for an invoice.
711
 *
712
 * @param WPInv_Invoice $invoice
713
 * @return string
714
 */
715
function wpinv_watermark( $invoice ) {
716
    $watermark = wpinv_get_watermark( $invoice );
717
    return apply_filters( 'wpinv_get_watermark', $watermark, $invoice );
718
}
719
720
/**
721
 * Generates a watermark text for an invoice.
722
 *
723
 * @param WPInv_Invoice $invoice
724
 * @return string
725
 */
726
function wpinv_get_watermark( $invoice ) {
727
    return $invoice->get_status_nicename();
728
}
729
730
/**
731
 * @deprecated
732
 */
733
function wpinv_display_invoice_details( $invoice ) {
734
    return getpaid_invoice_meta( $invoice );
735
}
736
737
/**
738
 * Displays invoice meta.
739
 */
740
function getpaid_invoice_meta( $invoice ) {
741
742
    $invoice = new WPInv_Invoice( $invoice );
743
744
    // Ensure that we have an invoice.
745
    if ( 0 == $invoice->get_id() ) {
746
        return;
747
    }
748
749
    // Get the invoice meta.
750
    $meta = getpaid_get_invoice_meta( $invoice );
751
752
    // Display the meta.
753
    wpinv_get_template( 'invoice/invoice-meta.php', compact( 'invoice', 'meta' ) );
754
755
}
756
add_action( 'getpaid_invoice_details_right', 'getpaid_invoice_meta', 10 );
757
758
/**
759
 * Retrieves the address markup to use on Invoices.
760
 *
761
 * @since 1.0.13
762
 * @see `wpinv_get_full_address_format`
763
 * @see `wpinv_get_invoice_address_replacements`
764
 * @param array $billing_details customer's billing details
765
 * @param  string $separator How to separate address lines.
766
 * @return string
767
 */
768
function wpinv_get_invoice_address_markup( $billing_details, $separator = '<br/>' ) {
769
770
    // Retrieve the address markup...
771
    $country = empty( $billing_details['country'] ) ? '' : $billing_details['country'];
772
    $format = wpinv_get_full_address_format( $country );
773
774
    // ... and the replacements.
775
    $replacements = wpinv_get_invoice_address_replacements( $billing_details );
776
777
    $formatted_address = str_ireplace( array_keys( $replacements ), $replacements, $format );
778
779
	// Remove unavailable tags.
780
    $formatted_address = preg_replace( '/\{\{\w+\}\}/', '', $formatted_address );
781
782
    // Clean up white space.
783
	$formatted_address = preg_replace( '/  +/', ' ', trim( $formatted_address ) );
784
    $formatted_address = preg_replace( '/\n\n+/', "\n", $formatted_address );
785
786
    // Break newlines apart and remove empty lines/trim commas and white space.
787
	$formatted_address = array_filter( array_map( 'wpinv_trim_formatted_address_line', explode( "\n", $formatted_address ) ) );
788
789
    // Add html breaks.
790
	$formatted_address = implode( $separator, $formatted_address );
791
792
	// We're done!
793
	return $formatted_address;
794
795
}
796
797
/**
798
 * Displays the billing address.
799
 *
800
 * @param WPInv_Invoice $invoice
801
 */
802
function wpinv_display_to_address( $invoice = 0 ) {
803
    if ( ! empty( $invoice ) ) {
804
        wpinv_get_template( 'invoice/billing-address.php', compact( 'invoice' ) );
805
    }
806
}
807
add_action( 'getpaid_invoice_details_left', 'wpinv_display_to_address', 40 );
808
809
810
/**
811
 * Displays invoice line items.
812
 */
813
function wpinv_display_line_items( $invoice_id = 0 ) {
814
815
    // Prepare the invoice.
816
    $invoice = new WPInv_Invoice( $invoice_id );
817
818
    // Abort if there is no invoice.
819
    if ( 0 == $invoice->get_id() ) {
820
        return;
821
    }
822
823
    // Line item columns.
824
    $columns = getpaid_invoice_item_columns( $invoice );
825
    $columns = apply_filters( 'getpaid_invoice_line_items_table_columns', $columns, $invoice );
826
827
    wpinv_get_template( 'invoice/line-items.php', compact( 'invoice', 'columns' ) );
828
}
829
add_action( 'getpaid_invoice_line_items', 'wpinv_display_line_items', 10 );
830
831
/**
832
 * Displays invoice subscriptions.
833
 *
834
 * @param WPInv_Invoice $invoice
835
 */
836
function getpaid_display_invoice_subscriptions( $invoice ) {
837
838
    // Subscriptions.
839
	$subscriptions = getpaid_get_invoice_subscriptions( $invoice );
840
841
    if ( empty( $subscriptions ) || ! $invoice->is_recurring() ) {
842
        return;
843
    }
844
845
    $main_subscription = getpaid_get_invoice_subscription( $invoice );
846
847
    // Display related subscriptions.
848
    if ( is_array( $subscriptions ) ) {
849
        printf( '<h2 class="mt-5 mb-1 h4">%s</h2>', esc_html__( 'Related Subscriptions', 'invoicing' ) );
850
        getpaid_admin_subscription_related_subscriptions_metabox( $main_subscription, false );
851
    }
852
853
    if ( $main_subscription->get_total_payments() > 1 ) {
854
        printf( '<h2 class="mt-5 mb-1 h4">%s</h2>', esc_html__( 'Related Invoices', 'invoicing' ) );
855
        getpaid_admin_subscription_invoice_details_metabox( $main_subscription, false );
856
    }
857
858
}
859
add_action( 'getpaid_invoice_line_items', 'getpaid_display_invoice_subscriptions', 55 );
860
add_action( 'wpinv_receipt_end', 'getpaid_display_invoice_subscriptions', 11 );
861
862
/**
863
 * Displays invoice notices on invoices.
864
 */
865
function wpinv_display_invoice_notice() {
866
867
    $label  = wpinv_get_option( 'vat_invoice_notice_label' );
868
    $notice = wpinv_get_option( 'vat_invoice_notice' );
869
870
    if ( empty( $label ) && empty( $notice ) ) {
871
        return;
872
    }
873
874
    echo '<div class="mt-4 mb-4 wpinv-vat-notice">';
875
876
    if ( ! empty( $label ) ) {
877
        echo "<h5>" . esc_html( $label ) . "</h5>";
878
    }
879
880
    if ( ! empty( $notice ) ) {
881
        echo '<small class="form-text text-muted">' . wp_kses_post( wpautop( wptexturize( $notice ) ) ) . '</small>';
882
    }
883
884
    echo '</div>';
885
}
886
add_action( 'getpaid_invoice_line_items', 'wpinv_display_invoice_notice', 100 );
887
888
/**
889
 * @param WPInv_Invoice $invoice
890
 */
891
function wpinv_display_invoice_notes( $invoice ) {
892
893
    // Retrieve the notes.
894
    $notes = wpinv_get_invoice_notes( $invoice->get_id(), 'customer' );
895
896
    // Abort if we have non.
897
    if ( empty( $notes ) ) {
898
        return;
899
    }
900
901
    // Echo the note.
902
    echo '<div class="getpaid-invoice-notes-wrapper position-relative my-4">';
903
    echo '<h2 class="getpaid-invoice-notes-title mb-1 p-0 h4">' . esc_html__( 'Notes', 'invoicing' ) . '</h2>';
904
    echo '<ul class="getpaid-invoice-notes text-break overflow-auto list-unstyled p-0 m-0">';
905
906
    foreach ( $notes as $note ) {
907
        wpinv_get_invoice_note_line_item( $note );
908
    }
909
910
    echo '</ul>';
911
    echo '</div>';
912
}
913
add_action( 'getpaid_invoice_line_items', 'wpinv_display_invoice_notes', 60 );
914
915
/**
916
 * Loads scripts on our invoice templates.
917
 */
918
function wpinv_display_style() {
919
920
    // Make sure that all scripts have been loaded.
921
    if ( ! did_action( 'wp_enqueue_scripts' ) ) {
922
        do_action( 'wp_enqueue_scripts' );
923
    }
924
925
    // Add global styles.
926
    if ( wp_is_block_theme() ) {
927
        wp_print_styles( 'global-styles' );
928
    }
929
930
    // Register the invoices style.
931
    wp_register_style( 'wpinv-single-style', WPINV_PLUGIN_URL . 'assets/css/invoice.css', array(), filemtime( WPINV_PLUGIN_DIR . 'assets/css/invoice.css' ) );
932
933
    // Load required styles
934
    wp_print_styles( 'wpinv-single-style' );
935
    wp_print_styles( 'ayecode-ui' );
936
937
    // Maybe load custom css.
938
    $custom_css = wpinv_get_option( 'template_custom_css' );
939
940
    if ( isset( $custom_css ) && ! empty( $custom_css ) ) {
941
        $custom_css     = wp_kses( $custom_css, array( '\'', '\"' ) );
942
        $custom_css     = str_replace( '&gt;', '>', $custom_css );
943
        echo '<style type="text/css">';
944
        echo wp_kses_post( $custom_css );
945
        echo '</style>';
946
    }
947
948
    wp_site_icon();
949
}
950
add_action( 'wpinv_invoice_print_head', 'wpinv_display_style' );
951
add_action( 'wpinv_invalid_invoice_head', 'wpinv_display_style' );
952
953
954
/**
955
 * Displays the checkout page.
956
 */
957
function wpinv_checkout_form() {
958
    global $wpi_checkout_id;
959
960
    // Retrieve the current invoice.
961
    $invoice_id = getpaid_get_current_invoice_id();
962
963
    if ( empty( $invoice_id ) ) {
964
965
        return aui()->alert(
966
            array(
967
                'type'    => 'warning',
968
                'content' => __( 'Invalid invoice', 'invoicing' ),
969
            )
970
        );
971
972
    }
973
974
    // Can the user view this invoice?
975
    if ( ! wpinv_user_can_view_invoice( $invoice_id ) ) {
976
977
        return aui()->alert(
978
            array(
979
                'type'    => 'warning',
980
                'content' => __( 'You are not allowed to view this invoice', 'invoicing' ),
981
            )
982
        );
983
984
    }
985
986
    // Ensure that it is not yet paid for.
987
    $invoice = new WPInv_Invoice( $invoice_id );
988
989
    // Maybe mark it as viewed.
990
    getpaid_maybe_mark_invoice_as_viewed( $invoice );
991
992
    if ( $invoice->is_paid() ) {
993
994
        return aui()->alert(
995
            array(
996
                'type'    => 'success',
997
                'content' => __( 'This invoice has already been paid.', 'invoicing' ),
998
            )
999
        );
1000
1001
    }
1002
1003
    // Set the global invoice id.
1004
    $wpi_checkout_id = $invoice_id;
1005
1006
    // Retrieve appropriate payment form.
1007
    $payment_form = new GetPaid_Payment_Form( wpinv_translate_post_id( $invoice->get_meta( 'force_payment_form' ) ) );
1008
    $payment_form = $payment_form->exists() ? $payment_form : new GetPaid_Payment_Form( wpinv_get_default_payment_form() );
1009
1010
    if ( ! $payment_form->exists() ) {
1011
1012
        return aui()->alert(
1013
            array(
1014
                'type'    => 'warning',
1015
                'content' => __( 'Error loading the payment form', 'invoicing' ),
1016
            )
1017
        );
1018
1019
    }
1020
1021
    // Set the invoice.
1022
    $payment_form->invoice = $invoice;
1023
1024
    if ( ! $payment_form->is_default() ) {
1025
1026
        $items    = array();
1027
        $item_ids = array();
1028
1029
        foreach ( $invoice->get_items() as $item ) {
1030
            if ( ! in_array( $item->get_id(), $item_ids ) ) {
1031
                $item_ids[] = $item->get_id();
1032
                $items[]    = $item;
1033
            }
1034
        }
1035
1036
        foreach ( $payment_form->get_items() as $item ) {
1037
            if ( ! in_array( $item->get_id(), $item_ids ) ) {
1038
                $item_ids[] = $item->get_id();
1039
                $items[]    = $item;
1040
            }
1041
        }
1042
1043
        $payment_form->set_items( $items );
1044
1045
    } else {
1046
        $payment_form->set_items( $invoice->get_items() );
1047
    }
1048
1049
    // Generate the html.
1050
    return $payment_form->get_html();
1051
1052
}
1053
1054
function wpinv_empty_cart_message() {
1055
	return apply_filters( 'wpinv_empty_cart_message', '<span class="wpinv_empty_cart">' . __( 'Your cart is empty.', 'invoicing' ) . '</span>' );
1056
}
1057
1058
/**
1059
 * Echoes the Empty Cart Message
1060
 *
1061
 * @since 1.0
1062
 * @return void
1063
 */
1064
function wpinv_empty_checkout_cart() {
1065
    aui()->alert(
1066
        array(
1067
            'type'    => 'warning',
1068
            'content' => wpinv_empty_cart_message(),
1069
        ),
1070
        true
1071
    );
1072
}
1073
add_action( 'wpinv_cart_empty', 'wpinv_empty_checkout_cart' );
1074
1075
/**
1076
 * Filters the receipt page.
1077
 */
1078
function wpinv_filter_success_page_content( $content ) {
1079
1080
    // Maybe abort early.
1081
    if ( is_admin() || ! is_singular() || ! in_the_loop() || ! is_main_query() || is_preview() ) {
1082
        return $content;
1083
    }
1084
1085
    // Ensure this is our page.
1086
    if ( isset( $_GET['payment-confirm'] ) && wpinv_is_success_page() ) {
1087
1088
        $gateway = sanitize_text_field( $_GET['payment-confirm'] );
1089
        return apply_filters( "wpinv_payment_confirm_$gateway", $content );
1090
1091
    }
1092
1093
    return $content;
1094
}
1095
add_filter( 'the_content', 'wpinv_filter_success_page_content', 99999 );
1096
1097
function wpinv_invoice_link( $invoice_id ) {
1098
    $invoice = wpinv_get_invoice( $invoice_id );
1099
1100
    if ( empty( $invoice ) ) {
1101
        return null;
1102
    }
1103
1104
    $invoice_link = '<a href="' . esc_url( $invoice->get_view_url() ) . '">' . $invoice->get_number() . '</a>';
1105
1106
    return apply_filters( 'wpinv_get_invoice_link', $invoice_link, $invoice );
1107
}
1108
1109
function wpinv_get_invoice_note_line_item( $note, $echo = true ) {
1110
    if ( empty( $note ) ) {
1111
        return null;
1112
    }
1113
1114
    if ( is_int( $note ) ) {
1115
        $note = get_comment( $note );
1116
    }
1117
1118
    if ( ! ( is_object( $note ) && is_a( $note, 'WP_Comment' ) ) ) {
1119
        return null;
1120
    }
1121
1122
    $note_classes   = array( 'note' );
1123
    $note_classes[] = get_comment_meta( $note->comment_ID, '_wpi_customer_note', true ) ? 'customer-note' : '';
1124
    $note_classes[] = $note->comment_author === 'System' ? 'system-note' : '';
1125
    $note_classes   = apply_filters( 'wpinv_invoice_note_class', array_filter( $note_classes ), $note );
1126
    $note_classes   = ! empty( $note_classes ) ? implode( ' ', $note_classes ) : '';
1127
1128
    ob_start();
1129
    ?>
1130
    <li rel="<?php echo absint( $note->comment_ID ); ?>" class="<?php echo esc_attr( $note_classes ); ?> mb-2">
1131
        <div class="note_content">
1132
1133
            <?php echo wp_kses_post( wptexturize( $note->comment_content ) ); ?>
1134
1135
            <?php if ( ! is_admin() ) : ?>
1136
                <em class="small form-text text-muted mt-0">
1137
                    <?php
1138
                        printf(
1139
                            esc_html__( '%1$s - %2$s at %3$s', 'invoicing' ),
1140
                            esc_html( $note->comment_author ),
1141
                            esc_html( getpaid_format_date_value( $note->comment_date ) ),
1142
                            esc_html( date_i18n( get_option( 'time_format' ), strtotime( $note->comment_date ) ) )
1143
                        );
1144
                    ?>
1145
                </em>
1146
            <?php endif; ?>
1147
1148
        </div>
1149
1150
        <?php if ( is_admin() ) : ?>
1151
1152
            <p class="meta px-4 py-2">
1153
                <abbr class="exact-date" title="<?php echo esc_attr( $note->comment_date ); ?>">
1154
                    <?php
1155
                        printf(
1156
                            esc_html__( '%1$s - %2$s at %3$s', 'invoicing' ),
1157
                            esc_html( $note->comment_author ),
1158
                            esc_html( getpaid_format_date_value( $note->comment_date ) ),
1159
                            esc_html( date_i18n( get_option( 'time_format' ), strtotime( $note->comment_date ) ) )
1160
                        );
1161
                    ?>
1162
                </abbr>&nbsp;&nbsp;
1163
                <?php if ( $note->comment_author !== 'System' && wpinv_current_user_can_manage_invoicing() ) { ?>
1164
                    <a href="#" class="delete_note" data-id="<?php echo esc_attr( $note->comment_ID ); ?>"><?php esc_html_e( 'Delete note', 'invoicing' ); ?></a>
1165
                <?php } ?>
1166
            </p>
1167
1168
        <?php endif; ?>
1169
1170
    </li>
1171
    <?php
1172
    $note_content = ob_get_clean();
1173
    $note_content = apply_filters( 'wpinv_get_invoice_note_line_item', $note_content, $note, $echo );
1174
1175
    if ( $echo ) {
1176
        echo wp_kses_post( $note_content );
1177
    } else {
1178
        return $note_content;
1179
    }
1180
}
1181
1182
/**
1183
 * Function to get privacy policy text.
1184
 *
1185
 * @since 1.0.13
1186
 * @return string
1187
 */
1188
function wpinv_get_policy_text() {
1189
    $privacy_page_id = get_option( 'wp_page_for_privacy_policy', 0 );
1190
1191
    $text = wpinv_get_option( 'invoicing_privacy_checkout_message', sprintf( __( 'Your personal data will be used to process your invoice, payment and for other purposes described in our %s.', 'invoicing' ), '[wpinv_privacy_policy]' ) );
1192
1193
    if ( ! $privacy_page_id ) {
1194
        $privacy_page_id = wpinv_get_option( 'privacy_page', 0 );
1195
    }
1196
1197
    $privacy_link    = $privacy_page_id ? '<a href="' . esc_url( get_permalink( $privacy_page_id ) ) . '" class="wpinv-privacy-policy-link" target="_blank">' . __( 'privacy policy', 'invoicing' ) . '</a>' : __( 'privacy policy', 'invoicing' );
1198
1199
    $find_replace = array(
1200
        '[wpinv_privacy_policy]' => $privacy_link,
1201
    );
1202
1203
    $privacy_text = str_replace( array_keys( $find_replace ), array_values( $find_replace ), $text );
1204
1205
    return wp_kses_post( wpautop( $privacy_text ) );
1206
}
1207
1208
function wpinv_oxygen_fix_conflict() {
1209
    global $ct_ignore_post_types;
1210
1211
    if ( ! is_array( $ct_ignore_post_types ) ) {
1212
        $ct_ignore_post_types = array();
1213
    }
1214
1215
    $post_types = array( 'wpi_discount', 'wpi_invoice', 'wpi_item', 'wpi_payment_form' );
1216
1217
    foreach ( $post_types as $post_type ) {
1218
        $ct_ignore_post_types[] = $post_type;
1219
1220
        // Ignore post type
1221
        add_filter( 'pre_option_oxygen_vsb_ignore_post_type_' . $post_type, '__return_true', 999 );
1222
    }
1223
1224
    remove_filter( 'template_include', 'wpinv_template', 10, 1 );
0 ignored issues
show
The call to remove_filter() has too many arguments starting with 1. ( Ignorable by Annotation )

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

1224
    /** @scrutinizer ignore-call */ 
1225
    remove_filter( 'template_include', 'wpinv_template', 10, 1 );

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1225
    add_filter( 'template_include', 'wpinv_template', 999, 1 );
1226
}
1227
1228
/**
1229
 * Helper function to display a payment form on the frontend.
1230
 *
1231
 * @param GetPaid_Payment_Form $form
1232
 */
1233
function getpaid_display_payment_form( $form ) {
1234
1235
    if ( is_numeric( $form ) ) {
1236
        $form = new GetPaid_Payment_Form( wpinv_translate_post_id( $form ) );
1237
    }
1238
1239
    $form->display();
1240
1241
}
1242
1243
/**
1244
 * Helper function to display a item payment form on the frontend.
1245
 */
1246
function getpaid_display_item_payment_form( $items ) {
1247
1248
    $form = new GetPaid_Payment_Form( wpinv_get_default_payment_form() );
1249
    $form->set_items( $items );
1250
1251
    if ( 0 == count( $form->get_items() ) ) {
1252
        aui()->alert(
1253
			array(
1254
				'type'    => 'warning',
1255
				'content' => __( 'No published items found', 'invoicing' ),
1256
            ),
1257
            true
1258
        );
1259
        return;
1260
    }
1261
1262
    $extra_items     = esc_attr( getpaid_convert_items_to_string( $items ) );
1263
    $extra_items_key = md5( NONCE_KEY . AUTH_KEY . $extra_items );
1264
    $extra_items     = "<input type='hidden' name='getpaid-form-items' value='$extra_items' />";
1265
    $extra_items    .= "<input type='hidden' name='getpaid-form-items-key' value='$extra_items_key' />";
1266
1267
    $form->display( $extra_items );
1268
}
1269
1270
/**
1271
 * Helper function to display an invoice payment form on the frontend.
1272
 */
1273
function getpaid_display_invoice_payment_form( $invoice_id ) {
1274
1275
    $invoice = wpinv_get_invoice( $invoice_id );
1276
1277
    if ( empty( $invoice ) ) {
1278
		aui()->alert(
1279
			array(
1280
				'type'    => 'warning',
1281
				'content' => __( 'Invoice not found', 'invoicing' ),
1282
            ),
1283
            true
1284
        );
1285
        return;
1286
    }
1287
1288
    if ( $invoice->is_paid() ) {
1289
		aui()->alert(
1290
			array(
1291
				'type'    => 'warning',
1292
				'content' => __( 'Invoice has already been paid', 'invoicing' ),
1293
            ),
1294
            true
1295
        );
1296
        return;
1297
    }
1298
1299
    $form = new GetPaid_Payment_Form( wpinv_get_default_payment_form() );
1300
    $form->set_items( $invoice->get_items() );
1301
1302
    $form->display();
1303
}
1304
1305
/**
1306
 * Helper function to convert item string to array.
1307
 */
1308
function getpaid_convert_items_to_array( $items ) {
1309
    $items    = array_filter( array_map( 'trim', explode( ',', $items ) ) );
1310
    $prepared = array();
1311
1312
    foreach ( $items as $item ) {
1313
        $data = array_map( 'trim', explode( '|', $item ) );
1314
1315
        if ( empty( $data[0] ) || ! is_numeric( $data[0] ) ) {
1316
            continue;
1317
        }
1318
1319
        $quantity = 1;
1320
        if ( isset( $data[1] ) && is_numeric( $data[1] ) ) {
1321
            $quantity = (float) $data[1];
1322
        }
1323
1324
        // WPML support.
1325
        $prepared[ wpinv_translate_post_id( $data[0] ) ] = $quantity;
1326
1327
    }
1328
1329
    return $prepared;
1330
}
1331
1332
/**
1333
 * Helper function to convert item array to string.
1334
 */
1335
function getpaid_convert_items_to_string( $items ) {
1336
    $prepared = array();
1337
1338
    foreach ( $items as $item => $quantity ) {
1339
        $prepared[] = "$item|$quantity";
1340
    }
1341
    return implode( ',', $prepared );
1342
}
1343
1344
/**
1345
 * Helper function to display a payment item.
1346
 *
1347
 * Provide a label and one of $form, $items or $invoice.
1348
 */
1349
function getpaid_get_payment_button( $label, $form = null, $items = null, $invoice = null ) {
1350
    $label = sanitize_text_field( $label );
1351
1352
    if ( ! empty( $form ) ) {
1353
        $form  = esc_attr( $form );
1354
        return "<button class='btn btn-primary getpaid-payment-button' type='button' data-form='$form'>$label</button>";
1355
    }
1356
1357
	if ( ! empty( $items ) ) {
1358
        $items  = esc_attr( $items );
1359
        return "<button class='btn btn-primary getpaid-payment-button' type='button' data-item='$items'>$label</button>";
1360
    }
1361
1362
    if ( ! empty( $invoice ) ) {
1363
        $invoice  = esc_attr( $invoice );
1364
        return "<button class='btn btn-primary getpaid-payment-button' type='button' data-invoice='$invoice'>$label</button>";
1365
    }
1366
1367
}
1368
1369
/**
1370
 * Display invoice description before line items.
1371
 *
1372
 * @param WPInv_Invoice $invoice
1373
 */
1374
function getpaid_the_invoice_description( $invoice ) {
1375
    $description = $invoice->get_description();
1376
1377
    if ( empty( $description ) ) {
1378
        return;
1379
    }
1380
1381
    echo "<small class='getpaid-invoice-description text-dark pl-2 ps-2 form-text' style='margin-bottom:20px;border-left:2px solid #2196F3;display:block;padding-left:.5rem'><em>" . wp_kses_post( wpautop( $description ) ) . "</em></small>";
1382
}
1383
add_action( 'getpaid_invoice_line_items', 'getpaid_the_invoice_description', 100 );
1384
add_action( 'wpinv_email_billing_details', 'getpaid_the_invoice_description', 100 );
1385
1386
/**
1387
 * Render element on a form.
1388
 *
1389
 * @param array $element
1390
 * @param GetPaid_Payment_Form $form
1391
 */
1392
function getpaid_payment_form_element( $element, $form ) {
1393
    $translatable = array( 'text', 'label', 'input_label', 'button_label', 'description' );
1394
1395
    foreach ( $translatable as $string ) {
1396
        if ( ! empty( $element[ $string ] ) && is_scalar( $element[ $string ] ) ) {
1397
            $element[ $string ] = __( $element[ $string ], 'invoicing' );
1398
        }
1399
    }
1400
1401
    // Set up the args.
1402
    $element_type    = trim( $element['type'] );
1403
    $element['form'] = $form;
1404
    extract( $element ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
1405
1406
    // Try to locate the appropriate template.
1407
    $located = wpinv_locate_template( "payment-forms/elements/$element_type.php" );
1408
1409
    // Abort if this is not our element.
1410
    if ( empty( $located ) || ! file_exists( $located ) ) {
1411
        return;
1412
    }
1413
1414
    // Generate the class and id of the element.
1415
    $wrapper_class = 'getpaid-payment-form-element-' . trim( esc_attr( $element_type ) );
1416
    $id            = isset( $id ) ? $id : uniqid( 'gp' );
1417
1418
    $element_id    = ! empty( $element['label'] ) ? sanitize_title( $element['label'] ) : $id;
1419
    $query_value   = isset( $_GET[ $element_id ] ) ? wpinv_clean( urldecode_deep( $_GET[ $element_id ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1420
1421
    $element_id    = 'getpaid-' . $element_id;
1422
    if ( ! empty( $GLOBALS['rendered_getpaid_forms'][ $form->get_id() ] ) ) {
1423
        $element_id = $element_id . '-' . $GLOBALS['rendered_getpaid_forms'][ $form->get_id() ];
1424
    }
1425
1426
    // Echo the opening wrapper.
1427
    echo "<div class='getpaid-payment-form-element " . esc_attr( $wrapper_class ) . "'>";
1428
1429
    // Fires before displaying a given element type's content.
1430
    do_action( "getpaid_before_payment_form_{$element_type}_element", $element, $form );
1431
1432
    // Include the template for the element.
1433
    include $located;
1434
1435
    // Fires after displaying a given element type's content.
1436
    do_action( "getpaid_payment_form_{$element_type}_element", $element, $form );
1437
1438
    // Echo the closing wrapper.
1439
    echo '</div>';
1440
}
1441
add_action( 'getpaid_payment_form_element', 'getpaid_payment_form_element', 10, 2 );
1442
1443
/**
1444
 * Render an element's edit page.
1445
 *
1446
 * @param WP_Post $post
1447
 */
1448
function getpaid_payment_form_edit_element_template( $post ) {
1449
1450
    // Retrieve all elements.
1451
    $all_elements = wp_list_pluck( wpinv_get_data( 'payment-form-elements' ), 'type' );
1452
1453
    foreach ( $all_elements as $element ) {
1454
1455
        // Try to locate the appropriate template.
1456
        $element = esc_attr( sanitize_key( $element ) );
1457
        $located = wpinv_locate_template( "payment-forms-admin/edit/$element.php" );
1458
1459
        // Continue if this is not our element.
1460
        if ( empty( $located ) || ! file_exists( $located ) ) {
1461
            continue;
1462
        }
1463
1464
        // Include the template for the element.
1465
        echo "<div v-if=\"active_form_element.type=='" . esc_attr( $element ) . "'\">";
1466
        include $located;
1467
        echo '</div>';
1468
    }
1469
1470
}
1471
add_action( 'getpaid_payment_form_edit_element_template', 'getpaid_payment_form_edit_element_template' );
1472
1473
/**
1474
 * Render an element's preview.
1475
 *
1476
 */
1477
function getpaid_payment_form_render_element_preview_template() {
1478
1479
    // Retrieve all elements.
1480
    $all_elements = wp_list_pluck( wpinv_get_data( 'payment-form-elements' ), 'type' );
1481
1482
    foreach ( $all_elements as $element ) {
1483
1484
        // Try to locate the appropriate template.
1485
        $element = sanitize_key( $element );
1486
        $located = wpinv_locate_template( "payment-forms-admin/previews/$element.php" );
1487
1488
        // Continue if this is not our element.
1489
        if ( empty( $located ) || ! file_exists( $located ) ) {
1490
            continue;
1491
        }
1492
1493
        // Include the template for the element.
1494
        echo "<div v-if=\"form_element.type=='" . esc_html( $element ) . "'\">";
1495
        include $located;
1496
        echo '</div>';
1497
    }
1498
1499
}
1500
add_action( 'wpinv_payment_form_render_element_template', 'getpaid_payment_form_render_element_preview_template' );
1501
1502
/**
1503
 * Shows a list of gateways that support recurring payments.
1504
 */
1505
function wpinv_get_recurring_gateways_text() {
1506
    $gateways = array();
1507
1508
    foreach ( wpinv_get_payment_gateways() as $key => $gateway ) {
1509
        if ( wpinv_gateway_support_subscription( $key ) ) {
1510
            $gateways[] = sanitize_text_field( $gateway['admin_label'] );
1511
        }
1512
    }
1513
1514
    if ( empty( $gateways ) ) {
1515
        return "<span class='form-text text-danger'>" . __( 'No active gateways support subscription payments.', 'invoicing' ) . '</span>';
1516
    }
1517
1518
    return "<span class='form-text text-muted'>" . wp_sprintf( __( 'Subscription payments only supported by: %s', 'invoicing' ), implode( ', ', $gateways ) ) . '</span>';
1519
1520
}
1521
1522
/**
1523
 * Returns the template.
1524
 *
1525
 * @return GetPaid_Template
1526
 */
1527
function getpaid_template() {
1528
    return getpaid()->get( 'template' );
1529
}
1530
1531
/**
1532
 * Displays pagination links.
1533
 *
1534
 * @param array args
1535
 * @return string
1536
 */
1537
function getpaid_paginate_links( $args ) {
1538
    return str_replace( 'page-link dots', 'page-link text-dark', aui()->pagination( $args ) );
1539
}
1540
1541
/**
1542
 * Displays the states select markup.
1543
 *
1544
 * @param string country
1545
 * @param string state
1546
 * @return string
1547
 */
1548
function getpaid_get_states_select_markup( $country, $state, $placeholder, $label, $help_text, $required = false, $wrapper_class = 'col-12', $field_name = 'wpinv_state', $echo = false ) {
1549
1550
    $states = wpinv_get_country_states( $country );
1551
    $uniqid = uniqid( '_' );
1552
1553
    if ( ! empty( $states ) ) {
1554
1555
        return aui()->select(
1556
            array(
1557
				'options'          => $states,
1558
				'name'             => esc_attr( $field_name ),
1559
				'id'               => sanitize_html_class( $field_name ) . $uniqid,
1560
				'value'            => sanitize_text_field( $state ),
1561
				'placeholder'      => $placeholder,
1562
				'required'         => $required,
1563
				'label'            => wp_kses_post( $label ),
1564
				'label_type'       => 'vertical',
1565
				'help_text'        => $help_text,
1566
				'class'            => 'getpaid-address-field wpinv_state',
1567
				'wrap_class'       => "$wrapper_class getpaid-address-field-wrapper__state",
1568
				'label_class'      => 'getpaid-address-field-label getpaid-address-field-label__state',
1569
				'extra_attributes' => array(
1570
					'autocomplete' => 'address-level1',
1571
				),
1572
            ),
1573
            $echo
1574
        );
1575
1576
    }
1577
1578
    return aui()->input(
1579
        array(
1580
            'name'             => esc_attr( $field_name ),
1581
            'id'               => sanitize_html_class( $field_name ) . $uniqid,
1582
            'placeholder'      => $placeholder,
1583
            'required'         => $required,
1584
            'label'            => wp_kses_post( $label ),
1585
            'label_type'       => 'vertical',
1586
            'help_text'        => $help_text,
1587
            'value'            => sanitize_text_field( $state ),
1588
            'class'            => 'getpaid-address-field wpinv_state',
1589
            'wrap_class'       => "$wrapper_class getpaid-address-field-wrapper__state",
1590
            'label_class'      => 'getpaid-address-field-label getpaid-address-field-label__state',
1591
            'extra_attributes' => array(
1592
                'autocomplete' => 'address-level1',
1593
            ),
1594
        ),
1595
        $echo
1596
    );
1597
1598
}
1599
1600
/**
1601
 * Retrieves an element's grid width.
1602
 *
1603
 * @param array $element
1604
 * @return string
1605
 */
1606
function getpaid_get_form_element_grid_class( $element ) {
1607
1608
    $class = 'col-12';
1609
    $width = empty( $element['grid_width'] ) ? 'full' : $element['grid_width'];
1610
1611
    if ( $width == 'half' ) {
1612
        $class .= ' col-md-6';
1613
    }
1614
1615
    if ( $width == 'third' ) {
1616
        $class .= ' col-md-4';
1617
    }
1618
1619
    return $class;
1620
}
1621
1622
/**
1623
 * Retrieves the payment form embed URL.
1624
 *
1625
 * @param int $payment_form payment form.
1626
 * @param string $items form items.
1627
 *
1628
 * @return string
1629
 */
1630
function getpaid_embed_url( $payment_form = false, $items = false ) {
1631
1632
    return add_query_arg(
1633
        array(
1634
            'getpaid_embed' => 1,
1635
            'form'          => $payment_form ? absint( $payment_form ) : false,
1636
            'item'          => $items ? urlencode( $items ) : false,
1637
        ),
1638
        home_url( 'index.php' )
1639
    );
1640
1641
}
1642
1643
/**
1644
 * Embeds a payment form.
1645
 *
1646
 * @return string
1647
 */
1648
function getpaid_filter_embed_template( $template ) {
1649
1650
    if ( isset( $_GET['getpaid_embed'] ) ) {
1651
        wpinv_get_template( 'payment-forms/embed.php' );
1652
        exit;
1653
    }
1654
1655
    return $template;
1656
}
1657
add_filter( 'template_include', 'getpaid_filter_embed_template' );
1658
1659
/**
1660
 * Get the payment forms custom fields.
1661
 *
1662
 * @since 2.8.23
1663
 *
1664
 * @return array Array of custom fields.
1665
 */
1666
function getpaid_get_payment_form_custom_fields() {
1667
	global $wpdb, $payment_form_meta_fields;
1668
1669
	if ( ! empty( $payment_form_meta_fields ) ) {
1670
		return $payment_form_meta_fields;
1671
	}
1672
1673
	$results = $wpdb->get_results( "SELECT `pm`.`meta_value` FROM `{$wpdb->postmeta}` AS pm LEFT JOIN `{$wpdb->posts}` AS p ON p.ID = pm.post_id WHERE `pm`.`meta_key` = 'wpinv_form_elements' AND `p`.`post_type` = 'wpi_payment_form'" );
1674
1675
	$meta_fields = array();
1676
1677
	if ( ! empty( $results ) ) {
1678
		foreach ( $results as $row ) {
1679
			$fields = maybe_unserialize( $row->meta_value );
1680
1681
			if ( ! empty( $fields ) && is_array( $fields ) ) {
1682
				foreach ( $fields as $field ) {
1683
					$label = ! empty( $field['add_meta'] ) && ! empty( $field['label'] ) ? wpinv_clean( wp_unslash( $field['label'] ) ) : '';
1684
1685
					if ( $label ) {
1686
						$field_key = '_' . str_replace( array( ' ', "'", '"', ',' ), array( '_', '', '', '_' ), getpaid_strtolower( $label ) );
1687
						$meta_fields[ $field_key ] = $label;
1688
					}
1689
				}
1690
			}
1691
		}
1692
	}
1693
1694
	$payment_form_meta_fields = $meta_fields;
1695
1696
	return $meta_fields;
1697
}
1698