Plugin Directory

Changeset 3398506


Ignore:
Timestamp:
11/19/2025 05:39:51 AM (4 months ago)
Author:
awesomefootnotes
Message:

Adding the first version of my plugin

Location:
awesome-footnotes
Files:
4 added
2 deleted
9 edited

Legend:

Unmodified
Added
Removed
  • awesome-footnotes/tags/3.9.0/awesome-footnotes.php

    r3366547 r3398506  
    1313 * Plugin Name:     Footnotes
    1414 * Description:     Allows post authors to easily add and manage footnotes in posts.
    15  * Version:         3.8.5
     15 * Version:         3.9.0
    1616 * Author:          Footnotes
    1717 * Author URI:      https://quotecites.com
     
    2222 */
    2323
    24 use AWEF\Helpers\Context_Helper;
     24use AWEF\Awesome_Footnotes;
    2525
    2626// If this file is called directly, abort.
     
    2929}
    3030
    31 define( 'AWEF_VERSION', '3.8.5' );
     31define( 'AWEF_VERSION', '3.9.0' );
    3232define( 'AWEF_TEXTDOMAIN', 'awesome-footnotes' );
    3333define( 'AWEF_NAME', 'Footnotes' );
     
    102102
    103103
    104 if ( ! function_exists( 'awef_is_just_in_time_for_2fa_domain' ) ) {
     104if ( ! function_exists( 'awef_is_just_in_time_for_domain' ) ) {
    105105    /**
    106106     * Whether it is the just_in_time_error for wp-2fa domains.
     
    114114     * @return bool
    115115     */
    116     function awef_is_just_in_time_for_2fa_domain( $status, string $function_name, string $message ): bool {
     116    function awef_is_just_in_time_for_domain( $status, string $function_name, string $message ): bool {
    117117
    118118        return '_load_textdomain_just_in_time' === $function_name && strpos( $message, '<code>' . AWEF_TEXTDOMAIN ) !== false;
     
    134134     * @since 2.9.0
    135135     */
    136     function awef_trigger_error( $status, string $function_name, $errstr, $version, $errno = E_USER_NOTICE ) {
     136    function awef_trigger_error( $status, string $function_name, $errstr, $version = '', $errno = E_USER_NOTICE ) {
    137137
    138138        if ( false === $status ) {
     
    140140        }
    141141
    142         if ( awef_is_just_in_time_for_2fa_domain( '', $function_name, $errstr ) ) {
     142        if ( awef_is_just_in_time_for_domain( '', $function_name, $errstr ) ) {
    143143            // This error code is not included in error_reporting, so let it fall.
    144144            // through to the standard PHP error handler.
     
    169169        $message       = (string) $message;
    170170
    171         if ( ! class_exists( '\QM_Collectors', false ) || ! awef_is_just_in_time_for_2fa_domain( '', $function_name, $message ) ) {
     171        if ( ! class_exists( '\QM_Collectors', false ) || ! awef_is_just_in_time_for_domain( '', $function_name, $message ) ) {
    172172            return;
    173173        }
     
    199199\add_action( 'aadvana_trigger_error_doing_it_wrong', 'awef_trigger_error', 0, 4 );
    200200
     201\add_action( 'aadvana_trigger_error', 'awef_trigger_error', 0, 3 );
     202
    201203\add_action( 'doing_it_wrong_run', 'awef_action_doing_it_wrong_run', 0, 3 );
    202204\add_action( 'doing_it_wrong_run', 'awef_action_doing_it_wrong_run', 20, 3 );
     
    204206$plugin_name_libraries = require AWEF_PLUGIN_ROOT . 'vendor/autoload.php';
    205207
    206 if ( ! Context_Helper::is_installing() ) {
    207     \register_activation_hook( AWEF_PLUGIN_ABSOLUTE, array( '\AWEF\Awesome_Footnotes', 'plugin_activate' ) );
    208     \add_action( 'plugins_loaded', array( '\AWEF\Awesome_Footnotes', 'init' ) );
    209 }
     208\register_activation_hook( AWEF_PLUGIN_ABSOLUTE, array( Awesome_Footnotes::class, 'plugin_activate' ) );
     209\add_action( 'plugins_loaded', array( Awesome_Footnotes::class, 'init' ) );
  • awesome-footnotes/tags/3.9.0/classes/class-awsome-footnotes.php

    r3366051 r3398506  
    1616
    1717use AWEF\Helpers\Ajax;
     18use AWEF\Controllers\TOC;
    1819use AWEF\Helpers\Settings;
    1920use AWEF\Migration\Migration;
     
    6364            } else {
    6465                Footnotes_Formatter::init();
     66                TOC::init();
    6567
    6668                \add_action( 'wp_footer', array( __CLASS__, 'powered_by' ), \PHP_INT_MAX );
  • awesome-footnotes/tags/3.9.0/classes/controllers/class-post-settings.php

    r3366345 r3398506  
    3131        public const POST_SETTINGS_NAME = '_awef_post_settings';
    3232        public const POST_SEO_TITLE     = '_awef_post_seo_title';
     33
     34        /**
     35         * Meta key for per-post TOC option overrides.
     36         */
     37        public const POST_TOC_SETTINGS_NAME = '_awef_post_toc_settings';
    3338
    3439        public const POST_OPTIONS = array(
     
    5257
    5358        /**
     59         * Whitelisted TOC options allowed to override per post. Excludes archive/caching/global scope settings.
     60         */
     61        public const POST_TOC_OPTIONS = array(
     62            'toc_enable',
     63            'toc_title',
     64            'toc_levels',
     65            'toc_initial_state',
     66            'toc_position',
     67            'toc_exclude_selectors',
     68            'toc_label_show',
     69            'toc_label_hide',
     70            'toc_transition',
     71            'toc_numbering',
     72            'toc_structure',
     73            'toc_hier_sep',
     74            'toc_numbering_style',
     75            'toc_include_h1',
     76            'toc_additional_tags',
     77            'toc_scroll_spy',
     78            'toc_subsection_toggle',
     79            'toc_aria_label',
     80            'toc_anchor_offset',
     81            'toc_indent_step',
     82        );
     83
     84        /**
    5485         * Initialize the class
    5586         *
     
    86117            }
    87118
     119            // Security: verify nonce and user capability.
     120            if ( ! isset( $_POST['awef_post_settings_nonce'] ) || ! \wp_verify_nonce( $_POST['awef_post_settings_nonce'], 'awef_post_settings' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
     121                return;
     122            }
     123
     124            if ( ! \current_user_can( 'edit_post', $post_id ) ) {
     125                return;
     126            }
     127
    88128            $data = \get_the_content( null, false, $post_id );
    89129
    90             $settings_collected = Settings::collect_and_sanitize_options( $_POST[ \AWEF_SETTINGS_NAME ] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
     130            // Collect and sanitize full submitted settings once.
     131            $raw_settings           = isset( $_POST[ \AWEF_SETTINGS_NAME ] ) ? \stripslashes_deep( $_POST[ \AWEF_SETTINGS_NAME ] ) : array(); // phpcs:ignore WordPress.Security.NonceVerification.Missing
     132            $all_settings_collected = Settings::collect_and_sanitize_options( $raw_settings );
     133
     134            // Footnotes subset for legacy behaviour.
     135            $settings_collected = $all_settings_collected;
    91136
    92137            $only_values = self::POST_OPTIONS;
     
    94139            $settings_collected = array_filter(
    95140                $settings_collected,
    96                 function( $v ) use ( $only_values ) {
    97                     return in_array( $v, $only_values );
     141                function( $k ) use ( $only_values ) {
     142                    return in_array( $k, $only_values, true );
    98143                },
    99144                ARRAY_FILTER_USE_KEY
    100145            );
     146
     147            // TOC subset for overrides.
     148            $toc_settings_collected = array_filter(
     149                $all_settings_collected,
     150                function( $k ) {
     151                    return in_array( $k, Post_Settings::POST_TOC_OPTIONS, true );
     152                },
     153                ARRAY_FILTER_USE_KEY
     154            );
     155
     156            // UI: Reset Footnotes to default (remove post meta and fall back to globals).
     157            $reset_footnotes_to_default = isset( $_POST[ \AWEF_SETTINGS_NAME ]['footnotes_reset_defaults'] )
     158                ? filter_var( \wp_unslash( $_POST[ \AWEF_SETTINGS_NAME ]['footnotes_reset_defaults'] ), FILTER_VALIDATE_BOOLEAN ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
     159                : false;
     160
     161            // UI: Reset TOC to default (remove TOC overrides and fall back to globals).
     162            $reset_toc_to_default = isset( $_POST[ \AWEF_SETTINGS_NAME ]['toc_reset_defaults'] )
     163                ? filter_var( \wp_unslash( $_POST[ \AWEF_SETTINGS_NAME ]['toc_reset_defaults'] ), FILTER_VALIDATE_BOOLEAN ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
     164                : false;
    101165
    102166            if ( isset( $_POST[ \AWEF_SETTINGS_NAME ]['seo_description'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
     
    123187            }
    124188
    125             if ( false === \mb_strpos( $data, Settings::get_current_options()['footnotes_open'] ) || false === \mb_strpos( $data, $settings_collected['footnotes_open'] ) ) {
    126 
    127                 // It looks like that post does not contain footnotes formatting - there is no need to store anything remove if there is something stored and bounce.
    128 
    129                 $global_options = Settings::get_global_options();
    130 
    131                 $global_options = array_filter(
    132                     $global_options,
    133                     function( $v ) use ( $only_values ) {
    134                         return in_array( $v, $only_values );
    135                     },
    136                     ARRAY_FILTER_USE_KEY
    137                 );
    138 
    139                 if ( $settings_collected != $global_options ) {
    140                     \update_post_meta( $post_id, self::POST_SETTINGS_NAME, $settings_collected );
    141                 } else {
    142                     \delete_post_meta( $post_id, self::POST_SETTINGS_NAME );
    143                 }
    144 
     189            if ( $reset_footnotes_to_default ) {
     190                // Remove per-post Footnotes settings and fall back to global ones.
     191                \delete_post_meta( $post_id, self::POST_SETTINGS_NAME );
     192            } else {
     193                if ( false === \mb_strpos( $data, Settings::get_current_options()['footnotes_open'] ) || false === \mb_strpos( $data, $settings_collected['footnotes_open'] ) ) {
     194
     195                    // It looks like that post does not contain footnotes formatting - there is no need to store anything remove if there is something stored and bounce.
     196
     197                    $global_options = Settings::get_global_options();
     198
     199                    $global_options = array_filter(
     200                        $global_options,
     201                        function( $k ) use ( $only_values ) {
     202                            return in_array( $k, $only_values, true );
     203                        },
     204                        ARRAY_FILTER_USE_KEY
     205                    );
     206
     207                    if ( $settings_collected !== $global_options ) {
     208                        \update_post_meta( $post_id, self::POST_SETTINGS_NAME, $settings_collected );
     209                    } else {
     210                        \delete_post_meta( $post_id, self::POST_SETTINGS_NAME );
     211                    }
     212
     213                    // Ensure TOC overrides are handled even when returning early.
     214                    if ( $reset_toc_to_default ) {
     215                        \delete_post_meta( $post_id, self::POST_TOC_SETTINGS_NAME );
     216                    } else {
     217                        self::save_toc_overrides( $post_id, $toc_settings_collected );
     218                    }
     219
     220                    return;
     221                }
     222
     223                \update_post_meta( $post_id, self::POST_SETTINGS_NAME, $settings_collected );
     224            }
     225
     226            // Handle TOC overrides: compare with global values, store only diffs.
     227            if ( $reset_toc_to_default ) {
     228                \delete_post_meta( $post_id, self::POST_TOC_SETTINGS_NAME );
     229            } else {
     230                self::save_toc_overrides( $post_id, $toc_settings_collected );
     231            }
     232        }
     233
     234        /**
     235         * Persist per-post TOC overrides only when they differ from global defaults.
     236         * Empty or identical values prune the meta to avoid clutter.
     237         *
     238         * @param int   $post_id Post ID.
     239         * @param array $toc_submitted Sanitized submitted TOC settings subset.
     240         * @return void
     241         */
     242        public static function save_toc_overrides( int $post_id, array $toc_submitted ): void {
     243            if ( empty( $toc_submitted ) ) {
     244                // Nothing posted, ensure no stale meta remains.
     245                \delete_post_meta( $post_id, self::POST_TOC_SETTINGS_NAME );
    145246                return;
    146247            }
    147 
    148             \update_post_meta( $post_id, self::POST_SETTINGS_NAME, $settings_collected );
     248            $global    = Settings::get_global_options();
     249            $overrides = array();
     250            foreach ( self::POST_TOC_OPTIONS as $key ) {
     251                if ( array_key_exists( $key, $toc_submitted ) ) {
     252                    $submitted_val = $toc_submitted[ $key ];
     253                    $global_val    = $global[ $key ] ?? null;
     254                    // Normalize types for comparison (cast scalars to string/int as needed).
     255                    if ( is_array( $submitted_val ) ) {
     256                        $norm_sub = array_map( 'strval', $submitted_val );
     257                        $norm_glb = is_array( $global_val ) ? array_map( 'strval', $global_val ) : (array) $global_val;
     258                        if ( $norm_sub !== $norm_glb ) {
     259                            $overrides[ $key ] = $submitted_val;
     260                        }
     261                    } else {
     262                        // Compare after string cast to avoid '1' vs 1 mismatch.
     263                        if ( (string) $submitted_val !== (string) $global_val ) {
     264                            $overrides[ $key ] = $submitted_val;
     265                        }
     266                    }
     267                }
     268            }
     269            if ( empty( $overrides ) ) {
     270                \delete_post_meta( $post_id, self::POST_TOC_SETTINGS_NAME );
     271                return;
     272            }
     273            \update_post_meta( $post_id, self::POST_TOC_SETTINGS_NAME, $overrides );
     274        }
     275
     276        /**
     277         * Retrieve TOC overrides for a post.
     278         *
     279         * @param int $post_id Post ID.
     280         * @return array Overrides array or empty array.
     281         */
     282        public static function get_post_toc_overrides( int $post_id ): array {
     283            $raw = \get_post_meta( $post_id, self::POST_TOC_SETTINGS_NAME, true );
     284            return is_array( $raw ) ? $raw : array();
     285        }
     286
     287        /**
     288         * Return effective TOC options for a post: global merged with overrides.
     289         *
     290         * @param int $post_id Post ID.
     291         * @return array Effective options array.
     292         */
     293        public static function get_effective_toc_options( int $post_id ): array {
     294            $global    = Settings::get_current_options();
     295            $overrides = self::get_post_toc_overrides( $post_id );
     296            if ( empty( $overrides ) ) {
     297                return $global;
     298            }
     299            // Merge allowing override keys only in whitelist.
     300            foreach ( self::POST_TOC_OPTIONS as $key ) {
     301                if ( array_key_exists( $key, $overrides ) ) {
     302                    $global[ $key ] = $overrides[ $key ];
     303                }
     304            }
     305            return $global;
    149306        }
    150307
     
    155312         */
    156313        public static function meta_boxes() {
     314
     315            // Determine which post types should display the plugin metabox from global option awef_footnote_options[post_types].
     316            $global_options = Settings::get_current_options();
     317
     318            $post_types = isset( $global_options['post_types'] ) && is_array( $global_options['post_types'] )
     319                ? $global_options['post_types']
     320                : array( 'post', 'page' );
     321
     322            // Only keep registered post types to avoid errors on sites without certain CPTs (e.g., product).
     323            $registered_types = function_exists( 'get_post_types' ) ? array_keys( (array) get_post_types( array(), 'names' ) ) : array( 'post', 'page' );
     324
     325            $post_types = array_values( array_intersect( $post_types, $registered_types ) );
     326            if ( empty( $post_types ) ) {
     327                $post_types = array( 'post', 'page' );
     328            }
    157329
    158330            \add_meta_box(
     
    160332                AWEF_NAME . ' - ' . esc_html__( 'Settings', 'awesome_footnotes' ),
    161333                array( __CLASS__, 'custom_options' ),
    162                 array( 'post', 'page' ),
     334                $post_types,
    163335                'normal',
    164336                'high'
     
    189361            }
    190362
    191             $settings_tabs['general'] = array(
     363            // Use dedicated post-level Footnotes settings file.
     364            $settings_tabs['footnotes-post'] = array(
    192365                'icon'  => 'admin-generic',
    193366                'title' => esc_html__( 'Footnotes', 'awesome-footnotes' ),
    194367            );
    195368
     369            // Post-level TOC overrides tab: show only if current post type is allowed in global settings.
     370            $show_toc_tab = false;
     371            global $post;
     372            $current_post_type = ( isset( $post ) && $post instanceof \WP_Post ) ? $post->post_type : ( function_exists( 'get_post_type' ) ? \get_post_type() : '' );
     373            $global_options    = Settings::get_global_options();
     374            $allowed_types     = isset( $global_options['toc_post_types'] ) && is_array( $global_options['toc_post_types'] ) ? $global_options['toc_post_types'] : array();
     375            if ( $current_post_type && in_array( $current_post_type, $allowed_types, true ) ) {
     376                $show_toc_tab = true;
     377            }
     378
     379            if ( $show_toc_tab ) {
     380                $settings_tabs['toc-post'] = array(
     381                    'icon'  => 'list-view',
     382                    'title' => esc_html__( 'TOC', 'awesome-footnotes' ),
     383                );
     384            }
     385
    196386            ?>
    197387
    198388            <input type="hidden" name="<?php echo \esc_attr( self::HIDDEN_FORM_ELEMENT ); ?>" value="true" />
     389            <?php \wp_nonce_field( 'awef_post_settings', 'awef_post_settings_nonce' ); ?>
    199390
    200391            <div class="awef-panel">
     
    216407                                </li>
    217408                            <?php } else { ?>
    218                                 <li class="awef-tab-menu-head"><?php echo $settings; ?></li>
     409                                <li class="awef-tab-menu-head"><?php echo \esc_html( $settings ); ?></li>
    219410                                <?php
    220411                            }
     
    277468             * words from adjoining paragraphs stick together.
    278469             * so replace the end <p> tags with space, to ensure unstickinees of words */
    279             $content = strip_tags( $content );
     470            $content = \wp_strip_all_tags( $content );
    280471            $content = \strip_shortcodes( $content );
    281472            $content = trim( preg_replace( '/\s+/', ' ', $content ) );
     
    314505         */
    315506        public static function get_post_seo_title( $post ) {
    316             $post = get_post( $post );
     507            $post = \get_post( $post );
    317508            if ( ! $post ) {
    318509                return '';
  • awesome-footnotes/tags/3.9.0/classes/helpers/class-ajax.php

    r3169434 r3398506  
    5454        public static function save_settings_ajax() {
    5555
    56             if ( \check_ajax_referer( 'awef-plugin-data', 'awef-security' ) ) {
     56            // Capability check: only admins can change plugin settings.
     57            if ( ! \current_user_can( 'manage_options' ) ) {
     58                \wp_send_json_error( array( 'message' => 'Unauthorized' ), 403 );
     59            }
    5760
    58                 if ( isset( $_POST[ \AWEF_SETTINGS_NAME ] ) && ! empty( $_POST[ \AWEF_SETTINGS_NAME ] ) && \is_array( $_POST[ \AWEF_SETTINGS_NAME ] ) ) {
     61            // Nonce check (don't die automatically to allow JSON error response).
     62            $nonce_ok = \check_ajax_referer( 'awef-plugin-data', 'awef-security', false );
     63            if ( false === $nonce_ok ) {
     64                \wp_send_json_error( array( 'message' => 'Invalid nonce' ), 400 );
     65            }
    5966
    60                     $data = array_map( 'sanitize_text_field', \stripslashes_deep( $_POST[ \AWEF_SETTINGS_NAME ] ) );
     67            if ( isset( $_POST[ \AWEF_SETTINGS_NAME ] ) && ! empty( $_POST[ \AWEF_SETTINGS_NAME ] ) && \is_array( $_POST[ \AWEF_SETTINGS_NAME ] ) ) {
    6168
    62                     if ( isset( $_POST[ \AWEF_SETTINGS_NAME ]['css_footnotes'] ) ) {
    63                         $data['css_footnotes'] = \_sanitize_text_fields( \wp_unslash( $_POST[ \AWEF_SETTINGS_NAME ]['css_footnotes'] ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    64                     }
     69                // Preserve arrays (e.g., multi-selects) and let the settings sanitizer handle types/values.
     70                $data = \stripslashes_deep( $_POST[ \AWEF_SETTINGS_NAME ] );
    6571
    66                     if ( isset( $_POST[ \AWEF_SETTINGS_NAME ]['pre_footnotes'] ) ) {
    67                         $data['pre_footnotes'] = \wpautop( \wp_unslash( $_POST[ \AWEF_SETTINGS_NAME ]['pre_footnotes'] ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    68                     }
     72                if ( isset( $_POST[ \AWEF_SETTINGS_NAME ]['css_footnotes'] ) ) {
     73                    $data['css_footnotes'] = \_sanitize_text_fields( \wp_unslash( $_POST[ \AWEF_SETTINGS_NAME ]['css_footnotes'] ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
     74                }
    6975
    70                     if ( isset( $_POST[ \AWEF_SETTINGS_NAME ]['post_footnotes'] ) ) {
    71                         $data['post_footnotes'] = \wpautop( \wp_unslash( $_POST[ \AWEF_SETTINGS_NAME ]['post_footnotes'] ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    72                     }
    73                     \update_option( AWEF_SETTINGS_NAME, Settings::collect_and_sanitize_options( $data ) );
     76                if ( isset( $_POST[ \AWEF_SETTINGS_NAME ]['pre_footnotes'] ) ) {
     77                    $data['pre_footnotes'] = \wpautop( \wp_unslash( $_POST[ \AWEF_SETTINGS_NAME ]['pre_footnotes'] ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
     78                }
    7479
    75                     \wp_send_json_success( 2 );
     80                if ( isset( $_POST[ \AWEF_SETTINGS_NAME ]['post_footnotes'] ) ) {
     81                    $data['post_footnotes'] = \wpautop( \wp_unslash( $_POST[ \AWEF_SETTINGS_NAME ]['post_footnotes'] ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    7682                }
    77                 \wp_die();
     83                \update_option( AWEF_SETTINGS_NAME, Settings::collect_and_sanitize_options( $data ) );
     84
     85                \wp_send_json_success( 2 );
    7886            }
     87
     88            \wp_send_json_error( array( 'message' => 'Invalid payload' ), 400 );
    7989        }
    8090    }
  • awesome-footnotes/tags/3.9.0/classes/helpers/class-settings.php

    r3366051 r3398506  
    3030    class Settings {
    3131
    32         public const OPTIONS_VERSION = '13'; // Incremented when the options array changes.
     32        public const OPTIONS_VERSION = '14'; // Incremented when the options array changes.
    3333
    3434        public const MENU_SLUG = 'awef_settings';
     
    130130            $footnotes_options['superscript'] = ( array_key_exists( 'superscript', $post_array ) ) ? filter_var( $post_array['superscript'], FILTER_VALIDATE_BOOLEAN ) : false;
    131131
    132             $footnotes_options['pre_backlink']  = ( array_key_exists( 'pre_backlink', $post_array ) ) ? sanitize_text_field( $post_array['pre_backlink'] ) : '';
     132            $footnotes_options['pre_backlink']  = ( array_key_exists( 'pre_backlink', $post_array ) ) ? \sanitize_text_field( $post_array['pre_backlink'] ) : '';
    133133            $footnotes_options['backlink']      = ( array_key_exists( 'backlink', $post_array ) ) ? sanitize_text_field( $post_array['backlink'] ) : '';
    134             $footnotes_options['post_backlink'] = ( array_key_exists( 'post_backlink', $post_array ) ) ? sanitize_text_field( $post_array['post_backlink'] ) : '';
     134            $footnotes_options['post_backlink'] = ( array_key_exists( 'post_backlink', $post_array ) ) ? \sanitize_text_field( $post_array['post_backlink'] ) : '';
    135135
    136136            $footnotes_options['pre_identifier']        = ( array_key_exists( 'pre_identifier', $post_array ) ) ? \sanitize_text_field( $post_array['pre_identifier'] ) : '';
     
    177177            $footnotes_options['no_posts_footnotes'] = ( array_key_exists( 'no_posts_footnotes', $post_array ) ) ? filter_var( $post_array['no_posts_footnotes'], FILTER_VALIDATE_BOOLEAN ) : false;
    178178
     179            // Plugin-level: post types that should show the Footnotes metabox.
     180            if ( array_key_exists( 'post_types', $post_array ) ) {
     181                $values = $post_array['post_types'];
     182                if ( ! is_array( $values ) ) {
     183                    $values = array( $values );
     184                }
     185                $values                          = array_map( 'sanitize_text_field', $values );
     186                $registered                      = get_post_types( array(), 'names' );
     187                $footnotes_options['post_types'] = array_values( array_intersect( $values, $registered ) );
     188            } else {
     189                $footnotes_options['post_types'] = self::get_default_options()['post_types'];
     190            }
     191
     192            // TOC settings.
     193            // For checkboxes, absence in POST means unchecked => false (do not fallback to defaults here).
     194            $footnotes_options['toc_enable']            = ( array_key_exists( 'toc_enable', $post_array ) ) ? filter_var( $post_array['toc_enable'], FILTER_VALIDATE_BOOLEAN ) : false;
     195            $footnotes_options['toc_title']             = ( array_key_exists( 'toc_title', $post_array ) ) ? sanitize_text_field( $post_array['toc_title'] ) : self::get_default_options()['toc_title'];
     196            $footnotes_options['toc_levels']            = ( array_key_exists( 'toc_levels', $post_array ) ) ? sanitize_text_field( $post_array['toc_levels'] ) : self::get_default_options()['toc_levels'];
     197            $footnotes_options['toc_initial_state']     = ( array_key_exists( 'toc_initial_state', $post_array ) && in_array( $post_array['toc_initial_state'], array( 'collapsed', 'expanded' ), true ) ) ? sanitize_text_field( $post_array['toc_initial_state'] ) : self::get_default_options()['toc_initial_state'];
     198            $footnotes_options['toc_position']          = ( array_key_exists( 'toc_position', $post_array ) && in_array( $post_array['toc_position'], array( 'before', 'after' ), true ) ) ? sanitize_text_field( $post_array['toc_position'] ) : self::get_default_options()['toc_position'];
     199            $footnotes_options['toc_exclude_selectors'] = ( array_key_exists( 'toc_exclude_selectors', $post_array ) ) ? sanitize_text_field( $post_array['toc_exclude_selectors'] ) : self::get_default_options()['toc_exclude_selectors'];
     200            $footnotes_options['toc_label_show']        = ( array_key_exists( 'toc_label_show', $post_array ) ) ? sanitize_text_field( $post_array['toc_label_show'] ) : self::get_default_options()['toc_label_show'];
     201            $footnotes_options['toc_label_hide']        = ( array_key_exists( 'toc_label_hide', $post_array ) ) ? sanitize_text_field( $post_array['toc_label_hide'] ) : self::get_default_options()['toc_label_hide'];
     202            $footnotes_options['toc_transition']        = ( array_key_exists( 'toc_transition', $post_array ) ) ? filter_var( $post_array['toc_transition'], FILTER_VALIDATE_BOOLEAN ) : false;
     203            $footnotes_options['toc_cache_ttl']         = ( array_key_exists( 'toc_cache_ttl', $post_array ) && is_numeric( $post_array['toc_cache_ttl'] ) ) ? (int) $post_array['toc_cache_ttl'] : self::get_default_options()['toc_cache_ttl'];
     204            $footnotes_options['toc_anchor_offset']     = ( array_key_exists( 'toc_anchor_offset', $post_array ) && is_numeric( $post_array['toc_anchor_offset'] ) ) ? (int) $post_array['toc_anchor_offset'] : self::get_default_options()['toc_anchor_offset'];
     205            $footnotes_options['toc_structure']         = ( array_key_exists( 'toc_structure', $post_array ) && in_array( $post_array['toc_structure'], array( 'flat', 'nested' ), true ) ) ? sanitize_text_field( $post_array['toc_structure'] ) : self::get_default_options()['toc_structure'];
     206            $footnotes_options['toc_numbering']         = ( array_key_exists( 'toc_numbering', $post_array ) ) ? filter_var( $post_array['toc_numbering'], FILTER_VALIDATE_BOOLEAN ) : self::get_default_options()['toc_numbering'];
     207            $footnotes_options['toc_numbering_style']   = ( array_key_exists( 'toc_numbering_style', $post_array ) && in_array( $post_array['toc_numbering_style'], array( 'simple', 'hierarchical' ), true ) ) ? sanitize_text_field( $post_array['toc_numbering_style'] ) : self::get_default_options()['toc_numbering_style'];
     208            $footnotes_options['toc_hier_sep']          = ( array_key_exists( 'toc_hier_sep', $post_array ) && in_array( $post_array['toc_hier_sep'], array( '.', '-' ), true ) ) ? sanitize_text_field( $post_array['toc_hier_sep'] ) : self::get_default_options()['toc_hier_sep'];
     209            $footnotes_options['toc_indent_step']       = ( array_key_exists( 'toc_indent_step', $post_array ) && is_numeric( $post_array['toc_indent_step'] ) ) ? max( 0, min( 200, (int) $post_array['toc_indent_step'] ) ) : self::get_default_options()['toc_indent_step'];
     210            $footnotes_options['toc_aria_label']        = ( array_key_exists( 'toc_aria_label', $post_array ) ) ? sanitize_text_field( $post_array['toc_aria_label'] ) : self::get_default_options()['toc_aria_label'];
     211            $footnotes_options['toc_include_h1']        = ( array_key_exists( 'toc_include_h1', $post_array ) ) ? filter_var( $post_array['toc_include_h1'], FILTER_VALIDATE_BOOLEAN ) : self::get_default_options()['toc_include_h1'];
     212            $footnotes_options['toc_additional_tags']   = ( array_key_exists( 'toc_additional_tags', $post_array ) ) ? sanitize_text_field( $post_array['toc_additional_tags'] ) : self::get_default_options()['toc_additional_tags'];
     213            $footnotes_options['toc_scroll_spy']        = ( array_key_exists( 'toc_scroll_spy', $post_array ) ) ? filter_var( $post_array['toc_scroll_spy'], FILTER_VALIDATE_BOOLEAN ) : self::get_default_options()['toc_scroll_spy'];
     214            $footnotes_options['toc_subsection_toggle'] = ( array_key_exists( 'toc_subsection_toggle', $post_array ) ) ? filter_var( $post_array['toc_subsection_toggle'], FILTER_VALIDATE_BOOLEAN ) : self::get_default_options()['toc_subsection_toggle'];
     215            $footnotes_options['toc_include_archives']  = ( array_key_exists( 'toc_include_archives', $post_array ) ) ? filter_var( $post_array['toc_include_archives'], FILTER_VALIDATE_BOOLEAN ) : false;
     216
     217            // Allowed post types for TOC insertion. Must be a subset of plugin-level post_types.
     218            if ( array_key_exists( 'toc_post_types', $post_array ) ) {
     219                $values = $post_array['toc_post_types'];
     220                if ( ! is_array( $values ) ) {
     221                    $values = array( $values );
     222                }
     223                $values     = array_map( 'sanitize_text_field', $values );
     224                $registered = \get_post_types( array(), 'names' );
     225                $subset     = array_values( array_intersect( $values, $registered ) );
     226                // Enforce plugin scope constraint if available.
     227                $plugin_scope                        = isset( $footnotes_options['post_types'] ) && is_array( $footnotes_options['post_types'] ) ? $footnotes_options['post_types'] : self::get_default_options()['post_types'];
     228                $footnotes_options['toc_post_types'] = array_values( array_intersect( $subset, $plugin_scope ) );
     229            } else {
     230                $footnotes_options['toc_post_types'] = self::get_default_options()['toc_post_types'];
     231            }
     232
    179233            // add_settings_error(AWEF_SETTINGS_NAME, '<field_name>', 'Please enter a valid email!', $type = 'error'); .
    180234
     
    182236
    183237            self::$current_options = $footnotes_options;
     238
     239            // Purge TOC transients so changes reflect immediately and queue admin notice.
     240            self::clear_toc_transients();
     241            set_transient( 'awef_toc_cache_cleared', 1, 60 );
    184242
    185243            return $footnotes_options;
     
    266324                        self::$current_post = $post;
    267325
    268                         $post_settings = \get_post_meta( $post->ID, Post_Settings::POST_SETTINGS_NAME, true );
     326                        $post_settings      = \get_post_meta( $post->ID, Post_Settings::POST_SETTINGS_NAME, true );
     327                        $post_toc_overrides = \get_post_meta( $post->ID, Post_Settings::POST_TOC_SETTINGS_NAME, true );
    269328
    270329                        if ( isset( $post_settings ) && ! empty( $post_settings ) ) {
    271330                            self::$current_options = \array_merge( self::$current_options, $post_settings );
     331                        }
     332
     333                        if ( is_array( $post_toc_overrides ) && ! empty( $post_toc_overrides ) ) {
     334                            self::$current_options = \array_merge( self::$current_options, $post_toc_overrides );
    272335                        }
    273336                    }
     
    290353                    self::$current_post = $post;
    291354
    292                     $post_settings = \get_post_meta( $post->ID, Post_Settings::POST_SETTINGS_NAME, true );
     355                    $post_settings      = \get_post_meta( $post->ID, Post_Settings::POST_SETTINGS_NAME, true );
     356                    $post_toc_overrides = \get_post_meta( $post->ID, Post_Settings::POST_TOC_SETTINGS_NAME, true );
    293357
    294358                    if ( isset( $post_settings ) && ! empty( $post_settings ) ) {
     
    297361                        self::$current_options['footnotes_open']  = ( array_key_exists( 'footnotes_open', self::$current_options ) && ! empty( self::$current_options['footnotes_open'] ) ) ? self::$current_options['footnotes_open'] : self::get_default_options()['footnotes_open']; // That one can not be without a value.
    298362                        self::$current_options['footnotes_close'] = ( array_key_exists( 'footnotes_close', self::$current_options ) && ! empty( self::$current_options['footnotes_close'] ) ) ? self::$current_options['footnotes_close'] : self::get_default_options()['footnotes_close']; // That one can not be without a value.
     363                    }
     364
     365                    if ( is_array( $post_toc_overrides ) && ! empty( $post_toc_overrides ) ) {
     366                        self::$current_options = \array_merge( self::$current_options, $post_toc_overrides );
    299367                    }
    300368                }
     
    349417                    'position_before_footnote' => false,
    350418                    'no_posts_footnotes'       => false,
     419                    // TOC defaults.
     420                    'toc_enable'               => true,
     421                    'toc_title'                => __( 'Table of contents', 'awesome-footnotes' ),
     422                    'toc_levels'               => '2,3,4,5,6',
     423                    'toc_initial_state'        => 'expanded', // collapsed|expanded.
     424                    'toc_position'             => 'before', // before|after.
     425                    'toc_exclude_selectors'    => '', // Comma-separated list of selectors (#id,.class,tag).
     426                    'toc_label_show'           => __( 'show', 'awesome-footnotes' ),
     427                    'toc_label_hide'           => __( 'hide', 'awesome-footnotes' ),
     428                    'toc_transition'           => true,
     429                    'toc_cache_ttl'            => 86400, // 24h in seconds.
     430                    'toc_anchor_offset'        => 0,
     431                    'toc_structure'            => 'nested', // flat|nested.
     432                    'toc_numbering'            => false,
     433                    'toc_numbering_style'      => 'simple', // simple|hierarchical.
     434                    'toc_hier_sep'             => '.', // separator for hierarchical numbering path.
     435                    'toc_indent_step'          => 15, // px indentation per relative depth.
     436                    'toc_aria_label'           => __( 'Table of contents navigation', 'awesome-footnotes' ),
     437                    'toc_include_h1'           => false,
     438                    'toc_additional_tags'      => '',
     439                    'toc_scroll_spy'           => false,
     440                    'toc_subsection_toggle'    => false,
     441                    // Plugin-level: where to display the Footnotes metabox by default.
     442                    'post_types'               => array( 'post', 'page' ),
     443                    'toc_post_types'           => array( 'post', 'page' ),
     444                    'toc_include_archives'     => false,
    351445                );
    352446            }
     
    483577            \wp_enqueue_style( 'awef-admin-style', \AWEF_PLUGIN_ROOT_URL . 'css/admin/style.css', array(), \AWEF_VERSION, 'all' );
    484578
     579            // Show notice if TOC cache was cleared.
     580            if ( get_transient( 'awef_toc_cache_cleared' ) ) {
     581                delete_transient( 'awef_toc_cache_cleared' );
     582                echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'TOC cache cleared. Your changes are now active.', 'awesome-footnotes' ) . '</p></div>';
     583            }
     584
    485585            self::awef_show_options();
    486586        }
     
    608708                    'icon'  => 'admin-settings',
    609709                    'title' => esc_html__( 'Options', 'awesome-footnotes' ),
     710                ),
     711
     712                'head-toc'    => esc_html__( 'TOC (table of contents)', 'awesome-footnotes' ),
     713
     714                'general-toc' => array(
     715                    'icon'  => 'admin-generic',
     716                    'title' => esc_html__( 'General', 'awesome-footnotes' ),
    610717                ),
    611718
     
    8991006            \update_option( self::SETTINGS_VERSION, \AWEF_VERSION );
    9001007        }
     1008
     1009        /**
     1010         * Clears all TOC-related transients (awef_toc_*).
     1011         * Invoked after settings changes to ensure regenerated markup.
     1012         *
     1013         * @return void
     1014         */
     1015        private static function clear_toc_transients(): void {
     1016            global $wpdb;
     1017            // Delete value and timeout rows for awef_toc_ transients.
     1018            $like         = $wpdb->esc_like( '_transient_awef_toc_' ) . '%';
     1019            $timeout_like = $wpdb->esc_like( '_transient_timeout_awef_toc_' ) . '%';
     1020            $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", $like, $timeout_like ) );
     1021        }
    9011022    }
    9021023}
  • awesome-footnotes/tags/3.9.0/classes/settings/settings-options/advanced.php

    r3169434 r3398506  
    2323        'id'    => 'advanced-settings',
    2424        'title' => esc_html__( 'Advanced Settings', 'awesome-footnotes' ),
     25    )
     26);
     27
     28// Plugin-level scope: choose which post types show the plugin metabox.
     29Settings::build_option(
     30    array(
     31        'type'  => 'header',
     32        'id'    => 'plugin-scope-settings',
     33        'title' => esc_html__( 'Plugin Scope', 'awesome-footnotes' ),
     34    )
     35);
     36
     37// Build choices from registered, public post types.
     38$awef_post_type_objects = get_post_types( array( 'public' => true ), 'objects' );
     39$awef_pt_choices        = array();
     40if ( is_array( $awef_post_type_objects ) ) {
     41    foreach ( $awef_post_type_objects as $slug => $obj ) {
     42        $awef_pt_choices[ $slug ] = isset( $obj->labels->singular_name ) ? $obj->labels->singular_name : $slug;
     43    }
     44}
     45
     46Settings::build_option(
     47    array(
     48        'name'    => esc_html__( 'Enable metabox on post types', 'awesome-footnotes' ),
     49        'id'      => 'post_types', // results in input name awef_footnote_options[post_types]
     50        'type'    => 'select-multiple',
     51        'options' => $awef_pt_choices,
     52        'default' => Settings::get_current_options()['post_types'],
     53        'hint'    => esc_html__( 'Controls where the Footnotes settings metabox appears.', 'awesome-footnotes' ),
    2554    )
    2655);
  • awesome-footnotes/tags/3.9.0/css/admin/style.css

    r3169434 r3398506  
    26282628.awef-badge {
    26292629  color: #fff;
    2630   font-size: 13px;
    26312630  text-align: center;
    26322631  font-weight: 500;
    26332632  margin: 0;
    2634   padding: 20px 0;
    2635   width: 120px;
    26362633  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    26372634  float: right;
    26382635  border-radius: 5px;
    2639   background: linear-gradient(135deg, #0f0896, #42067e);
    26402636  margin-left: 20px;
    26412637}
  • awesome-footnotes/tags/3.9.0/readme.txt

    r3366547 r3398506  
    22Tags: footnotes, formatting, notes, reference
    33Requires at least: 6.0
    4 Tested up to: 6.8.2
     4Tested up to: 6.8
    55Requires PHP: 7.4
    6 Stable tag: 3.8.5
     6Stable tag: 3.9.0
    77License: GPLv2 or later
    88License URI: http://www.gnu.org/licenses/gpl-2.0.html
     
    1414**Footnotes & Content** plugin is a powerful method of adding **footnotes** into your posts and pages. You can have as many **footnotes** as you like pretty easily in every page, post or ACF block, WooCommerce is also supported. That is the fastest footnote plugin which is using extremely low resources - you wont even notice that it is there.
    1515
    16 You can visit the [Github page](https://github.com/sdobreff/footnotes/ "Github") for the latest code development, or if you want to report an issue with the code.
    17 
    18 To gain more control over WP, directly from the WP admin - try out our plugin **[WP Control](https://wordpress.org/plugins/0-day-analytics/)**
     16You can visit the [Github page](https://github.com/sdobreff/awesome-footnotes/ "Github") for the latest code development, or if you want to report an issue with the code.
     17
     18To gain more control over WP, directly from the WP admin - try out our plugin **[0 day analytics](https://wordpress.org/plugins/0-day-analytics/)**
    1919
    2020## Key features include...
     
    141141== Change Log ==
    142142
     143= 3.9.0 =
     144Added TOC option and settings related (still experimental). Code optimizations and bug fixes.
     145
    143146= 3.8.5 =
    144147Fixed WP translation problem - called too early.
  • awesome-footnotes/trunk/readme.txt

    r3398363 r3398506  
    22Tags: footnotes, formatting, notes, reference
    33Requires at least: 6.0
    4 Tested up to: 6.8.2
     4Tested up to: 6.8
    55Requires PHP: 7.4
    66Stable tag: 3.9.0
Note: See TracChangeset for help on using the changeset viewer.