PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 0.0.1
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v0.0.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
suredonation / inc / api / campaigns-api.php

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

916 lines 24.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Campaigns REST API endpoints.
4 *
5 * @package SureDonation
6 */
7
8 namespace SureDonation\Inc\API;
9
10 use SureDonation\Inc\Helper;
11 use WP_Error;
12 use WP_REST_Request;
13 use WP_REST_Response;
14 use WP_REST_Server;
15
16 // Exit if accessed directly.
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20
21 /**
22 * Campaigns API class.
23 *
24 * @since 0.0.1
25 */
26 class Campaigns_API {
27 /**
28 * Get campaign endpoints.
29 *
30 * @return array<string, mixed>
31 * @since 0.0.1
32 */
33 public function get_endpoints() {
34 return [
35 // Get campaigns list & create campaign.
36 '/campaigns' => [
37 [
38 'methods' => WP_REST_Server::READABLE,
39 'callback' => [ $this, 'get_campaigns' ],
40 'permission_callback' => [ $this, 'check_permissions' ],
41 ],
42 [
43 'methods' => WP_REST_Server::CREATABLE,
44 'callback' => [ $this, 'create_campaign' ],
45 'permission_callback' => [ $this, 'check_permissions' ],
46 'args' => [
47 'title' => [
48 'required' => true,
49 'sanitize_callback' => 'sanitize_text_field',
50 ],
51 'description' => [
52 'sanitize_callback' => 'wp_kses_post',
53 ],
54 'goal_type' => [
55 'default' => 'raised_amount',
56 'enum' => [ 'raised_amount', 'donation_count' ],
57 'sanitize_callback' => 'sanitize_text_field',
58 ],
59 'goal_amount' => [
60 'sanitize_callback' => static function ( $value ) {
61 return '' !== $value ? floatval( $value ) : '';
62 },
63 'validate_callback' => static function ( $value ) {
64 if ( '' === $value ) {
65 return true;
66 }
67 $amount = floatval( $value );
68 return $amount >= 0 && $amount <= 999999999.99;
69 },
70 ],
71 'campaign_status' => [
72 'default' => 'active',
73 'enum' => [ 'active', 'paused', 'completed' ],
74 ],
75 'featured_image' => [
76 'sanitize_callback' => 'absint',
77 ],
78 'email_settings' => [
79 'type' => 'object',
80 'sanitize_callback' => [ $this, 'sanitize_email_settings' ],
81 ],
82 'allow_fees_coverage' => [
83 'type' => 'boolean',
84 'sanitize_callback' => 'rest_sanitize_boolean',
85 ],
86 'require_terms' => [
87 'type' => 'boolean',
88 'sanitize_callback' => 'rest_sanitize_boolean',
89 ],
90 'terms_text' => [
91 'sanitize_callback' => 'sanitize_text_field',
92 ],
93 ],
94 ],
95 ],
96
97 // Update campaign.
98 '/campaigns/(?P<id>\d+)' => [
99 [
100 'methods' => WP_REST_Server::READABLE,
101 'callback' => [ $this, 'get_campaign' ],
102 'permission_callback' => [ $this, 'check_permissions' ],
103 'args' => [
104 'id' => [
105 'required' => true,
106 'validate_callback' => static function ( $param ) {
107 return is_numeric( $param );
108 },
109 ],
110 ],
111 ],
112 [
113 'methods' => WP_REST_Server::EDITABLE,
114 'callback' => [ $this, 'update_campaign' ],
115 'permission_callback' => [ $this, 'check_permissions' ],
116 'args' => [
117 'id' => [
118 'required' => true,
119 'validate_callback' => static function ( $param ) {
120 return is_numeric( $param );
121 },
122 ],
123 'title' => [
124 'sanitize_callback' => 'sanitize_text_field',
125 ],
126 'description' => [
127 'sanitize_callback' => 'wp_kses_post',
128 ],
129 'goal_type' => [
130 'enum' => [ 'raised_amount', 'donation_count' ],
131 'sanitize_callback' => 'sanitize_text_field',
132 ],
133 'goal_amount' => [
134 'sanitize_callback' => static function ( $value ) {
135 return '' !== $value ? floatval( $value ) : '';
136 },
137 'validate_callback' => static function ( $value ) {
138 if ( '' === $value ) {
139 return true;
140 }
141 $amount = floatval( $value );
142 return $amount >= 0 && $amount <= 999999999.99;
143 },
144 ],
145 'campaign_status' => [
146 'enum' => [ 'active', 'paused', 'completed' ],
147 ],
148 'featured_image' => [
149 'sanitize_callback' => 'absint',
150 ],
151 'email_settings' => [
152 'type' => 'object',
153 'sanitize_callback' => [ $this, 'sanitize_email_settings' ],
154 ],
155 'allow_fees_coverage' => [
156 'type' => 'boolean',
157 'sanitize_callback' => 'rest_sanitize_boolean',
158 ],
159 'require_terms' => [
160 'type' => 'boolean',
161 'sanitize_callback' => 'rest_sanitize_boolean',
162 ],
163 'terms_text' => [
164 'sanitize_callback' => 'sanitize_text_field',
165 ],
166 ],
167 ],
168 [
169 'methods' => WP_REST_Server::DELETABLE,
170 'callback' => [ $this, 'delete_campaign' ],
171 'permission_callback' => [ $this, 'check_permissions' ],
172 'args' => [
173 'id' => [
174 'required' => true,
175 'validate_callback' => static function ( $param ) {
176 return is_numeric( $param );
177 },
178 ],
179 ],
180 ],
181 ],
182
183 // Update campaign status.
184 '/campaigns/(?P<id>\d+)/status' => [
185 'methods' => WP_REST_Server::EDITABLE,
186 'callback' => [ $this, 'update_campaign_status' ],
187 'permission_callback' => [ $this, 'check_permissions' ],
188 'args' => [
189 'id' => [
190 'required' => true,
191 'validate_callback' => static function ( $param ) {
192 return is_numeric( $param );
193 },
194 ],
195 'status' => [
196 'required' => true,
197 'enum' => [ 'publish', 'draft', 'trash' ],
198 ],
199 ],
200 ],
201
202 // Duplicate campaign.
203 '/campaigns/(?P<id>\d+)/duplicate' => [
204 'methods' => WP_REST_Server::CREATABLE,
205 'callback' => [ $this, 'duplicate_campaign' ],
206 'permission_callback' => [ $this, 'check_permissions' ],
207 'args' => [
208 'id' => [
209 'required' => true,
210 'validate_callback' => static function ( $param ) {
211 return is_numeric( $param );
212 },
213 ],
214 ],
215 ],
216
217 // Bulk actions.
218 '/campaigns/bulk' => [
219 'methods' => WP_REST_Server::EDITABLE,
220 'callback' => [ $this, 'bulk_action' ],
221 'permission_callback' => [ $this, 'check_permissions' ],
222 'args' => [
223 'action' => [
224 'required' => true,
225 'enum' => [ 'delete', 'publish', 'draft', 'trash' ],
226 ],
227 'ids' => [
228 'required' => true,
229 'validate_callback' => static function ( $param ) {
230 return is_array( $param ) && ! empty( $param );
231 },
232 ],
233 ],
234 ],
235
236 // Get form locations for a campaign.
237 '/campaigns/(?P<id>\d+)/form-locations' => [
238 'methods' => WP_REST_Server::READABLE,
239 'callback' => [ $this, 'get_form_locations' ],
240 'permission_callback' => [ $this, 'check_permissions' ],
241 'args' => [
242 'id' => [
243 'required' => true,
244 'validate_callback' => static function ( $param ) {
245 return is_numeric( $param );
246 },
247 ],
248 ],
249 ],
250 ];
251 }
252
253 /**
254 * Get a single campaign by ID.
255 *
256 * @param WP_REST_Request $request Request object.
257 * @return WP_REST_Response|WP_Error Response object.
258 * @since 0.0.1
259 */
260 public function get_campaign( $request ) {
261 $campaign_id = absint( $request->get_param( 'id' ) );
262
263 // Get the campaign post.
264 $post = get_post( $campaign_id );
265
266 // Check if campaign exists and is of correct type.
267 if ( ! $post || SUREDONATION_POST_TYPE !== $post->post_type ) {
268 return new WP_Error(
269 'campaign_not_found',
270 __( 'Campaign not found.', 'suredonation' ),
271 [ 'status' => 404 ]
272 );
273 }
274
275 // Format and return the campaign data.
276 $campaign = $this->format_campaign( $post );
277
278 return new WP_REST_Response(
279 [
280 'success' => true,
281 'campaign' => $campaign,
282 ],
283 200
284 );
285 }
286
287 /**
288 * Get campaigns list with filters, sorting, and pagination.
289 *
290 * @param WP_REST_Request $request Request object.
291 * @return WP_REST_Response|WP_Error Response object.
292 * @since 0.0.1
293 */
294 public function get_campaigns( $request ) {
295 $page = $request->get_param( 'page' ) ?? 1;
296 $per_page = $request->get_param( 'per_page' ) ?? 20;
297 $search = $request->get_param( 'search' ) ?? '';
298 $status = $request->get_param( 'status' ) ?? 'all';
299 $sort_by = $request->get_param( 'sort_by' ) ?? 'date';
300 $order = $request->get_param( 'order' ) ?? 'desc';
301
302 // Build query args.
303 $args = [
304 'post_type' => SUREDONATION_POST_TYPE,
305 'posts_per_page' => absint( $per_page ),
306 'paged' => absint( $page ),
307 'orderby' => $this->get_orderby_field( $sort_by ),
308 'order' => strtoupper( $order ),
309 ];
310
311 // Add status filter.
312 if ( 'all' !== $status ) {
313 $args['post_status'] = $status;
314 } else {
315 $args['post_status'] = [ 'publish', 'draft' ];
316 }
317
318 // Add search.
319 if ( ! empty( $search ) ) {
320 $args['s'] = sanitize_text_field( $search );
321 }
322
323 // Execute query.
324 $query = new \WP_Query( $args );
325
326 // Format campaigns data.
327 $campaigns = [];
328 if ( $query->have_posts() ) {
329 foreach ( $query->posts as $post ) {
330 $post_obj = $post instanceof \WP_Post ? $post : get_post( $post );
331 if ( $post_obj instanceof \WP_Post ) {
332 $campaigns[] = $this->format_campaign( $post_obj );
333 }
334 }
335 }
336
337 // Prepare response.
338 return new WP_REST_Response(
339 [
340 'campaigns' => $campaigns,
341 'pagination' => [
342 'total' => (int) $query->found_posts,
343 'total_pages' => (int) $query->max_num_pages,
344 'per_page' => (int) $per_page,
345 'current' => (int) $page,
346 ],
347 ]
348 );
349 }
350
351 /**
352 * Create a new campaign.
353 *
354 * @param WP_REST_Request $request Request object.
355 * @return WP_REST_Response|WP_Error Response object.
356 * @since 0.0.1
357 */
358 public function create_campaign( $request ) {
359 $title = $request->get_param( 'title' );
360 $description = $request->get_param( 'description' );
361 $goal_type = $request->get_param( 'goal_type' ) ?? 'raised_amount';
362 $goal_amount = $request->get_param( 'goal_amount' );
363 $campaign_status = $request->get_param( 'campaign_status' ) ?? 'active';
364 $featured_image = $request->get_param( 'featured_image' );
365
366 // Create the campaign post.
367 $post_id = wp_insert_post(
368 [
369 'post_type' => SUREDONATION_POST_TYPE,
370 'post_title' => $title,
371 'post_content' => $description,
372 'post_status' => 'publish',
373 'post_author' => get_current_user_id(),
374 ],
375 true
376 );
377
378 if ( is_wp_error( $post_id ) ) {
379 return new WP_Error(
380 'create_failed',
381 __( 'Failed to create campaign.', 'suredonation' ),
382 [ 'status' => 500 ]
383 );
384 }
385
386 // Save campaign meta as single consolidated meta key.
387 $meta_values = [
388 'goal_type' => $goal_type,
389 'goal_amount' => isset( $goal_amount ) && '' !== $goal_amount ? floatval( $goal_amount ) : 0,
390 'campaign_status' => $campaign_status,
391 ];
392
393 $email_settings = $request->get_param( 'email_settings' );
394 if ( ! empty( $email_settings ) ) {
395 $meta_values['email_settings'] = $email_settings;
396 }
397
398 $allow_fees_coverage = $request->get_param( 'allow_fees_coverage' );
399 if ( ! is_null( $allow_fees_coverage ) ) {
400 $meta_values['allow_fees_coverage'] = (bool) $allow_fees_coverage;
401 }
402
403 $require_terms = $request->get_param( 'require_terms' );
404 if ( ! is_null( $require_terms ) ) {
405 $meta_values['require_terms'] = (bool) $require_terms;
406 }
407
408 $terms_text = $request->get_param( 'terms_text' );
409 if ( ! is_null( $terms_text ) ) {
410 $meta_values['terms_text'] = $terms_text;
411 }
412
413 Helper::update_campaign_meta( $post_id, $meta_values );
414
415 // Set featured image if provided.
416 if ( ! empty( $featured_image ) ) {
417 set_post_thumbnail( $post_id, $featured_image );
418 }
419
420 return new WP_REST_Response(
421 [
422 'success' => true,
423 'message' => __( 'Campaign created successfully.', 'suredonation' ),
424 'campaign' => $this->format_campaign( get_post( $post_id ) ),
425 ],
426 201
427 );
428 }
429
430 /**
431 * Update an existing campaign.
432 *
433 * @param WP_REST_Request $request Request object.
434 * @return WP_REST_Response|WP_Error Response object.
435 * @since 0.0.1
436 */
437 public function update_campaign( $request ) {
438 $campaign_id = $request->get_param( 'id' );
439
440 // Check if campaign exists.
441 $campaign = get_post( $campaign_id );
442 if ( ! $campaign || SUREDONATION_POST_TYPE !== $campaign->post_type ) {
443 return new WP_Error(
444 'campaign_not_found',
445 __( 'Campaign not found.', 'suredonation' ),
446 [ 'status' => 404 ]
447 );
448 }
449
450 // Prepare post data.
451 $post_data = [
452 'ID' => $campaign_id,
453 ];
454
455 $title = $request->get_param( 'title' );
456 $description = $request->get_param( 'description' );
457
458 if ( ! empty( $title ) ) {
459 $post_data['post_title'] = $title;
460 }
461
462 if ( ! empty( $description ) ) {
463 $post_data['post_content'] = $description;
464 }
465
466 // Update post if there are changes.
467 if ( count( $post_data ) > 1 ) {
468 $result = wp_update_post( $post_data, true );
469 if ( is_wp_error( $result ) ) {
470 return new WP_Error(
471 'update_failed',
472 __( 'Failed to update campaign.', 'suredonation' ),
473 [ 'status' => 500 ]
474 );
475 }
476 }
477
478 // Update campaign meta (consolidated single key).
479 $meta_values = [];
480 $featured_image = $request->get_param( 'featured_image' );
481
482 $goal_type = $request->get_param( 'goal_type' );
483 if ( ! is_null( $goal_type ) ) {
484 $meta_values['goal_type'] = $goal_type;
485 }
486
487 $goal_amount = $request->get_param( 'goal_amount' );
488 if ( ! is_null( $goal_amount ) ) {
489 $meta_values['goal_amount'] = '' !== $goal_amount ? floatval( $goal_amount ) : 0;
490 }
491
492 $campaign_status = $request->get_param( 'campaign_status' );
493 if ( ! empty( $campaign_status ) ) {
494 $meta_values['campaign_status'] = $campaign_status;
495 }
496
497 $email_settings = $request->get_param( 'email_settings' );
498 if ( ! is_null( $email_settings ) ) {
499 $meta_values['email_settings'] = $email_settings;
500 }
501
502 $allow_fees_coverage = $request->get_param( 'allow_fees_coverage' );
503 if ( ! is_null( $allow_fees_coverage ) ) {
504 $meta_values['allow_fees_coverage'] = (bool) $allow_fees_coverage;
505 }
506
507 $require_terms = $request->get_param( 'require_terms' );
508 if ( ! is_null( $require_terms ) ) {
509 $meta_values['require_terms'] = (bool) $require_terms;
510 }
511
512 $terms_text = $request->get_param( 'terms_text' );
513 if ( ! is_null( $terms_text ) ) {
514 $meta_values['terms_text'] = $terms_text;
515 }
516
517 if ( ! empty( $meta_values ) ) {
518 Helper::update_campaign_meta( $campaign_id, $meta_values );
519 }
520
521 // Update featured image if provided.
522 if ( ! is_null( $featured_image ) ) {
523 if ( empty( $featured_image ) ) {
524 delete_post_thumbnail( $campaign_id );
525 } else {
526 set_post_thumbnail( $campaign_id, $featured_image );
527 }
528 }
529
530 return new WP_REST_Response(
531 [
532 'success' => true,
533 'message' => __( 'Campaign updated successfully.', 'suredonation' ),
534 'campaign' => $this->format_campaign( get_post( $campaign_id ) ),
535 ],
536 200
537 );
538 }
539
540 /**
541 * Update campaign status.
542 *
543 * @param WP_REST_Request $request Request object.
544 * @return WP_REST_Response|WP_Error Response object.
545 * @since 0.0.1
546 */
547 public function update_campaign_status( $request ) {
548 $campaign_id = absint( $request->get_param( 'id' ) );
549 $status = $request->get_param( 'status' );
550
551 // Verify the post exists and belongs to our post type.
552 $campaign = get_post( $campaign_id );
553 if ( ! $campaign || SUREDONATION_POST_TYPE !== $campaign->post_type ) {
554 return new WP_Error(
555 'campaign_not_found',
556 __( 'Campaign not found.', 'suredonation' ),
557 [ 'status' => 404 ]
558 );
559 }
560
561 $result = wp_update_post(
562 [
563 'ID' => $campaign_id,
564 'post_status' => $status,
565 ],
566 true
567 );
568
569 if ( is_wp_error( $result ) ) {
570 return new WP_Error(
571 'update_failed',
572 __( 'Failed to update campaign status.', 'suredonation' ),
573 [ 'status' => 500 ]
574 );
575 }
576
577 return new WP_REST_Response(
578 [
579 'success' => true,
580 'message' => __( 'Campaign status updated successfully.', 'suredonation' ),
581 ],
582 200
583 );
584 }
585
586 /**
587 * Duplicate campaign.
588 *
589 * @param WP_REST_Request $request Request object.
590 * @return WP_REST_Response|WP_Error Response object.
591 * @since 0.0.1
592 */
593 public function duplicate_campaign( $request ) {
594 $campaign_id = $request->get_param( 'id' );
595
596 // Get original campaign.
597 $original = get_post( $campaign_id );
598 if ( ! $original || SUREDONATION_POST_TYPE !== $original->post_type ) {
599 return new WP_Error(
600 'campaign_not_found',
601 __( 'Campaign not found.', 'suredonation' ),
602 [ 'status' => 404 ]
603 );
604 }
605
606 // Create duplicate.
607 $duplicate_id = wp_insert_post(
608 [
609 'post_type' => $original->post_type,
610 'post_title' => $original->post_title . ' (Copy)',
611 'post_content' => $original->post_content,
612 'post_status' => 'draft',
613 'post_author' => get_current_user_id(),
614 ],
615 true
616 );
617
618 if ( is_wp_error( $duplicate_id ) ) {
619 return new WP_Error(
620 'duplicate_failed',
621 __( 'Failed to duplicate campaign.', 'suredonation' ),
622 [ 'status' => 500 ]
623 );
624 }
625
626 // Copy consolidated campaign meta.
627 $meta = get_post_meta( $campaign_id, Helper::SUREDONATION_CAMPAIGN_META_KEY, true );
628 if ( ! empty( $meta ) ) {
629 update_post_meta( $duplicate_id, Helper::SUREDONATION_CAMPAIGN_META_KEY, $meta );
630 }
631
632 return new WP_REST_Response(
633 [
634 'success' => true,
635 'message' => __( 'Campaign duplicated successfully.', 'suredonation' ),
636 'campaign' => $this->format_campaign( get_post( $duplicate_id ) ),
637 ],
638 201
639 );
640 }
641
642 /**
643 * Delete campaign.
644 *
645 * @param WP_REST_Request $request Request object.
646 * @return WP_REST_Response|WP_Error Response object.
647 * @since 0.0.1
648 */
649 public function delete_campaign( $request ) {
650 $campaign_id = $request->get_param( 'id' );
651
652 $campaign = get_post( $campaign_id );
653
654 if ( ! $campaign || SUREDONATION_POST_TYPE !== $campaign->post_type ) {
655 return new WP_Error(
656 'invalid_campaign',
657 __( 'Invalid campaign ID.', 'suredonation' ),
658 [ 'status' => 404 ]
659 );
660 }
661
662 $result = wp_delete_post( $campaign_id, true );
663
664 if ( ! $result ) {
665 return new WP_Error(
666 'delete_failed',
667 __( 'Failed to delete campaign.', 'suredonation' ),
668 [ 'status' => 500 ]
669 );
670 }
671
672 return new WP_REST_Response(
673 [
674 'success' => true,
675 'message' => __( 'Campaign deleted successfully.', 'suredonation' ),
676 ],
677 200
678 );
679 }
680
681 /**
682 * Bulk action on campaigns.
683 *
684 * @param WP_REST_Request $request Request object.
685 * @return WP_REST_Response|WP_Error Response object.
686 * @since 0.0.1
687 */
688 public function bulk_action( $request ) {
689 $action = $request->get_param( 'action' );
690 $ids = $request->get_param( 'ids' );
691
692 $success_count = 0;
693 $error_count = 0;
694
695 foreach ( $ids as $id ) {
696 $id = absint( $id );
697
698 // Verify each post belongs to our post type before operating on it.
699 $post = get_post( $id );
700 if ( ! $post || SUREDONATION_POST_TYPE !== $post->post_type ) {
701 ++$error_count;
702 continue;
703 }
704
705 if ( 'delete' === $action ) {
706 $result = wp_delete_post( $id, true );
707 if ( $result ) {
708 ++$success_count;
709 } else {
710 ++$error_count;
711 }
712 } else {
713 $result = wp_update_post(
714 [
715 'ID' => $id,
716 'post_status' => $action,
717 ],
718 true
719 );
720 if ( ! is_wp_error( $result ) ) {
721 ++$success_count;
722 } else {
723 ++$error_count;
724 }
725 }
726 }
727
728 return new WP_REST_Response(
729 [
730 'success' => true,
731 'message' => sprintf(
732 // translators: %1$d: success count, %2$d: error count.
733 __( 'Bulk action completed. Success: %1$d, Failed: %2$d', 'suredonation' ),
734 $success_count,
735 $error_count
736 ),
737 'success_count' => $success_count,
738 'error_count' => $error_count,
739 ],
740 200
741 );
742 }
743
744 /**
745 * Sanitize email settings.
746 *
747 * @param array<string, mixed> $settings Email settings to sanitize.
748 * @return array<string, mixed> Sanitized email settings.
749 * @since 0.0.1
750 */
751 public function sanitize_email_settings( $settings ) {
752 if ( empty( $settings ) || ! is_array( $settings ) ) {
753 return [];
754 }
755
756 $sanitized = [];
757
758 // Sanitize boolean.
759 $sanitized['enabled'] = ! empty( $settings['enabled'] );
760
761 // Sanitize text fields.
762 if ( isset( $settings['subject'] ) ) {
763 $sanitized['subject'] = sanitize_text_field( Helper::get_string_value( $settings['subject'] ) );
764 }
765
766 if ( isset( $settings['from_name'] ) ) {
767 $sanitized['from_name'] = sanitize_text_field( Helper::get_string_value( $settings['from_name'] ) );
768 }
769
770 // Sanitize email fields.
771 if ( isset( $settings['from_email'] ) ) {
772 $sanitized['from_email'] = sanitize_email( Helper::get_string_value( $settings['from_email'] ) );
773 }
774
775 if ( isset( $settings['reply_to'] ) ) {
776 $sanitized['reply_to'] = sanitize_email( Helper::get_string_value( $settings['reply_to'] ) );
777 }
778
779 // Sanitize email body (allow HTML).
780 if ( isset( $settings['email_body'] ) ) {
781 $sanitized['email_body'] = wp_kses_post( Helper::get_string_value( $settings['email_body'] ) );
782 }
783
784 return $sanitized;
785 }
786
787 /**
788 * Get pages/posts where donation form block is used for this campaign.
789 *
790 * @param WP_REST_Request $request Request object.
791 * @return WP_REST_Response|WP_Error Response object.
792 * @since 0.0.1
793 */
794 public function get_form_locations( $request ) {
795 $campaign_id = absint( $request->get_param( 'id' ) );
796
797 // Check if campaign exists.
798 $campaign = get_post( $campaign_id );
799 if ( ! $campaign || SUREDONATION_POST_TYPE !== $campaign->post_type ) {
800 return new WP_Error(
801 'campaign_not_found',
802 __( 'Campaign not found.', 'suredonation' ),
803 [ 'status' => 404 ]
804 );
805 }
806
807 global $wpdb;
808
809 // Search for posts containing the donation form block for this campaign.
810 // The block stores campaignId in its attributes like: "campaignId":123.
811 $search_pattern = '%"campaignId":' . $campaign_id . '%';
812
813 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
814 $posts = $wpdb->get_results(
815 $wpdb->prepare(
816 "SELECT ID, post_title, post_type, post_status, post_modified
817 FROM %i
818 WHERE post_content LIKE %s
819 AND post_status IN ('publish', 'draft', 'pending', 'private')
820 AND post_type IN ('page', 'post')
821 ORDER BY post_modified DESC",
822 $wpdb->posts,
823 $search_pattern
824 )
825 );
826
827 $locations = [];
828 if ( $posts ) {
829 foreach ( $posts as $post ) {
830 $edit_url = admin_url( 'post.php?post=' . $post->ID . '&action=edit' );
831 $view_url = get_permalink( $post->ID );
832
833 $locations[] = [
834 'id' => (int) $post->ID,
835 'title' => $post->post_title,
836 'type' => $post->post_type,
837 'status' => $post->post_status,
838 'modified_at' => $post->post_modified,
839 'edit_url' => $edit_url,
840 'view_url' => 'publish' === $post->post_status ? $view_url : null,
841 ];
842 }
843 }
844
845 return new WP_REST_Response(
846 [
847 'success' => true,
848 'locations' => $locations,
849 ],
850 200
851 );
852 }
853
854 /**
855 * Check if user has permission to manage campaigns.
856 *
857 * @return bool True if user has permission.
858 * @since 0.0.1
859 */
860 public function check_permissions() {
861 return current_user_can( 'manage_options' );
862 }
863
864 /**
865 * Format campaign data for API response.
866 *
867 * @param \WP_Post|null $post Post object.
868 * @return array<string,mixed> Formatted campaign data.
869 * @since 0.0.1
870 */
871 private function format_campaign( $post ) {
872 if ( ! $post instanceof \WP_Post ) {
873 return [];
874 }
875 // Get real-time stats from donations database table.
876 $stats = \SureDonation\Inc\Campaigns\Campaign_Stats::get_stats( $post->ID );
877 $meta = Helper::get_campaign_meta( $post->ID );
878
879 return [
880 'id' => $post->ID,
881 'title' => $post->post_title,
882 'description' => $post->post_content,
883 'status' => $stats['campaign_status'],
884 'goal_type' => $meta['goal_type'],
885 'goal' => $stats['goal_amount'],
886 'raised' => $stats['total_raised'],
887 'donors' => $stats['donor_count'],
888 'progress' => $stats['progress_percentage'],
889 'email_settings' => $meta['email_settings'],
890 'allow_fees_coverage' => $meta['allow_fees_coverage'],
891 'require_terms' => $meta['require_terms'],
892 'terms_text' => $meta['terms_text'],
893 'created_at' => $post->post_date,
894 'modified_at' => $post->post_modified,
895 'author' => get_the_author_meta( 'display_name', (int) $post->post_author ),
896 ];
897 }
898
899 /**
900 * Get orderby field based on sort parameter.
901 *
902 * @param string $sort_by Sort parameter.
903 * @return string WordPress orderby field.
904 * @since 0.0.1
905 */
906 private function get_orderby_field( $sort_by ) {
907 $orderby_map = [
908 'date' => 'date',
909 'title' => 'title',
910 'status' => 'post_status',
911 ];
912
913 return $orderby_map[ $sort_by ] ?? 'date';
914 }
915 }
916