# suredonation/0.0.1/inc/field-validation.php

SureDonation – Donation Forms, Fundraising Campaigns &amp; Donor Management, version 0.0.1. 311 lines.

- Page: https://pluginprobe.com/plugins/suredonation/0.0.1/code/inc/field-validation.php
- Raw: https://pluginprobe.com/plugins/suredonation/0.0.1/raw/inc/field-validation.php
- Modified: 2026-03-04T10:31:12+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/suredonation/0.0.1/code/inc/field-validation.php#L10-L20`.

```php
<?php
/**
 * Field Validation Class
 *
 * Handles field validation for SureDonation forms.
 * Stores block configuration on form save and retrieves it for validation.
 *
 * @package SureDonation
 * @since 0.0.1
 */

namespace SureDonation\Inc;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Field Validation Class
 */
class Field_Validation {
	/**
	 * Meta key for storing block configuration.
	 *
	 * @since 0.0.1
	 */
	public const BLOCK_CONFIG_META_KEY = '_suredonation_block_config';

	/**
	 * Add block configuration for form fields.
	 *
	 * This function processes blocks in a form and stores their configuration as post meta.
	 * It extracts payment block settings (amount type, fixed amount, minimum amount, etc.)
	 * which are used for server-side validation to prevent payment manipulation.
	 *
	 * @param array<mixed> $blocks  Array of blocks to process.
	 * @param int          $form_id Form post ID.
	 * @return void
	 * @since 0.0.1
	 */
	public static function add_block_config( $blocks, $form_id ) {
		// Initialize array to store processed block configurations.
		$block_config = [];

		// Process blocks recursively.
		self::process_blocks_recursive( $blocks, $block_config );

		// Only update meta if we have processed configurations.
		if ( ! empty( $block_config ) ) {
			update_post_meta( $form_id, self::BLOCK_CONFIG_META_KEY, $block_config );
		}
	}

	/**
	 * Retrieve or migrate the block configuration for legacy forms.
	 *
	 * This function checks if the _suredonation_block_config post meta exists for the given form ID.
	 * If not found, it attempts to parse the form's post content and generate the block config.
	 *
	 * @param int $form_id The ID of the form post.
	 * @since 0.0.1
	 * @return array<string, array<string, mixed>>|null The block configuration array, or null if not found or invalid.
	 */
	public static function get_or_migrate_block_config_for_legacy_form( $form_id ) {
		// Validate that $form_id is a positive integer.
		if ( ! is_int( $form_id ) || $form_id <= 0 ) {
			return null;
		}

		// Retrieve the block config from post meta.
		$block_config = get_post_meta( $form_id, self::BLOCK_CONFIG_META_KEY, true );
		if ( ! empty( $block_config ) && is_array( $block_config ) ) {
			// If it exists and is an array, return it directly (no migration needed).
			return $block_config;
		}

		// Get the post by ID and validate.
		$post = get_post( $form_id );
		if ( ! ( $post instanceof \WP_Post ) || empty( $post->post_content ) ) {
			return null;
		}

		// Parse the blocks from the post content and attempt migration.
		if ( function_exists( 'parse_blocks' ) ) {
			$blocks = parse_blocks( $post->post_content );
			if ( is_array( $blocks ) && ! empty( $blocks ) ) {
				self::add_block_config( $blocks, $form_id );
			}
		}

		// Retrieve the block config again after migration attempt.
		$block_config = get_post_meta( $form_id, self::BLOCK_CONFIG_META_KEY, true );

		return ! empty( $block_config ) && is_array( $block_config ) ? $block_config : null;
	}

	/**
	 * Process blocks recursively to extract configuration.
	 *
	 * @param array<mixed> $blocks       Array of blocks to process.
	 * @param array<mixed> $block_config Reference to block config array.
	 * @return void
	 * @since 0.0.1
	 */
	private static function process_blocks_recursive( $blocks, &$block_config ) {
		foreach ( $blocks as $block ) {
			// Ensure $block is an array and has the required structure.
			if ( ! is_array( $block ) ) {
				continue;
			}

			// Process inner blocks recursively (for columns, groups, etc.).
			if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
				self::process_blocks_recursive( $block['innerBlocks'], $block_config );
			}

			if ( ! isset( $block['blockName'] ) || ! isset( $block['attrs'] ) || ! is_array( $block['attrs'] ) ) {
				continue;
			}

			// Validate block_id exists.
			if ( ! array_key_exists( 'block_id', $block['attrs'] ) || empty( $block['attrs']['block_id'] ) || ! is_string( $block['attrs']['block_id'] ) ) {
				continue;
			}

			$block_id   = sanitize_text_field( $block['attrs']['block_id'] );
			$block_name = $block['blockName'];

			// Process specific block types.
			$processed_config = null;

			switch ( $block_name ) {
				case 'sd/payment':
					$processed_config = self::process_payment_block( $block['attrs'], $blocks );
					break;
				case 'sd/multi-choice':
					$processed_config = self::process_multichoice_block( $block['attrs'] );
					break;
				case 'sd/number':
					$processed_config = self::process_number_block( $block['attrs'] );
					break;
			}

			// If block was processed, store its configuration.
			if ( null !== $processed_config && ! empty( $processed_config ) ) {
				$processed_config['block_name'] = $block_name;

				// Add the slug to the configuration.
				if ( isset( $block['attrs']['slug'] ) && ! empty( $block['attrs']['slug'] ) ) {
					$processed_config['slug'] = sanitize_text_field( $block['attrs']['slug'] );
				}

				$block_config[ $block_id ] = $processed_config;
			}
		}
	}

	/**
	 * Process payment block configuration.
	 *
	 * Extracts payment-related settings that are needed for server-side validation:
	 * - amount_type: 'fixed' or 'variable'
	 * - fixed_amount: The configured fixed amount
	 * - minimum_amount: The minimum allowed amount for variable amounts
	 * - variable_amount_field: The slug of the field providing the variable amount
	 *
	 * @param array<mixed> $attrs  Block attributes.
	 * @param array<mixed> $blocks All blocks in the form.
	 * @return array<string, mixed> Processed payment configuration.
	 * @since 0.0.1
	 */
	private static function process_payment_block( $attrs, $blocks ) {
		$payment_config = [];

		// Extract payment type (one-time or subscription).
		// Default to 'one-time' if not set (Gutenberg may not save default values).
		$payment_config['payment_type'] = isset( $attrs['paymentType'] ) && is_string( $attrs['paymentType'] )
			? sanitize_text_field( $attrs['paymentType'] )
			: 'one-time';

		// Extract amount type (fixed or variable).
		// IMPORTANT: Always store this - Gutenberg may not save attributes that match defaults.
		// Default to 'fixed' which is the block.json default.
		$payment_config['amount_type'] = isset( $attrs['amountType'] ) && is_string( $attrs['amountType'] )
			? sanitize_text_field( $attrs['amountType'] )
			: 'fixed';

		// Extract configured fixed amount.
		// Default to 10.00 to match block.json default.
		$payment_config['fixed_amount'] = isset( $attrs['fixedAmount'] )
			? floatval( $attrs['fixedAmount'] )
			: 10.00;

		// Extract minimum amount for variable amounts.
		// Default to 1.0 to match block.json default.
		$payment_config['minimum_amount'] = isset( $attrs['minimumAmount'] )
			? floatval( $attrs['minimumAmount'] )
			: 1.0;

		// Extract variable amount field reference.
		if ( isset( $attrs['variableAmountField'] ) ) {
			$variable_amount_slug                    = sanitize_text_field( $attrs['variableAmountField'] );
			$payment_config['variable_amount_field'] = $variable_amount_slug;

			// Find and add the block name from which the variable amount field comes from.
			if ( ! empty( $variable_amount_slug ) && is_array( $blocks ) ) {
				$block_name = self::find_block_name_by_slug( $blocks, $variable_amount_slug );
				if ( $block_name ) {
					$payment_config['variable_amount_field_block_name'] = $block_name;
				}
			}
		}

		return $payment_config;
	}

	/**
	 * Find block name by slug recursively.
	 *
	 * @param array<mixed> $blocks Array of blocks.
	 * @param string       $slug   Slug to find.
	 * @return string|null Block name if found, null otherwise.
	 * @since 0.0.1
	 */
	private static function find_block_name_by_slug( $blocks, $slug ) {
		foreach ( $blocks as $block ) {
			if ( ! is_array( $block ) ) {
				continue;
			}

			// Check inner blocks first.
			if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
				$found = self::find_block_name_by_slug( $block['innerBlocks'], $slug );
				if ( $found ) {
					return $found;
				}
			}

			if ( isset( $block['attrs']['slug'] ) && $block['attrs']['slug'] === $slug ) {
				return $block['blockName'];
			}
		}
		return null;
	}

	/**
	 * Process multi-choice block configuration.
	 *
	 * @param array<mixed> $attrs Block attributes.
	 * @return array<string, mixed> Processed multi-choice configuration.
	 * @since 0.0.1
	 */
	private static function process_multichoice_block( $attrs ) {
		$multichoice_config = [];

		// Extract required field.
		if ( isset( $attrs['required'] ) ) {
			$multichoice_config['required'] = ! empty( $attrs['required'] );
		}

		// Extract choice type (radio or checkbox).
		if ( isset( $attrs['choiceType'] ) ) {
			$multichoice_config['choice_type'] = sanitize_text_field( $attrs['choiceType'] );
		}

		// Extract options with their full structure (label, value).
		if ( isset( $attrs['options'] ) && is_array( $attrs['options'] ) ) {
			$sanitized_options = [];
			foreach ( $attrs['options'] as $option ) {
				if ( is_array( $option ) ) {
					$sanitized_options[] = [
						'label' => isset( $option['label'] ) ? sanitize_text_field( $option['label'] ) : '',
						'value' => isset( $option['value'] ) ? sanitize_text_field( $option['value'] ) : '',
					];
				}
			}
			$multichoice_config['options'] = $sanitized_options;
		}

		return $multichoice_config;
	}

	/**
	 * Process number block configuration.
	 *
	 * @param array<mixed> $attrs Block attributes.
	 * @return array<string, mixed> Processed number block configuration.
	 * @since 0.0.1
	 */
	private static function process_number_block( $attrs ) {
		$number_config = [];

		// Extract required field.
		if ( isset( $attrs['required'] ) ) {
			$number_config['required'] = ! empty( $attrs['required'] );
		}

		// Extract min value.
		if ( isset( $attrs['min'] ) ) {
			$number_config['min'] = floatval( $attrs['min'] );
		}

		// Extract max value.
		if ( isset( $attrs['max'] ) ) {
			$number_config['max'] = floatval( $attrs['max'] );
		}

		return $number_config;
	}
}

```
