balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

class-redux-filesystem.php (29210B)


      1 <?php
      2 /**
      3  * Redux Filesystem Class
      4  *
      5  * @class Redux_Filesystem
      6  * @version 4.0.0
      7  * @package Redux Framework/Classes
      8  */
      9 
     10 defined( 'ABSPATH' ) || exit;
     11 
     12 if ( ! class_exists( 'Redux_Filesystem', false ) ) {
     13 
     14 	/**
     15 	 * Class Redux_Filesystem
     16 	 */
     17 	class Redux_Filesystem {
     18 
     19 		/**
     20 		 * Instance of this class.
     21 		 *
     22 		 * @since    1.0.0
     23 		 * @var      object
     24 		 */
     25 		protected static $instance = null;
     26 
     27 		/**
     28 		 * WP Filesystem object.
     29 		 *
     30 		 * @var object
     31 		 */
     32 		protected static $direct = null;
     33 
     34 		/**
     35 		 * File system credentials.
     36 		 *
     37 		 * @var array
     38 		 */
     39 		private $creds = array();
     40 
     41 		/**
     42 		 * ReduxFramework object pointer.
     43 		 *
     44 		 * @var object
     45 		 */
     46 		public $parent = null;
     47 
     48 		/**
     49 		 * Instance of WP_Filesystem
     50 		 *
     51 		 * @var WP_Filesystem|null
     52 		 */
     53 		private $wp_filesystem;
     54 
     55 		/**
     56 		 * If DBI_Filesystem should attempt to use the WP_Filesystem class.
     57 		 *
     58 		 * @var bool
     59 		 */
     60 		private $use_filesystem = false;
     61 
     62 		/**
     63 		 * Default chmod octal value for directories.
     64 		 *
     65 		 * @var int
     66 		 */
     67 		private $chmod_dir;
     68 
     69 		/**
     70 		 * Default chmod octal value for files.
     71 		 *
     72 		 * @var int
     73 		 */
     74 		private $chmod_file;
     75 
     76 		/**
     77 		 * Default cache folder.
     78 		 *
     79 		 * @var string
     80 		 */
     81 		public $cache_folder;
     82 
     83 		/**
     84 		 * Pass `true` when instantiating to skip using WP_Filesystem.
     85 		 *
     86 		 * @param bool $force_no_fs Force no use of the filesystem.
     87 		 */
     88 		public function __construct( bool $force_no_fs = false ) {
     89 
     90 			// This little number fixes some issues with certain filesystem setups.
     91 
     92 			if ( ! function_exists( 'request_filesystem_credentials' ) ) {
     93 				require_once ABSPATH . '/wp-admin/includes/template.php';
     94 				require_once ABSPATH . '/wp-includes/pluggable.php';
     95 				require_once ABSPATH . '/wp-admin/includes/file.php';
     96 			}
     97 
     98 			if ( ! $force_no_fs && function_exists( 'request_filesystem_credentials' ) ) {
     99 				if ( ( defined( 'WPMDB_WP_FILESYSTEM' ) && WPMDB_WP_FILESYSTEM ) || ! defined( 'WPMDB_WP_FILESYSTEM' ) ) {
    100 					$this->maybe_init_wp_filesystem();
    101 				}
    102 			}
    103 
    104 			$uploads_dir        = wp_upload_dir();
    105 			$this->cache_folder = trailingslashit( $uploads_dir['basedir'] ) . 'redux/';
    106 			if ( ! $this->file_exists( $this->cache_folder ) ) {
    107 				$this->mkdir( $this->cache_folder );
    108 			}
    109 		}
    110 
    111 		/**
    112 		 * Return an instance of this class.
    113 		 *
    114 		 * @param object $parent ReduxFramework pointer.
    115 		 *
    116 		 * @since     1.0.0
    117 		 * @return    object    A single instance of this class.
    118 		 */
    119 		public static function get_instance( $parent = null ) {
    120 
    121 			// If the single instance hasn't been set, set it now.
    122 			if ( null === self::$instance ) {
    123 				self::$instance = new self();
    124 			}
    125 
    126 			if ( null !== $parent ) {
    127 				self::$instance->parent = $parent;
    128 			}
    129 
    130 			return self::$instance;
    131 		}
    132 
    133 		/**
    134 		 * Build FTP form.
    135 		 */
    136 		public function ftp_form() {
    137 			if ( isset( $this->parent->ftp_form ) && ! empty( $this->parent->ftp_form ) ) {
    138 				echo '<div class="wrap">';
    139 				echo '<div class="error">';
    140 				echo '<p>';
    141 				// translators: %1$s: Upload URL.  %2$s: Codex URL.
    142 				echo '<strong>' . esc_html__( 'File Permission Issues', 'redux-framework' ) . '</strong><br/>' . sprintf( esc_html__( 'We were unable to modify required files. Please ensure that %1$s has the proper read-write permissions, or modify your wp-config.php file to contain your FTP login credentials as %2$s.', 'redux-framework' ), '<code>' . esc_url( Redux_Functions_Ex::wp_normalize_path( trailingslashit( WP_CONTENT_DIR ) ) . '/uploads/' ) . '</code>', ' <a href="https://codex.wordpress.org/Editing_wp-config.php#WordPress_Upgrade_Constants" target="_blank">' . esc_html__( 'outlined here', 'redux-framework' ) . '</a>' );
    143 				echo '</p>';
    144 				echo '</div>';
    145 				echo '<h2></h2>';
    146 				echo '</div>';
    147 			}
    148 		}
    149 
    150 		/**
    151 		 * Attempt to initiate WP_Filesystem
    152 		 * If this fails, $use_filesystem is set to false and all methods in this class should use native php fallbacks
    153 		 * Thwarts `request_filesystem_credentials()` attempt to display a form for obtaining creds from users
    154 		 * TODO: provide notice and input in wp-admin for users when this fails
    155 		 */
    156 		public function maybe_init_wp_filesystem() {
    157 			// Set up the filesystem with creds.
    158 			require_once ABSPATH . '/wp-admin/includes/template.php';
    159 			require_once ABSPATH . '/wp-includes/pluggable.php';
    160 			require_once ABSPATH . '/wp-admin/includes/file.php';
    161 			ob_start();
    162 			$credentials = request_filesystem_credentials( '', '', false, false );
    163 			$ob_contents = ob_get_contents();
    164 			ob_end_clean();
    165 			if ( @wp_filesystem( $credentials ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors
    166 				global $wp_filesystem;
    167 				$this->wp_filesystem  = $wp_filesystem;
    168 				$this->use_filesystem = true;
    169 				$this->generate_default_files();
    170 			}
    171 		}
    172 
    173 		/**
    174 		 * Init WO Filesystem.
    175 		 *
    176 		 * @param string $form_url Form URL.
    177 		 * @param string $method   Connect method.
    178 		 * @param bool   $context  Context.
    179 		 * @param null   $fields   Fields.
    180 		 *
    181 		 * @return bool
    182 		 */
    183 		public function advanced_filesystem_init( string $form_url, string $method = '', bool $context = false, $fields = null ): bool {
    184 			if ( ! empty( $this->wp_filesystem ) && $this->use_filesystem ) {
    185 				return true;
    186 			}
    187 
    188 			if ( ! empty( $this->creds ) ) {
    189 				return true;
    190 			}
    191 
    192 			ob_start();
    193 
    194 			$this->creds = request_filesystem_credentials( $form_url, $method, false, $context );
    195 
    196 			/* first attempt to get credentials */
    197 			if ( false === $this->creds ) {
    198 				$this->creds            = array();
    199 				$this->parent->ftp_form = ob_get_contents();
    200 				ob_end_clean();
    201 
    202 				/**
    203 				 * If we come here - we don't have credentials
    204 				 * so the request for them is displaying
    205 				 * no need for further processing
    206 				 * */
    207 				return false;
    208 			}
    209 
    210 			/* now we got some credentials - try to use them */
    211 			if ( ! @wp_filesystem( $this->creds ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors
    212 				$this->creds = array();
    213 				/* incorrect connection data - ask for credentials again, now with error message */
    214 				request_filesystem_credentials( $form_url, '', true, $context );
    215 				$this->parent->ftp_form = ob_get_contents();
    216 				ob_end_clean();
    217 
    218 				return false;
    219 			}
    220 
    221 			global $wp_filesystem;
    222 			$this->wp_filesystem  = $wp_filesystem;
    223 			$this->use_filesystem = true;
    224 			$this->generate_default_files();
    225 
    226 			return true;
    227 		}
    228 
    229 		/**
    230 		 * Load WP fiesystem directly.
    231 		 */
    232 		public static function load_direct() {
    233 			if ( null === self::$direct ) {
    234 				require_once ABSPATH . '/wp-admin/includes/class-wp-filesystem-base.php';
    235 				require_once ABSPATH . '/wp-admin/includes/class-wp-filesystem-direct.php';
    236 
    237 				self::$direct = new WP_Filesystem_Direct( array() );
    238 			}
    239 		}
    240 
    241 		/**
    242 		 * Execute filesystem request.
    243 		 *
    244 		 * @param string $action Action to perform.
    245 		 * @param string $file File to perform upon.
    246 		 * @param array  $params Argument for action.
    247 		 *
    248 		 * @return bool|void
    249 		 */
    250 		public function execute( string $action, string $file = '', array $params = array() ) {
    251 			if ( empty( $this->parent->args ) ) {
    252 				return;
    253 			}
    254 
    255 			if ( ! empty( $params ) ) {
    256 				// phpcs:ignore WordPress.PHP.DontExtract
    257 				extract( $params );
    258 			}
    259 
    260 			if ( empty( $this->wp_filesystem ) ) {
    261 				if ( 'submenu' === $this->parent->args['menu_type'] ) {
    262 					$page_parent = $this->parent->args['page_parent'];
    263 					$base        = $page_parent . '?page=' . $this->parent->args['page_slug'];
    264 				} else {
    265 					$base = 'admin.php?page=' . $this->parent->args['page_slug'];
    266 				}
    267 
    268 				$url = wp_nonce_url( $base, 'redux-options' );
    269 				$this->advanced_filesystem_init( $url, 'direct', dirname( $file ) );
    270 			}
    271 
    272 			return $this->do_action( $action, $file, $params );
    273 		}
    274 
    275 
    276 		/**
    277 		 * Generates the default Redux cache folder.
    278 		 *
    279 		 * @return void
    280 		 */
    281 		private function generate_default_files() {
    282 
    283 			// Set default permissions.
    284 			if ( defined( 'FS_CHMOD_DIR' ) ) {
    285 				$this->chmod_dir = FS_CHMOD_DIR;
    286 			} else {
    287 				$this->chmod_dir = ( fileperms( ABSPATH ) & 0777 | 0755 );
    288 			}
    289 
    290 			if ( defined( 'FS_CHMOD_FILE' ) ) {
    291 				$this->chmod_file = FS_CHMOD_FILE;
    292 			} else {
    293 				$this->chmod_file = ( fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 );
    294 			}
    295 
    296 			if ( ! $this->is_dir( Redux_Core::$upload_dir ) ) {
    297 				$this->mkdir( Redux_Core::$upload_dir );
    298 			}
    299 
    300 			$hash_path = trailingslashit( Redux_Core::$upload_dir ) . 'hash';
    301 			if ( ! $this->file_exists( $hash_path ) ) {
    302 				$this->put_contents( $hash_path, Redux_Helpers::get_hash() );
    303 			}
    304 
    305 			$version_path = trailingslashit( Redux_Core::$upload_dir ) . 'version';
    306 			if ( ! $this->file_exists( $version_path ) ) {
    307 				$this->put_contents( $version_path, Redux_Core::$version );
    308 			} else {
    309 				$version_compare = $this->get_contents( $version_path );
    310 				if ( (string) Redux_Core::$version !== $version_compare ) {
    311 					$this->put_contents( $version_path, Redux_Core::$version );
    312 				}
    313 			}
    314 		}
    315 
    316 		/**
    317 		 * Do request filesystem action.
    318 		 *
    319 		 * @param string $action Requested action.
    320 		 * @param string $file File to perform action upon.
    321 		 * @param array  $params Action arguments.
    322 		 *
    323 		 * @return bool|void
    324 		 */
    325 		public function do_action( string $action, string $file = '', array $params = array() ) {
    326 			if ( ! empty( $params ) ) {
    327 
    328 				// phpcs:ignore WordPress.PHP.DontExtract
    329 				extract( $params );
    330 			}
    331 
    332 			global $wp_filesystem;
    333 
    334 			if ( defined( 'FS_CHMOD_FILE' ) ) {
    335 				$chmod = FS_CHMOD_FILE;
    336 			} else {
    337 				$chmod = 0644;
    338 			}
    339 
    340 			if ( isset( $params['chmod'] ) && ! empty( $params['chmod'] ) ) {
    341 				$chmod = $params['chmod'];
    342 			}
    343 			$res = false;
    344 			if ( ! isset( $recursive ) ) {
    345 				$recursive = false;
    346 			}
    347 
    348 			// Do unique stuff.
    349 			if ( 'mkdir' === $action ) {
    350 				$chmod = null;
    351 				if ( isset( $params['chmod'] ) && ! empty( $params['chmod'] ) ) {
    352 					$chmod = $params['chmod'];
    353 				}
    354 				$res = $this->mkdir( $file, $chmod );
    355 			} elseif ( 'rmdir' === $action ) {
    356 				$res = $this->rmdir( $file, $recursive );
    357 			} elseif ( 'copy' === $action && ! isset( $this->wp_filesystem->killswitch ) ) {
    358 				$res = $this->copy( $file, $destination, $overwrite, $chmod );
    359 			} elseif ( 'move' === $action && ! isset( $this->wp_filesystem->killswitch ) ) {
    360 				$res = $this->move( $file, $destination, $overwrite );
    361 			} elseif ( 'delete' === $action ) {
    362 				if ( $this->is_dir( $file ) ) {
    363 					$res = $this->rmdir( $file, $recursive );
    364 				} else {
    365 					$res = $this->unlink( $file );
    366 				}
    367 			} elseif ( 'rmdir' === $action ) {
    368 				$res = $this->rmdir( $file, $recursive );
    369 			} elseif ( 'dirlist' === $action ) {
    370 				if ( ! isset( $include_hidden ) ) {
    371 					$include_hidden = true;
    372 				}
    373 				$res = $this->scandir( $file, $include_hidden, $recursive );
    374 			} elseif ( 'put_contents' === $action && ! isset( $this->wp_filesystem->killswitch ) ) {
    375 				// Write a string to a file.
    376 				if ( isset( $this->parent->ftp_form ) && ! empty( $this->parent->ftp_form ) ) {
    377 					self::load_direct();
    378 					$res = self::$direct->put_contents( $file, $content, $chmod );
    379 				} else {
    380 					$res = $this->put_contents( $file, $content, $chmod );
    381 				}
    382 			} elseif ( 'chown' === $action ) {
    383 				// Changes file owner.
    384 				if ( isset( $owner ) && ! empty( $owner ) ) {
    385 					$res = $wp_filesystem->chmod( $file, $chmod, $recursive );
    386 				}
    387 			} elseif ( 'owner' === $action ) {
    388 				// Gets file owner.
    389 				$res = $this->wp_filesystem->owner( $file );
    390 			} elseif ( 'chmod' === $action ) {
    391 				if ( ! isset( $params['chmod'] ) || ( isset( $params['chmod'] ) && empty( $params['chmod'] ) ) ) {
    392 					$chmod = false;
    393 				}
    394 
    395 				$res = $this->chmod( $file, $chmod, $recursive );
    396 			} elseif ( 'get_contents' === $action ) {
    397 				// Reads entire file into a string.
    398 				if ( isset( $this->parent->ftp_form ) && ! empty( $this->parent->ftp_form ) ) {
    399 					self::load_direct();
    400 					$res = self::$direct->get_contents( $file );
    401 				} else {
    402 					$res = $this->get_contents( $file );
    403 				}
    404 			} elseif ( 'get_contents_array' === $action ) {
    405 				// Reads entire file into an array.
    406 				$res = $this->wp_filesystem->get_contents_array( $file );
    407 			} elseif ( 'object' === $action ) {
    408 				$res = $this->wp_filesystem;
    409 			} elseif ( 'unzip' === $action ) {
    410 				$unzipfile = unzip_file( $file, $destination );
    411 				if ( $unzipfile ) {
    412 					$res = true;
    413 				}
    414 			}
    415 
    416 			if ( ! $res ) {
    417 				if ( 'dirlist' === $action ) {
    418 					if ( empty( $res ) || '' === $res ) {
    419 						return;
    420 					}
    421 
    422 					if ( ! is_array( $res ) ) {
    423 						if ( count( glob( "$file*" ) ) === 0 ) {
    424 							return;
    425 						}
    426 					}
    427 				}
    428 
    429 				$this->killswitch = true;
    430 
    431 				// translators: %1$s: Upload URL.  %2$s: Codex URL.
    432 				$msg = '<strong>' . esc_html__( 'File Permission Issues', 'redux-framework' ) . '</strong><br/>' . sprintf( esc_html__( 'We were unable to modify required files. Please ensure that %1$s has the proper read-write permissions, or modify your wp-config.php file to contain your FTP login credentials as %2$s.', 'redux-framework' ), '<code>' . esc_url( Redux_Functions_Ex::wp_normalize_path( trailingslashit( WP_CONTENT_DIR ) ) ) . '/uploads/</code>', '<a href="https://codex.wordpress.org/Editing_wp-config.php#WordPress_Upgrade_Constants" target="_blank">' . esc_html__( 'outlined here', 'redux-framework' ) . '</a>' );
    433 
    434 				$data = array(
    435 					'parent'  => self::$instance->parent,
    436 					'type'    => 'error',
    437 					'msg'     => $msg,
    438 					'id'      => 'redux-wp-login',
    439 					'dismiss' => false,
    440 				);
    441 
    442 				Redux_Admin_Notices::set_notice( $data );
    443 			}
    444 
    445 			return $res;
    446 		}
    447 
    448 
    449 		/**
    450 		 * Getter for the instantiated WP_Filesystem. This should be used carefully since $wp_filesystem won't always have a value.
    451 		 *
    452 		 * @return WP_Filesystem|false
    453 		 */
    454 		public function get_wp_filesystem() {
    455 			if ( $this->use_filesystem ) {
    456 				return $this->wp_filesystem;
    457 			} else {
    458 				return false;
    459 			}
    460 		}
    461 
    462 		/**
    463 		 * Check if WP_Filesystem being used.
    464 		 *
    465 		 * @return bool
    466 		 */
    467 		public function using_wp_filesystem(): bool {
    468 			return $this->use_filesystem;
    469 		}
    470 
    471 		/**
    472 		 * Attempts to use the correct path for the FS method being used.
    473 		 *
    474 		 * @param string $abs_path Absolute path.
    475 		 *
    476 		 * @return string
    477 		 */
    478 		public function get_sanitized_path( string $abs_path ): string {
    479 			if ( $this->using_wp_filesystem() ) {
    480 				return str_replace( ABSPATH, $this->wp_filesystem->abspath(), $abs_path );
    481 			}
    482 
    483 			return $abs_path;
    484 		}
    485 
    486 		/**
    487 		 * Create file if not exists then set mtime and atime on file
    488 		 *
    489 		 * @param string $abs_path Absolute path.
    490 		 * @param int    $time Time.
    491 		 * @param int    $atime Altered time.
    492 		 *
    493 		 * @return bool
    494 		 */
    495 		public function touch( string $abs_path, int $time = 0, int $atime = 0 ): bool {
    496 			if ( 0 === $time ) {
    497 				$time = time();
    498 			}
    499 
    500 			if ( 0 === $atime ) {
    501 				$atime = time();
    502 			}
    503 
    504 			// phpcs:ignore WordPress.PHP.NoSilencedErrors
    505 			$return = @touch( $abs_path, $time, $atime );
    506 
    507 			if ( ! $return && $this->use_filesystem ) {
    508 				$abs_path = $this->get_sanitized_path( $abs_path );
    509 				$return   = $this->wp_filesystem->touch( $abs_path, $time, $atime );
    510 			}
    511 
    512 			return $return;
    513 		}
    514 
    515 		/**
    516 		 * Calls file_put_contents with chmod.
    517 		 *
    518 		 * @param string      $abs_path Absolute path.
    519 		 * @param string      $contents Content to write to the file.
    520 		 * @param string|null $perms    Default permissions value.
    521 		 *
    522 		 * @return bool
    523 		 */
    524 		public function put_contents( string $abs_path, string $contents, string $perms = null ): bool {
    525 
    526 			if ( ! $this->is_dir( dirname( $abs_path ) ) ) {
    527 				$this->mkdir( dirname( $abs_path ) );
    528 			}
    529 
    530 			// phpcs:ignore WordPress.PHP.NoSilencedErrors
    531 			// @codingStandardsIgnoreStart
    532 			$return = @file_put_contents( $abs_path, $contents );
    533 			// @codingStandardsIgnoreEnd
    534 			$this->chmod( $abs_path );
    535 
    536 			if ( null === $perms ) {
    537 				$perms = $this->chmod_file;
    538 			}
    539 
    540 			if ( ! $return && $this->use_filesystem ) {
    541 				$abs_path = $this->get_sanitized_path( $abs_path );
    542 				$return   = $this->wp_filesystem->put_contents( $abs_path, $contents, $perms );
    543 			}
    544 
    545 			return (bool) $return;
    546 		}
    547 
    548 		/**
    549 		 * Calls file_put_contents with chmod.
    550 		 *
    551 		 * @param string $path Get full cache path.
    552 		 *
    553 		 * @return string
    554 		 */
    555 		public function get_cache_path( string $path ): string {
    556 			return $this->folder . $path;
    557 		}
    558 
    559 		/**
    560 		 * Calls file_put_contents with chmod in cache directory.
    561 		 *
    562 		 * @param string $abs_path Absolute path.
    563 		 * @param string $contents Contents to put in the cache.
    564 		 *
    565 		 * @return bool
    566 		 */
    567 		public function put_contents_cache( string $abs_path, string $contents ): bool {
    568 			return $this->put_contents( $this->get_cache_path( $abs_path ), $contents );
    569 		}
    570 
    571 		/**
    572 		 * Does the specified file or dir exist.
    573 		 *
    574 		 * @param string $abs_path Absolute path.
    575 		 * @return bool
    576 		 */
    577 		public function file_exists( string $abs_path ): bool {
    578 			$return = file_exists( $abs_path );
    579 
    580 			if ( ! $return && $this->use_filesystem ) {
    581 				$abs_path = $this->get_sanitized_path( $abs_path );
    582 				$return   = $this->wp_filesystem->exists( $abs_path );
    583 			}
    584 
    585 			return (bool) $return;
    586 		}
    587 
    588 		/**
    589 		 * Get a file's size.
    590 		 *
    591 		 * @param string $abs_path Absolute path.
    592 		 *
    593 		 * @return int
    594 		 */
    595 		public function filesize( string $abs_path ): int {
    596 			$return = filesize( $abs_path );
    597 
    598 			if ( ! $return && $this->use_filesystem ) {
    599 				$abs_path = $this->get_sanitized_path( $abs_path );
    600 				$return   = $this->wp_filesystem->size( $abs_path );
    601 			}
    602 
    603 			return $return;
    604 		}
    605 
    606 		/**
    607 		 * Get the contents of a file as a string.
    608 		 *
    609 		 * @param string $abs_path Absolute path.
    610 		 *
    611 		 * @return string
    612 		 */
    613 		public function get_local_file_contents( string $abs_path ): string {
    614 
    615 			try {
    616 				ob_start();
    617 				if ( $this->file_exists( $abs_path ) && is_file( $abs_path ) ) {
    618 					require_once $abs_path;
    619 				}
    620 				$contents = ob_get_clean();
    621 			} catch ( Exception $e ) {
    622 				// This means that ob_start has been disabled on the system. Lets fallback to good old file_get_contents.
    623 				$contents = file_get_contents( $abs_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
    624 			}
    625 
    626 			return $contents;
    627 		}
    628 
    629 		/**
    630 		 * Get the contents of a file as a string.
    631 		 *
    632 		 * @param string $abs_path Absolute path.
    633 		 *
    634 		 * @return string
    635 		 */
    636 		public function get_contents( string $abs_path ): string {
    637 			$abs_path = $this->get_sanitized_path( $abs_path );
    638 			$return   = '';
    639 			if ( $this->use_filesystem ) {
    640 				$return = $this->wp_filesystem->get_contents( $abs_path );
    641 			}
    642 			if ( empty( $return ) ) {
    643 				$return = $this->get_local_file_contents( $abs_path );
    644 			}
    645 
    646 			return $return;
    647 		}
    648 
    649 		/**
    650 		 * Delete a file.
    651 		 *
    652 		 * @param string $abs_path Absolute path.
    653 		 *
    654 		 * @return bool
    655 		 */
    656 		public function unlink( string $abs_path ): bool {
    657 			// phpcs:ignore WordPress.PHP.NoSilencedErrors
    658 			$return = @unlink( $abs_path );
    659 
    660 			if ( ! $return && $this->use_filesystem ) {
    661 				$abs_path = $this->get_sanitized_path( $abs_path );
    662 				$return   = $this->wp_filesystem->delete( $abs_path, false, false );
    663 			}
    664 
    665 			return $return;
    666 		}
    667 
    668 		/**
    669 		 * Chmod a file.
    670 		 *
    671 		 * @param string   $abs_path Absolute path.
    672 		 * @param int|null $perms    Permission value, if not provided, defaults to WP standards.
    673 		 *
    674 		 * @return bool
    675 		 */
    676 		public function chmod( string $abs_path, int $perms = null ): bool {
    677 			if ( ! $this->file_exists( $abs_path ) ) {
    678 				return false;
    679 			}
    680 			if ( is_null( $perms ) ) {
    681 				$perms = $this->is_file( $abs_path ) ? $this->chmod_file : $this->chmod_dir;
    682 			}
    683 			// phpcs:ignore WordPress.PHP.NoSilencedErrors
    684 			$return = @chmod( $abs_path, $perms );
    685 
    686 			if ( ! $return && $this->use_filesystem ) {
    687 				$abs_path = $this->get_sanitized_path( $abs_path );
    688 				$return   = $this->wp_filesystem->chmod( $abs_path, $perms, false );
    689 			}
    690 
    691 			return $return;
    692 		}
    693 
    694 		/**
    695 		 * Check if this path is a directory.
    696 		 *
    697 		 * @param string $abs_path Absolute path.
    698 		 *
    699 		 * @return bool
    700 		 */
    701 		public function is_dir( string $abs_path ): bool {
    702 			$return = is_dir( $abs_path );
    703 
    704 			if ( ! $return && $this->use_filesystem ) {
    705 				$abs_path = $this->get_sanitized_path( $abs_path );
    706 				$return   = $this->wp_filesystem->is_dir( $abs_path );
    707 			}
    708 
    709 			return $return;
    710 		}
    711 
    712 		/**
    713 		 * Check if the specified path is a file.
    714 		 *
    715 		 * @param string $abs_path Absolute path.
    716 		 *
    717 		 * @return bool
    718 		 */
    719 		public function is_file( string $abs_path ): bool {
    720 			$return = is_file( $abs_path );
    721 
    722 			if ( ! $return && $this->use_filesystem ) {
    723 				$abs_path = $this->get_sanitized_path( $abs_path );
    724 				$return   = $this->wp_filesystem->is_file( $abs_path );
    725 			}
    726 
    727 			return $return;
    728 		}
    729 
    730 		/**
    731 		 * Is the specified path readable.
    732 		 *
    733 		 * @param string $abs_path Absolute path.
    734 		 *
    735 		 * @return bool
    736 		 */
    737 		public function is_readable( string $abs_path ): bool {
    738 			$return = is_readable( $abs_path );
    739 
    740 			if ( ! $return && $this->use_filesystem ) {
    741 				$abs_path = $this->get_sanitized_path( $abs_path );
    742 				$return   = $this->wp_filesystem->is_readable( $abs_path );
    743 			}
    744 
    745 			return $return;
    746 		}
    747 
    748 		/**
    749 		 * Is the specified path writable.
    750 		 *
    751 		 * @param string $abs_path Absolute path.
    752 		 *
    753 		 * @return bool
    754 		 */
    755 		public function is_writable( string $abs_path ): bool {
    756 			$return = is_writable( $abs_path );
    757 
    758 			if ( ! $return && $this->use_filesystem ) {
    759 				$abs_path = $this->get_sanitized_path( $abs_path );
    760 				$return   = $this->wp_filesystem->is_writable( $abs_path );
    761 			}
    762 
    763 			return $return;
    764 		}
    765 
    766 		/**
    767 		 * Create an index file at the given path.
    768 		 *
    769 		 * @param string $path Directory to add the index to.
    770 		 */
    771 		private function create_index( string $path ) {
    772 			$index_path = trailingslashit( $path ) . 'index.php';
    773 			if ( ! $this->file_exists( $index_path ) ) {
    774 				$this->put_contents( $index_path, "<?php\n//Silence is golden" );
    775 			}
    776 		}
    777 
    778 		/**
    779 		 * Recursive mkdir.
    780 		 *
    781 		 * @param string   $abs_path Absolute path.
    782 		 * @param int|null $perms    Permissions, if default not required.
    783 		 *
    784 		 * @return bool
    785 		 */
    786 		public function mkdir( string $abs_path, int $perms = null ): bool {
    787 			if ( is_null( $perms ) ) {
    788 				$perms = $this->chmod_dir;
    789 			}
    790 
    791 			if ( $this->is_dir( $abs_path ) ) {
    792 				$this->chmod( $abs_path, $perms );
    793 				$this->create_index( $abs_path );
    794 
    795 				return true;
    796 			}
    797 
    798 			try {
    799 				$mkdirp = wp_mkdir_p( $abs_path );
    800 			} catch ( Exception $e ) {
    801 				$mkdirp = false;
    802 			}
    803 
    804 			if ( $mkdirp ) {
    805 				$this->chmod( $abs_path, $perms );
    806 				$this->create_index( $abs_path );
    807 
    808 				return true;
    809 			}
    810 			// phpcs:ignore WordPress.PHP.NoSilencedErrors
    811 			$return = @mkdir( $abs_path, $perms, true );
    812 
    813 			if ( ! $return && $this->use_filesystem ) {
    814 				$abs_path = $this->get_sanitized_path( $abs_path );
    815 
    816 				if ( $this->is_dir( $abs_path ) ) {
    817 					$this->create_index( $abs_path );
    818 
    819 					return true;
    820 				}
    821 
    822 				// WP_Filesystem doesn't offer a recursive mkdir().
    823 				$abs_path = str_replace( '//', '/', $abs_path );
    824 				$abs_path = rtrim( $abs_path, '/' );
    825 				if ( empty( $abs_path ) ) {
    826 					$abs_path = '/';
    827 				}
    828 
    829 				$dirs        = explode( '/', ltrim( $abs_path, '/' ) );
    830 				$current_dir = '';
    831 
    832 				foreach ( $dirs as $dir ) {
    833 					$current_dir .= '/' . $dir;
    834 					if ( ! $this->is_dir( $current_dir ) ) {
    835 						$this->wp_filesystem->mkdir( $current_dir, $perms );
    836 					}
    837 				}
    838 
    839 				$return = $this->is_dir( $abs_path );
    840 			}
    841 
    842 			return $return;
    843 		}
    844 
    845 		/**
    846 		 * Delete a directory.
    847 		 *
    848 		 * @param string $abs_path  Absolute path.
    849 		 * @param bool   $recursive Set to recursive create.
    850 		 *
    851 		 * @return bool
    852 		 */
    853 		public function rmdir( string $abs_path, bool $recursive = false ): bool {
    854 			if ( ! $this->is_dir( $abs_path ) ) {
    855 				return false;
    856 			}
    857 
    858 			// Taken from WP_Filesystem_Direct.
    859 			if ( ! $recursive ) {
    860 				// phpcs:ignore WordPress.PHP.NoSilencedErrors
    861 				$return = @rmdir( $abs_path );
    862 			} else {
    863 
    864 				// At this point it's a folder, and we're in recursive mode.
    865 				$abs_path = trailingslashit( $abs_path );
    866 				$filelist = $this->scandir( $abs_path );
    867 
    868 				$return = true;
    869 				if ( is_array( $filelist ) ) {
    870 					foreach ( $filelist as $filename => $fileinfo ) {
    871 
    872 						if ( 'd' === $fileinfo['type'] ) {
    873 							$return = $this->rmdir( $abs_path . $filename, $recursive );
    874 						} else {
    875 							$return = $this->unlink( $abs_path . $filename );
    876 						}
    877 					}
    878 				}
    879 				// phpcs:ignore WordPress.PHP.NoSilencedErrors
    880 				if ( file_exists( $abs_path ) && ! @rmdir( $abs_path ) ) {
    881 					$return = false;
    882 				}
    883 			}
    884 
    885 			if ( ! $return && $this->use_filesystem ) {
    886 				$abs_path = $this->get_sanitized_path( $abs_path );
    887 
    888 				return $this->wp_filesystem->rmdir( $abs_path, $recursive );
    889 			}
    890 
    891 			return $return;
    892 
    893 		}
    894 
    895 		/**
    896 		 * Get a list of files/folders under specified directory.
    897 		 *
    898 		 * @param string $abs_path       Absolute path.
    899 		 * @param bool   $include_hidden Include hidden files, defaults to true.
    900 		 * @param bool   $recursive      Recursive search, defaults to false.
    901 		 *
    902 		 * @return array|bool
    903 		 */
    904 		public function scandir( string $abs_path, bool $include_hidden = true, bool $recursive = false ) {
    905 			// phpcs:ignore WordPress.PHP.NoSilencedErrors
    906 			$dirlist = @scandir( $abs_path );
    907 			if ( false === $dirlist ) {
    908 				if ( $this->use_filesystem ) {
    909 					$abs_path = $this->get_sanitized_path( $abs_path );
    910 
    911 					return $this->wp_filesystem->dirlist( $abs_path, $include_hidden, $recursive );
    912 				}
    913 
    914 				return false;
    915 			}
    916 
    917 			$return = array();
    918 
    919 			// Normalize return to look somewhat like the return value for WP_Filesystem::dirlist.
    920 			foreach ( $dirlist as $entry ) {
    921 				if ( '.' === $entry || '..' === $entry ) {
    922 					continue;
    923 				}
    924 				$return[ $entry ] = array(
    925 					'name' => $entry,
    926 					'type' => $this->is_dir( $abs_path . '/' . $entry ) ? 'd' : 'f',
    927 				);
    928 			}
    929 
    930 			return $return;
    931 
    932 		}
    933 
    934 		/**
    935 		 * Light wrapper for move_uploaded_file with chmod.
    936 		 *
    937 		 * @param string   $file        Source file.
    938 		 * @param string   $destination File destination.
    939 		 * @param int|null $perms       Permission value.
    940 		 *
    941 		 * @return bool
    942 		 */
    943 		public function move_uploaded_file( string $file, string $destination, int $perms = null ): bool {
    944 			// TODO: look into replicating more functionality from wp_handle_upload().
    945 			// phpcs:ignore WordPress.PHP.NoSilencedErrors
    946 			$return = @move_uploaded_file( $file, $destination );
    947 
    948 			if ( $return ) {
    949 				$this->chmod( $destination, $perms );
    950 			}
    951 
    952 			return $return;
    953 		}
    954 
    955 		/**
    956 		 * Copy a file.
    957 		 *
    958 		 * @param string $source_abs_path Source path.
    959 		 * @param string $destination_abs_path Destination path.
    960 		 * @param bool   $overwrite Overwrite file.
    961 		 * @param mixed  $perms Permission value.
    962 		 * @return bool
    963 		 * Taken from WP_Filesystem_Direct
    964 		 */
    965 		public function copy( string $source_abs_path, string $destination_abs_path, bool $overwrite = true, $perms = false ): bool {
    966 
    967 			// Error if source file doesn't exist.
    968 			if ( ! $this->file_exists( $source_abs_path ) ) {
    969 				return false;
    970 			}
    971 
    972 			if ( ! $overwrite && $this->file_exists( $destination_abs_path ) ) {
    973 				return false;
    974 			}
    975 			if ( ! $this->is_dir( dirname( $destination_abs_path ) ) ) {
    976 				$this->mkdir( dirname( $destination_abs_path ) );
    977 			}
    978 
    979 			// phpcs:ignore WordPress.PHP.NoSilencedErrors
    980 			$return = @copy( $source_abs_path, $destination_abs_path );
    981 			if ( $perms && $return ) {
    982 				$this->chmod( $destination_abs_path, $perms );
    983 			}
    984 
    985 			if ( ! $return && $this->use_filesystem ) {
    986 				$source_abs_path      = $this->get_sanitized_path( $source_abs_path );
    987 				$destination_abs_path = $this->get_sanitized_path( $destination_abs_path );
    988 				$return               = $this->wp_filesystem->copy(
    989 					$source_abs_path,
    990 					$destination_abs_path,
    991 					$overwrite,
    992 					$perms
    993 				);
    994 			}
    995 
    996 			return $return;
    997 		}
    998 
    999 		/**
   1000 		 * Move a file.
   1001 		 *
   1002 		 * @param string $source_abs_path Source absolute path.
   1003 		 * @param string $destination_abs_path Destination absolute path.
   1004 		 * @param bool   $overwrite Overwrite if file exists.
   1005 		 * @return bool
   1006 		 */
   1007 		public function move( string $source_abs_path, string $destination_abs_path, bool $overwrite = true ): bool {
   1008 
   1009 			// Error if source file doesn't exist.
   1010 			if ( ! $this->file_exists( $source_abs_path ) ) {
   1011 				return false;
   1012 			}
   1013 
   1014 			// Try using rename first. if that fails (for example, source is read only) try copy.
   1015 			// Taken in part from WP_Filesystem_Direct.
   1016 			if ( ! $overwrite && $this->file_exists( $destination_abs_path ) ) {
   1017 				return false;
   1018 			} elseif ( @rename( $source_abs_path, $destination_abs_path ) ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors
   1019 				return true;
   1020 			} else {
   1021 				if ( $this->copy( $source_abs_path, $destination_abs_path, $overwrite ) && $this->file_exists(
   1022 					$destination_abs_path
   1023 				) ) {
   1024 					$this->unlink( $source_abs_path );
   1025 
   1026 					return true;
   1027 				} else {
   1028 					$return = false;
   1029 				}
   1030 			}
   1031 
   1032 			if ( $this->use_filesystem ) {
   1033 				$source_abs_path      = $this->get_sanitized_path( $source_abs_path );
   1034 				$destination_abs_path = $this->get_sanitized_path( $destination_abs_path );
   1035 
   1036 				$return = $this->wp_filesystem->move( $source_abs_path, $destination_abs_path, $overwrite );
   1037 			}
   1038 
   1039 			return $return;
   1040 		}
   1041 
   1042 		/**
   1043 		 * Shim: get_template.
   1044 		 *
   1045 		 * @param string $file Template name.
   1046 		 *
   1047 		 * @return void Path to template file.
   1048 		 */
   1049 		public function get_template( string $file ) {
   1050 			$panel = new Redux_Panel( $this );
   1051 			$panel->get_template( $file );
   1052 		}
   1053 	}
   1054 
   1055 	Redux_Filesystem::get_instance();
   1056 }