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
release: v3.1.0 premium smart core engines and 14 new accessibility rules
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class AriaLabelledbyExists extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_4_1_2_ARIA_LABELLEDBY'; }
|
||||
public function getDescription(): string { return 'aria-labelledby and aria-describedby attributes must refer to existing element IDs.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 3; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
$attrs = ['aria-labelledby', 'aria-describedby'];
|
||||
foreach ($attrs as $attr) {
|
||||
if ($element->hasAttribute($attr)) {
|
||||
$ids = preg_split('/\s+/', trim($element->getAttribute($attr)));
|
||||
if ($ids === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc = $element->ownerDocument;
|
||||
if (!$doc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($ids as $id) {
|
||||
if ($id === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$target = $doc->getElementById($id);
|
||||
if (!$target) {
|
||||
// XPath fallback in case DOMDocument getElementById fails (which is common without DTD/XML schemas)
|
||||
$xpath = new \DOMXPath($doc);
|
||||
$query = $xpath->query("//*[@id='" . addslashes($id) . "']");
|
||||
if ($query === false || $query->length === 0) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
"The attribute $attr refers to a non-existent ID: \"$id\".",
|
||||
"Ensure the element referenced by ID \"$id\" exists in the document, or correct the ID spelling."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class AutocompleteValid extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_1_3_5_AUTOCOMPLETE'; }
|
||||
public function getDescription(): string { return 'Input fields that collect common user information must have a valid autocomplete attribute.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::AA; }
|
||||
public function getSeverity(): Severity { return Severity::WARNING; }
|
||||
public function getLevel(): int { return 4; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if (strtolower($element->tagName) !== 'input') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = strtolower($element->getAttribute('type') ?: 'text');
|
||||
if (in_array($type, ['hidden', 'submit', 'button', 'reset', 'file', 'checkbox', 'radio', 'image'], true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$name = strtolower($element->getAttribute('name'));
|
||||
$id = strtolower($element->getAttribute('id'));
|
||||
|
||||
$personalFields = ['email', 'tel', 'phone', 'zip', 'postal', 'address', 'username', 'password', 'name', 'fname', 'lname', 'search'];
|
||||
$isPersonal = false;
|
||||
|
||||
if ($type === 'email' || $type === 'tel' || $type === 'password') {
|
||||
$isPersonal = true;
|
||||
} else {
|
||||
foreach ($personalFields as $field) {
|
||||
if (str_contains($name, $field) || str_contains($id, $field)) {
|
||||
$isPersonal = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($isPersonal && !$element->hasAttribute('autocomplete')) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
$this->getDescription(),
|
||||
'Add a valid autocomplete attribute (e.g., autocomplete="email" or autocomplete="current-password").'
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class AutofocusBanned extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_2_4_3_AUTOFOCUS'; }
|
||||
public function getDescription(): string { return 'Form controls should not use the autofocus attribute as it disorients screen reader and keyboard users.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::WARNING; }
|
||||
public function getLevel(): int { return 3; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if ($element->hasAttribute('autofocus')) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
$this->getDescription(),
|
||||
'Remove the autofocus attribute and let the user navigate to the form field naturally, or use script-based focus with care.'
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use YakNet\AccessibilityConsole\Core\AbstractRule;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class BannedSelectors extends AbstractRule
|
||||
{
|
||||
/** @var array<string, string> */
|
||||
private array $bannedSelectors = [
|
||||
'center' => 'Obsolete <center> tag. Use CSS text-align instead.',
|
||||
'blink' => 'Obsolete <blink> tag. Avoid blinking/flashing content.',
|
||||
'marquee' => 'Obsolete <marquee> tag. Avoid moving content.',
|
||||
'font' => 'Obsolete <font> tag. Use CSS font styling instead.',
|
||||
'*[align]' => 'Deprecated align attribute. Use CSS text-align or float instead.',
|
||||
'*[bgcolor]' => 'Deprecated bgcolor attribute. Use CSS background-color instead.',
|
||||
];
|
||||
|
||||
public function check(\DOMDocument $doc): array
|
||||
{
|
||||
$violations = [];
|
||||
$crawler = new Crawler($doc);
|
||||
|
||||
foreach ($this->bannedSelectors as $selector => $message) {
|
||||
try {
|
||||
$nodes = $crawler->filter($selector);
|
||||
foreach ($nodes as $node) {
|
||||
if ($node instanceof \DOMElement) {
|
||||
$violations[] = $this->createViolation(
|
||||
sprintf('[Banned Selector: "%s"] %s', $selector, $message),
|
||||
$node
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Skip invalid selector configurations
|
||||
}
|
||||
}
|
||||
|
||||
return $violations;
|
||||
}
|
||||
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 2; }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class BlinkTagBanned extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_2_2_2_BLINK'; }
|
||||
public function getDescription(): string { return 'The <blink> tag is obsolete and should not be used as it causes text to flash/blink.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 1; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if (strtolower($element->tagName) === 'blink') {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
$this->getDescription(),
|
||||
'Remove the <blink> tag and use CSS styling/animations if needed, or preferably avoid blinking content.'
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class DuplicateH1 extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_1_3_1_DUPLICATE_H1'; }
|
||||
public function getDescription(): string { return 'Pages should not contain more than one h1 element.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::WARNING; }
|
||||
public function getLevel(): int { return 2; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if (strtolower($element->tagName) !== 'h1') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$doc = $element->ownerDocument;
|
||||
if ($doc) {
|
||||
$h1s = $doc->getElementsByTagName('h1');
|
||||
if ($h1s->length > 1 && $h1s->item(0) !== $element) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
$this->getDescription(),
|
||||
'Change this heading to an h2 or lower level heading, keeping only one h1 per page.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class FormSubmitButton extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_3_2_2_FORM_SUBMIT'; }
|
||||
public function getDescription(): string { return 'Every form must contain a submit button to enable keyboard submission.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 3; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if (strtolower($element->tagName) !== 'form') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$hasSubmit = false;
|
||||
|
||||
$buttons = $element->getElementsByTagName('button');
|
||||
foreach ($buttons as $btn) {
|
||||
$type = strtolower($btn->getAttribute('type') ?: 'submit');
|
||||
if ($type === 'submit') {
|
||||
$hasSubmit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasSubmit) {
|
||||
$inputs = $element->getElementsByTagName('input');
|
||||
foreach ($inputs as $input) {
|
||||
$type = strtolower($input->getAttribute('type'));
|
||||
if ($type === 'submit' || $type === 'image') {
|
||||
$hasSubmit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasSubmit) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
$this->getDescription(),
|
||||
'Add a submit control, such as a <button type="submit"> or <input type="submit"> inside the form.'
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class ImageAltLong extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_1_1_1_ALT_LONG'; }
|
||||
public function getDescription(): string { return 'Image alt text should be concise (less than 150 characters).'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::WARNING; }
|
||||
public function getLevel(): int { return 3; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if (strtolower($element->tagName) !== 'img') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$element->hasAttribute('alt')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$alt = trim($element->getAttribute('alt'));
|
||||
if (mb_strlen($alt) > 150) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
$this->getDescription(),
|
||||
'Shorten the alt text description to be under 150 characters, and move longer explanations to standard page content.'
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class ImageAltPlaceholder extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_1_1_1_ALT_PLACEHOLDER'; }
|
||||
public function getDescription(): string { return 'Image alt text should not be a generic placeholder or filename.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 2; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if (strtolower($element->tagName) !== 'img') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$element->hasAttribute('alt')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$alt = trim($element->getAttribute('alt'));
|
||||
if ($alt === '') {
|
||||
return null; // Empty alt is allowed for decorative images (WCAG 1.1.1)
|
||||
}
|
||||
|
||||
$cleanAlt = strtolower($alt);
|
||||
|
||||
// Generic placeholder checks
|
||||
$placeholders = [
|
||||
'image', 'photo', 'picture', 'logo', 'icon', 'placeholder', 'untitled', 'temp', 'blank', 'pic', 'dot',
|
||||
'resim', 'foto', 'fotoğraf', 'görsel', 'grafik', 'ikon', 'logo'
|
||||
];
|
||||
if (in_array($cleanAlt, $placeholders, true)) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
$this->getDescription(),
|
||||
'Provide a specific description of what the image shows, rather than using a generic word.'
|
||||
);
|
||||
}
|
||||
|
||||
// Filename checks
|
||||
if (preg_match('/\.(png|jpg|jpeg|gif|svg|webp|bmp)$/i', $cleanAlt)) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
'Image alt text should not be a filename.',
|
||||
'Provide a descriptive alternative text instead of the image filename.'
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class LinkTextUrl extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_2_4_4_LINK_URL'; }
|
||||
public function getDescription(): string { return 'Link text should not be a raw URL.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::WARNING; }
|
||||
public function getLevel(): int { return 3; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if (strtolower($element->tagName) !== 'a') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$text = trim($element->textContent);
|
||||
if (preg_match('/^(https?:\/\/|www\.)[^\s]+$/i', $text)) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
$this->getDescription(),
|
||||
'Replace the raw URL link text with a descriptive label explaining where the link goes.'
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class ListStructure extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_1_3_1_LIST_STRUCTURE'; }
|
||||
public function getDescription(): string { return 'Lists (ul, ol) must only contain list item elements (li) as direct children.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 2; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
$tag = strtolower($element->tagName);
|
||||
if ($tag !== 'ul' && $tag !== 'ol') {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($element->childNodes as $child) {
|
||||
if ($child instanceof DOMElement) {
|
||||
$childTag = strtolower($child->tagName);
|
||||
if (!in_array($childTag, ['li', 'script', 'template'], true)) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
sprintf('List element <%s> contains invalid direct child <%s>. Only <li> elements are allowed.', $tag, $childTag),
|
||||
'Wrap the child element in an <li> tag, or move it outside the list structure.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class PresentationTagsBanned extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_1_3_1_PRESENTATION_TAGS'; }
|
||||
public function getDescription(): string { return 'Obsolete presentation tags (<center>, <strike>, <basefont>, <tt>, <big>) should not be used.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 2; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
$bannedTags = ['center', 'strike', 'basefont', 'tt', 'big'];
|
||||
$tag = strtolower($element->tagName);
|
||||
if (in_array($tag, $bannedTags, true)) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
sprintf('The <%s> presentation tag is obsolete and should not be used.', $tag),
|
||||
sprintf('Remove the <%s> tag and use modern CSS styling instead.', $tag)
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -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 ScrollableRegionFocusable extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_2_1_1_SCROLLABLE_FOCUS'; }
|
||||
public function getDescription(): string { return 'Scrollable regions must be focusable via keyboard using tabindex="0".'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 3; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
$style = $element->getAttribute('style');
|
||||
if ($style && preg_match('/overflow(-x|-y)?\s*:\s*(scroll|auto)/i', $style)) {
|
||||
if (!$element->hasAttribute('tabindex') || (int)$element->getAttribute('tabindex') < 0) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
$this->getDescription(),
|
||||
'Add tabindex="0" to the scrollable element so keyboard users can navigate to and scroll it.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class SvgAltText extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_1_1_1_SVG'; }
|
||||
public function getDescription(): string { return 'SVG elements must have alternative text or be hidden from screen readers.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 2; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if (strtolower($element->tagName) !== 'svg') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($element->getAttribute('aria-hidden') === 'true') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($element->hasAttribute('aria-label') || $element->hasAttribute('aria-labelledby')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$titles = $element->getElementsByTagName('title');
|
||||
if ($titles->length > 0) {
|
||||
$titleNode = $titles->item(0);
|
||||
if ($titleNode !== null) {
|
||||
$titleText = trim($titleNode->textContent);
|
||||
if ($titleText !== '') {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
$this->getDescription(),
|
||||
'Add an aria-label, aria-labelledby, a <title> element inside the SVG, or set aria-hidden="true" if it is decorative.'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 TableSummaryBanned extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_1_3_1_TABLE_SUMMARY'; }
|
||||
public function getDescription(): string { return 'The summary attribute on table elements is obsolete and should not be used.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::WARNING; }
|
||||
public function getLevel(): int { return 2; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if (strtolower($element->tagName) !== 'table') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($element->hasAttribute('summary')) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
$this->getDescription(),
|
||||
'Remove the summary attribute. Use the <caption> element or aria-describedby instead.'
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user