session, * which REST requests don't load. Samples never include the session key or value. * * @since 1.0.0 */ class ExpiredSessions extends BaseCheck { /** * Sessions table. * * @since 1.0.0 * @return string */ public static function table(): string { global $wpdb; return $wpdb->prefix . 'woocommerce_sessions'; } /** * {@inheritDoc} Also when another plugin stores sessions. */ public function unavailable(): string { $reason = parent::unavailable(); if ( '' !== $reason ) { return $reason; } $handler = apply_filters( 'woocommerce_session_handler', 'WC_Session_Handler' ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment, WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WooCommerce's own filter, read the way WooCommerce picks its session class. if ( ! is_string( $handler ) || ! class_exists( $handler ) || ! is_a( $handler, 'WC_Session_Handler', true ) ) { return __( 'Another plugin stores shopper sessions on this site, so they aren’t in WooCommerce’s table.', 'admin-powerups-for-woocommerce' ); } return ''; } /** * {@inheritDoc} * * @param array $state State from the last call. * @param float $budget Seconds to spend. * @return array{count: int, done: bool, state: array} */ public function count( array $state, float $budget ): array { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- live count on the session_expiry index. $count = (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE session_expiry < %d', self::table(), time() ) ); return array( 'count' => $count, 'done' => true, 'state' => array(), ); } /** * {@inheritDoc} * * @param array $state State from the last call. * @param int $batch Rows per statement. * @param float $budget Seconds to spend. * @return array{deleted: int, skipped: int, notes: array, done: bool, state: array} */ public function clean( array $state, int $batch, float $budget ): array { global $wpdb; $out = self::cleaned( $state, false ); $start = microtime( true ); $now = time(); do { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- WooCommerce's own clean-up statement, bounded; the cache group is invalidated below. $gone = (int) $wpdb->query( $wpdb->prepare( 'DELETE FROM %i WHERE session_expiry < %d ORDER BY session_expiry LIMIT %d', self::table(), $now, $batch ) ); $out['deleted'] += max( 0, $gone ); if ( $gone < $batch || '' !== $wpdb->last_error ) { $out['done'] = '' === $wpdb->last_error; break; } } while ( self::time_left( $start, $budget ) ); if ( $out['deleted'] && class_exists( '\WC_Cache_Helper' ) && defined( 'WC_SESSION_CACHE_GROUP' ) ) { \WC_Cache_Helper::invalidate_cache_group( WC_SESSION_CACHE_GROUP ); } return $out; } /** * {@inheritDoc} ID, expiry and size only. * * @param int $limit Rows (at most 10). * @return array{columns: array, rows: array>} */ public function sample( int $limit = 10 ): array { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- a few rows for the sample; never the key or value. $rows = (array) $wpdb->get_results( $wpdb->prepare( 'SELECT session_id, session_expiry, LENGTH(session_value) AS size FROM %i WHERE session_expiry < %d ORDER BY session_expiry LIMIT %d', self::table(), time(), max( 1, min( 10, $limit ) ) ) ); $out = array(); foreach ( $rows as $row ) { $out[] = array( 'id' => self::cell( $row->session_id ), 'expired' => self::date_cell( (int) $row->session_expiry ), 'size' => (string) size_format( (int) $row->size ), ); } return array( 'columns' => array( 'id' => __( 'Session ID', 'admin-powerups-for-woocommerce' ), 'expired' => __( 'Expired', 'admin-powerups-for-woocommerce' ), 'size' => __( 'Size', 'admin-powerups-for-woocommerce' ), ), 'rows' => $out, ); } }