fields_general = new AHA_Hero_Slider_Settings_Fields( self::OPTION_GENERAL, array( $this, 'get_settings' ) ); $this->fields_design = new AHA_Hero_Slider_Settings_Fields( self::OPTION_DESIGN, array( $this, 'get_settings' ) ); $this->fields_shortcode = new AHA_Hero_Slider_Settings_Fields( self::OPTION_SHORTCODE, array( $this, 'get_settings' ) ); add_action( 'admin_menu', array( $this, 'register_menu' ), 9 ); add_action( 'admin_menu', array( $this, 'register_cpt_submenu' ), 20 ); add_action( 'admin_init', array( $this, 'maybe_migrate_legacy_settings' ), 8 ); add_action( 'admin_init', array( $this, 'register_settings' ) ); add_action( 'admin_init', array( $this, 'maybe_save_uninstall_preference' ) ); add_filter( 'aha_hero_slider_settings_tabs', array( $this, 'append_uninstall_tab' ), 100 ); } /** * One-time migration from legacy unified option to per-tab options. * * @return void */ public function maybe_migrate_legacy_settings() { if ( get_option( 'aha_hero_slider_migrated_to_per_tab' ) ) { return; } $legacy = get_option( self::OPTION_LEGACY, array() ); if ( empty( $legacy ) || ! is_array( $legacy ) ) { update_option( 'aha_hero_slider_migrated_to_per_tab', '1' ); return; } // Field mapping: which legacy fields belong to each tab $field_map = array( 'general' => array( 'enable_slideshow', 'enable_custom_image_sizes', 'shortcode_autoplay_speed', 'shortcode_show_arrows', 'shortcode_border_radius', ), 'design' => array( 'color_arrow', 'color_arrow_icon', 'color_bullet', 'color_bullet_active', 'color_progress_track', 'color_progress_fill', 'overlay_color', 'overlay_enabled', 'overlay_opacity', 'color_badge', 'color_headline', 'color_subtitle', 'color_btn1_bg', 'color_btn1_text', 'color_btn1_border', 'color_btn1_radius', 'color_btn2_bg', 'color_btn2_text', 'color_btn2_border', 'color_btn2_radius', 'color_mobile_bg', 'color_btn1_bg_mobile', 'color_btn1_text_mobile', 'color_btn1_border_mobile', 'color_btn1_radius_mobile', 'color_btn2_bg_mobile', 'color_btn2_text_mobile', 'color_btn2_border_mobile', 'color_btn2_radius_mobile', ), ); // Distribute to per-tab options $general_data = array(); $design_data = array(); foreach ( $legacy as $key => $value ) { if ( in_array( $key, $field_map['general'], true ) ) { $general_data[ $key ] = $value; } elseif ( in_array( $key, $field_map['design'], true ) ) { $design_data[ $key ] = $value; } } if ( ! empty( $general_data ) ) { update_option( self::OPTION_GENERAL, $general_data ); } if ( ! empty( $design_data ) ) { update_option( self::OPTION_DESIGN, $design_data ); } update_option( 'aha_hero_slider_migrated_to_per_tab', '1' ); } /** * Append Uninstall tab after other add-ons register tabs. * * @param array $tabs Tab slug => label. * @return array */ public function append_uninstall_tab( $tabs ) { if ( ! is_array( $tabs ) ) { $tabs = array(); } $tabs['uninstall'] = __( 'Uninstall', 'aha-hero-slider' ); return $tabs; } /** * Save "remove data on uninstall" preference (Uninstall tab). * * @return void */ public function maybe_save_uninstall_preference() { if ( ! isset( $_POST['aha_hs_uninstall_prefs'] ) ) { return; } if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'Permission denied.', 'aha-hero-slider' ) ); } check_admin_referer( 'aha_hs_uninstall_prefs', 'aha_hs_uninstall_prefs_nonce' ); $erase = isset( $_POST['aha_hs_remove_data_on_uninstall'] ) ? '1' : '0'; update_option( AHA_HERO_SLIDER_UNINSTALL_DELETE_DATA_OPTION, $erase, false ); wp_safe_redirect( add_query_arg( array( 'page' => AHA_HERO_SLIDER_ADMIN_SLUG, 'tab' => 'uninstall', 'aha_hs_uninstall_saved' => '1', ), admin_url( 'admin.php' ) ) ); exit; } /** * Top-level menu: settings page. */ public function register_menu() { add_menu_page( __( 'Aha Hero Slider', 'aha-hero-slider' ), __( 'Aha Hero Slider', 'aha-hero-slider' ), 'manage_options', AHA_HERO_SLIDER_ADMIN_SLUG, array( $this, 'render_settings_page' ), 'dashicons-images-alt2', 76 ); } /** * Submenu: Hero slides list + Slide groups taxonomy. */ public function register_cpt_submenu() { add_submenu_page( AHA_HERO_SLIDER_ADMIN_SLUG, __( 'Hero slides', 'aha-hero-slider' ), __( 'Hero slides', 'aha-hero-slider' ), 'edit_posts', 'edit.php?post_type=' . AHA_HERO_SLIDER_CPT ); add_submenu_page( AHA_HERO_SLIDER_ADMIN_SLUG, __( 'Slide groups', 'aha-hero-slider' ), __( 'Slide groups', 'aha-hero-slider' ), 'edit_posts', 'edit-tags.php?taxonomy=' . AHA_HERO_SLIDER_TAXONOMY . '&post_type=' . AHA_HERO_SLIDER_CPT ); } /** * Register options and fields for each tab. */ public function register_settings() { // —— General —— register_setting( 'aha_hero_slider_general_group', self::OPTION_GENERAL, array( 'sanitize_callback' => array( $this, 'sanitize_general' ), ) ); add_settings_section( 'aha_hero_slider_general', '', array( $this->fields_general, 'render_section_general' ), 'aha-hero-slider-general' ); add_settings_field( 'aha_hero_slider_enable_slideshow', __( 'Slideshow output', 'aha-hero-slider' ), array( $this->fields_general, 'render_field_enable_slideshow' ), 'aha-hero-slider-general', 'aha_hero_slider_general' ); add_settings_section( 'aha_hero_slider_image_sizes', __( 'Automatic image sizes', 'aha-hero-slider' ), array( $this->fields_general, 'render_section_image_sizes' ), 'aha-hero-slider-general' ); add_settings_field( 'aha_hero_slider_enable_custom_image_sizes', __( 'Custom sizes', 'aha-hero-slider' ), array( $this->fields_general, 'render_field_image_sizes' ), 'aha-hero-slider-general', 'aha_hero_slider_image_sizes' ); add_settings_section( 'aha_hero_slider_slide_library', __( 'Slide library & shortcode defaults', 'aha-hero-slider' ), array( $this->fields_general, 'render_section_slide_library' ), 'aha-hero-slider-general' ); add_settings_field( 'aha_hero_slider_quick_actions', __( 'Actions', 'aha-hero-slider' ), array( $this->fields_general, 'render_field_quick_actions' ), 'aha-hero-slider-general', 'aha_hero_slider_slide_library' ); add_settings_field( 'aha_hero_slider_shortcode_autoplay', __( 'Shortcode default: autoplay interval', 'aha-hero-slider' ), array( $this->fields_general, 'render_field_shortcode_autoplay' ), 'aha-hero-slider-general', 'aha_hero_slider_slide_library' ); add_settings_field( 'aha_hero_slider_shortcode_arrows', __( 'Shortcode default: arrows', 'aha-hero-slider' ), array( $this->fields_general, 'render_field_shortcode_arrows' ), 'aha-hero-slider-general', 'aha_hero_slider_slide_library' ); add_settings_field( 'aha_hero_slider_shortcode_radius', __( 'Shortcode default: border radius', 'aha-hero-slider' ), array( $this->fields_general, 'render_field_shortcode_radius' ), 'aha-hero-slider-general', 'aha_hero_slider_slide_library' ); // —— Design —— register_setting( 'aha_hero_slider_design_group', self::OPTION_DESIGN, array( 'sanitize_callback' => array( $this, 'sanitize_design' ), ) ); add_settings_section( 'aha_hero_slider_design_nav', __( 'Navigation', 'aha-hero-slider' ), array( $this->fields_design, 'render_section_design_nav' ), 'aha-hero-slider-design' ); add_settings_field( 'aha_hero_slider_design_nav_fields', '', array( $this->fields_design, 'render_field_design_nav' ), 'aha-hero-slider-design', 'aha_hero_slider_design_nav' ); add_settings_section( 'aha_hero_slider_design_progress', __( 'Autoplay progress', 'aha-hero-slider' ), array( $this->fields_design, 'render_section_design_progress' ), 'aha-hero-slider-design' ); add_settings_field( 'aha_hero_slider_design_progress_fields', '', array( $this->fields_design, 'render_field_design_progress' ), 'aha-hero-slider-design', 'aha_hero_slider_design_progress' ); add_settings_section( 'aha_hero_slider_design_mobile', __( 'Mobile (Card Default template)', 'aha-hero-slider' ), array( $this->fields_design, 'render_section_design_mobile' ), 'aha-hero-slider-design' ); add_settings_field( 'aha_hero_slider_design_mobile_fields', '', array( $this->fields_design, 'render_field_design_mobile' ), 'aha-hero-slider-design', 'aha_hero_slider_design_mobile' ); add_settings_section( 'aha_hero_slider_design_overlay', __( 'Overlay mask', 'aha-hero-slider' ), array( $this->fields_design, 'render_section_design_overlay' ), 'aha-hero-slider-design' ); add_settings_field( 'aha_hero_slider_design_overlay_fields', '', array( $this->fields_design, 'render_field_design_overlay' ), 'aha-hero-slider-design', 'aha_hero_slider_design_overlay' ); add_settings_section( 'aha_hero_slider_design_texts', __( 'Default text colors', 'aha-hero-slider' ), array( $this->fields_design, 'render_section_design_texts' ), 'aha-hero-slider-design' ); add_settings_field( 'aha_hero_slider_design_texts_fields', '', array( $this->fields_design, 'render_field_design_texts' ), 'aha-hero-slider-design', 'aha_hero_slider_design_texts' ); add_settings_section( 'aha_hero_slider_design_buttons', __( 'Default button styles', 'aha-hero-slider' ), array( $this->fields_design, 'render_section_design_buttons' ), 'aha-hero-slider-design' ); add_settings_field( 'aha_hero_slider_design_btn1_fields', '' . __( 'Primary button (desktop)', 'aha-hero-slider' ), array( $this->fields_design, 'render_field_design_btn1' ), 'aha-hero-slider-design', 'aha_hero_slider_design_buttons' ); add_settings_field( 'aha_hero_slider_design_btn1_mobile_fields', '' . __( 'Primary button (mobile)', 'aha-hero-slider' ), array( $this->fields_design, 'render_field_design_btn1_mobile' ), 'aha-hero-slider-design', 'aha_hero_slider_design_buttons' ); add_settings_field( 'aha_hero_slider_design_btn2_fields', '' . __( 'Secondary button (desktop)', 'aha-hero-slider' ), array( $this->fields_design, 'render_field_design_btn2' ), 'aha-hero-slider-design', 'aha_hero_slider_design_buttons' ); add_settings_field( 'aha_hero_slider_design_btn2_mobile_fields', '' . __( 'Secondary button (mobile)', 'aha-hero-slider' ), array( $this->fields_design, 'render_field_design_btn2_mobile' ), 'aha-hero-slider-design', 'aha_hero_slider_design_buttons' ); } /** * Sanitize general tab settings. * * @param mixed $input Raw input. * @return array */ public function sanitize_general( $input ) { if ( ! is_array( $input ) ) { return $this->get_general_settings(); } $sanitized = array( 'enable_slideshow' => ! empty( $input['enable_slideshow'] ) ? 1 : 0, 'enable_custom_image_sizes' => ! empty( $input['enable_custom_image_sizes'] ) ? 1 : 0, 'shortcode_autoplay_speed' => isset( $input['shortcode_autoplay_speed'] ) ? max( 0, absint( $input['shortcode_autoplay_speed'] ) ) : 5000, 'shortcode_show_arrows' => ! empty( $input['shortcode_show_arrows'] ) ? 1 : 0, 'shortcode_border_radius' => isset( $input['shortcode_border_radius'] ) ? max( 0, min( 100, absint( $input['shortcode_border_radius'] ) ) ) : 0, ); return $sanitized; } /** * Sanitize design tab settings. * * @param mixed $input Raw input. * @return array */ public function sanitize_design( $input ) { if ( ! is_array( $input ) ) { return $this->get_design_settings(); } $sanitized = array(); $hex_keys = array( 'color_arrow', 'color_arrow_icon', 'color_bullet', 'color_bullet_active', 'color_progress_track', 'color_progress_fill', 'overlay_color', 'color_badge', 'color_headline', 'color_subtitle', 'color_btn1_bg', 'color_btn1_text', 'color_btn1_border', 'color_btn2_bg', 'color_btn2_text', 'color_btn2_border', 'color_mobile_bg', 'color_btn1_bg_mobile', 'color_btn1_text_mobile', 'color_btn1_border_mobile', 'color_btn2_bg_mobile', 'color_btn2_text_mobile', 'color_btn2_border_mobile', ); foreach ( $hex_keys as $key ) { $raw = isset( $input[ $key ] ) ? wp_unslash( $input[ $key ] ) : ''; $sanitized[ $key ] = sanitize_hex_color( $raw ) ?? ''; } $sanitized['overlay_enabled'] = ! empty( $input['overlay_enabled'] ) ? 1 : 0; $sanitized['overlay_opacity'] = isset( $input['overlay_opacity'] ) ? max( 0, min( 100, absint( $input['overlay_opacity'] ) ) ) : 40; $sanitized['color_btn1_radius'] = isset( $input['color_btn1_radius'] ) ? max( 0, min( 100, absint( $input['color_btn1_radius'] ) ) ) : 12; $sanitized['color_btn2_radius'] = isset( $input['color_btn2_radius'] ) ? max( 0, min( 100, absint( $input['color_btn2_radius'] ) ) ) : 12; $sanitized['color_btn1_radius_mobile'] = isset( $input['color_btn1_radius_mobile'] ) && '' !== $input['color_btn1_radius_mobile'] ? max( 0, min( 100, absint( $input['color_btn1_radius_mobile'] ) ) ) : ''; $sanitized['color_btn2_radius_mobile'] = isset( $input['color_btn2_radius_mobile'] ) && '' !== $input['color_btn2_radius_mobile'] ? max( 0, min( 100, absint( $input['color_btn2_radius_mobile'] ) ) ) : ''; return $sanitized; } /** * Get general tab settings with defaults. * * @return array */ public function get_general_settings() { $defaults = array( 'enable_slideshow' => 1, 'enable_custom_image_sizes' => 1, 'shortcode_autoplay_speed' => 5000, 'shortcode_show_arrows' => 1, 'shortcode_border_radius' => 0, ); $settings = get_option( self::OPTION_GENERAL, array() ); if ( ! is_array( $settings ) ) { $settings = array(); } return wp_parse_args( $settings, $defaults ); } /** * Get design tab settings with defaults. * * @return array */ public function get_design_settings() { $defaults = array( // Design palette 'color_arrow' => '#ffffff', 'color_arrow_icon' => '#1a1a1a', 'color_bullet' => '#c7c7c7', 'color_bullet_active' => '#ffffff', 'color_progress_track' => '', 'color_progress_fill' => '', 'overlay_enabled' => 0, 'overlay_color' => '#000000', 'overlay_opacity' => 40, 'color_badge' => '#ffffff', 'color_headline' => '#ffffff', 'color_subtitle' => '#ffffff', 'color_btn1_bg' => '#d81e5b', 'color_btn1_text' => '#ffffff', 'color_btn1_border' => '', 'color_btn1_radius' => 12, 'color_btn2_bg' => '', 'color_btn2_text' => '#ffffff', 'color_btn2_border' => '#ffffff', 'color_btn2_radius' => 12, 'color_mobile_bg' => '#efefef', 'color_btn1_bg_mobile' => '', 'color_btn1_text_mobile' => '', 'color_btn1_border_mobile' => '', 'color_btn1_radius_mobile' => '', 'color_btn2_bg_mobile' => '', 'color_btn2_text_mobile' => '', 'color_btn2_border_mobile' => '', 'color_btn2_radius_mobile' => '', ); $settings = get_option( self::OPTION_DESIGN, array() ); if ( ! is_array( $settings ) ) { $settings = array(); } return wp_parse_args( $settings, $defaults ); } /** * Get all settings merged from per-tab options. * * @return array */ public function get_settings() { $settings = array_merge( $this->get_general_settings(), $this->get_design_settings() ); /** * Filter all settings (allows add-ons to inject custom settings). * * @param array $settings Merged settings from all tabs. * @return array */ return apply_filters( 'aha_hero_slider_get_settings', $settings ); } /** * Get option group name for a given tab (allows customization via filter). * * @param string $tab Tab slug. * @return string|null Option group name, or null if not found. */ public function get_option_group_for_tab( $tab ) { $option_groups = array( 'general' => 'aha_hero_slider_general_group', 'design' => 'aha_hero_slider_design_group', 'shortcode' => null, // Read-only documentation tab ); $group = isset( $option_groups[ $tab ] ) ? $option_groups[ $tab ] : null; /** * Filter option group for a tab (allows add-ons to register their own option groups). * * @param string|null $group Option group name, or null. * @param string $tab Tab slug. * @return string|null */ return apply_filters( 'aha_hero_slider_tab_option_group', $group, $tab ); } /** * Render settings page. */ public function render_settings_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $active_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general'; $tabs = array( 'general' => __( 'General', 'aha-hero-slider' ), 'design' => __( 'Design', 'aha-hero-slider' ), 'shortcode' => __( 'Shortcode', 'aha-hero-slider' ), ); /** * Filter settings tabs. * * @param array $tabs Tab slug => label. */ $tabs = apply_filters( 'aha_hero_slider_settings_tabs', $tabs ); if ( ! array_key_exists( $active_tab, $tabs ) ) { $active_tab = 'general'; } ?>
render_uninstall_tab(); ?>
render_shortcode_tab(); ?>
get_option_group_for_tab( $active_tab ); ?>
base_tab_slugs, true ) || 'uninstall' === $tab_slug ) { continue; } $tab_option_group = $this->get_option_group_for_tab( $tab_slug ); ?>