false, 'language' => 'en', 'agency_name' => '', 'logo_id' => 0, 'primary_color' => '#871a37', 'hide_footer' => false, 'hide_badge' => false, ]; } public static function init(): void { add_action( 'wp_ajax_awbseo_save_white_label', [ __CLASS__, 'ajax_save' ] ); add_filter( 'admin_body_class', [ __CLASS__, 'filter_body_class' ] ); add_action( 'admin_enqueue_scripts', [ __CLASS__, 'enqueue_arabic_font' ] ); // Auto-detect Arabic (via WPML, Polylang, or the site locale) and // keep the CitedSEO admin language in sync, unless the site owner // has turned auto-detection off in Settings. add_action( 'admin_init', [ __CLASS__, 'maybe_auto_switch_language' ], 5 ); } /** * Loads Cairo from Google Fonts -- the same typeface used everywhere * else in CitedSEO (general admin RTL layout, meta box, and the PRO * white-label client reports), so Arabic text renders in one consistent * font across the whole plugin instead of switching typefaces between * screens. * * Loaded when the admin language is set to Arabic, and unconditionally * on the post editor screens (post.php / post-new.php) so the CitedSEO * meta box always renders Arabic post content (title, description, * SERP preview) in Cairo even when the editor's own interface language * is English -- editing Arabic client content from an English admin UI * is the common case for agency use. */ public static function enqueue_arabic_font( $hook = '' ): void { $on_editor_screen = in_array( $hook, [ 'post.php', 'post-new.php' ], true ); if ( ! self::is_arabic() && ! $on_editor_screen ) { return; } wp_enqueue_style( 'awbseo-cairo-arabic', 'https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700&display=swap', [], null ); } /** Return the full white-label config, merged with defaults. */ public static function get(): array { return wp_parse_args( (array) get_option( self::OPTION_KEY, [] ), self::defaults() ); } public static function is_enabled(): bool { return (bool) self::get()['enabled']; } /** * True when the admin language is set to Arabic. * Reads from the general settings so it works independently of white-label. * Falls back to the white-label option for backward compatibility. */ public static function is_arabic(): bool { $s = get_option( 'awbseo_settings', [] ); if ( isset( $s['admin_language'] ) ) { return $s['admin_language'] === 'ar'; } // Backward compat: check white-label option. $wl = self::get(); return $wl['enabled'] && ( $wl['language'] ?? 'en' ) === 'ar'; } /** * The name to show wherever the plugin references itself. * Returns the agency name when white-label is active, 'CitedSEO' otherwise. */ public static function brand_name(): string { $wl = self::get(); if ( $wl['enabled'] && trim( $wl['agency_name'] ) !== '' ) { return sanitize_text_field( $wl['agency_name'] ); } return 'CitedSEO'; } /** * URL of the logo to use in the admin. * Falls back to the CitedSEO icon when white-label is off or no logo set. */ public static function logo_url(): string { $wl = self::get(); if ( $wl['enabled'] && absint( $wl['logo_id'] ) ) { $src = wp_get_attachment_image_url( absint( $wl['logo_id'] ), 'thumbnail' ); if ( $src ) { return $src; } } return AWBSEO_PLUGIN_URL . 'admin/assets/icon-256x256.png'; } /** * The primary brand color (hex) to inject as a CSS variable. */ public static function primary_color(): string { $wl = self::get(); $color = $wl['enabled'] && $wl['primary_color'] ? $wl['primary_color'] : '#871a37'; // Sanitize: must be 3- or 6-digit hex. if ( preg_match( '/^#[0-9a-fA-F]{3}$|^#[0-9a-fA-F]{6}$/', $color ) ) { return $color; } return '#871a37'; } /** * CSS injected on every CitedSEO admin page. * White-label section: logo + accent color (requires white-label enabled). * RTL section: Arabic layout (independent - only requires admin_language = ar). */ public static function inline_css(): string { $css = ''; // White-label branding CSS (logo, colors). if ( self::is_enabled() ) { $logo = esc_url( self::logo_url() ); $color = esc_attr( self::primary_color() ); $dark = self::darken_hex( $color, 20 ); $css .= " :root { --awb-primary: {$color}; --awb-primary-dark: {$dark}; } #toplevel_page_awbseo .wp-menu-image img { content: url('{$logo}') !important; } .awb-page-header__logo { content: url('{$logo}') !important; } "; } // end is_enabled() // Arabic RTL - independent of white-label; only requires admin_language = ar. if ( self::is_arabic() ) { $css .= " /* ---- Arabic / RTL overrides ---- */ /* Force Cairo on everything inside CitedSEO admin pages -- matches the font used across the rest of the admin (admin-rtl.css) and in the PRO white-label client reports, so switching to Arabic never changes the typeface mid-plugin. */ body.awbseo-lang-ar, body.awbseo-lang-ar *, body.awbseo-lang-ar input, body.awbseo-lang-ar textarea, body.awbseo-lang-ar select, body.awbseo-lang-ar button { font-family: 'Cairo', 'Segoe UI', Tahoma, Arial, sans-serif !important; } .awb-seo-wrap { direction: rtl; text-align: right; font-family: 'Cairo', 'Segoe UI', Tahoma, Arial, sans-serif !important; } .awb-seo-wrap .awb-page-header, .awb-seo-wrap h1, .awb-seo-wrap h2, .awb-seo-wrap h3, .awb-seo-wrap p, .awb-seo-wrap label { text-align: right; direction: rtl; } .awb-seo-wrap .form-table th { text-align: right; padding-right: 0; padding-left: 20px; } .awb-seo-wrap .form-table td { text-align: right; } .awb-seo-wrap input[type=text], .awb-seo-wrap input[type=email], .awb-seo-wrap input[type=url], .awb-seo-wrap input[type=number], .awb-seo-wrap textarea, .awb-seo-wrap select { direction: rtl; text-align: right; } .awb-seo-wrap .notice, .awb-seo-wrap .description { text-align: right; direction: rtl; } .awb-seo-wrap table { direction: rtl; } .awb-seo-wrap table th, .awb-seo-wrap table td { text-align: right; } /* Flip flex rows that use gap/align */ .awb-seo-wrap [style*='display:flex'], .awb-seo-wrap [style*='display: flex'] { direction: rtl; } /* Keep CitedSEO brand name left-aligned in sidebar (it's an English brand name) */ #toplevel_page_awbseo .wp-menu-name { direction: ltr; text-align: left; } "; } return $css; } // ---- Body class --------------------------------------------------------------- /** * Adds awbseo-lang-ar to when Arabic mode is active. * Lets external CSS and JS detect the language without reading PHP options. */ public static function filter_body_class( string $classes ): string { if ( self::is_arabic() ) { $classes .= ' awbseo-lang-ar'; } return $classes; } // ---- Auto language detection --------------------------------------------------- /** * Detect an active translation/multilingual plugin. * Returns the plugin's display name, or '' when none is detected. */ public static function detect_translation_plugin(): string { if ( defined( 'ICL_SITEPRESS_VERSION' ) || function_exists( 'icl_get_languages' ) ) { return 'WPML'; } if ( defined( 'POLYLANG_VERSION' ) || function_exists( 'pll_default_language' ) ) { return 'Polylang'; } return ''; } /** * Detect the site's primary/default language as a two-letter code. * Prefers WPML or Polylang's configured default language (so a * multilingual site with a non-Arabic admin locale but an Arabic * default front-end language is still detected correctly), and * falls back to the WordPress site locale. */ public static function detect_site_language(): string { if ( function_exists( 'icl_get_languages' ) ) { $default = apply_filters( 'wpml_default_language', null ); if ( $default ) { return strtolower( substr( $default, 0, 2 ) ); } } if ( function_exists( 'pll_default_language' ) ) { $default = pll_default_language( 'slug' ); if ( $default ) { return strtolower( substr( $default, 0, 2 ) ); } } return strtolower( substr( get_locale(), 0, 2 ) ); } /** * Detects the language of ONE SPECIFIC POST via Polylang or WPML, if * either is active. Independent of detect_site_language() above, which * only ever reports the site's overall default language -- this looks * at the post actually being edited, so an English-default multilingual * site with an Arabic translation of a page is still detected * correctly for that one post. * * Returns a two-letter language code (e.g. 'ar', 'en') or '' when no * supported multilingual plugin is active, or the post has no language * assigned yet. */ public static function get_post_language( int $post_id ): string { if ( function_exists( 'pll_get_post_language' ) ) { $lang = pll_get_post_language( $post_id, 'slug' ); if ( $lang ) { return strtolower( substr( $lang, 0, 2 ) ); } } if ( has_filter( 'wpml_post_language_details' ) ) { $details = apply_filters( 'wpml_post_language_details', null, $post_id ); if ( is_array( $details ) && ! empty( $details['language_code'] ) ) { return strtolower( substr( $details['language_code'], 0, 2 ) ); } } return ''; } /** * True when Polylang or WPML is active AND this specific post's * language is Arabic. Used to switch the meta box's own UI chrome * (labels, hints, buttons -- not the post's actual title/description * content, which is only ever whatever the editor typed) into Arabic * for that one post, regardless of the site's default language or the * plugin's own admin-interface language setting (is_arabic() above). */ public static function is_post_arabic( int $post_id ): bool { return 'ar' === self::get_post_language( $post_id ); } /** * Keeps the CitedSEO admin language in sync with the site's detected * language whenever auto-detection is on (the default). Runs on every * admin_init so it reacts the moment WPML/Polylang is installed, its * default language is changed, or the site locale changes -- without * requiring the site owner to find the Settings toggle themselves. * * A site owner who explicitly turns "Auto-detect Language" off in * Settings is left alone; their manual choice is never overwritten. */ public static function maybe_auto_switch_language(): void { $settings = get_option( 'awbseo_settings', [] ); // Missing key = pre-existing install from before this feature shipped; // default it to "on" so everyone benefits without a manual opt-in. if ( empty( $settings['admin_language_auto'] ) && isset( $settings['admin_language_auto'] ) ) { return; } $target = ( 'ar' === self::detect_site_language() ) ? 'ar' : 'en'; if ( ( $settings['admin_language'] ?? 'en' ) !== $target ) { $settings['admin_language'] = $target; update_option( 'awbseo_settings', $settings ); } } // ---- Locale filter ------------------------------------------------------------ /** * Forces the plugin text domain to load Arabic translations when the * white-label language is set to 'ar', regardless of the site locale. * * Registered below (outside the class) so it fires before load_plugin_textdomain(). */ public static function filter_locale( string $locale, string $domain ): string { if ( $domain === 'adworksbid-seo' && self::is_arabic() ) { return 'ar'; } return $locale; } // ---- Hex helpers -------------------------------------------------------------- /** * Very lightweight hex darkening: subtract $amount from each R/G/B channel. */ private static function darken_hex( string $hex, int $amount ): string { $hex = ltrim( $hex, '#' ); if ( strlen( $hex ) === 3 ) { $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; } $r = max( 0, hexdec( substr( $hex, 0, 2 ) ) - $amount ); $g = max( 0, hexdec( substr( $hex, 2, 2 ) ) - $amount ); $b = max( 0, hexdec( substr( $hex, 4, 2 ) ) - $amount ); return sprintf( '#%02x%02x%02x', $r, $g, $b ); } // ---- AJAX ------------------------------------------------------------------ public static function ajax_save(): void { check_ajax_referer( 'awbseo_admin', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( 'Permission denied.' ); } $allowed_langs = [ 'en', 'ar' ]; $data = [ 'enabled' => ! empty( $_POST['enabled'] ), 'language' => in_array( $_POST['language'] ?? 'en', $allowed_langs, true ) ? sanitize_key( $_POST['language'] ) : 'en', 'agency_name' => sanitize_text_field( $_POST['agency_name'] ?? '' ), 'logo_id' => absint( $_POST['logo_id'] ?? 0 ), 'primary_color' => sanitize_hex_color( $_POST['primary_color'] ?? '#871a37' ) ?: '#871a37', 'hide_footer' => ! empty( $_POST['hide_footer'] ), 'hide_badge' => ! empty( $_POST['hide_badge'] ), ]; update_option( self::OPTION_KEY, $data ); wp_send_json_success( $data ); } } // Register locale filter early -- must fire before load_plugin_textdomain(). // Running here (on file include, before any action hooks) guarantees timing. add_filter( 'plugin_locale', [ 'AWBSEO_White_Label', 'filter_locale' ], 1, 2 );