". * * @phpstan-type Hold array{key:string,time:string,party:int,expires:int} */ final class CheckoutHolds { private const OPTION = 'fxtr_holds_'; public static function register(): void { add_filter( 'fxtr_table_holds', [ self::class, 'filter' ], 10, 2 ); add_action( 'fxtr_reservation_created', static function ( int $id, string $hold ): void { if ( '' !== $hold ) { ( new Reserver() )->release( $hold ); } }, 10, 2 ); } /** How long a hold lasts; 0 when the owner turned holds off. */ public static function minutes(): int { return max( 0, min( 30, (int) Settings::get( 'hold_minutes', 10 ) ) ); } /** * `fxtr_table_holds`: the holds of the date that have not run out. * * @param list> $holds * @return list> */ public static function filter( array $holds, string $date ): array { foreach ( self::live( $date ) as $hold ) { $holds[] = [ 'time' => $hold['time'], 'party' => $hold['party'], 'key' => $hold['key'], ]; } return $holds; } /** * The date's holds that have not run out. * * @return list */ public static function live( string $date, ?int $now = null ): array { $now = $now ?? time(); $holds = get_option( self::OPTION . str_replace( '-', '', $date ), [] ); return array_values( array_filter( is_array( $holds ) ? $holds : [], static fn( mixed $hold ): bool => is_array( $hold ) && (int) ( $hold['expires'] ?? 0 ) > $now ) ); } /** * Adds a hold. Call under the date's lock. * * @return Hold */ public static function put( string $date, string $time, int $party, int $minutes, ?int $now = null ): array { $now = $now ?? time(); $hold = [ 'key' => str_replace( '-', '', $date ) . '.' . wp_generate_password( 20, false ), 'time' => $time, 'party' => $party, 'expires' => $now + $minutes * MINUTE_IN_SECONDS, ]; $holds = self::live( $date, $now ); $holds[] = $hold; self::save( $date, $holds ); return $hold; } /** * Takes a hold away. Call under the date's lock. */ public static function remove( string $key, ?int $now = null ): void { $date = self::date( $key ); if ( null === $date ) { return; } self::save( $date, array_values( array_filter( self::live( $date, $now ), static fn( array $hold ): bool => $hold['key'] !== $key ) ) ); } /** * The hold's date ("YYYY-MM-DD"), read from its key, or null. */ public static function date( string $key ): ?string { if ( 1 !== preg_match( '/^(\d{4})(\d{2})(\d{2})\.[A-Za-z0-9]{20}$/', $key, $m ) ) { return null; } return Planner::is_date( "{$m[1]}-{$m[2]}-{$m[3]}" ) ? "{$m[1]}-{$m[2]}-{$m[3]}" : null; } /** * @param list $holds */ private static function save( string $date, array $holds ): void { $name = self::OPTION . str_replace( '-', '', $date ); if ( [] === $holds ) { delete_option( $name ); return; } update_option( $name, $holds, false ); } }