PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.6.0
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.6.0
1.6.0 1.5.1 1.5.0 1.4.0 1.3.0 trunk 0.0.1 1.0.0 1.1.0 1.1.1 1.1.2 1.2.0
suredonation / inc / api / settings-api.php

settings-api.php in SureDonation – Donation Forms, Fundraising Campaigns & Donor Management 1.6.0, at inc/api/settings-api.php

715 lines 19.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * General Settings REST API endpoints.
4 *
5 * @package SureDonation
6 */
7
8 namespace SureDonation\Inc\API;
9
10 use SureDonation\Inc\Helper;
11 use SureDonation\Inc\Payments\Payment_Helper;
12 use WP_Error;
13 use WP_REST_Request;
14 use WP_REST_Response;
15 use WP_REST_Server;
16
17 // Exit if accessed directly.
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit;
20 }
21
22 /**
23 * Settings API class.
24 *
25 * @since 0.0.1
26 */
27 class Settings_API {
28 /**
29 * Option key for email notifications within consolidated options.
30 *
31 * @since 0.0.1
32 */
33 public const EMAIL_OPTION_KEY = 'email_notifications';
34
35 /**
36 * Option key for AI settings within consolidated options.
37 *
38 * @since 1.0.0
39 */
40 public const AI_OPTION_KEY = 'ai_settings';
41
42 /**
43 * Option key for spam protection settings within consolidated options.
44 *
45 * @since 1.1.0
46 */
47 public const SPAM_OPTION_KEY = 'spam_protection_settings';
48
49 /**
50 * Option key for donor management settings within consolidated options.
51 *
52 * @since 1.0.0
53 */
54 public const DONOR_OPTION_KEY = 'donor_settings';
55
56 /**
57 * Get settings endpoints.
58 *
59 * @return array<string, mixed>
60 * @since 0.0.1
61 */
62 public function get_endpoints() {
63 return [
64 // Get currency data for block editor (public endpoint).
65 '/settings' => [
66 'methods' => WP_REST_Server::READABLE,
67 'callback' => [ $this, 'get_currency_settings' ],
68 'permission_callback' => '__return_true',
69 ],
70
71 // Get and update general settings.
72 '/settings/general' => [
73 [
74 'methods' => WP_REST_Server::READABLE,
75 'callback' => [ $this, 'get_settings' ],
76 'permission_callback' => [ $this, 'check_permissions' ],
77 ],
78 [
79 'methods' => WP_REST_Server::EDITABLE,
80 'callback' => [ $this, 'update_settings' ],
81 'permission_callback' => [ $this, 'check_permissions' ],
82 ],
83 ],
84
85 // Get available currencies.
86 '/settings/currencies' => [
87 'methods' => WP_REST_Server::READABLE,
88 'callback' => [ $this, 'get_currencies' ],
89 'permission_callback' => [ $this, 'check_permissions' ],
90 ],
91
92 // Email notifications are managed per-form via post meta.
93 // See inc/form-editor/assets.php for the form-level email system.
94 // AI settings.
95 '/settings/ai' => [
96 [
97 'methods' => WP_REST_Server::READABLE,
98 'callback' => [ $this, 'get_ai_settings' ],
99 'permission_callback' => [ $this, 'check_permissions' ],
100 ],
101 [
102 'methods' => WP_REST_Server::EDITABLE,
103 'callback' => [ $this, 'update_ai_settings' ],
104 'permission_callback' => [ $this, 'check_permissions' ],
105 ],
106 ],
107
108 // Spam protection settings.
109 '/settings/spam-protection' => [
110 [
111 'methods' => WP_REST_Server::READABLE,
112 'callback' => [ $this, 'get_spam_protection_settings' ],
113 'permission_callback' => [ $this, 'check_permissions' ],
114 ],
115 [
116 'methods' => WP_REST_Server::EDITABLE,
117 'callback' => [ $this, 'update_spam_protection_settings' ],
118 'permission_callback' => [ $this, 'check_permissions' ],
119 ],
120 ],
121
122 // Miscellaneous settings (usage tracking, etc.).
123 '/settings/misc' => [
124 [
125 'methods' => WP_REST_Server::READABLE,
126 'callback' => [ $this, 'get_misc_settings' ],
127 'permission_callback' => [ $this, 'check_permissions' ],
128 ],
129 [
130 'methods' => WP_REST_Server::EDITABLE,
131 'callback' => [ $this, 'update_misc_settings' ],
132 'permission_callback' => [ $this, 'check_permissions' ],
133 'args' => [
134 'usage_tracking' => [
135 'type' => 'boolean',
136 'sanitize_callback' => 'rest_sanitize_boolean',
137 ],
138 ],
139 ],
140 ],
141
142 // Donor management settings.
143 '/settings/donor' => [
144 [
145 'methods' => WP_REST_Server::READABLE,
146 'callback' => [ $this, 'get_donor_settings' ],
147 'permission_callback' => [ $this, 'check_permissions' ],
148 ],
149 [
150 'methods' => WP_REST_Server::EDITABLE,
151 'callback' => [ $this, 'update_donor_settings' ],
152 'permission_callback' => [ $this, 'check_permissions' ],
153 'args' => [
154 'create_wp_user' => [
155 'type' => 'boolean',
156 'sanitize_callback' => 'rest_sanitize_boolean',
157 ],
158 // Registered so the handler's (bool) cast receives a real
159 // boolean: a form-encoded "false" would otherwise cast to true
160 // and switch moderation ON when the admin asked for OFF.
161 'hold_donor_comments' => [
162 'type' => 'boolean',
163 'sanitize_callback' => 'rest_sanitize_boolean',
164 ],
165 ],
166 ],
167 ],
168
169 // Form validation default messages.
170 '/settings/validation' => [
171 [
172 'methods' => WP_REST_Server::READABLE,
173 'callback' => [ $this, 'get_validation_settings' ],
174 'permission_callback' => [ $this, 'check_permissions' ],
175 ],
176 [
177 'methods' => WP_REST_Server::EDITABLE,
178 'callback' => [ $this, 'update_validation_settings' ],
179 'permission_callback' => [ $this, 'check_permissions' ],
180 ],
181 ],
182
183 // Privacy settings (data retention, consent, privacy/terms fields).
184 '/settings/privacy' => [
185 [
186 'methods' => WP_REST_Server::READABLE,
187 'callback' => [ $this, 'get_privacy_settings' ],
188 'permission_callback' => [ $this, 'check_permissions' ],
189 ],
190 [
191 'methods' => WP_REST_Server::EDITABLE,
192 'callback' => [ $this, 'update_privacy_settings' ],
193 'permission_callback' => [ $this, 'check_permissions' ],
194 ],
195 ],
196
197 // Send test email.
198 '/settings/email/test' => [
199 'methods' => WP_REST_Server::CREATABLE,
200 'callback' => [ $this, 'send_test_email' ],
201 'permission_callback' => [ $this, 'check_permissions' ],
202 ],
203 ];
204 }
205
206 /**
207 * Get the Privacy settings (stored values merged over the defaults).
208 *
209 * @param WP_REST_Request $request Request object.
210 * @return WP_REST_Response
211 * @since 1.2.0
212 */
213 public function get_privacy_settings( $request ) {
214 unset( $request ); // Unused parameter.
215
216 return new WP_REST_Response(
217 [
218 'success' => true,
219 'settings' => \SureDonation\Inc\Privacy\Privacy_Settings::get_settings(),
220 ],
221 200
222 );
223 }
224
225 /**
226 * Update the Privacy settings.
227 *
228 * @param WP_REST_Request $request Request object.
229 * @return WP_REST_Response
230 * @since 1.2.0
231 */
232 public function update_privacy_settings( $request ) {
233 $params = $request->get_json_params();
234 $sanitized = \SureDonation\Inc\Privacy\Privacy_Settings::sanitize( is_array( $params ) ? $params : [] );
235
236 Helper::update_suredonation_option( \SureDonation\Inc\Privacy\Privacy_Settings::OPTION_KEY, $sanitized );
237
238 return new WP_REST_Response(
239 [
240 'success' => true,
241 'settings' => $sanitized,
242 ],
243 200
244 );
245 }
246
247 /**
248 * Get the form-validation default messages.
249 *
250 * Returns the stored admin overrides merged over the translatable defaults
251 * so every configurable message always has a value in the editor.
252 *
253 * @param WP_REST_Request $request Request object.
254 * @return WP_REST_Response
255 * @since 1.1.0
256 */
257 public function get_validation_settings( $request ) {
258 unset( $request ); // Unused parameter.
259
260 $defaults = \SureDonation\Inc\Field_Validation::default_validation_messages();
261 $stored = Helper::get_suredonation_option( \SureDonation\Inc\Field_Validation::VALIDATION_MESSAGES_OPTION_KEY, [] );
262
263 return new WP_REST_Response(
264 [
265 'success' => true,
266 'settings' => wp_parse_args( is_array( $stored ) ? $stored : [], $defaults ),
267 ],
268 200
269 );
270 }
271
272 /**
273 * Update the form-validation default messages.
274 *
275 * @param WP_REST_Request $request Request object.
276 * @return WP_REST_Response
277 * @since 1.1.0
278 */
279 public function update_validation_settings( $request ) {
280 $current = Helper::get_suredonation_option( \SureDonation\Inc\Field_Validation::VALIDATION_MESSAGES_OPTION_KEY, [] );
281
282 if ( ! is_array( $current ) ) {
283 $current = [];
284 }
285
286 /**
287 * Filter the list of allowed validation-message keys.
288 *
289 * Lets extensions register additional message keys for their own field
290 * types, mirroring the `suredonation.settings.tab.validationFields` and
291 * `suredonation.settings.tab.requiredValidationFields` JS filters.
292 *
293 * @since 1.1.0
294 * @param array<int, string> $keys Allowed message keys.
295 */
296 $allowed_keys = apply_filters(
297 'suredonation_validation_message_keys',
298 array_keys( \SureDonation\Inc\Field_Validation::default_validation_messages() )
299 );
300
301 foreach ( $allowed_keys as $key ) {
302 if ( ! is_string( $key ) ) {
303 continue;
304 }
305
306 $value = $request->get_param( $key );
307 // Guard against non-scalar input (array/object) which would make
308 // sanitize_text_field() emit a warning / type error on PHP 8.1+.
309 if ( null !== $value && is_scalar( $value ) ) {
310 $current[ $key ] = sanitize_text_field( (string) $value );
311 }
312 }
313
314 Helper::update_suredonation_option( \SureDonation\Inc\Field_Validation::VALIDATION_MESSAGES_OPTION_KEY, $current );
315
316 return new WP_REST_Response(
317 [
318 'success' => true,
319 'message' => __( 'Form validation settings saved', 'suredonation' ),
320 ],
321 200
322 );
323 }
324
325 /**
326 * Get currency settings for block editor.
327 *
328 * Returns minimal currency data needed for frontend/block previews.
329 *
330 * @param WP_REST_Request $request Request object.
331 * @return WP_REST_Response Response object.
332 * @since 0.0.1
333 */
334 public function get_currency_settings( $request ) {
335 unset( $request ); // Unused parameter.
336
337 $currency = Payment_Helper::get_currency();
338
339 return new WP_REST_Response(
340 [
341 'currency' => $currency,
342 'currencySymbol' => Payment_Helper::get_currency_symbol( $currency ),
343 'isZeroDecimal' => Payment_Helper::is_zero_decimal_currency( $currency ),
344 ],
345 200
346 );
347 }
348
349 /**
350 * Get general settings.
351 *
352 * @param WP_REST_Request $request Request object.
353 * @return WP_REST_Response Response object.
354 * @since 0.0.1
355 */
356 public function get_settings( $request ) {
357 unset( $request ); // Unused parameter.
358
359 $settings = Payment_Helper::get_all_payment_settings();
360
361 return new WP_REST_Response(
362 [
363 'success' => true,
364 'settings' => [
365 'currency' => $settings['currency'] ?? 'USD',
366 'payment_mode' => $settings['payment_mode'] ?? 'test',
367 'currency_sign_position' => Payment_Helper::get_currency_sign_position(),
368 ],
369 ],
370 200
371 );
372 }
373
374 /**
375 * Update general settings.
376 *
377 * @param WP_REST_Request $request Request object.
378 * @return WP_REST_Response|WP_Error Response object.
379 * @since 0.0.1
380 */
381 public function update_settings( $request ) {
382 $params = $request->get_json_params();
383
384 if ( empty( $params ) ) {
385 return new WP_Error(
386 'invalid_settings',
387 __( 'Invalid settings provided', 'suredonation' ),
388 [ 'status' => 400 ]
389 );
390 }
391
392 $current_settings = Payment_Helper::get_all_payment_settings();
393
394 // Update currency if provided.
395 if ( isset( $params['currency'] ) ) {
396 $currency = strtoupper( sanitize_text_field( $params['currency'] ) );
397
398 // Validate currency.
399 $valid_currencies = array_keys( Payment_Helper::get_all_currencies_data() );
400 if ( in_array( $currency, $valid_currencies, true ) ) {
401 $current_settings['currency'] = $currency;
402 }
403 }
404
405 // Update payment mode if provided.
406 if ( isset( $params['payment_mode'] ) ) {
407 $mode = sanitize_text_field( $params['payment_mode'] );
408 if ( in_array( $mode, [ 'test', 'live' ], true ) ) {
409 $current_settings['payment_mode'] = $mode;
410 }
411 }
412
413 // Update currency sign position if provided.
414 if ( isset( $params['currency_sign_position'] ) ) {
415 $position = sanitize_text_field( $params['currency_sign_position'] );
416 if ( in_array( $position, Payment_Helper::ALLOWED_SIGN_POSITIONS, true ) ) {
417 $current_settings['currency_sign_position'] = $position;
418 }
419 }
420
421 $success = Payment_Helper::update_all_payment_settings( $current_settings );
422
423 if ( ! $success ) {
424 return new WP_Error(
425 'update_failed',
426 __( 'Failed to update settings', 'suredonation' ),
427 [ 'status' => 500 ]
428 );
429 }
430
431 return new WP_REST_Response(
432 [
433 'success' => true,
434 'message' => __( 'Settings updated successfully', 'suredonation' ),
435 ],
436 200
437 );
438 }
439
440 /**
441 * Get available currencies.
442 *
443 * @param WP_REST_Request $request Request object.
444 * @return WP_REST_Response Response object.
445 * @since 0.0.1
446 */
447 public function get_currencies( $request ) {
448 unset( $request ); // Unused parameter.
449
450 return new WP_REST_Response(
451 [
452 'success' => true,
453 'currencies' => Payment_Helper::get_currencies_list(),
454 ],
455 200
456 );
457 }
458
459 /**
460 * Get AI settings.
461 *
462 * @param WP_REST_Request $request Request object.
463 * @return WP_REST_Response Response object.
464 * @since 1.0.0
465 */
466 public function get_ai_settings( $request ) {
467 unset( $request ); // Unused parameter.
468
469 $defaults = [
470 'enable_abilities' => false,
471 'allow_updates' => false,
472 'allow_delete' => false,
473 'mcp_server' => false,
474 ];
475 $settings = Helper::get_suredonation_option( self::AI_OPTION_KEY, [] );
476
477 return new WP_REST_Response(
478 [
479 'success' => true,
480 'settings' => wp_parse_args( is_array( $settings ) ? $settings : [], $defaults ),
481 ],
482 200
483 );
484 }
485
486 /**
487 * Update AI settings.
488 *
489 * @param WP_REST_Request $request Request object.
490 * @return WP_REST_Response Response object.
491 * @since 1.0.0
492 */
493 public function update_ai_settings( $request ) {
494 $params = $request->get_json_params();
495 $current = Helper::get_suredonation_option( self::AI_OPTION_KEY, [] );
496
497 if ( ! is_array( $current ) ) {
498 $current = [];
499 }
500
501 $allowed = [ 'enable_abilities', 'allow_updates', 'allow_delete', 'mcp_server' ];
502 foreach ( $allowed as $key ) {
503 if ( isset( $params[ $key ] ) ) {
504 $current[ $key ] = (bool) $params[ $key ];
505 }
506 }
507
508 Helper::update_suredonation_option( self::AI_OPTION_KEY, $current );
509
510 return new WP_REST_Response(
511 [
512 'success' => true,
513 'message' => __( 'AI settings saved', 'suredonation' ),
514 ],
515 200
516 );
517 }
518
519 /**
520 * Get spam protection settings.
521 *
522 * @param WP_REST_Request $request Request object.
523 * @return WP_REST_Response Response object.
524 * @since 1.1.0
525 */
526 public function get_spam_protection_settings( $request ) {
527 unset( $request ); // Unused parameter.
528
529 $defaults = [
530 'honeypot' => false,
531 ];
532 $settings = Helper::get_suredonation_option( self::SPAM_OPTION_KEY, [] );
533
534 return new WP_REST_Response(
535 [
536 'success' => true,
537 'settings' => wp_parse_args( is_array( $settings ) ? $settings : [], $defaults ),
538 ],
539 200
540 );
541 }
542
543 /**
544 * Update spam protection settings.
545 *
546 * @param WP_REST_Request $request Request object.
547 * @return WP_REST_Response Response object.
548 * @since 1.1.0
549 */
550 public function update_spam_protection_settings( $request ) {
551 $current = Helper::get_suredonation_option( self::SPAM_OPTION_KEY, [] );
552
553 if ( ! is_array( $current ) ) {
554 $current = [];
555 }
556
557 // Read each setting via get_param() so the endpoint accepts JSON, body,
558 // or query params (matches the sibling /settings/* update handlers).
559 $allowed = [ 'honeypot' ];
560 foreach ( $allowed as $key ) {
561 $value = $request->get_param( $key );
562 if ( null !== $value ) {
563 $current[ $key ] = (bool) $value;
564 }
565 }
566
567 Helper::update_suredonation_option( self::SPAM_OPTION_KEY, $current );
568
569 return new WP_REST_Response(
570 [
571 'success' => true,
572 'message' => __( 'Spam protection settings saved', 'suredonation' ),
573 ],
574 200
575 );
576 }
577
578 /**
579 * Get miscellaneous settings.
580 *
581 * @param WP_REST_Request $request Request object.
582 * @return WP_REST_Response Response object.
583 * @since 1.0.0
584 */
585 public function get_misc_settings( $request ) {
586 unset( $request ); // Unused parameter.
587
588 return new WP_REST_Response(
589 [
590 'success' => true,
591 'settings' => [
592 // Site option - the BSF Analytics library reads this via
593 // get_site_option(), so the toggle must use the same
594 // scope to stay in sync on multisite.
595 'usage_tracking' => 'yes' === get_site_option( 'suredonation_usage_optin', false ),
596 ],
597 ],
598 200
599 );
600 }
601
602 /**
603 * Update miscellaneous settings.
604 *
605 * Stores the usage-tracking opt-in as the standalone 'yes'/'no'
606 * suredonation_usage_optin option read by the BSF Analytics library.
607 *
608 * @param WP_REST_Request $request Request object.
609 * @return WP_REST_Response Response object.
610 * @since 1.0.0
611 */
612 public function update_misc_settings( $request ) {
613 $usage_tracking = $request->get_param( 'usage_tracking' );
614
615 if ( null !== $usage_tracking ) {
616 if ( $usage_tracking ) {
617 update_site_option( 'suredonation_usage_optin', 'yes' );
618 } else {
619 // Mirror the library's optout() side effects (see
620 // class-bsf-analytics.php) so the cross-product notice
621 // throttle and the send-check transient stay consistent.
622 update_site_option( 'suredonation_usage_optin', 'no' );
623 update_site_option( 'bsf_usage_last_displayed_time', time() );
624 delete_site_transient( 'bsf_usage_track' );
625 }
626 }
627
628 return new WP_REST_Response(
629 [
630 'success' => true,
631 'message' => __( 'Settings saved', 'suredonation' ),
632 ],
633 200
634 );
635 }
636
637 /**
638 * Get donor management settings.
639 *
640 * @param WP_REST_Request $request Request object.
641 * @return WP_REST_Response Response object.
642 * @since 1.0.0
643 */
644 public function get_donor_settings( $request ) {
645 unset( $request ); // Unused parameter.
646
647 $donor_settings = Helper::get_suredonation_option( self::DONOR_OPTION_KEY, [] );
648 if ( ! is_array( $donor_settings ) ) {
649 $donor_settings = [];
650 }
651
652 return new WP_REST_Response(
653 [
654 'success' => true,
655 'settings' => [
656 // Off by default: guest donations never auto-create WP user accounts.
657 'create_wp_user' => ! empty( $donor_settings['create_wp_user'] ),
658 // Off by default: donor comments publish as soon as the donation
659 // completes, matching GiveWP and Charitable out of the box. Turning
660 // it on holds new comments as `pending` for review instead.
661 'hold_donor_comments' => ! empty( $donor_settings['hold_donor_comments'] ),
662 ],
663 ],
664 200
665 );
666 }
667
668 /**
669 * Update donor management settings.
670 *
671 * @param WP_REST_Request $request Request object.
672 * @return WP_REST_Response Response object.
673 * @since 1.0.0
674 */
675 public function update_donor_settings( $request ) {
676 $donor_settings = Helper::get_suredonation_option( self::DONOR_OPTION_KEY, [] );
677 if ( ! is_array( $donor_settings ) ) {
678 $donor_settings = [];
679 }
680
681 // Read each setting via get_param() so the endpoint accepts JSON, body,
682 // or query params (matches the sibling /settings/* update handlers).
683 $changed = false;
684 foreach ( [ 'create_wp_user', 'hold_donor_comments' ] as $key ) {
685 $value = $request->get_param( $key );
686 if ( null !== $value ) {
687 $donor_settings[ $key ] = (bool) $value;
688 $changed = true;
689 }
690 }
691
692 if ( $changed ) {
693 Helper::update_suredonation_option( self::DONOR_OPTION_KEY, $donor_settings );
694 }
695
696 return new WP_REST_Response(
697 [
698 'success' => true,
699 'message' => __( 'Settings saved', 'suredonation' ),
700 ],
701 200
702 );
703 }
704
705 /**
706 * Check if user has permission to manage settings.
707 *
708 * @return bool True if user has permission.
709 * @since 0.0.1
710 */
711 public function check_permissions() {
712 return current_user_can( 'manage_options' );
713 }
714 }
715