'array', 'sanitize_callback' => array( __CLASS__, 'save_settings' ), 'default' => Tocnest_Settings::defaults(), ) ); } /** * Merge a single settings tab with existing validated values. * * @param mixed $input Submitted values. * @return array */ public static function save_settings( $input ) { if ( ! current_user_can( 'manage_options' ) ) { return Tocnest_Settings::get(); } return array_merge( Tocnest_Settings::get(), Tocnest_Settings::sanitize( $input, true ) ); } /** * Translated tab names. * * @return array */ private static function tabs() { return array( 'general' => __( 'General', 'tocnest' ), 'appearance' => __( 'Appearance', 'tocnest' ), 'auto' => __( 'Auto insert', 'tocnest' ), 'advanced' => __( 'Advanced', 'tocnest' ), 'import' => __( 'Import', 'tocnest' ), ); } /** * Field labels, grouped by tab. * * @return array */ private static function fields() { return array( 'general' => array( 'title' => __( 'Title text', 'tocnest' ), 'show_title' => __( 'Show title', 'tocnest' ), 'title_tag' => __( 'Title element', 'tocnest' ), 'toggle' => __( 'Show toggle button', 'tocnest' ), 'label_show' => __( 'Show button label', 'tocnest' ), 'label_hide' => __( 'Hide button label', 'tocnest' ), 'collapsed' => __( 'Start collapsed', 'tocnest' ), 'min' => __( 'Minimum headings', 'tocnest' ), 'levels' => __( 'Included heading levels', 'tocnest' ), 'depth' => __( 'Maximum nesting depth', 'tocnest' ), 'hierarchical' => __( 'Hierarchical list', 'tocnest' ), 'numbering' => __( 'Numbering', 'tocnest' ), 'suffix' => __( 'Number suffix', 'tocnest' ), ), 'appearance' => array( 'scheme' => __( 'Color scheme', 'tocnest' ), 'background' => __( 'Background color', 'tocnest' ), 'border' => __( 'Border color', 'tocnest' ), 'title_color' => __( 'Title color', 'tocnest' ), 'link' => __( 'Link color', 'tocnest' ), 'hover' => __( 'Link hover color', 'tocnest' ), 'visited' => __( 'Visited link color', 'tocnest' ), 'width' => __( 'Width: auto, full, or CSS length', 'tocnest' ), 'float' => __( 'Float', 'tocnest' ), 'title_size' => __( 'Title font size', 'tocnest' ), 'title_weight' => __( 'Title font weight', 'tocnest' ), 'items_size' => __( 'Items font size', 'tocnest' ), 'markup' => __( 'List markup', 'tocnest' ), 'class' => __( 'Extra container CSS classes', 'tocnest' ), ), 'auto' => array( 'auto' => __( 'Automatically insert a TOC', 'tocnest' ), 'post_types' => __( 'Automatic insertion post types', 'tocnest' ), 'position' => __( 'Insertion position', 'tocnest' ), 'anchor_types' => __( 'Add anchors even without a TOC on these post types', 'tocnest' ), 'metabox_types' => __( 'Show per-post controls on these post types', 'tocnest' ), ), 'advanced' => array( 'skip_levels' => __( 'Excluded levels, for example h1,h5', 'tocnest' ), 'skip_text' => __( 'Skip headings by text, one per line; * matches any text', 'tocnest' ), 'skip_class' => __( 'Skip headings with these CSS classes', 'tocnest' ), 'anchor_format' => __( 'Anchor format', 'tocnest' ), 'lowercase' => __( 'Lowercase anchors', 'tocnest' ), 'dashes' => __( 'Replace underscores with dashes', 'tocnest' ), 'smooth' => __( 'Smooth scrolling', 'tocnest' ), 'offset' => __( 'Scroll offset in pixels', 'tocnest' ), 'highlight' => __( 'Highlight current section', 'tocnest' ), 'nofollow' => __( 'Add nofollow to TOC links', 'tocnest' ), 'compat' => __( 'Keep old shortcodes and blocks working', 'tocnest' ), 'override_previous' => __( 'Allow automatic insertion while the previous plugin also inserts a TOC', 'tocnest' ), ), ); } /** * Translate select option labels. * * @return array */ private static function choices() { return array( 'none' => __( 'None', 'tocnest' ), 'decimal' => __( 'Decimal', 'tocnest' ), 'decimalnested' => __( 'Nested decimal', 'tocnest' ), 'roman' => __( 'Roman', 'tocnest' ), 'romannested' => __( 'Nested Roman', 'tocnest' ), 'dot' => __( 'Dot (.)', 'tocnest' ), 'roundbracket' => __( 'Closing parenthesis', 'tocnest' ), 'light' => __( 'Light', 'tocnest' ), 'dark' => __( 'Dark', 'tocnest' ), 'white' => __( 'White', 'tocnest' ), 'transparent' => __( 'Transparent', 'tocnest' ), 'inherit' => __( 'Inherit from theme', 'tocnest' ), 'left' => __( 'Left', 'tocnest' ), 'right' => __( 'Right', 'tocnest' ), 'center' => __( 'Center', 'tocnest' ), 'text' => __( 'Heading text, transliterated', 'tocnest' ), 'text_raw' => __( 'Heading text, original characters', 'tocnest' ), 'counter' => __( 'Counter (toc-1)', 'tocnest' ), 'beforefirstheading' => __( 'Before first heading', 'tocnest' ), 'afterfirstheading' => __( 'After first heading', 'tocnest' ), 'afterfirstblock' => __( 'After first paragraph or block', 'tocnest' ), 'top' => __( 'Top', 'tocnest' ), 'bottom' => __( 'Bottom', 'tocnest' ), 'p' => __( 'Paragraph', 'tocnest' ), 'div' => __( 'Division', 'tocnest' ), 'ol' => __( 'Ordered list', 'tocnest' ), 'ul' => __( 'Unordered list', 'tocnest' ), ); } /** Render the complete settings screen. */ public static function page() { if ( ! current_user_can( 'manage_options' ) ) { return; } $tabs = self::tabs(); $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only tab selection. $tab = isset( $tabs[ $tab ] ) ? $tab : 'general'; echo '
' . esc_html__( 'Fast, accessible navigation for your content. Every feature is included.', 'tocnest' ) . '
'; settings_errors(); if ( Tocnest_Migration::old_active() ) { echo '' . esc_html__( 'The previous table of contents plugin is active. Deactivate it to avoid duplicate tables. Automatic insertion pauses while its auto insert is enabled, unless you override this in Advanced.', 'tocnest' ) . '
' . esc_html__( 'Import settings from the LuckyWP Table of Contents plugin. Tocnest is independent and is not affiliated with its authors. Original data and post content stay intact.', 'tocnest' ) . '
'; /* translators: 1: Option groups, 2: Posts with overrides, 3: Posts with shortcodes. */ echo '' . esc_html( sprintf( __( 'Found %1$d option groups, %2$d posts with settings and %3$d posts with old shortcodes. Post counts are capped at 10,001 and cached for five minutes.', 'tocnest' ), count( $detected['options'] ), $detected['meta'], $detected['shortcodes'] ) ) . '
'; self::action_form( 'tocnest_import', __( 'Import settings', 'tocnest' ) ); if ( false !== get_option( 'tocnest_settings_backup', false ) ) { self::action_form( 'tocnest_undo', __( 'Undo import', 'tocnest' ) ); } $report = get_option( 'tocnest_import_report', array() ); if ( $report ) { echo '' . esc_html__( 'Per-post settings work immediately through lazy conversion. To save them permanently, run the optional batch conversion below. Each request processes up to 200 posts. Existing Tocnest overrides are preserved. Undo restores global settings only.', 'tocnest' ) . '
'; } /** * Output a nonce-protected administrative action form. * * @param string $action Handler name. * @param string $label Button label. */ private static function action_form( $action, $label ) { echo ''; } /** * Load only the migration progress script on this settings screen. * * @param string $hook Screen ID. */ public static function assets( $hook ) { if ( 'settings_page_tocnest' === $hook ) { wp_enqueue_style( 'tocnest-admin', TOCNEST_URL . 'assets/admin.css', array(), TOCNEST_VERSION ); wp_enqueue_script( 'tocnest-admin', TOCNEST_URL . 'assets/admin.js', array( 'wp-i18n' ), TOCNEST_VERSION, true ); wp_set_script_translations( 'tocnest-admin', 'tocnest', TOCNEST_PATH . 'languages' ); } } /** Register a compatible meta box in both editors. */ public static function metaboxes() { foreach ( Tocnest_Settings::get()['metabox_types'] as $type ) { add_meta_box( 'tocnest-settings', __( 'Tocnest', 'tocnest' ), array( __CLASS__, 'metabox' ), $type, 'side', 'default', array( '__block_editor_compatible_meta_box' => true ) ); } } /** * Render per-post controls. * * @param WP_Post $post Current post. */ public static function metabox( $post ) { $settings = Tocnest_Settings::post_overrides( $post->ID ); wp_nonce_field( 'tocnest_meta', 'tocnest_meta_nonce' ); echo '