Changeset 3764690
- Timestamp:
- 10/25/2026 01:45:31 PM (2 hours ago)
- Location:
- rvs-database-doctor
- Files:
-
- 36 edited
- 1 copied
-
tags/2.8.6 (copied) (copied from rvs-database-doctor/trunk )
-
tags/2.8.6/CHANGELOG.md (modified) (1 diff)
-
tags/2.8.6/assets/admin.js (modified) (1 diff)
-
tags/2.8.6/includes/actions/class-rvs-db-doctor-actions-cleanup.php (modified) (2 diffs)
-
tags/2.8.6/includes/admin/class-rvs-db-doctor-admin-ajax-schedules.php (modified) (1 diff)
-
tags/2.8.6/includes/admin/class-rvs-db-doctor-admin-backups-schedules.php (modified) (8 diffs)
-
tags/2.8.6/includes/admin/class-rvs-db-doctor-admin-cleanup.php (modified) (3 diffs)
-
tags/2.8.6/includes/analyzer/class-rvs-db-doctor-analyzer-cleanup-counts.php (modified) (2 diffs)
-
tags/2.8.6/includes/analyzer/class-rvs-db-doctor-analyzer-orphan-tables.php (modified) (7 diffs)
-
tags/2.8.6/includes/backup/class-rvs-db-doctor-migration.php (modified) (2 diffs)
-
tags/2.8.6/includes/backup/class-rvs-db-doctor-site-archiver.php (modified) (10 diffs)
-
tags/2.8.6/includes/class-rvs-db-doctor-actions.php (modified) (5 diffs)
-
tags/2.8.6/includes/class-rvs-db-doctor-admin.php (modified) (1 diff)
-
tags/2.8.6/includes/class-rvs-db-doctor-advisor.php (modified) (1 diff)
-
tags/2.8.6/includes/class-rvs-db-doctor-analyzer.php (modified) (1 diff)
-
tags/2.8.6/includes/class-rvs-db-doctor-runner.php (modified) (3 diffs)
-
tags/2.8.6/languages/rvs-database-doctor.pot (modified) (1 diff)
-
tags/2.8.6/readme.txt (modified) (3 diffs)
-
tags/2.8.6/rvs-database-doctor.php (modified) (2 diffs)
-
trunk/CHANGELOG.md (modified) (1 diff)
-
trunk/assets/admin.js (modified) (1 diff)
-
trunk/includes/actions/class-rvs-db-doctor-actions-cleanup.php (modified) (2 diffs)
-
trunk/includes/admin/class-rvs-db-doctor-admin-ajax-schedules.php (modified) (1 diff)
-
trunk/includes/admin/class-rvs-db-doctor-admin-backups-schedules.php (modified) (8 diffs)
-
trunk/includes/admin/class-rvs-db-doctor-admin-cleanup.php (modified) (3 diffs)
-
trunk/includes/analyzer/class-rvs-db-doctor-analyzer-cleanup-counts.php (modified) (2 diffs)
-
trunk/includes/analyzer/class-rvs-db-doctor-analyzer-orphan-tables.php (modified) (7 diffs)
-
trunk/includes/backup/class-rvs-db-doctor-migration.php (modified) (2 diffs)
-
trunk/includes/backup/class-rvs-db-doctor-site-archiver.php (modified) (10 diffs)
-
trunk/includes/class-rvs-db-doctor-actions.php (modified) (5 diffs)
-
trunk/includes/class-rvs-db-doctor-admin.php (modified) (1 diff)
-
trunk/includes/class-rvs-db-doctor-advisor.php (modified) (1 diff)
-
trunk/includes/class-rvs-db-doctor-analyzer.php (modified) (1 diff)
-
trunk/includes/class-rvs-db-doctor-runner.php (modified) (3 diffs)
-
trunk/languages/rvs-database-doctor.pot (modified) (1 diff)
-
trunk/readme.txt (modified) (3 diffs)
-
trunk/rvs-database-doctor.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
rvs-database-doctor/tags/2.8.6/CHANGELOG.md
r3760203 r3764690 4 4 5 5 The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). 6 7 ## [2.8.6] - 2026-10-25 8 9 ### Added 10 - Explicit, batched emptying of Action Scheduler actions and logs, including pending and recurring jobs. Requires typing `PURGE` and deactivating plugins that load Action Scheduler; whole-queue deletion cannot be scheduled. 11 - Directory selection for site backups: choose immediate directories within `wp-content` or keep the entire tree. All authorized database tables remain included, and existing file exclusions still apply. 12 - Automatic backup controls on the Backups tab: save the selected directories and export methods with hourly, daily, weekly or monthly recurrence, and pause, resume or delete backup schedules. 13 14 ### Fixed 15 - Advisor checks WooCommerce availability and the current user's permissions before offering WooCommerce Tools, including cached recommendations. Unavailable tools now lead to Cleanup with guidance about remaining WooCommerce data. 16 - Known table-name signatures no longer count as proof of an active plugin. Leftover WooCommerce and other recognized tables remain visible for review, while active owners and the loaded Action Scheduler library remain protected. 17 - Old completed, canceled and failed Action Scheduler actions with an empty last-attempt date use their scheduled date for retention. Candidate counts, previews and deletion now use the same date rule. 18 19 ### Changed 20 - Expanded potential-leftover table identification with signatures for Formidable Forms, Ninja Forms, FluentCRM, Fluent Support, WP All Import/Export, activity logs, membership, learning, newsletter and other plugins. 21 - Selected-directory backups record their scope in the signed manifest and are excluded from full-site migration packages. 6 22 7 23 ## [2.8.5] - 2026-10-22 -
rvs-database-doctor/tags/2.8.6/assets/admin.js
r3760203 r3764690 1343 1343 }); 1344 1344 1345 $(document).on('click', '#rvs-as-purge', function () { 1346 RVSModal.prompt({ title: RVSDADO.i18n.as_purge_title, message: RVSDADO.i18n.as_purge_message, expected: 'PURGE', danger: true }).then(function (typed) { 1347 if (typed !== 'PURGE') return; 1348 runJob({ type: 'purge_action_scheduler', params: { confirm: typed }, run_mode: 'fg', output: '#rvs-cleanup-output', container: '.rvsdado-tab-cleanup' }); 1349 }); 1350 }); 1351 1345 1352 /* ===== Full-site backup / restore ===== */ 1353 $(document).on('change', '#rvs-site-all-content', function () { 1354 $('.rvs-site-directory').prop('disabled', this.checked); 1355 }); 1356 function siteBackupParams() { 1357 var params = { 1358 db_method: $('input[name="rvs-site-db-method"]:checked').val() || 'auto', 1359 archive_method: $('input[name="rvs-site-archive-method"]:checked').val() || 'auto' 1360 }; 1361 if (!$('#rvs-site-all-content').prop('checked')) { 1362 params.directories = $('.rvs-site-directory:checked').map(function () { return this.value; }).get(); 1363 if (!params.directories.length) { RVSToast.error(RVSDADO.i18n.backup_select_dir); return null; } 1364 } 1365 return params; 1366 } 1367 $(document).on('click', '#rvs-backup-schedule-save', function () { 1368 var params = siteBackupParams(); 1369 if (!params) return; 1370 var $button = $(this).prop('disabled', true); 1371 ajax('rvsdado_schedule_save', { name: $('#rvs-backup-schedule-name').val() || RVSDADO.i18n.backup_schedule_name, job_type: 'full_site_backup', recurrence: $('#rvs-backup-schedule-recurrence').val(), enabled: 1, params: JSON.stringify(params) }).done(function (r) { 1372 if (r && r.success) { location.reload(); } 1373 else { RVSToast.error((r && r.data && r.data.message) || RVSDADO.i18n.error); } 1374 }).fail(function () { RVSToast.error(RVSDADO.i18n.request_failed); }).always(function () { $button.prop('disabled', false); }); 1375 }); 1376 1346 1377 $(document).on('click', '#rvs-site-backup-run', function () { 1347 1378 var bg = $('#rvs-site-backup-bg').prop('checked'); 1348 var dbMethod = $('input[name="rvs-site-db-method"]:checked').val() || 'auto';1349 var archiveMethod = $('input[name="rvs-site-archive-method"]:checked').val() || 'auto';1379 var params = siteBackupParams(); 1380 if (!params) return; 1350 1381 $('#rvs-site-bk-output').text((RVSDADO.i18n.site_backup_running || 'Creating full-site backup…') + '\n'); 1351 1382 runJob({ 1352 1383 type: 'full_site_backup', 1353 params: { db_method: dbMethod, archive_method: archiveMethod },1384 params: params, 1354 1385 run_mode: bg ? 'bg' : 'fg', 1355 1386 output: '#rvs-site-bk-output', -
rvs-database-doctor/tags/2.8.6/includes/actions/class-rvs-db-doctor-actions-cleanup.php
r3754548 r3764690 203 203 } 204 204 205 /** Explicit whole-queue reset, separate from routine cleanup and schedules. */ 206 public static function step_purge_action_scheduler( array $params, int $batch_size, array $job ) { 207 global $wpdb; 208 if ( 'PURGE' !== ( $params['confirm'] ?? '' ) ) { 209 return new WP_Error( 'rvsdado_confirmation_required', 'Type PURGE to empty Action Scheduler actions and logs.' ); 210 } 211 if ( class_exists( 'ActionScheduler' ) || function_exists( 'as_schedule_single_action' ) ) { 212 return new WP_Error( 'rvsdado_scheduler_active', 'Deactivate plugins that load Action Scheduler before emptying its queue. Use old-record cleanup while the scheduler is active.' ); 213 } 214 // Bounded DELETE avoids long table locks on queues with hundreds of thousands of rows. 215 foreach ( array( 'actionscheduler_logs', 'actionscheduler_actions' ) as $suffix ) { 216 $table = $wpdb->prefix . $suffix; 217 if ( ! RVSDADO_Analyzer::table_exists( $table ) ) { 218 continue; 219 } 220 $deleted = (int) RVSDADO_Actions::db_query( $wpdb->prepare( 'DELETE FROM %i LIMIT %d', $table, max( 1, $batch_size ) ) ); 221 RVSDADO_Analyzer::invalidate_tags( array( 'cleanup' ) ); 222 if ( $deleted > 0 ) { 223 return self::pkt( $deleted, (int) $job['processed'] + $deleted, false ); 224 } 225 } 226 return self::pkt( 0, (int) $job['processed'], true ); 227 } 228 205 229 public static function step_as_old_actions( array $params, int $batch_size, array $job ): array { 206 230 global $wpdb; … … 212 236 $ids = RVSDADO_Actions::db_get_col( 213 237 $wpdb->prepare( 214 "SELECT action_id FROM %i WHERE status IN ('complete','canceled','failed') AND last_attempt_gmt > '0000-00-00 00:00:00' AND last_attempt_gmt< DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY) LIMIT %d",238 "SELECT action_id FROM %i WHERE status IN ('complete','canceled','failed') AND COALESCE(NULLIF(last_attempt_gmt, '0000-00-00 00:00:00'), NULLIF(scheduled_date_gmt, '0000-00-00 00:00:00')) < DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY) LIMIT %d", 215 239 $tbl, 216 240 $days, -
rvs-database-doctor/tags/2.8.6/includes/admin/class-rvs-db-doctor-admin-ajax-schedules.php
r3572373 r3764690 17 17 'params' => array(), 18 18 ); 19 if ( isset( $_POST['params'] ) ) { 20 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON decoded here and validated against the job schema by Scheduler::save(). 21 $raw = wp_unslash( $_POST['params'] ); 22 $params = is_string( $raw ) ? json_decode( $raw, true ) : null; 23 if ( ! is_string( $raw ) || 0 !== strpos( ltrim( $raw ), '{' ) || ! is_array( $params ) ) { 24 wp_send_json_error( array( 'message' => 'Schedule parameters must be a JSON object.' ) ); 25 } 26 $data['params'] = $params; 27 } 19 28 // phpcs:enable WordPress.Security.NonceVerification.Missing 20 29 $id = RVSDADO_Scheduler::save( $data ); -
rvs-database-doctor/tags/2.8.6/includes/admin/class-rvs-db-doctor-admin-backups-schedules.php
r3760203 r3764690 112 112 $sites = array(); 113 113 foreach ( RVSDADO_Backup_Site_Archiver::list() as $b ) { 114 if ( ! empty( $b['is_full_site'] ) ) {114 if ( ! empty( $b['is_full_site'] ) && empty( $b['site_manifest']['selected_directories'] ) ) { 115 115 $sites[] = $b; 116 116 } … … 204 204 205 205 echo '<div class="rvsdado-panel">'; 206 echo '<h3>' . esc_html__( 'Full-site backup (database + files)', 'rvs-database-doctor' ) . '</h3>'; 207 echo '<p>' . esc_html__( 'Backs up all authorized database tables plus the entire wp-content tree (plugins, themes, uploads, mu-plugins) into a single ZIP archive. It excludes the plugin backup directory, caches, node_modules, .git, *.log files and symlinks that escape wp-content. It does not include wp-config.php or WordPress core files. Partial restore is available: files only (optionally a single directory such as plugins/akismet) or selected database tables.', 'rvs-database-doctor' ) . '</p>'; 206 echo '<h3>' . esc_html__( 'Site backup (database + selected files)', 'rvs-database-doctor' ) . '</h3>'; 207 echo '<p>' . esc_html__( 'Backs up all authorized database tables plus the entire wp-content tree or selected directories (plugins, themes, uploads, mu-plugins). It excludes the plugin backup directory, caches, node_modules, .git, *.log files and symlinks that escape wp-content. It does not include wp-config.php or WordPress core files. Partial restore is available: files only (optionally a single directory such as plugins/akismet) or selected database tables.', 'rvs-database-doctor' ) . '</p>'; 208 209 echo '<fieldset><legend><strong>' . esc_html__( 'Directories to back up', 'rvs-database-doctor' ) . '</strong></legend>'; 210 echo '<label><input type="checkbox" id="rvs-site-all-content" checked> ' . esc_html__( 'Entire wp-content, including files in its root', 'rvs-database-doctor' ) . '</label>'; 211 echo '<div id="rvs-site-directories">'; 212 foreach ( RVSDADO_Backup_Site_Archiver::backup_directories() as $directory ) { 213 echo '<label style="display:block"><input type="checkbox" class="rvs-site-directory" value="' . esc_attr( $directory ) . '" checked disabled> ' . esc_html( $directory ) . '</label>'; 214 } 215 echo '</div><p class="description">' . esc_html__( 'Uncheck Entire wp-content to choose directories. Selected-directory backups still include all authorized database tables, exclude root files and cannot be used as full migration packages. Existing cache and backup exclusions always apply.', 'rvs-database-doctor' ) . '</p></fieldset>'; 208 216 209 217 echo '<fieldset class="rvs-backup-mode"><legend><strong>' . esc_html__( 'Database export', 'rvs-database-doctor' ) . '</strong></legend>'; … … 229 237 } 230 238 231 echo '<p><button type="button" class="button button-primary" id="rvs-site-backup-run">' . esc_html__( 'Create full-site backup', 'rvs-database-doctor' ) . '</button> ';239 echo '<p><button type="button" class="button button-primary" id="rvs-site-backup-run">' . esc_html__( 'Create site backup', 'rvs-database-doctor' ) . '</button> '; 232 240 echo '<label><input type="checkbox" id="rvs-site-backup-bg"> ' . esc_html__( 'Run in background', 'rvs-database-doctor' ) . '</label></p>'; 233 241 echo '<div class="rvsdado-progress" role="progressbar" aria-label="Progress" aria-valuemin="0" aria-valuemax="100"><div class="rvsdado-progress-bar"></div></div>'; 234 242 echo '<pre class="rvsdado-output" role="log" aria-live="polite" id="rvs-site-bk-output"></pre>'; 235 echo '</div>'; 243 $this->render_backup_schedule_controls(); 244 echo '</div>'; 245 } 246 247 private function render_backup_schedule_controls(): void { 248 echo '<h4>' . esc_html__( 'Automatic backups', 'rvs-database-doctor' ) . '</h4>'; 249 echo '<p>' . esc_html__( 'Save the directory selection and export methods above as a recurring background backup. WP-Cron needs site traffic or a server cron trigger; maintenance-window and backup-retention settings also apply.', 'rvs-database-doctor' ) . '</p>'; 250 echo '<p><input id="rvs-backup-schedule-name" aria-label="' . esc_attr__( 'Schedule name', 'rvs-database-doctor' ) . '" placeholder="' . esc_attr__( 'Schedule name', 'rvs-database-doctor' ) . '"> <select id="rvs-backup-schedule-recurrence" aria-label="' . esc_attr__( 'Recurrence', 'rvs-database-doctor' ) . '">'; 251 foreach ( array( 'hourly', 'daily', 'weekly', 'monthly' ) as $recurrence ) { 252 echo '<option value="' . esc_attr( $recurrence ) . '"' . selected( $recurrence, 'daily', false ) . '>' . esc_html( $recurrence ) . '</option>'; 253 } 254 echo '</select> <button type="button" class="button" id="rvs-backup-schedule-save">' . esc_html__( 'Schedule backup', 'rvs-database-doctor' ) . '</button></p>'; 255 foreach ( RVSDADO_Scheduler::get_all() as $schedule ) { 256 if ( 'full_site_backup' !== $schedule['job_type'] ) { continue; } 257 $params = is_array( $schedule['params'] ) ? $schedule['params'] : json_decode( (string) $schedule['params'], true ); 258 $selection = isset( $params['directories'] ) ? implode( ', ', $params['directories'] ) : __( 'Entire wp-content', 'rvs-database-doctor' ); 259 echo '<p><strong>' . esc_html( $schedule['name'] ) . '</strong> ' . esc_html( $schedule['recurrence'] . ' | ' . $selection ) . ' ' . esc_html( $schedule['enabled'] ? __( 'Enabled', 'rvs-database-doctor' ) : __( 'Paused', 'rvs-database-doctor' ) ) . ' | ' . esc_html__( 'Next run (UTC):', 'rvs-database-doctor' ) . ' ' . esc_html( (string) $schedule['next_run_at'] ) . ' '; 260 echo '<button type="button" class="button rvs-sched-toggle" data-id="' . esc_attr( (string) $schedule['id'] ) . '" data-enabled="' . esc_attr( $schedule['enabled'] ? '0' : '1' ) . '">' . esc_html( $schedule['enabled'] ? __( 'Pause', 'rvs-database-doctor' ) : __( 'Resume', 'rvs-database-doctor' ) ) . '</button> '; 261 echo '<button type="button" class="button rvs-sched-delete" data-id="' . esc_attr( (string) $schedule['id'] ) . '">' . esc_html__( 'Delete', 'rvs-database-doctor' ) . '</button></p>'; 262 } 236 263 } 237 264 … … 253 280 echo '<p><button type="button" class="button rvs-backup-security-action" data-operation="recheck">' . esc_html__( 'Recheck public access', 'rvs-database-doctor' ) . '</button> '; 254 281 echo '<button type="button" class="button rvs-backup-security-action" data-operation="protect">' . esc_html__( 'Apply Apache protection and recheck', 'rvs-database-doctor' ) . '</button></p>'; 255 echo '<p class="description">' . esc_html__( 'Apply protection writes deny rules into this directory ’s .htaccess when PHP has write permission. Success is verified with an HTTP probe. Nginx requires a server configuration change.', 'rvs-database-doctor' ) . '</p>';282 echo '<p class="description">' . esc_html__( 'Apply protection writes deny rules into this directoryâs .htaccess when PHP has write permission. Success is verified with an HTTP probe. Nginx requires a server configuration change.', 'rvs-database-doctor' ) . '</p>'; 256 283 if ( $allowed ) { 257 284 echo '<p><strong>' . esc_html__( 'Public-storage override is enabled for this directory.', 'rvs-database-doctor' ) . '</strong> ' . esc_html__( 'Manual, background and automatic backups may write here even if public access is detected. This permission remains enabled until revoked.', 'rvs-database-doctor' ) . '</p>'; … … 263 290 echo '<p id="rvs-backup-security-result" role="status" aria-live="polite"></p>'; 264 291 echo '<details><summary>' . esc_html__( 'Server configuration and terminal instructions', 'rvs-database-doctor' ) . '</summary>'; 265 echo '<p>' . esc_html__( 'Apache: place these rules in the backup directory ’s .htaccess. The host must allow authorization directives through AllowOverride. The button above preserves custom rules and replaces the plugin’s old generated rules.', 'rvs-database-doctor' ) . '</p>';292 echo '<p>' . esc_html__( 'Apache: place these rules in the backup directoryâs .htaccess. The host must allow authorization directives through AllowOverride. The button above preserves custom rules and replaces the pluginâs old generated rules.', 'rvs-database-doctor' ) . '</p>'; 266 293 echo '<pre class="rvsdado-output">' . esc_html( RVSDADO_Backup::apache_deny_rules() ) . '</pre>'; 267 294 $path = wp_parse_url( RVSDADO_Backup::backup_url(), PHP_URL_PATH ); … … 306 333 307 334 echo '<div class="rvs-bulk-bar">'; 308 echo '<input type="text" id="rvs-bk-filter" placeholder="' . esc_attr__( 'Filter tables …', 'rvs-database-doctor' ) . '" /> ';335 echo '<input type="text" id="rvs-bk-filter" placeholder="' . esc_attr__( 'Filter tablesâ¦', 'rvs-database-doctor' ) . '" /> '; 309 336 echo '<button type="button" class="button" id="rvs-bk-select-all">' . esc_html__( 'Select all', 'rvs-database-doctor' ) . '</button> '; 310 337 echo '<button type="button" class="button" id="rvs-bk-select-wp">' . esc_html__( 'Select WP prefix only', 'rvs-database-doctor' ) . '</button> '; … … 385 412 echo '<td><code>' . esc_html( $b['job_uid'] ) . '</code></td>'; 386 413 echo '<td>' . esc_html( gmdate( 'Y-m-d H:i:s', $b['mtime'] ) ) . '</td>'; 387 echo '<td>' . esc_html( $method ) . ( $full_site ? ' <strong>(' . esc_html __( 'Full site', 'rvs-database-doctor' ) . ')</strong>' : '' ) . ( $full_site && $site && 'zip' === $site['archive'] ? '· ZIP' : '' ) . '</td>';414 echo '<td>' . esc_html( $method ) . ( $full_site ? ' <strong>(' . esc_html( ! empty( $site['selected_directories'] ) ? __( 'Selected directories', 'rvs-database-doctor' ) : __( 'Full site', 'rvs-database-doctor' ) ) . ')</strong>' : '' ) . ( $full_site && $site && 'zip' === $site['archive'] ? ' · ZIP' : '' ) . '</td>'; 388 415 echo '<td>' . wp_kses_post( implode( '<br>', $contents ) ) . '</td>'; 389 416 echo '<td>' . esc_html( $size_label ) . '</td>'; … … 416 443 echo '<td><div class="rvs-row-actions">'; 417 444 if ( $full_site ) { 418 echo '<button type="button" class="button button-primary rvs-site-restore" data-uid="' . esc_attr( $b['job_uid'] ) . '">' . esc_html__( 'Restore …', 'rvs-database-doctor' ) . '</button>';445 echo '<button type="button" class="button button-primary rvs-site-restore" data-uid="' . esc_attr( $b['job_uid'] ) . '">' . esc_html__( 'Restoreâ¦', 'rvs-database-doctor' ) . '</button>'; 419 446 } 420 447 if ( $restore_ok ) { 421 echo '<button type="button" class="button rvs-backup-restore" data-uid="' . esc_attr( $b['job_uid'] ) . '">' . esc_html__( 'Restore DB …', 'rvs-database-doctor' ) . '</button>';448 echo '<button type="button" class="button rvs-backup-restore" data-uid="' . esc_attr( $b['job_uid'] ) . '">' . esc_html__( 'Restore DBâ¦', 'rvs-database-doctor' ) . '</button>'; 422 449 } 423 450 echo '<button type="button" class="button button-danger rvs-backup-delete" data-uid="' . esc_attr( $b['job_uid'] ) . '">' . esc_html__( 'Delete', 'rvs-database-doctor' ) . '</button>'; -
rvs-database-doctor/tags/2.8.6/includes/admin/class-rvs-db-doctor-admin-cleanup.php
r3755667 r3764690 145 145 146 146 private function render_cleanup_orphan_tables( array $orphans ): void { 147 echo '<h3>' . esc_html__( 'Empty Action Scheduler tables', 'rvs-database-doctor' ) . '</h3>'; 148 echo '<p>' . esc_html__( 'Deletes ALL actions and logs, including pending, recurring and failed jobs, in batches. Other plugins also use Action Scheduler. Deactivate its providers first; use old-record cleanup while they are active. This operation cannot be scheduled.', 'rvs-database-doctor' ) . '</p>'; 149 echo '<button type="button" class="button button-danger" id="rvs-as-purge">' . esc_html__( 'Empty actions and logs 150 ', 'rvs-database-doctor' ) . '</button>'; 147 151 /* --- Orphan tables (likely from deleted plugins) --- */ 148 152 echo '<h3>' . esc_html__( 'Potential leftover tables', 'rvs-database-doctor' ) . '</h3>'; … … 161 165 } else { 162 166 echo '<div class="rvs-bulk-bar">'; 163 echo '<input type="text" id="rvs-orphan-filter" placeholder="' . esc_attr__( 'Filter tables …', 'rvs-database-doctor' ) . '" style="margin-right:10px; max-width:200px;" /> ';167 echo '<input type="text" id="rvs-orphan-filter" placeholder="' . esc_attr__( 'Filter tablesâ¦', 'rvs-database-doctor' ) . '" style="margin-right:10px; max-width:200px;" /> '; 164 168 echo '<button type="button" class="button" id="rvs-orphan-select-high">' . esc_html__( 'Select HIGH only', 'rvs-database-doctor' ) . '</button> '; 165 169 echo '<button type="button" class="button" id="rvs-orphan-select-all">' . esc_html__( 'Select all', 'rvs-database-doctor' ) . '</button> '; … … 185 189 $cls = ( 'high' === $conf ) ? 'critical' : ( 'medium' === $conf ? 'warn' : 'ok' ); 186 190 $status = ! empty( $o['owner_status'] ) ? (string) $o['owner_status'] : 'unknown'; 187 $status_label = ucwords( str_replace( '_', ' ', $status ) );191 $status_label = 'known_platform' === $status ? __( 'Known signature; no installed owner identified', 'rvs-database-doctor' ) : ucwords( str_replace( '_', ' ', $status ) ); 188 192 echo '<tr data-confidence="' . esc_attr( $conf ) . '" data-owner-status="' . esc_attr( $status ) . '">'; 189 193 echo '<td><input type="checkbox" class="rvs-orphan-pick" value="' . esc_attr( $o['table_name'] ) . '"></td>'; -
rvs-database-doctor/tags/2.8.6/includes/analyzer/class-rvs-db-doctor-analyzer-cleanup-counts.php
r3754548 r3764690 133 133 $wpdb->prepare( 134 134 "SELECT 135 ( SELECT COUNT(*) FROM %i WHERE status IN ('complete','canceled','failed') AND last_attempt_gmt< DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY) ) AS as_old_actions,135 ( SELECT COUNT(*) FROM %i WHERE status IN ('complete','canceled','failed') AND COALESCE(NULLIF(last_attempt_gmt, '0000-00-00 00:00:00'), NULLIF(scheduled_date_gmt, '0000-00-00 00:00:00')) < DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY) ) AS as_old_actions, 136 136 ( SELECT COUNT(*) FROM %i WHERE log_date_gmt < DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY) ) AS as_old_logs", 137 137 $as_actions, … … 146 146 $out['as_old_actions'] = (int) $wpdb->get_var( 147 147 $wpdb->prepare( 148 "SELECT COUNT(*) FROM %i WHERE status IN ('complete','canceled','failed') AND last_attempt_gmt< DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY)",148 "SELECT COUNT(*) FROM %i WHERE status IN ('complete','canceled','failed') AND COALESCE(NULLIF(last_attempt_gmt, '0000-00-00 00:00:00'), NULLIF(scheduled_date_gmt, '0000-00-00 00:00:00')) < DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY)", 149 149 $as_actions, 150 150 $as_days -
rvs-database-doctor/tags/2.8.6/includes/analyzer/class-rvs-db-doctor-analyzer-orphan-tables.php
r3754548 r3764690 27 27 $slugs = array(); 28 28 foreach ( self::table_owner_registry() as $owner ) { 29 if ( in_array( $owner['status'], array( 'active_plugin', 'network_active_plugin', 'mu_plugin', 'active_theme', ' known_platform' ), true ) ) {29 if ( in_array( $owner['status'], array( 'active_plugin', 'network_active_plugin', 'mu_plugin', 'active_theme', 'runtime_library' ), true ) ) { 30 30 $slugs = array_merge( $slugs, (array) $owner['identifiers'] ); 31 31 } … … 174 174 'Ultimate Member' => array( 'um' ), 175 175 'BuddyPress' => array( 'bp', 'buddypress' ), 176 'FluentCRM' => array( 'fc', 'fluentcrm' ), 177 'Fluent Support' => array( 'fs', 'fluent_support' ), 178 'Formidable Forms' => array( 'frm' ), 179 'Ninja Forms' => array( 'nf3' ), 180 'WP All Import' => array( 'pmxi' ), 181 'WP All Export' => array( 'pmxe' ), 182 'WP Activity Log' => array( 'wsal' ), 183 'Simple History' => array( 'simple_history' ), 184 'WP Statistics' => array( 'statistics' ), 185 'WP Mail SMTP' => array( 'wpmailsmtp' ), 186 'Mailster' => array( 'mailster' ), 187 'Newsletter' => array( 'newsletter' ), 188 'AffiliateWP' => array( 'affiliate_wp' ), 189 'Easy Digital Downloads' => array( 'edd' ), 190 'LearnDash' => array( 'learndash' ), 191 'LearnPress' => array( 'learnpress' ), 192 'MemberPress' => array( 'mepr' ), 193 'Paid Memberships Pro' => array( 'pmpro' ), 194 'The Events Calendar' => array( 'tec', 'tribe' ), 195 'Booking Calendar' => array( 'booking' ), 196 'Smush' => array( 'smush' ), 197 'W3 Total Cache' => array( 'w3tc' ), 176 198 ); 177 199 … … 180 202 $owners[] = array( 181 203 'name' => $name, 182 'status' => 'known_platform',204 'status' => ( 'Action Scheduler' === $name && class_exists( 'ActionScheduler' ) ) ? 'runtime_library' : 'known_platform', 183 205 'identifiers' => self::normalize_identifier_list( $ids ), 184 'reason' => ' known platform alias',206 'reason' => 'Known table-name signature; installation and ownership must be verified', 185 207 ); 186 208 } … … 214 236 ); 215 237 238 foreach ( self::known_platform_owners() as $known ) { 239 $map[ strtolower( $known['name'] ) ] = $known['identifiers']; 240 } 216 241 $ids = array(); 217 242 foreach ( $map as $needle => $aliases ) { … … 405 430 'inactive_plugin' => 5, 406 431 'known_platform' => 2, 432 'runtime_library' => 10, 407 433 ); 408 434 $best = array(); … … 468 494 469 495 public static function find_orphan_tables(): array { 470 $cache_key = 'rvsdado_orphan_tables_v 5';496 $cache_key = 'rvsdado_orphan_tables_v6'; 471 497 $cached = get_transient( $cache_key ); 472 498 if ( is_array( $cached ) ) { … … 515 541 516 542 $owner = self::match_table_owner( $slow, $owners ); 517 if ( ! empty( $owner ) && in_array( $owner['status'], array( 'active_plugin', 'network_active_plugin', 'mu_plugin', 'active_theme', ' known_platform' ), true ) ) {543 if ( ! empty( $owner ) && in_array( $owner['status'], array( 'active_plugin', 'network_active_plugin', 'mu_plugin', 'active_theme', 'runtime_library' ), true ) ) { 518 544 continue; 519 545 } -
rvs-database-doctor/tags/2.8.6/includes/backup/class-rvs-db-doctor-migration.php
r3760203 r3764690 81 81 $site_dir = (string) $valid['site_dir']; 82 82 $site_manifest = (array) $valid['manifest']; 83 if ( null !== ( $site_manifest['selected_directories'] ?? null ) ) { 84 return new WP_Error( 'rvsdado_partial_backup', 'Migration requires a backup of the entire wp-content tree.' ); 85 } 83 86 $archive_file = (string) ( $site_manifest['archive']['file'] ?? '' ); 84 87 $unified = '' !== $archive_file … … 435 438 private static function latest_full_site_uid(): string { 436 439 foreach ( RVSDADO_Backup_Site_Archiver::list() as $b ) { 437 if ( ! empty( $b['is_full_site'] ) ) {440 if ( ! empty( $b['is_full_site'] ) && empty( $b['site_manifest']['selected_directories'] ) ) { 438 441 return (string) $b['job_uid']; 439 442 } -
rvs-database-doctor/tags/2.8.6/includes/backup/class-rvs-db-doctor-site-archiver.php
r3760203 r3764690 57 57 * =================================================================== */ 58 58 59 /** Selectable immediate directories inside wp-content; never follow symlinks. */ 60 public static function backup_directories(): array { 61 $root = self::content_dir(); 62 $dirs = array(); 63 if ( '' === $root || ! is_dir( $root ) ) { 64 return $dirs; 65 } 66 foreach ( (array) @scandir( $root ) as $name ) { 67 if ( '.' === $name || '..' === $name || ! is_string( $name ) ) { 68 continue; 69 } 70 $path = $root . '/' . $name; 71 if ( is_dir( $path ) && ! is_link( $path ) && ! self::is_backup_path( $path ) && '' === self::dir_exclusion( $name, $name ) ) { 72 $dirs[] = $name; 73 } 74 } 75 return $dirs; 76 } 77 78 public static function validate_directories( $value ) { 79 if ( ! is_array( $value ) || empty( $value ) || array_keys( $value ) !== range( 0, count( $value ) - 1 ) ) { 80 return new WP_Error( 'rvsdado_directories', 'Select at least one wp-content directory.' ); 81 } 82 $allowed = self::backup_directories(); 83 foreach ( $value as $dir ) { 84 if ( ! is_string( $dir ) || ! in_array( $dir, $allowed, true ) ) { 85 return new WP_Error( 'rvsdado_directories', 'A selected directory is missing, excluded or outside wp-content.' ); 86 } 87 } 88 return array_values( array_unique( $value ) ); 89 } 90 59 91 /** 60 92 * Resolve the exact file set a full-site backup would archive. … … 64 96 * applied. Nothing is ever resolved outside wp-content. 65 97 * 66 * @param array $args Reserved; no parameter may widen or change the scope.98 * @param array $args Optional directories narrow the file selection within wp-content. 67 99 * @return array 68 100 */ … … 91 123 } 92 124 $root_real = rtrim( str_replace( '\\', '/', $root_real ), '/' ); 125 $selected = null; 126 if ( array_key_exists( 'directories', $args ) ) { 127 $selected = self::validate_directories( $args['directories'] ); 128 if ( is_wp_error( $selected ) ) { 129 $out['error'] = $selected->get_error_message(); 130 return $out; 131 } 132 $out['selected_directories'] = $selected; 133 } 93 134 94 135 $files = array(); … … 115 156 $rel = '' === $rel_dir ? $name : $rel_dir . '/' . $name; 116 157 $abs = $abs_dir . '/' . $name; 158 if ( null !== $selected && '' === $rel_dir && ! in_array( $name, $selected, true ) ) { 159 continue; 160 } 117 161 118 162 // Symlinks: never followed. Escaping or dangling links are … … 232 276 throw new RuntimeException( 'Unsafe site archive directory.' ); 233 277 } 234 $plan = self::plan( );278 $plan = self::plan( $args ); 235 279 if ( empty( $plan['ok'] ) ) { 236 280 throw new RuntimeException( (string) $plan['error'] ); … … 427 471 } 428 472 $b['site_manifest'] = array( 473 'selected_directories' => $site['selected_directories'] ?? null, 429 474 'file_count' => count( $files ), 430 475 'total_bytes' => $bytes, … … 471 516 $site_dir = $dir . '/' . self::SITE_DIR; 472 517 $manifest = RVSDADO_Backup::read_manifest( $site_dir ); 473 if ( ! is_array( $manifest ) || empty( $manifest['files'] ) || ! is_array( $manifest['files'] ) ) {518 if ( ! is_array( $manifest ) || ! isset( $manifest['files'] ) || ! is_array( $manifest['files'] ) ) { 474 519 return new WP_Error( 'rvsdado_no_site_manifest', 'A complete signed full-site manifest is required.' ); 475 520 } … … 594 639 $tables_done = isset( $params['tables'] ) && is_array( $params['tables'] ) ? count( $params['tables'] ) : 0; 595 640 $files_total = 0; 596 $plan = self::plan( );641 $plan = self::plan( $params ); 597 642 if ( ! empty( $plan['ok'] ) && isset( $plan['files'] ) && is_array( $plan['files'] ) ) { 598 643 $files_total = count( $plan['files'] ); … … 1028 1073 private static function build_site_manifest( string $uid, array $plan, string $mode, array $files, string $compression = 'none', string $db_method = '', array $archive_meta = array() ): array { 1029 1074 return array( 1075 'selected_directories' => $plan['selected_directories'] ?? null, 1030 1076 'scope' => self::SCOPE, 1031 1077 'format' => self::FORMAT, … … 1378 1424 } 1379 1425 $state = self::load_site_state( $site_dir ); 1380 $plan = isset( $state['plan'] ) && is_array( $state['plan'] ) ? $state['plan'] : self::plan( );1426 $plan = isset( $state['plan'] ) && is_array( $state['plan'] ) ? $state['plan'] : self::plan( $params ); 1381 1427 if ( empty( $plan['ok'] ) ) { 1382 1428 return new WP_Error( 'rvsdado_site_backup_failed', (string) $plan['error'] ); -
rvs-database-doctor/tags/2.8.6/includes/class-rvs-db-doctor-actions.php
r3760203 r3764690 307 307 'cleanup_orphan_term_relationships' => array( $orph, 'step_orphan_term_relationships' ), 308 308 'cleanup_wc_expired_sessions' => array( $wc, 'step_wc_expired_sessions' ), 309 'purge_action_scheduler' => array( $cleanup, 'step_purge_action_scheduler' ), 309 310 'cleanup_as_old_actions' => array( $cleanup, 'step_as_old_actions' ), 310 311 'cleanup_as_old_logs' => array( $cleanup, 'step_as_old_logs' ), … … 390 391 $registry['cleanup_revisions'] = array( 'risk' => 'reversible_mutation', 'schema' => array( 'confirm' => 'text' ) ); 391 392 $registry['cleanup_old_trash'] = array( 'risk' => 'reversible_mutation', 'schema' => array( 'days' => 'int', 'confirm' => 'text' ) ); 393 $registry['purge_action_scheduler'] = array( 'risk' => 'destructive', 'confirm' => 'PURGE', 'schema' => array( 'confirm' => 'text' ) ); 392 394 $registry['cleanup_as_old_actions'] = array( 'risk' => 'reversible_mutation', 'schema' => array( 'days' => 'int', 'confirm' => 'text' ) ); 393 395 $registry['cleanup_as_old_logs'] = array( 'risk' => 'reversible_mutation', 'schema' => array( 'days' => 'int', 'confirm' => 'text' ) ); … … 409 411 // Full-site backup/restore. Scope is server-derived (all authorized 410 412 // tables + wp-content); method keys only choose how the dump and the 411 // file archive are produced and never widen or narrow the scope.412 $registry['full_site_backup'] = array( 'risk' => 'backup', 'schema' => array( 'db_method' => 'key', 'archive_method' => 'key' ) );413 // file archive are produced. Optional directories narrow the file scope. 414 $registry['full_site_backup'] = array( 'risk' => 'backup', 'schema' => array( 'db_method' => 'key', 'archive_method' => 'key', 'directories' => 'backup_directories' ) ); 413 415 $registry['full_site_restore'] = array( 414 416 'risk' => 'restore', … … 562 564 } 563 565 return array_values( array_unique( $value ) ); 566 case 'backup_directories': 567 return RVSDADO_Backup_Site_Archiver::validate_directories( $value ); 564 568 case 'path_list': 565 569 // Relative wp-content paths (e.g. 'plugins/acme') for partial file … … 697 701 if ( ! RVSDADO_Analyzer::table_exists( $tbl ) ) return array( 'total' => 0, 'sql' => '-- not present' ); 698 702 $days = isset( $params['days'] ) ? max( 1, (int) $params['days'] ) : self::settings_days_as(); 699 $total = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM %i WHERE status IN ('complete','canceled','failed') AND last_attempt_gmt< DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY)", $tbl, $days ) );703 $total = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM %i WHERE status IN ('complete','canceled','failed') AND COALESCE(NULLIF(last_attempt_gmt, '0000-00-00 00:00:00'), NULLIF(scheduled_date_gmt, '0000-00-00 00:00:00')) < DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY)", $tbl, $days ) ); 700 704 return array( 'total' => $total, 'sql' => 'DELETE AS old actions' ); 701 705 case 'cleanup_as_old_logs': -
rvs-database-doctor/tags/2.8.6/includes/class-rvs-db-doctor-admin.php
r3760203 r3764690 196 196 'restore_modal_selected' => __( 'of %d selected - only the checked tables are restored.', 'rvs-database-doctor' ), 197 197 'restore_modal_authorized' => __( 'Only tables contained in this signed backup can be listed; tables outside your authorized scope are never restored.', 'rvs-database-doctor' ), 198 'as_purge_title' => __( 'Empty Action Scheduler', 'rvs-database-doctor' ), 199 'as_purge_message' => __( 'Delete ALL actions and logs, including pending and recurring jobs? Deactivate Action Scheduler providers first. Type PURGE to confirm.', 'rvs-database-doctor' ), 200 'backup_select_dir' => __( 'Select at least one directory.', 'rvs-database-doctor' ), 201 'backup_schedule_name' => __( 'Site backup', 'rvs-database-doctor' ), 198 202 'site_backup_warn' => __( 'A full-site backup includes all authorized database tables AND the wp-content files (plugins, themes, uploads). It does not include wp-config.php or WordPress core, and skips caches, logs, VCS folders and symlinks that escape wp-content.', 'rvs-database-doctor' ), 199 203 'site_backup_running' => __( 'Creating full-site backup…', 'rvs-database-doctor' ), 200 'site_backup_done' => __( ' Full-site backup complete.', 'rvs-database-doctor' ),204 'site_backup_done' => __( 'Site backup complete.', 'rvs-database-doctor' ), 201 205 'site_restore_warn' => __( 'A full-site restore replaces the database tables AND overwrites wp-content files from the backup. A pre-restore database backup is created automatically. Type RESTORE to confirm.', 'rvs-database-doctor' ), 202 206 'site_restore_confirm' => __( 'You must type RESTORE to confirm.', 'rvs-database-doctor' ), -
rvs-database-doctor/tags/2.8.6/includes/class-rvs-db-doctor-advisor.php
r3754548 r3764690 39 39 $cached = get_transient( 'rvsdado_advisor_eval' ); 40 40 if ( is_array( $cached ) && isset( $cached['score'], $cached['tips'] ) ) { 41 return $cached;41 return self::available_actions( $cached ); 42 42 } 43 43 } 44 44 $result = self::evaluate_uncached(); 45 45 set_transient( 'rvsdado_advisor_eval', $result, 15 * MINUTE_IN_SECONDS ); 46 return self::available_actions( $result ); 47 } 48 49 /** Resolve user permissions and plugin availability outside the shared cache. */ 50 private static function available_actions( array $result ): array { 51 foreach ( $result['tips'] as &$tip ) { 52 if ( false !== strpos( (string) ( $tip['action']['url'] ?? '' ), 'page=wc-status' ) 53 && ( ! class_exists( 'WooCommerce' ) || ! current_user_can( 'manage_woocommerce' ) ) ) { 54 $tip['action'] = array( 'tab' => 'cleanup' ); 55 $tip['detail'] = __( 'WooCommerce tools are unavailable. Review the remaining WooCommerce tables under Potential leftover tables in Cleanup. Deactivation does not mean the stored orders and products are disposable; back up and verify ownership before removing tables.', 'rvs-database-doctor' ); 56 } 57 } 58 unset( $tip ); 46 59 return $result; 47 60 } -
rvs-database-doctor/tags/2.8.6/includes/class-rvs-db-doctor-analyzer.php
r3754548 r3764690 72 72 delete_transient( 'rvsdado_orphan_tables_v4' ); 73 73 delete_transient( 'rvsdado_orphan_tables_v5' ); 74 delete_transient( 'rvsdado_orphan_tables_v6' ); 74 75 self::$owner_registry_cache = null; 75 76 } -
rvs-database-doctor/tags/2.8.6/includes/class-rvs-db-doctor-runner.php
r3755680 r3764690 267 267 return apply_filters( 268 268 'rvsdado_snapshot_types', 269 array( 'optimize_now', 'cleanup_revisions', 'cleanup_orphan_postmeta', 'cleanup_orphan_commentmeta', 'cleanup_orphan_term_relationships', 'cleanup_orphan_usermeta', 'cleanup_orphan_termmeta', 'cleanup_oembed_cache', 'cleanup_wc_orphan_order_items', 'cleanup_wc_orphan_order_itemmeta', 'cleanup_old_trash', 'engine_convert', 'optimize_table', ' cleanup_as_old_actions', 'cleanup_as_old_logs', 'drop_orphan_tables' )269 array( 'optimize_now', 'cleanup_revisions', 'cleanup_orphan_postmeta', 'cleanup_orphan_commentmeta', 'cleanup_orphan_term_relationships', 'cleanup_orphan_usermeta', 'cleanup_orphan_termmeta', 'cleanup_oembed_cache', 'cleanup_wc_orphan_order_items', 'cleanup_wc_orphan_order_itemmeta', 'cleanup_old_trash', 'engine_convert', 'optimize_table', 'purge_action_scheduler', 'cleanup_as_old_actions', 'cleanup_as_old_logs', 'drop_orphan_tables' ) 270 270 ); 271 271 } … … 308 308 return apply_filters( 309 309 'rvsdado_backup_types', 310 array( 'optimize_now', 'cleanup_chain', 'engine_convert', 'index_add', 'index_bulk_add', 'index_drop', 'add_pk', 'fix_collation', 'autoload_update', 'autoload_bulk_update', 'cleanup_expired_transients', 'cleanup_auto_drafts', 'cleanup_spam_comments', 'cleanup_trash_comments', 'cleanup_wc_expired_sessions', ' cleanup_as_old_actions', 'cleanup_as_old_logs', 'cleanup_duplicate_postmeta', 'cleanup_stale_session_tokens', 'cleanup_edit_locks', 'cleanup_unused_terms', 'cleanup_plugin_caches', 'cleanup_orphan_postmeta', 'cleanup_orphan_commentmeta', 'cleanup_orphan_term_relationships', 'cleanup_orphan_usermeta', 'cleanup_orphan_termmeta', 'cleanup_oembed_cache', 'cleanup_wc_orphan_order_items', 'cleanup_wc_orphan_order_itemmeta', 'cleanup_old_trash', 'cleanup_revisions', 'quarantine_orphan_tables', 'restore_quarantined_tables', 'delete_quarantined_tables', 'drop_orphan_tables' )310 array( 'optimize_now', 'cleanup_chain', 'engine_convert', 'index_add', 'index_bulk_add', 'index_drop', 'add_pk', 'fix_collation', 'autoload_update', 'autoload_bulk_update', 'cleanup_expired_transients', 'cleanup_auto_drafts', 'cleanup_spam_comments', 'cleanup_trash_comments', 'cleanup_wc_expired_sessions', 'purge_action_scheduler', 'cleanup_as_old_actions', 'cleanup_as_old_logs', 'cleanup_duplicate_postmeta', 'cleanup_stale_session_tokens', 'cleanup_edit_locks', 'cleanup_unused_terms', 'cleanup_plugin_caches', 'cleanup_orphan_postmeta', 'cleanup_orphan_commentmeta', 'cleanup_orphan_term_relationships', 'cleanup_orphan_usermeta', 'cleanup_orphan_termmeta', 'cleanup_oembed_cache', 'cleanup_wc_orphan_order_items', 'cleanup_wc_orphan_order_itemmeta', 'cleanup_old_trash', 'cleanup_revisions', 'quarantine_orphan_tables', 'restore_quarantined_tables', 'delete_quarantined_tables', 'drop_orphan_tables' ) 311 311 ); 312 312 } … … 352 352 case 'cleanup_unused_terms': 353 353 return array( $wpdb->terms, $wpdb->term_taxonomy, $wpdb->term_relationships, $wpdb->termmeta, $wpdb->options ); 354 case 'purge_action_scheduler': 354 355 case 'cleanup_as_old_actions': 355 356 return array_values( array_filter( array( $p . 'actionscheduler_actions', $p . 'actionscheduler_logs' ), array( 'RVSDADO_Analyzer', 'table_exists' ) ) ); -
rvs-database-doctor/tags/2.8.6/languages/rvs-database-doctor.pot
r3760203 r3764690 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: RVS Database Doctor 2.8. 5\n"5 "Project-Id-Version: RVS Database Doctor 2.8.6\n" 6 6 "Report-Msgid-Bugs-To: https://rberet.com/wp-plugins/rvs-database-doctor\n" 7 7 "POT-Creation-Date: 2026-05-02 00:00+0000\n" -
rvs-database-doctor/tags/2.8.6/readme.txt
r3760203 r3764690 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 2.8. 57 Stable tag: 2.8.6 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 81 81 == Changelog == 82 82 83 = 2.8.6 = 84 * Fixed: Advisor only offers WooCommerce Tools when WooCommerce is loaded and the current user has permission; otherwise it directs users to Cleanup. 85 * Added: batched emptying of all Action Scheduler actions and logs with PURGE confirmation. Requires inactive Action Scheduler providers and cannot be scheduled. 86 * Fixed: retention cleanup handles old actions without a last-attempt date consistently with candidate counts and previews. 87 * Fixed: known table signatures no longer hide leftover WooCommerce and other plugin tables as if their owners were active. 88 * Changed: expanded plugin identification for potential leftover tables while preserving protection for active owners and the Action Scheduler runtime. 89 * Added: selection of wp-content directories for site backups, with the selected scope stored in the signed manifest. Partial backups cannot be used as full migration packages. 90 * Added: hourly, daily, weekly and monthly automatic backups from the Backups tab, preserving selected directories and export methods, with pause, resume and delete controls. 91 83 92 = 2.8.5 = 84 93 * Changed: the standalone migration installer imports the database dump in batched multi-statement transactions (up to 2000 statements or 512 KB per round trip, one commit per work slice) instead of one query per statement. Import slices are 5-15 seconds depending on the host limit and the wizard resumes 100 ms after each slice. … … 234 243 == Upgrade Notice == 235 244 245 = 2.8.6 = 246 Fixes WooCommerce Advisor links and leftover-table detection. Adds explicit Action Scheduler queue emptying, selected-directory backups and recurring backup controls. Whole-queue emptying requires inactive Action Scheduler providers. 247 236 248 = 2.8.5 = 237 249 Adds full-site migration packages and the standalone rvsmigrate.php installer, single-archive full-site backups, and much faster restore/migration imports. Take a backup before updating. -
rvs-database-doctor/tags/2.8.6/rvs-database-doctor.php
r3760203 r3764690 4 4 * Plugin URI: https://rberet.com/wp-plugins/rvs-database-doctor 5 5 * Description: Safely analyze and optimize WordPress / WooCommerce databases with backups, restore, scheduled jobs, and an actionable health advisor. 6 * Version: 2.8. 56 * Version: 2.8.6 7 7 * Requires at least: 6.2 8 8 * Requires PHP: 7.4 … … 22 22 } 23 23 24 define( 'RVSDADO_VERSION', '2.8. 5' );24 define( 'RVSDADO_VERSION', '2.8.6' ); 25 25 define( 'RVSDADO_DB_VERSION', '6' ); 26 26 define( 'RVSDADO_FILE', __FILE__ ); -
rvs-database-doctor/trunk/CHANGELOG.md
r3760203 r3764690 4 4 5 5 The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). 6 7 ## [2.8.6] - 2026-10-25 8 9 ### Added 10 - Explicit, batched emptying of Action Scheduler actions and logs, including pending and recurring jobs. Requires typing `PURGE` and deactivating plugins that load Action Scheduler; whole-queue deletion cannot be scheduled. 11 - Directory selection for site backups: choose immediate directories within `wp-content` or keep the entire tree. All authorized database tables remain included, and existing file exclusions still apply. 12 - Automatic backup controls on the Backups tab: save the selected directories and export methods with hourly, daily, weekly or monthly recurrence, and pause, resume or delete backup schedules. 13 14 ### Fixed 15 - Advisor checks WooCommerce availability and the current user's permissions before offering WooCommerce Tools, including cached recommendations. Unavailable tools now lead to Cleanup with guidance about remaining WooCommerce data. 16 - Known table-name signatures no longer count as proof of an active plugin. Leftover WooCommerce and other recognized tables remain visible for review, while active owners and the loaded Action Scheduler library remain protected. 17 - Old completed, canceled and failed Action Scheduler actions with an empty last-attempt date use their scheduled date for retention. Candidate counts, previews and deletion now use the same date rule. 18 19 ### Changed 20 - Expanded potential-leftover table identification with signatures for Formidable Forms, Ninja Forms, FluentCRM, Fluent Support, WP All Import/Export, activity logs, membership, learning, newsletter and other plugins. 21 - Selected-directory backups record their scope in the signed manifest and are excluded from full-site migration packages. 6 22 7 23 ## [2.8.5] - 2026-10-22 -
rvs-database-doctor/trunk/assets/admin.js
r3760203 r3764690 1343 1343 }); 1344 1344 1345 $(document).on('click', '#rvs-as-purge', function () { 1346 RVSModal.prompt({ title: RVSDADO.i18n.as_purge_title, message: RVSDADO.i18n.as_purge_message, expected: 'PURGE', danger: true }).then(function (typed) { 1347 if (typed !== 'PURGE') return; 1348 runJob({ type: 'purge_action_scheduler', params: { confirm: typed }, run_mode: 'fg', output: '#rvs-cleanup-output', container: '.rvsdado-tab-cleanup' }); 1349 }); 1350 }); 1351 1345 1352 /* ===== Full-site backup / restore ===== */ 1353 $(document).on('change', '#rvs-site-all-content', function () { 1354 $('.rvs-site-directory').prop('disabled', this.checked); 1355 }); 1356 function siteBackupParams() { 1357 var params = { 1358 db_method: $('input[name="rvs-site-db-method"]:checked').val() || 'auto', 1359 archive_method: $('input[name="rvs-site-archive-method"]:checked').val() || 'auto' 1360 }; 1361 if (!$('#rvs-site-all-content').prop('checked')) { 1362 params.directories = $('.rvs-site-directory:checked').map(function () { return this.value; }).get(); 1363 if (!params.directories.length) { RVSToast.error(RVSDADO.i18n.backup_select_dir); return null; } 1364 } 1365 return params; 1366 } 1367 $(document).on('click', '#rvs-backup-schedule-save', function () { 1368 var params = siteBackupParams(); 1369 if (!params) return; 1370 var $button = $(this).prop('disabled', true); 1371 ajax('rvsdado_schedule_save', { name: $('#rvs-backup-schedule-name').val() || RVSDADO.i18n.backup_schedule_name, job_type: 'full_site_backup', recurrence: $('#rvs-backup-schedule-recurrence').val(), enabled: 1, params: JSON.stringify(params) }).done(function (r) { 1372 if (r && r.success) { location.reload(); } 1373 else { RVSToast.error((r && r.data && r.data.message) || RVSDADO.i18n.error); } 1374 }).fail(function () { RVSToast.error(RVSDADO.i18n.request_failed); }).always(function () { $button.prop('disabled', false); }); 1375 }); 1376 1346 1377 $(document).on('click', '#rvs-site-backup-run', function () { 1347 1378 var bg = $('#rvs-site-backup-bg').prop('checked'); 1348 var dbMethod = $('input[name="rvs-site-db-method"]:checked').val() || 'auto';1349 var archiveMethod = $('input[name="rvs-site-archive-method"]:checked').val() || 'auto';1379 var params = siteBackupParams(); 1380 if (!params) return; 1350 1381 $('#rvs-site-bk-output').text((RVSDADO.i18n.site_backup_running || 'Creating full-site backup…') + '\n'); 1351 1382 runJob({ 1352 1383 type: 'full_site_backup', 1353 params: { db_method: dbMethod, archive_method: archiveMethod },1384 params: params, 1354 1385 run_mode: bg ? 'bg' : 'fg', 1355 1386 output: '#rvs-site-bk-output', -
rvs-database-doctor/trunk/includes/actions/class-rvs-db-doctor-actions-cleanup.php
r3754548 r3764690 203 203 } 204 204 205 /** Explicit whole-queue reset, separate from routine cleanup and schedules. */ 206 public static function step_purge_action_scheduler( array $params, int $batch_size, array $job ) { 207 global $wpdb; 208 if ( 'PURGE' !== ( $params['confirm'] ?? '' ) ) { 209 return new WP_Error( 'rvsdado_confirmation_required', 'Type PURGE to empty Action Scheduler actions and logs.' ); 210 } 211 if ( class_exists( 'ActionScheduler' ) || function_exists( 'as_schedule_single_action' ) ) { 212 return new WP_Error( 'rvsdado_scheduler_active', 'Deactivate plugins that load Action Scheduler before emptying its queue. Use old-record cleanup while the scheduler is active.' ); 213 } 214 // Bounded DELETE avoids long table locks on queues with hundreds of thousands of rows. 215 foreach ( array( 'actionscheduler_logs', 'actionscheduler_actions' ) as $suffix ) { 216 $table = $wpdb->prefix . $suffix; 217 if ( ! RVSDADO_Analyzer::table_exists( $table ) ) { 218 continue; 219 } 220 $deleted = (int) RVSDADO_Actions::db_query( $wpdb->prepare( 'DELETE FROM %i LIMIT %d', $table, max( 1, $batch_size ) ) ); 221 RVSDADO_Analyzer::invalidate_tags( array( 'cleanup' ) ); 222 if ( $deleted > 0 ) { 223 return self::pkt( $deleted, (int) $job['processed'] + $deleted, false ); 224 } 225 } 226 return self::pkt( 0, (int) $job['processed'], true ); 227 } 228 205 229 public static function step_as_old_actions( array $params, int $batch_size, array $job ): array { 206 230 global $wpdb; … … 212 236 $ids = RVSDADO_Actions::db_get_col( 213 237 $wpdb->prepare( 214 "SELECT action_id FROM %i WHERE status IN ('complete','canceled','failed') AND last_attempt_gmt > '0000-00-00 00:00:00' AND last_attempt_gmt< DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY) LIMIT %d",238 "SELECT action_id FROM %i WHERE status IN ('complete','canceled','failed') AND COALESCE(NULLIF(last_attempt_gmt, '0000-00-00 00:00:00'), NULLIF(scheduled_date_gmt, '0000-00-00 00:00:00')) < DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY) LIMIT %d", 215 239 $tbl, 216 240 $days, -
rvs-database-doctor/trunk/includes/admin/class-rvs-db-doctor-admin-ajax-schedules.php
r3572373 r3764690 17 17 'params' => array(), 18 18 ); 19 if ( isset( $_POST['params'] ) ) { 20 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON decoded here and validated against the job schema by Scheduler::save(). 21 $raw = wp_unslash( $_POST['params'] ); 22 $params = is_string( $raw ) ? json_decode( $raw, true ) : null; 23 if ( ! is_string( $raw ) || 0 !== strpos( ltrim( $raw ), '{' ) || ! is_array( $params ) ) { 24 wp_send_json_error( array( 'message' => 'Schedule parameters must be a JSON object.' ) ); 25 } 26 $data['params'] = $params; 27 } 19 28 // phpcs:enable WordPress.Security.NonceVerification.Missing 20 29 $id = RVSDADO_Scheduler::save( $data ); -
rvs-database-doctor/trunk/includes/admin/class-rvs-db-doctor-admin-backups-schedules.php
r3760203 r3764690 112 112 $sites = array(); 113 113 foreach ( RVSDADO_Backup_Site_Archiver::list() as $b ) { 114 if ( ! empty( $b['is_full_site'] ) ) {114 if ( ! empty( $b['is_full_site'] ) && empty( $b['site_manifest']['selected_directories'] ) ) { 115 115 $sites[] = $b; 116 116 } … … 204 204 205 205 echo '<div class="rvsdado-panel">'; 206 echo '<h3>' . esc_html__( 'Full-site backup (database + files)', 'rvs-database-doctor' ) . '</h3>'; 207 echo '<p>' . esc_html__( 'Backs up all authorized database tables plus the entire wp-content tree (plugins, themes, uploads, mu-plugins) into a single ZIP archive. It excludes the plugin backup directory, caches, node_modules, .git, *.log files and symlinks that escape wp-content. It does not include wp-config.php or WordPress core files. Partial restore is available: files only (optionally a single directory such as plugins/akismet) or selected database tables.', 'rvs-database-doctor' ) . '</p>'; 206 echo '<h3>' . esc_html__( 'Site backup (database + selected files)', 'rvs-database-doctor' ) . '</h3>'; 207 echo '<p>' . esc_html__( 'Backs up all authorized database tables plus the entire wp-content tree or selected directories (plugins, themes, uploads, mu-plugins). It excludes the plugin backup directory, caches, node_modules, .git, *.log files and symlinks that escape wp-content. It does not include wp-config.php or WordPress core files. Partial restore is available: files only (optionally a single directory such as plugins/akismet) or selected database tables.', 'rvs-database-doctor' ) . '</p>'; 208 209 echo '<fieldset><legend><strong>' . esc_html__( 'Directories to back up', 'rvs-database-doctor' ) . '</strong></legend>'; 210 echo '<label><input type="checkbox" id="rvs-site-all-content" checked> ' . esc_html__( 'Entire wp-content, including files in its root', 'rvs-database-doctor' ) . '</label>'; 211 echo '<div id="rvs-site-directories">'; 212 foreach ( RVSDADO_Backup_Site_Archiver::backup_directories() as $directory ) { 213 echo '<label style="display:block"><input type="checkbox" class="rvs-site-directory" value="' . esc_attr( $directory ) . '" checked disabled> ' . esc_html( $directory ) . '</label>'; 214 } 215 echo '</div><p class="description">' . esc_html__( 'Uncheck Entire wp-content to choose directories. Selected-directory backups still include all authorized database tables, exclude root files and cannot be used as full migration packages. Existing cache and backup exclusions always apply.', 'rvs-database-doctor' ) . '</p></fieldset>'; 208 216 209 217 echo '<fieldset class="rvs-backup-mode"><legend><strong>' . esc_html__( 'Database export', 'rvs-database-doctor' ) . '</strong></legend>'; … … 229 237 } 230 238 231 echo '<p><button type="button" class="button button-primary" id="rvs-site-backup-run">' . esc_html__( 'Create full-site backup', 'rvs-database-doctor' ) . '</button> ';239 echo '<p><button type="button" class="button button-primary" id="rvs-site-backup-run">' . esc_html__( 'Create site backup', 'rvs-database-doctor' ) . '</button> '; 232 240 echo '<label><input type="checkbox" id="rvs-site-backup-bg"> ' . esc_html__( 'Run in background', 'rvs-database-doctor' ) . '</label></p>'; 233 241 echo '<div class="rvsdado-progress" role="progressbar" aria-label="Progress" aria-valuemin="0" aria-valuemax="100"><div class="rvsdado-progress-bar"></div></div>'; 234 242 echo '<pre class="rvsdado-output" role="log" aria-live="polite" id="rvs-site-bk-output"></pre>'; 235 echo '</div>'; 243 $this->render_backup_schedule_controls(); 244 echo '</div>'; 245 } 246 247 private function render_backup_schedule_controls(): void { 248 echo '<h4>' . esc_html__( 'Automatic backups', 'rvs-database-doctor' ) . '</h4>'; 249 echo '<p>' . esc_html__( 'Save the directory selection and export methods above as a recurring background backup. WP-Cron needs site traffic or a server cron trigger; maintenance-window and backup-retention settings also apply.', 'rvs-database-doctor' ) . '</p>'; 250 echo '<p><input id="rvs-backup-schedule-name" aria-label="' . esc_attr__( 'Schedule name', 'rvs-database-doctor' ) . '" placeholder="' . esc_attr__( 'Schedule name', 'rvs-database-doctor' ) . '"> <select id="rvs-backup-schedule-recurrence" aria-label="' . esc_attr__( 'Recurrence', 'rvs-database-doctor' ) . '">'; 251 foreach ( array( 'hourly', 'daily', 'weekly', 'monthly' ) as $recurrence ) { 252 echo '<option value="' . esc_attr( $recurrence ) . '"' . selected( $recurrence, 'daily', false ) . '>' . esc_html( $recurrence ) . '</option>'; 253 } 254 echo '</select> <button type="button" class="button" id="rvs-backup-schedule-save">' . esc_html__( 'Schedule backup', 'rvs-database-doctor' ) . '</button></p>'; 255 foreach ( RVSDADO_Scheduler::get_all() as $schedule ) { 256 if ( 'full_site_backup' !== $schedule['job_type'] ) { continue; } 257 $params = is_array( $schedule['params'] ) ? $schedule['params'] : json_decode( (string) $schedule['params'], true ); 258 $selection = isset( $params['directories'] ) ? implode( ', ', $params['directories'] ) : __( 'Entire wp-content', 'rvs-database-doctor' ); 259 echo '<p><strong>' . esc_html( $schedule['name'] ) . '</strong> ' . esc_html( $schedule['recurrence'] . ' | ' . $selection ) . ' ' . esc_html( $schedule['enabled'] ? __( 'Enabled', 'rvs-database-doctor' ) : __( 'Paused', 'rvs-database-doctor' ) ) . ' | ' . esc_html__( 'Next run (UTC):', 'rvs-database-doctor' ) . ' ' . esc_html( (string) $schedule['next_run_at'] ) . ' '; 260 echo '<button type="button" class="button rvs-sched-toggle" data-id="' . esc_attr( (string) $schedule['id'] ) . '" data-enabled="' . esc_attr( $schedule['enabled'] ? '0' : '1' ) . '">' . esc_html( $schedule['enabled'] ? __( 'Pause', 'rvs-database-doctor' ) : __( 'Resume', 'rvs-database-doctor' ) ) . '</button> '; 261 echo '<button type="button" class="button rvs-sched-delete" data-id="' . esc_attr( (string) $schedule['id'] ) . '">' . esc_html__( 'Delete', 'rvs-database-doctor' ) . '</button></p>'; 262 } 236 263 } 237 264 … … 253 280 echo '<p><button type="button" class="button rvs-backup-security-action" data-operation="recheck">' . esc_html__( 'Recheck public access', 'rvs-database-doctor' ) . '</button> '; 254 281 echo '<button type="button" class="button rvs-backup-security-action" data-operation="protect">' . esc_html__( 'Apply Apache protection and recheck', 'rvs-database-doctor' ) . '</button></p>'; 255 echo '<p class="description">' . esc_html__( 'Apply protection writes deny rules into this directory ’s .htaccess when PHP has write permission. Success is verified with an HTTP probe. Nginx requires a server configuration change.', 'rvs-database-doctor' ) . '</p>';282 echo '<p class="description">' . esc_html__( 'Apply protection writes deny rules into this directoryâs .htaccess when PHP has write permission. Success is verified with an HTTP probe. Nginx requires a server configuration change.', 'rvs-database-doctor' ) . '</p>'; 256 283 if ( $allowed ) { 257 284 echo '<p><strong>' . esc_html__( 'Public-storage override is enabled for this directory.', 'rvs-database-doctor' ) . '</strong> ' . esc_html__( 'Manual, background and automatic backups may write here even if public access is detected. This permission remains enabled until revoked.', 'rvs-database-doctor' ) . '</p>'; … … 263 290 echo '<p id="rvs-backup-security-result" role="status" aria-live="polite"></p>'; 264 291 echo '<details><summary>' . esc_html__( 'Server configuration and terminal instructions', 'rvs-database-doctor' ) . '</summary>'; 265 echo '<p>' . esc_html__( 'Apache: place these rules in the backup directory ’s .htaccess. The host must allow authorization directives through AllowOverride. The button above preserves custom rules and replaces the plugin’s old generated rules.', 'rvs-database-doctor' ) . '</p>';292 echo '<p>' . esc_html__( 'Apache: place these rules in the backup directoryâs .htaccess. The host must allow authorization directives through AllowOverride. The button above preserves custom rules and replaces the pluginâs old generated rules.', 'rvs-database-doctor' ) . '</p>'; 266 293 echo '<pre class="rvsdado-output">' . esc_html( RVSDADO_Backup::apache_deny_rules() ) . '</pre>'; 267 294 $path = wp_parse_url( RVSDADO_Backup::backup_url(), PHP_URL_PATH ); … … 306 333 307 334 echo '<div class="rvs-bulk-bar">'; 308 echo '<input type="text" id="rvs-bk-filter" placeholder="' . esc_attr__( 'Filter tables …', 'rvs-database-doctor' ) . '" /> ';335 echo '<input type="text" id="rvs-bk-filter" placeholder="' . esc_attr__( 'Filter tablesâ¦', 'rvs-database-doctor' ) . '" /> '; 309 336 echo '<button type="button" class="button" id="rvs-bk-select-all">' . esc_html__( 'Select all', 'rvs-database-doctor' ) . '</button> '; 310 337 echo '<button type="button" class="button" id="rvs-bk-select-wp">' . esc_html__( 'Select WP prefix only', 'rvs-database-doctor' ) . '</button> '; … … 385 412 echo '<td><code>' . esc_html( $b['job_uid'] ) . '</code></td>'; 386 413 echo '<td>' . esc_html( gmdate( 'Y-m-d H:i:s', $b['mtime'] ) ) . '</td>'; 387 echo '<td>' . esc_html( $method ) . ( $full_site ? ' <strong>(' . esc_html __( 'Full site', 'rvs-database-doctor' ) . ')</strong>' : '' ) . ( $full_site && $site && 'zip' === $site['archive'] ? '· ZIP' : '' ) . '</td>';414 echo '<td>' . esc_html( $method ) . ( $full_site ? ' <strong>(' . esc_html( ! empty( $site['selected_directories'] ) ? __( 'Selected directories', 'rvs-database-doctor' ) : __( 'Full site', 'rvs-database-doctor' ) ) . ')</strong>' : '' ) . ( $full_site && $site && 'zip' === $site['archive'] ? ' · ZIP' : '' ) . '</td>'; 388 415 echo '<td>' . wp_kses_post( implode( '<br>', $contents ) ) . '</td>'; 389 416 echo '<td>' . esc_html( $size_label ) . '</td>'; … … 416 443 echo '<td><div class="rvs-row-actions">'; 417 444 if ( $full_site ) { 418 echo '<button type="button" class="button button-primary rvs-site-restore" data-uid="' . esc_attr( $b['job_uid'] ) . '">' . esc_html__( 'Restore …', 'rvs-database-doctor' ) . '</button>';445 echo '<button type="button" class="button button-primary rvs-site-restore" data-uid="' . esc_attr( $b['job_uid'] ) . '">' . esc_html__( 'Restoreâ¦', 'rvs-database-doctor' ) . '</button>'; 419 446 } 420 447 if ( $restore_ok ) { 421 echo '<button type="button" class="button rvs-backup-restore" data-uid="' . esc_attr( $b['job_uid'] ) . '">' . esc_html__( 'Restore DB …', 'rvs-database-doctor' ) . '</button>';448 echo '<button type="button" class="button rvs-backup-restore" data-uid="' . esc_attr( $b['job_uid'] ) . '">' . esc_html__( 'Restore DBâ¦', 'rvs-database-doctor' ) . '</button>'; 422 449 } 423 450 echo '<button type="button" class="button button-danger rvs-backup-delete" data-uid="' . esc_attr( $b['job_uid'] ) . '">' . esc_html__( 'Delete', 'rvs-database-doctor' ) . '</button>'; -
rvs-database-doctor/trunk/includes/admin/class-rvs-db-doctor-admin-cleanup.php
r3755667 r3764690 145 145 146 146 private function render_cleanup_orphan_tables( array $orphans ): void { 147 echo '<h3>' . esc_html__( 'Empty Action Scheduler tables', 'rvs-database-doctor' ) . '</h3>'; 148 echo '<p>' . esc_html__( 'Deletes ALL actions and logs, including pending, recurring and failed jobs, in batches. Other plugins also use Action Scheduler. Deactivate its providers first; use old-record cleanup while they are active. This operation cannot be scheduled.', 'rvs-database-doctor' ) . '</p>'; 149 echo '<button type="button" class="button button-danger" id="rvs-as-purge">' . esc_html__( 'Empty actions and logs 150 ', 'rvs-database-doctor' ) . '</button>'; 147 151 /* --- Orphan tables (likely from deleted plugins) --- */ 148 152 echo '<h3>' . esc_html__( 'Potential leftover tables', 'rvs-database-doctor' ) . '</h3>'; … … 161 165 } else { 162 166 echo '<div class="rvs-bulk-bar">'; 163 echo '<input type="text" id="rvs-orphan-filter" placeholder="' . esc_attr__( 'Filter tables …', 'rvs-database-doctor' ) . '" style="margin-right:10px; max-width:200px;" /> ';167 echo '<input type="text" id="rvs-orphan-filter" placeholder="' . esc_attr__( 'Filter tablesâ¦', 'rvs-database-doctor' ) . '" style="margin-right:10px; max-width:200px;" /> '; 164 168 echo '<button type="button" class="button" id="rvs-orphan-select-high">' . esc_html__( 'Select HIGH only', 'rvs-database-doctor' ) . '</button> '; 165 169 echo '<button type="button" class="button" id="rvs-orphan-select-all">' . esc_html__( 'Select all', 'rvs-database-doctor' ) . '</button> '; … … 185 189 $cls = ( 'high' === $conf ) ? 'critical' : ( 'medium' === $conf ? 'warn' : 'ok' ); 186 190 $status = ! empty( $o['owner_status'] ) ? (string) $o['owner_status'] : 'unknown'; 187 $status_label = ucwords( str_replace( '_', ' ', $status ) );191 $status_label = 'known_platform' === $status ? __( 'Known signature; no installed owner identified', 'rvs-database-doctor' ) : ucwords( str_replace( '_', ' ', $status ) ); 188 192 echo '<tr data-confidence="' . esc_attr( $conf ) . '" data-owner-status="' . esc_attr( $status ) . '">'; 189 193 echo '<td><input type="checkbox" class="rvs-orphan-pick" value="' . esc_attr( $o['table_name'] ) . '"></td>'; -
rvs-database-doctor/trunk/includes/analyzer/class-rvs-db-doctor-analyzer-cleanup-counts.php
r3754548 r3764690 133 133 $wpdb->prepare( 134 134 "SELECT 135 ( SELECT COUNT(*) FROM %i WHERE status IN ('complete','canceled','failed') AND last_attempt_gmt< DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY) ) AS as_old_actions,135 ( SELECT COUNT(*) FROM %i WHERE status IN ('complete','canceled','failed') AND COALESCE(NULLIF(last_attempt_gmt, '0000-00-00 00:00:00'), NULLIF(scheduled_date_gmt, '0000-00-00 00:00:00')) < DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY) ) AS as_old_actions, 136 136 ( SELECT COUNT(*) FROM %i WHERE log_date_gmt < DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY) ) AS as_old_logs", 137 137 $as_actions, … … 146 146 $out['as_old_actions'] = (int) $wpdb->get_var( 147 147 $wpdb->prepare( 148 "SELECT COUNT(*) FROM %i WHERE status IN ('complete','canceled','failed') AND last_attempt_gmt< DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY)",148 "SELECT COUNT(*) FROM %i WHERE status IN ('complete','canceled','failed') AND COALESCE(NULLIF(last_attempt_gmt, '0000-00-00 00:00:00'), NULLIF(scheduled_date_gmt, '0000-00-00 00:00:00')) < DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY)", 149 149 $as_actions, 150 150 $as_days -
rvs-database-doctor/trunk/includes/analyzer/class-rvs-db-doctor-analyzer-orphan-tables.php
r3754548 r3764690 27 27 $slugs = array(); 28 28 foreach ( self::table_owner_registry() as $owner ) { 29 if ( in_array( $owner['status'], array( 'active_plugin', 'network_active_plugin', 'mu_plugin', 'active_theme', ' known_platform' ), true ) ) {29 if ( in_array( $owner['status'], array( 'active_plugin', 'network_active_plugin', 'mu_plugin', 'active_theme', 'runtime_library' ), true ) ) { 30 30 $slugs = array_merge( $slugs, (array) $owner['identifiers'] ); 31 31 } … … 174 174 'Ultimate Member' => array( 'um' ), 175 175 'BuddyPress' => array( 'bp', 'buddypress' ), 176 'FluentCRM' => array( 'fc', 'fluentcrm' ), 177 'Fluent Support' => array( 'fs', 'fluent_support' ), 178 'Formidable Forms' => array( 'frm' ), 179 'Ninja Forms' => array( 'nf3' ), 180 'WP All Import' => array( 'pmxi' ), 181 'WP All Export' => array( 'pmxe' ), 182 'WP Activity Log' => array( 'wsal' ), 183 'Simple History' => array( 'simple_history' ), 184 'WP Statistics' => array( 'statistics' ), 185 'WP Mail SMTP' => array( 'wpmailsmtp' ), 186 'Mailster' => array( 'mailster' ), 187 'Newsletter' => array( 'newsletter' ), 188 'AffiliateWP' => array( 'affiliate_wp' ), 189 'Easy Digital Downloads' => array( 'edd' ), 190 'LearnDash' => array( 'learndash' ), 191 'LearnPress' => array( 'learnpress' ), 192 'MemberPress' => array( 'mepr' ), 193 'Paid Memberships Pro' => array( 'pmpro' ), 194 'The Events Calendar' => array( 'tec', 'tribe' ), 195 'Booking Calendar' => array( 'booking' ), 196 'Smush' => array( 'smush' ), 197 'W3 Total Cache' => array( 'w3tc' ), 176 198 ); 177 199 … … 180 202 $owners[] = array( 181 203 'name' => $name, 182 'status' => 'known_platform',204 'status' => ( 'Action Scheduler' === $name && class_exists( 'ActionScheduler' ) ) ? 'runtime_library' : 'known_platform', 183 205 'identifiers' => self::normalize_identifier_list( $ids ), 184 'reason' => ' known platform alias',206 'reason' => 'Known table-name signature; installation and ownership must be verified', 185 207 ); 186 208 } … … 214 236 ); 215 237 238 foreach ( self::known_platform_owners() as $known ) { 239 $map[ strtolower( $known['name'] ) ] = $known['identifiers']; 240 } 216 241 $ids = array(); 217 242 foreach ( $map as $needle => $aliases ) { … … 405 430 'inactive_plugin' => 5, 406 431 'known_platform' => 2, 432 'runtime_library' => 10, 407 433 ); 408 434 $best = array(); … … 468 494 469 495 public static function find_orphan_tables(): array { 470 $cache_key = 'rvsdado_orphan_tables_v 5';496 $cache_key = 'rvsdado_orphan_tables_v6'; 471 497 $cached = get_transient( $cache_key ); 472 498 if ( is_array( $cached ) ) { … … 515 541 516 542 $owner = self::match_table_owner( $slow, $owners ); 517 if ( ! empty( $owner ) && in_array( $owner['status'], array( 'active_plugin', 'network_active_plugin', 'mu_plugin', 'active_theme', ' known_platform' ), true ) ) {543 if ( ! empty( $owner ) && in_array( $owner['status'], array( 'active_plugin', 'network_active_plugin', 'mu_plugin', 'active_theme', 'runtime_library' ), true ) ) { 518 544 continue; 519 545 } -
rvs-database-doctor/trunk/includes/backup/class-rvs-db-doctor-migration.php
r3760203 r3764690 81 81 $site_dir = (string) $valid['site_dir']; 82 82 $site_manifest = (array) $valid['manifest']; 83 if ( null !== ( $site_manifest['selected_directories'] ?? null ) ) { 84 return new WP_Error( 'rvsdado_partial_backup', 'Migration requires a backup of the entire wp-content tree.' ); 85 } 83 86 $archive_file = (string) ( $site_manifest['archive']['file'] ?? '' ); 84 87 $unified = '' !== $archive_file … … 435 438 private static function latest_full_site_uid(): string { 436 439 foreach ( RVSDADO_Backup_Site_Archiver::list() as $b ) { 437 if ( ! empty( $b['is_full_site'] ) ) {440 if ( ! empty( $b['is_full_site'] ) && empty( $b['site_manifest']['selected_directories'] ) ) { 438 441 return (string) $b['job_uid']; 439 442 } -
rvs-database-doctor/trunk/includes/backup/class-rvs-db-doctor-site-archiver.php
r3760203 r3764690 57 57 * =================================================================== */ 58 58 59 /** Selectable immediate directories inside wp-content; never follow symlinks. */ 60 public static function backup_directories(): array { 61 $root = self::content_dir(); 62 $dirs = array(); 63 if ( '' === $root || ! is_dir( $root ) ) { 64 return $dirs; 65 } 66 foreach ( (array) @scandir( $root ) as $name ) { 67 if ( '.' === $name || '..' === $name || ! is_string( $name ) ) { 68 continue; 69 } 70 $path = $root . '/' . $name; 71 if ( is_dir( $path ) && ! is_link( $path ) && ! self::is_backup_path( $path ) && '' === self::dir_exclusion( $name, $name ) ) { 72 $dirs[] = $name; 73 } 74 } 75 return $dirs; 76 } 77 78 public static function validate_directories( $value ) { 79 if ( ! is_array( $value ) || empty( $value ) || array_keys( $value ) !== range( 0, count( $value ) - 1 ) ) { 80 return new WP_Error( 'rvsdado_directories', 'Select at least one wp-content directory.' ); 81 } 82 $allowed = self::backup_directories(); 83 foreach ( $value as $dir ) { 84 if ( ! is_string( $dir ) || ! in_array( $dir, $allowed, true ) ) { 85 return new WP_Error( 'rvsdado_directories', 'A selected directory is missing, excluded or outside wp-content.' ); 86 } 87 } 88 return array_values( array_unique( $value ) ); 89 } 90 59 91 /** 60 92 * Resolve the exact file set a full-site backup would archive. … … 64 96 * applied. Nothing is ever resolved outside wp-content. 65 97 * 66 * @param array $args Reserved; no parameter may widen or change the scope.98 * @param array $args Optional directories narrow the file selection within wp-content. 67 99 * @return array 68 100 */ … … 91 123 } 92 124 $root_real = rtrim( str_replace( '\\', '/', $root_real ), '/' ); 125 $selected = null; 126 if ( array_key_exists( 'directories', $args ) ) { 127 $selected = self::validate_directories( $args['directories'] ); 128 if ( is_wp_error( $selected ) ) { 129 $out['error'] = $selected->get_error_message(); 130 return $out; 131 } 132 $out['selected_directories'] = $selected; 133 } 93 134 94 135 $files = array(); … … 115 156 $rel = '' === $rel_dir ? $name : $rel_dir . '/' . $name; 116 157 $abs = $abs_dir . '/' . $name; 158 if ( null !== $selected && '' === $rel_dir && ! in_array( $name, $selected, true ) ) { 159 continue; 160 } 117 161 118 162 // Symlinks: never followed. Escaping or dangling links are … … 232 276 throw new RuntimeException( 'Unsafe site archive directory.' ); 233 277 } 234 $plan = self::plan( );278 $plan = self::plan( $args ); 235 279 if ( empty( $plan['ok'] ) ) { 236 280 throw new RuntimeException( (string) $plan['error'] ); … … 427 471 } 428 472 $b['site_manifest'] = array( 473 'selected_directories' => $site['selected_directories'] ?? null, 429 474 'file_count' => count( $files ), 430 475 'total_bytes' => $bytes, … … 471 516 $site_dir = $dir . '/' . self::SITE_DIR; 472 517 $manifest = RVSDADO_Backup::read_manifest( $site_dir ); 473 if ( ! is_array( $manifest ) || empty( $manifest['files'] ) || ! is_array( $manifest['files'] ) ) {518 if ( ! is_array( $manifest ) || ! isset( $manifest['files'] ) || ! is_array( $manifest['files'] ) ) { 474 519 return new WP_Error( 'rvsdado_no_site_manifest', 'A complete signed full-site manifest is required.' ); 475 520 } … … 594 639 $tables_done = isset( $params['tables'] ) && is_array( $params['tables'] ) ? count( $params['tables'] ) : 0; 595 640 $files_total = 0; 596 $plan = self::plan( );641 $plan = self::plan( $params ); 597 642 if ( ! empty( $plan['ok'] ) && isset( $plan['files'] ) && is_array( $plan['files'] ) ) { 598 643 $files_total = count( $plan['files'] ); … … 1028 1073 private static function build_site_manifest( string $uid, array $plan, string $mode, array $files, string $compression = 'none', string $db_method = '', array $archive_meta = array() ): array { 1029 1074 return array( 1075 'selected_directories' => $plan['selected_directories'] ?? null, 1030 1076 'scope' => self::SCOPE, 1031 1077 'format' => self::FORMAT, … … 1378 1424 } 1379 1425 $state = self::load_site_state( $site_dir ); 1380 $plan = isset( $state['plan'] ) && is_array( $state['plan'] ) ? $state['plan'] : self::plan( );1426 $plan = isset( $state['plan'] ) && is_array( $state['plan'] ) ? $state['plan'] : self::plan( $params ); 1381 1427 if ( empty( $plan['ok'] ) ) { 1382 1428 return new WP_Error( 'rvsdado_site_backup_failed', (string) $plan['error'] ); -
rvs-database-doctor/trunk/includes/class-rvs-db-doctor-actions.php
r3760203 r3764690 307 307 'cleanup_orphan_term_relationships' => array( $orph, 'step_orphan_term_relationships' ), 308 308 'cleanup_wc_expired_sessions' => array( $wc, 'step_wc_expired_sessions' ), 309 'purge_action_scheduler' => array( $cleanup, 'step_purge_action_scheduler' ), 309 310 'cleanup_as_old_actions' => array( $cleanup, 'step_as_old_actions' ), 310 311 'cleanup_as_old_logs' => array( $cleanup, 'step_as_old_logs' ), … … 390 391 $registry['cleanup_revisions'] = array( 'risk' => 'reversible_mutation', 'schema' => array( 'confirm' => 'text' ) ); 391 392 $registry['cleanup_old_trash'] = array( 'risk' => 'reversible_mutation', 'schema' => array( 'days' => 'int', 'confirm' => 'text' ) ); 393 $registry['purge_action_scheduler'] = array( 'risk' => 'destructive', 'confirm' => 'PURGE', 'schema' => array( 'confirm' => 'text' ) ); 392 394 $registry['cleanup_as_old_actions'] = array( 'risk' => 'reversible_mutation', 'schema' => array( 'days' => 'int', 'confirm' => 'text' ) ); 393 395 $registry['cleanup_as_old_logs'] = array( 'risk' => 'reversible_mutation', 'schema' => array( 'days' => 'int', 'confirm' => 'text' ) ); … … 409 411 // Full-site backup/restore. Scope is server-derived (all authorized 410 412 // tables + wp-content); method keys only choose how the dump and the 411 // file archive are produced and never widen or narrow the scope.412 $registry['full_site_backup'] = array( 'risk' => 'backup', 'schema' => array( 'db_method' => 'key', 'archive_method' => 'key' ) );413 // file archive are produced. Optional directories narrow the file scope. 414 $registry['full_site_backup'] = array( 'risk' => 'backup', 'schema' => array( 'db_method' => 'key', 'archive_method' => 'key', 'directories' => 'backup_directories' ) ); 413 415 $registry['full_site_restore'] = array( 414 416 'risk' => 'restore', … … 562 564 } 563 565 return array_values( array_unique( $value ) ); 566 case 'backup_directories': 567 return RVSDADO_Backup_Site_Archiver::validate_directories( $value ); 564 568 case 'path_list': 565 569 // Relative wp-content paths (e.g. 'plugins/acme') for partial file … … 697 701 if ( ! RVSDADO_Analyzer::table_exists( $tbl ) ) return array( 'total' => 0, 'sql' => '-- not present' ); 698 702 $days = isset( $params['days'] ) ? max( 1, (int) $params['days'] ) : self::settings_days_as(); 699 $total = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM %i WHERE status IN ('complete','canceled','failed') AND last_attempt_gmt< DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY)", $tbl, $days ) );703 $total = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM %i WHERE status IN ('complete','canceled','failed') AND COALESCE(NULLIF(last_attempt_gmt, '0000-00-00 00:00:00'), NULLIF(scheduled_date_gmt, '0000-00-00 00:00:00')) < DATE_SUB(UTC_TIMESTAMP(), INTERVAL %d DAY)", $tbl, $days ) ); 700 704 return array( 'total' => $total, 'sql' => 'DELETE AS old actions' ); 701 705 case 'cleanup_as_old_logs': -
rvs-database-doctor/trunk/includes/class-rvs-db-doctor-admin.php
r3760203 r3764690 196 196 'restore_modal_selected' => __( 'of %d selected - only the checked tables are restored.', 'rvs-database-doctor' ), 197 197 'restore_modal_authorized' => __( 'Only tables contained in this signed backup can be listed; tables outside your authorized scope are never restored.', 'rvs-database-doctor' ), 198 'as_purge_title' => __( 'Empty Action Scheduler', 'rvs-database-doctor' ), 199 'as_purge_message' => __( 'Delete ALL actions and logs, including pending and recurring jobs? Deactivate Action Scheduler providers first. Type PURGE to confirm.', 'rvs-database-doctor' ), 200 'backup_select_dir' => __( 'Select at least one directory.', 'rvs-database-doctor' ), 201 'backup_schedule_name' => __( 'Site backup', 'rvs-database-doctor' ), 198 202 'site_backup_warn' => __( 'A full-site backup includes all authorized database tables AND the wp-content files (plugins, themes, uploads). It does not include wp-config.php or WordPress core, and skips caches, logs, VCS folders and symlinks that escape wp-content.', 'rvs-database-doctor' ), 199 203 'site_backup_running' => __( 'Creating full-site backup…', 'rvs-database-doctor' ), 200 'site_backup_done' => __( ' Full-site backup complete.', 'rvs-database-doctor' ),204 'site_backup_done' => __( 'Site backup complete.', 'rvs-database-doctor' ), 201 205 'site_restore_warn' => __( 'A full-site restore replaces the database tables AND overwrites wp-content files from the backup. A pre-restore database backup is created automatically. Type RESTORE to confirm.', 'rvs-database-doctor' ), 202 206 'site_restore_confirm' => __( 'You must type RESTORE to confirm.', 'rvs-database-doctor' ), -
rvs-database-doctor/trunk/includes/class-rvs-db-doctor-advisor.php
r3754548 r3764690 39 39 $cached = get_transient( 'rvsdado_advisor_eval' ); 40 40 if ( is_array( $cached ) && isset( $cached['score'], $cached['tips'] ) ) { 41 return $cached;41 return self::available_actions( $cached ); 42 42 } 43 43 } 44 44 $result = self::evaluate_uncached(); 45 45 set_transient( 'rvsdado_advisor_eval', $result, 15 * MINUTE_IN_SECONDS ); 46 return self::available_actions( $result ); 47 } 48 49 /** Resolve user permissions and plugin availability outside the shared cache. */ 50 private static function available_actions( array $result ): array { 51 foreach ( $result['tips'] as &$tip ) { 52 if ( false !== strpos( (string) ( $tip['action']['url'] ?? '' ), 'page=wc-status' ) 53 && ( ! class_exists( 'WooCommerce' ) || ! current_user_can( 'manage_woocommerce' ) ) ) { 54 $tip['action'] = array( 'tab' => 'cleanup' ); 55 $tip['detail'] = __( 'WooCommerce tools are unavailable. Review the remaining WooCommerce tables under Potential leftover tables in Cleanup. Deactivation does not mean the stored orders and products are disposable; back up and verify ownership before removing tables.', 'rvs-database-doctor' ); 56 } 57 } 58 unset( $tip ); 46 59 return $result; 47 60 } -
rvs-database-doctor/trunk/includes/class-rvs-db-doctor-analyzer.php
r3754548 r3764690 72 72 delete_transient( 'rvsdado_orphan_tables_v4' ); 73 73 delete_transient( 'rvsdado_orphan_tables_v5' ); 74 delete_transient( 'rvsdado_orphan_tables_v6' ); 74 75 self::$owner_registry_cache = null; 75 76 } -
rvs-database-doctor/trunk/includes/class-rvs-db-doctor-runner.php
r3755680 r3764690 267 267 return apply_filters( 268 268 'rvsdado_snapshot_types', 269 array( 'optimize_now', 'cleanup_revisions', 'cleanup_orphan_postmeta', 'cleanup_orphan_commentmeta', 'cleanup_orphan_term_relationships', 'cleanup_orphan_usermeta', 'cleanup_orphan_termmeta', 'cleanup_oembed_cache', 'cleanup_wc_orphan_order_items', 'cleanup_wc_orphan_order_itemmeta', 'cleanup_old_trash', 'engine_convert', 'optimize_table', ' cleanup_as_old_actions', 'cleanup_as_old_logs', 'drop_orphan_tables' )269 array( 'optimize_now', 'cleanup_revisions', 'cleanup_orphan_postmeta', 'cleanup_orphan_commentmeta', 'cleanup_orphan_term_relationships', 'cleanup_orphan_usermeta', 'cleanup_orphan_termmeta', 'cleanup_oembed_cache', 'cleanup_wc_orphan_order_items', 'cleanup_wc_orphan_order_itemmeta', 'cleanup_old_trash', 'engine_convert', 'optimize_table', 'purge_action_scheduler', 'cleanup_as_old_actions', 'cleanup_as_old_logs', 'drop_orphan_tables' ) 270 270 ); 271 271 } … … 308 308 return apply_filters( 309 309 'rvsdado_backup_types', 310 array( 'optimize_now', 'cleanup_chain', 'engine_convert', 'index_add', 'index_bulk_add', 'index_drop', 'add_pk', 'fix_collation', 'autoload_update', 'autoload_bulk_update', 'cleanup_expired_transients', 'cleanup_auto_drafts', 'cleanup_spam_comments', 'cleanup_trash_comments', 'cleanup_wc_expired_sessions', ' cleanup_as_old_actions', 'cleanup_as_old_logs', 'cleanup_duplicate_postmeta', 'cleanup_stale_session_tokens', 'cleanup_edit_locks', 'cleanup_unused_terms', 'cleanup_plugin_caches', 'cleanup_orphan_postmeta', 'cleanup_orphan_commentmeta', 'cleanup_orphan_term_relationships', 'cleanup_orphan_usermeta', 'cleanup_orphan_termmeta', 'cleanup_oembed_cache', 'cleanup_wc_orphan_order_items', 'cleanup_wc_orphan_order_itemmeta', 'cleanup_old_trash', 'cleanup_revisions', 'quarantine_orphan_tables', 'restore_quarantined_tables', 'delete_quarantined_tables', 'drop_orphan_tables' )310 array( 'optimize_now', 'cleanup_chain', 'engine_convert', 'index_add', 'index_bulk_add', 'index_drop', 'add_pk', 'fix_collation', 'autoload_update', 'autoload_bulk_update', 'cleanup_expired_transients', 'cleanup_auto_drafts', 'cleanup_spam_comments', 'cleanup_trash_comments', 'cleanup_wc_expired_sessions', 'purge_action_scheduler', 'cleanup_as_old_actions', 'cleanup_as_old_logs', 'cleanup_duplicate_postmeta', 'cleanup_stale_session_tokens', 'cleanup_edit_locks', 'cleanup_unused_terms', 'cleanup_plugin_caches', 'cleanup_orphan_postmeta', 'cleanup_orphan_commentmeta', 'cleanup_orphan_term_relationships', 'cleanup_orphan_usermeta', 'cleanup_orphan_termmeta', 'cleanup_oembed_cache', 'cleanup_wc_orphan_order_items', 'cleanup_wc_orphan_order_itemmeta', 'cleanup_old_trash', 'cleanup_revisions', 'quarantine_orphan_tables', 'restore_quarantined_tables', 'delete_quarantined_tables', 'drop_orphan_tables' ) 311 311 ); 312 312 } … … 352 352 case 'cleanup_unused_terms': 353 353 return array( $wpdb->terms, $wpdb->term_taxonomy, $wpdb->term_relationships, $wpdb->termmeta, $wpdb->options ); 354 case 'purge_action_scheduler': 354 355 case 'cleanup_as_old_actions': 355 356 return array_values( array_filter( array( $p . 'actionscheduler_actions', $p . 'actionscheduler_logs' ), array( 'RVSDADO_Analyzer', 'table_exists' ) ) ); -
rvs-database-doctor/trunk/languages/rvs-database-doctor.pot
r3760203 r3764690 3 3 msgid "" 4 4 msgstr "" 5 "Project-Id-Version: RVS Database Doctor 2.8. 5\n"5 "Project-Id-Version: RVS Database Doctor 2.8.6\n" 6 6 "Report-Msgid-Bugs-To: https://rberet.com/wp-plugins/rvs-database-doctor\n" 7 7 "POT-Creation-Date: 2026-05-02 00:00+0000\n" -
rvs-database-doctor/trunk/readme.txt
r3760203 r3764690 5 5 Tested up to: 7.1 6 6 Requires PHP: 7.4 7 Stable tag: 2.8. 57 Stable tag: 2.8.6 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 81 81 == Changelog == 82 82 83 = 2.8.6 = 84 * Fixed: Advisor only offers WooCommerce Tools when WooCommerce is loaded and the current user has permission; otherwise it directs users to Cleanup. 85 * Added: batched emptying of all Action Scheduler actions and logs with PURGE confirmation. Requires inactive Action Scheduler providers and cannot be scheduled. 86 * Fixed: retention cleanup handles old actions without a last-attempt date consistently with candidate counts and previews. 87 * Fixed: known table signatures no longer hide leftover WooCommerce and other plugin tables as if their owners were active. 88 * Changed: expanded plugin identification for potential leftover tables while preserving protection for active owners and the Action Scheduler runtime. 89 * Added: selection of wp-content directories for site backups, with the selected scope stored in the signed manifest. Partial backups cannot be used as full migration packages. 90 * Added: hourly, daily, weekly and monthly automatic backups from the Backups tab, preserving selected directories and export methods, with pause, resume and delete controls. 91 83 92 = 2.8.5 = 84 93 * Changed: the standalone migration installer imports the database dump in batched multi-statement transactions (up to 2000 statements or 512 KB per round trip, one commit per work slice) instead of one query per statement. Import slices are 5-15 seconds depending on the host limit and the wizard resumes 100 ms after each slice. … … 234 243 == Upgrade Notice == 235 244 245 = 2.8.6 = 246 Fixes WooCommerce Advisor links and leftover-table detection. Adds explicit Action Scheduler queue emptying, selected-directory backups and recurring backup controls. Whole-queue emptying requires inactive Action Scheduler providers. 247 236 248 = 2.8.5 = 237 249 Adds full-site migration packages and the standalone rvsmigrate.php installer, single-archive full-site backups, and much faster restore/migration imports. Take a backup before updating. -
rvs-database-doctor/trunk/rvs-database-doctor.php
r3760203 r3764690 4 4 * Plugin URI: https://rberet.com/wp-plugins/rvs-database-doctor 5 5 * Description: Safely analyze and optimize WordPress / WooCommerce databases with backups, restore, scheduled jobs, and an actionable health advisor. 6 * Version: 2.8. 56 * Version: 2.8.6 7 7 * Requires at least: 6.2 8 8 * Requires PHP: 7.4 … … 22 22 } 23 23 24 define( 'RVSDADO_VERSION', '2.8. 5' );24 define( 'RVSDADO_VERSION', '2.8.6' ); 25 25 define( 'RVSDADO_DB_VERSION', '6' ); 26 26 define( 'RVSDADO_FILE', __FILE__ );
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)