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 "Ok\n";
} else {
echo "KO\n";
}
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);
}
}
}
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 own addDir
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 uses ZipArchive::setExternalAttributesName
instead to be safe.
#!/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 (false !== $mtimeDummy = tempnam(sys_get_temp_dir(), 'mtimeDummy')) {
if ($zip->open('test.zip', ZipArchive::CREATE) === true) {
addDir($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();
}
unlink($mtimeDummy); // Deletes only after Zip closed
}
function addDir(ZipArchive $zip, string $dirPath, string $entryName)
{
global $mtimeDummy; // Would be class member in an OOP context
touch($mtimeDummy, filemtime($dirPath));
$zip->addFile($mtimeDummy, $entryName); // Add empty dummy as a directory
if (false !== $filePerms = fileperms($dirPath)) { // filePerms supported
$zip->setExternalAttributesName($entryName, \ZipArchive::OPSYS_UNIX, $filePerms << 16);
}
}