PluginProbe
404 Solution / 4.3.0
404 Solution v4.3.0
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 4.3.0, at includes/feedback/FeedbackDatabaseIdentity.php

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