[TikiWiki-commits] [Git][tikiwiki/tiki][master] [FIX] Enable batch downloading of folders in file galleries
Benoit Grégoire (@benoitg) via TikiWiki-cvs <[email protected]>
| Newsgroups | gmane.comp.cms.tiki.cvs |
|---|---|
| Message-ID | <698396d46c401_3b18432c11885@gitlab-sidekiq-low-urgency-cpu-bound-v2-7d899c995b-qtqmx.mail> |
Benoit Grégoire pushed to branch master at Tiki Wiki CMS Groupware / Tiki
Commits:
fd7de913 by Moïse Nturubika at 2026-02-04T18:49:32+00:00
[FIX] Enable batch downloading of folders in file galleries
---
* [FIX] Eliminate polymorphism in zip() and remove error suppression operator
* [REF] Improve zip method code quality per code review
* [FIX] Enable batch downloading of folders in file galleries
See merge request tikiwiki/tiki!9296
- - - - -
2 changed files:
- lib/Filegals/FileGalLib.php
- tiki-download_file.php
Changes:
=====================================
lib/Filegals/FileGalLib.php
=====================================
@@ -23,6 +23,7 @@ use Tiki\FileGallery\File as TikiFile;
use Tiki\FileGallery\FileDraft as TikiFileDraft;
use Tiki\FileGallery\ImageTransformer;
use Tiki\Lib\Filegals\FileIsNotSafeException;
+use Symfony\Component\Filesystem\Filesystem;
use TikiDb;
use TikiLib;
use TikiMail;
@@ -1266,10 +1267,22 @@ class FileGalLib extends TikiLib
$this->table('tiki_files')->update(['maxhits' => (int) $limit], ['fileId' => (int) $fileId]);
}
// not the best optimisation as using a library using files and not content
- public function zip($fileIds, &$error, $zipName = '')
+ /**
+ * Create a zip file from file gallery files
+ *
+ * @param array $fileEntries Array of file entries with structure:
+ * ['fileId' => int, 'path' => string]
+ * The 'path' key specifies the relative folder path within the zip
+ * to preserve folder hierarchy when downloading galleries.
+ * @param string &$error Error message on failure
+ * @param string $zipName Optional custom zip name
+ * @return array|false|null File info array on success, false on error, null on no permission
+ */
+ public function zip($fileEntries, &$error, $zipName = '')
{
global $tiki_p_admin_file_galleries, $prefs, $user;
$userlib = TikiLib::lib('user');
+
$list = [];
$temp = '/' . md5(random_bytes(10)) . '/';
if (! mkdir(sys_get_temp_dir() . $temp)) {
@@ -1280,24 +1293,48 @@ class FileGalLib extends TikiLib
$error = "Can not create directory $temp";
return false;
}
- $fileIds = array_unique($fileIds);
- Perms::bulk(['type' => 'file'], 'object', $fileIds);
+ // Check permissions
+ $checkIds = array_unique(array_column($fileEntries, 'fileId'));
+ Perms::bulk(['type' => 'file'], 'object', $checkIds);
+
$filenames = [];
- $padding = strlen(count($fileIds)) - 1;
- foreach ($fileIds as $fileId) {
+ $padding = strlen(count($fileEntries)) - 1;
+ foreach ($fileEntries as $fileEntry) {
+ $fileId = $fileEntry['fileId'];
+ $relativePath = $fileEntry['path'];
+
$file = TikiFile::id($fileId);
if ($tiki_p_admin_file_galleries == 'y' || $userlib->user_has_perm_on_object($user, $file->fileId, 'file', 'tiki_p_download_files')) {
if (empty($zipName)) {
$zipName = $file->galleryId;
}
+
+ $relativePath = str_replace('..', '', $relativePath);
+ $relativePath = trim($relativePath, '/\\');
+ $destDir = $temp;
+ if ($relativePath !== '') {
+ $destDir .= $relativePath . '/';
+ if (! is_dir($destDir)) {
+ if (! mkdir($destDir, 0777, true)) {
+ $error = "Cannot create directory: $destDir";
+ return false;
+ }
+ }
+ }
+
$filename = $file->filename;
+ $pathKey = $relativePath;
+ if (! isset($filenames[$pathKey])) {
+ $filenames[$pathKey] = [];
+ }
+
$counter = 1;
- while (in_array($filename, $filenames)) {
+ while (in_array($filename, $filenames[$pathKey])) {
$filename = $file->filename . '_' . str_pad($counter, $padding, '0', STR_PAD_LEFT);
$counter++;
}
- $filenames[] = $filename;
- $tmp = $temp . $filename;
+ $filenames[$pathKey][] = $filename;
+ $tmp = $destDir . $filename;
if (! copy($file->getWrapper()->getReadableFile(), $tmp)) {
$error = "Can not copy to $tmp";
return false;
@@ -1312,7 +1349,7 @@ class FileGalLib extends TikiLib
}
$info['filename'] = "$zipName.zip";
$zip = $temp . $info['filename'];
- define('PCZLIB_SEPARATOR', '\001');
+ define('PCZLIB_SEPARATOR', '\\001');
if (! $archive = new PclZip($zip)) {
$error = $archive->errorInfo(true);
return false;
@@ -1325,11 +1362,12 @@ class FileGalLib extends TikiLib
$info['path'] = '';
$info['filetype'] = 'application/x-zip-compressed';
$info['filesize'] = strlen($info['data']);
- foreach ($list as $tmp) {
- unlink($tmp);
- }
+
+ // Clean up temp directory using Symfony Filesystem
unlink($zip);
- rmdir($temp);
+ $fs = new Filesystem();
+ $fs->remove($temp);
+
return $info;
}
=====================================
tiki-download_file.php
=====================================
@@ -52,16 +52,64 @@ $zip = false;
$error = '';
if (! $skip) {
- if (isset($_REQUEST['fileId'])) {
- if (! is_array($_REQUEST['fileId'])) {
+ if (isset($_REQUEST['fileId']) || isset($_REQUEST['galId'])) {
+ $isBatch = (isset($_REQUEST['fileId']) && is_array($_REQUEST['fileId'])) || ! empty($_REQUEST['galId']);
+
+ if (! $isBatch) {
if (isset($_GET['draft'])) {
$info = \Tiki\FileGallery\FileDraft::id($_REQUEST['fileId'])->getParams();
} else {
$info = $filegallib->get_file($_REQUEST['fileId']);
}
} else {
+ // Collect file entries in structured format: ['fileId' => int, 'path' => string]
+ $fileEntries = [];
+
+ // Convert plain fileIds from request to structured format
+ $requestFileIds = $_REQUEST['fileId'] ?? [];
+ if (! is_array($requestFileIds)) {
+ $requestFileIds = [$requestFileIds];
+ }
+ foreach ($requestFileIds as $fid) {
+ $fileEntries[] = ['fileId' => $fid, 'path' => ''];
+ }
+
+ // Process galleries recursively to collect files with paths
+ if (! empty($_REQUEST['galId'])) {
+ $galIds = is_array($_REQUEST['galId']) ? $_REQUEST['galId'] : [$_REQUEST['galId']];
+
+ $visited = [];
+ $processGallery = function ($galId, $currentPath) use (&$fileEntries, $filegallib, &$processGallery, &$visited) {
+ if (in_array($galId, $visited)) {
+ return;
+ }
+ $visited[] = $galId;
+
+ $res = $filegallib->get_files(0, -1, 'name_asc', '', $galId, false, true, false, true, false, false, false, false);
+ if (! empty($res['data'])) {
+ foreach ($res['data'] as $item) {
+ // Galleries have both fileId and galleryId; use 'id' for the actual gallery ID
+ if (! empty($item['isgal']) || (isset($item['galleryId']) && $item['id'] == $item['galleryId'])) {
+ $subGalId = $item['id'];
+ if ($subGalId != $galId && ! in_array($subGalId, $visited)) {
+ $processGallery($subGalId, $currentPath . $item['name'] . '/');
+ }
+ } elseif (isset($item['fileId'])) {
+ $fileEntries[] = ['fileId' => $item['fileId'], 'path' => $currentPath];
+ }
+ }
+ }
+ };
+
+ foreach ($galIds as $gId) {
+ $gInfo = $filegallib->get_file_gallery_info($gId);
+ if ($gInfo) {
+ $processGallery($gId, $gInfo['name'] . '/');
+ }
+ }
+ }
$zipName = $_REQUEST['zipName'] ?? '';
- $info = $filegallib->zip($_REQUEST['fileId'], $error, $zipName);
+ $info = $filegallib->zip($fileEntries, $error, $zipName);
$zip = true;
}
} elseif (isset($_REQUEST['galleryId']) && isset($_REQUEST['name'])) {
@@ -94,7 +142,7 @@ if (! $skip) {
TikiLib::lib('cache')->cacheItem($src, serialize($info), 'external_downloaded_files');
}
} else {
- $access->display_error('', tra('Incorrect param'), 400);
+ $access->display_error('', tra('Incorrect param. No file or gallery selected for download.'), 400);
}
if (! is_array($info)) {
$access->display_error(null, tra('File has been deleted'), 404);
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/fd7de9131ffd0619f747322efb5d8059437511d1
--
View it on GitLab: https://gitlab.com/tikiwiki/tiki/-/commit/fd7de9131ffd0619f747322efb5d8059437511d1
You're receiving this email because of your account on gitlab.com.
_______________________________________________
TikiWiki-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tikiwiki-cvs