PageRenderTime 55ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-content/plugins/loco-translate/src/admin/init/InitPoController.php

https://gitlab.com/campus-academy/krowkaramel
PHP | 331 lines | 227 code | 34 blank | 70 comment | 33 complexity | ddc051acf4b1734d21e04e364264c33f MD5 | raw file
  1. <?php
  2. /**
  3. * pre-msginit function. Prepares arguments for creating a new PO language file
  4. */
  5. class Loco_admin_init_InitPoController extends Loco_admin_bundle_BaseController {
  6. /**
  7. * {@inheritdoc}
  8. */
  9. public function init(){
  10. parent::init();
  11. $this->enqueueStyle('poinit');
  12. //
  13. $bundle = $this->getBundle();
  14. $this->set('title', __('New language','loco-translate').' &lsaquo; '.$bundle );
  15. }
  16. /**
  17. * {@inheritdoc}
  18. */
  19. public function getHelpTabs(){
  20. return [
  21. __('Overview','default') => $this->viewSnippet('tab-init-po'),
  22. ];
  23. }
  24. /**
  25. * Sort to the left the best option for saving new translation files
  26. * @param Loco_mvc_ViewParams[]
  27. * @return Loco_mvc_ViewParams|null
  28. */
  29. private function sortPreferred( array $choices ){
  30. usort( $choices, [__CLASS__,'_onSortPreferred'] );
  31. $best = current( $choices );
  32. if( $best && ! $best['disabled'] ){
  33. return $best;
  34. }
  35. return null;
  36. }
  37. /**
  38. * @internal
  39. * @param Loco_mvc_ViewParams
  40. * @param Loco_mvc_ViewParams
  41. * @return int
  42. */
  43. public static function _onSortPreferred( Loco_mvc_ViewParams $a, Loco_mvc_ViewParams $b ){
  44. $x = self::scoreFileChoice($a);
  45. $y = self::scoreFileChoice($b);
  46. return $x === $y ? 0 : ( $x > $y ? -1 : 1 );
  47. }
  48. /**
  49. * Score an individual file choice for sorting preferred
  50. * @param Loco_mvc_ViewParams
  51. * @return int
  52. */
  53. private static function scoreFileChoice( Loco_mvc_ViewParams $p ){
  54. $score = 0;
  55. if( $p['writable'] ){
  56. $score++;
  57. }
  58. if( $p['disabled'] ){
  59. $score -= 2;
  60. }
  61. if( $p['systype'] ){
  62. $score--;
  63. }
  64. return $score;
  65. }
  66. /**
  67. * @internal
  68. * @param int
  69. * @param int
  70. * @return int
  71. */
  72. private static function compareLocationKeys( $a, $b ){
  73. static $order = ['custom' => 4, 'wplang' => 3, 'theme' => 2, 'plugin' => 2, 'other' => 1 ];
  74. $x = $order[$a];
  75. $y = $order[$b];
  76. return $x === $y ? 0 : ( $x > $y ? -1 : 1 );
  77. }
  78. /**
  79. * {@inheritdoc}
  80. */
  81. public function render(){
  82. $breadcrumb = $this->prepareNavigation();
  83. // "new" tab is confusing when no project-scope navigation
  84. // $this->get('tabs')->add( __('New PO','loco-translate'), '', true );
  85. // bundle mandatory, but project optional
  86. $bundle = $this->getBundle();
  87. try {
  88. $project = $this->getProject();
  89. $slug = $project->getSlug();
  90. $domain = (string) $project->getDomain();
  91. $subhead = sprintf( __('Initializing new translations in "%s"','loco-translate'), $slug?$slug:$domain );
  92. }
  93. catch( Loco_error_Exception $e ){
  94. $project = null;
  95. $subhead = __('Initializing new translations in unknown set','loco-translate');
  96. }
  97. $title = __('New language','loco-translate');
  98. $this->set('subhead', $subhead );
  99. // navigate up to bundle listing page
  100. $breadcrumb->add( $title );
  101. $this->set( 'breadcrumb', $breadcrumb );
  102. // default locale is a placeholder
  103. $locale = new Loco_Locale('zxx');
  104. $content_dir = untrailingslashit( loco_constant('WP_CONTENT_DIR') );
  105. $copying = false;
  106. // Permit using any provided file a template instead of POT
  107. if( $potpath = $this->get('path') ){
  108. $potfile = new Loco_fs_LocaleFile($potpath);
  109. $potfile->normalize( $content_dir );
  110. if( ! $potfile->exists() ){
  111. throw new Loco_error_Exception('Forced template argument must exist');
  112. }
  113. $copying = true;
  114. // forced source could be a POT (although UI would normally prevent it)
  115. if( $potfile->getSuffix() ){
  116. $locale = $potfile->getLocale();
  117. $this->set('sourceLocale', $locale );
  118. }
  119. }
  120. // else project not configured. UI should prevent this by not offering msginit
  121. else if( ! $project ){
  122. throw new Loco_error_Exception('Cannot add new language to unconfigured set');
  123. }
  124. // else POT file may or may not be known, and may or may not exist
  125. else {
  126. $potfile = $project->getPot();
  127. }
  128. $locales = [];
  129. $installed = [];
  130. $api = new Loco_api_WordPressTranslations;
  131. $prefs = Loco_data_Preferences::get();
  132. // pull installed list first, this will include en_US and any non-standard languages installed
  133. foreach( $api->getInstalledCore() as $tag ){
  134. $locale = Loco_Locale::parse($tag);
  135. if( $locale->isValid() && $prefs->has_locale($locale) ){
  136. $tag = (string) $tag;
  137. // We may not have names for these, so just the language tag will show
  138. $installed[$tag] = new Loco_mvc_ViewParams( [
  139. 'value' => $tag,
  140. 'icon' => $locale->getIcon(),
  141. 'label' => $locale->ensureName($api),
  142. ] );
  143. }
  144. }
  145. // pull the same list of "available" languages as used in WordPress settings
  146. foreach( $api->getAvailableCore() as $tag => $locale ){
  147. if( ! array_key_exists($tag,$installed) && $prefs->has_locale($locale) ){
  148. $locales[$tag] = new Loco_mvc_ViewParams( [
  149. 'value' => $tag,
  150. 'icon' => $locale->getIcon(),
  151. 'label' => $locale->ensureName($api),
  152. ] );
  153. }
  154. }
  155. // two locale lists built for "installed" and "available" dropdowns
  156. $this->set( 'locales', $locales );
  157. $this->set( 'installed', $installed );
  158. // Critical that user selects the correct save location:
  159. if( $project ){
  160. $filechoice = $project->initLocaleFiles( $locale );
  161. }
  162. // without configured project we will only allow save to same location
  163. else {
  164. $filechoice = new Loco_fs_FileList;
  165. }
  166. // show information about POT file if we are initializing from template
  167. if( $potfile && $potfile->exists() ){
  168. $meta = Loco_gettext_Metadata::load($potfile);
  169. $total = $meta->getTotal();
  170. // translators: 1: Number of strings; 2: Name of POT file; e.g. "100 strings found in file.pot"
  171. $summary = sprintf( _n('%1$s string found in %2$s','%1$s strings found in %2$s',$total,'loco-translate'), number_format($total), $potfile->basename() );
  172. $this->set( 'pot', new Loco_mvc_ViewParams( [
  173. 'name' => $potfile->basename(),
  174. 'path' => $meta->getPath(false),
  175. ] ) );
  176. // if copying an existing PO file, we can fairly safely establish the correct prefixing
  177. if( $copying ){
  178. $poname = ( $prefix = $potfile->getPrefix() ) ? sprintf('%s-%s.po',$prefix,$locale) : sprintf('%s.po',$locale);
  179. $pofile = new Loco_fs_LocaleFile( $poname );
  180. $pofile->normalize( $potfile->dirname() );
  181. $filechoice->add( $pofile );
  182. }
  183. /// else if POT is in a folder we don't know about, we may as well add to the choices
  184. // TODO this means another utility function in project for prefixing rules on individual location
  185. }
  186. // else no template exists, so we prompt to extract from source
  187. else if( 2 > Loco_data_Settings::get()->pot_expected ){
  188. $this->set( 'ext', new Loco_mvc_ViewParams( [
  189. 'link' => Loco_mvc_AdminRouter::generate( $this->get('type').'-xgettext', $_GET ),
  190. 'text' => __('Create template','loco-translate'),
  191. ] ) );
  192. // if allowing source extraction without warning show brief description of source files
  193. if( $this->get('extract') || 0 === Loco_data_Settings::get()->pot_expected ){
  194. // Tokenizer required for string extraction
  195. if( ! loco_check_extension('tokenizer') ){
  196. return $this->view('admin/errors/no-tokenizer');
  197. }
  198. $nfiles = count( $project->findSourceFiles() );
  199. // translators: Were %s is number of source files that will be scanned
  200. $summary = sprintf( _n('%s source file will be scanned for translatable strings','%s source files will be scanned for translatable strings',$nfiles,'loco-translate'), number_format_i18n($nfiles) );
  201. }
  202. // else prompt for template creation before continuing
  203. else {
  204. $this->set( 'skip', new Loco_mvc_ViewParams( [
  205. 'link' => Loco_mvc_AdminRouter::generate( $this->get('_route'), $_GET + [ 'extract' => '1' ] ),
  206. 'text' => __('Skip template','loco-translate'),
  207. ] ) );
  208. // POT could still be defined, it might just not exist yet
  209. if( $potfile ){
  210. $this->set('pot', Loco_mvc_FileParams::create($potfile) );
  211. }
  212. // else offer assignment of a new file
  213. else {
  214. $this->set( 'conf', new Loco_mvc_ViewParams( [
  215. 'link' => Loco_mvc_AdminRouter::generate( $this->get('type').'-conf', array_intersect_key($_GET,['bundle'=>'']) ),
  216. 'text' => __('Assign template','loco-translate'),
  217. ] ) );
  218. }
  219. return $this->view('admin/init/init-prompt');
  220. }
  221. }
  222. else {
  223. throw new Loco_error_Exception('Plugin settings disallow missing templates');
  224. }
  225. $this->set( 'summary', $summary );
  226. // group established locations into types (official, etc..)
  227. // there is no point checking whether any of these file exist, because we don't know what language will be chosen yet.
  228. $sortable = [];
  229. $locations = [];
  230. $fs_failure = null;
  231. /* @var Loco_fs_LocaleFile $pofile */
  232. foreach( $filechoice as $pofile ){
  233. $parent = new Loco_fs_LocaleDirectory( $pofile->dirname() );
  234. $systype = $parent->getUpdateType();
  235. $typeId = $parent->getTypeId();
  236. if( ! isset($locations[$typeId]) ){
  237. $locations[$typeId] = new Loco_mvc_ViewParams( [
  238. 'label' => $parent->getTypeLabel( $typeId ),
  239. 'paths' => [],
  240. ] );
  241. }
  242. // folder may be unwritable (requiring connect to create file) or may be denied under security settings
  243. try {
  244. $context = $parent->getWriteContext()->authorize();
  245. $writable = $context->writable();
  246. $disabled = false;
  247. }
  248. catch( Loco_error_WriteException $e ){
  249. $fs_failure = $e->getMessage();
  250. $writable = false;
  251. $disabled = true;
  252. }
  253. $suffix = $pofile->getSuffix().'.po';
  254. $choice = new Loco_mvc_ViewParams( [
  255. 'checked' => '',
  256. 'writable' => $writable,
  257. 'disabled' => $disabled,
  258. 'systype' => $systype,
  259. 'parent' => Loco_mvc_FileParams::create( $parent ),
  260. 'hidden' => $pofile->getRelativePath($content_dir),
  261. 'holder' => str_replace( $suffix, '<span>{locale}</span>.po', $pofile->basename() ),
  262. ] );
  263. $sortable[] = $choice;
  264. $locations[$typeId]['paths'][] = $choice;
  265. }
  266. // display locations in runtime preference order
  267. uksort( $locations, [__CLASS__,'compareLocationKeys'] );
  268. $this->set( 'locations', $locations );
  269. // pre-select best (safest/writable) option
  270. if( $preferred = $this->sortPreferred( $sortable ) ){
  271. $preferred['checked'] = 'checked';
  272. }
  273. // else show total lock message. probably file mods disallowed
  274. else if( $fs_failure ){
  275. $this->set('fsLocked', $fs_failure );
  276. }
  277. // hidden fields to pass through to Ajax endpoint
  278. $this->set('hidden', new Loco_mvc_HiddenFields( [
  279. 'action' => 'loco_json',
  280. 'route' => 'msginit',
  281. 'loco-nonce' => $this->setNonce('msginit')->value,
  282. 'type' => $bundle->getType(),
  283. 'bundle' => $bundle->getHandle(),
  284. 'domain' => $project ? $project->getId() : '',
  285. 'source' => $potpath,
  286. ] ) );
  287. $this->set('help', new Loco_mvc_ViewParams( [
  288. 'href' => apply_filters('loco_external','https://localise.biz/wordpress/plugin/manual/msginit'),
  289. 'text' => __("What's this?",'loco-translate'),
  290. ] ) );
  291. // file system prompts will be handled when paths are selected (i.e. we don't have one yet)
  292. $this->prepareFsConnect( 'create', '' );
  293. $this->enqueueScript('poinit');
  294. return $this->view( 'admin/init/init-po', [] );
  295. }
  296. }