Skip to content

Commit ef7fa1c

Browse files
costdevmikachan
authored andcommitted
Filesystem API: Introduce filters for before/after unzipping archives.
This introduces the following new filters which wrap the process of unzipping an archive: - `pre_unzip_file` - Filters archive unzipping to allow an override with a custom process. - `unzip_file` - Filters the result of unzipping an archive. Both filters pass the following: - `string $file` - Full path and filename of ZIP archive. - `string $to` - Full path on the filesystem to extract archive to. - `string[] $needed_dirs` - A full list of required folders that need to be created. - `float|false $required_space` - The space required to unzip the file and copy its contents, with a 10% buffer. Props dfavor, azaozz, oglekler, afragen, costdev. Fixes #37719. git-svn-id: https://develop.svn.wordpress.org/trunk@56689 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 0eae815 commit ef7fa1c

3 files changed

Lines changed: 217 additions & 6 deletions

File tree

src/wp-admin/includes/file.php

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1697,6 +1697,9 @@ function _unzip_file_ziparchive( $file, $to, $needed_dirs = array() ) {
16971697
}
16981698
}
16991699

1700+
// Enough space to unzip the file and copy its contents, with a 10% buffer.
1701+
$required_space = $uncompressed_size * 2.1;
1702+
17001703
/*
17011704
* disk_free_space() could return false. Assume that any falsey value is an error.
17021705
* A disk that has zero free bytes has bigger problems.
@@ -1705,7 +1708,7 @@ function _unzip_file_ziparchive( $file, $to, $needed_dirs = array() ) {
17051708
if ( wp_doing_cron() ) {
17061709
$available_space = function_exists( 'disk_free_space' ) ? @disk_free_space( WP_CONTENT_DIR ) : false;
17071710

1708-
if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) {
1711+
if ( $available_space && ( $required_space > $available_space ) ) {
17091712
return new WP_Error(
17101713
'disk_full_unzip_file',
17111714
__( 'Could not copy files. You may have run out of disk space.' ),
@@ -1746,7 +1749,26 @@ function _unzip_file_ziparchive( $file, $to, $needed_dirs = array() ) {
17461749
return new WP_Error( 'mkdir_failed_ziparchive', __( 'Could not create directory.' ), $_dir );
17471750
}
17481751
}
1749-
unset( $needed_dirs );
1752+
1753+
/**
1754+
* Filters archive unzipping to override with a custom process.
1755+
*
1756+
* @since 6.4.0
1757+
*
1758+
* @param null|true|WP_Error $result The result of the override. True on success, otherwise WP Error. Default null.
1759+
* @param string $file Full path and filename of ZIP archive.
1760+
* @param string $to Full path on the filesystem to extract archive to.
1761+
* @param string[] $needed_dirs A full list of required folders that need to be created.
1762+
* @param float $required_space The space required to unzip the file and copy its contents, with a 10% buffer.
1763+
*/
1764+
$pre = apply_filters( 'pre_unzip_file', null, $file, $to, $needed_dirs, $required_space );
1765+
1766+
if ( null !== $pre ) {
1767+
// Ensure the ZIP file archive has been closed.
1768+
$z->close();
1769+
1770+
return $pre;
1771+
}
17501772

17511773
for ( $i = 0; $i < $z->numFiles; $i++ ) {
17521774
$info = $z->statIndex( $i );
@@ -1781,7 +1803,22 @@ function _unzip_file_ziparchive( $file, $to, $needed_dirs = array() ) {
17811803

17821804
$z->close();
17831805

1784-
return true;
1806+
/**
1807+
* Filters the result of unzipping an archive.
1808+
*
1809+
* @since 6.4.0
1810+
*
1811+
* @param true|WP_Error $result The result of unzipping the archive. True on success, otherwise WP_Error. Default true.
1812+
* @param string $file Full path and filename of ZIP archive.
1813+
* @param string $to Full path on the filesystem the archive was extracted to.
1814+
* @param string[] $needed_dirs A full list of required folders that were created.
1815+
* @param float $required_space The space required to unzip the file and copy its contents, with a 10% buffer.
1816+
*/
1817+
$result = apply_filters( 'unzip_file', true, $file, $to, $needed_dirs, $required_space );
1818+
1819+
unset( $needed_dirs );
1820+
1821+
return $result;
17851822
}
17861823

17871824
/**
@@ -1838,6 +1875,9 @@ function _unzip_file_pclzip( $file, $to, $needed_dirs = array() ) {
18381875
$needed_dirs[] = $to . untrailingslashit( $file['folder'] ? $file['filename'] : dirname( $file['filename'] ) );
18391876
}
18401877

1878+
// Enough space to unzip the file and copy its contents, with a 10% buffer.
1879+
$required_space = $uncompressed_size * 2.1;
1880+
18411881
/*
18421882
* disk_free_space() could return false. Assume that any falsey value is an error.
18431883
* A disk that has zero free bytes has bigger problems.
@@ -1846,7 +1886,7 @@ function _unzip_file_pclzip( $file, $to, $needed_dirs = array() ) {
18461886
if ( wp_doing_cron() ) {
18471887
$available_space = function_exists( 'disk_free_space' ) ? @disk_free_space( WP_CONTENT_DIR ) : false;
18481888

1849-
if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) {
1889+
if ( $available_space && ( $required_space > $available_space ) ) {
18501890
return new WP_Error(
18511891
'disk_full_unzip_file',
18521892
__( 'Could not copy files. You may have run out of disk space.' ),
@@ -1887,7 +1927,13 @@ function _unzip_file_pclzip( $file, $to, $needed_dirs = array() ) {
18871927
return new WP_Error( 'mkdir_failed_pclzip', __( 'Could not create directory.' ), $_dir );
18881928
}
18891929
}
1890-
unset( $needed_dirs );
1930+
1931+
/** This filter is documented in src/wp-admin/includes/file.php */
1932+
$pre = apply_filters( 'pre_unzip_file', null, $file, $to, $needed_dirs, $required_space );
1933+
1934+
if ( null !== $pre ) {
1935+
return $pre;
1936+
}
18911937

18921938
// Extract the files from the zip.
18931939
foreach ( $archive_files as $file ) {
@@ -1909,7 +1955,12 @@ function _unzip_file_pclzip( $file, $to, $needed_dirs = array() ) {
19091955
}
19101956
}
19111957

1912-
return true;
1958+
/** This action is documented in src/wp-admin/includes/file.php */
1959+
$result = apply_filters( 'unzip_file', true, $file, $to, $needed_dirs, $required_space );
1960+
1961+
unset( $needed_dirs );
1962+
1963+
return $result;
19131964
}
19141965

19151966
/**
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
/**
4+
* Tests _unzip_file_pclzip().
5+
*
6+
* @group file.php
7+
*
8+
* @covers ::_unzip_file_pclzip
9+
*/
10+
class Tests_Filesystem_UnzipFilePclzip extends WP_UnitTestCase {
11+
12+
/**
13+
* The test data directory.
14+
*
15+
* @var string $test_data_dir
16+
*/
17+
private static $test_data_dir;
18+
19+
/**
20+
* Sets up the filesystem and test data directory property
21+
* before any tests run.
22+
*/
23+
public static function set_up_before_class() {
24+
parent::set_up_before_class();
25+
26+
require_once ABSPATH . 'wp-admin/includes/file.php';
27+
WP_Filesystem();
28+
29+
self::$test_data_dir = DIR_TESTDATA . '/filesystem/';
30+
}
31+
32+
/**
33+
* Tests that _unzip_file_pclzip() applies "pre_unzip_file" filters.
34+
*
35+
* @ticket 37719
36+
*/
37+
public function test_should_apply_pre_unzip_file_filters() {
38+
$filter = new MockAction();
39+
add_filter( 'pre_unzip_file', array( $filter, 'filter' ) );
40+
41+
// Prepare test environment.
42+
$unzip_destination = self::$test_data_dir . 'archive/';
43+
mkdir( $unzip_destination );
44+
45+
_unzip_file_pclzip( self::$test_data_dir . 'archive.zip', $unzip_destination );
46+
47+
// Cleanup test environment.
48+
$this->rmdir( $unzip_destination );
49+
$this->delete_folders( $unzip_destination );
50+
51+
$this->assertSame( 1, $filter->get_call_count() );
52+
}
53+
54+
/**
55+
* Tests that _unzip_file_pclzip() applies "unzip_file" filters.
56+
*
57+
* @ticket 37719
58+
*/
59+
public function test_should_apply_unzip_file_filters() {
60+
$filter = new MockAction();
61+
add_filter( 'unzip_file', array( $filter, 'filter' ) );
62+
63+
// Prepare test environment.
64+
$unzip_destination = self::$test_data_dir . 'archive/';
65+
mkdir( $unzip_destination );
66+
67+
_unzip_file_pclzip( self::$test_data_dir . 'archive.zip', $unzip_destination );
68+
69+
// Cleanup test environment.
70+
$this->rmdir( $unzip_destination );
71+
$this->delete_folders( $unzip_destination );
72+
73+
$this->assertSame( 1, $filter->get_call_count() );
74+
}
75+
76+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
/**
4+
* Tests _unzip_file_ziparchive().
5+
*
6+
* @group file.php
7+
*
8+
* @covers ::_unzip_file_ziparchive
9+
*/
10+
class Tests_Filesystem_UnzipFileZiparchive extends WP_UnitTestCase {
11+
12+
/**
13+
* The test data directory.
14+
*
15+
* @var string $test_data_dir
16+
*/
17+
private static $test_data_dir;
18+
19+
/**
20+
* Sets up the filesystem and test data directory property
21+
* before any tests run.
22+
*/
23+
public static function set_up_before_class() {
24+
parent::set_up_before_class();
25+
26+
require_once ABSPATH . 'wp-admin/includes/file.php';
27+
WP_Filesystem();
28+
29+
self::$test_data_dir = DIR_TESTDATA . '/filesystem/';
30+
}
31+
32+
/**
33+
* Tests that _unzip_file_ziparchive() applies "pre_unzip_file" filters.
34+
*
35+
* @ticket 37719
36+
*/
37+
public function test_should_apply_pre_unzip_file_filters() {
38+
if ( ! class_exists( 'ZipArchive' ) ) {
39+
$this->markTestSkipped( 'This test requires the ZipArchive class.' );
40+
}
41+
42+
$filter = new MockAction();
43+
add_filter( 'pre_unzip_file', array( $filter, 'filter' ) );
44+
45+
// Prepare test environment.
46+
$unzip_destination = self::$test_data_dir . 'archive/';
47+
mkdir( $unzip_destination );
48+
49+
_unzip_file_ziparchive( self::$test_data_dir . 'archive.zip', $unzip_destination );
50+
51+
// Cleanup test environment.
52+
$this->rmdir( $unzip_destination );
53+
$this->delete_folders( $unzip_destination );
54+
55+
$this->assertSame( 1, $filter->get_call_count() );
56+
}
57+
58+
/**
59+
* Tests that _unzip_file_ziparchive() applies "unzip_file" filters.
60+
*
61+
* @ticket 37719
62+
*/
63+
public function test_should_apply_unzip_file_filters() {
64+
if ( ! class_exists( 'ZipArchive' ) ) {
65+
$this->markTestSkipped( 'This test requires the ZipArchive class.' );
66+
}
67+
68+
$filter = new MockAction();
69+
add_filter( 'unzip_file', array( $filter, 'filter' ) );
70+
71+
// Prepare test environment.
72+
$unzip_destination = self::$test_data_dir . 'archive/';
73+
mkdir( $unzip_destination );
74+
75+
_unzip_file_ziparchive( self::$test_data_dir . 'archive.zip', $unzip_destination );
76+
77+
// Cleanup test environment.
78+
$this->rmdir( $unzip_destination );
79+
$this->delete_folders( $unzip_destination );
80+
81+
$this->assertSame( 1, $filter->get_call_count() );
82+
}
83+
84+
}

0 commit comments

Comments
 (0)