PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / feedback / FeedbackDatabaseIdentity.php

FeedbackDatabaseIdentity.php in 404 Solution trunk, at includes/feedback/FeedbackDatabaseIdentity.php

167 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 require_once __DIR__ . '/FeedbackTransportLog.php';
8
9 /**
10 * Detects the database family/version used in outbound feedback payloads.
11 *
12 * FeedbackTransport only needs the normalized wire value, but the detection
13 * requires several engine-specific probes. Keeping those probes here prevents
14 * the transport from growing a second database-detection responsibility.
15 */
16 class ABJ_404_Solution_FeedbackDatabaseIdentity {
17
18 /**
19 * Detect the active database family and printable version without letting
20 * one engine-specific probe abort report creation. MySQL-compatible wpdb
21 * installs usually answer SELECT VERSION(); SQLite-backed drop-ins may not,
22 * so PDO driver/schema signals are checked separately and failures degrade
23 * to the base db_version() value.
24 *
25 * @param mixed $wpdb
26 * @return array{type: string, version: string}
27 */
28 public static function detect($wpdb): array {
29 $dbVersion = '';
30 if (is_object($wpdb) && method_exists($wpdb, 'db_version')) {
31 try {
32 $raw = $wpdb->db_version();
33 $dbVersion = is_scalar($raw) ? (string)$raw : '';
34 } catch (\Throwable $e) {
35 ABJ_404_Solution_FeedbackTransportLog::log('warn', 'FeedbackTransport db_version probe failed: ' . $e->getMessage());
36 }
37 }
38
39 // db_version() typically returns the numeric portion only; for
40 // mariadb detection we also probe the full VERSION() string.
41 $fullVersion = $dbVersion;
42 if (is_object($wpdb) && method_exists($wpdb, 'get_var')) {
43 try {
44 // DAO-bypass-approved: SELECT VERSION() is a parameterless server-introspection probe with no plugin tables involved; routing through queryAndGetResults() would force a missing-table-repair detour for a query that cannot fail with that error class
45 $probed = $wpdb->get_var('SELECT VERSION()');
46 if (is_scalar($probed) && trim((string)$probed) !== '') {
47 $fullVersion = (string)$probed;
48 }
49 } catch (\Throwable $e) {
50 ABJ_404_Solution_FeedbackTransportLog::log('warn', 'FeedbackTransport SELECT VERSION() probe failed: ' . $e->getMessage());
51 }
52 }
53
54 $pdoDriver = self::wpdbPdoDriverName($wpdb);
55 $schemaSignal = $pdoDriver === '' ? self::databaseSchemaSignal() : '';
56
57 return array(
58 'type' => self::classifyDbType($pdoDriver, $fullVersion, $schemaSignal, $wpdb),
59 'version' => $fullVersion,
60 );
61 }
62
63 /**
64 * @param mixed $wpdb
65 * @return string Lowercase PDO driver name, or empty string when unavailable.
66 */
67 private static function wpdbPdoDriverName($wpdb): string {
68 if (!is_object($wpdb) || !isset($wpdb->dbh) || !is_object($wpdb->dbh) ||
69 !method_exists($wpdb->dbh, 'getAttribute') || !class_exists('PDO', false)) {
70 return '';
71 }
72
73 try {
74 $driver = $wpdb->dbh->getAttribute(\PDO::ATTR_DRIVER_NAME);
75 return is_scalar($driver) ? strtolower(trim((string)$driver)) : '';
76 } catch (\Throwable $e) {
77 ABJ_404_Solution_FeedbackTransportLog::log('warn', 'FeedbackTransport PDO driver probe failed: ' . $e->getMessage());
78 return '';
79 }
80 }
81
82 /**
83 * @return string Best-effort CREATE TABLE schema text. Empty when the
84 * WordPress schema helper is unavailable or fails.
85 */
86 private static function databaseSchemaSignal(): string {
87 if (!function_exists('wp_get_db_schema')) {
88 return '';
89 }
90
91 try {
92 $schema = wp_get_db_schema();
93 return is_string($schema) ? $schema : '';
94 } catch (\Throwable $e) {
95 ABJ_404_Solution_FeedbackTransportLog::log('warn', 'FeedbackTransport wp_get_db_schema probe failed: ' . $e->getMessage());
96 return '';
97 }
98 }
99
100 /**
101 * @param mixed $wpdb
102 */
103 private static function classifyDbType(string $pdoDriver, string $fullVersion, string $schemaSignal, $wpdb): string {
104 $driver = strtolower(trim($pdoDriver));
105 $version = strtolower(trim($fullVersion));
106 $serverInfo = strtolower(self::wpdbServerInfo($wpdb));
107 $schema = strtolower($schemaSignal);
108
109 if ($driver === 'sqlite' || strpos($version, 'sqlite') !== false || self::schemaLooksSqlite($schema)) {
110 return 'sqlite';
111 }
112 if (strpos($version, 'mariadb') !== false || strpos($serverInfo, 'mariadb') !== false) {
113 return 'mariadb';
114 }
115 if ($driver === 'mysql' || $driver === 'mysqli') {
116 return 'mysql';
117 }
118 if ($driver !== '') {
119 return 'other';
120 }
121 if (is_object($wpdb) && isset($wpdb->is_mysql)) {
122 return $wpdb->is_mysql === true ? 'mysql' : 'other';
123 }
124 if (strpos($version, 'mysql') !== false) {
125 return 'mysql';
126 }
127 if (preg_match('/^\d+(?:\.\d+){1,3}(?:[-+].*)?$/', $version) === 1) {
128 return 'mysql';
129 }
130
131 return 'other';
132 }
133
134 private static function schemaLooksSqlite(string $schema): bool {
135 if ($schema === '') {
136 return false;
137 }
138 if (strpos($schema, 'sqlite') !== false) {
139 return true;
140 }
141 return preg_match('/integer\s+primary\s+key\s+autoincrement/i', $schema) === 1;
142 }
143
144 /**
145 * @param mixed $wpdb
146 */
147 private static function wpdbServerInfo($wpdb): string {
148 if (!is_object($wpdb)) {
149 return '';
150 }
151 if (isset($wpdb->db_server_info) && is_scalar($wpdb->db_server_info)) {
152 return (string)$wpdb->db_server_info;
153 }
154 if (!method_exists($wpdb, 'db_server_info')) {
155 return '';
156 }
157
158 try {
159 $info = $wpdb->db_server_info();
160 return is_scalar($info) ? (string)$info : '';
161 } catch (\Throwable $e) {
162 ABJ_404_Solution_FeedbackTransportLog::log('warn', 'FeedbackTransport db_server_info probe failed: ' . $e->getMessage());
163 return '';
164 }
165 }
166 }
167