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 +1293 -94 1.7.0 → 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,17 +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 - }//end if
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 + ) );
260 676
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' );
680 +
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
763 + );
764 +
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 + );
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 +
261 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(
262 814 'charitable-admin-notice',
263 - $assets_dir . 'js/charitable-admin-notice' . $suffix . '.js',
815 + $assets_dir . 'js/admin/charitable-admin-notice' . $min . '.js',
264 816 array( 'jquery' ),
265 817 $version,
266 818 false
267 819 );
@@ -267,9 +819,9 @@
267 819 );
268 820
269 821 wp_register_script(
270 822 'charitable-admin-media',
271 - $assets_dir . 'js/charitable-admin-media' . $suffix . '.js',
823 + $assets_dir . 'js/admin/charitable-admin-media' . $min . '.js',
272 824 array( 'jquery' ),
273 825 $version,
274 826 false
275 827 );
@@ -275,9 +827,9 @@
275 827 );
276 828
277 829 wp_register_script(
278 830 'lean-modal',
279 - $assets_dir . 'js/libraries/leanModal' . $suffix . '.js',
831 + $assets_dir . 'js/libraries/leanModal' . $min . '.js',
280 832 array( 'jquery' ),
281 833 $version,
282 834 true
283 835 );
@@ -283,9 +835,9 @@
283 835 );
284 836
285 837 wp_register_style(
286 838 'lean-modal-css',
287 - $assets_dir . 'css/modal' . $suffix . '.css',
839 + $assets_dir . 'css/modal' . $min . '.css',
288 840 array(),
289 841 $version
290 842 );
291 843
@@ -290,9 +842,9 @@
290 842 );
291 843
292 844 wp_register_script(
293 845 'charitable-admin-tables',
294 - $assets_dir . 'js/charitable-admin-tables' . $suffix . '.js',
846 + $assets_dir . 'js/admin/charitable-admin-tables' . $min . '.js',
295 847 array( 'jquery', 'lean-modal' ),
296 848 $version,
297 849 true
298 850 );
@@ -298,26 +850,32 @@
298 850 );
299 851
300 852 wp_register_script(
301 853 'select2',
302 - $assets_dir . 'js/libraries/select2' . $suffix . '.js',
854 + $assets_dir . 'js/libraries/select2' . $min . '.js',
303 855 array( 'jquery' ),
304 - '4.0.12',
856 + $version,
305 857 true
306 858 );
307 859
308 860 wp_register_style(
309 861 'select2-css',
310 - $assets_dir . 'css/libraries/select2' . $suffix . '.css',
862 + $assets_dir . 'css/libraries/select2' . $min . '.css',
311 863 array(),
312 - '4.0.12'
864 + $version
313 865 );
866 +
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.
314 868 }
315 869
316 870 /**
317 871 * Set admin body classes.
318 872 *
319 - * @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.
320 878 *
321 879 * @param string $classes Existing list of classes.
322 880 * @return string
323 881 */
@@ -323,12 +881,46 @@
323 881 */
324 882 public function set_body_class( $classes ) {
325 883 $screen = get_current_screen();
326 884
327 - 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.
328 886 $classes .= ' charitable-admin-donation-form';
329 887 }
330 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 +
331 923 return $classes;
332 924 }
333 925
334 926 /**
@@ -334,23 +926,78 @@
334 926 /**
335 927 * Add notices to the dashboard.
336 928 *
337 929 * @since 1.4.0
930 + * @version 1.8.3 Added check for PHP Version deprecation.
338 931 *
339 932 * @return void
340 933 */
341 934 public function add_notices() {
342 - /* 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.
343 955 $this->add_version_update_notices();
956 + }
344 957
345 - /* Render notices. */
346 - 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 + }
347 994 }
348 995
349 996 /**
350 997 * Add version update notices to the dashboard.
351 998 *
352 - * @since 1.4.6
999 + * @since 1.4.6 (deprecated in 1.8.0)
353 1000 *
354 1001 * @return void
355 1002 */
356 1003 public function add_version_update_notices() {
@@ -358,14 +1005,16 @@
358 1005 return;
359 1006 }
360 1007
361 1008 $notices = array(
362 - /* translators: %s: link */
363 - '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' ),
364 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'
365 1013 ),
366 - /* translators: %s: link */
367 - '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' ),
368 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'
369 1018 ),
370 1019 );
371 1020
@@ -380,21 +1029,100 @@
380 1029 }
381 1030 }
382 1031
383 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 + /**
384 1101 * Dismiss a notice.
385 1102 *
386 - * @since 1.4.0
1103 + * @since 1.4.6
387 1104 *
388 1105 * @return void
389 1106 */
390 1107 public function dismiss_notice() {
391 - if ( ! isset( $_POST['notice'] ) ) {
1108 + if ( ! isset( $_POST['notice'] ) ) { // phpcs:ignore.
392 1109 wp_send_json_error();
393 1110 }
394 1111
395 - $ret = delete_transient( 'charitable_' . $_POST['notice'] . '_notice', true );
1112 + $notice = sanitize_text_field( wp_unslash( $_POST['notice'] ) ); // phpcs:ignore.
396 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 +
397 1125 if ( ! $ret ) {
398 1126 wp_send_json_error( $ret );
399 1127 }
400 1128
@@ -408,13 +1136,13 @@
408 1136 *
409 1137 * @return void
410 1138 */
411 1139 public function dismiss_banner() {
412 - if ( ! isset( $_POST['banner_id'] ) ) {
1140 + if ( ! isset( $_POST['banner_id'] ) ) { // phpcs:ignore.
413 1141 wp_send_json_error();
414 1142 }
415 1143
416 - $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.
417 1145
418 1146 if ( ! $ret ) {
419 1147 wp_send_json_error( $ret );
420 1148 }
@@ -421,9 +1149,29 @@
421 1149
422 1150 wp_send_json_success();
423 1151 }
424 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 + }
425 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 +
426 1174 /**
427 1175 * Dismiss a five star rating.
428 1176 *
429 1177 * @since 1.7.0
@@ -430,16 +1178,16 @@
430 1178 *
431 1179 * @return void
432 1180 */
433 1181 public function dismiss_five_star_rating() {
434 - if ( ! isset( $_POST['banner_id'] ) ) {
1182 + if ( ! isset( $_POST['banner_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
435 1183 wp_send_json_error();
436 1184 }
437 1185
438 - $check = get_transient( 'charitable_' . $_POST['banner_id'] . '_banner' );
1186 + $check = get_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner' ); // phpcs:ignore.
439 1187
440 1188 if ( ! $check ) {
441 - $ret = set_transient( 'charitable_' . $_POST['banner_id'] . '_banner', true );
1189 + $ret = set_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner', true ); // phpcs:ignore.
442 1190 if ( ! $ret ) {
443 1191 wp_send_json_error( $ret );
444 1192 }
445 1193 }
@@ -446,11 +1194,116 @@
446 1194
447 1195 wp_send_json_success();
448 1196 }
449 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 + }
450 1209
1210 + $ret = delete_transient( 'charitable_dashboard_notices' );
451 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 +
452 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 + /**
453 1306 * Adds one or more classes to the body tag in the dashboard.
454 1307 *
455 1308 * @since 1.0.0
456 1309 *
@@ -463,8 +1316,15 @@
463 1316 if ( in_array( $screen->post_type, array( Charitable::DONATION_POST_TYPE, Charitable::CAMPAIGN_POST_TYPE ) ) ) {
464 1317 $classes .= ' post-type-charitable';
465 1318 }
466 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 +
467 1327 return $classes;
468 1328 }
469 1329
470 1330 /**
@@ -496,9 +1356,9 @@
496 1356
497 1357 // Use an updated UTM.
498 1358 $extensions_link = charitable_ga_url(
499 1359 'https://wpcharitable.com/extensions/',
500 - urlencode( 'Admin Plugin Page'),
1360 + urlencode( 'Admin Plugin Page' ),
501 1361 urlencode( 'Extensions' )
502 1362 );
503 1363
504 1364 $links[] = '<a href="' . $extensions_link . '" target="_blank" rel="noopener">' . __( 'Extensions', 'charitable' ) . '</a>';
@@ -526,9 +1386,10 @@
526 1386 *
527 1387 * @return false|void Returns false if the export failed. Exits otherwise.
528 1388 */
529 1389 public function export_donations() {
530 - 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
531 1392 return false;
532 1393 }
533 1394
534 1395 /**
@@ -540,13 +1401,13 @@
540 1401 */
541 1402 $export_args = apply_filters(
542 1403 'charitable_donations_export_args',
543 1404 array(
544 - 'start_date' => $_GET['start_date'],
545 - 'end_date' => $_GET['end_date'],
546 - 'status' => $_GET['post_status'],
547 - 'campaign_id' => $_GET['campaign_id'],
548 - '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'] ) ) : '',
549 1410 )
550 1411 );
551 1412
552 1413 /**
@@ -556,9 +1417,9 @@
556 1417 *
557 1418 * @param string $report_type The type of report.
558 1419 * @param array $args Export arguments.
559 1420 */
560 - $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
561 1422
562 1423 new $export_class( $export_args );
563 1424
564 1425 die();
@@ -571,9 +1432,10 @@
571 1432 *
572 1433 * @return false|void Returns false if the export failed. Exits otherwise.
573 1434 */
574 1435 public function export_campaigns() {
575 - 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
576 1438 return false;
577 1439 }
578 1440
579 1441 /**
@@ -585,14 +1447,13 @@
585 1447 */
586 1448 $export_args = apply_filters(
587 1449 'charitable_campaigns_export_args',
588 1450 array(
589 - 'start_date_from' => $_GET['start_date_from'],
590 - 'start_date_to' => $_GET['start_date_to'],
591 - 'end_date_from' => $_GET['end_date_from'],
592 - 'end_date_to' => $_GET['end_date_to'],
593 - 'status' => $_GET['status'],
594 - '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'] ) ) : '',
595 1456 )
596 1457 );
597 1458
598 1459 /**
@@ -602,9 +1463,9 @@
602 1463 *
603 1464 * @param string $report_type The type of report.
604 1465 * @param array $args Export arguments.
605 1466 */
606 - $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
607 1468
608 1469 new $export_class( $export_args );
609 1470
610 1471 die();
@@ -611,8 +1472,9 @@
611 1472 }
612 1473
613 1474 /**
614 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).
615 1477 *
616 1478 * @uses charitable_admin_screens
617 1479 *
618 1480 * @since 1.0.0
@@ -622,21 +1484,358 @@
622 1484 public function get_charitable_screens() {
623 1485 /**
624 1486 * Filter admin screens where Charitable styles & scripts should be loaded.
625 1487 *
626 - * @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.
627 1492 *
628 1493 * @param string[] $screens List of screen ids.
629 1494 */
630 - return apply_filters( 'charitable_admin_screens', array(
631 - 'campaign',
632 - 'donation',
633 - 'charitable_page_charitable-settings',
634 - 'edit-campaign',
635 - 'edit-donation',
636 - 'dashboard',
637 - '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,
638 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' );
639 1838 }
640 1839 }
641 1840
642 1841 endif;