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 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 All 212 releases
← All changes | includes/admin/class-charitable-admin.php +1365 -90 1.6.60 → 1.8.13 View file →
@@ -2,13 +2,13 @@
2 2 /**
3 3 * Class that sets up the Charitable Admin functionality.
4 4 *
5 5 * @package Charitable/Classes/Charitable_Admin
6 - * @author Eric Daams
7 - * @copyright Copyright (c) 2022, Studio 164a
6 + * @author David Bisset
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
@@ -153,12 +489,22 @@
153 489 );
154 490
155 491 wp_enqueue_style( 'charitable-admin-menu' );
156 492
493 + wp_register_script(
494 + 'charitable-admin-pages-all',
495 + $assets_dir . 'js/admin/charitable-admin-pages' . $min . '.js',
496 + array( 'jquery' ),
497 + $version,
498 + false
499 + );
500 +
501 + wp_enqueue_script( 'charitable-admin-pages-all' );
502 +
157 503 /* Admin page styles are registered but only enqueued when necessary. */
158 504 wp_register_style(
159 505 'charitable-admin-pages',
160 - $assets_dir . 'css/charitable-admin-pages' . $suffix . '.css',
506 + $assets_dir . 'css/admin/charitable-admin-pages' . $min . '.css',
161 507 array(),
162 508 $version
163 509 );
164 510
@@ -164,13 +510,14 @@
164 510
165 511 /* The following styles are only loaded on Charitable screens. */
166 512 $screen = get_current_screen();
167 513
168 - if ( ! is_null( $screen ) && in_array( $screen->id, $this->get_charitable_screens() ) ) {
514 + /* This check covers the category and tags pages, only load the main admin css (upgrade banner, etc.) */
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 ) ) ) ) {
169 516
170 517 wp_register_style(
171 518 'charitable-admin',
172 - $assets_dir . 'css/charitable-admin' . $suffix . '.css',
519 + $assets_dir . 'css/admin/charitable-admin-legacy' . $min . '.css',
173 520 array(),
174 521 $version
175 522 );
176 523
@@ -175,18 +522,52 @@
175 522 );
176 523
177 524 wp_enqueue_style( 'charitable-admin' );
178 525
179 - $dependencies = array( 'jquery-ui-datepicker', 'jquery-ui-tabs', 'jquery-ui-sortable' );
180 - $localized_vars = array(
181 - 'suggested_amount_placeholder' => __( 'Amount', 'charitable' ),
182 - 'suggested_amount_description_placeholder' => __( 'Optional Description', 'charitable' ),
526 + wp_register_script(
527 + 'charitable-admin-notice',
528 + $assets_dir . 'js/admin/charitable-admin-notice' . $min . '.js',
529 + array( 'jquery' ),
530 + $version,
531 + false
183 532 );
184 533
185 - if ( 'donation' == $screen->id ) {
534 + }
535 +
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 + );
543 +
544 + wp_enqueue_style( 'charitable-admin-2.0' );
545 +
546 + wp_enqueue_style(
547 + 'charitable-notifications-v2',
548 + $assets_dir . 'css/admin/notifications-v2' . $min . '.css',
549 + array(),
550 + $version
551 + );
552 +
553 + wp_register_style(
554 + 'charitable-admin',
555 + $assets_dir . 'css/admin/charitable-admin-legacy' . $min . '.css',
556 + array(),
557 + $version
558 + );
559 +
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 ) {
186 567 wp_register_script(
187 568 'accounting',
188 - $assets_dir . 'js/libraries/accounting'. $suffix . '.js',
569 + $assets_dir . 'js/libraries/accounting' . $min . '.js',
189 570 array( 'jquery' ),
190 571 $version,
191 572 true
192 573 );
@@ -204,9 +585,9 @@
204 585 }
205 586
206 587 wp_register_script(
207 588 'charitable-admin',
208 - $assets_dir . 'js/charitable-admin' . $suffix . '.js',
589 + $assets_dir . 'js/admin/charitable-admin' . $min . '.js',
209 590 $dependencies,
210 591 $version,
211 592 false
212 593 );
@@ -212,8 +593,72 @@
212 593 );
213 594
214 595 wp_enqueue_script( 'charitable-admin' );
215 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 +
216 661 /**
217 662 * Filter the admin Javascript vars.
218 663 *
219 664 * @since 1.0.0
@@ -219,17 +664,156 @@
219 664 * @since 1.0.0
220 665 *
221 666 * @param array $localized_vars The vars.
222 667 */
223 - $localized_vars = apply_filters( 'charitable_localized_javascript_vars', $localized_vars );
668 + $localized_vars = (array) apply_filters( 'charitable_localized_javascript_vars', $localized_vars );
224 669
225 670 wp_localize_script( 'charitable-admin', 'CHARITABLE', $localized_vars );
226 671
227 - }//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 + ) );
228 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 +
229 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(
230 814 'charitable-admin-notice',
231 - $assets_dir . 'js/charitable-admin-notice' . $suffix . '.js',
815 + $assets_dir . 'js/admin/charitable-admin-notice' . $min . '.js',
232 816 array( 'jquery' ),
233 817 $version,
234 818 false
235 819 );
@@ -235,9 +819,9 @@
235 819 );
236 820
237 821 wp_register_script(
238 822 'charitable-admin-media',
239 - $assets_dir . 'js/charitable-admin-media' . $suffix . '.js',
823 + $assets_dir . 'js/admin/charitable-admin-media' . $min . '.js',
240 824 array( 'jquery' ),
241 825 $version,
242 826 false
243 827 );
@@ -243,9 +827,9 @@
243 827 );
244 828
245 829 wp_register_script(
246 830 'lean-modal',
247 - $assets_dir . 'js/libraries/leanModal' . $suffix . '.js',
831 + $assets_dir . 'js/libraries/leanModal' . $min . '.js',
248 832 array( 'jquery' ),
249 833 $version,
250 834 true
251 835 );
@@ -251,9 +835,9 @@
251 835 );
252 836
253 837 wp_register_style(
254 838 'lean-modal-css',
255 - $assets_dir . 'css/modal' . $suffix . '.css',
839 + $assets_dir . 'css/modal' . $min . '.css',
256 840 array(),
257 841 $version
258 842 );
259 843
@@ -258,9 +842,9 @@
258 842 );
259 843
260 844 wp_register_script(
261 845 'charitable-admin-tables',
262 - $assets_dir . 'js/charitable-admin-tables' . $suffix . '.js',
846 + $assets_dir . 'js/admin/charitable-admin-tables' . $min . '.js',
263 847 array( 'jquery', 'lean-modal' ),
264 848 $version,
265 849 true
266 850 );
@@ -266,26 +850,32 @@
266 850 );
267 851
268 852 wp_register_script(
269 853 'select2',
270 - $assets_dir . 'js/libraries/select2' . $suffix . '.js',
854 + $assets_dir . 'js/libraries/select2' . $min . '.js',
271 855 array( 'jquery' ),
272 - '4.0.12',
856 + $version,
273 857 true
274 858 );
275 859
276 860 wp_register_style(
277 861 'select2-css',
278 - $assets_dir . 'css/libraries/select2' . $suffix . '.css',
862 + $assets_dir . 'css/libraries/select2' . $min . '.css',
279 863 array(),
280 - '4.0.12'
864 + $version
281 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.
282 868 }
283 869
284 870 /**
285 871 * Set admin body classes.
286 872 *
287 - * @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.
288 878 *
289 879 * @param string $classes Existing list of classes.
290 880 * @return string
291 881 */
@@ -291,12 +881,46 @@
291 881 */
292 882 public function set_body_class( $classes ) {
293 883 $screen = get_current_screen();
294 884
295 - 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.
296 886 $classes .= ' charitable-admin-donation-form';
297 887 }
298 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 +
299 923 return $classes;
300 924 }
301 925
302 926 /**
@@ -302,23 +926,78 @@
302 926 /**
303 927 * Add notices to the dashboard.
304 928 *
305 929 * @since 1.4.0
930 + * @version 1.8.3 Added check for PHP Version deprecation.
306 931 *
307 932 * @return void
308 933 */
309 934 public function add_notices() {
310 - /* 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.
311 955 $this->add_version_update_notices();
956 + }
312 957
313 - /* Render notices. */
314 - 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 + }
315 994 }
316 995
317 996 /**
318 997 * Add version update notices to the dashboard.
319 998 *
320 - * @since 1.4.6
999 + * @since 1.4.6 (deprecated in 1.8.0)
321 1000 *
322 1001 * @return void
323 1002 */
324 1003 public function add_version_update_notices() {
@@ -326,15 +1005,17 @@
326 1005 return;
327 1006 }
328 1007
329 1008 $notices = array(
330 - /* translators: %s: link */
331 - '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' ),
332 - 'https://www.wpcharitable.com/charitable-1-5-release-notes/?utm_source=notice&utm_medium=wordpress-dashboard&utm_campaign=release-notes&utm_content=release-150'
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' ),
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'
333 1013 ),
334 - /* translators: %s: link */
335 - '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' ),
336 - 'https://www.wpcharitable.com/charitable-1-6-user-privacy-gdpr-better-refunds-and-a-new-shortcode/?utm_source=notice&utm_medium=wordpress-dashboard&utm_campaign=release-notes&utm_content=release-160'
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' ),
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'
337 1018 ),
338 1019 );
339 1020
340 1021 $helper = charitable_get_admin_notices();
@@ -348,21 +1029,100 @@
348 1029 }
349 1030 }
350 1031
351 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 + /**
352 1101 * Dismiss a notice.
353 1102 *
354 - * @since 1.4.0
1103 + * @since 1.4.6
355 1104 *
356 1105 * @return void
357 1106 */
358 1107 public function dismiss_notice() {
359 - if ( ! isset( $_POST['notice'] ) ) {
1108 + if ( ! isset( $_POST['notice'] ) ) { // phpcs:ignore.
360 1109 wp_send_json_error();
361 1110 }
362 1111
363 - $ret = delete_transient( 'charitable_' . $_POST['notice'] . '_notice', true );
1112 + $notice = sanitize_text_field( wp_unslash( $_POST['notice'] ) ); // phpcs:ignore.
364 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 +
365 1125 if ( ! $ret ) {
366 1126 wp_send_json_error( $ret );
367 1127 }
368 1128
@@ -369,8 +1129,181 @@
369 1129 wp_send_json_success();
370 1130 }
371 1131
372 1132 /**
1133 + * Dismiss a banner.
1134 + *
1135 + * @since 1.7.0
1136 + *
1137 + * @return void
1138 + */
1139 + public function dismiss_banner() {
1140 + if ( ! isset( $_POST['banner_id'] ) ) { // phpcs:ignore.
1141 + wp_send_json_error();
1142 + }
1143 +
1144 + $ret = set_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner', 1, WEEK_IN_SECONDS ); // phpcs:ignore.
1145 +
1146 + if ( ! $ret ) {
1147 + wp_send_json_error( $ret );
1148 + }
1149 +
1150 + wp_send_json_success();
1151 + }
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 + }
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 +
1174 + /**
1175 + * Dismiss a five star rating.
1176 + *
1177 + * @since 1.7.0
1178 + *
1179 + * @return void
1180 + */
1181 + public function dismiss_five_star_rating() {
1182 + if ( ! isset( $_POST['banner_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
1183 + wp_send_json_error();
1184 + }
1185 +
1186 + $check = get_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner' ); // phpcs:ignore.
1187 +
1188 + if ( ! $check ) {
1189 + $ret = set_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner', true ); // phpcs:ignore.
1190 + if ( ! $ret ) {
1191 + wp_send_json_error( $ret );
1192 + }
1193 + }
1194 +
1195 + wp_send_json_success();
1196 + }
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 + }
1209 +
1210 + $ret = delete_transient( 'charitable_dashboard_notices' );
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 +
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 + /**
373 1306 * Adds one or more classes to the body tag in the dashboard.
374 1307 *
375 1308 * @since 1.0.0
376 1309 *
@@ -383,8 +1316,15 @@
383 1316 if ( in_array( $screen->post_type, array( Charitable::DONATION_POST_TYPE, Charitable::CAMPAIGN_POST_TYPE ) ) ) {
384 1317 $classes .= ' post-type-charitable';
385 1318 }
386 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 +
387 1327 return $classes;
388 1328 }
389 1329
390 1330 /**
@@ -413,20 +1353,16 @@
413 1353 if ( plugin_basename( charitable()->get_path() ) != $file ) {
414 1354 return $links;
415 1355 }
416 1356
417 - $extensions_link = esc_url(
418 - add_query_arg(
419 - array(
420 - 'utm_source' => 'plugins-page',
421 - 'utm_medium' => 'plugin-row',
422 - 'utm_campaign' => 'admin',
423 - ),
424 - 'https://wpcharitable.com/extensions/'
425 - )
1357 + // Use an updated UTM.
1358 + $extensions_link = charitable_ga_url(
1359 + 'https://wpcharitable.com/extensions/',
1360 + urlencode( 'Admin Plugin Page' ),
1361 + urlencode( 'Extensions' )
426 1362 );
427 1363
428 - $links[] = '<a href="' . $extensions_link . '">' . __( 'Extensions', 'charitable' ) . '</a>';
1364 + $links[] = '<a href="' . $extensions_link . '" target="_blank" rel="noopener">' . __( 'Extensions', 'charitable' ) . '</a>';
429 1365
430 1366 return $links;
431 1367 }
432 1368
@@ -450,9 +1386,10 @@
450 1386 *
451 1387 * @return false|void Returns false if the export failed. Exits otherwise.
452 1388 */
453 1389 public function export_donations() {
454 - 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
455 1392 return false;
456 1393 }
457 1394
458 1395 /**
@@ -464,13 +1401,13 @@
464 1401 */
465 1402 $export_args = apply_filters(
466 1403 'charitable_donations_export_args',
467 1404 array(
468 - 'start_date' => $_GET['start_date'],
469 - 'end_date' => $_GET['end_date'],
470 - 'status' => $_GET['post_status'],
471 - 'campaign_id' => $_GET['campaign_id'],
472 - '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'] ) ) : '',
473 1410 )
474 1411 );
475 1412
476 1413 /**
@@ -480,9 +1417,9 @@
480 1417 *
481 1418 * @param string $report_type The type of report.
482 1419 * @param array $args Export arguments.
483 1420 */
484 - $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
485 1422
486 1423 new $export_class( $export_args );
487 1424
488 1425 die();
@@ -495,9 +1432,10 @@
495 1432 *
496 1433 * @return false|void Returns false if the export failed. Exits otherwise.
497 1434 */
498 1435 public function export_campaigns() {
499 - 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
500 1438 return false;
501 1439 }
502 1440
503 1441 /**
@@ -509,14 +1447,13 @@
509 1447 */
510 1448 $export_args = apply_filters(
511 1449 'charitable_campaigns_export_args',
512 1450 array(
513 - 'start_date_from' => $_GET['start_date_from'],
514 - 'start_date_to' => $_GET['start_date_to'],
515 - 'end_date_from' => $_GET['end_date_from'],
516 - 'end_date_to' => $_GET['end_date_to'],
517 - 'status' => $_GET['status'],
518 - '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'] ) ) : '',
519 1456 )
520 1457 );
521 1458
522 1459 /**
@@ -526,9 +1463,9 @@
526 1463 *
527 1464 * @param string $report_type The type of report.
528 1465 * @param array $args Export arguments.
529 1466 */
530 - $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
531 1468
532 1469 new $export_class( $export_args );
533 1470
534 1471 die();
@@ -533,11 +1470,11 @@
533 1470
534 1471 die();
535 1472 }
536 1473
537 -
538 1474 /**
539 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).
540 1477 *
541 1478 * @uses charitable_admin_screens
542 1479 *
543 1480 * @since 1.0.0
@@ -547,20 +1484,358 @@
547 1484 public function get_charitable_screens() {
548 1485 /**
549 1486 * Filter admin screens where Charitable styles & scripts should be loaded.
550 1487 *
551 - * @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.
552 1492 *
553 1493 * @param string[] $screens List of screen ids.
554 1494 */
555 - return apply_filters( 'charitable_admin_screens', array(
556 - 'campaign',
557 - 'donation',
558 - 'charitable_page_charitable-settings',
559 - 'edit-campaign',
560 - 'edit-donation',
561 - 'dashboard',
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,
562 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' );
563 1838 }
564 1839 }
565 1840
566 1841 endif;