├── CHANGELOG.md
├── composer.json
├── inpsyde-translation-cache.php
├── README.md
├── src
└── MoCache.php
└── LICENSE
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/).
3 |
4 | ## [1.0.3](https://github.com/inpsyde/translation-cache/compare/1.0.2...1.0.) - 2019-01-21
5 | * Add parameter to make thrid param optional to avoid errors, #3
6 |
7 | ## [1.0.2](https://github.com/inpsyde/translation-cache/compare/1.0.1...1.0.2) - 2017-09-20
8 | * Fixed warning for undefined variable access when dropins replaced `wp_cache_get` with a signature different from core
9 |
10 | ## [1.0.1](https://github.com/inpsyde/translation-cache/compare/1.0.0...1.0.1) - 2016-10-31
11 | * Codestyles.
12 |
13 | ## 1.0.0
14 | * Initial Release.
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "inpsyde/translation-cache",
3 | "description": "Improves site performance by caching translation files using WordPress object cache.",
4 | "keywords": [
5 | "wordpress",
6 | "cache",
7 | "caching",
8 | "performance",
9 | "mo file",
10 | "i18n",
11 | "internationalization",
12 | "l10n",
13 | "localization",
14 | "language",
15 | "languages",
16 | "translation",
17 | "translations",
18 | "translate"
19 | ],
20 | "type": "wordpress-plugin",
21 | "license": "gpl-2.0-or-later",
22 | "authors": [
23 | {
24 | "name": "Inpsyde GmbH",
25 | "email": "hello@inpsyde.com",
26 | "homepage": "http://inpsyde.com",
27 | "role": "Company"
28 | },
29 | {
30 | "name": "Giuseppe Mazzapica",
31 | "email": "giuseppe.mazzapica@gmail.com",
32 | "role": "Developer"
33 | },
34 | {
35 | "name": "Masaki Takeuchi",
36 | "role": "Developer"
37 | }
38 | ],
39 | "minimum-stability": "stable",
40 | "require": {
41 | "php": ">=5.5"
42 | },
43 | "autoload": {
44 | "psr-4": {
45 | "Inpsyde\\TranslationCache\\": "src/"
46 | }
47 | },
48 | "config": {
49 | "optimize-autoloader": true
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/inpsyde-translation-cache.php:
--------------------------------------------------------------------------------
1 | id ) {
95 | return;
96 | }
97 |
98 | $markup = '
';
99 | $format = 'Inpsyde Translation Cache can\'t install itself as MU plugin.
';
100 | $format .= 'Please copy "%s" to "%s" to improve translation performance';
101 | printf( $markup, sprintf( $format, __FILE__, trailingslashit( WPMU_PLUGIN_DIR ) . basename( __FILE__ ) ) );
102 | }
103 | );
104 |
105 | /*
106 | * When regular plugin is deactivated, but MU plugin is still there because deleting it failed, show an admin notice
107 | * telling users to manually delete the file from MU plugins folder, or in case it was edited, to removed the code that
108 | * show the notice.
109 | */
110 | ( ! $is_regular_plugin && ! get_site_option( 'inpsyde-mocache-class-path' ) ) and add_action(
111 | 'all_admin_notices',
112 | function () {
113 | $line = __LINE__ - 3;
114 |
115 | $markup = '%s
';
116 | $msg = 'Inpsyde Translation Cache is installed as MU plugin without its "regular" plugin counterpart.
';
117 | $msg .= 'Possible causes are:
';
118 | $msg .= '- was not possible to delete MU plugin on plugin deactivation
';
119 | $msg .= '- the MU plugin was edited "manually" and preserved to avoid deleting custom work
';
120 | $msg .= 'In the first case, please delete MU plugin file at %s ';
121 | $msg .= '(note it is not doing anything but showing this notice).
';
122 | $msg .= 'In the second case, if the MU plugin was edited to do something without regular plugin, ';
123 | $msg .= 'you should probably also delete the code that shows this notice (starts around line %d).
';
124 | printf( $markup, sprintf( $msg, __FILE__, $line ) );
125 | }
126 | );
127 |
128 | // When installed as MU plugin successfully, there's nothing more to do.
129 | if ( $is_regular_plugin && $is_loaded ) {
130 | unset( $is_regular_plugin, $is_loaded );
131 |
132 | return;
133 | }
134 |
135 | // We need to check for existence because when copied to MU plugin this file is loaded twice.
136 | if ( ! function_exists( __NAMESPACE__ . '\\load_translation_cache' ) ) {
137 |
138 | /**
139 | * Bootstrap plugin routine.
140 | *
141 | * @wp-hook muplugins_loaded
142 | * @wp-hook plugins_loaded
143 | */
144 | function load_translation_cache() {
145 |
146 | $class_missing = FALSE;
147 |
148 | if ( ! class_exists( MoCache::class ) ) {
149 | $class_missing = TRUE;
150 | $class_path = get_site_option( 'inpsyde-mocache-class-path' );
151 | if ( $class_path && file_exists( $class_path ) && is_readable( $class_path ) ) {
152 | /** @noinspection PhpIncludeInspection */
153 | require_once $class_path;
154 | }
155 | }
156 |
157 | if ( ! $class_missing || class_exists( MoCache::class ) ) {
158 |
159 | // Add plugin hooks, avoiding to add them more than once.
160 | $has_load = has_filter( 'override_load_textdomain', [ MoCache::class, 'load' ] );
161 | $has_load or add_filter( 'override_load_textdomain', [ MoCache::class, 'load' ], 30, 3 );
162 |
163 | $has_theme_switch = has_action( 'switch_theme', [ MoCache::class, 'on_theme_switch' ] );
164 | $has_theme_switch or add_action( 'switch_theme', [ MoCache::class, 'on_theme_switch' ], 30, 3 );
165 |
166 | $has_plugin_activated = has_action( 'activated_plugin', [ MoCache::class, 'on_plugin_switch' ] );
167 | $has_plugin_activated or add_action( 'activated_plugin', [ MoCache::class, 'on_plugin_switch' ], 30 );
168 |
169 | $has_plugin_deactivated = has_action( 'deactivate_plugin', [ MoCache::class, 'on_plugin_switch' ] );
170 | $has_plugin_deactivated or add_action( 'deactivate_plugin', [ MoCache::class, 'on_plugin_switch' ], 30 );
171 |
172 | $has_shutdown = has_action( 'shutdown', [ MoCache::class, 'sync_options' ] );
173 | $has_shutdown or add_action( 'shutdown', [ MoCache::class, 'sync_options' ], 30 );
174 |
175 | /**
176 | * Fires just after the plugin class has been discovered.
177 | * Useful to wrap calls to `Inpsyde\MoCache::flush_cache()`
178 | */
179 | do_action( 'inpsyde_translation_cache' );
180 | }
181 | }
182 | }
183 |
184 | /*
185 | * When installed as MU plugin, we lunch plugin workflow at 'muplugins_loaded'.
186 | * When self installation as MU plugin failed, run loading routine at 'plugins_loaded' as a fallback.
187 | */
188 | add_action(
189 | $is_regular_plugin ? 'plugins_loaded' : 'muplugins_loaded',
190 | __NAMESPACE__ . '\\load_translation_cache',
191 | 0
192 | );
193 |
194 | // Cleanup.
195 | unset( $is_regular_plugin, $is_loaded );
196 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Inpsyde Translation Cache
2 | =========================
3 |
4 | > Improves site performance by caching translation files using WordPress object cache.
5 |
6 | ----
7 |
8 | ## Description
9 |
10 | _'Inpsyde Translation Cache'_ provides caching of the translation `.mo` files using [WP Object Cache](http://codex.wordpress.org/Class_Reference/WP_Object_Cache)
11 | mechanism.
12 |
13 | Performance benefit of the plugin can be seen when using [persistent cache plugin](http://codex.wordpress.org/Class_Reference/WP_Object_Cache#Persistent_Cache_Plugins)
14 | because, by default, WordPress object cache does not 'survive' the single request and translation files are _already_
15 | cached on a per-request basis in WP.
16 |
17 | For this reason **the plugin, by default, does nothing when there's no persistent caching plugin installed**.
18 |
19 | --------
20 |
21 | ## MU plugin self-installation
22 |
23 | Considering that many plugins have their language files, to be allowed to cache plugins translations, _'Inpsyde Translation Cache'_
24 | has to run as [MU plugin](https://codex.wordpress.org/Must_Use_Plugins).
25 |
26 | For this reason, **_'Inpsyde Translation Cache'_ copies its main file to the MU plugin folder on installation**,
27 | so it can work as MU plugin.
28 |
29 | The 'regular' plugin stays active though, but doing nothing, or better, it listed for deactivation to delete the MU
30 | plugin copy.
31 |
32 | In fact, when 'regular' plugin is deactivated, the MU plugin copy is deleted (unless it was modified).
33 |
34 | Please note that on some configurations this 'self-installation' routine may not work, e.g. if MU plugins folder is not
35 | writable.
36 |
37 | In those cases _'Inpsyde Translation Cache'_ will continue to work as a regular plugin, but it will not be able to cache
38 | translation files loaded before `'plugins_loaded'` hook is fired. Moreover, an admin notice is also shown to suggest users
39 | to manually copy main plugin file to MU plugins folder to improve performance.
40 |
41 | --------
42 |
43 | ## Cache invalidation
44 |
45 | _'Inpsyde Translation Cache'_ stores all the text domains:
46 |
47 | - core
48 | - plugins
49 | - themes
50 |
51 | it means that when a new version of those is installed, the cache need to cleared.
52 |
53 | ### Automatic invalidation
54 |
55 | For invalidation of WordPress core translations the plugin relies on WordPress version: if that change all cached core
56 | translations are invalidated.
57 |
58 | For plugin and themes, things are less straightforward.
59 |
60 | _'Inpsyde Translation Cache'_ uses [`'switch_theme'`](https://developer.wordpress.org/reference/functions/switch_theme/)
61 | hook to invalidate cache of both the 'old' and the 'new' theme.
62 |
63 | It also uses [`'activated_plugin'`](https://developer.wordpress.org/reference/hooks/activated_plugin/) and
64 | [`'deactivated_plugin'`](https://developer.wordpress.org/reference/hooks/deactivated_plugin/) to invalidate cache for
65 | plugins.
66 |
67 | However, to invalidate cache of themes and plugins _'Inpsyde Translation Cache'_ needs to know the text domain they use.
68 |
69 | For that scope, _'Inpsyde Translation Cache'_ uses 'TextDomain' file header of plugin and themes.
70 |
71 | Considering that 'TextDomain' file header is not mandatory, it is possible that some plugins or themes that don't provide
72 | that header (even if they should).
73 |
74 | ### Manual invalidation
75 |
76 | In those cases it is possible to invalidate the cache 'manually' by calling `Inpsyde\MoCache::flush_cache()` method.
77 |
78 | For example, let's assume there's a plugin that loads a text domain like this:
79 |
80 | ```php
81 | load_plugin_textdomain( 'some-plugin-txt-domain', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
82 | ```
83 |
84 | and let's assume this plugin has **not** in plugin headers something like:
85 |
86 | ```
87 | Text Domain: some-plugin-txt-domain
88 | ```
89 |
90 | _'Inpsyde Translation Cache'_ will not be able to flush cache for that plugin automatically when activated and deactivated.
91 |
92 | However, 'manually' invalidating cache for that plugin would be just a matter of:
93 |
94 | ```php
95 | function invalidate_some_plugin_translation_cache() {
96 | if ( class_exists( 'Inpsyde\TranslationCache\MoCache' ) ) {
97 | Inpsyde\TranslationCache\MoCache::flush_cache( 'some-plugin-txt-domain' );
98 | }
99 | }
100 |
101 | register_activation_hook(
102 | WP_PLUGIN_DIR . '/some-plugin-dir/some-plugin-filename.php',
103 | 'invalidate_some_plugin_translation_cache'
104 | );
105 |
106 | register_deactivation_hook(
107 | WP_PLUGIN_DIR . '/some-plugin-dir/some-plugin-filename.php',
108 | 'invalidate_some_plugin_translation_cache'
109 | );
110 | ```
111 |
112 | Of course, when it is possible to edit the plugin to include the `Text Domain` plugin header, it is surely better,
113 | because _'Inpsyde Translation Cache'_ will be able to invalidate the cache for plugin translations automatically.
114 |
115 | For themes the workflow is very similar, just use `'switch_theme'` hook to intercept theme change.
116 |
117 | ### Invalidation by version
118 |
119 | There's another way to invalidate cached translations.
120 |
121 | _'Inpsyde Translation Cache'_ builds an unique key for the combination of text domain and `.mo` file path.
122 |
123 | This unique key is built using an 'hash' of:
124 |
125 | - text domain
126 | - `.mo` file path
127 | - _'Inpsyde Translation Cache'_ own version
128 | - an arbitrary 'version' string that can be set via `mocache_cache_version` filter
129 |
130 | It means that using `mocache_cache_version` filter it is possible to invalidate all the stored keys for a
131 | given text domain.
132 |
133 | For example, assuming same plugin as above we could do:
134 |
135 | ```php
136 | add_filter( 'mocache_cache_version', function( $version, $domain ) {
137 | $plugin_path = WP_PLUGIN_DIR . '/some-plugin-dir/some-plugin-filename.php';
138 | $headers = get_plugin_data( $plugin_file_path, FALSE, FALSE );
139 |
140 | return $headers['Version'];
141 | }, 10, 2 );
142 | ```
143 |
144 | So we use the 'Version' plugin header to invalidate the cache whenever the version change.
145 |
146 | Compared to the 'manual' approach above, this approach has the benefit to do not explicitly call _'Inpsyde Translation Cache'_
147 | objects, so there's no need to check for class existence, in fact, if _'Inpsyde Translation Cache'_ is not installed or
148 | not activated, then the code does nothing with no effect.
149 |
150 | But it also has two flaws:
151 |
152 | - Cache entries for 'old' versions are **not** deleted, they are just ignored because there's a new version.
153 | However, they will be discarded when 'natural' expiration for keys is reached and considering that, by default, the
154 | 'natural' expiration is 12 hours, this issue should not be that critical.
155 | - A code like the one above requires the plugin to define the version in headers and to be effective it relies on the
156 | version be updated every time any translation file is changed.
157 | Even if these are common practices, theme / plugins authors may just ignore or forget them.
158 |
159 | So, again, the suggestion is to use `Text Domain` header whenever possible, maybe suggesting to maintainers of
160 | third party plugins and themes to add it when not present.
161 |
162 | --------
163 |
164 | ## Available hooks
165 |
166 | _'Inpsyde Translation Cache'_ provides some hooks for customization. Most of them will only work properly from a MU plugin,
167 | especially if _'Inpsyde Translation Cache'_ is running as MU plugin.
168 |
169 | Available **action** hooks:
170 |
171 | - **`inpsyde_translation_cache`**, Fires just after the plugin class has been loaded.
172 | Useful to wrap calls to `Inpsyde\TranslationCache\MoCache::flush_cache()`
173 |
174 | Available **filter** hooks:
175 |
176 | - **`mocache_cache_enabled`** Can be used to disable plugin programmatically returning `false` via hooked callback.
177 |
178 | - **`mocache_cache_version`** Can be used to invalidate the cache for given text domain (passed as argument)
179 | If returned version change, cache is invalidated. See _' Invalidation by version'_ above.
180 |
181 | - **`mocache_cache_expire`** Can be used to customize the duration in seconds of cached values. Returning a value
182 | less or equal to zero prevent cache to be done at all.
183 |
184 | --------
185 |
186 | ## Installation
187 |
188 | _'Inpsyde Translation Cache'_ is available via Composer with package name **`inpsyde/mo-cache`**, but it does not
189 | require Composer to be installed or used.
190 |
191 | The 'classical' installation method (_download_ -> _put in plugins folder_ -> _activate_) works as well.
192 |
193 | --------
194 |
195 | ## Requirements
196 |
197 | * PHP 5.5 or higher.
198 | * WordPress, tested currently in version 5.1
199 |
200 | --------
201 |
202 | ## License
203 |
204 | This repository is a free software, and is released under the terms of the GNU General Public License version 2 or (at your option) any later version. See [LICENSE](./LICENSE) for complete license.
205 |
206 | _'Inpsyde Translation Cache'_ incorporates work covered by the following copyright and
207 | permission notices:
208 |
209 | - ['MO Cache' WordPress plugin](https://wordpress.org/plugins/mo-cache/),
210 | Copyright (c) Masaki Takeuchi (m4i)
211 | Released under the MIT.
212 |
213 |
214 | ```
215 | ___ _
216 | |_ _|_ __ _ __ ___ _ _ __| | ___
217 | | || '_ \| '_ \/ __| | | |/ _` |/ _ \
218 | | || | | | |_) \__ \ |_| | (_| | __/
219 | |___|_| |_| .__/|___/\__, |\__,_|\___|
220 | |_| |___/
221 | ```
222 |
223 | The team at [Inpsyde](https://inpsyde.com) is engineering the Web since 2006.
224 |
--------------------------------------------------------------------------------
/src/MoCache.php:
--------------------------------------------------------------------------------
1 |
19 | * @author Masaki Takeuchi
20 | * @package inpsyde-translation-cache
21 | */
22 | namespace Inpsyde\TranslationCache;
23 |
24 | /**
25 | * Class MoCache
26 | *
27 | * @package Inpsyde\TranslationCache
28 | */
29 | class MoCache {
30 |
31 | const VERSION = '1.0.1';
32 | const GROUP = 'mo_cache';
33 | const KEYS_OPTION = 'inpsyde_translation_cache';
34 | const DEFAULT_EXPIRE = 43200; // 12 hours
35 |
36 | /**
37 | * Store options for each domain.
38 | *
39 | * @var array
40 | */
41 | private static $options;
42 |
43 | /**
44 | * Flush cache for given domain(s).
45 | *
46 | * @param string|string[] $domains The given domains.
47 | *
48 | * @return bool
49 | */
50 | public static function flush_cache( $domains ) {
51 |
52 | $options = is_array( self::$options ) ? self::$options : get_site_option( self::KEYS_OPTION, [] );
53 | $flushed = 0;
54 |
55 | foreach ( (array) $domains as $domain ) {
56 | if ( ! $domain || ! is_string( $domain ) || ! array_key_exists( $domain, $options ) ) {
57 | continue;
58 | }
59 |
60 | $flushed ++;
61 |
62 | $keys = $options[ $domain ];
63 | unset( $options[ $domain ] );
64 |
65 | array_walk(
66 | $keys,
67 | function ( $key ) {
68 |
69 | wp_cache_delete( $key, self::GROUP );
70 | }
71 | );
72 | }
73 |
74 | self::$options = $options;
75 |
76 | return $flushed > 0;
77 | }
78 |
79 | /**
80 | * Flush all cached translations.
81 | */
82 | public static function flush_all() {
83 |
84 | $options = is_array( self::$options ) ? self::$options : get_site_option( self::KEYS_OPTION, [] );
85 | self::$options = [];
86 |
87 | /**
88 | * The stored options.
89 | *
90 | * @var array $options
91 | */
92 | foreach ( $options as $domain => $keys ) {
93 | array_walk(
94 | $keys,
95 | function ( $key ) {
96 |
97 | wp_cache_delete( $key, self::GROUP );
98 | }
99 | );
100 | }
101 |
102 | delete_site_option( self::KEYS_OPTION );
103 |
104 | return TRUE;
105 | }
106 |
107 | /**
108 | * Runs when theme is switched and tries to invalidate the cache for bot old and new theme.
109 | *
110 | * @wp-hook switch_theme
111 | *
112 | * @param string $new_theme_name New theme string.
113 | * @param \WP_Theme $new_theme New Theme.
114 | * @param \WP_Theme $old_theme Old Theme.
115 | *
116 | * @return bool
117 | */
118 | public static function on_theme_switch( $new_theme_name, \WP_Theme $new_theme, \WP_Theme $old_theme = null ) {
119 |
120 | $domains = $old_theme ? [ $old_theme->get( 'TextDomain' ) ] : [];
121 | $domains[] = $new_theme->get( 'TextDomain' );
122 |
123 | return self::flush_cache( $domains );
124 | }
125 |
126 | /**
127 | * Runs when a plugin is activated and deactivated, trying to invalidate the cache for it.
128 | *
129 | * @wp-hook activated_plugin
130 | * @wp-hook deactivate_plugin
131 | *
132 | * @param string $plugin_file The plugin file name string.
133 | *
134 | * @return bool
135 | */
136 | public static function on_plugin_switch( $plugin_file ) {
137 |
138 | if ( ! is_file( $plugin_file ) ) {
139 | $plugin_file = trailingslashit( WP_PLUGIN_DIR ) . $plugin_file;
140 | }
141 |
142 | if ( ! is_file( $plugin_file ) ) {
143 | return FALSE;
144 | }
145 |
146 | $headers = get_plugin_data( $plugin_file, FALSE, FALSE );
147 | if ( $headers && isset( $headers[ 'TextDomain' ] ) ) {
148 | return self::flush_cache( [ $headers[ 'TextDomain' ] ] );
149 | }
150 |
151 | return FALSE;
152 |
153 | }
154 |
155 | /**
156 | * Sync options with database.
157 | *
158 | * @wp-hook shutdown
159 | */
160 | public static function sync_options() {
161 |
162 | if ( ! is_array( self::$options ) ) {
163 | return;
164 | }
165 |
166 | $cache_found_filter = function ( $key ) {
167 |
168 | $found = FALSE;
169 | is_string( $key ) and wp_cache_get( $key, self::GROUP, TRUE, $found );
170 |
171 | return $found;
172 | };
173 |
174 | // Remove keys that can't be found in cache anymore.
175 | array_walk(
176 | self::$options,
177 | function ( &$keys ) use ( $cache_found_filter ) {
178 |
179 | $keys = array_filter( (array) $keys, $cache_found_filter );
180 | }
181 | );
182 |
183 | array_filter( self::$options );
184 |
185 | get_site_option( self::KEYS_OPTION )
186 | ? update_site_option( self::KEYS_OPTION, self::$options )
187 | : add_site_option( self::KEYS_OPTION, self::$options );
188 | }
189 |
190 | /**
191 | * Overrides default loading of .mo file, by loading translation values from cache.
192 | *
193 | * @wp-hook override_load_textdomain
194 | *
195 | * @param bool $override Whether to override the .mo file loading. Default false.
196 | * @param string $domain Text domain. Unique identifier for retrieving translated strings.
197 | * @param string $mo_file Path to the MO file.
198 | *
199 | * @return bool
200 | */
201 | public static function load( $override, $domain, $mo_file ) {
202 |
203 | $instance = new static();
204 |
205 | return $instance->override_load_textdomain( $override, $domain, $mo_file );
206 | }
207 |
208 | /**
209 | * Rewrite the textdomain load with cached values.
210 | *
211 | * @param bool $override Whether to override the .mo file loading. Default false.
212 | * @param string $domain Text domain. Unique identifier for retrieving translated strings.
213 | * @param string $mo_file Path to the MO file.
214 | *
215 | * @return bool
216 | */
217 | public function override_load_textdomain( $override, $domain, $mo_file ) {
218 |
219 | if ( ! $this->is_enabled( $domain, $mo_file ) ) {
220 | return FALSE;
221 | }
222 |
223 | /**
224 | * Fires before the MO translation file is loaded.
225 | *
226 | * @param string $domain Text domain. Unique identifier for retrieving translated strings.
227 | * @param string $mo_file Path to the .mo file.
228 | *
229 | * @see \load_textdomain()
230 | */
231 | do_action( 'load_textdomain', $domain, $mo_file );
232 |
233 | /**
234 | * Filters MO file path for loading translations for a specific text domain.
235 | *
236 | * @param string $mo_file Path to the MO file.
237 | * @param string $domain Text domain. Unique identifier for retrieving translated strings.
238 | *
239 | * @see \load_textdomain()
240 | */
241 | $mo_file = (string) apply_filters( 'load_textdomain_mofile', $mo_file, $domain );
242 |
243 | if ( function_exists( 'wp_cache_add_global_groups' ) ) {
244 | wp_cache_add_global_groups( self::GROUP );
245 | }
246 |
247 | $key = $this->get_key( $domain, $mo_file );
248 |
249 | $mo = new \MO();
250 | $cached = $this->load_cache( $key, $mo );
251 |
252 | if ( $cached ) {
253 | return $this->setup_globals( $domain, $mo );
254 | }
255 |
256 | if ( is_readable( $mo_file ) && $mo->import_from_file( $mo_file ) ) {
257 | $this->set_cache( $key, $mo, $domain, $mo_file );
258 | $override = $this->setup_globals( $domain, $mo );
259 | }
260 |
261 | return $override;
262 | }
263 |
264 | /**
265 | * Returns true when cache is enabled.
266 | *
267 | * @param string $domain Text domain. Unique identifier for retrieving translated strings.
268 | * @param string $mo_file The path to the mo file.
269 | *
270 | * @return bool
271 | */
272 | private function is_enabled( $domain, $mo_file ) {
273 |
274 | $enabled = wp_using_ext_object_cache();
275 |
276 | /**
277 | * Filters the value of enabled. By default is true when using external object
278 | * cache, because there are no real benefit in using the plugin with default
279 | * cache mechanism that does not survive the request.
280 | *
281 | * If filters return false, plugin do nothing.
282 | *
283 | * @param bool $enabled True if plugin should work, by default when external object cache is in place.
284 | * @param string $domain Text domain. Unique identifier for retrieving translated strings.
285 | * @param string $mo_file Path to the MO file.
286 | */
287 | $enabled = apply_filters( 'mocache_cache_enabled', $enabled, $domain, $mo_file );
288 |
289 | return filter_var( $enabled, FILTER_VALIDATE_BOOLEAN );
290 |
291 | }
292 |
293 | /**
294 | * Build a cache key from domain and mo file.
295 | *
296 | * @param string $domain Text domain. Unique identifier for retrieving translated strings.
297 | * @param string $mo_file The path to the mo file.
298 | *
299 | * @return string
300 | */
301 | private function get_key( $domain, $mo_file ) {
302 |
303 | $seed = '';
304 | $is_default = ! $domain || 'default' === $domain;
305 | if ( $is_default ) {
306 | $seed = $GLOBALS[ 'wp_version' ];
307 | }
308 |
309 | if ( ! $seed ) {
310 |
311 | /**
312 | * Filters the cache version, can be used to invalidate the cache
313 | * when the plugin/theme that uses the mo file change version.
314 | *
315 | * @param string $seed Cache ver for given domain / mo file, when changes old cache is invalidated.
316 | * @param string $domain Text domain. Unique identifier for retrieving translated strings.
317 | */
318 | $seed = apply_filters( 'mocache_cache_version', $seed, $domain );
319 | }
320 |
321 | is_string( $seed ) or $seed = '';
322 |
323 | return md5( self::VERSION . $seed . $domain . $mo_file );
324 | }
325 |
326 | /**
327 | * Loads MO entries and headers from cache if available.
328 | *
329 | * @param string $key The key of the entry.
330 | * @param \MO $mo The translation entries.
331 | *
332 | * @return bool
333 | */
334 | private function load_cache( $key, \MO $mo ) {
335 |
336 | $cache = wp_cache_get( $key, self::GROUP, FALSE );
337 | if ( is_array( $cache ) && isset( $cache[ 'entries' ], $cache[ 'headers' ] ) ) {
338 | $mo->entries = $cache[ 'entries' ];
339 | $mo->set_headers( $cache[ 'headers' ] );
340 |
341 | return TRUE;
342 | }
343 |
344 | return FALSE;
345 | }
346 |
347 | /**
348 | * Set MO entries and headers in cache.
349 | *
350 | * @param string $key The key of the entry.
351 | * @param \MO $mo The translation entries.
352 | * @param string $domain Text domain. Unique identifier for retrieving translated strings.
353 | * @param string $mo_file The path to the mo file.
354 | *
355 | * @return void
356 | */
357 | private function set_cache( $key, \MO $mo, $domain, $mo_file ) {
358 |
359 | $cache = [
360 | 'entries' => $mo->entries,
361 | 'headers' => $mo->headers,
362 | ];
363 |
364 | $expire = wp_using_ext_object_cache() ? self::DEFAULT_EXPIRE : 0;
365 |
366 | /**
367 | * Filters the expiration of cache.
368 | *
369 | * @param int $expire Time-to-live in seconds, 0 means no limit.
370 | * @param string $domain Text domain. Unique identifier for retrieving translated strings.
371 | * @param string $mo_file Path to the MO file.
372 | */
373 | $expire = apply_filters( 'mocache_cache_expire', $expire, $domain, $mo_file );
374 |
375 | // If filters messed up expire, just use a minimum value.
376 | is_int( $expire ) or $expire = MINUTE_IN_SECONDS;
377 |
378 | $set = $expire >= 0 ? wp_cache_set( $key, $cache, self::GROUP, $expire ) : FALSE;
379 |
380 | if ( $set && $domain && 'default' !== $domain ) {
381 | // We store options of all generated keys.
382 | $options = is_array( self::$options ) ? self::$options : get_site_option( self::KEYS_OPTION, [] );
383 | array_key_exists( $domain, $options ) or $options[ $domain ] = [];
384 | in_array( $key, $options[ $domain ], TRUE ) or $options[ $domain ][] = $key;
385 | self::$options = $options;
386 | }
387 | }
388 |
389 | /**
390 | * Set MO in global $l10n array, merging with any existing MO value.
391 | *
392 | * @param string $domain Text domain. Unique identifier for retrieving translated strings.
393 | * @param \MO $mo The path to the mo file.
394 | *
395 | * @return bool
396 | */
397 | private function setup_globals( $domain, \MO $mo ) {
398 |
399 | global $l10n;
400 |
401 | array_key_exists( $domain, (array) $l10n ) and $mo->merge_with( $l10n[ $domain ] );
402 | $l10n[ $domain ] = $mo;
403 |
404 | return TRUE;
405 | }
406 | }
407 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ### GNU GENERAL PUBLIC LICENSE
2 |
3 | Version 2, June 1991
4 |
5 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.
6 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
7 |
8 | Everyone is permitted to copy and distribute verbatim copies
9 | of this license document, but changing it is not allowed.
10 |
11 | ### Preamble
12 |
13 | The licenses for most software are designed to take away your freedom
14 | to share and change it. By contrast, the GNU General Public License is
15 | intended to guarantee your freedom to share and change free
16 | software--to make sure the software is free for all its users. This
17 | General Public License applies to most of the Free Software
18 | Foundation's software and to any other program whose authors commit to
19 | using it. (Some other Free Software Foundation software is covered by
20 | the GNU Lesser General Public License instead.) You can apply it to
21 | your programs, too.
22 |
23 | When we speak of free software, we are referring to freedom, not
24 | price. Our General Public Licenses are designed to make sure that you
25 | have the freedom to distribute copies of free software (and charge for
26 | this service if you wish), that you receive source code or can get it
27 | if you want it, that you can change the software or use pieces of it
28 | in new free programs; and that you know you can do these things.
29 |
30 | To protect your rights, we need to make restrictions that forbid
31 | anyone to deny you these rights or to ask you to surrender the rights.
32 | These restrictions translate to certain responsibilities for you if
33 | you distribute copies of the software, or if you modify it.
34 |
35 | For example, if you distribute copies of such a program, whether
36 | gratis or for a fee, you must give the recipients all the rights that
37 | you have. You must make sure that they, too, receive or can get the
38 | source code. And you must show them these terms so they know their
39 | rights.
40 |
41 | We protect your rights with two steps: (1) copyright the software, and
42 | (2) offer you this license which gives you legal permission to copy,
43 | distribute and/or modify the software.
44 |
45 | Also, for each author's protection and ours, we want to make certain
46 | that everyone understands that there is no warranty for this free
47 | software. If the software is modified by someone else and passed on,
48 | we want its recipients to know that what they have is not the
49 | original, so that any problems introduced by others will not reflect
50 | on the original authors' reputations.
51 |
52 | Finally, any free program is threatened constantly by software
53 | patents. We wish to avoid the danger that redistributors of a free
54 | program will individually obtain patent licenses, in effect making the
55 | program proprietary. To prevent this, we have made it clear that any
56 | patent must be licensed for everyone's free use or not licensed at
57 | all.
58 |
59 | The precise terms and conditions for copying, distribution and
60 | modification follow.
61 |
62 | ### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
63 |
64 | **0.** This License applies to any program or other work which
65 | contains a notice placed by the copyright holder saying it may be
66 | distributed under the terms of this General Public License. The
67 | "Program", below, refers to any such program or work, and a "work
68 | based on the Program" means either the Program or any derivative work
69 | under copyright law: that is to say, a work containing the Program or
70 | a portion of it, either verbatim or with modifications and/or
71 | translated into another language. (Hereinafter, translation is
72 | included without limitation in the term "modification".) Each licensee
73 | is addressed as "you".
74 |
75 | Activities other than copying, distribution and modification are not
76 | covered by this License; they are outside its scope. The act of
77 | running the Program is not restricted, and the output from the Program
78 | is covered only if its contents constitute a work based on the Program
79 | (independent of having been made by running the Program). Whether that
80 | is true depends on what the Program does.
81 |
82 | **1.** You may copy and distribute verbatim copies of the Program's
83 | source code as you receive it, in any medium, provided that you
84 | conspicuously and appropriately publish on each copy an appropriate
85 | copyright notice and disclaimer of warranty; keep intact all the
86 | notices that refer to this License and to the absence of any warranty;
87 | and give any other recipients of the Program a copy of this License
88 | along with the Program.
89 |
90 | You may charge a fee for the physical act of transferring a copy, and
91 | you may at your option offer warranty protection in exchange for a
92 | fee.
93 |
94 | **2.** You may modify your copy or copies of the Program or any
95 | portion of it, thus forming a work based on the Program, and copy and
96 | distribute such modifications or work under the terms of Section 1
97 | above, provided that you also meet all of these conditions:
98 |
99 |
100 | **a)** You must cause the modified files to carry prominent notices
101 | stating that you changed the files and the date of any change.
102 |
103 |
104 | **b)** You must cause any work that you distribute or publish, that in
105 | whole or in part contains or is derived from the Program or any part
106 | thereof, to be licensed as a whole at no charge to all third parties
107 | under the terms of this License.
108 |
109 |
110 | **c)** If the modified program normally reads commands interactively
111 | when run, you must cause it, when started running for such interactive
112 | use in the most ordinary way, to print or display an announcement
113 | including an appropriate copyright notice and a notice that there is
114 | no warranty (or else, saying that you provide a warranty) and that
115 | users may redistribute the program under these conditions, and telling
116 | the user how to view a copy of this License. (Exception: if the
117 | Program itself is interactive but does not normally print such an
118 | announcement, your work based on the Program is not required to print
119 | an announcement.)
120 |
121 | These requirements apply to the modified work as a whole. If
122 | identifiable sections of that work are not derived from the Program,
123 | and can be reasonably considered independent and separate works in
124 | themselves, then this License, and its terms, do not apply to those
125 | sections when you distribute them as separate works. But when you
126 | distribute the same sections as part of a whole which is a work based
127 | on the Program, the distribution of the whole must be on the terms of
128 | this License, whose permissions for other licensees extend to the
129 | entire whole, and thus to each and every part regardless of who wrote
130 | it.
131 |
132 | Thus, it is not the intent of this section to claim rights or contest
133 | your rights to work written entirely by you; rather, the intent is to
134 | exercise the right to control the distribution of derivative or
135 | collective works based on the Program.
136 |
137 | In addition, mere aggregation of another work not based on the Program
138 | with the Program (or with a work based on the Program) on a volume of
139 | a storage or distribution medium does not bring the other work under
140 | the scope of this License.
141 |
142 | **3.** You may copy and distribute the Program (or a work based on it,
143 | under Section 2) in object code or executable form under the terms of
144 | Sections 1 and 2 above provided that you also do one of the following:
145 |
146 |
147 | **a)** Accompany it with the complete corresponding machine-readable
148 | source code, which must be distributed under the terms of Sections 1
149 | and 2 above on a medium customarily used for software interchange; or,
150 |
151 |
152 | **b)** Accompany it with a written offer, valid for at least three
153 | years, to give any third party, for a charge no more than your cost of
154 | physically performing source distribution, a complete machine-readable
155 | copy of the corresponding source code, to be distributed under the
156 | terms of Sections 1 and 2 above on a medium customarily used for
157 | software interchange; or,
158 |
159 |
160 | **c)** Accompany it with the information you received as to the offer
161 | to distribute corresponding source code. (This alternative is allowed
162 | only for noncommercial distribution and only if you received the
163 | program in object code or executable form with such an offer, in
164 | accord with Subsection b above.)
165 |
166 | The source code for a work means the preferred form of the work for
167 | making modifications to it. For an executable work, complete source
168 | code means all the source code for all modules it contains, plus any
169 | associated interface definition files, plus the scripts used to
170 | control compilation and installation of the executable. However, as a
171 | special exception, the source code distributed need not include
172 | anything that is normally distributed (in either source or binary
173 | form) with the major components (compiler, kernel, and so on) of the
174 | operating system on which the executable runs, unless that component
175 | itself accompanies the executable.
176 |
177 | If distribution of executable or object code is made by offering
178 | access to copy from a designated place, then offering equivalent
179 | access to copy the source code from the same place counts as
180 | distribution of the source code, even though third parties are not
181 | compelled to copy the source along with the object code.
182 |
183 | **4.** You may not copy, modify, sublicense, or distribute the Program
184 | except as expressly provided under this License. Any attempt otherwise
185 | to copy, modify, sublicense or distribute the Program is void, and
186 | will automatically terminate your rights under this License. However,
187 | parties who have received copies, or rights, from you under this
188 | License will not have their licenses terminated so long as such
189 | parties remain in full compliance.
190 |
191 | **5.** You are not required to accept this License, since you have not
192 | signed it. However, nothing else grants you permission to modify or
193 | distribute the Program or its derivative works. These actions are
194 | prohibited by law if you do not accept this License. Therefore, by
195 | modifying or distributing the Program (or any work based on the
196 | Program), you indicate your acceptance of this License to do so, and
197 | all its terms and conditions for copying, distributing or modifying
198 | the Program or works based on it.
199 |
200 | **6.** Each time you redistribute the Program (or any work based on
201 | the Program), the recipient automatically receives a license from the
202 | original licensor to copy, distribute or modify the Program subject to
203 | these terms and conditions. You may not impose any further
204 | restrictions on the recipients' exercise of the rights granted herein.
205 | You are not responsible for enforcing compliance by third parties to
206 | this License.
207 |
208 | **7.** If, as a consequence of a court judgment or allegation of
209 | patent infringement or for any other reason (not limited to patent
210 | issues), conditions are imposed on you (whether by court order,
211 | agreement or otherwise) that contradict the conditions of this
212 | License, they do not excuse you from the conditions of this License.
213 | If you cannot distribute so as to satisfy simultaneously your
214 | obligations under this License and any other pertinent obligations,
215 | then as a consequence you may not distribute the Program at all. For
216 | example, if a patent license would not permit royalty-free
217 | redistribution of the Program by all those who receive copies directly
218 | or indirectly through you, then the only way you could satisfy both it
219 | and this License would be to refrain entirely from distribution of the
220 | Program.
221 |
222 | If any portion of this section is held invalid or unenforceable under
223 | any particular circumstance, the balance of the section is intended to
224 | apply and the section as a whole is intended to apply in other
225 | circumstances.
226 |
227 | It is not the purpose of this section to induce you to infringe any
228 | patents or other property right claims or to contest validity of any
229 | such claims; this section has the sole purpose of protecting the
230 | integrity of the free software distribution system, which is
231 | implemented by public license practices. Many people have made
232 | generous contributions to the wide range of software distributed
233 | through that system in reliance on consistent application of that
234 | system; it is up to the author/donor to decide if he or she is willing
235 | to distribute software through any other system and a licensee cannot
236 | impose that choice.
237 |
238 | This section is intended to make thoroughly clear what is believed to
239 | be a consequence of the rest of this License.
240 |
241 | **8.** If the distribution and/or use of the Program is restricted in
242 | certain countries either by patents or by copyrighted interfaces, the
243 | original copyright holder who places the Program under this License
244 | may add an explicit geographical distribution limitation excluding
245 | those countries, so that distribution is permitted only in or among
246 | countries not thus excluded. In such case, this License incorporates
247 | the limitation as if written in the body of this License.
248 |
249 | **9.** The Free Software Foundation may publish revised and/or new
250 | versions of the General Public License from time to time. Such new
251 | versions will be similar in spirit to the present version, but may
252 | differ in detail to address new problems or concerns.
253 |
254 | Each version is given a distinguishing version number. If the Program
255 | specifies a version number of this License which applies to it and
256 | "any later version", you have the option of following the terms and
257 | conditions either of that version or of any later version published by
258 | the Free Software Foundation. If the Program does not specify a
259 | version number of this License, you may choose any version ever
260 | published by the Free Software Foundation.
261 |
262 | **10.** If you wish to incorporate parts of the Program into other
263 | free programs whose distribution conditions are different, write to
264 | the author to ask for permission. For software which is copyrighted by
265 | the Free Software Foundation, write to the Free Software Foundation;
266 | we sometimes make exceptions for this. Our decision will be guided by
267 | the two goals of preserving the free status of all derivatives of our
268 | free software and of promoting the sharing and reuse of software
269 | generally.
270 |
271 | **NO WARRANTY**
272 |
273 | **11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO
274 | WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
275 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
276 | OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY
277 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
278 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
279 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
280 | PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME
281 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
282 |
283 | **12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
284 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
285 | AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU
286 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
287 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
288 | PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
289 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
290 | FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF
291 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
292 | DAMAGES.
293 |
294 | ### END OF TERMS AND CONDITIONS
295 |
296 | ### How to Apply These Terms to Your New Programs
297 |
298 | If you develop a new program, and you want it to be of the greatest
299 | possible use to the public, the best way to achieve this is to make it
300 | free software which everyone can redistribute and change under these
301 | terms.
302 |
303 | To do so, attach the following notices to the program. It is safest to
304 | attach them to the start of each source file to most effectively
305 | convey the exclusion of warranty; and each file should have at least
306 | the "copyright" line and a pointer to where the full notice is found.
307 |
308 | one line to give the program's name and an idea of what it does.
309 | Copyright (C) yyyy name of author
310 |
311 | This program is free software; you can redistribute it and/or
312 | modify it under the terms of the GNU General Public License
313 | as published by the Free Software Foundation; either version 2
314 | of the License, or (at your option) any later version.
315 |
316 | This program is distributed in the hope that it will be useful,
317 | but WITHOUT ANY WARRANTY; without even the implied warranty of
318 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
319 | GNU General Public License for more details.
320 |
321 | You should have received a copy of the GNU General Public License
322 | along with this program; if not, write to the Free Software
323 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
324 |
325 | Also add information on how to contact you by electronic and paper
326 | mail.
327 |
328 | If the program is interactive, make it output a short notice like this
329 | when it starts in an interactive mode:
330 |
331 | Gnomovision version 69, Copyright (C) year name of author
332 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
333 | type `show w'. This is free software, and you are welcome
334 | to redistribute it under certain conditions; type `show c'
335 | for details.
336 |
337 | The hypothetical commands \`show w' and \`show c' should show the
338 | appropriate parts of the General Public License. Of course, the
339 | commands you use may be called something other than \`show w' and
340 | \`show c'; they could even be mouse-clicks or menu items--whatever
341 | suits your program.
342 |
343 | You should also get your employer (if you work as a programmer) or
344 | your school, if any, to sign a "copyright disclaimer" for the program,
345 | if necessary. Here is a sample; alter the names:
346 |
347 | Yoyodyne, Inc., hereby disclaims all copyright
348 | interest in the program `Gnomovision'
349 | (which makes passes at compilers) written
350 | by James Hacker.
351 |
352 | signature of Ty Coon, 1 April 1989
353 | Ty Coon, President of Vice
354 |
355 | This General Public License does not permit incorporating your program
356 | into proprietary programs. If your program is a subroutine library,
357 | you may consider it more useful to permit linking proprietary
358 | applications with the library. If this is what you want to do, use the
359 | [GNU Lesser General Public
360 | License](https://www.gnu.org/licenses/lgpl.html) instead of this
361 | License.
362 |
--------------------------------------------------------------------------------