<?php /** * Plugin Name: POMOdoro Translation Cache * Description: A cached translation override for WordPress. * Plugin URI: https://github.com/pressjitsu/pomodoro/ * * Bakes and stows away expensive translation lookups * as PHP hashtables. Fast and beautiful. * * GPL3 * Pressjitsu, Inc. * https://pressjitsu.com */ namespace Pressjitsu\Pomodoro; add_filter( 'override_load_textdomain', function( $plugin_override, $domain, $mofile ) { if ( ! is_readable( $mofile ) ) return false; global $l10n; $mo = new MoCache_Translation( $mofile, $domain, $upstream = empty( $l10n[ $domain ] ) ? null : $l10n[ $domain ] ); $l10n[ $domain ] = $mo; return true; }, 999, 3 ); class MoCache_Translation { /** * Private state. */ private $domain = null; private $cache = array(); private $busted = false; private $override = null; private $upstream = null; private $mofile = null; /** * Cache file end marker. */ private $end = 'POMODORO_END_e867edfb-4a36-4643-8ad4-b95507068e44'; /** * Construct the main translation cache instance for a domain. * * @param string $mofile The path to the mo file. * @param string $domain The textdomain. * @param Translations $merge The class in the same domain, we have overriden it. */ public function __construct( $mofile, $domain, $override ) { $this->mofile = apply_filters( 'load_textdomain_mofile', $mofile, $domain ); $this->domain = $domain; $this->override = $override; $temp_dir = get_temp_dir(); $filename = md5( serialize( array( get_home_url(), $this->domain, $this->mofile ) ) ); if ( defined( 'POMODORO_CACHE_DIR' ) && POMODORO_CACHE_DIR && wp_mkdir_p( POMODORO_CACHE_DIR ) ) { $temp_dir = POMODORO_CACHE_DIR; } $cache_file = sprintf( '%s/%s.mocache', untrailingslashit( $temp_dir ), $filename ); $mtime = filemtime( $this->mofile ); if ( $file_exists = file_exists( $cache_file ) ) { /** * Load cache. * * OPcache will grab the values from memory. */ include $cache_file; $this->cache = &$_cache; /** * Mofile has been modified, invalidate it all. */ if ( ! isset( $_mtime ) || ( isset( $_mtime ) && $_mtime < $mtime ) ) { $this->cache = array(); } } $_this = &$this; register_shutdown_function( function() use ( $cache_file, $_this, $mtime, $domain, $file_exists ) { /** * New values have been found. Dump everything into a valid PHP script. */ if ( $_this->busted || ( empty( $_this->cache ) && ! $file_exists ) ) { file_put_contents( "$cache_file.test", sprintf( '<?php $_mtime = %d; $_domain = %s; $_cache = %s; // %s', $mtime, var_export( $domain, true ), var_export( $_this->cache, true ), $this->end ), LOCK_EX ); // Test the file before committing. $fp = fopen( "$cache_file.test", 'rb' ); fseek( $fp, -strlen( $_this->end ), SEEK_END ); if ( fgets( $fp ) == $_this->end ) { rename( "$cache_file.test", $cache_file ); } else { trigger_error( "pomodoro $cache_file.test cache file missing end marker." ); unlink( "$cache_file.test" ); } fclose( $fp ); } } ); } private function get_translation( $cache_key, $text, $args ) { /** * Check cache first. */ if ( isset( $this->cache[ $cache_key ] ) ) { return $this->cache[ $cache_key ]; } /** * Bust it. */ $this->busted = true; $translate_function = count( $args ) > 2 ? 'translate_plural' : 'translate'; /** * Merge overrides. */ if ( $this->override ) { return $this->cache[ $cache_key ] = call_user_func_array( array( $this->override, $translate_function ), $args ); } /** * Default Mo upstream. */ if ( ! $this->upstream ) { $this->upstream = new \Mo(); do_action( 'load_textdomain', $this->domain, $this->mofile ); $this->upstream->import_from_file( $this->mofile ); } return $this->cache[ $cache_key ] = call_user_func_array( array( $this->upstream, $translate_function ), $args ); } /** * The translate() function implementation that WordPress calls. */ public function translate( $text, $context = null ) { return $this->get_translation( $this->cache_key( func_get_args() ), $text, func_get_args() ); } /** * The translate_plural() function implementation that WordPress calls. */ public function translate_plural( $singular, $plural, $count, $context = null ) { $text = ( abs( $count ) == 1 ) ? $singular : $plural; return $this->get_translation( $this->cache_key( array( $text, $count, $context ) ), $text, func_get_args() ); } /** * Cache key calculator. */ private function cache_key( $args ) { return md5( serialize( array( $args, $this->domain ) ) ); } }