In PHP 7.4, neither ZipArchive::setMtimeIndex
nor ZipArchive::setMtimeName
is available.
When creating Zip archives from files in PHP 7.4; files’ mtime
and permissions are applied to the corresponding Zip entries’ attributes by the ZipArchive::addFile
method.
Unfortunately, it is not the case with directories; as those are created with the ZipArchive::addEmptyDir
method, and not read from the filesystem.
I created a fixDirAttributes
function to fix the mtime
and permissions for directories within a Zip archive. This cannot work with PHP 7.4 though.
Do you know a way to fix the mtime
of a Zip archive’s directories in PHP 7.4?
Here is my code to reproduce the issue:
#!/usr/bin/env php7.4
<?php
if (!file_exists('myDir')) mkdir('myDir');
file_put_contents('myDir/test.txt', 'test');
chmod('myDir/test.txt', 0740);
chmod('myDir', 0750);
touch('myDir/test.txt', mktime(10, 10, 0, 1, 1, 2024));
touch('myDir', mktime(5, 42, 0, 1, 1, 2024));
if (file_exists('test.zip')) unlink('test.zip');
$zip = new ZipArchive();
if ($zip->open('test.zip', ZipArchive::CREATE) === true) {
$zip->addEmptyDir("myDir");
fixDirAttributes($zip, 'myDir', 'myDir'); // Directory already existe in current dir ./myDir
$zip->addFile('myDir/test.txt', 'myDir/test.txt'); // file exist in ./myDir dir
$zip->close();
echo "Okn";
} else {
echo "KOn";
}
function fixDirAttributes(ZipArchive $zip, string $dirPath, string $pathInZip)
{
$indexInZip = $zip->locateName('/' === mb_substr($pathInZip, -1) ? $pathInZip : $pathInZip . '/');
if (false !== $indexInZip) {
if (method_exists($zip, 'setMtimeIndex')) { // PHP >= 8.0.0, PECL zip >= 1.16.0
$zip->setMtimeIndex($indexInZip, filemtime($dirPath));
}
$filePerms = fileperms($dirPath);
if (false !== $filePerms) { // filePerms supported
$zip->setExternalAttributesIndex($indexInZip, ZipArchive::OPSYS_DEFAULT, $filePerms << 16);
}
}
}
2
Answers
Finally I found a solution that does not involve low level Zip handling and that works with PHP 7.4.
The trick is to use a timestamp dummy empty file, and use it to create the directory in the Zip Archive.
Instead of using
ZipArchive::addEmptyDir
, I use my ownaddDir
implementation that uses the dummy empty file to create the desired directory entry. As a bonus it could also transfer permissions, but changing these in a temporary file might be restricted, so it usesZipArchive::setExternalAttributesName
instead to be safe.You can accomplish this with a 3rd party zip library that does not require the native PHP Zip extension.
By default, this library will use the live filesystem attributes, but it yields an archive with no explicit directory entry:
When you unzip the archive, you’d get the correct mtime for the file but the current date/time for the subdir. It seems that’s not what you want however, so you can instead tell the library to explicitly add the separate subdirectory entry, using the live attributes read from the filesystem:
This yields a dedicated subdirectory entry with the desired mtime, and it works the same from 7.4 to 8.3