settings_capability = $settings_capability;
}
/**
* Register hooks.
*
* @return void
*/
public function add_page() {
add_filter( 'edac_filter_settings_tab_items', [ $this, 'add_reports_tab' ] );
add_action( 'edac_settings_tab_content', [ $this, 'maybe_render_tab_content' ] );
}
/**
* Add the Accessibility Reports tab.
*
* @param array $tabs Existing tabs.
* @return array
*/
public function add_reports_tab( $tabs ) {
$tabs[] = [
'slug' => 'accessibility-reports',
'label' => esc_html__( 'Accessibility Reports', 'accessibility-checker' ),
'badge' => esc_html__( 'New', 'accessibility-checker' ),
'order' => defined( 'EDACP_VERSION' ) ? 4 : 3,
'capability' => $this->settings_capability,
];
return $tabs;
}
/**
* Render content for the active tab.
*
* @param string|null $settings_tab Active tab slug.
* @return void
*/
public function maybe_render_tab_content( $settings_tab ) {
if ( 'accessibility-reports' === $settings_tab ) {
$this->render_page();
}
}
/**
* Render the page.
*
* @return void
*/
public function render_page() {
$license_context = $this->get_license_context();
$is_pro = $license_context['is_pro'];
$license_key = (string) get_option( 'edacp_license_key', '' );
$is_connected = $license_context['is_connected'];
$is_registered = $license_context['is_registered'];
$status = $license_context['status'];
$next_send_date = $this->get_next_send_estimate_date();
$scans_stats = new Scans_Stats();
$summary = $scans_stats->summary();
$preview_data = is_array( $summary ) ? $this->get_preview_data( $summary, $is_pro ) : [];
$upgrade_url = edac_generate_link_type(
[
'utm_campaign' => 'settings-page',
'utm_content' => 'accessibility-reports-upgrade',
]
);
$dashboard_url = edac_link_wrapper( 'https://my.equalizedigital.com/license-keys/', 'accessibility-reports', 'account', false );
$signup_url = edac_link_wrapper( 'https://my.equalizedigital.com/sign-up/', 'accessibility-reports', 'signup', false );
$privacy_url = edac_link_wrapper( 'https://equalizedigital.com/privacy-policy/', 'accessibility-reports', 'privacy', false );
$data_processing_url = edac_link_wrapper( 'https://equalizedigital.com/data-terms/', 'accessibility-reports', 'dpa', false );
$allowed_icon_html = [
'span' => [
'class' => true,
'aria-hidden' => true,
'aria-label' => true,
],
'svg' => [
'width' => true,
'height' => true,
'viewbox' => true,
'fill' => true,
'xmlns' => true,
],
'path' => [
'd' => true,
'stroke' => true,
'stroke-width' => true,
'stroke-linecap' => true,
'stroke-linejoin' => true,
],
'rect' => [
'x' => true,
'y' => true,
'width' => true,
'height' => true,
'rx' => true,
'stroke' => true,
'stroke-width' => true,
],
];
?>
dashboard.', 'accessibility-checker' ) ),
esc_url( $dashboard_url )
);
?>
Create a free account to get one.', 'accessibility-checker' ) ),
esc_url( $signup_url )
);
?>
create a free account. It only takes a minute.', 'accessibility-checker' ) ),
esc_url( $signup_url )
);
?>
mask_license_key( $license_key ) ); ?>
Privacy Policy ↗ and Data Processing Agreement ↗.', 'accessibility-checker' ) ),
esc_url( $privacy_url ),
esc_url( $data_processing_url ),
esc_attr__( 'Privacy Policy (opens in a new window)', 'accessibility-checker' ),
esc_attr__( 'Data Processing Agreement (opens in a new window)', 'accessibility-checker' )
);
?>
$has_pro_plugin,
'is_pro' => $is_pro,
'status' => $status,
'is_connected' => $is_connected,
'is_registered' => $is_registered,
'fallback_active' => $fallback_active,
];
}
/**
* Gets the next estimated send date, assuming each send will be on
* Mondays.
*
* @return string
*/
private function get_next_send_estimate_date(): string {
try {
// If today is Monday, use today. Otherwise, use next Monday.
$today = new \DateTime( 'now', wp_timezone() );
if ( '1' === $today->format( 'N' ) ) {
return $today->format( 'Y-m-d' );
}
$next_monday = new \DateTime( 'next monday', wp_timezone() );
return $next_monday->format( 'Y-m-d' );
} catch ( \Exception $exception ) {
return '';
}
}
/**
* Build preview data from current site stats.
*
* @param array $summary Current site summary.
* @param bool $is_pro Whether the effective active license is Pro.
* @return array
*/
private function get_preview_data( array $summary, bool $is_pro ): array {
$problems = (int) ( $summary['errors'] ?? 0 );
$needs_review = (int) ( $summary['warnings'] ?? 0 );
$total_issues = $problems + $needs_review;
$urls_scanned = (int) ( $summary['posts_scanned'] ?? 0 );
$post_types_checked = (int) ( $summary['scannable_post_types_count'] ?? 0 );
$public_post_types = (int) ( $summary['public_post_types_count'] ?? 0 );
$taxonomies_checked = $this->get_taxonomy_coverage_counts( $is_pro );
$rules = $this->get_rule_index();
return [
'total_issues' => $total_issues,
'problems' => $problems,
'needs_review' => $needs_review,
'passed_checks' => (string) ( $summary['passed_percentage_formatted'] ?? __( 'N/A', 'accessibility-checker' ) ),
'urls_scanned' => $urls_scanned,
'post_types_checked' => sprintf( '%1$d/%2$d', $post_types_checked, max( $public_post_types, $post_types_checked ) ),
'taxonomies_checked' => sprintf( '%1$d/%2$d', $taxonomies_checked['checked'], max( $taxonomies_checked['total'], $taxonomies_checked['checked'] ) ),
'top_pages' => array_slice( $summary['top_pages_with_issues'] ?? [], 0, 5 ),
'top_issues' => $this->format_top_issues( $summary['top_issues_found_on_site'] ?? [], $rules ),
];
}
/**
* Map rules by slug for display.
*
* @return array
*/
private function get_rule_index(): array {
$rules = edac_register_rules();
$index = [];
foreach ( $rules as $rule ) {
if ( empty( $rule['slug'] ) ) {
continue;
}
$index[ $rule['slug'] ] = $rule;
}
return $index;
}
/**
* Get a resized status icon using the shared helper output.
*
* @param string $icon_name Icon name for edac_icon().
* @return string
*/
private function get_status_icon( string $icon_name ): string {
return edac_icon( $icon_name, '', true, '', 'edac-reports-card__status-icon' );
}
/**
* Format top issue rows for the preview table.
*
* @param array $issues Raw issue counts.
* @param array $rules Indexed rules.
* @return array
*/
private function format_top_issues( array $issues, array $rules ): array {
$formatted = [];
foreach ( array_slice( $issues, 0, 10 ) as $issue ) {
$rule = $rules[ $issue['rule_slug'] ] ?? [];
$severity = isset( $rule['severity'] ) ? (int) $rule['severity'] : 99;
$formatted[] = [
'title' => $rule['title'] ?? $issue['rule_slug'],
'severity' => $this->format_severity( $severity ),
'rank' => $severity,
'count' => (int) $issue['issue_count'],
];
}
usort(
$formatted,
function ( $a, $b ) {
if ( $a['rank'] === $b['rank'] ) {
return $b['count'] <=> $a['count'];
}
return $a['rank'] <=> $b['rank'];
}
);
return array_map(
function ( $issue ) {
unset( $issue['rank'] );
return $issue;
},
array_slice( $formatted, 0, 5 )
);
}
/**
* Convert a numeric severity to a human-readable label.
*
* @param int $severity Severity number.
* @return string
*/
private function format_severity( int $severity ): string {
switch ( $severity ) {
case 1:
return __( 'Critical', 'accessibility-checker' );
case 2:
return __( 'High', 'accessibility-checker' );
case 3:
return __( 'Medium', 'accessibility-checker' );
case 4:
return __( 'Low', 'accessibility-checker' );
default:
return __( 'Unknown', 'accessibility-checker' );
}
}
/**
* Get taxonomy coverage counts for the preview panel.
*
* @param bool $is_pro Whether the effective active license is Pro.
* @return array
*/
private function get_taxonomy_coverage_counts( bool $is_pro ): array {
$taxonomies = get_taxonomies(
[
'public' => true,
],
'names'
);
unset( $taxonomies['post_format'] );
$total = count( $taxonomies );
$checked = 0;
if ( $is_pro && get_option( 'edacp_enable_archive_scanning' ) ) {
$checked = $total;
}
return [
'checked' => $checked,
'total' => $total,
];
}
/**
* Mask a license key for display.
*
* @param string $license_key License key.
* @return string
*/
private function mask_license_key( string $license_key ): string {
if ( strlen( $license_key ) <= 8 ) {
return $license_key;
}
return substr( $license_key, 0, 4 ) . str_repeat( '*', max( strlen( $license_key ) - 8, 4 ) ) . substr( $license_key, -4 );
}
}