PluginProbe
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) / 1.8.13
Charitable – Donation & Fundraising Platform (Donation Forms, Recurring Donations & Fundraising Campaigns) v1.8.13
1.8.13 1.8.12.3 1.8.12.2 1.8.12.1 1.8.12 1.8.11.3 1.8.11.2 1.8.11.1 1.8.11 1.6.6 1.6.60 1.6.7 1.6.8 1.6.9 1.7.0 1.7.0.1 1.7.0.11 1.7.0.12 1.7.0.14 1.7.0.2 1.7.0.3 1.7.0.5 1.7.0.6 1.7.0.7 1.7.0.9 All 211 releases
← All changes | includes/admin/class-charitable-admin.php +1404 -101 1.6.8 → 1.8.13 View file →
@@ -2,17 +2,19 @@
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) 2019, 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.5.0
10 + * @version 1.8.4
11 11 */
12 12
13 13 // Exit if accessed directly.
14 -if ( ! defined( 'ABSPATH' ) ) { exit; }
14 +if ( ! defined( 'ABSPATH' ) ) {
15 + exit;
16 +}
15 17
16 18 if ( ! class_exists( 'Charitable_Admin' ) ) :
17 19
18 20 /**
@@ -44,18 +46,319 @@
44 46 * which can only be called during the start phase. In other words, don't try
45 47 * to instantiate this object.
46 48 *
47 49 * @since 1.0.0
50 + * @version 1.8.3 added third party plugin area.
48 51 */
49 52 protected function __construct() {
50 53 $this->load_dependencies();
51 54
52 - $this->donation_actions = new Charitable_Donation_Admin_Actions;
55 + $this->donation_actions = new Charitable_Donation_Admin_Actions();
53 56
57 + // Initialize About page.
58 + new Charitable_About();
59 +
54 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 );
55 83 }
56 84
57 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 + /**
58 361 * Returns and/or create the single instance of this class.
59 362 *
60 363 * @since 1.2.0
61 364 *
@@ -78,13 +381,33 @@
78 381 */
79 382 private function load_dependencies() {
80 383 $admin_dir = charitable()->get_path( 'includes' ) . 'admin/';
81 384
82 - require_once( $admin_dir . 'charitable-core-admin-functions.php' );
83 - require_once( $admin_dir . 'campaigns/charitable-admin-campaign-hooks.php' );
84 - require_once( $admin_dir . 'dashboard-widgets/charitable-dashboard-widgets-hooks.php' );
85 - require_once( $admin_dir . 'donations/charitable-admin-donation-hooks.php' );
86 - 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';
87 410 }
88 411
89 412 /**
90 413 * Get Charitable_Donation_Admin_Actions class.
@@ -104,25 +427,27 @@
104 427 *
105 428 * @return boolean|WP_Error WP_Error in case of error. Mixed results if the action was performed.
106 429 */
107 430 public function maybe_do_admin_action() {
431 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended
108 432 if ( ! array_key_exists( 'charitable_admin_action', $_GET ) ) {
109 433 return false;
110 434 }
111 435
436 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Check for required keys only
112 437 if ( count( array_diff( array( 'action_type', '_nonce', 'object_id' ), array_keys( $_GET ) ) ) ) {
113 438 return new WP_Error( __( 'Action could not be executed.', 'charitable' ) );
114 439 }
115 440
116 - if ( ! wp_verify_nonce( $_GET['_nonce'], 'donation_action' ) ) {
441 + if ( ! isset( $_GET['_nonce'] ) || ! wp_verify_nonce( $_GET['_nonce'], 'donation_action' ) ) { // phpcs:ignore.
117 442 return new WP_Error( __( 'Action could not be executed. Nonce check failed.', 'charitable' ) );
118 443 }
119 444
120 - if ( 'donation' != $_GET['action_type'] ) {
445 + if ( ! isset( $_GET['action_type'] ) || 'donation' !== $_GET['action_type'] ) {
121 446 return new WP_Error( __( 'Action from an unknown action type executed.', 'charitable' ) );
122 447 }
123 448
124 - 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.
125 450 }
126 451
127 452 /**
128 453 * Loads admin-only scripts and stylesheets.
@@ -127,26 +452,39 @@
127 452 /**
128 453 * Loads admin-only scripts and stylesheets.
129 454 *
130 455 * @since 1.0.0
456 + * @version 1.8.8.6
131 457 *
132 458 * @return void
133 459 */
134 460 public function admin_enqueue_scripts() {
135 - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
136 - $suffix = '';
137 - $version = '';
138 - } else {
139 - $suffix = '.min';
140 - $version = charitable()->get_version();
141 - }
142 461
462 + $min = charitable_get_min_suffix(); // 1.8.3
463 + $version = charitable_is_break_cache() ? charitable()->get_version() . '.' . time() : charitable()->get_version();
464 +
143 465 $assets_dir = charitable()->get_path( 'assets', false );
144 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 +
145 483 /* Menu styles are loaded everywhere in the WordPress dashboard. */
146 484 wp_register_style(
147 485 'charitable-admin-menu',
148 - $assets_dir . 'css/charitable-admin-menu' . $suffix . '.css',
486 + $assets_dir . 'css/admin/charitable-admin-menu' . $min . '.css',
149 487 array(),
150 488 $version
151 489 );
152 490
@@ -151,12 +489,22 @@
151 489 );
152 490
153 491 wp_enqueue_style( 'charitable-admin-menu' );
154 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 +
155 503 /* Admin page styles are registered but only enqueued when necessary. */
156 504 wp_register_style(
157 505 'charitable-admin-pages',
158 - $assets_dir . 'css/charitable-admin-pages' . $suffix . '.css',
506 + $assets_dir . 'css/admin/charitable-admin-pages' . $min . '.css',
159 507 array(),
160 508 $version
161 509 );
162 510
@@ -162,13 +510,14 @@
162 510
163 511 /* The following styles are only loaded on Charitable screens. */
164 512 $screen = get_current_screen();
165 513
166 - 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 ) ) ) ) {
167 516
168 517 wp_register_style(
169 518 'charitable-admin',
170 - $assets_dir . 'css/charitable-admin' . $suffix . '.css',
519 + $assets_dir . 'css/admin/charitable-admin-legacy' . $min . '.css',
171 520 array(),
172 521 $version
173 522 );
174 523
@@ -173,35 +522,72 @@
173 522 );
174 523
175 524 wp_enqueue_style( 'charitable-admin' );
176 525
177 - $dependencies = array( 'jquery-ui-datepicker', 'jquery-ui-tabs', 'jquery-ui-sortable' );
178 - $localized_vars = array(
179 - 'suggested_amount_description_placeholder' => __( 'Optional Description', 'charitable' ),
180 - 'suggested_amount_placeholder' => __( 'Amount', '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
181 532 );
182 533
183 - 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 ) {
184 567 wp_register_script(
185 568 'accounting',
186 - $assets_dir . 'js/libraries/accounting'. $suffix . '.js',
187 - array( 'jquery-core' ),
569 + $assets_dir . 'js/libraries/accounting' . $min . '.js',
570 + array( 'jquery' ),
188 571 $version,
189 572 true
190 573 );
191 574
192 575 $dependencies[] = 'accounting';
193 - $localized_vars = array_merge( $localized_vars, array(
194 - 'currency_format_num_decimals' => esc_attr( charitable_get_currency_helper()->get_decimals() ),
195 - 'currency_format_decimal_sep' => esc_attr( charitable_get_currency_helper()->get_decimal_separator() ),
196 - 'currency_format_thousand_sep' => esc_attr( charitable_get_currency_helper()->get_thousands_separator() ),
197 - 'currency_format' => esc_attr( charitable_get_currency_helper()->get_accounting_js_format() ),
198 - ) );
576 + $localized_vars = array_merge(
577 + $localized_vars,
578 + array(
579 + 'currency_format_num_decimals' => esc_attr( charitable_get_currency_helper()->get_decimals() ),
580 + 'currency_format_decimal_sep' => esc_attr( charitable_get_currency_helper()->get_decimal_separator() ),
581 + 'currency_format_thousand_sep' => esc_attr( charitable_get_currency_helper()->get_thousands_separator() ),
582 + 'currency_format' => esc_attr( charitable_get_currency_helper()->get_accounting_js_format() ),
583 + )
584 + );
199 585 }
200 586
201 587 wp_register_script(
202 588 'charitable-admin',
203 - $assets_dir . 'js/charitable-admin' . $suffix . '.js',
589 + $assets_dir . 'js/admin/charitable-admin' . $min . '.js',
204 590 $dependencies,
205 591 $version,
206 592 false
207 593 );
@@ -207,8 +593,72 @@
207 593 );
208 594
209 595 wp_enqueue_script( 'charitable-admin' );
210 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 +
211 661 /**
212 662 * Filter the admin Javascript vars.
213 663 *
214 664 * @since 1.0.0
@@ -214,18 +664,157 @@
214 664 * @since 1.0.0
215 665 *
216 666 * @param array $localized_vars The vars.
217 667 */
218 - $localized_vars = apply_filters( 'charitable_localized_javascript_vars', $localized_vars );
668 + $localized_vars = (array) apply_filters( 'charitable_localized_javascript_vars', $localized_vars );
219 669
220 670 wp_localize_script( 'charitable-admin', 'CHARITABLE', $localized_vars );
221 671
222 - }//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 + ) );
223 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 +
224 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(
225 814 'charitable-admin-notice',
226 - $assets_dir . 'js/charitable-admin-notice' . $suffix . '.js',
227 - array( 'jquery-core' ),
815 + $assets_dir . 'js/admin/charitable-admin-notice' . $min . '.js',
816 + array( 'jquery' ),
228 817 $version,
229 818 false
230 819 );
231 820
@@ -230,10 +819,10 @@
230 819 );
231 820
232 821 wp_register_script(
233 822 'charitable-admin-media',
234 - $assets_dir . 'js/charitable-admin-media' . $suffix . '.js',
235 - array( 'jquery-core' ),
823 + $assets_dir . 'js/admin/charitable-admin-media' . $min . '.js',
824 + array( 'jquery' ),
236 825 $version,
237 826 false
238 827 );
239 828
@@ -238,10 +827,10 @@
238 827 );
239 828
240 829 wp_register_script(
241 830 'lean-modal',
242 - $assets_dir . 'js/libraries/leanModal' . $suffix . '.js',
243 - array( 'jquery-core' ),
831 + $assets_dir . 'js/libraries/leanModal' . $min . '.js',
832 + array( 'jquery' ),
244 833 $version,
245 834 true
246 835 );
247 836
@@ -246,9 +835,9 @@
246 835 );
247 836
248 837 wp_register_style(
249 838 'lean-modal-css',
250 - $assets_dir . 'css/modal' . $suffix . '.css',
839 + $assets_dir . 'css/modal' . $min . '.css',
251 840 array(),
252 841 $version
253 842 );
254 843
@@ -253,19 +842,40 @@
253 842 );
254 843
255 844 wp_register_script(
256 845 'charitable-admin-tables',
257 - $assets_dir . 'js/charitable-admin-tables' . $suffix . '.js',
258 - array( 'jquery-core', 'lean-modal' ),
846 + $assets_dir . 'js/admin/charitable-admin-tables' . $min . '.js',
847 + array( 'jquery', 'lean-modal' ),
259 848 $version,
260 849 true
261 850 );
851 +
852 + wp_register_script(
853 + 'select2',
854 + $assets_dir . 'js/libraries/select2' . $min . '.js',
855 + array( 'jquery' ),
856 + $version,
857 + true
858 + );
859 +
860 + wp_register_style(
861 + 'select2-css',
862 + $assets_dir . 'css/libraries/select2' . $min . '.css',
863 + array(),
864 + $version
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.
262 868 }
263 869
264 870 /**
265 871 * Set admin body classes.
266 872 *
267 - * @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.
268 878 *
269 879 * @param string $classes Existing list of classes.
270 880 * @return string
271 881 */
@@ -271,12 +881,46 @@
271 881 */
272 882 public function set_body_class( $classes ) {
273 883 $screen = get_current_screen();
274 884
275 - 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.
276 886 $classes .= ' charitable-admin-donation-form';
277 887 }
278 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 +
279 923 return $classes;
280 924 }
281 925
282 926 /**
@@ -282,23 +926,78 @@
282 926 /**
283 927 * Add notices to the dashboard.
284 928 *
285 929 * @since 1.4.0
930 + * @version 1.8.3 Added check for PHP Version deprecation.
286 931 *
287 932 * @return void
288 933 */
289 934 public function add_notices() {
290 - /* 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.
291 955 $this->add_version_update_notices();
956 + }
292 957
293 - /* Render notices. */
294 - 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 + }
295 994 }
296 995
297 996 /**
298 997 * Add version update notices to the dashboard.
299 998 *
300 - * @since 1.4.6
999 + * @since 1.4.6 (deprecated in 1.8.0)
301 1000 *
302 1001 * @return void
303 1002 */
304 1003 public function add_version_update_notices() {
@@ -306,15 +1005,17 @@
306 1005 return;
307 1006 }
308 1007
309 1008 $notices = array(
310 - /* translators: %s: link */
311 - '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' ),
312 - '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'
313 1013 ),
314 - /* translators: %s: link */
315 - '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' ),
316 - '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'
317 1018 ),
318 1019 );
319 1020
320 1021 $helper = charitable_get_admin_notices();
@@ -328,21 +1029,100 @@
328 1029 }
329 1030 }
330 1031
331 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 + /**
332 1101 * Dismiss a notice.
333 1102 *
334 - * @since 1.4.0
1103 + * @since 1.4.6
335 1104 *
336 1105 * @return void
337 1106 */
338 1107 public function dismiss_notice() {
339 - if ( ! isset( $_POST['notice'] ) ) {
1108 + if ( ! isset( $_POST['notice'] ) ) { // phpcs:ignore.
340 1109 wp_send_json_error();
341 1110 }
342 1111
343 - $ret = delete_transient( 'charitable_' . $_POST['notice'] . '_notice', true );
1112 + $notice = sanitize_text_field( wp_unslash( $_POST['notice'] ) ); // phpcs:ignore.
344 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 +
345 1125 if ( ! $ret ) {
346 1126 wp_send_json_error( $ret );
347 1127 }
348 1128
@@ -349,8 +1129,181 @@
349 1129 wp_send_json_success();
350 1130 }
351 1131
352 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 + /**
353 1306 * Adds one or more classes to the body tag in the dashboard.
354 1307 *
355 1308 * @since 1.0.0
356 1309 *
@@ -363,8 +1316,15 @@
363 1316 if ( in_array( $screen->post_type, array( Charitable::DONATION_POST_TYPE, Charitable::CAMPAIGN_POST_TYPE ) ) ) {
364 1317 $classes .= ' post-type-charitable';
365 1318 }
366 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 +
367 1327 return $classes;
368 1328 }
369 1329
370 1330 /**
@@ -393,20 +1353,16 @@
393 1353 if ( plugin_basename( charitable()->get_path() ) != $file ) {
394 1354 return $links;
395 1355 }
396 1356
397 - $extensions_link = esc_url(
398 - add_query_arg(
399 - array(
400 - 'utm_source' => 'plugins-page',
401 - 'utm_medium' => 'plugin-row',
402 - 'utm_campaign' => 'admin',
403 - ),
404 - 'https://wpcharitable.com/extensions/'
405 - )
1357 + // Use an updated UTM.
1358 + $extensions_link = charitable_ga_url(
1359 + 'https://wpcharitable.com/extensions/',
1360 + urlencode( 'Admin Plugin Page' ),
1361 + urlencode( 'Extensions' )
406 1362 );
407 1363
408 - $links[] = '<a href="' . $extensions_link . '">' . __( 'Extensions', 'charitable' ) . '</a>';
1364 + $links[] = '<a href="' . $extensions_link . '" target="_blank" rel="noopener">' . __( 'Extensions', 'charitable' ) . '</a>';
409 1365
410 1366 return $links;
411 1367 }
412 1368
@@ -430,9 +1386,10 @@
430 1386 *
431 1387 * @return false|void Returns false if the export failed. Exits otherwise.
432 1388 */
433 1389 public function export_donations() {
434 - 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
435 1392 return false;
436 1393 }
437 1394
438 1395 /**
@@ -441,15 +1398,18 @@
441 1398 * @since 1.3.0
442 1399 *
443 1400 * @param array $args Export arguments.
444 1401 */
445 - $export_args = apply_filters( 'charitable_donations_export_args', array(
446 - 'start_date' => $_GET['start_date'],
447 - 'end_date' => $_GET['end_date'],
448 - 'status' => $_GET['post_status'],
449 - 'campaign_id' => $_GET['campaign_id'],
450 - 'report_type' => $_GET['report_type'],
451 - ) );
1402 + $export_args = apply_filters(
1403 + 'charitable_donations_export_args',
1404 + array(
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'] ) ) : '',
1410 + )
1411 + );
452 1412
453 1413 /**
454 1414 * Filter the export class name.
455 1415 *
@@ -457,9 +1417,9 @@
457 1417 *
458 1418 * @param string $report_type The type of report.
459 1419 * @param array $args Export arguments.
460 1420 */
461 - $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
462 1422
463 1423 new $export_class( $export_args );
464 1424
465 1425 die();
@@ -472,25 +1432,30 @@
472 1432 *
473 1433 * @return false|void Returns false if the export failed. Exits otherwise.
474 1434 */
475 1435 public function export_campaigns() {
476 - 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
477 1438 return false;
478 1439 }
479 1440
480 1441 /**
481 - * Filter the donation export arguments.
1442 + * Filter the campaign export arguments.
482 1443 *
483 1444 * @since 1.6.0
484 1445 *
485 1446 * @param array $args Export arguments.
486 1447 */
487 - $export_args = apply_filters( 'charitable_campaigns_export_args', array(
488 - 'start_date' => $_GET['start_date'],
489 - 'end_date' => $_GET['end_date'],
490 - 'status' => $_GET['status'],
491 - 'report_type' => $_GET['report_type'],
492 - ) );
1448 + $export_args = apply_filters(
1449 + 'charitable_campaigns_export_args',
1450 + array(
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'] ) ) : '',
1456 + )
1457 + );
493 1458
494 1459 /**
495 1460 * Filter the export class name.
496 1461 *
@@ -498,9 +1463,9 @@
498 1463 *
499 1464 * @param string $report_type The type of report.
500 1465 * @param array $args Export arguments.
501 1466 */
502 - $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
503 1468
504 1469 new $export_class( $export_args );
505 1470
506 1471 die();
@@ -505,11 +1470,11 @@
505 1470
506 1471 die();
507 1472 }
508 1473
509 -
510 1474 /**
511 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).
512 1477 *
513 1478 * @uses charitable_admin_screens
514 1479 *
515 1480 * @since 1.0.0
@@ -519,20 +1484,358 @@
519 1484 public function get_charitable_screens() {
520 1485 /**
521 1486 * Filter admin screens where Charitable styles & scripts should be loaded.
522 1487 *
523 - * @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.
524 1492 *
525 1493 * @param string[] $screens List of screen ids.
526 1494 */
527 - return apply_filters( 'charitable_admin_screens', array(
528 - 'campaign',
529 - 'donation',
530 - 'charitable_page_charitable-settings',
531 - 'edit-campaign',
532 - 'edit-donation',
533 - '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,
534 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' );
535 1838 }
536 1839 }
537 1840
538 1841 endif;