/var/www/html_it/wp-content/plugins/loco-translate/src/api/WordPressFileSystem.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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
<?php
/**
 * Abstracts WordPress filesystem connection.
 * https://codex.wordpress.org/Filesystem_API
 */
class Loco_api_WordPressFileSystem {
    
    
/**
     * Currently authenticated file system connection
     * @var WP_Filesystem_Direct
     */
    
private $fs;
    
    
/**
     * Whether global file modifications have already passed check
     * @var bool
     */
    
private $fs_allowed;
    
    
/**
     * Credentials form HTML echoed from request_filesystem_credentials
     * @var string
     */
    
private $form '';

    
/**
     * Credentials posted into the API
     * @var array
     */
    
private $creds_in = [];

    
/**
     * Credentials returned from the API
     * @var array
     */
    
private $creds_out = [];


    
/**
     * Create direct filesystem accessor
     * @return WP_Filesystem_Direct
     */
    
public static function direct(){
        
// Emulate WP_Filesystem to avoid FS_METHOD and filters overriding "direct" type
        
if( ! class_exists('WP_Filesystem_Direct',false) ){
            require_once 
ABSPATH.'wp-admin/includes/class-wp-filesystem-base.php';
            require_once 
ABSPATH.'wp-admin/includes/class-wp-filesystem-direct.php';
        }
        return new 
WP_Filesystem_Direct(null);
    }


    
/**
     * Get HTML form rendered by request_filesystem_credentials
     * @return string
     */
    
public function getForm(){
        return 
$this->form;
    }


    
/**
     * Pre-auth checks for superficial file system denials and disconnects any active remotes
     * @param Loco_fs_File $file the file you wish to modify
     * @throws Loco_error_WriteException
     * @return void
     */
    
public function preAuthorizeLoco_fs_File $file ){
        if( ! 
$this->fs_allowed ){
            
$file->getWriteContext()->authorize();
            
$this->fs_allowed true;
        }
        
// Disconnecting remote file system ensures the auth functions always start with direct file access
        
$file->getWriteContext()->disconnect();
    }


    
/**
     * Authorize for the creation of a file that does not exist
     * @param Loco_fs_File $file
     * @return bool whether file system is authorized NOT necessarily whether file is creatable
     */
    
public function authorizeCreateLoco_fs_File $file ){
        
$this->preAuthorize($file);
        if( 
$file->exists() ){
            
// translators: %s refers to the name of a new file to be created, but which already existed
            
throw new Loco_error_WriteExceptionsprintf__('%s already exists in this folder','loco-translate'), $file->basename() ) );
        }
        return 
$file->creatable() || $this->authorize($file);
    }


    
/**
     * Authorize for the update of a file that does exist
     * @param Loco_fs_File $file
     * @return bool whether file system is authorized NOT necessarily whether file is updatable
     */
    
public function authorizeUpdateLoco_fs_File $file ){
        
$this->preAuthorize($file);
        if( ! 
$file->exists() ){
            throw new 
Loco_error_WriteException("File doesn't exist, try authorizeCreate");
        }
        return 
$file->writable() || $this->authorize($file);
    }


    
/**
     * Authorize for update or creation, depending on whether file exists
     * @param Loco_fs_File $file
     * @return bool
     */
    
public function authorizeSaveLoco_fs_File $file ){
        
$this->preAuthorize($file);
        return ( 
$file->exists() ? $file->writable() : $file->creatable() ) || $this->authorize($file);
    }


    
/**
     * Authorize for copy (to same directory), meaning source file must exist and directory be writable
     * @param Loco_fs_File $file
     * @return bool
     */
    
public function authorizeCopyLoco_fs_File $file ){
        
$this->preAuthorize($file);
        if( ! 
$file->exists() ){
            throw new 
Loco_error_WriteException("Can't copy a file that doesn't exist");
        }
        return 
$file->creatable() || $this->authorize($file);
    }


    
/**
     * Authorize for move (to another path if given).
     * @param Loco_fs_File $source file being moved (must exist)
     * @param Loco_fs_File|null $target target path (should not exist)
     */
    
public function authorizeMoveLoco_fs_File $source, ?Loco_fs_File $target null ):bool {
        
// source is in charge of its own deletion
        
$result $this->authorizeDelete($source);
        
// target is in charge of copying original which it must also be able to read.
        
if( $target && ! $this->authorizeCreate($target) ){
            
$result false;
        }
        
// value returned will be false if at least one file requires we add credentials
        
return $result;
    }
    
    
    
/**
     * Authorize for the removal of an existing file
     * @param Loco_fs_File $file
     * @return bool whether file system is authorized NOT necessarily whether file is removable
     */
    
public function authorizeDeleteLoco_fs_File $file ){
        
$this->preAuthorize($file);
        if( ! 
$file->exists() ){
            throw new 
Loco_error_WriteException("Can't delete a file that doesn't exist");
        }
        return 
$file->deletable() || $this->authorize($file);
    }


    
/**
     * Connect file to credentials in posted data. Used when established in advance what connection is needed
     * @param Loco_fs_File $file
     * @return bool whether file system is authorized
     */    
    
public function authorizeConnectLoco_fs_File $file ){
        
$this->preAuthorize($file);
        
// front end may have posted that "direct" connection will work
        
$post Loco_mvc_PostParams::get();
        if( 
'direct' === $post->connection_type ){
            return 
true;
        }
        return 
$this->authorize($file);
    }


    
/**
     * Wraps `request_filesystem_credentials` negotiation to obtain a remote connection and buffer WordPress form output
     * Call before output started, because buffers.
     * @param Loco_fs_File $file
     * @return bool
     */
    
private function authorizeLoco_fs_File $file ){
        
// may already have authorized successfully
        
if( $this->fs instanceof WP_Filesystem_Base ){
            
$file->getWriteContext()->connect$this->fsfalse );
            return 
true;
        }
        
        
// may have already failed authorization
        
if( $this->form ){
            return 
false;
        }
        
        
// network access may be disabled
        
if( ! apply_filters('loco_allow_remote'true ) ){
            throw new 
Loco_error_WriteException('Remote connection required, but network access is disabled');
        }
    
        
// else begin new auth
        
$this->fs null;
        
$this->form '';
        
$this->creds_out = [];
        
        
// observe settings held temporarily in session
        
try {
            
$session Loco_data_Session::get();
            if( isset(
$session['loco-fs']) ){
                
$creds $session['loco-fs'];
                if( 
is_array($creds) && $this->tryCredentials($creds,$file) ){
                    
$this->creds_in = [];
                    return 
true;
                }
            }
        }
        catch( 
Exception $e ){
            
// tolerate session failure
        
}

        
$post Loco_mvc_PostParams::get();
        
$dflt = [ 'hostname' => '''username' => '''password' => '''public_key' => '''private_key' => '''connection_type' => '''_fs_nonce' => '' ];
        
$this->creds_in array_intersect_key$post->getArrayCopy(), $dflt );
        
        
// deliberately circumventing call to `get_filesystem_method`
        // risk of WordPress version compatibility issues, but only sane way to force a remote connection
        // @codeCoverageIgnoreStart
        
if( defined('FS_METHOD') && FS_METHOD ){
            
$type FS_METHOD;
            
// forcing direct access means request_filesystem_credentials will never give us a form :( 
            
if( 'direct' === $type ){
                
Loco_error_AdminNotices::debug('Cannot connect remotely when FS_METHOD is "direct"');
                return 
false;
            }
        }
        
// direct filesystem is OK if the front end already posted it
        
else if( 'direct' === $post->connection_type ){
            return 
true;
        }
        
// else perform same logic as request_filesystem_credentials does to establish type
        
else if( 'ssh' === $post->connection_type && extension_loaded('ssh2') && function_exists('stream_get_contents') ){
            
$type 'ssh2';
        }
        else if( 
extension_loaded('ftp') ){
            
$type 'ftpext';
        }
        else if( 
extension_loaded('sockets') || function_exists('fsockopen') ){
            
$type 'ftpsockets';
        }
        
// @codeCoverageIgnoreEnd
        
else {
            
$type '';
        }
        
        
// context is nonsense here as the system doesn't know what operation we're performing
        // testing directory write-permission when we're updating a file, for example.
        
$context '/ignore/this';
        
        
$type apply_filters'filesystem_method'$type$post->getArrayCopy(), $contexttrue );
        
        
// the only params we'll pass into form will be those used by the ajax fsConnect end point
        
$extra = [ 'loco-nonce''path''auth''dest' ];
        
        
// capture WordPress output during negotiation.
        
$buffer Loco_output_Buffer::start();

        
$creds request_filesystem_credentials''$typefalse$context$extra );
        if( 
is_array($creds) ){
            
// credentials passed through, should allow to connect if they are correct
            
if( $this->tryCredentials($creds,$file) ){
                
$this->persistCredentials();
                return 
true;
            }
            
// else there must be an error with the credentials
            
$error true;
            
// pull more useful connection error for display in form
            
if( isset($GLOBALS['wp_filesystem']) ){
                
$fs $GLOBALS['wp_filesystem'];
                
$GLOBALS['wp_filesystem'] = null;
                if( 
$fs && $fs->errors && $fs->errors->get_error_code() ){
                    
$error $fs->errors;
                }
            }
            
// annoyingly WordPress moves the error notice above the navigation tabs :-/
            
request_filesystem_credentials''$type$error$context$extra );
        }

        
// should now have unauthorized remote connection form
        
$this->form = (string) $buffer->close();
        if( 
'' === $this->form ){
            
Loco_error_AdminNotices::debug('Unknown error capturing output from request_filesystem_credentials');
        }
        return 
false;
    }


    
/**
     * @param array $creds credentials returned from request_filesystem_credentials
     * @param Loco_fs_File $file file to authorize write context
     * @return bool when credentials connected ok
     */
    
private function tryCredentials( array $credsLoco_fs_File $file ){
        
// lazy construct the file system from current credentials if possible
        // in typical WordPress style, after success the object will be held in a global.
        
if( WP_Filesystem$creds'/ignore/this/' ) ){
            
$this->fs $GLOBALS['wp_filesystem'];
            
// hook new file system into write context (specifying that connect has already been performed)
            
$file->getWriteContext()->connect$this->fsfalse );
            
$this->creds_out $creds;
            return 
true;
        }
        return 
false;
    }


    
/**
     * Set current credentials in session if settings allow
     * @return bool whether credentials persisted
     */
    
private function persistCredentials(){
        try {
            
$settings Loco_data_Settings::get();
            if( 
$settings['fs_persist'] ){
                
$session Loco_data_Session::get();
                
$session['loco-fs'] = $this->creds_out;
                
$session->persist();
                return 
true;
            }
        }
        catch( 
Exception $e ){
            
// tolerate session failure
            
Loco_error_AdminNotices::debug$e->getMessage() );
        }
        return 
false;
    }    


    
/**
     * Get working credentials that resulted in connection
     * @return array
     */
    
public function getOutputCredentials(){
        return 
$this->creds_out;
    }
   
    
    
/**
     * Get input credentials from original post.
     * this is not the same as getCredentials. It is designed for replay only, regardless of success
     * Note that input to request_filesystem_credentials is not the same as the output (specifically how hostname:port is handled)
     */
    
public function getInputCredentials(){
        return 
$this->creds_in;
    }


    
/**
     * Get currently configured filesystem API
     * @return WP_Filesystem_Direct
     */
    
public function getFileSystem(){
        if( ! 
$this->fs ){
            return 
self::direct();
        }
        return 
$this->fs;     
    }


    
/**
     * Check if a file is subject to WordPress automatic updates
     * @param Loco_fs_File $file
     * @return bool
     */
    
public function isAutoUpdatableLoco_fs_File $file ){
        
// all paths safe from auto-updates if auto-updates are completely disabled
        
if( $this->isAutoUpdateDenied() ){
            return 
false;
        }
        
// Auto-updates aren't denied, so ascertain location "type" and run through the same filters as WP_Automatic_Updater::should_update()
        
$type $file->getUpdateType();
        if( 
'' !== $type ){
            
// Since 5.5.0: "{type}_s_auto_update_enabled" filters auto-update status for themes and plugins
            // admins must also enable auto-updates on plugins and themes individually, but not checking that here. 
            
if( function_exists('wp_is_auto_update_enabled_for_type') && ('plugin'===$type||'theme'===$type) ){
                
$enabled = (bool) apply_filters"{$type}s_auto_update_enabled"true );
                if( 
$enabled ){
                    
// resolve given file to plugin/theme handle, so we can check if it's been enabled
                    
$bundle Loco_package_Bundle::fromFile($file);
                    if( 
$bundle instanceof Loco_package_Bundle ){
                        
$handle $bundle->getHandle();
                        
$option = (array) get_site_option"auto_update_{$type}s", [] );
                        
// var_dump( compact('handle','option') );
                        
if( ! in_array($handle,$option,true) ){
                            
$enabled false;
                        }
                    }
                }
                return 
$enabled;
            }
            
// WordPress updater will have {item} from remote API data which we don't have here.
            
$item = new stdClass;
            
$item->new_files false;
            
$item->autoupdate true;
            
$item->disable_autoupdate false;
            return 
apply_filters'auto_update_'.$typetrue$item );
        }
        
// else safe (not auto-updatable)
        
return false;
    }


    
/**
     * Check if system is configured to deny auto-updates
     * @return bool
     */
    
public function isAutoUpdateDenied(){
        
// WordPress >= 4.8 can disable auto updates completely with "automatic_updater" context
        
if( function_exists('wp_is_file_mod_allowed') && ! wp_is_file_mod_allowed('automatic_updater') ){
            return 
true;
        }
        
// else simply observe AUTOMATIC_UPDATER_DISABLED constant
        
if( apply_filters'automatic_updater_disabled'loco_constant('AUTOMATIC_UPDATER_DISABLED') ) ) {
            return 
true;
        }
        
// else nothing explicitly denying updates
        
return false;
    }

}