Changeset 3448585
- Timestamp:
- 01/28/2026 10:30:37 AM (2 months ago)
- Location:
- zenpress
- Files:
-
- 32 added
- 104 edited
- 1 copied
-
tags/2.2.0 (copied) (copied from zenpress/trunk)
-
tags/2.2.0/assets/build/index.asset.php (modified) (1 diff)
-
tags/2.2.0/assets/build/index.js (modified) (1 diff)
-
tags/2.2.0/assets/src/index.js (modified) (1 diff)
-
tags/2.2.0/assets/src/js/components/Notices.js (modified) (1 diff)
-
tags/2.2.0/assets/src/js/components/SaveButton.js (modified) (1 diff)
-
tags/2.2.0/assets/src/js/components/SnippetToggleControl.js (modified) (2 diffs)
-
tags/2.2.0/assets/src/js/components/Tabs.js (modified) (5 diffs)
-
tags/2.2.0/assets/src/js/hooks/useSettings.js (modified) (1 diff)
-
tags/2.2.0/assets/src/js/pages/SettingsPage.js (modified) (2 diffs)
-
tags/2.2.0/inc/admin/enqueue.php (modified) (3 diffs)
-
tags/2.2.0/inc/core/constants.php (modified) (1 diff)
-
tags/2.2.0/inc/core/metadata.php (modified) (2 diffs)
-
tags/2.2.0/inc/core/sanitize.php (modified) (1 diff)
-
tags/2.2.0/inc/settings/loader.php (modified) (2 diffs)
-
tags/2.2.0/inc/settings/options.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/functions/block-user-enumeration.php (modified) (2 diffs)
-
tags/2.2.0/inc/snippets/functions/clean-admin-bar.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/functions/clean-dashboard-items.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/functions/disable-author-archives.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/functions/disable-autosave.php (added)
-
tags/2.2.0/inc/snippets/functions/disable-capital-p-dangit.php (added)
-
tags/2.2.0/inc/snippets/functions/disable-dashicons.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/functions/disable-default-pattern-categories.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/functions/disable-emoji-scripts.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/functions/disable-jquery-migrate.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/functions/disable-oembed.php (modified) (2 diffs)
-
tags/2.2.0/inc/snippets/functions/disable-password-strength-meter.php (added)
-
tags/2.2.0/inc/snippets/functions/disable-pdf-thumbnails.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/functions/disable-pingback-trackback.php (modified) (3 diffs)
-
tags/2.2.0/inc/snippets/functions/disable-rest-api.php (modified) (3 diffs)
-
tags/2.2.0/inc/snippets/functions/disable-rss.php (modified) (2 diffs)
-
tags/2.2.0/inc/snippets/functions/disable-woocommerce-cart-fragments.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/functions/disable-woocommerce-scripts-styles.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/functions/disable-woocommerce-widgets.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/functions/disable-wordpress-default-lazy-loading.php (added)
-
tags/2.2.0/inc/snippets/functions/hide-woocommerce-version.php (modified) (3 diffs)
-
tags/2.2.0/inc/snippets/functions/hide-wordpress-version.php (modified) (2 diffs)
-
tags/2.2.0/inc/snippets/functions/limit-post-revisions.php (added)
-
tags/2.2.0/inc/snippets/functions/protect-wp-login.php (modified) (4 diffs)
-
tags/2.2.0/inc/snippets/functions/remove-gutenberg-unwanted-block-patterns.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/functions/remove-help-button.php (added)
-
tags/2.2.0/inc/snippets/functions/remove-thanks-for-using-wordpress-in-footer.php (added)
-
tags/2.2.0/inc/snippets/functions/remove-woocommerce-patterns.php (modified) (3 diffs)
-
tags/2.2.0/inc/snippets/functions/remove-wordpress-logo.php (added)
-
tags/2.2.0/inc/snippets/meta/clean-dashboard-items.meta.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/meta/disable-application-passwords.meta.php (modified) (2 diffs)
-
tags/2.2.0/inc/snippets/meta/disable-autosave.meta.php (added)
-
tags/2.2.0/inc/snippets/meta/disable-capital-p-dangit.meta.php (added)
-
tags/2.2.0/inc/snippets/meta/disable-dashicons.meta.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/meta/disable-default-pattern-categories.meta.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/meta/disable-dns-prefetch.meta.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/meta/disable-emoji-scripts.meta.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/meta/disable-jquery-migrate.meta.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/meta/disable-password-strength-meter.meta.php (added)
-
tags/2.2.0/inc/snippets/meta/disable-pdf-thumbnails.meta.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/meta/disable-pingback-trackback.meta.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/meta/disable-rest-api.meta.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/meta/disable-wordpress-default-lazy-loading.meta.php (added)
-
tags/2.2.0/inc/snippets/meta/limit-post-revisions.meta.php (added)
-
tags/2.2.0/inc/snippets/meta/protect-wp-login.meta.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/meta/remove-gutenberg-unwanted-block-patterns.meta.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/meta/remove-help-button.meta.php (added)
-
tags/2.2.0/inc/snippets/meta/remove-rest-api-link.meta.php (modified) (1 diff)
-
tags/2.2.0/inc/snippets/meta/remove-thanks-for-using-wordpress-in-footer.meta.php (added)
-
tags/2.2.0/inc/snippets/meta/remove-wordpress-logo.meta.php (added)
-
tags/2.2.0/languages/zenpress.pot (modified) (26 diffs)
-
tags/2.2.0/readme.txt (modified) (8 diffs)
-
tags/2.2.0/zenpress.php (modified) (2 diffs)
-
trunk/assets/build/index.asset.php (modified) (1 diff)
-
trunk/assets/build/index.js (modified) (1 diff)
-
trunk/assets/src/index.js (modified) (1 diff)
-
trunk/assets/src/js/components/Notices.js (modified) (1 diff)
-
trunk/assets/src/js/components/SaveButton.js (modified) (1 diff)
-
trunk/assets/src/js/components/SnippetToggleControl.js (modified) (2 diffs)
-
trunk/assets/src/js/components/Tabs.js (modified) (5 diffs)
-
trunk/assets/src/js/hooks/useSettings.js (modified) (1 diff)
-
trunk/assets/src/js/pages/SettingsPage.js (modified) (2 diffs)
-
trunk/inc/admin/enqueue.php (modified) (3 diffs)
-
trunk/inc/core/constants.php (modified) (1 diff)
-
trunk/inc/core/metadata.php (modified) (2 diffs)
-
trunk/inc/core/sanitize.php (modified) (1 diff)
-
trunk/inc/settings/loader.php (modified) (2 diffs)
-
trunk/inc/settings/options.php (modified) (1 diff)
-
trunk/inc/snippets/functions/block-user-enumeration.php (modified) (2 diffs)
-
trunk/inc/snippets/functions/clean-admin-bar.php (modified) (1 diff)
-
trunk/inc/snippets/functions/clean-dashboard-items.php (modified) (1 diff)
-
trunk/inc/snippets/functions/disable-author-archives.php (modified) (1 diff)
-
trunk/inc/snippets/functions/disable-autosave.php (added)
-
trunk/inc/snippets/functions/disable-capital-p-dangit.php (added)
-
trunk/inc/snippets/functions/disable-dashicons.php (modified) (1 diff)
-
trunk/inc/snippets/functions/disable-default-pattern-categories.php (modified) (1 diff)
-
trunk/inc/snippets/functions/disable-emoji-scripts.php (modified) (1 diff)
-
trunk/inc/snippets/functions/disable-jquery-migrate.php (modified) (1 diff)
-
trunk/inc/snippets/functions/disable-oembed.php (modified) (2 diffs)
-
trunk/inc/snippets/functions/disable-password-strength-meter.php (added)
-
trunk/inc/snippets/functions/disable-pdf-thumbnails.php (modified) (1 diff)
-
trunk/inc/snippets/functions/disable-pingback-trackback.php (modified) (3 diffs)
-
trunk/inc/snippets/functions/disable-rest-api.php (modified) (3 diffs)
-
trunk/inc/snippets/functions/disable-rss.php (modified) (2 diffs)
-
trunk/inc/snippets/functions/disable-woocommerce-cart-fragments.php (modified) (1 diff)
-
trunk/inc/snippets/functions/disable-woocommerce-scripts-styles.php (modified) (1 diff)
-
trunk/inc/snippets/functions/disable-woocommerce-widgets.php (modified) (1 diff)
-
trunk/inc/snippets/functions/disable-wordpress-default-lazy-loading.php (added)
-
trunk/inc/snippets/functions/hide-woocommerce-version.php (modified) (3 diffs)
-
trunk/inc/snippets/functions/hide-wordpress-version.php (modified) (2 diffs)
-
trunk/inc/snippets/functions/limit-post-revisions.php (added)
-
trunk/inc/snippets/functions/protect-wp-login.php (modified) (4 diffs)
-
trunk/inc/snippets/functions/remove-gutenberg-unwanted-block-patterns.php (modified) (1 diff)
-
trunk/inc/snippets/functions/remove-help-button.php (added)
-
trunk/inc/snippets/functions/remove-thanks-for-using-wordpress-in-footer.php (added)
-
trunk/inc/snippets/functions/remove-woocommerce-patterns.php (modified) (3 diffs)
-
trunk/inc/snippets/functions/remove-wordpress-logo.php (added)
-
trunk/inc/snippets/meta/clean-dashboard-items.meta.php (modified) (1 diff)
-
trunk/inc/snippets/meta/disable-application-passwords.meta.php (modified) (2 diffs)
-
trunk/inc/snippets/meta/disable-autosave.meta.php (added)
-
trunk/inc/snippets/meta/disable-capital-p-dangit.meta.php (added)
-
trunk/inc/snippets/meta/disable-dashicons.meta.php (modified) (1 diff)
-
trunk/inc/snippets/meta/disable-default-pattern-categories.meta.php (modified) (1 diff)
-
trunk/inc/snippets/meta/disable-dns-prefetch.meta.php (modified) (1 diff)
-
trunk/inc/snippets/meta/disable-emoji-scripts.meta.php (modified) (1 diff)
-
trunk/inc/snippets/meta/disable-jquery-migrate.meta.php (modified) (1 diff)
-
trunk/inc/snippets/meta/disable-password-strength-meter.meta.php (added)
-
trunk/inc/snippets/meta/disable-pdf-thumbnails.meta.php (modified) (1 diff)
-
trunk/inc/snippets/meta/disable-pingback-trackback.meta.php (modified) (1 diff)
-
trunk/inc/snippets/meta/disable-rest-api.meta.php (modified) (1 diff)
-
trunk/inc/snippets/meta/disable-wordpress-default-lazy-loading.meta.php (added)
-
trunk/inc/snippets/meta/limit-post-revisions.meta.php (added)
-
trunk/inc/snippets/meta/protect-wp-login.meta.php (modified) (1 diff)
-
trunk/inc/snippets/meta/remove-gutenberg-unwanted-block-patterns.meta.php (modified) (1 diff)
-
trunk/inc/snippets/meta/remove-help-button.meta.php (added)
-
trunk/inc/snippets/meta/remove-rest-api-link.meta.php (modified) (1 diff)
-
trunk/inc/snippets/meta/remove-thanks-for-using-wordpress-in-footer.meta.php (added)
-
trunk/inc/snippets/meta/remove-wordpress-logo.meta.php (added)
-
trunk/languages/zenpress.pot (modified) (26 diffs)
-
trunk/readme.txt (modified) (8 diffs)
-
trunk/zenpress.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
zenpress/tags/2.2.0/assets/build/index.asset.php
r3412245 r3448585 1 <?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-notices'), 'version' => ' 980bfa58281a120e84d0');1 <?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-notices'), 'version' => '253355e0f124eff32114'); -
zenpress/tags/2.2.0/assets/build/index.js
r3412245 r3448585 1 (()=>{"use strict";var e={n:s=>{var t=s&&s.__esModule?()=>s.default:()=>s;return e.d(t,{a:t}),t},d:(s,t)=>{for(var n in t)e.o(t,n)&&!e.o(s,n)&&Object.defineProperty(s,n,{enumerable:!0,get:t[n]})},o:(e,s)=>Object.prototype.hasOwnProperty.call(e,s)};const s=window.wp.domReady;var t=e.n(s);const n=window.wp.element,r=window.wp.i18n,a=window.wp.components,i=window.wp.apiFetch;var o=e.n(i);const c=window.wp.data,l=window.wp.notices,p=window.ReactJSXRuntime,d=({label:e,value:s,onChange:t,help:r})=>{const i=(0,n.useRef)(null);return(0,n.useEffect)(()=>((e,s)=>{if(!e)return;const t=t=>{if("Enter"===t.key){const n=e.querySelector('input[type="checkbox"]') ;n&&(document.activeElement===n||e.contains(document.activeElement))&&(t.preventDefault(),t.stopPropagation(),s())}};return e.addEventListener("keydown",t),()=>{e.removeEventListener("keydown",t)}})(i.current,t),[t]),(0,p.jsx)("div",{ref:i,children:(0,p.jsx)(a.ToggleControl,{label:e,checked:s,onChange:t,help:r,__nextHasNoMarginBottom:!0})})},u=({onClick:e,isBusy:s})=>(0,p.jsx)(a.Button,{variant:"primary",onClick:e,isBusy:s,__next40pxDefaultSize:!0,children:(0,r.__)("Save settings","zenpress")}),b=()=>{const{removeNotice:e}=(0,c.useDispatch)(l.store),s=(0,c.useSelect)(e=>e(l.store).getNotices(),[]);return s&&0!==s.length?(0,p.jsx)(a.NoticeList,{notices:s,onRemove:e}):null},h=(0,n.createContext)(),f=({selectedTabId:e,onSelect:s,orientation:t="horizontal",children:r})=>{const[a,i]=(0,n.useState)(),o=(0,n.useRef)(null),c=void 0!==e?e:a;return(0,p.jsx)(h.Provider,{value:{selectedTabId:c,onSelect:t=>{void 0===e&&i(t),s?.(t)},orientation:t,getOrderedTabIds:()=>o.current?Array.from(o.current.querySelectorAll('[role="tab"]')).map(e=>{const s=e.getAttribute("id");return s?s.replace("zenpress-tab-",""):null}).filter(Boolean):[],tabListRef:o},children:(0,p.jsx)("div",{className:`zenpress-tabs zenpress-tabs--${t}`,children:r})})};f.TabList=({children:e})=>{const{orientation:s,tabListRef:t}=(0,n.useContext)(h);return(0,p.jsx)("div",{ref:t,className:`zenpress-tabs__list zenpress-tabs__list--${s}`,role:"tablist","aria-orientation":s,children:e})},f.Tab=({tabId:e,title:s,className:t="",children:r})=>{const{selectedTabId:a,onSelect:i,orientation:o,getOrderedTabIds:c}=(0,n.useContext)(h),l=a===e,d=(0,n.useRef)(null);return(0,p.jsx)("button",{ref:d,className:`zenpress-tabs__tab ${l?"zenpress-tabs__tab--is-active":""} ${t}`.trim(),role:"tab","aria-selected":l,"aria-controls":`zenpress-tab-panel-${e}`,id:`zenpress-tab-${e}`,tabIndex:l?0:-1,onClick:()=>i(e),onKeyDown:s=>{const t=c();if(!t||0===t.length)return;const n=t.indexOf(e);if(-1===n)return;let r=n;if("vertical"===o?"ArrowDown"===s.key?(s.preventDefault(),r=n<t.length-1?n+1:0):"ArrowUp"===s.key&&(s.preventDefault(),r=n>0?n-1:t.length-1):"ArrowRight"===s.key?(s.preventDefault(),r=n<t.length-1?n+1:0):"ArrowLeft"===s.key&&(s.preventDefault(),r=n>0?n-1:t.length-1),"Home"===s.key?(s.preventDefault(),r=0):"End"===s.key&&(s.preventDefault(),r=t.length-1)," "===s.key||"Enter"===s.key)return s.preventDefault(),void i(e);if(r!==n&&r>=0&&r<t.length){const e=t[r],s=document.getElementById(`zenpress-tab-${e}`);s&&(s.focus(),i(e))}},onFocus:()=>{l||i(e)},children:s||r})},f.TabPanel=({tabId:e,children:s})=>{const{selectedTabId:t}=(0,n.useContext)(h),r=(0,n.useRef)(null),a=t===e;return(0,n.useEffect)(()=>{a&&r.current&&(0===r.current.querySelectorAll('a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"]), input:not([disabled]), select:not([disabled]), textarea:not([disabled])').length?r.current.setAttribute("tabindex","0"):r.current.removeAttribute("tabindex"))},[a]),a?(0,p.jsx)("div",{ref:r,className:"zenpress-tabs__panel",role:"tabpanel",id:`zenpress-tab-panel-${e}`,"aria-labelledby":`zenpress-tab-${e}`,children:s}):(0,p.jsx)("div",{className:"zenpress-tabs__panel",role:"tabpanel",id:`zenpress-tab-panel-${e}`,"aria-labelledby":`zenpress-tab-${e}`,hidden:!0,children:s})};const x=()=>{const{snippets:e,setSnippets:s,saveSettings:t,isSaving:i}=(()=>{const[e,s]=(0,n.useState)({}),[t,a]=(0,n.useState)(!1),{createSuccessNotice:i,createErrorNotice:p}=(0,c.useDispatch)(l.store);return(0,n.useEffect)(()=>{o()({path:"/wp/v2/settings"}).then(e=>{const t=Array.isArray(e?.zenpress_active_snippets)?e.zenpress_active_snippets:[],n=window?.zenpressSnippetsMeta||{},r={};Object.keys(n).forEach(e=>{r[e]={...n[e],"enable-snippet":t.includes(e)}}),s(r)}).catch(()=>{p((0,r.__)("Failed to load settings.","zenpress"))})},[p]),{snippets:e,setSnippets:s,saveSettings:async()=>{a(!0);const s=Object.keys(e).filter(s=>e[s]?.["enable-snippet"]);try{await o()({path:"/wp/v2/settings",method:"POST",data:{zenpress_active_snippets:s}}),i((0,r.__)("Settings saved.","zenpress"))}catch{p((0,r.__)("Failed to save settings.","zenpress"))}finally{a(!1)}},isSaving:t}})(),[h,x]=(0,n.useState)(),_=e=>{s(s=>{const t={};return Object.entries(s).forEach(([s,n])=>{const r=(Array.isArray(n?.preset)?n.preset:[]).includes(e);t[s]={...n,"enable-snippet":r}}),t})},m=e=>e?e.charAt(0).toUpperCase()+e.slice(1).toLowerCase():e,v=["core","gutenberg","woocommerce","ads-blocker","tools"],y={};Object.keys(e).forEach(s=>{const t=e[s],n=(t?.category||(0,r.__)("Uncategorized","zenpress")).toLowerCase(),a=(t?.subcategory||(0,r.__)("uncategorized","zenpress")).toLowerCase();y[n]||(y[n]={}),y[n][a]||(y[n][a]=[]),y[n][a].push({name:s,data:t})});const z=Object.keys(y).sort((e,s)=>{const t=v.indexOf(e.toLowerCase()),n=v.indexOf(s.toLowerCase());return-1!==t&&-1!==n?t-n:-1!==t?-1:-1!==n?1:e.localeCompare(s,void 0,{sensitivity:"base"})});return(0,n.useEffect)(()=>{!h&&z.length>0&&x(z[0])},[h,z.length]),(0,n.useEffect)(()=>{const e=e=>{(e.ctrlKey||e.metaKey)&&"s"===e.key&&(e.preventDefault(),i||t())};return document.addEventListener("keydown",e),()=>{document.removeEventListener("keydown",e)}},[t,i]),(0,p.jsxs)("article",{className:"zenpress-row",children:[(0,p.jsxs)("section",{className:"zenpress-main",children:[(0,p.jsx)("div",{className:"zenpress-notices",children:(0,p.jsx)(b,{})}),(0,p.jsxs)("div",{className:"zenpress-panel",children:[(0,p.jsxs)(f,{orientation:"vertical",selectedTabId:h,onSelect:e=>{x(e),x(e)},children:[(0,p.jsx)(f.TabList,{children:z.map(e=>{const s=`zenpress-tabs__tab--category-${e.toLowerCase().replace(/\s+/g,"-")}`;return(0,p.jsx)(f.Tab,{tabId:e,title:m(e),className:s,children:m(e)},e)})}),z.map(e=>{const t=Object.keys(y[e]).sort();return(0,p.jsxs)(f.TabPanel,{tabId:e,children:[(0,p.jsx)("h2",{children:m(e)}),t.map(t=>(0,p.jsxs)("div",{className:`zenpress-subcategory zenpress-subcategory-${t.toLowerCase().replace(/\s+/g,"-")}`,children:[(0,p.jsx)("hr",{}),(0,p.jsx)("h3",{children:m(t)}),y[e][t].map(({name:e,data:t})=>(0,p.jsx)(d,{label:t.title||e,value:t?.["enable-snippet"]||!1,onChange:()=>{return t=e,void s(e=>({...e,[t]:{...e[t],"enable-snippet":!e[t]?.["enable-snippet"]}}));var t},help:t.description||""},e))]},t))]},e)})]}),(0,p.jsxs)("div",{className:"zenpress-actions",children:[(0,p.jsxs)("div",{className:"zenpress-actions-bulk",children:[(0,p.jsx)(a.Button,{variant:"tertiary",onClick:()=>{s(e=>{const s={};return Object.keys(e).forEach(t=>{s[t]={...e[t],"enable-snippet":!0}}),s})},__next40pxDefaultSize:!0,children:(0,r.__)("Enable all actions","zenpress")}),(0,p.jsx)(a.Button,{isDestructive:!0,onClick:()=>{s(e=>{const s={};return Object.keys(e).forEach(t=>{s[t]={...e[t],"enable-snippet":!1}}),s})},__next40pxDefaultSize:!0,children:(0,r.__)("Disable all actions","zenpress")})]}),(0,p.jsx)(u,{onClick:t,isBusy:i})]})]})]}),(0,p.jsx)("aside",{className:"zenpress-sidebar",children:(0,p.jsx)("div",{className:"zenpress-presets",children:(0,p.jsxs)("div",{className:"zenpress-presets-description",children:[(0,p.jsx)("h2",{children:(0,r.__)("Pick configuration preset","zenpress")}),(0,p.jsx)("p",{children:(0,r.__)("Don't know which features to enable? Quickly configure ZenPress by selecting a preset that matches your site type. Each preset enables optimized features for your specific use case.","zenpress")}),(0,p.jsx)("hr",{}),(0,p.jsxs)("h3",{children:["🖼️ ",(0,r.__)("Corporate website","zenpress")]}),(0,p.jsx)("p",{children:(0,r.__)("Optimized for business sites and portfolios. Focuses on security, performance, and removing unnecessary features like RSS feeds and author archives.","zenpress")}),(0,p.jsx)(a.Button,{variant:"secondary",onClick:()=>_("corporate-website"),__next40pxDefaultSize:!0,children:(0,r.__)("Enable","zenpress")}),(0,p.jsx)("hr",{}),(0,p.jsxs)("h3",{children:[" 📰 ",(0,r.__)("Blog","zenpress")]}),(0,p.jsx)("p",{children:(0,r.__)("Tailored for content-focused blogs. Includes performance and security optimizations while preserving essential blog features like RSS feeds.","zenpress")}),(0,p.jsx)(a.Button,{variant:"secondary",onClick:()=>_("blog"),__next40pxDefaultSize:!0,children:(0,r.__)("Enable","zenpress")}),(0,p.jsx)("hr",{}),(0,p.jsxs)("h3",{children:["🛒 ",(0,r.__)("E-commerce","zenpress")]}),(0,p.jsx)("p",{children:(0,r.__)("Designed for WooCommerce stores. Includes all performance and security features plus WooCommerce-specific optimizations for faster checkout.","zenpress")}),(0,p.jsx)(a.Button,{variant:"secondary",onClick:()=>_("ecommerce"),__next40pxDefaultSize:!0,children:(0,r.__)("Enable","zenpress")})]})})})]})};t()(()=>{const e=document.getElementById("zenpress-settings");e&&(0,n.createRoot)(e).render((0,p.jsx)(x,{}))})})();1 (()=>{"use strict";var e={n:s=>{var t=s&&s.__esModule?()=>s.default:()=>s;return e.d(t,{a:t}),t},d:(s,t)=>{for(var n in t)e.o(t,n)&&!e.o(s,n)&&Object.defineProperty(s,n,{enumerable:!0,get:t[n]})},o:(e,s)=>Object.prototype.hasOwnProperty.call(e,s)};const s=window.wp.domReady;var t=e.n(s);const n=window.wp.element,r=window.wp.i18n,a=window.wp.components,i=window.wp.apiFetch;var o=e.n(i);const c=window.wp.data,l=window.wp.notices,p=window.ReactJSXRuntime,d=({label:e,value:s,onChange:t,help:r})=>{const i=(0,n.useRef)(null);return(0,n.useEffect)(()=>((e,s)=>{if(!e)return;const t=t=>{if("Enter"===t.key){const n=e.querySelector('input[type="checkbox"]'),r=e.ownerDocument||document;n&&(r.activeElement===n||e.contains(r.activeElement))&&(t.preventDefault(),t.stopPropagation(),s())}};return e.addEventListener("keydown",t),()=>{e.removeEventListener("keydown",t)}})(i.current,t),[t]),(0,p.jsx)("div",{ref:i,children:(0,p.jsx)(a.ToggleControl,{label:e,checked:s,onChange:t,help:r,__nextHasNoMarginBottom:!0})})},u=({onClick:e,isBusy:s})=>(0,p.jsx)(a.Button,{variant:"primary",onClick:e,isBusy:s,__next40pxDefaultSize:!0,children:(0,r.__)("Save settings","zenpress")}),b=()=>{const{removeNotice:e}=(0,c.useDispatch)(l.store),s=(0,c.useSelect)(e=>e(l.store).getNotices(),[]);return s&&0!==s.length?(0,p.jsx)(a.NoticeList,{notices:s,onRemove:e}):null},h=(0,n.createContext)(),f=({selectedTabId:e,onSelect:s,orientation:t="horizontal",children:r})=>{const[a,i]=(0,n.useState)(),o=(0,n.useRef)(null),c=void 0!==e?e:a;return(0,p.jsx)(h.Provider,{value:{selectedTabId:c,onSelect:t=>{void 0===e&&i(t),s?.(t)},orientation:t,getOrderedTabIds:()=>o.current?Array.from(o.current.querySelectorAll('[role="tab"]')).map(e=>{const s=e.getAttribute("id");return s?s.replace("zenpress-tab-",""):null}).filter(Boolean):[],tabListRef:o},children:(0,p.jsx)("div",{className:`zenpress-tabs zenpress-tabs--${t}`,children:r})})};f.TabList=({children:e})=>{const{orientation:s,tabListRef:t}=(0,n.useContext)(h);return(0,p.jsx)("div",{ref:t,className:`zenpress-tabs__list zenpress-tabs__list--${s}`,role:"tablist","aria-orientation":s,children:e})},f.Tab=({tabId:e,title:s,className:t="",children:r})=>{const{selectedTabId:a,onSelect:i,orientation:o,getOrderedTabIds:c}=(0,n.useContext)(h),l=a===e,d=(0,n.useRef)(null);return(0,p.jsx)("button",{ref:d,className:`zenpress-tabs__tab ${l?"zenpress-tabs__tab--is-active":""} ${t}`.trim(),role:"tab","aria-selected":l,"aria-controls":`zenpress-tab-panel-${e}`,id:`zenpress-tab-${e}`,tabIndex:l?0:-1,onClick:()=>i(e),onKeyDown:s=>{const t=c();if(!t||0===t.length)return;const n=t.indexOf(e);if(-1===n)return;let r=n;if("vertical"===o?"ArrowDown"===s.key?(s.preventDefault(),r=n<t.length-1?n+1:0):"ArrowUp"===s.key&&(s.preventDefault(),r=n>0?n-1:t.length-1):"ArrowRight"===s.key?(s.preventDefault(),r=n<t.length-1?n+1:0):"ArrowLeft"===s.key&&(s.preventDefault(),r=n>0?n-1:t.length-1),"Home"===s.key?(s.preventDefault(),r=0):"End"===s.key&&(s.preventDefault(),r=t.length-1)," "===s.key||"Enter"===s.key)return s.preventDefault(),void i(e);if(r!==n&&r>=0&&r<t.length){const e=t[r],s=document.getElementById(`zenpress-tab-${e}`);s&&(s.focus(),i(e))}},onFocus:()=>{l||i(e)},children:s||r})},f.TabPanel=({tabId:e,children:s})=>{const{selectedTabId:t}=(0,n.useContext)(h),r=(0,n.useRef)(null),a=t===e;return(0,n.useEffect)(()=>{a&&r.current&&(0===r.current.querySelectorAll('a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"]), input:not([disabled]), select:not([disabled]), textarea:not([disabled])').length?r.current.setAttribute("tabindex","0"):r.current.removeAttribute("tabindex"))},[a]),a?(0,p.jsx)("div",{ref:r,className:"zenpress-tabs__panel",role:"tabpanel",id:`zenpress-tab-panel-${e}`,"aria-labelledby":`zenpress-tab-${e}`,children:s}):(0,p.jsx)("div",{className:"zenpress-tabs__panel",role:"tabpanel",id:`zenpress-tab-panel-${e}`,"aria-labelledby":`zenpress-tab-${e}`,hidden:!0,children:s})};const x=()=>{const{snippets:e,setSnippets:s,saveSettings:t,isSaving:i}=(()=>{const[e,s]=(0,n.useState)({}),[t,a]=(0,n.useState)(!1),{createSuccessNotice:i,createErrorNotice:p}=(0,c.useDispatch)(l.store);return(0,n.useEffect)(()=>{o()({path:"/wp/v2/settings"}).then(e=>{const t=Array.isArray(e?.zenpress_active_snippets)?e.zenpress_active_snippets:[],n=window?.zenpressSnippetsMeta||{},r={};Object.keys(n).forEach(e=>{r[e]={...n[e],"enable-snippet":t.includes(e)}}),s(r)}).catch(()=>{p((0,r.__)("Failed to load settings.","zenpress"))})},[p]),{snippets:e,setSnippets:s,saveSettings:async()=>{a(!0);const s=Object.keys(e).filter(s=>e[s]?.["enable-snippet"]);try{await o()({path:"/wp/v2/settings",method:"POST",data:{zenpress_active_snippets:s}}),i((0,r.__)("Settings saved.","zenpress"))}catch{p((0,r.__)("Failed to save settings.","zenpress"))}finally{a(!1)}},isSaving:t}})(),[h,x]=(0,n.useState)(),_=e=>{s(s=>{const t={};return Object.entries(s).forEach(([s,n])=>{const r=(Array.isArray(n?.preset)?n.preset:[]).includes(e);t[s]={...n,"enable-snippet":r}}),t})},m=e=>e?e.charAt(0).toUpperCase()+e.slice(1).toLowerCase():e,v=["core","gutenberg","woocommerce","ads-blocker","tools"],y={};Object.keys(e).forEach(s=>{const t=e[s],n=(t?.category||(0,r.__)("Uncategorized","zenpress")).toLowerCase(),a=(t?.subcategory||(0,r.__)("uncategorized","zenpress")).toLowerCase();y[n]||(y[n]={}),y[n][a]||(y[n][a]=[]),y[n][a].push({name:s,data:t})});const z=Object.keys(y).sort((e,s)=>{const t=v.indexOf(e.toLowerCase()),n=v.indexOf(s.toLowerCase());return-1!==t&&-1!==n?t-n:-1!==t?-1:-1!==n?1:e.localeCompare(s,void 0,{sensitivity:"base"})});return(0,n.useEffect)(()=>{!h&&z.length>0&&x(z[0])},[h,z]),(0,n.useEffect)(()=>{const e=e=>{(e.ctrlKey||e.metaKey)&&"s"===e.key&&(e.preventDefault(),i||t())};return document.addEventListener("keydown",e),()=>{document.removeEventListener("keydown",e)}},[t,i]),(0,p.jsxs)("article",{className:"zenpress-row",children:[(0,p.jsxs)("section",{className:"zenpress-main",children:[(0,p.jsx)("div",{className:"zenpress-notices",children:(0,p.jsx)(b,{})}),(0,p.jsxs)("div",{className:"zenpress-panel",children:[(0,p.jsxs)(f,{orientation:"vertical",selectedTabId:h,onSelect:e=>{x(e),x(e)},children:[(0,p.jsx)(f.TabList,{children:z.map(e=>{const s=`zenpress-tabs__tab--category-${e.toLowerCase().replace(/\s+/g,"-")}`;return(0,p.jsx)(f.Tab,{tabId:e,title:m(e),className:s,children:m(e)},e)})}),z.map(e=>{const t=Object.keys(y[e]).sort();return(0,p.jsxs)(f.TabPanel,{tabId:e,children:[(0,p.jsx)("h2",{children:m(e)}),t.map(t=>(0,p.jsxs)("div",{className:`zenpress-subcategory zenpress-subcategory-${t.toLowerCase().replace(/\s+/g,"-")}`,children:[(0,p.jsx)("hr",{}),(0,p.jsx)("h3",{children:m(t)}),y[e][t].map(({name:e,data:t})=>(0,p.jsx)(d,{label:t.title||e,value:t?.["enable-snippet"]||!1,onChange:()=>{return t=e,void s(e=>({...e,[t]:{...e[t],"enable-snippet":!e[t]?.["enable-snippet"]}}));var t},help:t.description||""},e))]},t))]},e)})]}),(0,p.jsxs)("div",{className:"zenpress-actions",children:[(0,p.jsxs)("div",{className:"zenpress-actions-bulk",children:[(0,p.jsx)(a.Button,{variant:"tertiary",onClick:()=>{s(e=>{const s={};return Object.keys(e).forEach(t=>{s[t]={...e[t],"enable-snippet":!0}}),s})},__next40pxDefaultSize:!0,children:(0,r.__)("Enable all actions","zenpress")}),(0,p.jsx)(a.Button,{isDestructive:!0,onClick:()=>{s(e=>{const s={};return Object.keys(e).forEach(t=>{s[t]={...e[t],"enable-snippet":!1}}),s})},__next40pxDefaultSize:!0,children:(0,r.__)("Disable all actions","zenpress")})]}),(0,p.jsx)(u,{onClick:t,isBusy:i})]})]})]}),(0,p.jsx)("aside",{className:"zenpress-sidebar",children:(0,p.jsx)("div",{className:"zenpress-presets",children:(0,p.jsxs)("div",{className:"zenpress-presets-description",children:[(0,p.jsx)("h2",{children:(0,r.__)("Pick configuration preset","zenpress")}),(0,p.jsx)("p",{children:(0,r.__)("Don't know which features to enable? Quickly configure ZenPress by selecting a preset that matches your site type. Each preset enables optimized features for your specific use case.","zenpress")}),(0,p.jsx)("hr",{}),(0,p.jsxs)("h3",{children:["🖼️ ",(0,r.__)("Corporate website","zenpress")]}),(0,p.jsx)("p",{children:(0,r.__)("Optimized for business sites and portfolios. Focuses on security, performance, and removing unnecessary features like RSS feeds and author archives.","zenpress")}),(0,p.jsx)(a.Button,{variant:"secondary",onClick:()=>_("corporate-website"),__next40pxDefaultSize:!0,children:(0,r.__)("Enable","zenpress")}),(0,p.jsx)("hr",{}),(0,p.jsxs)("h3",{children:[" 📰 ",(0,r.__)("Blog","zenpress")]}),(0,p.jsx)("p",{children:(0,r.__)("Tailored for content-focused blogs. Includes performance and security optimizations while preserving essential blog features like RSS feeds.","zenpress")}),(0,p.jsx)(a.Button,{variant:"secondary",onClick:()=>_("blog"),__next40pxDefaultSize:!0,children:(0,r.__)("Enable","zenpress")}),(0,p.jsx)("hr",{}),(0,p.jsxs)("h3",{children:["🛒 ",(0,r.__)("E-commerce","zenpress")]}),(0,p.jsx)("p",{children:(0,r.__)("Designed for WooCommerce stores. Includes all performance and security features plus WooCommerce-specific optimizations for faster checkout.","zenpress")}),(0,p.jsx)(a.Button,{variant:"secondary",onClick:()=>_("ecommerce"),__next40pxDefaultSize:!0,children:(0,r.__)("Enable","zenpress")})]})})})]})};t()(()=>{const e=document.getElementById("zenpress-settings");e&&(0,n.createRoot)(e).render((0,p.jsx)(x,{}))})})(); -
zenpress/tags/2.2.0/assets/src/index.js
r3412245 r3448585 8 8 */ 9 9 domReady(() => { 10 const rootEl = document.getElementById('zenpress-settings'); 11 if (!rootEl) return; 10 const rootEl = document.getElementById('zenpress-settings'); 11 if (!rootEl) { 12 return; 13 } 12 14 13 const root = createRoot(rootEl);14 root.render(<SettingsPage />);15 const root = createRoot(rootEl); 16 root.render(<SettingsPage />); 15 17 }); -
zenpress/tags/2.2.0/assets/src/js/components/Notices.js
r3412245 r3448585 9 9 */ 10 10 export const Notices = () => { 11 const { removeNotice } = useDispatch(noticesStore); 12 const notices = useSelect((select) => select(noticesStore).getNotices(), []); 11 const { removeNotice } = useDispatch(noticesStore); 12 const notices = useSelect( 13 (select) => select(noticesStore).getNotices(), 14 [] 15 ); 13 16 14 if (!notices || notices.length === 0) {15 return null;16 }17 if (!notices || notices.length === 0) { 18 return null; 19 } 17 20 18 return <NoticeList notices={notices} onRemove={removeNotice} />;21 return <NoticeList notices={notices} onRemove={removeNotice} />; 19 22 }; -
zenpress/tags/2.2.0/assets/src/js/components/SaveButton.js
r3412245 r3448585 11 11 */ 12 12 export const SaveButton = ({ onClick, isBusy }) => { 13 return ( 14 <Button variant="primary" onClick={onClick} isBusy={isBusy} __next40pxDefaultSize> 15 {__('Save settings', 'zenpress')} 16 </Button> 17 ); 13 return ( 14 <Button 15 variant="primary" 16 onClick={onClick} 17 isBusy={isBusy} 18 __next40pxDefaultSize 19 > 20 {__('Save settings', 'zenpress')} 21 </Button> 22 ); 18 23 }; -
zenpress/tags/2.2.0/assets/src/js/components/SnippetToggleControl.js
r3412245 r3448585 7 7 * 8 8 * @param {HTMLElement} container - Container element to attach listener to. 9 * @param {Function} onChange- Change handler to call when Enter is pressed.9 * @param {Function} onChange - Change handler to call when Enter is pressed. 10 10 */ 11 11 const addEnterKeySupport = (container, onChange) => { 12 if (!container) return; 12 if (!container) { 13 return; 14 } 13 15 14 const handleKeyDown = (e) => { 15 // Handle Enter key on the toggle control 16 if (e.key === 'Enter') { 17 const toggleInput = container.querySelector('input[type="checkbox"]'); 18 if (toggleInput && (document.activeElement === toggleInput || container.contains(document.activeElement))) { 19 e.preventDefault(); 20 e.stopPropagation(); 21 onChange(); 22 } 23 } 24 }; 16 const handleKeyDown = (e) => { 17 // Handle Enter key on the toggle control 18 if (e.key === 'Enter') { 19 const toggleInput = container.querySelector( 20 'input[type="checkbox"]' 21 ); 22 const ownerDocument = container.ownerDocument || document; 23 if ( 24 toggleInput && 25 (ownerDocument.activeElement === toggleInput || 26 container.contains(ownerDocument.activeElement)) 27 ) { 28 e.preventDefault(); 29 e.stopPropagation(); 30 onChange(); 31 } 32 } 33 }; 25 34 26 container.addEventListener('keydown', handleKeyDown);27 return () => {28 container.removeEventListener('keydown', handleKeyDown);29 };35 container.addEventListener('keydown', handleKeyDown); 36 return () => { 37 container.removeEventListener('keydown', handleKeyDown); 38 }; 30 39 }; 31 40 … … 44 53 */ 45 54 export const SnippetToggleControl = ({ label, value, onChange, help }) => { 46 const containerRef = useRef(null);55 const containerRef = useRef(null); 47 56 48 // Temporary support for Enter key handling49 useEffect(() => {50 return addEnterKeySupport(containerRef.current, onChange);51 }, [onChange]);57 // Temporary support for Enter key handling 58 useEffect(() => { 59 return addEnterKeySupport(containerRef.current, onChange); 60 }, [onChange]); 52 61 53 return ( 54 <div ref={containerRef}> 55 <ToggleControl label={label} checked={value} onChange={onChange} help={help} __nextHasNoMarginBottom /> 56 </div> 57 ); 62 return ( 63 <div ref={containerRef}> 64 <ToggleControl 65 label={label} 66 checked={value} 67 onChange={onChange} 68 help={help} 69 __nextHasNoMarginBottom 70 /> 71 </div> 72 ); 58 73 }; -
zenpress/tags/2.2.0/assets/src/js/components/Tabs.js
r3412245 r3448585 1 import { useState, createContext, useContext, useRef, useEffect } from '@wordpress/element'; 1 import { 2 useState, 3 createContext, 4 useContext, 5 useRef, 6 useEffect, 7 } from '@wordpress/element'; 2 8 3 9 const TabsContext = createContext(); … … 6 12 * Custom Tabs component with vertical orientation support 7 13 * 8 * @param {Object} props - Component props.9 * @param {string} props.selectedTabId - Currently selected tab ID.10 * @param {Function} props.onSelect - Callback when tab is selected.11 * @param {string} props.orientation - Tab orientation ('vertical' or 'horizontal').12 * @param {Object} props.children - Child components (TabList and TabPanels).14 * @param {Object} props - Component props. 15 * @param {string} props.selectedTabId - Currently selected tab ID. 16 * @param {Function} props.onSelect - Callback when tab is selected. 17 * @param {string} props.orientation - Tab orientation ('vertical' or 'horizontal'). 18 * @param {Object} props.children - Child components (TabList and TabPanels). 13 19 * @return {JSX.Element} The tabs container. 14 20 */ 15 export const Tabs = ({ selectedTabId: controlledId, onSelect, orientation = 'horizontal', children }) => { 16 const [internalId, setInternalId] = useState(); 17 const tabListRef = useRef(null); 18 const selectedId = controlledId !== undefined ? controlledId : internalId; 19 const handleSelect = (id) => { 20 if (controlledId === undefined) setInternalId(id); 21 onSelect?.(id); 22 }; 23 24 // Function to get ordered tab IDs from DOM 25 const getOrderedTabIds = () => { 26 if (!tabListRef.current) return []; 27 28 const tabs = Array.from(tabListRef.current.querySelectorAll('[role="tab"]')); 29 return tabs 30 .map((tab) => { 31 const id = tab.getAttribute('id'); 32 return id ? id.replace('zenpress-tab-', '') : null; 33 }) 34 .filter(Boolean); 35 }; 36 37 return ( 38 <TabsContext.Provider 39 value={{ selectedTabId: selectedId, onSelect: handleSelect, orientation, getOrderedTabIds, tabListRef }} 40 > 41 <div className={`zenpress-tabs zenpress-tabs--${orientation}`}>{children}</div> 42 </TabsContext.Provider> 43 ); 21 export const Tabs = ({ 22 selectedTabId: controlledId, 23 onSelect, 24 orientation = 'horizontal', 25 children, 26 }) => { 27 const [internalId, setInternalId] = useState(); 28 const tabListRef = useRef(null); 29 const selectedId = controlledId !== undefined ? controlledId : internalId; 30 const handleSelect = (id) => { 31 if (controlledId === undefined) { 32 setInternalId(id); 33 } 34 onSelect?.(id); 35 }; 36 37 // Function to get ordered tab IDs from DOM 38 const getOrderedTabIds = () => { 39 if (!tabListRef.current) { 40 return []; 41 } 42 43 const tabs = Array.from( 44 tabListRef.current.querySelectorAll('[role="tab"]') 45 ); 46 return tabs 47 .map((tab) => { 48 const id = tab.getAttribute('id'); 49 return id ? id.replace('zenpress-tab-', '') : null; 50 }) 51 .filter(Boolean); 52 }; 53 54 return ( 55 <TabsContext.Provider 56 value={{ 57 selectedTabId: selectedId, 58 onSelect: handleSelect, 59 orientation, 60 getOrderedTabIds, 61 tabListRef, 62 }} 63 > 64 <div className={`zenpress-tabs zenpress-tabs--${orientation}`}> 65 {children} 66 </div> 67 </TabsContext.Provider> 68 ); 44 69 }; 45 70 … … 47 72 * TabList component - container for Tab components 48 73 * 49 * @param {Object} props - Component props.74 * @param {Object} props - Component props. 50 75 * @param {Object} props.children - Tab components. 51 76 * @return {JSX.Element} The tab list container. 52 77 */ 53 78 export const TabList = ({ children }) => { 54 const { orientation, tabListRef } = useContext(TabsContext);55 56 return (57 <div58 ref={tabListRef}59 className={`zenpress-tabs__list zenpress-tabs__list--${orientation}`}60 role="tablist"61 aria-orientation={orientation}62 >63 {children}64 </div>65 );79 const { orientation, tabListRef } = useContext(TabsContext); 80 81 return ( 82 <div 83 ref={tabListRef} 84 className={`zenpress-tabs__list zenpress-tabs__list--${orientation}`} 85 role="tablist" 86 aria-orientation={orientation} 87 > 88 {children} 89 </div> 90 ); 66 91 }; 67 92 … … 69 94 * Tab component - individual tab button 70 95 * 71 * @param {Object} props- Component props.72 * @param {string} props.tabId- Unique identifier for the tab.73 * @param {string} props.title- Tab title (optional, uses children if not provided).74 * @param {string} props.className - Additional CSS class name.75 * @param {Object} props.children- Tab content.96 * @param {Object} props - Component props. 97 * @param {string} props.tabId - Unique identifier for the tab. 98 * @param {string} props.title - Tab title (optional, uses children if not provided). 99 * @param {string} props.className - Additional CSS class name. 100 * @param {Object} props.children - Tab content. 76 101 * @return {JSX.Element} The tab button. 77 102 */ 78 103 export const Tab = ({ tabId, title, className = '', children }) => { 79 const { selectedTabId, onSelect, orientation, getOrderedTabIds } = useContext(TabsContext); 80 const isSelected = selectedTabId === tabId; 81 const tabRef = useRef(null); 82 83 // Handle keyboard navigation according to W3C ARIA pattern 84 const handleKeyDown = (e) => { 85 const tabIds = getOrderedTabIds(); 86 if (!tabIds || tabIds.length === 0) return; 87 88 const currentIndex = tabIds.indexOf(tabId); 89 if (currentIndex === -1) return; 90 91 let targetIndex = currentIndex; 92 93 // Handle arrow keys based on orientation 94 if (orientation === 'vertical') { 95 if (e.key === 'ArrowDown') { 96 e.preventDefault(); 97 targetIndex = currentIndex < tabIds.length - 1 ? currentIndex + 1 : 0; 98 } else if (e.key === 'ArrowUp') { 99 e.preventDefault(); 100 targetIndex = currentIndex > 0 ? currentIndex - 1 : tabIds.length - 1; 101 } 102 } else { 103 // Horizontal orientation 104 if (e.key === 'ArrowRight') { 105 e.preventDefault(); 106 targetIndex = currentIndex < tabIds.length - 1 ? currentIndex + 1 : 0; 107 } else if (e.key === 'ArrowLeft') { 108 e.preventDefault(); 109 targetIndex = currentIndex > 0 ? currentIndex - 1 : tabIds.length - 1; 110 } 111 } 112 113 // Handle Home and End keys 114 if (e.key === 'Home') { 115 e.preventDefault(); 116 targetIndex = 0; 117 } else if (e.key === 'End') { 118 e.preventDefault(); 119 targetIndex = tabIds.length - 1; 120 } 121 122 // Handle Space and Enter for activation (if not auto-activated) 123 if (e.key === ' ' || e.key === 'Enter') { 124 e.preventDefault(); 125 onSelect(tabId); 126 return; 127 } 128 129 // Move focus to target tab if index changed 130 if (targetIndex !== currentIndex && targetIndex >= 0 && targetIndex < tabIds.length) { 131 const targetTabId = tabIds[targetIndex]; 132 const targetTabElement = document.getElementById(`zenpress-tab-${targetTabId}`); 133 if (targetTabElement) { 134 targetTabElement.focus(); 135 // Auto-activate on focus (recommended by W3C for better UX) 136 onSelect(targetTabId); 137 } 138 } 139 }; 140 141 // Auto-activate tab when it receives focus (recommended by W3C) 142 const handleFocus = () => { 143 if (!isSelected) { 144 onSelect(tabId); 145 } 146 }; 147 148 return ( 149 <button 150 ref={tabRef} 151 className={`zenpress-tabs__tab ${isSelected ? 'zenpress-tabs__tab--is-active' : ''} ${className}`.trim()} 152 role="tab" 153 aria-selected={isSelected} 154 aria-controls={`zenpress-tab-panel-${tabId}`} 155 id={`zenpress-tab-${tabId}`} 156 tabIndex={isSelected ? 0 : -1} 157 onClick={() => onSelect(tabId)} 158 onKeyDown={handleKeyDown} 159 onFocus={handleFocus} 160 > 161 {title || children} 162 </button> 163 ); 104 const { selectedTabId, onSelect, orientation, getOrderedTabIds } = 105 useContext(TabsContext); 106 const isSelected = selectedTabId === tabId; 107 const tabRef = useRef(null); 108 109 // Handle keyboard navigation according to W3C ARIA pattern 110 const handleKeyDown = (e) => { 111 const tabIds = getOrderedTabIds(); 112 if (!tabIds || tabIds.length === 0) { 113 return; 114 } 115 116 const currentIndex = tabIds.indexOf(tabId); 117 if (currentIndex === -1) { 118 return; 119 } 120 121 let targetIndex = currentIndex; 122 123 // Handle arrow keys based on orientation 124 if (orientation === 'vertical') { 125 if (e.key === 'ArrowDown') { 126 e.preventDefault(); 127 targetIndex = 128 currentIndex < tabIds.length - 1 ? currentIndex + 1 : 0; 129 } else if (e.key === 'ArrowUp') { 130 e.preventDefault(); 131 targetIndex = 132 currentIndex > 0 ? currentIndex - 1 : tabIds.length - 1; 133 } 134 } else if (e.key === 'ArrowRight') { 135 // Horizontal orientation 136 e.preventDefault(); 137 targetIndex = 138 currentIndex < tabIds.length - 1 ? currentIndex + 1 : 0; 139 } else if (e.key === 'ArrowLeft') { 140 // Horizontal orientation 141 e.preventDefault(); 142 targetIndex = 143 currentIndex > 0 ? currentIndex - 1 : tabIds.length - 1; 144 } 145 146 // Handle Home and End keys 147 if (e.key === 'Home') { 148 e.preventDefault(); 149 targetIndex = 0; 150 } else if (e.key === 'End') { 151 e.preventDefault(); 152 targetIndex = tabIds.length - 1; 153 } 154 155 // Handle Space and Enter for activation (if not auto-activated) 156 if (e.key === ' ' || e.key === 'Enter') { 157 e.preventDefault(); 158 onSelect(tabId); 159 return; 160 } 161 162 // Move focus to target tab if index changed 163 if ( 164 targetIndex !== currentIndex && 165 targetIndex >= 0 && 166 targetIndex < tabIds.length 167 ) { 168 const targetTabId = tabIds[targetIndex]; 169 const targetTabElement = document.getElementById( 170 `zenpress-tab-${targetTabId}` 171 ); 172 if (targetTabElement) { 173 targetTabElement.focus(); 174 // Auto-activate on focus (recommended by W3C for better UX) 175 onSelect(targetTabId); 176 } 177 } 178 }; 179 180 // Auto-activate tab when it receives focus (recommended by W3C) 181 const handleFocus = () => { 182 if (!isSelected) { 183 onSelect(tabId); 184 } 185 }; 186 187 return ( 188 <button 189 ref={tabRef} 190 className={`zenpress-tabs__tab ${isSelected ? 'zenpress-tabs__tab--is-active' : ''} ${className}`.trim()} 191 role="tab" 192 aria-selected={isSelected} 193 aria-controls={`zenpress-tab-panel-${tabId}`} 194 id={`zenpress-tab-${tabId}`} 195 tabIndex={isSelected ? 0 : -1} 196 onClick={() => onSelect(tabId)} 197 onKeyDown={handleKeyDown} 198 onFocus={handleFocus} 199 > 200 {title || children} 201 </button> 202 ); 164 203 }; 165 204 … … 167 206 * TabPanel component - container for tab content 168 207 * 169 * @param {Object} props - Component props.170 * @param {string} props.tabId - Unique identifier matching a Tab's tabId.171 * @param {Object} props.children - Panel content.208 * @param {Object} props - Component props. 209 * @param {string} props.tabId - Unique identifier matching a Tab's tabId. 210 * @param {Object} props.children - Panel content. 172 211 * @return {JSX.Element|null} The tab panel or null if not selected. 173 212 */ 174 213 export const TabPanel = ({ tabId, children }) => { 175 const { selectedTabId } = useContext(TabsContext);176 const panelRef = useRef(null);177 const isSelected = selectedTabId === tabId;178 179 // Check if panel contains focusable elements180 useEffect(() => {181 if (isSelected && panelRef.current) {182 const focusableElements = panelRef.current.querySelectorAll(183 'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"]), input:not([disabled]), select:not([disabled]), textarea:not([disabled])'184 );185 186 // If no focusable elements, make the panel itself focusable187 if (focusableElements.length === 0) {188 panelRef.current.setAttribute('tabindex', '0');189 } else {190 panelRef.current.removeAttribute('tabindex');191 }192 }193 }, [isSelected]);194 195 if (!isSelected) {196 return (197 <div198 className="zenpress-tabs__panel"199 role="tabpanel"200 id={`zenpress-tab-panel-${tabId}`}201 aria-labelledby={`zenpress-tab-${tabId}`}202 hidden203 >204 {children}205 </div>206 );207 }208 209 return (210 <div211 ref={panelRef}212 className="zenpress-tabs__panel"213 role="tabpanel"214 id={`zenpress-tab-panel-${tabId}`}215 aria-labelledby={`zenpress-tab-${tabId}`}216 >217 {children}218 </div>219 );214 const { selectedTabId } = useContext(TabsContext); 215 const panelRef = useRef(null); 216 const isSelected = selectedTabId === tabId; 217 218 // Check if panel contains focusable elements 219 useEffect(() => { 220 if (isSelected && panelRef.current) { 221 const focusableElements = panelRef.current.querySelectorAll( 222 'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"]), input:not([disabled]), select:not([disabled]), textarea:not([disabled])' 223 ); 224 225 // If no focusable elements, make the panel itself focusable 226 if (focusableElements.length === 0) { 227 panelRef.current.setAttribute('tabindex', '0'); 228 } else { 229 panelRef.current.removeAttribute('tabindex'); 230 } 231 } 232 }, [isSelected]); 233 234 if (!isSelected) { 235 return ( 236 <div 237 className="zenpress-tabs__panel" 238 role="tabpanel" 239 id={`zenpress-tab-panel-${tabId}`} 240 aria-labelledby={`zenpress-tab-${tabId}`} 241 hidden 242 > 243 {children} 244 </div> 245 ); 246 } 247 248 return ( 249 <div 250 ref={panelRef} 251 className="zenpress-tabs__panel" 252 role="tabpanel" 253 id={`zenpress-tab-panel-${tabId}`} 254 aria-labelledby={`zenpress-tab-${tabId}`} 255 > 256 {children} 257 </div> 258 ); 220 259 }; 221 260 -
zenpress/tags/2.2.0/assets/src/js/hooks/useSettings.js
r3412245 r3448585 15 15 */ 16 16 export const useSettings = () => { 17 const [snippets, setSnippets] = useState({}); 18 const [isSaving, setIsSaving] = useState(false); 19 const { createSuccessNotice, createErrorNotice } = useDispatch(noticesStore); 17 const [snippets, setSnippets] = useState({}); 18 const [isSaving, setIsSaving] = useState(false); 19 const { createSuccessNotice, createErrorNotice } = 20 useDispatch(noticesStore); 20 21 21 useEffect(() => {22 apiFetch({ path: '/wp/v2/settings' })23 .then((settings) => {24 const active = Array.isArray(settings?.zenpress_active_snippets)25 ? settings.zenpress_active_snippets26 : [];22 useEffect(() => { 23 apiFetch({ path: '/wp/v2/settings' }) 24 .then((settings) => { 25 const active = Array.isArray(settings?.zenpress_active_snippets) 26 ? settings.zenpress_active_snippets 27 : []; 27 28 28 const meta = window?.zenpressSnippetsMeta || {};29 const snippetsData = {};29 const meta = window?.zenpressSnippetsMeta || {}; 30 const snippetsData = {}; 30 31 31 Object.keys(meta).forEach((name) => {32 snippetsData[name] = {33 ...meta[name],34 'enable-snippet': active.includes(name),35 };36 });32 Object.keys(meta).forEach((name) => { 33 snippetsData[name] = { 34 ...meta[name], 35 'enable-snippet': active.includes(name), 36 }; 37 }); 37 38 38 setSnippets(snippetsData);39 })40 .catch(() => {41 createErrorNotice(__('Failed to load settings.', 'zenpress'));42 });43 }, [createErrorNotice]);39 setSnippets(snippetsData); 40 }) 41 .catch(() => { 42 createErrorNotice(__('Failed to load settings.', 'zenpress')); 43 }); 44 }, [createErrorNotice]); 44 45 45 const saveSettings = async () => {46 setIsSaving(true);46 const saveSettings = async () => { 47 setIsSaving(true); 47 48 48 const active = Object.keys(snippets).filter((name) => snippets[name]?.['enable-snippet']); 49 const active = Object.keys(snippets).filter( 50 (name) => snippets[name]?.['enable-snippet'] 51 ); 49 52 50 try {51 await apiFetch({52 path: '/wp/v2/settings',53 method: 'POST',54 data: { zenpress_active_snippets: active },55 });56 createSuccessNotice(__('Settings saved.', 'zenpress'));57 } catch {58 createErrorNotice(__('Failed to save settings.', 'zenpress'));59 } finally {60 setIsSaving(false);61 }62 };53 try { 54 await apiFetch({ 55 path: '/wp/v2/settings', 56 method: 'POST', 57 data: { zenpress_active_snippets: active }, 58 }); 59 createSuccessNotice(__('Settings saved.', 'zenpress')); 60 } catch { 61 createErrorNotice(__('Failed to save settings.', 'zenpress')); 62 } finally { 63 setIsSaving(false); 64 } 65 }; 63 66 64 return { snippets, setSnippets, saveSettings, isSaving };67 return { snippets, setSnippets, saveSettings, isSaving }; 65 68 }; -
zenpress/tags/2.2.0/assets/src/js/pages/SettingsPage.js
r3412245 r3448585 1 import { useState, useEffect , useRef} from '@wordpress/element';1 import { useState, useEffect } from '@wordpress/element'; 2 2 import { __ } from '@wordpress/i18n'; 3 3 import { Button } from '@wordpress/components'; … … 14 14 */ 15 15 export const SettingsPage = () => { 16 const { snippets, setSnippets, saveSettings, isSaving } = useSettings(); 17 const [selectedTabId, setSelectedTabId] = useState(); 18 19 const handleToggleChange = (snippetName) => { 20 setSnippets((prev) => ({ 21 ...prev, 22 [snippetName]: { 23 ...prev[snippetName], 24 'enable-snippet': !prev[snippetName]?.['enable-snippet'], 25 }, 26 })); 27 }; 28 29 const enableAllSnippets = () => { 30 setSnippets((prev) => { 31 const updated = {}; 32 Object.keys(prev).forEach((name) => { 33 updated[name] = { ...prev[name], 'enable-snippet': true }; 34 }); 35 return updated; 36 }); 37 }; 38 39 const resetSettings = () => { 40 setSnippets((prev) => { 41 const updated = {}; 42 Object.keys(prev).forEach((name) => { 43 updated[name] = { ...prev[name], 'enable-snippet': false }; 44 }); 45 return updated; 46 }); 47 }; 48 49 const enableByPreset = (preset) => { 50 setSnippets((prev) => { 51 const updated = {}; 52 Object.entries(prev).forEach(([name, data]) => { 53 const presets = Array.isArray(data?.preset) ? data.preset : []; 54 const isEnabled = presets.includes(preset); 55 updated[name] = { ...data, 'enable-snippet': isEnabled }; 56 }); 57 return updated; 58 }); 59 }; 60 61 // Helper function to capitalize category name 62 const capitalizeCategory = (category) => { 63 if (!category) return category; 64 return category.charAt(0).toUpperCase() + category.slice(1).toLowerCase(); 65 }; 66 67 // Category order: Core, Gutenberg, WooCommerce, ads-blocker, Tools 68 // Using English lowercase values for comparison since categories are stored in lowercase 69 const categoryOrder = ['core', 'gutenberg', 'woocommerce', 'ads-blocker', 'tools']; 70 71 // Group snippets by category, then by subcategory 72 const groupedSnippets = {}; 73 Object.keys(snippets).forEach((snippetName) => { 74 const snippet = snippets[snippetName]; 75 const category = (snippet?.category || __('Uncategorized', 'zenpress')).toLowerCase(); 76 const subcategory = (snippet?.subcategory || __('uncategorized', 'zenpress')).toLowerCase(); 77 78 if (!groupedSnippets[category]) { 79 groupedSnippets[category] = {}; 80 } 81 if (!groupedSnippets[category][subcategory]) { 82 groupedSnippets[category][subcategory] = []; 83 } 84 groupedSnippets[category][subcategory].push({ name: snippetName, data: snippet }); 85 }); 86 87 // Sort categories according to the specified order 88 const sortedCategories = Object.keys(groupedSnippets).sort((a, b) => { 89 const indexA = categoryOrder.indexOf(a.toLowerCase()); 90 const indexB = categoryOrder.indexOf(b.toLowerCase()); 91 92 // If both are in the order array, sort by their position 93 if (indexA !== -1 && indexB !== -1) { 94 return indexA - indexB; 95 } 96 // If only A is in the order array, A comes first 97 if (indexA !== -1) return -1; 98 // If only B is in the order array, B comes first 99 if (indexB !== -1) return 1; 100 // If neither is in the order array, sort alphabetically 101 return a.localeCompare(b, undefined, { sensitivity: 'base' }); 102 }); 103 104 // Set initial selected tab if none is selected 105 useEffect(() => { 106 if (!selectedTabId && sortedCategories.length > 0) { 107 setSelectedTabId(sortedCategories[0]); 108 } 109 }, [selectedTabId, sortedCategories.length]); 110 111 const onSelect = (tabName) => { 112 setSelectedTabId(tabName); 113 }; 114 115 // Add keyboard shortcuts and ensure toggles are keyboard accessible 116 useEffect(() => { 117 const handleKeyDown = (e) => { 118 // Ctrl+S or Cmd+S to save 119 if ((e.ctrlKey || e.metaKey) && e.key === 's') { 120 e.preventDefault(); 121 if (!isSaving) { 122 saveSettings(); 123 } 124 } 125 }; 126 127 document.addEventListener('keydown', handleKeyDown); 128 return () => { 129 document.removeEventListener('keydown', handleKeyDown); 130 }; 131 }, [saveSettings, isSaving]); 132 133 return ( 134 <article className="zenpress-row"> 135 <section className="zenpress-main"> 136 <div className="zenpress-notices"> 137 <Notices /> 138 </div> 139 <div className="zenpress-panel"> 140 <Tabs 141 orientation="vertical" 142 selectedTabId={selectedTabId} 143 onSelect={(selectedId) => { 144 setSelectedTabId(selectedId); 145 onSelect(selectedId); 146 }} 147 > 148 <Tabs.TabList> 149 {sortedCategories.map((category) => { 150 const categoryClass = `zenpress-tabs__tab--category-${category.toLowerCase().replace(/\s+/g, '-')}`; 151 return ( 152 <Tabs.Tab 153 key={category} 154 tabId={category} 155 title={capitalizeCategory(category)} 156 className={categoryClass} 157 > 158 {capitalizeCategory(category)} 159 </Tabs.Tab> 160 ); 161 })} 162 </Tabs.TabList> 163 {sortedCategories.map((category) => { 164 const subcategories = Object.keys(groupedSnippets[category]).sort(); 165 return ( 166 <Tabs.TabPanel key={category} tabId={category}> 167 <h2>{capitalizeCategory(category)}</h2> 168 {subcategories.map((subcategory) => ( 169 <div 170 key={subcategory} 171 className={`zenpress-subcategory zenpress-subcategory-${subcategory.toLowerCase().replace(/\s+/g, '-')}`} 172 > 173 <hr /> 174 <h3>{capitalizeCategory(subcategory)}</h3> 175 {groupedSnippets[category][subcategory].map(({ name, data }) => ( 176 <SnippetToggleControl 177 key={name} 178 label={data.title || name} 179 value={data?.['enable-snippet'] || false} 180 onChange={() => handleToggleChange(name)} 181 help={data.description || ''} 182 /> 183 ))} 184 </div> 185 ))} 186 </Tabs.TabPanel> 187 ); 188 })} 189 </Tabs> 190 <div className="zenpress-actions"> 191 <div className="zenpress-actions-bulk"> 192 <Button variant="tertiary" onClick={enableAllSnippets} __next40pxDefaultSize> 193 {__('Enable all actions', 'zenpress')} 194 </Button> 195 <Button isDestructive onClick={resetSettings} __next40pxDefaultSize> 196 {__('Disable all actions', 'zenpress')} 197 </Button> 198 </div> 199 <SaveButton onClick={saveSettings} isBusy={isSaving} /> 200 </div> 201 </div> 202 </section> 203 <aside className="zenpress-sidebar"> 204 <div className="zenpress-presets"> 205 <div className="zenpress-presets-description"> 206 <h2>{__('Pick configuration preset', 'zenpress')}</h2> 207 <p> 208 {__( 209 "Don't know which features to enable? Quickly configure ZenPress by selecting a preset that matches your site type. Each preset enables optimized features for your specific use case.", 210 'zenpress' 211 )} 212 </p> 213 <hr /> 214 <h3>🖼️ {__('Corporate website', 'zenpress')}</h3> 215 <p> 216 {__( 217 'Optimized for business sites and portfolios. Focuses on security, performance, and removing unnecessary features like RSS feeds and author archives.', 218 'zenpress' 219 )} 220 </p> 221 <Button 222 variant="secondary" 223 onClick={() => enableByPreset('corporate-website')} 224 __next40pxDefaultSize 225 > 226 {__('Enable', 'zenpress')} 227 </Button> 228 <hr /> 229 <h3> 📰 {__('Blog', 'zenpress')}</h3> 230 <p> 231 {__( 232 'Tailored for content-focused blogs. Includes performance and security optimizations while preserving essential blog features like RSS feeds.', 233 'zenpress' 234 )} 235 </p> 236 <Button variant="secondary" onClick={() => enableByPreset('blog')} __next40pxDefaultSize> 237 {__('Enable', 'zenpress')} 238 </Button> 239 <hr /> 240 <h3>🛒 {__('E-commerce', 'zenpress')}</h3> 241 <p> 242 {__( 243 'Designed for WooCommerce stores. Includes all performance and security features plus WooCommerce-specific optimizations for faster checkout.', 244 'zenpress' 245 )} 246 </p> 247 <Button variant="secondary" onClick={() => enableByPreset('ecommerce')} __next40pxDefaultSize> 248 {__('Enable', 'zenpress')} 249 </Button> 250 </div> 251 </div> 252 </aside> 253 </article> 254 ); 16 const { snippets, setSnippets, saveSettings, isSaving } = useSettings(); 17 const [selectedTabId, setSelectedTabId] = useState(); 18 19 const handleToggleChange = (snippetName) => { 20 setSnippets((prev) => ({ 21 ...prev, 22 [snippetName]: { 23 ...prev[snippetName], 24 'enable-snippet': !prev[snippetName]?.['enable-snippet'], 25 }, 26 })); 27 }; 28 29 const enableAllSnippets = () => { 30 setSnippets((prev) => { 31 const updated = {}; 32 Object.keys(prev).forEach((name) => { 33 updated[name] = { ...prev[name], 'enable-snippet': true }; 34 }); 35 return updated; 36 }); 37 }; 38 39 const resetSettings = () => { 40 setSnippets((prev) => { 41 const updated = {}; 42 Object.keys(prev).forEach((name) => { 43 updated[name] = { ...prev[name], 'enable-snippet': false }; 44 }); 45 return updated; 46 }); 47 }; 48 49 const enableByPreset = (preset) => { 50 setSnippets((prev) => { 51 const updated = {}; 52 Object.entries(prev).forEach(([name, data]) => { 53 const presets = Array.isArray(data?.preset) ? data.preset : []; 54 const isEnabled = presets.includes(preset); 55 updated[name] = { ...data, 'enable-snippet': isEnabled }; 56 }); 57 return updated; 58 }); 59 }; 60 61 // Helper function to capitalize category name 62 const capitalizeCategory = (category) => { 63 if (!category) { 64 return category; 65 } 66 return ( 67 category.charAt(0).toUpperCase() + category.slice(1).toLowerCase() 68 ); 69 }; 70 71 // Category order: Core, Gutenberg, WooCommerce, ads-blocker, Tools 72 // Using English lowercase values for comparison since categories are stored in lowercase 73 const categoryOrder = [ 74 'core', 75 'gutenberg', 76 'woocommerce', 77 'ads-blocker', 78 'tools', 79 ]; 80 81 // Group snippets by category, then by subcategory 82 const groupedSnippets = {}; 83 Object.keys(snippets).forEach((snippetName) => { 84 const snippet = snippets[snippetName]; 85 const category = ( 86 snippet?.category || __('Uncategorized', 'zenpress') 87 ).toLowerCase(); 88 const subcategory = ( 89 snippet?.subcategory || __('uncategorized', 'zenpress') 90 ).toLowerCase(); 91 92 if (!groupedSnippets[category]) { 93 groupedSnippets[category] = {}; 94 } 95 if (!groupedSnippets[category][subcategory]) { 96 groupedSnippets[category][subcategory] = []; 97 } 98 groupedSnippets[category][subcategory].push({ 99 name: snippetName, 100 data: snippet, 101 }); 102 }); 103 104 // Sort categories according to the specified order 105 const sortedCategories = Object.keys(groupedSnippets).sort((a, b) => { 106 const indexA = categoryOrder.indexOf(a.toLowerCase()); 107 const indexB = categoryOrder.indexOf(b.toLowerCase()); 108 109 // If both are in the order array, sort by their position 110 if (indexA !== -1 && indexB !== -1) { 111 return indexA - indexB; 112 } 113 // If only A is in the order array, A comes first 114 if (indexA !== -1) { 115 return -1; 116 } 117 // If only B is in the order array, B comes first 118 if (indexB !== -1) { 119 return 1; 120 } 121 // If neither is in the order array, sort alphabetically 122 return a.localeCompare(b, undefined, { sensitivity: 'base' }); 123 }); 124 125 // Set initial selected tab if none is selected 126 useEffect(() => { 127 if (!selectedTabId && sortedCategories.length > 0) { 128 setSelectedTabId(sortedCategories[0]); 129 } 130 }, [selectedTabId, sortedCategories]); 131 132 const onSelect = (tabName) => { 133 setSelectedTabId(tabName); 134 }; 135 136 // Add keyboard shortcuts and ensure toggles are keyboard accessible 137 useEffect(() => { 138 const handleKeyDown = (e) => { 139 // Ctrl+S or Cmd+S to save 140 if ((e.ctrlKey || e.metaKey) && e.key === 's') { 141 e.preventDefault(); 142 if (!isSaving) { 143 saveSettings(); 144 } 145 } 146 }; 147 148 document.addEventListener('keydown', handleKeyDown); 149 return () => { 150 document.removeEventListener('keydown', handleKeyDown); 151 }; 152 }, [saveSettings, isSaving]); 153 154 return ( 155 <article className="zenpress-row"> 156 <section className="zenpress-main"> 157 <div className="zenpress-notices"> 158 <Notices /> 159 </div> 160 <div className="zenpress-panel"> 161 <Tabs 162 orientation="vertical" 163 selectedTabId={selectedTabId} 164 onSelect={(selectedId) => { 165 setSelectedTabId(selectedId); 166 onSelect(selectedId); 167 }} 168 > 169 <Tabs.TabList> 170 {sortedCategories.map((category) => { 171 const categoryClass = `zenpress-tabs__tab--category-${category.toLowerCase().replace(/\s+/g, '-')}`; 172 return ( 173 <Tabs.Tab 174 key={category} 175 tabId={category} 176 title={capitalizeCategory(category)} 177 className={categoryClass} 178 > 179 {capitalizeCategory(category)} 180 </Tabs.Tab> 181 ); 182 })} 183 </Tabs.TabList> 184 {sortedCategories.map((category) => { 185 const subcategories = Object.keys( 186 groupedSnippets[category] 187 ).sort(); 188 return ( 189 <Tabs.TabPanel key={category} tabId={category}> 190 <h2>{capitalizeCategory(category)}</h2> 191 {subcategories.map((subcategory) => ( 192 <div 193 key={subcategory} 194 className={`zenpress-subcategory zenpress-subcategory-${subcategory.toLowerCase().replace(/\s+/g, '-')}`} 195 > 196 <hr /> 197 <h3> 198 {capitalizeCategory( 199 subcategory 200 )} 201 </h3> 202 {groupedSnippets[category][ 203 subcategory 204 ].map(({ name, data }) => ( 205 <SnippetToggleControl 206 key={name} 207 label={data.title || name} 208 value={ 209 data?.[ 210 'enable-snippet' 211 ] || false 212 } 213 onChange={() => 214 handleToggleChange(name) 215 } 216 help={ 217 data.description || '' 218 } 219 /> 220 ))} 221 </div> 222 ))} 223 </Tabs.TabPanel> 224 ); 225 })} 226 </Tabs> 227 <div className="zenpress-actions"> 228 <div className="zenpress-actions-bulk"> 229 <Button 230 variant="tertiary" 231 onClick={enableAllSnippets} 232 __next40pxDefaultSize 233 > 234 {__('Enable all actions', 'zenpress')} 235 </Button> 236 <Button 237 isDestructive 238 onClick={resetSettings} 239 __next40pxDefaultSize 240 > 241 {__('Disable all actions', 'zenpress')} 242 </Button> 243 </div> 244 <SaveButton onClick={saveSettings} isBusy={isSaving} /> 245 </div> 246 </div> 247 </section> 248 <aside className="zenpress-sidebar"> 249 <div className="zenpress-presets"> 250 <div className="zenpress-presets-description"> 251 <h2>{__('Pick configuration preset', 'zenpress')}</h2> 252 <p> 253 {__( 254 "Don't know which features to enable? Quickly configure ZenPress by selecting a preset that matches your site type. Each preset enables optimized features for your specific use case.", 255 'zenpress' 256 )} 257 </p> 258 <hr /> 259 <h3>🖼️ {__('Corporate website', 'zenpress')}</h3> 260 <p> 261 {__( 262 'Optimized for business sites and portfolios. Focuses on security, performance, and removing unnecessary features like RSS feeds and author archives.', 263 'zenpress' 264 )} 265 </p> 266 <Button 267 variant="secondary" 268 onClick={() => enableByPreset('corporate-website')} 269 __next40pxDefaultSize 270 > 271 {__('Enable', 'zenpress')} 272 </Button> 273 <hr /> 274 <h3> 📰 {__('Blog', 'zenpress')}</h3> 275 <p> 276 {__( 277 'Tailored for content-focused blogs. Includes performance and security optimizations while preserving essential blog features like RSS feeds.', 278 'zenpress' 279 )} 280 </p> 281 <Button 282 variant="secondary" 283 onClick={() => enableByPreset('blog')} 284 __next40pxDefaultSize 285 > 286 {__('Enable', 'zenpress')} 287 </Button> 288 <hr /> 289 <h3>🛒 {__('E-commerce', 'zenpress')}</h3> 290 <p> 291 {__( 292 'Designed for WooCommerce stores. Includes all performance and security features plus WooCommerce-specific optimizations for faster checkout.', 293 'zenpress' 294 )} 295 </p> 296 <Button 297 variant="secondary" 298 onClick={() => enableByPreset('ecommerce')} 299 __next40pxDefaultSize 300 > 301 {__('Enable', 'zenpress')} 302 </Button> 303 </div> 304 </div> 305 </aside> 306 </article> 307 ); 255 308 }; -
zenpress/tags/2.2.0/inc/admin/enqueue.php
r3372200 r3448585 23 23 24 24 $asset = include $asset_file; 25 if (!is_array($asset) || empty($asset['dependencies']) || empty($asset['version'])) { 26 return; 27 } 28 25 29 wp_enqueue_script( 26 30 'zenpress-scripts', 27 31 plugins_url('assets/build/index.js', ZENPRESS_PLUGIN_FILE), 28 $asset['dependencies'],32 (array) $asset['dependencies'], 29 33 $asset['version'], 30 34 true … … 35 39 plugins_url('assets/build/index.css', ZENPRESS_PLUGIN_FILE), 36 40 array_filter( 37 $asset['dependencies'],38 function ($style){41 (array) $asset['dependencies'], 42 static function (string $style): bool { 39 43 return wp_style_is($style, 'registered'); 40 44 } … … 62 66 } 63 67 64 foreach (glob($snippets_path . '*.php') as $file) { 68 $files = glob($snippets_path . '*.php') ?: []; 69 foreach ($files as $file) { 65 70 $basename = basename($file, '.php'); 66 71 $snippets[$basename] = zenpress_extract_snippet_metadata($basename); -
zenpress/tags/2.2.0/inc/core/constants.php
r3372200 r3448585 10 10 11 11 if (!defined('ZENPRESS_PLUGIN_FILE')) { 12 define( 13 'ZENPRESS_PLUGIN_FILE', 14 __FILE__ === realpath(__FILE__) 15 ? __FILE__ // fallback 16 : dirname(__DIR__, 1) . '/zenpress.php' 17 ); 12 $plugin_file = dirname(__DIR__, 2) . '/zenpress.php'; 13 define('ZENPRESS_PLUGIN_FILE', is_file($plugin_file) ? $plugin_file : __FILE__); 18 14 } 19 15 -
zenpress/tags/2.2.0/inc/core/metadata.php
r3412245 r3448585 18 18 'subcategory' => '', 19 19 'weight' => 0, 20 'preset' => [] 20 'preset' => [], 21 21 ]; 22 22 … … 31 31 'subcategory' => sanitize_text_field($metadata['subcategory']), 32 32 'weight' => (int) $metadata['weight'], 33 'preset' => array_map('sanitize_text_field', (array) $metadata['preset']) 33 'preset' => array_map('sanitize_text_field', (array) $metadata['preset']), 34 34 ]; 35 35 } -
zenpress/tags/2.2.0/inc/core/sanitize.php
r3372200 r3448585 11 11 * @return array<string> Sanitized base names. 12 12 */ 13 function zenpress_sanitize_snippets_option( $value): array {13 function zenpress_sanitize_snippets_option(mixed $value): array { 14 14 return array_values( 15 15 array_filter( -
zenpress/tags/2.2.0/inc/settings/loader.php
r3372200 r3448585 12 12 */ 13 13 function zenpress_load_snippets(string $folder = 'inc/snippets/functions/'): array { 14 // Prevent path traversal if $folder is ever passed from external input. 15 if (str_contains($folder, '..')) { 16 return []; 17 } 18 14 19 $path = ZENPRESS_PLUGIN_DIR . rtrim($folder, '/') . '/'; 15 20 … … 36 41 * BOOT ZENPRESS PLUGIN 37 42 **************************************/ 38 add_action('init', function(): void {43 add_action('init', static function (): void { 39 44 zenpress_load_snippets(); 40 45 }); -
zenpress/tags/2.2.0/inc/settings/options.php
r3372200 r3448585 20 20 'schema' => [ 21 21 'type' => 'array', 22 'items' => ['type' => 'string'] 23 ] 24 ] 22 'items' => ['type' => 'string'], 23 ], 24 ], 25 25 ] 26 26 ); -
zenpress/tags/2.2.0/inc/snippets/functions/block-user-enumeration.php
r3372200 r3448585 7 7 if (!is_admin()) { 8 8 // Block enumeration via query string (?author=1). 9 if ( 10 isset($_SERVER['QUERY_STRING']) && 11 preg_match('/author=([0-9]+)/i', sanitize_text_field(wp_unslash($_SERVER['QUERY_STRING']))) 12 ) { 9 $query_string = $_SERVER['QUERY_STRING'] ?? ''; 10 if (preg_match('/author=([0-9]+)/i', sanitize_text_field(wp_unslash($query_string)))) { 13 11 wp_die(esc_html__('Access denied.', 'zenpress'), '', ['response' => 403]); 14 12 } … … 17 15 add_filter( 18 16 'redirect_canonical', 19 static function ( $redirect, $request){17 static function (string|false $redirect, string $request): string|false { 20 18 if (preg_match('/\?author=([0-9]+)(\/*)/i', sanitize_text_field(wp_unslash($request)))) { 21 19 wp_die(esc_html__('Access denied.', 'zenpress'), '', ['response' => 403]); -
zenpress/tags/2.2.0/inc/snippets/functions/clean-admin-bar.php
r3372200 r3448585 7 7 add_action( 8 8 'admin_bar_menu', 9 static function ($wp_admin_bar) { 10 if (!($wp_admin_bar instanceof WP_Admin_Bar)) { 11 return; 12 } 13 9 static function (WP_Admin_Bar $wp_admin_bar): void { 14 10 /** 15 11 * Backend clean-up. -
zenpress/tags/2.2.0/inc/snippets/functions/clean-dashboard-items.php
r3372200 r3448585 7 7 add_action( 8 8 'wp_dashboard_setup', 9 static function () {9 static function (): void { 10 10 /** 11 11 * Core widgets. -
zenpress/tags/2.2.0/inc/snippets/functions/disable-author-archives.php
r3372200 r3448585 8 8 remove_filter('template_redirect', 'redirect_canonical'); 9 9 10 add_action('template_redirect', static function () {10 add_action('template_redirect', static function (): void { 11 11 if (is_author()) { 12 12 global $wp_query; -
zenpress/tags/2.2.0/inc/snippets/functions/disable-dashicons.php
r3372200 r3448585 5 5 } 6 6 7 add_action('wp_enqueue_scripts', static function () {7 add_action('wp_enqueue_scripts', static function (): void { 8 8 if (!is_user_logged_in()) { 9 9 wp_dequeue_style('dashicons'); -
zenpress/tags/2.2.0/inc/snippets/functions/disable-default-pattern-categories.php
r3412245 r3448585 11 11 * simplifying the interface and reducing clutter. 12 12 */ 13 add_action('init', static function (){13 add_action('init', static function (): void { 14 14 if (!class_exists('WP_Block_Pattern_Categories_Registry')) { 15 15 return; -
zenpress/tags/2.2.0/inc/snippets/functions/disable-emoji-scripts.php
r3372200 r3448585 22 22 23 23 // Remove emoji support from TinyMCE editor. 24 add_filter('tiny_mce_plugins', static function ( $plugins){24 add_filter('tiny_mce_plugins', static function (array $plugins): array { 25 25 return array_diff($plugins, ['wpemoji']); 26 26 }); -
zenpress/tags/2.2.0/inc/snippets/functions/disable-jquery-migrate.php
r3372200 r3448585 5 5 } 6 6 7 add_action('wp_default_scripts', static function ( &$scripts){7 add_action('wp_default_scripts', static function (WP_Scripts $scripts): void { 8 8 if (!is_admin() && isset($scripts->registered['jquery'])) { 9 9 $script = $scripts->registered['jquery']; -
zenpress/tags/2.2.0/inc/snippets/functions/disable-oembed.php
r3372200 r3448585 5 5 } 6 6 7 add_action('init', static function () {7 add_action('init', static function (): void { 8 8 // Remove oEmbed from query vars. 9 9 global $wp; … … 25 25 26 26 // Remove the wpembed TinyMCE plugin. 27 add_filter('tiny_mce_plugins', function ($plugins){27 add_filter('tiny_mce_plugins', static function (array $plugins): array { 28 28 return array_diff($plugins, ['wpembed']); 29 29 }); 30 30 31 31 // Remove embed-related rewrite rules. 32 add_filter('rewrite_rules_array', function ($rules){32 add_filter('rewrite_rules_array', static function (array $rules): array { 33 33 foreach ($rules as $rule => $rewrite) { 34 if (str pos($rewrite, 'embed=true') !== false) {34 if (str_contains($rewrite, 'embed=true')) { 35 35 unset($rules[$rule]); 36 36 } -
zenpress/tags/2.2.0/inc/snippets/functions/disable-pdf-thumbnails.php
r3372200 r3448585 5 5 } 6 6 7 add_filter('fallback_intermediate_image_sizes', static function () {7 add_filter('fallback_intermediate_image_sizes', static function (): array { 8 8 return []; 9 9 }); -
zenpress/tags/2.2.0/inc/snippets/functions/disable-pingback-trackback.php
r3372200 r3448585 6 6 7 7 // Remove X-Pingback header from HTTP responses. 8 add_filter('wp_headers', static function ( $headers){8 add_filter('wp_headers', static function (array $headers): array { 9 9 unset($headers['X-Pingback']); 10 10 … … 13 13 14 14 // Disable pingbacks and trackbacks for new posts. 15 add_action('after_setup_theme', static function () {15 add_action('after_setup_theme', static function (): void { 16 16 update_option('default_ping_status', 'closed'); 17 17 update_option('default_pingback_flag', 0); … … 19 19 20 20 // Prevent self-pingbacks. 21 add_action('pre_ping', static function ( &$links){21 add_action('pre_ping', static function (array &$links): void { 22 22 $home = get_option('home'); 23 23 foreach ($links as $l => $link) { 24 if (str pos($link, $home) === 0) {24 if (str_starts_with($link, $home)) { 25 25 unset($links[$l]); 26 26 } -
zenpress/tags/2.2.0/inc/snippets/functions/disable-rest-api.php
r3412245 r3448585 14 14 remove_action('xmlrpc_rsd_apis', 'rest_output_rsd'); 15 15 16 // Disable REST API 17 if (version_compare(get_bloginfo('version'), '4.7', '>=')) { 18 add_filter('rest_authentication_errors', 'zenpress_disable_wp_rest_api'); 19 } else { 20 zenpress_disable_wp_rest_api_legacy(); 21 } 22 23 function zenpress_disable_wp_rest_api($access) { 24 if (!is_user_logged_in() && !zenpress_disable_wp_rest_api_allow_access()) { 25 $message = apply_filters('zenpress_disable_wp_rest_api_error', __('REST API restricted to authenticated users.', 'zenpress')); 26 27 return new WP_Error('rest_login_required', $message, ['status' => rest_authorization_required_code()]); 28 } 29 30 return $access; 31 } 32 33 function zenpress_disable_wp_rest_api_allow_access() { 16 /** 17 * Check whether to allow unauthenticated REST API access (bypass). 18 * 19 * Filters zenpress_disable_wp_rest_api_post_var and zenpress_disable_wp_rest_api_server_var 20 * let site owners allow specific POST keys or REQUEST_URI values for webhooks/third-party 21 * integrations. Security note: only use values that are secret or not guessable (e.g. a 22 * random token in POST, or a unique path). Using predictable values can re-enable public 23 * REST API access and weaken the protection provided by the "Disable REST API" snippet. 24 */ 25 $zenpress_disable_wp_rest_api_allow_access = static function (): bool { 34 26 $post_var = apply_filters('zenpress_disable_wp_rest_api_post_var', false); 35 27 $server_var = apply_filters('zenpress_disable_wp_rest_api_server_var', false); 36 28 37 29 if (!empty($post_var)) { 38 if (is_array($post_var)) { 39 foreach($post_var as $var) { 40 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Intentional: Allows bypass via specific POST vars for webhooks/third-party integrations 41 if (isset($_POST[$var]) && !empty($_POST[$var])) { 42 return true; 43 } 44 } 45 } else { 30 $post_vars = is_array($post_var) ? $post_var : [$post_var]; 31 foreach ($post_vars as $var) { 46 32 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Intentional: Allows bypass via specific POST vars for webhooks/third-party integrations 47 if ( isset($_POST[$post_var]) && !empty($_POST[$post_var])) {33 if (!empty($_POST[$var] ?? null)) { 48 34 return true; 49 35 } … … 52 38 53 39 if (!empty($server_var)) { 54 if (is_array($server_var)) { 55 foreach($server_var as $var) { 56 if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === $var) { 57 return true; 58 } 59 } 60 } else { 61 if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === $server_var) { 40 $server_vars = is_array($server_var) ? $server_var : [$server_var]; 41 $request_uri = $_SERVER['REQUEST_URI'] ?? ''; 42 foreach ($server_vars as $var) { 43 if ($request_uri === $var) { 62 44 return true; 63 45 } … … 66 48 67 49 return false; 68 } 50 }; 69 51 70 function zenpress_disable_wp_rest_api_legacy() { 52 // Disable REST API 53 if (version_compare(get_bloginfo('version'), '4.7', '>=')) { 54 add_filter('rest_authentication_errors', static function (WP_Error|bool|null $access) use ($zenpress_disable_wp_rest_api_allow_access): WP_Error|bool|null { 55 if (!is_user_logged_in() && !$zenpress_disable_wp_rest_api_allow_access()) { 56 $message = apply_filters('zenpress_disable_wp_rest_api_error', __('REST API restricted to authenticated users.', 'zenpress')); 57 58 return new WP_Error('rest_login_required', $message, ['status' => rest_authorization_required_code()]); 59 } 60 61 return $access; 62 }); 63 } else { 71 64 // REST API 1.x 72 65 add_filter('json_enabled', '__return_false'); -
zenpress/tags/2.2.0/inc/snippets/functions/disable-rss.php
r3372200 r3448585 2 2 3 3 if (!defined('ABSPATH')) { 4 exit; 5 } 6 7 // Redirect all feed requests to homepage. 8 function zenpress_disable_all_feeds(): void { 9 wp_safe_redirect(home_url(), 301); 4 10 exit; 5 11 } … … 17 23 remove_action('wp_head', 'feed_links_extra', 3); 18 24 remove_action('wp_head', 'feed_links', 2); 19 20 // Redirect all feed requests to homepage.21 function zenpress_disable_all_feeds() {22 wp_safe_redirect(home_url(), 301);23 exit;24 } -
zenpress/tags/2.2.0/inc/snippets/functions/disable-woocommerce-cart-fragments.php
r3372200 r3448585 6 6 7 7 if (class_exists('WooCommerce')) { 8 add_action('wp_enqueue_scripts', static function () {8 add_action('wp_enqueue_scripts', static function (): void { 9 9 wp_dequeue_script('wc-cart-fragments'); 10 10 }, 11); -
zenpress/tags/2.2.0/inc/snippets/functions/disable-woocommerce-scripts-styles.php
r3372200 r3448585 6 6 7 7 if (class_exists('WooCommerce')) { 8 add_action('wp_enqueue_scripts', static function () {8 add_action('wp_enqueue_scripts', static function (): void { 9 9 if (!is_woocommerce() && !is_cart() && !is_checkout() && !is_account_page() && !is_product() && !is_product_category() && !is_shop()) { 10 10 // Dequeue WooCommerce Styles -
zenpress/tags/2.2.0/inc/snippets/functions/disable-woocommerce-widgets.php
r3372200 r3448585 6 6 7 7 if (class_exists('WooCommerce')) { 8 add_action('widgets_init', static function (){8 add_action('widgets_init', static function (): void { 9 9 unregister_widget('WC_Widget_Products'); 10 10 unregister_widget('WC_Widget_Product_Categories'); -
zenpress/tags/2.2.0/inc/snippets/functions/hide-woocommerce-version.php
r3372200 r3448585 7 7 if (class_exists('WooCommerce') && !is_admin()) { 8 8 // Remove WooCommerce version from HTTP headers 9 add_filter('wp_headers', static function ($headers) { 10 if (isset($headers['X-WooCommerce-Version'])) { 11 unset($headers['X-WooCommerce-Version']); 12 } 9 add_filter('wp_headers', static function (array $headers): array { 10 unset($headers['X-WooCommerce-Version']); 13 11 14 12 return $headers; … … 16 14 17 15 // Remove WooCommerce version from style URLs 18 add_filter('style_loader_src', static function ( $src){19 if (str pos($src, 'ver=') !== false && strpos($src, 'woocommerce') !== false) {16 add_filter('style_loader_src', static function (string $src): string { 17 if (str_contains($src, 'ver=') && str_contains($src, 'woocommerce')) { 20 18 $src = remove_query_arg('ver', $src); 21 19 } … … 25 23 26 24 // Remove WooCommerce version from script URLs 27 add_filter('script_loader_src', static function ( $src){28 if (str pos($src, 'ver=') !== false && strpos($src, 'woocommerce') !== false) {25 add_filter('script_loader_src', static function (string $src): string { 26 if (str_contains($src, 'ver=') && str_contains($src, 'woocommerce')) { 29 27 $src = remove_query_arg('ver', $src); 30 28 } -
zenpress/tags/2.2.0/inc/snippets/functions/hide-wordpress-version.php
r3372200 r3448585 9 9 10 10 // Remove WordPress version from generator 11 add_filter('the_generator', static function () {11 add_filter('the_generator', static function (): string { 12 12 return ''; 13 13 }); 14 14 15 15 // Remove WordPress version from script URLs 16 add_filter('script_loader_src', static function ( $src){17 if (str pos($src, 'ver=' . get_bloginfo('version')) !== false) {16 add_filter('script_loader_src', static function (string $src): string { 17 if (str_contains($src, 'ver=' . get_bloginfo('version'))) { 18 18 $src = remove_query_arg('ver', $src); 19 19 } … … 23 23 24 24 // Remove WordPress version from style URLs 25 add_filter('style_loader_src', static function ( $src){26 if (str pos($src, 'ver=' . get_bloginfo('version')) !== false) {25 add_filter('style_loader_src', static function (string $src): string { 26 if (str_contains($src, 'ver=' . get_bloginfo('version'))) { 27 27 $src = remove_query_arg('ver', $src); 28 28 } -
zenpress/tags/2.2.0/inc/snippets/functions/protect-wp-login.php
r3372200 r3448585 6 6 7 7 // Remove detailed login errors 8 add_filter('login_errors', static function () {8 add_filter('login_errors', static function (): string { 9 9 return __('Login error.', 'zenpress'); 10 10 }); … … 15 15 $BLOCK_DURATION = 300; // 5 minutes 16 16 17 $ipAddress = isset($_SERVER['REMOTE_ADDR']) 18 ? filter_var(wp_unslash($_SERVER['REMOTE_ADDR']), FILTER_VALIDATE_IP) 19 : 'unknown'; 17 $ipAddress = filter_var( 18 wp_unslash($_SERVER['REMOTE_ADDR'] ?? ''), 19 FILTER_VALIDATE_IP 20 ) ?: 'unknown'; 20 21 21 22 $blockKey = 'zenpress_login_block_' . $ipAddress; … … 34 35 wp_die( 35 36 esc_html__('Too many failed login attempts. Try again later.', 'zenpress'), 36 403 37 '', 38 ['response' => 403] 37 39 ); 38 40 } … … 47 49 wp_die( 48 50 esc_html__('Too many failed login attempts. Try again later.', 'zenpress'), 49 403 51 '', 52 ['response' => 403] 50 53 ); 51 54 } -
zenpress/tags/2.2.0/inc/snippets/functions/remove-gutenberg-unwanted-block-patterns.php
r3372200 r3448585 9 9 10 10 // Remove core block patterns 11 add_action('after_setup_theme', static function () {11 add_action('after_setup_theme', static function (): void { 12 12 remove_theme_support('core-block-patterns'); 13 13 }); -
zenpress/tags/2.2.0/inc/snippets/functions/remove-woocommerce-patterns.php
r3412245 r3448585 37 37 }); 38 38 39 add_action('init', static function (){39 add_action('init', static function (): void { 40 40 if (!class_exists('WP_Block_Patterns_Registry')) { 41 41 return; … … 44 44 $all_patterns = WP_Block_Patterns_Registry::get_instance()->get_all_registered(); 45 45 foreach ($all_patterns as $pattern) { 46 if (isset($pattern['name'])) { 47 // Use str_starts_with() for PHP 8.0+, fallback to strpos() for older versions 48 $is_woocommerce_pattern = function_exists('str_starts_with') 49 ? str_starts_with($pattern['name'], 'woocommerce-blocks') 50 : strpos($pattern['name'], 'woocommerce-blocks') === 0; 51 52 if ($is_woocommerce_pattern) { 53 unregister_block_pattern($pattern['name']); 54 } 46 if (isset($pattern['name']) && str_starts_with($pattern['name'], 'woocommerce-blocks')) { 47 unregister_block_pattern($pattern['name']); 55 48 } 56 49 } … … 63 56 * sometimes be tied to large transients/caching issues. 64 57 */ 65 add_filter('woocommerce_admin_features', static function($features) { 66 // Ensure $features is an array 67 if (!is_array($features)) { 68 return $features; 69 } 70 58 add_filter('woocommerce_admin_features', static function (array $features): array { 71 59 $feature_to_disable = 'pattern-toolkit-full-composability'; 72 60 -
zenpress/tags/2.2.0/inc/snippets/meta/clean-dashboard-items.meta.php
r3412245 r3448585 13 13 'title' => __('Clean up the WordPress Dashboard', 'zenpress'), 14 14 'description' => __( 15 'Removes unnecessary widgets and adswidgets from the dashboard. Declutters the admin area and improves usability.',15 'Removes unnecessary and ad widgets from the dashboard. Declutters the admin area and improves usability.', 16 16 'zenpress' 17 17 ), -
zenpress/tags/2.2.0/inc/snippets/meta/disable-application-passwords.meta.php
r3412245 r3448585 13 13 'title' => __('Disable application passwords', 'zenpress'), 14 14 'description' => __( 15 'Disables WordPress application passwords for all users , improving security. Only disable if API access is not needed.',15 'Disables WordPress application passwords for all users. Improves security; do not enable if you need API or app-based authentication (e.g. mobile apps, REST clients).', 16 16 'zenpress' 17 17 ), … … 19 19 'subcategory' => __('security', 'zenpress'), 20 20 'weight' => 0, 21 'preset' => [ ''],21 'preset' => [], 22 22 ]; 23 23 -
zenpress/tags/2.2.0/inc/snippets/meta/disable-dashicons.meta.php
r3412245 r3448585 16 16 'zenpress' 17 17 ), 18 'category' => __(' core', 'zenpress'),18 'category' => __('core', 'zenpress'), 19 19 'subcategory' => __('performance', 'zenpress'), 20 20 'weight' => 0, -
zenpress/tags/2.2.0/inc/snippets/meta/disable-default-pattern-categories.meta.php
r3412245 r3448585 12 12 return [ 13 13 'title' => __('Disable default pattern categories in site editor', 'zenpress'), 14 'description' => __('Removes default pattern categories from the block pattern inserter in the site editor . Disables default categories: featured, about, audio, banner, buttons, call-to-action, columns, contact, footer, gallery, header, media, portfolio, posts, query, services, team, testimonials, text, videos, and any custom categories. This simplifies the interface by hiding category navigation whilepatterns remain accessible.', 'zenpress'),14 'description' => __('Removes default pattern categories from the block pattern inserter in the site editor (e.g. featured, about, audio, banner, buttons, call-to-action, columns, contact, footer, gallery, header, media, portfolio, posts, query, services, team, testimonials, text, videos) and any custom ones. Simplifies the interface; patterns remain accessible.', 'zenpress'), 15 15 'category' => __('gutenberg', 'zenpress'), 16 16 'subcategory' => __('user-interface', 'zenpress'), -
zenpress/tags/2.2.0/inc/snippets/meta/disable-dns-prefetch.meta.php
r3412245 r3448585 13 13 'title' => __('Disable DNS prefetch', 'zenpress'), 14 14 'description' => __( 15 'Removes DNS prefetch resource hints from wp_head avoids unnecessary DNS lookups and slightly improveperformance on some sites.',15 'Removes DNS prefetch resource hints from wp_head. Avoids unnecessary DNS lookups and slightly improves performance on some sites.', 16 16 'zenpress' 17 17 ), -
zenpress/tags/2.2.0/inc/snippets/meta/disable-emoji-scripts.meta.php
r3412245 r3448585 16 16 'zenpress' 17 17 ), 18 'category' => __(' core', 'zenpress'),18 'category' => __('core', 'zenpress'), 19 19 'subcategory' => __('performance', 'zenpress'), 20 20 'weight' => 0, -
zenpress/tags/2.2.0/inc/snippets/meta/disable-jquery-migrate.meta.php
r3412245 r3448585 16 16 'zenpress' 17 17 ), 18 'category' => __(' core', 'zenpress'),18 'category' => __('core', 'zenpress'), 19 19 'subcategory' => __('performance', 'zenpress'), 20 20 'weight' => 0, -
zenpress/tags/2.2.0/inc/snippets/meta/disable-pdf-thumbnails.meta.php
r3412245 r3448585 12 12 return [ 13 13 'title' => __('Disable PDF thumbnails', 'zenpress'), 14 'description' => __('Prevents WordPress from generating thumbnails for uploaded PDF files by removing fallback image sizes. saves storage space and improves performance by avoiding unnecessary image generation.', 'zenpress'),15 'category' => __(' core', 'zenpress'),14 'description' => __('Prevents WordPress from generating thumbnails for uploaded PDF files by removing fallback image sizes. Saves storage space and improves performance by avoiding unnecessary image generation.', 'zenpress'), 15 'category' => __('core', 'zenpress'), 16 16 'subcategory' => __('performance', 'zenpress'), 17 17 'weight' => 0, -
zenpress/tags/2.2.0/inc/snippets/meta/disable-pingback-trackback.meta.php
r3412245 r3448585 12 12 return [ 13 13 'title' => __('Disable pingback and trackback', 'zenpress'), 14 'description' => __('Removes the X-Pingback header, disables pingbacks and trackbacks on new posts, and prevents self-pingbacks. reduces spam, blocks potential DDoS vectors, and slightly improves performance by avoiding useless requests.', 'zenpress'),14 'description' => __('Removes the X-Pingback header, disables pingbacks and trackbacks on new posts, and prevents self-pingbacks. Reduces spam, blocks potential DDoS vectors, and slightly improves performance by avoiding useless requests.', 'zenpress'), 15 15 'category' => __('core', 'zenpress'), 16 16 'subcategory' => __('security', 'zenpress'), -
zenpress/tags/2.2.0/inc/snippets/meta/disable-rest-api.meta.php
r3412245 r3448585 14 14 'title' => __('Disable REST API for visitors not logged into WordPress', 'zenpress'), 15 15 'description' => __( 16 ' Disable the WP REST API for visitors not logged into WordPress.',16 'Restricts the WP REST API to logged-in users only; unauthenticated requests receive an error. Bypass filters (zenpress_disable_wp_rest_api_post_var, zenpress_disable_wp_rest_api_server_var) allow specific integrations (e.g. webhooks); use non-guessable values only.', 17 17 'zenpress' 18 18 ), -
zenpress/tags/2.2.0/inc/snippets/meta/protect-wp-login.meta.php
r3412245 r3448585 12 12 return [ 13 13 'title' => __('Protect the wp-login form from brute force attacks', 'zenpress'), 14 'description' => __('Removes detailed login error messages and limits failed login attempts per IP address. Blocks further attempts for a set duration after too many failures. Improves security by mitigating bruteforce attacks.', 'zenpress'),14 'description' => __('Removes detailed login error messages and limits failed login attempts per IP. Blocks further attempts for 5 minutes after 5 failed tries. Mitigates brute-force attacks.', 'zenpress'), 15 15 'category' => __('tools', 'zenpress'), 16 16 'subcategory' => __('security', 'zenpress'), -
zenpress/tags/2.2.0/inc/snippets/meta/remove-gutenberg-unwanted-block-patterns.meta.php
r3412245 r3448585 13 13 'title' => __('Remove WordPress default remote block patterns', 'zenpress'), 14 14 'description' => __('Prevents WordPress from loading remote block patterns and removes the built-in core block patterns. Reduces editor clutter and improves performance by avoiding unnecessary data loading.', 'zenpress'), 15 'category' => __(' gutenberg', 'zenpress'),15 'category' => __('gutenberg', 'zenpress'), 16 16 'subcategory' => __('performance', 'zenpress'), 17 17 'weight' => 0, -
zenpress/tags/2.2.0/inc/snippets/meta/remove-rest-api-link.meta.php
r3412245 r3448585 12 12 return [ 13 13 'title' => __('Remove REST API links', 'zenpress'), 14 'description' => __('Prevents WordPress from adding REST API discovery links to the head section of the site. reduces unnecessary HTML output and slightly improves performance while keeping REST API functionality available.', 'zenpress'),14 'description' => __('Prevents WordPress from adding REST API discovery links to the head section of the site. Reduces unnecessary HTML output and slightly improves performance while keeping REST API functionality available.', 'zenpress'), 15 15 'category' => __('core', 'zenpress'), 16 16 'subcategory' => __('performance', 'zenpress'), -
zenpress/tags/2.2.0/languages/zenpress.pot
r3412245 r3448585 1 # Copyright (C) 202 5Quentin Le Duff1 # Copyright (C) 2026 Quentin Le Duff 2 2 # This file is distributed under the GPL v2 or later. 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: ZenPress - Cleaner, Lighter, Faster WP 2. 1.0\n"5 "Project-Id-Version: ZenPress - Cleaner, Lighter, Faster WP 2.2.0\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/zenpress\n" 7 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" … … 10 10 "Content-Type: text/plain; charset=UTF-8\n" 11 11 "Content-Transfer-Encoding: 8bit\n" 12 "POT-Creation-Date: 202 5-12-05T13:56:04+00:00\n"12 "POT-Creation-Date: 2026-01-28T10:05:55+00:00\n" 13 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 14 "X-Generator: WP-CLI 2.12.0\n" … … 142 142 msgstr "" 143 143 144 #: inc/snippets/functions/block-user-enumeration.php:1 3145 #: inc/snippets/functions/block-user-enumeration.php: 21144 #: inc/snippets/functions/block-user-enumeration.php:11 145 #: inc/snippets/functions/block-user-enumeration.php:19 146 146 msgid "Access denied." 147 147 msgstr "" 148 148 149 #: inc/snippets/functions/disable-rest-api.php: 25149 #: inc/snippets/functions/disable-rest-api.php:56 150 150 msgid "REST API restricted to authenticated users." 151 151 msgstr "" … … 155 155 msgstr "" 156 156 157 #: inc/snippets/functions/protect-wp-login.php:3 5158 #: inc/snippets/functions/protect-wp-login.php: 48157 #: inc/snippets/functions/protect-wp-login.php:36 158 #: inc/snippets/functions/protect-wp-login.php:50 159 159 msgid "Too many failed login attempts. Try again later." 160 160 msgstr "" … … 173 173 #: inc/snippets/meta/disable-application-passwords.meta.php:18 174 174 #: inc/snippets/meta/disable-author-archives.meta.php:18 175 #: inc/snippets/meta/disable-autosave.meta.php:18 176 #: inc/snippets/meta/disable-capital-p-dangit.meta.php:18 177 #: inc/snippets/meta/disable-dashicons.meta.php:18 175 178 #: inc/snippets/meta/disable-dns-prefetch.meta.php:18 179 #: inc/snippets/meta/disable-emoji-scripts.meta.php:18 180 #: inc/snippets/meta/disable-jquery-migrate.meta.php:18 176 181 #: inc/snippets/meta/disable-login-language-selector.meta.php:15 177 182 #: inc/snippets/meta/disable-oembed.meta.php:15 183 #: inc/snippets/meta/disable-password-strength-meter.meta.php:18 184 #: inc/snippets/meta/disable-pdf-thumbnails.meta.php:15 178 185 #: inc/snippets/meta/disable-pingback-trackback.meta.php:15 179 186 #: inc/snippets/meta/disable-rest-api.meta.php:19 … … 181 188 #: inc/snippets/meta/disable-shortlink.meta.php:15 182 189 #: inc/snippets/meta/disable-wlw-manifest.meta.php:15 190 #: inc/snippets/meta/disable-wordpress-default-lazy-loading.meta.php:18 183 191 #: inc/snippets/meta/disable-xmlrpc-rsdlink.meta.php:15 184 192 #: inc/snippets/meta/hide-wordpress-version.meta.php:15 193 #: inc/snippets/meta/limit-post-revisions.meta.php:18 194 #: inc/snippets/meta/remove-help-button.meta.php:18 185 195 #: inc/snippets/meta/remove-rest-api-link.meta.php:15 196 #: inc/snippets/meta/remove-thanks-for-using-wordpress-in-footer.meta.php:18 197 #: inc/snippets/meta/remove-wordpress-logo.meta.php:18 186 198 msgid "core" 187 199 msgstr "" … … 211 223 #: inc/snippets/meta/disable-default-pattern-categories.meta.php:16 212 224 #: inc/snippets/meta/disable-login-language-selector.meta.php:16 225 #: inc/snippets/meta/remove-help-button.meta.php:19 226 #: inc/snippets/meta/remove-thanks-for-using-wordpress-in-footer.meta.php:19 227 #: inc/snippets/meta/remove-wordpress-logo.meta.php:19 213 228 msgid "user-interface" 214 229 msgstr "" … … 219 234 220 235 #: inc/snippets/meta/clean-dashboard-items.meta.php:14 221 msgid "Removes unnecessary widgets and adswidgets from the dashboard. Declutters the admin area and improves usability."236 msgid "Removes unnecessary and ad widgets from the dashboard. Declutters the admin area and improves usability." 222 237 msgstr "" 223 238 … … 235 250 236 251 #: inc/snippets/meta/disable-adjacent-posts.meta.php:19 252 #: inc/snippets/meta/disable-autosave.meta.php:19 253 #: inc/snippets/meta/disable-capital-p-dangit.meta.php:19 237 254 #: inc/snippets/meta/disable-dashicons.meta.php:19 238 255 #: inc/snippets/meta/disable-dns-prefetch.meta.php:19 … … 240 257 #: inc/snippets/meta/disable-jquery-migrate.meta.php:19 241 258 #: inc/snippets/meta/disable-oembed.meta.php:16 259 #: inc/snippets/meta/disable-password-strength-meter.meta.php:19 242 260 #: inc/snippets/meta/disable-pdf-thumbnails.meta.php:16 243 261 #: inc/snippets/meta/disable-rss.meta.php:16 … … 248 266 #: inc/snippets/meta/disable-woocommerce-stripe-scripts.meta.php:16 249 267 #: inc/snippets/meta/disable-woocommerce-widgets.meta.php:16 268 #: inc/snippets/meta/disable-wordpress-default-lazy-loading.meta.php:19 269 #: inc/snippets/meta/limit-post-revisions.meta.php:19 250 270 #: inc/snippets/meta/remove-gutenberg-unwanted-block-patterns.meta.php:16 251 271 #: inc/snippets/meta/remove-rest-api-link.meta.php:16 … … 260 280 261 281 #: inc/snippets/meta/disable-application-passwords.meta.php:14 262 msgid "Disables WordPress application passwords for all users , improving security. Only disable if API access is not needed."282 msgid "Disables WordPress application passwords for all users. Improves security; do not enable if you need API or app-based authentication (e.g. mobile apps, REST clients)." 263 283 msgstr "" 264 284 … … 271 291 msgstr "" 272 292 293 #: inc/snippets/meta/disable-autosave.meta.php:13 294 msgid "Disable autosave" 295 msgstr "" 296 297 #: inc/snippets/meta/disable-autosave.meta.php:14 298 msgid "Stops the classic editor from autosaving drafts periodically. Reduces database writes and heartbeat traffic. The block editor may still use its own autosave; this targets the legacy post editor." 299 msgstr "" 300 301 #: inc/snippets/meta/disable-capital-p-dangit.meta.php:13 302 msgid "Disable capital_P_dangit filter" 303 msgstr "" 304 305 #: inc/snippets/meta/disable-capital-p-dangit.meta.php:14 306 msgid "Removes the filter that forces \"Wordpress\" to \"WordPress\" in titles, content, comments, and widget text. Saves a small amount of processing on each page load." 307 msgstr "" 308 273 309 #: inc/snippets/meta/disable-dashicons.meta.php:13 274 310 msgid "Disable dashicons" … … 279 315 msgstr "" 280 316 281 #: inc/snippets/meta/disable-dashicons.meta.php:18282 #: inc/snippets/meta/disable-emoji-scripts.meta.php:18283 #: inc/snippets/meta/disable-jquery-migrate.meta.php:18284 #: inc/snippets/meta/disable-pdf-thumbnails.meta.php:15285 msgid " core"286 msgstr ""287 288 317 #: inc/snippets/meta/disable-default-pattern-categories.meta.php:13 289 318 msgid "Disable default pattern categories in site editor" … … 291 320 292 321 #: inc/snippets/meta/disable-default-pattern-categories.meta.php:14 293 msgid "Removes default pattern categories from the block pattern inserter in the site editor . Disables default categories: featured, about, audio, banner, buttons, call-to-action, columns, contact, footer, gallery, header, media, portfolio, posts, query, services, team, testimonials, text, videos, and any custom categories. This simplifies the interface by hiding category navigation whilepatterns remain accessible."322 msgid "Removes default pattern categories from the block pattern inserter in the site editor (e.g. featured, about, audio, banner, buttons, call-to-action, columns, contact, footer, gallery, header, media, portfolio, posts, query, services, team, testimonials, text, videos) and any custom ones. Simplifies the interface; patterns remain accessible." 294 323 msgstr "" 295 324 296 325 #: inc/snippets/meta/disable-default-pattern-categories.meta.php:15 326 #: inc/snippets/meta/remove-gutenberg-unwanted-block-patterns.meta.php:15 297 327 #: inc/snippets/meta/separate-gutenberg-core-block-styles.meta.php:15 298 328 msgid "gutenberg" … … 304 334 305 335 #: inc/snippets/meta/disable-dns-prefetch.meta.php:14 306 msgid "Removes DNS prefetch resource hints from wp_head avoids unnecessary DNS lookups and slightly improveperformance on some sites."336 msgid "Removes DNS prefetch resource hints from wp_head. Avoids unnecessary DNS lookups and slightly improves performance on some sites." 307 337 msgstr "" 308 338 … … 339 369 msgstr "" 340 370 371 #: inc/snippets/meta/disable-password-strength-meter.meta.php:13 372 msgid "Disable Password Strength Meter" 373 msgstr "" 374 375 #: inc/snippets/meta/disable-password-strength-meter.meta.php:14 376 msgid "Prevents the password strength meter and zxcvbn scripts from loading on profile, login, and similar pages. Saves roughly 400KB and reduces script parsing. Users will not see the strength indicator when choosing passwords." 377 msgstr "" 378 341 379 #: inc/snippets/meta/disable-pdf-thumbnails.meta.php:13 342 380 msgid "Disable PDF thumbnails" … … 344 382 345 383 #: inc/snippets/meta/disable-pdf-thumbnails.meta.php:14 346 msgid "Prevents WordPress from generating thumbnails for uploaded PDF files by removing fallback image sizes. saves storage space and improves performance by avoiding unnecessary image generation."384 msgid "Prevents WordPress from generating thumbnails for uploaded PDF files by removing fallback image sizes. Saves storage space and improves performance by avoiding unnecessary image generation." 347 385 msgstr "" 348 386 … … 352 390 353 391 #: inc/snippets/meta/disable-pingback-trackback.meta.php:14 354 msgid "Removes the X-Pingback header, disables pingbacks and trackbacks on new posts, and prevents self-pingbacks. reduces spam, blocks potential DDoS vectors, and slightly improves performance by avoiding useless requests."392 msgid "Removes the X-Pingback header, disables pingbacks and trackbacks on new posts, and prevents self-pingbacks. Reduces spam, blocks potential DDoS vectors, and slightly improves performance by avoiding useless requests." 355 393 msgstr "" 356 394 … … 360 398 361 399 #: inc/snippets/meta/disable-rest-api.meta.php:15 362 msgid " Disable the WP REST API for visitors not logged into WordPress."400 msgid "Restricts the WP REST API to logged-in users only; unauthenticated requests receive an error. Bypass filters (zenpress_disable_wp_rest_api_post_var, zenpress_disable_wp_rest_api_server_var) allow specific integrations (e.g. webhooks); use non-guessable values only." 363 401 msgstr "" 364 402 … … 428 466 msgstr "" 429 467 468 #: inc/snippets/meta/disable-wordpress-default-lazy-loading.meta.php:13 469 msgid "Disable WordPress default lazy loading" 470 msgstr "" 471 472 #: inc/snippets/meta/disable-wordpress-default-lazy-loading.meta.php:14 473 msgid "Stops WordPress from adding loading=\"lazy\" to images and iframes. Use only if you rely on a theme, plugin, or CDN for lazy loading, or if you prefer to load all media immediately." 474 msgstr "" 475 430 476 #: inc/snippets/meta/disable-xmlrpc-rsdlink.meta.php:13 431 477 msgid "Disable XML-RPC and remove RSD link" … … 452 498 msgstr "" 453 499 500 #: inc/snippets/meta/limit-post-revisions.meta.php:13 501 msgid "Limit post revision to 10" 502 msgstr "" 503 504 #: inc/snippets/meta/limit-post-revisions.meta.php:14 505 msgid "Keeps at most 10 revisions per post (or page). Older revisions are deleted when new ones are created. Reduces database size and improves performance." 506 msgstr "" 507 454 508 #: inc/snippets/meta/protect-wp-login.meta.php:13 455 509 msgid "Protect the wp-login form from brute force attacks" … … 457 511 458 512 #: inc/snippets/meta/protect-wp-login.meta.php:14 459 msgid "Removes detailed login error messages and limits failed login attempts per IP address. Blocks further attempts for a set duration after too many failures. Improves security by mitigating bruteforce attacks."513 msgid "Removes detailed login error messages and limits failed login attempts per IP. Blocks further attempts for 5 minutes after 5 failed tries. Mitigates brute-force attacks." 460 514 msgstr "" 461 515 … … 472 526 msgstr "" 473 527 474 #: inc/snippets/meta/remove-gutenberg-unwanted-block-patterns.meta.php:15 475 msgid " gutenberg" 528 #: inc/snippets/meta/remove-help-button.meta.php:13 529 msgid "Remove \"Help\" button" 530 msgstr "" 531 532 #: inc/snippets/meta/remove-help-button.meta.php:14 533 msgid "Hides the Help tab and panel on all admin screens. Reduces clutter for users who do not use the in-app help." 476 534 msgstr "" 477 535 … … 481 539 482 540 #: inc/snippets/meta/remove-rest-api-link.meta.php:14 483 msgid "Prevents WordPress from adding REST API discovery links to the head section of the site. reduces unnecessary HTML output and slightly improves performance while keeping REST API functionality available." 541 msgid "Prevents WordPress from adding REST API discovery links to the head section of the site. Reduces unnecessary HTML output and slightly improves performance while keeping REST API functionality available." 542 msgstr "" 543 544 #: inc/snippets/meta/remove-thanks-for-using-wordpress-in-footer.meta.php:13 545 msgid "Remove \"Thanks for using WordPress\" in footer" 546 msgstr "" 547 548 #: inc/snippets/meta/remove-thanks-for-using-wordpress-in-footer.meta.php:14 549 msgid "Removes the \"Thank you for creating with WordPress\" message from the admin footer. Cleans up the interface." 484 550 msgstr "" 485 551 … … 490 556 #: inc/snippets/meta/remove-woocommerce-patterns.meta.php:14 491 557 msgid "Removes all WooCommerce block patterns to avoid unnecessary pattern registration in the editor." 558 msgstr "" 559 560 #: inc/snippets/meta/remove-wordpress-logo.meta.php:13 561 msgid "Remove WordPress logo" 562 msgstr "" 563 564 #: inc/snippets/meta/remove-wordpress-logo.meta.php:14 565 msgid "Removes the WordPress logo and its dropdown from the admin bar. Cleans up the interface for a more neutral or branded look." 492 566 msgstr "" 493 567 -
zenpress/tags/2.2.0/readme.txt
r3412245 r3448585 5 5 Requires at least: 6.0 6 6 Tested up to: 6.9 7 Stable tag: 2. 1.08 Requires PHP: 7.47 Stable tag: 2.2.0 8 Requires PHP: 8.3 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html/ … … 34 34 35 35 = Settings subpage 🧰 = 36 * Organized interface with vertical tabs for easy navigation between categories (Core, Gutenberg, WooCommerce, Tools).36 * Organized interface with vertical tabs for easy navigation between categories (Core, Gutenberg, WooCommerce, Ads-blocker, Tools). 37 37 * Features grouped by subcategories (Performance, Security, User Interface) with visual icons for quick identification. 38 38 * Three ready-to-use presets: Corporate website, Blog, and E-commerce - each optimized for specific site types. … … 53 53 * Disable WordPress shortlink. 54 54 * Disable WLW link. 55 * Remove WordPress default remote block patterns.56 55 * Remove REST API links. 57 * Separate loading of core block styles. 56 * Disable capital_P_dangit filter. 57 * Disable autosave. 58 * Limit post revision to 10. 59 * Disable Password Strength Meter. 60 * Disable WordPress default lazy loading. 58 61 59 62 = Core - Security = … … 66 69 * Disable XML-RPC and remove RSD link. 67 70 * Hide WordPress version. 68 * Protect the wp-login form from brute force attacks.69 71 70 72 = Core - User Interface = 71 73 72 74 * Clean up the WordPress admin bar. 73 * Clean up the WordPress Dashboard.74 75 * Disable the login language selector. 76 * Remove WordPress logo. 77 * Remove "Help" button. 78 * Remove "Thanks for using WordPress" in footer. 75 79 76 80 = WooCommerce - Performance = … … 91 95 * Disable default pattern categories in site editor. 92 96 97 = Ads-blocker - User Interface = 98 * Clean up the WordPress Dashboard. 99 100 = Tools - Security = 101 * Protect the wp-login form from brute force attacks. 102 93 103 = Presets = 94 104 * Corporate website / Portfolio: Optimized for business sites and portfolios. Focuses on security, performance, and removing unnecessary features like RSS feeds and author archives. … … 112 122 * Manage Heartbeat API (frontend + backend + admin whitelist). 113 123 114 = Performance =115 * Disable capital_P_dangit filter.116 * Disable autosave.117 * Disable post revision.118 * Disable Password Strength Meter.119 * Disable WordPress default lazy loading.120 121 124 = User Interface = 122 * Remove "howdy" from admin bar.123 * Remove WordPress logo.124 * Remove "Help button".125 * Remove "Thanks for using WordPress" in footer.126 125 * Remove "site health" page. 127 126 * Remove "Privacy tools". … … 175 174 In addition, if you like the plugin then I'd love for you to [leave a review](https://wordpress.org/support/plugin/zenpress/reviews/). Tell all your friends about it too! 176 175 176 = Does ZenPress work with my existing caching / optimization plugins? = 177 178 Yes. ZenPress focuses on disabling unnecessary core features and plugin bloat; it does not handle page caching, minification, or image optimization. It is designed to work alongside plugins like Cache Enabler, Autoptimize, and most object-cache solutions. If a performance feature overlaps, simply disable it in one of the tools. 179 180 = Can ZenPress break my theme or plugins? = 181 182 Potentially, yes—especially if your theme or plugins rely on features you disable (RSS feeds, oEmbed, REST API, emojis, WooCommerce assets, etc.). That’s why each snippet includes clear descriptions and categories (performance, security, UI). Always test changes on a staging site first and enable snippets gradually. 183 184 = How do I know which snippets are safe to enable? = 185 186 If you are unsure, start with a preset (Corporate, Blog, E‑commerce) and then adjust. For manual tuning, prefer UI and performance snippets first (dashicons, emojis, dashboard/admin-bar cleanup) before more invasive ones (REST API, XML‑RPC, RSS). After each change, check: frontend pages, login, editor, and (if used) WooCommerce flows. 187 188 = What happens if I disable the REST API? = 189 190 Unauthenticated REST requests will be blocked except for any explicit bypasses configured via the filters documented in the snippet (`zenpress_disable_wp_rest_api_post_var`, `zenpress_disable_wp_rest_api_server_var`). Core features and plugins that depend on public REST endpoints (some blocks, headless/front-end apps, third-party integrations) may stop working until you whitelist the required routes. 191 192 **Important**: If you don't know how to configure bypass filters or whitelist specific routes, don't activate this snippet. It can break functionality that relies on public REST API access. 193 194 = Does ZenPress store any personal data or phone home? = 195 196 No. ZenPress does not collect, store, or transmit any personal data. It does not contact external services or include third‑party trackers. All settings are stored in standard WordPress options and remain on your site only. 197 198 = Is ZenPress multisite compatible? = 199 200 ZenPress can be network‑activated or activated per site. Settings are stored per site, so each site in a network can have different snippets enabled. As with any optimization/security plugin, test network‑wide changes carefully, especially REST API and XML‑RPC related snippets. 201 177 202 = I have a suggestion = 178 203 179 204 Nice ! If you can't find anything in the roadmap, feel free to submit your suggestion on the support page! If you know how to code, you can even contribute on GitHub. 180 205 181 = Does this plugin work with PHP 8 =182 183 Yes, it 's been tested actively and works from PHP 7.4 to PHP 8.4.184 185 206 == Changelog == 207 208 = 2.2.0 = 209 - Global: Dropped PHP 7.4 support and aligned minimum PHP requirement with the currently recommended WordPress version. 210 - Global: Replaced strpos() with str_contains() and str_starts_with() throughout all snippets. 211 - Snippets: Converted snippets to use direct execution pattern where applicable for better performance. 212 - Security: Protect wp-login: fix wp_die() so blocked login responses return HTTP 403 instead of 200. 213 - Security: Loader: guard against path traversal in zenpress_load_snippets() when the folder argument contains '..'. 214 - Security: Disable REST API: document in-code that bypass filters (zenpress_disable_wp_rest_api_post_var, zenpress_disable_wp_rest_api_server_var) should use non-guessable values only. 215 - New actionable function: Disable autosave. 216 - New actionable function: Disable capital_P_dangit filter. 217 - New actionable function: Disable Password Strength Meter. 218 - New actionable function: Disable WordPress default lazy loading. 219 - New actionable function: Limit post revision to 10. 220 - New actionable function: Remove "Help" button. 221 - New actionable function: Remove "Thanks for using WordPress" in footer. 222 - New actionable function: Remove WordPress logo. 186 223 187 224 = 2.1.0 = … … 296 333 == Upgrade Notice == 297 334 335 = 2.2.0 = 336 - Breaking: PHP 8.3 is now required (PHP 7.4 support dropped). Major code modernization with improved type safety and performance. 337 - Security: HTTP 403 on login block, path traversal guard in snippet loader, and in-code docs for REST API bypass filters. 338 298 339 = 1.0.0.1 = 299 340 -
zenpress/tags/2.2.0/zenpress.php
r3412245 r3448585 12 12 * Plugin Name: ZenPress - Cleaner, Lighter, Faster WP 13 13 * Description: Easily speed up and strengthen your WordPress site by cleaning out unnecessary features and protecting weak points. 14 * Version: 2. 1.014 * Version: 2.2.0 15 15 * Plugin URI: https://wordpress.org/plugins/zenpress/ 16 16 * Author: Quentin Le Duff … … 20 20 * Requires at least: 6.0 21 21 * Tested up to: 6.9 22 * Requires PHP: 7.422 * Requires PHP: 8.3 23 23 * License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html/ 24 24 * License: GPL v2 or later -
zenpress/trunk/assets/build/index.asset.php
r3412245 r3448585 1 <?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-notices'), 'version' => ' 980bfa58281a120e84d0');1 <?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-notices'), 'version' => '253355e0f124eff32114'); -
zenpress/trunk/assets/build/index.js
r3412245 r3448585 1 (()=>{"use strict";var e={n:s=>{var t=s&&s.__esModule?()=>s.default:()=>s;return e.d(t,{a:t}),t},d:(s,t)=>{for(var n in t)e.o(t,n)&&!e.o(s,n)&&Object.defineProperty(s,n,{enumerable:!0,get:t[n]})},o:(e,s)=>Object.prototype.hasOwnProperty.call(e,s)};const s=window.wp.domReady;var t=e.n(s);const n=window.wp.element,r=window.wp.i18n,a=window.wp.components,i=window.wp.apiFetch;var o=e.n(i);const c=window.wp.data,l=window.wp.notices,p=window.ReactJSXRuntime,d=({label:e,value:s,onChange:t,help:r})=>{const i=(0,n.useRef)(null);return(0,n.useEffect)(()=>((e,s)=>{if(!e)return;const t=t=>{if("Enter"===t.key){const n=e.querySelector('input[type="checkbox"]') ;n&&(document.activeElement===n||e.contains(document.activeElement))&&(t.preventDefault(),t.stopPropagation(),s())}};return e.addEventListener("keydown",t),()=>{e.removeEventListener("keydown",t)}})(i.current,t),[t]),(0,p.jsx)("div",{ref:i,children:(0,p.jsx)(a.ToggleControl,{label:e,checked:s,onChange:t,help:r,__nextHasNoMarginBottom:!0})})},u=({onClick:e,isBusy:s})=>(0,p.jsx)(a.Button,{variant:"primary",onClick:e,isBusy:s,__next40pxDefaultSize:!0,children:(0,r.__)("Save settings","zenpress")}),b=()=>{const{removeNotice:e}=(0,c.useDispatch)(l.store),s=(0,c.useSelect)(e=>e(l.store).getNotices(),[]);return s&&0!==s.length?(0,p.jsx)(a.NoticeList,{notices:s,onRemove:e}):null},h=(0,n.createContext)(),f=({selectedTabId:e,onSelect:s,orientation:t="horizontal",children:r})=>{const[a,i]=(0,n.useState)(),o=(0,n.useRef)(null),c=void 0!==e?e:a;return(0,p.jsx)(h.Provider,{value:{selectedTabId:c,onSelect:t=>{void 0===e&&i(t),s?.(t)},orientation:t,getOrderedTabIds:()=>o.current?Array.from(o.current.querySelectorAll('[role="tab"]')).map(e=>{const s=e.getAttribute("id");return s?s.replace("zenpress-tab-",""):null}).filter(Boolean):[],tabListRef:o},children:(0,p.jsx)("div",{className:`zenpress-tabs zenpress-tabs--${t}`,children:r})})};f.TabList=({children:e})=>{const{orientation:s,tabListRef:t}=(0,n.useContext)(h);return(0,p.jsx)("div",{ref:t,className:`zenpress-tabs__list zenpress-tabs__list--${s}`,role:"tablist","aria-orientation":s,children:e})},f.Tab=({tabId:e,title:s,className:t="",children:r})=>{const{selectedTabId:a,onSelect:i,orientation:o,getOrderedTabIds:c}=(0,n.useContext)(h),l=a===e,d=(0,n.useRef)(null);return(0,p.jsx)("button",{ref:d,className:`zenpress-tabs__tab ${l?"zenpress-tabs__tab--is-active":""} ${t}`.trim(),role:"tab","aria-selected":l,"aria-controls":`zenpress-tab-panel-${e}`,id:`zenpress-tab-${e}`,tabIndex:l?0:-1,onClick:()=>i(e),onKeyDown:s=>{const t=c();if(!t||0===t.length)return;const n=t.indexOf(e);if(-1===n)return;let r=n;if("vertical"===o?"ArrowDown"===s.key?(s.preventDefault(),r=n<t.length-1?n+1:0):"ArrowUp"===s.key&&(s.preventDefault(),r=n>0?n-1:t.length-1):"ArrowRight"===s.key?(s.preventDefault(),r=n<t.length-1?n+1:0):"ArrowLeft"===s.key&&(s.preventDefault(),r=n>0?n-1:t.length-1),"Home"===s.key?(s.preventDefault(),r=0):"End"===s.key&&(s.preventDefault(),r=t.length-1)," "===s.key||"Enter"===s.key)return s.preventDefault(),void i(e);if(r!==n&&r>=0&&r<t.length){const e=t[r],s=document.getElementById(`zenpress-tab-${e}`);s&&(s.focus(),i(e))}},onFocus:()=>{l||i(e)},children:s||r})},f.TabPanel=({tabId:e,children:s})=>{const{selectedTabId:t}=(0,n.useContext)(h),r=(0,n.useRef)(null),a=t===e;return(0,n.useEffect)(()=>{a&&r.current&&(0===r.current.querySelectorAll('a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"]), input:not([disabled]), select:not([disabled]), textarea:not([disabled])').length?r.current.setAttribute("tabindex","0"):r.current.removeAttribute("tabindex"))},[a]),a?(0,p.jsx)("div",{ref:r,className:"zenpress-tabs__panel",role:"tabpanel",id:`zenpress-tab-panel-${e}`,"aria-labelledby":`zenpress-tab-${e}`,children:s}):(0,p.jsx)("div",{className:"zenpress-tabs__panel",role:"tabpanel",id:`zenpress-tab-panel-${e}`,"aria-labelledby":`zenpress-tab-${e}`,hidden:!0,children:s})};const x=()=>{const{snippets:e,setSnippets:s,saveSettings:t,isSaving:i}=(()=>{const[e,s]=(0,n.useState)({}),[t,a]=(0,n.useState)(!1),{createSuccessNotice:i,createErrorNotice:p}=(0,c.useDispatch)(l.store);return(0,n.useEffect)(()=>{o()({path:"/wp/v2/settings"}).then(e=>{const t=Array.isArray(e?.zenpress_active_snippets)?e.zenpress_active_snippets:[],n=window?.zenpressSnippetsMeta||{},r={};Object.keys(n).forEach(e=>{r[e]={...n[e],"enable-snippet":t.includes(e)}}),s(r)}).catch(()=>{p((0,r.__)("Failed to load settings.","zenpress"))})},[p]),{snippets:e,setSnippets:s,saveSettings:async()=>{a(!0);const s=Object.keys(e).filter(s=>e[s]?.["enable-snippet"]);try{await o()({path:"/wp/v2/settings",method:"POST",data:{zenpress_active_snippets:s}}),i((0,r.__)("Settings saved.","zenpress"))}catch{p((0,r.__)("Failed to save settings.","zenpress"))}finally{a(!1)}},isSaving:t}})(),[h,x]=(0,n.useState)(),_=e=>{s(s=>{const t={};return Object.entries(s).forEach(([s,n])=>{const r=(Array.isArray(n?.preset)?n.preset:[]).includes(e);t[s]={...n,"enable-snippet":r}}),t})},m=e=>e?e.charAt(0).toUpperCase()+e.slice(1).toLowerCase():e,v=["core","gutenberg","woocommerce","ads-blocker","tools"],y={};Object.keys(e).forEach(s=>{const t=e[s],n=(t?.category||(0,r.__)("Uncategorized","zenpress")).toLowerCase(),a=(t?.subcategory||(0,r.__)("uncategorized","zenpress")).toLowerCase();y[n]||(y[n]={}),y[n][a]||(y[n][a]=[]),y[n][a].push({name:s,data:t})});const z=Object.keys(y).sort((e,s)=>{const t=v.indexOf(e.toLowerCase()),n=v.indexOf(s.toLowerCase());return-1!==t&&-1!==n?t-n:-1!==t?-1:-1!==n?1:e.localeCompare(s,void 0,{sensitivity:"base"})});return(0,n.useEffect)(()=>{!h&&z.length>0&&x(z[0])},[h,z.length]),(0,n.useEffect)(()=>{const e=e=>{(e.ctrlKey||e.metaKey)&&"s"===e.key&&(e.preventDefault(),i||t())};return document.addEventListener("keydown",e),()=>{document.removeEventListener("keydown",e)}},[t,i]),(0,p.jsxs)("article",{className:"zenpress-row",children:[(0,p.jsxs)("section",{className:"zenpress-main",children:[(0,p.jsx)("div",{className:"zenpress-notices",children:(0,p.jsx)(b,{})}),(0,p.jsxs)("div",{className:"zenpress-panel",children:[(0,p.jsxs)(f,{orientation:"vertical",selectedTabId:h,onSelect:e=>{x(e),x(e)},children:[(0,p.jsx)(f.TabList,{children:z.map(e=>{const s=`zenpress-tabs__tab--category-${e.toLowerCase().replace(/\s+/g,"-")}`;return(0,p.jsx)(f.Tab,{tabId:e,title:m(e),className:s,children:m(e)},e)})}),z.map(e=>{const t=Object.keys(y[e]).sort();return(0,p.jsxs)(f.TabPanel,{tabId:e,children:[(0,p.jsx)("h2",{children:m(e)}),t.map(t=>(0,p.jsxs)("div",{className:`zenpress-subcategory zenpress-subcategory-${t.toLowerCase().replace(/\s+/g,"-")}`,children:[(0,p.jsx)("hr",{}),(0,p.jsx)("h3",{children:m(t)}),y[e][t].map(({name:e,data:t})=>(0,p.jsx)(d,{label:t.title||e,value:t?.["enable-snippet"]||!1,onChange:()=>{return t=e,void s(e=>({...e,[t]:{...e[t],"enable-snippet":!e[t]?.["enable-snippet"]}}));var t},help:t.description||""},e))]},t))]},e)})]}),(0,p.jsxs)("div",{className:"zenpress-actions",children:[(0,p.jsxs)("div",{className:"zenpress-actions-bulk",children:[(0,p.jsx)(a.Button,{variant:"tertiary",onClick:()=>{s(e=>{const s={};return Object.keys(e).forEach(t=>{s[t]={...e[t],"enable-snippet":!0}}),s})},__next40pxDefaultSize:!0,children:(0,r.__)("Enable all actions","zenpress")}),(0,p.jsx)(a.Button,{isDestructive:!0,onClick:()=>{s(e=>{const s={};return Object.keys(e).forEach(t=>{s[t]={...e[t],"enable-snippet":!1}}),s})},__next40pxDefaultSize:!0,children:(0,r.__)("Disable all actions","zenpress")})]}),(0,p.jsx)(u,{onClick:t,isBusy:i})]})]})]}),(0,p.jsx)("aside",{className:"zenpress-sidebar",children:(0,p.jsx)("div",{className:"zenpress-presets",children:(0,p.jsxs)("div",{className:"zenpress-presets-description",children:[(0,p.jsx)("h2",{children:(0,r.__)("Pick configuration preset","zenpress")}),(0,p.jsx)("p",{children:(0,r.__)("Don't know which features to enable? Quickly configure ZenPress by selecting a preset that matches your site type. Each preset enables optimized features for your specific use case.","zenpress")}),(0,p.jsx)("hr",{}),(0,p.jsxs)("h3",{children:["🖼️ ",(0,r.__)("Corporate website","zenpress")]}),(0,p.jsx)("p",{children:(0,r.__)("Optimized for business sites and portfolios. Focuses on security, performance, and removing unnecessary features like RSS feeds and author archives.","zenpress")}),(0,p.jsx)(a.Button,{variant:"secondary",onClick:()=>_("corporate-website"),__next40pxDefaultSize:!0,children:(0,r.__)("Enable","zenpress")}),(0,p.jsx)("hr",{}),(0,p.jsxs)("h3",{children:[" 📰 ",(0,r.__)("Blog","zenpress")]}),(0,p.jsx)("p",{children:(0,r.__)("Tailored for content-focused blogs. Includes performance and security optimizations while preserving essential blog features like RSS feeds.","zenpress")}),(0,p.jsx)(a.Button,{variant:"secondary",onClick:()=>_("blog"),__next40pxDefaultSize:!0,children:(0,r.__)("Enable","zenpress")}),(0,p.jsx)("hr",{}),(0,p.jsxs)("h3",{children:["🛒 ",(0,r.__)("E-commerce","zenpress")]}),(0,p.jsx)("p",{children:(0,r.__)("Designed for WooCommerce stores. Includes all performance and security features plus WooCommerce-specific optimizations for faster checkout.","zenpress")}),(0,p.jsx)(a.Button,{variant:"secondary",onClick:()=>_("ecommerce"),__next40pxDefaultSize:!0,children:(0,r.__)("Enable","zenpress")})]})})})]})};t()(()=>{const e=document.getElementById("zenpress-settings");e&&(0,n.createRoot)(e).render((0,p.jsx)(x,{}))})})();1 (()=>{"use strict";var e={n:s=>{var t=s&&s.__esModule?()=>s.default:()=>s;return e.d(t,{a:t}),t},d:(s,t)=>{for(var n in t)e.o(t,n)&&!e.o(s,n)&&Object.defineProperty(s,n,{enumerable:!0,get:t[n]})},o:(e,s)=>Object.prototype.hasOwnProperty.call(e,s)};const s=window.wp.domReady;var t=e.n(s);const n=window.wp.element,r=window.wp.i18n,a=window.wp.components,i=window.wp.apiFetch;var o=e.n(i);const c=window.wp.data,l=window.wp.notices,p=window.ReactJSXRuntime,d=({label:e,value:s,onChange:t,help:r})=>{const i=(0,n.useRef)(null);return(0,n.useEffect)(()=>((e,s)=>{if(!e)return;const t=t=>{if("Enter"===t.key){const n=e.querySelector('input[type="checkbox"]'),r=e.ownerDocument||document;n&&(r.activeElement===n||e.contains(r.activeElement))&&(t.preventDefault(),t.stopPropagation(),s())}};return e.addEventListener("keydown",t),()=>{e.removeEventListener("keydown",t)}})(i.current,t),[t]),(0,p.jsx)("div",{ref:i,children:(0,p.jsx)(a.ToggleControl,{label:e,checked:s,onChange:t,help:r,__nextHasNoMarginBottom:!0})})},u=({onClick:e,isBusy:s})=>(0,p.jsx)(a.Button,{variant:"primary",onClick:e,isBusy:s,__next40pxDefaultSize:!0,children:(0,r.__)("Save settings","zenpress")}),b=()=>{const{removeNotice:e}=(0,c.useDispatch)(l.store),s=(0,c.useSelect)(e=>e(l.store).getNotices(),[]);return s&&0!==s.length?(0,p.jsx)(a.NoticeList,{notices:s,onRemove:e}):null},h=(0,n.createContext)(),f=({selectedTabId:e,onSelect:s,orientation:t="horizontal",children:r})=>{const[a,i]=(0,n.useState)(),o=(0,n.useRef)(null),c=void 0!==e?e:a;return(0,p.jsx)(h.Provider,{value:{selectedTabId:c,onSelect:t=>{void 0===e&&i(t),s?.(t)},orientation:t,getOrderedTabIds:()=>o.current?Array.from(o.current.querySelectorAll('[role="tab"]')).map(e=>{const s=e.getAttribute("id");return s?s.replace("zenpress-tab-",""):null}).filter(Boolean):[],tabListRef:o},children:(0,p.jsx)("div",{className:`zenpress-tabs zenpress-tabs--${t}`,children:r})})};f.TabList=({children:e})=>{const{orientation:s,tabListRef:t}=(0,n.useContext)(h);return(0,p.jsx)("div",{ref:t,className:`zenpress-tabs__list zenpress-tabs__list--${s}`,role:"tablist","aria-orientation":s,children:e})},f.Tab=({tabId:e,title:s,className:t="",children:r})=>{const{selectedTabId:a,onSelect:i,orientation:o,getOrderedTabIds:c}=(0,n.useContext)(h),l=a===e,d=(0,n.useRef)(null);return(0,p.jsx)("button",{ref:d,className:`zenpress-tabs__tab ${l?"zenpress-tabs__tab--is-active":""} ${t}`.trim(),role:"tab","aria-selected":l,"aria-controls":`zenpress-tab-panel-${e}`,id:`zenpress-tab-${e}`,tabIndex:l?0:-1,onClick:()=>i(e),onKeyDown:s=>{const t=c();if(!t||0===t.length)return;const n=t.indexOf(e);if(-1===n)return;let r=n;if("vertical"===o?"ArrowDown"===s.key?(s.preventDefault(),r=n<t.length-1?n+1:0):"ArrowUp"===s.key&&(s.preventDefault(),r=n>0?n-1:t.length-1):"ArrowRight"===s.key?(s.preventDefault(),r=n<t.length-1?n+1:0):"ArrowLeft"===s.key&&(s.preventDefault(),r=n>0?n-1:t.length-1),"Home"===s.key?(s.preventDefault(),r=0):"End"===s.key&&(s.preventDefault(),r=t.length-1)," "===s.key||"Enter"===s.key)return s.preventDefault(),void i(e);if(r!==n&&r>=0&&r<t.length){const e=t[r],s=document.getElementById(`zenpress-tab-${e}`);s&&(s.focus(),i(e))}},onFocus:()=>{l||i(e)},children:s||r})},f.TabPanel=({tabId:e,children:s})=>{const{selectedTabId:t}=(0,n.useContext)(h),r=(0,n.useRef)(null),a=t===e;return(0,n.useEffect)(()=>{a&&r.current&&(0===r.current.querySelectorAll('a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"]), input:not([disabled]), select:not([disabled]), textarea:not([disabled])').length?r.current.setAttribute("tabindex","0"):r.current.removeAttribute("tabindex"))},[a]),a?(0,p.jsx)("div",{ref:r,className:"zenpress-tabs__panel",role:"tabpanel",id:`zenpress-tab-panel-${e}`,"aria-labelledby":`zenpress-tab-${e}`,children:s}):(0,p.jsx)("div",{className:"zenpress-tabs__panel",role:"tabpanel",id:`zenpress-tab-panel-${e}`,"aria-labelledby":`zenpress-tab-${e}`,hidden:!0,children:s})};const x=()=>{const{snippets:e,setSnippets:s,saveSettings:t,isSaving:i}=(()=>{const[e,s]=(0,n.useState)({}),[t,a]=(0,n.useState)(!1),{createSuccessNotice:i,createErrorNotice:p}=(0,c.useDispatch)(l.store);return(0,n.useEffect)(()=>{o()({path:"/wp/v2/settings"}).then(e=>{const t=Array.isArray(e?.zenpress_active_snippets)?e.zenpress_active_snippets:[],n=window?.zenpressSnippetsMeta||{},r={};Object.keys(n).forEach(e=>{r[e]={...n[e],"enable-snippet":t.includes(e)}}),s(r)}).catch(()=>{p((0,r.__)("Failed to load settings.","zenpress"))})},[p]),{snippets:e,setSnippets:s,saveSettings:async()=>{a(!0);const s=Object.keys(e).filter(s=>e[s]?.["enable-snippet"]);try{await o()({path:"/wp/v2/settings",method:"POST",data:{zenpress_active_snippets:s}}),i((0,r.__)("Settings saved.","zenpress"))}catch{p((0,r.__)("Failed to save settings.","zenpress"))}finally{a(!1)}},isSaving:t}})(),[h,x]=(0,n.useState)(),_=e=>{s(s=>{const t={};return Object.entries(s).forEach(([s,n])=>{const r=(Array.isArray(n?.preset)?n.preset:[]).includes(e);t[s]={...n,"enable-snippet":r}}),t})},m=e=>e?e.charAt(0).toUpperCase()+e.slice(1).toLowerCase():e,v=["core","gutenberg","woocommerce","ads-blocker","tools"],y={};Object.keys(e).forEach(s=>{const t=e[s],n=(t?.category||(0,r.__)("Uncategorized","zenpress")).toLowerCase(),a=(t?.subcategory||(0,r.__)("uncategorized","zenpress")).toLowerCase();y[n]||(y[n]={}),y[n][a]||(y[n][a]=[]),y[n][a].push({name:s,data:t})});const z=Object.keys(y).sort((e,s)=>{const t=v.indexOf(e.toLowerCase()),n=v.indexOf(s.toLowerCase());return-1!==t&&-1!==n?t-n:-1!==t?-1:-1!==n?1:e.localeCompare(s,void 0,{sensitivity:"base"})});return(0,n.useEffect)(()=>{!h&&z.length>0&&x(z[0])},[h,z]),(0,n.useEffect)(()=>{const e=e=>{(e.ctrlKey||e.metaKey)&&"s"===e.key&&(e.preventDefault(),i||t())};return document.addEventListener("keydown",e),()=>{document.removeEventListener("keydown",e)}},[t,i]),(0,p.jsxs)("article",{className:"zenpress-row",children:[(0,p.jsxs)("section",{className:"zenpress-main",children:[(0,p.jsx)("div",{className:"zenpress-notices",children:(0,p.jsx)(b,{})}),(0,p.jsxs)("div",{className:"zenpress-panel",children:[(0,p.jsxs)(f,{orientation:"vertical",selectedTabId:h,onSelect:e=>{x(e),x(e)},children:[(0,p.jsx)(f.TabList,{children:z.map(e=>{const s=`zenpress-tabs__tab--category-${e.toLowerCase().replace(/\s+/g,"-")}`;return(0,p.jsx)(f.Tab,{tabId:e,title:m(e),className:s,children:m(e)},e)})}),z.map(e=>{const t=Object.keys(y[e]).sort();return(0,p.jsxs)(f.TabPanel,{tabId:e,children:[(0,p.jsx)("h2",{children:m(e)}),t.map(t=>(0,p.jsxs)("div",{className:`zenpress-subcategory zenpress-subcategory-${t.toLowerCase().replace(/\s+/g,"-")}`,children:[(0,p.jsx)("hr",{}),(0,p.jsx)("h3",{children:m(t)}),y[e][t].map(({name:e,data:t})=>(0,p.jsx)(d,{label:t.title||e,value:t?.["enable-snippet"]||!1,onChange:()=>{return t=e,void s(e=>({...e,[t]:{...e[t],"enable-snippet":!e[t]?.["enable-snippet"]}}));var t},help:t.description||""},e))]},t))]},e)})]}),(0,p.jsxs)("div",{className:"zenpress-actions",children:[(0,p.jsxs)("div",{className:"zenpress-actions-bulk",children:[(0,p.jsx)(a.Button,{variant:"tertiary",onClick:()=>{s(e=>{const s={};return Object.keys(e).forEach(t=>{s[t]={...e[t],"enable-snippet":!0}}),s})},__next40pxDefaultSize:!0,children:(0,r.__)("Enable all actions","zenpress")}),(0,p.jsx)(a.Button,{isDestructive:!0,onClick:()=>{s(e=>{const s={};return Object.keys(e).forEach(t=>{s[t]={...e[t],"enable-snippet":!1}}),s})},__next40pxDefaultSize:!0,children:(0,r.__)("Disable all actions","zenpress")})]}),(0,p.jsx)(u,{onClick:t,isBusy:i})]})]})]}),(0,p.jsx)("aside",{className:"zenpress-sidebar",children:(0,p.jsx)("div",{className:"zenpress-presets",children:(0,p.jsxs)("div",{className:"zenpress-presets-description",children:[(0,p.jsx)("h2",{children:(0,r.__)("Pick configuration preset","zenpress")}),(0,p.jsx)("p",{children:(0,r.__)("Don't know which features to enable? Quickly configure ZenPress by selecting a preset that matches your site type. Each preset enables optimized features for your specific use case.","zenpress")}),(0,p.jsx)("hr",{}),(0,p.jsxs)("h3",{children:["🖼️ ",(0,r.__)("Corporate website","zenpress")]}),(0,p.jsx)("p",{children:(0,r.__)("Optimized for business sites and portfolios. Focuses on security, performance, and removing unnecessary features like RSS feeds and author archives.","zenpress")}),(0,p.jsx)(a.Button,{variant:"secondary",onClick:()=>_("corporate-website"),__next40pxDefaultSize:!0,children:(0,r.__)("Enable","zenpress")}),(0,p.jsx)("hr",{}),(0,p.jsxs)("h3",{children:[" 📰 ",(0,r.__)("Blog","zenpress")]}),(0,p.jsx)("p",{children:(0,r.__)("Tailored for content-focused blogs. Includes performance and security optimizations while preserving essential blog features like RSS feeds.","zenpress")}),(0,p.jsx)(a.Button,{variant:"secondary",onClick:()=>_("blog"),__next40pxDefaultSize:!0,children:(0,r.__)("Enable","zenpress")}),(0,p.jsx)("hr",{}),(0,p.jsxs)("h3",{children:["🛒 ",(0,r.__)("E-commerce","zenpress")]}),(0,p.jsx)("p",{children:(0,r.__)("Designed for WooCommerce stores. Includes all performance and security features plus WooCommerce-specific optimizations for faster checkout.","zenpress")}),(0,p.jsx)(a.Button,{variant:"secondary",onClick:()=>_("ecommerce"),__next40pxDefaultSize:!0,children:(0,r.__)("Enable","zenpress")})]})})})]})};t()(()=>{const e=document.getElementById("zenpress-settings");e&&(0,n.createRoot)(e).render((0,p.jsx)(x,{}))})})(); -
zenpress/trunk/assets/src/index.js
r3412245 r3448585 8 8 */ 9 9 domReady(() => { 10 const rootEl = document.getElementById('zenpress-settings'); 11 if (!rootEl) return; 10 const rootEl = document.getElementById('zenpress-settings'); 11 if (!rootEl) { 12 return; 13 } 12 14 13 const root = createRoot(rootEl);14 root.render(<SettingsPage />);15 const root = createRoot(rootEl); 16 root.render(<SettingsPage />); 15 17 }); -
zenpress/trunk/assets/src/js/components/Notices.js
r3412245 r3448585 9 9 */ 10 10 export const Notices = () => { 11 const { removeNotice } = useDispatch(noticesStore); 12 const notices = useSelect((select) => select(noticesStore).getNotices(), []); 11 const { removeNotice } = useDispatch(noticesStore); 12 const notices = useSelect( 13 (select) => select(noticesStore).getNotices(), 14 [] 15 ); 13 16 14 if (!notices || notices.length === 0) {15 return null;16 }17 if (!notices || notices.length === 0) { 18 return null; 19 } 17 20 18 return <NoticeList notices={notices} onRemove={removeNotice} />;21 return <NoticeList notices={notices} onRemove={removeNotice} />; 19 22 }; -
zenpress/trunk/assets/src/js/components/SaveButton.js
r3412245 r3448585 11 11 */ 12 12 export const SaveButton = ({ onClick, isBusy }) => { 13 return ( 14 <Button variant="primary" onClick={onClick} isBusy={isBusy} __next40pxDefaultSize> 15 {__('Save settings', 'zenpress')} 16 </Button> 17 ); 13 return ( 14 <Button 15 variant="primary" 16 onClick={onClick} 17 isBusy={isBusy} 18 __next40pxDefaultSize 19 > 20 {__('Save settings', 'zenpress')} 21 </Button> 22 ); 18 23 }; -
zenpress/trunk/assets/src/js/components/SnippetToggleControl.js
r3412245 r3448585 7 7 * 8 8 * @param {HTMLElement} container - Container element to attach listener to. 9 * @param {Function} onChange- Change handler to call when Enter is pressed.9 * @param {Function} onChange - Change handler to call when Enter is pressed. 10 10 */ 11 11 const addEnterKeySupport = (container, onChange) => { 12 if (!container) return; 12 if (!container) { 13 return; 14 } 13 15 14 const handleKeyDown = (e) => { 15 // Handle Enter key on the toggle control 16 if (e.key === 'Enter') { 17 const toggleInput = container.querySelector('input[type="checkbox"]'); 18 if (toggleInput && (document.activeElement === toggleInput || container.contains(document.activeElement))) { 19 e.preventDefault(); 20 e.stopPropagation(); 21 onChange(); 22 } 23 } 24 }; 16 const handleKeyDown = (e) => { 17 // Handle Enter key on the toggle control 18 if (e.key === 'Enter') { 19 const toggleInput = container.querySelector( 20 'input[type="checkbox"]' 21 ); 22 const ownerDocument = container.ownerDocument || document; 23 if ( 24 toggleInput && 25 (ownerDocument.activeElement === toggleInput || 26 container.contains(ownerDocument.activeElement)) 27 ) { 28 e.preventDefault(); 29 e.stopPropagation(); 30 onChange(); 31 } 32 } 33 }; 25 34 26 container.addEventListener('keydown', handleKeyDown);27 return () => {28 container.removeEventListener('keydown', handleKeyDown);29 };35 container.addEventListener('keydown', handleKeyDown); 36 return () => { 37 container.removeEventListener('keydown', handleKeyDown); 38 }; 30 39 }; 31 40 … … 44 53 */ 45 54 export const SnippetToggleControl = ({ label, value, onChange, help }) => { 46 const containerRef = useRef(null);55 const containerRef = useRef(null); 47 56 48 // Temporary support for Enter key handling49 useEffect(() => {50 return addEnterKeySupport(containerRef.current, onChange);51 }, [onChange]);57 // Temporary support for Enter key handling 58 useEffect(() => { 59 return addEnterKeySupport(containerRef.current, onChange); 60 }, [onChange]); 52 61 53 return ( 54 <div ref={containerRef}> 55 <ToggleControl label={label} checked={value} onChange={onChange} help={help} __nextHasNoMarginBottom /> 56 </div> 57 ); 62 return ( 63 <div ref={containerRef}> 64 <ToggleControl 65 label={label} 66 checked={value} 67 onChange={onChange} 68 help={help} 69 __nextHasNoMarginBottom 70 /> 71 </div> 72 ); 58 73 }; -
zenpress/trunk/assets/src/js/components/Tabs.js
r3412245 r3448585 1 import { useState, createContext, useContext, useRef, useEffect } from '@wordpress/element'; 1 import { 2 useState, 3 createContext, 4 useContext, 5 useRef, 6 useEffect, 7 } from '@wordpress/element'; 2 8 3 9 const TabsContext = createContext(); … … 6 12 * Custom Tabs component with vertical orientation support 7 13 * 8 * @param {Object} props - Component props.9 * @param {string} props.selectedTabId - Currently selected tab ID.10 * @param {Function} props.onSelect - Callback when tab is selected.11 * @param {string} props.orientation - Tab orientation ('vertical' or 'horizontal').12 * @param {Object} props.children - Child components (TabList and TabPanels).14 * @param {Object} props - Component props. 15 * @param {string} props.selectedTabId - Currently selected tab ID. 16 * @param {Function} props.onSelect - Callback when tab is selected. 17 * @param {string} props.orientation - Tab orientation ('vertical' or 'horizontal'). 18 * @param {Object} props.children - Child components (TabList and TabPanels). 13 19 * @return {JSX.Element} The tabs container. 14 20 */ 15 export const Tabs = ({ selectedTabId: controlledId, onSelect, orientation = 'horizontal', children }) => { 16 const [internalId, setInternalId] = useState(); 17 const tabListRef = useRef(null); 18 const selectedId = controlledId !== undefined ? controlledId : internalId; 19 const handleSelect = (id) => { 20 if (controlledId === undefined) setInternalId(id); 21 onSelect?.(id); 22 }; 23 24 // Function to get ordered tab IDs from DOM 25 const getOrderedTabIds = () => { 26 if (!tabListRef.current) return []; 27 28 const tabs = Array.from(tabListRef.current.querySelectorAll('[role="tab"]')); 29 return tabs 30 .map((tab) => { 31 const id = tab.getAttribute('id'); 32 return id ? id.replace('zenpress-tab-', '') : null; 33 }) 34 .filter(Boolean); 35 }; 36 37 return ( 38 <TabsContext.Provider 39 value={{ selectedTabId: selectedId, onSelect: handleSelect, orientation, getOrderedTabIds, tabListRef }} 40 > 41 <div className={`zenpress-tabs zenpress-tabs--${orientation}`}>{children}</div> 42 </TabsContext.Provider> 43 ); 21 export const Tabs = ({ 22 selectedTabId: controlledId, 23 onSelect, 24 orientation = 'horizontal', 25 children, 26 }) => { 27 const [internalId, setInternalId] = useState(); 28 const tabListRef = useRef(null); 29 const selectedId = controlledId !== undefined ? controlledId : internalId; 30 const handleSelect = (id) => { 31 if (controlledId === undefined) { 32 setInternalId(id); 33 } 34 onSelect?.(id); 35 }; 36 37 // Function to get ordered tab IDs from DOM 38 const getOrderedTabIds = () => { 39 if (!tabListRef.current) { 40 return []; 41 } 42 43 const tabs = Array.from( 44 tabListRef.current.querySelectorAll('[role="tab"]') 45 ); 46 return tabs 47 .map((tab) => { 48 const id = tab.getAttribute('id'); 49 return id ? id.replace('zenpress-tab-', '') : null; 50 }) 51 .filter(Boolean); 52 }; 53 54 return ( 55 <TabsContext.Provider 56 value={{ 57 selectedTabId: selectedId, 58 onSelect: handleSelect, 59 orientation, 60 getOrderedTabIds, 61 tabListRef, 62 }} 63 > 64 <div className={`zenpress-tabs zenpress-tabs--${orientation}`}> 65 {children} 66 </div> 67 </TabsContext.Provider> 68 ); 44 69 }; 45 70 … … 47 72 * TabList component - container for Tab components 48 73 * 49 * @param {Object} props - Component props.74 * @param {Object} props - Component props. 50 75 * @param {Object} props.children - Tab components. 51 76 * @return {JSX.Element} The tab list container. 52 77 */ 53 78 export const TabList = ({ children }) => { 54 const { orientation, tabListRef } = useContext(TabsContext);55 56 return (57 <div58 ref={tabListRef}59 className={`zenpress-tabs__list zenpress-tabs__list--${orientation}`}60 role="tablist"61 aria-orientation={orientation}62 >63 {children}64 </div>65 );79 const { orientation, tabListRef } = useContext(TabsContext); 80 81 return ( 82 <div 83 ref={tabListRef} 84 className={`zenpress-tabs__list zenpress-tabs__list--${orientation}`} 85 role="tablist" 86 aria-orientation={orientation} 87 > 88 {children} 89 </div> 90 ); 66 91 }; 67 92 … … 69 94 * Tab component - individual tab button 70 95 * 71 * @param {Object} props- Component props.72 * @param {string} props.tabId- Unique identifier for the tab.73 * @param {string} props.title- Tab title (optional, uses children if not provided).74 * @param {string} props.className - Additional CSS class name.75 * @param {Object} props.children- Tab content.96 * @param {Object} props - Component props. 97 * @param {string} props.tabId - Unique identifier for the tab. 98 * @param {string} props.title - Tab title (optional, uses children if not provided). 99 * @param {string} props.className - Additional CSS class name. 100 * @param {Object} props.children - Tab content. 76 101 * @return {JSX.Element} The tab button. 77 102 */ 78 103 export const Tab = ({ tabId, title, className = '', children }) => { 79 const { selectedTabId, onSelect, orientation, getOrderedTabIds } = useContext(TabsContext); 80 const isSelected = selectedTabId === tabId; 81 const tabRef = useRef(null); 82 83 // Handle keyboard navigation according to W3C ARIA pattern 84 const handleKeyDown = (e) => { 85 const tabIds = getOrderedTabIds(); 86 if (!tabIds || tabIds.length === 0) return; 87 88 const currentIndex = tabIds.indexOf(tabId); 89 if (currentIndex === -1) return; 90 91 let targetIndex = currentIndex; 92 93 // Handle arrow keys based on orientation 94 if (orientation === 'vertical') { 95 if (e.key === 'ArrowDown') { 96 e.preventDefault(); 97 targetIndex = currentIndex < tabIds.length - 1 ? currentIndex + 1 : 0; 98 } else if (e.key === 'ArrowUp') { 99 e.preventDefault(); 100 targetIndex = currentIndex > 0 ? currentIndex - 1 : tabIds.length - 1; 101 } 102 } else { 103 // Horizontal orientation 104 if (e.key === 'ArrowRight') { 105 e.preventDefault(); 106 targetIndex = currentIndex < tabIds.length - 1 ? currentIndex + 1 : 0; 107 } else if (e.key === 'ArrowLeft') { 108 e.preventDefault(); 109 targetIndex = currentIndex > 0 ? currentIndex - 1 : tabIds.length - 1; 110 } 111 } 112 113 // Handle Home and End keys 114 if (e.key === 'Home') { 115 e.preventDefault(); 116 targetIndex = 0; 117 } else if (e.key === 'End') { 118 e.preventDefault(); 119 targetIndex = tabIds.length - 1; 120 } 121 122 // Handle Space and Enter for activation (if not auto-activated) 123 if (e.key === ' ' || e.key === 'Enter') { 124 e.preventDefault(); 125 onSelect(tabId); 126 return; 127 } 128 129 // Move focus to target tab if index changed 130 if (targetIndex !== currentIndex && targetIndex >= 0 && targetIndex < tabIds.length) { 131 const targetTabId = tabIds[targetIndex]; 132 const targetTabElement = document.getElementById(`zenpress-tab-${targetTabId}`); 133 if (targetTabElement) { 134 targetTabElement.focus(); 135 // Auto-activate on focus (recommended by W3C for better UX) 136 onSelect(targetTabId); 137 } 138 } 139 }; 140 141 // Auto-activate tab when it receives focus (recommended by W3C) 142 const handleFocus = () => { 143 if (!isSelected) { 144 onSelect(tabId); 145 } 146 }; 147 148 return ( 149 <button 150 ref={tabRef} 151 className={`zenpress-tabs__tab ${isSelected ? 'zenpress-tabs__tab--is-active' : ''} ${className}`.trim()} 152 role="tab" 153 aria-selected={isSelected} 154 aria-controls={`zenpress-tab-panel-${tabId}`} 155 id={`zenpress-tab-${tabId}`} 156 tabIndex={isSelected ? 0 : -1} 157 onClick={() => onSelect(tabId)} 158 onKeyDown={handleKeyDown} 159 onFocus={handleFocus} 160 > 161 {title || children} 162 </button> 163 ); 104 const { selectedTabId, onSelect, orientation, getOrderedTabIds } = 105 useContext(TabsContext); 106 const isSelected = selectedTabId === tabId; 107 const tabRef = useRef(null); 108 109 // Handle keyboard navigation according to W3C ARIA pattern 110 const handleKeyDown = (e) => { 111 const tabIds = getOrderedTabIds(); 112 if (!tabIds || tabIds.length === 0) { 113 return; 114 } 115 116 const currentIndex = tabIds.indexOf(tabId); 117 if (currentIndex === -1) { 118 return; 119 } 120 121 let targetIndex = currentIndex; 122 123 // Handle arrow keys based on orientation 124 if (orientation === 'vertical') { 125 if (e.key === 'ArrowDown') { 126 e.preventDefault(); 127 targetIndex = 128 currentIndex < tabIds.length - 1 ? currentIndex + 1 : 0; 129 } else if (e.key === 'ArrowUp') { 130 e.preventDefault(); 131 targetIndex = 132 currentIndex > 0 ? currentIndex - 1 : tabIds.length - 1; 133 } 134 } else if (e.key === 'ArrowRight') { 135 // Horizontal orientation 136 e.preventDefault(); 137 targetIndex = 138 currentIndex < tabIds.length - 1 ? currentIndex + 1 : 0; 139 } else if (e.key === 'ArrowLeft') { 140 // Horizontal orientation 141 e.preventDefault(); 142 targetIndex = 143 currentIndex > 0 ? currentIndex - 1 : tabIds.length - 1; 144 } 145 146 // Handle Home and End keys 147 if (e.key === 'Home') { 148 e.preventDefault(); 149 targetIndex = 0; 150 } else if (e.key === 'End') { 151 e.preventDefault(); 152 targetIndex = tabIds.length - 1; 153 } 154 155 // Handle Space and Enter for activation (if not auto-activated) 156 if (e.key === ' ' || e.key === 'Enter') { 157 e.preventDefault(); 158 onSelect(tabId); 159 return; 160 } 161 162 // Move focus to target tab if index changed 163 if ( 164 targetIndex !== currentIndex && 165 targetIndex >= 0 && 166 targetIndex < tabIds.length 167 ) { 168 const targetTabId = tabIds[targetIndex]; 169 const targetTabElement = document.getElementById( 170 `zenpress-tab-${targetTabId}` 171 ); 172 if (targetTabElement) { 173 targetTabElement.focus(); 174 // Auto-activate on focus (recommended by W3C for better UX) 175 onSelect(targetTabId); 176 } 177 } 178 }; 179 180 // Auto-activate tab when it receives focus (recommended by W3C) 181 const handleFocus = () => { 182 if (!isSelected) { 183 onSelect(tabId); 184 } 185 }; 186 187 return ( 188 <button 189 ref={tabRef} 190 className={`zenpress-tabs__tab ${isSelected ? 'zenpress-tabs__tab--is-active' : ''} ${className}`.trim()} 191 role="tab" 192 aria-selected={isSelected} 193 aria-controls={`zenpress-tab-panel-${tabId}`} 194 id={`zenpress-tab-${tabId}`} 195 tabIndex={isSelected ? 0 : -1} 196 onClick={() => onSelect(tabId)} 197 onKeyDown={handleKeyDown} 198 onFocus={handleFocus} 199 > 200 {title || children} 201 </button> 202 ); 164 203 }; 165 204 … … 167 206 * TabPanel component - container for tab content 168 207 * 169 * @param {Object} props - Component props.170 * @param {string} props.tabId - Unique identifier matching a Tab's tabId.171 * @param {Object} props.children - Panel content.208 * @param {Object} props - Component props. 209 * @param {string} props.tabId - Unique identifier matching a Tab's tabId. 210 * @param {Object} props.children - Panel content. 172 211 * @return {JSX.Element|null} The tab panel or null if not selected. 173 212 */ 174 213 export const TabPanel = ({ tabId, children }) => { 175 const { selectedTabId } = useContext(TabsContext);176 const panelRef = useRef(null);177 const isSelected = selectedTabId === tabId;178 179 // Check if panel contains focusable elements180 useEffect(() => {181 if (isSelected && panelRef.current) {182 const focusableElements = panelRef.current.querySelectorAll(183 'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"]), input:not([disabled]), select:not([disabled]), textarea:not([disabled])'184 );185 186 // If no focusable elements, make the panel itself focusable187 if (focusableElements.length === 0) {188 panelRef.current.setAttribute('tabindex', '0');189 } else {190 panelRef.current.removeAttribute('tabindex');191 }192 }193 }, [isSelected]);194 195 if (!isSelected) {196 return (197 <div198 className="zenpress-tabs__panel"199 role="tabpanel"200 id={`zenpress-tab-panel-${tabId}`}201 aria-labelledby={`zenpress-tab-${tabId}`}202 hidden203 >204 {children}205 </div>206 );207 }208 209 return (210 <div211 ref={panelRef}212 className="zenpress-tabs__panel"213 role="tabpanel"214 id={`zenpress-tab-panel-${tabId}`}215 aria-labelledby={`zenpress-tab-${tabId}`}216 >217 {children}218 </div>219 );214 const { selectedTabId } = useContext(TabsContext); 215 const panelRef = useRef(null); 216 const isSelected = selectedTabId === tabId; 217 218 // Check if panel contains focusable elements 219 useEffect(() => { 220 if (isSelected && panelRef.current) { 221 const focusableElements = panelRef.current.querySelectorAll( 222 'a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"]), input:not([disabled]), select:not([disabled]), textarea:not([disabled])' 223 ); 224 225 // If no focusable elements, make the panel itself focusable 226 if (focusableElements.length === 0) { 227 panelRef.current.setAttribute('tabindex', '0'); 228 } else { 229 panelRef.current.removeAttribute('tabindex'); 230 } 231 } 232 }, [isSelected]); 233 234 if (!isSelected) { 235 return ( 236 <div 237 className="zenpress-tabs__panel" 238 role="tabpanel" 239 id={`zenpress-tab-panel-${tabId}`} 240 aria-labelledby={`zenpress-tab-${tabId}`} 241 hidden 242 > 243 {children} 244 </div> 245 ); 246 } 247 248 return ( 249 <div 250 ref={panelRef} 251 className="zenpress-tabs__panel" 252 role="tabpanel" 253 id={`zenpress-tab-panel-${tabId}`} 254 aria-labelledby={`zenpress-tab-${tabId}`} 255 > 256 {children} 257 </div> 258 ); 220 259 }; 221 260 -
zenpress/trunk/assets/src/js/hooks/useSettings.js
r3412245 r3448585 15 15 */ 16 16 export const useSettings = () => { 17 const [snippets, setSnippets] = useState({}); 18 const [isSaving, setIsSaving] = useState(false); 19 const { createSuccessNotice, createErrorNotice } = useDispatch(noticesStore); 17 const [snippets, setSnippets] = useState({}); 18 const [isSaving, setIsSaving] = useState(false); 19 const { createSuccessNotice, createErrorNotice } = 20 useDispatch(noticesStore); 20 21 21 useEffect(() => {22 apiFetch({ path: '/wp/v2/settings' })23 .then((settings) => {24 const active = Array.isArray(settings?.zenpress_active_snippets)25 ? settings.zenpress_active_snippets26 : [];22 useEffect(() => { 23 apiFetch({ path: '/wp/v2/settings' }) 24 .then((settings) => { 25 const active = Array.isArray(settings?.zenpress_active_snippets) 26 ? settings.zenpress_active_snippets 27 : []; 27 28 28 const meta = window?.zenpressSnippetsMeta || {};29 const snippetsData = {};29 const meta = window?.zenpressSnippetsMeta || {}; 30 const snippetsData = {}; 30 31 31 Object.keys(meta).forEach((name) => {32 snippetsData[name] = {33 ...meta[name],34 'enable-snippet': active.includes(name),35 };36 });32 Object.keys(meta).forEach((name) => { 33 snippetsData[name] = { 34 ...meta[name], 35 'enable-snippet': active.includes(name), 36 }; 37 }); 37 38 38 setSnippets(snippetsData);39 })40 .catch(() => {41 createErrorNotice(__('Failed to load settings.', 'zenpress'));42 });43 }, [createErrorNotice]);39 setSnippets(snippetsData); 40 }) 41 .catch(() => { 42 createErrorNotice(__('Failed to load settings.', 'zenpress')); 43 }); 44 }, [createErrorNotice]); 44 45 45 const saveSettings = async () => {46 setIsSaving(true);46 const saveSettings = async () => { 47 setIsSaving(true); 47 48 48 const active = Object.keys(snippets).filter((name) => snippets[name]?.['enable-snippet']); 49 const active = Object.keys(snippets).filter( 50 (name) => snippets[name]?.['enable-snippet'] 51 ); 49 52 50 try {51 await apiFetch({52 path: '/wp/v2/settings',53 method: 'POST',54 data: { zenpress_active_snippets: active },55 });56 createSuccessNotice(__('Settings saved.', 'zenpress'));57 } catch {58 createErrorNotice(__('Failed to save settings.', 'zenpress'));59 } finally {60 setIsSaving(false);61 }62 };53 try { 54 await apiFetch({ 55 path: '/wp/v2/settings', 56 method: 'POST', 57 data: { zenpress_active_snippets: active }, 58 }); 59 createSuccessNotice(__('Settings saved.', 'zenpress')); 60 } catch { 61 createErrorNotice(__('Failed to save settings.', 'zenpress')); 62 } finally { 63 setIsSaving(false); 64 } 65 }; 63 66 64 return { snippets, setSnippets, saveSettings, isSaving };67 return { snippets, setSnippets, saveSettings, isSaving }; 65 68 }; -
zenpress/trunk/assets/src/js/pages/SettingsPage.js
r3412245 r3448585 1 import { useState, useEffect , useRef} from '@wordpress/element';1 import { useState, useEffect } from '@wordpress/element'; 2 2 import { __ } from '@wordpress/i18n'; 3 3 import { Button } from '@wordpress/components'; … … 14 14 */ 15 15 export const SettingsPage = () => { 16 const { snippets, setSnippets, saveSettings, isSaving } = useSettings(); 17 const [selectedTabId, setSelectedTabId] = useState(); 18 19 const handleToggleChange = (snippetName) => { 20 setSnippets((prev) => ({ 21 ...prev, 22 [snippetName]: { 23 ...prev[snippetName], 24 'enable-snippet': !prev[snippetName]?.['enable-snippet'], 25 }, 26 })); 27 }; 28 29 const enableAllSnippets = () => { 30 setSnippets((prev) => { 31 const updated = {}; 32 Object.keys(prev).forEach((name) => { 33 updated[name] = { ...prev[name], 'enable-snippet': true }; 34 }); 35 return updated; 36 }); 37 }; 38 39 const resetSettings = () => { 40 setSnippets((prev) => { 41 const updated = {}; 42 Object.keys(prev).forEach((name) => { 43 updated[name] = { ...prev[name], 'enable-snippet': false }; 44 }); 45 return updated; 46 }); 47 }; 48 49 const enableByPreset = (preset) => { 50 setSnippets((prev) => { 51 const updated = {}; 52 Object.entries(prev).forEach(([name, data]) => { 53 const presets = Array.isArray(data?.preset) ? data.preset : []; 54 const isEnabled = presets.includes(preset); 55 updated[name] = { ...data, 'enable-snippet': isEnabled }; 56 }); 57 return updated; 58 }); 59 }; 60 61 // Helper function to capitalize category name 62 const capitalizeCategory = (category) => { 63 if (!category) return category; 64 return category.charAt(0).toUpperCase() + category.slice(1).toLowerCase(); 65 }; 66 67 // Category order: Core, Gutenberg, WooCommerce, ads-blocker, Tools 68 // Using English lowercase values for comparison since categories are stored in lowercase 69 const categoryOrder = ['core', 'gutenberg', 'woocommerce', 'ads-blocker', 'tools']; 70 71 // Group snippets by category, then by subcategory 72 const groupedSnippets = {}; 73 Object.keys(snippets).forEach((snippetName) => { 74 const snippet = snippets[snippetName]; 75 const category = (snippet?.category || __('Uncategorized', 'zenpress')).toLowerCase(); 76 const subcategory = (snippet?.subcategory || __('uncategorized', 'zenpress')).toLowerCase(); 77 78 if (!groupedSnippets[category]) { 79 groupedSnippets[category] = {}; 80 } 81 if (!groupedSnippets[category][subcategory]) { 82 groupedSnippets[category][subcategory] = []; 83 } 84 groupedSnippets[category][subcategory].push({ name: snippetName, data: snippet }); 85 }); 86 87 // Sort categories according to the specified order 88 const sortedCategories = Object.keys(groupedSnippets).sort((a, b) => { 89 const indexA = categoryOrder.indexOf(a.toLowerCase()); 90 const indexB = categoryOrder.indexOf(b.toLowerCase()); 91 92 // If both are in the order array, sort by their position 93 if (indexA !== -1 && indexB !== -1) { 94 return indexA - indexB; 95 } 96 // If only A is in the order array, A comes first 97 if (indexA !== -1) return -1; 98 // If only B is in the order array, B comes first 99 if (indexB !== -1) return 1; 100 // If neither is in the order array, sort alphabetically 101 return a.localeCompare(b, undefined, { sensitivity: 'base' }); 102 }); 103 104 // Set initial selected tab if none is selected 105 useEffect(() => { 106 if (!selectedTabId && sortedCategories.length > 0) { 107 setSelectedTabId(sortedCategories[0]); 108 } 109 }, [selectedTabId, sortedCategories.length]); 110 111 const onSelect = (tabName) => { 112 setSelectedTabId(tabName); 113 }; 114 115 // Add keyboard shortcuts and ensure toggles are keyboard accessible 116 useEffect(() => { 117 const handleKeyDown = (e) => { 118 // Ctrl+S or Cmd+S to save 119 if ((e.ctrlKey || e.metaKey) && e.key === 's') { 120 e.preventDefault(); 121 if (!isSaving) { 122 saveSettings(); 123 } 124 } 125 }; 126 127 document.addEventListener('keydown', handleKeyDown); 128 return () => { 129 document.removeEventListener('keydown', handleKeyDown); 130 }; 131 }, [saveSettings, isSaving]); 132 133 return ( 134 <article className="zenpress-row"> 135 <section className="zenpress-main"> 136 <div className="zenpress-notices"> 137 <Notices /> 138 </div> 139 <div className="zenpress-panel"> 140 <Tabs 141 orientation="vertical" 142 selectedTabId={selectedTabId} 143 onSelect={(selectedId) => { 144 setSelectedTabId(selectedId); 145 onSelect(selectedId); 146 }} 147 > 148 <Tabs.TabList> 149 {sortedCategories.map((category) => { 150 const categoryClass = `zenpress-tabs__tab--category-${category.toLowerCase().replace(/\s+/g, '-')}`; 151 return ( 152 <Tabs.Tab 153 key={category} 154 tabId={category} 155 title={capitalizeCategory(category)} 156 className={categoryClass} 157 > 158 {capitalizeCategory(category)} 159 </Tabs.Tab> 160 ); 161 })} 162 </Tabs.TabList> 163 {sortedCategories.map((category) => { 164 const subcategories = Object.keys(groupedSnippets[category]).sort(); 165 return ( 166 <Tabs.TabPanel key={category} tabId={category}> 167 <h2>{capitalizeCategory(category)}</h2> 168 {subcategories.map((subcategory) => ( 169 <div 170 key={subcategory} 171 className={`zenpress-subcategory zenpress-subcategory-${subcategory.toLowerCase().replace(/\s+/g, '-')}`} 172 > 173 <hr /> 174 <h3>{capitalizeCategory(subcategory)}</h3> 175 {groupedSnippets[category][subcategory].map(({ name, data }) => ( 176 <SnippetToggleControl 177 key={name} 178 label={data.title || name} 179 value={data?.['enable-snippet'] || false} 180 onChange={() => handleToggleChange(name)} 181 help={data.description || ''} 182 /> 183 ))} 184 </div> 185 ))} 186 </Tabs.TabPanel> 187 ); 188 })} 189 </Tabs> 190 <div className="zenpress-actions"> 191 <div className="zenpress-actions-bulk"> 192 <Button variant="tertiary" onClick={enableAllSnippets} __next40pxDefaultSize> 193 {__('Enable all actions', 'zenpress')} 194 </Button> 195 <Button isDestructive onClick={resetSettings} __next40pxDefaultSize> 196 {__('Disable all actions', 'zenpress')} 197 </Button> 198 </div> 199 <SaveButton onClick={saveSettings} isBusy={isSaving} /> 200 </div> 201 </div> 202 </section> 203 <aside className="zenpress-sidebar"> 204 <div className="zenpress-presets"> 205 <div className="zenpress-presets-description"> 206 <h2>{__('Pick configuration preset', 'zenpress')}</h2> 207 <p> 208 {__( 209 "Don't know which features to enable? Quickly configure ZenPress by selecting a preset that matches your site type. Each preset enables optimized features for your specific use case.", 210 'zenpress' 211 )} 212 </p> 213 <hr /> 214 <h3>🖼️ {__('Corporate website', 'zenpress')}</h3> 215 <p> 216 {__( 217 'Optimized for business sites and portfolios. Focuses on security, performance, and removing unnecessary features like RSS feeds and author archives.', 218 'zenpress' 219 )} 220 </p> 221 <Button 222 variant="secondary" 223 onClick={() => enableByPreset('corporate-website')} 224 __next40pxDefaultSize 225 > 226 {__('Enable', 'zenpress')} 227 </Button> 228 <hr /> 229 <h3> 📰 {__('Blog', 'zenpress')}</h3> 230 <p> 231 {__( 232 'Tailored for content-focused blogs. Includes performance and security optimizations while preserving essential blog features like RSS feeds.', 233 'zenpress' 234 )} 235 </p> 236 <Button variant="secondary" onClick={() => enableByPreset('blog')} __next40pxDefaultSize> 237 {__('Enable', 'zenpress')} 238 </Button> 239 <hr /> 240 <h3>🛒 {__('E-commerce', 'zenpress')}</h3> 241 <p> 242 {__( 243 'Designed for WooCommerce stores. Includes all performance and security features plus WooCommerce-specific optimizations for faster checkout.', 244 'zenpress' 245 )} 246 </p> 247 <Button variant="secondary" onClick={() => enableByPreset('ecommerce')} __next40pxDefaultSize> 248 {__('Enable', 'zenpress')} 249 </Button> 250 </div> 251 </div> 252 </aside> 253 </article> 254 ); 16 const { snippets, setSnippets, saveSettings, isSaving } = useSettings(); 17 const [selectedTabId, setSelectedTabId] = useState(); 18 19 const handleToggleChange = (snippetName) => { 20 setSnippets((prev) => ({ 21 ...prev, 22 [snippetName]: { 23 ...prev[snippetName], 24 'enable-snippet': !prev[snippetName]?.['enable-snippet'], 25 }, 26 })); 27 }; 28 29 const enableAllSnippets = () => { 30 setSnippets((prev) => { 31 const updated = {}; 32 Object.keys(prev).forEach((name) => { 33 updated[name] = { ...prev[name], 'enable-snippet': true }; 34 }); 35 return updated; 36 }); 37 }; 38 39 const resetSettings = () => { 40 setSnippets((prev) => { 41 const updated = {}; 42 Object.keys(prev).forEach((name) => { 43 updated[name] = { ...prev[name], 'enable-snippet': false }; 44 }); 45 return updated; 46 }); 47 }; 48 49 const enableByPreset = (preset) => { 50 setSnippets((prev) => { 51 const updated = {}; 52 Object.entries(prev).forEach(([name, data]) => { 53 const presets = Array.isArray(data?.preset) ? data.preset : []; 54 const isEnabled = presets.includes(preset); 55 updated[name] = { ...data, 'enable-snippet': isEnabled }; 56 }); 57 return updated; 58 }); 59 }; 60 61 // Helper function to capitalize category name 62 const capitalizeCategory = (category) => { 63 if (!category) { 64 return category; 65 } 66 return ( 67 category.charAt(0).toUpperCase() + category.slice(1).toLowerCase() 68 ); 69 }; 70 71 // Category order: Core, Gutenberg, WooCommerce, ads-blocker, Tools 72 // Using English lowercase values for comparison since categories are stored in lowercase 73 const categoryOrder = [ 74 'core', 75 'gutenberg', 76 'woocommerce', 77 'ads-blocker', 78 'tools', 79 ]; 80 81 // Group snippets by category, then by subcategory 82 const groupedSnippets = {}; 83 Object.keys(snippets).forEach((snippetName) => { 84 const snippet = snippets[snippetName]; 85 const category = ( 86 snippet?.category || __('Uncategorized', 'zenpress') 87 ).toLowerCase(); 88 const subcategory = ( 89 snippet?.subcategory || __('uncategorized', 'zenpress') 90 ).toLowerCase(); 91 92 if (!groupedSnippets[category]) { 93 groupedSnippets[category] = {}; 94 } 95 if (!groupedSnippets[category][subcategory]) { 96 groupedSnippets[category][subcategory] = []; 97 } 98 groupedSnippets[category][subcategory].push({ 99 name: snippetName, 100 data: snippet, 101 }); 102 }); 103 104 // Sort categories according to the specified order 105 const sortedCategories = Object.keys(groupedSnippets).sort((a, b) => { 106 const indexA = categoryOrder.indexOf(a.toLowerCase()); 107 const indexB = categoryOrder.indexOf(b.toLowerCase()); 108 109 // If both are in the order array, sort by their position 110 if (indexA !== -1 && indexB !== -1) { 111 return indexA - indexB; 112 } 113 // If only A is in the order array, A comes first 114 if (indexA !== -1) { 115 return -1; 116 } 117 // If only B is in the order array, B comes first 118 if (indexB !== -1) { 119 return 1; 120 } 121 // If neither is in the order array, sort alphabetically 122 return a.localeCompare(b, undefined, { sensitivity: 'base' }); 123 }); 124 125 // Set initial selected tab if none is selected 126 useEffect(() => { 127 if (!selectedTabId && sortedCategories.length > 0) { 128 setSelectedTabId(sortedCategories[0]); 129 } 130 }, [selectedTabId, sortedCategories]); 131 132 const onSelect = (tabName) => { 133 setSelectedTabId(tabName); 134 }; 135 136 // Add keyboard shortcuts and ensure toggles are keyboard accessible 137 useEffect(() => { 138 const handleKeyDown = (e) => { 139 // Ctrl+S or Cmd+S to save 140 if ((e.ctrlKey || e.metaKey) && e.key === 's') { 141 e.preventDefault(); 142 if (!isSaving) { 143 saveSettings(); 144 } 145 } 146 }; 147 148 document.addEventListener('keydown', handleKeyDown); 149 return () => { 150 document.removeEventListener('keydown', handleKeyDown); 151 }; 152 }, [saveSettings, isSaving]); 153 154 return ( 155 <article className="zenpress-row"> 156 <section className="zenpress-main"> 157 <div className="zenpress-notices"> 158 <Notices /> 159 </div> 160 <div className="zenpress-panel"> 161 <Tabs 162 orientation="vertical" 163 selectedTabId={selectedTabId} 164 onSelect={(selectedId) => { 165 setSelectedTabId(selectedId); 166 onSelect(selectedId); 167 }} 168 > 169 <Tabs.TabList> 170 {sortedCategories.map((category) => { 171 const categoryClass = `zenpress-tabs__tab--category-${category.toLowerCase().replace(/\s+/g, '-')}`; 172 return ( 173 <Tabs.Tab 174 key={category} 175 tabId={category} 176 title={capitalizeCategory(category)} 177 className={categoryClass} 178 > 179 {capitalizeCategory(category)} 180 </Tabs.Tab> 181 ); 182 })} 183 </Tabs.TabList> 184 {sortedCategories.map((category) => { 185 const subcategories = Object.keys( 186 groupedSnippets[category] 187 ).sort(); 188 return ( 189 <Tabs.TabPanel key={category} tabId={category}> 190 <h2>{capitalizeCategory(category)}</h2> 191 {subcategories.map((subcategory) => ( 192 <div 193 key={subcategory} 194 className={`zenpress-subcategory zenpress-subcategory-${subcategory.toLowerCase().replace(/\s+/g, '-')}`} 195 > 196 <hr /> 197 <h3> 198 {capitalizeCategory( 199 subcategory 200 )} 201 </h3> 202 {groupedSnippets[category][ 203 subcategory 204 ].map(({ name, data }) => ( 205 <SnippetToggleControl 206 key={name} 207 label={data.title || name} 208 value={ 209 data?.[ 210 'enable-snippet' 211 ] || false 212 } 213 onChange={() => 214 handleToggleChange(name) 215 } 216 help={ 217 data.description || '' 218 } 219 /> 220 ))} 221 </div> 222 ))} 223 </Tabs.TabPanel> 224 ); 225 })} 226 </Tabs> 227 <div className="zenpress-actions"> 228 <div className="zenpress-actions-bulk"> 229 <Button 230 variant="tertiary" 231 onClick={enableAllSnippets} 232 __next40pxDefaultSize 233 > 234 {__('Enable all actions', 'zenpress')} 235 </Button> 236 <Button 237 isDestructive 238 onClick={resetSettings} 239 __next40pxDefaultSize 240 > 241 {__('Disable all actions', 'zenpress')} 242 </Button> 243 </div> 244 <SaveButton onClick={saveSettings} isBusy={isSaving} /> 245 </div> 246 </div> 247 </section> 248 <aside className="zenpress-sidebar"> 249 <div className="zenpress-presets"> 250 <div className="zenpress-presets-description"> 251 <h2>{__('Pick configuration preset', 'zenpress')}</h2> 252 <p> 253 {__( 254 "Don't know which features to enable? Quickly configure ZenPress by selecting a preset that matches your site type. Each preset enables optimized features for your specific use case.", 255 'zenpress' 256 )} 257 </p> 258 <hr /> 259 <h3>🖼️ {__('Corporate website', 'zenpress')}</h3> 260 <p> 261 {__( 262 'Optimized for business sites and portfolios. Focuses on security, performance, and removing unnecessary features like RSS feeds and author archives.', 263 'zenpress' 264 )} 265 </p> 266 <Button 267 variant="secondary" 268 onClick={() => enableByPreset('corporate-website')} 269 __next40pxDefaultSize 270 > 271 {__('Enable', 'zenpress')} 272 </Button> 273 <hr /> 274 <h3> 📰 {__('Blog', 'zenpress')}</h3> 275 <p> 276 {__( 277 'Tailored for content-focused blogs. Includes performance and security optimizations while preserving essential blog features like RSS feeds.', 278 'zenpress' 279 )} 280 </p> 281 <Button 282 variant="secondary" 283 onClick={() => enableByPreset('blog')} 284 __next40pxDefaultSize 285 > 286 {__('Enable', 'zenpress')} 287 </Button> 288 <hr /> 289 <h3>🛒 {__('E-commerce', 'zenpress')}</h3> 290 <p> 291 {__( 292 'Designed for WooCommerce stores. Includes all performance and security features plus WooCommerce-specific optimizations for faster checkout.', 293 'zenpress' 294 )} 295 </p> 296 <Button 297 variant="secondary" 298 onClick={() => enableByPreset('ecommerce')} 299 __next40pxDefaultSize 300 > 301 {__('Enable', 'zenpress')} 302 </Button> 303 </div> 304 </div> 305 </aside> 306 </article> 307 ); 255 308 }; -
zenpress/trunk/inc/admin/enqueue.php
r3372200 r3448585 23 23 24 24 $asset = include $asset_file; 25 if (!is_array($asset) || empty($asset['dependencies']) || empty($asset['version'])) { 26 return; 27 } 28 25 29 wp_enqueue_script( 26 30 'zenpress-scripts', 27 31 plugins_url('assets/build/index.js', ZENPRESS_PLUGIN_FILE), 28 $asset['dependencies'],32 (array) $asset['dependencies'], 29 33 $asset['version'], 30 34 true … … 35 39 plugins_url('assets/build/index.css', ZENPRESS_PLUGIN_FILE), 36 40 array_filter( 37 $asset['dependencies'],38 function ($style){41 (array) $asset['dependencies'], 42 static function (string $style): bool { 39 43 return wp_style_is($style, 'registered'); 40 44 } … … 62 66 } 63 67 64 foreach (glob($snippets_path . '*.php') as $file) { 68 $files = glob($snippets_path . '*.php') ?: []; 69 foreach ($files as $file) { 65 70 $basename = basename($file, '.php'); 66 71 $snippets[$basename] = zenpress_extract_snippet_metadata($basename); -
zenpress/trunk/inc/core/constants.php
r3372200 r3448585 10 10 11 11 if (!defined('ZENPRESS_PLUGIN_FILE')) { 12 define( 13 'ZENPRESS_PLUGIN_FILE', 14 __FILE__ === realpath(__FILE__) 15 ? __FILE__ // fallback 16 : dirname(__DIR__, 1) . '/zenpress.php' 17 ); 12 $plugin_file = dirname(__DIR__, 2) . '/zenpress.php'; 13 define('ZENPRESS_PLUGIN_FILE', is_file($plugin_file) ? $plugin_file : __FILE__); 18 14 } 19 15 -
zenpress/trunk/inc/core/metadata.php
r3412245 r3448585 18 18 'subcategory' => '', 19 19 'weight' => 0, 20 'preset' => [] 20 'preset' => [], 21 21 ]; 22 22 … … 31 31 'subcategory' => sanitize_text_field($metadata['subcategory']), 32 32 'weight' => (int) $metadata['weight'], 33 'preset' => array_map('sanitize_text_field', (array) $metadata['preset']) 33 'preset' => array_map('sanitize_text_field', (array) $metadata['preset']), 34 34 ]; 35 35 } -
zenpress/trunk/inc/core/sanitize.php
r3372200 r3448585 11 11 * @return array<string> Sanitized base names. 12 12 */ 13 function zenpress_sanitize_snippets_option( $value): array {13 function zenpress_sanitize_snippets_option(mixed $value): array { 14 14 return array_values( 15 15 array_filter( -
zenpress/trunk/inc/settings/loader.php
r3372200 r3448585 12 12 */ 13 13 function zenpress_load_snippets(string $folder = 'inc/snippets/functions/'): array { 14 // Prevent path traversal if $folder is ever passed from external input. 15 if (str_contains($folder, '..')) { 16 return []; 17 } 18 14 19 $path = ZENPRESS_PLUGIN_DIR . rtrim($folder, '/') . '/'; 15 20 … … 36 41 * BOOT ZENPRESS PLUGIN 37 42 **************************************/ 38 add_action('init', function(): void {43 add_action('init', static function (): void { 39 44 zenpress_load_snippets(); 40 45 }); -
zenpress/trunk/inc/settings/options.php
r3372200 r3448585 20 20 'schema' => [ 21 21 'type' => 'array', 22 'items' => ['type' => 'string'] 23 ] 24 ] 22 'items' => ['type' => 'string'], 23 ], 24 ], 25 25 ] 26 26 ); -
zenpress/trunk/inc/snippets/functions/block-user-enumeration.php
r3372200 r3448585 7 7 if (!is_admin()) { 8 8 // Block enumeration via query string (?author=1). 9 if ( 10 isset($_SERVER['QUERY_STRING']) && 11 preg_match('/author=([0-9]+)/i', sanitize_text_field(wp_unslash($_SERVER['QUERY_STRING']))) 12 ) { 9 $query_string = $_SERVER['QUERY_STRING'] ?? ''; 10 if (preg_match('/author=([0-9]+)/i', sanitize_text_field(wp_unslash($query_string)))) { 13 11 wp_die(esc_html__('Access denied.', 'zenpress'), '', ['response' => 403]); 14 12 } … … 17 15 add_filter( 18 16 'redirect_canonical', 19 static function ( $redirect, $request){17 static function (string|false $redirect, string $request): string|false { 20 18 if (preg_match('/\?author=([0-9]+)(\/*)/i', sanitize_text_field(wp_unslash($request)))) { 21 19 wp_die(esc_html__('Access denied.', 'zenpress'), '', ['response' => 403]); -
zenpress/trunk/inc/snippets/functions/clean-admin-bar.php
r3372200 r3448585 7 7 add_action( 8 8 'admin_bar_menu', 9 static function ($wp_admin_bar) { 10 if (!($wp_admin_bar instanceof WP_Admin_Bar)) { 11 return; 12 } 13 9 static function (WP_Admin_Bar $wp_admin_bar): void { 14 10 /** 15 11 * Backend clean-up. -
zenpress/trunk/inc/snippets/functions/clean-dashboard-items.php
r3372200 r3448585 7 7 add_action( 8 8 'wp_dashboard_setup', 9 static function () {9 static function (): void { 10 10 /** 11 11 * Core widgets. -
zenpress/trunk/inc/snippets/functions/disable-author-archives.php
r3372200 r3448585 8 8 remove_filter('template_redirect', 'redirect_canonical'); 9 9 10 add_action('template_redirect', static function () {10 add_action('template_redirect', static function (): void { 11 11 if (is_author()) { 12 12 global $wp_query; -
zenpress/trunk/inc/snippets/functions/disable-dashicons.php
r3372200 r3448585 5 5 } 6 6 7 add_action('wp_enqueue_scripts', static function () {7 add_action('wp_enqueue_scripts', static function (): void { 8 8 if (!is_user_logged_in()) { 9 9 wp_dequeue_style('dashicons'); -
zenpress/trunk/inc/snippets/functions/disable-default-pattern-categories.php
r3412245 r3448585 11 11 * simplifying the interface and reducing clutter. 12 12 */ 13 add_action('init', static function (){13 add_action('init', static function (): void { 14 14 if (!class_exists('WP_Block_Pattern_Categories_Registry')) { 15 15 return; -
zenpress/trunk/inc/snippets/functions/disable-emoji-scripts.php
r3372200 r3448585 22 22 23 23 // Remove emoji support from TinyMCE editor. 24 add_filter('tiny_mce_plugins', static function ( $plugins){24 add_filter('tiny_mce_plugins', static function (array $plugins): array { 25 25 return array_diff($plugins, ['wpemoji']); 26 26 }); -
zenpress/trunk/inc/snippets/functions/disable-jquery-migrate.php
r3372200 r3448585 5 5 } 6 6 7 add_action('wp_default_scripts', static function ( &$scripts){7 add_action('wp_default_scripts', static function (WP_Scripts $scripts): void { 8 8 if (!is_admin() && isset($scripts->registered['jquery'])) { 9 9 $script = $scripts->registered['jquery']; -
zenpress/trunk/inc/snippets/functions/disable-oembed.php
r3372200 r3448585 5 5 } 6 6 7 add_action('init', static function () {7 add_action('init', static function (): void { 8 8 // Remove oEmbed from query vars. 9 9 global $wp; … … 25 25 26 26 // Remove the wpembed TinyMCE plugin. 27 add_filter('tiny_mce_plugins', function ($plugins){27 add_filter('tiny_mce_plugins', static function (array $plugins): array { 28 28 return array_diff($plugins, ['wpembed']); 29 29 }); 30 30 31 31 // Remove embed-related rewrite rules. 32 add_filter('rewrite_rules_array', function ($rules){32 add_filter('rewrite_rules_array', static function (array $rules): array { 33 33 foreach ($rules as $rule => $rewrite) { 34 if (str pos($rewrite, 'embed=true') !== false) {34 if (str_contains($rewrite, 'embed=true')) { 35 35 unset($rules[$rule]); 36 36 } -
zenpress/trunk/inc/snippets/functions/disable-pdf-thumbnails.php
r3372200 r3448585 5 5 } 6 6 7 add_filter('fallback_intermediate_image_sizes', static function () {7 add_filter('fallback_intermediate_image_sizes', static function (): array { 8 8 return []; 9 9 }); -
zenpress/trunk/inc/snippets/functions/disable-pingback-trackback.php
r3372200 r3448585 6 6 7 7 // Remove X-Pingback header from HTTP responses. 8 add_filter('wp_headers', static function ( $headers){8 add_filter('wp_headers', static function (array $headers): array { 9 9 unset($headers['X-Pingback']); 10 10 … … 13 13 14 14 // Disable pingbacks and trackbacks for new posts. 15 add_action('after_setup_theme', static function () {15 add_action('after_setup_theme', static function (): void { 16 16 update_option('default_ping_status', 'closed'); 17 17 update_option('default_pingback_flag', 0); … … 19 19 20 20 // Prevent self-pingbacks. 21 add_action('pre_ping', static function ( &$links){21 add_action('pre_ping', static function (array &$links): void { 22 22 $home = get_option('home'); 23 23 foreach ($links as $l => $link) { 24 if (str pos($link, $home) === 0) {24 if (str_starts_with($link, $home)) { 25 25 unset($links[$l]); 26 26 } -
zenpress/trunk/inc/snippets/functions/disable-rest-api.php
r3412245 r3448585 14 14 remove_action('xmlrpc_rsd_apis', 'rest_output_rsd'); 15 15 16 // Disable REST API 17 if (version_compare(get_bloginfo('version'), '4.7', '>=')) { 18 add_filter('rest_authentication_errors', 'zenpress_disable_wp_rest_api'); 19 } else { 20 zenpress_disable_wp_rest_api_legacy(); 21 } 22 23 function zenpress_disable_wp_rest_api($access) { 24 if (!is_user_logged_in() && !zenpress_disable_wp_rest_api_allow_access()) { 25 $message = apply_filters('zenpress_disable_wp_rest_api_error', __('REST API restricted to authenticated users.', 'zenpress')); 26 27 return new WP_Error('rest_login_required', $message, ['status' => rest_authorization_required_code()]); 28 } 29 30 return $access; 31 } 32 33 function zenpress_disable_wp_rest_api_allow_access() { 16 /** 17 * Check whether to allow unauthenticated REST API access (bypass). 18 * 19 * Filters zenpress_disable_wp_rest_api_post_var and zenpress_disable_wp_rest_api_server_var 20 * let site owners allow specific POST keys or REQUEST_URI values for webhooks/third-party 21 * integrations. Security note: only use values that are secret or not guessable (e.g. a 22 * random token in POST, or a unique path). Using predictable values can re-enable public 23 * REST API access and weaken the protection provided by the "Disable REST API" snippet. 24 */ 25 $zenpress_disable_wp_rest_api_allow_access = static function (): bool { 34 26 $post_var = apply_filters('zenpress_disable_wp_rest_api_post_var', false); 35 27 $server_var = apply_filters('zenpress_disable_wp_rest_api_server_var', false); 36 28 37 29 if (!empty($post_var)) { 38 if (is_array($post_var)) { 39 foreach($post_var as $var) { 40 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Intentional: Allows bypass via specific POST vars for webhooks/third-party integrations 41 if (isset($_POST[$var]) && !empty($_POST[$var])) { 42 return true; 43 } 44 } 45 } else { 30 $post_vars = is_array($post_var) ? $post_var : [$post_var]; 31 foreach ($post_vars as $var) { 46 32 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Intentional: Allows bypass via specific POST vars for webhooks/third-party integrations 47 if ( isset($_POST[$post_var]) && !empty($_POST[$post_var])) {33 if (!empty($_POST[$var] ?? null)) { 48 34 return true; 49 35 } … … 52 38 53 39 if (!empty($server_var)) { 54 if (is_array($server_var)) { 55 foreach($server_var as $var) { 56 if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === $var) { 57 return true; 58 } 59 } 60 } else { 61 if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === $server_var) { 40 $server_vars = is_array($server_var) ? $server_var : [$server_var]; 41 $request_uri = $_SERVER['REQUEST_URI'] ?? ''; 42 foreach ($server_vars as $var) { 43 if ($request_uri === $var) { 62 44 return true; 63 45 } … … 66 48 67 49 return false; 68 } 50 }; 69 51 70 function zenpress_disable_wp_rest_api_legacy() { 52 // Disable REST API 53 if (version_compare(get_bloginfo('version'), '4.7', '>=')) { 54 add_filter('rest_authentication_errors', static function (WP_Error|bool|null $access) use ($zenpress_disable_wp_rest_api_allow_access): WP_Error|bool|null { 55 if (!is_user_logged_in() && !$zenpress_disable_wp_rest_api_allow_access()) { 56 $message = apply_filters('zenpress_disable_wp_rest_api_error', __('REST API restricted to authenticated users.', 'zenpress')); 57 58 return new WP_Error('rest_login_required', $message, ['status' => rest_authorization_required_code()]); 59 } 60 61 return $access; 62 }); 63 } else { 71 64 // REST API 1.x 72 65 add_filter('json_enabled', '__return_false'); -
zenpress/trunk/inc/snippets/functions/disable-rss.php
r3372200 r3448585 2 2 3 3 if (!defined('ABSPATH')) { 4 exit; 5 } 6 7 // Redirect all feed requests to homepage. 8 function zenpress_disable_all_feeds(): void { 9 wp_safe_redirect(home_url(), 301); 4 10 exit; 5 11 } … … 17 23 remove_action('wp_head', 'feed_links_extra', 3); 18 24 remove_action('wp_head', 'feed_links', 2); 19 20 // Redirect all feed requests to homepage.21 function zenpress_disable_all_feeds() {22 wp_safe_redirect(home_url(), 301);23 exit;24 } -
zenpress/trunk/inc/snippets/functions/disable-woocommerce-cart-fragments.php
r3372200 r3448585 6 6 7 7 if (class_exists('WooCommerce')) { 8 add_action('wp_enqueue_scripts', static function () {8 add_action('wp_enqueue_scripts', static function (): void { 9 9 wp_dequeue_script('wc-cart-fragments'); 10 10 }, 11); -
zenpress/trunk/inc/snippets/functions/disable-woocommerce-scripts-styles.php
r3372200 r3448585 6 6 7 7 if (class_exists('WooCommerce')) { 8 add_action('wp_enqueue_scripts', static function () {8 add_action('wp_enqueue_scripts', static function (): void { 9 9 if (!is_woocommerce() && !is_cart() && !is_checkout() && !is_account_page() && !is_product() && !is_product_category() && !is_shop()) { 10 10 // Dequeue WooCommerce Styles -
zenpress/trunk/inc/snippets/functions/disable-woocommerce-widgets.php
r3372200 r3448585 6 6 7 7 if (class_exists('WooCommerce')) { 8 add_action('widgets_init', static function (){8 add_action('widgets_init', static function (): void { 9 9 unregister_widget('WC_Widget_Products'); 10 10 unregister_widget('WC_Widget_Product_Categories'); -
zenpress/trunk/inc/snippets/functions/hide-woocommerce-version.php
r3372200 r3448585 7 7 if (class_exists('WooCommerce') && !is_admin()) { 8 8 // Remove WooCommerce version from HTTP headers 9 add_filter('wp_headers', static function ($headers) { 10 if (isset($headers['X-WooCommerce-Version'])) { 11 unset($headers['X-WooCommerce-Version']); 12 } 9 add_filter('wp_headers', static function (array $headers): array { 10 unset($headers['X-WooCommerce-Version']); 13 11 14 12 return $headers; … … 16 14 17 15 // Remove WooCommerce version from style URLs 18 add_filter('style_loader_src', static function ( $src){19 if (str pos($src, 'ver=') !== false && strpos($src, 'woocommerce') !== false) {16 add_filter('style_loader_src', static function (string $src): string { 17 if (str_contains($src, 'ver=') && str_contains($src, 'woocommerce')) { 20 18 $src = remove_query_arg('ver', $src); 21 19 } … … 25 23 26 24 // Remove WooCommerce version from script URLs 27 add_filter('script_loader_src', static function ( $src){28 if (str pos($src, 'ver=') !== false && strpos($src, 'woocommerce') !== false) {25 add_filter('script_loader_src', static function (string $src): string { 26 if (str_contains($src, 'ver=') && str_contains($src, 'woocommerce')) { 29 27 $src = remove_query_arg('ver', $src); 30 28 } -
zenpress/trunk/inc/snippets/functions/hide-wordpress-version.php
r3372200 r3448585 9 9 10 10 // Remove WordPress version from generator 11 add_filter('the_generator', static function () {11 add_filter('the_generator', static function (): string { 12 12 return ''; 13 13 }); 14 14 15 15 // Remove WordPress version from script URLs 16 add_filter('script_loader_src', static function ( $src){17 if (str pos($src, 'ver=' . get_bloginfo('version')) !== false) {16 add_filter('script_loader_src', static function (string $src): string { 17 if (str_contains($src, 'ver=' . get_bloginfo('version'))) { 18 18 $src = remove_query_arg('ver', $src); 19 19 } … … 23 23 24 24 // Remove WordPress version from style URLs 25 add_filter('style_loader_src', static function ( $src){26 if (str pos($src, 'ver=' . get_bloginfo('version')) !== false) {25 add_filter('style_loader_src', static function (string $src): string { 26 if (str_contains($src, 'ver=' . get_bloginfo('version'))) { 27 27 $src = remove_query_arg('ver', $src); 28 28 } -
zenpress/trunk/inc/snippets/functions/protect-wp-login.php
r3372200 r3448585 6 6 7 7 // Remove detailed login errors 8 add_filter('login_errors', static function () {8 add_filter('login_errors', static function (): string { 9 9 return __('Login error.', 'zenpress'); 10 10 }); … … 15 15 $BLOCK_DURATION = 300; // 5 minutes 16 16 17 $ipAddress = isset($_SERVER['REMOTE_ADDR']) 18 ? filter_var(wp_unslash($_SERVER['REMOTE_ADDR']), FILTER_VALIDATE_IP) 19 : 'unknown'; 17 $ipAddress = filter_var( 18 wp_unslash($_SERVER['REMOTE_ADDR'] ?? ''), 19 FILTER_VALIDATE_IP 20 ) ?: 'unknown'; 20 21 21 22 $blockKey = 'zenpress_login_block_' . $ipAddress; … … 34 35 wp_die( 35 36 esc_html__('Too many failed login attempts. Try again later.', 'zenpress'), 36 403 37 '', 38 ['response' => 403] 37 39 ); 38 40 } … … 47 49 wp_die( 48 50 esc_html__('Too many failed login attempts. Try again later.', 'zenpress'), 49 403 51 '', 52 ['response' => 403] 50 53 ); 51 54 } -
zenpress/trunk/inc/snippets/functions/remove-gutenberg-unwanted-block-patterns.php
r3372200 r3448585 9 9 10 10 // Remove core block patterns 11 add_action('after_setup_theme', static function () {11 add_action('after_setup_theme', static function (): void { 12 12 remove_theme_support('core-block-patterns'); 13 13 }); -
zenpress/trunk/inc/snippets/functions/remove-woocommerce-patterns.php
r3412245 r3448585 37 37 }); 38 38 39 add_action('init', static function (){39 add_action('init', static function (): void { 40 40 if (!class_exists('WP_Block_Patterns_Registry')) { 41 41 return; … … 44 44 $all_patterns = WP_Block_Patterns_Registry::get_instance()->get_all_registered(); 45 45 foreach ($all_patterns as $pattern) { 46 if (isset($pattern['name'])) { 47 // Use str_starts_with() for PHP 8.0+, fallback to strpos() for older versions 48 $is_woocommerce_pattern = function_exists('str_starts_with') 49 ? str_starts_with($pattern['name'], 'woocommerce-blocks') 50 : strpos($pattern['name'], 'woocommerce-blocks') === 0; 51 52 if ($is_woocommerce_pattern) { 53 unregister_block_pattern($pattern['name']); 54 } 46 if (isset($pattern['name']) && str_starts_with($pattern['name'], 'woocommerce-blocks')) { 47 unregister_block_pattern($pattern['name']); 55 48 } 56 49 } … … 63 56 * sometimes be tied to large transients/caching issues. 64 57 */ 65 add_filter('woocommerce_admin_features', static function($features) { 66 // Ensure $features is an array 67 if (!is_array($features)) { 68 return $features; 69 } 70 58 add_filter('woocommerce_admin_features', static function (array $features): array { 71 59 $feature_to_disable = 'pattern-toolkit-full-composability'; 72 60 -
zenpress/trunk/inc/snippets/meta/clean-dashboard-items.meta.php
r3412245 r3448585 13 13 'title' => __('Clean up the WordPress Dashboard', 'zenpress'), 14 14 'description' => __( 15 'Removes unnecessary widgets and adswidgets from the dashboard. Declutters the admin area and improves usability.',15 'Removes unnecessary and ad widgets from the dashboard. Declutters the admin area and improves usability.', 16 16 'zenpress' 17 17 ), -
zenpress/trunk/inc/snippets/meta/disable-application-passwords.meta.php
r3412245 r3448585 13 13 'title' => __('Disable application passwords', 'zenpress'), 14 14 'description' => __( 15 'Disables WordPress application passwords for all users , improving security. Only disable if API access is not needed.',15 'Disables WordPress application passwords for all users. Improves security; do not enable if you need API or app-based authentication (e.g. mobile apps, REST clients).', 16 16 'zenpress' 17 17 ), … … 19 19 'subcategory' => __('security', 'zenpress'), 20 20 'weight' => 0, 21 'preset' => [ ''],21 'preset' => [], 22 22 ]; 23 23 -
zenpress/trunk/inc/snippets/meta/disable-dashicons.meta.php
r3412245 r3448585 16 16 'zenpress' 17 17 ), 18 'category' => __(' core', 'zenpress'),18 'category' => __('core', 'zenpress'), 19 19 'subcategory' => __('performance', 'zenpress'), 20 20 'weight' => 0, -
zenpress/trunk/inc/snippets/meta/disable-default-pattern-categories.meta.php
r3412245 r3448585 12 12 return [ 13 13 'title' => __('Disable default pattern categories in site editor', 'zenpress'), 14 'description' => __('Removes default pattern categories from the block pattern inserter in the site editor . Disables default categories: featured, about, audio, banner, buttons, call-to-action, columns, contact, footer, gallery, header, media, portfolio, posts, query, services, team, testimonials, text, videos, and any custom categories. This simplifies the interface by hiding category navigation whilepatterns remain accessible.', 'zenpress'),14 'description' => __('Removes default pattern categories from the block pattern inserter in the site editor (e.g. featured, about, audio, banner, buttons, call-to-action, columns, contact, footer, gallery, header, media, portfolio, posts, query, services, team, testimonials, text, videos) and any custom ones. Simplifies the interface; patterns remain accessible.', 'zenpress'), 15 15 'category' => __('gutenberg', 'zenpress'), 16 16 'subcategory' => __('user-interface', 'zenpress'), -
zenpress/trunk/inc/snippets/meta/disable-dns-prefetch.meta.php
r3412245 r3448585 13 13 'title' => __('Disable DNS prefetch', 'zenpress'), 14 14 'description' => __( 15 'Removes DNS prefetch resource hints from wp_head avoids unnecessary DNS lookups and slightly improveperformance on some sites.',15 'Removes DNS prefetch resource hints from wp_head. Avoids unnecessary DNS lookups and slightly improves performance on some sites.', 16 16 'zenpress' 17 17 ), -
zenpress/trunk/inc/snippets/meta/disable-emoji-scripts.meta.php
r3412245 r3448585 16 16 'zenpress' 17 17 ), 18 'category' => __(' core', 'zenpress'),18 'category' => __('core', 'zenpress'), 19 19 'subcategory' => __('performance', 'zenpress'), 20 20 'weight' => 0, -
zenpress/trunk/inc/snippets/meta/disable-jquery-migrate.meta.php
r3412245 r3448585 16 16 'zenpress' 17 17 ), 18 'category' => __(' core', 'zenpress'),18 'category' => __('core', 'zenpress'), 19 19 'subcategory' => __('performance', 'zenpress'), 20 20 'weight' => 0, -
zenpress/trunk/inc/snippets/meta/disable-pdf-thumbnails.meta.php
r3412245 r3448585 12 12 return [ 13 13 'title' => __('Disable PDF thumbnails', 'zenpress'), 14 'description' => __('Prevents WordPress from generating thumbnails for uploaded PDF files by removing fallback image sizes. saves storage space and improves performance by avoiding unnecessary image generation.', 'zenpress'),15 'category' => __(' core', 'zenpress'),14 'description' => __('Prevents WordPress from generating thumbnails for uploaded PDF files by removing fallback image sizes. Saves storage space and improves performance by avoiding unnecessary image generation.', 'zenpress'), 15 'category' => __('core', 'zenpress'), 16 16 'subcategory' => __('performance', 'zenpress'), 17 17 'weight' => 0, -
zenpress/trunk/inc/snippets/meta/disable-pingback-trackback.meta.php
r3412245 r3448585 12 12 return [ 13 13 'title' => __('Disable pingback and trackback', 'zenpress'), 14 'description' => __('Removes the X-Pingback header, disables pingbacks and trackbacks on new posts, and prevents self-pingbacks. reduces spam, blocks potential DDoS vectors, and slightly improves performance by avoiding useless requests.', 'zenpress'),14 'description' => __('Removes the X-Pingback header, disables pingbacks and trackbacks on new posts, and prevents self-pingbacks. Reduces spam, blocks potential DDoS vectors, and slightly improves performance by avoiding useless requests.', 'zenpress'), 15 15 'category' => __('core', 'zenpress'), 16 16 'subcategory' => __('security', 'zenpress'), -
zenpress/trunk/inc/snippets/meta/disable-rest-api.meta.php
r3412245 r3448585 14 14 'title' => __('Disable REST API for visitors not logged into WordPress', 'zenpress'), 15 15 'description' => __( 16 ' Disable the WP REST API for visitors not logged into WordPress.',16 'Restricts the WP REST API to logged-in users only; unauthenticated requests receive an error. Bypass filters (zenpress_disable_wp_rest_api_post_var, zenpress_disable_wp_rest_api_server_var) allow specific integrations (e.g. webhooks); use non-guessable values only.', 17 17 'zenpress' 18 18 ), -
zenpress/trunk/inc/snippets/meta/protect-wp-login.meta.php
r3412245 r3448585 12 12 return [ 13 13 'title' => __('Protect the wp-login form from brute force attacks', 'zenpress'), 14 'description' => __('Removes detailed login error messages and limits failed login attempts per IP address. Blocks further attempts for a set duration after too many failures. Improves security by mitigating bruteforce attacks.', 'zenpress'),14 'description' => __('Removes detailed login error messages and limits failed login attempts per IP. Blocks further attempts for 5 minutes after 5 failed tries. Mitigates brute-force attacks.', 'zenpress'), 15 15 'category' => __('tools', 'zenpress'), 16 16 'subcategory' => __('security', 'zenpress'), -
zenpress/trunk/inc/snippets/meta/remove-gutenberg-unwanted-block-patterns.meta.php
r3412245 r3448585 13 13 'title' => __('Remove WordPress default remote block patterns', 'zenpress'), 14 14 'description' => __('Prevents WordPress from loading remote block patterns and removes the built-in core block patterns. Reduces editor clutter and improves performance by avoiding unnecessary data loading.', 'zenpress'), 15 'category' => __(' gutenberg', 'zenpress'),15 'category' => __('gutenberg', 'zenpress'), 16 16 'subcategory' => __('performance', 'zenpress'), 17 17 'weight' => 0, -
zenpress/trunk/inc/snippets/meta/remove-rest-api-link.meta.php
r3412245 r3448585 12 12 return [ 13 13 'title' => __('Remove REST API links', 'zenpress'), 14 'description' => __('Prevents WordPress from adding REST API discovery links to the head section of the site. reduces unnecessary HTML output and slightly improves performance while keeping REST API functionality available.', 'zenpress'),14 'description' => __('Prevents WordPress from adding REST API discovery links to the head section of the site. Reduces unnecessary HTML output and slightly improves performance while keeping REST API functionality available.', 'zenpress'), 15 15 'category' => __('core', 'zenpress'), 16 16 'subcategory' => __('performance', 'zenpress'), -
zenpress/trunk/languages/zenpress.pot
r3412245 r3448585 1 # Copyright (C) 202 5Quentin Le Duff1 # Copyright (C) 2026 Quentin Le Duff 2 2 # This file is distributed under the GPL v2 or later. 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: ZenPress - Cleaner, Lighter, Faster WP 2. 1.0\n"5 "Project-Id-Version: ZenPress - Cleaner, Lighter, Faster WP 2.2.0\n" 6 6 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/zenpress\n" 7 7 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" … … 10 10 "Content-Type: text/plain; charset=UTF-8\n" 11 11 "Content-Transfer-Encoding: 8bit\n" 12 "POT-Creation-Date: 202 5-12-05T13:56:04+00:00\n"12 "POT-Creation-Date: 2026-01-28T10:05:55+00:00\n" 13 13 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 14 "X-Generator: WP-CLI 2.12.0\n" … … 142 142 msgstr "" 143 143 144 #: inc/snippets/functions/block-user-enumeration.php:1 3145 #: inc/snippets/functions/block-user-enumeration.php: 21144 #: inc/snippets/functions/block-user-enumeration.php:11 145 #: inc/snippets/functions/block-user-enumeration.php:19 146 146 msgid "Access denied." 147 147 msgstr "" 148 148 149 #: inc/snippets/functions/disable-rest-api.php: 25149 #: inc/snippets/functions/disable-rest-api.php:56 150 150 msgid "REST API restricted to authenticated users." 151 151 msgstr "" … … 155 155 msgstr "" 156 156 157 #: inc/snippets/functions/protect-wp-login.php:3 5158 #: inc/snippets/functions/protect-wp-login.php: 48157 #: inc/snippets/functions/protect-wp-login.php:36 158 #: inc/snippets/functions/protect-wp-login.php:50 159 159 msgid "Too many failed login attempts. Try again later." 160 160 msgstr "" … … 173 173 #: inc/snippets/meta/disable-application-passwords.meta.php:18 174 174 #: inc/snippets/meta/disable-author-archives.meta.php:18 175 #: inc/snippets/meta/disable-autosave.meta.php:18 176 #: inc/snippets/meta/disable-capital-p-dangit.meta.php:18 177 #: inc/snippets/meta/disable-dashicons.meta.php:18 175 178 #: inc/snippets/meta/disable-dns-prefetch.meta.php:18 179 #: inc/snippets/meta/disable-emoji-scripts.meta.php:18 180 #: inc/snippets/meta/disable-jquery-migrate.meta.php:18 176 181 #: inc/snippets/meta/disable-login-language-selector.meta.php:15 177 182 #: inc/snippets/meta/disable-oembed.meta.php:15 183 #: inc/snippets/meta/disable-password-strength-meter.meta.php:18 184 #: inc/snippets/meta/disable-pdf-thumbnails.meta.php:15 178 185 #: inc/snippets/meta/disable-pingback-trackback.meta.php:15 179 186 #: inc/snippets/meta/disable-rest-api.meta.php:19 … … 181 188 #: inc/snippets/meta/disable-shortlink.meta.php:15 182 189 #: inc/snippets/meta/disable-wlw-manifest.meta.php:15 190 #: inc/snippets/meta/disable-wordpress-default-lazy-loading.meta.php:18 183 191 #: inc/snippets/meta/disable-xmlrpc-rsdlink.meta.php:15 184 192 #: inc/snippets/meta/hide-wordpress-version.meta.php:15 193 #: inc/snippets/meta/limit-post-revisions.meta.php:18 194 #: inc/snippets/meta/remove-help-button.meta.php:18 185 195 #: inc/snippets/meta/remove-rest-api-link.meta.php:15 196 #: inc/snippets/meta/remove-thanks-for-using-wordpress-in-footer.meta.php:18 197 #: inc/snippets/meta/remove-wordpress-logo.meta.php:18 186 198 msgid "core" 187 199 msgstr "" … … 211 223 #: inc/snippets/meta/disable-default-pattern-categories.meta.php:16 212 224 #: inc/snippets/meta/disable-login-language-selector.meta.php:16 225 #: inc/snippets/meta/remove-help-button.meta.php:19 226 #: inc/snippets/meta/remove-thanks-for-using-wordpress-in-footer.meta.php:19 227 #: inc/snippets/meta/remove-wordpress-logo.meta.php:19 213 228 msgid "user-interface" 214 229 msgstr "" … … 219 234 220 235 #: inc/snippets/meta/clean-dashboard-items.meta.php:14 221 msgid "Removes unnecessary widgets and adswidgets from the dashboard. Declutters the admin area and improves usability."236 msgid "Removes unnecessary and ad widgets from the dashboard. Declutters the admin area and improves usability." 222 237 msgstr "" 223 238 … … 235 250 236 251 #: inc/snippets/meta/disable-adjacent-posts.meta.php:19 252 #: inc/snippets/meta/disable-autosave.meta.php:19 253 #: inc/snippets/meta/disable-capital-p-dangit.meta.php:19 237 254 #: inc/snippets/meta/disable-dashicons.meta.php:19 238 255 #: inc/snippets/meta/disable-dns-prefetch.meta.php:19 … … 240 257 #: inc/snippets/meta/disable-jquery-migrate.meta.php:19 241 258 #: inc/snippets/meta/disable-oembed.meta.php:16 259 #: inc/snippets/meta/disable-password-strength-meter.meta.php:19 242 260 #: inc/snippets/meta/disable-pdf-thumbnails.meta.php:16 243 261 #: inc/snippets/meta/disable-rss.meta.php:16 … … 248 266 #: inc/snippets/meta/disable-woocommerce-stripe-scripts.meta.php:16 249 267 #: inc/snippets/meta/disable-woocommerce-widgets.meta.php:16 268 #: inc/snippets/meta/disable-wordpress-default-lazy-loading.meta.php:19 269 #: inc/snippets/meta/limit-post-revisions.meta.php:19 250 270 #: inc/snippets/meta/remove-gutenberg-unwanted-block-patterns.meta.php:16 251 271 #: inc/snippets/meta/remove-rest-api-link.meta.php:16 … … 260 280 261 281 #: inc/snippets/meta/disable-application-passwords.meta.php:14 262 msgid "Disables WordPress application passwords for all users , improving security. Only disable if API access is not needed."282 msgid "Disables WordPress application passwords for all users. Improves security; do not enable if you need API or app-based authentication (e.g. mobile apps, REST clients)." 263 283 msgstr "" 264 284 … … 271 291 msgstr "" 272 292 293 #: inc/snippets/meta/disable-autosave.meta.php:13 294 msgid "Disable autosave" 295 msgstr "" 296 297 #: inc/snippets/meta/disable-autosave.meta.php:14 298 msgid "Stops the classic editor from autosaving drafts periodically. Reduces database writes and heartbeat traffic. The block editor may still use its own autosave; this targets the legacy post editor." 299 msgstr "" 300 301 #: inc/snippets/meta/disable-capital-p-dangit.meta.php:13 302 msgid "Disable capital_P_dangit filter" 303 msgstr "" 304 305 #: inc/snippets/meta/disable-capital-p-dangit.meta.php:14 306 msgid "Removes the filter that forces \"Wordpress\" to \"WordPress\" in titles, content, comments, and widget text. Saves a small amount of processing on each page load." 307 msgstr "" 308 273 309 #: inc/snippets/meta/disable-dashicons.meta.php:13 274 310 msgid "Disable dashicons" … … 279 315 msgstr "" 280 316 281 #: inc/snippets/meta/disable-dashicons.meta.php:18282 #: inc/snippets/meta/disable-emoji-scripts.meta.php:18283 #: inc/snippets/meta/disable-jquery-migrate.meta.php:18284 #: inc/snippets/meta/disable-pdf-thumbnails.meta.php:15285 msgid " core"286 msgstr ""287 288 317 #: inc/snippets/meta/disable-default-pattern-categories.meta.php:13 289 318 msgid "Disable default pattern categories in site editor" … … 291 320 292 321 #: inc/snippets/meta/disable-default-pattern-categories.meta.php:14 293 msgid "Removes default pattern categories from the block pattern inserter in the site editor . Disables default categories: featured, about, audio, banner, buttons, call-to-action, columns, contact, footer, gallery, header, media, portfolio, posts, query, services, team, testimonials, text, videos, and any custom categories. This simplifies the interface by hiding category navigation whilepatterns remain accessible."322 msgid "Removes default pattern categories from the block pattern inserter in the site editor (e.g. featured, about, audio, banner, buttons, call-to-action, columns, contact, footer, gallery, header, media, portfolio, posts, query, services, team, testimonials, text, videos) and any custom ones. Simplifies the interface; patterns remain accessible." 294 323 msgstr "" 295 324 296 325 #: inc/snippets/meta/disable-default-pattern-categories.meta.php:15 326 #: inc/snippets/meta/remove-gutenberg-unwanted-block-patterns.meta.php:15 297 327 #: inc/snippets/meta/separate-gutenberg-core-block-styles.meta.php:15 298 328 msgid "gutenberg" … … 304 334 305 335 #: inc/snippets/meta/disable-dns-prefetch.meta.php:14 306 msgid "Removes DNS prefetch resource hints from wp_head avoids unnecessary DNS lookups and slightly improveperformance on some sites."336 msgid "Removes DNS prefetch resource hints from wp_head. Avoids unnecessary DNS lookups and slightly improves performance on some sites." 307 337 msgstr "" 308 338 … … 339 369 msgstr "" 340 370 371 #: inc/snippets/meta/disable-password-strength-meter.meta.php:13 372 msgid "Disable Password Strength Meter" 373 msgstr "" 374 375 #: inc/snippets/meta/disable-password-strength-meter.meta.php:14 376 msgid "Prevents the password strength meter and zxcvbn scripts from loading on profile, login, and similar pages. Saves roughly 400KB and reduces script parsing. Users will not see the strength indicator when choosing passwords." 377 msgstr "" 378 341 379 #: inc/snippets/meta/disable-pdf-thumbnails.meta.php:13 342 380 msgid "Disable PDF thumbnails" … … 344 382 345 383 #: inc/snippets/meta/disable-pdf-thumbnails.meta.php:14 346 msgid "Prevents WordPress from generating thumbnails for uploaded PDF files by removing fallback image sizes. saves storage space and improves performance by avoiding unnecessary image generation."384 msgid "Prevents WordPress from generating thumbnails for uploaded PDF files by removing fallback image sizes. Saves storage space and improves performance by avoiding unnecessary image generation." 347 385 msgstr "" 348 386 … … 352 390 353 391 #: inc/snippets/meta/disable-pingback-trackback.meta.php:14 354 msgid "Removes the X-Pingback header, disables pingbacks and trackbacks on new posts, and prevents self-pingbacks. reduces spam, blocks potential DDoS vectors, and slightly improves performance by avoiding useless requests."392 msgid "Removes the X-Pingback header, disables pingbacks and trackbacks on new posts, and prevents self-pingbacks. Reduces spam, blocks potential DDoS vectors, and slightly improves performance by avoiding useless requests." 355 393 msgstr "" 356 394 … … 360 398 361 399 #: inc/snippets/meta/disable-rest-api.meta.php:15 362 msgid " Disable the WP REST API for visitors not logged into WordPress."400 msgid "Restricts the WP REST API to logged-in users only; unauthenticated requests receive an error. Bypass filters (zenpress_disable_wp_rest_api_post_var, zenpress_disable_wp_rest_api_server_var) allow specific integrations (e.g. webhooks); use non-guessable values only." 363 401 msgstr "" 364 402 … … 428 466 msgstr "" 429 467 468 #: inc/snippets/meta/disable-wordpress-default-lazy-loading.meta.php:13 469 msgid "Disable WordPress default lazy loading" 470 msgstr "" 471 472 #: inc/snippets/meta/disable-wordpress-default-lazy-loading.meta.php:14 473 msgid "Stops WordPress from adding loading=\"lazy\" to images and iframes. Use only if you rely on a theme, plugin, or CDN for lazy loading, or if you prefer to load all media immediately." 474 msgstr "" 475 430 476 #: inc/snippets/meta/disable-xmlrpc-rsdlink.meta.php:13 431 477 msgid "Disable XML-RPC and remove RSD link" … … 452 498 msgstr "" 453 499 500 #: inc/snippets/meta/limit-post-revisions.meta.php:13 501 msgid "Limit post revision to 10" 502 msgstr "" 503 504 #: inc/snippets/meta/limit-post-revisions.meta.php:14 505 msgid "Keeps at most 10 revisions per post (or page). Older revisions are deleted when new ones are created. Reduces database size and improves performance." 506 msgstr "" 507 454 508 #: inc/snippets/meta/protect-wp-login.meta.php:13 455 509 msgid "Protect the wp-login form from brute force attacks" … … 457 511 458 512 #: inc/snippets/meta/protect-wp-login.meta.php:14 459 msgid "Removes detailed login error messages and limits failed login attempts per IP address. Blocks further attempts for a set duration after too many failures. Improves security by mitigating bruteforce attacks."513 msgid "Removes detailed login error messages and limits failed login attempts per IP. Blocks further attempts for 5 minutes after 5 failed tries. Mitigates brute-force attacks." 460 514 msgstr "" 461 515 … … 472 526 msgstr "" 473 527 474 #: inc/snippets/meta/remove-gutenberg-unwanted-block-patterns.meta.php:15 475 msgid " gutenberg" 528 #: inc/snippets/meta/remove-help-button.meta.php:13 529 msgid "Remove \"Help\" button" 530 msgstr "" 531 532 #: inc/snippets/meta/remove-help-button.meta.php:14 533 msgid "Hides the Help tab and panel on all admin screens. Reduces clutter for users who do not use the in-app help." 476 534 msgstr "" 477 535 … … 481 539 482 540 #: inc/snippets/meta/remove-rest-api-link.meta.php:14 483 msgid "Prevents WordPress from adding REST API discovery links to the head section of the site. reduces unnecessary HTML output and slightly improves performance while keeping REST API functionality available." 541 msgid "Prevents WordPress from adding REST API discovery links to the head section of the site. Reduces unnecessary HTML output and slightly improves performance while keeping REST API functionality available." 542 msgstr "" 543 544 #: inc/snippets/meta/remove-thanks-for-using-wordpress-in-footer.meta.php:13 545 msgid "Remove \"Thanks for using WordPress\" in footer" 546 msgstr "" 547 548 #: inc/snippets/meta/remove-thanks-for-using-wordpress-in-footer.meta.php:14 549 msgid "Removes the \"Thank you for creating with WordPress\" message from the admin footer. Cleans up the interface." 484 550 msgstr "" 485 551 … … 490 556 #: inc/snippets/meta/remove-woocommerce-patterns.meta.php:14 491 557 msgid "Removes all WooCommerce block patterns to avoid unnecessary pattern registration in the editor." 558 msgstr "" 559 560 #: inc/snippets/meta/remove-wordpress-logo.meta.php:13 561 msgid "Remove WordPress logo" 562 msgstr "" 563 564 #: inc/snippets/meta/remove-wordpress-logo.meta.php:14 565 msgid "Removes the WordPress logo and its dropdown from the admin bar. Cleans up the interface for a more neutral or branded look." 492 566 msgstr "" 493 567 -
zenpress/trunk/readme.txt
r3412245 r3448585 5 5 Requires at least: 6.0 6 6 Tested up to: 6.9 7 Stable tag: 2. 1.08 Requires PHP: 7.47 Stable tag: 2.2.0 8 Requires PHP: 8.3 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html/ … … 34 34 35 35 = Settings subpage 🧰 = 36 * Organized interface with vertical tabs for easy navigation between categories (Core, Gutenberg, WooCommerce, Tools).36 * Organized interface with vertical tabs for easy navigation between categories (Core, Gutenberg, WooCommerce, Ads-blocker, Tools). 37 37 * Features grouped by subcategories (Performance, Security, User Interface) with visual icons for quick identification. 38 38 * Three ready-to-use presets: Corporate website, Blog, and E-commerce - each optimized for specific site types. … … 53 53 * Disable WordPress shortlink. 54 54 * Disable WLW link. 55 * Remove WordPress default remote block patterns.56 55 * Remove REST API links. 57 * Separate loading of core block styles. 56 * Disable capital_P_dangit filter. 57 * Disable autosave. 58 * Limit post revision to 10. 59 * Disable Password Strength Meter. 60 * Disable WordPress default lazy loading. 58 61 59 62 = Core - Security = … … 66 69 * Disable XML-RPC and remove RSD link. 67 70 * Hide WordPress version. 68 * Protect the wp-login form from brute force attacks.69 71 70 72 = Core - User Interface = 71 73 72 74 * Clean up the WordPress admin bar. 73 * Clean up the WordPress Dashboard.74 75 * Disable the login language selector. 76 * Remove WordPress logo. 77 * Remove "Help" button. 78 * Remove "Thanks for using WordPress" in footer. 75 79 76 80 = WooCommerce - Performance = … … 91 95 * Disable default pattern categories in site editor. 92 96 97 = Ads-blocker - User Interface = 98 * Clean up the WordPress Dashboard. 99 100 = Tools - Security = 101 * Protect the wp-login form from brute force attacks. 102 93 103 = Presets = 94 104 * Corporate website / Portfolio: Optimized for business sites and portfolios. Focuses on security, performance, and removing unnecessary features like RSS feeds and author archives. … … 112 122 * Manage Heartbeat API (frontend + backend + admin whitelist). 113 123 114 = Performance =115 * Disable capital_P_dangit filter.116 * Disable autosave.117 * Disable post revision.118 * Disable Password Strength Meter.119 * Disable WordPress default lazy loading.120 121 124 = User Interface = 122 * Remove "howdy" from admin bar.123 * Remove WordPress logo.124 * Remove "Help button".125 * Remove "Thanks for using WordPress" in footer.126 125 * Remove "site health" page. 127 126 * Remove "Privacy tools". … … 175 174 In addition, if you like the plugin then I'd love for you to [leave a review](https://wordpress.org/support/plugin/zenpress/reviews/). Tell all your friends about it too! 176 175 176 = Does ZenPress work with my existing caching / optimization plugins? = 177 178 Yes. ZenPress focuses on disabling unnecessary core features and plugin bloat; it does not handle page caching, minification, or image optimization. It is designed to work alongside plugins like Cache Enabler, Autoptimize, and most object-cache solutions. If a performance feature overlaps, simply disable it in one of the tools. 179 180 = Can ZenPress break my theme or plugins? = 181 182 Potentially, yes—especially if your theme or plugins rely on features you disable (RSS feeds, oEmbed, REST API, emojis, WooCommerce assets, etc.). That’s why each snippet includes clear descriptions and categories (performance, security, UI). Always test changes on a staging site first and enable snippets gradually. 183 184 = How do I know which snippets are safe to enable? = 185 186 If you are unsure, start with a preset (Corporate, Blog, E‑commerce) and then adjust. For manual tuning, prefer UI and performance snippets first (dashicons, emojis, dashboard/admin-bar cleanup) before more invasive ones (REST API, XML‑RPC, RSS). After each change, check: frontend pages, login, editor, and (if used) WooCommerce flows. 187 188 = What happens if I disable the REST API? = 189 190 Unauthenticated REST requests will be blocked except for any explicit bypasses configured via the filters documented in the snippet (`zenpress_disable_wp_rest_api_post_var`, `zenpress_disable_wp_rest_api_server_var`). Core features and plugins that depend on public REST endpoints (some blocks, headless/front-end apps, third-party integrations) may stop working until you whitelist the required routes. 191 192 **Important**: If you don't know how to configure bypass filters or whitelist specific routes, don't activate this snippet. It can break functionality that relies on public REST API access. 193 194 = Does ZenPress store any personal data or phone home? = 195 196 No. ZenPress does not collect, store, or transmit any personal data. It does not contact external services or include third‑party trackers. All settings are stored in standard WordPress options and remain on your site only. 197 198 = Is ZenPress multisite compatible? = 199 200 ZenPress can be network‑activated or activated per site. Settings are stored per site, so each site in a network can have different snippets enabled. As with any optimization/security plugin, test network‑wide changes carefully, especially REST API and XML‑RPC related snippets. 201 177 202 = I have a suggestion = 178 203 179 204 Nice ! If you can't find anything in the roadmap, feel free to submit your suggestion on the support page! If you know how to code, you can even contribute on GitHub. 180 205 181 = Does this plugin work with PHP 8 =182 183 Yes, it 's been tested actively and works from PHP 7.4 to PHP 8.4.184 185 206 == Changelog == 207 208 = 2.2.0 = 209 - Global: Dropped PHP 7.4 support and aligned minimum PHP requirement with the currently recommended WordPress version. 210 - Global: Replaced strpos() with str_contains() and str_starts_with() throughout all snippets. 211 - Snippets: Converted snippets to use direct execution pattern where applicable for better performance. 212 - Security: Protect wp-login: fix wp_die() so blocked login responses return HTTP 403 instead of 200. 213 - Security: Loader: guard against path traversal in zenpress_load_snippets() when the folder argument contains '..'. 214 - Security: Disable REST API: document in-code that bypass filters (zenpress_disable_wp_rest_api_post_var, zenpress_disable_wp_rest_api_server_var) should use non-guessable values only. 215 - New actionable function: Disable autosave. 216 - New actionable function: Disable capital_P_dangit filter. 217 - New actionable function: Disable Password Strength Meter. 218 - New actionable function: Disable WordPress default lazy loading. 219 - New actionable function: Limit post revision to 10. 220 - New actionable function: Remove "Help" button. 221 - New actionable function: Remove "Thanks for using WordPress" in footer. 222 - New actionable function: Remove WordPress logo. 186 223 187 224 = 2.1.0 = … … 296 333 == Upgrade Notice == 297 334 335 = 2.2.0 = 336 - Breaking: PHP 8.3 is now required (PHP 7.4 support dropped). Major code modernization with improved type safety and performance. 337 - Security: HTTP 403 on login block, path traversal guard in snippet loader, and in-code docs for REST API bypass filters. 338 298 339 = 1.0.0.1 = 299 340 -
zenpress/trunk/zenpress.php
r3412245 r3448585 12 12 * Plugin Name: ZenPress - Cleaner, Lighter, Faster WP 13 13 * Description: Easily speed up and strengthen your WordPress site by cleaning out unnecessary features and protecting weak points. 14 * Version: 2. 1.014 * Version: 2.2.0 15 15 * Plugin URI: https://wordpress.org/plugins/zenpress/ 16 16 * Author: Quentin Le Duff … … 20 20 * Requires at least: 6.0 21 21 * Tested up to: 6.9 22 * Requires PHP: 7.422 * Requires PHP: 8.3 23 23 * License URI: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html/ 24 24 * License: GPL v2 or later
Note: See TracChangeset
for help on using the changeset viewer.