Commit ce268477f8cab496a2d185131fe8ecde46a90604
1 parent
48c32a49
KTS-4140. Moved the bulk download into a background task. Changed it to use PEAR…
… archive functionality instead of the zip binary. "CLONE -error: The ZIP file can only be downloaded once - if you cancel the download, you will need to reload the page. (SUP-1680)" In progress. Committed by: Megan Watson Reviewed by: Kevin Fourie
Showing
8 changed files
with
715 additions
and
157 deletions
lib/foldermanagement/compressionArchiveUtil.inc.php
| ... | ... | @@ -36,6 +36,8 @@ |
| 36 | 36 | * |
| 37 | 37 | */ |
| 38 | 38 | |
| 39 | +require_once('File/Archive.php'); | |
| 40 | + | |
| 39 | 41 | /** |
| 40 | 42 | * Class to create and download a zip file |
| 41 | 43 | */ |
| ... | ... | @@ -50,6 +52,8 @@ class ZipFolder { |
| 50 | 52 | var $aReplaceKeys = array(); |
| 51 | 53 | var $aReplaceValues = array(); |
| 52 | 54 | var $sOutputEncoding = 'UTF-8'; |
| 55 | + var $extension = 'zip'; | |
| 56 | + var $exportCode = null; | |
| 53 | 57 | |
| 54 | 58 | /** |
| 55 | 59 | * Constructor |
| ... | ... | @@ -57,22 +61,22 @@ class ZipFolder { |
| 57 | 61 | * @param string $sZipFileName The name of the zip file - gets ignored at the moment. |
| 58 | 62 | * @param string $exportCode The code to use if a zip file has already been created. |
| 59 | 63 | */ |
| 60 | - function ZipFolder($sZipFileName, $exportCode = null) { | |
| 64 | + function ZipFolder($sZipFileName = null, $exportCode = null, $extension = 'zip') { | |
| 61 | 65 | $this->oKTConfig =& KTConfig::getSingleton(); |
| 62 | 66 | $this->oStorage =& KTStorageManagerUtil::getSingleton(); |
| 63 | 67 | |
| 64 | 68 | $this->sOutputEncoding = $this->oKTConfig->get('export/encoding', 'UTF-8'); |
| 69 | + $this->extension = $extension; | |
| 65 | 70 | |
| 66 | 71 | $this->sPattern = "[\*|\%|\\\|\/|\<|\>|\+|\:|\?|\||\'|\"]"; |
| 67 | 72 | $this->sFolderPattern = "[\*|\%|\<|\>|\+|\:|\?|\||\'|\"]"; |
| 68 | 73 | |
| 69 | - // If the export code exists then a temp zip directory has already been created | |
| 70 | - if(!empty($exportCode)){ | |
| 71 | - $aData = KTUtil::arrayGet($_SESSION['zipcompression'], $exportCode); | |
| 74 | + $this->exportCode = $exportCode; | |
| 72 | 75 | |
| 73 | - if(!empty($aData)){ | |
| 74 | - $sTmpPath = $aData['dir']; | |
| 75 | - } | |
| 76 | + // Check if the temp directory has been created and stored in session | |
| 77 | + $aData = KTUtil::arrayGet($_SESSION['zipcompression'], $exportCode); | |
| 78 | + if(!empty($aData) && isset($aData['dir'])){ | |
| 79 | + $sTmpPath = $aData['dir']; | |
| 76 | 80 | }else { |
| 77 | 81 | $sBasedir = $this->oKTConfig->get("urls/tmpDirectory"); |
| 78 | 82 | $sTmpPath = tempnam($sBasedir, 'kt_compress_zip'); |
| ... | ... | @@ -100,6 +104,15 @@ class ZipFolder { |
| 100 | 104 | $this->aReplaceValues = array_values($aReplace); |
| 101 | 105 | } |
| 102 | 106 | |
| 107 | + static public function get($exportCode) | |
| 108 | + { | |
| 109 | + static $zipFolder = null; | |
| 110 | + if(is_null($zipFolder)){ | |
| 111 | + $zipFolder = new ZipFolder('', $exportCode); | |
| 112 | + } | |
| 113 | + return $zipFolder; | |
| 114 | + } | |
| 115 | + | |
| 103 | 116 | /** |
| 104 | 117 | * Return the full path |
| 105 | 118 | * |
| ... | ... | @@ -154,7 +167,7 @@ class ZipFolder { |
| 154 | 167 | |
| 155 | 168 | $sOrigFile = $this->oStorage->temporaryFile($oDocument); |
| 156 | 169 | $sFilename = $sParentFolder.'/'.$sDocName; |
| 157 | - copy($sOrigFile, $sFilename); | |
| 170 | + @copy($sOrigFile, $sFilename); | |
| 158 | 171 | |
| 159 | 172 | $this->aPaths[] = $sDocPath.'/'.$sDocName; |
| 160 | 173 | return true; |
| ... | ... | @@ -190,49 +203,62 @@ class ZipFolder { |
| 190 | 203 | return PEAR::raiseError(_kt("No folders or documents found to compress")); |
| 191 | 204 | } |
| 192 | 205 | |
| 206 | + $config = KTConfig::getSingleton(); | |
| 207 | + $useBinary = $config->get('export/useBinary', false); | |
| 208 | + | |
| 193 | 209 | // Set environment language to output character encoding |
| 194 | 210 | $loc = $this->sOutputEncoding; |
| 195 | 211 | putenv("LANG=$loc"); |
| 196 | 212 | putenv("LANGUAGE=$loc"); |
| 197 | 213 | $loc = setlocale(LC_ALL, $loc); |
| 198 | 214 | |
| 215 | + if($useBinary){ | |
| 216 | + $sManifest = sprintf("%s/%s", $this->sTmpPath, "MANIFEST"); | |
| 217 | + file_put_contents($sManifest, join("\n", $this->aPaths)); | |
| 218 | + } | |
| 199 | 219 | |
| 200 | - | |
| 201 | - $sManifest = sprintf("%s/%s", $this->sTmpPath, "MANIFEST"); | |
| 202 | - file_put_contents($sManifest, join("\n", $this->aPaths)); | |
| 203 | - $sZipFile = sprintf("%s/%s.zip", $this->sTmpPath, $this->sZipFileName); | |
| 220 | + $sZipFile = sprintf("%s/%s.".$this->extension, $this->sTmpPath, $this->sZipFileName); | |
| 204 | 221 | $sZipFile = str_replace('<', '', str_replace('</', '', str_replace('>', '', $sZipFile))); |
| 205 | - $sZipCommand = KTUtil::findCommand("export/zip", "zip"); | |
| 206 | - $aCmd = array($sZipCommand, "-r", $sZipFile, ".", "-i@MANIFEST"); | |
| 207 | - $sOldPath = getcwd(); | |
| 208 | - chdir($this->sTmpPath); | |
| 209 | - | |
| 210 | - // Note that the popen means that pexec will return a file descriptor | |
| 211 | - $aOptions = array('popen' => 'r'); | |
| 212 | - $fh = KTUtil::pexec($aCmd, $aOptions); | |
| 213 | - | |
| 214 | - if($bEchoStatus){ | |
| 215 | - $last_beat = time(); | |
| 216 | - while(!feof($fh)) { | |
| 217 | - if ($i % 1000 == 0) { | |
| 218 | - $this_beat = time(); | |
| 219 | - if ($last_beat + 1 < $this_beat) { | |
| 220 | - $last_beat = $this_beat; | |
| 221 | - print " "; | |
| 222 | + | |
| 223 | + if($useBinary){ | |
| 224 | + $sZipCommand = KTUtil::findCommand("export/zip", "zip"); | |
| 225 | + $aCmd = array($sZipCommand, "-r", $sZipFile, ".", "-i@MANIFEST"); | |
| 226 | + $sOldPath = getcwd(); | |
| 227 | + chdir($this->sTmpPath); | |
| 228 | + | |
| 229 | + // Note that the popen means that pexec will return a file descriptor | |
| 230 | + $aOptions = array('popen' => 'r'); | |
| 231 | + $fh = KTUtil::pexec($aCmd, $aOptions); | |
| 232 | + | |
| 233 | + if($bEchoStatus){ | |
| 234 | + $last_beat = time(); | |
| 235 | + while(!feof($fh)) { | |
| 236 | + if ($i % 1000 == 0) { | |
| 237 | + $this_beat = time(); | |
| 238 | + if ($last_beat + 1 < $this_beat) { | |
| 239 | + $last_beat = $this_beat; | |
| 240 | + print " "; | |
| 241 | + } | |
| 222 | 242 | } |
| 243 | + $contents = fread($fh, 4096); | |
| 244 | + if ($contents) { | |
| 245 | + print nl2br($this->_convertEncoding($contents, false)); | |
| 246 | + } | |
| 247 | + $i++; | |
| 223 | 248 | } |
| 224 | - $contents = fread($fh, 4096); | |
| 225 | - if ($contents) { | |
| 226 | - print nl2br($this->_convertEncoding($contents, false)); | |
| 227 | - } | |
| 228 | - $i++; | |
| 229 | 249 | } |
| 250 | + pclose($fh); | |
| 251 | + }else{ | |
| 252 | + // Create the zip archive using the PEAR File_Archive | |
| 253 | + File_Archive::extract( | |
| 254 | + File_Archive::read($this->sTmpPath.'/Root Folder'), | |
| 255 | + File_Archive::toArchive($this->sZipFileName.'.'.$this->extension, File_Archive::toFiles($this->sTmpPath), $this->extension) | |
| 256 | + ); | |
| 230 | 257 | } |
| 231 | - pclose($fh); | |
| 232 | 258 | |
| 233 | 259 | // Save the zip file and path into session |
| 234 | 260 | $_SESSION['zipcompression'] = KTUtil::arrayGet($_SESSION, 'zipcompression', array()); |
| 235 | - $sExportCode = KTUtil::randomString(); | |
| 261 | + $sExportCode = $this->exportCode; | |
| 236 | 262 | $_SESSION['zipcompression'][$sExportCode] = array( |
| 237 | 263 | 'file' => $sZipFile, |
| 238 | 264 | 'dir' => $this->sTmpPath, |
| ... | ... | @@ -262,18 +288,41 @@ class ZipFolder { |
| 262 | 288 | } |
| 263 | 289 | |
| 264 | 290 | if (!file_exists($sZipFile)) { |
| 265 | - return PEAR::raiseError(_kt('The ZIP file can only be downloaded once - if you cancel the download, you will need to reload the page.')); | |
| 291 | + return PEAR::raiseError(_kt('The zip file has not been created, if you are downloading a large number of documents | |
| 292 | + or a large document then it may take a few seconds to finish. Try refreshing the page.')); | |
| 266 | 293 | } |
| 267 | 294 | |
| 268 | 295 | $mimeType = 'application/zip; charset=utf-8;'; |
| 269 | 296 | $fileSize = filesize($sZipFile); |
| 270 | - $fileName = $this->sZipFileName . '.zip'; | |
| 297 | + $fileName = $this->sZipFileName .'.'.$this->extension; | |
| 271 | 298 | |
| 272 | 299 | KTUtil::download($sZipFile, $mimeType, $fileSize, $fileName); |
| 273 | 300 | KTUtil::deleteDirectory($sTmpPath); |
| 274 | 301 | return true; |
| 275 | 302 | } |
| 276 | 303 | |
| 304 | + function checkArchiveExists($exportCode = null) | |
| 305 | + { | |
| 306 | + if(!(isset($exportCode) && !empty($exportCode))) { | |
| 307 | + $exportCode = KTUtil::arrayGet($_SESSION['zipcompression'], 'exportcode'); | |
| 308 | + } | |
| 309 | + | |
| 310 | + $aData = KTUtil::arrayGet($_SESSION['zipcompression'], $exportCode); | |
| 311 | + | |
| 312 | + if(!empty($aData)){ | |
| 313 | + $sZipFile = $aData['file']; | |
| 314 | + $sTmpPath = $aData['dir']; | |
| 315 | + }else{ | |
| 316 | + $sZipFile = $this->sZipFile; | |
| 317 | + $sTmpPath = $this->sTmpPath; | |
| 318 | + } | |
| 319 | + | |
| 320 | + if (!file_exists($sZipFile)) { | |
| 321 | + return false; | |
| 322 | + } | |
| 323 | + return true; | |
| 324 | + } | |
| 325 | + | |
| 277 | 326 | /** |
| 278 | 327 | * Check that iconv exists and that the selected encoding is supported. |
| 279 | 328 | */ |
| ... | ... | @@ -301,5 +350,407 @@ class ZipFolder { |
| 301 | 350 | return iconv($this->sOutputEncoding, "UTF-8", $sMystring); |
| 302 | 351 | } |
| 303 | 352 | } |
| 353 | + | |
| 354 | + static public function checkDownloadSize($object) | |
| 355 | + { | |
| 356 | + return true; | |
| 357 | + | |
| 358 | + if($object instanceof Document || $object instanceof DocumentProxy){ | |
| 359 | + } | |
| 360 | + | |
| 361 | + if($object instanceof Folder || $object instanceof FolderProxy){ | |
| 362 | + $id = $object->iId; | |
| 363 | + | |
| 364 | + // If we're working with the root folder | |
| 365 | + if($id = 1){ | |
| 366 | + $sql = 'SELECT count(*) as cnt FROM documents where folder_id = 1'; | |
| 367 | + }else{ | |
| 368 | + $sql[] = "SELECT count(*) as cnt FROM documents where parent_folder_ids like '%,?' OR parent_folder_ids like '%,?,%' OR folder_id = ?"; | |
| 369 | + $sql[] = array($id, $id, $id); | |
| 370 | + } | |
| 371 | + | |
| 372 | + /* | |
| 373 | + SELECT count(*) FROM documents d | |
| 374 | + INNER JOIN document_metadata_version m ON d.metadata_version_id = m.id | |
| 375 | + INNER JOIN document_content_version c ON m.content_version_id = c.id | |
| 376 | + where (d.parent_folder_ids like '%,12' OR d.parent_folder_ids like '%,12,%' OR d.folder_id = 12) AND d.status_id < 3 AND size > 100000 | |
| 377 | + */ | |
| 378 | + | |
| 379 | + $result = DBUtil::getOneResult($sql); | |
| 380 | + | |
| 381 | + if($result['cnt'] > 10){ | |
| 382 | + return true; | |
| 383 | + } | |
| 384 | + } | |
| 385 | + | |
| 386 | + return false; | |
| 387 | + } | |
| 388 | +} | |
| 389 | + | |
| 390 | +/** | |
| 391 | + * Class to manage the queue of bulk downloads | |
| 392 | + * | |
| 393 | + */ | |
| 394 | +class DownloadQueue | |
| 395 | +{ | |
| 396 | + private $bNoisy; | |
| 397 | + private $bNotifications; | |
| 398 | + private $errors; | |
| 399 | + | |
| 400 | + public function __construct() | |
| 401 | + { | |
| 402 | + $config = KTConfig::getSingleton(); | |
| 403 | + $this->bNoisy = $config->get('tweaks/noisyBulkOperations', false); | |
| 404 | + $this->bNotifications = ($config->get('export/enablenotifications', 'on') == 'on') ? true : false; | |
| 405 | + } | |
| 406 | + | |
| 407 | + /** | |
| 408 | + * Add an item to the download queue | |
| 409 | + * | |
| 410 | + * @param string $code The identification string for the download | |
| 411 | + * @param string $id The object id | |
| 412 | + * @param string $type The type of object Folder | Document | |
| 413 | + */ | |
| 414 | + static public function addItem($code, $folderId, $id, $type) | |
| 415 | + { | |
| 416 | + $fields = array(); | |
| 417 | + $fields['code'] = $code; | |
| 418 | + $fields['folder_id'] = $folderId; | |
| 419 | + $fields['object_id'] = $id; | |
| 420 | + $fields['object_type'] = $type; | |
| 421 | + $fields['user_id'] = $_SESSION['userID']; | |
| 422 | + $fields['date_added'] = date('Y-m-d H:i:s'); | |
| 423 | + | |
| 424 | + $res = DBUtil::autoInsert('download_queue', $fields); | |
| 425 | + } | |
| 426 | + | |
| 427 | + public function removeItem($code) | |
| 428 | + { | |
| 429 | + $where = array('code' => $code); | |
| 430 | + $res = DBUtil::whereDelete('download_queue', $where); | |
| 431 | + return $res; | |
| 432 | + } | |
| 433 | + | |
| 434 | + public function getQueue() | |
| 435 | + { | |
| 436 | + $sql = 'SELECT * FROM download_queue d ORDER BY date_added, code'; | |
| 437 | + $rows = DBUtil::getResultArray($sql); | |
| 438 | + | |
| 439 | + if(PEAR::isError($rows)){ | |
| 440 | + return $rows; | |
| 441 | + } | |
| 442 | + | |
| 443 | + $queue = array(); | |
| 444 | + foreach ($rows as $item){ | |
| 445 | + $queue[$item['code']][] = $item; | |
| 446 | + } | |
| 447 | + return $queue; | |
| 448 | + } | |
| 449 | + | |
| 450 | + public function getItemStatus($code) | |
| 451 | + { | |
| 452 | + $sql = array(); | |
| 453 | + $sql[] = 'SELECT status, errors FROM download_queue WHERE code = ?'; | |
| 454 | + $sql[] = $code; | |
| 455 | + $result = DBUtil::getResultArray($sql); | |
| 456 | + return $result; | |
| 457 | + } | |
| 458 | + | |
| 459 | + public function setItemStatus($code, $status = 1, $error = null) | |
| 460 | + { | |
| 461 | + $fields = array(); | |
| 462 | + $fields['status'] = $status; | |
| 463 | + $fields['errors'] = !empty($error) ? json_encode($error) : null; | |
| 464 | + $where = array('code' => $code); | |
| 465 | + $res = DBUtil::whereUpdate('download_queue', $fields, $where); | |
| 466 | + return $res; | |
| 467 | + } | |
| 468 | + | |
| 469 | + public function processQueue() | |
| 470 | + { | |
| 471 | + global $default; | |
| 472 | + | |
| 473 | + // get items from queue | |
| 474 | + $queue = $this->getQueue(); | |
| 475 | + if(PEAR::isError($queue)){ | |
| 476 | + $default->log->debug('Download Queue: error on fetching queue - '.$queue->getMessage()); | |
| 477 | + return false; | |
| 478 | + } | |
| 479 | + | |
| 480 | + // Loop through items and create downloads | |
| 481 | + foreach ($queue as $code => $download){ | |
| 482 | + // reset the error messages | |
| 483 | + $this->errors = null; | |
| 484 | + | |
| 485 | + // if the user_id is not set then skip | |
| 486 | + if(!isset($download[0]['user_id']) || empty($download[0]['user_id'])){ | |
| 487 | + $default->log->debug('Download Queue: no user id set for download code '.$code); | |
| 488 | + $error = array(_kt('No user id has been set, the archive cannot be created.')); | |
| 489 | + $result = $this->setItemStatus($code, 2, $error); | |
| 490 | + continue; | |
| 491 | + } | |
| 492 | + | |
| 493 | + // Force a session for the user | |
| 494 | + $_SESSION['userID'] = $download[0]['user_id']; | |
| 495 | + $baseFolderId = $download[0]['folder_id']; | |
| 496 | + | |
| 497 | + // Create a new instance of the archival class | |
| 498 | + $zip = new ZipFolder('', $code); | |
| 499 | + $res = $zip->checkConvertEncoding(); | |
| 500 | + | |
| 501 | + if(PEAR::isError($res)){ | |
| 502 | + $default->log->error('Download Queue: Archive class check convert encoding error - '.$res->getMessage()); | |
| 503 | + $error = array(_kt('The archive cannot be created. An error occurred in the encoding.')); | |
| 504 | + $result = $this->setItemStatus($code, 2, $error); | |
| 505 | + continue; | |
| 506 | + } | |
| 507 | + | |
| 508 | + $default->log->debug('Download Queue: Creating download for user: '.$_SESSION['userID'].', code: '.$code); | |
| 509 | + | |
| 510 | + DBUtil::startTransaction(); | |
| 511 | + | |
| 512 | + // Add the individual files and folders into the archive | |
| 513 | + foreach ($download as $item){ | |
| 514 | + if($item['object_type'] == 'document'){ | |
| 515 | + $docId = $item['object_id']; | |
| 516 | + $this->addDocument($zip, $docId); | |
| 517 | + } | |
| 518 | + if($item['object_type'] == 'folder'){ | |
| 519 | + $folderId = $item['object_id']; | |
| 520 | + $this->addFolder($zip, $folderId); | |
| 521 | + } | |
| 522 | + } | |
| 523 | + | |
| 524 | + $res = $zip->createZipFile(); | |
| 525 | + | |
| 526 | + if(PEAR::isError($res)){ | |
| 527 | + $default->log->debug('Download Queue: Archive could not be created. Exiting transaction. '.$res->getMessage()); | |
| 528 | + DBUtil::rollback(); | |
| 529 | + | |
| 530 | + $error = array(_kt('The archive could not be created.')); | |
| 531 | + $result = $this->setItemStatus($code, 2, $error); | |
| 532 | + continue; | |
| 533 | + } | |
| 534 | + | |
| 535 | + $default->log->debug('Download Queue: Archival successful'); | |
| 536 | + | |
| 537 | + $oTransaction = KTFolderTransaction::createFromArray(array( | |
| 538 | + 'folderid' => $baseFolderId, | |
| 539 | + 'comment' => "Bulk export", | |
| 540 | + 'transactionNS' => 'ktstandard.transactions.bulk_export', | |
| 541 | + 'userid' => $_SESSION['userID'], | |
| 542 | + 'ip' => Session::getClientIP(), | |
| 543 | + )); | |
| 544 | + | |
| 545 | + if(PEAR::isError($oTransaction)){ | |
| 546 | + $default->log->debug('Download Queue: transaction could not be logged. '.$oTransaction->getMessage()); | |
| 547 | + } | |
| 548 | + | |
| 549 | + DBUtil::commit(); | |
| 550 | + | |
| 551 | + // Set status for the download | |
| 552 | + $this->errors['archive'] = $_SESSION['zipcompression']; | |
| 553 | + $result = $this->setItemStatus($code, 1, $this->errors); | |
| 554 | + if(PEAR::isError($result)){ | |
| 555 | + $default->log->error('Download Queue: item status could not be set for user: '.$_SESSION['userID'].', code: '.$code.', error: '.$result->getMessage()); | |
| 556 | + } | |
| 557 | + // reset the error messages | |
| 558 | + $this->errors = null; | |
| 559 | + $_SESSION['zipcompression'] = null; | |
| 560 | + } | |
| 561 | + } | |
| 562 | + | |
| 563 | + public function addDocument(&$zip, $docId) | |
| 564 | + { | |
| 565 | + | |
| 566 | + $oDocument = Document::get($docId); | |
| 567 | + if(PEAR::isError($oDocument)){ | |
| 568 | + $this->errors[] = _kt('Document cannot be exported, an error occurred: ').$oDocument->getMessage(); | |
| 569 | + return $oDocument; | |
| 570 | + } | |
| 571 | + | |
| 572 | + if ($this->bNoisy) { | |
| 573 | + $oDocumentTransaction = new DocumentTransaction($oDocument, "Document part of bulk export", 'ktstandard.transactions.bulk_export', array()); | |
| 574 | + $oDocumentTransaction->create(); | |
| 575 | + } | |
| 576 | + | |
| 577 | + // fire subscription alerts for the downloaded document - if global config is set | |
| 578 | + if($this->bNotifications){ | |
| 579 | + $oSubscriptionEvent = new SubscriptionEvent(); | |
| 580 | + $oFolder = Folder::get($oDocument->getFolderID()); | |
| 581 | + $oSubscriptionEvent->DownloadDocument($oDocument, $oFolder); | |
| 582 | + } | |
| 583 | + | |
| 584 | + return $zip->addDocumentToZip($oDocument); | |
| 585 | + } | |
| 586 | + | |
| 587 | + public function addFolder(&$zip, $folderId) | |
| 588 | + { | |
| 589 | + $oFolder = Folder::get($folderId); | |
| 590 | + | |
| 591 | + if(PEAR::isError($oFolder)){ | |
| 592 | + $this->errors[] = _kt('Folder cannot be exported, an error occurred: ').$oFolder->getMessage(); | |
| 593 | + return $oFolder; | |
| 594 | + } | |
| 595 | + | |
| 596 | + $sFolderDocs = $oFolder->getDocumentIDs($folderId); | |
| 597 | + if(PEAR::isError($sFolderDocs)){ | |
| 598 | + $default->log->error('Download Queue: get document ids for folder caused an error: '.$sFolderDocs->getMessage()); | |
| 599 | + $sFolderDocs = ''; | |
| 600 | + } | |
| 601 | + | |
| 602 | + // Add folder to zip | |
| 603 | + $zip->addFolderToZip($oFolder); | |
| 604 | + | |
| 605 | + $aDocuments = array(); | |
| 606 | + if(!empty($sFolderDocs)){ | |
| 607 | + $aDocuments = explode(',', $sFolderDocs); | |
| 608 | + } | |
| 609 | + | |
| 610 | + // Get all the folders within the current folder | |
| 611 | + $sWhereClause = "parent_folder_ids like '%,{$folderId}' | |
| 612 | + OR parent_folder_ids like '%,{$folderId},%' | |
| 613 | + OR parent_id = {$folderId}"; | |
| 614 | + | |
| 615 | + $aFolderList = $oFolder->getList($sWhereClause); | |
| 616 | + $aLinkingFolders = $this->getLinkingEntities($aFolderList); | |
| 617 | + $aFolderList = array_merge($aFolderList,$aLinkingFolders); | |
| 618 | + | |
| 619 | + $aFolderObjects = array(); | |
| 620 | + $aFolderObjects[$folderId] = $oFolder; | |
| 621 | + | |
| 622 | + // Export the folder structure to ensure the export of empty directories | |
| 623 | + if(!empty($aFolderList)){ | |
| 624 | + foreach($aFolderList as $k => $oFolderItem){ | |
| 625 | + if($oFolderItem->isSymbolicLink()){ | |
| 626 | + $oFolderItem = $oFolderItem->getLinkedFolder(); | |
| 627 | + } | |
| 628 | + if(Permission::userHasFolderReadPermission($oFolderItem)){ | |
| 629 | + // Get documents for each folder | |
| 630 | + $sFolderItemId = $oFolderItem->getID(); | |
| 631 | + $sFolderItemDocs = $oFolderItem->getDocumentIDs($sFolderItemId); | |
| 632 | + | |
| 633 | + if(!empty($sFolderItemDocs)){ | |
| 634 | + $aFolderDocs = explode(',', $sFolderItemDocs); | |
| 635 | + $aDocuments = array_merge($aDocuments, $aFolderDocs); | |
| 636 | + } | |
| 637 | + $zip->addFolderToZip($oFolderItem); | |
| 638 | + $aFolderObjects[$oFolderItem->getId()] = $oFolderItem; | |
| 639 | + } | |
| 640 | + } | |
| 641 | + } | |
| 642 | + | |
| 643 | + // Add all documents to the export | |
| 644 | + if(!empty($aDocuments)){ | |
| 645 | + foreach($aDocuments as $sDocumentId){ | |
| 646 | + $oDocument = Document::get($sDocumentId); | |
| 647 | + if($oDocument->isSymbolicLink()){ | |
| 648 | + $oDocument->switchToLinkedCore(); | |
| 649 | + } | |
| 650 | + if(Permission::userHasDocumentReadPermission($oDocument)){ | |
| 651 | + | |
| 652 | + if(!KTWorkflowUtil::actionEnabledForDocument($oDocument, 'ktcore.actions.document.view')){ | |
| 653 | + $this->errors[] = $oDocument->getName().': '._kt('Document cannot be exported as it is restricted by the workflow.'); | |
| 654 | + continue; | |
| 655 | + } | |
| 656 | + | |
| 657 | + $sDocFolderId = $oDocument->getFolderID(); | |
| 658 | + $oFolder = isset($aFolderObjects[$sDocFolderId]) ? $aFolderObjects[$sDocFolderId] : Folder::get($sDocFolderId); | |
| 659 | + | |
| 660 | + if ($this->bNoisy) { | |
| 661 | + $oDocumentTransaction = new DocumentTransaction($oDocument, "Document part of bulk export", 'ktstandard.transactions.bulk_export', array()); | |
| 662 | + $oDocumentTransaction->create(); | |
| 663 | + } | |
| 664 | + | |
| 665 | + // fire subscription alerts for the downloaded document | |
| 666 | + if($this->bNotifications){ | |
| 667 | + $oSubscriptionEvent = new SubscriptionEvent(); | |
| 668 | + $oSubscriptionEvent->DownloadDocument($oDocument, $oFolder); | |
| 669 | + } | |
| 670 | + | |
| 671 | + $zip->addDocumentToZip($oDocument, $oFolder); | |
| 672 | + } | |
| 673 | + } | |
| 674 | + } | |
| 675 | + } | |
| 676 | + | |
| 677 | + function getLinkingEntities($aFolderList){ | |
| 678 | + $aSearchFolders = array(); | |
| 679 | + if(!empty($aFolderList)){ | |
| 680 | + foreach($aFolderList as $oFolderItem){ | |
| 681 | + if(Permission::userHasFolderReadPermission($oFolderItem)){ | |
| 682 | + // If it is a shortcut, we should do some more searching | |
| 683 | + if($oFolderItem->isSymbolicLink()){ | |
| 684 | + $oFolderItem = $oFolderItem->getLinkedFolder(); | |
| 685 | + $aSearchFolders[] = $oFolderItem->getID(); | |
| 686 | + } | |
| 687 | + } | |
| 688 | + } | |
| 689 | + } | |
| 690 | + $aLinkingFolders = array(); | |
| 691 | + $aSearchCompletedFolders = array(); | |
| 692 | + $count = 0; | |
| 693 | + while(count($aSearchFolders)>0){ | |
| 694 | + $count++; | |
| 695 | + $oFolder = Folder::get(array_pop($aSearchFolders)); | |
| 696 | + $folderId = $oFolder->getId(); | |
| 697 | + // Get all the folders within the current folder | |
| 698 | + $sWhereClause = "parent_folder_ids = '{$folderId}' OR | |
| 699 | + parent_folder_ids LIKE '{$folderId},%' OR | |
| 700 | + parent_folder_ids LIKE '%,{$folderId},%' OR | |
| 701 | + parent_folder_ids LIKE '%,{$folderId}'"; | |
| 702 | + $aFolderList = $this->oFolder->getList($sWhereClause); | |
| 703 | + foreach($aFolderList as $oFolderItem){ | |
| 704 | + if($oFolderItem->isSymbolicLink()){ | |
| 705 | + $oFolderItem = $oFolderItem->getLinkedFolder(); | |
| 706 | + } | |
| 707 | + if(Permission::userHasFolderReadPermission($oFolderItem)){ | |
| 708 | + if($aSearchCompletedFolders[$oFolderItem->getID()] != true){ | |
| 709 | + $aSearchFolders[] = $oFolderItem->getID(); | |
| 710 | + $aSearchCompletedFolders[$oFolderItem->getID()] = true; | |
| 711 | + } | |
| 712 | + } | |
| 713 | + } | |
| 714 | + if(!isset($aLinkingFolders[$oFolder->getId()])){ | |
| 715 | + $aLinkingFolders[$oFolder->getId()] = $oFolder; | |
| 716 | + } | |
| 717 | + } | |
| 718 | + return $aLinkingFolders; | |
| 719 | + } | |
| 720 | + | |
| 721 | + public function isDownloadAvailable($code) | |
| 722 | + { | |
| 723 | + $check = $this->getItemStatus($code); | |
| 724 | + $status = $check[0]['status']; | |
| 725 | + | |
| 726 | + if($status < 1){ | |
| 727 | + return false; | |
| 728 | + } | |
| 729 | + | |
| 730 | + $message = $check[0]['errors']; | |
| 731 | + $message = json_decode($message, true); | |
| 732 | + | |
| 733 | + if($status > 1){ | |
| 734 | + return $message; | |
| 735 | + } | |
| 736 | + | |
| 737 | + // Create the archive session variables | |
| 738 | + $_SESSION['zipcompression'] = $message['archive']; | |
| 739 | + unset($message['archive']); | |
| 740 | + | |
| 741 | + // Check that the archive has been created | |
| 742 | + $zip = new ZipFolder('', $code); | |
| 743 | + if($zip->checkArchiveExists($code)){ | |
| 744 | + // Clean up the download queue and return errors | |
| 745 | + $this->removeItem($code); | |
| 746 | + return $message; | |
| 747 | + } | |
| 748 | + return false; | |
| 749 | + } | |
| 750 | + | |
| 751 | + public function isLocked() | |
| 752 | + { | |
| 753 | + return false; | |
| 754 | + } | |
| 304 | 755 | } |
| 305 | 756 | ?> | ... | ... |
lib/foldermanagement/downloadTask.php
0 → 100644
| 1 | +<?php | |
| 2 | + | |
| 3 | +chdir(dirname(__FILE__)); | |
| 4 | +require_once('../../config/dmsDefaults.php'); | |
| 5 | +require_once('compressionArchiveUtil.inc.php'); | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * The download task provides 2 pieces of functionality. The first is to process the download queue and the second is to "ping" the download queue. | |
| 9 | + * The processing of the download queue will create the archives for the bulk download action and set a flag to indicate their completion. | |
| 10 | + * The "ping" will check if the flag has been set and inform the user that the archive is ready for download. | |
| 11 | + */ | |
| 12 | + | |
| 13 | +$queue = new DownloadQueue(); | |
| 14 | + | |
| 15 | +// Check for a ping then check if the download is finished | |
| 16 | +$ping = isset($_POST['ping']) ? $_POST['ping'] : false; | |
| 17 | +if($ping == 'ping'){ | |
| 18 | + | |
| 19 | + $code = $_POST['code']; | |
| 20 | + $status = $queue->isDownloadAvailable($code); | |
| 21 | + | |
| 22 | + if($status === false){ | |
| 23 | + echo 'wait'; | |
| 24 | + }else{ | |
| 25 | + $str = ''; | |
| 26 | + // display any error messages | |
| 27 | + if(!empty($status)){ | |
| 28 | + $str = '<div><b>'._kt('The following errors occurred during the download').': </><br />'; | |
| 29 | + $str .= '<table style="padding-top: 5px;" cellspacing="0" cellpadding="5">'; | |
| 30 | + foreach ($status as $msg){ | |
| 31 | + $str .= '<tr><td style="border: 1px #888 solid;">'.$msg.'</td></tr>'; | |
| 32 | + } | |
| 33 | + $str .= '</table></div>'; | |
| 34 | + } | |
| 35 | + echo $str; | |
| 36 | + } | |
| 37 | + exit(0); | |
| 38 | +} | |
| 39 | + | |
| 40 | +if($queue->isLocked()){ | |
| 41 | + exit(0); | |
| 42 | +} | |
| 43 | +// Not a ping, process the queue | |
| 44 | +$queue->processQueue(); | |
| 45 | + | |
| 46 | +exit(0); | |
| 47 | +?> | |
| 0 | 48 | \ No newline at end of file | ... | ... |
plugins/ktcore/KTBulkActions.php
| ... | ... | @@ -410,12 +410,12 @@ class KTBulkMoveAction extends KTBulkAction { |
| 410 | 410 | if(is_a($oEntity, 'Folder')) { |
| 411 | 411 | $aDocuments = array(); |
| 412 | 412 | $aChildFolders = array(); |
| 413 | - | |
| 413 | + | |
| 414 | 414 | $oFolder = $oEntity; |
| 415 | 415 | |
| 416 | 416 | // Get folder id |
| 417 | 417 | $sFolderId = $oFolder->getID(); |
| 418 | - | |
| 418 | + | |
| 419 | 419 | // Get documents in folder |
| 420 | 420 | $sDocuments = $oFolder->getDocumentIDs($sFolderId); |
| 421 | 421 | $aDocuments = (!empty($sDocuments)) ? explode(',', $sDocuments) : array(); |
| ... | ... | @@ -645,7 +645,7 @@ class KTBulkCopyAction extends KTBulkAction { |
| 645 | 645 | return PEAR::raiseError(_kt('Document cannot be copied')); |
| 646 | 646 | } |
| 647 | 647 | } |
| 648 | - | |
| 648 | + | |
| 649 | 649 | if(is_a($oEntity, 'Folder')) { |
| 650 | 650 | $aDocuments = array(); |
| 651 | 651 | $aChildFolders = array(); |
| ... | ... | @@ -1068,18 +1068,42 @@ class KTBrowseBulkExportAction extends KTBulkAction { |
| 1068 | 1068 | return PEAR::raiseError(_kt('Document cannot be exported as it is restricted by the workflow.')); |
| 1069 | 1069 | } |
| 1070 | 1070 | } |
| 1071 | + | |
| 1071 | 1072 | return parent::check_entity($oEntity); |
| 1072 | 1073 | } |
| 1073 | 1074 | |
| 1074 | - | |
| 1075 | 1075 | function do_performaction() { |
| 1076 | 1076 | |
| 1077 | - $folderName = $this->oFolder->getName(); | |
| 1078 | - $this->oZip = new ZipFolder($folderName); | |
| 1079 | - $res = $this->oZip->checkConvertEncoding(); | |
| 1077 | + $config = KTConfig::getSingleton(); | |
| 1078 | + $useQueue = $config->get('export/useDownloadQueue', true); | |
| 1079 | + | |
| 1080 | + // Create the export code | |
| 1081 | + $this->sExportCode = KTUtil::randomString(); | |
| 1082 | + $_SESSION['exportcode'] = $this->sExportCode; | |
| 1080 | 1083 | |
| 1081 | 1084 | $folderurl = $this->getReturnUrl(); |
| 1082 | 1085 | $sReturn = sprintf('<p>' . _kt('Return to the original <a href="%s">folder</a>') . "</p>\n", $folderurl); |
| 1086 | + $download_url = KTUtil::addQueryStringSelf("action=downloadZipFile&fFolderId={$this->oFolder->getId()}&exportcode={$this->sExportCode}"); | |
| 1087 | + | |
| 1088 | + if($useQueue){ | |
| 1089 | + $result = parent::do_performaction(); | |
| 1090 | + | |
| 1091 | + $url = KTUtil::kt_url() . '/lib/foldermanagement/downloadTask.php'; | |
| 1092 | + | |
| 1093 | + $oTemplating =& KTTemplating::getSingleton(); | |
| 1094 | + $oTemplate = $oTemplating->loadTemplate('ktcore/action/bulk_download'); | |
| 1095 | + | |
| 1096 | + $aParams = array( | |
| 1097 | + 'return' => $sReturn, | |
| 1098 | + 'url' => $url, | |
| 1099 | + 'code' => $this->sExportCode, | |
| 1100 | + 'download_url' => $download_url | |
| 1101 | + ); | |
| 1102 | + return $oTemplate->render($aParams); | |
| 1103 | + } | |
| 1104 | + | |
| 1105 | + $this->oZip = new ZipFolder('', $this->sExportCode); | |
| 1106 | + $res = $this->oZip->checkConvertEncoding(); | |
| 1083 | 1107 | |
| 1084 | 1108 | if(PEAR::isError($res)){ |
| 1085 | 1109 | $this->addErrorMessage($res->getMessage()); |
| ... | ... | @@ -1087,16 +1111,14 @@ class KTBrowseBulkExportAction extends KTBulkAction { |
| 1087 | 1111 | } |
| 1088 | 1112 | |
| 1089 | 1113 | $this->startTransaction(); |
| 1090 | - $oKTConfig =& KTConfig::getSingleton(); | |
| 1091 | - $this->bNoisy = $oKTConfig->get("tweaks/noisyBulkOperations"); | |
| 1092 | - | |
| 1093 | - $this->bNotifications = ($oKTConfig->get('export/enablenotifications', 'on') == 'on') ? true : false; | |
| 1094 | 1114 | |
| 1095 | 1115 | $result = parent::do_performaction(); |
| 1116 | + | |
| 1096 | 1117 | $sExportCode = $this->oZip->createZipFile(); |
| 1097 | 1118 | |
| 1098 | 1119 | if(PEAR::isError($sExportCode)){ |
| 1099 | 1120 | $this->addErrorMessage($sExportCode->getMessage()); |
| 1121 | + $this->rollbackTransaction(); | |
| 1100 | 1122 | return $sReturn; |
| 1101 | 1123 | } |
| 1102 | 1124 | |
| ... | ... | @@ -1110,23 +1132,28 @@ class KTBrowseBulkExportAction extends KTBulkAction { |
| 1110 | 1132 | |
| 1111 | 1133 | $this->commitTransaction(); |
| 1112 | 1134 | |
| 1113 | - $url = KTUtil::addQueryStringSelf(sprintf('action=downloadZipFile&fFolderId=%d&exportcode=%s', $this->oFolder->getId(), $sExportCode)); | |
| 1114 | - $str = sprintf('<p>' . _kt('Your download will begin shortly. If you are not automatically redirected to your download, please click <a href="%s">here</a> ') . "</p>\n", $url); | |
| 1135 | + $str = sprintf('<p>' . _kt('Your download will begin shortly. If you are not automatically redirected to your download, please click <a href="%s">here</a> ') . "</p>\n", $download_url); | |
| 1115 | 1136 | $str .= sprintf('<p>' . _kt('Once your download is complete, click <a href="%s">here</a> to return to the original folder') . "</p>\n", $folderurl); |
| 1116 | - //$str .= sprintf("</div></div></body></html>\n"); | |
| 1117 | 1137 | $str .= sprintf('<script language="JavaScript"> |
| 1118 | 1138 | function kt_bulkexport_redirect() { |
| 1119 | - document.location.href = "%s"; | |
| 1139 | + document.location.href = "%s"; | |
| 1120 | 1140 | } |
| 1121 | - callLater(2, kt_bulkexport_redirect); | |
| 1122 | - | |
| 1123 | - </script>', $url); | |
| 1141 | + callLater(5, kt_bulkexport_redirect); | |
| 1142 | + </script>', $download_url); | |
| 1124 | 1143 | |
| 1125 | 1144 | return $str; |
| 1126 | 1145 | } |
| 1127 | 1146 | |
| 1128 | 1147 | function perform_action($oEntity) { |
| 1129 | 1148 | |
| 1149 | + $exportCode = $_SESSION['exportcode']; | |
| 1150 | + $this->oZip = ZipFolder::get($exportCode); | |
| 1151 | + | |
| 1152 | + $oQueue = new DownloadQueue(); | |
| 1153 | + | |
| 1154 | + $config = KTConfig::getSingleton(); | |
| 1155 | + $useQueue = $config->get('export/useDownloadQueue'); | |
| 1156 | + | |
| 1130 | 1157 | if(is_a($oEntity, 'Document')) { |
| 1131 | 1158 | |
| 1132 | 1159 | $oDocument = $oEntity; |
| ... | ... | @@ -1134,19 +1161,12 @@ class KTBrowseBulkExportAction extends KTBulkAction { |
| 1134 | 1161 | $oDocument->switchToLinkedCore(); |
| 1135 | 1162 | } |
| 1136 | 1163 | |
| 1137 | - if ($this->bNoisy) { | |
| 1138 | - $oDocumentTransaction = new DocumentTransaction($oDocument, "Document part of bulk export", 'ktstandard.transactions.bulk_export', array()); | |
| 1139 | - $oDocumentTransaction->create(); | |
| 1140 | - } | |
| 1141 | - | |
| 1142 | - // fire subscription alerts for the downloaded document - if global config is set | |
| 1143 | - if($this->bNotifications){ | |
| 1144 | - $oSubscriptionEvent = new SubscriptionEvent(); | |
| 1145 | - $oFolder = Folder::get($oDocument->getFolderID()); | |
| 1146 | - $oSubscriptionEvent->DownloadDocument($oDocument, $oFolder); | |
| 1147 | - } | |
| 1164 | + if($useQueue){ | |
| 1165 | + DownloadQueue::addItem($this->sExportCode, $this->oFolder->getId(), $oDocument->iId, 'document'); | |
| 1166 | + }else{ | |
| 1167 | + $oQueue->addDocument($this->oZip, $oDocument->iId); | |
| 1168 | + } | |
| 1148 | 1169 | |
| 1149 | - $this->oZip->addDocumentToZip($oDocument); | |
| 1150 | 1170 | |
| 1151 | 1171 | }else if(is_a($oEntity, 'Folder')) { |
| 1152 | 1172 | $aDocuments = array(); |
| ... | ... | @@ -1156,79 +1176,11 @@ class KTBrowseBulkExportAction extends KTBulkAction { |
| 1156 | 1176 | $oFolder = $oFolder->getLinkedFolder(); |
| 1157 | 1177 | } |
| 1158 | 1178 | $sFolderId = $oFolder->getId(); |
| 1159 | - $sFolderDocs = $oFolder->getDocumentIDs($sFolderId); | |
| 1160 | - | |
| 1161 | - // Add folder to zip | |
| 1162 | - $this->oZip->addFolderToZip($oFolder); | |
| 1163 | 1179 | |
| 1164 | - if(!empty($sFolderDocs)){ | |
| 1165 | - $aDocuments = explode(',', $sFolderDocs); | |
| 1166 | - } | |
| 1167 | - | |
| 1168 | - // Get all the folders within the current folder | |
| 1169 | - $sWhereClause = "parent_folder_ids = '{$sFolderId}' OR | |
| 1170 | - parent_folder_ids LIKE '{$sFolderId},%' OR | |
| 1171 | - parent_folder_ids LIKE '%,{$sFolderId},%' OR | |
| 1172 | - parent_folder_ids LIKE '%,{$sFolderId}'"; | |
| 1173 | - $aFolderList = $this->oFolder->getList($sWhereClause); | |
| 1174 | - $aLinkingFolders = $this->getLinkingEntities($aFolderList); | |
| 1175 | - $aFolderList = array_merge($aFolderList,$aLinkingFolders); | |
| 1176 | - | |
| 1177 | - $aFolderObjects = array(); | |
| 1178 | - $aFolderObjects[$sFolderId] = $oFolder; | |
| 1179 | - | |
| 1180 | - // Export the folder structure to ensure the export of empty directories | |
| 1181 | - if(!empty($aFolderList)){ | |
| 1182 | - foreach($aFolderList as $k => $oFolderItem){ | |
| 1183 | - if($oFolderItem->isSymbolicLink()){ | |
| 1184 | - $oFolderItem = $oFolderItem->getLinkedFolder(); | |
| 1185 | - } | |
| 1186 | - if(Permission::userHasFolderReadPermission($oFolderItem)){ | |
| 1187 | - // Get documents for each folder | |
| 1188 | - $sFolderItemId = $oFolderItem->getID(); | |
| 1189 | - $sFolderItemDocs = $oFolderItem->getDocumentIDs($sFolderItemId); | |
| 1190 | - | |
| 1191 | - if(!empty($sFolderItemDocs)){ | |
| 1192 | - $aFolderDocs = explode(',', $sFolderItemDocs); | |
| 1193 | - $aDocuments = array_merge($aDocuments, $aFolderDocs); | |
| 1194 | - } | |
| 1195 | - $this->oZip->addFolderToZip($oFolderItem); | |
| 1196 | - $aFolderObjects[$oFolderItem->getId()] = $oFolderItem; | |
| 1197 | - } | |
| 1198 | - } | |
| 1199 | - } | |
| 1200 | - | |
| 1201 | - // Add all documents to the export | |
| 1202 | - if(!empty($aDocuments)){ | |
| 1203 | - foreach($aDocuments as $sDocumentId){ | |
| 1204 | - $oDocument = Document::get($sDocumentId); | |
| 1205 | - if($oDocument->isSymbolicLink()){ | |
| 1206 | - $oDocument->switchToLinkedCore(); | |
| 1207 | - } | |
| 1208 | - if(Permission::userHasDocumentReadPermission($oDocument)){ | |
| 1209 | - | |
| 1210 | - if(!KTWorkflowUtil::actionEnabledForDocument($oDocument, 'ktcore.actions.document.view')){ | |
| 1211 | - $this->addErrorMessage($oDocument->getName().': '._kt('Document cannot be exported as it is restricted by the workflow.')); | |
| 1212 | - continue; | |
| 1213 | - } | |
| 1214 | - | |
| 1215 | - $sDocFolderId = $oDocument->getFolderID(); | |
| 1216 | - $oFolder = isset($aFolderObjects[$sDocFolderId]) ? $aFolderObjects[$sDocFolderId] : Folder::get($sDocFolderId); | |
| 1217 | - | |
| 1218 | - if ($this->bNoisy) { | |
| 1219 | - $oDocumentTransaction = new DocumentTransaction($oDocument, "Document part of bulk export", 'ktstandard.transactions.bulk_export', array()); | |
| 1220 | - $oDocumentTransaction->create(); | |
| 1221 | - } | |
| 1222 | - | |
| 1223 | - // fire subscription alerts for the downloaded document | |
| 1224 | - if($this->bNotifications){ | |
| 1225 | - $oSubscriptionEvent = new SubscriptionEvent(); | |
| 1226 | - $oSubscriptionEvent->DownloadDocument($oDocument, $oFolder); | |
| 1227 | - } | |
| 1228 | - | |
| 1229 | - $this->oZip->addDocumentToZip($oDocument, $oFolder); | |
| 1230 | - } | |
| 1231 | - } | |
| 1180 | + if($useQueue){ | |
| 1181 | + DownloadQueue::addItem($this->sExportCode, $this->oFolder->getId(), $sFolderId, 'folder'); | |
| 1182 | + }else{ | |
| 1183 | + $oQueue->addFolder($this->oZip, $sFolderId); | |
| 1232 | 1184 | } |
| 1233 | 1185 | } |
| 1234 | 1186 | return true; |
| ... | ... | @@ -1237,9 +1189,7 @@ class KTBrowseBulkExportAction extends KTBulkAction { |
| 1237 | 1189 | function do_downloadZipFile() { |
| 1238 | 1190 | $sCode = $this->oValidator->validateString($_REQUEST['exportcode']); |
| 1239 | 1191 | |
| 1240 | - $folderName = $this->oFolder->getName(); | |
| 1241 | - $this->oZip = new ZipFolder($folderName, $sCode); | |
| 1242 | - | |
| 1192 | + $this->oZip = new ZipFolder('', $sCode); | |
| 1243 | 1193 | $res = $this->oZip->downloadZipFile($sCode); |
| 1244 | 1194 | |
| 1245 | 1195 | if(PEAR::isError($res)){ | ... | ... |
plugins/ktstandard/KTBulkExportPlugin.php
| ... | ... | @@ -71,20 +71,44 @@ class KTBulkExportAction extends KTFolderAction { |
| 71 | 71 | } |
| 72 | 72 | |
| 73 | 73 | function do_main() { |
| 74 | - $folderName = $this->oFolder->getName(); | |
| 75 | - $this->oZip = new ZipFolder($folderName); | |
| 74 | + $config = KTConfig::getSingleton(); | |
| 75 | + $useQueue = $config->get('export/useDownloadQueue', true); | |
| 76 | + | |
| 77 | + // Create the export code | |
| 78 | + $exportCode = KTUtil::randomString(); | |
| 79 | + $this->oZip = new ZipFolder('', $exportCode); | |
| 76 | 80 | |
| 77 | 81 | if(!$this->oZip->checkConvertEncoding()) { |
| 78 | 82 | redirect(KTBrowseUtil::getUrlForFolder($this->oFolder)); |
| 79 | 83 | exit(0); |
| 80 | 84 | } |
| 81 | 85 | |
| 82 | - $oKTConfig =& KTConfig::getSingleton(); | |
| 83 | - $bNoisy = $oKTConfig->get("tweaks/noisyBulkOperations"); | |
| 84 | - $bNotifications = ($oKTConfig->get('export/enablenotifications', 'on') == 'on') ? true : false; | |
| 86 | + $bNoisy = $config->get("tweaks/noisyBulkOperations"); | |
| 87 | + $bNotifications = ($config->get('export/enablenotifications', 'on') == 'on') ? true : false; | |
| 85 | 88 | |
| 86 | - // Get all folders and sub-folders | |
| 87 | 89 | $sCurrentFolderId = $this->oFolder->getId(); |
| 90 | + $url = KTUtil::addQueryStringSelf(sprintf('action=downloadZipFile&fFolderId=%d&exportcode=%s', $sCurrentFolderId, $exportCode)); | |
| 91 | + $folderurl = KTBrowseUtil::getUrlForFolder($this->oFolder); | |
| 92 | + $sReturn = '<p>' . _kt('Once your download is complete, click <a href="'.$folderurl.'">here</a> to return to the original folder') . "</p>\n"; | |
| 93 | + | |
| 94 | + if($useQueue){ | |
| 95 | + DownloadQueue::addItem($exportCode, $sCurrentFolderId, $sCurrentFolderId, 'folder'); | |
| 96 | + | |
| 97 | + $task_url = KTUtil::kt_url() . '/lib/foldermanagement/downloadTask.php'; | |
| 98 | + | |
| 99 | + $oTemplating =& KTTemplating::getSingleton(); | |
| 100 | + $oTemplate = $oTemplating->loadTemplate('ktcore/action/bulk_download'); | |
| 101 | + | |
| 102 | + $aParams = array( | |
| 103 | + 'return' => $sReturn, | |
| 104 | + 'url' => $task_url, | |
| 105 | + 'code' => $exportCode, | |
| 106 | + 'download_url' => $url | |
| 107 | + ); | |
| 108 | + return $oTemplate->render($aParams); | |
| 109 | + } | |
| 110 | + | |
| 111 | + // Get all folders and sub-folders | |
| 88 | 112 | $sWhereClause = "parent_folder_ids = '{$sCurrentFolderId}' OR |
| 89 | 113 | parent_folder_ids LIKE '{$sCurrentFolderId},%' OR |
| 90 | 114 | parent_folder_ids LIKE '%,{$sCurrentFolderId},%' OR |
| ... | ... | @@ -173,16 +197,14 @@ class KTBulkExportAction extends KTFolderAction { |
| 173 | 197 | 'ip' => Session::getClientIP(), |
| 174 | 198 | )); |
| 175 | 199 | |
| 176 | - $url = KTUtil::addQueryStringSelf(sprintf('action=downloadZipFile&fFolderId=%d&exportcode=%s', $this->oFolder->getId(), $sExportCode)); | |
| 177 | 200 | printf('<p>' . _kt('Your download will begin shortly. If you are not automatically redirected to your download, please click <a href="%s">here</a> ') . "</p>\n", $url); |
| 178 | - $folderurl = KTBrowseUtil::getUrlForFolder($this->oFolder); | |
| 179 | - printf('<p>' . _kt('Once your download is complete, click <a href="%s">here</a> to return to the original folder') . "</p>\n", $folderurl); | |
| 201 | + print($sReturn); | |
| 180 | 202 | printf("</div></div></body></html>\n"); |
| 181 | 203 | printf('<script language="JavaScript"> |
| 182 | 204 | function kt_bulkexport_redirect() { |
| 183 | 205 | document.location.href = "%s"; |
| 184 | 206 | } |
| 185 | - callLater(1, kt_bulkexport_redirect); | |
| 207 | + callLater(2, kt_bulkexport_redirect); | |
| 186 | 208 | |
| 187 | 209 | </script>', $url); |
| 188 | 210 | |
| ... | ... | @@ -230,8 +252,7 @@ class KTBulkExportAction extends KTFolderAction { |
| 230 | 252 | function do_downloadZipFile() { |
| 231 | 253 | $sCode = $this->oValidator->validateString($_REQUEST['exportcode']); |
| 232 | 254 | |
| 233 | - $folderName = $this->oFolder->getName(); | |
| 234 | - $this->oZip = new ZipFolder($folderName); | |
| 255 | + $this->oZip = new ZipFolder('', $sCode); | |
| 235 | 256 | |
| 236 | 257 | $res = $this->oZip->downloadZipFile($sCode); |
| 237 | 258 | ... | ... |
sql/mysql/install/data.sql
| ... | ... | @@ -287,7 +287,9 @@ INSERT INTO `config_settings` VALUES |
| 287 | 287 | (112, 'urls', 'Var Directory', 'The path to the var directory.', 'varDirectory', 'default', '${fileSystemRoot}/var', 'string', NULL, 1), |
| 288 | 288 | (113, 'tweaks','Increment version on rename','Defines whether to update the version number if a document filename is changed/renamed.','incrementVersionOnRename','default','true','boolean',NULL,1), |
| 289 | 289 | (114, 'ui', 'System URL', 'The system url, used in the main logo.', 'systemUrl', 'default', 'http://www.knowledgetree.com', 'string', '', 1), |
| 290 | -(115, 'ldapAuthentication', 'Allow Moving Users in LDAP/AD', 'Moving users around within the LDAP or Active Directory structure will cause failed logins for these users. When this setting is enabled, a failed login will trigger a search for the user using their sAMAccountName setting and update their authentication details.', 'enableLdapUpdate', 'default', 'false', 'boolean', NULL, 1); | |
| 290 | +(115, 'ldapAuthentication', 'Allow Moving Users in LDAP/AD', 'Moving users around within the LDAP or Active Directory structure will cause failed logins for these users. When this setting is enabled, a failed login will trigger a search for the user using their sAMAccountName setting and update their authentication details.', 'enableLdapUpdate', 'default', 'false', 'boolean', NULL, 1), | |
| 291 | +(116, 'export', 'Use External Zip Binary', 'Utilises the external zip binary for compressing archives. The default is to use the PEAR archive class.', 'useBinary', 'default', 'false', 'boolean', NULL, 1), | |
| 292 | +(117, 'export', 'Use Bulk Download Queue', 'The bulk download can be large and can prevent normal browsing. The download queue performs the bulk downloads in the background.', 'useDownloadQueue', 'default', 'true', 'boolean', NULL, 1); | |
| 291 | 293 | /*!40000 ALTER TABLE `config_settings` ENABLE KEYS */; |
| 292 | 294 | UNLOCK TABLES; |
| 293 | 295 | |
| ... | ... | @@ -1370,7 +1372,8 @@ INSERT INTO `scheduler_tasks` VALUES |
| 1370 | 1372 | (7,'Cleanup Temporary Directory','search2/bin/cronCleanup.php','',0,'1min','2007-10-01 00:00:00',NULL,0,'enabled'), |
| 1371 | 1373 | (8,'Disk Usage and Folder Utilisation Statistics','plugins/housekeeper/bin/UpdateStats.php','',0,'5mins','2007-10-01 00:00:00',NULL,0,'enabled'), |
| 1372 | 1374 | (9,'Refresh Index Statistics','search2/bin/cronIndexStats.php','',0,'1min','2007-10-01',NULL,0,'enabled'), |
| 1373 | -(10,'Refresh Resource Dependancies','search2/bin/cronResources.php','',0,'1min','2007-10-01',NULL,0,'enabled'); | |
| 1375 | +(10,'Refresh Resource Dependancies','search2/bin/cronResources.php','',0,'1min','2007-10-01',NULL,0,'enabled'), | |
| 1376 | +(11,'Bulk Download Queue','lib/foldermanagement/downloadTask.php','',0,'1min','2007-10-01',NULL,0,'system'); | |
| 1374 | 1377 | |
| 1375 | 1378 | /*!40000 ALTER TABLE `scheduler_tasks` ENABLE KEYS */; |
| 1376 | 1379 | UNLOCK TABLES; | ... | ... |
sql/mysql/install/structure.sql
| ... | ... | @@ -639,6 +639,22 @@ CREATE TABLE `download_files` ( |
| 639 | 639 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
| 640 | 640 | |
| 641 | 641 | -- |
| 642 | +-- Table structure for table `download_queue` | |
| 643 | +-- | |
| 644 | + | |
| 645 | +CREATE TABLE `download_queue` ( | |
| 646 | + `code` char(16) NOT NULL, | |
| 647 | + `folder_id` int(11) NOT NULL, | |
| 648 | + `object_id` int(11) NOT NULL, | |
| 649 | + `object_type` enum('document', 'folder') NOT NULL default 'folder', | |
| 650 | + `user_id` int(11) NOT NULL, | |
| 651 | + `date_added` timestamp NOT NULL default CURRENT_TIMESTAMP, | |
| 652 | + `status` tinyint(4) NOT NULL default 0, | |
| 653 | + `errors` mediumtext, | |
| 654 | + INDEX (`code`) | |
| 655 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8; | |
| 656 | + | |
| 657 | +-- | |
| 642 | 658 | -- Table structure for table `field_behaviour_options` |
| 643 | 659 | -- |
| 644 | 660 | ... | ... |
sql/mysql/upgrade/3.6/download_queue.sql
0 → 100644
| 1 | +-- | |
| 2 | +-- Table structure for table `download_queue` | |
| 3 | +-- | |
| 4 | + | |
| 5 | +CREATE TABLE `download_queue` ( | |
| 6 | + `code` char(16) NOT NULL, | |
| 7 | + `folder_id` int(11) NOT NULL, | |
| 8 | + `object_id` int(11) NOT NULL, | |
| 9 | + `object_type` enum('document', 'folder') NOT NULL default 'folder', | |
| 10 | + `user_id` int(11) NOT NULL, | |
| 11 | + `date_added` timestamp NOT NULL default CURRENT_TIMESTAMP, | |
| 12 | + `status` tinyint(4) NOT NULL default 0, | |
| 13 | + `errors` mediumtext, | |
| 14 | + INDEX (`code`) | |
| 15 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8; | |
| 16 | + | |
| 17 | +INSERT INTO `config_settings` (group_name, display_name, description, item, value, default_value, type, options, can_edit) | |
| 18 | +VALUES ('export', 'Use External Zip Binary', 'Utilises the external zip binary for compressing archives. The default is to use the PEAR archive class.', 'useBinary', 'default', 'false', 'boolean', NULL, 1), | |
| 19 | +('export', 'Use Bulk Download Queue', 'The bulk download can be large and can prevent normal browsing. The download queue performs the bulk downloads in | |
| 20 | +the background.', 'useDownloadQueue', 'default', 'true', 'boolean', NULL, 1); | |
| 21 | + | |
| 22 | +INSERT INTO `scheduler_tasks` (task, script_url, frequency, run_time, status) | |
| 23 | +VALUES ('Bulk Download Queue','lib/foldermanagement/downloadTask.php','1min','2007-10-01','system'); | |
| 0 | 24 | \ No newline at end of file | ... | ... |
templates/ktcore/action/bulk_download.smarty
0 → 100644
| 1 | +<p style='margin-bottom: 10px;'> | |
| 2 | +The documents you have selected are being compressed and archived, once the archive has been created a download link will be displayed. | |
| 3 | +<br /><b>Please do not close the page.</b> | |
| 4 | +</p> | |
| 5 | + | |
| 6 | + | |
| 7 | +<div id='download_link' style='visibility: hidden'> | |
| 8 | +<p style='margin-bottom: 5px;'> | |
| 9 | +Click <a href="{$download_url}">here</a> to download the archive. {$str} | |
| 10 | +</p> | |
| 11 | +<p style='margin-bottom: 10px;'> | |
| 12 | +{$return} | |
| 13 | +<br /> | |
| 14 | +</p> | |
| 15 | +</div> | |
| 16 | + | |
| 17 | + | |
| 18 | +<script type='text/javascript'> | |
| 19 | +{literal} | |
| 20 | +function ping() | |
| 21 | +{ | |
| 22 | + Ext.Ajax.request({ | |
| 23 | + url: {/literal}'{$url}'{literal}, | |
| 24 | + success: function(response) { | |
| 25 | + if(response.responseText == 'wait'){ | |
| 26 | + setTimeout("ping()", 2000); | |
| 27 | + return; | |
| 28 | + } | |
| 29 | + | |
| 30 | + var div = document.getElementById('download_link'); | |
| 31 | + div.style.visibility = 'visible'; | |
| 32 | + div.innerHTML = div.innerHTML + response.responseText; | |
| 33 | + }, | |
| 34 | + failure: function(response) { | |
| 35 | + alert({/literal}{i18n}'There was an error connecting to the server. Please refresh the page.'{/i18n}{literal}); | |
| 36 | + }, | |
| 37 | + params: { | |
| 38 | + ping: 'ping', | |
| 39 | + code: {/literal}'{$code}'{literal} | |
| 40 | + } | |
| 41 | + }); | |
| 42 | + | |
| 43 | +} | |
| 44 | +{/literal} | |
| 45 | + | |
| 46 | +ping(); | |
| 47 | +</script> | |
| 0 | 48 | \ No newline at end of file | ... | ... |