PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.1
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.1
2.7.1 2.7.2 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 All 141 releases
← All changes | includes/class-metasync-heartbeat-manager.php +41 -6 2.6.23 → 2.7.1 View file →
@@ -154,11 +154,16 @@
154 154 # path runs on the same host, so if its breaker has tripped we already know
155 155 # this call cannot succeed. This only helps when page traffic has tripped the
156 156 # breaker first — an admin loading this page on an idle site still pays the
157 157 # full 48s, which is why the retry budget itself is flagged for follow-up.
158 - $endpoint_unhealthy = (class_exists('Metasync_API_Backoff_Manager')
158 + #
159 + # Both helpers are checked with class_provides() rather than class_exists():
160 + # they live in files separate from this one, so a partially updated install
161 + # can leave a previous copy of either class loaded that predates the method
162 + # being called. See class_provides() for why that matters here.
163 + $endpoint_unhealthy = (self::class_provides('Metasync_API_Backoff_Manager', 'is_endpoint_in_backoff')
159 164 && Metasync_API_Backoff_Manager::get_instance()->is_endpoint_in_backoff($api_url))
160 - || (class_exists('Metasync_Otto_Transient_Cache')
165 + || (self::class_provides('Metasync_Otto_Transient_Cache', 'is_host_breaker_open')
161 166 && Metasync_Otto_Transient_Cache::is_host_breaker_open($api_url));
162 167
163 168 if ($endpoint_unhealthy) {
164 169 $this->log_fetch_hash_error('error', 'Public hash fetch skipped - endpoint unhealthy', [
@@ -255,8 +260,32 @@
255 260 // ------------------------------------------------------------------
256 261 // Public-hash helpers (private)
257 262 // ------------------------------------------------------------------
258 263
264 + /**
265 + * Is $method actually available on $class in this process right now?
266 + *
267 + * A partially updated install can leave a newer copy of one plugin file
268 + * beside an older copy of another — stale opcache bytecode for a single file
269 + * is enough. class_exists() is satisfied by the older copy, and calling a
270 + * method it does not declare is a fatal: that is how the health checks in
271 + * fetch_public_hash() brought down the whole Dashboard admin page with
272 + * "Call to undefined method ...::is_host_breaker_open()".
273 + *
274 + * $class and $method are parameters rather than literals at the call site so
275 + * the check survives static analysis, which would otherwise narrow a literal
276 + * method_exists() on a known class to a constant true — the skew this guards
277 + * against exists only at runtime.
278 + *
279 + * @param string $class Class about to be called.
280 + * @param string $method Method about to be called on it.
281 + * @return bool
282 + */
283 + private static function class_provides($class, $method)
284 + {
285 + return class_exists($class) && method_exists($class, $method);
286 + }
287 +
259 288 private function validate_fetch_hash_inputs($uuid, $token)
260 289 {
261 290 if (empty($uuid) || empty($token)) {
262 291 return false;
@@ -523,9 +552,9 @@
523 552 }
524 553 $log_message .= ' | ' . implode(', ', $context_parts);
525 554 }
526 555
527 - error_log($log_message);
556 + error_log($log_message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets
528 557 }
529 558
530 559 public function clear_public_hash_cache($otto_pixel_uuid = '')
531 560 {
@@ -597,9 +626,9 @@
597 626 }
598 627 $message .= ' | ' . implode(', ', $details_formatted);
599 628 }
600 629
601 - error_log($message);
630 + error_log($message); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets
602 631
603 632 if ($level === 'error' || $level === 'critical') {
604 633 $this->store_heartbeat_error_log($event, $details);
605 634 }
@@ -617,9 +646,9 @@
617 646 if ($last_space !== false && $last_space > $length * 0.75) {
618 647 $truncated = substr($truncated, 0, $last_space);
619 648 }
620 649
621 - $truncated = strip_tags($truncated);
650 + $truncated = wp_strip_all_tags($truncated);
622 651 return $truncated . '... [truncated]';
623 652 }
624 653
625 654 private function should_throttle_log($level, $event, $details = array())
@@ -666,9 +695,9 @@
666 695 'created_at' => current_time('mysql')
667 696 ));
668 697 }
669 698 } catch (Exception $e) {
670 - error_log('Failed to store heartbeat error log: ' . $e->getMessage());
699 + error_log('Failed to store heartbeat error log: ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- genuine failure path, bounded, no secrets
671 700 }
672 701 }
673 702
674 703 // ------------------------------------------------------------------
@@ -958,32 +987,38 @@
958 987 public function add_heartbeat_cron_schedule($schedules)
959 988 {
960 989 $schedules['metasync_every_2_hours'] = array(
961 990 'interval' => 2 * HOUR_IN_SECONDS,
991 + /* translators: %s: effective plugin name (whitelabel-aware). */
962 992 'display' => esc_html(sprintf(__('Every 2 Hours (%s)', 'metasync'), Metasync::get_effective_plugin_name()))
963 993 );
964 994
965 995 $schedules['metasync_every_2_minutes'] = array(
966 996 'interval' => 2 * MINUTE_IN_SECONDS,
997 + /* translators: %s: effective plugin name (whitelabel-aware). */
967 998 'display' => esc_html(sprintf(__('Every 2 Minutes (%s Burst)', 'metasync'), Metasync::get_effective_plugin_name()))
968 999 );
969 1000 $schedules['metasync_every_5_minutes'] = array(
970 1001 'interval' => 5 * MINUTE_IN_SECONDS,
1002 + /* translators: %s: effective plugin name (whitelabel-aware). */
971 1003 'display' => esc_html(sprintf(__('Every 5 Minutes (%s)', 'metasync'), Metasync::get_effective_plugin_name()))
972 1004 );
973 1005 // 10-minute heartbeat cadence used for UNREGISTERED + KEY_PENDING short-interval behavior
974 1006 $schedules['metasync_every_10_minutes'] = array(
975 1007 'interval' => 10 * MINUTE_IN_SECONDS,
1008 + /* translators: %s: effective plugin name (whitelabel-aware). */
976 1009 'display' => esc_html(sprintf(__('Every 10 Minutes (%s)', 'metasync'), Metasync::get_effective_plugin_name()))
977 1010 );
978 1011
979 1012 $schedules['metasync_daily_cleanup'] = array(
980 1013 'interval' => DAY_IN_SECONDS,
1014 + /* translators: %s: effective plugin name (whitelabel-aware). */
981 1015 'display' => esc_html(sprintf(__('Daily (%s Cleanup)', 'metasync'), Metasync::get_effective_plugin_name()))
982 1016 );
983 1017
984 1018 $schedules['metasync_weekly'] = array(
985 1019 'interval' => 7 * DAY_IN_SECONDS,
1020 + /* translators: %s: effective plugin name (whitelabel-aware). */
986 1021 'display' => esc_html(sprintf(__('Weekly (%s)', 'metasync'), Metasync::get_effective_plugin_name()))
987 1022 );
988 1023
989 1024 return $schedules;