PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.0.0
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.0.0
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 1.0.0, at inc/field-validation.php

351 lines 11.7 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 'suredonation/payment':
134 $processed_config = self::process_payment_block( $block['attrs'], $blocks );
135 break;
136 case 'suredonation/donation-amount':
137 $processed_config = self::process_donation_amount_block( $block['attrs'] );
138 break;
139 case 'suredonation/number':
140 $processed_config = self::process_number_block( $block['attrs'] );
141 break;
142 case 'suredonation/cover-fees':
143 $processed_config = self::process_cover_fees_block( $block['attrs'] );
144 break;
145 }
146
147 // If block was processed, store its configuration.
148 if ( null !== $processed_config && ! empty( $processed_config ) ) {
149 $processed_config['block_name'] = $block_name;
150
151 // Add the slug to the configuration.
152 if ( isset( $block['attrs']['slug'] ) && ! empty( $block['attrs']['slug'] ) ) {
153 $processed_config['slug'] = sanitize_text_field( $block['attrs']['slug'] );
154 }
155
156 $block_config[ $block_id ] = $processed_config;
157 }
158 }
159 }
160
161 /**
162 * Process payment block configuration.
163 *
164 * Extracts payment-related settings that are needed for server-side validation:
165 * - amount_type: 'fixed' or 'variable'
166 * - fixed_amount: The configured fixed amount
167 * - minimum_amount: The minimum allowed amount for variable amounts
168 * - variable_amount_field: The slug of the field providing the variable amount
169 *
170 * @param array<mixed> $attrs Block attributes.
171 * @param array<mixed> $blocks All blocks in the form.
172 * @return array<string, mixed> Processed payment configuration.
173 * @since 0.0.1
174 */
175 private static function process_payment_block( $attrs, $blocks ) {
176 $payment_config = [];
177
178 // Extract payment type (one-time or subscription).
179 // Default to 'one-time' if not set (Gutenberg may not save default values).
180 $payment_config['payment_type'] = isset( $attrs['paymentType'] ) && is_string( $attrs['paymentType'] )
181 ? sanitize_text_field( $attrs['paymentType'] )
182 : 'one-time';
183
184 // Extract amount type (fixed or variable).
185 // IMPORTANT: Always store this - Gutenberg may not save attributes that match defaults.
186 // Default to 'fixed' which is the block.json default.
187 $payment_config['amount_type'] = isset( $attrs['amountType'] ) && is_string( $attrs['amountType'] )
188 ? sanitize_text_field( $attrs['amountType'] )
189 : 'fixed';
190
191 // Extract configured fixed amount.
192 // Default to 10.00 to match block.json default.
193 $payment_config['fixed_amount'] = isset( $attrs['fixedAmount'] )
194 ? floatval( $attrs['fixedAmount'] )
195 : 10.00;
196
197 // Extract minimum amount for variable amounts.
198 // Defaults to 0 (no minimum) — only enforced if the block setting specifies one.
199 $payment_config['minimum_amount'] = isset( $attrs['minimumAmount'] )
200 ? floatval( $attrs['minimumAmount'] )
201 : 0.0;
202
203 // Extract variable amount field reference.
204 if ( isset( $attrs['variableAmountField'] ) ) {
205 $variable_amount_slug = sanitize_text_field( $attrs['variableAmountField'] );
206 $payment_config['variable_amount_field'] = $variable_amount_slug;
207
208 // Find and add the block name from which the variable amount field comes from.
209 if ( ! empty( $variable_amount_slug ) && is_array( $blocks ) ) {
210 $block_name = self::find_block_name_by_slug( $blocks, $variable_amount_slug );
211 if ( $block_name ) {
212 $payment_config['variable_amount_field_block_name'] = $block_name;
213 }
214 }
215 }
216
217 return $payment_config;
218 }
219
220 /**
221 * Find block name by slug recursively.
222 *
223 * @param array<mixed> $blocks Array of blocks.
224 * @param string $slug Slug to find.
225 * @return string|null Block name if found, null otherwise.
226 * @since 0.0.1
227 */
228 private static function find_block_name_by_slug( $blocks, $slug ) {
229 foreach ( $blocks as $block ) {
230 if ( ! is_array( $block ) ) {
231 continue;
232 }
233
234 // Check inner blocks first.
235 if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
236 $found = self::find_block_name_by_slug( $block['innerBlocks'], $slug );
237 if ( $found ) {
238 return $found;
239 }
240 }
241
242 if ( isset( $block['attrs']['slug'] ) && $block['attrs']['slug'] === $slug ) {
243 return $block['blockName'];
244 }
245 }
246 return null;
247 }
248
249 /**
250 * Process donation-amount block configuration.
251 *
252 * @param array<mixed> $attrs Block attributes.
253 * @return array<string, mixed> Processed donation-amount configuration.
254 * @since 0.0.1
255 */
256 private static function process_donation_amount_block( $attrs ) {
257 $donation_amount_config = [];
258
259 // Extract required field.
260 if ( isset( $attrs['required'] ) ) {
261 $donation_amount_config['required'] = ! empty( $attrs['required'] );
262 }
263
264 // Extract choice type (radio or checkbox).
265 if ( isset( $attrs['choiceType'] ) ) {
266 $donation_amount_config['choice_type'] = sanitize_text_field( $attrs['choiceType'] );
267 }
268
269 // Extract options with their full structure (label, value).
270 if ( isset( $attrs['options'] ) && is_array( $attrs['options'] ) ) {
271 $sanitized_options = [];
272 foreach ( $attrs['options'] as $option ) {
273 if ( is_array( $option ) ) {
274 $sanitized_options[] = [
275 'label' => isset( $option['label'] ) ? sanitize_text_field( $option['label'] ) : '',
276 'value' => isset( $option['value'] ) ? sanitize_text_field( $option['value'] ) : '',
277 ];
278 }
279 }
280 $donation_amount_config['options'] = $sanitized_options;
281 }
282
283 // Custom amount settings (radio-mode only).
284 $donation_amount_config['allow_custom_amount'] = ! isset( $attrs['allowCustomAmount'] ) || ! empty( $attrs['allowCustomAmount'] );
285 $donation_amount_config['custom_amount_min'] = isset( $attrs['customAmountMin'] ) ? (float) $attrs['customAmountMin'] : 0.0;
286 $donation_amount_config['custom_amount_max'] = isset( $attrs['customAmountMax'] ) ? (float) $attrs['customAmountMax'] : 0.0;
287
288 return $donation_amount_config;
289 }
290
291 /**
292 * Process cover-fees block configuration.
293 *
294 * Resolves global vs block-level fee rates and stores them for server-side validation.
295 *
296 * @param array<mixed> $attrs Block attributes.
297 * @return array<string, mixed> Processed cover fees configuration.
298 * @since 1.0.0
299 */
300 private static function process_cover_fees_block( $attrs ) {
301 $use_global = $attrs['useGlobalDefaults'] ?? true;
302
303 if ( $use_global ) {
304 $fee_config = \SureDonation\Inc\Payments\Payment_Helper::get_fee_recovery_settings();
305 } else {
306 $fee_config = [
307 'fee_percentage' => isset( $attrs['feePercentage'] ) ? floatval( $attrs['feePercentage'] ) : 2.9,
308 'fee_fixed' => isset( $attrs['feeFixed'] ) ? floatval( $attrs['feeFixed'] ) : 0.30,
309 'fee_mode' => $attrs['feeMode'] ?? 'all_gateways',
310 'gateways' => $attrs['gatewayFees'] ?? [],
311 ];
312 }
313
314 return [
315 'use_global_defaults' => $use_global,
316 'fee_percentage' => (float) ( $fee_config['fee_percentage'] ?? 2.9 ),
317 'fee_fixed' => (float) ( $fee_config['fee_fixed'] ?? 0.30 ),
318 'fee_mode' => $fee_config['fee_mode'] ?? 'all_gateways',
319 'gateway_fees' => $fee_config['gateways'] ?? [],
320 ];
321 }
322
323 /**
324 * Process number block configuration.
325 *
326 * @param array<mixed> $attrs Block attributes.
327 * @return array<string, mixed> Processed number block configuration.
328 * @since 0.0.1
329 */
330 private static function process_number_block( $attrs ) {
331 $number_config = [];
332
333 // Extract required field.
334 if ( isset( $attrs['required'] ) ) {
335 $number_config['required'] = ! empty( $attrs['required'] );
336 }
337
338 // Extract min value.
339 if ( isset( $attrs['min'] ) ) {
340 $number_config['min'] = floatval( $attrs['min'] );
341 }
342
343 // Extract max value.
344 if ( isset( $attrs['max'] ) ) {
345 $number_config['max'] = floatval( $attrs['max'] );
346 }
347
348 return $number_config;
349 }
350 }
351