*/ private $names = array(); /** * WooCommerce's country list, loaded at most once. * * @var array|null */ private $fallback; /** * The readable name of a country. * * @param string $code Two-letter code, in any case. * @return string The name, or the code itself when nothing can name it. */ public function name( string $code ): string { $code = strtoupper( trim( $code ) ); if ( ! $this->is_code( $code ) ) { // The report uses this for traffic it could not place, and "(not set)" is more honest on a // table row than an empty cell that reads as a rendering fault. return '' === $code ? __( '(not set)', 'adpix-analytics' ) : $code; } if ( isset( $this->names[ $code ] ) ) { return $this->names[ $code ]; } $this->names[ $code ] = $this->resolve( $code ); return $this->names[ $code ]; } /** * The flag for a country code. * * @param string $code Two-letter code, in any case. * @return string Empty when the code is not a country code. */ public function flag( string $code ): string { $code = strtoupper( trim( $code ) ); if ( ! $this->is_code( $code ) ) { return ''; } $flag = ''; for ( $i = 0; $i < 2; $i++ ) { $flag .= $this->indicator( ord( $code[ $i ] ) - ord( 'A' ) ); } return $flag; } /** * Find a name for a code. * * @param string $code Upper-case two-letter code. * @return string */ private function resolve( string $code ): string { // PHP's own internationalisation data, when the host has it: names in the admin's language, // current with CLDR, costing nothing to ship. if ( class_exists( '\Locale' ) && method_exists( '\Locale', 'getDisplayRegion' ) ) { $name = \Locale::getDisplayRegion( '-' . $code, str_replace( '_', '-', get_user_locale() ) ); if ( is_string( $name ) && '' !== $name && $name !== $code ) { return $name; } } // Otherwise WooCommerce, which most sites running this plugin's commerce tracking already have, // and which translates its list through the same locale the admin is being read in. $fallback = $this->woocommerce_countries(); if ( isset( $fallback[ $code ] ) ) { return $fallback[ $code ]; } return $code; } /** * WooCommerce's translated country list, if WooCommerce is here. * * @return array */ private function woocommerce_countries(): array { if ( null !== $this->fallback ) { return $this->fallback; } $this->fallback = array(); if ( ! function_exists( 'WC' ) ) { return $this->fallback; } $wc = WC(); if ( ! is_object( $wc ) || ! isset( $wc->countries ) || ! is_object( $wc->countries ) ) { return $this->fallback; } $countries = $wc->countries->get_countries(); if ( is_array( $countries ) ) { $this->fallback = array_map( 'strval', $countries ); } return $this->fallback; } /** * Whether a string is shaped like an ISO 3166-1 alpha-2 code. * * @param string $code Candidate. * @return bool */ private function is_code( string $code ): bool { return 1 === preg_match( '/^[A-Z]{2}$/', $code ); } /** * One regional indicator symbol, as UTF-8. * * Encoded by hand because the alternative, mb_chr(), needs an extension that is not guaranteed, * and this is four lines of a format that has not changed since 1996. * * @param int $offset Letter index, zero for "A". * @return string */ private function indicator( int $offset ): string { $codepoint = self::INDICATOR_BASE + $offset; return chr( 0xF0 | ( $codepoint >> 18 ) ) . chr( 0x80 | ( ( $codepoint >> 12 ) & 0x3F ) ) . chr( 0x80 | ( ( $codepoint >> 6 ) & 0x3F ) ) . chr( 0x80 | ( $codepoint & 0x3F ) ); } }