Plugin Directory

Changeset 3323103


Ignore:
Timestamp:
07/06/2025 06:29:42 PM (8 months ago)
Author:
ehtmlu
Message:

Version 3.1.0

Location:
advanced-settings
Files:
2 added
2 deleted
24 edited
1 copied

Legend:

Unmodified
Added
Removed
  • advanced-settings/tags/3.1.0/admin-ui/admin-ui.css

    r3281733 r3323103  
    125125    padding: 0;
    126126    max-width: 90%;
    127     width: 60em;
     127    width: 65em;
    128128    height: 80vh;
    129129    background-color: #fff;
  • advanced-settings/tags/3.1.0/admin-ui/react/app.css

    r3281733 r3323103  
    1818.advset-category-sidebar {
    1919    min-width: 10em;
     20    max-width: 18em;
    2021    flex-shrink: 0;
    2122    background-color: var(--advset-color-base-95);
    2223    border-right: 1px solid var(--advset-color-border);
    2324    position: sticky;
     25    display: flex;
     26    flex-direction: column;
     27}
     28
     29/* Tab Navigation */
     30.advset-tab-navigation {
     31    display: flex;
     32    border-bottom: 1px solid var(--advset-color-border);
     33    background-color: var(--advset-color-base-97);
     34}
     35
     36.advset-tab-button {
     37    flex: 1;
     38    background: none;
     39    border: none;
     40    padding: .75em 1em;
     41    font-size: var(--advset-font-size-small);
     42    font-weight: 600;
     43    text-transform: uppercase;
     44    letter-spacing: .03em;
     45    color: var(--advset-color-base-70);
     46    cursor: pointer;
     47    transition: all .3s;
     48    border-bottom: 2px solid transparent;
     49}
     50
     51.advset-tab-button:hover:not(:disabled),
     52.advset-tab-button:focus-visible:not(:disabled) {
     53    color: var(--advset-color-base);
     54    background-color: var(--advset-color-base-90);
     55}
     56
     57.advset-tab-button.is-active {
     58    color: var(--advset-color-base);
     59    border-bottom-color: var(--advset-color-base);
     60    background-color: var(--advset-color-base-95);
     61    font-weight: 700;
     62}
     63
     64.advset-tab-button:disabled {
     65    opacity: 0.5;
     66    cursor: not-allowed;
     67}
     68
     69/* Tab Content */
     70.advset-tab-content {
     71    flex: 1;
     72    overflow-y: auto;
    2473}
    2574
     
    69118    font-weight: 600;
    70119    text-transform: uppercase;
     120}
     121
     122
     123
     124.advset-tags-list {
     125    display: flex;
     126    flex-wrap: wrap;
     127    gap: .5em;
     128    padding: 1em;
     129}
     130
     131.advset-tag {
     132    background: none;
     133    border: 1px solid var(--advset-color-base-70);
     134    border-radius: 1em;
     135    padding: .25em .75em;
     136    font-size: var(--advset-font-size-tiny);
     137    color: var(--advset-color-base-70);
     138    cursor: pointer;
     139    transition: all .3s;
     140    font-family: inherit;
     141    line-height: inherit;
     142}
     143
     144.advset-tag:hover,
     145.advset-tag:focus-visible {
     146    border-color: var(--advset-color-primary);
     147    color: var(--advset-color-primary);
     148    outline: var(--advset-color-primary) 1px solid;
     149}
     150
     151.advset-tag.is-active {
     152    background-color: var(--advset-color-primary);
     153    border-color: var(--advset-color-primary);
     154    color: var(--advset-color-background);
     155}
     156
     157.advset-tag.is-disabled {
     158    opacity: 0.5;
     159    cursor: not-allowed;
     160    color: var(--advset-color-base);
     161    border-color: var(--advset-color-base-70);
     162    background-color: var(--advset-color-base-70);
     163}
     164
     165.advset-tag.is-disabled:hover,
     166.advset-tag.is-disabled:focus-visible {
     167    border-color: var(--advset-color-base-50);
     168    color: var(--advset-color-base-50);
     169    outline: none;
    71170}
    72171
     
    260359}
    261360
     361
     362
     363/* ===== Item Tags ===== */
     364.advset-item-tags {
     365    display: flex;
     366    flex-wrap: wrap;
     367    justify-content: flex-end;
     368    gap: .5em;
     369    margin-top: .5em;
     370}
     371
     372.advset-item-tag {
     373    background: none;
     374    border: 1px solid var(--advset-color-base-70);
     375    border-radius: 1em;
     376    padding: .25em .75em;
     377    font-size: var(--advset-font-size-tiny);
     378    color: var(--advset-color-base-70);
     379    cursor: pointer;
     380    transition: all .3s;
     381    font-family: inherit;
     382    line-height: inherit;
     383}
     384
     385.advset-item-tag:hover,
     386.advset-item-tag:focus-visible {
     387    border-color: var(--advset-color-primary);
     388    color: var(--advset-color-primary);
     389    outline: var(--advset-color-primary) 1px solid;
     390}
     391
     392.advset-item-tag.is-active {
     393    background-color: var(--advset-color-primary);
     394    border-color: var(--advset-color-primary);
     395    color: var(--advset-color-background);
     396}
     397
     398
     399
  • advanced-settings/tags/3.1.0/admin-ui/react/app.js

    r3286786 r3323103  
    1717 */
    1818function App(props) {
    19     const { items, categories, onSettingChange, onCategoryClick, settings, searchQuery, activeCategory } = props;
     19    const { items, categories, onSettingChange, onCategoryClick, onTagClick, onTabChange, settings, searchQuery, parsedSearchQuery, activeCategory, hiddenItems, activeTab } = props;
    2020   
    2121    // Group items by category
     
    3737    );
    3838
     39    // Extract all unique tags from all items (both visible and hidden)
     40    const allTags = new Set();
     41    const visibleTagCounts = new Map();
     42    const hiddenTagCounts = new Map();
     43   
     44    // Count tags from visible items
     45    items.forEach(item => {
     46        if (item.ui_config?.tags) {
     47            item.ui_config.tags.forEach(tag => {
     48                allTags.add(tag);
     49                visibleTagCounts.set(tag, (visibleTagCounts.get(tag) || 0) + 1);
     50            });
     51        }
     52    });
     53   
     54    // Count tags from hidden items
     55    hiddenItems.forEach(item => {
     56        if (item.ui_config?.tags) {
     57            item.ui_config.tags.forEach(tag => {
     58                allTags.add(tag);
     59                hiddenTagCounts.set(tag, (hiddenTagCounts.get(tag) || 0) + 1);
     60            });
     61        }
     62    });
     63   
     64    const sortedTags = Array.from(allTags).sort();
     65
    3966   
    4067    return React.createElement('div', { className: 'advset-react-app' },
     
    4269        React.createElement('div', { className: 'advset-notifications' }),
    4370       
    44         // Category sidebar
    45         visibleCategories.length > 0 && React.createElement('div', { className: 'advset-category-sidebar' },
    46             React.createElement('ul', { className: 'advset-category-menu' },
    47                 visibleCategoriesIncludingSeparator.map(category =>
    48                     React.createElement('li', {
    49                         key: category.id,
    50                         className: 'advset-category-menu-item'
    51                     },
    52                     category.title ?
    53                         React.createElement('a', {
    54                             href: `#category-${category.id}`,
    55                             onClick: (e) => {
    56                                 e.preventDefault();
    57                                 onCategoryClick(category.id);
    58                             },
    59                             className: activeCategory === category.id ? 'is-active' : ''
    60                         },
    61                             React.createElement('span', {
    62                                 className: 'advset-category-icon',
    63                                 dangerouslySetInnerHTML: { __html: category.icon || '' }
    64                             }),
    65                             React.createElement('span', {
    66                                 className: 'advset-category-text'
    67                             }, category.title || category.id)
    68                         ) : React.createElement('div', {
    69                             className: 'advset-category-separator',
    70                             style: {
    71                                 borderTop: '1px solid #ccc',
    72                             }
    73                         })
     71        // Sidebar with tab navigation
     72        React.createElement('div', { className: 'advset-category-sidebar' },
     73            // Tab navigation
     74            React.createElement('div', { className: 'advset-tab-navigation' },
     75                React.createElement('button', {
     76                    className: `advset-tab-button ${activeTab === 'categories' ? 'is-active' : ''}`,
     77                    onClick: () => onTabChange('categories'),
     78                    disabled: visibleCategories.length === 0
     79                }, 'Categories'),
     80                React.createElement('button', {
     81                    className: `advset-tab-button ${activeTab === 'tags' ? 'is-active' : ''}`,
     82                    onClick: () => onTabChange('tags'),
     83                    disabled: sortedTags.length === 0
     84                }, 'Tags')
     85            ),
     86           
     87            // Categories tab content
     88            activeTab === 'categories' && visibleCategories.length > 0 && React.createElement('div', { className: 'advset-tab-content' },
     89                React.createElement('ul', { className: 'advset-category-menu' },
     90                    visibleCategoriesIncludingSeparator.map(category =>
     91                        React.createElement('li', {
     92                            key: category.id,
     93                            className: 'advset-category-menu-item'
     94                        },
     95                        category.title ?
     96                            React.createElement('a', {
     97                                href: `#category-${category.id}`,
     98                                onClick: (e) => {
     99                                    e.preventDefault();
     100                                    onCategoryClick(category.id);
     101                                },
     102                                className: activeCategory === category.id ? 'is-active' : ''
     103                            },
     104                                React.createElement('span', {
     105                                    className: 'advset-category-icon',
     106                                    dangerouslySetInnerHTML: { __html: category.icon || '' }
     107                                }),
     108                                React.createElement('span', {
     109                                    className: 'advset-category-text'
     110                                }, category.title || category.id)
     111                            ) : React.createElement('div', {
     112                                className: 'advset-category-separator',
     113                                style: {
     114                                    borderTop: '1px solid #ccc',
     115                                }
     116                            })
     117                        )
    74118                    )
     119                )
     120            ),
     121           
     122            // Tags tab content
     123            activeTab === 'tags' && sortedTags.length > 0 && React.createElement('div', { className: 'advset-tab-content' },
     124                React.createElement('div', { className: 'advset-tags-list' },
     125                    sortedTags.map(tag => {
     126                        const isActive = parsedSearchQuery?.included?.tags?.includes(tag.toLowerCase());
     127                        const hasVisibleItems = visibleTagCounts.has(tag);
     128                        const hasHiddenItems = hiddenTagCounts.has(tag);
     129                       
     130                        let className = 'advset-tag';
     131                        if (isActive) className += ' is-active';
     132                        if (!hasVisibleItems && hasHiddenItems) className += ' is-disabled';
     133                       
     134                        return React.createElement('button', {
     135                            key: tag,
     136                            className: className,
     137                            onClick: () => onTagClick(tag),
     138                            disabled: !hasVisibleItems && hasHiddenItems
     139                        }, tag);
     140                    })
    75141                )
    76142            )
     
    102168                                item: item,
    103169                                onSettingChange: onSettingChange,
    104                                 settingValue: settings[item.id] || {}
     170                                onTagClick: onTagClick,
     171                                settingValue: settings[item.id] || {},
     172                                parsedSearchQuery: parsedSearchQuery
    105173                            })
    106174                        )
     
    138206 */
    139207function ItemCard(props) {
    140     const { item, onSettingChange, settingValue } = props;
     208    const { item, onSettingChange, onTagClick, settingValue, parsedSearchQuery } = props;
    141209   
    142210    // Get the component from the registry
     
    197265                config: item.ui_config || {}
    198266            })
     267        ),
     268        // Show tags if item has tags
     269        item.ui_config?.tags && item.ui_config.tags.length > 0 && React.createElement('div', {
     270            className: 'advset-item-tags'
     271        },
     272            item.ui_config.tags.map(tag =>
     273                React.createElement('button', {
     274                    key: tag,
     275                    className: `advset-item-tag ${parsedSearchQuery?.included?.tags?.includes(tag.toLowerCase()) ? 'is-active' : ''}`,
     276                    onClick: () => onTagClick(tag)
     277                }, tag)
     278            )
    199279        )
    200280    );
     
    212292        items: [],
    213293        allItems: [], // Cache for all items
     294        hiddenItems: [], // Items that are hidden by current filter
    214295        isLoading: false,
    215296        categories: [],
    216297        settings: {}, // Store for settings values
    217298        activeCategory: null, // Track active category for scrolling
    218         parsedSearchQuery: null // Cache for parsed search query
     299        parsedSearchQuery: null, // Cache for parsed search query
     300        activeTab: 'categories' // Track active tab: 'categories' or 'tags'
    219301    },
    220302
     
    304386    performLocalSearch(query) {
    305387        const parsedSearchQuery = query ? parseSearchQuery(query) : null;
     388        const { filteredItems, hiddenItems } = this.filterItems(this.state.allItems, parsedSearchQuery);
    306389       
    307390        this.setState({
    308391            searchQuery: query,
    309392            parsedSearchQuery,
    310             items: this.filterItems(this.state.allItems, parsedSearchQuery)
     393            items: filteredItems,
     394            hiddenItems: hiddenItems
    311395        });
    312396    },
     
    317401     * @param {Array} items - The items to filter
    318402     * @param {Object} parsedSearchQuery - The parsed search query
    319      * @returns {Array} - Filtered items
     403     * @returns {Object} - Object containing filtered and hidden items
    320404     */
    321405    filterItems(items, parsedSearchQuery) {
     
    323407        const showExperimental = this.state.settings['advset.features.show_experimental']?.enable;
    324408       
    325         return items.filter(item => {
    326             // Filter by feature flags
     409        const filteredItems = [];
     410        const hiddenItems = [];
     411       
     412        items.forEach(item => {
     413            // Check feature flags first
     414            let isHiddenByFlags = false;
    327415            if (item.deprecated && !showDeprecated && typeof this.state.settings[item.id] === 'undefined') {
    328                 return false;
    329             }
    330 
     416                isHiddenByFlags = true;
     417            }
    331418            if (item.experimental && !showExperimental && typeof this.state.settings[item.id] === 'undefined') {
    332                 return false;
    333             }
    334 
    335             // If no search query, include the item
     419                isHiddenByFlags = true;
     420            }
     421
     422            // If no search query, include the item based on flags only
    336423            if (!parsedSearchQuery) {
    337                 return true;
    338             }
    339 
    340             // Search in ui_config fields
     424                if (isHiddenByFlags) {
     425                    hiddenItems.push(item);
     426                } else {
     427                    filteredItems.push(item);
     428                }
     429                return;
     430            }
     431
     432            // Search in ui_config fields and tags
    341433            const searchTexts = [];
    342434           
     
    354446            }
    355447           
    356             const searchText = searchTexts.join(' ').toLowerCase();
     448            // Add tags to searchable text
     449            if (item.ui_config?.tags) {
     450                searchTexts.push(...item.ui_config.tags);
     451            }
     452           
     453            const searchText = searchTexts.join(Math.random()).toLowerCase();
    357454
    358455            // Check if item matches all required terms
    359             const matchesTerms = parsedSearchQuery.terms.every(term =>
     456            const matchesIncludedTerms = parsedSearchQuery.included.terms.length === 0 || parsedSearchQuery.included.terms.every(term =>
    360457                searchText.includes(term)
    361458            );
    362459
    363460            // Check if item doesn't contain any excluded terms
    364             const matchesExclusions = !parsedSearchQuery.exclusions.some(exclusion =>
     461            const matchesExcludedTerms = !parsedSearchQuery.excluded.terms.some(exclusion =>
    365462                searchText.includes(exclusion)
    366463            );
    367464
    368             // Item must match all terms and no exclusions
    369             return matchesTerms && matchesExclusions;
     465            // Check tag requirements (now using labels)
     466            const itemTags = (item.ui_config?.tags || []).map(tag => tag.toLowerCase());
     467           
     468            // Item must have ALL required tags
     469            const matchesIncludedTags = parsedSearchQuery.included.tags.length === 0 || parsedSearchQuery.included.tags.every(tag =>
     470                itemTags.includes(tag)
     471            );
     472           
     473            // Item must NOT have ANY excluded tags
     474            const matchesExcludedTags = !parsedSearchQuery.excluded.tags.some(tag =>
     475                itemTags.includes(tag)
     476            );
     477
     478            // Item must match all terms, no exclusions, required tags, and no excluded tags
     479            const matchesSearch = matchesIncludedTerms && matchesExcludedTerms && matchesIncludedTags && matchesExcludedTags;
     480
     481            if (isHiddenByFlags || !matchesSearch) {
     482                hiddenItems.push(item);
     483            } else {
     484                filteredItems.push(item);
     485            }
    370486        });
     487       
     488        return { filteredItems, hiddenItems };
    371489    },
    372490
     
    399517
    400518            // Apply initial filtering
     519            const { filteredItems, hiddenItems } = this.filterItems(data.features);
    401520            this.setState({
    402                 items: this.filterItems(data.features),
     521                items: filteredItems,
     522                hiddenItems: hiddenItems
    403523            });
    404524           
     
    433553    },
    434554
     555
     556
     557    /**
     558     * Handle tab change
     559     *
     560     * @param {string} tab - The tab to switch to
     561     */
     562    handleTabChange(tab) {
     563        this.setState({ activeTab: tab });
     564    },
     565
     566    /**
     567     * Handle tag click
     568     *
     569     * @param {string} tag - The tag that was clicked
     570     */
     571    handleTagClick(tag) {
     572        const searchInput = document.querySelector('.advset-modal-search input');
     573        if (!searchInput) return;
     574
     575        let currentQuery = searchInput.value.trim();
     576       
     577        // Handle tags with spaces by adding quotes
     578        const tagPrefix = tag.includes(' ') ? `tag:"${tag}"` : `tag:${tag}`;
     579       
     580        // Check if tag is already in query (handle both quoted and unquoted versions)
     581        const tagRegex = new RegExp(`\\btag:("${tag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"|${tag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})\\b`, 'g');
     582        const hasTag = tagRegex.test(currentQuery);
     583       
     584        if (hasTag) {
     585            // Remove tag from query
     586            currentQuery = currentQuery.replace(tagRegex, '').replace(/\s+/g, ' ').trim();
     587        } else {
     588            // Add tag to query
     589            currentQuery = currentQuery ? `${currentQuery} ${tagPrefix}` : tagPrefix;
     590        }
     591       
     592        // Update search input
     593        searchInput.value = currentQuery;
     594       
     595        // Trigger search
     596        document.dispatchEvent(new CustomEvent('advset-search', {
     597            detail: { query: currentQuery }
     598        }));
     599    },
     600
    435601    /**
    436602     * Render the application
    437603     */
    438604    render() {
    439         const { searchQuery, items, isLoading, categories } = this.state;
     605        const { searchQuery, parsedSearchQuery, items, hiddenItems, isLoading, categories, activeTab } = this.state;
    440606       
    441607        // Render the React app
     
    446612                onSettingChange: this.handleSettingChange.bind(this),
    447613                onCategoryClick: this.scrollToCategory.bind(this),
     614                onTagClick: this.handleTagClick.bind(this),
     615                onTabChange: this.handleTabChange.bind(this),
    448616                settings: this.state.settings,
    449617                searchQuery: searchQuery,
    450                 activeCategory: this.state.activeCategory
     618                parsedSearchQuery: parsedSearchQuery,
     619                activeCategory: this.state.activeCategory,
     620                hiddenItems: hiddenItems,
     621                activeTab: activeTab
    451622            });
    452623           
     
    588759function parseSearchQuery(query) {
    589760    const result = {
    590         terms: [],
    591         exclusions: []
     761        excluded: {
     762            tags: [],
     763            terms: [],
     764        },
     765        included: {
     766            tags: [],
     767            terms: [],
     768        },
    592769    };
    593770
     
    596773
    597774    // Extract terms and phrases (with optional minus)
    598     const matches = query.match(/-?"[^"]+"|-[^\s]+|[^\s]+/g) || [];
    599    
    600     matches.forEach(match => {
    601         if (match.startsWith('-')) {
    602             // Handle exclusions
    603             const exclusion = match.startsWith('-"')
    604                 ? match.slice(2, -1) // Remove -" and "
    605                 : match.slice(1);    // Remove -
    606             if (exclusion) {
    607                 result.exclusions.push(exclusion.toLowerCase());
    608             }
    609         } else {
    610             // Handle regular terms
    611             const term = match.startsWith('"')
    612                 ? match.slice(1, -1) // Remove quotes
    613                 : match;
    614             if (term) {
    615                 result.terms.push(term.toLowerCase());
    616             }
    617         }
     775    query.matchAll(/(\-)?(tag:)?(?:("[^"]+")|([^"\s]+))/g).forEach(match => {
     776        const isExclusion = match[1] === '-';
     777        const isTag = match[2] === 'tag:';
     778        const term = (match[3] ? match[3].slice(1, -1) : match[4]).toLowerCase();
     779
     780        const targetList = result[isExclusion ? 'excluded' : 'included'][isTag ? 'tags' : 'terms'];
     781        targetList.push(term);
    618782    });
    619783
  • advanced-settings/tags/3.1.0/advanced-settings.php

    r3306579 r3323103  
    66Author: Helmut Wandl
    77Author URI: https://ehtmlu.com/
    8 Version: 3.0.2
     8Version: 3.1.0
    99Requires at least: 5.0.0
    1010Requires PHP: 7.4
  • advanced-settings/tags/3.1.0/feature-setup/categories.php

    r3281733 r3323103  
    1313
    1414    advset_register_category([
    15         'id' => 'dashboard',
     15        'id' => 'adminarea',
    1616        'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>gauge</title><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12C20,14.4 19,16.5 17.3,18C15.9,16.7 14,16 12,16C10,16 8.2,16.7 6.7,18C5,16.5 4,14.4 4,12A8,8 0 0,1 12,4M14,5.89C13.62,5.9 13.26,6.15 13.1,6.54L11.81,9.77L11.71,10C11,10.13 10.41,10.6 10.14,11.26C9.73,12.29 10.23,13.45 11.26,13.86C12.29,14.27 13.45,13.77 13.86,12.74C14.12,12.08 14,11.32 13.57,10.76L13.67,10.5L14.96,7.29L14.97,7.26C15.17,6.75 14.92,6.17 14.41,5.96C14.28,5.91 14.15,5.89 14,5.89M10,6A1,1 0 0,0 9,7A1,1 0 0,0 10,8A1,1 0 0,0 11,7A1,1 0 0,0 10,6M7,9A1,1 0 0,0 6,10A1,1 0 0,0 7,11A1,1 0 0,0 8,10A1,1 0 0,0 7,9M17,9A1,1 0 0,0 16,10A1,1 0 0,0 17,11A1,1 0 0,0 18,10A1,1 0 0,0 17,9Z" /></svg>',
    17         'title' => __('Dashboard', 'advanced-settings'),
     17        'title' => __('Admin Area', 'advanced-settings'),
    1818        'priority' => 10,
     19    ]);
     20
     21    advset_register_category([
     22        'id' => 'frontend',
     23        'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>monitor</title><path d="M21,16H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10V20H8V22H16V20H14V18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2Z" /></svg>',
     24        'title' => __('Frontend', 'advanced-settings'),
     25        'priority' => 20,
     26    ]);
     27
     28    advset_register_category([
     29        'id' => 'editing',
     30        'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>pencil</title><path d="M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z" /></svg>',
     31        'title' => __('Editing', 'advanced-settings'),
     32        'priority' => 30,
    1933    ]);
    2034
     
    2337        'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>application-cog-outline</title><path d="M21.7 18.6V17.6L22.8 16.8C22.9 16.7 23 16.6 22.9 16.5L21.9 14.8C21.9 14.7 21.7 14.7 21.6 14.7L20.4 15.2C20.1 15 19.8 14.8 19.5 14.7L19.3 13.4C19.3 13.3 19.2 13.2 19.1 13.2H17.1C16.9 13.2 16.8 13.3 16.8 13.4L16.6 14.7C16.3 14.9 16.1 15 15.8 15.2L14.6 14.7C14.5 14.7 14.4 14.7 14.3 14.8L13.3 16.5C13.3 16.6 13.3 16.7 13.4 16.8L14.5 17.6V18.6L13.4 19.4C13.3 19.5 13.2 19.6 13.3 19.7L14.3 21.4C14.4 21.5 14.5 21.5 14.6 21.5L15.8 21C16 21.2 16.3 21.4 16.6 21.5L16.8 22.8C16.9 22.9 17 23 17.1 23H19.1C19.2 23 19.3 22.9 19.3 22.8L19.5 21.5C19.8 21.3 20 21.2 20.3 21L21.5 21.4C21.6 21.4 21.7 21.4 21.8 21.3L22.8 19.6C22.9 19.5 22.9 19.4 22.8 19.4L21.7 18.6M18 19.5C17.2 19.5 16.5 18.8 16.5 18S17.2 16.5 18 16.5 19.5 17.2 19.5 18 18.8 19.5 18 19.5M12.3 22H3C1.9 22 1 21.1 1 20V4C1 2.9 1.9 2 3 2H21C22.1 2 23 2.9 23 4V13.1C22.4 12.5 21.7 12 21 11.7V6H3V20H11.3C11.5 20.7 11.8 21.4 12.3 22Z" /></svg>',
    2438        'title' => __('System', 'advanced-settings'),
    25         'priority' => 20,
    26     ]);
    27 
    28     advset_register_category([
    29         'id' => 'advset',
    30         'icon' => file_get_contents(ADVSET_DIR . '/admin-ui/images/admin-bar-icon.svg'),
    31         'title' => __('About & Options', 'advanced-settings'),
    32         'priority' => 100,
    33     ]);
    34 
    35     advset_register_category([
    36         'id' => 'advset-separator',
    37         'priority' => 99,
    38     ]);
    39 
    40     advset_register_category([
    41         'id' => 'frontend',
    42         'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>monitor</title><path d="M21,16H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10V20H8V22H16V20H14V18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2Z" /></svg>',
    43         'title' => __('Frontend', 'advanced-settings'),
    44         'priority' => 30,
    45     ]);
    46 
    47     advset_register_category([
    48         'id' => 'editing',
    49         'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>pencil</title><path d="M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z" /></svg>',
    50         'title' => __('Editing', 'advanced-settings'),
    5139        'priority' => 40,
    5240    ]);
     
    5846        'priority' => 50,
    5947    ]);
     48
     49    advset_register_category([
     50        'id' => 'advset-separator',
     51        'priority' => 99,
     52    ]);
     53
     54    advset_register_category([
     55        'id' => 'advset',
     56        'icon' => file_get_contents(ADVSET_DIR . '/admin-ui/images/admin-bar-icon.svg'),
     57        'title' => __('About & Options', 'advanced-settings'),
     58        'priority' => 100,
     59    ]);
    6060});
  • advanced-settings/tags/3.1.0/feature-setup/features/advset.php

    r3281733 r3323103  
    6565                ],
    6666                'default' => '',
     67                'description' => __('By default, we do not track plugin usage. If you agree to share your plugin usage with the developers, we will collect anonymous data about your usage. This data will be used to improve the plugin and to provide you with a better experience.', 'advanced-settings'),
    6768            ],
    6869        ]
  • advanced-settings/tags/3.1.0/feature-setup/features/developer.php

    r3306579 r3323103  
    1515    'category' => 'developer',
    1616    'ui_config' => fn() => [
     17        'tags' => [
     18            __('Developer', 'advanced-settings'),
     19            __('Debug', 'advanced-settings'),
     20            __('Performance', 'advanced-settings'),
     21        ],
    1722        'fields' => [
    1823            'enable' => [
     
    4449    'experimental' => true,
    4550    'ui_config' => fn() => [
     51        'tags' => [
     52            __('Developer', 'advanced-settings'),
     53            __('Frontend', 'advanced-settings'),
     54            __('Performance', 'advanced-settings'),
     55        ],
    4656        'fields' => [
    4757            'enable' => [
     
    8393    'experimental' => true,
    8494    'ui_config' => fn() => [
     95        'tags' => [
     96            __('Developer', 'advanced-settings'),
     97            __('Frontend', 'advanced-settings'),
     98            __('Performance', 'advanced-settings'),
     99        ],
    85100        'fields' => [
    86101            'enable' => [
     
    122137    'experimental' => true,
    123138    'ui_config' => fn() => [
     139        'tags' => [
     140            __('Developer', 'advanced-settings'),
     141            __('Admin', 'advanced-settings'),
     142            __('Posts', 'advanced-settings'),
     143        ],
    124144        'fields' => [
    125145            'enable' => [
     
    221241    'experimental' => true,
    222242    'ui_config' => fn() => [
     243        'tags' => [
     244            __('Developer', 'advanced-settings'),
     245            __('Admin', 'advanced-settings'),
     246            __('Frontend', 'advanced-settings'),
     247            __('Performance', 'advanced-settings'),
     248        ],
    223249        'fields' => [
    224250            'enable' => [
  • advanced-settings/tags/3.1.0/feature-setup/features/editing.php

    r3281733 r3323103  
    1212
    1313advset_register_feature([
     14    'id' => 'editing.posts.disable_autosave',
     15    'category' => 'editing',
     16    'ui_config' => fn() => [
     17        'tags' => [
     18            __('Editing', 'advanced-settings'),
     19            __('Posts', 'advanced-settings'),
     20            __('Performance', 'advanced-settings'),
     21            __('Database', 'advanced-settings'),
     22            __('Admin', 'advanced-settings'),
     23        ],
     24        'fields' => [
     25            'enable' => [
     26                'type' => 'toggle',
     27                'label' => __('Disable auto save', 'advanced-settings'),
     28            ],
     29        ]
     30    ],
     31    'execution_handler' => function() {
     32        define('AUTOSAVE_INTERVAL', 60 * 60 * 24 * 365 * 100); // save interval => 100 years
     33    },
     34    'priority' => 10,
     35]);
     36
     37
     38
     39advset_register_feature([
     40    'id' => 'editing.posts.limit_revisions',
     41    'category' => 'editing',
     42    'ui_config' => fn() => [
     43        'tags' => [
     44            __('Editing', 'advanced-settings'),
     45            __('Posts', 'advanced-settings'),
     46            __('Performance', 'advanced-settings'),
     47            __('Database', 'advanced-settings'),
     48            __('Admin', 'advanced-settings'),
     49        ],
     50        'fields' => [
     51            'enable' => [
     52                'type' => 'toggle',
     53                'label' => __('Limit post revisions', 'advanced-settings'),
     54                'description' => __('Reduce database size by limiting the number of saved revisions', 'advanced-settings'),
     55            ],
     56            'limit' => [
     57                'type' => 'number',
     58                'label' => __('Revisions to keep', 'advanced-settings'),
     59                'description' => __('0 means revisions are disabled.', 'advanced-settings'),
     60                'min' => 0,
     61                'default' => 5,
     62                'visible' => ['enable' => true],
     63            ],
     64            'type' => [
     65                'type' => 'radio',
     66                'label' => __('Post types', 'advanced-settings'),
     67                'description' => __('Select the post types that should be affected by the revision limit.', 'advanced-settings'),
     68                'options' => [
     69                    'post' => ['label' => 'Posts only'],
     70                    'page' => ['label' => 'Pages only'],
     71                    'post_and_page' => ['label' => 'Posts and Pages'],
     72                    'all' => ['label' => 'All post types (includes custom post types)'],
     73                ],
     74                'default' => 'post',
     75                'visible' => ['enable' => true],
     76            ],
     77        ]
     78    ],
     79    'handler_cleanup' => function($settings) {
     80        return empty($settings['enable']) ? null : $settings;
     81    },
     82    'execution_handler' => function($settings) {
     83        if ($settings['type'] === 'all') {
     84            add_filter('wp_revisions_to_keep', function($num) use($settings) {
     85                return $settings['limit'];
     86            });
     87            return;
     88        }
     89        foreach (explode('_and_', $settings['type']) as $type) {
     90            add_filter('wp_' . $type . '_revisions_to_keep', function($num) use($settings) {
     91                return $settings['limit'];
     92            });
     93        }
     94    },
     95    'priority' => 20,
     96]);
     97
     98
     99
     100advset_register_feature([
     101    'id' => 'editing.media.enable_svg',
     102    'category' => 'editing',
     103    'ui_config' => fn() => [
     104        'tags' => [
     105            __('Editing', 'advanced-settings'),
     106            __('Media', 'advanced-settings'),
     107            __('Images', 'advanced-settings'),
     108            __('Frontend', 'advanced-settings'),
     109            __('Performance', 'advanced-settings'),
     110        ],
     111        'fields' => [
     112            'enable' => [
     113                'type' => 'toggle',
     114                'label' => __('Allow SVG upload for admins', 'advanced-settings'),
     115                'description' => __('To keep the plugin lightweight, the SVG security checks in this feature are very limited. Therefore, this feature is only available to administrators.', 'advanced-settings'),
     116            ],
     117        ]
     118    ],
     119    'execution_handler' => function() {
     120        if (!current_user_can('administrator')) {
     121            return;
     122        }
     123       
     124        add_filter('upload_mimes', function($mimes) {
     125            $mimes['svg'] = 'image/svg+xml';
     126            $mimes['svgz'] = 'image/svg+xml';
     127            return $mimes;
     128        });
     129       
     130        add_filter('wp_handle_upload_prefilter', function($file) {
     131            if ($file['type'] !== 'image/svg+xml') {
     132                return $file;
     133            }
     134           
     135            $file_content = file_get_contents($file['tmp_name']);
     136
     137            // 1. Check if the file is empty
     138            if (empty($file_content)) {
     139                $file['error'] = __('Empty SVG file', 'advanced-settings');
     140                return $file;
     141            }
     142
     143            // 2. Remove XML declaration and Doctype for better compatibility
     144            $clean_content = preg_replace('/<\?xml\b[^>]*>\s*/i', '', $file_content);
     145            $clean_content = preg_replace('/<!DOCTYPE[^>[]*(\[[^]]*\])?[^>]*>\s*/is', '', $clean_content);
     146           
     147            // 3. XXE-Protection (critical!)
     148            $entity_loader_state = libxml_disable_entity_loader(true);
     149            libxml_use_internal_errors(true);
     150            libxml_clear_errors();
     151           
     152            $svg = @simplexml_load_string($clean_content);
     153           
     154            if ($svg === false) {
     155                $errors = libxml_get_errors();
     156                $first_error = !empty($errors[0]) ? $errors[0]->message : __('Unknown error', 'advanced-settings');
     157                libxml_clear_errors();
     158                /* translators: %s is the first error message from libxml */
     159                $file['error'] = sprintf(__('Invalid SVG: %s', 'advanced-settings'), esc_html($first_error));
     160                return $file;
     161            }
     162
     163            // 4. Simple, but effective security checks
     164            $unsafe_patterns = [
     165                '/<script/i',
     166                '/\bon\w+\s*=/i',
     167                '/javascript:\s*[a-z]+/i',
     168                '/<!ENTITY/i',
     169                '/<object/i',
     170                '/<iframe/i',
     171                '/<embed/i',
     172                '/href\s*=\s*["\']\s*javascript:/i',
     173                '/xlink:href\s*=\s*["\']\s*javascript:/i',
     174                '/style\s*=\s*["\'][^"]*expression\s*\(/i',
     175                '/style\s*=\s*["\'][^"]*url\s*\(\s*javascript:/i',
     176                '/<!--\[if[^\]]*?\]>.*?<!\[endif\]-->/is',
     177            ];
     178           
     179            foreach ($unsafe_patterns as $pattern) {
     180                if (preg_match($pattern, $file_content)) {
     181                    $file['error'] = __('SVG security check failed: Potential dangerous element detected.', 'advanced-settings') . ' ' . $pattern;
     182                    return $file;
     183                }
     184            }
     185   
     186            // 5. Additional protection against Base64-Encoded malicious code
     187            if (preg_match('/base64\s*[,;]/i', $file_content)) {
     188                $file['error'] = __('SVG security check failed: Base64 encoding not allowed.', 'advanced-settings');
     189                return $file;
     190            }
     191
     192            // Reset libxml state
     193            libxml_disable_entity_loader($entity_loader_state);
     194            libxml_clear_errors();
     195
     196            return $file;
     197        });
     198    },
     199    'priority' => 30,
     200]);
     201
     202
     203
     204advset_register_feature([
     205    'id' => 'editing.image.downsize_on_upload',
     206    'category' => 'editing',
     207    'ui_config' => fn() => [
     208        'tags' => [
     209            __('Editing', 'advanced-settings'),
     210            __('Media', 'advanced-settings'),
     211            __('Images', 'advanced-settings'),
     212            __('Frontend', 'advanced-settings'),
     213            __('Performance', 'advanced-settings'),
     214        ],
     215        'fields' => [
     216            'enable' => [
     217                'type' => 'toggle',
     218                'label' => __('Downsize images on upload', 'advanced-settings'),
     219            ],
     220            'max_width' => [
     221                'type' => 'number',
     222                'label' => __('Max width', 'advanced-settings'),
     223                'description' => __('Empty or 0 means no limit.', 'advanced-settings'),
     224                'min' => 0,
     225                'visible' => ['enable' => true],
     226            ],
     227            'max_height' => [
     228                'type' => 'number',
     229                'label' => __('Max height', 'advanced-settings'),
     230                'description' => __('Empty or 0 means no limit.', 'advanced-settings'),
     231                'min' => 0,
     232                'visible' => ['enable' => true],
     233            ],
     234        ]
     235    ],
     236    'handler_cleanup' => function($settings) {
     237        return empty($settings['enable']) ? null : $settings;
     238    },
     239    'execution_handler' => function($settings) {
     240        add_action('wp_handle_upload', function($upload) use($settings) {
     241            $file_path = $upload['file'];
     242            $image_info = getimagesize($file_path);
     243       
     244            if (!$image_info) return $upload;
     245       
     246            list($width, $height, $type) = $image_info;
     247       
     248            $max_width  = ((int) $settings['max_width']) ?? null;
     249            $max_height = ((int) $settings['max_height']) ?? null;
     250       
     251            if (($max_width === null || $width <= $max_width) && ($max_height === null || $height <= $max_height)) {
     252                return $upload; // nothig to do
     253            }
     254       
     255            $editor = wp_get_image_editor($file_path);
     256            if (is_wp_error($editor)) return $upload;
     257       
     258            $editor->resize($max_width, $max_height, false);
     259            $editor->save($file_path); // overwrites original
     260
     261            return $upload;
     262        });
     263    },
     264    'priority' => 40,
     265]);
     266
     267
     268
     269advset_register_feature([
    14270    'id' => 'editing.image.jpeg_quality',
    15271    'category' => 'editing',
    16272    'ui_config' => fn() => [
     273        'tags' => [
     274            __('Editing', 'advanced-settings'),
     275            __('Media', 'advanced-settings'),
     276            __('Images', 'advanced-settings'),
     277            __('Frontend', 'advanced-settings'),
     278            __('Performance', 'advanced-settings'),
     279        ],
    17280        'fields' => [
    18281            'jpeg_quality' => [
     
    33296        });
    34297    },
    35     'priority' => 20,
    36 ]);
    37 
    38 
    39 
    40 advset_register_feature([
    41     'id' => 'editing.image.downsize_on_upload',
    42     'category' => 'editing',
    43     'ui_config' => fn() => [
    44         'fields' => [
    45             'enable' => [
    46                 'type' => 'toggle',
    47                 'label' => __('Downsize images on upload', 'advanced-settings'),
    48             ],
    49             'max_width' => [
    50                 'type' => 'number',
    51                 'label' => __('Max width', 'advanced-settings'),
    52                 'description' => __('Empty or 0 means no limit.', 'advanced-settings'),
    53                 'min' => 0,
    54                 'visible' => ['enable' => true],
    55             ],
    56             'max_height' => [
    57                 'type' => 'number',
    58                 'label' => __('Max height', 'advanced-settings'),
    59                 'description' => __('Empty or 0 means no limit.', 'advanced-settings'),
    60                 'min' => 0,
    61                 'visible' => ['enable' => true],
    62             ],
    63         ]
    64     ],
    65     'handler_cleanup' => function($settings) {
    66         return empty($settings['enable']) ? null : $settings;
    67     },
    68     'execution_handler' => function($settings) {
    69         add_action('wp_handle_upload', function($upload) use($settings) {
    70             $file_path = $upload['file'];
    71             $image_info = getimagesize($file_path);
    72        
    73             if (!$image_info) return $upload;
    74        
    75             list($width, $height, $type) = $image_info;
    76        
    77             $max_width  = ((int) $settings['max_width']) ?? null;
    78             $max_height = ((int) $settings['max_height']) ?? null;
    79        
    80             if (($max_width === null || $width <= $max_width) && ($max_height === null || $height <= $max_height)) {
    81                 return $upload; // nothig to do
    82             }
    83        
    84             $editor = wp_get_image_editor($file_path);
    85             if (is_wp_error($editor)) return $upload;
    86        
    87             $editor->resize($max_width, $max_height, false);
    88             $editor->save($file_path); // overwrites original
    89 
    90             return $upload;
    91         });
    92     },
    93     'priority' => 20,
    94 ]);
    95 
    96 
     298    'priority' => 50,
     299]);
     300
     301
     302
     303advset_register_feature([
     304    'id' => 'editing.thumbnails.enable_support',
     305    'category' => 'editing',
     306    'ui_config' => fn() => [
     307        'tags' => [
     308            __('Editing', 'advanced-settings'),
     309            __('Frontend', 'advanced-settings'),
     310            __('Content', 'advanced-settings'),
     311            __('Images', 'advanced-settings'),
     312            __('Media', 'advanced-settings'),
     313        ],
     314        'fields' => [
     315            'enable' => [
     316                'type' => 'toggle',
     317                'label' => __('Add thumbnail support', 'advanced-settings'),
     318                'disabled' => !ADVSET_THUMBS,
     319                'description' => ADVSET_THUMBS
     320                    ? ''
     321                    : __('Already supported by current theme', 'advanced-settings'),
     322            ],
     323        ]
     324    ],
     325    'execution_handler' => function() {
     326        add_action('after_setup_theme', function (){
     327            add_theme_support( 'post-thumbnails' );
     328        });
     329    },
     330    'priority' => 60,
     331]);
     332define( 'ADVSET_THUMBS', !current_theme_supports('post-thumbnails') );
     333
     334
     335
     336advset_register_feature([
     337    'id' => 'editing.thumbnails.auto_from_first_image',
     338    'category' => 'editing',
     339    'ui_config' => fn() => [
     340        'tags' => [
     341            __('Editing', 'advanced-settings'),
     342            __('Frontend', 'advanced-settings'),
     343            __('Content', 'advanced-settings'),
     344            __('Images', 'advanced-settings'),
     345            __('Media', 'advanced-settings'),
     346            __('Automations', 'advanced-settings'),
     347        ],
     348        'fields' => [
     349            'enable' => [
     350                'type' => 'toggle',
     351                'label' => __('Automatically generate the Post Thumbnail', 'advanced-settings'),
     352                'description' => __('from the first image in post', 'advanced-settings'),
     353            ],
     354        ]
     355    ],
     356    'execution_handler' => function() {
     357        require_once ADVSET_DIR . '/feature-setup/features/includes/frontend.auto_thumbs.php';
     358        add_action('transition_post_status', 'advset__feature__auto_thumbs', 10, 3);
     359    },
     360    'priority' => 70,
     361]);
     362
     363
  • advanced-settings/tags/3.1.0/feature-setup/features/frontend.php

    r3281733 r3323103  
    1212
    1313advset_register_feature([
    14     'id' => 'frontend.meta.facebook_og_metas',
    15     'category' => 'frontend',
    16     'ui_config' => fn() => [
    17         'fields' => [
    18             'enable' => [
    19                 'type' => 'toggle',
    20                 'label' => __('Enable Facebook Open Graph Meta Tags', 'advanced-settings'),
     14    'id' => 'frontend.http_headers.remove_php_version',
     15    'category' => 'frontend',
     16    'ui_config' => fn() => [
     17        'tags' => [
     18            __('Frontend', 'advanced-settings'),
     19            __('HTTP', 'advanced-settings'),
     20            __('Security', 'advanced-settings'),
     21            __('Cleanup', 'advanced-settings'),
     22        ],
     23        'fields' => [
     24            'enable' => [
     25                'type' => 'toggle',
     26                'label' => __('Remove PHP version from HTTP headers', 'advanced-settings'),
     27                'description' => __('Removes the PHP version from the HTTP headers of the website', 'advanced-settings'),
     28            ],
     29        ]
     30    ],
     31    'execution_handler' => function() {
     32        header_remove('X-Powered-By');
     33    },
     34    'priority' => 10,
     35]);
     36
     37
     38
     39advset_register_feature([
     40    'id' => 'frontend.http_headers.add_security_headers',
     41    'category' => 'frontend',
     42    'ui_config' => fn() => [
     43        'tags' => [
     44            __('Frontend', 'advanced-settings'),
     45            __('HTTP', 'advanced-settings'),
     46            __('Security', 'advanced-settings'),
     47        ],
     48        'fields' => [
     49            'enable' => [
     50                'type' => 'toggle',
     51                'label' => __('Add security headers', 'advanced-settings'),
     52                /* translators: %s is a link to securityheaders.com */
     53                'descriptionHtml' => sprintf(__('Adds various security headers to HTTP responses. You can check your website\'s security headers with %s.', 'advanced-settings'), '<a href="https://securityheaders.com/?q=' . rawurlencode(get_home_url()) . '&hide=on&followRedirects=on" target="_blank">securityheaders.com</a>'),
     54            ],
     55            'info' => [
     56                'type' => 'info',
     57                'label' => __('Caution:', 'advanced-settings'),
     58                'description' => __('If you integrate external services into your website or your website requires access to browser features like camera, microphone, geolocation, or other device sensors, the security headers may be set too strictly. In this case, simply disable this feature. You can implement customized headers via code if necessary.', 'advanced-settings'),
     59                'visible' => ['enable' => true]
    2160            ],
    2261        ]
     
    2463    'execution_handler' => function() {
    2564        if ( advset_is_admin_area() ) return;
    26 
    27         add_action('wp_head', function() {
    28             global $post;
    29             if (is_single() || is_page()) { ?>
    30                 <meta property="og:title" content="<?php single_post_title(''); ?>" />
    31                 <meta property="og:description" content="<?php echo strip_tags(get_the_excerpt($post->ID)); ?>" />
    32                 <meta property="og:type" content="article" />
    33                 <meta property="og:image" content="<?php if (function_exists('wp_get_attachment_thumb_url')) {echo wp_get_attachment_url(get_post_thumbnail_id($post->ID)); }?>" />
    34             <?php }
    35         });
    36     },
    37     'priority' => 10,
     65        add_action('send_headers', function() {
     66            header('X-Content-Type-Options: nosniff');
     67            header('X-Frame-Options: SAMEORIGIN');
     68            header('X-XSS-Protection: 1; mode=block');
     69            header('Referrer-Policy: strict-origin-when-cross-origin');
     70            header('Content-Security-Policy: object-src \'none\';');
     71            header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
     72            header('Permissions-Policy: accelerometer=(),camera=(),geolocation=(),gyroscope=(),magnetometer=(),microphone=(),payment=(),usb=(),interest-cohort=()');
     73        });
     74    },
     75    'priority' => 20,
    3876]);
    3977
     
    4482    'category' => 'frontend',
    4583    'ui_config' => fn() => [
     84        'tags' => [
     85            __('Frontend', 'advanced-settings'),
     86            __('Meta', 'advanced-settings'),
     87            __('Favicon', 'advanced-settings'),
     88            __('SEO', 'advanced-settings'),
     89        ],
    4690        'fields' => [
    4791            'enable' => [
     
    66110        });
    67111    },
    68     'priority' => 10,
     112    'priority' => 30,
     113]);
     114
     115
     116
     117advset_register_feature([
     118    'id' => 'frontend.meta.facebook_og_metas',
     119    'category' => 'frontend',
     120    'ui_config' => fn() => [
     121        'tags' => [
     122            __('Frontend', 'advanced-settings'),
     123            __('Meta', 'advanced-settings'),
     124            __('SEO', 'advanced-settings'),
     125        ],
     126        'fields' => [
     127            'enable' => [
     128                'type' => 'toggle',
     129                'label' => __('Enable Facebook Open Graph Meta Tags', 'advanced-settings'),
     130            ],
     131        ]
     132    ],
     133    'execution_handler' => function() {
     134        if ( advset_is_admin_area() ) return;
     135
     136        add_action('wp_head', function() {
     137            global $post;
     138            if (is_single() || is_page()) { ?>
     139                <meta property="og:title" content="<?php single_post_title(''); ?>" />
     140                <meta property="og:description" content="<?php echo strip_tags(get_the_excerpt($post->ID)); ?>" />
     141                <meta property="og:type" content="article" />
     142                <meta property="og:image" content="<?php if (function_exists('wp_get_attachment_thumb_url')) {echo wp_get_attachment_url(get_post_thumbnail_id($post->ID)); }?>" />
     143            <?php }
     144        });
     145    },
     146    'priority' => 40,
    69147]);
    70148
     
    75153    'category' => 'frontend',
    76154    'ui_config' => fn() => [
     155        'tags' => [
     156            __('Frontend', 'advanced-settings'),
     157            __('Meta', 'advanced-settings'),
     158            __('Cleanup', 'advanced-settings'),
     159        ],
    77160        'fields' => [
    78161            'enable' => [
     
    86169        remove_action( 'wp_head', 'wp_shortlink_wp_head');
    87170    },
    88     'priority' => 10,
     171    'priority' => 50,
    89172]);
    90173
     
    95178    'category' => 'frontend',
    96179    'ui_config' => fn() => [
     180        'tags' => [
     181            __('Frontend', 'advanced-settings'),
     182            __('Meta', 'advanced-settings'),
     183            __('Cleanup', 'advanced-settings'),
     184        ],
    97185        'fields' => [
    98186            'enable' => [
     
    107195        remove_action( 'wp_head', 'rsd_link');
    108196    },
    109     'priority' => 10,
     197    'priority' => 60,
    110198]);
    111199
     
    116204    'category' => 'frontend',
    117205    'ui_config' => fn() => [
     206        'tags' => [
     207            __('Frontend', 'advanced-settings'),
     208            __('Meta', 'advanced-settings'),
     209            __('Security', 'advanced-settings'),
     210            __('Cleanup', 'advanced-settings'),
     211        ],
    118212        'fields' => [
    119213            'enable' => [
    120214                'type' => 'toggle',
    121215                'label' => __('Remove generator meta tag', 'advanced-settings'),
     216                'description' => __('Removes the WordPress version from the head section of the website', 'advanced-settings'),
    122217            ],
    123218        ]
     
    127222        remove_action( 'wp_head', 'wp_generator');
    128223    },
    129     'priority' => 10,
     224    'priority' => 70,
    130225]);
    131226
     
    136231    'category' => 'frontend',
    137232    'ui_config' => fn() => [
     233        'tags' => [
     234            __('Frontend', 'advanced-settings'),
     235            __('Meta', 'advanced-settings'),
     236            __('SEO', 'advanced-settings'),
     237        ],
    138238        'fields' => [
    139239            'enable' => [
     
    178278        });
    179279    },
    180     'priority' => 10,
     280    'priority' => 80,
    181281]);
    182282
     
    187287    'category' => 'frontend',
    188288    'ui_config' => fn() => [
     289        'tags' => [
     290            __('Frontend', 'advanced-settings'),
     291            __('Content', 'advanced-settings'),
     292            __('Security', 'advanced-settings'),
     293            __('Cleanup', 'advanced-settings'),
     294        ],
    189295        'fields' => [
    190296            'enable' => [
     
    210316        }, 10, 2 );
    211317    },
    212     'priority' => 20,
     318    'priority' => 90,
     319]);
     320
     321
     322
     323advset_register_feature([
     324    'id' => 'frontend.title.improve_format',
     325    'category' => 'frontend',
     326    'deprecated' => true,
     327    'ui_config' => fn() => [
     328        'tags' => [
     329            __('Developer', 'advanced-settings'),
     330            __('Frontend', 'advanced-settings'),
     331            __('Content', 'advanced-settings'),
     332            __('Automations', 'advanced-settings'),
     333        ],
     334        'fields' => [
     335            'enable' => [
     336                'type' => 'toggle',
     337                'label' => __('Adjust the wp_title function', 'advanced-settings'),
     338            ],
     339        ]
     340    ],
     341    'execution_handler' => function() {
     342        add_filter('wp_title', function($title, $sep) {
     343            global $paged, $page;
     344   
     345            if ( is_feed() )
     346                return $title;
     347   
     348            // Add the site name.
     349            $title .= get_bloginfo( 'name' );
     350   
     351            // Add the site description for the home/front page.
     352            $site_description = get_bloginfo( 'description', 'display' );
     353            if ( $site_description && ( is_home() || is_front_page() ) )
     354                $title = "$title $sep $site_description";
     355   
     356            // Add a page number if necessary.
     357            if ( $paged >= 2 || $page >= 2 )
     358                $title = "$title $sep " . sprintf( __( 'Page %s', 'responsive' ), max( $paged, $page ) );
     359   
     360            return $title;
     361        }, 10, 2);
     362    },
     363    'priority' => 100,
    213364]);
    214365
     
    219370    'category' => 'frontend',
    220371    'ui_config' => fn() => [
     372        'tags' => [
     373            __('Editing', 'advanced-settings'),
     374            __('Frontend', 'advanced-settings'),
     375            __('Content', 'advanced-settings'),
     376            __('Cleanup', 'advanced-settings'),
     377            __('Automations', 'advanced-settings'),
     378        ],
    221379        'fields' => [
    222380            'enable' => [
     
    230388        add_filter( 'run_wptexturize', '__return_false' );
    231389    },
    232     'priority' => 10,
    233 ]);
    234 
    235 
    236 
    237 advset_register_feature([
    238     'id' => 'frontend.thumbnails.enable_support',
    239     'category' => 'frontend',
    240     'ui_config' => fn() => [
    241         'fields' => [
    242             'enable' => [
    243                 'type' => 'toggle',
    244                 'label' => __('Add thumbnail support', 'advanced-settings'),
    245                 'disabled' => !ADVSET_THUMBS,
    246                 'description' => ADVSET_THUMBS
    247                     ? ''
    248                     : __('Already supported by current theme', 'advanced-settings'),
    249             ],
    250         ]
    251     ],
    252     'execution_handler' => function() {
    253         add_action('after_setup_theme', function (){
    254             add_theme_support( 'post-thumbnails' );
    255         });
    256     },
    257     'priority' => 30,
    258 ]);
    259 define( 'ADVSET_THUMBS', !current_theme_supports('post-thumbnails') );
    260 
    261 
    262 
    263 advset_register_feature([
    264     'id' => 'frontend.thumbnails.auto_from_first_image',
    265     'category' => 'frontend',
    266     'ui_config' => fn() => [
    267         'fields' => [
    268             'enable' => [
    269                 'type' => 'toggle',
    270                 'label' => __('Automatically generate the Post Thumbnail', 'advanced-settings'),
    271                 'description' => __('from the first image in post', 'advanced-settings'),
    272             ],
    273         ]
    274     ],
    275     'execution_handler' => function() {
    276         require_once ADVSET_DIR . '/feature-setup/features/includes/frontend.auto_thumbs.php';
    277         add_action('transition_post_status', 'advset__feature__auto_thumbs', 10, 3);
    278     },
     390    'priority' => 110,
     391]);
     392
     393
     394
     395advset_register_feature([
     396    'id' => 'frontend.oembed.disable',
     397    'category' => 'frontend',
     398    'ui_config' => fn() => [
     399        'tags' => [
     400            __('Editing', 'advanced-settings'),
     401            __('Frontend', 'advanced-settings'),
     402            __('Content', 'advanced-settings'),
     403            __('Security', 'advanced-settings'),
     404            __('Cleanup', 'advanced-settings'),
     405            __('GDPR', 'advanced-settings'),
     406            __('Performance', 'advanced-settings'),
     407            __('Automations', 'advanced-settings'),
     408        ],
     409        'fields' => [
     410            'enable' => [
     411                'type' => 'toggle',
     412                'label' => __('Disable auto-embed of external content', 'advanced-settings'),
     413                'description' => __('Disables WordPress oEmbed, which automatically converts URLs into embedded content.', 'advanced-settings'),
     414            ],
     415        ]
     416    ],
     417    'execution_handler' => function() {
     418        remove_action('wp_head', 'wp_oembed_add_discovery_links');
     419        remove_action('wp_head', 'wp_oembed_add_host_js');
     420        remove_action('wp_head', 'rest_output_link_wp_head');
     421        remove_action('rest_api_init', 'wp_oembed_register_route');
     422        remove_filter('the_content', [$GLOBALS['wp_embed'], 'autoembed'], 8);
     423        remove_filter('oembed_dataparse', 'wp_filter_oembed_result', 10);
     424        add_filter('embed_oembed_discover', '__return_false');
     425    },
     426    'priority' => 120,
    279427]);
    280428
     
    285433    'category' => 'frontend',
    286434    'ui_config' => fn() => [
     435        'tags' => [
     436            __('Editing', 'advanced-settings'),
     437            __('Frontend', 'advanced-settings'),
     438            __('Content', 'advanced-settings'),
     439            __('Cleanup', 'advanced-settings'),
     440            __('Automations', 'advanced-settings'),
     441        ],
    287442        'fields' => [
    288443            'enable' => [
     
    308463        });
    309464    },
     465    'priority' => 130,
    310466]);
    311467
     
    316472    'category' => 'frontend',
    317473    'ui_config' => fn() => [
     474        'tags' => [
     475            __('Frontend', 'advanced-settings'),
     476            __('Content', 'advanced-settings'),
     477            __('SEO', 'advanced-settings'),
     478            __('Cleanup', 'advanced-settings'),
     479            __('Automations', 'advanced-settings'),
     480        ],
    318481        'fields' => [
    319482            'enable' => [
     
    339502        });
    340503    },
     504    'priority' => 140,
    341505]);
    342506
     
    347511    'category' => 'frontend',
    348512    'ui_config' => fn() => [
     513        'tags' => [
     514            __('Frontend', 'advanced-settings'),
     515            __('Content', 'advanced-settings'),
     516            __('Comments', 'advanced-settings'),
     517            __('Automations', 'advanced-settings'),
     518            __('Cleanup', 'advanced-settings'),
     519        ],
    349520        'fields' => [
    350521            'enable' => [
     
    367538        }, 10);
    368539    },
    369     'priority' => 40,
     540    'priority' => 150,
     541]);
     542
     543
     544
     545advset_register_feature([
     546    'id' => 'frontend.post.show_author_bio',
     547    'category' => 'frontend',
     548    'deprecated' => true,
     549    'ui_config' => fn() => [
     550        'tags' => [
     551            __('Frontend', 'advanced-settings'),
     552            __('Content', 'advanced-settings'),
     553            __('Automations', 'advanced-settings'),
     554        ],
     555        'fields' => [
     556            'enable' => [
     557                'type' => 'toggle',
     558                'label' => __('Insert author bio in each post', 'advanced-settings'),
     559            ],
     560        ]
     561    ],
     562    'execution_handler' => function() {
     563        add_filter('the_content', function($content='') {
     564            return $content.' <div id="entry-author-info">
     565                <div id="author-avatar">
     566                    '. get_avatar( get_the_author_meta( 'user_email' ), apply_filters( 'author_bio_avatar_size', 100 ) ) .'
     567                </div>
     568                <div id="author-description">
     569                    <h2>'. sprintf( __( 'About %s' ), get_the_author() ) .'</h2>
     570                    '. get_the_author_meta( 'description' ) .'
     571                    <div id="author-link">
     572                        <a href="'. get_author_posts_url( get_the_author_meta( 'ID' ) ) .'">
     573                            '. sprintf( __( 'View all posts by %s <span class="meta-nav">&rarr;</span>' ), get_the_author() ) .'
     574                        </a>
     575                    </div>
     576                </div>
     577            </div>';
     578        });
     579    },
     580    'priority' => 160,
     581]);
     582
     583
     584
     585advset_register_feature([
     586    'id' => 'frontend.user.allow_html_bio',
     587    'category' => 'frontend',
     588    'deprecated' => true,
     589    'ui_config' => fn() => [
     590        'tags' => [
     591            __('Admin', 'advanced-settings'),
     592            __('Frontend', 'advanced-settings'),
     593            __('Content', 'advanced-settings'),
     594        ],
     595        'fields' => [
     596            'enable' => [
     597                'type' => 'toggle',
     598                'label' => __('Allow complex HTML in user profile description', 'advanced-settings'),
     599            ],
     600        ]
     601    ],
     602    'execution_handler' => function() {
     603        remove_filter('pre_user_description', 'wp_filter_kses');
     604    },
     605    'priority' => 170,
    370606]);
    371607
     
    376612    'category' => 'frontend',
    377613    'ui_config' => fn() => [
     614        'tags' => [
     615            __('Frontend', 'advanced-settings'),
     616            __('Content', 'advanced-settings'),
     617            __('Security', 'advanced-settings'),
     618            __('Automations', 'advanced-settings'),
     619        ],
    378620        'fields' => [
    379621            'enable' => [
     
    480722        });
    481723    },
    482     'priority' => 10,
    483 ]);
    484 
    485 
    486 
    487 advset_register_feature([
    488     'id' => 'frontend.title.improve_format',
     724    'priority' => 180,
     725]);
     726
     727
     728
     729advset_register_feature([
     730    'id' => 'frontend.analytics.google',
    489731    'category' => 'frontend',
    490732    'deprecated' => true,
    491733    'ui_config' => fn() => [
    492         'fields' => [
    493             'enable' => [
    494                 'type' => 'toggle',
    495                 'label' => __('Adjust the wp_title function', 'advanced-settings'),
    496             ],
    497         ]
    498     ],
    499     'execution_handler' => function() {
    500         add_filter('wp_title', function($title, $sep) {
    501             global $paged, $page;
    502    
    503             if ( is_feed() )
    504                 return $title;
    505    
    506             // Add the site name.
    507             $title .= get_bloginfo( 'name' );
    508    
    509             // Add the site description for the home/front page.
    510             $site_description = get_bloginfo( 'description', 'display' );
    511             if ( $site_description && ( is_home() || is_front_page() ) )
    512                 $title = "$title $sep $site_description";
    513    
    514             // Add a page number if necessary.
    515             if ( $paged >= 2 || $page >= 2 )
    516                 $title = "$title $sep " . sprintf( __( 'Page %s', 'responsive' ), max( $paged, $page ) );
    517    
    518             return $title;
    519         }, 10, 2);
    520     },
    521     'priority' => 10,
    522 ]);
    523 
    524 
    525 
    526 advset_register_feature([
    527     'id' => 'frontend.post.show_author_bio',
    528     'category' => 'frontend',
    529     'deprecated' => true,
    530     'ui_config' => fn() => [
    531         'fields' => [
    532             'enable' => [
    533                 'type' => 'toggle',
    534                 'label' => __('Insert author bio in each post', 'advanced-settings'),
    535             ],
    536         ]
    537     ],
    538     'execution_handler' => function() {
    539         add_filter('the_content', function($content='') {
    540             return $content.' <div id="entry-author-info">
    541                 <div id="author-avatar">
    542                     '. get_avatar( get_the_author_meta( 'user_email' ), apply_filters( 'author_bio_avatar_size', 100 ) ) .'
    543                 </div>
    544                 <div id="author-description">
    545                     <h2>'. sprintf( __( 'About %s' ), get_the_author() ) .'</h2>
    546                     '. get_the_author_meta( 'description' ) .'
    547                     <div id="author-link">
    548                         <a href="'. get_author_posts_url( get_the_author_meta( 'ID' ) ) .'">
    549                             '. sprintf( __( 'View all posts by %s <span class="meta-nav">&rarr;</span>' ), get_the_author() ) .'
    550                         </a>
    551                     </div>
    552                 </div>
    553             </div>';
    554         });
    555     },
    556     'priority' => 10,
    557 ]);
    558 
    559 
    560 
    561 advset_register_feature([
    562     'id' => 'frontend.user.allow_html_bio',
    563     'category' => 'frontend',
    564     'deprecated' => true,
    565     'ui_config' => fn() => [
    566         'fields' => [
    567             'enable' => [
    568                 'type' => 'toggle',
    569                 'label' => __('Allow complex HTML in user profile description', 'advanced-settings'),
    570             ],
    571         ]
    572     ],
    573     'execution_handler' => function() {
    574         remove_filter('pre_user_description', 'wp_filter_kses');
    575     },
    576     'priority' => 10,
    577 ]);
    578 
    579 
    580 
    581 advset_register_feature([
    582     'id' => 'frontend.analytics.google',
    583     'category' => 'frontend',
    584     'deprecated' => true,
    585     'ui_config' => fn() => [
     734        'tags' => [
     735            __('Frontend', 'advanced-settings'),
     736            __('Meta', 'advanced-settings'),
     737            __('GDPR', 'advanced-settings'),
     738        ],
    586739        'fields' => [
    587740            'enable' => [
     
    614767        });
    615768    },
    616     'priority' => 10,
     769    'priority' => 190,
    617770]);
    618771
     
    624777    'deprecated' => true,
    625778    'ui_config' => fn() => [
     779        'tags' => [
     780            __('Frontend', 'advanced-settings'),
     781            __('Meta', 'advanced-settings'),
     782        ],
    626783        'fields' => [
    627784            'enable' => [
     
    655812        }, 10, 2 );
    656813    },
    657     'priority' => 10,
     814    'priority' => 200,
    658815]);
    659816
     
    664821    'category' => 'frontend',
    665822    'ui_config' => fn() => [
     823        'tags' => [
     824            __('Frontend', 'advanced-settings'),
     825            __('HTML', 'advanced-settings'),
     826            __('Performance', 'advanced-settings'),
     827            __('Automations', 'advanced-settings'),
     828        ],
    666829        'fields' => [
    667830            'enable' => [
     
    678841        });
    679842    },
    680     'priority' => 10,
     843    'priority' => 210,
    681844]);
    682845
     
    687850    'category' => 'frontend',
    688851    'ui_config' => fn() => [
     852        'tags' => [
     853            __('Frontend', 'advanced-settings'),
     854            __('HTML', 'advanced-settings'),
     855            __('Performance', 'advanced-settings'),
     856            __('Cleanup', 'advanced-settings'),
     857            __('Automations', 'advanced-settings'),
     858        ],
    689859        'fields' => [
    690860            'enable' => [
     
    701871        });
    702872    },
    703     'priority' => 10,
     873    'priority' => 220,
    704874]);
    705875
     
    710880    'category' => 'frontend',
    711881    'ui_config' => fn() => [
     882        'tags' => [
     883            __('Frontend', 'advanced-settings'),
     884            __('Content', 'advanced-settings'),
     885            __('Emojis', 'advanced-settings'),
     886            __('Automations', 'advanced-settings'),
     887            __('Cleanup', 'advanced-settings'),
     888            __('GDPR', 'advanced-settings'),
     889        ],
    712890        'fields' => [
    713891            'enable' => [
     
    728906        add_filter('emoji_svg_url', '__return_false');
    729907    },
    730     'priority' => 10,
    731 ]);
    732 
    733 
     908    'priority' => 230,
     909]);
     910
     911
  • advanced-settings/tags/3.1.0/feature-setup/features/system.php

    r3306579 r3323103  
    1515    'category' => 'system',
    1616    'ui_config' => fn() => [
     17        'tags' => [
     18            __('Admin', 'advanced-settings'),
     19            __('Frontend', 'advanced-settings'),
     20            __('Meta', 'advanced-settings'),
     21            __('Favicon', 'advanced-settings'),
     22            __('Cleanup', 'advanced-settings'),
     23            __('Branding', 'advanced-settings'),
     24        ],
    1725        'fields' => [
    1826            'enable' => [
     
    4048    'category' => 'system',
    4149    'ui_config' => fn() => [
     50        'tags' => [
     51            __('System', 'advanced-settings'),
     52            __('Admin', 'advanced-settings'),
     53            __('Frontend', 'advanced-settings'),
     54            __('Cleanup', 'advanced-settings'),
     55            __('Security', 'advanced-settings'),
     56            __('Comments', 'advanced-settings'),
     57        ],
    4258        'fields' => [
    4359            'enable' => [
     
    6076        } );
    6177    },
    62     'priority' => 10,
    63 ]);
    64 
    65 
    66 
    67 advset_register_feature([
    68     'id' => 'system.posts.disable_autosave',
    69     'category' => 'system',
    70     'ui_config' => fn() => [
    71         'fields' => [
    72             'enable' => [
    73                 'type' => 'toggle',
    74                 'label' => __('Disable auto save', 'advanced-settings'),
    75             ],
    76         ]
    77     ],
    78     'execution_handler' => function() {
    79         define('AUTOSAVE_INTERVAL', 60 * 60 * 24 * 365 * 100); // save interval => 100 years
    80     },
    8178    'priority' => 20,
    8279]);
     
    8582
    8683advset_register_feature([
     84    'id' => 'system.xmlrpc.disable',
     85    'category' => 'system',
     86    'ui_config' => fn() => [
     87        'tags' => [
     88            __('System', 'advanced-settings'),
     89            __('Security', 'advanced-settings'),
     90            __('Performance', 'advanced-settings'),
     91            __('Cleanup', 'advanced-settings'),
     92        ],
     93        'fields' => [
     94            'enable' => [
     95                'type' => 'toggle',
     96                'label' => __('Disable XML-RPC', 'advanced-settings'),
     97                'description' => __('Disables XML-RPC functionality for security', 'advanced-settings'),
     98            ],
     99        ]
     100    ],
     101    'execution_handler' => function() {
     102        add_filter('xmlrpc_enabled', '__return_false');
     103    },
     104    'priority' => 30,
     105]);
     106
     107
     108
     109advset_register_feature([
     110    'id' => 'system.rest.disable_public',
     111    'category' => 'system',
     112    'ui_config' => fn() => [
     113        'tags' => [
     114            __('System', 'advanced-settings'),
     115            __('Security', 'advanced-settings'),
     116            __('Performance', 'advanced-settings'),
     117            __('Cleanup', 'advanced-settings'),
     118        ],
     119        'fields' => [
     120            'enable' => [
     121                'type' => 'toggle',
     122                'label' => __('Disable public REST API', 'advanced-settings'),
     123                'description' => __('Disables REST API access for non-authenticated users', 'advanced-settings'),
     124            ],
     125        ]
     126    ],
     127    'execution_handler' => function() {
     128        add_filter('rest_authentication_errors', function($result) {
     129            if (!empty($result)) {
     130                return $result;
     131            }
     132            if (!is_user_logged_in()) {
     133                return new WP_Error('rest_forbidden', 'REST API restricted to authenticated users.', ['status' => 401]);
     134            }
     135            return $result;
     136        });
     137    },
     138    'priority' => 40,
     139]);
     140
     141
     142
     143advset_register_feature([
    87144    'id' => 'system.updates.skip_bundled_themes',
    88145    'category' => 'system',
    89146    'ui_config' => fn() => [
     147        'tags' => [
     148            __('System', 'advanced-settings'),
     149            __('Updates', 'advanced-settings'),
     150            __('Cleanup', 'advanced-settings'),
     151        ],
    90152        'fields' => [
    91153            'enable' => [
     
    130192        }
    131193    },
    132     'priority' => 20,
     194    'priority' => 50,
    133195]);
    134196
     
    139201    'category' => 'system',
    140202    'ui_config' => fn() => [
     203        'tags' => [
     204            __('System', 'advanced-settings'),
     205            __('Updates', 'advanced-settings'),
     206            __('Notifications', 'advanced-settings'),
     207            __('Emails', 'advanced-settings'),
     208        ],
    141209        'fields' => [
    142210            'enable' => [
     
    150218        add_filter('auto_core_update_send_email', '__return_false');
    151219    },
    152     'priority' => 20,
     220    'priority' => 60,
    153221]);
    154222
     
    159227    'category' => 'system',
    160228    'ui_config' => fn() => [
     229        'tags' => [
     230            __('System', 'advanced-settings'),
     231            __('Updates', 'advanced-settings'),
     232            __('Notifications', 'advanced-settings'),
     233            __('Emails', 'advanced-settings'),
     234        ],
    161235        'fields' => [
    162236            'enable' => [
     
    170244        add_filter('auto_plugin_update_send_email', '__return_false');
    171245    },
    172     'priority' => 30,
     246    'priority' => 70,
    173247]);
    174248
     
    179253    'category' => 'system',
    180254    'ui_config' => fn() => [
     255        'tags' => [
     256            __('System', 'advanced-settings'),
     257            __('Updates', 'advanced-settings'),
     258            __('Notifications', 'advanced-settings'),
     259            __('Emails', 'advanced-settings'),
     260        ],
    181261        'fields' => [
    182262            'enable' => [
     
    190270        add_filter('auto_theme_update_send_email', '__return_false');
    191271    },
    192     'priority' => 40,
    193 ]);
    194 
    195 
     272    'priority' => 80,
     273]);
     274
     275
  • advanced-settings/tags/3.1.0/migrations/3.x.x.php

    r3281733 r3323103  
    152152    },
    153153
     154    '3.1.0' => function() {
     155        $settings = get_option('advanced_settings_settings', []);
     156
     157        // Add admin bar logo to branding feature
     158        if ( !empty($settings['dashboard.adminbar.custom_logo']['url']) ) {
     159            $settings['dashboard.branding.customize'] = [
     160                'enable' => true,
     161                'admin_bar_logo' => $settings['dashboard.adminbar.custom_logo']['url'],
     162            ];
     163        }
     164
     165        // Remove old admin bar logo option
     166        unset($settings['dashboard.adminbar.custom_logo']);
     167
     168        // Move old auto save setting to editing category
     169        if ( !empty($settings['system.posts.disable_autosave']) ) {
     170            $settings['editing.posts.disable_autosave'] = $settings['system.posts.disable_autosave'];
     171            unset($settings['system.posts.disable_autosave']);
     172        }
     173
     174        // Move old auto thumbs setting to editing category
     175        if ( !empty($settings['frontend.thumbnails.auto_from_first_image']) ) {
     176            $settings['editing.thumbnails.auto_from_first_image'] = $settings['frontend.thumbnails.auto_from_first_image'];
     177            unset($settings['frontend.thumbnails.auto_from_first_image']);
     178        }
     179
     180        // Move old thumbs setting to editing category
     181        if ( !empty($settings['frontend.thumbnails.enable_support']) ) {
     182            $settings['editing.thumbnails.enable_support'] = $settings['frontend.thumbnails.enable_support'];
     183            unset($settings['frontend.thumbnails.enable_support']);
     184        }
     185
     186        // Rename dashboard features to adminarea features
     187        foreach ($settings as $feature_id => $feature_settings) {
     188            if ( strpos($feature_id, 'dashboard.') === 0 ) {
     189                $settings['adminarea.' . substr($feature_id, 10)] = $feature_settings;
     190                unset($settings[$feature_id]);
     191            }
     192        }
     193
     194        // Update settings
     195        update_option('advanced_settings_settings', $settings);
     196    },
     197
    154198];
  • advanced-settings/tags/3.1.0/readme.txt

    r3308393 r3323103  
    88Requires at least: 5.0.0
    99Tested up to: 6.8
    10 Stable tag: 3.0.2
     10Stable tag: 3.1.0
    1111License: GPLv3 or later
    1212License URI: http://www.gnu.org/licenses/gpl-3.0.html
     
    2626[→ details in FAQ](https://wordpress.org/plugins/advanced-settings#how%20is%20the%20security%20of%20the%20plugin%20ensured%3F)
    2727
    28 **✳️ INFO ABOUT BAD REVIEWS**
    29 2 bad reviews occurred in 2017 because outdated PHP versions were used, but can't happen again.
     28**✳️ INFO ABOUT THE 2 BAD RATINGS**
     292 bad ratings occurred in 2017 because outdated PHP versions were used, but can't happen again.
    3030[→ details in FAQ](https://wordpress.org/plugins/advanced-settings#what%20caused%20the%20two%20bad%20ratings%3F)
    3131
     
    3737Advanced Settings 3 was developed to help as many users as possible. If you'd like to see a feature added to this plugin, please let us know. Don't worry, we'll keep the plugin fast and lean; this is a high priority for us. We'll only implement features that don't conflict with this.
    3838
    39 = Dashboard =
    40 
    41 * Customize dashboard logo
     39= Admin Area =
     40
     41* Hide the top admin bar for all users in the frontend
    4242* Hide WordPress update message in dashboard
    43 * Hide the top admin bar for all users in the frontend
    4443* Hide the welcome panel in the dashboard
     44* Hide the default widgets in the dashboard 💥 new
     45* Customize the admin area branding 💥 new
     46
     47= Frontend =
     48
     49* Remove PHP version from HTTP headers 💥 new
     50* Add security HTTP headers 💥 new
     51* Automatically add FavIcon (when favicon.ico, favicon.png or favicon.svg exists in template folder)
     52* Add Facebook Open Graph meta tags
     53* Remove shortlink meta tag
     54* Remove RSD (Weblog Client Link) meta tag
     55* Remove WordPress generator meta tag
     56* Automatically add description meta tag using blog description and post excerpt (SEO)
     57* Disable author pages
     58* Remove wptexturize filter
     59* Disable auto embed of external content 💥 new
     60* Limit excerpt length
     61* Add "Read more" link after excerpt
     62* Remove trackbacks and pingbacks from comment count
     63* Protect email addresses from spam bots
     64* Compress HTML code
     65* Remove HTML comments (except conditional IE comments)
     66* Disable emoji image replacement
     67
     68= Editing =
     69
     70* Disable posts auto saving
     71* Limit post revisions 💥 new
     72* Allow SVG uploads for admins 💥 new
     73* Downsize images on upload to max size
     74* Set JPEG quality
     75* Add thumbnail support
     76* Automatically generate post thumbnail (from first image in post)
    4577
    4678= System =
     
    4880* Hide default WordPress favicon
    4981* Disable comment system
    50 * Disable posts auto saving
     82* Disable XML-RPC 💥 new
     83* Disable public REST API 💥 new
    5184* Prevent installation of new default WordPress themes during core updates
    5285* Disable email notifications for core updates
    5386* Disable email notifications for plugin updates
    5487* Disable email notifications for theme updates
    55 
    56 = Frontend =
    57 
    58 * Disable author pages
    59 * Automatically add FavIcon (when favicon.ico, favicon.png or favicon.svg exists in template folder)
    60 * Automatically add description meta tag using blog description and post excerpt (SEO)
    61 * Remove WordPress generator meta tag
    62 * Remove RSD (Weblog Client Link) meta tag
    63 * Remove shortlink meta tag
    64 * Limit excerpt length
    65 * Add "Read more" link after excerpt
    66 * Remove wptexturize filter
    67 * Remove trackbacks and pingbacks from comment count
    68 * Compress HTML code
    69 * Remove HTML comments (except conditional IE comments)
    70 * Disable author pages
    71 * Add thumbnail support
    72 * Automatically generate post thumbnail (from first image in post)
    73 * Protect email addresses from spam bots
    74 * Add Facebook Open Graph meta tags
    75 * Disable emoji image replacement
    76 
    77 = Editing =
    78 
    79 * Set JPEG quality
    80 * Downsize images on upload to max size
    8188
    8289= Developer =
     
    131138
    132139**Detailed answer:**
    133 The plugin developer at the time had decided in 2017 to use PHP features that were introduced with PHP 5.4. Support for the previous version, PHP 5.3, had already been officially discontinued by The PHP Group 3 years earlier. It was therefore natural to assume that all websites actually in use had already been converted to PHP 5.4 or newer. Unfortunately, this was not the case for a few of them.
    134 
    135 Nowadays, you can specify in the plugin metadata which PHP version is required as a minimum for the plugin, so that users can only update the plugin if they are using the correct PHP version. While this WordPress feature came too late to prevent the problems and the negative reviews, this problem can fortunately be prevented in the future.
    136 
    137 It would be great if you could contribute with your own review to ensure that the ratings reflect the current state of the plugin.
     140The plugin developer at the time had decided in 2017 to use PHP features that were introduced with PHP 5.4. Support for the previous version, PHP 5.3, had already been officially discontinued by The PHP Group 3 years earlier. It was therefore natural to assume that all websites actually in use had already been converted to PHP 5.4 or newer. Unfortunately, this was not the case for a few users.
     141
     142Nowadays, WordPress allowes to specify in the plugin metadata which PHP version is required as a minimum for the plugin, so that users can only update the plugin if they are using the correct PHP version. While this WordPress feature came too late to prevent the problems and the negative reviews, this problem can fortunately be prevented in the future.
     143
     144It would be great if you could contribute with your own review to ensure that the ratings reflect the quality of the plugin.
    138145
    139146== Screenshots ==
     
    144151
    145152== Changelog ==
     153
     154= 3.1.0 - 2025-07-06 =
     155* Added 9 new features (see feature list)
     156* Added tags and tag navigation
    146157
    147158= 3.0.2 - 2025-06-04 =
  • advanced-settings/trunk/admin-ui/admin-ui.css

    r3281733 r3323103  
    125125    padding: 0;
    126126    max-width: 90%;
    127     width: 60em;
     127    width: 65em;
    128128    height: 80vh;
    129129    background-color: #fff;
  • advanced-settings/trunk/admin-ui/react/app.css

    r3281733 r3323103  
    1818.advset-category-sidebar {
    1919    min-width: 10em;
     20    max-width: 18em;
    2021    flex-shrink: 0;
    2122    background-color: var(--advset-color-base-95);
    2223    border-right: 1px solid var(--advset-color-border);
    2324    position: sticky;
     25    display: flex;
     26    flex-direction: column;
     27}
     28
     29/* Tab Navigation */
     30.advset-tab-navigation {
     31    display: flex;
     32    border-bottom: 1px solid var(--advset-color-border);
     33    background-color: var(--advset-color-base-97);
     34}
     35
     36.advset-tab-button {
     37    flex: 1;
     38    background: none;
     39    border: none;
     40    padding: .75em 1em;
     41    font-size: var(--advset-font-size-small);
     42    font-weight: 600;
     43    text-transform: uppercase;
     44    letter-spacing: .03em;
     45    color: var(--advset-color-base-70);
     46    cursor: pointer;
     47    transition: all .3s;
     48    border-bottom: 2px solid transparent;
     49}
     50
     51.advset-tab-button:hover:not(:disabled),
     52.advset-tab-button:focus-visible:not(:disabled) {
     53    color: var(--advset-color-base);
     54    background-color: var(--advset-color-base-90);
     55}
     56
     57.advset-tab-button.is-active {
     58    color: var(--advset-color-base);
     59    border-bottom-color: var(--advset-color-base);
     60    background-color: var(--advset-color-base-95);
     61    font-weight: 700;
     62}
     63
     64.advset-tab-button:disabled {
     65    opacity: 0.5;
     66    cursor: not-allowed;
     67}
     68
     69/* Tab Content */
     70.advset-tab-content {
     71    flex: 1;
     72    overflow-y: auto;
    2473}
    2574
     
    69118    font-weight: 600;
    70119    text-transform: uppercase;
     120}
     121
     122
     123
     124.advset-tags-list {
     125    display: flex;
     126    flex-wrap: wrap;
     127    gap: .5em;
     128    padding: 1em;
     129}
     130
     131.advset-tag {
     132    background: none;
     133    border: 1px solid var(--advset-color-base-70);
     134    border-radius: 1em;
     135    padding: .25em .75em;
     136    font-size: var(--advset-font-size-tiny);
     137    color: var(--advset-color-base-70);
     138    cursor: pointer;
     139    transition: all .3s;
     140    font-family: inherit;
     141    line-height: inherit;
     142}
     143
     144.advset-tag:hover,
     145.advset-tag:focus-visible {
     146    border-color: var(--advset-color-primary);
     147    color: var(--advset-color-primary);
     148    outline: var(--advset-color-primary) 1px solid;
     149}
     150
     151.advset-tag.is-active {
     152    background-color: var(--advset-color-primary);
     153    border-color: var(--advset-color-primary);
     154    color: var(--advset-color-background);
     155}
     156
     157.advset-tag.is-disabled {
     158    opacity: 0.5;
     159    cursor: not-allowed;
     160    color: var(--advset-color-base);
     161    border-color: var(--advset-color-base-70);
     162    background-color: var(--advset-color-base-70);
     163}
     164
     165.advset-tag.is-disabled:hover,
     166.advset-tag.is-disabled:focus-visible {
     167    border-color: var(--advset-color-base-50);
     168    color: var(--advset-color-base-50);
     169    outline: none;
    71170}
    72171
     
    260359}
    261360
     361
     362
     363/* ===== Item Tags ===== */
     364.advset-item-tags {
     365    display: flex;
     366    flex-wrap: wrap;
     367    justify-content: flex-end;
     368    gap: .5em;
     369    margin-top: .5em;
     370}
     371
     372.advset-item-tag {
     373    background: none;
     374    border: 1px solid var(--advset-color-base-70);
     375    border-radius: 1em;
     376    padding: .25em .75em;
     377    font-size: var(--advset-font-size-tiny);
     378    color: var(--advset-color-base-70);
     379    cursor: pointer;
     380    transition: all .3s;
     381    font-family: inherit;
     382    line-height: inherit;
     383}
     384
     385.advset-item-tag:hover,
     386.advset-item-tag:focus-visible {
     387    border-color: var(--advset-color-primary);
     388    color: var(--advset-color-primary);
     389    outline: var(--advset-color-primary) 1px solid;
     390}
     391
     392.advset-item-tag.is-active {
     393    background-color: var(--advset-color-primary);
     394    border-color: var(--advset-color-primary);
     395    color: var(--advset-color-background);
     396}
     397
     398
     399
  • advanced-settings/trunk/admin-ui/react/app.js

    r3286786 r3323103  
    1717 */
    1818function App(props) {
    19     const { items, categories, onSettingChange, onCategoryClick, settings, searchQuery, activeCategory } = props;
     19    const { items, categories, onSettingChange, onCategoryClick, onTagClick, onTabChange, settings, searchQuery, parsedSearchQuery, activeCategory, hiddenItems, activeTab } = props;
    2020   
    2121    // Group items by category
     
    3737    );
    3838
     39    // Extract all unique tags from all items (both visible and hidden)
     40    const allTags = new Set();
     41    const visibleTagCounts = new Map();
     42    const hiddenTagCounts = new Map();
     43   
     44    // Count tags from visible items
     45    items.forEach(item => {
     46        if (item.ui_config?.tags) {
     47            item.ui_config.tags.forEach(tag => {
     48                allTags.add(tag);
     49                visibleTagCounts.set(tag, (visibleTagCounts.get(tag) || 0) + 1);
     50            });
     51        }
     52    });
     53   
     54    // Count tags from hidden items
     55    hiddenItems.forEach(item => {
     56        if (item.ui_config?.tags) {
     57            item.ui_config.tags.forEach(tag => {
     58                allTags.add(tag);
     59                hiddenTagCounts.set(tag, (hiddenTagCounts.get(tag) || 0) + 1);
     60            });
     61        }
     62    });
     63   
     64    const sortedTags = Array.from(allTags).sort();
     65
    3966   
    4067    return React.createElement('div', { className: 'advset-react-app' },
     
    4269        React.createElement('div', { className: 'advset-notifications' }),
    4370       
    44         // Category sidebar
    45         visibleCategories.length > 0 && React.createElement('div', { className: 'advset-category-sidebar' },
    46             React.createElement('ul', { className: 'advset-category-menu' },
    47                 visibleCategoriesIncludingSeparator.map(category =>
    48                     React.createElement('li', {
    49                         key: category.id,
    50                         className: 'advset-category-menu-item'
    51                     },
    52                     category.title ?
    53                         React.createElement('a', {
    54                             href: `#category-${category.id}`,
    55                             onClick: (e) => {
    56                                 e.preventDefault();
    57                                 onCategoryClick(category.id);
    58                             },
    59                             className: activeCategory === category.id ? 'is-active' : ''
    60                         },
    61                             React.createElement('span', {
    62                                 className: 'advset-category-icon',
    63                                 dangerouslySetInnerHTML: { __html: category.icon || '' }
    64                             }),
    65                             React.createElement('span', {
    66                                 className: 'advset-category-text'
    67                             }, category.title || category.id)
    68                         ) : React.createElement('div', {
    69                             className: 'advset-category-separator',
    70                             style: {
    71                                 borderTop: '1px solid #ccc',
    72                             }
    73                         })
     71        // Sidebar with tab navigation
     72        React.createElement('div', { className: 'advset-category-sidebar' },
     73            // Tab navigation
     74            React.createElement('div', { className: 'advset-tab-navigation' },
     75                React.createElement('button', {
     76                    className: `advset-tab-button ${activeTab === 'categories' ? 'is-active' : ''}`,
     77                    onClick: () => onTabChange('categories'),
     78                    disabled: visibleCategories.length === 0
     79                }, 'Categories'),
     80                React.createElement('button', {
     81                    className: `advset-tab-button ${activeTab === 'tags' ? 'is-active' : ''}`,
     82                    onClick: () => onTabChange('tags'),
     83                    disabled: sortedTags.length === 0
     84                }, 'Tags')
     85            ),
     86           
     87            // Categories tab content
     88            activeTab === 'categories' && visibleCategories.length > 0 && React.createElement('div', { className: 'advset-tab-content' },
     89                React.createElement('ul', { className: 'advset-category-menu' },
     90                    visibleCategoriesIncludingSeparator.map(category =>
     91                        React.createElement('li', {
     92                            key: category.id,
     93                            className: 'advset-category-menu-item'
     94                        },
     95                        category.title ?
     96                            React.createElement('a', {
     97                                href: `#category-${category.id}`,
     98                                onClick: (e) => {
     99                                    e.preventDefault();
     100                                    onCategoryClick(category.id);
     101                                },
     102                                className: activeCategory === category.id ? 'is-active' : ''
     103                            },
     104                                React.createElement('span', {
     105                                    className: 'advset-category-icon',
     106                                    dangerouslySetInnerHTML: { __html: category.icon || '' }
     107                                }),
     108                                React.createElement('span', {
     109                                    className: 'advset-category-text'
     110                                }, category.title || category.id)
     111                            ) : React.createElement('div', {
     112                                className: 'advset-category-separator',
     113                                style: {
     114                                    borderTop: '1px solid #ccc',
     115                                }
     116                            })
     117                        )
    74118                    )
     119                )
     120            ),
     121           
     122            // Tags tab content
     123            activeTab === 'tags' && sortedTags.length > 0 && React.createElement('div', { className: 'advset-tab-content' },
     124                React.createElement('div', { className: 'advset-tags-list' },
     125                    sortedTags.map(tag => {
     126                        const isActive = parsedSearchQuery?.included?.tags?.includes(tag.toLowerCase());
     127                        const hasVisibleItems = visibleTagCounts.has(tag);
     128                        const hasHiddenItems = hiddenTagCounts.has(tag);
     129                       
     130                        let className = 'advset-tag';
     131                        if (isActive) className += ' is-active';
     132                        if (!hasVisibleItems && hasHiddenItems) className += ' is-disabled';
     133                       
     134                        return React.createElement('button', {
     135                            key: tag,
     136                            className: className,
     137                            onClick: () => onTagClick(tag),
     138                            disabled: !hasVisibleItems && hasHiddenItems
     139                        }, tag);
     140                    })
    75141                )
    76142            )
     
    102168                                item: item,
    103169                                onSettingChange: onSettingChange,
    104                                 settingValue: settings[item.id] || {}
     170                                onTagClick: onTagClick,
     171                                settingValue: settings[item.id] || {},
     172                                parsedSearchQuery: parsedSearchQuery
    105173                            })
    106174                        )
     
    138206 */
    139207function ItemCard(props) {
    140     const { item, onSettingChange, settingValue } = props;
     208    const { item, onSettingChange, onTagClick, settingValue, parsedSearchQuery } = props;
    141209   
    142210    // Get the component from the registry
     
    197265                config: item.ui_config || {}
    198266            })
     267        ),
     268        // Show tags if item has tags
     269        item.ui_config?.tags && item.ui_config.tags.length > 0 && React.createElement('div', {
     270            className: 'advset-item-tags'
     271        },
     272            item.ui_config.tags.map(tag =>
     273                React.createElement('button', {
     274                    key: tag,
     275                    className: `advset-item-tag ${parsedSearchQuery?.included?.tags?.includes(tag.toLowerCase()) ? 'is-active' : ''}`,
     276                    onClick: () => onTagClick(tag)
     277                }, tag)
     278            )
    199279        )
    200280    );
     
    212292        items: [],
    213293        allItems: [], // Cache for all items
     294        hiddenItems: [], // Items that are hidden by current filter
    214295        isLoading: false,
    215296        categories: [],
    216297        settings: {}, // Store for settings values
    217298        activeCategory: null, // Track active category for scrolling
    218         parsedSearchQuery: null // Cache for parsed search query
     299        parsedSearchQuery: null, // Cache for parsed search query
     300        activeTab: 'categories' // Track active tab: 'categories' or 'tags'
    219301    },
    220302
     
    304386    performLocalSearch(query) {
    305387        const parsedSearchQuery = query ? parseSearchQuery(query) : null;
     388        const { filteredItems, hiddenItems } = this.filterItems(this.state.allItems, parsedSearchQuery);
    306389       
    307390        this.setState({
    308391            searchQuery: query,
    309392            parsedSearchQuery,
    310             items: this.filterItems(this.state.allItems, parsedSearchQuery)
     393            items: filteredItems,
     394            hiddenItems: hiddenItems
    311395        });
    312396    },
     
    317401     * @param {Array} items - The items to filter
    318402     * @param {Object} parsedSearchQuery - The parsed search query
    319      * @returns {Array} - Filtered items
     403     * @returns {Object} - Object containing filtered and hidden items
    320404     */
    321405    filterItems(items, parsedSearchQuery) {
     
    323407        const showExperimental = this.state.settings['advset.features.show_experimental']?.enable;
    324408       
    325         return items.filter(item => {
    326             // Filter by feature flags
     409        const filteredItems = [];
     410        const hiddenItems = [];
     411       
     412        items.forEach(item => {
     413            // Check feature flags first
     414            let isHiddenByFlags = false;
    327415            if (item.deprecated && !showDeprecated && typeof this.state.settings[item.id] === 'undefined') {
    328                 return false;
    329             }
    330 
     416                isHiddenByFlags = true;
     417            }
    331418            if (item.experimental && !showExperimental && typeof this.state.settings[item.id] === 'undefined') {
    332                 return false;
    333             }
    334 
    335             // If no search query, include the item
     419                isHiddenByFlags = true;
     420            }
     421
     422            // If no search query, include the item based on flags only
    336423            if (!parsedSearchQuery) {
    337                 return true;
    338             }
    339 
    340             // Search in ui_config fields
     424                if (isHiddenByFlags) {
     425                    hiddenItems.push(item);
     426                } else {
     427                    filteredItems.push(item);
     428                }
     429                return;
     430            }
     431
     432            // Search in ui_config fields and tags
    341433            const searchTexts = [];
    342434           
     
    354446            }
    355447           
    356             const searchText = searchTexts.join(' ').toLowerCase();
     448            // Add tags to searchable text
     449            if (item.ui_config?.tags) {
     450                searchTexts.push(...item.ui_config.tags);
     451            }
     452           
     453            const searchText = searchTexts.join(Math.random()).toLowerCase();
    357454
    358455            // Check if item matches all required terms
    359             const matchesTerms = parsedSearchQuery.terms.every(term =>
     456            const matchesIncludedTerms = parsedSearchQuery.included.terms.length === 0 || parsedSearchQuery.included.terms.every(term =>
    360457                searchText.includes(term)
    361458            );
    362459
    363460            // Check if item doesn't contain any excluded terms
    364             const matchesExclusions = !parsedSearchQuery.exclusions.some(exclusion =>
     461            const matchesExcludedTerms = !parsedSearchQuery.excluded.terms.some(exclusion =>
    365462                searchText.includes(exclusion)
    366463            );
    367464
    368             // Item must match all terms and no exclusions
    369             return matchesTerms && matchesExclusions;
     465            // Check tag requirements (now using labels)
     466            const itemTags = (item.ui_config?.tags || []).map(tag => tag.toLowerCase());
     467           
     468            // Item must have ALL required tags
     469            const matchesIncludedTags = parsedSearchQuery.included.tags.length === 0 || parsedSearchQuery.included.tags.every(tag =>
     470                itemTags.includes(tag)
     471            );
     472           
     473            // Item must NOT have ANY excluded tags
     474            const matchesExcludedTags = !parsedSearchQuery.excluded.tags.some(tag =>
     475                itemTags.includes(tag)
     476            );
     477
     478            // Item must match all terms, no exclusions, required tags, and no excluded tags
     479            const matchesSearch = matchesIncludedTerms && matchesExcludedTerms && matchesIncludedTags && matchesExcludedTags;
     480
     481            if (isHiddenByFlags || !matchesSearch) {
     482                hiddenItems.push(item);
     483            } else {
     484                filteredItems.push(item);
     485            }
    370486        });
     487       
     488        return { filteredItems, hiddenItems };
    371489    },
    372490
     
    399517
    400518            // Apply initial filtering
     519            const { filteredItems, hiddenItems } = this.filterItems(data.features);
    401520            this.setState({
    402                 items: this.filterItems(data.features),
     521                items: filteredItems,
     522                hiddenItems: hiddenItems
    403523            });
    404524           
     
    433553    },
    434554
     555
     556
     557    /**
     558     * Handle tab change
     559     *
     560     * @param {string} tab - The tab to switch to
     561     */
     562    handleTabChange(tab) {
     563        this.setState({ activeTab: tab });
     564    },
     565
     566    /**
     567     * Handle tag click
     568     *
     569     * @param {string} tag - The tag that was clicked
     570     */
     571    handleTagClick(tag) {
     572        const searchInput = document.querySelector('.advset-modal-search input');
     573        if (!searchInput) return;
     574
     575        let currentQuery = searchInput.value.trim();
     576       
     577        // Handle tags with spaces by adding quotes
     578        const tagPrefix = tag.includes(' ') ? `tag:"${tag}"` : `tag:${tag}`;
     579       
     580        // Check if tag is already in query (handle both quoted and unquoted versions)
     581        const tagRegex = new RegExp(`\\btag:("${tag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"|${tag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})\\b`, 'g');
     582        const hasTag = tagRegex.test(currentQuery);
     583       
     584        if (hasTag) {
     585            // Remove tag from query
     586            currentQuery = currentQuery.replace(tagRegex, '').replace(/\s+/g, ' ').trim();
     587        } else {
     588            // Add tag to query
     589            currentQuery = currentQuery ? `${currentQuery} ${tagPrefix}` : tagPrefix;
     590        }
     591       
     592        // Update search input
     593        searchInput.value = currentQuery;
     594       
     595        // Trigger search
     596        document.dispatchEvent(new CustomEvent('advset-search', {
     597            detail: { query: currentQuery }
     598        }));
     599    },
     600
    435601    /**
    436602     * Render the application
    437603     */
    438604    render() {
    439         const { searchQuery, items, isLoading, categories } = this.state;
     605        const { searchQuery, parsedSearchQuery, items, hiddenItems, isLoading, categories, activeTab } = this.state;
    440606       
    441607        // Render the React app
     
    446612                onSettingChange: this.handleSettingChange.bind(this),
    447613                onCategoryClick: this.scrollToCategory.bind(this),
     614                onTagClick: this.handleTagClick.bind(this),
     615                onTabChange: this.handleTabChange.bind(this),
    448616                settings: this.state.settings,
    449617                searchQuery: searchQuery,
    450                 activeCategory: this.state.activeCategory
     618                parsedSearchQuery: parsedSearchQuery,
     619                activeCategory: this.state.activeCategory,
     620                hiddenItems: hiddenItems,
     621                activeTab: activeTab
    451622            });
    452623           
     
    588759function parseSearchQuery(query) {
    589760    const result = {
    590         terms: [],
    591         exclusions: []
     761        excluded: {
     762            tags: [],
     763            terms: [],
     764        },
     765        included: {
     766            tags: [],
     767            terms: [],
     768        },
    592769    };
    593770
     
    596773
    597774    // Extract terms and phrases (with optional minus)
    598     const matches = query.match(/-?"[^"]+"|-[^\s]+|[^\s]+/g) || [];
    599    
    600     matches.forEach(match => {
    601         if (match.startsWith('-')) {
    602             // Handle exclusions
    603             const exclusion = match.startsWith('-"')
    604                 ? match.slice(2, -1) // Remove -" and "
    605                 : match.slice(1);    // Remove -
    606             if (exclusion) {
    607                 result.exclusions.push(exclusion.toLowerCase());
    608             }
    609         } else {
    610             // Handle regular terms
    611             const term = match.startsWith('"')
    612                 ? match.slice(1, -1) // Remove quotes
    613                 : match;
    614             if (term) {
    615                 result.terms.push(term.toLowerCase());
    616             }
    617         }
     775    query.matchAll(/(\-)?(tag:)?(?:("[^"]+")|([^"\s]+))/g).forEach(match => {
     776        const isExclusion = match[1] === '-';
     777        const isTag = match[2] === 'tag:';
     778        const term = (match[3] ? match[3].slice(1, -1) : match[4]).toLowerCase();
     779
     780        const targetList = result[isExclusion ? 'excluded' : 'included'][isTag ? 'tags' : 'terms'];
     781        targetList.push(term);
    618782    });
    619783
  • advanced-settings/trunk/advanced-settings.php

    r3306579 r3323103  
    66Author: Helmut Wandl
    77Author URI: https://ehtmlu.com/
    8 Version: 3.0.2
     8Version: 3.1.0
    99Requires at least: 5.0.0
    1010Requires PHP: 7.4
  • advanced-settings/trunk/feature-setup/categories.php

    r3281733 r3323103  
    1313
    1414    advset_register_category([
    15         'id' => 'dashboard',
     15        'id' => 'adminarea',
    1616        'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>gauge</title><path d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12C20,14.4 19,16.5 17.3,18C15.9,16.7 14,16 12,16C10,16 8.2,16.7 6.7,18C5,16.5 4,14.4 4,12A8,8 0 0,1 12,4M14,5.89C13.62,5.9 13.26,6.15 13.1,6.54L11.81,9.77L11.71,10C11,10.13 10.41,10.6 10.14,11.26C9.73,12.29 10.23,13.45 11.26,13.86C12.29,14.27 13.45,13.77 13.86,12.74C14.12,12.08 14,11.32 13.57,10.76L13.67,10.5L14.96,7.29L14.97,7.26C15.17,6.75 14.92,6.17 14.41,5.96C14.28,5.91 14.15,5.89 14,5.89M10,6A1,1 0 0,0 9,7A1,1 0 0,0 10,8A1,1 0 0,0 11,7A1,1 0 0,0 10,6M7,9A1,1 0 0,0 6,10A1,1 0 0,0 7,11A1,1 0 0,0 8,10A1,1 0 0,0 7,9M17,9A1,1 0 0,0 16,10A1,1 0 0,0 17,11A1,1 0 0,0 18,10A1,1 0 0,0 17,9Z" /></svg>',
    17         'title' => __('Dashboard', 'advanced-settings'),
     17        'title' => __('Admin Area', 'advanced-settings'),
    1818        'priority' => 10,
     19    ]);
     20
     21    advset_register_category([
     22        'id' => 'frontend',
     23        'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>monitor</title><path d="M21,16H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10V20H8V22H16V20H14V18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2Z" /></svg>',
     24        'title' => __('Frontend', 'advanced-settings'),
     25        'priority' => 20,
     26    ]);
     27
     28    advset_register_category([
     29        'id' => 'editing',
     30        'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>pencil</title><path d="M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z" /></svg>',
     31        'title' => __('Editing', 'advanced-settings'),
     32        'priority' => 30,
    1933    ]);
    2034
     
    2337        'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>application-cog-outline</title><path d="M21.7 18.6V17.6L22.8 16.8C22.9 16.7 23 16.6 22.9 16.5L21.9 14.8C21.9 14.7 21.7 14.7 21.6 14.7L20.4 15.2C20.1 15 19.8 14.8 19.5 14.7L19.3 13.4C19.3 13.3 19.2 13.2 19.1 13.2H17.1C16.9 13.2 16.8 13.3 16.8 13.4L16.6 14.7C16.3 14.9 16.1 15 15.8 15.2L14.6 14.7C14.5 14.7 14.4 14.7 14.3 14.8L13.3 16.5C13.3 16.6 13.3 16.7 13.4 16.8L14.5 17.6V18.6L13.4 19.4C13.3 19.5 13.2 19.6 13.3 19.7L14.3 21.4C14.4 21.5 14.5 21.5 14.6 21.5L15.8 21C16 21.2 16.3 21.4 16.6 21.5L16.8 22.8C16.9 22.9 17 23 17.1 23H19.1C19.2 23 19.3 22.9 19.3 22.8L19.5 21.5C19.8 21.3 20 21.2 20.3 21L21.5 21.4C21.6 21.4 21.7 21.4 21.8 21.3L22.8 19.6C22.9 19.5 22.9 19.4 22.8 19.4L21.7 18.6M18 19.5C17.2 19.5 16.5 18.8 16.5 18S17.2 16.5 18 16.5 19.5 17.2 19.5 18 18.8 19.5 18 19.5M12.3 22H3C1.9 22 1 21.1 1 20V4C1 2.9 1.9 2 3 2H21C22.1 2 23 2.9 23 4V13.1C22.4 12.5 21.7 12 21 11.7V6H3V20H11.3C11.5 20.7 11.8 21.4 12.3 22Z" /></svg>',
    2438        'title' => __('System', 'advanced-settings'),
    25         'priority' => 20,
    26     ]);
    27 
    28     advset_register_category([
    29         'id' => 'advset',
    30         'icon' => file_get_contents(ADVSET_DIR . '/admin-ui/images/admin-bar-icon.svg'),
    31         'title' => __('About & Options', 'advanced-settings'),
    32         'priority' => 100,
    33     ]);
    34 
    35     advset_register_category([
    36         'id' => 'advset-separator',
    37         'priority' => 99,
    38     ]);
    39 
    40     advset_register_category([
    41         'id' => 'frontend',
    42         'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>monitor</title><path d="M21,16H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10V20H8V22H16V20H14V18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2Z" /></svg>',
    43         'title' => __('Frontend', 'advanced-settings'),
    44         'priority' => 30,
    45     ]);
    46 
    47     advset_register_category([
    48         'id' => 'editing',
    49         'icon' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>pencil</title><path d="M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z" /></svg>',
    50         'title' => __('Editing', 'advanced-settings'),
    5139        'priority' => 40,
    5240    ]);
     
    5846        'priority' => 50,
    5947    ]);
     48
     49    advset_register_category([
     50        'id' => 'advset-separator',
     51        'priority' => 99,
     52    ]);
     53
     54    advset_register_category([
     55        'id' => 'advset',
     56        'icon' => file_get_contents(ADVSET_DIR . '/admin-ui/images/admin-bar-icon.svg'),
     57        'title' => __('About & Options', 'advanced-settings'),
     58        'priority' => 100,
     59    ]);
    6060});
  • advanced-settings/trunk/feature-setup/features/advset.php

    r3281733 r3323103  
    6565                ],
    6666                'default' => '',
     67                'description' => __('By default, we do not track plugin usage. If you agree to share your plugin usage with the developers, we will collect anonymous data about your usage. This data will be used to improve the plugin and to provide you with a better experience.', 'advanced-settings'),
    6768            ],
    6869        ]
  • advanced-settings/trunk/feature-setup/features/developer.php

    r3306579 r3323103  
    1515    'category' => 'developer',
    1616    'ui_config' => fn() => [
     17        'tags' => [
     18            __('Developer', 'advanced-settings'),
     19            __('Debug', 'advanced-settings'),
     20            __('Performance', 'advanced-settings'),
     21        ],
    1722        'fields' => [
    1823            'enable' => [
     
    4449    'experimental' => true,
    4550    'ui_config' => fn() => [
     51        'tags' => [
     52            __('Developer', 'advanced-settings'),
     53            __('Frontend', 'advanced-settings'),
     54            __('Performance', 'advanced-settings'),
     55        ],
    4656        'fields' => [
    4757            'enable' => [
     
    8393    'experimental' => true,
    8494    'ui_config' => fn() => [
     95        'tags' => [
     96            __('Developer', 'advanced-settings'),
     97            __('Frontend', 'advanced-settings'),
     98            __('Performance', 'advanced-settings'),
     99        ],
    85100        'fields' => [
    86101            'enable' => [
     
    122137    'experimental' => true,
    123138    'ui_config' => fn() => [
     139        'tags' => [
     140            __('Developer', 'advanced-settings'),
     141            __('Admin', 'advanced-settings'),
     142            __('Posts', 'advanced-settings'),
     143        ],
    124144        'fields' => [
    125145            'enable' => [
     
    221241    'experimental' => true,
    222242    'ui_config' => fn() => [
     243        'tags' => [
     244            __('Developer', 'advanced-settings'),
     245            __('Admin', 'advanced-settings'),
     246            __('Frontend', 'advanced-settings'),
     247            __('Performance', 'advanced-settings'),
     248        ],
    223249        'fields' => [
    224250            'enable' => [
  • advanced-settings/trunk/feature-setup/features/editing.php

    r3281733 r3323103  
    1212
    1313advset_register_feature([
     14    'id' => 'editing.posts.disable_autosave',
     15    'category' => 'editing',
     16    'ui_config' => fn() => [
     17        'tags' => [
     18            __('Editing', 'advanced-settings'),
     19            __('Posts', 'advanced-settings'),
     20            __('Performance', 'advanced-settings'),
     21            __('Database', 'advanced-settings'),
     22            __('Admin', 'advanced-settings'),
     23        ],
     24        'fields' => [
     25            'enable' => [
     26                'type' => 'toggle',
     27                'label' => __('Disable auto save', 'advanced-settings'),
     28            ],
     29        ]
     30    ],
     31    'execution_handler' => function() {
     32        define('AUTOSAVE_INTERVAL', 60 * 60 * 24 * 365 * 100); // save interval => 100 years
     33    },
     34    'priority' => 10,
     35]);
     36
     37
     38
     39advset_register_feature([
     40    'id' => 'editing.posts.limit_revisions',
     41    'category' => 'editing',
     42    'ui_config' => fn() => [
     43        'tags' => [
     44            __('Editing', 'advanced-settings'),
     45            __('Posts', 'advanced-settings'),
     46            __('Performance', 'advanced-settings'),
     47            __('Database', 'advanced-settings'),
     48            __('Admin', 'advanced-settings'),
     49        ],
     50        'fields' => [
     51            'enable' => [
     52                'type' => 'toggle',
     53                'label' => __('Limit post revisions', 'advanced-settings'),
     54                'description' => __('Reduce database size by limiting the number of saved revisions', 'advanced-settings'),
     55            ],
     56            'limit' => [
     57                'type' => 'number',
     58                'label' => __('Revisions to keep', 'advanced-settings'),
     59                'description' => __('0 means revisions are disabled.', 'advanced-settings'),
     60                'min' => 0,
     61                'default' => 5,
     62                'visible' => ['enable' => true],
     63            ],
     64            'type' => [
     65                'type' => 'radio',
     66                'label' => __('Post types', 'advanced-settings'),
     67                'description' => __('Select the post types that should be affected by the revision limit.', 'advanced-settings'),
     68                'options' => [
     69                    'post' => ['label' => 'Posts only'],
     70                    'page' => ['label' => 'Pages only'],
     71                    'post_and_page' => ['label' => 'Posts and Pages'],
     72                    'all' => ['label' => 'All post types (includes custom post types)'],
     73                ],
     74                'default' => 'post',
     75                'visible' => ['enable' => true],
     76            ],
     77        ]
     78    ],
     79    'handler_cleanup' => function($settings) {
     80        return empty($settings['enable']) ? null : $settings;
     81    },
     82    'execution_handler' => function($settings) {
     83        if ($settings['type'] === 'all') {
     84            add_filter('wp_revisions_to_keep', function($num) use($settings) {
     85                return $settings['limit'];
     86            });
     87            return;
     88        }
     89        foreach (explode('_and_', $settings['type']) as $type) {
     90            add_filter('wp_' . $type . '_revisions_to_keep', function($num) use($settings) {
     91                return $settings['limit'];
     92            });
     93        }
     94    },
     95    'priority' => 20,
     96]);
     97
     98
     99
     100advset_register_feature([
     101    'id' => 'editing.media.enable_svg',
     102    'category' => 'editing',
     103    'ui_config' => fn() => [
     104        'tags' => [
     105            __('Editing', 'advanced-settings'),
     106            __('Media', 'advanced-settings'),
     107            __('Images', 'advanced-settings'),
     108            __('Frontend', 'advanced-settings'),
     109            __('Performance', 'advanced-settings'),
     110        ],
     111        'fields' => [
     112            'enable' => [
     113                'type' => 'toggle',
     114                'label' => __('Allow SVG upload for admins', 'advanced-settings'),
     115                'description' => __('To keep the plugin lightweight, the SVG security checks in this feature are very limited. Therefore, this feature is only available to administrators.', 'advanced-settings'),
     116            ],
     117        ]
     118    ],
     119    'execution_handler' => function() {
     120        if (!current_user_can('administrator')) {
     121            return;
     122        }
     123       
     124        add_filter('upload_mimes', function($mimes) {
     125            $mimes['svg'] = 'image/svg+xml';
     126            $mimes['svgz'] = 'image/svg+xml';
     127            return $mimes;
     128        });
     129       
     130        add_filter('wp_handle_upload_prefilter', function($file) {
     131            if ($file['type'] !== 'image/svg+xml') {
     132                return $file;
     133            }
     134           
     135            $file_content = file_get_contents($file['tmp_name']);
     136
     137            // 1. Check if the file is empty
     138            if (empty($file_content)) {
     139                $file['error'] = __('Empty SVG file', 'advanced-settings');
     140                return $file;
     141            }
     142
     143            // 2. Remove XML declaration and Doctype for better compatibility
     144            $clean_content = preg_replace('/<\?xml\b[^>]*>\s*/i', '', $file_content);
     145            $clean_content = preg_replace('/<!DOCTYPE[^>[]*(\[[^]]*\])?[^>]*>\s*/is', '', $clean_content);
     146           
     147            // 3. XXE-Protection (critical!)
     148            $entity_loader_state = libxml_disable_entity_loader(true);
     149            libxml_use_internal_errors(true);
     150            libxml_clear_errors();
     151           
     152            $svg = @simplexml_load_string($clean_content);
     153           
     154            if ($svg === false) {
     155                $errors = libxml_get_errors();
     156                $first_error = !empty($errors[0]) ? $errors[0]->message : __('Unknown error', 'advanced-settings');
     157                libxml_clear_errors();
     158                /* translators: %s is the first error message from libxml */
     159                $file['error'] = sprintf(__('Invalid SVG: %s', 'advanced-settings'), esc_html($first_error));
     160                return $file;
     161            }
     162
     163            // 4. Simple, but effective security checks
     164            $unsafe_patterns = [
     165                '/<script/i',
     166                '/\bon\w+\s*=/i',
     167                '/javascript:\s*[a-z]+/i',
     168                '/<!ENTITY/i',
     169                '/<object/i',
     170                '/<iframe/i',
     171                '/<embed/i',
     172                '/href\s*=\s*["\']\s*javascript:/i',
     173                '/xlink:href\s*=\s*["\']\s*javascript:/i',
     174                '/style\s*=\s*["\'][^"]*expression\s*\(/i',
     175                '/style\s*=\s*["\'][^"]*url\s*\(\s*javascript:/i',
     176                '/<!--\[if[^\]]*?\]>.*?<!\[endif\]-->/is',
     177            ];
     178           
     179            foreach ($unsafe_patterns as $pattern) {
     180                if (preg_match($pattern, $file_content)) {
     181                    $file['error'] = __('SVG security check failed: Potential dangerous element detected.', 'advanced-settings') . ' ' . $pattern;
     182                    return $file;
     183                }
     184            }
     185   
     186            // 5. Additional protection against Base64-Encoded malicious code
     187            if (preg_match('/base64\s*[,;]/i', $file_content)) {
     188                $file['error'] = __('SVG security check failed: Base64 encoding not allowed.', 'advanced-settings');
     189                return $file;
     190            }
     191
     192            // Reset libxml state
     193            libxml_disable_entity_loader($entity_loader_state);
     194            libxml_clear_errors();
     195
     196            return $file;
     197        });
     198    },
     199    'priority' => 30,
     200]);
     201
     202
     203
     204advset_register_feature([
     205    'id' => 'editing.image.downsize_on_upload',
     206    'category' => 'editing',
     207    'ui_config' => fn() => [
     208        'tags' => [
     209            __('Editing', 'advanced-settings'),
     210            __('Media', 'advanced-settings'),
     211            __('Images', 'advanced-settings'),
     212            __('Frontend', 'advanced-settings'),
     213            __('Performance', 'advanced-settings'),
     214        ],
     215        'fields' => [
     216            'enable' => [
     217                'type' => 'toggle',
     218                'label' => __('Downsize images on upload', 'advanced-settings'),
     219            ],
     220            'max_width' => [
     221                'type' => 'number',
     222                'label' => __('Max width', 'advanced-settings'),
     223                'description' => __('Empty or 0 means no limit.', 'advanced-settings'),
     224                'min' => 0,
     225                'visible' => ['enable' => true],
     226            ],
     227            'max_height' => [
     228                'type' => 'number',
     229                'label' => __('Max height', 'advanced-settings'),
     230                'description' => __('Empty or 0 means no limit.', 'advanced-settings'),
     231                'min' => 0,
     232                'visible' => ['enable' => true],
     233            ],
     234        ]
     235    ],
     236    'handler_cleanup' => function($settings) {
     237        return empty($settings['enable']) ? null : $settings;
     238    },
     239    'execution_handler' => function($settings) {
     240        add_action('wp_handle_upload', function($upload) use($settings) {
     241            $file_path = $upload['file'];
     242            $image_info = getimagesize($file_path);
     243       
     244            if (!$image_info) return $upload;
     245       
     246            list($width, $height, $type) = $image_info;
     247       
     248            $max_width  = ((int) $settings['max_width']) ?? null;
     249            $max_height = ((int) $settings['max_height']) ?? null;
     250       
     251            if (($max_width === null || $width <= $max_width) && ($max_height === null || $height <= $max_height)) {
     252                return $upload; // nothig to do
     253            }
     254       
     255            $editor = wp_get_image_editor($file_path);
     256            if (is_wp_error($editor)) return $upload;
     257       
     258            $editor->resize($max_width, $max_height, false);
     259            $editor->save($file_path); // overwrites original
     260
     261            return $upload;
     262        });
     263    },
     264    'priority' => 40,
     265]);
     266
     267
     268
     269advset_register_feature([
    14270    'id' => 'editing.image.jpeg_quality',
    15271    'category' => 'editing',
    16272    'ui_config' => fn() => [
     273        'tags' => [
     274            __('Editing', 'advanced-settings'),
     275            __('Media', 'advanced-settings'),
     276            __('Images', 'advanced-settings'),
     277            __('Frontend', 'advanced-settings'),
     278            __('Performance', 'advanced-settings'),
     279        ],
    17280        'fields' => [
    18281            'jpeg_quality' => [
     
    33296        });
    34297    },
    35     'priority' => 20,
    36 ]);
    37 
    38 
    39 
    40 advset_register_feature([
    41     'id' => 'editing.image.downsize_on_upload',
    42     'category' => 'editing',
    43     'ui_config' => fn() => [
    44         'fields' => [
    45             'enable' => [
    46                 'type' => 'toggle',
    47                 'label' => __('Downsize images on upload', 'advanced-settings'),
    48             ],
    49             'max_width' => [
    50                 'type' => 'number',
    51                 'label' => __('Max width', 'advanced-settings'),
    52                 'description' => __('Empty or 0 means no limit.', 'advanced-settings'),
    53                 'min' => 0,
    54                 'visible' => ['enable' => true],
    55             ],
    56             'max_height' => [
    57                 'type' => 'number',
    58                 'label' => __('Max height', 'advanced-settings'),
    59                 'description' => __('Empty or 0 means no limit.', 'advanced-settings'),
    60                 'min' => 0,
    61                 'visible' => ['enable' => true],
    62             ],
    63         ]
    64     ],
    65     'handler_cleanup' => function($settings) {
    66         return empty($settings['enable']) ? null : $settings;
    67     },
    68     'execution_handler' => function($settings) {
    69         add_action('wp_handle_upload', function($upload) use($settings) {
    70             $file_path = $upload['file'];
    71             $image_info = getimagesize($file_path);
    72        
    73             if (!$image_info) return $upload;
    74        
    75             list($width, $height, $type) = $image_info;
    76        
    77             $max_width  = ((int) $settings['max_width']) ?? null;
    78             $max_height = ((int) $settings['max_height']) ?? null;
    79        
    80             if (($max_width === null || $width <= $max_width) && ($max_height === null || $height <= $max_height)) {
    81                 return $upload; // nothig to do
    82             }
    83        
    84             $editor = wp_get_image_editor($file_path);
    85             if (is_wp_error($editor)) return $upload;
    86        
    87             $editor->resize($max_width, $max_height, false);
    88             $editor->save($file_path); // overwrites original
    89 
    90             return $upload;
    91         });
    92     },
    93     'priority' => 20,
    94 ]);
    95 
    96 
     298    'priority' => 50,
     299]);
     300
     301
     302
     303advset_register_feature([
     304    'id' => 'editing.thumbnails.enable_support',
     305    'category' => 'editing',
     306    'ui_config' => fn() => [
     307        'tags' => [
     308            __('Editing', 'advanced-settings'),
     309            __('Frontend', 'advanced-settings'),
     310            __('Content', 'advanced-settings'),
     311            __('Images', 'advanced-settings'),
     312            __('Media', 'advanced-settings'),
     313        ],
     314        'fields' => [
     315            'enable' => [
     316                'type' => 'toggle',
     317                'label' => __('Add thumbnail support', 'advanced-settings'),
     318                'disabled' => !ADVSET_THUMBS,
     319                'description' => ADVSET_THUMBS
     320                    ? ''
     321                    : __('Already supported by current theme', 'advanced-settings'),
     322            ],
     323        ]
     324    ],
     325    'execution_handler' => function() {
     326        add_action('after_setup_theme', function (){
     327            add_theme_support( 'post-thumbnails' );
     328        });
     329    },
     330    'priority' => 60,
     331]);
     332define( 'ADVSET_THUMBS', !current_theme_supports('post-thumbnails') );
     333
     334
     335
     336advset_register_feature([
     337    'id' => 'editing.thumbnails.auto_from_first_image',
     338    'category' => 'editing',
     339    'ui_config' => fn() => [
     340        'tags' => [
     341            __('Editing', 'advanced-settings'),
     342            __('Frontend', 'advanced-settings'),
     343            __('Content', 'advanced-settings'),
     344            __('Images', 'advanced-settings'),
     345            __('Media', 'advanced-settings'),
     346            __('Automations', 'advanced-settings'),
     347        ],
     348        'fields' => [
     349            'enable' => [
     350                'type' => 'toggle',
     351                'label' => __('Automatically generate the Post Thumbnail', 'advanced-settings'),
     352                'description' => __('from the first image in post', 'advanced-settings'),
     353            ],
     354        ]
     355    ],
     356    'execution_handler' => function() {
     357        require_once ADVSET_DIR . '/feature-setup/features/includes/frontend.auto_thumbs.php';
     358        add_action('transition_post_status', 'advset__feature__auto_thumbs', 10, 3);
     359    },
     360    'priority' => 70,
     361]);
     362
     363
  • advanced-settings/trunk/feature-setup/features/frontend.php

    r3281733 r3323103  
    1212
    1313advset_register_feature([
    14     'id' => 'frontend.meta.facebook_og_metas',
    15     'category' => 'frontend',
    16     'ui_config' => fn() => [
    17         'fields' => [
    18             'enable' => [
    19                 'type' => 'toggle',
    20                 'label' => __('Enable Facebook Open Graph Meta Tags', 'advanced-settings'),
     14    'id' => 'frontend.http_headers.remove_php_version',
     15    'category' => 'frontend',
     16    'ui_config' => fn() => [
     17        'tags' => [
     18            __('Frontend', 'advanced-settings'),
     19            __('HTTP', 'advanced-settings'),
     20            __('Security', 'advanced-settings'),
     21            __('Cleanup', 'advanced-settings'),
     22        ],
     23        'fields' => [
     24            'enable' => [
     25                'type' => 'toggle',
     26                'label' => __('Remove PHP version from HTTP headers', 'advanced-settings'),
     27                'description' => __('Removes the PHP version from the HTTP headers of the website', 'advanced-settings'),
     28            ],
     29        ]
     30    ],
     31    'execution_handler' => function() {
     32        header_remove('X-Powered-By');
     33    },
     34    'priority' => 10,
     35]);
     36
     37
     38
     39advset_register_feature([
     40    'id' => 'frontend.http_headers.add_security_headers',
     41    'category' => 'frontend',
     42    'ui_config' => fn() => [
     43        'tags' => [
     44            __('Frontend', 'advanced-settings'),
     45            __('HTTP', 'advanced-settings'),
     46            __('Security', 'advanced-settings'),
     47        ],
     48        'fields' => [
     49            'enable' => [
     50                'type' => 'toggle',
     51                'label' => __('Add security headers', 'advanced-settings'),
     52                /* translators: %s is a link to securityheaders.com */
     53                'descriptionHtml' => sprintf(__('Adds various security headers to HTTP responses. You can check your website\'s security headers with %s.', 'advanced-settings'), '<a href="https://securityheaders.com/?q=' . rawurlencode(get_home_url()) . '&hide=on&followRedirects=on" target="_blank">securityheaders.com</a>'),
     54            ],
     55            'info' => [
     56                'type' => 'info',
     57                'label' => __('Caution:', 'advanced-settings'),
     58                'description' => __('If you integrate external services into your website or your website requires access to browser features like camera, microphone, geolocation, or other device sensors, the security headers may be set too strictly. In this case, simply disable this feature. You can implement customized headers via code if necessary.', 'advanced-settings'),
     59                'visible' => ['enable' => true]
    2160            ],
    2261        ]
     
    2463    'execution_handler' => function() {
    2564        if ( advset_is_admin_area() ) return;
    26 
    27         add_action('wp_head', function() {
    28             global $post;
    29             if (is_single() || is_page()) { ?>
    30                 <meta property="og:title" content="<?php single_post_title(''); ?>" />
    31                 <meta property="og:description" content="<?php echo strip_tags(get_the_excerpt($post->ID)); ?>" />
    32                 <meta property="og:type" content="article" />
    33                 <meta property="og:image" content="<?php if (function_exists('wp_get_attachment_thumb_url')) {echo wp_get_attachment_url(get_post_thumbnail_id($post->ID)); }?>" />
    34             <?php }
    35         });
    36     },
    37     'priority' => 10,
     65        add_action('send_headers', function() {
     66            header('X-Content-Type-Options: nosniff');
     67            header('X-Frame-Options: SAMEORIGIN');
     68            header('X-XSS-Protection: 1; mode=block');
     69            header('Referrer-Policy: strict-origin-when-cross-origin');
     70            header('Content-Security-Policy: object-src \'none\';');
     71            header('Strict-Transport-Security: max-age=31536000; includeSubDomains; preload');
     72            header('Permissions-Policy: accelerometer=(),camera=(),geolocation=(),gyroscope=(),magnetometer=(),microphone=(),payment=(),usb=(),interest-cohort=()');
     73        });
     74    },
     75    'priority' => 20,
    3876]);
    3977
     
    4482    'category' => 'frontend',
    4583    'ui_config' => fn() => [
     84        'tags' => [
     85            __('Frontend', 'advanced-settings'),
     86            __('Meta', 'advanced-settings'),
     87            __('Favicon', 'advanced-settings'),
     88            __('SEO', 'advanced-settings'),
     89        ],
    4690        'fields' => [
    4791            'enable' => [
     
    66110        });
    67111    },
    68     'priority' => 10,
     112    'priority' => 30,
     113]);
     114
     115
     116
     117advset_register_feature([
     118    'id' => 'frontend.meta.facebook_og_metas',
     119    'category' => 'frontend',
     120    'ui_config' => fn() => [
     121        'tags' => [
     122            __('Frontend', 'advanced-settings'),
     123            __('Meta', 'advanced-settings'),
     124            __('SEO', 'advanced-settings'),
     125        ],
     126        'fields' => [
     127            'enable' => [
     128                'type' => 'toggle',
     129                'label' => __('Enable Facebook Open Graph Meta Tags', 'advanced-settings'),
     130            ],
     131        ]
     132    ],
     133    'execution_handler' => function() {
     134        if ( advset_is_admin_area() ) return;
     135
     136        add_action('wp_head', function() {
     137            global $post;
     138            if (is_single() || is_page()) { ?>
     139                <meta property="og:title" content="<?php single_post_title(''); ?>" />
     140                <meta property="og:description" content="<?php echo strip_tags(get_the_excerpt($post->ID)); ?>" />
     141                <meta property="og:type" content="article" />
     142                <meta property="og:image" content="<?php if (function_exists('wp_get_attachment_thumb_url')) {echo wp_get_attachment_url(get_post_thumbnail_id($post->ID)); }?>" />
     143            <?php }
     144        });
     145    },
     146    'priority' => 40,
    69147]);
    70148
     
    75153    'category' => 'frontend',
    76154    'ui_config' => fn() => [
     155        'tags' => [
     156            __('Frontend', 'advanced-settings'),
     157            __('Meta', 'advanced-settings'),
     158            __('Cleanup', 'advanced-settings'),
     159        ],
    77160        'fields' => [
    78161            'enable' => [
     
    86169        remove_action( 'wp_head', 'wp_shortlink_wp_head');
    87170    },
    88     'priority' => 10,
     171    'priority' => 50,
    89172]);
    90173
     
    95178    'category' => 'frontend',
    96179    'ui_config' => fn() => [
     180        'tags' => [
     181            __('Frontend', 'advanced-settings'),
     182            __('Meta', 'advanced-settings'),
     183            __('Cleanup', 'advanced-settings'),
     184        ],
    97185        'fields' => [
    98186            'enable' => [
     
    107195        remove_action( 'wp_head', 'rsd_link');
    108196    },
    109     'priority' => 10,
     197    'priority' => 60,
    110198]);
    111199
     
    116204    'category' => 'frontend',
    117205    'ui_config' => fn() => [
     206        'tags' => [
     207            __('Frontend', 'advanced-settings'),
     208            __('Meta', 'advanced-settings'),
     209            __('Security', 'advanced-settings'),
     210            __('Cleanup', 'advanced-settings'),
     211        ],
    118212        'fields' => [
    119213            'enable' => [
    120214                'type' => 'toggle',
    121215                'label' => __('Remove generator meta tag', 'advanced-settings'),
     216                'description' => __('Removes the WordPress version from the head section of the website', 'advanced-settings'),
    122217            ],
    123218        ]
     
    127222        remove_action( 'wp_head', 'wp_generator');
    128223    },
    129     'priority' => 10,
     224    'priority' => 70,
    130225]);
    131226
     
    136231    'category' => 'frontend',
    137232    'ui_config' => fn() => [
     233        'tags' => [
     234            __('Frontend', 'advanced-settings'),
     235            __('Meta', 'advanced-settings'),
     236            __('SEO', 'advanced-settings'),
     237        ],
    138238        'fields' => [
    139239            'enable' => [
     
    178278        });
    179279    },
    180     'priority' => 10,
     280    'priority' => 80,
    181281]);
    182282
     
    187287    'category' => 'frontend',
    188288    'ui_config' => fn() => [
     289        'tags' => [
     290            __('Frontend', 'advanced-settings'),
     291            __('Content', 'advanced-settings'),
     292            __('Security', 'advanced-settings'),
     293            __('Cleanup', 'advanced-settings'),
     294        ],
    189295        'fields' => [
    190296            'enable' => [
     
    210316        }, 10, 2 );
    211317    },
    212     'priority' => 20,
     318    'priority' => 90,
     319]);
     320
     321
     322
     323advset_register_feature([
     324    'id' => 'frontend.title.improve_format',
     325    'category' => 'frontend',
     326    'deprecated' => true,
     327    'ui_config' => fn() => [
     328        'tags' => [
     329            __('Developer', 'advanced-settings'),
     330            __('Frontend', 'advanced-settings'),
     331            __('Content', 'advanced-settings'),
     332            __('Automations', 'advanced-settings'),
     333        ],
     334        'fields' => [
     335            'enable' => [
     336                'type' => 'toggle',
     337                'label' => __('Adjust the wp_title function', 'advanced-settings'),
     338            ],
     339        ]
     340    ],
     341    'execution_handler' => function() {
     342        add_filter('wp_title', function($title, $sep) {
     343            global $paged, $page;
     344   
     345            if ( is_feed() )
     346                return $title;
     347   
     348            // Add the site name.
     349            $title .= get_bloginfo( 'name' );
     350   
     351            // Add the site description for the home/front page.
     352            $site_description = get_bloginfo( 'description', 'display' );
     353            if ( $site_description && ( is_home() || is_front_page() ) )
     354                $title = "$title $sep $site_description";
     355   
     356            // Add a page number if necessary.
     357            if ( $paged >= 2 || $page >= 2 )
     358                $title = "$title $sep " . sprintf( __( 'Page %s', 'responsive' ), max( $paged, $page ) );
     359   
     360            return $title;
     361        }, 10, 2);
     362    },
     363    'priority' => 100,
    213364]);
    214365
     
    219370    'category' => 'frontend',
    220371    'ui_config' => fn() => [
     372        'tags' => [
     373            __('Editing', 'advanced-settings'),
     374            __('Frontend', 'advanced-settings'),
     375            __('Content', 'advanced-settings'),
     376            __('Cleanup', 'advanced-settings'),
     377            __('Automations', 'advanced-settings'),
     378        ],
    221379        'fields' => [
    222380            'enable' => [
     
    230388        add_filter( 'run_wptexturize', '__return_false' );
    231389    },
    232     'priority' => 10,
    233 ]);
    234 
    235 
    236 
    237 advset_register_feature([
    238     'id' => 'frontend.thumbnails.enable_support',
    239     'category' => 'frontend',
    240     'ui_config' => fn() => [
    241         'fields' => [
    242             'enable' => [
    243                 'type' => 'toggle',
    244                 'label' => __('Add thumbnail support', 'advanced-settings'),
    245                 'disabled' => !ADVSET_THUMBS,
    246                 'description' => ADVSET_THUMBS
    247                     ? ''
    248                     : __('Already supported by current theme', 'advanced-settings'),
    249             ],
    250         ]
    251     ],
    252     'execution_handler' => function() {
    253         add_action('after_setup_theme', function (){
    254             add_theme_support( 'post-thumbnails' );
    255         });
    256     },
    257     'priority' => 30,
    258 ]);
    259 define( 'ADVSET_THUMBS', !current_theme_supports('post-thumbnails') );
    260 
    261 
    262 
    263 advset_register_feature([
    264     'id' => 'frontend.thumbnails.auto_from_first_image',
    265     'category' => 'frontend',
    266     'ui_config' => fn() => [
    267         'fields' => [
    268             'enable' => [
    269                 'type' => 'toggle',
    270                 'label' => __('Automatically generate the Post Thumbnail', 'advanced-settings'),
    271                 'description' => __('from the first image in post', 'advanced-settings'),
    272             ],
    273         ]
    274     ],
    275     'execution_handler' => function() {
    276         require_once ADVSET_DIR . '/feature-setup/features/includes/frontend.auto_thumbs.php';
    277         add_action('transition_post_status', 'advset__feature__auto_thumbs', 10, 3);
    278     },
     390    'priority' => 110,
     391]);
     392
     393
     394
     395advset_register_feature([
     396    'id' => 'frontend.oembed.disable',
     397    'category' => 'frontend',
     398    'ui_config' => fn() => [
     399        'tags' => [
     400            __('Editing', 'advanced-settings'),
     401            __('Frontend', 'advanced-settings'),
     402            __('Content', 'advanced-settings'),
     403            __('Security', 'advanced-settings'),
     404            __('Cleanup', 'advanced-settings'),
     405            __('GDPR', 'advanced-settings'),
     406            __('Performance', 'advanced-settings'),
     407            __('Automations', 'advanced-settings'),
     408        ],
     409        'fields' => [
     410            'enable' => [
     411                'type' => 'toggle',
     412                'label' => __('Disable auto-embed of external content', 'advanced-settings'),
     413                'description' => __('Disables WordPress oEmbed, which automatically converts URLs into embedded content.', 'advanced-settings'),
     414            ],
     415        ]
     416    ],
     417    'execution_handler' => function() {
     418        remove_action('wp_head', 'wp_oembed_add_discovery_links');
     419        remove_action('wp_head', 'wp_oembed_add_host_js');
     420        remove_action('wp_head', 'rest_output_link_wp_head');
     421        remove_action('rest_api_init', 'wp_oembed_register_route');
     422        remove_filter('the_content', [$GLOBALS['wp_embed'], 'autoembed'], 8);
     423        remove_filter('oembed_dataparse', 'wp_filter_oembed_result', 10);
     424        add_filter('embed_oembed_discover', '__return_false');
     425    },
     426    'priority' => 120,
    279427]);
    280428
     
    285433    'category' => 'frontend',
    286434    'ui_config' => fn() => [
     435        'tags' => [
     436            __('Editing', 'advanced-settings'),
     437            __('Frontend', 'advanced-settings'),
     438            __('Content', 'advanced-settings'),
     439            __('Cleanup', 'advanced-settings'),
     440            __('Automations', 'advanced-settings'),
     441        ],
    287442        'fields' => [
    288443            'enable' => [
     
    308463        });
    309464    },
     465    'priority' => 130,
    310466]);
    311467
     
    316472    'category' => 'frontend',
    317473    'ui_config' => fn() => [
     474        'tags' => [
     475            __('Frontend', 'advanced-settings'),
     476            __('Content', 'advanced-settings'),
     477            __('SEO', 'advanced-settings'),
     478            __('Cleanup', 'advanced-settings'),
     479            __('Automations', 'advanced-settings'),
     480        ],
    318481        'fields' => [
    319482            'enable' => [
     
    339502        });
    340503    },
     504    'priority' => 140,
    341505]);
    342506
     
    347511    'category' => 'frontend',
    348512    'ui_config' => fn() => [
     513        'tags' => [
     514            __('Frontend', 'advanced-settings'),
     515            __('Content', 'advanced-settings'),
     516            __('Comments', 'advanced-settings'),
     517            __('Automations', 'advanced-settings'),
     518            __('Cleanup', 'advanced-settings'),
     519        ],
    349520        'fields' => [
    350521            'enable' => [
     
    367538        }, 10);
    368539    },
    369     'priority' => 40,
     540    'priority' => 150,
     541]);
     542
     543
     544
     545advset_register_feature([
     546    'id' => 'frontend.post.show_author_bio',
     547    'category' => 'frontend',
     548    'deprecated' => true,
     549    'ui_config' => fn() => [
     550        'tags' => [
     551            __('Frontend', 'advanced-settings'),
     552            __('Content', 'advanced-settings'),
     553            __('Automations', 'advanced-settings'),
     554        ],
     555        'fields' => [
     556            'enable' => [
     557                'type' => 'toggle',
     558                'label' => __('Insert author bio in each post', 'advanced-settings'),
     559            ],
     560        ]
     561    ],
     562    'execution_handler' => function() {
     563        add_filter('the_content', function($content='') {
     564            return $content.' <div id="entry-author-info">
     565                <div id="author-avatar">
     566                    '. get_avatar( get_the_author_meta( 'user_email' ), apply_filters( 'author_bio_avatar_size', 100 ) ) .'
     567                </div>
     568                <div id="author-description">
     569                    <h2>'. sprintf( __( 'About %s' ), get_the_author() ) .'</h2>
     570                    '. get_the_author_meta( 'description' ) .'
     571                    <div id="author-link">
     572                        <a href="'. get_author_posts_url( get_the_author_meta( 'ID' ) ) .'">
     573                            '. sprintf( __( 'View all posts by %s <span class="meta-nav">&rarr;</span>' ), get_the_author() ) .'
     574                        </a>
     575                    </div>
     576                </div>
     577            </div>';
     578        });
     579    },
     580    'priority' => 160,
     581]);
     582
     583
     584
     585advset_register_feature([
     586    'id' => 'frontend.user.allow_html_bio',
     587    'category' => 'frontend',
     588    'deprecated' => true,
     589    'ui_config' => fn() => [
     590        'tags' => [
     591            __('Admin', 'advanced-settings'),
     592            __('Frontend', 'advanced-settings'),
     593            __('Content', 'advanced-settings'),
     594        ],
     595        'fields' => [
     596            'enable' => [
     597                'type' => 'toggle',
     598                'label' => __('Allow complex HTML in user profile description', 'advanced-settings'),
     599            ],
     600        ]
     601    ],
     602    'execution_handler' => function() {
     603        remove_filter('pre_user_description', 'wp_filter_kses');
     604    },
     605    'priority' => 170,
    370606]);
    371607
     
    376612    'category' => 'frontend',
    377613    'ui_config' => fn() => [
     614        'tags' => [
     615            __('Frontend', 'advanced-settings'),
     616            __('Content', 'advanced-settings'),
     617            __('Security', 'advanced-settings'),
     618            __('Automations', 'advanced-settings'),
     619        ],
    378620        'fields' => [
    379621            'enable' => [
     
    480722        });
    481723    },
    482     'priority' => 10,
    483 ]);
    484 
    485 
    486 
    487 advset_register_feature([
    488     'id' => 'frontend.title.improve_format',
     724    'priority' => 180,
     725]);
     726
     727
     728
     729advset_register_feature([
     730    'id' => 'frontend.analytics.google',
    489731    'category' => 'frontend',
    490732    'deprecated' => true,
    491733    'ui_config' => fn() => [
    492         'fields' => [
    493             'enable' => [
    494                 'type' => 'toggle',
    495                 'label' => __('Adjust the wp_title function', 'advanced-settings'),
    496             ],
    497         ]
    498     ],
    499     'execution_handler' => function() {
    500         add_filter('wp_title', function($title, $sep) {
    501             global $paged, $page;
    502    
    503             if ( is_feed() )
    504                 return $title;
    505    
    506             // Add the site name.
    507             $title .= get_bloginfo( 'name' );
    508    
    509             // Add the site description for the home/front page.
    510             $site_description = get_bloginfo( 'description', 'display' );
    511             if ( $site_description && ( is_home() || is_front_page() ) )
    512                 $title = "$title $sep $site_description";
    513    
    514             // Add a page number if necessary.
    515             if ( $paged >= 2 || $page >= 2 )
    516                 $title = "$title $sep " . sprintf( __( 'Page %s', 'responsive' ), max( $paged, $page ) );
    517    
    518             return $title;
    519         }, 10, 2);
    520     },
    521     'priority' => 10,
    522 ]);
    523 
    524 
    525 
    526 advset_register_feature([
    527     'id' => 'frontend.post.show_author_bio',
    528     'category' => 'frontend',
    529     'deprecated' => true,
    530     'ui_config' => fn() => [
    531         'fields' => [
    532             'enable' => [
    533                 'type' => 'toggle',
    534                 'label' => __('Insert author bio in each post', 'advanced-settings'),
    535             ],
    536         ]
    537     ],
    538     'execution_handler' => function() {
    539         add_filter('the_content', function($content='') {
    540             return $content.' <div id="entry-author-info">
    541                 <div id="author-avatar">
    542                     '. get_avatar( get_the_author_meta( 'user_email' ), apply_filters( 'author_bio_avatar_size', 100 ) ) .'
    543                 </div>
    544                 <div id="author-description">
    545                     <h2>'. sprintf( __( 'About %s' ), get_the_author() ) .'</h2>
    546                     '. get_the_author_meta( 'description' ) .'
    547                     <div id="author-link">
    548                         <a href="'. get_author_posts_url( get_the_author_meta( 'ID' ) ) .'">
    549                             '. sprintf( __( 'View all posts by %s <span class="meta-nav">&rarr;</span>' ), get_the_author() ) .'
    550                         </a>
    551                     </div>
    552                 </div>
    553             </div>';
    554         });
    555     },
    556     'priority' => 10,
    557 ]);
    558 
    559 
    560 
    561 advset_register_feature([
    562     'id' => 'frontend.user.allow_html_bio',
    563     'category' => 'frontend',
    564     'deprecated' => true,
    565     'ui_config' => fn() => [
    566         'fields' => [
    567             'enable' => [
    568                 'type' => 'toggle',
    569                 'label' => __('Allow complex HTML in user profile description', 'advanced-settings'),
    570             ],
    571         ]
    572     ],
    573     'execution_handler' => function() {
    574         remove_filter('pre_user_description', 'wp_filter_kses');
    575     },
    576     'priority' => 10,
    577 ]);
    578 
    579 
    580 
    581 advset_register_feature([
    582     'id' => 'frontend.analytics.google',
    583     'category' => 'frontend',
    584     'deprecated' => true,
    585     'ui_config' => fn() => [
     734        'tags' => [
     735            __('Frontend', 'advanced-settings'),
     736            __('Meta', 'advanced-settings'),
     737            __('GDPR', 'advanced-settings'),
     738        ],
    586739        'fields' => [
    587740            'enable' => [
     
    614767        });
    615768    },
    616     'priority' => 10,
     769    'priority' => 190,
    617770]);
    618771
     
    624777    'deprecated' => true,
    625778    'ui_config' => fn() => [
     779        'tags' => [
     780            __('Frontend', 'advanced-settings'),
     781            __('Meta', 'advanced-settings'),
     782        ],
    626783        'fields' => [
    627784            'enable' => [
     
    655812        }, 10, 2 );
    656813    },
    657     'priority' => 10,
     814    'priority' => 200,
    658815]);
    659816
     
    664821    'category' => 'frontend',
    665822    'ui_config' => fn() => [
     823        'tags' => [
     824            __('Frontend', 'advanced-settings'),
     825            __('HTML', 'advanced-settings'),
     826            __('Performance', 'advanced-settings'),
     827            __('Automations', 'advanced-settings'),
     828        ],
    666829        'fields' => [
    667830            'enable' => [
     
    678841        });
    679842    },
    680     'priority' => 10,
     843    'priority' => 210,
    681844]);
    682845
     
    687850    'category' => 'frontend',
    688851    'ui_config' => fn() => [
     852        'tags' => [
     853            __('Frontend', 'advanced-settings'),
     854            __('HTML', 'advanced-settings'),
     855            __('Performance', 'advanced-settings'),
     856            __('Cleanup', 'advanced-settings'),
     857            __('Automations', 'advanced-settings'),
     858        ],
    689859        'fields' => [
    690860            'enable' => [
     
    701871        });
    702872    },
    703     'priority' => 10,
     873    'priority' => 220,
    704874]);
    705875
     
    710880    'category' => 'frontend',
    711881    'ui_config' => fn() => [
     882        'tags' => [
     883            __('Frontend', 'advanced-settings'),
     884            __('Content', 'advanced-settings'),
     885            __('Emojis', 'advanced-settings'),
     886            __('Automations', 'advanced-settings'),
     887            __('Cleanup', 'advanced-settings'),
     888            __('GDPR', 'advanced-settings'),
     889        ],
    712890        'fields' => [
    713891            'enable' => [
     
    728906        add_filter('emoji_svg_url', '__return_false');
    729907    },
    730     'priority' => 10,
    731 ]);
    732 
    733 
     908    'priority' => 230,
     909]);
     910
     911
  • advanced-settings/trunk/feature-setup/features/system.php

    r3306579 r3323103  
    1515    'category' => 'system',
    1616    'ui_config' => fn() => [
     17        'tags' => [
     18            __('Admin', 'advanced-settings'),
     19            __('Frontend', 'advanced-settings'),
     20            __('Meta', 'advanced-settings'),
     21            __('Favicon', 'advanced-settings'),
     22            __('Cleanup', 'advanced-settings'),
     23            __('Branding', 'advanced-settings'),
     24        ],
    1725        'fields' => [
    1826            'enable' => [
     
    4048    'category' => 'system',
    4149    'ui_config' => fn() => [
     50        'tags' => [
     51            __('System', 'advanced-settings'),
     52            __('Admin', 'advanced-settings'),
     53            __('Frontend', 'advanced-settings'),
     54            __('Cleanup', 'advanced-settings'),
     55            __('Security', 'advanced-settings'),
     56            __('Comments', 'advanced-settings'),
     57        ],
    4258        'fields' => [
    4359            'enable' => [
     
    6076        } );
    6177    },
    62     'priority' => 10,
    63 ]);
    64 
    65 
    66 
    67 advset_register_feature([
    68     'id' => 'system.posts.disable_autosave',
    69     'category' => 'system',
    70     'ui_config' => fn() => [
    71         'fields' => [
    72             'enable' => [
    73                 'type' => 'toggle',
    74                 'label' => __('Disable auto save', 'advanced-settings'),
    75             ],
    76         ]
    77     ],
    78     'execution_handler' => function() {
    79         define('AUTOSAVE_INTERVAL', 60 * 60 * 24 * 365 * 100); // save interval => 100 years
    80     },
    8178    'priority' => 20,
    8279]);
     
    8582
    8683advset_register_feature([
     84    'id' => 'system.xmlrpc.disable',
     85    'category' => 'system',
     86    'ui_config' => fn() => [
     87        'tags' => [
     88            __('System', 'advanced-settings'),
     89            __('Security', 'advanced-settings'),
     90            __('Performance', 'advanced-settings'),
     91            __('Cleanup', 'advanced-settings'),
     92        ],
     93        'fields' => [
     94            'enable' => [
     95                'type' => 'toggle',
     96                'label' => __('Disable XML-RPC', 'advanced-settings'),
     97                'description' => __('Disables XML-RPC functionality for security', 'advanced-settings'),
     98            ],
     99        ]
     100    ],
     101    'execution_handler' => function() {
     102        add_filter('xmlrpc_enabled', '__return_false');
     103    },
     104    'priority' => 30,
     105]);
     106
     107
     108
     109advset_register_feature([
     110    'id' => 'system.rest.disable_public',
     111    'category' => 'system',
     112    'ui_config' => fn() => [
     113        'tags' => [
     114            __('System', 'advanced-settings'),
     115            __('Security', 'advanced-settings'),
     116            __('Performance', 'advanced-settings'),
     117            __('Cleanup', 'advanced-settings'),
     118        ],
     119        'fields' => [
     120            'enable' => [
     121                'type' => 'toggle',
     122                'label' => __('Disable public REST API', 'advanced-settings'),
     123                'description' => __('Disables REST API access for non-authenticated users', 'advanced-settings'),
     124            ],
     125        ]
     126    ],
     127    'execution_handler' => function() {
     128        add_filter('rest_authentication_errors', function($result) {
     129            if (!empty($result)) {
     130                return $result;
     131            }
     132            if (!is_user_logged_in()) {
     133                return new WP_Error('rest_forbidden', 'REST API restricted to authenticated users.', ['status' => 401]);
     134            }
     135            return $result;
     136        });
     137    },
     138    'priority' => 40,
     139]);
     140
     141
     142
     143advset_register_feature([
    87144    'id' => 'system.updates.skip_bundled_themes',
    88145    'category' => 'system',
    89146    'ui_config' => fn() => [
     147        'tags' => [
     148            __('System', 'advanced-settings'),
     149            __('Updates', 'advanced-settings'),
     150            __('Cleanup', 'advanced-settings'),
     151        ],
    90152        'fields' => [
    91153            'enable' => [
     
    130192        }
    131193    },
    132     'priority' => 20,
     194    'priority' => 50,
    133195]);
    134196
     
    139201    'category' => 'system',
    140202    'ui_config' => fn() => [
     203        'tags' => [
     204            __('System', 'advanced-settings'),
     205            __('Updates', 'advanced-settings'),
     206            __('Notifications', 'advanced-settings'),
     207            __('Emails', 'advanced-settings'),
     208        ],
    141209        'fields' => [
    142210            'enable' => [
     
    150218        add_filter('auto_core_update_send_email', '__return_false');
    151219    },
    152     'priority' => 20,
     220    'priority' => 60,
    153221]);
    154222
     
    159227    'category' => 'system',
    160228    'ui_config' => fn() => [
     229        'tags' => [
     230            __('System', 'advanced-settings'),
     231            __('Updates', 'advanced-settings'),
     232            __('Notifications', 'advanced-settings'),
     233            __('Emails', 'advanced-settings'),
     234        ],
    161235        'fields' => [
    162236            'enable' => [
     
    170244        add_filter('auto_plugin_update_send_email', '__return_false');
    171245    },
    172     'priority' => 30,
     246    'priority' => 70,
    173247]);
    174248
     
    179253    'category' => 'system',
    180254    'ui_config' => fn() => [
     255        'tags' => [
     256            __('System', 'advanced-settings'),
     257            __('Updates', 'advanced-settings'),
     258            __('Notifications', 'advanced-settings'),
     259            __('Emails', 'advanced-settings'),
     260        ],
    181261        'fields' => [
    182262            'enable' => [
     
    190270        add_filter('auto_theme_update_send_email', '__return_false');
    191271    },
    192     'priority' => 40,
    193 ]);
    194 
    195 
     272    'priority' => 80,
     273]);
     274
     275
  • advanced-settings/trunk/migrations/3.x.x.php

    r3281733 r3323103  
    152152    },
    153153
     154    '3.1.0' => function() {
     155        $settings = get_option('advanced_settings_settings', []);
     156
     157        // Add admin bar logo to branding feature
     158        if ( !empty($settings['dashboard.adminbar.custom_logo']['url']) ) {
     159            $settings['dashboard.branding.customize'] = [
     160                'enable' => true,
     161                'admin_bar_logo' => $settings['dashboard.adminbar.custom_logo']['url'],
     162            ];
     163        }
     164
     165        // Remove old admin bar logo option
     166        unset($settings['dashboard.adminbar.custom_logo']);
     167
     168        // Move old auto save setting to editing category
     169        if ( !empty($settings['system.posts.disable_autosave']) ) {
     170            $settings['editing.posts.disable_autosave'] = $settings['system.posts.disable_autosave'];
     171            unset($settings['system.posts.disable_autosave']);
     172        }
     173
     174        // Move old auto thumbs setting to editing category
     175        if ( !empty($settings['frontend.thumbnails.auto_from_first_image']) ) {
     176            $settings['editing.thumbnails.auto_from_first_image'] = $settings['frontend.thumbnails.auto_from_first_image'];
     177            unset($settings['frontend.thumbnails.auto_from_first_image']);
     178        }
     179
     180        // Move old thumbs setting to editing category
     181        if ( !empty($settings['frontend.thumbnails.enable_support']) ) {
     182            $settings['editing.thumbnails.enable_support'] = $settings['frontend.thumbnails.enable_support'];
     183            unset($settings['frontend.thumbnails.enable_support']);
     184        }
     185
     186        // Rename dashboard features to adminarea features
     187        foreach ($settings as $feature_id => $feature_settings) {
     188            if ( strpos($feature_id, 'dashboard.') === 0 ) {
     189                $settings['adminarea.' . substr($feature_id, 10)] = $feature_settings;
     190                unset($settings[$feature_id]);
     191            }
     192        }
     193
     194        // Update settings
     195        update_option('advanced_settings_settings', $settings);
     196    },
     197
    154198];
  • advanced-settings/trunk/readme.txt

    r3308393 r3323103  
    88Requires at least: 5.0.0
    99Tested up to: 6.8
    10 Stable tag: 3.0.2
     10Stable tag: 3.1.0
    1111License: GPLv3 or later
    1212License URI: http://www.gnu.org/licenses/gpl-3.0.html
     
    2626[→ details in FAQ](https://wordpress.org/plugins/advanced-settings#how%20is%20the%20security%20of%20the%20plugin%20ensured%3F)
    2727
    28 **✳️ INFO ABOUT BAD REVIEWS**
    29 2 bad reviews occurred in 2017 because outdated PHP versions were used, but can't happen again.
     28**✳️ INFO ABOUT THE 2 BAD RATINGS**
     292 bad ratings occurred in 2017 because outdated PHP versions were used, but can't happen again.
    3030[→ details in FAQ](https://wordpress.org/plugins/advanced-settings#what%20caused%20the%20two%20bad%20ratings%3F)
    3131
     
    3737Advanced Settings 3 was developed to help as many users as possible. If you'd like to see a feature added to this plugin, please let us know. Don't worry, we'll keep the plugin fast and lean; this is a high priority for us. We'll only implement features that don't conflict with this.
    3838
    39 = Dashboard =
    40 
    41 * Customize dashboard logo
     39= Admin Area =
     40
     41* Hide the top admin bar for all users in the frontend
    4242* Hide WordPress update message in dashboard
    43 * Hide the top admin bar for all users in the frontend
    4443* Hide the welcome panel in the dashboard
     44* Hide the default widgets in the dashboard 💥 new
     45* Customize the admin area branding 💥 new
     46
     47= Frontend =
     48
     49* Remove PHP version from HTTP headers 💥 new
     50* Add security HTTP headers 💥 new
     51* Automatically add FavIcon (when favicon.ico, favicon.png or favicon.svg exists in template folder)
     52* Add Facebook Open Graph meta tags
     53* Remove shortlink meta tag
     54* Remove RSD (Weblog Client Link) meta tag
     55* Remove WordPress generator meta tag
     56* Automatically add description meta tag using blog description and post excerpt (SEO)
     57* Disable author pages
     58* Remove wptexturize filter
     59* Disable auto embed of external content 💥 new
     60* Limit excerpt length
     61* Add "Read more" link after excerpt
     62* Remove trackbacks and pingbacks from comment count
     63* Protect email addresses from spam bots
     64* Compress HTML code
     65* Remove HTML comments (except conditional IE comments)
     66* Disable emoji image replacement
     67
     68= Editing =
     69
     70* Disable posts auto saving
     71* Limit post revisions 💥 new
     72* Allow SVG uploads for admins 💥 new
     73* Downsize images on upload to max size
     74* Set JPEG quality
     75* Add thumbnail support
     76* Automatically generate post thumbnail (from first image in post)
    4577
    4678= System =
     
    4880* Hide default WordPress favicon
    4981* Disable comment system
    50 * Disable posts auto saving
     82* Disable XML-RPC 💥 new
     83* Disable public REST API 💥 new
    5184* Prevent installation of new default WordPress themes during core updates
    5285* Disable email notifications for core updates
    5386* Disable email notifications for plugin updates
    5487* Disable email notifications for theme updates
    55 
    56 = Frontend =
    57 
    58 * Disable author pages
    59 * Automatically add FavIcon (when favicon.ico, favicon.png or favicon.svg exists in template folder)
    60 * Automatically add description meta tag using blog description and post excerpt (SEO)
    61 * Remove WordPress generator meta tag
    62 * Remove RSD (Weblog Client Link) meta tag
    63 * Remove shortlink meta tag
    64 * Limit excerpt length
    65 * Add "Read more" link after excerpt
    66 * Remove wptexturize filter
    67 * Remove trackbacks and pingbacks from comment count
    68 * Compress HTML code
    69 * Remove HTML comments (except conditional IE comments)
    70 * Disable author pages
    71 * Add thumbnail support
    72 * Automatically generate post thumbnail (from first image in post)
    73 * Protect email addresses from spam bots
    74 * Add Facebook Open Graph meta tags
    75 * Disable emoji image replacement
    76 
    77 = Editing =
    78 
    79 * Set JPEG quality
    80 * Downsize images on upload to max size
    8188
    8289= Developer =
     
    131138
    132139**Detailed answer:**
    133 The plugin developer at the time had decided in 2017 to use PHP features that were introduced with PHP 5.4. Support for the previous version, PHP 5.3, had already been officially discontinued by The PHP Group 3 years earlier. It was therefore natural to assume that all websites actually in use had already been converted to PHP 5.4 or newer. Unfortunately, this was not the case for a few of them.
    134 
    135 Nowadays, you can specify in the plugin metadata which PHP version is required as a minimum for the plugin, so that users can only update the plugin if they are using the correct PHP version. While this WordPress feature came too late to prevent the problems and the negative reviews, this problem can fortunately be prevented in the future.
    136 
    137 It would be great if you could contribute with your own review to ensure that the ratings reflect the current state of the plugin.
     140The plugin developer at the time had decided in 2017 to use PHP features that were introduced with PHP 5.4. Support for the previous version, PHP 5.3, had already been officially discontinued by The PHP Group 3 years earlier. It was therefore natural to assume that all websites actually in use had already been converted to PHP 5.4 or newer. Unfortunately, this was not the case for a few users.
     141
     142Nowadays, WordPress allowes to specify in the plugin metadata which PHP version is required as a minimum for the plugin, so that users can only update the plugin if they are using the correct PHP version. While this WordPress feature came too late to prevent the problems and the negative reviews, this problem can fortunately be prevented in the future.
     143
     144It would be great if you could contribute with your own review to ensure that the ratings reflect the quality of the plugin.
    138145
    139146== Screenshots ==
     
    144151
    145152== Changelog ==
     153
     154= 3.1.0 - 2025-07-06 =
     155* Added 9 new features (see feature list)
     156* Added tags and tag navigation
    146157
    147158= 3.0.2 - 2025-06-04 =
Note: See TracChangeset for help on using the changeset viewer.