PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.3.0
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.3.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
suredonation / inc / fields / form-renderer.php

form-renderer.php in SureDonation – Donation Forms, Fundraising Campaigns & Donor Management 1.3.0, at inc/fields/form-renderer.php

128 lines 4.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Donation form markup renderer.
4 *
5 * Shared by the donation-form block and the [suredonation_form] shortcode so
6 * both produce identical markup (wrapper, form, field blocks, success box) and
7 * pick up per-form styling from a single place.
8 *
9 * Callers handle validation, campaign resolution, and asset enqueueing; this
10 * class only builds the markup.
11 *
12 * @package SureDonation
13 * @since 1.0.0
14 */
15
16 namespace SureDonation\Inc\Fields;
17
18 use SureDonation\Inc\Helper;
19
20 if ( ! defined( 'ABSPATH' ) ) {
21 exit; // Exit if accessed directly.
22 }
23
24 /**
25 * Form_Renderer class.
26 *
27 * @since 1.0.0
28 */
29 class Form_Renderer {
30
31 /**
32 * Render the donation form markup for a form + campaign.
33 *
34 * @param \WP_Post $form Donation form post.
35 * @param int $campaign_id Resolved campaign ID (0 for a standalone form).
36 * @return string Form HTML.
37 * @since 1.0.0
38 */
39 public static function render( $form, $campaign_id ) {
40 if ( ! $form instanceof \WP_Post ) {
41 return '';
42 }
43
44 $form_id = (int) $form->ID;
45 $campaign_id = (int) $campaign_id;
46 $unique_form_id = 'suredonation-form-' . $form_id . '-' . wp_rand();
47 $form_style = Form_Styling::get_style_attr( $form_id );
48 $nonce_action = Helper::get_donation_nonce_action( $campaign_id );
49 $blocks = parse_blocks( $form->post_content );
50
51 ob_start();
52 ?>
53 <div id="<?php echo esc_attr( $unique_form_id ); ?>" class="sd-form-container" data-form-id="<?php echo esc_attr( (string) $form_id ); ?>" data-campaign-id="<?php echo esc_attr( (string) $campaign_id ); ?>"<?php echo '' !== $form_style ? ' style="' . esc_attr( $form_style ) . '"' : ''; ?>>
54 <form class="sd-form" method="post">
55 <?php wp_nonce_field( $nonce_action, 'suredonation_nonce' ); ?>
56 <input type="hidden" name="form_id" value="<?php echo esc_attr( (string) $form_id ); ?>">
57 <input type="hidden" name="campaign_id" value="<?php echo esc_attr( (string) $campaign_id ); ?>">
58 <?php if ( ! $campaign_id ) { ?>
59 <input type="hidden" name="is_standalone" value="1">
60 <?php } ?>
61 <input type="hidden" name="action" value="suredonation_submit_donation">
62 <?php Helper::render_honeypot_field(); ?>
63
64 <?php
65 // Privacy fields (consent / privacy policy / terms) enabled in the
66 // Privacy settings are injected as the last thing before the submit
67 // button so the donor sees them right before submitting. Anchor on the
68 // donate button; if a form has none, fall back to the payment block, and
69 // finally to the end of the form. The anchor may be nested inside a
70 // layout block (Group/Columns), so match the top-level block that either
71 // is, or contains, the anchor.
72 $privacy_fields = \SureDonation\Inc\Privacy\Privacy_Frontend::render_form_fields();
73 $privacy_anchor = self::block_tree_contains( $blocks, 'suredonation/donate-button' ) ? 'suredonation/donate-button' : 'suredonation/payment';
74 $privacy_injected = false;
75 foreach ( $blocks as $block ) {
76 if ( empty( $block['blockName'] ) ) {
77 continue;
78 }
79 $is_anchor = $block['blockName'] === $privacy_anchor
80 || ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) && self::block_tree_contains( $block['innerBlocks'], $privacy_anchor ) );
81 if ( ! $privacy_injected && '' !== $privacy_fields && $is_anchor ) {
82 echo wp_kses( $privacy_fields, Helper::get_allowed_form_html() );
83 $privacy_injected = true;
84 }
85 $block['attrs']['formId'] = $form_id;
86 // Allow the data: protocol so a lazy-load optimizer's inline SVG
87 // placeholder (Image block) survives this second kses pass.
88 echo wp_kses( render_block( $block ), Helper::get_allowed_form_html(), array_merge( wp_allowed_protocols(), [ 'data' ] ) );
89 }
90 if ( ! $privacy_injected && '' !== $privacy_fields ) {
91 echo wp_kses( $privacy_fields, Helper::get_allowed_form_html() );
92 }
93 ?>
94 </form>
95 <!-- Success Message Container -->
96 <div class="sd-single-form sd-success-box">
97 <div aria-live="polite" aria-atomic="true" role="alert" id="sd-success-message-<?php echo esc_attr( (string) $form_id ); ?>" class="sd-success-box-description"></div>
98 </div>
99 </div>
100 <?php
101 $output = ob_get_clean();
102 return false !== $output ? $output : '';
103 }
104
105 /**
106 * Whether a (possibly nested) block tree contains a block of the given name.
107 *
108 * @since 1.2.0
109 * @param array<int|string, mixed> $blocks Parsed blocks (parse_blocks output).
110 * @param string $target Block name to look for.
111 * @return bool
112 */
113 private static function block_tree_contains( $blocks, $target ) {
114 foreach ( $blocks as $block ) {
115 if ( ! is_array( $block ) ) {
116 continue;
117 }
118 if ( isset( $block['blockName'] ) && $block['blockName'] === $target ) {
119 return true;
120 }
121 if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) && self::block_tree_contains( $block['innerBlocks'], $target ) ) {
122 return true;
123 }
124 }
125 return false;
126 }
127 }
128