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