diff --git a/.gitignore b/.gitignore index 8114f97f..8104dba7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,7 @@ vendor/ test/ _dev/ _private/ -.ddev/ \ No newline at end of file +.ddev/ +test.json +test.php +test.html \ No newline at end of file diff --git a/actions/toggle_site_status.php b/actions/toggle_site_status.php index 1ae3d67b..ff8c9dfc 100644 --- a/actions/toggle_site_status.php +++ b/actions/toggle_site_status.php @@ -86,7 +86,6 @@ function update_alerts($new_status, $site_id) { DataAccess::get_meta_value('active_integrations') ); - // Create active sites to alerts filter. $integration_uris = NULL; if(!empty($active_integrations)){ diff --git a/actions/update_meta.php b/actions/update_meta.php index 7ab26ad3..17cc595e 100644 --- a/actions/update_meta.php +++ b/actions/update_meta.php @@ -25,8 +25,11 @@ // TODO: update logic, so the $_POST parameters are // set in the integrations file. See Github Issue #12. $account_records = []; -if(!empty($_POST['wave_key'])){ +if(isset($_POST['wave_key'])){ DataAccess::update_meta_value('wave_key', $_POST['wave_key']); }; +if(isset($_POST['axe_uri'])){ + DataAccess::update_meta_value('axe_uri', $_POST['axe_uri']); +}; header('Location: ../index.php?view='.$last_view.'&status=success'); \ No newline at end of file diff --git a/cli/scan.php b/cli/scan.php index 6e66ab6b..be83c4cd 100644 --- a/cli/scan.php +++ b/cli/scan.php @@ -6,10 +6,6 @@ * be designed to be as efficient as possible so that * Equalify works for everyone. **********************************************************/ - -// PRIVATE session ID is declared as an argument in CLI. -if(!isset($_SESSION)) - $_SESSION['id'] = $argv[1]; // Since this file can run in the CLI, we must set the // directory if it isn't already set. diff --git a/helpers/process_alerts.php b/helpers/process_alerts.php index 9bb8024d..fb78607e 100644 --- a/helpers/process_alerts.php +++ b/helpers/process_alerts.php @@ -116,7 +116,8 @@ function process_alerts( array $integration_output) { 'status' => $alert->status, 'site_id' => $alert->site_id, 'tags' => $alert->tags, - 'source' => $alert->source + 'source' => $alert->source, + 'more_info' => $alert->more_info ); array_push($rows, $new_row); }; diff --git a/install.php b/install.php index 4804a920..28f95ced 100644 --- a/install.php +++ b/install.php @@ -7,10 +7,6 @@ * Equalify works for everyone. **********************************************************/ -// Wave key is required for activation. -if(empty($GLOBALS['wave_key'])) - throw new Exception('Equalify requires a WAVE key. Get your key at https://wave.webaim.org/api/ and add it to the config.php file.'); - // All the tables are created with this action. if(DataAccess::table_exists('alerts') == false) DataAccess::create_alerts_table(); diff --git a/integrations/axe/axe_tags.json b/integrations/axe/axe_tags.json new file mode 100644 index 00000000..79b06a2a --- /dev/null +++ b/integrations/axe/axe_tags.json @@ -0,0 +1,284 @@ +[ + { + "slug": "wcag2a", + "title": "WCAG 2.0 Level A", + "description": "", + "category": "WCAG Axe Tags" + }, + { + "slug": "wcag2aa", + "title": "WCAG 2.0 Level AA", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag2aaa", + "title": "WCAG 2.0 Level AAA", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag21a", + "title": "WCAG 2.1 Level A", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag21aa", + "title": "WCAG 2.1 Level AA", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag21aaa", + "title": "WCAG 2.1 Level AAA", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag111", + "title": "WCAG Success Criteria 1.1.1", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag121", + "title": "WCAG Success Criteria 1.2.1", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag122", + "title": "WCAG Success Criteria 1.2.2", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag131", + "title": "WCAG Success Criteria 1.3.1", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag141", + "title": "WCAG Success Criteria 1.4.1", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag143", + "title": "WCAG Success Criteria 1.4.3", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag144", + "title": "WCAG Success Criteria 1.4.4", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag211", + "title": "WCAG Success Criteria 2.1.1", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag221", + "title": "WCAG Success Criteria 2.2.1", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag222", + "title": "WCAG Success Criteria 2.2.2", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag241", + "title": "WCAG Success Criteria 2.4.1", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag242", + "title": "WCAG Success Criteria 2.4.2", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag244", + "title": "WCAG Success Criteria 2.4.4", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag311", + "title": "WCAG Success Criteria 3.1.1", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag312", + "title": "WCAG Success Criteria 3.1.2", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag332", + "title": "WCAG Success Criteria 3.3.2", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag411", + "title": "WCAG Success Criteria 4.1.1", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "wcag412", + "title": "WCAG Success Criteria 4.1.2", + "category": "WCAG Axe Tags", + "description": "" + }, + { + "slug": "section508", + "title": "Section 508", + "category": "Other Axe Tags", + "description": "Section 508 is part of a 1998 amendment to the Rehabilitation Act of 1973. It requires all Federal electronic content to be accessible" + }, + { + "slug": "section50822a", + "title": "Section 508.22.a", + "category": "Other Axe Tags", + "description": "" + }, + { + "slug": "section50822f", + "title": "Section 508.22.f", + "category": "Other Axe Tags", + "description": "" + }, + { + "slug": "section50822g", + "title": "Section 508.22.g", + "category": "Other Axe Tags", + "description": "" + }, + { + "slug": "section50822i", + "title": "Section 508.22.i", + "category": "Other Axe Tags", + "description": "" + }, + { + "slug": "section50822j", + "title": "Section 508.22.j", + "category": "Other Axe Tags", + "description": "" + }, + { + "slug": "section50822n", + "title": "Section 508.22.n", + "category": "Other Axe Tags", + "description": "" + }, + { + "slug": "section50822o", + "title": "Section 508.22.o", + "category": "Other Axe Tags", + "description": "" + }, + { + "slug": "ACT", + "title": "ACT", + "category": "Other Axe Tags", + "description": "ACT Rules are designed to harmonize how edge cases for WCAG and other accessibility guidance are tested." + }, + { + "slug": "best-practice", + "title": "Best Practices", + "category": "Other Axe Tags", + "description": "Common accessibility best practices." + }, + { + "slug": "cat.aria", + "title": "Aria", + "category": "Axe Categories", + "description": "" + }, + { + "slug": "cat.color", + "title": "Color", + "category": "Axe Categories", + "description": "" + }, + { + "slug": "cat.forms", + "title": "Forms", + "category": "Axe Categories", + "description": "axe/forms contains accessibility rules from the axe forms category." + }, + { + "slug": "cat.keyboard", + "title": "Keyboard", + "category": "Axe Categories", + "description": "axe/keyboard contains accessibility rules from the axe keyboard category." + }, + { + "slug": "cat.language", + "title": "Language", + "category": "Axe Categories", + "description": "axe/language contains accessibility rules from the axe language category." + }, + { + "slug": "cat.name-role-value", + "title": "Name Role Value", + "category": "Axe Categories", + "description": "axe/name-role-value contains accessibility rules from the axe name-role-value category." + }, + { + "slug": "cat.parsing", + "title": "Parsing", + "category": "Axe Categories", + "description": "axe/parsing contains accessibility rules from the axe parsing category." + }, + { + "slug": "cat.semantics", + "title": "Semantics", + "category": "Axe Categories", + "description": "axe/semantics contains accessibility rules from the axe semantics category." + }, + { + "slug": "cat.sensory-and-visual-cues", + "title": "Sensory and Visual Cues", + "category": "Axe Categories", + "description": "axe/sensory-and-visual-cues contains accessibility rules from the axe sensory-and-visual-cues category." + }, + { + "slug": "cat.structure", + "title": "Structure", + "category": "Axe Categories", + "description": "axe/structure contains accessibility rules from the axe structure category." + }, + { + "slug": "cat.tables", + "title": "Tables", + "category": "Axe Categories", + "description": "axe/tables contains accessibility rules from the axe tables category." + }, + { + "slug": "cat.text-alternatives", + "title": "Text Alternatives", + "category": "Axe Categories", + "description": "axe/text-alternatives contains accessibility rules from the axe text-alternatives category." + }, + { + "slug": "cat.time-and-media", + "title": "Time and Media", + "category": "Axe Categories", + "description": "axe/time-and-media contains accessibility rules from the axe time-and-media category." + } +] diff --git a/integrations/axe/functions.php b/integrations/axe/functions.php new file mode 100644 index 00000000..5775dc0c --- /dev/null +++ b/integrations/axe/functions.php @@ -0,0 +1,195 @@ + [ + + // Meta values. + 'meta' => [ + array( + 'name' => 'axe_uri', + 'value' => '', + ) + ] + + ], + + // These fields are HTML fields on the settings view. + 'settings' => [ + + // Meta settings. + 'meta' => [ + array( + 'name' => 'axe_uri', + 'label' => 'axe-core URI (ie- https://axe.equalify.app/?url=)', + 'type' => 'text', + ) + ] + + ] + + ); + + // Return fields + return $axe_fields; + +} + +/** + * axe Tags + */ +function axe_tags(){ + + // We don't know where helpers are being called, so we + // have to set the directory if it isn't already set. + if(!defined('__DIR__')) + define('__DIR__', dirname(dirname(__FILE__))); + + // Read the JSON file - pulled from https://axe.webaim.org/api/docs?format=json + $axe_tag_json = file_get_contents(__DIR__.'/axe_tags.json'); + $axe_tags = json_decode($axe_tag_json,true); + + // Convert axe format into Equalify format: + // tags [ array('slug' => $value, 'name' => $value, 'description' => $value) ] + $tags = array(); + if(!empty($axe_tags)){ + foreach($axe_tags as $axe_tag){ + + // First, let's prepare the description, which is + // the summary and guidelines. + $description = '

'.$axe_tag['description'].'

'; + + // Now lets put it all together into the Equalify format. + array_push( + $tags, array( + 'title' => $axe_tag['title'], + 'category' => $axe_tag['category'], + 'description' => $description, + + // axe-core uses periods, which get screwed up + // when equalify serializes them, so we're + // just not going to use periods + 'slug' => str_replace('.', '', $axe_tag['slug']) + + ) + ); + + } + } + + // Return tags. + return $tags; + +} + + /** + * Axe URLs + * Maps site URLs to Axe URLs for processing. + */ +function axe_urls($page_url) { + + // Require axe_uri + $axe_uri = DataAccess::get_meta_value('axe_uri'); + if(empty($axe_uri)){ + throw new Exception('axe-core URI is not entered. Please add the URI in the integration settings.'); + }else{ + return $axe_uri.$page_url; + } + +} + +/** + * Axe Alerts + * @param string response_body + * @param string page_url + */ +function axe_alerts($response_body, $page_url){ + + // Our goal is to return alerts. + $axe_alerts = []; + $axe_json = $response_body; + + // Decode JSON. + $axe_json_decoded = json_decode($axe_json); + + // Sometimes Axe can't read the json. + if(empty($axe_json_decoded)){ + + // And add an alert. + $alert = array( + 'source' => 'axe', + 'url' => $page_url, + 'message' => 'axe-core cannot reach the page.', + ); + array_push($axe_alerts, $alert); + + }else{ + + // We're add a lit of violations. + $axe_violations = array(); + + // Show axe violations + foreach($axe_json_decoded[0]->violations as $violation){ + + // Only show violations. + $axe_violations[] = $violation; + + } + + // Add alerts. + if(!empty($axe_violations)) { + + // Setup alert variables. + foreach($axe_violations as $violation){ + + // Default variables. + $alert = array(); + $alert['source'] = 'axe'; + $alert['url'] = $page_url; + + // Setup tags. + $alert['tags'] = ''; + if(!empty($violation->tags)){ + + // We need to get rid of periods so Equalify + // wont convert them to underscores and they + // need to be comma separated. + $tags = $violation->tags; + $copy = $tags; + foreach($tags as $tag){ + $alert['tags'].= str_replace('.', '', $tag); + if (next($copy )) + $alert['tags'].= ','; + } + } + + // Setup message. + $alert['message'] = '"'.$violation->id.'" violation: '.$violation->help; + + // Setup more info. + $alert['more_info'] = ''; + if($violation->nodes) + $alert['more_info'] = $violation->nodes; + + // Push alert. + $axe_alerts[] = $alert; + + } + + } + + } + // Return alerts. + return $axe_alerts; + +} \ No newline at end of file diff --git a/integrations/axe/logo.jpg b/integrations/axe/logo.jpg new file mode 100644 index 00000000..6422f29d Binary files /dev/null and b/integrations/axe/logo.jpg differ diff --git a/integrations/drupal/functions.php b/integrations/drupal/functions.php deleted file mode 100644 index f227e039..00000000 --- a/integrations/drupal/functions.php +++ /dev/null @@ -1,7 +0,0 @@ - \ No newline at end of file diff --git a/integrations/drupal/logo.jpg b/integrations/drupal/logo.jpg deleted file mode 100644 index 8daaed15..00000000 Binary files a/integrations/drupal/logo.jpg and /dev/null differ diff --git a/integrations/pantheon/functions.php b/integrations/pantheon/functions.php deleted file mode 100644 index 74e28385..00000000 --- a/integrations/pantheon/functions.php +++ /dev/null @@ -1,10 +0,0 @@ -WAVE Report'; + $alert['message'] = $wave_item['description'].$appended_text.' - WAVE Report (opens in a new tab)'; // Push alert. $wave_alerts[] = $alert; diff --git a/integrations/wordpress/functions.php b/integrations/wordpress/functions.php deleted file mode 100644 index 5c610b1a..00000000 --- a/integrations/wordpress/functions.php +++ /dev/null @@ -1,6 +0,0 @@ -id "; @@ -750,6 +751,7 @@ public static function create_alerts_table(){ `url` text, `message` text, `tags` text, + `more_info` text, `archived` BOOLEAN NOT NULL DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"; @@ -806,6 +808,7 @@ public static function create_queued_alerts_table(){ `url` text, `message` text, `tags` text, + `more_info` text, `archived` BOOLEAN NOT NULL DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"; @@ -836,34 +839,35 @@ public static function create_meta_table(){ // Query 1 self::query($sql_1, $params, false); - // Now, create the content in the meta table. + // Now, create the content in the meta table with axe + // as the default integration. $sql_2 = " INSERT INTO `meta` (meta_name, meta_value) VALUES ('active_integrations', ?), - ('wave_key', ?), ('scan_status', ?), ('scan_schedule', ?), ('scan_log', ?), ('scannable_pages', ?), + ('axe_key', ?), ('pages_scanned', ?), ('last_scan_time', ?); "; - $default_active_integrations = serialize(array('wave')); - $default_wave_key = $GLOBALS['wave_key']; + $default_active_integrations = serialize(array('axe')); $default_scan_status = ''; $default_scan_schedule = 'manually'; $default_scan_log = ''; $default_scannable_pages = serialize(array()); + $default_axe_key = ''; $default_last_scan_time = ''; $default_pages_scanned = 0; $params = array( $default_active_integrations, - $default_wave_key, $default_scan_status, $default_scan_schedule, $default_scan_log, $default_scannable_pages, + $default_axe_key, $default_pages_scanned, $default_last_scan_time ); diff --git a/models/view_components.php b/models/view_components.php index 74c922a5..e542c39c 100644 --- a/models/view_components.php +++ b/models/view_components.php @@ -61,10 +61,8 @@ function the_active_class($selection){ } // We need to return active for the default view. - }elseif(empty($_GET['view']) && $selection == 'alerts'){ - + }elseif(empty($_GET['view']) && $selection == 'reports'){ echo 'active'; - }else{ return null; } diff --git a/sample-config.php b/sample-config.php index 01985a20..270efb73 100644 --- a/sample-config.php +++ b/sample-config.php @@ -23,6 +23,9 @@ // Visit https://wave.webaim.org/api/ to get a WAVE key. $GLOBALS['wave_key'] = ''; +// Visit https://github.com/bbertucc/axe-equalify for more info. +$GLOBALS['axe_uri'] = ''; + // Additional options. $GLOBALS['page_limit'] = '2222'; $GLOBALS['scan_concurrency'] = '6'; diff --git a/test.php b/test.php index f35a8efd..884aaefd 100644 --- a/test.php +++ b/test.php @@ -1,3 +1,83 @@ request('GET', 'https://wave.webaim.org/api/docs?format=json'); +$response = $client->request('GET', 'https://axe.equalify.app/index.php?url=decubing.com'); + +$axe_json = $response->getBody()->getContents(); +$axe_json_decoded = json_decode($axe_json); + +$page_url = 'test.com'; + +// START PLUGIN + +// Decode JSON. +$axe_json_decoded = json_decode($axe_json); + +// Sometimes Axe can't read the json. +if(empty($axe_json_decoded)){ + + // And add an alert. + $alert = array( + 'source' => 'axe-core', + 'url' => $page_url, + 'message' => 'axe-core cannot reach the page.', + ); + array_push($axe_alerts, $alert); + +}else{ + + // We're add a lit of violations. + $axe_violations = array(); + + // Show axe violations + foreach($axe_json_decoded[0]->violations as $violation){ + + // Only show violations. + $axe_violations[] = $violation; + + } + + // Add alerts. + if(!empty($axe_violations)) { + + // Setup alert variables. + foreach($axe_violations as $violation){ + + // Default variables. + $alert = array(); + $alert['source'] = 'axe-core'; + $alert['url'] = $page_url; + + // Setup tags. + $alert['tags'] = $violation->tags; + + // Setup message. + $alert['message'] = '"'.$violation->id.'" violation: '.$violation->help; + + // Push alert. + $axe_alerts[] = $alert; + + } + + } + +} + +// Test to make sure it works +echo '
';
+print_r($axe_alerts);
+echo '
'; +die; + +// Return everything +// return $axe_alerts; \ No newline at end of file diff --git a/theme.css b/theme.css index 6d342f90..d2ba4eb7 100644 --- a/theme.css +++ b/theme.css @@ -66,4 +66,11 @@ pre code{ white-space: -pre-wrap; /* Opera 4-6 */ white-space: -o-pre-wrap; /* Opera 7 */ word-wrap: break-word; /* Internet Explorer 5.5+ */ -} \ No newline at end of file +} +.screen-reader-only { + position: absolute; + width: 1px; + clip: rect(0 0 0 0); + overflow: hidden; + white-space: nowrap; + } \ No newline at end of file diff --git a/views/integration_settings.php b/views/integration_settings.php index ecabf3e0..04d1e637 100644 --- a/views/integration_settings.php +++ b/views/integration_settings.php @@ -46,13 +46,13 @@ $settings = $settings['meta']; foreach($settings as $setting): $name = $setting['name']; - $report = $setting['report']; + $label = $setting['label']; $type = $setting['type']; ?>
- +

diff --git a/views/reports.php b/views/reports.php index a1055f3c..6c30b34c 100644 --- a/views/reports.php +++ b/views/reports.php @@ -237,6 +237,17 @@ more_info)){ + ?> + + + More Info + + + status == 'active' && $alert->archived != 1 ){ ?> @@ -257,6 +268,7 @@ + diff --git a/views/single_alert.php b/views/single_alert.php new file mode 100644 index 00000000..df79c3b1 --- /dev/null +++ b/views/single_alert.php @@ -0,0 +1,117 @@ + 'id', + 'value' => $alert_id + ) +); +$report = (array)DataAccess::get_db_rows( + 'alerts', $filtered_to_alert +)['content'][0]; +?> + +
+
+

+ + + +

+
+
+ + + 20){ + echo substr($report['url'], 0, 20).'...'; + }else{ + echo $report['url']; + } + ?> + + + + 'slug', + 'value' => $tag + ) + ); + $tag_info = (array)DataAccess::get_db_rows( + 'tags', $filtered_to_tag + )['content'][0]; + echo ''.$tag_info['title'].' '; + } + + } + } + ?> + +
+
+
+ +

+ + + +

+ + + +

More Info

+ + any[0]) && !empty($info->all[0])){ + $message = $info->all[0]->message; + }elseif(!empty($info->any[0]) && empty($info->all[0])){ + $message = $info->any[0]->message; + }else{ + $message = ''; + } + + $count++; + echo '
'; + echo '

'.$message.'

'; + echo '
'.$info->html.'
'; + echo '
'; + } + ?> + + + +
\ No newline at end of file