array( 'invalid_report' ) ); } $health = get_option( 'kconv_retention_health', array() ); if ( ! is_array( $health ) ) { $health = array(); } $health['finished'] = time(); $health['deleted'] = max( 0, (int) ( $report['deleted'] ?? 0 ) ); $health['batches'] = max( 0, (int) ( $report['batches'] ?? 0 ) ); // Do not retain exception/SQL text supplied by an extension or database driver. $health['errors'] = isset( $report['errors'] ) ? ( is_array( $report['errors'] ) ? count( $report['errors'] ) : (int) (bool) $report['errors'] ) : 0; $health['remaining'] = isset( $report['remaining'] ) ? max( 0, (int) $report['remaining'] ) : null; if ( ! $health['errors'] ) { $health['last_success'] = $health['finished']; } update_option( 'kconv_retention_health', $health, false ); } public static function health() { global $wpdb; $saved = get_option( 'kconv_retention_health', array() ); if ( ! is_array( $saved ) ) { $saved = array(); } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- The local retention health count is a fresh aggregate over the fixed plugin entries table. It is administrator diagnostic data; caching would misstate current overdue cleanup. $rows = $wpdb->get_row( $wpdb->prepare( 'SELECT COUNT(*) AS expired,MIN(expires_at) AS oldest FROM %i WHERE expires_at < %s', KCONV_Core::table( 'entries' ), gmdate( 'Y-m-d H:i:s' ) ), ARRAY_A ); $storage_ok = ! $wpdb->last_error && is_array( $rows ); $daily = wp_next_scheduled( 'kconv_cleanup' ); $continuation = wp_next_scheduled( 'kconv_cleanup_continue' ); $next = $daily && $continuation ? min( $daily, $continuation ) : ( $daily ? $daily : $continuation ); $overdue = $storage_ok && ! empty( $rows['oldest'] ) && $rows['oldest'] < gmdate( 'Y-m-d H:i:s', time() - 2 * DAY_IN_SECONDS ); $continuation_overdue = $storage_ok && ! empty( $rows['expired'] ) && $continuation && $continuation < time() - 900; $unfinished = ! empty( $saved['started'] ) && (int) $saved['started'] > (int) ( $saved['finished'] ?? 0 ); return array( 'saved' => $saved, 'next' => $next, 'continuation' => $continuation, 'continuation_overdue' => $continuation_overdue, 'storage_ok' => $storage_ok, 'expired' => $storage_ok ? (int) $rows['expired'] : null, 'oldest' => $storage_ok ? $rows['oldest'] : null, 'unfinished' => $unfinished, 'attention' => ! $storage_ok || ! $daily || ! empty( $saved['errors'] ) || $overdue || $continuation_overdue || ( $unfinished && $saved['started'] < time() - 900 ) ); } public static function panel() { if ( ! current_user_can( 'manage_options' ) ) { return; } if ( get_option( 'kconv_schema_problem' ) ) { echo '
' . esc_html__( 'Form storage setup needs attention. Some required database tables or columns could not be verified. Repair storage before relying on new submissions.', 'killerconversions' ) . '
' . esc_html__( 'An entry was saved, but a follow-up action did not finish. Review the entry and its delivery history before taking another action.', 'killerconversions' ) . ' ' . esc_html__( 'Review saved entry', 'killerconversions' ) . '
' . esc_html__( 'Retention storage could not be inspected. Counts are unavailable; repair storage before retrying.', 'killerconversions' ) . '
'; } else { /* translators: %s: number of expired entries. */ echo '' . esc_html( sprintf( _n( '%s entry is past its retention time.', '%s entries are past their retention time.', $health['expired'], 'killerconversions' ), number_format_i18n( $health['expired'] ) ) ) . '
'; if ( $health['oldest'] ) { echo '' . esc_html__( 'Oldest expiry:', 'killerconversions' ) . ' ' . esc_html( KCONV_Entries::display_time( $health['oldest'] ) ) . '
'; } } echo '' . esc_html( $health['continuation_overdue'] ? __( 'The next cleanup batch is overdue. Check that WordPress cron is running, or run cleanup now.', 'killerconversions' ) : __( 'More expired entries remain. Another cleanup batch is scheduled; it will run when WordPress cron runs.', 'killerconversions' ) ) . '
'; } /* translators: 1: number of deleted entries, 2: cleanup batch count. */ if ( ! empty( $saved['finished'] ) ) { echo '' . esc_html( sprintf( __( 'Last run deleted %1$s entries in %2$s batches.', 'killerconversions' ), number_format_i18n( $saved['deleted'] ?? 0 ), number_format_i18n( $saved['batches'] ?? 0 ) ) ) . '
'; } if ( ! empty( $saved['errors'] ) ) { echo '' . esc_html__( 'The last cleanup reported a storage, processing or scheduling failure. Remaining records were not reported as deleted. Repair the cause and run cleanup again.', 'killerconversions' ) . '
'; } if ( $health['unfinished'] ) { echo '' . esc_html__( 'The latest cleanup started but has not recorded completion. It may still be running or may have been interrupted.', 'killerconversions' ) . '
'; } echo '' . esc_html__( 'Cleanup permanently removes expired entries and their related local files, delivery and measurement records. It does not change retention periods or remove data already received by another service. A schedule alone does not prove the server runs WordPress cron.', 'killerconversions' ) . '