Compare commits

...
3 Commits
9 changed files with 170 additions and 54 deletions
+7 -2
View File
@@ -17,7 +17,7 @@
"authors": [ "authors": [
{ {
"name": "YakNet Bilişim", "name": "YakNet Bilişim",
"email": "[email protected].tr", "email": "[email protected].tr",
"role": "Developer" "role": "Developer"
} }
], ],
@@ -45,5 +45,10 @@
"bin/a11y" "bin/a11y"
], ],
"minimum-stability": "stable", "minimum-stability": "stable",
"prefer-stable": true "prefer-stable": true,
"config": {
"allow-plugins": {
"php-http/discovery": true
}
}
} }
+1 -1
View File
@@ -37,7 +37,7 @@ If it's about ARIA roles, add the correct ones.
PROMPT; PROMPT;
try { try {
$result = $this->client->geminiPro()->generateContent($prompt); $result = $this->client->generativeModel('gemini-2.5-flash')->generateContent($prompt);
$suggestion = trim($result->text()); $suggestion = trim($result->text());
// Clean up if AI included markdown blocks anyway // Clean up if AI included markdown blocks anyway
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace YakNet\AccessibilityConsole\Core;
abstract class AbstractRule
{
/**
* @return Violation[]
*/
abstract public function check(\DOMDocument $doc): array;
/**
* Get the WCAG standard Enum
*/
abstract public function getStandard(): WCAGStandard;
/**
* Get the severity level Enum
*/
abstract public function getSeverity(): Severity;
protected function createViolation(string $message, \DOMElement $element): Violation
{
return new Violation(
ruleId: (new \ReflectionClass($this))->getShortName(),
message: $message,
severity: $this->getSeverity(),
standard: $this->getStandard(),
htmlSnippet: $element->ownerDocument->saveHTML($element)
);
}
}
+10 -7
View File
@@ -2,23 +2,26 @@
namespace YakNet\AccessibilityConsole\Core; namespace YakNet\AccessibilityConsole\Core;
use YakNet\AccessibilityConsole\Rules\RuleInterface;
class RuleEngine class RuleEngine
{ {
/** @var RuleInterface[] */ /** @var AbstractRule[] */
private array $rules = []; private array $rules = [];
public function addRule(RuleInterface $rule): void public function addRule(AbstractRule $rule): void
{ {
$this->rules[] = $rule; $this->rules[] = $rule;
} }
/** /**
* @return RuleInterface[] * @return Violation[]
*/ */
public function getRules(): array public function run(\DOMDocument $doc): array
{ {
return $this->rules; $allViolations = [];
foreach ($this->rules as $rule) {
$violations = $rule->check($doc);
$allViolations = array_merge($allViolations, $violations);
}
return $allViolations;
} }
} }
+12 -43
View File
@@ -2,62 +2,31 @@
namespace YakNet\AccessibilityConsole\Core; namespace YakNet\AccessibilityConsole\Core;
use DOMDocument;
use YakNet\AccessibilityConsole\Rules\RuleInterface;
class Scanner class Scanner
{ {
private array $rules = []; public function __construct(private ?RuleEngine $engine = null)
public function __construct(
private readonly ?RuleEngine $engine = null
) {
if ($this->engine) {
$this->rules = $this->engine->getRules();
}
}
public function addRule(RuleInterface $rule): void
{ {
$this->rules[] = $rule; if ($this->engine === null) {
$this->engine = new RuleEngine();
}
} }
/** /**
* Scan HTML content for accessibility violations.
*
* @param string $html
* @return Violation[] * @return Violation[]
*/ */
public function scan(string $html): array public function scan(string $html): array
{ {
if (trim($html) === '') { if (empty(trim($html))) {
return []; return [];
} }
$dom = new DOMDocument(); $doc = new \DOMDocument();
$previousState = libxml_use_internal_errors(true); // Suppress warnings for malformed HTML
$loaded = $dom->loadHTML('<?xml encoding="utf-8" ?>' . $html, LIBXML_NOERROR | LIBXML_NOWARNING); @$doc->loadHTML('<?xml encoding="utf-8" ?>' . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
libxml_clear_errors();
libxml_use_internal_errors($previousState);
if (!$loaded) { return $this->engine->run($doc);
return [];
}
$violations = [];
$elements = $dom->getElementsByTagName('*');
foreach ($elements as $element) {
if (!$element instanceof \DOMElement) continue;
foreach ($this->rules as $rule) {
try {
$violation = $rule->check($element);
if ($violation) {
$violations[] = $violation;
}
} catch (\Throwable) {
continue;
}
}
}
return $violations;
} }
} }
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace YakNet\AccessibilityConsole\Rules;
use YakNet\AccessibilityConsole\Core\AbstractRule;
use YakNet\AccessibilityConsole\Core\Severity;
use YakNet\AccessibilityConsole\Core\WCAGStandard;
class EmptyLinkRule extends AbstractRule
{
public function check(\DOMDocument $doc): array
{
$violations = [];
$links = $doc->getElementsByTagName('a');
foreach ($links as $link) {
if (trim($link->textContent) === '' && !$link->hasAttribute('aria-label')) {
$violations[] = $this->createViolation(
"Bağlantı (a) etiketi metin içermiyor ve 'aria-label' tanımlanmamış. Ekran okuyucular bu linkin nereye gittiğini anlayamaz.",
$link
);
}
}
return $violations;
}
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
public function getSeverity(): Severity { return Severity::CRITICAL; }
}
+33
View File
@@ -0,0 +1,33 @@
<?php
namespace YakNet\AccessibilityConsole\Rules;
use YakNet\AccessibilityConsole\Core\AbstractRule;
use YakNet\AccessibilityConsole\Core\Severity;
use YakNet\AccessibilityConsole\Core\WCAGStandard;
class HeadingOrderRule extends AbstractRule
{
public function check(\DOMDocument $doc): array
{
$violations = [];
$xpath = new \DOMXPath($doc);
$headings = $xpath->query('//h1|//h2|//h3|//h4|//h5|//h6');
$lastLevel = 0;
foreach ($headings as $h) {
$level = (int)substr($h->tagName, 1);
if ($lastLevel > 0 && $level > $lastLevel + 1) {
$violations[] = $this->createViolation(
"Başlık hiyerarşisi bozuk. H{$lastLevel} etiketinden doğrudan H{$level} etiketine geçiş yapılmış. Sıralı olmalıdır.",
$h
);
}
$lastLevel = $level;
}
return $violations;
}
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
public function getSeverity(): Severity { return Severity::WARNING; }
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace YakNet\AccessibilityConsole\Rules;
use YakNet\AccessibilityConsole\Core\AbstractRule;
use YakNet\AccessibilityConsole\Core\Severity;
use YakNet\AccessibilityConsole\Core\WCAGStandard;
class ImageAltRule extends AbstractRule
{
public function check(\DOMDocument $doc): array
{
$violations = [];
$images = $doc->getElementsByTagName('img');
foreach ($images as $img) {
if (!$img->hasAttribute('alt') || trim($img->getAttribute('alt')) === '') {
$violations[] = $this->createViolation(
"Görüntü (img) etiketi 'alt' özniteliğine sahip değil veya boş. WCAG 1.1.1 uyarınca her görüntünün bir açıklaması olmalıdır.",
$img
);
}
}
return $violations;
}
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
public function getSeverity(): Severity { return Severity::CRITICAL; }
}
+16
View File
@@ -0,0 +1,16 @@
<?php
namespace YakNet\AccessibilityConsole\Rules;
use YakNet\AccessibilityConsole\Core\RuleEngine;
class StrictRuleSet extends RuleEngine
{
public function __construct()
{
$this->addRule(new ImageAltRule());
$this->addRule(new HeadingOrderRule());
$this->addRule(new EmptyLinkRule());
// Daha fazla kural buraya eklenebilir
}
}