PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.0
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.0
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / inc / sitemaps / class-sitemaps-cache-validator.php

class-sitemaps-cache-validator.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 18.0, at inc/sitemaps/class-sitemaps-cache-validator.php

320 lines 9.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\XML_Sitemaps
6 */
7
8 /**
9 * Handles storage keys for sitemaps caching and invalidation.
10 *
11 * @since 3.2
12 */
13 class WPSEO_Sitemaps_Cache_Validator {
14
15 /**
16 * Prefix of the transient key for sitemap caches.
17 *
18 * @var string
19 */
20 const STORAGE_KEY_PREFIX = 'yst_sm_';
21
22 /**
23 * Name of the option that holds the global validation value.
24 *
25 * @var string
26 */
27 const VALIDATION_GLOBAL_KEY = 'wpseo_sitemap_cache_validator_global';
28
29 /**
30 * The format which creates the key of the option that holds the type validation value.
31 *
32 * @var string
33 */
34 const VALIDATION_TYPE_KEY_FORMAT = 'wpseo_sitemap_%s_cache_validator';
35
36 /**
37 * Get the cache key for a certain type and page.
38 *
39 * A type of cache would be something like 'page', 'post' or 'video'.
40 *
41 * Example key format for sitemap type "post", page 1: wpseo_sitemap_post_1:akfw3e_23azBa .
42 *
43 * @since 3.2
44 *
45 * @param string|null $type The type to get the key for. Null or self::SITEMAP_INDEX_TYPE for index cache.
46 * @param int $page The page of cache to get the key for.
47 *
48 * @return bool|string The key where the cache is stored on. False if the key could not be generated.
49 */
50 public static function get_storage_key( $type = null, $page = 1 ) {
51
52 // Using SITEMAP_INDEX_TYPE for sitemap index cache.
53 $type = is_null( $type ) ? WPSEO_Sitemaps::SITEMAP_INDEX_TYPE : $type;
54
55 $global_cache_validator = self::get_validator();
56 $type_cache_validator = self::get_validator( $type );
57
58 $prefix = self::STORAGE_KEY_PREFIX;
59 $postfix = sprintf( '_%d:%s_%s', $page, $global_cache_validator, $type_cache_validator );
60
61 try {
62 $type = self::truncate_type( $type, $prefix, $postfix );
63 } catch ( OutOfBoundsException $exception ) {
64 // Maybe do something with the exception, for now just mark as invalid.
65 return false;
66 }
67
68 // Build key.
69 $full_key = $prefix . $type . $postfix;
70
71 return $full_key;
72 }
73
74 /**
75 * If the type is over length make sure we compact it so we don't have any database problems.
76 *
77 * When there are more 'extremely long' post types, changes are they have variations in either the start or ending.
78 * Because of this, we cut out the excess in the middle which should result in less chance of collision.
79 *
80 * @since 3.2
81 *
82 * @param string $type The type of sitemap to be used.
83 * @param string $prefix The part before the type in the cache key. Only the length is used.
84 * @param string $postfix The part after the type in the cache key. Only the length is used.
85 *
86 * @return string The type with a safe length to use
87 *
88 * @throws OutOfRangeException When there is less than 15 characters of space for a key that is originally longer.
89 */
90 public static function truncate_type( $type, $prefix = '', $postfix = '' ) {
91 /*
92 * This length has been restricted by the database column length of 64 in the past.
93 * The prefix added by WordPress is '_transient_' because we are saving to a transient.
94 * We need to use a timeout on the transient, otherwise the values get autoloaded, this adds
95 * another restriction to the length.
96 */
97 $max_length = 45; // 64 - 19 ('_transient_timeout_')
98 $max_length -= strlen( $prefix );
99 $max_length -= strlen( $postfix );
100
101 if ( strlen( $type ) > $max_length ) {
102
103 if ( $max_length < 15 ) {
104 /*
105 * If this happens the most likely cause is a page number that is too high.
106 *
107 * So this would not happen unintentionally.
108 * Either by trying to cause a high server load, finding backdoors or misconfiguration.
109 */
110 throw new OutOfRangeException(
111 __(
112 'Trying to build the sitemap cache key, but the postfix and prefix combination leaves too little room to do this. You are probably requesting a page that is way out of the expected range.',
113 'wordpress-seo'
114 )
115 );
116 }
117
118 $half = ( $max_length / 2 );
119
120 $first_part = substr( $type, 0, ( ceil( $half ) - 1 ) );
121 $last_part = substr( $type, ( 1 - floor( $half ) ) );
122
123 $type = $first_part . '..' . $last_part;
124 }
125
126 return $type;
127 }
128
129 /**
130 * Invalidate sitemap cache.
131 *
132 * @since 3.2
133 *
134 * @param string|null $type The type to get the key for. Null for all caches.
135 *
136 * @return void
137 */
138 public static function invalidate_storage( $type = null ) {
139
140 // Global validator gets cleared when no type is provided.
141 $old_validator = null;
142
143 // Get the current type validator.
144 if ( ! is_null( $type ) ) {
145 $old_validator = self::get_validator( $type );
146 }
147
148 // Refresh validator.
149 self::create_validator( $type );
150
151 if ( ! wp_using_ext_object_cache() ) {
152 // Clean up current cache from the database.
153 self::cleanup_database( $type, $old_validator );
154 }
155
156 // External object cache pushes old and unretrieved items out by itself so we don't have to do anything for that.
157 }
158
159 /**
160 * Cleanup invalidated database cache.
161 *
162 * @since 3.2
163 *
164 * @param string|null $type The type of sitemap to clear cache for.
165 * @param string|null $validator The validator to clear cache of.
166 *
167 * @return void
168 */
169 public static function cleanup_database( $type = null, $validator = null ) {
170
171 global $wpdb;
172
173 if ( is_null( $type ) ) {
174 // Clear all cache if no type is provided.
175 $like = sprintf( '%s%%', self::STORAGE_KEY_PREFIX );
176 }
177 else {
178 // Clear type cache for all type keys.
179 $like = sprintf( '%1$s%2$s_%%', self::STORAGE_KEY_PREFIX, $type );
180 }
181
182 /*
183 * Add slashes to the LIKE "_" single character wildcard.
184 *
185 * We can't use `esc_like` here because we need the % in the query.
186 */
187 $where = [];
188 $where[] = sprintf( "option_name LIKE '%s'", addcslashes( '_transient_' . $like, '_' ) );
189 $where[] = sprintf( "option_name LIKE '%s'", addcslashes( '_transient_timeout_' . $like, '_' ) );
190
191 // Delete transients.
192 $query = sprintf( 'DELETE FROM %1$s WHERE %2$s', $wpdb->options, implode( ' OR ', $where ) );
193 $wpdb->query( $query );
194
195 wp_cache_delete( 'alloptions', 'options' );
196 }
197
198 /**
199 * Get the current cache validator.
200 *
201 * Without the type the global validator is returned.
202 * This can invalidate -all- keys in cache at once.
203 *
204 * With the type parameter the validator for that specific type can be invalidated.
205 *
206 * @since 3.2
207 *
208 * @param string $type Provide a type for a specific type validator, empty for global validator.
209 *
210 * @return string|null The validator for the supplied type.
211 */
212 public static function get_validator( $type = '' ) {
213
214 $key = self::get_validator_key( $type );
215
216 $current = get_option( $key, null );
217 if ( ! is_null( $current ) ) {
218 return $current;
219 }
220
221 if ( self::create_validator( $type ) ) {
222 return self::get_validator( $type );
223 }
224
225 return null;
226 }
227
228 /**
229 * Get the cache validator option key for the specified type.
230 *
231 * @since 3.2
232 *
233 * @param string $type Provide a type for a specific type validator, empty for global validator.
234 *
235 * @return string Validator to be used to generate the cache key.
236 */
237 public static function get_validator_key( $type = '' ) {
238
239 if ( empty( $type ) ) {
240 return self::VALIDATION_GLOBAL_KEY;
241 }
242
243 return sprintf( self::VALIDATION_TYPE_KEY_FORMAT, $type );
244 }
245
246 /**
247 * Refresh the cache validator value.
248 *
249 * @since 3.2
250 *
251 * @param string $type Provide a type for a specific type validator, empty for global validator.
252 *
253 * @return bool True if validator key has been saved as option.
254 */
255 public static function create_validator( $type = '' ) {
256
257 $key = self::get_validator_key( $type );
258
259 // Generate new validator.
260 $microtime = microtime();
261
262 // Remove space.
263 list( $milliseconds, $seconds ) = explode( ' ', $microtime );
264
265 // Transients are purged every 24h.
266 $seconds = ( $seconds % DAY_IN_SECONDS );
267 $milliseconds = intval( substr( $milliseconds, 2, 3 ), 10 );
268
269 // Combine seconds and milliseconds and convert to integer.
270 $validator = intval( $seconds . '' . $milliseconds, 10 );
271
272 // Apply base 61 encoding.
273 $compressed = self::convert_base10_to_base61( $validator );
274
275 return update_option( $key, $compressed, false );
276 }
277
278 /**
279 * Encode to base61 format.
280 *
281 * This is base64 (numeric + alpha + alpha upper case) without the 0.
282 *
283 * @since 3.2
284 *
285 * @param int $base10 The number that has to be converted to base 61.
286 *
287 * @return string Base 61 converted string.
288 *
289 * @throws InvalidArgumentException When the input is not an integer.
290 */
291 public static function convert_base10_to_base61( $base10 ) {
292
293 if ( ! is_int( $base10 ) ) {
294 throw new InvalidArgumentException( __( 'Expected an integer as input.', 'wordpress-seo' ) );
295 }
296
297 // Characters that will be used in the conversion.
298 $characters = '123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
299 $length = strlen( $characters );
300
301 $remainder = $base10;
302 $output = '';
303
304 do {
305 // Building from right to left in the result.
306 $index = ( $remainder % $length );
307
308 // Prepend the character to the output.
309 $output = $characters[ $index ] . $output;
310
311 // Determine the remainder after removing the applied number.
312 $remainder = floor( $remainder / $length );
313
314 // Keep doing it until we have no remainder left.
315 } while ( $remainder );
316
317 return $output;
318 }
319 }
320