WordPress.org

Make WordPress Core

Ticket #46573: 46573.3.patch

File 46573.3.patch, 137.5 KB (added by Clorith, 17 months ago)
  • src/js/_enqueues/admin/site-health.js

     
     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// Debug information copy section.
     10jQuery(document).ready(function($) {
     11    $( '.health-check-copy-field' ).click(function( e ) {
     12        var $textarea = $( '#system-information-' + $( this ).data( 'copy-field' ) + '-copy-field' ),
     13            $wrapper = $( this ).closest( 'div' );
     14
     15        e.preventDefault();
     16
     17        $textarea.select();
     18
     19        if ( document.execCommand( 'copy' ) ) {
     20            $( '.copy-field-success', $wrapper ).addClass( 'visible' );
     21            $( this ).focus();
     22
     23            wp.a11y.speak( SiteHealth.string.site_info_copied, 'polite' );
     24        }
     25    });
     26
     27    $( '.health-check-toggle-copy-section' ).click(function( e ) {
     28        var $copySection = $( '.system-information-copy-wrapper' );
     29
     30        e.preventDefault();
     31
     32        if ( $copySection.hasClass( 'hidden' ) ) {
     33            $copySection.removeClass( 'hidden' );
     34
     35            $( this ).text( SiteHealth.string.site_info_hide_copy );
     36        } else {
     37            $copySection.addClass( 'hidden' );
     38
     39            $( this ).text( SiteHealth.string.site_info_show_copy );
     40        }
     41    });
     42});
     43
     44// Accordion handling in various areas.
     45jQuery(document).ready(function($) {
     46    $( '.health-check-accordion' ).on( 'click', '.health-check-accordion-trigger', function() {
     47        var isExpanded = ( 'true' === $( this ).attr( 'aria-expanded' ) );
     48
     49        if ( isExpanded ) {
     50            $( this ).attr( 'aria-expanded', 'false' );
     51            $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', true );
     52        } else {
     53            $( this ).attr( 'aria-expanded', 'true' );
     54            $( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', false );
     55        }
     56    });
     57
     58    $( '.health-check-accordion' ).on( 'keyup', '.health-check-accordion-trigger', function( e ) {
     59        if ( '38' === e.keyCode.toString() ) {
     60            $( '.health-check-accordion-trigger', $( this ).closest( 'dt' ).prevAll( 'dt' ) ).focus();
     61        } else if ( '40' === e.keyCode.toString() ) {
     62            $( '.health-check-accordion-trigger', $( this ).closest( 'dt' ).nextAll( 'dt' ) ).focus();
     63        }
     64    });
     65});
     66
     67// Site Health test handling.
     68jQuery( document ).ready(function( $ ) {
     69    var data;
     70
     71    $( '.site-health-view-passed' ).on( 'click', function() {
     72        var goodIssuesWrapper = $( '#health-check-issues-good' );
     73
     74        goodIssuesWrapper.toggleClass( 'hidden' );
     75        $( this ).attr( 'aria-expanded', ! goodIssuesWrapper.hasClass( 'hidden' ) );
     76    });
     77
     78    function HCAppendIssue( issue ) {
     79        var htmlOutput,
     80            issueWrapper,
     81            issueCounter;
     82
     83        SiteHealth.site_status.issues[ issue.status ]++;
     84
     85        issueWrapper = $( '#health-check-issues-' + issue.status );
     86
     87        issueCounter = $( '.issue-count', issueWrapper );
     88
     89        htmlOutput = '<dt role="heading" aria-level="4">\n' +
     90            '                <button aria-expanded="false" class="health-check-accordion-trigger" aria-controls="health-check-accordion-block-' + issue.test + '" id="health-check-accordion-heading-' + issue.test + '" type="button">\n' +
     91            '                    <span class="title">\n' +
     92            '                        ' + issue.label + '\n' +
     93            '                    </span>\n' +
     94            '                    <span class="badge ' + issue.badge.color + '">' + issue.badge.label + '</span>\n' +
     95            '                    <span class="icon"></span>\n' +
     96            '                </button>\n' +
     97            '            </dt>\n' +
     98            '            <dd id="health-check-accordion-block-' + issue.test + '" aria-labelledby="health-check-accordion-heading-' + issue.test + '" role="region" class="health-check-accordion-panel" hidden="hidden">\n' +
     99            '                ' + issue.description + '\n' +
     100            '                <div class="actions"><p>' + issue.actions + '</p></div>' +
     101            '            </dd>';
     102
     103        issueCounter.text( SiteHealth.site_status.issues[ issue.status ] );
     104        $( '.issues', '#health-check-issues-' + issue.status ).append( htmlOutput );
     105    }
     106
     107    function HCRecalculateProgression() {
     108        var r, c, pct;
     109        var $progressBar = $( '#progressbar' );
     110        var $circle = $( '#progressbar svg #bar' );
     111        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 );
     112        var failedTests = parseInt( SiteHealth.site_status.issues.recommended, 0 ) + ( parseInt( SiteHealth.site_status.issues.critical, 0 ) * 1.5 );
     113        var val = 100 - Math.ceil( ( failedTests / totalTests ) * 100 );
     114
     115        if ( 0 === totalTests ) {
     116            $progressBar.addClass( 'hidden' );
     117            return;
     118        }
     119
     120        $progressBar.removeClass( 'loading' );
     121
     122        if ( isNaN( val ) ) {
     123            val = 100;
     124        }
     125
     126        r = $circle.attr( 'r' );
     127        c = Math.PI * ( r * 2 );
     128
     129        if ( val < 0 ) {
     130            val = 0;
     131        }
     132        if ( val > 100 ) {
     133            val = 100;
     134        }
     135
     136        pct = ( ( 100 - val ) / 100 ) * c;
     137
     138        $circle.css( { strokeDashoffset: pct } );
     139
     140        if ( parseInt( SiteHealth.site_status.issues.critical, 0 ) < 1 ) {
     141            $( '#health-check-issues-critical' ).addClass( 'hidden' );
     142        }
     143
     144        if ( parseInt( SiteHealth.site_status.issues.recommended, 0 ) < 1 ) {
     145            $( '#health-check-issues-recommended' ).addClass( 'hidden' );
     146        }
     147
     148        if ( val >= 50 ) {
     149            $circle.addClass( 'orange' ).removeClass( 'red' );
     150        }
     151
     152        if ( val >= 90 ) {
     153            $circle.addClass( 'green' ).removeClass( 'orange' );
     154        }
     155
     156        if ( 100 === val ) {
     157            $( '.site-status-all-clear' ).removeClass( 'hide' );
     158            $( '.site-status-has-issues' ).addClass( 'hide' );
     159        }
     160
     161        $progressBar.attr( 'data-pct', val );
     162        $progressBar.attr( 'aria-valuenow', val );
     163
     164        $( '.health-check-body' ).attr( 'aria-hidden', false );
     165
     166        $.post(
     167            ajaxurl,
     168            {
     169                'action': 'health-check-site-status-result',
     170                '_wpnonce': SiteHealth.nonce.site_status_result,
     171                'counts': SiteHealth.site_status.issues
     172            }
     173        );
     174
     175        wp.a11y.speak( SiteHealth.string.site_health_complete_screen_reader.replace( '%s', val + '%' ), 'polite' );
     176    }
     177
     178    function maybeRunNextAsyncTest() {
     179        var doCalculation = true;
     180
     181        if ( SiteHealth.site_status.async.length >= 1 ) {
     182            $.each( SiteHealth.site_status.async, function() {
     183                var data = {
     184                    'action': 'health-check-site-status',
     185                    'feature': this.test,
     186                    '_wpnonce': SiteHealth.nonce.site_status
     187                };
     188
     189                if ( this.completed ) {
     190                    return true;
     191                }
     192
     193                doCalculation = false;
     194
     195                this.completed = true;
     196
     197                $.post(
     198                    ajaxurl,
     199                    data,
     200                    function( response ) {
     201                        HCAppendIssue( response.data );
     202                        maybeRunNextAsyncTest();
     203                    }
     204                );
     205
     206                return false;
     207            } );
     208        }
     209
     210        if ( doCalculation ) {
     211            HCRecalculateProgression();
     212        }
     213    }
     214
     215    if ( 'undefined' !== typeof SiteHealth ) {
     216        if ( 0 === SiteHealth.site_status.direct.length && 0 === SiteHealth.site_status.async.length ) {
     217            HCRecalculateProgression();
     218        } else {
     219            SiteHealth.site_status.issues = {
     220                'good': 0,
     221                'recommended': 0,
     222                'critical': 0
     223            };
     224        }
     225
     226        if ( SiteHealth.site_status.direct.length > 0 ) {
     227            $.each( SiteHealth.site_status.direct, function() {
     228                HCAppendIssue( this );
     229            });
     230        }
     231
     232        if ( SiteHealth.site_status.async.length > 0 ) {
     233            data = {
     234                'action': 'health-check-site-status',
     235                'feature': SiteHealth.site_status.async[0].test,
     236                '_wpnonce': SiteHealth.nonce.site_status
     237            };
     238
     239            SiteHealth.site_status.async[0].completed = true;
     240
     241            $.post(
     242                ajaxurl,
     243                data,
     244                function( response ) {
     245                    HCAppendIssue( response.data );
     246                    maybeRunNextAsyncTest();
     247                }
     248            );
     249        } else {
     250            HCRecalculateProgression();
     251        }
     252    }
     253});
  • src/wp-admin/css/site-health.css

     
     1body.site-health {
     2    background: #fff;
     3}
     4
     5body.site-health #wpcontent {
     6    padding-left: 0;
     7}
     8
     9body.site-health .wrap {
     10    margin-right: 0;
     11    margin-left: 0;
     12}
     13
     14body.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
     25body.site-health ul li,
     26body.site-health ol li {
     27    line-height: 1.5;
     28}
     29
     30body.site-health .health-check-header {
     31    text-align: center;
     32    margin-bottom: 1rem;
     33    border-bottom: 2px solid #ccc;
     34}
     35
     36body.site-health .health-check-header .title-section {
     37    display: flex;
     38    align-items: center;
     39    justify-content: center;
     40}
     41
     42body.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
     48body.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
     59body.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
     74body.site-health .health-check-header .title-section #progressbar.hidden {
     75    display: none;
     76}
     77
     78body.site-health .health-check-header .title-section #progressbar.loading:after {
     79    animation: loadingEllipsis 3s infinite ease-in-out;
     80}
     81
     82body.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
     88body.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
     95body.site-health .health-check-header .title-section #progressbar svg #bar {
     96    stroke-dashoffset: 565;
     97    stroke: #dc3232;
     98}
     99
     100body.site-health .health-check-header .title-section #progressbar svg #bar.green {
     101    stroke: #46b450;
     102}
     103
     104body.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
     132body.site-health .health-check-header .tabs-wrapper {
     133    display: block;
     134}
     135
     136body.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;
     142    margin: 0 1rem;
     143    border-bottom: 4px solid transparent;
     144    transition: border 0.5s ease-in-out;
     145}
     146
     147body.site-health .health-check-header .tabs-wrapper .tab.active {
     148    border-bottom: 4px solid #00a0d2;
     149    font-weight: 600;
     150}
     151
     152body.site-health .health-check-header .tabs-wrapper .tab:focus, body.site-health .health-check-header .tabs-wrapper .tab:hover, body.site-health .health-check-header .tabs-wrapper .tab:active {
     153    border-bottom: 4px solid #00a0d2;
     154}
     155
     156body.site-health .health-check-body {
     157    max-width: 800px;
     158    width: 100%;
     159    margin: 0 auto;
     160}
     161
     162body.site-health .health-check-table thead th:first-child,
     163body.site-health .health-check-table thead td:first-child {
     164    width: 30%;
     165}
     166
     167body.site-health .health-check-table tbody td {
     168    width: 70%;
     169}
     170
     171body.site-health .health-check-table tbody td:first-child {
     172    width: 30%;
     173}
     174
     175body.site-health .health-check-table tbody td ul,
     176body.site-health .health-check-table tbody td ol {
     177    margin: 0;
     178}
     179
     180body.site-health .pass:before,
     181body.site-health .good:before {
     182    content: "\f147";
     183    display: inline-block;
     184    color: #46b450;
     185    font-family: dashicons;
     186}
     187
     188body.site-health .warning:before {
     189    content: "\f460";
     190    display: inline-block;
     191    color: #ffb900;
     192    font-family: dashicons;
     193}
     194
     195body.site-health .info:before {
     196    content: "\f348";
     197    display: inline-block;
     198    color: #00a0d2;
     199    font-family: dashicons;
     200}
     201
     202body.site-health .fail:before,
     203body.site-health .error:before {
     204    content: "\f335";
     205    display: inline-block;
     206    color: #dc3232;
     207    font-family: dashicons;
     208}
     209
     210body.site-health .spinner {
     211    float: none;
     212}
     213
     214body.site-health .system-information-copy-wrapper {
     215    display: block;
     216    margin: 1rem 0;
     217}
     218
     219body.site-health .system-information-copy-wrapper.hidden {
     220    display: none;
     221}
     222
     223body.site-health .system-information-copy-wrapper textarea {
     224    width: 100%;
     225}
     226
     227body.site-health .system-information-copy-wrapper .copy-button-wrapper {
     228    margin: 0.5rem 0 1rem;
     229}
     230
     231body.site-health .copy-field-success {
     232    display: none;
     233    color: #40860a;
     234    line-height: 1.8;
     235    margin-left: 0.5rem;
     236}
     237
     238body.site-health .copy-field-success.visible {
     239    display: inline-block;
     240}
     241
     242body.site-health .site-status-has-issues {
     243    display: block;
     244}
     245
     246body.site-health .site-status-has-issues.hide {
     247    display: none;
     248}
     249
     250body.site-health h3 {
     251    padding: 0 0.5rem;
     252    font-weight: 600;
     253}
     254
     255body.site-health .view-more {
     256    text-align: center;
     257}
     258
     259body.site-health .issues-wrapper {
     260    margin-bottom: 5rem;
     261}
     262
     263body.site-health .site-status-all-clear {
     264    display: flex;
     265    flex-direction: column;
     266    align-items: center;
     267    justify-content: center;
     268    text-align: center;
     269    height: 100%;
     270    width: 100%;
     271    margin-top: 0;
     272}
     273
     274@media all and (min-width: 784px) {
     275    body.site-health .site-status-all-clear {
     276        margin: 5rem 0;
     277    }
     278}
     279
     280body.site-health .site-status-all-clear.hide {
     281    display: none;
     282}
     283
     284body.site-health .site-status-all-clear .dashicons {
     285    font-size: 150px;
     286    height: 130px;
     287    width: 150px;
     288}
     289
     290body.site-health .site-status-all-clear .encouragement {
     291    font-size: 1.5rem;
     292    font-weight: 600;
     293}
     294
     295body.site-health .site-status-all-clear p {
     296    margin: 0;
     297}
     298
     299body .health-check-accordion {
     300    border: 1px solid #d1d1d1;
     301}
     302
     303body .health-check-accordion dt {
     304    font-weight: 600;
     305    border-top: 1px solid #d1d1d1;
     306}
     307
     308body .health-check-accordion dt:first-child {
     309    border-radius: 0.3em 0.3em 0 0;
     310    border-top: none;
     311}
     312
     313body .health-check-accordion .health-check-accordion-trigger {
     314    background: #fff;
     315    border: 0;
     316    color: #212121;
     317    cursor: pointer;
     318    display: block;
     319    font-weight: 400;
     320    margin: 0;
     321    padding: 1em 1.5em;
     322    position: relative;
     323    text-align: left;
     324    width: 100%;
     325}
     326
     327body .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 {
     328    background: #dedede;
     329}
     330
     331body .health-check-accordion .health-check-accordion-trigger .title {
     332    display: inline-block;
     333    pointer-events: none;
     334    font-weight: 600;
     335}
     336
     337body .health-check-accordion .health-check-accordion-trigger .icon {
     338    border: solid #191e23;
     339    border-width: 0 2px 2px 0;
     340    height: 0.5rem;
     341    pointer-events: none;
     342    position: absolute;
     343    right: 1.5em;
     344    top: 50%;
     345    transform: translateY(-60%) rotate(45deg);
     346    width: 0.5rem;
     347}
     348
     349body .health-check-accordion .health-check-accordion-trigger .badge {
     350    display: inline-block;
     351    padding: 0.1rem 0.5rem;
     352    background-color: #dcdcdc;
     353    color: #000;
     354    font-weight: 600;
     355    margin: 0 0.5rem;
     356}
     357
     358body .health-check-accordion .health-check-accordion-trigger .badge.blue {
     359    background-color: #0073af;
     360    color: #fff;
     361}
     362
     363body .health-check-accordion .health-check-accordion-trigger .badge.orange {
     364    background-color: #ffb900;
     365    color: #000;
     366}
     367
     368body .health-check-accordion .health-check-accordion-trigger .badge.red {
     369    background-color: #dc3232;
     370    color: #fff;
     371}
     372
     373body .health-check-accordion .health-check-accordion-trigger .badge.green {
     374    background-color: #40860a;
     375    color: #fff;
     376}
     377
     378body .health-check-accordion .health-check-accordion-trigger .badge.pink {
     379    background-color: #f4b0fc;
     380    color: #000;
     381}
     382
     383body .health-check-accordion .health-check-accordion-trigger .badge.gray {
     384    background-color: #ccc;
     385    color: #000;
     386}
     387
     388body .health-check-accordion .health-check-accordion-trigger .badge.light-blue {
     389    background-color: #10e9fb;
     390    color: #000;
     391}
     392
     393body .health-check-accordion .health-check-accordion-trigger .badge.light-green {
     394    background-color: #60f999;
     395    color: #000;
     396}
     397
     398body .health-check-accordion .health-check-accordion-trigger[aria-expanded="true"] .icon {
     399    transform: translateY(-50%) rotate(-135deg);
     400}
     401
     402body .health-check-accordion .health-check-accordion-panel {
     403    margin: 0;
     404    padding: 1em 1.5em;
     405    background: #fff;
     406}
     407
     408body .health-check-accordion .health-check-accordion-panel > div {
     409    display: block;
     410}
     411
     412body .health-check-accordion .health-check-accordion-panel[hidden] {
     413    display: none;
     414}
     415
     416body .health-check-accordion dl dd {
     417    margin: 0 0 0.5em 2em;
     418}
  • src/wp-admin/css/wp-admin.css

     
    1212@import url(widgets.css);
    1313@import url(site-icon.css);
    1414@import url(l10n.css);
     15@import url(site-health.css);
  • src/wp-admin/includes/ajax-actions.php

     
    48334833
    48344834        wp_send_json_success( $response );
    48354835}
     4836
     4837function wp_ajax_health_check_site_status() {
     4838        check_ajax_referer( 'health-check-site-status' );
     4839
     4840        if ( ! current_user_can( 'manage_options' ) ) {
     4841                wp_send_json_error();
     4842        }
     4843
     4844        if ( ! class_exists( 'WP_Site_Health' ) ) {
     4845                require_once( ABSPATH . 'wp-admin/includes/class-wp-site-health.php' );
     4846        }
     4847
     4848        $site_health = new WP_Site_Health();
     4849
     4850        $function = sprintf(
     4851                'json_test_%s',
     4852                $_POST['feature']
     4853        );
     4854
     4855        if ( ! method_exists( $site_health, $function ) || ! is_callable( array( $site_health, $function ) ) ) {
     4856                return;
     4857        }
     4858
     4859        call_user_func( array( $site_health, $function ) );
     4860}
     4861
     4862function wp_ajax_health_check_site_status_result() {
     4863        check_ajax_referer( 'health-check-site-status-result' );
     4864
     4865        if ( ! current_user_can( 'manage_options' ) ) {
     4866                wp_send_json_error();
     4867        }
     4868
     4869        set_transient( 'health-check-site-status-result', wp_json_encode( $_POST['counts'] ) );
     4870
     4871        wp_send_json_success();
     4872}
  • src/wp-admin/includes/class-wp-debug-data.php

     
     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// Make sure the file is not directly accessible.
     11if ( ! defined( 'ABSPATH' ) ) {
     12        die( 'We\'re sorry, but you can not directly access this file.' );
     13}
     14
     15/**
     16 * Class WP_Debug_Data
     17 */
     18class WP_Debug_Data {
     19
     20        /**
     21         * Calls all core functions to check for updates
     22         *
     23         * @uses wp_version_check()
     24         * @uses wp_update_plugins()
     25         * @uses wp_update_themes()
     26         *
     27         * @return void
     28         */
     29        static function check_for_updates() {
     30
     31                wp_version_check();
     32                wp_update_plugins();
     33                wp_update_themes();
     34
     35        }
     36
     37        static function debug_data( $locale = null ) {
     38                if ( ! empty( $locale ) ) {
     39                        // Change the language used for translations
     40                        if ( function_exists( 'switch_to_locale' ) ) {
     41                                $original_locale = get_locale();
     42                                $switched_locale = switch_to_locale( $locale );
     43                        }
     44                }
     45                global $wpdb;
     46
     47                $upload_dir = wp_upload_dir();
     48                if ( file_exists( ABSPATH . 'wp-config.php' ) ) {
     49                        $wp_config_path = ABSPATH . 'wp-config.php';
     50                        // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
     51                } elseif ( @file_exists( dirname( ABSPATH ) . '/wp-config.php' ) && ! @file_exists( dirname( ABSPATH ) . '/wp-settings.php' ) ) {
     52                        $wp_config_path = dirname( ABSPATH ) . '/wp-config.php';
     53                }
     54
     55                $core_current_version = get_bloginfo( 'version' );
     56                $core_updates         = get_core_updates();
     57                $core_update_needed   = '';
     58
     59                foreach ( $core_updates as $core => $update ) {
     60                        if ( 'upgrade' === $update->response ) {
     61                                // translators: %s: Latest WordPress version number.
     62                                $core_update_needed = ' ' . sprintf( __( '( Latest version: %s )' ), $update->version );
     63                        } else {
     64                                $core_update_needed = '';
     65                        }
     66                }
     67
     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                if ( is_multisite() ) {
     242                        $network_query = new WP_Network_Query();
     243                        $network_ids   = $network_query->query( array(
     244                                'fields'        => 'ids',
     245                                'number'        => 100,
     246                                'no_found_rows' => false,
     247                        ) );
     248
     249                        $site_count = 0;
     250                        foreach ( $network_ids as $network_id ) {
     251                                $site_count += get_blog_count( $network_id );
     252                        }
     253
     254                        $info['wp-core']['fields']['user_count'] = array(
     255                                'label' => __( 'User Count' ),
     256                                'value' => get_user_count(),
     257                        );
     258                        $info['wp-core']['fields']['site_count'] = array(
     259                                'label' => __( 'Site Count' ),
     260                                'value' => $site_count,
     261                        );
     262                        $info['wp-core']['fields']['network_count'] = array(
     263                                'label' => __( 'Network Count' ),
     264                                'value' => $network_query->found_networks,
     265                        );
     266                } else {
     267                        $user_count = count_users();
     268
     269                        $info['wp-core']['fields']['user_count'] = array(
     270                                'label' => __( 'User Count' ),
     271                                'value' => $user_count['total_users'],
     272                        );
     273                }
     274
     275                // WordPress features requiring processing.
     276                $wp_dotorg = wp_remote_get( 'https://wordpress.org', array(
     277                        'timeout' => 10,
     278                ) );
     279                if ( ! is_wp_error( $wp_dotorg ) ) {
     280                        $info['wp-core']['fields']['dotorg_communication'] = array(
     281                                'label' => __( 'Communication with WordPress.org' ),
     282                                'value' => sprintf(
     283                                        __( 'WordPress.org is reachable' )
     284                                ),
     285                        );
     286                } else {
     287                        $info['wp-core']['fields']['dotorg_communication'] = array(
     288                                'label' => __( 'Communication with WordPress.org' ),
     289                                'value' => sprintf(
     290                                // translators: %1$s: The IP address WordPress.org resolves to. %2$s: The error returned by the lookup.
     291                                        __( 'Unable to reach WordPress.org at %1$s: %2$s' ),
     292                                        gethostbyname( 'wordpress.org' ),
     293                                        $wp_dotorg->get_error_message()
     294                                ),
     295                        );
     296                }
     297
     298                // Get drop-ins.
     299                $dropins            = get_dropins();
     300                $dropin_description = _get_dropins();
     301                foreach ( $dropins as $dropin_key => $dropin ) {
     302                        $info['wp-dropins']['fields'][ sanitize_key( $dropin_key ) ] = array(
     303                                'label' => $dropin_key,
     304                                'value' => $dropin_description[ $dropin_key ][0],
     305                        );
     306                }
     307
     308                // Populate the media fields.
     309                $info['wp-media']['fields']['image_editor'] = array(
     310                        'label' => __( 'Active editor' ),
     311                        'value' => _wp_image_editor_choose(),
     312                );
     313
     314                // Get ImageMagic information, if available.
     315                if ( class_exists( 'Imagick' ) ) {
     316                        // Save the Imagick instance for later use.
     317                        $imagick         = new Imagick();
     318                        $imagick_version = $imagick->getVersion();
     319                } else {
     320                        $imagick_version = __( 'Imagick not available' );
     321                }
     322                $info['wp-media']['fields']['imagick_module_version'] = array(
     323                        'label' => __( 'Imagick Module Version' ),
     324                        'value' => ( is_array( $imagick_version ) ? $imagick_version['versionNumber'] : $imagick_version ),
     325                );
     326                $info['wp-media']['fields']['imagemagick_version'] = array(
     327                        'label' => __( 'ImageMagick Version' ),
     328                        'value' => ( is_array( $imagick_version ) ? $imagick_version['versionString'] : $imagick_version ),
     329                );
     330
     331                // If Imagick is used as our editor, provide some more information about its limitations.
     332                if ( 'WP_Image_Editor_Imagick' === _wp_image_editor_choose() && isset( $imagick ) && $imagick instanceof Imagick ) {
     333                        $limits = array(
     334                                'area'   => ( defined( 'imagick::RESOURCETYPE_AREA' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_AREA ) ) : __( 'Not Available' ) ),
     335                                'disk'   => ( defined( 'imagick::RESOURCETYPE_DISK' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_DISK ) : __( 'Not Available' ) ),
     336                                'file'   => ( defined( 'imagick::RESOURCETYPE_FILE' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_FILE ) : __( 'Not Available' ) ),
     337                                'map'    => ( defined( 'imagick::RESOURCETYPE_MAP' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MAP ) ) : __( 'Not Available' ) ),
     338                                'memory' => ( defined( 'imagick::RESOURCETYPE_MEMORY' ) ? size_format( $imagick->getResourceLimit( imagick::RESOURCETYPE_MEMORY ) ) : __( 'Not Available' ) ),
     339                                'thread' => ( defined( 'imagick::RESOURCETYPE_THREAD' ) ? $imagick->getResourceLimit( imagick::RESOURCETYPE_THREAD ) : __( 'Not Available' ) ),
     340                        );
     341
     342                        $info['wp-media']['fields']['imagick_limits'] = array(
     343                                'label' => __( 'Imagick Resource Limits' ),
     344                                'value' => $limits,
     345                        );
     346                }
     347
     348                // Get GD information, if available.
     349                if ( function_exists( 'gd_info' ) ) {
     350                        $gd = gd_info();
     351                } else {
     352                        $gd = false;
     353                }
     354                $info['wp-media']['fields']['gd_version'] = array(
     355                        'label' => __( 'GD Version' ),
     356                        'value' => ( is_array( $gd ) ? $gd['GD Version'] : __( 'GD not available' ) ),
     357                );
     358
     359                // Get Ghostscript information, if available.
     360                if ( function_exists( 'exec' ) ) {
     361                        $gs = exec( 'gs --version' );
     362                        $gs = ( ! empty( $gs ) ? $gs : __( 'Not available' ) );
     363                } else {
     364                        $gs = __( 'Unable to determine if Ghostscript is installed' );
     365                }
     366                $info['wp-media']['fields']['ghostscript_version'] = array(
     367                        'label' => __( 'Ghostscript Version' ),
     368                        'value' => $gs,
     369                );
     370
     371                // Populate the server debug fields.
     372                $info['wp-server']['fields']['server_architecture'] = array(
     373                        'label' => __( 'Server architecture' ),
     374                        'value' => ( ! function_exists( 'php_uname' ) ? __( 'Unable to determine server architecture' ) : sprintf( '%s %s %s', php_uname( 's' ), php_uname( 'r' ), php_uname( 'm' ) ) ),
     375                );
     376                $info['wp-server']['fields']['httpd_software'] = array(
     377                        'label' => __( 'Web Server Software' ),
     378                        'value' => ( isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] : __( 'Unable to determine what web server software is used' ) ),
     379                );
     380                $info['wp-server']['fields']['php_version'] = array(
     381                        'label' => __( 'PHP Version' ),
     382                        'value' => ( ! function_exists( 'phpversion' ) ? __( 'Unable to determine PHP version' ) : sprintf(
     383                                '%s %s',
     384                                phpversion(),
     385                                ( 64 === PHP_INT_SIZE * 8 ? __( '(Supports 64bit values)' ) : __( '(Does not support 64bit values)' ) )
     386                        )
     387                        ),
     388                );
     389                $info['wp-server']['fields']['php_sapi'] = array(
     390                        'label' => __( 'PHP SAPI' ),
     391                        'value' => ( ! function_exists( 'php_sapi_name' ) ? __( 'Unable to determine PHP SAPI' ) : php_sapi_name() ),
     392                );
     393
     394                if ( ! function_exists( 'ini_get' ) ) {
     395                        $info['wp-server']['fields']['ini_get'] = array(
     396                                'label' => __( 'Server settings' ),
     397                                'value' => __( 'Unable to determine some settings as the ini_get() function has been disabled' ),
     398                        );
     399                } else {
     400                        $info['wp-server']['fields']['max_input_variables'] = array(
     401                                'label' => __( 'PHP max input variables' ),
     402                                'value' => ini_get( 'max_input_vars' ),
     403                        );
     404                        $info['wp-server']['fields']['time_limit'] = array(
     405                                'label' => __( 'PHP time limit' ),
     406                                'value' => ini_get( 'max_execution_time' ),
     407                        );
     408                        $info['wp-server']['fields']['memory_limit'] = array(
     409                                'label' => __( 'PHP memory limit' ),
     410                                'value' => ini_get( 'memory_limit' ),
     411                        );
     412                        $info['wp-server']['fields']['max_input_time'] = array(
     413                                'label' => __( 'Max input time' ),
     414                                'value' => ini_get( 'max_input_time' ),
     415                        );
     416                        $info['wp-server']['fields']['upload_max_size'] = array(
     417                                'label' => __( 'Upload max filesize' ),
     418                                'value' => ini_get( 'upload_max_filesize' ),
     419                        );
     420                        $info['wp-server']['fields']['php_post_max_size'] = array(
     421                                'label' => __( 'PHP post max size' ),
     422                                'value' => ini_get( 'post_max_size' ),
     423                        );
     424                }
     425
     426                if ( function_exists( 'curl_version' ) ) {
     427                        $curl                          = curl_version();
     428                        $info['wp-server']['fields']['curl_version'] = array(
     429                                'label' => __( 'cURL Version' ),
     430                                'value' => sprintf( '%s %s', $curl['version'], $curl['ssl_version'] ),
     431                        );
     432                } else {
     433                        $info['wp-server']['fields']['curl_version'] = array(
     434                                'label' => __( 'cURL Version' ),
     435                                'value' => __( 'Your server does not support cURL' ),
     436                        );
     437                }
     438
     439                $info['wp-server']['fields']['suhosin'] = array(
     440                        'label' => __( 'SUHOSIN installed' ),
     441                        'value' => ( ( extension_loaded( 'suhosin' ) || ( defined( 'SUHOSIN_PATCH' ) && constant( 'SUHOSIN_PATCH' ) ) ) ? __( 'Yes' ) : __( 'No' ) ),
     442                );
     443
     444                $info['wp-server']['fields']['imagick_availability'] = array(
     445                        'label' => __( 'Is the Imagick library available' ),
     446                        'value' => ( extension_loaded( 'imagick' ) ? __( 'Yes' ) : __( 'No' ) ),
     447                );
     448
     449                // Check if a .htaccess file exists.
     450                if ( is_file( ABSPATH . '/.htaccess' ) ) {
     451                        // If the file exists, grab the content of it.
     452                        $htaccess_content = file_get_contents( ABSPATH . '/.htaccess' );
     453
     454                        // Filter away the core WordPress rules.
     455                        $filtered_htaccess_content = trim( preg_replace( '/\# BEGIN WordPress[\s\S]+?# END WordPress/si', '', $htaccess_content ) );
     456
     457                        $info['wp-server']['fields']['htaccess_extra_rules'] = array(
     458                                'label' => __( 'htaccess rules' ),
     459                                'value' => ( ! empty( $filtered_htaccess_content ) ? __( 'Custom rules have been added to your htaccess file' ) : __( 'Your htaccess file only contains core WordPress features' ) ),
     460                        );
     461                }
     462
     463                // Populate the database debug fields.
     464                if ( is_resource( $wpdb->dbh ) ) {
     465                        // Old mysql extension.
     466                        $extension = 'mysql';
     467                } elseif ( is_object( $wpdb->dbh ) ) {
     468                        // mysqli or PDO.
     469                        $extension = get_class( $wpdb->dbh );
     470                } else {
     471                        // Unknown sql extension.
     472                        $extension = null;
     473                }
     474
     475                if ( method_exists( $wpdb, 'db_version' ) ) {
     476                        if ( $wpdb->use_mysqli ) {
     477                                // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_server_info
     478                                $server = mysqli_get_server_info( $wpdb->dbh );
     479                        } else {
     480                                // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_server_info
     481                                $server = mysql_get_server_info( $wpdb->dbh );
     482                        }
     483                } else {
     484                        $server = null;
     485                }
     486
     487                if ( isset( $wpdb->use_mysqli ) && $wpdb->use_mysqli ) {
     488                        $client_version = $wpdb->dbh->client_info;
     489                } else {
     490                        // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_client_info
     491                        if ( preg_match( '|[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}|', mysql_get_client_info(), $matches ) ) {
     492                                $client_version = $matches[0];
     493                        } else {
     494                                $client_version = null;
     495                        }
     496                }
     497
     498                $info['wp-database']['fields']['extension'] = array(
     499                        'label' => __( 'Extension' ),
     500                        'value' => $extension,
     501                );
     502                $info['wp-database']['fields']['server_version'] = array(
     503                        'label' => __( 'Server version' ),
     504                        'value' => $server,
     505                );
     506                $info['wp-database']['fields']['client_version'] = array(
     507                        'label' => __( 'Client version' ),
     508                        'value' => $client_version,
     509                );
     510                $info['wp-database']['fields']['database_user'] = array(
     511                        'label'   => __( 'Database user' ),
     512                        'value'   => $wpdb->dbuser,
     513                        'private' => true,
     514                );
     515                $info['wp-database']['fields']['database_host'] = array(
     516                        'label'   => __( 'Database host' ),
     517                        'value'   => $wpdb->dbhost,
     518                        'private' => true,
     519                );
     520                $info['wp-database']['fields']['database_name'] = array(
     521                        'label'   => __( 'Database name' ),
     522                        'value'   => $wpdb->dbname,
     523                        'private' => true,
     524                );
     525                $info['wp-database']['fields']['database_prefix'] = array(
     526                        'label'   => __( 'Database prefix' ),
     527                        'value'   => $wpdb->prefix,
     528                        'private' => true,
     529                );
     530
     531                // List must use plugins if there are any.
     532                $mu_plugins = get_mu_plugins();
     533
     534                foreach ( $mu_plugins as $plugin_path => $plugin ) {
     535                        $plugin_version = $plugin['Version'];
     536                        $plugin_author  = $plugin['Author'];
     537
     538                        $plugin_version_string = __( 'No version or author information available' );
     539
     540                        if ( ! empty( $plugin_version ) && ! empty( $plugin_author ) ) {
     541                                // translators: %1$s: Plugin version number. %2$s: Plugin author name.
     542                                $plugin_version_string = sprintf( __( 'Version %1$s by %2$s' ), $plugin_version, $plugin_author );
     543                        }
     544                        if ( empty( $plugin_version ) && ! empty( $plugin_author ) ) {
     545                                // translators: %s: Plugin author name.
     546                                $plugin_version_string = sprintf( __( 'By %s' ), $plugin_author );
     547                        }
     548                        if ( ! empty( $plugin_version ) && empty( $plugin_author ) ) {
     549                                // translators: %s: Plugin version number.
     550                                $plugin_version_string = sprintf( __( 'Version %s' ), $plugin_version );
     551                        }
     552
     553                        $info['wp-mu-plugins']['fields'][ sanitize_key( $plugin['Name'] ) ] = array(
     554                                'label' => $plugin['Name'],
     555                                'value' => $plugin_version_string,
     556                        );
     557                }
     558
     559                // List all available plugins.
     560                $plugins        = get_plugins();
     561                $plugin_updates = get_plugin_updates();
     562
     563                foreach ( $plugins as $plugin_path => $plugin ) {
     564                        $plugin_part = ( is_plugin_active( $plugin_path ) ) ? 'wp-plugins-active' : 'wp-plugins-inactive';
     565
     566                        $plugin_version = $plugin['Version'];
     567                        $plugin_author  = $plugin['Author'];
     568
     569                        $plugin_version_string = __( 'No version or author information available' );
     570
     571                        if ( ! empty( $plugin_version ) && ! empty( $plugin_author ) ) {
     572                                // translators: %1$s: Plugin version number. %2$s: Plugin author name.
     573                                $plugin_version_string = sprintf( __( 'Version %1$s by %2$s' ), $plugin_version, $plugin_author );
     574                        }
     575                        if ( empty( $plugin_version ) && ! empty( $plugin_author ) ) {
     576                                // translators: %s: Plugin author name.
     577                                $plugin_version_string = sprintf( __( 'By %s' ), $plugin_author );
     578                        }
     579                        if ( ! empty( $plugin_version ) && empty( $plugin_author ) ) {
     580                                // translators: %s: Plugin version number.
     581                                $plugin_version_string = sprintf( __( 'Version %s' ), $plugin_version );
     582                        }
     583
     584                        if ( array_key_exists( $plugin_path, $plugin_updates ) ) {
     585                                // translators: %s: Latest plugin version number.
     586                                $plugin_update_needed = ' ' . sprintf( __( '( Latest version: %s )' ), $plugin_updates[ $plugin_path ]->update->new_version );
     587                        } else {
     588                                $plugin_update_needed = '';
     589                        }
     590
     591                        $info[ $plugin_part ]['fields'][ sanitize_key( $plugin['Name'] ) ] = array(
     592                                'label' => $plugin['Name'],
     593                                'value' => $plugin_version_string . $plugin_update_needed,
     594                        );
     595                }
     596
     597                // Populate the section for the currently active theme.
     598                global $_wp_theme_features;
     599                $theme_features = array();
     600                if ( ! empty( $_wp_theme_features ) ) {
     601                        foreach ( $_wp_theme_features as $feature => $options ) {
     602                                $theme_features[] = $feature;
     603                        }
     604                }
     605
     606                $active_theme  = wp_get_theme();
     607                $theme_updates = get_theme_updates();
     608
     609                if ( array_key_exists( $active_theme->stylesheet, $theme_updates ) ) {
     610                        // translators: %s: Latest theme version number.
     611                        $theme_update_needed_active = ' ' . sprintf( __( '( Latest version: %s )' ), $theme_updates[ $active_theme->stylesheet ]->update['new_version'] );
     612                } else {
     613                        $theme_update_needed_active = '';
     614                }
     615
     616                $info['wp-active-theme']['fields'] = array(
     617                        'name' => array(
     618                                'label' => __( 'Name' ),
     619                                // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
     620                                'value' => $active_theme->Name,
     621                        ),
     622                        'version' => array(
     623                                'label' => __( 'Version' ),
     624                                // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
     625                                'value' => $active_theme->Version . $theme_update_needed_active,
     626                        ),
     627                        'author' => array(
     628                                'label' => __( 'Author' ),
     629                                // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
     630                                'value' => wp_kses( $active_theme->Author, array() ),
     631                        ),
     632                        'author_website' => array(
     633                                'label' => __( 'Author website' ),
     634                                'value' => ( $active_theme->offsetGet( 'Author URI' ) ? $active_theme->offsetGet( 'Author URI' ) : __( 'Undefined' ) ),
     635                        ),
     636                        'parent_theme' => array(
     637                                'label' => __( 'Parent theme' ),
     638                                'value' => ( $active_theme->parent_theme ? $active_theme->parent_theme : __( 'Not a child theme' ) ),
     639                        ),
     640                        'theme_features' => array(
     641                                'label' => __( 'Supported theme features' ),
     642                                'value' => implode( ', ', $theme_features ),
     643                        ),
     644                );
     645
     646                // Populate a list of all themes available in the install.
     647                $all_themes = wp_get_themes();
     648
     649                foreach ( $all_themes as $theme_slug => $theme ) {
     650                        // Ignore the currently active theme from the list of all themes.
     651                        if ( $active_theme->stylesheet == $theme_slug ) {
     652                                continue;
     653                        }
     654                        // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
     655                        $theme_version = $theme->Version;
     656                        // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
     657                        $theme_author = $theme->Author;
     658
     659                        $theme_version_string = __( 'No version or author information available' );
     660
     661                        if ( ! empty( $theme_version ) && ! empty( $theme_author ) ) {
     662                                // translators: %1$s: Theme version number. %2$s: Theme author name.
     663                                $theme_version_string = sprintf( __( 'Version %1$s by %2$s' ), $theme_version, wp_kses( $theme_author, array() ) );
     664                        }
     665                        if ( empty( $theme_version ) && ! empty( $theme_author ) ) {
     666                                // translators: %s: Theme author name.
     667                                $theme_version_string = sprintf( __( 'By %s' ), wp_kses( $theme_author, array() ) );
     668                        }
     669                        if ( ! empty( $theme_version ) && empty( $theme_author ) ) {
     670                                // translators: %s: Theme version number.
     671                                $theme_version_string = sprintf( __( 'Version %s' ), $theme_version );
     672                        }
     673
     674                        if ( array_key_exists( $theme_slug, $theme_updates ) ) {
     675                                // translators: %s: Latest theme version number.
     676                                $theme_update_needed = ' ' . sprintf( __( '( Latest version: %s )' ), $theme_updates[ $theme_slug ]->update['new_version'] );
     677                        } else {
     678                                $theme_update_needed = '';
     679                        }
     680
     681                        $info['wp-themes']['fields'][ sanitize_key( $theme->Name ) ] = array(
     682                                'label' => sprintf(
     683                                // translators: %1$s: Theme name. %2$s: Theme slug.
     684                                        __( '%1$s (%2$s)' ),
     685                                        // phpcs:ignore WordPress.NamingConventions.ValidVariableName.NotSnakeCaseMemberVar
     686                                        $theme->Name,
     687                                        $theme_slug
     688                                ),
     689                                'value' => $theme_version_string . $theme_update_needed,
     690                        );
     691                }
     692
     693                // Add more filesystem checks
     694                if ( defined( 'WPMU_PLUGIN_DIR' ) && is_dir( WPMU_PLUGIN_DIR ) ) {
     695                        $info['wp-filesystem']['fields']['mu_plugin_directory'] = array(
     696                                'label' => __( 'The Must Use Plugins directory' ),
     697                                'value' => ( wp_is_writable( WPMU_PLUGIN_DIR ) ? __( 'Writable' ) : __( 'Not writable' ) ),
     698                        );
     699                }
     700
     701                /**
     702                 * Add or modify new debug sections.
     703                 *
     704                 * Plugin or themes may wish to introduce their own debug information without creating additional admin pages for this
     705                 * kind of information as it is rarely needed, they can then utilize this filter to introduce their own sections.
     706                 *
     707                 * Array keys added by core are all prefixed with `wp-`, plugins and themes are encouraged to use their own slug as
     708                 * a prefix, both for consistency as well as avoiding key collisions.
     709                 *
     710                 * @since 5.2.0
     711                 *
     712                 * @param array $args {
     713                 *     The debug information to be added to the core information page.
     714                 *
     715                 *     @type string  $label        The title for this section of the debug output.
     716                 *     @type string  $description  Optional. A description for your information section which may contain basic HTML
     717                 *                                 markup: `em`, `strong` and `a` for linking to documentation or putting emphasis.
     718                 *     @type boolean $show_count   Optional. If set to `true` the amount of fields will be included in the title for
     719                 *                                 this section.
     720                 *     @type boolean $private      Optional. If set to `true` the section and all associated fields will be excluded
     721                 *                                 from the copy-paste text area.
     722                 *     @type array   $fields {
     723                 *         An associative array containing the data to be displayed.
     724                 *
     725                 *         @type string  $label    The label for this piece of information.
     726                 *         @type string  $value    The output that is of interest for this field.
     727                 *         @type boolean $private  Optional. If set to `true` the field will not be included in the copy-paste text area
     728                 *                                 on top of the page, allowing you to show, for example, API keys here.
     729                 *     }
     730                 * }
     731                 */
     732                $info = apply_filters( 'debug_information', $info );
     733
     734                if ( ! empty( $locale ) ) {
     735                        // Change the language used for translations
     736                        if ( function_exists( 'restore_previous_locale' ) && $switched_locale ) {
     737                                restore_previous_locale();
     738                        }
     739                }
     740
     741                return $info;
     742        }
     743
     744        /**
     745         * Print the formatted variation of the information gathered for debugging, in a manner
     746         * suitable for a text area that can be instantly copied to a forum or support ticket.
     747         *
     748         * @param array $info_array
     749         *
     750         * @return void
     751         */
     752        public static function textarea_format( $info_array ) {
     753                echo "`\n";
     754
     755                foreach ( $info_array as $section => $details ) {
     756                        // Skip this section if there are no fields, or the section has been declared as private.
     757                        if ( empty( $details['fields'] ) || ( isset( $details['private'] ) && $details['private'] ) ) {
     758                                continue;
     759                        }
     760
     761                        printf(
     762                                "### %s%s ###\n\n",
     763                                $details['label'],
     764                                ( isset( $details['show_count'] ) && $details['show_count'] ? sprintf( ' (%d)', count( $details['fields'] ) ) : '' )
     765                        );
     766
     767                        foreach ( $details['fields'] as $field ) {
     768                                if ( isset( $field['private'] ) && true === $field['private'] ) {
     769                                        continue;
     770                                }
     771
     772                                $values = $field['value'];
     773                                if ( is_array( $field['value'] ) ) {
     774                                        $values = '';
     775
     776                                        foreach ( $field['value'] as $name => $value ) {
     777                                                $values .= sprintf(
     778                                                        "\n\t%s: %s",
     779                                                        $name,
     780                                                        $value
     781                                                );
     782                                        }
     783                                }
     784
     785                                printf(
     786                                        "%s: %s\n",
     787                                        $field['label'],
     788                                        $values
     789                                );
     790                        }
     791                        echo "\n";
     792                }
     793                echo '`';
     794        }
     795
     796        /**
     797         * Return the size of a directory, including all subdirectories.
     798         *
     799         * @param string $path
     800         *
     801         * @return int
     802         */
     803        public static function get_directory_size( $path ) {
     804                $size = 0;
     805
     806                foreach ( new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $path ) ) as $file ) {
     807                        $size += $file->getSize();
     808                }
     809
     810                return $size;
     811        }
     812
     813        /**
     814         * Fetch the total size of all the database tables for the active database user.
     815         *
     816         * @return int
     817         */
     818        public static function get_database_size() {
     819                global $wpdb;
     820                $size = 0;
     821                $rows = $wpdb->get_results( 'SHOW TABLE STATUS', ARRAY_A );
     822
     823                if ( $wpdb->num_rows > 0 ) {
     824                        foreach ( $rows as $row ) {
     825                                $size += $row['Data_length'] + $row['Index_length'];
     826                        }
     827                }
     828
     829                return $size;
     830        }
     831}
  • src/wp-admin/includes/class-wp-site-health-auto-updates.php

     
     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// Make sure the file is not directly accessible.
     11if ( ! defined( 'ABSPATH' ) ) {
     12        die( 'We\'re sorry, but you can not directly access this file.' );
     13}
     14
     15/**
     16 * Class Site_Health_Auto_Updates
     17 */
     18class Site_Health_Auto_Updates {
     19        /**
     20         * Health_Check_Auto_Updates constructor.
     21         *
     22         * @uses Health_Check::init()
     23         *
     24         * @return void
     25         */
     26        public function __construct() {
     27                $this->init();
     28        }
     29
     30        /**
     31         * Initiate the plugin class.
     32         *
     33         * @return void
     34         */
     35        public function init() {
     36                include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
     37        }
     38
     39        /**
     40         * Run tests to determine if auto-updates can run.
     41         *
     42         * @uses get_class_methods()
     43         * @uses substr()
     44         * @uses call_user_func()
     45         *
     46         * @return array
     47         */
     48        public function run_tests() {
     49                $tests = array();
     50
     51                foreach ( get_class_methods( $this ) as $method ) {
     52                        if ( 'test_' !== substr( $method, 0, 5 ) ) {
     53                                continue;
     54                        }
     55
     56                        $result = call_user_func( array( $this, $method ) );
     57
     58                        if ( false === $result || null === $result ) {
     59                                continue;
     60                        }
     61
     62                        $result = (object) $result;
     63
     64                        if ( empty( $result->severity ) ) {
     65                                $result->severity = 'warning';
     66                        }
     67
     68                        $tests[ $method ] = $result;
     69                }
     70
     71                return $tests;
     72        }
     73
     74        /**
     75         * Test if file modifications are possible.
     76         *
     77         * @uses defined()
     78         * @uses sprintf()
     79         * @uses esc_html__()
     80         *
     81         * @return array
     82         */
     83        function test_constant_FILE_MODS() {
     84                if ( defined( 'DISALLOW_FILE_MODS' ) && DISALLOW_FILE_MODS ) {
     85                        return array(
     86                                'desc'     => sprintf(
     87                                        /* translators: %s: Name of the constant used. */
     88                                        esc_html__( 'The %s constant is defined and enabled.' ),
     89                                        '<code>DISALLOW_FILE_MODS</code>'
     90                                ),
     91                                'severity' => 'fail',
     92                        );
     93                }
     94        }
     95
     96        /**
     97         * Check if automatic updates are disabled with a constant.
     98         *
     99         * @uses defined()
     100         * @uses sprintf()
     101         * @uses esc_html__()
     102         *
     103         * @return array
     104         */
     105        function test_constant_AUTOMATIC_UPDATER_DISABLED() {
     106                if ( defined( 'AUTOMATIC_UPDATER_DISABLED' ) && AUTOMATIC_UPDATER_DISABLED ) {
     107                        return array(
     108                                'desc'     => sprintf(
     109                                        /* translators: %s: Name of the constant used. */
     110                                        esc_html__( 'The %s constant is defined and enabled.' ),
     111                                        '<code>AUTOMATIC_UPDATER_DISABLED</code>'
     112                                ),
     113                                'severity' => 'fail',
     114                        );
     115                }
     116        }
     117
     118        /**
     119         * Check if automatic core updates are disabled with a constant.
     120         *
     121         * @uses defined()
     122         * @uses sprintf()
     123         * @uses esc_html__()
     124         *
     125         * @return array
     126         */
     127        function test_constant_WP_AUTO_UPDATE_CORE() {
     128                if ( defined( 'WP_AUTO_UPDATE_CORE' ) && false === WP_AUTO_UPDATE_CORE ) {
     129                        return array(
     130                                'desc'     => sprintf(
     131                                        /* translators: %s: Name of the constant used. */
     132                                        esc_html__( 'The %s constant is defined and enabled.' ),
     133                                        '<code>WP_AUTO_UPDATE_CORE</code>'
     134                                ),
     135                                'severity' => 'fail',
     136                        );
     137                }
     138        }
     139
     140        /**
     141         * Check if updates are intercepted by a filter.
     142         *
     143         * @uses has_filter()
     144         * @uses sprintf()
     145         * @uses esc_html__()
     146         *
     147         * @return array
     148         */
     149        function test_wp_version_check_attached() {
     150                $cookies = wp_unslash( $_COOKIE );
     151                $timeout = 10;
     152                $headers = array(
     153                        'Cache-Control' => 'no-cache',
     154                );
     155
     156                // Include Basic auth in loopback requests.
     157                if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
     158                        $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
     159                }
     160
     161                $url = add_query_arg( array(
     162                        'health-check-test-wp_version_check' => true,
     163                ), admin_url() );
     164
     165                $test = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
     166
     167                $response = wp_remote_retrieve_body( $test );
     168
     169                if ( 'yes' !== $response ) {
     170                        return array(
     171                                'desc'     => sprintf(
     172                                        /* translators: %s: Name of the filter used. */
     173                                        esc_html__( 'A plugin has prevented updates by disabling %s.' ),
     174                                        '<code>wp_version_check()</code>'
     175                                ),
     176                                'severity' => 'fail',
     177                        );
     178                }
     179        }
     180
     181        /**
     182         * Check if automatic updates are disabled by a filter.
     183         *
     184         * @uses apply_filters()
     185         * @uses sprintf()
     186         * @uses esc_html__()
     187         *
     188         * @return array
     189         */
     190        function test_filters_automatic_updater_disabled() {
     191                if ( apply_filters( 'automatic_updater_disabled', false ) ) {
     192                        return array(
     193                                'desc'     => sprintf(
     194                                        /* translators: %s: Name of the filter used. */
     195                                        esc_html__( 'The %s filter is enabled.' ),
     196                                        '<code>automatic_updater_disabled</code>'
     197                                ),
     198                                'severity' => 'fail',
     199                        );
     200                }
     201        }
     202
     203        /**
     204         * Check if automatic updates have tried to run, but failed, previously.
     205         *
     206         * @uses get_site_option()
     207         * @uses esc_html__()
     208         * @uses sprintf()
     209         *
     210         * @return array|bool
     211         */
     212        function test_if_failed_update() {
     213                $failed = get_site_option( 'auto_core_update_failed' );
     214
     215                if ( ! $failed ) {
     216                        return false;
     217                }
     218
     219                if ( ! empty( $failed['critical'] ) ) {
     220                        $desc  = esc_html__( 'A previous automatic background update ended with a critical failure, so updates are now disabled.' );
     221                        $desc .= ' ' . esc_html__( 'You would have received an email because of this.' );
     222                        $desc .= ' ' . esc_html__( "When you've been able to update using the \"Update Now\" button on Dashboard > Updates, we'll clear this error for future update attempts." );
     223                        $desc .= ' ' . sprintf(
     224                                /* translators: %s: Code of error shown. */
     225                                esc_html__( 'The error code was %s.' ),
     226                                '<code>' . $failed['error_code'] . '</code>'
     227                        );
     228                        return array(
     229                                'desc'     => $desc,
     230                                'severity' => 'warning',
     231                        );
     232                }
     233
     234                $desc = esc_html__( 'A previous automatic background update could not occur.' );
     235                if ( empty( $failed['retry'] ) ) {
     236                        $desc .= ' ' . esc_html__( 'You would have received an email because of this.' );
     237                }
     238
     239                $desc .= ' ' . esc_html__( "We'll try again with the next release." );
     240                $desc .= ' ' . sprintf(
     241                        /* translators: %s: Code of error shown. */
     242                        esc_html__( 'The error code was %s.' ),
     243                        '<code>' . $failed['error_code'] . '</code>'
     244                );
     245                return array(
     246                        'desc'     => $desc,
     247                        'severity' => 'warning',
     248                );
     249        }
     250
     251        /**
     252         * Check if WordPress is controlled by a VCS (Git, Subversion etc).
     253         *
     254         * @uses dirname()
     255         * @uses array_unique()
     256         * @uses is_dir()
     257         * @uses rtrim()
     258         * @uses apply_filters()
     259         * @uses sprintf()
     260         * @uses esc_html__()
     261         *
     262         * @param string $context The path to check from.
     263         *
     264         * @return array
     265         */
     266        function _test_is_vcs_checkout( $context ) {
     267                $context_dirs = array( ABSPATH );
     268                $vcs_dirs     = array( '.svn', '.git', '.hg', '.bzr' );
     269                $check_dirs   = array();
     270
     271                foreach ( $context_dirs as $context_dir ) {
     272                        // Walk up from $context_dir to the root.
     273                        do {
     274                                $check_dirs[] = $context_dir;
     275
     276                                // Once we've hit '/' or 'C:\', we need to stop. dirname will keep returning the input here.
     277                                if ( dirname( $context_dir ) == $context_dir ) {
     278                                        break;
     279                                }
     280
     281                                // Continue one level at a time.
     282                        } while ( $context_dir = dirname( $context_dir ) );
     283                }
     284
     285                $check_dirs = array_unique( $check_dirs );
     286
     287                // Search all directories we've found for evidence of version control.
     288                foreach ( $vcs_dirs as $vcs_dir ) {
     289                        foreach ( $check_dirs as $check_dir ) {
     290                                // phpcs:ignore
     291                                if ( $checkout = @is_dir( rtrim( $check_dir, '\\/' ) . "/$vcs_dir" ) ) {
     292                                        break 2;
     293                                }
     294                        }
     295                }
     296
     297                if ( $checkout && ! apply_filters( 'automatic_updates_is_vcs_checkout', true, $context ) ) {
     298                        return array(
     299                                'desc'     => sprintf(
     300                                        // translators: %1$s: Folder name. %2$s: Version control directory. %3$s: Filter name.
     301                                        esc_html__( 'The folder %1$s was detected as being under version control (%2$s), but the %3$s filter is allowing updates.' ),
     302                                        '<code>' . $check_dir . '</code>',
     303                                        "<code>$vcs_dir</code>",
     304                                        '<code>automatic_updates_is_vcs_checkout</code>'
     305                                ),
     306                                'severity' => 'info',
     307                        );
     308                }
     309
     310                if ( $checkout ) {
     311                        return array(
     312                                'desc'     => sprintf(
     313                                        // translators: %1$s: Folder name. %2$s: Version control directory.
     314                                        esc_html__( 'The folder %1$s was detected as being under version control (%2$s).' ),
     315                                        '<code>' . $check_dir . '</code>',
     316                                        "<code>$vcs_dir</code>"
     317                                ),
     318                                'severity' => 'fail',
     319                        );
     320                }
     321
     322                return array(
     323                        'desc'     => esc_html__( 'No version control systems were detected.' ),
     324                        'severity' => 'pass',
     325                );
     326        }
     327
     328        /**
     329         * Check if the absolute path is under Version Control.
     330         *
     331         * @uses Health_Check_Auto_Updates::_test_is_vcs_checkout()
     332         *
     333         * @return array
     334         */
     335        function test_vcs_ABSPATH() {
     336                $result = $this->_test_is_vcs_checkout( ABSPATH );
     337                return $result;
     338        }
     339
     340        /**
     341         * Check if we can access files without providing credentials.
     342         *
     343         * @uses Automatic_Upgrader_Skin
     344         * @uses Automatic_Upgrader_Skin::request_filesystem_credentials()
     345         * @uses esc_html__()
     346         *
     347         * @return array
     348         */
     349        function test_check_wp_filesystem_method() {
     350                $skin    = new Automatic_Upgrader_Skin;
     351                $success = $skin->request_filesystem_credentials( false, ABSPATH );
     352
     353                if ( ! $success ) {
     354                        $desc  = esc_html__( 'Your installation of WordPress prompts for FTP credentials to perform updates.' );
     355                        $desc .= ' ' . esc_html__( '(Your site is performing updates over FTP due to file ownership. Talk to your hosting company.)' );
     356
     357                        return array(
     358                                'desc'     => $desc,
     359                                'severity' => 'fail',
     360                        );
     361                }
     362
     363                return array(
     364                        'desc'     => esc_html__( "Your installation of WordPress doesn't require FTP credentials to perform updates." ),
     365                        'severity' => 'pass',
     366                );
     367        }
     368
     369        /**
     370         * Check if core files are writeable by the web user/group.
     371         *
     372         * @global $wp_filesystem
     373         *
     374         * @uses Automatic_Upgrader_Skin
     375         * @uses Automatic_Upgrader_Skin::request_filesystem_credentials()
     376         * @uses WP_Filesystem
     377         * @uses WP_Filesystem::method
     378         * @uses get_core_checksums()
     379         * @uses strpos()
     380         * @uses sprintf()
     381         * @uses esc_html__()
     382         * @uses array_keys()
     383         * @uses substr()
     384         * @uses file_exists()
     385         * @uses is_writable()
     386         * @uses count()
     387         * @uses array_slice()
     388         * @uses implode()
     389         *
     390         * @return array|bool
     391         */
     392        function test_all_files_writable() {
     393                global $wp_filesystem;
     394                include ABSPATH . WPINC . '/version.php'; // $wp_version; // x.y.z
     395
     396                $skin    = new Automatic_Upgrader_Skin;
     397                $success = $skin->request_filesystem_credentials( false, ABSPATH );
     398
     399                if ( ! $success ) {
     400                        return false;
     401                }
     402
     403                WP_Filesystem();
     404
     405                if ( 'direct' != $wp_filesystem->method ) {
     406                        return false;
     407                }
     408
     409                $checksums = get_core_checksums( $wp_version, 'en_US' );
     410                $dev       = ( false !== strpos( $wp_version, '-' ) );
     411                // Get the last stable version's files and test against that
     412                if ( ! $checksums && $dev ) {
     413                        $checksums = get_core_checksums( (float) $wp_version - 0.1, 'en_US' );
     414                }
     415
     416                // There aren't always checksums for development releases, so just skip the test if we still can't find any
     417                if ( ! $checksums && $dev ) {
     418                        return false;
     419                }
     420
     421                if ( ! $checksums ) {
     422                        $desc = sprintf(
     423                                // translators: %s: WordPress version
     424                                esc_html__( "Couldn't retrieve a list of the checksums for WordPress %s." ),
     425                                $wp_version
     426                        );
     427                        $desc .= ' ' . esc_html__( 'This could mean that connections are failing to WordPress.org.' );
     428                        return array(
     429                                'desc'     => $desc,
     430                                'severity' => 'warning',
     431                        );
     432                }
     433
     434                $unwritable_files = array();
     435                foreach ( array_keys( $checksums ) as $file ) {
     436                        if ( 'wp-content' == substr( $file, 0, 10 ) ) {
     437                                continue;
     438                        }
     439                        if ( ! file_exists( ABSPATH . '/' . $file ) ) {
     440                                continue;
     441                        }
     442                        if ( ! is_writable( ABSPATH . '/' . $file ) ) {
     443                                $unwritable_files[] = $file;
     444                        }
     445                }
     446
     447                if ( $unwritable_files ) {
     448                        if ( count( $unwritable_files ) > 20 ) {
     449                                $unwritable_files   = array_slice( $unwritable_files, 0, 20 );
     450                                $unwritable_files[] = '...';
     451                        }
     452                        return array(
     453                                'desc'     => esc_html__( 'Some files are not writable by WordPress:' ) . ' <ul><li>' . implode( '</li><li>', $unwritable_files ) . '</li></ul>',
     454                                'severity' => 'fail',
     455                        );
     456                } else {
     457                        return array(
     458                                'desc'     => esc_html__( 'All of your WordPress files are writable.' ),
     459                                'severity' => 'pass',
     460                        );
     461                }
     462        }
     463
     464        /**
     465         * Check if the install is using a development branch and can use nightly packages.
     466         *
     467         * @uses strpos()
     468         * @uses defined()
     469         * @uses sprintf()
     470         * @uses esc_html__()
     471         * @uses apply_filters()
     472         *
     473         * @return array|bool
     474         */
     475        function test_accepts_dev_updates() {
     476                include ABSPATH . WPINC . '/version.php'; // $wp_version; // x.y.z
     477                // Only for dev versions
     478                if ( false === strpos( $wp_version, '-' ) ) {
     479                        return false;
     480                }
     481
     482                if ( defined( 'WP_AUTO_UPDATE_CORE' ) && ( 'minor' === WP_AUTO_UPDATE_CORE || false === WP_AUTO_UPDATE_CORE ) ) {
     483                        return array(
     484                                'desc'     => sprintf(
     485                                        /* translators: %s: Name of the constant used. */
     486                                        esc_html__( 'WordPress development updates are blocked by the %s constant.' ),
     487                                        '<code>WP_AUTO_UPDATE_CORE</code>'
     488                                ),
     489                                'severity' => 'fail',
     490                        );
     491                }
     492
     493                if ( ! apply_filters( 'allow_dev_auto_core_updates', $wp_version ) ) {
     494                        return array(
     495                                'desc'     => sprintf(
     496                                        /* translators: %s: Name of the filter used. */
     497                                        esc_html__( 'WordPress development updates are blocked by the %s filter.' ),
     498                                        '<code>allow_dev_auto_core_updates</code>'
     499                                ),
     500                                'severity' => 'fail',
     501                        );
     502                }
     503        }
     504
     505        /**
     506         * Check if the site supports automatic minor updates.
     507         *
     508         * @uses defined()
     509         * @uses sprintf()
     510         * @uses esc_html__()
     511         * @uses apply_filters()
     512         *
     513         * @return array
     514         */
     515        function test_accepts_minor_updates() {
     516                if ( defined( 'WP_AUTO_UPDATE_CORE' ) && false === WP_AUTO_UPDATE_CORE ) {
     517                        return array(
     518                                'desc'     => sprintf(
     519                                        /* translators: %s: Name of the constant used. */
     520                                        esc_html__( 'WordPress security and maintenance releases are blocked by %s.' ),
     521                                        "<code>define( 'WP_AUTO_UPDATE_CORE', false );</code>"
     522                                ),
     523                                'severity' => 'fail',
     524                        );
     525                }
     526
     527                if ( ! apply_filters( 'allow_minor_auto_core_updates', true ) ) {
     528                        return array(
     529                                'desc'     => sprintf(
     530                                        /* translators: %s: Name of the filter used. */
     531                                        esc_html__( 'WordPress security and maintenance releases are blocked by the %s filter.' ),
     532                                        '<code>allow_minor_auto_core_updates</code>'
     533                                ),
     534                                'severity' => 'fail',
     535                        );
     536                }
     537        }
     538}
  • src/wp-admin/includes/class-wp-site-health.php

     
     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// Make sure the file is not directly accessible.
     11if ( ! defined( 'ABSPATH' ) ) {
     12        die( 'We\'re sorry, but you can not directly access this file.' );
     13}
     14
     15class WP_Site_Health {
     16        private $php_min_version_check = '5.2.4';
     17        private $php_supported_version_check = '5.6';
     18        private $php_rec_version_check = '7.3';
     19
     20        private $mysql_min_version_check;
     21        private $mysql_rec_version_check;
     22
     23        public  $mariadb                        = false;
     24        private $mysql_server_version           = null;
     25        private $health_check_mysql_rec_version = null;
     26
     27        public $schedules;
     28        public $crons;
     29        public $last_missed_cron = null;
     30
     31        public function __construct() {
     32                $this->init();
     33        }
     34
     35        public function init() {
     36                $this->prepare_sql_data();
     37
     38                add_action( 'wp_ajax_health-check-site-status', array( $this, 'site_status' ) );
     39
     40                add_action( 'wp_ajax_health-check-site-status-result', array( $this, 'site_status_result' ) );
     41
     42                add_action( 'wp_loaded', array( $this, 'check_wp_version_check_exists' ) );
     43
     44                add_action( 'admin_enqueue_scripts', array( $this, 'enqueues' ) );
     45        }
     46
     47        public function enqueues() {
     48                $screen = get_current_screen();
     49                if ( 'site-health' !== $screen->id ) {
     50                        return;
     51                }
     52
     53                $health_check_js_variables = array(
     54                        'screen' => $screen->id,
     55                        'string'      => array(
     56                                'please_wait'                        => esc_html__( 'Please wait...' ),
     57                                'copied'                             => esc_html__( 'Copied' ),
     58                                'running_tests'                      => esc_html__( 'Currently being tested...' ),
     59                                'site_health_complete'               => esc_html__( 'All site health tests have finished running.' ),
     60                                'site_info_show_copy'                => esc_html__( 'Show options for copying this information' ),
     61                                'site_info_hide_copy'                => esc_html__( 'Hide options for copying this information' ),
     62                                // translators: %s: The percentage score for the tests.
     63                                'site_health_complete_screen_reader' => esc_html__( 'All site health tests have finished running. Your site scored %s, and the results are now available on the page.' ),
     64                                'site_info_copied'                   => esc_html__( 'Site information has been added to your clipboard.' ),
     65                        ),
     66                        'nonce'       => array(
     67                                'site_status'                 => wp_create_nonce( 'health-check-site-status' ),
     68                                'site_status_result'          => wp_create_nonce( 'health-check-site-status-result' ),
     69                        ),
     70                        'site_status' => array(
     71                                'direct' => array(),
     72                                'async'  => array(),
     73                                'issues' => array(
     74                                        'good'        => 0,
     75                                        'recommended' => 0,
     76                                        'critical'    => 0,
     77                                ),
     78                        ),
     79                );
     80
     81                $issue_counts = get_transient( 'health-check-site-status-result' );
     82
     83                if ( false !== $issue_counts ) {
     84                        $issue_counts = json_decode( $issue_counts );
     85
     86                        $health_check_js_variables['site_status']['issues'] = $issue_counts;
     87                }
     88
     89                if ( 'site-health' === $screen->id && ! isset( $_GET['tab'] ) ) {
     90                        $tests = WP_Site_Health::get_tests();
     91                        foreach ( $tests['direct'] as $test ) {
     92                                $test_function = sprintf(
     93                                        'get_test_%s',
     94                                        $test['test']
     95                                );
     96
     97                                if ( method_exists( $this, $test_function ) && is_callable( array( $this, $test_function ) ) ) {
     98                                        $health_check_js_variables['site_status']['direct'][] = call_user_func( array( $this, $test_function ) );
     99                                } else {
     100                                        $health_check_js_variables['site_status']['direct'][] = call_user_func( $test['test'] );
     101                                }
     102                        }
     103
     104                        foreach ( $tests['async'] as $test ) {
     105                                $health_check_js_variables['site_status']['async'][] = array(
     106                                        'test'      => $test['test'],
     107                                        'completed' => false,
     108                                );
     109                        }
     110                }
     111
     112                wp_localize_script( 'site-health', 'SiteHealth', $health_check_js_variables );
     113        }
     114
     115        private function prepare_sql_data() {
     116                global $wpdb;
     117
     118                if ( method_exists( $wpdb, 'db_version' ) ) {
     119                        if ( $wpdb->use_mysqli ) {
     120                                // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_server_info
     121                                $mysql_server_type = mysqli_get_server_info( $wpdb->dbh );
     122                        } else {
     123                                // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_server_info
     124                                $mysql_server_type = mysql_get_server_info( $wpdb->dbh );
     125                        }
     126
     127                        $this->mysql_server_version = $wpdb->get_var( 'SELECT VERSION()' );
     128                }
     129
     130                $this->health_check_mysql_rec_version = '5.6';
     131
     132                if ( stristr( $mysql_server_type, 'mariadb' ) ) {
     133                        $this->mariadb                        = true;
     134                        $this->health_check_mysql_rec_version = '10.0';
     135                }
     136
     137                $this->mysql_min_version_check = version_compare( '5.0', $this->mysql_server_version, '<=' );
     138                $this->mysql_rec_version_check = version_compare( $this->health_check_mysql_rec_version, $this->mysql_server_version, '<=' );
     139        }
     140
     141        public function check_wp_version_check_exists() {
     142                if ( ! is_admin() || ! is_user_logged_in() || ! current_user_can( 'manage_options' ) || ! isset( $_GET['health-check-test-wp_version_check'] ) ) {
     143                        return;
     144                }
     145
     146                echo ( has_filter( 'wp_version_check', 'wp_version_check' ) ? 'yes' : 'no' );
     147
     148                die();
     149        }
     150
     151        public function site_status_result() {
     152                check_ajax_referer( 'health-check-site-status-result' );
     153
     154                if ( ! current_user_can( 'manage_options' ) ) {
     155                        wp_send_json_error();
     156                }
     157
     158                set_transient( 'health-check-site-status-result', wp_json_encode( $_POST['counts'] ) );
     159        }
     160
     161        public function site_status() {
     162                check_ajax_referer( 'health-check-site-status' );
     163
     164                if ( ! current_user_can( 'manage_options' ) ) {
     165                        wp_send_json_error();
     166                }
     167
     168                $function = sprintf(
     169                        'json_test_%s',
     170                        $_POST['feature']
     171                );
     172
     173                if ( ! method_exists( $this, $function ) || ! is_callable( array( $this, $function ) ) ) {
     174                        return;
     175                }
     176
     177                $call = call_user_func( array( $this, $function ) );
     178        }
     179
     180        /**
     181         * Tests for WordPress version and outputs it.
     182         *
     183         * @return array
     184         */
     185        public function get_test_wordpress_version() {
     186                $result = array(
     187                        'label'       => '',
     188                        'status'      => '',
     189                        'badge'       => array(
     190                                'label' => 'Security',
     191                                'color' => 'red',
     192                        ),
     193                        'description' => '',
     194                        'actions'     => '',
     195                        'test'        => 'wordpress_version',
     196                );
     197
     198                $core_current_version = get_bloginfo( 'version' );
     199                $core_updates         = get_core_updates();
     200
     201                if ( ! is_array( $core_updates ) ) {
     202                        $result['status'] = 'recommended';
     203
     204                        $result['label'] = sprintf(
     205                        // translators: %s: Your current version of WordPress.
     206                                esc_html__( 'WordPress version %s' ),
     207                                $core_current_version
     208                        );
     209
     210                        $result['description'] = sprintf(
     211                                '<p>%s</p>',
     212                                esc_html__( 'We were unable to check if any new versions of WordPress are available.' )
     213                        );
     214
     215                        $result['actions'] = sprintf(
     216                                '<a href="%s">%s</a>',
     217                                esc_url( admin_url( 'update-core.php?force-check=1' ) ),
     218                                esc_html__( 'Check for updates manually' )
     219                        );
     220                } else {
     221                        foreach ( $core_updates as $core => $update ) {
     222                                if ( 'upgrade' === $update->response ) {
     223                                        $current_version = explode( '.', $core_current_version );
     224                                        $new_version     = explode( '.', $update->version );
     225
     226                                        $current_major = $current_version[0] . '.' . $current_version[1];
     227                                        $new_major     = $new_version[0] . '.' . $new_version[1];
     228
     229                                        $result['label'] = sprintf(
     230                                        // translators: %s: The latest version of WordPress available.
     231                                                esc_html__( 'WordPress update available (%s)' ),
     232                                                $update->version
     233                                        );
     234
     235                                        $result['actions'] = sprintf(
     236                                                '<a href="%s">%s</a>',
     237                                                esc_url( admin_url( '' ) ),
     238                                                esc_html__( 'Install the latest version of WordPress' )
     239                                        );
     240
     241                                        if ( $current_major !== $new_major ) {
     242                                                // This is a major version mismatch.
     243                                                $result['status']      = 'recommended';
     244                                                $result['description'] = sprintf(
     245                                                        '<p>%s</p>',
     246                                                        esc_html__( 'A new version of WordPress is available.' )
     247                                                );
     248                                        } else {
     249                                                // This is a minor version, sometimes considered more critical.
     250                                                $result['status']      = 'critical';
     251                                                $result['description'] = sprintf(
     252                                                        '<p>%s</p>',
     253                                                        esc_html__( 'A new minor update is available for your site. Because minor updates often address security, it\'s important to install them.' )
     254                                                );
     255                                        }
     256                                } else {
     257                                        $result['status'] = 'good';
     258                                        $result['label']  = sprintf(
     259                                        // translators: %s: The current version of WordPress installed on this site.
     260                                                esc_html__( 'Your WordPress version is up to date (%s)' ),
     261                                                $core_current_version
     262                                        );
     263
     264                                        $result['description'] = sprintf(
     265                                                '<p>%s</p>',
     266                                                esc_html__( 'You are currently running the latest version of WordPress available, keep it up!' )
     267                                        );
     268                                }
     269                        }
     270                }
     271
     272                return $result;
     273        }
     274
     275        public function test_wordpress_version() {
     276                $check = $this->get_test_wordpress_version();
     277
     278                printf( '<span class="%s"></span> %s', esc_attr( $check['status'] ), esc_html( $check['label'] ) );
     279        }
     280
     281        public function json_wordpress_version() {
     282                wp_send_json_success( $this->get_test_wordpress_version() );
     283        }
     284
     285        /**
     286         * Check if the user is currently in Troubleshooting Mode or not.
     287         *
     288         * @return bool
     289         */
     290        public function is_troubleshooting() {
     291                // Check if a session cookie to disable plugins has been set.
     292                if ( isset( $_COOKIE['wp-health-check-disable-plugins'] ) ) {
     293                        $_GET['health-check-disable-plugin-hash'] = $_COOKIE['wp-health-check-disable-plugins'];
     294                }
     295
     296                // If the disable hash isn't set, no need to interact with things.
     297                if ( ! isset( $_GET['health-check-disable-plugin-hash'] ) ) {
     298                        return false;
     299                }
     300
     301                $disable_hash = get_option( 'health-check-disable-plugin-hash', null );
     302
     303                if ( empty( $disable_hash ) ) {
     304                        return false;
     305                }
     306
     307                // If the plugin hash is not valid, we also break out
     308                if ( $disable_hash !== $_GET['health-check-disable-plugin-hash'] ) {
     309                        return false;
     310                }
     311
     312                return true;
     313        }
     314
     315        public function get_test_plugin_version() {
     316                $result = array(
     317                        'label'       => esc_html__( 'Your plugins are up to date' ),
     318                        'status'      => 'good',
     319                        'badge'       => array(
     320                                'label' => 'Security',
     321                                'color' => 'red',
     322                        ),
     323                        'description' => sprintf(
     324                                '<p>%s</p>',
     325                                esc_html__( '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.' )
     326                        ),
     327                        'actions'     => '',
     328                        'test'        => 'plugin_version',
     329                );
     330
     331                $plugins        = get_plugins();
     332                $plugin_updates = get_plugin_updates();
     333
     334                $show_unused_plugins  = true;
     335                $plugins_have_updates = false;
     336                $plugins_active       = 0;
     337                $plugins_total        = 0;
     338                $plugins_needs_update = 0;
     339
     340                if ( $this->is_troubleshooting() ) {
     341                        $show_unused_plugins = false;
     342                }
     343
     344                foreach ( $plugins as $plugin_path => $plugin ) {
     345                        $plugins_total++;
     346
     347                        if ( is_plugin_active( $plugin_path ) ) {
     348                                $plugins_active++;
     349                        }
     350
     351                        $plugin_version = $plugin['Version'];
     352
     353                        if ( array_key_exists( $plugin_path, $plugin_updates ) ) {
     354                                $plugins_needs_update++;
     355                                $plugins_have_updates = true;
     356                        }
     357                }
     358
     359                if ( $plugins_needs_update > 0 ) {
     360                        $result['status'] = 'critical';
     361
     362                        $result['label'] = esc_html__( 'You have plugins waiting to be updated' );
     363
     364                        $result['description'] .= sprintf(
     365                                '<p>%s</p>',
     366                                sprintf(
     367                                // translators: %d: The amount of outdated plugins.
     368                                        esc_html( _n(
     369                                                'Your site has %d plugin waiting to be updated.',
     370                                                'Your site has %d plugins waiting for updates.',
     371                                                $plugins_needs_update
     372                                        ) ),
     373                                        $plugins_needs_update
     374                                )
     375                        );
     376                } else {
     377                        $result['description'] .= sprintf(
     378                                '<p>%s</p>',
     379                                sprintf(
     380                                // translators: %d: The amount of plugins.
     381                                        esc_html( _n(
     382                                                'Your site has %d active plugin, and it is up to date.',
     383                                                'Your site has %d active plugins, and they are all up to date.',
     384                                                $plugins_active
     385                                        ) ),
     386                                        $plugins_active
     387                                )
     388                        );
     389                }
     390
     391                if ( ( $plugins_total > $plugins_active ) && $show_unused_plugins ) {
     392                        $unused_plugins = $plugins_total - $plugins_active;
     393
     394                        $result['status'] = 'recommended';
     395
     396                        $result['label'] = esc_html__( 'Inactive plugins should be removed' );
     397
     398                        $result['description'] .= sprintf(
     399                                '<p>%s</p>',
     400                                sprintf(
     401                                // translators: %d: The amount of inactive plugins.
     402                                        esc_html( _n(
     403                                                '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.',
     404                                                '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.',
     405                                                $unused_plugins
     406                                        ) ),
     407                                        $unused_plugins
     408                                )
     409                        );
     410                }
     411
     412                return $result;
     413        }
     414
     415        public function get_test_theme_version() {
     416                $result = array(
     417                        'label'       => esc_html__( 'Your themes are up to date' ),
     418                        'status'      => 'good',
     419                        'badge'       => array(
     420                                'label' => 'Security',
     421                                'color' => 'red',
     422                        ),
     423                        'description' => sprintf(
     424                                '<p>%s</p>',
     425                                esc_html__( '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.' )
     426                        ),
     427                        'actions'     => '',
     428                        'test'        => 'theme_version',
     429                );
     430
     431                $theme_updates = get_theme_updates();
     432
     433                $themes_total        = 0;
     434                $themes_need_updates = 0;
     435                $themes_inactive     = 0;
     436
     437                // This value is changed during processing to determine how many themes are considered a reasonable amount.
     438                $allowed_theme_count = 1;
     439
     440                $has_default_theme  = false;
     441                $has_unused_themes  = false;
     442                $show_unused_themes = true;
     443
     444                if ( $this->is_troubleshooting() ) {
     445                        $show_unused_themes = false;
     446                }
     447
     448                // Populate a list of all themes available in the install.
     449                $all_themes   = wp_get_themes();
     450                $active_theme = wp_get_theme();
     451
     452                foreach ( $all_themes as $theme_slug => $theme ) {
     453                        $themes_total++;
     454
     455                        if ( WP_DEFAULT_THEME === $theme_slug ) {
     456                                $has_default_theme = true;
     457                        }
     458
     459                        if ( array_key_exists( $theme_slug, $theme_updates ) ) {
     460                                $themes_need_updates++;
     461                        }
     462                }
     463
     464                // If this is a child theme, increase the allowed theme count by one, to account for the parent.
     465                if ( $active_theme->parent() ) {
     466                        $allowed_theme_count++;
     467                }
     468
     469                // If there's a default theme installed, we count that as allowed as well.
     470                if ( $has_default_theme ) {
     471                        $allowed_theme_count++;
     472                }
     473
     474                if ( $themes_total > $allowed_theme_count ) {
     475                        $has_unused_themes = true;
     476                        $themes_inactive   = ( $themes_total - $allowed_theme_count );
     477                }
     478
     479                if ( $themes_need_updates > 0 ) {
     480                        $result['status'] = 'critical';
     481
     482                        $result['label'] = esc_html__( 'You have themes waiting to be updated' );
     483
     484                        $result['description'] .= sprintf(
     485                                '<p>%s</p>',
     486                                sprintf(
     487                                // translators: %d: The amount of outdated themes.
     488                                        esc_html( _n(
     489                                                'Your site has %d theme waiting to be updated.',
     490                                                'Your site has %d themes waiting to be updated.',
     491                                                $themes_need_updates
     492                                        ) ),
     493                                        $themes_need_updates
     494                                )
     495                        );
     496                } else {
     497                        $result['description'] .= sprintf(
     498                                '<p>%s</p>',
     499                                sprintf(
     500                                // translators: %d: The amount of themes.
     501                                        esc_html( _n(
     502                                                'Your site has %d installed theme, and it is up to date.',
     503                                                'Your site has %d installed themes, and they are all up to date.',
     504                                                $themes_total
     505                                        ) ),
     506                                        $themes_total
     507                                )
     508                        );
     509                }
     510
     511                if ( $has_unused_themes && $show_unused_themes ) {
     512
     513                        // This is a child theme, so we want to be a bit more explicit in our messages.
     514                        if ( $active_theme->parent() ) {
     515                                $result['status'] = 'recommended';
     516
     517                                $result['label'] = esc_html__( 'You should remove inactive themes' );
     518
     519                                $result['description'] .= sprintf(
     520                                        '<p>%s</p>',
     521                                        sprintf(
     522                                        // 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.
     523                                                esc_html( _n(
     524                                                        '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.',
     525                                                        '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.',
     526                                                        $themes_inactive
     527                                                ) ),
     528                                                $themes_inactive,
     529                                                WP_DEFAULT_THEME,
     530                                                $active_theme->name,
     531                                                $active_theme->parent()->name
     532                                        )
     533                                );
     534
     535                        } else {
     536                                $result['status'] = 'recommended';
     537
     538                                $result['label'] = esc_html__( 'You should remove inactive themes' );
     539
     540                                $result['description'] .= sprintf(
     541                                        '<p>%s</p>',
     542                                        sprintf(
     543                                        // translators: %1$d: The amount of inactive themes. %2$s: The default theme for WordPress. %3$s: The currently active theme.
     544                                                esc_html( _n(
     545                                                        '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.',
     546                                                        '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.',
     547                                                        $themes_inactive
     548                                                ) ),
     549                                                $themes_inactive,
     550                                                WP_DEFAULT_THEME,
     551                                                $active_theme->name
     552                                        )
     553                                );
     554                        }
     555                }
     556
     557                if ( ! $has_default_theme ) {
     558                        $result['status'] = 'recommended';
     559
     560                        $result['label'] = esc_html__( 'Have a default theme available' );
     561
     562                        $result['description'] .= sprintf(
     563                                '<p>%s</p>',
     564                                esc_html__( 'Your site does not have any default theme. Default themes are used by WordPress automatically if anything is wrong with your normal theme.' )
     565                        );
     566                }
     567
     568                return $result;
     569        }
     570
     571        public function get_test_php_version() {
     572                $result = array(
     573                        'label'       => sprintf(
     574                        // translators: %s: The current PHP version
     575                                esc_html__( 'PHP is up to date (%s)' ),
     576                                PHP_VERSION
     577                        ),
     578                        'status'      => 'good',
     579                        'badge'       => array(
     580                                'label' => 'Security',
     581                                'color' => 'red',
     582                        ),
     583                        'description' => sprintf(
     584                                '<p>%s</p>',
     585                                esc_html__( '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.' )
     586                        ),
     587                        'actions'     => '',
     588                        'test'        => 'php_version',
     589                );
     590
     591                if ( ! $this->php_min_version_check ) {
     592                        $result['status'] = 'critical';
     593
     594                        $result['label'] = esc_html__( 'Your PHP version requires an update' );
     595
     596                        $result['actions'] = sprintf(
     597                                '<a href="%s">%s</a>',
     598                                esc_url(
     599                                        _x( 'https://wordpress.org/support/upgrade-php/', 'The link to the Update PHP page, which may be localized.' )
     600                                ),
     601                                esc_html__( 'Learn more about why you should update PHP' )
     602                        );
     603
     604                        $result['description'] .= sprintf(
     605                                '<p>%s</p>',
     606                                sprintf(
     607                                // translators: %1$s: Current PHP version. %2$s: Recommended PHP version. %3$s: Minimum PHP version.
     608                                        esc_html__( '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.' ),
     609                                        PHP_VERSION,
     610                                        HEALTH_CHECK_PHP_REC_VERSION,
     611                                        HEALTH_CHECK_PHP_MIN_VERSION
     612                                )
     613                        );
     614                } elseif ( ! $this->php_supported_version_check ) {
     615                        $result['status'] = 'recommended';
     616
     617                        $result['label'] = esc_html__( 'Your PHP version should be updated' );
     618
     619                        $result['actions'] = sprintf(
     620                                '<a href="%s">%s</a>',
     621                                esc_url(
     622                                        _x( 'https://wordpress.org/support/upgrade-php/', 'The link to the Update PHP page, which may be localized.' )
     623                                ),
     624                                esc_html__( 'Learn more about why you should update PHP' )
     625                        );
     626
     627                        $result['description'] .= sprintf(
     628                                '<p>%s</p>',
     629                                sprintf(
     630                                // translators: %1$s: Current PHP version. %2$s: Recommended PHP version.
     631                                        esc_html__( '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.' ),
     632                                        PHP_VERSION,
     633                                        HEALTH_CHECK_PHP_REC_VERSION
     634                                )
     635                        );
     636                } elseif ( ! $this->php_rec_version_check ) {
     637                        $result['status'] = 'recommended';
     638
     639                        $result['label'] = esc_html__( 'We recommend that you update PHP' );
     640
     641                        $result['actions'] = sprintf(
     642                                '<a href="%s">%s</a>',
     643                                esc_url(
     644                                        _x( 'https://wordpress.org/support/upgrade-php/', 'The link to the Update PHP page, which may be localized.' )
     645                                ),
     646                                esc_html__( 'Learn more about why you should update PHP' )
     647                        );
     648
     649                        $result['description'] .= sprintf(
     650                                '<p>%s</p>',
     651                                sprintf(
     652                                // translators: %s: Recommended PHP version
     653                                        esc_html__( 'For best performance we recommend using PHP %s or higher.' ),
     654                                        HEALTH_CHECK_PHP_REC_VERSION
     655                                )
     656                        );
     657                }
     658
     659                return $result;
     660        }
     661
     662        public function child_test_php_extension_availability( $extension = null, $function = null ) {
     663                // If no extension or function is passed, claim to fail testing, as we have nothing to test against.
     664                if ( null === $extension && null === $function ) {
     665                        return false;
     666                }
     667
     668                $available = true;
     669
     670                if ( null !== $extension && ! extension_loaded( $extension ) ) {
     671                        $available = false;
     672                }
     673                if ( null !== $function && ! function_exists( $function ) ) {
     674                        $available = false;
     675                }
     676
     677                return $available;
     678        }
     679
     680        public function get_test_php_extensions() {
     681                $result = array(
     682                        'label'       => esc_html__( 'Required and recommended modules are installed' ),
     683                        'status'      => 'good',
     684                        'badge'       => array(
     685                                'label' => 'Performance',
     686                                'color' => 'orange',
     687                        ),
     688                        'description' => sprintf(
     689                                '<p>%s</p><p>%s</p>',
     690                                esc_html__( 'PHP modules perform most of the tasks on the server that make your site run.' ),
     691                                sprintf(
     692                                // translators: %s: Link to the hosting group page about recommended PHP modules.
     693                                        esc_html__( 'The Hosting team maintains a list of those modules, both recommended and required, in %s.' ),
     694                                        sprintf(
     695                                                '<a href="https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions">%s</a>',
     696                                                esc_html__( 'the team handbook' )
     697                                        )
     698                                )
     699                        ),
     700                        'actions'     => '',
     701                        'test'        => 'php_extensions',
     702                );
     703
     704                /*
     705                 * An array representing all the modules we wish to test for.
     706                 *
     707                 * array $modules {
     708                 *     An associated array of modules to test for.
     709                 *
     710                 *     array $module {
     711                 *         An associated array of module properties used during testing.
     712                 *         One of either `$function` or `$extension` must be provided, or they will fail by default.
     713                 *
     714                 *         string $function     Optional. A function name to test for the existence of.
     715                 *         string $extension    Optional. An extension to check if is loaded in PHP.
     716                 *         bool   $required     Is this a required feature or not.
     717                 *         string $fallback_for Optional. The module this module replaces as a fallback.
     718                 *     }
     719                 * }
     720                 */
     721                $modules = array(
     722                        'bcmath'    => array(
     723                                'function' => 'bcadd',
     724                                'required' => false,
     725                        ),
     726                        'curl'      => array(
     727                                'function' => 'curl_version',
     728                                'required' => false,
     729                        ),
     730                        'exif'      => array(
     731                                'function' => 'exif_read_data',
     732                                'required' => false,
     733                        ),
     734                        'filter'    => array(
     735                                'function' => 'filter_list',
     736                                'required' => false,
     737                        ),
     738                        'fileinfo'  => array(
     739                                'function' => 'finfo_file',
     740                                'required' => false,
     741                        ),
     742                        'mod_xml'   => array(
     743                                'extension' => 'libxml',
     744                                'required'  => false,
     745                        ),
     746                        'mysqli'    => array(
     747                                'function' => 'mysqli_connect',
     748                                'required' => false,
     749                        ),
     750                        'libsodium' => array(
     751                                'function'            => 'sodium_compare',
     752                                'required'            => false,
     753                                'php_bundled_version' => '7.2.0',
     754                        ),
     755                        'openssl'   => array(
     756                                'function' => 'openssl_encrypt',
     757                                'required' => false,
     758                        ),
     759                        'pcre'      => array(
     760                                'function' => 'preg_match',
     761                                'required' => false,
     762                        ),
     763                        'imagick'   => array(
     764                                'extension' => 'imagick',
     765                                'required'  => false,
     766                        ),
     767                        'gd'        => array(
     768                                'extension'    => 'gd',
     769                                'required'     => false,
     770                                'fallback_for' => 'imagick',
     771                        ),
     772                        'mcrypt'    => array(
     773                                'extension'    => 'mcrypt',
     774                                'required'     => false,
     775                                'fallback_for' => 'libsodium',
     776                        ),
     777                        'xmlreader' => array(
     778                                'extension'    => 'xmlreader',
     779                                'required'     => false,
     780                                'fallback_for' => 'xml',
     781                        ),
     782                        'zlib'      => array(
     783                                'extension'    => 'zlib',
     784                                'required'     => false,
     785                                'fallback_for' => 'zip',
     786                        ),
     787                );
     788
     789                $failures = array();
     790
     791                foreach ( $modules as $library => $module ) {
     792                        $extension = ( isset( $module['extension'] ) ? $module['extension'] : null );
     793                        $function  = ( isset( $module['function'] ) ? $module['function'] : null );
     794
     795                        // If this module is a fallback for another function, check if that other function passed.
     796                        if ( isset( $module['fallback_for'] ) ) {
     797                                /*
     798                                 * If that other function has a failure, mark this module as required for normal operations.
     799                                 * If that other function hasn't failed, skip this test as it's only a fallback.
     800                                 */
     801                                if ( isset( $failures[ $module['fallback_for'] ] ) ) {
     802                                        $module['required'] = true;
     803                                } else {
     804                                        continue;
     805                                }
     806                        }
     807
     808                        if ( ! $this->child_test_php_extension_availability( $extension, $function ) && ( ! isset( $module['php_bundled_version'] ) || version_compare( PHP_VERSION, $module['php_bundled_version'], '<' ) ) ) {
     809                                if ( $module['required'] ) {
     810                                        $result['status'] = 'critical';
     811                                }
     812
     813                                if ( ! $module['required'] && 'good' === $result['status'] ) {
     814                                        $result['status'] = 'recommended';
     815                                }
     816
     817                                $failures[ $library ] = sprintf(
     818                                        '<span class="%s"><span class="screen-reader-text">%s</span></span> %s',
     819                                        ( $module['required'] ? 'error' : 'warning' ),
     820                                        ( $module['required'] ? esc_html__( 'Error' ) : esc_html__( 'Warning' ) ),
     821                                        sprintf(
     822                                        // translators: %1$2: If a module is required or recommended. %2$s: The module name.
     823                                                __( 'The %1$s module, %2$s, is not installed, or has been disabled.' ),
     824                                                ( $module['required'] ? __( 'required' ) : __( 'optional' ) ),
     825                                                $library
     826                                        )
     827                                );
     828                        }
     829                }
     830
     831                if ( ! empty( $failures ) ) {
     832                        $output = '<ul>';
     833
     834                        foreach ( $failures as $failure ) {
     835                                $output .= sprintf(
     836                                        '<li>%s</li>',
     837                                        $failure
     838                                );
     839                        }
     840
     841                        $output .= '</ul>';
     842                }
     843
     844                if ( 'good' !== $result['status'] ) {
     845                        if ( 'recommended' === $result['status'] ) {
     846                                $result['label'] = esc_html__( 'One or more recommended modules are missing' );
     847                        }
     848                        if ( 'critical' === $result['status'] ) {
     849                                $result['label'] = esc_html__( 'One or more required modules are missing' );
     850                        }
     851
     852                        $result['description'] .= sprintf(
     853                                '<p>%s</p>',
     854                                $output
     855                        );
     856                }
     857
     858                return $result;
     859        }
     860
     861        public function get_test_sql_server() {
     862                $result = array(
     863                        'label'       => esc_html__( 'SQL server is up to date' ),
     864                        'status'      => 'good',
     865                        'badge'       => array(
     866                                'label' => 'Security',
     867                                'color' => 'red',
     868                        ),
     869                        'description' => sprintf(
     870                                '<p>%s</p>',
     871                                esc_html__( 'The SQL server is the database where WordPress stores all your site’s content and settings' )
     872                        ),
     873                        'actions'     => '',
     874                        'test'        => 'sql_server',
     875                );
     876
     877                $db_dropin = file_exists( WP_CONTENT_DIR . '/db.php' );
     878
     879                if ( ! $this->mysql_rec_version_check ) {
     880                        $result['status'] = 'recommended';
     881
     882                        $result['label'] = esc_html__( 'Outdated SQL server' );
     883
     884                        $result['description'] .= sprintf(
     885                                '<p>%s</p>',
     886                                sprintf(
     887                                // translators: %1$s: The database engine in use (MySQL or MariaDB). %2$s: Database server recommended version number.
     888                                        esc_html__( 'For optimal performance and security reasons, we recommend running %1$s version %2$s or higher. Contact your web hosting company to correct this.' ),
     889                                        ( $this->mariadb ? 'MariaDB' : 'MySQL' ),
     890                                        $this->health_check_mysql_rec_version
     891                                )
     892                        );
     893                }
     894
     895                if ( ! $this->mysql_min_version_check ) {
     896                        $result['status'] = 'critical';
     897
     898                        $result['label'] = esc_html__( 'Severely outdated SQL server' );
     899
     900                        $result['description'] .= sprintf(
     901                                '<p>%s</p>',
     902                                sprintf(
     903                                // translators: %1$s: The database engine in use (MySQL or MariaDB). %2$s: Database server minimum version number.
     904                                        esc_html__( 'WordPress requires %1$s version %2$s or higher. Contact your web hosting company to correct this.' ),
     905                                        ( $this->mariadb ? 'MariaDB' : 'MySQL' ),
     906                                        HEALTH_CHECK_MYSQL_MIN_VERSION
     907                                )
     908                        );
     909                }
     910
     911                if ( $db_dropin ) {
     912                        // translators: %s: The database engine in use (MySQL or MariaDB).
     913                        $result['description'] .= sprintf(
     914                                '<p>%s</p>',
     915                                wp_kses(
     916                                        sprintf(
     917                                        // translators: %s: The name of the database engine being used.
     918                                                __( 'You are using a <code>wp-content/db.php</code> drop-in which might mean that a %s database is not being used.' ),
     919                                                ( $this->mariadb ? 'MariaDB' : 'MySQL' )
     920                                        ),
     921                                        array(
     922                                                'code' => true,
     923                                        )
     924                                )
     925                        );
     926                }
     927
     928                return $result;
     929        }
     930
     931        public function get_test_utf8mb4_support() {
     932                global $wpdb;
     933
     934                $result = array(
     935                        'label'       => esc_html__( 'UTF8MB4 is supported' ),
     936                        'status'      => 'good',
     937                        'badge'       => array(
     938                                'label' => 'Performance',
     939                                'color' => 'orange',
     940                        ),
     941                        'description' => sprintf(
     942                                '<p>%s</p>',
     943                                esc_html__( '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.' )
     944                        ),
     945                        'actions'     => '',
     946                        'test'        => 'utf8mb4_support',
     947                );
     948
     949                if ( ! $this->mariadb ) {
     950                        if ( version_compare( $this->mysql_server_version, '5.5.3', '<' ) ) {
     951                                $result['status'] = 'recommended';
     952
     953                                $result['label'] = esc_html__( 'UTF8MB4 requires an SQL update' );
     954
     955                                $result['description'] .= sprintf(
     956                                        '<p>%s</p>',
     957                                        sprintf(
     958                                        /* translators: %1$s: Database engine name. %2$s: Version number. */
     959                                                esc_html__( 'WordPress\' utf8mb4 support requires %1$s version %2$s or greater' ),
     960                                                'MySQL',
     961                                                '5.5.3'
     962                                        )
     963                                );
     964                        } else {
     965                                $result['description'] .= sprintf(
     966                                        '<p>%s</p>',
     967                                        esc_html__( 'Your MySQL version supports utf8mb4' )
     968                                );
     969                        }
     970                } else { // MariaDB introduced utf8mb4 support in 5.5.0
     971                        if ( version_compare( $this->mysql_server_version, '5.5.0', '<' ) ) {
     972                                $result['status'] = 'recommended';
     973
     974                                $result['label'] = esc_html__( 'UTF8MB4 requires an SQL update' );
     975
     976                                $result['description'] .= sprintf(
     977                                        '<p>%s</p>',
     978                                        sprintf(
     979                                        /* translators: %1$s: Database engine name. %2$s: Version number. */
     980                                                esc_html__( 'WordPress\' utf8mb4 support requires %1$s version %2$s or greater' ),
     981                                                'MariaDB',
     982                                                '5.5.0'
     983                                        )
     984                                );
     985                        } else {
     986                                $result['description'] .= sprintf(
     987                                        '<p>%s</p>',
     988                                        esc_html__( 'Your MariaDB version supports utf8mb4' )
     989                                );
     990                        }
     991                }
     992
     993                if ( $wpdb->use_mysqli ) {
     994                        // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_get_client_info
     995                        $mysql_client_version = mysqli_get_client_info();
     996                } else {
     997                        // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_get_client_info
     998                        $mysql_client_version = mysql_get_client_info();
     999                }
     1000
     1001                /*
     1002                 * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server.
     1003                 * mysqlnd has supported utf8mb4 since 5.0.9.
     1004                 */
     1005                if ( false !== strpos( $mysql_client_version, 'mysqlnd' ) ) {
     1006                        $mysql_client_version = preg_replace( '/^\D+([\d.]+).*/', '$1', $mysql_client_version );
     1007                        if ( version_compare( $mysql_client_version, '5.0.9', '<' ) ) {
     1008                                $result['status'] = 'recommended';
     1009
     1010                                $result['label'] = esc_html__( 'UTF8MB4 requires a newer client library' );
     1011
     1012                                $result['description'] .= sprintf(
     1013                                        '<p>%s</p>',
     1014                                        sprintf(
     1015                                        /* translators: %1$s: Name of the library, %2$s: Number of version. */
     1016                                                esc_html__( 'WordPress\' utf8mb4 support requires MySQL client library (%1$s) version %2$s or newer.' ),
     1017                                                'mysqlnd',
     1018                                                '5.0.9'
     1019                                        )
     1020                                );
     1021                        }
     1022                } else {
     1023                        if ( version_compare( $mysql_client_version, '5.5.3', '<' ) ) {
     1024                                $result['status'] = 'recommended';
     1025
     1026                                $result['label'] = esc_html__( 'UTF8MB4 requires a newer client library' );
     1027
     1028                                $result['description'] .= sprintf(
     1029                                        '<p>%s</p>',
     1030                                        sprintf(
     1031                                        /* translators: %1$s: Name of the library, %2$s: Number of version. */
     1032                                                __( 'WordPress\' utf8mb4 support requires MySQL client library (%1$s) version %2$s or newer.' ),
     1033                                                'libmysql',
     1034                                                '5.5.3'
     1035                                        )
     1036                                );
     1037                        }
     1038                }
     1039
     1040                return $result;
     1041        }
     1042
     1043        public function get_test_dotorg_communication() {
     1044                $result = array(
     1045                        'label'       => esc_html__( 'Can communicate with WordPress.org' ),
     1046                        'status'      => '',
     1047                        'badge'       => array(
     1048                                'label' => 'Security',
     1049                                'color' => 'red',
     1050                        ),
     1051                        'description' => sprintf(
     1052                                '<p>%s</p>',
     1053                                esc_html__( 'Communicating with the WordPress servers is used to check for new versions, Communicating with the WordPress servers is used to check for new versions, and to both install and update WordPress core, themes or plugins.' )
     1054                        ),
     1055                        'actions'     => '',
     1056                        'test'        => 'dotorg_communication',
     1057                );
     1058
     1059                $wp_dotorg = wp_remote_get( 'https://wordpress.org', array(
     1060                        'timeout' => 10,
     1061                ) );
     1062                if ( ! is_wp_error( $wp_dotorg ) ) {
     1063                        $result['status'] = 'good';
     1064                } else {
     1065                        $result['status'] = 'critical';
     1066
     1067                        $result['label'] = esc_html__( 'Could not reach WordPress.org' );
     1068
     1069                        $result['description'] .= sprintf(
     1070                                '<p>%s</p>',
     1071                                sprintf(
     1072                                        '<span class="error"><span class="screen-reader-text">%s</span></span> %s',
     1073                                        esc_html__( 'Error' ),
     1074                                        sprintf(
     1075                                        // translators: %1$s: The IP address WordPress.org resolves to. %2$s: The error returned by the lookup.
     1076                                                __( 'Your site is unable to reach WordPress.org at %1$s, and returned the error: %2$s' ),
     1077                                                gethostbyname( 'wordpress.org' ),
     1078                                                $wp_dotorg->get_error_message()
     1079                                        )
     1080                                )
     1081                        );
     1082                }
     1083
     1084                return $result;
     1085        }
     1086
     1087        public function json_test_dotorg_communication() {
     1088                wp_send_json_success( $this->get_test_dotorg_communication() );
     1089        }
     1090
     1091        public function get_test_is_in_debug_mode() {
     1092                $result = array(
     1093                        'label'       => esc_html__( 'Your site is not set to output debug information' ),
     1094                        'status'      => 'good',
     1095                        'badge'       => array(
     1096                                'label' => 'Security',
     1097                                'color' => 'red',
     1098                        ),
     1099                        'description' => sprintf(
     1100                                '<p>%s</p>',
     1101                                esc_html__( '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.' )
     1102                        ),
     1103                        'actions'     => '',
     1104                        'test'        => 'is_in_debug_mode',
     1105                );
     1106
     1107                if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
     1108                        if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
     1109                                $result['label'] = esc_html__( 'Your site is set to log errors to a potentially public file.' );
     1110
     1111                                $result['status'] = 'critical';
     1112
     1113                                $result['description'] .= sprintf(
     1114                                        '<p>%s</p>',
     1115                                        esc_html__( '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.' )
     1116                                );
     1117                        }
     1118
     1119                        if ( defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY ) {
     1120                                $result['label'] = esc_html__( 'Your site is set to display errors to site visitors.' );
     1121
     1122                                $result['status'] = 'critical';
     1123
     1124                                $result['description'] .= sprintf(
     1125                                        '<p>%s</p>',
     1126                                        esc_html__( '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.' )
     1127                                );
     1128                        }
     1129                }
     1130
     1131                return $result;
     1132        }
     1133
     1134        public function json_test_is_in_debug_mode() {
     1135                wp_send_json_success( $this->get_test_is_in_debug_mode() );
     1136        }
     1137
     1138        public function get_test_https_status() {
     1139                $result = array(
     1140                        'label'       => '',
     1141                        'status'      => '',
     1142                        'badge'       => array(
     1143                                'label' => 'Security',
     1144                                'color' => 'red',
     1145                        ),
     1146                        'description' => '',
     1147                        'actions'     => '',
     1148                        'test'        => 'https_status',
     1149                );
     1150
     1151                if ( is_ssl() ) {
     1152                        $wp_url   = get_bloginfo( 'wpurl' );
     1153                        $site_url = get_bloginfo( 'url' );
     1154
     1155                        if ( 'https' !== substr( $wp_url, 0, 5 ) || 'https' !== substr( $site_url, 0, 5 ) ) {
     1156                                $result['status'] = 'recommended';
     1157
     1158                                $result['label'] = esc_html__( 'Only parts of your site are using HTTPS' );
     1159
     1160                                $result['description'] = sprintf(
     1161                                        '<p>%s</p>',
     1162                                        sprintf(
     1163                                        // translators: %s: URL to Settings > General to change options.
     1164                                                __( 'You are accessing this website using HTTPS, but your <a href="%s">WordPress Address</a> is not set up to use HTTPS by default.' ),
     1165                                                esc_url( admin_url( 'options-general.php' ) )
     1166                                        )
     1167                                );
     1168
     1169                                $result['actions'] = sprintf(
     1170                                        '<a href="%s">%s</a>',
     1171                                        esc_url( admin_url( 'options-general.php' ) ),
     1172                                        esc_html__( 'Update your site addresses' )
     1173                                );
     1174                        } else {
     1175                                $result['status'] = 'good';
     1176
     1177                                $result['label'] = esc_html__( 'Your website is using an active HTTPS connection.' );
     1178                        }
     1179                } else {
     1180                        $result['status'] = 'recommended';
     1181
     1182                        $result['label'] = esc_html__( 'Your site does not use HTTPS' );
     1183
     1184                        $result['description'] = sprintf(
     1185                                '<p>%s</p>',
     1186                                esc_html__( '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.' )
     1187                        );
     1188
     1189                        $result['actions'] = sprintf(
     1190                                '<a href="%s">%s</a>',
     1191                                esc_url(
     1192                                // translators: Website for explaining HTTPS and why it should be used.
     1193                                        __( 'https://www.cloudflare.com/learning/security/why-use-https/' )
     1194                                ),
     1195                                esc_html__( 'Read more about why you should use HTTPS' )
     1196                        );
     1197                }
     1198
     1199                return $result;
     1200        }
     1201
     1202        public function get_test_ssl_support() {
     1203                $result = array(
     1204                        'label'       => '',
     1205                        'status'      => '',
     1206                        'badge'       => array(
     1207                                'label' => 'Security',
     1208                                'color' => 'red',
     1209                        ),
     1210                        'description' => sprintf(
     1211                                '<p>%s</p>',
     1212                                esc_html__( 'Securely communicating between servers are needed for transactions such as fetching files, conducting sales on store sites, and much more.' )
     1213                        ),
     1214                        'actions'     => '',
     1215                        'test'        => 'ssl_support',
     1216                );
     1217
     1218                $supports_https = wp_http_supports( array( 'ssl' ) );
     1219
     1220                if ( $supports_https ) {
     1221                        $result['status'] = 'good';
     1222
     1223                        $result['label'] = esc_html__( 'Your site can communicate securely with other services.' );
     1224                } else {
     1225                        $result['status'] = 'critical';
     1226
     1227                        $result['label'] = esc_html__( 'Your site is unable to communicate securely with other services.' );
     1228
     1229                        $result['description'] .= sprintf(
     1230                                '<p>%s</p>',
     1231                                esc_html__( 'Talk to your web host about OpenSSL support for PHP' )
     1232                        );
     1233                }
     1234
     1235                return $result;
     1236        }
     1237
     1238        public function get_test_scheduled_events() {
     1239                $result = array(
     1240                        'label'       => esc_html__( 'Scheduled events are running' ),
     1241                        'status'      => 'good',
     1242                        'badge'       => array(
     1243                                'label' => 'Performance',
     1244                                'color' => 'orange',
     1245                        ),
     1246                        'description' => sprintf(
     1247                                '<p>%s</p>',
     1248                                esc_html__( '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.' )
     1249                        ),
     1250                        'actions'     => '',
     1251                        'test'        => 'scheduled_events',
     1252                );
     1253
     1254                $this->wp_schedule_test_init();
     1255
     1256                if ( is_wp_error( $this->has_missed_cron() ) ) {
     1257                        $result['status'] = 'critical';
     1258
     1259                        $result['label'] = esc_html__( 'It was not possible to check your scheduled events' );
     1260
     1261                        $result['description'] = sprintf(
     1262                                '<p>%s</p>',
     1263                                sprintf(
     1264                                // translators: %s: The error message returned while from the cron scheduler.
     1265                                        esc_html__( 'While trying to test your sites scheduled events, the following error was returned: %s' ),
     1266                                        $this->has_missed_cron()->get_error_message()
     1267                                )
     1268                        );
     1269                } else {
     1270                        if ( $this->has_missed_cron() ) {
     1271                                $result['status'] = 'recommended';
     1272
     1273                                $result['label'] = esc_html__( 'A scheduled event has failed' );
     1274
     1275                                $result['description'] = sprintf(
     1276                                        '<p>%s</p>',
     1277                                        sprintf(
     1278                                        // translators: %s: The name of the failed cron event.
     1279                                                esc_html__( '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.' ),
     1280                                                $this->last_missed_cron
     1281                                        )
     1282                                );
     1283                        }
     1284                }
     1285
     1286                return $result;
     1287        }
     1288
     1289        public function get_test_background_updates() {
     1290                $result = array(
     1291                        'label'       => esc_html__( 'Background updates are working' ),
     1292                        'status'      => 'good',
     1293                        'badge'       => array(
     1294                                'label' => 'Security',
     1295                                'color' => 'red',
     1296                        ),
     1297                        'description' => sprintf(
     1298                                '<p>%s</p>',
     1299                                esc_html__( 'Background updates ensure that WordPress can auto-update if a security update is released for the version you are currently using.' )
     1300                        ),
     1301                        'actions'     => '',
     1302                        'test'        => 'background_updates',
     1303                );
     1304
     1305                if ( ! class_exists( 'Site_Health_Auto_Updates' ) ) {
     1306                        require_once( ABSPATH . 'wp-admin/includes/class-wp-site-health-auto-updates.php' );
     1307                }
     1308
     1309                $automatic_updates = new Site_Health_Auto_Updates();
     1310                $tests             = $automatic_updates->run_tests();
     1311
     1312                $output = '<ul>';
     1313
     1314                foreach ( $tests as $test ) {
     1315                        $severity_string = esc_html__( 'Passed' );
     1316
     1317                        if ( 'fail' === $test->severity ) {
     1318                                $result['label'] = esc_html__( 'Background updates are not working as expected' );
     1319
     1320                                $result['status'] = 'critical';
     1321
     1322                                $severity_string = esc_html__( 'Error' );
     1323                        }
     1324
     1325                        if ( 'warning' === $test->severity && 'good' === $result['status'] ) {
     1326                                $result['label'] = esc_html__( 'Background updates may not be working properly' );
     1327
     1328                                $result['status'] = 'recommended';
     1329
     1330                                $severity_string = esc_html__( 'Warning' );
     1331                        }
     1332
     1333                        $output .= sprintf(
     1334                                '<li><span class="%s"><span class="screen-reader-text">%s</span></span> %s</li>',
     1335                                esc_attr( $test->severity ),
     1336                                $severity_string,
     1337                                $test->desc
     1338                        );
     1339                }
     1340
     1341                $output .= '</ul>';
     1342
     1343                if ( 'good' !== $result['status'] ) {
     1344                        $result['description'] .= sprintf(
     1345                                '<p>%s</p>',
     1346                                $output
     1347                        );
     1348                }
     1349
     1350                return $result;
     1351        }
     1352
     1353        public function json_test_background_updates() {
     1354                wp_send_json_success( $this->get_test_background_updates() );
     1355        }
     1356
     1357        public function get_test_loopback_requests() {
     1358                $result = array(
     1359                        'label'       => esc_html__( 'Your site can perform loopback requests' ),
     1360                        'status'      => 'good',
     1361                        'badge'       => array(
     1362                                'label' => 'Performance',
     1363                                'color' => 'orange',
     1364                        ),
     1365                        'description' => sprintf(
     1366                                '<p>%s</p>',
     1367                                esc_html__( '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.' )
     1368                        ),
     1369                        'actions'     => '',
     1370                        'test'        => 'loopback_requests',
     1371                );
     1372
     1373                $check_loopback = $this->can_perform_loopback();
     1374
     1375                $result['status'] = $check_loopback->status;
     1376
     1377                if ( 'good' !== $check_loopback->status ) {
     1378                        $result['label'] = esc_html__( 'Your site could not complete a loopback request' );
     1379
     1380                        $result['description'] .= sprintf(
     1381                                '<p>%s</p>',
     1382                                $check_loopback->message
     1383                        );
     1384                }
     1385
     1386                if ( 'critical' === $check_loopback->status ) {
     1387                        $result['actions'] .= sprintf(
     1388                                '<button type="button" id="loopback-no-plugins" class="button button-primary">%s</button>',
     1389                                esc_html__( 'Test without plugins' )
     1390                        );
     1391                }
     1392
     1393                return $result;
     1394        }
     1395
     1396        public function json_test_loopback_requests() {
     1397                wp_send_json_success( $this->get_test_loopback_requests() );
     1398        }
     1399
     1400        public function get_test_http_requests() {
     1401                $result = array(
     1402                        'label'       => esc_html__( 'HTTP requests seem to be working as expected' ),
     1403                        'status'      => 'good',
     1404                        'badge'       => array(
     1405                                'label' => 'Performance',
     1406                                'color' => 'orange',
     1407                        ),
     1408                        'description' => sprintf(
     1409                                '<p>%s</p>',
     1410                                esc_html__( '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.' )
     1411                        ),
     1412                        'actions'     => '',
     1413                        'test'        => 'http_requests',
     1414                );
     1415
     1416                $blocked = false;
     1417                $hosts   = array();
     1418
     1419                if ( defined( 'WP_HTTP_BLOCK_EXTERNAL' ) ) {
     1420                        $blocked = true;
     1421                }
     1422
     1423                if ( defined( 'WP_ACCESSIBLE_HOSTS' ) ) {
     1424                        $hosts = explode( ',', WP_ACCESSIBLE_HOSTS );
     1425                }
     1426
     1427                if ( $blocked && 0 === sizeof( $hosts ) ) {
     1428                        $result['status'] = 'critical';
     1429
     1430                        $result['label'] = esc_html__( 'HTTP requests are blocked' );
     1431
     1432                        $result['description'] .= sprintf(
     1433                                '<p>%s</p>',
     1434                                esc_html__( 'HTTP requests have been blocked by the WP_HTTP_BLOCK_EXTERNAL constant, with no allowed hosts.' )
     1435                        );
     1436                }
     1437
     1438                if ( $blocked && 0 < sizeof( $hosts ) ) {
     1439                        $result['status'] = 'recommended';
     1440
     1441                        $result['label'] = esc_html__( 'HTTP requests are partially blocked' );
     1442
     1443                        $result['description'] .= sprintf(
     1444                                '<p>%s</p>',
     1445                                sprintf(
     1446                                /* translators: %s: List of hostnames whitelisted. */
     1447                                        esc_html__( 'HTTP requests have been blocked by the WP_HTTP_BLOCK_EXTERNAL constant, with some hosts whitelisted: %s.' ),
     1448                                        implode( ',', $hosts )
     1449                                )
     1450                        );
     1451                }
     1452
     1453                return $result;
     1454        }
     1455
     1456        public function get_test_rest_availability() {
     1457                $result = array(
     1458                        'label'       => esc_html__( 'The REST API is available' ),
     1459                        'status'      => 'good',
     1460                        'badge'       => array(
     1461                                'label' => 'Performance',
     1462                                'color' => 'orange',
     1463                        ),
     1464                        'description' => sprintf(
     1465                                '<p>%s</p>',
     1466                                esc_html__( '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.' )
     1467                        ),
     1468                        'actions'     => '',
     1469                        'test'        => 'rest_availability',
     1470                );
     1471
     1472                $cookies = wp_unslash( $_COOKIE );
     1473                $timeout = 10;
     1474                $headers = array(
     1475                        'Cache-Control' => 'no-cache',
     1476                        'X-WP-Nonce'    => wp_create_nonce( 'wp_rest' ),
     1477                );
     1478
     1479                // Include Basic auth in loopback requests.
     1480                if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
     1481                        $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
     1482                }
     1483
     1484                $url = rest_url( 'wp/v2/types/post' );
     1485
     1486                // We only need the first post to ensure this works, to make it low impact.
     1487                $url = add_query_arg( array(
     1488                        'context' => 'edit',
     1489                ), $url );
     1490
     1491                $r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
     1492
     1493                if ( is_wp_error( $r ) ) {
     1494                        $result['status'] = 'critical';
     1495
     1496                        $result['label'] = esc_html__( 'The REST API encountered an error' );
     1497
     1498                        $result['description'] .= sprintf(
     1499                                '<p>%s</p>',
     1500                                sprintf(
     1501                                        '%s<br>%s',
     1502                                        esc_html__( 'The REST API request failed due to an error.' ),
     1503                                        sprintf(
     1504                                        /* translators: %1$d: The HTTP response code. %2$s: The error message returned. */
     1505                                                esc_html__( 'Error encountered: (%1$d) %2$s' ),
     1506                                                wp_remote_retrieve_response_code( $r ),
     1507                                                $r->get_error_message()
     1508                                        )
     1509                                )
     1510                        );
     1511                } elseif ( 200 !== wp_remote_retrieve_response_code( $r ) ) {
     1512                        $result['status'] = 'recommended';
     1513
     1514                        $result['label'] = esc_html__( 'The REST API encountered an unexpected result' );
     1515
     1516                        $result['description'] .= sprintf(
     1517                                '<p>%s</p>',
     1518                                sprintf(
     1519                                /* translators: %1$d: The HTTP response code returned. %2$s: The error message returned. */
     1520                                        esc_html__( 'The REST API call gave the following unexpected result: (%1$d) %2$s.' ),
     1521                                        wp_remote_retrieve_response_code( $r ),
     1522                                        wp_remote_retrieve_body( $r )
     1523                                )
     1524                        );
     1525                } else {
     1526                        $json = json_decode( wp_remote_retrieve_body( $r ), true );
     1527
     1528                        if ( false !== $json && ! isset( $json['capabilities'] ) ) {
     1529                                $result['status'] = 'recommended';
     1530
     1531                                $result['label'] = esc_html__( 'The REST API did not behave correctly' );
     1532
     1533                                $result['description'] .= sprintf(
     1534                                        '<p>%s</p>',
     1535                                        esc_html__( 'The REST API did not process the \'context\' query parameter correctly.' )
     1536                                );
     1537                        }
     1538                }
     1539
     1540                return $result;
     1541        }
     1542
     1543        /**
     1544         * Return a set of tests that belong to the site status page.
     1545         *
     1546         * Each site status test is defined here, they may be `direct` tests, that run on page load,
     1547         * or `async` tests which will run later down the line via JavaScript calls to improve page
     1548         * performance and hopefully also user experiences.
     1549         *
     1550         * @return array
     1551         */
     1552        public static function get_tests() {
     1553                $tests = array(
     1554                        'direct' => array(
     1555                                'wordpress_version' => array(
     1556                                        'label' => __( 'WordPress Version' ),
     1557                                        'test'  => 'wordpress_version',
     1558                                ),
     1559                                'plugin_version'    => array(
     1560                                        'label' => __( 'Plugin Versions' ),
     1561                                        'test'  => 'plugin_version',
     1562                                ),
     1563                                'theme_version'     => array(
     1564                                        'label' => __( 'Theme Versions' ),
     1565                                        'test'  => 'theme_version',
     1566                                ),
     1567                                'php_version'       => array(
     1568                                        'label' => __( 'PHP Version' ),
     1569                                        'test'  => 'php_version',
     1570                                ),
     1571                                'sql_server'        => array(
     1572                                        'label' => __( 'Database Server version' ),
     1573                                        'test'  => 'sql_server',
     1574                                ),
     1575                                'php_extensions'    => array(
     1576                                        'label' => __( 'PHP Extensions' ),
     1577                                        'test'  => 'php_extensions',
     1578                                ),
     1579                                'utf8mb4_support'   => array(
     1580                                        'label' => __( 'MySQL utf8mb4 support' ),
     1581                                        'test'  => 'utf8mb4_support',
     1582                                ),
     1583                                'https_status'      => array(
     1584                                        'label' => __( 'HTTPS status' ),
     1585                                        'test'  => 'https_status',
     1586                                ),
     1587                                'ssl_support'       => array(
     1588                                        'label' => __( 'Secure communication' ),
     1589                                        'test'  => 'ssl_support',
     1590                                ),
     1591                                'scheduled_events'  => array(
     1592                                        'label' => __( 'Scheduled events' ),
     1593                                        'test'  => 'scheduled_events',
     1594                                ),
     1595                                'http_requests'     => array(
     1596                                        'label' => __( 'HTTP Requests' ),
     1597                                        'test'  => 'http_requests',
     1598                                ),
     1599                                'debug_enabled'     => array(
     1600                                        'label' => __( 'Debugging enabled' ),
     1601                                        'test'  => 'is_in_debug_mode',
     1602                                ),
     1603                        ),
     1604                        'async'  => array(
     1605                                'dotorg_communication' => array(
     1606                                        'label' => __( 'Communication with WordPress.org' ),
     1607                                        'test'  => 'dotorg_communication',
     1608                                ),
     1609                                'background_updates'   => array(
     1610                                        'label' => __( 'Background updates' ),
     1611                                        'test'  => 'background_updates',
     1612                                ),
     1613                                'loopback_requests'    => array(
     1614                                        'label' => __( 'Loopback request' ),
     1615                                        'test'  => 'loopback_requests',
     1616                                ),
     1617                        ),
     1618                );
     1619
     1620                // Conditionally include REST rules if the function for it exists.
     1621                if ( function_exists( 'rest_url' ) ) {
     1622                        $tests['direct']['rest_availability'] = array(
     1623                                'label' => __( 'REST API availability' ),
     1624                                'test'  => 'rest_availability',
     1625                        );
     1626                }
     1627
     1628                /**
     1629                 * Add or modify which site status tests are ran on a site.
     1630                 *
     1631                 * The site health is determined by a set of tests based on best practices from
     1632                 * both the WordPress Hosting Team, but also web standards in general.
     1633                 *
     1634                 * Some sites may not have the same requirements, for example the automatic update
     1635                 * checks may be handled by a host, and are therefore disabled in core.
     1636                 * Or maybe you want to introduce a new test, is caching enabled/disabled/stale for example.
     1637                 *
     1638                 * Test may be added either as direct, or asynchronous ones. Any test that may require some time
     1639                 * to complete should run asynchronously, to avoid extended loading periods within wp-admin.
     1640                 *
     1641                 */
     1642                $tests = apply_filters( 'site_status_tests', $tests );
     1643
     1644                return $tests;
     1645        }
     1646
     1647        public static function admin_body_class( $body_class ) {
     1648                $body_class .= ' site-health';
     1649
     1650                return $body_class;
     1651        }
     1652
     1653        /**
     1654         * Initiate the class
     1655         *
     1656         * @uses wp_get_schedules()
     1657         * @uses Health_Check_WP_Cron::get_cron_tasks()
     1658         *
     1659         * @return void
     1660         */
     1661        public function wp_schedule_test_init() {
     1662                $this->schedules = wp_get_schedules();
     1663                $this->get_cron_tasks();
     1664        }
     1665
     1666        /**
     1667         * Populate our list of cron events and store them to a class-wide variable.
     1668         *
     1669         * Derived from `get_cron_events()` in WP Crontrol (https://plugins.svn.wordpress.org/wp-crontrol)
     1670         * by John Blackburn.
     1671         *
     1672         * @uses _get_cron_array()
     1673         * @uses WP_Error
     1674         *
     1675         * @return void
     1676         */
     1677        private function get_cron_tasks() {
     1678                $cron_tasks = _get_cron_array();
     1679
     1680                if ( empty( $cron_tasks ) ) {
     1681                        $this->crons = new WP_Error( 'no_tasks', __( 'No scheduled events exist on this site.' ) );
     1682                        return;
     1683                }
     1684
     1685                $this->crons = array();
     1686
     1687                foreach ( $cron_tasks as $time => $cron ) {
     1688                        foreach ( $cron as $hook => $dings ) {
     1689                                foreach ( $dings as $sig => $data ) {
     1690
     1691                                        $this->crons[ "$hook-$sig-$time" ] = (object) array(
     1692                                                'hook'     => $hook,
     1693                                                'time'     => $time,
     1694                                                'sig'      => $sig,
     1695                                                'args'     => $data['args'],
     1696                                                'schedule' => $data['schedule'],
     1697                                                'interval' => isset( $data['interval'] ) ? $data['interval'] : null,
     1698                                        );
     1699
     1700                                }
     1701                        }
     1702                }
     1703        }
     1704
     1705        /**
     1706         * Check if any scheduled tasks have been missed.
     1707         *
     1708         * Returns a boolean value of `true` if a scheduled task has been missed and ends processing.
     1709         * If the list of crons is an instance of WP_Error, return the instance instead of a boolean value.
     1710         *
     1711         * @uses is_wp_error()
     1712         * @uses time()
     1713         *
     1714         * @return bool|WP_Error
     1715         */
     1716        public function has_missed_cron() {
     1717                if ( is_wp_error( $this->crons ) ) {
     1718                        return $this->crons;
     1719                }
     1720
     1721                foreach ( $this->crons as $id => $cron ) {
     1722                        if ( ( $cron->time - time() ) < 0 ) {
     1723                                $this->last_missed_cron = $cron->hook;
     1724                                return true;
     1725                        }
     1726                }
     1727
     1728                return false;
     1729        }
     1730
     1731        /**
     1732         * Run a loopback test on our site.
     1733         *
     1734         * @uses wp_unslash()
     1735         * @uses base64_encode()
     1736         * @uses admin_url()
     1737         * @uses add_query_arg()
     1738         * @uses is_array()
     1739         * @uses implode()
     1740         * @uses wp_remote_get()
     1741         * @uses compact()
     1742         * @uses is_wp_error()
     1743         * @uses wp_remote_retrieve_response_code()
     1744         * @uses sprintf()
     1745         *
     1746         * @return object
     1747         */
     1748        function can_perform_loopback() {
     1749                $cookies = wp_unslash( $_COOKIE );
     1750                $timeout = 10;
     1751                $headers = array(
     1752                        'Cache-Control' => 'no-cache',
     1753                );
     1754
     1755                // Include Basic auth in loopback requests.
     1756                if ( isset( $_SERVER['PHP_AUTH_USER'] ) && isset( $_SERVER['PHP_AUTH_PW'] ) ) {
     1757                        $headers['Authorization'] = 'Basic ' . base64_encode( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) . ':' . wp_unslash( $_SERVER['PHP_AUTH_PW'] ) );
     1758                }
     1759
     1760                $url = admin_url();
     1761
     1762                $r = wp_remote_get( $url, compact( 'cookies', 'headers', 'timeout' ) );
     1763
     1764                if ( is_wp_error( $r ) ) {
     1765                        return (object) array(
     1766                                'status'  => 'critical',
     1767                                'message' => sprintf(
     1768                                        '%s<br>%s',
     1769                                        esc_html__( 'The loopback request to your site failed, this means features relying on them are not currently working as expected.' ),
     1770                                        sprintf(
     1771                                        /* translators: %1$d: The HTTP response code. %2$s: The error message returned. */
     1772                                                esc_html__( 'Error encountered: (%1$d) %2$s' ),
     1773                                                wp_remote_retrieve_response_code( $r ),
     1774                                                $r->get_error_message()
     1775                                        )
     1776                                ),
     1777                        );
     1778                }
     1779
     1780                if ( 200 !== wp_remote_retrieve_response_code( $r ) ) {
     1781                        return (object) array(
     1782                                'status'  => 'recommended',
     1783                                'message' => sprintf(
     1784                                /* translators: %d: The HTTP response code returned. */
     1785                                        esc_html__( '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.' ),
     1786                                        wp_remote_retrieve_response_code( $r )
     1787                                ),
     1788                        );
     1789                }
     1790
     1791                return (object) array(
     1792                        'status'  => 'good',
     1793                        'message' => __( 'The loopback request to your site completed successfully.' ),
     1794                );
     1795        }
     1796}
  • src/wp-admin/admin-ajax.php

     
    131131        'edit-theme-plugin-file',
    132132        'wp-privacy-export-personal-data',
    133133        'wp-privacy-erase-personal-data',
     134        'health-check-site-status',
     135        'health-check-site-status-result'
    134136);
    135137
    136138// Deprecated
  • src/wp-admin/menu.php

     
    4646        unset( $cap );
    4747}
    4848
     49$submenu['index.php'][11] = array( __( 'Site Health'), 'manage_options', 'site-health.php' );
     50
    4951$menu[4] = array( '', 'read', 'separator1', '', 'wp-menu-separator' );
    5052
    5153// $menu[5] = Posts
  • src/wp-admin/site-health-info.php

     
     1<?php
     2/**
     3 * Tools Administration Screen.
     4 *
     5 * @package WordPress
     6 * @subpackage Administration
     7 */
     8
     9/** WordPress Administration Bootstrap */
     10require_once( dirname( __FILE__ ) . '/admin.php' );
     11
     12if ( ! current_user_can( 'manage_options' ) ) {
     13        wp_die( __( 'Sorry, you do not have permission to access the debug data.' ), '', array( 'reponse' => 401 ) );
     14}
     15
     16wp_enqueue_style( 'site-health' );
     17wp_enqueue_script( 'site-health' );
     18
     19if ( ! class_exists( 'WP_Debug_Data' ) ) {
     20    require_once( ABSPATH . 'wp-admin/includes/class-wp-debug-data.php' );
     21}
     22if ( ! 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
     28add_filter( 'admin_body_class', array( 'WP_Site_Health', 'admin_body_class' ) );
     29
     30require_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 esc_html_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 esc_html_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 esc_html_e( 'Site Info' ); ?>
     69                </h2>
     70
     71                <p>
     72                        <?php esc_html_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 esc_html_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 esc_html_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 esc_html_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( $details['description'], array(
     132                                                                'a'      => array(
     133                                                                        'href' => true,
     134                                                                ),
     135                                                                'strong' => true,
     136                                                                'em'     => true,
     137                                                        ) )
     138                                                );
     139                                        }
     140                                        ?>
     141                                        <table class="widefat striped health-check-table">
     142                                                <tbody>
     143                                                <?php
     144                                                foreach ( $details['fields'] as $field ) {
     145                                                        if ( is_array( $field['value'] ) ) {
     146                                                                $values = '';
     147                                                                foreach ( $field['value'] as $name => $value ) {
     148                                                                        $values .= sprintf(
     149                                                                                '<li>%s: %s</li>',
     150                                                                                esc_html( $name ),
     151                                                                                esc_html( $value )
     152                                                                        );
     153                                                                }
     154                                                        } else {
     155                                                                $values = esc_html( $field['value'] );
     156                                                        }
     157
     158                                                        printf(
     159                                                                '<tr><td>%s</td><td>%s</td></tr>',
     160                                                                esc_html( $field['label'] ),
     161                                                                $values
     162                                                        );
     163                                                }
     164                                                ?>
     165                                                </tbody>
     166                                        </table>
     167                                </dd>
     168                        <?php } ?>
     169                </dl>
     170        </div>
     171
     172<?php
     173include( ABSPATH . 'wp-admin/admin-footer.php' );
  • src/wp-admin/site-health.php

     
     1<?php
     2/**
     3 * Tools Administration Screen.
     4 *
     5 * @package WordPress
     6 * @subpackage Administration
     7 */
     8
     9if ( isset( $_GET['tab'] ) && 'debug' === $_GET['tab'] ) {
     10    require_once( dirname( __FILE__ ) . '/site-health-info.php' );
     11    return;
     12}
     13
     14/** WordPress Administration Bootstrap */
     15require_once( dirname( __FILE__ ) . '/admin.php' );
     16
     17if ( ! current_user_can( 'manage_options' ) ) {
     18        wp_die( __( 'Sorry, you do not have permission to access site health information.' ), '', array( 'reponse' => 401 ) );
     19}
     20
     21wp_enqueue_style( 'site-health' );
     22wp_enqueue_script( 'site-health' );
     23
     24if ( ! class_exists( 'WP_Site_Health' ) ) {
     25        require_once( ABSPATH . 'wp-admin/includes/class-wp-site-health.php' );
     26}
     27
     28add_filter( 'admin_body_class', array( 'WP_Site_Health', 'admin_body_class' ) );
     29
     30$health_check_site_status = new WP_Site_Health();
     31
     32require_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 esc_html_e( 'Status' ); ?>
     52                </a>
     53
     54                <a href="<?php echo esc_url( admin_url( 'site-health.php?tab=debug' ) ); ?>" class="tab">
     55                        <?php esc_html_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 esc_html_e( 'Great job!' ); ?>
     70                </p>
     71
     72                <p>
     73                        <?php esc_html_e( 'Everything is running smoothly here.' ); ?>
     74                </p>
     75        </div>
     76
     77        <div class="site-status-has-issues">
     78                <h2>
     79                        <?php esc_html_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 esc_html_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 esc_html_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 esc_html_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 esc_html_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<?php
     115include( ABSPATH . 'wp-admin/admin-footer.php' );
  • src/wp-includes/script-loader.php

     
    16591659                        )
    16601660                );
    16611661
     1662                $scripts->add( 'site-health', "/wp-admin/js/site-health$suffix.js", array( 'jquery', 'wp-a11y' ), false, 1 );
     1663
    16621664                $scripts->add( 'updates', "/wp-admin/js/updates$suffix.js", array( 'jquery', 'wp-util', 'wp-a11y' ), false, 1 );
    16631665                did_action( 'init' ) && $scripts->localize(
    16641666                        'updates',
     
    19041906        $styles->add( 'site-icon', "/wp-admin/css/site-icon$suffix.css" );
    19051907        $styles->add( 'l10n', "/wp-admin/css/l10n$suffix.css" );
    19061908        $styles->add( 'code-editor', "/wp-admin/css/code-editor$suffix.css", array( 'wp-codemirror' ) );
     1909        $styles->add( 'site-health', "/wp-admin/css/site-health$suffix.css" );
    19071910
    19081911        $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' ) );
    19091912
  • Gruntfile.js

     
    263263                                        [ WORKING_DIR + 'wp-admin/js/tags-box.js' ]: [ './src/js/_enqueues/admin/tags-box.js' ],
    264264                                        [ WORKING_DIR + 'wp-admin/js/tags-suggest.js' ]: [ './src/js/_enqueues/admin/tags-suggest.js' ],
    265265                                        [ 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' ],
    266267                                        [ WORKING_DIR + 'wp-admin/js/theme-plugin-editor.js' ]: [ './src/js/_enqueues/wp/theme-plugin-editor.js' ],
    267268                                        [ WORKING_DIR + 'wp-admin/js/theme.js' ]: [ './src/js/_enqueues/wp/theme.js' ],
    268269                                        [ WORKING_DIR + 'wp-admin/js/updates.js' ]: [ './src/js/_enqueues/wp/updates.js' ],