4

This maybe a silly question to most, but I have the following folder, which I want to zip.

FolderI
   Folder1
   Folder2
   Folder3
   ..
   ..
   FolderN

Now, zipping up FolderI into a zipFile.zip is pretty easy because of so many resources online, specially here! But, I want to create zipFile.zip, containing Folder1...FolderN, such that when the file is unzipped, it would directly populate the current directory with Folder1...FolderN,instead of creating FolderI, which has Folder1...FolderN in it.

Some of the things I've been trying are:

$zip = new ZipArchive();

// open archive 
if ($zip->open('my-archive.zip', ZIPARCHIVE::CREATE) !== TRUE) {
    die ("Could not open archive");
}

// initialize an iterator
// pass it the directory to be processed
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator("FolderI/"));

// iterate over the directory
// add each file found to the archive
foreach ($iterator as $key=>$value) {
    $zip->addFile(realpath($key), $key) or die ("ERROR: Could not add file: $key");
}

// close and save archive
$zip->close();

This was picked up from compress/archive folder using php script and I tried to change it around. Didn't work though - The FolderI is still zipped. But, when I unzip the my-archive.zip, I get a FolderI and then Folder1..FolderN inside it. I also saw code from @Alix somewhere here, which did the same thing. But, didn't really follow much.

Any kind of help would be great. If not the code, at least point me to the right direction.

Thanks

****Anyone here knows how to do this? ****

@Mods and Admins: Is the solution too simple or PHP can't do this at all?

Community
  • 1
  • 1
Bruner
  • 43
  • 1
  • 8
  • 1
    Good question. Hope you'll get answers. If you didn't in a couple of days, remind me and I may put a bounty on it. – deceze Mar 15 '13 at 19:08
  • Thanks, @deceze! I'm pretty sure that this is tricky. Been breaking my head all over google since yesterday. Thanks for the helpful comment though! :) – Bruner Mar 15 '13 at 19:15
  • could you add a description of what it currently does (explain the "*didn't work though*"? Like, does your current code still zip `FolderI`? – UnholyRanger Mar 15 '13 at 19:27
  • @UnholyRanger: Edited! Hope that makes it more clear. :) – Bruner Mar 15 '13 at 19:38
  • 1
    Your code and description will do two very different things. The code puts all of the files inside the zip without any sort of directory structure (due to using basename). Your description says the zip contains folders: your code *cannot* be doing that. P.S. To get the relative path of the current file without the path to the base directory being iterated, use the RecursiveDirectoryIterator's [`getSubPathname()`](http://php.net/recursivedirectoryiterator.getsubpathname) method. – salathe Mar 15 '13 at 20:21
  • @salathe: Sorry about that. I put in one of the codes I was trying. I've updated that now. Can you please tell me how and where I should use the `getSubpathName()`? There's no documentation online and the `php.net` doesn't document it all that well either. thanks.. – Bruner Mar 18 '13 at 15:02
  • What software are you using to extract the zipped files? Some offer a choice of extracting to a main folder (usually named after the zip file), some always extract to a folder, some don't. If you examine the contents of the zip (without extracting) is the extra folder there? – salathe Mar 18 '13 at 16:45
  • @salathe: I'm on a Mac 10.7.4 and I am just using the default software called `Archive Utility` to unzip the `my-archive.zip` I get. Unholy Ranger posted the code using `getSubPathName()`, but that does the same thing as my present code. Just to be clear `I do not want the FolderI when I unzip the file. I directly want my subfolders in the same folder as my zip file`. I'm not sure what you mean by viewing the contents of zip. Can that be done on Mac directly? – Bruner Mar 18 '13 at 18:16
  • Further to UnholyRanger's comments below, using Archive Utility will create a folder (of the same name as the zip file) when the zip file contains *more than one item* at the top level. If there is only one file or folder at the top level of the zip, then that file or folder gets extracted to the directory containing the zip file. More files or folders, at the top level, and a folder is created to contain them. At least, that's how it is for me. – salathe Mar 18 '13 at 19:40

2 Answers2

2

The problem that is occuring here is that the $key in the iterator is the full path (since the RecursiveDirectoryIterator::KEY_AS_PATHNAME flag is used by default), which includes the relative part specified in the RecursiveDirectoryIterator constructor.

So for example we have the following hierarchy:

folderI
├─ folder1
│  ├─ item.php
│  └─ so.php
└─ folder2
   └─ item.html

And an iterator created with new RecursiveDirectoryIterator("folderI").

When you echo out the $key you will get the following for item.php

folderI/folder1/item.php

Similarly, if we did new RecursiveDirectoryIterator("../somewhere/folderI"), then $key would look like:

../somewhere/folderI/folder1/item.php

You wish to get the path to the file, without including the path used to create the iterator. Thankfully, the RecursiveDirectoryIterator has a method whose sole purpose in life is to do exactly this.

RecursiveDirectoryIterator::getSubPathname()

You can use the following which will be easier

$newKey = $iterator->getSubPathname();

This will return the following for item.php, given the same folder structure shown above:

folder1/item.php

The method can be used like so:

foreach ($iterator as $key=>$value) {
    $zip->addFile(realpath($key), $iterator->getSubPathname()) or die ("ERROR: Could not add file: $key");
}

Aside: we can use $iterator->getSubPathname() here because the RecursiveIteratorIterator passes along any calls to unknown methods to the "inner iterator", which in this case is the RecursiveDirectoryIterator. Equally, you could do $iterator->getInnerIterator()->getSubPathname(), both do the same job.

salathe
  • 51,324
  • 12
  • 104
  • 132
UnholyRanger
  • 1,977
  • 14
  • 24
  • Hint: [`getSubPathname()`](http://php.net/recursivedirectoryiterator.getsubpathname) – salathe Mar 15 '13 at 20:22
  • @Unholy Ranger: I tried this and it does the exact same thing as my `updated` code. I had incorrectly inserted one of the codes I was trying. But after @salathe pointed that out, I have updated the code. And the code you provided does the same thing. I still get the parent folder with child folders inside it, after I unzip `my-archive.zip` – Bruner Mar 18 '13 at 15:04
  • @UnholyRanger: The code does `exactly` the same thing as it did earlier. After I unzip `my-archive.zip`, I get a folder `FolderI` and then all subfolders inside it. What I need is for the script to directly show all subfolders once I unzip the file. Can't this be done? – Bruner Mar 18 '13 at 18:04
  • 1
    @Bruner Yes it can. However, I don't know how much more I can explain it. My answer as a whole covers why the `folderI` is in the zip and how to remove it. Please read carefully and understand how the `$zip->addFile` function works. I have personally tested this code and it works – UnholyRanger Mar 18 '13 at 18:08
  • @UnholyRanger: Okay. Can you tell me what OS you are using? I even tried copy-pasting the entire code as is, but it still creates the `FolderI`. – Bruner Mar 18 '13 at 18:18
  • @Bruner OS shouldn't matter, but its Centos 5 w/ PHP 5.3. I just ran the code again and it worked. You could even try in the `foreach` to echo out each value of `$iterator->getSubPathname()`. I'll explore this some more – UnholyRanger Mar 18 '13 at 18:24
  • @UnholyRanger: It's bizarre, if it can't be affected by the system and it works on yours! I mean.. I tried to run this from my macbook as well and I still get the `my-archive` folder after I unzip `my-archive.zip` and then that has the subfolders. I'm really clueless .. – Bruner Mar 18 '13 at 18:44
  • 1
    If you're unzipping the file, sometimes the system unzips it into a local folder named off the zip file. I believe this is what Mac does when you open a ZIP. If you unzip the contents, you will see that it is doing what you want it to do. – UnholyRanger Mar 18 '13 at 18:46
  • @UnholyRanger: It is probably the Mac then! The code obviously seems to make sense and its the system which would be at fault, if its working for you and not me. Thanks for the help! :) – Bruner Mar 18 '13 at 18:55
  • @Bruner yes. whenever you open a zip on a Mac `item.zip` it will create a local folder `item` which it dumps the contents of `item.zip`. If on windows, you could do an option of `extract here..` where it would just dump the contents locally. So forth – UnholyRanger Mar 18 '13 at 18:57
  • Yep. Gotcha. There's no such option and there are some softwares to do that. Will use them. But, this one would at least help my Windows based client. Thanks.. – Bruner Mar 18 '13 at 18:59
1

As far as directions go, I can offer this:

This looks a bit overcomplicated to me as it is, but you can pile on by adding a foreach loop to create those iterators like so:

foreach (glob('./*', GLOB_ONLYDIR) as $dir_name) {
   // iterator
   // add file
}

Glob could probably do a lot more for you, possibly eliminate those iterator objects in favour of just iterating over glob output.

guessimtoolate
  • 8,372
  • 2
  • 18
  • 26
  • Wouldn't adding the iterator inside the foreach be problematic! I did try it and it seems to blow up. I am using `$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator("FolderI/"));` in place of the iterator in your suggestion. – Bruner Mar 15 '13 at 12:57