2022/CVE-2022-0412/vultarget/ti-woocommerce-wishlist/includes/api/yoasti18n/i18n-module.php
<?php
/**
 * This class defines a promo box and checks your translation site's API for stats about it, then shows them to the user.
 *
 * @since             1.4.0
 * @package           TInvWishlist\API
 */

// If this file is called directly, abort.
if ( ! defined( 'ABSPATH' ) ) {
	die;
}

/**
 * This class defines a promo box and checks your translation site's API for stats about it, then shows them to the user.
 */
class TInvWL_Yoast_I18n_V2 {

	/**
	 * Your translation site's logo
	 *
	 * @var string
	 */
	private $glotpress_logo;

	/**
	 * Your translation site's name
	 *
	 * @var string
	 */
	private $glotpress_name;

	/**
	 * Your translation site's URL
	 *
	 * @var string
	 */
	private $glotpress_url;

	/**
	 * The URL to actually do the API request to
	 *
	 * @var string
	 */
	private $api_url;

	/**
	 * Hook where you want to show the promo box
	 *
	 * @var string
	 */
	private $hook;

	/**
	 * Will contain the site's locale
	 *
	 * @access private
	 * @var string
	 */
	private $locale;

	/**
	 * Will contain the locale's name, obtained from your translation site
	 *
	 * @access private
	 * @var string
	 */
	private $locale_name;

	/**
	 * Will contain the percentage translated for the plugin translation project in the locale
	 *
	 * @access private
	 * @var int
	 */
	private $percent_translated;

	/**
	 * Name of your plugin
	 *
	 * @var string
	 */
	private $plugin_name;

	/**
	 * Project slug for the project on your translation site
	 *
	 * @var string
	 */
	private $project_slug;

	/**
	 * URL to point to for registration links
	 *
	 * @var string
	 */
	private $register_url;

	/**
	 * Indicates whether there's a translation available at all.
	 *
	 * @access private
	 * @var bool
	 */
	private $translation_exists;

	/**
	 * Indicates whether the translation's loaded.
	 *
	 * @access private
	 * @var bool
	 */
	private $translation_loaded;

	/**
	 * Class constructor
	 *
	 * @param array $args Contains the settings for the class.
	 */
	public function __construct( $args ) {
		if ( ! is_admin() ) {
			return;
		}

		$this->locale = $this->get_admin_locale();
		if ( 'en_US' === $this->locale ) {
			return;
		}

		$this->init( $args );

		if ( ! $this->hide_promo() ) {
			add_action( $this->hook, array( $this, 'promo' ) );
		}
	}

	/**
	 * Returns the locale used in the admin.
	 *
	 * WordPress 4.7 introduced the ability for users to specify an Admin language
	 * different from the language used on the front end. This checks if the feature
	 * is available and returns the user's language, with a fallback to the site's language.
	 * Can be removed when support for WordPress 4.6 will be dropped, in favor
	 * of WordPress get_user_locale() that already fallbacks to the site’s locale.
	 *
	 * @returns string The locale.
	 */
	private function get_admin_locale() {
		if ( function_exists( 'get_user_locale' ) ) {
			return get_user_locale();
		}

		return get_locale();
	}

	/**
	 * This is where you decide where to display the messages and where you set the plugin specific variables.
	 *
	 * @access private
	 *
	 * @param array $args Array wit arguments.
	 */
	private function init( $args ) {
		foreach ( $args as $key => $arg ) {
			$this->$key = $arg;
		}
	}

	/**
	 * Check whether the promo should be hidden or not
	 *
	 * @access private
	 *
	 * @return bool
	 */
	private function hide_promo() {
		$hide_promo = get_transient( 'yoast_i18n_' . $this->project_slug . '_promo_hide' );
		if ( ! $hide_promo ) {
			if ( filter_input( INPUT_GET, 'remove_i18n_promo', FILTER_VALIDATE_INT ) === 1 ) {
				// No expiration time, so this would normally not expire, but it wouldn't be copied to other sites etc.
				set_transient( 'yoast_i18n_' . $this->project_slug . '_promo_hide', true );
				$hide_promo = true;
			}
		}

		return $hide_promo;
	}

	/**
	 * Generates a promo message
	 *
	 * @access private
	 *
	 * @return bool|string $message
	 */
	private function promo_message() {
		$message = false;

		if ( $this->translation_exists && $this->translation_loaded && $this->percent_translated < 90 ) {
			$message = __( 'As you can see, there is a translation of this plugin in %1$s. This translation is currently %3$d%% complete. We need your help to make it complete and to fix any errors. Please register at %4$s to help complete the translation to %1$s!', 'ti-woocommerce-wishlist' ); // @codingStandardsIgnoreLine WordPress.WP.I18n.NonSingularStringLiteralDomain
		} elseif ( ! $this->translation_loaded && $this->translation_exists ) {
			$message = __( 'You\'re using WordPress in <strong>%1$s</strong>. While %2$s has been translated to %1$s for <strong>%3$d%%</strong>, it\'s not been shipped with the plugin yet.<br> You can help! Register at %4$s to help complete the translation to %1$s!', 'ti-woocommerce-wishlist' ); // @codingStandardsIgnoreLine WordPress.WP.I18n.NonSingularStringLiteralDomain
		} elseif ( ! $this->translation_exists ) {
			$message = __( 'You\'re using WordPress in a language we don\'t support yet. We\'d love for %2$s to be translated in that language too, but unfortunately, it isn\'t right now. You can change that! Register at %4$s to help translate it!', 'ti-woocommerce-wishlist' ); // @codingStandardsIgnoreLine WordPress.WP.I18n.NonSingularStringLiteralDomain
		}

		$registration_link = sprintf( '<a href="%1$s">%2$s</a>', esc_url( $this->register_url ), esc_html( $this->glotpress_name ) );
		$message           = sprintf( $message, esc_html( $this->locale_name ), esc_html( $this->plugin_name ), $this->percent_translated, $registration_link );

		return $message;
	}

	/**
	 * Outputs a promo box
	 */
	public function promo( $data ) {

		if ( is_array( $data ) ) {
			return;
		}
		$this->translation_details();

		$message = $this->promo_message();

		if ( $message ) {
			echo '<div id="i18n_promo_box" style="position: relative" class="notice notice-warning tinv-notice-translation">';
			echo '<a href="' . esc_url( add_query_arg( array( 'remove_i18n_promo' => '1' ) ) ) . '" class="notice-dismiss" style="text-decoration: none;"><span class="screen-reader-text">Hide</span></a>';

			echo '<div style="overflow: hidden;padding:20px 10px 10px">';

			if ( isset( $this->glotpress_logo ) && '' !== $this->glotpress_logo ) {
				echo '<a href="' . esc_url( $this->register_url ) . '"><img class="alignleft" style="margin:0 15px 10px 0;max-width:200px;" src="' . esc_url( $this->glotpress_logo ) . '" alt="' . esc_attr( $this->glotpress_name ) . '"/></a>';
			}
			echo '<h2>' . sprintf( __( 'Translation of %s', 'ti-woocommerce-wishlist' ), $this->plugin_name ) . '</h2>'; // @codingStandardsIgnoreLine WordPress.WP.I18n.NonSingularStringLiteralDomain
			echo '<p>' . $message . '</p>'; // WPCS: xss ok.
			echo '<p><a href="' . esc_url( $this->register_url ) . '">' . __( 'Register now &raquo;', 'ti-woocommerce-wishlist' ) . '</a></p>'; // @codingStandardsIgnoreLine WordPress.WP.I18n.NonSingularStringLiteralDomain
			echo '</div>';
			echo '</div>';
		}
	}

	/**
	 * Try to find the transient for the translation set or retrieve them.
	 *
	 * @access private
	 *
	 * @return object|null
	 */
	private function find_or_initialize_translation_details() {
		$set = get_transient( 'yoast_i18n_' . $this->project_slug . '_' . $this->locale );

		if ( ! $set ) {
			$set = $this->retrieve_translation_details();
			set_transient( 'yoast_i18n_' . $this->project_slug . '_' . $this->locale, $set, DAY_IN_SECONDS );
		}

		return $set;
	}

	/**
	 * Try to get translation details from cache, otherwise retrieve them, then parse them.
	 *
	 * @access private
	 */
	private function translation_details() {
		$set = $this->find_or_initialize_translation_details();

		$this->translation_exists = ! is_null( $set );
		$this->translation_loaded = is_textdomain_loaded( 'ti-woocommerce-wishlist' );

		$this->parse_translation_set( $set );
	}

	/**
	 * The API URL to use when requesting translation information.
	 *
	 * @param string $api_url The new API URL.
	 */
	public function set_api_url( $api_url ) {
		$this->api_url = $api_url;
	}

	/**
	 * Returns the API URL to use when requesting translation information.
	 *
	 * @return string
	 */
	private function get_api_url() {
		if ( empty( $this->api_url ) ) {
			$this->api_url = trailingslashit( $this->glotpress_url ) . 'api/projects/' . $this->project_slug;
		}

		return $this->api_url;
	}

	/**
	 * Retrieve the translation details from Yoast Translate
	 *
	 * @access private
	 *
	 * @return object|null
	 */
	private function retrieve_translation_details() {
		$api_url = $this->get_api_url();

		$resp = wp_remote_get( $api_url );
		if ( is_wp_error( $resp ) || wp_remote_retrieve_response_code( $resp ) !== 200 ) {
			return null;
		}
		$body = wp_remote_retrieve_body( $resp );
		unset( $resp );

		if ( $body ) {
			$body = json_decode( $body );
			if ( empty( $body->translation_sets ) ) {
				return null;
			}
			foreach ( $body->translation_sets as $set ) {
				if ( ! property_exists( $set, 'wp_locale' ) ) {
					continue;
				}

				if ( $this->locale === $set->wp_locale ) {
					return $set;
				}
			}
		}

		return null;
	}

	/**
	 * Set the needed private variables based on the results from Yoast Translate
	 *
	 * @param object $set The translation set.
	 *
	 * @access private
	 */
	private function parse_translation_set( $set ) {
		if ( $this->translation_exists && is_object( $set ) ) {
			$this->locale_name        = $set->name;
			$this->percent_translated = $set->percent_translated;
		} else {
			$this->locale_name        = '';
			$this->percent_translated = '';
		}
	}
}
