110 lines
3.5 KiB
PHP
110 lines
3.5 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Services;
|
||
|
|
|
||
|
|
class AccessibilityAnalyzer
|
||
|
|
{
|
||
|
|
/**
|
||
|
|
* Analyze block-based content for common accessibility issues.
|
||
|
|
* This method scans for missing alt text, incorrect heading hierarchy, and other WCAG-related gaps.
|
||
|
|
*
|
||
|
|
* @param array $content The JSON-encoded block content from the editor.
|
||
|
|
* @return array A list of identified issues with severity and messages.
|
||
|
|
*/
|
||
|
|
public function analyze(array $content): array
|
||
|
|
{
|
||
|
|
// Handle multi-locale content
|
||
|
|
$isMultiLocale = false;
|
||
|
|
$locales = array_keys($content);
|
||
|
|
foreach ($locales as $locale) {
|
||
|
|
if (is_string($locale) && is_array($content[$locale]) && isset($content[$locale][0]['type'])) {
|
||
|
|
$isMultiLocale = true;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($isMultiLocale) {
|
||
|
|
$allIssues = [];
|
||
|
|
foreach ($content as $locale => $localeContent) {
|
||
|
|
if (is_array($localeContent)) {
|
||
|
|
$localeIssues = $this->analyzeBlocks($localeContent);
|
||
|
|
if (!empty($localeIssues)) {
|
||
|
|
$allIssues[$locale] = $localeIssues;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return $allIssues;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $this->analyzeBlocks($content);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Internal helper to analyze a flat list of blocks.
|
||
|
|
*
|
||
|
|
* @param array $content
|
||
|
|
* @return array
|
||
|
|
*/
|
||
|
|
protected function analyzeBlocks(array $content): array
|
||
|
|
{
|
||
|
|
$issues = [];
|
||
|
|
$headings = [];
|
||
|
|
|
||
|
|
foreach ($content as $index => $block) {
|
||
|
|
$type = $block['type'] ?? '';
|
||
|
|
$data = $block['data'] ?? [];
|
||
|
|
|
||
|
|
switch ($type) {
|
||
|
|
case 'heading':
|
||
|
|
$level = (int) ($data['level'] ?? 0);
|
||
|
|
if ($level > 0) {
|
||
|
|
$headings[] = [
|
||
|
|
'level' => $level,
|
||
|
|
'index' => $index
|
||
|
|
];
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'image':
|
||
|
|
$alt = $data['alt'] ?? '';
|
||
|
|
if (empty($alt)) {
|
||
|
|
$issues[] = [
|
||
|
|
'severity' => 'error',
|
||
|
|
'message' => 'Image block is missing alternative text (alt tag).',
|
||
|
|
'block_index' => $index,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'media':
|
||
|
|
$alt = $data['alt'] ?? '';
|
||
|
|
if (empty($alt)) {
|
||
|
|
$issues[] = [
|
||
|
|
'severity' => 'warning',
|
||
|
|
'message' => 'Media block may be missing alternative text.',
|
||
|
|
'block_index' => $index,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!empty($headings)) {
|
||
|
|
$prevLevel = 0;
|
||
|
|
foreach ($headings as $h) {
|
||
|
|
$level = $h['level'];
|
||
|
|
if ($prevLevel > 0 && $level > $prevLevel + 1) {
|
||
|
|
$issues[] = [
|
||
|
|
'severity' => 'warning',
|
||
|
|
'message' => "Skipped heading level from H$prevLevel to H$level. Headings should be sequential.",
|
||
|
|
'block_index' => $h['index'],
|
||
|
|
];
|
||
|
|
}
|
||
|
|
$prevLevel = $level;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $issues;
|
||
|
|
}
|
||
|
|
}
|