wp/wp-includes/class-wp-textdomain-registry.php
changeset 21 48c4eec2b7e6
child 22 8c2e4d02f4ef
equal deleted inserted replaced
20:7b1b88e27a20 21:48c4eec2b7e6
       
     1 <?php
       
     2 /**
       
     3  * Locale API: WP_Textdomain_Registry class.
       
     4  *
       
     5  * This file uses rtrim() instead of untrailingslashit() and trailingslashit()
       
     6  * to avoid formatting.php dependency.
       
     7  *
       
     8  * @package WordPress
       
     9  * @subpackage i18n
       
    10  * @since 6.1.0
       
    11  */
       
    12 
       
    13 /**
       
    14  * Core class used for registering text domains.
       
    15  *
       
    16  * @since 6.1.0
       
    17  */
       
    18 #[AllowDynamicProperties]
       
    19 class WP_Textdomain_Registry {
       
    20 	/**
       
    21 	 * List of domains and all their language directory paths for each locale.
       
    22 	 *
       
    23 	 * @since 6.1.0
       
    24 	 *
       
    25 	 * @var array
       
    26 	 */
       
    27 	protected $all = array();
       
    28 
       
    29 	/**
       
    30 	 * List of domains and their language directory path for the current (most recent) locale.
       
    31 	 *
       
    32 	 * @since 6.1.0
       
    33 	 *
       
    34 	 * @var array
       
    35 	 */
       
    36 	protected $current = array();
       
    37 
       
    38 	/**
       
    39 	 * List of domains and their custom language directory paths.
       
    40 	 *
       
    41 	 * @see load_plugin_textdomain()
       
    42 	 * @see load_theme_textdomain()
       
    43 	 *
       
    44 	 * @since 6.1.0
       
    45 	 *
       
    46 	 * @var array
       
    47 	 */
       
    48 	protected $custom_paths = array();
       
    49 
       
    50 	/**
       
    51 	 * Holds a cached list of available .mo files to improve performance.
       
    52 	 *
       
    53 	 * @since 6.1.0
       
    54 	 * @since 6.5.0 This property is no longer used.
       
    55 	 *
       
    56 	 * @var array
       
    57 	 *
       
    58 	 * @deprecated
       
    59 	 */
       
    60 	protected $cached_mo_files = array();
       
    61 
       
    62 	/**
       
    63 	 * Holds a cached list of domains with translations to improve performance.
       
    64 	 *
       
    65 	 * @since 6.2.0
       
    66 	 *
       
    67 	 * @var string[]
       
    68 	 */
       
    69 	protected $domains_with_translations = array();
       
    70 
       
    71 	/**
       
    72 	 * Initializes the registry.
       
    73 	 *
       
    74 	 * Hooks into the {@see 'upgrader_process_complete'} filter
       
    75 	 * to invalidate MO files caches.
       
    76 	 *
       
    77 	 * @since 6.5.0
       
    78 	 */
       
    79 	public function init() {
       
    80 		add_action( 'upgrader_process_complete', array( $this, 'invalidate_mo_files_cache' ), 10, 2 );
       
    81 	}
       
    82 
       
    83 	/**
       
    84 	 * Returns the languages directory path for a specific domain and locale.
       
    85 	 *
       
    86 	 * @since 6.1.0
       
    87 	 *
       
    88 	 * @param string $domain Text domain.
       
    89 	 * @param string $locale Locale.
       
    90 	 *
       
    91 	 * @return string|false Languages directory path or false if there is none available.
       
    92 	 */
       
    93 	public function get( $domain, $locale ) {
       
    94 		$path = $this->all[ $domain ][ $locale ] ?? $this->get_path_from_lang_dir( $domain, $locale );
       
    95 
       
    96 		/**
       
    97 		 * Filters the determined languages directory path for a specific domain and locale.
       
    98 		 *
       
    99 		 * @since 6.6.0
       
   100 		 *
       
   101 		 * @param string|false $path   Languages directory path for the given domain and locale.
       
   102 		 * @param string       $domain Text domain.
       
   103 		 * @param string       $locale Locale.
       
   104 		 */
       
   105 		return apply_filters( 'lang_dir_for_domain', $path, $domain, $locale );
       
   106 	}
       
   107 
       
   108 	/**
       
   109 	 * Determines whether any MO file paths are available for the domain.
       
   110 	 *
       
   111 	 * This is the case if a path has been set for the current locale,
       
   112 	 * or if there is no information stored yet, in which case
       
   113 	 * {@see _load_textdomain_just_in_time()} will fetch the information first.
       
   114 	 *
       
   115 	 * @since 6.1.0
       
   116 	 *
       
   117 	 * @param string $domain Text domain.
       
   118 	 * @return bool Whether any MO file paths are available for the domain.
       
   119 	 */
       
   120 	public function has( $domain ) {
       
   121 		return (
       
   122 			isset( $this->current[ $domain ] ) ||
       
   123 			empty( $this->all[ $domain ] ) ||
       
   124 			in_array( $domain, $this->domains_with_translations, true )
       
   125 		);
       
   126 	}
       
   127 
       
   128 	/**
       
   129 	 * Sets the language directory path for a specific domain and locale.
       
   130 	 *
       
   131 	 * Also sets the 'current' property for direct access
       
   132 	 * to the path for the current (most recent) locale.
       
   133 	 *
       
   134 	 * @since 6.1.0
       
   135 	 *
       
   136 	 * @param string       $domain Text domain.
       
   137 	 * @param string       $locale Locale.
       
   138 	 * @param string|false $path   Language directory path or false if there is none available.
       
   139 	 */
       
   140 	public function set( $domain, $locale, $path ) {
       
   141 		$this->all[ $domain ][ $locale ] = $path ? rtrim( $path, '/' ) . '/' : false;
       
   142 		$this->current[ $domain ]        = $this->all[ $domain ][ $locale ];
       
   143 	}
       
   144 
       
   145 	/**
       
   146 	 * Sets the custom path to the plugin's/theme's languages directory.
       
   147 	 *
       
   148 	 * Used by {@see load_plugin_textdomain()} and {@see load_theme_textdomain()}.
       
   149 	 *
       
   150 	 * @since 6.1.0
       
   151 	 *
       
   152 	 * @param string $domain Text domain.
       
   153 	 * @param string $path   Language directory path.
       
   154 	 */
       
   155 	public function set_custom_path( $domain, $path ) {
       
   156 		$this->custom_paths[ $domain ] = rtrim( $path, '/' );
       
   157 	}
       
   158 
       
   159 	/**
       
   160 	 * Retrieves translation files from the specified path.
       
   161 	 *
       
   162 	 * Allows early retrieval through the {@see 'pre_get_mo_files_from_path'} filter to optimize
       
   163 	 * performance, especially in directories with many files.
       
   164 	 *
       
   165 	 * @since 6.5.0
       
   166 	 *
       
   167 	 * @param string $path The directory path to search for translation files.
       
   168 	 * @return array Array of translation file paths. Can contain .mo and .l10n.php files.
       
   169 	 */
       
   170 	public function get_language_files_from_path( $path ) {
       
   171 		$path = rtrim( $path, '/' ) . '/';
       
   172 
       
   173 		/**
       
   174 		 * Filters the translation files retrieved from a specified path before the actual lookup.
       
   175 		 *
       
   176 		 * Returning a non-null value from the filter will effectively short-circuit
       
   177 		 * the MO files lookup, returning that value instead.
       
   178 		 *
       
   179 		 * This can be useful in situations where the directory contains a large number of files
       
   180 		 * and the default glob() function becomes expensive in terms of performance.
       
   181 		 *
       
   182 		 * @since 6.5.0
       
   183 		 *
       
   184 		 * @param null|array $files List of translation files. Default null.
       
   185 		 * @param string $path The path from which translation files are being fetched.
       
   186 		 **/
       
   187 		$files = apply_filters( 'pre_get_language_files_from_path', null, $path );
       
   188 
       
   189 		if ( null !== $files ) {
       
   190 			return $files;
       
   191 		}
       
   192 
       
   193 		$cache_key = md5( $path );
       
   194 		$files     = wp_cache_get( $cache_key, 'translation_files' );
       
   195 
       
   196 		if ( false === $files ) {
       
   197 			$files = glob( $path . '*.mo' );
       
   198 			if ( false === $files ) {
       
   199 				$files = array();
       
   200 			}
       
   201 
       
   202 			$php_files = glob( $path . '*.l10n.php' );
       
   203 			if ( is_array( $php_files ) ) {
       
   204 				$files = array_merge( $files, $php_files );
       
   205 			}
       
   206 
       
   207 			wp_cache_set( $cache_key, $files, 'translation_files', HOUR_IN_SECONDS );
       
   208 		}
       
   209 
       
   210 		return $files;
       
   211 	}
       
   212 
       
   213 	/**
       
   214 	 * Invalidate the cache for .mo files.
       
   215 	 *
       
   216 	 * This function deletes the cache entries related to .mo files when triggered
       
   217 	 * by specific actions, such as the completion of an upgrade process.
       
   218 	 *
       
   219 	 * @since 6.5.0
       
   220 	 *
       
   221 	 * @param WP_Upgrader $upgrader   Unused. WP_Upgrader instance. In other contexts this might be a
       
   222 	 *                                Theme_Upgrader, Plugin_Upgrader, Core_Upgrade, or Language_Pack_Upgrader instance.
       
   223 	 * @param array       $hook_extra {
       
   224 	 *     Array of bulk item update data.
       
   225 	 *
       
   226 	 *     @type string $action       Type of action. Default 'update'.
       
   227 	 *     @type string $type         Type of update process. Accepts 'plugin', 'theme', 'translation', or 'core'.
       
   228 	 *     @type bool   $bulk         Whether the update process is a bulk update. Default true.
       
   229 	 *     @type array  $plugins      Array of the basename paths of the plugins' main files.
       
   230 	 *     @type array  $themes       The theme slugs.
       
   231 	 *     @type array  $translations {
       
   232 	 *         Array of translations update data.
       
   233 	 *
       
   234 	 *         @type string $language The locale the translation is for.
       
   235 	 *         @type string $type     Type of translation. Accepts 'plugin', 'theme', or 'core'.
       
   236 	 *         @type string $slug     Text domain the translation is for. The slug of a theme/plugin or
       
   237 	 *                                'default' for core translations.
       
   238 	 *         @type string $version  The version of a theme, plugin, or core.
       
   239 	 *     }
       
   240 	 * }
       
   241 	 */
       
   242 	public function invalidate_mo_files_cache( $upgrader, $hook_extra ) {
       
   243 		if (
       
   244 			! isset( $hook_extra['type'] ) ||
       
   245 			'translation' !== $hook_extra['type'] ||
       
   246 			array() === $hook_extra['translations']
       
   247 		) {
       
   248 			return;
       
   249 		}
       
   250 
       
   251 		$translation_types = array_unique( wp_list_pluck( $hook_extra['translations'], 'type' ) );
       
   252 
       
   253 		foreach ( $translation_types as $type ) {
       
   254 			switch ( $type ) {
       
   255 				case 'plugin':
       
   256 					wp_cache_delete( md5( WP_LANG_DIR . '/plugins/' ), 'translation_files' );
       
   257 					break;
       
   258 				case 'theme':
       
   259 					wp_cache_delete( md5( WP_LANG_DIR . '/themes/' ), 'translation_files' );
       
   260 					break;
       
   261 				default:
       
   262 					wp_cache_delete( md5( WP_LANG_DIR . '/' ), 'translation_files' );
       
   263 					break;
       
   264 			}
       
   265 		}
       
   266 	}
       
   267 
       
   268 	/**
       
   269 	 * Returns possible language directory paths for a given text domain.
       
   270 	 *
       
   271 	 * @since 6.2.0
       
   272 	 *
       
   273 	 * @param string $domain Text domain.
       
   274 	 * @return string[] Array of language directory paths.
       
   275 	 */
       
   276 	private function get_paths_for_domain( $domain ) {
       
   277 		$locations = array(
       
   278 			WP_LANG_DIR . '/plugins',
       
   279 			WP_LANG_DIR . '/themes',
       
   280 		);
       
   281 
       
   282 		if ( isset( $this->custom_paths[ $domain ] ) ) {
       
   283 			$locations[] = $this->custom_paths[ $domain ];
       
   284 		}
       
   285 
       
   286 		return $locations;
       
   287 	}
       
   288 
       
   289 	/**
       
   290 	 * Gets the path to the language directory for the current domain and locale.
       
   291 	 *
       
   292 	 * Checks the plugins and themes language directories as well as any
       
   293 	 * custom directory set via {@see load_plugin_textdomain()} or {@see load_theme_textdomain()}.
       
   294 	 *
       
   295 	 * @since 6.1.0
       
   296 	 *
       
   297 	 * @see _get_path_to_translation_from_lang_dir()
       
   298 	 *
       
   299 	 * @param string $domain Text domain.
       
   300 	 * @param string $locale Locale.
       
   301 	 * @return string|false Language directory path or false if there is none available.
       
   302 	 */
       
   303 	private function get_path_from_lang_dir( $domain, $locale ) {
       
   304 		$locations = $this->get_paths_for_domain( $domain );
       
   305 
       
   306 		$found_location = false;
       
   307 
       
   308 		foreach ( $locations as $location ) {
       
   309 			$files = $this->get_language_files_from_path( $location );
       
   310 
       
   311 			$mo_path  = "$location/$domain-$locale.mo";
       
   312 			$php_path = "$location/$domain-$locale.l10n.php";
       
   313 
       
   314 			foreach ( $files as $file_path ) {
       
   315 				if (
       
   316 					! in_array( $domain, $this->domains_with_translations, true ) &&
       
   317 					str_starts_with( str_replace( "$location/", '', $file_path ), "$domain-" )
       
   318 				) {
       
   319 					$this->domains_with_translations[] = $domain;
       
   320 				}
       
   321 
       
   322 				if ( $file_path === $mo_path || $file_path === $php_path ) {
       
   323 					$found_location = rtrim( $location, '/' ) . '/';
       
   324 					break 2;
       
   325 				}
       
   326 			}
       
   327 		}
       
   328 
       
   329 		if ( $found_location ) {
       
   330 			$this->set( $domain, $locale, $found_location );
       
   331 
       
   332 			return $found_location;
       
   333 		}
       
   334 
       
   335 		/*
       
   336 		 * If no path is found for the given locale and a custom path has been set
       
   337 		 * using load_plugin_textdomain/load_theme_textdomain, use that one.
       
   338 		 */
       
   339 		if ( 'en_US' !== $locale && isset( $this->custom_paths[ $domain ] ) ) {
       
   340 			$fallback_location = rtrim( $this->custom_paths[ $domain ], '/' ) . '/';
       
   341 			$this->set( $domain, $locale, $fallback_location );
       
   342 			return $fallback_location;
       
   343 		}
       
   344 
       
   345 		$this->set( $domain, $locale, false );
       
   346 
       
   347 		return false;
       
   348 	}
       
   349 }