PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13 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 All 211 releases
← All changes | includes/admin/class-charitable-admin.php +1288 -112 1.7.0.3 → 1.8.13 View file →
@@ -3,12 +3,12 @@
3 3 * Class that sets up the Charitable Admin functionality.
4 4 *
5 5 * @package Charitable/Classes/Charitable_Admin
6 6 * @author David Bisset
7 - * @copyright Copyright (c) 2022, WP Charitable LLC
7 + * @copyright Copyright (c) 2023, WP Charitable LLC
8 8 * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 9 * @since 1.0.0
10 - * @version 1.6.44
10 + * @version 1.8.4
11 11 */
12 12
13 13 // Exit if accessed directly.
14 14 if ( ! defined( 'ABSPATH' ) ) {
@@ -46,18 +46,319 @@
46 46 * which can only be called during the start phase. In other words, don't try
47 47 * to instantiate this object.
48 48 *
49 49 * @since 1.0.0
50 + * @version 1.8.3 added third party plugin area.
50 51 */
51 52 protected function __construct() {
52 53 $this->load_dependencies();
53 54
54 - $this->donation_actions = new Charitable_Donation_Admin_Actions;
55 + $this->donation_actions = new Charitable_Donation_Admin_Actions();
55 56
57 + // Initialize About page.
58 + new Charitable_About();
59 +
56 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 );
57 83 }
58 84
59 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 + // The wizard finishes by POSTing the user's choices from the
118 + // hosted app straight into this admin, cross-site and
119 + // top-level. Browsers drop that body when a SameSite=Lax auth
120 + // cookie forces a trip through wp-login.php, or when a
121 + // canonical / http-to-https / security-plugin redirect turns
122 + // the POST into a GET. The user is logged in the whole time;
123 + // the cookie simply did not ride along.
124 +
125 + // Applying onboarding choices writes plugin options and
126 + // third-party activation flags, so it is an administrator
127 + // action. This previously ran for any user who could reach
128 + // wp-admin.
129 + if ( ! charitable_current_user_can( 'administrator' ) ) {
130 + wp_safe_redirect( admin_url( 'admin.php?page=charitable-dashboard' ) );
131 + exit;
132 + }
133 +
134 + $charitable_return_data = wp_unslash( $_POST ); // phpcs:ignore
135 +
136 + if ( empty( $charitable_return_data ) && is_admin() ) {
137 + // Body dropped in transit. Recover the choices with a
138 + // server-to-server read: no browser, no cookies, no
139 + // redirects, so nothing that drops the POST applies.
140 + $charitable_return_data = $this->pull_onboarding_return_data();
141 + }
142 +
143 + if ( empty( $charitable_return_data ) ) {
144 + wp_safe_redirect( admin_url( 'admin.php?page=charitable&wpchar_lite=lite&setup=welcome&resume=true&lostconnection=1' ) );
145 + exit;
146 + }
147 +
148 + $this->process_onboarding_return_data( $charitable_return_data );
149 +
150 + wp_safe_redirect( admin_url( 'admin.php?page=charitable-setup&setup=1' ) );
151 + exit;
152 +
153 + case 'cancelled':
154 + // Either user is going "back" to the checklist page or (if the checklist is disabled) the welcome page.
155 + // https://mydomain.com/wp-admin/admin.php?page=charitable&wpchar_lite=lite&setup=cancelled.
156 + $checklist_class = Charitable_Checklist::get_instance();
157 + $checklist_possible = $checklist_class->is_checklist_skipped() || $checklist_class->is_checklist_completed();
158 + $welcome_go_back_url = $checklist_possible ? admin_url( 'admin.php?page=charitable-dashboard' ) : admin_url( 'admin.php?page=charitable-setup-checklist' );
159 +
160 + // Clear the transient.
161 + delete_transient( 'charitable_activation_redirect' );
162 +
163 + // Clear the option.
164 + delete_option( 'charitable_started_onboarding' );
165 +
166 + wp_safe_redirect( $welcome_go_back_url );
167 + exit;
168 +
169 + default:
170 + wp_safe_redirect( admin_url( 'admin.php?page=charitable-dashboard' ) );
171 + exit;
172 +
173 + }
174 + } else {
175 +
176 + wp_safe_redirect( admin_url( 'admin.php?page=charitable-dashboard' ) );
177 + exit;
178 +
179 + }
180 + }
181 + }
182 +
183 + /**
184 + * Apply a set of onboarding choices handed back by the setup wizard.
185 + *
186 + * Extracted verbatim from the `setup=return` branch so the same logic
187 + * serves both routes into it: the cross-site POST the wizard sends, and
188 + * the server-to-server pull used when that POST body is dropped. Keeping
189 + * one implementation is the point - a second decoder would drift.
190 + *
191 + * Expects the same keys and encodings as the POST: `meta`, `pm`,
192 + * `features`, `plugins` and `license_key` base64-encoded, and `template`
193 + * plus the `campaign_*` fields plain. Every field is optional.
194 + *
195 + * @since 1.8.12.4
196 + *
197 + * @param array $data Onboarding fields, already unslashed.
198 + * @return void
199 + */
200 + private function process_onboarding_return_data( $data ) {
201 +
202 + if ( ! is_array( $data ) ) {
203 + return;
204 + }
205 +
206 + $charitable_field = function ( $key ) use ( $data ) {
207 + return isset( $data[ $key ] ) && ! is_array( $data[ $key ] )
208 + ? sanitize_text_field( $data[ $key ] )
209 + : '';
210 + };
211 +
212 + $plugins = '' !== $charitable_field( 'plugins' ) ? base64_decode( $charitable_field( 'plugins' ) ) : '';
213 + $features = '' !== $charitable_field( 'features' ) ? base64_decode( $charitable_field( 'features' ) ) : '';
214 + $meta = '' !== $charitable_field( 'meta' ) ? base64_decode( $charitable_field( 'meta' ) ) : '';
215 + $pm = '' !== $charitable_field( 'pm' ) ? base64_decode( $charitable_field( 'pm' ) ) : '';
216 + $license_key = '' !== $charitable_field( 'license_key' ) ? base64_decode( $charitable_field( 'license_key' ) ) : '';
217 +
218 + // process meta.
219 + if ( ! empty( $meta ) ) {
220 + $meta = str_replace( '\\"', '"', $meta );
221 + if ( ! empty( $meta ) ) {
222 + $meta = json_decode( $meta, true );
223 + }
224 + }
225 + // process plugins.
226 + if ( ! empty( $plugins ) ) {
227 + $plugins = json_decode( $plugins );
228 + $plugins = str_replace( array( '"', '[', ']' ), '', $plugins );
229 + if ( ! empty( $plugins ) ) {
230 + $plugins = explode( ',', $plugins );
231 + }
232 + }
233 +
234 + // process payment methods.
235 + // NOTE: the inner guard tests $plugins, not $pm. That is how this has
236 + // always behaved, so it is preserved here rather than quietly changed
237 + // during a refactor whose whole value is being behaviour-identical.
238 + // Worth fixing on its own, separately.
239 + if ( ! empty( $pm ) ) {
240 + $pm = json_decode( $pm );
241 + if ( $pm ) {
242 + $pm = str_replace( array( '"', '[', ']' ), '', $pm );
243 + if ( ! empty( $plugins ) ) {
244 + $pm = explode( ',', $pm );
245 + }
246 + }
247 + }
248 +
249 + // process features.
250 + if ( ! empty( $features ) ) {
251 + $features = json_decode( $features );
252 + $features = str_replace( array( '"', '[', ']' ), '', $features );
253 + if ( ! empty( $features ) ) {
254 + $features = explode( ',', $features );
255 + }
256 + }
257 +
258 + // clean license (remove quotes).
259 + $license_key = str_replace( '"', '', $license_key );
260 +
261 + // process campaign.
262 + $campaign = array(
263 + 'template' => $charitable_field( 'template' ),
264 + 'title' => $charitable_field( 'campaign_title' ),
265 + 'description' => $charitable_field( 'campaign_description' ),
266 + 'goal' => $charitable_field( 'campaign_goal' ),
267 + 'end_date' => $charitable_field( 'campaign_end_date' ),
268 + );
269 +
270 + // store server side setups.
271 + $serverside = new Charitable_Setup();
272 +
273 + $serverside->store_meta( $meta );
274 + $serverside->store_plugins( $plugins );
275 + $serverside->store_features( $features );
276 + $serverside->store_payment_methods( $pm );
277 + $serverside->store_campaign( $campaign );
278 + // Guarded, so a payload without a licence key never overwrites one
279 + // already stored. The pull payload deliberately never carries one.
280 + if ( $license_key ) {
281 + $serverside->store_license_key( $license_key );
282 + }
283 +
284 + // If this is a select few addons that we need to disable their auto-activation, do so.
285 + if ( ! empty( $plugins ) && is_array( $plugins ) ) {
286 + foreach ( $plugins as $plugin ) {
287 + if ( $plugin === 'all-in-one-seo-pack' ) {
288 + update_option( 'aioseo_activation_redirect', true );
289 + } elseif ( $plugin === 'wp-mail-smtp' ) {
290 + update_option( 'wp_mail_smtp_activation_prevent_redirect', true );
291 + }
292 + }
293 + }
294 +
295 + // add a transient to indicate that the server side onboarding has moved to the plugin.
296 + set_transient( 'charitable_ss_onboarding', 1, 0 );
297 +
298 + delete_option( 'charitable_started_onboarding' );
299 + }
300 +
301 + /**
302 + * Read the onboarding choices back from the wizard, server to server.
303 + *
304 + * Used only when the wizard's cross-site POST arrived with no body. A
305 + * plain server-side GET has no browser, no cookies and no redirects, so
306 + * none of the mechanisms that drop that POST can affect it.
307 + *
308 + * Returns an empty array on any doubt at all - disabled, missing
309 + * helpers, transport error, non-200, unparseable body, or a payload that
310 + * does not say `status: ok`. The caller then falls back to exactly the
311 + * behaviour that shipped before this existed.
312 + *
313 + * @since 1.8.12.4
314 + *
315 + * @return array Onboarding fields, or an empty array.
316 + */
317 + private function pull_onboarding_return_data() {
318 +
319 + // Field kill switch: both of these turn the fallback off without
320 + // needing a release, reverting to the previous behaviour exactly.
321 + if ( ! apply_filters( 'charitable_onboarding_pull_enabled', true ) ) {
322 + return array();
323 + }
324 +
325 + if ( defined( 'CHARITABLE_ONBOARDING_DISABLE_PULL' ) && CHARITABLE_ONBOARDING_DISABLE_PULL ) {
326 + return array();
327 + }
328 +
329 + if ( ! function_exists( 'charitable_onboarding_app_url' ) || ! function_exists( 'charitable_get_site_token' ) ) {
330 + return array();
331 + }
332 +
333 + $site_token = charitable_get_site_token();
334 +
335 + if ( empty( $site_token ) ) {
336 + return array();
337 + }
338 +
339 + $response = wp_remote_get(
340 + charitable_onboarding_app_url() . '/setup-wizard-charitable_lite/pull/' . rawurlencode( $site_token ),
341 + array(
342 + 'timeout' => 10,
343 + 'sslverify' => true,
344 + )
345 + );
346 +
347 + if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
348 + return array();
349 + }
350 +
351 + $body = json_decode( wp_remote_retrieve_body( $response ), true );
352 +
353 + if ( ! is_array( $body ) || empty( $body['status'] ) || 'ok' !== $body['status'] ) {
354 + return array();
355 + }
356 +
357 + return $body;
358 + }
359 +
360 + /**
60 361 * Returns and/or create the single instance of this class.
61 362 *
62 363 * @since 1.2.0
63 364 *
@@ -80,13 +381,33 @@
80 381 */
81 382 private function load_dependencies() {
82 383 $admin_dir = charitable()->get_path( 'includes' ) . 'admin/';
83 384
84 - require_once( $admin_dir . 'charitable-core-admin-functions.php' );
85 - require_once( $admin_dir . 'campaigns/charitable-admin-campaign-hooks.php' );
86 - require_once( $admin_dir . 'dashboard-widgets/charitable-dashboard-widgets-hooks.php' );
87 - require_once( $admin_dir . 'donations/charitable-admin-donation-hooks.php' );
88 - require_once( $admin_dir . 'settings/charitable-settings-admin-hooks.php' );
385 + require_once $admin_dir . 'charitable-core-admin-functions.php';
386 + require_once $admin_dir . 'campaigns/charitable-admin-campaign-hooks.php';
387 + require_once $admin_dir . 'dashboard/class-charitable-dashboard-ajax.php';
388 + require_once $admin_dir . 'charitable-dashboard-hooks.php';
389 + require_once $admin_dir . 'dashboard-widgets/charitable-dashboard-widgets-hooks.php';
390 + require_once $admin_dir . 'donations/charitable-admin-donation-hooks.php';
391 + require_once $admin_dir . 'settings/charitable-settings-admin-hooks.php';
392 + // Security hooks are now loaded in charitable.php to ensure CAPTCHA works on frontend.
393 + // require_once $admin_dir . 'security/charitable-security-hooks.php';
394 + require_once $admin_dir . 'activities/charitable-admin-activity-hooks.php';
395 + require_once $admin_dir . 'reports/charitable-core-reports-functions.php';
396 + require_once $admin_dir . 'reports/charitable-admin-reports-hooks.php';
397 + require_once $admin_dir . 'plugins/class-charitable-admin-plugins-third-party.php';
398 + require_once $admin_dir . 'tools/charitable-tools-admin-hooks.php';
399 + require_once $admin_dir . 'growth-tools/charitable-growth-tools-admin-hooks.php';
400 + require_once $admin_dir . 'onboarding/charitable-onboarding-admin-hooks.php';
401 + require_once $admin_dir . 'tracking/charitable-tracking-admin-hooks.php';
402 + require_once $admin_dir . 'smtp/charitable-smtp-admin-hooks.php';
403 + require_once $admin_dir . 'class-charitable-about.php';
404 + require_once $admin_dir . 'charitable-admin-addons-functions.php';
405 + require_once $admin_dir . 'privacy-compliance/charitable-privacy-compliance-admin-hooks.php';
406 + require_once $admin_dir . 'intergrations/charitable-privacy-compliance-admin-hooks.php';
407 + require_once $admin_dir . 'intergrations/charitable-backups-admin-hooks.php';
408 + require_once $admin_dir . 'intergrations/charitable-seo-admin-hooks.php';
409 + require_once $admin_dir . 'intergrations/charitable-automation-admin-hooks.php';
89 410 }
90 411
91 412 /**
92 413 * Get Charitable_Donation_Admin_Actions class.
@@ -106,25 +427,27 @@
106 427 *
107 428 * @return boolean|WP_Error WP_Error in case of error. Mixed results if the action was performed.
108 429 */
109 430 public function maybe_do_admin_action() {
431 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
110 432 if ( ! array_key_exists( 'charitable_admin_action', $_GET ) ) {
111 433 return false;
112 434 }
113 435
436 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Check for required keys only
114 437 if ( count( array_diff( array( 'action_type', '_nonce', 'object_id' ), array_keys( $_GET ) ) ) ) {
115 438 return new WP_Error( __( 'Action could not be executed.', 'charitable' ) );
116 439 }
117 440
118 - if ( ! wp_verify_nonce( $_GET['_nonce'], 'donation_action' ) ) {
441 + if ( ! isset( $_GET['_nonce'] ) || ! wp_verify_nonce( $_GET['_nonce'], 'donation_action' ) ) { // phpcs:ignore.
119 442 return new WP_Error( __( 'Action could not be executed. Nonce check failed.', 'charitable' ) );
120 443 }
121 444
122 - if ( 'donation' != $_GET['action_type'] ) {
445 + if ( ! isset( $_GET['action_type'] ) || 'donation' !== $_GET['action_type'] ) {
123 446 return new WP_Error( __( 'Action from an unknown action type executed.', 'charitable' ) );
124 447 }
125 448
126 - return $this->donation_actions->do_action( $_GET['charitable_admin_action'], $_GET['object_id'] );
449 + return $this->donation_actions->do_action( esc_html( $_GET['charitable_admin_action'] ), esc_html( $_GET['object_id'] ) ); // phpcs:ignore.
127 450 }
128 451
129 452 /**
130 453 * Loads admin-only scripts and stylesheets.
@@ -129,26 +452,39 @@
129 452 /**
130 453 * Loads admin-only scripts and stylesheets.
131 454 *
132 455 * @since 1.0.0
456 + * @version 1.8.8.6
133 457 *
134 458 * @return void
135 459 */
136 460 public function admin_enqueue_scripts() {
137 - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
138 - $suffix = '';
139 - $version = '';
140 - } else {
141 - $suffix = '.min';
142 - $version = charitable()->get_version();
143 - }
144 461
462 + $min = charitable_get_min_suffix(); // 1.8.3
463 + $version = charitable_is_break_cache() ? charitable()->get_version() . '.' . time() : charitable()->get_version();
464 +
145 465 $assets_dir = charitable()->get_path( 'assets', false );
146 466
467 + $localized_vars = array(
468 + 'suggested_amount_placeholder' => __( 'Amount', 'charitable' ),
469 + 'suggested_amount_description_placeholder' => __( 'Optional Description', 'charitable' ),
470 + 'ajax_url' => admin_url( 'admin-ajax.php' ),
471 + 'nonce' => wp_create_nonce( 'charitable_admin_nonce' ),
472 + 'heads_up' => __( 'Heads Up!', 'charitable' ),
473 + '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' ),
474 + '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' ),
475 + 'oops' => __( 'Oops!', 'charitable' ),
476 + 'square_legacy_switch_error' => __( 'There was an error switching the payment gateways. Please try again.', 'charitable' ),
477 + 'ok' => __( 'OK', 'charitable' ),
478 + 'cancel' => __( 'Cancel', 'charitable' ),
479 + 'square_legacy_active' => charitable_get_helper( 'gateways' )->is_active_gateway( 'square' ),
480 + 'square_core_active' => charitable_get_helper( 'gateways' )->is_active_gateway( 'square_core' ),
481 + );
482 +
147 483 /* Menu styles are loaded everywhere in the WordPress dashboard. */
148 484 wp_register_style(
149 485 'charitable-admin-menu',
150 - $assets_dir . 'css/charitable-admin-menu' . $suffix . '.css',
486 + $assets_dir . 'css/admin/charitable-admin-menu' . $min . '.css',
151 487 array(),
152 488 $version
153 489 );
154 490
@@ -155,9 +491,9 @@
155 491 wp_enqueue_style( 'charitable-admin-menu' );
156 492
157 493 wp_register_script(
158 494 'charitable-admin-pages-all',
159 - $assets_dir . 'js/charitable-admin-pages' . $suffix . '.js',
495 + $assets_dir . 'js/admin/charitable-admin-pages' . $min . '.js',
160 496 array( 'jquery' ),
161 497 $version,
162 498 false
163 499 );
@@ -166,9 +502,9 @@
166 502
167 503 /* Admin page styles are registered but only enqueued when necessary. */
168 504 wp_register_style(
169 505 'charitable-admin-pages',
170 - $assets_dir . 'css/charitable-admin-pages' . $suffix . '.css',
506 + $assets_dir . 'css/admin/charitable-admin-pages' . $min . '.css',
171 507 array(),
172 508 $version
173 509 );
174 510
@@ -175,13 +511,13 @@
175 511 /* The following styles are only loaded on Charitable screens. */
176 512 $screen = get_current_screen();
177 513
178 514 /* This check covers the category and tags pages, only load the main admin css (upgrade banner, etc.) */
179 - if ( ! is_null( $screen ) && in_array( $screen->base, array( 'edit-tags', 'edit-categories' ) ) ) {
515 + 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 ) ) ) ) {
180 516
181 517 wp_register_style(
182 518 'charitable-admin',
183 - $assets_dir . 'css/charitable-admin' . $suffix . '.css',
519 + $assets_dir . 'css/admin/charitable-admin-legacy' . $min . '.css',
184 520 array(),
185 521 $version
186 522 );
187 523
@@ -188,9 +524,9 @@
188 524 wp_enqueue_style( 'charitable-admin' );
189 525
190 526 wp_register_script(
191 527 'charitable-admin-notice',
192 - $assets_dir . 'js/charitable-admin-notice' . $suffix . '.js',
528 + $assets_dir . 'js/admin/charitable-admin-notice' . $min . '.js',
193 529 array( 'jquery' ),
194 530 $version,
195 531 false
196 532 );
@@ -196,29 +532,42 @@
196 532 );
197 533
198 534 }
199 535
200 - if ( ! is_null( $screen ) && in_array( $screen->id, $this->get_charitable_screens() ) ) {
536 + // v 1.8.0.
537 + wp_register_style(
538 + 'charitable-admin-2.0',
539 + $assets_dir . 'css/admin/charitable-admin' . $min . '.css',
540 + array(),
541 + $version
542 + );
201 543
202 - wp_register_style(
203 - 'charitable-admin',
204 - $assets_dir . 'css/charitable-admin' . $suffix . '.css',
205 - array(),
206 - $version
207 - );
544 + wp_enqueue_style( 'charitable-admin-2.0' );
208 545
209 - wp_enqueue_style( 'charitable-admin' );
546 + wp_enqueue_style(
547 + 'charitable-notifications-v2',
548 + $assets_dir . 'css/admin/notifications-v2' . $min . '.css',
549 + array(),
550 + $version
551 + );
210 552
211 - $dependencies = array( 'jquery-ui-datepicker', 'jquery-ui-tabs', 'jquery-ui-sortable' );
212 - $localized_vars = array(
213 - 'suggested_amount_placeholder' => __( 'Amount', 'charitable' ),
214 - 'suggested_amount_description_placeholder' => __( 'Optional Description', 'charitable' ),
215 - );
553 + wp_register_style(
554 + 'charitable-admin',
555 + $assets_dir . 'css/admin/charitable-admin-legacy' . $min . '.css',
556 + array(),
557 + $version
558 + );
216 559
217 - if ( 'donation' == $screen->id ) {
560 + wp_enqueue_style( 'charitable-admin' );
561 +
562 + if ( ! is_null( $screen ) && in_array( $screen->id, charitable_get_charitable_screens() ) ) {
563 +
564 + $dependencies = array( 'jquery-ui-datepicker', 'jquery-ui-tabs', 'jquery-ui-sortable' );
565 +
566 + if ( 'donation' === $screen->id ) {
218 567 wp_register_script(
219 568 'accounting',
220 - $assets_dir . 'js/libraries/accounting'. $suffix . '.js',
569 + $assets_dir . 'js/libraries/accounting' . $min . '.js',
221 570 array( 'jquery' ),
222 571 $version,
223 572 true
224 573 );
@@ -236,9 +585,9 @@
236 585 }
237 586
238 587 wp_register_script(
239 588 'charitable-admin',
240 - $assets_dir . 'js/charitable-admin' . $suffix . '.js',
589 + $assets_dir . 'js/admin/charitable-admin' . $min . '.js',
241 590 $dependencies,
242 591 $version,
243 592 false
244 593 );
@@ -244,8 +593,72 @@
244 593 );
245 594
246 595 wp_enqueue_script( 'charitable-admin' );
247 596
597 + // v 1.8.0.
598 +
599 + wp_enqueue_style(
600 + 'jquery-confirm',
601 + charitable()->get_path( 'directory', false ) . 'assets/lib/jquery.confirm/jquery-confirm.min.css',
602 + null,
603 + '3.3.4'
604 + );
605 +
606 + wp_enqueue_style(
607 + 'charitable-font-awesome',
608 + charitable()->get_path( 'directory', false ) . 'assets/lib/font-awesome/font-awesome.min.css',
609 + null,
610 + '4.7.0'
611 + );
612 +
613 + wp_enqueue_script(
614 + 'jquery-confirm',
615 + charitable()->get_path( 'directory', false ) . 'assets/lib/jquery.confirm/jquery-confirm.min.js',
616 + array( 'jquery' ),
617 + '3.3.4',
618 + false
619 + );
620 +
621 + wp_enqueue_style(
622 + 'lity',
623 + charitable()->get_path( 'directory', false ) . 'assets/lib/lity/lity.min.css',
624 + null,
625 + '3.0.0'
626 + );
627 +
628 + // Add custom CSS for lity image sizing
629 + wp_add_inline_style( 'lity', '
630 + .lity-image img {
631 + max-width: 90vw !important;
632 + max-height: 90vh !important;
633 + width: auto !important;
634 + height: auto !important;
635 + margin: 0 auto !important;
636 + }
637 + .lity-image .lity-container {
638 + max-width: 90vw !important;
639 + max-height: 90vh !important;
640 + }
641 + ' );
642 +
643 + wp_enqueue_script(
644 + 'lity',
645 + charitable()->get_path( 'directory', false ) . 'assets/lib/lity/lity.min.js',
646 + array( 'jquery' ),
647 + '3.0.0',
648 + false
649 + );
650 +
651 + wp_register_script(
652 + 'charitable-admin-2.0',
653 + $assets_dir . 'js/admin/charitable-admin-2.0' . $min . '.js',
654 + $dependencies,
655 + $version,
656 + false
657 + );
658 +
659 + wp_enqueue_script( 'charitable-admin-2.0' );
660 +
248 661 /**
249 662 * Filter the admin Javascript vars.
250 663 *
251 664 * @since 1.0.0
@@ -251,38 +664,156 @@
251 664 * @since 1.0.0
252 665 *
253 666 * @param array $localized_vars The vars.
254 667 */
255 - $localized_vars = apply_filters( 'charitable_localized_javascript_vars', $localized_vars );
668 + $localized_vars = (array) apply_filters( 'charitable_localized_javascript_vars', $localized_vars );
256 669
257 670 wp_localize_script( 'charitable-admin', 'CHARITABLE', $localized_vars );
258 671
259 - $strings = [
260 - 'nonce' => wp_create_nonce( 'charitable-admin' ),
261 - 'ajax_url' => admin_url( 'admin-ajax.php' ),
262 - 'davidtest' => 1,
263 - ];
672 + // Localize script for charitable-admin-2.0.js
673 + wp_localize_script( 'charitable-admin-2.0', 'charitable_admin', array(
674 + 'autoshow_plugin_notifications' => charitable_get_autoshow_plugin_notifications(),
675 + ) );
264 676
265 - /**
266 - * Allow theme/plugin developers to adjust main strings on backend/admin part.
267 - *
268 - * @since 1.7.0.3
269 - *
270 - * @param array $strings Main admin localized strings.
271 - */
272 - $strings = (array) apply_filters( 'charitable_admin_strings', $strings );
677 + /* color picker for admin settings */
678 + wp_enqueue_script( 'wp-color-picker', false, false, false, true ); // phpcs:ignore.
679 + wp_enqueue_style( 'wp-color-picker' );
273 680
274 - wp_localize_script(
275 - 'charitable-admin',
276 - 'charitable_admin',
277 - $strings
681 + } // end if
682 +
683 + wp_enqueue_script(
684 + 'charitable-admin-utils',
685 + charitable()->get_path( 'directory', false ) . "assets/js/admin/charitable-admin-utils{$min}.js",
686 + array( 'jquery' ),
687 + charitable()->get_version(),
688 + false
689 + );
690 +
691 + // Register these scripts only for reports and dashboard pages.
692 + if ( ! is_null( $screen ) && ( $screen->id === 'charitable_page_charitable-reports' || $screen->id === 'charitable_page_charitable-dashboard' ) ) {
693 +
694 + // Specific styles for the "overview" and main reporting tabs.
695 + if ( empty( $_GET['tab'] ) || ( ! empty( $_GET['tab'] ) && charitable_reports_allow_tab_load_scripts( strtolower( sanitize_text_field( wp_unslash( $_GET['tab'] ) ) ) ) ) ) { // phpcs:ignore
696 +
697 + wp_register_script(
698 + 'charitable-apex-charts',
699 + charitable()->get_path( 'assets', false ) . 'js/libraries/apexcharts.min.js',
700 + array( 'jquery' ),
701 + $version,
702 + true
703 + );
704 +
705 + /**
706 + * Use WordPress core's Moment.js library.
707 + *
708 + * WordPress core includes Moment.js and exposes it via wp_enqueue_script('moment').
709 + * This library is required as a dependency for daterangepicker.min.js, which powers
710 + * the date range picker functionality in the reporting interface.
711 + *
712 + * @see https://github.com/moment/moment
713 + * @since 1.0.0
714 + * @version 1.8.8.6
715 + */
716 + wp_enqueue_script( 'moment' );
717 +
718 + wp_register_script(
719 + 'charitable-report-date-range-picker',
720 + charitable()->get_path( 'assets', false ) . 'js/libraries/daterangepicker.min.js',
721 + array( 'jquery', 'moment' ),
722 + $version,
723 + true
724 + );
725 +
726 + wp_register_script(
727 + 'charitable-reporting',
728 + $assets_dir . 'js/admin/charitable-admin-reporting' . $min . '.js',
729 + array( 'jquery', 'charitable-apex-charts', 'charitable-report-date-range-picker' ),
730 + $version,
731 + true
732 + );
733 +
734 + wp_enqueue_style(
735 + 'charitable-report-date-range-picker',
736 + charitable()->get_path( 'assets', false ) . 'css/libraries/daterangepicker.css',
737 + null,
738 + '4.7.0'
739 + );
740 +
741 + wp_enqueue_script( 'charitable-apex-charts' );
742 + wp_enqueue_script( 'charitable-report-date-range-picker' );
743 + wp_enqueue_script( 'charitable-reporting' );
744 +
745 + } elseif ( ! empty( $_GET['tab'] ) && 'analytics' === $_GET['tab'] ) { // phpcs:ignore
746 +
747 + // this loads a specific script for the analytics tab.
748 + do_action( 'charitable_admin_enqueue_analytics_scripts' );
749 +
750 + }
751 + }
752 +
753 + // Register v2 dashboard specific assets
754 + if ( ! is_null( $screen ) && $screen->id === 'charitable_page_charitable-dashboard' && ! charitable_use_legacy_dashboard() ) {
755 +
756 + // Register ApexCharts for the v2 dashboard chart
757 + wp_register_script(
758 + 'charitable-apex-charts',
759 + charitable()->get_path( 'assets', false ) . 'js/libraries/apexcharts.min.js',
760 + array( 'jquery' ),
761 + $version,
762 + true
278 763 );
279 764
280 - } // end if
765 + // Enqueue dashboard specific CSS
766 + wp_enqueue_style(
767 + 'charitable-admin-dashboard',
768 + charitable()->get_path( 'assets', false ) . 'css/admin/charitable-admin-dashboard.css',
769 + array(),
770 + charitable()->get_version()
771 + );
281 772
773 + // Enqueue dashboard specific JavaScript with ApexCharts dependency
774 + wp_enqueue_script(
775 + 'charitable-admin-dashboard',
776 + $assets_dir . 'js/admin/charitable-admin-dashboard.js',
777 + array( 'jquery', 'charitable-apex-charts' ),
778 + charitable()->get_version(),
779 + true
780 + );
781 +
782 + // Enqueue ApexCharts
783 + wp_enqueue_script( 'charitable-apex-charts' );
784 + }
785 +
786 + // Register these scripts only for checklist, onboarding, and similar pages.
787 + if ( ! is_null( $screen ) && ( $screen->id === 'charitable_page_charitable-setup-checklist' ) ) {
788 +
789 + // v 1.8.1.10.
790 +
791 + wp_register_script(
792 + 'charitable-onboarding',
793 + $assets_dir . 'js/admin/charitable-admin-onboarding' . $min . '.js',
794 + array( 'jquery' ),
795 + $version,
796 + true
797 + );
798 +
799 + wp_enqueue_script( 'charitable-onboarding' );
800 +
801 + }
802 +
282 803 wp_register_script(
804 + 'charitable-admin-plugins',
805 + $assets_dir . 'js/plugins/charitable-admin-plugins' . $min . '.js',
806 + array( 'jquery' ),
807 + $version,
808 + true
809 + );
810 +
811 + wp_enqueue_script( 'charitable-admin-plugins' );
812 +
813 + wp_register_script(
283 814 'charitable-admin-notice',
284 - $assets_dir . 'js/charitable-admin-notice' . $suffix . '.js',
815 + $assets_dir . 'js/admin/charitable-admin-notice' . $min . '.js',
285 816 array( 'jquery' ),
286 817 $version,
287 818 false
288 819 );
@@ -288,9 +819,9 @@
288 819 );
289 820
290 821 wp_register_script(
291 822 'charitable-admin-media',
292 - $assets_dir . 'js/charitable-admin-media' . $suffix . '.js',
823 + $assets_dir . 'js/admin/charitable-admin-media' . $min . '.js',
293 824 array( 'jquery' ),
294 825 $version,
295 826 false
296 827 );
@@ -296,9 +827,9 @@
296 827 );
297 828
298 829 wp_register_script(
299 830 'lean-modal',
300 - $assets_dir . 'js/libraries/leanModal' . $suffix . '.js',
831 + $assets_dir . 'js/libraries/leanModal' . $min . '.js',
301 832 array( 'jquery' ),
302 833 $version,
303 834 true
304 835 );
@@ -304,9 +835,9 @@
304 835 );
305 836
306 837 wp_register_style(
307 838 'lean-modal-css',
308 - $assets_dir . 'css/modal' . $suffix . '.css',
839 + $assets_dir . 'css/modal' . $min . '.css',
309 840 array(),
310 841 $version
311 842 );
312 843
@@ -311,9 +842,9 @@
311 842 );
312 843
313 844 wp_register_script(
314 845 'charitable-admin-tables',
315 - $assets_dir . 'js/charitable-admin-tables' . $suffix . '.js',
846 + $assets_dir . 'js/admin/charitable-admin-tables' . $min . '.js',
316 847 array( 'jquery', 'lean-modal' ),
317 848 $version,
318 849 true
319 850 );
@@ -319,28 +850,32 @@
319 850 );
320 851
321 852 wp_register_script(
322 853 'select2',
323 - $assets_dir . 'js/libraries/select2' . $suffix . '.js',
854 + $assets_dir . 'js/libraries/select2' . $min . '.js',
324 855 array( 'jquery' ),
325 - '4.0.12',
856 + $version,
326 857 true
327 858 );
328 859
329 860 wp_register_style(
330 861 'select2-css',
331 - $assets_dir . 'css/libraries/select2' . $suffix . '.css',
862 + $assets_dir . 'css/libraries/select2' . $min . '.css',
332 863 array(),
333 - '4.0.12'
864 + $version
334 865 );
335 866
336 - do_action( 'after_charitable_admin_enqueue_scripts', $suffix, $version, $assets_dir );
867 + 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.
337 868 }
338 869
339 870 /**
340 871 * Set admin body classes.
341 872 *
342 - * @since 1.5.0
873 + * @since 1.5.0
874 + * @version 1.8.1.6
875 + * @version 1.8.1.16 Added pro/lite check.
876 + * @version 1.8.3
877 + * @version 1.8.5 Added dashboard and reports classes.
343 878 *
344 879 * @param string $classes Existing list of classes.
345 880 * @return string
346 881 */
@@ -346,12 +881,46 @@
346 881 */
347 882 public function set_body_class( $classes ) {
348 883 $screen = get_current_screen();
349 884
350 - if ( 'donation' == $screen->post_type && ( 'add' == $screen->action || isset( $_GET['show_form'] ) ) ) {
885 + if ( 'donation' === $screen->post_type && ( 'add' === $screen->action || isset( $_GET['show_form'] ) ) ) { // phpcs:ignore.
351 886 $classes .= ' charitable-admin-donation-form';
352 887 }
353 888
889 + if ( isset( $_GET['page'] ) && ( 'charitable-tools' === $_GET['page'] || 'charitable-settings' === $_GET['page'] ) ) { // phpcs:ignore.
890 + $classes .= ' charitable-admin-settings';
891 + }
892 +
893 + if ( isset( $_GET['page'] ) && 'charitable-settings' === $_GET['page'] && isset( $_GET['tab'] ) && '' !== $_GET['tab'] ) { // phpcs:ignore.
894 + $classes .= ' charitable-admin-settings-' . esc_attr( $_GET['tab'] ); // phpcs:ignore.
895 + }
896 +
897 + if ( isset( $_GET['page'] ) && 'charitable-settings' === $_GET['page'] && isset( $_GET['tab'] ) && 'gateways' === $_GET['tab'] && empty( $_GET['group'] ) ) { // phpcs:ignore.
898 + $classes .= ' charitable-admin-settings-gateways-main'; // phpcs:ignore.
899 + }
900 +
901 + if ( isset( $_GET['page'] ) && 'charitable-reports' === $_GET['page'] ) { // phpcs:ignore.
902 + $classes .= ' charitable-admin-reports';
903 + }
904 +
905 + if ( isset( $_GET['page'] ) && 'charitable-dashboard' === $_GET['page'] && ! isset( $_GET['tab'] ) ) { // phpcs:ignore.
906 + $classes .= ' charitable-admin-reports-dashboard';
907 + }
908 +
909 + if ( isset( $_GET['page'] ) && 'charitable-reports' === $_GET['page'] && isset( $_GET['tab'] ) && '' !== $_GET['tab'] ) { // phpcs:ignore.
910 + $classes .= ' charitable-admin-reports charitable-admin-reports-' . esc_attr( $_GET['tab'] ); // phpcs:ignore.
911 + }
912 +
913 + if ( function_exists( 'charitable_use_legacy_dashboard' ) && charitable_use_legacy_dashboard() ) {
914 + $classes .= ' charitable_legacy_dashboard';
915 + }
916 +
917 + if ( charitable_show_promotion_footer() ) {
918 + $classes .= ' charitable-admin-promotion-footer';
919 + }
920 +
921 + $classes .= function_exists( 'charitable_is_pro' ) && charitable_is_pro() ? ' charitable-pro' : ' charitable';
922 +
354 923 return $classes;
355 924 }
356 925
357 926 /**
@@ -357,23 +926,78 @@
357 926 /**
358 927 * Add notices to the dashboard.
359 928 *
360 929 * @since 1.4.0
930 + * @version 1.8.3 Added check for PHP Version deprecation.
361 931 *
362 932 * @return void
363 933 */
364 934 public function add_notices() {
365 - /* Get any version update notices first. */
935 + if ( charitable_is_debug( 'square' ) ) {
936 + error_log( 'Charitable_Admin::add_notices() - Starting to render admin notices' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
937 + }
938 +
939 + // Render notices.
940 + charitable_get_admin_notices()->render();
941 +
942 + if ( charitable_is_debug( 'square' ) ) {
943 + 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
944 + }
945 +
946 + // Check for PHP version deprecation.
947 + if ( version_compare( PHP_VERSION, '7.4', '<' ) ) {
948 + $this->php_notice_deprecated();
949 + }
950 +
951 + // Add third party notices.
952 + $this->add_third_party_notices();
953 +
954 + // Add version update notices.
366 955 $this->add_version_update_notices();
956 + }
367 957
368 - /* Render notices. */
369 - charitable_get_admin_notices()->render();
958 + /**
959 + * Add notices for potential third party warnings.
960 + *
961 + * @since 1.7.0.8
962 + */
963 + public function add_third_party_notices() {
964 + if ( ! current_user_can( 'manage_options' ) ) {
965 + return;
966 + }
967 +
968 + // Allow this feature to be disabled in case unforeseen problems come up.
969 + if ( false === apply_filters( 'charitable_donations_export_args', true ) ) {
970 + return;
971 + }
972 +
973 + $notices = array(
974 + /* translators: %s: link */
975 + 'code-snippets/code-snippets.php' => sprintf(
976 + // Translators: %s: link.
977 + __( "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' ),
978 + 'https://wpcharitable.com/code-snippet-best-practices/'
979 + ),
980 + );
981 +
982 + // Allow this list to be altered by a third party or charitable addon.
983 + $notices = apply_filters( 'charitable_admin_third_party_notices', $notices );
984 +
985 + $helper = charitable_get_admin_notices();
986 +
987 + foreach ( $notices as $notice => $message ) {
988 + 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' ) ) {
989 + continue;
990 + }
991 +
992 + $helper->add_third_party_warning( $message, $notice );
993 + }
370 994 }
371 995
372 996 /**
373 997 * Add version update notices to the dashboard.
374 998 *
375 - * @since 1.4.6
999 + * @since 1.4.6 (deprecated in 1.8.0)
376 1000 *
377 1001 * @return void
378 1002 */
379 1003 public function add_version_update_notices() {
@@ -381,14 +1005,16 @@
381 1005 return;
382 1006 }
383 1007
384 1008 $notices = array(
385 - /* translators: %s: link */
386 - 'release-150' => sprintf( __( "Charitable 1.5 is packed with new features and improvements. <a href='%s' target='_blank'>Find out what's new</a>.", 'charitable' ),
1009 + 'release-150' => sprintf(
1010 + /* translators: %s: link */
1011 + __( "Charitable 1.5 is packed with new features and improvements. <a href='%s' target='_blank'>Find out what's new</a>.", 'charitable' ),
387 1012 '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'
388 1013 ),
389 - /* translators: %s: link */
390 - 'release-160' => sprintf( __( 'Charitable 1.6 introduces important new user privacy features and other improvements. <a href="%s" target="_blank">Find out what\'s new</a>.', 'charitable' ),
1014 + 'release-160' => sprintf(
1015 + /* translators: %s: link */
1016 + __( 'Charitable 1.6 introduces important new user privacy features and other improvements. <a href="%s" target="_blank">Find out what\'s new</a>.', 'charitable' ),
391 1017 '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'
392 1018 ),
393 1019 );
394 1020
@@ -403,21 +1029,100 @@
403 1029 }
404 1030 }
405 1031
406 1032 /**
1033 + * Add php decpreation notices.
1034 + *
1035 + * @since 1.8.3
1036 + *
1037 + * @return void
1038 + */
1039 + public function php_notice_deprecated() {
1040 + $medium = charitable_is_pro() ? 'proplugin' : 'liteplugin';
1041 + ?>
1042 + <div class="notice notice-error">
1043 + <p>
1044 + <?php
1045 + echo wp_kses(
1046 + sprintf(
1047 + // Translators: 1 - Opening HTML bold tag, 2 - Closing HTML bold tag, 3 - Opening HTML link tag, 4 - Closing HTML link tag.
1048 + __( '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
1049 + '<strong>',
1050 + '</strong>',
1051 + '<strong>Charitable</strong>',
1052 + '<a href="https://www.wpbeginner.com/wordpress-hosting/" target="_blank" rel="noopener noreferrer">',
1053 + '</a>'
1054 + ),
1055 + array(
1056 + 'a' => array(
1057 + 'href' => array(),
1058 + 'target' => array(),
1059 + 'rel' => array(),
1060 + ),
1061 + 'strong' => array(),
1062 + )
1063 + );
1064 + ?>
1065 + <br><br>
1066 + <?php
1067 + echo wp_kses(
1068 + sprintf(
1069 + // phpcs:ignore Generic.Files.LineLength.MaxExceeded
1070 + // 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.
1071 + __( '%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
1072 + '<strong>',
1073 + '</strong>',
1074 + PHP_VERSION,
1075 + gmdate( 'Y' ),
1076 + 'Charitable',
1077 + '<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
1078 + '</a>'
1079 + ),
1080 + array(
1081 + 'a' => array(
1082 + 'href' => array(),
1083 + 'target' => array(),
1084 + 'rel' => array(),
1085 + ),
1086 + 'strong' => array(),
1087 + )
1088 + );
1089 + ?>
1090 + </p>
1091 + </div>
1092 +
1093 + <?php
1094 + // In case this is on plugin activation.
1095 + if ( isset( $_GET['activate'] ) ) { // phpcs:ignore HM.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Recommended
1096 + unset( $_GET['activate'] );
1097 + }
1098 + }
1099 +
1100 + /**
407 1101 * Dismiss a notice.
408 1102 *
409 - * @since 1.4.0
1103 + * @since 1.4.6
410 1104 *
411 1105 * @return void
412 1106 */
413 1107 public function dismiss_notice() {
414 - if ( ! isset( $_POST['notice'] ) ) {
1108 + if ( ! isset( $_POST['notice'] ) ) { // phpcs:ignore.
415 1109 wp_send_json_error();
416 1110 }
417 1111
418 - $ret = delete_transient( 'charitable_' . $_POST['notice'] . '_notice', true );
1112 + $notice = sanitize_text_field( wp_unslash( $_POST['notice'] ) ); // phpcs:ignore.
419 1113
1114 + // Handle Square connection error notice.
1115 + if ( 'square_connection_error' === $notice ) {
1116 + delete_option( 'charitable_square_connection_error_notice' );
1117 + wp_send_json_success();
1118 + return;
1119 + }
1120 +
1121 + $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 );
1122 +
1123 + do_action( 'charitable_dismiss_notice', $_POST ); // phpcs:ignore.
1124 +
420 1125 if ( ! $ret ) {
421 1126 wp_send_json_error( $ret );
422 1127 }
423 1128
@@ -431,13 +1136,13 @@
431 1136 *
432 1137 * @return void
433 1138 */
434 1139 public function dismiss_banner() {
435 - if ( ! isset( $_POST['banner_id'] ) ) {
1140 + if ( ! isset( $_POST['banner_id'] ) ) { // phpcs:ignore.
436 1141 wp_send_json_error();
437 1142 }
438 1143
439 - $ret = set_transient( 'charitable_' . $_POST['banner_id'] . '_banner', 1, WEEK_IN_SECONDS );
1144 + $ret = set_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner', 1, WEEK_IN_SECONDS ); // phpcs:ignore.
440 1145
441 1146 if ( ! $ret ) {
442 1147 wp_send_json_error( $ret );
443 1148 }
@@ -444,9 +1149,29 @@
444 1149
445 1150 wp_send_json_success();
446 1151 }
447 1152
1153 + /**
1154 + * Dismiss a banner.
1155 + *
1156 + * @since 1.8.0
1157 + *
1158 + * @return void
1159 + */
1160 + public function dismiss_list_banner() {
1161 + if ( ! isset( $_POST['banner_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
1162 + wp_send_json_error();
1163 + }
448 1164
1165 + $ret = set_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_list_banner', 1, WEEK_IN_SECONDS ); // phpcs:ignore.
1166 +
1167 + if ( ! $ret ) {
1168 + wp_send_json_error( $ret );
1169 + }
1170 +
1171 + wp_send_json_success();
1172 + }
1173 +
449 1174 /**
450 1175 * Dismiss a five star rating.
451 1176 *
452 1177 * @since 1.7.0
@@ -453,16 +1178,16 @@
453 1178 *
454 1179 * @return void
455 1180 */
456 1181 public function dismiss_five_star_rating() {
457 - if ( ! isset( $_POST['banner_id'] ) ) {
1182 + if ( ! isset( $_POST['banner_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
458 1183 wp_send_json_error();
459 1184 }
460 1185
461 - $check = get_transient( 'charitable_' . $_POST['banner_id'] . '_banner' );
1186 + $check = get_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner' ); // phpcs:ignore.
462 1187
463 1188 if ( ! $check ) {
464 - $ret = set_transient( 'charitable_' . $_POST['banner_id'] . '_banner', true );
1189 + $ret = set_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner', true ); // phpcs:ignore.
465 1190 if ( ! $ret ) {
466 1191 wp_send_json_error( $ret );
467 1192 }
468 1193 }
@@ -469,11 +1194,116 @@
469 1194
470 1195 wp_send_json_success();
471 1196 }
472 1197
1198 + /**
1199 + * Dismiss dashboard notices.
1200 + *
1201 + * @since 1.8.1.6
1202 + *
1203 + * @return void
1204 + */
1205 + public function dismiss_dashboard_notices() {
1206 + if ( ! isset( $_POST['notice_ids'] ) ) { // phpcs:ignore.
1207 + wp_send_json_error();
1208 + }
473 1209
1210 + $ret = delete_transient( 'charitable_dashboard_notices' );
474 1211
1212 + // take a comma delimited $_POST value and turn it into an array that is properly filtered only for alphanumeric values.
1213 + $notices = array_filter( array_map( 'sanitize_text_field', explode( ',', $_POST['notice_ids'] ) ) ); // phpcs:ignore
1214 +
1215 + if ( ! empty( $notices ) && in_array( 'donation-security', $notices, true ) ) {
1216 + delete_transient( 'charitable_donation_security_checks' );
1217 + }
1218 +
1219 + if ( ! $ret ) {
1220 + wp_send_json_error( $ret );
1221 + }
1222 +
1223 + wp_send_json_success();
1224 + }
1225 +
475 1226 /**
1227 + * Dismiss guide tool notices.
1228 + *
1229 + * @since 1.8.1.6
1230 + *
1231 + * @return void
1232 + */
1233 + public function dismiss_growth_tools_notices() {
1234 +
1235 + // check the nonce via ajax.
1236 + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'charitable_dismiss_growth_tools' ) ) { // phpcs:ignore
1237 + wp_send_json_error();
1238 + }
1239 +
1240 + $notice_type = isset( $_POST['notice_type'] ) ? sanitize_text_field( wp_unslash( $_POST['notice_type'] ) ) : ''; // phpcs:ignore
1241 +
1242 + if ( ! $notice_type ) {
1243 + wp_send_json_error();
1244 + }
1245 +
1246 + $charitable_growth_tool_notices = get_option( 'charitable_growth_tool_notices' );
1247 +
1248 + if ( ! is_array( $charitable_growth_tool_notices ) ) {
1249 + $charitable_growth_tool_notices = array();
1250 + }
1251 +
1252 + $charitable_growth_tool_notices['dismiss'][ $notice_type ] = time();
1253 +
1254 + $ret = update_option( 'charitable_growth_tool_notices', $charitable_growth_tool_notices );
1255 +
1256 + if ( ! $ret ) {
1257 + wp_send_json_error( $ret );
1258 + }
1259 +
1260 + wp_send_json_success();
1261 + }
1262 +
1263 + /**
1264 + * Dismiss a five star rating.
1265 + *
1266 + * @since 1.7.0
1267 + *
1268 + * @return void
1269 + */
1270 + public function dismiss_lite_cta() {
1271 + if ( ! isset( $_POST['charitable_action'] ) ) { // phpcs:ignore.
1272 + wp_send_json_error();
1273 + }
1274 +
1275 + $updated = update_option( 'charitable_lite_settings_upgrade', true );
1276 +
1277 + if ( $updated ) {
1278 + wp_send_json_success();
1279 + } else {
1280 + wp_send_json_error();
1281 + }
1282 + }
1283 +
1284 + /**
1285 + * Dismiss a five star rating.
1286 + *
1287 + * @since 1.8.1.6
1288 + *
1289 + * @return void
1290 + */
1291 + public function dismiss_lite_reports_cta() {
1292 + if ( ! isset( $_POST['charitable_action'] ) ) { // phpcs:ignore.
1293 + wp_send_json_error();
1294 + }
1295 +
1296 + $updated = update_option( 'charitable_lite_reports_upgrade', true );
1297 +
1298 + if ( $updated ) {
1299 + wp_send_json_success();
1300 + } else {
1301 + wp_send_json_error();
1302 + }
1303 + }
1304 +
1305 + /**
476 1306 * Adds one or more classes to the body tag in the dashboard.
477 1307 *
478 1308 * @since 1.0.0
479 1309 *
@@ -486,8 +1316,15 @@
486 1316 if ( in_array( $screen->post_type, array( Charitable::DONATION_POST_TYPE, Charitable::CAMPAIGN_POST_TYPE ) ) ) {
487 1317 $classes .= ' post-type-charitable';
488 1318 }
489 1319
1320 + // Add `charitable-settings-page-{tab}` so per-tab CSS (e.g. Payment
1321 + // Gateways tab layout overrides in charitable-admin.css) can scope
1322 + // itself without affecting other settings tabs.
1323 + if ( isset( $_GET['page'] ) && 'charitable-settings' === $_GET['page'] && isset( $_GET['tab'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1324 + $classes .= ' charitable-settings-page-' . sanitize_html_class( wp_unslash( $_GET['tab'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1325 + }
1326 +
490 1327 return $classes;
491 1328 }
492 1329
493 1330 /**
@@ -519,9 +1356,9 @@
519 1356
520 1357 // Use an updated UTM.
521 1358 $extensions_link = charitable_ga_url(
522 1359 'https://wpcharitable.com/extensions/',
523 - urlencode( 'Admin Plugin Page'),
1360 + urlencode( 'Admin Plugin Page' ),
524 1361 urlencode( 'Extensions' )
525 1362 );
526 1363
527 1364 $links[] = '<a href="' . $extensions_link . '" target="_blank" rel="noopener">' . __( 'Extensions', 'charitable' ) . '</a>';
@@ -549,9 +1386,10 @@
549 1386 *
550 1387 * @return false|void Returns false if the export failed. Exits otherwise.
551 1388 */
552 1389 public function export_donations() {
553 - if ( ! wp_verify_nonce( $_GET['_charitable_export_nonce'], 'charitable_export_donations' ) ) {
1390 +
1391 + if ( ! isset( $_GET['_charitable_export_nonce'] ) || ! wp_verify_nonce( $_GET['_charitable_export_nonce'], 'charitable_export_donations' ) ) { // phpcs:ignore
554 1392 return false;
555 1393 }
556 1394
557 1395 /**
@@ -563,13 +1401,13 @@
563 1401 */
564 1402 $export_args = apply_filters(
565 1403 'charitable_donations_export_args',
566 1404 array(
567 - 'start_date' => $_GET['start_date'],
568 - 'end_date' => $_GET['end_date'],
569 - 'status' => $_GET['post_status'],
570 - 'campaign_id' => $_GET['campaign_id'],
571 - 'report_type' => $_GET['report_type'],
1405 + 'start_date' => isset( $_GET['start_date'] ) ? sanitize_text_field( wp_unslash( $_GET['start_date'] ) ) : '',
1406 + 'end_date' => isset( $_GET['end_date'] ) ? sanitize_text_field( wp_unslash( $_GET['end_date'] ) ) : '',
1407 + 'status' => isset( $_GET['status'] ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : '',
1408 + 'campaign_id' => isset( $_GET['campaign_id'] ) ? sanitize_text_field( wp_unslash( $_GET['campaign_id'] ) ) : '',
1409 + 'report_type' => isset( $_GET['report_type'] ) ? sanitize_text_field( wp_unslash( $_GET['report_type'] ) ) : '',
572 1410 )
573 1411 );
574 1412
575 1413 /**
@@ -579,9 +1417,9 @@
579 1417 *
580 1418 * @param string $report_type The type of report.
581 1419 * @param array $args Export arguments.
582 1420 */
583 - $export_class = apply_filters( 'charitable_donations_export_class', 'Charitable_Export_Donations', $_GET['report_type'], $export_args );
1421 + $export_class = apply_filters( 'charitable_donations_export_class', 'Charitable_Export_Donations', esc_attr( $_GET['report_type'] ), $export_args ); // phpcs:ignore
584 1422
585 1423 new $export_class( $export_args );
586 1424
587 1425 die();
@@ -594,9 +1432,10 @@
594 1432 *
595 1433 * @return false|void Returns false if the export failed. Exits otherwise.
596 1434 */
597 1435 public function export_campaigns() {
598 - if ( ! wp_verify_nonce( $_GET['_charitable_export_nonce'], 'charitable_export_campaigns' ) ) {
1436 +
1437 + if ( ! isset( $_GET['_charitable_export_nonce'] ) || ! wp_verify_nonce( $_GET['_charitable_export_nonce'], 'charitable_export_campaigns' ) ) { // phpcs:ignore
599 1438 return false;
600 1439 }
601 1440
602 1441 /**
@@ -608,14 +1447,13 @@
608 1447 */
609 1448 $export_args = apply_filters(
610 1449 'charitable_campaigns_export_args',
611 1450 array(
612 - 'start_date_from' => $_GET['start_date_from'],
613 - 'start_date_to' => $_GET['start_date_to'],
614 - 'end_date_from' => $_GET['end_date_from'],
615 - 'end_date_to' => $_GET['end_date_to'],
616 - 'status' => $_GET['status'],
617 - 'report_type' => $_GET['report_type'],
1451 + 'start_date_to' => isset( $_GET['start_date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['start_date_to'] ) ) : '',
1452 + 'end_date_from' => isset( $_GET['end_date_from'] ) ? sanitize_text_field( wp_unslash( $_GET['end_date_from'] ) ) : '',
1453 + 'end_date_to' => isset( $_GET['end_date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['end_date_to'] ) ) : '',
1454 + 'status' => isset( $_GET['status'] ) ? sanitize_text_field( wp_unslash( $_GET['status'] ) ) : '',
1455 + 'report_type' => isset( $_GET['report_type'] ) ? sanitize_text_field( wp_unslash( $_GET['report_type'] ) ) : '',
618 1456 )
619 1457 );
620 1458
621 1459 /**
@@ -625,9 +1463,9 @@
625 1463 *
626 1464 * @param string $report_type The type of report.
627 1465 * @param array $args Export arguments.
628 1466 */
629 - $export_class = apply_filters( 'charitable_campaigns_export_class', 'Charitable_Export_Campaigns', $_GET['report_type'], $export_args );
1467 + $export_class = apply_filters( 'charitable_campaigns_export_class', 'Charitable_Export_Campaigns', esc_attr( $_GET['report_type'] ), $export_args ); // phpcs:ignore
630 1468
631 1469 new $export_class( $export_args );
632 1470
633 1471 die();
@@ -634,8 +1472,9 @@
634 1472 }
635 1473
636 1474 /**
637 1475 * Returns an array of screen IDs where the Charitable scripts should be loaded.
1476 + * (Deprecated in 1.8.1.15, use charitable_get_charitable_Screens() in core functions instead).
638 1477 *
639 1478 * @uses charitable_admin_screens
640 1479 *
641 1480 * @since 1.0.0
@@ -645,21 +1484,358 @@
645 1484 public function get_charitable_screens() {
646 1485 /**
647 1486 * Filter admin screens where Charitable styles & scripts should be loaded.
648 1487 *
649 - * @since 1.0.0
1488 + * @since 1.8.0
1489 + * @since 1.8.1 Added `charitable_page_charitable-dashboard` and 'charitable_page_charitable-reports' to the list of screens.
1490 + * @since 1.8.1.6 Added 'charitable_page_charitable-tools' and 'charitable_page_charitable-growth-tool' to the list of screens.
1491 + * @since 1.8.5 Added 'charitable_page_charitable-donors' to the list of screens.
650 1492 *
651 1493 * @param string[] $screens List of screen ids.
652 1494 */
653 - return apply_filters( 'charitable_admin_screens', array(
654 - 'campaign',
655 - 'donation',
656 - 'charitable_page_charitable-settings',
657 - 'edit-campaign',
658 - 'edit-donation',
659 - 'dashboard',
660 - 'toplevel_page_charitable'
1495 + return apply_filters(
1496 + 'charitable_admin_screens',
1497 + array(
1498 + 'campaign',
1499 + 'donation',
1500 + 'charitable_page_charitable-reports',
1501 + 'charitable_page_charitable-dashboard',
1502 + 'charitable_page_charitable-settings',
1503 + 'charitable_page_charitable-tools',
1504 + 'charitable_page_charitable-growth-tools',
1505 + 'edit-campaign',
1506 + 'edit-donation',
1507 + 'dashboard',
1508 + 'toplevel_page_charitable',
1509 + 'charitable_page_charitable-addons',
1510 + 'charitable_page_charitable-donors',
1511 + )
1512 + );
1513 + }
1514 +
1515 + /**
1516 + * Updates the "action row" of campaigns to add the ability to edit with builder.
1517 + *
1518 + * @since 1.8.0
1519 + *
1520 + * @param array $actions Actions.
1521 + * @param array $post Post data.
1522 + *
1523 + * @return array
1524 + */
1525 + public function campaign_action_row( $actions, $post ) {
1526 +
1527 + $actions_first = array();
1528 +
1529 + $suffix = ( charitable_is_debug() || charitable_is_script_debug() ) ? '#charitabledebug' : false;
1530 +
1531 + // Check for the post type.
1532 +
1533 + 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
1534 +
1535 + $post_id = intval( $post->ID );
1536 +
1537 + // Determine if this is a "legacy" campaign.
1538 + $is_legacy_campaign = charitable_is_campaign_legacy( $post_id );
1539 +
1540 + if ( isset( $actions['edit'] ) && 0 !== $post_id ) {
1541 +
1542 + $actions_show_id = array( 'id' => esc_html__( 'ID: ', 'charitable' ) . $post_id );
1543 + $actions_edit_legacy = array( 'edit' => $actions['edit'] );
1544 +
1545 + unset( $actions['edit'] );
1546 +
1547 + }
1548 +
1549 + if ( defined( 'CHARITABLE_BUILDER_SHOW_LEGACY_EDIT_LINKS' ) && CHARITABLE_BUILDER_SHOW_LEGACY_EDIT_LINKS === true ) {
1550 +
1551 + // 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).
1552 + $actions_edit_legacy = array( 'Edit' => '<a href="' . admin_url( 'post.php?post=' . $post_id . '&action=edit' ) . '">' . esc_html__( 'Legacy Edit', 'charitable' ) . '</a>' );
1553 +
1554 + $actions = ( false === $is_legacy_campaign )
1555 + ? $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
1556 + : $actions_show_id + $actions_edit_legacy + $actions;
1557 +
1558 + } else {
1559 +
1560 + $actions = ( false === $is_legacy_campaign )
1561 + ? $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
1562 + : $actions_show_id + $actions_edit_legacy + $actions;
1563 +
1564 + }
1565 + }
1566 +
1567 + return $actions;
1568 + }
1569 +
1570 + /**
1571 + * Updates the "post link" of campaigns to add the ability to edit with builder.
1572 + *
1573 + * @since 1.8.0
1574 + * @since 1.8.1 added check for get_current_screen().
1575 + *
1576 + * @param string $link The current link URL.
1577 + * @param integer $post_id Post ID.
1578 + * @param array $context Context.
1579 + *
1580 + * @return array
1581 + */
1582 + public function campaign_link( $link, $post_id, $context ) { // phpcs:ignore
1583 +
1584 + if ( ! function_exists( 'get_current_screen' ) ) {
1585 + return $link;
1586 + }
1587 +
1588 + $screen = get_current_screen();
1589 + $post_id = intval( $post_id );
1590 +
1591 + if ( 0 === $post_id || ! isset( $screen->id ) || $screen->id !== 'edit-campaign' ) {
1592 + return $link;
1593 + }
1594 +
1595 + // Determine if this is a "legacy" campaign.
1596 + $is_legacy_campaign = charitable_is_campaign_legacy( $post_id );
1597 +
1598 + $suffix = ( charitable_is_debug() || charitable_is_script_debug() ) ? '#charitabledebug' : false;
1599 +
1600 + $new_link = ( false === $is_legacy_campaign )
1601 + ? admin_url( 'admin.php?page=charitable-campaign-builder&campaign_id=' . $post_id ) . $suffix
1602 + : esc_url( $link );
1603 +
1604 + return $new_link;
1605 + }
1606 +
1607 + /**
1608 + * Filters the URL used for a campaign preview.
1609 + *
1610 + * @since 1.8.0
1611 + *
1612 + * @param string $preview_link URL used for the post preview.
1613 + * @param WP_Post $post Post object.
1614 + */
1615 + public function campaign_preview_link( $preview_link, $post ) {
1616 +
1617 + if ( ! empty( $post ) && 'campaign' === $post->post_type ) {
1618 +
1619 + $preview_link = add_query_arg(
1620 + array(
1621 + 'charitable_campaign_preview' => $post->ID,
1622 + ),
1623 + $preview_link
1624 + );
1625 +
1626 + }
1627 +
1628 + return $preview_link;
1629 + }
1630 +
1631 + /**
1632 + * Adjustments needed for Elementor.
1633 + * Last tested with Elementor Lite 3.24.7.
1634 + *
1635 + * @since 1.8.3
1636 + *
1637 + * @param array $post_types_objects The post types.
1638 + *
1639 + * @return void
1640 + */
1641 + public function elementor_post_types( $post_types_objects = array() ) {
1642 +
1643 + // If Elementor is not installed, ignore.
1644 + if ( ! defined( 'ELEMENTOR_VERSION' ) ) {
1645 + return;
1646 + }
1647 +
1648 + // Check for the Charitable Elementor integration override.
1649 + if ( defined( 'CHARITABLE_ELEMENTOR_ALLOW_POST_TYPE' ) && CHARITABLE_ELEMENTOR_ALLOW_POST_TYPE ) {
1650 + return;
1651 + }
1652 +
1653 + $post_types_to_remove = apply_filters( 'charitable_elementor_post_types_to_remove', array( 'campaign' ) );
1654 +
1655 + if ( empty( $post_types_to_remove ) || ! is_array( $post_types_to_remove ) ) {
1656 + return $post_types_objects;
1657 + }
1658 +
1659 + // Remove all the post types from the object array.
1660 + foreach ( $post_types_to_remove as $post_type ) {
1661 + if ( isset( $post_types_objects[ $post_type ] ) ) {
1662 + unset( $post_types_objects[ $post_type ] );
1663 + }
1664 + }
1665 +
1666 + return $post_types_objects;
1667 + }
1668 +
1669 + /**
1670 + * Install addon via AJAX.
1671 + *
1672 + * @since 1.8.7.6
1673 + */
1674 + public function install_addon() {
1675 + // phpcs:disable WordPress.Security.NonceVerification.Missing
1676 + // Check nonce.
1677 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1678 + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'charitable_admin_addons' ) ) {
1679 + wp_send_json_error( 'Invalid nonce' );
1680 + }
1681 +
1682 + // Check permissions.
1683 + if ( ! charitable_can_install( 'plugin' ) ) {
1684 + wp_send_json_error( 'Insufficient permissions' );
1685 + }
1686 +
1687 + $plugin = isset( $_POST['plugin'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) : '';
1688 + $type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : '';
1689 + // phpcs:enable WordPress.Security.NonceVerification.Missing
1690 +
1691 + if ( empty( $plugin ) ) {
1692 + wp_send_json_error( 'Plugin not specified' );
1693 + }
1694 +
1695 + // Ensure filesystem access (request credentials if needed) similar to legacy flow.
1696 + require_once ABSPATH . 'wp-admin/includes/file.php';
1697 + $method = '';
1698 + $url = esc_url( admin_url( 'admin.php?page=charitable-about' ) );
1699 +
1700 + ob_start();
1701 + $creds = request_filesystem_credentials( $url, $method, false, false, null );
1702 + if ( false === $creds ) {
1703 + $form = ob_get_clean();
1704 + wp_send_json_success( array( 'form' => $form ) );
1705 + }
1706 +
1707 + if ( ! WP_Filesystem( $creds ) ) {
1708 + ob_start();
1709 + request_filesystem_credentials( $url, $method, true, false, null );
1710 + $form = ob_get_clean();
1711 + wp_send_json_success( array( 'form' => $form ) );
1712 + }
1713 +
1714 + // Snapshot installed plugins BEFORE the install so we can identify what
1715 + // was actually unzipped. The legacy slug-derivation in
1716 + // charitable_get_plugin_basename_from_slug() reduces a download URL like
1717 + // `…/charitable-pro-plugin-1.8.13.5.zip` to the slug
1718 + // `charitable-pro-plugin-1.8.13.5`, then expects an installed plugin
1719 + // directory whose name *starts* with that slug — which doesn't hold for
1720 + // any addon whose zip filename includes a version suffix and whose
1721 + // directory does not (e.g. Charitable Pro lives at `charitable-pro/`).
1722 + // The before/after diff sidesteps that mismatch entirely.
1723 + if ( ! function_exists( 'get_plugins' ) ) {
1724 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
1725 + }
1726 + $plugins_before = array_keys( get_plugins() );
1727 +
1728 + // Download and install the plugin.
1729 + $result = charitable_install_wporg_plugin( $plugin );
1730 +
1731 + if ( is_wp_error( $result ) ) {
1732 + wp_send_json_error( $result->get_error_message() );
1733 + }
1734 +
1735 + // Force a fresh re-scan of the plugins directory and compute the diff.
1736 + wp_cache_delete( 'plugins', 'plugins' );
1737 + $plugins_after = array_keys( get_plugins() );
1738 + $newly_installed = array_values( array_diff( $plugins_after, $plugins_before ) );
1739 +
1740 + if ( ! empty( $newly_installed ) ) {
1741 + $plugin_basename = $newly_installed[0];
1742 + } else {
1743 + // Fallback to the legacy slug-derived guess if for some reason no new
1744 + // plugin was detected (e.g. the zip overwrote an existing install).
1745 + $plugin_basename = charitable_get_plugin_basename_from_slug( $plugin );
1746 + }
1747 +
1748 + wp_send_json_success( array(
1749 + 'msg' => __( 'Plugin installed successfully', 'charitable' ),
1750 + 'basename' => $plugin_basename,
1751 + 'is_activated' => false,
661 1752 ) );
1753 + }
1754 +
1755 + /**
1756 + * Activate addon via AJAX.
1757 + *
1758 + * @since 1.8.7.6
1759 + */
1760 + public function activate_addon() {
1761 + // phpcs:disable WordPress.Security.NonceVerification.Missing
1762 + // Check nonce.
1763 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1764 + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'charitable_admin_addons' ) ) {
1765 + wp_send_json_error( 'Invalid nonce' );
1766 + }
1767 +
1768 + // Check permissions.
1769 + if ( ! charitable_can_activate( 'plugin' ) ) {
1770 + wp_send_json_error( 'Insufficient permissions' );
1771 + }
1772 +
1773 + $plugin = isset( $_POST['plugin'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) : '';
1774 + // phpcs:enable WordPress.Security.NonceVerification.Missing
1775 +
1776 + if ( empty( $plugin ) ) {
1777 + wp_send_json_error( 'Plugin not specified' );
1778 + }
1779 +
1780 + // Normalize plugin basename if a folder only or slug was passed.
1781 + if ( false === strpos( $plugin, '/' ) ) {
1782 + $plugin = charitable_get_plugin_basename_from_slug( $plugin );
1783 + }
1784 +
1785 + // Activate the plugin.
1786 + $result = charitable_activate_wporg_plugin( $plugin );
1787 +
1788 + if ( is_wp_error( $result ) ) {
1789 + wp_send_json_error( $result->get_error_message() );
1790 + }
1791 +
1792 + $response = array( 'msg' => __( 'Plugin activated successfully', 'charitable' ) );
1793 +
1794 + // Activating Charitable Pro auto-deactivates Charitable Lite, which means
1795 + // the screen the user came from (page=charitable-addons, served by Lite)
1796 + // no longer exists in the menu. Redirect them to the Charitable dashboard
1797 + // so they don't land on a 404 / blank admin page after the AJAX returns.
1798 + if ( 0 === strpos( $plugin, 'charitable-pro/' ) ) {
1799 + $response['redirect'] = admin_url( 'admin.php?page=charitable-dashboard' );
1800 + }
1801 +
1802 + wp_send_json_success( $response );
1803 + }
1804 +
1805 + /**
1806 + * Deactivate addon via AJAX.
1807 + *
1808 + * @since 1.8.7.6
1809 + */
1810 + public function deactivate_addon() {
1811 + // phpcs:disable WordPress.Security.NonceVerification.Missing
1812 + // Check nonce.
1813 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1814 + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'charitable_admin_addons' ) ) {
1815 + wp_send_json_error( 'Invalid nonce' );
1816 + }
1817 +
1818 + // Check permissions.
1819 + if ( ! charitable_can_activate( 'plugin' ) ) {
1820 + wp_send_json_error( 'Insufficient permissions' );
1821 + }
1822 +
1823 + $plugin = isset( $_POST['plugin'] ) ? sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) : '';
1824 + // phpcs:enable WordPress.Security.NonceVerification.Missing
1825 +
1826 + if ( empty( $plugin ) ) {
1827 + wp_send_json_error( 'Plugin not specified' );
1828 + }
1829 +
1830 + // Deactivate the plugin.
1831 + $result = charitable_deactivate_wporg_plugin( $plugin );
1832 +
1833 + if ( is_wp_error( $result ) ) {
1834 + wp_send_json_error( $result->get_error_message() );
1835 + }
1836 +
1837 + wp_send_json_success( 'Plugin deactivated successfully' );
662 1838 }
663 1839 }
664 1840
665 1841 endif;