Plugin Directory

Changeset 3424156


Ignore:
Timestamp:
12/20/2025 11:11:10 AM (3 months ago)
Author:
loghin
Message:

1.2.997

Location:
dynamic-front-end-heartbeat-control
Files:
35 added
11 edited

Legend:

Unmodified
Added
Removed
  • dynamic-front-end-heartbeat-control/trunk/defibrillator/cli-helper.php

    r3396626 r3424156  
    8484class DFEHC_CLI_Command {
    8585
     86    private function key_candidates(string $base): array {
     87        $keys = [];
     88        $base = (string) $base;
     89        if ($base !== '') {
     90            $keys[] = $base;
     91        }
     92        $keys[] = dfehc_scope_key($base);
     93        if (\function_exists('\dfehc_scoped_key')) {
     94            $keys[] = \dfehc_scoped_key($base);
     95        }
     96        if (\function_exists('\dfehc_key')) {
     97            $keys[] = \dfehc_key($base);
     98        }
     99        $keys = \array_values(\array_unique(\array_filter($keys, 'strlen')));
     100        return $keys;
     101    }
     102
     103    private function delete_candidate_keys(string $base): void {
     104        $keys = $this->key_candidates($base);
     105        foreach ($keys as $k) {
     106            \delete_transient($k);
     107            if (\is_multisite()) {
     108                \delete_site_transient($k);
     109            }
     110        }
     111    }
     112
     113    private function set_candidate_keys(string $base, $value, int $ttl): void {
     114        $keys = $this->key_candidates($base);
     115        foreach ($keys as $k) {
     116            if (\function_exists('\dfehc_set_transient_noautoload')) {
     117                \dfehc_set_transient_noautoload($k, $value, $ttl);
     118            } else {
     119                \set_transient($k, $value, $ttl);
     120            }
     121        }
     122    }
     123
    86124    public function recalc_interval(array $args = [], array $assoc_args = []) {
    87125        if (!\function_exists('\dfehc_get_server_load')) {
     
    106144
    107145        $baseKey = \defined('DFEHC_RECOMMENDED_INTERVAL') ? \DFEHC_RECOMMENDED_INTERVAL : 'dfehc_recommended_interval';
    108         $key     = dfehc_scope_key($baseKey);
    109 
    110         if (\function_exists('\dfehc_set_transient_noautoload')) {
    111             \dfehc_set_transient_noautoload($key, $interval, 5 * MINUTE_IN_SECONDS);
    112         } else {
    113             \set_transient($key, $interval, 5 * MINUTE_IN_SECONDS);
    114         }
     146        $ttl = 5 * MINUTE_IN_SECONDS;
     147
     148        $this->set_candidate_keys((string) $baseKey, $interval, $ttl);
    115149
    116150        \WP_CLI::success("Heartbeat interval recalculated and cached: {$interval}s");
     
    162196            try {
    163197                foreach ($bases as $base) {
    164                     $key = dfehc_scope_key($base);
    165                     \delete_transient($key);
    166                     \delete_site_transient($key);
     198                    $this->delete_candidate_keys((string) $base);
    167199                }
    168200                \delete_option('dfehc_stale_total_visitors');
     
    201233
    202234        $baseKey  = \defined('DFEHC_RECOMMENDED_INTERVAL') ? \DFEHC_RECOMMENDED_INTERVAL : 'dfehc_recommended_interval';
    203         $key      = dfehc_scope_key($baseKey);
    204         $interval = \get_transient($key);
     235        $interval = \get_transient(dfehc_scope_key((string) $baseKey));
     236
     237        if (($interval === false || $interval === null) && \function_exists('\dfehc_scoped_key')) {
     238            $interval = \get_transient(\dfehc_scoped_key((string) $baseKey));
     239        }
     240        if (($interval === false || $interval === null) && \function_exists('\dfehc_key')) {
     241            $interval = \get_transient(\dfehc_key((string) $baseKey));
     242        }
    205243
    206244        $heartbeat_enabled = !\get_option('dfehc_disable_heartbeat');
  • dynamic-front-end-heartbeat-control/trunk/defibrillator/load-estimator.php

    r3412283 r3424156  
    3131            $duration = 0.5;
    3232        }
     33
    3334        $suffix    = self::scope_suffix();
    3435        $baselineT = self::get_baseline_transient_name($suffix);
    3536        $cacheTtl  = (int) \apply_filters('dfehc_load_cache_ttl', 90);
     37        if ($cacheTtl < 1) {
     38            $cacheTtl = 1;
     39        }
    3640        $cacheKey  = self::get_cache_key($suffix);
     41
    3742        $cached = self::get_scoped_transient($cacheKey);
    38         if ($cached !== false && $cached !== null) {
    39             return $cached;
    40         }
     43        if ($cached !== false && $cached !== null && \is_numeric($cached)) {
     44            return (float) $cached;
     45        }
     46
    4147        $sysAvg = self::try_sys_getloadavg();
    4248        if ($sysAvg !== null) {
     
    4450            return $sysAvg;
    4551        }
     52
    4653        $baseline = self::get_baseline_value($baselineT);
    47         if ($baseline === false || $baseline === null || !\is_numeric($baseline) || $baseline <= 0) {
     54        if ($baseline === false || $baseline === null || !\is_numeric($baseline) || (float) $baseline <= 0.0) {
    4855            $baseline = self::maybe_calibrate($baselineT, $duration);
    4956        }
     57        $baseline = (float) $baseline;
     58        if ($baseline <= 0.0) {
     59            $baseline = 1.0;
     60        }
     61
    5062        $loopsPerSec = self::run_loop_avg($duration);
    5163        if ($loopsPerSec <= 0) {
    5264            return false;
    5365        }
     66
    5467        $loadRatio   = ($baseline * 0.125) / \max($loopsPerSec, 1);
    5568        $loadPercent = \round(\min(100.0, \max(0.0, $loadRatio * 100.0)), 2);
    5669        $loadPercent = (float) \apply_filters('dfehc_computed_load_percent', $loadPercent, $baseline, $loopsPerSec);
     70
    5771        self::update_spike_score($loadPercent, $suffix);
    5872        self::set_scoped_transient_noautoload($cacheKey, $loadPercent, $cacheTtl);
     73
    5974        return $loadPercent;
    6075    }
     
    8499            return;
    85100        }
     101
    86102        $suffix  = self::scope_suffix();
    87103        $seenKey = 'dfehc_seen_recently_' . $suffix;
    88104        $ttl     = (int) \apply_filters('dfehc_seen_recently_ttl', 60);
     105        if ($ttl < 1) {
     106            $ttl = 1;
     107        }
    89108        if (\get_transient($seenKey) !== false) {
    90109            return;
     
    101120        $cdKey  = 'dfehc_cron_cal_cd_' . $suffix;
    102121        $cdTtl  = (int) \apply_filters('dfehc_cron_calibration_cooldown', 300);
     122        if ($cdTtl < 1) {
     123            $cdTtl = 1;
     124        }
    103125        if (\get_transient($cdKey) !== false) {
    104126            return;
     
    118140        $raw = (float) $avg[0];
    119141        $raw = (float) \apply_filters('dfehc_raw_sys_load', $raw);
     142
    120143        $cores = 0;
    121144        if (\defined('DFEHC_CPU_CORES')) {
    122145            $cores = (int) DFEHC_CPU_CORES;
    123         } elseif (\function_exists('\dfehc_get_cpu_cores')) {
    124             $cores = (int) \dfehc_get_cpu_cores();
    125146        } elseif (\function_exists('dfehc_get_cpu_cores')) {
    126147            $cores = (int) \dfehc_get_cpu_cores();
     
    130151            $cores = 1;
    131152        }
     153
    132154        return \min(100.0, \round(($raw / $cores) * 100.0, 2));
    133155    }
     
    148170        }
    149171        $duration += \mt_rand(0, 2) * 0.001;
     172
    150173        $warm = self::now();
    151174        for ($i = 0; $i < 1000; $i++) { $warm += 0; }
     175
    152176        $start = self::now();
    153177        $end   = $start + $duration;
    154178        $cnt   = 0;
    155179        $cap   = (int) \apply_filters('dfehc_loop_iteration_cap', 10000000);
     180        if ($cap < 1000) {
     181            $cap = 1000;
     182        }
    156183        $now   = $start;
     184
    157185        while ($now < $end && $cnt < $cap) {
    158186            ++$cnt;
    159187            $now = self::now();
    160188        }
     189
    161190        $elapsed = $now - $start;
    162191        return $elapsed > 0 ? $cnt / $elapsed : 0.0;
     
    178207        $lockKey  = 'dfehc_calibrating_' . $suffix;
    179208        $lock     = self::acquire_lock($lockKey, 30);
     209        if (!$lock) {
     210            $existing = self::get_baseline_value($baselineT);
     211            if ($existing !== false && $existing !== null && \is_numeric($existing) && (float) $existing > 0.0) {
     212                return (float) $existing;
     213            }
     214        }
     215
    180216        $baseline = self::run_loop_avg($duration);
    181217        if ($baseline <= 0.0) {
    182218            $baseline = 1.0;
    183219        }
     220
    184221        if ($lock) {
    185222            $exp = (int) \apply_filters('dfehc_baseline_expiration', 7 * DAY_IN_SECONDS);
     223            if ($exp < 60) {
     224                $exp = 60;
     225            }
    186226            self::set_baseline_value($baselineT, $baseline, $exp);
    187             self::release_lock($lock, $lockKey);
    188         }
     227            self::release_lock($lock);
     228        }
     229
    189230        return $baseline;
    190231    }
     
    192233    private static function update_spike_score(float $loadPercent, string $suffix): void {
    193234        $spikeKey   = self::get_spike_key($suffix);
    194         $score      = (float) self::get_scoped_transient($spikeKey);
     235        $scoreRaw   = self::get_scoped_transient($spikeKey);
     236        $score      = \is_numeric($scoreRaw) ? (float) $scoreRaw : 0.0;
     237
    195238        $decay      = (float) \apply_filters('dfehc_spike_decay', 0.5);
    196239        $increment  = (float) \apply_filters('dfehc_spike_increment', 1.0);
     
    198241        $trigger    = (float) \apply_filters('dfehc_spike_trigger', 90.0);
    199242        $resetCdTtl = (int) \apply_filters('dfehc_baseline_reset_cooldown', 3600);
     243        if ($resetCdTtl < 1) {
     244            $resetCdTtl = 1;
     245        }
    200246        $resetCdKey = self::BASELINE_RESET_CD_PREFIX . $suffix;
    201247        $scoreMax   = (float) \apply_filters('dfehc_spike_score_max', 20.0);
     248
    202249        if ($loadPercent > $trigger) {
    203250            $score += $increment * (1 + (($loadPercent - $trigger) / 20.0));
     
    205252            $score = \max(0.0, $score - $decay);
    206253        }
     254
    207255        $score = \min($scoreMax, \max(0.0, $score));
     256
    208257        if ($score >= $threshold) {
    209258            if (\get_transient($resetCdKey) === false) {
     
    215264            }
    216265        }
     266
    217267        self::set_scoped_transient_noautoload($spikeKey, $score, (int) \apply_filters('dfehc_spike_score_ttl', HOUR_IN_SECONDS));
    218268    }
     
    222272        $baselineT = self::get_baseline_transient_name($suffix);
    223273        $existing  = self::get_baseline_value($baselineT);
    224         if ($existing !== false && $existing !== null && \is_numeric($existing) && $existing > 0) {
    225             return;
    226         }
     274        if ($existing !== false && $existing !== null && \is_numeric($existing) && (float) $existing > 0.0) {
     275            return;
     276        }
     277
    227278        $lockKey = 'dfehc_calibrating_' . $suffix;
    228279        $lock    = self::acquire_lock($lockKey, 30);
     
    230281            return;
    231282        }
    232         $duration = (float) \apply_filters('dfehc_loop_duration', 0.025);
    233         if (!\is_finite($duration) || $duration <= 0.0) {
    234             $duration = 0.025;
    235         }
    236         if ($duration < 0.01) {
    237             $duration = 0.01;
    238         }
    239         if ($duration > 0.5) {
    240             $duration = 0.5;
    241         }
    242         $baseline = self::run_loop_avg($duration);
    243         if ($baseline <= 0.0) {
    244             $baseline = 1.0;
    245         }
    246         $exp = (int) \apply_filters('dfehc_baseline_expiration', 7 * DAY_IN_SECONDS);
    247         self::set_baseline_value($baselineT, $baseline, $exp);
    248         self::release_lock($lock, $lockKey);
     283
     284        try {
     285            $duration = (float) \apply_filters('dfehc_loop_duration', 0.025);
     286            if (!\is_finite($duration) || $duration <= 0.0) {
     287                $duration = 0.025;
     288            }
     289            if ($duration < 0.01) {
     290                $duration = 0.01;
     291            }
     292            if ($duration > 0.5) {
     293                $duration = 0.5;
     294            }
     295
     296            $baseline = self::run_loop_avg($duration);
     297            if ($baseline <= 0.0) {
     298                $baseline = 1.0;
     299            }
     300
     301            $exp = (int) \apply_filters('dfehc_baseline_expiration', 7 * DAY_IN_SECONDS);
     302            if ($exp < 60) {
     303                $exp = 60;
     304            }
     305            self::set_baseline_value($baselineT, $baseline, $exp);
     306        } finally {
     307            self::release_lock($lock);
     308        }
    249309    }
    250310
    251311    private static function acquire_lock(string $key, int $ttl) {
     312        $ttl = $ttl < 1 ? 1 : $ttl;
     313        $scopedKey = $key;
     314
    252315        if (\class_exists('\WP_Lock')) {
    253             $lock = new \WP_Lock($key, $ttl);
     316            $lock = new \WP_Lock($scopedKey, $ttl);
    254317            return $lock->acquire() ? $lock : null;
    255318        }
    256         if (\function_exists('wp_cache_add') && \wp_cache_add($key, 1, DFEHC_CACHE_GROUP, $ttl)) {
    257             return (object) ['type' => 'cache', 'key' => $key];
    258         }
    259         if (\get_transient($key) !== false) {
     319        if (\function_exists('wp_cache_add') && \wp_cache_add($scopedKey, 1, DFEHC_CACHE_GROUP, $ttl)) {
     320            return (object) ['type' => 'cache', 'key' => $scopedKey];
     321        }
     322        if (\get_transient($scopedKey) !== false) {
    260323            return null;
    261324        }
    262         if (\set_transient($key, 1, $ttl)) {
    263             return (object) ['type' => 'transient', 'key' => $key];
     325        if (\set_transient($scopedKey, 1, $ttl)) {
     326            return (object) ['type' => 'transient', 'key' => $scopedKey];
    264327        }
    265328        return null;
    266329    }
    267330
    268     private static function release_lock($lock, string $key): void {
     331    private static function release_lock($lock): void {
    269332        if ($lock instanceof \WP_Lock) {
    270333            $lock->release();
     
    273336        if (\is_object($lock) && isset($lock->type, $lock->key)) {
    274337            if ($lock->type === 'cache') {
    275                 \wp_cache_delete($key, DFEHC_CACHE_GROUP);
     338                if (\function_exists('wp_cache_delete')) {
     339                    \wp_cache_delete((string) $lock->key, DFEHC_CACHE_GROUP);
     340                }
    276341                return;
    277342            }
    278343            if ($lock->type === 'transient') {
    279                 \delete_transient($key);
     344                \delete_transient((string) $lock->key);
    280345                return;
    281346            }
     
    292357            $url = \defined('WP_HOME') && WP_HOME ? WP_HOME : (\function_exists('home_url') ? \home_url() : '');
    293358            $parts = \parse_url((string) $url);
    294             $host = $parts['host'] ?? ($url ?: 'unknown');
     359            $host = \is_array($parts) ? ($parts['host'] ?? '') : '';
     360            if (!$host) {
     361                $host = $url ?: 'unknown';
     362            }
    295363        }
    296364        $salt = \defined('DB_NAME') ? (string) DB_NAME : '';
     
    326394
    327395    private static function set_baseline_value(string $name, $value, int $exp): void {
     396        if ($exp < 1) {
     397            $exp = 1;
     398        }
    328399        if (\is_multisite()) {
    329400            self::set_site_transient_noautoload($name, $value, $exp);
     
    346417
    347418    private static function set_scoped_transient_noautoload(string $key, $value, int $ttl): void {
     419        if ($ttl < 1) {
     420            $ttl = 1;
     421        }
    348422        if (\is_multisite()) {
    349423            self::set_site_transient_noautoload($key, $value, $ttl);
     
    371445        }
    372446        $ttl = \max(1, $ttl + $jitter);
     447
    373448        if (\function_exists('wp_using_ext_object_cache') && \wp_using_ext_object_cache()) {
    374449            \wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $ttl);
    375450            return;
    376451        }
     452
    377453        \set_transient($key, $value, $ttl);
     454
    378455        global $wpdb;
    379         if (!isset($wpdb->options)) {
    380             return;
    381         }
     456        if (!isset($wpdb) || !\is_object($wpdb) || !isset($wpdb->options)) {
     457            return;
     458        }
     459
    382460        $opt_key = "_transient_$key";
    383461        $opt_key_to = "_transient_timeout_$key";
    384462        $wpdb->suppress_errors(true);
    385         $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key));
    386         if ($autoload === 'yes') {
    387             $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key, 'autoload' => 'yes'], ['%s'], ['%s','%s']);
    388         }
    389         $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to));
    390         if ($autoload_to === 'yes') {
    391             $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key_to, 'autoload' => 'yes'], ['%s'], ['%s','%s']);
    392         }
    393         $wpdb->suppress_errors(false);
     463        try {
     464            $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key));
     465            if ($autoload === 'yes') {
     466                $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key, 'autoload' => 'yes'], ['%s'], ['%s','%s']);
     467            }
     468            $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to));
     469            if ($autoload_to === 'yes') {
     470                $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key_to, 'autoload' => 'yes'], ['%s'], ['%s','%s']);
     471            }
     472        } finally {
     473            $wpdb->suppress_errors(false);
     474        }
    394475    }
    395476
     
    404485        }
    405486        $ttl = \max(1, $ttl + $jitter);
     487
    406488        if (\function_exists('wp_using_ext_object_cache') && \wp_using_ext_object_cache()) {
    407489            \wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $ttl);
    408490            return;
    409491        }
     492
    410493        \set_site_transient($key, $value, $ttl);
    411494    }
  • dynamic-front-end-heartbeat-control/trunk/engine/server-load.php

    r3412883 r3424156  
    6868    function dfehc_client_ip(): string
    6969    {
    70         $ip = (string)($_SERVER['REMOTE_ADDR'] ?? '0.0.0.0');
     70        $remote = (string) ($_SERVER['REMOTE_ADDR'] ?? '');
     71        $remote = $remote !== '' ? $remote : '0.0.0.0';
     72
     73        $is_valid_ip = static function (string $ip, bool $publicOnly): bool {
     74            $ip = trim($ip);
     75            if ($ip === '') return false;
     76
     77            if ($publicOnly) {
     78                return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false;
     79            }
     80            return filter_var($ip, FILTER_VALIDATE_IP) !== false;
     81        };
     82
     83        $public_only = (bool) apply_filters('dfehc_client_ip_public_only', true);
     84
    7185        $trusted = (array) apply_filters('dfehc_trusted_proxies', []);
    72         if ($trusted && in_array($ip, $trusted, true)) {
    73             $headers = (array) apply_filters('dfehc_proxy_ip_headers', ['HTTP_X_FORWARDED_FOR', 'HTTP_CF_CONNECTING_IP', 'HTTP_X_REAL_IP']);
     86        $trusted = array_values(array_filter(array_map('trim', $trusted), 'strlen'));
     87        $remote_is_trusted = $trusted && in_array($remote, $trusted, true);
     88
     89        if ($remote_is_trusted) {
     90            $headers = (array) apply_filters(
     91                'dfehc_proxy_ip_headers',
     92                ['HTTP_CF_CONNECTING_IP', 'HTTP_TRUE_CLIENT_IP', 'HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR']
     93            );
     94
    7495            foreach ($headers as $h) {
    75                 if (!empty($_SERVER[$h])) {
    76                     $raw = (string) $_SERVER[$h];
     96                $h = (string) $h;
     97                if ($h === '' || empty($_SERVER[$h])) {
     98                    continue;
     99                }
     100
     101                $raw = (string) $_SERVER[$h];
     102
     103                if ($h === 'HTTP_X_FORWARDED_FOR') {
    77104                    $parts = array_map('trim', explode(',', $raw));
    78                     $cand = end($parts);
    79                     if ($cand) {
     105
     106                    foreach ($parts as $cand) {
     107                        if ($is_valid_ip($cand, $public_only)) {
     108                            return (string) apply_filters('dfehc_client_ip', $cand);
     109                        }
     110                    }
     111                    foreach ($parts as $cand) {
     112                        if ($is_valid_ip($cand, false)) {
     113                            return (string) apply_filters('dfehc_client_ip', $cand);
     114                        }
     115                    }
     116                } else {
     117                    $cand = trim($raw);
     118                    if ($is_valid_ip($cand, $public_only) || $is_valid_ip($cand, false)) {
    80119                        return (string) apply_filters('dfehc_client_ip', $cand);
    81120                    }
     
    83122            }
    84123        }
    85         return (string) apply_filters('dfehc_client_ip', $ip);
     124
     125        if (!$is_valid_ip($remote, false)) {
     126            $remote = '0.0.0.0';
     127        }
     128
     129        return (string) apply_filters('dfehc_client_ip', $remote);
    86130    }
    87131}
     
    119163{
    120164    static $cached = null;
    121     static $ts = 0;
     165    static $last_probe_ts = 0;
     166
    122167    $retryAfter = (int) apply_filters('dfehc_cache_retry_after', 60);
    123     if ($cached !== null && ($cached['type'] !== 'none' || $ts > time() - $retryAfter)) {
    124         return $cached;
    125     }
    126     $ts = time();
     168    $retryAfter = max(0, $retryAfter);
     169
     170    if (is_array($cached) && isset($cached['type'])) {
     171        if ($cached['type'] !== 'none') {
     172            return $cached;
     173        }
     174        if ($retryAfter > 0 && (time() - (int) $last_probe_ts) < $retryAfter) {
     175            return $cached;
     176        }
     177    }
     178
     179    $last_probe_ts = time();
     180
    127181    global $wp_object_cache;
    128182    if (is_object($wp_object_cache) && isset($wp_object_cache->redis) && $wp_object_cache->redis instanceof Redis) {
     
    132186        return $cached = ['client' => $wp_object_cache->mc, 'type' => 'memcached'];
    133187    }
     188
    134189    if (class_exists('Redis')) {
    135190        try {
     
    138193                $pass = apply_filters('dfehc_redis_auth', getenv('REDIS_PASSWORD') ?: null);
    139194                $user = apply_filters('dfehc_redis_user', getenv('REDIS_USERNAME') ?: null);
    140                 if ($user && $pass && method_exists($redis, 'auth')) {
    141                     $redis->auth([$user, $pass]);
    142                 } elseif ($pass && method_exists($redis, 'auth')) {
    143                     $redis->auth($pass);
    144                 }
     195
     196                if (method_exists($redis, 'auth')) {
     197                    if ($user && $pass) {
     198                        $ok = $redis->auth([$user, $pass]);
     199                        if ($ok === false) {
     200                            throw new RuntimeException('Redis auth failed');
     201                        }
     202                    } elseif ($pass) {
     203                        $ok = $redis->auth($pass);
     204                        if ($ok === false) {
     205                            throw new RuntimeException('Redis auth failed');
     206                        }
     207                    }
     208                }
     209
    145210                return $cached = ['client' => $redis, 'type' => 'redis'];
    146211            }
     
    151216        }
    152217    }
     218
    153219    if (class_exists('Memcached')) {
    154220        try {
     
    157223                $mc->addServer(dfehc_get_memcached_server(), dfehc_get_memcached_port());
    158224            }
     225
    159226            $user = getenv('MEMCACHED_USERNAME');
    160227            $pass = getenv('MEMCACHED_PASSWORD');
     
    163230                $mc->setSaslAuthData($user, $pass);
    164231            }
     232
    165233            $versions = $mc->getVersion();
    166234            $first = is_array($versions) ? reset($versions) : false;
    167             $ok = $first && $first !== '0.0.0';
     235            $ok = $first && $first !== '0.0.0' && $first !== '0.0.0.0';
    168236            if ($ok) {
    169237                return $cached = ['client' => $mc, 'type' => 'memcached'];
     
    175243        }
    176244    }
     245
    177246    return $cached = ['client' => null, 'type' => 'none'];
    178247}
  • dynamic-front-end-heartbeat-control/trunk/engine/server-response.php

    r3412283 r3424156  
    7070
    7171if (!function_exists('dfehc_client_ip')) {
    72     function dfehc_client_ip(): string {
    73         $ip = (string)($_SERVER['REMOTE_ADDR'] ?? '0.0.0.0');
     72    function dfehc_client_ip(): string
     73    {
     74        $remote = (string) ($_SERVER['REMOTE_ADDR'] ?? '');
     75        $remote = $remote !== '' ? $remote : '0.0.0.0';
     76
     77        $is_valid_ip = static function (string $ip, bool $publicOnly): bool {
     78            $ip = trim($ip);
     79            if ($ip === '') return false;
     80            if ($publicOnly) {
     81                return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false;
     82            }
     83            return filter_var($ip, FILTER_VALIDATE_IP) !== false;
     84        };
     85
     86        $public_only = (bool) apply_filters('dfehc_client_ip_public_only', true);
     87
    7488        $trusted = (array) apply_filters('dfehc_trusted_proxies', []);
    75         if ($trusted && in_array($ip, $trusted, true)) {
    76             $headers = (array) apply_filters('dfehc_proxy_ip_headers', ['HTTP_X_FORWARDED_FOR', 'HTTP_CF_CONNECTING_IP', 'HTTP_X_REAL_IP']);
     89        $trusted = array_values(array_filter(array_map('trim', $trusted), 'strlen'));
     90        $remote_is_trusted = $trusted && in_array($remote, $trusted, true);
     91
     92        if ($remote_is_trusted) {
     93            $headers = (array) apply_filters(
     94                'dfehc_proxy_ip_headers',
     95                ['HTTP_CF_CONNECTING_IP', 'HTTP_TRUE_CLIENT_IP', 'HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR']
     96            );
     97
    7798            foreach ($headers as $h) {
    78                 if (!empty($_SERVER[$h])) {
    79                     $raw = (string) $_SERVER[$h];
     99                $h = (string) $h;
     100                if ($h === '' || empty($_SERVER[$h])) {
     101                    continue;
     102                }
     103
     104                $raw = (string) $_SERVER[$h];
     105
     106                if ($h === 'HTTP_X_FORWARDED_FOR') {
    80107                    $parts = array_map('trim', explode(',', $raw));
    81                     $cand = end($parts);
    82                     if ($cand) {
     108
     109                    foreach ($parts as $cand) {
     110                        if ($is_valid_ip($cand, $public_only)) {
     111                            return (string) apply_filters('dfehc_client_ip', $cand);
     112                        }
     113                    }
     114                    foreach ($parts as $cand) {
     115                        if ($is_valid_ip($cand, false)) {
     116                            return (string) apply_filters('dfehc_client_ip', $cand);
     117                        }
     118                    }
     119                } else {
     120                    $cand = trim($raw);
     121                    if ($is_valid_ip($cand, $public_only) || $is_valid_ip($cand, false)) {
    83122                        return (string) apply_filters('dfehc_client_ip', $cand);
    84123                    }
     
    86125            }
    87126        }
    88         return (string) apply_filters('dfehc_client_ip', $ip);
     127
     128        if (!$is_valid_ip($remote, false)) {
     129            $remote = '0.0.0.0';
     130        }
     131
     132        return (string) apply_filters('dfehc_client_ip', $remote);
    89133    }
    90134}
     
    177221        }
    178222
    179         $results['baseline_used'] = $baseline;
     223        $results['baseline_used'] = is_array($baseline);
    180224
    181225        if (is_array($baseline) && $results['method'] === 'http_loopback' && isset($results['main_response_ms'], $baseline['main_response_ms'])) {
  • dynamic-front-end-heartbeat-control/trunk/engine/system-load-fallback.php

    r3412283 r3424156  
    33
    44defined('DFEHC_SERVER_LOAD_TTL') || define('DFEHC_SERVER_LOAD_TTL', 60);
    5 defined('DFEHC_SENTINEL_NO_LOAD') || define('DFEHC_SENTINEL_NO_LOAD', 0.404);
     5defined('DFEHC_SENTINEL_NO_LOAD') || define('DFEHC_SENTINEL_NO_LOAD', -1.0);
    66defined('DFEHC_SYSTEM_LOAD_KEY') || define('DFEHC_SYSTEM_LOAD_KEY', 'dfehc_system_load_avg');
    77defined('DFEHC_CACHE_GROUP') || define('DFEHC_CACHE_GROUP', 'dfehc');
     
    4040        }
    4141        $expiration = max(1, $expiration + $jitter);
     42
    4243        if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) {
    4344            wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $expiration);
    4445            return;
    4546        }
     47
    4648        set_transient($key, $value, $expiration);
    47         if (isset($GLOBALS['wpdb'])) {
    48             global $wpdb;
    49             if (!isset($wpdb->options)) {
    50                 return;
    51             }
    52             $wpdb->suppress_errors(true);
    53             $wpdb->query($wpdb->prepare("UPDATE {$wpdb->options} SET autoload='no' WHERE option_name=%s AND autoload='yes' LIMIT 1", "_transient_$key"));
    54             $wpdb->query($wpdb->prepare("UPDATE {$wpdb->options} SET autoload='no' WHERE option_name=%s AND autoload='yes' LIMIT 1", "_transient_timeout_$key"));
    55             $wpdb->suppress_errors(false);
    56         }
     49
     50        if (!isset($GLOBALS['wpdb'])) {
     51            return;
     52        }
     53        global $wpdb;
     54        if (!isset($wpdb->options)) {
     55            return;
     56        }
     57
     58        $opt_key = "_transient_$key";
     59        $opt_key_to = "_transient_timeout_$key";
     60
     61        $wpdb->suppress_errors(true);
     62
     63        $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key));
     64        if ($autoload === 'yes') {
     65            $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key, 'autoload' => 'yes'], ['%s'], ['%s', '%s']);
     66        }
     67
     68        $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to));
     69        if ($autoload_to === 'yes') {
     70            $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key_to, 'autoload' => 'yes'], ['%s'], ['%s', '%s']);
     71        }
     72
     73        $wpdb->suppress_errors(false);
    5774    }
    5875}
     
    6582            return '';
    6683        }
     84
    6785        $can_proc = function_exists('proc_open') && !in_array('proc_open', $disabled, true);
    6886        if ($can_proc) {
     
    7088            $process = @proc_open($cmd, $descriptorspec, $pipes);
    7189            if (!is_resource($process)) return '';
    72             foreach ($pipes as $p) { @stream_set_blocking($p, false); @stream_set_timeout($p, (int)ceil($timeoutSec)); }
    73             $out = ''; $err = '';
     90
     91            foreach ($pipes as $p) {
     92                @stream_set_blocking($p, false);
     93                @stream_set_timeout($p, (int) ceil($timeoutSec));
     94            }
     95
     96            $out = '';
    7497            $start = microtime(true);
    7598            $spins = 0;
     99
    76100            while (true) {
    77                 $out .= @stream_get_contents($pipes[1]) ?: '';
    78                 $err .= @stream_get_contents($pipes[2]) ?: '';
     101                $out .= (string) (@stream_get_contents($pipes[1]) ?: '');
     102                @stream_get_contents($pipes[2]);
     103
    79104                $status = @proc_get_status($process);
    80                 if (!$status || !$status['running']) break;
    81                 if ((microtime(true) - $start) > $timeoutSec) { @proc_terminate($process); break; }
     105                if (!$status || empty($status['running'])) {
     106                    break;
     107                }
     108
     109                if ((microtime(true) - $start) > $timeoutSec) {
     110                    @proc_terminate($process);
     111                    break;
     112                }
     113
    82114                $spins++;
    83115                usleep($spins > 10 ? 25000 : 10000);
    84116            }
    85             foreach ($pipes as $p) { @fclose($p); }
     117
     118            foreach ($pipes as $p) {
     119                @fclose($p);
     120            }
    86121            @proc_close($process);
     122
    87123            return trim($out);
    88124        }
     125
    89126        $can_shell = function_exists('shell_exec') && !in_array('shell_exec', $disabled, true);
    90127        if ($can_shell) {
    91128            return trim((string) @shell_exec($cmd));
    92129        }
     130
    93131        return '';
    94132    }
     
    98136    function dfehc_get_cpu_cores(): int {
    99137        static $cached = null;
    100         if ($cached !== null) return $cached;
     138        if ($cached !== null) return (int) $cached;
     139
    101140        $tkey = dfehc_scoped_key('dfehc_cpu_cores');
     141
    102142        if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) {
    103143            $oc = wp_cache_get($tkey, DFEHC_CACHE_GROUP);
     
    106146            }
    107147        }
     148
    108149        $tc = get_transient($tkey);
    109150        if ($tc !== false && (int) $tc > 0) {
    110151            return $cached = (int) $tc;
    111152        }
     153
    112154        $disabled = array_map('trim', explode(',', (string) ini_get('disable_functions')));
     155
    113156        if (PHP_OS_FAMILY !== 'Windows' && function_exists('shell_exec') && !in_array('shell_exec', $disabled, true) && !ini_get('open_basedir')) {
    114157            $val = trim((string) @shell_exec('getconf _NPROCESSORS_ONLN 2>/dev/null'));
     
    119162            }
    120163        }
     164
    121165        if (PHP_OS_FAMILY === 'Windows' && !ini_get('open_basedir')) {
    122             $csv = dfehc_exec_with_timeout('typeperf -sc 1 "\Processor(_Total)\% Processor Time" 2>NUL', 1.5);
    123             if ($csv !== '') {
    124                 $wval = dfehc_exec_with_timeout('wmic cpu get NumberOfLogicalProcessors /value 2>NUL', 1.0);
    125                 if ($wval && preg_match('/NumberOfLogicalProcessors=(\d+)/i', $wval, $m) && (int) $m[1] > 0) {
    126                     $cores = (int) $m[1];
    127                     dfehc_set_transient_noautoload($tkey, $cores, (int) apply_filters('dfehc_cpu_cores_ttl', DAY_IN_SECONDS));
    128                     return $cached = $cores;
    129                 }
    130             }
    131166            $wout = dfehc_exec_with_timeout('wmic cpu get NumberOfLogicalProcessors /value 2>NUL', 1.0);
    132             if ($wout && preg_match('/NumberOfLogicalProcessors=(\d+)/i', $wout, $m2) && (int) $m2[1] > 0) {
    133                 $cores = (int) $m2[1];
     167            if ($wout && preg_match('/NumberOfLogicalProcessors=(\d+)/i', $wout, $m) && (int) $m[1] > 0) {
     168                $cores = (int) $m[1];
    134169                dfehc_set_transient_noautoload($tkey, $cores, (int) apply_filters('dfehc_cpu_cores_ttl', DAY_IN_SECONDS));
    135170                return $cached = $cores;
    136171            }
    137172        }
     173
    138174        if (is_readable('/proc/cpuinfo')) {
    139175            $cnt = preg_match_all('/^processor/m', (string) @file_get_contents('/proc/cpuinfo'));
     
    144180            }
    145181        }
     182
    146183        dfehc_set_transient_noautoload($tkey, 1, (int) apply_filters('dfehc_cpu_cores_ttl', DAY_IN_SECONDS));
    147184        return $cached = 1;
     
    153190        $ttl = (int) apply_filters('dfehc_system_load_ttl', DFEHC_SERVER_LOAD_TTL);
    154191        $key = dfehc_scoped_key(DFEHC_SYSTEM_LOAD_KEY);
     192
     193        $as_percent = false;
     194
    155195        if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) {
    156196            $vc = wp_cache_get($key, DFEHC_CACHE_GROUP);
     
    161201            }
    162202        }
     203
    163204        $cache = get_transient($key);
    164205        if ($cache !== false) {
     
    174215        if (function_exists('dfehc_get_server_load')) {
    175216            $val = dfehc_get_server_load();
    176             if ($val !== DFEHC_SENTINEL_NO_LOAD) {
    177                 dfehc_set_transient_noautoload($key, (float) $val, $ttl);
    178                 $ratio = (float) $val;
    179                 $as_percent = (bool) apply_filters('dfehc_system_load_return_percent', false, $ratio);
    180                 return $as_percent ? (float) round($ratio * 100, 2) : $ratio;
    181             }
    182         }
    183 
    184         if (function_exists('sys_getloadavg')) {
     217            if (is_numeric($val)) {
     218                $v = (float) $val;
     219                if ($v !== (float) DFEHC_SENTINEL_NO_LOAD && $v >= 0.0) {
     220                    if ($v > 1.0) {
     221                        $raw = $v / 100.0;
     222                        $normalized_ratio = true;
     223                        $source = 'dfehc_get_server_load_percent';
     224                    } else {
     225                        $raw = $v;
     226                        $normalized_ratio = true;
     227                        $source = 'dfehc_get_server_load_ratio';
     228                    }
     229                }
     230            }
     231        }
     232
     233        if ($raw === null && function_exists('sys_getloadavg')) {
    185234            $arr = @sys_getloadavg();
    186235            if ($arr && isset($arr[0])) {
     
    255304        if ($raw === null) {
    256305            $sentinel_ttl = (int) apply_filters('dfehc_sentinel_ttl', 5);
    257             dfehc_set_transient_noautoload($key, DFEHC_SENTINEL_NO_LOAD, $sentinel_ttl);
     306            dfehc_set_transient_noautoload($key, (float) DFEHC_SENTINEL_NO_LOAD, $sentinel_ttl);
    258307            $ratio = (float) DFEHC_SENTINEL_NO_LOAD;
    259308            $as_percent = (bool) apply_filters('dfehc_system_load_return_percent', false, $ratio);
     
    273322        }
    274323
     324        if (!is_finite($ratio)) {
     325            $ratio = (float) DFEHC_SENTINEL_NO_LOAD;
     326        }
     327
    275328        dfehc_set_transient_noautoload($key, $ratio, $ttl);
     329
    276330        $as_percent = (bool) apply_filters('dfehc_system_load_return_percent', false, $ratio);
    277331        return $as_percent ? (float) round($ratio * 100, 2) : (float) $ratio;
  • dynamic-front-end-heartbeat-control/trunk/heartbeat-async.php

    r3412936 r3424156  
    99if (!defined('DFEHC_CACHE_GROUP')) define('DFEHC_CACHE_GROUP', 'dfehc');
    1010
    11 function dfehc_max_server_load(): int { static $v; if ($v === null) { $v = (int) apply_filters('dfehc_max_server_load', 85); } return $v; }
    12 function dfehc_min_interval(): int { static $v; if ($v === null) { $v = (int) apply_filters('dfehc_min_interval', 15); } return $v; }
    13 function dfehc_max_interval(): int { static $v; if ($v === null) { $v = (int) apply_filters('dfehc_max_interval', 300); } return $v; }
    14 function dfehc_fallback_interval(): int { static $v; if ($v === null) { $v = (int) apply_filters('dfehc_fallback_interval', 60); } return $v; }
     11if (!function_exists('dfehc_max_server_load')) {
     12    function dfehc_max_server_load(): int { static $v; if ($v === null) { $v = (int) apply_filters('dfehc_max_server_load', 85); } return $v; }
     13}
     14if (!function_exists('dfehc_min_interval')) {
     15    function dfehc_min_interval(): int { static $v; if ($v === null) { $v = (int) apply_filters('dfehc_min_interval', 15); } return $v; }
     16}
     17if (!function_exists('dfehc_max_interval')) {
     18    function dfehc_max_interval(): int { static $v; if ($v === null) { $v = (int) apply_filters('dfehc_max_interval', 300); } return $v; }
     19}
     20if (!function_exists('dfehc_fallback_interval')) {
     21    function dfehc_fallback_interval(): int { static $v; if ($v === null) { $v = (int) apply_filters('dfehc_fallback_interval', 60); } return $v; }
     22}
    1523
    1624if (!function_exists('dfehc_host_token')) {
    1725    function dfehc_host_token(): string
    1826    {
     27        static $t = '';
     28        if ($t !== '') return $t;
    1929        $host = @php_uname('n') ?: (defined('WP_HOME') ? WP_HOME : (function_exists('home_url') ? home_url() : 'unknown'));
    2030        $salt = defined('DB_NAME') ? (string) DB_NAME : '';
    21         return substr(md5((string) $host . $salt), 0, 10);
     31        return $t = substr(md5((string) $host . $salt), 0, 10);
    2232    }
    2333}
     
    7282        dfehc_store_lockfree($key, $value, $expiration);
    7383        global $wpdb;
     84        if (!isset($wpdb) || !is_object($wpdb) || !isset($wpdb->options)) {
     85            return;
     86        }
    7487        $opt_key = "_transient_$key";
    7588        $opt_key_to = "_transient_timeout_$key";
     
    262275    }
    263276
    264     protected function run_action(): void
     277    public function run_action(): void
    265278    {
    266279        $lock = dfehc_acquire_lock('dfehc_async_run', 30);
     
    430443add_action('dfehc_prune_logs_hook', 'dfehc_prune_server_load_logs');
    431444
    432 if (!wp_next_scheduled('dfehc_prune_logs_hook')) {
     445function dfehc_schedule_prune_logs(): void
     446{
     447    if (!function_exists('wp_next_scheduled') || !function_exists('wp_schedule_event')) {
     448        return;
     449    }
     450    if (wp_next_scheduled('dfehc_prune_logs_hook')) {
     451        return;
     452    }
    433453    $t = strtotime('today 03:00');
    434454    if ($t < time()) {
     
    437457    wp_schedule_event($t, 'dfehc_daily', 'dfehc_prune_logs_hook');
    438458}
     459
     460add_action('init', 'dfehc_schedule_prune_logs', 1);
    439461
    440462add_filter('dfehc_required_capability', function () { return 'manage_options'; });
  • dynamic-front-end-heartbeat-control/trunk/heartbeat-controller.php

    r3412970 r3424156  
    44Plugin URI: https://heartbeat.support
    55Description: An enhanced solution to optimize the performance of your WordPress website. Stabilize your website's load averages and enhance the browsing experience for visitors during high-traffic fluctuations.
    6 Version: 1.2.996.2
     6Version: 1.2.997
    77Author: Codeloghin
    88Author URI: https://codeloghin.com
     
    5959require_once DFEHC_PLUGIN_PATH . 'settings.php';
    6060
    61 function dfehc_scoped_tkey(string $base): string
    62 {
    63     if (function_exists('dfehc_scoped_key')) {
    64         return dfehc_scoped_key($base);
    65     }
    66     $bid = function_exists('get_current_blog_id') ? (string) get_current_blog_id() : '0';
    67     $host = @php_uname('n') ?: (defined('WP_HOME') ? WP_HOME : (function_exists('home_url') ? home_url() : 'unknown'));
    68     $salt = defined('DB_NAME') ? (string) DB_NAME : '';
    69     $tok = substr(md5((string) $host . $salt), 0, 10);
    70     return "{$base}_{$bid}_{$tok}";
    71 }
    72 
    73 function dfehc_set_transient_noautoload(string $key, $value, int $expiration): void
    74 {
    75     $jitter = function_exists('random_int') ? random_int(0, 5) : 0;
    76     $expiration = max(1, $expiration + $jitter);
    77     if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) {
    78         wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $expiration);
    79         return;
    80     }
    81     set_transient($key, $value, $expiration);
    82     if (isset($GLOBALS['wpdb'])) {
    83         global $wpdb;
    84         if (!isset($wpdb->options)) {
     61if (!function_exists('dfehc_scoped_tkey')) {
     62    function dfehc_scoped_tkey(string $base): string
     63    {
     64        if (function_exists('dfehc_scoped_key')) {
     65            return dfehc_scoped_key($base);
     66        }
     67        $bid = function_exists('get_current_blog_id') ? (string) get_current_blog_id() : '0';
     68        $host = @php_uname('n') ?: (defined('WP_HOME') ? WP_HOME : (function_exists('home_url') ? home_url() : 'unknown'));
     69        $salt = defined('DB_NAME') ? (string) DB_NAME : '';
     70        $tok = substr(md5((string) $host . $salt), 0, 10);
     71        return "{$base}_{$bid}_{$tok}";
     72    }
     73}
     74
     75if (!function_exists('dfehc_set_transient_noautoload')) {
     76    function dfehc_set_transient_noautoload(string $key, $value, int $expiration): void
     77    {
     78        $jitter = 0;
     79        if (function_exists('random_int')) {
     80            try {
     81                $jitter = random_int(0, 5);
     82            } catch (\Throwable $e) {
     83                $jitter = 0;
     84            }
     85        }
     86        $expiration = max(1, $expiration + $jitter);
     87
     88        if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) {
     89            wp_cache_set($key, $value, DFEHC_CACHE_GROUP, $expiration);
    8590            return;
    8691        }
    87         $opt_key_val = '_transient_' . $key;
    88         $opt_key_to  = '_transient_timeout_' . $key;
    89         $wpdb->suppress_errors(true);
    90         $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_val));
    91         if ($autoload === 'yes') {
    92             $wpdb->query($wpdb->prepare("UPDATE {$wpdb->options} SET autoload='no' WHERE option_name=%s AND autoload='yes' LIMIT 1", $opt_key_val));
    93         }
    94         $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to));
    95         if ($autoload_to === 'yes') {
    96             $wpdb->query($wpdb->prepare("UPDATE {$wpdb->options} SET autoload='no' WHERE option_name=%s AND autoload='yes' LIMIT 1", $opt_key_to));
    97         }
    98         $wpdb->suppress_errors(false);
     92
     93        set_transient($key, $value, $expiration);
     94
     95        if (isset($GLOBALS['wpdb'])) {
     96            global $wpdb;
     97            if (!isset($wpdb) || !is_object($wpdb) || !isset($wpdb->options)) {
     98                return;
     99            }
     100            $opt_key_val = '_transient_' . $key;
     101            $opt_key_to  = '_transient_timeout_' . $key;
     102            $wpdb->suppress_errors(true);
     103            $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_val));
     104            if ($autoload === 'yes') {
     105                $wpdb->query($wpdb->prepare("UPDATE {$wpdb->options} SET autoload='no' WHERE option_name=%s AND autoload='yes' LIMIT 1", $opt_key_val));
     106            }
     107            $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to));
     108            if ($autoload_to === 'yes') {
     109                $wpdb->query($wpdb->prepare("UPDATE {$wpdb->options} SET autoload='no' WHERE option_name=%s AND autoload='yes' LIMIT 1", $opt_key_to));
     110            }
     111            $wpdb->suppress_errors(false);
     112        }
    99113    }
    100114}
     
    223237        class Dfehc_Get_Recommended_Heartbeat_Interval_Async extends Heartbeat_Async
    224238        {
     239            protected $action = 'dfehc_get_recommended_interval_async';
     240
    225241            public function dispatch(): void
    226242            {
    227                 $this->action = 'dfehc_get_recommended_interval_async';
    228 
    229243                if (has_action($this->action)) {
    230244                    do_action($this->action);
     
    236250            }
    237251
    238             protected function run_action(): void
     252            public function run_action(): void
    239253            {
    240254                $lock = function_exists('dfehc_acquire_lock') ? dfehc_acquire_lock('dfehc_interval_calculation_lock', (int) DFEHC_LOCK_TTL) : null;
     
    296310{
    297311    check_ajax_referer(DFEHC_NONCE_ACTION, 'nonce');
    298     $ip = (string) ($_SERVER['REMOTE_ADDR'] ?? '0.0.0.0');
     312    $ip = function_exists('dfehc_client_ip') ? (string) dfehc_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? '0.0.0.0');
    299313    $rlk = dfehc_scoped_tkey('dfehc_rl_' . md5($ip));
    300314    $cnt = (int) get_transient($rlk);
  • dynamic-front-end-heartbeat-control/trunk/readme.txt

    r3412883 r3424156  
    33Tested up to:      6.9
    44Requires PHP:      7.2
    5 Stable tag:        1.2.996.2
     5Stable tag:        1.2.997
    66License:           GPLv2 or later
    77License URI:       https://www.gnu.org/licenses/gpl-2.0.html
     
    4848
    4949== Changelog ==
     50
     51= 1.2.997 =
     52
     53* General enhancements.
    5054
    5155= 1.2.996.2 =
  • dynamic-front-end-heartbeat-control/trunk/visitor/cookie-helper.php

    r3412283 r3424156  
    1414    ]);
    1515    $tokens = array_map(
    16         static fn(string $s): string =>
    17             '(?<![A-Za-z0-9])' . preg_quote($s, '/') . '(?![A-Za-z0-9])',
     16        static function (string $s): string {
     17            return '(?<![A-Za-z0-9])' . preg_quote($s, '/') . '(?![A-Za-z0-9])';
     18        },
    1819        $sigs
    1920    );
     
    4950    function dfehc_client_ip(): string
    5051    {
    51         $ip = (string) ($_SERVER['REMOTE_ADDR'] ?? '0.0.0.0');
    52         return (string) apply_filters('dfehc_client_ip', $ip);
     52        $remote = (string) ($_SERVER['REMOTE_ADDR'] ?? '');
     53        $remote = trim($remote);
     54
     55        $trustedCidrs = (array) apply_filters('dfehc_trusted_proxies', []);
     56        $isTrustedRemote = false;
     57        if ($remote !== '' && $trustedCidrs) {
     58            foreach ($trustedCidrs as $cidr) {
     59                if (is_string($cidr) && dfehc_ip_in_cidr($remote, $cidr)) {
     60                    $isTrustedRemote = true;
     61                    break;
     62                }
     63            }
     64        }
     65
     66        $cand = null;
     67
     68        if ($isTrustedRemote) {
     69            $headers = (array) apply_filters('dfehc_proxy_ip_headers', ['HTTP_X_FORWARDED_FOR', 'HTTP_CF_CONNECTING_IP', 'HTTP_X_REAL_IP']);
     70            foreach ($headers as $h) {
     71                if (empty($_SERVER[$h])) {
     72                    continue;
     73                }
     74                $raw = trim((string) $_SERVER[$h]);
     75                if ($raw === '') {
     76                    continue;
     77                }
     78                if ($h === 'HTTP_X_FORWARDED_FOR') {
     79                    $picked = dfehc_select_client_ip_from_xff($raw, $trustedCidrs);
     80                    if ($picked !== null) {
     81                        $cand = $picked;
     82                        break;
     83                    }
     84                    continue;
     85                }
     86                $raw = preg_replace('/%[0-9A-Za-z.\-]+$/', '', $raw);
     87                if ($raw !== '' && filter_var($raw, FILTER_VALIDATE_IP)) {
     88                    $cand = $raw;
     89                    break;
     90                }
     91            }
     92        }
     93
     94        if ($cand === null) {
     95            $cand = $remote !== '' && filter_var($remote, FILTER_VALIDATE_IP) ? $remote : '0.0.0.0';
     96        }
     97
     98        return (string) apply_filters('dfehc_client_ip', $cand);
    5399    }
    54100}
     
    73119
    74120        global $wpdb;
    75         if (!isset($wpdb->options)) {
     121        if (!isset($wpdb) || !is_object($wpdb) || !isset($wpdb->options)) {
    76122            return;
    77123        }
     
    79125        $opt_key_to = "_transient_timeout_$key";
    80126        $wpdb->suppress_errors(true);
    81         $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key));
    82         if ($autoload === 'yes') {
    83             $wpdb->update(
    84                 $wpdb->options,
    85                 ['autoload' => 'no'],
    86                 ['option_name' => $opt_key, 'autoload' => 'yes'],
    87                 ['%s'],
    88                 ['%s', '%s']
    89             );
    90         }
    91         $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to));
    92         if ($autoload_to === 'yes') {
    93             $wpdb->update(
    94                 $wpdb->options,
    95                 ['autoload' => 'no'],
    96                 ['option_name' => $opt_key_to, 'autoload' => 'yes'],
    97                 ['%s'],
    98                 ['%s', '%s']
    99             );
    100         }
    101         $wpdb->suppress_errors(false);
     127        try {
     128            $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key));
     129            if ($autoload === 'yes') {
     130                $wpdb->update(
     131                    $wpdb->options,
     132                    ['autoload' => 'no'],
     133                    ['option_name' => $opt_key, 'autoload' => 'yes'],
     134                    ['%s'],
     135                    ['%s', '%s']
     136                );
     137            }
     138            $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to));
     139            if ($autoload_to === 'yes') {
     140                $wpdb->update(
     141                    $wpdb->options,
     142                    ['autoload' => 'no'],
     143                    ['option_name' => $opt_key_to, 'autoload' => 'yes'],
     144                    ['%s'],
     145                    ['%s', '%s']
     146                );
     147            }
     148        } finally {
     149            $wpdb->suppress_errors(false);
     150        }
    102151    }
    103152}
     
    263312    if ($ip && function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_incr')) {
    264313        $rpmKey = dfehc_scoped_key('dfehc_iprpm_') . md5($ip);
    265         $rpmTtl = 60 + (function_exists('random_int') ? random_int(0, 5) : 0);
     314        $rpmTtl = 60;
     315        if (function_exists('random_int')) {
     316            try {
     317                $rpmTtl += random_int(0, 5);
     318            } catch (\Throwable $e) {
     319            }
     320        }
    266321        if (false === wp_cache_add($rpmKey, 0, $group, $rpmTtl)) {
    267322            wp_cache_set($rpmKey, (int) (wp_cache_get($rpmKey, $group) ?: 0), $group, $rpmTtl);
     
    305360    $existing = $_COOKIE[$name] ?? '';
    306361    $val = $existing;
     362
    307363    if (!preg_match('/^[A-Fa-f0-9]{32,64}$/', (string) $val)) {
    308364        if (function_exists('random_bytes')) {
    309             $val = bin2hex(random_bytes(16));
     365            try {
     366                $val = bin2hex(random_bytes(16));
     367            } catch (\Throwable $e) {
     368                $val = bin2hex(pack('H*', substr(sha1(uniqid((string) mt_rand(), true)), 0, 32)));
     369            }
    310370        } else {
    311371            $val = bin2hex(pack('H*', substr(sha1(uniqid((string) mt_rand(), true)), 0, 32)));
     
    346406
    347407    if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_incr')) {
    348         $vTtl = $lifetime + (function_exists('random_int') ? random_int(0, 5) : 0);
     408        $vTtl = $lifetime;
     409        if (function_exists('random_int')) {
     410            try {
     411                $vTtl += random_int(0, 5);
     412            } catch (\Throwable $e) {
     413            }
     414        }
    349415        if (false === wp_cache_add($scopedVisitorKey, 0, $group, $vTtl)) {
    350416            wp_cache_set($scopedVisitorKey, (int) (wp_cache_get($scopedVisitorKey, $group) ?: 0), $group, $vTtl);
     
    393459    }
    394460    if ($client) {
    395         $client->incr($scopedVisitorKey);
    396         $client->expire($scopedVisitorKey, $lifetime);
    397         return;
     461        try {
     462            $client->incr($scopedVisitorKey);
     463            $client->expire($scopedVisitorKey, $lifetime);
     464            return;
     465        } catch (\Throwable $e) {
     466        }
    398467    }
    399468
     
    412481    }
    413482    if ($mem) {
    414         $valInc = $mem->increment($scopedVisitorKey, 1);
    415         if ($valInc === false) {
    416             $mem->set($scopedVisitorKey, 1, $lifetime);
    417         } else {
    418             $mem->touch($scopedVisitorKey, $lifetime);
    419         }
    420         return;
     483        try {
     484            $valInc = $mem->increment($scopedVisitorKey, 1);
     485            if ($valInc === false) {
     486                $mem->set($scopedVisitorKey, 1, $lifetime);
     487            } else {
     488                if (method_exists($mem, 'touch')) {
     489                    $mem->touch($scopedVisitorKey, $lifetime);
     490                } else {
     491                    $mem->set($scopedVisitorKey, (int) $valInc, $lifetime);
     492                }
     493            }
     494            return;
     495        } catch (\Throwable $e) {
     496        }
    421497    }
    422498
    423499    $cnt  = (int) get_transient($scopedVisitorKey);
    424     $vTtl = $lifetime + (function_exists('random_int') ? random_int(0, 5) : 0);
     500    $vTtl = $lifetime;
     501    if (function_exists('random_int')) {
     502        try {
     503            $vTtl += random_int(0, 5);
     504        } catch (\Throwable $e) {
     505        }
     506    }
    425507    dfehc_set_transient_noautoload($scopedVisitorKey, $cnt + 1, $vTtl);
    426508}
  • dynamic-front-end-heartbeat-control/trunk/visitor/manager.php

    r3412283 r3424156  
    2424    function dfehc_set_transient_noautoload(string $key, $value, int $expiration): void {
    2525        $group = defined('DFEHC_CACHE_GROUP') ? DFEHC_CACHE_GROUP : 'dfehc';
     26
    2627        if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache()) {
    2728            if (function_exists('wp_cache_add')) {
     
    3435            return;
    3536        }
     37
    3638        set_transient($key, $value, $expiration);
     39
    3740        global $wpdb;
    38         if (!isset($wpdb->options)) return;
     41        if (!isset($wpdb) || !is_object($wpdb) || !isset($wpdb->options)) return;
    3942        $opt_key = "_transient_$key";
    4043        $opt_key_to = "_transient_timeout_$key";
    4144        $wpdb->suppress_errors(true);
    42         $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key));
    43         if ($autoload === 'yes') {
    44             $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key, 'autoload' => 'yes'], ['%s'], ['%s','%s']);
    45         }
    46         $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to));
    47         if ($autoload_to === 'yes') {
    48             $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key_to, 'autoload' => 'yes'], ['%s'], ['%s','%s']);
    49         }
    50         $wpdb->suppress_errors(false);
     45        try {
     46            $autoload = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key));
     47            if ($autoload === 'yes') {
     48                $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key, 'autoload' => 'yes'], ['%s'], ['%s','%s']);
     49            }
     50            $autoload_to = $wpdb->get_var($wpdb->prepare("SELECT autoload FROM {$wpdb->options} WHERE option_name=%s LIMIT 1", $opt_key_to));
     51            if ($autoload_to === 'yes') {
     52                $wpdb->update($wpdb->options, ['autoload' => 'no'], ['option_name' => $opt_key_to, 'autoload' => 'yes'], ['%s'], ['%s','%s']);
     53            }
     54        } finally {
     55            $wpdb->suppress_errors(false);
     56        }
    5157    }
    5258}
     
    98104        return null;
    99105    }
     106}
     107if (!function_exists('dfehc_release_lock')) {
    100108    function dfehc_release_lock($lock): void {
    101109        if ($lock instanceof WP_Lock) {
     
    105113        if (is_object($lock) && isset($lock->cache_key)) {
    106114            $group = $lock->cache_group ?? apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? DFEHC_CACHE_GROUP : 'dfehc');
    107             wp_cache_delete($lock->cache_key, $group);
     115            if (function_exists('wp_cache_delete')) {
     116                wp_cache_delete($lock->cache_key, $group);
     117            }
    108118            return;
    109119        }
     
    121131
    122132function dfehc_add_intervals(array $s): array {
    123     $s['dfehc_5_minutes'] ??= ['interval' => 300, 'display' => __('Every 5 minutes (DFEHC)', 'dfehc')];
    124     $s['dfehc_daily'] ??= ['interval' => DAY_IN_SECONDS, 'display' => __('Daily (DFEHC)', 'dfehc')];
     133    if (!isset($s['dfehc_5_minutes'])) {
     134        $s['dfehc_5_minutes'] = ['interval' => 300, 'display' => __('Every 5 minutes (DFEHC)', 'dfehc')];
     135    }
     136    if (!isset($s['dfehc_daily'])) {
     137        $s['dfehc_daily'] = ['interval' => DAY_IN_SECONDS, 'display' => __('Daily (DFEHC)', 'dfehc')];
     138    }
    125139    return $s;
    126140}
     
    129143function dfehc_schedule_user_activity_processing(): void {
    130144    $lock = dfehc_acquire_lock('dfehc_cron_sched_lock', 15);
     145    if (!$lock) {
     146        return;
     147    }
     148
    131149    $aligned = time() - time() % 300 + 300;
    132     if (!get_option('dfehc_activity_cron_scheduled') && !wp_next_scheduled('dfehc_process_user_activity')) {
    133         $ok = wp_schedule_event($aligned, 'dfehc_5_minutes', 'dfehc_process_user_activity');
    134         if ($ok) {
    135             update_option('dfehc_activity_cron_scheduled', 1, false);
    136         } else {
    137             wp_schedule_single_event($aligned, 'dfehc_process_user_activity');
    138         }
    139     }
    140     if (!wp_next_scheduled('dfehc_cleanup_user_activity')) {
    141         $args = [0, (int) apply_filters('dfehc_cleanup_batch_size', 75)];
    142         $ok2 = wp_schedule_event($aligned + 300, 'dfehc_daily', 'dfehc_cleanup_user_activity', $args);
    143         if (!$ok2) {
    144             wp_schedule_single_event($aligned + 300, 'dfehc_cleanup_user_activity', $args);
    145         }
    146     }
    147     if ($lock) {
     150
     151    try {
     152        if (!get_option('dfehc_activity_cron_scheduled') && !wp_next_scheduled('dfehc_process_user_activity')) {
     153            $ok = wp_schedule_event($aligned, 'dfehc_5_minutes', 'dfehc_process_user_activity');
     154            if ($ok && !is_wp_error($ok)) {
     155                update_option('dfehc_activity_cron_scheduled', 1, false);
     156            } else {
     157                wp_schedule_single_event($aligned, 'dfehc_process_user_activity');
     158            }
     159        }
     160
     161        if (!wp_next_scheduled('dfehc_cleanup_user_activity')) {
     162            $args = [0, (int) apply_filters('dfehc_cleanup_batch_size', 75)];
     163            $ok2 = wp_schedule_event($aligned + 300, 'dfehc_daily', 'dfehc_cleanup_user_activity', $args);
     164            if (!$ok2 || is_wp_error($ok2)) {
     165                wp_schedule_single_event($aligned + 300, 'dfehc_cleanup_user_activity', $args);
     166            }
     167        }
     168    } finally {
    148169        dfehc_release_lock($lock);
    149170    }
     
    157178    }
    158179
    159     $prev = (bool) ignore_user_abort(true);
     180    $prev = (bool) ignore_user_abort(true);
    160181
    161182    try {
     
    174195    }
    175196    global $wpdb;
     197    if (!isset($wpdb) || !is_object($wpdb) || !isset($wpdb->users)) {
     198        return;
     199    }
    176200    $meta_key = (string) apply_filters('dfehc_last_activity_meta_key', 'last_activity_time');
    177201    $batch = (int) apply_filters('dfehc_activity_processing_batch_size', 75);
     
    193217    $max_writes = (int) apply_filters('dfehc_activity_max_writes_per_run', 500);
    194218    foreach ($ids as $id) {
    195         if (!get_user_meta($id, $meta_key, true)) {
    196             update_user_meta($id, $meta_key, $now);
     219        if (!get_user_meta((int) $id, $meta_key, true)) {
     220            update_user_meta((int) $id, $meta_key, $now);
    197221            $written++;
    198222            if ($written >= $max_writes) {
     
    201225        }
    202226    }
    203     update_option($last_id_opt, end($ids), false);
     227    $last = end($ids);
     228    update_option($last_id_opt, $last ? (int) $last : (int) $last_id, false);
    204229    update_option(dfehc_scoped_key('dfehc_last_activity_cron'), time(), false);
    205230}
     
    211236    static $cache = [];
    212237    $meta_key = (string) apply_filters('dfehc_last_activity_meta_key', 'last_activity_time');
    213     $uid = get_current_user_id();
     238    $uid = (int) get_current_user_id();
     239    if ($uid <= 0) {
     240        return;
     241    }
    214242    $now = time();
    215243    $interval = (int) apply_filters('dfehc_activity_update_interval', 900);
     
    229257    }
    230258
    231     $prev = (bool) ignore_user_abort(true);
     259    $prev = (bool) ignore_user_abort(true);
    232260
    233261    if (function_exists('set_time_limit')) {
     
    237265    try {
    238266        global $wpdb;
     267        if (!isset($wpdb) || !is_object($wpdb) || !isset($wpdb->users)) {
     268            return;
     269        }
    239270        $meta_key = (string) apply_filters('dfehc_last_activity_meta_key', 'last_activity_time');
    240271        $batch_size = (int) apply_filters('dfehc_cleanup_batch_size', $batch_size);
     
    244275            $wpdb->prepare(
    245276                "SELECT ID FROM $wpdb->users WHERE ID > %d ORDER BY ID ASC LIMIT %d",
    246                 $last_id,
    247                 $batch_size
     277                (int) $last_id,
     278                (int) $batch_size
    248279            )
    249280        );
     
    257288
    258289        foreach ($ids as $id) {
     290            $id = (int) $id;
    259291            $ts = (int) get_user_meta($id, $meta_key, true);
    260292            if ($ts && $ts < $cutoff) {
     
    264296
    265297        if (count($ids) === $batch_size) {
     298            $delay = 15;
     299            if (function_exists('random_int')) {
     300                try {
     301                    $delay += random_int(0, 5);
     302                } catch (\Throwable $e) {
     303                    $delay += rand(0, 5);
     304                }
     305            } else {
     306                $delay += rand(0, 5);
     307            }
    266308            wp_schedule_single_event(
    267                 time() + 15 + (function_exists('random_int') ? random_int(0, 5) : rand(0, 5)),
     309                time() + $delay,
    268310                'dfehc_cleanup_user_activity',
    269                 [end($ids), $batch_size]
     311                [(int) end($ids), (int) $batch_size]
    270312            );
    271313        }
     
    274316
    275317    } finally {
    276 
    277         ignore_user_abort((bool) $prev);
    278 
     318        ignore_user_abort((bool) $prev);
    279319        dfehc_release_lock($lock);
    280320    }
     
    286326    $grp = apply_filters('dfehc_cache_group', defined('DFEHC_CACHE_GROUP') ? DFEHC_CACHE_GROUP : 'dfehc');
    287327    $ttl = (int) apply_filters('dfehc_total_visitors_ttl', HOUR_IN_SECONDS);
    288     $ttl += function_exists('random_int') ? random_int(0, 5) : 0;
     328    if (function_exists('random_int')) {
     329        try {
     330            $ttl += random_int(0, 5);
     331        } catch (\Throwable $e) {
     332        }
     333    }
    289334
    290335    if (function_exists('wp_using_ext_object_cache') && wp_using_ext_object_cache() && function_exists('wp_cache_incr')) {
     
    327372    }
    328373    if ($redis) {
    329         $redis->incr($key);
    330         $redis->expire($key, $ttl);
    331         return;
     374        try {
     375            $redis->incr($key);
     376            $redis->expire($key, $ttl);
     377            return;
     378        } catch (\Throwable $e) {
     379        }
    332380    }
    333381
     
    343391    }
    344392    if ($mem) {
    345         $inc = $mem->increment($key, 1);
    346         if ($inc === false) {
    347             $mem->set($key, 1, $ttl);
    348         } else {
    349             $mem->touch($key, $ttl);
    350         }
    351         return;
     393        try {
     394            $inc = $mem->increment($key, 1);
     395            if ($inc === false) {
     396                $mem->set($key, 1, $ttl);
     397            } else {
     398                if (method_exists($mem, 'touch')) {
     399                    $mem->touch($key, $ttl);
     400                } else {
     401                    $mem->set($key, (int) $inc, $ttl);
     402                }
     403            }
     404            return;
     405        } catch (\Throwable $e) {
     406        }
    352407    }
    353408
    354409    $cnt = (int) get_transient($key);
    355     if ($cnt === 0) {
    356         dfehc_set_transient_noautoload($key, 0, $ttl);
    357         $cnt = 0;
    358     }
    359410    dfehc_set_transient_noautoload($key, $cnt + 1, $ttl);
    360411}
     
    396447    }
    397448
    398     $regen_ttl = MINUTE_IN_SECONDS + (function_exists('random_int') ? random_int(0, 5) : 0);
     449    $regen_ttl = (int) MINUTE_IN_SECONDS;
     450    if (function_exists('random_int')) {
     451        try {
     452            $regen_ttl += random_int(0, 5);
     453        } catch (\Throwable $e) {
     454        }
     455    }
    399456    dfehc_set_transient_noautoload($regen_key, true, $regen_ttl);
     457
    400458    $total = dfehc_safe_cache_get('dfehc_total_visitors');
     459
    401460    $ttl = (int) apply_filters('dfehc_visitors_cache_ttl', 10 * MINUTE_IN_SECONDS);
    402     $ttl += function_exists('random_int') ? random_int(0, 5) : 0;
     461    if (function_exists('random_int')) {
     462        try {
     463            $ttl += random_int(0, 5);
     464        } catch (\Throwable $e) {
     465        }
     466    }
    403467    dfehc_set_transient_noautoload($cache_key, (int) $total, $ttl);
    404468    update_option($stale_opt, (int) $total, false);
     
    453517    if (!wp_next_scheduled('dfehc_reset_total_visitors_event')) {
    454518        $ok = wp_schedule_event($aligned + HOUR_IN_SECONDS, 'hourly', 'dfehc_reset_total_visitors_event');
    455         if (!$ok) {
     519        if (!$ok || is_wp_error($ok)) {
    456520            wp_schedule_single_event($aligned + HOUR_IN_SECONDS, 'dfehc_reset_total_visitors_event');
    457521        }
     
    473537    $ts = $aligned + HOUR_IN_SECONDS;
    474538    $ok = wp_schedule_event($ts, 'hourly', 'dfehc_reset_total_visitors_event');
    475     if (!$ok) {
     539    if (!$ok || is_wp_error($ok)) {
    476540        wp_schedule_single_event($ts, 'dfehc_reset_total_visitors_event');
    477541    }
  • dynamic-front-end-heartbeat-control/trunk/widget.php

    r3303997 r3424156  
    2626    if ($heartbeat_status === false) {
    2727        $heartbeat_status = get_option('dfehc_disable_heartbeat') ? 'Stopped' : dfehc_get_server_health_status($server_load);
    28         set_transient('dfehc_heartbeat_health_status', $heartbeat_status, 60);
     28        set_transient('dfehc_heartbeat_health_status', $heartbeat_status, 20);
    2929    }
    3030
     
    5959    }
    6060
     61    $min_interval = (int) get_option('dfehc_min_interval', 15);
     62    $max_interval = (int) get_option('dfehc_max_interval', 300);
     63    $max_response_time = (float) get_option('dfehc_max_response_time', 5.0);
     64    $ema_alpha = (float) get_option('dfehc_ema_alpha', 0.35);
     65
     66    $min_interval = max(1, $min_interval);
     67    $max_interval = max($min_interval, $max_interval);
     68    $max_response_time = max(0.1, $max_response_time);
     69    $ema_alpha = max(0.01, min(1.0, $ema_alpha));
     70
     71    $glow_rgb = '0, 204, 0';
     72    if ($status_color === 'yellow') $glow_rgb = '255, 204, 0';
     73    elseif ($status_color === 'red') $glow_rgb = '204, 0, 0';
     74    elseif ($status_color === 'black') $glow_rgb = '0, 0, 0';
     75
     76    $server_load_text = '';
     77    if (is_numeric($server_load)) {
     78        $server_load_text = (string) round((float) $server_load, 2);
     79    } elseif (is_scalar($server_load) && $server_load !== null) {
     80        $server_load_text = (string) $server_load;
     81    } else {
     82        $server_load_text = function_exists('wp_json_encode') ? (string) wp_json_encode($server_load) : (string) json_encode($server_load);
     83    }
     84
     85    $response_seconds = 0.0;
     86    if (is_numeric($server_response_time)) {
     87        $response_seconds = (float) $server_response_time;
     88    } elseif (is_array($server_response_time)) {
     89        if (isset($server_response_time['main_response_ms']) && is_numeric($server_response_time['main_response_ms'])) {
     90            $response_seconds = ((float) $server_response_time['main_response_ms']) / 1000.0;
     91        } elseif (isset($server_response_time['response_time']) && is_numeric($server_response_time['response_time'])) {
     92            $response_seconds = (float) $server_response_time['response_time'];
     93        } elseif (isset($server_response_time['response_ms']) && is_numeric($server_response_time['response_ms'])) {
     94            $response_seconds = ((float) $server_response_time['response_ms']) / 1000.0;
     95        }
     96    } elseif (is_object($server_response_time)) {
     97        $arr = (array) $server_response_time;
     98        if (isset($arr['main_response_ms']) && is_numeric($arr['main_response_ms'])) {
     99            $response_seconds = ((float) $arr['main_response_ms']) / 1000.0;
     100        } elseif (isset($arr['response_time']) && is_numeric($arr['response_time'])) {
     101            $response_seconds = (float) $arr['response_time'];
     102        } elseif (isset($arr['response_ms']) && is_numeric($arr['response_ms'])) {
     103            $response_seconds = ((float) $arr['response_ms']) / 1000.0;
     104        }
     105    }
     106    $response_seconds = max(0.0, $response_seconds);
     107    $response_display = (string) round($response_seconds, 3);
     108
     109    $recommended_interval_text = (string) round((float) $recommended_interval, 2);
     110
     111    $ajax_url = function_exists('admin_url') ? admin_url('admin-ajax.php') : '';
     112    $ajax_nonce = function_exists('wp_create_nonce') ? wp_create_nonce('dfehc_widget_stats') : '';
     113
    61114    echo "<style>
    62115        .heartbeat {
     
    87140            0%, 100% { box-shadow: 0 0 5px {$status_color}, 0 0 10px {$status_color}; }
    88141        }
     142
     143        .dfehc-pulse-wrap {
     144            --size: 30px;
     145            --pulse-color: {$status_color};
     146            --glow-rgb: {$glow_rgb};
     147            display: flex;
     148            justify-content: center;
     149            align-items: center;
     150            margin: 20px auto;
     151            width: var(--size);
     152            height: var(--size);
     153        }
     154
     155        .dfehc-pulse {
     156            position: relative;
     157            width: var(--size);
     158            height: var(--size);
     159            border-radius: 50%;
     160            background: var(--pulse-color);
     161            overflow: hidden;
     162            animation: dfehc-heartbeat 2s ease-in-out infinite, {$animation_name} 2s linear infinite;
     163        }
     164
     165        .dfehc-pulse::before {
     166            content: '';
     167            position: absolute;
     168            top: 50%;
     169            left: 50%;
     170            width: 190%;
     171            height: 190%;
     172            transform: translate(-50%, -50%) scale(0.9);
     173            border-radius: 50%;
     174            background: radial-gradient(circle, rgba(var(--glow-rgb), 0.22) 0%, rgba(var(--glow-rgb), 0.10) 34%, rgba(var(--glow-rgb), 0) 70%);
     175            pointer-events: none;
     176            opacity: 0.55;
     177            filter: blur(0.2px);
     178            animation: dfehc-glow-sync 2s ease-in-out infinite;
     179        }
     180
     181        .dfehc-spark {
     182            position: absolute;
     183            top: 50%;
     184            left: 50%;
     185            width: 4%;
     186            height: 4%;
     187            border-radius: 50%;
     188            background: radial-gradient(circle at center, #ffffff 0%, #eaffea 70%, #d4ffd4 100%);
     189            box-shadow: 0 0 12px 2px #ffffff, 0 0 24px 6px rgba(var(--glow-rgb), 0.35);
     190            transform-origin: 0 0;
     191            animation: dfehc-orbit var(--duration,6s) linear forwards, dfehc-flash var(--duration,6s) ease-in-out forwards;
     192            pointer-events: none;
     193        }
     194
     195        @keyframes dfehc-heartbeat {
     196            0%   { transform: scale(1); }
     197            25%  { transform: scale(1.1); }
     198            50%  { transform: scale(0.96); }
     199            75%  { transform: scale(1.05); }
     200            100% { transform: scale(1); }
     201        }
     202
     203        @keyframes dfehc-glow-sync {
     204            0%   { transform: translate(-50%, -50%) scale(0.92); opacity: 0.35; }
     205            18%  { transform: translate(-50%, -50%) scale(1.18); opacity: 0.90; }
     206            34%  { transform: translate(-50%, -50%) scale(1.02); opacity: 0.55; }
     207
     208            64%  { transform: translate(-50%, -50%) scale(1.12); opacity: 0.78; }
     209            100% { transform: translate(-50%, -50%) scale(0.92); opacity: 0.35; }
     210        }
     211
     212        @keyframes dfehc-orbit {
     213            from { transform: rotate(0deg) translate(var(--radius, 0px)) scale(1); }
     214            to   { transform: rotate(360deg) translate(var(--radius, 0px)) scale(1); }
     215        }
     216
     217        @keyframes dfehc-flash {
     218            0%, 95%, 100% { opacity: 0; box-shadow: 0 0 8px 2px #ffffff, 0 0 16px 4px rgba(var(--glow-rgb), 0.30); }
     219            45%, 55%      { opacity: 0.7; box-shadow: 0 0 14px 3px #ffffff, 0 0 24px 6px rgba(var(--glow-rgb), 0.45); }
     220        }
     221
     222        .dfehc-matrix {
     223            width: 100%;
     224            max-width: 520px;
     225            margin: 14px auto 0;
     226            border-radius: 10px;
     227            border: 1px solid rgba(0,0,0,0.10);
     228            background: rgba(255,255,255,0.75);
     229            box-shadow: 0 8px 20px rgba(0,0,0,0.06);
     230            overflow: hidden;
     231            cursor: pointer;
     232            user-select: none;
     233            -webkit-tap-highlight-color: transparent;
     234            transition: transform 120ms ease, box-shadow 120ms ease, background 120ms ease, opacity 120ms ease;
     235        }
     236
     237        .dfehc-matrix:hover {
     238            box-shadow: 0 10px 24px rgba(0,0,0,0.08);
     239            transform: translateY(-1px);
     240        }
     241
     242        .dfehc-matrix:active {
     243            transform: translateY(0px) scale(0.99);
     244            opacity: 0.98;
     245        }
     246
     247        .dfehc-matrix.is-loading {
     248            opacity: 0.65;
     249        }
     250
     251        .dfehc-matrix-inner {
     252            display: grid;
     253            grid-template-columns: 1fr 1fr 1fr;
     254            gap: 0;
     255        }
     256
     257        .dfehc-matrix-cell {
     258            padding: 10px 12px;
     259            text-align: center;
     260        }
     261
     262        .dfehc-matrix-cell + .dfehc-matrix-cell {
     263            border-left: 1px solid rgba(0,0,0,0.08);
     264        }
     265        .dfehc-pulse.dfehc-ack {
     266         animation-duration: 1.35s, 1.35s;
     267        }
     268
     269        .dfehc-matrix-label {
     270            font-size: 11px;
     271            letter-spacing: 0.08em;
     272            text-transform: uppercase;
     273            color: rgba(0,0,0,0.55);
     274            line-height: 1.2;
     275            margin-bottom: 6px;
     276        }
     277
     278        .dfehc-matrix-value {
     279            font-size: 16px;
     280            font-weight: 700;
     281            color: #111;
     282            line-height: 1.2;
     283            font-variant-numeric: tabular-nums;
     284            word-break: break-word;
     285        }
     286
     287        .dfehc-matrix-unit {
     288            font-size: 11px;
     289            font-weight: 600;
     290            color: rgba(0,0,0,0.55);
     291            margin-left: 6px;
     292        }
     293
     294        @media (max-width: 520px) {
     295            .dfehc-matrix-inner {
     296                grid-template-columns: 1fr;
     297            }
     298            .dfehc-matrix-cell + .dfehc-matrix-cell {
     299                border-left: none;
     300                border-top: 1px solid rgba(0,0,0,0.08);
     301            }
     302        }
     303
     304        @media (prefers-reduced-motion: reduce) {
     305            .dfehc-pulse, .dfehc-pulse::before, .dfehc-spark { animation: none !important; }
     306            .dfehc-matrix { transition: none !important; }
     307        }
    89308    </style>";
    90309
    91     echo "<p style='text-align: center; font-size: 24px; margin-top: 20px;'>Systolic Pressure: <strong>{$server_load}</strong></p>";
    92     echo "<div style='width: 30px; height: 30px; background-color: {$status_color}; margin: 20px auto; border-radius: 50%; animation: {$animation_name} 2s infinite;'></div>";
     310    echo "<div class='dfehc-pulse-wrap'><div class='dfehc-pulse' aria-label='Heartbeat status indicator' style='margin-top: 20px;'></div></div>";
    93311    echo "<p style='text-align: center; font-size: 24px; margin-top: 20px;'>Heartbeat: <strong>{$heartbeat_status}</strong></p>";
    94312
     313    echo "<div class='dfehc-matrix' id='dfehc-matrix' role='button' tabindex='0' aria-label='Refresh current heartbeat metrics' style='margin-bottom: 20px;'>
     314            <div class='dfehc-matrix-inner'>
     315                <div class='dfehc-matrix-cell'>
     316                    <div class='dfehc-matrix-label'>Current Load</div>
     317                    <div class='dfehc-matrix-value' id='dfehc-stat-load'>{$server_load_text}</div>
     318                </div>
     319                <div class='dfehc-matrix-cell'>
     320                    <div class='dfehc-matrix-label'>Response Time</div>
     321                    <div class='dfehc-matrix-value'><span id='dfehc-stat-rt'>{$response_display}</span><span class='dfehc-matrix-unit'>s</span></div>
     322                </div>
     323                <div class='dfehc-matrix-cell'>
     324                    <div class='dfehc-matrix-label'>Interval</div>
     325                    <div class='dfehc-matrix-value'><span id='dfehc-stat-int'>{$recommended_interval_text}</span><span class='dfehc-matrix-unit'>s</span></div>
     326                </div>
     327            </div>
     328          </div>";
     329
     330    echo "<script>
     331(function() {
     332    var pulse = document.querySelector('.dfehc-pulse');
     333    var box = document.getElementById('dfehc-matrix');
     334    var elLoad = document.getElementById('dfehc-stat-load');
     335    var elRt = document.getElementById('dfehc-stat-rt');
     336    var elInt = document.getElementById('dfehc-stat-int');
     337
     338    if (pulse) {
     339        pulse.style.cursor = 'pointer';
     340        pulse.setAttribute('role', 'button');
     341        pulse.setAttribute('tabindex', '0');
     342        pulse.setAttribute('aria-label', 'Refresh heartbeat animation');
     343    }
     344
     345    window.DFEHC_METRICS = window.DFEHC_METRICS || {};
     346    window.DFEHC_METRICS.recommended_interval = " . json_encode((float) $recommended_interval) . ";
     347    window.DFEHC_METRICS.server_response_time = " . json_encode((float) $response_seconds) . ";
     348    window.DFEHC_METRICS.min_interval = " . json_encode((int) $min_interval) . ";
     349    window.DFEHC_METRICS.max_interval = " . json_encode((int) $max_interval) . ";
     350    window.DFEHC_METRICS.max_response_time = " . json_encode((float) $max_response_time) . ";
     351    window.DFEHC_METRICS.ema_alpha = " . json_encode((float) $ema_alpha) . ";
     352
     353    function clamp(v, lo, hi) {
     354        v = Number(v);
     355        if (!Number.isFinite(v)) return lo;
     356        return Math.max(lo, Math.min(hi, v));
     357    }
     358
     359    function getSeed() {
     360        try {
     361            if (window.crypto && window.crypto.getRandomValues) {
     362                var a = new Uint32Array(1);
     363                window.crypto.getRandomValues(a);
     364                return a[0] >>> 0;
     365            }
     366        } catch (e) {}
     367        return (Date.now() >>> 0) ^ ((Math.random() * 0xffffffff) >>> 0);
     368    }
     369
     370    function mulberry32(seed) {
     371        var t = seed >>> 0;
     372        return function() {
     373            t += 0x6D2B79F5;
     374            var x = t;
     375            x = Math.imul(x ^ (x >>> 15), x | 1);
     376            x ^= x + Math.imul(x ^ (x >>> 7), x | 61);
     377            return ((x ^ (x >>> 14)) >>> 0) / 4294967296;
     378        };
     379    }
     380
     381    var rng = mulberry32(getSeed());
     382    function rnd() { return rng(); }
     383
     384    function jitterFactor(pct) {
     385        var p = clamp(pct, 0, 0.75);
     386        return 1 + ((rnd() * 2 - 1) * p);
     387    }
     388
     389    var emaStress = null;
     390    function ema(next, alpha) {
     391        var a = clamp(alpha, 0.01, 1.0);
     392        if (emaStress === null) {
     393            emaStress = next;
     394            return emaStress;
     395        }
     396        emaStress = a * next + (1 - a) * emaStress;
     397        return emaStress;
     398    }
     399
     400    function getMetrics() {
     401        var m = (window.DFEHC_METRICS && typeof window.DFEHC_METRICS === 'object') ? window.DFEHC_METRICS : {};
     402        var minInterval = clamp(m.min_interval != null ? m.min_interval : 15, 1, 3600);
     403        var maxInterval = clamp(m.max_interval != null ? m.max_interval : 300, minInterval, 36000);
     404        var recInterval = clamp(m.recommended_interval != null ? m.recommended_interval : maxInterval, minInterval, maxInterval);
     405
     406        var rt = Number(m.server_response_time != null ? m.server_response_time : 0);
     407        if (!Number.isFinite(rt)) rt = 0;
     408
     409        var maxRT = clamp(m.max_response_time != null ? m.max_response_time : 5.0, 0.1, 120);
     410
     411        if (rt > (maxRT * 3) && rt <= 60000) rt = rt / 1000;
     412        rt = clamp(rt, 0, 600);
     413
     414        return { minInterval: minInterval, maxInterval: maxInterval, recInterval: recInterval, rt: rt, maxRT: maxRT };
     415    }
     416
     417    function computeStress() {
     418        var mm = getMetrics();
     419        var intervalNorm = (mm.recInterval - mm.minInterval) / Math.max(1e-9, (mm.maxInterval - mm.minInterval));
     420        intervalNorm = clamp(intervalNorm, 0, 1);
     421        var activity = clamp(1 - intervalNorm, 0, 1);
     422        var rtNorm = clamp(mm.rt / mm.maxRT, 0, 1);
     423
     424        var raw = clamp((0.65 * activity) + (0.35 * rtNorm), 0, 1);
     425        var alpha = (window.DFEHC_METRICS && window.DFEHC_METRICS.ema_alpha != null) ? window.DFEHC_METRICS.ema_alpha : 0.35;
     426        var smoothed = ema(raw, alpha);
     427
     428        return clamp(smoothed, 0, 1);
     429    }
     430
     431    function pickSpawnDelayMs(stress) {
     432        var minMs = 900;
     433        var maxMs = 9500;
     434        var base = maxMs - (stress * (maxMs - 1800));
     435        return clamp(base * jitterFactor(0.18), minMs, maxMs);
     436    }
     437
     438    function pickSparkDurationSec(stress) {
     439        var minS = 2.6;
     440        var maxS = 8.4;
     441        var base = maxS - (stress * (maxS - 3.2));
     442        return clamp(base * jitterFactor(0.12), minS, maxS);
     443    }
     444
     445    function pickRadiusPx(stress) {
     446        var w = pulse ? (pulse.clientWidth || 30) : 30;
     447        var lo = 0.15;
     448        var hi = 0.35;
     449        var bias = lo + (hi - lo) * (0.25 + 0.75 * stress);
     450        var pct = clamp(bias * jitterFactor(0.10), lo, hi);
     451        return w * pct;
     452    }
     453
     454    var sparkTimer = null;
     455    var firstTimer = null;
     456    var ackTimer = null;
     457
     458    function clearTimers() {
     459        if (sparkTimer) { window.clearTimeout(sparkTimer); sparkTimer = null; }
     460        if (firstTimer) { window.clearTimeout(firstTimer); firstTimer = null; }
     461        if (ackTimer) { window.clearTimeout(ackTimer); ackTimer = null; }
     462    }
     463
     464    function removeSparks() {
     465        if (!pulse) return;
     466        var sparks = pulse.querySelectorAll('.dfehc-spark');
     467        for (var i = 0; i < sparks.length; i++) {
     468            if (sparks[i] && sparks[i].parentNode) sparks[i].parentNode.removeChild(sparks[i]);
     469        }
     470    }
     471
     472    function restartCssAnimations() {
     473        if (!pulse) return;
     474        var prev = pulse.style.animation;
     475        pulse.style.animation = 'none';
     476        pulse.offsetHeight;
     477        pulse.style.animation = prev || '';
     478    }
     479
     480    function ackBeat() {
     481        if (!pulse) return;
     482        pulse.classList.add('dfehc-ack');
     483        if (ackTimer) window.clearTimeout(ackTimer);
     484        ackTimer = window.setTimeout(function() {
     485            if (pulse) pulse.classList.remove('dfehc-ack');
     486        }, 220);
     487    }
     488
     489    function spawnSpark() {
     490        if (!pulse) return;
     491
     492        var stress = computeStress();
     493
     494        var spark = document.createElement('span');
     495        spark.className = 'dfehc-spark';
     496
     497        var duration = pickSparkDurationSec(stress).toFixed(2) + 's';
     498        var radiusPx = pickRadiusPx(stress).toFixed(2) + 'px';
     499
     500        spark.style.setProperty('--duration', duration);
     501        spark.style.setProperty('--radius', radiusPx);
     502
     503        pulse.appendChild(spark);
     504
     505        spark.addEventListener('animationend', function() {
     506            if (spark && spark.parentNode) spark.parentNode.removeChild(spark);
     507        });
     508
     509        sparkTimer = window.setTimeout(spawnSpark, pickSpawnDelayMs(stress));
     510    }
     511
     512    function startSparks() {
     513        if (!pulse) return;
     514        clearTimers();
     515        var s = computeStress();
     516        var firstDelay = clamp((800 + (1 - s) * 2400) * jitterFactor(0.22), 400, 4200);
     517        firstTimer = window.setTimeout(spawnSpark, firstDelay);
     518    }
     519
     520    function refreshAnimation() {
     521        rng = mulberry32(getSeed());
     522        emaStress = null;
     523        removeSparks();
     524        restartCssAnimations();
     525        ackBeat();
     526        startSparks();
     527    }
     528
     529    startSparks();
     530
     531    if (pulse) {
     532        pulse.addEventListener('click', function() {
     533            refreshAnimation();
     534        });
     535
     536        pulse.addEventListener('keydown', function(e) {
     537            var k = e.key || e.keyCode;
     538            if (k === 'Enter' || k === ' ' || k === 13 || k === 32) {
     539                e.preventDefault();
     540                refreshAnimation();
     541            }
     542        });
     543    }
     544
     545    var ajaxUrl = " . json_encode((string) $ajax_url) . ";
     546    var ajaxNonce = " . json_encode((string) $ajax_nonce) . ";
     547    var inFlight = false;
     548
     549    function parseResponseSeconds(payload) {
     550        if (payload == null) return 0;
     551        if (typeof payload === 'number' && isFinite(payload)) return Math.max(0, payload);
     552        if (typeof payload === 'string') {
     553            var n = Number(payload);
     554            if (isFinite(n)) return Math.max(0, n);
     555            return 0;
     556        }
     557        if (typeof payload === 'object') {
     558            if (payload.main_response_ms != null && isFinite(Number(payload.main_response_ms))) return Math.max(0, Number(payload.main_response_ms) / 1000);
     559            if (payload.response_ms != null && isFinite(Number(payload.response_ms))) return Math.max(0, Number(payload.response_ms) / 1000);
     560            if (payload.response_time != null && isFinite(Number(payload.response_time))) return Math.max(0, Number(payload.response_time));
     561        }
     562        return 0;
     563    }
     564
     565    function parseLoad(payload) {
     566        if (payload == null) return '';
     567        if (typeof payload === 'number' && isFinite(payload)) return String(Math.round(payload * 100) / 100);
     568        if (typeof payload === 'string') {
     569            var n = Number(payload);
     570            if (isFinite(n)) return String(Math.round(n * 100) / 100);
     571            return payload;
     572        }
     573        if (typeof payload === 'object') {
     574            if (payload.load != null && isFinite(Number(payload.load))) return String(Math.round(Number(payload.load) * 100) / 100);
     575            if (payload.server_load != null && isFinite(Number(payload.server_load))) return String(Math.round(Number(payload.server_load) * 100) / 100);
     576        }
     577        try { return JSON.stringify(payload); } catch (e) { return ''; }
     578    }
     579
     580    function parseInterval(payload) {
     581        var n = Number(payload);
     582        if (isFinite(n)) return Math.max(0, n);
     583        return 0;
     584    }
     585
     586    function updateUI(data) {
     587        if (elLoad && data && data.server_load != null) elLoad.textContent = parseLoad(data.server_load);
     588        if (elRt && data && data.server_response_time != null) {
     589            var s = parseResponseSeconds(data.server_response_time);
     590            elRt.textContent = (Math.round(s * 1000) / 1000).toFixed(3);
     591            window.DFEHC_METRICS.server_response_time = s;
     592        }
     593        if (elInt && data && data.recommended_interval != null) {
     594            var iv = parseInterval(data.recommended_interval);
     595            elInt.textContent = (Math.round(iv * 100) / 100).toFixed(2);
     596            window.DFEHC_METRICS.recommended_interval = iv;
     597        }
     598    }
     599
     600    function refreshStats() {
     601        if (!ajaxUrl || inFlight) return;
     602        inFlight = true;
     603        if (box) box.classList.add('is-loading');
     604
     605        var fd = new FormData();
     606        fd.append('action', 'dfehc_widget_refresh_stats');
     607        fd.append('_ajax_nonce', ajaxNonce);
     608
     609        fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', body: fd })
     610            .then(function(r) { return r.json(); })
     611            .then(function(json) {
     612                if (json && json.success && json.data) updateUI(json.data);
     613            })
     614            .catch(function() {})
     615            .finally(function() {
     616                inFlight = false;
     617                if (box) box.classList.remove('is-loading');
     618            });
     619    }
     620
     621    function onActivate(e) {
     622        if (e && e.type === 'keydown') {
     623            var k = e.key || e.keyCode;
     624            if (k !== 'Enter' && k !== ' ' && k !== 13 && k !== 32) return;
     625            e.preventDefault();
     626        }
     627        refreshStats();
     628    }
     629
     630    if (box) {
     631        box.addEventListener('click', onActivate);
     632        box.addEventListener('keydown', onActivate);
     633    }
     634})();
     635</script>";
    95636    $labels = [];
    96637    $data = [];
     
    103644        foreach ($load_logs as $log) {
    104645            if ($log['timestamp'] >= $timestamp && $log['timestamp'] < ($timestamp + $interval)) {
    105                 $load_sum += $log['load'];
     646                $load_sum += is_numeric($log['load']) ? (float) $log['load'] : 0;
    106647                $count++;
    107648            }
     
    148689    </script>';
    149690}
     691add_action('wp_ajax_dfehc_widget_refresh_stats', function() {
     692    if (!current_user_can('manage_options')) {
     693        wp_send_json_error(['message' => 'forbidden'], 403);
     694    }
     695    check_ajax_referer('dfehc_widget_stats');
     696
     697    $server_load = dfehc_get_server_load();
     698    $server_response_time = dfehc_get_server_response_time();
     699    $recommended_interval = dfehc_calculate_recommended_interval_user_activity($server_load);
     700
     701    $response_seconds = 0.0;
     702    if (is_numeric($server_response_time)) {
     703        $response_seconds = (float) $server_response_time;
     704    } elseif (is_array($server_response_time)) {
     705        if (isset($server_response_time['main_response_ms']) && is_numeric($server_response_time['main_response_ms'])) {
     706            $response_seconds = ((float) $server_response_time['main_response_ms']) / 1000.0;
     707        } elseif (isset($server_response_time['response_ms']) && is_numeric($server_response_time['response_ms'])) {
     708            $response_seconds = ((float) $server_response_time['response_ms']) / 1000.0;
     709        } elseif (isset($server_response_time['response_time']) && is_numeric($server_response_time['response_time'])) {
     710            $response_seconds = (float) $server_response_time['response_time'];
     711        }
     712    } elseif (is_object($server_response_time)) {
     713        $arr = (array) $server_response_time;
     714        if (isset($arr['main_response_ms']) && is_numeric($arr['main_response_ms'])) {
     715            $response_seconds = ((float) $arr['main_response_ms']) / 1000.0;
     716        } elseif (isset($arr['response_ms']) && is_numeric($arr['response_ms'])) {
     717            $response_seconds = ((float) $arr['response_ms']) / 1000.0;
     718        } elseif (isset($arr['response_time']) && is_numeric($arr['response_time'])) {
     719            $response_seconds = (float) $arr['response_time'];
     720        }
     721    }
     722    $response_seconds = max(0.0, $response_seconds);
     723
     724    wp_send_json_success([
     725        'server_load' => $server_load,
     726        'server_response_time' => $response_seconds,
     727        'recommended_interval' => (float) $recommended_interval,
     728    ]);
     729});
     730
    150731
    151732function dfehc_add_heartbeat_health_dashboard_widget() {
Note: See TracChangeset for help on using the changeset viewer.