value to not filter at all, like for 'All Statuses'.
*
* This is also used in WP_List_Table, like for the 'Bulk Actions' option.
* When this is present, this ensures that this isn't filtered.
*
* @var int
*/
const NO_FILTER_VALUE = '';
/**
* Validation code for an invalid element.
*
* @var string
*/
const INVALID_ELEMENT_CODE = 'invalid_element';
/**
* Validation code for an invalid attribute.
*
* @var string
*/
const INVALID_ATTRIBUTE_CODE = 'invalid_attribute';
/**
* The 'type' of error for invalid HTML elements, like .
*
* These usually have the 'code' of 'invalid_element'.
* Except for 'invalid_element' errors for a
cap->edit_posts ) ) {
return;
}
$id = 'link-errors-url';
printf(
'%s ',
esc_url(
add_query_arg(
'post_type',
AMP_Validated_URL_Post_Type::POST_TYPE_SLUG,
admin_url( 'edit.php' )
)
),
esc_attr( $id ),
esc_html__( 'View Validated URLs', 'amp' )
);
?>
element.
*
* There is a difference how the errors are counted, depending on which screen this is on.
* For example: Accepted Errors (10).
* This status filter element is rendered on the validation error post page (Errors by URL),
* and the validation error taxonomy page (Error Index).
* On the taxonomy page, this simply needs to count the number of terms with a given type.
* On the post page, this needs to count the number of posts that have at least one error of a given type.
*/
public static function render_error_status_filter() {
global $wp_query;
$screen_base = get_current_screen()->base;
if ( 'edit-tags' === $screen_base ) {
$total_term_count = self::get_validation_error_count();
$ack_rejected_term_count = self::get_validation_error_count( array( 'group' => array( self::VALIDATION_ERROR_ACK_REJECTED_STATUS ) ) );
$ack_accepted_term_count = self::get_validation_error_count( array( 'group' => array( self::VALIDATION_ERROR_ACK_ACCEPTED_STATUS ) ) );
$new_term_count = $total_term_count - $ack_rejected_term_count - $ack_accepted_term_count;
} elseif ( 'edit' === $screen_base ) {
$args = array(
'post_type' => AMP_Validated_URL_Post_Type::POST_TYPE_SLUG,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
);
$error_type = sanitize_key( $wp_query->get( self::VALIDATION_ERROR_TYPE_QUERY_VAR ) );
if ( $error_type && in_array( $error_type, self::get_error_types(), true ) ) {
$args[ self::VALIDATION_ERROR_TYPE_QUERY_VAR ] = $error_type;
}
$with_new_query = new WP_Query(
array_merge(
$args,
array(
self::VALIDATION_ERROR_STATUS_QUERY_VAR => array(
self::VALIDATION_ERROR_NEW_ACCEPTED_STATUS,
self::VALIDATION_ERROR_NEW_REJECTED_STATUS,
),
)
)
);
$new_term_count = $with_new_query->found_posts;
$with_rejected_query = new WP_Query(
array_merge(
$args,
array( self::VALIDATION_ERROR_STATUS_QUERY_VAR => self::VALIDATION_ERROR_ACK_REJECTED_STATUS )
)
);
$ack_rejected_term_count = $with_rejected_query->found_posts;
$with_accepted_query = new WP_Query(
array_merge(
$args,
array( self::VALIDATION_ERROR_STATUS_QUERY_VAR => self::VALIDATION_ERROR_ACK_ACCEPTED_STATUS )
)
);
$ack_accepted_term_count = $with_accepted_query->found_posts;
} else {
return;
}
$selected_groups = array();
if ( isset( $_GET[ self::VALIDATION_ERROR_STATUS_QUERY_VAR ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$selected_groups = self::sanitize_term_status( $_GET[ self::VALIDATION_ERROR_STATUS_QUERY_VAR ], array( 'multiple' => true ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
}
if ( ! empty( $selected_groups ) ) {
sort( $selected_groups );
$error_status_filter_value = implode( ',', $selected_groups );
} else {
$error_status_filter_value = self::NO_FILTER_VALUE;
}
?>
(%s)',
'With New Errors (%s) ',
$new_term_count,
'terms',
'amp'
),
number_format_i18n( $new_term_count )
);
} else {
$new_term_text = sprintf(
/* translators: %s: the new term count. */
_nx(
'New Error (%s) ',
'New Errors (%s) ',
$new_term_count,
'terms',
'amp'
),
number_format_i18n( $new_term_count )
);
}
$value = self::VALIDATION_ERROR_NEW_REJECTED_STATUS . ',' . self::VALIDATION_ERROR_NEW_ACCEPTED_STATUS;
?>
>
(%s)',
'With Accepted Errors (%s) ',
$ack_accepted_term_count,
'terms',
'amp'
),
number_format_i18n( $ack_accepted_term_count )
);
} else {
$accepted_term_text = sprintf(
/* translators: %s: the accepted term count. */
_nx(
'Accepted Error (%s) ',
'Accepted Errors (%s) ',
$ack_accepted_term_count,
'terms',
'amp'
),
number_format_i18n( $ack_accepted_term_count )
);
}
$value = self::VALIDATION_ERROR_ACK_ACCEPTED_STATUS;
?>
>
(%s)',
'With Rejected Errors (%s) ',
$ack_rejected_term_count,
'terms',
'amp'
),
number_format_i18n( $ack_rejected_term_count )
);
} else {
$rejected_term_text = sprintf(
/* translators: %s: the rejected term count. */
_nx(
'Rejected Error (%s) ',
'Rejected Errors (%s) ',
$ack_rejected_term_count,
'terms',
'amp'
),
number_format_i18n( $ack_rejected_term_count )
);
}
$value = self::VALIDATION_ERROR_ACK_REJECTED_STATUS;
?>
>
element is rendered on the validation error post page (Errors by URL),
* and the validation error taxonomy page (Error Index).
*/
public static function render_error_type_filter() {
$error_type_filter_value = isset( $_GET[ self::VALIDATION_ERROR_TYPE_QUERY_VAR ] ) ? sanitize_key( wp_unslash( $_GET[ self::VALIDATION_ERROR_TYPE_QUERY_VAR ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
/*
* On the 'Errors by URL' page, the text should be different.
* For example, it should be 'With JS Errors' instead of 'JS Errors'.
*/
$screen_base = get_current_screen()->base;
?>
>
>
>
>
get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE taxonomy = %s AND count = 0", self::TAXONOMY_SLUG ) );
if ( $count > 0 ) {
wp_nonce_field( self::VALIDATION_ERROR_CLEAR_EMPTY_ACTION, self::VALIDATION_ERROR_CLEAR_EMPTY_ACTION . '_nonce', false );
submit_button( __( 'Clear Empty', 'amp' ), '', self::VALIDATION_ERROR_CLEAR_EMPTY_ACTION, false );
}
}
/**
* Prevent user from being able to delete validation errors in order to disable the checkbox on the post list table.
*
* Yes, this is not ideal.
*
* @param array $allcaps All caps.
* @param array $caps Requested caps.
* @param array $args Cap args.
* @return array All caps.
*/
public static function filter_user_has_cap_for_hiding_term_list_table_checkbox( $allcaps, $caps, $args ) {
unset( $caps );
if ( isset( $args[0] ) && 'delete_term' === $args[0] ) {
$term = get_term( $args[2] );
$error = json_decode( $term->description, true );
if ( ! is_array( $error ) ) {
return $allcaps;
}
}
return $allcaps;
}
/**
* Include searching taxonomy term descriptions and sources term meta.
*
* @param array $clauses Clauses.
* @param array $taxonomies Taxonomies.
* @param array $args Args.
* @return array Clauses.
*/
public static function filter_terms_clauses_for_description_search( $clauses, $taxonomies, $args ) {
global $wpdb;
if ( ! empty( $args['search'] ) && in_array( self::TAXONOMY_SLUG, $taxonomies, true ) ) {
$clauses['where'] = preg_replace(
'#(?<=\()(?=\(t\.name LIKE \')#',
$wpdb->prepare( '(tt.description LIKE %s) OR ', '%' . $wpdb->esc_like( $args['search'] ) . '%' ),
$clauses['where']
);
}
return $clauses;
}
/**
* Show notices for changes to amp_validation_error terms.
*/
public static function add_admin_notices() {
if ( ! ( self::TAXONOMY_SLUG === get_current_screen()->taxonomy || AMP_Validated_URL_Post_Type::POST_TYPE_SLUG === get_current_screen()->post_type ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return;
}
// Show success messages for accepting/rejecting validation errors.
if ( ! empty( $_GET['amp_actioned'] ) && ! empty( $_GET['amp_actioned_count'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$actioned = sanitize_key( $_GET['amp_actioned'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$count = intval( $_GET['amp_actioned_count'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$message = null;
if ( self::VALIDATION_ERROR_ACCEPT_ACTION === $actioned ) {
$message = sprintf(
/* translators: %s is number of errors accepted */
_n(
'Accepted %s error. It will no longer block related URLs from being served as AMP.',
'Accepted %s errors. They will no longer block related URLs from being served as AMP.',
number_format_i18n( $count ),
'amp'
),
$count
);
} elseif ( self::VALIDATION_ERROR_REJECT_ACTION === $actioned ) {
$message = sprintf(
/* translators: %s is number of errors rejected */
_n(
'Rejected %s error. It will continue to block related URLs from being served as AMP.',
'Rejected %s errors. They will continue to block related URLs from being served as AMP.',
number_format_i18n( $count ),
'amp'
),
$count
);
}
if ( $message ) {
printf( '', esc_html( $message ) );
}
}
// Show success message for clearing empty terms.
if ( isset( $_GET[ self::VALIDATION_ERRORS_CLEARED_QUERY_VAR ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$cleared_count = (int) $_GET[ self::VALIDATION_ERRORS_CLEARED_QUERY_VAR ]; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
printf(
'',
esc_html(
sprintf(
/* translators: %s is the number of validation errors cleared */
_n(
'Cleared %s validation error that no longer occurs on the site.',
'Cleared %s validation errors that no longer occur on the site.',
$cleared_count,
'amp'
),
number_format_i18n( $cleared_count )
)
)
);
}
}
/**
* Add row actions.
*
* @param array $actions Actions.
* @param WP_Term $tag Tag.
* @return array Actions.
*/
public static function filter_tag_row_actions( $actions, WP_Term $tag ) {
global $pagenow;
if ( self::TAXONOMY_SLUG === $tag->taxonomy ) {
$term_id = $tag->term_id;
$term = get_term( $tag->term_id ); // We don't want filter=display given by $tag.
/*
* Hide deletion link since a validation error should only be removed once
* it no longer has an occurrence on the site. When a validated URL is re-checked
* and it no longer has this validation error, then the count will be decremented.
* When a validation error term no longer has a count, then it is hidden from the
* list table. A cron job could periodically delete terms that have no counts.
*/
unset( $actions['delete'] );
if ( 'post.php' === $pagenow ) {
$actions['details'] = sprintf(
'%s ',
esc_attr__( 'Toggle error details', 'amp' ),
esc_html__( 'Details', 'amp' )
);
} else {
$actions['details'] = sprintf(
'%s ',
admin_url(
add_query_arg(
array(
self::TAXONOMY_SLUG => $term->name,
'post_type' => AMP_Validated_URL_Post_Type::POST_TYPE_SLUG,
),
'edit.php'
)
),
esc_html__( 'Details', 'amp' )
);
}
// @todo We should consider reversing the order.
// Only add the 'Reject' and 'Accept' links to the index page, not the individual URL page.
$sanitization = self::get_validation_error_sanitization( json_decode( $term->description, true ) );
if ( 'edit-tags.php' === $pagenow && self::VALIDATION_ERROR_ACK_REJECTED_STATUS !== $sanitization['term_status'] ) {
$actions[ self::VALIDATION_ERROR_REJECT_ACTION ] = sprintf(
'%s ',
wp_nonce_url(
add_query_arg( array_merge( array( 'action' => self::VALIDATION_ERROR_REJECT_ACTION ), compact( 'term_id' ) ) ),
self::VALIDATION_ERROR_REJECT_ACTION
),
esc_html__( 'Reject', 'amp' )
);
}
if ( 'edit-tags.php' === $pagenow && self::VALIDATION_ERROR_ACK_ACCEPTED_STATUS !== $sanitization['term_status'] ) {
$actions[ self::VALIDATION_ERROR_ACCEPT_ACTION ] = sprintf(
'%s ',
wp_nonce_url(
add_query_arg( array_merge( array( 'action' => self::VALIDATION_ERROR_ACCEPT_ACTION ), compact( 'term_id' ) ) ),
self::VALIDATION_ERROR_ACCEPT_ACTION
),
esc_html__( 'Accept', 'amp' )
);
}
}
return $actions;
}
/**
* Show AMP validation errors under AMP admin menu.
*/
public static function add_admin_menu_validation_error_item() {
$menu_item_label = esc_html__( 'Error Index', 'amp' );
$new_error_count = self::get_validation_error_count(
array(
'group' => array( self::VALIDATION_ERROR_NEW_REJECTED_STATUS, self::VALIDATION_ERROR_NEW_ACCEPTED_STATUS ),
)
);
if ( $new_error_count ) {
$menu_item_label .= ' ' . esc_html( number_format_i18n( $new_error_count ) ) . ' ';
}
$taxonomy_caps = (object) get_taxonomy( self::TAXONOMY_SLUG )->cap; // Yes, cap is an object not an array.
add_submenu_page(
AMP_Options_Manager::OPTION_NAME,
$menu_item_label,
$menu_item_label,
$taxonomy_caps->manage_terms,
// The following esc_attr() is sadly needed due to .
esc_attr( 'edit-tags.php?taxonomy=' . self::TAXONOMY_SLUG . '&post_type=' . AMP_Validated_URL_Post_Type::POST_TYPE_SLUG )
);
}
/**
* Parses the term query on post.php pages (single error URL).
*
* This post.php page for amp_validated_url is more like an edit-tags.php page,
* in that it has a WP_Terms_List_Table of terms (of type amp_validation_error).
* So this needs to only show the terms (errors) associated with this amp_validated_url post.
*
* @param WP_Term_Query $wp_term_query Instance of WP_Term_Query.
*/
public static function parse_post_php_term_query( $wp_term_query ) {
global $pagenow;
if ( ! is_admin() || 'post.php' !== $pagenow || ! isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return;
}
// Only set the query var if this is the validated URL post type.
$post_id = (int) $_GET['post']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( AMP_Validated_URL_Post_Type::POST_TYPE_SLUG === get_post_type( $post_id ) ) {
$wp_term_query->query_vars['object_ids'] = $post_id;
}
}
/**
* Provides a reader-friendly string for a term's error type.
*
* @param string $error_type The error type from the term's validation error JSON.
* @return string Reader-friendly string.
*/
public static function get_reader_friendly_error_type_text( $error_type ) {
switch ( $error_type ) {
case 'js_error':
return esc_html__( 'JS', 'amp' );
case 'html_element_error':
return esc_html__( 'HTML (Element)', 'amp' );
case 'html_attribute_error':
return esc_html__( 'HTML (Attribute)', 'amp' );
case 'css_error':
return esc_html__( 'CSS', 'amp' );
default:
return $error_type;
}
}
/**
* Provides the label for the details summary element.
*
* @param array $validation_error Validation error data.
* @return string The label.
*/
public static function get_details_summary_label( $validation_error ) {
if ( self::INVALID_ATTRIBUTE_CODE === $validation_error['code'] || self::INVALID_ELEMENT_CODE === $validation_error['code'] ) {
$summary_label = sprintf( '<%s>', $validation_error['parent_name'] );
} elseif ( isset( $validation_error['node_name'] ) ) {
$summary_label = sprintf( '<%s>', $validation_error['node_name'] );
} else {
$summary_label = '…';
}
return sprintf( '%s', esc_html( $summary_label ) );
}
/**
* Supply the content for the custom columns.
*
* @param string $content Column content.
* @param string $column_name Column name.
* @param int $term_id Term ID.
* @return string Content.
*/
public static function filter_manage_custom_columns( $content, $column_name, $term_id ) {
global $pagenow;
$term = get_term( $term_id );
$validation_error = json_decode( $term->description, true );
if ( ! isset( $validation_error['code'] ) ) {
$validation_error['code'] = 'unknown';
}
switch ( $column_name ) {
case 'error':
if ( 'post.php' === $pagenow ) {
$content .= sprintf(
'',
esc_attr__( 'Toggle error details', 'amp' )
);
$content .= sprintf( '%s', esc_html( $validation_error['code'] ) );
} else {
$content .= '';
$content .= sprintf(
'%s ',
admin_url(
add_query_arg(
array(
self::TAXONOMY_SLUG => $term->name,
'post_type' => AMP_Validated_URL_Post_Type::POST_TYPE_SLUG,
),
'edit.php'
)
),
esc_html( $validation_error['code'] )
);
}
if ( self::INVALID_ELEMENT_CODE === $validation_error['code'] ) {
$content .= sprintf( ': <%s>', esc_html( $validation_error['node_name'] ) );
} elseif ( self::INVALID_ATTRIBUTE_CODE === $validation_error['code'] ) {
$content .= sprintf( ': [%s]', esc_html( $validation_error['node_name'] ) );
} elseif ( 'illegal_css_at_rule' === $validation_error['code'] ) {
$content .= sprintf( ': @%s', esc_html( $validation_error['at_rule'] ) );
}
if ( 'post.php' === $pagenow ) {
$content .= '
';
} else {
$content .= '
';
}
if ( isset( $validation_error['message'] ) ) {
$content .= sprintf( '%s
', esc_html( $validation_error['message'] ) );
}
break;
case 'status':
if ( 'post.php' === $pagenow ) {
$select_name = sprintf( '%s[%s]', AMP_Validation_Manager::VALIDATION_ERROR_TERM_STATUS_QUERY_VAR, $term->slug );
switch ( $term->term_group ) {
case self::VALIDATION_ERROR_NEW_REJECTED_STATUS:
$img_src = 'baseline-error-red';
break;
case self::VALIDATION_ERROR_NEW_ACCEPTED_STATUS:
$img_src = 'baseline-error-green';
break;
case self::VALIDATION_ERROR_ACK_ACCEPTED_STATUS:
$img_src = 'baseline-check-circle-green';
break;
case self::VALIDATION_ERROR_ACK_REJECTED_STATUS:
$img_src = 'error-rejected';
break;
}
if ( ! isset( $img_src ) ) {
break;
}
ob_start();
?>
term_group || self::VALIDATION_ERROR_NEW_REJECTED_STATUS === $term->term_group ) : ?>
term_group ) : ?>
term_group ); ?> data-status-icon="">
term_group ); ?> data-status-icon="">
setTimezone( new DateTimeZone( get_option( 'timezone_string' ) ) );
}
} catch ( Exception $e ) {
unset( $e );
}
}
if ( ! $created_datetime ) {
$time_ago = __( 'n/a', 'amp' );
} else {
$time_ago = sprintf(
'%s ',
esc_attr(
$created_datetime->format(
/* translators: localized date and time format, see http://php.net/date */
__( 'F j, Y g:i a', 'amp' )
)
),
/* translators: %s: human readable timestamp */
esc_html( sprintf( __( '%s ago', 'amp' ), human_time_diff( $created_datetime->getTimestamp() ) ) )
);
}
if ( $created_datetime ) {
$time_ago = sprintf(
'%s ',
$created_datetime->format( 'c' ),
$time_ago
);
}
$content .= $time_ago;
break;
case 'details':
if ( 'post.php' === $pagenow ) {
return self::render_single_url_error_details( $validation_error, $term );
}
if ( isset( $validation_error['parent_name'] ) ) {
$summary = self::get_details_summary_label( $validation_error );
unset( $validation_error['error_type'] );
unset( $validation_error['parent_name'] );
$attributes = array();
$attributes_heading = '';
if ( ! empty( $validation_error['node_attributes'] ) ) {
$attributes_heading = sprintf( '%s
', esc_html__( 'Element attributes:', 'amp' ) );
$attributes = $validation_error['node_attributes'];
} elseif ( ! empty( $validation_error['element_attributes'] ) ) {
$attributes_heading = sprintf( '%s
', esc_html__( 'Other attributes:', 'amp' ) );
$attributes = $validation_error['element_attributes'];
}
if ( empty( $attributes ) ) {
$content .= $summary;
} else {
$content = '';
$content .= '';
$content .= $summary;
$content .= ' ';
$content .= $attributes_heading;
$content .= '';
foreach ( $attributes as $attr => $value ) {
$content .= sprintf( '%s ', esc_html( $attr ) );
if ( isset( $value ) ) {
$content .= sprintf( ': %s ', esc_html( $value ) );
}
$content .= ' ';
}
$content .= ' ';
$content .= ' ';
}
}
break;
case 'sources_with_invalid_output':
if ( ! isset( $_GET['post'], $_GET['action'] ) || 'edit' !== $_GET['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
break;
}
$url_post_id = intval( $_GET['post'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$validation_errors = AMP_Validated_URL_Post_Type::get_invalid_url_validation_errors( $url_post_id );
$validation_errors = array_filter(
$validation_errors,
function( $error ) use ( $term ) {
return $error['term']->term_id === $term->term_id;
}
);
$error_summary = self::summarize_validation_errors( wp_list_pluck( $validation_errors, 'data' ) );
AMP_Validated_URL_Post_Type::render_sources_column( $error_summary, $url_post_id );
break;
case 'error_type':
if ( isset( $validation_error['type'] ) ) {
$text = self::get_reader_friendly_error_type_text( $validation_error['type'] );
if ( 'post.php' === $pagenow ) {
$content .= sprintf(
'%s
',
isset( $validation_error['type'] ) ? $validation_error['type'] : '',
esc_html( $text )
);
} else {
$content .= $text;
}
}
break;
}
return $content;
}
/**
* Adds post columns to the /wp-admin/post.php page for amp_validated_url.
*
* @param array $sortable_columns The sortable columns.
* @return array $sortable_columns The filtered sortable columns.
*/
public static function add_single_post_sortable_columns( $sortable_columns ) {
return array_merge(
$sortable_columns,
array(
'error' => self::VALIDATION_DETAILS_ERROR_CODE_QUERY_VAR,
'error_type' => self::VALIDATION_ERROR_TYPE_QUERY_VAR,
)
);
}
/**
* Renders error details when viewing a single URL page.
*
* @param array $validation_error Validation error data.
* @param WP_Term $term The validation error term.
* @return string HTML for the details section.
*/
public static function render_single_url_error_details( $validation_error, $term ) {
// Get the sources, if they exist.
if ( isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$validation_errors = AMP_Validated_URL_Post_Type::get_invalid_url_validation_errors( intval( $_GET['post'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
foreach ( $validation_errors as $error ) {
if ( isset( $error['data']['sources'], $error['term']->term_id ) && $error['term']->term_id === $term->term_id ) {
$validation_error['sources'] = $error['data']['sources'];
break;
}
}
}
ob_start();
?>
0 ) {
echo ' … ';
}
echo '>';
?>
1 ) {
echo ' …';
}
echo '
';
printf( ' %s="%s"', esc_html( $validation_error['node_name'] ), esc_html( $validation_error['element_attributes'][ $validation_error['node_name'] ] ) );
echo ' ';
if ( count( $validation_error['element_attributes'] ) > 1 ) {
echo ' …';
}
echo '>';
?>
$value ) : ?>
>
$attr ) : ?>
%s', esc_html( $key ) );
if ( ! empty( $attr ) ) :
printf( ': %s', esc_html( $attr ) );
endif;
?>
%s %s',
self::get_details_summary_label( $validation_error ),
ob_get_clean()
);
}
/**
* Gets the translated error type name from the given the validation error.
*
* @param array $validation_error The validation error data.
* @return string|null $slug The translated type of the error.
*/
public static function get_translated_type_name( $validation_error ) {
if ( ! isset( $validation_error['type'] ) ) {
return null;
}
$translated_names = array(
self::HTML_ELEMENT_ERROR_TYPE => __( 'HTML Element', 'amp' ),
self::HTML_ATTRIBUTE_ERROR_TYPE => __( 'HTML Attribute', 'amp' ),
self::JS_ERROR_TYPE => __( 'JavaScript', 'amp' ),
self::CSS_ERROR_TYPE => __( 'CSS', 'amp' ),
);
if ( isset( $translated_names[ $validation_error['type'] ] ) ) {
return $translated_names[ $validation_error['type'] ];
}
return null;
}
/**
* Handle inline edit links.
*/
public static function handle_inline_edit_request() {
// Check for necessary arguments.
if ( ! isset( $_GET['action'] ) || ! isset( $_GET['_wpnonce'] ) || ! isset( $_GET['term_id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return;
}
// Check if we are on either the taxonomy page or a single error page (which has the post_type argument).
if ( self::TAXONOMY_SLUG !== get_current_screen()->taxonomy && ! isset( $_GET['post_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return;
}
// If we have a post_type check that it is the correct one.
if ( isset( $_GET['post_type'] ) && \AMP_Validated_URL_Post_Type::POST_TYPE_SLUG !== $_GET['post_type'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return;
}
$action = sanitize_key( $_GET['action'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
check_admin_referer( $action );
$taxonomy_caps = (object) get_taxonomy( self::TAXONOMY_SLUG )->cap; // Yes, cap is an object not an array.
if ( ! current_user_can( $taxonomy_caps->manage_terms ) ) {
return;
}
$referer = wp_get_referer();
$term_id = intval( $_GET['term_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$redirect = self::handle_validation_error_update( $referer, $action, array( $term_id ) );
if ( $redirect !== $referer ) {
wp_safe_redirect( $redirect );
exit;
}
}
/**
* On the single URL page, handles the bulk actions of 'Accept' and 'Reject'
*
* On /wp-admin/post.php, this handles these bulk actions.
* This page is more like an edit-tags.php page, in that it has a WP_Terms_List_Table of amp_validation_error terms.
* So this reuses handle_validation_error_update(), which the edit-tags.php page uses.
*
* @param int $post_id The ID of the post for which to apply the bulk action.
*/
public static function handle_single_url_page_bulk_and_inline_actions( $post_id ) {
if ( ! isset( $_REQUEST['action'] ) || AMP_Validated_URL_Post_Type::POST_TYPE_SLUG !== get_post_type( $post_id ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return;
}
$action = sanitize_key( $_REQUEST['action'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$term_ids = isset( $_POST['delete_tags'] ) ? array_map( 'sanitize_key', $_POST['delete_tags'] ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Missing
$single_term_id = isset( $_GET['term_id'] ) ? sanitize_key( $_GET['term_id'] ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
$redirect_query_args = array(
'action' => 'edit',
'amp_actioned' => $action,
);
if ( $term_ids ) {
// If this is a bulk action.
self::handle_validation_error_update( null, $action, $term_ids );
$redirect_query_args['amp_actioned_count'] = count( $term_ids );
} elseif ( $single_term_id ) {
// If this is an inline action, like 'Reject' or 'Accept'.
self::handle_validation_error_update( null, $action, array( $single_term_id ) );
$redirect_query_args['amp_actioned_count'] = 1;
}
// Even if the user didn't select any errors to bulk edit, redirect back to the same page.
wp_safe_redirect(
add_query_arg(
$redirect_query_args,
get_edit_post_link( $post_id, 'raw' )
)
);
exit();
}
/**
* Handle bulk and inline edits to amp_validation_error terms.
*
* @param string $redirect_to Redirect to.
* @param string $action Action.
* @param int[] $term_ids Term IDs.
*
* @return string Redirect.
*/
public static function handle_validation_error_update( $redirect_to, $action, $term_ids ) {
$term_group = null;
if ( self::VALIDATION_ERROR_ACCEPT_ACTION === $action ) {
$term_group = self::VALIDATION_ERROR_ACK_ACCEPTED_STATUS;
} elseif ( self::VALIDATION_ERROR_REJECT_ACTION === $action ) {
$term_group = self::VALIDATION_ERROR_ACK_REJECTED_STATUS;
}
if ( $term_group ) {
$has_pre_term_description_filter = has_filter( 'pre_term_description', 'wp_filter_kses' );
if ( false !== $has_pre_term_description_filter ) {
remove_filter( 'pre_term_description', 'wp_filter_kses', $has_pre_term_description_filter );
}
foreach ( $term_ids as $term_id ) {
wp_update_term( $term_id, self::TAXONOMY_SLUG, compact( 'term_group' ) );
}
if ( false !== $has_pre_term_description_filter ) {
add_filter( 'pre_term_description', 'wp_filter_kses', $has_pre_term_description_filter );
}
$redirect_to = add_query_arg(
array(
'amp_actioned' => $action,
'amp_actioned_count' => count( $term_ids ),
),
$redirect_to
);
}
return $redirect_to;
}
/**
* Handle request to delete empty terms.
*/
public static function handle_clear_empty_terms_request() {
if ( ! isset( $_POST[ self::VALIDATION_ERROR_CLEAR_EMPTY_ACTION ] ) || ! isset( $_POST[ self::VALIDATION_ERROR_CLEAR_EMPTY_ACTION . '_nonce' ] ) ) {
return;
}
if ( ! check_ajax_referer( self::VALIDATION_ERROR_CLEAR_EMPTY_ACTION, self::VALIDATION_ERROR_CLEAR_EMPTY_ACTION . '_nonce', false ) ) {
wp_die( esc_html__( 'The link you followed has expired.', 'amp' ) );
}
$taxonomy_caps = (object) get_taxonomy( self::TAXONOMY_SLUG )->cap; // Yes, cap is an object not an array.
if ( ! current_user_can( $taxonomy_caps->manage_terms ) ) {
wp_die( esc_html__( 'You do not have authorization.', 'amp' ) );
}
$deleted_terms = self::delete_empty_terms();
$referer = wp_validate_redirect( wp_get_raw_referer() );
if ( ! $referer ) {
return;
}
$redirect = add_query_arg( self::VALIDATION_ERRORS_CLEARED_QUERY_VAR, $deleted_terms, $referer );
wp_safe_redirect( $redirect );
exit;
}
/**
* Get Error Title from Code
*
* @param string $error_code Error code.
*
* @return string
*/
public static function get_error_title_from_code( $error_code ) {
$error_title = 'Error';
if ( self::INVALID_ELEMENT_CODE === $error_code ) {
$error_title = __( 'Invalid element', 'amp' );
} elseif ( self::INVALID_ATTRIBUTE_CODE === $error_code ) {
$error_title = __( 'Invalid attribute', 'amp' );
} elseif ( 'file_path_not_allowed' === $error_code ) {
$error_title = __( 'File path not allowed', 'amp' );
} elseif ( 'excessive_css' === $error_code ) {
$error_title = __( 'Excessive CSS', 'amp' );
} elseif ( 'illegal_css_at_rule' === $error_code ) {
$error_title = sprintf(
/* translators: %s: @ */
__( 'Illegal CSS %s rule', 'amp' ),
'@'
);
} elseif ( 'disallowed_file_extension' === $error_code ) {
$error_title = __( 'Disallowed file extension', 'amp' );
} elseif ( 'file_path_not_allowed' === $error_code ) {
$error_title = __( 'File path not allowed', 'amp' );
} elseif ( 'removed_unused_css_rules' === $error_code ) {
$error_title = __( 'Remove unused CSS rules', 'amp' );
}
return $error_title;
}
/**
* Get Status Text with Icon
*
* @see \AMP_Validation_Error_Taxonomy::get_validation_error_sanitization()
*
* @param array $sanitization Sanitization.
* @return string Status text.
*/
public static function get_status_text_with_icon( $sanitization ) {
if ( self::VALIDATION_ERROR_ACK_ACCEPTED_STATUS === $sanitization['term_status'] ) {
$class = 'ack accepted';
$text = __( 'Accepted', 'amp' );
} elseif ( self::VALIDATION_ERROR_ACK_REJECTED_STATUS === $sanitization['term_status'] ) {
$class = 'ack rejected';
$text = __( 'Rejected', 'amp' );
} elseif ( self::VALIDATION_ERROR_NEW_REJECTED_STATUS === $sanitization['term_status'] ) {
$class = 'new rejected';
$text = __( 'New Rejected', 'amp' );
} else {
$class = 'new accepted';
$text = __( 'New Accepted', 'amp' );
}
return sprintf( '
%s ', esc_attr( $class ), esc_html( $text ) );
}
}