PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.12
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.12
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.12, at includes/admin/class-charitable-admin.php

1,726 lines 57.7 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_enqueue_style(
431 'charitable-notifications-v2',
432 $assets_dir . 'css/admin/notifications-v2' . $min . '.css',
433 array(),
434 $version
435 );
436
437 wp_register_style(
438 'charitable-admin',
439 $assets_dir . 'css/admin/charitable-admin-legacy' . $min . '.css',
440 array(),
441 $version
442 );
443
444 wp_enqueue_style( 'charitable-admin' );
445
446 if ( ! is_null( $screen ) && in_array( $screen->id, charitable_get_charitable_screens() ) ) {
447
448 $dependencies = array( 'jquery-ui-datepicker', 'jquery-ui-tabs', 'jquery-ui-sortable' );
449
450 if ( 'donation' === $screen->id ) {
451 wp_register_script(
452 'accounting',
453 $assets_dir . 'js/libraries/accounting' . $min . '.js',
454 array( 'jquery' ),
455 $version,
456 true
457 );
458
459 $dependencies[] = 'accounting';
460 $localized_vars = array_merge(
461 $localized_vars,
462 array(
463 'currency_format_num_decimals' => esc_attr( charitable_get_currency_helper()->get_decimals() ),
464 'currency_format_decimal_sep' => esc_attr( charitable_get_currency_helper()->get_decimal_separator() ),
465 'currency_format_thousand_sep' => esc_attr( charitable_get_currency_helper()->get_thousands_separator() ),
466 'currency_format' => esc_attr( charitable_get_currency_helper()->get_accounting_js_format() ),
467 )
468 );
469 }
470
471 wp_register_script(
472 'charitable-admin',
473 $assets_dir . 'js/admin/charitable-admin' . $min . '.js',
474 $dependencies,
475 $version,
476 false
477 );
478
479 wp_enqueue_script( 'charitable-admin' );
480
481 // v 1.8.0.
482
483 wp_enqueue_style(
484 'jquery-confirm',
485 charitable()->get_path( 'directory', false ) . 'assets/lib/jquery.confirm/jquery-confirm.min.css',
486 null,
487 '3.3.4'
488 );
489
490 wp_enqueue_style(
491 'charitable-font-awesome',
492 charitable()->get_path( 'directory', false ) . 'assets/lib/font-awesome/font-awesome.min.css',
493 null,
494 '4.7.0'
495 );
496
497 wp_enqueue_script(
498 'jquery-confirm',
499 charitable()->get_path( 'directory', false ) . 'assets/lib/jquery.confirm/jquery-confirm.min.js',
500 array( 'jquery' ),
501 '3.3.4',
502 false
503 );
504
505 wp_enqueue_style(
506 'lity',
507 charitable()->get_path( 'directory', false ) . 'assets/lib/lity/lity.min.css',
508 null,
509 '3.0.0'
510 );
511
512 // Add custom CSS for lity image sizing
513 wp_add_inline_style( 'lity', '
514 .lity-image img {
515 max-width: 90vw !important;
516 max-height: 90vh !important;
517 width: auto !important;
518 height: auto !important;
519 margin: 0 auto !important;
520 }
521 .lity-image .lity-container {
522 max-width: 90vw !important;
523 max-height: 90vh !important;
524 }
525 ' );
526
527 wp_enqueue_script(
528 'lity',
529 charitable()->get_path( 'directory', false ) . 'assets/lib/lity/lity.min.js',
530 array( 'jquery' ),
531 '3.0.0',
532 false
533 );
534
535 wp_register_script(
536 'charitable-admin-2.0',
537 $assets_dir . 'js/admin/charitable-admin-2.0' . $min . '.js',
538 $dependencies,
539 $version,
540 false
541 );
542
543 wp_enqueue_script( 'charitable-admin-2.0' );
544
545 /**
546 * Filter the admin Javascript vars.
547 *
548 * @since 1.0.0
549 *
550 * @param array $localized_vars The vars.
551 */
552 $localized_vars = (array) apply_filters( 'charitable_localized_javascript_vars', $localized_vars );
553
554 wp_localize_script( 'charitable-admin', 'CHARITABLE', $localized_vars );
555
556 // Localize script for charitable-admin-2.0.js
557 wp_localize_script( 'charitable-admin-2.0', 'charitable_admin', array(
558 'autoshow_plugin_notifications' => charitable_get_autoshow_plugin_notifications(),
559 ) );
560
561 /* color picker for admin settings */
562 wp_enqueue_script( 'wp-color-picker', false, false, false, true ); // phpcs:ignore.
563 wp_enqueue_style( 'wp-color-picker' );
564
565 } // end if
566
567 wp_enqueue_script(
568 'charitable-admin-utils',
569 charitable()->get_path( 'directory', false ) . "assets/js/admin/charitable-admin-utils{$min}.js",
570 array( 'jquery' ),
571 charitable()->get_version(),
572 false
573 );
574
575 // Register these scripts only for reports and dashboard pages.
576 if ( ! is_null( $screen ) && ( $screen->id === 'charitable_page_charitable-reports' || $screen->id === 'charitable_page_charitable-dashboard' ) ) {
577
578 // Specific styles for the "overview" and main reporting tabs.
579 if ( empty( $_GET['tab'] ) || ( ! empty( $_GET['tab'] ) && charitable_reports_allow_tab_load_scripts( strtolower( sanitize_text_field( wp_unslash( $_GET['tab'] ) ) ) ) ) ) { // phpcs:ignore
580
581 wp_register_script(
582 'charitable-apex-charts',
583 charitable()->get_path( 'assets', false ) . 'js/libraries/apexcharts.min.js',
584 array( 'jquery' ),
585 $version,
586 true
587 );
588
589 /**
590 * Use WordPress core's Moment.js library.
591 *
592 * WordPress core includes Moment.js and exposes it via wp_enqueue_script('moment').
593 * This library is required as a dependency for daterangepicker.min.js, which powers
594 * the date range picker functionality in the reporting interface.
595 *
596 * @see https://github.com/moment/moment
597 * @since 1.0.0
598 * @version 1.8.8.6
599 */
600 wp_enqueue_script( 'moment' );
601
602 wp_register_script(
603 'charitable-report-date-range-picker',
604 charitable()->get_path( 'assets', false ) . 'js/libraries/daterangepicker.min.js',
605 array( 'jquery', 'moment' ),
606 $version,
607 true
608 );
609
610 wp_register_script(
611 'charitable-reporting',
612 $assets_dir . 'js/admin/charitable-admin-reporting' . $min . '.js',
613 array( 'jquery', 'charitable-apex-charts', 'charitable-report-date-range-picker' ),
614 $version,
615 true
616 );
617
618 wp_enqueue_style(
619 'charitable-report-date-range-picker',
620 charitable()->get_path( 'assets', false ) . 'css/libraries/daterangepicker.css',
621 null,
622 '4.7.0'
623 );
624
625 wp_enqueue_script( 'charitable-apex-charts' );
626 wp_enqueue_script( 'charitable-report-date-range-picker' );
627 wp_enqueue_script( 'charitable-reporting' );
628
629 } elseif ( ! empty( $_GET['tab'] ) && 'analytics' === $_GET['tab'] ) { // phpcs:ignore
630
631 // this loads a specific script for the analytics tab.
632 do_action( 'charitable_admin_enqueue_analytics_scripts' );
633
634 }
635 }
636
637 // Register v2 dashboard specific assets
638 if ( ! is_null( $screen ) && $screen->id === 'charitable_page_charitable-dashboard' && ! charitable_use_legacy_dashboard() ) {
639
640 // Register ApexCharts for the v2 dashboard chart
641 wp_register_script(
642 'charitable-apex-charts',
643 charitable()->get_path( 'assets', false ) . 'js/libraries/apexcharts.min.js',
644 array( 'jquery' ),
645 $version,
646 true
647 );
648
649 // Enqueue dashboard specific CSS
650 wp_enqueue_style(
651 'charitable-admin-dashboard',
652 charitable()->get_path( 'assets', false ) . 'css/admin/charitable-admin-dashboard.css',
653 array(),
654 charitable()->get_version()
655 );
656
657 // Enqueue dashboard specific JavaScript with ApexCharts dependency
658 wp_enqueue_script(
659 'charitable-admin-dashboard',
660 $assets_dir . 'js/admin/charitable-admin-dashboard.js',
661 array( 'jquery', 'charitable-apex-charts' ),
662 charitable()->get_version(),
663 true
664 );
665
666 // Enqueue ApexCharts
667 wp_enqueue_script( 'charitable-apex-charts' );
668 }
669
670 // Register these scripts only for checklist, onboarding, and similar pages.
671 if ( ! is_null( $screen ) && ( $screen->id === 'charitable_page_charitable-setup-checklist' ) ) {
672
673 // v 1.8.1.10.
674
675 wp_register_script(
676 'charitable-onboarding',
677 $assets_dir . 'js/admin/charitable-admin-onboarding' . $min . '.js',
678 array( 'jquery' ),
679 $version,
680 true
681 );
682
683 wp_enqueue_script( 'charitable-onboarding' );
684
685 }
686
687 wp_register_script(
688 'charitable-admin-plugins',
689 $assets_dir . 'js/plugins/charitable-admin-plugins' . $min . '.js',
690 array( 'jquery' ),
691 $version,
692 true
693 );
694
695 wp_enqueue_script( 'charitable-admin-plugins' );
696
697 wp_register_script(
698 'charitable-admin-notice',
699 $assets_dir . 'js/admin/charitable-admin-notice' . $min . '.js',
700 array( 'jquery' ),
701 $version,
702 false
703 );
704
705 wp_register_script(
706 'charitable-admin-media',
707 $assets_dir . 'js/admin/charitable-admin-media' . $min . '.js',
708 array( 'jquery' ),
709 $version,
710 false
711 );
712
713 wp_register_script(
714 'lean-modal',
715 $assets_dir . 'js/libraries/leanModal' . $min . '.js',
716 array( 'jquery' ),
717 $version,
718 true
719 );
720
721 wp_register_style(
722 'lean-modal-css',
723 $assets_dir . 'css/modal' . $min . '.css',
724 array(),
725 $version
726 );
727
728 wp_register_script(
729 'charitable-admin-tables',
730 $assets_dir . 'js/admin/charitable-admin-tables' . $min . '.js',
731 array( 'jquery', 'lean-modal' ),
732 $version,
733 true
734 );
735
736 wp_register_script(
737 'select2',
738 $assets_dir . 'js/libraries/select2' . $min . '.js',
739 array( 'jquery' ),
740 $version,
741 true
742 );
743
744 wp_register_style(
745 'select2-css',
746 $assets_dir . 'css/libraries/select2' . $min . '.css',
747 array(),
748 $version
749 );
750
751 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.
752 }
753
754 /**
755 * Set admin body classes.
756 *
757 * @since 1.5.0
758 * @version 1.8.1.6
759 * @version 1.8.1.16 Added pro/lite check.
760 * @version 1.8.3
761 * @version 1.8.5 Added dashboard and reports classes.
762 *
763 * @param string $classes Existing list of classes.
764 * @return string
765 */
766 public function set_body_class( $classes ) {
767 $screen = get_current_screen();
768
769 if ( 'donation' === $screen->post_type && ( 'add' === $screen->action || isset( $_GET['show_form'] ) ) ) { // phpcs:ignore.
770 $classes .= ' charitable-admin-donation-form';
771 }
772
773 if ( isset( $_GET['page'] ) && ( 'charitable-tools' === $_GET['page'] || 'charitable-settings' === $_GET['page'] ) ) { // phpcs:ignore.
774 $classes .= ' charitable-admin-settings';
775 }
776
777 if ( isset( $_GET['page'] ) && 'charitable-settings' === $_GET['page'] && isset( $_GET['tab'] ) && '' !== $_GET['tab'] ) { // phpcs:ignore.
778 $classes .= ' charitable-admin-settings-' . esc_attr( $_GET['tab'] ); // phpcs:ignore.
779 }
780
781 if ( isset( $_GET['page'] ) && 'charitable-settings' === $_GET['page'] && isset( $_GET['tab'] ) && 'gateways' === $_GET['tab'] && empty( $_GET['group'] ) ) { // phpcs:ignore.
782 $classes .= ' charitable-admin-settings-gateways-main'; // phpcs:ignore.
783 }
784
785 if ( isset( $_GET['page'] ) && 'charitable-reports' === $_GET['page'] ) { // phpcs:ignore.
786 $classes .= ' charitable-admin-reports';
787 }
788
789 if ( isset( $_GET['page'] ) && 'charitable-dashboard' === $_GET['page'] && ! isset( $_GET['tab'] ) ) { // phpcs:ignore.
790 $classes .= ' charitable-admin-reports-dashboard';
791 }
792
793 if ( isset( $_GET['page'] ) && 'charitable-reports' === $_GET['page'] && isset( $_GET['tab'] ) && '' !== $_GET['tab'] ) { // phpcs:ignore.
794 $classes .= ' charitable-admin-reports charitable-admin-reports-' . esc_attr( $_GET['tab'] ); // phpcs:ignore.
795 }
796
797 if ( function_exists( 'charitable_use_legacy_dashboard' ) && charitable_use_legacy_dashboard() ) {
798 $classes .= ' charitable_legacy_dashboard';
799 }
800
801 if ( charitable_show_promotion_footer() ) {
802 $classes .= ' charitable-admin-promotion-footer';
803 }
804
805 $classes .= function_exists( 'charitable_is_pro' ) && charitable_is_pro() ? ' charitable-pro' : ' charitable';
806
807 return $classes;
808 }
809
810 /**
811 * Add notices to the dashboard.
812 *
813 * @since 1.4.0
814 * @version 1.8.3 Added check for PHP Version deprecation.
815 *
816 * @return void
817 */
818 public function add_notices() {
819 if ( charitable_is_debug( 'square' ) ) {
820 error_log( 'Charitable_Admin::add_notices() - Starting to render admin notices' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
821 }
822
823 // Render notices.
824 charitable_get_admin_notices()->render();
825
826 if ( charitable_is_debug( 'square' ) ) {
827 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
828 }
829
830 // Check for PHP version deprecation.
831 if ( version_compare( PHP_VERSION, '7.4', '<' ) ) {
832 $this->php_notice_deprecated();
833 }
834
835 // Add third party notices.
836 $this->add_third_party_notices();
837
838 // Add version update notices.
839 $this->add_version_update_notices();
840 }
841
842 /**
843 * Add notices for potential third party warnings.
844 *
845 * @since 1.7.0.8
846 */
847 public function add_third_party_notices() {
848 if ( ! current_user_can( 'manage_options' ) ) {
849 return;
850 }
851
852 // Allow this feature to be disabled in case unforeseen problems come up.
853 if ( false === apply_filters( 'charitable_donations_export_args', true ) ) {
854 return;
855 }
856
857 $notices = array(
858 /* translators: %s: link */
859 'code-snippets/code-snippets.php' => sprintf(
860 // Translators: %s: link.
861 __( "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' ),
862 'https://wpcharitable.com/code-snippet-best-practices/'
863 ),
864 );
865
866 // Allow this list to be altered by a third party or charitable addon.
867 $notices = apply_filters( 'charitable_admin_third_party_notices', $notices );
868
869 $helper = charitable_get_admin_notices();
870
871 foreach ( $notices as $notice => $message ) {
872 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' ) ) {
873 continue;
874 }
875
876 $helper->add_third_party_warning( $message, $notice );
877 }
878 }
879
880 /**
881 * Add version update notices to the dashboard.
882 *
883 * @since 1.4.6 (deprecated in 1.8.0)
884 *
885 * @return void
886 */
887 public function add_version_update_notices() {
888 if ( ! current_user_can( 'manage_options' ) ) {
889 return;
890 }
891
892 $notices = array(
893 'release-150' => sprintf(
894 /* translators: %s: link */
895 __( "Charitable 1.5 is packed with new features and improvements. <a href='%s' target='_blank'>Find out what's new</a>.", 'charitable' ),
896 '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'
897 ),
898 'release-160' => sprintf(
899 /* translators: %s: link */
900 __( 'Charitable 1.6 introduces important new user privacy features and other improvements. <a href="%s" target="_blank">Find out what\'s new</a>.', 'charitable' ),
901 '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'
902 ),
903 );
904
905 $helper = charitable_get_admin_notices();
906
907 foreach ( $notices as $notice => $message ) {
908 if ( ! get_transient( 'charitable_' . $notice . '_notice' ) ) {
909 continue;
910 }
911
912 $helper->add_version_update( $message, $notice );
913 }
914 }
915
916 /**
917 * Add php decpreation notices.
918 *
919 * @since 1.8.3
920 *
921 * @return void
922 */
923 public function php_notice_deprecated() {
924 $medium = charitable_is_pro() ? 'proplugin' : 'liteplugin';
925 ?>
926 <div class="notice notice-error">
927 <p>
928 <?php
929 echo wp_kses(
930 sprintf(
931 // Translators: 1 - Opening HTML bold tag, 2 - Closing HTML bold tag, 3 - Opening HTML link tag, 4 - Closing HTML link tag.
932 __( '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
933 '<strong>',
934 '</strong>',
935 '<strong>Charitable</strong>',
936 '<a href="https://www.wpbeginner.com/wordpress-hosting/" target="_blank" rel="noopener noreferrer">',
937 '</a>'
938 ),
939 array(
940 'a' => array(
941 'href' => array(),
942 'target' => array(),
943 'rel' => array(),
944 ),
945 'strong' => array(),
946 )
947 );
948 ?>
949 <br><br>
950 <?php
951 echo wp_kses(
952 sprintf(
953 // phpcs:ignore Generic.Files.LineLength.MaxExceeded
954 // 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.
955 __( '%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
956 '<strong>',
957 '</strong>',
958 PHP_VERSION,
959 gmdate( 'Y' ),
960 'Charitable',
961 '<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
962 '</a>'
963 ),
964 array(
965 'a' => array(
966 'href' => array(),
967 'target' => array(),
968 'rel' => array(),
969 ),
970 'strong' => array(),
971 )
972 );
973 ?>
974 </p>
975 </div>
976
977 <?php
978 // In case this is on plugin activation.
979 if ( isset( $_GET['activate'] ) ) { // phpcs:ignore HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended
980 unset( $_GET['activate'] );
981 }
982 }
983
984 /**
985 * Dismiss a notice.
986 *
987 * @since 1.4.6
988 *
989 * @return void
990 */
991 public function dismiss_notice() {
992 if ( ! isset( $_POST['notice'] ) ) { // phpcs:ignore.
993 wp_send_json_error();
994 }
995
996 $notice = sanitize_text_field( wp_unslash( $_POST['notice'] ) ); // phpcs:ignore.
997
998 // Handle Square connection error notice.
999 if ( 'square_connection_error' === $notice ) {
1000 delete_option( 'charitable_square_connection_error_notice' );
1001 wp_send_json_success();
1002 return;
1003 }
1004
1005 $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 );
1006
1007 do_action( 'charitable_dismiss_notice', $_POST ); // phpcs:ignore.
1008
1009 if ( ! $ret ) {
1010 wp_send_json_error( $ret );
1011 }
1012
1013 wp_send_json_success();
1014 }
1015
1016 /**
1017 * Dismiss a banner.
1018 *
1019 * @since 1.7.0
1020 *
1021 * @return void
1022 */
1023 public function dismiss_banner() {
1024 if ( ! isset( $_POST['banner_id'] ) ) { // phpcs:ignore.
1025 wp_send_json_error();
1026 }
1027
1028 $ret = set_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner', 1, WEEK_IN_SECONDS ); // phpcs:ignore.
1029
1030 if ( ! $ret ) {
1031 wp_send_json_error( $ret );
1032 }
1033
1034 wp_send_json_success();
1035 }
1036
1037 /**
1038 * Dismiss a banner.
1039 *
1040 * @since 1.8.0
1041 *
1042 * @return void
1043 */
1044 public function dismiss_list_banner() {
1045 if ( ! isset( $_POST['banner_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
1046 wp_send_json_error();
1047 }
1048
1049 $ret = set_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_list_banner', 1, WEEK_IN_SECONDS ); // phpcs:ignore.
1050
1051 if ( ! $ret ) {
1052 wp_send_json_error( $ret );
1053 }
1054
1055 wp_send_json_success();
1056 }
1057
1058 /**
1059 * Dismiss a five star rating.
1060 *
1061 * @since 1.7.0
1062 *
1063 * @return void
1064 */
1065 public function dismiss_five_star_rating() {
1066 if ( ! isset( $_POST['banner_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
1067 wp_send_json_error();
1068 }
1069
1070 $check = get_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner' ); // phpcs:ignore.
1071
1072 if ( ! $check ) {
1073 $ret = set_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner', true ); // phpcs:ignore.
1074 if ( ! $ret ) {
1075 wp_send_json_error( $ret );
1076 }
1077 }
1078
1079 wp_send_json_success();
1080 }
1081
1082 /**
1083 * Dismiss dashboard notices.
1084 *
1085 * @since 1.8.1.6
1086 *
1087 * @return void
1088 */
1089 public function dismiss_dashboard_notices() {
1090 if ( ! isset( $_POST['notice_ids'] ) ) { // phpcs:ignore.
1091 wp_send_json_error();
1092 }
1093
1094 $ret = delete_transient( 'charitable_dashboard_notices' );
1095
1096 // take a comma delimited $_POST value and turn it into an array that is properly filtered only for alphanumeric values.
1097 $notices = array_filter( array_map( 'sanitize_text_field', explode( ',', $_POST['notice_ids'] ) ) ); // phpcs:ignore
1098
1099 if ( ! empty( $notices ) && in_array( 'donation-security', $notices, true ) ) {
1100 delete_transient( 'charitable_donation_security_checks' );
1101 }
1102
1103 if ( ! $ret ) {
1104 wp_send_json_error( $ret );
1105 }
1106
1107 wp_send_json_success();
1108 }
1109
1110 /**
1111 * Dismiss guide tool notices.
1112 *
1113 * @since 1.8.1.6
1114 *
1115 * @return void
1116 */
1117 public function dismiss_growth_tools_notices() {
1118
1119 // check the nonce via ajax.
1120 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'charitable_dismiss_growth_tools' ) ) { // phpcs:ignore
1121 wp_send_json_error();
1122 }
1123
1124 $notice_type = isset( $_POST['notice_type'] ) ? sanitize_text_field( wp_unslash( $_POST['notice_type'] ) ) : ''; // phpcs:ignore
1125
1126 if ( ! $notice_type ) {
1127 wp_send_json_error();
1128 }
1129
1130 $charitable_growth_tool_notices = get_option( 'charitable_growth_tool_notices' );
1131
1132 if ( ! is_array( $charitable_growth_tool_notices ) ) {
1133 $charitable_growth_tool_notices = array();
1134 }
1135
1136 $charitable_growth_tool_notices['dismiss'][ $notice_type ] = time();
1137
1138 $ret = update_option( 'charitable_growth_tool_notices', $charitable_growth_tool_notices );
1139
1140 if ( ! $ret ) {
1141 wp_send_json_error( $ret );
1142 }
1143
1144 wp_send_json_success();
1145 }
1146
1147 /**
1148 * Dismiss a five star rating.
1149 *
1150 * @since 1.7.0
1151 *
1152 * @return void
1153 */
1154 public function dismiss_lite_cta() {
1155 if ( ! isset( $_POST['charitable_action'] ) ) { // phpcs:ignore.
1156 wp_send_json_error();
1157 }
1158
1159 $updated = update_option( 'charitable_lite_settings_upgrade', true );
1160
1161 if ( $updated ) {
1162 wp_send_json_success();
1163 } else {
1164 wp_send_json_error();
1165 }
1166 }
1167
1168 /**
1169 * Dismiss a five star rating.
1170 *
1171 * @since 1.8.1.6
1172 *
1173 * @return void
1174 */
1175 public function dismiss_lite_reports_cta() {
1176 if ( ! isset( $_POST['charitable_action'] ) ) { // phpcs:ignore.
1177 wp_send_json_error();
1178 }
1179
1180 $updated = update_option( 'charitable_lite_reports_upgrade', true );
1181
1182 if ( $updated ) {
1183 wp_send_json_success();
1184 } else {
1185 wp_send_json_error();
1186 }
1187 }
1188
1189 /**
1190 * Adds one or more classes to the body tag in the dashboard.
1191 *
1192 * @since 1.0.0
1193 *
1194 * @param string $classes Current body classes.
1195 * @return string Altered body classes.
1196 */
1197 public function add_admin_body_class( $classes ) {
1198 $screen = get_current_screen();
1199
1200 if ( in_array( $screen->post_type, array( Charitable::DONATION_POST_TYPE, Charitable::CAMPAIGN_POST_TYPE ) ) ) {
1201 $classes .= ' post-type-charitable';
1202 }
1203
1204 // Add `charitable-settings-page-{tab}` so per-tab CSS (e.g. Payment
1205 // Gateways tab layout overrides in charitable-admin.css) can scope
1206 // itself without affecting other settings tabs.
1207 if ( isset( $_GET['page'] ) && 'charitable-settings' === $_GET['page'] && isset( $_GET['tab'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1208 $classes .= ' charitable-settings-page-' . sanitize_html_class( wp_unslash( $_GET['tab'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1209 }
1210
1211 return $classes;
1212 }
1213
1214 /**
1215 * Add custom links to the plugin actions.
1216 *
1217 * @since 1.0.0
1218 *
1219 * @param string[] $links Plugin action links.
1220 * @return string[]
1221 */
1222 public function add_plugin_action_links( $links ) {
1223 $links[] = '<a href="' . admin_url( 'admin.php?page=charitable-settings' ) . '">' . __( 'Settings', 'charitable' ) . '</a>';
1224 return $links;
1225 }
1226
1227 /**
1228 * Add Extensions link to the plugin row meta.
1229 *
1230 * @since 1.2.0
1231 *
1232 * @param string[] $links Plugin action links.
1233 * @param string $file The plugin file.
1234 * @return string[] $links
1235 */
1236 public function add_plugin_row_meta( $links, $file ) {
1237 if ( plugin_basename( charitable()->get_path() ) != $file ) {
1238 return $links;
1239 }
1240
1241 // Use an updated UTM.
1242 $extensions_link = charitable_ga_url(
1243 'https://wpcharitable.com/extensions/',
1244 urlencode( 'Admin Plugin Page' ),
1245 urlencode( 'Extensions' )
1246 );
1247
1248 $links[] = '<a href="' . $extensions_link . '" target="_blank" rel="noopener">' . __( 'Extensions', 'charitable' ) . '</a>';
1249
1250 return $links;
1251 }
1252
1253 /**
1254 * Remove the jQuery UI styles added by Ninja Forms.
1255 *
1256 * @since 1.2.0
1257 *
1258 * @param string $context Media buttons context.
1259 * @return string
1260 */
1261 public function remove_jquery_ui_styles_nf( $context ) {
1262 wp_dequeue_style( 'jquery-smoothness' );
1263 return $context;
1264 }
1265
1266 /**
1267 * Export donations.
1268 *
1269 * @since 1.3.0
1270 *
1271 * @return false|void Returns false if the export failed. Exits otherwise.
1272 */
1273 public function export_donations() {
1274
1275 if ( ! isset( $_GET['_charitable_export_nonce'] ) || ! wp_verify_nonce( $_GET['_charitable_export_nonce'], 'charitable_export_donations' ) ) { // phpcs:ignore
1276 return false;
1277 }
1278
1279 /**
1280 * Filter the donation export arguments.
1281 *
1282 * @since 1.3.0
1283 *
1284 * @param array $args Export arguments.
1285 */
1286 $export_args = apply_filters(
1287 'charitable_donations_export_args',
1288 array(
1289 'start_date' => isset( $_GET['start_date'] ) ? sanitize_text_field( wp_unslash( $_GET['start_date'] ) ) : '',
1290 'end_date' => isset( $_GET['end_date'] ) ? sanitize_text_field( wp_unslash( $_GET['end_date'] ) ) : '',
1291 'status' => isset( $_GET['status'] ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : '',
1292 'campaign_id' => isset( $_GET['campaign_id'] ) ? sanitize_text_field( wp_unslash( $_GET['campaign_id'] ) ) : '',
1293 'report_type' => isset( $_GET['report_type'] ) ? sanitize_text_field( wp_unslash( $_GET['report_type'] ) ) : '',
1294 )
1295 );
1296
1297 /**
1298 * Filter the export class name.
1299 *
1300 * @since 1.3.0
1301 *
1302 * @param string $report_type The type of report.
1303 * @param array $args Export arguments.
1304 */
1305 $export_class = apply_filters( 'charitable_donations_export_class', 'Charitable_Export_Donations', esc_attr( $_GET['report_type'] ), $export_args ); // phpcs:ignore
1306
1307 new $export_class( $export_args );
1308
1309 die();
1310 }
1311
1312 /**
1313 * Export campaigns.
1314 *
1315 * @since 1.6.0
1316 *
1317 * @return false|void Returns false if the export failed. Exits otherwise.
1318 */
1319 public function export_campaigns() {
1320
1321 if ( ! isset( $_GET['_charitable_export_nonce'] ) || ! wp_verify_nonce( $_GET['_charitable_export_nonce'], 'charitable_export_campaigns' ) ) { // phpcs:ignore
1322 return false;
1323 }
1324
1325 /**
1326 * Filter the campaign export arguments.
1327 *
1328 * @since 1.6.0
1329 *
1330 * @param array $args Export arguments.
1331 */
1332 $export_args = apply_filters(
1333 'charitable_campaigns_export_args',
1334 array(
1335 'start_date_to' => isset( $_GET['start_date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['start_date_to'] ) ) : '',
1336 'end_date_from' => isset( $_GET['end_date_from'] ) ? sanitize_text_field( wp_unslash( $_GET['end_date_from'] ) ) : '',
1337 'end_date_to' => isset( $_GET['end_date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['end_date_to'] ) ) : '',
1338 'status' => isset( $_GET['status'] ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : '',
1339 'report_type' => isset( $_GET['report_type'] ) ? sanitize_text_field( wp_unslash( $_GET['report_type'] ) ) : '',
1340 )
1341 );
1342
1343 /**
1344 * Filter the export class name.
1345 *
1346 * @since 1.6.0
1347 *
1348 * @param string $report_type The type of report.
1349 * @param array $args Export arguments.
1350 */
1351 $export_class = apply_filters( 'charitable_campaigns_export_class', 'Charitable_Export_Campaigns', esc_attr( $_GET['report_type'] ), $export_args ); // phpcs:ignore
1352
1353 new $export_class( $export_args );
1354
1355 die();
1356 }
1357
1358 /**
1359 * Returns an array of screen IDs where the Charitable scripts should be loaded.
1360 * (Deprecated in 1.8.1.15, use charitable_get_charitable_Screens() in core functions instead).
1361 *
1362 * @uses charitable_admin_screens
1363 *
1364 * @since 1.0.0
1365 *
1366 * @return array
1367 */
1368 public function get_charitable_screens() {
1369 /**
1370 * Filter admin screens where Charitable styles & scripts should be loaded.
1371 *
1372 * @since 1.8.0
1373 * @since 1.8.1 Added `charitable_page_charitable-dashboard` and 'charitable_page_charitable-reports' to the list of screens.
1374 * @since 1.8.1.6 Added 'charitable_page_charitable-tools' and 'charitable_page_charitable-growth-tool' to the list of screens.
1375 * @since 1.8.5 Added 'charitable_page_charitable-donors' to the list of screens.
1376 *
1377 * @param string[] $screens List of screen ids.
1378 */
1379 return apply_filters(
1380 'charitable_admin_screens',
1381 array(
1382 'campaign',
1383 'donation',
1384 'charitable_page_charitable-reports',
1385 'charitable_page_charitable-dashboard',
1386 'charitable_page_charitable-settings',
1387 'charitable_page_charitable-tools',
1388 'charitable_page_charitable-growth-tools',
1389 'edit-campaign',
1390 'edit-donation',
1391 'dashboard',
1392 'toplevel_page_charitable',
1393 'charitable_page_charitable-addons',
1394 'charitable_page_charitable-donors',
1395 )
1396 );
1397 }
1398
1399 /**
1400 * Updates the "action row" of campaigns to add the ability to edit with builder.
1401 *
1402 * @since 1.8.0
1403 *
1404 * @param array $actions Actions.
1405 * @param array $post Post data.
1406 *
1407 * @return array
1408 */
1409 public function campaign_action_row( $actions, $post ) {
1410
1411 $actions_first = array();
1412
1413 $suffix = ( charitable_is_debug() || charitable_is_script_debug() ) ? '#charitabledebug' : false;
1414
1415 // Check for the post type.
1416
1417 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
1418
1419 $post_id = intval( $post->ID );
1420
1421 // Determine if this is a "legacy" campaign.
1422 $is_legacy_campaign = charitable_is_campaign_legacy( $post_id );
1423
1424 if ( isset( $actions['edit'] ) && 0 !== $post_id ) {
1425
1426 $actions_show_id = array( 'id' => esc_html__( 'ID: ', 'charitable' ) . $post_id );
1427 $actions_edit_legacy = array( 'edit' => $actions['edit'] );
1428
1429 unset( $actions['edit'] );
1430
1431 }
1432
1433 if ( defined( 'CHARITABLE_BUILDER_SHOW_LEGACY_EDIT_LINKS' ) && CHARITABLE_BUILDER_SHOW_LEGACY_EDIT_LINKS === true ) {
1434
1435 // 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).
1436 $actions_edit_legacy = array( 'Edit' => '<a href="' . admin_url( 'post.php?post=' . $post_id . '&action=edit' ) . '">' . esc_html__( 'Legacy Edit', 'charitable' ) . '</a>' );
1437
1438 $actions = ( false === $is_legacy_campaign )
1439 ? $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
1440 : $actions_show_id + $actions_edit_legacy + $actions;
1441
1442 } else {
1443
1444 $actions = ( false === $is_legacy_campaign )
1445 ? $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
1446 : $actions_show_id + $actions_edit_legacy + $actions;
1447
1448 }
1449 }
1450
1451 return $actions;
1452 }
1453
1454 /**
1455 * Updates the "post link" of campaigns to add the ability to edit with builder.
1456 *
1457 * @since 1.8.0
1458 * @since 1.8.1 added check for get_current_screen().
1459 *
1460 * @param string $link The current link URL.
1461 * @param integer $post_id Post ID.
1462 * @param array $context Context.
1463 *
1464 * @return array
1465 */
1466 public function campaign_link( $link, $post_id, $context ) { // phpcs:ignore
1467
1468 if ( ! function_exists( 'get_current_screen' ) ) {
1469 return $link;
1470 }
1471
1472 $screen = get_current_screen();
1473 $post_id = intval( $post_id );
1474
1475 if ( 0 === $post_id || ! isset( $screen->id ) || $screen->id !== 'edit-campaign' ) {
1476 return $link;
1477 }
1478
1479 // Determine if this is a "legacy" campaign.
1480 $is_legacy_campaign = charitable_is_campaign_legacy( $post_id );
1481
1482 $suffix = ( charitable_is_debug() || charitable_is_script_debug() ) ? '#charitabledebug' : false;
1483
1484 $new_link = ( false === $is_legacy_campaign )
1485 ? admin_url( 'admin.php?page=charitable-campaign-builder&campaign_id=' . $post_id ) . $suffix
1486 : esc_url( $link );
1487
1488 return $new_link;
1489 }
1490
1491 /**
1492 * Filters the URL used for a campaign preview.
1493 *
1494 * @since 1.8.0
1495 *
1496 * @param string $preview_link URL used for the post preview.
1497 * @param WP_Post $post Post object.
1498 */
1499 public function campaign_preview_link( $preview_link, $post ) {
1500
1501 if ( ! empty( $post ) && 'campaign' === $post->post_type ) {
1502
1503 $preview_link = add_query_arg(
1504 array(
1505 'charitable_campaign_preview' => $post->ID,
1506 ),
1507 $preview_link
1508 );
1509
1510 }
1511
1512 return $preview_link;
1513 }
1514
1515 /**
1516 * Adjustments needed for Elementor.
1517 * Last tested with Elementor Lite 3.24.7.
1518 *
1519 * @since 1.8.3
1520 *
1521 * @param array $post_types_objects The post types.
1522 *
1523 * @return void
1524 */
1525 public function elementor_post_types( $post_types_objects = array() ) {
1526
1527 // If Elementor is not installed, ignore.
1528 if ( ! defined( 'ELEMENTOR_VERSION' ) ) {
1529 return;
1530 }
1531
1532 // Check for the Charitable Elementor integration override.
1533 if ( defined( 'CHARITABLE_ELEMENTOR_ALLOW_POST_TYPE' ) && CHARITABLE_ELEMENTOR_ALLOW_POST_TYPE ) {
1534 return;
1535 }
1536
1537 $post_types_to_remove = apply_filters( 'charitable_elementor_post_types_to_remove', array( 'campaign' ) );
1538
1539 if ( empty( $post_types_to_remove ) || ! is_array( $post_types_to_remove ) ) {
1540 return $post_types_objects;
1541 }
1542
1543 // Remove all the post types from the object array.
1544 foreach ( $post_types_to_remove as $post_type ) {
1545 if ( isset( $post_types_objects[ $post_type ] ) ) {
1546 unset( $post_types_objects[ $post_type ] );
1547 }
1548 }
1549
1550 return $post_types_objects;
1551 }
1552
1553 /**
1554 * Install addon via AJAX.
1555 *
1556 * @since 1.8.7.6
1557 */
1558 public function install_addon() {
1559 // phpcs:disable WordPress.Security.NonceVerification.Missing
1560 // Check nonce.
1561 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1562 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'charitable_admin_addons' ) ) {
1563 wp_send_json_error( 'Invalid nonce' );
1564 }
1565
1566 // Check permissions.
1567 if ( ! charitable_can_install( 'plugin' ) ) {
1568 wp_send_json_error( 'Insufficient permissions' );
1569 }
1570
1571 $plugin = isset( $_POST['plugin'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) : '';
1572 $type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';
1573 // phpcs:enable WordPress.Security.NonceVerification.Missing
1574
1575 if ( empty( $plugin ) ) {
1576 wp_send_json_error( 'Plugin not specified' );
1577 }
1578
1579 // Ensure filesystem access (request credentials if needed) similar to legacy flow.
1580 require_once ABSPATH . 'wp-admin/includes/file.php';
1581 $method = '';
1582 $url = esc_url( admin_url( 'admin.php?page=charitable-about' ) );
1583
1584 ob_start();
1585 $creds = request_filesystem_credentials( $url, $method, false, false, null );
1586 if ( false === $creds ) {
1587 $form = ob_get_clean();
1588 wp_send_json_success( array( 'form' => $form ) );
1589 }
1590
1591 if ( ! WP_Filesystem( $creds ) ) {
1592 ob_start();
1593 request_filesystem_credentials( $url, $method, true, false, null );
1594 $form = ob_get_clean();
1595 wp_send_json_success( array( 'form' => $form ) );
1596 }
1597
1598 // Snapshot installed plugins BEFORE the install so we can identify what
1599 // was actually unzipped. The legacy slug-derivation in
1600 // charitable_get_plugin_basename_from_slug() reduces a download URL like
1601 // `…/charitable-pro-plugin-1.8.13.5.zip` to the slug
1602 // `charitable-pro-plugin-1.8.13.5`, then expects an installed plugin
1603 // directory whose name *starts* with that slug — which doesn't hold for
1604 // any addon whose zip filename includes a version suffix and whose
1605 // directory does not (e.g. Charitable Pro lives at `charitable-pro/`).
1606 // The before/after diff sidesteps that mismatch entirely.
1607 if ( ! function_exists( 'get_plugins' ) ) {
1608 require_once ABSPATH . 'wp-admin/includes/plugin.php';
1609 }
1610 $plugins_before = array_keys( get_plugins() );
1611
1612 // Download and install the plugin.
1613 $result = charitable_install_wporg_plugin( $plugin );
1614
1615 if ( is_wp_error( $result ) ) {
1616 wp_send_json_error( $result->get_error_message() );
1617 }
1618
1619 // Force a fresh re-scan of the plugins directory and compute the diff.
1620 wp_cache_delete( 'plugins', 'plugins' );
1621 $plugins_after = array_keys( get_plugins() );
1622 $newly_installed = array_values( array_diff( $plugins_after, $plugins_before ) );
1623
1624 if ( ! empty( $newly_installed ) ) {
1625 $plugin_basename = $newly_installed[0];
1626 } else {
1627 // Fallback to the legacy slug-derived guess if for some reason no new
1628 // plugin was detected (e.g. the zip overwrote an existing install).
1629 $plugin_basename = charitable_get_plugin_basename_from_slug( $plugin );
1630 }
1631
1632 wp_send_json_success( array(
1633 'msg' => __( 'Plugin installed successfully', 'charitable' ),
1634 'basename' => $plugin_basename,
1635 'is_activated' => false,
1636 ) );
1637 }
1638
1639 /**
1640 * Activate addon via AJAX.
1641 *
1642 * @since 1.8.7.6
1643 */
1644 public function activate_addon() {
1645 // phpcs:disable WordPress.Security.NonceVerification.Missing
1646 // Check nonce.
1647 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1648 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'charitable_admin_addons' ) ) {
1649 wp_send_json_error( 'Invalid nonce' );
1650 }
1651
1652 // Check permissions.
1653 if ( ! charitable_can_activate( 'plugin' ) ) {
1654 wp_send_json_error( 'Insufficient permissions' );
1655 }
1656
1657 $plugin = isset( $_POST['plugin'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) : '';
1658 // phpcs:enable WordPress.Security.NonceVerification.Missing
1659
1660 if ( empty( $plugin ) ) {
1661 wp_send_json_error( 'Plugin not specified' );
1662 }
1663
1664 // Normalize plugin basename if a folder only or slug was passed.
1665 if ( false === strpos( $plugin, '/' ) ) {
1666 $plugin = charitable_get_plugin_basename_from_slug( $plugin );
1667 }
1668
1669 // Activate the plugin.
1670 $result = charitable_activate_wporg_plugin( $plugin );
1671
1672 if ( is_wp_error( $result ) ) {
1673 wp_send_json_error( $result->get_error_message() );
1674 }
1675
1676 $response = array( 'msg' => __( 'Plugin activated successfully', 'charitable' ) );
1677
1678 // Activating Charitable Pro auto-deactivates Charitable Lite, which means
1679 // the screen the user came from (page=charitable-addons, served by Lite)
1680 // no longer exists in the menu. Redirect them to the Charitable dashboard
1681 // so they don't land on a 404 / blank admin page after the AJAX returns.
1682 if ( 0 === strpos( $plugin, 'charitable-pro/' ) ) {
1683 $response['redirect'] = admin_url( 'admin.php?page=charitable-dashboard' );
1684 }
1685
1686 wp_send_json_success( $response );
1687 }
1688
1689 /**
1690 * Deactivate addon via AJAX.
1691 *
1692 * @since 1.8.7.6
1693 */
1694 public function deactivate_addon() {
1695 // phpcs:disable WordPress.Security.NonceVerification.Missing
1696 // Check nonce.
1697 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1698 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'charitable_admin_addons' ) ) {
1699 wp_send_json_error( 'Invalid nonce' );
1700 }
1701
1702 // Check permissions.
1703 if ( ! charitable_can_activate( 'plugin' ) ) {
1704 wp_send_json_error( 'Insufficient permissions' );
1705 }
1706
1707 $plugin = isset( $_POST['plugin'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) : '';
1708 // phpcs:enable WordPress.Security.NonceVerification.Missing
1709
1710 if ( empty( $plugin ) ) {
1711 wp_send_json_error( 'Plugin not specified' );
1712 }
1713
1714 // Deactivate the plugin.
1715 $result = charitable_deactivate_wporg_plugin( $plugin );
1716
1717 if ( is_wp_error( $result ) ) {
1718 wp_send_json_error( $result->get_error_message() );
1719 }
1720
1721 wp_send_json_success( 'Plugin deactivated successfully' );
1722 }
1723 }
1724
1725 endif;
1726