wp/wp-admin/js/site-health.js
changeset 18 be944660c56a
parent 16 a86126ab1dd4
child 21 48c4eec2b7e6
equal deleted inserted replaced
17:34716fd837a4 18:be944660c56a
     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;
   140 		}
   204 		}
   141 		if ( 100 < val ) {
   205 		if ( 100 < val ) {
   142 			val = 100;
   206 			val = 100;
   143 		}
   207 		}
   144 
   208 
   145 		pct = ( ( 100 - val ) / 100 ) * c;
   209 		pct = ( ( 100 - val ) / 100 ) * c + 'px';
   146 
   210 
   147 		$circle.css( { strokeDashoffset: pct } );
   211 		$circle.css( { strokeDashoffset: pct } );
   148 
   212 
   149 		if ( 1 > parseInt( SiteHealth.site_status.issues.critical, 0 ) ) {
   213 		if ( 1 > parseInt( SiteHealth.site_status.issues.critical, 0 ) ) {
   150 			$( '#health-check-issues-critical' ).addClass( 'hidden' );
   214 			$( '#health-check-issues-critical' ).addClass( 'hidden' );
   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 				 */
   342 			getDirectorySizes();
   442 			getDirectorySizes();
   343 		} else {
   443 		} else {
   344 			recalculateProgression();
   444 			recalculateProgression();
   345 		}
   445 		}
   346 	}
   446 	}
       
   447 
       
   448 	// Trigger a class toggle when the extended menu button is clicked.
       
   449 	$( '.health-check-offscreen-nav-wrapper' ).on( 'click', function() {
       
   450 		$( this ).toggleClass( 'visible' );
       
   451 	} );
   347 } );
   452 } );