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.

class-getpaid-invoice-notification-emails.php (7 issues)

Labels
1
<?php
2
/**
3
 * Contains the invoice notification emails management class.
4
 *
5
 */
6
7
defined( 'ABSPATH' ) || exit;
8
9
/**
10
 * This class handles invoice notificaiton emails.
11
 *
12
 */
13
class GetPaid_Invoice_Notification_Emails {
14
15
	/**
16
	 * The array of invoice email actions.
17
	 *
18
	 * @param array
19
	 */
20
	public $invoice_actions;
21
22
	/**
23
	 * Class constructor
24
	 *
25
	 */
26
	public function __construct() {
27
28
		$this->invoice_actions = apply_filters(
29
			'getpaid_notification_email_invoice_triggers',
30
			array(
31
				'getpaid_new_invoice'                   => array( 'new_invoice', 'user_invoice' ),
32
				'getpaid_invoice_status_wpi-cancelled'  => 'cancelled_invoice',
33
				'getpaid_invoice_status_wpi-failed'     => 'failed_invoice',
34
				'getpaid_invoice_status_wpi-onhold'     => 'onhold_invoice',
35
				'getpaid_invoice_status_wpi-processing' => 'processing_invoice',
36
				'getpaid_invoice_status_publish'        => 'completed_invoice',
37
				'getpaid_invoice_status_wpi-renewal'    => 'completed_invoice',
38
				'getpaid_invoice_status_wpi-refunded'   => 'refunded_invoice',
39
				'getpaid_new_customer_note'             => 'user_note',
40
				'getpaid_daily_maintenance'             => 'overdue',
41
			)
42
		);
43
44
		add_action( 'init', array( $this, 'init_hooks' ) );
45
	}
46
47
	/**
48
	 * Registers email hooks.
49
	 */
50
	public function init_hooks() {
51
52
		add_filter( 'getpaid_get_email_merge_tags', array( $this, 'invoice_merge_tags' ), 10, 2 );
53
		add_filter( 'getpaid_invoice_email_recipients', array( $this, 'filter_email_recipients' ), 10, 2 );
54
55
		foreach ( $this->invoice_actions as $hook => $email_type ) {
56
			$this->init_email_type_hook( $hook, $email_type );
57
		}
58
	}
59
60
	/**
61
	 * Registers an email hook for an invoice action.
62
	 *
63
	 * @param string $hook
64
	 * @param string|array $email_type
65
	 */
66
	public function init_email_type_hook( $hook, $email_type ) {
67
68
		$email_type = wpinv_parse_list( $email_type );
69
70
		foreach ( $email_type as $type ) {
71
72
			$email = new GetPaid_Notification_Email( $type );
73
74
			// Abort if it is not active.
75
			if ( ! $email->is_active() ) {
76
				continue;
77
			}
78
79
			if ( method_exists( $this, $type ) ) {
80
				add_action( $hook, array( $this, $type ), 100, 2 );
81
				continue;
82
			}
83
84
			do_action( 'getpaid_invoice_init_email_type_hook', $type, $hook );
85
		}
86
87
	}
88
89
	/**
90
	 * Filters invoice merge tags.
91
	 *
92
	 * @param array $merge_tags
93
	 * @param mixed|WPInv_Invoice|WPInv_Subscription $object
94
	 */
95
	public function invoice_merge_tags( $merge_tags, $object ) {
96
97
		if ( is_a( $object, 'WPInv_Invoice' ) ) {
98
			return array_merge(
99
				$merge_tags,
100
				$this->get_invoice_merge_tags( $object )
101
			);
102
		}
103
104
		if ( is_a( $object, 'WPInv_Subscription' ) ) {
105
			return array_merge(
106
				$merge_tags,
107
				$this->get_invoice_merge_tags( $object->get_parent_payment() )
108
			);
109
		}
110
111
		return $merge_tags;
112
113
	}
114
115
	/**
116
	 * Generates invoice merge tags.
117
	 *
118
	 * @param WPInv_Invoice $invoice
119
	 * @return array
120
	 */
121
	public function get_invoice_merge_tags( $invoice ) {
122
123
		// Abort if it does not exist.
124
		if ( ! $invoice->get_id() ) {
125
			return array();
126
		}
127
128
		$due_date   = $invoice->get_due_date();
129
		$due_date   = empty( $due_date ) ? time() + MINUTE_IN_SECONDS : strtotime( $due_date ) + ( (int) get_option( 'gmt_offset' ) * HOUR_IN_SECONDS );
130
		$merge_tags = array(
131
			'{name}'                 => sanitize_text_field( $invoice->get_user_full_name() ),
132
			'{full_name}'            => sanitize_text_field( $invoice->get_user_full_name() ),
133
			'{first_name}'           => sanitize_text_field( $invoice->get_first_name() ),
134
			'{last_name}'            => sanitize_text_field( $invoice->get_last_name() ),
135
			'{email}'                => sanitize_email( $invoice->get_email() ),
136
			'{invoice_number}'       => sanitize_text_field( $invoice->get_number() ),
137
			'{invoice_currency}'     => sanitize_text_field( $invoice->get_currency() ),
138
			'{invoice_total}'        => sanitize_text_field( wpinv_price( $invoice->get_total(), $invoice->get_currency() ) ),
139
			'{invoice_link}'         => esc_url( $invoice->get_view_url() ),
140
			'{invoice_pay_link}'     => esc_url( $invoice->get_checkout_payment_url() ),
141
			'{invoice_receipt_link}' => esc_url( $invoice->get_receipt_url() ),
142
			'{invoice_date}'         => getpaid_format_date_value( $invoice->get_date_created() ),
143
			'{invoice_due_date}'     => getpaid_format_date_value( $invoice->get_due_date(), __( 'on receipt', 'invoicing' ) ),
144
			'{invoice_quote}'        => sanitize_text_field( strtolower( $invoice->get_label() ) ),
145
			'{invoice_label}'        => sanitize_text_field( ucfirst( $invoice->get_label() ) ),
146
			'{invoice_description}'  => wp_kses_post( $invoice->get_description() ),
147
			'{subscription_name}'    => wp_kses_post( $invoice->get_subscription_name() ),
148
			'{is_was}'               => $due_date < time() ? __( 'was', 'invoicing' ) : __( 'is', 'invoicing' ),
149
		);
150
151
		$payment_form_data = $invoice->get_meta( 'payment_form_data', true );
152
153
		if ( is_array( $payment_form_data ) ) {
154
155
			foreach ( $payment_form_data as $label => $value ) {
156
157
				$label = preg_replace( '/[^a-z0-9]+/', '_', strtolower( $label ) );
158
				$value = is_array( $value ) ? implode( ', ', $value ) : $value;
159
160
				if ( is_scalar( $value ) ) {
161
					$merge_tags[ "{{$label}}" ] = wp_kses_post( $value );
162
				}
163
			}
164
		}
165
166
		return apply_filters( 'getpaid_invoice_email_merge_tags', $merge_tags, $invoice );
167
	}
168
169
	/**
170
	 * Helper function to send an email.
171
	 *
172
	 * @param WPInv_Invoice $invoice
173
	 * @param GetPaid_Notification_Email $email
174
	 * @param string $type
175
	 * @param string|array $recipients
176
	 * @param array $extra_args Extra template args.
177
	 */
178
	public function send_email( $invoice, $email, $type, $recipients, $extra_args = array() ) {
179
180
		do_action( 'getpaid_before_send_invoice_notification', $type, $invoice, $email );
181
182
		$skip = $invoice->is_free() && wpinv_get_option( 'skip_email_free_invoice' );
183
		if ( apply_filters( 'getpaid_skip_invoice_email', $skip, $type, $invoice ) ) {
184
			return;
185
		}
186
187
		$mailer     = new GetPaid_Notification_Email_Sender();
188
		$merge_tags = $email->get_merge_tags();
189
190
		$result = $mailer->send(
191
			apply_filters( 'getpaid_invoice_email_recipients', wpinv_parse_list( $recipients ), $email ),
192
			$email->add_merge_tags( $email->get_subject(), $merge_tags ),
193
			$email->get_content( $merge_tags, $extra_args ),
194
			$email->get_attachments()
195
		);
196
197
		// Maybe send a copy to the admin.
198
		if ( $email->include_admin_bcc() ) {
199
			$mailer->send(
200
				wpinv_get_admin_email(),
0 ignored issues
show
It seems like wpinv_get_admin_email() can also be of type false; however, parameter $to of GetPaid_Notification_Email_Sender::send() does only seem to accept array|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

200
				/** @scrutinizer ignore-type */ wpinv_get_admin_email(),
Loading history...
201
				$email->add_merge_tags( $email->get_subject() . __( ' - ADMIN BCC COPY', 'invoicing' ), $merge_tags ),
202
				$email->get_content( $merge_tags ),
203
				$email->get_attachments()
204
			);
205
		}
206
207
		if ( $result ) {
208
			$invoice->add_system_note(
209
				sprintf(
210
					// translators: %1 is the email type, %2 is the invoice recipient.
211
					__( 'Successfully sent %1$s notification email to %2$s.', 'invoicing' ),
212
					sanitize_key( $type ),
213
					$email->is_admin_email() ? __( 'admin', 'invoicing' ) : __( 'the customer', 'invoicing' )
214
				)
215
			);
216
		} else {
217
			$invoice->add_system_note(
218
				sprintf(
219
					// translators: %1 is the email type, %2 is the invoice recipient.
220
					__( 'Failed sending %1$s notification email to %2$s.', 'invoicing' ),
221
					sanitize_key( $type ),
222
					$email->is_admin_email() ? __( 'admin', 'invoicing' ) : __( 'the customer', 'invoicing' )
223
				)
224
			);
225
		}
226
227
		do_action( 'getpaid_after_send_invoice_notification', $type, $invoice, $email );
228
229
		return $result;
230
	}
231
232
	/**
233
	 * Also send emails to any cc users.
234
	 *
235
	 * @param array $recipients
236
	 * @param GetPaid_Notification_Email $email
237
	 */
238
	public function filter_email_recipients( $recipients, $email ) {
239
240
		if ( ! $email->is_admin_email() ) {
241
			$cc   = $email->object->get_email_cc();
0 ignored issues
show
The method get_email_cc() does not exist on WPInv_Subscription. ( Ignorable by Annotation )

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

241
			/** @scrutinizer ignore-call */ 
242
   $cc   = $email->object->get_email_cc();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
The method get_email_cc() does not exist on WPInv_Item. ( Ignorable by Annotation )

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

241
			/** @scrutinizer ignore-call */ 
242
   $cc   = $email->object->get_email_cc();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
242
			$cc_2 = get_user_meta( $email->object->get_user_id(), '_wpinv_email_cc', true );
0 ignored issues
show
The method get_user_id() does not exist on WPInv_Item. ( Ignorable by Annotation )

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

242
			$cc_2 = get_user_meta( $email->object->/** @scrutinizer ignore-call */ get_user_id(), '_wpinv_email_cc', true );

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
The method get_user_id() does not exist on WPInv_Subscription. Did you maybe mean get_customer_id()? ( Ignorable by Annotation )

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

242
			$cc_2 = get_user_meta( $email->object->/** @scrutinizer ignore-call */ get_user_id(), '_wpinv_email_cc', true );

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
243
244
			if ( ! empty( $cc ) ) {
245
				$cc = array_map( 'sanitize_email', wpinv_parse_list( $cc ) );
246
				$recipients = array_filter( array_unique( array_merge( $recipients, $cc ) ) );
247
			}
248
249
			if ( ! empty( $cc_2 ) ) {
250
				$cc_2 = array_map( 'sanitize_email', wpinv_parse_list( $cc_2 ) );
251
				$recipients = array_filter( array_unique( array_merge( $recipients, $cc_2 ) ) );
252
			}
253
		}
254
255
		return $recipients;
256
257
	}
258
259
	/**
260
	 * Sends a new invoice notification.
261
	 *
262
	 * @param WPInv_Invoice $invoice
263
	 */
264
	public function new_invoice( $invoice ) {
265
266
		// Only send this email for invoices created via the admin page.
267
		if ( ! $invoice->is_type( 'invoice' ) || $invoice->is_paid() || $this->is_payment_form_invoice( $invoice->get_id() ) ) {
268
			return;
269
		}
270
271
		$email     = new GetPaid_Notification_Email( __FUNCTION__, $invoice );
272
		$recipient = wpinv_get_admin_email();
273
274
		return $this->send_email( $invoice, $email, __FUNCTION__, $recipient );
0 ignored issues
show
It seems like $recipient can also be of type false; however, parameter $recipients of GetPaid_Invoice_Notification_Emails::send_email() does only seem to accept array|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

274
		return $this->send_email( $invoice, $email, __FUNCTION__, /** @scrutinizer ignore-type */ $recipient );
Loading history...
275
276
	}
277
278
	/**
279
	 * Sends a cancelled invoice notification.
280
	 *
281
	 * @param WPInv_Invoice $invoice
282
	 */
283
	public function cancelled_invoice( $invoice ) {
284
285
		$email     = new GetPaid_Notification_Email( __FUNCTION__, $invoice );
286
		$recipient = $invoice->get_email();
287
288
		return $this->send_email( $invoice, $email, __FUNCTION__, $recipient );
289
	}
290
291
	/**
292
	 * Sends a failed invoice notification.
293
	 *
294
	 * @param WPInv_Invoice $invoice
295
	 */
296
	public function failed_invoice( $invoice ) {
297
298
		$email     = new GetPaid_Notification_Email( __FUNCTION__, $invoice );
299
		$recipient = wpinv_get_admin_email();
300
301
		return $this->send_email( $invoice, $email, __FUNCTION__, $recipient );
0 ignored issues
show
It seems like $recipient can also be of type false; however, parameter $recipients of GetPaid_Invoice_Notification_Emails::send_email() does only seem to accept array|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

301
		return $this->send_email( $invoice, $email, __FUNCTION__, /** @scrutinizer ignore-type */ $recipient );
Loading history...
302
303
	}
304
305
	/**
306
	 * Sends a notification whenever an invoice is put on hold.
307
	 *
308
	 * @param WPInv_Invoice $invoice
309
	 */
310
	public function onhold_invoice( $invoice ) {
311
312
		$email     = new GetPaid_Notification_Email( __FUNCTION__, $invoice );
313
		$recipient = $invoice->get_email();
314
315
		return $this->send_email( $invoice, $email, __FUNCTION__, $recipient );
316
317
	}
318
319
	/**
320
	 * Sends a notification whenever an invoice is marked as processing payment.
321
	 *
322
	 * @param WPInv_Invoice $invoice
323
	 */
324
	public function processing_invoice( $invoice ) {
325
326
		$email     = new GetPaid_Notification_Email( __FUNCTION__, $invoice );
327
		$recipient = $invoice->get_email();
328
329
		return $this->send_email( $invoice, $email, __FUNCTION__, $recipient );
330
331
	}
332
333
	/**
334
	 * Sends a notification whenever an invoice is paid.
335
	 *
336
	 * @param WPInv_Invoice $invoice
337
	 */
338
	public function completed_invoice( $invoice ) {
339
340
		// (Maybe) abort if it is a renewal invoice.
341
		if ( $invoice->is_renewal() && ! wpinv_get_option( 'email_completed_invoice_renewal_active', false ) ) {
342
			return;
343
		}
344
345
		$email     = new GetPaid_Notification_Email( __FUNCTION__, $invoice );
346
		$recipient = $invoice->get_email();
347
348
		return $this->send_email( $invoice, $email, __FUNCTION__, $recipient );
349
350
	}
351
352
	/**
353
	 * Sends a notification whenever an invoice is refunded.
354
	 *
355
	 * @param WPInv_Invoice $invoice
356
	 */
357
	public function refunded_invoice( $invoice ) {
358
359
		$email     = new GetPaid_Notification_Email( __FUNCTION__, $invoice );
360
		$recipient = $invoice->get_email();
361
362
		return $this->send_email( $invoice, $email, __FUNCTION__, $recipient );
363
364
	}
365
366
	/**
367
	 * Notifies a user about new invoices
368
	 *
369
	 * @param WPInv_Invoice $invoice
370
	 * @param bool $force
371
	 */
372
	public function user_invoice( $invoice, $force = false ) {
373
374
		if ( ! $force && ! empty( $GLOBALS['wpinv_skip_invoice_notification'] ) ) {
375
			return;
376
		}
377
378
		// Only send this email for invoices created via the admin page.
379
		if ( ! $invoice->is_type( 'invoice' ) || ( empty( $force ) && $invoice->is_paid() ) || ( empty( $force ) && $this->is_payment_form_invoice( $invoice->get_id() ) ) ) {
380
			return;
381
		}
382
383
		$email     = new GetPaid_Notification_Email( __FUNCTION__, $invoice );
384
		$recipient = $invoice->get_email();
385
386
		return $this->send_email( $invoice, $email, __FUNCTION__, $recipient );
387
388
	}
389
390
	/**
391
	 * Checks if an invoice is a payment form invoice.
392
	 *
393
	 * @param int $invoice
394
	 * @return bool
395
	 */
396
	public function is_payment_form_invoice( $invoice ) {
397
		$created_via             = get_post_meta( $invoice, 'wpinv_created_via', true );
398
		$is_payment_form_invoice = 'payment_form' === $created_via || 'geodirectory' === $created_via;
399
		$is_payment_form_invoice = apply_filters( 'getpaid_invoice_notifications_is_payment_form_invoice', $is_payment_form_invoice, $invoice );
400
		return empty( $_GET['getpaid-admin-action'] ) && $is_payment_form_invoice;
401
	}
402
403
	/**
404
	 * Notifies admin about new invoice notes
405
	 *
406
	 * @param WPInv_Invoice $invoice
407
	 * @param string $note
408
	 */
409
	public function user_note( $invoice, $note ) {
410
411
		$email     = new GetPaid_Notification_Email( __FUNCTION__, $invoice );
412
		$recipient = $invoice->get_email();
413
414
		return $this->send_email( $invoice, $email, __FUNCTION__, $recipient, array( 'customer_note' => $note ) );
415
416
	}
417
418
	/**
419
	 * (Force) Sends overdue notices.
420
	 *
421
	 * @param WPInv_Invoice $invoice
422
	 */
423
	public function force_send_overdue_notice( $invoice ) {
424
		$email = new GetPaid_Notification_Email( 'overdue', $invoice );
425
		return $this->send_email( $invoice, $email, 'overdue', $invoice->get_email() );
426
	}
427
428
	/**
429
	 * Sends overdue notices.
430
	 *
431
	 * @TODO: Create an invoices query class.
432
	 */
433
	public function overdue() {
434
		global $wpdb;
435
436
		$email = new GetPaid_Notification_Email( __FUNCTION__ );
437
438
		// Fetch reminder days.
439
		$reminder_days = array_unique( wp_parse_id_list( $email->get_option( 'days' ) ) );
440
441
		// Abort if non is set.
442
		if ( empty( $reminder_days ) ) {
443
			return;
444
		}
445
446
		// Retrieve date query.
447
		$date_query = $this->get_date_query( $reminder_days );
448
449
		// Invoices table.
450
		$table = $wpdb->prefix . 'getpaid_invoices';
451
452
		// Fetch invoices.
453
		$invoices  = $wpdb->get_col(
454
			"SELECT posts.ID FROM $wpdb->posts as posts
455
			LEFT JOIN $table as invoices ON invoices.post_id = posts.ID
456
			WHERE posts.post_type = 'wpi_invoice' AND posts.post_status = 'wpi-pending' $date_query"
457
        );
458
459
		foreach ( $invoices as $invoice ) {
460
461
			// Only send this email for invoices created via the admin page.
462
			if ( ! $this->is_payment_form_invoice( $invoice ) ) {
463
				$invoice       = new WPInv_Invoice( $invoice );
464
				$email->object = $invoice;
465
466
				if ( $invoice->needs_payment() && ! $invoice->is_renewal() ) {
467
					$this->send_email( $invoice, $email, __FUNCTION__, $invoice->get_email() );
468
				}
469
			}
470
		}
471
472
	}
473
474
	/**
475
	 * Calculates the date query for an invoices query
476
	 *
477
	 * @param array $reminder_days
478
	 * @return string
479
	 */
480
	public function get_date_query( $reminder_days ) {
481
482
		$date_query = array(
483
			'relation' => 'OR',
484
		);
485
486
		foreach ( $reminder_days as $days ) {
487
			$date = date_parse( date( 'Y-m-d', strtotime( "-$days days", current_time( 'timestamp' ) ) ) );
488
489
			$date_query[] = array(
490
				'year'  => $date['year'],
491
				'month' => $date['month'],
492
				'day'   => $date['day'],
493
			);
494
495
		}
496
497
		$date_query = new WP_Date_Query( $date_query, 'invoices.due_date' );
498
499
		return $date_query->get_sql();
500
501
	}
502
503
}
504