# subscription/1.9.6/includes/Admin/SettingsHelper.php

Subscriptions for WooCommerce with Stripe Recurring Payments, version 1.9.6. 583 lines.

- Page: https://pluginprobe.com/plugins/subscription/1.9.6/code/includes/Admin/SettingsHelper.php
- Raw: https://pluginprobe.com/plugins/subscription/1.9.6/raw/includes/Admin/SettingsHelper.php
- Modified: 2026-04-12T05:55:20+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/subscription/1.9.6/code/includes/Admin/SettingsHelper.php#L10-L20`.

```php
<?php
/**
 * Settings Helper File
 *
 * @package SpringDevs\Subscription\Admin
 */

namespace SpringDevs\Subscription\Admin;

/**
 * Settings Helper Class
 *
 * @package SpringDevs\Subscription\Admin
 */
class SettingsHelper {
	/**
	 * Singleton instance
	 *
	 * @var SettingsHelper|null
	 */
	private static $instance = null;

	/**
	 * Get singleton instance
	 *
	 * @return SettingsHelper
	 */
	public static function get_instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Initialize the class.
	 */
	private function __construct() {
		add_filter( 'process_subscrpt_settings_fields', [ $this, 'process_settings_fields' ], 100, 1 );
	}

	/**
	 * Process settings fields.
	 *
	 * @param array $fields Settings fields.
	 * @return array Processed settings fields.
	 */
	public function process_settings_fields( $fields ) {
		// Group settings fields.
		$fields = $this->group_settings_fields( $fields );

		// Sort fields by priority (groups & fields).
		$fields = $this->sort_settings_fields( $fields );

		return $fields;
	}

	/**
	 * Group settings fields.
	 *
	 * @param array $fields Settings fields.
	 * @return array Processed settings fields.
	 */
	public function group_settings_fields( $fields ) {
		$tmp_fields = [];
		foreach ( $fields as $field ) {
			$field_group = $field['group'] ?? 'main';

			if ( $field['type'] === 'heading' ) {
				$group_priority                         = $field['priority'] ?? 0;
				$tmp_fields[ $field_group ]['priority'] = $group_priority;
				$field['priority']                      = -1;
			}

			$tmp_fields[ $field_group ]['fields'][] = $field;
		}
		return $tmp_fields;
	}

	/**
	 * Sort settings fields.
	 *
	 * @param array $fields Settings fields.
	 * @return array Processed settings fields.
	 */
	public function sort_settings_fields( $fields ) {
		// Sort groups by priority.
		uasort(
			$fields,
			function ( $a, $b ) {
				$priority_a = $a['priority'] ?? 0;
				$priority_b = $b['priority'] ?? 0;
				return $priority_a <=> $priority_b;
			}
		);

		// Sort fields within each group by priority.
		foreach ( $fields as $group_key => $group_data ) {
			uasort(
				$group_data['fields'],
				function ( $a, $b ) {
					$priority_a = $a['priority'] ?? 0;
					$priority_b = $b['priority'] ?? 0;
					return $priority_a <=> $priority_b;
				}
			);
			$fields[ $group_key ]['fields'] = $group_data['fields'];
		}
		return $fields;
	}

	/**
	 * Render specified settings field.
	 *
	 * @param string $field Field type.
	 * @param array  $args Field arguments.
	 * @param bool   $should_print Whether to print the field or return as HTML string.
	 */
	public static function render_settings_field( $field, $args, $should_print = true ) {
		if ( empty( $field ) ) {
			$field = 'input'; // Default field type.
			subscrpt_write_debug_log( "[SettingsHelper] Field type not specified. Defaulting to 'input'." );
		}

		switch ( $field ) {
			case 'heading':
				return self::render_heading( $args, $should_print );
			case 'switch':
			case 'toggle':
				return self::render_switch_field( $args, $should_print );
			case 'select':
				return self::render_select_field( $args, $should_print );
			case 'multi_select':
				return self::render_multiselect_field( $args, $should_print );
			case 'join':
				return self::render_joined_field( $args, $should_print );
			case 'input':
			default:
				return self::render_input_field( $args, $should_print );
		}
	}


	/**
	 * Text Element HTML.
	 *
	 * @param array $args Same as 'render_text_field'.
	 * @param bool  $join_item Whether to return element for 'join' container or not.
	 */
	public static function inp_element( $args = [], $join_item = false ) {
		$id          = $args['id'];
		$value       = $args['value'] ?? '';
		$placeholder = $args['placeholder'] ?? '';
		$type        = $args['type'] ?? 'text';

		$join_class = $join_item ? 'join-item mx-0!' : '';

		$disabled_attr = isset( $args['disabled'] ) && $args['disabled'] ? 'disabled' : '';

		$style_attr = 'outline-offset: 0.5px !important; outline-color: #e5e7eb !important;';
		if ( isset( $args['style'] ) ) {
			$style_attr .= ' ' . $args['style'];
		}

		$other_attrs_html = '';
		foreach ( ( $args['attributes'] ?? [] ) as $attr_key => $attr_value ) {
			$other_attrs_html .= sprintf( ' %s="%s" ', esc_attr( $attr_key ), esc_attr( $attr_value ) );
		}

		ob_start();
		?>
			<input 
				id="<?php echo esc_attr( $id ); ?>"
				name="<?php echo esc_attr( $id ); ?>"
				class="input! min-w-80! max-w-full! <?php echo esc_attr( $join_class ); ?>"
				style="<?php echo esc_attr( $style_attr ); ?>"
				type="<?php echo esc_attr( $type ); ?>"
				placeholder="<?php echo esc_attr( $placeholder ); ?>"
				value="<?php echo esc_attr( $value ); ?>"
				<?php echo esc_attr( $disabled_attr ); ?>
				<?php echo wp_kses_post( $other_attrs_html ); ?>
			/>
		<?php
		return ob_get_clean();
	}

	/**
	 * Select Element HTML.
	 *
	 * @param array $args Same as 'render_select_field'.
	 * @param bool  $join_item Whether to return element for 'join' container or not.
	 */
	public static function select_element( $args = [], $join_item = false ) {
		$id    = $args['id'];
		$value = $args['value'] ?? '';

		$join_class        = $join_item ? 'join-item mx-0!' : '';
		$wc_enhanced_class = isset( $args['enhanced'] ) && $args['enhanced'];

		$basic_classes = 'select! min-w-80! max-w-full!';

		// WC Select2 style (multiselect).
		if ( $wc_enhanced_class ) {
			// Enqueue WooCommerce enhanced select script & styles.
			wp_enqueue_style( 'woocommerce_admin_styles' );
			wp_enqueue_script( 'wc-enhanced-select' );

			// Update basic classes for wc-enhanced-select.
			$basic_classes = 'min-w-80! max-w-full! wc-enhanced-select';
		}

		// Need to prefix name with [] for multiple select.
		$name_prefix = '';
		if ( isset( $args['attributes']['multiple'] ) && $args['attributes']['multiple'] ) {
			$name_prefix = '[]';
		}

		$style_attr = 'outline-offset: 0.5px !important; outline-color: #e5e7eb !important;';
		if ( isset( $args['style'] ) ) {
			$style_attr .= ' ' . $args['style'];
		}

		$other_attrs_html = '';
		foreach ( ( $args['attributes'] ?? [] ) as $attr_key => $attr_value ) {
			$other_attrs_html .= sprintf( ' %s="%s" ', esc_attr( $attr_key ), esc_attr( $attr_value ) );
		}

		$options_html = '';
		foreach ( ( $args['options'] ?? [] ) as $value => $label ) {
			$selected = false;
			if ( isset( $args['selected'] ) ) {
				if ( is_array( $args['selected'] ) ) {
					$selected = in_array( $value, $args['selected'], true );
				} else {
					$selected = $args['selected'] === $value;
				}
			}

			$disabled = false;
			if ( isset( $args['disabled'] ) ) {
				if ( is_array( $args['disabled'] ) ) {
					$disabled = in_array( $value, $args['disabled'], true );
				} else {
					$disabled = $args['disabled'] === $value;
				}
			}

			$options_tmp_html = sprintf(
				'<option value="%s" %s %s>%s</option>',
				esc_attr( $value ),
				$selected ? 'selected' : '',
				$disabled ? 'disabled' : '',
				esc_html( $label ),
			);
			$options_html    .= $options_tmp_html;
		}

		ob_start();
		?>
			<select
				id="<?php echo esc_attr( $id ); ?>"
				name="<?php echo esc_attr( $id . $name_prefix ); ?>"
				class="<?php echo esc_attr( $basic_classes . ' ' . $join_class ); ?>"
				style="<?php echo esc_attr( $style_attr ); ?>"
				<?php echo wp_kses_post( $other_attrs_html ); ?>
			>
				<?php
					// Output intentionally not escaped as options are already escaped during generation & re-escaping breaks the HTML structure.
					// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
					echo $options_html;
				?>
			</select>
		<?php
		return ob_get_clean();
	}

	/**
	 * Render Field Heading.
	 *
	 * - Args:
	 *   - title (string) - Field title.
	 *   - description (string) - Field description (optional).
	 *
	 * @param array $args Field arguments.
	 * @param bool  $should_print Whether to print the field or return as HTML string.
	 */
	public static function render_heading( $args = [], $should_print = true ) {
		$title       = $args['title'] ?? '';
		$description = $args['description'] ?? '';

		ob_start();
		?>
			<div class="my-4 first-of-type:mt-0">
				<h2 class="m-0!"><?php echo esc_html( $title ); ?></h2>

				<?php if ( ! empty( $description ) ) : ?>
					<p class="mb-0! mt-2! ml-0.5! text-[13px]! text-gray-500!">
						<?php echo wp_kses_post( $description ); ?>
					</p>
				<?php endif; ?>
			</div>
		<?php
		$html_content = ob_get_clean();

		// Output not escaped intentionally. Breaks the HTML structure when escaped.
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		return $should_print ? print( $html_content ) : $html_content;
	}

	/**
	 * Render Text field.
	 *
	 * - Args:
	 *   - id (string) - Field ID.
	 *   - title (string) - Field title.
	 *   - description (string) - Field description (optional).
	 *   - value (string) - Default value.
	 *   - placeholder (string) - Default placeholder.
	 *   - disabled (bool) - Disabled status.
	 *   - type (string) - Input type [text, email, number, date, time, etc.].
	 *
	 * @param array $args Field arguments.
	 * @param bool  $should_print Whether to print the field or return as HTML string.
	 */
	public static function render_input_field( $args = [], $should_print = true ) {
		$title       = $args['title'] ?? '';
		$description = $args['description'] ?? '';

		// Return error if ID is not provided.
		if ( empty( $args['id'] ?? '' ) ) {
			$field_hint = empty( $title ) ? 'Error' : $title;
			$no_id_msg  = '<p><strong>' . $field_hint . ':</strong> ' . __( 'Field ID is required.', 'subscription' ) . '</p>';
			return $should_print ? print wp_kses_post( $no_id_msg ) : $no_id_msg;
		}

		// Input HTML.
		$text_el_html = self::inp_element( $args );

		ob_start();
		?>
			<div class="grid grid-cols-6 gap-4">
				<span class="font-semibold text-sm mt-0.5"><?php echo esc_html( $title ); ?></span>

				<div class="col-span-5">
					<?php
						// Output intentionally not escaped as element is already escaped during generation & re-escaping breaks the HTML structure.
						// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
						echo $text_el_html;
					?>
					<br/>
					<?php if ( ! empty( $description ) ) : ?>
						<p class="mb-0! mt-2! ml-0.5! text-[13px]! text-gray-500!">
							<?php echo wp_kses_post( $description ); ?>
						</p>
					<?php endif; ?>
				</div>
			</div>
		<?php
		$html_content = ob_get_clean();

		// Output not escaped intentionally. Breaks the HTML structure when escaped.
		// All form elements inside $html_content are pre-escaped during generation (esc_attr, esc_html).
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		return $should_print ? print( $html_content ) : $html_content;
	}

	/**
	 * Render Switch field.
	 *
	 * - Args:
	 *   - id (string) - Field ID.
	 *   - title (string) - Field title.
	 *   - label (string) - Checkbox label.
	 *   - description (string) - Field description (optional).
	 *   - value (string) - Checked value.
	 *   - checked (bool) - Checked status.
	 *   - disabled (bool) - Disabled status.
	 *
	 * @param array $args Field arguments.
	 * @param bool  $should_print Whether to print the field or return as HTML string.
	 */
	public static function render_switch_field( $args = [], $should_print = true ) {
		$id          = $args['id'];
		$title       = $args['title'] ?? '';
		$label       = $args['label'] ?? '';
		$description = $args['description'] ?? '';
		$value       = $args['value'] ?? '0';

		// Return error if ID is not provided.
		if ( empty( $args['id'] ?? '' ) ) {
			$field_hint = empty( $title ) ? 'Error' : $title;
			$no_id_msg  = '<p><strong>' . $field_hint . ':</strong> ' . __( 'Field ID is required.', 'subscription' ) . '</p>';
			return $should_print ? print wp_kses_post( $no_id_msg ) : $no_id_msg;
		}

		$description_html = '';
		if ( ! empty( $description ) ) {
			$description_html = sprintf(
				'<p class="mb-0! mt-2! ml-0.5! text-[13px]! text-gray-500!">%s</p>',
				wp_kses_post( $description )
			);
		}

		$style_attr = '';
		if ( isset( $args['style'] ) ) {
			$style_attr .= ' ' . $args['style'];
		}

		$other_attrs_html = '';
		foreach ( ( $args['attributes'] ?? [] ) as $attr_key => $attr_value ) {
			$other_attrs_html .= sprintf( ' %s="%s" ', esc_attr( $attr_key ), esc_attr( $attr_value ) );
		}

		$checked_attr  = isset( $args['checked'] ) && (bool) $args['checked'] ? 'checked' : '';
		$disabled_attr = isset( $args['disabled'] ) && (bool) $args['disabled'] ? 'disabled' : '';

		ob_start();
		?>
			<div class="grid grid-cols-6 gap-4">
				<span class="font-semibold text-sm mt-0.5"><?php echo esc_html( $title ); ?></span>

				<div class="col-span-5">
					<label for="<?php echo esc_attr( $id ); ?>">
						<input 
							id="<?php echo esc_attr( $id ); ?>"
							name="<?php echo esc_attr( $id ); ?>"
							class="wp-subscription-toggle"
							style="<?php echo esc_attr( $style_attr ); ?>"
							type="checkbox" 
							value="<?php echo esc_attr( $value ); ?>"
							<?php echo esc_attr( $checked_attr ); ?>
							<?php echo esc_attr( $disabled_attr ); ?>
							<?php
								// Output intentionally not escaped as element is already escaped during generation & re-escaping breaks the HTML structure.
								// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
								echo $other_attrs_html;
							?>
						/>
						<span class="wp-subscription-toggle-ui" aria-hidden="true"></span>

						<span class="ml-2 text-sm align-middle"><?php echo esc_html( $label ); ?></span>
					</label>

					<br/>
					<?php echo wp_kses_post( $description_html ); ?>
				</div>
			</div>
		<?php
		$html_content = ob_get_clean();

		// Output not escaped intentionally. Breaks the HTML structure when escaped.
		// All form elements inside $html_content are pre-escaped during generation (esc_attr, esc_html).
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		return $should_print ? print( $html_content ) : $html_content;
	}

	/**
	 * Render Select field.
	 *
	 * - Args:
	 *   - id (string) - Field ID.
	 *   - title (string) - Field title.
	 *   - description (string) - Field description (optional).
	 *   - options (array) - Field options [value => label].
	 *   - selected (string) - Selected option value.
	 *   - disabled (string|array) - Disabled option value(s).
	 *
	 * @param array $args Field arguments.
	 * @param bool  $should_print Whether to print the field or return as HTML string.
	 */
	public static function render_select_field( $args = [], $should_print = true ) {
		$title       = $args['title'] ?? '';
		$description = $args['description'] ?? '';

		// Return error if ID is not provided.
		if ( empty( $args['id'] ?? '' ) ) {
			$field_hint = empty( $title ) ? 'Error' : $title;
			$no_id_msg  = '<p><strong>' . $field_hint . ':</strong> ' . __( 'Field ID is required.', 'subscription' ) . '</p>';
			return $should_print ? print wp_kses_post( $no_id_msg ) : $no_id_msg;
		}

		// Select HTML.
		$select_el_html = self::select_element( $args );

		ob_start();
		?>
			<div class="grid grid-cols-6 gap-4">
				<span class="font-semibold text-sm mt-0.5"><?php echo esc_html( $title ); ?></span>

				<div class="col-span-5">
					<?php
						// Output intentionally not escaped as element is already escaped during generation & re-escaping breaks the HTML structure.
						// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
						echo $select_el_html;
					?>
					<br/>
					<?php if ( ! empty( $description ) ) : ?>
						<p class="mb-0! mt-2! ml-0.5! text-[13px]! text-gray-500!">
							<?php echo wp_kses_post( $description ); ?>
						</p>
					<?php endif; ?>
				</div>
			</div>
		<?php
		$html_content = ob_get_clean();

		// Output not escaped intentionally. Breaks the HTML structure when escaped.
		// All form elements inside $html_content are pre-escaped during generation (esc_attr, esc_html).
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		return $should_print ? print( $html_content ) : $html_content;
	}

	/**
	 * Render Multiselect field.
	 * Just a wrapper over 'render_select_field' with multiple attribute.
	 *
	 * @param array $args Field arguments.
	 * @param bool  $should_print Whether to print the field or return as HTML string.
	 */
	public static function render_multiselect_field( $args = [], $should_print = true ) {
		$default_multiselect_args = [
			'attributes' => [
				'multiple' => 'multiple',
			],
			'enhanced'   => true,
		];

		$args = wp_parse_args( $args, $default_multiselect_args );

		return self::render_select_field( $args, $should_print );
	}

	/**
	 * Render Joined field with multiple elements.
	 *
	 * - Args:
	 *   - title (string) - Field title.
	 *   - description (string) - Field description (optional).
	 *   - vertical (bool) - Whether to show items vertically or not.
	 *   - elements ([...string]) - Array of HTML elements to join.
	 *
	 * @param array $args Field arguments.
	 * @param bool  $should_print Whether to print the field or return as HTML string.
	 */
	public static function render_joined_field( $args = [], $should_print = true ) {
		$title       = $args['title'] ?? '';
		$description = $args['description'] ?? '';

		$vertical_class = ( $args['vertical'] ?? false ) ? 'join-vertical' : '';

		ob_start();
		?>
			<div class="grid grid-cols-6 gap-4">
				<span class="font-semibold text-sm mt-0.5"><?php echo esc_html( $title ); ?></span>

				<div class="col-span-5">
					<div class="join <?php echo esc_attr( $vertical_class ); ?>">
						<?php
						foreach ( ( $args['elements'] ?? [] ) as $element_html ) {
							// Output intentionally not escaped as element is already escaped during generation & re-escaping breaks the HTML structure.
							// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
							echo $element_html;
						}
						?>
					</div>
					<br/>
					<?php if ( ! empty( $description ) ) : ?>
						<p class="mb-0! mt-2! ml-0.5! text-[13px]! text-gray-500!">
							<?php echo wp_kses_post( $description ); ?>
						</p>
					<?php endif; ?>
				</div>
			</div>
		<?php
		$html_content = ob_get_clean();

		// Output not escaped intentionally. Breaks the HTML structure when escaped.
		// All form elements inside $html_content are pre-escaped during generation (esc_attr, esc_html).
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		return $should_print ? print( $html_content ) : $html_content;
	}
}

```
