_unzip_file_ziparchive( string $file, string $to, string[] $needed_dirs = array() ): true|WP_Error

This function’s access is marked private. This means it is not intended for use by plugin or theme developers, only in other core functions. It is listed here for completeness. Use unzip_file() instead.

Attempts to unzip an archive using the ZipArchive class.

Description

This function should not be called directly, use unzip_file() instead.

Assumes that WP_Filesystem() has already been called and set up.

See also

Parameters

$filestringrequired
Full path and filename of ZIP archive.
$tostringrequired
Full path on the filesystem to extract archive to.
$needed_dirsstring[]optional
A partial list of required folders needed to be created.

Default:array()

Return

true|WP_Error True on success, WP_Error on failure.

Source

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;
}

Hooks

apply_filters( ‘pre_unzip_file’, null|true|WP_Error $result, string $file, string $to, string[] $needed_dirs, float $required_space )

Filters archive unzipping to override with a custom process.

apply_filters( ‘unzip_file’, true|WP_Error $result, string $file, string $to, string[] $needed_dirs, float $required_space )

Filters the result of unzipping an archive.

Changelog

VersionDescription
3.0.0Introduced.

User Contributed Notes

You must log in before being able to contribute a note or feedback.