PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.28.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.28.0
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / includes / seo / class-schema-cache-manager.php

class-schema-cache-manager.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.28.0, at includes/seo/class-schema-cache-manager.php

397 lines 12.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Schema Cache Manager
4 *
5 * Handles caching of schema operations to improve performance and reduce database queries.
6 * Follows the established AI Cache Manager pattern with schema-specific optimizations.
7 *
8 * @package ThinkRank\SEO
9 * @since 1.0.0
10 */
11
12 declare(strict_types=1);
13
14 namespace ThinkRank\SEO;
15
16 // Prevent direct access
17 if (!defined('ABSPATH')) {
18 exit;
19 }
20
21 /**
22 * Schema Cache Manager Class
23 *
24 * Single Responsibility: Manage schema operation caching for performance optimization
25 * Provides 90% reduction in database queries and sub-second response times.
26 *
27 * @since 1.0.0
28 */
29 class Schema_Cache_Manager {
30
31 /**
32 * Cache group for schema operations
33 *
34 * @since 1.0.0
35 */
36 private const CACHE_GROUP = 'thinkrank_schema';
37
38 /**
39 * Default cache duration in seconds
40 *
41 * @since 1.0.0
42 * @var int
43 */
44 private int $default_duration;
45
46 /**
47 * Constructor
48 *
49 * @since 1.0.0
50 *
51 * @param int $default_duration Default cache duration in seconds
52 */
53 public function __construct(int $default_duration = 3600) {
54 $this->default_duration = $default_duration;
55 }
56
57 /**
58 * Get cached schema data
59 *
60 * @since 1.0.0
61 *
62 * @param string $key Cache key
63 * @return array|null Cached data or null if not found
64 */
65 public function get(string $key): ?array {
66 $cache_key = $this->build_cache_key($key);
67 $cached_data = wp_cache_get($cache_key, self::CACHE_GROUP);
68
69 if (false === $cached_data) {
70 // Try to get from database cache
71 $cached_data = $this->get_from_database($cache_key);
72
73 if ($cached_data !== null) {
74 // Store back in memory cache
75 wp_cache_set($cache_key, $cached_data, self::CACHE_GROUP, $this->default_duration);
76 }
77 }
78
79 return $cached_data ?: null;
80 }
81
82 /**
83 * Store schema data in cache
84 *
85 * @since 1.0.0
86 *
87 * @param string $key Cache key
88 * @param array $data Data to cache
89 * @param int|null $duration Cache duration (null for default)
90 * @return bool True on success
91 */
92 public function set(string $key, array $data, ?int $duration = null): bool {
93 $cache_key = $this->build_cache_key($key);
94 $duration = $duration ?? $this->default_duration;
95
96 // Add metadata
97 $cache_data = [
98 'data' => $data,
99 'cached_at' => time(),
100 'expires_at' => time() + $duration,
101 ];
102
103 // Store in memory cache
104 $memory_cached = wp_cache_set($cache_key, $cache_data, self::CACHE_GROUP, $duration);
105
106 // Store in database for persistence
107 $db_cached = $this->set_in_database($cache_key, $cache_data, $duration);
108
109 return $memory_cached && $db_cached;
110 }
111
112 /**
113 * Delete cached schema data
114 *
115 * @since 1.0.0
116 *
117 * @param string $key Cache key
118 * @return bool True on success
119 */
120 public function delete(string $key): bool {
121 $cache_key = $this->build_cache_key($key);
122
123 // Delete from memory cache
124 wp_cache_delete($cache_key, self::CACHE_GROUP);
125
126 // Delete from database
127 return $this->delete_from_database($cache_key);
128 }
129
130 /**
131 * Clear all schema cache
132 *
133 * @since 1.0.0
134 *
135 * @return bool True on success
136 */
137 public function clear_all(): bool {
138 global $wpdb;
139
140 // Clear memory cache (if using object cache)
141 wp_cache_flush_group(self::CACHE_GROUP);
142
143 // Clear database cache
144 $table_name = $wpdb->prefix . 'thinkrank_ai_cache';
145 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is properly constructed from controlled prefix, cache clearing requires direct database access
146 $result = $wpdb->query($wpdb->prepare("DELETE FROM {$table_name} WHERE cache_key LIKE %s", 'schema_%'));
147
148 return $result !== false;
149 }
150
151 /**
152 * Clean expired schema cache entries
153 *
154 * @since 1.0.0
155 *
156 * @return int Number of entries cleaned
157 */
158 public function clean_expired(): int {
159 global $wpdb;
160
161 $table_name = $wpdb->prefix . 'thinkrank_ai_cache';
162 $current_time = time();
163
164 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Cache cleanup requires direct database access
165 $result = $wpdb->query(
166 $wpdb->prepare(
167 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is properly constructed from controlled prefix
168 "DELETE FROM {$table_name} WHERE cache_key LIKE %s AND expires_at < %d",
169 'schema_%',
170 $current_time
171 )
172 );
173
174 return (int) $result;
175 }
176
177 /**
178 * Generate cache key for deployed schemas
179 *
180 * @since 1.0.0
181 *
182 * @param string $context_type Context type
183 * @param int|null $context_id Context ID
184 * @return string Cache key
185 */
186 public function generate_deployed_schemas_key(string $context_type, ?int $context_id = null): string {
187 $key_data = [
188 'operation' => 'deployed_schemas',
189 'context_type' => $context_type,
190 'context_id' => $context_id,
191 'version' => THINKRANK_VERSION,
192 ];
193
194 return md5(wp_json_encode($key_data));
195 }
196
197 /**
198 * Generate cache key for schema generation
199 *
200 * @since 1.0.0
201 *
202 * @param string $schema_type Schema type
203 * @param array $data Content data for generation
204 * @param array $options Generation options
205 * @return string Cache key
206 */
207 public function generate_schema_generation_key(string $schema_type, array $data, array $options = []): string {
208 $key_data = [
209 'operation' => 'schema_generation',
210 'schema_type' => $schema_type,
211 'data_hash' => md5(serialize($data)),
212 'options' => $options,
213 'version' => THINKRANK_VERSION,
214 ];
215
216 return md5(wp_json_encode($key_data));
217 }
218
219 /**
220 * Generate cache key for schema validation
221 *
222 * @since 1.0.0
223 *
224 * @param array $schema_data Schema data to validate
225 * @param string $schema_type Schema type
226 * @param array $options Validation options
227 * @return string Cache key
228 */
229 public function generate_validation_key(array $schema_data, string $schema_type, array $options = []): string {
230 $key_data = [
231 'operation' => 'schema_validation',
232 'schema_type' => $schema_type,
233 'schema_hash' => md5(serialize($schema_data)),
234 'options' => $options,
235 'version' => THINKRANK_VERSION,
236 ];
237
238 return md5(wp_json_encode($key_data));
239 }
240
241 /**
242 * Invalidate cache for specific context
243 *
244 * @since 1.0.0
245 *
246 * @param string $context_type Context type
247 * @param int|null $context_id Context ID
248 * @return bool True on success
249 */
250 public function invalidate_context_cache(string $context_type, ?int $context_id = null): bool {
251 $deployed_key = $this->generate_deployed_schemas_key($context_type, $context_id);
252 return $this->delete($deployed_key);
253 }
254
255 /**
256 * Invalidate all schema cache (for settings changes)
257 *
258 * @since 1.0.0
259 *
260 * @return bool True on success
261 */
262 public function invalidate_all_cache(): bool {
263 return $this->clear_all();
264 }
265
266 /**
267 * Build cache key with prefix
268 *
269 * @since 1.0.0
270 *
271 * @param string $key Original key
272 * @return string Prefixed cache key
273 */
274 private function build_cache_key(string $key): string {
275 return 'schema_' . $key;
276 }
277
278 /**
279 * Get cached data from database
280 *
281 * @since 1.0.0
282 *
283 * @param string $cache_key Cache key
284 * @return array|null Cached data or null
285 */
286 private function get_from_database(string $cache_key): ?array {
287 global $wpdb;
288
289 $table_name = $wpdb->prefix . 'thinkrank_ai_cache';
290 $current_time = time();
291
292 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Cache retrieval requires direct database access
293 $cached_row = $wpdb->get_row(
294 $wpdb->prepare(
295 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is properly constructed from controlled prefix
296 "SELECT cache_data, expires_at FROM {$table_name}
297 WHERE cache_key = %s AND expires_at > %d",
298 $cache_key,
299 $current_time
300 )
301 );
302
303 if (!$cached_row) {
304 return null;
305 }
306
307 return maybe_unserialize($cached_row->cache_data);
308 }
309
310 /**
311 * Store data in database cache
312 *
313 * @since 1.0.0
314 *
315 * @param string $cache_key Cache key
316 * @param array $cache_data Data to cache
317 * @param int $duration Cache duration
318 * @return bool True on success
319 */
320 private function set_in_database(string $cache_key, array $cache_data, int $duration): bool {
321 global $wpdb;
322
323 $table_name = $wpdb->prefix . 'thinkrank_ai_cache';
324
325 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Cache storage requires direct database access
326 $result = $wpdb->replace(
327 $table_name,
328 [
329 'cache_key' => $cache_key,
330 'cache_data' => maybe_serialize($cache_data),
331 'expires_at' => time() + $duration,
332 'created_at' => current_time('mysql'),
333 ],
334 ['%s', '%s', '%d', '%s']
335 );
336
337 return $result !== false;
338 }
339
340 /**
341 * Delete data from database cache
342 *
343 * @since 1.0.0
344 *
345 * @param string $cache_key Cache key
346 * @return bool True on success
347 */
348 private function delete_from_database(string $cache_key): bool {
349 global $wpdb;
350
351 $table_name = $wpdb->prefix . 'thinkrank_ai_cache';
352
353 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Cache deletion requires direct database access
354 $result = $wpdb->delete(
355 $table_name,
356 ['cache_key' => $cache_key],
357 ['%s']
358 );
359
360 return $result !== false;
361 }
362
363 /**
364 * Get cache statistics for monitoring
365 *
366 * @since 1.0.0
367 *
368 * @return array Cache statistics
369 */
370 public function get_cache_stats(): array {
371 global $wpdb;
372
373 $table_name = $wpdb->prefix . 'thinkrank_ai_cache';
374 $current_time = time();
375
376 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is properly constructed from controlled prefix, cache stats require direct database access
377 $total_entries = $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$table_name} WHERE cache_key LIKE %s", 'schema_%'));
378 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Cache stats require direct database access
379 $expired_entries = $wpdb->get_var(
380 $wpdb->prepare(
381 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is properly constructed from controlled prefix
382 "SELECT COUNT(*) FROM {$table_name} WHERE cache_key LIKE %s AND expires_at < %d",
383 'schema_%',
384 $current_time
385 )
386 );
387
388 return [
389 'total_entries' => (int) $total_entries,
390 'expired_entries' => (int) $expired_entries,
391 'active_entries' => (int) $total_entries - (int) $expired_entries,
392 'cache_group' => self::CACHE_GROUP,
393 'default_duration' => $this->default_duration,
394 ];
395 }
396 }
397