From 1cd2d2632fa7fe68feec0a589b87ae8b59c11a6d Mon Sep 17 00:00:00 2001 From: abaicus Date: Tue, 6 Aug 2024 17:57:24 +0300 Subject: [PATCH] fix: translations workflow always requiring updates [closes Codeinwp/neve-pro-addon#2840] --- src/Modules/Translations.php | 129 +++++++++++++++++++++++++---------- 1 file changed, 92 insertions(+), 37 deletions(-) diff --git a/src/Modules/Translations.php b/src/Modules/Translations.php index be98bda..d4f9f57 100644 --- a/src/Modules/Translations.php +++ b/src/Modules/Translations.php @@ -24,7 +24,8 @@ */ class Translations extends Abstract_Module { - const API_URL = 'https://translations.themeisle.com/wp-json/gpb-themeisle/'; + const API_URL = 'https://translations.themeisle.com/wp-json/gpb-themeisle/'; + const CACHE_KEY = 'ti_translations_data'; /** * Check if we should load module for this. @@ -61,62 +62,66 @@ public function load( $product ) { $this->product = $product; add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'add_translations' ], 11 ); - - // Allow external downloads for this API. - add_filter( - 'http_request_host_is_external', - function ( $external, $host, $url ) { - if ( strpos( $url, self::API_URL ) === 0 ) { - return true; - } - return $external; - }, - 10, - 3 - ); + add_filter( 'http_request_host_is_external', [ $this, 'allow_translations_api' ], 10, 3 ); return $this; } + /** + * Allow external downloads for the translations API. + * + * @param bool $external Whether the host is external. + * @param string $host The host being checked. + * @param string $url The URL being checked. + * @return bool + */ + public function allow_translations_api( $external, $host, $url ) { + return strpos( $url, self::API_URL ) === 0 ? true : $external; + } + /** * Get translations from API. * - * @return mixed + * @return bool | array */ private function get_api_translations() { $translation_data = $this->get_translation_data(); - if ( empty( $translation_data ) ) { - return false; - } - return $translation_data; + return empty( $translation_data ) ? false : $translation_data; } /** * Get translation data from API. * - * @return mixed + * @return array */ private function get_translation_data() { + $cached = get_transient( self::CACHE_KEY ); + + if ( $cached ) { + return $cached; + } + $response = $this->safe_get( - sprintf( - '%stranslations', - self::API_URL - ), + self::API_URL . 'translations', array( 'timeout' => 15, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout, Inherited by wp_remote_get only, for vip environment we use defaults. 'sslverify' => false, ) ); if ( is_wp_error( $response ) || 200 != wp_remote_retrieve_response_code( $response ) ) { - return false; + return []; } + $data = json_decode( wp_remote_retrieve_body( $response ) ); + if ( ! is_array( $data ) ) { - return false; + return []; } - return (array) $data; + set_transient( self::CACHE_KEY, $data, 12 * HOUR_IN_SECONDS ); + + return $data; } /** @@ -127,15 +132,7 @@ private function get_translation_data() { * @return mixed */ public function add_translations( $_transient_data ) { - $locales = array_values( get_available_languages() ); - $locales = apply_filters( 'plugins_update_check_locales', $locales ); - $locales = array_unique( $locales ); - - // append translations via API call $translations = $this->get_api_translations(); - if ( is_wp_error( $translations ) ) { - return $_transient_data; - } if ( ! is_array( $translations ) ) { return $_transient_data; @@ -145,13 +142,71 @@ public function add_translations( $_transient_data ) { return $_transient_data; } + $installed_translations = wp_get_installed_translations( 'plugins' ); + foreach ( $translations as $translation ) { $translation = (array) $translation; - if ( isset( $translation['language'] ) && in_array( $translation['language'], $locales, true ) ) { - $_transient_data->translations[] = $translation; + + if ( ! $this->is_valid_translation( $translation ) ) { + continue; + } + + $latest_translation = strtotime( $translation['updated'] ); + + if ( ! is_int( $latest_translation ) ) { + continue; } + + $existing = (int) get_option( $this->get_translation_option_key( $translation ) ); + $has_translation = isset( $installed_translations[ $translation['slug'] ][ $translation['language'] ] ); + + // If we already have the latest translation, skip. + if ( $existing >= $latest_translation && $has_translation ) { + continue; + } + + $_transient_data->translations[] = $translation; + + update_option( $this->get_translation_option_key( $translation ), $latest_translation ); } return $_transient_data; } + + /** + * Get the option key for storing translations. + * + * @param array $translation the translation data from the API. + * + * @return string + */ + private function get_translation_option_key( $translation ) { + return $translation['slug'] . '_translation_' . $translation['language']; + } + + /** + * Check if a translation is valid and applies for the current site. + * + * @param array $translation The translation data. + * + * @return bool + */ + private function is_valid_translation( $translation ) { + $locales = apply_filters( 'plugins_update_check_locales', array_values( get_available_languages() ) ); + + if ( ! is_array( $locales ) ) { + return false; + } + $locales = array_unique( $locales ); + + if ( ! isset( $translation['language'], $translation['slug'], $translation['updated'] ) ) { + return false; + } + + if ( ! in_array( $translation['language'], $locales, true ) ) { + return false; + } + + return true; + } }