# blocks/trunk/src/Settings/FieldRenderer.php

Blocks – Reusable Content, Shortcodes &amp; Site Variables, version trunk. 253 lines.

- Page: https://pluginprobe.com/plugins/blocks/trunk/code/src/Settings/FieldRenderer.php
- Raw: https://pluginprobe.com/plugins/blocks/trunk/raw/src/Settings/FieldRenderer.php
- Modified: 2026-09-01T01:24:38+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/blocks/trunk/code/src/Settings/FieldRenderer.php#L10-L20`.

```php
<?php

declare(strict_types=1);

namespace RenzoJohnson\Blocks\Settings;

use RenzoJohnson\Blocks\Security\SecretStore;

\defined( 'ABSPATH' ) || exit;

final class FieldRenderer {
	public function __construct(
		private readonly \Blocks_Settings_Repository $settings,
		private readonly SecretStore $secrets,
	) {
	}

	public function render_section( SectionDefinition $section ): void {
		if ( $section->honeypot_description ) {
			$this->render_honeypot_description();
			return;
		}

		foreach ( $section->description_paragraphs as $paragraph ) {
			?>
			<p><?php echo \esc_html( $paragraph ); ?></p>
			<?php
		}
	}

	public function render( FieldDefinition $field ): void {
		match ( $field->type ) {
			FieldType::Checkbox => $this->render_checkbox( $field ),
			FieldType::Image => $this->render_image( $field ),
			FieldType::Number => $this->render_number( $field ),
			FieldType::Product => $this->render_product( $field ),
			FieldType::Secret => $this->render_secret( $field ),
			FieldType::Select => $this->render_select( $field ),
			FieldType::SupportHours => $this->render_support_hours( $field ),
			FieldType::Text => $this->render_text( $field ),
			FieldType::Textarea => $this->render_textarea( $field ),
		};
	}

	private function render_text( FieldDefinition $field ): void {
		$setting     = $field->primary();
		$type        = isset( $field->render_args['type'] ) && \is_string( $field->render_args['type'] ) ? $field->render_args['type'] : 'text';
		$placeholder = isset( $field->render_args['placeholder'] ) && \is_string( $field->render_args['placeholder'] ) ? $field->render_args['placeholder'] : '';
		?>
		<div class="blocks-variable-field">
			<input
				type="<?php echo \esc_attr( $type ); ?>"
				id="<?php echo \esc_attr( $setting->option_name ); ?>"
				name="<?php echo \esc_attr( $setting->option_name ); ?>"
				value="<?php echo \esc_attr( $this->settings->get_string( $setting->option_name, (string) $setting->default_value ) ); ?>"
				class="regular-text"
				placeholder="<?php echo \esc_attr( $placeholder ); ?>"
			/>
			<?php $this->render_variable_control( $setting ); ?>
		</div>
		<?php $this->render_description( $field ); ?>
		<?php
	}

	private function render_textarea( FieldDefinition $field ): void {
		$setting = $field->primary();
		?>
		<div class="blocks-variable-field blocks-variable-field-textarea">
			<textarea
				id="<?php echo \esc_attr( $setting->option_name ); ?>"
				name="<?php echo \esc_attr( $setting->option_name ); ?>"
				rows="3"
				cols="50"
				class="large-text"
			><?php echo \esc_textarea( $this->settings->get_string( $setting->option_name, (string) $setting->default_value ) ); ?></textarea>
			<?php $this->render_variable_control( $setting ); ?>
		</div>
		<?php $this->render_description( $field ); ?>
		<?php
	}

	private function render_checkbox( FieldDefinition $field ): void {
		$setting = $field->primary();
		$value   = $this->settings->get_bool( $setting->option_name, (bool) $setting->default_value );
		?>
		<label for="<?php echo \esc_attr( $setting->option_name ); ?>">
			<input
				type="checkbox"
				id="<?php echo \esc_attr( $setting->option_name ); ?>"
				name="<?php echo \esc_attr( $setting->option_name ); ?>"
				value="1"
				<?php \checked( $value ); ?>
			/>
			<?php echo \esc_html( $field->description ); ?>
		</label>
		<?php
		$this->render_doc_link( $field );
	}

	/**
	 * Render a field's documentation link outside its label, so following the
	 * link does not toggle the control it sits beside.
	 */
	private function render_doc_link( FieldDefinition $field ): void {
		if ( '' === $field->doc_url || '' === $field->doc_label ) {
			return;
		}
		?>
		<a href="<?php echo \esc_url( $field->doc_url ); ?>" target="_blank" rel="noopener noreferrer"><?php echo \esc_html( $field->doc_label ); ?></a>
		<?php
	}

	private function render_number( FieldDefinition $field ): void {
		$setting = $field->primary();
		$min     = $this->scalar_arg( $field, 'min', 0 );
		$max     = $this->scalar_arg( $field, 'max', 9999 );
		$step    = $this->scalar_arg( $field, 'step', 1 );
		?>
		<input
			type="number"
			id="<?php echo \esc_attr( $setting->option_name ); ?>"
			name="<?php echo \esc_attr( $setting->option_name ); ?>"
			value="<?php echo \esc_attr( (string) $this->settings->get_int( $setting->option_name, (int) $setting->default_value ) ); ?>"
			min="<?php echo \esc_attr( (string) $min ); ?>"
			max="<?php echo \esc_attr( (string) $max ); ?>"
			step="<?php echo \esc_attr( (string) $step ); ?>"
			class="small-text"
		/>
		<?php $this->render_description( $field ); ?>
		<?php
	}

	private function render_select( FieldDefinition $field ): void {
		$setting = $field->primary();
		$options = isset( $field->render_args['options'] ) && \is_array( $field->render_args['options'] ) ? $field->render_args['options'] : array();
		$value   = $this->settings->get_string( $setting->option_name, (string) $setting->default_value );
		?>
		<select id="<?php echo \esc_attr( $setting->option_name ); ?>" name="<?php echo \esc_attr( $setting->option_name ); ?>">
			<?php foreach ( $options as $key => $label ) : ?>
				<?php if ( \is_scalar( $key ) && \is_scalar( $label ) ) : ?>
					<option value="<?php echo \esc_attr( (string) $key ); ?>" <?php \selected( $value, (string) $key ); ?>><?php echo \esc_html( (string) $label ); ?></option>
				<?php endif; ?>
			<?php endforeach; ?>
		</select>
		<?php $this->render_description( $field ); ?>
		<?php
	}

	private function render_image( FieldDefinition $field ): void {
		$setting       = $field->primary();
		$attachment_id = $this->settings->get_int( $setting->option_name );
		$image_url     = $this->settings->get_image_url( $setting->option_name, 'medium' );
		$hidden_class  = '' !== $image_url ? '' : ' blocks-hidden';
		?>
		<div class="blocks-variable-field blocks-variable-field-image">
			<div class="blocks-image-field" data-option="<?php echo \esc_attr( $setting->option_name ); ?>">
				<input type="hidden" id="<?php echo \esc_attr( $setting->option_name ); ?>" name="<?php echo \esc_attr( $setting->option_name ); ?>" value="<?php echo \esc_attr( (string) $attachment_id ); ?>" class="blocks-image-id" />
				<div class="blocks-image-preview<?php echo \esc_attr( $hidden_class ); ?>"><img src="<?php echo \esc_url( $image_url ); ?>" alt="" /></div>
				<p class="blocks-image-buttons">
					<button type="button" class="button blocks-upload-image"><?php \esc_html_e( 'Select Image', 'blocks' ); ?></button>
					<button type="button" class="button blocks-remove-image<?php echo \esc_attr( $hidden_class ); ?>"><?php \esc_html_e( 'Remove', 'blocks' ); ?></button>
				</p>
			</div>
			<?php $this->render_variable_control( $setting ); ?>
		</div>
		<?php
	}

	private function render_secret( FieldDefinition $field ): void {
		$setting    = $field->primary();
		$has_secret = $this->secrets->has( $setting->option_name );
		$status_id  = $setting->option_name . '-status';
		?>
		<div class="blocks-secret-field" data-has-secret="<?php echo $has_secret ? '1' : '0'; ?>" data-saved-label="<?php echo \esc_attr__( 'Saved and encrypted.', 'blocks' ); ?>" data-replace-label="<?php echo \esc_attr__( 'Enter a replacement, then save changes.', 'blocks' ); ?>" data-remove-label="<?php echo \esc_attr__( 'The secret will be removed when you save changes.', 'blocks' ); ?>" data-remove-button-label="<?php echo \esc_attr__( 'Remove', 'blocks' ); ?>" data-undo-button-label="<?php echo \esc_attr__( 'Undo remove', 'blocks' ); ?>">
			<input type="password" id="<?php echo \esc_attr( $setting->option_name ); ?>" name="<?php echo \esc_attr( $setting->option_name ); ?>" value="" class="regular-text" autocomplete="new-password" aria-describedby="<?php echo \esc_attr( $status_id ); ?>" placeholder="<?php echo \esc_attr( $has_secret ? \__( 'Encrypted secret saved', 'blocks' ) : \__( 'Enter API secret', 'blocks' ) ); ?>" <?php \disabled( $has_secret ); ?> />
			<input type="hidden" class="blocks-secret-clear" name="<?php echo \esc_attr( $setting->option_name ); ?>_clear" value="0" />
			<?php if ( $has_secret ) : ?>
				<button type="button" class="button blocks-secret-replace"><?php \esc_html_e( 'Replace', 'blocks' ); ?></button>
				<button type="button" class="button-link-delete blocks-secret-remove"><?php \esc_html_e( 'Remove', 'blocks' ); ?></button>
			<?php endif; ?>
			<span id="<?php echo \esc_attr( $status_id ); ?>" class="blocks-secret-status" aria-live="polite"><?php echo \esc_html( $has_secret ? \__( 'Saved and encrypted.', 'blocks' ) : \__( 'No secret saved.', 'blocks' ) ); ?></span>
		</div>
		<?php $this->render_description( $field ); ?>
		<?php
	}

	private function render_support_hours( FieldDefinition $field ): void {
		$start = $field->settings[0];
		$end   = $field->settings[1];
		?>
		<input type="number" id="<?php echo \esc_attr( $start->option_name ); ?>" name="<?php echo \esc_attr( $start->option_name ); ?>" value="<?php echo \esc_attr( (string) $this->settings->get_int( $start->option_name, (int) $start->default_value ) ); ?>" min="0" max="23" step="1" style="width: 70px;" />
		<span><?php \esc_html_e( 'to', 'blocks' ); ?></span>
		<input type="number" id="<?php echo \esc_attr( $end->option_name ); ?>" name="<?php echo \esc_attr( $end->option_name ); ?>" value="<?php echo \esc_attr( (string) $this->settings->get_int( $end->option_name, (int) $end->default_value ) ); ?>" min="0" max="23" step="1" style="width: 70px;" />
		<span><?php \esc_html_e( 'hours', 'blocks' ); ?></span>
		<p class="description"><?php \esc_html_e( 'Hours 0-23. Set both to 0 for closed days.', 'blocks' ); ?></p>
		<?php
	}

	private function render_product( FieldDefinition $field ): void {
		$setting    = $field->primary();
		$product_id = $this->settings->get_int( $setting->option_name );
		$label      = '';
		if ( 0 < $product_id && \function_exists( 'wc_get_product' ) ) {
			$product = \wc_get_product( $product_id );
			if ( $product instanceof \WC_Product ) {
				/* translators: 1: product formatted name, 2: variation count. */
				$label = \sprintf( \__( '%1$s (%2$d variations)', 'blocks' ), $product->get_formatted_name(), \count( $product->get_children() ) );
			}
		}
		?>
		<select id="<?php echo \esc_attr( $setting->option_name ); ?>" name="<?php echo \esc_attr( $setting->option_name ); ?>" class="wc-product-search" style="width: 50%;" data-placeholder="<?php \esc_attr_e( 'Search for a product&hellip;', 'blocks' ); ?>" data-action="woocommerce_json_search_products" data-allow_clear="true" data-limit="50">
			<?php
			if ( 0 < $product_id && '' !== $label ) :
				?>
				<option value="<?php echo \esc_attr( (string) $product_id ); ?>" selected="selected"><?php echo \esc_html( $label ); ?></option><?php endif; ?>
		</select>
		<?php $this->render_description( $field ); ?>
		<?php
	}

	private function render_variable_control( SettingDefinition $setting ): void {
		$public_token = PublicTokenRegistry::for_option( $setting->option_name );
		if ( null === $public_token ) {
			return;
		}

		$token = '{{blocks:' . $public_token . '}}';
		?>
		<span class="blocks-variable-token"><code><?php echo \esc_html( $token ); ?></code><button type="button" class="button blocks-copy-variable" data-token="<?php echo \esc_attr( $token ); ?>" aria-label="<?php echo \esc_attr( \sprintf( /* translators: %s: public variable token. */ \__( 'Copy %s', 'blocks' ), $token ) ); ?>"><span class="dashicons dashicons-admin-page" aria-hidden="true"></span></button></span>
		<?php
	}

	private function render_description( FieldDefinition $field ): void {
		if ( '' !== $field->description ) {
			?>
			<p class="description"><?php echo \esc_html( $field->description ); ?></p>
			<?php
		}
	}

	private function render_honeypot_description(): void {
		?>
		<p><?php \esc_html_e( 'When enabled, Blocks automatically injects the hidden honeypot and optional timing fields into every Contact Form 7 form. No form shortcode is required.', 'blocks' ); ?></p>
		<?php
	}

	private function scalar_arg( FieldDefinition $field, string $name, int $default_value ): int|float|string {
		$value = $field->render_args[ $name ] ?? $default_value;

		return \is_int( $value ) || \is_float( $value ) || \is_string( $value ) ? $value : $default_value;
	}
}

```
