PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / trunk
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO vtrunk
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 1.11.0 All 47 releases
thinkrank / includes / diagnostics / class-connection-status.php

class-connection-status.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO trunk, at includes/diagnostics/class-connection-status.php

215 lines 6.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Connection status reporter.
4 *
5 * @package ThinkRank\Diagnostics
6 */
7
8 declare(strict_types=1);
9
10 namespace ThinkRank\Diagnostics;
11
12 use ThinkRank\Core\Capability_Manager;
13 use ThinkRank\Core\Plan_Config;
14 use ThinkRank\Database\Database_Schema;
15 use ThinkRank\Mcp\Mcp_Manager;
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit; // Exit if accessed directly.
19 }
20
21 /**
22 * Builds a read-only integration health report for ThinkRank.
23 *
24 * A single source of truth consumed by the `thinkrank/get-connection-status`
25 * ability and the `GET /thinkrank/v1/connection-status` REST route. It reports
26 * whatever an external MCP/Abilities client needs to distinguish a certificate
27 * problem from an authentication problem from a permission problem from an
28 * ability-registration problem — without reading plugin source or DB tables by
29 * hand (see #188).
30 *
31 * The report is side-effect free: it makes no outbound HTTP request and never
32 * returns secret material (API keys, OAuth tokens, connection tokens). Actively
33 * exercising the round trip (a live "Test Connection") belongs to the
34 * integration screen tracked in #189.
35 */
36 class Connection_Status {
37
38 /**
39 * Build the full connection-status report.
40 *
41 * @return array<string, mixed>
42 */
43 public static function report(): array {
44 return [
45 'plugin' => self::plugin_info(),
46 'abilities' => self::abilities_info(),
47 'mcp_server' => self::mcp_info(),
48 'user' => self::user_info(),
49 'urls' => self::analyze_schemes( home_url(), site_url(), rest_url() ),
50 'database' => self::database_info(),
51 'scoring' => self::scoring_info(),
52 ];
53 }
54
55 /**
56 * ThinkRank + ThinkRank Pro versions and whether Pro is active.
57 *
58 * @return array<string, mixed>
59 */
60 private static function plugin_info(): array {
61 return [
62 'thinkrank_version' => defined( 'THINKRANK_VERSION' ) ? THINKRANK_VERSION : null,
63 'pro_active' => Plan_Config::is_pro(),
64 'pro_version' => defined( 'THINKRANK_PRO_VERSION' ) ? THINKRANK_PRO_VERSION : null,
65 ];
66 }
67
68 /**
69 * Abilities API availability + which ThinkRank abilities are registered.
70 *
71 * @return array<string, mixed>
72 */
73 private static function abilities_info(): array {
74 $api_available = function_exists( 'wp_register_ability' ) && function_exists( 'wp_get_abilities' );
75 $names = [];
76
77 if ( $api_available ) {
78 foreach ( wp_get_abilities() as $ability ) {
79 if ( ! is_object( $ability ) || ! method_exists( $ability, 'get_name' ) ) {
80 continue;
81 }
82 $name = (string) $ability->get_name();
83 if ( 0 === strpos( $name, 'thinkrank/' ) || 0 === strpos( $name, 'thinkrank-pro/' ) ) {
84 $names[] = $name;
85 }
86 }
87 sort( $names );
88 }
89
90 return [
91 'api_available' => $api_available,
92 'registered' => count( $names ) > 0,
93 'thinkrank_count' => count( $names ),
94 'names' => $names,
95 ];
96 }
97
98 /**
99 * MCP server toggle + the endpoints a client would connect to.
100 *
101 * @return array<string, mixed>
102 */
103 private static function mcp_info(): array {
104 return [
105 'enabled' => class_exists( Mcp_Manager::class ) ? Mcp_Manager::is_enabled() : false,
106 'endpoint' => home_url( '/thinkrank/mcp' ),
107 'rest_fallback' => rest_url( 'thinkrank/v1/mcp' ),
108 ];
109 }
110
111 /**
112 * Current user + the ThinkRank capabilities they hold.
113 *
114 * @return array<string, mixed>
115 */
116 private static function user_info(): array {
117 $user = wp_get_current_user();
118
119 return [
120 'id' => get_current_user_id(),
121 'login' => ( $user && $user->exists() ) ? $user->user_login : '',
122 'is_admin' => current_user_can( 'manage_options' ),
123 'thinkrank_capabilities' => class_exists( Capability_Manager::class )
124 ? Capability_Manager::user_capabilities()
125 : [],
126 ];
127 }
128
129 /**
130 * ThinkRank custom-table status: version, whether an update is pending,
131 * and per-table existence.
132 *
133 * @return array<string, mixed>
134 */
135 private static function database_info(): array {
136 if ( ! class_exists( Database_Schema::class ) ) {
137 return [
138 'installed_version' => null,
139 'expected_version' => null,
140 'up_to_date' => false,
141 'tables' => [],
142 ];
143 }
144
145 $schema = new Database_Schema();
146 $status = $schema->get_database_status();
147
148 $tables = [];
149 foreach ( (array) ( $status['tables'] ?? [] ) as $name => $info ) {
150 $tables[ $name ] = ! empty( $info['exists'] );
151 }
152
153 return [
154 'installed_version' => $status['version'] ?? null,
155 'expected_version' => $schema->get_schema_version(),
156 'up_to_date' => empty( $status['needs_update'] ),
157 'tables' => $tables,
158 ];
159 }
160
161 /**
162 * Whether scoring (and saved-score storage) is available: the scoring
163 * ability is registered and the analysis table exists.
164 *
165 * @return array<string, bool>
166 */
167 private static function scoring_info(): array {
168 $ability_available = function_exists( 'wp_has_ability' )
169 ? (bool) wp_has_ability( 'thinkrank/get-seo-score' )
170 : false;
171
172 $table_exists = false;
173 if ( class_exists( Database_Schema::class ) ) {
174 $status = ( new Database_Schema() )->get_database_status();
175 $table_exists = ! empty( $status['tables']['seo_analysis']['exists'] );
176 }
177
178 return [
179 'ability_available' => $ability_available,
180 'analysis_table_exists' => $table_exists,
181 ];
182 }
183
184 /**
185 * Compare the URL schemes WordPress uses. A `home`/`siteurl` scheme
186 * mismatch (or a REST URL whose scheme differs from `home`) is the classic
187 * cause of an MCP client's requests bouncing between HTTP and HTTPS.
188 *
189 * Pure function of its inputs (native `parse_url` only) so it is unit
190 * testable without a WordPress runtime.
191 *
192 * @param string $home_url The `home` URL.
193 * @param string $site_url The `siteurl` URL.
194 * @param string $rest_url The REST API base URL.
195 * @return array<string, mixed>
196 */
197 public static function analyze_schemes( string $home_url, string $site_url, string $rest_url ): array {
198 $home = strtolower( (string) wp_parse_url( $home_url, PHP_URL_SCHEME ) );
199 $site = strtolower( (string) wp_parse_url( $site_url, PHP_URL_SCHEME ) );
200 $rest = strtolower( (string) wp_parse_url( $rest_url, PHP_URL_SCHEME ) );
201
202 return [
203 'home' => $home_url,
204 'siteurl' => $site_url,
205 'rest_url' => $rest_url,
206 'home_scheme' => $home,
207 'siteurl_scheme' => $site,
208 'rest_scheme' => $rest,
209 'is_https' => 'https' === $home,
210 'home_siteurl_match' => '' !== $home && $home === $site,
211 'rest_matches_home' => '' !== $rest && $rest === $home,
212 ];
213 }
214 }
215