settings = new DB_Cleaner_Settings( $this->options ); add_action( 'wp_ajax_adminoptim_clean_db_now', [ $this, 'ajax_clean_db_now' ] ); // admin_init, not init: this only re-verifies a recurring schedule that // has no front-end contract, so it never needs to run for anonymous // front-end traffic — an admin visiting the dashboard is enough to // keep it in sync. add_action( 'admin_init', [ $this, 'schedule_database_cleanup' ] ); add_action( 'adminoptim_database_cleanup', [ $this, 'clean_db_on_schedule' ] ); add_action( 'adminoptim_deactivate_plugin', [ $this, 'remove_schedule_on_deactivation' ] ); } /** * Set default options for this module. * * @return array */ protected function get_default_options() { return [ 'enable_day' => 'sunday', 'enable_time_hour' => '3', 'enable_time_min' => '00', 'enable_logging' => '1', 'delete_revisions' => '1', 'delete_auto_draft' => '1', 'delete_trashed_posts' => '1', 'delete_orphaned_postmeta' => '1', 'delete_duplicate_postmeta' => '1', 'delete_empty_postmeta' => '1', 'delete_oembed_cache' => '1', 'delete_unapproved_comments' => '1', 'delete_spam_comments' => '1', 'delete_trashed_comments' => '1', 'delete_duplicate_commentmeta' => '1', 'delete_orphaned_commentmeta' => '1', 'delete_empty_commentmeta' => '1', 'delete_pingbacks' => '1', 'delete_unused_terms' => '1', 'delete_duplicate_termmeta' => '1', 'delete_orphaned_termmeta' => '1', 'delete_orphaned_term_rs' => '1', 'delete_duplicate_usermeta' => '1', 'delete_orphaned_usermeta' => '1', 'batch_size' => 500, ]; } /** * Schedule database cleanup * * @return void */ public function schedule_database_cleanup() { if ( ! empty( $this->options['enable'] ) ) { $day = $this->options['enable_day'] ?? 'sunday'; $hour = $this->options['enable_time_hour'] ?? '3'; // set default time as 3am. $min = $this->options['enable_time_min'] ?? '00'; $timestamp = strtotime( "this $day $hour hour $min minutes" ); if ( $timestamp < time() ) { $timestamp = strtotime( "next $day $hour hour $min minutes" ); } if ( ! as_has_scheduled_action( 'adminoptim_database_cleanup' ) ) { as_schedule_recurring_action( $timestamp, WEEK_IN_SECONDS, 'adminoptim_database_cleanup', [], '', true ); } } elseif ( as_has_scheduled_action( 'adminoptim_database_cleanup' ) ) { as_unschedule_all_actions( 'adminoptim_database_cleanup' ); } /** * Fires after the main cleanup schedule is (re)evaluated. Pro hooks * this to layer its own remnant-job (continuous cleaning) schedule * on top, without Free needing to know that capability exists. */ do_action( 'adminoptim_db_cleaner_schedule', $this->options ); } /** * Deactivate schedule * * @return void */ public function remove_schedule_on_deactivation() { if ( as_has_scheduled_action( 'adminoptim_database_cleanup' ) ) { as_unschedule_action( 'adminoptim_database_cleanup' ); } /** * Fires when the cleanup schedule is being torn down. Pro hooks * this to unschedule its own remnant-job action alongside it. */ do_action( 'adminoptim_db_cleaner_deactivate' ); } /** * Clean database on schedule * * @return void */ public function clean_db_on_schedule() { set_time_limit( 300 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged /** * Fires before a full cleanup run starts. Pro hooks this to clear * any pending remnant-job state from a previous run, since this run * is about to recompute it from scratch. */ do_action( 'adminoptim_db_cleaner_before_run', $this->options ); $log = [ 'start' => time() ]; $log_message = []; /** * The query engine used for this cleanup run. Pro hooks this to * supply its own engine (deep cleaning, remnant-aware batching) — * Free's default engine below has no knowledge of that extension. */ $cleaner = apply_filters( 'adminoptim_db_cleaner_query_engine', new DB_Queries(), $this->options ); try { if ( ! empty( $this->options['delete_revisions'] ) ) { $revisions_interval = 'custom' === $this->options['delete_revisions'] ? absint( $this->options['delete_revisions_day'] ?? 0 ) : 0; $response = $cleaner->clean_revisions( $revisions_interval ); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' revisions deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_auto_draft'] ) ) { $response = $cleaner->clean_auto_draft(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' Auto draft deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_trashed_posts'] ) ) { $response = $cleaner->clean_trashed_post(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' trashed posts deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_orphaned_postmeta'] ) ) { $response = $cleaner->clean_orphaned_postmeta(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' orphaned post meta deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_duplicate_postmeta'] ) ) { $response = $cleaner->clean_duplicate_postmeta(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' duplicate post meta deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_empty_postmeta'] ) ) { $response = $cleaner->clean_empty_postmeta(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' empty post meta deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_oembed_cache'] ) ) { $response = $cleaner->clean_oembed_cache(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' oEmbed cache deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_unapproved_comments'] ) ) { $response = $cleaner->clean_comments(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' unapproved comments deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_spam_comments'] ) ) { $response = $cleaner->clean_comments( 'spam' ); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' spam comments deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_trashed_comments'] ) ) { $response = $cleaner->clean_comments( 'trash' ); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' trashed comments deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_duplicate_commentmeta'] ) ) { $response = $cleaner->clean_duplicate_commentmeta(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' duplicate comment meta deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_orphaned_commentmeta'] ) ) { $response = $cleaner->clean_orphaned_commentmeta(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' orphaned comment meta deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_empty_commmentmeta'] ) ) { $response = $cleaner->clean_empty_commentmeta(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' empty comment meta deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_pingbacks'] ) ) { $response = $cleaner->clean_pingbacks(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' pingbacks deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_unused_terms'] ) ) { $response = $cleaner->clean_unused_terms(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' unused terms deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_duplicate_termmeta'] ) ) { $response = $cleaner->clean_duplicate_termmeta(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' duplicate term meta deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_orphaned_termmeta'] ) ) { $response = $cleaner->clean_orphaned_termmeta(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' orphaned term meta deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_orphaned_term_rs'] ) ) { $response = $cleaner->clean_orphaned_term_rs(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' orphaned term relationship deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_duplicate_usermeta'] ) ) { $response = $cleaner->clean_duplicate_usermeta(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' duplicate user meta deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_orphaned_usermeta'] ) ) { $response = $cleaner->clean_orphaned_usermeta(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' orphaned user meta deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } if ( ! empty( $this->options['delete_transient'] ) && in_array( $this->options['delete_transient'], [ 'all', 'expired' ], true ) && method_exists( $cleaner, 'clean_transient' ) ) { $response = $cleaner->clean_transient( $this->options['delete_transient'] ); if ( 'success' === $response['status'] ) { $log_message[] = esc_html( $response['count'] . __( ' transient deleted', 'admin-optimizer' ) ); } else { $log_message[] = esc_html( $response['message'] ); } } $response = $cleaner->optimize_database(); if ( 'success' === $response['status'] ) { $log_message[] = esc_html__( 'Optimized all tables in database', 'admin-optimizer' ); } else { $log_message[] = $response['message']; } } catch ( \Error $e ) { $log_message[] = $e->getMessage(); } $log['end'] = time(); $log['clean_log'] = implode( '%%', $log_message ); if ( ! empty( $this->options['enable_logging'] ) ) { $db_log = get_option( 'adminoptim_db_log', [] ); $db_log[] = $log; if ( 5 < count( $db_log ) ) { array_shift( $db_log ); } update_option( 'adminoptim_db_log', $db_log, false ); } } /** * Ajax function to clean db * * @return void */ public function ajax_clean_db_now() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( new \WP_Error( 'adminoptim-unauthorized-user', ao_get_admin_notice( __( 'Unauthorized action.', 'admin-optimizer' ), [ 'type' => 'error', ] ) ) ); } check_ajax_referer( 'adminoptim-db-cleanup', 'nonce' ); do_action( 'adminoptim_database_cleanup' ); $response = [ 'message' => __( 'Database cleanup completed. Check the logs for more details.', 'admin-optimizer' ) ]; wp_send_json_success( $response ); } }