| 1 |
<?php |
| 2 |
/** |
| 3 |
* Threshold alerts for WP-Memory-Usage |
| 4 |
* - This does NOT attribute memory usage to a specific plugin. It records the request context where the peak happened. |
| 5 |
*/ |
| 6 |
if ( ! defined( 'ABSPATH' ) ) { exit; } |
| 7 |
if ( ! class_exists( 'WPMU_Threshold_Alerts' ) ) : |
| 8 |
final class WPMU_Threshold_Alerts { |
| 9 |
const OPTION_KEY = 'wpmu_threshold_alerts'; |
| 10 |
const CAP = 'manage_options'; |
| 11 |
const DIGEST_HOOK = 'wpmu_daily_digest'; |
| 12 |
#private static $nolog_admin = FALSE; |
| 13 |
private static $event_anz_display = 10; |
| 14 |
private static $event_anz_store = 10; |
| 15 |
const SETTING_COLOR_BACK = "#e7f3ff"; |
| 16 |
const SETTING_COLOR_FONT = "#0c5d9e"; |
| 17 |
const SETTING_COLOR_BORDER = "#3498db"; |
| 18 |
|
| 19 |
|
| 20 |
const WPMU_LOG_FILE = "wpmu-log.cgi"; |
| 21 |
const WPMU_LOG_PATH = ABSPATH. '../logs/wpmu/'; |
| 22 |
private static $log_path = ""; |
| 23 |
|
| 24 |
|
| 25 |
public static function init($event_anz_display, $event_anz_store, $log_path) { |
| 26 |
if (is_admin()) { |
| 27 |
add_action( 'admin_menu', array( __CLASS__, 'add_admin_menu' ) ); |
| 28 |
add_action( 'admin_init', array( __CLASS__, 'register_settings' ) ); |
| 29 |
add_action( 'admin_bar_menu', array( __CLASS__, 'admin_bar_node' ), 100 ); |
| 30 |
self::$event_anz_display = $event_anz_display; |
| 31 |
} |
| 32 |
self::$log_path = $log_path; |
| 33 |
self::$event_anz_store = $event_anz_store; |
| 34 |
add_action('plugins_loaded', function () { |
| 35 |
register_shutdown_function(array( __CLASS__, 'check_thresholds_on_shutdown' )); |
| 36 |
}); |
| 37 |
# add_action( self::DIGEST_HOOK, array( __CLASS__, 'send_daily_digest' ) ); |
| 38 |
add_filter( 'cron_schedules', array( __CLASS__, 'add_cron_intervals' ) ); |
| 39 |
add_action( 'wpmu_cleanup_hook', array( __CLASS__, 'cron_cleanup' ) ); |
| 40 |
// Cron planen – Intervall kommt aus Settings |
| 41 |
self::reschedule_cron(); |
| 42 |
|
| 43 |
register_activation_hook( dirname( __FILE__ ) . '/../wp-memory-usage.php', array( __CLASS__, 'activate' ) ); |
| 44 |
register_deactivation_hook( dirname( __FILE__ ) . '/../wp-memory-usage.php', array( __CLASS__, 'deactivate' ) ); |
| 45 |
} |
| 46 |
|
| 47 |
public static function reschedule_cron() { |
| 48 |
$opts = self::get_settings(); |
| 49 |
$interval_min = isset( $opts['rotate_log'] ) ? (int) $opts['rotate_log'] : 60; |
| 50 |
#$interval_sec = $interval_min * 60; |
| 51 |
$hook = 'wpmu_cleanup_hook'; |
| 52 |
$next = wp_next_scheduled( $hook ); |
| 53 |
$schedule = wp_get_schedule( $hook ); |
| 54 |
$expected = 'wpmu_every_' . $interval_min . '_min'; |
| 55 |
if ( ! $next || $schedule !== $expected ) { |
| 56 |
if ( $next ) { |
| 57 |
wp_unschedule_event( $next, $hook ); |
| 58 |
} |
| 59 |
wp_schedule_event( time(), $expected, $hook ); |
| 60 |
} |
| 61 |
} |
| 62 |
|
| 63 |
public static function add_cron_intervals( $schedules ) { |
| 64 |
$opts = self::get_settings(); |
| 65 |
$interval_min = isset( $opts['rotate_log'] ) ? (int) $opts['rotate_log'] : 60; |
| 66 |
$schedules[ 'wpmu_every_' . $interval_min . '_min' ] = array( |
| 67 |
'interval' => $interval_min * 60, |
| 68 |
#'display' => sprintf( 'Every %d minutes (WPMU)', $interval_min ), |
| 69 |
/* translators: %d: number of minutes */ |
| 70 |
'display' => sprintf( __( 'Every %d minutes (WPMU)', 'wp-memory-usage' ), $interval_min ), |
| 71 |
); |
| 72 |
return $schedules; |
| 73 |
} |
| 74 |
|
| 75 |
public static function activate() {} |
| 76 |
|
| 77 |
public static function deactivate() { |
| 78 |
$ts = wp_next_scheduled( 'wpmu_cleanup_hook' ); |
| 79 |
if ( $ts ) { |
| 80 |
wp_unschedule_event( $ts, 'wpmu_cleanup_hook' ); |
| 81 |
} |
| 82 |
} |
| 83 |
|
| 84 |
public static function cron_cleanup() { |
| 85 |
$dir = self::WPMU_LOG_PATH; |
| 86 |
$file = $dir . self::WPMU_LOG_FILE; |
| 87 |
|
| 88 |
$ts = wp_date("Y-m-d-H-i-s"); |
| 89 |
$backup = $dir . 'wpmu-log_' . $ts . '.cgibak'; |
| 90 |
|
| 91 |
global $wp_filesystem; |
| 92 |
if ( ! function_exists( 'WP_Filesystem' ) ) { |
| 93 |
require_once ABSPATH . 'wp-admin/includes/file.php'; |
| 94 |
} |
| 95 |
WP_Filesystem(); |
| 96 |
$wp_filesystem->move( $file, $backup, true ); // true = overwrite falls Ziel existiert |
| 97 |
#rename( $file, $backup ); # first rename, close this file |
| 98 |
|
| 99 |
#error_log("cron_cleanup: $file"); |
| 100 |
#if ( file_exists( $file )) { |
| 101 |
if ( file_exists( $backup )) { |
| 102 |
$opts = self::get_complete_file( basename( $backup ) ); |
| 103 |
|
| 104 |
$aggregation = array( |
| 105 |
'status' => array( 'ok' => 0, 'warn' => 0, 'danger' => 0, 'critical' => 0 ), |
| 106 |
'type' => array(), |
| 107 |
'uri' => array(), |
| 108 |
'interval' => array( |
| 109 |
'first' => PHP_INT_MAX, |
| 110 |
'last' => 0, |
| 111 |
'per_minute' => array(), |
| 112 |
), |
| 113 |
); |
| 114 |
|
| 115 |
foreach ( $opts as $e1 ) { |
| 116 |
$s = (string) ( $e1['s'] ?? 'ok' ); |
| 117 |
$t = (int) ( $e1['t'] ?? 0 ); |
| 118 |
$u = (int) ( $e1['u'] ?? 0 ); |
| 119 |
$c = is_array( $e1['c'] ) ? $e1['c'] : array(); |
| 120 |
$type = (string) ( $c['type'] ?? '' ); |
| 121 |
$uri = (string) ( $c['uri'] ?? '' ); |
| 122 |
|
| 123 |
// 1) Status-Häufigkeit |
| 124 |
$aggregation['status'][ $s ] = ( $aggregation['status'][ $s ] ?? 0 ) + 1; |
| 125 |
|
| 126 |
// 2) Request-Typ |
| 127 |
if ( $type ) { |
| 128 |
$aggregation['type'][ $type ] = ( $aggregation['type'][ $type ] ?? 0 ) + 1; |
| 129 |
} |
| 130 |
|
| 131 |
// 3) URI-Häufigkeit + avg/max |
| 132 |
if ( $uri ) { |
| 133 |
if ( ! isset( $aggregation['uri'][ $uri ] ) ) { |
| 134 |
$aggregation['uri'][ $uri ] = array( |
| 135 |
'total' => 0, |
| 136 |
'warn' => 0, |
| 137 |
'danger' => 0, |
| 138 |
'critical' => 0, |
| 139 |
'sum_usage' => 0, |
| 140 |
'max_usage' => 0, |
| 141 |
); |
| 142 |
} |
| 143 |
$aggregation['uri'][ $uri ]['total']++; |
| 144 |
$aggregation['uri'][ $uri ][ $s ] = ( $aggregation['uri'][ $uri ][ $s ] ?? 0 ) + 1; |
| 145 |
$aggregation['uri'][ $uri ]['sum_usage'] += $u; |
| 146 |
if ( $u > $aggregation['uri'][ $uri ]['max_usage'] ) { |
| 147 |
$aggregation['uri'][ $uri ]['max_usage'] = $u; |
| 148 |
} |
| 149 |
} |
| 150 |
|
| 151 |
// 4) Zeitintervall |
| 152 |
if ( $t < $aggregation['interval']['first'] ) { $aggregation['interval']['first'] = $t; } |
| 153 |
if ( $t > $aggregation['interval']['last'] ) { $aggregation['interval']['last'] = $t; } |
| 154 |
|
| 155 |
// 5) Ereignisse pro Minute |
| 156 |
$minute_key = wp_date( get_option('date_format') . ', ' . get_option('time_format'), (int) $t ); #date( 'Y-m-d H:i', $t ); |
| 157 |
$aggregation['interval']['per_minute'][ $minute_key ] = |
| 158 |
( $aggregation['interval']['per_minute'][ $minute_key ] ?? 0 ) + 1; |
| 159 |
} |
| 160 |
|
| 161 |
// Zeitspanne berechnen |
| 162 |
$aggregation['interval']['duration_sec'] = |
| 163 |
$aggregation['interval']['last'] - $aggregation['interval']['first']; |
| 164 |
|
| 165 |
// Mittelwert pro URI berechnen, sum_usage entfernen |
| 166 |
foreach ( $aggregation['uri'] as $uri_key => &$udata ) { |
| 167 |
$udata['avg_usage'] = $udata['total'] > 0 |
| 168 |
? (int) round( $udata['sum_usage'] / $udata['total'] ) |
| 169 |
: 0; |
| 170 |
unset( $udata['sum_usage'] ); |
| 171 |
} |
| 172 |
unset( $udata ); |
| 173 |
|
| 174 |
$filenamedigest = 'digest_' . $ts . '.cgibak'; |
| 175 |
#error_log("filenamedigest $filenamedigest"); |
| 176 |
self::update_file( $aggregation, $filenamedigest ); |
| 177 |
|
| 178 |
// ── Digest-E-Mail senden, wenn send_email aktiv und Alerts vorhanden ── |
| 179 |
$settings_for_mail = self::get_settings(); |
| 180 |
if ( |
| 181 |
! empty( $settings_for_mail['send_email'] ) && |
| 182 |
is_email( $settings_for_mail['email_to'] ) && |
| 183 |
( |
| 184 |
( $aggregation['status']['warn'] ?? 0 ) > 0 || |
| 185 |
( $aggregation['status']['danger'] ?? 0 ) > 0 || |
| 186 |
( $aggregation['status']['critical'] ?? 0 ) > 0 |
| 187 |
) |
| 188 |
) { |
| 189 |
self::send_digest_email( $settings_for_mail['email_to'], $aggregation, $filenamedigest ); |
| 190 |
} |
| 191 |
|
| 192 |
} // end if ( file_exists( $backup ) ) |
| 193 |
|
| 194 |
// Alte Backups löschen |
| 195 |
$opts = self::get_settings(); |
| 196 |
$del_min = isset( $opts['del_frequency_logfiles'] ) ? (int) $opts['del_frequency_logfiles'] : 1440; |
| 197 |
#error_log("del_min: $del_min"); |
| 198 |
|
| 199 |
if ( $del_min > 0 ) { |
| 200 |
#error_log("do del"); |
| 201 |
$max_age_sec = $del_min * 60; |
| 202 |
$now = time(); |
| 203 |
foreach ( glob( $dir . '*.cgibak' ) as $bak ) { |
| 204 |
// Nur rotierte Log-Backups (wpmu-log_*.cgibak) löschen, keine Digest-Dateien |
| 205 |
#error_log("del: ".$bak); |
| 206 |
if ( strpos( basename( $bak ), 'wpmu-log_' ) === 0 ) { |
| 207 |
if ( filemtime( $bak ) < ( $now - $max_age_sec ) ) { |
| 208 |
#error_log("do del: ".$bak); |
| 209 |
wp_delete_file( $bak ); |
| 210 |
} |
| 211 |
} |
| 212 |
} |
| 213 |
} |
| 214 |
} |
| 215 |
|
| 216 |
public static function add_admin_menu() { |
| 217 |
add_options_page( |
| 218 |
esc_html__( 'Memory Threshold Alerts', 'wp-memory-usage' ), |
| 219 |
esc_html__( 'Memory Alerts', 'wp-memory-usage' ), |
| 220 |
self::CAP, |
| 221 |
'wpmu-memory-alerts', |
| 222 |
array( __CLASS__, 'render_settings_page' ) |
| 223 |
); |
| 224 |
} |
| 225 |
|
| 226 |
#### LOGGING: SAVE and LOAD : BEGIN #### |
| 227 |
private static function get_from_file($filename) { |
| 228 |
if (empty($filename)) { return NULL; } |
| 229 |
$dir = self::WPMU_LOG_PATH; |
| 230 |
|
| 231 |
global $wp_filesystem; |
| 232 |
if ( ! function_exists( 'WP_Filesystem' ) ) { |
| 233 |
require_once ABSPATH . 'wp-admin/includes/file.php'; |
| 234 |
} |
| 235 |
WP_Filesystem(); |
| 236 |
if ( ! $wp_filesystem->is_dir( $dir ) ) { |
| 237 |
$wp_filesystem->mkdir( $dir, 0755 ); |
| 238 |
} |
| 239 |
#if (!is_dir($dir)) { @mkdir($dir, 0755, true); } |
| 240 |
|
| 241 |
$file = $dir . $filename; |
| 242 |
if (!file_exists($file)) { |
| 243 |
return []; |
| 244 |
} |
| 245 |
// phpcs:disable WordPress.WP.AlternativeFunctions |
| 246 |
$fh = @fopen($file, 'rb'); |
| 247 |
if (!$fh) return []; |
| 248 |
if (!flock($fh, LOCK_SH)) { fclose($fh); return []; } |
| 249 |
$content = fgets($fh); |
| 250 |
$content = trim($content); |
| 251 |
flock($fh, LOCK_UN); |
| 252 |
fclose($fh); |
| 253 |
// phpcs:enable WordPress.WP.AlternativeFunctions |
| 254 |
return json_decode($content, TRUE); |
| 255 |
} |
| 256 |
|
| 257 |
private static function get_complete_file($filename) { |
| 258 |
if (empty($filename)) { return NULL; } |
| 259 |
$dir = self::WPMU_LOG_PATH; |
| 260 |
|
| 261 |
global $wp_filesystem; |
| 262 |
if ( ! function_exists( 'WP_Filesystem' ) ) { |
| 263 |
require_once ABSPATH . 'wp-admin/includes/file.php'; |
| 264 |
} |
| 265 |
WP_Filesystem(); |
| 266 |
if ( ! $wp_filesystem->is_dir( $dir ) ) { |
| 267 |
$wp_filesystem->mkdir( $dir, 0755 ); |
| 268 |
} |
| 269 |
#if (!is_dir($dir)) { @mkdir($dir, 0755, true); } |
| 270 |
|
| 271 |
$file = $dir . $filename; |
| 272 |
// phpcs:disable WordPress.WP.AlternativeFunctions |
| 273 |
$fh = @fopen($file, 'rb'); |
| 274 |
if (!$fh) return []; |
| 275 |
if (!flock($fh, LOCK_SH)) { fclose($fh); return []; } |
| 276 |
$content = stream_get_contents($fh); |
| 277 |
flock($fh, LOCK_UN); |
| 278 |
fclose($fh); |
| 279 |
// phpcs:enable WordPress.WP.AlternativeFunctions |
| 280 |
$lines = array_filter(explode("\n", trim($content))); |
| 281 |
$result = []; |
| 282 |
foreach ($lines as $line) { |
| 283 |
$decoded = json_decode(trim($line), true); |
| 284 |
if (is_array($decoded)) { |
| 285 |
$result[] = $decoded; |
| 286 |
} |
| 287 |
} |
| 288 |
return $result; |
| 289 |
} |
| 290 |
|
| 291 |
private static function get_log() { |
| 292 |
return self::get_complete_file(self::WPMU_LOG_FILE); |
| 293 |
} |
| 294 |
|
| 295 |
private static function add_log($opts) { |
| 296 |
return self::append_file( $opts, self::WPMU_LOG_FILE); |
| 297 |
} |
| 298 |
|
| 299 |
private static function get_options_file() { |
| 300 |
$opts = self::get_log(); |
| 301 |
if ( ! is_array( $opts ) ) { $opts = array(); } |
| 302 |
$merged = array_merge( self::defaults(), $opts ); |
| 303 |
if ( isset( $merged['last_context'] ) && ! is_array( $merged['last_context'] ) ) { |
| 304 |
$merged['last_context'] = array( 'summary' => (string) $merged['last_context'] ); |
| 305 |
} |
| 306 |
if ( ! isset( $merged['ev'] ) || ! is_array( $merged['ev'] ) ) { |
| 307 |
$merged['ev'] = array(); |
| 308 |
} |
| 309 |
if ( ! isset( $merged['fingerprint_sent'] ) || ! is_array( $merged['fingerprint_sent'] ) ) { |
| 310 |
$merged['fingerprint_sent'] = array(); |
| 311 |
} |
| 312 |
return $merged; |
| 313 |
} |
| 314 |
|
| 315 |
private static function append_file( $opts, $filename ) { |
| 316 |
if (empty($filename)) { return NULL; } |
| 317 |
$dir = self::WPMU_LOG_PATH; |
| 318 |
|
| 319 |
global $wp_filesystem; |
| 320 |
if ( ! function_exists( 'WP_Filesystem' ) ) { |
| 321 |
require_once ABSPATH . 'wp-admin/includes/file.php'; |
| 322 |
} |
| 323 |
WP_Filesystem(); |
| 324 |
if ( ! $wp_filesystem->is_dir( $dir ) ) { |
| 325 |
$wp_filesystem->mkdir( $dir, 0755 ); |
| 326 |
} |
| 327 |
#if (!is_dir($dir)) { @mkdir($dir, 0755, true); } |
| 328 |
$file = $dir . $filename; |
| 329 |
$optsStr = json_encode($opts) . "\n"; |
| 330 |
// phpcs:disable WordPress.WP.AlternativeFunctions |
| 331 |
$fh = fopen($file, 'ab'); |
| 332 |
if ( $fh ) { |
| 333 |
flock($fh, LOCK_EX); |
| 334 |
fwrite($fh, $optsStr); |
| 335 |
fflush($fh); |
| 336 |
flock($fh, LOCK_UN); |
| 337 |
fclose($fh); |
| 338 |
} |
| 339 |
#file_put_contents($file, $optsStr, FILE_APPEND | LOCK_EX); |
| 340 |
// phpcs:enable WordPress.WP.AlternativeFunctions |
| 341 |
} |
| 342 |
|
| 343 |
private static function update_file( $opts, $filename ) { |
| 344 |
if (empty($filename)) { return NULL; } |
| 345 |
$dir = self::WPMU_LOG_PATH; |
| 346 |
#error_log("DIR: ".$dir); |
| 347 |
global $wp_filesystem; |
| 348 |
if ( ! function_exists( 'WP_Filesystem' ) ) { |
| 349 |
require_once ABSPATH . 'wp-admin/includes/file.php'; |
| 350 |
} |
| 351 |
WP_Filesystem(); |
| 352 |
if ( ! $wp_filesystem->is_dir( $dir ) ) { |
| 353 |
$wp_filesystem->mkdir( $dir, 0755 ); |
| 354 |
} |
| 355 |
#if (!is_dir($dir)) { @mkdir($dir, 0755, true); } |
| 356 |
$file = $dir . $filename; |
| 357 |
$optsStr = json_encode($opts); |
| 358 |
// phpcs:disable WordPress.WP.AlternativeFunctions |
| 359 |
$fh = fopen($file, 'c+'); |
| 360 |
if (!$fh) return; |
| 361 |
flock($fh, LOCK_EX); |
| 362 |
ftruncate($fh, 0); |
| 363 |
rewind($fh); |
| 364 |
fwrite($fh, $optsStr); |
| 365 |
fflush($fh); |
| 366 |
flock($fh, LOCK_UN); |
| 367 |
fclose($fh); |
| 368 |
// phpcs:enable WordPress.WP.AlternativeFunctions |
| 369 |
} |
| 370 |
|
| 371 |
private static function update_options_file( $opts ) { |
| 372 |
self::update_file($opts, self::WPMU_LOG_FILE); |
| 373 |
} |
| 374 |
|
| 375 |
private static function get_settings() { |
| 376 |
$settings = self::get_from_file("settings.cgi"); |
| 377 |
#error_log("settings: ".print_r($settings, true)); |
| 378 |
if ($settings === []) { |
| 379 |
#error_log("settings: DEF"); |
| 380 |
$settings = self::defaults(); |
| 381 |
} |
| 382 |
#error_log("Settings: ".print_r($settings, true)); |
| 383 |
return $settings; |
| 384 |
} |
| 385 |
|
| 386 |
private static function set_settings($opts) { |
| 387 |
return self::update_file($opts, "settings.cgi"); |
| 388 |
} |
| 389 |
#### LOGGING: SAVE and LOAD : END #### |
| 390 |
|
| 391 |
############################################################### |
| 392 |
private static function defaults() { |
| 393 |
return array( |
| 394 |
'warn_pct' => 70, |
| 395 |
'danger_pct' => 85, |
| 396 |
'critical_pct' => 95, |
| 397 |
'logop_warn_pct' => 0, |
| 398 |
'logop_danger_pct' => 0, |
| 399 |
'logop_critical_pct' => 0, |
| 400 |
'email_to' => get_option( 'admin_email' ), |
| 401 |
'send_email' => 0, |
| 402 |
'track_peak' => 1, |
| 403 |
'log_ajax' => 1, |
| 404 |
'log_cron' => 1, |
| 405 |
'log_rest' => 1, |
| 406 |
'log_admin' => 1, |
| 407 |
'log_favicon' => 1, |
| 408 |
'log_ok' => 0, |
| 409 |
'digest_enabled' => 1, |
| 410 |
'rotate_log' => 30, |
| 411 |
'del_frequency_logfiles' => 1440, # 1day=24*60 |
| 412 |
); |
| 413 |
} |
| 414 |
|
| 415 |
/** ---------------- Settings UI ---------------- */ |
| 416 |
#public static function error_logging($errortxt) { |
| 417 |
# error_log($errortxt); |
| 418 |
# return TRUE; |
| 419 |
#} |
| 420 |
|
| 421 |
public static function section_intro_measure() { |
| 422 |
echo esc_html__( 'Choose how memory usage should be measured. Peak memory catches short spikes during the request.', 'wp-memory-usage' ); |
| 423 |
} |
| 424 |
public static function section_intro_log() { |
| 425 |
echo esc_html__( 'Configure which request types and memory states should be logged.', 'wp-memory-usage' ); |
| 426 |
} |
| 427 |
public static function section_intro_howalert() { |
| 428 |
echo esc_html__( 'Configure when and how email alerts are sent.', 'wp-memory-usage' ); |
| 429 |
} |
| 430 |
public static function section_intro_thresholds() { |
| 431 |
echo esc_html__('Thresholds are evaluated as percentage of the effective memory limit (min of PHP and WP limits where applicable).', 'wp-memory-usage' ); |
| 432 |
} |
| 433 |
|
| 434 |
public static function sanitize_options_file( $input ) { |
| 435 |
$opts_settings = self::get_settings(); |
| 436 |
$in = is_array( $input ) ? $input : array(); |
| 437 |
$opts_settings['warn_pct'] = self::clamp_int( isset( $in['warn_pct'] ) ? $in['warn_pct'] : $opts_settings['warn_pct'], 1, 99 ); |
| 438 |
$opts_settings['danger_pct'] = self::clamp_int( isset( $in['danger_pct'] ) ? $in['danger_pct'] : $opts_settings['danger_pct'], 1, 100 ); |
| 439 |
$opts_settings['critical_pct'] = self::clamp_int( isset( $in['critical_pct'] ) ? $in['critical_pct'] : $opts_settings['critical_pct'], 1, 100 ); |
| 440 |
$opts_settings['logop_warn_pct'] = ! empty( $in['logop_warn_pct'] ) ? 1 : 0; |
| 441 |
$opts_settings['logop_danger_pct'] = ! empty( $in['logop_danger_pct'] ) ? 1 : 0; |
| 442 |
$opts_settings['logop_critical_pct'] = ! empty( $in['logop_critical_pct'] ) ? 1 : 0; |
| 443 |
if ( $opts_settings['danger_pct'] <= $opts_settings['warn_pct'] ) { |
| 444 |
$opts_settings['danger_pct'] = min( 100, $opts_settings['warn_pct'] + 5 ); |
| 445 |
} |
| 446 |
if ( $opts_settings['critical_pct'] <= $opts_settings['danger_pct'] ) { |
| 447 |
$opts_settings['critical_pct'] = min( 100, $opts_settings['danger_pct'] + 5 ); |
| 448 |
} |
| 449 |
$opts_settings['email_to'] = sanitize_email( isset( $in['email_to'] ) ? $in['email_to'] : $opts_settings['email_to'] ); |
| 450 |
$opts_settings['send_email'] = ! empty( $in['send_email'] ) ? 1 : 0; |
| 451 |
$opts_settings['track_peak'] = ! empty( $in['track_peak'] ) ? 1 : 0; |
| 452 |
$opts_settings['del_frequency_logfiles'] = self::clamp_int( isset( $in['del_frequency_logfiles'] ) ? $in['del_frequency_logfiles'] : $opts_settings['del_frequency_logfiles'], 0, 43200 ); |
| 453 |
$opts_settings['digest_enabled'] = ! empty( $in['digest_enabled'] ) ? 1 : 0; |
| 454 |
$opts_settings['rotate_log'] = self::clamp_int( isset( $in['rotate_log'] ) ? $in['rotate_log'] : $opts_settings['rotate_log'], 1, 600 ); |
| 455 |
$opts_settings['log_admin'] = ! empty( $in['log_admin'] ) ? 1 : 0; |
| 456 |
$opts_settings['log_ajax'] = ! empty( $in['log_ajax'] ) ? 1 : 0; |
| 457 |
$opts_settings['log_rest'] = ! empty( $in['log_rest'] ) ? 1 : 0; |
| 458 |
$opts_settings['log_cron'] = ! empty( $in['log_cron'] ) ? 1 : 0; |
| 459 |
$opts_settings['log_favicon'] = ! empty( $in['log_favicon'] ) ? 1 : 0; |
| 460 |
$opts_settings['log_ok'] = ! empty( $in['log_ok'] ) ? 1 : 0; |
| 461 |
self::set_settings( $opts_settings ); |
| 462 |
self::reschedule_cron(); |
| 463 |
return $opts_settings; |
| 464 |
} |
| 465 |
|
| 466 |
private static function clamp_int( $value, $min, $max ) { |
| 467 |
$v = (int) $value; |
| 468 |
if ( $v < (int) $min ) { $v = (int) $min; } |
| 469 |
if ( $v > (int) $max ) { $v = (int) $max; } |
| 470 |
return $v; |
| 471 |
} |
| 472 |
|
| 473 |
public static function render_field( $args ) { |
| 474 |
$opts = self::get_settings(); |
| 475 |
$key = isset( $args['key'] ) ? (string) $args['key'] : ''; |
| 476 |
$val = isset( $opts[ $key ] ) ? $opts[ $key ] : ''; |
| 477 |
$type = 'number'; |
| 478 |
if ( 'email_to' === $key ) { $type = 'email'; } |
| 479 |
$style = ''; |
| 480 |
if ( 'number' === $key ) { $style = ' style="width: 200px;"'; } |
| 481 |
$attrs = ''; |
| 482 |
if ( 'number' === $type ) { |
| 483 |
$attrs = 'step="1"'; |
| 484 |
if ( in_array( $key, array( 'warn_pct', 'danger_pct', 'critical_pct' ), true ) ) { |
| 485 |
$attrs .= ' min="1" max="100" size="4"'; |
| 486 |
} elseif ( in_array( $key, array( 'del_frequency_logfiles' ), true ) ) { |
| 487 |
$attrs .= ' min="0" max="43200"'; #max 30 days |
| 488 |
} elseif ( in_array( $key, array( 'rotate_log' ), true ) ) { |
| 489 |
$attrs .= ' min="1" max="600"'; |
| 490 |
} |
| 491 |
} |
| 492 |
printf( |
| 493 |
'<input type="%s" name="%s[%s]" value="%s" %s %s />', |
| 494 |
esc_attr( $type ), |
| 495 |
esc_attr( self::OPTION_KEY ), |
| 496 |
esc_attr( $key ), |
| 497 |
esc_attr( (string) $val ), |
| 498 |
esc_attr( (string) $style ), |
| 499 |
wp_kses_data( $attrs ) |
| 500 |
); |
| 501 |
$desc = ''; |
| 502 |
switch ( $key ) { |
| 503 |
case 'email_to': |
| 504 |
$desc = __( 'Email address that should receive alerts. Tip: use a shared mailbox or a ticket system address so alerts don\'t get lost. You can also use your agency\'s address if they maintain the site.', 'wp-memory-usage' ); |
| 505 |
break; |
| 506 |
case 'warn_pct': |
| 507 |
$desc = __( 'Warning threshold (in percent). Use this as an early heads‑up. Example: 70 means "warn me when a request uses 70% of the available memory". This is usually not an emergency, but it tells you where to look.', 'wp-memory-usage' ); |
| 508 |
break; |
| 509 |
case 'danger_pct': |
| 510 |
$desc = __( 'Danger threshold (in percent). Use this for "take action soon". Example: 85 means "this request consumed 85% of the available memory". Repeated hits here often lead to errors on heavy pages or imports.', 'wp-memory-usage' ); |
| 511 |
break; |
| 512 |
case 'critical_pct': |
| 513 |
$desc = __( 'Critical threshold (in percent). This is "high risk of out-of-memory errors". Example: 95 means "the request almost hit the limit". If you see this on the frontend, visitors can get 500 errors.', 'wp-memory-usage' ); |
| 514 |
break; |
| 515 |
case 'del_frequency_logfiles': |
| 516 |
$desc = __( 'Delete rotated Logfiles older than X Minutes', 'wp-memory-usage' ); |
| 517 |
break; |
| 518 |
case 'rotate_log': |
| 519 |
$desc = __( 'Rotate every X Minutes the logfile and create a digest', 'wp-memory-usage' ); |
| 520 |
break; |
| 521 |
default: |
| 522 |
break; |
| 523 |
} |
| 524 |
if ( $desc ) { |
| 525 |
echo '<br>' . esc_html( $desc ) . ''; |
| 526 |
} |
| 527 |
} |
| 528 |
|
| 529 |
public static function render_checkbox( $args ) { |
| 530 |
$opts = self::get_settings(); |
| 531 |
$key = isset( $args['key'] ) ? (string) $args['key'] : ''; |
| 532 |
$val = ! empty( $opts[ $key ] ) ? 1 : 0; |
| 533 |
printf( |
| 534 |
'<label><input type="checkbox" name="%s[%s]" value="1" %s /></label>', |
| 535 |
esc_attr( self::OPTION_KEY ), |
| 536 |
esc_attr( $key ), |
| 537 |
checked( 1, $val, false ) |
| 538 |
); |
| 539 |
$desc = ''; |
| 540 |
switch ( $key ) { |
| 541 |
case 'send_email': |
| 542 |
$desc = __( 'Enable email alerts. If disabled, the plugin still shows the status in the admin bar and stores results, but it will not send any emails.', 'wp-memory-usage' ); |
| 543 |
break; |
| 544 |
case 'track_peak': |
| 545 |
$desc = __( 'Use peak memory (recommended). The plugin measures the highest memory usage during the request. This catches short spikes (for example during image processing) that might be missed when only looking at the current value.', 'wp-memory-usage' ); |
| 546 |
break; |
| 547 |
#case 'digest_enabled': |
| 548 |
# $desc = __( 'Enable daily digest (Smart mode). You will receive one summary email per day with the most important alerts and where they happened. Useful if you don\'t want immediate emails for every event.', 'wp-memory-usage' ); |
| 549 |
# break; |
| 550 |
default: |
| 551 |
break; |
| 552 |
} |
| 553 |
if ( $desc ) { |
| 554 |
echo '<p class="description">' . esc_html( $desc ) . '</p>'; |
| 555 |
} |
| 556 |
} |
| 557 |
|
| 558 |
public static function render_select( $args ) { |
| 559 |
$opts = self::get_settings(); |
| 560 |
$key = isset( $args['key'] ) ? (string) $args['key'] : ''; |
| 561 |
$val = isset( $opts[ $key ] ) ? (string) $opts[ $key ] : ''; |
| 562 |
$options = isset( $args['options'] ) && is_array( $args['options'] ) ? $args['options'] : array(); |
| 563 |
printf( '<select name="%s[%s]">', esc_attr( self::OPTION_KEY ), esc_attr( $key ) ); |
| 564 |
foreach ( $options as $k => $label ) { |
| 565 |
printf( |
| 566 |
'<option value="%s" %s>%s</option>', |
| 567 |
esc_attr( (string) $k ), |
| 568 |
selected( $val, (string) $k, false ), |
| 569 |
esc_html( (string) $label ) |
| 570 |
); |
| 571 |
} |
| 572 |
echo '</select>'; |
| 573 |
} |
| 574 |
|
| 575 |
/** ---------------- Admin Bar ---------------- */ |
| 576 |
public static function admin_bar_node( $bar ) { |
| 577 |
if ( ! is_user_logged_in() || ! current_user_can( self::CAP ) ) { return; } |
| 578 |
$opts = self::get_settings(); |
| 579 |
#error_log("admin_bar_node: ".print_r($opts, true)); |
| 580 |
$limit = self::get_effective_memory_limit_bytes(); |
| 581 |
$usage = self::get_current_memory_bytes( ! empty( $opts['track_peak'] ) ); |
| 582 |
$state = self::state_for_usage( $usage, $limit, $opts ); |
| 583 |
$label = sprintf( |
| 584 |
'%s %s / %s', |
| 585 |
strtoupper( $state ), |
| 586 |
self::format_bytes( $usage ), |
| 587 |
$limit > 0 ? self::format_bytes( $limit ) : __( 'unlimited', 'wp-memory-usage' ) |
| 588 |
); |
| 589 |
$bar->add_node( array( |
| 590 |
'id' => 'wpmu_memory_alerts', |
| 591 |
'title' => esc_html( $label ), |
| 592 |
'href' => admin_url( 'options-general.php?page=wpmu-memory-alerts' ), |
| 593 |
'meta' => array( 'title' => 'WP Memory Threshold Alerts' ), |
| 594 |
) ); |
| 595 |
} |
| 596 |
|
| 597 |
/** ---------------- Core logic ---------------- */ |
| 598 |
public static function check_thresholds_on_shutdown() { |
| 599 |
$opts_settings = self::get_settings(); |
| 600 |
$opts = []; |
| 601 |
$limit = self::get_effective_memory_limit_bytes(); |
| 602 |
$usage = self::get_current_memory_bytes( ! empty( $opts_settings['track_peak'] ) ); |
| 603 |
$context = self::collect_context(); |
| 604 |
$now = time(); |
| 605 |
$state = self::state_for_usage( $usage, $limit, $opts_settings ); |
| 606 |
$opts['st'] = $state; |
| 607 |
$opts['pe'] = (int) $usage; |
| 608 |
$opts['li'] = (int) $limit; |
| 609 |
self::log_event( $opts, $opts_settings, $context ); |
| 610 |
} |
| 611 |
|
| 612 |
#private static function is_favicon_wpjson_call() { |
| 613 |
# $request_uri = $_SERVER['REQUEST_URI']; |
| 614 |
# return !(strpos($request_uri, '/wp-json/') !== false && strpos($request_uri, 'favicon.ico') !== false); |
| 615 |
#} |
| 616 |
|
| 617 |
private static function send_email_alert( $to, $state, $usage, $limit, $context ) { |
| 618 |
$site = wp_parse_url( home_url(), PHP_URL_HOST ); |
| 619 |
$subject = sprintf( |
| 620 |
'[%s] Memory %s (%s)', |
| 621 |
$site ? $site : 'WordPress', |
| 622 |
strtoupper( $state ), |
| 623 |
self::format_pct( $usage, $limit ) |
| 624 |
); |
| 625 |
$lines = array(); |
| 626 |
$lines[] = __('WP Memory Threshold Alert', 'wp-memory-usage' ); |
| 627 |
$lines[] = ''; |
| 628 |
$lines[] = __('State: ', 'wp-memory-usage' ) . strtoupper( $state ); |
| 629 |
$lines[] = __('Usage: ', 'wp-memory-usage' ) . self::format_bytes( $usage ); |
| 630 |
$lines[] = __('Limit: ', 'wp-memory-usage' ) . ( $limit > 0 ? self::format_bytes( $limit ) : __('unlimited', 'wp-memory-usage' ) ); |
| 631 |
$lines[] = __('Ratio: ', 'wp-memory-usage' ) . self::format_pct( $usage, $limit ); |
| 632 |
$lines[] = ''; |
| 633 |
$lines[] = __('Context:', 'wp-memory-usage' ); |
| 634 |
$lines[] = __('- Type:', 'wp-memory-usage' ) .' ' . (string) ( isset( $context['type'] ) ? $context['type'] : '' ); |
| 635 |
$lines[] = __('- Method:', 'wp-memory-usage' ).' ' . (string) ( isset( $context['method'] ) ? $context['method'] : '' ); |
| 636 |
$lines[] = __('- URI:', 'wp-memory-usage' ).' '. (string) ( isset( $context['uri'] ) ? $context['uri'] : '' ); |
| 637 |
if ( ! empty( $context['admin_screen'] ) ) { $lines[] = __('- Admin screen:', 'wp-memory-usage' ).' '. (string) $context['admin_screen']; } |
| 638 |
if ( ! empty( $context['post_id'] ) ) { $lines[] = __('- Post ID:', 'wp-memory-usage' ) .' ' . (string) $context['post_id']; } |
| 639 |
if ( ! empty( $context['ajax_action'] ) ) { $lines[] = __('- AJAX action:', 'wp-memory-usage' ) .' ' . (string) $context['ajax_action']; } |
| 640 |
if ( ! empty( $context['rest_route'] ) ) { $lines[] = __('- REST route:', 'wp-memory-usage' ).' ' . (string) $context['rest_route']; } |
| 641 |
if ( ! empty( $context['user'] ) ) { $lines[] = __('- User:', 'wp-memory-usage' ) .' ' . (string) $context['user']; } |
| 642 |
$lines[] = ''; |
| 643 |
$lines[] = __('Suggested actions:', 'wp-memory-usage' ); |
| 644 |
$lines[] = __('- Reproduce this URL/action and temporarily disable recently added/updated plugins', 'wp-memory-usage' ); |
| 645 |
$lines[] = __('- If it is an import/editor screen, try with fewer plugins active', 'wp-memory-usage' ); |
| 646 |
$lines[] = __('- Consider raising PHP memory_limit / WP_MEMORY_LIMIT if appropriate', 'wp-memory-usage' ); |
| 647 |
$lines[] = ''; |
| 648 |
$lines[] = __('Settings: ', 'wp-memory-usage' ) . admin_url( 'options-general.php?page=wpmu-memory-alerts' ); |
| 649 |
$headers = array( 'Content-Type: text/plain; charset=UTF-8' ); |
| 650 |
return (bool) wp_mail( $to, $subject, implode( "\n", $lines ), $headers ); |
| 651 |
} |
| 652 |
|
| 653 |
/** ---------------- Digest email ---------------- */ |
| 654 |
public static function send_digest_email( $to, $aggregation, $filename ) { |
| 655 |
$site = wp_parse_url( home_url(), PHP_URL_HOST ); |
| 656 |
$site = $site ? $site : 'WordPress'; |
| 657 |
$warn = (int) ( $aggregation['status']['warn'] ?? 0 ); |
| 658 |
$danger = (int) ( $aggregation['status']['danger'] ?? 0 ); |
| 659 |
$critical = (int) ( $aggregation['status']['critical'] ?? 0 ); |
| 660 |
$total = $warn + $danger + $critical + (int) ( $aggregation['status']['ok'] ?? 0 ); |
| 661 |
|
| 662 |
// Subject: höchste Severity hervorheben |
| 663 |
$severity = $critical > 0 ? __('CRITICAL','wp-memory-usage') : ( $danger > 0 ? __('DANGER','wp-memory-usage') : __('WARN','wp-memory-usage') ); |
| 664 |
$subject = sprintf( '[%s] Memory Digest %s – warn:%d danger:%d critical:%d', $site, $severity, $warn, $danger, $critical ); |
| 665 |
|
| 666 |
$lines = array(); |
| 667 |
$lines[] = __('WP Memory Usage – Digest Report', 'wp-memory-usage' ); |
| 668 |
$lines[] = str_repeat( '-', 50 ); |
| 669 |
$lines[] = __('File : ', 'wp-memory-usage' ) . $filename; |
| 670 |
$lines[] = __('Site : ', 'wp-memory-usage' ) . home_url(); |
| 671 |
$lines[] = ''; |
| 672 |
$lines[] = __('STATUS SUMMARY', 'wp-memory-usage' ); |
| 673 |
$lines[] = ' '.__('OK : ', 'wp-memory-usage' ) . (int) ( $aggregation['status']['ok'] ?? 0 ); |
| 674 |
$lines[] = ' '.__('WARN : ', 'wp-memory-usage' ) . $warn; |
| 675 |
$lines[] = ' '.__('DANGER : ', 'wp-memory-usage' ) . $danger; |
| 676 |
$lines[] = ' '.__('CRITICAL : ', 'wp-memory-usage' ) . $critical; |
| 677 |
$lines[] = ' '.__('Total : ', 'wp-memory-usage' ) . $total; |
| 678 |
$lines[] = ''; |
| 679 |
|
| 680 |
// Zeitintervall |
| 681 |
$first = $aggregation['interval']['first'] ?? 0; |
| 682 |
$last = $aggregation['interval']['last'] ?? 0; |
| 683 |
if ( $first && $last ) { |
| 684 |
$lines[] = __('TIME RANGE', 'wp-memory-usage' ); |
| 685 |
$lines[] = ' '.__('From : ', 'wp-memory-usage' ) . wp_date( 'Y-m-d H:i:s', $first ); |
| 686 |
$lines[] = ' '.__('To : ', 'wp-memory-usage' ) . wp_date( 'Y-m-d H:i:s', $last ); |
| 687 |
$dur = $last - $first; |
| 688 |
$lines[] = ' '.__('Dur : ', 'wp-memory-usage' ) . floor( $dur / 60 ) . ' min ' . ( $dur % 60 ) . ' sec'; |
| 689 |
$lines[] = ''; |
| 690 |
} |
| 691 |
|
| 692 |
// Request-Typen |
| 693 |
if ( ! empty( $aggregation['type'] ) ) { |
| 694 |
$lines[] = __('REQUEST TYPES', 'wp-memory-usage' ); |
| 695 |
foreach ( $aggregation['type'] as $type => $cnt ) { |
| 696 |
$lines[] = sprintf( ' %-12s: %d', strtoupper( $type ), $cnt ); |
| 697 |
} |
| 698 |
$lines[] = ''; |
| 699 |
} |
| 700 |
|
| 701 |
// Top-URIs mit Alerts (max. 20, sortiert nach warn+danger+critical) |
| 702 |
if ( ! empty( $aggregation['uri'] ) ) { |
| 703 |
$uris_with_alerts = array_filter( $aggregation['uri'], function ( $u ) { |
| 704 |
return ( ( $u['warn'] ?? 0 ) + ( $u['danger'] ?? 0 ) + ( $u['critical'] ?? 0 ) ) > 0; |
| 705 |
} ); |
| 706 |
uasort( $uris_with_alerts, function ( $a, $b ) { |
| 707 |
$sa = ( $a['warn'] ?? 0 ) + ( $a['danger'] ?? 0 ) * 2 + ( $a['critical'] ?? 0 ) * 3; |
| 708 |
$sb = ( $b['warn'] ?? 0 ) + ( $b['danger'] ?? 0 ) * 2 + ( $b['critical'] ?? 0 ) * 3; |
| 709 |
return $sb <=> $sa; |
| 710 |
} ); |
| 711 |
$lines[] = __('TOP URIs WITH ALERTS (warn/danger/critical | avg | max)', 'wp-memory-usage' ); |
| 712 |
$count = 0; |
| 713 |
foreach ( $uris_with_alerts as $uri => $ud ) { |
| 714 |
if ( ++$count > 20 ) { break; } |
| 715 |
$lines[] = sprintf( |
| 716 |
' %s', |
| 717 |
$uri |
| 718 |
); |
| 719 |
$lines[] = sprintf( |
| 720 |
' W:%d D:%d C:%d | total:%d | avg:%s | max:%s', |
| 721 |
$ud['warn'] ?? 0, |
| 722 |
$ud['danger'] ?? 0, |
| 723 |
$ud['critical'] ?? 0, |
| 724 |
$ud['total'] ?? 0, |
| 725 |
self::format_bytes( $ud['avg_usage'] ?? 0 ), |
| 726 |
self::format_bytes( $ud['max_usage'] ?? 0 ) |
| 727 |
); |
| 728 |
} |
| 729 |
$lines[] = ''; |
| 730 |
} |
| 731 |
|
| 732 |
$lines[] = __( 'Settings:', 'wp-memory-usage' ) . admin_url( 'options-general.php?page=wpmu-memory-alerts&tab=digest' ); |
| 733 |
$headers = array( 'Content-Type: text/plain; charset=UTF-8' ); |
| 734 |
return (bool) wp_mail( $to, $subject, implode( "\n", $lines ), $headers ); |
| 735 |
} |
| 736 |
|
| 737 |
/** ---------------- Digest ---------------- */ |
| 738 |
private static function ensure_digest_schedule( $hour ) { |
| 739 |
$hour = (int) $hour; |
| 740 |
if ( $hour < 0 || $hour > 23 ) { $hour = 8; } |
| 741 |
if ( wp_next_scheduled( self::DIGEST_HOOK ) ) { return; } |
| 742 |
$now = current_time( 'timestamp' ); |
| 743 |
$run = strtotime( gmdate( 'Y-m-d', $now ) . sprintf( ' %02d:00:00', $hour ) ); |
| 744 |
if ( $run <= $now ) { $run = strtotime( '+1 day', $run ); } |
| 745 |
wp_schedule_event( $run, 'daily', self::DIGEST_HOOK ); |
| 746 |
} |
| 747 |
|
| 748 |
/** ---------------- Context capture ---------------- */ |
| 749 |
private static function collect_context() { |
| 750 |
$uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; |
| 751 |
#$method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( (string) $_SERVER['REQUEST_METHOD'] ) : ''; |
| 752 |
$method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) : ''; |
| 753 |
$is_ajax = function_exists( 'wp_doing_ajax' ) && wp_doing_ajax(); |
| 754 |
$is_cron = function_exists( 'wp_doing_cron' ) && wp_doing_cron(); |
| 755 |
$is_rest = ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || ( '' !== $uri && false !== strpos( $uri, '/wp-json/' ) ); |
| 756 |
$is_admin = is_admin(); |
| 757 |
$type = $is_cron ? 'cron' : ( $is_ajax ? 'ajax' : ( $is_rest ? 'rest' : ( $is_admin ? 'admin' : 'front' ) ) ); |
| 758 |
$ajax_action = ''; |
| 759 |
// phpcs:disable WordPress.Security.NonceVerification.Recommended |
| 760 |
if ( $is_ajax && isset( $_REQUEST['action'] ) ) { |
| 761 |
$ajax_action = sanitize_key( wp_unslash( $_REQUEST['action'] ) ); |
| 762 |
} |
| 763 |
// phpcs:enable WordPress.Security.NonceVerification.Recommended |
| 764 |
$rest_route = ''; |
| 765 |
if ( $is_rest ) { |
| 766 |
if ( isset( $GLOBALS['wp'] ) && is_object( $GLOBALS['wp'] ) && isset( $GLOBALS['wp']->query_vars['rest_route'] ) ) { |
| 767 |
$rest_route = (string) $GLOBALS['wp']->query_vars['rest_route']; |
| 768 |
} else { |
| 769 |
$rest_route = $uri; |
| 770 |
} |
| 771 |
} |
| 772 |
$admin_screen = ''; |
| 773 |
if ( $is_admin && function_exists( 'get_current_screen' ) ) { |
| 774 |
$screen = get_current_screen(); |
| 775 |
if ( $screen && ! empty( $screen->id ) ) { |
| 776 |
$admin_screen = (string) $screen->id; |
| 777 |
} |
| 778 |
} |
| 779 |
global $post; |
| 780 |
$post_id = isset( $post->ID ) ? (string) $post->ID : 'unknown'; |
| 781 |
$user_str = ''; |
| 782 |
$user = wp_get_current_user(); |
| 783 |
if ( $user && $user->exists() ) { |
| 784 |
$roles = is_array( $user->roles ) ? implode( ',', $user->roles ) : ''; |
| 785 |
$user_str = sprintf( '%s (#%d)%s', $user->user_login, (int) $user->ID, $roles ? ' roles:' . $roles : '' ); |
| 786 |
} |
| 787 |
return array( |
| 788 |
'type' => $type, |
| 789 |
'method' => $method, |
| 790 |
'uri' => $uri, |
| 791 |
'admin_screen' => $admin_screen, |
| 792 |
'post_id' => $post_id ? $post_id : '', |
| 793 |
'ajax_action' => $ajax_action, |
| 794 |
'rest_route' => $rest_route, |
| 795 |
'user' => $user_str, |
| 796 |
'is_admin' => $is_admin ? 1 : 0, |
| 797 |
'is_ajax' => $is_ajax ? 1 : 0, |
| 798 |
'is_cron' => $is_cron ? 1 : 0, |
| 799 |
'is_rest' => $is_rest ? 1 : 0, |
| 800 |
); |
| 801 |
} |
| 802 |
|
| 803 |
/** ---------------- set limits ---------------- */ |
| 804 |
private static function log_event( &$opts, $opts_settings, $ctx ) { |
| 805 |
if ( ( ! $opts_settings["log_admin"] ) && ( $ctx['is_admin'] ) ) { |
| 806 |
#error_log("NO LOG admin"); |
| 807 |
return NULL; |
| 808 |
} |
| 809 |
if ( ( ! $opts_settings["log_ok"] ) && ( $opts['st'] === "ok" ) ) { |
| 810 |
return NULL; |
| 811 |
} |
| 812 |
if ( ( ! $opts_settings["logop_warn_pct"] ) && ( $opts['st'] === "warn" ) ) { |
| 813 |
#error_log("NO LOG warn"); |
| 814 |
return NULL; |
| 815 |
} |
| 816 |
if ( ( ! $opts_settings["logop_danger_pct"] ) && ( $opts['st'] === "danger" ) ) { |
| 817 |
#error_log("NO LOG danger"); |
| 818 |
return NULL; |
| 819 |
} |
| 820 |
if ( ( ! $opts_settings["logop_critical_pct"] ) && ( $opts['st'] === "critical" ) ) { |
| 821 |
#error_log("NO LOG critical"); |
| 822 |
return NULL; |
| 823 |
} |
| 824 |
if ( ( ! $opts_settings["log_rest"] ) && ( $ctx['is_rest'] ) ) { return NULL; } |
| 825 |
if ( ( ! $opts_settings["log_ajax"] ) && ( $ctx['is_ajax'] ) ) { return NULL; } |
| 826 |
if ( ( ! $opts_settings["log_cron"] ) && ( $ctx['is_cron'] ) ) { return NULL; } |
| 827 |
if ( preg_match("/favicon.ico/", $ctx['uri']) ) { |
| 828 |
#error_log("NO LOG favicon"); |
| 829 |
return TRUE; |
| 830 |
} |
| 831 |
$optsout = array( |
| 832 |
't' => time(), |
| 833 |
's' => (string) $opts['st'], |
| 834 |
'u' => (int) $opts['pe'], |
| 835 |
'l' => (int) $opts['li'], |
| 836 |
'c' => array( |
| 837 |
'type' => isset( $ctx['type'] ) ? (string) $ctx['type'] : '', |
| 838 |
'uri' => isset( $ctx['uri'] ) ? (string) $ctx['uri'] : '', |
| 839 |
'admin_screen' => isset( $ctx['admin_screen'] ) ? (string) $ctx['admin_screen'] : '', |
| 840 |
'ajax_action' => isset( $ctx['ajax_action'] ) ? (string) $ctx['ajax_action'] : '', |
| 841 |
'rest_route' => isset( $ctx['rest_route'] ) ? (string) $ctx['rest_route'] : '', |
| 842 |
'user' => isset( $ctx['user'] ) ? (string) $ctx['user'] : '', |
| 843 |
'is_admin' => ! empty( $ctx['is_admin'] ) ? 1 : 0, |
| 844 |
), |
| 845 |
); |
| 846 |
self::add_log( $optsout ); |
| 847 |
} |
| 848 |
|
| 849 |
############################################################# |
| 850 |
# DONE |
| 851 |
############################################################# |
| 852 |
|
| 853 |
### UI |
| 854 |
public static function register_settings() { |
| 855 |
register_setting( |
| 856 |
'wpmu_threshold_settings', |
| 857 |
self::OPTION_KEY, |
| 858 |
array( __CLASS__, 'sanitize_options_file' ) |
| 859 |
); |
| 860 |
// Thresholds |
| 861 |
add_settings_section( |
| 862 |
'wpmu_main', |
| 863 |
'<h2 style="background-color: '.self::SETTING_COLOR_BACK.'; padding: 10px; color: '.self::SETTING_COLOR_FONT.'; border: 2px solid '.self::SETTING_COLOR_BORDER.';">' |
| 864 |
. esc_html__( 'Thresholds', 'wp-memory-usage' ) . "</h2>", |
| 865 |
array( __CLASS__, 'section_intro_thresholds' ), |
| 866 |
'wpmu-memory-alerts' |
| 867 |
); |
| 868 |
#$fields = array( |
| 869 |
# 'warn_pct' => esc_html__( 'Warning threshold', 'wp-memory-usage' ), |
| 870 |
# 'danger_pct' => esc_html__( 'Danger threshold', 'wp-memory-usage' ), |
| 871 |
# 'critical_pct' => esc_html__( 'Critical threshold', 'wp-memory-usage' ), |
| 872 |
#); |
| 873 |
$fields = array( |
| 874 |
'warn_pct' => array( |
| 875 |
'label' => esc_html__( 'Warning threshold', 'wp-memory-usage' ), |
| 876 |
'log_label' => esc_html__( 'Log Warning', 'wp-memory-usage' ), |
| 877 |
), |
| 878 |
'danger_pct' => array( |
| 879 |
'label' => esc_html__( 'Danger threshold', 'wp-memory-usage' ), |
| 880 |
'log_label' => esc_html__( 'Log Danger', 'wp-memory-usage' ), |
| 881 |
), |
| 882 |
'critical_pct' => array( |
| 883 |
'label' => esc_html__( 'Critical threshold', 'wp-memory-usage' ), |
| 884 |
'log_label' => esc_html__( 'Log Critical', 'wp-memory-usage' ), |
| 885 |
), |
| 886 |
); |
| 887 |
#foreach ( $fields as $key => $label ) { |
| 888 |
foreach ( $fields as $key => $field ) { |
| 889 |
add_settings_field( |
| 890 |
$key, |
| 891 |
#esc_html( $label . " %" ), |
| 892 |
esc_html( $field['label'] . ' %' ), |
| 893 |
array( __CLASS__, 'render_field' ), |
| 894 |
'wpmu-memory-alerts', |
| 895 |
'wpmu_main', |
| 896 |
array( 'key' => $key ) |
| 897 |
); |
| 898 |
add_settings_field( |
| 899 |
( "logop_" . $key ), |
| 900 |
$field['log_label'], |
| 901 |
#esc_html__( 'Log ' . $fields[ $key ], 'wp-memory-usage' ), |
| 902 |
array( __CLASS__, 'render_checkbox' ), |
| 903 |
'wpmu-memory-alerts', |
| 904 |
'wpmu_main', |
| 905 |
array( 'key' => ( "logop_" . $key ) ) |
| 906 |
); |
| 907 |
} |
| 908 |
// How to measure |
| 909 |
add_settings_section( |
| 910 |
'wpmu_measure', |
| 911 |
'<h2 style="background-color: '.self::SETTING_COLOR_BACK.'; padding: 10px; color: '.self::SETTING_COLOR_FONT.'; border: 2px solid '.self::SETTING_COLOR_BORDER.';">' |
| 912 |
. esc_html__( 'How to measure', 'wp-memory-usage' ) . "</h2>", |
| 913 |
array( __CLASS__, 'section_intro_measure' ), |
| 914 |
'wpmu-memory-alerts' |
| 915 |
); |
| 916 |
add_settings_field( |
| 917 |
'track_peak', |
| 918 |
esc_html__( 'Use peak memory (recommended)', 'wp-memory-usage' ), |
| 919 |
array( __CLASS__, 'render_checkbox' ), |
| 920 |
'wpmu-memory-alerts', |
| 921 |
'wpmu_measure', |
| 922 |
array( 'key' => 'track_peak' ) |
| 923 |
); |
| 924 |
// Logging |
| 925 |
add_settings_section( |
| 926 |
'wpmu_logging', |
| 927 |
'<h2 style="background-color: '.self::SETTING_COLOR_BACK.'; padding: 10px; color: '.self::SETTING_COLOR_FONT.'; border: 2px solid '.self::SETTING_COLOR_BORDER.';">' |
| 928 |
. esc_html__( 'Logging', 'wp-memory-usage' ) . "</h2>", |
| 929 |
array( __CLASS__, 'section_intro_log' ), |
| 930 |
'wpmu-memory-alerts' |
| 931 |
); |
| 932 |
|
| 933 |
|
| 934 |
$log_fields = array( |
| 935 |
'log_ajax' => esc_html__( 'Log Ajax', 'wp-memory-usage' ), |
| 936 |
'log_rest' => esc_html__( 'Log Rest', 'wp-memory-usage' ), |
| 937 |
'log_admin' => esc_html__( 'Log Admin', 'wp-memory-usage' ), |
| 938 |
'log_cron' => esc_html__( 'Log Cron', 'wp-memory-usage' ), |
| 939 |
'log_favicon' => esc_html__( 'Log favicon.ico', 'wp-memory-usage' ), |
| 940 |
'log_ok' => esc_html__( 'Log OK', 'wp-memory-usage' ), |
| 941 |
); |
| 942 |
|
| 943 |
foreach ( $log_fields as $logkey => $label ) { |
| 944 |
|
| 945 |
|
| 946 |
|
| 947 |
#foreach ( array( 'log_ajax', 'log_rest', 'log_admin', 'log_cron', 'log_favicon', 'log_ok' ) as $logkey ) { |
| 948 |
#$labels = array( |
| 949 |
# 'log_ajax' => 'Log Ajax', |
| 950 |
# 'log_rest' => 'Log Rest', |
| 951 |
# 'log_admin' => 'Log Admin', |
| 952 |
# 'log_cron' => 'Log Cron', |
| 953 |
# 'log_favicon' => 'Log favicon.ico', |
| 954 |
# 'log_ok' => 'Log OK', |
| 955 |
#); |
| 956 |
add_settings_field( |
| 957 |
$logkey, |
| 958 |
#esc_html__( $labels[ $logkey ], 'wp-memory-usage' ), |
| 959 |
$label, |
| 960 |
array( __CLASS__, 'render_checkbox' ), |
| 961 |
'wpmu-memory-alerts', |
| 962 |
'wpmu_logging', |
| 963 |
array( 'key' => $logkey ) |
| 964 |
); |
| 965 |
} |
| 966 |
// Alert settings |
| 967 |
add_settings_section( |
| 968 |
'wpmu_alertsettings', |
| 969 |
'<h2 style="background-color: '.self::SETTING_COLOR_BACK.'; padding: 10px; color: '.self::SETTING_COLOR_FONT.'; border: 2px solid '.self::SETTING_COLOR_BORDER.';">' |
| 970 |
. esc_html__( 'How to alert', 'wp-memory-usage' ) . "</h2>", |
| 971 |
array( __CLASS__, 'section_intro_howalert' ), |
| 972 |
'wpmu-memory-alerts' |
| 973 |
); |
| 974 |
add_settings_field( 'email_to', esc_html__('Alert email recipient','wp-memory-usage' ), array( __CLASS__, 'render_field' ), 'wpmu-memory-alerts', 'wpmu_alertsettings', array( 'key' => 'email_to' ) ); |
| 975 |
add_settings_field( 'send_email', esc_html__('Send email alerts','wp-memory-usage' ), array( __CLASS__, 'render_checkbox' ), 'wpmu-memory-alerts', 'wpmu_alertsettings', array( 'key' => 'send_email' ) ); |
| 976 |
add_settings_field( 'rotate_log', esc_html__('Digest interval','wp-memory-usage' ), array( __CLASS__, 'render_field' ), 'wpmu-memory-alerts', 'wpmu_alertsettings', array( 'key' => 'rotate_log' ) ); |
| 977 |
add_settings_field( 'del_frequency_logfiles', esc_html__('Delete rotated Logfiles','wp-memory-usage' ), array( __CLASS__, 'render_field' ), 'wpmu-memory-alerts', 'wpmu_alertsettings', array( 'key' => 'del_frequency_logfiles' ) ); |
| 978 |
} |
| 979 |
|
| 980 |
public static function render_settings_page() { |
| 981 |
if ( ! current_user_can( self::CAP ) ) { |
| 982 |
wp_die( esc_html__( 'Insufficient permissions.', 'wp-memory-usage' ) ); |
| 983 |
} |
| 984 |
$opts = self::get_settings(); |
| 985 |
$tab = 'history'; |
| 986 |
if ( isset( $_GET['tab'], $_GET['_wpmu_tab_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpmu_tab_nonce'] ) ), 'wpmu_tab_nav' ) ) { |
| 987 |
$tab = sanitize_key( wp_unslash( $_GET['tab'] ) ); |
| 988 |
} |
| 989 |
if ( ! in_array( $tab, array( 'settings', 'current', 'actions', 'digest', 'history', 'check_installation' ), true ) ) { |
| 990 |
$tab = 'history'; |
| 991 |
} |
| 992 |
$page_url = admin_url( 'options-general.php?page=wpmu-memory-alerts' ); |
| 993 |
$tab_nonce = wp_create_nonce( 'wpmu_tab_nav' ); |
| 994 |
?> |
| 995 |
<div class="wrap"> |
| 996 |
<h1><?php echo esc_html__( 'WP Memory Usage', 'wp-memory-usage' ); ?> – <?php echo esc_html__( 'Threshold Alerts', 'wp-memory-usage' ); ?></h1> |
| 997 |
<h2 class="nav-tab-wrapper" style="margin-top: 12px;"> |
| 998 |
<?php |
| 999 |
$tabs = array( |
| 1000 |
'settings' => esc_html__( '⚙️ Settings', 'wp-memory-usage' ), |
| 1001 |
'history' => esc_html__( '📋 History', 'wp-memory-usage' ), |
| 1002 |
'digest' => esc_html__( '📊 Digest', 'wp-memory-usage' ), |
| 1003 |
'actions' => esc_html__( '🛠️ Actions', 'wp-memory-usage' ), |
| 1004 |
'current' => esc_html__( '📏 Memory Thresholds', 'wp-memory-usage' ), |
| 1005 |
'check_installation' => esc_html__( '🔍 Check Installation', 'wp-memory-usage' ), |
| 1006 |
); |
| 1007 |
# ); |
| 1008 |
foreach ( $tabs as $t => $label ) : |
| 1009 |
?> |
| 1010 |
<a href="<?php echo esc_url( add_query_arg( array( 'tab' => $t, '_wpmu_tab_nonce' => $tab_nonce ), $page_url ) ); ?>" |
| 1011 |
class="nav-tab <?php echo ( $t === $tab ) ? 'nav-tab-active' : ''; ?>"> |
| 1012 |
<?php echo esc_html($label); ?> |
| 1013 |
</a> |
| 1014 |
<?php endforeach; ?> |
| 1015 |
</h2> |
| 1016 |
|
| 1017 |
<?php if ( 'settings' === $tab ) : ?> |
| 1018 |
<form method="post" action="options.php"> |
| 1019 |
<?php |
| 1020 |
settings_fields( 'wpmu_threshold_settings' ); |
| 1021 |
do_settings_sections( 'wpmu-memory-alerts' ); |
| 1022 |
submit_button(); |
| 1023 |
?> |
| 1024 |
</form> |
| 1025 |
|
| 1026 |
<?php elseif ( 'current' === $tab ) : ?> |
| 1027 |
<?php |
| 1028 |
$php_limit_raw = ini_get( 'memory_limit' ); |
| 1029 |
$wp_limit_raw = defined( 'WP_MEMORY_LIMIT' ) ? WP_MEMORY_LIMIT : ''; |
| 1030 |
$wpmax_raw = defined( 'WP_MAX_MEMORY_LIMIT' ) ? WP_MAX_MEMORY_LIMIT : ''; |
| 1031 |
$php_limit_b = self::parse_size_to_bytes( is_string( $php_limit_raw ) ? $php_limit_raw : '' ); |
| 1032 |
$wp_limit_b = self::parse_size_to_bytes( is_string( $wp_limit_raw ) ? $wp_limit_raw : '' ); |
| 1033 |
$wpmax_b = self::parse_size_to_bytes( is_string( $wpmax_raw ) ? $wpmax_raw : '' ); |
| 1034 |
$effective_b = self::get_effective_memory_limit_bytes(); |
| 1035 |
$effective_mb = $effective_b > 0 ? (int) round( $effective_b / 1048576 ) : 0; |
| 1036 |
|
| 1037 |
// Threshold settings |
| 1038 |
$s_opts = self::get_settings(); |
| 1039 |
$warn_pct = (int) ( $s_opts['warn_pct'] ?? 70 ); |
| 1040 |
$danger_pct = (int) ( $s_opts['danger_pct'] ?? 85 ); |
| 1041 |
$critical_pct = (int) ( $s_opts['critical_pct'] ?? 95 ); |
| 1042 |
$warn_mb = $effective_mb > 0 ? round( $effective_mb * $warn_pct / 100, 1 ) : null; |
| 1043 |
$danger_mb = $effective_mb > 0 ? round( $effective_mb * $danger_pct / 100, 1 ) : null; |
| 1044 |
$critical_mb = $effective_mb > 0 ? round( $effective_mb * $critical_pct / 100, 1 ) : null; |
| 1045 |
|
| 1046 |
// ── Bewertungen ──────────────────────────────────────────────── |
| 1047 |
$recs = array(); // ['icon','color','bg','text'] |
| 1048 |
|
| 1049 |
// Effektives Limit |
| 1050 |
if ( $effective_mb <= 0 ) { |
| 1051 |
$recs[] = array( 'icon' => '🆘', 'color' => '#8B0000', 'bg' => '#fdf2f2', |
| 1052 |
'text' => __('Could not determine the effective memory limit. Check that WP_MEMORY_LIMIT or PHP memory_limit are set.', 'wp-memory-usage') ); |
| 1053 |
} elseif ( $effective_mb < 64 ) { |
| 1054 |
$recs[] = array( 'icon' => '🆘', 'color' => '#8B0000', 'bg' => '#fdf2f2', |
| 1055 |
'text' => __('Effective limit is very low:', 'wp-memory-usage').$effective_mb.' MB.'.__('Most WordPress sites need at least 128 MB; WooCommerce, page builders or heavy plugins often need 256 MB or more. You will likely see out-of-memory errors.', 'wp-memory-usage') ); |
| 1056 |
} elseif ( $effective_mb < 128 ) { |
| 1057 |
$recs[] = array( 'icon' => '⚠️', 'color' => '#7a5200', 'bg' => '#fef9ec', |
| 1058 |
'text' => __('Effective limit is', 'wp-memory-usage').' {'.($effective_mb).' MB.'.__('Tight for a typical WordPress site. Consider raising to at least 128 MB (256 MB recommended). Add to wp-config.php: define(\'WP_MEMORY_LIMIT\', \'256M\');', 'wp-memory-usage') ); |
| 1059 |
} elseif ( $effective_mb < 256 ) { |
| 1060 |
$recs[] = array( 'icon' => '� |
| 1061 |
', 'color' => '#2d6a2d', 'bg' => '#f2faf2', |
| 1062 |
'text' => __('Effective limit is', 'wp-memory-usage').' {'.($effective_mb).' MB.'.__('Acceptable for most sites. For WooCommerce, LMS or heavy builders, 256 MB+ is better.', 'wp-memory-usage') ); |
| 1063 |
} else { |
| 1064 |
$recs[] = array( 'icon' => '� |
| 1065 |
', 'color' => '#2d6a2d', 'bg' => '#f2faf2', |
| 1066 |
'text' => __('Effective limit is', 'wp-memory-usage').' ('.($effective_mb).' MB.'.__('Good.', 'wp-memory-usage') ); |
| 1067 |
} |
| 1068 |
|
| 1069 |
// PHP vs WP Limit-Verhältnis |
| 1070 |
if ( $php_limit_b > 0 && $wp_limit_b > 0 && $wp_limit_b > $php_limit_b ) { |
| 1071 |
$recs[] = array( 'icon' => '⚠️', 'color' => '#7a5200', 'bg' => '#fef9ec', |
| 1072 |
'text' => 'WP_MEMORY_LIMIT (' . ( (string) $wp_limit_raw ) . ') '.__('is higher than PHP memory_limit', 'wp-memory-usage').' (' . ( (string) $php_limit_raw ) . '). '.__('WordPress cannot exceed the PHP ceiling – the PHP limit wins. Raise memory_limit in php.ini / .htaccess / php_value.', 'wp-memory-usage') ); |
| 1073 |
} |
| 1074 |
if ( $wp_limit_b > 0 && $wpmax_b > 0 && $wp_limit_b > $wpmax_b ) { |
| 1075 |
$recs[] = array( 'icon' => '⚠️', 'color' => '#7a5200', 'bg' => '#fef9ec', |
| 1076 |
'text' => 'WP_MEMORY_LIMIT (' . ( (string) $wp_limit_raw ) . ') '.__('is higher than WP_MAX_MEMORY_LIMIT', 'wp-memory-usage').' (' . ( (string) $wpmax_raw ) . '). '.__('WP_MAX_MEMORY_LIMIT should be ≥ WP_MEMORY_LIMIT.', 'wp-memory-usage') ); |
| 1077 |
} |
| 1078 |
|
| 1079 |
// Threshold-Abstände |
| 1080 |
$gap_warn_danger = $danger_pct - $warn_pct; |
| 1081 |
$gap_danger_crit = $critical_pct - $danger_pct; |
| 1082 |
if ( $gap_warn_danger < 5 ) { |
| 1083 |
$recs[] = array( 'icon' => '⚠️', 'color' => '#7a5200', 'bg' => '#fef9ec', |
| 1084 |
'text' => __( 'Warning ({', 'wp-memory-usage').($warn_pct).'}%) '.__('and Danger', 'wp-memory-usage').' ({'.($danger_pct).'}%) '.__('thresholds are very close together (gap:', 'wp-memory-usage').' {'.($gap_warn_danger).'}%). '.__('Consider a gap of at least 10% so you have time to react between levels.', 'wp-memory-usage') ); |
| 1085 |
} |
| 1086 |
if ( $gap_danger_crit < 5 ) { |
| 1087 |
$recs[] = array( 'icon' => '⚠️', 'color' => '#7a5200', 'bg' => '#fef9ec', |
| 1088 |
'text' => __('Danger', 'wp-memory-usage').' ({'.($danger_pct).'}%) '.__('and Critical', 'wp-memory-usage').' ({'.($critical_pct).'}%) '.__('thresholds are very close together (gap', 'wp-memory-usage' ).': {'.($gap_danger_crit).'}%). '.__('Consider a gap of at least 5%.', 'wp-memory-usage' )); |
| 1089 |
} |
| 1090 |
if ( $warn_pct < 50 ) { |
| 1091 |
$recs[] = array( 'icon' => '⚠️', 'color' => '#7a5200', 'bg' => '#fef9ec', |
| 1092 |
'text' => __('Warning threshold is very low', 'wp-memory-usage').' ({'.($warn_pct).'}%). '.__('You will receive many false-positive alerts on normal pages. A value of 65–75% is typical.', 'wp-memory-usage') ); |
| 1093 |
} |
| 1094 |
if ( $critical_pct < 90 ) { |
| 1095 |
$recs[] = array( 'icon' => '⚠️', 'color' => '#7a5200', 'bg' => '#fef9ec', |
| 1096 |
'text' => __('Critical threshold is set to', 'wp-memory-usage').' {'.($critical_pct).'}% – '.__('that leaves little headroom before actual out-of-memory errors. Consider raising to 92–95%.', 'wp-memory-usage') ); |
| 1097 |
} |
| 1098 |
if ( $critical_pct > 98 ) { |
| 1099 |
$recs[] = array( 'icon' => '⚠️', 'color' => '#7a5200', 'bg' => '#fef9ec', |
| 1100 |
'text' => __('Critical threshold is very high', 'wp-memory-usage').' ({'.($critical_pct).'}%). '.__('At this level you may already be getting OOM errors before the alert fires. 95% is a safer upper bound.', 'wp-memory-usage') ); |
| 1101 |
} |
| 1102 |
if ( count( $recs ) === 1 && $recs[0]['icon'] === '� |
| 1103 |
' ) { |
| 1104 |
$recs[] = array( 'icon' => '� |
| 1105 |
', 'color' => '#2d6a2d', 'bg' => '#f2faf2', |
| 1106 |
'text' => __('Threshold settings look good: Warn', 'wp-memory-usage').' {'.($warn_pct).'}% / '.__('Danger', 'wp-memory-usage').' {'.($danger_pct).'}% / '.__('Critical', 'wp-memory-usage').' {'.($critical_pct).'}%.' ); |
| 1107 |
} |
| 1108 |
?> |
| 1109 |
<p><?php echo esc_html__( 'This tab shows the memory limits that actually apply to your site. Memory issues are often caused by a limit that is lower than expected.', 'wp-memory-usage' ); ?></p> |
| 1110 |
<table class="widefat striped" style="max-width: 1000px;"> |
| 1111 |
<thead><tr> |
| 1112 |
<th><?php echo esc_html__( 'Setting', 'wp-memory-usage' ); ?></th> |
| 1113 |
<th><?php echo esc_html__( 'Value', 'wp-memory-usage' ); ?></th> |
| 1114 |
<th><?php echo esc_html__( 'Meaning', 'wp-memory-usage' ); ?></th> |
| 1115 |
</tr></thead> |
| 1116 |
<tbody> |
| 1117 |
<tr> |
| 1118 |
<td><code>WP_MEMORY_LIMIT</code></td> |
| 1119 |
<td><?php echo $wp_limit_raw ? esc_html( (string) $wp_limit_raw ) : '<em>' . esc_html__( 'not defined', 'wp-memory-usage' ) . '</em>'; ?></td> |
| 1120 |
<td><?php echo esc_html__( 'Memory limit for the regular (frontend) WordPress runtime. WordPress may increase the PHP limit up to this value if possible.', 'wp-memory-usage' ); ?></td> |
| 1121 |
</tr> |
| 1122 |
<tr> |
| 1123 |
<td><code>WP_MAX_MEMORY_LIMIT</code></td> |
| 1124 |
<td><?php echo $wpmax_raw ? esc_html( (string) $wpmax_raw ) : '<em>' . esc_html__( 'not defined', 'wp-memory-usage' ) . '</em>'; ?></td> |
| 1125 |
<td><?php echo esc_html__( 'Memory limit for admin-area tasks that can be heavier (updates, editor, imports). This is often higher than WP_MEMORY_LIMIT.', 'wp-memory-usage' ); ?></td> |
| 1126 |
</tr> |
| 1127 |
<tr> |
| 1128 |
<td><code>PHP memory_limit</code></td> |
| 1129 |
<td><?php echo $php_limit_raw ? esc_html( (string) $php_limit_raw ) : '<em>' . esc_html__( 'unknown', 'wp-memory-usage' ) . '</em>'; ?></td> |
| 1130 |
<td><?php echo esc_html__( 'The PHP-level memory limit set by your server / hosting. This is the hard ceiling unless you can raise it in PHP configuration.', 'wp-memory-usage' ); ?></td> |
| 1131 |
</tr> |
| 1132 |
<tr> |
| 1133 |
<td><strong><?php echo esc_html__( 'Effective limit used for alerts', 'wp-memory-usage' ); ?></strong></td> |
| 1134 |
<td><strong><?php echo $effective_b > 0 ? esc_html( self::format_bytes( (int) $effective_b ) ) : esc_html__( 'unlimited/unknown', 'wp-memory-usage' ); ?></strong></td> |
| 1135 |
<td><?php echo esc_html__( 'For safety, this plugin uses the lower of the WordPress and PHP limits (when both are set). That reflects what will actually break first.', 'wp-memory-usage' ); ?></td> |
| 1136 |
</tr> |
| 1137 |
</tbody> |
| 1138 |
</table> |
| 1139 |
|
| 1140 |
<?php if ( $effective_mb > 0 ) : ?> |
| 1141 |
<h3 style="margin-top:20px;"><?php echo esc_html__( 'Alert thresholds in absolute values', 'wp-memory-usage' ); ?></h3> |
| 1142 |
<table class="widefat striped" style="max-width:600px;"> |
| 1143 |
<thead><tr> |
| 1144 |
<th><?php echo esc_html__( 'Level', 'wp-memory-usage' ); ?></th> |
| 1145 |
<th><?php echo esc_html__( '%', 'wp-memory-usage' ); ?></th> |
| 1146 |
<th><?php echo esc_html__( '≈ MB', 'wp-memory-usage' ); ?></th> |
| 1147 |
</tr></thead> |
| 1148 |
<tbody> |
| 1149 |
<tr><td><strong style="color:#f0ad4e;">⚠ <?php echo esc_html__( 'Warn', 'wp-memory-usage' ); ?></strong></td> |
| 1150 |
<td><?php echo esc_html($warn_pct); ?>%</td> |
| 1151 |
<td><?php echo esc_html($warn_mb !== null ? $warn_mb . ' MB' : '–'); ?></td></tr> |
| 1152 |
<tr><td><strong style="color:#d9534f;">🔴 <?php echo esc_html__( 'Danger', 'wp-memory-usage' ); ?></strong></td> |
| 1153 |
<td><?php echo esc_html($danger_pct); ?>%</td> |
| 1154 |
<td><?php echo esc_html($danger_mb !== null ? $danger_mb . ' MB' : '–'); ?></td></tr> |
| 1155 |
<tr><td><strong style="color:#8B0000;">🆘 <?php echo esc_html__( 'Critical', 'wp-memory-usage' ); ?></strong></td> |
| 1156 |
<td><?php echo esc_html($critical_pct); ?>%</td> |
| 1157 |
<td><?php echo esc_html($critical_mb !== null ? $critical_mb . ' MB' : '–'); ?></td></tr> |
| 1158 |
</tbody> |
| 1159 |
</table> |
| 1160 |
<?php endif; ?> |
| 1161 |
|
| 1162 |
<h3 style="margin-top:20px;"><?php echo esc_html__( 'Assessment & Recommendations', 'wp-memory-usage' ); ?></h3> |
| 1163 |
<?php foreach ( $recs as $rec ) : ?> |
| 1164 |
<div style="display:flex;align-items:flex-start;gap:10px;padding:10px 14px;margin-bottom:8px;border-radius:5px;border-left:4px solid <?php echo esc_attr( $rec['color'] ); ?>;background:<?php echo esc_attr( $rec['bg'] ); ?>;"> |
| 1165 |
<span style="font-size:18px;line-height:1.3;"><?php echo esc_html($rec['icon']); ?></span> |
| 1166 |
<span style="color:<?php echo esc_attr( $rec['color'] ); ?>;font-size:13px;"><?php echo esc_html( $rec['text'] ); ?></span> |
| 1167 |
</div> |
| 1168 |
<?php endforeach; ?> |
| 1169 |
|
| 1170 |
<?php elseif ( 'actions' === $tab ) : ?> |
| 1171 |
<h2><?php echo esc_html__( 'What you can do', 'wp-memory-usage' ); ?></h2> |
| 1172 |
<p><?php echo esc_html__( 'This tab explains practical next steps when you receive a memory alert. You do not need to be a developer to apply most of these actions.', 'wp-memory-usage' ); ?></p> |
| 1173 |
<h3><?php echo esc_html__( 'Step 1: Understand the severity', 'wp-memory-usage' ); ?></h3> |
| 1174 |
<ul style="list-style: disc; padding-left: 20px;"> |
| 1175 |
<li><strong><?php echo esc_html__( 'Warning', 'wp-memory-usage' ); ?></strong>: <?php echo esc_html__( 'Close to the limit. Usually no immediate outage, but you should watch it.', 'wp-memory-usage' ); ?></li> |
| 1176 |
<li><strong><?php echo esc_html__( 'Danger', 'wp-memory-usage' ); ?></strong>: <?php echo esc_html__( 'High risk. Actions like editing, imports, backups or WooCommerce tasks may fail.', 'wp-memory-usage' ); ?></li> |
| 1177 |
<li><strong><?php echo esc_html__( 'Critical', 'wp-memory-usage' ); ?></strong>: <?php echo esc_html__( 'Very likely to trigger "Allowed memory size exhausted". Act now to avoid outages.', 'wp-memory-usage' ); ?></li> |
| 1178 |
</ul> |
| 1179 |
<h3><?php echo esc_html__( 'Step 2: Identify what triggered it', 'wp-memory-usage' ); ?></h3> |
| 1180 |
<p><?php echo esc_html__( 'Open the "Digest" or "History" tab and look at the context (URL, admin screen, AJAX action, REST route, cron). This tells you which page is involved.', 'wp-memory-usage' ); ?></p> |
| 1181 |
<h3><?php echo esc_html__( 'Common actions', 'wp-memory-usage' ); ?></h3> |
| 1182 |
|
| 1183 |
|
| 1184 |
<ol style="list-style: decimal; padding-left: 20px;"> |
| 1185 |
<li><?php echo esc_html__( 'If it happens during imports/backups/crawlers: schedule these jobs at low traffic times.', 'wp-memory-usage' ); ?></li> |
| 1186 |
<li> |
| 1187 |
<?php echo esc_html__( 'Increase the PHP memory limit if your hosting allows it (often the quickest fix).', 'wp-memory-usage' ); ?><br> |
| 1188 |
• <?php echo esc_html__( 'In wp-config.php you can define WP_MEMORY_LIMIT and WP_MAX_MEMORY_LIMIT (example: define(\'WP_MEMORY_LIMIT\', "256M");). This affects WordPress, but cannot exceed the server PHP limit.', 'wp-memory-usage' ); ?> |
| 1189 |
<br> |
| 1190 |
• <?php echo esc_html__( 'The PHP memory_limit is controlled by your hosting environment (php.ini, user.ini, .htaccess, or a hosting control panel). If it is lower than your WordPress settings, PHP wins.', 'wp-memory-usage' ); ?> |
| 1191 |
<br> |
| 1192 |
• <?php echo esc_html__( 'After changing values, clear caches (object cache, page cache) and re-test the action that caused the peak.', 'wp-memory-usage' ); ?> |
| 1193 |
</li> |
| 1194 |
<li><?php echo esc_html__( 'Update WordPress core, themes and plugins. Memory leaks and inefficiencies are often fixed in updates.', 'wp-memory-usage' ); ?></li> |
| 1195 |
<li><?php echo esc_html__( 'If it happens on frontend pages: check the page and its plugins (builder, gallery, search, related posts, cache). Disable suspects temporarily to confirm.', 'wp-memory-usage' ); ?></li> |
| 1196 |
</ol> |
| 1197 |
<h3><?php echo esc_html__( 'When to adjust thresholds', 'wp-memory-usage' ); ?></h3> |
| 1198 |
<p><?php echo esc_html__( 'If you receive alerts during expected heavy tasks (e.g. backups), increase the Warning/Danger thresholds slightly. Keep Critical high (e. g. 95%) so you still get a real emergency signal.', 'wp-memory-usage' ); ?></p> |
| 1199 |
|
| 1200 |
<?php elseif ( 'digest' === $tab ) : |
| 1201 |
// ── Digest-Datei löschen (POST-Action) ─────────────────────────── |
| 1202 |
$delete_notice = ''; |
| 1203 |
if ( |
| 1204 |
isset( $_POST['wpmu_digest_delete'], $_POST['wpmu_digest_delete_nonce'], $_POST['wpmu_digest_delete_file'] ) && |
| 1205 |
wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['wpmu_digest_delete_nonce'] ) ), 'wpmu_digest_delete' ) && |
| 1206 |
current_user_can( self::CAP ) |
| 1207 |
) { |
| 1208 |
$del_fn = sanitize_file_name( wp_unslash( $_POST['wpmu_digest_delete_file'] ) ); |
| 1209 |
// Nur digest_*.cgibak erlaubt |
| 1210 |
if ( preg_match( '/^digest_[\d\-]+\.cgibak$/', $del_fn ) ) { |
| 1211 |
$del_path = self::WPMU_LOG_PATH . $del_fn; |
| 1212 |
if ( file_exists( $del_path ) && wp_delete_file( $del_path ) ) { |
| 1213 |
$delete_notice = '<div class="notice notice-success is-dismissible"><p>' . |
| 1214 |
sprintf( |
| 1215 |
/* translators: %s: filename that was deleted */ |
| 1216 |
esc_html__( 'Deleted: %s', 'wp-memory-usage' ), |
| 1217 |
esc_html( $del_fn ) ) . |
| 1218 |
'</p></div>'; |
| 1219 |
} else { |
| 1220 |
$delete_notice = '<div class="notice notice-error is-dismissible"><p>' . |
| 1221 |
sprintf( |
| 1222 |
/* translators: %s: filename that could not deleted */ |
| 1223 |
esc_html__( 'Could not delete: %s', 'wp-memory-usage' ), |
| 1224 |
esc_html( $del_fn ) ) . |
| 1225 |
'</p></div>'; |
| 1226 |
} |
| 1227 |
} elseif ("all"==$del_fn) { |
| 1228 |
$del_path = self::WPMU_LOG_PATH . $del_fn; |
| 1229 |
#error_log("del: ".$del_path); |
| 1230 |
$del_ok = 0; |
| 1231 |
$del_notok = 0; |
| 1232 |
$del_no = 0; |
| 1233 |
foreach ( glob( self::WPMU_LOG_PATH . 'digest_*.cgibak' ) as $bak ) { |
| 1234 |
#error_log("do del: ".$bak); |
| 1235 |
$del_no++; |
| 1236 |
if ( file_exists( $bak ) && wp_delete_file( $bak ) ) { |
| 1237 |
$del_ok++; |
| 1238 |
} else { |
| 1239 |
$del_notok++; |
| 1240 |
} |
| 1241 |
} |
| 1242 |
if ( $del_ok==$del_no ) { |
| 1243 |
$delete_notice = '<div class="notice notice-success is-dismissible"><p>' . |
| 1244 |
sprintf( |
| 1245 |
/* translators: %s: filename that could not deleted */ |
| 1246 |
esc_html__( 'Deleted: %s files', 'wp-memory-usage' ), |
| 1247 |
esc_html( $del_ok ) ) . |
| 1248 |
'</p></div>'; |
| 1249 |
} else { |
| 1250 |
$delete_notice = '<div class="notice notice-error is-dismissible"><p>' . |
| 1251 |
sprintf( |
| 1252 |
/* translators: %s: filename that could not deleted */ |
| 1253 |
esc_html__( 'Could not delete: %s files', 'wp-memory-usage' ), |
| 1254 |
esc_html( $del_notok ) ) . |
| 1255 |
'</p></div>'; |
| 1256 |
} |
| 1257 |
} |
| 1258 |
} |
| 1259 |
echo wp_kses_post( $delete_notice ); |
| 1260 |
|
| 1261 |
// ── Alle Digest-Dateien einlesen ────────────────────────────────── |
| 1262 |
$digest_files = array(); |
| 1263 |
foreach ( glob( self::WPMU_LOG_PATH . 'digest_*.cgibak' ) as $bak ) { |
| 1264 |
$fn = basename( $bak ); |
| 1265 |
$raw = str_replace( array( 'digest_', '.cgibak' ), '', $fn ); |
| 1266 |
$dt = DateTime::createFromFormat( 'Y-m-d-H-i-s', $raw ); |
| 1267 |
$label = $dt |
| 1268 |
? wp_date( get_option('date_format') . ', ' . get_option('time_format'), $dt->getTimestamp() ) |
| 1269 |
: $fn; // Fallback: Rohdateiname, falls Format nicht passt |
| 1270 |
$digest_files[ $fn ] = $label; |
| 1271 |
} |
| 1272 |
krsort( $digest_files ); // neueste zuerst |
| 1273 |
|
| 1274 |
// ── Auswahl auslesen (Nonce-gesichert) ──────────────────────────── |
| 1275 |
$selected_file = ''; |
| 1276 |
$merge_all = false; |
| 1277 |
if ( |
| 1278 |
isset( $_GET['wpmu_digest_nonce'], $_GET['wpmu_digest_sel'] ) && |
| 1279 |
wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['wpmu_digest_nonce'] ) ), 'wpmu_digest_sel' ) |
| 1280 |
) { |
| 1281 |
$raw = sanitize_text_field( wp_unslash( $_GET['wpmu_digest_sel'] ) ); |
| 1282 |
if ( $raw === '__all__' ) { |
| 1283 |
$merge_all = true; |
| 1284 |
} elseif ( isset( $digest_files[ $raw ] ) ) { |
| 1285 |
$selected_file = $raw; |
| 1286 |
} |
| 1287 |
} |
| 1288 |
|
| 1289 |
// ── Daten laden ─────────────────────────────────────────────────── |
| 1290 |
$data = array(); |
| 1291 |
if ( $merge_all && ! empty( $digest_files ) ) { |
| 1292 |
foreach ( array_keys( $digest_files ) as $fn ) { |
| 1293 |
$d = self::get_from_file( $fn ); |
| 1294 |
if ( ! is_array( $d ) || empty( $d ) ) { continue; } |
| 1295 |
if ( empty( $data ) ) { |
| 1296 |
$data = $d; |
| 1297 |
continue; |
| 1298 |
} |
| 1299 |
foreach ( $d['status'] as $s => $cnt ) { |
| 1300 |
$data['status'][ $s ] = ( $data['status'][ $s ] ?? 0 ) + $cnt; |
| 1301 |
} |
| 1302 |
foreach ( ( $d['type'] ?? array() ) as $t => $cnt ) { |
| 1303 |
$data['type'][ $t ] = ( $data['type'][ $t ] ?? 0 ) + $cnt; |
| 1304 |
} |
| 1305 |
foreach ( ( $d['uri'] ?? array() ) as $uri => $ud ) { |
| 1306 |
if ( ! isset( $data['uri'][ $uri ] ) ) { |
| 1307 |
$data['uri'][ $uri ] = array( 'total' => 0, 'warn' => 0, 'danger' => 0, 'critical' => 0, 'avg_usage' => 0, 'max_usage' => 0 ); |
| 1308 |
} |
| 1309 |
$existing_total = $data['uri'][ $uri ]['total']; |
| 1310 |
$new_total = $ud['total'] ?? 0; |
| 1311 |
$merged_total = $existing_total + $new_total; |
| 1312 |
$data['uri'][ $uri ]['avg_usage'] = $merged_total > 0 |
| 1313 |
? (int) round( ( $data['uri'][ $uri ]['avg_usage'] * $existing_total + ( $ud['avg_usage'] ?? 0 ) * $new_total ) / $merged_total ) |
| 1314 |
: 0; |
| 1315 |
$data['uri'][ $uri ]['total'] = $merged_total; |
| 1316 |
$data['uri'][ $uri ]['warn'] = ( $data['uri'][ $uri ]['warn'] ?? 0 ) + ( $ud['warn'] ?? 0 ); |
| 1317 |
$data['uri'][ $uri ]['danger'] = ( $data['uri'][ $uri ]['danger'] ?? 0 ) + ( $ud['danger'] ?? 0 ); |
| 1318 |
$data['uri'][ $uri ]['critical'] = ( $data['uri'][ $uri ]['critical'] ?? 0 ) + ( $ud['critical'] ?? 0 ); |
| 1319 |
if ( ( $ud['max_usage'] ?? 0 ) > $data['uri'][ $uri ]['max_usage'] ) { |
| 1320 |
$data['uri'][ $uri ]['max_usage'] = $ud['max_usage']; |
| 1321 |
} |
| 1322 |
} |
| 1323 |
if ( isset( $d['interval']['first'] ) && $d['interval']['first'] < $data['interval']['first'] ) { |
| 1324 |
$data['interval']['first'] = $d['interval']['first']; |
| 1325 |
} |
| 1326 |
if ( isset( $d['interval']['last'] ) && $d['interval']['last'] > $data['interval']['last'] ) { |
| 1327 |
$data['interval']['last'] = $d['interval']['last']; |
| 1328 |
} |
| 1329 |
foreach ( ( $d['interval']['per_minute'] ?? array() ) as $min_key => $cnt ) { |
| 1330 |
$data['interval']['per_minute'][ $min_key ] = ( $data['interval']['per_minute'][ $min_key ] ?? 0 ) + $cnt; |
| 1331 |
} |
| 1332 |
} |
| 1333 |
if ( isset( $data['interval']['first'], $data['interval']['last'] ) ) { |
| 1334 |
$data['interval']['duration_sec'] = $data['interval']['last'] - $data['interval']['first']; |
| 1335 |
} |
| 1336 |
} elseif ( $selected_file ) { |
| 1337 |
$data = self::get_from_file( $selected_file ); |
| 1338 |
if ( ! is_array( $data ) ) { $data = array(); } |
| 1339 |
} elseif ( ! empty( $digest_files ) ) { |
| 1340 |
$selected_file = array_key_first( $digest_files ); |
| 1341 |
$data = self::get_from_file( $selected_file ); |
| 1342 |
if ( ! is_array( $data ) ) { $data = array(); } |
| 1343 |
} |
| 1344 |
|
| 1345 |
// ── Auswahl-Formular ────────────────────────────────────────────── |
| 1346 |
$digest_nonce = wp_create_nonce( 'wpmu_digest_sel' ); |
| 1347 |
?> |
| 1348 |
<div style="margin:16px 0;display:flex;align-items:center;gap:12px;flex-wrap:wrap;background:#f9f9f9;padding:12px 16px;border:1px solid #ddd;border-radius:6px;"> |
| 1349 |
<form method="get" style="display:contents;"> |
| 1350 |
<input type="hidden" name="page" value="wpmu-memory-alerts"> |
| 1351 |
<input type="hidden" name="tab" value="digest"> |
| 1352 |
<input type="hidden" name="_wpmu_tab_nonce" value="<?php echo esc_attr( $tab_nonce ); ?>"> |
| 1353 |
<input type="hidden" name="wpmu_digest_nonce" value="<?php echo esc_attr( $digest_nonce ); ?>"> |
| 1354 |
<label for="wpmu_digest_sel" style="font-weight:600;white-space:nowrap;"> |
| 1355 |
<?php echo esc_html__( 'Show Digest:', 'wp-memory-usage' ); ?> |
| 1356 |
</label> |
| 1357 |
<select name="wpmu_digest_sel" id="wpmu_digest_sel" onchange="this.form.submit()" |
| 1358 |
style="min-width:280px;padding:5px 8px;"> |
| 1359 |
<?php if ( empty( $digest_files ) ) : ?> |
| 1360 |
<option value=""><?php echo esc_html__( '— no digest files found —', 'wp-memory-usage' ); ?></option> |
| 1361 |
<?php else : ?> |
| 1362 |
<?php if ( count( $digest_files ) > 1 ) : ?> |
| 1363 |
<option value="__all__" <?php selected( $merge_all, true ); ?>> |
| 1364 |
<?php printf( |
| 1365 |
/* translators: %d: number of digest files */ |
| 1366 |
esc_html__( '⊕ Merge all %d digest files', 'wp-memory-usage' ), count( $digest_files ) ); ?> |
| 1367 |
</option> |
| 1368 |
<option disabled>──────────────────────────────</option> |
| 1369 |
<?php endif; ?> |
| 1370 |
<?php foreach ( $digest_files as $fn => $label ) : ?> |
| 1371 |
<option value="<?php echo esc_attr( $fn ); ?>" |
| 1372 |
<?php selected( ! $merge_all && $selected_file === $fn ); ?>> |
| 1373 |
<?php |
| 1374 |
echo esc_html( $label ); ?> |
| 1375 |
</option> |
| 1376 |
<?php endforeach; ?> |
| 1377 |
<?php endif; ?> |
| 1378 |
</select> |
| 1379 |
<noscript><button type="submit" class="button"><?php echo esc_html__( 'Show', 'wp-memory-usage' ); ?></button></noscript> |
| 1380 |
</form> |
| 1381 |
<?php if ( $selected_file && ! $merge_all ) : ?> |
| 1382 |
<form method="post" style="display:inline;" |
| 1383 |
onsubmit="return confirm('<?php echo esc_js( __( 'Delete this digest file? This cannot be undone.', 'wp-memory-usage' ) ); ?>');"> |
| 1384 |
<input type="hidden" name="page" value="wpmu-memory-alerts"> |
| 1385 |
<input type="hidden" name="tab" value="digest"> |
| 1386 |
<input type="hidden" name="_wpmu_tab_nonce" value="<?php echo esc_attr( $tab_nonce ); ?>"> |
| 1387 |
<input type="hidden" name="wpmu_digest_delete" value="1"> |
| 1388 |
<input type="hidden" name="wpmu_digest_delete_file" value="<?php echo esc_attr( $selected_file ); ?>"> |
| 1389 |
<input type="hidden" name="wpmu_digest_delete_nonce" |
| 1390 |
value="<?php echo esc_attr( wp_create_nonce( 'wpmu_digest_delete' ) ); ?>"> |
| 1391 |
<button type="submit" class="button button-small" |
| 1392 |
style="border-color:#d9534f;color:#d9534f;background:#fff;"> |
| 1393 |
🗑 <?php echo esc_html__( 'Delete this file', 'wp-memory-usage' ); ?> |
| 1394 |
</button> |
| 1395 |
</form> |
| 1396 |
<form method="post" style="display:inline;" |
| 1397 |
onsubmit="return confirm('<?php echo esc_js( __( 'Delete all digest files? This cannot be undone.', 'wp-memory-usage' ) ); ?>');"> |
| 1398 |
<input type="hidden" name="page" value="wpmu-memory-alerts"> |
| 1399 |
<input type="hidden" name="tab" value="digest"> |
| 1400 |
<input type="hidden" name="_wpmu_tab_nonce" value="<?php echo esc_attr( $tab_nonce ); ?>"> |
| 1401 |
<input type="hidden" name="wpmu_digest_delete" value="1"> |
| 1402 |
<input type="hidden" name="wpmu_digest_delete_file" value="<?php echo esc_attr( "all" ); ?>"> |
| 1403 |
<input type="hidden" name="wpmu_digest_delete_nonce" |
| 1404 |
value="<?php echo esc_attr( wp_create_nonce( 'wpmu_digest_delete' ) ); ?>"> |
| 1405 |
<button type="submit" class="button button-small" |
| 1406 |
style="border-color:#d9534f;color:#d9534f;background:#fff;"> |
| 1407 |
🗑 <?php echo esc_html__( 'Delete all files', 'wp-memory-usage' ); ?> |
| 1408 |
</button> |
| 1409 |
</form> |
| 1410 |
<?php endif; ?> |
| 1411 |
<?php if ( $merge_all ) : ?> |
| 1412 |
<span style="background:#e7f3ff;border:1px solid #3498db;border-radius:4px;padding:4px 10px;font-size:12px;color:#0c5d9e;font-weight:600;"> |
| 1413 |
<?php printf( |
| 1414 |
/* translators: %d: number of merged digest files */ |
| 1415 |
esc_html__( 'Merged: %d files', 'wp-memory-usage' ), count( $digest_files ) ); ?> |
| 1416 |
</span> |
| 1417 |
<?php endif; |
| 1418 |
$currenttime = wp_date( get_option('date_format') . ', ' . get_option('time_format') ); |
| 1419 |
echo esc_html__( 'Servertime', 'wp-memory-usage' ). ": ".esc_html($currenttime); |
| 1420 |
?> |
| 1421 |
</div> |
| 1422 |
<?php |
| 1423 |
if (count($data)>0) { |
| 1424 |
?> |
| 1425 |
<table> |
| 1426 |
<tr><td valign="top"> |
| 1427 |
<!-- STATUS --> |
| 1428 |
<table class="widefat striped" style="max-width:400px;"> |
| 1429 |
<thead><tr><th colspan="2"><h3><?php echo esc_html__( 'Status Summary', 'wp-memory-usage' ); ?></h3></th></tr></thead> |
| 1430 |
<thead><tr><th><?php echo esc_html__( 'Status', 'wp-memory-usage' ); ?></th><th><?php echo esc_html__( 'Occurrences', 'wp-memory-usage' ); ?></th></tr></thead> |
| 1431 |
<tbody> |
| 1432 |
<?php foreach ( $data['status'] as $s => $count ) : |
| 1433 |
$color = match( $s ) { |
| 1434 |
'warn' => '#f0ad4e', |
| 1435 |
'danger' => '#d9534f', |
| 1436 |
'critical' => '#8B0000', |
| 1437 |
default => '#5cb85c', |
| 1438 |
}; |
| 1439 |
$outtxt = match( $s ) { |
| 1440 |
'warn' => __('warn', 'wp-memory-usage' ), |
| 1441 |
'danger' => __('danger', 'wp-memory-usage' ), |
| 1442 |
'critical' => __('critical', 'wp-memory-usage' ), |
| 1443 |
default => __('ok', 'wp-memory-usage' ), |
| 1444 |
}; |
| 1445 |
?> |
| 1446 |
<tr> |
| 1447 |
<td><strong style="color:<?php echo esc_html($color); ?>"><?php echo esc_html(strtoupper( $outtxt )); ?></strong></td> |
| 1448 |
<td><?php echo esc_html($count); ?></td> |
| 1449 |
</tr> |
| 1450 |
<?php endforeach; ?> |
| 1451 |
</tbody> |
| 1452 |
</table> |
| 1453 |
</td><td valign="top"> |
| 1454 |
<!-- TYP --> |
| 1455 |
<table class="widefat striped" style="max-width:400px;"> |
| 1456 |
<thead><tr><th colspan="2"><h3><?php echo esc_html__( 'Request types', 'wp-memory-usage' ); ?></h3></th></tr></thead> |
| 1457 |
<thead><tr><th><?php echo esc_html__( 'Type', 'wp-memory-usage' ); ?></th><th><?php echo esc_html__( 'Occurrences', 'wp-memory-usage' ); ?></th></tr></thead> |
| 1458 |
<tbody> |
| 1459 |
<?php foreach ( $data['type'] as $type => $count ) : ?> |
| 1460 |
<tr> |
| 1461 |
<td><?php echo esc_html( $type ); ?></td> |
| 1462 |
<td><?php echo esc_html($count); ?></td> |
| 1463 |
</tr> |
| 1464 |
<?php endforeach; ?> |
| 1465 |
</tbody> |
| 1466 |
</table> |
| 1467 |
</td><td valign="top"> |
| 1468 |
<!-- ZEITINTERVALL --> |
| 1469 |
<?php |
| 1470 |
$dur = $data['interval']['duration_sec']; |
| 1471 |
$mins = floor( $dur / 60 ); |
| 1472 |
$secs = $dur % 60; |
| 1473 |
?> |
| 1474 |
<!-- PRO INTERVALL --> |
| 1475 |
<?php |
| 1476 |
$interval_min = 60 * 3; |
| 1477 |
$per_interval = array(); |
| 1478 |
foreach ( $data['interval']['per_minute'] as $minute => $count ) { |
| 1479 |
$ts_slot = strtotime( $minute ); |
| 1480 |
$slot_ts = floor( $ts_slot / ( $interval_min * 60 ) ) * ( $interval_min * 60 ); |
| 1481 |
$slot_key = wp_date( get_option('date_format') . ', ' . get_option('time_format'), $slot_ts ); |
| 1482 |
$per_interval[ $slot_key ] = ( $per_interval[ $slot_key ] ?? 0 ) + $count; |
| 1483 |
} |
| 1484 |
$max_count = $per_interval ? max( $per_interval ) : 1; |
| 1485 |
?> |
| 1486 |
<table class="widefat striped" style="max-width:600px;"> |
| 1487 |
<thead><tr><th colspan="3"><h3><?php echo esc_html__( 'Events each', 'wp-memory-usage' ) . " ". esc_html($interval_min) . " " . esc_html__( 'Minutes', 'wp-memory-usage' ); ?></h3></th></tr></thead> |
| 1488 |
<tr><th colspan="3"> |
| 1489 |
<?php echo esc_html__( 'From', 'wp-memory-usage' ); ?>: <strong><?php echo esc_html(wp_date( get_option('date_format') . ', ' . get_option('time_format'), $data['interval']['first'] )); ?></strong> | |
| 1490 |
<?php echo esc_html__( 'To', 'wp-memory-usage' ); ?>: <strong><?php echo esc_html(wp_date( get_option('date_format') . ', ' . get_option('time_format'), $data['interval']['last'] )); ?></strong> | |
| 1491 |
<?php echo esc_html__( 'Duration', 'wp-memory-usage' ); ?>: <strong><?php echo esc_html($mins) . " " . esc_html__( 'min.', 'wp-memory-usage' ) . " ". esc_html($secs) . " " . esc_html__( 'sec.', 'wp-memory-usage' ); ?> </strong> |
| 1492 |
</th></tr> |
| 1493 |
<thead><tr><th><?php echo esc_html__( 'Time window', 'wp-memory-usage' ); ?> </th><th><?php echo esc_html__( 'Occurrences', 'wp-memory-usage' ); ?> </th><th><?php echo esc_html__( 'Event Distribution', 'wp-memory-usage' ); ?> </th></tr></thead> |
| 1494 |
<tbody> |
| 1495 |
<?php foreach ( $per_interval as $slot => $count ) : |
| 1496 |
$bar_width = round( ( $count / $max_count ) * 200 ); |
| 1497 |
$bar_color = $count >= 10 ? '#d9534f' : ( $count >= 5 ? '#f0ad4e' : '#5cb85c' ); |
| 1498 |
?> |
| 1499 |
<tr> |
| 1500 |
<td><?php echo esc_html( $slot ); ?> – <?php echo esc_html( |
| 1501 |
wp_date( 'H:i', strtotime( $slot ) + $interval_min * 60 ) |
| 1502 |
); |
| 1503 |
?></td> |
| 1504 |
<td><strong><?php echo esc_html($count); ?></strong></td> |
| 1505 |
<td><div style="width:<?php echo esc_attr($bar_width); ?>px;height:14px;background:<?php echo esc_attr($bar_color); ?>;border-radius:3px;"></div></td> |
| 1506 |
</tr> |
| 1507 |
<?php endforeach; ?> |
| 1508 |
</tbody> |
| 1509 |
</table> |
| 1510 |
</td></tr></table> |
| 1511 |
|
| 1512 |
<!-- ====================================================== |
| 1513 |
URI-TABELLE: sortierbar, Spalten ausblendbar, avg + max |
| 1514 |
====================================================== --> |
| 1515 |
<h3>URIs</h3> |
| 1516 |
<?php |
| 1517 |
uasort( $data['uri'], fn( $a, $b ) => $b['total'] <=> $a['total'] ); |
| 1518 |
$uri_json = json_encode( $data['uri'], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ); |
| 1519 |
$home_url = json_encode( trailingslashit( home_url() ) ); |
| 1520 |
?> |
| 1521 |
<style> |
| 1522 |
.wpmu-uri-wrap { max-width: 1150px; margin-top: 8px; } |
| 1523 |
.wpmu-uri-controls { |
| 1524 |
display: flex; align-items: center; gap: 10px; |
| 1525 |
flex-wrap: wrap; margin-bottom: 10px; |
| 1526 |
} |
| 1527 |
.wpmu-toggle-btn { |
| 1528 |
display: inline-flex; align-items: center; gap: 4px; |
| 1529 |
padding: 4px 11px; border-radius: 4px; border: 2px solid transparent; |
| 1530 |
font-size: 12px; font-weight: 600; cursor: pointer; |
| 1531 |
background: #fff; transition: opacity .15s, box-shadow .15s; |
| 1532 |
user-select: none; line-height: 1.5; |
| 1533 |
} |
| 1534 |
.wpmu-toggle-btn[data-col="warn"] { border-color: #f0ad4e; color: #7a5200; } |
| 1535 |
.wpmu-toggle-btn[data-col="danger"] { border-color: #d9534f; color: #a02020; } |
| 1536 |
.wpmu-toggle-btn[data-col="critical"] { border-color: #8B0000; color: #8B0000; } |
| 1537 |
.wpmu-toggle-btn.is-hidden { opacity: .35; } |
| 1538 |
.wpmu-toggle-btn .btn-icon { font-size: 10px; } |
| 1539 |
#wpmu-uri-tbl { width: 100%; border-collapse: collapse; font-size: 13px; } |
| 1540 |
#wpmu-uri-tbl th { |
| 1541 |
background: #f5f5f5; padding: 7px 10px; text-align: left; |
| 1542 |
border-bottom: 2px solid #ddd; white-space: nowrap; |
| 1543 |
cursor: pointer; user-select: none; |
| 1544 |
} |
| 1545 |
#wpmu-uri-tbl th:hover { background: #eaeaea; } |
| 1546 |
#wpmu-uri-tbl th .si { font-size: 10px; margin-left: 3px; color: #aaa; } |
| 1547 |
#wpmu-uri-tbl th.sa .si::after { content: "▲"; color: #0073aa; } |
| 1548 |
#wpmu-uri-tbl th.sd .si::after { content: "▼"; color: #0073aa; } |
| 1549 |
#wpmu-uri-tbl th:not(.sa):not(.sd) .si::after { content: "� |
| 1550 |
"; } |
| 1551 |
#wpmu-uri-tbl td { padding: 6px 10px; border-bottom: 1px solid #eee; vertical-align: middle; } |
| 1552 |
#wpmu-uri-tbl tbody tr:hover td { background: #f9f9f9; } |
| 1553 |
#wpmu-uri-tbl td.nr { text-align: right; font-variant-numeric: tabular-nums; } |
| 1554 |
.wpmu-badge { |
| 1555 |
display: inline-block; padding: 2px 7px; border-radius: 10px; |
| 1556 |
font-size: 11px; font-weight: 700; |
| 1557 |
} |
| 1558 |
.wpmu-bw { background: #f0ad4e; color: #3d2b00; } |
| 1559 |
.wpmu-bd { background: #d9534f; color: #fff; } |
| 1560 |
.wpmu-bc { background: #8B0000; color: #fff; } |
| 1561 |
.wpmu-avg { color: #666; font-size: 12px; } |
| 1562 |
.wpmu-max { font-weight: 700; } |
| 1563 |
</style> |
| 1564 |
|
| 1565 |
<div class="wpmu-uri-wrap"> |
| 1566 |
<div class="wpmu-uri-controls"> |
| 1567 |
<strong style="font-size:13px;"><?php echo esc_html__( 'Toggle Columns', 'wp-memory-usage' ); ?>:</strong> |
| 1568 |
<button type="button" class="wpmu-toggle-btn" data-col="warn"> |
| 1569 |
<span class="btn-icon">✓</span> <?php echo esc_html__( '⚠ warn', 'wp-memory-usage' ); ?> |
| 1570 |
</button> |
| 1571 |
<button type="button" class="wpmu-toggle-btn" data-col="danger"> |
| 1572 |
<span class="btn-icon">✓</span> <?php echo esc_html__( '🔴 danger', 'wp-memory-usage' ); ?> |
| 1573 |
</button> |
| 1574 |
<button type="button" class="wpmu-toggle-btn" data-col="critical"> |
| 1575 |
<span class="btn-icon">✓</span> <?php echo esc_html__( '🆘 critical', 'wp-memory-usage' ); ?> |
| 1576 |
</button> |
| 1577 |
</div> |
| 1578 |
<table id="wpmu-uri-tbl" class="widefat striped"> |
| 1579 |
<thead> |
| 1580 |
<tr> |
| 1581 |
<th data-col="uri">URI <span class="si"></span></th> |
| 1582 |
<th data-col="total" class="nr sd"><?php echo esc_html__( 'total', 'wp-memory-usage' ); ?> <span class="si"></span></th> |
| 1583 |
<th data-col="warn" class="nr col-warn" style="text-align:center;"><?php echo esc_html__( '⚠ warn', 'wp-memory-usage' ); ?><span class="si"></span></th> |
| 1584 |
<th data-col="danger" class="nr col-danger" style="text-align:center;"><?php echo esc_html__( '🔴 danger', 'wp-memory-usage' ); ?><span class="si"></span></th> |
| 1585 |
<th data-col="critical" class="nr col-critical" style="text-align:center;"><?php echo esc_html__( '🆘 critical', 'wp-memory-usage' ); ?><span class="si"></span></th> |
| 1586 |
<th data-col="avg" class="nr"><?php echo esc_html__( 'Ø average', 'wp-memory-usage' ); ?><span class="si"></span></th> |
| 1587 |
<th data-col="max" class="nr"><?php echo esc_html__( 'max', 'wp-memory-usage' ); ?> <span class="si"></span></th> |
| 1588 |
</tr> |
| 1589 |
</thead> |
| 1590 |
<tbody id="wpmu-uri-tbody"></tbody> |
| 1591 |
</table> |
| 1592 |
</div> |
| 1593 |
<?php |
| 1594 |
$wpmu_no_data = __( 'No data', 'wp-memory-usage' ); |
| 1595 |
?> |
| 1596 |
<script> |
| 1597 |
var WPMU_NO_DATA = <?php echo wp_json_encode( $wpmu_no_data ); ?>; |
| 1598 |
(function () { |
| 1599 |
var RAW = <?php echo wp_json_encode( json_decode($uri_json) ); ?>; |
| 1600 |
var HOMEURL = <?php echo wp_json_encode( json_decode($home_url) ); ?>; |
| 1601 |
|
| 1602 |
// Build flat row array |
| 1603 |
var rows = Object.entries( RAW ).map( function ( e ) { |
| 1604 |
var uri = e[0], d = e[1]; |
| 1605 |
return { |
| 1606 |
uri: uri, |
| 1607 |
total: d.total || 0, |
| 1608 |
warn: d.warn || 0, |
| 1609 |
danger: d.danger || 0, |
| 1610 |
critical: d.critical || 0, |
| 1611 |
avg: d.avg_usage || 0, |
| 1612 |
max: d.max_usage || 0, |
| 1613 |
}; |
| 1614 |
}); |
| 1615 |
|
| 1616 |
var sortCol = 'total'; |
| 1617 |
var sortDir = 'desc'; |
| 1618 |
var hidden = {}; // col -> bool |
| 1619 |
|
| 1620 |
function fmtB( b ) { |
| 1621 |
b = parseInt( b, 10 ) || 0; |
| 1622 |
if ( ! b ) return '–'; |
| 1623 |
var u = ['B','KB','MB','GB'], i = 0, v = b; |
| 1624 |
while ( v >= 1024 && i < u.length - 1 ) { v /= 1024; i++; } |
| 1625 |
return ( i === 0 ? v.toFixed(0) : v.toFixed(1) ) + '\u202f' + u[i]; |
| 1626 |
} |
| 1627 |
|
| 1628 |
function esc( s ) { |
| 1629 |
return String( s ) |
| 1630 |
.replace(/&/g,'&').replace(/</g,'<') |
| 1631 |
.replace(/>/g,'>').replace(/"/g,'"'); |
| 1632 |
} |
| 1633 |
|
| 1634 |
function render() { |
| 1635 |
// Sort |
| 1636 |
var sorted = rows.slice().sort( function ( a, b ) { |
| 1637 |
var av = a[ sortCol ], bv = b[ sortCol ]; |
| 1638 |
if ( typeof av === 'string' ) { |
| 1639 |
av = av.toLowerCase(); bv = bv.toLowerCase(); |
| 1640 |
return sortDir === 'asc' ? ( av < bv ? -1 : av > bv ? 1 : 0 ) |
| 1641 |
: ( av > bv ? -1 : av < bv ? 1 : 0 ); |
| 1642 |
} |
| 1643 |
return sortDir === 'asc' ? av - bv : bv - av; |
| 1644 |
}); |
| 1645 |
|
| 1646 |
// Render rows |
| 1647 |
var html = ''; |
| 1648 |
sorted.forEach( function ( r ) { |
| 1649 |
var uriDisplay = r.uri.replace( /\?doing_wp_cron=[\d.]+/, '?doing_wp_cron=\u2026' ); |
| 1650 |
var fullUrl = HOMEURL + r.uri.replace( /^\//, '' ); |
| 1651 |
|
| 1652 |
var warnCell = hidden['warn'] ? '' : |
| 1653 |
'<td class="nr col-warn" style="text-align:center;">' + |
| 1654 |
( r.warn ? '<span class="wpmu-badge wpmu-bw">' + r.warn + '</span>' : '<span style="color:#ccc">–</span>' ) + |
| 1655 |
'</td>'; |
| 1656 |
var dangerCell = hidden['danger'] ? '' : |
| 1657 |
'<td class="nr col-danger" style="text-align:center;">' + |
| 1658 |
( r.danger ? '<span class="wpmu-badge wpmu-bd">' + r.danger + '</span>' : '<span style="color:#ccc">–</span>' ) + |
| 1659 |
'</td>'; |
| 1660 |
var criticalCell = hidden['critical'] ? '' : |
| 1661 |
'<td class="nr col-critical" style="text-align:center;">' + |
| 1662 |
( r.critical ? '<span class="wpmu-badge wpmu-bc">' + r.critical + '</span>' : '<span style="color:#ccc">–</span>' ) + |
| 1663 |
'</td>'; |
| 1664 |
|
| 1665 |
html += '<tr>' |
| 1666 |
+ '<td><a href="' + esc( fullUrl ) + '" target="_blank" rel="noopener" style="word-break:break-all;">' + esc( uriDisplay ) + '</a></td>' |
| 1667 |
+ '<td class="nr"><strong>' + r.total + '</strong></td>' |
| 1668 |
+ warnCell |
| 1669 |
+ dangerCell |
| 1670 |
+ criticalCell |
| 1671 |
+ '<td class="nr wpmu-avg">' + fmtB( r.avg ) + '</td>' |
| 1672 |
+ '<td class="nr wpmu-max">' + fmtB( r.max ) + '</td>' |
| 1673 |
+ '</tr>'; |
| 1674 |
}); |
| 1675 |
document.getElementById('wpmu-uri-tbody').innerHTML = |
| 1676 |
html || '<tr><td colspan="7" style="color:#999;text-align:center;padding:12px;">' + WPMU_NO_DATA + '</td></tr>'; |
| 1677 |
|
| 1678 |
// Update header classes + visibility |
| 1679 |
document.querySelectorAll('#wpmu-uri-tbl thead th').forEach( function ( th ) { |
| 1680 |
th.classList.remove('sa','sd'); |
| 1681 |
if ( th.dataset.col === sortCol ) { |
| 1682 |
th.classList.add( sortDir === 'asc' ? 'sa' : 'sd' ); |
| 1683 |
} |
| 1684 |
var col = th.dataset.col; |
| 1685 |
if ( col && ( col === 'warn' || col === 'danger' || col === 'critical' ) ) { |
| 1686 |
th.style.display = hidden[ col ] ? 'none' : ''; |
| 1687 |
} |
| 1688 |
}); |
| 1689 |
} |
| 1690 |
|
| 1691 |
// Sort on header click |
| 1692 |
document.querySelectorAll('#wpmu-uri-tbl thead th').forEach( function ( th ) { |
| 1693 |
th.addEventListener('click', function () { |
| 1694 |
var col = this.dataset.col; |
| 1695 |
if ( ! col ) return; |
| 1696 |
if ( sortCol === col ) { |
| 1697 |
sortDir = sortDir === 'asc' ? 'desc' : 'asc'; |
| 1698 |
} else { |
| 1699 |
sortCol = col; |
| 1700 |
sortDir = col === 'uri' ? 'asc' : 'desc'; |
| 1701 |
} |
| 1702 |
render(); |
| 1703 |
}); |
| 1704 |
}); |
| 1705 |
|
| 1706 |
// Toggle buttons |
| 1707 |
document.querySelectorAll('.wpmu-toggle-btn').forEach( function ( btn ) { |
| 1708 |
btn.addEventListener('click', function () { |
| 1709 |
var col = this.dataset.col; |
| 1710 |
hidden[ col ] = ! hidden[ col ]; |
| 1711 |
this.classList.toggle('is-hidden', !! hidden[ col ] ); |
| 1712 |
this.querySelector('.btn-icon').textContent = hidden[ col ] ? '✕' : '✓'; |
| 1713 |
render(); |
| 1714 |
}); |
| 1715 |
}); |
| 1716 |
|
| 1717 |
render(); |
| 1718 |
})(); |
| 1719 |
</script> |
| 1720 |
<?PHP |
| 1721 |
} else { |
| 1722 |
echo "<h2>".esc_html__( 'No digest created yet. See "Digest interval" in the settings.', 'wp-memory-usage' )."</h2>"; |
| 1723 |
} |
| 1724 |
?> |
| 1725 |
|
| 1726 |
<?php elseif ( 'history' === $tab ) : |
| 1727 |
$opts = self::get_log(); |
| 1728 |
$totalanz = count($opts); |
| 1729 |
$log = array_slice( $opts, -1 * self::$event_anz_display ); |
| 1730 |
$log = array_reverse( $log ); |
| 1731 |
?> |
| 1732 |
<h2><?php |
| 1733 |
if (self::$event_anz_display >= $totalanz) { |
| 1734 |
echo esc_html($totalanz) . " " . esc_html__( 'recent events', 'wp-memory-usage' ) . ", "; |
| 1735 |
} else { |
| 1736 |
#echo esc_html(self::$event_anz_display) . " " . esc_html__( 'recent events displayed', 'wp-memory-usage' ) . " (" . esc_html($totalanz) . esc_html__(" stored events", 'wp-memory-usage' ). "), "; |
| 1737 |
echo esc_html( |
| 1738 |
sprintf( |
| 1739 |
/* translators: 1: number of displayed events, 2: number of total stored events */ |
| 1740 |
__( '%1$d recent events displayed (%2$d stored events),', 'wp-memory-usage' ), |
| 1741 |
self::$event_anz_display, |
| 1742 |
$totalanz |
| 1743 |
) ); |
| 1744 |
} |
| 1745 |
$currenttime = wp_date( get_option('date_format') . ', ' . get_option('time_format') ); |
| 1746 |
echo esc_html__( 'Servertime', 'wp-memory-usage' ). ": ".esc_html($currenttime); |
| 1747 |
?></h2> |
| 1748 |
<?php if ( empty( $log ) ) : ?> |
| 1749 |
<p><?php |
| 1750 |
echo esc_html__( 'No events recorded yet.', 'wp-memory-usage' ); |
| 1751 |
echo "<hr>"; |
| 1752 |
|
| 1753 |
$opts = self::get_settings(); |
| 1754 |
|
| 1755 |
$count_off = 0; |
| 1756 |
|
| 1757 |
echo esc_html__('Log WARN', 'wp-memory-usage' ).": "; |
| 1758 |
if ($opts["logop_warn_pct"]=="0") { |
| 1759 |
echo '<span style="color: red;">'; |
| 1760 |
echo esc_html__('Off', 'wp-memory-usage' ); |
| 1761 |
echo "</span>"; |
| 1762 |
$count_off++; |
| 1763 |
} else { |
| 1764 |
echo esc_html__('On', 'wp-memory-usage' ); |
| 1765 |
} |
| 1766 |
echo "<p>"; |
| 1767 |
|
| 1768 |
echo esc_html__('Log DANGER', 'wp-memory-usage' ).": "; |
| 1769 |
if ($opts["logop_danger_pct"]=="0") { |
| 1770 |
echo '<span style="color: red;">'; |
| 1771 |
echo esc_html__('Off', 'wp-memory-usage' ); |
| 1772 |
echo "</span>"; |
| 1773 |
$count_off++; |
| 1774 |
} else { |
| 1775 |
echo esc_html__('On', 'wp-memory-usage' ); |
| 1776 |
} |
| 1777 |
echo "<p>"; |
| 1778 |
|
| 1779 |
echo esc_html__('Log CRITICAL', 'wp-memory-usage' ).": "; |
| 1780 |
if ($opts["logop_critical_pct"]=="0") { |
| 1781 |
echo '<span style="color: red;">'; |
| 1782 |
echo esc_html__('Off', 'wp-memory-usage' ); |
| 1783 |
echo "</span>"; |
| 1784 |
$count_off++; |
| 1785 |
} else { |
| 1786 |
echo esc_html__('On', 'wp-memory-usage' ); |
| 1787 |
} |
| 1788 |
/* |
| 1789 |
echo "<p>"; |
| 1790 |
|
| 1791 |
echo __('Log OK', 'wp-memory-usage' ).": ";; |
| 1792 |
if ($opts["log_ok"]=="0") { |
| 1793 |
echo '<span style="color: red;">'; |
| 1794 |
echo __('Off', 'wp-memory-usage' ); |
| 1795 |
echo "</span>"; |
| 1796 |
$count_off++; |
| 1797 |
} else { |
| 1798 |
echo __('On', 'wp-memory-usage' ); |
| 1799 |
} |
| 1800 |
*/ |
| 1801 |
|
| 1802 |
if ($count_off==3) { |
| 1803 |
echo "<hr>"; |
| 1804 |
echo '<span style="color: red;">'; |
| 1805 |
echo esc_html__('Logging switched off', 'wp-memory-usage' ); |
| 1806 |
echo "</span>"; |
| 1807 |
} |
| 1808 |
#echo "<hr>".print_r($opts, true); |
| 1809 |
?></p> |
| 1810 |
<?php else : ?> |
| 1811 |
<table class="widefat striped" style="max-width: 1200px;"> |
| 1812 |
<thead> |
| 1813 |
<tr> |
| 1814 |
<th><?php echo esc_html__( 'Time', 'wp-memory-usage' ); ?></th> |
| 1815 |
<th><?php echo esc_html__( 'State', 'wp-memory-usage' ); ?></th> |
| 1816 |
<th><?php echo esc_html__( 'Usage / Limit', 'wp-memory-usage' ); ?></th> |
| 1817 |
<th><?php echo esc_html__( 'Type', 'wp-memory-usage' ); ?></th> |
| 1818 |
<th><?php echo esc_html__( 'URI', 'wp-memory-usage' ); ?></th> |
| 1819 |
</tr> |
| 1820 |
</thead> |
| 1821 |
<tbody> |
| 1822 |
<?php foreach ( $log as $e1 ) : |
| 1823 |
$c = isset( $e1['c'] ) && is_array( $e1['c'] ) ? $e1['c'] : array(); |
| 1824 |
$lastmb = (int) $e1['l']; |
| 1825 |
$perc = $lastmb > 0 ? (int) ( ( (int) $e1['u'] ) / $lastmb * 100 ) : 0; |
| 1826 |
?> |
| 1827 |
<tr> |
| 1828 |
<td><?php echo esc_html( |
| 1829 |
wp_date( get_option('date_format') . ', ' . get_option('time_format'), (int) $e1['t'] ) ); |
| 1830 |
?></td> |
| 1831 |
<td><strong><?php echo esc_html( strtoupper( (string) $e1['s'] ) ); echo "<br>" . esc_html($perc) . "%"; ?></strong></td> |
| 1832 |
<td><?php echo esc_html( self::format_bytes( (int) $e1['u'] ) . ' / ' . ( (int) $e1['l'] > 0 ? self::format_bytes( (int) $e1['l'] ) : __( 'unlimited', 'wp-memory-usage' ) ) ); ?></td> |
| 1833 |
<td><?php echo esc_html( (string) ( isset( $c['type'] ) ? $c['type'] : '' ) ); ?></td> |
| 1834 |
<td><code><?php echo esc_html( (string) ( isset( $c['uri'] ) ? $c['uri'] : '' ) ); ?></code></td> |
| 1835 |
</tr> |
| 1836 |
<?php endforeach; ?> |
| 1837 |
</tbody> |
| 1838 |
</table> |
| 1839 |
<?php endif; ?> |
| 1840 |
<?php elseif ( 'check_installation' === $tab ) : |
| 1841 |
// ══════════════════════════════════════════════════════ |
| 1842 |
// CHECK INSTALLATION |
| 1843 |
// ══════════════════════════════════════════════════════ |
| 1844 |
global $wp_filesystem; |
| 1845 |
if ( ! function_exists( 'WP_Filesystem' ) ) { |
| 1846 |
require_once ABSPATH . 'wp-admin/includes/file.php'; |
| 1847 |
} |
| 1848 |
WP_Filesystem(); |
| 1849 |
|
| 1850 |
$checks = array(); // array of ['label', 'status' (ok|warn|error), 'detail', 'hint'] |
| 1851 |
|
| 1852 |
// ── 1. PHP-Mindestversion ────────────────────────────── |
| 1853 |
$php_min = '7.4.0'; |
| 1854 |
$php_rec = '8.0.0'; |
| 1855 |
$php_current = PHP_VERSION; |
| 1856 |
if ( version_compare( $php_current, $php_min, '<' ) ) { |
| 1857 |
$checks[] = array( |
| 1858 |
'status' => 'error', |
| 1859 |
'label' => __( 'PHP Version', 'wp-memory-usage' ), |
| 1860 |
'hint' => __( 'Upgrade PHP to at least 7.4. PHP 8.1+ is recommended.', 'wp-memory-usage' ), |
| 1861 |
'detail' => __( 'PHP', 'wp-memory-usage' ) . ' ' . $php_current . ' – ' . __( 'minimum required:', 'wp-memory-usage' ) . ' ' . $php_min, |
| 1862 |
|
| 1863 |
); |
| 1864 |
} elseif ( version_compare( $php_current, $php_rec, '<' ) ) { |
| 1865 |
$checks[] = array( |
| 1866 |
'label' => __( 'PHP Version', 'wp-memory-usage' ), |
| 1867 |
'status' => 'warn', |
| 1868 |
'detail' => __( 'PHP', 'wp-memory-usage' ) . ' ' . $php_current . ' – ' . __( 'works, but PHP', 'wp-memory-usage' ) . ' ' . $php_rec . '+ ' . __( 'is recommended (required for match-expressions)', 'wp-memory-usage' ), |
| 1869 |
'hint' => __( 'Consider upgrading to PHP 8.0 or higher for full feature support.', 'wp-memory-usage' ), |
| 1870 |
); |
| 1871 |
} else { |
| 1872 |
$checks[] = array( |
| 1873 |
'label' => __( 'PHP Version', 'wp-memory-usage' ), |
| 1874 |
'status' => 'ok', |
| 1875 |
'detail' => __( 'PHP', 'wp-memory-usage' ) . ' ' . $php_current . ': ' . __( 'OK', 'wp-memory-usage' ), |
| 1876 |
'hint' => '', |
| 1877 |
); |
| 1878 |
} |
| 1879 |
|
| 1880 |
// ── 2. PHP-Erweiterungen ─────────────────────────────── |
| 1881 |
foreach ( array( 'json', 'pcre' ) as $ext ) { |
| 1882 |
if ( extension_loaded( $ext ) ) { |
| 1883 |
$checks[] = array( |
| 1884 |
'label' => __( 'PHP extension:', 'wp-memory-usage' ) . ' ' . $ext, |
| 1885 |
'status' => 'ok', |
| 1886 |
'detail' => 'ext/' . $ext . ' ' . __( 'loaded', 'wp-memory-usage' ), |
| 1887 |
'hint' => '' |
| 1888 |
); |
| 1889 |
} else { |
| 1890 |
$checks[] = array( |
| 1891 |
'label' => __( 'PHP extension:', 'wp-memory-usage' ) . ' ' . $ext, |
| 1892 |
'status' => 'error', |
| 1893 |
'detail' => 'ext/' . $ext . ' ' . __( 'NOT loaded – required by this plugin', 'wp-memory-usage' ), |
| 1894 |
'hint' => 'ext/' . $ext . ' ' . __( 'in your php.ini.', 'wp-memory-usage' ) |
| 1895 |
); |
| 1896 |
} |
| 1897 |
} |
| 1898 |
|
| 1899 |
// ── 3. Log-Verzeichnis: vorhanden / erstellbar ───────── |
| 1900 |
$log_dir = self::WPMU_LOG_PATH; |
| 1901 |
if ( is_dir( $log_dir ) ) { |
| 1902 |
$checks[] = array( |
| 1903 |
'label' => __( 'Log directory exists', 'wp-memory-usage' ), |
| 1904 |
'status' => 'ok', |
| 1905 |
'detail' => esc_html( $log_dir ), |
| 1906 |
'hint' => '', |
| 1907 |
); |
| 1908 |
} else { |
| 1909 |
// Versuch, es anzulegen |
| 1910 |
if ( ! $wp_filesystem->is_dir( $log_dir ) ) { |
| 1911 |
$created = $wp_filesystem->mkdir( $log_dir, 0755 ); |
| 1912 |
} |
| 1913 |
#$created = @mkdir( $log_dir, 0755, true ); |
| 1914 |
|
| 1915 |
if ( $created ) { |
| 1916 |
$checks[] = array( |
| 1917 |
'label' => __( 'Log directory exists', 'wp-memory-usage' ), |
| 1918 |
'status' => 'warn', |
| 1919 |
'detail' => __('Directory did not exist – created now: ', 'wp-memory-usage' ) . esc_html( $log_dir ), |
| 1920 |
'hint' => __('Make sure this path persists across deployments.', 'wp-memory-usage' ), |
| 1921 |
); |
| 1922 |
} else { |
| 1923 |
$checks[] = array( |
| 1924 |
'label' => __( 'Log directory exists', 'wp-memory-usage' ), |
| 1925 |
'status' => 'error', |
| 1926 |
'detail' => __('Does NOT exist and could not be created: ', 'wp-memory-usage' ) . esc_html( $log_dir ), |
| 1927 |
'hint' => __('Create the directory manually and make it writable for the webserver user (e.g. www-data). Command: mkdir -p ', 'wp-memory-usage' ) . $log_dir . ' && chmod 755 ' . $log_dir, |
| 1928 |
); |
| 1929 |
} |
| 1930 |
} |
| 1931 |
|
| 1932 |
// ── 4. Log-Verzeichnis: schreibbar ──────────────────── |
| 1933 |
if ( is_dir( $log_dir ) ) { |
| 1934 |
#if ( is_writable( $log_dir ) ) { |
| 1935 |
if ( $wp_filesystem->is_writable( $log_dir ) ) { |
| 1936 |
$checks[] = array( |
| 1937 |
'label' => __('Log directory writable', 'wp-memory-usage' ), |
| 1938 |
'status' => 'ok', |
| 1939 |
'detail' => __('Directory is writable', 'wp-memory-usage' ), |
| 1940 |
'hint' => '' |
| 1941 |
); |
| 1942 |
} else { |
| 1943 |
$checks[] = array( |
| 1944 |
'label' => __('Log directory writable', 'wp-memory-usage' ), |
| 1945 |
'status' => 'error', |
| 1946 |
'detail' => __('Directory exists but is NOT writable: ', 'wp-memory-usage' ) . esc_html( $log_dir ), |
| 1947 |
'hint' => __('Run: chmod 755 ', 'wp-memory-usage' ) . $log_dir . __(' (or chown it to the webserver user)', 'wp-memory-usage' ), |
| 1948 |
); |
| 1949 |
} |
| 1950 |
} |
| 1951 |
|
| 1952 |
// ── 5. Log-Datei: schreiben & lesen ─────────────────── |
| 1953 |
if ( is_dir( $log_dir ) && $wp_filesystem->is_writable( $log_dir ) ) { |
| 1954 |
$test_file = $log_dir . 'wpmu-test-' . time() . '.tmp'; |
| 1955 |
$test_data = array( 'wpmu_test' => true, 'ts' => time() ); |
| 1956 |
// phpcs:disable WordPress.WP.AlternativeFunctions |
| 1957 |
$write_ok = @file_put_contents( $test_file, json_encode( $test_data ) . "\n" ); |
| 1958 |
// phpcs:enable WordPress.WP.AlternativeFunctions |
| 1959 |
if ( $write_ok !== false ) { |
| 1960 |
// phpcs:disable WordPress.WP.AlternativeFunctions |
| 1961 |
$read_back = @file_get_contents( $test_file ); |
| 1962 |
// phpcs:enable WordPress.WP.AlternativeFunctions |
| 1963 |
$parsed = json_decode( trim( $read_back ), true ); |
| 1964 |
wp_delete_file( $test_file ); |
| 1965 |
if ( is_array( $parsed ) && ! empty( $parsed['wpmu_test'] ) ) { |
| 1966 |
$checks[] = array( |
| 1967 |
'label' => __('Log file write & read test', 'wp-memory-usage' ), |
| 1968 |
'status' => 'ok', |
| 1969 |
'detail' => __('Write → read → parse: OK', 'wp-memory-usage' ), |
| 1970 |
'hint' => '' ); |
| 1971 |
} else { |
| 1972 |
$checks[] = array( |
| 1973 |
'label' => __('Log file write & read test', 'wp-memory-usage' ), |
| 1974 |
'status' => 'error', |
| 1975 |
'detail' => __('File written but JSON parse failed', 'wp-memory-usage' ), |
| 1976 |
'hint' => __('Check filesystem for corruption or encoding issues.', 'wp-memory-usage' ) |
| 1977 |
); |
| 1978 |
} |
| 1979 |
} else { |
| 1980 |
$checks[] = array( |
| 1981 |
'label' => __('Log file write & read test', 'wp-memory-usage' ), |
| 1982 |
'status' => 'error', |
| 1983 |
'detail' => __('Could not write test file to ', 'wp-memory-usage' ) . esc_html( $log_dir ), |
| 1984 |
'hint' => __('Check directory permissions and available disk space.', 'wp-memory-usage' ) |
| 1985 |
); |
| 1986 |
} |
| 1987 |
} else { |
| 1988 |
$checks[] = array( |
| 1989 |
'label' => __('Log file write & read test', 'wp-memory-usage' ), |
| 1990 |
'status' => 'error', |
| 1991 |
'detail' => __('Skipped – log directory not writable', 'wp-memory-usage' ), |
| 1992 |
'hint' => __('Fix the log directory issue first.', 'wp-memory-usage' ) |
| 1993 |
); |
| 1994 |
} |
| 1995 |
|
| 1996 |
// ── 6. Vorhandene Logdateien ────────────────────────── |
| 1997 |
if ( is_dir( $log_dir ) ) { |
| 1998 |
$log_file = $log_dir . self::WPMU_LOG_FILE; |
| 1999 |
$digest_files = glob( $log_dir . 'digest_*.cgibak' ); |
| 2000 |
$backup_files = glob( $log_dir . 'wpmu-log_*.cgibak' ); |
| 2001 |
$n_digest = is_array( $digest_files ) ? count( $digest_files ) : 0; |
| 2002 |
$n_backup = is_array( $backup_files ) ? count( $backup_files ) : 0; |
| 2003 |
$log_exists = file_exists( $log_file ); |
| 2004 |
$log_size = $log_exists ? size_format( filesize( $log_file ) ) : '–'; |
| 2005 |
$detail = |
| 2006 |
__( 'Active log:', 'wp-memory-usage' ) . ' ' . |
| 2007 |
( $log_exists ? __( 'exists', 'wp-memory-usage' ) : __( 'not yet created', 'wp-memory-usage' ) ) . |
| 2008 |
' (' . __( 'size:', 'wp-memory-usage' ) . ' ' . $log_size . ') | ' . |
| 2009 |
__( 'Digest files:', 'wp-memory-usage' ) . ' ' . $n_digest . ' | ' . |
| 2010 |
__( 'Backup log files:', 'wp-memory-usage' ) . ' ' . $n_backup; |
| 2011 |
|
| 2012 |
$checks[] = array( |
| 2013 |
'label' => __('Log files overview', 'wp-memory-usage' ), |
| 2014 |
'status' => 'ok', |
| 2015 |
'detail' => $detail, |
| 2016 |
'hint' => $n_digest === 0 ? __('No digest files yet – they are created automatically on the first cron run.', 'wp-memory-usage' ) : '', |
| 2017 |
); |
| 2018 |
} |
| 2019 |
|
| 2020 |
// ── 7. WP-Cron aktiv ────────────────────────────────── |
| 2021 |
if ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) { |
| 2022 |
$checks[] = array( |
| 2023 |
'label' => 'WP-Cron', |
| 2024 |
'status' => 'warn', |
| 2025 |
'detail' => __('DISABLE_WP_CRON is TRUE – WP-Cron is disabled', 'wp-memory-usage' ), |
| 2026 |
'hint' => __('This plugin uses WP-Cron for log rotation and digest creation. Make sure a real system cron runs wp-cron.php regularly (e.g. every 5 minutes), otherwise no digests will be created.', 'wp-memory-usage' ), |
| 2027 |
); |
| 2028 |
} else { |
| 2029 |
$next_cleanup = wp_next_scheduled( 'wpmu_cleanup_hook' ); |
| 2030 |
if ( $next_cleanup ) { |
| 2031 |
$checks[] = array( |
| 2032 |
'label' => __('WP-Cron: cleanup hook scheduled', 'wp-memory-usage' ), |
| 2033 |
'status' => 'ok', |
| 2034 |
'detail' => __('Next run: ', 'wp-memory-usage' ) . wp_date( get_option('date_format') . ', ' . get_option('time_format'), $next_cleanup ), |
| 2035 |
'hint' => '', |
| 2036 |
); |
| 2037 |
} else { |
| 2038 |
$checks[] = array( |
| 2039 |
'label' => __('WP-Cron: cleanup hook scheduled', 'wp-memory-usage' ), |
| 2040 |
'status' => 'warn', |
| 2041 |
'detail' => __('wpmu_cleanup_hook is NOT scheduled', 'wp-memory-usage' ), |
| 2042 |
'hint' => __('Deactivate and reactivate the plugin, or visit the settings page once to trigger rescheduling.', 'wp-memory-usage' ), |
| 2043 |
); |
| 2044 |
} |
| 2045 |
} |
| 2046 |
|
| 2047 |
// ── 8. wp_mail verfügbar ────────────────────────────── |
| 2048 |
if ( function_exists( 'wp_mail' ) ) { |
| 2049 |
$opts_chk = self::get_settings(); |
| 2050 |
$mail_en = ! empty( $opts_chk['send_email'] ); |
| 2051 |
$mail_to = $opts_chk['email_to'] ?? ''; |
| 2052 |
$mail_valid = is_email( $mail_to ); |
| 2053 |
if ( $mail_en && $mail_valid ) { |
| 2054 |
$checks[] = array( |
| 2055 |
'label' => __('Email alerts', 'wp-memory-usage' ), |
| 2056 |
'status' => 'ok', |
| 2057 |
'detail' => __('Enabled, recipient: ', 'wp-memory-usage' ) . esc_html( $mail_to ), |
| 2058 |
'hint' => '' |
| 2059 |
); |
| 2060 |
} elseif ( $mail_en && ! $mail_valid ) { |
| 2061 |
$checks[] = array( |
| 2062 |
'label' => __('Email alerts', 'wp-memory-usage' ), |
| 2063 |
'status' => 'error', |
| 2064 |
'detail' => __('Enabled but recipient address is invalid: "', 'wp-memory-usage' ) . esc_html( $mail_to ) . '"', |
| 2065 |
'hint' => __('Enter a valid email address in Settings.' , 'wp-memory-usage' ) |
| 2066 |
); |
| 2067 |
} else { |
| 2068 |
$checks[] = array( |
| 2069 |
'label' => __( 'Email alerts', 'wp-memory-usage' ), |
| 2070 |
'status' => 'warn', |
| 2071 |
'detail' => __( 'Disabled in settings', 'wp-memory-usage' ), |
| 2072 |
'hint' => __( 'Enable "Send email alerts" in Settings to receive notifications.', 'wp-memory-usage' ), |
| 2073 |
); |
| 2074 |
} |
| 2075 |
} else { |
| 2076 |
$checks[] = array( |
| 2077 |
'label' => __( 'Email alerts', 'wp-memory-usage' ), |
| 2078 |
'status' => 'error', |
| 2079 |
'detail' => __( 'wp_mail() not available', 'wp-memory-usage' ), |
| 2080 |
'hint' => __( 'Email sending is not functional on this installation.', 'wp-memory-usage' ), |
| 2081 |
); |
| 2082 |
} |
| 2083 |
|
| 2084 |
// ── 9. memory_get_peak_usage verfügbar ──────────────── |
| 2085 |
if ( function_exists( 'memory_get_peak_usage' ) ) { |
| 2086 |
$checks[] = array( |
| 2087 |
'label' => 'memory_get_peak_usage()', |
| 2088 |
'status' => 'ok', |
| 2089 |
'detail' => __('Function available – current peak: ', 'wp-memory-usage' ) . size_format( memory_get_peak_usage( true ) ), |
| 2090 |
'hint' => '' ); |
| 2091 |
} else { |
| 2092 |
$checks[] = array( |
| 2093 |
'label' => __('memory_get_peak_usage()', 'wp-memory-usage' ), |
| 2094 |
'status' => 'warn', |
| 2095 |
'detail' => __('Not available on this PHP build', 'wp-memory-usage' ), |
| 2096 |
'hint' => __('Peak memory tracking is disabled. The plugin falls back to memory_get_usage().', 'wp-memory-usage' ) |
| 2097 |
); |
| 2098 |
} |
| 2099 |
|
| 2100 |
// ── 10. Disk-Space ──────────────────────────────────── |
| 2101 |
if ( is_dir( $log_dir ) ) { |
| 2102 |
$free = @disk_free_space( $log_dir ); |
| 2103 |
if ( $free !== false ) { |
| 2104 |
$status_disk = $free < 10 * 1024 * 1024 ? 'warn' : 'ok'; // < 10 MB |
| 2105 |
$checks[] = array( |
| 2106 |
'label' => __('Disk space (log directory)', 'wp-memory-usage' ), |
| 2107 |
'status' => $status_disk, |
| 2108 |
'detail' => __('Free: ', 'wp-memory-usage' ) . size_format( $free ), |
| 2109 |
'hint' => $status_disk === 'warn' ? __('Very little free disk space – log files may not be written.', 'wp-memory-usage' ) : '', |
| 2110 |
); |
| 2111 |
} |
| 2112 |
} |
| 2113 |
|
| 2114 |
// ── Render ───────────────────────────────────────────── |
| 2115 |
$count_ok = count( array_filter( $checks, fn( $c ) => $c['status'] === 'ok' ) ); |
| 2116 |
$count_warn = count( array_filter( $checks, fn( $c ) => $c['status'] === 'warn' ) ); |
| 2117 |
$count_error = count( array_filter( $checks, fn( $c ) => $c['status'] === 'error' ) ); |
| 2118 |
|
| 2119 |
$overall_color = $count_error > 0 ? '#8B0000' : ( $count_warn > 0 ? '#7a5200' : '#2d6a2d' ); |
| 2120 |
$overall_bg = $count_error > 0 ? '#fdf2f2' : ( $count_warn > 0 ? '#fef9ec' : '#f2faf2' ); |
| 2121 |
$overall_icon = $count_error > 0 ? '🆘' : ( $count_warn > 0 ? '⚠️' : '� |
| 2122 |
' ); |
| 2123 |
$overall_text = $count_error > 0 |
| 2124 |
? $count_error . ' ' . __( 'error(s),', 'wp-memory-usage' ) . ' ' . $count_warn . ' ' . __( 'warning(s)', 'wp-memory-usage' ) |
| 2125 |
: ( $count_warn > 0 |
| 2126 |
? $count_warn . ' ' . __( 'warning(s) – no errors', 'wp-memory-usage' ) |
| 2127 |
: __( 'All checks passed', 'wp-memory-usage' ) ); |
| 2128 |
?> |
| 2129 |
<h2><?php echo esc_html__( 'Check Installation', 'wp-memory-usage' ); ?></h2> |
| 2130 |
<p><?php echo esc_html__( 'This tab checks whether the plugin can work correctly on this server.', 'wp-memory-usage' ); ?></p> |
| 2131 |
|
| 2132 |
<div style="display:inline-flex;align-items:center;gap:10px;padding:10px 18px;border-radius:6px;margin-bottom:18px;background:<?php echo esc_attr( $overall_bg ); ?>;border:2px solid <?php echo esc_attr( $overall_color ); ?>;color:<?php echo esc_attr( $overall_color ); ?>;font-weight:700;font-size:14px;"> |
| 2133 |
<?php echo esc_html($overall_icon); ?> <?php echo esc_html( $overall_text ); ?> |
| 2134 |
| � |
| 2135 |
<?php echo (int) $count_ok; ?> |
| 2136 |
⚠️ <?php echo (int) $count_warn; ?> |
| 2137 |
🆘 <?php echo (int) $count_error; ?> |
| 2138 |
</div> |
| 2139 |
|
| 2140 |
<table class="widefat striped" style="max-width:1100px;"> |
| 2141 |
<thead> |
| 2142 |
<tr> |
| 2143 |
<th style="width:24px;"></th> |
| 2144 |
<th style="width:280px;"><?php echo esc_html__( 'Check', 'wp-memory-usage' ); ?></th> |
| 2145 |
<th><?php echo esc_html__( 'Result', 'wp-memory-usage' ); ?></th> |
| 2146 |
<th><?php echo esc_html__( 'Hint / Action', 'wp-memory-usage' ); ?></th> |
| 2147 |
</tr> |
| 2148 |
</thead> |
| 2149 |
<tbody> |
| 2150 |
<?php foreach ( $checks as $chk ) : |
| 2151 |
$icon = $chk['status'] === 'ok' ? '� |
| 2152 |
' : ( $chk['status'] === 'warn' ? '⚠️' : '🆘' ); |
| 2153 |
$color = $chk['status'] === 'ok' ? '' : ( $chk['status'] === 'warn' ? '#7a5200' : '#8B0000' ); |
| 2154 |
$bg = $chk['status'] === 'ok' ? '' : ( $chk['status'] === 'warn' ? '#fef9ec' : '#fdf2f2' ); |
| 2155 |
?> |
| 2156 |
<tr<?php echo $bg ? ' style="background:' . esc_attr( $bg ) . ';"' : ''; ?>> |
| 2157 |
<td style="text-align:center;font-size:16px;"><?php echo esc_html($icon); ?></td> |
| 2158 |
<td><strong style="color:<?php echo esc_attr( $color ); ?>"><?php echo esc_html( $chk['label'] ); ?></strong></td> |
| 2159 |
<td style="font-family:monospace;font-size:12px;color:<?php echo esc_attr( $color ); ?>"><?php echo esc_html( $chk['detail'] ); ?></td> |
| 2160 |
<td style="font-size:12px;color:#555;"><?php echo esc_html( $chk['hint'] ); ?></td> |
| 2161 |
</tr> |
| 2162 |
<?php endforeach; ?> |
| 2163 |
</tbody> |
| 2164 |
</table> |
| 2165 |
|
| 2166 |
<?php endif; ?> |
| 2167 |
</div> |
| 2168 |
<?php |
| 2169 |
} |
| 2170 |
|
| 2171 |
/** ---------------- Helpers ---------------- */ |
| 2172 |
private static function format_bytes( $bytes ) { |
| 2173 |
$bytes = (int) $bytes; |
| 2174 |
if ( $bytes <= 0 ) { return '0 B'; } |
| 2175 |
$units = array( 'B', 'KB', 'MB', 'GB', 'TB' ); |
| 2176 |
$idx = 0; |
| 2177 |
$val = (float) $bytes; |
| 2178 |
while ( $val >= 1024 && $idx < count( $units ) - 1 ) { $val /= 1024; $idx++; } |
| 2179 |
return sprintf( $idx === 0 ? '%.0f %s' : '%.1f %s', $val, $units[ $idx ] ); |
| 2180 |
} |
| 2181 |
|
| 2182 |
private static function format_pct( $usage, $limit ) { |
| 2183 |
if ( $limit <= 0 ) { return 'n/a'; } |
| 2184 |
return sprintf( '%.1f%%', ( $usage / $limit ) * 100 ); |
| 2185 |
} |
| 2186 |
|
| 2187 |
private static function get_effective_memory_limit_bytes() { |
| 2188 |
$php_raw = ini_get( 'memory_limit' ); |
| 2189 |
$php = self::parse_size_to_bytes( is_string( $php_raw ) ? $php_raw : '' ); |
| 2190 |
$wp_raw = defined( 'WP_MEMORY_LIMIT' ) ? (string) WP_MEMORY_LIMIT : ''; |
| 2191 |
$wp = self::parse_size_to_bytes( $wp_raw ); |
| 2192 |
if ( '-1' === (string) $php_raw || -1 === $php ) { |
| 2193 |
return $wp > 0 ? $wp : 0; |
| 2194 |
} |
| 2195 |
if ( $php > 0 && $wp > 0 ) { return min( $php, $wp ); } |
| 2196 |
return $php > 0 ? $php : ( $wp > 0 ? $wp : 0 ); |
| 2197 |
} |
| 2198 |
|
| 2199 |
private static function parse_size_to_bytes( $val ) { |
| 2200 |
$val = trim( (string) $val ); |
| 2201 |
if ( '' === $val ) { return 0; } |
| 2202 |
if ( '-1' === $val ) { return -1; } |
| 2203 |
if ( ctype_digit( $val ) ) { return (int) $val; } |
| 2204 |
$unit = strtoupper( substr( $val, -1 ) ); |
| 2205 |
$num = substr( $val, 0, -1 ); |
| 2206 |
if ( ! is_numeric( $num ) ) { return 0; } |
| 2207 |
$n = (float) $num; |
| 2208 |
switch ( $unit ) { |
| 2209 |
case 'K': return (int) round( $n * 1024 ); |
| 2210 |
case 'M': return (int) round( $n * 1024 * 1024 ); |
| 2211 |
case 'G': return (int) round( $n * 1024 * 1024 * 1024 ); |
| 2212 |
case 'T': return (int) round( $n * 1024 * 1024 * 1024 * 1024 ); |
| 2213 |
default: return 0; |
| 2214 |
} |
| 2215 |
} |
| 2216 |
|
| 2217 |
/** ---------------- Threshold evaluation ---------------- */ |
| 2218 |
private static function state_for_usage( $usage, $limit, $opts ) { |
| 2219 |
if ( $limit <= 0 ) { return 'ok'; } |
| 2220 |
$ratio = $usage / $limit; |
| 2221 |
$warn = max( 0.01, ( (int) $opts['warn_pct'] ) / 100 ); |
| 2222 |
$danger = max( 0.01, ( (int) $opts['danger_pct'] ) / 100 ); |
| 2223 |
$critical = max( 0.01, ( (int) $opts['critical_pct'] ) / 100 ); |
| 2224 |
if ( $ratio >= $critical ) { return 'critical'; } |
| 2225 |
if ( $ratio >= $danger ) { return 'danger'; } |
| 2226 |
if ( $ratio >= $warn ) { return 'warn'; } |
| 2227 |
return 'ok'; |
| 2228 |
} |
| 2229 |
|
| 2230 |
private static function state_rank( $state ) { |
| 2231 |
switch ( $state ) { |
| 2232 |
case 'warn': return 1; |
| 2233 |
case 'danger': return 2; |
| 2234 |
case 'critical': return 3; |
| 2235 |
default: return 0; |
| 2236 |
} |
| 2237 |
} |
| 2238 |
|
| 2239 |
private static function get_current_memory_bytes( $use_peak ) { |
| 2240 |
if ( $use_peak && function_exists( 'memory_get_peak_usage' ) ) { |
| 2241 |
return (int) memory_get_peak_usage( true ); |
| 2242 |
} |
| 2243 |
return (int) memory_get_usage( true ); |
| 2244 |
} |
| 2245 |
|
| 2246 |
} // end class |
| 2247 |
endif; |
| 2248 |
// Boot |
| 2249 |
#WPMU_Threshold_Alerts::init(); |
| 2250 |
|