PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / trunk
Search Atlas SEO – OTTO AI SEO Automation for WordPress vtrunk
2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 2.6.4 2.6.3 All 139 releases
← All changes | telemetry/sentry-wordpress-integration.php +158 -37 2.6.10 → trunk View file →
@@ -3,8 +3,10 @@
3 3 if (!defined('ABSPATH')) {
4 4 exit;
5 5 }
6 6
7 +require_once __DIR__ . '/privacy.php';
8 +
7 9 /**
8 10 * WordPress-Native Sentry Integration
9 11 *
10 12 * Uses WordPress HTTP API to send data directly to Sentry
@@ -41,14 +43,14 @@
41 43 $this->host = null; // Proxy handles this
42 44 $this->scheme = 'proxy';
43 45 } else {
44 46 // Legacy DSN format for backward compatibility
45 - $parsed = parse_url($this->dsn);
46 - $this->public_key = isset($parsed['user']) ? $parsed['user'] : null;
47 - $this->secret_key = isset($parsed['pass']) ? $parsed['pass'] : null;
48 - $this->project_id = trim($parsed['path'], '/');
49 - $this->host = isset($parsed['host']) ? $parsed['host'] : null;
50 - $this->scheme = isset($parsed['scheme']) ? $parsed['scheme'] : 'https';
47 + $parsed = wp_parse_url($this->dsn);
48 + $this->public_key = is_array($parsed) && isset($parsed['user']) ? $parsed['user'] : null;
49 + $this->secret_key = is_array($parsed) && isset($parsed['pass']) ? $parsed['pass'] : null;
50 + $this->project_id = is_array($parsed) && isset($parsed['path']) ? trim($parsed['path'], '/') : null;
51 + $this->host = is_array($parsed) && isset($parsed['host']) ? $parsed['host'] : null;
52 + $this->scheme = is_array($parsed) && isset($parsed['scheme']) ? $parsed['scheme'] : 'https';
51 53 }
52 54 }
53 55
54 56 /**
@@ -67,8 +69,97 @@
67 69 $data = $this->formatMessage($message, $level, $extra);
68 70 $result = $this->sendToSentry($data, 'event', $attachment);
69 71 return is_array($result) ? $result['success'] : $result;
70 72 }
73 +
74 + /**
75 + * Capture a message without waiting for Sentry to answer.
76 + *
77 + * captureMessage() blocks: sendToSentry() uses cURL with a 5s connect and 5s
78 + * read timeout, so a slow or unreachable collector can add up to ten seconds
79 + * to whatever request called it. That is acceptable for admin-side and cron
80 + * work, but not on a visitor's page render — least of all when the reason we
81 + * are reporting is that the request is already degraded.
82 + *
83 + * This variant hands the envelope to WordPress's HTTP API with
84 + * 'blocking' => false, so the request is dispatched and the caller continues
85 + * immediately. The trade-off is that delivery is unconfirmed and never
86 + * retried: a dropped report is silently lost. For counting how often
87 + * something happens that is a fair exchange for not touching page-load time.
88 + *
89 + * The token is read from cache only. metasync_get_jwt_token() falls through to
90 + * a fresh fetch on a cache miss — a blocking POST with a 15 second timeout —
91 + * which would defeat the whole point of this method and land that cost on an
92 + * already-degraded request. With no cached token the report is skipped; the
93 + * next admin or cron request repopulates the cache.
94 + *
95 + * @param string $message Message to record.
96 + * @param string $level Sentry level (info|warning|error|fatal).
97 + * @param array $extra Additional context.
98 + * @return bool True if a request was dispatched, false if it could not be.
99 + */
100 + public function captureMessageNonBlocking($message, $level = 'info', $extra = []) {
101 + if (metasync_telemetry_is_disabled()) {
102 + return false;
103 + }
104 +
105 + # Mirrors sendToSentry()'s preconditions.
106 + if ($this->isLocalhost()) {
107 + return false;
108 + }
109 +
110 + try {
111 + # method_exists() is redundant to static analysis — this MR adds the
112 + # method, so PHPStan proves the call always true. Kept for the upgrade
113 + # window: during a plugin update an opcache can still hold the previous
114 + # Metasync_Connect_Manager, where class_exists() passes but the
115 + # cache-only accessor is absent. Falling through to the else branch is
116 + # the safe outcome there; an unguarded call would fatal.
117 + # @phpstan-ignore-next-line function.alreadyNarrowedType
118 + if (class_exists('Metasync_Connect_Manager') && method_exists('Metasync_Connect_Manager', 'get_cached_jwt_token')) {
119 + $jwt_token = Metasync_Connect_Manager::get_cached_jwt_token();
120 + } else {
121 + # No cache-only accessor available — skip rather than risk the
122 + # blocking fetch path.
123 + return false;
124 + }
125 + } catch (Exception $e) {
126 + return false;
127 + } catch (Error $e) {
128 + return false;
129 + }
130 +
131 + if (empty($jwt_token)) {
132 + return false;
133 + }
134 +
135 + $data = $this->formatMessage($message, $level, $extra);
136 + $envelope = $this->createSentryEnvelope($data, 'event', null);
137 +
138 + if (empty($envelope)) {
139 + return false;
140 + }
141 +
142 + $plugin_version = defined('METASYNC_VERSION') ? METASYNC_VERSION : '1.0.0';
143 +
144 + # Same tunnel endpoint sendToSentry() posts to.
145 + $url = 'https://wordpress.telemetry.infra.searchatlas.com/api/4509950439849985/envelope/';
146 +
147 + wp_remote_post($url, [
148 + 'blocking' => false,
149 + 'timeout' => 0.01,
150 + 'sslverify' => true,
151 + 'headers' => [
152 + 'Authorization' => 'Bearer ' . $jwt_token,
153 + 'Content-Type' => 'application/x-sentry-envelope',
154 + 'X-Plugin-Version' => $plugin_version,
155 + 'User-Agent' => 'WordPress MetaSync Plugin/' . $plugin_version,
156 + ],
157 + 'body' => $envelope,
158 + ]);
159 +
160 + return true;
161 + }
71 162
72 163 /**
73 164 * Capture user feedback and send to Sentry
74 165 *
@@ -302,9 +393,9 @@
302 393 /**
303 394 * Check if the current environment is localhost/development
304 395 */
305 396 private function isLocalhost() {
306 - $host = parse_url(home_url(), PHP_URL_HOST);
397 + $host = wp_parse_url(home_url(), PHP_URL_HOST);
307 398
308 399 // Check for common localhost patterns
309 400 $localhost_patterns = [
310 401 'localhost',
@@ -317,9 +408,9 @@
317 408 '.localhost'
318 409 ];
319 410
320 411 foreach ($localhost_patterns as $pattern) {
321 - if (strpos($host, $pattern) !== false) {
412 + if ($host !== false && $host !== null && strpos($host, $pattern) !== false) {
322 413 return true;
323 414 }
324 415 }
325 416
@@ -347,8 +438,12 @@
347 438 * @param array|null $attachment Optional attachment data
348 439 * @return bool|array Success status, or array with success and event_id
349 440 */
350 441 private function sendToSentry($data, $item_type = 'event', $attachment = null) {
442 + if (metasync_telemetry_is_disabled()) {
443 + return false;
444 + }
445 +
351 446 // Skip sending to Sentry if running on localhost/development environment
352 447 if ($this->isLocalhost()) {
353 448 return false;
354 449 }
@@ -384,32 +479,23 @@
384 479 'X-Plugin-Version' => $plugin_version,
385 480 'User-Agent' => 'WordPress MetaSync Plugin/' . $plugin_version
386 481 ];
387 482
388 - // Use cURL directly to ensure proper envelope format
389 - $ch = curl_init();
390 - curl_setopt($ch, CURLOPT_URL, $url);
391 - curl_setopt($ch, CURLOPT_POST, true);
392 - curl_setopt($ch, CURLOPT_POSTFIELDS, $envelope);
393 - curl_setopt($ch, CURLOPT_HTTPHEADER, array_map(function($key, $value) {
394 - return $key . ': ' . $value;
395 - }, array_keys($headers), $headers));
396 - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
397 - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
398 - curl_setopt($ch, CURLOPT_TIMEOUT, 5); // 5 second timeout as requested
399 - curl_setopt($ch, CURLOPT_USERAGENT, 'WordPress MetaSync Plugin/' . $plugin_version);
400 - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); // 5 second connection timeout
401 -
402 - $response = curl_exec($ch);
403 - $response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
404 - $error = curl_error($ch);
483 + $response_data = wp_remote_post($url, [
484 + 'body' => $envelope,
485 + 'headers' => $headers,
486 + 'timeout' => 5, // 5 second timeout as requested
487 + 'sslverify' => true,
488 + 'user-agent' => 'WordPress MetaSync Plugin/' . $plugin_version,
489 + ]);
490 + $error = is_wp_error($response_data) ? $response_data->get_error_message() : '';
491 + $response_code = is_wp_error($response_data) ? 0 : (int) wp_remote_retrieve_response_code($response_data);
492 + $response = is_wp_error($response_data) ? '' : wp_remote_retrieve_body($response_data);
405 493
406 - #curl_close($ch);
407 -
408 494 // Log errors in debug mode for troubleshooting
409 495 if (defined('WP_DEBUG') && WP_DEBUG && WP_DEBUG_LOG) {
410 496 if ($error) {
411 - error_log(sprintf(
497 + error_log(sprintf( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- debug-gated, no secrets
412 498 'MetaSync Sentry Error (%s): %s | Response Code: %s | Item Type: %s',
413 499 $item_type,
414 500 $error,
415 501 $response_code,
@@ -415,13 +501,12 @@
415 501 $response_code,
416 502 $item_type
417 503 ));
418 504 } elseif ($response_code < 200 || $response_code >= 300) {
419 - error_log(sprintf(
420 - 'MetaSync Sentry HTTP Error (%s): Response Code: %s | Response: %s | Item Type: %s',
505 + error_log(sprintf( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- debug-gated, no secrets
506 + 'MetaSync Sentry HTTP Error (%s): Response Code: %s | Item Type: %s',
421 507 $item_type,
422 508 $response_code,
423 - substr($response, 0, 200),
424 509 $item_type
425 510 ));
426 511 }
427 512 }
@@ -625,9 +710,9 @@
625 710 if ($cached_context !== false) {
626 711 // Add dynamic data that changes per request
627 712 $cached_context['memory_usage'] = memory_get_usage(true);
628 713 $cached_context['memory_peak'] = memory_get_peak_usage(true);
629 - $cached_context['request_uri'] = $_SERVER['REQUEST_URI'] ?? 'unknown';
714 + $cached_context['request_uri'] = metasync_telemetry_request_path();
630 715 return $cached_context;
631 716 }
632 717
633 718 // Collect static system context (expensive operations)
@@ -644,14 +729,14 @@
644 729 'max_execution_time' => ini_get('max_execution_time'),
645 730 'active_plugins' => count(get_option('active_plugins', [])),
646 731 'active_theme' => get_template(),
647 732 'multisite' => is_multisite(),
648 - 'mysql_version' => method_exists($wpdb, 'get_var') ? $wpdb->get_var('SELECT VERSION()') : 'unknown',
733 + 'mysql_version' => method_exists($wpdb, 'get_var') ? $wpdb->get_var('SELECT VERSION()') : 'unknown', // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- diagnostic metadata — SELECT VERSION() has no cached WordPress API
649 734 'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'unknown',
650 735 // Dynamic data added per request
651 736 'memory_usage' => memory_get_usage(true),
652 737 'memory_peak' => memory_get_peak_usage(true),
653 - 'request_uri' => $_SERVER['REQUEST_URI'] ?? 'unknown'
738 + 'request_uri' => metasync_telemetry_request_path()
654 739 ];
655 740
656 741 // Cache static context for 1 hour
657 742 wp_cache_set($cache_key, $context, 'metasync', HOUR_IN_SECONDS);
@@ -727,10 +812,10 @@
727 812 if ($this->isLocalhost()) {
728 813 return 'development';
729 814 }
730 815
731 - $host = parse_url(home_url(), PHP_URL_HOST);
732 - if (strpos($host, 'staging') !== false || strpos($host, 'dev') !== false) {
816 + $host = wp_parse_url(home_url(), PHP_URL_HOST);
817 + if ($host !== false && $host !== null && (strpos($host, 'staging') !== false || strpos($host, 'dev') !== false)) {
733 818 return 'staging';
734 819 }
735 820
736 821 return 'production';
@@ -789,8 +874,12 @@
789 874 * Initialize Sentry with DSN configuration
790 875 */
791 876 function init_metasync_sentry_wordpress() {
792 877 global $metasync_sentry_wordpress;
878 +
879 + if (metasync_telemetry_is_disabled()) {
880 + return null;
881 + }
793 882
794 883 $dsn = '';
795 884
796 885 // Use constants defined in metasync.php for configuration
@@ -820,8 +909,11 @@
820 909 * Helper function to capture exceptions
821 910 */
822 911 function metasync_sentry_capture_exception($exception, $extra = []) {
823 912 global $metasync_sentry_wordpress;
913 + if (metasync_telemetry_is_disabled()) {
914 + return false;
915 + }
824 916 if (!$metasync_sentry_wordpress) {
825 917 $metasync_sentry_wordpress = init_metasync_sentry_wordpress();
826 918 }
827 919
@@ -835,12 +927,15 @@
835 927 * Helper function to capture messages
836 928 */
837 929 function metasync_sentry_capture_message($message, $level = 'info', $extra = [], $attachment = null) {
838 930 global $metasync_sentry_wordpress;
931 + if (metasync_telemetry_is_disabled()) {
932 + return false;
933 + }
839 934 if (!$metasync_sentry_wordpress) {
840 935 $metasync_sentry_wordpress = init_metasync_sentry_wordpress();
841 936 }
842 -
937 +
843 938 if ($metasync_sentry_wordpress) {
844 939 return $metasync_sentry_wordpress->captureMessage($message, $level, $extra, $attachment);
845 940 }
846 941 return false;
@@ -846,8 +941,31 @@
846 941 return false;
847 942 }
848 943
849 944 /**
945 + * Helper function to capture messages without blocking the current request.
946 + *
947 + * Use this instead of metasync_sentry_capture_message() from anything that runs
948 + * on a visitor's page render — the blocking variant can hold the request for up
949 + * to ten seconds if the collector is slow. See
950 + * MetaSync_Sentry_WordPress::captureMessageNonBlocking() for the trade-off.
951 + */
952 +function metasync_sentry_capture_message_nonblocking($message, $level = 'info', $extra = []) {
953 + global $metasync_sentry_wordpress;
954 + if (metasync_telemetry_is_disabled()) {
955 + return false;
956 + }
957 + if (!$metasync_sentry_wordpress) {
958 + $metasync_sentry_wordpress = init_metasync_sentry_wordpress();
959 + }
960 +
961 + if ($metasync_sentry_wordpress) {
962 + return $metasync_sentry_wordpress->captureMessageNonBlocking($message, $level, $extra);
963 + }
964 + return false;
965 +}
966 +
967 +/**
850 968 * Helper function to capture user feedback
851 969 *
852 970 * @param array $feedback Feedback data with keys: name (optional), email (optional), message (required), event_id (optional)
853 971 * @return bool Success status
@@ -853,8 +971,11 @@
853 971 * @return bool Success status
854 972 */
855 973 function metasync_sentry_capture_feedback($feedback, $attachment = null) {
856 974 global $metasync_sentry_wordpress;
975 + if (metasync_telemetry_is_disabled()) {
976 + return false;
977 + }
857 978 if (!$metasync_sentry_wordpress) {
858 979 $metasync_sentry_wordpress = init_metasync_sentry_wordpress();
859 980 }
860 981