Ticket #46573: 46573.5.diff
File 46573.5.diff, 140.8 KB (added by , 6 years ago) |
---|
-
Gruntfile.js
diff --git a/Gruntfile.js b/Gruntfile.js index a27fc2b735..f40fa336cf 100644
a b module.exports = function(grunt) { 263 263 [ WORKING_DIR + 'wp-admin/js/tags-box.js' ]: [ './src/js/_enqueues/admin/tags-box.js' ], 264 264 [ WORKING_DIR + 'wp-admin/js/tags-suggest.js' ]: [ './src/js/_enqueues/admin/tags-suggest.js' ], 265 265 [ WORKING_DIR + 'wp-admin/js/tags.js' ]: [ './src/js/_enqueues/admin/tags.js' ], 266 [ WORKING_DIR + 'wp-admin/js/site-health.js' ]: [ './src/js/_enqueues/admin/site-health.js' ], 266 267 [ WORKING_DIR + 'wp-admin/js/theme-plugin-editor.js' ]: [ './src/js/_enqueues/wp/theme-plugin-editor.js' ], 267 268 [ WORKING_DIR + 'wp-admin/js/theme.js' ]: [ './src/js/_enqueues/wp/theme.js' ], 268 269 [ WORKING_DIR + 'wp-admin/js/updates.js' ]: [ './src/js/_enqueues/wp/updates.js' ], -
new file src/js/_enqueues/admin/site-health.js
diff --git a/src/js/_enqueues/admin/site-health.js b/src/js/_enqueues/admin/site-health.js new file mode 100644 index 0000000000..139875f49e
- + 1 /** 2 * Interactions used by the Site Health modules in WordPress. 3 * 4 * @output wp-admin/js/site-health.js 5 */ 6 7 /* global ajaxurl, SiteHealth, wp */ 8 9 jQuery( document ).ready( function( $ ) { 10 11 var data; 12 13 // Debug information copy section. 14 $( '.health-check-copy-field' ).click( function( e ) { 15 var $textarea = $( '#system-information-' + $( this ).data( 'copy-field' ) + '-copy-field' ), 16 $wrapper = $( this ).closest( 'div' ); 17 18 e.preventDefault(); 19 20 $textarea.select(); 21 22 if ( document.execCommand( 'copy' ) ) { 23 $( '.copy-field-success', $wrapper ).addClass( 'visible' ); 24 $( this ).focus(); 25 26 wp.a11y.speak( SiteHealth.string.site_info_copied, 'polite' ); 27 } 28 } ); 29 30 // Accordion handling in various areas. 31 $( '.health-check-accordion' ).on( 'click', '.health-check-accordion-trigger', function() { 32 var isExpanded = ( 'true' === $( this ).attr( 'aria-expanded' ) ); 33 34 if ( isExpanded ) { 35 $( this ).attr( 'aria-expanded', 'false' ); 36 $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', true ); 37 } else { 38 $( this ).attr( 'aria-expanded', 'true' ); 39 $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', false ); 40 } 41 } ); 42 43 $( '.health-check-accordion' ).on( 'keyup', '.health-check-accordion-trigger', function( e ) { 44 if ( '38' === e.keyCode.toString() ) { 45 $( '.health-check-accordion-trigger', $( this ).closest( 'dt' ).prevAll( 'dt' ) ).focus(); 46 } else if ( '40' === e.keyCode.toString() ) { 47 $( '.health-check-accordion-trigger', $( this ).closest( 'dt' ).nextAll( 'dt' ) ).focus(); 48 } 49 } ); 50 51 // Site Health test handling. 52 53 $( '.site-health-view-passed' ).on( 'click', function() { 54 var goodIssuesWrapper = $( '#health-check-issues-good' ); 55 56 goodIssuesWrapper.toggleClass( 'hidden' ); 57 $( this ).attr( 'aria-expanded', ! goodIssuesWrapper.hasClass( 'hidden' ) ); 58 } ); 59 60 /** 61 * Append a new issue to the issue list. 62 * 63 * @since 5.2.0 64 * 65 * @param {Object} issue The issue data. 66 */ 67 function AppendIssue( issue ) { 68 var template = wp.template( 'health-check-issue' ), 69 issueWrapper = $( '#health-check-issues-' + issue.status ), 70 issueCounter = $( '.issue-count', issueWrapper ); 71 72 SiteHealth.site_status.issues[ issue.status ]++; 73 74 issueCounter.text( SiteHealth.site_status.issues[ issue.status ] ); 75 $( '.issues', '#health-check-issues-' + issue.status ).append( template( issue ) ); 76 } 77 78 /** 79 * Update site health status indicator as asynchronous tests are run and returned. 80 * 81 * @since 5.2.0 82 */ 83 function RecalculateProgression() { 84 var r, c, pct; 85 var $progressBar = $( '#progressbar' ); 86 var $circle = $( '#progressbar svg #bar' ); 87 var totalTests = parseInt( SiteHealth.site_status.issues.good, 0 ) + parseInt( SiteHealth.site_status.issues.recommended, 0 ) + ( parseInt( SiteHealth.site_status.issues.critical, 0 ) * 1.5 ); 88 var failedTests = parseInt( SiteHealth.site_status.issues.recommended, 0 ) + ( parseInt( SiteHealth.site_status.issues.critical, 0 ) * 1.5 ); 89 var val = 100 - Math.ceil( ( failedTests / totalTests ) * 100 ); 90 91 if ( 0 === totalTests ) { 92 $progressBar.addClass( 'hidden' ); 93 return; 94 } 95 96 $progressBar.removeClass( 'loading' ); 97 98 r = $circle.attr( 'r' ); 99 c = Math.PI * ( r * 2 ); 100 101 if ( 0 > val ) { 102 val = 0; 103 } 104 if ( 100 < val ) { 105 val = 100; 106 } 107 108 pct = ( ( 100 - val ) / 100 ) * c; 109 110 $circle.css({ strokeDashoffset: pct } ); 111 112 if ( 1 > parseInt( SiteHealth.site_status.issues.critical, 0 ) ) { 113 $( '#health-check-issues-critical' ).addClass( 'hidden' ); 114 } 115 116 if ( 1 > parseInt( SiteHealth.site_status.issues.recommended, 0 ) ) { 117 $( '#health-check-issues-recommended' ).addClass( 'hidden' ); 118 } 119 120 if ( 50 <= val ) { 121 $circle.addClass( 'orange' ).removeClass( 'red' ); 122 } 123 124 if ( 90 <= val ) { 125 $circle.addClass( 'green' ).removeClass( 'orange' ); 126 } 127 128 if ( 100 === val ) { 129 $( '.site-status-all-clear' ).removeClass( 'hide' ); 130 $( '.site-status-has-issues' ).addClass( 'hide' ); 131 } 132 133 $progressBar.attr( 'data-pct', val ); 134 $progressBar.attr( 'aria-valuenow', val ); 135 136 $( '.health-check-body' ).attr( 'aria-hidden', false ); 137 138 $.post( 139 ajaxurl, 140 { 141 'action': 'health-check-site-status-result', 142 '_wpnonce': SiteHealth.nonce.site_status_result, 143 'counts': SiteHealth.site_status.issues 144 } 145 ); 146 147 wp.a11y.speak( SiteHealth.string.site_health_complete_screen_reader.replace( '%s', val + '%' ), 'polite' ); 148 } 149 150 /** 151 * Queue the next asynchronous test when we're ready to run it. 152 * 153 * @since 5.2.0 154 */ 155 function maybeRunNextAsyncTest() { 156 var doCalculation = true; 157 158 if ( 1 <= SiteHealth.site_status.async.length ) { 159 $.each( SiteHealth.site_status.async, function() { 160 var data = { 161 'action': 'health-check-' + this.test.replace( '_', '-' ), 162 '_wpnonce': SiteHealth.nonce.site_status 163 }; 164 165 if ( this.completed ) { 166 return true; 167 } 168 169 doCalculation = false; 170 171 this.completed = true; 172 173 $.post( 174 ajaxurl, 175 data, 176 function( response ) { 177 AppendIssue( response.data ); 178 maybeRunNextAsyncTest(); 179 } 180 ); 181 182 return false; 183 } ); 184 } 185 186 if ( doCalculation ) { 187 RecalculateProgression(); 188 } 189 } 190 191 if ( 'undefined' !== typeof SiteHealth ) { 192 if ( 0 === SiteHealth.site_status.direct.length && 0 === SiteHealth.site_status.async.length ) { 193 RecalculateProgression(); 194 } else { 195 SiteHealth.site_status.issues = { 196 'good': 0, 197 'recommended': 0, 198 'critical': 0 199 }; 200 } 201 202 if ( 0 < SiteHealth.site_status.direct.length ) { 203 $.each( SiteHealth.site_status.direct, function() { 204 AppendIssue( this ); 205 } ); 206 } 207 208 if ( 0 < SiteHealth.site_status.async.length ) { 209 data = { 210 'action': 'health-check-' + SiteHealth.site_status.async[0].test.replace( '_', '-' ), 211 '_wpnonce': SiteHealth.nonce.site_status 212 }; 213 214 SiteHealth.site_status.async[0].completed = true; 215 216 $.post( 217 ajaxurl, 218 data, 219 function( response ) { 220 AppendIssue( response.data ); 221 maybeRunNextAsyncTest(); 222 } 223 ); 224 } else { 225 RecalculateProgression(); 226 } 227 } 228 229 } ); -
src/wp-admin/admin-ajax.php
diff --git a/src/wp-admin/admin-ajax.php b/src/wp-admin/admin-ajax.php index 93c32393e0..e0194ceba8 100644
a b $core_actions_post = array( 131 131 'edit-theme-plugin-file', 132 132 'wp-privacy-export-personal-data', 133 133 'wp-privacy-erase-personal-data', 134 'health-check-site-status-result', 135 'health-check-dotorg-communication', 136 'health-check-is-in-debug-mode', 137 'health-check-background-updates', 138 'health-check-loopback-requests', 134 139 ); 135 140 136 141 // Deprecated -
new file src/wp-admin/css/site-health.css
diff --git a/src/wp-admin/css/site-health.css b/src/wp-admin/css/site-health.css new file mode 100644 index 0000000000..c12b52b019
- + 1 body.site-health #wpcontent { 2 padding-left: 0; 3 } 4 5 body.site-health .wrap { 6 margin-right: 0; 7 margin-left: 0; 8 } 9 10 body.site-health .wrap h2 { 11 padding: 1rem 0; 12 font-size: 1.3em; 13 line-height: 1.4; 14 font-weight: 600; 15 } 16 17 body.site-health ul li, 18 body.site-health ol li { 19 line-height: 1.5; 20 } 21 22 body.site-health .health-check-header { 23 text-align: center; 24 margin-top: 0; 25 margin-bottom: 1rem; 26 background: #fff; 27 border-bottom: 1px solid #e2e4e7; 28 } 29 30 body.site-health .health-check-header .title-section { 31 display: flex; 32 align-items: center; 33 justify-content: center; 34 } 35 36 body.site-health .health-check-header .title-section h1 { 37 display: inline-block; 38 font-weight: 600; 39 margin: 1rem 0.8rem 1rem 0.8rem; 40 } 41 42 body.site-health .health-check-header .title-section #progressbar { 43 display: inline-block; 44 height: 40px; 45 width: 40px; 46 margin: 0; 47 border-radius: 100%; 48 position: relative; 49 font-weight: 600; 50 font-size: 0.4rem; 51 } 52 53 body.site-health .health-check-header .title-section #progressbar:after { 54 position: absolute; 55 display: block; 56 height: 80px; 57 width: 80px; 58 left: 50%; 59 top: 50%; 60 content: attr(data-pct) "%"; 61 margin-top: -40px; 62 margin-left: -40px; 63 border-radius: 100%; 64 line-height: 80px; 65 font-size: 2em; 66 } 67 68 body.site-health .health-check-header .title-section #progressbar.hidden { 69 display: none; 70 } 71 72 body.site-health .health-check-header .title-section #progressbar.loading:after { 73 animation: loadingEllipsis 3s infinite ease-in-out; 74 } 75 76 body.site-health .health-check-header .title-section #progressbar.loading svg #bar { 77 stroke-dashoffset: 0; 78 stroke: #adc5d2; 79 animation: loadingPulse 3s infinite ease-in-out; 80 } 81 82 body.site-health .health-check-header .title-section #progressbar svg circle { 83 stroke-dashoffset: 0; 84 transition: stroke-dashoffset 1s linear; 85 stroke: #ccc; 86 stroke-width: 2em; 87 } 88 89 body.site-health .health-check-header .title-section #progressbar svg #bar { 90 stroke-dashoffset: 565; 91 stroke: #dc3232; 92 } 93 94 body.site-health .health-check-header .title-section #progressbar svg #bar.green { 95 stroke: #46b450; 96 } 97 98 body.site-health .health-check-header .title-section #progressbar svg #bar.orange { 99 stroke: #ffb900; 100 } 101 102 @keyframes loadingPulse { 103 0% { 104 stroke: #adc5d2; 105 } 106 50% { 107 stroke: #00a0d2; 108 } 109 100% { 110 stroke: #adc5d2; 111 } 112 } 113 114 @keyframes loadingEllipsis { 115 0% { 116 content: "."; 117 } 118 50% { 119 content: ".."; 120 } 121 100% { 122 content: "..."; 123 } 124 } 125 126 body.site-health .health-check-header .tabs-wrapper { 127 display: block; 128 } 129 130 body.site-health .health-check-header .tabs-wrapper .tab { 131 float: none; 132 display: inline-block; 133 text-decoration: none; 134 color: inherit; 135 padding: 0.5rem 1rem 1rem; 136 margin: 0 1rem; 137 transition: box-shadow 0.5s ease-in-out; 138 } 139 140 body.site-health .health-check-header .tabs-wrapper .tab:focus { 141 color: #191e23; 142 outline: 1px solid #6c7781; 143 box-shadow: none; 144 } 145 146 body.site-health .health-check-header .tabs-wrapper .tab.active { 147 box-shadow: inset 0 -3px #007cba; 148 font-weight: 600; 149 } 150 151 body.site-health .health-check-body { 152 max-width: 800px; 153 margin: 0 auto; 154 } 155 156 body.site-health .health-check-table thead th:first-child, 157 body.site-health .health-check-table thead td:first-child { 158 width: 30%; 159 } 160 161 body.site-health .health-check-table tbody td { 162 width: 70%; 163 } 164 165 body.site-health .health-check-table tbody td:first-child { 166 width: 30%; 167 } 168 169 body.site-health .health-check-table tbody td ul, 170 body.site-health .health-check-table tbody td ol { 171 margin: 0; 172 } 173 174 body.site-health .pass:before, 175 body.site-health .good:before { 176 content: "\f147"; 177 display: inline-block; 178 color: #46b450; 179 font-family: dashicons; 180 vertical-align: top; 181 } 182 183 body.site-health .warning:before { 184 content: "\f460"; 185 display: inline-block; 186 color: #ffb900; 187 font-family: dashicons; 188 } 189 190 body.site-health .info:before { 191 content: "\f348"; 192 display: inline-block; 193 color: #00a0d2; 194 font-family: dashicons; 195 } 196 197 body.site-health .fail:before, 198 body.site-health .error:before { 199 content: "\f335"; 200 display: inline-block; 201 color: #dc3232; 202 font-family: dashicons; 203 } 204 205 body.site-health .spinner { 206 float: none; 207 } 208 209 body.site-health .system-information-copy-wrapper { 210 display: block; 211 margin: 1rem 0; 212 } 213 214 body.site-health .system-information-copy-wrapper textarea { 215 border: 0; 216 padding: 0; 217 margin: 0; 218 position: absolute; 219 left: -9999px; 220 top: -9999px; 221 } 222 223 body.site-health .health-check-toggle-copy-section:hover { 224 background: none; 225 } 226 227 body.site-health .system-information-copy-wrapper .copy-button-wrapper { 228 margin: 0.5rem 0 1rem; 229 } 230 231 body.site-health .copy-field-success { 232 display: none; 233 color: #40860a; 234 line-height: 1.8; 235 margin-left: 0.5rem; 236 } 237 238 body.site-health .copy-field-success.visible { 239 display: inline-block; 240 height: 28px; 241 line-height: 28px; 242 } 243 244 body.site-health .site-status-has-issues { 245 display: block; 246 } 247 248 body.site-health .site-status-has-issues.hide { 249 display: none; 250 } 251 252 body.site-health h3 { 253 padding: 0; 254 font-weight: 400; 255 } 256 257 body.site-health .view-more { 258 text-align: center; 259 } 260 261 body.site-health .issues-wrapper:first-of-type { 262 margin-top: 3rem; 263 } 264 265 body.site-health .issues-wrapper { 266 margin-bottom: 3rem; 267 margin-top: 2rem; 268 } 269 270 body.site-health .site-status-all-clear { 271 display: flex; 272 flex-direction: column; 273 align-items: center; 274 justify-content: center; 275 text-align: center; 276 height: 100%; 277 width: 100%; 278 margin-top: 0; 279 } 280 281 @media all and (min-width: 784px) { 282 body.site-health .site-status-all-clear { 283 margin: 5rem 0; 284 } 285 } 286 287 body.site-health .site-status-all-clear.hide { 288 display: none; 289 } 290 291 body.site-health .site-status-all-clear .dashicons { 292 font-size: 150px; 293 height: 130px; 294 width: 150px; 295 } 296 297 body.site-health .site-status-all-clear .encouragement { 298 font-size: 1.5rem; 299 font-weight: 600; 300 } 301 302 body.site-health .site-status-all-clear p { 303 margin: 0; 304 } 305 306 body .health-check-accordion { 307 border: 1px solid #e2e4e7; 308 } 309 310 body .health-check-accordion dt { 311 font-weight: 600; 312 border-top: 1px solid #e2e4e7; 313 } 314 315 body .health-check-accordion dt:first-child { 316 border-radius: 0.3em 0.3em 0 0; 317 border-top: none; 318 } 319 320 body .health-check-accordion .health-check-accordion-trigger { 321 background: #fff; 322 border: 0; 323 color: #212121; 324 cursor: pointer; 325 display: block; 326 font-weight: 400; 327 margin: 0; 328 padding: 1em 1.5em; 329 position: relative; 330 text-align: left; 331 width: 100%; 332 } 333 334 body .health-check-accordion .health-check-accordion-trigger:hover, 335 body .health-check-accordion .health-check-accordion-trigger:active { 336 background: #f8f9f9; 337 } 338 339 body .health-check-accordion .health-check-accordion-trigger:focus { 340 color: #191e23; 341 border: none; 342 box-shadow: none; 343 outline-offset: -2px; 344 outline: 1px dotted #555d66; 345 } 346 347 body .health-check-accordion .health-check-accordion-trigger .title { 348 display: inline-block; 349 pointer-events: none; 350 font-weight: 600; 351 } 352 353 body .health-check-accordion .health-check-accordion-trigger .icon { 354 border: solid #191e23; 355 border-width: 0 2px 2px 0; 356 height: 0.5rem; 357 pointer-events: none; 358 position: absolute; 359 right: 1.5em; 360 top: 50%; 361 transform: translateY(-70%) rotate(45deg); 362 width: 0.5rem; 363 } 364 365 body .health-check-accordion .health-check-accordion-trigger .badge { 366 display: inline-block; 367 padding: 0.1rem 0.5rem 0.15rem; 368 background-color: #d7dade; 369 border-radius: 3px; 370 color: #000; 371 font-weight: 600; 372 margin: 0 0.5rem; 373 } 374 375 body .health-check-accordion .health-check-accordion-trigger .badge.blue { 376 background-color: #0073af; 377 color: #fff; 378 } 379 380 body .health-check-accordion .health-check-accordion-trigger .badge.orange { 381 background-color: #ffb900; 382 color: #000; 383 } 384 385 body .health-check-accordion .health-check-accordion-trigger .badge.red { 386 background-color: #dc3232; 387 color: #fff; 388 } 389 390 body .health-check-accordion .health-check-accordion-trigger .badge.green { 391 background-color: #40860a; 392 color: #fff; 393 } 394 395 body .health-check-accordion .health-check-accordion-trigger .badge.pink { 396 background-color: #f4b0fc; 397 color: #000; 398 } 399 400 body .health-check-accordion .health-check-accordion-trigger .badge.gray { 401 background-color: #ccc; 402 color: #000; 403 } 404 405 body .health-check-accordion .health-check-accordion-trigger .badge.light-blue { 406 background-color: #10e9fb; 407 color: #000; 408 } 409 410 body .health-check-accordion .health-check-accordion-trigger .badge.light-green { 411 background-color: #60f999; 412 color: #000; 413 } 414 415 body .health-check-accordion .health-check-accordion-trigger[aria-expanded="true"] .icon { 416 transform: translateY(-30%) rotate(-135deg) 417 } 418 419 body .health-check-accordion .health-check-accordion-panel { 420 margin: 0; 421 padding: 1em 1.5em; 422 background: #fff; 423 } 424 425 body .health-check-accordion .health-check-accordion-panel > div { 426 display: block; 427 } 428 429 body .health-check-accordion .health-check-accordion-panel[hidden] { 430 display: none; 431 } 432 433 body .health-check-accordion dl dd { 434 margin: 0 0 0.5em 2em; 435 } 436 437 @media screen and (max-width: 782px) { 438 body.site-health .health-check-body { 439 margin: 0 12px; 440 width: initial; 441 } 442 } 443 444 /* The breakpoint is usually at 960px, the additional space is to allow for the margin. */ 445 @media only screen and (max-width: 1004px) { 446 body.site-health .health-check-body { 447 margin: 0 22px; 448 width: initial; 449 } 450 } 451 No newline at end of file -
src/wp-admin/css/wp-admin.css
diff --git a/src/wp-admin/css/wp-admin.css b/src/wp-admin/css/wp-admin.css index 14c10f9cd9..b475cf0aaf 100644
a b 12 12 @import url(widgets.css); 13 13 @import url(site-icon.css); 14 14 @import url(l10n.css); 15 @import url(site-health.css); -
src/wp-admin/includes/ajax-actions.php
diff --git a/src/wp-admin/includes/ajax-actions.php b/src/wp-admin/includes/ajax-actions.php index 474319e75c..cb39a2ee4f 100644
a b function wp_ajax_wp_privacy_erase_personal_data() { 4858 4858 4859 4859 wp_send_json_success( $response ); 4860 4860 } 4861 4862 /** 4863 * Ajax handler for site health checks on server communication. 4864 * 4865 * @since 5.2.0 4866 */ 4867 function wp_ajax_health_check_dotorg_communication() { 4868 check_ajax_referer( 'health-check-site-status' ); 4869 4870 if ( ! current_user_can( 'install_plugins' ) ) { 4871 wp_send_json_error(); 4872 } 4873 4874 if ( ! class_exists( 'WP_Site_Health' ) ) { 4875 require_once( ABSPATH . 'wp-admin/includes/class-wp-site-health.php' ); 4876 } 4877 4878 $site_health = new WP_Site_Health(); 4879 wp_send_json_success( $site_health->get_test_dotorg_communication() ); 4880 } 4881 4882 /** 4883 * Ajax handler for site health checks on debug mode. 4884 * 4885 * @since 5.2.0 4886 */ 4887 function wp_ajax_health_check_is_in_debug_mode() { 4888 wp_verify_nonce( 'health-check-site-status' ); 4889 4890 if ( ! current_user_can( 'install_plugins' ) ) { 4891 wp_send_json_error(); 4892 } 4893 4894 if ( ! class_exists( 'WP_Site_Health' ) ) { 4895 require_once( ABSPATH . 'wp-admin/includes/class-wp-site-health.php' ); 4896 } 4897 4898 $site_health = new WP_Site_Health(); 4899 wp_send_json_success( $site_health->get_test_is_in_debug_mode() ); 4900 } 4901 4902 /** 4903 * Ajax handler for site health checks on background updates. 4904 * 4905 * @since 5.2.0 4906 */ 4907 function wp_ajax_health_check_background_updates() { 4908 check_ajax_referer( 'health-check-site-status' ); 4909 4910 if ( ! current_user_can( 'install_plugins' ) ) { 4911 wp_send_json_error(); 4912 } 4913 4914 if ( ! class_exists( 'WP_Site_Health' ) ) { 4915 require_once( ABSPATH . 'wp-admin/includes/class-wp-site-health.php' ); 4916 } 4917 4918 $site_health = new WP_Site_Health(); 4919 wp_send_json_success( $site_health->get_test_background_updates() ); 4920 } 4921 4922 4923 /** 4924 * Ajax handler for site health checks on loopback requests. 4925 * 4926 * @since 5.2.0 4927 */ 4928 function wp_ajax_health_check_loopback_requests() { 4929 check_ajax_referer( 'health-check-site-status' ); 4930 4931 if ( ! current_user_can( 'install_plugins' ) ) { 4932 wp_send_json_error(); 4933 } 4934 4935 if ( ! class_exists( 'WP_Site_Health' ) ) { 4936 require_once( ABSPATH . 'wp-admin/includes/class-wp-site-health.php' ); 4937 } 4938 4939 $site_health = new WP_Site_Health(); 4940 wp_send_json_success( $site_health->get_test_loopback_requests() ); 4941 } 4942 4943 /** 4944 * Ajax handler for site health check to update the result status. 4945 * 4946 * @since 5.2.0 4947 */ 4948 function wp_ajax_health_check_site_status_result() { 4949 check_ajax_referer( 'health-check-site-status-result' ); 4950 4951 if ( ! current_user_can( 'install_plugins' ) ) { 4952 wp_send_json_error(); 4953 } 4954 4955 set_transient( 'health-check-site-status-result', wp_json_encode( $_POST['counts'] ) ); 4956 4957 wp_send_json_success(); 4958 } -
new file src/wp-admin/includes/class-wp-debug-data.php
diff --git a/src/wp-admin/includes/class-wp-debug-data.php b/src/wp-admin/includes/class-wp-debug-data.php new file mode 100644 index 0000000000..6e82e0b672
- + 1 <?php 2 /** 3 * Class for providing debug data based on a users WordPress environment. 4 * 5 * @package WordPress 6 * @subpackage Site_Health 7 * @since 5.2.0 8 */ 9 10 class WP_Debug_Data { 11 /** 12 * Calls all core functions to check for updates. 13 * 14 * @since 5.2.0 15 */ 16 static function check_for_updates() { 17 wp_version_check(); 18 wp_update_plugins(); 19 wp_update_themes(); 20 } 21 22 /** 23 * Static function for generating site debug data when required. 24 * 25 * @since 5.2.0 26 * 27 * @throws ImagickException 28 * @global wpdb $wpdb WordPress database abstraction object. 29 * 30 * @param string $locale Optional. An ISO formatted language code to provide debug translations in. Default null. 31 * @return array The debug data for the site. 32 */ 33 static function debug_data( $locale = null ) { 34 global $wpdb; 35 36 if ( ! empty( $locale ) ) { 37 // Change the language used for translations 38 if ( function_exists( 'switch_to_locale' ) ) { 39 $original_locale = get_locale(); 40 $switched_locale = switch_to_locale( $locale ); 41 } 42 } 43 44 $upload_dir = wp_upload_dir(); 45 if ( file_exists( ABSPATH . 'wp-config.php' ) ) { 46 $wp_config_path = ABSPATH . 'wp-config.php'; 47 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged 48 } elseif ( @file_exists( dirname( ABSPATH ) . '/wp-config.php' ) && ! @file_exists( dirname( ABSPATH ) . '/wp-settings.php' ) ) { 49 $wp_config_path = dirname( ABSPATH ) . '/wp-config.php'; 50 } 51 52 $core_current_version = get_bloginfo( 'version' ); 53 $core_updates = get_core_updates(); 54 $core_update_needed = ''; 55 56 foreach ( $core_updates as $core => $update ) { 57 if ( 'upgrade' === $update->response ) { 58 // translators: %s: Latest WordPress version number. 59 $core_update_needed = ' ' . sprintf( __( '( Latest version: %s )' ), $update->version ); 60 } else { 61 $core_update_needed = ''; 62 } 63 } 64 65 // Set up the array that holds all debug information. 66 $info = array( 67 'wp-core' => array( 68 'label' => __( 'WordPress' ), 69 'fields' => array( 70 'version' => array( 71 'label' => __( 'Version' ), 72 'value' => $core_current_version . $core_update_needed, 73 ), 74 'language' => array( 75 'label' => __( 'Language' ), 76 'value' => ( ! empty( $locale ) ? $original_locale : get_locale() ), 77 ), 78 'home_url' => array( 79 'label' => __( 'Home URL' ), 80 'value' => get_bloginfo( 'url' ), 81 'private' => true, 82 ), 83 'site_url' => array( 84 'label' => __( 'Site URL' ), 85 'value' => get_bloginfo( 'wpurl' ), 86 'private' => true, 87 ), 88 'permalink' => array( 89 'label' => __( 'Permalink structure' ), 90 'value' => get_option( 'permalink_structure' ) ?: __( 'No permalink structure set' ), 91 ), 92 'https_status' => array( 93 'label' => __( 'Is this site using HTTPS?' ), 94 'value' => ( is_ssl() ? __( 'Yes' ) : __( 'No' ) ), 95 ), 96 'user_registration' => array( 97 'label' => __( 'Can anyone register on this site?' ), 98 'value' => ( get_option( 'users_can_register' ) ? __( 'Yes' ) : __( 'No' ) ), 99 ), 100 'default_comment_status' => array( 101 'label' => __( 'Default comment status' ), 102 'value' => get_option( 'default_comment_status' ), 103 ), 104 'multisite' => array( 105 'label' => __( 'Is this a multisite?' ), 106 'value' => ( is_multisite() ? __( 'Yes' ) : __( 'No' ) ), 107 ), 108 ), 109 ), 110 'wp-paths-sizes' => array( 111 'label' => __( 'Directories and Sizes' ), 112 'fields' => array(), 113 ), 114 'wp-dropins' => array( 115 'label' => __( 'Drop-ins' ), 116 'show_count' => true, 117 'description' => __( 'Drop-ins are single files that replace or enhance WordPress features in ways that are not possible for traditional plugins.' ), 118 'fields' => array(), 119 ), 120 'wp-active-theme' => array( 121 'label' => __( 'Active Theme' ), 122 'fields' => array(), 123 ), 124 'wp-themes' => array( 125 'label' => __( 'Other Themes' ), 126 'show_count' => true, 127 'fields' => array(), 128 ), 129 'wp-mu-plugins' => array( 130 'label' => __( 'Must Use Plugins' ), 131 'show_count' => true, 132 'fields' => array(), 133 ), 134 'wp-plugins-active' => array( 135 'label' => __( 'Active Plugins' ), 136 'show_count' => true, 137 'fields' => array(), 138 ), 139 'wp-plugins-inactive' => array( 140 'label' => __( 'Inactive Plugins' ), 141 'show_count' => true, 142 'fields' => array(), 143 ), 144 'wp-media' => array( 145 'label' => __( 'Media Handling' ), 146 'fields' => array(), 147 ), 148 'wp-server' => array( 149 'label' => __( 'Server' ), 150 'description' => __( 'The options shown below relate to your server setup. If changes are required, you may need your web host’s assistance.' ), 151 'fields' => array(), 152 ), 153 'wp-database' => array( 154 'label' => __( 'Database' ), 155 'fields' => array(), 156 ), 157 'wp-constants' => array( 158 'label' => __( 'WordPress Constants' ), 159 'description' => __( 'These settings are defined in your wp-config.php file, and alter where and how parts of WordPress are loaded.' ), 160 'fields' => array( 161 'ABSPATH' => array( 162 'label' => 'ABSPATH', 163 'value' => ( ! defined( 'ABSPATH' ) ? __( 'Undefined' ) : ABSPATH ), 164 'private' => true, 165 ), 166 'WP_HOME' => array( 167 'label' => 'WP_HOME', 168 'value' => ( ! defined( 'WP_HOME' ) ? __( 'Undefined' ) : WP_HOME ), 169 ), 170 'WP_SITEURL' => array( 171 'label' => 'WP_SITEURL', 172 'value' => ( ! defined( 'WP_SITEURL' ) ? __( 'Undefined' ) : WP_SITEURL ), 173 ), 174 'WP_CONTENT_DIR' => array( 175 'label' => 'WP_CONTENT_DIR', 176 'value' => ( ! defined( 'WP_CONTENT_DIR' ) ? __( 'Undefined' ) : WP_CONTENT_DIR ), 177 ), 178 'WP_PLUGIN_DIR' => array( 179 'label' => 'WP_PLUGIN_DIR', 180 'value' => ( ! defined( 'WP_PLUGIN_DIR' ) ? __( 'Undefined' ) : WP_PLUGIN_DIR ), 181 ), 182 'WP_DEBUG' => array( 183 'label' => 'WP_DEBUG', 184 'value' => ( ! defined( 'WP_DEBUG' ) ? __( 'Undefined' ) : ( WP_DEBUG ? __( 'Enabled' ) : __( 'Disabled' ) ) ), 185 ), 186 'WP_MAX_MEMORY_LIMIT' => array( 187 'label' => 'WP_MAX_MEMORY_LIMIT', 188 'value' => ( ! defined( 'WP_MAX_MEMORY_LIMIT' ) ? __( 'Undefined' ) : WP_MAX_MEMORY_LIMIT ), 189 ), 190 'WP_DEBUG_DISPLAY' => array( 191 'label' => 'WP_DEBUG_DISPLAY', 192 'value' => ( ! defined( 'WP_DEBUG_DISPLAY' ) ? __( 'Undefined' ) : ( WP_DEBUG_DISPLAY ? __( 'Enabled' ) : __( 'Disabled' ) ) ), 193 ), 194 'WP_DEBUG_LOG' => array( 195 'label' => 'WP_DEBUG_LOG', 196 'value' => ( ! defined( 'WP_DEBUG_LOG' ) ? __( 'Undefined' ) : ( WP_DEBUG_LOG ? __( 'Enabled' ) : __( 'Disabled' ) ) ), 197 ), 198 'SCRIPT_DEBUG' => array( 199 'label' => 'SCRIPT_DEBUG', 200 'value' => ( ! defined( 'SCRIPT_DEBUG' ) ? __( 'Undefined' ) : ( SCRIPT_DEBUG ? __( 'Enabled' ) : __( 'Disabled' ) ) ), 201 ), 202 'WP_CACHE' => array( 203 'label' => 'WP_CACHE', 204 'value' => ( ! defined( 'WP_CACHE' ) ? __( 'Undefined' ) : ( WP_CACHE ? __( 'Enabled' ) : __( 'Disabled' ) ) ), 205 ), 206 'CONCATENATE_SCRIPTS' => array( 207 'label' => 'CONCATENATE_SCRIPTS', 208 'value' => ( ! defined( 'CONCATENATE_SCRIPTS' ) ? __( 'Undefined' ) : ( CONCATENATE_SCRIPTS ? __( 'Enabled' ) : __( 'Disabled' ) ) ), 209 ), 210 'COMPRESS_SCRIPTS' => array( 211 'label' => 'COMPRESS_SCRIPTS', 212 'value' => ( ! defined( 'COMPRESS_SCRIPTS' ) ? __( 'Undefined' ) : ( COMPRESS_SCRIPTS ? __( 'Enabled' ) : __( 'Disabled' ) ) ), 213 ), 214 'COMPRESS_CSS' => array( 215 'label' => 'COMPRESS_CSS', 216 'value' => ( ! defined( 'COMPRESS_CSS' ) ? __( 'Undefined' ) : ( COMPRESS_CSS ? __( 'Enabled' ) : __( 'Disabled' ) ) ), 217 ), 218 'WP_LOCAL_DEV' => array( 219 'label' => 'WP_LOCAL_DEV', 220 'value' => ( ! defined( 'WP_LOCAL_DEV' ) ? __( 'Undefined' ) : ( WP_LOCAL_DEV ? __( 'Enabled' ) : __( 'Disabled' ) ) ), 221 ), 222 ), 223 ), 224 'wp-filesystem' => array( 225 'label' => __( 'Filesystem Permissions' ), 226 'description' => __( 'Shows whether WordPress is able to write to the directories it needs access to.' ), 227 'fields' => array( 228 'all' => array( 229 'label' => __( 'The main WordPress directory' ), 230 'value' => ( wp_is_writable( ABSPATH ) ? __( 'Writable' ) : __( 'Not writable' ) ), 231 ), 232 'wp-content' => array( 233 'label' => __( 'The wp-content directory' ), 234 'value' => ( wp_is_writable( WP_CONTENT_DIR ) ? __( 'Writable' ) : __( 'Not writable' ) ), 235 ), 236 'uploads' => array( 237 'label' => __( 'The uploads directory' ), 238 'value' => ( wp_is_writable( $upload_dir['basedir'] ) ? __( 'Writable' ) : __( 'Not writable' ) ), 239 ), 240 'plugins' => array( 241 'label' => __( 'The plugins directory' ), 242 'value' => ( wp_is_writable( WP_PLUGIN_DIR ) ? __( 'Writable' ) : __( 'Not writable' ) ), 243 ), 244 'themes' => array( 245 'label' => __( 'The themes directory' ), 246 'value' => ( wp_is_writable( get_template_directory() . '/..' ) ? __( 'Writable' ) : __( 'Not writable' ) ), 247 ), 248 ), 249 ), 250 ); 251 252 // Conditionally add debug information for multisite setups. 253 if ( is_multisite() ) { 254 $network_query = new WP_Network_Query(); 255 $network_ids = $network_query->query( 256 array( 257 'fields' => 'ids', 258 'number' => 100, 259 'no_found_rows' => false, 260 ) 261 ); 262 263 $site_count = 0; 264 foreach ( $network_ids as $network_id ) { 265 $site_count += get_blog_count( $network_id ); 266 } 267 268 $info['wp-core']['fields']['user_count'] = array( 269 'label' => __( 'User count' ), 270 'value' => get_user_count(), 271 ); 272 $info['wp-core']['fields']['site_count'] = array( 273 'label' => __( 'Site count' ), 274 'value' => $site_count, 275 ); 276 $info['wp-core']['fields']['network_count'] = array( 277 'label' => __( 'Network count' ), 278 'value' => $network_query->found_networks, 279 ); 280 } else { 281 $user_count = count_users(); 282 283 $info['wp-core']['fields']['user_count'] = array( 284 'label' => __( 'User count' ), 285 'value' => $user_count['total_users'], 286 ); 287 } 288 289 // WordPress features requiring processing. 290 $wp_dotorg = wp_remote_get( 291 'https://wordpress.org', 292 array( 293 'timeout' => 10, 294 ) 295 ); 296 if ( ! is_wp_error( $wp_dotorg ) ) { 297 $info['wp-core']['fields']['dotorg_communication'] = array( 298 'label' => __( 'Communication with WordPress.org' ), 299 'value' => sprintf( 300 __( 'WordPress.org is reachable' ) 301 ), 302 ); 303 } else { 304 $info['wp-core']['fields']['dotorg_communication'] = array( 305 'label' => __( 'Communication with WordPress.org' ), 306 'value' => sprintf( 307 // translators: %1$s: The IP address WordPress.org resolves to. %2$s: The error returned by the lookup. 308 __( 'Unable to reach WordPress.org at %1$s: %2$s' ), 309 gethostbyname( 'wordpress.org' ), 310 $wp_dotorg->get_error_message() 311 ), 312 ); 313 } 314 315 // Go through the various installation directories and calculate their sizes. 316 $uploads_dir = wp_upload_dir(); 317 $inaccurate = false; 318 319 /* 320 * We will be using the PHP max execution time to prevent the size calculations 321 * from causing a timeout. We provide a default value of 30 seconds, as some 322 * hosts do not allow you to read configuration values. 323 */ 324 $max_execution_time = 30; 325 $start_execution_time = microtime( true ); 326 if ( function_exists( 'ini_get' ) ) { 327 $max_execution_time = ini_get( 'max_execution_time' ); 328 } 329 330 $size_directories = array( 331 'wordpress' => array( 332 'path' => ABSPATH, 333 'size' => 0, 334 ), 335 'themes' => array( 336 'path' => trailingslashit( get_theme_root() ), 337 'size' => 0, 338 ), 339 'plugins' => array( 340 'path' => trailingslashit( WP_PLUGIN_DIR ), 341 'size' => 0, 342 ), 343 'uploads' => array( 344 'path' => $uploads_dir['basedir'], 345 'size' => 0, 346 ), 347 ); 348 349 // Loop over all the directories we want to gather the sizes for. 350 foreach ( $size_directories as $size => $attributes ) { 351 /* 352 * We run a helper function with a RecursiveIterator, which 353 * may throw an exception if it can't access directories. 354 * 355 * If a failure is detected we mark the result as inaccurate. 356 */ 357 try { 358 $calculated_size = WP_Debug_data::get_directory_size( $attributes['path'], $max_execution_time, $start_execution_time ); 359 360 $size_directories[ $size ]['size'] = $calculated_size; 361 362 /* 363 * If the size returned is -1, this means execution has 364 * exceeded the maximum execution time, also denoting an 365 * inaccurate value in the end. 366 */ 367 if ( -1 === $calculated_size ) { 368 $inaccurate = true; 369 } 370 } catch ( Exception $e ) { 371 $inaccurate = true; 372 } 373 } 374 375 $size_db = WP_Debug_Data::get_database_size(); 376 377 $size_total = $size_directories['wordpress']['size'] + $size_db; 378 379 $info['wp-paths-sizes']['fields'] = array( 380 array( 381 'label' => __( 'Uploads Directory Location' ), 382 'value' => $size_directories['uploads']['path'], 383 ), 384 array( 385 'label' => __( 'Uploads Directory Size' ), 386 'value' => ( -1 === $size_directories['uploads']['size'] ? __( 'Unable to determine the size of this directory' ) : size_format( $size_directories['uploads']['size'], 2 ) ), 387 ), 388 array( 389 'label' => __( 'Themes Directory Location' ), 390 'value' => $size_directories['themes']['path'], 391 ), 392 array( 393 'label' => __( 'Current Theme Directory' ), 394 'value' => get_template_directory(), 395 ), 396 array( 397 'label' => __( 'Themes Directory Size' ), 398 'value' => ( -1 === $size_directories['themes']['size'] ? __( 'Unable to determine the size of this directory' ) : size_format( $size_directories['themes']['size'], 2 ) ), 399 ), 400 array( 401 'label' => __( 'Plugins Directory Location' ), 402 'value' => $size_directories['plugins']['path'], 403 ), 404 array( 405 'label' => __( 'Plugins Directory Size' ), 406 'value' => ( -1 === $size_directories['plugins']['size'] ? __( 'Unable to determine the size of this directory' ) : size_format( $size_directories['plugins']['size'], 2 ) ), 407 ), 408 array( 409 'label' => __( 'WordPress Directory Location' ), 410 'value' => $size_directories['wordpress']['path'], 411 ), 412 array( 413 'label' => __( 'WordPress Directory Size' ), 414 'value' => size_format( $size_directories['wordpress']['size'], 2 ), 415 ), 416 array( 417 'label' => __( 'Database size' ), 418 'value' => size_format( $size_db, 2 ), 419 ), 420 array( 421 'label' => __( 'Total installation size' ), 422 'value' => sprintf( 423 '%s%s', 424 size_format( $size_total, 2 ), 425 ( false === $inaccurate ? '' : __( '- Some errors, likely caused by invalid permissions, were encountered when determining the size of your installation. This means the values represented may be inaccurate.' ) ) 426 ), 427 ), 428 ); 429 430 // Get a list of all drop-in replacements. 431 $dropins = get_dropins(); 432 $dropin_description = _get_dropins(); 433 foreach ( $dropins as $dropin_key => $dropin ) { 434 $info['wp-dropins']['fields'][ sanitize_key( $dropin_key ) ] = array( 435 'label' => $dropin_key, 436 'value' => $dropin_description[ $dropin_key ][0], 437 ); 438 } 439 440 // Populate the media fields. 441 $info['wp-media']['fields']['image_editor'] = array( 442 'label' => __( 'Active editor' ), 443 'value' => _wp_image_editor_choose(), 444 ); 445 446 // Get ImageMagic information, if available. 447 if ( class_exists( 'Imagick' ) ) { 448 // Save the Imagick instance for later use. 449 $imagick = new Imagick(); 450 $imagick_version = $imagick->getVersion(); 451 } else { 452 $imagick_version = __( 'Not available' ); 453 } 454 $info['wp-media']['fields']['imagick_module_version'] = array( 455 'label' => __( 'ImageMagick version number' ), 456 'value' => ( is_array( $imagick_version ) ? $imagick_version['versionNumber'] : $imagick_version ), 457 ); 458 $info['wp-media']['fields']['imagemagick_version'] = array( 459 'label' => __( 'ImageMagick version string' ), 460 'value' => ( is_array( $imagick_version ) ? $imagick_version['versionString'] : $imagick_version ), 461 ); 462 463 // If Imagick is used as our editor, provide some more information about its limitations. 464 if ( 'WP_Image_Editor_Imagick' === _wp_image_editor_choose() && isset( $imagick ) && $imagick instanceof Imagick ) { 465 $limits = array( 466 'area' => ( defined( 'imagick::RESOURCETYPE_AREA' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_AREA ) ) : __( 'Not available' ) ), 467 'disk' => ( defined( 'imagick::RESOURCETYPE_DISK' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_DISK ) : __( 'Not available' ) ), 468 'file' => ( defined( 'imagick::RESOURCETYPE_FILE' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_FILE ) : __( 'Not available' ) ), 469 'map' => ( defined( 'imagick::RESOURCETYPE_MAP' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MAP ) ) : __( 'Not available' ) ), 470 'memory' => ( defined( 'imagick::RESOURCETYPE_MEMORY' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MEMORY ) ) : __( 'Not available' ) ), 471 'thread' => ( defined( 'imagick::RESOURCETYPE_THREAD' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_THREAD ) : __( 'Not available' ) ), 472 ); 473 474 $info['wp-media']['fields']['imagick_limits'] = array( 475 'label' => __( 'Imagick Resource Limits' ), 476 'value' => $limits, 477 ); 478 } 479 480 // Get GD information, if available. 481 if ( function_exists( 'gd_info' ) ) { 482 $gd = gd_info(); 483 } else { 484 $gd = false; 485 } 486 $info['wp-media']['fields']['gd_version'] = array( 487 'label' => __( 'GD version' ), 488 'value' => ( is_array( $gd ) ? $gd['GD Version'] : __( 'Not available' ) ), 489 ); 490 491 // Get Ghostscript information, if available. 492 if ( function_exists( 'exec' ) ) { 493 $gs = exec( 'gs --version' ); 494 $gs = ( ! empty( $gs ) ? $gs : __( 'Not available' ) ); 495 } else { 496 $gs = __( 'Unable to determine if Ghostscript is installed' ); 497 } 498 $info['wp-media']['fields']['ghostscript_version'] = array( 499 'label' => __( 'Ghostscript version' ), 500 'value' => $gs, 501 ); 502 503 // Populate the server debug fields. 504 $info['wp-server']['fields']['server_architecture'] = array( 505 'label' => __( 'Server architecture' ), 506 'value' => ( ! function_exists( 'php_uname' ) ? __( 'Unable to determine server architecture' ) : sprintf( '%s %s %s', php_uname( 's' ), php_uname( 'r' ), php_uname( 'm' ) ) ), 507 ); 508 $info['wp-server']['fields']['httpd_software'] = array( 509 'label' => __( 'Web server' ), 510 'value' => ( isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : __( 'Unable to determine what web server software is used' ) ), 511 ); 512 $info['wp-server']['fields']['php_version'] = array( 513 'label' => __( 'PHP version' ), 514 'value' => ( ! function_exists( 'phpversion' ) ? __( 'Unable to determine PHP version' ) : sprintf( 515 '%s %s', 516 phpversion(), 517 ( 64 === PHP_INT_SIZE * 8 ? __( '(Supports 64bit values)' ) : __( '(Does not support 64bit values)' ) ) 518 ) 519 ), 520 ); 521 $info['wp-server']['fields']['php_sapi'] = array( 522 'label' => __( 'PHP SAPI' ), 523 'value' => ( ! function_exists( 'php_sapi_name' ) ? __( 'Unable to determine PHP SAPI' ) : php_sapi_name() ), 524 ); 525 526 // Some servers disable `ini_set()` and `ini_get()`, we check this before trying to get configuration values. 527 if ( ! function_exists( 'ini_get' ) ) { 528 $info['wp-server']['fields']['ini_get'] = array( 529 'label' => __( 'Server settings' ), 530 'value' => __( 'Unable to determine some settings, as the ini_get() function has been disabled.' ), 531 ); 532 } else { 533 $info['wp-server']['fields']['max_input_variables'] = array( 534 'label' => __( 'PHP max input variables' ), 535 'value' => ini_get( 'max_input_vars' ), 536 ); 537 $info['wp-server']['fields']['time_limit'] = array( 538 'label' => __( 'PHP time limit' ), 539 'value' => ini_get( 'max_execution_time' ), 540 ); 541 $info['wp-server']['fields']['memory_limit'] = array( 542 'label' => __( 'PHP memory limit' ), 543 'value' => ini_get( 'memory_limit' ), 544 ); 545 $info['wp-server']['fields']['max_input_time'] = array( 546 'label' => __( 'Max input time' ), 547 'value' => ini_get( 'max_input_time' ), 548 ); 549 $info['wp-server']['fields']['upload_max_size'] = array( 550 'label' => __( 'Upload max filesize' ), 551 'value' => ini_get( 'upload_max_filesize' ), 552 ); 553 $info['wp-server']['fields']['php_post_max_size'] = array( 554 'label' => __( 'PHP post max size' ), 555 'value' => ini_get( 'post_max_size' ), 556 ); 557 } 558 559 if ( function_exists( 'curl_version' ) ) { 560 $curl = curl_version(); 561 562 $info['wp-server']['fields']['curl_version'] = array( 563 'label' => __( 'cURL version' ), 564 'value' => sprintf( '%s %s', $curl['version'], $curl['ssl_version'] ), 565 ); 566 } else { 567 $info['wp-server']['fields']['curl_version'] = array( 568 'label' => __( 'cURL version' ), 569 'value' => __( 'Not available' ), 570 ); 571 } 572 573 $info['wp-server']['fields']['suhosin'] = array( 574 'label' => __( 'Is SUHOSIN installed?' ), 575 'value' => ( ( extension_loaded( 'suhosin' ) || ( defined( 'SUHOSIN_PATCH' ) && constant( 'SUHOSIN_PATCH' ) ) ) ? __( 'Yes' ) : __( 'No' ) ), 576 ); 577 578 $info['wp-server']['fields']['imagick_availability'] = array( 579 'label' => __( 'Is the Imagick library available?' ), 580 'value' => ( extension_loaded( 'imagick' ) ? __( 'Yes' ) : __( 'No' ) ), 581 ); 582 583 // Check if a .htaccess file exists. 584 if ( is_file( ABSPATH . '/.htaccess' ) ) { 585 // If the file exists, grab the content of it. 586 $htaccess_content = file_get_contents( ABSPATH . '/.htaccess' ); 587 588 // Filter away the core WordPress rules. 589 $filtered_htaccess_content = trim( preg_replace( '/\# BEGIN WordPress[\s\S]+?# END WordPress/si', '', $htaccess_content ) ); 590 591 $info['wp-server']['fields']['htaccess_extra_rules'] = array( 592 'label' => __( 'htaccess rules' ), 593 'value' => ( ! empty( $filtered_htaccess_content ) ? __( 'Custom rules have been added to your htaccess file.' ) : __( 'Your htaccess file contains only core WordPress features.' ) ), 594 ); 595 } 596 597 // Populate the database debug fields. 598 if ( is_resource( $wpdb->dbh ) ) { 599 // Old mysql extension. 600 $extension = 'mysql'; 601 } elseif ( is_object( $wpdb->dbh ) ) { 602 // mysqli or PDO. 603 $extension = get_class( $wpdb->dbh ); 604 } else { 605 // Unknown sql extension. 606 $extension = null; 607 } 608 609 /* 610 * Check what database engine is used, this will throw compatibility 611 * warnings from PHP compatibility testers, but `mysql_*` is 612 * still valid in PHP 5.6, so we need to account for that. 613 */ 614 if ( method_exists( $wpdb, 'db_version' ) ) { 615 if ( $wpdb->use_mysqli ) { 616 // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_server_info 617 $server = mysqli_get_server_info( $wpdb->dbh ); 618 } else { 619 // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_server_info 620 $server = mysql_get_server_info( $wpdb->dbh ); 621 } 622 } else { 623 $server = null; 624 } 625 626 if ( isset( $wpdb->use_mysqli ) && $wpdb->use_mysqli ) { 627 $client_version = $wpdb->dbh->client_info; 628 } else { 629 // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_client_info 630 if ( preg_match( '|[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}|', mysql_get_client_info(), $matches ) ) { 631 $client_version = $matches[0]; 632 } else { 633 $client_version = null; 634 } 635 } 636 637 $info['wp-database']['fields']['extension'] = array( 638 'label' => __( 'Extension' ), 639 'value' => $extension, 640 ); 641 $info['wp-database']['fields']['server_version'] = array( 642 'label' => __( 'Server version' ), 643 'value' => $server, 644 ); 645 $info['wp-database']['fields']['client_version'] = array( 646 'label' => __( 'Client version' ), 647 'value' => $client_version, 648 ); 649 $info['wp-database']['fields']['database_user'] = array( 650 'label' => __( 'Database user' ), 651 'value' => $wpdb->dbuser, 652 'private' => true, 653 ); 654 $info['wp-database']['fields']['database_host'] = array( 655 'label' => __( 'Database host' ), 656 'value' => $wpdb->dbhost, 657 'private' => true, 658 ); 659 $info['wp-database']['fields']['database_name'] = array( 660 'label' => __( 'Database name' ), 661 'value' => $wpdb->dbname, 662 'private' => true, 663 ); 664 $info['wp-database']['fields']['database_prefix'] = array( 665 'label' => __( 'Database prefix' ), 666 'value' => $wpdb->prefix, 667 'private' => true, 668 ); 669 670 // List must use plugins if there are any. 671 $mu_plugins = get_mu_plugins(); 672 673 foreach ( $mu_plugins as $plugin_path => $plugin ) { 674 $plugin_version = $plugin['Version']; 675 $plugin_author = $plugin['Author']; 676 677 $plugin_version_string = __( 'No version or author information is available.' ); 678 679 if ( ! empty( $plugin_version ) && ! empty( $plugin_author ) ) { 680 // translators: %1$s: Plugin version number. %2$s: Plugin author name. 681 $plugin_version_string = sprintf( __( 'Version %1$s by %2$s' ), $plugin_version, $plugin_author ); 682 } 683 if ( empty( $plugin_version ) && ! empty( $plugin_author ) ) { 684 // translators: %s: Plugin author name. 685 $plugin_version_string = sprintf( __( 'By %s' ), $plugin_author ); 686 } 687 if ( ! empty( $plugin_version ) && empty( $plugin_author ) ) { 688 // translators: %s: Plugin version number. 689 $plugin_version_string = sprintf( __( 'Version %s' ), $plugin_version ); 690 } 691 692 $info['wp-mu-plugins']['fields'][ sanitize_key( $plugin['Name'] ) ] = array( 693 'label' => $plugin['Name'], 694 'value' => $plugin_version_string, 695 ); 696 } 697 698 // List all available plugins. 699 $plugins = get_plugins(); 700 $plugin_updates = get_plugin_updates(); 701 702 foreach ( $plugins as $plugin_path => $plugin ) { 703 $plugin_part = ( is_plugin_active( $plugin_path ) ) ? 'wp-plugins-active' : 'wp-plugins-inactive'; 704 705 $plugin_version = $plugin['Version']; 706 $plugin_author = $plugin['Author']; 707 708 $plugin_version_string = __( 'No version or author information is available.' ); 709 710 if ( ! empty( $plugin_version ) && ! empty( $plugin_author ) ) { 711 // translators: %1$s: Plugin version number. %2$s: Plugin author name. 712 $plugin_version_string = sprintf( __( 'Version %1$s by %2$s' ), $plugin_version, $plugin_author ); 713 } 714 if ( empty( $plugin_version ) && ! empty( $plugin_author ) ) { 715 // translators: %s: Plugin author name. 716 $plugin_version_string = sprintf( __( 'By %s' ), $plugin_author ); 717 } 718 if ( ! empty( $plugin_version ) && empty( $plugin_author ) ) { 719 // translators: %s: Plugin version number. 720 $plugin_version_string = sprintf( __( 'Version %s' ), $plugin_version ); 721 } 722 723 if ( array_key_exists( $plugin_path, $plugin_updates ) ) { 724 // translators: %s: Latest plugin version number. 725 $plugin_update_needed = ' ' . sprintf( __( '(Latest version: %s)' ), $plugin_updates[ $plugin_path ]->update->new_version ); 726 } else { 727 $plugin_update_needed = ''; 728 } 729 730 $info[ $plugin_part ]['fields'][ sanitize_key( $plugin['Name'] ) ] = array( 731 'label' => $plugin['Name'], 732 'value' => $plugin_version_string . $plugin_update_needed, 733 ); 734 } 735 736 // Populate the section for the currently active theme. 737 global $_wp_theme_features; 738 $theme_features = array(); 739 if ( ! empty( $_wp_theme_features ) ) { 740 foreach ( $_wp_theme_features as $feature => $options ) { 741 $theme_features[] = $feature; 742 } 743 } 744 745 $active_theme = wp_get_theme(); 746 $theme_updates = get_theme_updates(); 747 748 if ( array_key_exists( $active_theme->stylesheet, $theme_updates ) ) { 749 // translators: %s: Latest theme version number. 750 $theme_update_needed_active = ' ' . sprintf( __( '(Latest version: %s)' ), $theme_updates[ $active_theme->stylesheet ]->update['new_version'] ); 751 } else { 752 $theme_update_needed_active = ''; 753 } 754 755 $info['wp-active-theme']['fields'] = array( 756 'name' => array( 757 'label' => __( 'Name' ), 758 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase 759 'value' => $active_theme->Name, 760 ), 761 'version' => array( 762 'label' => __( 'Version' ), 763 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase 764 'value' => $active_theme->Version . $theme_update_needed_active, 765 ), 766 'author' => array( 767 'label' => __( 'Author' ), 768 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase 769 'value' => wp_kses( $active_theme->Author, array() ), 770 ), 771 'author_website' => array( 772 'label' => __( 'Author website' ), 773 'value' => ( $active_theme->offsetGet( 'Author URI' ) ? $active_theme->offsetGet( 'Author URI' ) : __( 'Undefined' ) ), 774 ), 775 'parent_theme' => array( 776 'label' => __( 'Parent theme' ), 777 'value' => ( $active_theme->parent_theme ? $active_theme->parent_theme : __( 'None' ) ), 778 ), 779 'theme_features' => array( 780 'label' => __( 'Theme features' ), 781 'value' => implode( ', ', $theme_features ), 782 ), 783 ); 784 785 // Populate a list of all themes available in the install. 786 $all_themes = wp_get_themes(); 787 788 foreach ( $all_themes as $theme_slug => $theme ) { 789 // Ignore the currently active theme from the list of all themes. 790 if ( $active_theme->stylesheet == $theme_slug ) { 791 continue; 792 } 793 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase 794 $theme_version = $theme->Version; 795 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase 796 $theme_author = $theme->Author; 797 798 $theme_version_string = __( 'No version or author information is available.' ); 799 800 if ( ! empty( $theme_version ) && ! empty( $theme_author ) ) { 801 // translators: %1$s: Theme version number. %2$s: Theme author name. 802 $theme_version_string = sprintf( __( 'Version %1$s by %2$s' ), $theme_version, wp_kses( $theme_author, array() ) ); 803 } 804 if ( empty( $theme_version ) && ! empty( $theme_author ) ) { 805 // translators: %s: Theme author name. 806 $theme_version_string = sprintf( __( 'By %s' ), wp_kses( $theme_author, array() ) ); 807 } 808 if ( ! empty( $theme_version ) && empty( $theme_author ) ) { 809 // translators: %s: Theme version number. 810 $theme_version_string = sprintf( __( 'Version %s' ), $theme_version ); 811 } 812 813 if ( array_key_exists( $theme_slug, $theme_updates ) ) { 814 // translators: %s: Latest theme version number. 815 $theme_update_needed = ' ' . sprintf( __( '(Latest version: %s)' ), $theme_updates[ $theme_slug ]->update['new_version'] ); 816 } else { 817 $theme_update_needed = ''; 818 } 819 820 $info['wp-themes']['fields'][ sanitize_key( $theme->Name ) ] = array( 821 'label' => sprintf( 822 // translators: %1$s: Theme name. %2$s: Theme slug. 823 __( '%1$s (%2$s)' ), 824 // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase 825 $theme->Name, 826 $theme_slug 827 ), 828 'value' => $theme_version_string . $theme_update_needed, 829 ); 830 } 831 832 // Add more filesystem checks 833 if ( defined( 'WPMU_PLUGIN_DIR' ) && is_dir( WPMU_PLUGIN_DIR ) ) { 834 $info['wp-filesystem']['fields']['mu_plugin_directory'] = array( 835 'label' => __( 'The must use plugins directory' ), 836 'value' => ( wp_is_writable( WPMU_PLUGIN_DIR ) ? __( 'Writable' ) : __( 'Not writable' ) ), 837 ); 838 } 839 840 /** 841 * Add or modify new debug sections. 842 * 843 * Plugin or themes may wish to introduce their own debug information without creating additional admin pages for this 844 * kind of information as it is rarely needed, they can then utilize this filter to introduce their own sections. 845 * 846 * Array keys added by core are all prefixed with `wp-`, plugins and themes are encouraged to use their own slug as 847 * a prefix, both for consistency as well as avoiding key collisions. 848 * 849 * @since 5.2.0 850 * 851 * @param array $args { 852 * The debug information to be added to the core information page. 853 * 854 * @type string $label The title for this section of the debug output. 855 * @type string $description Optional. A description for your information section which may contain basic HTML 856 * markup: `em`, `strong` and `a` for linking to documentation or putting emphasis. 857 * @type boolean $show_count Optional. If set to `true` the amount of fields will be included in the title for 858 * this section. 859 * @type boolean $private Optional. If set to `true` the section and all associated fields will be excluded 860 * from the copy-paste text area. 861 * @type array $fields { 862 * An associative array containing the data to be displayed. 863 * 864 * @type string $label The label for this piece of information. 865 * @type string $value The output that is of interest for this field. 866 * @type boolean $private Optional. If set to `true` the field will not be included in the copy-paste text area 867 * on top of the page, allowing you to show, for example, API keys here. 868 * } 869 * } 870 */ 871 $info = apply_filters( 'debug_information', $info ); 872 873 if ( ! empty( $locale ) ) { 874 // Change the language used for translations 875 if ( function_exists( 'restore_previous_locale' ) && $switched_locale ) { 876 restore_previous_locale(); 877 } 878 } 879 880 return $info; 881 } 882 883 /** 884 * Print the formatted variation of the information gathered for debugging, in a manner 885 * suitable for a text area that can be instantly copied to a forum or support ticket. 886 * 887 * @since 5.2.0 888 * 889 * @param array $info_array Information gathered from the `WP_Debug_Data::debug_data` function. 890 */ 891 public static function textarea_format( $info_array ) { 892 echo "`\n"; 893 894 foreach ( $info_array as $section => $details ) { 895 // Skip this section if there are no fields, or the section has been declared as private. 896 if ( empty( $details['fields'] ) || ( isset( $details['private'] ) && $details['private'] ) ) { 897 continue; 898 } 899 900 printf( 901 "### %s%s ###\n\n", 902 $details['label'], 903 ( isset( $details['show_count'] ) && $details['show_count'] ? sprintf( ' (%d)', count( $details['fields'] ) ) : '' ) 904 ); 905 906 foreach ( $details['fields'] as $field ) { 907 if ( isset( $field['private'] ) && true === $field['private'] ) { 908 continue; 909 } 910 911 $values = $field['value']; 912 if ( is_array( $field['value'] ) ) { 913 $values = ''; 914 915 foreach ( $field['value'] as $name => $value ) { 916 $values .= sprintf( 917 "\n\t%s: %s", 918 $name, 919 $value 920 ); 921 } 922 } 923 924 printf( 925 "%s: %s\n", 926 $field['label'], 927 $values 928 ); 929 } 930 echo "\n"; 931 } 932 echo '`'; 933 } 934 935 /** 936 * Return the size of a directory, including all subdirectories. 937 * 938 * @since 5.2.0 939 * 940 * @param string $path The directory to check. 941 * @param string|int $max_execution_time How long a PHP script can run on this host. 942 * @param float $start_execution_time When we started executing this section of the script. 943 * 944 * @return int The directory size, in bytes. 945 */ 946 public static function get_directory_size( $path, $max_execution_time, $start_execution_time ) { 947 $size = 0; 948 949 foreach ( new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $path ) ) as $file ) { 950 // Check if the maximum execution time is a value considered "infinite". 951 if ( 0 !== $max_execution_time && -1 !== $max_execution_time ) { 952 $runtime = ( microtime( true ) - $start_execution_time ); 953 954 // If the script has been running as long, or longer, as it is allowed, return a failure message. 955 if ( $runtime >= $max_execution_time ) { 956 return -1; 957 } 958 } 959 $size += $file->getSize(); 960 } 961 962 return $size; 963 } 964 965 /** 966 * Fetch the total size of all the database tables for the active database user. 967 * 968 * @since 5.2.0 969 * 970 * @return int The size of the database, in bytes. 971 */ 972 public static function get_database_size() { 973 global $wpdb; 974 $size = 0; 975 $rows = $wpdb->get_results( 'SHOW TABLE STATUS', ARRAY_A ); 976 977 if ( $wpdb->num_rows > 0 ) { 978 foreach ( $rows as $row ) { 979 $size += $row['Data_length'] + $row['Index_length']; 980 } 981 } 982 983 return $size; 984 } 985 } -
new file src/wp-admin/includes/class-wp-site-health-auto-updates.php
diff --git a/src/wp-admin/includes/class-wp-site-health-auto-updates.php b/src/wp-admin/includes/class-wp-site-health-auto-updates.php new file mode 100644 index 0000000000..b445fd5f59
- + 1 <?php 2 /** 3 * Class for testing automatic updates in the WordPress code. 4 * 5 * @package WordPress 6 * @subpackage Site_Health 7 * @since 5.2.0 8 */ 9 10 class WP_Site_Health_Auto_Updates { 11 /** 12 * WP_Site_Health_Auto_Updates constructor. 13 * @since 5.2.0 14 */ 15 public function __construct() { 16 include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; 17 } 18 19 20 /** 21 * Run tests to determine if auto-updates can run. 22 * 23 * @since 5.2.0 24 * 25 * @return array The test results. 26 */ 27 public function run_tests() { 28 $tests = array( 29 $this->test_constants( 'DISALLOW_FILE_MODS', false ), 30 $this->test_constants( 'AUTOMATIC_UPDATER_DISABLED', false ), 31 $this->test_constants( 'WP_AUTO_UPDATE_CORE', true ), 32 $this->test_wp_version_check_attached(), 33 $this->test_filters_automatic_updater_disabled(), 34 $this->test_if_failed_update(), 35 $this->test_vcs_abspath(), 36 $this->test_check_wp_filesystem_method(), 37 $this->test_all_files_writable(), 38 $this->test_accepts_dev_updates(), 39 $this->test_accepts_minor_updates(), 40 ); 41 42 $tests = array_filter( $tests ); 43 $tests = array_map( 44 function( $test ) { 45 $test = (object) $test; 46 47 if ( empty( $test->severity ) ) { 48 $test->severity = 'warning'; 49 } 50 51 return $test; 52 }, 53 $tests 54 ); 55 56 return $tests; 57 } 58 59 /** 60 * Test if auto-updated related constants are set correctly. 61 * 62 * @since 5.2.0 63 * 64 * @param string $constant The name of the constant to check. 65 * @param bool $value The value that the constant should be, if set. 66 * @return array The test results. 67 */ 68 public function test_constants( $constant, $value ) { 69 if ( defined( $constant ) && constant( $constant ) != $allowed_constants[ $constant ] ) { 70 return array( 71 'description' => sprintf( 72 /* translators: %s: Name of the constant used. */ 73 __( 'The %s constant is defined and enabled.' ), 74 "<code>$constant</code>" 75 ), 76 'severity' => 'fail', 77 ); 78 } 79 } 80 81 /** 82 * Check if updates are intercepted by a filter. 83 * 84 * @since 5.2.0 85 * 86 * @return array The test results. 87 */ 88 public function test_wp_version_check_attached() { 89 $cookies = wp_unslash( $_COOKIE ); 90 $timeout = 10; 91 $headers = array( 92 'Cache-Control' => 'no-cache', 93 ); 94 95 // Include Basic auth in loopback requests. 96 if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) { 97 $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) ); 98 } 99 100 $url = add_query_arg( 101 array( 102 'health-check-test-wp_version_check' => true, 103 ), 104 admin_url() 105 ); 106 107 $test = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) ); 108 109 $response = wp_remote_retrieve_body( $test ); 110 111 if ( 'yes' !== $response ) { 112 return array( 113 'description' => sprintf( 114 /* translators: %s: Name of the filter used. */ 115 __( 'A plugin has prevented updates by disabling %s.' ), 116 '<code>wp_version_check()</code>' 117 ), 118 'severity' => 'fail', 119 ); 120 } 121 } 122 123 /** 124 * Check if automatic updates are disabled by a filter. 125 * 126 * @since 5.2.0 127 * 128 * @return array The test results. 129 */ 130 public function test_filters_automatic_updater_disabled() { 131 if ( apply_filters( 'automatic_updater_disabled', false ) ) { 132 return array( 133 'description' => sprintf( 134 /* translators: %s: Name of the filter used. */ 135 __( 'The %s filter is enabled.' ), 136 '<code>automatic_updater_disabled</code>' 137 ), 138 'severity' => 'fail', 139 ); 140 } 141 } 142 143 /** 144 * Check if automatic updates have tried to run, but failed, previously. 145 * 146 * @since 5.2.0 147 * 148 * @return array|bool The test results. false if the auto updates failed. 149 */ 150 function test_if_failed_update() { 151 $failed = get_site_option( 'auto_core_update_failed' ); 152 153 if ( ! $failed ) { 154 return false; 155 } 156 157 if ( ! empty( $failed['critical'] ) ) { 158 $description = __( 'A previous automatic background update ended with a critical failure, so updates are now disabled.' ); 159 $description .= ' ' . __( 'You would have received an email because of this.' ); 160 $description .= ' ' . __( "When you've been able to update using the \"Update Now\" button on Dashboard > Updates, we'll clear this error for future update attempts." ); 161 $description .= ' ' . sprintf( 162 /* translators: %s: Code of error shown. */ 163 __( 'The error code was %s.' ), 164 '<code>' . $failed['error_code'] . '</code>' 165 ); 166 return array( 167 'description' => $description, 168 'severity' => 'warning', 169 ); 170 } 171 172 $description = __( 'A previous automatic background update could not occur.' ); 173 if ( empty( $failed['retry'] ) ) { 174 $description .= ' ' . __( 'You would have received an email because of this.' ); 175 } 176 177 $description .= ' ' . __( "We'll try again with the next release." ); 178 $description .= ' ' . sprintf( 179 /* translators: %s: Code of error shown. */ 180 __( 'The error code was %s.' ), 181 '<code>' . $failed['error_code'] . '</code>' 182 ); 183 return array( 184 'description' => $description, 185 'severity' => 'warning', 186 ); 187 } 188 189 /** 190 * Check if WordPress is controlled by a VCS (Git, Subversion etc). 191 * 192 * @since 5.2.0 193 * 194 * @return array The test results. 195 */ 196 public function test_vcs_abspath() { 197 $context_dirs = array( ABSPATH ); 198 $vcs_dirs = array( '.svn', '.git', '.hg', '.bzr' ); 199 $check_dirs = array(); 200 201 foreach ( $context_dirs as $context_dir ) { 202 // Walk up from $context_dir to the root. 203 do { 204 $check_dirs[] = $context_dir; 205 206 // Once we've hit '/' or 'C:\', we need to stop. dirname will keep returning the input here. 207 if ( dirname( $context_dir ) == $context_dir ) { 208 break; 209 } 210 211 // Continue one level at a time. 212 } while ( $context_dir = dirname( $context_dir ) ); 213 } 214 215 $check_dirs = array_unique( $check_dirs ); 216 217 // Search all directories we've found for evidence of version control. 218 foreach ( $vcs_dirs as $vcs_dir ) { 219 foreach ( $check_dirs as $check_dir ) { 220 // phpcs:ignore 221 if ( $checkout = @is_dir( rtrim( $check_dir, '\\/' ) . "/$vcs_dir" ) ) { 222 break 2; 223 } 224 } 225 } 226 227 if ( $checkout && ! apply_filters( 'automatic_updates_is_vcs_checkout', true, ABSPATH ) ) { 228 return array( 229 'description' => sprintf( 230 // translators: %1$s: Folder name. %2$s: Version control directory. %3$s: Filter name. 231 __( 'The folder %1$s was detected as being under version control (%2$s), but the %3$s filter is allowing updates.' ), 232 '<code>' . $check_dir . '</code>', 233 "<code>$vcs_dir</code>", 234 '<code>automatic_updates_is_vcs_checkout</code>' 235 ), 236 'severity' => 'info', 237 ); 238 } 239 240 if ( $checkout ) { 241 return array( 242 'description' => sprintf( 243 // translators: %1$s: Folder name. %2$s: Version control directory. 244 __( 'The folder %1$s was detected as being under version control (%2$s).' ), 245 '<code>' . $check_dir . '</code>', 246 "<code>$vcs_dir</code>" 247 ), 248 'severity' => 'fail', 249 ); 250 } 251 252 return array( 253 'description' => __( 'No version control systems were detected.' ), 254 'severity' => 'pass', 255 ); 256 } 257 258 /** 259 * Check if we can access files without providing credentials. 260 * 261 * @since 5.2.0 262 * 263 * @return array The test results. 264 */ 265 function test_check_wp_filesystem_method() { 266 $skin = new Automatic_Upgrader_Skin; 267 $success = $skin->request_filesystem_credentials( false, ABSPATH ); 268 269 if ( ! $success ) { 270 $description = __( 'Your installation of WordPress prompts for FTP credentials to perform updates.' ); 271 $description .= ' ' . __( '(Your site is performing updates over FTP due to file ownership. Talk to your hosting company.)' ); 272 273 return array( 274 'description' => $description, 275 'severity' => 'fail', 276 ); 277 } 278 279 return array( 280 'description' => __( "Your installation of WordPress doesn't require FTP credentials to perform updates." ), 281 'severity' => 'pass', 282 ); 283 } 284 285 /** 286 * Check if core files are writable by the web user/group. 287 * 288 * @since 5.2.0 289 * 290 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass. 291 * 292 * @return array|bool The test results. false if they're not writeable. 293 */ 294 function test_all_files_writable() { 295 global $wp_filesystem; 296 297 include ABSPATH . WPINC . '/version.php'; // $wp_version; // x.y.z 298 299 $skin = new Automatic_Upgrader_Skin; 300 $success = $skin->request_filesystem_credentials( false, ABSPATH ); 301 302 if ( ! $success ) { 303 return false; 304 } 305 306 WP_Filesystem(); 307 308 if ( 'direct' != $wp_filesystem->method ) { 309 return false; 310 } 311 312 $checksums = get_core_checksums( $wp_version, 'en_US' ); 313 $dev = ( false !== strpos( $wp_version, '-' ) ); 314 // Get the last stable version's files and test against that 315 if ( ! $checksums && $dev ) { 316 $checksums = get_core_checksums( (float) $wp_version - 0.1, 'en_US' ); 317 } 318 319 // There aren't always checksums for development releases, so just skip the test if we still can't find any 320 if ( ! $checksums && $dev ) { 321 return false; 322 } 323 324 if ( ! $checksums ) { 325 $description = sprintf( 326 // translators: %s: WordPress version 327 __( "Couldn't retrieve a list of the checksums for WordPress %s." ), 328 $wp_version 329 ); 330 $description .= ' ' . __( 'This could mean that connections are failing to WordPress.org.' ); 331 return array( 332 'description' => $description, 333 'severity' => 'warning', 334 ); 335 } 336 337 $unwritable_files = array(); 338 foreach ( array_keys( $checksums ) as $file ) { 339 if ( 'wp-content' == substr( $file, 0, 10 ) ) { 340 continue; 341 } 342 if ( ! file_exists( ABSPATH . '/' . $file ) ) { 343 continue; 344 } 345 if ( ! is_writable( ABSPATH . '/' . $file ) ) { 346 $unwritable_files[] = $file; 347 } 348 } 349 350 if ( $unwritable_files ) { 351 if ( count( $unwritable_files ) > 20 ) { 352 $unwritable_files = array_slice( $unwritable_files, 0, 20 ); 353 $unwritable_files[] = '...'; 354 } 355 return array( 356 'description' => __( 'Some files are not writable by WordPress:' ) . ' <ul><li>' . implode( '</li><li>', $unwritable_files ) . '</li></ul>', 357 'severity' => 'fail', 358 ); 359 } else { 360 return array( 361 'description' => __( 'All of your WordPress files are writable.' ), 362 'severity' => 'pass', 363 ); 364 } 365 } 366 367 /** 368 * Check if the install is using a development branch and can use nightly packages. 369 * 370 * @since 5.2.0 371 * 372 * @return array|bool The test results. false if it isn't a development version. 373 */ 374 function test_accepts_dev_updates() { 375 include ABSPATH . WPINC . '/version.php'; // $wp_version; // x.y.z 376 // Only for dev versions 377 if ( false === strpos( $wp_version, '-' ) ) { 378 return false; 379 } 380 381 if ( defined( 'WP_AUTO_UPDATE_CORE' ) && ( 'minor' === WP_AUTO_UPDATE_CORE || false === WP_AUTO_UPDATE_CORE ) ) { 382 return array( 383 'description' => sprintf( 384 /* translators: %s: Name of the constant used. */ 385 __( 'WordPress development updates are blocked by the %s constant.' ), 386 '<code>WP_AUTO_UPDATE_CORE</code>' 387 ), 388 'severity' => 'fail', 389 ); 390 } 391 392 if ( ! apply_filters( 'allow_dev_auto_core_updates', $wp_version ) ) { 393 return array( 394 'description' => sprintf( 395 /* translators: %s: Name of the filter used. */ 396 __( 'WordPress development updates are blocked by the %s filter.' ), 397 '<code>allow_dev_auto_core_updates</code>' 398 ), 399 'severity' => 'fail', 400 ); 401 } 402 } 403 404 /** 405 * Check if the site supports automatic minor updates. 406 * 407 * @since 5.2.0 408 * 409 * @return array The test results. 410 */ 411 function test_accepts_minor_updates() { 412 if ( defined( 'WP_AUTO_UPDATE_CORE' ) && false === WP_AUTO_UPDATE_CORE ) { 413 return array( 414 'description' => sprintf( 415 /* translators: %s: Name of the constant used. */ 416 __( 'WordPress security and maintenance releases are blocked by %s.' ), 417 "<code>define( 'WP_AUTO_UPDATE_CORE', false );</code>" 418 ), 419 'severity' => 'fail', 420 ); 421 } 422 423 if ( ! apply_filters( 'allow_minor_auto_core_updates', true ) ) { 424 return array( 425 'description' => sprintf( 426 /* translators: %s: Name of the filter used. */ 427 __( 'WordPress security and maintenance releases are blocked by the %s filter.' ), 428 '<code>allow_minor_auto_core_updates</code>' 429 ), 430 'severity' => 'fail', 431 ); 432 } 433 } 434 } -
new file src/wp-admin/includes/class-wp-site-health.php
diff --git a/src/wp-admin/includes/class-wp-site-health.php b/src/wp-admin/includes/class-wp-site-health.php new file mode 100644 index 0000000000..3921e8f734
- + 1 <?php 2 /** 3 * Class for looking up a sites health based on a users WordPress environment. 4 * 5 * @package WordPress 6 * @subpackage Site_Health 7 * @since 5.2.0 8 */ 9 10 class WP_Site_Health { 11 private $mysql_min_version_check; 12 private $mysql_rec_version_check; 13 14 public $is_mariadb = false; 15 private $mysql_server_version = ''; 16 private $health_check_mysql_required_version = '5.5'; 17 private $health_check_mysql_rec_version = ''; 18 19 public $schedules; 20 public $crons; 21 public $last_missed_cron = null; 22 23 /** 24 * WP_Site_Health constructor. 25 * 26 * @since 5.2.0 27 */ 28 public function __construct() { 29 $this->prepare_sql_data(); 30 31 add_action( 'wp_loaded', array( $this, 'check_wp_version_check_exists' ) ); 32 33 add_filter( 'admin_body_class', array( $this, 'admin_body_class' ) ); 34 35 add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); 36 } 37 38 /** 39 * Enqueues the site health scripts. 40 * 41 * @since 5.2.0 42 */ 43 public function enqueue_scripts() { 44 $screen = get_current_screen(); 45 if ( 'site-health' !== $screen->id ) { 46 return; 47 } 48 49 $health_check_js_variables = array( 50 'screen' => $screen->id, 51 'string' => array( 52 'please_wait' => __( 'Please wait...' ), 53 'copied' => __( 'Copied' ), 54 'running_tests' => __( 'Currently being tested...' ), 55 'site_health_complete' => __( 'All site health tests have finished running.' ), 56 'site_info_show_copy' => __( 'Show options for copying this information' ), 57 'site_info_hide_copy' => __( 'Hide options for copying this information' ), 58 // translators: %s: The percentage score for the tests. 59 'site_health_complete_screen_reader' => __( 'All site health tests have finished running. Your site scored %s, and the results are now available on the page.' ), 60 'site_info_copied' => __( 'Site information has been added to your clipboard.' ), 61 ), 62 'nonce' => array( 63 'site_status' => wp_create_nonce( 'health-check-site-status' ), 64 'site_status_result' => wp_create_nonce( 'health-check-site-status-result' ), 65 ), 66 'site_status' => array( 67 'direct' => array(), 68 'async' => array(), 69 'issues' => array( 70 'good' => 0, 71 'recommended' => 0, 72 'critical' => 0, 73 ), 74 ), 75 ); 76 77 $issue_counts = get_transient( 'health-check-site-status-result' ); 78 79 if ( false !== $issue_counts ) { 80 $issue_counts = json_decode( $issue_counts ); 81 82 $health_check_js_variables['site_status']['issues'] = $issue_counts; 83 } 84 85 if ( 'site-health' === $screen->id && ! isset( $_GET['tab'] ) ) { 86 $tests = WP_Site_Health::get_tests(); 87 // Don't run https test on localhost 88 if ( 'localhost' === preg_replace( '|https?://|', '', get_site_url() ) ) { 89 unset( $tests['direct']['https_status'] ); 90 } 91 foreach ( $tests['direct'] as $test ) { 92 $test_function = sprintf( 93 'get_test_%s', 94 $test['test'] 95 ); 96 97 if ( method_exists( $this, $test_function ) && is_callable( array( $this, $test_function ) ) ) { 98 $health_check_js_variables['site_status']['direct'][] = call_user_func( array( $this, $test_function ) ); 99 } else { 100 $health_check_js_variables['site_status']['direct'][] = call_user_func( $test['test'] ); 101 } 102 } 103 104 foreach ( $tests['async'] as $test ) { 105 $health_check_js_variables['site_status']['async'][] = array( 106 'test' => $test['test'], 107 'completed' => false, 108 ); 109 } 110 } 111 112 wp_localize_script( 'site-health', 'SiteHealth', $health_check_js_variables ); 113 } 114 115 /** 116 * Run the SQL version checks. 117 * 118 * These values are used in later tests, but the part of preparing them is more easily managed early 119 * in the class for ease of access and discovery. 120 * 121 * @since 5.2.0 122 * 123 * @global wpdb $wpdb WordPress database abstraction object. 124 */ 125 private function prepare_sql_data() { 126 global $wpdb; 127 128 if ( method_exists( $wpdb, 'db_version' ) ) { 129 if ( $wpdb->use_mysqli ) { 130 // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_server_info 131 $mysql_server_type = mysqli_get_server_info( $wpdb->dbh ); 132 } else { 133 // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_server_info 134 $mysql_server_type = mysql_get_server_info( $wpdb->dbh ); 135 } 136 137 $this->mysql_server_version = $wpdb->get_var( 'SELECT VERSION()' ); 138 } 139 140 $this->health_check_mysql_rec_version = '5.6'; 141 142 if ( stristr( $mysql_server_type, 'mariadb' ) ) { 143 $this->is_mariadb = true; 144 $this->health_check_mysql_rec_version = '10.0'; 145 } 146 147 $this->mysql_min_version_check = version_compare( '5.5', $this->mysql_server_version, '<=' ); 148 $this->mysql_rec_version_check = version_compare( $this->health_check_mysql_rec_version, $this->mysql_server_version, '<=' ); 149 } 150 151 /** 152 * Test if `wp_version_check` is blocked. 153 * 154 * It's possible to block updates with the `wp_version_check` filter, but this can't be checked during an 155 * AJAX call, as the filter is never introduced then. 156 * 157 * This filter overrides a normal page request if it's made by an admin through the AJAX call with the 158 * right query argument to check for this. 159 * 160 * @since 5.2.0 161 */ 162 public function check_wp_version_check_exists() { 163 if ( ! is_admin() || ! is_user_logged_in() || ! current_user_can( 'update_core' ) || ! isset( $_GET['health-check-test-wp_version_check'] ) ) { 164 return; 165 } 166 167 echo ( has_filter( 'wp_version_check', 'wp_version_check' ) ? 'yes' : 'no' ); 168 169 die(); 170 } 171 172 /** 173 * Tests for WordPress version and outputs it. 174 * 175 * Gives various results depending on what kind of updates are available, if any, to encourage the 176 * user to install security updates as a priority. 177 * 178 * @since 5.2.0 179 * 180 * @return array The test result. 181 */ 182 public function get_test_wordpress_version() { 183 $result = array( 184 'label' => '', 185 'status' => '', 186 'badge' => array( 187 'label' => 'Security', 188 'color' => 'red', 189 ), 190 'description' => '', 191 'actions' => '', 192 'test' => 'wordpress_version', 193 ); 194 195 $core_current_version = get_bloginfo( 'version' ); 196 $core_updates = get_core_updates(); 197 198 if ( ! is_array( $core_updates ) ) { 199 $result['status'] = 'recommended'; 200 201 $result['label'] = sprintf( 202 // translators: %s: Your current version of WordPress. 203 __( 'WordPress version %s' ), 204 $core_current_version 205 ); 206 207 $result['description'] = sprintf( 208 '<p>%s</p>', 209 __( 'We were unable to check if any new versions of WordPress are available.' ) 210 ); 211 212 $result['actions'] = sprintf( 213 '<a href="%s">%s</a>', 214 esc_url( admin_url( 'update-core.php?force-check=1' ) ), 215 __( 'Check for updates manually' ) 216 ); 217 } else { 218 foreach ( $core_updates as $core => $update ) { 219 if ( 'upgrade' === $update->response ) { 220 $current_version = explode( '.', $core_current_version ); 221 $new_version = explode( '.', $update->version ); 222 223 $current_major = $current_version[0] . '.' . $current_version[1]; 224 $new_major = $new_version[0] . '.' . $new_version[1]; 225 226 $result['label'] = sprintf( 227 // translators: %s: The latest version of WordPress available. 228 __( 'WordPress update available (%s)' ), 229 $update->version 230 ); 231 232 $result['actions'] = sprintf( 233 '<a href="%s">%s</a>', 234 esc_url( admin_url( 'update-core.php' ) ), 235 __( 'Install the latest version of WordPress' ) 236 ); 237 238 if ( $current_major !== $new_major ) { 239 // This is a major version mismatch. 240 $result['status'] = 'recommended'; 241 $result['description'] = sprintf( 242 '<p>%s</p>', 243 __( 'A new version of WordPress is available.' ) 244 ); 245 } else { 246 // This is a minor version, sometimes considered more critical. 247 $result['status'] = 'critical'; 248 $result['description'] = sprintf( 249 '<p>%s</p>', 250 __( 'A new minor update is available for your site. Because minor updates often address security, it’s important to install them.' ) 251 ); 252 } 253 } else { 254 $result['status'] = 'good'; 255 $result['label'] = sprintf( 256 // translators: %s: The current version of WordPress installed on this site. 257 __( 'Your WordPress version is up to date (%s)' ), 258 $core_current_version 259 ); 260 261 $result['description'] = sprintf( 262 '<p>%s</p>', 263 __( 'You are currently running the latest version of WordPress available, keep it up!' ) 264 ); 265 } 266 } 267 } 268 269 return $result; 270 } 271 272 /** 273 * Test if plugins are outdated, or unnecessary. 274 * 275 * The tests checks if your plugins are up to date, and encourages you to remove any that are not in use. 276 * 277 * @since 5.2.0 278 * 279 * @return array The test result. 280 */ 281 public function get_test_plugin_version() { 282 $result = array( 283 'label' => __( 'Your plugins are up to date' ), 284 'status' => 'good', 285 'badge' => array( 286 'label' => 'Security', 287 'color' => 'red', 288 ), 289 'description' => sprintf( 290 '<p>%s</p>', 291 __( 'Plugins extend your site’s functionality with things like contact forms, ecommerce and much more. That means they have deep access to your site, so it’s vital to keep them up to date.' ) 292 ), 293 'actions' => '', 294 'test' => 'plugin_version', 295 ); 296 297 $plugins = get_plugins(); 298 $plugin_updates = get_plugin_updates(); 299 300 $plugins_have_updates = false; 301 $plugins_active = 0; 302 $plugins_total = 0; 303 $plugins_needs_update = 0; 304 305 // Loop over the available plugins and check their versions and active state. 306 foreach ( $plugins as $plugin_path => $plugin ) { 307 $plugins_total++; 308 309 if ( is_plugin_active( $plugin_path ) ) { 310 $plugins_active++; 311 } 312 313 $plugin_version = $plugin['Version']; 314 315 if ( array_key_exists( $plugin_path, $plugin_updates ) ) { 316 $plugins_needs_update++; 317 $plugins_have_updates = true; 318 } 319 } 320 321 // Add a notice if there are outdated plugins. 322 if ( $plugins_needs_update > 0 ) { 323 $result['status'] = 'critical'; 324 325 $result['label'] = __( 'You have plugins waiting to be updated' ); 326 327 $result['description'] .= sprintf( 328 '<p>%s</p>', 329 sprintf( 330 esc_html( 331 /* translators: %d: The number of outdated plugins. */ 332 _n( 333 'Your site has %d plugin waiting to be updated.', 334 'Your site has %d plugins waiting for updates.', 335 $plugins_needs_update 336 ) 337 ), 338 $plugins_needs_update 339 ) 340 ); 341 } else { 342 $result['description'] .= sprintf( 343 '<p>%s</p>', 344 sprintf( 345 esc_html( 346 /* translators: %d: The number of active plugins. */ 347 _n( 348 'Your site has %d active plugin, and it is up to date.', 349 'Your site has %d active plugins, and they are all up to date.', 350 $plugins_active 351 ) 352 ), 353 $plugins_active 354 ) 355 ); 356 } 357 358 // Check if there are inactive plugins. 359 if ( $plugins_total > $plugins_active ) { 360 $unused_plugins = $plugins_total - $plugins_active; 361 362 $result['status'] = 'recommended'; 363 364 $result['label'] = __( 'Inactive plugins should be removed' ); 365 366 $result['description'] .= sprintf( 367 '<p>%s</p>', 368 sprintf( 369 esc_html( 370 /* translators: %d: The number of inactive plugins. */ 371 _n( 372 'Your site has %d inactive plugin. Inactive plugins are tempting targets for attackers. if you’re not going to use a plugin, we recommend you remove it.', 373 'Your site has %d inactive plugins. Inactive plugins are tempting targets for attackers. if you’re not going to use a plugin, we recommend you remove it.', 374 $unused_plugins 375 ) 376 ), 377 $unused_plugins 378 ) 379 ); 380 } 381 382 return $result; 383 } 384 385 /** 386 * Test if themes are outdated, or unnecessary. 387 * 388 * The tests checks if your site has a default theme (to fall back on if there is a need), if your themes 389 * are up to date and, finally, encourages you to remove any themes that are not needed. 390 * 391 * @since 5.2.0 392 * 393 * @return array The test results. 394 */ 395 public function get_test_theme_version() { 396 $result = array( 397 'label' => __( 'Your themes are up to date' ), 398 'status' => 'good', 399 'badge' => array( 400 'label' => 'Security', 401 'color' => 'red', 402 ), 403 'description' => sprintf( 404 '<p>%s</p>', 405 __( 'Themes add your site’s look and feel. It’s important to keep them up to date, to stay consistent with your brand and keep your site secure.' ) 406 ), 407 'actions' => '', 408 'test' => 'theme_version', 409 ); 410 411 $theme_updates = get_theme_updates(); 412 413 $themes_total = 0; 414 $themes_need_updates = 0; 415 $themes_inactive = 0; 416 417 // This value is changed during processing to determine how many themes are considered a reasonable amount. 418 $allowed_theme_count = 1; 419 420 $has_default_theme = false; 421 $has_unused_themes = false; 422 $show_unused_themes = true; 423 $using_default_theme = false; 424 425 // Populate a list of all themes available in the install. 426 $all_themes = wp_get_themes(); 427 $active_theme = wp_get_theme(); 428 429 foreach ( $all_themes as $theme_slug => $theme ) { 430 $themes_total++; 431 432 if ( WP_DEFAULT_THEME === $theme_slug ) { 433 $has_default_theme = true; 434 435 if ( get_stylesheet() === $theme_slug ) { 436 $using_default_theme = true; 437 } 438 } 439 440 if ( array_key_exists( $theme_slug, $theme_updates ) ) { 441 $themes_need_updates++; 442 } 443 } 444 445 // If this is a child theme, increase the allowed theme count by one, to account for the parent. 446 if ( $active_theme->parent() ) { 447 $allowed_theme_count++; 448 } 449 450 // If there's a default theme installed, we count that as allowed as well. 451 if ( $has_default_theme ) { 452 $allowed_theme_count++; 453 } 454 455 if ( $themes_total > $allowed_theme_count ) { 456 $has_unused_themes = true; 457 $themes_inactive = ( $themes_total - $allowed_theme_count ); 458 } 459 460 // Check if any themes need to be updated. 461 if ( $themes_need_updates > 0 ) { 462 $result['status'] = 'critical'; 463 464 $result['label'] = __( 'You have themes waiting to be updated' ); 465 466 $result['description'] .= sprintf( 467 '<p>%s</p>', 468 sprintf( 469 esc_html( 470 /* translators: %d: The number of outdated themes. */ 471 _n( 472 'Your site has %d theme waiting to be updated.', 473 'Your site has %d themes waiting to be updated.', 474 $themes_need_updates 475 ) 476 ), 477 $themes_need_updates 478 ) 479 ); 480 } else { 481 // Give positive feedback about the site being good about keeping things up to date. 482 $result['description'] .= sprintf( 483 '<p>%s</p>', 484 sprintf( 485 esc_html( 486 /* translators: %d: The number of themes. */ 487 _n( 488 'Your site has %d installed theme, and it is up to date.', 489 'Your site has %d installed themes, and they are all up to date.', 490 $themes_total 491 ) 492 ), 493 $themes_total 494 ) 495 ); 496 } 497 498 if ( $has_unused_themes && $show_unused_themes ) { 499 500 // This is a child theme, so we want to be a bit more explicit in our messages. 501 if ( $active_theme->parent() ) { 502 // Recommend removing inactive themes, except a default theme, your current one, and the parent theme. 503 $result['status'] = 'recommended'; 504 505 $result['label'] = __( 'You should remove inactive themes' ); 506 507 if ( $using_default_theme ) { 508 $result['description'] .= sprintf( 509 '<p>%s</p>', 510 sprintf( 511 esc_html( 512 /* translators: %d: The number of inactive themes. */ 513 _n( 514 'Your site has %1$d inactive theme. To enhance your site’s security, we recommend you remove any themes you’re not using. You should keep your current theme, %2$s, and %3$s, its parent theme.', 515 'Your site has %1$d inactive themes. To enhance your site’s security, we recommend you remove any themes you’re not using. You should keep your current theme, %2$s, and %3$s, its parent theme.', 516 $themes_inactive 517 ) 518 ), 519 $themes_inactive, 520 $active_theme->name, 521 $active_theme->parent()->name 522 ) 523 ); 524 } else { 525 $result['description'] .= sprintf( 526 '<p>%s</p>', 527 sprintf( 528 esc_html( 529 /* translators: %1$d: The number of inactive themes. %2$s: The default theme for WordPress. %3$s: The currently active theme. %4$s: The active themes parent theme. */ 530 _n( 531 'Your site has %1$d inactive theme. To enhance your site’s security, we recommend you remove any themes you’re not using. You should keep %2$s, the default WordPress theme, %3$s, your current theme and %4$s, its parent theme.', 532 'Your site has %1$d inactive themes. To enhance your site’s security, we recommend you remove any themes you’re not using. You should keep %2$s, the default WordPress theme, %3$s, your current theme and %4$s, its parent theme.', 533 $themes_inactive 534 ) 535 ), 536 $themes_inactive, 537 WP_DEFAULT_THEME, 538 $active_theme->name, 539 $active_theme->parent()->name 540 ) 541 ); 542 } 543 } else { 544 // Recommend removing all inactive themes. 545 $result['status'] = 'recommended'; 546 547 $result['label'] = __( 'You should remove inactive themes' ); 548 549 if ( $using_default_theme ) { 550 $result['description'] .= sprintf( 551 '<p>%s</p>', 552 sprintf( 553 esc_html( 554 /* translators: %1$d: The amount of inactive themes. %2$s: The currently active theme. */ 555 _n( 556 'Your site has %1$d inactive theme, other than %2$s, your active theme. We recommend removing any unused themes to enhance your sites security.', 557 'Your site has %1$d inactive themes, other than %2$s, your active theme. We recommend removing any unused themes to enhance your sites security.', 558 $themes_inactive 559 ) 560 ), 561 $themes_inactive, 562 $active_theme->name 563 ) 564 ); 565 } else { 566 $result['description'] .= sprintf( 567 '<p>%s</p>', 568 sprintf( 569 esc_html( 570 /* translators: %1$d: The amount of inactive themes. %2$s: The default theme for WordPress. %3$s: The currently active theme. */ 571 _n( 572 'Your site has %1$d inactive theme, other than %2$s, the default WordPress theme, and %3$s, your active theme. We recommend removing any unused themes to enhance your sites security.', 573 'Your site has %1$d inactive themes, other than %2$s, the default WordPress theme, and %3$s, your active theme. We recommend removing any unused themes to enhance your sites security.', 574 $themes_inactive 575 ) 576 ), 577 $themes_inactive, 578 WP_DEFAULT_THEME, 579 $active_theme->name 580 ) 581 ); 582 } 583 } 584 } 585 586 // If not default Twenty* theme exists. 587 if ( ! $has_default_theme ) { 588 $result['status'] = 'recommended'; 589 590 $result['label'] = __( 'Have a default theme available' ); 591 592 $result['description'] .= sprintf( 593 '<p>%s</p>', 594 __( 'Your site does not have any default theme. Default themes are used by WordPress automatically if anything is wrong with your normal theme.' ) 595 ); 596 } 597 598 return $result; 599 } 600 601 /** 602 * Test if the supplied PHP version is supported. 603 * 604 * @since 5.2.0 605 * 606 * @return array The test results. 607 */ 608 public function get_test_php_version() { 609 $response = wp_check_php_version(); 610 611 $result = array( 612 'label' => sprintf( 613 // translators: %s: The current PHP version. 614 __( 'PHP is up to date (%s)' ), 615 PHP_VERSION 616 ), 617 'status' => 'good', 618 'badge' => array( 619 'label' => 'Security', 620 'color' => 'red', 621 ), 622 'description' => sprintf( 623 '<p>%s</p>', 624 __( 'PHP is the programming language we use to build and maintain WordPress. Newer versions of PHP are both faster and more secure, so updating will have a positive effect on your site’s performance.' ) 625 ), 626 'actions' => sprintf( 627 '<a class="button button-primary" href="%1$s" target="_blank" rel="noopener noreferrer">%2$s <span class="screen-reader-text">%3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a>', 628 esc_url( wp_get_update_php_url() ), 629 __( 'Learn more about updating PHP' ), 630 /* translators: accessibility text */ 631 __( '(opens in a new tab)' ) 632 ), 633 'test' => 'php_version', 634 ); 635 636 // PHP is up to date. 637 if ( ! $response || version_compare( PHP_VERSION, $response['recommended_version'], '>=' ) ) { 638 return $result; 639 } 640 641 // The PHP version is older than the recommended version, but still acceptable. 642 if ( $response['is_supported'] ) { 643 $result['label'] = __( 'We recommend that you update PHP' ); 644 $result['status'] = 'recommended'; 645 646 return $result; 647 } 648 649 // The PHP version is only recieving security fixes. 650 if ( $response['is_secure'] ) { 651 $result['label'] = __( 'Your PHP version should be updated' ); 652 $result['status'] = 'recommended'; 653 654 return $result; 655 } 656 657 // Anything no longer secure must be updated. 658 $result['label'] = __( 'Your PHP version requires an update' ); 659 $result['status'] = 'critical'; 660 661 return $result; 662 } 663 664 /** 665 * Check if the passed extension or function are available. 666 * 667 * Make the check for available PHP modules into a simple boolean operator for a cleaner test runner. 668 * 669 * @since 5.2.0 670 * 671 * @param string $extension Optional. The extension name to test. Default null. 672 * @param string $function Optional. The function name to test. Default null. 673 * 674 * @return bool Whether or not the extension and function are available. 675 */ 676 private function test_php_extension_availability( $extension = null, $function = null ) { 677 // If no extension or function is passed, claim to fail testing, as we have nothing to test against. 678 if ( ! $extension && ! $function ) { 679 return false; 680 } 681 682 if ( $extension && ! extension_loaded( $extension ) ) { 683 return false; 684 } 685 if ( $function && ! function_exists( $function ) ) { 686 return false; 687 } 688 689 return true; 690 } 691 692 /** 693 * Test if required PHP modules are installed on the host. 694 * 695 * This test builds on the recommendations made by the WordPress Hosting Team 696 * as seen at https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions 697 * 698 * @return array 699 */ 700 public function get_test_php_extensions() { 701 $result = array( 702 'label' => __( 'Required and recommended modules are installed' ), 703 'status' => 'good', 704 'badge' => array( 705 'label' => 'Performance', 706 'color' => 'orange', 707 ), 708 'description' => sprintf( 709 '<p>%s</p><p>%s</p>', 710 __( 'PHP modules perform most of the tasks on the server that make your site run.' ), 711 sprintf( 712 /* translators: %s: Link to the hosting group page about recommended PHP modules. */ 713 __( 'The Hosting team maintains a list of those modules, both recommended and required, in %s.' ), 714 sprintf( 715 '<a href="%s">%s</a>', 716 esc_url( _x( 'https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions', 'The address to describe PHP modules and their use.' ) ), 717 __( 'the team handbook' ) 718 ) 719 ) 720 ), 721 'actions' => '', 722 'test' => 'php_extensions', 723 ); 724 725 $modules = array( 726 'bcmath' => array( 727 'function' => 'bcadd', 728 'required' => false, 729 ), 730 'curl' => array( 731 'function' => 'curl_version', 732 'required' => false, 733 ), 734 'exif' => array( 735 'function' => 'exif_read_data', 736 'required' => false, 737 ), 738 'filter' => array( 739 'function' => 'filter_list', 740 'required' => false, 741 ), 742 'fileinfo' => array( 743 'function' => 'finfo_file', 744 'required' => false, 745 ), 746 'mod_xml' => array( 747 'extension' => 'libxml', 748 'required' => false, 749 ), 750 'mysqli' => array( 751 'function' => 'mysqli_connect', 752 'required' => false, 753 ), 754 'libsodium' => array( 755 'function' => 'sodium_compare', 756 'required' => false, 757 'php_bundled_version' => '7.2.0', 758 ), 759 'openssl' => array( 760 'function' => 'openssl_encrypt', 761 'required' => false, 762 ), 763 'pcre' => array( 764 'function' => 'preg_match', 765 'required' => false, 766 ), 767 'imagick' => array( 768 'extension' => 'imagick', 769 'required' => false, 770 ), 771 'gd' => array( 772 'extension' => 'gd', 773 'required' => false, 774 'fallback_for' => 'imagick', 775 ), 776 'mcrypt' => array( 777 'extension' => 'mcrypt', 778 'required' => false, 779 'fallback_for' => 'libsodium', 780 ), 781 'xmlreader' => array( 782 'extension' => 'xmlreader', 783 'required' => false, 784 'fallback_for' => 'xml', 785 ), 786 'zlib' => array( 787 'extension' => 'zlib', 788 'required' => false, 789 'fallback_for' => 'zip', 790 ), 791 ); 792 793 /** 794 * An array representing all the modules we wish to test for. 795 * 796 * @since 5.2.0 797 * 798 * @param array $modules { 799 * An associated array of modules to test for. 800 * 801 * array $module { 802 * An associated array of module properties used during testing. 803 * One of either `$function` or `$extension` must be provided, or they will fail by default. 804 * 805 * string $function Optional. A function name to test for the existence of. 806 * string $extension Optional. An extension to check if is loaded in PHP. 807 * bool $required Is this a required feature or not. 808 * string $fallback_for Optional. The module this module replaces as a fallback. 809 * } 810 * } 811 */ 812 $modules = apply_filters( 'site_status_test_php_modules', $modules ); 813 814 $failures = array(); 815 816 foreach ( $modules as $library => $module ) { 817 $extension = ( isset( $module['extension'] ) ? $module['extension'] : null ); 818 $function = ( isset( $module['function'] ) ? $module['function'] : null ); 819 820 // If this module is a fallback for another function, check if that other function passed. 821 if ( isset( $module['fallback_for'] ) ) { 822 /* 823 * If that other function has a failure, mark this module as required for normal operations. 824 * If that other function hasn't failed, skip this test as it's only a fallback. 825 */ 826 if ( isset( $failures[ $module['fallback_for'] ] ) ) { 827 $module['required'] = true; 828 } else { 829 continue; 830 } 831 } 832 833 if ( ! $this->test_php_extension_availability( $extension, $function ) && ( ! isset( $module['php_bundled_version'] ) || version_compare( PHP_VERSION, $module['php_bundled_version'], '<' ) ) ) { 834 if ( $module['required'] ) { 835 $result['status'] = 'critical'; 836 837 $class = 'error'; 838 $screen_reader = __( 'Error' ); 839 $message = sprintf( 840 /* translators: %s: The module name. */ 841 __( 'The required module, %s, is not installed, or has been disabled.' ), 842 $library 843 ); 844 } else { 845 $class = 'warning'; 846 $screen_reader = __( 'Warning' ); 847 $message = sprintf( 848 /* translators: %s: The module name. */ 849 __( 'The optional module, %s, is not installed, or has been disabled.' ), 850 $library 851 ); 852 } 853 854 if ( ! $module['required'] && 'good' === $result['status'] ) { 855 $result['status'] = 'recommended'; 856 } 857 858 $failures[ $library ] = "<span class='$class'><span class='screen-reader-text'>$screen_reader</span></span> $message"; 859 } 860 } 861 862 if ( ! empty( $failures ) ) { 863 $output = '<ul>'; 864 865 foreach ( $failures as $failure ) { 866 $output .= sprintf( 867 '<li>%s</li>', 868 $failure 869 ); 870 } 871 872 $output .= '</ul>'; 873 } 874 875 if ( 'good' !== $result['status'] ) { 876 if ( 'recommended' === $result['status'] ) { 877 $result['label'] = __( 'One or more recommended modules are missing' ); 878 } 879 if ( 'critical' === $result['status'] ) { 880 $result['label'] = __( 'One or more required modules are missing' ); 881 } 882 883 $result['description'] .= sprintf( 884 '<p>%s</p>', 885 $output 886 ); 887 } 888 889 return $result; 890 } 891 892 /** 893 * Test if the SQL server is up to date. 894 * 895 * @since 5.2.0 896 * 897 * @return array The test results. 898 */ 899 public function get_test_sql_server() { 900 $result = array( 901 'label' => __( 'SQL server is up to date' ), 902 'status' => 'good', 903 'badge' => array( 904 'label' => 'Security', 905 'color' => 'red', 906 ), 907 'description' => sprintf( 908 '<p>%s</p>', 909 __( 'The SQL server is the database where WordPress stores all your site’s content and settings' ) 910 ), 911 'actions' => '', 912 'test' => 'sql_server', 913 ); 914 915 $db_dropin = file_exists( WP_CONTENT_DIR . '/db.php' ); 916 917 if ( ! $this->mysql_rec_version_check ) { 918 $result['status'] = 'recommended'; 919 920 $result['label'] = __( 'Outdated SQL server' ); 921 922 $result['description'] .= sprintf( 923 '<p>%s</p>', 924 sprintf( 925 /* translators: %1$s: The database engine in use (MySQL or MariaDB). %2$s: Database server recommended version number. */ 926 __( 'For optimal performance and security reasons, we recommend running %1$s version %2$s or higher. Contact your web hosting company to correct this.' ), 927 ( $this->mariadb ? 'MariaDB' : 'MySQL' ), 928 $this->health_check_mysql_rec_version 929 ) 930 ); 931 } 932 933 if ( ! $this->mysql_min_version_check ) { 934 $result['status'] = 'critical'; 935 936 $result['label'] = __( 'Severely outdated SQL server' ); 937 938 $result['description'] .= sprintf( 939 '<p>%s</p>', 940 sprintf( 941 /* translators: %1$s: The database engine in use (MySQL or MariaDB). %2$s: Database server minimum version number. */ 942 __( 'WordPress requires %1$s version %2$s or higher. Contact your web hosting company to correct this.' ), 943 ( $this->mariadb ? 'MariaDB' : 'MySQL' ), 944 $this->health_check_mysql_required_version 945 ) 946 ); 947 } 948 949 if ( $db_dropin ) { 950 $result['description'] .= sprintf( 951 '<p>%s</p>', 952 wp_kses( 953 sprintf( 954 /* translators: %s: The name of the database engine being used. */ 955 __( 'You are using a <code>wp-content/db.php</code> drop-in which might mean that a %s database is not being used.' ), 956 ( $this->is_mariadb ? 'MariaDB' : 'MySQL' ) 957 ), 958 array( 959 'code' => true, 960 ) 961 ) 962 ); 963 } 964 965 return $result; 966 } 967 968 /** 969 * Test if the database server is capable of using utf8mb4. 970 * 971 * @since 5.2.0 972 * 973 * @return array The test results. 974 */ 975 public function get_test_utf8mb4_support() { 976 global $wpdb; 977 978 $result = array( 979 'label' => __( 'UTF8MB4 is supported' ), 980 'status' => 'good', 981 'badge' => array( 982 'label' => 'Performance', 983 'color' => 'orange', 984 ), 985 'description' => sprintf( 986 '<p>%s</p>', 987 __( 'UTF8MB4 is a database storage attribute that makes sure your site can store non-English text and other strings (for instance emoticons) without unexpected problems.' ) 988 ), 989 'actions' => '', 990 'test' => 'utf8mb4_support', 991 ); 992 993 if ( ! $this->is_mariadb ) { 994 if ( version_compare( $this->mysql_server_version, '5.5.3', '<' ) ) { 995 $result['status'] = 'recommended'; 996 997 $result['label'] = __( 'utf8mb4 requires a MySQL update' ); 998 999 $result['description'] .= sprintf( 1000 '<p>%s</p>', 1001 sprintf( 1002 /* translators: %s: Version number. */ 1003 __( 'WordPress’ utf8mb4 support requires MySQL version %s or greater.' ), 1004 '5.5.3' 1005 ) 1006 ); 1007 } else { 1008 $result['description'] .= sprintf( 1009 '<p>%s</p>', 1010 __( 'Your MySQL version supports utf8mb4.' ) 1011 ); 1012 } 1013 } else { // MariaDB introduced utf8mb4 support in 5.5.0 1014 if ( version_compare( $this->mysql_server_version, '5.5.0', '<' ) ) { 1015 $result['status'] = 'recommended'; 1016 1017 $result['label'] = __( 'utf8mb4 requires a MariaDB update' ); 1018 1019 $result['description'] .= sprintf( 1020 '<p>%s</p>', 1021 sprintf( 1022 /* translators: %s: Version number. */ 1023 __( 'WordPress’ utf8mb4 support requires MariaDB version %s or greater.' ), 1024 '5.5.0' 1025 ) 1026 ); 1027 } else { 1028 $result['description'] .= sprintf( 1029 '<p>%s</p>', 1030 __( 'Your MariaDB version supports utf8mb4.' ) 1031 ); 1032 } 1033 } 1034 1035 if ( $wpdb->use_mysqli ) { 1036 // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_client_info 1037 $mysql_client_version = mysqli_get_client_info(); 1038 } else { 1039 // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_client_info 1040 $mysql_client_version = mysql_get_client_info(); 1041 } 1042 1043 /* 1044 * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server. 1045 * mysqlnd has supported utf8mb4 since 5.0.9. 1046 */ 1047 if ( false !== strpos( $mysql_client_version, 'mysqlnd' ) ) { 1048 $mysql_client_version = preg_replace( '/^\D+([\d.]+).*/', '$1', $mysql_client_version ); 1049 if ( version_compare( $mysql_client_version, '5.0.9', '<' ) ) { 1050 $result['status'] = 'recommended'; 1051 1052 $result['label'] = __( 'utf8mb4 requires a newer client library' ); 1053 1054 $result['description'] .= sprintf( 1055 '<p>%s</p>', 1056 sprintf( 1057 /* translators: %1$s: Name of the library, %2$s: Number of version. */ 1058 __( 'WordPress’ utf8mb4 support requires MySQL client library (%1$s) version %2$s or newer.' ), 1059 'mysqlnd', 1060 '5.0.9' 1061 ) 1062 ); 1063 } 1064 } else { 1065 if ( version_compare( $mysql_client_version, '5.5.3', '<' ) ) { 1066 $result['status'] = 'recommended'; 1067 1068 $result['label'] = __( 'UTF8MB4 requires a newer client library' ); 1069 1070 $result['description'] .= sprintf( 1071 '<p>%s</p>', 1072 sprintf( 1073 /* translators: %1$s: Name of the library, %2$s: Number of version. */ 1074 __( 'WordPress’ utf8mb4 support requires MySQL client library (%1$s) version %2$s or newer.' ), 1075 'libmysql', 1076 '5.5.3' 1077 ) 1078 ); 1079 } 1080 } 1081 1082 return $result; 1083 } 1084 1085 /** 1086 * Test if the site can communicate with WordPress.org. 1087 * 1088 * @since 5.2.0 1089 * 1090 * @return array The test results. 1091 */ 1092 public function get_test_dotorg_communication() { 1093 $result = array( 1094 'label' => __( 'Can communicate with WordPress.org' ), 1095 'status' => '', 1096 'badge' => array( 1097 'label' => 'Security', 1098 'color' => 'red', 1099 ), 1100 'description' => sprintf( 1101 '<p>%s</p>', 1102 __( 'Communicating with the WordPress servers is used to check for new versions, and to both install and update WordPress core, themes or plugins.' ) 1103 ), 1104 'actions' => '', 1105 'test' => 'dotorg_communication', 1106 ); 1107 1108 $wp_dotorg = wp_remote_get( 1109 'https://api.wordpress.org', 1110 array( 1111 'timeout' => 10, 1112 ) 1113 ); 1114 if ( ! is_wp_error( $wp_dotorg ) ) { 1115 $result['status'] = 'good'; 1116 } else { 1117 $result['status'] = 'critical'; 1118 1119 $result['label'] = __( 'Could not reach WordPress.org' ); 1120 1121 $result['description'] .= sprintf( 1122 '<p>%s</p>', 1123 sprintf( 1124 '<span class="error"><span class="screen-reader-text">%s</span></span> %s', 1125 __( 'Error' ), 1126 sprintf( 1127 /* translators: %1$s: The IP address WordPress.org resolves to. %2$s: The error returned by the lookup. */ 1128 __( 'Your site is unable to reach WordPress.org at %1$s, and returned the error: %2$s' ), 1129 gethostbyname( 'wordpress.org' ), 1130 $wp_dotorg->get_error_message() 1131 ) 1132 ) 1133 ); 1134 } 1135 1136 return $result; 1137 } 1138 1139 /** 1140 * Test if debug information is enabled. 1141 * 1142 * When WP_DEBUG is enabled, errors and information may be disclosed to site visitors, or it may be 1143 * logged to a publicly accessible file. 1144 * 1145 * Debugging is also frequently left enabled after looking for errors on a site, as site owners do 1146 * not understand the implications of this. 1147 * 1148 * @since 5.2.0 1149 * 1150 * @return array The test results. 1151 */ 1152 public function get_test_is_in_debug_mode() { 1153 $result = array( 1154 'label' => __( 'Your site is not set to output debug information' ), 1155 'status' => 'good', 1156 'badge' => array( 1157 'label' => 'Security', 1158 'color' => 'red', 1159 ), 1160 'description' => sprintf( 1161 '<p>%s</p>', 1162 __( 'Debug mode is often enabled to gather more details about an error or site failure, but may contain sensitive information which should not be available on a publicly available website.' ) 1163 ), 1164 'actions' => '', 1165 'test' => 'is_in_debug_mode', 1166 ); 1167 1168 if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { 1169 if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) { 1170 $result['label'] = __( 'Your site is set to log errors to a potentially public file.' ); 1171 1172 $result['status'] = 'critical'; 1173 1174 $result['description'] .= sprintf( 1175 '<p>%s</p>', 1176 __( 'The value, WP_DEBUG_LOG, has been added to this websites configuration file. This means any errors on the site will be written to a file which is potentially available to normal users.' ) 1177 ); 1178 } 1179 1180 if ( defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY ) { 1181 $result['label'] = __( 'Your site is set to display errors to site visitors' ); 1182 1183 $result['status'] = 'critical'; 1184 1185 $result['description'] .= sprintf( 1186 '<p>%s</p>', 1187 __( 'The value, WP_DEBUG_DISPLAY, has either been added to your configuration file, or left with its default value. This will make errors display on the front end of your site.' ) 1188 ); 1189 } 1190 } 1191 1192 return $result; 1193 } 1194 1195 /** 1196 * Test if your site is serving content over HTTPS. 1197 * 1198 * Many sites have varying degrees of HTTPS suppoort, the most common of which is sites that have it 1199 * enabled, but only if you visit the right site address. 1200 * 1201 * @since 5.2.0 1202 * 1203 * @return array The test results. 1204 */ 1205 public function get_test_https_status() { 1206 $result = array( 1207 'label' => '', 1208 'status' => '', 1209 'badge' => array( 1210 'label' => 'Security', 1211 'color' => 'red', 1212 ), 1213 'description' => '', 1214 'actions' => '', 1215 'test' => 'https_status', 1216 ); 1217 1218 if ( is_ssl() ) { 1219 $wp_url = get_bloginfo( 'wpurl' ); 1220 $site_url = get_bloginfo( 'url' ); 1221 1222 if ( 'https' !== substr( $wp_url, 0, 5 ) || 'https' !== substr( $site_url, 0, 5 ) ) { 1223 $result['status'] = 'recommended'; 1224 1225 $result['label'] = __( 'Only parts of your site are using HTTPS' ); 1226 1227 $result['description'] = sprintf( 1228 '<p>%s</p>', 1229 sprintf( 1230 /* translators: %s: URL to Settings > General to change options. */ 1231 __( 'You are accessing this website using HTTPS, but your <a href="%s">WordPress Address</a> is not set up to use HTTPS by default.' ), 1232 esc_url( admin_url( 'options-general.php' ) ) 1233 ) 1234 ); 1235 1236 $result['actions'] = sprintf( 1237 '<a href="%s">%s</a>', 1238 esc_url( admin_url( 'options-general.php' ) ), 1239 __( 'Update your site addresses' ) 1240 ); 1241 } else { 1242 $result['status'] = 'good'; 1243 1244 $result['label'] = __( 'Your website is using an active HTTPS connection.' ); 1245 } 1246 } else { 1247 $result['status'] = 'recommended'; 1248 1249 $result['label'] = __( 'Your site does not use HTTPS' ); 1250 1251 $result['description'] = sprintf( 1252 '<p>%s</p>', 1253 __( 'An HTTPS connection is needed for many features on the web today, it also gains the trust of your visitors by helping to protecting their online privacy.' ) 1254 ); 1255 1256 $result['actions'] = sprintf( 1257 '<a href="%s">%s</a>', 1258 esc_url( 1259 /* translators: Website for explaining HTTPS and why it should be used. */ 1260 __( 'https://wordpress.org/support/article/why-should-i-use-https/' ) 1261 ), 1262 __( 'Read more about why you should use HTTPS.' ) 1263 ); 1264 } 1265 1266 return $result; 1267 } 1268 1269 /** 1270 * Check if the HTTP API can handle SSL/TLS requests. 1271 * 1272 * @since 5.2.0 1273 * 1274 * @return array The test results. 1275 */ 1276 public function get_test_ssl_support() { 1277 $result = array( 1278 'label' => '', 1279 'status' => '', 1280 'badge' => array( 1281 'label' => 'Security', 1282 'color' => 'red', 1283 ), 1284 'description' => sprintf( 1285 '<p>%s</p>', 1286 __( 'Securely communicating between servers are needed for transactions such as fetching files, conducting sales on store sites, and much more.' ) 1287 ), 1288 'actions' => '', 1289 'test' => 'ssl_support', 1290 ); 1291 1292 $supports_https = wp_http_supports( array( 'ssl' ) ); 1293 1294 if ( $supports_https ) { 1295 $result['status'] = 'good'; 1296 1297 $result['label'] = __( 'Your site can communicate securely with other services' ); 1298 } else { 1299 $result['status'] = 'critical'; 1300 1301 $result['label'] = __( 'Your site is unable to communicate securely with other services' ); 1302 1303 $result['description'] .= sprintf( 1304 '<p>%s</p>', 1305 __( 'Talk to your web host about OpenSSL support for PHP.' ) 1306 ); 1307 } 1308 1309 return $result; 1310 } 1311 1312 /** 1313 * Test if scheduled events run as intended. 1314 * 1315 * If scheduled events are not running, this may indicate something with WP_Cron is not working as intended, 1316 * or that there are orphaned events hanging around from older code. 1317 * 1318 * @since 5.2.0 1319 * 1320 * @return array The test results. 1321 */ 1322 public function get_test_scheduled_events() { 1323 $result = array( 1324 'label' => __( 'Scheduled events are running' ), 1325 'status' => 'good', 1326 'badge' => array( 1327 'label' => 'Performance', 1328 'color' => 'orange', 1329 ), 1330 'description' => sprintf( 1331 '<p>%s</p>', 1332 __( 'Scheduled events are what periodically looks for updates to plugins, themes and WordPress it self. It is also what makes sure scheduled posts are published on time. It may also be used by various plugins to make sure that planned actions are executed.' ) 1333 ), 1334 'actions' => '', 1335 'test' => 'scheduled_events', 1336 ); 1337 1338 $this->wp_schedule_test_init(); 1339 1340 if ( is_wp_error( $this->has_missed_cron() ) ) { 1341 $result['status'] = 'critical'; 1342 1343 $result['label'] = __( 'It was not possible to check your scheduled events' ); 1344 1345 $result['description'] = sprintf( 1346 '<p>%s</p>', 1347 sprintf( 1348 /* translators: %s: The error message returned while from the cron scheduler. */ 1349 __( 'While trying to test your sites scheduled events, the following error was returned: %s' ), 1350 $this->has_missed_cron()->get_error_message() 1351 ) 1352 ); 1353 } else { 1354 if ( $this->has_missed_cron() ) { 1355 $result['status'] = 'recommended'; 1356 1357 $result['label'] = __( 'A scheduled event has failed' ); 1358 1359 $result['description'] = sprintf( 1360 '<p>%s</p>', 1361 sprintf( 1362 /* translators: %s: The name of the failed cron event. */ 1363 __( 'The scheduled event, %s, failed to run. Your site still works, but this may indicate that scheduling posts or automated updates may not work as intended.' ), 1364 $this->last_missed_cron 1365 ) 1366 ); 1367 } 1368 } 1369 1370 return $result; 1371 } 1372 1373 /** 1374 * Test if WordPress can run automated background updates. 1375 * 1376 * Background updates in WordPress are primarely used for minor releases and security updates. It's important 1377 * to either have these working, or be aware that they are intentionally disabled for whatever reason. 1378 * 1379 * @since 5.2.0 1380 * 1381 * @return array The test results. 1382 */ 1383 public function get_test_background_updates() { 1384 $result = array( 1385 'label' => __( 'Background updates are working' ), 1386 'status' => 'good', 1387 'badge' => array( 1388 'label' => 'Security', 1389 'color' => 'red', 1390 ), 1391 'description' => sprintf( 1392 '<p>%s</p>', 1393 __( 'Background updates ensure that WordPress can auto-update if a security update is released for the version you are currently using.' ) 1394 ), 1395 'actions' => '', 1396 'test' => 'background_updates', 1397 ); 1398 1399 if ( ! class_exists( 'WP_Site_Health_Auto_Updates' ) ) { 1400 require_once( ABSPATH . 'wp-admin/includes/class-wp-site-health-auto-updates.php' ); 1401 } 1402 1403 // Run the auto-update tests in a separate class, 1404 // as there are many considerations to be made. 1405 $automatic_updates = new WP_Site_Health_Auto_Updates(); 1406 $tests = $automatic_updates->run_tests(); 1407 1408 $output = '<ul>'; 1409 1410 foreach ( $tests as $test ) { 1411 $severity_string = __( 'Passed' ); 1412 1413 if ( 'fail' === $test->severity ) { 1414 $result['label'] = __( 'Background updates are not working as expected' ); 1415 1416 $result['status'] = 'critical'; 1417 1418 $severity_string = __( 'Error' ); 1419 } 1420 1421 if ( 'warning' === $test->severity && 'good' === $result['status'] ) { 1422 $result['label'] = __( 'Background updates may not be working properly' ); 1423 1424 $result['status'] = 'recommended'; 1425 1426 $severity_string = __( 'Warning' ); 1427 } 1428 1429 $output .= sprintf( 1430 '<li><span class="%s"><span class="screen-reader-text">%s</span></span> %s</li>', 1431 esc_attr( $test->severity ), 1432 $severity_string, 1433 $test->description 1434 ); 1435 } 1436 1437 $output .= '</ul>'; 1438 1439 if ( 'good' !== $result['status'] ) { 1440 $result['description'] .= sprintf( 1441 '<p>%s</p>', 1442 $output 1443 ); 1444 } 1445 1446 return $result; 1447 } 1448 1449 /** 1450 * Test if loopbacks work as expected. 1451 * 1452 * A loopback is when WordPress queries it self, for example to start a new WP_Cron instance, or when editing a 1453 * plugin or theme. This has shown it self to be a recurring issue as code can very easily break this interaction. 1454 * 1455 * @since 5.2.0 1456 * 1457 * @return array The test results. 1458 */ 1459 public function get_test_loopback_requests() { 1460 $result = array( 1461 'label' => __( 'Your site can perform loopback requests' ), 1462 'status' => 'good', 1463 'badge' => array( 1464 'label' => 'Performance', 1465 'color' => 'orange', 1466 ), 1467 'description' => sprintf( 1468 '<p>%s</p>', 1469 __( 'Loopback requests are used to run scheduled events, and are also used by the built-in editors for themes and plugins to verify code stability.' ) 1470 ), 1471 'actions' => '', 1472 'test' => 'loopback_requests', 1473 ); 1474 1475 $check_loopback = $this->can_perform_loopback(); 1476 1477 $result['status'] = $check_loopback->status; 1478 1479 if ( 'good' !== $check_loopback->status ) { 1480 $result['label'] = __( 'Your site could not complete a loopback request' ); 1481 1482 $result['description'] .= sprintf( 1483 '<p>%s</p>', 1484 $check_loopback->message 1485 ); 1486 } 1487 1488 return $result; 1489 } 1490 1491 /** 1492 * Test if HTTP requests are blocked. 1493 * 1494 * It's possible to block all outgoing communication (with the possibility of whitelisting hosts) via the 1495 * HTTP API. This may create problems for users as many features are running as services these days. 1496 * 1497 * @since 5.2.0 1498 * 1499 * @return array The test results. 1500 */ 1501 public function get_test_http_requests() { 1502 $result = array( 1503 'label' => __( 'HTTP requests seem to be working as expected' ), 1504 'status' => 'good', 1505 'badge' => array( 1506 'label' => 'Performance', 1507 'color' => 'orange', 1508 ), 1509 'description' => sprintf( 1510 '<p>%s</p>', 1511 __( 'It is possible for site maintainers to block all, or some, communication to other sites and services. If set up incorrectly, this may prevent plugins and themes from working as intended.' ) 1512 ), 1513 'actions' => '', 1514 'test' => 'http_requests', 1515 ); 1516 1517 $blocked = false; 1518 $hosts = array(); 1519 1520 if ( defined( 'WP_HTTP_BLOCK_EXTERNAL' ) ) { 1521 $blocked = true; 1522 } 1523 1524 if ( defined( 'WP_ACCESSIBLE_HOSTS' ) ) { 1525 $hosts = explode( ',', WP_ACCESSIBLE_HOSTS ); 1526 } 1527 1528 if ( $blocked && 0 === sizeof( $hosts ) ) { 1529 $result['status'] = 'critical'; 1530 1531 $result['label'] = __( 'HTTP requests are blocked' ); 1532 1533 $result['description'] .= sprintf( 1534 '<p>%s</p>', 1535 __( 'HTTP requests have been blocked by the WP_HTTP_BLOCK_EXTERNAL constant, with no allowed hosts.' ) 1536 ); 1537 } 1538 1539 if ( $blocked && 0 < sizeof( $hosts ) ) { 1540 $result['status'] = 'recommended'; 1541 1542 $result['label'] = __( 'HTTP requests are partially blocked' ); 1543 1544 $result['description'] .= sprintf( 1545 '<p>%s</p>', 1546 sprintf( 1547 /* translators: %s: List of hostnames whitelisted. */ 1548 __( 'HTTP requests have been blocked by the WP_HTTP_BLOCK_EXTERNAL constant, with some hosts whitelisted: %s.' ), 1549 implode( ',', $hosts ) 1550 ) 1551 ); 1552 } 1553 1554 return $result; 1555 } 1556 1557 /** 1558 * Test if the REST API is accessible. 1559 * 1560 * Various security measures may block the REST API from working, or it may have been disabled in general. 1561 * This is required for the new block editor to work, so we explicitly test for this. 1562 * 1563 * @since 5.2.0 1564 * 1565 * @return array The test results. 1566 */ 1567 public function get_test_rest_availability() { 1568 $result = array( 1569 'label' => __( 'The REST API is available' ), 1570 'status' => 'good', 1571 'badge' => array( 1572 'label' => 'Performance', 1573 'color' => 'orange', 1574 ), 1575 'description' => sprintf( 1576 '<p>%s</p>', 1577 __( 'The REST API is one way WordPress, and other applications, communicate with the server. One example is the block editor screen, which relies on this to display, and save, your posts and pages.' ) 1578 ), 1579 'actions' => '', 1580 'test' => 'rest_availability', 1581 ); 1582 1583 $cookies = wp_unslash( $_COOKIE ); 1584 $timeout = 10; 1585 $headers = array( 1586 'Cache-Control' => 'no-cache', 1587 'X-WP-Nonce' => wp_create_nonce( 'wp_rest' ), 1588 ); 1589 1590 // Include Basic auth in loopback requests. 1591 if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) { 1592 $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) ); 1593 } 1594 1595 $url = rest_url( 'wp/v2/types/post' ); 1596 1597 // The context for this is editing with the new block editor. 1598 $url = add_query_arg( 1599 array( 1600 'context' => 'edit', 1601 ), 1602 $url 1603 ); 1604 1605 $r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) ); 1606 1607 if ( is_wp_error( $r ) ) { 1608 $result['status'] = 'critical'; 1609 1610 $result['label'] = __( 'The REST API encountered an error' ); 1611 1612 $result['description'] .= sprintf( 1613 '<p>%s</p>', 1614 sprintf( 1615 '%s<br>%s', 1616 __( 'The REST API request failed due to an error.' ), 1617 sprintf( 1618 /* translators: %1$d: The HTTP response code. %2$s: The error message returned. */ 1619 __( 'Error encountered: (%1$d) %2$s' ), 1620 wp_remote_retrieve_response_code( $r ), 1621 $r->get_error_message() 1622 ) 1623 ) 1624 ); 1625 } elseif ( 200 !== wp_remote_retrieve_response_code( $r ) ) { 1626 $result['status'] = 'recommended'; 1627 1628 $result['label'] = __( 'The REST API encountered an unexpected result' ); 1629 1630 $result['description'] .= sprintf( 1631 '<p>%s</p>', 1632 sprintf( 1633 /* translators: %1$d: The HTTP response code returned. %2$s: The error message returned. */ 1634 __( 'The REST API call gave the following unexpected result: (%1$d) %2$s.' ), 1635 wp_remote_retrieve_response_code( $r ), 1636 wp_remote_retrieve_body( $r ) 1637 ) 1638 ); 1639 } else { 1640 $json = json_decode( wp_remote_retrieve_body( $r ), true ); 1641 1642 if ( false !== $json && ! isset( $json['capabilities'] ) ) { 1643 $result['status'] = 'recommended'; 1644 1645 $result['label'] = __( 'The REST API did not behave correctly' ); 1646 1647 $result['description'] .= sprintf( 1648 '<p>%s</p>', 1649 sprintf( 1650 /* translators: %s: the name of the query parameter being tested. */ 1651 __( 'The REST API did not process the %s query parameter correctly.' ), 1652 '<code>context</code>' 1653 ) 1654 ); 1655 } 1656 } 1657 1658 return $result; 1659 } 1660 1661 /** 1662 * Return a set of tests that belong to the site status page. 1663 * 1664 * Each site status test is defined here, they may be `direct` tests, that run on page load, or `async` tests 1665 * which will run later down the line via JavaScript calls to improve page performance and hopefully also user 1666 * experiences. 1667 * 1668 * @since 5.2.0 1669 * 1670 * @return array The list of tests to run. 1671 */ 1672 public static function get_tests() { 1673 $tests = array( 1674 'direct' => array( 1675 'wordpress_version' => array( 1676 'label' => __( 'WordPress Version' ), 1677 'test' => 'wordpress_version', 1678 ), 1679 'plugin_version' => array( 1680 'label' => __( 'Plugin Versions' ), 1681 'test' => 'plugin_version', 1682 ), 1683 'theme_version' => array( 1684 'label' => __( 'Theme Versions' ), 1685 'test' => 'theme_version', 1686 ), 1687 'php_version' => array( 1688 'label' => __( 'PHP Version' ), 1689 'test' => 'php_version', 1690 ), 1691 'sql_server' => array( 1692 'label' => __( 'Database Server version' ), 1693 'test' => 'sql_server', 1694 ), 1695 'php_extensions' => array( 1696 'label' => __( 'PHP Extensions' ), 1697 'test' => 'php_extensions', 1698 ), 1699 'utf8mb4_support' => array( 1700 'label' => __( 'MySQL utf8mb4 support' ), 1701 'test' => 'utf8mb4_support', 1702 ), 1703 'https_status' => array( 1704 'label' => __( 'HTTPS status' ), 1705 'test' => 'https_status', 1706 ), 1707 'ssl_support' => array( 1708 'label' => __( 'Secure communication' ), 1709 'test' => 'ssl_support', 1710 ), 1711 'scheduled_events' => array( 1712 'label' => __( 'Scheduled events' ), 1713 'test' => 'scheduled_events', 1714 ), 1715 'http_requests' => array( 1716 'label' => __( 'HTTP Requests' ), 1717 'test' => 'http_requests', 1718 ), 1719 'debug_enabled' => array( 1720 'label' => __( 'Debugging enabled' ), 1721 'test' => 'is_in_debug_mode', 1722 ), 1723 ), 1724 'async' => array( 1725 'dotorg_communication' => array( 1726 'label' => __( 'Communication with WordPress.org' ), 1727 'test' => 'dotorg_communication', 1728 ), 1729 'background_updates' => array( 1730 'label' => __( 'Background updates' ), 1731 'test' => 'background_updates', 1732 ), 1733 'loopback_requests' => array( 1734 'label' => __( 'Loopback request' ), 1735 'test' => 'loopback_requests', 1736 ), 1737 ), 1738 ); 1739 1740 // Conditionally include REST rules if the function for it exists. 1741 if ( function_exists( 'rest_url' ) ) { 1742 $tests['direct']['rest_availability'] = array( 1743 'label' => __( 'REST API availability' ), 1744 'test' => 'rest_availability', 1745 ); 1746 } 1747 1748 /** 1749 * Add or modify which site status tests are ran on a site. 1750 * 1751 * The site health is determined by a set of tests based on best practices from 1752 * both the WordPress Hosting Team, but also web standards in general. 1753 * 1754 * Some sites may not have the same requirements, for example the automatic update 1755 * checks may be handled by a host, and are therefore disabled in core. 1756 * Or maybe you want to introduce a new test, is caching enabled/disabled/stale for example. 1757 * 1758 * Test may be added either as direct, or asynchronous ones. Any test that may require some time 1759 * to complete should run asynchronously, to avoid extended loading periods within wp-admin. 1760 * 1761 * @since 5.2.0 1762 * 1763 * @param array $test_type { 1764 * An associative arraay, where the `$test_type` is either `direct` or 1765 * `async`, to declare if the test should run via AJAX calls after page load. 1766 * 1767 * @type array $identifier { 1768 * `$identifier` should be a unque identifier for the test that should run. 1769 * Plugins and themes are encouraged to prefix test identifiers with their slug 1770 * to avoid any collisions between tests. 1771 * 1772 * @type string $label A friendly label for your test to identify it by. 1773 * @type string $test The ajax action to be called to perform the tests. 1774 * } 1775 * } 1776 */ 1777 $tests = apply_filters( 'site_status_tests', $tests ); 1778 1779 return $tests; 1780 } 1781 1782 /** 1783 * Add a class to the body HTML tag. 1784 * 1785 * Filters the body class string for admin pages and adds our own class for easier styling. 1786 * 1787 * @since 5.2.0 1788 * 1789 * @param string $body_class The body class string. 1790 * @return string The modified body class string. 1791 */ 1792 public function admin_body_class( $body_class ) { 1793 $body_class .= ' site-health'; 1794 1795 return $body_class; 1796 } 1797 1798 /** 1799 * Initiate the WP_Cron schedule test cases. 1800 * 1801 * @since 5.2.0 1802 */ 1803 private function wp_schedule_test_init() { 1804 $this->schedules = wp_get_schedules(); 1805 $this->get_cron_tasks(); 1806 } 1807 1808 /** 1809 * Populate our list of cron events and store them to a class-wide variable. 1810 * 1811 * @since 5.2.0 1812 */ 1813 private function get_cron_tasks() { 1814 $cron_tasks = _get_cron_array(); 1815 1816 if ( empty( $cron_tasks ) ) { 1817 $this->crons = new WP_Error( 'no_tasks', __( 'No scheduled events exist on this site.' ) ); 1818 return; 1819 } 1820 1821 $this->crons = array(); 1822 1823 foreach ( $cron_tasks as $time => $cron ) { 1824 foreach ( $cron as $hook => $dings ) { 1825 foreach ( $dings as $sig => $data ) { 1826 1827 $this->crons[ "$hook-$sig-$time" ] = (object) array( 1828 'hook' => $hook, 1829 'time' => $time, 1830 'sig' => $sig, 1831 'args' => $data['args'], 1832 'schedule' => $data['schedule'], 1833 'interval' => isset( $data['interval'] ) ? $data['interval'] : null, 1834 ); 1835 1836 } 1837 } 1838 } 1839 } 1840 1841 /** 1842 * Check if any scheduled tasks have been missed. 1843 * 1844 * Returns a boolean value of `true` if a scheduled task has been missed and ends processing. If the list of 1845 * crons is an instance of WP_Error, return the instance instead of a boolean value. 1846 * 1847 * @since 5.2.0 1848 * 1849 * @return bool|WP_Error true if a cron was missed, false if it wasn't. WP_Error if the cron is set to that. 1850 */ 1851 public function has_missed_cron() { 1852 if ( is_wp_error( $this->crons ) ) { 1853 return $this->crons; 1854 } 1855 1856 foreach ( $this->crons as $id => $cron ) { 1857 if ( ( $cron->time - time() ) < 0 ) { 1858 $this->last_missed_cron = $cron->hook; 1859 return true; 1860 } 1861 } 1862 1863 return false; 1864 } 1865 1866 /** 1867 * Run a loopback test on our site. 1868 * 1869 * Loopbacks are what WordPress uses to communicate with it self to start up WP_Cron, scheduled posts, make 1870 * sure plugin or theme edits dont cause site failures and similar. 1871 * 1872 * @since 5.2.0 1873 * 1874 * @return object The test results. 1875 */ 1876 function can_perform_loopback() { 1877 $cookies = wp_unslash( $_COOKIE ); 1878 $timeout = 10; 1879 $headers = array( 1880 'Cache-Control' => 'no-cache', 1881 ); 1882 1883 // Include Basic auth in loopback requests. 1884 if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) { 1885 $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) ); 1886 } 1887 1888 $url = admin_url(); 1889 1890 $r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) ); 1891 1892 if ( is_wp_error( $r ) ) { 1893 return (object) array( 1894 'status' => 'critical', 1895 'message' => sprintf( 1896 '%s<br>%s', 1897 __( 'The loopback request to your site failed, this means features relying on them are not currently working as expected.' ), 1898 sprintf( 1899 // translators: %1$d: The HTTP response code. %2$s: The error message returned. 1900 __( 'Error encountered: (%1$d) %2$s' ), 1901 wp_remote_retrieve_response_code( $r ), 1902 $r->get_error_message() 1903 ) 1904 ), 1905 ); 1906 } 1907 1908 if ( 200 !== wp_remote_retrieve_response_code( $r ) ) { 1909 return (object) array( 1910 'status' => 'recommended', 1911 'message' => sprintf( 1912 // translators: %d: The HTTP response code returned. 1913 __( 'The loopback request returned an unexpected http status code, %d, it was not possible to determine if this will prevent features from working as expected.' ), 1914 wp_remote_retrieve_response_code( $r ) 1915 ), 1916 ); 1917 } 1918 1919 return (object) array( 1920 'status' => 'good', 1921 'message' => __( 'The loopback request to your site completed successfully.' ), 1922 ); 1923 } 1924 } -
src/wp-admin/menu.php
diff --git a/src/wp-admin/menu.php b/src/wp-admin/menu.php index f4490b7bf3..ca9357b89b 100644
a b $menu[75] = array( __( 'Tools' ), 'edit_posts', 'tools.php', 263 263 $submenu['tools.php'][5] = array( __( 'Available Tools' ), 'edit_posts', 'tools.php' ); 264 264 $submenu['tools.php'][10] = array( __( 'Import' ), 'import', 'import.php' ); 265 265 $submenu['tools.php'][15] = array( __( 'Export' ), 'export', 'export.php' ); 266 $submenu['tools.php'][20] = array( __( 'Site Health' ), 'install_plugins', 'site-health.php' ); 266 267 if ( is_multisite() && ! is_main_site() ) { 267 268 $submenu['tools.php'][25] = array( __( 'Delete Site' ), 'delete_site', 'ms-delete-site.php' ); 268 269 } -
new file src/wp-admin/site-health-info.php
diff --git a/src/wp-admin/site-health-info.php b/src/wp-admin/site-health-info.php new file mode 100644 index 0000000000..164ff989c4
- + 1 <?php 2 /** 3 * Tools Administration Screen. 4 * 5 * @package WordPress 6 * @subpackage Administration 7 */ 8 9 /** WordPress Administration Bootstrap */ 10 require_once( dirname( __FILE__ ) . '/admin.php' ); 11 12 if ( ! current_user_can( 'install_plugins' ) ) { 13 wp_die( __( 'Sorry, you do not have permission to access the debug data.' ), '', array( 'reponse' => 401 ) ); 14 } 15 16 wp_enqueue_style( 'site-health' ); 17 wp_enqueue_script( 'site-health' ); 18 19 if ( ! class_exists( 'WP_Debug_Data' ) ) { 20 require_once( ABSPATH . 'wp-admin/includes/class-wp-debug-data.php' ); 21 } 22 if ( ! class_exists( 'WP_Site_Health' ) ) { 23 require_once( ABSPATH . 'wp-admin/includes/class-wp-site-health.php' ); 24 } 25 26 $health_check_site_status = new WP_Site_Health(); 27 28 require_once( ABSPATH . 'wp-admin/admin-header.php' ); 29 ?> 30 31 <div class="wrap health-check-header"> 32 <div class="title-section"> 33 <h1> 34 <?php _ex( 'Site Health', 'Menu, Section and Page Title' ); ?> 35 </h1> 36 37 <div id="progressbar" class="loading" data-pct="0" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" aria-valuetext="<?php esc_attr_e( 'Site tests are running, please wait a moment.' ); ?>"> 38 <svg width="100%" height="100%" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg"> 39 <circle r="90" cx="100" cy="100" fill="transparent" stroke-dasharray="565.48" stroke-dashoffset="0"></circle> 40 <circle id="bar" r="90" cx="100" cy="100" fill="transparent" stroke-dasharray="565.48" stroke-dashoffset="0"></circle> 41 </svg> 42 </div> 43 </div> 44 45 <nav class="tabs-wrapper" aria-label="<?php esc_attr_e( 'Secondary menu' ); ?>"> 46 <a href="<?php echo esc_url( admin_url( 'site-health.php' ) ); ?>" class="tab"> 47 <?php _e( 'Status' ); ?> 48 </a> 49 50 <a href="<?php echo esc_url( admin_url( 'site-health.php?tab=debug' ) ); ?>" class="tab active" aria-current="true"> 51 <?php _e( 'Info' ); ?> 52 </a> 53 </nav> 54 55 <div class="wp-clearfix"></div> 56 </div> 57 58 <div class="wrap health-check-body"> 59 <?php 60 WP_Debug_Data::check_for_updates(); 61 62 $info = WP_Debug_Data::debug_data(); 63 ?> 64 65 <h2> 66 <?php _e( 'Site Info' ); ?> 67 </h2> 68 69 <p> 70 <?php _e( 'You can export the information on this page so it can be easily copied and pasted in support requests such as on the WordPress.org forums, or shared with your website / theme / plugin developers.' ); ?> 71 </p> 72 73 <div class="system-information-copy-wrapper"> 74 <textarea id="system-information-default-copy-field" readonly><?php WP_Debug_Data::textarea_format( $info ); ?></textarea> 75 76 <?php 77 if ( 0 !== strpos( get_locale(), 'en' ) ) : 78 79 $english_info = WP_Debug_Data::debug_data( 'en_US' ); 80 ?> 81 <textarea id="system-information-english-copy-field" readonly><?php WP_Debug_Data::textarea_format( $english_info ); ?></textarea> 82 83 <?php endif; ?> 84 85 <div class="copy-button-wrapper"> 86 <button type="button" class="button button-primary health-check-copy-field" data-copy-field="default"><?php _e( 'Copy to clipboard' ); ?></button> 87 <span class="copy-field-success" aria-hidden="true">Copied!</span> 88 </div> 89 <?php if ( 0 !== strpos( get_locale(), 'en' ) ) : ?> 90 <div class="copy-button-wrapper"> 91 <button type="button" class="button health-check-copy-field" data-copy-field="english"><?php _e( 'Copy to clipboard (English)' ); ?></button> 92 <span class="copy-field-success" aria-hidden="true">Copied!</span> 93 </div> 94 <?php endif; ?> 95 </div> 96 97 <dl id="health-check-debug" role="presentation" class="health-check-accordion"> 98 99 <?php 100 foreach ( $info as $section => $details ) { 101 if ( ! isset( $details['fields'] ) || empty( $details['fields'] ) ) { 102 continue; 103 } 104 ?> 105 <dt role="heading" aria-level="3"> 106 <button aria-expanded="false" class="health-check-accordion-trigger" aria-controls="health-check-accordion-block-<?php echo esc_attr( $section ); ?>" id="health-check-accordion-heading-<?php echo esc_attr( $section ); ?>" type="button"> 107 <span class="title"> 108 <?php echo esc_html( $details['label'] ); ?> 109 110 <?php if ( isset( $details['show_count'] ) && $details['show_count'] ) : ?> 111 <?php printf( '(%d)', count( $details['fields'] ) ); ?> 112 <?php endif; ?> 113 </span> 114 <span class="icon"></span> 115 </button> 116 </dt> 117 118 <dd id="health-check-accordion-block-<?php echo esc_attr( $section ); ?>" role="region" aria-labelledby="health-check-accordion-heading-<?php echo esc_attr( $section ); ?>" class="health-check-accordion-panel" hidden="hidden"> 119 <?php 120 if ( isset( $details['description'] ) && ! empty( $details['description'] ) ) { 121 printf( 122 '<p>%s</p>', 123 wp_kses( 124 $details['description'], 125 array( 126 'a' => array( 127 'href' => true, 128 ), 129 'strong' => true, 130 'em' => true, 131 ) 132 ) 133 ); 134 } 135 ?> 136 <table class="widefat striped health-check-table"> 137 <tbody> 138 <?php 139 foreach ( $details['fields'] as $field ) { 140 if ( is_array( $field['value'] ) ) { 141 $values = ''; 142 foreach ( $field['value'] as $name => $value ) { 143 $values .= sprintf( 144 '<li>%s: %s</li>', 145 esc_html( $name ), 146 esc_html( $value ) 147 ); 148 } 149 } else { 150 $values = esc_html( $field['value'] ); 151 } 152 153 printf( 154 '<tr><td>%s</td><td>%s</td></tr>', 155 esc_html( $field['label'] ), 156 $values 157 ); 158 } 159 ?> 160 </tbody> 161 </table> 162 </dd> 163 <?php } ?> 164 </dl> 165 </div> 166 167 <?php 168 include( ABSPATH . 'wp-admin/admin-footer.php' ); -
new file src/wp-admin/site-health.php
diff --git a/src/wp-admin/site-health.php b/src/wp-admin/site-health.php new file mode 100644 index 0000000000..341ca43a1a
- + 1 <?php 2 /** 3 * Tools Administration Screen. 4 * 5 * @package WordPress 6 * @subpackage Administration 7 */ 8 9 if ( isset( $_GET['tab'] ) && 'debug' === $_GET['tab'] ) { 10 require_once( dirname( __FILE__ ) . '/site-health-info.php' ); 11 return; 12 } 13 14 /** WordPress Administration Bootstrap */ 15 require_once( dirname( __FILE__ ) . '/admin.php' ); 16 17 if ( ! current_user_can( 'install_plugins' ) ) { 18 wp_die( __( 'Sorry, you do not have permission to access site health information.' ), '', array( 'reponse' => 401 ) ); 19 } 20 21 wp_enqueue_style( 'site-health' ); 22 wp_enqueue_script( 'site-health' ); 23 24 if ( ! class_exists( 'WP_Site_Health' ) ) { 25 require_once( ABSPATH . 'wp-admin/includes/class-wp-site-health.php' ); 26 } 27 28 $health_check_site_status = new WP_Site_Health(); 29 30 require_once( ABSPATH . 'wp-admin/admin-header.php' ); 31 ?> 32 33 <div class="wrap health-check-header"> 34 <div class="title-section"> 35 <h1> 36 <?php _ex( 'Site Health', 'Menu, Section and Page Title' ); ?> 37 </h1> 38 39 <div id="progressbar" class="loading" data-pct="0" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" aria-valuetext="<?php esc_attr_e( 'Site tests are running, please wait a moment.' ); ?>"> 40 <svg width="100%" height="100%" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg"> 41 <circle r="90" cx="100" cy="100" fill="transparent" stroke-dasharray="565.48" stroke-dashoffset="0"></circle> 42 <circle id="bar" r="90" cx="100" cy="100" fill="transparent" stroke-dasharray="565.48" stroke-dashoffset="0"></circle> 43 </svg> 44 </div> 45 </div> 46 47 <nav class="tabs-wrapper" aria-label="<?php esc_attr_e( 'Secondary menu' ); ?>"> 48 <a href="<?php echo esc_url( admin_url( 'site-health.php' ) ); ?>" class="tab active" aria-current="true"> 49 <?php _e( 'Status' ); ?> 50 </a> 51 52 <a href="<?php echo esc_url( admin_url( 'site-health.php?tab=debug' ) ); ?>" class="tab"> 53 <?php _e( 'Info' ); ?> 54 </a> 55 </nav> 56 57 <div class="wp-clearfix"></div> 58 </div> 59 60 <div class="wrap health-check-body"> 61 <div class="site-status-all-clear hide"> 62 <p class="icon"> 63 <span class="dashicons dashicons-yes"></span> 64 </p> 65 66 <p class="encouragement"> 67 <?php _e( 'Great job!' ); ?> 68 </p> 69 70 <p> 71 <?php _e( 'Everything is running smoothly here.' ); ?> 72 </p> 73 </div> 74 75 <div class="site-status-has-issues"> 76 <h2> 77 <?php _e( 'Site Health Status' ); ?> 78 </h2> 79 80 <p><?php _e( 'The site health check shows critical information about your WordPress configuration and items that require your attention.' ); ?></p> 81 82 <div class="issues-wrapper" id="health-check-issues-critical"> 83 <h3> 84 <span class="issue-count">0</span> <?php _e( 'Critical issues' ); ?> 85 </h3> 86 87 <dl id="health-check-site-status-critical" role="presentation" class="health-check-accordion issues"></dl> 88 </div> 89 90 <div class="issues-wrapper" id="health-check-issues-recommended"> 91 <h3> 92 <span class="issue-count">0</span> <?php _e( 'Recommended improvements' ); ?> 93 </h3> 94 95 <dl id="health-check-site-status-recommended" role="presentation" class="health-check-accordion issues"></dl> 96 </div> 97 </div> 98 99 <div class="view-more"> 100 <button type="button" class="button site-health-view-passed" aria-expanded="false"> 101 <?php _e( 'Show passed tests' ); ?> 102 </button> 103 </div> 104 105 <div class="issues-wrapper hidden" id="health-check-issues-good"> 106 <h3> 107 <span class="issue-count">0</span> <?php _e( 'Items with no issues detected' ); ?> 108 </h3> 109 110 <dl id="health-check-site-status-good" role="presentation" class="health-check-accordion issues"></dl> 111 </div> 112 </div> 113 114 <script id="tmpl-health-check-issue" type="text/template"> 115 <dt role="heading" aria-level="4"> 116 <button aria-expanded="false" class="health-check-accordion-trigger" aria-controls="health-check-accordion-block-{{ data.test }}" id="health-check-accordion-heading-{{ data.test }}" type="button"> 117 <span class="title">{{ data.label }}</span> 118 <span class="badge {{ data.badge.color }}">{{ data.badge.label }}</span> 119 <span class="icon"></span> 120 </button> 121 </dt> 122 <dd id="health-check-accordion-block-{{ data.test }}" aria-labelledby="health-check-accordion-heading-{{ data.test }}" role="region" class="health-check-accordion-panel" hidden="hidden"> 123 {{{ data.description }}} 124 <div class="actions"> 125 <p class="button-container">{{{ data.actions }}}</p> 126 </div> 127 </dd> 128 </script> 129 130 <?php 131 include( ABSPATH . 'wp-admin/admin-footer.php' ); -
src/wp-includes/script-loader.php
diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index a3ca60e75e..5bba581d8f 100644
a b function wp_default_scripts( &$scripts ) { 1688 1688 ) 1689 1689 ); 1690 1690 1691 $scripts->add( 'site-health', "/wp-admin/js/site-health$suffix.js", array( 'jquery', 'wp-util', 'wp-a11y' ), false, 1 ); 1692 1691 1693 $scripts->add( 'updates', "/wp-admin/js/updates$suffix.js", array( 'jquery', 'wp-util', 'wp-a11y' ), false, 1 ); 1692 1694 did_action( 'init' ) && $scripts->localize( 1693 1695 'updates', … … function wp_default_styles( &$styles ) { 1933 1935 $styles->add( 'site-icon', "/wp-admin/css/site-icon$suffix.css" ); 1934 1936 $styles->add( 'l10n', "/wp-admin/css/l10n$suffix.css" ); 1935 1937 $styles->add( 'code-editor', "/wp-admin/css/code-editor$suffix.css", array( 'wp-codemirror' ) ); 1938 $styles->add( 'site-health', "/wp-admin/css/site-health$suffix.css" ); 1936 1939 1937 1940 $styles->add( 'wp-admin', false, array( 'dashicons', 'common', 'forms', 'admin-menu', 'dashboard', 'list-tables', 'edit', 'revisions', 'media', 'themes', 'about', 'nav-menus', 'widgets', 'site-icon', 'l10n' ) ); 1938 1941