[ $this, 'sanitize_options' ],
'default' => Helpers::get_defaults(),
) );
}
public function sanitize_options( $input ) {
$defaults = Helpers::get_defaults();
$sanitized = array();
// Migrate legacy style values saved before v2.2.0.
$raw_style = $input['style'] ?? '';
if ( 'default' === $raw_style ) { $raw_style = 'modern'; }
if ( 'custom' === $raw_style ) { $raw_style = 'academic'; }
$valid_styles = array( 'modern', 'academic', 'minimal', 'card', 'ruled', 'editorial', 'summary', 'bulletin', 'default', 'custom' );
$sanitized['style'] = in_array( $raw_style, $valid_styles, true ) ? $raw_style : $defaults['style'];
$sanitized['use_theme_css'] = ! empty( $input['use_theme_css'] );
$color_keys = array( 'title_color', 'text_color', 'bg_color', 'bg_color_end', 'accent_color', 'bullet_color', 'stripe_color' );
foreach ( $color_keys as $ck ) {
$sanitized[ $ck ] = sanitize_hex_color( $input[ $ck ] ?? '' ) ?: $defaults[ $ck ];
}
$sanitized['border_radius'] = absint( $input['border_radius'] ?? $defaults['border_radius'] );
$sanitized['border_radius'] = min( 50, max( 0, $sanitized['border_radius'] ) );
$valid_fonts = array( 'sans-serif', 'serif', 'humanist', 'monospace', 'slab', 'system' );
$sanitized['font_family'] = in_array( $input['font_family'] ?? '', $valid_fonts, true )
? $input['font_family'] : $defaults['font_family'];
$sanitized['enable_schema'] = ! empty( $input['enable_schema'] );
$valid_schema_types = array( 'CreativeWork', 'ScholarlyArticle', 'Article' );
$sanitized['schema_type'] = in_array( $input['schema_type'] ?? '', $valid_schema_types, true )
? $input['schema_type'] : $defaults['schema_type'];
$sanitized['custom_css_class'] = sanitize_html_class( $input['custom_css_class'] ?? '' );
$sanitized['hover_effect'] = ! empty( $input['hover_effect'] );
return $sanitized;
}
public function render_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$tabs = array(
'appearance' => __( 'Appearance', 'abstract-box' ),
'schema' => __( 'Schema', 'abstract-box' ),
'advanced' => __( 'Advanced', 'abstract-box' ),
'usage' => __( 'Usage', 'abstract-box' ),
);
$active_tab = 'appearance';
if ( isset( $_GET['tab'] ) ) {
$requested = sanitize_key( wp_unslash( $_GET['tab'] ) );
if ( array_key_exists( $requested, $tabs ) ) {
$active_tab = $requested;
}
}
include ABSTRACT_BOX_DIR . 'includes/views/admin-settings.php';
}
/* ── Card renderer helpers ────────────────────────────────────── */
/**
* Render a setting row as a flex card.
*
* @param string $label Field label.
* @param string $description Optional helper text (plain text, will be escaped).
* @param string $control_html Already-escaped HTML for the control.
* @param bool $stacked If true, control sits below label instead of inline-right.
*/
private function render_card( $label, $description, $control_html, $stacked = false ) {
$modifier = $stacked ? ' ab-field-card--stacked' : '';
echo '
';
foreach ( $style_opts as $val => $opt ) {
$checked = ( $saved_style === $val );
?>
/>
';
$style_html = ob_get_clean();
$this->render_card(
__( 'Box Style', 'abstract-box' ),
__( 'Choose the visual style of the abstract box.', 'abstract-box' ),
$style_html,
true
);
/* Use Theme CSS toggle */
$this->render_card(
__( 'Use Theme CSS', 'abstract-box' ),
__( 'Disable plugin styles and rely on your theme instead.', 'abstract-box' ),
$this->toggle( 'use_theme_css', $opts['use_theme_css'] )
);
?>
render_card(
__( 'Colour Presets', 'abstract-box' ),
__( 'Apply a pre-configured palette to all colour fields at once.', 'abstract-box' ),
$presets_html,
true
);
/* All five colour pickers — one compact card */
$color_fields = array(
'title_color' => __( 'Title', 'abstract-box' ),
'text_color' => __( 'Body Text', 'abstract-box' ),
'bg_color' => __( 'Background Start', 'abstract-box' ),
'bg_color_end' => __( 'Background End', 'abstract-box' ),
'accent_color' => __( 'Accent', 'abstract-box' ),
'bullet_color' => __( 'Bullet Dots', 'abstract-box' ),
'stripe_color' => __( 'Bulletin Stripe', 'abstract-box' ),
);
?>
__( 'Sans-Serif (Modernist)', 'abstract-box' ),
'serif' => __( 'Serif (Traditional)', 'abstract-box' ),
'humanist' => __( 'Humanist Sans', 'abstract-box' ),
'monospace' => __( 'Monospace', 'abstract-box' ),
'slab' => __( 'Slab Serif (Academic)', 'abstract-box' ),
'system' => __( 'System Default', 'abstract-box' ),
);
?>
$lbl ) : ?>
>
render_card(
__( 'Enable Schema Output', 'abstract-box' ),
__( 'Output schema.org JSON-LD structured data in the page head.', 'abstract-box' ),
$this->toggle( 'enable_schema', $opts['enable_schema'] )
);
ob_start();
?>
>CreativeWork
>ScholarlyArticle
>Article
render_card(
__( 'Schema Type', 'abstract-box' ),
__( 'CreativeWork is recommended to avoid conflicts with SEO plugin graphs.', 'abstract-box' ),
ob_get_clean()
);
}
/* ── Tab: Advanced ────────────────────────────────────────────── */
public function render_advanced_tab() {
$opts = Helpers::get_options();
settings_fields( 'abstract_box_group' );
?>
render_card(
__( 'Custom CSS Class', 'abstract-box' ),
__( 'Add an extra CSS class to every abstract box for targeted theme styling.', 'abstract-box' ),
ob_get_clean()
);
$this->render_card(
__( 'Hover Effect', 'abstract-box' ),
__( 'Enable a subtle lift-and-shadow on hover (desktop only).', 'abstract-box' ),
$this->toggle( 'hover_effect', $opts['hover_effect'] )
);
/* ── CSS class reference ─────────────────────────────────── */
?>
.abstract-box
.abstract-box__title
.abstract-box__subtitle
.abstract-box__content
.abstract-box--custom
array( 'style', 'use_theme_css', 'title_color', 'text_color', 'bg_color', 'bg_color_end', 'accent_color', 'bullet_color', 'stripe_color', 'border_radius', 'font_family' ),
'schema' => array( 'enable_schema', 'schema_type' ),
'advanced' => array( 'custom_css_class', 'hover_effect' ),
);
foreach ( $tab_keys as $tab => $keys ) {
if ( $tab === $active_tab ) {
continue;
}
foreach ( $keys as $key ) {
$value = isset( $options[ $key ] ) ? $options[ $key ] : ( $defaults[ $key ] ?? '' );
if ( is_bool( $value ) ) {
if ( $value ) {
echo '
';
}
continue;
}
echo '
';
}
}
}
/* ── Live preview renderer ────────────────────────────────────── */
public function render_preview() {
$options = Helpers::get_options();
$style_map = array(
'modern' => 'abstract-box--modern',
'academic' => 'abstract-box--academic',
'minimal' => 'abstract-box--minimal',
'card' => 'abstract-box--card',
'ruled' => 'abstract-box--ruled',
'editorial' => 'abstract-box--editorial',
'summary' => 'abstract-box--summary',
'bulletin' => 'abstract-box--bulletin',
'default' => '',
'custom' => 'abstract-box--academic',
);
$modifier = $style_map[ $options['style'] ] ?? '';
$classes = 'abstract-box';
if ( $modifier ) {
$classes .= ' ' . $modifier;
}
$font_stack = Helpers::font_stack( $options['font_family'] );
$radius = absint( $options['border_radius'] );
$css_vars = '--ab-bg-color: ' . sanitize_hex_color( $options['bg_color'] ) . '; ';
$css_vars .= '--ab-bg-color-end: ' . sanitize_hex_color( $options['bg_color_end'] ) . '; ';
$css_vars .= '--ab-accent-color: ' . sanitize_hex_color( $options['accent_color'] ) . '; ';
if ( ! empty( $options['bullet_color'] ) ) {
$css_vars .= '--ab-bullet-color: ' . sanitize_hex_color( $options['bullet_color'] ) . '; ';
}
if ( ! empty( $options['stripe_color'] ) ) {
$css_vars .= '--ab-stripe-color: ' . sanitize_hex_color( $options['stripe_color'] ) . '; ';
}
$css_vars .= '--ab-title-color: ' . sanitize_hex_color( $options['title_color'] ) . '; ';
$css_vars .= '--ab-text-color: ' . sanitize_hex_color( $options['text_color'] ) . '; ';
$css_vars .= '--ab-border-radius: ' . $radius . 'px; ';
$css_vars .= '--ab-font-family: ' . $font_stack . ';';
$html = '
';
$html .= '
' . esc_html__( 'Abstract', 'abstract-box' ) . '
';
$html .= '
' . esc_html__( 'Section 1.1', 'abstract-box' ) . '
';
$html .= '
';
$html .= '
' . esc_html__( 'This is a live preview of the abstract box. Changes to style, colours, font, and border radius are reflected here immediately without saving.', 'abstract-box' ) . '
';
$html .= '
';
$html .= '
';
return $html;
}
}