JezK
Edit File: Filemanager.php
<?php defined('AUTOUPDATER_LIB') or die; class AutoUpdater_Filemanager { protected static $instance = null; protected $FS_CHMOD_FILE; protected $FS_CHMOD_DIR; /** @see WP_Filesystem_Direct */ protected static $wp_filesystem; public function __construct() { $this->FS_CHMOD_DIR = defined('FS_CHMOD_DIR') ? FS_CHMOD_DIR : (fileperms(AUTOUPDATER_SITE_PATH) & 0777 | 0755); if (defined('FS_CHMOD_FILE')) { $this->FS_CHMOD_FILE = FS_CHMOD_FILE; } elseif (file_exists(AUTOUPDATER_SITE_PATH . 'index.php')) { $this->FS_CHMOD_FILE = (fileperms(AUTOUPDATER_SITE_PATH . 'index.php') & 0777 | 0644); } require_once ABSPATH . 'wp-admin/includes/file.php'; if (function_exists('WP_Filesystem') and WP_Filesystem()) { global $wp_filesystem; static::$wp_filesystem = $wp_filesystem; } add_filter('file_mod_allowed', 'AutoUpdater_Filemanager::filter_file_mod_allowed'); } /** * @return static */ public static function getInstance() { if (!is_null(static::$instance)) { return static::$instance; } $class_name = AutoUpdater_Loader::loadClass('Filemanager'); static::$instance = new $class_name(); return static::$instance; } /** * Filters whether file modifications are allowed. * * @since WP 4.8.0 * * @param bool $file_mod_allowed Whether file modifications are allowed. * @param string $context The usage context. */ public static function filter_file_mod_allowed($file_mod_allowed = true, $context = '') { if (defined('AUTOUPDATER_IN_PROGRESS') && AUTOUPDATER_IN_PROGRESS) { return true; } return $file_mod_allowed; } /** * Reads entire file into a string * * @access public * * @param string $file Name of the file to read. * * @return string|bool The function returns the read data or false on failure. */ public function get_contents($file) { if (static::$wp_filesystem) { return static::$wp_filesystem->get_contents($file); } return @file_get_contents($file); // phpcs:ignore } /** * Write a string to a file * * @access public * * @param string $file Remote path to the file where to write the data. * @param string $contents The data to write. * @param int $flags * * @return bool False upon failure, true otherwise. */ public function put_contents($file, $contents, $flags = 0) { return @file_put_contents($file, $contents, $flags) !== false; // phpcs:ignore } /** * Changes file group * * @access public * * @param string $file Path to the file. * @param mixed $group A group name or number. * @param bool $recursive Optional. If set True changes file group recursively. Default false. * * @return bool Returns true on success or false on failure. */ public function chgrp($file, $group, $recursive = false) { if (static::$wp_filesystem) { return static::$wp_filesystem->chgrp($file, $group, $recursive); } if (!$this->exists($file)) { return false; } if (!$recursive) { return @chgrp($file, $group); // phpcs:ignore } if (!$this->is_dir($file)) { return @chgrp($file, $group); // phpcs:ignore } // Is a directory, and we want recursive $file = $this->trailingslashit($file); $filelist = $this->dirlist($file); foreach ($filelist as $filename => $fileinfo) { $this->chgrp($file . $filename, $group, $recursive); } return true; } /** * Changes filesystem permissions * * @access public * * @param string $file Path to the file. * @param int $mode Optional. The permissions as octal number, usually 0644 for files, * 0755 for dirs. Default false. * @param bool $recursive Optional. If set True changes file group recursively. Default false. * * @return bool Returns true on success or false on failure. */ public function chmod($file, $mode = false, $recursive = false) { if (static::$wp_filesystem) { return static::$wp_filesystem->chmod($file, $mode, $recursive); } if (!$mode) { if ($this->is_file($file)) { $mode = $this->FS_CHMOD_FILE; } elseif ($this->is_dir($file)) { $mode = $this->FS_CHMOD_DIR; } else { return false; } } if (!$recursive || !$this->is_dir($file)) { return @chmod($file, $mode); // phpcs:ignore } // Is a directory, and we want recursive $file = $this->trailingslashit($file); $filelist = $this->dirlist($file); foreach ((array) $filelist as $filename => $fileinfo) { $this->chmod($file . $filename, $mode, $recursive); } return true; } /** * Changes file owner * * @access public * * @param string $file Path to the file. * @param mixed $owner A user name or number. * @param bool $recursive Optional. If set True changes file owner recursively. * Default false. * * @return bool Returns true on success or false on failure. */ public function chown($file, $owner, $recursive = false) { if (static::$wp_filesystem) { return static::$wp_filesystem->chown($file, $owner, $recursive); } if (!$this->exists($file)) { return false; } if (!$recursive) { return @chown($file, $owner); // phpcs:ignore } if (!$this->is_dir($file)) { return @chown($file, $owner); // phpcs:ignore } // Is a directory, and we want recursive $filelist = $this->dirlist($file); foreach ($filelist as $filename => $fileinfo) { $this->chown($file . '/' . $filename, $owner, $recursive); } return true; } /** * @access public * * @param string $source * @param string $destination * @param bool $overwrite * @param int $mode * * @return bool */ public function copy($source, $destination, $overwrite = false, $mode = false) { if (static::$wp_filesystem) { return static::$wp_filesystem->copy($source, $destination, $overwrite, $mode); } if (!$overwrite && $this->exists($destination)) { return false; } $rtval = copy($source, $destination); // phpcs:ignore if ($mode) { $this->chmod($destination, $mode); } return $rtval; } /** * @access public * * @param string $source * @param string $destination * @param bool $overwrite * * @return bool */ public function move($source, $destination, $overwrite = false) { if (static::$wp_filesystem) { return static::$wp_filesystem->move($source, $destination, $overwrite); } if (!$overwrite && $this->exists($destination)) { return false; } // Try using rename first. if that fails (for example, source is read only) try copy. if (@rename($source, $destination)) { // phpcs:ignore return true; } if ($this->copy($source, $destination, $overwrite) && $this->exists($destination)) { $this->delete($source); return true; } else { return false; } } /** * @access public * * @param string $file * @param bool $recursive * @param string $type * * @return bool */ public function delete($file, $recursive = false, $type = false) { if (static::$wp_filesystem) { return static::$wp_filesystem->delete($file, $recursive, $type); } if (empty($file)) // Some filesystems report this as /, which can cause non-expected recursive deletion of all files in the filesystem. { return false; } $file = str_replace('\\', '/', $file); // for win32, occasional problems deleting files otherwise if ('f' === $type || $this->is_file($file)) { return @unlink($file); // phpcs:ignore } if (!$recursive && $this->is_dir($file)) { return @rmdir($file); // phpcs:ignore } // At this point it's a folder, and we're in recursive mode $file = $this->trailingslashit($file); $filelist = $this->dirlist($file, true); $retval = true; if (is_array($filelist)) { foreach ($filelist as $filename => $fileinfo) { if (!$this->delete($file . $filename, $recursive, $fileinfo['type'])) { $retval = false; } } } if (file_exists($file) && !@rmdir($file)) { // phpcs:ignore $retval = false; } return $retval; } /** * @access public * * @param string $file * * @return bool */ public function exists($file) { if (static::$wp_filesystem) { return static::$wp_filesystem->exists($file); } return @file_exists($file); } /** * @access public * * @param string $file * * @return bool */ public function is_file($file) { if (static::$wp_filesystem) { return static::$wp_filesystem->is_file($file); } return @is_file($file); } /** * @access public * * @param string $path * * @return bool */ public function is_dir($path) { if (static::$wp_filesystem) { return static::$wp_filesystem->is_dir($path); } return @is_dir($path); } /** * @access public * * @param string $file * * @return bool */ public function is_readable($file) { if (static::$wp_filesystem) { return static::$wp_filesystem->is_readable($file); } return @is_readable($file); } /** * @access public * * @param string $file * * @return bool */ public function is_writable($file) { if (static::$wp_filesystem) { return static::$wp_filesystem->is_writable($file); } return @is_writable($file); // phpcs:ignore } /** * @access public * * @param string $path * @param mixed $chmod * @param mixed $chown * @param mixed $chgrp * * @return bool */ public function mkdir($path, $chmod = false, $chown = false, $chgrp = false) { if (static::$wp_filesystem) { return static::$wp_filesystem->mkdir($path, $chmod, $chown, $chgrp); } // Safe mode fails with a trailing slash under certain PHP versions. $path = $this->untrailingslashit($path); if (empty($path)) { return false; } if (!$chmod) { $chmod = $this->FS_CHMOD_DIR; } if (!@mkdir($path)) { // phpcs:ignore return false; } $this->chmod($path, $chmod); if ($chown) { $this->chown($path, $chown); } if ($chgrp) { $this->chgrp($path, $chgrp); } return true; } /** * @param string $url * @param string|null $destination Destination file name * * @return bool|string Path on success, FALSE on failure * @throws Exception */ public function download($url, $destination = null) { $result = download_url($url); if (is_wp_error($result)) { /** @var WP_Error $result */ $safe_url = strtok($url, '?') . (strpos($url, '?') !== false ? '?...' : ''); $e = new AutoUpdater_Exception_Response('Failed to download file from URL: ' . $safe_url, 200); $e->setError($result->get_error_code(), $result->get_error_message()); throw $e; } if ($destination) { $this->move($result, $destination); return $destination; } return $result; } /** * @param string $file * @param string|null $destination * * @return string Path on success * @throws Exception */ public function unpack($file, $destination = null) { // WordPress will create destination directory if it does not exist if ($destination) { $path = $destination; } else { $path = dirname($file) . '/' . $this->getRandomName() . '/'; } $result = unzip_file($file, $path); if (is_wp_error($result)) { /** @var WP_Error $result */ $e = new AutoUpdater_Exception_Response('Failed to unpack file: ' . basename($file), 200); $e->setError($result->get_error_code(), $result->get_error_message()); throw $e; } return $path; } /** * @return bool */ public function setTempDirWithinSite() { if (defined('WP_TEMP_DIR') && strpos(realpath(WP_TEMP_DIR), realpath(AUTOUPDATER_SITE_PATH)) === false) { AutoUpdater_Log::error(sprintf('WP_TEMP_DIR is defined and it points to a location outside of the site: %s', WP_TEMP_DIR)); return false; } if (!defined('WP_TEMP_DIR')) { define('WP_TEMP_DIR', WP_CONTENT_DIR . '/upgrade'); } if (!$this->exists(WP_TEMP_DIR) && !$this->mkdir(WP_TEMP_DIR, 0700)) { AutoUpdater_Log::error(sprintf('Failed to create the directory: %s', WP_TEMP_DIR)); return false; } if (!$this->is_writable(WP_TEMP_DIR) && !$this->chmod(WP_TEMP_DIR, 0700)) { AutoUpdater_Log::error(sprintf('Failed to set write permission to the directory: %s', WP_TEMP_DIR)); return false; } return true; } /** * @return string */ public function getTempPath() { return get_temp_dir(); } /** * @return string */ public function getRandomName() { return wp_generate_password(16, false, false); } public function clearPhpCache() { // Make sure that PHP has the latest data of the files. @clearstatcache(); // Remove all compiled files from opcode cache. if (function_exists('opcache_reset')) { // Always reset the OPcache if it's enabled. Otherwise there's a good chance the server will not know we are // replacing .php scripts. This is a major concern since PHP 5.5 included and enabled OPcache by default. @opcache_reset(); } elseif (function_exists('apc_clear_cache')) { @apc_clear_cache(); // phpcs:ignore } } /** * @access public * * @param string $path * @param bool $include_hidden * @param bool $recursive * * @return bool|array */ public function dirlist($path, $include_hidden = true, $recursive = false) { if (static::$wp_filesystem) { return static::$wp_filesystem->dirlist($path, $include_hidden, $recursive); } if ($this->is_file($path)) { $limit_file = basename($path); $path = dirname($path); } else { $limit_file = false; } if (!$this->is_dir($path)) { return false; } $dir = @dir($path); if (!$dir) { return false; } $ret = array(); while (false !== ($entry = $dir->read())) { $struc = array(); $struc['name'] = $entry; if ('.' === $struc['name'] || '..' === $struc['name']) { continue; } if (!$include_hidden && '.' === $struc['name'][0]) { continue; } if ($limit_file && $struc['name'] !== $limit_file) { continue; } $struc['type'] = $this->is_dir($path . '/' . $entry) ? 'd' : 'f'; if ('d' === $struc['type']) { if ($recursive) { $struc['files'] = $this->dirlist($path . '/' . $struc['name'], $include_hidden, $recursive); } else { $struc['files'] = array(); } } $ret[$struc['name']] = $struc; } $dir->close(); unset($dir); return $ret; } /** * Appends a trailing slash. * * Will remove trailing forward and backslashes if it exists already before adding * a trailing forward slash. This prevents double slashing a string or path. * * @param string $string_value What to add the trailing slash to. * * @return string String with trailing slash added. */ public function trailingslashit($string_value) { return $this->untrailingslashit($string_value) . '/'; } /** * Removes trailing forward slashes and backslashes if they exist. * * @param string $string_value What to remove the trailing slashes from. * * @return string String without the trailing slashes. */ public function untrailingslashit($string_value) { return rtrim($string_value, '/\\'); } /** * Trims the path to site's root from a given path (i.e. replaces the left-most occurrence of it). * * @param string $path * * @return string */ public function trimPath($path) { // Only replace the site's path from the beginning of the path to the file list // this makes sure that if the site's chrooted (i.e. AUTOUPDATER_SITE_PATH === '/') // we'll not replace every '/' in the string if (substr($path, 0, strlen(AUTOUPDATER_SITE_PATH)) === AUTOUPDATER_SITE_PATH) { $path = substr($path, strlen(AUTOUPDATER_SITE_PATH)); } return $path; } }