diff --git a/README.md b/README.md
index 7692f71..463dd10 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-[](https://github.com/emilia-capital/aaa-option-optimizer/actions/workflows/cs.yml)
-[](https://github.com/Emilia-Capital/aaa-option-optimizer/actions/workflows/phpstan.yml)
-[](https://github.com/emilia-capital/aaa-option-optimizer/actions/workflows/lint.yml)
-[](https://github.com/emilia-capital/aaa-option-optimizer/actions/workflows/security.yml)
+[](https://github.com/ProgressPlanner/aaa-option-optimizer/actions/workflows/cs.yml)
+[](https://github.com/ProgressPlanner/aaa-option-optimizer/actions/workflows/phpstan.yml)
+[](https://github.com/ProgressPlanner/aaa-option-optimizer/actions/workflows/lint.yml)
+[](https://github.com/ProgressPlanner/aaa-option-optimizer/actions/workflows/security.yml)
[](https://wordpress.org/plugins/aaa-option-optimizer/)

@@ -10,7 +10,7 @@
[](https://wordpress.org/support/plugin/aaa-option-optimizer/reviews/)
[](https://github.com/ProgressPlanner/aaa-option-optimizer/blob/main/LICENSE)
-[](https://playground.wordpress.net/#%7B%22landingPage%22:%22/wp-admin/tools.php?page=aaa-option-optimizer%22,%22features%22:%7B%22networking%22:true%7D,%22steps%22:%5B%7B%22step%22:%22defineWpConfigConsts%22,%22consts%22:%7B%22IS_PLAYGROUND_PREVIEW%22:true%7D%7D,%7B%22step%22:%22login%22,%22username%22:%22admin%22,%22password%22:%22password%22%7D,%7B%22step%22:%22installPlugin%22,%22pluginZipFile%22:%7B%22resource%22:%22url%22,%22url%22:%22https://bypass-cors.altha.workers.dev/https://github.com/Emilia-Capital/aaa-option-optimizer/archive/refs/heads/develop.zip%22%7D,%22options%22:%7B%22activate%22:true%7D%7D%5D%7D)
+[](https://playground.wordpress.net/#%7B%22landingPage%22:%22/wp-admin/tools.php?page=aaa-option-optimizer%22,%22features%22:%7B%22networking%22:true%7D,%22steps%22:%5B%7B%22step%22:%22defineWpConfigConsts%22,%22consts%22:%7B%22IS_PLAYGROUND_PREVIEW%22:true%7D%7D,%7B%22step%22:%22login%22,%22username%22:%22admin%22,%22password%22:%22password%22%7D,%7B%22step%22:%22installPlugin%22,%22pluginZipFile%22:%7B%22resource%22:%22url%22,%22url%22:%22https://bypass-cors.altha.workers.dev/https://github.com/ProgressPlanner/aaa-option-optimizer/archive/refs/heads/develop.zip%22%7D,%22options%22:%7B%22activate%22:true%7D%7D%5D%7D)

@@ -26,7 +26,7 @@ Install this plugin, and go through your entire site. Best is to use it normally
### Why the AAA prefix in the plugin name?
-Because the plugin needs to measure options being loaded, it benefits from being loaded itself first. As WordPress loads plugins alphabetically,
+Because the plugin needs to measure options being loaded, it benefits from being loaded itself first. As WordPress loads plugins alphabetically,
starting the name with AAA made sense.
### Do I need to take precautions?
@@ -35,7 +35,7 @@ Yes!! Backup your database.
### How can I add recognized plugins?
-Please do a pull request via GitHub on [this file](https://github.com/Emilia-Capital/aaa-option-optimizer/blob/develop/known-plugins/known-plugins.json) in the plugin.
+Please do a pull request via GitHub on [this file](https://github.com/ProgressPlanner/aaa-option-optimizer/blob/develop/known-plugins/known-plugins.json) in the plugin.
### How can I report security bugs?
diff --git a/aaa-option-optimizer.php b/aaa-option-optimizer.php
index c568fa6..057afeb 100644
--- a/aaa-option-optimizer.php
+++ b/aaa-option-optimizer.php
@@ -2,7 +2,7 @@
/**
* Plugin that tracks autoloaded options usage and allows the user to optimize them.
*
- * @package Emilia\OptionOptimizer
+ * @package Progress_Planner\OptionOptimizer
*
* Plugin Name: AAA Option Optimizer
* Plugin URI: https://progressplanner.com/plugins/aaa-option-optimizer/
@@ -27,12 +27,16 @@
register_deactivation_hook( __FILE__, 'aaa_option_optimizer_deactivation' );
/**
- * Activation hooked function to store start stats.
+ * Activation hooked function to store start stats and create table.
*
* @return void
*/
function aaa_option_optimizer_activation() {
global $wpdb;
+
+ // Create the custom table.
+ Progress_Planner\OptionOptimizer\Database::create_table();
+
$autoload_values = \wp_autoload_values_to_autoload();
$placeholders = implode( ',', array_fill( 0, count( $autoload_values ), '%s' ) );
@@ -42,19 +46,23 @@ function aaa_option_optimizer_activation() {
);
// phpcs:enable WordPress.DB
- update_option(
- 'option_optimizer',
- [
- 'starting_point_kb' => ( $result->autoload_size / 1024 ),
- 'starting_point_num' => $result->count,
- 'starting_point_date' => current_time( 'mysql' ),
- 'used_options' => [],
- 'settings' => [
- 'option_tracking' => 'pre_option',
+ // Only set starting point if not already set (preserve existing data).
+ $existing = get_option( 'option_optimizer' );
+ if ( empty( $existing['starting_point_date'] ) ) {
+ update_option(
+ 'option_optimizer',
+ [
+ 'starting_point_kb' => ( $result->autoload_size / 1024 ),
+ 'starting_point_num' => $result->count,
+ 'starting_point_date' => current_time( 'mysql' ),
+ 'used_options' => [], // For backward compatibility.
+ 'settings' => [
+ 'option_tracking' => 'pre_option',
+ ],
],
- ],
- false
- );
+ false
+ );
+ }
}
/**
@@ -67,13 +75,33 @@ function aaa_option_optimizer_deactivation() {
update_option( 'option_optimizer', $aaa_option_value, false );
}
+/**
+ * Ensure database table exists.
+ * Runs on plugins_loaded to handle existing installs that don't trigger activation.
+ * Migration is handled via AJAX on the plugin admin page.
+ *
+ * @return void
+ */
+function aaa_option_optimizer_maybe_upgrade() {
+ // Only run on admin pages, not on AJAX or REST requests to avoid race conditions.
+ if ( ! is_admin() || wp_doing_ajax() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
+ return;
+ }
+
+ // Check if table exists, create if not.
+ if ( ! Progress_Planner\OptionOptimizer\Database::table_exists() ) {
+ Progress_Planner\OptionOptimizer\Database::create_table();
+ }
+}
+add_action( 'plugins_loaded', 'aaa_option_optimizer_maybe_upgrade' );
+
/**
* Initializes the plugin.
*
* @return void
*/
function aaa_option_optimizer_init() {
- $optimizer = new Emilia\OptionOptimizer\Plugin();
+ $optimizer = new Progress_Planner\OptionOptimizer\Plugin();
$optimizer->register_hooks();
}
diff --git a/composer.json b/composer.json
index 918fc4f..8ee7555 100644
--- a/composer.json
+++ b/composer.json
@@ -1,5 +1,5 @@
{
- "name": "emilia/aaa-option-optimizer",
+ "name": "progress-planner/aaa-option-optimizer",
"description": "Plugin that tracks autoloaded options usage and allows the user to optimize them.",
"type": "wordpress-plugin",
"license": "GPL-3.0-or-later",
diff --git a/composer.lock b/composer.lock
index 68b99dc..d186fc9 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "b719f200626be0650504e5dddce6534d",
+ "content-hash": "68e73cf23844dd66f4d8b3b14eef46cd",
"packages": [],
"packages-dev": [
{
diff --git a/js/admin-script.js b/js/admin-script.js
index 45cfd25..3531a73 100644
--- a/js/admin-script.js
+++ b/js/admin-script.js
@@ -631,4 +631,74 @@ jQuery( document ).ready( function () {
initializeDataTable( selector );
}
} );
+
+ // Migration functionality.
+ jQuery( '#aaa-start-migration' ).on( 'click', function ( e ) {
+ e.preventDefault();
+ const button = jQuery( this );
+ const progressContainer = jQuery( '#aaa-migration-progress' );
+ const progressBar = jQuery( '#aaa-migration-progress-bar' );
+ const statusText = jQuery( '#aaa-migration-status' );
+ const total = aaaOptionOptimizer.migration.total;
+
+ button.prop( 'disabled', true );
+ progressContainer.show();
+ statusText.text( aaaOptionOptimizer.i18n.migrating );
+
+ /**
+ * Performs a single migration chunk via AJAX.
+ */
+ function migrateChunk() {
+ jQuery.ajax( {
+ url:
+ aaaOptionOptimizer.root +
+ 'aaa-option-optimizer/v1/migrate',
+ method: 'POST',
+ beforeSend: ( xhr ) =>
+ xhr.setRequestHeader(
+ 'X-WP-Nonce',
+ aaaOptionOptimizer.nonce
+ ),
+ success( response ) {
+ if ( ! response.success ) {
+ statusText.text(
+ response.message ||
+ aaaOptionOptimizer.i18n.migrationError
+ );
+ button.prop( 'disabled', false );
+ return;
+ }
+
+ const migrated = total - response.remaining;
+ const percent = Math.round( ( migrated / total ) * 100 );
+
+ progressBar.css( 'width', percent + '%' );
+ statusText.text(
+ aaaOptionOptimizer.i18n.migratedOf
+ .replace( '%1$d', migrated )
+ .replace( '%2$d', total )
+ );
+
+ if ( response.remaining > 0 ) {
+ // Continue with next chunk.
+ migrateChunk();
+ } else {
+ // Migration complete.
+ statusText.text(
+ aaaOptionOptimizer.i18n.migrationComplete
+ );
+ setTimeout( function () {
+ window.location.reload();
+ }, 1000 );
+ }
+ },
+ error() {
+ statusText.text( aaaOptionOptimizer.i18n.migrationError );
+ button.prop( 'disabled', false );
+ },
+ } );
+ }
+
+ migrateChunk();
+ } );
} );
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 5b93576..7c4ebd2 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -108,8 +108,9 @@
-
+
+
diff --git a/readme.txt b/readme.txt
index a070888..da538c8 100644
--- a/readme.txt
+++ b/readme.txt
@@ -29,7 +29,7 @@ Yes!! Backup your database.
= Where can I report bugs? =
-Please use [our GitHub](https://github.com/emilia-Capital/aaa-option-optimizer/) for reporting bugs or making code suggestions. Feel free to use the forums for asking questions too, of course.
+Please use [our GitHub](https://github.com/ProgressPlanner/aaa-option-optimizer/) for reporting bugs or making code suggestions. Feel free to use the forums for asking questions too, of course.
For security issues, please see the next question.
@@ -39,7 +39,7 @@ You can report security bugs through the Patchstack Vulnerability Disclosure Pro
= How can I add recognized plugins? =
-Please do a pull request via GitHub on [this file](https://github.com/Emilia-Capital/aaa-option-optimizer/blob/develop/known-plugins/known-plugins.json) in the plugin.
+Please do a pull request via GitHub on [this file](https://github.com/ProgressPlanner/aaa-option-optimizer/blob/develop/known-plugins/known-plugins.json) in the plugin.
== Installation ==
1. Search for AAA Option Optimizer on the repository.
@@ -113,7 +113,7 @@ Implement the missing functionality to create an option with value `false` when
= 1.1 =
The plugin now recognizes plugins from which the options came (thanks to a great pull by [Rogier Lankhorst](https://profiles.wordpress.org/rogierlankhorst/)). If you're a plugin developer and want your plugin's options
-properly recognized, please do a pull request [on this file](https://github.com/Emilia-Capital/aaa-option-optimizer/blob/main/known-plugins/known-plugins.json).
+properly recognized, please do a pull request [on this file](https://github.com/ProgressPlanner/aaa-option-optimizer/blob/main/known-plugins/known-plugins.json).
Small enhancements:
diff --git a/src/autoload.php b/src/autoload.php
index 502fda2..4f04541 100644
--- a/src/autoload.php
+++ b/src/autoload.php
@@ -2,12 +2,12 @@
/**
* Autoload PHP classes for the plugin.
*
- * @package Emilia\OptionOptimizer
+ * @package Progress_Planner\OptionOptimizer
*/
spl_autoload_register(
function ( $class_name ) {
- $prefix = 'Emilia\\OptionOptimizer\\';
+ $prefix = 'Progress_Planner\\OptionOptimizer\\';
if ( 0 !== \strpos( $class_name, $prefix ) ) {
return;
diff --git a/src/class-admin-page.php b/src/class-admin-page.php
index 4e7d6dd..e45a243 100644
--- a/src/class-admin-page.php
+++ b/src/class-admin-page.php
@@ -2,10 +2,10 @@
/**
* Admin page functionality for AAA Option Optimizer.
*
- * @package Emilia\OptionOptimizer
+ * @package Progress_Planner\OptionOptimizer
*/
-namespace Emilia\OptionOptimizer;
+namespace Progress_Planner\OptionOptimizer;
/**
* Admin page functionality for AAA Option Optimizer.
@@ -189,9 +189,10 @@ public function enqueue_scripts( $hook ) {
'aaa-option-optimizer-admin-js',
'aaaOptionOptimizer',
[
- 'root' => \esc_url_raw( \rest_url() ),
- 'nonce' => \wp_create_nonce( 'wp_rest' ),
- 'i18n' => [
+ 'root' => \esc_url_raw( \rest_url() ),
+ 'nonce' => \wp_create_nonce( 'wp_rest' ),
+ 'migration' => Database::get_migration_status(),
+ 'i18n' => [
'filterBySource' => \esc_html__( 'Filter by source', 'aaa-option-optimizer' ),
'showValue' => \esc_html__( 'Show', 'aaa-option-optimizer' ),
'addAutoload' => \esc_html__( 'Add autoload', 'aaa-option-optimizer' ),
@@ -207,6 +208,11 @@ public function enqueue_scripts( $hook ) {
'apply' => \esc_html__( 'Apply', 'aaa-option-optimizer' ),
'search' => \esc_html__( 'Search:', 'aaa-option-optimizer' ),
+ 'migrating' => \esc_html__( 'Migrating...', 'aaa-option-optimizer' ),
+ 'migrationComplete' => \esc_html__( 'Migration complete! Reloading page...', 'aaa-option-optimizer' ),
+ 'migrationError' => \esc_html__( 'Migration error. Please try again.', 'aaa-option-optimizer' ),
+ /* translators: %1$d: number of migrated options, %2$d: total number of options */
+ 'migratedOf' => \esc_html__( 'Migrated %1$d of %2$d options', 'aaa-option-optimizer' ),
'entries' => [
'_' => \esc_html__( 'entries', 'aaa-option-optimizer' ),
'1' => \esc_html__( 'entry', 'aaa-option-optimizer' ),
@@ -260,10 +266,39 @@ public function render_admin_page_ajax() {
$wpdb->prepare( "SELECT count(*) AS count, SUM( LENGTH( option_value ) ) as autoload_size FROM {$wpdb->options} WHERE autoload IN ( $placeholders )", $autoload_values )
);
// phpcs:enable WordPress.DB
+
+ // Check if migration is needed.
+ $migration_status = Database::get_migration_status();
?>
+
+
+
+
diff --git a/src/class-database.php b/src/class-database.php
new file mode 100644
index 0000000..16c5793
--- /dev/null
+++ b/src/class-database.php
@@ -0,0 +1,281 @@
+prefix . self::TABLE_NAME;
+ }
+
+ /**
+ * Create the custom table.
+ *
+ * @return void
+ */
+ public static function create_table() {
+ global $wpdb;
+
+ $table_name = self::get_table_name();
+ $charset_collate = $wpdb->get_charset_collate();
+
+ $sql = "CREATE TABLE {$table_name} (
+ option_name VARCHAR(191) NOT NULL,
+ access_count BIGINT UNSIGNED DEFAULT 1,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (option_name)
+ ) {$charset_collate};";
+
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
+ \dbDelta( $sql );
+ }
+
+ /**
+ * Drop the custom table.
+ *
+ * @return void
+ */
+ public static function drop_table() {
+ global $wpdb;
+
+ $table_name = self::get_table_name();
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe (from constant).
+ $wpdb->query( "DROP TABLE IF EXISTS {$table_name}" );
+ }
+
+ /**
+ * Check if the table exists.
+ *
+ * @return bool
+ */
+ public static function table_exists() {
+ global $wpdb;
+
+ $table_name = self::get_table_name();
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+ return $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) ) === $table_name;
+ }
+
+ /**
+ * Number of options to migrate per request.
+ *
+ * @var int
+ */
+ const MIGRATION_CHUNK_SIZE = 1000;
+
+ /**
+ * Get migration status.
+ *
+ * @return array{needs_migration: bool, total: int, remaining: int}
+ */
+ public static function get_migration_status() {
+ $option_data = \get_option( 'option_optimizer' );
+
+ if ( ! \is_array( $option_data ) || empty( $option_data['used_options'] ) ) {
+ return [
+ 'needs_migration' => false,
+ 'total' => 0,
+ 'remaining' => 0,
+ ];
+ }
+
+ $remaining = \count( $option_data['used_options'] );
+
+ // Get total from transient or set it on first check.
+ $total = \get_transient( 'aaa_option_optimizer_migration_total' );
+ if ( false === $total ) {
+ $total = $remaining;
+ \set_transient( 'aaa_option_optimizer_migration_total', $total, HOUR_IN_SECONDS );
+ }
+
+ return [
+ 'needs_migration' => true,
+ 'total' => (int) $total,
+ 'remaining' => $remaining,
+ ];
+ }
+
+ /**
+ * Migrate a chunk of data from the old option format to the custom table.
+ *
+ * Processes in chunks to avoid timeouts on slow hosts with large datasets.
+ *
+ * @return array{success: bool, remaining: int, total: int}
+ */
+ public static function migrate_chunk() {
+ $option_data = \get_option( 'option_optimizer' );
+
+ // No data or already migrated.
+ if ( ! \is_array( $option_data ) || empty( $option_data['used_options'] ) ) {
+ \delete_transient( 'aaa_option_optimizer_migration_total' );
+ return [
+ 'success' => true,
+ 'remaining' => 0,
+ 'total' => 0,
+ ];
+ }
+
+ // Ensure table exists.
+ if ( ! self::table_exists() ) {
+ self::create_table();
+ }
+
+ // Get total for progress tracking.
+ $total = \get_transient( 'aaa_option_optimizer_migration_total' );
+ if ( false === $total ) {
+ $total = \count( $option_data['used_options'] );
+ \set_transient( 'aaa_option_optimizer_migration_total', $total, HOUR_IN_SECONDS );
+ }
+
+ // Take a chunk of options to migrate.
+ $chunk = \array_slice( $option_data['used_options'], 0, self::MIGRATION_CHUNK_SIZE, true );
+
+ // Batch insert chunk to custom table.
+ self::batch_insert( $chunk );
+
+ // Remove migrated options from the array.
+ $option_data['used_options'] = \array_slice( $option_data['used_options'], self::MIGRATION_CHUNK_SIZE, null, true );
+
+ \update_option( 'option_optimizer', $option_data, false );
+
+ $remaining = \count( $option_data['used_options'] );
+
+ // Clean up total transient when done.
+ if ( 0 === $remaining ) {
+ \delete_transient( 'aaa_option_optimizer_migration_total' );
+ }
+
+ return [
+ 'success' => true,
+ 'remaining' => $remaining,
+ 'total' => (int) $total,
+ ];
+ }
+
+ /**
+ * Batch insert or update option counts.
+ *
+ * Splits large datasets into chunks and wraps them in a transaction
+ * for optimal performance on slow hosts with large datasets.
+ *
+ * @param array $options Array of option_name => count.
+ * @param int $chunk_size Number of options per query. Default 500.
+ *
+ * @return void
+ */
+ public static function batch_insert( $options, $chunk_size = 500 ) {
+ global $wpdb;
+
+ if ( empty( $options ) ) {
+ return;
+ }
+
+ $table_name = self::get_table_name();
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+ $wpdb->query( 'BEGIN' );
+
+ foreach ( array_chunk( $options, $chunk_size, true ) as $chunk ) {
+ $values = [];
+ $placeholders = [];
+
+ foreach ( $chunk as $option_name => $count ) {
+ $placeholders[] = '(%s, %d, NOW())';
+ $values[] = $option_name;
+ $values[] = (int) $count;
+ }
+
+ $sql = "INSERT INTO {$table_name} (option_name, access_count, created_at)
+ VALUES " . implode( ', ', $placeholders ) . '
+ ON DUPLICATE KEY UPDATE access_count = access_count + VALUES(access_count)';
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
+ $wpdb->query( $wpdb->prepare( $sql, ...$values ) );
+ }
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
+ $wpdb->query( 'COMMIT' );
+ }
+
+ /**
+ * Get all tracked options as an associative array.
+ *
+ * @return array Array of option_name => access_count.
+ */
+ public static function get_tracked_options() {
+ global $wpdb;
+
+ $table_name = self::get_table_name();
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe (from constant).
+ $results = $wpdb->get_results( "SELECT option_name, access_count FROM {$table_name}", ARRAY_A );
+
+ if ( empty( $results ) ) {
+ return [];
+ }
+
+ $options = [];
+ foreach ( $results as $row ) {
+ $options[ $row['option_name'] ] = (int) $row['access_count'];
+ }
+
+ return $options;
+ }
+
+ /**
+ * Get tracked option names as a keyed array for efficient lookups.
+ *
+ * @return array Array of option_name => true.
+ */
+ public static function get_tracked_option_keys() {
+ global $wpdb;
+
+ $table_name = self::get_table_name();
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe (from constant).
+ $option_names = $wpdb->get_col( "SELECT option_name FROM {$table_name}" );
+
+ if ( empty( $option_names ) ) {
+ return [];
+ }
+
+ return array_fill_keys( $option_names, true );
+ }
+
+ /**
+ * Clear all tracked options from the table.
+ *
+ * @return void
+ */
+ public static function clear_tracked_options() {
+ global $wpdb;
+
+ $table_name = self::get_table_name();
+
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe (from constant).
+ $wpdb->query( "TRUNCATE TABLE {$table_name}" );
+ }
+}
diff --git a/src/class-map-plugin-to-options.php b/src/class-map-plugin-to-options.php
index 2732f9a..799d8fe 100644
--- a/src/class-map-plugin-to-options.php
+++ b/src/class-map-plugin-to-options.php
@@ -2,15 +2,15 @@
/**
* Functionality to map options to plugins.
*
- * @package Emilia\OptionOptimizer
+ * @package Progress_Planner\OptionOptimizer
*/
-namespace Emilia\OptionOptimizer;
+namespace Progress_Planner\OptionOptimizer;
/**
* Class Map_Plugin_To_Options
*
- * @package Emilia\OptionOptimizer
+ * @package Progress_Planner\OptionOptimizer
*/
class Map_Plugin_To_Options {
/**
diff --git a/src/class-plugin.php b/src/class-plugin.php
index 0df3325..0caf091 100644
--- a/src/class-plugin.php
+++ b/src/class-plugin.php
@@ -2,10 +2,10 @@
/**
* Plugin functionality for AAA Option Optimizer.
*
- * @package Emilia\OptionOptimizer
+ * @package Progress_Planner\OptionOptimizer
*/
-namespace Emilia\OptionOptimizer;
+namespace Progress_Planner\OptionOptimizer;
/**
* Core functionality of AAA Option Optimizer.
@@ -19,9 +19,9 @@ class Plugin {
public static $instance;
/**
- * Holds the names of the options accessed during the request.
+ * Holds the options accessed during the request with their access counts.
*
- * @var string[]
+ * @var array
*/
protected $accessed_options = [];
@@ -60,8 +60,6 @@ public static function get_instance() {
* @return void
*/
public function register_hooks() {
- $this->accessed_options = \get_option( 'option_optimizer', [ 'used_options' => [] ] )['used_options'];
-
if ( Admin_Page::get_option_tracking() === 'pre_option' ) {
\add_filter( 'pre_option', [ $this, 'monitor_option_accesses_pre_option' ], PHP_INT_MAX, 2 );
} else {
@@ -144,7 +142,7 @@ protected function add_option_usage( $option_name ) {
}
/**
- * Update the 'option_optimizer' option with the list of used options at the end of the page load.
+ * Update the tracked options at the end of the page load.
*
* @return void
*/
@@ -153,19 +151,16 @@ public function update_tracked_options() {
if ( isset( $_GET['page'] ) && $_GET['page'] === 'aaa-option-optimizer' ) {
return;
}
- // Retrieve the existing option_optimizer data.
- $option_optimizer = get_option( 'option_optimizer', [ 'used_options' => [] ] );
-
- foreach ( $this->accessed_options as $option => $count ) {
- $option_optimizer['used_options'][ $option ] =
- ( $option_optimizer['used_options'][ $option ] ?? 0 ) + $count;
- }
+ // Handle reset.
if ( $this->should_reset ) {
- $option_optimizer['used_options'] = [];
+ Database::clear_tracked_options();
+ return;
}
- // Update the 'option_optimizer' option with the new list.
- update_option( 'option_optimizer', $option_optimizer, false );
+ // Write accessed options directly to the custom table.
+ if ( ! empty( $this->accessed_options ) ) {
+ Database::batch_insert( $this->accessed_options );
+ }
}
}
diff --git a/src/class-rest.php b/src/class-rest.php
index 5934a77..472eea4 100644
--- a/src/class-rest.php
+++ b/src/class-rest.php
@@ -2,10 +2,10 @@
/**
* REST functionality for AAA Option Optimizer.
*
- * @package Emilia\OptionOptimizer
+ * @package Progress_Planner\OptionOptimizer
*/
-namespace Emilia\OptionOptimizer;
+namespace Progress_Planner\OptionOptimizer;
use WP_Error;
use WP_REST_Request;
@@ -181,6 +181,18 @@ public function register_rest_routes() {
},
]
);
+
+ \register_rest_route(
+ 'aaa-option-optimizer/v1',
+ '/migrate',
+ [
+ 'methods' => 'POST',
+ 'callback' => [ $this, 'migrate_chunk' ],
+ 'permission_callback' => function () {
+ return current_user_can( 'manage_options' );
+ },
+ ]
+ );
}
/**
@@ -193,6 +205,16 @@ public function reset_stats() {
return new \WP_REST_Response( [ 'success' => true ], 200 );
}
+ /**
+ * Migrate a chunk of data from old format to custom table.
+ *
+ * @return \WP_REST_Response
+ */
+ public function migrate_chunk() {
+ $result = Database::migrate_chunk();
+ return new \WP_REST_Response( $result, 200 );
+ }
+
/**
* Update autoload status of an option.
*
@@ -229,9 +251,8 @@ public function get_unused_options() {
global $wpdb;
- // Load used options from option_optimizer.
- $option_optimizer = get_option( 'option_optimizer', [ 'used_options' => [] ] );
- $used_options = $option_optimizer['used_options'];
+ // Load used options from custom table.
+ $used_options = Database::get_tracked_option_keys();
$query = "
SELECT option_name
@@ -345,9 +366,8 @@ public function get_used_not_autoloaded_options() {
global $wpdb;
- // Load used options from option_optimizer.
- $option_optimizer = get_option( 'option_optimizer', [ 'used_options' => [] ] );
- $used_options = $option_optimizer['used_options'];
+ // Load used options from custom table (with counts).
+ $used_options = Database::get_tracked_options();
if ( empty( $used_options ) ) {
return new \WP_REST_Response(
@@ -481,9 +501,8 @@ public function get_options_that_do_not_exist() {
global $wpdb;
- // Load used options.
- $option_optimizer = get_option( 'option_optimizer', [ 'used_options' => [] ] );
- $used_options = $option_optimizer['used_options'];
+ // Load used options from custom table (with counts).
+ $used_options = Database::get_tracked_options();
if ( empty( $used_options ) ) {
return new \WP_REST_Response(
diff --git a/uninstall.php b/uninstall.php
index 4fa17e2..05bf9fe 100644
--- a/uninstall.php
+++ b/uninstall.php
@@ -2,9 +2,9 @@
/**
* Uninstall the plugin.
*
- * Delete the plugin option.
+ * Delete the plugin option and custom table.
*
- * @package Progress_Planner
+ * @package Progress_Planner\OptionOptimizer
*/
// If uninstall not called from WordPress, then exit.
@@ -12,5 +12,15 @@
exit;
}
+global $wpdb;
+
+// Drop the custom table.
+$aaa_option_optimizer_table = $wpdb->prefix . 'option_optimizer_tracked';
+// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe (from constant prefix).
+$wpdb->query( "DROP TABLE IF EXISTS {$aaa_option_optimizer_table}" );
+
+// Delete the batch transient.
+delete_transient( 'option_optimizer_batch' );
+
// Delete the plugin option.
delete_option( 'option_optimizer' );