PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 11.1.0
WooCommerce v11.1.0
11.1.0 11.1.0-rc.2 11.1.0-rc.1 11.1.0-beta.2 11.1.0-beta.1 11.0.1 11.0.0 11.0.0-rc.3 11.0.0-rc.2 11.0.0-rc.1 11.0.0-beta.2 11.0.0-beta.1 10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / src / Internal / Caches / VersionStringGenerator.php
woocommerce / src / Internal / Caches Last commit date
OrdersVersionStringInvalidator.php 6 months ago ProductCache.php 7 months ago ProductCacheController.php 1 month ago ProductTransientsDeferrer.php 1 month ago ProductVersionStringInvalidator.php 1 month ago TaxRateVersionStringInvalidator.php 6 months ago VersionStringGenerator.php 2 weeks ago
VersionStringGenerator.php
264 lines
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Automattic\WooCommerce\Internal\Caches;
6
7 use Automattic\WooCommerce\Proxies\LegacyProxy;
8
9 /**
10 * Version string generator/cache class.
11 *
12 * Provides a generic mechanism for generating and caching unique version strings
13 * for any identifiable item. Each item is identified by a string ID, and has
14 * an associated version string (UUID) that can be regenerated to invalidate caches.
15 * This is useful for cache invalidation strategies where items change over time.
16 * The standard WordPress cache is used to store the version strings.
17 */
18 class VersionStringGenerator {
19
20 /**
21 * Cache group name.
22 */
23 private const CACHE_GROUP = 'woocommerce_version_strings';
24
25 /**
26 * Can the version string cache be used?
27 *
28 * @var bool|null
29 */
30 private ?bool $can_use = null;
31
32 /**
33 * Legacy proxy instance.
34 *
35 * @var LegacyProxy
36 */
37 private LegacyProxy $legacy_proxy;
38
39 /**
40 * Initialize the class dependencies.
41 *
42 * @internal
43 *
44 * @param LegacyProxy $legacy_proxy Legacy proxy instance.
45 */
46 final public function init( LegacyProxy $legacy_proxy ) {
47 $this->legacy_proxy = $legacy_proxy;
48 }
49
50 /**
51 * Tells whether the version string cache can be used or not.
52 *
53 * This will return true only if an external object cache is configured in WordPress,
54 * since otherwise the cached entries will only persist for the current request.
55 *
56 * @return bool
57 */
58 public function can_use(): bool {
59 if ( ! is_null( $this->can_use ) ) {
60 return $this->can_use;
61 }
62
63 $this->can_use = $this->legacy_proxy->call_function( 'wp_using_ext_object_cache' ) ?? false;
64
65 return $this->can_use;
66 }
67
68 /**
69 * Get the current version string for an ID.
70 *
71 * If no valid version exists and $generate is true, a new version will be created.
72 * If no valid version exists and $generate is false, null will be returned.
73 *
74 * Cached values that aren't non-empty strings are treated as invalid and are never
75 * returned. When $generate is true they are overwritten by the newly generated
76 * version; when $generate is false they are deleted, which means a call with
77 * $generate set to false can write to the cache.
78 *
79 * @param string $id The ID to get the version string for.
80 * @param bool $generate Whether to generate a new version if one doesn't exist. Default true.
81 * @return string|null Version string, or null if not found and $generate is false.
82 * @throws \InvalidArgumentException If id is invalid.
83 *
84 * @since 10.4.0
85 */
86 public function get_version( string $id, bool $generate = true ): ?string {
87 $this->validate_input( $id );
88
89 $cache_key = $this->get_cache_key( $id );
90 $found = false;
91 $version = wp_cache_get( $cache_key, self::CACHE_GROUP, false, $found );
92
93 if ( ! is_string( $version ) || '' === $version ) {
94 $entry_exists = $this->cache_entry_exists( $version, $found );
95
96 if ( $entry_exists ) {
97 $this->log_invalid_cached_value( $id, $version, $generate );
98 }
99
100 if ( $generate ) {
101 // The new version is written to the same cache key, replacing the invalid
102 // value, so deleting it first would only add a redundant round-trip.
103 return $this->generate_version( $id );
104 }
105
106 if ( $entry_exists ) {
107 wp_cache_delete( $cache_key, self::CACHE_GROUP );
108 }
109
110 return null;
111 }
112
113 // Refresh the cache lifetime.
114 $this->store_version( $id, $version );
115 return $version;
116 }
117
118 /**
119 * Generate and store a new version string for an ID.
120 * The already existing version string, if any, will be replaced.
121 *
122 * @param string $id The ID to generate a version string for.
123 * @return string The new version string.
124 * @throws \InvalidArgumentException If id is invalid.
125 *
126 * @since 10.4.0
127 */
128 public function generate_version( string $id ): string {
129 $this->validate_input( $id );
130
131 $version = wp_generate_uuid4();
132 $this->store_version( $id, $version );
133 return $version;
134 }
135
136 /**
137 * Store the version string in cache with a filterable TTL.
138 *
139 * @param string $id The ID to store the version string for.
140 * @param string $version The version string to store.
141 * @return bool True on success, false on failure.
142 */
143 protected function store_version( string $id, string $version ): bool {
144 $cache_key = $this->get_cache_key( $id );
145
146 /**
147 * Filter the TTL for version string cache.
148 *
149 * @param int $ttl Time to live in seconds. Default 1 day.
150 * @param string $id The ID.
151 *
152 * @since 10.4.0
153 */
154 $ttl = apply_filters( 'woocommerce_version_string_generator_ttl', DAY_IN_SECONDS, $id );
155 $ttl = max( 0, (int) $ttl );
156
157 $result = wp_cache_set( $cache_key, $version, self::CACHE_GROUP, $ttl );
158
159 if ( is_bool( $result ) ) {
160 return $result;
161 }
162
163 // Some object cache implementations may return non-boolean values.
164 // Verify the store by reading the value back.
165 $found = false;
166 $stored_value = wp_cache_get( $cache_key, self::CACHE_GROUP, false, $found );
167 if ( $stored_value === $version ) {
168 return true;
169 }
170
171 // The stored value doesn't match; clean up and report failure.
172 if ( $this->cache_entry_exists( $stored_value, $found ) ) {
173 wp_cache_delete( $cache_key, self::CACHE_GROUP );
174 }
175 return false;
176 }
177
178 /**
179 * Tell whether a value read from the cache is evidence that an entry is stored.
180 *
181 * Object cache drop-ins replace wp_cache_get() wholesale, so neither the returned
182 * value nor the found flag is trustworthy on its own: some drop-ins never populate
183 * $found, and some signal a miss with null rather than false. The value is therefore
184 * the primary evidence, with $found as a tie-breaker that tells a stored false apart
185 * from a genuine miss. $found is checked loosely because a drop-in may report it as a
186 * truthy non-boolean.
187 *
188 * @param mixed $value The value returned by wp_cache_get().
189 * @param mixed $found The found flag as populated by wp_cache_get(), if it populates it at all.
190 * @return bool True if an entry appears to be stored, false if this looks like a miss.
191 */
192 private function cache_entry_exists( $value, $found ): bool {
193 return (bool) $found || ( false !== $value && null !== $value );
194 }
195
196 /**
197 * Log a value found in the version string cache that isn't a usable version string.
198 *
199 * This should never happen with a well-behaved object cache, so surface it for
200 * diagnosis rather than silently self-healing.
201 *
202 * @param string $id The ID the invalid value was cached for.
203 * @param mixed $value The invalid cached value.
204 * @param bool $regenerating Whether a replacement version is being generated.
205 * @return void
206 */
207 private function log_invalid_cached_value( string $id, $value, bool $regenerating ): void {
208 $this->legacy_proxy->call_function( 'wc_get_logger' )->warning(
209 sprintf(
210 'Discarded an invalid version string cache entry for ID "%1$s" (got %2$s); %3$s.',
211 $id,
212 gettype( $value ),
213 $regenerating ? 'the version will be regenerated' : 'the entry will be deleted'
214 ),
215 array( 'source' => 'version-string-generator' )
216 );
217 }
218
219 /**
220 * Delete the version string for an ID by deleting its cached entry.
221 *
222 * @param string $id The ID to delete the version string for.
223 * @return bool True on success, false on failure.
224 * @throws \InvalidArgumentException If id is invalid.
225 *
226 * @since 10.4.0
227 */
228 public function delete_version( string $id ): bool {
229 $this->validate_input( $id );
230
231 $cache_key = $this->get_cache_key( $id );
232 $result = wp_cache_delete( $cache_key, self::CACHE_GROUP );
233
234 // Some object cache implementations may return non-boolean values.
235 return ! is_bool( $result ) || $result;
236 }
237
238 /**
239 * Get the cache key for an ID.
240 *
241 * The ID is hashed to ensure a consistent key length and avoid issues
242 * with special characters or very long IDs.
243 *
244 * @param string $id The ID to get the cache key for.
245 * @return string The cache key.
246 */
247 private function get_cache_key( string $id ): string {
248 return 'wc_version_string_' . md5( $id );
249 }
250
251 /**
252 * Validate ID input.
253 *
254 * @param string $id The ID to validate.
255 * @return void
256 * @throws \InvalidArgumentException If id is invalid.
257 */
258 private function validate_input( string $id ): void {
259 if ( '' === $id ) {
260 throw new \InvalidArgumentException( 'ID cannot be empty.' );
261 }
262 }
263 }
264