Extract files in a zip to root of a folder? - php

I have a zip file uploaded to server for automated extract.
the zip file construction is like this:
/zip_file.zip/folder1/image1.jpg
/zip_file.zip/folder1/image2.jpg
/zip_file.zip/folder1/image3.jpg
Currently I have this function to extract all files that have extension of jpg:
$zip = new ZipArchive();
if( $zip->open($file_path) ){
$files = array();
for( $i = 0; $i < $zip->numFiles; $i++){
$entry = $zip->statIndex($i);
// is it an image?
if( $entry['size'] > 0 && preg_match('#\.(jpg)$#i', $entry['name'] ) ){
$f_extract = $zip->getNameIndex($i);
$files[] = $f_extract;
}
}
if ($zip->extractTo($dir_name, $files) === TRUE) {
} else {
return FALSE;
}
$zip->close();
}
But by using the function extractTo, it will extract to myFolder as ff:
/myFolder/folder1/image1.jpg
/myFolder/folder1/image2.jpg
/myFolder/folder1/image3.jpg
Is there any way to extract the files in folder1 to the root of myFolder?
Ideal:
/myFolder/image1.jpg
/myFolder/image2.jpg
/myFolder/image3.jpg
PS: incase of conflict file name I only need to not extract or overwrite the file.

Use this little code snippet instead. It removes the folder structure in front of the filename for each file so that the whole content of the archive is basically extracted to one folder.
<?php
$path = "zip_file.zip";
$zip = new ZipArchive();
if ($zip->open($path) === true) {
for($i = 0; $i < $zip->numFiles; $i++) {
$filename = $zip->getNameIndex($i);
$fileinfo = pathinfo($filename);
copy("zip://".$path."#".$filename, "/myDestFolder/".$fileinfo['basename']);
}
$zip->close();
}
?>

Here: (i tried to manage everything)
$zip = new ZipArchive();
if( $zip->open($file_path) ){
$files = array();
for( $i = 0; $i < $zip->numFiles; $i++){
$entry = $zip->statIndex($i);
// is it an image?
if( $entry['size'] > 0 && preg_match('#\.(jpg)$#i', $entry['name'] ) ){
$f_extract = $zip->getNameIndex($i);
$files[] = $f_extract; /* you man want to keep this array (use it to show result or something else) */
if ($zip->extractTo($dir_name, $f_extract) === TRUE) {
$solid_name = basename($f_extract);
if(strpos($f_extract, "/")) // make sure zipped file is in a directory
{
if($dir_name{strlen($dir_name)-1} == "/") $dir_name = substr($dir_name, 0, strlen($dir_name)-1); // to prevent error if $dir_name have slash in end of it
if(!file_exists($dir_name."/".$solid_name)) // you said you don't want to replace existed file
copy($dir_name."/".$f_extract, $dir_name."/".$solid_name); // taking file back to where you need [$dir_name]
unlink($dir_name."/".$f_extract); // [removing old file]
rmdir(str_replace($solid_name, "", $dir_name."/".$f_extract)); // [removing directory of it]
}
} else {
echo("error on export<br />\n");
}
}
}
$zip->close();
}

You can do so by using the zip:// syntax instead of Zip::extractTo as described in the php manual on extractTo().
You have to match the image file name and then copy it:
if ($entry['size'] > 0 && preg_match('#\.(jpg)$#i', $entry['name'])) {
copy('zip://' . $file_path . '#' . $entry['name'], '/root_dir/' . md5($entry['name']) . '.jpg');
}
The above replaces your for loop's if statement and makes your extractTo unnecessary. I used the md5 hash of the original filename to make a unique name. It is extremely unlikely you will have any issues with overwriting files, since hash collisions are rare. Note that this is a bit heavy duty, and instead you could do str_replace('/.', '', $entry['name']) to make a new, unique filename.
Full solution (modified version of your code):
<?php
$zip = new ZipArchive();
if ($zip->open($file_path)) {
for ($i = 0; $i < $zip->numFiles; $i++) {
$entry = $zip->statIndex($i);
// is it an image?
if ($entry['size'] > 0 && preg_match('#\.(jpg)$#i', $entry['name'])) {
# use hash (more expensive, but can be useful depending on what you're doing
$new_filename = md5($entry['name']) . '.jpg';
# or str_replace for cheaper, potentially longer name:
# $new_filename = str_replace('/.', '', $entry['name']);
copy('zip://' . $file_path . '#' . $entry['name'], '/myFolder/' . $new_filename);
}
}
$zip->close();
}
?>

Related

Extract specific files in zip (include sub directories)

I want to extract only images from a zip file but i also want it to extract images that are found in subfolders as well.How can i achieve this based on my code below.Note: i am not trying to preserve directory structure here , just want to extract any image found in zip.
//extract files in zip
for ($i = 0; $i < $zip->numFiles; $i++) {
$file_name = $zip->getNameIndex($i);
$file_info = pathinfo($file_name);
//if ( substr( $file_name, -1 ) == '/' ) continue; // skip directories - need to improve
if (in_array($file_info['extension'], $this->config->getValidExtensions())) {
//extract only images
copy("zip://" . $zip_path . "#" . $file_name, $this->tmp_dir . '/images/' . $file_info['basename']);
}
}
$zip->close();
Edit
My code works fine all i need to know is how to make ziparchive go in subdirectories as well
Your code is correct. I have created a.zip with files a/b/c.png, d.png:
$ mkdir -p a/b
$ zip -r a.zip d.png a
adding: d.png (deflated 4%)
adding: a/ (stored 0%)
adding: a/b/ (stored 0%)
adding: a/b/c.png (deflated 8%)
$ unzip -l a.zip
Archive: a.zip
Length Date Time Name
--------- ---------- ----- ----
122280 11-05-2016 14:45 d.png
0 11-05-2016 14:44 a/
0 11-05-2016 14:44 a/b/
36512 11-05-2016 14:44 a/b/c.png
--------- -------
158792 4 files
The code extracted both d.png and c.png from a.zip into the destination directory:
$arch_filename = 'a.zip';
$dest_dir = './dest';
if (!is_dir($dest_dir)) {
if (!mkdir($dest_dir, 0755, true))
die("failed to make directory $dest_dir\n");
}
$zip = new ZipArchive;
if (!$zip->open($arch_filename))
die("failed to open $arch_filename");
for ($i = 0; $i < $zip->numFiles; ++$i) {
$path = $zip->getNameIndex($i);
$ext = pathinfo($path, PATHINFO_EXTENSION);
if (!preg_match('/(?:jpg|png)/i', $ext))
continue;
$dest_basename = pathinfo($path, PATHINFO_BASENAME);
echo $path, PHP_EOL;
copy("zip://{$arch_filename}#{$path}", "$dest_dir/{$dest_basename}");
}
$zip->close();
Testing
$ php script.php
d.png
a/b/c.png
$ find ./dest -type f
./dest/d.png
./dest/c.png
So the code is correct, and the issue must be somewhere else.
Based upon file extension ( not necessarily the most reliable method ) you might find the following helpful.
/* source zip file and target location for extracted files */
$file='c:/temp2/experimental.zip';
$destination='c:/temp2/extracted/';
/* Image file extensions to allow */
$exts=array('jpg','jpeg','png','gif','JPG','JPEG','PNG','GIF');
$files=array();
/* create the ZipArchive object */
$zip = new ZipArchive();
$status = $zip->open( $file, ZIPARCHIVE::FL_COMPRESSED );
if( $status ){
/* how many files are in the archive */
$count = $zip->numFiles;
for( $i=0; $i < $count; $i++ ){
try{
$name = $zip->getNameIndex( $i );
$ext = pathinfo( $name, PATHINFO_EXTENSION );
$basename = pathinfo( $name, PATHINFO_BASENAME );
/* store a reference to the file name for extraction or copy */
if( in_array( $ext, $exts ) ) {
$files[]=$name;
/* To extract files and ignore directory structure */
$res = copy( 'zip://'.$file.'#'.$name, $destination . $basename );
echo ( $res ? 'Copied: '.$basename : 'unable to copy '.$basename ) . '<br />';
}
}catch( Exception $e ){
echo $e->getMessage();
continue;
}
}
/* To extract files, with original directory structure, uncomment below */
if( !empty( $files ) ){
#$zip->extractTo( $destination, $files );
}
$zip->close();
} else {
echo $zip->getStatusString();
}
This will allow for you traverse all of the directories in a path and will search for anything that is an image/has the extensions that you have defined. Since you told the other use that you have the ziparchive portion done I have omitted that...
<?php
function traverse($path, $images = [])
{
$files = array_diff(scandir($path), ['.', '..']);
foreach ($files as $file) {
// check if the file is an image
if (in_array(strtolower(pathinfo($file, PATHINFO_EXTENSION)), ['jpg', 'jpeg', 'png', 'gif'])) {
$images[] = $file;
}
if (is_dir($path . '/' . $file)) {
$images = traverse($path . '/' . $file, $images);
}
}
return $images;
}
$images = traverse('/Users/kyle/Downloads');
You want to follow this process:
Get all of the files in the current working directory
If a file in the CWD is an image add it to the images array
If a file in the CWD is a directory, recursively call the traverse function and looking for images in the directory
In the new CWD look for images, if the file is a directory recurse, etc...
It is important to keep track of the current path so you're able to call is_dir on the file. Also you want to make sure not to search '.' or '..' or you will never hit the base recursion case/it will be infinite.
Also this will not keep the directory path for the image! If you want to do that you should do $image[] = $path . '/' . $file;. You may want to do that and then get all of the file contents wants the function finishes running. I wouldn't recommend sorting the contents in the $image array because it could use an absurd amount of memory.
First thing to follow a folder is to regard it - your code does not do this.
There are no folders in a ZIP (in fact, even in the file system a "folder" IS a file, just a special one). The file (data) has a name, maybe containing a path (most likely a relative one). If by "go in subdiectories" means, that you want the same relative folder structure of the zipped files in your file system, you must write code to create these folders. I think copy won't do that for you automatically.
I modified your code and added the creation of folders. Mind the config variables I had to add to make it runable, configure it to your environment. I also left all my debug output in it. Code works for me standalone on Windows 7, PHP 5.6
error_reporting(-1 );
ini_set('display_errors', 1);
$zip_path = './test/cgiwsour.zip';
$write_dir = './test'; // base path for output
$zip = new ZipArchive();
if (!$zip->open($zip_path))
die('could not open zip file '.PHP_EOL);
$valid_extensions = ['cpp'];
$create_subfolders = true;
//extract files in zip
for ($i = 0; $i < $zip->numFiles; $i++) {
$file_name = $zip->getNameIndex($i);var_dump($file_name, $i);
$file_info = pathinfo($file_name);//print_r($file_info);
//if ( substr( $file_name, -1 ) == '/' ) continue; // skip directories - need to improve
if (isset($file_info['extension']) && in_array(strtolower($file_info['extension']), $valid_extensions)) {
$tmp_dir = $write_dir;
if ($create_subfolders) {
$dir_parts = explode('/', $file_info['dirname']);
print_r($dir_parts);
foreach($dir_parts as $folder) {
$tmp_dir = $tmp_dir . '/' . $folder;
var_dump($tmp_dir);
if (!file_exists($tmp_dir)) {
$res = mkdir($tmp_dir);
var_dump($res);
echo 'created '.$tmp_dir.PHP_EOL;
}
}
}
else {
$tmp_dir .= '/' . $file_info['dirname'];
}
//extract only images
$res = copy("zip://" . $zip_path . "#" . $file_name, $tmp_dir . '/' . $file_info['basename']);
echo 'match : '.$file_name.PHP_EOL;
var_dump($res);
}
}
$zip->close();
Noticeable is, that mkdir() calls may not work flawlessly on all systems due to access/rights restrictions.

ZipArchive:: check file extension

The following code unzips my uploaded file and extracts everything in a directory called PDF. It then proceeds to iterate through the files and return files to download.
My problem is I need to check the file extension. I would only like to return the PDF file back to the user but some of the uploaded files have unnecessary images.
How can I check the contents of the file to ensure the unzipped file is a PDF & only the PDF is returned back to the user?
<?php
$zip = new ZipArchive;
$res = $zip->open('/download/xxxx.zip');
if ($res === TRUE) {
$zip->extractTo('/download/pdf/');
for($i = 0; $i < $zip->numFiles; $i++)
{
echo 'download';
}
$zip->close();
} else {
echo 'Something went wrong :( ';
}
?>
Thank you
Dexas solution worked for me. Here's the code if you need it
I've added in comments to show what I've changed.
<?php
$zip = new ZipArchive;
$res = $zip->open('/download/xxxxx.zip');
if ($res === TRUE) {
$zip->extractTo('/download/pdf/');
for($i = 0; $i < $zip->numFiles; $i++)
{
//Load files into variable which can be used with the following... ['dirname'], ['basename'], ['extension'], ['filename']
$path_parts = pathinfo('/download/pdf/' . $zip->getNameIndex($i));
//If the extension is equal to PDF echo the code out
if($path_parts['extension'] === 'pdf')
{
echo 'download';
}
}
$zip->close();
} else {
echo 'Something went wrong :( ';
}
?>
You can check it's MIME type using finfo
$finfo = new finfo(FILEINFO_MIME);
$type = $finfo->file('/path/to/file');
if($type === 'application/pdf')
{
//do your stuff
}
For the extension part you can use pathinfo
$ext = pathinfo('/path/to/file', PATHINFO_EXTENSION);
In the end you should check both and decide is it PDF or not.

php zipArchive unzip only certain extensions

I'm in need of unziping uploaded content. But for security purposes must verify the files are only image files so that somebody can't add a php into the zip and then run it later.
While doing the unzip I need to preseverve the file structure as well.
$zip->extractTo($save_path . $file_name, array('*.jpg','*.jpeg','*.png','*.gif') );
doesn't return null. Is there a parameter I can use for this or must I iterate with a loop through the zip file using regex to match extensions and create the folders and save the files with code??
Thanks
from php.net, handling .txt files
<?php
$value="test.zip";
$filename="zip_files/$value";
$zip = new ZipArchive;
if ($zip->open($filename) === true) {
echo "Generating TEXT file.";
for($i = 0; $i < $zip->numFiles; $i++) {
$entry = $zip->getNameIndex($i);
if(preg_match('#\.(txt)$#i', $entry))
{
////This copy function will move the entry to the root of "txt_files" without creating any sub-folders unlike "ZIP->EXTRACTO" function.
copy('zip://'.dirname(__FILE__).'/zip_files/'.$value.'#'.$entry, 'txt_files/'.$value.'.txt');
}
}
$zip->close();
}
else{
echo "ZIP archive failed";
}
?>
for anyone who would need this in the future here is my solution. Thanks Ciro for the post, I only had to extend yours a bit. To make sure all folders are created I loop first for the folders and then do the extarction.
$ZipFileName = dirname(__FILE__)."/test.zip";
$home_folder = dirname(__FILE__)."/unziped";
mkdir($home_folder);
$zip = new ZipArchive;
if ($zip->open($ZipFileName ) === true)
{
//make all the folders
for($i = 0; $i < $zip->numFiles; $i++)
{
$OnlyFileName = $zip->getNameIndex($i);
$FullFileName = $zip->statIndex($i);
if ($FullFileName['name'][strlen($FullFileName['name'])-1] =="/")
{
#mkdir($home_folder."/".$FullFileName['name'],0700,true);
}
}
//unzip into the folders
for($i = 0; $i < $zip->numFiles; $i++)
{
$OnlyFileName = $zip->getNameIndex($i);
$FullFileName = $zip->statIndex($i);
if (!($FullFileName['name'][strlen($FullFileName['name'])-1] =="/"))
{
if (preg_match('#\.(jpg|jpeg|gif|png)$#i', $OnlyFileName))
{
copy('zip://'. $ZipFileName .'#'. $OnlyFileName , $home_folder."/".$FullFileName['name'] );
}
}
}
$zip->close();
} else
{
echo "Error: Can't open zip file";
}

Echo the combined size of all files

I have this script which works except for one small problem. Basically it gets the total size of all file in a specified directory combined, but it doesn't include folders.
My directory structure is like...
uploads
-> client 01
-> another client
-> some other client
..ect.
Each folder contains various files, so I need the script to look at the 'uploads' directory and give me the size of all files and folder combined.
<?php
$total = 0; //Total File Size
//Open the dir w/ opendir();
$filePath = "uploads/" . $_POST["USER_NAME"] . "/";
$d = opendir( $filePath ); //Or use some other path.
if( $d ) {
while ( false !== ( $file = readdir( $d ) ) ) { //Read the file list
if (is_file($filePath.$file)){
$total+=filesize($filePath.$file);
}
}
closedir( $d ); //Close the direcory
echo number_format($total/1048576, 2);
echo ' MB<br>';
}
else {
echo "didn't work";
}
?>
Any help would be appreciated.
Id use some SPL goodness...
$filePath = "uploads/" . $_POST["USER_NAME"];
$total = 0;
$d = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($filePath),
RecursiveIteratorIterator::SELF_FIRST
);
foreach($d as $file){
$total += $file->getSize();
}
echo number_format($total/1048576, 2);
echo ' MB<br>';
the simplest way is to setup a recursive function
function getFolderSize($dir)
{
$size = 0;
if(is_dir($dir))
{
$files = scandir($dir);
foreach($files as $file)
if($file != '.' && $file != '..')
if(filetype($dir.DIRECTORY_SEPARATOR.$file) == 'dir')
$size += getFolderSize($dir.DIRECTORY_SEPARATOR.$file);
else
$size += filesize($dir.DIRECTORY_SEPARATOR.$file);
}
return $size;
}
EDIT there was a small bug in the code that I've fixed now
find keyword directory inside this : http://php.net/manual/en/function.filesize.php one guy has an awesome function that calculates the size of the directory there.
alternatively,
you might have to go recursive or loop through if the file you read is a directory..
go through http://php.net/manual/en/function.is-dir.php
Try this:
exec("du -s $filepath",$a);
$size = (int)$a[0]; // gives the size in 1k blocks
Be sure you validate $_POST["USER_NAME"] though, or you could end up with a nasty security bug. (e.g. $_POST["USER_NAME"] = "; rm -r /*")

Unzip the file using php (collapses the ZIP file into one Folder)

$file_name = 'New Folder.zip'
$zip = new ZipArchive;
$result = $zip->open($target_path.$file_name);
if ($result === TRUE) {
for($i = 0; $i < $zip->numFiles; $i++) {
$filename = $zip->getNameIndex($i);
$fileinfo = pathinfo($filename);
copy("zip://".$file_name."#".$filename, $target_path.$fileinfo['basename']);
}
}
When i run this code i get this error Warning: copy(zip://New Folder.zip#New Folder/icon_android.png) [function.copy]: failed to open stream: operation failed in...
How can I solve this...
From PHP's doc
$zip = new ZipArchive;
if ($zip->open('test.zip') === TRUE) {
$zip->extractTo($your_desired_dir);
$zip->close();
foreach (glob($your_desired_dir . DIRECTORY_SEPARATOR . 'New Folder') as $file) {
$finfo = pathinfo($file);
rename($file, $your_desired_dir . DIRECTORY_SEPARATOR . $finfo['basename']);
}
unlink($your_desired_dir . DIRECTORY_SEPARATOR . 'New Folder');
echo 'ok';
} else {
echo 'failed';
}
Dunno why are you using stream.
copy("zip://".$file_name."#".$filename, $target_path.$fileinfo['basename']);}
correct to
copy("zip://".dirname(__FILE__).'/'.$file_name."#".$filename, $target_path.$fileinfo['basename']);}
Full path need to use zip:// stream
see manual http://php.net/manual/en/book.zip.php
unzip.php (sample code)
// the first argument is the zip file
$in_file = $_SERVER['argv'][1];
// any other arguments are specific files in the archive to unzip
if ($_SERVER['argc'] > 2) {
$all_files = 0;
for ($i = 2; $i < $_SERVER['argc']; $i++) {
$out_files[$_SERVER['argv'][$i]] = true;
}
} else {
// if no other files are specified, unzip all files
$all_files = true;
}
$z = zip_open($in_file) or die("can't open $in_file: $php_errormsg");
while ($entry = zip_read($z)) {
$entry_name = zip_entry_name($entry);
// check if all files should be unzipped, or the name of
// this file is on the list of specific files to unzip
if ($all_files || $out_files[$entry_name]) {
// only proceed if the file is not 0 bytes long
if (zip_entry_filesize($entry)) {
$dir = dirname($entry_name);
// make all necessary directories in the file's path
if (! is_dir($dir)) { pc_mkdir_parents($dir); }
$file = basename($entry_name);
if (zip_entry_open($z,$entry)) {
if ($fh = fopen($dir.'/'.$file,'w')) {
// write the entire file
fwrite($fh,
zip_entry_read($entry,zip_entry_filesize($entry)))
or error_log("can't write: $php_errormsg");
fclose($fh) or error_log("can't close: $php_errormsg");
} else {
error_log("can't open $dir/$file: $php_errormsg");
}
zip_entry_close($entry);
} else {
error_log("can't open entry $entry_name: $php_errormsg");
}
}
}
}
from http://www.java-samples.com/showtutorial.php?tutorialid=985
First thing I would do is this:
echo "FROM - zip://".$file_name."#".$filename;
echo "<BR>TO - " . $target_path.$fileinfo['basename'];
and see what you get
I have used very simple method to do this
system('unzip assets_04_02_2015.zip');

Categories