ZipArchive: `failed to open stream: Is a directory` - php

I keep getting a failed to open stream: Is a directory error while trying to unzip a file with PHP.
This is usually an issue with the destination path.
However, in my case, the destination is fine: I can unzip different files using the same code, or unzip the same file without PHP.
The ZipArchive::extractTo documentation did not help. Nor did other SO threads on this error message.
FYI, this is a Q&A post.
Here is the source code as requested in a comment. I shared the Dropbox link publicly, seeing as this matter is dependent on the format of the ZIP input (/ path in ZIP file, see answer.)
/** Config **/
$url = 'https://www.dropbox.com/sh/nx5792q9syppaoo/AAAJZBWlGNAd_EkmQaFvEwe0a?dl=1';
/** Where to save **/
$zipped = ABSPATH . 'tmp/updates/dialogues.zip'; /* Keep in mind this is public-facing. */
$outputdirectory = ABSPATH . 'tmp/updates/testing'; /* The trailing slash is optinal. */
/** Download the dialogues **/
echo 'Fetching the dialogues...<br>';
if ( ( $dropboxfile = fopen( $url, 'r' ) ) === FALSE ) { /* == has higher precedent over = */
echo 'FATAL ERROR: could not open Dropbox file.';
die();
} else if ( file_put_contents( $zipped, $dropboxfile ) === FALSE ) {
echo 'FATAL ERROR: could not copy Dropbox file.';
die();
}
echo "Dialogues downloaded as zip OK. (See {$zipped})<br>";
/** Unzip the dialogues **/
print 'Unzipping...<br>';
$zip = new ZipArchive;
$res = $zip->open( $zipped );
if ( $res === TRUE ) {
// Does not work
// $zip->extractTo( ABSPATH . 'tmp/updates/testing' );
// Works
for( $i = 0 ; $i < $zip->numFiles ; $i++ ) {
if ( $zip->getNameIndex( $i ) != '/' && $zip->getNameIndex( $i ) != '__MACOSX/_' ) {
print $zip->getNameIndex( $i ) . '<br>';
$zip->extractTo( $outputdirectory, array($zip->getNameIndex($i)) );
}
}
$zip->close();
print 'Done unzipping.<br>';
} else {
print 'FATAL ERROR: unzipping failed.';
}

As it turned out, I had a / "folder" inside the ZIP archive.
This is confirmed by browsing the ZIP archive through the command-line. e.g., on OS X, using unzip -vl dialogues.zip gave me this:
Archive: dialogues.zip
Length Method Size Ratio Date Time CRC-32 Name
-------- ------ ------- ----- ---- ---- ------ ----
0 Defl:N 2 0% 08-22-15 12:07 00000000 /
2154 Defl:N 1064 51% 06-08-15 15:18 602c6793 dialogue001-en.txt
...
4379 Defl:N 1793 59% 08-21-15 13:56 75e1ef52 dialogue106-en.txt
70 Defl:N 39 44% 08-22-15 12:07 985af458 __MACOSX/_
-------- ------- --- -------
775512 343864 56% 312 files
The file is downloaded from Dropbox and I'd rather be able to work with it as it is.
So I just ended up filtering the list of files contained inside the ZIP archive, before deciding to unzip any given file:
print 'Unzipping...<br>';
$zip = new ZipArchive;
$res = $zip->open( $zipped );
if ( $res === TRUE ) {
//
for( $i = 0 ; $i < $zip->numFiles ; $i++ ) {
if ( $zip->getNameIndex( $i ) != '/' && $zip->getNameIndex( $i ) != '__MACOSX/_' ) {
print $zip->getNameIndex( $i ) . '<br>';
$zip->extractTo( $tempdirectory, array($zip->getNameIndex($i)) );
}
}
$zip->close();
print 'Done unzipping.<br>';
} else {
print 'FATAL ERROR: unzipping failed.';
}
Did not see this documented anywhere and wasted close to an hour on this. I hope this will help someone.
I'm still not clear as to whether the / reference inside of the ZIP file is correct. (Comments welcome.)

Related

How do i update a CSV file with data from external CSV file link using PHP?

I need help with my PHP code.
We have an e-commerce site made with wordpress and woocommerce.
The site needs to import products from another site's CSV-file through a datafeed.
The datafeed setup requires a PHP-file in the website's file system
and a Cron Job that executes the PHP-file.
The PHP-file takes the data from
5dkmiglm.csv (cdn.sandberg.world)
and appends it to
pricelist_206876.csv (in our system)
These two CSV files differ in number of columns.
5dkmiglm.csv has these columns that should be imported to our pricelist:
Partno
Name
Weight
Width
Height
SE:retailPriceIncVat
sv:CategoryName
The PHP-code looks like this:
`<?php
$current = file_get_contents("https://cdn.sandberg.world/feeds/5dkmiglm.csv");
$pricefile = "/pricelist_206876.csv";
file_put_contents($pricefile,$current);
?>`
I placed the PHP-file in the same folder as the pricelist that needs to be updated
but nothing happens. pricelist_206876 is not updated as it should be.
The Cron Job is not in the wp-admin panel, but in the control panel (DirectAdmin) of our web host Inleed.
The Cron Job command looks like this:
/usr/bin/php -q /dev/null "/home/goffero/domains/goffero.com/datafeedcode.php"
I tried modifying the PHP code in various ways with no result.
realpath() didn't work.
I changed the write/read/execute system permissions for the files but that didn't solve the problem.
I have a project in Magento and the import of the products is done the same way you want.
We take a csv with 10 or more columns but we export only qty and SKU in this case.
Here is what we have done:
<?php
// downloaded file path
$filesRoot = BP . '/pub/path/to/your/folder/';
// If folder doesn't exist, create it
if ( ! file_exists( $filesRoot ) ) {
mkdir( $filesRoot, 0777, true );
}
// their file
$fileIn = $filesRoot . '5dkmiglm.csv';
// your file
$fileOut = $filesRoot . 'pricelist_206876.csv';
$url = 'https://cdn.sandberg.world/feeds/5dkmiglm.csv';
// Download their csv in your environment
if ( ! file_put_contents( $fileIn, file_get_contents( $url ) ) ) {
die( 'error download file' );
}
$countLine = 0;
$fileInOpn = fopen( $fileIn, 'r' );
$fileOutOpn = fopen( $fileOut, 'w' );
while ( ( $line = fgetcsv( $fileInOpn ) ) !== false ) {
// position of each column
if ( $countLine == 0 ) {
$keyQty = array_search( 'qty', $line );
$keySku = array_search( 'sku', $line );
}
$newLineRev = array();
foreach ( $line as $key => $el ) {
if ( $key == $keyQty || $key == $keySku ) {
$newLineRev[ $keyQty ] = $line[ $keyQty ];
$newLineRev[ $keySku ] = $line[ $keySku ];
continue;
}
}
// Write in your file
fputcsv( $fileOutOpn, $newLineRev );
$countLine++;
}
fclose( $fileInOpn );
fclose( $fileOutOpn );

PHP | Get file that is most similar to string

Currently I have a zip folder with files in it that I do not know the filenames of. The only thing I know is that one filename is very similar to a string a have. It is literally one character off.
What I am trying to do right now is to extract only the file that is the most similar to the string I have. To extract only one file from a zip I use the following code that works:
$zip = new ZipArchive;
if ($zip->open('directory/to/zipfile') === TRUE)
{
$zip->extractTo('directory/where/to/extract', array('the/filename/that/is/most/similair/most/go/here'));
$zip->close();
echo 'ok';
}
else
{
echo 'failed';
}
I know that to check the similarity of strings I can use the following code:
$var_1 = 'PHP IS GREAT';
$var_2 = 'WITH MYSQL';
similar_text($var_1, $var_2, $percent);
And based on the percentage I can tell which file is most similar to the string I have. The only thing I am worried about is that ZipArchieve doesn't have a function to retrieve files from a zip without knowing the exact filename.
So I was wondering if there is a way to retrieve a single file from a zip based on a string that is most similar to the filename.
This comment in the docs mentions how to list the files in a zip archive, so, all you would have to do is loop through all the file names and find the one that closest matches the string you have and then extract it.
$search = 'Closefilename.doc';
$za = new ZipArchive();
$za->open('theZip.zip');
$similarity = 0;
for( $i = 0; $i < $za->numFiles; $i++ ){
$stat = $za->statIndex( $i );
similar_text($stat['name'], $search, $sim);
if ($sim > $similarity) {
$similarity = $sim;
$filename = $stat['name'];
}
}
// Now extract $filename;
Try this code:
// Your Zip File path
$zip = zip_open( $fileName );
if ( is_resource( $zip ) ) {
while( $zip_entry = zip_read( $zip ) ) {
$zip_entry_string = zip_entry_read ( $zip_entry );
// Compare here with similar_text
// If success you can write this string to file
}
}
zip_close( $zip );
?>

What is wrong with this PHP file rename code?

The following code is meant to iterate through a directory of images and rename them.
I had it working well, but what I would like to add is a 'static' token at the beginning of the file that once renamed, it will then in future ignore.
For example, let's say that we have a directory of 100 files.
The first 20 of which go by the name "image-JTzkT1RYWnCqd3m1VXYcmfZ2nhMOCCunucvRhuaR5.jpg"
The last 80 go by the name "FejVQ881qPO5t92KmItkNYpny.jpg" where this could be absolutely anything.
I would like to ignore the files that have already been renamed (denoted by the 'image-" at the beginning of the file name)
How can I do this?
<?php
function crypto_rand_secure($min, $max) {
$range = $max - $min;
if ($range < 0) return $min; // not so random...
$log = log($range, 2);
$bytes = (int) ($log / 8) + 1; // length in bytes
$bits = (int) $log + 1; // length in bits
$filter = (int) (1 << $bits) - 1; // set all lower bits to 1
do {
$rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
$rnd = $rnd & $filter; // discard irrelevant bits
} while ($rnd >= $range);
return $min + $rnd;
}
function getToken($length){
$token = "";
$codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$codeAlphabet.= "abcdefghijklmnopqrstuvwxyz";
$codeAlphabet.= "0123456789";
for($i=0;$i<$length;$i++){
$token .= $codeAlphabet[crypto_rand_secure(0,strlen($codeAlphabet))];
}
return $token;
}
$dir = "/path/to/images";
if ( $handle = opendir ( $dir)) {
echo "Directory Handle = $handles<br />" ;
echo "Files: <br />" ;
while ( false !== ($file = readdir($handle))) {
if ( $file != "." && $file != ".." ) {
$isdir = is_dir ( $file ) ;
if ( $isdir == "1" ) {} // if its a directory do nothing
else {
$file_array[] = "$file" ; // get all the file names and put them in an array
//echo "$file<br />" ;
} // closes else
} // closes directory check
} // closes while
} // closes opendir
//Lets go through the array
$arr_count = count( $file_array ) ; // find out how many files we have found so we can initiliase the counter
for ( $counter=1; $counter<$arr_count; $counter++ ) {
echo "Array = $file_array[$counter] - " ; // tell me how many files there are
//$new = str_replace ( "C", "CIMG", $file_array[$counter] ) ; // now create the new file name
$new =getToken(50);
//if (substr($file_array[$counter]), 0, 3) == "gallery_image")
//{
//}
//else
//{
$ren = rename ( "$dir/$file_array[$counter]" , "image-$dir/$new.jpg" ) ; // now do the actual file rename
echo "$new<br />" ; // print out the new file name
//}
}
closedir ( $handle ) ;
?>
It seems like you're doing a lot of unnecessary work for something that should be quite trivial.
As far as I understand you want to get a list of the files from a directory and rename all of the ones not already pre-pended with image-.
You can do that with the following piece of code:
$dir = "/path/to/dir/"; //Trailing slash is necessary or you would have to change $dir.$newname to $dir.'/'.$newname further down the code
if(is_dir($dir)){
$dirHandle = opendir($dir);
while($file = readdir($dirHandle)){
if(is_file($dir.$file) === TRUE){
if(strpos($file, 'image-') === 0)
continue; //Skip if the image- is apready prepended
while(TRUE){
$cryptname = md5(mt_rand(1,10000).$file.mt_rand(1,10000)); //Create random string
$newname = 'image-'.$cryptname.'.jpg'; //Compile new file name
if(is_file($dir.$newname) === FALSE) //Check file name doesn't exist
break; //Exit loop if it doesn't
}
rename($dir.$file, $dir.$newname); //Rename file with new name
}
}
}
If you want to use your own function then you can the change the line:
$cryptname = md5(mt_rand(1,10000).$file.mt_rand(1,10000));
To use your function instead of md5. Like:
$cryptname = getToken(50);
I suggest that you should also check that the file name doesn't already exist otherwise - unlikely as it may be - you run the risk of overwriting files.
/images/ vs ./images/
Firstly, when dealing with the web you have to understand that there are effectively two root directories.
The web root and the document root. Take the example url http://www.mysite.com/images/myimage.jpg as far as the web root is concerned the path name is /images/myimage.jpg however, from php you have yo use the document root which would actually be something like: /home/mysite/pubic_html/images/myimage.jpg
So when you type / in php it thinks that you mean the directory which home is located in.
./images/ works because the ./ means this directory i.e. the one that the script/php is in.
In your case you probably have a file structure like:
>/
>home
>public_html
>images
image-243.jpg
image-243.jpg
index.php
So because index.php is in the same folder as images: ./images/ is equal to /home/public_html/images/. / on the other hand means the parent directory of home which has no images folder let alone the one you're targeting.
If you're more used to windows think of it like this: in php / means the document root directory (on windows that would be something like C:\).
You have the 'image-' in front of the directory portion of your file name:
$ren = rename ( "$dir/$file_array[$counter]" , "image-$dir/$new.jpg" )
should be
$ren = rename ( "$dir/$file_array[$counter]" , "$dir/image-$new.jpg" )

php - extract files from folder in a zip

I have a zip file containing one folder, that contains more folders and files, like this:
myfile.zip
-firstlevel
--folder1
--folder2
--folder3
--file1
--file2
Now, I want to extract this file using PHPs ZipArchive, but without the "firstlevel" folder. At the moment, the results look like this:
destination/firstlevel/folder1
destination/firstlevel/folder2
...
The result I'd like to have would look like this:
destination/folder1
destination/folder2
...
I've tried extractTo, which produces the first mentioned result, and copy(), as suggested here, but this doesn't seem to work at all.
My current code is here:
if($zip->open('myfile.zip') === true) {
$firstlevel = $zip->getNameIndex(0);
for($i = 0; $i < $zip->numFiles; $i++) {
$entry = $zip->getNameIndex($i);
$pos = strpos($entry, $firstlevel);
if ($pos !== false) {
$file = substr($entry, strlen($firstlevel));
if(strlen($file) > 0){
$files[] = $file;
}
}
}
//attempt 1 (extractTo):
//$zip->extractTo('./test', $files);
//attempt 2 (copy):
foreach($files as $filename){
copy('zip://'.$firstlevel.'/'.$filename, 'test/'.$filename);
}
}
How can I achieve the result I'm aiming for?
Take a look at my Quick Unzipper script. I wrote this for personal use a while back when uploading large zip files to a server. It was a backup, and 1,000s of files take forever with FTP so using a zip file was faster. I use Git and everything, but there wasn't another option for me. I place this php file in the directory I want the files to go, and put the zip file in the same directory. For my script, they all have to operate in the same directory. It was an easy way to secure it for my needs, as everything I needed was in the same dir.
Quick Unzipper: https://github.com/incomepitbull/QuickUnzipper/blob/master/unzip.php
I linked the file because I am not showcasing the repo, just the code that makes the unzip tick. With modern versions of PHP, there should't be anything that isn't included on your setup. So you shouldn't need to do any server config changes to use this.
Here is the PHP Doc for the ZipArchive class it uses: http://php.net/manual/en/class.ziparchive.php
There isn't any included way to do what you want, which is a shame. So I would unzip the file to a temp directory, then use another function to copy the contents to where you want. So when using ZipArchive, you will need to return the first item to get the folder name if it is unknown. If the folder is known, ie: the same pesky folder name every time, then you could hard code the name.
I have made it return the first item from the index. So if you ALWAYS have a zip with 1 folder inside it, and everything in that folder, this would work. However, if you have a zip file without everything consolidated inside 1 folder, it would fail. The code I have added will take care of your question. You will need to add further logic to handle alternate cases.
Also, You will still be left with the old directory from when we extract it to the temp directory for "processing". So I included code to delete it too.
NOTE: The code uses a lot of if's to show the processing steps, and print a message for testing purposes. You would need to modify it to your needs.
<?php
public function copyDirectoryContents($source, $destination, $create=false)
{
if ( ! is_dir($source) ) {
return false;
}
if ( ! is_dir($destination) && $create === true ) {
#mkdir($destination);
}
if ( is_dir($destination) ) {
$files = array_diff(scandir($source), array('.','..'));
foreach ($files as $file)
{
if ( is_dir($file) ) {
copyDirectoryContents("$source/$file", "$destination/$file");
} else {
#copy("$source/$file", "$destination/$file");
}
}
return true;
}
return false;
}
public function removeDirectory($directory, $options=array())
{
if(!isset($options['traverseSymlinks']))
$options['traverseSymlinks']=false;
$files = array_diff(scandir($directory), array('.','..'));
foreach ($files as $file)
{
if (is_dir("$directory/$file"))
{
if(!$options['traverseSymlinks'] && is_link(rtrim($file,DIRECTORY_SEPARATOR))) {
unlink("$directory/$file");
} else {
removeDirectory("$directory/$file",$options);
}
} else {
unlink("$directory/$file");
}
}
return rmdir($directory);
}
$file = dirname(__FILE__) . '/file.zip'; // full path to zip file needing extracted
$temp = dirname(__FILE__) . '/zip-temp'; // full path to temp dir to process extractions
$path = dirname(__FILE__) . '/extracted'; // full path to final destination to put the files (not the folder)
$firstDir = null; // holds the name of the first directory
$zip = new ZipArchive;
$res = $zip->open($file);
if ($res === TRUE) {
$firstDir = $zip->getNameIndex(0);
$zip->extractTo($temp);
$zip->close();
$status = "<strong>Success:</strong> '$file' extracted to '$temp'.";
} else {
$status = "<strong>Error:</strong> Could not extract '$file'.";
}
echo $status . '<br />';
if ( empty($firstDir) ) {
echo 'Error: first directory was empty!';
} else {
$firstDir = realpath($temp . '/' . $firstDir);
echo "First Directory: $firstDir <br />";
if ( is_dir($firstDir) ) {
if ( copyDirectoryContents($firstDir, $path) ) {
echo 'Directory contents copied!<br />';
if ( removeDirectory($directory) ) {
echo 'Temp directory deleted!<br />';
echo 'Done!<br />';
} else {
echo 'Error deleting temp directory!<br />';
}
} else {
echo 'Error copying directory contents!<br />';
}
} else {
echo 'Error: Could not find first directory';
}
}

Fatal error: Class 'ZipArchive' not found (PHP 5.3.1)

When the function below is run, I'm getting...Fatal error: Class 'ZipArchive' not found in /home/test/dummyurl.com/wp-content/themes/mytheme/upload-zip.php on line 14
PHP Version is 5.3.1
function openZip($file_to_open) {
global $target;
$zip = new ZipArchive(); //This is line 14
$x = $zip->open($file_to_open);
if($x === true) {
$zip->extractTo($target);
$zip->close();
unlink($file_to_open);
} else {
die("There was a problem. Please try again!");
}
}
This is in a wordpress application, and I think I might perhaps be able to use a built-in function (see below) instead, but still not sure why ZipArchive class is missing above...
/**
* Unzip's a specified ZIP file to a location on the Filesystem via the WordPress Filesystem Abstraction.
* Assumes that WP_Filesystem() has already been called and set up. Does not extract a root-level __MACOSX directory, if present.
*
* Attempts to increase the PHP Memory limit to 256M before uncompressing,
* However, The most memory required shouldn't be much larger than the Archive itself.
*
* #since 2.5.0
*
* #param string $file Full path and filename of zip archive
* #param string $to Full path on the filesystem to extract archive to
* #return mixed WP_Error on failure, True on success
*/
function unzip_file($file, $to) {
global $wp_filesystem;
if ( ! $wp_filesystem || !is_object($wp_filesystem) )
return new WP_Error('fs_unavailable', __('Could not access filesystem.'));
// Unzip can use a lot of memory, but not this much hopefully
#ini_set('memory_limit', '256M');
$needed_dirs = array();
$to = trailingslashit($to);
// Determine any parent dir's needed (of the upgrade directory)
if ( ! $wp_filesystem->is_dir($to) ) { //Only do parents if no children exist
$path = preg_split('![/\\\]!', untrailingslashit($to));
for ( $i = count($path); $i >= 0; $i-- ) {
if ( empty($path[$i]) )
continue;
$dir = implode('/', array_slice($path, 0, $i+1) );
if ( preg_match('!^[a-z]:$!i', $dir) ) // Skip it if it looks like a Windows Drive letter.
continue;
if ( ! $wp_filesystem->is_dir($dir) )
$needed_dirs[] = $dir;
else
break; // A folder exists, therefor, we dont need the check the levels below this
}
}
if ( class_exists('ZipArchive') && apply_filters('unzip_file_use_ziparchive', true ) ) {
$result = _unzip_file_ziparchive($file, $to, $needed_dirs);
if ( true === $result ) {
return $result;
} elseif ( is_wp_error($result) ) {
if ( 'incompatible_archive' != $result->get_error_code() )
return $result;
}
}
// Fall through to PclZip if ZipArchive is not available, or encountered an error opening the file.
return _unzip_file_pclzip($file, $to, $needed_dirs);
}
To enable ZipArchive, you need to compile PHP with the --enable-zip option, as stated in the documentation.
Alternatively, you could install the zip PECL package.
You have to enable the php extention
1) php_zip
2) php_zlib_filters

Categories