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' );