WordPress Version: 6.4
/**
* Attempts to unzip an archive using the ZipArchive class.
*
* This function should not be called directly, use `unzip_file()` instead.
*
* Assumes that WP_Filesystem() has already been called and set up.
*
* @since 3.0.0
* @access private
*
* @see unzip_file()
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*
* @param string $file Full path and filename of ZIP archive.
* @param string $to Full path on the filesystem to extract archive to.
* @param string[] $needed_dirs A partial list of required folders needed to be created.
* @return true|WP_Error True on success, WP_Error on failure.
*/
function _unzip_file_ziparchive($file, $to, $needed_dirs = array())
{
global $wp_filesystem;
$z = new ZipArchive();
$zopen = $z->open($file, ZIPARCHIVE::CHECKCONS);
if (true !== $zopen) {
return new WP_Error('incompatible_archive', __('Incompatible Archive.'), array('ziparchive_error' => $zopen));
}
$uncompressed_size = 0;
for ($i = 0; $i < $z->numFiles; $i++) {
$info = $z->statIndex($i);
if (!$info) {
$z->close();
return new WP_Error('stat_failed_ziparchive', __('Could not retrieve file from archive.'));
}
if (str_starts_with($info['name'], '__MACOSX/')) {
// Skip the OS X-created __MACOSX directory.
continue;
}
// Don't extract invalid files:
if (0 !== validate_file($info['name'])) {
continue;
}
$uncompressed_size += $info['size'];
$dirname = dirname($info['name']);
if (str_ends_with($info['name'], '/')) {
// Directory.
$needed_dirs[] = $to . untrailingslashit($info['name']);
} elseif ('.' !== $dirname) {
// Path to a file.
$needed_dirs[] = $to . untrailingslashit($dirname);
}
}
// Enough space to unzip the file and copy its contents, with a 10% buffer.
$required_space = $uncompressed_size * 2.1;
/*
* disk_free_space() could return false. Assume that any falsey value is an error.
* A disk that has zero free bytes has bigger problems.
* Require we have enough space to unzip the file and copy its contents, with a 10% buffer.
*/
if (wp_doing_cron()) {
$available_space = function_exists('disk_free_space') ? @disk_free_space(WP_CONTENT_DIR) : false;
if ($available_space && $required_space > $available_space) {
$z->close();
return new WP_Error('disk_full_unzip_file', __('Could not copy files. You may have run out of disk space.'), compact('uncompressed_size', 'available_space'));
}
}
$needed_dirs = array_unique($needed_dirs);
foreach ($needed_dirs as $dir) {
// Check the parent folders of the folders all exist within the creation array.
if (untrailingslashit($to) === $dir) {
// Skip over the working directory, we know this exists (or will exist).
continue;
}
if (!str_contains($dir, $to)) {
// If the directory is not within the working directory, skip it.
continue;
}
$parent_folder = dirname($dir);
while (!empty($parent_folder) && untrailingslashit($to) !== $parent_folder && !in_array($parent_folder, $needed_dirs, true)) {
$needed_dirs[] = $parent_folder;
$parent_folder = dirname($parent_folder);
}
}
asort($needed_dirs);
// Create those directories if need be:
foreach ($needed_dirs as $_dir) {
// Only check to see if the Dir exists upon creation failure. Less I/O this way.
if (!$wp_filesystem->mkdir($_dir, FS_CHMOD_DIR) && !$wp_filesystem->is_dir($_dir)) {
$z->close();
return new WP_Error('mkdir_failed_ziparchive', __('Could not create directory.'), $_dir);
}
}
/**
* Filters archive unzipping to override with a custom process.
*
* @since 6.4.0
*
* @param null|true|WP_Error $result The result of the override. True on success, otherwise WP Error. Default null.
* @param string $file Full path and filename of ZIP archive.
* @param string $to Full path on the filesystem to extract archive to.
* @param string[] $needed_dirs A full list of required folders that need to be created.
* @param float $required_space The space required to unzip the file and copy its contents, with a 10% buffer.
*/
$pre = apply_filters('pre_unzip_file', null, $file, $to, $needed_dirs, $required_space);
if (null !== $pre) {
// Ensure the ZIP file archive has been closed.
$z->close();
return $pre;
}
for ($i = 0; $i < $z->numFiles; $i++) {
$info = $z->statIndex($i);
if (!$info) {
$z->close();
return new WP_Error('stat_failed_ziparchive', __('Could not retrieve file from archive.'));
}
if (str_ends_with($info['name'], '/')) {
// Directory.
continue;
}
if (str_starts_with($info['name'], '__MACOSX/')) {
// Don't extract the OS X-created __MACOSX directory files.
continue;
}
// Don't extract invalid files:
if (0 !== validate_file($info['name'])) {
continue;
}
$contents = $z->getFromIndex($i);
if (false === $contents) {
$z->close();
return new WP_Error('extract_failed_ziparchive', __('Could not extract file from archive.'), $info['name']);
}
if (!$wp_filesystem->put_contents($to . $info['name'], $contents, FS_CHMOD_FILE)) {
$z->close();
return new WP_Error('copy_failed_ziparchive', __('Could not copy file.'), $info['name']);
}
}
$z->close();
/**
* Filters the result of unzipping an archive.
*
* @since 6.4.0
*
* @param true|WP_Error $result The result of unzipping the archive. True on success, otherwise WP_Error. Default true.
* @param string $file Full path and filename of ZIP archive.
* @param string $to Full path on the filesystem the archive was extracted to.
* @param string[] $needed_dirs A full list of required folders that were created.
* @param float $required_space The space required to unzip the file and copy its contents, with a 10% buffer.
*/
$result = apply_filters('unzip_file', true, $file, $to, $needed_dirs, $required_space);
unset($needed_dirs);
return $result;
}