$settings { * Module settings configuration. * * @type string $module Module ID. * @type string $title Panel title. * @type string $subtitle Panel subtitle. * @type array $fields Array of field definition arrays. * } * * @return void */ public static function create( $settings ) { $module_id = ( isset( $_GET['module'] ) ) ? sanitize_text_field( wp_unslash( $_GET['module'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended /** * Hook: merchant_module_settings * * @param array $settings Module settings. * @param string $module_id Module ID. * * @since 1.0 */ $settings = apply_filters( 'merchant_module_settings', $settings, $module_id ); Merchant_Settings_Saver::save_options( $settings ); $options = get_option( 'merchant', array() ); ?>
$settings Module settings with 'module' and 'fields' keys. * @param array $options All saved merchant options. * @param string $module_id The current module ID. * * @return void */ private static function render_fields( $settings, $options, $module_id ) { if ( empty( $settings['fields'] ) ) { return; } $current_module = Merchant_Admin_Modules::get_module_info( $settings['module'] ); $is_pro_module = ! merchant_is_pro_active() && isset( $current_module['pro'] ) && $current_module['pro'] === true; foreach ( $settings['fields'] as $field ) { $value = $field['default'] ?? null; if ( isset( $field['id'] ) && isset( $options[ $settings['module'] ][ $field['id'] ] ) ) { $value = $options[ $settings['module'] ][ $field['id'] ]; } $is_pro_field = ! merchant_is_pro_active() && isset( $field['pro'] ) && $field['pro'] === true; if ( $is_pro_module || $is_pro_field ) { self::disabled_field( $field, $value ); } else { self::field( $field, $value, $module_id ); } } } /** * Render a single field. * * Outputs the full field markup including the wrapper div, * title, description, and the field's inner HTML delegated * to {@see Merchant_Field_Registry}. * * @since 1.0 * * @param array $settings The field configuration array. * @param mixed $value The current saved value for this field. * @param string $module_id The module ID (used for nested field contexts). * * @return void */ public static function field( $settings, $value, $module_id = '' ) { if ( empty( $settings['type'] ) ) { return; } $type = $settings['type']; $id = $settings['id'] ?? ''; $is_upsell = ! merchant_is_pro_active() && isset( $settings['pro'] ) && $settings['pro'] === true; $value = self::resolve_field_value( $settings, $value, $module_id ); self::render_field_wrapper_open( $settings, $value, $module_id, $type, $id ); self::render_field_title( $settings, $id, $is_upsell ); echo '
'; self::render_field_inner( $type, $settings, $value, $module_id ); echo '
'; self::render_field_description( $settings, $value, $module_id ); echo ''; } /** * Resolve the default value for a field based on its type. * * Handles type-specific fallback logic for checkbox_multiple, * text, and url field types. * * @since 1.9.3 * * @param array $settings The field configuration array. * @param mixed $value The current raw value. * @param string $module_id The module ID. * * @return mixed The resolved value. */ private static function resolve_field_value( $settings, $value, $module_id ) { $type = $settings['type']; $id = $settings['id'] ?? ''; $default = $settings['default'] ?? null; if ( $value || 0 === $value || '0' === $value ) { return $value; } if ( $type === 'checkbox_multiple' ) { return (array) $default; } if ( in_array( $type, array( 'text', 'url' ), true ) && ! empty( $module_id ) ) { return Merchant_Option::get( $module_id, $id ); } return $default; } /** * Render the opening wrapper div for a field. * * Builds CSS classes and data attributes, then outputs the * opening div tag. * * @since 1.9.3 * * @param array $settings The field configuration array. * @param mixed $value The field value. * @param string $module_id The module ID. * @param string $type The field type. * @param string $id The field ID. * * @return void */ private static function render_field_wrapper_open( $settings, $value, $module_id, $type, $id ) { $class = ! empty( $settings['class'] ) ? ' ' . $settings['class'] : ''; $condition = $settings['condition'] ?? array(); $conditions = $settings['conditions'] ?? ''; $wrapper_classes = array( 'merchant-module-page-setting-field' ); $wrapper_classes[] = 'merchant-module-page-setting-field-' . $type; if ( ! empty( $class ) ) { $wrapper_classes[] = $class; } /** * Hook 'merchant_admin_module_field_wrapper_classes' * * @since 1.9.3 */ $wrapper_classes = apply_filters( 'merchant_admin_module_field_wrapper_classes', $wrapper_classes, $settings, $value, $module_id ); echo '
'; } /** * Render the title bar for a field, including Pro upsell badge. * * @since 1.9.3 * * @param array $settings The field configuration array. * @param string $id The field ID. * @param bool $is_upsell Whether to show the Pro upsell badge. * * @return void */ private static function render_field_title( $settings, $id, $is_upsell ) { if ( empty( $settings['title'] ) ) { return; } ?> $settings The field configuration array. * @param mixed $value The field value. * @param string $module_id The module ID. * * @return void */ private static function render_field_inner( $type, $settings, $value, $module_id ) { $registry = Merchant_Field_Registry::instance(); if ( $registry->has( $type ) ) { try { $field_instance = $registry->create( $type, $settings, $value, $module_id ); if ( $field_instance !== null ) { $field_instance->render(); } } catch ( \Exception $e ) { wp_trigger_error( __METHOD__, 'Merchant field render error (' . $type . '): ' . $e->getMessage() ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped } } else { printf( '
%s %s
', esc_html__( 'Merchant field error:', 'merchant' ), esc_html( sprintf( /* translators: %s: the unsupported field type slug */ __( 'Unknown field type "%s". Register it via the merchant_field_types filter.', 'merchant' ), $type ) ) ); _doing_it_wrong( __METHOD__, sprintf( 'Unknown field type "%s". Register it via the merchant_field_types filter.', esc_html( $type ) ), '2.2.5' ); } } /** * Render the description and hidden description for a field. * * @since 1.9.3 * * @param array $settings The field configuration array. * @param mixed $value The field value. * @param string $module_id The module ID. * * @return void */ private static function render_field_description( $settings, $value, $module_id ) { $hidden_desc = $settings['hidden_desc'] ?? ''; /** * Hook 'merchant_admin_module_field_hidden_description' * * @since 1.9.3 */ $hidden_desc = apply_filters( 'merchant_admin_module_field_hidden_description', $hidden_desc, $settings, $value, $module_id ); $desc = $settings['desc'] ?? ''; /** * Hook 'merchant_admin_module_field_description' * * @since 1.9.3 */ $desc = apply_filters( 'merchant_admin_module_field_description', $desc, $settings, $value, $module_id ); if ( ! empty( $desc ) ) { $hidden_desc_html = ''; if ( ! empty( $hidden_desc ) ) { $hidden_desc_html = '
' . esc_html__( 'Show more', 'merchant' ) . ''; $hidden_desc_html .= 'Merchant'; $hidden_desc_html .= '
'; } $desc_class = 'merchant-module-page-setting-field-desc' . ( $hidden_desc ? ' merchant-module-page-setting-field-desc-has-hidden-desc' : '' ); printf( '
%s%s
', esc_attr( $desc_class ), wp_kses_post( $desc ), wp_kses_post( $hidden_desc_html ) ); } if ( ! empty( $hidden_desc ) ) { printf( '
%s
', wp_kses_post( nl2br( $hidden_desc ) ) ); } } /** * Render a disabled (pro-gated) field. * * Renders the field via the normal registry path, then disables all * interactive elements so the user can see the control but not interact. * * @since 1.0 * * @param array $settings Field settings. * @param mixed $value Field value. * @param string $module_id Module ID. * * @return void */ public static function disabled_field( $settings, $value, $module_id = '' ) { ob_start(); self::field( $settings, $value, $module_id ); $field_html = (string) ob_get_clean(); $field_html = str_replace( array( '