Issues (865)

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/admin/class-getpaid-admin.php (23 issues)

1
<?php
2
/**
3
 * Contains the admin class.
4
 *
5
 */
6
7
defined( 'ABSPATH' ) || exit;
8
9
/**
10
 * The main admin class.
11
 *
12
 * @since       1.0.19
13
 */
14
class GetPaid_Admin {
15
16
    /**
17
	 * Local path to this plugins admin directory
18
	 *
19
	 * @var         string
20
	 */
21
	public $admin_path;
22
23
	/**
24
	 * Web path to this plugins admin directory
25
	 *
26
	 * @var         string
27
	 */
28
	public $admin_url;
29
30
	/**
31
	 * Reports components.
32
	 *
33
	 * @var GetPaid_Reports
34
	 */
35
    public $reports;
36
37
    /**
38
	 * Class constructor.
39
	 */
40
	public function __construct() {
41
42
        $this->admin_path  = plugin_dir_path( __FILE__ );
43
		$this->admin_url   = plugins_url( '/', __FILE__ );
44
		$this->reports     = new GetPaid_Reports();
45
46
        if ( is_admin() ) {
47
			$this->init_admin_hooks();
48
        }
49
50
    }
51
52
    /**
53
	 * Init action and filter hooks
54
	 *
55
	 */
56
	private function init_admin_hooks() {
57
        add_action( 'admin_enqueue_scripts', array( $this, 'enqeue_scripts' ), 9 );
58
        add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) );
59
        add_action( 'admin_init', array( $this, 'init_ayecode_connect_helper' ) );
60
        add_action( 'admin_init', array( $this, 'activation_redirect' ) );
61
        add_action( 'admin_init', array( $this, 'maybe_do_admin_action' ) );
62
		add_action( 'admin_notices', array( $this, 'show_notices' ) );
63
		add_action( 'getpaid_authenticated_admin_action_rate_plugin', array( $this, 'redirect_to_wordpress_rating_page' ) );
64
		add_action( 'getpaid_authenticated_admin_action_duplicate_form', array( $this, 'duplicate_payment_form' ) );
65
		add_action( 'getpaid_authenticated_admin_action_reset_form_stats', array( $this, 'reset_form_stats' ) );
66
		add_action( 'getpaid_authenticated_admin_action_duplicate_invoice', array( $this, 'duplicate_invoice' ) );
67
		add_action( 'getpaid_authenticated_admin_action_refund_invoice', array( $this, 'refund_invoice' ) );
68
		add_action( 'getpaid_authenticated_admin_action_send_invoice', array( $this, 'send_customer_invoice' ) );
69
		add_action( 'getpaid_authenticated_admin_action_send_invoice_reminder', array( $this, 'send_customer_payment_reminder' ) );
70
        add_action( 'getpaid_authenticated_admin_action_reset_tax_rates', array( $this, 'admin_reset_tax_rates' ) );
71
		add_action( 'getpaid_authenticated_admin_action_create_missing_pages', array( $this, 'admin_create_missing_pages' ) );
72
		add_action( 'getpaid_authenticated_admin_action_refresh_permalinks', array( $this, 'admin_refresh_permalinks' ) );
73
		add_action( 'getpaid_authenticated_admin_action_create_missing_tables', array( $this, 'admin_create_missing_tables' ) );
74
		add_action( 'getpaid_authenticated_admin_action_migrate_old_invoices', array( $this, 'admin_migrate_old_invoices' ) );
75
		add_action( 'getpaid_authenticated_admin_action_download_customers', array( $this, 'admin_download_customers' ) );
76
		add_action( 'getpaid_authenticated_admin_action_recalculate_discounts', array( $this, 'admin_recalculate_discounts' ) );
77
		add_action( 'getpaid_authenticated_admin_action_translate_db_texts', array( $this, 'tool_translate_db_texts' ) );
78
		add_action( 'getpaid_authenticated_admin_action_install_plugin', array( $this, 'admin_install_plugin' ) );
79
		add_action( 'getpaid_authenticated_admin_action_connect_gateway', array( $this, 'admin_connect_gateway' ) );
80
		add_filter( 'admin_footer_text', array( $this, 'admin_footer_text' ) );
81
		do_action( 'getpaid_init_admin_hooks', $this );
82
83
		// Setup/welcome
84
		if ( ! empty( $_GET['page'] ) ) {
85
			switch ( sanitize_text_field( $_GET['page'] ) ) {
86
				case 'gp-setup':
87
					include_once dirname( __FILE__ ) . '/class-getpaid-admin-setup-wizard.php';
88
					break;
89
			}
90
		}
91
92
    }
93
94
    /**
95
	 * Register admin scripts
96
	 *
97
	 */
98
	public function enqeue_scripts() {
99
        global $current_screen, $pagenow;
100
101
		$page    = isset( $_GET['page'] ) ? sanitize_text_field( $_GET['page'] ) : '';
102
		$editing = $pagenow == 'post.php' || $pagenow == 'post-new.php';
103
104
        if ( ! empty( $current_screen->post_type ) ) {
105
			$page = $current_screen->post_type;
106
        }
107
108
        // General styles.
109
        if ( false !== stripos( $page, 'wpi' ) || false !== stripos( $page, 'getpaid' ) || 'gp-setup' == $page || false !== stripos( $page, 'geodir-tickets' ) ) {
110
111
            // Styles.
112
            $version = filemtime( WPINV_PLUGIN_DIR . 'assets/css/admin.css' );
113
            wp_enqueue_style( 'wpinv_admin_style', WPINV_PLUGIN_URL . 'assets/css/admin.css', array( 'wp-color-picker' ), $version );
114
            wp_enqueue_style( 'select2', WPINV_PLUGIN_URL . 'assets/css/select2/select2.min.css', array(), '4.0.13', 'all' );
115
116
            // Scripts.
117
            wp_enqueue_script( 'select2', WPINV_PLUGIN_URL . 'assets/js/select2/select2.full.min.js', array( 'jquery' ), WPINV_VERSION );
118
119
            $version = filemtime( WPINV_PLUGIN_DIR . 'assets/js/admin.js' );
120
            wp_enqueue_script( 'wpinv-admin-script', WPINV_PLUGIN_URL . 'assets/js/admin.js', array( 'jquery', 'wp-color-picker', 'jquery-ui-tooltip' ), $version );
121
            wp_localize_script( 'wpinv-admin-script', 'WPInv_Admin', apply_filters( 'wpinv_admin_js_localize', $this->get_admin_i18() ) );
122
123
        }
124
125
        // Payment form scripts.
126
		if ( 'wpi_payment_form' == $page && $editing ) {
127
            $this->load_payment_form_scripts();
128
        }
129
130
		if ( $page == 'wpinv-subscriptions' ) {
131
			wp_enqueue_script( 'postbox' );
132
		}
133
134
    }
135
136
    /**
137
	 * Returns admin js translations.
138
	 *
139
	 */
140
	protected function get_admin_i18() {
141
        global $post;
142
143
		$date_range = array(
144
			'period' => isset( $_GET['date_range'] ) ? sanitize_text_field( $_GET['date_range'] ) : '7_days',
145
		);
146
147
		if ( $date_range['period'] == 'custom' ) {
148
149
			if ( isset( $_GET['from'] ) ) {
150
				$date_range['after'] = date( 'Y-m-d', strtotime( sanitize_text_field( $_GET['from'] ), current_time( 'timestamp' ) ) - DAY_IN_SECONDS );
151
			}
152
153
			if ( isset( $_GET['to'] ) ) {
154
				$date_range['before'] = date( 'Y-m-d', strtotime( sanitize_text_field( $_GET['to'] ), current_time( 'timestamp' ) ) + DAY_IN_SECONDS );
155
			}
156
}
157
158
        $i18n = array(
159
            'ajax_url'                  => admin_url( 'admin-ajax.php' ),
160
            'post_ID'                   => isset( $post->ID ) ? $post->ID : '',
161
			'wpinv_nonce'               => wp_create_nonce( 'wpinv-nonce' ),
162
			'rest_nonce'                => wp_create_nonce( 'wp_rest' ),
163
			'rest_root'                 => esc_url_raw( rest_url() ),
164
			'date_range'                => $date_range,
165
            'add_invoice_note_nonce'    => wp_create_nonce( 'add-invoice-note' ),
166
            'delete_invoice_note_nonce' => wp_create_nonce( 'delete-invoice-note' ),
167
            'invoice_item_nonce'        => wp_create_nonce( 'invoice-item' ),
168
            'billing_details_nonce'     => wp_create_nonce( 'get-billing-details' ),
169
            'tax'                       => wpinv_tax_amount(),
170
            'discount'                  => 0,
171
			'currency_symbol'           => wpinv_currency_symbol(),
172
			'currency'                  => wpinv_get_currency(),
173
            'currency_pos'              => wpinv_currency_position(),
174
            'thousand_sep'              => wpinv_thousands_separator(),
175
            'decimal_sep'               => wpinv_decimal_separator(),
176
            'decimals'                  => wpinv_decimals(),
177
            'save_invoice'              => __( 'Save Invoice', 'invoicing' ),
178
            'status_publish'            => wpinv_status_nicename( 'publish' ),
179
            'status_pending'            => wpinv_status_nicename( 'wpi-pending' ),
180
            'delete_tax_rate'           => __( 'Are you sure you wish to delete this tax rate?', 'invoicing' ),
181
            'status_pending'            => wpinv_status_nicename( 'wpi-pending' ),
182
            'FillBillingDetails'        => __( 'Fill the user\'s billing information? This will remove any currently entered billing information', 'invoicing' ),
183
            'confirmCalcTotals'         => __( 'Recalculate totals? This will recalculate totals based on the user billing country. If no billing country is set it will use the base country.', 'invoicing' ),
184
            'AreYouSure'                => __( 'Are you sure?', 'invoicing' ),
185
            'errDeleteItem'             => __( 'This item is in use! Before delete this item, you need to delete all the invoice(s) using this item.', 'invoicing' ),
186
            'delete_subscription'       => __( 'Are you sure you want to delete this subscription?', 'invoicing' ),
187
            'action_edit'               => __( 'Edit', 'invoicing' ),
188
            'action_cancel'             => __( 'Cancel', 'invoicing' ),
189
            'item_description'          => __( 'Item Description', 'invoicing' ),
190
            'invoice_description'       => __( 'Invoice Description', 'invoicing' ),
191
            'discount_description'      => __( 'Discount Description', 'invoicing' ),
192
			'searching'                 => __( 'Searching', 'invoicing' ),
193
			'loading'                   => __( 'Loading...', 'invoicing' ),
194
			'search_customers'          => __( 'Enter customer name or email', 'invoicing' ),
195
			'search_items'              => __( 'Enter item name', 'invoicing' ),
196
			'graphs'                    => array_merge( array( 'refunded_fees', 'refunded_items', 'refunded_subtotal', 'refunded_tax' ), array_keys( wpinv_get_report_graphs() ) ),
197
        );
198
199
		if ( ! empty( $post ) && getpaid_is_invoice_post_type( $post->post_type ) ) {
200
201
			$invoice              = new WPInv_Invoice( $post );
202
			$i18n['save_invoice'] = sprintf(
203
				__( 'Save %s', 'invoicing' ),
204
				ucfirst( $invoice->get_invoice_quote_type() )
205
			);
206
207
			$i18n['invoice_description'] = sprintf(
208
				__( '%s Description', 'invoicing' ),
209
				ucfirst( $invoice->get_invoice_quote_type() )
210
			);
211
212
		}
213
		return $i18n;
214
	}
215
216
	/**
217
	 * Change the admin footer text on GetPaid admin pages.
218
	 *
219
	 * @since  2.0.0
220
	 * @param  string $footer_text
221
	 * @return string
222
	 */
223
	public function admin_footer_text( $footer_text ) {
224
		global $current_screen;
225
226
		$page    = isset( $_GET['page'] ) ? sanitize_text_field( $_GET['page'] ) : '';
227
228
        if ( ! empty( $current_screen->post_type ) ) {
229
			$page = $current_screen->post_type;
230
        }
231
232
        // General styles.
233
        if ( apply_filters( 'getpaid_display_admin_footer_text', wpinv_current_user_can_manage_invoicing() ) && false !== stripos( $page, 'wpi' ) ) {
234
235
			// Change the footer text
236
			if ( ! get_user_meta( get_current_user_id(), 'getpaid_admin_footer_text_rated', true ) ) {
237
238
				$rating_url  = esc_url(
239
					wp_nonce_url(
240
						admin_url( 'admin.php?page=wpinv-reports&getpaid-admin-action=rate_plugin' ),
241
						'getpaid-nonce',
242
						'getpaid-nonce'
243
                    )
244
				);
245
246
				$footer_text = sprintf(
247
					/* translators: %s: five stars */
248
					__( 'If you like <strong>GetPaid</strong>, please leave us a %s rating. A huge thanks in advance!', 'invoicing' ),
249
					"<a href='$rating_url'>&#9733;&#9733;&#9733;&#9733;&#9733;</a>"
250
				);
251
252
			} else {
253
254
				$footer_text = sprintf(
255
					/* translators: %s: GetPaid */
256
					__( 'Thank you for using %s!', 'invoicing' ),
257
					"<a href='https://wpgetpaid.com/' target='_blank'><strong>GetPaid</strong></a>"
258
				);
259
260
			}
261
}
262
263
		return $footer_text;
264
	}
265
266
	/**
267
	 * Redirects to wp.org to rate the plugin.
268
	 *
269
	 * @since  2.0.0
270
	 */
271
	public function redirect_to_wordpress_rating_page() {
272
		update_user_meta( get_current_user_id(), 'getpaid_admin_footer_text_rated', 1 );
273
		wp_redirect( 'https://wordpress.org/support/plugin/invoicing/reviews?rate=5#new-post' );
274
		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...
275
	}
276
277
    /**
278
	 * Loads payment form js.
279
	 *
280
	 */
281
	protected function load_payment_form_scripts() {
282
        global $post;
283
284
        wp_enqueue_script( 'vue', WPINV_PLUGIN_URL . 'assets/js/vue/vue.min.js', array(), WPINV_VERSION );
285
		wp_enqueue_script( 'sortable', WPINV_PLUGIN_URL . 'assets/js/sortable.min.js', array(), WPINV_VERSION );
286
		wp_enqueue_script( 'vue_draggable', WPINV_PLUGIN_URL . 'assets/js/vue/vuedraggable.min.js', array( 'sortable', 'vue' ), WPINV_VERSION );
287
288
		wp_register_script( 'wpinv-admin-payment-form-script', WPINV_PLUGIN_URL . 'assets/js/admin-payment-forms.min.js', array( 'wpinv-admin-script', 'vue_draggable', 'wp-hooks' ), WPINV_VERSION );
289
290
		wp_localize_script(
291
            'wpinv-admin-payment-form-script',
292
            'wpinvPaymentFormAdmin',
293
            array(
294
				'elements'      => wpinv_get_data( 'payment-form-elements' ),
295
				'form_elements' => getpaid_get_payment_form_elements( $post->ID ),
296
				'currency'      => wpinv_currency_symbol(),
297
				'position'      => wpinv_currency_position(),
298
				'decimals'      => (int) wpinv_decimals(),
299
				'thousands_sep' => wpinv_thousands_separator(),
300
				'decimals_sep'  => wpinv_decimal_separator(),
301
				'form_items'    => gepaid_get_form_items( $post->ID ),
302
				'is_default'    => $post->ID == wpinv_get_default_payment_form(),
303
            )
304
        );
305
306
        wp_enqueue_script( 'wpinv-admin-payment-form-script' );
307
308
    }
309
310
    /**
311
	 * Add our classes to admin pages.
312
     *
313
     * @param string $classes
314
     * @return string
315
	 *
316
	 */
317
    public function admin_body_class( $classes ) {
318
		global $pagenow, $post, $current_screen;
319
320
        $page = isset( $_GET['page'] ) ? sanitize_text_field( $_GET['page'] ) : '';
321
322
        if ( ! empty( $current_screen->post_type ) ) {
323
			$page = $current_screen->post_type;
324
        }
325
326
        if ( false !== stripos( $page, 'wpi' ) ) {
327
            $classes .= ' wpi-' . sanitize_key( $page );
328
        }
329
330
        if ( in_array( $page, wpinv_parse_list( 'wpi_invoice wpi_payment_form wpi_quote' ) ) ) {
331
            $classes .= ' wpinv-cpt wpinv';
332
		}
333
334
		if ( getpaid_is_invoice_post_type( $page ) ) {
335
            $classes .= ' getpaid-is-invoice-cpt';
336
        }
337
338
		return $classes;
339
    }
340
341
    /**
342
	 * Maybe show the AyeCode Connect Notice.
343
	 */
344
	public function init_ayecode_connect_helper() {
345
346
		// Register with the deactivation survey class.
347
		AyeCode_Deactivation_Survey::instance(
348
            array(
349
				'slug'              => 'invoicing',
350
				'version'           => WPINV_VERSION,
351
				'support_url'       => 'https://wpgetpaid.com/support/',
352
				'documentation_url' => 'https://docs.wpgetpaid.com/',
353
				'activated'         => (int) get_option( 'gepaid_installed_on' ),
354
            )
355
        );
356
357
        new AyeCode_Connect_Helper(
358
            array(
359
				'connect_title'     => __( 'WP Invoicing - an AyeCode product!', 'invoicing' ),
360
				'connect_external'  => __( 'Please confirm you wish to connect your site?', 'invoicing' ),
361
				'connect'           => sprintf( __( '<strong>Have a license?</strong> Forget about entering license keys or downloading zip files, connect your site for instant access. %1$slearn more%2$s', 'invoicing' ), "<a href='https://ayecode.io/introducing-ayecode-connect/' target='_blank'>", '</a>' ),
362
				'connect_button'    => __( 'Connect Site', 'invoicing' ),
363
				'connecting_button' => __( 'Connecting...', 'invoicing' ),
364
				'error_localhost'   => __( 'This service will only work with a live domain, not a localhost.', 'invoicing' ),
365
				'error'             => __( 'Something went wrong, please refresh and try again.', 'invoicing' ),
366
            ),
367
            array( 'wpi-addons' )
368
        );
369
370
    }
371
372
	/**
373
	 * Redirect users to settings on activation.
374
	 *
375
	 * @return void
376
	 */
377
	public function activation_redirect() {
378
379
		$redirected = get_option( 'wpinv_redirected_to_settings' );
380
381
		if ( ! empty( $redirected ) || wp_doing_ajax() || ! current_user_can( 'manage_options' ) ) {
382
			return;
383
		}
384
385
		// Bail if activating from network, or bulk
386
		if ( is_network_admin() || isset( $_GET['activate-multi'] ) ) {
387
			return;
388
		}
389
390
	    update_option( 'wpinv_redirected_to_settings', 1 );
391
392
        wp_safe_redirect( admin_url( 'index.php?page=gp-setup' ) );
393
        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...
394
395
	}
396
397
    /**
398
     * Fires an admin action after verifying that a user can fire them.
399
     */
400
    public function maybe_do_admin_action() {
401
        if ( isset( $_REQUEST['getpaid-admin-action'] ) && isset( $_REQUEST['getpaid-nonce'] ) && wp_verify_nonce( $_REQUEST['getpaid-nonce'], 'getpaid-nonce' ) && wpinv_current_user_can( sanitize_text_field( $_REQUEST['getpaid-admin-action'] ), $_REQUEST ) ) {
402
            $key = sanitize_key( $_REQUEST['getpaid-admin-action'] );
403
404
            do_action( "getpaid_authenticated_admin_action_$key", $_REQUEST );
405
        }
406
    }
407
408
	/**
409
     * Duplicate invoice.
410
	 *
411
	 * @param array $args
412
     */
413
    public function duplicate_invoice( $args ) {
414
415
		if ( empty( $args['invoice_id'] ) ) {
416
			return;
417
		}
418
419
		$invoice = new WPInv_Invoice( (int) $args['invoice_id'] );
420
421
		if ( ! $invoice->exists() ) {
422
			return;
423
		}
424
425
		$new_invoice = getpaid_duplicate_invoice( $invoice );
426
		$new_invoice->save();
427
428
		if ( $new_invoice->exists() ) {
429
430
			getpaid_admin()->show_success( __( 'Invoice duplicated successfully.', 'invoicing' ) );
431
432
			wp_safe_redirect(
433
				add_query_arg(
434
					array(
435
						'action' => 'edit',
436
						'post'   => $new_invoice->get_id(),
437
					),
438
					admin_url( 'post.php' )
439
				)
440
			);
441
			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...
442
443
		}
444
445
		getpaid_admin()->show_error( __( 'There was an error duplicating this invoice. Please try again.', 'invoicing' ) );
446
447
	}
448
449
	/**
450
     * Refund an invoice.
451
	 *
452
	 * @param array $args
453
     */
454
    public function refund_invoice( $args ) {
455
456
		if ( empty( $args['invoice_id'] ) ) {
457
			return;
458
		}
459
460
		$invoice = new WPInv_Invoice( (int) $args['invoice_id'] );
461
462
		if ( ! $invoice->exists() || $invoice->is_refunded() ) {
463
			return;
464
		}
465
466
		$invoice->refund();
467
468
		// Refund remotely.
469
		if ( getpaid_payment_gateway_supports( $invoice->get_gateway(), 'refunds' ) && ! empty( $args['getpaid_refund_remote'] ) ) {
470
			do_action( 'getpaid_refund_invoice_remotely', $invoice );
471
		}
472
473
		// Cancel subscriptions.
474
		if ( ! empty( $args['getpaid_cancel_subscription'] ) ) {
475
			$subscriptions = getpaid_get_invoice_subscriptions( $invoice );
476
477
			if ( ! empty( $subscriptions ) ) {
478
				if ( ! is_array( $subscriptions ) ) {
0 ignored issues
show
The condition is_array($subscriptions) is always false.
Loading history...
479
					$subscriptions = array( $subscriptions );
480
				}
481
482
				foreach ( $subscriptions as $subscription ) {
483
					$subscription->cancel();
484
					$invoice->add_system_note(
485
						sprintf(
486
							// translators: %s: subscription ID.
487
							__( 'Subscription #%s cancelled', 'invoicing' ),
488
							$subscription->get_id()
489
						)
490
					);
491
				}
492
			}
493
		}
494
495
		// Add notice.
496
		$this->show_success( __( 'Invoice refunded successfully.', 'invoicing' ) );
497
498
		// Redirect.
499
		wp_safe_redirect(
500
			remove_query_arg( array( 'getpaid-admin-action', 'getpaid-nonce', 'invoice_id', 'getpaid_cancel_subscription', 'getpaid_refund_remote' ) )
501
		);
502
	}
503
504
	/**
505
     * Sends a payment reminder to a customer.
506
	 *
507
	 * @param array $args
508
     */
509
    public function duplicate_payment_form( $args ) {
510
511
		if ( empty( $args['form_id'] ) ) {
512
			return;
513
		}
514
515
		$form = new GetPaid_Payment_Form( (int) $args['form_id'] );
516
517
		if ( ! $form->exists() ) {
518
			return;
519
		}
520
521
		$new_form = new GetPaid_Payment_Form();
522
		$new_form->set_author( $form->get_author( 'edit' ) );
523
		$new_form->set_name( $form->get_name( 'edit' ) . __( '(copy)', 'invoicing' ) );
524
		$new_form->set_elements( $form->get_elements( 'edit' ) );
525
		$new_form->set_items( $form->get_items( 'edit' ) );
526
		$new_form->save();
527
528
		if ( $new_form->exists() ) {
529
			$this->show_success( __( 'Form duplicated successfully', 'invoicing' ) );
530
			$url = get_edit_post_link( $new_form->get_id(), 'edit' );
531
		} else {
532
			$this->show_error( __( 'Unable to duplicate form', 'invoicing' ) );
533
			$url = remove_query_arg( array( 'getpaid-admin-action', 'form_id', 'getpaid-nonce' ) );
534
		}
535
536
		wp_redirect( $url );
537
		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...
538
	}
539
540
	/**
541
     * Resets form stats.
542
	 *
543
	 * @param array $args
544
     */
545
    public function reset_form_stats( $args ) {
546
547
		if ( empty( $args['form_id'] ) ) {
548
			return;
549
		}
550
551
		$form = new GetPaid_Payment_Form( (int) $args['form_id'] );
552
553
		if ( ! $form->exists() ) {
554
			return;
555
		}
556
557
		$form->set_earned( 0 );
558
		$form->set_refunded( 0 );
559
		$form->set_cancelled( 0 );
560
		$form->set_failed( 0 );
561
		$form->save();
562
563
		$this->show_success( __( 'Form stats reset successfully', 'invoicing' ) );
564
565
		wp_safe_redirect( remove_query_arg( array( 'getpaid-admin-action', 'form_id', 'getpaid-nonce' ) ) );
566
		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...
567
	}
568
569
	/**
570
     * Sends a payment reminder to a customer.
571
	 *
572
	 * @param array $args
573
     */
574
    public function send_customer_invoice( $args ) {
575
		getpaid()->get( 'invoice_emails' )->user_invoice( new WPInv_Invoice( $args['invoice_id'] ), true );
576
		wp_safe_redirect( remove_query_arg( array( 'getpaid-admin-action', 'getpaid-nonce', 'invoice_id' ) ) );
577
		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...
578
	}
579
580
	/**
581
     * Sends a payment reminder to a customer.
582
	 *
583
	 * @param array $args
584
     */
585
    public function send_customer_payment_reminder( $args ) {
586
		$sent = getpaid()->get( 'invoice_emails' )->force_send_overdue_notice( new WPInv_Invoice( $args['invoice_id'] ) );
587
588
		if ( $sent ) {
589
			$this->show_success( __( 'Payment reminder was successfully sent to the customer', 'invoicing' ) );
590
		} else {
591
			$this->show_error( __( 'Could not sent payment reminder to the customer', 'invoicing' ) );
592
		}
593
594
		wp_safe_redirect( remove_query_arg( array( 'getpaid-admin-action', 'getpaid-nonce', 'invoice_id' ) ) );
595
		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...
596
	}
597
598
	/**
599
     * Resets tax rates.
600
	 *
601
     */
602
    public function admin_reset_tax_rates() {
603
604
		update_option( 'wpinv_tax_rates', wpinv_get_data( 'tax-rates' ) );
605
		wp_safe_redirect( remove_query_arg( array( 'getpaid-admin-action', 'getpaid-nonce' ) ) );
606
		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...
607
608
	}
609
610
	/**
611
     * Resets admin pages.
612
	 *
613
     */
614
    public function admin_create_missing_pages() {
615
		$installer = new GetPaid_Installer();
616
		$installer->create_pages();
617
		$this->show_success( __( 'GetPaid pages updated.', 'invoicing' ) );
618
		wp_safe_redirect( remove_query_arg( array( 'getpaid-admin-action', 'getpaid-nonce' ) ) );
619
		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...
620
	}
621
622
	/**
623
	 * Refreshes the permalinks.
624
	 */
625
	public function admin_refresh_permalinks() {
626
		flush_rewrite_rules();
627
		$this->show_success( __( 'Permalinks refreshed.', 'invoicing' ) );
628
		wp_safe_redirect( remove_query_arg( array( 'getpaid-admin-action', 'getpaid-nonce' ) ) );
629
		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...
630
	}
631
632
	/**
633
     * Creates missing admin tables.
634
	 *
635
     */
636
    public function admin_create_missing_tables() {
637
		global $wpdb;
638
639
		GetPaid_Installer::create_db_tables();
640
		GetPaid_Installer::migrate_old_customers();
641
642
		if ( '' !== $wpdb->last_error ) {
643
			$this->show_error( __( 'Your GetPaid tables have been updated:', 'invoicing' ) . ' ' . $wpdb->last_error );
644
		} else {
645
			$this->show_success( __( 'Your GetPaid tables have been updated.', 'invoicing' ) );
646
		}
647
648
		wp_safe_redirect( remove_query_arg( array( 'getpaid-admin-action', 'getpaid-nonce' ) ) );
649
		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...
650
	}
651
652
	/**
653
     * Migrates old invoices to the new database tables.
654
	 *
655
     */
656
    public function admin_migrate_old_invoices() {
657
658
		// Migrate the invoices.
659
		$installer = new GetPaid_Installer();
660
		$installer->migrate_old_invoices();
661
662
		// Show an admin message.
663
		$this->show_success( __( 'Your invoices have been migrated.', 'invoicing' ) );
664
665
		// Redirect the admin.
666
		wp_safe_redirect( remove_query_arg( array( 'getpaid-admin-action', 'getpaid-nonce' ) ) );
667
		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...
668
669
	}
670
671
	/**
672
     * Download customers.
673
	 *
674
     */
675
    public function admin_download_customers() {
676
677
		$output = fopen( 'php://output', 'w' );
678
679
		if ( false === $output ) {
680
			wp_die( esc_html__( 'Unsupported server', 'invoicing' ), 500 );
681
		}
682
683
		header( 'Content-Type:text/csv' );
684
		header( 'Content-Disposition:attachment;filename=customers.csv' );
685
686
		/** @var GetPaid_Customer[] $customers */
687
		$customers = getpaid_get_customers( array( 'number' => -1 ) );
688
		$columns   = array_keys( GetPaid_Customer_Data_Store::get_database_fields() );
689
690
		// Output the csv column headers.
691
		fputcsv( $output, $columns );
692
693
		// Loop through
694
		foreach ( $customers as $customer ) {
695
696
			$row  = array();
697
698
			foreach ( $columns as $column ) {
699
				$row[]  = (string) maybe_serialize( $customer->get( $column, 'edit' ) );
700
			}
701
702
			fputcsv( $output, $row );
703
		}
704
705
		fclose( $output );
706
		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...
707
708
	}
709
710
	/**
711
     * Installs a plugin.
712
	 *
713
	 * @param array $data
714
     */
715
    public function admin_install_plugin( $data ) {
716
717
		if ( ! empty( $data['plugins'] ) ) {
718
			include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
719
			wp_cache_flush();
720
721
			foreach ( $data['plugins'] as $slug => $file ) {
722
				$plugin_zip = esc_url( 'https://downloads.wordpress.org/plugin/' . $slug . '.latest-stable.zip' );
723
				$upgrader   = new Plugin_Upgrader( new Automatic_Upgrader_Skin() );
724
				$installed  = $upgrader->install( $plugin_zip );
725
726
				if ( ! is_wp_error( $installed ) && $installed ) {
727
					activate_plugin( $file, '', false, true );
728
				} else {
729
					wpinv_error_log( $upgrader->skin->get_upgrade_messages(), false );
0 ignored issues
show
The method get_upgrade_messages() does not exist on WP_Upgrader_Skin. It seems like you code against a sub-type of WP_Upgrader_Skin such as Automatic_Upgrader_Skin. ( Ignorable by Annotation )

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

729
					wpinv_error_log( $upgrader->skin->/** @scrutinizer ignore-call */ get_upgrade_messages(), false );
Loading history...
$upgrader->skin->get_upgrade_messages() of type string[] is incompatible with the type string expected by parameter $log of wpinv_error_log(). ( Ignorable by Annotation )

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

729
					wpinv_error_log( /** @scrutinizer ignore-type */ $upgrader->skin->get_upgrade_messages(), false );
Loading history...
730
				}
731
}
732
}
733
734
		$redirect = isset( $data['redirect'] ) ? esc_url_raw( $data['redirect'] ) : admin_url( 'plugins.php' );
735
		wp_safe_redirect( $redirect );
736
		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...
737
738
	}
739
740
	/**
741
     * Connects a gateway.
742
	 *
743
	 * @param array $data
744
     */
745
    public function admin_connect_gateway( $data ) {
746
747
		if ( ! empty( $data['plugin'] ) ) {
748
749
			$gateway     = sanitize_key( $data['plugin'] );
750
			$connect_url = apply_filters( "getpaid_get_{$gateway}_connect_url", false, $data );
751
752
			if ( ! empty( $connect_url ) ) {
753
				wp_redirect( $connect_url );
754
				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...
755
			}
756
757
			if ( 'stripe' == $data['plugin'] ) {
758
				require_once ABSPATH . 'wp-admin/includes/plugin.php';
759
				include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
760
				wp_cache_flush();
761
762
				if ( ! array_key_exists( 'getpaid-stripe-payments/getpaid-stripe-payments.php', get_plugins() ) ) {
763
					$plugin_zip = esc_url( 'https://downloads.wordpress.org/plugin/getpaid-stripe-payments.latest-stable.zip' );
764
					$upgrader   = new Plugin_Upgrader( new Automatic_Upgrader_Skin() );
765
					$upgrader->install( $plugin_zip );
766
				}
767
768
				activate_plugin( 'getpaid-stripe-payments/getpaid-stripe-payments.php', '', false, true );
769
			}
770
771
			$connect_url = apply_filters( "getpaid_get_{$gateway}_connect_url", false, $data );
772
			if ( ! empty( $connect_url ) ) {
773
				wp_redirect( $connect_url );
774
				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...
775
			}
776
}
777
778
		$redirect = isset( $data['redirect'] ) ? esc_url_raw( urldecode( $data['redirect'] ) ) : admin_url( 'admin.php?page=wpinv-settings&tab=gateways' );
779
		wp_safe_redirect( $redirect );
780
		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...
781
782
	}
783
784
	/**
785
     * Recalculates discounts.
786
	 *
787
     */
788
    public function admin_recalculate_discounts() {
789
		global $wpdb;
790
791
		// Fetch all invoices that have discount codes.
792
		$table    = $wpdb->prefix . 'getpaid_invoices';
793
		$invoices = $wpdb->get_col( "SELECT `post_id` FROM `$table` WHERE `discount` = 0 && `discount_code` <> ''" );
794
795
		foreach ( $invoices as $invoice ) {
796
797
			$invoice = new WPInv_Invoice( $invoice );
798
799
			if ( ! $invoice->exists() ) {
800
				continue;
801
			}
802
803
			// Abort if the discount does not exist or does not apply here.
804
			$discount = new WPInv_Discount( $invoice->get_discount_code() );
805
			if ( ! $discount->exists() ) {
806
				continue;
807
			}
808
809
			$invoice->add_discount( getpaid_calculate_invoice_discount( $invoice, $discount ) );
810
			$invoice->recalculate_total();
811
812
			if ( $invoice->get_total_discount() > 0 ) {
813
				$invoice->save();
814
			}
815
}
816
817
		// Show an admin message.
818
		$this->show_success( __( 'Discounts have been recalculated.', 'invoicing' ) );
819
820
		// Redirect the admin.
821
		wp_safe_redirect( remove_query_arg( array( 'getpaid-admin-action', 'getpaid-nonce' ) ) );
822
		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...
823
824
	}
825
826
	/**
827
	 * Load dynamic strings in to file to translate via po editor
828
	 *
829
	 * @since   2.8.32
830
	 *
831
	 * @global null|object $wp_filesystem WP_Filesystem object.
832
	 *
833
	 * @return bool True if file created otherwise false
834
	 */
835
    public function tool_translate_db_texts() {
836
		$language_file = wp_normalize_path( WPINV_PLUGIN_DIR . 'db-language.php' );
837
838
		if ( getpaid_sync_db_text_translation() ) {
839
			// Success
840
			$this->show_success( wp_sprintf( __( 'Strings are added in the file <b>%s</b> for translation.', 'invoicing' ), $language_file ) );
841
		} else {
842
			// Failure
843
			$this->show_error( wp_sprintf( __( 'There was a problem creating the file <b>%s</b>. Please check file permissions.', 'invoicing' ), $language_file ) );
844
		}
845
846
		// Redirect the admin.
847
		wp_safe_redirect( remove_query_arg( array( 'getpaid-admin-action', 'getpaid-nonce' ) ) );
848
849
		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...
850
	}
851
852
    /**
853
	 * Returns an array of admin notices.
854
	 *
855
	 * @since       1.0.19
856
     * @return array
857
	 */
858
	public function get_notices() {
859
		$notices = get_option( 'wpinv_admin_notices' );
860
        return is_array( $notices ) ? $notices : array();
861
	}
862
863
	/**
864
	 * Checks if we have any admin notices.
865
	 *
866
	 * @since       2.0.4
867
     * @return array
868
	 */
869
	public function has_notices() {
870
		return count( $this->get_notices() ) > 0;
0 ignored issues
show
Bug Best Practice introduced by
The expression return count($this->get_notices()) > 0 returns the type boolean which is incompatible with the documented return type array.
Loading history...
871
	}
872
873
	/**
874
	 * Clears all admin notices
875
	 *
876
	 * @access      public
877
	 * @since       1.0.19
878
	 */
879
	public function clear_notices() {
880
		delete_option( 'wpinv_admin_notices' );
881
	}
882
883
	/**
884
	 * Saves a new admin notice
885
	 *
886
	 * @access      public
887
	 * @since       1.0.19
888
	 */
889
	public function save_notice( $type, $message ) {
890
		$notices = $this->get_notices();
891
892
		if ( empty( $notices[ $type ] ) || ! is_array( $notices[ $type ] ) ) {
893
			$notices[ $type ] = array();
894
		}
895
896
		$notices[ $type ][] = $message;
897
898
		update_option( 'wpinv_admin_notices', $notices );
899
	}
900
901
	/**
902
	 * Displays a success notice
903
	 *
904
	 * @param       string $msg The message to qeue.
905
	 * @access      public
906
	 * @since       1.0.19
907
	 */
908
	public function show_success( $msg ) {
909
		$this->save_notice( 'success', $msg );
910
	}
911
912
	/**
913
	 * Displays a error notice
914
	 *
915
	 * @access      public
916
	 * @param       string $msg The message to qeue.
917
	 * @since       1.0.19
918
	 */
919
	public function show_error( $msg ) {
920
		$this->save_notice( 'error', $msg );
921
	}
922
923
	/**
924
	 * Displays a warning notice
925
	 *
926
	 * @access      public
927
	 * @param       string $msg The message to qeue.
928
	 * @since       1.0.19
929
	 */
930
	public function show_warning( $msg ) {
931
		$this->save_notice( 'warning', $msg );
932
	}
933
934
	/**
935
	 * Displays a info notice
936
	 *
937
	 * @access      public
938
	 * @param       string $msg The message to qeue.
939
	 * @since       1.0.19
940
	 */
941
	public function show_info( $msg ) {
942
		$this->save_notice( 'info', $msg );
943
	}
944
945
	/**
946
	 * Show notices
947
	 *
948
	 * @access      public
949
	 * @since       1.0.19
950
	 */
951
	public function show_notices() {
952
953
        $notices = $this->get_notices();
954
        $this->clear_notices();
955
956
		foreach ( $notices as $type => $messages ) {
957
958
			if ( ! is_array( $messages ) ) {
959
				continue;
960
			}
961
962
            $type  = esc_attr( $type );
963
			foreach ( $messages as $message ) {
964
				echo wp_kses_post( "<div class='notice notice-$type is-dismissible'><p>$message</p></div>" );
965
            }
966
}
967
968
		foreach ( array( 'checkout_page', 'invoice_history_page', 'success_page', 'failure_page', 'invoice_subscription_page' ) as $page ) {
969
970
			if ( ! is_numeric( wpinv_get_option( $page, false ) ) ) {
971
				$url     = wp_nonce_url(
972
					add_query_arg( 'getpaid-admin-action', 'create_missing_pages' ),
973
					'getpaid-nonce',
974
					'getpaid-nonce'
975
				);
976
				$message  = __( 'Some GetPaid pages are missing. To use GetPaid without any issues, click the button below to generate the missing pages.', 'invoicing' );
977
				$message2 = __( 'Generate Pages', 'invoicing' );
978
				echo wp_kses_post( "<div class='notice notice-warning is-dismissible'><p>$message<br><br><a href='$url' class='button button-primary'>$message2</a></p></div>" );
979
				break;
980
			}
981
}
982
983
	}
984
985
}
986