Changeset 3383675
- Timestamp:
- 10/24/2025 12:21:31 AM (5 months ago)
- Location:
- sitepulse
- Files:
-
- 66 added
- 16 edited
-
assets/screenshot-3.png (modified) (previous)
-
assets/screenshot-4.png (modified) (previous)
-
assets/screenshot-5.png (modified) (previous)
-
tags/1.1.0 (added)
-
tags/1.1.0/assets (added)
-
tags/1.1.0/assets/css (added)
-
tags/1.1.0/assets/css/backend.css (added)
-
tags/1.1.0/assets/css/backend_global.css (added)
-
tags/1.1.0/assets/css/frontend.css (added)
-
tags/1.1.0/assets/css/sitepulse_general.css (added)
-
tags/1.1.0/assets/externals (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/css (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/css/bootstrap.min.css (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/css/bootstrap.min.css.map (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/js (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/js/bootstrap.bundle.js (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/js/bootstrap.bundle.js.map (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/js/bootstrap.bundle.min.js (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/js/bootstrap.bundle.min.js.map (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/js/bootstrap.esm.js (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/js/bootstrap.esm.js.map (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/js/bootstrap.esm.min.js (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/js/bootstrap.esm.min.js.map (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/js/bootstrap.js (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/js/bootstrap.js.map (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/js/bootstrap.min.js (added)
-
tags/1.1.0/assets/externals/bootstrap-5.3.8-dist/js/bootstrap.min.js.map (added)
-
tags/1.1.0/assets/img (added)
-
tags/1.1.0/assets/img/only-logo.png (added)
-
tags/1.1.0/assets/img/sitepulse-file.svg (added)
-
tags/1.1.0/assets/img/sitepulse-logo.png (added)
-
tags/1.1.0/assets/img/sitepulse_120x120.png (added)
-
tags/1.1.0/assets/img/sitepulse_12x12.svg (added)
-
tags/1.1.0/assets/img/sitepulse_34x34.svg (added)
-
tags/1.1.0/assets/js (added)
-
tags/1.1.0/assets/js/backend.js (added)
-
tags/1.1.0/assets/js/frontend.js (added)
-
tags/1.1.0/assets/js/sitepulse_global.js (added)
-
tags/1.1.0/class (added)
-
tags/1.1.0/class/ProMiniProfiler.php (added)
-
tags/1.1.0/class/backend.php (added)
-
tags/1.1.0/class/curloader.php (added)
-
tags/1.1.0/class/frontend.php (added)
-
tags/1.1.0/class/plugin.php (added)
-
tags/1.1.0/class/profiler.php (added)
-
tags/1.1.0/class/settings.php (added)
-
tags/1.1.0/class/setup.php (added)
-
tags/1.1.0/inc (added)
-
tags/1.1.0/inc/api_backend.php (added)
-
tags/1.1.0/languages (added)
-
tags/1.1.0/languages/sitepulse.pot (added)
-
tags/1.1.0/loader.php (added)
-
tags/1.1.0/readme.txt (added)
-
tags/1.1.0/templates (added)
-
tags/1.1.0/templates/backend (added)
-
tags/1.1.0/templates/backend/curl_api.php (added)
-
tags/1.1.0/templates/backend/dashboard.php (added)
-
tags/1.1.0/templates/backend/header.php (added)
-
tags/1.1.0/templates/backend/resource_load.php (added)
-
tags/1.1.0/templates/backend/settings.php (added)
-
tags/1.1.0/templates/frontend (added)
-
tags/1.1.0/templates/frontend/frontend.php (added)
-
tags/1.1.0/uninstall.php (added)
-
trunk/assets/css/backend.css (modified) (2 diffs)
-
trunk/assets/css/frontend.css (modified) (1 diff)
-
trunk/assets/css/sitepulse_general.css (modified) (1 diff)
-
trunk/assets/js/backend.js (modified) (3 diffs)
-
trunk/assets/js/frontend.js (modified) (2 diffs)
-
trunk/class/ProMiniProfiler.php (added)
-
trunk/class/backend.php (modified) (4 diffs)
-
trunk/class/frontend.php (modified) (7 diffs)
-
trunk/class/profiler.php (modified) (4 diffs)
-
trunk/class/settings.php (added)
-
trunk/inc/api_backend.php (modified) (2 diffs)
-
trunk/loader.php (modified) (4 diffs)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/templates/backend/dashboard.php (modified) (1 diff)
-
trunk/templates/backend/resource_load.php (modified) (2 diffs)
-
trunk/templates/backend/settings.php (added)
-
trunk/templates/frontend (added)
-
trunk/templates/frontend/frontend.php (added)
Legend:
- Unmodified
- Added
- Removed
-
sitepulse/trunk/assets/css/backend.css
r3374045 r3383675 14 14 flex-direction: column; 15 15 } 16 } 17 18 body.sitepulse_css_class .notice, 19 body.sitepulse_css_class .notice * { 20 color: white; 21 background-color: #283746; 16 22 } 17 23 … … 248 254 #pulseLine { transition:stroke 0.3s; } 249 255 body.dark-mode #pulseLine { stroke:#ffb347; } 256 257 258 /* ========================================== 259 SETTINGS PAGE STYLES 260 Following SitePulse design patterns 261 ========================================== */ 262 263 .sitepulse_css_class select { 264 background-color: var(--sp-card-2); 265 color: var(--sp-text); 266 border: 1px solid rgba(255,255,255,0.1); 267 border-radius: 6px; 268 padding: 8px 12px; 269 min-width: 200px; 270 cursor: pointer; 271 transition: all 0.3s ease; 272 } 273 274 .sitepulse_css_class select:focus { 275 outline: none; 276 border-color: var(--accent-1); 277 box-shadow: 0 0 0 2px rgba(0,194,168,0.2); 278 } 279 280 .sitepulse_css_class select option { 281 background-color: var(--sp-card); 282 color: var(--sp-text); 283 padding: 10px; 284 } 285 286 287 /* Email blocking mode row styling */ 288 .sitepulse_css_class #email_blocking_mode_row { 289 background: rgba(0,194,168,0.05); 290 border-left: 3px solid var(--accent-1); 291 border-radius: 0 8px 8px 0; 292 margin-left: 20px; 293 transition: all 0.3s ease; 294 } 295 296 .sitepulse_css_class #email_blocking_mode_row.hidden { 297 opacity: 0; 298 max-height: 0; 299 overflow: hidden; 300 margin: 0 0 0 20px; 301 padding: 0; 302 } 303 304 /* Submit button enhancement */ 305 .sitepulse_css_class .button-primary { 306 background: linear-gradient(135deg, var(--accent-1), var(--accent-2)) !important; 307 border: none !important; 308 color: white !important; 309 padding: 10px 20px !important; 310 border-radius: 6px !important; 311 font-weight: 500 !important; 312 text-shadow: none !important; 313 box-shadow: 0 4px 15px rgba(0,194,168,0.3) !important; 314 transition: all 0.3s ease !important; 315 } 316 317 .sitepulse_css_class .button-primary:hover { 318 background: linear-gradient(135deg, var(--accent-2), var(--accent-1)) !important; 319 box-shadow: 0 6px 20px rgba(0,194,168,0.4) !important; 320 transform: translateY(-1px) !important; 321 } -
sitepulse/trunk/assets/css/frontend.css
r3374045 r3383675 1 /* Admin real time tracking button styles */ 2 .sitepulse-realtime-tracking { 3 display: flex; 4 justify-content: center; 5 } 6 7 .sitepulse-realtime-tracking .sitepulse-btn { 8 border-radius: 5px !important; 9 padding: 2px 10px !important; 10 margin: 5px 0 !important; 11 } 12 13 /* SitePulse Completion Info Styles - Matching Modal Theme */ 14 #sitepulse-completion-info { 15 background: #f8f9fa; 16 border: 1px solid #e9ecef; 17 border-radius: 8px; 18 padding: 20px; 19 margin: 20px 0; 20 color: #555; 21 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 22 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 23 max-height: 400px; 24 overflow-y: auto; 25 /* border-left: 4px solid #cc1111; */ 26 } 27 28 #sitepulse-completion-info::-webkit-scrollbar { 29 width: 6px; 30 } 31 32 #sitepulse-completion-info::-webkit-scrollbar-track { 33 background: #f1f1f1; 34 border-radius: 3px; 35 } 36 37 .sitepulse-event { 38 background: #ffffff; 39 border: 1px solid #e9ecef; 40 border-radius: 8px; 41 padding: 15px; 42 margin-bottom: 15px; 43 position: relative; 44 transition: all 0.2s ease; 45 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); 46 } 47 48 .sitepulse-event:hover { 49 border-color: #cc1111; 50 transform: translateY(-1px); 51 box-shadow: 0 3px 8px rgba(204, 17, 17, 0.15); 52 } 53 54 .sitepulse-event:last-child { 55 margin-bottom: 0; 56 } 57 58 .sitepulse-event::before { 59 content: ''; 60 position: absolute; 61 top: 0; 62 left: 0; 63 width: 3px; 64 height: 100%; 65 background: #cc1111; 66 border-radius: 3px 0 0 3px; 67 } 68 69 .sitepulse-event strong { 70 color: #cc1111; 71 font-weight: 600; 72 margin-right: 8px; 73 min-width: 90px; 74 display: inline-block; 75 font-size: 13px; 76 } 77 78 .sitepulse-event br { 79 margin-bottom: 6px; 80 } 81 82 /* Value styling for readability */ 83 .sitepulse-event { 84 line-height: 1.5; 85 font-size: 14px; 86 color: #555; 87 } 88 89 /* Responsive design */ 90 @media (max-width: 600px) { 91 #sitepulse-completion-info { 92 padding: 15px; 93 margin: 15px 0; 94 font-size: 13px; 95 } 96 97 .sitepulse-event { 98 padding: 12px; 99 margin-bottom: 12px; 100 } 101 102 .sitepulse-event strong { 103 display: block; 104 margin-bottom: 3px; 105 min-width: auto; 106 font-size: 12px; 107 } 108 109 .sitepulse-event br { 110 display: none; 111 } 112 } 113 114 /* Animation for new events */ 115 @keyframes slideInFromTop { 116 from { 117 opacity: 0; 118 transform: translateY(-10px); 119 } 120 to { 121 opacity: 1; 122 transform: translateY(0); 123 } 124 } 125 126 .sitepulse-event { 127 animation: slideInFromTop 0.3s ease-out; 128 } 129 130 /* Loading state styling */ 131 #sitepulse-completion-info.loading { 132 position: relative; 133 overflow: hidden; 134 } 135 136 #sitepulse-completion-info.loading::after { 137 content: ''; 138 position: absolute; 139 top: 0; 140 left: -100%; 141 width: 100%; 142 height: 100%; 143 background: linear-gradient(90deg, transparent, rgba(102, 126, 234, 0.1), transparent); 144 animation: shimmer 1.5s infinite; 145 } 146 147 @keyframes shimmer { 148 0% { left: -100%; } 149 100% { left: 100%; } 150 } 151 152 /* Modal CSS */ 153 154 .sitepulse-modal { 155 display: none; 156 position: fixed; 157 z-index: 999; 158 left: 0; top: 0; 159 width: 100%; height: 100%; 160 overflow: auto; 161 background: rgba(0,0,0,0.8); 162 backdrop-filter: blur(2px); 163 } 164 165 .sitepulse-modal-content { 166 background: #fff; 167 margin: 8% auto; 168 padding: 0; 169 border-radius: 12px; 170 width: 480px; 171 max-width: 90vw; 172 position: relative; 173 box-shadow: 0 10px 30px rgba(0,0,0,0.3); 174 animation: modalSlideIn 0.3s ease-out; 175 } 176 177 @keyframes modalSlideIn { 178 from { 179 opacity: 0; 180 transform: translateY(-50px) scale(0.9); 181 } 182 to { 183 opacity: 1; 184 transform: translateY(0) scale(1); 185 } 186 } 187 188 .sitepulse-close-btn { 189 color: #999; 190 position: absolute; 191 top: 15px; right: 20px; 192 font-size: 24px; 193 font-weight: bold; 194 cursor: pointer; 195 z-index: 1000; 196 transition: color 0.2s ease; 197 } 198 199 .sitepulse-close-btn:hover { 200 color: #333; 201 } 202 203 .sitepulse-modal-header { 204 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 205 color: white; 206 padding: 25px 30px 20px; 207 border-radius: 12px 12px 0 0; 208 position: relative; 209 } 210 211 .sitepulse-modal-header h2 { 212 margin: 0 0 15px 0; 213 font-size: 24px; 214 font-weight: 600; 215 padding-right: 40px; 216 } 217 218 .sitepulse-status-indicator { 219 display: flex; 220 align-items: center; 221 gap: 8px; 222 font-size: 14px; 223 opacity: 0.9; 224 } 225 226 .sitepulse-status-dot { 227 width: 10px; 228 height: 10px; 229 border-radius: 50%; 230 transition: all 0.3s ease; 231 } 232 233 .sitepulse-status-dot.idle { 234 background: #95a5a6; 235 } 236 237 .sitepulse-status-dot.tracking { 238 background: #27ae60; 239 animation: pulse 2s infinite; 240 } 241 242 .sitepulse-status-dot.error { 243 background: #e74c3c; 244 } 245 246 @keyframes pulse { 247 0% { opacity: 1; } 248 50% { opacity: 0.5; } 249 100% { opacity: 1; } 250 } 251 252 .sitepulse-modal-body { 253 padding: 30px; 254 } 255 256 .sitepulse-modal-content-section { 257 transition: opacity 0.3s ease, height 0.3s ease; 258 } 259 260 .sitepulse-tracking-description { 261 color: #666; 262 margin: 0 0 15px 0; 263 font-size: 16px; 264 line-height: 1.5; 265 } 266 267 .sitepulse-tracking-description.setup-info { 268 color: #555; 269 font-size: 14px; 270 line-height: 1.6; 271 margin: 0 0 25px 0; 272 padding: 15px; 273 background: #f0f7ff; 274 border-radius: 8px; 275 border-left: 4px solid #667eea; 276 } 277 278 .sitepulse-tracking-description.current-page { 279 color: #333; 280 font-weight: 500; 281 margin: 0 0 20px 0; 282 } 283 284 .sitepulse-tracking-description.tracking-explanation { 285 color: #555; 286 font-size: 14px; 287 line-height: 1.6; 288 margin: 0 0 25px 0; 289 padding: 15px; 290 background: #f8fdf8; 291 border-radius: 8px; 292 border-left: 4px solid #27ae60; 293 } 294 295 #current-page-name { 296 color: #667eea; 297 font-weight: 600; 298 } 299 300 .sitepulse-tracking-controls { 301 display: flex; 302 gap: 15px; 303 margin-bottom: 25px; 304 } 305 306 .sitepulse-btn { 307 display: flex; 308 align-items: center; 309 gap: 8px; 310 padding: 12px 24px; 311 border: none; 312 border-radius: 8px; 313 font-size: 16px; 314 font-weight: 500; 315 cursor: pointer; 316 transition: all 0.2s ease; 317 min-width: 140px; 318 justify-content: center; 319 } 320 321 .sitepulse-btn-primary { 322 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 323 color: white; 324 } 325 326 .sitepulse-btn-primary:hover { 327 transform: translateY(-2px); 328 box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); 329 } 330 331 .sitepulse-btn-secondary { 332 background: #e74c3c; 333 color: white; 334 } 335 336 .sitepulse-btn-secondary:hover { 337 background: #c0392b; 338 transform: translateY(-2px); 339 box-shadow: 0 5px 15px rgba(231, 76, 60, 0.4); 340 } 341 342 .sitepulse-btn:disabled { 343 opacity: 0.6; 344 cursor: not-allowed; 345 transform: none !important; 346 box-shadow: none !important; 347 } 348 349 .sitepulse-btn-icon { 350 font-size: 18px; 351 } 352 353 .sitepulse-tracking-info { 354 background: #f8f9fa; 355 border-radius: 8px; 356 padding: 20px; 357 border-left: 4px solid #667eea; 358 } 359 360 .sitepulse-info-grid { 361 display: grid; 362 grid-template-columns: 1fr 1fr 1fr; 363 gap: 15px; 364 } 365 366 .sitepulse-info-item { 367 text-align: center; 368 } 369 370 .sitepulse-info-label { 371 display: block; 372 font-size: 12px; 373 color: #666; 374 text-transform: uppercase; 375 letter-spacing: 0.5px; 376 margin-bottom: 5px; 377 } 378 379 .sitepulse-info-value { 380 display: block; 381 font-size: 18px; 382 font-weight: 600; 383 color: #333; 384 } 385 386 /* Completion Modal Styles */ 387 .sitepulse-completion-animation { 388 display: flex; 389 justify-content: center; 390 margin-bottom: 20px; 391 } 392 393 .sitepulse-checkmark-circle { 394 width: 80px; 395 height: 80px; 396 border-radius: 50%; 397 background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%); 398 display: flex; 399 align-items: center; 400 justify-content: center; 401 box-shadow: 0 5px 20px rgba(46, 204, 113, 0.3); 402 animation: scaleIn 0.6s ease-out; 403 } 404 405 .sitepulse-checkmark { 406 color: white; 407 font-size: 40px; 408 font-weight: bold; 409 animation: checkmarkPop 0.4s ease-out 0.2s both; 410 } 411 412 @keyframes scaleIn { 413 from { 414 transform: scale(0); 415 opacity: 0; 416 } 417 to { 418 transform: scale(1); 419 opacity: 1; 420 } 421 } 422 423 @keyframes checkmarkPop { 424 from { 425 transform: scale(0); 426 opacity: 0; 427 } 428 50% { 429 transform: scale(1.2); 430 } 431 to { 432 transform: scale(1); 433 opacity: 1; 434 } 435 } 436 437 .sitepulse-completion-title { 438 text-align: center; 439 color: #333; 440 font-size: 24px; 441 font-weight: 600; 442 margin: 0 0 20px 0; 443 } 444 445 .sitepulse-completion-message { 446 color: #555; 447 font-size: 16px; 448 line-height: 1.6; 449 margin: 0 0 15px 0; 450 padding: 20px; 451 background: #f8fdf8; 452 border-radius: 8px; 453 border-left: 4px solid #27ae60; 454 } 455 456 .sitepulse-results-info { 457 color: #666; 458 font-size: 14px; 459 line-height: 1.6; 460 margin: 0 0 25px 0; 461 text-align: center; 462 font-style: italic; 463 } 464 465 .sitepulse-completion-actions { 466 display: flex; 467 gap: 15px; 468 justify-content: center; 469 flex-wrap: wrap; 470 } 471 472 .sitepulse-view-results-btn { 473 background: linear-gradient(135deg, #27ae60 0%, #2ecc71 100%); 474 color: white; 475 text-decoration: none; 476 transition: all 0.2s ease; 477 } 478 479 .sitepulse-view-results-btn:hover { 480 background: linear-gradient(135deg, #229954 0%, #27ae60 100%); 481 transform: translateY(-2px); 482 box-shadow: 0 5px 15px rgba(46, 204, 113, 0.4); 483 color: white; 484 text-decoration: none; 485 } 486 487 #close-completed-modal { 488 background: #95a5a6; 489 color: white; 490 } 491 492 #close-completed-modal:hover { 493 background: #7f8c8d; 494 transform: translateY(-2px); 495 box-shadow: 0 5px 15px rgba(149, 165, 166, 0.4); 496 } 497 498 /* Responsive Design */ 499 @media (max-width: 600px) { 500 .sitepulse-modal-content { 501 margin: 5% auto; 502 width: 95vw; 503 } 504 505 .sitepulse-modal-header { 506 padding: 20px 25px 15px; 507 } 508 509 .sitepulse-modal-header h2 { 510 font-size: 20px; 511 } 512 513 .sitepulse-modal-body { 514 padding: 25px; 515 } 516 517 .sitepulse-tracking-controls { 518 flex-direction: column; 519 } 520 521 .sitepulse-info-grid { 522 grid-template-columns: 1fr; 523 gap: 10px; 524 } 525 } -
sitepulse/trunk/assets/css/sitepulse_general.css
r3374045 r3383675 217 217 color:#fff; 218 218 padding:0 4px; 219 border-radius: 999px;219 border-radius:7px; 220 220 margin-left:6px; 221 } 222 223 /* Tiny circular count */ 224 #wp-admin-bar-wpsp .wshp-site-load-time { 225 display: inline-block; 226 min-width: 16px; 227 height: 16px; 228 line-height: 16px; 229 font-size: 10px; 230 text-align: center; 231 background: #c4cebf26; 232 color: #fff; 233 padding: 0 4px; 234 border-radius: 7px; 235 margin-left: 6px; 236 } 237 238 #wp-admin-bar-wpsp .wshp-site-load-time.green { 239 color: #04ff00; 240 } 241 242 #wp-admin-bar-wpsp .wshp-site-load-time.yellow { 243 color: #ffec00; 244 } 245 246 #wp-admin-bar-wpsp .wshp-site-load-time.red { 247 color: #ff3b3b; 221 248 } 222 249 -
sitepulse/trunk/assets/js/backend.js
r3374045 r3383675 218 218 }, 10000); 219 219 220 $('.sp_profiler_clear_events ').on('click', async function() {220 $('.sp_profiler_clear_events, .sp_curl_and_profiler_clear_events').on('click', async function() { 221 221 try { 222 222 const result = await setClearLoadEvents(); … … 233 233 }); 234 234 235 $('.sp_curl_api_clear_events ').on('click', async function() {235 $('.sp_curl_api_clear_events, .sp_curl_and_profiler_clear_events').on('click', async function() { 236 236 try { 237 237 const result = await setClearCurlApiEvents(); … … 503 503 // Optional custom event support 504 504 window.addEventListener('sitepulse:refresh', () => refreshPulse(true)); 505 506 // Toggle email blocking mode visibility 507 function toggleEmailModeRow() { 508 if ($('#sitepulse_email_blocking_enabled').is(':checked')) { 509 $('#email_blocking_mode_row').show(); 510 } else { 511 $('#email_blocking_mode_row').hide(); 512 } 513 } 514 515 // Initial state 516 toggleEmailModeRow(); 517 518 // On change event 519 $('#sitepulse_email_blocking_enabled').change(function() { 520 toggleEmailModeRow(); 521 }); 522 523 // Add confirmation for dangerous operations 524 $('#sitepulse_cron_disabled').change(function() { 525 if ($(this).is(':checked')) { 526 if (!confirm( SitePulse.cron_confirm_message )) { 527 $(this).prop('checked', false); 528 } 529 } 530 }); 531 532 $('#sitepulse_email_blocking_enabled').change(function() { 533 if ($(this).is(':checked')) { 534 if (!confirm( SitePulse.email_blocking_confirm_message )) { 535 $(this).prop('checked', false); 536 toggleEmailModeRow(); 537 } 538 } 539 }); 505 540 }); -
sitepulse/trunk/assets/js/frontend.js
r3374045 r3383675 57 57 } 58 58 59 $('#curlSwitch, #loadSwitch').on('click', async function() { 59 async function setRealTimeMode( real_time_status ) { 60 const url = SitePulse.rest_url.replace(/\/$/, '') + '/sitepulse/v1/wpsprealtimemode/set_active'; 61 const res = await fetch(url, { 62 method: 'POST', 63 headers: { 64 'Content-Type': 'application/json', 65 'X-WP-Nonce': SitePulse.nonce 66 }, 67 body: JSON.stringify({ real_time_status, _wpnonce: SitePulse.nonce }) 68 }); 69 70 if ( ! res.ok ) { 71 const err = await res.json().catch(()=>null); 72 throw new Error( 'Request failed: ' + (err?.message || res.status) ); 73 } 74 75 if ( res.ok ) { 76 // if ( real_time_status ) { 77 // $('#curlSwitch').closest('.switch-item').addClass('rainbow-border'); 78 // } else { 79 // $('#curlSwitch').closest('.switch-item').removeClass('rainbow-border'); 80 // } 81 } 82 83 return res.json(); 84 } 85 86 async function getRealTimeMode() { 87 const url = SitePulse.rest_url.replace(/\/$/, '') + '/sitepulse/v1/wpsprealtimemode/get_active'; 88 const res = await fetch(url, { 89 method: 'POST', 90 headers: { 91 'Content-Type': 'application/json', 92 'X-WP-Nonce': SitePulse.nonce 93 }, 94 body: JSON.stringify({ _wpnonce: SitePulse.nonce }) 95 }); 96 97 if ( ! res.ok ) { 98 const err = await res.json().catch(()=>null); 99 throw new Error( 'Request failed: ' + (err?.message || res.status) ); 100 } 101 102 return res.json(); 103 } 104 105 async function setSPReportMode(report_mode) { 106 const url = SitePulse.rest_url.replace(/\/$/, '') + '/sitepulse/v1/sp_report_mode/set_active'; 107 const res = await fetch(url, { 108 method: 'POST', 109 headers: { 110 'Content-Type': 'application/json', 111 'X-WP-Nonce': SitePulse.nonce 112 }, 113 body: JSON.stringify({ report_mode, _wpnonce: SitePulse.nonce }) 114 }); 115 116 if ( ! res.ok ) { 117 const err = await res.json().catch(()=>null); 118 throw new Error( 'Request failed: ' + (err?.message || res.status) ); 119 } 120 121 return res.json(); 122 } 123 124 $('#curlSwitch').on('click', async function() { 60 125 61 126 var page_id = $(this).data('sitepulse-page-id'); 62 127 var curlSwitch = $('#curlSwitch').is(':checked') ? 1 : 0; 63 var loadSwitch = $('#loadSwitch').is(':checked') ? 1 : 0; 64 65 try { 66 const result = await setPageTPLoadActive( page_id, curlSwitch, loadSwitch ); 128 129 try { 130 const result = await setPageTPLoadActive( page_id, curlSwitch, 0 ); 67 131 68 132 setTimeout(() => { … … 77 141 }); 78 142 143 // Real-time tracking modal and functionality. 144 let trackingInterval; 145 let trackingStartTime; 146 let isTracking = false; 147 148 // Show modal on page load 149 // $('#sitepulse-modal').show(); 150 151 // Close modal functionality 152 $('#sitepulse-close-modal').on('click', function() { 153 // Close modal and handle tracking state 154 if ( isTracking ) { 155 if ( confirm('Are you sure you want to close the SitePulse tracking modal? The Real-Time tracking session will be stopped.') ) { 156 stopTracking(); 157 $('#sitepulse-modal').hide(); 158 } 159 } else { 160 // Close modal without stopping tracking 161 $('#sitepulse-modal').hide(); 162 } 163 }); 164 165 // Is the real-time tracking element present? 166 if ( $('[data-sitepulse-realtime="tracking"]').length > 0 ) { 167 restoreTracking(); 168 } 169 170 // Realtime tracking was active and finished 171 if ( $('[data-sitepulse-realtime="stopped"]').length > 0 && localStorage.getItem('sitepulse_realtime_tracking') === 'true' ) { 172 localStorage.removeItem('sitepulse_realtime_tracking'); 173 var page_id = $('#curlSwitch').data('sitepulse-page-id'); 174 var curlSwitch = $('#curlSwitch').is(':checked') ? 1 : 0; 175 176 // Loop through SitePulse.post_load_events and log each event's details 177 Object.entries(SitePulse.post_load_events).forEach(([id, event]) => { 178 // Generate HTML for event details 179 // Format time values to milliseconds with 3 decimal places 180 function formatMs(val) { 181 return (parseFloat(val) * 1000).toFixed(3) + ' ms'; 182 } 183 184 const eventHtml = ` 185 <div class="sitepulse-event"> 186 <strong>Event ID:</strong> ${id}<br> 187 <strong>Hook:</strong> ${event.hook}<br> 188 <strong>Priority:</strong> ${event.priority}<br> 189 <strong>Signature:</strong> ${event.sig}<br> 190 <strong>File/Line:</strong> ${event.fileline}<br> 191 <strong>Calls:</strong> ${event.calls}<br> 192 <strong>Total Time:</strong> ${formatMs(event.total_ms)}<br> 193 <strong>Max Time:</strong> ${formatMs(event.max_ms)}<br> 194 <strong>Avg:</strong> ${formatMs(event.avg_ms)}<br> 195 <strong>Source:</strong> ${event.source} 196 </div>`; 197 198 $('#sitepulse-completion-info').append(eventHtml); 199 }); 200 201 (async function() { 202 try { 203 const result = await setSPReportMode( 1 ); 204 } catch (err) { 205 console.error('Failed to update Report mode:', err); 206 } 207 208 try { 209 const result = await setPageTPLoadActive( page_id, curlSwitch, 0 ); 210 } catch (err) { 211 // Optionally handle error, e.g. show error message 212 console.error('Failed to update Page tracker Events:', err); 213 } 214 215 showCompletionModal(); 216 })(); 217 } 218 219 // // Close modal when clicking outside 220 // $(window).on('click', function(event) { 221 // if (event.target.id === 'sitepulse-modal') { 222 // $('#sitepulse-modal').hide(); 223 // if (isTracking) { 224 // stopTracking(); 225 // } 226 // } 227 // }); 228 229 // Realtime Tracking button in admin bar 230 $('#realtime-tracking-btn').on('click', function() { 231 $('#sitepulse-modal').show(); 232 }); 233 234 // Start tracking button 235 $('#start-tracking-btn').on('click', function() { 236 startTracking(); 237 }); 238 239 // Stop tracking button 240 $('#stop-tracking-btn').on('click', function() { 241 stopTracking(); 242 }); 243 244 async function startTracking() { 245 isTracking = true; 246 trackingStartTime = Date.now(); 247 248 // Update modal content when realtime activated 249 $('#modal-title').text('Real-Time Tracking Enabled'); 250 $('#inactive-content').hide(); 251 $('#active-content').show(); 252 $('.sitepulse-tracking-description.current-page').text('Real-time tracking is active'); 253 254 localStorage.setItem('sitepulse_realtime_tracking', 'true'); 255 localStorage.setItem('sitepulse_tracking_start_time', trackingStartTime.toString()); 256 257 var page_id = $('#curlSwitch').data('sitepulse-page-id'); 258 var curlSwitch = $('#curlSwitch').is(':checked') ? 1 : 0; 259 260 try { 261 const result = await setPageTPLoadActive( page_id, curlSwitch, 1 ); 262 } catch (err) { 263 // Optionally handle error, e.g. show error message 264 console.error('Failed to update Page tracker Events:', err); 265 } 266 267 try { 268 const result = await setRealTimeMode( 1 ); 269 270 if ( result.success ) { 271 console.log('Real Time mode activated on server.'); 272 } 273 274 } catch (err) { 275 // Optionally handle error, e.g. show error message 276 console.error('Failed to update Page tracker Events:', err); 277 } 278 279 // Update UI 280 updateTrackingStatus('tracking', 'Tracking Active'); 281 $('#start-tracking-btn').hide(); 282 $('#stop-tracking-btn').show(); 283 $('#tracking-info').show(); 284 $('#tracking-state').text('Monitoring...'); 285 286 // Start the tracking interval 287 trackingInterval = setInterval(function() { 288 updateTrackingDisplay(); 289 simulateResourceDetection(); 290 }, 1000); 291 292 try { 293 const result = await setSPReportMode( 1 ); 294 } catch (err) { 295 console.error('Failed to update Report mode:', err); 296 } 297 298 console.log('SitePulse: Resource tracking started'); 299 300 setTimeout(() => { 301 window.location.reload(); 302 }, 10000); 303 } 304 305 async function restoreTracking() { 306 isTracking = true; 307 308 // Restore tracking start time from localStorage or set to current time 309 const savedStartTime = localStorage.getItem('sitepulse_tracking_start_time'); 310 trackingStartTime = savedStartTime ? parseInt(savedStartTime) : Date.now(); 311 312 // Update modal content when realtime activated 313 $('#modal-title').text('Real-Time Tracking Enabled'); 314 $('#inactive-content').hide(); 315 $('#active-content').show(); 316 317 try { 318 const result = await getRealTimeMode(); 319 320 if ( result.success && result.real_time_status ) { 321 setTimeout(() => { 322 window.location.reload(); 323 }, 10000); 324 } 325 326 if ( result.success && result.real_time_status == false ) { 327 stopTracking(); 328 console.log('Real Time mode is not active on server.'); 329 } 330 331 } catch (err) { 332 // Optionally handle error, e.g. show error message 333 console.error('Failed to update Page tracker Events:', err); 334 } 335 336 // Update UI 337 updateTrackingStatus('tracking', 'Tracking Active'); 338 $('#start-tracking-btn').hide(); 339 $('#stop-tracking-btn').show(); 340 $('#tracking-info').show(); 341 $('#tracking-state').text('Monitoring...'); 342 343 // Start the tracking interval 344 trackingInterval = setInterval(function() { 345 updateTrackingDisplay(); 346 simulateResourceDetection(); 347 }, 1000); 348 349 console.log('SitePulse: Resource tracking started'); 350 351 // You can add your actual tracking logic here 352 // For example, monitoring network requests, performance metrics, etc. 353 } 354 355 async function stopTracking() { 356 $('#modal-title').text('Real-Time Tracking Stopped'); 357 $('#inactive-content').show(); 358 $('#active-content').hide(); 359 360 isTracking = false; 361 362 if (trackingInterval) { 363 clearInterval(trackingInterval); 364 } 365 366 // Clear localStorage tracking data 367 localStorage.removeItem('sitepulse_tracking_start_time'); 368 localStorage.removeItem('sitepulse_tracking_elapsed'); 369 localStorage.removeItem('sitepulse_realtime_tracking'); 370 371 // Update UI 372 updateTrackingStatus('idle', 'Ready to Track'); 373 $('#start-tracking-btn').show(); 374 $('#stop-tracking-btn').hide(); 375 $('#tracking-info').hide(); 376 377 var page_id = $('#curlSwitch').data('sitepulse-page-id'); 378 var curlSwitch = $('#curlSwitch').is(':checked') ? 1 : 0; 379 380 try { 381 const result = await setPageTPLoadActive( page_id, curlSwitch, 0 ); 382 } catch (err) { 383 // Optionally handle error, e.g. show error message 384 console.error('Failed to update Page tracker Events:', err); 385 } 386 387 try { 388 const result = await setRealTimeMode( 0 ); 389 390 if ( result.success ) { 391 console.log('Real Time mode activated on server.'); 392 } 393 394 } catch (err) { 395 // Optionally handle error, e.g. show error message 396 console.error('Failed to update Page tracker Events:', err); 397 } 398 399 console.log('SitePulse: Resource tracking stopped'); 400 } 401 402 function updateTrackingStatus(status, text) { 403 const statusDot = $('.sitepulse-status-dot'); 404 statusDot.removeClass('idle tracking error').addClass(status); 405 $('.status-text').text(text); 406 } 407 408 function updateTrackingDisplay() { 409 if (!isTracking) return; 410 411 const elapsed = Date.now() - trackingStartTime; 412 const minutes = Math.floor(elapsed / 60000); 413 const seconds = Math.floor((elapsed % 60000) / 1000); 414 415 // Save elapsed time to localStorage for persistence across refreshes 416 localStorage.setItem('sitepulse_tracking_elapsed', elapsed.toString()); 417 418 $('#tracking-duration').text( 419 `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}` 420 ); 421 422 $('#resources-count').text(SitePulse.post_load_events_count); 423 } 424 425 function simulateResourceDetection() { 426 // This is a simulation - replace with actual resource detection logic 427 if (Math.random() < 0.3) { // 30% chance each second 428 $('#tracking-state').text('Resource detected...'); 429 430 setTimeout(function() { 431 if (isTracking) { 432 $('#tracking-state').text('Monitoring...'); 433 } 434 }, 500); 435 } 436 } 437 438 function showCompletionModal() { 439 // Update modal title and show completion content 440 $('#modal-title').text('SitePulse Tracking Complete'); 441 $('#inactive-content').hide(); 442 $('#active-content').hide(); 443 $('#completed-content').show(); 444 445 // Update status indicator 446 updateTrackingStatus('idle', 'Tracking Complete'); 447 448 // Hide tracking controls and info 449 $('.sitepulse-tracking-controls').hide(); 450 $('#tracking-info').hide(); 451 452 // Show the modal 453 $('#sitepulse-modal').show(); 454 } 455 456 // Close completion modal button 457 $('#close-completed-modal').on('click', function() { 458 $('#sitepulse-modal').hide(); 459 // Reset modal to default state 460 resetModalToDefault(); 461 }); 462 463 function resetModalToDefault() { 464 $('#modal-title').text('SitePulse Resource Tracking'); 465 $('#inactive-content').show(); 466 $('#active-content').hide(); 467 $('#completed-content').hide(); 468 $('.sitepulse-tracking-controls').show(); 469 updateTrackingStatus('idle', 'Ready to Track'); 470 } 471 472 // Handle escape key to close modal 473 $(document).on('keydown', function(event) { 474 if (event.key === 'Escape' && $('#sitepulse-modal').is(':visible')) { 475 $('#sitepulse-modal').hide(); 476 if (isTracking) { 477 stopTracking(); 478 } 479 } 480 }); 481 79 482 }); -
sitepulse/trunk/class/backend.php
r3374045 r3383675 43 43 wp_enqueue_script($this->plugin->setPrefix( SITEPULSE_PREFIX . '_backend_script'), SITEPULSE_ADMIN_ASSETS_JS_URL . 'backend.js', ['jquery'], filemtime(SITEPULSE_ADMIN_ASSETS_JS_PATH . 'backend.js'), true); 44 44 wp_localize_script( $this->plugin->setPrefix( SITEPULSE_PREFIX . '_backend_script'), 'SitePulse', [ 45 'cron_confirm_message' => esc_js(__('Are you sure you want to disable WordPress cron? This will stop all scheduled tasks including updates, backups, and other automated processes.', 'sitepulse')), 46 'email_blocking_confirm_message' => esc_js(__('Are you sure you want to block all outgoing emails? This will prevent password resets, notifications, and other important emails from being sent.', 'sitepulse')), 45 47 'rest_url' => esc_url_raw( rest_url() ), 46 48 'nonce' => wp_create_nonce( 'wp_rest' ), … … 105 107 $page_resource_load = add_submenu_page( 106 108 $this->plugin->setPrefix("sitepulse"), 107 __(' LoadSentinel', 'sitepulse'),108 __(' LoadSentinel', 'sitepulse'),109 __('Insights', 'sitepulse'), 110 __('Insights', 'sitepulse'), 109 111 'manage_options', 110 112 $this->plugin->setPrefix("sitepulse") . '_' . SITEPULSE_PROFILER_SLUG, … … 123 125 $this->plugin->setPrefix("sitepulse") . '_' . SITEPULSE_CURL_API_SLUG, 124 126 [&$this, 'render_curl_api'], 125 2127 3 126 128 ); 127 129 128 130 array_push($this->pages, $page_curl_api); 131 132 // Add a submenu for settings page 133 $page_settings = add_submenu_page( 134 $this->plugin->setPrefix("sitepulse"), 135 __('Settings', 'sitepulse'), 136 __('Settings', 'sitepulse'), 137 'manage_options', 138 $this->plugin->setPrefix("sitepulse") . '_settings', 139 [&$this, 'render_page_sitepulse_settings'], 140 10 141 ); 142 143 array_push($this->pages, $page_settings); 129 144 } 130 145 … … 233 248 } 234 249 250 public function render_page_sitepulse_settings() { 251 if ( ! current_user_can('manage_options') ) return; 252 253 // Memory ram used 254 $mem = $this->plugin->sp_get_memory_info(); 255 256 // Initialize settings class 257 require_once SITEPULSE_CLASS_PATH . 'settings.php'; 258 $sitepulse_settings = Sitepulse_Settings::getInstance(); 259 260 // Handle form submission for SitePulse settings 261 if ( isset( $_POST['submit'] ) && isset( $_POST['sitepulse_settings_nonce'] ) ) { 262 if ( wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['sitepulse_settings_nonce'] ) ), 'sitepulse_settings_action' ) ) { 263 $updated_settings = array(); 264 265 // Email blocking settings 266 $updated_settings['email_blocking_enabled'] = isset( $_POST['sitepulse_email_blocking_enabled'] ) ? true : false; 267 if ( isset( $_POST['sitepulse_email_blocking_mode'] ) ) { 268 $updated_settings['email_blocking_mode'] = sanitize_text_field( wp_unslash( $_POST['sitepulse_email_blocking_mode'] ) ); 269 } 270 271 // Cron settings 272 $updated_settings['cron_disabled'] = isset( $_POST['sitepulse_cron_disabled'] ) ? true : false; 273 274 // Update settings 275 if ( $sitepulse_settings->update_settings( $updated_settings ) ) { 276 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'SitePulse settings updated successfully!', 'sitepulse' ) . '</p></div>'; 277 } 278 } else { 279 echo '<div class="notice notice-error is-dismissible"><p>' . esc_html__( 'Security check failed. Please try again.', 'sitepulse' ) . '</p></div>'; 280 } 281 } 282 283 // List of activated plugins 284 $active_plugins = get_option( 'active_plugins', [] ); 285 286 // Get current settings 287 $current_settings = $sitepulse_settings->get_all_settings(); 288 $status_info = $sitepulse_settings->get_status_info(); 289 290 // Render all sections of the page 291 require_once SITEPULSE_PATH . 'templates/backend/settings.php'; 292 } 293 235 294 public function render_curl_api() { 236 295 if (!current_user_can('manage_options')) return; -
sitepulse/trunk/class/frontend.php
r3374045 r3383675 17 17 // Register admin bar node on both admin and front-end (method checks capabilities) 18 18 add_action('admin_bar_menu', [&$this, 'admin_bar_node'], 90); 19 20 // Add to footer (before </body>) 21 add_action('wp_footer', [ &$this, 'sitepulse_modal_html'], 100 ); 19 22 } 23 24 // Frontend modal HTML output 25 public static function sitepulse_modal_html() { 26 $sp_current_tracked_pageid = get_option( "sitepulse_current_tracked_pageid" ); 27 $realtime_tracking = get_transient( "sitepulse_realtime_tracking" ); 28 $tracking_status = 'stopped'; 29 30 if ( $realtime_tracking ) { 31 // Ensure is_logging() is defined or replace with appropriate check 32 if ( current_user_can( 'manage_options' ) ) { 33 $tracking_status = 'tracking'; 34 } 35 } 36 37 require_once SITEPULSE_PATH . 'templates/frontend/frontend.php'; 38 } 20 39 21 40 public static function admin_bar_node($wp_admin_bar) { … … 62 81 . '</span>'; 63 82 83 $time_load = $time_load_in_ms = self::get_current_load_time(); 84 85 // $time_load seconds, minutes or hours formatting can be added here if needed 86 $time_format_siteload = 'ms'; 87 if ( $time_load > 1000 ) { 88 $time_load = number_format( $time_load / 1000, 1 ); 89 $time_format_siteload = 's'; 90 } elseif ( $time_load > 60000 ) { 91 $time_load = number_format( $time_load / 60000, 1 ); 92 $time_format_siteload = 'min'; 93 } elseif ( $time_load > 3600000 ) { 94 // Extreme conditions 95 $time_load = number_format( $time_load / 3600000, 1 ); 96 $time_format_siteload = 'hr'; 97 } 98 99 // Time load color coding 100 if ( $time_load_in_ms ) { 101 if ( $time_load_in_ms < 1000 ) { 102 // Green 103 $time_load = '<span class="wshp-site-load-time green" aria-hidden="true"> ' . __('LoadTime', 'sitepulse') . ': ' . number_format($time_load, 1) . ' ' . $time_format_siteload . '</span>'; 104 } elseif ( $time_load_in_ms >= 1000 && $time_load_in_ms < 3000 ) { 105 // Yellow 106 $time_load = '<span class="wshp-site-load-time yellow" aria-hidden="true"> ' . __('LoadTime', 'sitepulse') . ': ' . number_format($time_load, 1) . ' ' . $time_format_siteload . '</span>'; 107 } else { 108 // Red 109 $time_load = '<span class="wshp-site-load-time red" aria-hidden="true"> ' . __('LoadTime', 'sitepulse') . ': ' . number_format($time_load, 1) . ' ' . $time_format_siteload . '</span>'; 110 } 111 } 112 64 113 $badge = $count ? '<span class="wshp-count wpsp-stats" aria-hidden="true">API: ' . intval($count) . '</span>' : ''; 65 114 $badge .= $stats_count ? '<span class="wshp-count" aria-hidden="true">Load: ' . intval($stats_count) . '</span>' : ''; 115 $badge .= $time_load ? $time_load : ''; 66 116 67 117 // Use very short label to conserve width; title contains full link … … 141 191 <div class="switch-container"> 142 192 <div class="switch-item ' . $sp_pagechttp_rain . '"> 143 <span class="switch-label"> cURL Events</span>193 <span class="switch-label">Track API Events for this page</span> 144 194 <label class="switch"> 145 195 <input type="checkbox" ' . checked( $sp_pageloadhttp_curlstatus, true, false ) . ' id="curlSwitch" data-sitepulse-page-id="' . get_the_ID() . '"> 146 <span class="slider round"></span>147 </label>148 </div>149 <div class="switch-item ' . $sp_pagelhttp_rain . '">150 <span class="switch-label">Load Tracking</span>151 <label class="switch">152 <input type="checkbox" ' . checked( $sp_pageloadhttp_loadstatus, true, false ) . ' id="loadSwitch" data-sitepulse-page-id="' . get_the_ID() . '">153 196 <span class="slider round"></span> 154 197 </label> … … 158 201 <div class="sitepulse-ontracking" style="' . $sp_pagelhttp_view . '">Tracking page: ' . $page_post_title . '<span class="wshp-count wpsp-stats">API: ' . $count_current_curl_page . '</span><span class="wshp-count">Load: ' . $count_current_load_page . '</span></div> 159 202 </div> 203 <div class="sitepulse-container"> 204 <div class="sitepulse-realtime-tracking"> 205 <button id="realtime-tracking-btn" class="sitepulse-btn sitepulse-btn-primary"> 206 <span class="sitepulse-btn-icon">📊</span> 207 Start Real-Time Tracking 208 </button> 209 </div> 210 </div> 160 211 ', 161 212 'meta' => [ … … 165 216 'href' => false, 166 217 ]); 167 } 218 // Warning for unsupported page types and not show on admin pages 219 } elseif ( ! is_admin() ) { 220 $wp_admin_bar->add_node([ 221 'id' => SITEPULSE_PREFIX . '_unsupported_page', 222 'title' => '<span class="sitepulse-warning-label"><span class="sitepulse-warning-icon" aria-hidden="true">⚠</span> ' . __('Tracking not supported on this page', 'sitepulse') . '</span>', 223 'parent' => SITEPULSE_PREFIX, 224 'href' => false, 225 'meta' => [ 226 'html' => false, 227 'class' => 'sitepulse-unsupported', 228 'title' => __('Tracking is not available for this page type.', 'sitepulse'), 229 ], 230 ]); 231 } 168 232 } 169 233 … … 178 242 true // Load in footer 179 243 ); 180 wp_enqueue_script( 181 $this->plugin->setPrefix(SITEPULSE_PREFIX . '_frontend_script'), 182 SITEPULSE_ADMIN_ASSETS_JS_URL . 'frontend.js', 183 ['jquery'], 184 filemtime(SITEPULSE_ADMIN_ASSETS_JS_PATH . 'frontend.js'), 185 true // Load in footer 186 ); 244 245 // Pass $post_load_events to frontend.js 246 $sp_current_tracked_pageid = get_option( "sitepulse_current_tracked_pageid" ); 247 $post_load_events = []; 248 if ( $sp_current_tracked_pageid ) { 249 $transient = get_transient( 'sitepulse_load_single_page_' . $sp_current_tracked_pageid ); 250 if ( $transient ) { 251 $post_load_events = $transient; 252 } 253 } 254 255 wp_enqueue_script( 256 $this->plugin->setPrefix(SITEPULSE_PREFIX . '_frontend_script'), 257 SITEPULSE_ADMIN_ASSETS_JS_URL . 'frontend.js', 258 ['jquery'], 259 filemtime(SITEPULSE_ADMIN_ASSETS_JS_PATH . 'frontend.js'), 260 true // Load in footer 261 ); 262 263 $post_load_events = array_values($post_load_events); 264 usort($post_load_events, function($a, $b) { 265 return $b['total_ms'] <=> $a['total_ms']; 266 }); 267 187 268 wp_localize_script( 188 269 $this->plugin->setPrefix( SITEPULSE_PREFIX . '_frontend_script'), … … 190 271 'rest_url' => esc_url_raw( rest_url() ), 191 272 'nonce' => wp_create_nonce( 'wp_rest' ), 192 ] 273 'post_load_events' => array_slice($post_load_events, 0, 5), 274 'post_load_events_count' => count( $post_load_events ), 275 ] 193 276 ); 194 277 } 278 279 /** 280 * Get current page load time in milliseconds 281 */ 282 public static function get_current_load_time() { 283 global $timestart; 284 285 if ( ! defined( 'SITEPULSE_SITELOAD_START' ) ) { 286 $start = $timestart; 287 } else { 288 $start = SITEPULSE_SITELOAD_START; 289 } 290 291 if ( ! $start ) return null; 292 293 return (microtime(true) - $start) * 1000; 294 } 195 295 } -
sitepulse/trunk/class/profiler.php
r3374045 r3383675 109 109 foreach ($callbacks as $id => $cb) { 110 110 // Avoid double-wrapping and skip our own closures 111 // var_dump($id);112 // exit;113 111 $key = self::key($hook_name, $priority, $id); 114 112 if (isset(self::$wrapped[$key])) continue; … … 124 122 $elapsed = microtime(true) - $start; 125 123 126 Sitepulse_Profiler::record($hook_name, (int)$priority, (string)$id, $orig, $elapsed); 124 // Only record if execution time is measurable (> 0.0001 seconds = 0.1ms) 125 if ($elapsed > 0.0001) { 126 Sitepulse_Profiler::record($hook_name, (int)$priority, (string)$id, $orig, $elapsed); 127 } 127 128 return $result; 128 129 }; … … 196 197 $fileline = self::fileline($callable); 197 198 199 // Convert seconds to milliseconds for better readability 200 $elapsed_ms = $elapsed * 1000; 201 202 if ( $elapsed_ms < 0.5 ) { 203 // Ignore very fast callbacks 204 return; 205 } 206 198 207 // Use a stable key per callback (hook+priority+signature+fileline) 199 208 $key = md5(implode('|', [$hook, $priority, $sig, $fileline])); 200 209 201 if ( !isset(self::$stats[$key])) {210 if ( ! isset( self::$stats[$key] ) ) { 202 211 $plugin_or_theme = '(unknown)'; 203 212 if (strpos($fileline, 'wp-content/plugins/') === 0) { … … 216 225 'calls' => 0, 217 226 'total' => 0.0, 227 'total_ms' => 0.0, // Total time in milliseconds 228 'avg_ms' => 0.0, // Average time in milliseconds 218 229 'max' => 0.0, 230 'max_ms' => 0.0, // Max time in milliseconds 219 231 'source' => $plugin_or_theme, 220 232 ]; 221 233 } 234 222 235 self::$stats[$key]['calls']++; 223 236 self::$stats[$key]['total'] += $elapsed; 237 238 self::$stats[$key]['total_ms'] += $elapsed_ms; 239 self::$stats[$key]['avg_ms'] = self::$stats[$key]['total_ms'] / self::$stats[$key]['calls']; 240 224 241 if ($elapsed > self::$stats[$key]['max']) { 225 242 self::$stats[$key]['max'] = $elapsed; 226 } 243 self::$stats[$key]['max_ms'] = $elapsed_ms; 244 } 245 } 246 247 /** 248 * Format elapsed time with appropriate units (ms for < 1s, s for >= 1s) 249 * @param float $seconds Time in seconds 250 * @return string Formatted time string 251 */ 252 public static function format_time(float $seconds): string { 253 if ($seconds >= 1.0) { 254 return number_format($seconds, 3) . 's'; 255 } else { 256 return number_format($seconds * 1000, 2) . 'ms'; 257 } 258 } 259 260 /** 261 * Get stats with formatted timing values 262 * @return array Stats with formatted timing 263 */ 264 public static function get_formatted_stats(): array { 265 $formatted_stats = []; 266 foreach (self::$stats as $key => $stat) { 267 $formatted_stats[$key] = $stat; 268 $formatted_stats[$key]['total_formatted'] = self::format_time($stat['total']); 269 $formatted_stats[$key]['avg_formatted'] = self::format_time($stat['total'] / max(1, $stat['calls'])); 270 $formatted_stats[$key]['max_formatted'] = self::format_time($stat['max']); 271 } 272 return $formatted_stats; 227 273 } 228 274 } -
sitepulse/trunk/inc/api_backend.php
r3374045 r3383675 224 224 } ); 225 225 226 // Register the REST API route for setting page load active status 227 add_action( 'rest_api_init', function() { 228 register_rest_route( 'sitepulse/v1', '/wpsprealtimemode/set_active', [ 229 'methods' => 'POST', 230 'callback' => 'sitepulse_realtime_mode', 231 'permission_callback' => function() { 232 // adjust capability as needed 233 return current_user_can( 'manage_options' ); 234 }, 235 'args' => [ 236 '_wpnonce' => [ 237 'required' => true, 238 'description' => 'WP REST API nonce for authentication', 239 'type' => 'string', 240 ], 241 ], 242 ] ); 243 } ); 244 245 add_action( 'rest_api_init', function() { 246 register_rest_route( 'sitepulse/v1', '/wpsprealtimemode/get_active', [ 247 'methods' => 'POST', 248 'callback' => 'sitepulse_get_realtime_mode', 249 'permission_callback' => function() { 250 // adjust capability as needed 251 return current_user_can( 'manage_options' ); 252 }, 253 'args' => [ 254 '_wpnonce' => [ 255 'required' => true, 256 'description' => 'WP REST API nonce for authentication', 257 'type' => 'string', 258 ], 259 ], 260 ] ); 261 } ); 262 263 // sitepulse_get_realtime_mode 264 function sitepulse_get_realtime_mode( WP_REST_Request $request ) { 265 $is_active = get_transient( "sitepulse_realtime_tracking" ); 266 267 return rest_ensure_response( [ 268 'success' => true, 269 'real_time_status' => $is_active ? true : false 270 ] ); 271 } 272 273 function sitepulse_realtime_mode( WP_REST_Request $request ) { 274 275 $result = false; 276 if ( isset( $request['real_time_status'] ) && $request['real_time_status'] ) { 277 set_transient( "sitepulse_realtime_tracking", true, SITEPULSE_REALTIME_TRACKING_TIME ); 278 $result = true; 279 } else { 280 delete_transient( "sitepulse_realtime_tracking" ); 281 } 282 283 return rest_ensure_response( [ 284 'success' => $result, 285 ] ); 286 } 287 226 288 function sitepulse_enable_clear_curl_api_events( WP_REST_Request $request ) { 227 289 $sitepulse_CurLoader = new Sitepulse_CurLoader(); … … 296 358 297 359 // Enable the single report mode 298 if ( $curlSwitch || $loadSwitch ) {299 update_option( "sitepulse_report_mode_active", 1 );300 }301 302 if ( ! $curlSwitch && ! $loadSwitch ) {303 update_option( "sitepulse_report_mode_active", 0 );304 }360 // if ( $curlSwitch || $loadSwitch ) { 361 // update_option( "sitepulse_report_mode_active", 1 ); 362 // } 363 364 // if ( ! $curlSwitch && ! $loadSwitch ) { 365 // update_option( "sitepulse_report_mode_active", 0 ); 366 // } 305 367 306 368 return rest_ensure_response( [ -
sitepulse/trunk/loader.php
r3374045 r3383675 1 1 <?php 2 2 /** 3 * Plugin Name: SitePulse 3 * Plugin Name: SitePulse - See What’s Powering (or Slowing) Your Site 4 4 * Description: SitePulse gives you real-time insights into your WordPress site’s performance, slow queries, and bottlenecks - so you can keep your site fast, healthy, and optimized. 5 * Version: 1. 0.85 * Version: 1.1.0 6 6 * Author: Frederic Guzman 7 7 * Author URI: https://www.nilbug.com … … 13 13 14 14 // Prevent to access the file from outside of WordPress 15 if (!defined('ABSPATH')) {15 if ( ! defined('ABSPATH') ) { 16 16 exit; 17 17 } … … 19 19 // SitePulse defines. He. 20 20 define( 'SITEPULSE_PLUGIN_FILE', __FILE__ ); 21 define( 'SITEPULSE_DEBUG', false ); 22 define( 'SITEPULSE_STRESS_MODE', false ); 23 define( 'SITEPULSE_VERSION', '1.0.8' ); 21 if ( ! defined( 'SITEPULSE_DEBUG' ) ) { 22 define( 'SITEPULSE_DEBUG', false ); 23 } 24 25 if ( ! defined( 'SITEPULSE_STRESS_MODE' ) ) { 26 define( 'SITEPULSE_STRESS_MODE', false ); 27 } 28 29 define( 'SITEPULSE_VERSION', '1.1.0' ); 24 30 define( 'SITEPULSE_NAME', 'SitePulse' ); 25 31 define( 'SITEPULSE_SLUG', 'sitepulse' ); 26 32 define( 'SITEPULSE_PREFIX', 'wpsp' ); 27 33 define( 'SITEPULSE_PREFIX_SEPARATOR', '_' ); 28 define( 'SITEPULSE_WEB_MAIN', 'https://wp-rocket.me/' );29 34 define( 'SITEPULSE_PATH', plugin_dir_path( __FILE__ ) . '/' ); 30 35 define( 'SITEPULSE_TEXT_DOMAIN_PATH', SITEPULSE_PATH . 'languages/' . '/' ); … … 61 66 define( 'SITEPULSE_CURL_API_DEFAULT_THRESHOLD', 1 ); 62 67 68 // Real time tracking constants 69 define( 'SITEPULSE_REALTIME_TRACKING_TIME', 30 ); // In seconds 70 63 71 // Load profiler class 64 72 require_once SITEPULSE_CLASS_PATH . 'profiler.php'; 65 73 require_once SITEPULSE_CLASS_PATH . 'curloader.php'; 66 74 require_once SITEPULSE_PATH . 'inc/api_backend.php'; 75 76 // Enabling plugin profiler 77 update_option( 'sitepulse_plugins_profiler_enabled', true ); 67 78 68 79 class Sitepulse_Loader { -
sitepulse/trunk/readme.txt
r3374073 r3383675 1 === SitePulse ===1 === SitePulse - See What’s Powering (or Slowing) Your Site === 2 2 Contributors: nilbug, frederickgzmn 3 Tags: monitoring, profiler, performance, pagespeed, admin3 Tags: performance, profiler, optimization, speed, insight, monitoring, hooks, load time 4 4 Tested up to: 6.8 5 5 Requires PHP: 7.4 6 Stable tag: 1. 0.86 Stable tag: 1.1.0 7 7 License: GPLv2 or later 8 8 License URI: https://www.gnu.org/licenses/gpl-2.0.html 9 Lightweight uptime monitoring, API checks, and performance profiling dashboard for WordPress.9 Understand what powers (or slows) your site - get real-time insights into page loads, plugins, and hooks directly from your dashboard. 10 10 11 11 == Description == … … 37 37 == Screenshots == 38 38 1. Dashboard overview (status and recent checks) 39 2. API monitorscreen40 3. Performance profiler output41 4. Frontend Page/Post Tracker Icons42 5. Frontend Page/Post Tracker Toggles39 2. insights screen 40 3. Settings and system info 41 4. Resources tracker 42 5. Real time tracker running 43 43 44 44 == Changelog == 45 = 1.1.0 = 46 * User-friendly focus: Settings page, block cronjobs, block emails from SMTP or send email, real-time tracking frontend interface, load time per page, plugin space usage improved etc. 47 45 48 = 1.0.0 = 46 49 * Initial public release: uptime monitoring, admin dashboard, basic profiler, and API endpoints. -
sitepulse/trunk/templates/backend/dashboard.php
r3374045 r3383675 20 20 <div class="col-12"> 21 21 <div class="sp-buttons"> 22 <button type="button" class="btn btn-outline-danger sp_curl_and_profiler_clear_events m-1"> 23 <?php echo esc_html__( 'Clear Event and Load Data', 'sitepulse' ); ?> 24 </button> 25 22 26 <div class="header-actions <?php if ( $lowhttp_enabled != 1 && $sitepulse_profiler_enabled != 1 ) { echo 'disabled'; } ?>"> 23 27 <label class="form-switch me-2"> -
sitepulse/trunk/templates/backend/resource_load.php
r3374045 r3383675 5 5 ?> 6 6 <div class="sp-dashboard container-fluid my-4"> 7 <div class="row">8 <div class="col-lg-6 col-12"></div>9 <div class="col-lg-6 col-12">7 <div class="row"> 8 <div class="col-lg-6 col-12"></div> 9 <div class="col-lg-6 col-12"> 10 10 <div class="sp-buttons"> 11 <button type="button" class="btn btn-outline-danger sp_profiler_clear_events m-1"> 12 <?php echo esc_html__( 'Clear Load Events', 'sitepulse' ); ?> 13 </button> 14 <div class="header-actions"> 15 <?php $sitepulse_profiler_enabled = get_option('sitepulse_profiler_enabled', null ); ?> 16 <label class="form-switch me-2"> 17 <input type="checkbox" <?php checked($sitepulse_profiler_enabled, '1'); ?> id="sp-profiler" class="form-check-input sp_profiler"> 18 <span class="form-check-label"><?php echo esc_html__( 'LoadSentinel', 'sitepulse' ); ?></span> 19 </label> 20 </div> 21 </div> 11 <button type="button" class="btn btn-outline-danger sp_profiler_clear_events m-1"> 12 <?php echo esc_html__( 'Clear Load Events', 'sitepulse' ); ?> 13 </button> 14 <div class="header-actions"> 15 <?php $sitepulse_profiler_enabled = get_option('sitepulse_profiler_enabled', null ); ?> 16 <label class="form-switch me-2"> 17 <input type="checkbox" <?php checked($sitepulse_profiler_enabled, '1'); ?> id="sp-profiler" class="form-check-input sp_profiler"> 18 <span class="form-check-label"><?php echo esc_html__( 'LoadSentinel', 'sitepulse' ); ?></span> 19 </label> 20 </div> 22 21 </div> 22 </div> 23 23 </div> 24 24 … … 57 57 58 58 <div class="row"> 59 <div> 60 <div class="card"> 61 <div class="card-header d-flex justify-content-between align-items-center"> 62 <div> 63 <strong><?php echo esc_html__( 'Plugins/Themes — Hooks', 'sitepulse' ); ?></strong> 64 <div class="text-muted small"><?php echo esc_html__( 'Functions and methods from plugins/themes that hit the site', 'sitepulse' ); ?></div> 65 </div> 66 <div class="text-end"> 67 <?php 68 if ( $single_load_events ) { 69 if ( isset( $sp_current_tracked_pageid ) && ! empty( $sp_current_tracked_pageid ) && is_numeric( $sp_current_tracked_pageid ) ) { 70 $sitepulse_load_single_page_id = sanitize_text_field( wp_unslash( $sp_current_tracked_pageid ) ); 71 $sitepulse_load_single_page_title = get_the_title( $sitepulse_load_single_page_id ); 72 73 if ( $sitepulse_load_single_page_title ) { 74 ?> 75 <span class="badge bg-secondary"><?php echo esc_html__('Single Detail:', 'sitepulse'); ?> <?php echo esc_attr($sitepulse_load_single_page_title); ?></span> 76 <?php 77 } 59 <div class="card"> 60 <div class="card-header d-flex justify-content-between align-items-center"> 61 <div> 62 <strong><?php echo esc_html__( 'Plugins/Themes — Hooks', 'sitepulse' ); ?></strong> 63 <div class="text-muted small"><?php echo esc_html__( 'Functions and methods from plugins/themes that hit the site', 'sitepulse' ); ?></div> 64 </div> 65 <div class="text-end"> 66 <?php 67 if ( $single_load_events ) { 68 if ( isset( $sp_current_tracked_pageid ) && ! empty( $sp_current_tracked_pageid ) && is_numeric( $sp_current_tracked_pageid ) ) { 69 $sitepulse_load_single_page_id = sanitize_text_field( wp_unslash( $sp_current_tracked_pageid ) ); 70 $sitepulse_load_single_page_title = get_the_title( $sitepulse_load_single_page_id ); 71 72 if ( $sitepulse_load_single_page_title ) { 73 ?> 74 <span class="badge bg-secondary"><?php echo esc_html__('Single Detail:', 'sitepulse'); ?> <?php echo esc_attr($sitepulse_load_single_page_title); ?></span> 75 <?php 78 76 } 79 77 } 78 } 79 ?> 80 </div> 81 <div class="text-end"> 82 <span class="badge bg-primary"><?php echo esc_html( sprintf( 83 /* translators: %d: number of active plugins */ 84 esc_html__( 'Active plugins: %d', 'sitepulse' ), 85 (int) $active_plugins_count 86 ) ); ?></span> 87 <span class="badge bg-outline-secondary ms-2"><?php echo esc_html( sprintf( 88 /* translators: %d: number of hook events detected */ 89 esc_html__( 'Events: %d', 'sitepulse' ), 90 (int) count( $stats ) 91 ) ); ?></span> 92 </div> 93 </div> 94 <div class="card-body p-0"> 95 <ul class="list-group list-group-flush scroll-list"> 96 <?php 97 // Sort by total time desc by default 98 $stats = array_values($stats); 99 100 usort($stats, function($a, $b) { 101 return $b['total_ms'] <=> $a['total_ms']; 102 }); 103 104 if ( empty( $stat ) && round( count( $stats ) ) == 0 ) { 80 105 ?> 81 </div> 82 <div class="text-end"> 83 <span class="badge bg-primary"><?php echo esc_html( sprintf( 84 /* translators: %d: number of active plugins */ 85 esc_html__( 'Active plugins: %d', 'sitepulse' ), 86 (int) $active_plugins_count 87 ) ); ?></span> 88 <span class="badge bg-outline-secondary ms-2"><?php echo esc_html( sprintf( 89 /* translators: %d: number of hook events detected */ 90 esc_html__( 'Events: %d', 'sitepulse' ), 91 (int) count( $stats ) 92 ) ); ?></span> 93 </div> 94 </div> 95 <div class="card-body p-0"> 96 <ul class="list-group list-group-flush scroll-list"> 97 <?php 98 // Sort by total time desc by default 99 $stats = array_values($stats); 100 101 usort($stats, function($a, $b) { 102 return $b['total'] <=> $a['total']; 103 }); 104 105 if ( empty( $stat ) && round( count( $stats ) ) == 0 ) { 106 ?> 107 <li class="list-group-item"> 108 <div class="text-end"> 109 <div class="item-meta m-3"> 110 <?php echo esc_html__( 'No plugin or theme activity detected. Please enable the monitor tracker to start collecting data.', 'sitepulse' ); ?> 111 </div> 106 <li class="list-group-item"> 107 <div class="text-end"> 108 <div class="item-meta m-3"> 109 <?php echo esc_html__( 'No plugin or theme activity detected. Please enable the monitor tracker to start collecting data.', 'sitepulse' ); ?> 112 110 </div> 113 <div class="text-end"> 114 <span class="badge bg-success"><?php echo esc_html__( 'Log', 'sitepulse' ); ?></span> 115 </div> 116 </li> 111 </div> 112 <div class="text-end"> 113 <span class="badge bg-success"><?php echo esc_html__( 'Log', 'sitepulse' ); ?></span> 114 </div> 115 </li> 116 <?php 117 } 118 119 foreach ( $stats as $stat ) : 120 if ( ! empty( $stat ) ) { 121 $avg = $stat['calls'] ? ($stat['total_ms'] / $stat['calls']) : 0; 122 123 // Assign badge class based on total time in ms 124 $time_ms = $stat['total_ms']; 125 if ($time_ms > 1000) { 126 $badge_class = 'bg-danger'; 127 } elseif ($time_ms > 500) { 128 $badge_class = 'bg-warning text-dark'; 129 } elseif ($time_ms > 200) { 130 $badge_class = 'bg-info text-dark'; 131 } elseif ($time_ms > 100) { 132 $badge_class = 'bg-secondary'; 133 } else { 134 $badge_class = 'bg-success'; 135 } 136 137 // Remove special characters from plugin_or_theme 138 $plugin_or_theme = str_replace('-', ' ', $stat['source']); 139 140 ?> 141 <li class="list-group-item row"> 142 <div class="col-lg-6 col-12"> 143 <div class="fw-semibold"> <?php echo esc_html( ucfirst( $plugin_or_theme ) ); ?> </div> 144 <div class="item-meta"><?php echo esc_html( $stat['sig'] ); ?> · <span class="mono"><?php echo esc_html( $stat['fileline'] ); ?></span></div> 145 </div> 146 <div class="text-end col-lg-2 col-12"> 147 <span class="badge <?php echo esc_attr( $badge_class ); ?>"><?php echo esc_html( sprintf( 148 /* translators: %s: total execution time in milliseconds */ 149 esc_html__( 'Total: %s ms', 'sitepulse' ), 150 number_format( $time_ms, 2 ) 151 ) ); ?></span> 152 <div class="text-muted small mt-1"> 153 <p><?php echo esc_html( sprintf( 154 /* translators: %s: average execution time in milliseconds */ 155 esc_html__( 'Avg: %s', 'sitepulse' ), 156 number_format( $avg, 2 ) 157 ) ); ?></p> 158 </div> 159 </div> 160 <div class="text-end col-lg-2 col-12"> 161 <span class="badge bg-success"><?php echo esc_html( sprintf( 162 /* translators: %s: WordPress hook name */ 163 esc_html__( 'hook: %s', 'sitepulse' ), 164 $stat['hook'] 165 ) ); ?></span> 166 <div class="text-muted small mt-1"> 167 <?php echo esc_html( sprintf( 168 /* translators: %d: number of times the hook was called */ 169 esc_html__( '%d calls', 'sitepulse' ), 170 (int) $stat['calls'] 171 ) ); ?> 172 </div> 173 </div> 174 </li> 117 175 <?php 118 176 } 119 120 foreach ( $stats as $stat ) : 121 if ( ! empty( $stat ) ) { 122 $avg = $stat['calls'] ? ($stat['total'] / $stat['calls']) : 0; 123 124 // Assign badge class based on total time in ms 125 $time_ms = $stat['total'] * 1000; 126 if ($time_ms > 1000) { 127 $badge_class = 'bg-danger'; 128 } elseif ($time_ms > 500) { 129 $badge_class = 'bg-warning text-dark'; 130 } elseif ($time_ms > 200) { 131 $badge_class = 'bg-info text-dark'; 132 } elseif ($time_ms > 100) { 133 $badge_class = 'bg-secondary'; 134 } else { 135 $badge_class = 'bg-success'; 136 } 137 138 // Remove special characters from plugin_or_theme 139 $plugin_or_theme = str_replace('-', ' ', $stat['source']); 140 141 ?> 142 <li class="list-group-item row"> 143 <div class="col-lg-6 col-12"> 144 <div class="fw-semibold"> <?php echo esc_html( ucfirst( $plugin_or_theme ) ); ?> </div> 145 <div class="item-meta"><?php echo esc_html( $stat['sig'] ); ?> · <span class="mono"><?php echo esc_html( $stat['fileline'] ); ?></span></div> 146 </div> 147 <div class="text-end col-lg-2 col-12"> 148 <span class="badge <?php echo esc_attr( $badge_class ); ?>"><?php echo esc_html( sprintf( 149 /* translators: %s: total execution time in milliseconds */ 150 esc_html__( 'Total: %s ms', 'sitepulse' ), 151 number_format( $time_ms, 2 ) 152 ) ); ?></span> 153 <div class="text-muted small mt-1"> 154 <p><?php echo esc_html( sprintf( 155 /* translators: %s: average execution time in milliseconds */ 156 esc_html__( 'Avg: %s', 'sitepulse' ), 157 number_format( $avg * 1000, 2 ) 158 ) ); ?></p> 159 </div> 160 </div> 161 <div class="text-end col-lg-2 col-12"> 162 <span class="badge bg-success"><?php echo esc_html( sprintf( 163 /* translators: %s: WordPress hook name */ 164 esc_html__( 'hook: %s', 'sitepulse' ), 165 $stat['hook'] 166 ) ); ?></span> 167 <div class="text-muted small mt-1"> 168 <?php echo esc_html( sprintf( 169 /* translators: %d: number of times the hook was called */ 170 esc_html__( '%d calls', 'sitepulse' ), 171 (int) $stat['calls'] 172 ) ); ?> 173 </div> 174 </div> 175 </li> 176 <?php 177 } 178 endforeach; ?> 179 180 <!-- example static items commented out --> 181 </ul> 182 </div> 177 endforeach; ?> 178 179 <!-- example static items commented out --> 180 </ul> 183 181 </div> 184 182 </div> 183 185 184 186 185 <?php if ( isset($disk_write) && is_array($disk_write) ): ?>
Note: See TracChangeset
for help on using the changeset viewer.