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