PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / trunk
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management vtrunk
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 / forms-api.php

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

503 lines 13.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Forms REST API endpoints.
4 *
5 * @package SureDonation
6 */
7
8 namespace SureDonation\Inc\API;
9
10 use SureDonation\Inc\Campaigns\Campaign_Cpt;
11 use SureDonation\Inc\Duplicate_Form;
12 use SureDonation\Inc\Post_Types\Donation_Form;
13 use WP_Error;
14 use WP_REST_Request;
15 use WP_REST_Response;
16 use WP_REST_Server;
17
18 // Exit if accessed directly.
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit;
21 }
22
23 /**
24 * Forms API class.
25 *
26 * @since 0.0.1
27 */
28 class Forms_API {
29 /**
30 * Get forms endpoints.
31 *
32 * @return array<string, mixed>
33 * @since 0.0.1
34 */
35 public function get_endpoints() {
36 return [
37 // Get forms list.
38 '/forms' => [
39 [
40 'methods' => WP_REST_Server::READABLE,
41 'callback' => [ $this, 'get_forms' ],
42 'permission_callback' => [ $this, 'check_permissions' ],
43 'args' => [
44 'campaign_id' => [
45 'type' => 'integer',
46 'sanitize_callback' => 'absint',
47 ],
48 'per_page' => [
49 'type' => 'integer',
50 'default' => 100,
51 'sanitize_callback' => 'absint',
52 ],
53 'status' => [
54 'type' => 'string',
55 'default' => 'any',
56 'sanitize_callback' => 'sanitize_text_field',
57 ],
58 ],
59 ],
60 ],
61
62 // Manage form lifecycle (trash/restore/delete).
63 '/forms/manage' => [
64 [
65 'methods' => WP_REST_Server::CREATABLE,
66 'callback' => [ $this, 'manage_form_lifecycle' ],
67 'permission_callback' => [ $this, 'check_permissions' ],
68 'args' => [
69 'form_ids' => [
70 'required' => true,
71 'type' => [ 'array', 'integer' ],
72 'sanitize_callback' => static function ( $value ) {
73 if ( is_array( $value ) ) {
74 return array_map( 'intval', $value );
75 }
76 return [ intval( $value ) ];
77 },
78 ],
79 'action' => [
80 'required' => true,
81 'type' => 'string',
82 'enum' => [ 'trash', 'restore', 'delete' ],
83 'sanitize_callback' => 'sanitize_text_field',
84 ],
85 ],
86 ],
87 ],
88
89 // Set the campaign's default form.
90 '/forms/set-default' => [
91 [
92 'methods' => WP_REST_Server::CREATABLE,
93 'callback' => [ $this, 'set_default_form' ],
94 'permission_callback' => [ $this, 'check_permissions' ],
95 'args' => [
96 'form_id' => [
97 'required' => true,
98 'type' => 'integer',
99 'sanitize_callback' => 'absint',
100 ],
101 'campaign_id' => [
102 'required' => true,
103 'type' => 'integer',
104 'sanitize_callback' => 'absint',
105 ],
106 ],
107 ],
108 ],
109
110 // Duplicate a form.
111 '/forms/duplicate' => [
112 [
113 'methods' => WP_REST_Server::CREATABLE,
114 'callback' => [ Duplicate_Form::get_instance(), 'handle_duplicate_form_rest' ],
115 'permission_callback' => [ $this, 'check_permissions' ],
116 'args' => [
117 'form_id' => [
118 'required' => true,
119 'type' => 'integer',
120 'sanitize_callback' => 'absint',
121 'validate_callback' => static function ( $value ) {
122 return $value > 0;
123 },
124 ],
125 'title_suffix' => [
126 'required' => false,
127 'type' => 'string',
128 'default' => __( ' (Copy)', 'suredonation' ),
129 'sanitize_callback' => 'sanitize_text_field',
130 ],
131 ],
132 ],
133 ],
134
135 // Get/update single form.
136 '/forms/(?P<id>\d+)' => [
137 [
138 'methods' => WP_REST_Server::READABLE,
139 'callback' => [ $this, 'get_form' ],
140 'permission_callback' => [ $this, 'check_permissions' ],
141 'args' => [
142 'id' => [
143 'required' => true,
144 'validate_callback' => static function ( $param ) {
145 return is_numeric( $param );
146 },
147 ],
148 ],
149 ],
150 [
151 'methods' => WP_REST_Server::EDITABLE,
152 'callback' => [ $this, 'update_form' ],
153 'permission_callback' => [ $this, 'check_permissions' ],
154 'args' => [
155 'id' => [
156 'required' => true,
157 'validate_callback' => static function ( $param ) {
158 return is_numeric( $param );
159 },
160 ],
161 'campaign_id' => [
162 'type' => 'integer',
163 'sanitize_callback' => 'absint',
164 ],
165 ],
166 ],
167 ],
168 ];
169 }
170
171 /**
172 * Get forms list.
173 *
174 * @param WP_REST_Request $request Request object.
175 * @return WP_REST_Response|WP_Error Response object.
176 * @since 0.0.1
177 */
178 public function get_forms( $request ) {
179 $campaign_id = $request->get_param( 'campaign_id' );
180 $per_page = $request->get_param( 'per_page' ) ?? 100;
181 $status = $request->get_param( 'status' ) ?? 'any';
182
183 // Map status parameter to post_status values.
184 $post_status = 'any' === $status ? [ 'publish', 'draft', 'trash' ] : $status;
185
186 $args = [
187 'posts_per_page' => $per_page,
188 'post_status' => $post_status,
189 ];
190
191 // Filter by campaign if provided.
192 if ( $campaign_id ) {
193 $args['meta_query'] = [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
194 [
195 'key' => Donation_Form::META_CAMPAIGN_ID,
196 'value' => $campaign_id,
197 'compare' => '=',
198 'type' => 'NUMERIC',
199 ],
200 ];
201 }
202
203 $forms = Donation_Form::get_forms( $args );
204
205 // Flag the campaign's default form so the UI can mark it.
206 $default_form_id = $campaign_id ? Campaign_Cpt::get_default_form_id( $campaign_id ) : 0;
207
208 $formatted_forms = [];
209 foreach ( $forms as $form ) {
210 $formatted = $this->format_form( $form );
211 $formatted['is_default'] = ( (int) $form->ID === $default_form_id );
212 $formatted_forms[] = $formatted;
213 }
214
215 return new WP_REST_Response(
216 [
217 'success' => true,
218 'forms' => $formatted_forms,
219 ],
220 200
221 );
222 }
223
224 /**
225 * Set the default form for a campaign.
226 *
227 * @param WP_REST_Request $request Request object.
228 * @return WP_REST_Response|WP_Error Response object.
229 * @since 1.0.0
230 */
231 public function set_default_form( $request ) {
232 $form_id = absint( $request->get_param( 'form_id' ) );
233 $campaign_id = absint( $request->get_param( 'campaign_id' ) );
234
235 $campaign = get_post( $campaign_id );
236 if ( ! $campaign || SUREDONATION_POST_TYPE !== $campaign->post_type ) {
237 return new WP_Error(
238 'invalid_campaign',
239 __( 'Invalid campaign.', 'suredonation' ),
240 [ 'status' => 404 ]
241 );
242 }
243
244 $form = get_post( $form_id );
245 if ( ! $form || Donation_Form::POST_TYPE !== $form->post_type ) {
246 return new WP_Error(
247 'form_not_found',
248 __( 'Form not found.', 'suredonation' ),
249 [ 'status' => 404 ]
250 );
251 }
252
253 if ( 'publish' !== $form->post_status ) {
254 return new WP_Error(
255 'form_not_published',
256 __( 'Only a published form can be set as the default.', 'suredonation' ),
257 [ 'status' => 400 ]
258 );
259 }
260
261 if ( absint( Donation_Form::get_form_campaign_id( $form_id ) ) !== $campaign_id ) {
262 return new WP_Error(
263 'form_campaign_mismatch',
264 __( 'This form does not belong to the selected campaign.', 'suredonation' ),
265 [ 'status' => 400 ]
266 );
267 }
268
269 update_post_meta( $campaign_id, Campaign_Cpt::META_DEFAULT_FORM_ID, $form_id );
270
271 return new WP_REST_Response(
272 [
273 'success' => true,
274 'message' => __( 'Default form updated.', 'suredonation' ),
275 ],
276 200
277 );
278 }
279
280 /**
281 * Get a single form.
282 *
283 * @param WP_REST_Request $request Request object.
284 * @return WP_REST_Response|WP_Error Response object.
285 * @since 0.0.1
286 */
287 public function get_form( $request ) {
288 $form_id = absint( $request->get_param( 'id' ) );
289 $form = get_post( $form_id );
290
291 if ( ! $form || Donation_Form::POST_TYPE !== $form->post_type ) {
292 return new WP_Error(
293 'form_not_found',
294 __( 'Form not found.', 'suredonation' ),
295 [ 'status' => 404 ]
296 );
297 }
298
299 return new WP_REST_Response(
300 [
301 'success' => true,
302 'form' => $this->format_form( $form, true ),
303 ],
304 200
305 );
306 }
307
308 /**
309 * Update form (primarily for updating campaign association).
310 *
311 * @param WP_REST_Request $request Request object.
312 * @return WP_REST_Response|WP_Error Response object.
313 * @since 0.0.1
314 */
315 public function update_form( $request ) {
316 $form_id = absint( $request->get_param( 'id' ) );
317 $campaign_id = $request->get_param( 'campaign_id' );
318
319 $form = get_post( $form_id );
320
321 if ( ! $form || Donation_Form::POST_TYPE !== $form->post_type ) {
322 return new WP_Error(
323 'form_not_found',
324 __( 'Form not found.', 'suredonation' ),
325 [ 'status' => 404 ]
326 );
327 }
328
329 // Update campaign association.
330 if ( ! is_null( $campaign_id ) ) {
331 Donation_Form::set_form_campaign_id( $form_id, $campaign_id );
332 }
333
334 return new WP_REST_Response(
335 [
336 'success' => true,
337 'message' => __( 'Form updated successfully.', 'suredonation' ),
338 'form' => $this->format_form( $form ),
339 ],
340 200
341 );
342 }
343
344 /**
345 * Manage form lifecycle (trash, restore, permanent delete).
346 *
347 * @param WP_REST_Request $request Request object.
348 * @return WP_REST_Response|WP_Error Response object.
349 * @since 0.0.1
350 */
351 public function manage_form_lifecycle( $request ) {
352 $form_ids = $request->get_param( 'form_ids' );
353 $action = $request->get_param( 'action' );
354
355 if ( empty( $form_ids ) || empty( $action ) ) {
356 return new WP_Error(
357 'missing_parameters',
358 __( 'Missing required parameters.', 'suredonation' ),
359 [ 'status' => 400 ]
360 );
361 }
362
363 $results = [];
364 $errors = [];
365
366 foreach ( $form_ids as $form_id ) {
367 $post = get_post( $form_id );
368
369 if ( ! $post || Donation_Form::POST_TYPE !== $post->post_type ) {
370 $errors[] = [
371 'form_id' => $form_id,
372 'error' => __( 'Form not found.', 'suredonation' ),
373 ];
374 continue;
375 }
376
377 $result = false;
378
379 switch ( $action ) {
380 case 'trash':
381 if ( 'trash' === $post->post_status ) {
382 $errors[] = [
383 'form_id' => $form_id,
384 'error' => __( 'Form is already in trash.', 'suredonation' ),
385 ];
386 continue 2;
387 }
388 $result = wp_trash_post( $form_id );
389 break;
390
391 case 'restore':
392 if ( 'trash' !== $post->post_status ) {
393 $errors[] = [
394 'form_id' => $form_id,
395 'error' => __( 'Form is not in trash.', 'suredonation' ),
396 ];
397 continue 2;
398 }
399 $result = wp_untrash_post( $form_id );
400 break;
401
402 case 'delete':
403 $result = wp_delete_post( $form_id, true );
404 break;
405 }
406
407 if ( $result ) {
408 $results[] = [
409 'form_id' => $form_id,
410 'action' => $action,
411 'success' => true,
412 ];
413 } else {
414 $errors[] = [
415 'form_id' => $form_id,
416 'error' => __( 'Operation failed.', 'suredonation' ),
417 ];
418 }
419 }
420
421 return new WP_REST_Response(
422 [
423 'success' => ! empty( $results ),
424 'action' => $action,
425 'processed_ids' => array_column( $results, 'form_id' ),
426 'success_count' => count( $results ),
427 'error_count' => count( $errors ),
428 'results' => $results,
429 'errors' => $errors,
430 ],
431 200
432 );
433 }
434
435 /**
436 * Check if user has read permissions.
437 *
438 * @return bool True if user has permission.
439 * @since 0.0.1
440 */
441 public function check_permissions() {
442 return current_user_can( 'manage_options' );
443 }
444
445 /**
446 * Format form data for API response.
447 *
448 * @param \WP_Post $form Form post object.
449 * @param bool $include_html Whether to include rendered HTML.
450 * @return array<string, mixed> Formatted form data.
451 * @since 0.0.1
452 */
453 private function format_form( $form, $include_html = false ) {
454 $campaign_id = Donation_Form::get_form_campaign_id( $form->ID );
455 $campaign = $campaign_id ? get_post( $campaign_id ) : null;
456 $form_stats = $this->get_form_stats( $form->ID );
457
458 $data = [
459 'id' => $form->ID,
460 'title' => $form->post_title,
461 'status' => $form->post_status,
462 'campaign_id' => $campaign_id,
463 'campaign_name' => $campaign ? $campaign->post_title : '',
464 'entries' => $form_stats['entries'],
465 'revenue' => $form_stats['revenue'],
466 'created_at' => $form->post_date,
467 'modified_at' => $form->post_modified,
468 'edit_url' => admin_url( 'post.php?post=' . $form->ID . '&action=edit' ),
469 ];
470
471 if ( $include_html ) {
472 // Parse and render the form blocks.
473 $blocks = parse_blocks( $form->post_content );
474 $blocks_html = '';
475
476 foreach ( $blocks as $block ) {
477 if ( ! empty( $block['blockName'] ) ) {
478 $block['attrs']['formId'] = $form->ID;
479 $blocks_html .= render_block( $block );
480 }
481 }
482
483 $data['content'] = $form->post_content;
484 $data['rendered_html'] = $blocks_html;
485 }
486
487 return $data;
488 }
489
490 /**
491 * Get donation stats for a specific form.
492 *
493 * @param int $form_id Form ID.
494 * @return array{entries: int, revenue: float} Form stats.
495 * @since 1.0.0
496 */
497 private function get_form_stats( $form_id ) {
498 // Delegates to the donations table so this surface and the abilities
499 // report the same figures from one query.
500 return \SureDonation\Inc\Database\Tables\Donations::get_form_stats( $form_id );
501 }
502 }
503