diff --git a/aaa-option-optimizer.php b/aaa-option-optimizer.php
index 3bacb4d..aba743c 100644
--- a/aaa-option-optimizer.php
+++ b/aaa-option-optimizer.php
@@ -34,8 +34,14 @@
function aaa_option_optimizer_activation() {
global $wpdb;
- // Create the custom table.
+ // Create the custom tables.
Progress_Planner\OptionOptimizer\Database::create_table();
+ Progress_Planner\OptionOptimizer\Database::create_quarantine_table();
+
+ // Schedule the daily quarantine cleanup event.
+ if ( ! wp_next_scheduled( 'aaa_option_optimizer_quarantine_cleanup' ) ) {
+ wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'aaa_option_optimizer_quarantine_cleanup' );
+ }
$autoload_values = \wp_autoload_values_to_autoload();
$placeholders = implode( ',', array_fill( 0, count( $autoload_values ), '%s' ) );
@@ -73,6 +79,12 @@ function aaa_option_optimizer_activation() {
function aaa_option_optimizer_deactivation() {
$aaa_option_value = get_option( 'option_optimizer' );
update_option( 'option_optimizer', $aaa_option_value, false );
+
+ // Unschedule the quarantine cleanup event.
+ $timestamp = wp_next_scheduled( 'aaa_option_optimizer_quarantine_cleanup' );
+ if ( $timestamp ) {
+ wp_unschedule_event( $timestamp, 'aaa_option_optimizer_quarantine_cleanup' );
+ }
}
/**
@@ -92,6 +104,16 @@ function aaa_option_optimizer_maybe_upgrade() {
if ( ! Progress_Planner\OptionOptimizer\Database::table_exists() ) {
Progress_Planner\OptionOptimizer\Database::create_table();
}
+
+ // Check if quarantine table exists, create if not.
+ if ( ! Progress_Planner\OptionOptimizer\Database::quarantine_table_exists() ) {
+ Progress_Planner\OptionOptimizer\Database::create_quarantine_table();
+ }
+
+ // Ensure cleanup event is scheduled (covers installs that predate this feature).
+ if ( ! wp_next_scheduled( 'aaa_option_optimizer_quarantine_cleanup' ) ) {
+ wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'aaa_option_optimizer_quarantine_cleanup' );
+ }
}
add_action( 'plugins_loaded', 'aaa_option_optimizer_maybe_upgrade' );
diff --git a/css/style.css b/css/style.css
index 21e90b6..f48c748 100644
--- a/css/style.css
+++ b/css/style.css
@@ -19,6 +19,9 @@
.aaa_option_table .actions .button {
margin-right: 10px;
}
+.aaa_option_table .actions > .button:last-child {
+ margin-right: 0;
+}
.aaa_option_table select {
max-width: 20% !important;
}
@@ -27,7 +30,6 @@
max-width: 200px !important;
}
.aaa_option_table .actions .button-delete, .aaa-option-optimizer-reset .button-delete {
- margin-right: 0;
color: #a00;
border-color: #a00;
}
diff --git a/js/admin-script.js b/js/admin-script.js
index 3531a73..e43e0f7 100644
--- a/js/admin-script.js
+++ b/js/admin-script.js
@@ -1,4 +1,4 @@
-/* global jQuery, aaaOptionOptimizer, Option, DataTable, alert */
+/* global jQuery, aaaOptionOptimizer, Option, DataTable, alert, Blob, URL, FileReader */
/**
* JavaScript for the admin page.
@@ -19,6 +19,7 @@ jQuery( document ).ready( function () {
'#unused_options_table',
'#used_not_autoloaded_table',
'#requested_do_not_exist_table',
+ '#quarantine_table',
];
jQuery( '#all_options_table' ).hide();
@@ -162,9 +163,105 @@ jQuery( document ).ready( function () {
options.order = [ [ 1, 'asc' ] ]; // Order by 2nd column, first column is checkbox.
}
+ if ( selector === '#quarantine_table' ) {
+ options.ajax = {
+ url: `${ aaaOptionOptimizer.root }aaa-option-optimizer/v1/quarantine`,
+ headers: { 'X-WP-Nonce': aaaOptionOptimizer.nonce },
+ type: 'GET',
+ dataSrc: 'data',
+ };
+ options.columns = [
+ { name: 'name', data: 'name' },
+ { name: 'size', data: 'size', searchable: false },
+ {
+ name: 'autoload',
+ data: 'autoload',
+ searchable: false,
+ },
+ {
+ name: 'quarantined_at',
+ data: 'quarantined_at',
+ searchable: false,
+ },
+ {
+ name: 'expires_at',
+ data: 'expires_at',
+ searchable: false,
+ },
+ {
+ name: 'actions',
+ data: 'name',
+ render: ( data, type, row ) =>
+ renderQuarantineActionsColumn( row ),
+ orderable: false,
+ searchable: false,
+ className: 'actions',
+ },
+ ];
+ options.order = [ [ 3, 'desc' ] ];
+ options.language = {
+ sZeroRecords: aaaOptionOptimizer.i18n.quarantineEmpty,
+ };
+ delete options.initComplete;
+ }
+
new DataTable( selector, options ).columns.adjust().responsive.recalc();
}
+ /**
+ * Renders the Actions column for a quarantine row.
+ *
+ * @param {Object} row - The row data.
+ * @return {string} HTML.
+ */
+ function renderQuarantineActionsColumn( row ) {
+ return `
+ `;
+ }
+
+ /**
+ * Handles quarantine table actions (restore, permanently-delete).
+ *
+ * @param {Event} e - The click event.
+ */
+ function handleQuarantineActions( e ) {
+ e.preventDefault();
+ const button = jQuery( this );
+ const optionName = button.data( 'option' );
+ const dt = jQuery( '#quarantine_table' ).DataTable();
+
+ let route;
+ if ( button.hasClass( 'restore-option' ) ) {
+ route = 'quarantine/restore';
+ } else {
+ // eslint-disable-next-line no-alert
+ if ( ! window.confirm( aaaOptionOptimizer.i18n.confirmPermanentDelete ) ) {
+ return;
+ }
+ route = 'quarantine/delete';
+ }
+
+ jQuery.ajax( {
+ url: `${ aaaOptionOptimizer.root }aaa-option-optimizer/v1/${ route }`,
+ method: 'POST',
+ beforeSend: ( xhr ) =>
+ xhr.setRequestHeader( 'X-WP-Nonce', aaaOptionOptimizer.nonce ),
+ data: { option_name: optionName },
+ success: () => {
+ dt.ajax.reload( null, false );
+ },
+ error: ( response ) =>
+ // eslint-disable-next-line no-console
+ console.error( 'Quarantine action failed.', response ),
+ } );
+ }
+
/**
* Retrieves the columns configuration based on the selector.
*
@@ -333,13 +430,10 @@ jQuery( document ).ready( function () {
${ row.value }
`;
- const actions = [
- ``,
- popoverContent,
- row.autoload === 'no'
+ const protectedRow = isProtected( row.name );
+ const autoloadBtn = protectedRow
+ ? ''
+ : row.autoload === 'no'
? `