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/gateways/class-charitable-gateways.php +1622 -99 1.6.7 → 1.8.13 View file →
@@ -1,17 +1,21 @@
1 1 <?php
2 2 /**
3 3 * Class that sets up the gateways.
4 4 *
5 - * @version 1.0.0
6 - * @package Charitable/Classes/Charitable_Gateways
7 - * @author Eric Daams
8 - * @copyright Copyright (c) 2018, Studio 164a
9 - * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
5 + * @package Charitable/Classes/Charitable_Gateways
6 + * @author David Bisset
7 + * @copyright Copyright (c) 2023, WP Charitable LLC
8 + * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9 + * @since 1.0.0
10 + * @version 1.6.57
11 + * @version 1.8.7 - Added Square.
10 12 */
11 13
12 14 // Exit if accessed directly.
13 -if ( ! defined( 'ABSPATH' ) ) { exit; }
15 +if ( ! defined( 'ABSPATH' ) ) {
16 + exit;
17 +}
14 18
15 19 if ( ! class_exists( 'Charitable_Gateways' ) ) :
16 20
17 21 /**
@@ -16,9 +20,9 @@
16 20
17 21 /**
18 22 * Charitable_Gateways
19 23 *
20 - * @since 1.0.0
24 + * @since 1.0.0
21 25 */
22 26 class Charitable_Gateways {
23 27
24 28 /**
@@ -23,9 +27,11 @@
23 27
24 28 /**
25 29 * The single instance of this class.
26 30 *
27 - * @var Charitable_Gateways|null
31 + * @since 1.0.0
32 + *
33 + * @var Charitable_Gateways|null
28 34 */
29 35 private static $instance = null;
30 36
31 37 /**
@@ -30,9 +36,11 @@
30 36
31 37 /**
32 38 * All available payment gateways.
33 39 *
34 - * @var array
40 + * @since 1.0.0
41 + *
42 + * @var array
35 43 */
36 44 private $gateways;
37 45
38 46 /**
@@ -41,26 +49,53 @@
41 49 * Note that the only way to instantiate an object is with the charitable_start method,
42 50 * which can only be called during the start phase. In other words, don't try
43 51 * to instantiate this object.
44 52 *
45 - * @since 1.0.0
53 + * @since 1.0.0
46 54 */
47 55 protected function __construct() {
48 - add_action( 'init', array( $this, 'register_gateways' ) );
56 + add_action( 'init', array( $this, 'check_settings' ), 10 );
57 + add_action( 'init', array( $this, 'register_gateways' ), 1 );
58 + add_action( 'init', array( $this, 'connect_gateways' ), 99 ); // Stripe.
59 + add_action( 'init', array( $this, 'disconnect_gateways' ), 99 ); // Stripe.
60 +
49 61 add_action( 'charitable_make_default_gateway', array( $this, 'handle_gateway_settings_request' ) );
50 62 add_action( 'charitable_enable_gateway', array( $this, 'handle_gateway_settings_request' ) );
51 63 add_action( 'charitable_disable_gateway', array( $this, 'handle_gateway_settings_request' ) );
52 64 add_filter( 'charitable_settings_fields_gateways_gateway', array( $this, 'register_gateway_settings' ), 10, 2 );
53 65
66 + add_action( 'charitable_disable_gateway', array( $this, 'handle_gateway_settings_request' ) );
67 +
68 + /**
69 + * Square Connect
70 + */
71 + add_action( 'init', array( $this, 'disconnect_gateways_square' ), 99 ); // Square.
72 + add_action( 'init', array( $this, 'refresh_square_token' ), 99 ); // Square.
73 +
74 + add_action( 'init', array( $this, 'get_gateway_error_message' ), 99 );
75 +
54 76 do_action( 'charitable_gateway_start', $this );
77 +
78 + add_action( 'wp_ajax_charitable-show-stripe-keys', array( $this, 'show_stripe_manual_keys_ajax' ), 10 );
79 + add_action( 'wp_ajax_charitable_switch_square_gateways', array( $this, 'handle_square_gateway_switch' ) );
80 + add_action( 'wp_ajax_charitable_switch_square_core_to_legacy', array( $this, 'handle_square_core_to_legacy_switch' ) );
81 +
82 + // Dequeue Square Legacy scripts if Square Legacy is not active.
83 + add_action( 'wp_enqueue_scripts', array( $this, 'maybe_dequeue_square_legacy_scripts' ), 999 );
84 +
85 + // PayPal Hard Fork (1.8.11): last-word defense against third-party
86 + // callbacks that re-inject the hidden PayPal gateway. Mirrors the
87 + // register_gateways + get_active_gateways strips.
88 + add_filter( 'charitable_active_gateways', array( $this, 'apply_paypal_tier_visibility' ), PHP_INT_MAX );
55 89 }
56 90
91 +
57 92 /**
58 93 * Returns and/or create the single instance of this class.
59 94 *
60 - * @since 1.2.0
95 + * @since 1.2.0
61 96 *
62 - * @return Charitable_Gateways
97 + * @return Charitable_Gateways
63 98 */
64 99 public static function get_instance() {
65 100 if ( is_null( self::$instance ) ) {
66 101 self::$instance = new self();
@@ -74,55 +109,146 @@
74 109 *
75 110 * To register a new gateway, you need to hook into the `charitable_payment_gateways`
76 111 * hook and give Charitable the name of your gateway class.
77 112 *
78 - * @since 1.2.0
113 + * @since 1.2.0
79 114 *
80 - * @return void
115 + * @return void
81 116 */
82 117 public function register_gateways() {
83 - $this->gateways = apply_filters( 'charitable_payment_gateways', array(
84 - 'offline' => 'Charitable_Gateway_Offline',
85 - 'paypal' => 'Charitable_Gateway_Paypal',
86 - ) );
118 + /**
119 + * Filter the list of payment gateways.
120 + *
121 + * @since 1.2.0
122 + * @version 1.8.7
123 + *
124 + * @param array $gateways The list of gateways in gateway ID => gateway class format.
125 + */
126 +
127 + $gateways = array(
128 + 'stripe' => 'Charitable_Gateway_Stripe_AM',
129 + 'paypal' => 'Charitable_Gateway_Paypal',
130 + 'paypal_commerce' => 'Charitable_Gateway_Paypal_Commerce',
131 + 'offline' => 'Charitable_Gateway_Offline',
132 + );
133 +
134 + // Always add Square Core to the available gateways.
135 + $gateways['square_core'] = 'Charitable_Gateway_Square';
136 +
137 + $this->gateways = apply_filters(
138 + 'charitable_payment_gateways',
139 + $gateways
140 + );
141 +
142 + // PayPal Hard Fork (1.8.11): hide the off-tier PayPal gateway entirely so it
143 + // cannot be enabled, listed in admin, or referenced in active_gateways.
144 + $this->gateways = $this->apply_paypal_tier_visibility( $this->gateways );
145 +
146 + // If Legacy mode is active and Square Legacy gateway is active, deactivate Square Core.
147 + if ( charitable_square_legacy_mode() && $this->is_active_gateway( 'square' ) ) {
148 + $this->disable_gateway( 'square_core' );
149 +
150 + if ( charitable_is_debug( 'square' ) ) {
151 + error_log( '[Charitable Square Core] Deactivated Square Core because Square Legacy is active' ); // phpcs:ignore
152 + }
153 + }
87 154 }
88 155
89 156 /**
157 + * Read the locked PayPal user tier set by the 1.8.11 detection upgrade.
158 + *
159 + * The result is escapable at runtime via the standard `pre_option_*` filter
160 + * (`pre_option_charitable_paypal_user_tier`) since this is a vanilla `get_option`
161 + * read. See `Charitable_Upgrade::detect_paypal_user_tier()` for how the value
162 + * is computed and persisted.
163 + *
164 + * @since 1.8.11
165 + *
166 + * @return string `legacy` or `commerce`. Defaults to `commerce` when unset.
167 + */
168 + public function get_paypal_tier() {
169 + $tier = get_option( 'charitable_paypal_user_tier', 'commerce' );
170 + return ( 'legacy' === $tier ) ? 'legacy' : 'commerce';
171 + }
172 +
173 + /**
174 + * Strip the off-tier PayPal gateway from a gateways map.
175 + *
176 + * `legacy` tier hides `paypal_commerce`. `commerce` tier hides `paypal`.
177 + * Used in three places to layer defense:
178 + * 1. `register_gateways()` — strips before `$this->gateways` is exposed.
179 + * 2. `get_active_gateways()` — inline cleanup mirroring the Square pattern.
180 + * 3. `charitable_active_gateways` filter (PHP_INT_MAX priority) — last word
181 + * against third-party callbacks that re-inject the hidden gateway.
182 + *
183 + * @since 1.8.11
184 + *
185 + * @param array $gateways Map of gateway_id => class_name (or anything keyed by id).
186 + * @return array Same shape, with the off-tier PayPal key removed when present.
187 + */
188 + public function apply_paypal_tier_visibility( $gateways ) {
189 + if ( ! is_array( $gateways ) ) {
190 + return $gateways;
191 + }
192 +
193 + if ( 'legacy' === $this->get_paypal_tier() ) {
194 + unset( $gateways['paypal_commerce'] );
195 + } else {
196 + unset( $gateways['paypal'] );
197 + }
198 +
199 + return $gateways;
200 + }
201 +
202 + /**
90 203 * Receives a request to enable or disable a payment gateway and validates it before passing it off.
91 204 *
92 - * @since 1.0.0
205 + * @since 1.0.0
93 206 *
94 - * @return void
207 + * @return void
95 208 */
96 209 public function handle_gateway_settings_request() {
97 - if ( ! wp_verify_nonce( $_REQUEST['_nonce'], 'gateway' ) ) {
98 - wp_die( __( 'Cheatin\' eh?!', 'charitable' ) );
210 + // phpcs:disable WordPress.Security.NonceVerification.Missing
211 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
212 + if ( ! isset( $_REQUEST['_nonce'] ) || ! wp_verify_nonce( wp_unslash( $_REQUEST['_nonce'] ), 'gateway' ) ) {
213 + wp_die( __( 'Cheatin\' eh?!', 'charitable' ) ); // phpcs:ignore
99 214 }
100 215
101 - $gateway = isset( $_REQUEST['gateway_id'] ) ? $_REQUEST['gateway_id'] : false;
216 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Gateway ID, sanitized below
217 + $gateway = isset( $_REQUEST['gateway_id'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['gateway_id'] ) ) : false;
218 + // phpcs:enable WordPress.Security.NonceVerification.Missing
102 219
103 220 /* Gateway must be set */
104 221 if ( false === $gateway ) {
105 - wp_die( __( 'Missing gateway.', 'charitable' ) );
222 + wp_die( __( 'Missing gateway.', 'charitable' ) ); // phpcs:ignore
106 223 }
107 224
108 225 /* Validate gateway. */
109 226 if ( ! isset( $this->gateways[ $gateway ] ) ) {
110 - wp_die( __( 'Invalid gateway.', 'charitable' ) );
227 + wp_die( __( 'Invalid gateway.', 'charitable' ) ); // phpcs:ignore
111 228 }
112 229
113 - switch ( $_REQUEST['charitable_action'] ) {
114 - case 'disable_gateway' :
230 + switch ( $_REQUEST['charitable_action'] ) { // phpcs:ignore
231 + case 'disable_gateway':
115 232 $this->disable_gateway( $gateway );
116 233 break;
117 - case 'enable_gateway' :
234 + case 'enable_gateway':
118 235 $this->enable_gateway( $gateway );
236 + $this->redirect_gateway_settings( $gateway );
119 237 break;
120 - case 'make_default_gateway' :
238 + case 'make_default_gateway':
121 239 $this->set_default_gateway( $gateway );
122 240 break;
123 - default :
124 - do_action( 'charitable_gateway_settings_request', $_REQUEST['charitable_action'], $gateway );
241 + default:
242 + /**
243 + * Do something when a gateway settings request takes place.
244 + *
245 + * @since 1.0.0
246 + *
247 + * @param string $action The action taking place.
248 + * @param string $gateway_id The gateway ID.
249 + */
250 + do_action( 'charitable_gateway_settings_request', $_REQUEST['charitable_action'], $gateway ); // phpcs:ignore
125 251 }
126 252 }
127 253
128 254 /**
@@ -127,13 +253,16 @@
127 253
128 254 /**
129 255 * Returns all available payment gateways.
130 256 *
131 - * @since 1.0.0
257 + * @since 1.0.0
132 258 *
133 - * @return string
259 + * @return array
134 260 */
135 261 public function get_available_gateways() {
262 + if ( isset( $this->gateways['stripe'] ) ) {
263 + $this->gateways['stripe'] = charitable()->is_stripe_connect_addon() ? 'Charitable_Gateway_Stripe_AM' : $this->gateways['stripe'];
264 + }
136 265 return $this->gateways;
137 266 }
138 267
139 268 /**
@@ -138,15 +267,18 @@
138 267
139 268 /**
140 269 * Returns the current active gateways.
141 270 *
142 - * @since 1.0.0
271 + * @since 1.0.0
143 272 *
144 - * @return string[]
273 + * @return string[]
145 274 */
146 275 public function get_active_gateways() {
147 276 $active_gateways = charitable_get_option( 'active_gateways', array() );
148 277
278 + // Force cleanup of 'square' gateway if charitable-square plugin is not active (runs once per 24 hours).
279 + $this->maybe_force_cleanup_square_gateway( $active_gateways );
280 +
149 281 foreach ( $active_gateways as $gateway_id => $gateway_class ) {
150 282 if ( ! class_exists( $gateway_class ) ) {
151 283 unset( $active_gateways[ $gateway_id ] );
152 284 }
@@ -151,10 +283,31 @@
151 283 unset( $active_gateways[ $gateway_id ] );
152 284 }
153 285 }
154 286
287 + // Remove 'square' gateway if charitable-square plugin is not active.
288 + if ( isset( $active_gateways['square'] ) && ! $this->is_square_addon_active() ) {
289 + unset( $active_gateways['square'] );
290 + }
291 +
292 + // PayPal Hard Fork (1.8.11): strip the off-tier PayPal gateway in case both
293 + // were saved into active_gateways (direct DB edit, multisite weirdness, etc.).
294 + $active_gateways = $this->apply_paypal_tier_visibility( $active_gateways );
295 +
155 296 uksort( $active_gateways, array( $this, 'sort_by_default' ) );
156 297
298 + // force the strip active gateway normally for stripe addon to point to the stripe built into core.
299 + if ( isset( $active_gateways['stripe'] ) ) {
300 + $active_gateways['stripe'] = 'Charitable_Gateway_Stripe_AM';
301 + }
302 +
303 + /**
304 + * Filter the list of active gateways.
305 + *
306 + * @since 1.0.0
307 + *
308 + * @param array $gateways Active gateways.
309 + */
157 310 return apply_filters( 'charitable_active_gateways', $active_gateways );
158 311 }
159 312
160 313 /**
@@ -161,17 +314,17 @@
161 314 * Returns an array of the active gateways, in ID => name format.
162 315 *
163 316 * This is useful for select/radio input fields.
164 317 *
165 - * @since 1.0.0
318 + * @since 1.0.0
166 319 *
167 - * @return string[]
320 + * @return string[]
168 321 */
169 322 public function get_gateway_choices() {
170 323 $gateways = array();
171 324
172 325 foreach ( $this->get_active_gateways() as $id => $class ) {
173 - $gateway = new $class;
326 + $gateway = new $class();
174 327 $gateways[ $id ] = $gateway->get_label();
175 328 }
176 329
177 330 return $gateways;
@@ -179,17 +332,17 @@
179 332
180 333 /**
181 334 * Returns a text description of the active gateways.
182 335 *
183 - * @since 1.3.0
336 + * @since 1.3.0
184 337 *
185 - * @return string[]
338 + * @return string[]
186 339 */
187 340 public function get_active_gateways_names() {
188 341 $gateways = array();
189 342
190 343 foreach ( $this->get_active_gateways() as $id => $class ) {
191 - $gateway = new $class;
344 + $gateway = new $class();
192 345 $gateways[] = $gateway->get_name();
193 346 }
194 347
195 348 return $gateways;
@@ -197,12 +350,12 @@
197 350
198 351 /**
199 352 * Return the gateway class name for a given gateway.
200 353 *
201 - * @since 1.0.0
354 + * @since 1.0.0
202 355 *
203 - * @param string $gateway Gateway ID.
204 - * @return string|false
356 + * @param string $gateway Gateway ID.
357 + * @return string|false
205 358 */
206 359 public function get_gateway( $gateway ) {
207 360 return isset( $this->gateways[ $gateway ] ) ? $this->gateways[ $gateway ] : false;
208 361 }
@@ -209,25 +362,34 @@
209 362
210 363 /**
211 364 * Return the gateway object for a given gateway.
212 365 *
213 - * @since 1.0.0
366 + * @since 1.0.0
214 367 *
215 - * @param string $gateway Gateway ID.
216 - * @return Charitable_Gateway|null
368 + * @param string $gateway Gateway ID.
369 + * @return Charitable_Gateway|null
217 370 */
218 371 public function get_gateway_object( $gateway ) {
219 - $class = $this->get_gateway( $gateway );
220 - return $class ? new $class : null;
372 + $class = $this->get_gateway( $gateway );
373 + $object = $class ? new $class() : null;
374 +
375 + /**
376 + * Filter the gateway object.
377 + *
378 + * @since 1.6.30
379 + *
380 + * @param Charitable_Gateway|null $object The gateway object.
381 + */
382 + return apply_filters( 'charitable_gateway_object_' . $gateway, $object, $gateway );
221 383 }
222 384
223 385 /**
224 386 * Returns whether the passed gateway is active.
225 387 *
226 - * @since 1.0.0
388 + * @since 1.0.0
227 389 *
228 - * @param string $gateway_id Gateway ID.
229 - * @return boolean
390 + * @param string $gateway_id Gateway ID.
391 + * @return boolean
230 392 */
231 393 public function is_active_gateway( $gateway_id ) {
232 394 return array_key_exists( $gateway_id, $this->get_active_gateways() );
233 395 }
@@ -234,14 +396,31 @@
234 396
235 397 /**
236 398 * Checks whether the submitted gateway is valid.
237 399 *
238 - * @since 1.4.3
400 + * @since 1.4.3
239 401 *
240 - * @param string $gateway Gateway ID.
241 - * @return boolean
402 + * @param string $gateway Gateway ID.
403 + * @return boolean
242 404 */
243 405 public function is_valid_gateway( $gateway ) {
406 + /**
407 + * Validate a particular gatewya.
408 + *
409 + * @since 1.4.3
410 + *
411 + * @param boolean $valid Whether a gateway is valid.
412 + * @param string $gateway The gateway ID.
413 + */
414 +
415 + if ( charitable_is_debug( 'webhook' ) ) {
416 + // phpcs:disable
417 + error_log( 'is_valid_gateway' );
418 + error_log( print_r( $gateway, true ) );
419 + error_log( print_r( $this->gateways, true ) );
420 + // phpcs:enable
421 + }
422 +
244 423 return apply_filters( 'charitable_is_valid_gateway', array_key_exists( $gateway, $this->gateways ), $gateway );
245 424 }
246 425
247 426 /**
@@ -246,11 +425,11 @@
246 425
247 426 /**
248 427 * Returns the default gateway.
249 428 *
250 - * @since 1.0.0
429 + * @since 1.0.0
251 430 *
252 - * @return string
431 + * @return string
253 432 */
254 433 public function get_default_gateway() {
255 434 return charitable_get_option( 'default_gateway', '' );
256 435 }
@@ -257,17 +436,25 @@
257 436
258 437 /**
259 438 * Provide default gateway settings fields.
260 439 *
261 - * @since 1.0.0
440 + * @since 1.0.0
262 441 *
263 - * @param array $settings Gateway settings.
264 - * @param Charitable_Gateway $gateway The gateway's helper object.
265 - * @return array
442 + * @param array $settings Gateway settings.
443 + * @param Charitable_Gateway $gateway The gateway's helper object.
444 + * @return array
266 445 */
267 446 public function register_gateway_settings( $settings, Charitable_Gateway $gateway ) {
268 447 add_filter( 'charitable_settings_fields_gateways_gateway_' . $gateway->get_gateway_id(), array( $gateway, 'default_gateway_settings' ), 5 );
269 448 add_filter( 'charitable_settings_fields_gateways_gateway_' . $gateway->get_gateway_id(), array( $gateway, 'gateway_settings' ), 15 );
449 +
450 + /**
451 + * Filter the settings to show for a particular gateway.
452 + *
453 + * @since 1.0.0
454 + *
455 + * @param array $settings Gateway settings.
456 + */
270 457 return apply_filters( 'charitable_settings_fields_gateways_gateway_' . $gateway->get_gateway_id(), $settings );
271 458 }
272 459
273 460 /**
@@ -272,14 +459,22 @@
272 459
273 460 /**
274 461 * Returns true if test mode is enabled.
275 462 *
276 - * @since 1.0.0
463 + * @since 1.0.0
277 464 *
278 - * @return boolean
465 + * @return boolean
279 466 */
280 467 public function in_test_mode() {
281 468 $enabled = charitable_get_option( 'test_mode', false );
469 +
470 + /**
471 + * Return whether Charitable is in test mode.
472 + *
473 + * @since 1.0.0
474 + *
475 + * @param boolean $enabled Whether test mode is on.
476 + */
282 477 return apply_filters( 'charitable_in_test_mode', $enabled );
283 478 }
284 479
285 480 /**
@@ -286,17 +481,17 @@
286 481 * Checks whether all of the active gateways support a feature.
287 482 *
288 483 * If ANY gateway doesn't support the feature, this returns false.
289 484 *
290 - * @since 1.4.0
485 + * @since 1.4.0
291 486 *
292 - * @param string $feature Feature to search for.
293 - * @return boolean
487 + * @param string $feature Feature to search for.
488 + * @return boolean
294 489 */
295 490 public function all_gateways_support( $feature ) {
296 491 foreach ( $this->get_active_gateways() as $gateway_id => $gateway_class ) {
297 492
298 - $gateway_object = new $gateway_class;
493 + $gateway_object = new $gateway_class();
299 494
300 495 if ( false === $gateway_object->supports( $feature ) ) {
301 496 return false;
302 497 }
@@ -309,17 +504,17 @@
309 504 * Checks whether any of the active gateways support a feature.
310 505 *
311 506 * If any gateway supports the feature, this returns true. Otherwise false.
312 507 *
313 - * @since 1.4.0
508 + * @since 1.4.0
314 509 *
315 - * @param string $feature Feature to check for.
316 - * @return boolean
510 + * @param string $feature Feature to check for.
511 + * @return boolean
317 512 */
318 513 public function any_gateway_supports( $feature ) {
319 514 foreach ( $this->get_active_gateways() as $gateway_id => $gateway_class ) {
320 515
321 - $gateway_object = new $gateway_class;
516 + $gateway_object = new $gateway_class();
322 517
323 518 if ( true === $gateway_object->supports( $feature ) ) {
324 519 return true;
325 520 }
@@ -332,11 +527,11 @@
332 527 * Checks whether all of the active gateways support AJAX.
333 528 *
334 529 * If ANY gateway doesn't support AJAX, this returns false.
335 530 *
336 - * @since 1.3.0
531 + * @since 1.3.0
337 532 *
338 - * @return boolean
533 + * @return boolean
339 534 */
340 535 public function gateways_support_ajax() {
341 536 return $this->all_gateways_support( '1.3.0' );
342 537 }
@@ -355,11 +550,16 @@
355 550 public function get_recommended_gateways() {
356 551 $available = $this->get_available_gateways();
357 552 $gateways = array(
358 553 'payfast' => __( 'Payfast', 'charitable' ),
359 - 'payumoney' => __( 'PayUMoney', 'charitable' ),
360 - 'stripe' => __( 'Stripe', 'charitable' ),
554 + 'paystack' => __( 'Paystack', 'charitable' ),
555 + 'stripe' => __( 'Stripe', 'charitable' ),
361 556 'authorize_net' => __( 'Authorize.Net', 'charitable' ),
557 + 'windcave' => __( 'Windcave', 'charitable' ),
558 + 'braintree' => __( 'Braintree', 'charitable' ),
559 + 'mollie' => __( 'Mollie', 'charitable' ),
560 + 'gocardless' => __( 'GoCardless', 'charitable' ),
561 + 'payrexx' => __( 'Payrexx', 'charitable' ),
362 562 );
363 563
364 564 /* If the user has already enabled one of these, leave them alone. :) */
365 565 foreach ( $gateways as $gateway_id => $gateway ) {
@@ -367,32 +567,53 @@
367 567 return array();
368 568 }
369 569 }
370 570
371 - $currency = charitable_get_option( 'currency', 'AUD' );
571 + $currency = charitable_get_default_currency();
372 572 $locale = get_locale();
373 573
374 574 if ( 'en_ZA' == $locale || 'ZAR' == $currency ) {
375 - return charitable_array_subset( $gateways, array( 'payfast' ) );
575 + return charitable_array_subset( $gateways, array( 'payfast', 'paystack' ) );
376 576 }
377 577
378 - if ( 'hi_IN' == $locale || 'INR' == $currency ) {
379 - return charitable_array_subset( $gateways, array( 'payumoney' ) );
578 + if ( in_array( $currency, array( 'NGN', 'GHS' ) ) ) {
579 + return charitable_array_subset( $gateways, array( 'paystack' ) );
380 580 }
381 581
382 - return charitable_array_subset( $gateways, array( 'stripe', 'authorize_net' ) );
582 + if ( in_array( $locale, array( 'en_NZ', 'en_AU', 'en_GB' ) ) || in_array( $currency, array( 'NZD', 'AUD', 'GBP' ) ) ) {
583 + return charitable_array_subset( $gateways, array( 'stripe', 'gocardless' ) );
584 + }
585 +
586 + if ( in_array( $locale, array( 'ms_MY', 'ja', 'zh_HK' ) ) || in_array( $currency, array( 'MYR', 'JPY', 'HKD' ) ) ) {
587 + return charitable_array_subset( $gateways, array( 'stripe', 'windcave' ) );
588 + }
589 +
590 + if ( in_array( $locale, array( 'th' ) ) || in_array( $currency, array( 'BND', 'FJD', 'KWD', 'PGK', 'SBD', 'THB', 'TOP', 'VUV', 'WST' ) ) ) {
591 + return charitable_array_subset( $gateways, array( 'windcave' ) );
592 + }
593 +
594 + if ( in_array( $currency, array( 'EUR' ) ) ) {
595 + return charitable_array_subset( $gateways, array( 'stripe', 'mollie' ) );
596 + }
597 +
598 + if ( in_array( $currency, array( 'CHF', 'DKK' ) ) ) {
599 + return charitable_array_subset( $gateways, array( 'stripe', 'payrexx' ) );
600 + }
601 +
602 + return charitable_array_subset( $gateways, array( 'stripe', 'braintree' ) );
383 603 }
384 604
385 605 /**
386 606 * Sets the default gateway.
387 607 *
388 - * @since 1.0.0
608 + * @since 1.0.0
389 609 *
390 - * @param string $gateway Gateway ID.
391 - * @return void
610 + * @param string $gateway Gateway ID.
611 + * @return void
392 612 */
393 613 protected function set_default_gateway( $gateway ) {
394 614 $settings = get_option( 'charitable_settings' );
615 +
395 616 $settings['default_gateway'] = $gateway;
396 617
397 618 update_option( 'charitable_settings', $settings );
398 619
@@ -403,18 +624,29 @@
403 624
404 625 /**
405 626 * Enable a payment gateway.
406 627 *
407 - * @since 1.0.0
628 + * @since 1.0.0
408 629 *
409 - * @param string $gateway Gateway ID.
410 - * @return void
630 + * @param string $gateway Gateway ID.
631 + * @param bool $show_update_message Whether to show the update message.
632 + * @return void
411 633 */
412 - protected function enable_gateway( $gateway ) {
634 + protected function enable_gateway( $gateway, $show_update_message = true ) {
413 635 $settings = get_option( 'charitable_settings' );
414 636
415 - $active_gateways = isset( $settings['active_gateways'] ) ? $settings['active_gateways'] : array();
637 + if ( false === $settings || ! is_array( $settings ) ) {
638 + echo 'settings is false';
639 + $settings = array();
640 + }
641 +
642 + $active_gateways = isset( $settings['active_gateways'] ) ? $settings['active_gateways'] : array();
416 643 $active_gateways[ $gateway ] = $this->gateways[ $gateway ];
644 +
645 + if ( ! isset( $settings['active_gateways'] ) ) {
646 + $settings['active_gateways'] = array();
647 + }
648 +
417 649 $settings['active_gateways'] = $active_gateways;
418 650
419 651 /* If this is the only gateway, make it the default gateway */
420 652 if ( 1 == count( $settings['active_gateways'] ) ) {
@@ -422,10 +654,19 @@
422 654 }
423 655
424 656 update_option( 'charitable_settings', $settings );
425 657
426 - Charitable_Settings::get_instance()->add_update_message( __( 'Gateway enabled', 'charitable' ), 'success' );
658 + if ( $show_update_message ) {
659 + Charitable_Settings::get_instance()->add_update_message( __( 'Gateway enabled', 'charitable' ), 'success' );
660 + }
427 661
662 + /**
663 + * Do something when a payment gateway is enabled.
664 + *
665 + * @since 1.0.0
666 + *
667 + * @param string $gateway The payment gateway.
668 + */
428 669 do_action( 'charitable_gateway_enable', $gateway );
429 670 }
430 671
431 672 /**
@@ -430,12 +671,12 @@
430 671
431 672 /**
432 673 * Disable a payment gateway.
433 674 *
434 - * @since 1.0.0
675 + * @since 1.0.0
435 676 *
436 - * @param string $gateway Gateway ID.
437 - * @return void
677 + * @param string $gateway Gateway ID.
678 + * @return void
438 679 */
439 680 protected function disable_gateway( $gateway ) {
440 681 $settings = get_option( 'charitable_settings' );
441 682
@@ -455,32 +696,1314 @@
455 696 update_option( 'charitable_settings', $settings );
456 697
457 698 Charitable_Settings::get_instance()->add_update_message( __( 'Gateway disabled', 'charitable' ), 'success' );
458 699
700 + /**
701 + * Do something when a payment gateway is disabled.
702 + *
703 + * @since 1.0.0
704 + *
705 + * @param string $gateway The payment gateway.
706 + */
459 707 do_action( 'charitable_gateway_disable', $gateway );
460 708 }
461 709
462 710 /**
711 + * Connects to Stripe by saving account information passed back to the plugin.
712 + *
713 + * @since 4.2.2
714 + * @version 1.8.9
715 + *
716 + * @return void
717 + */
718 + public function connect_gateways() {
719 + // Current user cannot handle this request.
720 + if ( ! current_user_can( 'manage_options' ) ) {
721 + return;
722 + }
723 +
724 + // Do not need to handle this request, bail.
725 + if (
726 + ! isset( $_GET['wpcharitable_gateway_connect_completion'] ) || // phpcs:ignore
727 + 'stripe_connect' !== $_GET['wpcharitable_gateway_connect_completion'] || // phpcs:ignore
728 + ! isset( $_GET['state'] ) // phpcs:ignore
729 + ) {
730 + return;
731 + }
732 +
733 + // Unable to redirect, bail.
734 + if ( headers_sent() ) {
735 + return;
736 + }
737 +
738 + if ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Server variables for URL construction
739 + $current_url = ( is_ssl() ? 'https' : 'http' ) . '://' . wp_unslash( $_SERVER['HTTP_HOST'] ) . wp_unslash( $_SERVER['REQUEST_URI'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Server variables are unslashed and then sanitized via esc_url_raw()
740 + $current_url = esc_url_raw( $current_url ); // phpcs:ignore
741 + } else {
742 + $current_url = '';
743 + }
744 +
745 + $customer_site_url = remove_query_arg(
746 + array(
747 + 'state',
748 + 'wpcharitable_gateway_connect_completion',
749 + ),
750 + $current_url
751 + );
752 +
753 + $wpcharitable_credentials_url = add_query_arg(
754 + array(
755 + 'live_mode' => (int) ! charitable_get_option( 'test_mode' ),
756 + 'state' => sanitize_text_field( $_GET['state'] ), // phpcs:ignore
757 + 'customer_site_url' => urlencode( $customer_site_url ),
758 + ),
759 + 'https://wpcharitable.com/stripe-connect/?wpcharitable_gateway_connect_credentials=stripe_connect'
760 + );
761 +
762 + // You might need to add add_filter('https_ssl_verify', '__return_false'); here if testing locally.
763 + $response = wp_remote_get( esc_url_raw( $wpcharitable_credentials_url ) );
764 +
765 + if (
766 + is_wp_error( $response ) ||
767 + 200 !== wp_remote_retrieve_response_code( $response )
768 + ) {
769 +
770 + // Log error details for debugging.
771 + if ( is_wp_error( $response ) ) {
772 + if ( charitable_is_debug( 'stripe' ) ) {
773 + error_log( 'Charitable Stripe Connect Error: ' . $response->get_error_message() . ' | URL: ' . $wpcharitable_credentials_url ); // phpcs:ignore
774 + }
775 + } else {
776 + $response_code = wp_remote_retrieve_response_code( $response );
777 + $response_body = wp_remote_retrieve_body( $response );
778 + if ( charitable_is_debug( 'stripe' ) ) {
779 + error_log( 'Charitable Stripe Connect Error: HTTP ' . $response_code . ' | URL: ' . $wpcharitable_credentials_url . ' | Response: ' . substr( $response_body, 0, 500 ) ); // phpcs:ignore
780 + }
781 + }
782 +
783 + $stripe_account_settings_url = add_query_arg(
784 + array(
785 + 'tab' => 'gateways',
786 + 'page' => 'charitable-settings',
787 + 'group' => 'gateways_stripe',
788 + ),
789 + admin_url( 'admin.php' )
790 + );
791 +
792 + $message = wpautop(
793 + sprintf(
794 + /* translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. */
795 + __(
796 + 'There was an error getting your Stripe credentials. Please %1$stry again%2$s. If you continue to have this problem, please contact support.',
797 + 'charitable'
798 + ),
799 + '<a href="' . esc_url( $stripe_account_settings_url ) . '">',
800 + '</a>'
801 + )
802 + );
803 +
804 + wp_die( $message ); // phpcs:ignore
805 + }
806 +
807 + $body = wp_remote_retrieve_body( $response );
808 +
809 + /** @var string $body */
810 + $body = json_decode( $body, true );
811 +
812 + /** @var array<array<string>> $body */
813 + $account_data = $body['data'];
814 +
815 + $this->save_account_information( $account_data );
816 +
817 + // update the option to track that we are using stripe connect.
818 + update_option( 'charitable_using_stripe_connect', time() );
819 +
820 + /**
821 + * Allow further processing after connecting a Stripe account.
822 + *
823 + * @since 3.6.0
824 + *
825 + * @param array $data Stripe response data.
826 + */
827 + do_action( 'wpcharitable_stripe_account_connected', $account_data ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- This is a legacy hook used by other Charitable code. Changing it would break existing functionality.
828 +
829 + wp_safe_redirect( esc_url_raw( $customer_site_url ) );
830 + exit;
831 + }
832 +
833 + /**
834 + * Refreshes the Square token.
835 + *
836 + * @since 1.8.7
837 + *
838 + * @return void
839 + */
840 + public function refresh_square_token() {
841 + // Current user cannot handle this request.
842 + if ( ! current_user_can( 'manage_options' ) || charitable_square_legacy_mode() ) {
843 + return;
844 + }
845 +
846 + // Do not need to handle this request, bail.
847 + if (
848 + ! isset( $_GET['wpcharitable_gateway_refresh_token_completion'] ) || // phpcs:ignore
849 + 'square_connect' !== $_GET['wpcharitable_gateway_refresh_token_completion'] || // phpcs:ignore
850 + ! isset( $_GET['state'] ) // phpcs:ignore
851 + ) {
852 + return;
853 + }
854 +
855 + // Unable to redirect, bail.
856 + if ( headers_sent() ) {
857 + return;
858 + }
859 +
860 + if ( charitable_is_debug( 'square' ) ) {
861 + error_log( 'checking for use: refresh_square_token' ); // phpcs:ignore
862 + }
863 +
864 + if ( isset( $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'] ) ) {
865 + $current_url = ( is_ssl() ? 'https' : 'http' ) . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; // phpcs:ignore
866 + } else {
867 + $current_url = '';
868 + }
869 +
870 + $customer_site_url = remove_query_arg(
871 + array(
872 + 'state',
873 + 'wpcharitable_gateway_refresh_token_completion',
874 + 'message',
875 + ),
876 + $current_url
877 + );
878 +
879 + $wpcharitable_credentials_url = add_query_arg(
880 + array(
881 + 'live_mode' => (int) ! charitable_get_option( 'test_mode' ),
882 + 'state' => sanitize_text_field( $_GET['state'] ), // phpcs:ignore
883 + 'code' => 'request_credentials',
884 + 'customer_site_url' => urlencode( $customer_site_url ),
885 + ),
886 + 'https://connect.wpcharitable.com/square-connect/'
887 + );
888 +
889 + $response = wp_remote_get( esc_url_raw( $wpcharitable_credentials_url ) );
890 +
891 + if (
892 + is_wp_error( $response ) ||
893 + 200 !== wp_remote_retrieve_response_code( $response )
894 + ) {
895 +
896 + $square_account_settings_url = add_query_arg(
897 + array(
898 + 'tab' => 'gateways',
899 + 'page' => 'charitable-settings',
900 + 'group' => 'gateways_square_core',
901 + ),
902 + admin_url( 'admin.php' )
903 + );
904 +
905 + $message = wpautop(
906 + sprintf(
907 + /* translators: %1$s Opening anchor tag, do not translate. %2$s Closing anchor tag, do not translate. */
908 + __(
909 + 'There was an error getting your Square credentials. Please %1$stry again%2$s. If you continue to have this problem, please contact support.',
910 + 'charitable'
911 + ),
912 + '<a href="' . esc_url( $square_account_settings_url ) . '">',
913 + '</a>'
914 + )
915 + );
916 +
917 + wp_die( $message ); // phpcs:ignore
918 + }
919 +
920 + $body = wp_remote_retrieve_body( $response );
921 +
922 + $body = json_decode( $body, true );
923 +
924 + $account_data = $body['data'];
925 +
926 + $this->save_account_information_square( $account_data );
927 +
928 + // Don't show the success message if we are on the welcome / getting started page (otherwise it's fine).
929 + if ( ! isset( $_GET['page'] ) || 'charitable-getting-started' !== $_GET['page'] ) { // phpcs:ignore
930 + // translators: %s is the mode of the connection.
931 + charitable_get_admin_notices()->add_success( sprintf( __( 'You have reconnected and updated your access token for Square in %s mode.', 'charitable' ), charitable_get_option( 'test_mode' ) ? 'test' : 'live' ) );
932 + }
933 +
934 + // update the option to track that we are using square connect.
935 + // Get the value and update it.
936 + $charitable_using_square_connect = get_option( 'charitable_using_square_connect', array() );
937 + if ( ! is_array( $charitable_using_square_connect ) ) {
938 + $charitable_using_square_connect = array();
939 + }
940 + $charitable_using_square_connect['test'] = time();
941 + update_option( 'charitable_using_square_connect', $charitable_using_square_connect );
942 +
943 + /**
944 + * Allow further processing after reconnecting a Square account.
945 + *
946 + * @since 3.6.0
947 + *
948 + * @param array $data Square response data.
949 + */
950 + do_action( 'wpcharitable_square_token_refreshed', $account_data ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- This is a legacy hook used by other Charitable code. Changing it would break existing functionality.
951 +
952 + wp_safe_redirect( esc_url_raw( $customer_site_url ) );
953 + exit;
954 + }
955 +
956 + /**
957 + * Schedule the Square token renewal cron job.
958 + *
959 + * @since 1.8.7
960 + *
961 + * @param string $mode The mode to schedule the cron for (test/live).
962 + * @return void
963 + */
964 + private function schedule_square_token_renewal_cron( $mode ) {
965 + // Clear any existing cron jobs for this mode.
966 + wp_clear_scheduled_hook( 'charitable_square_token_renewal', array( $mode ) );
967 +
968 + // Schedule the cron job to run every 14 days.
969 + wp_schedule_event( time() + ( 14 * DAY_IN_SECONDS ), '14days', 'charitable_square_token_renewal', array( $mode ) );
970 + }
971 +
972 + /**
973 + * Renew the Square token via cron job.
974 + *
975 + * @since 1.8.7
976 + *
977 + * @param string $mode The mode to renew the token for (test/live).
978 + * @return void
979 + */
980 + public function renew_square_token_cron( $mode ) {
981 + if ( charitable_is_debug( 'square' ) ) {
982 + // phpcs:disable
983 + error_log( 'renew_square_token_cron for test mode: ' . $mode );
984 + // phpcs:enable
985 + }
986 +
987 + // Get the URL to renew the token.
988 + $url = Charitable_Gateway_Square::get_instance()->get_square_renew_token_url( '', true );
989 +
990 + if ( charitable_is_debug( 'square' ) ) {
991 + // phpcs:disable
992 + error_log( 'url: ' . $url );
993 + // phpcs:enable
994 + }
995 +
996 + // Clear both failed and success options.
997 + delete_option( 'wpcharitable_square_failed_token_renewal_' . $mode );
998 + delete_option( 'wpcharitable_square_token_refreshed_' . $mode );
999 +
1000 + if ( empty( $url ) ) {
1001 + update_option( 'wpcharitable_square_failed_token_renewal_' . $mode, time() );
1002 + return;
1003 + }
1004 +
1005 + // Add SSL verification argsget_square_renew_token_url.
1006 + $args = array(
1007 + 'timeout' => 30,
1008 + 'sslverify' => false, // Temporarily disable SSL verification.
1009 + 'headers' => array(
1010 + 'Content-Type' => 'application/json',
1011 + ),
1012 + );
1013 +
1014 + // Make the request to the proxy server.
1015 + $response = wp_remote_get( esc_url_raw( $url ), $args );
1016 +
1017 + if ( charitable_is_debug( 'square' ) ) {
1018 + // phpcs:disable
1019 + error_log( 'response: ' . print_r( $response, true ) );
1020 + // phpcs:enable
1021 + }
1022 +
1023 + if ( is_wp_error( $response ) ) {
1024 + if ( charitable_is_debug( 'square' ) ) {
1025 + // phpcs:disable
1026 + error_log( 'The cron job url has failed with a wp_error: ' . $response->get_error_message() );
1027 + // phpcs:enable
1028 + }
1029 + update_option( 'wpcharitable_square_failed_token_renewal_' . $mode, time() );
1030 + return;
1031 + }
1032 +
1033 + $response_code = wp_remote_retrieve_response_code( $response );
1034 + if ( 200 !== $response_code ) {
1035 + if ( charitable_is_debug( 'square' ) ) {
1036 + // phpcs:disable
1037 + error_log( 'The cron job url has failed with response code: ' . $response_code );
1038 + // phpcs:enable
1039 + }
1040 + update_option( 'wpcharitable_square_failed_token_renewal_' . $mode, time() );
1041 + return;
1042 + }
1043 +
1044 + $body = wp_remote_retrieve_body( $response );
1045 + $data = json_decode( $body, true );
1046 +
1047 + if ( charitable_is_debug( 'square' ) ) {
1048 + // phpcs:disable
1049 + error_log( 'data: ' . print_r( $data, true ) );
1050 + error_log( 'body: ' . print_r( $body, true ) );
1051 + // phpcs:enable
1052 + }
1053 +
1054 + if ( empty( $data ) || ! isset( $data['data'] ) ) {
1055 + if ( charitable_is_debug( 'square' ) ) {
1056 + // phpcs:disable
1057 + error_log( 'The cron job url has failed - no data returned.' );
1058 + // phpcs:enable
1059 + }
1060 + update_option( 'wpcharitable_square_failed_token_renewal_' . $mode, time() );
1061 + return;
1062 + }
1063 +
1064 + // Update the stored tokens.
1065 + $settings = get_option( 'charitable_settings' );
1066 + $settings['gateways_square'][ $mode ]['access_token'] = isset( $data['data']['access_token'] ) ? sanitize_text_field( $data['data']['access_token'] ) : false;
1067 + $settings['gateways_square'][ $mode ]['refresh_token'] = isset( $data['data']['refresh_token'] ) ? sanitize_text_field( $data['data']['refresh_token'] ) : false;
1068 + $settings['gateways_square'][ $mode ]['token_refresh_date'] = time();
1069 +
1070 + update_option( 'charitable_settings', $settings );
1071 +
1072 + // Clear any failure notices.
1073 + delete_option( 'wpcharitable_square_failed_token_renewal_' . $mode );
1074 +
1075 + // Add a success notice.
1076 + update_option( 'wpcharitable_square_token_refreshed_' . $mode, time() );
1077 +
1078 + if ( charitable_is_debug( 'square' ) ) {
1079 + // phpcs:disable
1080 + error_log( 'wpcharitable_square_token_refreshed_' . $mode . ' updated to: ' . time() );
1081 + // phpcs:enable
1082 + }
1083 + }
1084 +
1085 + /**
1086 + * Saves the account information sent back from Square, alongside other, to identify the connected account.
1087 + *
1088 + * @since 1.8.7
1089 + *
1090 + * @param array<string> $data Square oAuth account data.
1091 + * @param string $gateway_id The gateway ID.
1092 + * @return void
1093 + */
1094 + private function save_account_information_square( $data, $gateway_id = 'square' ) {
1095 + if ( charitable_is_debug( 'square' ) ) {
1096 + // phpcs:disable
1097 + error_log( 'save_account_information_square' );
1098 + error_log( print_r( $data, true ) );
1099 + // phpcs:enable
1100 + }
1101 +
1102 + $mode = $this->in_test_mode()
1103 + ? 'test'
1104 + : 'live';
1105 +
1106 + $settings = get_option( 'charitable_settings' );
1107 +
1108 + $settings[ 'gateways_' . $gateway_id ][ $mode ]['access_token'] = isset( $data['access_token'] ) ? sanitize_text_field( $data['access_token'] ) : false;
1109 + $settings[ 'gateways_' . $gateway_id ][ $mode ]['refresh_token'] = isset( $data['refresh_token'] ) ? sanitize_text_field( $data['refresh_token'] ) : false;
1110 + $settings[ 'gateways_' . $gateway_id ][ $mode ]['merchant_id'] = isset( $data['merchant_id'] ) ? sanitize_text_field( $data['merchant_id'] ) : false;
1111 + $settings[ 'gateways_' . $gateway_id ][ $mode ]['location_id'] = isset( $data['location_id'] ) ? sanitize_text_field( $data['location_id'] ) : false;
1112 + $settings[ 'gateways_' . $gateway_id ][ $mode ]['token_refresh_date'] = time();
1113 +
1114 + update_option( 'charitable_settings', $settings );
1115 +
1116 + // We save the AES key in it's own WordPress options field, marked as 'charitable_square_aes_key_test' or 'charitable_square_aes_key_live'.
1117 + $aes_key = isset( $data['aes_key'] ) ? sanitize_text_field( $data['aes_key'] ) : false;
1118 +
1119 + if ( charitable_is_debug( 'square' ) ) {
1120 + // phpcs:disable
1121 + error_log( 'aes_key: ' . $aes_key );
1122 + // phpcs:enable
1123 + }
1124 +
1125 + update_option( 'charitable_square_aes_key_' . $mode, $aes_key );
1126 +
1127 + // Schedule the token renewal cron job.
1128 + $this->schedule_square_token_renewal_cron( $mode );
1129 +
1130 + do_action( 'charitable_save_account_information_gateway_' . $gateway_id, $settings, $data, $gateway_id );
1131 + }
1132 +
1133 + /**
1134 + * Saves the account information sent back from Stripe, alongside other, to identify the connected account.
1135 + *
1136 + * @since 4.4.2
1137 + * @version 1.8.1.12
1138 + *
1139 + * @param array<string> $data Stripe oAuth account data.
1140 + * @param string $gateway_id The gateway ID.
1141 + * @return void
1142 + */
1143 + private function save_account_information( $data, $gateway_id = 'stripe' ) {
1144 +
1145 + $prefix = $this->in_test_mode()
1146 + ? 'test'
1147 + : 'live';
1148 +
1149 + $settings = get_option( 'charitable_settings' );
1150 +
1151 + $settings[ 'gateways_' . $gateway_id ]['live_secret_key'] = '';
1152 + $settings[ 'gateways_' . $gateway_id ]['live_public_key'] = '';
1153 + $settings[ 'gateways_' . $gateway_id ]['test_secret_key'] = '';
1154 + $settings[ 'gateways_' . $gateway_id ]['test_public_key'] = '';
1155 +
1156 + $settings[ 'gateways_' . $gateway_id ][ $prefix . '_secret_key' ] = isset( $data['secret_key'] ) ? sanitize_text_field( $data['secret_key'] ) : false;
1157 + $settings[ 'gateways_' . $gateway_id ][ $prefix . '_public_key' ] = isset( $data['publishable_key'] ) ? sanitize_text_field( $data['publishable_key'] ) : false;
1158 +
1159 + if ( charitable_is_debug( 'webhook' ) ) {
1160 + // phpcs:disable
1161 + error_log( 'save_account_information' );
1162 + error_log( print_r( $settings, true ) );
1163 + // phpcs:enable
1164 + }
1165 +
1166 + update_option( 'charitable_settings', $settings );
1167 +
1168 + // Don't show the success message if we are on the welcome / getting started page (otherwise it's fine).
1169 + if ( ! isset( $_GET['page'] ) || 'charitable-getting-started' !== $_GET['page'] ) { // phpcs:ignore
1170 + charitable_get_admin_notices()->add_success( __( 'Stripe Information Updated', 'charitable' ) );
1171 + }
1172 +
1173 + do_action( 'charitable_save_account_information_gateway_' . $gateway_id, $settings, $data, $gateway_id );
1174 + }
1175 +
1176 + /**
1177 + * Disconnects from Stripe by removing associated account information.
1178 + *
1179 + * This does not deauthorize the application within the Stripe account.
1180 + *
1181 + * @since 4.2.2
1182 + *
1183 + * @return void
1184 + */
1185 + public function disconnect_gateways() {
1186 +
1187 + // Do not need to handle this request, bail.
1188 + if (
1189 + ! ( isset( $_GET['page'] ) && 'charitable-settings' === $_GET['page'] ) ||
1190 + ! isset( $_GET['wpcharitable-stripe-disconnect'] )
1191 + ) {
1192 + return;
1193 + }
1194 +
1195 + // Current user cannot handle this request.
1196 + if ( ! current_user_can( 'manage_options' ) ) {
1197 + return;
1198 + }
1199 +
1200 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display/routing parameter
1201 + if ( ! isset( $_GET['_wpnonce'] ) ) {
1202 + return;
1203 + }
1204 +
1205 + // Invalid nonce, bail.
1206 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1207 + if ( ! wp_verify_nonce( wp_unslash( $_GET['_wpnonce'] ), 'wpcharitable-stripe-connect-disconnect' ) ) {
1208 + return;
1209 + }
1210 +
1211 + $settings = get_option( 'charitable_settings' );
1212 +
1213 + unset( $settings['gateways_stripe']['live_secret_key'] );
1214 + unset( $settings['gateways_stripe']['live_public_key'] );
1215 + unset( $settings['gateways_stripe']['test_secret_key'] );
1216 + unset( $settings['gateways_stripe']['test_public_key'] );
1217 +
1218 + update_option( 'charitable_settings', $settings );
1219 +
1220 + // update the option to track that we are using stripe connect.
1221 + delete_option( 'charitable_using_stripe_connect' );
1222 +
1223 + charitable_get_admin_notices()->add_success( __( 'Stripe Connected Removed', 'charitable' ) );
1224 +
1225 + do_action( 'charitable_remove_connection_gateway_stripe', $settings );
1226 +
1227 + $redirect_url = add_query_arg(
1228 + array(
1229 + 'tab' => 'gateways',
1230 + 'page' => 'charitable-settings',
1231 + 'group' => 'gateways_stripe',
1232 + ),
1233 + admin_url( 'admin.php' )
1234 + );
1235 +
1236 + wp_safe_redirect( $redirect_url );
1237 + exit;
1238 + }
1239 +
1240 + /**
1241 + * Disconnects from Square by removing associated account information.
1242 + * Also remove webhooks from the Square account.
1243 + *
1244 + * This does not deauthorize the application within the Square account.
1245 + * LEGIT BEING CALLED.
1246 + *
1247 + * @since 1.8.7
1248 + *
1249 + * @return void
1250 + */
1251 + public function disconnect_gateways_square() {
1252 +
1253 + // Do not need to handle this request, bail.
1254 + if (
1255 + ! ( isset( $_GET['page'] ) && 'charitable-settings' === $_GET['page'] ) ||
1256 + ! isset( $_GET['wpcharitable-square-disconnect'] ) ||
1257 + charitable_square_legacy_mode()
1258 + ) {
1259 + return;
1260 + }
1261 +
1262 + // We need to know if this is a sandbox or live disconnect.
1263 + if ( ! isset( $_GET['mode'] ) ) {
1264 + return;
1265 + }
1266 +
1267 + $mode = sanitize_text_field( wp_unslash( $_GET['mode'] ) );
1268 + $mode = 'sandbox' === $mode ? 'test' : 'live';
1269 +
1270 + // Current user cannot handle this request or nonce is invalid.
1271 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1272 + if ( ! current_user_can( 'manage_options' ) || ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( wp_unslash( $_GET['_wpnonce'] ), 'wpcharitable-square-connect-disconnect' ) ) {
1273 + if ( charitable_is_debug( 'webhook' ) ) {
1274 + error_log( 'disconnect_gateways_square - user cannot manage options or nonce is invalid' ); // phpcs:ignore
1275 + }
1276 + return;
1277 + }
1278 +
1279 + $settings = get_option( 'charitable_settings' );
1280 +
1281 + // Delete the Square connection error notice.
1282 + delete_option( 'charitable_square_connection_error_notice' );
1283 +
1284 + // Attempt to disconnect / remove webhooks from the Square account.
1285 + $this->remove_webhooks_from_square_account( $mode, $settings );
1286 +
1287 + $settings['gateways_square'][ $mode ] = array();
1288 +
1289 + update_option( 'charitable_settings', $settings );
1290 +
1291 + // remove the option to track that we are using square connect (test or live).
1292 + $charitable_using_square_connect = get_option( 'charitable_using_square_connect', array() );
1293 + if ( isset( $charitable_using_square_connect[ $mode ] ) ) {
1294 + unset( $charitable_using_square_connect[ $mode ] );
1295 + update_option( 'charitable_using_square_connect', $charitable_using_square_connect );
1296 + }
1297 +
1298 + // translators: %s is the mode of the connection.
1299 + charitable_get_admin_notices()->add_success( sprintf( __( 'Your connection to Square in %s mode has been removed.', 'charitable' ), $mode ) );
1300 +
1301 + do_action( 'charitable_remove_connection_gateway_square', $settings );
1302 +
1303 + $redirect_url = add_query_arg(
1304 + array(
1305 + 'tab' => 'gateways',
1306 + 'page' => 'charitable-settings',
1307 + 'group' => 'gateways_square_core',
1308 + ),
1309 + admin_url( 'admin.php' )
1310 + );
1311 +
1312 + wp_safe_redirect( $redirect_url );
1313 + exit;
1314 + }
1315 +
1316 + /**
1317 + * Redirects to the gateway settings page once a gateway has been activated.
1318 + *
1319 + * @since 1.7.0
1320 + *
1321 + * @param string $gateway Gateway ID.
1322 + * @return void
1323 + */
1324 + public function redirect_gateway_settings( $gateway ) {
1325 +
1326 + $settings_url = add_query_arg(
1327 + array(
1328 + 'group' => 'gateways_' . $gateway,
1329 + ),
1330 + admin_url( 'admin.php?page=charitable-settings&tab=gateways' )
1331 + );
1332 +
1333 + wp_safe_redirect( $settings_url );
1334 + exit;
1335 + }
1336 +
1337 + /**
463 1338 * Sort the active gateways, placing the default gateway first.
464 1339 *
465 - * @since 1.4.0
1340 + * @since 1.4.0
466 1341 *
467 - * @param string $a Gateway to compare.
468 - * @param string $b Gateway to compare against.
469 - * @return int
1342 + * @param string $a Gateway to compare.
1343 + * @param string $b Gateway to compare against.
1344 + * @return int
470 1345 */
471 1346 protected function sort_by_default( $a, $b ) {
472 1347 $default = $this->get_default_gateway();
473 1348
474 - if ( $a == $default ) {
1349 + if ( $a == $default ) { // phpcs:ignore
475 1350 return -1;
476 1351 }
477 1352
478 - if ( $b == $default ) {
1353 + if ( $b == $default ) { // phpcs:ignore
479 1354 return 1;
480 1355 }
481 1356
482 1357 return 0;
1358 + }
1359 +
1360 + /**
1361 + * Remove webhooks from the Square account.
1362 + *
1363 + * @since 1.8.7
1364 + *
1365 + * @param string $mode The mode to remove the webhooks from.
1366 + * @param array $settings The settings array.
1367 + * @param bool $force_remove Whether to force remove the webhooks.
1368 + * @return void
1369 + */
1370 + public function remove_webhooks_from_square_account( $mode, $settings = array(), $force_remove = true ) {
1371 +
1372 + if ( charitable_is_debug( 'square' ) ) {
1373 + error_log( 'remove_webhooks_from_square_account' ); // phpcs:ignore
1374 + }
1375 +
1376 + // Check if settings are empty and if so, get the settings from the database.
1377 + if ( empty( $settings ) ) {
1378 + $settings = get_option( 'charitable_settings' );
1379 + }
1380 +
1381 + $access_token = $settings['gateways_square'][ $mode ]['access_token'];
1382 +
1383 + // Test to see if we have the access token.
1384 + if ( empty( $access_token ) ) {
1385 + // if force_remove is true, we can remove the webhooks from the Square account in Charitable.
1386 + if ( $force_remove ) {
1387 + if ( charitable_is_debug( 'square' ) ) {
1388 + error_log( 'remove_webhooks_from_square_account:force_remove' ); // phpcs:ignore
1389 + }
1390 + $settings['gateways_square'][ $mode ]['webhooks'] = array();
1391 + update_option( 'charitable_settings', $settings );
1392 + return;
1393 +
1394 + }
1395 + return;
1396 + } elseif ( isset( $settings['gateways_square'][ $mode ]['webhook_id'] ) ) {
1397 + $webhook_manager = new Charitable_Square_WebhooksManager( $mode, $access_token );
1398 + $result = $webhook_manager->remove_webhook( $access_token, $settings['gateways_square'][ $mode ]['webhook_id'] );
1399 +
1400 + }
1401 +
1402 + if ( charitable_is_debug( 'square' ) ) {
1403 + error_log( 'remove_webhooks_from_square_account:deactivate_webhook' ); // phpcs:ignore
1404 + }
1405 +
1406 + return $result;
1407 + }
1408 +
1409 + /**
1410 + * Ensure there is a settings option when landing on settings pages, even if init blank. Mostly applicable to new installs.
1411 + *
1412 + * @since 1.4.0
1413 + * @since 1.8.1.11
1414 + *
1415 + * @return void
1416 + */
1417 + public function check_settings() {
1418 +
1419 + if ( isset( $_GET['page'] ) && 'charitable-settings' !== $_GET['page'] ) { // phpcs:ignore
1420 + return;
1421 + }
1422 +
1423 + $settings = get_option( 'charitable_settings' );
1424 +
1425 + // attach this to CHARITABLE_DEBUG constant.
1426 + if ( charitable_is_debug() ) {
1427 + // phpcs:disable
1428 + // error_log( 'checking_settings' );
1429 + // error_log( print_r( $settings, true ) );
1430 + // phpcs:enable
1431 + }
1432 +
1433 + // If there is no settings, or the settings are empty, set the test mode to true and stripe is default gateway.
1434 + if ( false === $settings || ( is_array( $settings ) && empty( $settings ) ) ) {
1435 + $this->enable_gateway( 'stripe', false );
1436 + $settings = (array) get_option( 'charitable_settings' );
1437 + $settings['test_mode'] = true;
1438 + update_option( 'charitable_settings', $settings );
1439 + }
1440 + }
1441 +
1442 + /**
1443 + * Check the setup stored value of the payment methods.
1444 + *
1445 + * @since 1.8.4
1446 + *
1447 + * @return bool
1448 + */
1449 + public function check_setup_payment_methods() {
1450 +
1451 + $payment_methods = get_option( 'charitable_setup_payment_methods', array() );
1452 +
1453 + if ( ! is_array( $payment_methods ) ) {
1454 + return false;
1455 + }
1456 +
1457 + $payment_methods = array_map( 'sanitize_text_field', $payment_methods );
1458 +
1459 + if ( empty( $payment_methods ) ) {
1460 + return false;
1461 + }
1462 +
1463 + $gateways_enbled = 0;
1464 +
1465 + if ( in_array( 'stripe', $payment_methods, true ) ) {
1466 + // check if this gateway is already enabled.
1467 + if ( ! $this->is_active_gateway( 'stripe' ) ) {
1468 + $this->enable_gateway( 'stripe', false );
1469 + ++$gateways_enbled;
1470 + }
1471 + }
1472 +
1473 + if ( in_array( 'paypal', $payment_methods, true ) ) {
1474 + // check if this gateway is already enabled.
1475 + if ( ! $this->is_active_gateway( 'paypal' ) ) {
1476 + $this->enable_gateway( 'paypal', false );
1477 + ++$gateways_enbled;
1478 + }
1479 + }
1480 +
1481 + if ( in_array( 'offline', $payment_methods, true ) ) {
1482 + // check if this gateway is already enabled.
1483 + if ( ! $this->is_active_gateway( 'offline' ) ) {
1484 + $this->enable_gateway( 'offline', false );
1485 + }
1486 + }
1487 +
1488 + if ( $gateways_enbled > 0 && class_exists( 'Charitable_Checklist' ) ) {
1489 + $checklist_class = Charitable_Checklist::get_instance();
1490 + $checklist_class->mark_step_completed( 'connect-payment' );
1491 + }
1492 +
1493 + return true;
1494 + }
1495 +
1496 + /**
1497 + * Display the test and live fields that represent the manual keys for Stripe that users might be already using prior to 1.7.0.
1498 + *
1499 + * @since 1.7.0
1500 + */
1501 + public function show_stripe_manual_keys_ajax() {
1502 +
1503 + if ( ! isset( $_POST ) || 'charitable-show-stripe-keys' !== $_POST['action'] ) { // phpcs:ignore
1504 + return;
1505 + }
1506 +
1507 + // Invalid nonce, bail.
1508 + if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'charitable-show-stripe-keys' ) ) { // phpcs:ignore
1509 + return;
1510 + }
1511 +
1512 + // Current user cannot handle this request.
1513 + if ( ! current_user_can( 'manage_options' ) ) {
1514 + return;
1515 + }
1516 +
1517 + $settings = get_option( 'charitable_settings' );
1518 +
1519 + if ( false === $settings ) {
1520 + return;
1521 + }
1522 +
1523 + $gateway_id = 'stripe';
1524 +
1525 + $live_secret_key = $settings[ 'gateways_' . $gateway_id ]['live_secret_key'];
1526 + $live_public_key = $settings[ 'gateways_' . $gateway_id ]['live_public_key'];
1527 + $test_secret_key = $settings[ 'gateways_' . $gateway_id ]['test_secret_key'];
1528 + $test_public_key = $settings[ 'gateways_' . $gateway_id ]['test_public_key'];
1529 +
1530 + $html = '<tr><th scope="row">Live Settings</th><td><hr />
1531 + </td></tr><tr class="wide"><th scope="row">Live Secret Key</th><td><input type="text"
1532 + id="charitable_settings_gateways_stripe_live_secret_key"
1533 + name="charitable_settings[gateways_stripe][live_secret_key]"
1534 + value="' . trim( $live_secret_key ) . '"
1535 + class="charitable-settings-field wide"
1536 + />
1537 + </td></tr><tr class="wide"><th scope="row">Live Publishable Key</th><td><input type="text"
1538 + id="charitable_settings_gateways_stripe_live_public_key"
1539 + name="charitable_settings[gateways_stripe][live_public_key]"
1540 + value="' . trim( $live_public_key ) . '"
1541 + class="charitable-settings-field wide"
1542 + />
1543 + </td></tr><tr><th scope="row">Test Settings</th><td><hr />
1544 + </td></tr><tr class="wide"><th scope="row">Test Secret Key</th><td><input type="text"
1545 + id="charitable_settings_gateways_stripe_test_secret_key"
1546 + name="charitable_settings[gateways_stripe][test_secret_key]"
1547 + value="' . trim( $test_secret_key ) . '"
1548 + class="charitable-settings-field wide"
1549 + />
1550 + </td></tr><tr class="wide"><th scope="row">Test Publishable Key</th><td><input type="text"
1551 + id="charitable_settings_gateways_stripe_test_public_key"
1552 + name="charitable_settings[gateways_stripe][test_public_key]"
1553 + value="' . trim( $test_public_key ) . '"
1554 + class="charitable-settings-field wide"
1555 + />
1556 + </td></tr>';
1557 +
1558 + wp_send_json_success( $html );
1559 + }
1560 +
1561 + /**
1562 + * Returns the error message for the square connect gateway.
1563 + *
1564 + * @since 1.8.7
1565 + *
1566 + * @return void
1567 + */
1568 + public function get_gateway_error_message() {
1569 +
1570 + if ( ! isset( $_GET['square_connect_error'] ) && ! isset( $_GET['square_connect_error_description'] ) ) { // phpcs:ignore
1571 + return;
1572 + }
1573 +
1574 + $square_connect_error = sanitize_text_field( wp_unslash( $_GET['square_connect_error'] ) ); // phpcs:ignore
1575 + $square_connect_error_description = sanitize_text_field( wp_unslash( $_GET['square_connect_error_description'] ) ); // phpcs:ignore
1576 + $support_link = 'https://wpcharitable.com/support';
1577 +
1578 + switch ( $square_connect_error ) {
1579 + case 'access_denied':
1580 + switch ( $square_connect_error_description ) {
1581 + case 'user_denied':
1582 + $message = __( 'You have denied access to Square Connect.', 'charitable' );
1583 + break;
1584 + case 'missing_state_or_code':
1585 + $message = sprintf(
1586 + /* translators: %s: Support link URL */
1587 + __( 'There has been an issue with your request. Please try again or <a href="%s">contact support</a>.', 'charitable' ),
1588 + $support_link
1589 + );
1590 + break;
1591 + case 'missing_init_request':
1592 + $message = sprintf(
1593 + /* translators: %s: Support link URL */
1594 + __( 'There has been an issue confirming your request. Please try again or <a href="%s">contact support</a>.', 'charitable' ),
1595 + $support_link
1596 + );
1597 + break;
1598 + default:
1599 + $message = sprintf(
1600 + /* translators: %s: Support link URL */
1601 + __( 'Your connect to Square has been canceled for an unknown reason. Please try again or <a href="%s">contact support</a>.', 'charitable' ),
1602 + $support_link
1603 + );
1604 + break;
1605 + }
1606 + break;
1607 + case 'connect_error':
1608 + switch ( $square_connect_error_description ) {
1609 + case 'error_refreshing_token':
1610 + $refresh_by_date = Charitable_Gateway_Square::get_instance()->get_personal_access_token_should_refresh( 'test' );
1611 + $message = sprintf(
1612 + /* translators: %s: Refresh by date */
1613 + __( 'There has been an issue refreshing your Square token. Token should be refreshed by: %1$s. Please try again or <a href="%2$s">contact support</a>.', 'charitable' ),
1614 + $refresh_by_date,
1615 + $support_link
1616 + );
1617 + break;
1618 + case 'missing_response_fields':
1619 + case 'credentials_not_saved':
1620 + $message = sprintf(
1621 + /* translators: %s: Support link URL */
1622 + __( 'There has been an issue confirming your Square token. Please try again or <a href="%s">contact support</a>.', 'charitable' ),
1623 + $support_link
1624 + );
1625 + break;
1626 + case 'response_error':
1627 + case 'merchant_id_not_found':
1628 + case 'save_credentials_error':
1629 + case 'missing_state_or_code':
1630 + case 'invalid_request':
1631 + case 'error_getting_credentials':
1632 + case 'missing_state_or_site_url':
1633 + case 'error_getting_credentials':
1634 + case 'missing_information_for_gateway_connect':
1635 + case 'error_saving_init_request':
1636 + $message = sprintf(
1637 + /* translators: %s: Support link URL */
1638 + __( 'There has been an issue communicating with Square. Please try again or <a href="%s">contact support</a>.', 'charitable' ),
1639 + $support_link
1640 + );
1641 + break;
1642 + default:
1643 + $message = sprintf(
1644 + /* translators: %s: Support link URL */
1645 + __( 'There has been an issue with your request. Please try again or <a href="%s">contact support</a>.', 'charitable' ),
1646 + $support_link
1647 + );
1648 + break;
1649 + }
1650 + break;
1651 + default:
1652 + $message = sprintf(
1653 + /* translators: %s: Support link URL */
1654 + __( 'There has been an issue with your request. Please try again or <a href="%s">contact support</a>.', 'charitable' ),
1655 + $support_link
1656 + );
1657 + }
1658 +
1659 + charitable_get_admin_notices()->add_notice( $message, 'error', false, true );
1660 + }
1661 +
1662 + /**
1663 + * Get the personal access refresh token.
1664 + *
1665 + * @since 1.8.7
1666 + *
1667 + * @param string $mode The mode to get the personal access refresh token for.
1668 + * @return string
1669 + */
1670 + private function get_personal_access_refresh_token( $mode = 'test' ) {
1671 + $square_settings = charitable_get_option( 'gateways_square', array() );
1672 +
1673 + if ( ! empty( $square_settings[ $mode ]['refresh_token'] ) ) {
1674 + return $square_settings[ $mode ]['refresh_token'];
1675 + }
1676 +
1677 + return '';
1678 + }
1679 +
1680 + /**
1681 + * Add custom cron schedules.
1682 + *
1683 + * @since 1.8.7
1684 + *
1685 + * @param array $schedules Existing cron schedules.
1686 + * @return array
1687 + */
1688 + public function add_cron_schedules( $schedules ) {
1689 + $schedules['14days'] = array(
1690 + 'interval' => 14 * DAY_IN_SECONDS,
1691 + 'display' => __( 'Every 14 days', 'charitable' ),
1692 + );
1693 + return $schedules;
1694 + }
1695 +
1696 + /**
1697 + * Handle Square gateway switch.
1698 + *
1699 + * @since 1.8.7
1700 + * @version 1.8.9.1
1701 + *
1702 + * @return void
1703 + */
1704 + public function handle_square_gateway_switch() {
1705 + // phpcs:disable WordPress.Security.NonceVerification.Missing
1706 + // Verify nonce.
1707 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1708 + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'charitable_admin_nonce' ) ) {
1709 + wp_send_json_error( array( 'message' => __( 'Security check failed.', 'charitable' ) ) );
1710 + }
1711 + // phpcs:enable WordPress.Security.NonceVerification.Missing
1712 +
1713 + // Check user permissions.
1714 + if ( ! current_user_can( 'manage_options' ) ) {
1715 + wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'charitable' ) ) );
1716 + }
1717 +
1718 + // Check if Square Legacy is active and Square Core is not active.
1719 + if ( ! $this->is_active_gateway( 'square' ) || $this->is_active_gateway( 'square_core' ) ) {
1720 + wp_send_json_error( array( 'message' => __( 'Invalid gateway state for switching.', 'charitable' ) ) );
1721 + }
1722 +
1723 + // Disable Square Legacy.
1724 + $this->disable_gateway( 'square' );
1725 +
1726 + // Enable Square Core.
1727 + $this->enable_gateway( 'square_core', false );
1728 +
1729 + // Get the redirect URL for Square Core settings.
1730 + $redirect_url = add_query_arg(
1731 + array(
1732 + 'page' => 'charitable-settings',
1733 + 'tab' => 'gateways',
1734 + 'group' => 'gateways_square_core',
1735 + ),
1736 + admin_url( 'admin.php' )
1737 + );
1738 +
1739 + wp_send_json_success( array( 'redirect_url' => $redirect_url ) );
1740 + }
1741 +
1742 + /**
1743 + * Handle Square core to legacy switch.
1744 + *
1745 + * @since 1.8.7
1746 + * @version 1.8.9.1
1747 + *
1748 + * @return void
1749 + */
1750 + public function handle_square_core_to_legacy_switch() {
1751 + // phpcs:disable WordPress.Security.NonceVerification.Missing
1752 + // Verify nonce.
1753 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- wp_verify_nonce handles validation
1754 + if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nonce'] ), 'charitable_admin_nonce' ) ) {
1755 + wp_send_json_error( array( 'message' => __( 'Security check failed.', 'charitable' ) ) );
1756 + }
1757 + // phpcs:enable WordPress.Security.NonceVerification.Missing
1758 +
1759 + // Check user permissions.
1760 + if ( ! current_user_can( 'manage_options' ) ) {
1761 + wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'charitable' ) ) );
1762 + }
1763 +
1764 + // Check if Square Core is active and Square Legacy is not active.
1765 + if ( ! $this->is_active_gateway( 'square_core' ) || $this->is_active_gateway( 'square' ) ) {
1766 + wp_send_json_error( array( 'message' => __( 'Invalid gateway state for switching.', 'charitable' ) ) );
1767 + }
1768 +
1769 + if ( charitable_is_debug() ) {
1770 + error_log( 'Charitable: Starting Square Core to Legacy gateway switch process.' ); // phpcs:ignore
1771 + }
1772 +
1773 + // Disconnect Square Core connections for both live and test modes.
1774 + $this->disconnect_square_core_connections();
1775 +
1776 + // Disable Square Core.
1777 + $this->disable_gateway( 'square_core' );
1778 +
1779 + // Enable Square Legacy.
1780 + $this->enable_gateway( 'square', false );
1781 +
1782 + if ( charitable_is_debug() ) {
1783 + error_log( 'Charitable: Successfully switched from Square Core to Square Legacy gateway.' ); // phpcs:ignore
1784 + }
1785 +
1786 + // Get the redirect URL for Square Legacy settings.
1787 + $redirect_url = add_query_arg(
1788 + array(
1789 + 'page' => 'charitable-settings',
1790 + 'tab' => 'gateways',
1791 + 'group' => 'gateways_square',
1792 + ),
1793 + admin_url( 'admin.php' )
1794 + );
1795 +
1796 + wp_send_json_success( array( 'redirect_url' => $redirect_url ) );
1797 + }
1798 +
1799 + /**
1800 + * Disconnect Square Core connections for both live and test modes.
1801 + *
1802 + * @since 1.8.7
1803 + *
1804 + * @return void
1805 + */
1806 + private function disconnect_square_core_connections() {
1807 +
1808 + if ( charitable_is_debug() ) {
1809 + error_log( 'Charitable: Starting Square Core connection disconnection process.' ); // phpcs:ignore
1810 + }
1811 +
1812 + // Check if Charitable_Square_Connection class exists.
1813 + if ( ! class_exists( 'Charitable_Square_Connection' ) ) {
1814 + if ( charitable_is_debug() ) {
1815 + error_log( 'Charitable: Charitable_Square_Connection class not found. Skipping disconnect process.' ); // phpcs:ignore
1816 + }
1817 + return;
1818 + }
1819 +
1820 + // Check and disconnect live mode connection.
1821 + $live_connection = Charitable_Square_Connection::get( 'live' );
1822 + if ( $live_connection && $live_connection->is_configured() ) {
1823 + if ( charitable_is_debug() ) {
1824 + error_log( 'Charitable: Found active Square Core live connection. Attempting to disconnect.' ); // phpcs:ignore
1825 + }
1826 + $this->disconnect_square_core_mode( $live_connection, 'live' );
1827 + } elseif ( charitable_is_debug() ) {
1828 + error_log( 'Charitable: No active Square Core live connection found.' ); // phpcs:ignore
1829 +
1830 + }
1831 +
1832 + // Check and disconnect test mode connection.
1833 + $test_connection = Charitable_Square_Connection::get( 'test' );
1834 + if ( $test_connection && $test_connection->is_configured() ) {
1835 + if ( charitable_is_debug() ) {
1836 + error_log( 'Charitable: Found active Square Core test connection. Attempting to disconnect.' ); // phpcs:ignore
1837 + }
1838 + $this->disconnect_square_core_mode( $test_connection, 'test' );
1839 + } elseif ( charitable_is_debug() ) {
1840 + error_log( 'Charitable: No active Square Core test connection found.' ); // phpcs:ignore
1841 +
1842 + }
1843 +
1844 + if ( charitable_is_debug() ) {
1845 + error_log( 'Charitable: Completed Square Core connection disconnection process.' ); // phpcs:ignore
1846 + }
1847 + }
1848 +
1849 + /**
1850 + * Disconnect Square Core connection for a specific mode.
1851 + *
1852 + * @since 1.8.7
1853 + *
1854 + * @param Charitable_Square_Connection $connection The connection object.
1855 + * @param string $mode The mode (live/test).
1856 + * @return void
1857 + */
1858 + private function disconnect_square_core_mode( $connection, $mode ) {
1859 +
1860 + if ( charitable_is_debug() ) {
1861 + error_log( 'Charitable: Disconnecting Square Core connection for mode: ' . $mode ); // phpcs:ignore
1862 + }
1863 +
1864 + // Check if Charitable_Square_Connect class exists.
1865 + if ( ! class_exists( 'Charitable_Square_Connect' ) ) {
1866 + if ( charitable_is_debug() ) {
1867 + error_log( 'Charitable: Charitable_Square_Connect class not found. Cannot disconnect mode: ' . $mode ); // phpcs:ignore
1868 + }
1869 + return;
1870 + }
1871 +
1872 + try {
1873 + // Get the Square Connect instance.
1874 + $square_connect = Charitable_Square_Connect::get_instance();
1875 +
1876 + // Use reflection to access the private handle_disconnect method.
1877 + $reflection = new ReflectionClass( $square_connect );
1878 + $method = $reflection->getMethod( 'handle_disconnect' );
1879 + $method->setAccessible( true );
1880 +
1881 + // Set the live_mode parameter for the disconnect process.
1882 + $_GET['live_mode'] = ( $mode === 'live' ) ? 1 : 0;
1883 +
1884 + // Call the disconnect method without redirect.
1885 + $method->invoke( $square_connect, false );
1886 +
1887 + if ( charitable_is_debug() ) {
1888 + error_log( 'Charitable: Successfully disconnected Square Core connection for mode: ' . $mode ); // phpcs:ignore
1889 + }
1890 + } catch ( Exception $e ) {
1891 + if ( charitable_is_debug() ) {
1892 + error_log( 'Charitable: Error disconnecting Square Core connection for mode ' . $mode . ': ' . $e->getMessage() ); // phpcs:ignore
1893 + }
1894 + }
1895 + }
1896 +
1897 + /**
1898 + * Dequeue Square Legacy scripts if Square Legacy is not active.
1899 + *
1900 + * @since 1.8.7
1901 + *
1902 + * @return void
1903 + */
1904 + public function maybe_dequeue_square_legacy_scripts() {
1905 + // Only run on frontend.
1906 + if ( is_admin() ) {
1907 + return;
1908 + }
1909 +
1910 + // Debug: Log the status checks.
1911 + if ( charitable_is_debug( 'square' ) ) {
1912 + error_log( '[Charitable Square] Script dequeue check - Legacy mode: ' . ( charitable_square_legacy_mode() ? 'true' : 'false' ) ); // phpcs:ignore
1913 + error_log( '[Charitable Square] Script dequeue check - Square gateway active: ' . ( $this->is_active_gateway( 'square' ) ? 'true' : 'false' ) ); // phpcs:ignore
1914 + }
1915 +
1916 + // Check if Square Legacy is active.
1917 + if ( ! charitable_square_legacy_mode() || ! $this->is_active_gateway( 'square' ) ) {
1918 + // Dequeue Square Legacy scripts and styles.
1919 + wp_dequeue_script( 'charitable-square' );
1920 + wp_dequeue_script( 'square' );
1921 + wp_dequeue_style( 'charitable-square' );
1922 +
1923 + // Deregister scripts to prevent them from being loaded.
1924 + wp_deregister_script( 'charitable-square' );
1925 + wp_deregister_script( 'square' );
1926 + wp_deregister_style( 'charitable-square' );
1927 +
1928 + if ( charitable_is_debug( 'square' ) ) {
1929 + error_log( '[Charitable Square] Dequeued Square Legacy scripts - Square Legacy is not active' ); // phpcs:ignore
1930 + }
1931 + } elseif ( charitable_is_debug( 'square' ) ) {
1932 + error_log( '[Charitable Square] Square Legacy scripts will remain loaded - Square Legacy is active' ); // phpcs:ignore
1933 +
1934 + }
1935 + }
1936 +
1937 + /**
1938 + * Check if charitable-square plugin is active.
1939 + *
1940 + * @since 1.8.7
1941 + *
1942 + * @return boolean
1943 + */
1944 + private function is_square_addon_active() {
1945 + return is_plugin_active( 'charitable-square/charitable-square.php' );
1946 + }
1947 +
1948 + /**
1949 + * Force cleanup of 'square' gateway if charitable-square plugin is not active.
1950 + * Runs once per 24 hours (adjustable via filter) to avoid constant database checks.
1951 + *
1952 + * @since 1.8.7
1953 + *
1954 + * @param array $active_gateways The current active gateways array.
1955 + * @return void
1956 + */
1957 + private function maybe_force_cleanup_square_gateway( $active_gateways ) {
1958 + // Check if we need to run cleanup (once per 24 hours).
1959 + $transient_name = 'charitable_square_cleanup_last_run';
1960 + $check_interval = apply_filters( 'charitable_square_cleanup_check_interval', 24 * HOUR_IN_SECONDS );
1961 +
1962 + if ( get_transient( $transient_name ) ) {
1963 + return;
1964 + }
1965 +
1966 + // Check if 'square' gateway exists and charitable-square plugin is not active.
1967 + if ( isset( $active_gateways['square'] ) && ! $this->is_square_addon_active() ) {
1968 + try {
1969 + // Get current settings.
1970 + $settings = get_option( 'charitable_settings', array() );
1971 +
1972 + if ( ! empty( $settings['active_gateways']['square'] ) ) {
1973 + // Remove 'square' gateway from active gateways.
1974 + unset( $settings['active_gateways']['square'] );
1975 +
1976 + // Update default gateway if it was set to 'square'.
1977 + if ( isset( $settings['default_gateway'] ) && 'square' === $settings['default_gateway'] ) {
1978 + $settings['default_gateway'] = '';
1979 + }
1980 +
1981 + // Remove the option that warns users about incorrect square settings.
1982 + delete_option( 'charitable_square_mode_connection_warning' );
1983 +
1984 + // Save updated settings.
1985 + $updated = update_option( 'charitable_settings', $settings );
1986 +
1987 + if ( $updated ) {
1988 + if ( charitable_is_debug( 'square' ) ) {
1989 + error_log( '[Charitable Square] Successfully removed legacy square gateway from active gateways' ); // phpcs:ignore
1990 + }
1991 + } else {
1992 + if ( charitable_is_debug( 'square' ) ) {
1993 + error_log( '[Charitable Square] Failed to update settings when removing legacy square gateway' ); // phpcs:ignore
1994 + }
1995 + }
1996 + }
1997 + } catch ( Exception $e ) {
1998 + if ( charitable_is_debug( 'square' ) ) {
1999 + error_log( '[Charitable Square] Error during square gateway cleanup: ' . $e->getMessage() ); // phpcs:ignore
2000 + }
2001 + }
2002 + }
2003 +
2004 + // Set transient to prevent running again for the specified interval.
2005 + set_transient( $transient_name, time(), $check_interval );
483 2006 }
484 2007 }
485 2008
486 2009 endif;