ZipArchive can't unzip files that ZipArchive zips in PHP - php

Trying to get import/export functionality.
With a folder containing a file
Zip the folder in PHP using ZipArchive
Unzip the zipped folder using ZipArchive
New folder is created, but the file inside is missing
function unArchive(){
$zip = new ZipArchive;
$res = $zip->open('zipTest.zip');
if ($res === TRUE) {
echo 'ok';
$zip->extractTo("testfolder2");
$zip->close();
} else {
echo 'failed, code:' . $res;
}
}
function Zip($source, $destination){
if (!extension_loaded('zip') || !file_exists($source)){
return false;
}
$zip = new ZipArchive();
if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
return false;
}
$source = str_replace('\\', '/', realpath($source));
if (is_dir($source) === true){
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
foreach ($files as $file){
$file = str_replace('\\', '/', realpath($file));
if (is_dir($file) === true){
$zip->addEmptyDir( str_replace($source . '/', '', $file . '/') );
}else if (is_file($file) === true){
$zip->addFromString( str_replace($source . '/', '', $file), file_get_contents($file));
}
}
}else if (is_file($source) === true){
$zip->addFromString(basename($source), file_get_contents($source));
}
return $zip->close();
}
Zip( "testfolder/" , "./zipTest.zip" );
unArchive();
Code works fine for single files.
With a file zipped using ZipArchive, I can manually unzip using WinRAR, then rezip using WinRAR, and then ZipArchive can successfully unzip that file.
The problem is that ZipArchive won't extract the archive that it directly zipped.
I can also see in Notepad++ that the ZipArchive zip is different from the WinRAR zip, and the file size is different. I can successfully call winrar as a system command in php for the desired functionality, but this seems like a bad idea.
Is there some reason ZipArchive creates zip files that it can't extract?
Error message: ZipArchive::extractTo(): Invalid argument in [file] on line [$zip->extractTo("testfolder2");]

The RecursiveIteratorIterator returns the pseudo-files "." and, that's important here, ".." - the parent directory. When you use addEmptyDir to add the parent directory, the ZipArchive will add it (somehow)! You may even try "$zip->addEmptyDir("../something")" and it will not complain.
Now, usually, zip files don't behave this way. When you extract them, you expect all contents to be in the folder you extract them, not in the parent folders. This is why ordinary ZIP extraction programs do not show it.
The PHP ZipArchive will extract even these folders, however. Then, this error will somehow occur, because you tried to include the "." or ".." directories.
Add this at the beginning of the foreach cycle to resolve this:
if (basename($file) === ".") { continue;}
if (basename($file) === "..") { continue; }
Test case:
<?php
$archive = new ZipArchive();
$archive->open('c.zip',ZipArchive::CREATE);
$archive->addFile('a.txt');
$archive->addFile('b.txt');
$archive->addEmptyDir("../../down");
$archive->close();
$archive2 = new ZipArchive();
$archive2->open('c.zip');
$archive2->extractTo('here');
$archive2->close();
I tested this only on Windows.

Related

ZipArchive::close() returns false even with all permissions in folder

Everything works fine on localhost, but when I try to get this script to work on server I couldn't. I tried to change permission of root and uploads (where uploaded images are and where .zip files should be created) to 777 (I know it's not safe, but I just tried to test because it worked on localhost) but it doesn't. I also don't get any additional errors, and error reporintg (E_ALL) is turned on in php.ini. Directories are also created normally in that folder via mkdir() function.
Here is the function for zipping folders:
function Zip($source, $destination)
{
if (!extension_loaded('zip') || !file_exists($source)) {
return false;
}
$zip = new ZipArchive();
if (!$zip->open($destination, ZIPARCHIVE::CREATE)) {
return false;
}
$source = str_replace('\\', '/', realpath($source));
if (is_dir($source) === true)
{
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
foreach ($files as $file)
{
$file = str_replace('\\', '/', $file);
// Ignore "." and ".." folders
if( in_array(substr($file, strrpos($file, '/')+1), array('.', '..')) )
continue;
$file = realpath($file);
if (is_dir($file) === true)
{
$zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
}
else if (is_file($file) === true)
{
$zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
}
}
}
else if (is_file($source) === true)
{
$zip->addFromString(basename($source), file_get_contents($source));
}
return $zip->close();
}
if (Zip($folder.$archiveName."/", $folder.$archiveName.".zip")) {
echo "Done.";
rrmdir($folder.$archiveName."/");
} else {
addError("Error while trying to compress files.");
}
I also tried to write out the error message:
if ($zip->close()) {
echo "Done.";
}
else {
echo $zip->getStatusString();
}
But it doesn't write out anything.
Problem is solved - I localized problem with debugging and realized that zip extension wasn't loaded. In case you encounter this problem too (site is deployed on DigitalOcean) and you're using Debian or derivative just install php7.0-zip package:
sudo apt-get install php7.0-zip
Another possible cause for ZipArchive::close to return false can be caused by file permissions on any of the files being added to the archive.
If PHP can't open the file added to the archive, ZipArchive::addFile() will return true, but when ZipArchive::close() is called you will get:
"Warning: ZipArchive::close(): Can't open file: Permission denied"
More details in this comment from the PHP Manual:
https://www.php.net/manual/en/ziparchive.addfile.php#101605
Beware: calling $zip->addFile() on a file that doesn't exist will
succeed and return TRUE, delaying the failure until you make the final
$zip->close() call, which will return FALSE and potentially leave you
scratching your head.
If you're adding multiple files to a zip and your $zip->close() call
is returning FALSE, ensure that all the files you added actually
exist.
It's also a good idea to check each file with file_exists() or
is_readable() before calling $zip->addFile() on it.

how to create a zip file from a folder - php

I have a problem.
I searched the internet for a solution, but nothing works well. I'm trying to make a script that will take files from a folder and threw them into a zip archive.
But it doesn't work ..
I tried through different paths, but it's only me mixed up and nothing came of it.
Here is the current code that is according to me the easiest and should workbut doesn't do that ..
Can you help me?
function archivebackup(){
$zip = new ZipArchive;
if ($zip->open('Mail.zip', ZipArchive::CREATE) === TRUE){
foreach (new DirectoryIterator('Mails/') as $fileInfo) {
$fileName = $fileInfo->getFilename();
// echo $fileName ."<br>";
$zip->addFile($fileName);
}
$zip->close();
}
}
It seems you're not including files in your current working directory and getFilename() only returns a filename without a path.
Do the following:
function archivebackup(){
$zip = new ZipArchive;
if ($zip->open('Mail.zip', ZipArchive::CREATE) === TRUE){
foreach (new DirectoryIterator('Mails/') as $fileInfo) {
if (in_array($fileInfo->getFilename(), [ ".", ".." ]) { continue; }
$fileName = $fileInfo->getPathname();
$zip->addFile($fileName);
}
$zip->close();
}
}

Preserve the folder structure when creating a Zip archive

I want to create a zip file and copy all the folders and files from a directory to it. It is successfully created and contains the files and folders, but the file tree is not preserved, everything being in the root directory.
My directory:
folder/
test.txt
test2.txt
test.php
The zip archive:
folder/
test.txt
test2.txt
test.php
This is my code:
public function createZipFromDir($dir, $zip_file) {
$zip = new ZipArchive();
if(true !== $zip->open($zip_file, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)) {
return false;
}
$this->zipDir($dir, $zip);
return $zip;
}
public function zipDir($dir, $zip) {
$dir = rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$files = scandir($dir);
foreach($files as $file) {
if(in_array($file, array('.', '..'))) continue;
if(is_dir($dir . $file)) {
$zip->addEmptyDir($file);
$this->zipDir($dir . $file, $zip);
} else {
$zip->addFile($dir . $file, $file);
}
}
}
$zip = $this->createZipFromDir($rootPath, $archiveName);
The issue is that when you create a folder or set the localname (second argument of addFile()) when adding a file to the archive, you only use $file, therefore everything gets put at the root. It is necessary to provide the file hierarchy as well.
Now the obvious solution would be to use $dir.$file instead, but this would only work properly on a folder located in the same directory as the script.
We actually need to keep track of two file trees:
the real tree, as it exists on the machine
the archive tree, relative to the path we want to archive
But since one is just a subset of the other, we can easily keep track of that by splitting the real path in two:
$dir, a prefix pointing to the original path
$subdir, a path relative to $dir
When referring to a file on the machine, we use $dir.$subdir and when referring to a file in the archive we use only $subdir. This requires us to adapt zipDir() to keep track of the prefix by adding a third argument to it and slightly modifying the call to zipDir() in createZipFromDir().
function createZipFromDir($dir, $zip_file) {
$zip = new ZipArchive();
if(true !== $zip->open($zip_file, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)) {
return false;
}
zipDir(
// base dir, note we use a trailing separator from now on
rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR,
// subdir, empty on initial call
null,
// archive ref
$zip
);
return $zip;
}
function zipDir($dir, $subdir, $zip) {
// using real path
$files = scandir($dir.$subdir);
foreach($files as $file) {
if(in_array($file, array('.', '..')))
continue;
// check dir using real path
if(is_dir($dir.$subdir.$file)) {
// create folder using relative path
$zip->addEmptyDir($subdir.$file);
zipDir(
$dir, // remember base dir
$subdir.$file.DIRECTORY_SEPARATOR, // relative path, don't forget separator
$zip // archive
);
}
// file
else {
// get real path, set relative path
$zip->addFile($dir.$subdir.$file, $subdir.$file);
}
}
}
This code has been tested and is working.

Extracting zip file on host by PHP destroys directory structure

I have a directory structure like this :
members/
login.php
register.php
I zip them by PHP ZipArchive in my windows machine, but when I upload it to linux host and extract there by PHP it gives me these as two files with no directory :
members\login.php
members\register.php
I want to have the complete directory structure on the host after unzipping the file.
Note that this unpacking code runs without any problem in my local machine. Is it something about windows and linux or what? How can I resolve it?
PHP does not actually provide a function that extracts a ZIP including its directory structure. I found the following code in a user comment in the manual:
function unzip($zipfile)
{
$zip = zip_open($zipfile);
while ($zip_entry = zip_read($zip)) {
zip_entry_open($zip, $zip_entry);
if (substr(zip_entry_name($zip_entry), -1) == '/') {
$zdir = substr(zip_entry_name($zip_entry), 0, -1);
if (file_exists($zdir)) {
trigger_error('Directory "<b>' . $zdir . '</b>" exists', E_USER_ERROR);
return false;
}
mkdir($zdir);
}
else {
$name = zip_entry_name($zip_entry);
if (file_exists($name)) {
trigger_error('File "<b>' . $name . '</b>" exists', E_USER_ERROR);
return false;
}
$fopen = fopen($name, "w");
fwrite($fopen, zip_entry_read($zip_entry, zip_entry_filesize($zip_entry)), zip_entry_filesize($zip_entry));
}
zip_entry_close($zip_entry);
}
zip_close($zip);
return true;
}
Source here.
try DIRECTORY_SEPARATOR
instead of using:
$path = $someDirectory.'/'.$someFile;
use:
$path = $someDirectory. DIRECTORY_SEPARATOR .$someFile;
Change your code to this:
$zip = new ZipArchive;
if ($zip->open("module. DIRECTORY_SEPARATOR .$file[name]") === TRUE) {
$zip->extractTo('module. DIRECTORY_SEPARATOR');
}
And it will work for both operating systems.
Good luck,
The problem solved! Here's what I did :
I change the code of creating zip file into this function from php.net user comments :
function addFolderToZip($dir, $zipArchive){
if (is_dir($dir)) {
if ($dh = opendir($dir)) {
//Add the directory
$zipArchive->addEmptyDir($dir);
// Loop through all the files
while (($file = readdir($dh)) !== false) {
//If it's a folder, run the function again!
if(!is_file($dir . $file)){
// Skip parent and root directories
if(($file !== ".") && ($file !== "..")){
addFolderToZip($dir . $file . "/", $zipArchive);
}
}else{
// Add the files
$zipArchive->addFile($dir . $file);
}
}
}
}
}
$zip = new ZipArchive;
$zip->open("$modName.zip", ZipArchive::CREATE);
addFolderToZip("$modName/", $zip);
$zip->close();
And in the host I wrote just this code to extract the zipped file :
copy($file["tmp_name"], "module/$file[name]");
$zip = new ZipArchive;
if ($zip->open("module/$file[name]") === TRUE) {
$zip->extractTo('module/');
}
$zip->close();
It created the folder and sub-folders. The only bug left is that it extracts every file in all subfolders in the main folder too, so there is two versions of each file in subfolders.

php recursive zip?

Currently FTP is insanely slow on our server when it comes to multiple small files.
I am wanting to create a file that I can run in my browser that creates a zip file.
Basically this file will sit in /htdocs
The zip file it creates must take all files in /htdocss (and sub folders ect) and mimic the structure.
Can anyone point me in the right direction?
I am attempting to use the below code.
The only issue is that,
I am getting some blank folders
say the file is:
C:/xampp/htdocs/booking/zip.php
I get
/C
/C/xampp
/C/xampp/htdocs
/C/xampp/htdocs/booking
/C/xampp/htdocs/booking/image.png
/C/xampp/htdocs/booking/index.php
I dont want /C/xampp/htdocs/booking
I just want the zip file to contain
image.png
index.php
How can I fix this?
<?php
function Zip($source, $destination) {
if(extension_loaded('zip') === true) {
if(file_exists($source) === true) {
$zip = new ZipArchive();
if($zip->open($destination, ZIPARCHIVE::CREATE) === true) {
$source = realpath($source);
if(is_dir($source) === true) {
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
foreach($files as $file) {
$file = realpath($file);
if(is_dir($file) === true) {
$zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
} else if(is_file($file) === true) {
$zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
}
}
} else if(is_file($source) === true) {
$zip->addFromString(basename($source), file_get_contents($source));
}
}
return $zip->close();
}
}
return false;
}
Zip(dirname(__FILE__), 'testzip.zip');
?>
There are two options. You can use PHPs built-in ZipArchive and manually iterate over the directory contents (readdir or DirectoryIterator).
Or you can do the lazy thing:
header("Content-Type: archive/zip"); // mime 2.0
header("Content-Disposition: attachment; filename=dir.zip");
passthru("zip -q -r - .");
Depends on the installed zip tool. It complains if you try that on the terminal, but should work over the passthru pipe.

Categories