',$label,': ',Loco::html($message),'
'; } /** * Print warning notice */ public static function warning( $message, $label = '' ){ if( did_action('admin_notices') ){ $label or $label = _x('Warning','Message label','loco-legacy'); echo ' '; } else { self::$notices[] = array( __FUNCTION__, func_get_args() ); } } /** * Print success */ public static function success( $message, $label = '' ){ $label or $label = _x('OK','Message label','loco-legacy'); echo ' '; } /** * Exit forbidden */ private static function forbid(){ wp_die( __('Permission denied','loco-legacy'), 'Forbidden', array('response' => 403 ) ); trigger_error('wp_die failure', E_USER_ERROR ); exit(); } /** * Check current user has permission to access Loco admin screens, or exit forbidden */ private static function check_capability(){ current_user_can( Loco::admin_capablity() ) or self::forbid(); } /** * Admin settings page render call */ public static function render_page_options(){ self::check_capability(); // update application settings if posted if( isset($_POST['loco']) && is_array( $update = $_POST['loco'] ) ){ $update += array( 'gen_hash' => '0', 'use_fuzzy' => '0', 'enable_core' => '0' ); $args = Loco::config( $update ); $args['success'] = __('Settings saved','loco-legacy'); } else { $args = Loco::config(); } // establish a default msgfmt if required and possible if( $args['use_msgfmt'] && ! $args['which_msgfmt'] ){ function_exists('loco_find_executable') or loco_require('build/shell-compiled'); $args['which_msgfmt'] = loco_find_executable('msgfmt');// and Loco::config( $args ); } Loco::enqueue_scripts('build/admin-common'); Loco::render('admin-opts', $args ); } /** * Admin soft upgrade page */ public static function render_soft_upgrade(){ $args = array ( 'home' => self::uri(), 'nonce' => wp_create_nonce('upgrade-v2'), ); Loco::render('admin-upgrade', $args ); } /** * Admin tools page render call */ public static function render_page_tools(){ self::check_capability(); do { try { // libs required for all manage translation pages loco_require('loco-locales','loco-packages'); // most actions except root listing define a single package by name and type $package = null; if( isset($_GET['name']) && isset($_GET['type']) ){ $package = LocoPackage::get( $_GET['name'], $_GET['type'] ); } // Extract messages if 'xgettext' is in query string // if( isset($_GET['xgettext']) ){ $domain = $_GET['xgettext']; if( $pot_path = $package->get_pot($domain) ){ throw new Exception('POT already exists at '.$pot_path ); } // Establish best/intended location for new POT file $dir = $package->lang_dir( $domain ); $pot_path = $dir.'/'.$domain.'.pot'; $export = self::xgettext( $package, $dir ); self::render_poeditor( $package, $pot_path, $export ); break; } // Initialize a new PO file if 'msginit' is in query string // if( isset($_GET['msginit']) ){ $domain = $_GET['msginit']; $force_global = isset($_GET['gforce']) ? (bool) $_GET['gforce'] : null; // handle PO file creation if locale is set if( isset($_GET['custom-locale']) ){ try { $locale = $_GET['custom-locale'] or $locale = $_GET['common-locale']; $po_path = self::msginit( $package, $domain, $locale, $export, $head, $force_global ); if( $po_path ){ self::render_poeditor( $package, $po_path, $export, $head ); break; } } catch( Exception $Ex ){ // fall through to msginit screen with error self::error( $Ex->getMessage() ); } } // else do a dry run to pre-empt failures and allow manual alteration of target path $path = self::msginit( $package, $domain, 'zz_ZZ', $export, $head, $force_global ); // get alternative location options $pdir = $package->lang_dir( $domain, true ); $gdir = $package->global_lang_dir(); $pdir_ok = is_writeable($pdir); $gdir_ok = is_writeable($gdir); $is_global = $package->is_global_path( $path ); // warn about unwriteable locations? // render msginit start screen $title = __('New PO file','loco-legacy'); $locales = LocoLocale::get_names(); Loco::enqueue_scripts( 'build/admin-common', 'build/admin-poinit'); Loco::render('admin-poinit', compact('package','domain','title','locales','path','pdir','gdir','pdir_ok','gdir_ok','is_global') ); break; } // Render existing file in editor if 'poedit' contains a valid file path relative to content directory // if( isset($_GET['poedit']) && $po_path = self::resolve_path( $_GET['poedit'] ) ){ $export = self::parse_po_with_headers( $po_path, $head ); // support incorrect usage of PO files as templates if( isset($_GET['pot']) && ! self::is_pot($po_path) ){ $po_path = dirname($po_path).'/'.$_GET['pot'].'.pot'; self::warning( sprintf( __('PO file used as template. This will be renamed to %s on first save','loco-legacy'), basename($po_path) ) ); } self::render_poeditor( $package, $po_path, $export, $head ); break; } // Show filesystem check if 'fscheck' in query // if( isset($_GET['fscheck']) ){ $args = $package->meta() + compact('package'); Loco::enqueue_scripts('build/admin-common'); Loco::render('admin-fscheck', $args ); break; } } catch( Exception $Ex ){ self::error( $Ex->getMessage() ); } // default screen renders root page with available themes and plugins to translate // @var WP_Theme $theme $themes = array(); foreach( wp_get_themes( array( 'allowed' => true ) ) as $name => $theme ){ $package = LocoPackage::get( $name, 'theme' ) and $name = $package->get_name(); $themes[ $name ] = $package; } // @var array $plugin $plugins = array(); foreach( get_plugins() as $plugin_file => $plugin ){ $package = LocoPackage::get( $plugin_file, 'plugin' ) and $plugins[] = $package; } // @var array $core $core = array(); $conf = Loco::config(); if( ! empty($conf['enable_core']) ){ foreach( LocoPackage::get_core_packages() as $package ){ // if package has no PO or POT we skip it because core packages have no source if( $package->get_po() || $package->get_pot() ){ $core[] = $package; } } } // order most active packges first in each set $args = array ( 'themes' => LocoPackage::sort_modified( $themes ), 'plugins' => LocoPackage::sort_modified( $plugins ), 'core' => LocoPackage::sort_modified( $core ), ); // upgrade notice if( $updates = get_site_transient('update_plugins') ){ $key = Loco::NS.'/loco.php'; if( isset($updates->checked[$key]) && isset($updates->response[$key]) ){ $old = $updates->checked[$key]; $new = $updates->response[$key]->new_version; if( 1 === version_compare( $new, $old ) ){ // current version is lower than latest $args['update'] = $new; } } } Loco::enqueue_scripts('build/admin-common'); Loco::render('admin-root', $args ); } while( false ); } /** * utility gets newest file modification from an array of files */ private static function newest_mtime_recursive( array $files ){ $mtime = 0; foreach( func_get_args() as $files ){ foreach( $files as $path ){ $mtime = max( $mtime, filemtime($path) ); } } return $mtime; } /** * Initialize a new PO file from a locale code * @return string path where PO file will be saved to */ private static function msginit( LocoPackage $package, $domain = '', $code, &$export, &$head, $force_global = null ){ $head = null; $export = array(); $locale = $code ? loco_locale_resolve($code) : null; if( ! $locale ){ throw new Exception( __('You must specify a valid locale for a new PO file','loco-legacy') ); } // default PO file location $po_path = $package->create_po_path( $locale, $domain, $force_global ); $po_dir = dirname( $po_path ); $po_name = basename( $po_path ); // extract strings from POT if possible if( $pot_path = $package->get_pot($domain) ){ $pot = self::parse_po_with_headers( $pot_path, $head ); if( $pot && ! ( 1 === count($pot) && '' === $pot[0]['source'] ) ){ $export = $pot; $pot_dir = dirname( $pot_path ); // override default PO location if POT location is writable and getting best location if( is_writable($pot_dir) && is_null($force_global) ){ $po_dir = $pot_dir; } } } // else extract strings from source code when no POT if( ! $export ){ $export = self::xgettext( $package, $po_dir ); if( ! $export ){ throw new Exception( __('No translatable strings found','loco-legacy').'. '.__('Cannot create a PO file.','loco-legacy') ); } } // check for PO conflict as this is msginit, not a sync. $po_path = $po_dir.'/'.$po_name; if( file_exists($po_path) ){ throw new Exception( sprintf(__('PO file already exists with locale %s','loco-legacy'), $locale->get_code() ) ); } // return path, export and head set as references $head or $head = new LocoHeaders; return $po_path; } /** * Render poedit screen * @param string optional package root directory * @param string PO or PO file path * @param array data to load into editor */ private static function render_poeditor( LocoPackage $package, $path, array $data, LocoHeaders $head = null ){ $pot = $po = $locale = null; $warnings = array(); // remove header and check if empty $minlength = 1; if( isset($data[0]['source']) && $data[0]['source'] === '' ){ $data[0] = array(); $minlength = 2; } // path may not exist if we're creating a new one if( file_exists($path) ){ $modified = self::format_datetime( filemtime($path) ); } else { $modified = 0; } if( $is_pot = self::is_pot($path) ){ $pot = $data; $type = 'POT'; } // else PO is locked and has a locale else { $po = $data; $type = 'PO'; $locale = self::resolve_file_locale($path); $domain = self::resolve_file_domain($path); $haspot = $package->get_pot( $domain ); } // warn if new file can't be written $writable = self::is_writable( $path ); if( ! $writable && ! $modified ){ $warnings[] = __('File cannot be created automatically. Fix the file permissions or use Download instead of Save','loco-legacy'); } // Warnings if file is empty if( count($data) < $minlength ){ $lines = array(); if( $is_pot ){ if( $modified ){ // existing POT, may need sync $lines[] = sprintf( __('%s file is empty','loco-legacy'), 'POT' ); $lines[] = __('Run Sync to update from source code','loco-legacy'); } else { // new POT, would have tried to extract from source. Fine you can add by hand $lines[] = __('No strings could be extracted from source code','loco-legacy'); } } else if( $modified ){ $lines[] = sprintf( __('%s file is empty','loco-legacy'), 'PO' ); if( $haspot ){ // existing PO that might be updatable from POT $lines[] = sprintf( __('Run Sync to update from %s','loco-legacy'), basename($haspot) ); } else { // existing PO that might be updatable from sources $lines[] = __('Run Sync to update from source code','loco-legacy'); } } else { // this shouldn't happen if we throw an error during msginit throw new Exception( __('No translatable strings found','loco-legacy') ); } $warnings[] = implode('. ', $lines ); } // warning if file needs syncing else if( $modified ){ if( $is_pot ){ $sources = $package->get_source_files(); if( $sources && filemtime($path) < self::newest_mtime_recursive($sources) ){ $warnings[] = __('Source code has been modified, run Sync to update POT','loco-legacy'); } } else if( $haspot && filemtime($haspot) > filemtime($path) ){ $warnings[] = __('POT has been modified since PO file was saved, run Sync to update','loco-legacy'); } } // extract some PO headers if( $head instanceof LocoHeaders ){ $proj = $head->trimmed('Project-Id-Version'); if( $proj && 'PACKAGE VERSION' !== $proj ){ $name = $proj; } } else { $head = new LocoHeaders; } // set Last-Translator if PO file if( ! $is_pot ){ /* @var WP_User $user */ $user = wp_get_current_user() and $head->add( 'Last-Translator', $user->get('display_name').' <'.$user->get('user_email').'>' ); } // overwrite source location headers // create a relative path to target source directory from location of PO if( ! $head->has('X-Poedit-Basepath') ){ $head->add('X-Poedit-Basepath', '.' ); foreach( $package->get_source_dirs($path) as $i => $dir ){ $dir or $dir = '.'; $head->add('X-Poedit-SearchPath-'.$i, $dir ); } } // compiled keywords for running source extraction in POEdit // note that these aren't just wordpress keywords, but they're the same as we're using in self::xgettext $ext = new LocoPHPExtractor; $head->add('X-Poedit-KeywordsList', implode( ';', $ext->get_xgettext_keywords() ) ); // ensure nice name for project if( ! isset($name) ){ $meta = $package->meta(); $name = $meta['name']; } $head->add( 'Project-Id-Version', $name ); $headers = $head->export(); // no longer need the full local paths $path = self::trim_path( $path ); // If parsing MO file, from now on treat as PO if( ! $is_pot && self::is_mo($path) ){ $path = str_replace( '.mo', '.po', $path ); } Loco::enqueue_scripts('build/admin-common','build/admin-poedit'); Loco::render('admin-poedit', compact('package','path','po','pot','locale','headers','name','type','modified','writable','warnings') ); return true; } /** * Test if a file path is a POT (template) file */ public static function is_pot( $path ){ return 'pot' === strtolower( pathinfo($path,PATHINFO_EXTENSION) ); } /** * Test if a file path is a MO (compiled) file */ public static function is_mo( $path ){ return 'mo' === strtolower( pathinfo($path,PATHINFO_EXTENSION) ); } /** * Test if a file path is a PO file */ public static function is_po( $path ){ return 'po' === strtolower( pathinfo($path,PATHINFO_EXTENSION) ); } /** * resolve file path that may be relative to wp-content */ public static function resolve_path( $path, $isdir = false ){ if( $path && '/' !== $path{0} ){ $path = WP_CONTENT_DIR.'/'.$path; } $realpath = realpath( $path ); if( ! $realpath || ! is_readable($realpath) || ( $isdir && ! is_dir($realpath) ) || ( ! $isdir && ! is_file($realpath) ) ){ self::error( __('Bad file path','loco-legacy').' '.var_export($path,1) ); return ''; } // returning original path in case something was symlinked outside the web root return $path; } /** * remove wp-content from path for more compact display in urls and such */ public static function trim_path( $path ){ return str_replace( WP_CONTENT_DIR.'/', '', $path ); } /** * Test whether a file can be written to, whether it exists or not */ public static function is_writable( $path ){ // if file exists it must be writable itself: if( file_exists($path) ){ return is_writable($path); } // else file must be created, which may mean recursive directory permissions $dir = dirname( $path ); return is_dir($dir) && is_writable($dir); } /** * Recursively find PO and POT files under WP_LANG_DIR (wp-content/languages) * Then remove them so after all packages are processed we can pick up orphans. */ public static function pop_lang_dir( $domain = '', $filtered = array() ){ static $found; if( ! isset($found) ){ $found = array(); if( is_dir(WP_LANG_DIR) ){ $found = self::find_po( WP_LANG_DIR ); } } if( ! $domain ){ return $found; } foreach( $found as $ext => $paths ){ isset($filtered[$ext]) or $filtered[$ext] = array(); foreach( $paths as $i => $path ){ if( 0 === strpos( basename($path), $domain.'-' ) ){ $filtered[$ext][] = $path; unset( $found[$ext][$i] ); } } } return $filtered; } /** * Recursively find all PO and POT files anywhere under a directory */ public static function find_po( $dir ){ return self::find( $dir, array('po','pot') ); } /** * Recursively find all MO files anywhere under a directory */ public static function find_mo( $dir ){ $files = self::find( $dir, array('mo') ); return $files['mo']; } /** * Recursively find all POT files anywhere under a directory */ public static function find_pot( $dir ){ $files = self::find( $dir, array('pot') ); return $files['pot']; } /** * Recursively find all PHP source files anywhere under a directory */ public static function find_php( $dir ){ $files = self::find( $dir, array('php','phtml') ); return array_merge($files['php'], $files['phtml']); } /** * Recursively find files of any given extensions */ private static function find( $dir, array $exts ){ $found = array_fill_keys( $exts, array() ); if( is_readable($dir) && is_dir($dir) ){ $match = '/\\.(?:'.implode('|',$exts).')$/'; $found = self::find_grouped( $dir, $match, $found, true ); } return $found; } /** * @internal */ public static function find_grouped( $dir, $match, array $found = array(), $recurse = false, $recursions = array() ){ if( is_readable($dir) && is_dir($dir) && ( $rs = opendir($dir) ) ){ while( $f = readdir($rs) ){ if( '.' === $f{0} ){ continue; } $path = $dir.'/'.$f; if( ! file_exists($path) ){ // likely to be a symlink to outside PHP's open_basedir. file_exists call will have raised E_WARNING continue; } if( is_link($path) ){ $path = realpath($path); if( ! $path ){ continue; } } if( is_dir($path) ){ if( $recurse && ! isset($recursions[$path]) ){ $recursions[$path] = true; $found = self::find_grouped( $path, $match, $found, true, $recursions ); } } else if( ! $match || preg_match($match,$path) ){ $ext = strtolower( pathinfo($path,PATHINFO_EXTENSION ) ); $found[$ext][] = $path; } } closedir($rs); } return $found; } /** * Perform xgettext style extraction from PHP source files * @todo JavaScript files too * @todo filter on TextDomain? * @return array Loco's internal array format */ public static function xgettext( LocoPackage $package, $relative_to = '' ){ class_exists('LocoPHPExtractor') or loco_require('build/gettext-compiled'); $extractor = new LocoPHPExtractor; // parse out header tags in template files if( $package instanceof LocoThemePackage ){ $extractor->set_wp_theme(); } else if( $package instanceof LocoPluginPackage ){ $extractor->set_wp_plugin(); } $export = array(); // extract from PHP sources, as long as source locations exist if( $srcdirs = $package->get_source_dirs() ){ foreach( $srcdirs as $dir ){ $fileref = loco_relative_path( $relative_to, $dir ); foreach( self::find_php($dir) as $path ){ $source = file_get_contents($path) and $tokens = token_get_all($source) and $export = $extractor->extract( $tokens, str_replace( $dir, $fileref, $path ) ); } } } // extract from single file plugin else if( $path = $package->get_default_file() ){ $dir = dirname($path); $fileref = loco_relative_path( $relative_to, $dir ); $source = file_get_contents($path) and $tokens = token_get_all($source) and $export = $extractor->extract( $tokens, str_replace( $dir, $fileref, $path ) ); } // else use first existing PO file in place of POT else if( $po = $package->get_po() ){ foreach( $po as $code => $path ){ $export = self::parse_po( $path ); // strip translations, as this is intended as a POT foreach( $export as $i => $message ){ $export[$i]['target'] = ''; } break; } } // add translatable header tags that won't have been in PHP if( $package instanceof LocoThemePackage ){ $id = $target = ''; foreach( $package->get_headers() as $tag => $source ){ if( $source ){ $notes = str_replace('URI',' URI',$tag).' of the theme'; $export[] = compact('id','source','target','notes'); } } } return $export; } /** * Establish if translations are all empty */ private static function none_translated( array $data ){ foreach( $data as $message ){ if( ! empty($message['target']) ){ return false; } } return true; } /** * Parse MO, PO or POT file */ public static function parse_po( $path ){ function_exists('loco_parse_po') or loco_require('build/gettext-compiled'); $source = trim( file_get_contents($path) ); if( ! $source ){ return array(); } $parser = strpos($path,'.mo') ? 'loco_parse_mo' : 'loco_parse_po'; return call_user_func( $parser, $source ); } /** * Parse MO, PO or POT file, placing header object into argument */ public static function parse_po_with_headers( $path, &$headers ){ $export = self::parse_po( $path ); if( ! isset($export[0]) ){ $ext = strtoupper( pathinfo($path,PATHINFO_EXTENSION) ); throw new Exception( sprintf( __('Empty or invalid %s file','loco-legacy'), $ext ) ); } if( $export[0]['source'] !== '' ){ $ext = strtoupper( pathinfo($path,PATHINFO_EXTENSION) ); throw new Exception( sprintf( __('%s file has no header','loco-legacy'), $ext ) ); } $headers = loco_parse_po_headers( $export[0]['target'] ); $export[0] = array(); // <- avoid index errors as json return $export; } /** * Resolve a list of PO file paths to locale instances */ private static function resolve_file_locales( array $files ){ $locales = array(); foreach( $files as $key => $path ){ $locale = self::resolve_file_locale( $path ); $locales[$key] = $locale; } return $locales; } /** * Resolve a PO file path or file name to a locale. * Note that this does not read the file and the PO header, but perhaps it should. (performance!) * @return LocoLocale */ public static function resolve_file_locale( $path ){ $stub = str_replace( array('.po','.mo'), array('',''), basename($path) ); $locale = loco_locale_resolve($stub); return $locale; } /** * Resolve a PO file path or file name to TextDomain. * Note that this does not parse the file to read any data, it just extracts from filename * @param string e.g. "path/to/foo-fr_FR.po" or "foo.pot" * @return string e.g. "foo" */ public static function resolve_file_domain( $path ){ extract( pathinfo($path) ); if( ! isset($filename) ){ $filename = str_replace( '.'.$extension, '', $basename ); // PHP < 5.2.0 } if( 'pot' === $extension ){ // POT shouldn't have a locale code, but people do things like 'en_EN.pot' if( preg_match('/[a-z]{2,3}_[A-Z]{2}$/', $filename ) ){ return ''; } return $filename; } if( $domain = preg_replace('/[a-z]{2,3}(_[A-Z]{2})?$/', '', $filename ) ){ return rtrim( $domain, '-' ); } // empty domain means file name is probably just a locale return ''; } /** * Resolve a PO file to a theme * @return WP_Theme */ public static function resolve_file_theme( $path ){ if( false !== strpos($path,'/themes/') ){ $domain = self::resolve_file_domain($path); return wp_get_theme( $domain ); } } /** * Generate an admin page URI with custom args */ public static function uri( array $args = array(), $suffix = '' ){ $base_uri = admin_url('admin.php'); if( ! isset($args['page']) ){ $args['page'] = Loco::NS; if( $suffix ){ $args['page'].= '-'.$suffix; } } return add_query_arg($args,$base_uri); } /** * Test if we're on our own admin page * @param string optionally specify exact slug including Loco::NS * @return string current slug */ public static function is_self( $page = null ){ static $active; if( ! isset($active) ){ $screen = get_current_screen(); $splode = explode( Loco::NS, $screen->base, 2 ); $active = isset($splode[1]) ? Loco::NS.$splode[1] : false; } if( false !== $active && ( is_null($page) || $page === $active ) ){ return $active; } return ''; } /** * Generate a URL to edit a po/pot file */ public static function edit_uri( LocoPackage $package, $path ){ $args = $package->get_query() + array ( 'poedit' => self::trim_path( $path ), ); if( $domain = $package->is_pot($path) ){ $args['pot'] = $domain; } return self::uri( $args ); } /** * Generate a link to edit a po/pot file */ public static function edit_link( LocoPackage $package, $path, $label = '', $icon = '' ){ $url = self::edit_uri( $package, $path ); if( ! $label ){ $label = basename( $path ); } $inner = Loco::html($label); if( $icon ){ $inner = ''.$inner; } return ''.$inner.''; } /** * Generate a link to generate a new POT file */ public static function xgettext_link( LocoPackage $package, $domain = '', $label = '' ){ $url = self::uri( $package->get_query() + array( 'xgettext' => $domain ? $domain : $package->get_domain(), ) ); if( ! $label ){ $label = _x('New template','Add button','loco-legacy') ; } return ''.Loco::html($label).''; } /** * Generate a link to create a new PO file for a not-yet-specified locale */ public static function msginit_link( LocoPackage $package, $domain = '', $label = '' ){ if( ! $domain ){ $domain = $package->get_domain(); } $url = self::uri( $package->get_query() + array ( 'msginit' => $domain ? $domain : $package->get_domain(), ) ); if( ! $label ){ $label = _x('New language','Add button','loco-legacy'); } return ''.Loco::html($label).''; } /** * Generate a link to check file permissions on a packge */ public static function fscheck_link( LocoPackage $package, $domain = '', $label ){ if( ! $domain ){ $domain = $package->get_domain(); } $url = self::uri( $package->get_query() + array ( 'fscheck' => $domain ? $domain : $package->get_domain(), ) ); return ''.Loco::html($label).''; } /** * Date format util */ public static function format_datetime( $u ){ static $tf, $df; if( ! $tf ){ $tf = get_option('time_format') or $tf = 'g:i A'; $df = get_option('date_format') or $df= 'M jS Y'; } return date_i18n( $df.' '.$tf, $u ); } /** * PO translate progress summary */ public static function format_progress_summary( array $stats ){ extract( $stats ); $text = sprintf( __('%s%% translated','loco-legacy'), $p ).', '.sprintf( _n('1 string', '%s strings', $t,'loco-legacy' ), number_format($t) ); $extra = array(); if( $f ){ $extra[] = sprintf( __('%s fuzzy','loco-legacy'), number_format($f) ); } if( $u ){ $extra[] = sprintf( __('%s untranslated','loco-legacy'), number_format($f) ); } if( $extra ){ $text .= ' ('.implode(', ',$extra).')'; } return $text; } /** * get configured path to external msgfmt command, including --no-hash and --use-fuzzy arguments * @return string */ public static function msgfmt_command(){ $conf = Loco::config(); if( ! $conf['use_msgfmt'] || ! $conf['which_msgfmt'] ){ return ''; } $cmd = escapeshellarg( trim( $conf['which_msgfmt'] ) ); if( ! $conf['gen_hash'] ){ $cmd .= ' --no-hash'; } if( $conf['use_fuzzy'] ){ $cmd .= ' --use-fuzzy'; } return $cmd; } /** * Execute native msgfmt command * @param string po source * @return string binary mo source */ public static function msgfmt_native( $po ){ try { $conf = Loco::config(); loco_require('build/gettext-compiled'); $gen_hash = (bool) $conf['gen_hash']; $use_fuzzy = (bool) $conf['use_fuzzy']; $mo = loco_msgfmt( $po, $gen_hash, $use_fuzzy ); } catch( Exception $Ex ){ error_log( $Ex->getMessage(), 0 ); } if( ! $mo ){ throw new Exception( sprintf( __('Failed to compile MO file with built-in compiler','loco-legacy') ) ); } return $mo; } } // admin filter and action callbacks /** * Enqueue only admin styles we need */ function _loco_hook__current_screen(){ if( $slug = LocoAdmin::is_self() ){ // redirect legacy links if( $i = strpos( $slug,'-legacy') ){ $args = $_GET; $args['page'] = substr_replace( $slug, '', $i ); $uri = LocoAdmin::uri( $args, $slug ); wp_redirect( $uri ); } // handle upgrade post if( isset($_POST['loco-nonce']) && wp_verify_nonce($_POST['loco-nonce'],'upgrade-v2') ){ update_option( 'loco-branch', '2', true ); $uri = LocoAdmin::uri( array('page' => 'loco' ) ); wp_redirect( $uri ); } // add common resources for all Loco admin pages Loco::enqueue_styles('loco-admin'); // load colour scheme is user has non-default $skin = get_user_option('admin_color'); if( $skin && 'fresh' !== $skin ){ Loco::enqueue_styles( 'skins/'.$skin ); } } } /** * Admin menu registration callback */ function _loco_hook__admin_menu() { $cap = Loco::admin_capablity(); if( current_user_can($cap) ){ // hook in legacy wordpress styles as menu will display $wp_38 = version_compare( $GLOBALS['wp_version'], '3.8', '>=' ) or Loco::enqueue_styles('loco-legacy'); $page_title = __('Loco, Translation Management','loco-legacy'); $tool_title = __('Manage translations','loco-legacy'); $opts_title = __('Translation options','loco-legacy'); // Loco main menu item $slug = Loco::NS; $title = $page_title.' - '.$tool_title; $page = array( 'LocoAdmin', 'render_page_tools' ); // Dashicons were introduced in WP 3.8 $icon = $wp_38 ? 'dashicons-translation' : 'none'; add_menu_page( $title, __('Loco Translate','loco-legacy'), $cap, $slug, $page, $icon ); // add main link under self with different name add_submenu_page( $slug, $title, $tool_title, $cap, $slug, $page ); // also add under Tools menu (legacy) add_management_page( $title, $tool_title, $cap, $slug.'-legacy', $page ); // Settings page $slug = Loco::NS.'-settings'; $title = $page_title.' - '.$opts_title; $page = array( 'LocoAdmin', 'render_page_options' ); add_submenu_page( Loco::NS, $title, $opts_title, $cap, $slug, $page ); // also add under Settings menu (legacy) add_options_page( $title, $opts_title, $cap, $slug.'-legacy', $page ); // Version 2.0 soft upgrade page $slug = Loco::NS.'-upgrade'; $page = array( 'LocoAdmin', 'render_soft_upgrade' ); add_submenu_page( Loco::NS, 'Enable version 2', 'Enable version 2', $cap, $slug, $page ); // Hook in page stuff as soon as screen is avaiable add_action('current_screen', '_loco_hook__current_screen' ); } } /** * extra visibility of settings link */ function _loco_hook__plugin_row_meta( $links, $file = '' ){ if( false !== strpos($file,'/loco.php') ){ $links[] = ''.__('Manage translations','loco-legacy').''; $links[] = ''.__('Settings','loco-legacy').''; } return $links; } /** * execute ajax actions */ function _loco_hook__wp_ajax(){ extract( Loco::postdata() ); if( isset($action) ){ require Loco::basedir().'/php/loco-ajax.php'; } } /** * execute file download actions */ function _loco_hook__wp_ajax_download(){ extract( Loco::postdata() ); if( isset($action) ){ require Loco::basedir().'/php/loco-download.php'; die( __('File download failed','loco-legacy') ); } } /** * callback when admin notices are being printed */ function _loco_hook_admin_notices(){ if( defined('WPLANG') && LocoAdmin::is_self() && WPLANG && 3 < (int) $GLOBALS['wp_version'] ){ LocoAdmin::warning( __('WPLANG is deprecated and should be removed from wp-config.php','loco-legacy') ); } LocoAdmin::flush_notices(); } add_action('admin_menu', '_loco_hook__admin_menu' ); add_action('admin_notices', '_loco_hook_admin_notices'); add_action('plugin_row_meta', '_loco_hook__plugin_row_meta', 10, 2 ); // ajax hooks all going through one central function add_action('wp_ajax_loco-data', '_loco_hook__wp_ajax' ); add_action('wp_ajax_loco-posave', '_loco_hook__wp_ajax' ); add_action('wp_ajax_loco-posync', '_loco_hook__wp_ajax' ); add_action('wp_ajax_loco-download', '_loco_hook__wp_ajax_download' ); // WP_LANG_DIR was introduced in WordPress 2.1.0. if( ! defined('WP_LANG_DIR') ){ define('WP_LANG_DIR', WP_CONTENT_DIR.'/languages' ); } // Load polyfills and raise warnings in debug mode extension_loaded('mbstring') or loco_require('compat/loco-mbstring'); extension_loaded('tokenizer') or loco_require('compat/loco-tokenizer'); extension_loaded('iconv') or loco_require('compat/loco-iconv'); extension_loaded('json') or loco_require('compat/loco-json'); // emergency polyfills for php<5.4 version_compare( phpversion(), '5.4', '>=' ) or loco_require('compat/loco-php');