$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>|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 $blocks Array of blocks to process. * @param array $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 $attrs Block attributes. * @param array $blocks All blocks in the form. * @return array 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 $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 $attrs Block attributes. * @return array 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 $attrs Block attributes. * @return array 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; } }