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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
<?php
/**
 * Utility for compiling PO data to MO AND JSON files
 */
class Loco_gettext_Compiler {

    
/**
     * @var Loco_api_WordPressFileSystem|null
     */
    
private $fs;

    
/**
     * Target file group, where we're compiling to
     * @var Loco_fs_Siblings
     */
    
private $files;

    
/**
     * Result when files written
     * @var Loco_fs_FileList
     */
    
private $done;
    
    
/**
     * @var Loco_mvc_ViewParams
     */
    
private $progress;
    

    
/**
     * Construct with primary file (PO) being saved
     * @param Loco_fs_File $pofile Localised PO file which may or may not exist yet
     */
    
public function __constructLoco_fs_File $pofile ){
        
$this->files = new Loco_fs_Siblings($pofile);
        
$this->progress = new Loco_mvc_ViewParams( [
            
'pobytes' => 0,
            
'mobytes' => 0,
            
'numjson' => 0,
            
'phbytes' => 0,
        ] );
        
// Connect compiler to the file system, if writing to disk for real
        
if( ! $pofile instanceof Loco_fs_DummyFile ) {
            
$this->fs = new Loco_api_WordPressFileSystem;
        }
        
$this->done = new Loco_fs_FileList;
    }


    
/**
     * Write PO, MO and JSON siblings
     */
    
public function writeAllLoco_gettext_Data $po, ?Loco_package_Project $project null ):Loco_fs_FileList {
        
$this->writePo($po);
        
$this->writeMo($po);
        if( 
$project ){
            
$this->writeJson($project,$po);
        }
        return 
$this->done;
    }


    
/**
     * @return int bytes written to PO file
     * @throws Loco_error_WriteException
     */
    
public function writePoLoco_gettext_Data $po ):int {
        
$file $this->files->getSource();
        
// Perform PO file backup before overwriting an existing PO
        
if( $file->exists() && $this->fs ){
            
$backups = new Loco_fs_Revisions($file);
            
$backup $backups->rotate($this->fs);
            
// debug backup creation only under cli or ajax. too noisy printing on screen
            
if( $backup && ( loco_doing_ajax() || 'cli' === PHP_SAPI ) && $backup->exists() ){
                
Loco_error_AdminNotices::debugsprintf('Wrote backup: %s -> %s',$file->basename(),$backup->basename() ) );
            }
        }
        
$bytes $this->writeFile$file$po->msgcat() );
        
$this->progress['pobytes'] = $bytes;
        return 
$bytes;
    }


    
/**
     * @return int bytes written to MO file
     */
    
public function writeMoLoco_gettext_Data $po ):int {
        try {
            
$mofile $this->files->getBinary();
            
$bytes $this->writeFile$mofile$po->msgfmt() );
        }
        catch( 
Exception $e ){
            
Loco_error_AdminNotices::debug$e->getMessage() );
            
Loco_error_AdminNotices::warn__('PO file saved, but MO file compilation failed','loco-translate') );
            
$bytes 0;
        }
        
$this->progress['mobytes'] = $bytes;
        
// write PHP cache, if WordPress >= 6.5
        
if( !== $bytes ){
            try {
                
$this->progress['phbytes'] = $this->writePhp($po);
            }
            catch( 
Exception $e ){
                
Loco_error_AdminNotices::debug$e->getMessage() );
            }
        }
        return 
$bytes;
    }


    
/**
     * @return int bytes written to .l10n.php file
     */
    
private function writePhpLoco_gettext_Data $po ):int {
        
$phfile $this->files->getCache();
        if( 
$phfile && class_exists('WP_Translation_File_PHP',false) ){
            return 
$this->writeFile$phfileLoco_gettext_PhpCache::render($po) );
        }
        return 
0;
    }


    
/**
     * @param Loco_package_Project $project Translation set, required to resolve script paths
     * @param Loco_gettext_Data $po PO data to export
     */
    
public function writeJsonLoco_package_Project $projectLoco_gettext_Data $po ):Loco_fs_FileList {
        
$domain $project->getDomain()->getName();
        
$pofile $this->files->getSource();
        
$jsons = new Loco_fs_FileList;
        
// Allow plugins to dictate a single JSON file to hold all script translations for a text domain
        // authors will additionally have to filter at runtime on load_script_translation_file
        
$path apply_filters('loco_compile_single_json'''$pofile->getPath(), $domain );
        if( 
is_string($path) && '' !== $path ){
            
$refs $po->splitRefs$this->getJsExtMap() );
            if( 
array_key_exists('js',$refs) && $refs['js'] instanceof Loco_gettext_Data ){
                
$jsonfile = new Loco_fs_File($path);
                
$json $refs['js']->msgjed($domain,'*.js');
                try {
                    if( 
'' !== $json ){
                        
$this->writeFile($jsonfile,$json);
                        
$jsons->add($jsonfile);
                    }
                }
                catch( 
Loco_error_WriteException $e ){
                    
Loco_error_AdminNotices::debug$e->getMessage() );
                    
// translators: %s refers to a JSON file which could not be compiled due to an error
                    
Loco_error_AdminNotices::warnsprintf(__('JSON compilation failed for %s','loco-translate'),$jsonfile->basename()) );
                }
            }
        }
        
// continue as per default, generating multiple per-script JSON
        
else {
            
$buffer = [];
            
$base_dir $project->getBundle()->getDirectoryPath();
            
$extensions array_keys$this->getJsExtMap() );
            
$refsGrep '\\.(?:'.implode('|',$extensions).')';
            
/* @var Loco_gettext_Data $fragment */
            
foreach( $po->exportRefs($refsGrep) as $ref => $fragment ){
                
$use null;
                
// Reference could be a js source file, or a minified version. We'll try .min.js first, then .js
                // Build systems may differ, but WordPress only supports these suffixes. See WP-CLI MakeJsonCommand.
                
if( substr($ref,-7) === '.min.js' ) {
                    
$paths = [ $refsubstr($ref,-7).'.js' ];
                }
                else {
                    
$paths = [ substr($ref,0,-3).'.min.js'$ref ];
                }
                
// Try .js and .min.js paths to check whether deployed script actually exists
                
foreach( $paths as $path ){
                    
// Hook into load_script_textdomain_relative_path like load_script_textdomain() does.
                    
$url $project->getBundle()->getDirectoryUrl().$path;
                    
$path apply_filters'load_script_textdomain_relative_path'$path$url );
                    if( ! 
is_string($path) || '' === $path ){
                        continue;
                    }
                    
// by default ignore js file that is not in deployed code
                    
$file = new Loco_fs_File($path);
                    
$file->normalize($base_dir);
                    if( 
apply_filters('loco_compile_script_reference',$file->exists(),$path,$domain) ){
                        
$use $path;
                        break;
                    }
                }
                
// if neither exists in the bundle, this is a source path that will never be resolved at runtime
                
if( is_null($use) ){
                    
Loco_error_AdminNotices::debugsprintf('Skipping JSON for %s; script not found in bundle',$ref) );
                }
                
// add .js strings to buffer for this json and merge if already present
                
else if( array_key_exists($use,$buffer) ){
                    
$buffer[$use]->concat($fragment);
                }
                else {
                    
$buffer[$use] = $fragment;
                }
            }
            if( 
$buffer ){
                
// write all buffered fragments to their computed JSON paths
                
foreach( $buffer as $ref => $fragment ) {
                    
$json $fragment->msgjed($domain,$ref);
                    if( 
'' === $json ){
                        
Loco_error_AdminNotices::debugsprintf('Skipping JSON for %s; no translations',$ref) );
                        continue;
                    }
                    try {
                        
$jsonfile self::cloneJson($pofile,$ref,$domain);
                        
$this->writeFile$jsonfile$json );
                        
$jsons->add($jsonfile);
                    }
                    catch( 
Loco_error_WriteException $e ){
                        
Loco_error_AdminNotices::debug$e->getMessage() );
                        
// phpcs:ignore -- comment already applied to this string elsewhere
                        
Loco_error_AdminNotices::warnsprintf(__('JSON compilation failed for %s','loco-translate'),$ref));
                    }
                }
                
$buffer null;
            }
        }
        
// clean up redundant JSONs including if no JSONs were compiled
        
if( Loco_data_Settings::get()->jed_clean ){
            foreach( 
$this->files->getJsons($domain) as $path ){
                
$jsonfile = new Loco_fs_File($path);
                if( ! 
$jsons->has($jsonfile) ){
                    try {
                        
$jsonfile->unlink();
                    }
                    catch( 
Loco_error_WriteException $e ){
                        
Loco_error_AdminNotices::debug('Unable to remove redundant JSON: '.$e->getMessage() );
                    }
                }
            }
        }
        
$this->progress['numjson'] = $jsons->count();
        return 
$jsons;
    }


    
/**
     * Clone localised file as a WordPress script translation file
     */
    
private static function cloneJsonLoco_fs_File $pofilestring $refstring $domain ):Loco_fs_File {
        
$name $pofile->filename();
        
// Theme author PO files have no text domain, but JSON files must always be prefixed
        
if( $domain && 'default' !== $domain && preg_match('/^[a-z]{2,3}(?:_[a-z\\d_]+)?$/i',$name) ){
            
$name $domain.'-'.$name;
        }
        
// Hashable reference is always finally unminified, as per load_script_textdomain()
        
if( '' !== $ref ){
            
$name .= '-'.self::hashRef($ref);
        }
        return 
$pofile->cloneBasename$name.'.json' );
    }


    
/**
     * Hashable reference is always finally unminified, as per load_script_textdomain()
     * @param string $ref script path relative to plugin base
     */
    
private static function hashRefstring $ref ):string {
        if( 
substr($ref,-7) === '.min.js' ) {
            
$ref substr($ref,0,-7).'.js';
        }
        return 
md5($ref);
    }


    
/**
     * Fetch compilation summary and raise most relevant success message
     */
    
public function getSummary():Loco_mvc_ViewParams {
        
$pofile $this->files->getSource();
        
// Avoid calling this unless the initial PO save was successful
        
if( ! $this->progress['pobytes'] ){
            throw new 
LogicException('PO not saved');
        }
        
// Summary for localised file includes MO+JSONs
        
$mobytes $this->progress['mobytes'];
        
$numjson $this->progress['numjson'];
        if( 
$mobytes && $numjson ){
            
Loco_error_AdminNotices::success__('PO file saved and MO/JSON files compiled','loco-translate') );
        }
        else if( 
$mobytes ){
            
Loco_error_AdminNotices::success__('PO file saved and MO file compiled','loco-translate') );
        }
        else {
            
// translators: Success notice where %s is a file extension, e.g. "PO"
            
Loco_error_AdminNotices::successsprintf(__('%s file saved','loco-translate'),strtoupper($pofile->extension())) );
        }
        return 
$this->progress;
    }


    
/**
     * Obtain non-standard JavaScript file extensions.
     * @return string[] where keys are PCRE safe extensions, all mapped to "js"
     */
    
private function getJsExtMap():array {
        
$map = ['js'=>'js','jsx'=>'js'];
        
$exts Loco_data_Settings::get()->jsx_alias;
        if( 
is_array($exts) && $exts ){
            
$exts array_map( [__CLASS__,'pregQuote'], $exts);
            
$map array_fill_keys($exts,'js') + $map;
        }
        return 
$map;
    }


    
/**
     * @internal
     */
    
private static function pregQuotestring $value ):string {
        return 
preg_quote($value,'/');
    }


    
/**
     * @param Loco_fs_File $file
     * @param string $data to write to given file
     * @return int bytes written
     */
    
public function writeFileLoco_fs_File $filestring $data ):int {
        if( 
$this->fs ) {
            
$this->fs->authorizeSave$file );
        }
        
$bytes $file->putContents($data);
        if( 
!== $bytes ){
            
$this->done->add($file );
        }
        return 
$bytes;
    }

}