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

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

311 lines 9.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Field Validation Class
4 *
5 * Handles field validation for SureDonation forms.
6 * Stores block configuration on form save and retrieves it for validation.
7 *
8 * @package SureDonation
9 * @since 0.0.1
10 */
11
12 namespace SureDonation\Inc;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit; // Exit if accessed directly.
16 }
17
18 /**
19 * Field Validation Class
20 */
21 class Field_Validation {
22 /**
23 * Meta key for storing block configuration.
24 *
25 * @since 0.0.1
26 */
27 public const BLOCK_CONFIG_META_KEY = '_suredonation_block_config';
28
29 /**
30 * Add block configuration for form fields.
31 *
32 * This function processes blocks in a form and stores their configuration as post meta.
33 * It extracts payment block settings (amount type, fixed amount, minimum amount, etc.)
34 * which are used for server-side validation to prevent payment manipulation.
35 *
36 * @param array<mixed> $blocks Array of blocks to process.
37 * @param int $form_id Form post ID.
38 * @return void
39 * @since 0.0.1
40 */
41 public static function add_block_config( $blocks, $form_id ) {
42 // Initialize array to store processed block configurations.
43 $block_config = [];
44
45 // Process blocks recursively.
46 self::process_blocks_recursive( $blocks, $block_config );
47
48 // Only update meta if we have processed configurations.
49 if ( ! empty( $block_config ) ) {
50 update_post_meta( $form_id, self::BLOCK_CONFIG_META_KEY, $block_config );
51 }
52 }
53
54 /**
55 * Retrieve or migrate the block configuration for legacy forms.
56 *
57 * This function checks if the _suredonation_block_config post meta exists for the given form ID.
58 * If not found, it attempts to parse the form's post content and generate the block config.
59 *
60 * @param int $form_id The ID of the form post.
61 * @since 0.0.1
62 * @return array<string, array<string, mixed>>|null The block configuration array, or null if not found or invalid.
63 */
64 public static function get_or_migrate_block_config_for_legacy_form( $form_id ) {
65 // Validate that $form_id is a positive integer.
66 if ( ! is_int( $form_id ) || $form_id <= 0 ) {
67 return null;
68 }
69
70 // Retrieve the block config from post meta.
71 $block_config = get_post_meta( $form_id, self::BLOCK_CONFIG_META_KEY, true );
72 if ( ! empty( $block_config ) && is_array( $block_config ) ) {
73 // If it exists and is an array, return it directly (no migration needed).
74 return $block_config;
75 }
76
77 // Get the post by ID and validate.
78 $post = get_post( $form_id );
79 if ( ! ( $post instanceof \WP_Post ) || empty( $post->post_content ) ) {
80 return null;
81 }
82
83 // Parse the blocks from the post content and attempt migration.
84 if ( function_exists( 'parse_blocks' ) ) {
85 $blocks = parse_blocks( $post->post_content );
86 if ( is_array( $blocks ) && ! empty( $blocks ) ) {
87 self::add_block_config( $blocks, $form_id );
88 }
89 }
90
91 // Retrieve the block config again after migration attempt.
92 $block_config = get_post_meta( $form_id, self::BLOCK_CONFIG_META_KEY, true );
93
94 return ! empty( $block_config ) && is_array( $block_config ) ? $block_config : null;
95 }
96
97 /**
98 * Process blocks recursively to extract configuration.
99 *
100 * @param array<mixed> $blocks Array of blocks to process.
101 * @param array<mixed> $block_config Reference to block config array.
102 * @return void
103 * @since 0.0.1
104 */
105 private static function process_blocks_recursive( $blocks, &$block_config ) {
106 foreach ( $blocks as $block ) {
107 // Ensure $block is an array and has the required structure.
108 if ( ! is_array( $block ) ) {
109 continue;
110 }
111
112 // Process inner blocks recursively (for columns, groups, etc.).
113 if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
114 self::process_blocks_recursive( $block['innerBlocks'], $block_config );
115 }
116
117 if ( ! isset( $block['blockName'] ) || ! isset( $block['attrs'] ) || ! is_array( $block['attrs'] ) ) {
118 continue;
119 }
120
121 // Validate block_id exists.
122 if ( ! array_key_exists( 'block_id', $block['attrs'] ) || empty( $block['attrs']['block_id'] ) || ! is_string( $block['attrs']['block_id'] ) ) {
123 continue;
124 }
125
126 $block_id = sanitize_text_field( $block['attrs']['block_id'] );
127 $block_name = $block['blockName'];
128
129 // Process specific block types.
130 $processed_config = null;
131
132 switch ( $block_name ) {
133 case 'sd/payment':
134 $processed_config = self::process_payment_block( $block['attrs'], $blocks );
135 break;
136 case 'sd/multi-choice':
137 $processed_config = self::process_multichoice_block( $block['attrs'] );
138 break;
139 case 'sd/number':
140 $processed_config = self::process_number_block( $block['attrs'] );
141 break;
142 }
143
144 // If block was processed, store its configuration.
145 if ( null !== $processed_config && ! empty( $processed_config ) ) {
146 $processed_config['block_name'] = $block_name;
147
148 // Add the slug to the configuration.
149 if ( isset( $block['attrs']['slug'] ) && ! empty( $block['attrs']['slug'] ) ) {
150 $processed_config['slug'] = sanitize_text_field( $block['attrs']['slug'] );
151 }
152
153 $block_config[ $block_id ] = $processed_config;
154 }
155 }
156 }
157
158 /**
159 * Process payment block configuration.
160 *
161 * Extracts payment-related settings that are needed for server-side validation:
162 * - amount_type: 'fixed' or 'variable'
163 * - fixed_amount: The configured fixed amount
164 * - minimum_amount: The minimum allowed amount for variable amounts
165 * - variable_amount_field: The slug of the field providing the variable amount
166 *
167 * @param array<mixed> $attrs Block attributes.
168 * @param array<mixed> $blocks All blocks in the form.
169 * @return array<string, mixed> Processed payment configuration.
170 * @since 0.0.1
171 */
172 private static function process_payment_block( $attrs, $blocks ) {
173 $payment_config = [];
174
175 // Extract payment type (one-time or subscription).
176 // Default to 'one-time' if not set (Gutenberg may not save default values).
177 $payment_config['payment_type'] = isset( $attrs['paymentType'] ) && is_string( $attrs['paymentType'] )
178 ? sanitize_text_field( $attrs['paymentType'] )
179 : 'one-time';
180
181 // Extract amount type (fixed or variable).
182 // IMPORTANT: Always store this - Gutenberg may not save attributes that match defaults.
183 // Default to 'fixed' which is the block.json default.
184 $payment_config['amount_type'] = isset( $attrs['amountType'] ) && is_string( $attrs['amountType'] )
185 ? sanitize_text_field( $attrs['amountType'] )
186 : 'fixed';
187
188 // Extract configured fixed amount.
189 // Default to 10.00 to match block.json default.
190 $payment_config['fixed_amount'] = isset( $attrs['fixedAmount'] )
191 ? floatval( $attrs['fixedAmount'] )
192 : 10.00;
193
194 // Extract minimum amount for variable amounts.
195 // Default to 1.0 to match block.json default.
196 $payment_config['minimum_amount'] = isset( $attrs['minimumAmount'] )
197 ? floatval( $attrs['minimumAmount'] )
198 : 1.0;
199
200 // Extract variable amount field reference.
201 if ( isset( $attrs['variableAmountField'] ) ) {
202 $variable_amount_slug = sanitize_text_field( $attrs['variableAmountField'] );
203 $payment_config['variable_amount_field'] = $variable_amount_slug;
204
205 // Find and add the block name from which the variable amount field comes from.
206 if ( ! empty( $variable_amount_slug ) && is_array( $blocks ) ) {
207 $block_name = self::find_block_name_by_slug( $blocks, $variable_amount_slug );
208 if ( $block_name ) {
209 $payment_config['variable_amount_field_block_name'] = $block_name;
210 }
211 }
212 }
213
214 return $payment_config;
215 }
216
217 /**
218 * Find block name by slug recursively.
219 *
220 * @param array<mixed> $blocks Array of blocks.
221 * @param string $slug Slug to find.
222 * @return string|null Block name if found, null otherwise.
223 * @since 0.0.1
224 */
225 private static function find_block_name_by_slug( $blocks, $slug ) {
226 foreach ( $blocks as $block ) {
227 if ( ! is_array( $block ) ) {
228 continue;
229 }
230
231 // Check inner blocks first.
232 if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
233 $found = self::find_block_name_by_slug( $block['innerBlocks'], $slug );
234 if ( $found ) {
235 return $found;
236 }
237 }
238
239 if ( isset( $block['attrs']['slug'] ) && $block['attrs']['slug'] === $slug ) {
240 return $block['blockName'];
241 }
242 }
243 return null;
244 }
245
246 /**
247 * Process multi-choice block configuration.
248 *
249 * @param array<mixed> $attrs Block attributes.
250 * @return array<string, mixed> Processed multi-choice configuration.
251 * @since 0.0.1
252 */
253 private static function process_multichoice_block( $attrs ) {
254 $multichoice_config = [];
255
256 // Extract required field.
257 if ( isset( $attrs['required'] ) ) {
258 $multichoice_config['required'] = ! empty( $attrs['required'] );
259 }
260
261 // Extract choice type (radio or checkbox).
262 if ( isset( $attrs['choiceType'] ) ) {
263 $multichoice_config['choice_type'] = sanitize_text_field( $attrs['choiceType'] );
264 }
265
266 // Extract options with their full structure (label, value).
267 if ( isset( $attrs['options'] ) && is_array( $attrs['options'] ) ) {
268 $sanitized_options = [];
269 foreach ( $attrs['options'] as $option ) {
270 if ( is_array( $option ) ) {
271 $sanitized_options[] = [
272 'label' => isset( $option['label'] ) ? sanitize_text_field( $option['label'] ) : '',
273 'value' => isset( $option['value'] ) ? sanitize_text_field( $option['value'] ) : '',
274 ];
275 }
276 }
277 $multichoice_config['options'] = $sanitized_options;
278 }
279
280 return $multichoice_config;
281 }
282
283 /**
284 * Process number block configuration.
285 *
286 * @param array<mixed> $attrs Block attributes.
287 * @return array<string, mixed> Processed number block configuration.
288 * @since 0.0.1
289 */
290 private static function process_number_block( $attrs ) {
291 $number_config = [];
292
293 // Extract required field.
294 if ( isset( $attrs['required'] ) ) {
295 $number_config['required'] = ! empty( $attrs['required'] );
296 }
297
298 // Extract min value.
299 if ( isset( $attrs['min'] ) ) {
300 $number_config['min'] = floatval( $attrs['min'] );
301 }
302
303 // Extract max value.
304 if ( isset( $attrs['max'] ) ) {
305 $number_config['max'] = floatval( $attrs['max'] );
306 }
307
308 return $number_config;
309 }
310 }
311