php recursive zip? - php

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.

Related

When I create a zip file of a directory with a large zip file in PHP, I am facing a memory limit

I am facing a memory limit error in my WordPress plugin code.
My Code:
function zipData($source, $destination) {
if (extension_loaded('zip')) {
if (file_exists($source)) {
$zip = new ZipArchive();
if ($zip -> open($destination, ZipArchive::CREATE) === TRUE) {
$source = realpath($source);
if (is_dir($source)) {
$iterator = new RecursiveDirectoryIterator($source);
$iterator->setFlags(RecursiveDirectoryIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST);
foreach ($files as $file) {
$file = realpath($file);
if (is_dir($file)) {
$zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
} else if (is_file($file)) {
$zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
}
}
} else if (is_file($source)) {
$zip->addFromString(basename($source), file_get_contents($source));
}
}
return $zip->close();
}
}
return false;
}
ini_set('max_execution_time', 600);
ini_set('memory_limit','1024M');
zipData($upload_dir['basedir'], $upload_dir['basedir'].'/Jincowboy/JinBackupMedia'. time() .'.zip');
I encountered a memory limit so I checked Google.
I found that stream zip file generation is needed for avoiding the memory limit, but I couldn't confirm about this.
I want my code to bypass this memory limit and allow creation of a zip file based on a directory with large files.
How can I fix this?
You can try https://github.com/Grandt/PHPZip which supports creating ZIP files with streaming data (lowering memory usage by a lot).
And there are plenty of stackoverflow posts about zipping with streams with a pletora of answers.

Codeigniter Zip directory and download

I am having a very strange problem when zipping a directory and try to download using codeigniter.
Here is the code
$this->load->library('zip'); //Loading the zip library
$directory = $_SESSION['directory-download-path']; //Getting the directory from session
$name = basename($directory); //get the name of the folder
$name = str_replace(" ", "_", $name).".zip"; //create the zip name
unset($_SESSION['directory-download-path']); //removing it from session
$this->zip->read_dir($directory); //read the directory
$this->zip->download($name); //download the zip
It is very simple. The problem occurs with the download. I get the zip file but when i extract it i get a file with .zip.cpgz and continues to extract similar files. Thus i think it is corrupted. Can you please help me why is this occurring. I have permissions and everything on the directory because i am doing other operations.
EDIT:
I found after some more research to add the second parameter as follows:
$this->zip->read_dir($directory, false); //read the directory
But still not working.
Another solution was to add
ob_end_clean();
just before the line: $this->zip->download($name); //download the zip
but still no success!
Since I had no response i made a function outside of codeigniter that zips files and folder recursively which is below:
public 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);
if( in_array(substr($file, strrpos($file, '/')+1), array('.', '..', "Thumbs.db")) )
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();
}
After this in the codeigniter controller i made this:
$this->projects->Zip($source, $destination);
header("Content-type: application/zip");
header("Content-Disposition: attachment; filename=$name");
header("Content-length: " . filesize($destination));
header("Pragma: no-cache");
header("Expires: 0");
readfile($destination);
unlink($destination);
Of course the $destination should be to a temp folder where you have permissions 777 or equivalent so that when the file is sent to the client it will be deleted.
Hope this helps!

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.

ZipArchive can't unzip files that ZipArchive zips in 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.

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.

Categories