TranslateBuffer.php 0000666 00000004404 15213300116 0010342 0 ustar 00 buffer[$domain][$msgid] = null;
return $msgstr;
}
/**
* `gettext_with_context` filter callback
*/
public function filter_gettext_with_context( $msgstr, $msgid, $msgctxt, $domain ){
$this->buffer[$domain][$msgctxt."\x04".$msgid] = null;
return $msgstr;
}
/**
* `ngettext` filter callback
*/
public function filter_ngettext( $msgstr, $msgid, $msgid_plural, $number, $domain ){
$this->buffer[$domain][$msgid] = null;
return $msgstr;
}
/**
* `ngettext_with_context` filter callback
*/
function filter_ngettext_with_context( $msgstr, $msgid, $msgid_plural, $number, $msgctxt, $domain ){
$this->buffer[$domain][$msgctxt."\x04".$msgid] = null;
return $msgstr;
}
/**
* Export all captured translations in a raw form and reset buffer
* @param string the specific domain listened for
* @return array
*/
public function flush( $domain ){
$export = array();
if( isset($this->buffer[$domain]) ){
// what we captures was just a unique namespace
$captured = $this->buffer[$domain];
unset($this->buffer[$domain]);
// process raw data for all that actually exist
// this survives on WordPress internals not changing :-/
$loaded = get_translations_for_domain($domain);
if( $loaded instanceof Translations && is_array($loaded->entries) ){
$entries = array_intersect_key( $loaded->entries, $captured );
/* @var $entry Translation_Entry */
foreach( $entries as $key => $entry ){
$export[$key] = $entry->translations;
}
}
}
return $export;
}
}
AdminHooks.php 0000666 00000013011 15213300116 0007301 0 ustar 00
Error: Loco Translate failed to start up
';
}
/**
* {@inheritdoc}
*/
public function __construct(){
// renders failure notice if plugin failed to start up admin hooks.
add_action( 'admin_notices', array(__CLASS__,'print_hook_failure') );
// initialize hooks
parent::__construct();
// Ajax router will be called directly in tests
// @codeCoverageIgnoreStart
if( loco_doing_ajax() ){
$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : '';
// initialize Ajax router before hook fired so we can handle output buffering
if( 'loco_' === substr($action,0,5) && isset($_REQUEST['route']) ){
$this->router = new Loco_mvc_AjaxRouter;
Loco_package_Listener::create();
}
}
// @codeCoverageIgnoreEnd
// page router required on all pages as it hooks in the menu
else {
$this->router = new Loco_mvc_AdminRouter;
// we don't know we will render a page yet, but we need to listen for text domain hooks as early as possible
if( isset($_GET['page']) && 'loco' === substr($_GET['page'],0,4) ){
Loco_package_Listener::create();
// trigger post-upgrade process if required
$opts = Loco_data_Settings::get();
$opts->migrate();
}
}
}
/**
* "admin_init" callback.
*/
public function on_admin_init(){
// currently no better hook than `admin_init` for adding privacy statement.
// could use "load-tools.php" action, but WordPress could change that in future.
// this should fire just before WP_Privacy_Policy_Content::privacy_policy_guide is called
if( function_exists('wp_add_privacy_policy_content') ) {
$url = apply_filters('loco_external','https://localise.biz/wordpress/plugin/privacy');
wp_add_privacy_policy_content(
__('Loco Translate','loco-translate'),
esc_html( __("This plugin doesn't collect any data from public website visitors.",'loco-translate') ).'
'.
sprintf( __('Administrators and auditors may wish to review Loco\'s plugin privacy notice.','loco-translate'), esc_url($url) )
);
}
}
/**
* "admin_menu" callback.
*/
public function on_admin_menu(){
// This earliest we need translations, and admin user locale should be set by now
if( $this->router ){
$domainPath = dirname( loco_plugin_self() ).'/languages';
load_plugin_textdomain( 'loco-translate', false, $domainPath );
}
// Unhook failure notice that would fire if this hook was not successful
remove_action( 'admin_notices', array(__CLASS__,'print_hook_failure') );
}
/**
* plugin_action_links action callback
* @param string[]
* @param string
* @return string[]
*/
public function on_plugin_action_links( $links, $plugin = '' ){
try {
if( $plugin && current_user_can('loco_admin') && Loco_package_Plugin::get_plugin($plugin) ){
// coerce links to array
if( ! is_array($links) ){
$links = $links && is_string($links) ? (array) $links : array();
}
// ok to add "translate" link into meta row
$href = Loco_mvc_AdminRouter::generate('plugin-view', array( 'bundle' => $plugin) );
$links[] = ''.esc_html__('Translate','loco-translate').'';
}
}
catch( Exception $e ){
// $links[] = esc_html( 'Debug: '.$e->getMessage() );
}
return $links;
}
/**
* Purge in-memory caches that may be persisted by object caching plugins
*/
private function purge_wp_cache(){
global $wp_object_cache;
if( function_exists('wp_cache_delete') && method_exists($wp_object_cache,'delete') ){
wp_cache_delete('plugins','loco');
}
}
/**
* pre_update_option_{$option} filter callback for $option = "active_plugins"
* @param array active plugins
* @return array
*/
public function filter_pre_update_option_active_plugins( $value = null ){
$this->purge_wp_cache();
return $value;
}
/**
* pre_update_site_option_{$option} filter callback for $option = "active_sitewide_plugins"
* @param array active sitewide plugins
* @return array
*/
public function filter_pre_update_site_option_active_sitewide_plugins( $value = null ){
$this->purge_wp_cache();
return $value;
}
/**
* deactivate_plugin action callback
*
public function on_deactivate_plugin( $plugin, $network = false ){
if( loco_plugin_self() === $plugin ){
// TODO flush all our transient cache entries
// "DELETE FROM ___ WHERE `option_name` LIKE '_transient_loco_%' OR `option_name` LIKE '_transient_timeout_loco_%'";
}
}*/
/*public function filter_all( $hook ){
error_log( $hook, 0 );
}*/
}
Hookable.php 0000666 00000004541 15213300116 0007001 0 ustar 00 getMethods( ReflectionMethod::IS_PUBLIC ) as $method ){
$func = $method->name;
// support filter_{filter_hook} methods
if( 0 === strpos($func,'filter_' ) ) {
$hook = substr( $func, 7 );
}
// support on_{action_hook} methods
else if( 0 === strpos($func,'on_' ) ){
$hook = substr( $func, 3 );
}
else {
continue;
}
// this goes to 11 so we run after system defaults
$priority = 11;
// support @priority tag in comment block (uncomment if needed)
/*if( ( $docblock = $method->getDocComment() ) && ( $offset = strpos($docblock,'@priority ') ) ){
preg_match( '/^\d+/', substr($docblock,$offset+10), $r ) and
$priority = (int) $r[0];
}*/
// call add_action or add_filter with required arguments and hook is registered
// add_action actually calls add_filter, although unsure how long that's been the case.
$num_args = $method->getNumberOfParameters();
add_filter( $hook, array( $this, $func ), $priority, $num_args );
// register hook for destruction so object can be removed from memory
$reg[] = array( $hook, $func, $priority );
}
$this->hooks = $reg;
}
/**
* Deregister active hooks.
* We can't use __destruct because instances persist in WordPress hook registry
*/
public function unhook(){
if( is_array($this->hooks) ){
foreach( $this->hooks as $r ){
remove_filter( $r[0], array($this,$r[1]), $r[2] );
}
}
$this->hooks = null;
}
} LoadHelper.php 0000666 00000010272 15213300116 0007272 0 ustar 00 context = array( 'themes', $domain, $locale );
unset( $this->lock[$domain] );
return $locale;
}
/**
* `plugin_locale` filter callback.
* Signals the beginning of a "load_plugin_textdomain" process
* @param string
* @param string
* @return string
*/
public function filter_plugin_locale( $locale, $domain = '' ){
$this->context = array( 'plugins', $domain, $locale );
unset( $this->lock[$domain] );
return $locale;
}
/**
* `unload_textdomain` action callback.
* Lets us release lock so that custom file may be loaded again (hopefully for another locale)
* @param string
* @return void
*/
public function on_unload_textdomain( $domain ){
unset( $this->lock[$domain] );
}
/**
* `load_textdomain` action callback.
* Lets us load our custom translations before WordPress loads what it was going to anyway.
* We're deliberately not stopping WordPress loading $mopath, if it exists it will be merged on top of our custom strings.
* @param string
* @param string
* @return void
*/
public function on_load_textdomain( $domain, $mopath ){
$key = '';
// domains may be split into multiple files
$name = pathinfo( $mopath, PATHINFO_FILENAME );
if( $lpos = strrpos( $name, '-') ){
$slug = substr( $name, 0, $lpos );
if( $slug !== $domain ){
$key = $slug;
}
}
// avoid recursion when we've already handled this domain/slug
if( isset($this->lock[$domain][$key]) ){
return;
}
// language roots
$wp_lang_dir = trailingslashit( loco_constant('WP_LANG_DIR') );
$lc_lang_dir = trailingslashit( loco_constant('LOCO_LANG_DIR') );
// if context is set, then a theme or plugin initialized the loading process properly
if( is_array($this->context) ){
list( $subdir, $_domain, $locale ) = $this->context;
$this->context = null;
// It shouldn't be possible to catch a different domain after setting context, but we'd better bail just in case
if( $_domain !== $domain ){
return;
}
$mopath = $lc_lang_dir.$subdir.'/'.$domain.'-'.$locale.'.mo';
}
// else load_textdomain must have been called directly to bypass locale filters
else {
$snip = strlen($wp_lang_dir);
// direct file loads must be under WP_LANG_DIR if we are to map them
if( substr( dirname($mopath).'/', 0, $snip ) === $wp_lang_dir ){
$mopath = substr_replace( $mopath, $lc_lang_dir, 0, $snip );
}
// else no way to map files from WP_LANG_DIR to LOCO_LANG_DIR
else {
return;
}
}
// Load our custom translations avoiding recursion back into this hook
$this->lock[$domain][$key] = true;
load_textdomain( $domain, $mopath );
}
/**
* `load_textdomain_mofile` filter callback
* @param string
* @param string
* @return string
*/
public function filter_load_textdomain_mofile( $mopath, $domain ){
// 2.0.14 changed text domain from "loco" to "loco-translate"
// so if file doesn't exist, there's no harm in trying the legacy file name
if( 'loco-translate' === $domain && ! file_exists($mopath) ){
$mopath = str_replace('/loco-translate-','/loco-',$mopath);
}
return $mopath;
}
}