Considering Drupal's Archiver Interface
One of the annoying things about using Tar is that unless you remember -C the files that you are archiving include their full path. This means that when you go to extract the files you get the full directory tree. Though there are clearly good reasons for this, when you're just trying to put together a few files to send out this is probably not behavior that you want.
Poking at Drupal's ArchiverInterface it I ran into this problem. I want to be able to build arbitrary archives of single files at the root of the archive that can be used elsewhere without having to munge file paths at a later date. Using both types of archiving (Tar and Zip) that are implemented in core the files in my archives have their full paths. Looking at the code this seems to be borne out.
Zip::add uses $this->zip->addFile($file_path) which only adds the file path itself. Using $this->zip->addFile($file_path, basename($file_path)) would. Unfortunately Drupal doesn't have it implemented this way.
Likewise Tar::add has the same conundrum with $this->tar->add($file_path). This looks to be a bit more tricky to work around because there is no obvious way to pass the -C flag with the pear tar library. However the addModify() method does allow for adding a file path that *is removed* from the path in the archive so it's possible to strip the basename($file_path) off of the $file_path and pass that as a parameter. Feels a bit backwards but it's workable.
Here's the hack that I'm mulling over: First the Drupal archiver classes need to be altered so the add() method can be hijacked latter:
function my_module_archiver_info_alter(&$info) {
$info['zip']['class'] = 'ArchiverZipCustom';
$info['tar']['class'] = 'ArchiverTarCustom';
}
Now two classes can be built out. The one for Tar has to strip the file name out of the path.
class ArchiverTarCustom extends ArchiverTar {
/**
* Implement the add function with the ability to not use the full filepath
*
* In this case $basename can only be true or false. If it is set the full
* filepath is stripped.
*
* @param type $file_path
* @param type $basename
* @return \ArchiverZipCustom
*/
public function add($file_path, $basename = FALSE) {
if ($basename) {
$basename = basename($file_path);
$remove_path = str_replace($basename, '', $file_path);
$this->tar->addModify($file_path, '', $remove_path);
}
// Normal behavior
else {
$this->tar->add($filepath);
}
return $this;
}
The same technique can be done for Zip.
class ArchiverZipCustom extends ArchiverZip {
/**
* Implement the add function with the ability to not use the full filepath
*
* @param type $file_path
* @param type $basename
* @return \ArchiverZipCustom
*/
public function add($file_path, $basename = FALSE) {
if ($basename) {
if (! is_string($basename)) {
$basename = basename($file_path);
}
$this->zip->addFile($file_path, $basename);
}
else {
$this->zip->addFile($file_path);
}
return $this;
}
The implementation ends up being no different than before:
// Create a temporary file for this archive, $type is tar, zip, or other supported archive format
$path = file_create_filename(basename($file->uri) . '.' . $type, 'temporary://');
// Use touch to stub the archive out before it is created. Zip throws a fatal error if the archive
// file does not exist before create it
// NOTE that touch() does not support streamwrappers however all files are assumed to be local
// for these compression tools
// NOTE; drupal_realpath is supposedly being deprecated. Unfortunately I can't seem to live without it.
touch(drupal_realpath($path));
// Get the archiver
$archiver = archiver_get_archiver($path);
// The modified archiver classes support using local paths. I'm not totally sure that realpath is needed here
$archiver->add(drupal_realpath($file->uri), TRUE);
Ultimately this doesn't feel like the best way to go about this- it is a fair bit of code to do something that is ultimately quite simple. Core may have some very specific use cases for the archiver which are not inclusive of what I want to do with it. While duplication of code is never great, sometimes setting boundaries is. At least this hack seems somewhat workable for what I'm doing.
Categories: Planet Drupal