I'm working on a function that displays recursively the list of files inside a zip file. A zip file inside another zip file could happen and I want to display its contents like a folder.
I have read carefully this question but I'm yet lost. Ideally It will be perfect if I could get the within zip with getStream and send this stream to another ziparchive::open, but I've not had much this way.
Things I have tried (just to test):
Try 1:
$data = file_get_contents('outer.zip');
$data64 = base64_encode($data);
$zip = new ZipArchive();
$r = $zip->open('data::text/plain;base64,'.$data64);
var_dump($r);
if($r){
$zip->close();
}
Try 2:
$zip = new ZipArchive();
$r = $zip->open('zip://outer.zip#inner.zip');
var_dump($r);
if($r){
$zip->close();
}
It will be so slow and bloated if I have to recursively decompress any file to temp just to list the files inside, especially with large files. At this point I'm seeking even shell_exec solutions.
Related
I have a ZIP file on my server. I want to create a PHP file, loadZIP.php that will accept a single parameter, and then modify a text file within the ZIP to reflect that parameter.
So, accessing loadZIP.php?param=blue, will open up the zip file, and replace some text in a text file I specify with 'blue', and allow the user to download this edited zip file.
I've looked over all of the PHP ZIP functions, but I can't find a simple solution. It seems like a relatively easy problem, and I believe I'm over thinking it. Before I go and write some overly complex functions, I was wondering how you'd go about this.
Have you taken a look at PHP5's ZipArchive functions?
Basically, you can use ZipArchive::Open() to open the zip, then ZipArchive::getFromName() to read the file into memory. Then, modify it, use ZipArchive::deleteName() to remove the old file, use ZipArchive::AddFromString() to write the new contents back to the zip, and ZipArchive::close():
$zip = new ZipArchive;
$fileToModify = 'myfile.txt';
if ($zip->open('test1.zip') === TRUE) {
//Read contents into memory
$oldContents = $zip->getFromName($fileToModify);
//Modify contents:
$newContents = str_replace('key', $_GET['param'], $oldContents)
//Delete the old...
$zip->deleteName($fileToModify)
//Write the new...
$zip->addFromString($fileToModify, $newContents);
//And write back to the filesystem.
$zip->close();
echo 'ok';
} else {
echo 'failed';
}
Note ZipArchive was introduced in PHP 5.2.0 (but, ZipArchive is also available as a PECL package).
In PHP 8 you can use ZipArchive::replaceFile
As demonstrated by this example from the docs:
<?php
$zip = new ZipArchive;
if ($zip->open('test.zip') === TRUE) {
$zip->replaceFile('/path/to/index.txt', 1);
$zip->close();
echo 'ok';
} else {
echo 'failed';
}
?>
I have written the PHP script to generate the zip file. it's working fine when I use rar software to extract it but not getting extract with rar software. I can't ask to users to install rar software to extract downloaded zip file.
I don't know where i am commiting mistakes.
Here i attached error screen shot which i get when try to open zip file.
// Here is code snippet
$obj->create_zip($files_to_zip, $dir . '/download.zip');
// Code for create_zip function
//create the archive
$zip = new ZipArchive();
if ($zip->open($destination, $overwrite ? ZIPARCHIVE::OVERWRITE : ZIPARCHIVE::CREATE) !== true) {
return false;
}
//add the files
foreach ($valid_files as $file) {
$filearr = explode('/', $file);
$zip->addFile($file, end($filearr));
}
$zip->close();
If $valid_files is a glob'd array, use basename() instead of end(), your zip might not actually have added any files causing for it to be an invalid zip (however that would be visible in the size of the zip file).
Also try winrar/winzip/7zip and see what they return, microsoft's internal zip engine might not be up to date enough to open the zips.
I have also encountered this problem, using 7z solved the problem but we need to send the zip to somebody else so 7z is a nono.
I found that, in my case it is that the file path is too long:
When I use this:
$zip->addFile($files_path.'/people.txt');
And it generated a zip folder nested very deep e.g. ["/tmp/something/something1/something2/people.txt"]
So I need to use this instead
$zip->addFile($files_path.'/people.txt', 'people.txt');
Which generate a a zip folder with only 1 layer ["people.txt"], and Windows Zip read perfectly~
Hope this helps somebody that also have this problem!
I have a .tar.gz file downloaded from an external API which we have to implement. It contains images for an object.
I'm not sure how they managed to compress it this way, but the files are basically prefixed with the "current directory". It looks like this in WinRAR:
And like this in 7-Zip, note the .tar first level, and "." second level:
-> ->
When calling
$file = 'archive.tar.gz';
$phar = new PharData($file, FilesystemIterator::CURRENT_AS_FILEINFO);
var_dump($phar->offsetGet('./12613_s_cfe3e73.jpg'));
I get the exception:
Cannot access phar file entry '/12613_s_cfe3e73.jpg' in archive '{...}/archive.tar.gz'
Calling a file which does not exist, e.g.:
var_dump($phar->offsetGet('non-existent.jpg'));
Or calling it without the directory seperator, e.g.:
var_dump($phar->offsetGet('12613_s_cfe3e73.jpg'));
I get a
Entry 12613_s_cfe3e73.jpg does not exist
Exception.
It is not possible to get the archive formatted differently. Does anyone have an idea how to solve this?
Ended up using Archive_Tar. There must be something wrong in the source code of PHP, though I don't think this is the "normal" way of packaging a .tar either.
Unfortunately I'm not very good at C, but it's probably in here (line 1214) or here.
This library seems to handle it just fine, using this example code:
$file = 'archive.tar.gz';
$zip = new Archive_Tar($file);
foreach ($zip->listContent() as $file) {
echo $file['filename'] . '<br>';
}
Result:
./12613_s_f3b483d.jpg
./12613_s_cfe3e73.jpg
./1265717_s_db141dc.jpg
./1265717_s_af5de56.jpg
./1265717_s_b783547.jpg
./1265717_s_35b11f9.jpg
./1265716_s_83ef572.jpg
./1265716_s_9ac2725.jpg
./1265716_s_c5af3e9.jpg
./1265716_s_c070da3.jpg
./1265715_s_4339e8a.jpg
Note the filenames are still prefixed with "./" just like they are in WinRAR.
If you want to stick to using PharData, i suggest a more conservative, two-step approach, where you first decompress the gz and then unarchive all files of the tar to a target folder.
// decompress gz archive to get "/path/to/my.tar" file
$gz = new PharData('/path/to/my.tar.gz');
$gz->decompress();
// unarchive all files from the tar to the target path
$tar = new PharData('/path/to/my.tar');
$tar->extractTo('/target/path');
But it looks like you want to select individual files from the tar.gz archive directly, right?
It should work using fopen() with a StreamReader (compress.zlib or phar) and selecting the individual file. Some examples:
$f = fopen("compress.zlib://http://some.website.org/my.gz/file/in/the/archive", "r");
$f = fopen('phar:///path/to/my.tar.gz//file/in/archive', 'r');
$filecontent = file_get_contents('phar:///some/my.tar.gz/some/file/in/the/archive');
Streaming should also work, when using Iterators:
$rdi = new RecursiveDirectoryIterator('phar:///path/to/my.tar.gz')
$rii = new RecursiveIteratorIterator($rdi, RecursiveIteratorIterator::CHILD_FIRST);
foreach ($rii as $splFileInfo){
echo file_get_contents($splFileInfo->getPathname());
}
The downside is that you have to buffer the stream and save it to file.
Its not a direct file extraction to a target folder.
I'm trying to zip two files in another directory without zipping the folder hierarchy as well.
The event is triggered by a button press, which causes Javascript to send information using AJAX to PHP. PHP calls a Perl script (to take advantage of Perl's XLSX writer module and the fact that PHP kind of sucks, but I digress...), which puts the files a few folders down the hierarchy. The relevant code is shown below.
system("createFiles.pl -ids ${rows} -test ${test} -path ${path}",$retVal);
`zip ${path}/{$test}_both.zip ${path}/${test}.csv ${path}/${test}.xlsx`;
`zip ${path}/{$test}_csv.zip ${path}/${test}.csv`;
The problem is the zip file has ${path} hierarchy that has to be navigated before the files are shown as seen below:
I tried doing this (cd before each zip command):
system("createFiles.pl -ids ${rows} -test ${test} -path ${path}",$retVal);
`cd ${path}; zip {$test}_both.zip ${test}.csv ${test}.xlsx`;
`cd ${path}; zip {$test}_csv.zip ${test}.csv`;
And it worked, but it seems like a hack. Is there a better way?
The ZipArchive answer by Oldskool is good. I've used ZipArchive and it works. However, I recommend PclZip instead as it is more versatile (e.g. allows for zipping with no compression, ideal if you are zipping up images which are already compressed, much faster). PclZip supports the PCLZIP_OPT_REMOVE_ALL_PATH option to remove all file paths. e.g.
$zip = new PclZip("$path/{$test}_both.zip");
$files = array("$path/$test.csv", "$path/$test.xlsx");
// create the Zip archive, without paths or compression (images are already compressed)
$properties = $zip->create($files, PCLZIP_OPT_REMOVE_ALL_PATH);
if (!is_array($properties)) {
die($zip->errorInfo(true));
}
If you use PHP 5 >= 5.2.0 you can use the ZipArchive class. You can then use the full path as source filename and just the filename as target name. Like this:
$zip = new ZipArchive;
if($zip->open("{$test}_both.zip", ZIPARCHIVE::OVERWRITE) === true) {
// Add the files here with full path as source, short name as target
$zip->addFile("${path}/${test}.csv", "${test}.csv");
$zip->addFile("${path}/${test}.xlsx", "${test}.xlsx");
$zip->close();
} else {
die("Zip creation failed.");
}
// Same for the second archive
$zip2 = new ZipArchive;
if($zip2->open("{$test}_csv.zip", ZIPARCHIVE::OVERWRITE) === true) {
// Add the file here with full path as source, short name as target
$zip2->addFile("${path}/${test}.csv", "${test}.csv");
$zip2->close();
} else {
die("Zip creation failed.");
}
The code from this answer simply extracts the contents of a zip file to a folder:
<?php
$zip = new ZipArchive;
$res = $zip->open('file.zip');
if ($res === TRUE) {
$zip->extractTo('/myzips/extract_path/');
$zip->close();
echo 'woot!';
} else {
echo 'doh!';
}
?>
Is there any simple way (without a foreach loop, per file, and recursively descending into subfolders) to run this operation in reverse, in PHP, so that the original zip file is created again?
The "extract_path" folder must not be included in the zip file, which is why I can't simply use the recursive function linked above.
This would be the PHP equivalent of doing these linux shell commands, which are pretty simple:
cd /myzips/extract_path/
zip /dest/file.zip *
(before anyone comments "Why would you want to do that?", it's because I want to modify a file then recreate the zip file)