4 * @output wp-admin/js/site-health.js |
4 * @output wp-admin/js/site-health.js |
5 */ |
5 */ |
6 |
6 |
7 /* global ajaxurl, ClipboardJS, SiteHealth, wp */ |
7 /* global ajaxurl, ClipboardJS, SiteHealth, wp */ |
8 |
8 |
9 jQuery( document ).ready( function( $ ) { |
9 jQuery( function( $ ) { |
10 |
10 |
11 var __ = wp.i18n.__, |
11 var __ = wp.i18n.__, |
12 _n = wp.i18n._n, |
12 _n = wp.i18n._n, |
13 sprintf = wp.i18n.sprintf, |
13 sprintf = wp.i18n.sprintf, |
14 data, |
|
15 clipboard = new ClipboardJS( '.site-health-copy-buttons .copy-button' ), |
14 clipboard = new ClipboardJS( '.site-health-copy-buttons .copy-button' ), |
|
15 isStatusTab = $( '.health-check-body.health-check-status-tab' ).length, |
16 isDebugTab = $( '.health-check-body.health-check-debug-tab' ).length, |
16 isDebugTab = $( '.health-check-body.health-check-debug-tab' ).length, |
17 pathsSizesSection = $( '#health-check-accordion-block-wp-paths-sizes' ), |
17 pathsSizesSection = $( '#health-check-accordion-block-wp-paths-sizes' ), |
18 successTimeout; |
18 successTimeout; |
19 |
19 |
20 // Debug information copy section. |
20 // Debug information copy section. |
23 successElement = $( '.success', triggerElement.closest( 'div' ) ); |
23 successElement = $( '.success', triggerElement.closest( 'div' ) ); |
24 |
24 |
25 // Clear the selection and move focus back to the trigger. |
25 // Clear the selection and move focus back to the trigger. |
26 e.clearSelection(); |
26 e.clearSelection(); |
27 // Handle ClipboardJS focus bug, see https://github.com/zenorocha/clipboard.js/issues/680 |
27 // Handle ClipboardJS focus bug, see https://github.com/zenorocha/clipboard.js/issues/680 |
28 triggerElement.focus(); |
28 triggerElement.trigger( 'focus' ); |
29 |
29 |
30 // Show success visual feedback. |
30 // Show success visual feedback. |
31 clearTimeout( successTimeout ); |
31 clearTimeout( successTimeout ); |
32 successElement.removeClass( 'hidden' ); |
32 successElement.removeClass( 'hidden' ); |
33 |
33 |
63 var goodIssuesWrapper = $( '#health-check-issues-good' ); |
63 var goodIssuesWrapper = $( '#health-check-issues-good' ); |
64 |
64 |
65 goodIssuesWrapper.toggleClass( 'hidden' ); |
65 goodIssuesWrapper.toggleClass( 'hidden' ); |
66 $( this ).attr( 'aria-expanded', ! goodIssuesWrapper.hasClass( 'hidden' ) ); |
66 $( this ).attr( 'aria-expanded', ! goodIssuesWrapper.hasClass( 'hidden' ) ); |
67 } ); |
67 } ); |
|
68 |
|
69 /** |
|
70 * Validates the Site Health test result format. |
|
71 * |
|
72 * @since 5.6.0 |
|
73 * |
|
74 * @param {Object} issue |
|
75 * |
|
76 * @return {boolean} |
|
77 */ |
|
78 function validateIssueData( issue ) { |
|
79 // Expected minimum format of a valid SiteHealth test response. |
|
80 var minimumExpected = { |
|
81 test: 'string', |
|
82 label: 'string', |
|
83 description: 'string' |
|
84 }, |
|
85 passed = true, |
|
86 key, value, subKey, subValue; |
|
87 |
|
88 // If the issue passed is not an object, return a `false` state early. |
|
89 if ( 'object' !== typeof( issue ) ) { |
|
90 return false; |
|
91 } |
|
92 |
|
93 // Loop over expected data and match the data types. |
|
94 for ( key in minimumExpected ) { |
|
95 value = minimumExpected[ key ]; |
|
96 |
|
97 if ( 'object' === typeof( value ) ) { |
|
98 for ( subKey in value ) { |
|
99 subValue = value[ subKey ]; |
|
100 |
|
101 if ( 'undefined' === typeof( issue[ key ] ) || |
|
102 'undefined' === typeof( issue[ key ][ subKey ] ) || |
|
103 subValue !== typeof( issue[ key ][ subKey ] ) |
|
104 ) { |
|
105 passed = false; |
|
106 } |
|
107 } |
|
108 } else { |
|
109 if ( 'undefined' === typeof( issue[ key ] ) || |
|
110 value !== typeof( issue[ key ] ) |
|
111 ) { |
|
112 passed = false; |
|
113 } |
|
114 } |
|
115 } |
|
116 |
|
117 return passed; |
|
118 } |
68 |
119 |
69 /** |
120 /** |
70 * Appends a new issue to the issue list. |
121 * Appends a new issue to the issue list. |
71 * |
122 * |
72 * @since 5.2.0 |
123 * @since 5.2.0 |
76 function appendIssue( issue ) { |
127 function appendIssue( issue ) { |
77 var template = wp.template( 'health-check-issue' ), |
128 var template = wp.template( 'health-check-issue' ), |
78 issueWrapper = $( '#health-check-issues-' + issue.status ), |
129 issueWrapper = $( '#health-check-issues-' + issue.status ), |
79 heading, |
130 heading, |
80 count; |
131 count; |
81 |
132 |
|
133 /* |
|
134 * Validate the issue data format before using it. |
|
135 * If the output is invalid, discard it. |
|
136 */ |
|
137 if ( ! validateIssueData( issue ) ) { |
|
138 return false; |
|
139 } |
|
140 |
82 SiteHealth.site_status.issues[ issue.status ]++; |
141 SiteHealth.site_status.issues[ issue.status ]++; |
83 |
142 |
84 count = SiteHealth.site_status.issues[ issue.status ]; |
143 count = SiteHealth.site_status.issues[ issue.status ]; |
|
144 |
|
145 // If no test name is supplied, append a placeholder for markup references. |
|
146 if ( typeof issue.test === 'undefined' ) { |
|
147 issue.test = issue.status + count; |
|
148 } |
85 |
149 |
86 if ( 'critical' === issue.status ) { |
150 if ( 'critical' === issue.status ) { |
87 heading = sprintf( |
151 heading = sprintf( |
88 _n( '%s critical issue', '%s critical issues', count ), |
152 _n( '%s critical issue', '%s critical issues', count ), |
89 '<span class="issue-count">' + count + '</span>' |
153 '<span class="issue-count">' + count + '</span>' |
117 var $progress = $( '.site-health-progress' ); |
181 var $progress = $( '.site-health-progress' ); |
118 var $wrapper = $progress.closest( '.site-health-progress-wrapper' ); |
182 var $wrapper = $progress.closest( '.site-health-progress-wrapper' ); |
119 var $progressLabel = $( '.site-health-progress-label', $wrapper ); |
183 var $progressLabel = $( '.site-health-progress-label', $wrapper ); |
120 var $circle = $( '.site-health-progress svg #bar' ); |
184 var $circle = $( '.site-health-progress svg #bar' ); |
121 var totalTests = parseInt( SiteHealth.site_status.issues.good, 0 ) + |
185 var totalTests = parseInt( SiteHealth.site_status.issues.good, 0 ) + |
122 parseInt( SiteHealth.site_status.issues.recommended, 0 ) + |
186 parseInt( SiteHealth.site_status.issues.recommended, 0 ) + |
123 ( parseInt( SiteHealth.site_status.issues.critical, 0 ) * 1.5 ); |
187 ( parseInt( SiteHealth.site_status.issues.critical, 0 ) * 1.5 ); |
124 var failedTests = ( parseInt( SiteHealth.site_status.issues.recommended, 0 ) * 0.5 ) + |
188 var failedTests = ( parseInt( SiteHealth.site_status.issues.recommended, 0 ) * 0.5 ) + |
125 ( parseInt( SiteHealth.site_status.issues.critical, 0 ) * 1.5 ); |
189 ( parseInt( SiteHealth.site_status.issues.critical, 0 ) * 1.5 ); |
126 var val = 100 - Math.ceil( ( failedTests / totalTests ) * 100 ); |
190 var val = 100 - Math.ceil( ( failedTests / totalTests ) * 100 ); |
127 |
191 |
128 if ( 0 === totalTests ) { |
192 if ( 0 === totalTests ) { |
129 $progress.addClass( 'hidden' ); |
193 $progress.addClass( 'hidden' ); |
130 return; |
194 return; |
164 |
228 |
165 $progressLabel.text( __( 'Should be improved' ) ); |
229 $progressLabel.text( __( 'Should be improved' ) ); |
166 wp.a11y.speak( __( 'All site health tests have finished running. There are items that should be addressed, and the results are now available on the page.' ) ); |
230 wp.a11y.speak( __( 'All site health tests have finished running. There are items that should be addressed, and the results are now available on the page.' ) ); |
167 } |
231 } |
168 |
232 |
169 if ( ! isDebugTab ) { |
233 if ( isStatusTab ) { |
170 $.post( |
234 $.post( |
171 ajaxurl, |
235 ajaxurl, |
172 { |
236 { |
173 'action': 'health-check-site-status-result', |
237 'action': 'health-check-site-status-result', |
174 '_wpnonce': SiteHealth.nonce.site_status_result, |
238 '_wpnonce': SiteHealth.nonce.site_status_result, |
204 |
268 |
205 doCalculation = false; |
269 doCalculation = false; |
206 |
270 |
207 this.completed = true; |
271 this.completed = true; |
208 |
272 |
209 $.post( |
273 if ( 'undefined' !== typeof( this.has_rest ) && this.has_rest ) { |
210 ajaxurl, |
274 wp.apiRequest( { |
211 data, |
275 url: wp.url.addQueryArgs( this.test, { _locale: 'user' } ), |
212 function( response ) { |
276 headers: this.headers |
|
277 } ) |
|
278 .done( function( response ) { |
|
279 /** This filter is documented in wp-admin/includes/class-wp-site-health.php */ |
|
280 appendIssue( wp.hooks.applyFilters( 'site_status_test_result', response ) ); |
|
281 } ) |
|
282 .fail( function( response ) { |
|
283 var description; |
|
284 |
|
285 if ( 'undefined' !== typeof( response.responseJSON ) && 'undefined' !== typeof( response.responseJSON.message ) ) { |
|
286 description = response.responseJSON.message; |
|
287 } else { |
|
288 description = __( 'No details available' ); |
|
289 } |
|
290 |
|
291 addFailedSiteHealthCheckNotice( this.url, description ); |
|
292 } ) |
|
293 .always( function() { |
|
294 maybeRunNextAsyncTest(); |
|
295 } ); |
|
296 } else { |
|
297 $.post( |
|
298 ajaxurl, |
|
299 data |
|
300 ).done( function( response ) { |
213 /** This filter is documented in wp-admin/includes/class-wp-site-health.php */ |
301 /** This filter is documented in wp-admin/includes/class-wp-site-health.php */ |
214 appendIssue( wp.hooks.applyFilters( 'site_status_test_result', response.data ) ); |
302 appendIssue( wp.hooks.applyFilters( 'site_status_test_result', response.data ) ); |
|
303 } ).fail( function( response ) { |
|
304 var description; |
|
305 |
|
306 if ( 'undefined' !== typeof( response.responseJSON ) && 'undefined' !== typeof( response.responseJSON.message ) ) { |
|
307 description = response.responseJSON.message; |
|
308 } else { |
|
309 description = __( 'No details available' ); |
|
310 } |
|
311 |
|
312 addFailedSiteHealthCheckNotice( this.url, description ); |
|
313 } ).always( function() { |
215 maybeRunNextAsyncTest(); |
314 maybeRunNextAsyncTest(); |
216 } |
315 } ); |
217 ); |
316 } |
218 |
317 |
219 return false; |
318 return false; |
220 } ); |
319 } ); |
221 } |
320 } |
222 |
321 |
223 if ( doCalculation ) { |
322 if ( doCalculation ) { |
224 recalculateProgression(); |
323 recalculateProgression(); |
225 } |
324 } |
226 } |
325 } |
227 |
326 |
228 if ( 'undefined' !== typeof SiteHealth && ! isDebugTab ) { |
327 /** |
|
328 * Add the details of a failed asynchronous test to the list of test results. |
|
329 * |
|
330 * @since 5.6.0 |
|
331 */ |
|
332 function addFailedSiteHealthCheckNotice( url, description ) { |
|
333 var issue; |
|
334 |
|
335 issue = { |
|
336 'status': 'recommended', |
|
337 'label': __( 'A test is unavailable' ), |
|
338 'badge': { |
|
339 'color': 'red', |
|
340 'label': __( 'Unavailable' ) |
|
341 }, |
|
342 'description': '<p>' + url + '</p><p>' + description + '</p>', |
|
343 'actions': '' |
|
344 }; |
|
345 |
|
346 /** This filter is documented in wp-admin/includes/class-wp-site-health.php */ |
|
347 appendIssue( wp.hooks.applyFilters( 'site_status_test_result', issue ) ); |
|
348 } |
|
349 |
|
350 if ( 'undefined' !== typeof SiteHealth ) { |
229 if ( 0 === SiteHealth.site_status.direct.length && 0 === SiteHealth.site_status.async.length ) { |
351 if ( 0 === SiteHealth.site_status.direct.length && 0 === SiteHealth.site_status.async.length ) { |
230 recalculateProgression(); |
352 recalculateProgression(); |
231 } else { |
353 } else { |
232 SiteHealth.site_status.issues = { |
354 SiteHealth.site_status.issues = { |
233 'good': 0, |
355 'good': 0, |
241 appendIssue( this ); |
363 appendIssue( this ); |
242 } ); |
364 } ); |
243 } |
365 } |
244 |
366 |
245 if ( 0 < SiteHealth.site_status.async.length ) { |
367 if ( 0 < SiteHealth.site_status.async.length ) { |
246 data = { |
368 maybeRunNextAsyncTest(); |
247 'action': 'health-check-' + SiteHealth.site_status.async[0].test.replace( '_', '-' ), |
|
248 '_wpnonce': SiteHealth.nonce.site_status |
|
249 }; |
|
250 |
|
251 SiteHealth.site_status.async[0].completed = true; |
|
252 |
|
253 $.post( |
|
254 ajaxurl, |
|
255 data, |
|
256 function( response ) { |
|
257 appendIssue( response.data ); |
|
258 maybeRunNextAsyncTest(); |
|
259 } |
|
260 ); |
|
261 } else { |
369 } else { |
262 recalculateProgression(); |
370 recalculateProgression(); |
263 } |
371 } |
264 } |
372 } |
265 |
373 |
266 function getDirectorySizes() { |
374 function getDirectorySizes() { |
267 var data = { |
|
268 action: 'health-check-get-sizes', |
|
269 _wpnonce: SiteHealth.nonce.site_status_result |
|
270 }; |
|
271 |
|
272 var timestamp = ( new Date().getTime() ); |
375 var timestamp = ( new Date().getTime() ); |
273 |
376 |
274 // After 3 seconds announce that we're still waiting for directory sizes. |
377 // After 3 seconds announce that we're still waiting for directory sizes. |
275 var timeout = window.setTimeout( function() { |
378 var timeout = window.setTimeout( function() { |
276 wp.a11y.speak( __( 'Please wait...' ) ); |
379 wp.a11y.speak( __( 'Please wait...' ) ); |
277 }, 3000 ); |
380 }, 3000 ); |
278 |
381 |
279 $.post( { |
382 wp.apiRequest( { |
280 type: 'POST', |
383 path: '/wp-site-health/v1/directory-sizes' |
281 url: ajaxurl, |
|
282 data: data, |
|
283 dataType: 'json' |
|
284 } ).done( function( response ) { |
384 } ).done( function( response ) { |
285 updateDirSizes( response.data || {} ); |
385 updateDirSizes( response || {} ); |
286 } ).always( function() { |
386 } ).always( function() { |
287 var delay = ( new Date().getTime() ) - timestamp; |
387 var delay = ( new Date().getTime() ) - timestamp; |
288 |
388 |
289 $( '.health-check-wp-paths-sizes.spinner' ).css( 'visibility', 'hidden' ); |
389 $( '.health-check-wp-paths-sizes.spinner' ).css( 'visibility', 'hidden' ); |
290 recalculateProgression(); |
390 recalculateProgression(); |
291 |
391 |
292 if ( delay > 3000 ) { |
392 if ( delay > 3000 ) { |
293 /* |
393 /* |
294 * We have announced that we're waiting. |
394 * We have announced that we're waiting. |
295 * Announce that we're ready after giving at least 3 seconds |
395 * Announce that we're ready after giving at least 3 seconds |
296 * for the first announcement to be read out, or the two may collide. |
396 * for the first announcement to be read out, or the two may collide. |
297 */ |
397 */ |