PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.11.2
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.11.2
1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 1.8.0 All 210 releases
charitable / includes / admin / class-charitable-admin.php

class-charitable-admin.php in Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) 1.8.11.2, at includes/admin/class-charitable-admin.php

1,719 lines 57.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class that sets up the Charitable Admin functionality.
4 *
5 * @package Charitable/Classes/Charitable_Admin
6 * @author David Bisset
7 * @copyright Copyright (c) 2023, WP Charitable LLC
8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 * @since 1.0.0
10 * @version 1.8.4
11 */
12
13 // Exit if accessed directly.
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 if ( ! class_exists( 'Charitable_Admin' ) ) :
19
20 /**
21 * Charitable_Admin
22 *
23 * @final
24 * @since 1.0.0
25 */
26 final class Charitable_Admin {
27
28 /**
29 * The single instance of this class.
30 *
31 * @var Charitable_Admin|null
32 */
33 private static $instance = null;
34
35 /**
36 * Donation actions class.
37 *
38 * @var Charitable_Donation_Admin_Actions
39 */
40 private $donation_actions;
41
42 /**
43 * Set up the class.
44 *
45 * Note that the only way to instantiate an object is with the charitable_start method,
46 * which can only be called during the start phase. In other words, don't try
47 * to instantiate this object.
48 *
49 * @since 1.0.0
50 * @version 1.8.3 added third party plugin area.
51 */
52 protected function __construct() {
53 $this->load_dependencies();
54
55 $this->donation_actions = new Charitable_Donation_Admin_Actions();
56
57 // Initialize About page.
58 new Charitable_About();
59
60 do_action( 'charitable_admin_loaded' );
61
62 add_action( 'wp_ajax_charitable_lite_settings_upgrade', array( $this, 'dismiss_lite_cta' ) );
63 add_action( 'wp_ajax_charitable_lite_reports_upgrade', array( $this, 'dismiss_lite_reports_cta' ) );
64
65 // Addon management AJAX handlers.
66 add_action( 'wp_ajax_charitable_install_addon', array( $this, 'install_addon' ) );
67 add_action( 'wp_ajax_charitable_activate_addon', array( $this, 'activate_addon' ) );
68 add_action( 'wp_ajax_charitable_deactivate_addon', array( $this, 'deactivate_addon' ) );
69
70 // Dedicated About/Addons handlers to avoid conflicts with legacy hooks.
71 add_action( 'wp_ajax_charitable_addons_install_wporg', array( $this, 'install_addon' ) );
72 add_action( 'wp_ajax_charitable_addons_activate', array( $this, 'activate_addon' ) );
73 add_action( 'wp_ajax_charitable_addons_deactivate', array( $this, 'deactivate_addon' ) );
74
75 add_filter( 'post_row_actions', array( $this, 'campaign_action_row' ), 10, 2 );
76 add_filter( 'get_edit_post_link', array( $this, 'campaign_link' ), 99, 3 );
77 add_filter( 'preview_post_link', array( $this, 'campaign_preview_link' ), 10, 2 );
78
79 add_filter( 'admin_init', array( $this, 'update_dashboard_url' ), 10 );
80
81 /* third party plugins */
82 add_filter( 'elementor/settings/controls/checkbox_list_cpt/post_type_objects', array( $this, 'elementor_post_types' ), 10, 1 );
83 }
84
85 /**
86 * Redirect from the old dashboard to the new, unless legacy campaigns are being used and defined.
87 *
88 * @since 1.8.1
89 * @version 1.8.4
90 *
91 * @return void
92 */
93 public function update_dashboard_url() {
94 global $pagenow;
95
96 if ( function_exists( 'charitable_use_legacy_dashboard' ) && charitable_use_legacy_dashboard() ) {
97 return;
98 }
99
100 if ( $pagenow === 'admin.php' && isset( $_GET['page'] ) && $_GET['page'] === 'charitable' ) { // phpcs:ignore
101
102 // Check and see if this has to do with onboarding.
103
104 if ( isset( $_GET['wpchar_lite'] ) && isset( $_GET['setup'] ) ) { // phpcs:ignore
105
106 switch ( sanitize_text_field( wp_unslash( $_GET['setup'] ) ) ) { // phpcs:ignore
107
108 case 'welcome':
109 if ( empty( $_GET['wpchar_lite'] ) || 'lite' !== $_GET['wpchar_lite'] ) { // phpcs:ignore
110 wp_safe_redirect( admin_url( 'admin.php?page=charitable-dashboard' ) );
111 exit;
112 }
113
114 break;
115
116 case 'return':
117 // if the user is returning from a redirect from a login or back link detect that and redirect them to the welcome/continue page.
118 // Only good way so far would be to detect POST data.
119 if ( empty( $_POST ) && is_admin() ) { // phpcs:ignore
120 wp_safe_redirect( admin_url( 'admin.php?page=charitable&wpchar_lite=lite&setup=welcome&resume=true&lostconnection=1' ) );
121 exit;
122 }
123
124 $plugins = isset( $_POST['plugins'] ) ? base64_decode( sanitize_text_field( wp_unslash( $_POST['plugins'] ) ) ) : ''; // phpcs:ignore
125 $features = isset( $_POST['features'] ) ? base64_decode( sanitize_text_field( wp_unslash( $_POST['features'] ) ) ) : ''; // phpcs:ignore
126 $meta = isset( $_POST['meta'] ) ? base64_decode( sanitize_text_field( wp_unslash( $_POST['meta'] ) ) ) : ''; // phpcs:ignore
127 $pm = isset( $_POST['pm'] ) ? base64_decode( sanitize_text_field( wp_unslash( $_POST['pm'] ) ) ) : ''; // phpcs:ignore
128 $license_key = isset( $_POST['license_key'] ) ? base64_decode( sanitize_text_field( wp_unslash( $_POST['license_key'] ) ) ) : ''; // phpcs:ignore
129
130 // process meta.
131 if ( ! empty( $meta ) ) {
132 $meta = str_replace( '\"', '"', $meta );
133 if ( ! empty( $meta ) ) {
134 $meta = json_decode( $meta, true );
135 }
136 }
137 // process payment methods.
138 if ( ! empty( $plugins ) ) {
139 $plugins = json_decode( $plugins );
140 $plugins = str_replace( array( '"', '[', ']' ), '', $plugins );
141 if ( ! empty( $plugins ) ) {
142 $plugins = explode( ',', $plugins );
143 }
144 }
145
146 // process plugins.
147 if ( ! empty( $pm ) ) {
148 $pm = json_decode( $pm );
149 if ( $pm ) {
150 $pm = str_replace( array( '"', '[', ']' ), '', $pm );
151 if ( ! empty( $plugins ) ) {
152 $pm = explode( ',', $pm );
153 }
154 }
155 }
156
157 // process features.
158 if ( ! empty( $features ) ) {
159 $features = json_decode( $features );
160 $features = str_replace( array( '"', '[', ']' ), '', $features );
161 if ( ! empty( $features ) ) {
162 $features = explode( ',', $features );
163 }
164 }
165
166 // clean license (remove quotes).
167 $license_key = str_replace( '"', '', $license_key );
168
169 // process campaign.
170 $campaign_template = isset( $_POST['template'] ) && ! empty( $_POST['template'] ) ? sanitize_text_field( wp_unslash( $_POST['template'] ) ) : ''; // phpcs:ignore
171 $campaign_title = isset( $_POST['campaign_title'] ) && ! empty( $_POST['campaign_title'] ) ? sanitize_text_field( wp_unslash( $_POST['campaign_title'] ) ) : ''; // phpcs:ignore
172 $campaign_description = isset( $_POST['campaign_description'] ) && ! empty( $_POST['campaign_description'] ) ? sanitize_text_field( wp_unslash( $_POST['campaign_description'] ) ) : ''; // phpcs:ignore
173 $campaign_goal = isset( $_POST['campaign_goal'] ) && ! empty( $_POST['campaign_goal'] ) ? sanitize_text_field( wp_unslash( $_POST['campaign_goal'] ) ) : ''; // phpcs:ignore
174 $campaign_end_date = isset( $_POST['campaign_end_date'] ) && ! empty( $_POST['campaign_end_date'] ) ? sanitize_text_field( wp_unslash( $_POST['campaign_end_date'] ) ) : ''; // phpcs:ignore
175 $campaign = array(
176 'template' => $campaign_template,
177 'title' => $campaign_title,
178 'description' => $campaign_description,
179 'goal' => $campaign_goal,
180 'end_date' => $campaign_end_date,
181 );
182
183 // store server side setups.
184 $serverside = new Charitable_Setup();
185
186 $serverside->store_meta( $meta );
187 $serverside->store_plugins( $plugins );
188 $serverside->store_features( $features );
189 $serverside->store_payment_methods( $pm );
190 $serverside->store_campaign( $campaign );
191 if ( $license_key ) {
192 $serverside->store_license_key( $license_key );
193 }
194
195 // If this is a select few addons that we need to disable their auto-activation, do so.
196 if ( ! empty( $plugins ) ) {
197 foreach ( $plugins as $plugin ) {
198 if ( $plugin === 'all-in-one-seo-pack' ) {
199 update_option( 'aioseo_activation_redirect', true );
200 } elseif ( $plugin === 'wp-mail-smtp' ) {
201 update_option( 'wp_mail_smtp_activation_prevent_redirect', true );
202 }
203 }
204 }
205
206 // add a transient to indicate that the server side onboarding has moved to the plugin.
207 set_transient( 'charitable_ss_onboarding', 1, 0 );
208
209 delete_option( 'charitable_started_onboarding' );
210
211 wp_safe_redirect( admin_url( 'admin.php?page=charitable-setup&setup=1' ) );
212 exit;
213
214 case 'cancelled':
215 // Either user is going "back" to the checklist page or (if the checklist is disabled) the welcome page.
216 // https://mydomain.com/wp-admin/admin.php?page=charitable&wpchar_lite=lite&setup=cancelled.
217 $checklist_class = Charitable_Checklist::get_instance();
218 $checklist_possible = $checklist_class->is_checklist_skipped() || $checklist_class->is_checklist_completed();
219 $welcome_go_back_url = $checklist_possible ? admin_url( 'admin.php?page=charitable-dashboard' ) : admin_url( 'admin.php?page=charitable-setup-checklist' );
220
221 // Clear the transient.
222 delete_transient( 'charitable_activation_redirect' );
223
224 // Clear the option.
225 delete_option( 'charitable_started_onboarding' );
226
227 wp_safe_redirect( $welcome_go_back_url );
228 exit;
229
230 default:
231 wp_safe_redirect( admin_url( 'admin.php?page=charitable-dashboard' ) );
232 exit;
233
234 }
235 } else {
236
237 wp_safe_redirect( admin_url( 'admin.php?page=charitable-dashboard' ) );
238 exit;
239
240 }
241 }
242 }
243
244 /**
245 * Returns and/or create the single instance of this class.
246 *
247 * @since 1.2.0
248 *
249 * @return Charitable_Admin
250 */
251 public static function get_instance() {
252 if ( is_null( self::$instance ) ) {
253 self::$instance = new self();
254 }
255
256 return self::$instance;
257 }
258
259 /**
260 * Include admin-only files.
261 *
262 * @since 1.0.0
263 *
264 * @return void
265 */
266 private function load_dependencies() {
267 $admin_dir = charitable()->get_path( 'includes' ) . 'admin/';
268
269 require_once $admin_dir . 'charitable-core-admin-functions.php';
270 require_once $admin_dir . 'campaigns/charitable-admin-campaign-hooks.php';
271 require_once $admin_dir . 'dashboard/class-charitable-dashboard-ajax.php';
272 require_once $admin_dir . 'charitable-dashboard-hooks.php';
273 require_once $admin_dir . 'dashboard-widgets/charitable-dashboard-widgets-hooks.php';
274 require_once $admin_dir . 'donations/charitable-admin-donation-hooks.php';
275 require_once $admin_dir . 'settings/charitable-settings-admin-hooks.php';
276 // Security hooks are now loaded in charitable.php to ensure CAPTCHA works on frontend.
277 // require_once $admin_dir . 'security/charitable-security-hooks.php';
278 require_once $admin_dir . 'activities/charitable-admin-activity-hooks.php';
279 require_once $admin_dir . 'reports/charitable-core-reports-functions.php';
280 require_once $admin_dir . 'reports/charitable-admin-reports-hooks.php';
281 require_once $admin_dir . 'plugins/class-charitable-admin-plugins-third-party.php';
282 require_once $admin_dir . 'tools/charitable-tools-admin-hooks.php';
283 require_once $admin_dir . 'growth-tools/charitable-growth-tools-admin-hooks.php';
284 require_once $admin_dir . 'onboarding/charitable-onboarding-admin-hooks.php';
285 require_once $admin_dir . 'tracking/charitable-tracking-admin-hooks.php';
286 require_once $admin_dir . 'smtp/charitable-smtp-admin-hooks.php';
287 require_once $admin_dir . 'class-charitable-about.php';
288 require_once $admin_dir . 'charitable-admin-addons-functions.php';
289 require_once $admin_dir . 'privacy-compliance/charitable-privacy-compliance-admin-hooks.php';
290 require_once $admin_dir . 'intergrations/charitable-privacy-compliance-admin-hooks.php';
291 require_once $admin_dir . 'intergrations/charitable-backups-admin-hooks.php';
292 require_once $admin_dir . 'intergrations/charitable-seo-admin-hooks.php';
293 require_once $admin_dir . 'intergrations/charitable-automation-admin-hooks.php';
294 }
295
296 /**
297 * Get Charitable_Donation_Admin_Actions class.
298 *
299 * @since 1.5.0
300 *
301 * @return Charitable_Donation_Admin_Actions
302 */
303 public function get_donation_actions() {
304 return $this->donation_actions;
305 }
306
307 /**
308 * Do an admin action.
309 *
310 * @since 1.5.0
311 *
312 * @return boolean|WP_Error WP_Error in case of error. Mixed results if the action was performed.
313 */
314 public function maybe_do_admin_action() {
315 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
316 if ( ! array_key_exists( 'charitable_admin_action', $_GET ) ) {
317 return false;
318 }
319
320 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Check for required keys only
321 if ( count( array_diff( array( 'action_type', '_nonce', 'object_id' ), array_keys( $_GET ) ) ) ) {
322 return new WP_Error( __( 'Action could not be executed.', 'charitable' ) );
323 }
324
325 if ( ! isset( $_GET['_nonce'] ) || ! wp_verify_nonce( $_GET['_nonce'], 'donation_action' ) ) { // phpcs:ignore.
326 return new WP_Error( __( 'Action could not be executed. Nonce check failed.', 'charitable' ) );
327 }
328
329 if ( ! isset( $_GET['action_type'] ) || 'donation' !== $_GET['action_type'] ) {
330 return new WP_Error( __( 'Action from an unknown action type executed.', 'charitable' ) );
331 }
332
333 return $this->donation_actions->do_action( esc_html( $_GET['charitable_admin_action'] ), esc_html( $_GET['object_id'] ) ); // phpcs:ignore.
334 }
335
336 /**
337 * Loads admin-only scripts and stylesheets.
338 *
339 * @since 1.0.0
340 * @version 1.8.8.6
341 *
342 * @return void
343 */
344 public function admin_enqueue_scripts() {
345
346 $min = charitable_get_min_suffix(); // 1.8.3
347 $version = charitable_is_break_cache() ? charitable()->get_version() . '.' . time() : charitable()->get_version();
348
349 $assets_dir = charitable()->get_path( 'assets', false );
350
351 $localized_vars = array(
352 'suggested_amount_placeholder' => __( 'Amount', 'charitable' ),
353 'suggested_amount_description_placeholder' => __( 'Optional Description', 'charitable' ),
354 'ajax_url' => admin_url( 'admin-ajax.php' ),
355 'nonce' => wp_create_nonce( 'charitable_admin_nonce' ),
356 'heads_up' => __( 'Heads Up!', 'charitable' ),
357 'square_legacy_modal_message' => __( 'Square (Legacy) is currently active. Activating the Square (Core) payment method will deactivate the Square (Legacy) payment and you will need to reconnect your Square account.<br><br>Click <strong><span style="color: #ff6b35;">Ok</span></strong> to deactivate Square (Legacy) and activate Square (Core) payment. Click <strong><span style="color: #000000;">Cancel</span></strong> to keep Square (Legacy) payment active.</strong><br><br><a href="https://wpcharitable.com/documentation/square-legacy" target="_blank">Read our documentation</a>', 'charitable' ),
358 'square_core_modal_message' => __( 'Square (Core) is currently active. Activating the Square (Legacy) payment method will deactivate the Square (Core) payment and you will need to reconnect your Square account.<br><br>Click <strong><span style="color: #ff6b35;">Ok</span></strong> to deactivate Square (Core) and activate Square (Legacy) payment. Click <strong><span style="color: #000000;">Cancel</span></strong> to keep Square (Core) payment active.<br><br><a href="https://wpcharitable.com/documentation/square-legacy" target="_blank">Read our documentation</a>', 'charitable' ),
359 'oops' => __( 'Oops!', 'charitable' ),
360 'square_legacy_switch_error' => __( 'There was an error switching the payment gateways. Please try again.', 'charitable' ),
361 'ok' => __( 'OK', 'charitable' ),
362 'cancel' => __( 'Cancel', 'charitable' ),
363 'square_legacy_active' => charitable_get_helper( 'gateways' )->is_active_gateway( 'square' ),
364 'square_core_active' => charitable_get_helper( 'gateways' )->is_active_gateway( 'square_core' ),
365 );
366
367 /* Menu styles are loaded everywhere in the WordPress dashboard. */
368 wp_register_style(
369 'charitable-admin-menu',
370 $assets_dir . 'css/admin/charitable-admin-menu' . $min . '.css',
371 array(),
372 $version
373 );
374
375 wp_enqueue_style( 'charitable-admin-menu' );
376
377 wp_register_script(
378 'charitable-admin-pages-all',
379 $assets_dir . 'js/admin/charitable-admin-pages' . $min . '.js',
380 array( 'jquery' ),
381 $version,
382 false
383 );
384
385 wp_enqueue_script( 'charitable-admin-pages-all' );
386
387 /* Admin page styles are registered but only enqueued when necessary. */
388 wp_register_style(
389 'charitable-admin-pages',
390 $assets_dir . 'css/admin/charitable-admin-pages' . $min . '.css',
391 array(),
392 $version
393 );
394
395 /* The following styles are only loaded on Charitable screens. */
396 $screen = get_current_screen();
397
398 /* This check covers the category and tags pages, only load the main admin css (upgrade banner, etc.) */
399 if ( ! is_null( $screen ) && ( in_array( $screen->id, array( 'edit-campaign_category', 'edit-campaign_tag' ), true ) || ( in_array( $screen->base, array( 'edit-tags', 'edit-categories' ), true ) ) ) ) {
400
401 wp_register_style(
402 'charitable-admin',
403 $assets_dir . 'css/admin/charitable-admin-legacy' . $min . '.css',
404 array(),
405 $version
406 );
407
408 wp_enqueue_style( 'charitable-admin' );
409
410 wp_register_script(
411 'charitable-admin-notice',
412 $assets_dir . 'js/admin/charitable-admin-notice' . $min . '.js',
413 array( 'jquery' ),
414 $version,
415 false
416 );
417
418 }
419
420 // v 1.8.0.
421 wp_register_style(
422 'charitable-admin-2.0',
423 $assets_dir . 'css/admin/charitable-admin' . $min . '.css',
424 array(),
425 $version
426 );
427
428 wp_enqueue_style( 'charitable-admin-2.0' );
429
430 wp_register_style(
431 'charitable-admin',
432 $assets_dir . 'css/admin/charitable-admin-legacy' . $min . '.css',
433 array(),
434 $version
435 );
436
437 wp_enqueue_style( 'charitable-admin' );
438
439 if ( ! is_null( $screen ) && in_array( $screen->id, charitable_get_charitable_screens() ) ) {
440
441 $dependencies = array( 'jquery-ui-datepicker', 'jquery-ui-tabs', 'jquery-ui-sortable' );
442
443 if ( 'donation' === $screen->id ) {
444 wp_register_script(
445 'accounting',
446 $assets_dir . 'js/libraries/accounting' . $min . '.js',
447 array( 'jquery' ),
448 $version,
449 true
450 );
451
452 $dependencies[] = 'accounting';
453 $localized_vars = array_merge(
454 $localized_vars,
455 array(
456 'currency_format_num_decimals' => esc_attr( charitable_get_currency_helper()->get_decimals() ),
457 'currency_format_decimal_sep' => esc_attr( charitable_get_currency_helper()->get_decimal_separator() ),
458 'currency_format_thousand_sep' => esc_attr( charitable_get_currency_helper()->get_thousands_separator() ),
459 'currency_format' => esc_attr( charitable_get_currency_helper()->get_accounting_js_format() ),
460 )
461 );
462 }
463
464 wp_register_script(
465 'charitable-admin',
466 $assets_dir . 'js/admin/charitable-admin' . $min . '.js',
467 $dependencies,
468 $version,
469 false
470 );
471
472 wp_enqueue_script( 'charitable-admin' );
473
474 // v 1.8.0.
475
476 wp_enqueue_style(
477 'jquery-confirm',
478 charitable()->get_path( 'directory', false ) . 'assets/lib/jquery.confirm/jquery-confirm.min.css',
479 null,
480 '3.3.4'
481 );
482
483 wp_enqueue_style(
484 'charitable-font-awesome',
485 charitable()->get_path( 'directory', false ) . 'assets/lib/font-awesome/font-awesome.min.css',
486 null,
487 '4.7.0'
488 );
489
490 wp_enqueue_script(
491 'jquery-confirm',
492 charitable()->get_path( 'directory', false ) . 'assets/lib/jquery.confirm/jquery-confirm.min.js',
493 array( 'jquery' ),
494 '3.3.4',
495 false
496 );
497
498 wp_enqueue_style(
499 'lity',
500 charitable()->get_path( 'directory', false ) . 'assets/lib/lity/lity.min.css',
501 null,
502 '3.0.0'
503 );
504
505 // Add custom CSS for lity image sizing
506 wp_add_inline_style( 'lity', '
507 .lity-image img {
508 max-width: 90vw !important;
509 max-height: 90vh !important;
510 width: auto !important;
511 height: auto !important;
512 margin: 0 auto !important;
513 }
514 .lity-image .lity-container {
515 max-width: 90vw !important;
516 max-height: 90vh !important;
517 }
518 ' );
519
520 wp_enqueue_script(
521 'lity',
522 charitable()->get_path( 'directory', false ) . 'assets/lib/lity/lity.min.js',
523 array( 'jquery' ),
524 '3.0.0',
525 false
526 );
527
528 wp_register_script(
529 'charitable-admin-2.0',
530 $assets_dir . 'js/admin/charitable-admin-2.0' . $min . '.js',
531 $dependencies,
532 $version,
533 false
534 );
535
536 wp_enqueue_script( 'charitable-admin-2.0' );
537
538 /**
539 * Filter the admin Javascript vars.
540 *
541 * @since 1.0.0
542 *
543 * @param array $localized_vars The vars.
544 */
545 $localized_vars = (array) apply_filters( 'charitable_localized_javascript_vars', $localized_vars );
546
547 wp_localize_script( 'charitable-admin', 'CHARITABLE', $localized_vars );
548
549 // Localize script for charitable-admin-2.0.js
550 wp_localize_script( 'charitable-admin-2.0', 'charitable_admin', array(
551 'autoshow_plugin_notifications' => charitable_get_autoshow_plugin_notifications(),
552 ) );
553
554 /* color picker for admin settings */
555 wp_enqueue_script( 'wp-color-picker', false, false, false, true ); // phpcs:ignore.
556 wp_enqueue_style( 'wp-color-picker' );
557
558 } // end if
559
560 wp_enqueue_script(
561 'charitable-admin-utils',
562 charitable()->get_path( 'directory', false ) . "assets/js/admin/charitable-admin-utils{$min}.js",
563 array( 'jquery' ),
564 charitable()->get_version(),
565 false
566 );
567
568 // Register these scripts only for reports and dashboard pages.
569 if ( ! is_null( $screen ) && ( $screen->id === 'charitable_page_charitable-reports' || $screen->id === 'charitable_page_charitable-dashboard' ) ) {
570
571 // Specific styles for the "overview" and main reporting tabs.
572 if ( empty( $_GET['tab'] ) || ( ! empty( $_GET['tab'] ) && charitable_reports_allow_tab_load_scripts( strtolower( sanitize_text_field( wp_unslash( $_GET['tab'] ) ) ) ) ) ) { // phpcs:ignore
573
574 wp_register_script(
575 'charitable-apex-charts',
576 charitable()->get_path( 'assets', false ) . 'js/libraries/apexcharts.min.js',
577 array( 'jquery' ),
578 $version,
579 true
580 );
581
582 /**
583 * Use WordPress core's Moment.js library.
584 *
585 * WordPress core includes Moment.js and exposes it via wp_enqueue_script('moment').
586 * This library is required as a dependency for daterangepicker.min.js, which powers
587 * the date range picker functionality in the reporting interface.
588 *
589 * @see https://github.com/moment/moment
590 * @since 1.0.0
591 * @version 1.8.8.6
592 */
593 wp_enqueue_script( 'moment' );
594
595 wp_register_script(
596 'charitable-report-date-range-picker',
597 charitable()->get_path( 'assets', false ) . 'js/libraries/daterangepicker.min.js',
598 array( 'jquery', 'moment' ),
599 $version,
600 true
601 );
602
603 wp_register_script(
604 'charitable-reporting',
605 $assets_dir . 'js/admin/charitable-admin-reporting' . $min . '.js',
606 array( 'jquery', 'charitable-apex-charts', 'charitable-report-date-range-picker' ),
607 $version,
608 true
609 );
610
611 wp_enqueue_style(
612 'charitable-report-date-range-picker',
613 charitable()->get_path( 'assets', false ) . 'css/libraries/daterangepicker.css',
614 null,
615 '4.7.0'
616 );
617
618 wp_enqueue_script( 'charitable-apex-charts' );
619 wp_enqueue_script( 'charitable-report-date-range-picker' );
620 wp_enqueue_script( 'charitable-reporting' );
621
622 } elseif ( ! empty( $_GET['tab'] ) && 'analytics' === $_GET['tab'] ) { // phpcs:ignore
623
624 // this loads a specific script for the analytics tab.
625 do_action( 'charitable_admin_enqueue_analytics_scripts' );
626
627 }
628 }
629
630 // Register v2 dashboard specific assets
631 if ( ! is_null( $screen ) && $screen->id === 'charitable_page_charitable-dashboard' && ! charitable_use_legacy_dashboard() ) {
632
633 // Register ApexCharts for the v2 dashboard chart
634 wp_register_script(
635 'charitable-apex-charts',
636 charitable()->get_path( 'assets', false ) . 'js/libraries/apexcharts.min.js',
637 array( 'jquery' ),
638 $version,
639 true
640 );
641
642 // Enqueue dashboard specific CSS
643 wp_enqueue_style(
644 'charitable-admin-dashboard',
645 charitable()->get_path( 'assets', false ) . 'css/admin/charitable-admin-dashboard.css',
646 array(),
647 charitable()->get_version()
648 );
649
650 // Enqueue dashboard specific JavaScript with ApexCharts dependency
651 wp_enqueue_script(
652 'charitable-admin-dashboard',
653 $assets_dir . 'js/admin/charitable-admin-dashboard.js',
654 array( 'jquery', 'charitable-apex-charts' ),
655 charitable()->get_version(),
656 true
657 );
658
659 // Enqueue ApexCharts
660 wp_enqueue_script( 'charitable-apex-charts' );
661 }
662
663 // Register these scripts only for checklist, onboarding, and similar pages.
664 if ( ! is_null( $screen ) && ( $screen->id === 'charitable_page_charitable-setup-checklist' ) ) {
665
666 // v 1.8.1.10.
667
668 wp_register_script(
669 'charitable-onboarding',
670 $assets_dir . 'js/admin/charitable-admin-onboarding' . $min . '.js',
671 array( 'jquery' ),
672 $version,
673 true
674 );
675
676 wp_enqueue_script( 'charitable-onboarding' );
677
678 }
679
680 wp_register_script(
681 'charitable-admin-plugins',
682 $assets_dir . 'js/plugins/charitable-admin-plugins' . $min . '.js',
683 array( 'jquery' ),
684 $version,
685 true
686 );
687
688 wp_enqueue_script( 'charitable-admin-plugins' );
689
690 wp_register_script(
691 'charitable-admin-notice',
692 $assets_dir . 'js/admin/charitable-admin-notice' . $min . '.js',
693 array( 'jquery' ),
694 $version,
695 false
696 );
697
698 wp_register_script(
699 'charitable-admin-media',
700 $assets_dir . 'js/admin/charitable-admin-media' . $min . '.js',
701 array( 'jquery' ),
702 $version,
703 false
704 );
705
706 wp_register_script(
707 'lean-modal',
708 $assets_dir . 'js/libraries/leanModal' . $min . '.js',
709 array( 'jquery' ),
710 $version,
711 true
712 );
713
714 wp_register_style(
715 'lean-modal-css',
716 $assets_dir . 'css/modal' . $min . '.css',
717 array(),
718 $version
719 );
720
721 wp_register_script(
722 'charitable-admin-tables',
723 $assets_dir . 'js/admin/charitable-admin-tables' . $min . '.js',
724 array( 'jquery', 'lean-modal' ),
725 $version,
726 true
727 );
728
729 wp_register_script(
730 'select2',
731 $assets_dir . 'js/libraries/select2' . $min . '.js',
732 array( 'jquery' ),
733 $version,
734 true
735 );
736
737 wp_register_style(
738 'select2-css',
739 $assets_dir . 'css/libraries/select2' . $min . '.css',
740 array(),
741 $version
742 );
743
744 do_action( 'after_charitable_admin_enqueue_scripts', $min, $version, $assets_dir ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Hook name is already in use by other code (charitable-admin-hooks.php). Changing it would break existing functionality.
745 }
746
747 /**
748 * Set admin body classes.
749 *
750 * @since 1.5.0
751 * @version 1.8.1.6
752 * @version 1.8.1.16 Added pro/lite check.
753 * @version 1.8.3
754 * @version 1.8.5 Added dashboard and reports classes.
755 *
756 * @param string $classes Existing list of classes.
757 * @return string
758 */
759 public function set_body_class( $classes ) {
760 $screen = get_current_screen();
761
762 if ( 'donation' === $screen->post_type && ( 'add' === $screen->action || isset( $_GET['show_form'] ) ) ) { // phpcs:ignore.
763 $classes .= ' charitable-admin-donation-form';
764 }
765
766 if ( isset( $_GET['page'] ) && ( 'charitable-tools' === $_GET['page'] || 'charitable-settings' === $_GET['page'] ) ) { // phpcs:ignore.
767 $classes .= ' charitable-admin-settings';
768 }
769
770 if ( isset( $_GET['page'] ) && 'charitable-settings' === $_GET['page'] && isset( $_GET['tab'] ) && '' !== $_GET['tab'] ) { // phpcs:ignore.
771 $classes .= ' charitable-admin-settings-' . esc_attr( $_GET['tab'] ); // phpcs:ignore.
772 }
773
774 if ( isset( $_GET['page'] ) && 'charitable-settings' === $_GET['page'] && isset( $_GET['tab'] ) && 'gateways' === $_GET['tab'] && empty( $_GET['group'] ) ) { // phpcs:ignore.
775 $classes .= ' charitable-admin-settings-gateways-main'; // phpcs:ignore.
776 }
777
778 if ( isset( $_GET['page'] ) && 'charitable-reports' === $_GET['page'] ) { // phpcs:ignore.
779 $classes .= ' charitable-admin-reports';
780 }
781
782 if ( isset( $_GET['page'] ) && 'charitable-dashboard' === $_GET['page'] && ! isset( $_GET['tab'] ) ) { // phpcs:ignore.
783 $classes .= ' charitable-admin-reports-dashboard';
784 }
785
786 if ( isset( $_GET['page'] ) && 'charitable-reports' === $_GET['page'] && isset( $_GET['tab'] ) && '' !== $_GET['tab'] ) { // phpcs:ignore.
787 $classes .= ' charitable-admin-reports charitable-admin-reports-' . esc_attr( $_GET['tab'] ); // phpcs:ignore.
788 }
789
790 if ( function_exists( 'charitable_use_legacy_dashboard' ) && charitable_use_legacy_dashboard() ) {
791 $classes .= ' charitable_legacy_dashboard';
792 }
793
794 if ( charitable_show_promotion_footer() ) {
795 $classes .= ' charitable-admin-promotion-footer';
796 }
797
798 $classes .= function_exists( 'charitable_is_pro' ) && charitable_is_pro() ? ' charitable-pro' : ' charitable';
799
800 return $classes;
801 }
802
803 /**
804 * Add notices to the dashboard.
805 *
806 * @since 1.4.0
807 * @version 1.8.3 Added check for PHP Version deprecation.
808 *
809 * @return void
810 */
811 public function add_notices() {
812 if ( charitable_is_debug( 'square' ) ) {
813 error_log( 'Charitable_Admin::add_notices() - Starting to render admin notices' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
814 }
815
816 // Render notices.
817 charitable_get_admin_notices()->render();
818
819 if ( charitable_is_debug( 'square' ) ) {
820 error_log( 'Charitable_Admin::add_notices() - Current admin notices: ' . print_r( charitable_get_admin_notices()->get_notices(), true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log,WordPress.PHP.DevelopmentFunctions.error_log_print_r
821 }
822
823 // Check for PHP version deprecation.
824 if ( version_compare( PHP_VERSION, '7.4', '<' ) ) {
825 $this->php_notice_deprecated();
826 }
827
828 // Add third party notices.
829 $this->add_third_party_notices();
830
831 // Add version update notices.
832 $this->add_version_update_notices();
833 }
834
835 /**
836 * Add notices for potential third party warnings.
837 *
838 * @since 1.7.0.8
839 */
840 public function add_third_party_notices() {
841 if ( ! current_user_can( 'manage_options' ) ) {
842 return;
843 }
844
845 // Allow this feature to be disabled in case unforeseen problems come up.
846 if ( false === apply_filters( 'charitable_donations_export_args', true ) ) {
847 return;
848 }
849
850 $notices = array(
851 /* translators: %s: link */
852 'code-snippets/code-snippets.php' => sprintf(
853 // Translators: %s: link.
854 __( "You appear to be using a code snippet plugin. Please <a href='%s' target='_blank'>review best practices</a> when using snippets to avoid problems when upgrading or deactivating Charitable.", 'charitable' ),
855 'https://wpcharitable.com/code-snippet-best-practices/'
856 ),
857 );
858
859 // Allow this list to be altered by a third party or charitable addon.
860 $notices = apply_filters( 'charitable_admin_third_party_notices', $notices );
861
862 $helper = charitable_get_admin_notices();
863
864 foreach ( $notices as $notice => $message ) {
865 if ( false === charitable_get_third_party_warning_option( 'code-snippets/code-snippets.php' ) || 'dismissed' === charitable_get_third_party_warning_option( 'code-snippets/code-snippets.php' ) ) {
866 continue;
867 }
868
869 $helper->add_third_party_warning( $message, $notice );
870 }
871 }
872
873 /**
874 * Add version update notices to the dashboard.
875 *
876 * @since 1.4.6 (deprecated in 1.8.0)
877 *
878 * @return void
879 */
880 public function add_version_update_notices() {
881 if ( ! current_user_can( 'manage_options' ) ) {
882 return;
883 }
884
885 $notices = array(
886 'release-150' => sprintf(
887 /* translators: %s: link */
888 __( "Charitable 1.5 is packed with new features and improvements. <a href='%s' target='_blank'>Find out what's new</a>.", 'charitable' ),
889 'https://wpcharitable.com/charitable-1-5-release-notes/?utm_source=WordPress&utm_campaign=WP+Charitable&utm_medium=Admin+Notice&utm_content=Version+One+Five+Whats+New'
890 ),
891 'release-160' => sprintf(
892 /* translators: %s: link */
893 __( 'Charitable 1.6 introduces important new user privacy features and other improvements. <a href="%s" target="_blank">Find out what\'s new</a>.', 'charitable' ),
894 'https://www.wpcharitable.com/charitable-1-6-user-privacy-gdpr-better-refunds-and-a-new-shortcode/?utm_source=WordPress&utm_campaign=WP+Charitable&utm_medium=Admin+Notice&utm_content=Version+One+Six+Whats+New'
895 ),
896 );
897
898 $helper = charitable_get_admin_notices();
899
900 foreach ( $notices as $notice => $message ) {
901 if ( ! get_transient( 'charitable_' . $notice . '_notice' ) ) {
902 continue;
903 }
904
905 $helper->add_version_update( $message, $notice );
906 }
907 }
908
909 /**
910 * Add php decpreation notices.
911 *
912 * @since 1.8.3
913 *
914 * @return void
915 */
916 public function php_notice_deprecated() {
917 $medium = charitable_is_pro() ? 'proplugin' : 'liteplugin';
918 ?>
919 <div class="notice notice-error">
920 <p>
921 <?php
922 echo wp_kses(
923 sprintf(
924 // Translators: 1 - Opening HTML bold tag, 2 - Closing HTML bold tag, 3 - Opening HTML link tag, 4 - Closing HTML link tag.
925 __( 'Your site is running an %1$soutdated version%2$s of PHP that is no longer supported and may cause issues with %3$s. Please contact your web hosting provider to update your PHP version or switch to a %4$srecommended WordPress hosting company%5$s.', 'charitable' ), // phpcs:ignore Generic.Files.LineLength.MaxExceeded
926 '<strong>',
927 '</strong>',
928 '<strong>Charitable</strong>',
929 '<a href="https://www.wpbeginner.com/wordpress-hosting/" target="_blank" rel="noopener noreferrer">',
930 '</a>'
931 ),
932 array(
933 'a' => array(
934 'href' => array(),
935 'target' => array(),
936 'rel' => array(),
937 ),
938 'strong' => array(),
939 )
940 );
941 ?>
942 <br><br>
943 <?php
944 echo wp_kses(
945 sprintf(
946 // phpcs:ignore Generic.Files.LineLength.MaxExceeded
947 // Translators: 1 - Opening HTML bold tag, 2 - Closing HTML bold tag, 3 - The PHP version, 4 - The current year, 5 - The short plugin name ("Charitable"), 6 - Opening HTML link tag, 7 - Closing HTML link tag.
948 __( '%1$sNote:%2$s Support for PHP %3$s will be discontinued in %4$s. After this, if no further action is taken, %5$s functionality will be disabled. %6$sRead more for additional information.%7$s', 'charitable' ), // phpcs:ignore Generic.Files.LineLength.MaxExceeded
949 '<strong>',
950 '</strong>',
951 PHP_VERSION,
952 gmdate( 'Y' ),
953 'Charitable',
954 '<a href="https://wpcharitable.com/documentation/supported-php-versions-for-charitable/?utm_source=WordPress&utm_medium=' . $medium . '&utm_campaign=outdated-php-notice" target="_blank" rel="noopener noreferrer">', // phpcs:ignore Generic.Files.LineLength.MaxExceeded
955 '</a>'
956 ),
957 array(
958 'a' => array(
959 'href' => array(),
960 'target' => array(),
961 'rel' => array(),
962 ),
963 'strong' => array(),
964 )
965 );
966 ?>
967 </p>
968 </div>
969
970 <?php
971 // In case this is on plugin activation.
972 if ( isset( $_GET['activate'] ) ) { // phpcs:ignore HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended
973 unset( $_GET['activate'] );
974 }
975 }
976
977 /**
978 * Dismiss a notice.
979 *
980 * @since 1.4.6
981 *
982 * @return void
983 */
984 public function dismiss_notice() {
985 if ( ! isset( $_POST['notice'] ) ) { // phpcs:ignore.
986 wp_send_json_error();
987 }
988
989 $notice = sanitize_text_field( wp_unslash( $_POST['notice'] ) ); // phpcs:ignore.
990
991 // Handle Square connection error notice.
992 if ( 'square_connection_error' === $notice ) {
993 delete_option( 'charitable_square_connection_error_notice' );
994 wp_send_json_success();
995 return;
996 }
997
998 $ret = ( 'noted' === charitable_get_third_party_warning_option( 'code-snippets/code-snippets.php' ) ) ? charitable_set_third_party_warning_option( 'code-snippets/code-snippets.php', 'dismissed' ) : delete_transient( 'charitable_' . $notice . '_notice', true );
999
1000 do_action( 'charitable_dismiss_notice', $_POST ); // phpcs:ignore.
1001
1002 if ( ! $ret ) {
1003 wp_send_json_error( $ret );
1004 }
1005
1006 wp_send_json_success();
1007 }
1008
1009 /**
1010 * Dismiss a banner.
1011 *
1012 * @since 1.7.0
1013 *
1014 * @return void
1015 */
1016 public function dismiss_banner() {
1017 if ( ! isset( $_POST['banner_id'] ) ) { // phpcs:ignore.
1018 wp_send_json_error();
1019 }
1020
1021 $ret = set_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner', 1, WEEK_IN_SECONDS ); // phpcs:ignore.
1022
1023 if ( ! $ret ) {
1024 wp_send_json_error( $ret );
1025 }
1026
1027 wp_send_json_success();
1028 }
1029
1030 /**
1031 * Dismiss a banner.
1032 *
1033 * @since 1.8.0
1034 *
1035 * @return void
1036 */
1037 public function dismiss_list_banner() {
1038 if ( ! isset( $_POST['banner_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
1039 wp_send_json_error();
1040 }
1041
1042 $ret = set_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_list_banner', 1, WEEK_IN_SECONDS ); // phpcs:ignore.
1043
1044 if ( ! $ret ) {
1045 wp_send_json_error( $ret );
1046 }
1047
1048 wp_send_json_success();
1049 }
1050
1051 /**
1052 * Dismiss a five star rating.
1053 *
1054 * @since 1.7.0
1055 *
1056 * @return void
1057 */
1058 public function dismiss_five_star_rating() {
1059 if ( ! isset( $_POST['banner_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
1060 wp_send_json_error();
1061 }
1062
1063 $check = get_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner' ); // phpcs:ignore.
1064
1065 if ( ! $check ) {
1066 $ret = set_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner', true ); // phpcs:ignore.
1067 if ( ! $ret ) {
1068 wp_send_json_error( $ret );
1069 }
1070 }
1071
1072 wp_send_json_success();
1073 }
1074
1075 /**
1076 * Dismiss dashboard notices.
1077 *
1078 * @since 1.8.1.6
1079 *
1080 * @return void
1081 */
1082 public function dismiss_dashboard_notices() {
1083 if ( ! isset( $_POST['notice_ids'] ) ) { // phpcs:ignore.
1084 wp_send_json_error();
1085 }
1086
1087 $ret = delete_transient( 'charitable_dashboard_notices' );
1088
1089 // take a comma delimited $_POST value and turn it into an array that is properly filtered only for alphanumeric values.
1090 $notices = array_filter( array_map( 'sanitize_text_field', explode( ',', $_POST['notice_ids'] ) ) ); // phpcs:ignore
1091
1092 if ( ! empty( $notices ) && in_array( 'donation-security', $notices, true ) ) {
1093 delete_transient( 'charitable_donation_security_checks' );
1094 }
1095
1096 if ( ! $ret ) {
1097 wp_send_json_error( $ret );
1098 }
1099
1100 wp_send_json_success();
1101 }
1102
1103 /**
1104 * Dismiss guide tool notices.
1105 *
1106 * @since 1.8.1.6
1107 *
1108 * @return void
1109 */
1110 public function dismiss_growth_tools_notices() {
1111
1112 // check the nonce via ajax.
1113 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'charitable_dismiss_growth_tools' ) ) { // phpcs:ignore
1114 wp_send_json_error();
1115 }
1116
1117 $notice_type = isset( $_POST['notice_type'] ) ? sanitize_text_field( wp_unslash( $_POST['notice_type'] ) ) : ''; // phpcs:ignore
1118
1119 if ( ! $notice_type ) {
1120 wp_send_json_error();
1121 }
1122
1123 $charitable_growth_tool_notices = get_option( 'charitable_growth_tool_notices' );
1124
1125 if ( ! is_array( $charitable_growth_tool_notices ) ) {
1126 $charitable_growth_tool_notices = array();
1127 }
1128
1129 $charitable_growth_tool_notices['dismiss'][ $notice_type ] = time();
1130
1131 $ret = update_option( 'charitable_growth_tool_notices', $charitable_growth_tool_notices );
1132
1133 if ( ! $ret ) {
1134 wp_send_json_error( $ret );
1135 }
1136
1137 wp_send_json_success();
1138 }
1139
1140 /**
1141 * Dismiss a five star rating.
1142 *
1143 * @since 1.7.0
1144 *
1145 * @return void
1146 */
1147 public function dismiss_lite_cta() {
1148 if ( ! isset( $_POST['charitable_action'] ) ) { // phpcs:ignore.
1149 wp_send_json_error();
1150 }
1151
1152 $updated = update_option( 'charitable_lite_settings_upgrade', true );
1153
1154 if ( $updated ) {
1155 wp_send_json_success();
1156 } else {
1157 wp_send_json_error();
1158 }
1159 }
1160
1161 /**
1162 * Dismiss a five star rating.
1163 *
1164 * @since 1.8.1.6
1165 *
1166 * @return void
1167 */
1168 public function dismiss_lite_reports_cta() {
1169 if ( ! isset( $_POST['charitable_action'] ) ) { // phpcs:ignore.
1170 wp_send_json_error();
1171 }
1172
1173 $updated = update_option( 'charitable_lite_reports_upgrade', true );
1174
1175 if ( $updated ) {
1176 wp_send_json_success();
1177 } else {
1178 wp_send_json_error();
1179 }
1180 }
1181
1182 /**
1183 * Adds one or more classes to the body tag in the dashboard.
1184 *
1185 * @since 1.0.0
1186 *
1187 * @param string $classes Current body classes.
1188 * @return string Altered body classes.
1189 */
1190 public function add_admin_body_class( $classes ) {
1191 $screen = get_current_screen();
1192
1193 if ( in_array( $screen->post_type, array( Charitable::DONATION_POST_TYPE, Charitable::CAMPAIGN_POST_TYPE ) ) ) {
1194 $classes .= ' post-type-charitable';
1195 }
1196
1197 // Add `charitable-settings-page-{tab}` so per-tab CSS (e.g. Payment
1198 // Gateways tab layout overrides in charitable-admin.css) can scope
1199 // itself without affecting other settings tabs.
1200 if ( isset( $_GET['page'] ) && 'charitable-settings' === $_GET['page'] && isset( $_GET['tab'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1201 $classes .= ' charitable-settings-page-' . sanitize_html_class( wp_unslash( $_GET['tab'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1202 }
1203
1204 return $classes;
1205 }
1206
1207 /**
1208 * Add custom links to the plugin actions.
1209 *
1210 * @since 1.0.0
1211 *
1212 * @param string[] $links Plugin action links.
1213 * @return string[]
1214 */
1215 public function add_plugin_action_links( $links ) {
1216 $links[] = '<a href="' . admin_url( 'admin.php?page=charitable-settings' ) . '">' . __( 'Settings', 'charitable' ) . '</a>';
1217 return $links;
1218 }
1219
1220 /**
1221 * Add Extensions link to the plugin row meta.
1222 *
1223 * @since 1.2.0
1224 *
1225 * @param string[] $links Plugin action links.
1226 * @param string $file The plugin file.
1227 * @return string[] $links
1228 */
1229 public function add_plugin_row_meta( $links, $file ) {
1230 if ( plugin_basename( charitable()->get_path() ) != $file ) {
1231 return $links;
1232 }
1233
1234 // Use an updated UTM.
1235 $extensions_link = charitable_ga_url(
1236 'https://wpcharitable.com/extensions/',
1237 urlencode( 'Admin Plugin Page' ),
1238 urlencode( 'Extensions' )
1239 );
1240
1241 $links[] = '<a href="' . $extensions_link . '" target="_blank" rel="noopener">' . __( 'Extensions', 'charitable' ) . '</a>';
1242
1243 return $links;
1244 }
1245
1246 /**
1247 * Remove the jQuery UI styles added by Ninja Forms.
1248 *
1249 * @since 1.2.0
1250 *
1251 * @param string $context Media buttons context.
1252 * @return string
1253 */
1254 public function remove_jquery_ui_styles_nf( $context ) {
1255 wp_dequeue_style( 'jquery-smoothness' );
1256 return $context;
1257 }
1258
1259 /**
1260 * Export donations.
1261 *
1262 * @since 1.3.0
1263 *
1264 * @return false|void Returns false if the export failed. Exits otherwise.
1265 */
1266 public function export_donations() {
1267
1268 if ( ! isset( $_GET['_charitable_export_nonce'] ) || ! wp_verify_nonce( $_GET['_charitable_export_nonce'], 'charitable_export_donations' ) ) { // phpcs:ignore
1269 return false;
1270 }
1271
1272 /**
1273 * Filter the donation export arguments.
1274 *
1275 * @since 1.3.0
1276 *
1277 * @param array $args Export arguments.
1278 */
1279 $export_args = apply_filters(
1280 'charitable_donations_export_args',
1281 array(
1282 'start_date' => isset( $_GET['start_date'] ) ? sanitize_text_field( wp_unslash( $_GET['start_date'] ) ) : '',
1283 'end_date' => isset( $_GET['end_date'] ) ? sanitize_text_field( wp_unslash( $_GET['end_date'] ) ) : '',
1284 'status' => isset( $_GET['status'] ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : '',
1285 'campaign_id' => isset( $_GET['campaign_id'] ) ? sanitize_text_field( wp_unslash( $_GET['campaign_id'] ) ) : '',
1286 'report_type' => isset( $_GET['report_type'] ) ? sanitize_text_field( wp_unslash( $_GET['report_type'] ) ) : '',
1287 )
1288 );
1289
1290 /**
1291 * Filter the export class name.
1292 *
1293 * @since 1.3.0
1294 *
1295 * @param string $report_type The type of report.
1296 * @param array $args Export arguments.
1297 */
1298 $export_class = apply_filters( 'charitable_donations_export_class', 'Charitable_Export_Donations', esc_attr( $_GET['report_type'] ), $export_args ); // phpcs:ignore
1299
1300 new $export_class( $export_args );
1301
1302 die();
1303 }
1304
1305 /**
1306 * Export campaigns.
1307 *
1308 * @since 1.6.0
1309 *
1310 * @return false|void Returns false if the export failed. Exits otherwise.
1311 */
1312 public function export_campaigns() {
1313
1314 if ( ! isset( $_GET['_charitable_export_nonce'] ) || ! wp_verify_nonce( $_GET['_charitable_export_nonce'], 'charitable_export_campaigns' ) ) { // phpcs:ignore
1315 return false;
1316 }
1317
1318 /**
1319 * Filter the campaign export arguments.
1320 *
1321 * @since 1.6.0
1322 *
1323 * @param array $args Export arguments.
1324 */
1325 $export_args = apply_filters(
1326 'charitable_campaigns_export_args',
1327 array(
1328 'start_date_to' => isset( $_GET['start_date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['start_date_to'] ) ) : '',
1329 'end_date_from' => isset( $_GET['end_date_from'] ) ? sanitize_text_field( wp_unslash( $_GET['end_date_from'] ) ) : '',
1330 'end_date_to' => isset( $_GET['end_date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['end_date_to'] ) ) : '',
1331 'status' => isset( $_GET['status'] ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : '',
1332 'report_type' => isset( $_GET['report_type'] ) ? sanitize_text_field( wp_unslash( $_GET['report_type'] ) ) : '',
1333 )
1334 );
1335
1336 /**
1337 * Filter the export class name.
1338 *
1339 * @since 1.6.0
1340 *
1341 * @param string $report_type The type of report.
1342 * @param array $args Export arguments.
1343 */
1344 $export_class = apply_filters( 'charitable_campaigns_export_class', 'Charitable_Export_Campaigns', esc_attr( $_GET['report_type'] ), $export_args ); // phpcs:ignore
1345
1346 new $export_class( $export_args );
1347
1348 die();
1349 }
1350
1351 /**
1352 * Returns an array of screen IDs where the Charitable scripts should be loaded.
1353 * (Deprecated in 1.8.1.15, use charitable_get_charitable_Screens() in core functions instead).
1354 *
1355 * @uses charitable_admin_screens
1356 *
1357 * @since 1.0.0
1358 *
1359 * @return array
1360 */
1361 public function get_charitable_screens() {
1362 /**
1363 * Filter admin screens where Charitable styles & scripts should be loaded.
1364 *
1365 * @since 1.8.0
1366 * @since 1.8.1 Added `charitable_page_charitable-dashboard` and 'charitable_page_charitable-reports' to the list of screens.
1367 * @since 1.8.1.6 Added 'charitable_page_charitable-tools' and 'charitable_page_charitable-growth-tool' to the list of screens.
1368 * @since 1.8.5 Added 'charitable_page_charitable-donors' to the list of screens.
1369 *
1370 * @param string[] $screens List of screen ids.
1371 */
1372 return apply_filters(
1373 'charitable_admin_screens',
1374 array(
1375 'campaign',
1376 'donation',
1377 'charitable_page_charitable-reports',
1378 'charitable_page_charitable-dashboard',
1379 'charitable_page_charitable-settings',
1380 'charitable_page_charitable-tools',
1381 'charitable_page_charitable-growth-tools',
1382 'edit-campaign',
1383 'edit-donation',
1384 'dashboard',
1385 'toplevel_page_charitable',
1386 'charitable_page_charitable-addons',
1387 'charitable_page_charitable-donors',
1388 )
1389 );
1390 }
1391
1392 /**
1393 * Updates the "action row" of campaigns to add the ability to edit with builder.
1394 *
1395 * @since 1.8.0
1396 *
1397 * @param array $actions Actions.
1398 * @param array $post Post data.
1399 *
1400 * @return array
1401 */
1402 public function campaign_action_row( $actions, $post ) {
1403
1404 $actions_first = array();
1405
1406 $suffix = ( charitable_is_debug() || charitable_is_script_debug() ) ? '#charitabledebug' : false;
1407
1408 // Check for the post type.
1409
1410 if ( is_object( $post ) && isset( $post->post_type ) && 'campaign' === $post->post_type && ( ! isset( $_GET['post_status'] ) || ( isset( $_GET['post_status'] ) && 'trash' !== strtolower( $_GET['post_status'] ) ) ) ) { // phpcs:ignore
1411
1412 $post_id = intval( $post->ID );
1413
1414 // Determine if this is a "legacy" campaign.
1415 $is_legacy_campaign = charitable_is_campaign_legacy( $post_id );
1416
1417 if ( isset( $actions['edit'] ) && 0 !== $post_id ) {
1418
1419 $actions_show_id = array( 'id' => esc_html__( 'ID: ', 'charitable' ) . $post_id );
1420 $actions_edit_legacy = array( 'edit' => $actions['edit'] );
1421
1422 unset( $actions['edit'] );
1423
1424 }
1425
1426 if ( defined( 'CHARITABLE_BUILDER_SHOW_LEGACY_EDIT_LINKS' ) && CHARITABLE_BUILDER_SHOW_LEGACY_EDIT_LINKS === true ) {
1427
1428 // Add "edit" link that goes to the builder. The campaign can still be edited w/ legacy via direct link (example https://website.test/wp-admin/post.php?post=246&action=edit).
1429 $actions_edit_legacy = array( 'Edit' => '<a href="' . admin_url( 'post.php?post=' . $post_id . '&action=edit' ) . '">' . esc_html__( 'Legacy Edit', 'charitable' ) . '</a>' );
1430
1431 $actions = ( false === $is_legacy_campaign )
1432 ? $actions_show_id + $actions_edit_legacy + array( 'charitable_builder' => '<a href="' . admin_url( 'admin.php?page=charitable-campaign-builder&campaign_id=' . $post_id ) . $suffix . '">' . esc_html__( 'Edit With Builder', 'charitable' ) . '</a>' ) + $actions
1433 : $actions_show_id + $actions_edit_legacy + $actions;
1434
1435 } else {
1436
1437 $actions = ( false === $is_legacy_campaign )
1438 ? $actions_show_id + array( 'charitable_builder' => '<a href="' . admin_url( 'admin.php?page=charitable-campaign-builder&campaign_id=' . $post_id ) . $suffix . '">' . esc_html__( 'Edit', 'charitable' ) . '</a>' ) + $actions
1439 : $actions_show_id + $actions_edit_legacy + $actions;
1440
1441 }
1442 }
1443
1444 return $actions;
1445 }
1446
1447 /**
1448 * Updates the "post link" of campaigns to add the ability to edit with builder.
1449 *
1450 * @since 1.8.0
1451 * @since 1.8.1 added check for get_current_screen().
1452 *
1453 * @param string $link The current link URL.
1454 * @param integer $post_id Post ID.
1455 * @param array $context Context.
1456 *
1457 * @return array
1458 */
1459 public function campaign_link( $link, $post_id, $context ) { // phpcs:ignore
1460
1461 if ( ! function_exists( 'get_current_screen' ) ) {
1462 return $link;
1463 }
1464
1465 $screen = get_current_screen();
1466 $post_id = intval( $post_id );
1467
1468 if ( 0 === $post_id || ! isset( $screen->id ) || $screen->id !== 'edit-campaign' ) {
1469 return $link;
1470 }
1471
1472 // Determine if this is a "legacy" campaign.
1473 $is_legacy_campaign = charitable_is_campaign_legacy( $post_id );
1474
1475 $suffix = ( charitable_is_debug() || charitable_is_script_debug() ) ? '#charitabledebug' : false;
1476
1477 $new_link = ( false === $is_legacy_campaign )
1478 ? admin_url( 'admin.php?page=charitable-campaign-builder&campaign_id=' . $post_id ) . $suffix
1479 : esc_url( $link );
1480
1481 return $new_link;
1482 }
1483
1484 /**
1485 * Filters the URL used for a campaign preview.
1486 *
1487 * @since 1.8.0
1488 *
1489 * @param string $preview_link URL used for the post preview.
1490 * @param WP_Post $post Post object.
1491 */
1492 public function campaign_preview_link( $preview_link, $post ) {
1493
1494 if ( ! empty( $post ) && 'campaign' === $post->post_type ) {
1495
1496 $preview_link = add_query_arg(
1497 array(
1498 'charitable_campaign_preview' => $post->ID,
1499 ),
1500 $preview_link
1501 );
1502
1503 }
1504
1505 return $preview_link;
1506 }
1507
1508 /**
1509 * Adjustments needed for Elementor.
1510 * Last tested with Elementor Lite 3.24.7.
1511 *
1512 * @since 1.8.3
1513 *
1514 * @param array $post_types_objects The post types.
1515 *
1516 * @return void
1517 */
1518 public function elementor_post_types( $post_types_objects = array() ) {
1519
1520 // If Elementor is not installed, ignore.
1521 if ( ! defined( 'ELEMENTOR_VERSION' ) ) {
1522 return;
1523 }
1524
1525 // Check for the Charitable Elementor integration override.
1526 if ( defined( 'CHARITABLE_ELEMENTOR_ALLOW_POST_TYPE' ) && CHARITABLE_ELEMENTOR_ALLOW_POST_TYPE ) {
1527 return;
1528 }
1529
1530 $post_types_to_remove = apply_filters( 'charitable_elementor_post_types_to_remove', array( 'campaign' ) );
1531
1532 if ( empty( $post_types_to_remove ) || ! is_array( $post_types_to_remove ) ) {
1533 return $post_types_objects;
1534 }
1535
1536 // Remove all the post types from the object array.
1537 foreach ( $post_types_to_remove as $post_type ) {
1538 if ( isset( $post_types_objects[ $post_type ] ) ) {
1539 unset( $post_types_objects[ $post_type ] );
1540 }
1541 }
1542
1543 return $post_types_objects;
1544 }
1545
1546 /**
1547 * Install addon via AJAX.
1548 *
1549 * @since 1.8.7.6
1550 */
1551 public function install_addon() {
1552 // phpcs:disable WordPress.Security.NonceVerification.Missing
1553 // Check nonce.
1554 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1555 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'charitable_admin_addons' ) ) {
1556 wp_send_json_error( 'Invalid nonce' );
1557 }
1558
1559 // Check permissions.
1560 if ( ! charitable_can_install( 'plugin' ) ) {
1561 wp_send_json_error( 'Insufficient permissions' );
1562 }
1563
1564 $plugin = isset( $_POST['plugin'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) : '';
1565 $type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';
1566 // phpcs:enable WordPress.Security.NonceVerification.Missing
1567
1568 if ( empty( $plugin ) ) {
1569 wp_send_json_error( 'Plugin not specified' );
1570 }
1571
1572 // Ensure filesystem access (request credentials if needed) similar to legacy flow.
1573 require_once ABSPATH . 'wp-admin/includes/file.php';
1574 $method = '';
1575 $url = esc_url( admin_url( 'admin.php?page=charitable-about' ) );
1576
1577 ob_start();
1578 $creds = request_filesystem_credentials( $url, $method, false, false, null );
1579 if ( false === $creds ) {
1580 $form = ob_get_clean();
1581 wp_send_json_success( array( 'form' => $form ) );
1582 }
1583
1584 if ( ! WP_Filesystem( $creds ) ) {
1585 ob_start();
1586 request_filesystem_credentials( $url, $method, true, false, null );
1587 $form = ob_get_clean();
1588 wp_send_json_success( array( 'form' => $form ) );
1589 }
1590
1591 // Snapshot installed plugins BEFORE the install so we can identify what
1592 // was actually unzipped. The legacy slug-derivation in
1593 // charitable_get_plugin_basename_from_slug() reduces a download URL like
1594 // `…/charitable-pro-plugin-1.8.13.5.zip` to the slug
1595 // `charitable-pro-plugin-1.8.13.5`, then expects an installed plugin
1596 // directory whose name *starts* with that slug — which doesn't hold for
1597 // any addon whose zip filename includes a version suffix and whose
1598 // directory does not (e.g. Charitable Pro lives at `charitable-pro/`).
1599 // The before/after diff sidesteps that mismatch entirely.
1600 if ( ! function_exists( 'get_plugins' ) ) {
1601 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1602 }
1603 $plugins_before = array_keys( get_plugins() );
1604
1605 // Download and install the plugin.
1606 $result = charitable_install_wporg_plugin( $plugin );
1607
1608 if ( is_wp_error( $result ) ) {
1609 wp_send_json_error( $result->get_error_message() );
1610 }
1611
1612 // Force a fresh re-scan of the plugins directory and compute the diff.
1613 wp_cache_delete( 'plugins', 'plugins' );
1614 $plugins_after = array_keys( get_plugins() );
1615 $newly_installed = array_values( array_diff( $plugins_after, $plugins_before ) );
1616
1617 if ( ! empty( $newly_installed ) ) {
1618 $plugin_basename = $newly_installed[0];
1619 } else {
1620 // Fallback to the legacy slug-derived guess if for some reason no new
1621 // plugin was detected (e.g. the zip overwrote an existing install).
1622 $plugin_basename = charitable_get_plugin_basename_from_slug( $plugin );
1623 }
1624
1625 wp_send_json_success( array(
1626 'msg' => __( 'Plugin installed successfully', 'charitable' ),
1627 'basename' => $plugin_basename,
1628 'is_activated' => false,
1629 ) );
1630 }
1631
1632 /**
1633 * Activate addon via AJAX.
1634 *
1635 * @since 1.8.7.6
1636 */
1637 public function activate_addon() {
1638 // phpcs:disable WordPress.Security.NonceVerification.Missing
1639 // Check nonce.
1640 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1641 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'charitable_admin_addons' ) ) {
1642 wp_send_json_error( 'Invalid nonce' );
1643 }
1644
1645 // Check permissions.
1646 if ( ! charitable_can_activate( 'plugin' ) ) {
1647 wp_send_json_error( 'Insufficient permissions' );
1648 }
1649
1650 $plugin = isset( $_POST['plugin'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) : '';
1651 // phpcs:enable WordPress.Security.NonceVerification.Missing
1652
1653 if ( empty( $plugin ) ) {
1654 wp_send_json_error( 'Plugin not specified' );
1655 }
1656
1657 // Normalize plugin basename if a folder only or slug was passed.
1658 if ( false === strpos( $plugin, '/' ) ) {
1659 $plugin = charitable_get_plugin_basename_from_slug( $plugin );
1660 }
1661
1662 // Activate the plugin.
1663 $result = charitable_activate_wporg_plugin( $plugin );
1664
1665 if ( is_wp_error( $result ) ) {
1666 wp_send_json_error( $result->get_error_message() );
1667 }
1668
1669 $response = array( 'msg' => __( 'Plugin activated successfully', 'charitable' ) );
1670
1671 // Activating Charitable Pro auto-deactivates Charitable Lite, which means
1672 // the screen the user came from (page=charitable-addons, served by Lite)
1673 // no longer exists in the menu. Redirect them to the Charitable dashboard
1674 // so they don't land on a 404 / blank admin page after the AJAX returns.
1675 if ( 0 === strpos( $plugin, 'charitable-pro/' ) ) {
1676 $response['redirect'] = admin_url( 'admin.php?page=charitable-dashboard' );
1677 }
1678
1679 wp_send_json_success( $response );
1680 }
1681
1682 /**
1683 * Deactivate addon via AJAX.
1684 *
1685 * @since 1.8.7.6
1686 */
1687 public function deactivate_addon() {
1688 // phpcs:disable WordPress.Security.NonceVerification.Missing
1689 // Check nonce.
1690 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1691 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'charitable_admin_addons' ) ) {
1692 wp_send_json_error( 'Invalid nonce' );
1693 }
1694
1695 // Check permissions.
1696 if ( ! charitable_can_activate( 'plugin' ) ) {
1697 wp_send_json_error( 'Insufficient permissions' );
1698 }
1699
1700 $plugin = isset( $_POST['plugin'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) : '';
1701 // phpcs:enable WordPress.Security.NonceVerification.Missing
1702
1703 if ( empty( $plugin ) ) {
1704 wp_send_json_error( 'Plugin not specified' );
1705 }
1706
1707 // Deactivate the plugin.
1708 $result = charitable_deactivate_wporg_plugin( $plugin );
1709
1710 if ( is_wp_error( $result ) ) {
1711 wp_send_json_error( $result->get_error_message() );
1712 }
1713
1714 wp_send_json_success( 'Plugin deactivated successfully' );
1715 }
1716 }
1717
1718 endif;
1719