$name,
'site_name' => $name,
'standard' => 'wcag22-aa',
'status' => 'partial',
'measures' => array( 'mission', 'responsibility', 'testing_at' ),
'other_measures' => '',
'limitations' => array(),
'compatibility' => '',
'technologies' => array( 'html', 'css', 'js', 'aria' ),
'assessment' => array( 'self', 'automated' ),
'contact_email' => '',
'contact_phone' => '',
'contact_address' => '',
'response_days' => 5,
'enforcement' => '',
'enforcement_url' => '',
'statement_date' => '',
'reviewed_date' => '',
'feedback_page' => 0,
'statement_page' => 0,
);
}
/**
* Standards the site can measure itself against.
*
* @return array
*/
public static function standards() {
return array(
'wcag22-aa' => __( 'WCAG 2.2 level AA', 'equivora' ),
'wcag21-aa' => __( 'WCAG 2.1 level AA', 'equivora' ),
'en301549' => __( 'EN 301 549 (which includes WCAG 2.1 level AA)', 'equivora' ),
);
}
/**
* Conformance statuses.
*
* @return array
*/
public static function statuses() {
return array(
'full' => __( 'Fully conformant', 'equivora' ),
'partial' => __( 'Partially conformant', 'equivora' ),
'non' => __( 'Not conformant', 'equivora' ),
);
}
/**
* Organizational measures.
*
* @return array
*/
public static function measures() {
return array(
'mission' => __( 'Accessibility is part of our mission and values.', 'equivora' ),
'policy' => __( 'We follow a written accessibility policy.', 'equivora' ),
'responsibility' => __( 'A named person or team is responsible for accessibility.', 'equivora' ),
'training' => __( 'Our staff receive accessibility training.', 'equivora' ),
'goals' => __( 'We set clear accessibility goals and review them regularly.', 'equivora' ),
'procurement' => __( 'We include accessibility requirements when we buy tools and services.', 'equivora' ),
'testing_users' => __( 'We test with people with disabilities.', 'equivora' ),
'testing_at' => __( 'We test with screen readers, keyboard navigation and zoom.', 'equivora' ),
);
}
/**
* Technologies the site relies on.
*
* @return array
*/
public static function technologies() {
return array(
'html' => 'HTML',
'css' => 'CSS',
'js' => 'JavaScript',
'aria' => 'WAI-ARIA',
'svg' => 'SVG',
'pdf' => 'PDF',
);
}
/**
* Assessment approaches.
*
* @return array
*/
public static function assessments() {
return array(
'self' => __( 'Self-evaluation by our own team', 'equivora' ),
'automated' => __( 'Automated testing tools', 'equivora' ),
'manual' => __( 'Manual testing with assistive technologies', 'equivora' ),
'external' => __( 'An independent external audit', 'equivora' ),
);
}
/**
* All answers merged with defaults.
*
* @return array
*/
public static function all() {
$saved = get_option( self::OPTION, array() );
return wp_parse_args( is_array( $saved ) ? $saved : array(), self::defaults() );
}
/**
* One answer.
*
* @param string $key Key.
* @return mixed
*/
public static function get( $key ) {
$all = self::all();
return isset( $all[ $key ] ) ? $all[ $key ] : null;
}
/**
* Saves answers.
*
* @param array $data Sanitized data.
*/
public static function save( array $data ) {
update_option( self::OPTION, $data, false );
}
/**
* Sanitizes the builder form.
*
* @param array $input Unslashed input.
* @return array
*/
public static function sanitize( array $input ) {
$current = self::all();
$keys = function ( $value, $allowed ) {
return array_values( array_intersect( array_map( 'sanitize_key', (array) $value ), array_keys( $allowed ) ) );
};
$text = function ( $key, $multiline = false ) use ( $input ) {
if ( ! isset( $input[ $key ] ) || ! is_scalar( $input[ $key ] ) ) {
return '';
}
return $multiline ? sanitize_textarea_field( $input[ $key ] ) : sanitize_text_field( $input[ $key ] );
};
$date = function ( $key ) use ( $input ) {
$value = isset( $input[ $key ] ) && is_scalar( $input[ $key ] ) ? (string) $input[ $key ] : '';
return preg_match( '/^\d{4}-\d{2}-\d{2}$/', $value ) && strtotime( $value ) ? $value : '';
};
$limitations = array();
if ( isset( $input['limitations'] ) && is_array( $input['limitations'] ) ) {
foreach ( $input['limitations'] as $row ) {
if ( ! is_array( $row ) ) {
continue;
}
$clean = array();
foreach ( array( 'item', 'issue', 'reason', 'alternative' ) as $field ) {
$clean[ $field ] = isset( $row[ $field ] ) && is_scalar( $row[ $field ] ) ? sanitize_text_field( $row[ $field ] ) : '';
}
if ( '' !== $clean['item'] || '' !== $clean['issue'] ) {
$limitations[] = $clean;
}
if ( count( $limitations ) >= self::MAX_LIMITATIONS ) {
break;
}
}
}
$email = sanitize_email( $text( 'contact_email' ) );
$url = isset( $input['enforcement_url'] ) && is_scalar( $input['enforcement_url'] ) ? esc_url_raw( (string) $input['enforcement_url'], array( 'http', 'https' ) ) : '';
$today = current_time( 'Y-m-d' );
$created = $date( 'statement_date' );
return array(
'org_name' => $text( 'org_name' ),
'site_name' => $text( 'site_name' ),
'standard' => isset( $input['standard'] ) && is_string( $input['standard'] ) && isset( self::standards()[ $input['standard'] ] ) ? $input['standard'] : 'wcag22-aa',
'status' => isset( $input['status'] ) && is_string( $input['status'] ) && isset( self::statuses()[ $input['status'] ] ) ? $input['status'] : 'partial',
'measures' => $keys( isset( $input['measures'] ) ? $input['measures'] : array(), self::measures() ),
'other_measures' => $text( 'other_measures', true ),
'limitations' => $limitations,
'compatibility' => $text( 'compatibility', true ),
'technologies' => $keys( isset( $input['technologies'] ) ? $input['technologies'] : array(), self::technologies() ),
'assessment' => $keys( isset( $input['assessment'] ) ? $input['assessment'] : array(), self::assessments() ),
'contact_email' => is_email( $email ) ? $email : '',
'contact_phone' => $text( 'contact_phone' ),
'contact_address' => $text( 'contact_address', true ),
'response_days' => isset( $input['response_days'] ) && is_scalar( $input['response_days'] ) ? min( 60, max( 1, absint( $input['response_days'] ) ) ) : 5,
'enforcement' => $text( 'enforcement', true ),
'enforcement_url' => $url,
'statement_date' => '' !== $created ? $created : ( $current['statement_date'] ? $current['statement_date'] : $today ),
'reviewed_date' => $today,
'feedback_page' => isset( $input['feedback_page'] ) && is_scalar( $input['feedback_page'] ) ? absint( $input['feedback_page'] ) : 0,
'statement_page' => (int) $current['statement_page'],
);
}
/**
* Whether the builder has been saved at least once.
*
* @return bool
*/
public static function is_configured() {
return '' !== (string) self::get( 'reviewed_date' );
}
/**
* Days since the statement was last reviewed, or null.
*
* @return int|null
*/
public static function days_since_review() {
$reviewed = (string) self::get( 'reviewed_date' );
if ( '' === $reviewed ) {
return null;
}
$diff = strtotime( current_time( 'Y-m-d' ) ) - strtotime( $reviewed );
return max( 0, (int) floor( $diff / DAY_IN_SECONDS ) );
}
/**
* Formats a stored date for display.
*
* @param string $date Y-m-d date.
* @return string
*/
private static function date_label( $date ) {
return $date ? wp_date( get_option( 'date_format' ), strtotime( $date . ' 12:00:00' ) ) : '';
}
/**
* Statement HTML.
*
* @param int $level Heading level for section headings (2 to 4).
* @return string
*/
public static function render( $level = 2 ) {
$s = self::all();
$level = max( 2, min( 4, (int) $level ) );
$h = function ( $text ) use ( $level ) {
return sprintf( '%2$s', $level, esc_html( $text ) );
};
$org = '' !== $s['org_name'] ? $s['org_name'] : wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );
$site = '' !== $s['site_name'] ? $s['site_name'] : $org;
$out = '';
$out .= $h( __( 'Our commitment', 'equivora' ) );
/* translators: 1: organization name, 2: website name. */
$out .= '' . esc_html( sprintf( __( '%1$s wants everyone to be able to use %2$s, including people with disabilities and people who use assistive technologies such as screen readers, voice control or screen magnifiers. We keep improving the experience for all visitors and work towards recognized accessibility standards.', 'equivora' ), $org, $site ) ) . '
';
$out .= '';
$standards = self::standards();
$statuses = self::statuses();
$standard = isset( $standards[ $s['standard'] ] ) ? $standards[ $s['standard'] ] : $standards['wcag22-aa'];
$out .= '';
$out .= $h( __( 'Conformance status', 'equivora' ) );
/* translators: %s: accessibility standard. */
$out .= '' . esc_html( sprintf( __( 'We measure this website against %s. These guidelines explain how to make web content more accessible for people with a wide range of disabilities.', 'equivora' ), $standard ) ) . '
';
switch ( $s['status'] ) {
case 'full':
/* translators: 1: website name, 2: accessibility standard. */
$status_text = sprintf( __( '%1$s is fully conformant with %2$s. All content meets the standard without exceptions.', 'equivora' ), $site, $standard );
break;
case 'non':
/* translators: 1: website name, 2: accessibility standard. */
$status_text = sprintf( __( '%1$s is not yet conformant with %2$s. Much of the content does not meet the standard yet, and we are working to improve it.', 'equivora' ), $site, $standard );
break;
default:
/* translators: 1: website name, 2: accessibility standard. */
$status_text = sprintf( __( '%1$s is partially conformant with %2$s. Most content meets the standard, but some parts do not yet. They are listed under "Known limitations" below.', 'equivora' ), $site, $standard );
}
$out .= '' . esc_html( isset( $statuses[ $s['status'] ] ) ? $statuses[ $s['status'] ] : $statuses['partial'] ) . ': ' . esc_html( $status_text ) . '
';
$out .= '';
$measures = array_intersect_key( self::measures(), array_flip( (array) $s['measures'] ) );
if ( $measures || '' !== $s['other_measures'] ) {
$out .= '';
$out .= $h( __( 'What we do to support accessibility', 'equivora' ) );
$out .= '';
foreach ( $measures as $label ) {
$out .= '- ' . esc_html( $label ) . '
';
}
if ( '' !== $s['other_measures'] ) {
foreach ( array_filter( array_map( 'trim', explode( "\n", $s['other_measures'] ) ) ) as $line ) {
$out .= '- ' . esc_html( $line ) . '
';
}
}
$out .= '
';
}
$out .= '';
$out .= $h( __( 'Known limitations', 'equivora' ) );
if ( $s['limitations'] ) {
$out .= '' . esc_html__( 'Despite our efforts, some content is not fully accessible yet. These are the limitations we know about:', 'equivora' ) . '
';
foreach ( $s['limitations'] as $row ) {
$out .= '- ' . esc_html( $row['item'] ) . '';
if ( '' !== $row['issue'] ) {
$out .= ': ' . esc_html( $row['issue'] );
}
if ( '' !== $row['reason'] ) {
/* translators: %s: reason for the limitation. */
$out .= '
' . esc_html( sprintf( __( 'Reason: %s', 'equivora' ), $row['reason'] ) ) . '';
}
if ( '' !== $row['alternative'] ) {
/* translators: %s: accessible alternative. */
$out .= '
' . esc_html( sprintf( __( 'Alternative: %s', 'equivora' ), $row['alternative'] ) ) . '';
}
$out .= ' ';
}
$out .= '
';
} else {
$out .= '' . esc_html__( 'We are not aware of specific accessibility limitations at the moment. If you find a problem, please tell us.', 'equivora' ) . '
';
}
$out .= '';
$tech = array_intersect_key( self::technologies(), array_flip( (array) $s['technologies'] ) );
$assessment = array_intersect_key( self::assessments(), array_flip( (array) $s['assessment'] ) );
if ( '' !== $s['compatibility'] || $tech || $assessment ) {
$out .= '';
$out .= $h( __( 'Technical information', 'equivora' ) );
if ( '' !== $s['compatibility'] ) {
/* translators: %s: browsers and assistive technologies. */
$out .= '' . esc_html( sprintf( __( 'This website is designed to work with: %s', 'equivora' ), $s['compatibility'] ) ) . '
';
}
if ( $tech ) {
/* translators: %s: list of technologies. */
$out .= '' . esc_html( sprintf( __( 'Accessibility of this website relies on these technologies: %s.', 'equivora' ), wp_sprintf( '%l', array_values( $tech ) ) ) ) . '
';
}
if ( $assessment ) {
$out .= '' . esc_html__( 'We assessed the accessibility of this website through:', 'equivora' ) . '
';
foreach ( $assessment as $label ) {
$out .= '- ' . esc_html( $label ) . '
';
}
$out .= '
';
}
$out .= '';
}
$out .= '';
$out .= $h( __( 'Feedback and contact', 'equivora' ) );
$out .= '' . esc_html__( 'We welcome your feedback. If you come across a barrier, need content in a different format or have a suggestion, please contact us:', 'equivora' ) . '
';
$feedback_page = (int) $s['feedback_page'];
if ( $feedback_page && 'publish' === get_post_status( $feedback_page ) ) {
$out .= '- ' . esc_html__( 'Use our accessibility feedback form', 'equivora' ) . '
';
}
if ( '' !== $s['contact_email'] ) {
/* translators: %s: email link. */
$out .= '- ' . sprintf( esc_html__( 'Email: %s', 'equivora' ), '' . esc_html( antispambot( $s['contact_email'] ) ) . '' ) . '
';
}
if ( '' !== $s['contact_phone'] ) {
/* translators: %s: phone number. */
$out .= '- ' . sprintf( esc_html__( 'Phone: %s', 'equivora' ), '' . esc_html( $s['contact_phone'] ) . '' ) . '
';
}
if ( '' !== $s['contact_address'] ) {
/* translators: %s: postal address. */
$out .= '- ' . esc_html( sprintf( __( 'Postal address: %s', 'equivora' ), preg_replace( '/\s*\n\s*/', ', ', $s['contact_address'] ) ) ) . '
';
}
$out .= '
';
$days = (int) $s['response_days'];
/* translators: %d: number of business days. */
$out .= '' . esc_html( sprintf( _n( 'We aim to reply within %d business day.', 'We aim to reply within %d business days.', $days, 'equivora' ), $days ) ) . '
';
$out .= '';
if ( '' !== $s['enforcement'] || '' !== $s['enforcement_url'] ) {
$out .= '';
$out .= $h( __( 'Enforcement procedure', 'equivora' ) );
$out .= '' . esc_html__( 'If you are not satisfied with our response, you can contact the responsible authority:', 'equivora' ) . '
';
if ( '' !== $s['enforcement'] ) {
$out .= '' . nl2br( esc_html( $s['enforcement'] ) ) . '
';
}
if ( '' !== $s['enforcement_url'] ) {
$out .= '' . esc_html( $s['enforcement_url'] ) . '
';
}
$out .= '';
}
$out .= '';
$out .= $h( __( 'About this statement', 'equivora' ) );
if ( $s['statement_date'] ) {
/* translators: %s: date. */
$out .= '' . esc_html( sprintf( __( 'This statement was created on %s.', 'equivora' ), self::date_label( $s['statement_date'] ) ) ) . '
';
}
if ( $s['reviewed_date'] ) {
/* translators: %s: date. */
$out .= '' . esc_html( sprintf( __( 'It was last reviewed on %s.', 'equivora' ), self::date_label( $s['reviewed_date'] ) ) ) . '
';
}
$out .= '';
return $out;
}
}