PluginProbe ʕ •ᴥ•ʔ
WP 2FA – Two-factor authentication for WordPress / 2.8.0
WP 2FA – Two-factor authentication for WordPress v2.8.0
4.0.0 1.7.1 2.0.0 2.0.1 2.1.0 2.2.0 2.2.1 2.3.0 2.4.0 2.4.1 2.4.2 2.5.0 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.8.0 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0 3.0.1 3.1.0 3.1.1 3.1.1.2 trunk 1.2.0 1.3.0 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.1 1.6.2 1.7.0
wp-2fa / includes / classes / Admin / Helpers / class-file-writer.php
wp-2fa / includes / classes / Admin / Helpers Last commit date
class-ajax-helper.php 1 year ago class-classes-helper.php 1 year ago class-file-writer.php 1 year ago class-methods-helper.php 1 year ago class-php-helper.php 1 year ago class-user-helper.php 1 year ago class-wp-helper.php 1 year ago index.php 1 year ago
class-file-writer.php
809 lines
1 <?php
2 /**
3 * Responsible for the File writing operations
4 *
5 * @package wp2fa
6 * @subpackage helpers
7 * @since 2.4.0
8 * @copyright 2024 Melapress
9 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
10 * @link https://wordpress.org/plugins/wp-2fa/
11 */
12
13 namespace WP2FA\Admin\Helpers;
14
15 defined( 'ABSPATH' ) || exit; // Exit if accessed directly.
16
17 /**
18 * File writer settings class
19 */
20 if ( ! class_exists( '\WP2FA\Admin\Helpers\File_Writer' ) ) {
21
22 /**
23 * All the file operations must go trough this class.
24 *
25 * @since 2.4.0
26 */
27 class File_Writer {
28
29 public const SECRET_NAME = 'WP2FA_ENCRYPT_KEY';
30
31 public const WP2FA_UPLOADS_DIR = 'wp-2fa-data';
32
33 /**
34 * Saves a secret key in `wp-config.php`.
35 *
36 * @param string $secret The secret key to save.
37 *
38 * @return bool
39 *
40 * @since 2.4.0
41 */
42 public static function save_secret_key( string $secret ): bool {
43 if ( ! self::can_write_to_file( self::get_wp_config_file_path() ) ) {
44 return false;
45 }
46
47 $file = self::get_wp_config_file_path();
48 $contents = self::read( $file );
49
50 if ( false === $contents ) {
51 return false;
52 }
53
54 \set_error_handler( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
55 function ( $err_severity, $err_msg, $err_file, $err_line, array $err_context ) {
56 throw new \Error( $err_msg, 0, $err_severity, $err_file, $err_line ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
57 },
58 E_WARNING
59 );
60
61 try {
62 $current_secret = constant( self::SECRET_NAME );
63 } catch ( \Error $e ) {
64 $current_secret = null;
65 }
66
67 restore_error_handler();
68
69 $matches_found = $current_secret ? substr_count( $contents, $current_secret ) : 0;
70
71 if ( ! $current_secret || ! $matches_found ) {
72 if ( substr_count( $contents, self::SECRET_NAME ) ) {
73
74 $line_ending = self::get_line_ending( $contents );
75
76 $contents = explode( $line_ending, $contents );
77
78 foreach ( $contents as $key => $line ) {
79 if ( stristr( $line, self::SECRET_NAME ) ) {
80 unset( $contents[ $key ] );
81 }
82 }
83
84 $contents = implode( $line_ending, array_values( $contents ) );
85 self::write( $file, $contents );
86 }
87 self::write_wp_config( '/** WP 2FA plugin data encryption key. For more information please visit melapress.com */' . "\n" . 'define( \'' . self::SECRET_NAME . '\', \'' . $secret . '\' );' );
88 return true;
89 }
90
91 if ( $matches_found > 1 ) {
92 return false;
93 }
94
95 $replaced = str_replace( $current_secret, $secret, $contents );
96
97 if ( ! $replaced ) {
98 return false;
99 }
100
101 $written = self::write( $file, $replaced );
102
103 if ( false === $written ) {
104 return false;
105 }
106
107 return true;
108 }
109
110 /**
111 * Gets the permissions of given directory
112 *
113 * @param string $dir - The name of the directory to check.
114 *
115 * @return bool|int
116 *
117 * @since 2.4.0
118 */
119 public static function get_permissions( string $dir ) {
120 if ( ! is_dir( $dir ) ) {
121 return false;
122 }
123
124 if ( ! PHP_Helper::is_callable( 'fileperms' ) ) {
125 return false;
126 }
127
128 $dir = rtrim( $dir, '/' );
129 // phpcs:ignore -- Have Tide ignore the following line. We use arguments that don't exist in early versions, but these versions ignore the arguments.
130 @clearstatcache( true, $dir );
131
132 return fileperms( $dir ) & 0777;
133 }
134
135 /**
136 * Writes a content to a given file
137 *
138 * @param string $file - The file to write to.
139 * @param string $contents - The contents of the file to write.
140 * @param boolean $append - Append the contents of the file or overwrite.
141 *
142 * @return mixed
143 *
144 * @since 2.4.0
145 */
146 public static function write( string $file, string $contents, $append = false ) {
147 $callable = array();
148
149 if ( PHP_Helper::is_callable( 'fopen' ) && PHP_Helper::is_callable( 'fwrite' ) && PHP_Helper::is_callable( 'flock' ) ) {
150 $callable[] = 'fopen';
151 }
152 if ( PHP_Helper::is_callable( 'file_put_contents' ) ) {
153 $callable[] = 'file_put_contents';
154 }
155
156 if ( empty( $callable ) ) {
157 return false;
158 }
159
160 if ( is_dir( $file ) ) {
161 return false;
162 }
163
164 if ( ! is_dir( dirname( $file ) ) ) {
165 $result = self::create_dir( dirname( $file ) );
166
167 if ( false === $result ) {
168 return false;
169 }
170 }
171
172 $file_existed = is_file( $file );
173 $success = false;
174
175 // Different permissions to try in case the starting set of permissions are prohibiting write.
176 $trial_perms = array(
177 false,
178 0644,
179 0664,
180 0666,
181 );
182
183 foreach ( $trial_perms as $perms ) {
184 if ( false !== $perms ) {
185 if ( ! isset( $original_file_perms ) ) {
186 $original_file_perms = self::get_permissions( $file );
187 }
188
189 self::chmod( $file, $perms );
190 }
191
192 if ( in_array( 'fopen', $callable, true ) ) {
193 if ( $append ) {
194 $mode = 'ab';
195 } else {
196 $mode = 'wb';
197 }
198
199 if ( false !== ( $fh = @fopen( $file, $mode ) ) ) { // phpcs:ignore -- Ignored the assignment on the same line
200 flock( $fh, LOCK_EX );
201
202 mbstring_binary_safe_encoding();
203
204 $data_length = strlen( $contents );
205 $bytes_written = @fwrite( $fh, $contents ); // phpcs:ignore -- Ignored the error silencing
206
207 reset_mbstring_encoding();
208
209 @flock( $fh, LOCK_UN ); // phpcs:ignore -- Ignored the error silencing
210 @fclose( $fh ); // phpcs:ignore -- Ignored the error silencing
211
212 if ( $data_length === $bytes_written ) {
213 $success = true;
214 }
215 }
216 }
217
218 if ( ! $success && in_array( 'file_put_contents', $callable, true ) ) {
219 if ( $append ) {
220 $flags = FILE_APPEND;
221 } else {
222 $flags = 0;
223 }
224
225 mbstring_binary_safe_encoding();
226
227 $data_length = strlen( $contents );
228 $bytes_written = @file_put_contents( $file, $contents, $flags ); // phpcs:ignore -- Ignored the silencing warning
229
230 reset_mbstring_encoding();
231
232 if ( $data_length === $bytes_written ) {
233 $success = true;
234 }
235 }
236
237 if ( $success ) {
238 if ( ! $file_existed ) {
239 // Set default file permissions for the new file.
240 self::chmod( $file, self::get_default_permissions() );
241 } elseif ( isset( $original_file_perms ) && ! is_wp_error( $original_file_perms ) ) {
242 // Reset the original file permissions if they were modified.
243 self::chmod( $file, $original_file_perms );
244 }
245
246 return true;
247 }
248
249 if ( ! $file_existed ) {
250 // If the file is new, there is no point attempting different permissions.
251 break;
252 }
253 }
254
255 return false;
256 }
257
258 /**
259 * Adds index.php and .htaccess files to the given directory
260 *
261 * @param string $dir - The directory to protect.
262 *
263 * @return bool
264 *
265 * @since 2.4.0
266 */
267 public static function add_file_listing_protection( string $dir ) {
268 $dir = rtrim( $dir, \DIRECTORY_SEPARATOR );
269
270 if ( ! is_dir( $dir ) ) {
271 return false;
272 }
273
274 if ( self::exists( $dir . \DIRECTORY_SEPARATOR . 'index.php' ) ) {
275 return true;
276 }
277
278 return self::write( $dir . \DIRECTORY_SEPARATOR . '.htaccess', 'Deny from all' ) &&
279 self::write( $dir . \DIRECTORY_SEPARATOR . 'index.php', "<?php\n// Silence is golden." );
280 }
281
282 /**
283 * Checks if given file exists
284 *
285 * @param string $file - The name of the file to check.
286 *
287 * @return bool
288 *
289 * @since 2.4.0
290 */
291 public static function exists( string $file ): bool {
292
293 @clearstatcache( true, $file ); // phpcs:ignore -- Have Tide ignore the following line. We use arguments that don't exist in early versions, but these versions ignore the arguments.
294
295 return @file_exists( $file ); // phpcs:ignore -- Have Tide ignore the following line. We use arguments that don't exist in early versions, but these versions ignore the arguments.
296 }
297
298 /**
299 * Check the setting that allows writing files.
300 *
301 * @param string $filename - The name of the file and path.
302 *
303 * @since 2.4.0
304 *
305 * @return bool True if files can be written to, false otherwise.
306 */
307 public static function can_write_to_file( string $filename ) {
308 return is_writable( $filename );
309 }
310
311 /**
312 * Get full file path to the site's wp-config.php file.
313 *
314 * @since 2.4.0
315 *
316 * @return string Full path to the wp-config.php file or a blank string if modifications for the file are disabled.
317 */
318 public static function get_wp_config_file_path() {
319
320 if ( file_exists( ABSPATH . 'wp-config.php' ) ) {
321
322 /** The config file resides in ABSPATH */
323 $path = ABSPATH . 'wp-config.php';
324
325 } elseif ( @file_exists( dirname( ABSPATH ) . '/wp-config.php' ) && ! @file_exists( dirname( ABSPATH ) . '/wp-settings.php' ) ) {
326
327 /** The config file resides one level above ABSPATH */
328 $path = dirname( ABSPATH ) . '/wp-config.php';
329
330 } else {
331 $path = '';
332 }
333
334 /**
335 * Gives the ability to manually change the path to the config file.
336 *
337 * @param string - The current value for WP config file path.
338 *
339 * @since 2.6.2
340 */
341 $path = \apply_filters( WP_2FA_PREFIX . 'config_file_path', (string) $path );
342
343 return $path;
344 }
345
346 /**
347 * Creates a directory structure
348 *
349 * @param string $dir - The directory to create.
350 *
351 * @return boolean
352 *
353 * @since 2.4.0
354 */
355 public static function create_dir( string $dir ): bool {
356 $dir = rtrim( $dir, '/' );
357
358 if ( is_dir( $dir ) ) {
359 self::add_file_listing_protection( $dir );
360
361 return true;
362 }
363
364 if ( self::exists( $dir ) ) {
365 return false;
366 }
367
368 if ( ! PHP_Helper::is_callable( 'mkdir' ) ) {
369 return false;
370 }
371
372 $parent = dirname( $dir );
373
374 while ( ! empty( $parent ) && ! is_dir( $parent ) && dirname( $parent ) !== $parent ) {
375 $parent = dirname( $parent );
376 }
377
378 if ( empty( $parent ) ) {
379 return false;
380 }
381
382 $perms = self::get_permissions( $parent );
383
384 if ( ! is_int( $perms ) ) {
385 $perms = self::get_default_permissions();
386 }
387
388 $cached_umask = umask( 0 );
389 $result = @mkdir( $dir, $perms, true ); // phpcs:ignore -- We don't want to have fatalities here.
390 umask( $cached_umask );
391
392 if ( $result ) {
393 self::add_file_listing_protection( $dir );
394
395 return true;
396 }
397
398 return false;
399 }
400
401 /**
402 * Retrieves the full path to plugin's working directory. Returns a folder path with a trailing slash. It also
403 * creates the folder unless the $skip_creation parameter is set to true.
404 *
405 * Default path is "{uploads folder}/self::WP2FA_UPLOADS_DIR/"
406 *
407 * @param string $path Optional path relative to the working directory.
408 * @param bool $skip_creation If true, the folder will not be created.
409 * @param bool $ignore_site If true, there will be no sub-site specific subfolder in multisite context.
410 *
411 * @return string|\WP_Error
412 *
413 * @since 2.6.0
414 */
415 public static function get_upload_path( $path = '', $skip_creation = false, $ignore_site = false ) {
416 $result = '';
417
418 $upload_dir = wp_upload_dir( null, false );
419 if ( is_array( $upload_dir ) && array_key_exists( 'basedir', $upload_dir ) ) {
420 $result = $upload_dir['basedir'] . \DIRECTORY_SEPARATOR . self::WP2FA_UPLOADS_DIR . \DIRECTORY_SEPARATOR;
421 } elseif ( defined( 'WP_CONTENT_DIR' ) ) {
422 // Fallback in case there is a problem with filesystem.
423 $result = WP_CONTENT_DIR . \DIRECTORY_SEPARATOR . 'uploads' . \DIRECTORY_SEPARATOR . self::WP2FA_UPLOADS_DIR . \DIRECTORY_SEPARATOR;
424 }
425
426 if ( empty( $result ) ) {
427 // Empty result here means invalid custom path or a problem with WordPress (uploads folder issue or mission WP_CONTENT_DIR).
428 return new \WP_Error( '2fa_uplaods_dir_missing', __( 'The base of WSAL working directory cannot be determined. Custom path is invalid or there is some other issue with your WordPress installation.', 'wp-2fa' ) );
429 }
430
431 // Append site specific subfolder in multisite context.
432 if ( ! $ignore_site && WP_Helper::is_multisite() ) {
433 $site_id = \get_current_blog_id();
434 if ( $site_id > 0 ) {
435 $result .= 'sites' . \DIRECTORY_SEPARATOR . $site_id . \DIRECTORY_SEPARATOR;
436 }
437 }
438
439 // Append optional path passed as a parameter.
440 if ( $path && is_string( $path ) ) {
441 $result .= $path . \DIRECTORY_SEPARATOR;
442 }
443
444 if ( ! file_exists( $result ) ) {
445 if ( ! $skip_creation ) {
446 if ( ! \wp_mkdir_p( $result ) ) {
447 return new \WP_Error(
448 'mkdir_failed',
449 sprintf(
450 /* translators: %s: Directory path. */
451 __( 'Unable to create directory %s. Is its parent directory writable by the server?', 'wp-2fa' ),
452 esc_html( $result )
453 )
454 );
455 }
456 }
457
458 self::add_file_listing_protection( $result );
459 }
460
461 return $result;
462 }
463
464 /**
465 * Remove the supplied file.
466 *
467 * @param string $file - The name of the file and path.
468 *
469 * @return bool|WP_Error Boolean true on success or a WP_Error object if an error occurs.
470 *
471 * @since 2.6.0
472 */
473 public static function remove( $file ) {
474 if ( ! self::exists( $file ) ) {
475 return true;
476 }
477
478 if ( ! PHP_Helper::is_callable( 'unlink' ) ) {
479 return new \WP_Error(
480 'wp-2fa',
481 // translators: the name of the file.
482 sprintf( __( 'The file %s could not be removed as the unlink() function is disabled. This is a system configuration issue.', 'wp-2fa' ), $file )
483 );
484 }
485
486 $result = @unlink( $file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.unlink_unlink
487
488 @clearstatcache( true, $file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
489
490 if ( $result ) {
491 return true;
492 }
493
494 return new \WP_Error(
495 'wp-2fa',
496 sprintf(
497 // translators: the name of the file.
498 __( 'Unable to remove %s due to an unknown error.', 'wp-2fa' ),
499 $file
500 )
501 );
502 }
503
504 /**
505 * Gets the content of a file
506 *
507 * @param string $file - The name of the file.
508 *
509 * @return bool|string
510 *
511 * @since 2.4.0
512 */
513 protected static function get_file_contents( string $file ) {
514 if ( ! self::exists( $file ) ) {
515 return '';
516 }
517
518 $contents = self::read( $file );
519
520 if ( is_wp_error( $contents ) ) {
521 return false;
522 }
523
524 return $contents;
525 }
526
527 /**
528 * Write the supplied modification to the wp-config.php file.
529 *
530 * @since 2.4.0
531 *
532 * @param string $modification - The modification to add to the wp-config.php file.
533 *
534 * @return bool
535 */
536 private static function write_wp_config( $modification ) {
537 $file_path = self::get_wp_config_file_path();
538
539 return self::update( $file_path, $modification );
540 }
541
542 /**
543 * Updates the content of a file
544 *
545 * @param string $file - The name of the file to update.
546 * @param string $modification - The modification to be added to the file.
547 *
548 * @return boolean
549 *
550 * @since 2.4.0
551 */
552 private static function update( string $file, string $modification ): bool {
553 // Check to make sure that the settings give permission to write files.
554 if ( ! self::can_write_to_file( $file ) ) {
555
556 return false;
557 }
558
559 $contents = self::read( $file );
560
561 if ( is_wp_error( $contents ) ) {
562 return $contents;
563 }
564
565 if ( ! $contents ) {
566 return false;
567 }
568
569 $modification = ltrim( $modification, "\x0B\r\n\0" );
570 $modification = rtrim( $modification, " \t\x0B\r\n\0" );
571
572 if ( empty( $modification ) ) {
573 // If there isn't a new modification, write the content without any modification and return the result.
574
575 if ( empty( $contents ) ) {
576 $contents = PHP_EOL;
577 }
578
579 return false;
580 }
581
582 $placeholder = self::get_placeholder();
583
584 // Ensure that the generated placeholder can be uniquely identified in the contents.
585 while ( false !== strpos( $contents, $placeholder ) ) {
586 $placeholder = self::get_placeholder();
587 }
588
589 // Put the placeholder at the beginning of the file, after the <?php tag.
590 $contents = preg_replace( '/^(.*?<\?(?:php)?)\s*(?:\r\r\n|\r\n|\r|\n)/', "\${1}$placeholder", $contents, 1 );
591
592 if ( false === strpos( $contents, $placeholder ) ) {
593 $contents = preg_replace( '/^(.*?<\?(?:php)?)\s*(.+(?:\r\r\n|\r\n|\r|\n))/', "\${1}$placeholder$2", $contents, 1 );
594 }
595
596 if ( false === strpos( $contents, $placeholder ) ) {
597 $contents = "<?php$placeholder?" . ">$contents";
598 }
599
600 // Pad away from existing sections when adding iThemes Security modifications.
601 $line_ending = self::get_line_ending( $contents );
602
603 while ( ! preg_match( "/(?:^|(?:(?<!\r)\n|\r(?!\n)|(?<!\r)\r\n|\r\r\n)(?:(?<!\r)\n|\r(?!\n)|(?<!\r)\r\n|\r\r\n))$placeholder/", $contents ) ) {
604 $contents = preg_replace( "/$placeholder/", "$line_ending$placeholder", $contents );
605 }
606 while ( ! preg_match( "/$placeholder(?:$|(?:(?<!\r)\n|\r(?!\n)|(?<!\r)\r\n|\r\r\n)(?:(?<!\r)\n|\r(?!\n)|(?<!\r)\r\n|\r\r\n))/", $contents ) ) {
607 $contents = preg_replace( "/$placeholder/", "$placeholder$line_ending", $contents );
608 }
609
610 // Ensure that the file ends in a newline if the placeholder is at the end.
611 $contents = preg_replace( "/$placeholder$/", "$placeholder$line_ending", $contents );
612
613 if ( ! empty( $modification ) ) {
614 // Normalize line endings of the modification to match the file's line endings.
615 $modification = self::normalize_line_endings( $modification, $line_ending );
616
617 // Exchange the placeholder with the modification.
618 $contents = preg_replace( "/$placeholder/", $modification, $contents );
619 }
620
621 // Write the new contents to the file and return the results.
622 return self::write( $file, $contents );
623 }
624
625 /**
626 * Generates unique placeholder to be used in the string
627 *
628 * @return string
629 *
630 * @since 2.4.0
631 */
632 private static function get_placeholder(): string {
633 $characters = str_split( 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' );
634
635 $string = '';
636
637 for ( $x = 0; $x < 100; $x++ ) {
638 $string .= array_rand( $characters );
639 }
640
641 return $string;
642 }
643
644 /**
645 * Returns to proper line endings of a given content
646 *
647 * @param string $contents - The text to be checked.
648 *
649 * @return string
650 *
651 * @since 2.4.0
652 */
653 private static function get_line_ending( string $contents ) {
654 if ( empty( $contents ) ) {
655 return PHP_EOL;
656 }
657
658 $count["\n"] = preg_match_all( "/(?<!\r)\n/", $contents, $matches );
659 $count["\r"] = preg_match_all( "/\r(?!\n)/", $contents, $matches );
660 $count["\r\n"] = preg_match_all( "/(?<!\r)\r\n/", $contents, $matches );
661 $count["\r\r\n"] = preg_match_all( "/\r\r\n/", $contents, $matches );
662
663 if ( 0 === array_sum( $count ) ) {
664 return PHP_EOL;
665 }
666
667 $maxes = array_keys( $count, max( $count ), true );
668
669 if ( in_array( "\r\r\n", $maxes, true ) ) {
670 return "\r\r\n";
671 }
672
673 return $maxes[0];
674 }
675
676 /**
677 * Normalizing fileendings for different platforms
678 *
679 * @param string $content - The file content to be checked.
680 * @param string $line_ending - Line endings to be used.
681 *
682 * @return string
683 *
684 * @since 2.4.0
685 */
686 private static function normalize_line_endings( string $content, string $line_ending = "\n" ): string {
687 return preg_replace( '/(?<!\r)\n|\r(?!\n)|(?<!\r)\r\n|\r\r\n/', $line_ending, $content );
688 }
689
690 /**
691 * Reads the content of a file
692 *
693 * @param string $file - The file to read.
694 *
695 * @return bool|string
696 *
697 * @since 2.4.0
698 */
699 private static function read( string $file ) {
700 if ( ! is_file( $file ) ) {
701 return false;
702 }
703
704 $callable = array();
705
706 if ( PHP_Helper::is_callable( 'file_get_contents' ) ) {
707 $callable[] = 'file_get_contents';
708 }
709 if ( PHP_Helper::is_callable( 'fopen' ) && PHP_Helper::is_callable( 'feof' ) && PHP_Helper::is_callable( 'fread' ) && PHP_Helper::is_callable( 'flock' ) ) {
710 $callable[] = 'fopen';
711 }
712
713 if ( empty( $callable ) ) {
714 return false;
715 }
716
717 $contents = false;
718
719 // Different permissions to try in case the starting set of permissions are prohibiting read.
720 $trial_perms = array(
721 false,
722 0644,
723 0664,
724 0666,
725 );
726
727 foreach ( $trial_perms as $perms ) {
728 if ( false !== $perms ) {
729 if ( ! isset( $original_file_perms ) ) {
730 $original_file_perms = self::get_permissions( $file );
731 }
732
733 self::chmod( $file, $perms );
734 }
735
736 if ( in_array( 'fopen', $callable, true ) ) {
737 if ( false !== ( $fh = fopen( $file, 'rb' ) ) ) { // phpcs:ignore -- Ignored the assigned on the same line error
738 flock( $fh, LOCK_SH );
739
740 $contents = '';
741
742 while ( ! feof( $fh ) ) {
743 $contents .= fread( $fh, 1024 ); // phpcs:ignore -- Ignored the file operation notification
744 }
745
746 flock( $fh, LOCK_UN );
747 fclose( $fh ); // phpcs:ignore -- Ignored the file operation notification
748 }
749 }
750
751 if ( ( false === $contents ) && in_array( 'file_get_contents', $callable, true ) ) {
752 $contents = file_get_contents( $file ); // phpcs:ignore -- Ignored the wp_remote_get usage
753 }
754
755 if ( false !== $contents ) {
756 if ( isset( $original_file_perms ) && is_int( $original_file_perms ) ) {
757 // Reset the original file permissions if they were modified.
758 self::chmod( $file, $original_file_perms );
759 }
760
761 return $contents;
762 }
763 }
764
765 return false;
766 }
767
768 /**
769 * Changes the permissions of a file
770 *
771 * @param string $file - The file to change permissions to.
772 * @param mixed $perms - The permissions to be set.
773 *
774 * @return bool
775 *
776 * @since 2.4.0
777 */
778 private static function chmod( string $file, $perms ): bool {
779 if ( ! is_int( $perms ) ) {
780 return \CURLOPT_SSL_FALSESTART;
781 }
782
783 if ( ! PHP_Helper::is_callable( 'chmod' ) ) {
784 return false;
785 }
786
787 return @chmod( $file, $perms ); // phpcs:ignore -- Don't need fatalities here.
788 }
789
790 /**
791 * Returns the default filesystem permissions
792 *
793 * @return integer
794 *
795 * @since 2.4.0
796 */
797 private static function get_default_permissions() {
798
799 $perms = self::get_permissions( ABSPATH );
800
801 if ( ! is_wp_error( $perms ) ) {
802 return $perms;
803 }
804
805 return 0755;
806 }
807 }
808 }
809