Commit 8e398e6d by Pham Huy

Merge branch 'dev'

parents 986f4923 4f28f38a
This source diff could not be displayed because it is too large. You can view the blob instead.
{"translation-revision-date":"2019-11-21 10:43:45+0000","generator":"GlotPress\/2.4.0-alpha","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","plural-forms":"nplurals=1; plural=0;","lang":"vi_VN"},"%s item selected":["\u0111\u00e3 ch\u1ecdn %s item"]}},"comment":{"reference":"wp-includes\/js\/media-views.js"}}
\ No newline at end of file
{"translation-revision-date":"2019-11-21 10:43:45+0000","generator":"GlotPress\/2.4.0-alpha","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","plural-forms":"nplurals=1; plural=0;","lang":"vi_VN"},"Select or Upload Media":["Ch\u1ecdn ho\u1eb7c t\u1ea3i l\u00ean ph\u01b0\u01a1ng ti\u1ec7n"],"Error while uploading file %s to the media library.":["C\u00f3 l\u1ed7i x\u1ea3y ra khi t\u1ea3i file %s l\u00ean th\u01b0 vi\u1ec7n."],"This file is empty.":["T\u1ec7p tin n\u00e0y tr\u1ed1ng."],"Sorry, this file type is not supported here.":["Xin l\u1ed7i, lo\u1ea1i t\u1ec7p tin n\u00e0y kh\u00f4ng \u0111\u01b0\u1ee3c h\u1ed7 tr\u1ee3."],"Sorry, this file type is not permitted for security reasons.":["R\u1ea5t ti\u1ebfc, \u0111\u1ecbnh d\u1ea1ng t\u1eadp tin n\u00e0y kh\u00f4ng \u0111\u01b0\u1ee3c cho ph\u00e9p v\u00ec l\u00fd do b\u1ea3o m\u1eadt."],"This file exceeds the maximum upload size for this site.":["T\u1eadp tin v\u01b0\u1ee3t qu\u00e1 k\u00edch th\u01b0\u1edbc t\u1ed1i \u0111a \u0111\u01b0\u1ee3c ph\u00e9p t\u1ea3i l\u00ean trang n\u00e0y."]}},"comment":{"reference":"wp-includes\/js\/dist\/media-utils.js"}}
\ No newline at end of file
{"translation-revision-date":"2019-11-21 10:43:45+0000","generator":"GlotPress\/2.4.0-alpha","domain":"messages","locale_data":{"messages":{"":{"domain":"messages","plural-forms":"nplurals=1; plural=0;","lang":"vi_VN"},"Block rendered as empty.":["Kh\u1ed1i \u0111\u00e3 \u0111\u01b0\u1ee3c render r\u1ed7ng."],"Error loading block: %s":["L\u1ed7i n\u1ea1p kh\u1ed1i: %s"]}},"comment":{"reference":"wp-includes\/js\/dist\/server-side-render.js"}}
\ No newline at end of file
# 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.
<?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; }
<?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( is_array($value) ){ $nodes = $value; } else if( $value instanceof DOMDocument ){ $nodes = array( $value->documentElement ); } else if( $value instanceof DOMNodeList ){ $nodes = array(); foreach( $value as $node ){ $nodes[] = $node; } } else { $value = self::parse( $value ); $nodes = array( $value->documentElement ); } 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 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; } }
<?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');
<?php
/**
* Compiled data. Do not edit.
*/
return array('af'=>array(0=>'Afrikaans',1=>'Afrikaans'),'ar'=>array(0=>'Arabic',1=>'العربية'),'ary'=>array(0=>'Moroccan Arabic',1=>'العربية المغربية'),'as'=>array(0=>'Assamese',1=>'অসমীয়া'),'azb'=>array(0=>'South Azerbaijani',1=>'گؤنئی آذربایجان'),'az'=>array(0=>'Azerbaijani',1=>'Azərbaycan dili'),'bel'=>array(0=>'Belarusian',1=>'Беларуская мова'),'bg_BG'=>array(0=>'Bulgarian',1=>'Български'),'bn_BD'=>array(0=>'Bengali (Bangladesh)',1=>'বাংলা'),'bo'=>array(0=>'Tibetan',1=>'བོད་ཡིག'),'bs_BA'=>array(0=>'Bosnian',1=>'Bosanski'),'ca'=>array(0=>'Catalan',1=>'Català'),'ceb'=>array(0=>'Cebuano',1=>'Cebuano'),'cs_CZ'=>array(0=>'Czech',1=>'Čeština'),'cy'=>array(0=>'Welsh',1=>'Cymraeg'),'da_DK'=>array(0=>'Danish',1=>'Dansk'),'de_CH'=>array(0=>'German (Switzerland)',1=>'Deutsch (Schweiz)'),'de_DE'=>array(0=>'German',1=>'Deutsch'),'de_DE_formal'=>array(0=>'German (Formal)',1=>'Deutsch (Sie)'),'de_CH_informal'=>array(0=>'German (Switzerland, Informal)',1=>'Deutsch (Schweiz, Du)'),'dzo'=>array(0=>'Dzongkha',1=>'རྫོང་ཁ'),'el'=>array(0=>'Greek',1=>'Ελληνικά'),'en_AU'=>array(0=>'English (Australia)',1=>'English (Australia)'),'en_ZA'=>array(0=>'English (South Africa)',1=>'English (South Africa)'),'en_CA'=>array(0=>'English (Canada)',1=>'English (Canada)'),'en_GB'=>array(0=>'English (UK)',1=>'English (UK)'),'en_NZ'=>array(0=>'English (New Zealand)',1=>'English (New Zealand)'),'eo'=>array(0=>'Esperanto',1=>'Esperanto'),'es_ES'=>array(0=>'Spanish (Spain)',1=>'Español'),'es_CO'=>array(0=>'Spanish (Colombia)',1=>'Español de Colombia'),'es_VE'=>array(0=>'Spanish (Venezuela)',1=>'Español de Venezuela'),'es_CL'=>array(0=>'Spanish (Chile)',1=>'Español de Chile'),'es_MX'=>array(0=>'Spanish (Mexico)',1=>'Español de México'),'es_CR'=>array(0=>'Spanish (Costa Rica)',1=>'Español de Costa Rica'),'es_PE'=>array(0=>'Spanish (Peru)',1=>'Español de Perú'),'es_GT'=>array(0=>'Spanish (Guatemala)',1=>'Español de Guatemala'),'es_AR'=>array(0=>'Spanish (Argentina)',1=>'Español de Argentina'),'et'=>array(0=>'Estonian',1=>'Eesti'),'eu'=>array(0=>'Basque',1=>'Euskara'),'fa_IR'=>array(0=>'Persian',1=>'فارسی'),'fi'=>array(0=>'Finnish',1=>'Suomi'),'fr_BE'=>array(0=>'French (Belgium)',1=>'Français de Belgique'),'fr_FR'=>array(0=>'French (France)',1=>'Français'),'fr_CA'=>array(0=>'French (Canada)',1=>'Français du Canada'),'fur'=>array(0=>'Friulian',1=>'Friulian'),'gd'=>array(0=>'Scottish Gaelic',1=>'Gàidhlig'),'gl_ES'=>array(0=>'Galician',1=>'Galego'),'gu'=>array(0=>'Gujarati',1=>'ગુજરાતી'),'haz'=>array(0=>'Hazaragi',1=>'هزاره گی'),'he_IL'=>array(0=>'Hebrew',1=>'עִבְרִית'),'hi_IN'=>array(0=>'Hindi',1=>'हिन्दी'),'hr'=>array(0=>'Croatian',1=>'Hrvatski'),'hu_HU'=>array(0=>'Hungarian',1=>'Magyar'),'hy'=>array(0=>'Armenian',1=>'Հայերեն'),'id_ID'=>array(0=>'Indonesian',1=>'Bahasa Indonesia'),'is_IS'=>array(0=>'Icelandic',1=>'Íslenska'),'it_IT'=>array(0=>'Italian',1=>'Italiano'),'ja'=>array(0=>'Japanese',1=>'日本語'),'jv_ID'=>array(0=>'Javanese',1=>'Basa Jawa'),'ka_GE'=>array(0=>'Georgian',1=>'ქართული'),'kab'=>array(0=>'Kabyle',1=>'Taqbaylit'),'kk'=>array(0=>'Kazakh',1=>'Қазақ тілі'),'km'=>array(0=>'Khmer',1=>'ភាសាខ្មែរ'),'ko_KR'=>array(0=>'Korean',1=>'한국어'),'ckb'=>array(0=>'Kurdish (Sorani)',1=>'كوردی‎'),'lo'=>array(0=>'Lao',1=>'ພາສາລາວ'),'lt_LT'=>array(0=>'Lithuanian',1=>'Lietuvių kalba'),'lv'=>array(0=>'Latvian',1=>'Latviešu valoda'),'mk_MK'=>array(0=>'Macedonian',1=>'Македонски јазик'),'ml_IN'=>array(0=>'Malayalam',1=>'മലയാളം'),'mn'=>array(0=>'Mongolian',1=>'Монгол'),'mr'=>array(0=>'Marathi',1=>'मराठी'),'ms_MY'=>array(0=>'Malay',1=>'Bahasa Melayu'),'my_MM'=>array(0=>'Myanmar (Burmese)',1=>'ဗမာစာ'),'nb_NO'=>array(0=>'Norwegian (Bokmål)',1=>'Norsk bokmål'),'ne_NP'=>array(0=>'Nepali',1=>'नेपाली'),'nl_NL_formal'=>array(0=>'Dutch (Formal)',1=>'Nederlands (Formeel)'),'nl_BE'=>array(0=>'Dutch (Belgium)',1=>'Nederlands (België)'),'nl_NL'=>array(0=>'Dutch',1=>'Nederlands'),'nn_NO'=>array(0=>'Norwegian (Nynorsk)',1=>'Norsk nynorsk'),'oci'=>array(0=>'Occitan',1=>'Occitan'),'pa_IN'=>array(0=>'Punjabi',1=>'ਪੰਜਾਬੀ'),'pl_PL'=>array(0=>'Polish',1=>'Polski'),'ps'=>array(0=>'Pashto',1=>'پښتو'),'pt_PT'=>array(0=>'Portuguese (Portugal)',1=>'Português'),'pt_BR'=>array(0=>'Portuguese (Brazil)',1=>'Português do Brasil'),'pt_PT_ao90'=>array(0=>'Portuguese (Portugal, AO90)',1=>'Português (AO90)'),'rhg'=>array(0=>'Rohingya',1=>'Ruáinga'),'ro_RO'=>array(0=>'Romanian',1=>'Română'),'ru_RU'=>array(0=>'Russian',1=>'Русский'),'sah'=>array(0=>'Sakha',1=>'Сахалыы'),'si_LK'=>array(0=>'Sinhala',1=>'සිංහල'),'sk_SK'=>array(0=>'Slovak',1=>'Slovenčina'),'sl_SI'=>array(0=>'Slovenian',1=>'Slovenščina'),'sq'=>array(0=>'Albanian',1=>'Shqip'),'sr_RS'=>array(0=>'Serbian',1=>'Српски језик'),'sv_SE'=>array(0=>'Swedish',1=>'Svenska'),'szl'=>array(0=>'Silesian',1=>'Ślōnskŏ gŏdka'),'ta_IN'=>array(0=>'Tamil',1=>'தமிழ்'),'te'=>array(0=>'Telugu',1=>'తెలుగు'),'th'=>array(0=>'Thai',1=>'ไทย'),'tl'=>array(0=>'Tagalog',1=>'Tagalog'),'tr_TR'=>array(0=>'Turkish',1=>'Türkçe'),'tt_RU'=>array(0=>'Tatar',1=>'Татар теле'),'tah'=>array(0=>'Tahitian',1=>'Reo Tahiti'),'ug_CN'=>array(0=>'Uighur',1=>'ئۇيغۇرچە'),'uk'=>array(0=>'Ukrainian',1=>'Українська'),'ur'=>array(0=>'Urdu',1=>'اردو'),'uz_UZ'=>array(0=>'Uzbek',1=>'O‘zbekcha'),'vi'=>array(0=>'Vietnamese',1=>'Tiếng Việt'),'zh_HK'=>array(0=>'Chinese (Hong Kong)',1=>'香港中文版 '),'zh_CN'=>array(0=>'Chinese (China)',1=>'简体中文'),'zh_TW'=>array(0=>'Chinese (Taiwan)',1=>'繁體中文'));
<?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'))));
<?php
/**
* Compiled data. Do not edit.
*/
return array('AD'=>'Andorra','AE'=>'United Arab Emirates','AF'=>'Afghanistan','AG'=>'Antigua and Barbuda','AI'=>'Anguilla','AL'=>'Albania','AM'=>'Armenia','AO'=>'Angola','AQ'=>'Antarctica','AR'=>'Argentina','AS'=>'American Samoa','AT'=>'Austria','AU'=>'Australia','AW'=>'Aruba','AX'=>'Åland Islands','AZ'=>'Azerbaijan','BA'=>'Bosnia and Herzegovina','BB'=>'Barbados','BD'=>'Bangladesh','BE'=>'Belgium','BF'=>'Burkina Faso','BG'=>'Bulgaria','BH'=>'Bahrain','BI'=>'Burundi','BJ'=>'Benin','BL'=>'Saint Barthélemy','BM'=>'Bermuda','BN'=>'Brunei Darussalam','BO'=>'Bolivia','BQ'=>'Bonaire, Sint Eustatius and Saba','BR'=>'Brazil','BS'=>'Bahamas','BT'=>'Bhutan','BV'=>'Bouvet Island','BW'=>'Botswana','BY'=>'Belarus','BZ'=>'Belize','CA'=>'Canada','CC'=>'Cocos (Keeling) Islands','CD'=>'The Democratic Republic of the Congo','CF'=>'Central African Republic','CG'=>'Congo','CH'=>'Switzerland','CI'=>'Côte d\'Ivoire','CK'=>'Cook Islands','CL'=>'Chile','CM'=>'Cameroon','CN'=>'China','CO'=>'Colombia','CR'=>'Costa Rica','CU'=>'Cuba','CV'=>'Cabo Verde; Cape Verde','CW'=>'Curaçao','CX'=>'Christmas Island','CY'=>'Cyprus','CZ'=>'Czech Republic','DE'=>'Germany','DJ'=>'Djibouti','DK'=>'Denmark','DM'=>'Dominica','DO'=>'Dominican Republic','DZ'=>'Algeria','EC'=>'Ecuador','EE'=>'Estonia','EG'=>'Egypt','EH'=>'Western Sahara','ER'=>'Eritrea','ES'=>'Spain','ET'=>'Ethiopia','FI'=>'Finland','FJ'=>'Fiji','FK'=>'Falkland Islands (Malvinas)','FM'=>'Federated States of Micronesia','FO'=>'Faroe Islands','FR'=>'France','GA'=>'Gabon','GB'=>'United Kingdom','GD'=>'Grenada','GE'=>'Georgia','GF'=>'French Guiana','GG'=>'Guernsey','GH'=>'Ghana','GI'=>'Gibraltar','GL'=>'Greenland','GM'=>'Gambia','GN'=>'Guinea','GP'=>'Guadeloupe','GQ'=>'Equatorial Guinea','GR'=>'Greece','GS'=>'South Georgia and the South Sandwich Islands','GT'=>'Guatemala','GU'=>'Guam','GW'=>'Guinea-Bissau','GY'=>'Guyana','HK'=>'Hong Kong','HM'=>'Heard Island and McDonald Islands','HN'=>'Honduras','HR'=>'Croatia','HT'=>'Haiti','HU'=>'Hungary','ID'=>'Indonesia','IE'=>'Ireland','IL'=>'Israel','IM'=>'Isle of Man','IN'=>'India','IO'=>'British Indian Ocean Territory','IQ'=>'Iraq','IR'=>'Islamic Republic of Iran','IS'=>'Iceland','IT'=>'Italy','JE'=>'Jersey','JM'=>'Jamaica','JO'=>'Jordan','JP'=>'Japan','KE'=>'Kenya','KG'=>'Kyrgyzstan','KH'=>'Cambodia','KI'=>'Kiribati','KM'=>'Comoros','KN'=>'Saint Kitts and Nevis','KP'=>'Democratic People\'s Republic of Korea','KR'=>'Republic of Korea','KW'=>'Kuwait','KY'=>'Cayman Islands','KZ'=>'Kazakhstan','LA'=>'Lao People\'s Democratic Republic','LB'=>'Lebanon','LC'=>'Saint Lucia','LI'=>'Liechtenstein','LK'=>'Sri Lanka','LR'=>'Liberia','LS'=>'Lesotho','LT'=>'Lithuania','LU'=>'Luxembourg','LV'=>'Latvia','LY'=>'Libya','MA'=>'Morocco','MC'=>'Monaco','MD'=>'Moldova','ME'=>'Montenegro','MF'=>'Saint Martin (French part)','MG'=>'Madagascar','MH'=>'Marshall Islands','MK'=>'The Former Yugoslav Republic of Macedonia','ML'=>'Mali','MM'=>'Myanmar','MN'=>'Mongolia','MO'=>'Macao','MP'=>'Northern Mariana Islands','MQ'=>'Martinique','MR'=>'Mauritania','MS'=>'Montserrat','MT'=>'Malta','MU'=>'Mauritius','MV'=>'Maldives','MW'=>'Malawi','MX'=>'Mexico','MY'=>'Malaysia','MZ'=>'Mozambique','NA'=>'Namibia','NC'=>'New Caledonia','NE'=>'Niger','NF'=>'Norfolk Island','NG'=>'Nigeria','NI'=>'Nicaragua','NL'=>'Netherlands','NO'=>'Norway','NP'=>'Nepal','NR'=>'Nauru','NU'=>'Niue','NZ'=>'New Zealand','OM'=>'Oman','PA'=>'Panama','PE'=>'Peru','PF'=>'French Polynesia','PG'=>'Papua New Guinea','PH'=>'Philippines','PK'=>'Pakistan','PL'=>'Poland','PM'=>'Saint Pierre and Miquelon','PN'=>'Pitcairn','PR'=>'Puerto Rico','PS'=>'State of Palestine','PT'=>'Portugal','PW'=>'Palau','PY'=>'Paraguay','QA'=>'Qatar','RE'=>'Réunion','RO'=>'Romania','RS'=>'Serbia','RU'=>'Russian Federation','RW'=>'Rwanda','SA'=>'Saudi Arabia','SB'=>'Solomon Islands','SC'=>'Seychelles','SD'=>'Sudan','SE'=>'Sweden','SG'=>'Singapore','SH'=>'Saint Helena, Ascension and Tristan da Cunha','SI'=>'Slovenia','SJ'=>'Svalbard and Jan Mayen','SK'=>'Slovakia','SL'=>'Sierra Leone','SM'=>'San Marino','SN'=>'Senegal','SO'=>'Somalia','SR'=>'Suriname','SS'=>'South Sudan','ST'=>'Sao Tome and Principe','SV'=>'El Salvador','SX'=>'Sint Maarten (Dutch part)','SY'=>'Syrian Arab Republic','SZ'=>'Swaziland','TC'=>'Turks and Caicos Islands','TD'=>'Chad','TF'=>'French Southern Territories','TG'=>'Togo','TH'=>'Thailand','TJ'=>'Tajikistan','TK'=>'Tokelau','TL'=>'Timor-Leste','TM'=>'Turkmenistan','TN'=>'Tunisia','TO'=>'Tonga','TR'=>'Turkey','TT'=>'Trinidad and Tobago','TV'=>'Tuvalu','TW'=>'Taiwan','TZ'=>'United Republic of Tanzania','UA'=>'Ukraine','UG'=>'Uganda','UM'=>'United States Minor Outlying Islands','US'=>'United States','UY'=>'Uruguay','UZ'=>'Uzbekistan','VA'=>'Holy See (Vatican City State)','VC'=>'Saint Vincent and the Grenadines','VE'=>'Venezuela','VG'=>'British Virgin Islands','VI'=>'U.S. Virgin Islands','VN'=>'Viet Nam','VU'=>'Vanuatu','WF'=>'Wallis and Futuna','WS'=>'Samoa','YE'=>'Yemen','YT'=>'Mayotte','ZA'=>'South Africa','ZM'=>'Zambia','ZW'=>'Zimbabwe','ZZ'=>'Private use');
<?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.3.1
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;
}
// legacy plugin should not be installed at the same time
if( function_exists('loco_require') ){
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.3.1';
}
/**
* 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( ! isset($name{20}) && file_exists( $path = loco_plugin_root().'/src/compat/'.$name.'.php') ){
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;
}
<?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>
form.loco-filter{float:right}@media only screen and (max-width: 1024px){table.wp-list-table a.row-title{max-width:100px}}
\ No newline at end of file
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}
\ No newline at end of file
#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}
\ No newline at end of file
#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}
\ No newline at end of file
#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.3.1) 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}
\ No newline at end of file
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}
\ No newline at end of file
.js #loco.wrap .loco-loading{min-height:100px;background:#fff url(../img/spin-modal.gif?v=2.3.1) 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}.loco-modal{min-width:90% !important;min-height:100px}#loco-po-ref ol li{color:#aaa;margin:0;white-space:pre;padding:0 0 0 1em;font:normal 12px/17px Consolas,Monaco,monospace;background:transparent;border-left:1px solid #eee}#loco-po-ref ol li code{margin:0;padding:0;display:inline;background:inherit}#loco-po-ref ol li.highlighted{color:#666;background-color:#f8eec7}#loco-po-ref ol li.highlighted code.T_CONSTANT_ENCAPSED_STRING{color:#c931c7}
\ No newline at end of file
.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.3.1) 0 0 no-repeat !important}
.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.3.1) 0 0 no-repeat !important}
.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.3.1) 0 0 no-repeat !important}
.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.3.1) 0 0 no-repeat !important}
.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.3.1) 0 0 no-repeat !important}
.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.3.1) 0 0 no-repeat !important}
.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.3.1) 0 0 no-repeat !important}
/**
* Script for bundle configuration screen
*/
!function( window, document, $ ){
// utility for scrolling
function elementTop( el, ancestor ){
var y = el.offsetTop;
while( ( el = el.offsetParent ) && el !== ancestor ){
y += el.offsetTop;
}
return y;
}
// form duplicator for adding another project
function addProject(){
var $divs = $('#loco-conf > div'),
$copy = $divs.eq(0).clone(),
index = $divs.length,
id = 'loco-conf-'+index,
ns = '['+index+']'
;
function clearField( i, input ){
var name = input.name.replace('[0]',ns);
$(input).attr('name', name ).val('');
}
$copy.attr('id', 'loco-conf-'+index );
$copy.find('input').each( clearField );
$copy.find('textarea').each( clearField );
//$copy.find('div.notice').remove();
// TODO translations of this:
$copy.find('h2').eq(0).html('New set <span>(untitled)</span>');
$copy.insertBefore('#loco-form-foot');
createClickRemove( $copy.find('a.icon-del'), index );
// scroll to $copy
$copy.hide().slideDown( 500 );
$('html, body').animate( { scrollTop: elementTop($copy[0]) }, 500 );
}
function createClickRemove( $el, index ){
return $el.click( function(event){
event.preventDefault();
delProject( index );
return false;
} );
}
// remove whole set from form
function delProject( index ){
var $div = $('#loco-conf-'+index),
$fld = $div.find('input[name="conf['+index+'][removed]"]')
;
// setting removed flag saves having to re-index all sets. back end will ignore it.
$fld.val('1');
$div.slideUp( 500, function(){ $(this).hide().find('table').remove(); } );
}
// enable project removal from initial blocks
$('#loco-conf > div').each( function( index, div ){
createClickRemove( $(div).find('a.icon-del'), index );
} );
// enable project addition via button in footer
$('#loco-add-butt').attr('disabled',false).click( function(event){
event.preventDefault();
addProject();
return false;
} );
}( window, document, jQuery );
\ No newline at end of file
/**
* Run Javascript diagnostics
*/
!function( loco, $ ){
// check tick symbol is readable by JavaScript as UTF-8
// E2\x9C\x93 => 0x2713
var span = $('#loco-utf8-check'),
tick = span[0].textContent
;
if( 1 !== tick.length || 0x2713 !== tick.charCodeAt(0) ){
loco.notices.warn("This page has a problem rendering UTF-8").stick();
}
// show custom endpoint if set in global
if( window.ajaxurl ){
$('#loco-ajax-url').text( window.ajaxurl );
}
// check UTF-8 passes to Ajax and back without a problem
function onAjaxTestPass( data, status, xhr ){
if( data && data.ping ){
$('#loco-ajax-check').text( data.ping );
}
else {
onAjaxTestFail( xhr, status, data && data.error && data.error.message );
}
}
function onAjaxTestFail( xhr, error, message ){
if( 'success' !== error ){
message = loco.ajax.parse( loco.ajax.strip( xhr.responseText ) );
}
$('#loco-ajax-check').text( 'FAILED: '+message ).addClass('loco-danger');
}
loco.ajax.post( 'ping', {echo:'\u039F\u039A \u2713'}, onAjaxTestPass, onAjaxTestFail );
}( window.locoScope, window.jQuery );
\ No newline at end of file
/**
* Script for file delete operation
*/
!function( window, document, $ ){
var fsHook = document.getElementById('loco-fs'),
elForm = document.getElementById('loco-del')
;
if( fsHook && elForm ){
window.locoScope.fs.init(fsHook).setForm(elForm);
}
}( window, document, jQuery );
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
/**
* Script for file move operation
*/
!function( window, document, $ ){
var fsConn,
destPath,
fsHook = document.getElementById('loco-fs'),
elForm = document.getElementById('loco-move'),
origPath = elForm.path.value
;
function setFormDisabled( disabled ){
$(elForm).find('button.button-primary').each( function(i,button){
button.disabled = disabled;
} );
}
function onFsConnect( valid ){
setFormDisabled( ! ( valid && destPath ) );
}
function validate(event){
var field = event.target||{}, value;
if( 'dest' === field.name && ( field.checked || 'text' === field.type ) ){
value = field.value;
if( value && value !== destPath ){
destPath = value;
setFormDisabled(true);
// check chosen target permissions
if( origPath !== value ){
fsHook.dest.value = value;
fsConn.connect();
}
}
}
}
function process( event ){
if( destPath ){
return true;
}
event.preventDefault();
return false;
}
if( fsHook && elForm ){
fsConn = window.locoScope.fs.init(fsHook).setForm(elForm).listen(onFsConnect);
$(elForm).change(validate).submit(process);
}
}( window, document, jQuery );
/**
* Script for PO/POT file diff renderer and roll back function
*/
!function( window, document, $ ){
var xhr,
cache = [],
conf = window.locoConf,
loco = window.locoScope,
revOffset = 0,
maxOffset = conf.paths.length - 2,
elRoot = document.getElementById('loco-ui'),
fsHook = document.getElementById('loco-fs'),
elForm = elRoot.getElementsByTagName('form').item(0),
buttons = elRoot.getElementsByTagName('button'),
$metas = $(elRoot).find('div.diff-meta'),
prevBut = buttons.item(0),
nextBut = buttons.item(1)
;
if( fsHook && elForm ){
loco.fs
.init( fsHook )
.setForm( elForm );
}
function showLoading(){
return $(elRoot).addClass('loading');
}
function hideLoading(){
return $(elRoot).removeClass('loading');
}
function setContent( src ){
return $(elRoot).find('div.diff').html( src );
}
function setError( message ){
hideLoading();
return $('<p class="error"></p>').text( message ).appendTo( setContent('') );
}
// applying line numbers in JS is easier than hacking HTML on the back end, because dom is parsed
function applyLineNumbers( i, tbody ){
var i, cells,
rows = tbody.getElementsByTagName('tr'),
nrow = rows.length,
data = tbody.getAttribute('data-diff').split(/\D+/),
xmin = data[0], xmax = data[1],
ymin = data[2], ymax = data[3]
;
/*function lpad( n, max ){
var str = String( n ),
len = String(max).length;
while( str.length < len ){
str = '\xA0'+str;
}
return str;
}*/
function apply( td, num, max ){
if( num <= max ){
$('<span></span>').text( String(num) ).prependTo( td );
}
}
for( i = 0; i < nrow; i++ ){
cells = rows[i].getElementsByTagName('td');
apply( cells[0], xmin++, xmax );
apply( cells[2], ymin++, ymax );
}
}
function loadDiff( offset ){
if( xhr ){
xhr.abort();
}
// use in-code cache
var src = cache[offset];
if( null != src ){
setContent( src );
hideLoading();
return;
}
// remote load required
setContent('');
showLoading();
xhr = loco.ajax.post( 'diff',
{
lhs: conf.paths[offset],
rhs: conf.paths[offset+1]
},
function( r, state, _xhr ){
if( _xhr === xhr ){
if( src = r && r.html ){
cache[offset] = src;
setContent( src ).find('tbody').each( applyLineNumbers );
hideLoading();
}
else {
setError( r && r.error || 'Unknown error' );
}
}
},
function( _xhr, error, message ){
if( _xhr === xhr ){
xhr = null;
// ajax utilty should have extracted meaningful error
setError('Failed to generate diff');
}
}
);
}
/**
* Go to next revision (newer)
*/
function goNext( event ){
event.preventDefault();
go( revOffset - 1 );
return false;
}
/**
* Go to previous revision (older)
*/
function goPrev( event ){
event.preventDefault();
go( revOffset + 1 );
return false;
}
function go( offset ){
if( offset >=0 && offset <= maxOffset ){
revOffset = offset;
loadDiff( offset );
redraw();
}
}
function redraw(){
var i = revOffset, j = i + 1;
// lock navigation to available range
prevBut.disabled = i >= maxOffset;
nextBut.disabled = i <= 0;
//
$metas.addClass('jshide').removeClass('diff-meta-current');
$metas.eq(i).removeClass('jshide').addClass('diff-meta-current');
$metas.eq(j).removeClass('jshide');
}
// enable navigation if there is something to navigate to
if( maxOffset ){
$(prevBut).click( goPrev ).parent().removeClass('jshide');
$(nextBut).click( goNext ).parent().removeClass('jshide');
}
// load default diff, which is current version compared to most recent backup
go( 0 );
}( window, document, jQuery );
\ No newline at end of file
/**
* Script for PO file initializing page
*/
!function( window, document, $ ){
var path,
loco = window.locoScope,
fsHook = document.getElementById('loco-fs'),
elForm = document.getElementById('loco-poinit'),
fsConn = fsHook && loco.fs.init( fsHook )
;
/**
* Abstract selection of twin mode (Select/Custom) locale input
*/
var localeSelector = function( elForm ){
function isSelectMode(){
return elMode[0].checked;
}
function setSelectMode(){
elMode[0].checked = true;
redrawMode( true );
}
function setCustomMode(){
if( ! elCode.value ){
elCode.value = getValue();
}
elMode[1].checked = true;
//elOpts.selectedIndex = 0;
redrawMode( false );
}
function getValue(){
var data = $( isSelectMode() ? elOpts : elCode ).serializeArray();
return data[0] && data[0].value || '';
}
function getLocale(){
var value = getValue();
return value ? loco.locale.parse(value) : loco.locale.clone( {lang:'zxx'} );
}
function onModeChange(){
redrawMode( isSelectMode() );
return true;
}
function redrawMode( selectMode ){
elCode.disabled = selectMode;
elOpts.disabled = ! selectMode;
fsCode.className = selectMode ? 'disabled' : 'active';
fsOpts.className = selectMode ? 'active' : 'disabled';
validate();
}
var elOpts = elForm['select-locale'],
elCode = elForm['custom-locale'],
elMode = elForm['use-selector'],
fsOpts = $(elOpts).focus( setSelectMode ).closest('fieldset').click( setSelectMode )[0],
fsCode = $(elCode).focus( setCustomMode ).closest('fieldset').click( setCustomMode )[0];
$(elMode).change( onModeChange );
onModeChange();
loco.watchtext( elCode, function(v){ $(elCode.form).triggerHandler('change'); } );
return {
val: getLocale
};
}( elForm );
/**
* Abstract selection of target directory
*/
var pathSelector = function(){
var elOpts = elForm['select-path'];
function getIndex(){
var pairs = $(elOpts).serializeArray(), pair = pairs[0];
return pair && pair.value || null;
}
function getSelected( name ){
var index = getIndex();
return index && elForm[name+'['+index+']'];
}
function getValue(){
var elField = getSelected('path');
return elField && elField.value;
}
function getLabel(){
var elField = getSelected('path');
return elField && $(elField.parentNode).find('code.path').text();
}
/*$(elForm['path[0]']).focus( function(){
elOpts[0].checked = true;
} );*/
return {
val: getValue,
txt: getLabel
};
}( elForm );
// enable disable form submission
function setFormDisabled( disabled ){
$(elForm).find('button.button-primary').each( function( i, button ){
button.disabled = disabled;
} );
}
// Recalculate form submission when any data changes
function validate(){
var locale = localeSelector && localeSelector.val(),
hasloc = locale && locale.isValid() && 'zxx' !== locale.lang,
hasdir = pathSelector && pathSelector.val(),
valid = hasloc && hasdir
;
redrawLocale( locale );
// disabled until back end validates file path
setFormDisabled( true );
// check calculated path against back end
if( valid ){
var newPath = pathSelector.txt();
if( newPath !== path ){
path = newPath;
fsHook.path.value = path;
fsConn.listen(onFsConnect).connect();
}
else {
setFormDisabled( false );
}
// connection back end won't warn about system files, so we'll toggle notice here
// actually no need as user will be warned when they go through to the edit screen
// $('#loco-fs-info')[ pathSelector.typ() ? 'removeClass' : 'addClass' ]('jshide');
}
}
// callback after file system connect has returned
function onFsConnect( valid ){
setFormDisabled( ! valid );
}
// show locale in all file paths (or place holder if empty)
function redrawLocale( locale ){
var $form = $(elForm),
loctag = locale && locale.toString('_') || '',
suffix = loctag ? ( 'zxx' === loctag ? '<locale>' : loctag ) : '<invalid>'
;
$form.find('code.path span').each( function( i, el ){
el.textContent = suffix;
} );
$form.find('span.lang').each( function( i, icon ){
setLocaleIcon( icon, locale );
} );
}
function setLocaleIcon( icon, locale ){
if( locale && 'zxx' !== locale.lang ){
icon.setAttribute('lang',locale.lang);
icon.setAttribute('class',locale.getIcon());
}
else {
icon.setAttribute('lang','');
icon.setAttribute('class','lang nolang');
}
}
// Submit form to Ajax end point when ..erm.. submitted
function onMsginitSuccess( data ){
var href = data && data.redirect;
if( href ){
// TODO show success panel and hide form instead of redirect?
// loco.notices.success('YES');
location.assign( href );
}
}
function process( event ){
event.preventDefault();
fsConn.applyCreds( elForm );
loco.ajax.submit( event.target, onMsginitSuccess );
// TODO some kind of loader?
return false;
}
$(elForm)
.change( validate )
.submit( process );
redrawLocale( localeSelector.val() );
}( window, document, jQuery );
\ No newline at end of file
/**
* Script for POT file initializing page
*/
!function( window, document, $ ){
var loco = window.locoScope,
fsHook = document.getElementById('loco-fs'),
elForm = document.getElementById('loco-potinit')
;
// enable disable form submission
/*function setFormDisabled( disabled ){
$(elForm).find('button.button-primary').each( function( i, button ){
button.disabled = disabled;
} );
}*/
// Submit form to Ajax end point when ..erm.. submitted
function onXgettextSuccess( data ){
var href = data && data.redirect;
if( href ){
location.assign( href );
}
}
function process( event ){
event.preventDefault();
loco.ajax.submit( event.target, onXgettextSuccess );
return false;
}
$(elForm)
.submit( process );
//setFormDisabled( false );
if( fsHook ){
loco.fs.init(fsHook).setForm( elForm );
}
}( window, document, jQuery );
\ No newline at end of file
/**
* Script for PO/POT source view screen
*/
!function( window, document, $ ){
var $modal,
loco = window.locoScope,
conf = window.locoConf,
view = document.getElementById('loco-po');
// index messages and enable text filter
! function( view, dict ){
var min, max,
texts = [],
blocks = [],
valid = true,
filtered = false,
items = $(view).find('li')
;
function flush(){
if( texts.length ){
blocks.push( [min,max] );
dict.push( texts );
texts = [];
}
min = null;
}
items.each( function( i, li ){
var text, $li = $(li);
// empty line indicates end of message
if( $li.find('span.po-none').length ) {
flush();
}
// context indexable if po-text present
else {
max = i;
if( null == min ){
min = i;
}
if( text = $li.find('.po-text').text() ){
texts = texts.concat( text.replace(/\\[ntvfab\\"]/g, ' ').split(' ') );
}
}
} );
flush();
// indexing done, enable filtering
// TODO for filtering to perform well, we must perform off-screen buffering of redundant <li> nodes
// TODO found text highlighting. (more complex than first thought)
function ol( start ){
return $('<ol class="msgcat"></ol>').attr('start',start).appendTo(view);
}
function filter(s){
var i, block, found = dict.find(s), f = -1, length = found.length, $ol;
$('ol',view).remove();
if( length ){
while( ++f < length ){
block = blocks[ found[f] ];
i = block[0];
$ol = ol( i+1 );
for( ; i <= block[1]; i++ ){
$ol.append( items[i]/*.cloneNode(true)*/ );
}
}
validate(true);
}
else {
validate(false);
// translators: When text filtering reduces to an empty view
ol(0).append( $('<li></li>').text( loco.l10n._('Nothing matches the text filter') ) );
}
filtered = true;
resize();
};
function unfilter(){
if( filtered ){
validate(true);
filtered = false;
$('ol',view).remove();
ol(1).append( items );
resize();
}
}
function validate( bool ){
if( valid !== bool ){
$('#loco-content')[ bool ? 'removeClass' : 'addClass' ]('loco-invalid');
valid = bool;
}
}
loco.watchtext( $(view.parentNode).find('form.loco-filter')[0].q, function(q){
q ? filter(q) : unfilter();
} );
}( view, loco.fulltext.init() );
// OK to show view now. may have taken a while to render and index
$(view).removeClass('loco-loading');
// resize function fits scrollable viewport, accounting for headroom and touching bottom of screen.
var resize = function(){
function top( el, ancestor ){
var y = el.offsetTop||0;
while( ( el = el.offsetParent ) && el !== ancestor ){
y += el.offsetTop||0;
}
return y;
}
var fixHeight,
maxHeight = view.clientHeight - 2
;
return function(){
var topBanner = top( view, document.body ),
winHeight = window.innerHeight,
setHeight = winHeight - topBanner - 20
;
if( fixHeight !== setHeight ){
if( setHeight < maxHeight ){
view.style.height = String(setHeight)+'px';
}
else {
view.style.height = '';
}
fixHeight = setHeight;
}
};
}();
resize();
$(window).resize( resize );
// enable file reference links to open modal to ajax service
$(view).click( function(event){
var link = event.target;
if( link.hasAttribute('href') ){
event.preventDefault();
getModal().html('<div class="loco-loading"></div>').dialog('option','title','Loading..').off('dialogopen').dialog('open').on('dialogopen',onModalOpen);
var postdata = $.extend( { ref:link.textContent, path:conf.popath }, conf.project||{} );
loco.ajax.post( 'fsReference', postdata, onRefSource, onRefError );
return false;
}
} );
// http://api.jqueryui.com/dialog/
function getModal(){
return $modal || ( $modal = $('<div id="loco-po-ref"></div>').dialog( {
dialogClass : 'loco-modal',
modal : true,
autoOpen : false,
closeOnEscape : true,
resizable : false,
height : 500
} ) );
}
// when reference pulling fails (e.g. file not found, or line nonexistant)
function onRefError( xhr, error, message ){
$error = $('<p></p>').text( message );
getModal().dialog('close').html('').dialog('option','title','Error').append($error).dialog('open');
}
// display reference source when received via ajax response
function onRefSource( result ){
var code = result && result.code;
if( ! code ){
return;
}
var i = -1,
length = code.length,
$ol = $('<ol></ol>').attr('class',result.type);
while( ++i < length ){
$('<li></li>').html( code[i] ).appendTo($ol);
}
// Highlight referenced line
$ol.find('li').eq( result.line - 1 ).attr('class','highlighted');
// TODO enable highlight toggling of other lines via click/drag etc..
getModal().dialog('close').html('').dialog('option','title', result.path+':'+result.line).append($ol).dialog('open');
}
// scroll to first highlighted line of code once modal open
function onModalOpen( event, ui ){
var div = event.target,
line = $(div).find('li.highlighted')[0],
yAbs = line && line.offsetTop || 0, // <- position of line relative to container
yVis = Math.floor( div.clientHeight / 2 ), // <- target position of line relative to view port
yAdj = Math.max( 0, yAbs - yVis ) // scroll required to move line to visual position
;
div.scrollTop = yAdj;
}
}( window, document, jQuery );
\ No newline at end of file
/**
* Script for bundle setup page
* TODO translations
*/
!function( window, document, $ ){
/**
* Look up bundle configuration on remote server
*/
function find( vendor, slug, version ){
function onFailure(){
if( timer ){
destroy();
onTimeout();
}
};
function onResponse( data, status, obj ){
if( timer ){
destroy();
var match = data && data.exact,
status = data && data.status
;
if( match ){
setJson( match );
}
else if( 404 === status ){
unsetJson("Sorry, we don't know a bundle by this name");
}
else {
loco.notices.debug( data.error || 'Unknown server error' );
onTimeout();
}
}
};
function onTimeout(){
unsetJson('Failed to contact remote API');
timer = null;
}
function destroy(){
if( timer ){
clearTimeout( timer );
timer = null;
}
}
var timer = setTimeout( onTimeout, 3000 );
unsetJson('');
$.ajax( {
url: conf.apiUrl+'/'+vendor+'/'+slug+'.jsonp?version='+encodeURIComponent(version),
dataType: 'jsonp',
success: onResponse,
error: onFailure,
cache: true
} );
return {
abort: destroy
};
}
function setJson( json ){
elForm['json-content'].value = json;
$('#loco-remote-empty').hide();
//$('#loco-remote-query').hide();
$('#loco-remote-found').show();
}
function unsetJson( message ){
elForm['json-content'].value = '';
//$('#loco-remote-query').show();
$('#loco-remote-empty').show().find('span').text( message );
$('#loco-remote-found').hide().removeClass('jshide');
}
function onFindClick( event ){
event.preventDefault();
finder && finder.abort();
finder = find( elForm.vendor.value, elForm.slug.value, elForm.version.value );
return false;
}
function onCancelClick( event ){
event.preventDefault();
unsetJson('');
return false;
}
function setVendors( list ){
var i = -1,
value, label,
length = list.length,
$select = $(elForm.vendor).html('')
;
while( ++i < length ){
value = list[i][0];
label = list[i][1];
$select.append( $('<option></option>').attr('value',value).text(label) );
}
}
var loco = window.locoScope,
conf = window.locoConf,
finder,
elForm = document.getElementById('loco-remote'),
$findButt = $(elForm).find('button[type="button"]').click( onFindClick ),
$resetButt = $(elForm).find('input[type="reset"]').click( onCancelClick );
// pull vendor list
$.ajax( {
url: conf.apiUrl+'/vendors.jsonp',
dataType: 'jsonp',
success: setVendors,
cache: true
} );
}( window, document, jQuery );
\ No newline at end of file
<?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');
}
}
<?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() );
}
}
<?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 ];
}
}*/
}
\ No newline at end of file
<?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';
}
}
\ No newline at end of file
<?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');
}
}
\ No newline at end of file
<?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;
}
}
<?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') );
}
}
\ No newline at end of file
<?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');
}
}
\ No newline at end of file
<?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') );
}
}
<?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');
}
}
\ No newline at end of file
<?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'),
'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-settings'),
);
}
}
\ No newline at end of file
<?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') ),
) ) );
// 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') );
}
}
return $this->view('admin/config/debug', compact('breadcrumb','versions','encoding','memory','fs','debug') );
}
}
\ No newline at end of file
<?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 );
// default value for Last-Translator credit
$user = wp_get_current_user();
$name = $user->get('display_name') or $name = 'nobody';
$email = $user->get('user_email') or $email = 'nobody@localhost';
$this->set('credit', sprintf('%s <%s>', $name, $email ) );
// 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') );
}
}
<?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'
) );
}
/* @var $role WP_Role */
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 ' : '' ),
) );
}
}
/**
* {@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') );
}
}
<?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 ) );
}
}
\ No newline at end of file
<?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 );
}
}
\ No newline at end of file
<?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');
}
}
\ No newline at end of file
<?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') );
}
}
<?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'),
);
}
/**
* {@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
if( $locale = $this->getLocale() ){
// 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() );
}
// no way to get configured POT if invalid project
else if( is_null($project) ){
$potfile = null;
}
// else use project-configured template, assuming there is one
else if( $potfile = $project->getPot() ){
// Handle situation where project defines a localised file as the official template
if( $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 );
}
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;
}
if( $potfile && ! $potdata->equalSource($data) ){
Loco_error_AdminNotices::debug( sprintf( __("Translations don't match template. Run sync to update from %s",'loco-translate'), $potfile->basename() ) );
}
}
// 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');
}
}
}
// notify if template is locked (save and sync will be disabled)
if( is_null($locale) && $project && $project->isPotLocked() ){
$this->set('fsDenied', true );
$readonly = true;
}
// 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) Loco_data_Settings::get()->po_width,
'multipart' => (bool) Loco_data_Settings::get()->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' => $readonly ? null : array (
'save' => wp_create_nonce('save'),
'sync' => wp_create_nonce('sync'),
),
) ) );
$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 toggles a translation's Fuzzy flag
'fuzzy' => _x('Fuzzy','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() );
}
}
\ No newline at end of file
<?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 webserver 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 );
}
}
<?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');
}
}
\ No newline at end of file
<?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' );
}
}
\ No newline at end of file
<?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' );
}
}
\ No newline at end of file
<?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,
) );
}
}
\ No newline at end of file
<?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');
}
}
\ No newline at end of file
<?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' );
}
}
<?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();
}
}
\ No newline at end of file
<?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();
}
}
\ No newline at end of file
<?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();
}
}
<?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');
}
}
\ No newline at end of file
<?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;
}
}
\ No newline at end of file
<?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();
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment