1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
|
<?php /** * Provide basic rate limiting functionality via WP Options API. * * Currently only provides a simple limit by delaying action by X seconds. * * Example usage: * * When an action runs, call set_rate_limit, e.g.: * * WC_Rate_Limiter::set_rate_limit( "{$my_action_name}_{$user_id}", $delay ); * * This sets a timestamp for future timestamp after which action can run again. * * * Then before running the action again, check if the action is allowed to run, e.g.: * * if ( WC_Rate_Limiter::retried_too_soon( "{$my_action_name}_{$user_id}" ) ) { * add_notice( 'Sorry, too soon!' ); * } * * @package WooCommerce\Classes * @version 3.9.0 * @since 3.9.0 */
defined( 'ABSPATH' ) || exit;
/** * Rate limit class. */ class WC_Rate_Limiter {
/** * Cache group. */ const CACHE_GROUP = 'wc_rate_limit';
/** * Hook in methods. */ public static function init() { add_action( 'woocommerce_cleanup_rate_limits', array( __CLASS__, 'cleanup' ) ); }
/** * Constructs key name from action identifier. * Left in for backwards compatibility. * * @param string $action_id Identifier of the action. * @return string */ public static function storage_id( $action_id ) { return $action_id; }
/** * Gets a cache prefix. * * @param string $action_id Identifier of the action. * @return string */ protected static function get_cache_key( $action_id ) { return WC_Cache_Helper::get_cache_prefix( 'rate_limit' . $action_id ); }
/** * Retrieve a cached rate limit. * * @param string $action_id Identifier of the action. * @return bool|int */ protected static function get_cached( $action_id ) { return wp_cache_get( self::get_cache_key( $action_id ), self::CACHE_GROUP ); }
/** * Cache a rate limit. * * @param string $action_id Identifier of the action. * @param int $expiry Timestamp when the limit expires. * @return bool */ protected static function set_cache( $action_id, $expiry ) { return wp_cache_set( self::get_cache_key( $action_id ), $expiry, self::CACHE_GROUP ); }
/** * Returns true if the action is not allowed to be run by the rate limiter yet, false otherwise. * * @param string $action_id Identifier of the action. * @return bool */ public static function retried_too_soon( $action_id ) { global $wpdb;
$next_try_allowed_at = self::get_cached( $action_id );
if ( false === $next_try_allowed_at ) { $next_try_allowed_at = $wpdb->get_var( $wpdb->prepare( " SELECT rate_limit_expiry FROM {$wpdb->prefix}wc_rate_limits WHERE rate_limit_key = %s ", $action_id ) );
self::set_cache( $action_id, $next_try_allowed_at ); }
// No record of action running, so action is allowed to run. if ( null === $next_try_allowed_at ) { return false; }
// Before the next run is allowed, retry forbidden. if ( time() <= $next_try_allowed_at ) { return true; }
// After the next run is allowed, retry allowed. return false; }
/** * Sets the rate limit delay in seconds for action with identifier $id. * * @param string $action_id Identifier of the action. * @param int $delay Delay in seconds. * @return bool True if the option setting was successful, false otherwise. */ public static function set_rate_limit( $action_id, $delay ) { global $wpdb;
$next_try_allowed_at = time() + $delay;
$result = $wpdb->replace( $wpdb->prefix . 'wc_rate_limits', array( 'rate_limit_key' => $action_id, 'rate_limit_expiry' => $next_try_allowed_at, ), array( '%s', '%d' ) );
self::set_cache( $action_id, $next_try_allowed_at );
return false !== $result; }
/** * Cleanup expired rate limits from the database and clear caches. */ public static function cleanup() { global $wpdb;
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}wc_rate_limits WHERE rate_limit_expiry < %d", time() ) );
if ( class_exists( 'WC_Cache_Helper' ) ) { WC_Cache_Helper::invalidate_cache_group( self::CACHE_GROUP ); } } }
WC_Rate_Limiter::init();
|