BxFilesBulkDownloader.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * @defgroup Files Files
  7. * @ingroup UnaModules
  8. *
  9. * @{
  10. */
  11. /**
  12. * Files bulk downloader
  13. */
  14. define('BX_FILES_DOWNLOADER_TIMEOUT', 10); //seconds
  15. define('BX_FILES_DOWNLOADER_TIMEOUT_REQUESTS', 15); //seconds
  16. define('BX_FILES_DOWNLOADER_STATUS_EMPTY', 0);
  17. define('BX_FILES_DOWNLOADER_STATUS_TOO_LARGE', 1);
  18. define('BX_FILES_DOWNLOADER_STATUS_DOWNLOADING', 2);
  19. define('BX_FILES_DOWNLOADER_STATUS_READY', 3);
  20. class BxFilesBulkDownloader
  21. {
  22. var $oModule;
  23. public function __construct(&$oModule)
  24. {
  25. $this->oModule = &$oModule;
  26. }
  27. public function createDownloadingJob($aFiles, $sZipFileName) {
  28. $CNF = &$this->oModule->_oConfig->CNF;
  29. $aFilesList = $this->getFolderFiles($aFiles, '/', 'mixed');
  30. if(!$aFilesList) return [
  31. 'status' => BX_FILES_DOWNLOADER_STATUS_EMPTY,
  32. ];
  33. $iMaxSize = intval(getParam($CNF['PARAM_MAX_BULK_DOWNLOAD_SIZE'])) * 1024 * 1024;
  34. if ($iMaxSize) {
  35. $iTotalSize = 0;
  36. foreach ($aFilesList as $aFile) {
  37. $iTotalSize += $aFile['size'];
  38. }
  39. if ($iTotalSize >= $iMaxSize) return [
  40. 'status' => BX_FILES_DOWNLOADER_STATUS_TOO_LARGE,
  41. ];
  42. }
  43. $mRes = $this->processDownloading($aFilesList);
  44. if ($mRes === BX_FILES_DOWNLOADER_STATUS_DOWNLOADING) {
  45. $iDownloadingJobId = $this->oModule->_oDb->createDownloadingJob($aFilesList, $sZipFileName, bx_get_logged_profile_id());
  46. return [
  47. 'status' => BX_FILES_DOWNLOADER_STATUS_DOWNLOADING,
  48. 'job' => $iDownloadingJobId,
  49. ];
  50. } else {
  51. return [
  52. 'status' => BX_FILES_DOWNLOADER_STATUS_READY,
  53. 'file' => $mRes,
  54. 'filename' => $this->sanitizeFileName($sZipFileName).'.zip',
  55. ];
  56. }
  57. }
  58. public function processDownloading(&$aFiles) {
  59. $CNF = &$this->oModule->_oConfig->CNF;
  60. $oStorage = BxDolStorage::getObjectInstance($CNF['OBJECT_STORAGE']);
  61. $iJobStarted = time();
  62. foreach ($aFiles as $iIndex => $aFile) {
  63. if (isset($aFile['downloaded']) && file_exists($aFile['downloaded'])) continue;
  64. $sFileUrl = $oStorage->getFileUrlById($aFile['id']);
  65. $sFilePath = BX_DIRECTORY_PATH_TMP . 'download-job-file-'.$aFile['id'];
  66. if (!file_exists($sFilePath)) {
  67. @file_put_contents($sFilePath, bx_file_get_contents($sFileUrl));
  68. }
  69. $aFiles[$iIndex]['downloaded'] = $sFilePath;
  70. if (time() - $iJobStarted >= BX_FILES_DOWNLOADER_TIMEOUT) {
  71. return BX_FILES_DOWNLOADER_STATUS_DOWNLOADING;
  72. }
  73. }
  74. return $this->createZipArchive($aFiles);
  75. }
  76. private function createZipArchive(&$aFiles) {
  77. $sZipFile = BX_DIRECTORY_PATH_TMP.'files-folder-'.time().mt_rand().'.zip';
  78. if (file_exists($sZipFile)) @unlink($sZipFile);
  79. $oZipFile = new ZipArchive();
  80. if ($oZipFile->open($sZipFile, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE)) {
  81. $iIndex = 0;
  82. foreach ($aFiles as $aFile) {
  83. $sFilePath = $aFile['downloaded'];
  84. if (file_exists($sFilePath) && is_readable($sFilePath)) {
  85. $oZipFile->addFile($sFilePath, $aFile['filename']);
  86. $oZipFile->setCompressionIndex($iIndex++, ZipArchive::CM_STORE);
  87. }
  88. }
  89. $oZipFile->close();
  90. foreach ($aFiles as $aFile)
  91. @unlink($aFile['downloaded']);
  92. return $sZipFile;
  93. } else {
  94. return BX_FILES_DOWNLOADER_STATUS_EMPTY;
  95. }
  96. }
  97. private function getFolderFiles($mFile, $sRelPath, $sType) {
  98. $aFiles = $this->oModule->_oDb->getFolderFilesEx($mFile, $sType);
  99. if (!$aFiles) return [];
  100. $aResult = [];
  101. $aUniqueNames = [];
  102. foreach ($aFiles as $aFile) {
  103. if (CHECK_ACTION_RESULT_ALLOWED !== $this->oModule->checkAllowedView($aFile)) continue;
  104. //in case title has been changed we might have to add extensions manually
  105. $aFileInfo = pathinfo($aFile['title']);
  106. $sFileBaseName = isset($aFileInfo['filename']) ? $aFileInfo['filename'] : '_';
  107. $sFileExt = isset($aFileInfo['extension']) ? $aFileInfo['extension'] : '';
  108. if (!$sFileExt || $sFileExt != $aFile['ext']) $sFileExt = $aFile['ext'];
  109. //handle multiple files with the same name
  110. $sFilename = $this->sanitizeFileName($sFileBaseName);
  111. $iIndex = 1;
  112. while (isset($aUniqueNames[$sFilename])) {
  113. $sFilename = $this->sanitizeFileName($sFileBaseName.'_'.$iIndex++);
  114. }
  115. $aUniqueNames[$sFilename] = 1;
  116. if ($aFile['type'] == 'folder') {
  117. $aResult = array_merge($aResult, $this->getFolderFiles($aFile['id'], $sRelPath . $sFilename . '/', 'folder'));
  118. } else {
  119. $aResult[] = [
  120. 'filename' => trim($sRelPath.$sFilename.'.'.$sFileExt, '/'),
  121. 'size' => $aFile['size'],
  122. 'id' => $aFile['file_id'],
  123. ];
  124. }
  125. }
  126. return $aResult;
  127. }
  128. private function sanitizeFileName($sDesiredFilename) {
  129. $sDesiredFilename = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '_', $sDesiredFilename);
  130. $sDesiredFilename = mb_ereg_replace("([\.]{2,})", '_', $sDesiredFilename);
  131. return $sDesiredFilename;
  132. }
  133. }
  134. /** @} */