post_id = $post_id; $this->site_id = get_current_blog_id(); } /** * Generates a summary of accessibility tests for a specific post. * This method compiles a summary including passed tests, errors, warnings, and other relevant * information by querying the database and applying logic based on the set of rules and the post's content. * * @return array An associative array containing the summary of accessibility checks, such as * passed tests, errors, warnings, ignored checks, contrast errors, content grade, * readability, and whether a simplified summary is enabled. * * @since 1.9.0 */ public function generate_summary() { global $wpdb; $table_name = edac_get_valid_table_name( $wpdb->prefix . 'accessibility_checker' ); $summary = []; if ( ! $table_name ) { return $summary; } $rules = edac_register_rules(); if ( defined( 'ANWW_VERSION' ) ) { $rules = edac_remove_element_with_value( $rules, 'slug', 'link_blank' ); } $summary['passed_tests'] = $this->calculate_passed_tests( $rules ); $summary['errors'] = $this->count_errors(); $summary['warnings'] = $this->count_warnings(); $summary['ignored'] = $this->count_ignored(); $summary['contrast_errors'] = $this->count_contrast_errors(); $summary['errors'] -= $summary['contrast_errors']; $summary['content_grade'] = $this->calculate_content_grade(); $summary['readability'] = $this->get_readability( $summary ); $summary['simplified_summary'] = (bool) ( get_post_meta( $this->post_id, '_edac_simplified_summary', true ) ); $this->update_issue_density( $summary ); $this->save_summary_meta_data( $summary ); return $summary; } /** * Calculates the percentage of passed tests based on the provided rules. * This method queries the database to find which rules have not been violated (passed) for * the current post and calculates the percentage of these passed tests. * * @param array $rules An array of rules against which the post's accessibility is checked. * @return int The percentage of rules that have passed. * * @since 1.9.0 */ private function calculate_passed_tests( $rules ) { if ( empty( $rules ) ) { return 0; } global $wpdb; $passed_count = 0; foreach ( $rules as $rule ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Using direct query for interacting with custom database, safe variable used for table name, caching not required for one time operation. $count = $wpdb->get_var( $wpdb->prepare( 'SELECT count(*) FROM %i where rule = %s and siteid = %d and postid = %d and ignre = %d', $wpdb->prefix . 'accessibility_checker', $rule['slug'], $this->site_id, $this->post_id, 0 ) ); // If count is zero, it means the rule has passed for this post. if ( 0 === (int) $count ) { ++$passed_count; } } // Calculate the percentage of passed tests. return round( ( $passed_count / count( $rules ) ) * 100 ); } /** * Counts the number of errors found for the current post. * This method queries the database to count the number of accessibility issues classified as 'error'. * * @return int The count of errors. * * @since 1.9.0 */ private function count_errors() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Using direct query for interacting with custom database, safe variable used for table name, caching not required for one time operation. $errors_count = $wpdb->get_var( $wpdb->prepare( 'SELECT count(*) FROM %i where siteid = %d and postid = %d and ruletype = %s and ignre = %d', $wpdb->prefix . 'accessibility_checker', $this->site_id, $this->post_id, 'error', 0 ) ); return (int) $errors_count; } /** * Counts the number of warnings found for the current post. * This method queries the database to count the number of accessibility issues classified as 'warning'. * * @return int The count of warnings. * * @since 1.9.0 */ private function count_warnings() { global $wpdb; $warnings_parameters = [ get_current_blog_id(), $this->post_id, 'warning', 0 ]; $warnings_where = 'WHERE siteid = %d and postid = %d and ruletype = %s and ignre = %d'; if ( defined( 'ANWW_VERSION' ) ) { array_push( $warnings_parameters, 'link_blank' ); $warnings_where .= ' and rule != %s'; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Using direct query for interacting with custom database, safe variable used for table name, caching not required for one time operation. $warnings_count = $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare 'SELECT count(*) FROM ' . $wpdb->prefix . 'accessibility_checker ' . $warnings_where, $warnings_parameters ) ); return (int) $warnings_count; } /** * Counts the number of ignored issues for the current post. * This method queries the database to count the number of accessibility issues that have been ignored. * * @return int The count of ignored issues. * * @since 1.9.0 */ private function count_ignored() { global $wpdb; $ignored_parameters = [ get_current_blog_id(), $this->post_id, 1 ]; $ignored_where = 'WHERE siteid = %d and postid = %d and ignre = %d'; if ( defined( 'ANWW_VERSION' ) ) { array_push( $ignored_parameters, 'link_blank' ); $ignored_where .= ' and rule != %s'; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Using direct query for interacting with custom database, safe variable used for table name, caching not required for one time operation. $ignored_count = $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared , WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare "SELECT count(*) FROM {$wpdb->prefix}accessibility_checker $ignored_where", $ignored_parameters ) ); return (int) $ignored_count; } /** * Counts the number of contrast errors for the current post. * This method queries the database to count the number of accessibility issues specifically related to color contrast failures. * * @return int The count of contrast errors. * * @since 1.9.0 */ private function count_contrast_errors() { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Using direct query for interacting with custom database, safe variable used for table name, caching not required for one time operation. $contrast_errors_count = $wpdb->get_var( $wpdb->prepare( 'SELECT count(*) FROM %i where siteid = %d and postid = %d and rule = %s and ignre = %d', $wpdb->prefix . 'accessibility_checker', $this->site_id, $this->post_id, 'color_contrast_failure', 0 ) ); return (int) $contrast_errors_count; } /** * Updates the issue density metadata for the current post. * This method calculates and updates the issue density based on the summary of accessibility issues * and the content length of the post. * * @param array $summary An associative array containing the summary of accessibility checks. * * @since 1.9.0 */ private function update_issue_density( $summary ) { $issue_density_array = get_post_meta( $this->post_id, '_edac_density_data', false ); if ( ( is_array( $issue_density_array ) && count( $issue_density_array ) > 0 ) && ( is_array( $issue_density_array[0] ) && count( $issue_density_array[0] ) > 0 ) ) { $issue_count = $summary['warnings'] + $summary['errors'] + $summary['contrast_errors']; $element_count = $issue_density_array[0][0]; $content_length = $issue_density_array[0][1]; $issue_density = edac_get_issue_density( $issue_count, $element_count, $content_length ); update_post_meta( $this->post_id, '_edac_issue_density', floatval( $issue_density ) ); } else { delete_post_meta( $this->post_id, '_edac_issue_density' ); } } /** * Calculates the content grade of the post's content. * This method uses the Flesch-Kincaid Grade Level formula to determine the readability grade of the post's content. * * @return int The content grade, based on the Flesch-Kincaid Grade Level. * * @since 1.9.0 */ private function calculate_content_grade() { $content_post = get_post( $this->post_id ); if ( ! $content_post instanceof \WP_Post ) { return 0; } $content = $content_post->post_content; $content = wp_filter_nohtml_kses( $content ); $content = str_replace( ']]>', ']]>', $content ); $content_grade = 0; if ( class_exists( 'DaveChild\TextStatistics\TextStatistics' ) ) { $content_grade = edac_normalize_fk_grade( ( new \DaveChild\TextStatistics\TextStatistics() )->fleschKincaidGradeLevel( $content ) ); } return (int) round( $content_grade ); } /** * Determines the readability of the post's content based on the content grade. * This method translates the content grade into a human-readable format, indicating the level of education * required to understand the content. * * @param array $summary An associative array containing the summary of accessibility checks, including the content grade. * @return string The readability level of the post's content, or 'N/A' if the content grade is 0. * * @since 1.9.0 */ private function get_readability( $summary ) { return 0 === $summary['content_grade'] ? 'N/A' : edac_ordinal( $summary['content_grade'] ); } /** * Saves the summary metadata for the current post. * This method saves the summary of accessibility checks as post metadata, including the number of passed tests, * errors, warnings, ignored checks, contrast errors, content grade, readability, and whether a simplified summary is enabled. * * @param array $summary An associative array containing the summary of accessibility checks. * * @since 1.9.0 */ private function save_summary_meta_data( $summary ) { update_post_meta( $this->post_id, '_edac_summary', $this->sanitize_summary_meta_data( $summary ) ); update_post_meta( $this->post_id, '_edac_summary_passed_tests', absint( $summary['passed_tests'] ) ); update_post_meta( $this->post_id, '_edac_summary_errors', absint( $summary['errors'] ) ); update_post_meta( $this->post_id, '_edac_summary_warnings', absint( $summary['warnings'] ) ); update_post_meta( $this->post_id, '_edac_summary_ignored', absint( $summary['ignored'] ) ); update_post_meta( $this->post_id, '_edac_summary_contrast_errors', absint( $summary['contrast_errors'] ) ); } /** * Sanitizes the summary metadata before saving it to the database. * * @param array $summary An associative array containing the summary of accessibility checks. * * @return array The sanitized summary metadata. * * @since 1.11.0 */ private function sanitize_summary_meta_data( array $summary ): array { return [ 'passed_tests' => absint( $summary['passed_tests'] ?? 0 ), 'errors' => absint( $summary['errors'] ?? 0 ), 'warnings' => absint( $summary['warnings'] ?? 0 ), 'ignored' => absint( $summary['ignored'] ?? 0 ), 'contrast_errors' => absint( $summary['contrast_errors'] ?? 0 ), 'content_grade' => absint( $summary['content_grade'] ?? 0 ), 'readability' => sanitize_text_field( $summary['readability'] ?? '' ), 'simplified_summary' => filter_var( $summary['simplified_summary'] ?? false, FILTER_VALIDATE_BOOLEAN ), ]; } }