increment( $this->key( $identifier, $period ), $period ) > $limit; } /** * Seconds until the current window ends. Windows are aligned to the * clock, so this is the same for every identifier. * * @param int $period Window length in seconds. * @return int Seconds remaining. */ public function get_reset_time( int $period = 60 ): int { $period = max( 1, $period ); return $period - ( time() % $period ); } /** * Counter name for an identifier in the current window. */ private function key( string $identifier, int $period ): string { return self::PREFIX . md5( $identifier ) . '_' . $period . '_' . intdiv( time(), $period ); } /** * Add one to a counter and return the new value. */ private function increment( string $key, int $period ): int { if ( wp_using_ext_object_cache() ) { wp_cache_add( $key, 0, self::GROUP, $period ); $value = wp_cache_incr( $key, 1, self::GROUP ); return is_int( $value ) ? $value : 1; } global $wpdb; // LAST_INSERT_ID(expr) makes the incremented value readable on this // connection only, so the read below is ours even mid-burst. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- An atomic counter is the point; the cache API has no atomic increment without an object cache. $affected = $wpdb->query( $wpdb->prepare( "INSERT INTO {$wpdb->options} (option_name, option_value, autoload) VALUES (%s, '1', 'off') ON DUPLICATE KEY UPDATE option_value = LAST_INSERT_ID(option_value + 1)", '_transient_' . $key ) ); if ( 1 === $affected ) { // A new window: give it an expiry so core's cleanup deletes it. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Companion row of the counter above. $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO {$wpdb->options} (option_name, option_value, autoload) VALUES (%s, %d, 'off')", '_transient_timeout_' . $key, time() + $period ) ); return 1; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reads this connection's LAST_INSERT_ID. return (int) $wpdb->get_var( 'SELECT LAST_INSERT_ID()' ); } }