PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.1.0
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.1.0
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/campaigns-api.php +135 -70 0.0.1 → 1.1.0 View file →
@@ -43,21 +43,21 @@
43 43 'methods' => WP_REST_Server::CREATABLE,
44 44 'callback' => [ $this, 'create_campaign' ],
45 45 'permission_callback' => [ $this, 'check_permissions' ],
46 46 'args' => [
47 - 'title' => [
47 + 'title' => [
48 48 'required' => true,
49 49 'sanitize_callback' => 'sanitize_text_field',
50 50 ],
51 - 'description' => [
51 + 'description' => [
52 52 'sanitize_callback' => 'wp_kses_post',
53 53 ],
54 - 'goal_type' => [
54 + 'goal_type' => [
55 55 'default' => 'raised_amount',
56 56 'enum' => [ 'raised_amount', 'donation_count' ],
57 57 'sanitize_callback' => 'sanitize_text_field',
58 58 ],
59 - 'goal_amount' => [
59 + 'goal_amount' => [
60 60 'sanitize_callback' => static function ( $value ) {
61 61 return '' !== $value ? floatval( $value ) : '';
62 62 },
63 63 'validate_callback' => static function ( $value ) {
@@ -67,28 +67,24 @@
67 67 $amount = floatval( $value );
68 68 return $amount >= 0 && $amount <= 999999999.99;
69 69 },
70 70 ],
71 - 'campaign_status' => [
71 + 'campaign_status' => [
72 72 'default' => 'active',
73 73 'enum' => [ 'active', 'paused', 'completed' ],
74 74 ],
75 - 'featured_image' => [
75 + 'featured_image' => [
76 76 'sanitize_callback' => 'absint',
77 77 ],
78 - 'email_settings' => [
78 + 'email_settings' => [
79 79 'type' => 'object',
80 80 'sanitize_callback' => [ $this, 'sanitize_email_settings' ],
81 81 ],
82 - 'allow_fees_coverage' => [
82 + 'require_terms' => [
83 83 'type' => 'boolean',
84 84 'sanitize_callback' => 'rest_sanitize_boolean',
85 85 ],
86 - 'require_terms' => [
87 - 'type' => 'boolean',
88 - 'sanitize_callback' => 'rest_sanitize_boolean',
89 - ],
90 - 'terms_text' => [
86 + 'terms_text' => [
91 87 'sanitize_callback' => 'sanitize_text_field',
92 88 ],
93 89 ],
94 90 ],
@@ -113,25 +109,25 @@
113 109 'methods' => WP_REST_Server::EDITABLE,
114 110 'callback' => [ $this, 'update_campaign' ],
115 111 'permission_callback' => [ $this, 'check_permissions' ],
116 112 'args' => [
117 - 'id' => [
113 + 'id' => [
118 114 'required' => true,
119 115 'validate_callback' => static function ( $param ) {
120 116 return is_numeric( $param );
121 117 },
122 118 ],
123 - 'title' => [
119 + 'title' => [
124 120 'sanitize_callback' => 'sanitize_text_field',
125 121 ],
126 - 'description' => [
122 + 'description' => [
127 123 'sanitize_callback' => 'wp_kses_post',
128 124 ],
129 - 'goal_type' => [
125 + 'goal_type' => [
130 126 'enum' => [ 'raised_amount', 'donation_count' ],
131 127 'sanitize_callback' => 'sanitize_text_field',
132 128 ],
133 - 'goal_amount' => [
129 + 'goal_amount' => [
134 130 'sanitize_callback' => static function ( $value ) {
135 131 return '' !== $value ? floatval( $value ) : '';
136 132 },
137 133 'validate_callback' => static function ( $value ) {
@@ -141,27 +137,23 @@
141 137 $amount = floatval( $value );
142 138 return $amount >= 0 && $amount <= 999999999.99;
143 139 },
144 140 ],
145 - 'campaign_status' => [
141 + 'campaign_status' => [
146 142 'enum' => [ 'active', 'paused', 'completed' ],
147 143 ],
148 - 'featured_image' => [
144 + 'featured_image' => [
149 145 'sanitize_callback' => 'absint',
150 146 ],
151 - 'email_settings' => [
147 + 'email_settings' => [
152 148 'type' => 'object',
153 149 'sanitize_callback' => [ $this, 'sanitize_email_settings' ],
154 150 ],
155 - 'allow_fees_coverage' => [
151 + 'require_terms' => [
156 152 'type' => 'boolean',
157 153 'sanitize_callback' => 'rest_sanitize_boolean',
158 154 ],
159 - 'require_terms' => [
160 - 'type' => 'boolean',
161 - 'sanitize_callback' => 'rest_sanitize_boolean',
162 - ],
163 - 'terms_text' => [
155 + 'terms_text' => [
164 156 'sanitize_callback' => 'sanitize_text_field',
165 157 ],
166 158 ],
167 159 ],
@@ -246,8 +238,23 @@
246 238 },
247 239 ],
248 240 ],
249 241 ],
242 +
243 + // Create (seed) the campaign page layout.
244 + '/campaigns/(?P<id>\d+)/page' => [
245 + 'methods' => WP_REST_Server::CREATABLE,
246 + 'callback' => [ $this, 'create_campaign_page' ],
247 + 'permission_callback' => [ $this, 'check_permissions' ],
248 + 'args' => [
249 + 'id' => [
250 + 'required' => true,
251 + 'validate_callback' => static function ( $param ) {
252 + return is_numeric( $param );
253 + },
254 + ],
255 + ],
256 + ],
250 257 ];
251 258 }
252 259
253 260 /**
@@ -284,8 +291,45 @@
284 291 );
285 292 }
286 293
287 294 /**
295 + * Create (seed) the default campaign page layout for a campaign.
296 + *
297 + * Idempotent: seeding only runs when the campaign has no page content yet, so
298 + * an existing, user-edited page is never overwritten. Returns the campaign in
299 + * its post-seed state so the admin can flip the button to "View Campaign".
300 + *
301 + * @param WP_REST_Request $request Request object.
302 + * @return WP_REST_Response|WP_Error Response object.
303 + * @since 1.0.0
304 + */
305 + public function create_campaign_page( $request ) {
306 + $campaign_id = absint( $request->get_param( 'id' ) );
307 + $post = get_post( $campaign_id );
308 +
309 + if ( ! $post || SUREDONATION_POST_TYPE !== $post->post_type ) {
310 + return new WP_Error(
311 + 'campaign_not_found',
312 + __( 'Campaign not found.', 'suredonation' ),
313 + [ 'status' => 404 ]
314 + );
315 + }
316 +
317 + \SureDonation\Inc\Campaigns\Campaign_Page::seed_if_empty( $campaign_id );
318 +
319 + // Refresh the post so the response reflects the seeded content.
320 + $post = get_post( $campaign_id );
321 +
322 + return new WP_REST_Response(
323 + [
324 + 'success' => true,
325 + 'campaign' => $this->format_campaign( $post ),
326 + ],
327 + 200
328 + );
329 + }
330 +
331 + /**
288 332 * Get campaigns list with filters, sorting, and pagination.
289 333 *
290 334 * @param WP_REST_Request $request Request object.
291 335 * @return WP_REST_Response|WP_Error Response object.
@@ -292,25 +336,42 @@
292 336 * @since 0.0.1
293 337 */
294 338 public function get_campaigns( $request ) {
295 339 $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';
340 + $per_page = (int) ( $request->get_param( 'per_page' ) ?? 20 );
341 + $per_page = -1 === $per_page ? -1 : absint( $per_page );
342 + if ( 0 === $per_page ) {
343 + $per_page = 20;
344 + }
345 + $search = $request->get_param( 'search' ) ?? '';
346 + $status = $request->get_param( 'status' ) ?? 'all';
347 + $sort_by = $request->get_param( 'sort_by' ) ?? 'date';
348 + $order = $request->get_param( 'order' ) ?? 'desc';
301 349
302 350 // Build query args.
303 351 $args = [
304 352 'post_type' => SUREDONATION_POST_TYPE,
305 - 'posts_per_page' => absint( $per_page ),
306 - 'paged' => absint( $page ),
353 + 'posts_per_page' => $per_page,
354 + 'paged' => -1 === $per_page ? 1 : absint( $page ),
307 355 'orderby' => $this->get_orderby_field( $sort_by ),
308 356 'order' => strtoupper( $order ),
309 357 ];
310 358
311 359 // Add status filter.
312 - if ( 'all' !== $status ) {
360 + if ( 'paused' === $status ) {
361 + // "Paused" is a campaign business status stored inside the campaign
362 + // meta JSON (not a WP post status), so match published campaigns whose
363 + // meta marks them paused.
364 + $args['post_status'] = 'publish';
365 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Admin-only campaign list filter.
366 + $args['meta_query'] = [
367 + [
368 + 'key' => Helper::SUREDONATION_CAMPAIGN_META_KEY,
369 + 'value' => '"campaign_status":"paused"',
370 + 'compare' => 'LIKE',
371 + ],
372 + ];
373 + } elseif ( 'all' !== $status ) {
313 374 $args['post_status'] = $status;
314 375 } else {
315 376 $args['post_status'] = [ 'publish', 'draft' ];
316 377 }
@@ -362,14 +423,15 @@
362 423 $goal_amount = $request->get_param( 'goal_amount' );
363 424 $campaign_status = $request->get_param( 'campaign_status' ) ?? 'active';
364 425 $featured_image = $request->get_param( 'featured_image' );
365 426
366 - // Create the campaign post.
427 + // Create the campaign post. The description is stored as the excerpt so
428 + // post_content stays reserved for the campaign page layout.
367 429 $post_id = wp_insert_post(
368 430 [
369 431 'post_type' => SUREDONATION_POST_TYPE,
370 432 'post_title' => $title,
371 - 'post_content' => $description,
433 + 'post_excerpt' => $description,
372 434 'post_status' => 'publish',
373 435 'post_author' => get_current_user_id(),
374 436 ],
375 437 true
@@ -394,13 +456,8 @@
394 456 if ( ! empty( $email_settings ) ) {
395 457 $meta_values['email_settings'] = $email_settings;
396 458 }
397 459
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 460 $require_terms = $request->get_param( 'require_terms' );
404 461 if ( ! is_null( $require_terms ) ) {
405 462 $meta_values['require_terms'] = (bool) $require_terms;
406 463 }
@@ -458,10 +515,12 @@
458 515 if ( ! empty( $title ) ) {
459 516 $post_data['post_title'] = $title;
460 517 }
461 518
462 - if ( ! empty( $description ) ) {
463 - $post_data['post_content'] = $description;
519 + // Use is_null (not empty) so an empty string can clear the description;
520 + // it feeds the seeded campaign page paragraph.
521 + if ( ! is_null( $description ) ) {
522 + $post_data['post_excerpt'] = $description;
464 523 }
465 524
466 525 // Update post if there are changes.
467 526 if ( count( $post_data ) > 1 ) {
@@ -498,13 +557,8 @@
498 557 if ( ! is_null( $email_settings ) ) {
499 558 $meta_values['email_settings'] = $email_settings;
500 559 }
501 560
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 561 $require_terms = $request->get_param( 'require_terms' );
508 562 if ( ! is_null( $require_terms ) ) {
509 563 $meta_values['require_terms'] = (bool) $require_terms;
510 564 }
@@ -602,14 +656,18 @@
602 656 [ 'status' => 404 ]
603 657 );
604 658 }
605 659
606 - // Create duplicate.
660 + // Create duplicate. The page (post_content) is intentionally NOT copied:
661 + // its blocks carry the original campaign's id, so a fresh page is seeded
662 + // for the duplicate on first publish (or via the Create Campaign Page CTA),
663 + // guaranteeing the correct id and its own default form. The description
664 + // (post_excerpt) is carried over.
607 665 $duplicate_id = wp_insert_post(
608 666 [
609 667 'post_type' => $original->post_type,
610 668 'post_title' => $original->post_title . ' (Copy)',
611 - 'post_content' => $original->post_content,
669 + 'post_excerpt' => $original->post_excerpt,
612 670 'post_status' => 'draft',
613 671 'post_author' => get_current_user_id(),
614 672 ],
615 673 true
@@ -816,9 +874,9 @@
816 874 "SELECT ID, post_title, post_type, post_status, post_modified
817 875 FROM %i
818 876 WHERE post_content LIKE %s
819 877 AND post_status IN ('publish', 'draft', 'pending', 'private')
820 - AND post_type IN ('page', 'post')
878 + AND post_type IN ('page', 'post', 'suredonation_cmpgn')
821 879 ORDER BY post_modified DESC",
822 880 $wpdb->posts,
823 881 $search_pattern
824 882 )
@@ -872,28 +930,35 @@
872 930 if ( ! $post instanceof \WP_Post ) {
873 931 return [];
874 932 }
875 933 // 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 );
934 + $stats = \SureDonation\Inc\Campaigns\Campaign_Stats::get_stats( $post->ID );
935 + $meta = Helper::get_campaign_meta( $post->ID );
936 + $thumbnail_url = get_the_post_thumbnail_url( $post->ID, 'medium' );
878 937
879 938 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 ),
939 + 'id' => $post->ID,
940 + 'title' => $post->post_title,
941 + 'description' => $post->post_excerpt,
942 + 'status' => $stats['campaign_status'],
943 + 'goal_type' => $meta['goal_type'],
944 + 'goal' => $stats['goal_amount'],
945 + 'raised' => $stats['total_raised'],
946 + 'donors' => $stats['donor_count'],
947 + 'donations' => $stats['donation_count'],
948 + 'donation_count' => $stats['donation_count'],
949 + 'progress' => $stats['progress_percentage'],
950 + 'email_settings' => $meta['email_settings'],
951 + 'require_terms' => $meta['require_terms'],
952 + 'terms_text' => $meta['terms_text'],
953 + 'created_at' => $post->post_date,
954 + 'modified_at' => $post->post_modified,
955 + 'author' => get_the_author_meta( 'display_name', (int) $post->post_author ),
956 + 'has_page' => \SureDonation\Inc\Campaigns\Campaign_Page::has_page( $post->ID ),
957 + 'permalink' => 'publish' === $post->post_status ? get_permalink( $post->ID ) : null,
958 + 'edit_link' => admin_url( 'post.php?post=' . $post->ID . '&action=edit' ),
959 + 'featured_image' => (int) get_post_thumbnail_id( $post->ID ),
960 + 'featured_image_url' => $thumbnail_url ? $thumbnail_url : '',
896 961 ];
897 962 }
898 963
899 964 /**