s = AccessIQ_Settings::instance(); add_action( 'accessiq_scheduled_scan', [ $this, 'run_scheduled_scan' ] ); add_action( 'accessiq_cleanup_old_data', [ $this, 'cleanup_old_data' ] ); add_action( 'accessiq_settings_saved', [ $this, 'reschedule' ] ); add_filter( 'cron_schedules', [ $this, 'add_schedules' ] ); $this->maybe_schedule(); } public function add_schedules( $schedules ) { $schedules['accessiq_weekly'] = [ 'interval' => WEEK_IN_SECONDS, 'display' => 'Weekly' ]; $schedules['accessiq_monthly'] = [ 'interval' => MONTH_IN_SECONDS, 'display' => 'Monthly' ]; return $schedules; } public function maybe_schedule() { // Cleanup must always stay scheduled (independent of scan schedule). if ( ! wp_next_scheduled( 'accessiq_cleanup_old_data' ) ) { wp_schedule_event( time(), 'daily', 'accessiq_cleanup_old_data' ); } if ( ! $this->s->get( 'schedule_enabled' ) ) { return; } if ( ! wp_next_scheduled( 'accessiq_scheduled_scan' ) ) { $choice = $this->s->get( 'schedule_freq', 'weekly' ); // 'daily' is a core recurrence; weekly/monthly are registered in add_schedules(). $freq = ( 'daily' === $choice ) ? 'daily' : ( 'monthly' === $choice ? 'accessiq_monthly' : 'accessiq_weekly' ); wp_schedule_event( time() + HOUR_IN_SECONDS, $freq, 'accessiq_scheduled_scan' ); } } public function reschedule() { wp_clear_scheduled_hook( 'accessiq_scheduled_scan' ); // Keep cleanup cron; only rebuild scan schedule from settings. $this->maybe_schedule(); } /** * Run scheduled scan: trigger server-side scans for up to 10 published pages. */ public function run_scheduled_scan() { $pages = get_posts( array( 'post_type' => array( 'page', 'post' ), 'posts_per_page' => 10, 'post_status' => 'publish', ) ); $count = 0; foreach ( $pages as $page ) { $url = get_permalink( $page->ID ); if ( ! $url ) { continue; } // Direct in-process scan (no public AJAX endpoint). $result = AccessIQ_Scanner::run_scan_for_schedule( $url ); if ( ! is_wp_error( $result ) ) { ++$count; } } // Send notification email. $email = $this->s->get( 'notify_email', get_option( 'admin_email' ) ); if ( $email ) { wp_mail( $email, __( 'AccessIQ — Scheduled Scan Triggered', 'accessiq' ), /* translators: 1: number of pages, 2: site URL */ sprintf( __( 'A scheduled accessibility scan completed for %1$d pages on %2$s.', 'accessiq' ), $count, home_url() ) ); } } /** * Delete analytics rows older than the configured retention period. * Runs daily via cron. */ public function cleanup_old_data() { global $wpdb; $days = absint( get_option( 'accessiq_analytics_data_retention', 90 ) ); if ( $days < 7 ) { $days = 7; // safety minimum } $cutoff = gmdate( 'Y-m-d H:i:s', strtotime( '-' . $days . ' days', current_time( 'timestamp' ) ) ); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( $wpdb->prepare( 'DELETE FROM `' . esc_sql( $wpdb->prefix . 'accessiq_clicks' ) . '` WHERE created_at < %s', $cutoff ) ); $wpdb->query( $wpdb->prepare( 'DELETE FROM `' . esc_sql( $wpdb->prefix . 'accessiq_sessions' ) . '` WHERE created_at < %s', $cutoff ) ); $wpdb->query( $wpdb->prepare( 'DELETE FROM `' . esc_sql( $wpdb->prefix . 'accessiq_page_views' ) . '` WHERE created_at < %s', $cutoff ) ); // phpcs:enable $this->cleanup_old_scans(); /** * Fires after analytics + scan retention cleanup (Pro prunes its own tables here). * * @param string $cutoff Analytics cutoff (site-local datetime). */ do_action( 'accessiq_after_cleanup', $cutoff ); } /** * Delete scan history (CPT + issues) older than the scan retention window. * Default 365 days; return 0 from the filter to keep scans forever. */ public function cleanup_old_scans() { global $wpdb; $days = (int) apply_filters( 'accessiq_scan_retention_days', 365 ); if ( $days > 0 ) { $cutoff = gmdate( 'Y-m-d H:i:s', strtotime( '-' . $days . ' days', current_time( 'timestamp' ) ) ); do { $ids = get_posts( array( 'post_type' => ACCESSIQ_SCAN_CPT, 'post_status' => 'any', 'posts_per_page' => 100, 'fields' => 'ids', 'no_found_rows' => true, 'date_query' => array( array( 'column' => 'post_date', 'before' => $cutoff ) ), ) ); foreach ( $ids as $id ) { wp_delete_post( $id, true ); } } while ( ! empty( $ids ) ); } // Orphaned issue rows (scan deleted manually or by retention). // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query( "DELETE i FROM {$wpdb->prefix}accessiq_issues i LEFT JOIN {$wpdb->posts} p ON p.ID = i.scan_id WHERE p.ID IS NULL" ); } }