> */ public static function admin_kses(): array { return [ 'div' => ['class' => true, 'id' => true, 'style' => true, 'lang' => true, 'dir' => true, 'role' => true, 'aria-hidden' => true], 'span' => ['class' => true, 'style' => true, 'aria-hidden' => true, 'aria-label' => true], 'a' => ['href' => true, 'class' => true, 'style' => true, 'target' => true, 'rel' => true, 'title' => true, 'aria-label' => true], 'button' => ['type' => true, 'class' => true, 'style' => true, 'title' => true, 'aria-label' => true, 'disabled' => true], 'strong' => ['class' => true, 'style' => true], 'small' => ['class' => true, 'style' => true], 'em' => ['class' => true, 'style' => true], 'code' => ['class' => true, 'style' => true], 'p' => ['class' => true, 'id' => true, 'style' => true], 'h1' => ['class' => true, 'id' => true, 'style' => true], 'h2' => ['class' => true, 'id' => true, 'style' => true], 'h3' => ['class' => true, 'id' => true, 'style' => true], 'br' => [], 'bdi' => ['dir' => true, 'class' => true, 'style' => true], 'img' => ['src' => true, 'alt' => true, 'class' => true, 'style' => true, 'width' => true, 'height' => true], 'svg' => ['xmlns' => true, 'width' => true, 'height' => true, 'viewbox' => true, 'preserveaspectratio' => true, 'aria-hidden' => true, 'focusable' => true, 'class' => true, 'fill' => true, 'role' => true], 'g' => ['transform' => true, 'fill' => true], 'path' => ['fill' => true, 'd' => true, 'transform' => true], 'polyline' => ['fill' => true, 'stroke' => true, 'stroke-width' => true, 'points' => true], ]; } public static function header(string $page_title = ''): string { // v3.3.4 — switched from (which broke when data_uri failed and // the URL fallback hit the wrong path on some hosts) to an inline // SVG logo. Always renders, no asset path dependency, no broken alt // text overlay. The inline_icon() method already produces a valid // eagle silhouette; we just scale it up for the header. $logo_html = ''; $name_ar = defined('AETOS_SEO_BRAND_NAME_AR') ? (string) AETOS_SEO_BRAND_NAME_AR : 'Aetos SEO'; $name_en = defined('AETOS_SEO_BRAND_NAME') ? (string) AETOS_SEO_BRAND_NAME : 'Aetos SEO'; $is_rtl = I18n::is_arabic(); $primary = $is_rtl ? $name_ar : $name_en; $tagline = $is_rtl ? self::TAGLINE_AR : self::TAGLINE_EN; $title_html = $page_title !== '' ? '' . esc_html($page_title) . '' : ''; // v3.3.4 — contact info (site / email / whatsapp) was repeating on // every Aetos admin page header. PASS audit flagged it as P1-4. // Moved to a one-time block on the License + Settings pages instead. // Header now stays clean: logo + brand name + tagline + page badge. // v3.3.13 — quick-access actions on the right side of every Aetos // page header. The Help link → docs is always present. The free build // ships only the Help link; the Pro-only feedback button is stripped. $help_label = $is_rtl ? 'المساعدة' : 'Help'; $actions_html = '
' . '' . '' . esc_html($help_label) . ''; $actions_html .= '
'; return '
' . $logo_html . '
' . '
' . esc_html($primary) . ' ' . $title_html . '
' . '
' . esc_html($tagline) . '
' . '
' . $actions_html . '
'; } public static function wrap_open(string $extra_classes = ''): string { $lang = I18n::lang(); $dir = I18n::is_arabic() ? 'rtl' : 'ltr'; $classes = trim('wrap aetos-seo ' . $extra_classes); return '
'; } // (A dead v3.3.4 contact-links helper was removed here 2026-06-12 — zero // callers in either edition; see CHANGELOG.) public static function inline_icon(int $size = 20): string { // v3.3.9 — replaced 5-polygon approximation with the production // eagle paths from /assets/brand/logo.svg (the same vector the // brand identity uses on aetosseo.com and the marketing site). // PASS 9 audit flagged the polygon approximation as "looks like // a green triangle"; this is the real eagle. $s = esc_attr((string) $size); return ''; } private static function plugin_root(): string { // Resolve the plugin root from the WordPress-provided plugin path // (AETOS_SEO_PATH = plugin_dir_path() in the main file) instead of // walking a fixed number of parent directories. if (defined('AETOS_SEO_PATH')) { return rtrim((string) AETOS_SEO_PATH, '/\\'); } if (function_exists('plugin_dir_path')) { return rtrim((string) plugin_dir_path(dirname(__DIR__)), '/\\'); } return ''; } private static function local_file(string $relative): string { $relative = ltrim(str_replace('\\', '/', $relative), '/'); $candidate = self::plugin_root() . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $relative); return is_readable($candidate) ? $candidate : ''; } private static function is_aetos_admin_page(): bool { $page_raw = filter_input(INPUT_GET, 'page', FILTER_UNSAFE_RAW); $page = is_string($page_raw) ? sanitize_key(wp_unslash($page_raw)) : ''; return $page !== '' && strpos($page, 'aetos-seo') === 0; } private static function image_data_uri(string $relative): string { $file = self::local_file($relative); if ($file === '' || !is_readable($file)) { return ''; } $bytes = @file_get_contents($file); if (!is_string($bytes) || $bytes === '') { return ''; } return 'data:' . self::mime_for_file($file) . ';base64,' . base64_encode($bytes); } private static function mime_for_file(string $file): string { $ext = strtolower((string) pathinfo($file, PATHINFO_EXTENSION)); if ($ext === 'svg') { return 'image/svg+xml'; } if ($ext === 'jpg' || $ext === 'jpeg') { return 'image/jpeg'; } if ($ext === 'gif') { return 'image/gif'; } if ($ext === 'webp') { return 'image/webp'; } return 'image/png'; } }