'', 'prefix' => '', 'translation_strings' => array(), ); $args = wp_parse_args( $args, $defaults ); foreach ( $args as $name => $value ) { $this->$name = $value; } } /** * Get field description for display. * * @param array $args settings Arguments array. * * @return string Description of the field. */ public function get_field_description( $args ) { $desc = ! empty( $args['desc'] ) ? '
' . wp_kses_post( $args['desc'] ) . '
' : ''; /** * After Settings Output filter * * @param string $desc Description of the field. * @param array $args Arguments array. */ $desc = apply_filters( $this->prefix . '_setting_field_description', $desc, $args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound return $this->get_modified_indicator( $args ) . $desc . $this->get_default_description( $args ); } /** * Get the "Default: value" explanation shown below a field's description. * * Checkbox/toggle fields are excluded - their default state is visible * from the toggle itself and the modified indicator. * * @param array $args Field arguments. * @return string Default description HTML or empty string. */ protected function get_default_description( $args ) { $type = $args['type'] ?? 'text'; if ( in_array( $type, array( 'checkbox', 'toggle', 'header', 'descriptive_text', 'repeater' ), true ) ) { return ''; } $default = $this->get_field_default( $args ); if ( is_array( $default ) ) { $default = implode( ',', array_map( 'strval', $default ) ); } $default = trim( (string) $default, " \t\n\r\0\x0B" ); // Map option keys to their labels where the field has a choices list. if ( ! empty( $args['options'] ) && is_array( $args['options'] ) ) { if ( 'radiodesc' === $type && '' !== $default ) { foreach ( $args['options'] as $option ) { if ( isset( $option['id'], $option['name'] ) && (string) $option['id'] === $default ) { $default = (string) $option['name']; break; } } } elseif ( in_array( $type, array( 'select', 'radio' ), true ) ) { if ( isset( $args['options'][ $default ] ) && is_string( $args['options'][ $default ] ) ) { $default = $args['options'][ $default ]; } } elseif ( '' !== $default && 'multicheck' === $type ) { $labels = array(); foreach ( explode( ',', $default ) as $key ) { $key = trim( $key, " \t\n\r\0\x0B" ); $labels[] = isset( $args['options'][ $key ] ) && is_string( $args['options'][ $key ] ) ? $args['options'][ $key ] : $key; } $default = implode( ', ', $labels ); } } $label = $this->translation_strings['default_label'] ?? 'Default'; $none = $this->translation_strings['default_none'] ?? 'None'; $value_html = '' === $default ? esc_html( $none ) : '' . esc_html( $default ) . '';
return '' . esc_html( $label ) . ': ' . $value_html . '
'; } /** * Get the default value of a field, mirroring the back-compat logic in * Settings_API::settings_defaults(). * * @param array $args Field arguments. * @return mixed Default value. */ protected function get_field_default( $args ) { if ( isset( $args['default'] ) ) { return $args['default']; } $type = $args['type'] ?? 'text'; // Back-compat: checkbox used a truthy 'options' to indicate checked by default. if ( 'checkbox' === $type || 'toggle' === $type ) { return empty( $args['options'] ) ? 0 : 1; } // Back-compat: legacy configs used 'options' to store default values for text-like fields. if ( in_array( $type, array( 'textarea', 'css', 'html', 'text', 'url', 'csv', 'color', 'numbercsv', 'postids', 'posttypes', 'number', 'wysiwyg', 'file', 'password' ), true ) && isset( $args['options'] ) && is_scalar( $args['options'] ) ) { return $args['options']; } return ''; } /** * Whether the current value of a field differs from its default. * * @param array $args Field arguments. * @return bool True when the saved value does not match the default. */ protected function is_field_modified( $args ) { $type = $args['type'] ?? 'text'; if ( in_array( $type, array( 'header', 'descriptive_text', 'repeater' ), true ) ) { return false; } // Fields can opt out, e.g. when the default is environment-derived // (URLs based on the plugin location) and literal comparison is noise. if ( isset( $args['modified_indicator'] ) && false === $args['modified_indicator'] ) { return false; } $default = $this->get_field_default( $args ); $value = $this->get_field_value( $args ); if ( 'checkbox' === $type || 'toggle' === $type ) { return (bool) $value !== (bool) $default; } if ( is_array( $value ) || is_array( $default ) || in_array( $type, array( 'multicheck', 'posttypes', 'taxonomies' ), true ) ) { return $this->normalize_list_value( $value ) !== $this->normalize_list_value( $default ); } return trim( (string) $value, " \t\n\r\0\x0B" ) !== trim( (string) $default, " \t\n\r\0\x0B" ); } /** * Normalize a list-style value (array or comma-separated string) into a * sorted comma-separated string for comparison. * * @param mixed $value Value to normalize. * @return string Normalized value. */ protected function normalize_list_value( $value ) { if ( is_array( $value ) ) { $value = implode( ',', array_map( 'strval', $value ) ); } $list = array_filter( array_map( 'trim', explode( ',', (string) $value ) ), static function ( $item ) { return '' !== $item; } ); sort( $list ); return implode( ',', $list ); } /** * Get the modified-from-default indicator for a field. * * Returns a small dot with a tooltip when the saved value differs from * the default; the legend below the form buttons explains it. * * @param array $args Field arguments. * @return string Indicator HTML or empty string. */ public function get_modified_indicator( $args ) { if ( ! $this->is_field_modified( $args ) ) { return ''; } $title = $this->translation_strings['modified_field'] ?? 'Modified from default setting'; return sprintf( '', esc_attr( $title ) ); } /** * Get the value of a settings field. * * @param string $option Settings field name. * @param mixed $default_value Default value if option is not found. * @return mixed */ public function get_option( $option, $default_value = '' ) { $options = \get_option( $this->settings_key ); if ( isset( $options[ $option ] ) ) { return $options[ $option ]; } return $default_value; } /** * Get field value from args or options. * * @param array $args Field arguments. * @return mixed Field value. */ protected function get_field_value( $args ) { return $args['value'] ?? $this->get_option( $args['id'], $args['default'] ?? '' ); } /** * Get sanitized field class string. * * @param array $args Field arguments. * @param string $default_class Default class to prepend. * @return string Sanitized class string. */ protected function get_field_class( $args, $default_class = '' ) { $class = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['field_class'] ?? '' ) ) ); if ( $default_class ) { $class = $default_class . ' ' . $class; } return trim( $class, " \t\n\r\0\x0B" ); } /** * Get placeholder attribute string. * * @param array $args Field arguments. * @return string Placeholder attribute or empty string. */ protected function get_placeholder_attribute( $args ) { return empty( $args['placeholder'] ) ? '' : ' placeholder="' . esc_attr( $args['placeholder'] ) . '"'; } /** * Get boolean state attributes (disabled, readonly, required). * * @param array $args Field arguments. * @return string Concatenated boolean attributes. */ protected function get_boolean_attributes( $args ) { $disabled = $this->is_field_disabled( $args ) ? ' disabled="disabled"' : ''; $readonly = ( isset( $args['readonly'] ) && true === $args['readonly'] ) ? ' readonly="readonly"' : ''; $required = ( isset( $args['required'] ) && true === $args['required'] ) ? ' required' : ''; return $disabled . $readonly . $required; } /** * Whether a field is disabled, either explicitly or because it is a pro-only field. * * @param array $args Field arguments. * @return bool Whether the field is disabled. */ protected function is_field_disabled( $args ) { return ! empty( $args['disabled'] ) || ! empty( $args['pro'] ); } /** * Get field ID and name attributes. * * @param array $args Field arguments. * @return array Array containing field_id and field_name. */ protected function get_field_attributes( $args ) { $id = sanitize_key( $args['id'] ); if ( isset( $args['_repeater_id'] ) && isset( $args['_index'] ) ) { $field_id = sprintf( '%s-%s-%s-fields-%s', $this->settings_key, $args['_repeater_id'], $args['_index'], $id ); $field_name = sprintf( '%s[%s][%s][fields][%s]', $this->settings_key, $args['_repeater_id'], $args['_index'], $id ); } else { $field_id = $this->settings_key . '-' . $id; $field_name = $this->settings_key . '[' . $id . ']'; } return array( 'field_id' => $field_id, 'field_name' => $field_name, ); } /** * Returns the allowed HTML tags and attributes for settings form output. * * Use the `{prefix}_settings_form_allowed_html` filter to add extra tags or * attributes needed by custom field types or `field_attributes` entries. * * @return array