Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve handling of validation errors in response cache and admin display #1190

Merged
merged 10 commits into from
Jun 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 29 additions & 8 deletions includes/class-amp-theme-support.php
Original file line number Diff line number Diff line change
Expand Up @@ -1058,7 +1058,7 @@ public static function prepare_response( $response, $args = array() ) {
'allow_dirty_styles' => self::is_customize_preview_iframe(), // Dirty styles only needed when editing (e.g. for edit shortcodes).
'allow_dirty_scripts' => is_customize_preview(), // Scripts are always needed to inject changeset UUID.
'enable_response_caching' => (
( ! defined( 'WP_DEBUG' ) || true !== WP_DEBUG )
! ( defined( 'WP_DEBUG' ) && WP_DEBUG )
&&
! AMP_Validation_Manager::should_validate_response()
),
Expand All @@ -1080,8 +1080,20 @@ public static function prepare_response( $response, $args = array() ) {

$response_cache = wp_cache_get( $response_cache_key, self::RESPONSE_CACHE_GROUP );

if ( ! empty( $response_cache ) ) {
return $response_cache;
// Make sure that all of the validation errors should be sanitized in the same way; if not, then the cached body should be discarded.
if ( isset( $response_cache['validation_results'] ) ) {
foreach ( $response_cache['validation_results'] as $validation_result ) {
$should_sanitize = AMP_Validation_Error_Taxonomy::is_validation_error_sanitized( $validation_result['error'] );
if ( $should_sanitize !== $validation_result['sanitized'] ) {
unset( $response_cache['body'] );
break;
}
}
}

// Short-circuit response with cached body.
if ( isset( $response_cache['body'] ) ) {
return $response_cache['body'];
}
}

Expand All @@ -1102,14 +1114,13 @@ public static function prepare_response( $response, $args = array() ) {
1
);
}
$dom = AMP_DOM_Utils::get_dom( $response );

$xpath = new DOMXPath( $dom );

$dom = AMP_DOM_Utils::get_dom( $response );
$head = $dom->getElementsByTagName( 'head' )->item( 0 );

// Make sure scripts from the body get moved to the head.
if ( isset( $head ) ) {
// Make sure scripts from the body get moved to the head.
$xpath = new DOMXPath( $dom );
foreach ( $xpath->query( '//body//script[ @custom-element or @custom-template ]' ) as $script ) {
$head->appendChild( $script );
}
Expand Down Expand Up @@ -1208,7 +1219,17 @@ public static function prepare_response( $response, $args = array() ) {

// Cache response if enabled.
if ( true === $args['enable_response_caching'] ) {
wp_cache_set( $response_cache_key, $response, self::RESPONSE_CACHE_GROUP, MONTH_IN_SECONDS );
$response_cache = array(
'body' => $response,
'validation_results' => array_map(
function( $result ) {
unset( $result['error']['sources'] );
return $result;
},
AMP_Validation_Manager::$validation_results
),
);
wp_cache_set( $response_cache_key, $response_cache, self::RESPONSE_CACHE_GROUP, MONTH_IN_SECONDS );
}

return $response;
Expand Down
187 changes: 122 additions & 65 deletions includes/validation/class-amp-invalid-url-post-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public static function add_admin_menu_new_invalid_url_count() {
*
* @type bool $ignore_accepted Exclude validation errors that are accepted. Default false.
* }
* @return array List of errors.
* @return array List of errors, with keys for term, data, status, and (sanitization) forced.
*/
public static function get_invalid_url_validation_errors( $post, $args = array() ) {
$args = array_merge(
Expand All @@ -198,20 +198,18 @@ public static function get_invalid_url_validation_errors( $post, $args = array()
continue;
}
$term = get_term_by( 'slug', $stored_validation_error['term_slug'], AMP_Validation_Error_Taxonomy::TAXONOMY_SLUG );
if ( ! $term ) {
continue;
}
if ( $args['ignore_accepted'] && AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACCEPTED_STATUS === $term->term_group ) {

$sanitization = AMP_Validation_Error_Taxonomy::get_validation_error_sanitization( $stored_validation_error['data'] );
if ( $args['ignore_accepted'] && AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACCEPTED_STATUS === $sanitization['status'] ) {
continue;
}
$errors[] = array(
'term' => $term,
'data' => array_merge(
json_decode( $term->description, true ),
array(
'sources' => $stored_validation_error['sources'],
)

$errors[] = array_merge(
array(
'term' => $term,
'data' => $stored_validation_error['data'],
),
$sanitization
);
}
return $errors;
Expand All @@ -234,10 +232,8 @@ public static function get_invalid_url_validation_error_counts( $post ) {
);

$validation_errors = self::get_invalid_url_validation_errors( $post );
foreach ( wp_list_pluck( $validation_errors, 'term' ) as $term ) {
if ( isset( $counts[ $term->term_group ] ) ) {
$counts[ $term->term_group ]++;
}
foreach ( $validation_errors as $error ) {
$counts[ $error['status'] ]++;
}
return $counts;
}
Expand Down Expand Up @@ -318,15 +314,6 @@ public static function store_validation_errors( $validation_errors, $url ) {

$terms = array();
foreach ( $validation_errors as $data ) {
/*
* Exclude sources from data since not available unless sources are being obtained,
* and thus not able to be matched when hashed.
*/
$sources = null;
if ( isset( $data['sources'] ) ) {
$sources = $data['sources'];
}

$term_data = AMP_Validation_Error_Taxonomy::prepare_validation_error_taxonomy_term( $data );
$term_slug = $term_data['slug'];
if ( ! isset( $terms[ $term_slug ] ) ) {
Expand All @@ -352,7 +339,7 @@ public static function store_validation_errors( $validation_errors, $url ) {
$terms[ $term_slug ] = $term;
}

$stored_validation_errors[] = compact( 'term_slug', 'sources' );
$stored_validation_errors[] = compact( 'term_slug', 'data' );
}

$post_content = wp_json_encode( $stored_validation_errors );
Expand Down Expand Up @@ -627,13 +614,11 @@ public static function filter_row_actions( $actions, $post ) {
$view_url = add_query_arg( AMP_Validation_Manager::VALIDATE_QUERY_VAR, '', $url ); // Prevent redirection to non-AMP page.
$actions['view'] = sprintf( '<a href="%s">%s</a>', esc_url( $view_url ), esc_html__( 'View', 'amp' ) );

if ( ! empty( $url ) ) {
$actions[ self::RECHECK_ACTION ] = sprintf(
'<a href="%s">%s</a>',
self::get_recheck_url( $post, get_edit_post_link( $post->ID, 'raw' ), $url ),
esc_html__( 'Re-check', 'amp' )
);
}
$actions[ self::RECHECK_ACTION ] = sprintf(
'<a href="%s">%s</a>',
esc_url( self::get_recheck_url( $post, get_edit_post_link( $post->ID, 'raw' ) ) ),
esc_html__( 'Re-check', 'amp' )
);

return $actions;
}
Expand Down Expand Up @@ -708,9 +693,9 @@ public static function print_admin_notice() {
$errors_remain = ! empty( $_GET[ self::REMAINING_ERRORS ] ); // WPCS: CSRF ok.
if ( $errors_remain ) {
$class = 'notice-warning';
$message = _n( 'The rechecked URL still has validation errors.', 'The rechecked URLs still have validation errors.', $count_urls_tested, 'amp' );
$message = _n( 'The rechecked URL still has blocking validation errors.', 'The rechecked URLs still have validation errors.', $count_urls_tested, 'amp' );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice use of _n() to account for singular and plural.

} else {
$message = _n( 'The rechecked URL has no validation errors.', 'The rechecked URLs have no validation errors.', $count_urls_tested, 'amp' );
$message = _n( 'The rechecked URL has no blocking validation errors.', 'The rechecked URLs have no validation errors.', $count_urls_tested, 'amp' );
$class = 'updated';
}

Expand All @@ -722,8 +707,8 @@ public static function print_admin_notice() {
);
}

if ( isset( $_GET['amp_taxonomy_terms_updated'] ) ) { // WPCS: CSRF ok.
$count = intval( $_GET['amp_taxonomy_terms_updated'] );
$count = isset( $_GET['amp_taxonomy_terms_updated'] ) ? intval( $_GET['amp_taxonomy_terms_updated'] ) : 0; // WPCS: CSRF ok.
if ( $count > 0 ) {
$class = 'updated';
printf(
'<div class="notice is-dismissible %s"><p>%s</p><button type="button" class="notice-dismiss"><span class="screen-reader-text">%s</span></button></div>',
Expand Down Expand Up @@ -751,20 +736,14 @@ public static function print_admin_notice() {
*/
public static function handle_inline_recheck( $post_id ) {
check_admin_referer( self::NONCE_ACTION . $post_id );
$post = get_post( $post_id );
$url = $post->post_title;
if ( isset( $_GET['recheck_url'] ) ) {
$url = wp_validate_redirect( wp_unslash( $_GET['recheck_url'] ) );
}
$validation_errors = AMP_Validation_Manager::validate_url( $url );
$remaining_errors = true;
if ( is_array( $validation_errors ) ) {
self::store_validation_errors( $validation_errors, $url );
$remaining_errors = ! empty( $validation_errors );
}
$validation_results = self::recheck_post( $post_id );

$redirect = wp_get_referer();
if ( ! $redirect || empty( $validation_errors ) ) {
if ( $redirect ) {
$redirect = remove_query_arg( wp_removable_query_args(), $redirect );
}

if ( ! $redirect || is_wp_error( $validation_results ) || empty( $validation_results ) ) {
// If there are no remaining errors and the post was deleted, redirect to edit.php instead of post.php.
$redirect = add_query_arg(
'post_type',
Expand All @@ -773,13 +752,48 @@ public static function handle_inline_recheck( $post_id ) {
);
}
$args = array(
self::URLS_TESTED => '1',
self::REMAINING_ERRORS => $remaining_errors ? '1' : '0',
self::URLS_TESTED => '1',
);

// @todo For WP_Error case, see <https://github.com/Automattic/amp-wp/issues/1166>.
if ( ! is_wp_error( $validation_results ) ) {
$args[ self::REMAINING_ERRORS ] = count( array_filter(
$validation_results,
function( $result ) {
return ! $result['sanitized'];
}
) );
}

wp_safe_redirect( add_query_arg( $args, $redirect ) );
exit();
}

/**
* Re-check invalid URL post for whether it has blocking validation errors.
*
* @param int|WP_Post $post Post.
* @return array|WP_Error List of blocking validation resukts, or a WP_Error in the case of failure.
*/
public static function recheck_post( $post ) {
$post = get_post( $post );
$url = $post->post_title;

$validation_errors = AMP_Validation_Manager::validate_url( $url );
if ( is_wp_error( $validation_errors ) ) {
return $validation_errors;
}

$validation_results = array();
self::store_validation_errors( $validation_errors, $url );
foreach ( $validation_errors as $error ) {
$sanitized = AMP_Validation_Error_Taxonomy::is_validation_error_sanitized( $error );

$validation_results[] = compact( 'error', 'sanitized' );
}
return $validation_results;
}

/**
* Handle validation error status update.
*
Expand All @@ -792,6 +806,10 @@ public static function handle_validation_error_status_update() {
if ( empty( $_POST[ AMP_Validation_Manager::VALIDATION_ERROR_TERM_STATUS_QUERY_VAR ] ) || ! is_array( $_POST[ AMP_Validation_Manager::VALIDATION_ERROR_TERM_STATUS_QUERY_VAR ] ) ) {
return;
}
$post = get_post();
if ( ! $post || self::POST_TYPE_SLUG !== $post->post_type ) {
return;
}
$updated_count = 0;

$has_pre_term_description_filter = has_filter( 'pre_term_description', 'wp_filter_kses' );
Expand Down Expand Up @@ -819,7 +837,32 @@ public static function handle_validation_error_status_update() {
$args = array(
'amp_taxonomy_terms_updated' => $updated_count,
);
wp_safe_redirect( add_query_arg( $args, wp_get_referer() ) );

/*
* Re-check the post after the validation status change. This is particularly important for validation errors like
* 'removed_unused_css_rules' since whether it is accepted will determine whether other validation errors are triggered
* such as in this case 'excessive_css'.
*/
if ( $updated_count > 0 ) {
$validation_results = self::recheck_post( $post->ID );
// @todo For WP_Error case, see <https://github.com/Automattic/amp-wp/issues/1166>.
if ( ! is_wp_error( $validation_results ) ) {
$args[ self::REMAINING_ERRORS ] = count( array_filter(
$validation_results,
function( $result ) {
return ! $result['sanitized'];
}
) );
}
}

$redirect = wp_get_referer();
if ( ! $redirect ) {
$redirect = get_edit_post_link( $post->ID );
}

$redirect = remove_query_arg( wp_removable_query_args(), $redirect );
wp_safe_redirect( add_query_arg( $args, $redirect ) );
exit();
}

Expand Down Expand Up @@ -921,7 +964,7 @@ public static function print_validation_errors_meta_box( $post ) {
$validation_errors = self::get_invalid_url_validation_errors( $post );

$can_serve_amp = 0 === count( array_filter( $validation_errors, function( $validation_error ) {
return AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACCEPTED_STATUS !== $validation_error['term']->term_group;
return AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACCEPTED_STATUS !== $validation_error['status'];
} ) );
?>
<style>
Expand Down Expand Up @@ -966,6 +1009,22 @@ public static function print_validation_errors_meta_box( $post ) {
} );
</script>

<?php
$has_forced_sanitized = false;
foreach ( $validation_errors as $validation_error ) {
if ( $validation_error['forced'] ) {
Copy link
Contributor

@kienstra kienstra Jun 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're probably aware of this, but I saw notices on an "Invalid AMP Page (URL)" pages:

( ! ) Notice: Undefined index: forced in /srv/www/loc/public_html/wp-content/plugins/amp/includes/validation/class-amp-invalid-url-post-type.php on line 1015

amp-invalid-url

This occurred even after clicking "Re-check."

These could be from errors saved from before this PR. I tried bulk moving "Invalid Pages" to the trash, but this didn't help.

Still, you mentioned that we don't need backwards compatibility with an earlier "checkout" of 1.0. And the way validation errors are stored is very different from in 0.7.

$has_forced_sanitized = true;
break;
}
}
?>

<?php if ( $has_forced_sanitized ) : ?>
<div class="notice notice-info notice-alt inline">
<p><?php esc_html_e( 'This site is configured to automatically decide whether or not some validation errors may be accepted. For these errors, you cannot change their status below.', 'amp' ); ?></p>
</div>
<?php endif; ?>

<div class="amp-validation-errors">
<ul>
<?php foreach ( $validation_errors as $error ) : ?>
Expand All @@ -975,23 +1034,23 @@ public static function print_validation_errors_meta_box( $post ) {
$select_name = sprintf( '%s[%s]', AMP_Validation_Manager::VALIDATION_ERROR_TERM_STATUS_QUERY_VAR, $term->slug );
?>
<li>
<details <?php echo ( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_NEW_STATUS === $term->term_group ) ? 'open' : ''; ?>>
<details <?php echo ( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_NEW_STATUS === $error['status'] ) ? 'open' : ''; ?>>
<summary>
<label for="<?php echo esc_attr( $select_name ); ?>" class="screen-reader-text">
<?php esc_html_e( 'Status:', 'amp' ); ?>
</label>
<select class="amp-validation-error-status" id="<?php echo esc_attr( $select_name ); ?>" name="<?php echo esc_attr( $select_name ); ?>">
<?php if ( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_NEW_STATUS === $term->term_group ) : ?>
<select class="amp-validation-error-status" id="<?php echo esc_attr( $select_name ); ?>" name="<?php echo esc_attr( $select_name ); ?>" <?php disabled( $error['forced'] ); ?>>
<?php if ( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_NEW_STATUS === $error['status'] ) : ?>
<option value=""><?php esc_html_e( 'New', 'amp' ); ?></option>
<?php endif; ?>
<option value="<?php echo esc_attr( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACCEPTED_STATUS ); ?>" <?php selected( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACCEPTED_STATUS, $term->term_group ); ?>><?php esc_html_e( 'Accepted', 'amp' ); ?></option>
<option value="<?php echo esc_attr( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_REJECTED_STATUS ); ?>" <?php selected( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_REJECTED_STATUS, $term->term_group ); ?>><?php esc_html_e( 'Rejected', 'amp' ); ?></option>
<option value="<?php echo esc_attr( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACCEPTED_STATUS ); ?>" <?php selected( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACCEPTED_STATUS, $error['status'] ); ?>><?php esc_html_e( 'Accepted', 'amp' ); ?></option>
<option value="<?php echo esc_attr( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_REJECTED_STATUS ); ?>" <?php selected( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_REJECTED_STATUS, $error['status'] ); ?>><?php esc_html_e( 'Rejected', 'amp' ); ?></option>
</select>
<?php if ( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_NEW_STATUS === $term->term_group ) : ?>
<?php if ( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_NEW_STATUS === $error['status'] ) : ?>
&#x2753;
<?php elseif ( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_REJECTED_STATUS === $term->term_group ) : ?>
<?php elseif ( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_REJECTED_STATUS === $error['status'] ) : ?>
&#x274C;
<?php elseif ( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACCEPTED_STATUS === $term->term_group ) : ?>
<?php elseif ( AMP_Validation_Error_Taxonomy::VALIDATION_ERROR_ACCEPTED_STATUS === $error['status'] ) : ?>
&#x2705;
<?php endif; ?>
<code><?php echo esc_html( $error['data']['code'] ); ?></code>
Expand Down Expand Up @@ -1147,15 +1206,13 @@ public static function filter_the_title_in_post_list_table( $title, $post ) {
*
* @param WP_Post $post The post storing the validation error.
* @param string $redirect_url The URL of the redirect.
* @param string $recheck_url The URL to check. Optional.
* @return string The URL to recheck the post.
*/
public static function get_recheck_url( $post, $redirect_url, $recheck_url = null ) {
public static function get_recheck_url( $post, $redirect_url ) {
return wp_nonce_url(
add_query_arg(
array(
'action' => self::RECHECK_ACTION,
'recheck_url' => $recheck_url,
'action' => self::RECHECK_ACTION,
),
$redirect_url
),
Expand Down
Loading