Completed
Branch new-addon-api (b7bde0)
by
unknown
18:29 queued 08:41
created

EEH_File   F

Complexity

Total Complexity 113

Size/Duplication

Total Lines 915
Duplicated Lines 11.04 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
dl 101
loc 915
rs 1.685
c 0
b 0
f 0
wmc 113
lcom 1
cbo 2

35 Methods

Rating   Name   Duplication   Size   Complexity  
A _get_wp_filesystem() 0 12 3
B loadAlternateWpFileSystem() 14 56 8
B loadWpFileSystem() 0 74 8
A display_request_filesystem_credentials_form() 0 6 2
B verify_filepath_and_permissions() 0 36 7
A _permissions_error_for_unreadable_filepath() 0 31 4
B ensure_folder_exists_and_is_writable() 8 34 9
A verify_is_writable() 0 17 4
B ensure_file_exists_and_is_writable() 8 24 7
A get_parent_folder() 0 10 2
A get_file_contents() 0 15 2
B write_to_file() 13 43 8
A delete() 0 5 1
A exists() 0 5 1
A is_readable() 0 5 1
A remove_filename_from_filepath() 0 4 1
A get_filename_from_filepath() 0 4 1
A get_file_extension() 0 4 1
A add_htaccess_deny_from_all() 10 11 3
A add_index_file() 9 15 3
A get_classname_from_filepath_with_standard_filename() 0 8 1
A standardise_directory_separators() 0 5 2
A end_with_directory_separator() 0 4 1
A standardise_and_end_with_directory_separator() 0 4 1
B get_contents_of_folders() 0 25 6
B copy() 14 31 6
A is_in_uploads_folder() 0 5 1
A convert_local_filepath_to_remote_filepath() 0 5 1
A chmod() 0 5 1
A permissions() 0 5 1
A owner() 0 5 1
A group() 0 5 1
B move() 0 33 6
A validateFileForCopyOrMove() 13 18 4
A validateFolderForCopyOrMove() 12 19 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like EEH_File often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use EEH_File, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 *
5
 * Class EEH_File
6
 *
7
 * Mostly methods are wrappers for WP_Filesystem. Primarily these methods should be used
8
 * when you intend to WRITE to the filesystem. Many of these methods throw an EE_Error
9
 * when the current filesystem user doesn't have permission to read/write the given file.
10
 * Note: that means if FS_METHOD is defined to be ssh or ftp, and the ftp password isn't
11
 * entered into wp-config.php, code like `EEH_File::is_readable()` will THROW AN EXCEPTION
12
 * when trying ot read anything, unless the user has just entered their ftp/ssh credentials
13
 * into the wp filesystem credentials form. See http://ottopress.com/2011/tutorial-using-the-wp_filesystem/
14
 * If you want to test your usage of EEH_File and WP_Filesystem, you can use our
15
 * filesystem debugger plugin: https://github.com/eventespresso/filesystem-debug-helper,
16
 * which simulates requiring ftp or ssh to access your site (even if your site is
17
 * actually local and you haven't set it up for ftp or ssh access)
18
 *
19
 * @package             Event Espresso
20
 * @subpackage          core
21
 * @author              Brent Christensen
22
 *
23
 *
24
 */
25
class EEH_File extends EEH_Base implements EEHI_File
26
{
27
28
    /**
29
     * @var string $_credentials_form
30
     */
31
    private static $_credentials_form;
32
33
    /**
34
     * @var WP_Filesystem_Base $_wp_filesystem
35
     */
36
    protected static $_wp_filesystem;
37
38
39
    /**
40
     * @param string $filepath      the filepath we want to work in. If its in the
41
     *                              wp uploads directory, we'll want to just use the filesystem directly.
42
     *                              If not provided, we have to assume its not in the uploads directory
43
     * @return WP_Filesystem_Base
44
     */
45
    private static function _get_wp_filesystem($filepath = ''): WP_Filesystem_Base
46
    {
47
        if (apply_filters(
48
            'FHEE__EEH_File___get_wp_filesystem__allow_using_filesystem_direct',
49
            $filepath && EEH_File::is_in_uploads_folder($filepath),
50
            $filepath
51
        )
52
        ) {
53
            return EEH_File::loadAlternateWpFileSystem();
54
        }
55
        return EEH_File::loadWpFileSystem();
56
    }
57
58
59
    /**
60
     * @return WP_Filesystem_Base
61
     */
62
    private static function loadAlternateWpFileSystem(): WP_Filesystem_Base
63
    {
64
        if (! EEH_File::$_wp_filesystem instanceof WP_Filesystem_Base) {
65
            require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php');
66
            $method             = 'direct';
67
            $wp_filesystem_file =
68
                apply_filters(
69
                    'filesystem_method_file',
70
                    ABSPATH . 'wp-admin/includes/class-wp-filesystem-' . $method . '.php',
71
                    $method
72
                );
73
            // added the following validation logic
74
            // because we allow the filesystem filepath to be filtered,
75
            // and are loading whatever file the path pointed to,
76
            // but we were not validating things in any way :scream_emoji:
77
            $valid_wp_filesystem_types = [
78
                'direct'     => 'WP_Filesystem_Direct',
79
                'ftpext'     => 'WP_Filesystem_FTPext',
80
                'ftpsockets' => 'WP_Filesystem_ftpsockets',
81
                'ssh2'       => 'WP_Filesystem_SSH2',
82
            ];
83
            $valid                     = false;
84
            $wp_filesystem_class       = '';
85
            foreach ($valid_wp_filesystem_types as $method => $filesystem_class) {
86
                // if file path matches for one of valid types, then toggle $valid to true
87
                if (strpos($wp_filesystem_file, $method) > 0) {
88
                    $valid               = true;
89
                    $wp_filesystem_class = $filesystem_class;
90
                }
91
            }
92 View Code Duplication
            if (! $valid || ! file_exists($wp_filesystem_file)) {
93
                EE_Error::add_error(
94
                    sprintf(
95
                        esc_html__(
96
                            'The supplied WP Filesystem filepath "%1$s" is either missing or invalid.',
97
                            'event_espresso'
98
                        ),
99
                        $wp_filesystem_file
100
                    ),
101
                    __FILE__,
102
                    __FUNCTION__,
103
                    __LINE__
104
                );
105
            }
106
            // check constants defined, just like in the wp-admin/includes/file.php WP_Filesystem()
107
            if (! defined('FS_CHMOD_DIR')) {
108
                define('FS_CHMOD_DIR', (fileperms(ABSPATH) & 0775 | 0755));
109
            }
110
            if (! defined('FS_CHMOD_FILE')) {
111
                define('FS_CHMOD_FILE', (fileperms(ABSPATH . 'index.php') & 0775 | 0644));
112
            }
113
            require_once($wp_filesystem_file);
114
            EEH_File::$_wp_filesystem = new $wp_filesystem_class([]);
115
        }
116
        return EEH_File::$_wp_filesystem;
117
    }
118
119
120
    /**
121
     * @return WP_Filesystem_Base
122
     */
123
    private static function loadWpFileSystem(): WP_Filesystem_Base
124
    {
125
        global $wp_filesystem;
126
        // no filesystem setup ???
127
        if (! $wp_filesystem instanceof WP_Filesystem_Base) {
128
            // if some eager beaver's just trying to get in there too early...
129
            // let them do it, because we are one of those eager beavers! :P
130
            /**
131
             * more explanations are probably merited.
132
             * http://codex.wordpress.org/Filesystem_API#Initializing_WP_Filesystem_Base
133
             * says WP_Filesystem should be used after 'wp_loaded', but currently EE's activation process
134
             * is setup to mostly happen on 'init', and refactoring to have it happen on
135
             * 'wp_loaded' is too much work on a BETA milestone.
136
             * So this fix is expected to work if the WP files are owned by the server user,
137
             * but probably not if the user needs to enter their FTP credentials to modify files
138
             * and there may be troubles if the WP files are owned by a different user
139
             * than the server user. But both of these issues should exist in 4.4 and earlier too
140
             */
141
            // if (false && ! did_action('wp_loaded')) {
142
            //     $msg =
143
            //         esc_html__(
144
            //             'An attempt to access and/or write to a file on the server could not be completed due to a lack of sufficient credentials.',
145
            //             'event_espresso'
146
            //         );
147
            //     if (WP_DEBUG) {
148
            //         $msg .= '<br />' . esc_html__(
149
            //             'The WP Filesystem can not be accessed until after the "wp_loaded" hook has run, so it\'s best not to attempt access until the "admin_init" hookpoint.',
150
            //             'event_espresso'
151
            //         );
152
            //     }
153
            //     EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
154
            // }
155
            // should be loaded if we are past the wp_loaded hook...
156
            if (! function_exists('WP_Filesystem') || ! function_exists('submit_button')) {
157
                require_once(ABSPATH . 'wp-admin/includes/file.php');
158
                require_once(ABSPATH . 'wp-admin/includes/template.php');
159
            }
160
            // turn on output buffering so that we can capture the credentials form
161
            ob_start();
162
            $credentials = request_filesystem_credentials(false);
163
            // store credentials form for the time being
164
            EEH_File::$_credentials_form = ob_get_clean();
165
            // if credentials do NOT exist
166
            if ($credentials === false) {
167
                add_action('admin_notices', ['EEH_File', 'display_request_filesystem_credentials_form'], 999);
168
                EE_Error::add_error(
169
                    esc_html__(
170
                        'An attempt to access and/or write to a file on the server could not be completed due to a lack of sufficient credentials.',
171
                        'event_espresso'
172
                    ),
173
                    __FILE__,
174
                    __FUNCTION__,
175
                    __LINE__
176
                );
177
            }
178
            // basically check for direct or previously configured access
179
            if (! WP_Filesystem($credentials)
180
                && is_wp_error($wp_filesystem->errors)
181
                && $wp_filesystem->errors->get_error_code()
182
            ) {
183
                add_action('admin_notices', ['EEH_File', 'display_request_filesystem_credentials_form'], 999);
184
                EE_Error::add_error(
185
                    sprintf(
186
                        esc_html__('WP Filesystem Error: $1%s', 'event_espresso'),
187
                        $wp_filesystem->errors->get_error_message()
188
                    ),
189
                    __FILE__,
190
                    __FUNCTION__,
191
                    __LINE__
192
                );
193
            }
194
        }
195
        return $wp_filesystem;
196
    }
197
198
199
    /**
200
     * display_request_filesystem_credentials_form
201
     */
202
    public static function display_request_filesystem_credentials_form()
203
    {
204
        if (! empty(EEH_File::$_credentials_form)) {
205
            echo '<div class="updated espresso-notices-attention"><p>' . EEH_File::$_credentials_form . '</p></div>';
206
        }
207
    }
208
209
210
    /**
211
     *    verify_filepath_and_permissions
212
     *    checks that a file is readable and has sufficient file permissions set to access
213
     *
214
     * @access public
215
     * @param string $full_file_path - full server path to the folder or file
216
     * @param string $file_name      - name of file if checking a file
217
     * @param string $file_ext       - file extension (ie: "php") if checking a file
218
     * @param string $type_of_file   - general type of file (ie: "module"), this is only used to improve error messages
219
     * @return bool
220
     */
221
    public static function verify_filepath_and_permissions(
222
        string $full_file_path = '',
223
        string $file_name = '',
224
        string $file_ext = '',
225
        string $type_of_file = ''
226
    ): bool {
227
        // load WP_Filesystem and set file permissions
228
        $wp_filesystem  = EEH_File::_get_wp_filesystem($full_file_path);
229
        $full_file_path = EEH_File::standardise_directory_separators($full_file_path);
230
        if (! $wp_filesystem->is_readable(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path))) {
231
            $file_name = ! empty($type_of_file) ? $file_name . ' ' . $type_of_file : $file_name;
232
            $file_name .= ! empty($file_ext) ? ' file' : ' folder';
233
            $msg       = sprintf(
234
                esc_html__(
235
                    'The requested %1$s could not be found or is not readable, possibly due to an incorrect filepath, or incorrect file permissions.%2$s',
236
                    'event_espresso'
237
                ),
238
                $file_name,
239
                '<br />'
240
            );
241
            if (EEH_File::exists($full_file_path)) {
242
                $msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_file_path, $type_of_file);
243
            } else {
244
                // no file permissions means the file was not found
245
                $msg .= sprintf(
246
                    esc_html__('Please ensure the following path is correct: "%s".', 'event_espresso'),
247
                    $full_file_path
248
                );
249
            }
250
            if (defined('WP_DEBUG') && WP_DEBUG) {
251
                EE_Error::add_error($msg . '||' . $msg, __FILE__, __FUNCTION__, __LINE__);
252
            }
253
            return false;
254
        }
255
        return true;
256
    }
257
258
259
    /**
260
     * _permissions_error_for_unreadable_filepath - attempts to determine why permissions are set incorrectly for a
261
     * file or folder
262
     *
263
     * @access private
264
     * @param string $full_file_path - full server path to the folder or file
265
     * @param string $type_of_file   - general type of file (ie: "module"), this is only used to improve error messages
266
     * @return string
267
     */
268
    private static function _permissions_error_for_unreadable_filepath(
269
        string $full_file_path = '',
270
        string $type_of_file = ''
271
    ): string {
272
        // load WP_Filesystem and set file permissions
273
        $wp_filesystem = EEH_File::_get_wp_filesystem($full_file_path);
274
        // check file permissions
275
        $perms = $wp_filesystem->getchmod(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path));
276
        if ($perms) {
277
            // file permissions exist, but way be set incorrectly
278
            $type_of_file = ! empty($type_of_file) ? $type_of_file . ' ' : '';
279
            $type_of_file .= ! empty($type_of_file) ? 'file' : 'folder';
280
            return ' ' . sprintf(
281
                esc_html__(
282
                    'File permissions for the requested %1$s are currently set at "%2$s". The recommended permissions are 644 for files and 755 for folders.',
283
                    'event_espresso'
284
                ),
285
                $type_of_file,
286
                $perms
287
            );
288
        } else {
289
            // file exists but file permissions could not be read ?!?!
290
            return ' ' . sprintf(
291
                esc_html__(
292
                    'Please ensure that the server and/or PHP configuration allows the current process to access the following file: "%s".',
293
                    'event_espresso'
294
                ),
295
                $full_file_path
296
            );
297
        }
298
    }
299
300
301
    /**
302
     * ensure_folder_exists_and_is_writable
303
     * ensures that a folder exists and is writable, will attempt to create folder if it does not exist
304
     * Also ensures all the parent folders exist, and if not tries to create them.
305
     * Also, if this function creates the folder, adds a .htaccess file and index.html file
306
     *
307
     * @param string $folder
308
     * @return bool false if folder isn't writable; true if it exists and is writeable,
309
     */
310
    public static function ensure_folder_exists_and_is_writable(string $folder = ''): bool
311
    {
312
        if (empty($folder)) {
313
            return false;
314
        }
315
        // remove ending /
316
        $folder        = EEH_File::standardise_directory_separators(rtrim($folder, '/\\'));
317
        $parent_folder = EEH_File::get_parent_folder($folder);
318
        // add / to folder
319
        $folder        = EEH_File::end_with_directory_separator($folder);
320
        $wp_filesystem = EEH_File::_get_wp_filesystem($folder);
321
        $remote_dir    = EEH_File::convert_local_filepath_to_remote_filepath($folder);
322
        if (! $wp_filesystem->is_dir($remote_dir)) {
323
            // ok so it doesn't exist. Does its parent? Can we write to it?
324
            if (! EEH_File::ensure_folder_exists_and_is_writable($parent_folder)) {
325
                return false;
326
            }
327
            if (! EEH_File::verify_is_writable($parent_folder)) {
328
                return false;
329
            }
330 View Code Duplication
            if (! $wp_filesystem->mkdir(EEH_File::convert_local_filepath_to_remote_filepath($folder))) {
331
                if (defined('WP_DEBUG') && WP_DEBUG) {
332
                    $msg = sprintf(__('"%s" could not be created.', 'event_espresso'), $folder);
333
                    $msg .= EEH_File::_permissions_error_for_unreadable_filepath($folder);
334
                    EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
335
                }
336
                return false;
337
            }
338
            EEH_File::add_index_file($folder);
339
        } elseif (! EEH_File::verify_is_writable($folder)) {
340
            return false;
341
        }
342
        return true;
343
    }
344
345
346
    /**
347
     * verify_is_writable - checks if a file or folder is writable
348
     *
349
     * @param string $full_path      - full server path to file or folder
350
     * @param string $file_or_folder - whether checking a file or folder
351
     * @return bool
352
     */
353
    public static function verify_is_writable(string $full_path = '', string $file_or_folder = 'folder'): bool
354
    {
355
        // load WP_Filesystem and set file permissions
356
        $wp_filesystem = EEH_File::_get_wp_filesystem($full_path);
357
        $full_path     = EEH_File::standardise_directory_separators($full_path);
358
        $remote_path   = EEH_File::convert_local_filepath_to_remote_filepath($full_path);
359
        $remote_path   = rtrim($remote_path, '/\\');
360
        if (! $wp_filesystem->is_writable($remote_path)) {
361
            if (defined('WP_DEBUG') && WP_DEBUG) {
362
                $msg = sprintf(__('The "%1$s" %2$s is not writable.', 'event_espresso'), $full_path, $file_or_folder);
363
                $msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_path);
364
                EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
365
            }
366
            return false;
367
        }
368
        return true;
369
    }
370
371
372
    /**
373
     * ensure_file_exists_and_is_writable
374
     * ensures that a file exists and is writable, will attempt to create file if it does not exist.
375
     * Also ensures all the parent folders exist, and if not tries to create them.
376
     *
377
     * @param string $full_file_path
378
     * @return bool
379
     */
380
    public static function ensure_file_exists_and_is_writable(string $full_file_path = ''): bool
381
    {
382
        // load WP_Filesystem and set file permissions
383
        $wp_filesystem  = EEH_File::_get_wp_filesystem($full_file_path);
384
        $full_file_path = EEH_File::standardise_directory_separators($full_file_path);
385
        $parent_folder  = EEH_File::get_parent_folder($full_file_path);
386
        if (! EEH_File::exists($full_file_path)) {
387
            if (! EEH_File::ensure_folder_exists_and_is_writable($parent_folder)) {
388
                return false;
389
            }
390 View Code Duplication
            if (! $wp_filesystem->touch(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path))) {
391
                if (defined('WP_DEBUG') && WP_DEBUG) {
392
                    $msg = sprintf(__('The "%s" file could not be created.', 'event_espresso'), $full_file_path);
393
                    $msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_file_path);
394
                    EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
395
                }
396
                return false;
397
            }
398
        }
399
        if (! EEH_File::verify_is_writable($full_file_path, 'file')) {
400
            return false;
401
        }
402
        return true;
403
    }
404
405
406
    /**
407
     * Gets the parent folder. If provided with file, gets the folder that contains it.
408
     * If provided a folder, gets its parent folder.
409
     *
410
     * @param string $file_or_folder_path
411
     * @return string parent folder, ENDING with a directory separator
412
     */
413
    public static function get_parent_folder(string $file_or_folder_path): string
414
    {
415
        // find the last /, ignoring a / on the very end
416
        // eg if given "/var/something/somewhere/", we want to get "somewhere"'s
417
        // parent folder, "/var/something/"
418
        $ds = strlen($file_or_folder_path) > 1
419
            ? strrpos($file_or_folder_path, '/', -2)
420
            : strlen($file_or_folder_path);
421
        return substr($file_or_folder_path, 0, $ds + 1);
422
    }
423
424
425
    /**
426
     * get_file_contents
427
     *
428
     * @param string $full_file_path
429
     * @return string
430
     */
431
    public static function get_file_contents(string $full_file_path = ''): string
432
    {
433
        $full_file_path = EEH_File::standardise_directory_separators($full_file_path);
434
        if (EEH_File::verify_filepath_and_permissions(
435
            $full_file_path,
436
            EEH_File::get_filename_from_filepath($full_file_path),
437
            EEH_File::get_file_extension($full_file_path)
438
        )
439
        ) {
440
            // load WP_Filesystem and set file permissions
441
            $wp_filesystem = EEH_File::_get_wp_filesystem($full_file_path);
442
            return $wp_filesystem->get_contents(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path));
443
        }
444
        return '';
445
    }
446
447
448
    /**
449
     * write_file
450
     *
451
     * @param string $full_file_path
452
     * @param string $file_contents - the content to be written to the file
453
     * @param string $file_type
454
     * @return bool
455
     */
456
    public static function write_to_file(
457
        string $full_file_path = '',
458
        string $file_contents = '',
459
        string $file_type = ''
460
    ): bool {
461
        $full_file_path = EEH_File::standardise_directory_separators($full_file_path);
462
        $file_type      = ! empty($file_type) ? rtrim($file_type, ' ') . ' ' : '';
463
        $folder         = EEH_File::remove_filename_from_filepath($full_file_path);
464 View Code Duplication
        if (! EEH_File::verify_is_writable($folder)) {
465
            if (defined('WP_DEBUG') && WP_DEBUG) {
466
                $msg =
467
                    sprintf(
468
                        esc_html__('The %1$sfile located at "%2$s" is not writable.', 'event_espresso'),
469
                        $file_type,
470
                        $full_file_path
471
                    );
472
                $msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_file_path);
473
                EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
474
            }
475
            return false;
476
        }
477
        // load WP_Filesystem and set file permissions
478
        $wp_filesystem = EEH_File::_get_wp_filesystem($full_file_path);
479
        // write the file
480
        if (! $wp_filesystem->put_contents(
481
            EEH_File::convert_local_filepath_to_remote_filepath($full_file_path),
482
            $file_contents
483
        )
484
        ) {
485
            if (defined('WP_DEBUG') && WP_DEBUG) {
486
                $msg =
487
                    sprintf(
488
                        esc_html__('The %1$sfile located at "%2$s" could not be written to.', 'event_espresso'),
489
                        $file_type,
490
                        $full_file_path
491
                    );
492
                $msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_file_path, 'f');
493
                EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
494
            }
495
            return false;
496
        }
497
        return true;
498
    }
499
500
501
    /**
502
     * Wrapper for WP_Filesystem_Base::delete
503
     *
504
     * @param string         $filepath
505
     * @param boolean        $recursive
506
     * @param boolean|string $type 'd' for directory, 'f' for file
507
     * @return boolean
508
     */
509
    public static function delete(string $filepath, bool $recursive = false, $type = false): bool
510
    {
511
        $wp_filesystem = EEH_File::_get_wp_filesystem();
512
        return $wp_filesystem->delete($filepath, $recursive, $type);
513
    }
514
515
516
    /**
517
     * exists
518
     * checks if a file exists using the WP filesystem
519
     *
520
     * @param string $full_file_path
521
     * @return bool
522
     */
523
    public static function exists(string $full_file_path = ''): bool
524
    {
525
        $wp_filesystem = EEH_File::_get_wp_filesystem($full_file_path);
526
        return $wp_filesystem->exists(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path));
527
    }
528
529
530
    /**
531
     * is_readable
532
     * checks if a file is_readable using the WP filesystem
533
     *
534
     * @param string $full_file_path
535
     * @return bool
536
     */
537
    public static function is_readable(string $full_file_path = ''): bool
538
    {
539
        $wp_filesystem = EEH_File::_get_wp_filesystem($full_file_path);
540
        return $wp_filesystem->is_readable(EEH_File::convert_local_filepath_to_remote_filepath($full_file_path));
541
    }
542
543
544
    /**
545
     * remove_filename_from_filepath
546
     * given a full path to a file including the filename itself, this removes  the filename and returns the path, up
547
     * to, but NOT including the filename OR slash
548
     *
549
     * @param string $full_file_path
550
     * @return string
551
     */
552
    public static function remove_filename_from_filepath(string $full_file_path = ''): string
553
    {
554
        return pathinfo($full_file_path, PATHINFO_DIRNAME);
555
    }
556
557
558
    /**
559
     * get_filename_from_filepath. Arguably the same as basename()
560
     *
561
     * @param string $full_file_path
562
     * @return string
563
     */
564
    public static function get_filename_from_filepath(string $full_file_path = ''): string
565
    {
566
        return pathinfo($full_file_path, PATHINFO_BASENAME);
567
    }
568
569
570
    /**
571
     * get_file_extension
572
     *
573
     * @param string $full_file_path
574
     * @return string
575
     */
576
    public static function get_file_extension(string $full_file_path = ''): string
577
    {
578
        return pathinfo($full_file_path, PATHINFO_EXTENSION);
579
    }
580
581
582
    /**
583
     * add_htaccess_deny_from_all so the web server cannot access this folder
584
     *
585
     * @param string $folder
586
     * @return bool
587
     */
588 View Code Duplication
    public static function add_htaccess_deny_from_all(string $folder = ''): bool
589
    {
590
        $folder = EEH_File::standardise_and_end_with_directory_separator($folder);
591
        if (! EEH_File::exists($folder . '.htaccess')) {
592
            if (! EEH_File::write_to_file($folder . '.htaccess', 'deny from all', '.htaccess')) {
593
                return false;
594
            }
595
        }
596
597
        return true;
598
    }
599
600
601
    /**
602
     * Adds an index file to this folder, so folks can't list all the file's contents
603
     *
604
     * @param string $folder
605
     * @return boolean
606
     */
607 View Code Duplication
    public static function add_index_file(string $folder): bool
608
    {
609
        $folder = EEH_File::standardise_and_end_with_directory_separator($folder);
610
        if (! EEH_File::exists($folder . 'index.php')) {
611
            if (! EEH_File::write_to_file(
612
                $folder . 'index.php',
613
                'You are not permitted to read from this folder',
614
                '.php'
615
            )
616
            ) {
617
                return false;
618
            }
619
        }
620
        return true;
621
    }
622
623
624
    /**
625
     * Given that the file in $file_path has the normal name, (ie, CLASSNAME.whatever.php),
626
     * extract that classname.
627
     *
628
     * @param string $file_path
629
     * @return string
630
     */
631
    public static function get_classname_from_filepath_with_standard_filename(string $file_path): string
632
    {
633
        // extract file from path
634
        $filename = basename($file_path);
635
        // now remove the first period and everything after
636
        $pos_of_first_period = strpos($filename, '.');
637
        return substr($filename, 0, $pos_of_first_period);
638
    }
639
640
641
    /**
642
     * standardise_directory_separators
643
     *  convert all directory separators in a file path.
644
     *
645
     * @param string $file_path
646
     * @param bool   $rtrim will remove trailing backslash
647
     * @return string
648
     */
649
    public static function standardise_directory_separators(string $file_path, bool $rtrim = false): string
650
    {
651
        $file_path = $rtrim ? rtrim($file_path, '/\\') : $file_path;
652
        return str_replace(['\\', '/'], '/', $file_path);
653
    }
654
655
656
    /**
657
     * end_with_directory_separator
658
     *  ensures that file path ends with '/'
659
     *
660
     * @param string $file_path
661
     * @return string
662
     */
663
    public static function end_with_directory_separator(string $file_path): string
664
    {
665
        return rtrim($file_path, '/\\') . '/';
666
    }
667
668
669
    /**
670
     * shorthand for both EEH_FIle::end_with_directory_separator AND EEH_File::standardise_directory_separators
671
     *
672
     * @param string $file_path
673
     * @return string
674
     */
675
    public static function standardise_and_end_with_directory_separator(string $file_path): string
676
    {
677
        return self::end_with_directory_separator(self::standardise_directory_separators($file_path));
678
    }
679
680
681
    /**
682
     * takes the folder name (with or without trailing slash) and finds the files it in,
683
     * and what the class's name inside of each should be.
684
     *
685
     * @param array   $folder_paths
686
     * @param boolean $index_numerically if TRUE, the returned array will be indexed numerically;
687
     *                                   if FALSE (Default), returned array will be indexed by the filenames minus
688
     *                                   extensions. Set it TRUE if you know there are files in the directory with the
689
     *                                   same name but different extensions
690
     * @return array if $index_numerically == TRUE keys are numeric ,
691
     *                                   if $index_numerically == FALSE (Default) keys are what the class names SHOULD
692
     *                                   be; and values are their file paths
693
     */
694
    public static function get_contents_of_folders(array $folder_paths = [], bool $index_numerically = false): array
695
    {
696
        $class_to_folder_path = [];
697
        foreach ($folder_paths as $folder_path) {
698
            $folder_path = self::standardise_and_end_with_directory_separator($folder_path);
699
            // load WP_Filesystem and set file permissions
700
            $files_in_folder      = glob($folder_path . '*.php');
701
            $class_to_folder_path = [];
702
            if ($files_in_folder) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $files_in_folder of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
703
                foreach ($files_in_folder as $file_path) {
704
                    // only add files, not folders
705
                    if (! is_dir($file_path)) {
706
                        if ($index_numerically) {
707
                            $class_to_folder_path[] = $file_path;
708
                        } else {
709
                            $classname                          =
710
                                self::get_classname_from_filepath_with_standard_filename($file_path);
711
                            $class_to_folder_path[ $classname ] = $file_path;
712
                        }
713
                    }
714
                }
715
            }
716
        }
717
        return $class_to_folder_path;
718
    }
719
720
721
    /**
722
     * Copies a file. Mostly a wrapper of WP_Filesystem::copy
723
     *
724
     * @param string  $source_file
725
     * @param string  $destination_file
726
     * @param boolean $overwrite
727
     * @return boolean success
728
     */
729
    public static function copy(string $source_file, string $destination_file, bool $overwrite = false): bool
730
    {
731
        $source_file      = EEH_File::validateFileForCopyOrMove($source_file);
732
        $destination_file = EEH_File::validateFolderForCopyOrMove($destination_file);
733
        if (! $source_file || ! $destination_file) {
734
            return false;
735
        }
736
        // load WP_Filesystem and set file permissions
737
        $wp_filesystem = EEH_File::_get_wp_filesystem($destination_file);
738
        // write the file
739
        $copied = $wp_filesystem->copy(
740
            EEH_File::convert_local_filepath_to_remote_filepath($source_file),
741
            EEH_File::convert_local_filepath_to_remote_filepath($destination_file),
742
            $overwrite
743
        );
744 View Code Duplication
        if (! $copied) {
745
            if (defined('WP_DEBUG') && WP_DEBUG) {
746
                $msg = sprintf(
747
                    esc_html__(
748
                        'Attempted writing to file %1$s, but could not, probably because of permissions issues',
749
                        'event_espresso'
750
                    ),
751
                    $source_file
752
                );
753
                $msg .= EEH_File::_permissions_error_for_unreadable_filepath($source_file, 'f');
754
                EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
755
            }
756
            return false;
757
        }
758
        return true;
759
    }
760
761
762
    /**
763
     * Reports whether or not the filepath is in the EE uploads folder or not
764
     *
765
     * @param string $filepath
766
     * @return boolean
767
     */
768
    public static function is_in_uploads_folder(string $filepath): bool
769
    {
770
        $uploads = wp_upload_dir();
771
        return strpos($filepath, $uploads['basedir']) === 0;
772
    }
773
774
775
    /**
776
     * Given a "local" filepath (what you probably thought was the only filepath),
777
     * converts it into a "remote" filepath (the filepath the currently-in-use
778
     * $wp_filesystem needs to use access the folder or file).
779
     * See http://wordpress.stackexchange.com/questions/124900/using-wp-filesystem-in-plugins
780
     *
781
     * @param string $local_filepath the filepath to the folder/file locally
782
     * @return string the remote filepath (eg the filepath the filesystem method, eg
783
     *                               ftp or ssh, will use to access the folder
784
     */
785
    public static function convert_local_filepath_to_remote_filepath(string $local_filepath): string
786
    {
787
        $wp_filesystem = EEH_File::_get_wp_filesystem($local_filepath);
788
        return str_replace(WP_CONTENT_DIR . '/', $wp_filesystem->wp_content_dir(), $local_filepath);
789
    }
790
791
792
    /**
793
     * wrapper for WP_Filesystem::chmod()
794
     *
795
     * @param string    $file      Path to the file.
796
     * @param int|false $mode      Optional. The permissions as octal number, usually 0644 for files,
797
     *                             0755 for directories. Default false.
798
     * @param bool      $recursive Optional. If set to true, changes file permissions recursively.
799
     *                             Default false.
800
     * @return bool True on success, false on failure.
801
     */
802
    public static function chmod(string $file, $mode = false, bool $recursive = false): bool
803
    {
804
        $wp_filesystem = EEH_File::_get_wp_filesystem($file);
805
        return $wp_filesystem->chmod($file, $mode, $recursive);
806
    }
807
808
809
    /**
810
     * wrapper for WP_Filesystem::getchmod()
811
     *
812
     * @param string $file Path to the file.
813
     * @return string Mode of the file (the last 3 digits).
814
     */
815
    public static function permissions(string $file): string
816
    {
817
        $wp_filesystem = EEH_File::_get_wp_filesystem($file);
818
        return $wp_filesystem->getchmod($file);
819
    }
820
821
822
    /**
823
     * wrapper for WP_Filesystem::owner()
824
     *
825
     * @param string $file Path to the file.
826
     * @return string|false Username of the owner on success, false on failure.
827
     */
828
    public static function owner(string $file)
829
    {
830
        $wp_filesystem = EEH_File::_get_wp_filesystem($file);
831
        return $wp_filesystem->owner($file);
832
    }
833
834
835
    /**
836
     * wrapper for WP_Filesystem::group()
837
     *
838
     * @param string $file Path to the file.
839
     * @return string|false The group on success, false on failure.
840
     */
841
    public static function group(string $file)
842
    {
843
        $wp_filesystem = EEH_File::_get_wp_filesystem($file);
844
        return $wp_filesystem->group($file);
845
    }
846
847
848
    /**
849
     * wrapper for WP_Filesystem::move()
850
     *
851
     * @param string $source      Path to the source file.
852
     * @param string $destination Path to the destination file.
853
     * @param bool   $overwrite   Optional. Whether to overwrite the destination file if it exists.
854
     *                            Default false.
855
     * @return bool True on success, false on failure.
856
     */
857
    public static function move(string $source, string $destination, bool $overwrite = false): bool
858
    {
859
        // throw new RuntimeException("source: {$source} && destination: {$destination}");
860
        $source      = EEH_File::validateFileForCopyOrMove($source);
861
        $destination = EEH_File::validateFolderForCopyOrMove($destination);
862
        if (! $source || ! $destination) {
863
            return false;
864
        }
865
        $wp_filesystem = EEH_File::_get_wp_filesystem($source);
866
        if ($wp_filesystem->move($source, $destination, $overwrite)) {
867
            return true;
868
        }
869
        if (defined('WP_DEBUG') && WP_DEBUG) {
870
            $file        = EEH_File::convert_local_filepath_to_remote_filepath($source);
871
            $owner       = EEH_File::owner($file);
872
            $group       = EEH_File::group($file);
873
            $permissions = EEH_File::permissions($file);
874
            EE_Error::add_error(
875
                sprintf(
876
                    esc_html__(
877
                        'Unable to move the file "%1$s" to new location (possible permissions errors). The existing "owner:group permissions" for the file are: "%2$s"',
878
                        'event_espresso'
879
                    ),
880
                    $destination,
881
                    "{$owner}:{$group} $permissions"
882
                ),
883
                __FILE__,
884
                __FUNCTION__,
885
                __LINE__
886
            );
887
        }
888
        return false;
889
    }
890
891
892
    /**
893
     * @param string $source_file
894
     * @return string
895
     */
896
    private static function validateFileForCopyOrMove(string $source_file): string
897
    {
898
        $full_source_path = EEH_File::standardise_directory_separators($source_file);
899 View Code Duplication
        if (! EEH_File::exists($full_source_path)) {
900
            if (defined('WP_DEBUG') && WP_DEBUG) {
901
                $msg =
902
                    sprintf(
903
                        esc_html__('The file located at "%2$s" is not readable or doesn\'t exist.', 'event_espresso'),
904
                        '',
905
                        $full_source_path
906
                    );
907
                $msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_source_path);
908
                EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
909
            }
910
            return '';
911
        }
912
        return $full_source_path;
913
    }
914
915
916
    /**
917
     * @param string $destination_file
918
     * @return string
919
     */
920
    private static function validateFolderForCopyOrMove(string $destination_file): string
921
    {
922
        $full_dest_path = EEH_File::standardise_directory_separators($destination_file);
923
        $folder         = EEH_File::remove_filename_from_filepath($full_dest_path);
924
        EEH_File::ensure_folder_exists_and_is_writable($folder);
925 View Code Duplication
        if (! EEH_File::verify_is_writable($folder)) {
926
            if (defined('WP_DEBUG') && WP_DEBUG) {
927
                $msg = sprintf(
928
                    esc_html__('The file located at "%2$s" is not writable.', 'event_espresso'),
929
                    '',
930
                    $full_dest_path
931
                );
932
                $msg .= EEH_File::_permissions_error_for_unreadable_filepath($full_dest_path);
933
                EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
934
            }
935
            return '';
936
        }
937
        return $full_dest_path;
938
    }
939
}
940