PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.7.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.7.0
2.8.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 All 49 releases
thinkrank / includes / abilities / maintenance / class-purge-caches.php

class-purge-caches.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 2.7.0, at includes/abilities/maintenance/class-purge-caches.php

230 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Purge caches ability.
4 *
5 * @package ThinkRank\Abilities\Maintenance
6 */
7
8 declare(strict_types=1);
9
10 namespace ThinkRank\Abilities\Maintenance;
11
12 use ThinkRank\Abilities\Ability_Base;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit; // Exit if accessed directly.
16 }
17
18 /**
19 * Clears ThinkRank's own caches.
20 *
21 * ThinkRank caches Google and analytics responses, schema output and the site
22 * audit, so "it is stale, clear it" is a real question — and it had no
23 * supported answer at all: no ability, and no admin control either (#676).
24 *
25 * The recurrence is the argument for it rather than any single report. #629 was
26 * sitemap regeneration silently deferred to cron, and #235 was the Search
27 * Console property list cached for thirty minutes with no way to invalidate it;
28 * both would have been self-service with this.
29 *
30 * Scoped rather than a single blunt flush, so clearing a stale Search Console
31 * list does not also throw away an hour-old site audit that costs real work to
32 * rebuild.
33 */
34 class Purge_Caches extends Ability_Base {
35
36 /**
37 * Scope => human label, and the order they are reported in.
38 *
39 * @var array<string, string>
40 */
41 private const SCOPES = [
42 'analyzer' => 'Site SEO Analyzer audit',
43 'schema' => 'Schema output cache',
44 'ai' => 'AI response cache',
45 'integrations' => 'Google integration responses',
46 'content_type' => 'Content Type Matrix resolution',
47 'transients' => 'ThinkRank transients',
48 ];
49
50 /**
51 * Constructor.
52 */
53 public function __construct() {
54 $this->id = 'thinkrank/purge-caches';
55 $this->label = __( 'Purge ThinkRank Caches', 'thinkrank' );
56 $this->description = __( 'Clear ThinkRank\'s cached data when it is showing something stale — the site audit, schema output, AI responses, Google integration responses, or all of them. Pass scopes to clear only what you need; omit it to clear everything. Nothing is deleted except caches: settings and content are untouched, and each cache rebuilds on the next request. After clearing the analyzer scope, use run-seo-analyzer to rebuild the audit immediately rather than waiting for the next request.', 'thinkrank' );
57 }
58
59 /**
60 * {@inheritDoc}
61 *
62 * @return array<string, bool|float|string>
63 */
64 public function get_annotations() {
65 return [
66 'readonly' => false,
67 // Caches only. Nothing a user authored is lost, and everything
68 // cleared is rebuilt on demand — the cost is latency, not data.
69 'destructive' => false,
70 'idempotent' => true,
71 'priority' => 0.5,
72 'openWorldHint' => false,
73 ];
74 }
75
76 /**
77 * {@inheritDoc}
78 *
79 * @return array<string, mixed>
80 */
81 public function get_input_schema() {
82 return [
83 'type' => 'object',
84 'additionalProperties' => false,
85 'properties' => [
86 'scopes' => [
87 'type' => 'array',
88 'items' => [
89 'type' => 'string',
90 'enum' => array_keys( self::SCOPES ),
91 ],
92 'description' => __( 'Which caches to clear. Omit to clear all of them.', 'thinkrank' ),
93 ],
94 ],
95 ];
96 }
97
98 /**
99 * {@inheritDoc}
100 *
101 * @return array<string, mixed>
102 */
103 public function get_output_schema() {
104 return [
105 'type' => 'object',
106 'properties' => [
107 'success' => [ 'type' => 'boolean' ],
108 'cleared' => [
109 'type' => 'array',
110 'items' => [ 'type' => 'string' ],
111 'description' => __( 'Scopes that were cleared.', 'thinkrank' ),
112 ],
113 'skipped' => [
114 'type' => 'array',
115 'items' => [ 'type' => 'string' ],
116 'description' => __( 'Scopes whose subsystem is not present on this install, so there was nothing to clear.', 'thinkrank' ),
117 ],
118 ],
119 ];
120 }
121
122 /**
123 * Execute ability.
124 *
125 * @param array<string, mixed> $input Ability input payload.
126 * @return array<string, mixed>
127 */
128 public function execute( $input ) {
129 $input = (array) $input;
130 $requested = isset( $input['scopes'] ) && is_array( $input['scopes'] ) && ! empty( $input['scopes'] )
131 ? array_values( array_intersect( array_keys( self::SCOPES ), array_map( 'strval', $input['scopes'] ) ) )
132 : array_keys( self::SCOPES );
133
134 $cleared = [];
135 $skipped = [];
136
137 foreach ( $requested as $scope ) {
138 if ( $this->purge( $scope ) ) {
139 $cleared[] = $scope;
140 } else {
141 $skipped[] = $scope;
142 }
143 }
144
145 return [
146 'success' => true,
147 'cleared' => $cleared,
148 'skipped' => $skipped,
149 ];
150 }
151
152 /**
153 * Clear one scope.
154 *
155 * @param string $scope Scope key.
156 * @return bool True when the subsystem existed and was cleared.
157 */
158 private function purge( string $scope ): bool {
159 switch ( $scope ) {
160 case 'analyzer':
161 if ( ! class_exists( 'ThinkRank\\SEO\\SEO_Analyzer' ) ) {
162 return false;
163 }
164 ( new \ThinkRank\SEO\SEO_Analyzer() )->flush_cache();
165 return true;
166
167 case 'schema':
168 if ( ! class_exists( 'ThinkRank\\SEO\\Schema_Cache_Manager' ) ) {
169 return false;
170 }
171 ( new \ThinkRank\SEO\Schema_Cache_Manager() )->clear_all();
172 return true;
173
174 case 'ai':
175 if ( ! class_exists( 'ThinkRank\\AI\\Cache_Manager' ) ) {
176 return false;
177 }
178 ( new \ThinkRank\AI\Cache_Manager() )->clear_all();
179 return true;
180
181 case 'integrations':
182 if ( ! method_exists( 'ThinkRank\\API\\Integrations_Endpoint', 'purge_search_console_sites_cache' ) ) {
183 return false;
184 }
185 \ThinkRank\API\Integrations_Endpoint::purge_search_console_sites_cache();
186 return true;
187
188 case 'content_type':
189 if ( ! method_exists( 'ThinkRank\\SEO\\Content_Type_Settings', 'flush_cache' ) ) {
190 return false;
191 }
192 \ThinkRank\SEO\Content_Type_Settings::flush_cache();
193 return true;
194
195 case 'transients':
196 $this->purge_transients();
197 return true;
198 }
199
200 return false;
201 }
202
203 /**
204 * Delete ThinkRank's prefixed transients.
205 *
206 * Mirrors the sweep Deactivator runs, so the two cannot disagree about what
207 * counts as a ThinkRank transient.
208 *
209 * @return void
210 */
211 private function purge_transients(): void {
212 global $wpdb;
213
214 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- deleting the cache rows themselves; there is no core API for a prefix sweep.
215 $wpdb->query(
216 $wpdb->prepare(
217 "DELETE FROM {$wpdb->options}
218 WHERE option_name LIKE %s
219 OR option_name LIKE %s",
220 $wpdb->esc_like( '_transient_thinkrank_' ) . '%',
221 $wpdb->esc_like( '_transient_timeout_thinkrank_' ) . '%'
222 )
223 );
224
225 if ( function_exists( 'wp_cache_flush_group' ) ) {
226 wp_cache_flush_group( 'thinkrank' );
227 }
228 }
229 }
230