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 +1264 -120 1.7.0.7 → 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,20 +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' );
57 61
58 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 );
59 83 }
60 84
61 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 + /**
62 361 * Returns and/or create the single instance of this class.
63 362 *
64 363 * @since 1.2.0
65 364 *
@@ -82,13 +381,33 @@
82 381 */
83 382 private function load_dependencies() {
84 383 $admin_dir = charitable()->get_path( 'includes' ) . 'admin/';
85 384
86 - require_once( $admin_dir . 'charitable-core-admin-functions.php' );
87 - require_once( $admin_dir . 'campaigns/charitable-admin-campaign-hooks.php' );
88 - require_once( $admin_dir . 'dashboard-widgets/charitable-dashboard-widgets-hooks.php' );
89 - require_once( $admin_dir . 'donations/charitable-admin-donation-hooks.php' );
90 - 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';
91 410 }
92 411
93 412 /**
94 413 * Get Charitable_Donation_Admin_Actions class.
@@ -108,25 +427,27 @@
108 427 *
109 428 * @return boolean|WP_Error WP_Error in case of error. Mixed results if the action was performed.
110 429 */
111 430 public function maybe_do_admin_action() {
431 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
112 432 if ( ! array_key_exists( 'charitable_admin_action', $_GET ) ) {
113 433 return false;
114 434 }
115 435
436 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Check for required keys only
116 437 if ( count( array_diff( array( 'action_type', '_nonce', 'object_id' ), array_keys( $_GET ) ) ) ) {
117 438 return new WP_Error( __( 'Action could not be executed.', 'charitable' ) );
118 439 }
119 440
120 - if ( ! wp_verify_nonce( $_GET['_nonce'], 'donation_action' ) ) {
441 + if ( ! isset( $_GET['_nonce'] ) || ! wp_verify_nonce( $_GET['_nonce'], 'donation_action' ) ) { // phpcs:ignore.
121 442 return new WP_Error( __( 'Action could not be executed. Nonce check failed.', 'charitable' ) );
122 443 }
123 444
124 - if ( 'donation' != $_GET['action_type'] ) {
445 + if ( ! isset( $_GET['action_type'] ) || 'donation' !== $_GET['action_type'] ) {
125 446 return new WP_Error( __( 'Action from an unknown action type executed.', 'charitable' ) );
126 447 }
127 448
128 - 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.
129 450 }
130 451
131 452 /**
132 453 * Loads admin-only scripts and stylesheets.
@@ -131,26 +452,39 @@
131 452 /**
132 453 * Loads admin-only scripts and stylesheets.
133 454 *
134 455 * @since 1.0.0
456 + * @version 1.8.8.6
135 457 *
136 458 * @return void
137 459 */
138 460 public function admin_enqueue_scripts() {
139 - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
140 - $suffix = '';
141 - $version = '';
142 - } else {
143 - $suffix = '.min';
144 - $version = charitable()->get_version();
145 - }
146 461
462 + $min = charitable_get_min_suffix(); // 1.8.3
463 + $version = charitable_is_break_cache() ? charitable()->get_version() . '.' . time() : charitable()->get_version();
464 +
147 465 $assets_dir = charitable()->get_path( 'assets', false );
148 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 +
149 483 /* Menu styles are loaded everywhere in the WordPress dashboard. */
150 484 wp_register_style(
151 485 'charitable-admin-menu',
152 - $assets_dir . 'css/charitable-admin-menu' . $suffix . '.css',
486 + $assets_dir . 'css/admin/charitable-admin-menu' . $min . '.css',
153 487 array(),
154 488 $version
155 489 );
156 490
@@ -157,9 +491,9 @@
157 491 wp_enqueue_style( 'charitable-admin-menu' );
158 492
159 493 wp_register_script(
160 494 'charitable-admin-pages-all',
161 - $assets_dir . 'js/charitable-admin-pages' . $suffix . '.js',
495 + $assets_dir . 'js/admin/charitable-admin-pages' . $min . '.js',
162 496 array( 'jquery' ),
163 497 $version,
164 498 false
165 499 );
@@ -168,9 +502,9 @@
168 502
169 503 /* Admin page styles are registered but only enqueued when necessary. */
170 504 wp_register_style(
171 505 'charitable-admin-pages',
172 - $assets_dir . 'css/charitable-admin-pages' . $suffix . '.css',
506 + $assets_dir . 'css/admin/charitable-admin-pages' . $min . '.css',
173 507 array(),
174 508 $version
175 509 );
176 510
@@ -177,13 +511,13 @@
177 511 /* The following styles are only loaded on Charitable screens. */
178 512 $screen = get_current_screen();
179 513
180 514 /* This check covers the category and tags pages, only load the main admin css (upgrade banner, etc.) */
181 - 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 ) ) ) ) {
182 516
183 517 wp_register_style(
184 518 'charitable-admin',
185 - $assets_dir . 'css/charitable-admin' . $suffix . '.css',
519 + $assets_dir . 'css/admin/charitable-admin-legacy' . $min . '.css',
186 520 array(),
187 521 $version
188 522 );
189 523
@@ -190,9 +524,9 @@
190 524 wp_enqueue_style( 'charitable-admin' );
191 525
192 526 wp_register_script(
193 527 'charitable-admin-notice',
194 - $assets_dir . 'js/charitable-admin-notice' . $suffix . '.js',
528 + $assets_dir . 'js/admin/charitable-admin-notice' . $min . '.js',
195 529 array( 'jquery' ),
196 530 $version,
197 531 false
198 532 );
@@ -198,37 +532,42 @@
198 532 );
199 533
200 534 }
201 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 + );
202 543
203 - /* This check covers the category and tags pages, only load the main admin css (upgrade banner, etc.) */
204 - if ( ! is_null( $screen ) && in_array( $screen->base, array( 'charitable_page_charitable-addons' ) ) ) {
544 + wp_enqueue_style( 'charitable-admin-2.0' );
205 545
546 + wp_enqueue_style(
547 + 'charitable-notifications-v2',
548 + $assets_dir . 'css/admin/notifications-v2' . $min . '.css',
549 + array(),
550 + $version
551 + );
206 552
553 + wp_register_style(
554 + 'charitable-admin',
555 + $assets_dir . 'css/admin/charitable-admin-legacy' . $min . '.css',
556 + array(),
557 + $version
558 + );
207 559
208 - }
560 + wp_enqueue_style( 'charitable-admin' );
209 561
210 - if ( ! is_null( $screen ) && in_array( $screen->id, $this->get_charitable_screens() ) ) {
562 + if ( ! is_null( $screen ) && in_array( $screen->id, charitable_get_charitable_screens() ) ) {
211 563
212 - wp_register_style(
213 - 'charitable-admin',
214 - $assets_dir . 'css/charitable-admin' . $suffix . '.css',
215 - array(),
216 - $version
217 - );
564 + $dependencies = array( 'jquery-ui-datepicker', 'jquery-ui-tabs', 'jquery-ui-sortable' );
218 565
219 - wp_enqueue_style( 'charitable-admin' );
220 -
221 - $dependencies = array( 'jquery-ui-datepicker', 'jquery-ui-tabs', 'jquery-ui-sortable' );
222 - $localized_vars = array(
223 - 'suggested_amount_placeholder' => __( 'Amount', 'charitable' ),
224 - 'suggested_amount_description_placeholder' => __( 'Optional Description', 'charitable' ),
225 - );
226 -
227 - if ( 'donation' == $screen->id ) {
566 + if ( 'donation' === $screen->id ) {
228 567 wp_register_script(
229 568 'accounting',
230 - $assets_dir . 'js/libraries/accounting'. $suffix . '.js',
569 + $assets_dir . 'js/libraries/accounting' . $min . '.js',
231 570 array( 'jquery' ),
232 571 $version,
233 572 true
234 573 );
@@ -246,9 +585,9 @@
246 585 }
247 586
248 587 wp_register_script(
249 588 'charitable-admin',
250 - $assets_dir . 'js/charitable-admin' . $suffix . '.js',
589 + $assets_dir . 'js/admin/charitable-admin' . $min . '.js',
251 590 $dependencies,
252 591 $version,
253 592 false
254 593 );
@@ -254,8 +593,72 @@
254 593 );
255 594
256 595 wp_enqueue_script( 'charitable-admin' );
257 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 +
258 661 /**
259 662 * Filter the admin Javascript vars.
260 663 *
261 664 * @since 1.0.0
@@ -261,38 +664,156 @@
261 664 * @since 1.0.0
262 665 *
263 666 * @param array $localized_vars The vars.
264 667 */
265 - $localized_vars = apply_filters( 'charitable_localized_javascript_vars', $localized_vars );
668 + $localized_vars = (array) apply_filters( 'charitable_localized_javascript_vars', $localized_vars );
266 669
267 670 wp_localize_script( 'charitable-admin', 'CHARITABLE', $localized_vars );
268 671
269 - // $strings = [
270 - // 'nonce' => wp_create_nonce( 'charitable-admin' ),
271 - // 'ajax_url' => admin_url( 'admin-ajax.php' ),
272 - // 'davidtest' => 1,
273 - // ];
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 + ) );
274 676
275 - // /**
276 - // * Allow theme/plugin developers to adjust main strings on backend/admin part.
277 - // *
278 - // * @since 1.7.0.3
279 - // *
280 - // * @param array $strings Main admin localized strings.
281 - // */
282 - // $strings = (array) apply_filters( 'charitable_admin_strings', $strings );
677 + /* color picker for admin settings */
678 + wp_enqueue_script( 'wp-color-picker', false, false, false, true ); // phpcs:ignore.
679 + wp_enqueue_style( 'wp-color-picker' );
283 680
284 - // wp_localize_script(
285 - // 'charitable-admin',
286 - // 'charitable_admin',
287 - // $strings
288 - // );
681 + } // end if
289 682
290 - } // end if
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 + );
291 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 +
292 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(
293 814 'charitable-admin-notice',
294 - $assets_dir . 'js/charitable-admin-notice' . $suffix . '.js',
815 + $assets_dir . 'js/admin/charitable-admin-notice' . $min . '.js',
295 816 array( 'jquery' ),
296 817 $version,
297 818 false
298 819 );
@@ -298,9 +819,9 @@
298 819 );
299 820
300 821 wp_register_script(
301 822 'charitable-admin-media',
302 - $assets_dir . 'js/charitable-admin-media' . $suffix . '.js',
823 + $assets_dir . 'js/admin/charitable-admin-media' . $min . '.js',
303 824 array( 'jquery' ),
304 825 $version,
305 826 false
306 827 );
@@ -306,9 +827,9 @@
306 827 );
307 828
308 829 wp_register_script(
309 830 'lean-modal',
310 - $assets_dir . 'js/libraries/leanModal' . $suffix . '.js',
831 + $assets_dir . 'js/libraries/leanModal' . $min . '.js',
311 832 array( 'jquery' ),
312 833 $version,
313 834 true
314 835 );
@@ -314,9 +835,9 @@
314 835 );
315 836
316 837 wp_register_style(
317 838 'lean-modal-css',
318 - $assets_dir . 'css/modal' . $suffix . '.css',
839 + $assets_dir . 'css/modal' . $min . '.css',
319 840 array(),
320 841 $version
321 842 );
322 843
@@ -321,9 +842,9 @@
321 842 );
322 843
323 844 wp_register_script(
324 845 'charitable-admin-tables',
325 - $assets_dir . 'js/charitable-admin-tables' . $suffix . '.js',
846 + $assets_dir . 'js/admin/charitable-admin-tables' . $min . '.js',
326 847 array( 'jquery', 'lean-modal' ),
327 848 $version,
328 849 true
329 850 );
@@ -329,28 +850,32 @@
329 850 );
330 851
331 852 wp_register_script(
332 853 'select2',
333 - $assets_dir . 'js/libraries/select2' . $suffix . '.js',
854 + $assets_dir . 'js/libraries/select2' . $min . '.js',
334 855 array( 'jquery' ),
335 - '4.0.12',
856 + $version,
336 857 true
337 858 );
338 859
339 860 wp_register_style(
340 861 'select2-css',
341 - $assets_dir . 'css/libraries/select2' . $suffix . '.css',
862 + $assets_dir . 'css/libraries/select2' . $min . '.css',
342 863 array(),
343 - '4.0.12'
864 + $version
344 865 );
345 866
346 - do_action( 'after_charitable_admin_enqueue_scripts', $suffix, $version, $assets_dir );
867 + do_action( 'after_charitable_admin_enqueue_scripts', $min, $version, $assets_dir ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Hook name is already in use by other code (charitable-admin-hooks.php). Changing it would break existing functionality.
347 868 }
348 869
349 870 /**
350 871 * Set admin body classes.
351 872 *
352 - * @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.
353 878 *
354 879 * @param string $classes Existing list of classes.
355 880 * @return string
356 881 */
@@ -356,12 +881,46 @@
356 881 */
357 882 public function set_body_class( $classes ) {
358 883 $screen = get_current_screen();
359 884
360 - 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.
361 886 $classes .= ' charitable-admin-donation-form';
362 887 }
363 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 +
364 923 return $classes;
365 924 }
366 925
367 926 /**
@@ -367,23 +926,78 @@
367 926 /**
368 927 * Add notices to the dashboard.
369 928 *
370 929 * @since 1.4.0
930 + * @version 1.8.3 Added check for PHP Version deprecation.
371 931 *
372 932 * @return void
373 933 */
374 934 public function add_notices() {
375 - /* 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.
376 955 $this->add_version_update_notices();
956 + }
377 957
378 - /* Render notices. */
379 - 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 + }
380 994 }
381 995
382 996 /**
383 997 * Add version update notices to the dashboard.
384 998 *
385 - * @since 1.4.6
999 + * @since 1.4.6 (deprecated in 1.8.0)
386 1000 *
387 1001 * @return void
388 1002 */
389 1003 public function add_version_update_notices() {
@@ -391,14 +1005,16 @@
391 1005 return;
392 1006 }
393 1007
394 1008 $notices = array(
395 - /* translators: %s: link */
396 - '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' ),
397 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'
398 1013 ),
399 - /* translators: %s: link */
400 - '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' ),
401 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'
402 1018 ),
403 1019 );
404 1020
@@ -413,21 +1029,100 @@
413 1029 }
414 1030 }
415 1031
416 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 + /**
417 1101 * Dismiss a notice.
418 1102 *
419 - * @since 1.4.0
1103 + * @since 1.4.6
420 1104 *
421 1105 * @return void
422 1106 */
423 1107 public function dismiss_notice() {
424 - if ( ! isset( $_POST['notice'] ) ) {
1108 + if ( ! isset( $_POST['notice'] ) ) { // phpcs:ignore.
425 1109 wp_send_json_error();
426 1110 }
427 1111
428 - $ret = delete_transient( 'charitable_' . $_POST['notice'] . '_notice', true );
1112 + $notice = sanitize_text_field( wp_unslash( $_POST['notice'] ) ); // phpcs:ignore.
429 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 +
430 1125 if ( ! $ret ) {
431 1126 wp_send_json_error( $ret );
432 1127 }
433 1128
@@ -441,13 +1136,13 @@
441 1136 *
442 1137 * @return void
443 1138 */
444 1139 public function dismiss_banner() {
445 - if ( ! isset( $_POST['banner_id'] ) ) {
1140 + if ( ! isset( $_POST['banner_id'] ) ) { // phpcs:ignore.
446 1141 wp_send_json_error();
447 1142 }
448 1143
449 - $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.
450 1145
451 1146 if ( ! $ret ) {
452 1147 wp_send_json_error( $ret );
453 1148 }
@@ -454,9 +1149,29 @@
454 1149
455 1150 wp_send_json_success();
456 1151 }
457 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 + }
458 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 +
459 1174 /**
460 1175 * Dismiss a five star rating.
461 1176 *
462 1177 * @since 1.7.0
@@ -463,16 +1178,16 @@
463 1178 *
464 1179 * @return void
465 1180 */
466 1181 public function dismiss_five_star_rating() {
467 - if ( ! isset( $_POST['banner_id'] ) ) {
1182 + if ( ! isset( $_POST['banner_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
468 1183 wp_send_json_error();
469 1184 }
470 1185
471 - $check = get_transient( 'charitable_' . $_POST['banner_id'] . '_banner' );
1186 + $check = get_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner' ); // phpcs:ignore.
472 1187
473 1188 if ( ! $check ) {
474 - $ret = set_transient( 'charitable_' . $_POST['banner_id'] . '_banner', true );
1189 + $ret = set_transient( 'charitable_' . esc_attr( $_POST['banner_id'] ) . '_banner', true ); // phpcs:ignore.
475 1190 if ( ! $ret ) {
476 1191 wp_send_json_error( $ret );
477 1192 }
478 1193 }
@@ -480,8 +1195,73 @@
480 1195 wp_send_json_success();
481 1196 }
482 1197
483 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 + /**
484 1264 * Dismiss a five star rating.
485 1265 *
486 1266 * @since 1.7.0
487 1267 *
@@ -487,9 +1267,9 @@
487 1267 *
488 1268 * @return void
489 1269 */
490 1270 public function dismiss_lite_cta() {
491 - if ( ! isset( $_POST['chartiable_action'] ) ) {
1271 + if ( ! isset( $_POST['charitable_action'] ) ) { // phpcs:ignore.
492 1272 wp_send_json_error();
493 1273 }
494 1274
495 1275 $updated = update_option( 'charitable_lite_settings_upgrade', true );
@@ -498,9 +1278,29 @@
498 1278 wp_send_json_success();
499 1279 } else {
500 1280 wp_send_json_error();
501 1281 }
1282 + }
502 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 + }
503 1303 }
504 1304
505 1305 /**
506 1306 * Adds one or more classes to the body tag in the dashboard.
@@ -516,8 +1316,15 @@
516 1316 if ( in_array( $screen->post_type, array( Charitable::DONATION_POST_TYPE, Charitable::CAMPAIGN_POST_TYPE ) ) ) {
517 1317 $classes .= ' post-type-charitable';
518 1318 }
519 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 +
520 1327 return $classes;
521 1328 }
522 1329
523 1330 /**
@@ -549,9 +1356,9 @@
549 1356
550 1357 // Use an updated UTM.
551 1358 $extensions_link = charitable_ga_url(
552 1359 'https://wpcharitable.com/extensions/',
553 - urlencode( 'Admin Plugin Page'),
1360 + urlencode( 'Admin Plugin Page' ),
554 1361 urlencode( 'Extensions' )
555 1362 );
556 1363
557 1364 $links[] = '<a href="' . $extensions_link . '" target="_blank" rel="noopener">' . __( 'Extensions', 'charitable' ) . '</a>';
@@ -580,9 +1387,9 @@
580 1387 * @return false|void Returns false if the export failed. Exits otherwise.
581 1388 */
582 1389 public function export_donations() {
583 1390
584 - if ( ! wp_verify_nonce( $_GET['_charitable_export_nonce'], 'charitable_export_donations' ) ) {
1391 + if ( ! isset( $_GET['_charitable_export_nonce'] ) || ! wp_verify_nonce( $_GET['_charitable_export_nonce'], 'charitable_export_donations' ) ) { // phpcs:ignore
585 1392 return false;
586 1393 }
587 1394
588 1395 /**
@@ -594,13 +1401,13 @@
594 1401 */
595 1402 $export_args = apply_filters(
596 1403 'charitable_donations_export_args',
597 1404 array(
598 - 'start_date' => $_GET['start_date'],
599 - 'end_date' => $_GET['end_date'],
600 - 'status' => $_GET['post_status'],
601 - 'campaign_id' => $_GET['campaign_id'],
602 - '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'] ) ) : '',
603 1410 )
604 1411 );
605 1412
606 1413 /**
@@ -610,9 +1417,9 @@
610 1417 *
611 1418 * @param string $report_type The type of report.
612 1419 * @param array $args Export arguments.
613 1420 */
614 - $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
615 1422
616 1423 new $export_class( $export_args );
617 1424
618 1425 die();
@@ -625,9 +1432,10 @@
625 1432 *
626 1433 * @return false|void Returns false if the export failed. Exits otherwise.
627 1434 */
628 1435 public function export_campaigns() {
629 - 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
630 1438 return false;
631 1439 }
632 1440
633 1441 /**
@@ -639,14 +1447,13 @@
639 1447 */
640 1448 $export_args = apply_filters(
641 1449 'charitable_campaigns_export_args',
642 1450 array(
643 - 'start_date_from' => $_GET['start_date_from'],
644 - 'start_date_to' => $_GET['start_date_to'],
645 - 'end_date_from' => $_GET['end_date_from'],
646 - 'end_date_to' => $_GET['end_date_to'],
647 - 'status' => $_GET['status'],
648 - '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'] ) ) : '',
649 1456 )
650 1457 );
651 1458
652 1459 /**
@@ -656,9 +1463,9 @@
656 1463 *
657 1464 * @param string $report_type The type of report.
658 1465 * @param array $args Export arguments.
659 1466 */
660 - $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
661 1468
662 1469 new $export_class( $export_args );
663 1470
664 1471 die();
@@ -665,8 +1472,9 @@
665 1472 }
666 1473
667 1474 /**
668 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).
669 1477 *
670 1478 * @uses charitable_admin_screens
671 1479 *
672 1480 * @since 1.0.0
@@ -676,22 +1484,358 @@
676 1484 public function get_charitable_screens() {
677 1485 /**
678 1486 * Filter admin screens where Charitable styles & scripts should be loaded.
679 1487 *
680 - * @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.
681 1492 *
682 1493 * @param string[] $screens List of screen ids.
683 1494 */
684 - return apply_filters( 'charitable_admin_screens', array(
685 - 'campaign',
686 - 'donation',
687 - 'charitable_page_charitable-settings',
688 - 'edit-campaign',
689 - 'edit-donation',
690 - 'dashboard',
691 - 'toplevel_page_charitable',
692 - 'charitable_page_charitable-addons',
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,
693 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' );
694 1838 }
695 1839 }
696 1840
697 1841 endif;