PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.6.1
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.6.1
1.6.1 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
← All changes | inc/api/settings-api.php +402 -29 1.0.0 → 1.6.1 View file →
@@ -6,8 +6,9 @@
6 6 */
7 7
8 8 namespace SureDonation\Inc\API;
9 9
10 +use SureDonation\Inc\Emails\Email_Reports;
10 11 use SureDonation\Inc\Helper;
11 12 use SureDonation\Inc\Payments\Payment_Helper;
12 13 use WP_Error;
13 14 use WP_REST_Request;
@@ -39,8 +40,15 @@
39 40 */
40 41 public const AI_OPTION_KEY = 'ai_settings';
41 42
42 43 /**
44 + * Option key for spam protection settings within consolidated options.
45 + *
46 + * @since 1.1.0
47 + */
48 + public const SPAM_OPTION_KEY = 'spam_protection_settings';
49 +
50 + /**
43 51 * Option key for donor management settings within consolidated options.
44 52 *
45 53 * @since 1.0.0
46 54 */
@@ -54,9 +62,9 @@
54 62 */
55 63 public function get_endpoints() {
56 64 return [
57 65 // Get currency data for block editor (public endpoint).
58 - '/settings' => [
66 + '/settings' => [
59 67 'methods' => WP_REST_Server::READABLE,
60 68 'callback' => [ $this, 'get_currency_settings' ],
61 69 'permission_callback' => '__return_true',
62 70 ],
@@ -61,9 +69,9 @@
61 69 'permission_callback' => '__return_true',
62 70 ],
63 71
64 72 // Get and update general settings.
65 - '/settings/general' => [
73 + '/settings/general' => [
66 74 [
67 75 'methods' => WP_REST_Server::READABLE,
68 76 'callback' => [ $this, 'get_settings' ],
69 77 'permission_callback' => [ $this, 'check_permissions' ],
@@ -75,9 +83,9 @@
75 83 ],
76 84 ],
77 85
78 86 // Get available currencies.
79 - '/settings/currencies' => [
87 + '/settings/currencies' => [
80 88 'methods' => WP_REST_Server::READABLE,
81 89 'callback' => [ $this, 'get_currencies' ],
82 90 'permission_callback' => [ $this, 'check_permissions' ],
83 91 ],
@@ -84,9 +92,9 @@
84 92
85 93 // Email notifications are managed per-form via post meta.
86 94 // See inc/form-editor/assets.php for the form-level email system.
87 95 // AI settings.
88 - '/settings/ai' => [
96 + '/settings/ai' => [
89 97 [
90 98 'methods' => WP_REST_Server::READABLE,
91 99 'callback' => [ $this, 'get_ai_settings' ],
92 100 'permission_callback' => [ $this, 'check_permissions' ],
@@ -97,10 +105,24 @@
97 105 'permission_callback' => [ $this, 'check_permissions' ],
98 106 ],
99 107 ],
100 108
109 + // Spam protection settings.
110 + '/settings/spam-protection' => [
111 + [
112 + 'methods' => WP_REST_Server::READABLE,
113 + 'callback' => [ $this, 'get_spam_protection_settings' ],
114 + 'permission_callback' => [ $this, 'check_permissions' ],
115 + ],
116 + [
117 + 'methods' => WP_REST_Server::EDITABLE,
118 + 'callback' => [ $this, 'update_spam_protection_settings' ],
119 + 'permission_callback' => [ $this, 'check_permissions' ],
120 + ],
121 + ],
122 +
101 123 // Miscellaneous settings (usage tracking, etc.).
102 - '/settings/misc' => [
124 + '/settings/misc' => [
103 125 [
104 126 'methods' => WP_REST_Server::READABLE,
105 127 'callback' => [ $this, 'get_misc_settings' ],
106 128 'permission_callback' => [ $this, 'check_permissions' ],
@@ -118,9 +140,9 @@
118 140 ],
119 141 ],
120 142
121 143 // Donor management settings.
122 - '/settings/donor' => [
144 + '/settings/donor' => [
123 145 [
124 146 'methods' => WP_REST_Server::READABLE,
125 147 'callback' => [ $this, 'get_donor_settings' ],
126 148 'permission_callback' => [ $this, 'check_permissions' ],
@@ -129,20 +151,69 @@
129 151 'methods' => WP_REST_Server::EDITABLE,
130 152 'callback' => [ $this, 'update_donor_settings' ],
131 153 'permission_callback' => [ $this, 'check_permissions' ],
132 154 'args' => [
133 - 'create_wp_user' => [
155 + 'create_wp_user' => [
134 156 'type' => 'boolean',
135 157 'sanitize_callback' => 'rest_sanitize_boolean',
136 158 ],
159 + // Registered so the handler's (bool) cast receives a real
160 + // boolean: a form-encoded "false" would otherwise cast to true
161 + // and switch moderation ON when the admin asked for OFF.
162 + 'hold_donor_comments' => [
163 + 'type' => 'boolean',
164 + 'sanitize_callback' => 'rest_sanitize_boolean',
165 + ],
137 166 ],
138 167 ],
139 168 ],
140 169
141 - // Send test email.
142 - '/settings/email/test' => [
170 + // Form validation default messages.
171 + '/settings/validation' => [
172 + [
173 + 'methods' => WP_REST_Server::READABLE,
174 + 'callback' => [ $this, 'get_validation_settings' ],
175 + 'permission_callback' => [ $this, 'check_permissions' ],
176 + ],
177 + [
178 + 'methods' => WP_REST_Server::EDITABLE,
179 + 'callback' => [ $this, 'update_validation_settings' ],
180 + 'permission_callback' => [ $this, 'check_permissions' ],
181 + ],
182 + ],
183 +
184 + // Privacy settings (data retention, consent, privacy/terms fields).
185 + '/settings/privacy' => [
186 + [
187 + 'methods' => WP_REST_Server::READABLE,
188 + 'callback' => [ $this, 'get_privacy_settings' ],
189 + 'permission_callback' => [ $this, 'check_permissions' ],
190 + ],
191 + [
192 + 'methods' => WP_REST_Server::EDITABLE,
193 + 'callback' => [ $this, 'update_privacy_settings' ],
194 + 'permission_callback' => [ $this, 'check_permissions' ],
195 + ],
196 + ],
197 +
198 + // Email Reports (weekly donation digest).
199 + '/settings/email-reports' => [
200 + [
201 + 'methods' => WP_REST_Server::READABLE,
202 + 'callback' => [ $this, 'get_email_reports_settings' ],
203 + 'permission_callback' => [ $this, 'check_permissions' ],
204 + ],
205 + [
206 + 'methods' => WP_REST_Server::EDITABLE,
207 + 'callback' => [ $this, 'update_email_reports_settings' ],
208 + 'permission_callback' => [ $this, 'check_permissions' ],
209 + ],
210 + ],
211 +
212 + // Send this week's report now, to the addresses in the request.
213 + '/settings/email-reports/test' => [
143 214 'methods' => WP_REST_Server::CREATABLE,
144 - 'callback' => [ $this, 'send_test_email' ],
215 + 'callback' => [ $this, 'send_test_email_report' ],
145 216 'permission_callback' => [ $this, 'check_permissions' ],
146 217 ],
147 218 ];
148 219 }
@@ -147,8 +218,238 @@
147 218 ];
148 219 }
149 220
150 221 /**
222 + * Get the Email Reports settings (stored values merged over the defaults).
223 + *
224 + * @param WP_REST_Request $request Request object.
225 + * @return WP_REST_Response
226 + * @since 1.6.1
227 + */
228 + public function get_email_reports_settings( $request ) {
229 + unset( $request ); // Unused parameter.
230 +
231 + return new WP_REST_Response( self::email_reports_payload( Email_Reports::get_settings() ), 200 );
232 + }
233 +
234 + /**
235 + * Update the Email Reports settings and (re)schedule the weekly send.
236 + *
237 + * Turning the report on with no deliverable address is refused with a
238 + * 400 rather than quietly stored as off: the client's address check is
239 + * looser than is_email(), and a success response would leave the screen
240 + * showing the report as on while nothing is scheduled.
241 + *
242 + * @param WP_REST_Request $request Request object.
243 + * @return WP_REST_Response
244 + * @since 1.6.1
245 + */
246 + public function update_email_reports_settings( $request ) {
247 + $params = $request->get_json_params();
248 + $params = is_array( $params ) ? $params : [];
249 +
250 + $wants_on = filter_var( $params['enabled'] ?? false, FILTER_VALIDATE_BOOLEAN );
251 + $sanitized = Email_Reports::sanitize( $params );
252 +
253 + if ( $wants_on && ! $sanitized['enabled'] ) {
254 + return new WP_REST_Response(
255 + [
256 + 'success' => false,
257 + 'code' => 'no_valid_recipient',
258 + 'message' => __( 'Enter at least one valid email address to turn on email reports.', 'suredonation' ),
259 + ],
260 + 400
261 + );
262 + }
263 +
264 + return new WP_REST_Response( self::email_reports_payload( Email_Reports::save( $params ) ), 200 );
265 + }
266 +
267 + /**
268 + * The Email Reports response body: the stored settings plus the schedule
269 + * state, so the screen can show when the next report goes out, or that
270 + * none is queued.
271 + *
272 + * @param array<string, mixed> $settings Stored settings.
273 + * @return array<string, mixed>
274 + * @since 1.6.1
275 + */
276 + private static function email_reports_payload( $settings ) {
277 + $next_run = Email_Reports::next_run();
278 +
279 + return [
280 + 'success' => true,
281 + 'settings' => $settings,
282 + 'next_run' => $next_run,
283 + 'next_run_label' => null === $next_run
284 + ? ''
285 + : Helper::get_string_value( wp_date( Helper::get_string_value( get_option( 'date_format' ) ) . ' ' . Helper::get_string_value( get_option( 'time_format' ) ), $next_run ) ),
286 + ];
287 + }
288 +
289 + /**
290 + * Send this week's report immediately to the addresses in the request.
291 + *
292 + * Reads recipients from the request, not the stored settings, so an admin
293 + * can preview before saving. Sends even when the week has no donations.
294 + *
295 + * @param WP_REST_Request $request Request object.
296 + * @return WP_REST_Response
297 + * @since 1.6.1
298 + */
299 + public function send_test_email_report( $request ) {
300 + $params = $request->get_json_params();
301 + $recipients = Email_Reports::parse_recipients( is_array( $params ) ? ( $params['recipients'] ?? '' ) : '' );
302 +
303 + if ( [] === $recipients ) {
304 + return new WP_REST_Response(
305 + [
306 + 'success' => false,
307 + 'message' => __( 'Enter at least one valid email address.', 'suredonation' ),
308 + ],
309 + 400
310 + );
311 + }
312 +
313 + if ( ! Email_Reports::send_report( $recipients, true ) ) {
314 + return new WP_REST_Response(
315 + [
316 + 'success' => false,
317 + 'message' => __( 'The report could not be sent. Check your site’s email configuration.', 'suredonation' ),
318 + ],
319 + 500
320 + );
321 + }
322 +
323 + return new WP_REST_Response(
324 + [
325 + 'success' => true,
326 + 'message' => __( 'Test report sent.', 'suredonation' ),
327 + ],
328 + 200
329 + );
330 + }
331 +
332 + /**
333 + * Get the Privacy settings (stored values merged over the defaults).
334 + *
335 + * @param WP_REST_Request $request Request object.
336 + * @return WP_REST_Response
337 + * @since 1.2.0
338 + */
339 + public function get_privacy_settings( $request ) {
340 + unset( $request ); // Unused parameter.
341 +
342 + return new WP_REST_Response(
343 + [
344 + 'success' => true,
345 + 'settings' => \SureDonation\Inc\Privacy\Privacy_Settings::get_settings(),
346 + ],
347 + 200
348 + );
349 + }
350 +
351 + /**
352 + * Update the Privacy settings.
353 + *
354 + * @param WP_REST_Request $request Request object.
355 + * @return WP_REST_Response
356 + * @since 1.2.0
357 + */
358 + public function update_privacy_settings( $request ) {
359 + $params = $request->get_json_params();
360 + $sanitized = \SureDonation\Inc\Privacy\Privacy_Settings::sanitize( is_array( $params ) ? $params : [] );
361 +
362 + Helper::update_suredonation_option( \SureDonation\Inc\Privacy\Privacy_Settings::OPTION_KEY, $sanitized );
363 +
364 + return new WP_REST_Response(
365 + [
366 + 'success' => true,
367 + 'settings' => $sanitized,
368 + ],
369 + 200
370 + );
371 + }
372 +
373 + /**
374 + * Get the form-validation default messages.
375 + *
376 + * Returns the stored admin overrides merged over the translatable defaults
377 + * so every configurable message always has a value in the editor.
378 + *
379 + * @param WP_REST_Request $request Request object.
380 + * @return WP_REST_Response
381 + * @since 1.1.0
382 + */
383 + public function get_validation_settings( $request ) {
384 + unset( $request ); // Unused parameter.
385 +
386 + $defaults = \SureDonation\Inc\Field_Validation::default_validation_messages();
387 + $stored = Helper::get_suredonation_option( \SureDonation\Inc\Field_Validation::VALIDATION_MESSAGES_OPTION_KEY, [] );
388 +
389 + return new WP_REST_Response(
390 + [
391 + 'success' => true,
392 + 'settings' => wp_parse_args( is_array( $stored ) ? $stored : [], $defaults ),
393 + ],
394 + 200
395 + );
396 + }
397 +
398 + /**
399 + * Update the form-validation default messages.
400 + *
401 + * @param WP_REST_Request $request Request object.
402 + * @return WP_REST_Response
403 + * @since 1.1.0
404 + */
405 + public function update_validation_settings( $request ) {
406 + $current = Helper::get_suredonation_option( \SureDonation\Inc\Field_Validation::VALIDATION_MESSAGES_OPTION_KEY, [] );
407 +
408 + if ( ! is_array( $current ) ) {
409 + $current = [];
410 + }
411 +
412 + /**
413 + * Filter the list of allowed validation-message keys.
414 + *
415 + * Lets extensions register additional message keys for their own field
416 + * types, mirroring the `suredonation.settings.tab.validationFields` and
417 + * `suredonation.settings.tab.requiredValidationFields` JS filters.
418 + *
419 + * @since 1.1.0
420 + * @param array<int, string> $keys Allowed message keys.
421 + */
422 + $allowed_keys = apply_filters(
423 + 'suredonation_validation_message_keys',
424 + array_keys( \SureDonation\Inc\Field_Validation::default_validation_messages() )
425 + );
426 +
427 + foreach ( $allowed_keys as $key ) {
428 + if ( ! is_string( $key ) ) {
429 + continue;
430 + }
431 +
432 + $value = $request->get_param( $key );
433 + // Guard against non-scalar input (array/object) which would make
434 + // sanitize_text_field() emit a warning / type error on PHP 8.1+.
435 + if ( null !== $value && is_scalar( $value ) ) {
436 + $current[ $key ] = sanitize_text_field( (string) $value );
437 + }
438 + }
439 +
440 + Helper::update_suredonation_option( \SureDonation\Inc\Field_Validation::VALIDATION_MESSAGES_OPTION_KEY, $current );
441 +
442 + return new WP_REST_Response(
443 + [
444 + 'success' => true,
445 + 'message' => __( 'Form validation settings saved', 'suredonation' ),
446 + ],
447 + 200
448 + );
449 + }
450 +
451 + /**
151 452 * Get currency settings for block editor.
152 453 *
153 454 * Returns minimal currency data needed for frontend/block previews.
154 455 *
@@ -186,10 +487,11 @@
186 487 return new WP_REST_Response(
187 488 [
188 489 'success' => true,
189 490 'settings' => [
190 - 'currency' => $settings['currency'] ?? 'USD',
191 - 'payment_mode' => $settings['payment_mode'] ?? 'test',
491 + 'currency' => $settings['currency'] ?? 'USD',
492 + 'payment_mode' => $settings['payment_mode'] ?? 'test',
493 + 'currency_sign_position' => Payment_Helper::get_currency_sign_position(),
192 494 ],
193 495 ],
194 496 200
195 497 );
@@ -233,8 +535,16 @@
233 535 $current_settings['payment_mode'] = $mode;
234 536 }
235 537 }
236 538
539 + // Update currency sign position if provided.
540 + if ( isset( $params['currency_sign_position'] ) ) {
541 + $position = sanitize_text_field( $params['currency_sign_position'] );
542 + if ( in_array( $position, Payment_Helper::ALLOWED_SIGN_POSITIONS, true ) ) {
543 + $current_settings['currency_sign_position'] = $position;
544 + }
545 + }
546 +
237 547 $success = Payment_Helper::update_all_payment_settings( $current_settings );
238 548
239 549 if ( ! $success ) {
240 550 return new WP_Error(
@@ -262,20 +572,12 @@
262 572 */
263 573 public function get_currencies( $request ) {
264 574 unset( $request ); // Unused parameter.
265 575
266 - $currencies_data = Payment_Helper::get_all_currencies_data();
267 -
268 - // Format for frontend: "CODE - Name".
269 - $currencies = [];
270 - foreach ( $currencies_data as $code => $data ) {
271 - $currencies[ $code ] = $code . ' - ' . $data['name'];
272 - }
273 -
274 576 return new WP_REST_Response(
275 577 [
276 578 'success' => true,
277 - 'currencies' => $currencies,
579 + 'currencies' => Payment_Helper::get_currencies_list(),
278 580 ],
279 581 200
280 582 );
281 583 }
@@ -340,8 +642,67 @@
340 642 );
341 643 }
342 644
343 645 /**
646 + * Get spam protection settings.
647 + *
648 + * @param WP_REST_Request $request Request object.
649 + * @return WP_REST_Response Response object.
650 + * @since 1.1.0
651 + */
652 + public function get_spam_protection_settings( $request ) {
653 + unset( $request ); // Unused parameter.
654 +
655 + $defaults = [
656 + 'honeypot' => false,
657 + ];
658 + $settings = Helper::get_suredonation_option( self::SPAM_OPTION_KEY, [] );
659 +
660 + return new WP_REST_Response(
661 + [
662 + 'success' => true,
663 + 'settings' => wp_parse_args( is_array( $settings ) ? $settings : [], $defaults ),
664 + ],
665 + 200
666 + );
667 + }
668 +
669 + /**
670 + * Update spam protection settings.
671 + *
672 + * @param WP_REST_Request $request Request object.
673 + * @return WP_REST_Response Response object.
674 + * @since 1.1.0
675 + */
676 + public function update_spam_protection_settings( $request ) {
677 + $current = Helper::get_suredonation_option( self::SPAM_OPTION_KEY, [] );
678 +
679 + if ( ! is_array( $current ) ) {
680 + $current = [];
681 + }
682 +
683 + // Read each setting via get_param() so the endpoint accepts JSON, body,
684 + // or query params (matches the sibling /settings/* update handlers).
685 + $allowed = [ 'honeypot' ];
686 + foreach ( $allowed as $key ) {
687 + $value = $request->get_param( $key );
688 + if ( null !== $value ) {
689 + $current[ $key ] = (bool) $value;
690 + }
691 + }
692 +
693 + Helper::update_suredonation_option( self::SPAM_OPTION_KEY, $current );
694 +
695 + return new WP_REST_Response(
696 + [
697 + 'success' => true,
698 + 'message' => __( 'Spam protection settings saved', 'suredonation' ),
699 + ],
700 + 200
701 + );
702 + }
703 +
704 + /**
344 705 * Get miscellaneous settings.
345 706 *
346 707 * @param WP_REST_Request $request Request object.
347 708 * @return WP_REST_Response Response object.
@@ -353,9 +714,9 @@
353 714 return new WP_REST_Response(
354 715 [
355 716 'success' => true,
356 717 'settings' => [
357 - // Site option — the BSF Analytics library reads this via
718 + // Site option - the BSF Analytics library reads this via
358 719 // get_site_option(), so the toggle must use the same
359 720 // scope to stay in sync on multisite.
360 721 'usage_tracking' => 'yes' === get_site_option( 'suredonation_usage_optin', false ),
361 722 ],
@@ -418,9 +779,13 @@
418 779 [
419 780 'success' => true,
420 781 'settings' => [
421 782 // Off by default: guest donations never auto-create WP user accounts.
422 - 'create_wp_user' => ! empty( $donor_settings['create_wp_user'] ),
783 + 'create_wp_user' => ! empty( $donor_settings['create_wp_user'] ),
784 + // Off by default: donor comments publish as soon as the donation
785 + // completes, matching GiveWP and Charitable out of the box. Turning
786 + // it on holds new comments as `pending` for review instead.
787 + 'hold_donor_comments' => ! empty( $donor_settings['hold_donor_comments'] ),
423 788 ],
424 789 ],
425 790 200
426 791 );
@@ -433,17 +798,25 @@
433 798 * @return WP_REST_Response Response object.
434 799 * @since 1.0.0
435 800 */
436 801 public function update_donor_settings( $request ) {
437 - $create_wp_user = $request->get_param( 'create_wp_user' );
802 + $donor_settings = Helper::get_suredonation_option( self::DONOR_OPTION_KEY, [] );
803 + if ( ! is_array( $donor_settings ) ) {
804 + $donor_settings = [];
805 + }
438 806
439 - if ( null !== $create_wp_user ) {
440 - $donor_settings = Helper::get_suredonation_option( self::DONOR_OPTION_KEY, [] );
441 - if ( ! is_array( $donor_settings ) ) {
442 - $donor_settings = [];
807 + // Read each setting via get_param() so the endpoint accepts JSON, body,
808 + // or query params (matches the sibling /settings/* update handlers).
809 + $changed = false;
810 + foreach ( [ 'create_wp_user', 'hold_donor_comments' ] as $key ) {
811 + $value = $request->get_param( $key );
812 + if ( null !== $value ) {
813 + $donor_settings[ $key ] = (bool) $value;
814 + $changed = true;
443 815 }
816 + }
444 817
445 - $donor_settings['create_wp_user'] = (bool) $create_wp_user;
818 + if ( $changed ) {
446 819 Helper::update_suredonation_option( self::DONOR_OPTION_KEY, $donor_settings );
447 820 }
448 821
449 822 return new WP_REST_Response(