is_plugin_active() ) { return []; } // Get languages with full details to extract locales $languages = pll_languages_list( [ 'fields' => '' ] ); // Get full language objects if ( ! is_array( $languages ) ) { return []; } $locales = []; foreach ( $languages as $language ) { // Use locale if available, fallback to slug if ( isset( $language->locale ) && ! empty( $language->locale ) ) { $locales[] = $language->locale; } elseif ( isset( $language->slug ) ) { $locales[] = $language->slug; } } return $locales; } /** * Get the default language code * * @return string|null Default language code or null if not set */ public function get_default_language(): ?string { if ( ! $this->is_plugin_active() ) { return null; } $default_slug = pll_default_language(); if ( ! $default_slug ) { return null; } // Get the full locale for the default language $languages = pll_languages_list( [ 'fields' => '' ] ); if ( is_array( $languages ) ) { foreach ( $languages as $language ) { if ( isset( $language->slug ) && $language->slug === $default_slug ) { // Return locale if available, fallback to slug if ( isset( $language->locale ) && ! empty( $language->locale ) ) { return $language->locale; } return $language->slug; } } } // Fallback to the slug if no locale is found return $default_slug; } /** * Get the language code for a specific product * * @param int $product_id Product ID * @return string|null Language code or null if not found */ public function get_product_language( int $product_id ): ?string { if ( ! $this->is_plugin_active() ) { return null; } // Use Polylang function to get product language slug $language = pll_get_post_language( $product_id ); return false !== $language ? $language : null; } /** * Switch to a specific language context * * @param string $locale Full locale code (e.g., 'es_ES', 'zh_CN') * @return string|null The previous language slug if successful, null otherwise */ public function switch_to_language( string $locale ): ?string { if ( ! $this->is_plugin_active() ) { return null; } $polylang = PLL(); if ( ! $polylang || ! method_exists( $polylang, 'curlang' ) ) { return null; } // Store original language $original_lang = $polylang->curlang ? $polylang->curlang->slug : null; // Get Polylang language slug for the locale $polylang_slug = $this->get_polylang_slug_for_locale( $locale ); if ( ! $polylang_slug ) { return null; } // Get the language object $pll_language = null; foreach ( $polylang->model->get_languages_list() as $lang ) { if ( $lang->slug === $polylang_slug ) { $pll_language = $lang; break; } } if ( $pll_language ) { $polylang->curlang = $pll_language; return $original_lang; } return null; } /** * Restore a previous language context * * @param string $language_slug The language slug to restore * @return void */ public function restore_language( string $language_slug ): void { if ( ! $this->is_plugin_active() ) { return; } $polylang = PLL(); if ( ! $polylang || ! method_exists( $polylang, 'curlang' ) ) { return; } // Find the language object for this slug foreach ( $polylang->model->get_languages_list() as $lang ) { if ( $lang->slug === $language_slug ) { $polylang->curlang = $lang; break; } } } /** * Check if Polylang Pro features are available * * @return bool True if Polylang Pro is active */ public function is_pro_version(): bool { return defined( 'POLYLANG_PRO' ) && POLYLANG_PRO; } /** * Get the plugin-specific language identifier for a given locale * * Converts a full locale (e.g., 'es_ES') to Polylang's language slug. * * @param string $locale Full locale code (e.g., 'es_ES', 'fr_FR') * @return string|null Polylang language slug or null if not found */ protected function get_plugin_language_identifier( string $locale ): ?string { return $this->get_polylang_slug_for_locale( $locale ); } /** * Check if a product is in a specific language * * @param int $product_id Product ID to check * @param string $language_identifier Polylang language slug * @return bool True if product is in the specified language */ protected function is_product_in_language( int $product_id, string $language_identifier ): bool { $product_language = pll_get_post_language( $product_id ); return $product_language && $product_language === $language_identifier; } /** * Get products from the default language * * Uses Polylang's API to find products that are in the default language. * This ensures we're working with the original products, not translations. * * @param int $limit Maximum number of products to return (-1 for all products, matching legacy feed behavior) * @param int $offset Offset for pagination * @return array Array of product IDs from the default language */ public function get_products_from_default_language( int $limit = 10, int $offset = 0 ): array { // Use parent implementation with template method pattern return parent::get_products_from_default_language( $limit, $offset ); } /** * Get detailed translation information for a product * * Uses Polylang's API to get comprehensive translation data including * which fields are translated and translation status. * * @param int $product_id Product ID (should be from default language) * @return array Detailed translation information */ public function get_product_translation_details( int $product_id ): array { if ( ! $this->is_plugin_active() ) { return []; } $details = [ 'product_id' => $product_id, 'default_language' => $this->get_default_language(), 'translations' => [], 'translation_status' => [], ]; // Get the mapping between full locales and Polylang language slugs $polylang_languages = pll_languages_list( [ 'fields' => '' ] ); if ( ! is_array( $polylang_languages ) ) { return $details; } $locale_to_slug_map = []; $slug_to_locale_map = []; foreach ( $polylang_languages as $language ) { $locale = $language->locale ?? $language->slug; $locale_to_slug_map[ $locale ] = $language->slug; $slug_to_locale_map[ $language->slug ] = $locale; } $languages = $this->get_available_languages(); // This now returns full locales $default_language = $this->get_default_language(); // This now returns full locale foreach ( $languages as $full_locale ) { // Skip the default language if ( $full_locale === $default_language ) { continue; } // Get the Polylang language slug for this locale $polylang_slug = $locale_to_slug_map[ $full_locale ] ?? $full_locale; // Get translated product ID using Polylang function $translated_id = pll_get_post( $product_id, $polylang_slug ); if ( $translated_id && $translated_id !== $product_id ) { // Store using the full locale as the key $details['translations'][ $full_locale ] = $translated_id; // Polylang doesn't have built-in translation status like WPML // We'll mark as 'complete' if translation exists $details['translation_status'][ $full_locale ] = 'complete'; // Get which fields are translated $details['translated_fields'][ $full_locale ] = $this->get_translated_fields( $product_id, $translated_id, $full_locale ); } } return $details; } /** * Create a product translation in Polylang * * Creates a translated version of an existing product using Polylang API. * This method follows the confirmed working approach from debug testing. * * @param int $original_product_id The ID of the original product * @param string $target_language The target language code (locale format) * @param array $translated_data Array of translated content fields * @return int|null The ID of the created translated product, or null on failure */ public function create_product_translation( int $original_product_id, string $target_language, array $translated_data ): ?int { if ( ! $this->is_plugin_active() ) { return null; } // Check if required Polylang functions are available if ( ! function_exists( 'pll_set_post_language' ) || ! function_exists( 'pll_save_post_translations' ) ) { return null; } try { // Get the original product $original_product = wc_get_product( $original_product_id ); if ( ! $original_product ) { return null; } // Get Polylang language slug for target language $target_slug = $this->get_polylang_slug_for_locale( $target_language ); if ( ! $target_slug ) { return null; } // Create the translated product $translated_product = new \WC_Product_Simple(); // Set basic product data from original $translated_product->set_regular_price( $original_product->get_regular_price() ); $translated_product->set_sale_price( $original_product->get_sale_price() ); $translated_product->set_sku( $original_product->get_sku() . '_' . $target_slug ); $translated_product->set_status( 'publish' ); $translated_product->set_catalog_visibility( 'visible' ); // Set translated content if ( isset( $translated_data['name'] ) ) { $translated_product->set_name( $translated_data['name'] ); } else { $translated_product->set_name( $original_product->get_name() . ' (' . $target_language . ')' ); } if ( isset( $translated_data['description'] ) ) { $translated_product->set_description( $translated_data['description'] ); } if ( isset( $translated_data['short_description'] ) ) { $translated_product->set_short_description( $translated_data['short_description'] ); } // Save the translated product $translated_product_id = $translated_product->save(); if ( ! $translated_product_id ) { return null; } // Set languages for both products $default_language = $this->get_default_language(); $default_slug = $this->get_polylang_slug_for_locale( $default_language ); // Set language assignments if ( $default_slug ) { pll_set_post_language( $original_product_id, $default_slug ); } pll_set_post_language( $translated_product_id, $target_slug ); // Create translation relationship $translations_array = [ $default_slug => $original_product_id, $target_slug => $translated_product_id, ]; pll_save_post_translations( $translations_array ); return $translated_product_id; } catch ( \Exception $e ) { return null; } } /** * Get Polylang language slug for a given locale * * Maps full locale codes (e.g., 'en_US', 'es_ES') to Polylang language slugs (e.g., 'en', 'es') * * @param string $locale Full locale code * @return string|null Polylang language slug or null if not found */ private function get_polylang_slug_for_locale( string $locale ): ?string { if ( ! function_exists( 'pll_languages_list' ) ) { return null; } // In test environments, use simple mapping to avoid flag_code errors if ( defined( 'PHPUNIT_COMPOSER_INSTALL' ) ) { // Simple mapping based on locale format $locale_to_slug_map = [ 'en_US' => 'en', 'es_ES' => 'es', 'fr_FR' => 'fr', 'de_DE' => 'de', ]; if ( isset( $locale_to_slug_map[ $locale ] ) ) { return $locale_to_slug_map[ $locale ]; } // Fallback: extract language code from locale return substr( $locale, 0, 2 ); } // Production code: use Polylang API $polylang_languages = pll_languages_list( [ 'fields' => '' ] ); if ( ! is_array( $polylang_languages ) ) { return null; } foreach ( $polylang_languages as $language ) { $language_locale = $language->locale ?? $language->slug; if ( $language_locale === $locale ) { return $language->slug; } } // Fallback: try matching just the language part (e.g., 'en' from 'en_US') $language_code = substr( $locale, 0, 2 ); foreach ( $polylang_languages as $language ) { if ( $language->slug === $language_code ) { return $language->slug; } } return null; } /** * Get availability data for telemetry reporting * * Extends the base method to include Polylang-specific features. * * @return array Integration availability data */ public function get_availability_data(): array { $data = parent::get_availability_data(); if ( $this->is_plugin_active() ) { $data['features'] = [ 'is_pro_version' => $this->is_pro_version(), ]; $data['languages'] = $this->get_available_languages(); $data['default_language'] = $this->get_default_language(); } return $data; } /** * Get the integration status. * * Returns a status string indicating the current state of the integration: * - "Active" - Plugin is active and properly configured * - "Installed" - Plugin is installed but not active * - "Not Available" - Plugin is not installed * - "Misconfigured" - Plugin is active but missing required configuration * * Note: Polylang does not have a "legacy multi-language setup" concept like WPML, * so it cannot be "Ineligible". * * @return string Integration status * @since 3.6.0 */ public function get_integration_status(): string { // Check if plugin is installed if ( ! $this->is_plugin_installed() ) { return 'Not Available'; } // Check if plugin is active if ( ! $this->is_plugin_active() ) { return 'Installed'; } // Check if properly configured (has default language) if ( ! $this->is_available() ) { return 'Misconfigured'; } return 'Active'; } /** * Checks if this integration is eligible for language override feeds. * * Polylang is always eligible (no legacy multi-language setup like WPML). * * @return bool Always true for Polylang * @since 3.6.0 */ public function is_eligible_for_language_override_feeds(): bool { return true; } }