PageRenderTime 51ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/loco-translate/src/gettext/Compiler.php

https://gitlab.com/campus-academy/krowkaramel
PHP | 273 lines | 175 code | 23 blank | 75 comment | 33 complexity | 0e33bd5ed8097d3490e88a3b0d70dbdd MD5 | raw file
  1. <?php
  2. /**
  3. * Utility for compiling PO data to MO AND JSON files
  4. */
  5. class Loco_gettext_Compiler {
  6. /**
  7. * @var Loco_api_WordPressFileSystem
  8. */
  9. private $fs;
  10. /**
  11. * Target file group, where we're compiling to
  12. * @var Loco_fs_Siblings
  13. */
  14. private $files;
  15. /**
  16. * @var Loco_mvc_ViewParams
  17. */
  18. private $progress;
  19. /**
  20. * Whether to protect existing files during compilation (i.e. not overwrite them)
  21. * @var bool
  22. */
  23. private $keep = false;
  24. /**
  25. * Construct with primary file (PO) being saved
  26. * @param Loco_fs_LocaleFile Localised PO file which may or may not exist yet
  27. */
  28. public function __construct( Loco_fs_LocaleFile $pofile ){
  29. $this->fs = new Loco_api_WordPressFileSystem;
  30. $this->files = new Loco_fs_Siblings($pofile);
  31. $this->progress = new Loco_mvc_ViewParams( [
  32. 'pobytes' => 0,
  33. 'mobytes' => 0,
  34. 'numjson' => 0,
  35. ] );
  36. }
  37. /**
  38. * Set overwrite mode
  39. * @param bool whether to overwrite existing files during compilation
  40. * @return self
  41. */
  42. public function overwrite( $overwrite ){
  43. $this->keep = ! $overwrite;
  44. return $this;
  45. }
  46. /**
  47. * @param Loco_gettext_Data
  48. * @param Loco_package_Project|null
  49. * @return self
  50. */
  51. public function writeAll( Loco_gettext_Data $po, Loco_package_Project $project = null ){
  52. $this->writePo($po);
  53. $this->writeMo($po);
  54. if( $project ){
  55. $this->writeJson($project,$po);
  56. }
  57. return $this;
  58. }
  59. /**
  60. * @param Loco_gettext_Data PO data
  61. * @return int bytes written to PO file
  62. * @throws Loco_error_WriteException
  63. */
  64. public function writePo( Loco_gettext_Data $po ){
  65. $file = $this->files->getSource();
  66. // Perform PO file backup before overwriting an existing PO
  67. if( $file->exists() ){
  68. $backups = new Loco_fs_Revisions($file);
  69. $backup = $backups->rotate($this->fs);
  70. // debug backup creation only under cli or ajax. too noisy printing on screen
  71. if( $backup && ( loco_doing_ajax() || 'cli' === PHP_SAPI ) && $backup->exists() ){
  72. Loco_error_AdminNotices::debug( sprintf('Wrote backup: %s -> %s',$file->basename(),$backup->basename() ) );
  73. }
  74. }
  75. $this->fs->authorizeSave($file);
  76. $bytes = $file->putContents( $po->msgcat() );
  77. $this->progress['pobytes'] = $bytes;
  78. return $bytes;
  79. }
  80. /**
  81. * @param Loco_gettext_Data PO data
  82. * @return int bytes written to MO file
  83. */
  84. public function writeMo( Loco_gettext_Data $po ){
  85. try {
  86. $file = $this->files->getBinary();
  87. $this->fs->authorizeSave($file);
  88. $bytes = $file->putContents( $po->msgfmt() );
  89. $this->progress['mobytes'] = $bytes;
  90. }
  91. catch( Exception $e ){
  92. Loco_error_AdminNotices::debug( $e->getMessage() );
  93. Loco_error_AdminNotices::warn( __('PO file saved, but MO file compilation failed','loco-translate') );
  94. $bytes = 0;
  95. }
  96. return $bytes;
  97. }
  98. /**
  99. * @param Loco_package_Project Translation set, required to resolve script paths
  100. * @param Loco_gettext_Data PO data to export
  101. * @return Loco_fs_FileList All json files created
  102. */
  103. public function writeJson( Loco_package_Project $project, Loco_gettext_Data $po ){
  104. $domain = $project->getDomain()->getName();
  105. $pofile = $this->files->getSource();
  106. $jsons = new Loco_fs_FileList;
  107. // Allow plugins to dictate a single JSON file to hold all script translations for a text domain
  108. // authors will additionally have to filter at runtime on load_script_translation_file
  109. $path = apply_filters('loco_compile_single_json', '', $pofile->getPath(), $domain );
  110. if( is_string($path) && '' !== $path ){
  111. $refs = $po->splitRefs( $this->getJsExtMap() );
  112. if( array_key_exists('js',$refs) && $refs['js'] instanceof Loco_gettext_Data ){
  113. $jsonfile = new Loco_fs_File($path);
  114. try {
  115. $this->writeFile( $jsonfile, $refs['js']->msgjed($domain,'*.js') );
  116. $jsons->add($jsonfile);
  117. }
  118. catch( Loco_error_WriteException $e ){
  119. Loco_error_AdminNotices::debug( $e->getMessage() );
  120. Loco_error_AdminNotices::warn( sprintf(__('JSON compilation failed for %s','loco-translate'),$jsonfile->basename()) );
  121. }
  122. }
  123. }
  124. // continue as per default, generating multiple per-script JSON
  125. else {
  126. $base_dir = $project->getBundle()->getDirectoryPath();
  127. $buffer = [];
  128. /* @var Loco_gettext_Data $fragment */
  129. foreach( $po->exportRefs('\\.js') as $ref => $fragment ){
  130. $use = null;
  131. // Reference could be source js, or minified version.
  132. // Some build systems may differ, but WordPress only supports this. See WP-CLI MakeJsonCommand.
  133. if( substr($ref,-7) === '.min.js' ) {
  134. $min = $ref;
  135. $src = substr($ref,-7).'.js';
  136. }
  137. else {
  138. $src = $ref;
  139. $min = substr($ref,0,-3).'.min.js';
  140. }
  141. // Try .js and .min.js paths to check whether deployed script actually exists
  142. foreach( [$min,$src] as $try ){
  143. // Hook into load_script_textdomain_relative_path like load_script_textdomain() does.
  144. $url = $project->getBundle()->getDirectoryUrl().$try;
  145. $try = apply_filters( 'load_script_textdomain_relative_path', $try, $url );
  146. if( ! is_string($try) || '' === $try ){
  147. continue;
  148. }
  149. // by default ignore js file that is not in deployed code
  150. $file = new Loco_fs_File($try);
  151. $file->normalize($base_dir);
  152. if( apply_filters('loco_compile_script_reference',$file->exists(),$try,$domain) ){
  153. $use = $try;
  154. break;
  155. }
  156. }
  157. // if neither exists in the bundle, this is a source path that will never be resolved at runtime
  158. if( is_null($use) ){
  159. Loco_error_AdminNotices::debug( sprintf('Skipping JSON for %s; script not found in bundle',$ref) );
  160. }
  161. // add .js strings to buffer for this json and merge if already present
  162. else if( array_key_exists($use,$buffer) ){
  163. $buffer[$use]->concat($fragment);
  164. }
  165. else {
  166. $buffer[$use] = $fragment;
  167. }
  168. }
  169. // write all buffered fragments to their computed JSON paths
  170. foreach( $buffer as $ref => $fragment ) {
  171. $jsonfile = $pofile->cloneJson($ref);
  172. try {
  173. $this->writeFile( $jsonfile, $fragment->msgjed($domain,$ref) );
  174. $jsons->add($jsonfile);
  175. }
  176. catch( Loco_error_WriteException $e ){
  177. Loco_error_AdminNotices::debug( $e->getMessage() );
  178. Loco_error_AdminNotices::warn( sprintf(__('JSON compilation failed for %s','loco-translate'),$ref));
  179. }
  180. }
  181. $buffer = null;
  182. }
  183. // clean up redundant JSONs including if no JSONs were compiled
  184. if( Loco_data_Settings::get()->jed_clean ){
  185. foreach( $this->files->getJsons() as $path ){
  186. $jsonfile = new Loco_fs_File($path);
  187. if( ! $jsons->has($jsonfile) ){
  188. try {
  189. $jsonfile->unlink();
  190. }
  191. catch( Loco_error_WriteException $e ){
  192. Loco_error_AdminNotices::debug('Unable to remove redundant JSON: '.$e->getMessage() );
  193. }
  194. }
  195. }
  196. }
  197. $this->progress['numjson'] = $jsons->count();
  198. return $jsons;
  199. }
  200. /**
  201. * Fetch compilation summary and raise most relevant success message
  202. * @return Loco_mvc_ViewParams
  203. */
  204. public function getSummary(){
  205. $pofile = $this->files->getSource();
  206. // Avoid calling this unless the initial PO save was successful
  207. if( ! $this->progress['pobytes'] || ! $pofile->exists() ){
  208. throw new LogicException('PO not saved');
  209. }
  210. // Summary for localised file includes MO+JSONs
  211. $mobytes = $this->progress['mobytes'];
  212. $numjson = $this->progress['numjson'];
  213. if( $mobytes && $numjson ){
  214. Loco_error_AdminNotices::success( __('PO file saved and MO/JSON files compiled','loco-translate') );
  215. }
  216. else if( $mobytes ){
  217. Loco_error_AdminNotices::success( __('PO file saved and MO file compiled','loco-translate') );
  218. }
  219. else {
  220. // translators: Where %s is either PO or POT
  221. Loco_error_AdminNotices::success( sprintf(__('%s file saved','loco-translate'),strtoupper($pofile->extension())) );
  222. }
  223. return $this->progress;
  224. }
  225. /**
  226. * @return string[]
  227. */
  228. private function getJsExtMap(){
  229. $map = ['js'=>'js', 'jsx'=>'js'];
  230. $exts = Loco_data_Settings::get()->jsx_alias;
  231. if( is_array($exts) && $exts ){
  232. foreach( $exts as $ext ){
  233. $map[$ext] = 'js';
  234. }
  235. }
  236. return $map;
  237. }
  238. /**
  239. * @param Loco_fs_File $file
  240. * @param string Serialized JSON to write to given file
  241. * @return int bytes written
  242. */
  243. public function writeFile( Loco_fs_File $file, $data ){
  244. if( $this->keep && $file->exists() ){
  245. return 0;
  246. }
  247. $this->fs->authorizeSave($file);
  248. return $file->putContents($data);
  249. }
  250. }