Git Komut Rehberi & Terminal Asistanı
Sıfırdan Yeni Bir Depo Başlatın:
Mevcut Bir Git Deposu veya Klasörü Gönderin:
Hataları Geri Alma, İptal & Temizlik:
Son commit'i geri al (yapılan değişiklikleri korur):
git reset --soft HEAD~1
Yapılan yerel değişiklikleri iptal et (çalışma ağacını temizler):
git restore .
Takip edilmeyen gereksiz dosyaları sil:
git clean -fd
Değişiklikleri geçici sakla ve geri getir (stash):
git stash git stash pop
Dal (Branch) ve Sürüm Etiketleri:
Yeni dal aç ve geçiş yap:
git checkout -b yeni-ozellik git push -u origin yeni-ozellik
Sürüm etiketi (tag) oluştur ve gönder:
git tag -a v1.0.0 -m "Sürüm 1.0.0" git push origin --tags
feat: implement 5 new advanced WCAG rules (ColorContrast, DuplicateId, TabindexOrder, AriaRole, IframeTitle)
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use YakNet\AccessibilityConsole\Core\AbstractRule;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class AriaRole extends AbstractRule
|
||||
{
|
||||
private static array $validRoles = [
|
||||
'alert', 'alertdialog', 'application', 'article', 'banner', 'button', 'cell', 'checkbox',
|
||||
'columnheader', 'combobox', 'complementary', 'contentinfo', 'definition', 'dialog', 'directory',
|
||||
'document', 'feed', 'figure', 'form', 'grid', 'gridcell', 'group', 'heading', 'img', 'link',
|
||||
'list', 'listbox', 'listitem', 'log', 'main', 'marquee', 'math', 'menu', 'menubar', 'menuitem',
|
||||
'menuitemcheckbox', 'menuitemradio', 'navigation', 'none', 'note', 'option', 'presentation',
|
||||
'progressbar', 'radio', 'radiogroup', 'region', 'row', 'rowgroup', 'rowheader', 'scrollbar',
|
||||
'search', 'searchbox', 'separator', 'slider', 'spinbutton', 'status', 'switch', 'tab', 'table',
|
||||
'tablist', 'tabpanel', 'term', 'textbox', 'timer', 'toolbar', 'tooltip', 'tree', 'treegrid',
|
||||
'treeitem'
|
||||
];
|
||||
|
||||
public function check(\DOMDocument $doc): array
|
||||
{
|
||||
$violations = [];
|
||||
$xpath = new \DOMXPath($doc);
|
||||
|
||||
// 1. Check WAI-ARIA Role Validity
|
||||
$roleElements = $xpath->query('//*[@role]');
|
||||
foreach ($roleElements as $el) {
|
||||
if (!$el instanceof \DOMElement) {
|
||||
continue;
|
||||
}
|
||||
$role = trim($el->getAttribute('role'));
|
||||
if ($role === '') {
|
||||
continue;
|
||||
}
|
||||
$roles = array_filter(explode(' ', $role));
|
||||
foreach ($roles as $r) {
|
||||
if (!in_array(strtolower($r), self::$validRoles, true)) {
|
||||
$violations[] = $this->createViolation(
|
||||
"Invalid ARIA role \"{$r}\" declared on element. Must use standard WAI-ARIA roles.",
|
||||
$el
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check aria-controls Targets Existence
|
||||
$controlsElements = $xpath->query('//*[@aria-controls]');
|
||||
foreach ($controlsElements as $el) {
|
||||
if (!$el instanceof \DOMElement) {
|
||||
continue;
|
||||
}
|
||||
$targetsStr = trim($el->getAttribute('aria-controls'));
|
||||
if ($targetsStr === '') {
|
||||
continue;
|
||||
}
|
||||
$targets = array_filter(explode(' ', $targetsStr));
|
||||
foreach ($targets as $targetId) {
|
||||
$targetQuery = $xpath->query('//*[@id="' . $targetId . '"]');
|
||||
if ($targetQuery->length === 0) {
|
||||
$violations[] = $this->createViolation(
|
||||
"Attribute aria-controls=\"{$targetId}\" targets an element that does not exist in the document.",
|
||||
$el
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $violations;
|
||||
}
|
||||
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class ColorContrast extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_1_4_3_CONTRAST'; }
|
||||
public function getDescription(): string { return 'Text must have sufficient contrast against the background (4.5:1 minimum).'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::AA; }
|
||||
public function getSeverity(): Severity { return Severity::WARNING; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if (!$element->hasAttribute('style')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$style = $element->getAttribute('style');
|
||||
|
||||
$color = $this->parseColorValue($style, 'color');
|
||||
$bg = $this->parseColorValue($style, 'background-color') ?: $this->parseColorValue($style, 'background');
|
||||
|
||||
if ($color === null || $bg === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cRGB = $this->hexToRgb($color);
|
||||
$bgRGB = $this->hexToRgb($bg);
|
||||
|
||||
if ($cRGB === null || $bgRGB === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cLum = $this->getLuminance($cRGB[0], $cRGB[1], $cRGB[2]);
|
||||
$bgLum = $this->getLuminance($bgRGB[0], $bgRGB[1], $bgRGB[2]);
|
||||
|
||||
$ratio = ($cLum > $bgLum) ? ($cLum + 0.05) / ($bgLum + 0.05) : ($bgLum + 0.05) / ($cLum + 0.05);
|
||||
|
||||
$isLarge = false;
|
||||
if (preg_match('/font-size\s*:\s*([0-9.]+)(px|em|rem|pt)/i', $style, $matches)) {
|
||||
$val = (float)$matches[1];
|
||||
$unit = strtolower($matches[2]);
|
||||
if (($unit === 'px' && $val >= 24) || ($unit === 'pt' && $val >= 18) || (($unit === 'em' || $unit === 'rem') && $val >= 1.5)) {
|
||||
$isLarge = true;
|
||||
}
|
||||
}
|
||||
if (preg_match('/font-weight\s*:\s*(bold|[7-9]00)/i', $style)) {
|
||||
if (preg_match('/font-size\s*:\s*([0-9.]+)(px|em|rem|pt)/i', $style, $matches)) {
|
||||
$val = (float)$matches[1];
|
||||
$unit = strtolower($matches[2]);
|
||||
if (($unit === 'px' && $val >= 18) || ($unit === 'pt' && $val >= 14) || (($unit === 'em' || $unit === 'rem') && $val >= 1.2)) {
|
||||
$isLarge = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$minRatio = $isLarge ? 3.0 : 4.5;
|
||||
|
||||
if ($ratio < $minRatio) {
|
||||
$formattedRatio = round($ratio, 2);
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
"Insufficient contrast ratio of {$formattedRatio}:1. Minimum required is {$minRatio}:1.",
|
||||
"Adjust colors to increase contrast. For example, text: {$color}, background: {$bg}."
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function parseColorValue(string $style, string $property): ?string
|
||||
{
|
||||
if (preg_match('/(?:^|;)\s*' . preg_quote($property, '/') . '\s*:\s*([^;]+)/i', $style, $matches)) {
|
||||
$value = trim($matches[1]);
|
||||
if (preg_match('/^#([0-9a-f]{3,6})$/i', $value)) {
|
||||
return $value;
|
||||
}
|
||||
$colors = [
|
||||
'black' => '#000000', 'white' => '#ffffff', 'red' => '#ff0000',
|
||||
'green' => '#008000', 'blue' => '#0000ff', 'yellow' => '#ffff00',
|
||||
'gray' => '#808080', 'grey' => '#808080', 'silver' => '#c0c0c0',
|
||||
];
|
||||
if (isset($colors[strtolower($value)])) {
|
||||
return $colors[strtolower($value)];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function hexToRgb(string $hex): ?array
|
||||
{
|
||||
$hex = ltrim($hex, '#');
|
||||
if (strlen($hex) === 3) {
|
||||
$r = hexdec(substr($hex, 0, 1) . substr($hex, 0, 1));
|
||||
$g = hexdec(substr($hex, 1, 1) . substr($hex, 1, 1));
|
||||
$b = hexdec(substr($hex, 2, 1) . substr($hex, 2, 1));
|
||||
return [$r, $g, $b];
|
||||
} elseif (strlen($hex) === 6) {
|
||||
$r = hexdec(substr($hex, 0, 2));
|
||||
$g = hexdec(substr($hex, 2, 2));
|
||||
$b = hexdec(substr($hex, 4, 2));
|
||||
return [$r, $g, $b];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getLuminance(float $r, float $g, float $b): float
|
||||
{
|
||||
$rs = $r / 255.0;
|
||||
$gs = $g / 255.0;
|
||||
$bs = $b / 255.0;
|
||||
|
||||
$rVal = ($rs <= 0.03928) ? $rs / 12.92 : pow(($rs + 0.055) / 1.055, 2.4);
|
||||
$gVal = ($gs <= 0.03928) ? $gs / 12.92 : pow(($gs + 0.055) / 1.055, 2.4);
|
||||
$bVal = ($bs <= 0.03928) ? $bs / 12.92 : pow(($bs + 0.055) / 1.055, 2.4);
|
||||
|
||||
return 0.2126 * $rVal + 0.7152 * $gVal + 0.0722 * $bVal;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use YakNet\AccessibilityConsole\Core\AbstractRule;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class DuplicateId extends AbstractRule
|
||||
{
|
||||
public function check(\DOMDocument $doc): array
|
||||
{
|
||||
$violations = [];
|
||||
$xpath = new \DOMXPath($doc);
|
||||
$elements = $xpath->query('//*[@id]');
|
||||
|
||||
$ids = [];
|
||||
foreach ($elements as $el) {
|
||||
if (!$el instanceof \DOMElement) {
|
||||
continue;
|
||||
}
|
||||
$id = $el->getAttribute('id');
|
||||
if ($id === '') {
|
||||
continue;
|
||||
}
|
||||
if (!isset($ids[$id])) {
|
||||
$ids[$id] = [];
|
||||
}
|
||||
$ids[$id][] = $el;
|
||||
}
|
||||
|
||||
foreach ($ids as $id => $elementsWithId) {
|
||||
if (count($elementsWithId) > 1) {
|
||||
foreach ($elementsWithId as $el) {
|
||||
$violations[] = $this->createViolation(
|
||||
"Duplicate ID \"{$id}\" found. Element ID attribute must be unique across the document.",
|
||||
$el
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $violations;
|
||||
}
|
||||
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class IframeTitle extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_4_1_2_IFRAME_TITLE'; }
|
||||
public function getDescription(): string { return 'Iframe elements must have a non-empty title attribute to describe their content.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if (strtolower($element->tagName) !== 'iframe') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$element->hasAttribute('title') || trim($element->getAttribute('title')) === '') {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
"Iframe element is missing a descriptive title attribute.",
|
||||
"Add a descriptive title attribute, e.g., title=\"Embedded Map\" or title=\"Video Player\"."
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,11 @@ class StandardRuleSet
|
||||
new FieldsetLegend(),
|
||||
new HeadingOrder(),
|
||||
new MetaViewport(),
|
||||
new TabindexOrder(),
|
||||
new ColorContrast(),
|
||||
new DuplicateId(),
|
||||
new AriaRole(),
|
||||
new IframeTitle(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class TabindexOrder extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_2_4_3_TABINDEX'; }
|
||||
public function getDescription(): string { return 'Avoid positive tabindex values to maintain natural keyboard focus order.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::WARNING; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if ($element->hasAttribute('tabindex')) {
|
||||
$tabindex = $element->getAttribute('tabindex');
|
||||
if (is_numeric($tabindex)) {
|
||||
$val = (int)$tabindex;
|
||||
if ($val > 0) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
"Avoid positive tabindex values (tabindex=\"{$val}\") to preserve the natural tab order.",
|
||||
"Remove the positive tabindex or change it to \"0\" or \"-1\"."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user