diff --git a/CHANGELOG.md b/CHANGELOG.md index 164b1ec..e7b0073 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,32 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## 1.3.0 (2026-06-26) +## 1.6.0 (2026-06-26) + +### Cleanup + +- Removed dead `admin/partials/display.php` file — confirmed unused (admin UI rendered by `Codetot_Optimization_Admin_Options_Page`) +- Removed empty boilerplate files: `admin/js/codetot-optimization-admin.js` and `admin/css/codetot-optimization-admin.css` + +### Security + +- Added `esc_html()` to `$GLOBALS['title']` output in admin options page (prevents XSS) +- Code style consistency for `$_GET`/`$_POST` access in Gravity Forms class + +## 1.5.0 (2026-06-26) + +### Added + +- **Remove query strings** (`?ver=`) from enqueued scripts and styles — improves cache hit rate on CDN and proxy caches +- **Disable self pingbacks** — prevents WordPress from sending pingbacks to your own domain +- **Disable REST API for non-authenticated users** — returns 401 for unauthenticated REST requests; keeps public endpoints intact +- **Remove default dashboard widgets** — cleans up Quick Draft, WP News, Site Health, At a Glance, Activity, and Welcome panel +- **Disable attachment pages** — 301 redirects attachment pages to parent post (or homepage if no parent) +- **Remove jQuery Migrate** — removes the jquery-migrate dependency from jquery on front-end +- **Disable native XML sitemaps** — disables WordPress 5.5+ built-in sitemaps (let SEO plugins handle it) +- **Remove front-end dashicons** — dequeues dashicons stylesheet on front-end when not used by theme + +## 1.4.0 (2026-06-26) ### Added diff --git a/README.txt b/README.txt index 61da0f9..96b800d 100644 --- a/README.txt +++ b/README.txt @@ -4,7 +4,7 @@ Donate link: https://codetot.com Tags: optimization, compress, settings, codetot Requires at least: 6.0 Tested up to: 6.8 -Stable tag: 1.3.0 +Stable tag: 1.6.0 Requires PHP: 8.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -78,6 +78,29 @@ Yes, if you are using our themes. == Changelog == += 1.6.0 = +* **[Cleanup]** Removed dead `admin/partials/display.php` file (not used anywhere) +* **[Cleanup]** Removed empty boilerplate `admin/js/codetot-optimization-admin.js` and `admin/css/codetot-optimization-admin.css` +* **[Security]** Added `esc_html()` to `$GLOBALS['title']` output in admin options page +* **[Security]** Code style consistency for `$_GET`/`$_POST` access in Gravity Forms class + += 1.5.0 = +* **[New]** Remove query strings (`?ver=`) from static assets for improved cache hit rate +* **[New]** Disable self pingbacks to reduce server load +* **[New]** Disable REST API for non-authenticated users (returns 401) +* **[New]** Remove default dashboard widgets (Quick Draft, WP News, etc.) +* **[New]** Disable attachment pages (301 redirect to parent post or home) +* **[New]** Remove jQuery Migrate script from front-end +* **[New]** Disable native WordPress XML sitemaps (WP 5.5+) +* **[New]** Remove dashicons styles on front-end when not used by theme + += 1.4.0 = +* **[Fix]** `use_block_editor_for_post` filter was incorrectly registered as `add_action` → now uses proper `add_filter` +* **[Fix]** Plugin deactivation never cleaned up options — `delete_option()` was wrapped in `add_action('init', ...)` that never runs during deactivation +* **[Fix]** `update_option()` calls for default comment/ping statuses moved from per-request admin to activation hook (runs once) +* **[Fix]** `uninstall.php` now cleans up plugin options from database +* **[Perf]** Centralized option loading — `Codetot_Optimization::get_options()` with static cache; 1 DB call per request instead of 3 + = 1.3.0 = * Official PHP 8.0-8.4 support, bumped Requires PHP to 8.0 * Fix undefined variable warning in admin options page under PHP 8.0 diff --git a/admin/class-codetot-optimization-admin.php b/admin/class-codetot-optimization-admin.php index 7982ff5..0293dec 100644 --- a/admin/class-codetot-optimization-admin.php +++ b/admin/class-codetot-optimization-admin.php @@ -104,7 +104,15 @@ public function get_global_keys() 'disable_feed' => __('Feed', 'codetot-optimization'), 'disable_shortlink' => __('Shortlink', 'codetot-optimization'), 'disable_wlw_manifest' => __('WLW Manifest', 'codetot-optimization'), - 'disable_inline_comment_style' => __('Inline Comment Style', 'codetot-optimization') + 'disable_inline_comment_style' => __('Inline Comment Style', 'codetot-optimization'), + 'disable_query_strings' => __('Query Strings from Assets', 'codetot-optimization'), + 'disable_self_pingbacks' => __('Self Pingbacks', 'codetot-optimization'), + 'disable_rest_api' => __('REST API (non-auth)', 'codetot-optimization'), + 'remove_dashboard_widgets' => __('Dashboard Widgets', 'codetot-optimization'), + 'disable_attachment_pages' => __('Attachment Pages', 'codetot-optimization'), + 'remove_jquery_migrate' => __('jQuery Migrate', 'codetot-optimization'), + 'disable_xml_sitemaps' => __('XML Sitemaps', 'codetot-optimization'), + 'remove_frontend_dashicons' => __('Front-end Dashicons', 'codetot-optimization'), ]; } diff --git a/admin/css/codetot-optimization-admin.css b/admin/css/codetot-optimization-admin.css deleted file mode 100644 index 00c8c7f..0000000 --- a/admin/css/codetot-optimization-admin.css +++ /dev/null @@ -1,4 +0,0 @@ -/** - * All of the CSS for your admin-specific functionality should be - * included in this file. - */ \ No newline at end of file diff --git a/admin/js/codetot-optimization-admin.js b/admin/js/codetot-optimization-admin.js deleted file mode 100644 index b04717f..0000000 --- a/admin/js/codetot-optimization-admin.js +++ /dev/null @@ -1,32 +0,0 @@ -(function( $ ) { - 'use strict'; - - /** - * All of the code for your admin-facing JavaScript source - * should reside in this file. - * - * Note: It has been assumed you will write jQuery code here, so the - * $ function reference has been prepared for usage within the scope - * of this function. - * - * This enables you to define handlers, for when the DOM is ready: - * - * $(function() { - * - * }); - * - * When the window is loaded: - * - * $( window ).load(function() { - * - * }); - * - * ...and/or other possibilities. - * - * Ideally, it is not considered best practise to attach more than a - * single DOM-ready or window-load handler for a particular page. - * Although scripts in the WordPress core, Plugins and Themes may be - * practising this, we should strive to set a better example in our own work. - */ - -})( jQuery ); diff --git a/admin/partials/display.php b/admin/partials/display.php deleted file mode 100644 index 57da15b..0000000 --- a/admin/partials/display.php +++ /dev/null @@ -1,26 +0,0 @@ -ct_optimization_options = get_option( 'ct_optimization_option_name' ); ?> - -
-

- -
- -
-
diff --git a/codetot-optimization.php b/codetot-optimization.php index baf654c..3176b6c 100644 --- a/codetot-optimization.php +++ b/codetot-optimization.php @@ -8,7 +8,7 @@ * Plugin Name: CT Optimization * Plugin URI: https://codetot.com * Description: Provides settings for enable/disable WordPress core features and some tweaks for ACF, Gravity Forms, such like Enable CDN, Lazyload assets. - * Version: 1.3.0 + * Version: 1.6.0 * Requires at least: 6.0 * Requires PHP: 8.0 * Author: CODE TOT JSC @@ -24,7 +24,7 @@ die; } -define( 'CODETOT_OPTIMIZATION_VERSION', '1.3.0' ); +define( 'CODETOT_OPTIMIZATION_VERSION', '1.6.0' ); define( 'CODETOT_OPTIMIZATION_PATH', plugin_dir_path(__FILE__) ); define( 'CODETOT_OPTIMIZATION_URL', plugin_dir_url(__FILE__) ); diff --git a/includes/class-codetot-admin-options-page.php b/includes/class-codetot-admin-options-page.php index 2c2f3a3..c0f051d 100644 --- a/includes/class-codetot-admin-options-page.php +++ b/includes/class-codetot-admin-options-page.php @@ -350,7 +350,7 @@ protected function build_menu_page($page_key) $this->options = get_option($page_key, array()); ?>
-

diff --git a/includes/class-codetot-optimization-activator.php b/includes/class-codetot-optimization-activator.php index 0de1a44..778e23f 100644 --- a/includes/class-codetot-optimization-activator.php +++ b/includes/class-codetot-optimization-activator.php @@ -30,7 +30,13 @@ class Codetot_Optimization_Activator { * @since 1.0.0 */ public static function activate() { - + // Ensure default comment/ping statuses are set once on activation, + // not on every admin page load. + if ( ! get_option( 'ct_optimization_activation_flushed', false ) ) { + update_option( 'default_ping_status', 'closed' ); + update_option( 'default_comment_status', 'closed' ); + update_option( 'ct_optimization_activation_flushed', true ); + } } } diff --git a/includes/class-codetot-optimization-assets.php b/includes/class-codetot-optimization-assets.php index 8741382..07668d2 100644 --- a/includes/class-codetot-optimization-assets.php +++ b/includes/class-codetot-optimization-assets.php @@ -37,25 +37,12 @@ public final static function instance() public function __construct() { - $options = get_option('ct-optimization'); + $this->options = Codetot_Optimization::get_options(); - if (empty($options)) { + if (empty($this->options)) { return; } - foreach ($options as $key => $option) { - $key = str_replace('-', '_', $key); - - if ($option === 'yes') { - // Convert yes/no to true/false - $this->options[$key] = true; - } elseif ($option === 'no') { - $this->options[$key] = false; - } else { - $this->options[$key] = $option; - } - } - add_filter('clean_url', array($this, 'add_async_forscript'), 11, 1); if (!empty($this->options['load_lazysizes_scripts'])) { diff --git a/includes/class-codetot-optimization-deactivator.php b/includes/class-codetot-optimization-deactivator.php index 1048d0f..c1ee004 100644 --- a/includes/class-codetot-optimization-deactivator.php +++ b/includes/class-codetot-optimization-deactivator.php @@ -26,9 +26,7 @@ class Codetot_Optimization_Deactivator { * @since 1.0.0 */ public static function deactivate() { - add_action('init', function() { - delete_option('ct-optimization'); - }); + delete_option('ct-optimization'); } } diff --git a/includes/class-codetot-optimization-gravity-forms.php b/includes/class-codetot-optimization-gravity-forms.php index ab8b47d..3d5c115 100644 --- a/includes/class-codetot-optimization-gravity-forms.php +++ b/includes/class-codetot-optimization-gravity-forms.php @@ -38,25 +38,12 @@ public final static function instance() public function __construct() { - $options = get_option('ct-optimization'); + $this->options = Codetot_Optimization::get_options(); - if (empty($options)) { + if (empty($this->options)) { return; } - foreach ( $options as $key => $option ) { - $key = str_replace('-', '_', $key); - - if ( $option === 'yes' ) { - // Convert yes/no to true/false - $this->options[$key] = true; - } elseif( $option === 'no' ) { - $this->options[$key] = false; - } else { - $this->options[$key] = $option; - } - } - if ( ! empty( $this->options['disable_gravity_forms_default_styles'] ) ) { add_action('gform_enqueue_scripts', array($this, 'disable_gravity_forms_styles')); } @@ -113,8 +100,8 @@ public function do_wrap_gform_cdata() if ( is_admin() || (defined('DOING_AJAX') && DOING_AJAX) - || isset($_POST['gform_ajax']) - || isset($_GET['gf_page']) // Admin page (eg. form preview). + || isset( $_POST['gform_ajax'] ) + || isset( $_GET['gf_page'] ) // Admin page (eg. form preview). || doing_action('wp_footer') || did_action('wp_footer') ) { diff --git a/includes/class-codetot-optimization-process.php b/includes/class-codetot-optimization-process.php index 2cc76ad..4f3e51a 100644 --- a/includes/class-codetot-optimization-process.php +++ b/includes/class-codetot-optimization-process.php @@ -50,25 +50,12 @@ public final static function instance() public function __construct() { - $options = get_option('ct-optimization'); + $this->options = Codetot_Optimization::get_options(); - if (empty($options)) { + if (empty($this->options)) { return; } - foreach ($options as $key => $option) { - $key = str_replace('-', '_', $key); - - if ($option === 'yes') { - // Convert yes/no to true/false - $this->options[$key] = true; - } elseif($option === 'no') { - $this->options[$key] = false; - } else { - $this->options[$key] = $option; - } - } - // Global Settings add_action('init', array($this, 'check_gutenberg')); add_action('init', array($this, 'check_gutenberg_widget')); @@ -89,12 +76,23 @@ public function __construct() // Advanced Settings add_action('init', array($this, 'check_cdn')); + + // New Optimization Features + add_filter('script_loader_src', array($this, 'remove_query_strings'), 15, 1); + add_filter('style_loader_src', array($this, 'remove_query_strings'), 15, 1); + add_action('init', array($this, 'check_self_pingbacks')); + add_action('init', array($this, 'check_rest_api')); + add_action('init', array($this, 'check_dashboard_widgets')); + add_action('init', array($this, 'check_attachment_pages')); + add_action('init', array($this, 'check_jquery_migrate')); + add_action('init', array($this, 'check_xml_sitemaps')); + add_action('wp_enqueue_scripts', array($this, 'check_frontend_dashicons'), 100); } public function check_gutenberg() { if (!empty($this->options['disable_gutenberg_block_editor'])) { - add_action('use_block_editor_for_post', '__return_false'); + add_filter('use_block_editor_for_post', '__return_false'); add_action('wp_enqueue_scripts', array($this, 'disable_wp_block_assets'), 100); remove_action( 'try_gutenberg_panel', 'wp_try_gutenberg_panel' ); @@ -222,10 +220,6 @@ public function check_oembed() public function check_xmlrpc() { if (!empty($this->options['disable_xmlrpc'])) { - if (is_admin()) { - update_option('default_ping_status', 'closed'); // Might do something else here to reduce our queries - } - add_action('xmlrpc_enabled', '__return_false'); add_action('pre_update_option_enable_xmlrpc', '__return_false'); add_action('pre_option_enable_xmlrpc', '__return_zero'); @@ -258,11 +252,6 @@ public function check_xmlrpc() public function check_comments() { if (!empty($this->options['disable_comments'])) { - // by default, comments are closed. - if (is_admin()) { - update_option('default_comment_status', 'closed'); - } - // Closes plugins add_action('comments_open', '__return_false', 20, 2); add_action('pings_open', '__return_false', 20, 2); @@ -451,4 +440,155 @@ public function calculate_image_srcset($sources) } return $sources; } + + // ========================================================================== + // Phase 2: New Optimization Features + // ========================================================================== + + /** + * Remove query strings (?ver=) from enqueued scripts and styles. + * + * @since 1.5.0 + * @param string $src + * @return string + */ + public function remove_query_strings($src) + { + if (!empty($this->options['disable_query_strings'])) { + $src = remove_query_arg('ver', $src); + } + return $src; + } + + /** + * Disable self pingbacks. + * + * @since 1.5.0 + */ + public function check_self_pingbacks() + { + if (!empty($this->options['disable_self_pingbacks'])) { + add_action('pre_ping', function (&$links) { + $home = get_option('home'); + foreach ($links as $l => $link) { + if (strpos($link, $home) === 0) { + unset($links[$l]); + } + } + }); + } + } + + /** + * Disable REST API for non-authenticated users. + * + * @since 1.5.0 + */ + public function check_rest_api() + { + if (!empty($this->options['disable_rest_api'])) { + add_filter('rest_authentication_errors', function ($result) { + if (!empty($result)) { + return $result; + } + if (!is_user_logged_in()) { + return new WP_Error( + 'rest_not_logged_in', + __('You are not currently logged in.', 'codetot-optimization'), + array('status' => 401) + ); + } + return $result; + }); + } + } + + /** + * Remove default dashboard widgets. + * + * @since 1.5.0 + */ + public function check_dashboard_widgets() + { + if (!empty($this->options['remove_dashboard_widgets'])) { + add_action('wp_dashboard_setup', function () { + remove_meta_box('dashboard_quick_press', 'dashboard', 'side'); + remove_meta_box('dashboard_primary', 'dashboard', 'side'); + remove_meta_box('dashboard_secondary', 'dashboard', 'side'); + remove_meta_box('dashboard_site_health', 'dashboard', 'normal'); + remove_meta_box('dashboard_right_now', 'dashboard', 'normal'); + remove_meta_box('dashboard_activity', 'dashboard', 'normal'); + remove_meta_box('dashboard_incoming_links', 'dashboard', 'normal'); + remove_meta_box('dashboard_plugins', 'dashboard', 'normal'); + remove_meta_box('dashboard_recent_comments', 'dashboard', 'normal'); + remove_meta_box('dashboard_recent_drafts', 'dashboard', 'normal'); + remove_action('welcome_panel', 'wp_welcome_panel'); + }); + } + } + + /** + * Disable attachment pages — redirect to parent or home. + * + * @since 1.5.0 + */ + public function check_attachment_pages() + { + if (!empty($this->options['disable_attachment_pages'])) { + add_action('template_redirect', function () { + if (is_attachment()) { + global $post; + if ($post && $post->post_parent) { + wp_redirect(get_permalink($post->post_parent), 301); + } else { + wp_redirect(home_url(), 301); + } + exit; + } + }); + } + } + + /** + * Remove jQuery Migrate script. + * + * @since 1.5.0 + */ + public function check_jquery_migrate() + { + if (!empty($this->options['remove_jquery_migrate'])) { + add_action('wp_default_scripts', function ($scripts) { + if (!empty($scripts->registered['jquery'])) { + $jquery_deps = $scripts->registered['jquery']->deps; + $scripts->registered['jquery']->deps = array_diff($jquery_deps, array('jquery-migrate')); + } + }); + } + } + + /** + * Disable native WordPress XML sitemaps (WP 5.5+). + * + * @since 1.5.0 + */ + public function check_xml_sitemaps() + { + if (!empty($this->options['disable_xml_sitemaps'])) { + add_filter('wp_sitemaps_enabled', '__return_false'); + } + } + + /** + * Remove dashicons styles on front-end when not used by theme. + * + * @since 1.5.0 + */ + public function check_frontend_dashicons() + { + if (!empty($this->options['remove_frontend_dashicons'])) { + if (!is_admin() && !is_customize_preview()) { + wp_dequeue_style('dashicons'); + } + } + } } diff --git a/includes/class-codetot-optimization.php b/includes/class-codetot-optimization.php index 798492d..f412a0b 100644 --- a/includes/class-codetot-optimization.php +++ b/includes/class-codetot-optimization.php @@ -42,6 +42,47 @@ public function __construct() { Codetot_Optimization_Process::instance(); } + /** + * Get parsed plugin options — called once per request, cached statically. + * + * Reads the serialized 'ct-optimization' option, converts yes/no to booleans, + * and returns a flat array. All sub-classes use this instead of calling + * get_option() independently. + * + * @since 1.4.0 + * @return array + */ + public static function get_options() { + static $options = null; + + if ( $options !== null ) { + return $options; + } + + $raw = get_option( 'ct-optimization' ); + + if ( empty( $raw ) || ! is_array( $raw ) ) { + $options = array(); + return $options; + } + + $parsed = array(); + foreach ( $raw as $key => $value ) { + $key = str_replace( '-', '_', $key ); + + if ( $value === 'yes' ) { + $parsed[ $key ] = true; + } elseif ( $value === 'no' ) { + $parsed[ $key ] = false; + } else { + $parsed[ $key ] = $value; + } + } + + $options = $parsed; + return $options; + } + /** * @since 1.0.0 * @access private diff --git a/uninstall.php b/uninstall.php index da2127b..7c760a6 100644 --- a/uninstall.php +++ b/uninstall.php @@ -29,3 +29,7 @@ if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) { exit; } + +// Clean up plugin options. +delete_option( 'ct-optimization' ); +delete_option( 'ct_optimization_activation_flushed' );