feat:3.0.0版初次提交,新版将完全推倒重写

This commit is contained in:
sunxiyuan 2020-07-13 18:44:03 +08:00
parent e556c4b6d9
commit 39735249ee
293 changed files with 26908 additions and 475 deletions

5
vendor/loco-translate/CONTRIBUTING.md vendored Normal file
View file

@ -0,0 +1,5 @@
# This is a mirror
Please don't submit pull requests.
If you find any issues with this plugin, we'll greatly appreciate feedback posted in the [support forum](https://wordpress.org/support/plugin/loco-translate).

37
vendor/loco-translate/README.md vendored Normal file
View file

@ -0,0 +1,37 @@
# Loco WordPress Plugin
### Translate plugins and themes directly in your browser
Git mirror of the official Loco WordPress plugin:
[http://wordpress.org/plugins/loco-translate/](http://wordpress.org/plugins/loco-translate/)
**Please don't submit pull requests** this is just a mirror of the SVN repository at:
[https://plugins.svn.wordpress.org/loco-translate/trunk/](https://plugins.svn.wordpress.org/loco-translate/trunk/)
Please report issues in the WordPress plugin directory support forum:
[http://wordpress.org/support/plugin/loco-translate](http://wordpress.org/support/plugin/loco-translate)
## Installation
Note that the actual name of the plugin is "loco-translate" not "wp-loco". It's renamed on Github to differentiate it as a WordPress plugin.
Add the plugin to your WordPress project via Git as follows:
$ git submodule add git@github.com:loco/wp-loco.git wp-content/plugins/loco-translate
If you want to use a stable release listed in the WordPress plugin directory, you can checkout by tag, e.g:
$ cd wp-content/plugins/loco-translate
$ git fetch origin --tags
$ git checkout tags/x.y.z
Be sure to check out the latest version, replacing `x.y.z` in above example with the release number shown below.
[![Latest Version](https://img.shields.io/github/release/loco/wp-loco.svg?style=flat-square)](https://github.com/loco/wp-loco/releases)
## Contributing
There is no issue tracker here. Please submit bugs or feature requests in the [WordPress support forum](http://wordpress.org/support/plugin/loco-translate).
The Github repository is for people who want the latest development version of the plugin and prefer Git to [SVN](http://plugins.svn.wordpress.org/loco-translate/trunk/). This is not a collaborative project and there are no resources available for examining pull requests.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
# Compiled libraries
These files are built from the Loco core. Do not edit!
They've been converted down for PHP 5.2 compatibility in WordPress.

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,5 @@
<?php
/**
* Downgraded for PHP 5.2 compatibility. Do not edit.
*/
function loco_parse_wp_locale( $tag ){ if( ! preg_match( '/^([a-z]{2,3})(?:[-_]([a-z]{2}))?(?:[-_]([a-z0-9]{3,8}))?$/i', $tag, $tags ) ){ throw new InvalidArgumentException('Invalid WordPress locale: '.json_encode($tag) ); } $data = array( 'lang' => strtolower( $tags[1] ), ); if( isset($tags[2]) && ( $subtag = $tags[2] ) ){ $data['region'] = strtoupper($tags[2]); } if( isset($tags[3]) && ( $subtag = $tags[3] ) ){ $data['variant'] = strtolower($tags[3]); } return $data; }

View file

@ -0,0 +1,6 @@
<?php
/**
* Downgraded for PHP 5.2 compatibility. Do not edit.
*/
class LocoDomQuery extends ArrayIterator { public static function parse( $source ){ $dom = new DOMDocument('1.0', 'UTF-8' ); $dom->preserveWhitespace = true; $dom->formatOutput = false; $source = '<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /></head><body>'.$source.'</body></html>'; $used_errors = libxml_use_internal_errors(true); $opts = LIBXML_HTML_NODEFDTD; $parsed = $dom->loadHTML( $source, $opts ); $errors = libxml_get_errors(); $used_errors || libxml_use_internal_errors(false); libxml_clear_errors(); if( $errors || ! $parsed ){ $e = new Loco_error_ParseException('Unknown parse error'); foreach( $errors as $error ){ $e = new Loco_error_ParseException( trim($error->message) ); $e->setContext( $error->line, $error->column, $source ); if( LIBXML_ERR_FATAL === $error->level ){ throw $e; } } if( ! $parsed ){ throw $e; } } return $dom; } public function __construct( $value ){ if( $value instanceof DOMDocument ){ $value = array( $value->documentElement ); } else if( $value instanceof DOMNode ){ $value = array( $value ); } if( is_iterable($value) ){ $nodes = array(); foreach( $value as $node ){ $nodes[] = $node; } } else if( is_string($value) || method_exists($value,'__toString') ){ $value = self::parse( $value ); $nodes = array( $value->documentElement ); } else { $type = is_object($value) ? get_class($value) : gettype($value); throw new InvalidArgumentException('Cannot construct DOM from '.$type ); } parent::__construct( $nodes ); } public function eq( $index ){ $q = new LocoDomQuery(array()); if( $el = $this[$index] ){ $q[] = $el; } return $q; } public function find( $value ){ $q = new LocoDomQuery( array() ); $f = new _LocoDomQueryFilter($value); foreach( $this as $el ){ foreach( $f->filter($el) as $match ){ $q[] = $match; } } return $q; } public function children(){ $q = new LocoDomQuery(array()); foreach( $this as $el ){ if( $el instanceof DOMNode ){ foreach( $el->childNodes as $child ) { $q[] = $child; } } } return $q; } public function text(){ $s = ''; foreach( $this as $el ){ $s .= $el->textContent; } return $s; } public function html(){ $s = ''; foreach( $this as $outer ){ foreach( $outer->childNodes as $inner ){ $s .= $inner->ownerDocument->saveXML($inner); } break; } return $s; } public function attr( $name ){ foreach( $this as $el ){ return $el->getAttribute($name); } return null; } public function serialize(){ $pairs = array(); foreach( array('input','select','textarea','button') as $type ){ foreach( $this->find($type) as $field ){ $name = $field->getAttribute('name'); if( ! $name ){ continue; } if( $field->hasAttribute('type') ){ $type = $field->getAttribute('type'); } if( 'select' === $type ){ $value = null; $f = new _LocoDomQueryFilter('option'); foreach( $f->filter($field) as $option ){ if( $option->hasAttribute('value') ){ $_value = $option->getAttribute('value'); } else { $_value = $option->nodeValue; } if( $option->hasAttribute('selected') ){ $value = $_value; break; } else if( is_null($value) ){ $value = $_value; } } if( is_null($value) ){ $value = ''; } } else if( 'checkbox' === $type || 'radio' === $type ){ if( $field->hasAttribute('checked') ){ $value = $field->getAttribute('value'); } else { continue; } } else if( 'file' === $type ){ $value = ''; } else if( $field->hasAttribute('value') ){ $value = $field->getAttribute('value'); } else { $value = $field->textContent; } $pairs[] = sprintf('%s=%s', rawurlencode($name), rawurlencode($value) ); } } return implode('&',$pairs); } }
class _LocoDomQueryFilter { private $tag; private $attr = array(); public function __construct( $value ){ $id = '[-_a-z][-_a-z0-9]*'; if( ! preg_match('/^([a-z1-6]*)(#'.$id.')?(\.'.$id.')?(\[(\w+)="(.+)"\])?$/i', $value, $r ) ){ throw new InvalidArgumentException('Bad filter, '.$value ); } if( $r[1] ){ $this->tag = $r[1]; } if( ! empty($r[2]) ){ $this->attr['id'] = substr($r[2],1); } if( ! empty($r[3]) ){ $this->attr['class'] = substr($r[3],1); } if( ! empty($r[4]) ){ $this->attr[ $r[5] ] = $r[6]; } } public function filter( DOMElement $el ){ if( $this->tag ){ $list = $el->getElementsByTagName($this->tag); $recursive = false; } else { $list = $el->childNodes; $recursive = true; } if( $this->attr ){ $list = $this->reduce( $list, new ArrayIterator, $recursive )->getArrayCopy(); } return $list; } public function reduce( DOMNodeList $list, ArrayIterator $reduced, $recursive ){ foreach( $list as $node ){ if( $node instanceof DOMElement ){ $matched = false; foreach( $this->attr as $name => $value ){ if( ! $node->hasAttribute($name) ){ $matched = false; break; } $values = array_flip( explode(' ', $node->getAttribute($name) ) ); if( ! isset($values[$value]) ){ $matched = false; break; } $matched = true; } if( $matched ){ $reduced[] = $node; } if( $recursive && $node->hasChildNodes() ){ $this->reduce( $node->childNodes, $reduced, true ); } } } return $reduced; } }

View file

@ -0,0 +1,5 @@
<?php
/**
* Compiled data. Do not edit.
*/
return array('aa'=>'Afar','ab'=>'Abkhazian','ae'=>'Avestan','af'=>'Afrikaans','ak'=>'Akan','am'=>'Amharic','an'=>'Aragonese','ar'=>'Arabic','arq'=>'Algerian Arabic','ary'=>'Moroccan Arabic','as'=>'Assamese','ast'=>'Asturian','av'=>'Avaric','ay'=>'Aymara','az'=>'Azerbaijani','azb'=>'South Azerbaijani','ba'=>'Bashkir','bal'=>'Baluchi','bcc'=>'Southern Balochi','be'=>'Belarusian','bg'=>'Bulgarian','bi'=>'Bislama','bm'=>'Bambara','bn'=>'Bengali','bo'=>'Tibetan','br'=>'Breton','bs'=>'Bosnian','ca'=>'Catalan','ce'=>'Chechen','ceb'=>'Cebuano','ch'=>'Chamorro','ckb'=>'Central Kurdish','co'=>'Corsican','cr'=>'Cree','cs'=>'Czech','cu'=>'Church Slavic','cv'=>'Chuvash','cy'=>'Welsh','da'=>'Danish','de'=>'German','dv'=>'Dhivehi','dz'=>'Dzongkha','ee'=>'Ewe','el'=>'Greek','en'=>'English','eo'=>'Esperanto','es'=>'Spanish','et'=>'Estonian','eu'=>'Basque','fa'=>'Persian','ff'=>'Fulah','fi'=>'Finnish','fj'=>'Fijian','fo'=>'Faroese','fr'=>'French','frp'=>'Arpitan','fuc'=>'Pulaar','fur'=>'Friulian','fy'=>'Western Frisian','ga'=>'Irish','gd'=>'Scottish Gaelic','gl'=>'Galician','gn'=>'Guarani','gsw'=>'Swiss German','gu'=>'Gujarati','gv'=>'Manx','ha'=>'Hausa','haw'=>'Hawaiian','haz'=>'Hazaragi','he'=>'Hebrew','hi'=>'Hindi','ho'=>'Hiri Motu','hr'=>'Croatian','ht'=>'Haitian','hu'=>'Hungarian','hy'=>'Armenian','hz'=>'Herero','ia'=>'Interlingua','id'=>'Indonesian','ie'=>'Interlingue','ig'=>'Igbo','ii'=>'Sichuan Yi','ik'=>'Inupiaq','io'=>'Ido','is'=>'Icelandic','it'=>'Italian','iu'=>'Inuktitut','ja'=>'Japanese','jv'=>'Javanese','ka'=>'Georgian','kab'=>'Kabyle','kg'=>'Kongo','ki'=>'Kikuyu','kj'=>'Kuanyama','kk'=>'Kazakh','kl'=>'Kalaallisut','km'=>'Central Khmer','kn'=>'Kannada','ko'=>'Korean','kr'=>'Kanuri','ks'=>'Kashmiri','ku'=>'Kurdish','kv'=>'Komi','kw'=>'Cornish','ky'=>'Kirghiz','la'=>'Latin','lb'=>'Luxembourgish','lg'=>'Ganda','li'=>'Limburgan','ln'=>'Lingala','lo'=>'Lao','lt'=>'Lithuanian','lu'=>'Luba-Katanga','lv'=>'Latvian','mg'=>'Malagasy','mh'=>'Marshallese','mi'=>'Maori','mk'=>'Macedonian','ml'=>'Malayalam','mn'=>'Mongolian','mr'=>'Marathi','ms'=>'Malay','mt'=>'Maltese','my'=>'Burmese','na'=>'Nauru','nb'=>'Norwegian Bokmål','nd'=>'North Ndebele','ne'=>'Nepali','ng'=>'Ndonga','nl'=>'Dutch','nn'=>'Norwegian Nynorsk','no'=>'Norwegian','nr'=>'South Ndebele','nv'=>'Navajo','ny'=>'Nyanja','oc'=>'Occitan (post 1500)','oj'=>'Ojibwa','om'=>'Oromo','or'=>'Oriya','ory'=>'Oriya (individual language)','os'=>'Ossetian','pa'=>'Panjabi','pi'=>'Pali','pl'=>'Polish','ps'=>'Pushto','pt'=>'Portuguese','qu'=>'Quechua','rhg'=>'Rohingya','rm'=>'Romansh','rn'=>'Rundi','ro'=>'Romanian','ru'=>'Russian','rue'=>'Rusyn','rup'=>'Macedo-Romanian','rw'=>'Kinyarwanda','sa'=>'Sanskrit','sah'=>'Yakut','sc'=>'Sardinian','sd'=>'Sindhi','se'=>'Northern Sami','sg'=>'Sango','sh'=>'Serbo-Croatian','si'=>'Sinhala','sk'=>'Slovak','sl'=>'Slovenian','sm'=>'Samoan','sn'=>'Shona','so'=>'Somali','sq'=>'Albanian','sr'=>'Serbian','ss'=>'Swati','st'=>'Southern Sotho','su'=>'Sundanese','sv'=>'Swedish','sw'=>'Swahili','szl'=>'Silesian','ta'=>'Tamil','te'=>'Telugu','tg'=>'Tajik','th'=>'Thai','ti'=>'Tigrinya','tk'=>'Turkmen','tl'=>'Tagalog','tn'=>'Tswana','to'=>'Tonga (Tonga Islands)','tr'=>'Turkish','ts'=>'Tsonga','tt'=>'Tatar','tw'=>'Twi','twd'=>'Twents','ty'=>'Tahitian','tzm'=>'Central Atlas Tamazight','ug'=>'Uighur','uk'=>'Ukrainian','ur'=>'Urdu','uz'=>'Uzbek','ve'=>'Venda','vi'=>'Vietnamese','vo'=>'Volapük','wa'=>'Walloon','wo'=>'Wolof','xh'=>'Xhosa','xmf'=>'Mingrelian','yi'=>'Yiddish','yo'=>'Yoruba','za'=>'Zhuang','zh'=>'Chinese','zu'=>'Zulu','tlh'=>'Klingon');

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,5 @@
<?php
/**
* Compiled data. Do not edit.
*/
return array('ak'=>1,'am'=>1,'ar'=>2,'ary'=>2,'be'=>3,'bm'=>4,'bo'=>4,'br'=>1,'bs'=>3,'cs'=>5,'cy'=>6,'dz'=>4,'ff'=>1,'fr'=>1,'ga'=>7,'gd'=>8,'gv'=>9,'hr'=>10,'id'=>4,'ii'=>4,'iu'=>11,'ja'=>4,'ka'=>4,'kk'=>4,'km'=>4,'kn'=>4,'ko'=>4,'kw'=>11,'ky'=>4,'ln'=>1,'lo'=>4,'lt'=>12,'lv'=>13,'mg'=>1,'mi'=>1,'mk'=>14,'ms'=>4,'mt'=>15,'my'=>4,'nr'=>4,'oc'=>1,'pl'=>16,'ro'=>17,'ru'=>3,'sa'=>11,'sg'=>4,'sk'=>5,'sl'=>18,'sm'=>4,'sr'=>3,'su'=>4,'th'=>4,'ti'=>1,'tl'=>1,'to'=>4,'tt'=>4,'ug'=>4,'uk'=>3,'vi'=>4,'wa'=>1,'wo'=>4,'yo'=>4,'zh'=>4,''=>array(0=>array(0=>'n != 1',1=>array(0=>'one',1=>'other')),1=>array(0=>'n > 1',1=>array(0=>'one',1=>'other')),2=>array(0=>'n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100 >= 3 && n%100<=10 ? 3 : n%100 >= 11 && n%100<=99 ? 4 : 5',1=>array(0=>'zero',1=>'one',2=>'two',3=>'few',4=>'many',5=>'other')),3=>array(0=>'(n%10==1 && n%100!=11 ? 0 : n%10 >= 2 && n%10<=4 &&(n%100<10||n%100 >= 20)? 1 : 2)',1=>array(0=>'one',1=>'few',2=>'other')),4=>array(0=>'0',1=>array(0=>'other')),5=>array(0=>'( n == 1 ) ? 0 : ( n >= 2 && n <= 4 ) ? 1 : 2',1=>array(0=>'one',1=>'few',2=>'other')),6=>array(0=>'n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n==3 ? 3 : n==6 ? 4 : 5',1=>array(0=>'zero',1=>'one',2=>'two',3=>'few',4=>'many',5=>'other')),7=>array(0=>'n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4',1=>array(0=>'one',1=>'two',2=>'few',3=>'many',4=>'other')),8=>array(0=>'n==1||n==11 ? 0 : n==2||n==12 ? 1 :(n >= 3 && n<=10)||(n >= 13 && n<=19)? 2 : 3',1=>array(0=>'one',1=>'two',2=>'few',3=>'other')),9=>array(0=>'n%10==1 ? 0 : n%10==2 ? 1 : n%20==0 ? 2 : 3',1=>array(0=>'one',1=>'two',2=>'few',3=>'other')),10=>array(0=>'n%10==1 && n%100!=11 ? 0 : n%10 >= 2 && n%10<=4 &&(n%100<10||n%100 >= 20)? 1 : 2',1=>array(0=>'one',1=>'few',2=>'other')),11=>array(0=>'n == 1 ? 0 : n == 2 ? 1 : 2',1=>array(0=>'one',1=>'two',2=>'other')),12=>array(0=>'(n%10==1 && n%100!=11 ? 0 : n%10 >= 2 &&(n%100<10||n%100 >= 20)? 1 : 2)',1=>array(0=>'one',1=>'few',2=>'other')),13=>array(0=>'n % 10 == 1 && n % 100 != 11 ? 0 : n != 0 ? 1 : 2',1=>array(0=>'one',1=>'other',2=>'zero')),14=>array(0=>'( n % 10 == 1 && n % 100 != 11 ) ? 0 : 1',1=>array(0=>'one',1=>'other')),15=>array(0=>'(n==1 ? 0 : n==0||( n%100>1 && n%100<11)? 1 :(n%100>10 && n%100<20)? 2 : 3)',1=>array(0=>'one',1=>'few',2=>'many',3=>'other')),16=>array(0=>'(n==1 ? 0 : n%10 >= 2 && n%10<=4 &&(n%100<10||n%100 >= 20)? 1 : 2)',1=>array(0=>'one',1=>'few',2=>'other')),17=>array(0=>'(n==1 ? 0 :(((n%100>19)||(( n%100==0)&&(n!=0)))? 2 : 1))',1=>array(0=>'one',1=>'few',2=>'other')),18=>array(0=>'n%100==1 ? 0 : n%100==2 ? 1 : n%100==3||n%100==4 ? 2 : 3',1=>array(0=>'one',1=>'two',2=>'few',3=>'other'))));

File diff suppressed because one or more lines are too long

177
vendor/loco-translate/loco.php vendored Normal file
View file

@ -0,0 +1,177 @@
<?php
/*
Plugin Name: Loco Translate
Plugin URI: https://wordpress.org/plugins/loco-translate/
Description: Translate themes and plugins directly in WordPress
Author: Tim Whitlock
Version: 2.4.0
Author URI: https://localise.biz/wordpress/plugin
Text Domain: loco-translate
Domain Path: /languages/
*/
// disallow execution out of context
if( ! function_exists('is_admin') ){
return;
}
/**
* Get absolute path to Loco primary plugin file
* @return string
*/
function loco_plugin_file(){
return __FILE__;
}
/**
* Get version of this plugin
* @return string
*/
function loco_plugin_version(){
return '2.4.0';
}
/**
* Get Loco plugin handle, used by WordPress to identify plugin as a relative path
* @return string probably "loco-translate/loco.php"
*/
function loco_plugin_self(){
static $handle;
isset($handle) or $handle = plugin_basename(__FILE__);
return $handle;
}
/**
* Get absolute path to plugin root directory
* @return string __DIR__
*/
function loco_plugin_root(){
static $root;
isset($root) or $root = dirname(__FILE__);
return $root;
}
/**
* Check whether currently running in debug mode
* @return bool
*/
function loco_debugging(){
return apply_filters('loco_debug', WP_DEBUG );
}
/**
* Whether currently processing an Ajax request
* @return bool
*/
function loco_doing_ajax(){
return defined('DOING_AJAX') && DOING_AJAX;
}
/**
* Evaluate a constant by name
* @param string
* @return mixed
*/
function loco_constant( $name ){
$value = defined($name) ? constant($name) : null;
// constant values will only be modified in tests
if( defined('LOCO_TEST') && LOCO_TEST ){
$value = apply_filters('loco_constant', $value, $name );
$value = apply_filters('loco_constant_'.$name, $value );
}
return $value;
}
/**
* Runtime inclusion of any file under plugin root
* @param string PHP file path relative to __DIR__
* @return mixed return value from included file
*/
function loco_include( $relpath ){
$path = loco_plugin_root().'/'.$relpath;
if( ! file_exists($path) ){
throw new Loco_error_Exception('File not found: '.$path);
}
return include $path;
}
/**
* Require dependant library once only
* @param string PHP file path relative to ./lib
* @return void
*/
function loco_require_lib( $path ){
require_once loco_plugin_root().'/lib/'.$path;
}
/**
* Check PHP extension required by Loco and load polyfill if needed
* @param string
* @return bool
*/
function loco_check_extension( $name ) {
static $cache = array();
if ( ! isset( $cache[$name] ) ) {
if ( extension_loaded($name) ) {
$cache[ $name ] = true;
}
else {
Loco_error_AdminNotices::warn( sprintf( __('Loco requires the "%s" PHP extension. Ask your hosting provider to install it','loco-translate'), $name ) );
$class = 'Loco_compat_'.ucfirst($name).'Extension.php';
$cache[$name] = class_exists($class);
}
}
return $cache[ $name ];
}
/**
* Class autoloader for Loco classes under src directory.
* e.g. class "Loco_foo_FooBar" wil be found in "src/foo/FooBar.php"
* Also does autoload for polyfills under "src/compat" if $name < 20 chars
*
* @internal
* @param string
* @return void
*/
function loco_autoload( $name ){
if( 'Loco_' === substr($name,0,5) ){
loco_include( 'src/'.strtr( substr($name,5), '_', '/' ).'.php' );
}
else if( strlen($name) < 20 ){
$path = loco_plugin_root().'/src/compat/'.$name.'.php';
if( file_exists($path) ){
require $path;
}
}
}
spl_autoload_register( 'loco_autoload', false );
// provide safe directory for custom translations that won't be deleted during auto-updates
if( ! defined('LOCO_LANG_DIR') ){
define( 'LOCO_LANG_DIR', trailingslashit(loco_constant('WP_LANG_DIR')).'loco' );
}
// text domain loading helper for custom file locations. disable by setting constant empty
if( LOCO_LANG_DIR ){
new Loco_hooks_LoadHelper;
}
// initialize hooks for admin screens
if( is_admin() ){
new Loco_hooks_AdminHooks;
}

24
vendor/loco-translate/loco.xml vendored Normal file
View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<bundle name="Loco Translate" min-version="2.0.14">
<domain name="loco-translate">
<project name="Loco Translate" slug="loco-translate">
<source>
<directory>src</directory>
<directory>tpl</directory>
<file>loco.php</file>
</source>
<target>
<directory>languages</directory>
</target>
<template>
<file>languages/loco-translate.pot</file>
</template>
</project>
</domain>
<exclude>
<directory>tmp</directory>
<directory>lib</directory>
<directory>pub</directory>
<directory>test</directory>
</exclude>
</bundle>

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
form.loco-filter{float:right}@media only screen and (max-width: 1024px){table.wp-list-table a.row-title{max-width:100px}}

View file

@ -0,0 +1 @@
form#loco-conf>div{overflow:visible;border-bottom:solid 1px #ccc;padding-top:2em}form#loco-conf>div h2{margin-top:0}form#loco-conf td.twin>div{float:left;clear:none;width:50%}form#loco-conf td .description:first-child{margin-top:0;margin-bottom:4px}form#loco-conf a.icon-del{display:block;float:right;z-index:99;color:#aaa;outline:none}form#loco-conf a.icon-del:hover{color:#c00}form#loco-conf>div:first-child a.icon-del{display:none}form#loco-conf p.description{color:#aaa;font-size:12px;text-indent:.25em}form#loco-conf tr:hover p.description{color:#666}form#loco-reset{position:absolute;bottom:0;right:0}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
#loco.wrap .notice-info dl{margin-top:0;display:inline-block}#loco.wrap .notice-info dl dt,#loco.wrap .notice-info dl dd{line-height:1.4em}#loco.wrap .notice-info dl dt{font-weight:bold;color:#555}#loco.wrap .notice-info dl dd{margin-left:0;margin-bottom:.8em}#loco.wrap .notice-info dl div.progress .l{display:none}

View file

@ -0,0 +1 @@
#loco.wrap td.loco-not-active{color:#aaa}#loco.wrap div.loco-projects>h3{float:left}#loco.wrap div.loco-projects form.loco-filter{float:right;margin:1em 0}

View file

@ -0,0 +1 @@
#loco.wrap .revisions-diff{padding:10px;min-height:20px}#loco.wrap table.diff{border-collapse:collapse}#loco.wrap table.diff td{white-space:nowrap;overflow:hidden;font:normal 12px/17px "Monaco","Menlo","Ubuntu Mono","Consolas","source-code-pro",monospace;padding:2px}#loco.wrap table.diff td>span{color:#aaa}#loco.wrap table.diff td>span:after{content:". "}#loco.wrap table.diff tbody{border-top:1px dashed #ccc}#loco.wrap table.diff tbody:first-child{border-top:none}#loco.wrap .revisions.loading .diff-meta{color:#eee}#loco.wrap .revisions.loading .loading-indicator span.spinner{visibility:visible;background:#fff url(../img/spin-modal.gif?v=2.4.0) center center no-repeat}#loco.wrap .revisions-meta{clear:both;padding:10px 12px;margin:0;position:relative;top:10px}#loco.wrap .revisions-meta .diff-meta{clear:none;float:left;width:50%;padding:0;min-height:auto}#loco.wrap .revisions-meta .diff-meta button{margin-top:5px}#loco.wrap .revisions-meta .diff-meta-current{float:right;text-align:right}#loco.wrap .revisions-meta time{color:#72777c}#loco.wrap .revisions-control-frame{margin:10px 0}#loco.wrap .revisions-diff-frame{margin-top:20px}

View file

@ -0,0 +1 @@
form#loco-poinit .loco-locales fieldset{float:left;margin-right:2em}form#loco-poinit .loco-locales fieldset.disabled span.lang{visibility:hidden !important}form#loco-poinit .loco-locales fieldset span.nolang{background:#999}form#loco-poinit .loco-locales fieldset>label span{width:20px;text-align:center;display:inline-block;margin-right:4px}form#loco-poinit a.icon-help{color:#999;font-style:normal;text-decoration:none}form#loco-poinit a.icon-help:hover{color:#666}form#loco-poinit .form-table th{padding:15px 10px}form#loco-poinit .form-table tr:first-child td,form#loco-poinit .form-table tr:first-child th{padding-top:25px}form#loco-poinit label.for-disabled input{visibility:hidden}form#loco-poinit label.for-disabled .icon-lock{top:0;left:0;display:block;position:absolute;width:1em;font-size:14px;text-align:center;color:gray}

View file

@ -0,0 +1 @@
.js #loco.wrap .loco-loading{min-height:100px;background:#fff url(../img/spin-modal.gif?v=2.4.0) center center no-repeat}.js #loco.wrap .loco-loading ol.msgcat{display:none}#loco.wrap #loco-po{padding-right:0;overflow:auto}#loco.wrap ol.msgcat{margin-left:3em;padding-top:1em;border-top:1px dashed #ccc}#loco.wrap ol.msgcat:first-child{padding-top:0;border-top:none}#loco.wrap ol.msgcat li{color:#aaa;margin:0;padding:0 0 0 1em;font:normal 12px/17px "Monaco","Menlo","Ubuntu Mono","Consolas","source-code-pro",monospace;border-left:1px solid #eee}#loco.wrap ol.msgcat li>*{color:#333;white-space:pre}#loco.wrap ol.msgcat li>.po-comment{color:#3cc200}#loco.wrap ol.msgcat li>.po-refs{color:#0073aa}#loco.wrap ol.msgcat li>.po-refs a{color:inherit;text-decoration:none}#loco.wrap ol.msgcat li>.po-refs a:hover{text-decoration:underline}#loco.wrap ol.msgcat li>.po-flags{color:#77904a}#loco.wrap ol.msgcat li>.po-flags em{font-style:normal}#loco.wrap ol.msgcat li>.po-word{color:#000}#loco.wrap ol.msgcat li>.po-junk{font-style:italic;color:#ccc}#loco.wrap ol.msgcat li>.po-string>span{color:#c931c7}#loco.wrap form.loco-filter{top:0;right:0;position:absolute}#loco.wrap .loco-invalid form.loco-filter input[type=text]:focus{border-color:#c00;-webkit-box-shadow:0 0 2px rgba(153,0,0,.5);-moz-box-shadow:0 0 2px rgba(153,0,0,.5);box-shadow:0 0 2px rgba(153,0,0,.5)}#loco.wrap .loco-invalid ol.msgcat{list-style-type:none}#loco.wrap .loco-invalid ol.msgcat li{color:#000}

View file

@ -0,0 +1 @@
.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(9,100,132,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#096484}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}form button.loco-loading.button-primary[disabled]:before{background:transparent url(../../img/skins/blue/spin-primary-button.gif?v=2.4.0) 0 0 no-repeat !important}

View file

@ -0,0 +1 @@
.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(199,165,137,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#c7a589}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}form button.loco-loading.button-primary[disabled]:before{background:transparent url(../../img/skins/coffee/spin-primary-button.gif?v=2.4.0) 0 0 no-repeat !important}

View file

@ -0,0 +1 @@
.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(163,183,69,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#a3b745}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}form button.loco-loading.button-primary[disabled]:before{background:transparent url(../../img/skins/ectoplasm/spin-primary-button.gif?v=2.4.0) 0 0 no-repeat !important}

View file

@ -0,0 +1 @@
.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(136,136,136,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#888}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}form button.loco-loading.button-primary[disabled]:before{background:transparent url(../../img/skins/light/spin-primary-button.gif?v=2.4.0) 0 0 no-repeat !important}

View file

@ -0,0 +1 @@
.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(225,77,67,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#e14d43}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}form button.loco-loading.button-primary[disabled]:before{background:transparent url(../../img/skins/midnight/spin-primary-button.gif?v=2.4.0) 0 0 no-repeat !important}

View file

@ -0,0 +1 @@
.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(158,186,160,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#9ebaa0}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}form button.loco-loading.button-primary[disabled]:before{background:transparent url(../../img/skins/ocean/spin-primary-button.gif?v=2.4.0) 0 0 no-repeat !important}

View file

@ -0,0 +1 @@
.wrap #loco-editor .is-table .wg-tr:nth-child(even){background-color:rgba(221,130,59,.05)}.wrap #loco-editor .wg-split-x>nav.wg-tabs>a.active,.wrap #loco-editor .is-table .wg-cols>div>div.selected{background-color:#dd823b}.wrap #loco-editor .is-editable>.wg-content>textarea:focus,.wrap #loco-editor .is-editable>.wg-content.has-focus .ace_scroller,.wrap #loco-editor .is-editable>.wg-content.has-focus .mce-content-body{border-color:#5b9dd9;-webkit-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);-moz-box-shadow:inset 0 0 .6em rgba(30,140,190,.8);box-shadow:inset 0 0 .6em rgba(30,140,190,.8)}form button.loco-loading.button-primary[disabled]:before{background:transparent url(../../img/skins/sunrise/spin-primary-button.gif?v=2.4.0) 0 0 no-repeat !important}

BIN
vendor/loco-translate/pub/font/loco.eot vendored Normal file

Binary file not shown.

111
vendor/loco-translate/pub/font/loco.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 73 KiB

BIN
vendor/loco-translate/pub/font/loco.ttf vendored Normal file

Binary file not shown.

BIN
vendor/loco-translate/pub/font/loco.woff vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
vendor/loco-translate/pub/img/flags.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

View file

@ -0,0 +1,196 @@
(function(r,s,k,R){var n=function(){function f(d){throw Error("Failed to require "+d);}var d={};return{register:function(f,m){d[f]=m},require:function(k,m){return d[k]||f(m)},include:function(k,m,g){return d[k]||(g?f(m):null)}}}();n.register("$1",function(f,d,k){function m(g){var c=typeof g;if("string"===c)if(/[^ <>!=()%^&|?:n0-9]/.test(g))console.error("Invalid plural: "+g);else return new Function("n","return "+g);"function"!==c&&(g=function(b){return 1!=b});return g}f.init=function(g){function c(a,
l,c){return(a=b[a])&&a[c]?a[c]:l||""}g=m(g);var b={};return{_:function(a){return c(a,a,0)},_x:function(a,b){return c(b+"\u0004"+a,a,0)},_n:function(a,b,e){e=Number(g(e));isNaN(e)&&(e=0);return c(a,e?b:a,e)},load:function(a){b=a||{};return this},pluraleq:function(a){g=m(a);return this}}};return f}({},r,s));n.register("$2",function(f,d,n){f.ie=function(){var d=k&&k.browser||{},g=d.msie&&11>Number(d.version),d=null;return function(){return g}}();f.init=function(){return f};return f}({},r,s));n.register("$3",
function(f,d,k){Number.prototype.format=function(d){d=Math.pow(10,d||0);var g=Math.round(d*this)/d;d=[];var g=String(g),c=g.split("."),g=c[0],c=c[1],b=g.length;do d.unshift(g.substring(b-3,b));while(0<(b-=3));g=d.join(",");if(d=c){d=c;for(var a,c=d.length;"0"===d.charAt(--c);)a=c;a&&(d=d.substring(0,a));d=c=d}d&&(g+="."+c);return g};Number.prototype.percent=function(d){var g=0,c=this&&d?100*(this/d):0;if(0===c)return"0";if(100===c)return"100";if(99<c)c=Math.min(c,99.9),d=c.format(++g);else if(0.5>
c){c=Math.max(c,1E-4);do d=c.format(++g);while("0"===d&&4>g);d=d.substr(1)}else d=c.format(0);return d};return f}({},r,s));n.register("$4",function(f,d,k){Array.prototype.indexOf||(Array.prototype.indexOf=function(d){if(null==this)throw new TypeError;var g,c=Object(this),b=c.length>>>0;if(0===b)return-1;g=0;1<arguments.length&&(g=Number(arguments[1]),g!=g?g=0:0!=g&&Infinity!=g&&-Infinity!=g&&(g=(0<g||-1)*Math.floor(Math.abs(g))));if(g>=b)return-1;for(g=0<=g?g:Math.max(b-Math.abs(g),0);g<b;g++)if(g in
c&&c[g]===d)return g;return-1});return f}({},r,s));n.register("$5",function(f,d,k){f.trim=function(d,g){for(g||(g=" \n");d&&-1!==g.indexOf(d.substr(0,1));)d=d.substr(1);for(;d&&-1!==g.indexOf(d.substr(-1));)d=d.substr(0,d.length-1);return d};f.sprintf=function(d){var g=0,c,b=[].slice.call(arguments,1);return d.replace(/%([sud%])/g,function(a,l){if("%"===l)return"%";c=b[g++];return String(c)||""})};return f}({},r,s));n.register("$20",function(f,d,k){function m(g){return function(c,b){for(var a=c[g]||
0;(c=c.offsetParent)&&c!==(b||k.body);)a+=c[g]||0;return a}}f.top=m("offsetTop");f.left=m("offsetLeft");f.el=function(g,c){var b=k.createElement(g||"div");c&&(b.className=c);return b};f.txt=function(g){return k.createTextNode(g||"")};return f}({},r,s));n.register("$6",function(f,d,E){function m(a,b,c){function l(){e();w=setTimeout(b,c)}function e(){w&&clearTimeout(w);w=null}var w;l();k(a).mouseenter(e).mouseleave(l);return{die:function(){e();k(a).off("mouseenter mouseleave")}}}function g(a,b){a.fadeTo(b,
0,function(){a.slideUp(b,function(){a.remove();k(d).triggerHandler("resize")})});return a}function c(a,b){function c(b){p[w]=null;g(k(a),250);e&&e.die();var l;if(l=b)b.stopPropagation(),b.preventDefault(),l=!1;return l}function l(b){e&&e.die();return e=m(a,c,b)}var e,w,v,B=k(a),q=B.find("button");0===q.length&&(B.addClass("is-dismissible"),q=k('<button type="button" class="notice-dismiss"> </a>').appendTo(B));q.off("click").click(c);k(d).triggerHandler("resize");h();w=p.length;p.push(c);b&&(e=l(b));
return{link:function(b,l){var c=l||b,e=k(a).find("nav"),c=k("<nav></nav>").append(k("<a></a>").attr("href",b).text(c));v?(v.push(c.html()),e.html(v.join("<span> | </span>"))):(v=[c.html()],k(a).addClass("has-nav").append(c));return this},stick:function(){e&&e.die();e=null;p[w]=null;return this},slow:function(a){l(a||1E4);return this}}}function b(a,b,c){var l=n.require("$20","dom.js").el;a=k('<div class="notice notice-'+a+' loco-notice inline"></div>').prependTo(k("#loco-notices"));var e=k(l("p"));
c=k(l("span")).text(c);b=k(l("strong","has-icon")).text(b+": ");e.append(b).append(c).appendTo(a);return a}function a(a,l,e,w){a=b(e,l,a).css("opacity","0").fadeTo(500,1);k(d).triggerHandler("resize");return c(a,w)}function l(b){return a(b,D,"warning")}function e(){k("#loco-notices").find("div.notice").each(function(a,b){if(-1===b.className.indexOf("jshide")){var l=-1===b.className.indexOf("notice-success")?null:5E3;c(b,l)}})}var p=[],h=Date.now||function(){return(new Date).getTime()},v,D,q,w;f.error=
function(b){return a(b,v,"error")};f.warn=l;f.info=function(b){return a(b,q,"info")};f.success=function(b){return a(b,w,"success",5E3)};f.warning=l;f.log=function(){d.console&&console.log&&console.log.apply(console,arguments)};f.debug=function(a,b){d.console&&console.error&&(console.error("Loco Error: "+a),b&&console.debug&&console.debug(b))};f.clear=function(){for(var a=-1,b,l=p,c=l.length;++a<c;)(b=l[a])&&b.call&&b();p=[];return f};f.create=b;f.raise=function(a){(f[a.type]||f.error).call(f,a.message)};
f.convert=c;f.init=function(a){v=a._("Error");D=a._("Warning");q=a._("Notice");w=a._("OK");setTimeout(e,1E3);return f};return f}({},r,s));n.register("$7",function(f,d,E){function m(a){var b=k("<pre>"+a+"</pre>").text();b&&(b=b.replace(/[\r\n]+/g,"\n").replace(/(^|\n)\s+/g,"$1").replace(/\s+$/,""));b||(b=a)||(b="Blank response from server");return b}function g(a){return(a=a.split(/[\r\n]/)[0])?(a=a.replace(/ +in +\S+ on line \d+/,""),a=a.replace(/^[()! ]+Fatal error:\s*/,"")):t._("Server returned invalid data")}
function c(a,b,l){a[b]=l}function b(a,b,l){a.push({name:b,value:l})}function a(a,b,l){a.append(b,l)}function l(a,b,l,c){function e(b,c,v){if("abort"!==c){var d=p||{_:function(a){return a}},B=b.status,u=b.responseText,A=m(u),q=b.getResponseHeader("Content-Type")||"text/html",f=b.getResponseHeader("Content-Length")||u.length;"success"===c&&v?z.error(v):(z.error(g(A)+".\n"+d._("Check console output for debugging information")),z.debug("Ajax failure for "+a,{status:B,error:c,message:v,output:u}),"parsererror"===
c&&(v="Response not JSON"),z.log([d._("Provide the following text when reporting a problem")+":","----","Status "+B+' "'+(v||d._("Unknown error"))+'" ('+q+" "+f+" bytes)",A,"===="].join("\n")));l&&l.call&&l(b,c,v);h=b}}c.url=v;c.dataType="json";var z=n.require("$6","notices.js").clear();h=null;return k.ajax(c).fail(e).done(function(a,l,c){var w=a&&a.data,h=a&&a.notices,v=h&&h.length;for(!w||a.error?e(c,l,a&&a.error&&a.error.message):b&&b(w,l,c);v--;)z.raise(h[v])})}var e={},p,h,v=d.ajaxurl||"/wp-admin/admin-ajax.php";
f.init=function(a){e=a.nonces||e;return f};f.localise=function(a){p=a;return f};f.xhr=function(){return h};f.strip=m;f.parse=g;f.submit=function(a,b,c){function e(a,b){b.disabled?b.setAttribute("data-was-disabled","true"):b.disabled=!0}function h(a,b){b.getAttribute("data-was-disabled")||(b.disabled=!1)}function v(a){a.find(".button-primary").removeClass("loading");a.find("button").each(h);a.find("input").each(h);a.find("select").each(h);a.find("textarea").each(h);a.removeClass("disabled loading")}
var g=k(a),p=g.serialize();(function(a){a.find(".button-primary").addClass("loading");a.find("button").each(e);a.find("input").each(e);a.find("select").each(e);a.find("textarea").each(e);a.addClass("disabled loading")})(g);return l(a.route.value,function(a,l,c){v(g);b&&b(a,l,c)},function(a,b,l){v(g);c&&c(a,b,l)},{type:a.method,data:p})};f.post=function(h,v,w,g){var p,z=!0;v=v||{};(p=e[h])||(d.console&&console.error&&console.error('No nonce for "'+h+'"'),p="");var H=p;d.FormData&&v instanceof FormData?
(z=!1,p=a):p=Array.isArray(v)?b:c;p(v,"action","loco_json");p(v,"route",h);p(v,"loco-nonce",H);return l(h,w,g,{type:"post",data:v,processData:z,contentType:z?"application/x-www-form-urlencoded; charset=UTF-8":!1})};f.setNonce=function(a,b){e[a]=b;return f};return f}({},r,s));n.register("$21",{arab:1,aran:1,hebr:1,nkoo:1,syrc:1,syrn:1,syrj:1,syre:1,samr:1,mand:1,mend:1,thaa:1,adlm:1,cprt:1,phnx:1,armi:1,prti:1,phli:1,phlp:1,phlv:1,avst:1,mani:1,khar:1,orkh:1,ital:1,lydi:1,ar:1,ary:1,ckb:1,dv:1,fa:1,
he:1,nqo:1,ps:1,ur:1,yi:1});n.register("$8",function(f,d,k){function m(){}var g,c=n.require("$21","rtl.json");f.init=function(){return new m};f.cast=function(b){return b instanceof m?b:"string"===typeof b?f.parse(b):f.clone(b)};f.clone=function(b){var a,l=new m;for(a in b)l[a]=b[a];return l};f.parse=function(b){if(!(g||(g=/^([a-z]{2,3})(?:[-_]([a-z]{2}))?(?:[-_]([a-z0-9]{3,8}))?$/i)).exec(b))return null;var a=new m;a.lang=RegExp.$1.toLowerCase();if(b=RegExp.$2)a.region=b.toUpperCase();if(b=RegExp.$3)a.variant=
b.toLowerCase();return a};d=m.prototype;d.isValid=function(){return!!this.lang};d.isKnown=function(){var b=this.lang;return!(!b||"zxx"===b)};d.toString=function(b){b=b||"-";var a,l=this.lang||"zxx";if(a=this.region)l+=b+a;if(a=this.variant)l+=b+a;return l};d.getIcon=function(){for(var b=3,a,l,c=["variant","region","lang"],p=[];0!==b--;)if(a=c[b],l=this[a])p.push(a),p.push(a+"-"+l.toLowerCase());return p.join(" ")};d.isRTL=function(){return!!c[String(this.lang).toLowerCase()]};d=null;return f}({},
r,s));n.register("$22",{"\u00e1":"a","\u00e0":"a","\u0103":"a","\u1eaf":"a","\u1eb1":"a","\u1eb5":"a","\u1eb3":"a","\u00e2":"a","\u1ea5":"a","\u1ea7":"a","\u1eab":"a","\u1ea9":"a","\u01ce":"a","\u00e5":"a","\u01fb":"a","\u00e4":"a","\u01df":"a","\u00e3":"a","\u0227":"a","\u01e1":"a","\u0105":"a","\u0101":"a","\u1ea3":"a","\u0201":"a","\u0203":"a","\u1ea1":"a","\u1eb7":"a","\u1ead":"a","\u1e01":"a","\u01fd":"\u00e6","\u01e3":"\u00e6","\u1e03":"b","\u1e05":"b","\u1e07":"b","\u0107":"c","\u0109":"c",
"\u010d":"c","\u010b":"c","\u00e7":"c","\u1e09":"c","\u010f":"d","\u1e0b":"d","\u1e11":"d","\u0111":"d","\u1e0d":"d","\u1e13":"d","\u1e0f":"d","\u00f0":"d","\ua77a":"d","\u01c6":"\u01f3","\u00e9":"e","\u00e8":"e","\u0115":"e","\u00ea":"e","\u1ebf":"e","\u1ec1":"e","\u1ec5":"e","\u1ec3":"e","\u011b":"e","\u00eb":"e","\u1ebd":"e","\u0117":"e","\u0229":"e","\u1e1d":"e","\u0119":"e","\u0113":"e","\u1e17":"e","\u1e15":"e","\u1ebb":"e","\u0205":"e","\u0207":"e","\u1eb9":"e","\u1ec7":"e","\u1e19":"e","\u1e1b":"e",
"\u1e1f":"f","\ua77c":"f","\u01f5":"g","\u011f":"g","\u011d":"g","\u01e7":"g","\u0121":"g","\u0123":"g","\u1e21":"g","\ua7a1":"g","\u1d79":"g","\u0125":"h","\u021f":"h","\u1e27":"h","\u1e23":"h","\u1e29":"h","\u0127":"h","\u210f":"h","\u1e25":"h","\u1e2b":"h","\u1e96":"h","\u00ed":"i","\u00ec":"i","\u012d":"i","\u00ee":"i","\u01d0":"i","\u00ef":"i","\u1e2f":"i","\u0129":"i","\u012f":"i","\u012b":"i","\u1ec9":"i","\u0209":"i","\u020b":"i","\u1ecb":"i","\u1e2d":"i","\u0135":"j","\u01f0":"j","\u1e31":"k",
"\u01e9":"k","\u0137":"k","\ua7a3":"k","\u1e33":"k","\u1e35":"k","\u013a":"l","\u013e":"l","\u013c":"l","\u0142":"l","\u1e37":"l","\u1e39":"l","\u1e3d":"l","\u1e3b":"l","\u0140":"l","\u1e3f":"m","\u1e41":"m","\u1e43":"m","\u0144":"n","\u01f9":"n","\u0148":"n","\u00f1":"n","\u1e45":"n","\u0146":"n","\ua7a5":"n","\u1e47":"n","\u1e4b":"n","\u1e49":"n","\u00f3":"o","\u00f2":"o","\u014f":"o","\u00f4":"o","\u1ed1":"o","\u1ed3":"o","\u1ed7":"o","\u1ed5":"o","\u01d2":"o","\u00f6":"o","\u022b":"o","\u0151":"o",
"\u00f5":"o","\u1e4d":"o","\u1e4f":"o","\u022d":"o","\u022f":"o","\u0231":"o","\u00f8":"o","\u01ff":"o","\u01eb":"o","\u01ed":"o","\u014d":"o","\u1e53":"o","\u1e51":"o","\u1ecf":"o","\u020d":"o","\u020f":"o","\u01a1":"o","\u1edb":"o","\u1edd":"o","\u1ee1":"o","\u1edf":"o","\u1ee3":"o","\u1ecd":"o","\u1ed9":"o","\u1e55":"p","\u1e57":"p","\u0155":"r","\u0159":"r","\u1e59":"r","\u0157":"r","\ua7a7":"r","\u0211":"r","\u0213":"r","\u1e5b":"r","\u1e5d":"r","\u1e5f":"r","\ua783":"r","\u015b":"s","\u1e65":"s",
"\u015d":"s","\u0161":"s","\u1e67":"s","\u1e61":"s","\u015f":"s","\ua7a9":"s","\u1e63":"s","\u1e69":"s","\u0219":"s","\u017f":"s","\ua785":"s","\u1e9b":"s","\u0165":"t","\u1e97":"t","\u1e6b":"t","\u0163":"t","\u1e6d":"t","\u021b":"t","\u1e71":"t","\u1e6f":"t","\ua787":"t","\u00fa":"u","\u00f9":"u","\u016d":"u","\u00fb":"u","\u01d4":"u","\u016f":"u","\u00fc":"u","\u01d8":"u","\u01dc":"u","\u01da":"u","\u01d6":"u","\u0171":"u","\u0169":"u","\u1e79":"u","\u0173":"u","\u016b":"u","\u1e7b":"u","\u1ee7":"u",
"\u0215":"u","\u0217":"u","\u01b0":"u","\u1ee9":"u","\u1eeb":"u","\u1eef":"u","\u1eed":"u","\u1ef1":"u","\u1ee5":"u","\u1e73":"u","\u1e77":"u","\u1e75":"u","\u1e7d":"v","\u1e7f":"v","\u1e83":"w","\u1e81":"w","\u0175":"w","\u1e98":"w","\u1e85":"w","\u1e87":"w","\u1e89":"w","\u1e8d":"x","\u1e8b":"x","\u00fd":"y","\u1ef3":"y","\u0177":"y","\u1e99":"y","\u00ff":"y","\u1ef9":"y","\u1e8f":"y","\u0233":"y","\u1ef7":"y","\u1ef5":"y","\u017a":"z","\u1e91":"z","\u017e":"z","\u017c":"z","\u1e93":"z","\u1e95":"z",
"\u01ef":"\u0292","\u1f00":"\u03b1","\u1f04":"\u03b1","\u1f84":"\u03b1","\u1f02":"\u03b1","\u1f82":"\u03b1","\u1f06":"\u03b1","\u1f86":"\u03b1","\u1f80":"\u03b1","\u1f01":"\u03b1","\u1f05":"\u03b1","\u1f85":"\u03b1","\u1f03":"\u03b1","\u1f83":"\u03b1","\u1f07":"\u03b1","\u1f87":"\u03b1","\u1f81":"\u03b1","\u03ac":"\u03b1","\u1f71":"\u03b1","\u1fb4":"\u03b1","\u1f70":"\u03b1","\u1fb2":"\u03b1","\u1fb0":"\u03b1","\u1fb6":"\u03b1","\u1fb7":"\u03b1","\u1fb1":"\u03b1","\u1fb3":"\u03b1","\u1f10":"\u03b5",
"\u1f14":"\u03b5","\u1f12":"\u03b5","\u1f11":"\u03b5","\u1f15":"\u03b5","\u1f13":"\u03b5","\u03ad":"\u03b5","\u1f73":"\u03b5","\u1f72":"\u03b5","\u1f20":"\u03b7","\u1f24":"\u03b7","\u1f94":"\u03b7","\u1f22":"\u03b7","\u1f92":"\u03b7","\u1f26":"\u03b7","\u1f96":"\u03b7","\u1f90":"\u03b7","\u1f21":"\u03b7","\u1f25":"\u03b7","\u1f95":"\u03b7","\u1f23":"\u03b7","\u1f93":"\u03b7","\u1f27":"\u03b7","\u1f97":"\u03b7","\u1f91":"\u03b7","\u03ae":"\u03b7","\u1f75":"\u03b7","\u1fc4":"\u03b7","\u1f74":"\u03b7",
"\u1fc2":"\u03b7","\u1fc6":"\u03b7","\u1fc7":"\u03b7","\u1fc3":"\u03b7","\u1f30":"\u03b9","\u1f34":"\u03b9","\u1f32":"\u03b9","\u1f36":"\u03b9","\u1f31":"\u03b9","\u1f35":"\u03b9","\u1f33":"\u03b9","\u1f37":"\u03b9","\u03af":"\u03b9","\u1f77":"\u03b9","\u1f76":"\u03b9","\u1fd0":"\u03b9","\u1fd6":"\u03b9","\u03ca":"\u03b9","\u0390":"\u03b9","\u1fd3":"\u03b9","\u1fd2":"\u03b9","\u1fd7":"\u03b9","\u1fd1":"\u03b9","\u1f40":"\u03bf","\u1f44":"\u03bf","\u1f42":"\u03bf","\u1f41":"\u03bf","\u1f45":"\u03bf",
"\u1f43":"\u03bf","\u03cc":"\u03bf","\u1f79":"\u03bf","\u1f78":"\u03bf","\u1fe4":"\u03c1","\u1fe5":"\u03c1","\u1f50":"\u03c5","\u1f54":"\u03c5","\u1f52":"\u03c5","\u1f56":"\u03c5","\u1f51":"\u03c5","\u1f55":"\u03c5","\u1f53":"\u03c5","\u1f57":"\u03c5","\u03cd":"\u03c5","\u1f7b":"\u03c5","\u1f7a":"\u03c5","\u1fe0":"\u03c5","\u1fe6":"\u03c5","\u03cb":"\u03c5","\u03b0":"\u03c5","\u1fe3":"\u03c5","\u1fe2":"\u03c5","\u1fe7":"\u03c5","\u1fe1":"\u03c5","\u1f60":"\u03c9","\u1f64":"\u03c9","\u1fa4":"\u03c9",
"\u1f62":"\u03c9","\u1fa2":"\u03c9","\u1f66":"\u03c9","\u1fa6":"\u03c9","\u1fa0":"\u03c9","\u1f61":"\u03c9","\u1f65":"\u03c9","\u1fa5":"\u03c9","\u1f63":"\u03c9","\u1fa3":"\u03c9","\u1f67":"\u03c9","\u1fa7":"\u03c9","\u1fa1":"\u03c9","\u03ce":"\u03c9","\u1f7d":"\u03c9","\u1ff4":"\u03c9","\u1f7c":"\u03c9","\u1ff2":"\u03c9","\u1ff6":"\u03c9","\u1ff7":"\u03c9","\u1ff3":"\u03c9","\u0491":"\u0433","\u0450":"\u0435","\u0451":"\u0435","\u04c2":"\u0436","\u045d":"\u0438","\u04e3":"\u0438","\u04ef":"\u0443"});
n.register("$9",function(f,d,k){f.init=function(){function d(a){return h[a]||a}function g(a,b,l,c){b=a.split(b);for(var e=b.length;0!==e--;)(a=b[e])&&null==c[a]&&(l.push(a),c[a]=!0);return l}function c(a){return g(String(a||"").toLowerCase().replace(e,d),p,[],{})}function b(a,b){for(var c=[],h={},A,u=b.length,z=p;0!==u--;)(A=b[u])&&g(String(A||"").toLowerCase().replace(e,d),z,c,h);l[a]=c}function a(a,b){var c=[],e=-1,h=l,p=h.length,g,d,C,f,k,B,I=a.length,L=b?!0:!1;a:for(;++e<p;)if(C=h[e],null!=C&&
(f=C.length)){k=0;b:for(;k<I;k++){B=a[k];for(g=0;g<f;g++)if(d=C[g],0===d.indexOf(B))continue b;continue a}c.push(L?b[e]:e)}return c}var l=[],e=/[^a-z0-9]/g,p=/[\-_\s.?!;:,*^+=~`"(){}<>\[\]\/\\\u00a0\u1680\u180e\u2000-\u206f\u2e00-\u2e7f\u3000-\u303f]+/,h=n.require("$22","flatten.json");return{split:c,pull:function(b,c){return a(b,c)},find:function(b,l){return a(c(b),l)},add:function(a,b){l[a]=c(b)},push:function(a){b(l.length,a)},index:function(a,c){b(a,c)},size:function(){return l.length},clear:function(){l=
[]},remove:function(a){l[a]=null}}};return f}({},r,s));n.register("$10",function(f,d,n){f.listen=function(f,g){function c(){q[e?"show":"hide"]()}function b(a){D&&f.setAttribute("size",2+a.length);e=a;c();return a}function a(){p=null;g(e)}function l(){var c=f.value;v&&c===v&&(c="");c!==e&&(p&&clearTimeout(p),b(c),h?p=setTimeout(a,h):a())}f instanceof jQuery&&(f=f[0]);var e,p,h=150,v=d.attachEvent&&f.getAttribute("placeholder"),D=1===Number(f.size),q=k('<a href="#clear" tabindex="-1" class="icon clear"><span>clear</span></a>').click(function(){f.value=
"";l();return!1});b(f.value);k(f).on("input blur focus",function(){l();return!0}).after(q);c();return{delay:function(a){h=a},ping:function(c){c?(p&&clearTimeout(p),c=f.value,v&&c===v&&(c=""),b(c),a(),c=void 0):c=l();return c},val:function(a){if(null==a)return e;p&&clearTimeout(p);f.value=b(a);c()},el:function(){return f},blur:function(a){return k(f).on("blur",a)}}};return f}({},r,s));n.register("$11",function(f,d,n){function m(b,a){this.$element=k(b);this.options=a;this.enabled=!0;this.fixTitle()}
f.init=function(b,a){var l={fade:!0,offset:5,delayIn:g,delayOut:c,anchor:b.attr("data-anchor"),gravity:b.attr("data-gravity")||"s"};a&&(l=k.extend({},l,a));b.tipsy(l)};f.delays=function(b,a){g=b||150;c=a||100};f.kill=function(){k("div.tipsy").remove()};f.text=function(b,a){a.data("tipsy").setTitle(b)};var g,c;f.delays();k(n.body).on("overlayOpened overlayClosing",function(b){f.kill();return!0});m.prototype={show:function(){var b=this.getTitle();if(b&&this.enabled){var a=this.tip();a.find(".tipsy-inner")[this.options.html?
"html":"text"](b);a[0].className="tipsy";a.remove().css({top:0,left:0}).prependTo(n.body);var b=(b=this.options.anchor)?this.$element.find(b):this.$element,b=k.extend({},b.offset(),{width:b[0].offsetWidth,height:b[0].offsetHeight}),c=a[0].offsetWidth,e=a[0].offsetHeight,p="function"==typeof this.options.gravity?this.options.gravity.call(this.$element[0]):this.options.gravity,h;switch(p.charAt(0)){case "n":h={top:b.top+b.height+this.options.offset,left:b.left+b.width/2-c/2};break;case "s":h={top:b.top-
e-this.options.offset,left:b.left+b.width/2-c/2};break;case "e":h={top:b.top+b.height/2-e/2,left:b.left-c-this.options.offset};break;case "w":h={top:b.top+b.height/2-e/2,left:b.left+b.width+this.options.offset}}2==p.length&&("w"==p.charAt(1)?h.left=b.left+b.width/2-15:h.left=b.left+b.width/2-c+15);a.css(h).addClass("tipsy-"+p);a.find(".tipsy-arrow")[0].className="tipsy-arrow tipsy-arrow-"+p.charAt(0);this.options.className&&a.addClass("function"==typeof this.options.className?this.options.className.call(this.$element[0]):
this.options.className);a.addClass("in")}},hide:function(){this.tip().remove()},fixTitle:function(){var b=this.$element,a=b.attr("title")||"";(a||"string"!==typeof b.attr("original-title"))&&b.attr("original-title",a).removeAttr("title")},getTitle:function(){var b,a=this.$element,c=this.options;this.fixTitle();"string"==typeof c.title?b=a.attr("title"==c.title?"original-title":c.title):"function"==typeof c.title&&(b=c.title.call(a[0]));return(b=(""+b).replace(/(^\s*|\s*$)/,""))||c.fallback},setTitle:function(b){var a=
this.$element;a.attr("default-title")||a.attr("default-title",this.getTitle());null==b&&(b=a.attr("default-title")||this.getTitle());a.attr("original-title",b);if(this.$tip)this.$tip.find(".tipsy-inner")[this.options.html?"html":"text"](b)},tip:function(){this.$tip||(this.$tip=k('<div class="tipsy"></div>').html('<div class="tipsy-arrow"></div><div class="tipsy-inner"></div>'),this.$tip.data("tipsy-pointee",this.$element[0]));return this.$tip},validate:function(){this.$element[0].parentNode||(this.hide(),
this.options=this.$element=null)},enable:function(){this.enabled=!0},disable:function(){this.hide();this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled}};k.fn.tipsy=function(b){function a(a){var c=k.data(a,"tipsy");c||(c=new m(a,k.fn.tipsy.elementOptions(a,b)),k.data(a,"tipsy",c));return c}function c(){var l=a(this),e=b.delayIn;l.hoverState="in";0==e?l.show():(l.fixTitle(),setTimeout(function(){"in"==l.hoverState&&l.show()},e))}function e(){var c=a(this),l=b.delayOut;c.hoverState=
"out";0==l?c.hide():(c.tip().removeClass("in"),setTimeout(function(){"out"==c.hoverState&&c.hide()},l))}b=k.extend({},k.fn.tipsy.defaults,b);b.live||this.each(function(){a(this)});if("manual"!=b.trigger){var p=b.live?"live":"bind",h="hover"==b.trigger?"mouseleave":"blur";this[p]("hover"==b.trigger?"mouseenter":"focus",c)[p](h,e)}return this};k.fn.tipsy.defaults={className:null,delayIn:0,delayOut:0,fade:!1,fallback:"",gravity:"n",html:!1,live:!1,offset:0,opacity:0.8,title:"title",trigger:"hover",anchor:null};
k.fn.tipsy.elementOptions=function(b,a){return k.metadata?k.extend({},a,k(b).metadata()):a};k.fn.tipsy.autoNS=function(){return k(this).offset().top>k(n).scrollTop()+k(d).height()/2?"s":"n"};k.fn.tipsy.autoWE=function(){return k(this).offset().left>k(n).scrollLeft()+k(d).width()/2?"e":"w"};k.fn.tipsy.autoBounds=function(b,a){return function(){var c=a[0],e=1<a.length?a[1]:!1,p=k(n).scrollTop()+b,h=k(n).scrollLeft()+b,g=k(this);g.offset().top<p&&(c="n");g.offset().left<h&&(e="w");k(d).width()+k(n).scrollLeft()-
g.offset().left<b&&(e="e");k(d).height()+k(n).scrollTop()-g.offset().top<b&&(c="s");return c+(e?e:"")}};return f}({},r,s));n.register("$35",function(f,d,k){"".localeCompare||(String.prototype.localeCompare=function(){return 0});"".trim||(String.prototype.trim=function(){return n.require("$5","string.js").trim(this," \n\r\t")});f.html=function(){function d(){b=/[<>&]/g;a=/(\r\n|\n|\r)/g;l=/(?:https?):\/\/(\S+)/ig;e=location.hostname;d=null}function g(a){return"&#"+a.charCodeAt(0)+";"}function c(a,
b){return'<a href="'+a+'" target="'+(b.indexOf(e)?"_blank":"_top")+'">'+b+"</a>"}var b,a,l,e;return function(e,h){d&&d();var v=e.replace(b,g);h&&(v=v.replace(l,c).replace(a,"<br />"));return v}}();return f}({},r,s));n.register("$36",function(f,d,k){function m(){}var g,c,b=n.require("$21","rtl.json");f.init=function(){return new m};f.cast=function(a){return a instanceof m?a:"string"===typeof a?f.parse(a):f.clone(a)};f.clone=function(a){var b,c=new m;for(b in a)c[b]=a[b];return c};f.parse=function(a){g||
(c=/[-_+]/,g=/^([a-z]{2,3})(?:-([a-z]{4}))?(?:-([a-z]{2}|[0-9]{3}))?(?:-([0-9][a-z0-9]{3,8}|[a-z0-9]{5,8}))?(?:-([a-z]-[-a-z]+))?$/i);a=String(a).split(c).join("-");if(!g.exec(a))return null;var b=new m;b.lang=RegExp.$1.toLowerCase();if(a=RegExp.$2)b.script=a.charAt(0).toUpperCase()+a.substr(1).toLowerCase();if(a=RegExp.$3)b.region=a.toUpperCase();if(a=RegExp.$4)b.variant=a.toLowerCase();if(a=RegExp.$5)b.extension=a;return b};d=m.prototype;d.isValid=function(){return!!this.lang};d.isKnown=function(){var a=
this.lang;return!(!a||"zxx"===a)};d.toString=function(a){a=a||"-";var b,c=this.lang||"zxx";if(b=this.script)c+=a+b;if(b=this.region)c+=a+b;if(b=this.variant)c+=a+b;if(b=this.extension)c+=a+b;return c};d.getIcon=function(){for(var a=4,b,c,g=["variant","region","script","lang"],h=[];0!==a--;)if(b=g[a],c=this[b])c.join&&(c=c.join("-")),1===a&&3===c.length?h.push("region-m49"):h=h.concat([b,b+"-"+c.toLowerCase()]);return h.join(" ")};d.isRTL=function(){return!!b[String(this.script||this.lang).toLowerCase()]};
d=null;return f}({},r,s));n.register("$37",function(f,d,k){function m(a){d.console&&console.error&&console.error(a)}function g(){m("Method not implemented")}function c(){}function b(a){}c.prototype.toString=function(){return"[Undefined]"};b.prototype._validate=function(a){var b,e,p=!0;for(b in this)e=this[b],e===g?(m(a+"."+b+"() must be implemented"),p=!1):e instanceof c&&(m(a+"."+b+" must be defined"),p=!1);return p};f.init=function(a,l){var e,p=new b;if(a)for(e=a.length;0!==e--;)p[a[e]]=g;if(l)for(e=
l.length;0!==e--;)p[l[e]]=new c;return p};f.validate=function(a){var b=/function (\w+)\(/.exec(a.toString())?RegExp.$1:"";a.prototype._validate(b||"Object")};return f}({},r,s));n.register("$46",function(f,d,k){var m=d.requestAnimationFrame,g=d.cancelAnimationFrame,c=0;if(!m||!g)for(var b in{ms:1,moz:1,webkit:1,o:1})if(m=d[b+"RequestAnimationFrame"])if(g=d[b+"CancelAnimationFrame"]||d[b+"CancelRequestAnimationFrame"])break;m&&g||(m=function(b){var e=a();timeToCall=Math.max(0,16-(e-c));nextTime=e+timeToCall;
timerId=d.setTimeout(function(){b(nextTime)},timeToCall);c=nextTime;return timerId},g=function(a){clearTimeout(a)});var a=Date.now||function(){return(new Date).getTime()};f.loop=function(a,b){function c(){d=m(c,b);a(h++)}var h=0,d;c();return{stop:function(){d&&g(d);d=null}}};return f}({},r,s));n.register("$43",function(f,d,k){function m(a,c,e,l){if(b){var h=e;e=function(a){if((a.MSPOINTER_TYPE_TOUCH||"touch")===a.pointerType)return h(a)}}a.addEventListener(c,e,l);return{unbind:function(){a.removeEventListener(c,
e,l)}}}function g(a){a.preventDefault();a.stopPropagation();return!1}var c,b=!!d.navigator.msPointerEnabled,a=b?"MSPointerDown":"touchstart",l=b?"MSPointerMove":"touchmove",e=b?"MSPointerUp":"touchend";f.ok=function(a){null==c&&(c="function"===typeof k.body.addEventListener);c&&a&&a(f);return c};f.ms=function(){return b};f.dragger=function(b,c){function h(a){b.addEventListener(a,d[a],!1)}function w(a){b.removeEventListener(a,d[a],!1)}var d={};d[a]=function(b){p(b,function(e,h){h.type=a;c(b,h,u)});
h(l);h(e);return!0};d[e]=function(a){w(l);w(e);p(a,function(b,h){h.type=e;c(a,h,u)});return!0};d[l]=function(a){p(a,function(b,e){e.type=l;c(a,e,u)});return g(a)};h(a);var u={kill:function(){w(a);w(l);w(e);b=u=c=null}};return u};f.swiper=function(c,d,f){function w(a){c.addEventListener(a,k[a],!1)}function A(a){c.removeEventListener(a,k[a],!1)}function u(){z&&z.stop();z=null}var z,H,C,k={},m=[],B=[],I=[];k[a]=function(a){H=!1;u();var b=h();p(a,function(a,c){m[a]=b;B[a]=c.clientX;I[a]=c.clientY});C=
c.scrollLeft;return!0};k[e]=function(a){p(a,function(a,b){var c=h()-m[a],e=B[a]-b.clientX,c=Math.abs(e)/c;d(c,e?0>e?-1:1:0)});C=null;return!0};k[l]=function(a){var b,e;null==C||p(a,function(a,c){b=B[a]-c.clientX;e=I[a]-c.clientY});if(e&&Math.abs(e)>Math.abs(b))return H=!0;b&&(H=!0,c.scrollLeft=Math.max(0,C+b));return g(a)};if(!b||f)w(a),w(l),w(e),b&&(c.className+=" mstouch");return{kill:function(){A(a);A(l);A(e);u()},swiped:function(){return H},ms:function(){return b},snap:function(a){b&&!f&&(c.style["-ms-scroll-snap-points-x"]=
"snapInterval(0px,"+a+"px)",c.style["-ms-scroll-snap-type"]="mandatory",c.style["-ms-scroll-chaining"]="none")},scroll:function(a,b,e){u();var h=c.scrollLeft,l=a>h?1:-1,B=Math[1===l?"min":"max"],g=Math.round(16*b*l);return z=n.require("$46","fps.js").loop(function(b){b&&(h=Math.max(0,B(a,h+g)),c.scrollLeft=h,a===h&&(u(),e&&e(h)))},c)}}};f.start=function(b,c){return m(b,a,c,!1)};f.move=function(a,b){return m(a,l,b,!1)};f.end=function(a,b){return m(a,e,b,!1)};var p=f.each=function(a,c){if(b)(a.MSPOINTER_TYPE_TOUCH||
"touch")===a.pointerType&&c(0,a);else for(var e=-1,h=(a.originalEvent||a).changedTouches||[];++e<h.length;)c(e,h[e])},h=Date.now||function(){return(new Date).getTime()};return f}({},r,s));n.register("$47",function(f,d,n){f.init=function(d){function g(){l.style.top=String(-d.scrollTop)+"px";return!0}function c(){var a=l;a.textContent=d.value;a.innerHTML=a.innerHTML.replace(/[ \t]/g,b).split(/(?:\n|\r\n?)/).join('<span class="eol crlf"></span>\r\n')+'<span class="eol eof"></span>';return!0}function b(a){return'<span class="x'+
a.charCodeAt(0).toString(16)+'">'+a+"</span>"}var a=d.parentNode,l=a.insertBefore(n.createElement("div"),d);k(d).on("input",c).on("scroll",g);k(a).addClass("has-mirror");l.className="ta-mirror";var e=d.offsetWidth-d.clientWidth;2<e&&(l.style.marginRight=String(e-2)+"px");c();g();return{kill:function(){k(d).off("input",c).off("scroll",g);a.removeChild(l);l=null;k(a).removeClass("has-mirror")}}};return f}({},r,s));n.register("$29",function(f,d,k){function m(b,a){for(var c=0,e=-1,p=a&&d[a],h=g[b]||[],
v=h.length;++e<v;)callback=h[e],"function"===typeof callback&&(callback(p),c++);return c}var g={},c;f.load=function(b,a,c){function e(){v&&(clearTimeout(v),v=null);f&&(f.onreadystatechange=null,f=f=f.onload=null);b&&(delete g[b],b=null)}function p(a,h){var g=f&&f.readyState;if(h||!g||"loaded"===g||"complete"===g)h||m(b,c),e()}function h(){if(0===m(b))throw Error('Failed to load "'+(c||b)+'"');e()}if(c&&d[c])"function"===typeof a&&a(d[c]);else if(null!=g[b])g[b].push(a);else{g[b]=[a];var v=setTimeout(h,
4E3),f=k.createElement("script");f.setAttribute("src",b);f.setAttribute("async","true");f.onreadystatechange=p;f.onload=p;f.onerror=h;f.onabort=e;k.getElementsByTagName("head")[0].appendChild(f)}};f.stat=function(b){var a;if(!(a=c)){for(var l,e,g=k.getElementsByTagName("script"),h=-1,d=g.length;++h<d;)if(a=g[h].getAttribute("src"))if(l=a.indexOf("/lib/vendor"),-1!==l){e=a.substr(0,l);break}a=c=e||"/static"}return a+b};return f}({},r,s));n.register("$15",function(f,d,E){function m(a,b){a.setReadOnly(!1);
a.on("change",function(a,c){return b.val(c.getValue())});a.on("focus",function(){return b.focus()});a.on("blur",function(){return b.blur()})}function g(a){a.off("change");a.off("focus");a.off("blur")}function c(a){g(a);a.setReadOnly(!0);a.setHighlightGutterLine(!1);a.setHighlightActiveLine(!1)}function b(b,c){function e(){this.HighlightRules=l}var l=a(c),g=b.require,d=g("ace/lib/oop");d.inherits(l,g("ace/mode/text_highlight_rules").TextHighlightRules);d.inherits(e,g("ace/mode/text").Mode);return new e}
function a(a){return function(){var b={start:[{token:"empty_line",regex:/^$/},{token:"constant.language",regex:"(?:&#[0-9]+;)|(?:&#x[0-9a-fA-F]+;)|(?:&[a-zA-Z0-9_:\\.-]+;)"},{token:"constant.language",regex:/<!\[CDATA\[/},{token:"constant.language",regex:/\]\]>/},{token:"locked",regex:/<(?:xliff:)?(?:g|ph)[^>]*>[^<]*<\/(?:xliff:)?(?:g|ph)>/},{token:"locked",regex:/<(?:xliff:)?(bx|ex|x)[^\/>]*\/>/},{token:"constant.language",regex:/<\/?[:a-z]+[^>]*>/}]},c=l(a);"icu"===a?b={start:b.start.concat([{token:"icu-quoted",
regex:/'([{}][^']*)?'/},{token:"printf",regex:"{[^!-/:-@\\[-^{-~\u00a1\u00a2\u00a3\u00a4\u00a5\u00a6\u00a7\u00a9\u00ab\u00ac\u00ae\u00b0\u00b1\u00b6\u00bb\u00bf\u00d7\u00f7\\u2010-\\u2027\\u2030-\\u203E\\u2041-\\u2053\\u2055-\\u205E\\u2190-\\u245F\\u2500-\\u2775\\u2794-\\u2BFF\\u2E00-\\u2E7F\\u3001-\\u3003\\u3008-\\u3020\\u3030\\uFD3E\\uFD3F\\uFE45\\uFE46]+(,[\\s\\u0085\\u200E\\u200F\\u2028\\u2029]*(?:number|date|time|spellout|ordinal|duration)[\\s\\u0085\\u200E\\u200F\\u2028\\u2029]*(,[\\s\\u0085\\u200E\\u200F\\u2028\\u2029]*[^{}]+)?)?}"},
{token:"icu",regex:/{/,next:"icuName"},{token:"icu",regex:/}/,next:"icuType"}]),icuName:[{token:"icu",regex:"[\\s\\u0085\\u200E\\u200F\\u2028\\u2029]+"},{token:"icu.name",regex:"[^\\s\\u0085\\u200E\\u200F\\u2028\\u2029!-/:-@\\[-^{-~\u00a1\u00a2\u00a3\u00a4\u00a5\u00a6\u00a7\u00a9\u00ab\u00ac\u00ae\u00b0\u00b1\u00b6\u00bb\u00bf\u00d7\u00f7\\u2010-\\u2027\\u2030-\\u203E\\u2041-\\u2053\\u2055-\\u205E\\u2190-\\u245F\\u2500-\\u2775\\u2794-\\u2BFF\\u2E00-\\u2E7F\\u3001-\\u3003\\u3008-\\u3020\\u3030\\uFD3E\\uFD3F\\uFE45\\uFE46]+",
next:"icuType"},{defaultToken:"icu",next:"icuType"}],icuType:[{token:"icu",regex:/[{}]/,next:"start"},{defaultToken:"icu"}]}:c&&b.start.push({token:"printf",regex:c});this.$rules=b}}function l(a){switch(a){case "objc":return/%(?:\d+\$)?[-+'0# ]*\d*(?:\.\d+|\.\*(?:\d+\$)?)?(?:hh?|ll?|[qjzTL])?[sScCdDioOuUxXfFeEgGaAp%@]/;case "java":return/%(?:\d+\$)?[-+,(0# ]*\d*(?:\.\d+)?(?:[bBhHsScCdoxXeEfgGaA%n]|[tT][HIklMSLNpzZsQBbhAaCYyjmdeRTrDFc])/;case "php":return/%(?:\d+\$)?(?:'.|[-+0 ])*\d*(?:\.\d+)?[suxXbcdeEfFgGo%]/;
case "python":return/%(?:\([a-z]+\))?[-+0# ]*(?:\d+|\*)?(?:\.\d+|\.\*)?(?:[hlL])?[sdiouxXeEfFgGcra%]/;case "javascript":return/%(?:[1-9]\d*\$)?\+?(?:0|'[^$])?-?\d*(?:\.\d+)?[b-gijostTuvxX%]/;case "auto":return/%(?:\d+\$|\([a-z]+\))?(?:[-+0]?\d*(\.\d+)?[duxoefgaDUXOEFGA]|[@scSC%])/;case p:return e||"%%"}}var e,p="auto";f.init=function(a,e,l){var f,w=!1,A=l||p,u=a.parentNode,z=u.appendChild(E.createElement("div"));k(u).addClass("has-proxy has-ace");n.require("$29","remote.js").load("https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.1/ace.js",
function(l){if(z){if(!l)throw Error("Failed to load code editor");f=l.edit(z);var g=f.session,d=f.renderer;f.$blockScrolling=Infinity;f.setShowInvisibles(w);f.setWrapBehavioursEnabled(!1);f.setBehavioursEnabled(!1);f.setHighlightActiveLine(!1);g.setUseSoftTabs(!1);d.setShowGutter(!0);d.setPadding(10);d.setScrollMargin(8);g.setMode(b(l,A));f.setValue(a.value,-1);g.setUseWrapMode(!0);e?m(f,e):c(f)}},"ace");return{kill:function(){f&&(g(f),f.destroy(),f=null);z&&(u.removeChild(z),k(u).removeClass("has-proxy has-ace"),
z=null);return this},disable:function(){f&&c(f);e=null;return this},enable:function(a){e=a;f&&m(f,a);return this},resize:function(){f&&f.resize();return this},val:function(a){f&&a!==f.getValue()&&f.setValue(a,-1);return this},invs:function(a){a=a||!1;w!==a&&(w=a,f&&f.setShowInvisibles(a));return this},strf:function(a){a=a||p;a!==A&&(A=a,f&&f.session.setMode(b(d.ace,a)));return this},focus:function(){return this}}};f.strf=function(a,b){p=a;e=b;return f};return f}({},r,s));n.register("$48",function(f,
d,E){function m(a,b){function c(){return b.val(a.getContent())}a.on("input",c);a.on("change",c);a.on("focus",function(){return b.focus()});a.on("blur",function(){return b.blur()});a.setMode("design")}function g(a){a.off("input");a.off("change");a.off("focus");a.off("blur")}function c(a){g(a);a.setMode("readonly")}var b=0;f.load=function(a){var b=n.require("$29","remote.js");b.load(b.stat("/lib/tinymce.min.js"),a,"tinymce");return f};f.init=function(a,l){function e(a){D=a;q="<p>"===a.substr(0,3)&&
"</p>"===a.substr(-4);return a.replace(/(<\/?)script/ig,"$1loco:script")}function d(a){h=a;a._getContent=a.getContent;a.getContent=function(a){a=this._getContent(a);a=a.replace(/(<\/?)loco:script/ig,"$1script");if(!q&&"<p>"===a.substr(0,3)&&"</p>"===a.substr(-4)){var b=a.substr(3,a.length-7);if(b===D||-1===b.indexOf("</p>"))a=b}return a};a._setContent=a.setContent;a.setContent=function(a,b){return this._setContent(e(a),b)};l?(m(a,l),l.reset()):c(a);k(u).removeClass("loading")}var h,v=!1,D="",q=!1,
w=a.parentNode,A=w.parentNode,u=w.appendChild(E.createElement("div")),z=A.insertBefore(E.createElement("nav"),w);z.id="_tb"+String(++b);k(w).addClass("has-proxy has-mce");k(u).addClass("mce-content-body loading").html(e(a.value));f.load(function(a){if(!a)throw Error("Failed to load HTML editor");u&&a.init({inline:!0,target:u,hidden_input:!1,theme:"modern",skin:!1,plugins:"link lists",browser_spellcheck:!0,menubar:!1,fixed_toolbar_container:"#"+z.id,toolbar:"formatselect | bold italic link unlink | bullist numlist outdent indent",
block_formats:"Paragraph=p;Heading 1=h1;Heading 2=h2;Heading 3=h4;Heading 4=h4;Heading 5=h5;Heading 6=h6;",forced_root_block:"p",relative_urls:!1,convert_urls:!1,remove_script_host:!1,document_base_url:"",allow_script_urls:!1,formats:{alignleft:{classes:"alignleft"},alignright:{selector:"p,h1,h2,h3,h4,span,strong,em,a",classes:"alignright"},aligncenter:{selector:"p,h1,h2,h3,h4,span,strong,em,a",classes:"aligncenter"},strikethrough:{inline:"del"}},fix_list_elements:!0,extended_valid_elements:"span,b,i,u,loco:script",
entities:"38,amp,60,lt,62,gt,160,nbsp",entity_encoding:"named",keep_styles:!1,init_instance_callback:d})});return{val:function(b){b=e(b);null==h?(a.value=b,k(u).html(b)):h.getContent()!==b&&h.setContent(b);l&&l.val(b);return this},kill:function(){h&&(l&&l.val(h.getContent()),g(h),h.destroy(),h=null);u&&(w.removeChild(u),k(w).removeClass("has-proxy has-mce"),u=null);z&&(A.removeChild(z),z=null);return this},enable:function(a){l=a;h&&m(h,a);return this},disable:function(){h&&c(h);l=null;return this},
focus:function(){h&&l&&h.focus();return this},invs:function(a){a=a||!1;v!==a&&(v=a,k(w)[a?"addClass":"removeClass"]("show-invs"));return this}}};return f}({},r,s));n.register("$44",function(f,d,E){function m(b){function a(){d&&(f.off("input",l),d=!1)}function l(){var a=b.value;a!==w&&(f.trigger("changing",[a,w]),w=a)}function e(){l();d&&A!==w&&f.trigger("changed",[w])}function g(){c=b;A=w;d||(f.on("input",l),d=!0);f.trigger("editFocus");q.addClass("has-focus");return!0}function h(){c===b&&(c=null);
f.trigger("editBlur");q.removeClass("has-focus");d&&(e(),a());return!0}var d=!1,f=k(b),q=k(b.parentNode),w=b.value,A;f.blur(h).focus(g);return{val:function(a){w!==a&&(b.value=a,f.triggerHandler("input"),w=a);return!0},kill:function(){a();f.off("blur",h).off("focus",g)},fire:function(){w=null;l()},ping:e,blur:h,focus:g,reset:function(){A=w=b.value}}}function g(b){this.e=b}var c;f._new=function(b){return new g(b)};f.init=function(b){var a=new g(b);b.disabled?(b.removeAttribute("disabled"),a.disable()):
b.readOnly?a.disable():a.enable();return a};TextAreaPrototype=g.prototype;TextAreaPrototype.destroy=function(){this.unlisten();var b=this.p;b&&(b.kill(),this.p=null);this.e=null};TextAreaPrototype.reload=function(b,a){var c=this.l;c&&!a&&(this.disable(),c=null);this.val(b||"");a&&!c&&this.enable();return this};TextAreaPrototype.val=function(b){var a=this.e;if(null==b)return a.value;var c=this.l,e=this.p;e&&e.val(b);c&&c.val(b);c||a.value===b||(a.value=b,k(a).triggerHandler("input"));return this};
TextAreaPrototype.fire=function(){this.l&&this.l.fire();return this};TextAreaPrototype.ping=function(){this.l&&this.l.ping();return this};TextAreaPrototype.focus=function(){var b=this.p;b?b.focus():k(this.e).focus()};TextAreaPrototype.focused=function(){return c&&c===this.el};TextAreaPrototype.parent=function(){return this.e.parentNode};TextAreaPrototype.attr=function(b,a){var c=this.e;if(1===arguments.length)return c.getAttribute(b);null==a?c.removeAttribute(b):c.setAttribute(b,a);return this};TextAreaPrototype.editable=
function(){return!!this.l};TextAreaPrototype.enable=function(){var b=this.p;this.e.removeAttribute("readonly");this.listen();b&&b.enable&&b.enable(this.l);return this};TextAreaPrototype.disable=function(){var b=this.p;this.e.setAttribute("readonly",!0);this.unlisten();b&&b.disable&&b.disable();return this};TextAreaPrototype.listen=function(){var b=this.l;b&&b.kill();this.l=m(this.e);return this};TextAreaPrototype.unlisten=function(){var b=this.l;b&&(b.kill(),this.l=null);return this};TextAreaPrototype.setInvs=
function(b,a){var c=this.i||!1;if(a||c!==b)this._i&&(this._i.kill(),delete this._i),(c=this.p)?c.invs&&c.invs(b):b&&(this._i=n.require("$47","mirror.js").init(this.e)),this.i=b;return this};TextAreaPrototype.getInvs=function(){return this.i||!1};TextAreaPrototype.setMode=function(b){var a=this.p,g=this.i||!1;b!==(this.m||"")&&(this.m=b,a&&a.kill(),this.p=a="code"===b?n.require("$15","ace.js").init(this.e,this.l,this["%"]):"html"===b?n.require("$48","mce.js").init(this.e,this.l):null,this.setInvs(g,
!0),c&&this.focus());return this};TextAreaPrototype.setStrf=function(b){this["%"]=b;"code"===this.m&&this.p.strf(b);return this};TextAreaPrototype.name=function(b){this.e.setAttribute("name",b);return this};TextAreaPrototype.placeholder=function(b){this.e.setAttribute("placeholder",b);return this};TextAreaPrototype.redraw=function(){var b=this.p;b&&b.resize&&b.resize()};TextAreaPrototype=null;return f}({},r,s));n.register("$45",function(f,d,n){function m(a){var b=d.console;b&&b.error&&b.error(a)}
function g(a){var b=n.createElement("div");a&&b.setAttribute("class",a);return b}function c(a){return function(){a.resize();return this}}function b(a){return function(b){for(var c=b.target,e=c.$index;null==e&&"DIV"!==c.nodeName&&(c=c.parentElement);)e=c.$index;null!=e&&(b.stopImmediatePropagation(),a.select(e));return!0}}function a(a){return function(){a.redrawDirty()&&a.redraw();return!0}}function l(a){return function(b){var c;c=b.keyCode;if(40===c)c=1;else if(38===c)c=-1;else return!0;if(b.shiftKey||
b.ctrlKey||b.metaKey||b.altKey)return!0;a.selectNext(c);b.stopPropagation();b.preventDefault();return!1}}function e(a,b,c){function e(a){m("row["+a+"] disappeared");return{cellVal:function(){return""}}}return function(h){var g=b||0,d=c?-1:1,l=a.rows||[];h.sort(function(a,b){return d*(l[a]||e(a)).cellVal(g).localeCompare((l[b]||e(b)).cellVal(g))})}}function p(a){this.w=a}function h(a){this.t=a;this.length=0}function v(a,b,c){var e=n.createElement("div");e.className=c||"";this._=e;this.d=b||[];this.i=
a||0;this.length=b.length}function D(a){this.live=a;this.rows=[]}f.create=function(a){return new p(a)};var q=p.prototype;q.init=function(e){var h=this.w,d=h.id,p=h.splity(d+"-thead",d+"-tbody"),f=p[0],p=p[1],C=[],q=[],m=[],B=[];if(e)this.ds=e,this.idxs=q,this._idxs=null;else if(!(e=this.ds))throw Error("No datasource");f.css.push("wg-thead");p.css.push("wg-tbody");e.eachCol(function(a,b,c){m[a]=d+"-col-"+b;B[a]=c||b});for(var I=g(),L=-1,D=m.length,n=g("wg-cols"),J=f.splitx.apply(f,m);++L<D;)J[L].header(B[L]),
n.appendChild(I.cloneNode(!1)).setAttribute("for",m[L]);e.eachRow(function(a,b,c){C[a]=new v(a,b,c);q[a]=a});this.rows=C;this.cols=n;this.ww=null;this.root=I=p.body;this.head=f;f.redraw=c(this);f=p.fixed=J[0].bodyY()||20;h.lock().resize(f,p);h.css.push("is-table");h.restyle();this.sc?this._re_sort(D):e.sort&&e.sort(q);this.redrawDirty();this.render();k(I).attr("tabindex","-1").on("keydown",l(this)).on("mousedown",b(this)).on("scroll",a(this));return this};q.clear=function(){for(var a=this.pages||
[],b=a.length;0!==b--;)a[b].destroy();this.pages=[];this.sy=this.mx=this.mn=this.vh=null;void 0;return this};q.render=function(){for(var a,b,c=[],e=this.rows||[],h=-1,g,d=this.idxs,l=d.length,B=this.idxr={},p=this.r,f=this._r,v=this.root,k=this.cols;++h<l;){0===h%100&&(a=k.cloneNode(!0),b=new D(a),b.h=2200,b.insert(v),c.push(b));g=d[h];B[g]=h;a=e[g];if(null==a)throw Error("Render error, no data at ["+g+"]");a.page=b;b.rows.push(a)}b&&100!==b.size()&&b.sleepH(22);this.pages=c;this.mx=this.mn=null;
this.redrawDirty();this.redraw();null==p?null!=f&&(a=e[f])&&a.page&&(delete this._r,this.select(f,!0)):(a=e[p])&&a.page?this.select(p,!0):(this.deselect(),this._r=p);return this};q.resize=function(){var a=-1,b=this.ww||(this.ww=[]),c=this.w,e=c.cells[0],h=e.body.childNodes,g=h.length,d=this.pages||[],l=d.length;for(c.redraw.call(e);++a<g;)b[a]=h[a].style.width;if(l){c=this.mx;for(a=this.mn;a<=c;a++)d[a].widths(b);this.redrawDirty()&&this.redraw()}};q.redrawDirty=function(){var a=!1,b=this.root,c=
b.scrollTop,b=b.clientHeight;this.sy!==c&&(a=!0,this.sy=c);this.vh!==b&&(a=!0,this.vh=b);return a};q.redraw=function(){for(var a=0,b=-1,c=null,e=null,h=this.ww,g=this.sy,d=this.vh,l=this.mn,B=this.mx,p=Math.max(0,g-100),g=d+g+100,f=this.pages||[],v=f.length;++b<v&&!(a>g);)d=f[b],a+=d.height(),a<p||(null===c&&(c=b),e=b,d.rendered||d.render(h));if(l!==c){if(null!==l&&c>l)for(b=l;b<c;b++){d=f[b];if(!d)throw Error("Shit!");d.rendered&&d.sleep()}this.mn=c}if(B!==e){if(null!==B&&e<B)for(b=B;b>e;b--)d=f[b],
d.rendered&&d.sleep();this.mx=e}};q.selected=function(){return this.r};q.thead=function(){return this.w.cells[0]};q.tbody=function(){return this.w.cells[1]};q.tr=function(a){return(a=this.row(a))?a.cells():[]};q.row=function(a){return this.rows[a]};q.td=function(a,b){return this.tr(a)[b]};q.next=function(a,b,c){null==c&&(c=this.r||0);var e=this.idxs,h=e.length,d=(this.idxr||{})[c];for(c=d;c!==(d+=a)&&!(0<=d&&h>d);)if(b&&h)d=1===a?-1:h,b=!1;else return null;c=e[d];return null==c||null==this.rows[c]?
(m("Bad next: ["+d+"] does not map to data row"),null):c};q.selectNext=function(a,b,c){a=this.next(a,b);null!=a&&this.r!==a&&this.select(a,c);return this};q.deselect=function(a){var b=this.r;null!=b&&(this.r=null,k(this.tr(b)).removeClass("selected"),this.w.fire("wgRowDeselect",[b,a]));return this};q.selectRow=function(a,b){return this.select(this.idxs[a])};q.select=function(a,b){var c=this.rows[a],e=c&&c.page;if(!e)return this.deselect(!1),m("Row is filtered out"),this;this.deselect(!0);var d,h=
this.w.cells[1];e.rendered||(d=e.top(),h.scrollY(d),this.redrawDirty()&&this.redraw());if(!c.rendered)return e.rendered||m("Failed to render page"),m("Row ["+c.i+"] not rendered"),this;e=c.cells();k(e).addClass("selected");this.r=a;b||(d=h.scrollY(),k(this.root).focus(),d!==h.scrollY()&&h.scrollY(d));h.scrollTo(e[0],!0);this.w.fire("wgRowSelect",[a,c.data()]);return this};q.unfilter=function(){this._idxs&&(this.idxs=this._sort(this._idxs),this._idxs=null,this.clear().render());return this};q.filter=
function(a){this._idxs||(this._idxs=this.idxs);this.idxs=this._sort(a);return this.clear().render()};q.each=function(a){for(var b,c=-1,e=this.rows||[],d=this.idxs||[],h=d.length;++c<h;)b=d[c],a(e[b],c,b);return this};q.sortable=function(a){var b=this.sc||(this.sc=new h(this));b.has(a)||b.add(a);return this};q._re_sort=function(a){var b=-1,c=this.sc,e=c.active;for(this.sc=c=new h(this);++b<a;)c.add(b);e&&(b=this.head.indexOf(e.id),-1===b&&(b=Math.min(e.idx,a-1)),this.sort(b,e.desc));return this};q._sort=
function(a,b){b?(this.s=b,b(a)):(b=this.s)&&b(a);return a};q.sort=function(a,b){this._sort(this.idxs,e(this,a,b));this.sc.activate(a,b);return this};q=null;q=h.prototype;q.has=function(a){return null!=this[a]};q.add=function(a){var b=this,c=b.t.head.cells[a];b[a]={desc:null,idx:a,id:c.id};b.length++;c.addClass("wg-sortable").on("click",function(c){if("header"===c.target.nodeName.toLowerCase())return c.stopImmediatePropagation(),b.toggle(a),!1});return b};q.toggle=function(a){this.t.sort(a,!this[a].desc).clear().render();
return this};q.activate=function(a,b){var c,e;c=this.active;var d=this[a],h=this.t.head.cells;c&&(e=h[c.idx])&&(e.removeClass(c.css),c!==d&&e.restyle());(e=h[a])?(d.desc=b,this.active=d,c="wg-"+(b?"desc":"asc"),e.addClass(c).restyle(),d.css=c):this.active=null;return this};q=null;q=v.prototype;q.render=function(a){var b,c=[],e=this._,d=this.length;if(e){for(this.c=c;0!==d--;)b=e.cloneNode(!1),c[d]=this.update(d,b),b.$index=this.i,a[d].appendChild(b);this._=null}else for(c=this.c;0!==d--;)a[d].appendChild(c[d]);
this.rendered=!0;return this};q.update=function(a,b){var c=b||this.c[a]||{},e=(this.d[a]||function(){})()||"\u00a0";null==e.innerHTML?c.textContent=e:c.innerHTML=e.innerHTML;return c};q.cells=function(){return this.c||[this._]};q.data=function(){for(var a=-1,b=[],c=this.length;++a<c;)b[a]=this.cellVal(a);return b};q.destroy=function(){this.page=null;this.rendered=!1};q.cellVal=function(a){a=this.d[a]()||"";return String(a.textContent||a)};q=null;q=D.prototype;q.size=function(){return this.rows.length};
q.insert=function(a){var b=this.h,c=g("wg-dead");c.style.height=String(b)+"px";a.appendChild(c);return this.dead=c};q.top=function(){return(this.rendered?this.live:this.dead).offsetTop};q.height=function(){var a=this.h;null==a&&(this.h=a=this.rendered?this.live.firstChild.offsetHeight:this.dead.offsetHight);a||m("row has zero height");return a};q.render=function(a){for(var b,c=-1,e=this.rows,d=e.length,h=this.dead,g=this.live,l=g.childNodes;++c<d;)b=e[c],b.rendered||b.render(l);d=a.length;for(c=0;c<
d;c++)l[c].style.width=a[c];h.parentNode.replaceChild(g,h);this.rendered=!0;this.h=null;return this};q.sleep=function(){var a=this.height(),b=this.live,c=this.dead;c.style.height=String(a)+"px";b.parentNode.replaceChild(c,b);this.rendered=!1;this.h=a;return this};q.sleepH=function(a){a*=this.rows.length;var b=this.dead;b&&(b.style.height=String(a)+"px");this.rendered||(this.h=a);return this};q.widths=function(a){for(var b=this.live.childNodes,c=a.length;0!==c--;)b[c].style.width=a[c];return this};
q.destroy=function(){var a=this.rendered?this.live:this.dead,b=this.rows,c=b.length;for(a.parentNode.removeChild(a);0!==c--;)b[c].destroy()};q=null;return f}({},r,s));n.register("$38",function(f,d,s){function m(a,b){var c=a.id,e=c&&w[c],d=e&&e.parent();if(!e||!d)return null;var h=d.dir===q,c=h?"X":"Y",g="page"+c,h=h?D:v,l=h(d.el),c=b["offset"+c],p=d.el,f=p.className;null==c&&(c=b[g]-h(a));c&&(l+=c);p.className=f+" is-resizing";return{done:function(){p.className=f},move:function(a){d.resize(a[g]-l,
e);return!0}}}function g(a,c){function e(){k(s).off("mousemove",d);u&&(u.done(),u=null);return!0}function d(a){u?u.move(a):e();return!0}if(u)return!0;u=m(a.target,a);if(!u)return!0;k(s).one("mouseup",e).on("mousemove",d);return b(a)}function c(a,b){var c=b.type;"touchmove"===c?u&&u.move(b):"touchstart"===c?u=m(a.target,b):"touchend"===c&&u&&(u.done(),u=null)}function b(a){a.stopPropagation();a.preventDefault();return!1}function a(a){var b=A;b&&b.redraw();a&&a.redraw();return A=a}function l(b,c){var e=
k(c).on("editFocus",function(){e.trigger("wgFocus",[a(b)])}).on("editBlur",function(){e.trigger("wgBlur",[a(null)])})}function e(a){var b=a.id,c=a.className;this.id=b;this.el=a;this.pos=this.index=0;this.css=[c||"wg-root","wg-cell"];this._cn=c;w[b]=this;this.clear()}var p=n.include("$42","html.js")||n.include("$2","html.js",!0),h=n.require("$20","dom.js"),v=h.top,D=h.left,q=1,w={},A,u=!1;f.init=function(a){var b=new e(a);b.redraw();n.require("$43","touch.js").ok(function(b){b.dragger(a,c)});k(a).mousedown(g);
return b};d=e.prototype;d.fire=function(a,b){var c=k.Event(a);c.cell=this;k(this.el).trigger(c,b);return this};d.each=function(a){for(var b=-1,c=this.cells,e=c.length;++b<e;)a(c[b],b);return this};d.indexOf=function(a){return(a=w[a.id||String(a)])&&a.pid===this.id?a.index:-1};d.on=function(){return this.$("on",arguments)};d.off=function(){return this.$("off",arguments)};d.find=function(a){return k(this.el).find(a)};d.$=function(a,b){k.fn[a].apply(k(this.el),b);return this};d.addClass=function(a){this.css.push(a);
return this};d.removeClass=function(a){a=this.css.indexOf(a);-1!==a&&this.css.splice(a,1);return this};d.parent=function(){return this.pid&&w[this.pid]};d.splitx=function(){return this._split(q,arguments)};d.splity=function(){return this._split(2,arguments)};d._split=function(a,b){(this.length||this.field)&&this.clear();for(var c=-1,d,g=b.length,l=1/g,p=0;++c<g;){d=h.el();this.body.appendChild(d);for(var f=d,v=b[c],k=v,q=1;w[v];)v=k+"-"+ ++q;f.id=v;d=new e(d);d.index=c;d.pid=this.id;d._locale(this.lang,
this.rtl);d.pos=p;p+=l;this.cells.push(d);this.length++}this.dir=a;this.redraw();return this.cells};d.destroy=function(){this.clear();delete w[this.id];var a=this.el;a.innerHTML="";this.body=null;a.className=this._cn||"";k(a).off();return this};d.exists=function(){return this===w[this.id]};d.clear=function(){for(var a=this.el,b=this.cells,c=this.field,e=this.body,d=this.nav,g=this.length||0;0!==g--;)delete w[b[g].destroy().id];this.cells=[];this.length=0;d&&(a.removeChild(d),this.nav=null);e&&(c&&
(p.ie()&&k(e).triggerHandler("blur"),c.destroy(),this.field=null),this.table&&(this.table=null),a===e.parentNode&&a.removeChild(e));this.body=a.appendChild(h.el("","wg-body"));this._h=null;return this};d.resize=function(a,b){if(!b&&(b=this.cells[1],!b))return;var c=b.index,e=this.cells,d=k(this.el)[this.dir===q?"width":"height"](),h=e[c+1],c=e[c-1];pad=(b.body||b.el.firstChild).offsetTop||0;max=(h?h.pos*d:d)-pad;min=c?c.pos*d:0;b.pos=Math.min(max,Math.max(min,a))/d;this.redraw();return this};d.distribute=
function(a){for(var b=-1,c=0,e,d=this.cells,h=a.length;++b<h&&(e=d[++c]);)e.pos=Math.max(0,Math.min(1,a[b]));this.redraw();return this};d.distribution=function(){for(var a=[],b=0,c=this.cells,e=c.length-1;b<e;)a[b]=c[++b].pos;return a};d.restyle=function(){var a=this.css.concat();0===this.index?a.push("first"):a.push("not-first");this.dir&&(a.push("wg-split"),2===this.dir?a.push("wg-split-y"):a.push("wg-split-x"));this.t&&a.push("has-title");this.nav&&a.push("has-nav");this.field&&(a.push("is-field"),
this.field.editable()?a.push("is-editable"):a.push("is-readonly"));a=a.join(" ");a!==this._css&&(this._css=a,this.el.className=a);return this};d.redraw=function(a){this.restyle();var b=this.el,c=this.body,e=this.field;if(c){var d,h=b.clientWidth||0,g=b.clientHeight||0,l=c.offsetTop||0,g=l>g?0:g-l;this._h!==g&&(this._h=g,c.style.height=String(g)+"px",d=e);this._w!==h&&(this._w=h,d=e);d&&d.redraw()}c=this.length;h=1;g=this.nav;for(l=2===this.dir?"height":"width";0!==c--;)e=this.cells[c],g?d=1:(e.fixed&&
(e.pos=e.fixed/k(b)[l]()),d=h-e.pos,h=e.pos),e.el.style[l]=String(100*d)+"%",e.redraw(a);return this};d.contents=function(a,b){var c=this.el,e=this.body;if(null==a)return e.innerHTML;this.length?this.clear():e&&(c.removeChild(e),e=null);e||(this.body=e=c.appendChild(h.el("",b||"wg-content")),this._h=null,(c=this.lang)&&this._locale(c,this.rtl,!0));"string"===typeof a?k(e)._html(a):a&&this.append(a);this.redraw();return this};d.textarea=function(a,b){var c=this.field;if(c){var e=c.editable();c.reload(a,
b);e!==b&&this.restyle()}else this.length&&this.clear(),e=h.el("textarea"),e.setAttribute("wrap","virtual"),e.value=a,this.contents(e),c=n.require("$44","field.js")._new(e)[b?"enable":"disable"](),l(this,e),this.field=c,this.restyle();this.lang||this.locale("en");return c};d.locale=function(a){a=n.require("$36","locale.js").cast(a);return this._locale(String(a),a.isRTL())};d._locale=function(a,b,c){var e=this.body;if(c||a!==this.lang)this.lang=a,e&&e.setAttribute("lang",a);if(c||b!==this.rtl)this.rtl=
b,e&&e.setAttribute("dir",b?"RTL":"LTR");return this};d.editable=function(){var a=this.field;if(a)return a.editable()?a:null;var b=this.cells,c=b.length,e=this.navigated();if(null!=e)return b[e].editable();for(;++e<c;){for(e=0;e<c;c++);if(a=b[e].editable())return a}};d.eachTextarea=function(a){var b=this.field;b?a(b):this.each(function(b){b.eachTextarea(a)});return this};d.append=function(a){a&&(a.nodeType?p.init(this.body.appendChild(a)):p.init(k(a).appendTo(this.body)));return this};d.prepend=function(a){var b=
this.body;if(a.nodeType){var c=b.firstChild;p.init(c?b.insertBefore(a,c):b.appendChild(a))}else p.init(k(a).prependTo(b));return this};d.before=function(a){var b=this.body;a.nodeType?p.init(this.el.insertBefore(a,b)):p.init(k(a).insertBefore(b));return this};d.header=function(a,b){if(null==a&&null==b)return this.el.getElementsByTagName("header")[0];this.t=h.txt(a||"");this.el.insertBefore(h.el("header",b),this.body).appendChild(this.t);this.redraw();return this};d.title=function(a){var b=this.t;if(b)return b.nodeValue=
a||"",b;this.header(a);return this.t};d.titled=function(){var a=this.t;return a&&a.nodeValue};d.bodyY=function(){return v(this.body,this.el)};d.scrollY=function(a){if(R===a)return this.body.scrollTop;this.body.scrollTop=a};d.tabulate=function(a){var b=this.table;b?b.clear():b=n.require("$45","wgtable.js").create(this);b.init(a);return this.table=b};d.lock=function(){this.body.className+=" locked";return this};d.scrollTo=function(a,b){var c,e=this.body;c=e.scrollTop;var d=v(a,e);if(c>d)c=d;else{var h=
e.clientHeight,d=d+k(a).outerHeight();if(h+c<d)c=d-h;else return}b?e.scrollTop=c:k(e).stop(!0).animate({scrollTop:c},250)};d.navigize=function(a,c){function e(a){var b=g[a],c=f[a],d=k(b.el).show();c.addClass("active");p=a;v.data("idx",a);b.fire("wgTabSelect",[a]);return d}var d=this,g=d.cells,l=d.nav,p,f=[];l&&d.el.removeChild(l);var l=d.nav=d.el.insertBefore(h.el("nav","wg-tabs"),d.body),v=k(l).on("click",function(a){var c=k(a.target).data("idx");if(null==c)return!0;if(null!=p){var h=f[p];k(g[p].el).hide();
h.removeClass("active")}e(c);d.redraw();return b(a)});null==c&&(c=v.data("idx")||0);d.each(function(b,c){f[c]=k('<a href="#'+b.id+'"></a>').data("idx",c).text(a[c]).appendTo(v);b.pos=0;k(b.el).hide()});e(g[c]?c:0);d.lock();d.redraw();return d};d.navigated=function(){var a=this.nav;if(a)return k(a).data("idx")};d=null;return f}({},r,s));n.register("$23",function(f,d,s){function m(a){var b=[];a&&(a.saved()||b.push("po-unsaved"),a.fuzzy()?b.push("po-fuzzy"):a.flagged()&&b.push("po-flagged"),a.translation()||
b.push("po-empty"),a.comment()&&b.push("po-comment"));return b.join(" ")}function g(a,b,c){b=k(a.title(b).parentNode);var e=b.find("span.lang");c?(c=n.require("$36","locale.js").cast(c),e.length||(e=k("<span></span>").prependTo(b)),e.attr("lang",c.lang).attr("class",c.getIcon()||"lang region region-"+(c.region||"zz").toLowerCase())):(e.remove(),c="en");a.locale(c);return b}function c(a,b,c){b.click(function(b){var e=a.fire(c,[b.target]);e||b.preventDefault();return e})}function b(){this.dirty=0}n.require("$3",
"number.js");var a="poUpdate",l="changing",e="changed",p=0,h=1,v=2,D=3,q=4,w=5,A=/^\s+/,u,z,r=n.require("$35","string.js").html,C=n.require("$5","string.js").sprintf;f.extend=function(a){return a.prototype=new b};f.localise=function(a){z=a;return f};var F=function(){var a=s.createElement("p");return function(b){a.innerHTML=b.replace("src=","x=");return a.textContent}}(),x=b.prototype=n.require("$37","abstract.js").init(["getListColumns","getListHeadings","getListEntry"],["editable","t"]);x.init=function(){this.localise();
this.editable={source:!0,target:!0};this.mode="";this.html=!1;return this};x.localise=function(a){a||(a=z||n.require("$1","t.js").init());var b=[];b[p]=a._x("Source text","Editor")+":";b[D]=a._x("%s translation","Editor")+":";b[q]=a._x("Context","Editor")+":";b[w]=a._x("Comments","Editor")+":";b[h]=a._x("Single","Editor")+":";b[v]=a._x("Plural","Editor")+":";b[6]=a._x("Untranslated","Editor");b[7]=a._x("Translated","Editor");b[8]=a._x("Toggle Fuzzy","Editor");b[9]=a._x("Suggest translation","Editor");
this.labels=b;this.t=a;return this};x.setRootCell=function(a){function b(a){c.redraw(!0,a);return!0}var c=n.require("$38","wingrid.js").init(a);k(d).on("resize",b);this.redraw=b;k(a).on("wgFocus wgBlur",function(a,b){a.stopPropagation();u=b});this.destroy=function(){c.destroy();k(d).off("resize",b)};this.rootDiv=a;return c};x.$=function(){return k(this.rootDiv)};x.setListCell=function(a){var b=this;b.listCell=a;a.on("wgRowSelect",function(a,c){b.loadMessage(b.po.row(c));return!0}).on("wgRowDeselect",
function(a,c,e){e||b.loadNothing();return!0})};x.setSourceCell=function(a){this.sourceCell=a};x.setTargetCell=function(a){this.targetCell=a};x.next=function(a,b,c){for(var e=this.listTable,d=e.selected(),h=d,g,l=this.po;null!=(d=e.next(a,c,d));){if(h===d){d=null;break}if(b&&(g=l.row(d),g.translated(0)))continue;break}null!=d&&e.select(d,!0);return d};x.current=function(a){var b=this.active;if(null==a)return b;a?a.is(b)?this.reloadMessage(a):this.loadMessage(a):this.unloadActive();return this};x.getTargetOffset=
function(){if(this.active)return this.targetCell&&this.targetCell.navigated()||0};x.getTargetEditable=function(){return this.editable.target&&this.targetCell&&this.targetCell.editable()};x.getSourceEditable=function(){return this.editable.source&&this.sourceCell&&this.sourceCell.editable()};x.getContextEditable=function(){return this.editable.context&&this.contextCell&&this.contextCell.editable()};x.getFirstEditable=function(){return this.getTargetEditable()||this.getSourceEditable()||this.getContextEditable()};
x.searchable=function(a){a&&(this.dict=a,this.po&&this.rebuildSearch());return this.dict&&!0};x.rebuildSearch=function(){var a=-1,b=this.po.rows,c=b.length,e=this.dict;for(e.clear();++a<c;)e.add(a,b[a].toText())};x.filtered=function(){return this.lastSearch||""};x.filter=function(a,b){var c,e=this.listTable,d=this.lastFound,h=this.lastSearch;if(a){if(h===a)return d||0;if(h&&!d&&0===a.indexOf(h))return 0;c=this.dict.find(a)}this.lastSearch=h=a;this.lastFound=d=c?c.length:this.po.length;c?e.filter(c):
e.unfilter();b||this.fire("poFilter",[h,d]);return d};x.countFiltered=function(){return this.lastSearch?this.lastFound:this.po.length};x.unsave=function(a,b){var c=!1;if(a){if(c=a.saved(b))this.dirty++,a.unsave(b),this.fire("poUnsaved",[a,b]);this.markUnsaved(a)}return c};x.markUnsaved=function(a){var b=this.po.indexOf(a);if((b=this.listTable.tr(b))&&b.length){var c=b[0].className;a=c.replace(/(?:^| +)po-[a-z]+/g,"")+" "+m(a);a!==c&&k(b).attr("class",a)}};x.save=function(a){var b=this.po;if(this.dirty||
a)b.each(function(a,b){b.save()}),this.listCell.find("div.po-unsaved").removeClass("po-unsaved"),this.dirty=0,this.fire("poSave");return b};x.fire=function(a,b){var c=this.handle;if(c&&c[a]&&(c=c[a].apply(this,b||[]),!1===c))return!1;c=k.Event(a);this.$().trigger(c,b);return!c.isDefaultPrevented()};x.on=function(a,b){this.$().on(a,b);return this};x.getSorter=function(){return null};x.reload=function(){var a=this,b,c=a.listCell,e=a.listTable,d=a.po,h=d&&d.locale(),g=h&&h.isRTL(),l=d&&d.length||0;if(!d||
!d.row)return c&&c.clear().header("Error").contents("Invalid messages list"),!1;a.targetLocale=h;a.lastSearch&&(a.lastSearch="",a.lastFound=l,a.fire("poFilter",["",l]));e&&(b=e.thead().distribution());a.listTable=e=c.tabulate({eachCol:function(b){var c,e,d=a.getListColumns(),h=a.getListHeadings();for(e in d)c=d[e],b(c,e,h[c])},eachRow:function(b){d.each(function(c,e){b(e.idx,a.getListEntry(e),m(e))})},sort:a.getSorter()});var p,c=a.getListColumns();for(p in c)e.sortable(c[p]);b&&e.thead().distribute(b);
e.tbody().$(g?"addClass":"removeClass",["is-rtl"]);a.fire("poLoad");return!!l};x.load=function(a,b){this.po=a;this.dict&&this.rebuildSearch();this.reload()&&(-1!==b?this.listTable.selectRow(b||0):this.active&&this.unloadActive())};x.pasteMessage=function(a){var b,c=0;this.active===a&&((b=this.sourceCell)&&b.eachTextarea(function(b){b.val(a.source(null,c++))}),(b=this.contextCell)&&b.eachTextarea(function(b){b.val(a.context())}),b=this.targetCell)&&(c=0,b.eachTextarea(function(b){b.val(a.translation(c++))}));
this.updateListCell(a,"source");this.updateListCell(a,"target");return this};x.reloadMessage=function(a){var b=this.sourceCell,c=this.targetCell,e;this.pasteMessage(a);b&&this.setSrcMeta(a,b)&&b.redraw();c&&(e=c.navigated()||0,e=this.setTrgMeta(a,e,c),!b&&this.setSrcMeta(a,c)&&(e=!0),e&&c.redraw());return this};x.setStatus=function(){return null};x.setSrcMeta=function(a,b){var e=[],d,h=!1,g=this.$smeta,l=this.labels,p=[],f=a.tags(),v=f&&f.length;if(d=a.context())p.push("<span>"+r(l[q])+"</span>"),
p.push("<mark>"+r(d)+"</mark>");if(v&&this.getTag)for(p.push("<span>Tagged:</span>");0<=--v;)(d=this.getTag(f[v]))&&p.push('<mark class="tag">'+r(d.mod_name)+"</mark>");p.length&&e.push(p.join(" "));if(this.getMono()&&(d=a.refs())&&(f=d.split(/\s/),v=f.length)){for(p=[];0<=--v;)d=f[v],p.push("<code>"+r(d)+"</code>");e.push('<p class="has-icon icon-file">'+p.join(" ")+"</p>")}(d=a.notes())&&e.push('<p class="has-icon icon-info">'+r(d,!0)+"</p>");e.length?(g||(g=b.find("div.meta"),g.length||(g=k('<div class="meta"></div>').insertAfter(b.header())),
c(this,g,"poMeta"),this.$smeta=g),g.html(e.join("\n")).show(),h=!0):g&&g.text()&&(g.text("").hide(),h=!0);return h};x.setTrgMeta=function(a,b,c){var e=[],d=!1,h=this.$tmeta;b=(a=a.errors(b))&&a.length;var g;if(b)for(g=0;g<b;g++)e.push('<p class="has-icon icon-warn">'+r(a[g],!0)+".</p>");e.length?(h||(h=c.find("div.meta"),h.length||(h=k('<div class="meta"></div>').insertAfter(c.header())),this.$tmeta=h),h.html(e.join("\n")).show(),d=!0):h&&h.text()&&(h.text("").hide(),d=!0);return d};x.loadMessage=
function(b){function c(a,b){var e=b?a.split(" "):a.split(" ",1);a=e[0];"="===a.charAt(0)&&(a=a.substr(1),a=["zero","one","two"][Number(a)]||a);e[0]=a.charAt(0).toUpperCase()+a.substr(1).toLowerCase();return e.join(" ")}function d(a,e){var l=R,k=N[p];a.off();a.titled()!==k&&g(a,k,e||"en");k=!1;y.setSrcMeta(b,a)&&(k=!0);if(b.plural()){var k=-1,q=[],m=[],n=a.id+"-",D=b.sourceForms()||e&&e.plurals||["One","Other"],w=D.length;if(2!==w||"="===D[0].charAt(0)&&"=1"!==D[0])for(;++k<w;)q[k]=n+String(k),m[k]=
c(D[k])+":";else q=[n+"-0",n+"-1"],m=[N[h],N[v]];a.splity.apply(a,q);a.each(function(a,c){a.header(m[c]).textarea(b.source(null,c),l).setStrf(E).setMode(A).setInvs(z)});a.lock();l&&a.each(function(a,b){f(a,b)})}else k&&a.redraw(),a.textarea(b.source(),l).setStrf(E).setMode(A).setInvs(z),l&&f(a,0)}function f(c,d){c.on(l,function(a,c){b.source(c,d);0===d&&y.updateListCell(b,"source");y.unsave(b,d)}).on(e,function(){0===d&&y.po.reIndex(b);y.dict&&y.rebuildSearch();y.fire(a,[b])})}function k(a,e,d){O&&
a.eachTextarea(function(a){a.ping()});a.off();var h=e.isKnown()&&e.label||"Target",h=C(N[D],h);a.titled()!==h&&g(a,h,e);h=!1;!this.sourceCell&&y.setSrcMeta(b,a)&&(h=!0);y.setTrgMeta(b,d,a)&&(h=!0);y.setStatus(b,d);if(b.pluralized()){var l=[],p=[],f=a.id+"-",v=b.targetForms()||e.plurals||["One","Other"],h=v.length,q=function(a){var b=v[a];p.push(b?c(b,!0):"Form "+a);l.push(f+String(a))};for(b.each(q);(e=l.length)<h;)q(e);a.splitx.apply(a,l);a.each(function(a,c){var e=O&&!b.disabled(c);a.textarea(b.translation(c),
e).setStrf(E).setMode(A).setInvs(z);O&&m(a,c)});a.navigize(p,d||null).on("wgTabSelect",function(c,e){var d=O&&c.cell.editable();d&&d.focus();y.setTrgMeta(b,e,a);y.setStatus(b,e);y.fire("poTab",[e])})}else h&&a.redraw(),a.textarea(b.translation(),O&&!b.disabled(0)).setStrf(E).setMode(A).setInvs(z),O&&m(a,0)}function m(c,d){c.on(l,function(a,c,e){b.translate(c,d);0===d&&y.updateListCell(b,"target");b.fuzzy(d)?y.fuzzy(!1,b,d):y.unsave(b,d);""===c?(y.fire("poEmpty",[!0,b,d]),y.setStatus(b,d)):""===e&&
(y.fire("poEmpty",[!1,b,d]),y.setStatus(b,d))}).on(e,function(){y.dict&&y.rebuildSearch();y.fire(a,[b])})}function n(c){c.off();var d=N[q];c.titled()!==d&&(g(c,d),y.setStatus(null));c.textarea(b.context(),!0).setMode(A).setInvs(z);V&&c.on(l,function(a,c){b.context(c);y.updateListCell(b,"source");y.unsave(b,F)}).on(e,function(){y.po.reIndex(b);y.dict&&y.rebuildSearch();y.fire(a,[b])})}function x(a){var c=N[w];a.titled()!==c&&g(a,c);a.off().on(l,function(a,c){b.comment(c);y.fire("poComment",[b,c]);
y.unsave(b,F)}).textarea(b.comment(),!0)}var y=this,A=y.mode,s=b.isHTML(),z=y.inv||!1,r=this.fmt||null,E=b.format()||null,H=b.is(y.active),F=0,G=y.sourceCell,S=y.targetCell,K=y.contextCell,T=y.commentCell,O=y.editable.target,R=y.editable.source,V=y.editable.context,P=u,W=y.sourceLocale,U=y.targetLocale,N=y.labels;y.html!==s&&(y.html=s,"code"!==y.mode&&(A=s?"html":"",y.setMode(A)));y.active=b;G&&d(G,W);K&&n(K);S&&U&&(F=S.navigated()||0,k(S,U,F));T&&x(T);P&&(P.exists()||(P=P.parent()),(s=P.editable())&&
s.focus());r!==E&&(this.fmt=E);H||y.fire("poSelected",[b,F])};x.unloadActive=function(){function a(b){b&&b.text("").hide()}function b(a){a&&a.off().clear()}a(this.$smeta);a(this.$tmeta);b(this.sourceCell);b(this.contextCell);b(this.targetCell);this.commentCell&&this.commentCell.off();this.active&&(this.fire("poDeselected",[this.active]),this.active=null);return this};x.loadNothing=function(){var a,b=this.t,c=this.mode||"",e=this.inv||!1,d=this.fmt;this.unloadActive();this.setStatus(null);(a=this.commentCell)&&
a.textarea("",!1);if(a=this.sourceCell)a.textarea("",!1).setStrf(d).setMode(c).setInvs(e),a.title(b._x("Source text not loaded","Editor")+":");if(a=this.contextCell)a.textarea("",!1).setMode(c).setInvs(e),a.title(b._x("Context not loaded","Editor")+":");if(a=this.targetCell)a.textarea("",!1).setStrf(d).setMode(c).setInvs(e),a.title(b._x("Translation not loaded","Editor")+":");this.fire("poSelected",[null])};x.updateListCell=function(a,b){var c=this.getListColumns()[b],e=this.po.indexOf(a);(e=this.listTable.row(e))&&
e.rendered&&e.update(c)};x.cellText=function(a){if(-1!==a.indexOf("<")||-1!==a.indexOf("&"))a=F(a);return a.replace(A,"")||"\u00a0"};x.fuzzy=function(a,b,c){b=b||this.active;var e=b.fuzzy(c);!0!==a||e?!1===a&&e&&this.flag(0,b,c)&&this.fire("poFuzzy",[b,!1,c]):this.flag(4,b,c)&&this.fire("poFuzzy",[b,!0,c]);return e};x.flag=function(b,c,e){if(!c){c=this.active;e=this.getTargetOffset();if(null==e)return null;e&&c.targetForms()&&(e=0)}var d=c.flagged(e);if(null==b)return d;if(d===b||b&&!c.translated(e)||
!this.fire("poFlag",[b,d,c,e]))return!1;c.flag(b,e);this.fire(a,[c])&&this.unsave(c,e);this.setStatus(c,e);return!0};x.add=function(b,c){var e,d=this.po.get(b,c);d?e=this.po.indexOf(d):(e=this.po.length,d=this.po.add(b,c),this.load(this.po,-1),this.fire("poAdd",[d]),this.fire(a,[d]));this.lastSearch&&this.filter("");this.listTable.select(e);return d};x.del=function(b){if(b=b||this.active){var c=this.lastSearch,e=this.po.del(b);null!=e&&(this.unsave(b),this.fire("poDel",[b]),this.fire(a,[b]),this.reload(),
this.dict&&this.rebuildSearch(),this.active&&this.active.equals(b)&&this.unloadActive(),this.po.length&&(c&&this.filter(c),this.active||(e=Math.min(e,this.po.length-1),this.listTable.select(e))))}};x.setMono=function(a){return this.setMode(a?"code":this.html?"html":"")};x.setMode=function(a){if(this.mode!==a){this.mode=a;this.callTextareas(function(b){b.setMode(a)});var b=this.active,c=this.sourceCell;b&&b.refs()&&c&&this.setSrcMeta(b,c)&&c.redraw()}return this};x.getMono=function(){return"code"===
this.mode};x.setInvs=function(a){(this.inv||!1)!==a&&(this.inv=a,this.callTextareas(function(b){b.setInvs(a)}),this.fire("poInvs",[a]));return this};x.getInvs=function(){return this.inv||!1};x.callTextareas=function(a){var b=this.targetCell;b&&b.eachTextarea(a);(b=this.contextCell)&&b.eachTextarea(a);(b=this.sourceCell)&&b.eachTextarea(a);return this};x.focus=function(){var a=this.getTargetEditable();a&&a.focus();return this};x=null;return f}({},r,s));n.register("$12",function(f,d,s){function m(){this.init()._validate();
this.sourceLocale={lang:"en",label:"English",plurals:["One","Other"]}}function g(a){a=k('<button type="button" class="button button-small icon icon-'+a+' hastip"></button>');n.require("$11","tooltip.js").init(a);return a}function c(a){return g("cloud").attr("title",a.labels[8]+" (Ctrl-U)").click(function(b){b.preventDefault();a.focus().fuzzy(!a.fuzzy())})}function b(a){return g("robot").attr("title",a.labels[9]+" (Ctrl-J)").click(function(b){b.preventDefault();a.fire("poHint")})}d=n.require("$23",
"base.js");f.init=function(a){var b=new m;a=b.setRootCell(a);var c=a.splity("po-list","po-edit"),d=c[0],h=c[1],c=h.splitx("po-trans","po-comment"),g=c[0],f=c[1].header("Loading.."),c=g.splity("po-source","po-target"),g=c[0].header("Loading.."),c=c[1].header("Loading..");a.distribute([0.34]);h.distribute([0.8]);b.setListCell(d);b.setSourceCell(g);b.setTargetCell(c);b.commentCell=f;b.editable.source=!1;return b};d=m.prototype=d.extend(m);d.getListHeadings=function(){var a=this.t||{_x:function(a){return a}},
b=[a._x("Source text","Editor")];this.targetLocale&&(b[1]=a._x("Translation","Editor"));return b};d.getListColumns=function(){var a={source:0};this.targetLocale&&(a.target=1);return a};d.getListEntry=function(a){var b=this.cellText,c=[function(){var c,e=b(a.source()||""),d=a.context();return d?(c=s.createElement("p"),c.appendChild(s.createElement("mark")).innerText=d,c.appendChild(s.createTextNode("\u00a0"+e)),c):e}];this.targetLocale&&(c[1]=function(){return b(a.translation()||"")});return c};d.stats=
function(){var a=this.po,b=a.length,c=0,d=0,h=0;a.each(function(a,b){b.fuzzy()?h++:b.translated()?c++:d++});return{t:b,p:c.percent(b)+"%",f:h,u:d}};d.unlock=function(){var a=this.targetLocale;this._unlocked||(this.editable={source:!0,context:!0,target:!1},this.po&&this.po.unlock(),this.contextCell=this.targetCell,delete this.targetCell,a&&(this._unlocked=a,delete this.targetLocale,this.reload(),this.fire("poLock",[!1])),this.active&&this.loadMessage(this.active))};d.lock=function(){var a=this._unlocked;
a&&(this.targetLocale=a,delete this._unlocked,this.po&&this.po.lock(a),this.editable={source:!1,context:!1,target:!0},this.targetCell=this.contextCell,delete this.contextCell,this.reload(),this.fire("poLock",[!0,a]),this.active&&this.loadMessage(this.active))};d.locked=function(){return!this._unlocked};d.setStatus=function(a){var d=this.$tnav;if(null==a)d&&(d.remove(),this.$tnav=null);else{d||(this.$tnav=d=k("<nav></nav>").append(c(this)).append(b(this)).appendTo(this.targetCell.header()));var e=
[];a.translated()?a.fuzzy()&&e.push("po-fuzzy"):e.push("po-empty");d.attr("class",e.join(" "))}};d.getSorter=function(){function a(a,c){var e=a.weight(),d=c.weight();return e===d?b(a,c):e>d?-1:1}function b(a,c){return a.hash().localeCompare(c.hash())}var c=this;return function(d){var h=c.po,g=c.locked()?a:b;d.sort(function(a,b){return g(h.row(a),h.row(b))})}};return f}({},r,s));n.register("$13",function(f,d,n){var m={copy:66,clear:75,save:83,fuzzy:85,next:40,prev:38,enter:13,invis:73,hint:74},g={38:!0,
40:!0,73:!0},c={66:function(b,a){var c=a.current();c&&(c.normalize(),a.focus().pasteMessage(c))},75:function(b,a){var c=a.current();c&&(c.untranslate(),a.focus().pasteMessage(c))},85:function(b,a){a.focus().fuzzy(!a.fuzzy())},13:function(b,a){a.getFirstEditable()&&a.next(1,!0,!0)},40:function(b,a){var c=b.shiftKey;a.next(1,c,c)},38:function(b,a){var c=b.shiftKey;a.next(-1,c,c)},73:function(b,a){if(!b.shiftKey)return!1;a.setInvs(!a.getInvs())}};f.init=function(b,a){function l(a){if(a.isDefaultPrevented()||
!a.metaKey&&!a.ctrlKey)return!0;var d=a.which;if(!e[d])return!0;var l=c[d];if(!l)throw Error("command undefined #"+d);if(a.altKey||a.shiftKey&&!g[d]||!1===l(a,b))return!0;a.stopPropagation();a.preventDefault();return!1}var e={};k(a||d).on("keydown",l);return{add:function(a,b){c[m[a]]=b;return this},enable:function(){var a,b;for(b in arguments)a=m[arguments[b]],e[a]=!0;return this},disable:function(){k(a||d).off("keydown",l);b=a=e=null}}};return f}({},r,s));n.register("$24",function(f,d,k){function m(){this.reIndex([])}
f.init=function(){return new m};d=m.prototype;d.reIndex=function(d){for(var c={},b=-1,a=d.length;++b<a;)c[d[b]]=b;this.keys=d;this.length=b;this.ords=c};d.key=function(d,c){if(null==c)return this.keys[d];var b=this.keys[d],a=this.ords[c];if(c!==b){if(null!=a)throw Error("Clash with item at ["+a+"]");this.keys[d]=c;delete this.ords[b];this.ords[c]=d}return d};d.indexOf=function(d){d=this.ords[d];return null==d?-1:d};d.add=function(d,c){var b=this.ords[d];null==b&&(this.keys[this.length]=d,b=this.ords[d]=
this.length++);this[b]=c;return b};d.get=function(d){return this[this.ords[d]]};d.has=function(d){return null!=this.ords[d]};d.del=function(d){this.cut(this.ords[d],1)};d.cut=function(d,c){c=c||1;var b=[].splice.call(this,d,c);this.keys.splice(d,c);this.reIndex(this.keys);return b};d.each=function(d){for(var c=-1,b=this.keys,a=this.length;++c<a;)d(b[c],this[c],c);return this};d.sort=function(d){for(var c=-1,b=this.length,a,l=this.keys,e=this.ords,f=[];++c<b;)f[c]=[this[c],l[c]];f.sort(function(a,
b){return d(a[0],b[0])});for(c=0;c<b;c++)a=f[c],this[c]=a[0],a=a[1],l[c]=a,e[a]=c;return this};d.join=function(d){return[].join.call(this,d)};d=null;return f}({},r,s));n.register("$25",function(f,d,k){function m(d,c){var b=RegExp("^.{0,"+(d-1)+"}["+c+"]"),a=RegExp("^[^"+c+"]+");return function(c,e){for(var f=c.length,h;f>d;){h=b.exec(c)||a.exec(c);if(null==h)break;h=h[0];e.push(h);h=h.length;f-=h;c=c.substr(h)}0!==f&&e.push(c);return e}}f.create=function(d){function c(a){return h[a]||"\\"+a}var b,
a,l=/(?:\r\n|[\r\n\v\f\u2028\u2029])/g,e=/[ \r\n]+/g,f=/[\t\v\f\x07\x08\\\"]/g,h={"\t":"\\t","\v":"\\v","\f":"\\f","\u0007":"\\a","\b":"\\b"};if(null==d||isNaN(d=Number(d)))d=79;0<d&&(b=m(d-3," "),a=m(d-2,"-\u2013 \\.,:;\\?!\\)\\]\\}\\>"));return{pair:function(b,e){if(!e)return b+' ""';e=e.replace(f,c);var h=0;e=e.replace(l,function(){h++;return"\\n\n"});if(!(h||d&&d<e.length+b.length+3))return b+' "'+e+'"';var k=[b+' "'],m=e.split("\n");if(a)for(var n=-1,s=m.length;++n<s;)a(m[n],k);else k=k.concat(m);
return k.join('"\n"')+'"'},prefix:function(a,b){var c=a.split(l);return b+c.join("\n"+b)},refs:function(a){a=a.replace(e," ",a);b&&(a=b(a,[]).join("\n#: "));return"#: "+a}}};return f}({},r,s));n.register("$39",function(f,d,k){function m(){this.length=0}f.init=function(){return new m};d=m.prototype;d.push=function(d){this[this.length++]=d;return this};d.sort=function(d){[].sort.call(this,d);return this};d.each=function(d){for(var c=-1,b=this.length;++c<b;)d(c,this[c]);return this};return f}({},r,s));
n.register("$26",function(f,d,k){function m(){}f.extend=function(d){return d.prototype=new m};d=m.prototype=n.require("$37","abstract.js").init(["add","load"]);d.row=function(d){return this.rows[d]};d.lock=function(d){return this.locale(d||{lang:"zxx",label:"Unknown",nplurals:1,pluraleq:"n!=1"})};d.unlock=function(){var d=this.loc;this.loc=null;return d};d.locale=function(d){null==d?d=this.loc:this.loc=d=n.require("$36","locale.js").cast(d);return d};d.each=function(d){this.rows.each(d);return this};
d.indexOf=function(d){"object"!==typeof d&&(d=this.get(d));if(!d)return-1;null==d.idx&&(d.idx=this.rows.indexOf(d.hash()));return d.idx};d.get=function(d){return this.rows&&this.rows.get(d)};d.has=function(d){return this.rows&&this.rows.has(d)};d.del=function(d){d=this.indexOf(d);if(-1!==d){var c=this.rows.cut(d,1);if(c&&c.length)return this.length=this.rows.length,this.rows.each(function(b,a,c){a.idx=c}),d}};d.reIndex=function(d,c){var b=this.indexOf(d),a=d.hash(),f=this.rows.indexOf(a);return f===
b?b:-1!==f?(c=(c||0)+1,d.source("Error, duplicate "+String(c)+": "+d.source()),this.reIndex(d,c)):this.rows.key(b,a)};d.sort=function(d){this.rows.sort(d);return this};d["export"]=function(){for(var d=-1,c=this.rows,b=c.length,a=n.require("$39","list.js").init();++d<b;)a.push(c[d]);return a};d=null;return f}({},r,s));n.register("$27",function(f,d,n){function m(){function d(a){return/<[a-z]+[^>]*>/i.test(a)||/&(#\d+|#x[0-9a-f]|[a-z]+);/i.test(a)?k("<p></p>").html(a).text():a}function c(a){return a.replace(/%(?:\d+\$)?(?:'.|[-+0 ])*\d*(?:\.\d+)?[suxXbcdeEfFgGo]/g,
"%s")}function b(a){return a.replace(/[^\sa-z0-9]+/ig," ")}function a(a){return a.trim().replace(/\s+/g," ")}function f(e){return a(b(d(c(e).toLowerCase())))}var e={},p={};return{add:function(a){var b=f(a.source());(e[b]||(e[b]=[])).push(a);p[a.hash()]=a},match:function(h){var k=f(h.source()),k=e[k],m;if(m=k){m=k.length;var q=k[0];if(1!==m)for(var n=7,A,u=h.source(),s=h.context(),r;0<=--m;)if(h=0,A=k[m],!(A.context()!==s&&++h>=n)){r=A.source();if(u!==r){if(++h>=n)continue;u=u.toLowerCase();r=r.toLowerCase();
if(u!==r){if(++h>=n)continue;u=c(u);r=c(r);if(u!==r){if(++h>=n)continue;u=d(u);r=d(r);if(u!==r){if(++h>=n)continue;u=b(u);r=b(r);if(u!==r){if(++h>=n)continue;u=a(u);r=a(r);if(u!==r&&++h>=n)continue}}}}}n=h;q=A;if(0===h)break}delete p[q.hash()];m=q.copy()}return m},unmatched:function(){var a,b,c=[];for(a in p)b=p[a],c.push(b);return c}}}f.merge=function(f,c){var b=f.rows,a=c.rows,l=[],e=[],p=[],h=m();b.each(function(b,c){a.has(b)||(c.translated()?h.add(c):p.push(c))});f.clear();a.each(function(a,c){try{var p=
b.get(a),k;p?(k=p.flagged(0),p.merge(c),p.flagged(0)!==k&&e.push(p)):(p=h.match(c))?(p.merge(c),e.push(p)):(p=c.copy(),l.push(p));f.add(p,"")}catch(m){d.console&&console.error(String(m))}});f.header("POT-Creation-Date",c.header("POT-Creation-Date")||f.now());return{add:l,fuz:e,del:p.concat(h.unmatched())}};return f}({},r,s));n.register("$28",function(f,d,k){function m(c,b,a){if(null==a)return c[b]||"";c[b]=a||"";return c}function g(){this._id=this.id=""}f.extend=function(c){return c.prototype=new g};
d=g.prototype;d.flag=function(c,b){var a=this.flg||(this.flg=[]);if(null!=b)a[b]=c;else for(var d=Math.max(a.length,this.src.length,this.msg.length);0!==d--;)a[d]=c;return this};d.flagged=function(c){var b=this.flg||[];if(null!=c)return b[c]||0;for(c=b.length;0!==c--;)if(b[c])return!0;return!1};d.flags=function(){for(var c,b={},a=[],d=this.flg||[],e=d.length;0!==e--;)c=d[e],b[c]||(b[c]=!0,a.push(c));return a};d.flaggedAs=function(c,b){var a=this.flg||[];if(null!=b)return c===a[b]||0;for(var d=a.length;0!==
d--;)if(a[d]===c)return!0;return!1};d.fuzzy=function(c,b){var a=this.flaggedAs(4,c);null!=b&&this.flag(b?4:0,c);return a};d.source=function(c,b){if(null==c)return this.src[b||0]||"";this.src[b||0]=c;return this};d.plural=function(c,b){if(null==c)return this.src[b||1]||"";this.src[b||1]=c||"";return this};d.sourceForms=function(){return this.srcF};d.targetForms=function(){return this.msgF};d.each=function(c){for(var b=-1,a=this.src,d=this.msg,e=Math.max(a.length,d.length);++b<e;)c(b,a[b],d[b]);return this};
d.count=function(){return Math.max(this.src.length,this.msg.length)};d.pluralized=function(){return 1<this.src.length||1<this.msg.length};d.translate=function(c,b){this.msg[b||0]=c||"";return this};d.untranslate=function(c){if(null!=c)this.msg[c]="";else{var b=this.msg,a=b.length;for(c=0;c<a;c++)b[c]=""}return this};d.translation=function(c){return this.msg[c||0]||""};d.errors=function(c){return this.err&&this.err[c||0]||[]};d.translated=function(c){if(null!=c)return!!this.msg[c];var b=this.msg,a=
b.length;for(c=0;c<a;c++)if(!b[c])return!1;return!0};d.untranslated=function(c){if(null!=c)return!this.msg[c];var b=this.msg,a=b.length;for(c=0;c<a;c++)if(b[c])return!1;return!0};d.comment=function(c){return m(this,"cmt",c)};d.notes=function(c){return m(this,"xcmt",c)};d.refs=function(c){return m(this,"rf",c)};d.format=function(c){return m(this,"fmt",c)};d.context=function(c){return m(this,"ctx",c)};d.tags=function(){return this.tg};d.toString=d.toText=function(){return this.src.concat(this.msg,this.id,
this.ctx).join(" ")};d.weight=function(){var c=0;this.translation()||(c+=2);this.fuzzy()&&(c+=1);return c};d.equals=function(c){return this===c||this.hash()===c.hash()};d.hash=function(){return this.id};d.normalize=function(){for(var c=this.msg.length;0!==c--;)this.msg[c]=this.src[c]||""};d.disabled=function(c){return!!(this.lck||[])[c||0]};d.disable=function(c){(this.lck||(this.lck=[]))[c||0]=!0;return this};d.saved=function(c){var b=this.drt;if(null==b)return!0;if(null!=c)return!b[c];for(c=b.length;0!==
c--;)if(b[c])return!1;return!0};d.unsave=function(c){(this.drt||(this.drt=[]))[c||0]=!0;return this};d.save=function(c){null==c?this.drt=null:(this.drt||(this.drt=[]))[c]=!1;return this};d.is=function(c){return c&&(c===this||c.idx===this.idx)};d.isHTML=function(c){if(null==c)return this.htm||!1;this.htm=c};d=null;return f}({},r,s));n.register("$14",function(f,d,k){function m(a){return{"Project-Id-Version":"PACKAGE VERSION","Report-Msgid-Bugs-To":"","POT-Creation-Date":a||"","PO-Revision-Date":a||
"","Last-Translator":"","Language-Team":"",Language:"","Plural-Forms":"","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit"}}function g(a,b){var c=a||"";b&&(c+="\x00"+b);return c}function c(a){var b=d.console;b&&b.error&&b.error(a.message||String(a))}function b(a){return n.require("$25","format.js").create(a)}function a(a){this.locale(a);this.clear();this.head=m(this.now())}function l(a,b){this.src=[a||""];this.msg=[b||""]}f.create=function(b){return new a(b)};
k=n.require("$26","messages.js").extend(a);k.clear=function(){this.rows=n.require("$24","collection.js").init();this.length=0};k.now=function(){function a(b,c){for(var d=String(b);d.length<c;)d="0"+d;return d}var b=new Date,c=b.getUTCFullYear(),d=b.getUTCMonth()+1,f=b.getUTCDate(),l=b.getUTCHours(),b=b.getUTCMinutes();return a(c,4)+"-"+a(d,2)+"-"+a(f,2)+" "+a(l,2)+":"+a(b,2)+"+0000"};k.header=function(a,b){var c=this.head||(this.head={});if(null==b)return this.headers()[a]||"";c[a]=b||"";return this};
k.headers=function(a){var b,c=this.now(),d=this.head||(this.head=m(c));if(null!=a){for(b in a)d[b]=a[b];return this}var f=this.locale();a={};for(b in d)a[b]=String(d[b]);f?(a.Language=String(f)||"zxx",a["Language-Team"]=f.label||a.Language,a["Plural-Forms"]="nplurals="+(f.nplurals||"2")+"; plural="+(f.pluraleq||"n!=1")+";",b="PO-Revision-Date"):(a.Language="",a["Plural-Forms"]="nplurals=INTEGER; plural=EXPRESSION;",a["PO-Revision-Date"]="YEAR-MO-DA HO:MI+ZONE",b="POT-Creation-Date");a[b]||(a[b]=c);
a["X-Generator"]="Loco https://localise.biz/";return a};k.get=function(a,b){var c=g(a,b);return this.rows.get(c)};k.add=function(a,b){a instanceof l||(a=new l(a));b&&a.context(b);var d=a.hash();this.rows.get(d)?c("Duplicate message at index "+this.indexOf(a)):(a.idx=this.rows.add(d,a),this.length=this.rows.length);return a};k.load=function(a){for(var b=-1,d,f,g,k,m,n,u=(g=this.locale())&&g.nplurals||2,r=[];++b<a.length;)d=a[b],null==d.parent?(f=d.source||d.id,g=d.target||"",k=d.context,f||k?(m=new l(f,
g),m._id=d._id,k&&m.context(k),d.flag&&m.flag(d.flag,0),d.comment&&m.comment(d.comment),d.notes&&m.notes(d.notes),d.refs&&m.refs(d.refs),m.format(d.format),d.message=m,this.add(m)):0===b&&"object"===typeof g&&(this.head=g,this.headcmt=d.comment)):r.push(d);for(b=-1;++b<r.length;)try{d=r[b];f=d.source||d.id;m=a[d.parent]&&a[d.parent].message;if(!m)throw Error("parent missing for plural "+f);n=d.plural;1===n&&m.plural(f);n>=u||(d.flag&&m.flag(d.flag,n),m.translate(d.target||"",n),d.format&&!m.format()&&
m.format(d.format))}catch(s){c(s)}return this};k.merge=function(a){return n.require("$27","merge.js").merge(this,a)};k.wrap=function(a){this.fmtr=b(a);return this};k.toString=function(){var a,c=this.locale(),d=[],f=[],g=this.headers(),k=!c,m=c&&c.nplurals||2,n=this.fmtr||b();g[c?"PO-Revision-Date":"POT-Creation-Date"]=this.now();for(a in g)f.push(a+": "+g[a]);f=new l("",f.join("\n"));f.comment(this.headcmt||"");k&&f.fuzzy(0,!0);d.push(f.toString());d.push("");this.rows.each(function(a,b){a&&(d.push(b.cat(n,
k,m)),d.push(""))});return d.join("\n")};k=n.require("$28","message.js").extend(l);k.hash=function(){return g(this.source(),this.context())};k.toString=function(){return this.cat(b())};k.cat=function(a,b,c){var d,f=[],l;(l=this.cmt)&&f.push(a.prefix(l,"# "));(l=this.xcmt)&&f.push(a.prefix(l,"#. "));d=this.rf;if(l=this._id)d+=(d?" ":"")+"loco:"+l;d&&/\S/.test(d)&&f.push(a.refs(d));!b&&this.fuzzy()&&f.push("#, fuzzy");(l=this.fmt)&&f.push("#, "+l+"-format");(l=this._ctx)&&f.push(a.prefix(a.pair("msgctxt",
l),"#| "));if(l=this._src)l[0]&&f.push(a.prefix(a.pair("msgid",l[0]),"#| ")),l[1]&&f.push(a.prefix(a.pair("msgid_plural",l[1]),"#| "));(l=this.ctx)&&f.push(a.pair("msgctxt",l));f.push(a.pair("msgid",this.src[0]));if(null==this.src[1])f.push(a.pair("msgstr",b?"":this.msg[0]));else for(d=-1,f.push(a.pair("msgid_plural",this.src[1])),l=this.msg||["",""],c=c||l.length;++d<c;)f.push(a.pair("msgstr["+d+"]",b?"":l[d]||""));return f.join("\n")};k.compare=function(a,b){var c=this.weight(),d=a.weight();if(c>
d)return 1;if(c<d)return-1;if(b){c=this.hash().toLowerCase();d=a.hash().toLowerCase();if(c<d)return 1;if(c>d)return-1}return 0};k.merge=function(a){var b=!1;this.rf=a.rf;this.fmt=a.fmt;this.cmt=a.cmt;this.xcmt=a.xcmt;a.src.join("\x00")!==this.src.join("\x00")&&(this._src=this.src,this.src=a.src.concat(),this.msg=(this.msg||["",""]).slice(0,this.src.length),b=!0);a.ctx!==this.ctx&&(this._ctx=this.ctx,this.ctx=a.ctx,b=!0);b&&this.translated()&&this.fuzzy(0,!0)};k.copy=function(){var a=new l,b,c;for(b in this)this.hasOwnProperty(b)&&
((c=this[b])&&c.concat&&(c=c.concat()),a[b]=c);return a};k=k=null;return f}({},r,s));n.register("$16",function(f,d,n){f.init=function(d,f){function c(){return e||(e=k('<div id="loco-po-ref"></div>').dialog({dialogClass:"loco-modal loco-modal-wide",modal:!0,autoOpen:!1,closeOnEscape:!0,resizable:!1,height:500}))}function b(a,b,d){a=k("<p></p>").text(d);c().dialog("close").html("").dialog("option","title","Error").append(a).dialog("open")}function a(a){var b=a&&a.code;if(b){for(var d=-1,e=b.length,
f=k("<ol></ol>").attr("class",a.type);++d<e;)k("<li></li>").html(b[d]).appendTo(f);f.find("li").eq(a.line-1).attr("class","highlighted");c().dialog("close").html("").dialog("option","title",a.path+":"+a.line).append(f).dialog("open")}}function l(a){a=a.target;var b=k(a).find("li.highlighted")[0],b=Math.max(0,(b&&b.offsetTop||0)-Math.floor(a.clientHeight/2));a.scrollTop=b}var e;return{load:function(e){c().html('<div class="loco-loading"></div>').dialog("option","title","Loading..").off("dialogopen").dialog("open").on("dialogopen",
l);e=k.extend({ref:e,path:f.popath},f.project||{});d.ajax.post("fsReference",e,a,b)}}};return f}({},r,s));n.register("$30",function(f,d,k){function m(d){this.api=d;this.chars=0}f.create=function(d){return new m(d)};d=m.prototype;d.init=function(d,c){function b(a){var b={length:0,html:a.html,sources:[]};q.push(b);return w[a.html?1:0]=b}function a(a,d){var g=a.source(null,d);if(g&&(a.untranslated(d)||c)){var q=n[g];if(q)q.push(a);else{var q=g.length,r=f.isHtml(g),r=w[r?1:0],s=r.sources;if(r.length+
q>m||s.length===h)r=b(r),s=r.sources;s.push(g);n[g]=[a];r.length+=q;e+=q;k+=1}}}var f=this.api,e=0,k=0,h=50,m=5E3,n={},q=[],w=[];b({html:!1});b({html:!0});d.each(function(b,c){a(c,0);a(c,1)});w=null;this.map=n;this.chars=e;this.length=k;this.batches=q;this.locale=d.locale()};d.abort=function(){this.state="abort";return this};d.dispatch=function(){function d(a,b){function e(c,d,l){b!==l&&(a===d||1<c&&f.source(null,1)===a)&&(f.translate(b,c),q++,z++);return q}if(!c())return!1;if(!b)return m.stderr("Empty translation returned for: "+
a),!0;var f,l=n[a]||[],h=l.length,g=-1,q;for(s++;++g<h;)if(f=l[g])q=0,f.each(e),q&&k("each",[f]);return!0}function c(){return"abort"===h.state?(m&&(m.abort(),e()),!1):!0}function b(){var b=q.shift(),c;b?(c=b.sources)&&c.length?m.batch(c,r,b.html,d).fail(a).always(f):f():e()}function a(){h.abort();e()}function f(){u++;k("prog",[u,C]);c()&&b()}function e(){m=q=null;k("done")}function k(a,b){for(var c=F[a]||[],d=c.length;0<=--d;)c[d].apply(null,b)}var h=this,m=h.api,n=h.map,q=h.batches||[],r=h.locale,
s=0,u=0,z=0,E=h.length,C=q.length,F={done:[],each:[],prog:[]};h.state="";b();return{done:function(a){F.done.push(a);return this},each:function(a){F.each.push(a);return this},prog:function(a){F.prog.push(a);return this},stat:function(){return{todo:function(){return Math.max(E-s,0)},did:function(){return s}}}}};return f}({},r,s));n.register("$31",function(f,d,k){function m(){}f.create=function(d){d=m.prototype=new d;d.toString=function(){return"Yandex.Translate"};d.getId=function(){return"yandex"};
d.getUrl=function(){return"https://translate.yandex.com/"};d.parseError=function(c){return c&&c.code&&200!==c.code&&c.message?String(this)+" error "+c.code+": "+c.message:""};d.batch=function(c,b,a,d){function e(a){for(var e=c.length,f=-1;++f<e&&!1!==d(c[f],a[f]||"",b););}var f=this;return f._call({url:"https://translate.yandex.net/api/v1.5/tr.json/translate?format="+(a?"html":"plain")+"&lang="+b.lang,method:"POST",traditional:!0,data:{key:f.key(),text:c}}).done(function(a,b,c){a&&200===a.code?e(a.text||
[]):(f.stderr(f.parseError(a)||f.odderr(c)),e([]))}).fail(function(){e([])})};return new m};return f}({},r,s));n.register("$40",{zh:["zh","zh-TW"],he:["iw"],jv:["jw"]});n.register("$32",function(f,d,k){function m(){}f.create=function(d){d=m.prototype=new d;d.toString=function(){return"Google Translate"};d.getId=function(){return"google"};d.getUrl=function(){return"https://translate.google.com/"};d.parseError=function(c){if(c.error){for(var b=[],a=c.error.errors||[],d=a.length,e=-1;++e<d;)b.push(a[e].message||
"");return String(this)+" error "+c.error.code+": "+b.join(";")}return""};d.batch=function(c,b,a,d){function e(a){for(var e=c.length,f=-1,h;++f<e&&(h=a[f]||{},!1!==d(c[f],h.translatedText||"",b)););}var f=this;a=a?"html":"text";var h=f.mapLang(b,n.require("$40","google.json"));return f._call({url:"https://translation.googleapis.com/language/translate/v2?source=en&target="+h+"&format="+a,method:"POST",traditional:!0,data:{key:f.key(),q:c}}).done(function(a){a.data?e(a.data.translations||[]):(f.stderr(f.parseError(a)||
f.odderr(xhr)),e([]))}).fail(function(){e([])})};return new m};return f}({},r,s));n.register("$41",{pt:["pt","pt-pt"],sr:["sr-Cyrl","sr-Latn"],sr_RS:["sr-Cyrl"],tlh:["tlh-Latn","tlh-Piqd"],zh:["zh-Hans","zh-Hant"],zh_CN:["zh-Hans"],zh_HK:["zh-Hans"],zh_SG:["zh-Hans"],zh_TW:["zh-Hant"]});n.register("$33",function(f,d,k){function m(){}f.create=function(d){d=m.prototype=new d;d.toString=function(){return"Microsoft Translator text API"};d.getId=function(){return"microsoft"};d.getUrl=function(){return"https://aka.ms/MicrosoftTranslatorAttribution"};
d.parseError=function(c){return c&&c.error?c.error.message:""};d.batch=function(c,b,a,d){function e(a){for(var e=-1,f;++e<g&&(f=a[e]||{},f=f.translations||[],f=f[0]||{},!1!==d(c[e],f.text||"",b)););}var f=this,h=[],g=c.length,k=-1;a=a?"html":"plain";for(var m=f.mapLang(b,n.require("$41","ms.json"));++k<g;)h.push({text:c[k]});return f._call({url:"https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&from=en&to="+m+"&textType="+a,method:"POST",data:JSON.stringify(h),headers:{"Content-Type":"application/json; charset=UTF-8",
"Ocp-Apim-Subscription-Key":this.key(),"Ocp-Apim-Subscription-Region":f.param("region")||"global"}}).done(function(a){a&&a.length?e(a):(f.stderr(f.parseError(a)||f.odderr(xhr)),e([]))}).fail(function(){e([])})};return new m};return f}({},r,s));n.register("$34",function(f,d,n){function m(){}f.create=function(f){(m.prototype=new f).batch=function(c,b,a,f){function e(a){for(var d=c.length,e=-1;++e<d&&!1!==f(c[e],a[e],b););}var g=d.locoScope.ajax;a={hook:this.getId(),type:a?"html":"text",locale:String(b),
sources:c};var h=k.Deferred();this.abortable(g.post("apis",a,function(a){e(a&&a.targets||[]);h.resolve()},function(){e([]);h.reject()}));return h.promise()};return new m};return f}({},r,s));n.register("$17",function(f,d,r){function m(){this.inf={}}function g(){var a=r.createElement("p"),b=/&(#\d+|#x[0-9a-f]|[a-z]+);/i,c=/<[a-z]+\s/i,d,f;return{sniff:function(g){if(g===d)return f;d=g;if(b.test(g)||c.test(g))if(a.innerHTML=g,a.textContent!==g)return f=!0;return f=!1}}}var c=m.prototype;c.init=function(a){this.inf=
a||{}};c.param=function(a){return this.inf[a]||""};c.key=function(){return this.param("key")};c.getId=function(){return this.param("id")||"none"};c.getUrl=function(){return this.param("url")||"#"};c.toString=function(){return this.param("name")||this.getId()};c.stderr=function(a){var b=(d.locoScope||{}).notices||d.console;b&&b.error&&b.error(String(a))};c.odderr=function(){return String(this)+": Unknown failure"};c.parseError=function(a){return""};c.mapLang=function(a,b){var c=String(a),d=a.lang,
f=b[c]||b[d]||[],g=f.length;if(0===g)return d;if(1<g)for(var c=c.toLowerCase(),d=-1,k;++d<g;)if(k=f[d],k.toLowerCase().replace("-","_")===c)return k;return f[0]};c.translate=function(a,b,c){return this.batch([a],b,this.isHtml(a),c)};c.batch=function(){return{always:function(a){a()}}};c._call=function(a){var b=this;a.cache=!0;a.dataType="json";a.error=function(a,c,d){try{var f=a.responseText,g=f&&k.parseJSON(f);d=g&&b.parseError(g)||d}catch(m){}b.stderr(String(b)+": "+d)};return b.abortable(k.ajax(a))};
c.abortable=function(a){var b=this;a.always(function(){b.$r===a&&(b.$r=null)});return b.$r=a};c.abort=function(){var a=this.$r;a&&a.abort()};c.isHtml=function(a){return(b||(b=g())).sniff(a)};c.createJob=function(){return n.require("$30","job.js").create(this)};f.create=function(a){var b;b=a.id;b="yandex"===b?n.require("$31","yandex.js").create(m):"google"===b?n.require("$32","google.js").create(m):"microsoft"===b?n.require("$33","ms.js").create(m):n.require("$34","custom.js").create(m);b.init(a);
return b};f.suggest=function(a,b,c,d){var f,g,k=a.length;for(f=0;f<k;f++)g=a[f],g.translate(b,c,d)};var b;return f}({},r,s));n.register("$18",function(f,d,n){f.init=function(f){function g(){M||(L.click(h),M=k('<div id="loco-fs-creds"></div>').dialog({dialogClass:"request-filesystem-credentials-dialog loco-modal",minWidth:580,modal:!0,autoOpen:!1,closeOnEscape:!0}).on("change",'input[name="connection_type"]',function(){this.checked&&k("#ssh-keys").toggleClass("hidden","ssh"!==k(this).val())}));return M}
function c(){G&&(b(k(u)),G=!1);if(C&&K){var a=K,c=k(Q);c.find("span.loco-msg").text(a);J||(c.removeClass("jshide").hide().fadeIn(500),J=!0)}else J&&(b(k(Q)),J=!1)}function b(a){a.slideUp(250).fadeOut(250,function(){k(this).addClass("jshide")})}function a(){if(C)return M&&M.dialog("close"),c(),k(f).find('button[type="submit"]').attr("disabled",!1),k(d).triggerHandler("resize"),A&&A(!0),!0;x&&M?(G||(k(u).removeClass("jshide").hide().fadeIn(500),G=!0),J&&(b(k(Q)),J=!1)):c();k(f).find('input[type="submit"]').attr("disabled",
!0);A&&A(!1);return!1}function l(a){var b,c,d=w||{};for(b in d)d.hasOwnProperty(b)&&(c=d[b],a[b]?a[b].value=c:k('<input type="hidden" />').attr("name",b).appendTo(a).val(c))}function e(a){a.preventDefault();a=k(a.target).serializeArray();q(a);H=!0;return!1}function p(a){a.preventDefault();M.dialog("close");return!1}function h(a){a.preventDefault();M.dialog("open").find('input[name="connection_type"]').change();return!1}function r(b){C=b.authed;z=b.method;k(u).find("span.loco-msg").text(b.message||
"Something went wrong.");K=b.warning||"";b.notice&&F.notices.info(b.notice);if(C)"direct"!==z&&(w=b.creds,l(f),H&&b.success&&F.notices.success(b.success)),a();else if(b.reason)F.notices.info(b.reason);else if(b=b.prompt){var c=g();c.html(b).find("form").submit(e);c.dialog("option","title",c.find("h2").remove().text());c.find("button.cancel-button").show().click(p);c.find('input[type="submit"]').addClass("button-primary");a();k(d).triggerHandler("resize")}else F.notices.error("Server didn't return credentials, nor a prompt for credentials")}
function s(){a()}function q(a){H=!1;F.ajax.setNonce("fsConnect",I).post("fsConnect",a,r,s);return a}var w,A,u=f,z=null,H=!1,C=!1,F=d.locoScope,x=f.path.value,B=f.auth.value,I=f["loco-nonce"].value,L=k(u).find("button.button-primary"),Q=n.getElementById(u.id+"-warn"),G=!1,J=!1,K="",M;F.notices.convert(Q).stick();f.connection_type?(w={},w.connection_type=f.connection_type.value,C=!0):x&&B&&q({path:x,auth:B});a();return{applyCreds:function(a){if(a.nodeType)l(a);else{var b,c=w||{};for(b in c)c.hasOwnProperty(b)&&
(a[b]=c[b])}return this},setForm:function(b){f=b;a();l(b);return this},connect:function(){x=f.path.value;B=f.auth.value;q(k(f).serializeArray());return this},listen:function(a){A=a;C&&a(!0);return this}}};return f}({},r,s));n.register("$19",function(f,d,r){function m(d,e,f,h){e="n"===f?c(e):b(e);h&&(e=a(e));return g([].sort,[e])(d)}function g(a,b){return function(c){a.apply(c,b);return c}}function c(a){return function(b,c){var d=b&&b[a]||0,f=c&&c[a]||0;return d===f?0:d>f?1:-1}}function b(a){return function(b,
c){return(b&&b[a]||"").localeCompare(c&&c[a]||"")}}function a(a){return function(b,c){return-1*a(b,c)}}f.init=function(a){function b(a){var c=-1,d=a.length;for(k("tr",u).remove();++c<d;)u.appendChild(a[c].$)}function c(a){q=a?E.find(a,d):d.slice(0);s&&(a=f[s],q=m(q,s,a.type,a.desc));b(q)}var d=[],f=[],g=0,q,r,s,u=a.getElementsByTagName("tbody")[0],z=a.getElementsByTagName("thead")[0],E=n.require("$9","fulltext.js").init();z&&u&&(k("th",z).each(function(a,c){var l=c.getAttribute("data-sort-type");
l&&(a=g,k(c).addClass("loco-sort").click(function(c){c.preventDefault();c=a;var g=f[c],l=g.type,n=!(g.desc=!g.desc);q=m(q||d.slice(0),c,l,n);b(q);r&&r.removeClass("loco-desc loco-asc");r=k(g.$).addClass(n?"loco-desc":"loco-asc").removeClass(n?"loco-asc":"loco-desc");s=c;return!1}),f[g]={$:c,type:l});c.hasAttribute("colspan")?g+=Number(c.getAttribute("colspan")):g++}),k("tr",u).each(function(a,b){var c,e,g,k=[],l={_:a,$:b},m=b.getElementsByTagName("td");for(e in f){c=m[e];(g=c.textContent.replace(/(^\s+|\s+$)/g,
""))&&k.push(g);c.hasAttribute("data-sort-value")&&(g=c.getAttribute("data-sort-value"));switch(f[e].type){case "n":g=Number(g)}l[e]=g}d[a]=l;E.index(a,k)}),a=k('form.loco-filter input[type="text"]',a.parentNode),a.length&&(a=a[0],z=k(a.form),1<d.length?n.require("$10","LocoTextListener.js").listen(a,c):z.hide(),z.on("submit",function(a){a.preventDefault();return!1})))};return f}({},r,s));s=r.locoScope||(r.locoScope={});r=r.locoConf||(r.locoConf={});var G=n.require("$1","t.js").init(),K=r.wplang;
n.require("$2","html.js");n.require("$3","number.js");n.require("$4","array.js");s.l10n=G;G.load(r.wpl10n);K&&G.pluraleq(K.pluraleq);s.string=n.require("$5","string.js");s.notices=n.require("$6","notices.js").init(G);s.ajax=n.require("$7","ajax.js").init(r).localise(G);s.locale=n.require("$8","wplocale.js");s.fulltext=n.require("$9","fulltext.js");s.watchtext=n.require("$10","LocoTextListener.js").listen;s.tooltip=n.require("$11","tooltip.js");s.po={ed:n.require("$12","poedit.js"),kbd:n.require("$13",
"hotkeys.js"),init:n.require("$14","po.js").create,ace:n.require("$15","ace.js").strf("php"),ref:n.require("$16","refs.js")};s.apis=n.require("$17","apis.js");s.fs=n.require("$18","fsconn.js");k("#loco.wrap table.wp-list-table").each(function(f,d){n.require("$19","tables.js").init(d)})})(window,document,window.jQuery);

View file

@ -0,0 +1 @@
!function(n,e,c){function o(){var n=c("#loco-conf > div"),e=n.eq(0).clone(),o=n.length,t="["+o+"]";function i(n,e){var o=e.name.replace("[0]",t);c(e).attr("name",o).val("")}e.attr("id","loco-conf-"+o),e.find("input").each(i),e.find("textarea").each(i),e.find("h2").eq(0).html("New set <span>(untitled)</span>"),e.insertBefore("#loco-form-foot"),f(e.find("a.icon-del"),o),e.hide().slideDown(500),c("html, body").animate({scrollTop:function(n,e){for(var o=n.offsetTop;(n=n.offsetParent)&&n!==e;)o+=n.offsetTop;return o}(e[0])},500)}function f(n,e){return n.click(function(n){return n.preventDefault(),function(n){var e=c("#loco-conf-"+n);e.find('input[name="conf['+n+'][removed]"]').val("1"),e.slideUp(500,function(){c(this).hide().find("table").remove()})}(e),!1})}c("#loco-conf > div").each(function(n,e){f(c(e).find("a.icon-del"),n)}),c("#loco-add-butt").attr("disabled",!1).click(function(n){return n.preventDefault(),o(),!1})}(window,document,jQuery);

View file

@ -0,0 +1 @@
!function(n,o,t){var a=t("#loco-utf8-check")[0].textContent;function c(o,a,e){"success"!==a&&(e=n.ajax.parse(n.ajax.strip(o.responseText))),t("#loco-ajax-check").text("FAILED: "+e).addClass("loco-danger")}1===a.length&&10003===a.charCodeAt(0)||n.notices.warn("This page has a problem rendering UTF-8").stick(),window.ajaxurl&&t("#loco-ajax-url").text(window.ajaxurl),n.ajax.post("ping",{echo:"ΟΚ ✓"},function(o,a,e){o&&o.ping?t("#loco-ajax-check").text(o.ping):c(e,a,o&&o.error&&o.error.message)},c);var e,r=o.apis,i=r.length,s=-1,l=n.locale.parse("fr");function d(o,a){return t("#loco-api-"+o).text(a)}function p(o){var e=o.getId();o.key()?o.translate("OK",l,function(o,a){a?d(e,"OK ✓"):d(e,"FAILED").addClass("loco-danger")}):d(e,"No API key")}for(;++s<i;){e=r[s];try{p(n.apis.create(e))}catch(o){d(e.id,String(o))}}}(window.locoScope,window.locoConf,window.jQuery);

View file

@ -0,0 +1 @@
!function(e,o){var t=o.getElementById("loco-fs"),n=o.getElementById("loco-del");t&&n&&e.locoScope.fs.init(t).setForm(n)}(window,document,jQuery);

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
!function(e,t,o){var c,u,i=t.getElementById("loco-fs"),a=t.getElementById("loco-move"),l=a.path.value;function d(n){o(a).find("button.button-primary").each(function(e,t){t.disabled=n})}i&&a&&(c=e.locoScope.fs.init(i).setForm(a).listen(function(e){d(!(e&&u))}),o(a).change(function(e){var t,n=e.target||{};"dest"!==n.name||!n.checked&&"text"!==n.type||(t=n.value)&&t!==u&&(u=t,d(!0),l!==t&&(i.dest.value=t,c.connect()))}).submit(function(e){return!!u||(e.preventDefault(),!1)}))}(window,document,jQuery);

View file

@ -0,0 +1 @@
!function(e,t,u){var i,o=[],n=e.locoConf,s=e.locoScope,a=0,r=n.paths.length-2,d=t.getElementById("loco-ui"),l=t.getElementById("loco-fs"),f=d.getElementsByTagName("form").item(0),c=d.getElementsByTagName("button"),m=u(d).find("div.diff-meta"),p=c.item(0),g=c.item(1);function h(){return u(d).removeClass("loading")}function v(e){return u(d).find("div.diff").html(e)}function C(e){return h(),u('<p class="error"></p>').text(e).appendTo(v(""))}function y(e,t){var n,a=t.getElementsByTagName("tr"),r=a.length,i=t.getAttribute("data-diff").split(/\D+/),o=i[0],s=i[1],d=i[2],l=i[3];function f(e,t,n){t<=n&&u("<span></span>").text(String(t)).prependTo(e)}for(e=0;e<r;e++)f((n=a[e].getElementsByTagName("td"))[0],o++,s),f(n[2],d++,l)}function j(a){i&&i.abort();var r=o[a];if(null!=r)return v(r),void h();v(""),u(d).addClass("loading"),i=s.ajax.post("diff",{lhs:n.paths[a],rhs:n.paths[a+1]},function(e,t,n){n===i&&((r=e&&e.html)?(v(o[a]=r).find("tbody").each(y),h()):C(e&&e.error||"Unknown error"))},function(e,t,n){e===i&&(i=null,C("Failed to generate diff"))})}function b(e){0<=e&&e<=r&&(j(a=e),function(){var e=a,t=e+1;p.disabled=r<=e,g.disabled=e<=0,m.addClass("jshide").removeClass("diff-meta-current"),m.eq(e).removeClass("jshide").addClass("diff-meta-current"),m.eq(t).removeClass("jshide")}())}l&&f&&s.fs.init(l).setForm(f),r&&(u(p).click(function(e){return e.preventDefault(),b(a+1),!1}).parent().removeClass("jshide"),u(g).click(function(e){return e.preventDefault(),b(a-1),!1}).parent().removeClass("jshide")),b(0)}(window,document,jQuery);

View file

@ -0,0 +1 @@
!function(t,e,c){var i,n,a,l,o,r,u,s=t.locoScope,f=e.getElementById("loco-fs"),d=e.getElementById("loco-poinit"),v=f&&s.fs.init(f),g=(a=(n=d)["select-locale"],l=n["custom-locale"],o=n["use-selector"],r=c(a).focus(p).closest("fieldset").click(p)[0],u=c(l).focus(x).closest("fieldset").click(x)[0],c(o).change(m),m(),s.watchtext(l,function(t){c(l.form).triggerHandler("change")}),{val:function(){var t=b();return t?s.locale.parse(t):s.locale.clone({lang:"zxx"})}});function h(){return o[0].checked}function p(){y(o[0].checked=!0)}function x(){l.value||(l.value=b()),y(!(o[1].checked=!0))}function b(){var t=c(h()?a:l).serializeArray();return t[0]&&t[0].value||""}function m(){return y(h()),!0}function y(t){l.disabled=t,a.disabled=!t,u.className=t?"disabled":"active",r.className=t?"active":"disabled",I()}var z,A=(z=d["select-path"],{val:function(){var t=k("path");return t&&t.value},txt:function(){var t=k("path");return t&&c(t.parentNode).find("code.path").text()}});function k(t){var e=function(){var t=c(z).serializeArray()[0];return t&&t.value||null}();return e&&d[t+"["+e+"]"]}function w(n){c(d).find("button.button-primary").each(function(t,e){e.disabled=n})}function I(){var t=g&&g.val(),e=t&&t.isValid()&&"zxx"!==t.lang,n=A&&A.val(),a=e&&n;if(j(t),w(!0),a){var c=A.txt();c!==i?(i=c,f.path.value=i,v.listen(N).connect()):w(!1)}}function N(t){w(!t)}function j(n){var t=c(d),e=n&&n.toString("_")||"",a=e?"zxx"===e?"<locale>":e:"<invalid>";t.find("code.path span").each(function(t,e){e.textContent=a}),t.find("span.lang").each(function(t,e){!function(t,e){e&&"zxx"!==e.lang?(t.setAttribute("lang",e.lang),t.setAttribute("class",e.getIcon())):(t.setAttribute("lang",""),t.setAttribute("class","lang nolang"))}(e,n)})}function B(t){var e=t&&t.redirect;e&&location.assign(e)}c(d).change(I).submit(function(t){return t.preventDefault(),v.applyCreds(d),s.ajax.submit(t.target,B),!1}),j(g.val())}(window,document,jQuery);

View file

@ -0,0 +1 @@
!function(t,e,n){var o=t.locoScope,i=e.getElementById("loco-fs"),c=e.getElementById("loco-potinit");function r(t){var e=t&&t.redirect;e&&location.assign(e)}n(c).submit(function(t){return t.preventDefault(),o.ajax.submit(t.target,r),!1}),i&&o.fs.init(i).setForm(c)}(window,document,jQuery);

View file

@ -0,0 +1 @@
!function(n,o,f){var a,c,i,l,r,p,e,s,u,t=n.locoConf,d=n.locoScope,h=d.po.ref.init(d,t),v=o.getElementById("loco-po");function g(){r.length&&(p.push([i,l]),c.push(r),r=[]),i=null}function m(t){return f('<ol class="msgcat"></ol>').attr("start",t).appendTo(a)}function x(t){e!==t&&(f("#loco-content")[t?"removeClass":"addClass"]("loco-invalid"),e=t)}a=v,c=d.fulltext.init(),r=[],p=[],s=!(e=!0),(u=f(a).find("li")).each(function(t,e){var n,o=f(e);o.find("span.po-none").length?g():(l=t,null==i&&(i=t),(n=o.find(".po-text").text())&&(r=r.concat(n.replace(/\\[ntvfab\\"]/g," ").split(" "))))}),g(),d.watchtext(f(a.parentNode).find("form.loco-filter")[0].q,function(t){t?function(t){var e,n,o,i=c.find(t),l=-1,r=i.length;if(f("ol",a).remove(),r){for(;++l<r;)for(o=m((e=(n=p[i[l]])[0])+1);e<=n[1];e++)o.append(u[e]);x(!0)}else x(!1),m(0).append(f("<li></li>").text(d.l10n._("Nothing matches the text filter")));s=!0,b()}(t):s&&(x(!0),s=!1,f("ol",a).remove(),m(1).append(u),b())}),f(v).removeClass("loco-loading");var C,y,b=(y=v.clientHeight-2,function(){var t=function(t,e){for(var n=t.offsetTop||0;(t=t.offsetParent)&&t!==e;)n+=t.offsetTop||0;return n}(v,o.body),e=n.innerHeight-t-20;C!==e&&(v.style.height=e<y?String(e)+"px":"",C=e)});b(),f(n).resize(b),f(v).click(function(t){var e=t.target;if(e.hasAttribute("href"))return t.preventDefault(),h.load(e.textContent),!1})}(window,document,jQuery);

View file

@ -0,0 +1 @@
!function(e,o,a){function n(e,o,n){function r(){i("Failed to contact remote API"),c=null}function u(){c&&(clearTimeout(c),c=null)}var c=setTimeout(r,3e3);return i(""),a.ajax({url:s.apiUrl+"/"+e+"/"+o+".jsonp?version="+encodeURIComponent(n),dataType:"jsonp",success:function(e,o,n){if(c){u();var t=e&&e.exact;o=e&&e.status;t?function(e){d["json-content"].value=e,a("#loco-remote-empty").hide(),a("#loco-remote-found").show()}(t):404===o?i("Sorry, we don't know a bundle by this name"):(l.notices.debug(e.error||"Unknown server error"),r())}},error:function(){c&&(u(),r())},cache:!0}),{abort:u}}function i(e){d["json-content"].value="",a("#loco-remote-empty").show().find("span").text(e),a("#loco-remote-found").hide().removeClass("jshide")}var t,l=e.locoScope,s=e.locoConf,d=o.getElementById("loco-remote");a(d).find('button[type="button"]').click(function(e){return e.preventDefault(),t&&t.abort(),t=n(d.vendor.value,d.slug.value,d.version.value),!1}),a(d).find('input[type="reset"]').click(function(e){return e.preventDefault(),i(""),!1});a.ajax({url:s.apiUrl+"/vendors.jsonp",dataType:"jsonp",success:function(e){for(var o,n,t=-1,r=e.length,u=a(d.vendor).html("");++t<r;)o=e[t][0],n=e[t][1],u.append(a("<option></option>").attr("value",o).text(n))},cache:!0})}(window,document,jQuery);

367
vendor/loco-translate/readme.txt vendored Normal file
View file

@ -0,0 +1,367 @@
=== Loco Translate ===
Contributors: timwhitlock
Tags: translation, translators, localization, localisation, l10n, i18n, Gettext, PO, MO, productivity, multilingual, internationalization
Requires at least: 4.1
Requires PHP: 5.2.4
Tested up to: 5.4.1
Stable tag: 2.4.0
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Translate WordPress plugins and themes directly in your browser
== Description ==
Loco Translate provides in-browser editing of WordPress translation files.
It also provides localization tools for developers, such as extracting strings and generating templates.
Features include:
* Built-in translation editor within WordPress admin
* Integration with automatic translation APIs
* Create and update language files directly in your theme or plugin
* Extraction of translatable strings from your source code
* Native MO file compilation without the need for Gettext on your system
* Support for PO features including comments, references and plural forms
* PO source view with clickable source code references
* Protected language directory for saving custom translations
* Configurable PO file backups with diff and restore capability
* Built-in WordPress locale codes
Official [Loco](https://localise.biz/) WordPress plugin by Tim Whitlock.
For more information please visit our [plugin page](https://localise.biz/wordpress/plugin).
== Installation ==
= Basic usage: =
Translators: To translate a theme into your language, follow these steps:
1. Create the protected languages directory at `wp-content/languages/loco/themes`
2. Ensure this directory writeable by the web server
3. Find your theme in the list at *Loco Translate > Themes*
4. Click `+ New language` and follow the on-screen prompts.
Developers: To translate your own theme or plugin for distribution, follow these steps:
1. Create a `languages` subdirectory in your bundles root directory
2. Ensure this directory writeable by the web server
3. Find the bundle at either *Loco Translate > Themes* or *Loco Translate > Plugins*
4. Click `+ Create template` and follow the on-screen prompts to extract your strings.
5. Click `+ New language` and follow the on-screen prompts to add your own translations.
= Installing manually: =
1. Unzip all files to the `wp-content/plugins/loco-translate` directory
2. Log into WordPress admin and activate the 'Loco Translate' plugin through the 'Plugins' menu
3. Go to *Loco Translate > Home* in the left-hand menu to start translating
More information on using the plugin is [available here](https://localise.biz/wordpress/plugin).
== Frequently Asked Questions ==
Please visit the [FAQs page](https://localise.biz/wordpress/plugin/faqs) on our website for the most common issues.
= How do I use Loco Translate? =
Try our [Guides and Tutorials](https://localise.biz/wordpress/plugin#guides).
= How do I get more help? =
If you have a problem using Loco Translate, please try our [help pages](https://localise.biz/wordpress/plugin).
There's a lot of information there to help you understand how it works and the most common pitfalls to avoid.
To report a bug please start a new topic in the [support forum](https://wordpress.org/support/plugin/loco-translate),
but please check the [FAQs](https://localise.biz/wordpress/plugin/faqs) for similar issues first.
If you decide to submit a bug report please post enough [relevant detail](https://localise.biz/wordpress/plugin/faqs/debug-info) for us to reproduce your issue.
= Is my data protected? =
We don't collect your data or snoop on you. See the [plugin privacy notice](https://localise.biz/wordpress/plugin/privacy).
== Screenshots ==
1. Translating strings in the browser with the Loco PO Editor
2. Showing translation progress for theme language files
3. PO source view with text filter and clickable file references
4. Restore tab showing PO diff view with revert function
5. Showing access to translations by installed language
== Changelog ==
= 2.4.0
* Added support for third party translation APIs
* Added file references to editor source pane in code view
* Added fuzzy matching during editor Sync operation
* Style changes including rearrangement of editor buttons
* Elevated warnings when scripts are tampered with
* Removed remnants of legacy version 1.x
= 2.3.4 =
* Updated translatable strings
* Added missing template recommendation
* Alerting in debug mode when scripts are tampered with
* Fix for Hello Dolly being installed into a folder
* Removed translation column in POT edit mode
* Added setting to prevent 'translating' of POT files
* Enabled some linkable translations using wp_kses
* Bumped WordPress version to 5.4.1
= 2.3.3 =
* Fixed fatal error when class not found
= 2.3.2 =
* Removed login/email from default Last-Translator credit
* Bumped WP compatibility to 5.4
* Fixed PHP 7.4 deprecations
= 2.3.1 =
* Default POT getter now looks in "lang" directory
* Not calling deprecated magic quotes functions under PHP 7.4
* Fixed issue with conflicting page hooks
* Ajax file uploads now enabled by default
* Removed legacy option migrations from 1.x branch
* Bumped WP compatibility to 5.2.4
= 2.3.0 =
* Added experimental support for multipart uploads
* Added relocation tab for moving translation sets
* Creation of missing directories when writing new files
* Fixed duplicate file addition when iterating over symlink
* Bumped WP compatibility to 5.2.1
= 2.2.2 =
* Security fixes for reading sensitive files
* Fixed old PHP version error in data files
* Bumped WP compatibility to 5.1.1
= 2.2.1 =
* Fixed bug where plural tabs not displaying RTL
* Various improvements to PO parser incl. better charset handling
* Excluding node_modules and vendor directories by default
* Transients now have maximum lifespan of 10 days, refreshed after 24h
* Symlink fix for followed theme paths detected outside theme
* Deprecated config repository lookup
* Bumped WP compatibility to 5.1
= 2.2.0 =
* Fix for empty language code when getting plural rules
* Added X-Loco-Version header to generated Gettext files
* Added sanity check for mbstring.func_overload madness
* Added "Assign template" link on missing template page
* Added JavaScript string extraction (experimental)
* Editor supports sprintf-js when javascript-format tag present
* Fix for duplicate comments when end punctuation differs
* Marking msgctxt more clearly in editor views
* Added `loco_admin_shutdown` action hook
* Bumped WP compatibility to 5.0 (beta)
= 2.1.5 =
* Updated locale data
* Minor fix to file reference resolution
* Fixed windows paths with trailing backslash
* Fixed ssh-keys toggling issue
* Rejigged buffer handling during Ajax
* Bumped WP compatibility to 4.9.8
= 2.1.4 =
* Bumped WP compatibility to 4.9.6
* Hooked in privacy policy suggestion
= 2.1.3 =
* Added loco_locale_name filter and updated locale data
* Fixed editor column sorting to update as values change
* Supporting RTL text in editor preview rows
* Minor refactor of debug mode routing check
* Minor PO parser improvements
* Bumped WP compatibility to 4.9.5
= 2.1.2 =
* Fixed undeclared property in admin hook
* Fixed incompatibility with older WordPress
* Fixed incorrect millisecond reporting in footer
* Removed locale progress column for en_US locale
* Tweaks to debugging and error logging
= 2.1.1 =
* Setting `Project-Id-Version` on new POT files
* Added source view to quick links in file tables
* Supporting only WordPress style locale codes
* Editor screen tolerates missing PO headers
* Ajax debugging improvements for issue reporting
* Added loco_parse_locale action callback
= 2.1.0 =
* Add `fs_protect` setting to avoid overwriting system files
* Fixed bug in connect dialogue where errors not redisplayed
* Minor improvements to inline notices
* Removed downgrade notice under version tab
* Fixed extraction bug where file header confused with comment
* Resolved some inconsistencies between PHP and JS utilities
* Added Restore tab with diff display
* Added `loco_settings` hook
* Prevented editor from changing PO document order
* Added default string sorting to extracted strings
* Added "Languages" section for grouping files by locale
* Fixed bug where translations loaded before user profile language set
* Added loco_locale_plurals filter for customising plural rules
* Allowing PO files to enforce their own Plural-Forms rules
* Added `loco_allow_remote` filter for debugging remote problems
* Updated plural forms from Unicode CLDR
* PHP extractor avoids repeated comments
* Bumped WP compatibility to 4.9.4
= 2.0.17 =
* Unofficial languages showing in “Installed” dropdown
* Fixed extraction bug where comment confused with file header
* Fixed issue where src attributes requested from server during HTML strip
* Added loco_admin_init hook into ajax router for consistency
* Added warning on file info page when file is managed by WordPress
* Minor help link and layout tweaks
* Bumped WP compatibility to 4.9.1
= 2.0.16 =
* File writer observes wp_is_file_mod_allowed
* Fixed progress bug in editor for locales with nplurals=1
* Made plural form categories translatable for editor UI
* Sync-from-source raises warning when files are skipped
* Added hack for extracting from .twig as per .php
* Added warning when child themes declare parent text domain
* Added option to control PO line wrapping
* Bumped WP compatibility to 4.8.2
= 2.0.15 =
* Permanently removed legacy version 1.x
* Fixed bug where editor code view was not redrawn on resize
* Fixed bug where fuzzy flag caused format flag to be ignored
* Fixed bug where autoloader responded to very long class names
* Purging WP object cache when active plugin list changes
* Added experimental source word count into POT info tab
* Bumped WP compatibility to 4.8.1
= 2.0.14 =
* Editor improvements inc. column sorting
* Added warnings that legacy version will be removed
* Added PO source view text filtering
* Added _fs_nonce for 4.7.5 compatibility
* Migrated to canonical text domain
* Removed wp class autoloading
= 2.0.13 =
* CSS conflict fixes
* Added option for UTF-8 byte order mark
* Printf highlighting observes no-php-format flag
* Fixed issue with translator role losing “read” permission
= 2.0.12 =
* Minor fix for root path configs
* Added alternative PHP extensions setting
* Bumped WP version to 4.7.3
* LoadHelper fix for core files
* Allow revoking of permissions from translator role
* Allow network admins to deny access to site admins
= 2.0.11 =
* Extra debug logging and error diagnostics
* Forcefully clear output buffers before Ajax flush
* Bumped WordPress version to 4.7
* Experimental wildcard text domain support
= 2.0.10 =
* Allows missing domain argument in plugin_locale filter
* Reverted editor changes that disabled readonly text
* Added invisibles and coding editor switches
* Added table filtering via text query
* Added Last-Translator user preference
= 2.0.9 =
* Bumped minimum WordPress version to 4.1
* Some optimisation of transient caching
* Fixed hash table settings bug
= 2.0.8 =
* Source refs fix for files in unknown subsets
* Downgrades PO formatting exceptions to PHP warnings
* Renamed function prefixes to avoid PHP 7 warnings
* Better support for php-format and no-php-format flag
* PO source and editor UI tweaks
* Localised strings and implemented in js
= 2.0.7 =
* Fixed prototype.js conflict
* More Windows file path fixes
* Added loco_current_translator filter
* Fixed false positive in extra files test
= 2.0.6 =
* PO wrapping bugfix
* Downgraded source code bugfix
* Tolerating headerless POT files
* Core bundle metadata tweaks
= 2.0.5 =
* Deferred missing tokenizer warning
* Allows editing of files in unconfigured sets
* Added maximum PHP file size for string extraction
* Display of PHP fatal errors during Ajax
= 2.0.4 =
* Reduced session failures to debug notices
* Added wp_roles support for WP < 4.3
* Fixed domain listener bugs
= 2.0.3 =
* Added support for Windows servers
* Removed incomplete config warning on bundle overview
= 2.0.2 =
* Fixed bug when absolute path used to get plugins
* Added loco_plugins_data filter
* Added theme Template Name header extraction
* Minor copy amends
= 2.0.1 =
* Added help link in settings page
* Fixed opendir warnings in legacy code
* Catching session errors during init
* Removing meta row link when plugin not found
= 2.0.0 =
* First release of completely rebuilt version 2
== Upgrade Notice ==
= 2.4.0 =
* Various improvements including automatic translation support
== Keyboard shortcuts ==
The PO file editor supports the following keyboard shortcuts for faster translating:
* Done and Next: `Ctrl ↵`
* Next string: `Ctrl ↓`
* Previous string: `Ctrl ↑`
* Next untranslated: `Shift Ctrl ↓`
* Previous untranslated: `Shift Ctrl ↑`
* Copy from source text: `Ctrl B`
* Clear translation: `Ctrl K`
* Toggle Fuzzy: `Ctrl U`
* Save PO / compile MO: `Ctrl S`
* Toggle invisibles: `Shift Ctrl I`
* Suggest translation: `Ctrl J`
Mac users can use ⌘ Cmd instead of Ctrl.

473
vendor/loco-translate/src/Locale.php vendored Normal file
View file

@ -0,0 +1,473 @@
<?php
/**
* Represents a WordPress locale
*
* @property string $lang
* @property string $region
* @property string $variant
*/
class Loco_Locale implements JsonSerializable {
/**
* Language subtags
* @var array
*/
private $tag;
/**
* Cached composite tag
* @var string
*/
private $_tag;
/**
* Cached icon css class
* @var string
*/
private $icon;
/**
* Name in English
* @var string
*/
private $name;
/**
* Name in language of self
* @var string
*/
private $_name;
/**
* Cache of raw plural data
* @var array
*/
private $plurals;
/**
* Validity cache
* @var bool
*/
private $valid;
/**
* @param string
* @return Loco_Locale
*/
public static function parse( $tag ){
$locale = new Loco_Locale('');
try {
$locale->setSubtags( loco_parse_wp_locale($tag) );
}
catch( Exception $e ){
// isValid should return false
}
do_action( 'loco_parse_locale', $locale, $tag );
return $locale;
}
/**
* Construct from subtags NOT from composite tag. See self::parse
* Note that this skips normalization and validation steps
* @param string
* @param string
* @param string
*/
public function __construct( $lang = '', $region = '', $variant = '' ){
$this->tag = compact('lang','region','variant');
}
/**
* @internal
* Allow read access to subtags
*/
public function __get( $t ){
return isset($this->tag[$t]) ? $this->tag[$t] : '';
}
/**
* @internal
* Allow write access to subtags
*/
public function __set( $t, $s ){
if( isset($this->tag[$t]) ){
$this->tag[$t] = $s;
$this->setSubtags( $this->tag );
}
}
/**
* Set subtags as produced from loco_parse_wp_locale
* @return Loco_Locale
*/
public function setSubtags( array $tag ){
$this->valid = false;
$default = array( 'lang' => '', 'region' => '', 'variant' => '' );
// disallow setting of unsupported tags
if( $bad = array_diff_key($tag, $default) ){
throw new Loco_error_LocaleException('Unsupported subtags: '.implode(',',$bad) );
}
$tag += $default;
// language tag is minimum requirement
if( ! $tag['lang'] ){
throw new Loco_error_LocaleException('Locale must have a language');
}
// no UN codes in Wordpress
if( is_numeric($tag['region']) ){
throw new Loco_error_LocaleException('Numeric regions not supported');
}
// single, scalar variant. Only using for Formal german currently.
if( is_array($tag['variant']) ){
$tag['variant'] = implode('_',$tag['variant']);
}
// normalize case
$tag['lang'] = strtolower($tag['lang']);
$tag['region'] = strtoupper($tag['region']);
$tag['variant'] = strtolower($tag['variant']);
// set subtags and invalidate cache of language tag
$this->tag = $tag;
$this->_tag = null;
$this->icon = null;
$this->valid = true;
return $this;
}
/**
* @return Loco_Locale
*/
public function normalize(){
try {
$this->setSubtags( $this->tag );
}
catch( Loco_error_LocaleException $e ){
$this->_tag = '';
$this->icon = null;
$this->name = 'Invalid locale';
$this->_name = null;
}
return $this;
}
/**
* @return string
*/
public function __toString(){
$str = $this->_tag;
if( is_null($str) ){
$str = implode('_',array_filter($this->tag));
$this->_tag = $str;
}
return $str;
}
/**
* Get stored name in current display language.
* Note that no dynamic translation of English name is performed, but can be altered with loco_parse_locale filter
* @return string | null
*/
public function getName(){
if( $name = $this->name ){
// use canonical native name only when current language matches
// deliberately not matching whole tag such that fr_CA would show native name of fr_FR
if( $_name = $this->getNativeName() ){
$locale = self::parse( function_exists('get_user_locale') ? get_user_locale() : get_locale() );
if( $this->lang === $locale->lang ){
$name = $_name;
}
}
return $name;
}
}
/**
* Get canonical native name as defined by WordPress
* @return string | null
*/
public function getNativeName(){
if( $name = $this->_name ){
return $name;
}
}
/**
* @return string
*/
public function getIcon(){
$icon = $this->icon;
if( is_null($icon) ){
$tag = array();
if( ! $this->tag['lang'] ){
$tag[] = 'lang lang-zxx';
}
foreach( $this->tag as $class => $code ){
if( $code ){
$tag[] = $class.' '.$class.'-'.$code;
}
}
$icon = strtolower( implode(' ',$tag) );
$this->icon = $icon;
}
return $icon;
}
/**
* @return Loco_Locale
*/
public function setIcon( $css ){
if( $css ){
$this->icon = (string) $css;
}
else {
$this->icon = null;
}
return $this;
}
/**
* @return Loco_Locale
*/
public function setName( $english_name, $native_name = '' ){
$this->name = apply_filters('loco_locale_name', $english_name, $native_name );
$this->_name = (string) $native_name;
return $this;
}
/**
* Test whether locale is valid
*/
public function isValid(){
if( is_null($this->valid) ){
$this->normalize();
}
return $this->valid;
}
/**
* Resolve this locale's "official" name from WordPress's translation api
* @return string English name currently set
*/
public function fetchName( Loco_api_WordPressTranslations $api ){
$tag = (string) $this;
// pull from WordPress translations API if network allowed
if( $locale = $api->getLocale($tag) ){
$this->setName( $locale->getName(), $locale->getNativeName() );
}
return $this->getName();
}
/**
* Resolve this locale's name from compiled Loco data
* @return string English name currently set
*/
public function buildName(){
$names = array();
// should at least have a language or not valid
if( $this->isValid() ){
$code = $this->tag['lang'];
$db = Loco_data_CompiledData::get('languages');
if( $name = $db[$code] ){
// if variant is present add only that in brackets (no lookup required)
if( $code = $this->tag['variant'] ){
$name .= ' ('.ucfirst($code).')';
}
// else add region in brackets if present
else if( $code = $this->tag['region'] ){
$db = Loco_data_CompiledData::get('regions');
if( $extra = $db[$code] ){
$name .= ' ('.$extra.')';
}
else {
$name .= ' ('.$code.')';
}
}
$this->setName( $name );
}
}
else {
$this->setName( __('Invalid locale','loco-translate') );
}
return $this->getName();
}
/**
* Ensure locale has a label, even if it has to fall back to language code or error
* @return string
*/
public function ensureName( Loco_api_WordPressTranslations $api ){
$name = $this->getName();
if( ! $name ){
$name = $this->fetchName($api);
// failing that, build own own name from components
if( ! $name ){
$name = $this->buildName();
// last resort, use tag as name
if( ! $name ){
$name = (string) $this;
$this->setName( $name );
}
}
}
return $name;
}
/**
* @return array
*/
public function jsonSerialize(){
$a = $this->tag;
$a['label'] = $this->getName();
// plural data expected by editor
$p = $this->getPluralData();
$a['pluraleq'] = $p[0];
$a['plurals'] = $p[1];
$a['nplurals'] = count($p[1]);
return $a;
}
/**
* Get plural data with translated forms
* @internal
* @return array [ (string) equation, (array) forms ]
*/
public function getPluralData(){
$cache = $this->plurals;
if( ! $cache ){
$lc = $this->lang;
$db = Loco_data_CompiledData::get('plurals');
$id = $lc && isset($db[$lc]) ? $db[$lc] : 0;
$cache = $this->setPlurals( $db[''][$id] );
}
return $cache;
}
/**
* @return int
*/
public function getPluralCount(){
$raw = $this->getPluralData();
return count( $raw[1] );
}
/**
* @return array
*/
private function setPlurals( array $raw ){
$raw = apply_filters( 'loco_locale_plurals', $raw, $this );
// handle languages with no plural forms, where n is always 0
if( ! isset($raw[1][1]) ){
// Translators: Plural category for languages that have no plurals
$raw[1] = array( _x('All forms','Plural category','loco-translate') );
$raw[0] = '0';
}
// else translate all implemented plural forms
// for meaning of categories, see http://cldr.unicode.org/index/cldr-spec/plural-rules
else {
$forms = array(
// Translators: Plural category for zero quantity
'zero' => _x('Zero','Plural category','loco-translate'),
// Translators: Plural category for singular quantity
'one' => _x('One','Plural category','loco-translate'),
// Translators: Plural category used in some multi-plural languages
'two' => _x('Two','Plural category','loco-translate'),
// Translators: Plural category used in some multi-plural languages
'few' => _x('Few','Plural category','loco-translate'),
// Translators: Plural category used in some multi-plural languages
'many' => _x('Many','Plural category','loco-translate'),
// Translators: General plural category not covered by other forms
'other' => _x('Other','Plural category','loco-translate'),
);
foreach( $raw[1] as $k => $v ){
if( isset($forms[$v]) ){
$raw[1][$k] = $forms[$v];
}
}
}
$this->plurals = $raw;
return $raw;
}
/**
* Get PO style Plural-Forms header value comprising number of forms and integer equation for n
* @return string
*/
public function getPluralFormsHeader(){
list( $equation, $forms ) = $this->getPluralData();
return sprintf('nplurals=%u; plural=%s;', count($forms), $equation );
}
/**
* Apply PO style Plural-Forms header.
* @param string e.g. "nplurals=2; plural=n != 1;"
* @return Loco_Locale
*/
public function setPluralFormsHeader( $str ){
if( ! preg_match('/^nplurals=(\\d);\s*plural=([ +\\-\\/*%!=<>|&?:()n0-9]+);?$/', $str, $match ) ){
throw new InvalidArgumentException('Invalid Plural-Forms header, '.json_encode($str) );
}
$cache = $this->getPluralData();
$exprn = $match[2];
// always alter if equation differs
if( $cache[0] !== $exprn ){
$this->plurals[0] = $exprn;
// alter number of forms if changed
$nplurals = max( 1, (int) $match[1] );
if( $nplurals !== count($cache[1]) ){
// named forms must also change, but Plural-Forms cannot contain this information
// as a cheat, we'll assume first form always "one" and last always "other"
for( $i = 1; $i < $nplurals; $i++ ){
$name = 1 === $i ? 'one' : sprintf('Plural %u',$i);
$forms[] = $name;
}
$forms[] = 'other';
$this->setPlurals( array($exprn,$forms) );
}
}
return $this;
}
/**
* @return string
*/
public function exportJson(){
return json_encode( $this->jsonSerialize() );
}
}
// Depends on compiled library
if( ! function_exists('loco_parse_wp_locale') ){
loco_include('lib/compiled/locales.php');
}

View file

@ -0,0 +1,78 @@
<?php
/**
* @codeCoverageIgnore
*/
class Loco_admin_DebugController extends Loco_mvc_AdminController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$this->set('title','DEBUG');
}
/**
* {@inheritdoc}
*/
public function render(){
// debug package listener
$themes = array();
/* @var $bundle Loco_package_Bundle */
foreach( Loco_package_Listener::singleton()->getThemes() as $bundle ){
$themes[] = array (
'id' => $bundle->getId(),
'name' => $bundle->getName(),
'default' => $bundle->getDefaultProject()->getSlug(),
'count' => count($bundle),
);
}
$this->set('themes', $themes );
$plugins = array();
/* @var $bundle Loco_package_Bundle */
foreach( Loco_package_Listener::singleton()->getPlugins() as $bundle ){
$plugins[] = array (
'id' => $bundle->getId(),
'name' => $bundle->getName(),
'default' => $bundle->getDefaultProject()->getSlug(),
'count' => count($bundle),
);
}
// $this->set( 'plugins', Loco_package_Plugin::get_plugins() );
// $this->set('installed', wp_get_installed_translations('plugins') );
// $this->set('active', get_option( 'active_plugins', array() ) );
// $this->set('langs',get_available_languages());
/*$plugins = get_plugins();
$plugin_info = get_site_transient( 'update_plugins' );
foreach( $plugins as $plugin_file => $plugin_data ){
if ( isset( $plugin_info->response[$plugin_file] ) ) {
$plugins[$plugin_file]['____'] = $plugin_info->response[$plugin_file];
}
}*/
/*/ inspect session and test flash messages
$session = Loco_data_Session::get();
$session->flash( 'success', microtime() );
$this->set('session', $session->getArrayCopy() );
Loco_data_Session::close();*/
// try some notices
Loco_error_AdminNotices::add( new Loco_error_Success('This is a sample success message') );
Loco_error_AdminNotices::add( new Loco_error_Warning('This is a sample warning') );
Loco_error_AdminNotices::add( new Loco_error_Exception('This is a sample error') );
Loco_error_AdminNotices::add( new Loco_error_Debug('This is a sample debug message') );
//*/
return $this->view('admin/debug');
}
}

View file

@ -0,0 +1,42 @@
<?php
/**
*
*/
class Loco_admin_ErrorController extends Loco_mvc_AdminController {
public function renderError( Exception $e ){
$this->set('error', Loco_error_Exception::convert($e) );
return $this->render();
}
public function render(){
$e = $this->get('error');
if( $e ){
/* @var Loco_error_Exception $e */
$file = Loco_mvc_FileParams::create( new Loco_fs_File( $e->getRealFile() ) );
$file['line'] = $e->getRealLine();
$this->set('file', $file );
if( loco_debugging() ){
$trace = array();
foreach( $e->getRealTrace() as $raw ) {
$frame = new Loco_mvc_ViewParams($raw);
if( $frame->has('file') ){
$frame['file'] = Loco_mvc_FileParams::create( new Loco_fs_File($frame['file']) )->relpath;
}
$trace[] = $frame;
}
$this->set('trace',$trace);
}
}
else {
$e = new Loco_error_Exception('Unknown error');
$this->set('error', $e );
}
return $this->view( $e->getTemplate() );
}
}

View file

@ -0,0 +1,72 @@
<?php
/**
* Generic navigation helper.
*/
class Loco_admin_Navigation extends ArrayIterator {
/**
* @return Loco_admin_Navigation
*/
public function add( $name, $href = null, $active = false ){
$this[] = new Loco_mvc_ViewParams( compact('name','href','active') );
return $this;
}
/* not currently used
* @return Loco_admin_Navigation
*
public function addRoute( $name, $action ){
$href = Loco_mvc_AdminRouter::generate( $action );
return $this->add( $name, $href );
}*/
/**
* Create a breadcrumb trail for a given view below a bundle
* @return Loco_admin_Navigation
*/
public static function createBreadcrumb( Loco_package_Bundle $bundle ){
$nav = new Loco_admin_Navigation;
// root link depends on bundle type
$type = strtolower( $bundle->getType() );
if( 'core' !== $type ){
$link = new Loco_mvc_ViewParams( array(
'href' => Loco_mvc_AdminRouter::generate($type),
) );
if( 'theme' === $type ){
$link['name'] = __('Themes','loco-translate');
}
else {
$link['name'] = __('Plugins','loco-translate');
}
$nav[] = $link;
}
// Add actual bundle page, href may be unset to show as current page if needed
$nav->add (
$bundle->getName(),
Loco_mvc_AdminRouter::generate( $type.'-view', array( 'bundle' => $bundle->getHandle() ) )
);
// client code will add current page
return $nav;
}
/**
* @return Loco_mvc_ViewParams
*
public function getSecondLast(){
$i = count($this);
if( $i > 1 ){
return $this[ $i-2 ];
}
}*/
}

View file

@ -0,0 +1,35 @@
<?php
/**
*
*/
abstract class Loco_admin_RedirectController extends Loco_mvc_AdminController {
/**
* Get full URL for redirecting to.
* @var string
*/
abstract public function getLocation();
/**
* {@inheritdoc}
*/
public function init(){
$location = $this->getLocation();
if( $location && wp_redirect($location) ){
// @codeCoverageIgnoreStart
exit;
}
}
/**
* @internal
*/
public function render(){
return 'Failed to redirect';
}
}

View file

@ -0,0 +1,90 @@
<?php
/**
* Highest level Loco admin screen.
*/
class Loco_admin_RootController extends Loco_admin_list_BaseController {
/**
* {@inheritdoc}
*/
public function getHelpTabs(){
return array (
__('Overview','default') => $this->viewSnippet('tab-home'),
);
}
/**
* Render main entry home screen
*/
public function render(){
// translators: home screen title where %s is the version number
$this->set('title', sprintf( __('Loco Translate %s','loco-translate'), loco_plugin_version() ) );
// Show currently active theme on home page
$theme = Loco_package_Theme::create(null);
$this->set('theme', $this->bundleParam($theme) );
// Show plugins that have currently loaded translations
$bundles = array();
foreach( Loco_package_Listener::singleton()->getPlugins() as $bundle ){
try {
$bundles[] = $this->bundleParam($bundle);
}
catch( Exception $e ){
// bundle should exist if we heard it. reduce to debug notice
Loco_error_AdminNotices::debug( $e->getMessage() );
}
}
$this->set('plugins', $bundles );
// Show recently "used' bundles
$bundles = array();
$recent = Loco_data_RecentItems::get();
// filter in lieu of plugin setting
$maxlen = apply_filters('loco_num_recent_bundles', 10 );
foreach( $recent->getBundles(0,$maxlen) as $id ){
try {
$bundle = Loco_package_Bundle::fromId($id);
$bundles[] = $this->bundleParam($bundle);
}
catch( Exception $e ){
// possible that bundle ID changed since being saved in recent items list
}
}
$this->set('recent', $bundles );
// current locale and related links
$locale = Loco_Locale::parse( get_locale() );
$api = new Loco_api_WordPressTranslations;
$tag = (string) $locale;
$this->set( 'siteLocale', new Loco_mvc_ViewParams( array(
'code' => $tag,
'name' => ( $name = $locale->ensureName($api) ),
'attr' => 'class="'.$locale->getIcon().'" lang="'.$locale->lang.'"',
'link' => '<a href="'.esc_url(Loco_mvc_AdminRouter::generate('lang-view', array('locale'=>$tag) )).'">'.esc_html($name).'</a>',
//'opts' => admin_url('options-general.php').'#WPLANG',
) ) );
// user's "admin" language may differ and is worth showing
if( function_exists('get_user_locale') ){
$locale = Loco_Locale::parse( get_user_locale() );
$alt = (string) $locale;
if( $tag !== $alt ){
$this->set( 'adminLocale', new Loco_mvc_ViewParams( array(
'name' => ( $name = $locale->ensureName($api) ),
'link' => '<a href="'.esc_url(Loco_mvc_AdminRouter::generate('lang-view', array('locale'=>$tag) )).'">'.esc_html($name).'</a>',
) ) );
}
}
$this->set('title', __('Welcome to Loco Translate','loco-translate') );
return $this->view('admin/root');
}
}

View file

@ -0,0 +1,161 @@
<?php
/**
* Base controller for any admin screen related to a bundle
*/
abstract class Loco_admin_bundle_BaseController extends Loco_mvc_AdminController {
/**
* @var Loco_package_Bundle
*/
private $bundle;
/**
* @var Loco_package_Project
*/
private $project;
/**
* @return Loco_package_Bundle
*/
public function getBundle(){
if( ! $this->bundle ){
$type = $this->get('type');
$handle = $this->get('bundle');
$this->bundle = Loco_package_Bundle::createType( $type, $handle );
}
return $this->bundle;
}
/**
* Commit bundle config to database
* @return Loco_admin_bundle_BaseController
*/
protected function saveBundle(){
$custom = new Loco_config_CustomSaved;
if( $custom->setBundle($this->bundle)->persist() ){
Loco_error_AdminNotices::success( __('Configuration saved','loco-translate') );
}
// invalidate bundle in memory so next fetch is re-configured from DB
$this->bundle = null;
return $this;
}
/**
* Remove bundle config from database
* @return Loco_admin_bundle_BaseController
*/
protected function resetBundle(){
$option = $this->bundle->getCustomConfig();
if( $option && $option->remove() ){
Loco_error_AdminNotices::success( __('Configuration reset','loco-translate') );
// invalidate bundle in memory so next fetch falls back to auto-config
$this->bundle = null;
}
return $this;
}
/**
* @return Loco_package_Project
*/
public function getProject(){
if( ! $this->project ){
$bundle = $this->getBundle();
$domain = $this->get('domain');
if( ! $domain ){
throw new Loco_error_Exception( sprintf('Translation set not known in %s', $bundle ) );
}
$this->project = $bundle->getProjectById($domain);
if( ! $this->project ){
throw new Loco_error_Exception( sprintf('Unknown translation set: %s not in %s', json_encode($domain), $bundle ) );
}
}
return $this->project;
}
/**
* @return Loco_admin_Navigation
*/
protected function prepareNavigation(){
$bundle = $this->getBundle();
// navigate up to bundle listing page
$breadcrumb = Loco_admin_Navigation::createBreadcrumb( $bundle );
$this->set( 'breadcrumb', $breadcrumb );
// navigate between bundle view siblings
$tabs = new Loco_admin_Navigation;
$this->set( 'tabs', $tabs );
$actions = array (
'view' => __('Overview','loco-translate'),
'setup' => __('Setup','loco-translate'),
'conf' => __('Advanced','loco-translate'),
);
if( loco_debugging() ){
$actions['debug'] = __('Debug','loco-translate');
}
$suffix = $this->get('action');
$prefix = strtolower( $this->get('type') );
$getarg = array_intersect_key( $_GET, array('bundle'=>'') );
foreach( $actions as $action => $name ){
$href = Loco_mvc_AdminRouter::generate( $prefix.'-'.$action, $getarg );
$tabs->add( $name, $href, $action === $suffix );
}
return $breadcrumb;
}
/**
* Prepare file system connect
* @param string "create", "update", "delete"
* @param string path relative to wp-content
* @return Loco_mvc_HiddenFields
*/
protected function prepareFsConnect( $type, $relpath ){
$fields = new Loco_mvc_HiddenFields( array(
'auth' => $type,
'path' => $relpath,
'loco-nonce' => wp_create_nonce('fsConnect'),
'_fs_nonce' => wp_create_nonce('filesystem-credentials'), // <- WP 4.7.5 added security fix
) ) ;
$this->set('fsFields', $fields );
// may have fs credentials saved in session
try {
if( Loco_data_Settings::get()->fs_persist ){
$session = Loco_data_Session::get();
if( isset($session['loco-fs']) ){
$fields['connection_type'] = $session['loco-fs']['connection_type'];
}
}
}
catch( Exception $e ){
Loco_error_AdminNotices::debug( $e->getMessage() );
}
// Run pre-checks that may determine file should not be written
if( $relpath ){
$file = new Loco_fs_File( $relpath );
$file->normalize( loco_constant('WP_CONTENT_DIR') );
// total file system block makes connection type irrelevant
try {
$api = new Loco_api_WordPressFileSystem;
$api->preAuthorize($file);
}
catch( Loco_error_WriteException $e ){
$this->set('fsLocked', $e->getMessage() );
}
}
return $fields;
}
}

View file

@ -0,0 +1,145 @@
<?php
/**
* Bundle configuration page
*/
class Loco_admin_bundle_ConfController extends Loco_admin_bundle_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$this->enqueueStyle('config');
$this->enqueueScript('config');
$bundle = $this->getBundle();
// translators: where %s is a plugin or theme
$this->set( 'title', sprintf( __('Configure %s','loco-translate'),$bundle->getName() ) );
$post = Loco_mvc_PostParams::get();
// always set a nonce for current bundle
$nonce = $this->setNonce( $this->get('_route').'-'.$this->get('bundle') );
$this->set('nonce', $nonce );
try {
// Save configuration if posted
if( $post->has('conf') ){
if( ! $post->name ){
$post->name = $bundle->getName();
}
$this->checkNonce( $nonce->action );
$model = new Loco_config_FormModel;
$model->loadForm( $post );
// configure bundle from model in full
$bundle->clear();
$reader = new Loco_config_BundleReader( $bundle );
$reader->loadModel( $model );
$this->saveBundle();
}
// Delete configuration if posted
else if( $post->has('unconf') ){
$this->resetBundle();
}
}
catch( Exception $e ){
Loco_error_AdminNotices::warn( $e->getMessage() );
}
}
/**
* {@inheritdoc}
*/
public function getHelpTabs(){
return array (
__('Advanced tab','loco-translate') => $this->viewSnippet('tab-bundle-conf'),
);
}
/**
* {@inheritdoc}
*/
public function render() {
$parent = null;
$bundle = $this->getBundle();
$default = $bundle->getDefaultProject();
$base = $bundle->getDirectoryPath();
// parent themes are inherited into bundle, we don't want them in the child theme config
if( $bundle->isTheme() && ( $parent = $bundle->getParent() ) ){
$this->set( 'parent', new Loco_mvc_ViewParams( array(
'name' => $parent->getName(),
'href' => Loco_mvc_AdminRouter::generate('theme-conf', array( 'bundle' => $parent->getSlug() ) + $_GET ),
) ) );
}
// render postdata straight back to form if sent
$data = Loco_mvc_PostParams::get();
// else build initial data from current bundle state
if( ! $data->has('conf') ){
// create single default set for totally unconfigured bundles
if( 0 === count($bundle) ){
$bundle->createDefault('');
}
$writer = new Loco_config_BundleWriter($bundle);
$data = $writer->toForm();
// removed parent bundle from config form, as they are inherited
/* @var Loco_package_Project $project */
foreach( $bundle as $i => $project ){
if( $parent && $parent->hasProject($project) ){
// warn if child theme uses parent theme's text domain (but allowing to render so we don't get an empty form.
if( $project === $default ){
Loco_error_AdminNotices::warn( __("Child theme declares the same Text Domain as the parent theme",'loco-translate') );
}
// else safe to remove parent theme configuration as it should be held in its own bundle
else {
$data['conf'][$i]['removed'] = true;
}
}
}
}
// build config blocks for form
$i = 0;
$conf = array();
foreach( $data['conf'] as $raw ){
if( empty($raw['removed']) ){
$slug = $raw['slug'];
$domain = $raw['domain'] or $domain = 'untitled';
$raw['prefix'] = sprintf('conf[%u]', $i++ );
$raw['short'] = ! $slug || ( $slug === $domain ) ? $domain : $domain.'→'.$slug;
$conf[] = new Loco_mvc_ViewParams( $raw );
}
}
// bundle level configs
$name = $bundle->getName();
$excl = $data['exclude'];
// access to type of configuration that's currently saved
$this->set('saved', $bundle->isConfigured() );
// link to author if there are config problems
$info = $bundle->getHeaderInfo();
$this->set('author', $info->getAuthorLink() );
// link for downloading current configuration XML file
$args = array (
'path' => 'loco.xml',
'action' => 'loco_download',
'bundle' => $bundle->getHandle(),
'type' => $bundle->getType()
);
$this->set( 'xmlUrl', Loco_mvc_AjaxRouter::generate( 'DownloadConf', $args ) );
$this->set( 'manUrl', apply_filters('loco_external','https://localise.biz/wordpress/plugin/manual/bundle-config') );
$this->prepareNavigation()->add( __('Advanced configuration','loco-translate') );
return $this->view('admin/bundle/conf', compact('conf','base','name','excl') );
}
}

View file

@ -0,0 +1,52 @@
<?php
/**
* Bundle debugger.
* Shows bundle diagnostics and highlights problems
*/
class Loco_admin_bundle_DebugController extends Loco_admin_bundle_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$bundle = $this->getBundle();
$this->set('title', 'Debug: '.$bundle );
}
/**
* {@inheritdoc}
*/
public function render(){
$this->prepareNavigation()->add( __('Bundle diagnostics','loco-translate') );
$bundle = $this->getBundle();
$debugger = new Loco_package_Debugger($bundle);
$this->set('notices', $notices = new Loco_mvc_ViewParams );
/* @var $notice Loco_error_Exception */
foreach( $debugger as $notice ){
$notices[] = new Loco_mvc_ViewParams( array(
'style' => 'notice inline notice-'.$notice->getType(),
'title' => $notice->getTitle(),
'body' => $notice->getMessage(),
) );
}
$meta = $bundle->getHeaderInfo();
$this->set('meta', new Loco_mvc_ViewParams( array(
'vendor' => $meta->getVendorHost(),
'author' => $meta->getAuthorCredit(),
) ) );
if( count($bundle) ){
$writer = new Loco_config_BundleWriter( $bundle );
$this->set( 'xml', $writer->toXml() );
}
return $this->view('admin/bundle/debug');
}
}

View file

@ -0,0 +1,162 @@
<?php
/**
* Pseudo-bundle view, lists all files available in a single locale
*/
class Loco_admin_bundle_LocaleController extends Loco_mvc_AdminController {
/**
* @var Loco_Locale
*/
private $locale;
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$tag = $this->get('locale');
$locale = Loco_Locale::parse($tag);
if( $locale->isValid() ){
$api = new Loco_api_WordPressTranslations;
$this->set('title', $locale->ensureName($api) );
$this->locale = $locale;
$this->enqueueStyle('locale')->enqueueStyle('fileinfo');
}
}
/**
* {@inheritdoc}
*/
public function getHelpTabs(){
return array (
__('Overview','default') => $this->viewSnippet('tab-locale-view'),
);
}
/**
* {@inheritdoc}
*/
public function render(){
// locale already parsed during init (for page title)
$locale = $this->locale;
if( ! $locale || ! $locale->isValid() ){
throw new Loco_error_Exception('Invalid locale argument');
}
// language may not be "installed" but we still want to inspect available files
$api = new Loco_api_WordPressTranslations;
$installed = $api->isInstalled($locale);
$tag = (string) $locale;
$package = new Loco_package_Locale( $locale );
// Get PO files for this locale
$files = $package->findLocaleFiles();
$translations = array();
$modified = 0;
$npofiles = 0;
$nfiles = 0;
// source locale means we want to see POT instead of translations
if( 'en_US' === $tag ){
$files = $package->findTemplateFiles()->augment($files);
}
/* @var Loco_fs_File */
foreach( $files as $file ){
$nfiles++;
if( 'pot' !== $file->extension() ){
$npofiles++;
}
$modified = max( $modified, $file->modified() );
$project = $package->getProject($file);
// do similarly to Loco_admin_bundle_ViewController::createFileParams
$meta = Loco_gettext_Metadata::load($file);
$dir = new Loco_fs_LocaleDirectory( $file->dirname() );
// arguments for deep link into project
$slug = $project->getSlug();
$domain = $project->getDomain()->getName();
$bundle = $project->getBundle();
$type = strtolower( $bundle->getType() );
$args = array(
// 'locale' => $tag,
'bundle' => $bundle->getHandle(),
'domain' => $project->getId(),
'path' => $meta->getPath(false),
);
// append data required for PO table row, except use bundle data instead of locale data
$translations[$type][] = new Loco_mvc_ViewParams( array (
// bundle info
'title' => $project->getName(),
'domain' => $domain,
'short' => ! $slug || $project->isDomainDefault() ? $domain : $domain.'→'.$slug,
// file info
'meta' => $meta,
'name' => $file->basename(),
'time' => $file->modified(),
'type' => strtoupper( $file->extension() ),
'todo' => $meta->countIncomplete(),
'total' => $meta->getTotal(),
// author / system / custom / other
'store' => $dir->getTypeLabel( $dir->getTypeId() ),
// links
'view' => Loco_mvc_AdminRouter::generate( $type.'-file-view', $args ),
'info' => Loco_mvc_AdminRouter::generate( $type.'-file-info', $args ),
'edit' => Loco_mvc_AdminRouter::generate( $type.'-file-edit', $args ),
'move' => Loco_mvc_AdminRouter::generate( $type.'-file-move', $args ),
'delete' => Loco_mvc_AdminRouter::generate( $type.'-file-delete', $args ),
'copy' => Loco_mvc_AdminRouter::generate( $type.'-msginit', $args ),
) );
}
$title = __( 'Installed languages', 'loco-translate' );
$breadcrumb = new Loco_admin_Navigation;
$breadcrumb->add( $title, Loco_mvc_AdminRouter::generate('lang') );
//$breadcrumb->add( $locale->getName() );
$breadcrumb->add( $tag );
// It's unlikely that an "installed" language would have no files, but could happen if only MO on disk
if( 0 === $nfiles ){
return $this->view('admin/errors/no-locale', compact('breadcrumb','locale') );
}
// files may be available for language even if not installed (i.e. no core files on disk)
if( ! $installed || ! isset($translations['core']) && 'en_US' !== $tag ){
Loco_error_AdminNotices::warn( __('No core translation files are installed for this language','loco-translate') )
->addLink('https://codex.wordpress.org/Installing_WordPress_in_Your_Language', __('Documentation','loco-translate') );
}
// Translated type labels and "See all <type>" links
$types = array(
'core' => new Loco_mvc_ViewParams( array(
'name' => __('WordPress Core','loco-translate'),
'text' => __('See all core translations','loco-translate'),
'href' => Loco_mvc_AdminRouter::generate('core')
) ),
'theme' => new Loco_mvc_ViewParams( array(
'name' => __('Themes','loco-translate'),
'text' => __('See all themes','loco-translate'),
'href' => Loco_mvc_AdminRouter::generate('theme')
) ),
'plugin' => new Loco_mvc_ViewParams( array(
'name' => __('Plugins','loco-translate'),
'text' => __('See all plugins','loco-translate'),
'href' => Loco_mvc_AdminRouter::generate('plugin')
) ),
);
$this->set( 'locale', new Loco_mvc_ViewParams( array(
'code' => $tag,
'name' => $locale->getName(),
'attr' => 'class="'.$locale->getIcon().'" lang="'.$locale->lang.'"',
) ) );
return $this->view( 'admin/bundle/locale', compact('breadcrumb','translations','types','npofiles','modified') );
}
}

View file

@ -0,0 +1,193 @@
<?php
/**
*
*/
class Loco_admin_bundle_SetupController extends Loco_admin_bundle_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$bundle = $this->getBundle();
// translators: where %s is a plugin or theme
$this->set( 'title', sprintf( __('Set up %s','loco-translate'),$bundle->getName() ) );
}
/**
* {@inheritdoc}
*/
public function getHelpTabs(){
return array (
__('Setup tab','loco-translate') => $this->viewSnippet('tab-bundle-setup'),
);
}
/**
* {@inheritdoc}
*/
public function render(){
$this->prepareNavigation()->add( __('Bundle setup','loco-translate') );
$bundle = $this->getBundle();
$action = 'setup:'.$bundle->getId();
// execute auto-configure if posted
$post = Loco_mvc_PostParams::get();
if( $post->has('auto-setup') && $this->checkNonce( 'auto-'.$action) ){
if( 0 === count($bundle) ){
$bundle->createDefault();
}
foreach( $bundle as $project ){
if( ! $project->getPot() && ( $file = $project->guessPot() ) ){
$project->setPot( $file );
}
}
// forcefully add every additional project into bundle
foreach( $bundle->invert() as $project ){
if( ! $project->getPot() && ( $file = $project->guessPot() ) ){
$project->setPot( $file );
}
$bundle[] = $project;
}
$this->saveBundle();
$bundle = $this->getBundle();
$this->set('auto', null );
}
// execute XML-based config if posted
else if( $post->has('xml-setup') && $this->checkNonce( 'xml-'.$action) ){
$bundle->clear();
$model = new Loco_config_XMLModel;
$model->loadXml( trim( $post['xml-content'] ) );
$reader = new Loco_config_BundleReader($bundle);
$reader->loadModel( $model );
$this->saveBundle();
$bundle = $this->getBundle();
$this->set('xml', null );
}
// execute JSON-based config if posted
else if( $post->has('json-setup') && $this->checkNonce( 'json-'.$action) ){
$bundle->clear();
$model = new Loco_config_ArrayModel;
$model->loadJson( trim( $post['json-content'] ) );
$reader = new Loco_config_BundleReader($bundle);
$reader->loadModel( $model );
$this->saveBundle();
$bundle = $this->getBundle();
$this->set('json', null );
}
// execute reset if posted
else if( $post->has('reset-setup') && $this->checkNonce( 'reset-'.$action) ){
$this->resetBundle();
$bundle = $this->getBundle();
}
// bundle author links
$info = $bundle->getHeaderInfo();
$this->set( 'credit', $info->getAuthorCredit() );
// render according to current configuration method (save type)
$configured = $this->get('force') or $configured = $bundle->isConfigured();
$notices = new ArrayIterator;
$this->set('notices', $notices );
// collect configuration warnings
foreach( $bundle as $project ){
$potfile = $project->getPot();
if( ! $potfile ){
$notices[] = sprintf('No translation template for the "%s" text domain', $project->getSlug() );
}
}
// if extra files found consider incomplete
if( $bundle->isTheme() || ( $bundle->isPlugin() && ! $bundle->isSingleFile() ) ){
$unknown = Loco_package_Inverter::export($bundle);
$n = 0;
foreach( $unknown as $ext => $files ){
$n += count($files);
}
if( $n ){
$notices[] = sprintf( _n("One file can't be matched to a known set of strings","%s files can't be matched to a known set of strings",$n,'loco-translate'), number_format($n) );
}
}
// display setup options if at least one option specified
$doconf = false;
// enable form to invoke auto-configuration
if( $this->get('auto') ){
$fields = new Loco_mvc_HiddenFields();
$fields->setNonce( 'auto-'.$action );
$this->set('autoFields', $fields );
$doconf = true;
}
// enable form to paste XML config
if( $this->get('xml') ){
$fields = new Loco_mvc_HiddenFields();
$fields->setNonce( 'xml-'.$action );
$this->set('xmlFields', $fields );
$doconf = true;
}
// enable form to paste JSON config (via remote lookup)
if( $this->get('json') ){
$fields = new Loco_mvc_HiddenFields( array(
'json-content' => '',
'version' => $info->Version,
) );
$fields->setNonce( 'json-'.$action );
$this->set('jsonFields', $fields );
// other information for looking up bundle via api
$this->set('vendorSlug', $bundle->getSlug() );
// remote config is done via JavaScript
$this->enqueueScript('setup');
$apiBase = apply_filters( 'loco_api_url', 'https://localise.biz/api' );
$this->set('js', new Loco_mvc_ViewParams( array(
'apiUrl' => $apiBase.'/wp/'.strtolower( $bundle->getType() ),
) ) );
$doconf = true;
}
// display configurator if configurating
if( $doconf ){
return $this->view( 'admin/bundle/setup/conf' );
}
// else set configurator links back to self with required option
// ...
if( ! $configured || ! count($bundle) ){
return $this->view( 'admin/bundle/setup/none' );
}
if( 'db' === $configured ){
// form for resetting config
$fields = new Loco_mvc_HiddenFields();
$fields->setNonce( 'reset-'.$action );
$this->set( 'reset', $fields );
return $this->view('admin/bundle/setup/saved');
}
if( 'internal' === $configured ){
return $this->view('admin/bundle/setup/core');
}
if( 'file' === $configured ){
return $this->view('admin/bundle/setup/author');
}
if( count($notices) ){
return $this->view('admin/bundle/setup/partial');
}
return $this->view('admin/bundle/setup/meta');
}
}

View file

@ -0,0 +1,324 @@
<?php
/**
* Bundle overview.
* First tier bundle view showing resources across all projects
*/
class Loco_admin_bundle_ViewController extends Loco_admin_bundle_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$bundle = $this->getBundle();
$this->set('title', $bundle->getName() );
$this->enqueueStyle('bundle');
}
/**
* {@inheritdoc}
*/
public function getHelpTabs(){
return array (
__('Overview','default') => $this->viewSnippet('tab-bundle-view'),
);
}
/**
* Generate a link for a specific file resource within a project
* @return string
*/
private function getResourceLink( $page, Loco_package_Project $project, Loco_gettext_Metadata $meta, array $args = array() ){
$args['path'] = $meta->getPath(false);
return $this->getProjectLink( $page, $project, $args );
}
/**
* Generate a link for a project, but without being for a specific file
* @return string
*/
private function getProjectLink( $page, Loco_package_Project $project, array $args = array() ){
$args['bundle'] = $this->get('bundle');
$args['domain'] = $project->getId();
$route = strtolower( $this->get('type') ).'-'.$page;
return Loco_mvc_AdminRouter::generate( $route, $args );
}
/**
* Initialize view parameters for a project
* @param Loco_package_Project
* @return Loco_mvc_ViewParams
*/
private function createProjectParams( Loco_package_Project $project ){
$name = $project->getName();
$domain = $project->getDomain()->getName();
$slug = $project->getSlug();
$p = new Loco_mvc_ViewParams( array (
'id' => $project->getId(),
'name' => $name,
'slug' => $slug,
'domain' => $domain,
'short' => ! $slug || $project->isDomainDefault() ? $domain : $domain.'→'.$slug,
) );
// POT template file
$file = $project->getPot();
if( $file && $file->exists() ){
$meta = Loco_gettext_Metadata::load($file);
$p['pot'] = new Loco_mvc_ViewParams( array(
// POT info
'name' => $file->basename(),
'time' => $file->modified(),
// POT links
'info' => $this->getResourceLink('file-info', $project, $meta ),
'edit' => $this->getResourceLink('file-edit', $project, $meta ),
) );
}
// PO/MO files
$po = $project->findLocaleFiles('po');
$mo = $project->findLocaleFiles('mo');
$p['po'] = $this->createProjectPairs( $project, $po, $mo );
// also pull invalid files so everything is available to the UI
$mo = $project->findNotLocaleFiles('mo');
$po = $project->findNotLocaleFiles('po')->augment( $project->findNotLocaleFiles('pot') );
$p['_po'] = $this->createProjectPairs( $project, $po, $mo );
// always offer msginit even if we find out later we can't extract any strings
$p['nav'][] = new Loco_mvc_ViewParams( array(
'href' => $this->getProjectLink('msginit', $project ),
'name' => __('New language','loco-translate'),
'icon' => 'add',
) );
$pot = $project->getPot();
// prevent editing of POT when config prohibits
if( $project->isPotLocked() || 1 < Loco_data_Settings::get()->pot_protect ) {
if( $pot && $pot->exists() ){
$meta = Loco_gettext_Metadata::load($pot);
$p['nav'][] = new Loco_mvc_ViewParams( array(
'href' => $this->getResourceLink('file-view', $project, $meta ),
'name' => __('View template','loco-translate'),
'icon' => 'file',
) );
}
}
// offer template editing if permitted
else if( $pot && $pot->exists() ){
$p['pot'] = $pot;
$meta = Loco_gettext_Metadata::load($pot);
$p['nav'][] = new Loco_mvc_ViewParams( array(
'href' => $this->getResourceLink('file-edit', $project, $meta ),
'name' => __('Edit template','loco-translate'),
'icon' => 'pencil',
) );
}
// else offer creation of new Template
else {
$p['nav'][] = new Loco_mvc_ViewParams( array(
'href' => $this->getProjectLink('xgettext', $project ),
'name' => __('Create template','loco-translate'),
'icon' => 'add',
) );
}
return $p;
}
/**
* Collect PO/MO pairings, ignoring any PO that is in use as a template
*/
private function createPairs( Loco_fs_FileList $po, Loco_fs_FileList $mo, Loco_fs_File $pot = null ){
$pairs = array();
/* @var $pofile Loco_fs_LocaleFile */
foreach( $po as $pofile ){
if( $pot && $pofile->equal($pot) ){
continue;
}
$pair = array( $pofile, null );
$mofile = $pofile->cloneExtension('mo');
if( $mofile->exists() ){
$pair[1] = $mofile;
}
$pairs[] = $pair;
}
/* @var $mofile Loco_fs_LocaleFile */
foreach( $mo as $mofile ){
$pofile = $mofile->cloneExtension('po');
if( $pot && $pofile->equal($pot) ){
continue;
}
if( ! $pofile->exists() ){
$pairs[] = array( null, $mofile );
}
}
return $pairs;
}
/**
* Initialize view parameters for each row representing a localized resource pair
* @return array collection of entries corresponding to available PO/MO pair.
*/
private function createProjectPairs( Loco_package_Project $project, Loco_fs_LocaleFileList $po, Loco_fs_LocaleFileList $mo ){
// populate official locale names for all found, or default to our own
if( $locales = $po->getLocales() + $mo->getLocales() ){
$api = new Loco_api_WordPressTranslations;
/* @var $locale Loco_Locale */
foreach( $locales as $tag => $locale ){
$locale->ensureName($api);
}
}
// collate as unique [PO,MO] pairs ensuring canonical template excluded
$pairs = $this->createPairs( $po, $mo, $project->getPot() );
$rows = array();
foreach( $pairs as $pair ){
// favour PO file if it exists
list( $pofile, $mofile ) = $pair;
$file = $pofile or $file = $mofile;
// establish locale, or assume invalid
$locale = null;
/* @var Loco_fs_LocaleFile $file */
if( 'pot' !== $file->extension() ){
$tag = $file->getSuffix();
if( isset($locales[$tag]) ){
$locale = $locales[$tag];
}
}
$rows[] = $this->createFileParams( $project, $file, $locale );
}
return $rows;
}
/**
* @param Loco_package_Project
* @param Loco_fs_File
* @param Loco_Locale
* @return Loco_mvc_ViewParams
*/
private function createFileParams( Loco_package_Project $project, Loco_fs_File $file, Loco_Locale $locale = null ){
// Pull Gettext meta data from cache if possible
$meta = Loco_gettext_Metadata::load($file);
$dir = new Loco_fs_LocaleDirectory( $file->dirname() );
// routing arguments
$args = array (
'path' => $meta->getPath(false),
);
// Return data required for PO table row
return new Loco_mvc_ViewParams( array (
// locale info
'lcode' => $locale ? (string) $locale : '',
'lname' => $locale ? $locale->getName() : '',
'lattr' => $locale ? 'class="'.$locale->getIcon().'" lang="'.$locale->lang.'"' : '',
// file info
'meta' => $meta,
'name' => $file->basename(),
'time' => $file->modified(),
'type' => strtoupper( $file->extension() ),
'todo' => $meta->countIncomplete(),
'total' => $meta->getTotal(),
// author / system / custom / other
'store' => $dir->getTypeLabel( $dir->getTypeId() ),
// links
'view' => $this->getProjectLink('file-view', $project, $args ),
'info' => $this->getProjectLink('file-info', $project, $args ),
'edit' => $this->getProjectLink('file-edit', $project, $args ),
'move' => $this->getProjectLink('file-move', $project, $args ),
'delete' => $this->getProjectLink('file-delete', $project, $args ),
'copy' => $this->getProjectLink('msginit', $project, $args ),
) );
}
/**
* Prepare view parameters for all projects in a bundle
* @param Loco_package_Bundle
* @return array<Loco_mvc_ViewParams>
*/
private function createBundleListing( Loco_package_Bundle $bundle ){
$projects = array();
/* @var $project Loco_package_Project */
foreach( $bundle as $project ){
$projects[] = $this->createProjectParams($project);
}
return $projects;
}
/**
* {@inheritdoc}
*/
public function render(){
$this->prepareNavigation();
$bundle = $this->getBundle();
$this->set('name', $bundle->getName() );
// bundle may not be fully configured
$configured = $bundle->isConfigured();
// Hello Dolly is an exception. don't show unless configured deliberately
if( 'Hello Dolly' === $bundle->getName() && 'hello.php' === basename($bundle->getHandle()) ){
if( ! $configured || 'meta' === $configured ){
$this->set( 'redirect', Loco_mvc_AdminRouter::generate('core-view') );
return $this->view('admin/bundle/alias');
}
}
// Collect all configured projects
$projects = $this->createBundleListing( $bundle );
$unknown = array();
// sniff additional unknown files if bundle is a theme or directory-based plugin that's been auto-detected
if( 'file' === $configured || 'internal' === $configured ){
// presumed complete
}
else if( $bundle->isTheme() || ( $bundle->isPlugin() && ! $bundle->isSingleFile() ) ){
// TODO This needs abstracting into the Loco_package_Inverter class
$prefixes = array();
$po = new Loco_fs_LocaleFileList;
$mo = new Loco_fs_LocaleFileList;
foreach( Loco_package_Inverter::export($bundle) as $ext => $files ){
$list = 'mo' === $ext ? $mo : $po;
foreach( $files as $file ){
$file = new Loco_fs_LocaleFile($file);
$list->addLocalized( $file );
// Only look in system locations if locale is valid and domain/prefix available
$locale = $file->getLocale();
if( $locale->isValid() && ( $domain = $file->getPrefix() ) ){
$prefixes[$domain] = true;
}
}
}
// pick up given files in system locations only
foreach( $prefixes as $domain => $_bool ){
$dummy = new Loco_package_Project( $bundle, new Loco_package_TextDomain($domain), '' );
$bundle->addProject( $dummy ); // <- required to configure locations
$dummy->excludeTargetPath( $bundle->getDirectoryPath() );
$po->augment( $dummy->findLocaleFiles('po') );
$mo->augment( $dummy->findLocaleFiles('mo') );
}
// a fake project is required to disable functions that require a configured project
$dummy = new Loco_package_Project( $bundle, new Loco_package_TextDomain(''), '' );
$unknown = $this->createProjectPairs( $dummy, $po, $mo );
}
$this->set('projects', $projects );
$this->set('unknown', $unknown );
return $this->view( 'admin/bundle/view' );
}
}

View file

@ -0,0 +1,66 @@
<?php
/**
* API keys/settings screen
*/
class Loco_admin_config_ApisController extends Loco_admin_config_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$this->set( 'title', __('API keys','loco-translate') );
// collect support API keys
$apis = array();
foreach( Loco_api_Providers::builtin() as $api ){
$apis[ $api['id'] ] = new Loco_mvc_ViewParams($api);
}
$this->set('apis',$apis);
// handle save action
$nonce = $this->setNonce('save-apis');
try {
if( $this->checkNonce($nonce->action) ){
$post = Loco_mvc_PostParams::get();
if( $post->has('api') ){
// Save only options in post. Avoids overwrite of missing site options
$data = array();
$filter = array();
foreach( $apis as $id => $api ){
$fields = $post->api[$id];
if( is_array($fields) ){
foreach( $fields as $prop => $value ){
$apis[$id][$prop] = $value;
$prop = $id.'_api_'.$prop;
$data[$prop] = $value;
$filter[] = $prop;
}
}
}
if( $filter ){
Loco_data_Settings::get()->populate($data,$filter)->persistIfDirty();
Loco_error_AdminNotices::success( __('Settings saved','loco-translate') );
}
}
}
}
catch( Loco_error_Exception $e ){
Loco_error_AdminNotices::add($e);
}
}
/**
* {@inheritdoc}
*/
public function render(){
$title = __('Plugin settings','loco-translate');
$breadcrumb = new Loco_admin_Navigation;
$breadcrumb->add( $title );
return $this->view('admin/config/apis', compact('breadcrumb') );
}
}

View file

@ -0,0 +1,47 @@
<?php
/**
* Base controller for global plugin configuration screens
*/
abstract class Loco_admin_config_BaseController extends Loco_mvc_AdminController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
// navigate between config view siblings, but only if privileged user
if( current_user_can('manage_options') ){
$tabs = new Loco_admin_Navigation;
$this->set( 'tabs', $tabs );
$actions = array (
'' => __('Site options','loco-translate'),
'user' => __('User options','loco-translate'),
'apis' => __('API keys','loco-translate'),
'version' => __('Version','loco-translate'),
);
if( loco_debugging() ){
$actions['debug'] = __('Debug','loco-translate');
}
$suffix = (string) $this->get('action');
foreach( $actions as $action => $name ){
$href = Loco_mvc_AdminRouter::generate( 'config-'.$action, $_GET );
$tabs->add( $name, $href, $action === $suffix );
}
}
}
/**
* {@inheritdoc}
*/
public function getHelpTabs(){
return array (
__('Overview','default') => $this->viewSnippet('tab-config'),
__('API keys','loco-translate') => $this->viewSnippet('tab-config-apis'),
);
}
}

View file

@ -0,0 +1,168 @@
<?php
/**
* Plugin config check (system diagnostics)
*/
class Loco_admin_config_DebugController extends Loco_admin_config_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$this->set( 'title', __('Debug','loco-translate') );
}
/**
* @param string
* @return int
*/
private function memory_size( $raw ){
$bytes = wp_convert_hr_to_bytes($raw);
return Loco_mvc_FileParams::renderBytes($bytes);
}
/**
* @param string
* @return string
*/
private function rel_path( $path ){
if( is_string($path) && $path && '/' === $path[0] ){
$file = new Loco_fs_File( $path );
$path = $file->getRelativePath(ABSPATH);
}
else if( ! $path ){
$path = '(none)';
}
return $path;
}
/**
* {@inheritdoc}
*/
public function render(){
$title = __('System diagnostics','loco-translate');
$breadcrumb = new Loco_admin_Navigation;
$breadcrumb->add( $title );
// extensions that are normally enabled in PHP by default
loco_check_extension('json');
loco_check_extension('ctype');
// product versions:
$versions = new Loco_mvc_ViewParams( array (
'Loco Translate' => loco_plugin_version(),
'WordPress' => $GLOBALS['wp_version'],
'PHP' => phpversion().' ('.PHP_SAPI.')',
'Server' => isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : ( function_exists('apache_get_version') ? apache_get_version() : '' ),
) );
// we want to know about modules in case there are security mods installed known to break functionality
if( function_exists('apache_get_modules') && ( $mods = preg_grep('/^mod_/',apache_get_modules() ) ) ){
$versions['Server'] .= ' + '.implode(', ',$mods);
}
// byte code cache (currently only checking for Zend OPcache)
if( function_exists('opcache_get_configuration') && ini_get('opcache.enable') ){
$info = opcache_get_configuration();
$vers = $info['version'];
$versions[ $vers['opcache_product_name'] ] = ' '.$vers['version'];
}
// utf8 / encoding:
$encoding = new Loco_mvc_ViewParams( array (
'OK' => "\xCE\x9F\xCE\x9A",
'tick' => "\xE2\x9C\x93",
'json' => json_decode('"\\u039f\\u039a \\u2713"'),
'mbstring' => loco_check_extension('mbstring') ? "\xCE\x9F\xCE\x9A \xE2\x9C\x93" : 'No',
) );
// Sanity check mbstring.func_overload
if( 2 !== strlen("\xC2\xA3") ){
$encoding->mbstring = 'Error, disable mbstring.func_overload';
}
// PHP / env memory settings:
$memory = new Loco_mvc_PostParams( array(
'WP_MEMORY_LIMIT' => $this->memory_size( loco_constant('WP_MEMORY_LIMIT') ),
'WP_MAX_MEMORY_LIMIT' => $this->memory_size( loco_constant('WP_MAX_MEMORY_LIMIT') ),
'PHP memory_limit' => $this->memory_size( ini_get('memory_limit') ),
'PHP post_max_size' => $this->memory_size( ini_get('post_max_size') ),
//'PHP upload_max_filesize' => $this->memory_size( ini_get('upload_max_filesize') ),
'PHP max_execution_time' => (string) ini_get('max_execution_time'),
) );
// Check if raising memory limit works (wp>=4.6)
if( function_exists('wp_is_ini_value_changeable') && wp_is_ini_value_changeable('memory_limit') ){
$memory['PHP memory_limit'] .= ' (changeable)';
}
// Ajaxing:
$this->enqueueScript('debug');
$this->set( 'js', new Loco_mvc_ViewParams( array (
'nonces' => array( 'ping' => wp_create_nonce('ping'), 'apis' => wp_create_nonce('apis') ),
) ) );
// Third party API integrations:
$apis = array();
$jsapis = array();
foreach( Loco_api_Providers::export() as $api ){
$apis[] = new Loco_mvc_ViewParams($api);
$jsapis[] = $api;
}
if( $apis ){
$this->set('apis',$apis);
$jsconf = $this->get('js');
$jsconf['apis'] = $jsapis;
}
// File system access
$dir = new Loco_fs_Directory( loco_constant('LOCO_LANG_DIR') ) ;
$ctx = new Loco_fs_FileWriter( $dir );
$fsp = Loco_data_Settings::get()->fs_protect;
$fs = new Loco_mvc_PostParams( array(
'langdir' => $this->rel_path( $dir->getPath() ),
'writable' => $ctx->writable(),
'disabled' => $ctx->disabled(),
'fs_protect' => 1 === $fsp ? 'Warn' : ( $fsp ? 'Block' : 'Off' ),
) );
// Debug and error log settings
$debug = new Loco_mvc_ViewParams( array(
'WP_DEBUG' => loco_constant('WP_DEBUG') ? 'On' : 'Off',
'WP_DEBUG_LOG' => loco_constant('WP_DEBUG_LOG') ? 'On' : 'Off',
'WP_DEBUG_DISPLAY' => loco_constant('WP_DEBUG_DISPLAY') ? 'On' : 'Off',
'PHP display_errors' => ini_get('display_errors') ? 'On' : 'Off',
'PHP log_errors' => ini_get('log_errors') ? 'On' : 'Off',
'PHP error_log' => $this->rel_path( ini_get('error_log') ),
) );
/* Output buffering settings
$this->set('ob', new Loco_mvc_ViewParams( array(
'output_handler' => ini_get('output_handler'),
'zlib.output_compression' => ini_get('zlib.output_compression'),
'zlib.output_compression_level' => ini_get('zlib.output_compression_level'),
'zlib.output_handler' => ini_get('zlib.output_handler'),
) ) );*/
// alert to known system setting problems:
if( version_compare(PHP_VERSION,'7.4','<') ){
if( get_magic_quotes_gpc() ){
Loco_error_AdminNotices::add( new Loco_error_Debug('You have "magic_quotes_gpc" enabled. We recommend you disable this in PHP') );
}
if( get_magic_quotes_runtime() ){
Loco_error_AdminNotices::add( new Loco_error_Debug('You have "magic_quotes_runtime" enabled. We recommend you disable this in PHP') );
}
}
// alert to third party plugins known to interfere with functioning of this plugin
if( class_exists('\\LocoAutoTranslateAddon\\LocoAutoTranslate',false) ){
Loco_error_AdminNotices::add( new Loco_error_Warning('Unoffical add-ons for Loco Translate may affect functionality. We cannot provide support when third party products are installed.') );
}
return $this->view('admin/config/debug', compact('breadcrumb','versions','encoding','memory','fs','debug') );
}
}

View file

@ -0,0 +1,49 @@
<?php
/**
* User-level plugin preferences
*/
class Loco_admin_config_PrefsController extends Loco_admin_config_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$this->set( 'title', __('User options','loco-translate') );
// user preference options
$opts = Loco_data_Preferences::get();
$this->set( 'opts', $opts );
// handle save action
$nonce = $this->setNonce('save-prefs');
try {
if( $this->checkNonce($nonce->action) ){
$post = Loco_mvc_PostParams::get();
if( $post->has('opts') ){
$opts->populate( $post->opts )->persist();
Loco_error_AdminNotices::success( __('Settings saved','loco-translate') );
}
}
}
catch( Loco_error_Exception $e ){
Loco_error_AdminNotices::add($e);
}
}
/**
* {@inheritdoc}
*/
public function render(){
$title = __('Plugin settings','loco-translate');
$breadcrumb = new Loco_admin_Navigation;
$breadcrumb->add( $title );
return $this->view('admin/config/prefs', compact('breadcrumb') );
}
}

View file

@ -0,0 +1,86 @@
<?php
/**
* Site-wide Loco options (plugin settings)
*/
class Loco_admin_config_SettingsController extends Loco_admin_config_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
// set current plugin options and defaults for placeholders
$opts = Loco_data_Settings::get();
$this->set( 'opts', $opts );
$this->set( 'dflt', Loco_data_Settings::create() );
// roles and capabilities
$perms = new Loco_data_Permissions;
// handle save action
$nonce = $this->setNonce('save-config');
try {
if( $this->checkNonce($nonce->action) ){
$post = Loco_mvc_PostParams::get();
if( $post->has('opts') ){
$opts->populate( $post->opts )->persist();
$perms->populate( $post->has('caps') ? $post->caps : array() );
// done update
Loco_error_AdminNotices::success( __('Settings saved','loco-translate') );
// remove saved params from session if persistent options unset
if( ! $opts['fs_persist'] ){
$session = Loco_data_Session::get();
if( isset($session['loco-fs']) ){
unset( $session['loco-fs'] );
$session->persist();
}
}
}
}
}
catch( Loco_error_Exception $e ){
Loco_error_AdminNotices::add($e);
}
$this->set('caps', $caps = new Loco_mvc_ViewParams );
// there is no distinct role for network admin, so we'll fake it for UI
if( is_multisite() ){
$caps[''] = new Loco_mvc_ViewParams( array(
'label' => __('Super Admin','default'),
'name' => 'dummy-admin-cap',
'attrs' => 'checked disabled'
) );
}
foreach( $perms->getRoles() as $id => $role ){
$caps[$id] = new Loco_mvc_ViewParams( array(
'value' => '1',
'label' => $perms->getRoleName($id),
'name' => 'caps['.$id.'][loco_admin]',
'attrs' => $perms->isProtectedRole($role) ? 'checked disabled ' : ( $role->has_cap('loco_admin') ? 'checked ' : '' ),
) );
}
// allow/deny warning levels
$this->set('verbose', new Loco_mvc_ViewParams( array(
0 => __('Allow','loco-translate'),
1 => __('Allow (with warning)','loco-translate'),
2 => __('Disallow','loco-translate'),
) ) );
}
/**
* {@inheritdoc}
*/
public function render(){
$title = __('Plugin settings','loco-translate');
$breadcrumb = new Loco_admin_Navigation;
$breadcrumb->add( $title );
return $this->view('admin/config/settings', compact('breadcrumb') );
}
}

View file

@ -0,0 +1,65 @@
<?php
/**
* Plugin version / upgrade screen
*/
class Loco_admin_config_VersionController extends Loco_admin_config_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$this->set( 'title', __('Version','loco-translate') );
}
/**
* {@inheritdoc}
*/
public function render(){
$title = __('Plugin settings','loco-translate');
$breadcrumb = new Loco_admin_Navigation;
$breadcrumb->add( $title );
// current plugin version
$version = loco_plugin_version();
// check for auto-update availability
if( $updates = get_site_transient('update_plugins') ){
$key = loco_plugin_self();
if( isset($updates->response[$key]) ){
$latest = $updates->response[$key]->new_version;
// if current version is lower than latest, prompt update
if( version_compare($version,$latest,'<') ){
$this->setUpdate($latest);
}
}
}
// notify if running a development snapshot, but only if ahead of latest stable
if( '-dev' === substr($version,-4) ){
$this->set( 'devel', true );
}
// $this->setUpdate('2.0.1-debug');
return $this->view('admin/config/version', compact('breadcrumb','version') );
}
/**
* @param string version
* @return void
*/
private function setUpdate( $version ){
$action = 'upgrade-plugin_'.loco_plugin_self();
$link = admin_url( 'update.php?action=upgrade-plugin&plugin='.rawurlencode(loco_plugin_self()) );
$this->set('update', $version );
$this->set('update_href', wp_nonce_url( $link, $action ) );
}
}

View file

@ -0,0 +1,167 @@
<?php
/**
* Base class for a file resource belonging to a bundle
* Root > List > Bundle > Resource
*/
abstract class Loco_admin_file_BaseController extends Loco_admin_bundle_BaseController {
/**
* @var Loco_Locale
*/
private $locale;
/**
* @return Loco_Locale
*/
protected function getLocale(){
return $this->locale;
}
/**
* Check file is valid or return error
* @param Loco_fs_File
* @return string rendered error
*/
protected function getFileError( Loco_fs_File $file = null ){
// file must exist for editing
if( is_null($file) || ! $file->exists() ){
return $this->view( 'admin/errors/file-missing', array() );
}
if( $file->isDirectory() ){
$this->set('info', Loco_mvc_FileParams::create($file) );
return $this->view( 'admin/errors/file-isdir', array() );
}
// security validations
try {
Loco_gettext_Data::ext( $file );
// TODO also need to block access to files outside content directory
// this is more difficult as can symlink into and out of the tree.
}
catch( Exception $e ){
return $this->view( 'admin/errors/file-sec', array( 'reason' => $e->getMessage() ) );
}
return '';
}
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
// views at this level are always related to a file
// file is permitted to be missing during this execution.
$path = $this->get('path');
if( ! $path ){
throw new Loco_error_Exception('path argument required');
}
$file = new Loco_fs_LocaleFile( $path );
$file->normalize( loco_constant('WP_CONTENT_DIR') );
$ext = strtolower( $file->extension() );
// POT file has no locale
if( 'pot' === $ext ){
$locale = null;
$localised = false;
}
// else file may have a locale suffix (unless invalid, such as "default.po")
else {
$locale = $file->getLocale();
$localised = $locale->isValid();
}
if( $localised ){
$this->locale = $locale;
$code = (string) $locale;
$this->set( 'locale', new Loco_mvc_ViewParams( array(
'code' => $code,
'lang' => $locale->lang,
'icon' => $locale->getIcon(),
'name' => $locale->ensureName( new Loco_api_WordPressTranslations ),
'href' => Loco_mvc_AdminRouter::generate('lang-view', array('locale'=>$code) ),
) ) );
}
else {
$this->set( 'locale', null );
}
$this->set('file', $file );
$this->set('filetype', strtoupper($ext) );
$this->set('title', $file->basename() );
// navigate up to root from this bundle sub view
$bundle = $this->getBundle();
$breadcrumb = Loco_admin_Navigation::createBreadcrumb( $bundle );
$this->set( 'breadcrumb', $breadcrumb );
// navigate between sub view siblings for this resource
$tabs = new Loco_admin_Navigation;
$this->set( 'tabs', $tabs );
$actions = array (
'file-edit' => __('Editor','loco-translate'),
'file-view' => __('Source','loco-translate'),
'file-info' => __('File info','loco-translate'),
'file-diff' => __('Restore','loco-translate'),
'file-move' => $localised ? __('Relocate','loco-translate') : null,
'file-delete' => __('Delete','loco-translate'),
);
$suffix = $this->get('action');
$prefix = $this->get('type');
$args = array_intersect_key($_GET,array('path'=>1,'bundle'=>1,'domain'=>1));
foreach( $actions as $action => $name ){
if( is_string($name) ){
$href = Loco_mvc_AdminRouter::generate( $prefix.'-'.$action, $args );
$tabs->add( $name, $href, $action === $suffix );
}
}
// Provide common language creation link if project scope is is valid
try {
$project = $this->getProject();
$args = array( 'bundle' => $bundle->getHandle(), 'domain' => $project->getId() );
$this->set( 'msginit', new Loco_mvc_ViewParams( array (
'href' => Loco_mvc_AdminRouter::generate( $prefix.'-msginit', $args ),
'text' => __('New language','loco-translate'),
) ) );
}
catch( Exception $e ){
}
}
/**
* {@inheritdoc}
*/
public function view( $tpl, array $args = array() ){
if( $breadcrumb = $this->get('breadcrumb') ){
// Add project name into breadcrumb if not the same as bundle name
try {
$project = $this->getProject();
if( $project->getName() !== $this->getBundle()->getName() ){
$breadcrumb->add( $project->getName() );
}
}
catch( Loco_error_Exception $e ){
// ignore missing project in breadcrumb
}
// Always add page title as final breadcrumb element
$title = $this->get('title') or $title = 'Untitled';
$breadcrumb->add( $title );
}
return parent::view( $tpl, $args );
}
}

View file

@ -0,0 +1,113 @@
<?php
/**
* File delete function
*/
class Loco_admin_file_DeleteController extends Loco_admin_file_BaseController {
/**
* Expand single path to all files that will be deleted
* @param Loco_fs_File primary file being deleted, probably the PO
* @return array
*/
private function expandFiles( Loco_fs_File $file ){
try {
$siblings = new Loco_fs_Siblings( $file );
}
catch( InvalidArgumentException $e ){
$ext = $file->extension();
throw new Loco_error_Exception( sprintf('Refusing to delete a %s file', strtoupper($ext) ) );
}
return $siblings->expand();
}
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$file = $this->get('file');
// set up form for delete confirmation
if( $file->exists() && ! $file->isDirectory() ){
// nonce action will be specific to file for extra security
// TODO could also add file MD5 to avoid deletion after changes made.
$path = $file->getPath();
$action = 'delete:'.$path;
// set up view now in case of late failure
$fields = new Loco_mvc_HiddenFields( array() );
$fields->setNonce( $action );
$this->set( 'hidden', $fields );
// attempt delete if valid nonce posted back
if( $this->checkNonce($action) ){
$api = new Loco_api_WordPressFileSystem;
// delete dependant files first, so master still exists if others fail
$files = array_reverse( $this->expandFiles($file) );
try {
/* @var $trash Loco_fs_File */
foreach( $files as $trash ){
$api->authorizeDelete($trash);
$trash->unlink();
}
// flash message for display after redirect
try {
$n = count( $files );
Loco_data_Session::get()->flash('success', sprintf( _n('File deleted','%u files deleted',$n,'loco-translate'),$n) );
Loco_data_Session::close();
}
catch( Exception $e ){
// tolerate session failure
}
// redirect to bundle overview
$href = Loco_mvc_AdminRouter::generate( $this->get('type').'-view', array( 'bundle' => $this->get('bundle') ) );
if( wp_redirect($href) ){
exit;
}
}
catch( Loco_error_Exception $e ){
Loco_error_AdminNotices::add( $e );
}
}
}
// set page title before render sets inline title
$bundle = $this->getBundle();
$this->set('title', sprintf( __('Delete %s','loco-translate'), $file->basename() ).' &lsaquo; '.$bundle->getName() );
}
/**
* {@inheritdoc}
*/
public function render(){
$file = $this->get('file');
if( $fail = $this->getFileError($file) ){
return $fail;
}
$files = $this->expandFiles( $file );
$info = Loco_mvc_FileParams::create($file);
$this->set( 'info', $info );
$this->set( 'title', sprintf( __('Delete %s','loco-translate'), $info->name ) );
// warn about additional files that will be deleted along with this
if( $deps = array_slice($files,1) ){
$count = count($deps);
$this->set('warn', sprintf( _n( 'One dependent file will also be deleted', '%u dependent files will also be deleted', $count, 'loco-translate' ), $count ) );
$infos = array();
foreach( $deps as $depfile ){
$infos[] = Loco_mvc_FileParams::create( $depfile );
}
$this->set('deps', $infos );
}
$this->prepareFsConnect( 'delete', $this->get('path') );
$this->enqueueScript('delete');
return $this->view('admin/file/delete');
}
}

View file

@ -0,0 +1,156 @@
<?php
/**
* File revisions and rollback
*/
class Loco_admin_file_DiffController extends Loco_admin_file_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$this->enqueueStyle('podiff');
$pofile = $this->get('file');
if( $pofile->exists() && ! $pofile->isDirectory() ){
$path = $pofile->getPath();
$action = 'restore:'.$path;
// set up view now in case of late failure
$fields = new Loco_mvc_HiddenFields( array() );
$fields->setNonce( $action );
$this->set( 'hidden', $fields );
// attempt rollback if valid nonce posted back with backup path
if( $this->checkNonce($action) ){
try {
$post = Loco_mvc_PostParams::get();
$api = new Loco_api_WordPressFileSystem;
// Restore
if( $path = $post->backup ){
$target = new Loco_fs_File( $path );
$target->normalize( loco_constant('WP_CONTENT_DIR') );
// parse PO. we'll need it for MO compile anyway
$source = $target->getContents();
$data = Loco_gettext_Data::fromSource( $source );
// backup current master before restoring
$backups = new Loco_fs_Revisions($pofile);
if( $num_backups = Loco_data_Settings::get()->num_backups ){
$api->authorizeCopy($pofile);
$backups->create();
}
// authorize master for file modification
$api->authorizeUpdate($pofile);
// recompile binary if it exists
$mofile = $pofile->cloneExtension('mo');
if( $mofile->exists() ){
$mofile->putContents( $data->msgfmt() );
}
// replacing source file last in case of failures
$pofile->putContents( $source );
Loco_error_AdminNotices::success( __('File restored','loco-translate') );
// prune to configured level after success
$backups->prune( $num_backups );
$backups = null;
}
// Delete
else if( $path = $post->delete ){
$target = new Loco_fs_File( $path );
$target->normalize( loco_constant('WP_CONTENT_DIR') );
$api->authorizeDelete( $target );
$target->unlink();
Loco_error_AdminNotices::success( __('File deleted','loco-translate') );
}
else {
throw new Loco_error_Exception('Nothing selected');
}
}
catch( Loco_error_Exception $e ){
Loco_error_AdminNotices::add( $e );
}
}
}
$bundle = $this->getBundle();
$this->set('title', sprintf( __('Restore %s','loco-translate'), $pofile->basename() ).' &lsaquo; '.$bundle->getName() );
}
/**
* {@inheritdoc}
*/
public function render(){
$file = $this->get('file');
if( $fail = $this->getFileError($file) ){
return $fail;
}
$info = Loco_mvc_FileParams::create($file);
$info['mtime'] = $file->modified();
$this->set( 'master', $info );
$this->set( 'title', sprintf( __('Restore %s','loco-translate'), $info->name ) );
$enabled = Loco_data_Settings::get()->num_backups;
$this->set( 'enabled', $enabled );
$files = array();
$wp_content = loco_constant('WP_CONTENT_DIR');
$paths = array( $file->getRelativePath($wp_content) );
$podate = 'pot' === $file->extension() ? 'POT-Creation-Date' : 'PO-Revision-Date';
$backups = new Loco_fs_Revisions($file);
foreach( $backups->getPaths() as $path ){
$tmp = new Loco_fs_File( $path );
$info = Loco_mvc_FileParams::create($tmp);
// time file was snapshotted is actually the time the next version was updated
// $info['mtime'] = $backups->getTimestamp($path);
// pull "real" update time, meaning when the revision was last updated as current version
try {
$head = Loco_gettext_Data::head($tmp)->getHeaders();
if( $value = $head->trimmed($podate) ){
$info['potime'] = Loco_gettext_Data::parseDate($value);
}
else {
throw new Loco_error_Exception('Backup has no '.$podate.' field');
}
}
catch( Exception $e ){
Loco_error_AdminNotices::debug( $e->getMessage() );
continue;
}
$paths[] = $tmp->getRelativePath($wp_content);
$files[] = $info;
}
// no backups = no restore
if( ! $files ){
return $this->view('admin/errors/no-backups');
}
/*/ warn if current backup settings aren't enough to restore without losing older revisions
$min = count($files) + 1;
if( $enabled < $min ){
$notice = Loco_error_AdminNotices::info('We recommend enabling more backups before restoring');
$notice->addLink( apply_filters('loco_external','https://localise.biz/wordpress/plugin/manual/settings#po'), __('Documentation','loco-translate') )
->addLink( Loco_mvc_AdminRouter::generate('config').'#loco--num-backups', __('Settings') );
}*/
// restore permissions required are create and delete on current location
$this->prepareFsConnect( 'update', $this->get('path') );
// prepare revision arguments for JavaScript
$this->set( 'js', new Loco_mvc_ViewParams( array(
'paths' => $paths,
'nonces' => array (
'diff' => wp_create_nonce('diff'),
)
) ) );
$this->enqueueScript('podiff');
return $this->view('admin/file/diff', compact('files','backups') );
}
}

View file

@ -0,0 +1,245 @@
<?php
/**
* PO editor view
*/
class Loco_admin_file_EditController extends Loco_admin_file_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$this->enqueueStyle('editor');
//
$file = $this->get('file');
$bundle = $this->getBundle();
// translators: %1$s is the file name, %2$s is the bundle name
$this->set('title', sprintf( __('Editing %1$s in %2$s','loco-translate'), $file->basename(), $bundle ) );
}
/**
* {@inheritdoc}
*/
public function getHelpTabs(){
return array (
__('Overview','default') => $this->viewSnippet('tab-file-edit'),
);
}
/**
* @param bool whether po files is in read-only mode
* @return array
*/
private function getNonces( $readonly ){
$nonces = array();
foreach( $readonly ? array('fsReference') : array('sync','save','fsReference','apis') as $name ){
$nonces[$name] = wp_create_nonce($name);
}
return $nonces;
}
/**
* @param bool whether po files is in read-only mode
* @return array
*/
private function getApiProviders( $readonly ){
return $readonly ? null : array_values( array_filter(Loco_api_Providers::export(),array(__CLASS__,'filterApiProvider') ) );
}
/**
* @internal
* @param string[]
* @return bool
*/
private static function filterApiProvider( array $api ){
return (bool) $api['key'];
}
/**
* {@inheritdoc}
*/
public function render(){
// file must exist for editing
$file = $this->get('file');
if( $fail = $this->getFileError($file) ){
return $fail;
}
// editor will be rendered
$this->enqueueScript('editor');
// Parse file data into JavaScript for editor
try {
$this->set('modified', $file->modified() );
$data = Loco_gettext_Data::load( $file );
}
catch( Exception $e ){
Loco_error_AdminNotices::add( Loco_error_Exception::convert($e) );
$data = Loco_gettext_Data::dummy();
}
$head = $data->getHeaders();
// default is to permit editing of any file
$readonly = false;
// Establish if file belongs to a configured project
try {
$bundle = $this->getBundle();
$project = $this->getProject();
}
// Fine if not, this just means sync isn't possible.
catch( Loco_error_Exception $e ){
Loco_error_AdminNotices::add( $e );
Loco_error_AdminNotices::debug( sprintf("Sync is disabled because this file doesn't relate to a known set of translations", $bundle ) );
$project = null;
}
// Establish PO/POT edit mode
$potfile = null;
$locale = $this->getLocale();
if( $locale instanceof Loco_Locale ){
// alternative POT file may be forced by PO headers
if( $value = $head['X-Loco-Template'] ){
$potfile = new Loco_fs_File($value);
$potfile->normalize( $bundle->getDirectoryPath() );
}
// else use project-configured template, assuming there is one
// no way to get configured POT if invalid project
else if( $project ){
$potfile = $project->getPot();
// Handle situation where project defines a localised file as the official template
if( $potfile && $potfile->equal($file) ){
$locale = null;
$potfile = null;
}
}
if( $potfile ){
// Validate template file as long as it exists
if( $potfile->exists() ){
try {
$potdata = Loco_gettext_Data::load( $potfile );
if( ! $potdata->equalSource($data) ){
Loco_error_AdminNotices::debug( sprintf( __("Translations don't match template. Run sync to update from %s",'loco-translate'), $potfile->basename() ) );
}
}
catch( Exception $e ){
// translators: Where %s is the name of the invalid POT file
Loco_error_AdminNotices::warn( sprintf( __('Translation template is invalid (%s)','loco-translate'), $potfile->basename() ) );
$potfile = null;
}
}
// else template doesn't exist, so sync will be done to source code
else {
// Loco_error_AdminNotices::debug( sprintf( __('Template file not found (%s)','loco-translate'), $potfile->basename() ) );
$potfile = null;
}
}
if( $locale ){
// allow PO file to dictate its own Plural-Forms
try {
$locale->setPluralFormsHeader( $head['Plural-Forms'] );
}
catch( InvalidArgumentException $e ){
// ignore invalid Plural-Forms
}
// fill in missing PO headers now locale is fully resolved
$data->localize($locale);
// If MO file will be compiled, check for library/config problems
if ( 2 !== strlen( "\xC2\xA3" ) ) {
Loco_error_AdminNotices::warn('Your mbstring configuration will result in corrupt MO files. Please ensure mbstring.func_overload is disabled');
}
}
}
$settings = Loco_data_Settings::get();
if( is_null($locale) ){
// notify if template is locked (save and sync will be disabled)
if( $project && $project->isPotLocked() ){
$this->set('fsDenied', true );
$readonly = true;
}
// translators: Warning when POT file is opened in the file editor. It can be disabled in settings.
else if( 1 === $settings->pot_protect ){
Loco_error_AdminNotices::warn( __("This is NOT a translation file. Manual editing of source strings is not recommended.",'loco-translate') )
->addLink( Loco_mvc_AdminRouter::generate('config').'#loco--pot-protect', __('Settings','loco-translate') )
->addLink( apply_filters('loco_external','https://localise.biz/wordpress/plugin/manual/templates'), __('Documentation','loco-translate') );
}
}
// back end expects paths relative to wp-content
$wp_content = loco_constant('WP_CONTENT_DIR');
$this->set( 'js', new Loco_mvc_ViewParams( array(
'podata' => $data->jsonSerialize(),
'powrap' => (int) $settings->po_width,
'multipart' => (bool) $settings->ajax_files,
'locale' => $locale ? $locale->jsonSerialize() : null,
'potpath' => $locale && $potfile ? $potfile->getRelativePath($wp_content) : null,
'popath' => $this->get('path'),
'readonly' => $readonly,
'project' => $project ? array (
'bundle' => $bundle->getId(),
'domain' => (string) $project->getId(),
) : null,
'nonces' => $this->getNonces($readonly),
'apis' => $locale ? $this->getApiProviders($readonly) : null,
) ) );
$this->set( 'ui', new Loco_mvc_ViewParams( array(
// Translators: button for adding a new string when manually editing a POT file
'add' => _x('Add','Editor','loco-translate'),
// Translators: button for removing a string when manually editing a POT file
'del' => _x('Remove','Editor','loco-translate'),
'help' => __('Help','loco-translate'),
// Translators: Button that saves translations to disk
'save' => _x('Save','Editor','loco-translate'),
// Translators: Button that runs in-editor sync/operation
'sync' => _x('Sync','Editor','loco-translate'),
// Translators: Button that reloads current screen
'revert' => _x('Revert','Editor','loco-translate'),
// Translators: Button that opens window for auto-translating
'auto' => _x('Auto','Editor','loco-translate'),
// Translators: Button for downloading a PO, MO or POT file
'download' => _x('Download','Editor','loco-translate'),
// Translators: Placeholder text for text filter above editor
'filter' => __('Filter translations','loco-translate'),
// Translators: Button that toggles invisible characters
'invs' => _x('Toggle invisibles','Editor','loco-translate'),
// Translators: Button that toggles between "code" and regular text editing modes
'code' => _x('Toggle code view','Editor','loco-translate'),
) ) );
// Download form params
$hidden = new Loco_mvc_HiddenFields( array(
'path' => '',
'source' => '',
'route' => 'download',
'action' => 'loco_download',
) );
$this->set( 'dlFields', $hidden->setNonce('download') );
$this->set( 'dlAction', admin_url('admin-ajax.php','relative') );
// Remote file system required if file is not directly writable
$this->prepareFsConnect( 'update', $this->get('path') );
// set simpler title for breadcrumb
$this->set('title', $file->basename() );
// ok to render editor as either po or pot
$tpl = $locale ? 'po' : 'pot';
return $this->view( 'admin/file/edit-'.$tpl, array() );
}
}

View file

@ -0,0 +1,201 @@
<?php
/**
* File info / management view.
*/
class Loco_admin_file_InfoController extends Loco_admin_file_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$this->enqueueStyle('fileinfo');
//
$file = $this->get('file');
$bundle = $this->getBundle();
$this->set('title', $file->basename().' &lsaquo; '.$bundle->getName() );
}
/**
* {@inheritdoc}
*/
public function getHelpTabs(){
return array (
__('Overview','default') => $this->viewSnippet('tab-file-info'),
);
}
/**
* {@inheritdoc}
*/
public function render(){
$file = $this->get('file');
$name = $file->basename();
$this->set('title', $name );
if( $fail = $this->getFileError($file) ){
return $fail;
}
// file info
$ext = strtolower( $file->extension() );
$finfo = Loco_mvc_FileParams::create( $file );
$this->set('file', $finfo );
$finfo['type'] = strtoupper($ext);
if( $file->exists() ){
$finfo['existent'] = true;
$finfo['writable'] = $file->writable();
$finfo['deletable'] = $file->deletable();
$finfo['mtime'] = $file->modified();
// Notify if file is managed by WordPress
$api = new Loco_api_WordPressFileSystem;
if( $api->isAutoUpdatable($file) ){
$finfo['autoupdate'] = true;
}
}
// location info
$dir = new Loco_fs_LocaleDirectory( $file->dirname() );
$dinfo = Loco_mvc_FileParams::create( $dir );
$this->set('dir', $dinfo );
$dinfo['type'] = $dir->getTypeId();
if( $dir->exists() && $dir->isDirectory() ){
$dinfo['existent'] = true;
$dinfo['writable'] = $dir->writable();
}
// collect note worthy problems with file headers
$debugging = loco_debugging();
$debug = array();
// get the name of the web server for information purposes
$this->set('httpd', Loco_compat_PosixExtension::getHttpdUser() );
// unknown file template if required
$locale = null;
$project = null;
$tpl = 'admin/file/info-other';
// we should know the project the file belongs to, but permitting orphans for debugging
try {
$project = $this->getProject();
$template = $project->getPot();
$isTemplate = $template && $file->equal($template);
$this->set('isTemplate', $isTemplate );
$this->set('project', $project );
}
catch( Loco_error_Exception $e ){
$debug[] = $e->getMessage();
$isTemplate = false;
$template = null;
}
// file will be Gettext most likely
if( 'pot' === $ext || 'po' === $ext || 'mo' === $ext ){
// treat as template until locale verified
$tpl = 'admin/file/info-pot';
// don't attempt to pull locale of template file
if( 'pot' !== $ext && ! $isTemplate ){
$locale = $file->getLocale();
$code = (string) $locale;
if( $locale->isValid() ){
// find PO/MO counter parts
if( 'po' === $ext ){
$tpl = 'admin/file/info-po';
$sibling = $file->cloneExtension('mo');
}
else {
$tpl = 'admin/file/info-mo';
$sibling = $file->cloneExtension('po');
}
$info = Loco_mvc_FileParams::create($sibling);
$this->set( 'sibling', $info );
if( $sibling->exists() ){
$info['existent'] = true;
$info['writable'] = $sibling->writable();
}
}
}
// Do full parse to get stats and headers
try {
$data = Loco_gettext_Data::load($file);
$head = $data->getHeaders();
$author = $head->trimmed('Last-Translator') or $author = __('Unknown author','loco-translate');
$this->set( 'author', $author );
// date headers may not be same as file modification time (files copied to server etc..)
$podate = $head->trimmed( $locale ? 'PO-Revision-Date' : 'POT-Creation-Date' );
$potime = Loco_gettext_Data::parseDate($podate) or $potime = $file->modified();
$this->set('potime', $potime );
// access to meta stats, normally cached on listing pages
$meta = Loco_gettext_Metadata::create($file,$data);
$this->set( 'meta', $meta );
// allow PO header to specify alternative template for sync
if( $head->has('X-Loco-Template') ){
$altpot = new Loco_fs_File($head['X-Loco-Template']);
$altpot->normalize( $this->getBundle()->getDirectoryPath() );
if( $altpot->exists() && ( ! $template || ! $template->equal($altpot) ) ){
$template = $altpot;
}
}
// establish whether PO is in sync with POT
if( $template && ! $isTemplate && 'po' === $ext && $template->exists() ){
try {
$this->set('potfile', new Loco_mvc_FileParams( array(
'synced' => Loco_gettext_Data::load($template)->equalSource($data),
), $template ) );
}
catch( Exception $e ){
// ignore invalid template in this context
}
}
if( $debugging ){
// missing or invalid headers are tollerated but developers should be notified
if( $debugging && ! count($head) ){
$debug[] = __('File does not have a valid header','loco-translate');
}
// Language header sanity checks, raising developer (debug) warnings
if( $locale ){
if( $value = $head['Language'] ){
$check = (string) Loco_Locale::parse($value);
if( $check !== $code ){
$debug[]= sprintf( __('Language header is "%s" but file name contains "%s"','loco-translate'), $value, $code );
}
}
if( $value = $head['Plural-Forms'] ){
try {
$locale->setPluralFormsHeader($value);
}
catch( InvalidArgumentException $e ){
$debug[] = sprintf('Plural-Forms header is invalid, "%s"',$value);
}
}
}
// Other sanity checks
if( $project && ( $value = $head['Project-Id-Version'] ) && $value !== $project->getName() ){
$debug[] = sprintf('Project-Id-Version header is "%s" but project is "%s"', $value, $project );
}
}
// Count source text for templates only (assumed English)
if( 'admin/file/info-pot' === $tpl ){
$counter = new Loco_gettext_WordCount($data);
$this->set('words', $counter->count() );
}
}
catch( Loco_error_Exception $e ){
$this->set('error', $e->getMessage() );
$tpl = 'admin/file/info-other';
}
}
if( $debugging && $debug ){
$this->set( 'debug', new Loco_mvc_ViewParams($debug) );
}
return $this->view( $tpl );
}
}

View file

@ -0,0 +1,185 @@
<?php
/**
* Translation set relocation tool.
* Moves PO/MO pair and all related files to a new location
*/
class Loco_admin_file_MoveController extends Loco_admin_file_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$file = $this->get('file');
/* @var Loco_fs_File $file */
if( $file->exists() && ! $file->isDirectory() ){
$files = new Loco_fs_Siblings($file);
// nonce action will be specific to file for extra security
$path = $file->getPath();
$action = 'move:'.$path;
// set up view now in case of late failure
$fields = new Loco_mvc_HiddenFields( array() );
$fields->setNonce( $action );
$fields['auth'] = 'move';
$fields['path'] = $this->get('path');
$this->set('hidden',$fields );
// attempt move if valid nonce posted back
while( $this->checkNonce($action) ){
// Chosen location should be valid as a posted "dest" parameter
if( ! Loco_mvc_PostParams::get()->has('dest') ){
Loco_error_AdminNotices::err('No destination posted');
break;
}
$target = new Loco_fs_LocaleFile( Loco_mvc_PostParams::get()->dest );
$ext = $target->extension();
// primary file extension should only be permitted to change between po and pot
if( $ext !== $file->extension() && 'po' !== $ext && 'pot' !== $ext ){
Loco_error_AdminNotices::err('Invalid file extension, *.po or *.pot only');
break;
}
$target->normalize( loco_constant('WP_CONTENT_DIR') );
$target_dir = $target->getParent()->getPath();
// Primary file gives template remapping, so all files are renamed with same stub.
// this can only be one of three things: (en -> en) or (foo-en -> en) or (en -> foo-en)
// suffix will then consist of file extension, plus any other stuff like backup file date.
$target_base = $target->filename();
$source_snip = strlen( $file->filename() );
// buffer all files to move to preempt write failures
$movable = array();
$api = new Loco_api_WordPressFileSystem;
foreach( $files->expand() as $source ){
$suffix = substr( $source->basename(), $source_snip ); // <- e.g. "-backup.po~"
$target = new Loco_fs_File( $target_dir.'/'.$target_base.$suffix );
// permit valid change of file extension on primary source file (po/pot)
if( $source === $files->getSource() && $target->extension() !== $ext ){
$target = $target->cloneExtension($ext);
}
if( ! $api->authorizeMove($source,$target) ) {
Loco_error_AdminNotices::err('Failed to authorize relocation of '.$source->basename() );
break 2;
}
$movable[] = array($source,$target);
}
// commit moves. If any fail we'll have separated the files, which is bad
$count = 0;
$total = count($movable);
foreach( $movable as $pair ){
try {
$pair[0]->move( $pair[1] );
$count++;
}
catch( Loco_error_Exception $e ){
Loco_error_AdminNotices::add($e);
}
}
// flash messages for display after redirect
try {
if( $count ) {
Loco_data_Session::get()->flash( 'success', sprintf( _n( 'File moved', '%u files moved', $total, 'loco-translate' ), $total ) );
}
if( $total > $count ){
$diff = $total - $count;
Loco_data_Session::get()->flash( 'error', sprintf( _n( 'One file could not be moved', '%u files could not be moved', $diff, 'loco-translate' ), $diff ) );
}
Loco_data_Session::close();
}
catch( Exception $e ){
// tolerate session failure
}
// redirect to bundle overview
$href = Loco_mvc_AdminRouter::generate( $this->get('type').'-view', array( 'bundle' => $this->get('bundle') ) );
if( wp_redirect($href) ){
exit;
}
break;
}
}
// set page title before render sets inline title
$bundle = $this->getBundle();
$this->set('title', sprintf( __('Move %s','loco-translate'), $file->basename() ).' &lsaquo; '.$bundle->getName() );
}
/**
* {@inheritdoc}
*/
public function render(){
$file = $this->get('file');
if( $fail = $this->getFileError($file) ){
return $fail;
}
// relocation requires knowing text domain and locale
try {
$project = $this->getProject();
}
catch( Loco_error_Exception $e ){
Loco_error_AdminNotices::warn($e->getMessage());
$project = null;
}
$files = new Loco_fs_Siblings($file);
$file = new Loco_fs_LocaleFile( $files->getSource() );
$locale = $file->getLocale();
// switch between canonical move and custom file path mode
$custom = is_null($project) || $this->get('custom') || 'po' !== $file->extension() || ! $locale->isValid();
// common page elements:
$this->set('files',$files->expand() );
$this->set('title', sprintf( __('Move %s','loco-translate'), $file->filename() ) );
$this->enqueueScript('move');
// set info for existing file location
$content_dir = loco_constant('WP_CONTENT_DIR');
$current = $file->getRelativePath($content_dir);
$parent = new Loco_fs_LocaleDirectory( $file->dirname() );
$typeId = $parent->getTypeId();
$this->set('current', new Loco_mvc_ViewParams(array(
'path' => $parent->getRelativePath($content_dir),
'type' => $parent->getTypeLabel($typeId),
)) );
// moving files will require deletion permission on current file location
// plus write permission on target location, but we don't know what that is yet.
$fields = $this->prepareFsConnect('move',$current);
$fields['path'] = '';
$fields['dest'] = '';
// custom file move template (POT mode)
if( $custom ){
$this->get('hidden')->offsetSet('custom','1');
$this->set('file', Loco_mvc_FileParams::create($file) );
return $this->view('admin/file/move-pot');
}
// establish valid locations for translation set, which may include current:
$filechoice = $project->initLocaleFiles($locale);
// start with current location so always first in list
$locations = array();
$locations[$typeId] = new Loco_mvc_ViewParams( array(
'label' => $parent->getTypeLabel($typeId),
'paths' => array( new Loco_mvc_ViewParams( array(
'path' => $current,
'active' => true,
) ) )
) );
/* @var Loco_fs_File $pofile */
foreach( $filechoice as $pofile ){
$relpath = $pofile->getRelativePath($content_dir);
if( $current === $relpath ){
continue;
}
// initialize location type (system, etc..)
$parent = new Loco_fs_LocaleDirectory( $pofile->dirname() );
$typeId = $parent->getTypeId();
if( ! isset($locations[$typeId]) ){
$locations[$typeId] = new Loco_mvc_ViewParams( array(
'label' => $parent->getTypeLabel($typeId),
'paths' => array(),
) );
}
$choice = new Loco_mvc_ViewParams( array(
'path' => $relpath,
) );
$locations[$typeId]['paths'][] = $choice;
}
$this->set('locations', $locations );
$this->set('advanced', $_SERVER['REQUEST_URI'].'&custom=1' );
return $this->view('admin/file/move-po');
}
}

View file

@ -0,0 +1,101 @@
<?php
/**
* File view / source formatted view.
*/
class Loco_admin_file_ViewController extends Loco_admin_file_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$this->enqueueStyle('poview');
//
$file = $this->get('file');
$bundle = $this->getBundle();
$this->set( 'title', 'Source of '.$file->basename().' &lsaquo; '.$bundle->getName() );
}
/**
* {@inheritdoc}
*/
public function getHelpTabs(){
return array (
__('Overview','default') => $this->viewSnippet('tab-file-view'),
);
}
/**
* {@inheritdoc}
*/
public function render(){
// file must exist for editing
/* @var Loco_fs_File $file */
$file = $this->get('file');
$name = $file->basename();
$type = strtolower( $file->extension() );
$this->set('title', $name );
if( $fail = $this->getFileError($file) ){
return $fail;
}
// Establish if file belongs to a configured project
try {
$bundle = $this->getBundle();
$project = $this->getProject();
}
catch( Exception $e ){
$project = null;
}
// Parse data before rendering, so we know it's a valid Gettext format
try {
$this->set('modified', $file->modified() );
$data = Loco_gettext_Data::load( $file );
}
catch( Loco_error_ParseException $e ){
Loco_error_AdminNotices::add( Loco_error_Exception::convert($e) );
$data = Loco_gettext_Data::dummy();
}
$this->set( 'meta', Loco_gettext_Metadata::create($file, $data) );
// binary MO will be hex-formatted in template
if( 'mo' === $type ){
$this->set('bin', $file->getContents() );
return $this->view('admin/file/view-mo' );
}
// else is a PO or POT file
$this->enqueueScript('poview');//->enqueueScript('min/highlight');
$lines = preg_split('/(?:\\n|\\r\\n?)/', Loco_gettext_Data::ensureUtf8( $file->getContents() ) );
$this->set( 'lines', $lines );
// ajax parameters required for pulling reference sources
$this->set('js', new Loco_mvc_ViewParams( array (
'popath' => $this->get('path'),
'nonces' => array(
'fsReference' => wp_create_nonce('fsReference'),
),
'project' => $bundle ? array (
'bundle' => $bundle->getId(),
'domain' => $project ? $project->getId() : '',
) : null,
) ) );
// treat as PO if file name has locale
if( $this->getLocale() ){
return $this->view('admin/file/view-po' );
}
// else view as POT
return $this->view('admin/file/view-pot' );
}
}

View file

@ -0,0 +1,327 @@
<?php
/**
* pre-msginit function. Prepares arguments for creating a new PO language file
*/
class Loco_admin_init_InitPoController extends Loco_admin_bundle_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$this->enqueueStyle('poinit');
//
$bundle = $this->getBundle();
$this->set('title', __('New language','loco-translate').' &lsaquo; '.$bundle );
}
/**
* {@inheritdoc}
*/
public function getHelpTabs(){
return array (
__('Overview','default') => $this->viewSnippet('tab-init-po'),
);
}
/**
* Sort to the left the best option for saving new translation files
* @return Loco_mvc_ViewParams
*/
private function sortPreferred( array $choices ){
usort( $choices, array(__CLASS__,'_onSortPreferred') );
$best = current( $choices );
if( $best && ! $best['disabled'] ){
return $best;
}
}
/**
* @internal
*/
public static function _onSortPreferred( Loco_mvc_ViewParams $a, Loco_mvc_ViewParams $b ){
$x = self::scoreFileChoice($a);
$y = self::scoreFileChoice($b);
return $x === $y ? 0 : ( $x > $y ? -1 : 1 );
}
/**
* Score an individual file choice for sorting preferred
* @return int
*/
private static function scoreFileChoice( Loco_mvc_ViewParams $p ){
$score = 0;
if( $p['writable'] ){
$score++;
}
if( $p['disabled'] ){
$score -= 2;
}
if( $p['systype'] ){
$score--;
}
return $score;
}
/**
* @internal
*/
public static function _onSortLocationKeys( $a, $b ){
static $order = array('custom' => 4, 'wplang' => 3, 'theme' => 2, 'plugin' => 2, 'other' => 1 );
$x = $order[$a];
$y = $order[$b];
return $x === $y ? 0 : ( $x > $y ? -1 : 1 );
}
/**
* {@inheritdoc}
*/
public function render(){
$breadcrumb = $this->prepareNavigation();
// "new" tab is confusing when no project-scope navigation
// $this->get('tabs')->add( __('New PO','loco-translate'), '', true );
// bundle mandatory, but project optional
$bundle = $this->getBundle();
try {
$project = $this->getProject();
$slug = $project->getSlug();
$domain = (string) $project->getDomain();
$subhead = sprintf( __('Initializing new translations in "%s"','loco-translate'), $slug?$slug:$domain );
}
catch( Loco_error_Exception $e ){
$project = null;
$subhead = __('Initializing new translations in unknown set','loco-translate');
}
$title = __('New language','loco-translate');
$this->set('subhead', $subhead );
// navigate up to bundle listing page
$breadcrumb->add( $title );
$this->set( 'breadcrumb', $breadcrumb );
// default locale is a placeholder
$locale = new Loco_Locale('zxx');
$content_dir = untrailingslashit( loco_constant('WP_CONTENT_DIR') );
$copying = false;
// Permit using any provided file a template instead of POT
if( $potpath = $this->get('path') ){
$potfile = new Loco_fs_LocaleFile($potpath);
$potfile->normalize( $content_dir );
if( ! $potfile->exists() ){
throw new Loco_error_Exception('Forced template argument must exist');
}
$copying = true;
// forced source could be a POT (although UI would normally prevent it)
if( $potfile->getSuffix() ){
$locale = $potfile->getLocale();
$this->set('sourceLocale', $locale );
}
}
// else project not configured. UI should prevent this by not offering msginit
else if( ! $project ){
throw new Loco_error_Exception('Cannot add new language to unconfigured set');
}
// else POT file may or may not be known, and may or may not exist
else {
$potfile = $project->getPot();
}
$locales = array();
$installed = array();
$api = new Loco_api_WordPressTranslations;
// pull installed list first, this will include en_US and any non-standard languages installed
foreach( $api->getInstalledCore() as $tag ){
$locale = Loco_Locale::parse($tag);
if( $locale->isValid() ){
$tag = (string) $tag;
// We may not have names for these, so just the language tag will show
$installed[$tag] = new Loco_mvc_ViewParams( array(
'value' => $tag,
'icon' => $locale->getIcon(),
'label' => $locale->ensureName($api),
) );
}
}
// pull the same list of "available" languages as used in WordPress settings
/* @var $locale Loco_Locale */
foreach( $api->getAvailableCore() as $tag => $locale ){
if( ! array_key_exists($tag,$installed) ){
$locales[$tag] = new Loco_mvc_ViewParams( array(
'value' => $tag,
'icon' => $locale->getIcon(),
'label' => $locale->ensureName($api),
) );
}
}
// two locale lists built for "installed" and "available" dropdowns
$this->set( 'locales', $locales );
$this->set( 'installed', $installed );
// Critical that user selects the correct save location:
if( $project ){
$filechoice = $project->initLocaleFiles( $locale );
}
// without configured project we will only allow save to same location
else {
$filechoice = new Loco_fs_FileList;
}
// show information about POT file if we are initializing from template
if( $potfile && $potfile->exists() ){
$meta = Loco_gettext_Metadata::load($potfile);
$total = $meta->getTotal();
$summary = sprintf( _n('One string found in %2$s','%s strings found in %s',$total,'loco-translate'), number_format($total), $potfile->basename() );
$this->set( 'pot', new Loco_mvc_ViewParams( array(
'name' => $potfile->basename(),
'path' => $meta->getPath(false),
) ) );
// if copying an existing PO file, we can fairly safely establish the correct prefixing
if( $copying ){
$poname = ( $prefix = $potfile->getPrefix() ) ? sprintf('%s-%s.po',$prefix,$locale) : sprintf('%s.po',$locale);
$pofile = new Loco_fs_LocaleFile( $poname );
$pofile->normalize( $potfile->dirname() );
$filechoice->add( $pofile );
}
/// else if POT is in a folder we don't know about, we may as well add to the choices
// TODO this means another utility function in project for prefixing rules on individual location
}
// else no template exists, so we prompt to extract from source
else {
$this->set( 'ext', new Loco_mvc_ViewParams( array(
'link' => Loco_mvc_AdminRouter::generate( $this->get('type').'-xgettext', $_GET ),
'text' => __('Create template','loco-translate'),
) ) );
// if forcing source extraction show brief description of source files
if( $this->get('extract') ){
// Tokenizer required for string extraction
if( ! loco_check_extension('tokenizer') ){
return $this->view('admin/errors/no-tokenizer');
}
$nfiles = count( $project->findSourceFiles() );
$summary = sprintf( _n('1 source file will be scanned for translatable strings','%s source files will be scanned for translatable strings',$nfiles,'loco-translate'), number_format_i18n($nfiles) );
}
// else prompt for template creation before continuing
else {
$this->set( 'skip', new Loco_mvc_ViewParams( array(
'link' => Loco_mvc_AdminRouter::generate( $this->get('_route'), $_GET + array( 'extract' => '1' ) ),
'text' => __('Skip template','loco-translate'),
) ) );
// POT could still be defined, it might just not exist yet
if( $potfile ){
$this->set('pot', Loco_mvc_FileParams::create($potfile) );
}
// else offer assignment of a new file
else {
$this->set( 'conf', new Loco_mvc_ViewParams( array(
'link' => Loco_mvc_AdminRouter::generate( $this->get('type').'-conf', array_intersect_key($_GET,array('bundle'=>'')) ),
'text' => __('Assign template','loco-translate'),
) ) );
}
return $this->view('admin/init/init-prompt');
}
}
$this->set( 'summary', $summary );
// group established locations into types (official, etc..)
// there is no point checking whether any of these file exist, because we don't know what language will be chosen yet.
$sortable = array();
$locations = array();
$fs_protect = Loco_data_Settings::get()->fs_protect;
$fs_failure = null;
/* @var Loco_fs_File $pofile */
foreach( $filechoice as $pofile ){
$parent = new Loco_fs_LocaleDirectory( $pofile->dirname() );
$systype = $parent->getUpdateType();
$typeId = $parent->getTypeId();
if( ! isset($locations[$typeId]) ){
$locations[$typeId] = new Loco_mvc_ViewParams( array(
'label' => $parent->getTypeLabel( $typeId ),
'paths' => array(),
) );
}
// folder may be unwritable (requiring connect to create file) or may be denied under security settings
try {
$context = $parent->getWriteContext()->authorize();
$writable = $context->writable();
$disabled = false;
}
catch( Loco_error_WriteException $e ){
$fs_failure = $e->getMessage();
$writable = false;
$disabled = true;
}
$choice = new Loco_mvc_ViewParams( array (
'checked' => '',
'writable' => $writable,
'disabled' => $disabled,
'systype' => $systype,
'parent' => Loco_mvc_FileParams::create( $parent ),
'hidden' => $pofile->getRelativePath($content_dir),
'holder' => str_replace( (string) $locale, '<span>&lt;locale&gt;</span>', $pofile->basename() ),
) );
// may need to show system file warnings
if( $systype && $fs_protect ){
$choice['syswarn'] = true;
}
$sortable[] = $choice;
$locations[$typeId]['paths'][] = $choice;
}
// display locations in runtime preference order
uksort( $locations, array(__CLASS__,'_onSortLocationKeys') );
$this->set( 'locations', $locations );
// pre-select best (safest/writable) option
if( $preferred = $this->sortPreferred( $sortable ) ){
$preferred['checked'] = 'checked';
}
// else show total lock message. probably file mods disallowed
else if( $fs_failure ){
$this->set('fsLocked', $fs_failure );
}
// hidden fields to pass through to Ajax endpoint
$this->set('hidden', new Loco_mvc_HiddenFields( array(
'action' => 'loco_json',
'route' => 'msginit',
'loco-nonce' => $this->setNonce('msginit')->value,
'type' => $bundle->getType(),
'bundle' => $bundle->getHandle(),
'domain' => $project ? $project->getId() : '',
'source' => $potpath,
) ) );
$this->set('help', new Loco_mvc_ViewParams( array(
'href' => apply_filters('loco_external','https://localise.biz/wordpress/plugin/manual/msginit'),
'text' => __("What's this?",'loco-translate'),
) ) );
// file system prompts will be handled when paths are selected (i.e. we don't have one yet)
$this->prepareFsConnect( 'create', '' );
$this->enqueueScript('poinit');
return $this->view( 'admin/init/init-po', array() );
}
}

View file

@ -0,0 +1,151 @@
<?php
/**
* pre-xgettext function. Initializes a new PO file for a given locale
*/
class Loco_admin_init_InitPotController extends Loco_admin_bundle_BaseController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$this->enqueueStyle('poinit');
//
$bundle = $this->getBundle();
$this->set('title', __('New template','loco-translate').' &lsaquo; '.$bundle );
}
/**
* {@inheritdoc}
*/
public function getHelpTabs(){
return array (
__('Overview','default') => $this->viewSnippet('tab-init-pot'),
);
}
/**
* {@inheritdoc}
*/
public function render(){
$breadcrumb = $this->prepareNavigation();
// "new" tab is confusing when no project-scope navigation
// $this->get('tabs')->add( __('New POT','loco-translate'), '', true );
$bundle = $this->getBundle();
$project = $this->getProject();
$slug = $project->getSlug();
$domain = (string) $project->getDomain();
$this->set('domain', $domain );
// Tokenizer required for string extraction
if( ! loco_check_extension('tokenizer') ){
return $this->view('admin/errors/no-tokenizer');
}
// Establish default POT path whether it exists or not
$pot = $project->getPot();
while( ! $pot ){
$name = ( $slug ? $slug : $domain ).'.pot';
/* @var $dir Loco_fs_Directory */
foreach( $project->getConfiguredTargets() as $dir ){
$pot = new Loco_fs_File( $dir->getPath().'/'.$name );
break 2;
}
// unlikely to have no configured targets, but possible ... so default to standard
$pot = new Loco_fs_File( $bundle->getDirectoryPath().'/languages/'.$name );
break;
}
// POT should actually not exist at this stage. It should be edited instead.
if( $pot->exists() ){
throw new Loco_error_Exception( __('Template file already exists','loco-translate') );
}
// Bundle may deliberately lock template to avoid end-user tampering
// it makes little sense to do so when template doesn't exist, but we will honour the setting anyway.
if( $project->isPotLocked() ){
throw new Loco_error_Exception('Template is protected from updates by the bundle configuration');
}
// Just warn if POT writing will fail when saved, but still show screen
$dir = $pot->getParent();
// Avoiding full source scan until actioned, but calculate size to manage expectations
$bytes = 0;
$nfiles = 0;
$nskip = 0;
$largest = 0;
$sources = $project->findSourceFiles();
// skip files larger than configured maximum
$opts = Loco_data_Settings::get();
$max = wp_convert_hr_to_bytes( $opts->max_php_size );
/* @var $sourceFile Loco_fs_File */
foreach( $sources as $sourceFile ){
$nfiles++;
$fsize = $sourceFile->size();
$largest = max( $largest, $fsize );
if( $fsize > $max ){
$nskip += 1;
// uncomment to log which files are too large to be scanned
// Loco_error_AdminNotices::debug( sprintf('%s is %s',$sourceFile,Loco_mvc_FileParams::renderBytes($fsize)) );
}
else {
$bytes += $fsize;
}
}
$this->set( 'scan', new Loco_mvc_ViewParams( array (
'bytes' => $bytes,
'count' => $nfiles,
'skip' => $nskip,
'size' => Loco_mvc_FileParams::renderBytes($bytes),
'large' => Loco_mvc_FileParams::renderBytes($max),
'largest' => Loco_mvc_FileParams::renderBytes($largest),
) ) );
// file metadata
$this->set('pot', Loco_mvc_FileParams::create( $pot ) );
$this->set('dir', Loco_mvc_FileParams::create( $dir ) );
$title = __('New template file','loco-translate');
$subhead = sprintf( __('New translations template for "%s"','loco-translate'), $project );
$this->set('subhead', $subhead );
// navigate up to bundle listing page
$breadcrumb->add( $title );
$this->set( 'breadcrumb', $breadcrumb );
// ajax service takes the target directory path
$content_dir = loco_constant('WP_CONTENT_DIR');
$target_path = $pot->getParent()->getRelativePath($content_dir);
// hidden fields to pass through to Ajax endpoint
$this->set( 'hidden', new Loco_mvc_ViewParams( array(
'action' => 'loco_json',
'route' => 'xgettext',
'loco-nonce' => $this->setNonce('xgettext')->value,
'type' => $bundle->getType(),
'bundle' => $bundle->getHandle(),
'domain' => $project->getId(),
'path' => $target_path,
'name' => $pot->basename(),
) ) );
// File system connect required if location not writable
$relpath = $pot->getRelativePath($content_dir);
$this->prepareFsConnect('create', $relpath );
$this->enqueueScript('potinit');
return $this->view( 'admin/init/init-pot' );
}
}

View file

@ -0,0 +1,86 @@
<?php
/**
* Common controller for listing of all bundle types
*/
abstract class Loco_admin_list_BaseController extends Loco_mvc_AdminController {
private $bundles = array();
/**
* build renderable bundle variables
* @return Loco_mvc_ViewParams
*/
protected function bundleParam( Loco_package_Bundle $bundle ){
$handle = $bundle->getHandle();
// compatibility will be 'ok', 'warn' or 'error' depending on severity
if( $default = $bundle->getDefaultProject() ){
$compat = $default->getPot() instanceof Loco_fs_File;
}
else {
$compat = false;
}
//$info = $bundle->getHeaderInfo();
return new Loco_mvc_ViewParams( array (
'id' => $bundle->getId(),
'name' => $bundle->getName(),
'dflt' => $default ? $default->getDomain() : '--',
'size' => count( $bundle ),
'save' => $bundle->isConfigured(),
'type' => $type = strtolower( $bundle->getType() ),
'view' => Loco_mvc_AdminRouter::generate( $type.'-view', array( 'bundle' => $handle ) ),
'time' => $bundle->getLastUpdated(),
) );
}
/**
* Add bundle to enabled or disabled list, depending on whether it is configured
*/
protected function addBundle( Loco_package_Bundle $bundle ){
$this->bundles[] = $this->bundleParam($bundle);
}
/**
* {@inheritdoc}
*/
public function getHelpTabs(){
return array (
__('Overview','default') => $this->viewSnippet('tab-list-bundles'),
);
}
/**
* {@inheritdoc}
*/
public function render(){
// breadcrumb is just the root
$here = new Loco_admin_Navigation( array (
new Loco_mvc_ViewParams( array( 'name' => $this->get('title') ) ),
) );
/*/ tab between the types of bundles
$types = array (
'' => __('Home','loco-translate'),
'theme' => __('Themes','loco-translate'),
'plugin' => __('Plugins','loco-translate'),
);
$current = $this->get('_route');
$tabs = new Loco_admin_Navigation;
foreach( $types as $type => $name ){
$href = Loco_mvc_AdminRouter::generate($type);
$tabs->add( $name, $href, $type === $current );
}
*/
return $this->view( 'admin/list/bundles', array (
'bundles' => $this->bundles,
'breadcrumb' => $here,
) );
}
}

View file

@ -0,0 +1,16 @@
<?php
/**
* Dummy controller skips "core" list view, rendering the core projects directly as a single bundle.
* Route: loco-core -> loco-core-view
*/
class Loco_admin_list_CoreController extends Loco_admin_RedirectController {
/**
* {@inheritdoc}
*/
public function getLocation(){
return Loco_mvc_AdminRouter::generate('core-view');
}
}

View file

@ -0,0 +1,109 @@
<?php
/**
* Lists all installed locales.
* WordPress decides what is "installed" based on presence of core translation files
*/
class Loco_admin_list_LocalesController extends Loco_mvc_AdminController {
/**
* {@inheritdoc}
*/
public function init(){
parent::init();
$this->enqueueStyle('locale');
}
/**
* {@inheritdoc}
*/
public function getHelpTabs(){
return array (
__('Overview','default') => $this->viewSnippet('tab-list-locales'),
);
}
/**
* {@inheritdoc}
*/
public function render(){
$this->set( 'title', __( 'Installed languages', 'loco-translate' ) );
$used = array();
$locales = array();
$api = new Loco_api_WordPressTranslations;
$active = get_locale();
// list which sites have each language as their WPLANG setting
if( $multisite = is_multisite() ){
$this->set('multisite',true);
/* @var WP_Site $site */
foreach( get_sites() as $site ){
$id = (int) $site->blog_id;
$tag = get_blog_option( $id, 'WPLANG') or $tag = 'en_US';
$name = get_blog_option( $id, 'blogname' );
$used[$tag][] = $name;
}
}
// else single site shows tick instead of site name
else {
$used[$active][] = '✓';
}
// add installed languages to file crawler
$finder = new Loco_package_Locale;
// Pull "installed" languages (including en_US)
foreach( $api->getInstalledCore() as $tag ){
$locale = Loco_Locale::parse($tag);
if( $locale->isValid() ){
$tag = (string) $locale;
$finder->addLocale($locale);
$args = array( 'locale' => $tag );
$locales[$tag] = new Loco_mvc_ViewParams( array(
'nfiles' => 0,
'time' => 0,
'lcode' => $tag,
'lname' => $locale->ensureName($api),
'lattr' => 'class="'.$locale->getIcon().'" lang="'.$locale->lang.'"',
'href' => Loco_mvc_AdminRouter::generate('lang-view',$args),
'used' => isset($used[$tag]) ? implode( ', ', $used[$tag] ) : ( $multisite ? '--' : '' ),
'active' => $active === $tag,
) );
}
}
$this->set('locales', $locales );
// Count up unique PO files
foreach( $finder->findLocaleFiles() as $file ){
if( preg_match('/(?:^|-)([_a-zA-Z]+).po$/', $file->basename(), $r ) ){
$locale = Loco_Locale::parse($r[1]);
if( $locale->isValid() ){
$tag = (string) $locale;
$locales[$tag]['nfiles']++;
$locales[$tag]['time'] = max( $locales[$tag]['time'], $file->modified() );
}
}
}
// POT files are in en_US locale
$tag = 'en_US';
foreach( $finder->findTemplateFiles() as $file ){
$locales[$tag]['nfiles']++;
$locales[$tag]['time'] = max( $locales[$tag]['time'], $file->modified() );
}
return $this->view( 'admin/list/locales' );
}
}

View file

@ -0,0 +1,31 @@
<?php
/**
* List all bundles of type "plugin"
* Route: loco-plugin
*/
class Loco_admin_list_PluginsController extends Loco_admin_list_BaseController {
public function render(){
$this->set( 'type', 'plugin' );
$this->set( 'title', __( 'Translate plugins', 'loco-translate' ) );
foreach( Loco_package_Plugin::get_plugins() as $handle => $data ){
try {
$bundle = Loco_package_Plugin::create( $handle );
$this->addBundle($bundle);
}
// @codeCoverageIgnoreStart
catch( Exception $e ){
$bundle = new Loco_package_Plugin( $handle, $handle );
$this->addBundle( $bundle );
}
// @codeCoverageIgnoreEnd
}
return parent::render();
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* List all bundles of type "theme"
* Route: loco-theme
*/
class Loco_admin_list_ThemesController extends Loco_admin_list_BaseController {
public function render(){
$this->set('type', 'theme' );
$this->set('title', __( 'Translate themes', 'loco-translate' ) );
/* @var $theme WP_Theme */
foreach( wp_get_themes() as $theme ){
$bundle = Loco_package_Theme::create( $theme->get_stylesheet() );
$this->addBundle( $bundle );
}
return parent::render();
}
}

View file

@ -0,0 +1,67 @@
<?php
/**
* Ajax "apis" route, for handing off Ajax requests to hooked API integrations.
*/
class Loco_ajax_ApisController extends Loco_mvc_AjaxController {
/**
* {@inheritdoc}
*/
public function render(){
$post = $this->validate();
// Fire an event so translation apis can register their hooks as lazily as possible
do_action('loco_api_ajax');
// API client id should be posted
$hook = (string) $post->hook;
// API client must be hooked in using loco_api_providers filter
// this normally filters on Loco_api_Providers::export() but should do the same with an empty array.
$config = null;
foreach( apply_filters('loco_api_providers', array() ) as $candidate ){
if( is_array($candidate) && array_key_exists('id',$candidate) && $candidate['id'] === $hook ){
$config = $candidate;
break;
}
}
if( is_null($config) ){
throw new Loco_error_Exception('API not registered: '.$hook );
}
// Get input texts to translate via registered hook. shouldn't be posted if empty.
$sources = $post->sources;
if( ! is_array($sources) || ! $sources ){
throw new Loco_error_Exception('Empty sources posted to '.$hook.' hook');
}
// The front end sends translations detected as HTML separately. This is to support common external apis.
// $isHtml = 'html' === $post->type;
// We need a locale too, which should be valid as it's the same one loaded into the front end.
$locale = Loco_Locale::parse( (string) $post->locale );
if( ! $locale->isValid() ){
throw new Loco_error_Exception('Invalid locale');
}
// Check if hook is registered, else sources will be returned as-is
$action = 'loco_api_translate_'.$hook;
if( ! has_filter($action) ){
throw new Loco_error_Exception('API not hooked. Use `add_filter('.var_export($action,1).',...)`');
}
// This is effectively a filter whereby the returned array should be a translation of the input array
// TODO might be useful for translation hooks to know the PO file this comes from
$targets = apply_filters( $action, $sources, $locale, $config );
if( count($targets) !== count($sources) ){
Loco_error_AdminNotices::warn('Number of translations does not match number of source strings');
}
// Response data doesn't need anything except the translations
$this->set('targets',$targets);
return parent::render();
}
}

View file

@ -0,0 +1,58 @@
<?php
/**
* Ajax "diff" route, for rendering PO/POT file diffs
*/
class Loco_ajax_DiffController extends Loco_mvc_AjaxController {
/**
* {@inheritdoc}
*/
public function render(){
$post = $this->validate();
// require x2 valid files for diffing
if( ! $post->lhs || ! $post->rhs ){
throw new InvalidArgumentException('Path parameters required');
}
$dir = loco_constant('WP_CONTENT_DIR');
$lhs = new Loco_fs_File( $post->lhs ); $lhs->normalize($dir);
$rhs = new Loco_fs_File( $post->rhs ); $rhs->normalize($dir);
// avoid diffing non Gettext source files
$exts = array_flip( array( 'pot', 'pot~', 'po', 'po~' ) );
/* @var $file Loco_fs_File */
foreach( array($lhs,$rhs) as $file ){
if( ! $file->exists() ){
throw new InvalidArgumentException('File paths must exist');
}
if( ! $file->underContentDirectory() ){
throw new InvalidArgumentException('Files must be under '.basename($dir) );
}
$ext = $file->extension();
if( ! isset($exts[$ext]) ){
throw new InvalidArgumentException('Disallowed file extension');
}
}
// OK to diff files as HTML table
$renderer = new Loco_output_DiffRenderer;
$emptysrc = $renderer->_startDiff().$renderer->_endDiff();
$tablesrc = $renderer->renderFiles( $rhs, $lhs );
if( $tablesrc === $emptysrc ){
// translators: Where %s is a file name
$message = __('Revisions are identical, you can delete %s','loco-translate');
$this->set( 'error', sprintf( $message, $rhs->basename() ) );
}
else {
$this->set( 'html', $tablesrc );
}
return parent::render();
}
}

View file

@ -0,0 +1,35 @@
<?php
/**
* Downloads a bundle configuration as XML or Json
*/
class Loco_ajax_DownloadConfController extends Loco_ajax_common_BundleController {
/**
* {@inheritdoc}
*/
public function render(){
$this->validate();
$bundle = $this->getBundle();
$file = new Loco_fs_File( $this->get('path') );
// TODO should we download axtual loco.xml file if bundle is configured from it?
//$file->normalize( $bundle->getDirectoryPath() );
//if( $file->exists() ){}
$writer = new Loco_config_BundleWriter($bundle);
switch( $file->extension() ){
case 'xml':
return $writer->toXml();
case 'json':
return json_encode( $writer->jsonSerialize() );
}
// @codeCoverageIgnoreStart
throw new Loco_error_Exception('Specify either XML or JSON file path');
}
}

View file

@ -0,0 +1,59 @@
<?php
/**
* Ajax "download" route, for outputting raw gettext file contents.
*/
class Loco_ajax_DownloadController extends Loco_mvc_AjaxController {
/**
* {@inheritdoc}
*/
public function render(){
$post = $this->validate();
// we need a path, but it may not need to exist
$file = new Loco_fs_File( $this->get('path') );
$file->normalize( loco_constant( 'WP_CONTENT_DIR') );
$is_binary = 'mo' === strtolower( $file->extension() );
// posted source must be clean and must parse as whatever the file extension claims to be
if( $raw = $post->source ){
// compile source if target is MO
if( $is_binary ) {
$raw = Loco_gettext_Data::fromSource($raw)->msgfmt();
}
}
// else file can be output directly if it exists.
// note that files on disk will not be parsed or manipulated. they will download strictly as-is
else if( $file->exists() ){
$raw = $file->getContents();
}
/*/ else if PO exists but MO doesn't, we can compile it on the fly
else if( ! $is_binary ){
}*/
else {
throw new Loco_error_Exception('File not found and no source posted');
}
// Observe UTF-8 BOM setting
if( ! $is_binary ){
$has_bom = "\xEF\xBB\xBF" === substr($raw,0,3);
$use_bom = (bool) Loco_data_Settings::get()->po_utf8_bom;
// only alter file if valid UTF-8. Deferring detection overhead until required
if( $has_bom !== $use_bom && 'UTF-8' === mb_detect_encoding( $raw, array('UTF-8','ISO-8859-1'), true ) ){
if( $use_bom ){
$raw = "\xEF\xBB\xBF".$raw; // prepend
}
else {
$raw = substr($raw,3); // strip bom
}
}
}
return $raw;
}
}

View file

@ -0,0 +1,151 @@
<?php
/**
* Ajax service that provides remote server authentication for file system *write* operations
*/
class Loco_ajax_FsConnectController extends Loco_mvc_AjaxController {
/**
* @var Loco_api_WordPressFileSystem
*/
private $api;
/**
* @param Loco_fs_File existing file path (must exist)
* @return bool
*/
private function authorizeDelete( Loco_fs_File $file ){
$files = new Loco_fs_Siblings($file);
// require remote authentication if at least one dependant file is not deletable directly
foreach( $files->expand() as $file ){
if( ! $this->api->authorizeDelete($file) ){
return false;
}
}
// else no dependants failed deletable test
return true;
}
/**
* @param Loco_fs_File file being moved (must exist)
* @param Loco_fs_File target path (should not exist)
* @return bool
*/
private function authorizeMove( Loco_fs_File $source, Loco_fs_File $target = null ){
return $this->api->authorizeMove($source,$target);
}
/**
* @param Loco_fs_File new file path (should not exist)
* @return bool
*/
private function authorizeCreate( Loco_fs_File $file ){
return $this->api->authorizeCreate($file);
}
/**
* @return bool
*/
private function authorizeUpdate( Loco_fs_File $file ){
if( ! $this->api->authorizeUpdate($file) ){
return false;
}
// if backups are enabled, we need to be able to create new files too (i.e. update parent directory)
if( Loco_data_Settings::get()->num_backups && ! $this->api->authorizeCopy($file) ){
return false;
}
// updating file may also recompile binary, which may or may not exist
$files = new Loco_fs_Siblings( $file );
if( $file = $files->getBinary() ){
return $this->api->authorizeSave($file);
}
// else no dependants to update
return true;
}
/**
* {@inheritdoc}
*/
public function render(){
// establish operation being authorized (create,delete,etc..)
$post = $this->validate();
$type = $post->auth;
$func = 'authorize'.ucfirst($type);
$auth = array( $this, $func );
if( ! is_callable($auth) ){
throw new Loco_error_Exception('Unexpected file operation');
}
// all auth methods require at least one file argument
$file = new Loco_fs_File( $post->path );
$base = loco_constant('WP_CONTENT_DIR');
$file->normalize($base);
$args = array($file);
// some auth methods also require a destination/target (move,copy,etc..)
if( $dest = $post->dest ){
$file = new Loco_fs_File($dest);
$file->normalize($base);
$args[] = $file;
}
// call auth method and respond with status and prompt HTML if connect required
try {
$this->api = new Loco_api_WordPressFileSystem;
if( call_user_func_array($auth,$args) ){
$this->set( 'authed', true );
$this->set( 'valid', $this->api->getOutputCredentials() );
$this->set( 'creds', $this->api->getInputCredentials() );
$this->set( 'method', $this->api->getFileSystem()->method );
$this->set( 'success', __('Connected to remote file system','loco-translate') );
// warning when writing to this location is risky (overwrites during wp update)
if( Loco_data_Settings::get()->fs_protect && $file->getUpdateType() ){
if( 'create' === $type ){
$message = __('This file may be overwritten or deleted when you update WordPress','loco-translate');
}
else if( 'delete' === $type ){
$message = __('This directory is managed by WordPress, be careful what you delete','loco-translate');
}
else if( 'move' === $type ){
$message = __('This directory is managed by WordPress. Removed files may be restored during updates','loco-translate');
}
else {
$message = __('Changes to this file may be overwritten or deleted when you update WordPress','loco-translate');
}
$this->set('warning',$message);
}
}
else if( $html = $this->api->getForm() ){
$this->set( 'authed', false );
$this->set( 'prompt', $html );
// supporting text based on file operation type explains why auth is required
if( 'create' === $type ){
$message = __('Creating this file requires permission','loco-translate');
}
else if( 'delete' === $type ){
$message = __('Deleting this file requires permission','loco-translate');
}
else if( 'move' === $type ){
$message = __('This move operation requires permission','loco-translate');
}
else {
$message = __('Saving this file requires permission','loco-translate');
}
// message is printed before default text, so needs delimiting.
$this->set('message',$message.'.');
}
else {
throw new Loco_error_Exception('Failed to get credentials form');
}
}
catch( Loco_error_WriteException $e ){
$this->set('authed', false );
$this->set('reason', $e->getMessage() );
}
return parent::render();
}
}

View file

@ -0,0 +1,198 @@
<?php
/**
* Ajax service that returns source code for a given file system reference
* Currently this is only PHP, but could theoretically be any file type.
*/
class Loco_ajax_FsReferenceController extends Loco_ajax_common_BundleController {
/**
* @param string
* @return Loco_fs_File
*/
private function findSourceFile( $refpath ){
/*/ absolute file path means no search paths required
if( Loco_fs_File::abs($refpath) ){
$srcfile = new Loco_fs_File( $refpath );
if( $srcfile->exists() ){
return $srcfile;
}
}*/
// reference may be resolvable via referencing PO file's location
$pofile = new Loco_fs_File( $this->get('path') );
$pofile->normalize( loco_constant('WP_CONTENT_DIR') );
if( ! $pofile->exists() ){
throw new InvalidArgumentException('PO/POT file required to resolve reference');
}
$search = new Loco_gettext_SearchPaths;
$search->init($pofile);
if( $srcfile = $search->match($refpath) ){
return $srcfile;
}
// check against PO file location when no search paths or search paths failed
$srcfile = new Loco_fs_File($refpath);
$srcfile->normalize( $pofile->dirname() );
if( $srcfile->exists() ){
return $srcfile;
}
// reference may be resolvable via known project roots
try {
$bundle = $this->getBundle();
// Loco extractions will always be relative to bundle root
$srcfile = new Loco_fs_File( $refpath );
$srcfile->normalize( $bundle->getDirectoryPath() );
if( $srcfile->exists() ){
return $srcfile;
}
// check relative to parent theme root
if( $bundle->isTheme() && ( $parent = $bundle->getParent() ) ){
$srcfile = new Loco_fs_File( $refpath );
$srcfile->normalize( $parent->getDirectoryPath() );
if( $srcfile->exists() ){
return $srcfile;
}
}
// final attempt - search all project source roots
// TODO is there too large a risk of false positives? especially with files like index.php
/* @var $root Loco_fs_Directory */
/*foreach( $this->getProject($bundle)->getConfiguredSources() as $root ){
if( $root->isDirectory() ){
$srcfile = new Loco_fs_File( $refpath );
$srcfile->normalize( $root->getPath() );
if( $srcfile->exists() ){
return $srcfile;
}
}
}*/
}
catch( Loco_error_Exception $e ){
// permitted for there to be no bundle or project when viewing orphaned file
}
throw new Loco_error_Exception( sprintf('Failed to find source file matching "%s"',$refpath) );
}
/**
* {@inheritdoc}
*/
public function render(){
$post = $this->validate();
// at the very least we need a reference to examine
if( ! $post->has('ref') ){
throw new InvalidArgumentException('ref parameter required');
}
// reference must parse as <path>:<line>
$ref = $post->ref;
if( ! preg_match('/^(.+):(\\d+)$/', $ref, $r ) ){
throw new InvalidArgumentException('Invalid file reference, '.$ref );
}
// find file or fail
list( , $refpath, $refline ) = $r;
$srcfile = $this->findSourceFile($refpath);
// deny access to sensitive files
if( 'wp-config.php' === $srcfile->basename() ){
throw new InvalidArgumentException('File access disallowed');
}
// validate allowed source file types
$conf = Loco_data_Settings::get();
$ext = strtolower( $srcfile->extension() );
$allow = array_merge( array('php','js'), $conf->php_alias, $conf->jsx_alias );
if( ! in_array($ext,$allow,true) ){
throw new InvalidArgumentException('File extension disallowed, '.$ext );
}
// get file type from registered file extensions:
$type = $conf->ext2type( $ext );
$this->set('type', $type );
$this->set('line', (int) $refline );
$this->set('path', $srcfile->getRelativePath( loco_constant('WP_CONTENT_DIR') ) );
// source code will be HTML-tokenized into multiple lines
$code = array();
// observe the same size limits for source highlighting as for string extraction as tokenizing will use the same amount of juice
$maxbytes = wp_convert_hr_to_bytes( $conf->max_php_size );
// tokenizers require gettext utilities, easiest just to ping the extraction library
if( ! class_exists('Loco_gettext_Extraction',true) ){
throw new RuntimeException('Failed to load tokenizers'); // @codeCoverageIgnore
}
// PHP is the most likely format.
if( 'php' === $type && ( $srcfile->size() <= $maxbytes ) && loco_check_extension('tokenizer') ) {
$tokens = new LocoPHPTokens( token_get_all( $srcfile->getContents() ) );
}
else if( 'js' === $type ){
$tokens = new LocoJsTokens( $srcfile->getContents() );
}
else {
$tokens = null;
}
// highlighting on back end because tokenizer provides more control than highlight.js
if( $tokens instanceof LocoTokensInterface ){
$thisline = 1;
while( $tok = $tokens->advance() ){
if( is_array($tok) ){
// line numbers added in PHP 5.2.2 - WordPress minimum is 5.2.4
list( $t, $str, $startline ) = $tok;
$clss = token_name($t);
// tokens can span multiple lines (whitespace/html/comments)
$lines = preg_split('/\\R/', $str );
}
else {
// scalar symbol will always start on the line that the previous token ended on
$clss = 'T_NONE';
$lines = array( $tok );
$startline = $thisline;
}
// token can span multiple lines, so include only bytes on required line[s]
foreach( $lines as $i => $line ){
$thisline = $startline + $i;
$html = '<code class="'.$clss.'">'.htmlentities($line,ENT_COMPAT,'UTF-8').'</code>';
// append highlighted token to current line
$j = $thisline - 1;
if( isset($code[$j]) ){
$code[$j] .= $html;
}
else {
$code[$j] = $html;
}
}
}
}
// permit limited other file types, but without back end highlighting
else if( 'js' === $type || 'twig' === $type || 'php' === $type ){
foreach( preg_split( '/\\R/u', $srcfile->getContents() ) as $line ){
$code[] = '<code>'.htmlentities($line,ENT_COMPAT,'UTF-8').'</code>';
}
}
else {
throw new Loco_error_Exception( sprintf('%s source view not supported', $type) ); // @codeCoverageIgnore
}
if( ! isset($code[$refline-1]) ){
throw new Loco_error_Exception( sprintf('Line %u not in source file', $refline) );
}
$this->set( 'code', $code );
return parent::render();
}
}

Some files were not shown because too many files have changed in this diff Show more