diff --git a/accessibility-checker.php b/accessibility-checker.php index 2727a33d..49469d3c 100755 --- a/accessibility-checker.php +++ b/accessibility-checker.php @@ -10,7 +10,7 @@ * Plugin Name: Accessibility Checker * Plugin URI: https://a11ychecker.com * Description: Audit and check your website for accessibility before you hit publish. In-post accessibility scanner and guidance. - * Version: 1.10.0 + * Version: 1.10.1 * Author: Equalize Digital * Author URI: https://equalizedigital.com * License: GPL-2.0+ @@ -41,7 +41,7 @@ // Current plugin version. if ( ! defined( 'EDAC_VERSION' ) ) { - define( 'EDAC_VERSION', '1.10.0' ); + define( 'EDAC_VERSION', '1.10.1' ); } // Current database version. @@ -123,13 +123,11 @@ require_once plugin_dir_path( __FILE__ ) . 'includes/activation.php'; require_once plugin_dir_path( __FILE__ ) . 'includes/deactivation.php'; require_once plugin_dir_path( __FILE__ ) . 'includes/helper-functions.php'; -require_once plugin_dir_path( __FILE__ ) . 'includes/meta-boxes.php'; require_once plugin_dir_path( __FILE__ ) . 'includes/options-page.php'; require_once plugin_dir_path( __FILE__ ) . 'includes/validate.php'; /** * Filters and Actions */ -add_action( 'add_meta_boxes', 'edac_register_meta_boxes' ); add_action( 'admin_menu', 'edac_add_options_page' ); add_action( 'admin_init', 'edac_register_setting' ); add_action( 'admin_head', 'edac_post_on_load' ); diff --git a/admin/class-admin.php b/admin/class-admin.php index 0fce92c2..c1b05bbb 100644 --- a/admin/class-admin.php +++ b/admin/class-admin.php @@ -16,9 +16,21 @@ class Admin { /** - * Class constructor. + * Meta boxes instance. + * + * @since 1.10.0 + * + * @var Meta_Boxes */ - public function __construct() { + private Meta_Boxes $meta_boxes; + + /** + * Class constructor for injecting dependencies. + * + * @param Meta_Boxes|null $meta_boxes Meta boxes instance. + */ + public function __construct( Meta_Boxes $meta_boxes = null ) { + $this->meta_boxes = $meta_boxes; } /** @@ -26,7 +38,7 @@ public function __construct() { * * @return void */ - public function init() { + public function init(): void { $update_database = new Update_Database(); $update_database->init_hooks(); @@ -44,6 +56,8 @@ public function init() { $site_health_info->init_hooks(); $this->init_ajax(); + + $this->meta_boxes->init_hooks(); } /** @@ -51,7 +65,7 @@ public function init() { * * @return void */ - private function init_ajax() { + private function init_ajax(): void { if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) { return; } diff --git a/admin/class-enqueue-admin.php b/admin/class-enqueue-admin.php index d3b7234d..f4c590bc 100644 --- a/admin/class-enqueue-admin.php +++ b/admin/class-enqueue-admin.php @@ -36,8 +36,8 @@ public static function enqueue() { */ public static function enqueue_styles() { wp_enqueue_style( 'edac', plugin_dir_url( EDAC_PLUGIN_FILE ) . 'build/css/admin.css', array(), EDAC_VERSION, 'all' ); - } - + } + /** * Enqueue the admin and editorApp scripts. * @@ -57,13 +57,23 @@ public static function maybe_enqueue_admin_and_editor_app_scripts() { 'accessibility_checker_ignored', ); - if ( is_array( $post_types ) && count( $post_types ) && ( in_array( $current_post_type, $post_types, true ) || in_array( $page, $enabled_pages, true ) ) || ( 'site-editor.php' !== $pagenow ) ) { + if ( + ( + is_array( $post_types ) && + count( $post_types ) && + ( + in_array( $current_post_type, $post_types, true ) || + in_array( $page, $enabled_pages, true ) + ) + ) || + 'site-editor.php' !== $pagenow + ) { global $post; $post_id = is_object( $post ) ? $post->ID : null; wp_enqueue_script( 'edac', plugin_dir_url( EDAC_PLUGIN_FILE ) . 'build/admin.bundle.js', array( 'jquery' ), EDAC_VERSION, false ); - + wp_localize_script( 'edac', 'edac_script_vars', @@ -74,26 +84,26 @@ public static function maybe_enqueue_admin_and_editor_app_scripts() { 'restNonce' => wp_create_nonce( 'wp_rest' ), ) ); - + if ( 'post.php' === $pagenow ) { - + // Is this posttype setup to be checked? $post_types = get_option( 'edac_post_types' ); $current_post_type = get_post_type(); $active = ( is_array( $post_types ) && in_array( $current_post_type, $post_types, true ) ); - + $pro = is_plugin_active( 'accessibility-checker-pro/accessibility-checker-pro.php' ) && EDAC_KEY_VALID; - + if ( EDAC_DEBUG || strpos( EDAC_VERSION, '-beta' ) !== false ) { $debug = true; } else { $debug = false; } - + wp_enqueue_script( 'edac-editor-app', plugin_dir_url( EDAC_PLUGIN_FILE ) . 'build/editorApp.bundle.js', false, EDAC_VERSION, false ); - + wp_localize_script( 'edac-editor-app', 'edac_editor_app', @@ -107,13 +117,13 @@ public static function maybe_enqueue_admin_and_editor_app_scripts() { 'authOk' => false === (bool) get_option( 'edac_password_protected', false ), 'debug' => $debug, 'scanUrl' => get_preview_post_link( - $post_id, + $post_id, array( 'edac_pageScanner' => 1 ) ), ) ); - - } + + } } } @@ -125,13 +135,13 @@ public static function maybe_enqueue_admin_and_editor_app_scripts() { public static function maybe_enqueue_email_opt_in_script() { $page = isset( $_GET['page'] ) ? sanitize_text_field( $_GET['page'] ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only. - if ( 'accessibility_checker' === $page - && true !== (bool) get_user_meta( get_current_user_id(), 'edac_email_optin', true ) + if ( 'accessibility_checker' === $page + && true !== (bool) get_user_meta( get_current_user_id(), 'edac_email_optin', true ) ) { wp_enqueue_style( 'email-opt-in-form', plugin_dir_url( EDAC_PLUGIN_FILE ) . 'build/css/emailOptIn.css', false, EDAC_VERSION, 'all' ); wp_enqueue_script( 'email-opt-in-form', plugin_dir_url( EDAC_PLUGIN_FILE ) . 'build/emailOptIn.bundle.js', false, EDAC_VERSION, true ); - + wp_localize_script( 'email-opt-in-form', 'edac_email_opt_in_form', @@ -140,7 +150,7 @@ public static function maybe_enqueue_email_opt_in_script() { 'ajaxurl' => admin_url( 'admin-ajax.php' ), ) ); - + } } } diff --git a/admin/class-meta-boxes.php b/admin/class-meta-boxes.php new file mode 100644 index 00000000..3f724d91 --- /dev/null +++ b/admin/class-meta-boxes.php @@ -0,0 +1,61 @@ +init(); } else { $this->init(); @@ -36,9 +38,9 @@ public function __construct() { * @return void */ private function init() { - + add_action( 'wp_enqueue_scripts', array( 'EDAC\Inc\Enqueue_Frontend', 'enqueue' ) ); - + $accessibility_statement = new Accessibility_Statement(); $accessibility_statement->init_hooks(); diff --git a/includes/classes/class-summary-generator.php b/includes/classes/class-summary-generator.php index 8111796b..45c5e8c3 100644 --- a/includes/classes/class-summary-generator.php +++ b/includes/classes/class-summary-generator.php @@ -9,7 +9,7 @@ /** * Class that handles summary generator - * + * * @since 1.9.0 */ class Summary_Generator { @@ -21,7 +21,7 @@ class Summary_Generator { * @since 1.9.0 */ private $post_id; - + /** * Specifies the site ID in a multisite WordPress environment. * @@ -33,10 +33,10 @@ class Summary_Generator { /** * Summary_Generator constructor. * Initializes the class by setting up its properties with the provided parameters. - * - * @param int $post_id The ID of the post for which the summary will be generated. + * + * @param int $post_id The ID of the post for which the summary will be generated. * This is used to retrieve and store data related to the specific post. - * + * * @since 1.9.0 */ public function __construct( $post_id ) { @@ -48,16 +48,16 @@ public function __construct( $post_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 = array(); @@ -90,10 +90,10 @@ public function generate_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 ) { @@ -130,9 +130,9 @@ private function calculate_passed_tests( $rules ) { /** * 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() { @@ -156,9 +156,9 @@ private function count_errors() { /** * 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() { @@ -185,9 +185,9 @@ private function count_warnings() { /** * 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() { @@ -215,9 +215,9 @@ private function count_ignored() { /** * 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() { @@ -242,17 +242,22 @@ private function count_contrast_errors() { * 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' ); - if ( is_array( $issue_density_array ) && - count( $issue_density_array ) > 0 && - count( $issue_density_array[0] ) > 0 + 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]; @@ -268,9 +273,9 @@ private function update_issue_density( $summary ) { /** * 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() { @@ -293,10 +298,10 @@ private function calculate_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 ) { @@ -309,9 +314,9 @@ private function get_readability( $summary ) { * 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 ) { diff --git a/includes/deprecated/deprecated.php b/includes/deprecated/deprecated.php index 1210aa6e..60e38630 100644 --- a/includes/deprecated/deprecated.php +++ b/includes/deprecated/deprecated.php @@ -75,6 +75,30 @@ function edac_delete_cpt_posts( $post_type ) { Purge_Post_Data::delete_cpt_posts( $post_type ); } +/** + * Register custom meta boxes + * + * @deprecated 1.10.0 + * + * @return void + */ +function edac_register_meta_boxes() { + _deprecated_function( __FUNCTION__, '1.10.0', 'EDAC\Admin\Meta_Boxes::register_meta_boxes' ); + ( new EDAC\Admin\Meta_Boxes() )->register_meta_boxes(); +} + +/** + * Render the custom meta box html + * + * @deprecated 1.10.0 + * + * @return void + */ +function edac_custom_meta_box_cb() { + _deprecated_function( __FUNCTION__, '1.10.0', 'EDAC\Admin\Meta_Boxes::render' ); + ( new EDAC\Admin\Meta_Boxes() )->render(); +} + /** * Insert rule date into database * diff --git a/includes/helper-functions.php b/includes/helper-functions.php index a048b5de..7f678df5 100644 --- a/includes/helper-functions.php +++ b/includes/helper-functions.php @@ -104,7 +104,7 @@ function edac_ordinal( $number ) { NumberFormatter::ORDINAL ) )->format( $number ); - + } else { if ( $number % 100 >= 11 && $number % 100 <= 13 ) { $ordinal = $number . 'th'; @@ -125,7 +125,7 @@ function edac_ordinal( $number ) { } } return $ordinal; - + } } @@ -649,7 +649,10 @@ function edac_get_issue_density( $issue_count, $element_count, $content_length ) $error_elements_percentage = $issue_count / $element_count; $error_content_percentage = $issue_count / $content_length; - $score = ( ( $error_elements_percentage * $element_weight ) + ( $error_content_percentage * $content_weight ) ); + $score = ( + ( $error_elements_percentage * $element_weight ) + + ( $error_content_percentage * $content_weight ) + ); return round( $score * 100, 2 ); } @@ -857,3 +860,97 @@ function edac_database_table_count( $table ) { return $count; } + +/** + * Add a scheme to file it looks like a url but doesn't have one. + * + * @since 1.10.1 + * + * @param string $file The filename. + * @param string $site_protocol The site protocol. Default is 'https'. + * + * @return string The filename unchanged if it doesn't look like a URL, or with a scheme added if it does. + */ +function edac_url_add_scheme_if_not_existing( string $file, string $site_protocol = '' ): string { + + // if it starts with some valid scheme return unchanged. + $valid_schemes = array( 'http', 'https', 'ftp', 'ftps', 'mailto', 'tel', 'file', 'data', 'irc', 'ssh', 'sftp' ); + $start_of_file = substr( $file, 0, 6 ); + foreach ( $valid_schemes as $scheme ) { + if ( str_starts_with( $start_of_file, $scheme ) ) { + return $file; + } + } + + // if it starts with / followed by any alphanumeric assume it's a relative url. + if ( preg_match( '/^\/[a-zA-Z0-9]/', $file ) ) { + return $file; + } + + // by this point it doesn't seem like a url or a relative path so make it into one. + $file_location = ltrim( $file, '/' ); + $site_scheme = ( ! empty( $site_protocol ) ) + ? $site_protocol + : ( is_ssl() ? 'https' : 'http' ); + + return "{$site_scheme}://{$file_location}"; +} + +/** + * Requests the headers of a URL to check if it exists. + * + * @since 1.10.1 + * + * @param string $url the url to check. + * @return bool + */ +function edac_url_exists( string $url ): bool { + + $response = wp_remote_head( $url ); + + if ( + is_wp_error( $response ) || + ( // Check if the response code is not in the 2xx range. + wp_remote_retrieve_response_code( $response ) < 200 || + wp_remote_retrieve_response_code( $response ) > 299 + ) + ) { + return false; + } + + return true; +} + +/** + * Get a file from local or remote source as a binary file handle. + * + * @since 1.10.1 + * + * @param string $filename The file location, either local or a remote URL. + * @return resource|bool The file binary string or false if the file could not be opened. + */ +function edac_get_file_opened_as_binary( string $filename ) { + if ( + str_starts_with( $filename, 'http' ) || + preg_match( '/^\/[a-zA-Z0-9]/', $filename ) + ) { + $file = $filename; + } else { + $file = edac_url_add_scheme_if_not_existing( $filename ); + $url_exists = edac_url_exists( $file ); + } + + // if this url doesn't exist, return false. + if ( isset( $url_exists ) && false === $url_exists ) { + return false; + } + + try { + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- path validated above. + $fh = fopen( $file, 'rb' ); + } catch ( Exception $e ) { + return false; + } + + return $fh; +} diff --git a/includes/meta-boxes.php b/includes/meta-boxes.php deleted file mode 100644 index 470023b9..00000000 --- a/includes/meta-boxes.php +++ /dev/null @@ -1,36 +0,0 @@ -getAttribute( 'alt' ) ) === '' ) && ( ! isset( $input[0] ) || trim( $input[0]->getAttribute( 'value' ) ) === '' ) - && ( ! isset( $i[0] ) || ( trim( $i[0]->getAttribute( 'title' ) ) === '' ) && trim( $i[0]->getAttribute( 'aria-label' ) ) === '' ) + && ( + ! isset( $i[0] ) || + ( + ( trim( $i[0]->getAttribute( 'title' ) ) === '' ) && + ( trim( $i[0]->getAttribute( 'aria-label' ) ) === '' ) + ) + ) ) { $errors[] = $error_code; } diff --git a/includes/rules/img_alt_empty.php b/includes/rules/img_alt_empty.php index a277d227..a3ee5b60 100644 --- a/includes/rules/img_alt_empty.php +++ b/includes/rules/img_alt_empty.php @@ -23,15 +23,18 @@ function edac_rule_img_alt_empty( $content, $post ) { // phpcs:ignore -- $post i $elements = $dom->find( $tag ); foreach ( $elements as $element ) { if ( - isset( $element ) - && 'img' === $element->tag - && $element->hasAttribute( 'alt' ) - && $element->getAttribute( 'alt' ) === '' - && $element->getAttribute( 'role' ) !== 'presentation' - || ( 'input' === $element->tag + ( + isset( $element ) + && 'img' === $element->tag + && $element->hasAttribute( 'alt' ) + && $element->getAttribute( 'alt' ) === '' + && $element->getAttribute( 'role' ) !== 'presentation' + ) || ( + 'input' === $element->tag && $element->hasAttribute( 'alt' ) && $element->getAttribute( 'type' ) === 'image' - && $element->getAttribute( 'alt' ) === '' ) + && $element->getAttribute( 'alt' ) === '' + ) ) { $image_code = $element->outertext; diff --git a/includes/rules/img_alt_missing.php b/includes/rules/img_alt_missing.php index 5100b981..2d57b801 100644 --- a/includes/rules/img_alt_missing.php +++ b/includes/rules/img_alt_missing.php @@ -22,14 +22,20 @@ function edac_rule_img_alt_missing( $content, $post ) { // phpcs:ignore -- $post $elements = $dom->find( $tag ); foreach ( $elements as $element ) { - if ( isset( $element ) - && ( 'img' === $element->tag - && ! $element->hasAttribute( 'alt' ) - && $element->getAttribute( 'role' ) !== 'presentation' ) - && $element->getAttribute( 'aria-hidden' ) !== 'true' - || ( 'input' === $element->tag - && ! $element->hasAttribute( 'alt' ) - && $element->getAttribute( 'type' ) === 'image' ) + if ( + ( + isset( $element ) && + ( + 'img' === $element->tag + && ! $element->hasAttribute( 'alt' ) + && $element->getAttribute( 'role' ) !== 'presentation' + ) + && $element->getAttribute( 'aria-hidden' ) !== 'true' + ) || ( + 'input' === $element->tag + && ! $element->hasAttribute( 'alt' ) + && $element->getAttribute( 'type' ) === 'image' + ) ) { $image_code = $element->outertext; diff --git a/includes/rules/img_animated_gif.php b/includes/rules/img_animated_gif.php index 2a20b01a..b6b908fd 100644 --- a/includes/rules/img_animated_gif.php +++ b/includes/rules/img_animated_gif.php @@ -69,31 +69,14 @@ function edac_rule_img_animated_gif( $content, $post ) { // phpcs:ignore -- $pos } /** - * Checks if a gif image is anaimated + * Checks if a gif image is animated * * @param string $filename The filename. * @return bool */ -function edac_img_gif_is_animated( $filename ) { +function edac_img_gif_is_animated( string $filename ): bool { - $upload_dir_info = wp_get_upload_dir(); - $uploads_base_dir = trailingslashit( $upload_dir_info['basedir'] ); - - // Get WordPress base URL and remove it from the filename to get the relative path. - $base_url = trailingslashit( get_site_url() ); - $relative_path = str_replace( $base_url, '', $filename ); - - // Construct the full file system path. - $file_system_path = $uploads_base_dir . $relative_path; - - // Check if the file is within the WordPress uploads directory. - if ( 0 !== strpos( $file_system_path, $uploads_base_dir ) ) { - return false; - } - - // First, attempt to open the file. - // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- path validated above. - $fh = fopen( $filename, 'rb' ); + $fh = edac_get_file_opened_as_binary( $filename ); // Then, check if the file handle is false, indicating an error. if ( false === $fh ) { @@ -133,24 +116,7 @@ function edac_img_gif_is_animated( $filename ) { */ function edac_img_webp_is_animated( $filename ) { - $upload_dir_info = wp_get_upload_dir(); - $uploads_base_dir = trailingslashit( $upload_dir_info['basedir'] ); - - // Get WordPress base URL and remove it from the filename to get the relative path. - $base_url = trailingslashit( get_site_url() ); - $relative_path = str_replace( $base_url, '', $filename ); - - // Construct the full file system path. - $file_system_path = $uploads_base_dir . $relative_path; - - // Check if the file is within the WordPress uploads directory. - if ( 0 !== strpos( $file_system_path, $uploads_base_dir ) ) { - return false; - } - - // First, attempt to open the file. - // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- path validated above. - $fh = fopen( $filename, 'rb' ); + $fh = edac_get_file_opened_as_binary( $filename ); // Then, check if the file handle is false, indicating an error. if ( false === $fh ) { diff --git a/includes/rules/missing_form_label.php b/includes/rules/missing_form_label.php index 2f352eeb..40fea13c 100644 --- a/includes/rules/missing_form_label.php +++ b/includes/rules/missing_form_label.php @@ -43,7 +43,8 @@ function ac_input_has_label( $field, $dom ) { if ( $field->getAttribute( 'aria-label' ) ) { return true; } - if ( $dom->find( 'label[for="' . $field->getAttribute( 'id' ) . '"]', -1 ) !== '' ) { + if ( $dom->find( 'label[for="' . $field->getAttribute( 'id' ) . '"]', -1 ) !== null ) { + return true; } return edac_field_has_label_parent( $field ); diff --git a/includes/rules/text_small.php b/includes/rules/text_small.php index 291ec039..abce7e85 100644 --- a/includes/rules/text_small.php +++ b/includes/rules/text_small.php @@ -43,8 +43,13 @@ function edac_rule_text_small( $content, $post ) { // phpcs:ignore -- $post is r preg_match_all( '!\d+!', $absolute_fontsize_errorcode, $matches ); if ( - stristr( $absolute_fontsize_errorcode, 'px' ) === 'px' && implode( ' ', $matches[0] ) <= 10 - || stristr( $absolute_fontsize_errorcode, 'pt' ) === 'pt' && implode( ' ', $matches[0] ) <= 13 + ( + stristr( $absolute_fontsize_errorcode, 'px' ) === 'px' && + implode( ' ', $matches[0] ) <= 10 + ) || ( + ( stristr( $absolute_fontsize_errorcode, 'pt' ) === 'pt' ) && + ( implode( ' ', $matches[0] ) <= 13 ) + ) ) { $errors[] = $element; } @@ -86,7 +91,10 @@ function ac_css_small_text_check( $content ) { $value = str_replace( '.', '', preg_replace( '/\d/', '', $rules['font-size'] ) ); - if ( 'px' === $value && $rules['font-size'] <= 10 || 'pt' === $value && $rules['font-size'] <= 13 ) { + if ( + ( 'px' === $value && $rules['font-size'] <= 10 ) || + ( 'pt' === $value && $rules['font-size'] <= 13 ) + ) { $error_code = $element . '{ '; foreach ( $rules as $key => $value ) { diff --git a/includes/validate.php b/includes/validate.php index 5b9ea259..fd4167cf 100644 --- a/includes/validate.php +++ b/includes/validate.php @@ -52,6 +52,8 @@ function edac_post_on_load() { * @param object $post The post object being saved. * @param bool $update Whether this is an existing post being updated. * + * @modified 1.10.0 to add a return when post_status is trash. + * * @return void */ function edac_save_post( $post_ID, $post, $update ) { @@ -66,6 +68,11 @@ function edac_save_post( $post_ID, $post, $update ) { return; } + // Ignore posts in, or going to, trash. + if ( 'trash' === $post->post_status ) { + return; + } + // ignore revisions. if ( wp_is_post_revision( $post_ID ) ) { return; @@ -355,12 +362,12 @@ function edac_get_content( $post ) { } } } catch ( Exception $e ) { - update_post_meta( $post->ID, '_edac_density_data', '0,0' ); + update_post_meta( $post->ID, '_edac_density_data', array( 0, 0 ) ); $content['html'] = false; } } else { - update_post_meta( $post->ID, '_edac_density_data', '0,0' ); + update_post_meta( $post->ID, '_edac_density_data', array( 0, 0 ) ); $content['html'] = false; } diff --git a/readme.txt b/readme.txt index ae72ec8c..7d9f4b1e 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: equalizedigital, alh0319, stevejonesdev Tags: accessibility, accessible, wcag, ada, WP accessibility Requires at least: 6.2 Tested up to: 6.4.3 -Stable tag: 1.10.0 +Stable tag: 1.10.1 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -171,6 +171,14 @@ No, Accessibility Checker runs completely on your server and does not require yo == Changelog == += 1.10.1 = +* Fixed: Prevent scheme-relative URLs from causing an error when scanning for animated gif of webp files +* Fixed: Potential edge case where an issue density calculation could cause a PHP warning and cause a failed scan +* Fixed: Ensure that missing form labels are reported in the scan results appropriately +* Fixed: Avoid error log when trashing posts in the block editor +* Created: Class to handle the editor meta box for scan results +* Deprecated: `edac_register_meta_boxes`, `edac_custom_meta_box_cb` functions + = 1.10.0 = * Updated: Improved aria-hidden scanning rule * Fixed: Prevent missing_transcript rule from flagging on certain links @@ -181,8 +189,6 @@ No, Accessibility Checker runs completely on your server and does not require yo * Deprecated: `edac_insert_rule_data` function * Created: Class to handle data purging and cleanup * Deprecated: `edac_delete_post`, `edac_delete_post_meta`, `edac_delete_cpt_posts` functions -* Created: Class to handle the editor meta box for scan results -* Deprecated: `edac_register_meta_boxes`, `edac_custom_meta_box_cb` functions = 1.9.3 = * Updated: capability checks for the welcome page, dashboard widget, and admin notices diff --git a/tests/assets/animated.gif b/tests/assets/animated.gif new file mode 100644 index 00000000..c76b6f6b Binary files /dev/null and b/tests/assets/animated.gif differ diff --git a/tests/assets/animated.webp b/tests/assets/animated.webp new file mode 100644 index 00000000..d36261fa Binary files /dev/null and b/tests/assets/animated.webp differ diff --git a/tests/assets/static.gif b/tests/assets/static.gif new file mode 100644 index 00000000..06107759 Binary files /dev/null and b/tests/assets/static.gif differ diff --git a/tests/assets/static.webp b/tests/assets/static.webp new file mode 100644 index 00000000..9d6f0af4 Binary files /dev/null and b/tests/assets/static.webp differ diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 9b080e7e..e5e2cf00 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -35,3 +35,5 @@ function _manually_load_plugin() { // Start up the WP testing environment. require "{$_tests_dir}/includes/bootstrap.php"; + +define( 'EDAC_TEST_ASSETS_DIR', plugin_dir_path( __FILE__ ) . 'assets/' ); diff --git a/tests/phpunit/Admin/MetaBoxesTest.php b/tests/phpunit/Admin/MetaBoxesTest.php new file mode 100644 index 00000000..fbab350a --- /dev/null +++ b/tests/phpunit/Admin/MetaBoxesTest.php @@ -0,0 +1,77 @@ +getMockBuilder( Meta_Boxes::class ) + ->onlyMethods( array( 'register_meta_boxes' ) ) + ->getMock(); + + $meta_boxes->expects( $this->once() ) + ->method( 'register_meta_boxes' ); + + $this->invoke_admin_init( $meta_boxes ); + do_action( 'add_meta_boxes' ); + } + + /** + * Test the init_hooks method. + * + * @return void + */ + public function test_init_hooks(): void { + $meta_boxes = new Meta_Boxes(); + $meta_boxes->init_hooks(); + + $this->assertEquals( + 10, + has_action( + 'add_meta_boxes', + array( + $meta_boxes, + 'register_meta_boxes', + ) + ) + ); + } + + /** + * Test the render method. + * + * @return void + */ + public function test_render(): void { + $meta_boxes = new Meta_Boxes(); + $meta_boxes->render(); + + $this->expectOutputRegex( '/^
/' ); + } + + /** + * Invoke the admin init method. + * + * @param Meta_Boxes $meta_boxes The metabox class. + * @return void + */ + private function invoke_admin_init( $meta_boxes ): void { + $admin = new Admin( $meta_boxes ); + $admin->init(); + } +} diff --git a/tests/phpunit/helper-functions/GetFileOpenedAsBinaryTest.php b/tests/phpunit/helper-functions/GetFileOpenedAsBinaryTest.php new file mode 100644 index 00000000..02214f37 --- /dev/null +++ b/tests/phpunit/helper-functions/GetFileOpenedAsBinaryTest.php @@ -0,0 +1,41 @@ +assertNotFalse( $fh ); + fclose( $fh ); + } + + /** + * Test if file is not opened as binary. + * + * @group external-http + */ + public function test_file_not_opened_as_binary() { + $file = 'https://httpbin.org/status/404'; + + $fh = edac_get_file_opened_as_binary( $file ); + + $this->assertFalse( $fh ); + } +} diff --git a/tests/phpunit/helper-functions/UrlAddSchemeIfNotExistingTest.php b/tests/phpunit/helper-functions/UrlAddSchemeIfNotExistingTest.php new file mode 100644 index 00000000..9766044b --- /dev/null +++ b/tests/phpunit/helper-functions/UrlAddSchemeIfNotExistingTest.php @@ -0,0 +1,62 @@ +assertEquals( $expected, edac_url_add_scheme_if_not_existing( $url, 'https' ) ); + + $url_http = '//example.com'; + $expected = 'http://example.com'; + $this->assertEquals( $expected, edac_url_add_scheme_if_not_existing( $url_http, 'http' ) ); + + $url_one_slash = '/example.com'; + $this->assertEquals( $url_one_slash, edac_url_add_scheme_if_not_existing( $url_one_slash ) ); + + $url_no_slash = 'example.com'; + $this->assertStringStartsWith( 'http', edac_url_add_scheme_if_not_existing( $url_no_slash ) ); + } + + /** + * Test that the function doesn't add a scheme to a url that already has one. + */ + public function test_url_add_scheme_if_not_existing_with_scheme() { + $url = 'https://example.com'; + $this->assertEquals( $url, edac_url_add_scheme_if_not_existing( $url ) ); + + $url_http = 'http://example.com/'; + $this->assertEquals( $url_http, edac_url_add_scheme_if_not_existing( $url_http, 'http' ) ); + } + + /** + * Test that the function doesn't add a scheme to a url that already has one, even when not http*. + */ + public function test_url_add_scheme_if_not_existing_unmoidfied_with_ftp() { + $ftp_url = 'ftp://example.com'; + $this->assertEquals( $ftp_url, edac_url_add_scheme_if_not_existing( $ftp_url ) ); + } + + /** + * Test that the function doesn't add a scheme to a local or relative url. + */ + public function test_url_add_scheme_if_not_existing_unmoidfied_when_local_or_relative() { + $local_path = '/wp-content/uploads/2024/03/image.gif'; + $this->assertEquals( $local_path, edac_url_add_scheme_if_not_existing( $local_path ) ); + + $relative_url = '/about.gif'; + $this->assertEquals( $relative_url, edac_url_add_scheme_if_not_existing( $relative_url ) ); + } +} diff --git a/tests/phpunit/helper-functions/UrlExistsTest.php b/tests/phpunit/helper-functions/UrlExistsTest.php new file mode 100644 index 00000000..e2bda1a1 --- /dev/null +++ b/tests/phpunit/helper-functions/UrlExistsTest.php @@ -0,0 +1,34 @@ +assertTrue( edac_url_exists( $url ) ); + } + + /** + * Test that we get false on a non-2xx status code. + * + * @group external-http + */ + public function test_url_does_not_exist() { + $url = 'https://httpbin.org/status/404'; + $this->assertFalse( edac_url_exists( $url ) ); + $url = 'https://httpbin.org/status/418'; + $this->assertFalse( edac_url_exists( $url ) ); + } +} diff --git a/tests/phpunit/includes/SummaryGeneratorTest.php b/tests/phpunit/includes/SummaryGeneratorTest.php new file mode 100644 index 00000000..b48e7748 --- /dev/null +++ b/tests/phpunit/includes/SummaryGeneratorTest.php @@ -0,0 +1,42 @@ +post->create(); + update_post_meta( $post_id, '_edac_density_data', '0,0' ); + + $simplified_summary = new Summary_Generator( $post_id ); + + // Reflection means that the method was hard to test in isolation and + // likely warrants a refactor so that the method is more testable. + $method = ( new ReflectionClass( get_class( $simplified_summary ) ) ) + ->getMethod( 'update_issue_density' ); + $method->setAccessible( true ); + + $method->invoke( $simplified_summary, array() ); + + // We are really testing here that the method does not throw an error, + // but we may as well check that the meta didn't change as well since + // we are here and by this point already know the method did not fatal. + $this->assertEquals( + '0,0', + get_post_meta( $post_id, '_edac_density_data', true ) + ); + } +} diff --git a/tests/phpunit/includes/rules/ImgAnimatedGifTest.php b/tests/phpunit/includes/rules/ImgAnimatedGifTest.php new file mode 100644 index 00000000..a10cf7e5 --- /dev/null +++ b/tests/phpunit/includes/rules/ImgAnimatedGifTest.php @@ -0,0 +1,114 @@ +assertTrue( edac_img_gif_is_animated( $filename ) ); + } + + /** + * Test that static gif is not detected as animated. + */ + public function testAnimatedGifDetectionWithStaticGif() { + $filename = EDAC_TEST_ASSETS_DIR . 'static.gif'; + $this->assertFalse( edac_img_gif_is_animated( $filename ) ); + } + + /** + * Test that animated webp is detected as animated. + */ + public function testAnimatedWebpDetectionWithAnimatedWebp() { + $filename = EDAC_TEST_ASSETS_DIR . 'animated.webp'; + $this->assertTrue( edac_img_webp_is_animated( $filename ) ); + } + + /** + * Test that static webp is not detected as animated. + */ + public function testAnimatedWebpDetectionWithStaticWebp() { + $filename = EDAC_TEST_ASSETS_DIR . 'static.webp'; + $this->assertFalse( edac_img_webp_is_animated( $filename ) ); + } + + /** + * Test that animated gif is detected when passed through entire rule. + */ + public function testRuleWithAnimatedGifInContent() { + $html = ''; + $dom = $this->get_DOM( $html ); + $content = array( 'html' => $dom ); + $post = new stdClass(); + $errors = edac_rule_img_animated_gif( $content, $post ); + $this->assertNotEmpty( $errors ); + } + + /** + * Test that static gif is not detected as animated when passed through entire rule. + */ + public function testRuleWithStaticGifInContent() { + $html = ''; + $dom = $this->get_DOM( $html ); + $content = array( 'html' => $dom ); + $post = new stdClass(); + $errors = edac_rule_img_animated_gif( $content, $post ); + $this->assertEmpty( $errors ); + } + + /** + * Test that animated webp is detected as animated when passed through entire rule. + */ + public function testRuleWithAnimatedWebpInContent() { + $html = ''; + $dom = $this->get_DOM( $html ); + $content = array( 'html' => $dom ); + $post = new stdClass(); + $errors = edac_rule_img_animated_gif( $content, $post ); + $this->assertNotEmpty( $errors ); + } + + /** + * Test that static webp is not detected as animated when passed through entire rule. + */ + public function testRuleWithStaticWebpInContent() { + + $html = ''; + $dom = $this->get_DOM( $html ); + $content = array( 'html' => $dom ); + $post = new stdClass(); + $errors = edac_rule_img_animated_gif( $content, $post ); + $this->assertEmpty( $errors ); + } + + /** + * Wrapper to generate dom objects that match the shape of the object in the plugin. + * + * @param string $html_string HTML string. + * @return EDAC_Dom + */ + private function get_DOM( string $html_string = '' ) { + return new EDAC_Dom( + $html_string, + true, + true, + DEFAULT_TARGET_CHARSET, + true, + DEFAULT_BR_TEXT, + DEFAULT_SPAN_TEXT + ); + } +}