Commit ce268477f8cab496a2d185131fe8ecde46a90604

Authored by Megan
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
lib/foldermanagement/compressionArchiveUtil.inc.php
@@ -36,6 +36,8 @@ @@ -36,6 +36,8 @@
36 * 36 *
37 */ 37 */
38 38
  39 +require_once('File/Archive.php');
  40 +
39 /** 41 /**
40 * Class to create and download a zip file 42 * Class to create and download a zip file
41 */ 43 */
@@ -50,6 +52,8 @@ class ZipFolder { @@ -50,6 +52,8 @@ class ZipFolder {
50 var $aReplaceKeys = array(); 52 var $aReplaceKeys = array();
51 var $aReplaceValues = array(); 53 var $aReplaceValues = array();
52 var $sOutputEncoding = 'UTF-8'; 54 var $sOutputEncoding = 'UTF-8';
  55 + var $extension = 'zip';
  56 + var $exportCode = null;
53 57
54 /** 58 /**
55 * Constructor 59 * Constructor
@@ -57,22 +61,22 @@ class ZipFolder { @@ -57,22 +61,22 @@ class ZipFolder {
57 * @param string $sZipFileName The name of the zip file - gets ignored at the moment. 61 * @param string $sZipFileName The name of the zip file - gets ignored at the moment.
58 * @param string $exportCode The code to use if a zip file has already been created. 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 $this->oKTConfig =& KTConfig::getSingleton(); 65 $this->oKTConfig =& KTConfig::getSingleton();
62 $this->oStorage =& KTStorageManagerUtil::getSingleton(); 66 $this->oStorage =& KTStorageManagerUtil::getSingleton();
63 67
64 $this->sOutputEncoding = $this->oKTConfig->get('export/encoding', 'UTF-8'); 68 $this->sOutputEncoding = $this->oKTConfig->get('export/encoding', 'UTF-8');
  69 + $this->extension = $extension;
65 70
66 $this->sPattern = "[\*|\%|\\\|\/|\<|\>|\+|\:|\?|\||\'|\"]"; 71 $this->sPattern = "[\*|\%|\\\|\/|\<|\>|\+|\:|\?|\||\'|\"]";
67 $this->sFolderPattern = "[\*|\%|\<|\>|\+|\:|\?|\||\'|\"]"; 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 }else { 80 }else {
77 $sBasedir = $this->oKTConfig->get("urls/tmpDirectory"); 81 $sBasedir = $this->oKTConfig->get("urls/tmpDirectory");
78 $sTmpPath = tempnam($sBasedir, 'kt_compress_zip'); 82 $sTmpPath = tempnam($sBasedir, 'kt_compress_zip');
@@ -100,6 +104,15 @@ class ZipFolder { @@ -100,6 +104,15 @@ class ZipFolder {
100 $this->aReplaceValues = array_values($aReplace); 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 * Return the full path 117 * Return the full path
105 * 118 *
@@ -154,7 +167,7 @@ class ZipFolder { @@ -154,7 +167,7 @@ class ZipFolder {
154 167
155 $sOrigFile = $this->oStorage->temporaryFile($oDocument); 168 $sOrigFile = $this->oStorage->temporaryFile($oDocument);
156 $sFilename = $sParentFolder.'/'.$sDocName; 169 $sFilename = $sParentFolder.'/'.$sDocName;
157 - copy($sOrigFile, $sFilename); 170 + @copy($sOrigFile, $sFilename);
158 171
159 $this->aPaths[] = $sDocPath.'/'.$sDocName; 172 $this->aPaths[] = $sDocPath.'/'.$sDocName;
160 return true; 173 return true;
@@ -190,49 +203,62 @@ class ZipFolder { @@ -190,49 +203,62 @@ class ZipFolder {
190 return PEAR::raiseError(_kt("No folders or documents found to compress")); 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 // Set environment language to output character encoding 209 // Set environment language to output character encoding
194 $loc = $this->sOutputEncoding; 210 $loc = $this->sOutputEncoding;
195 putenv("LANG=$loc"); 211 putenv("LANG=$loc");
196 putenv("LANGUAGE=$loc"); 212 putenv("LANGUAGE=$loc");
197 $loc = setlocale(LC_ALL, $loc); 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 $sZipFile = str_replace('<', '', str_replace('</', '', str_replace('>', '', $sZipFile))); 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 "&nbsp;"; 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 "&nbsp;";
  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 // Save the zip file and path into session 259 // Save the zip file and path into session
234 $_SESSION['zipcompression'] = KTUtil::arrayGet($_SESSION, 'zipcompression', array()); 260 $_SESSION['zipcompression'] = KTUtil::arrayGet($_SESSION, 'zipcompression', array());
235 - $sExportCode = KTUtil::randomString(); 261 + $sExportCode = $this->exportCode;
236 $_SESSION['zipcompression'][$sExportCode] = array( 262 $_SESSION['zipcompression'][$sExportCode] = array(
237 'file' => $sZipFile, 263 'file' => $sZipFile,
238 'dir' => $this->sTmpPath, 264 'dir' => $this->sTmpPath,
@@ -262,18 +288,41 @@ class ZipFolder { @@ -262,18 +288,41 @@ class ZipFolder {
262 } 288 }
263 289
264 if (!file_exists($sZipFile)) { 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 $mimeType = 'application/zip; charset=utf-8;'; 295 $mimeType = 'application/zip; charset=utf-8;';
269 $fileSize = filesize($sZipFile); 296 $fileSize = filesize($sZipFile);
270 - $fileName = $this->sZipFileName . '.zip'; 297 + $fileName = $this->sZipFileName .'.'.$this->extension;
271 298
272 KTUtil::download($sZipFile, $mimeType, $fileSize, $fileName); 299 KTUtil::download($sZipFile, $mimeType, $fileSize, $fileName);
273 KTUtil::deleteDirectory($sTmpPath); 300 KTUtil::deleteDirectory($sTmpPath);
274 return true; 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 * Check that iconv exists and that the selected encoding is supported. 327 * Check that iconv exists and that the selected encoding is supported.
279 */ 328 */
@@ -301,5 +350,407 @@ class ZipFolder { @@ -301,5 +350,407 @@ class ZipFolder {
301 return iconv($this->sOutputEncoding, "UTF-8", $sMystring); 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 \ No newline at end of file 48 \ No newline at end of file
plugins/ktcore/KTBulkActions.php
@@ -410,12 +410,12 @@ class KTBulkMoveAction extends KTBulkAction { @@ -410,12 +410,12 @@ class KTBulkMoveAction extends KTBulkAction {
410 if(is_a($oEntity, 'Folder')) { 410 if(is_a($oEntity, 'Folder')) {
411 $aDocuments = array(); 411 $aDocuments = array();
412 $aChildFolders = array(); 412 $aChildFolders = array();
413 - 413 +
414 $oFolder = $oEntity; 414 $oFolder = $oEntity;
415 415
416 // Get folder id 416 // Get folder id
417 $sFolderId = $oFolder->getID(); 417 $sFolderId = $oFolder->getID();
418 - 418 +
419 // Get documents in folder 419 // Get documents in folder
420 $sDocuments = $oFolder->getDocumentIDs($sFolderId); 420 $sDocuments = $oFolder->getDocumentIDs($sFolderId);
421 $aDocuments = (!empty($sDocuments)) ? explode(',', $sDocuments) : array(); 421 $aDocuments = (!empty($sDocuments)) ? explode(',', $sDocuments) : array();
@@ -645,7 +645,7 @@ class KTBulkCopyAction extends KTBulkAction { @@ -645,7 +645,7 @@ class KTBulkCopyAction extends KTBulkAction {
645 return PEAR::raiseError(_kt('Document cannot be copied')); 645 return PEAR::raiseError(_kt('Document cannot be copied'));
646 } 646 }
647 } 647 }
648 - 648 +
649 if(is_a($oEntity, 'Folder')) { 649 if(is_a($oEntity, 'Folder')) {
650 $aDocuments = array(); 650 $aDocuments = array();
651 $aChildFolders = array(); 651 $aChildFolders = array();
@@ -1068,18 +1068,42 @@ class KTBrowseBulkExportAction extends KTBulkAction { @@ -1068,18 +1068,42 @@ class KTBrowseBulkExportAction extends KTBulkAction {
1068 return PEAR::raiseError(_kt('Document cannot be exported as it is restricted by the workflow.')); 1068 return PEAR::raiseError(_kt('Document cannot be exported as it is restricted by the workflow.'));
1069 } 1069 }
1070 } 1070 }
  1071 +
1071 return parent::check_entity($oEntity); 1072 return parent::check_entity($oEntity);
1072 } 1073 }
1073 1074
1074 -  
1075 function do_performaction() { 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 $folderurl = $this->getReturnUrl(); 1084 $folderurl = $this->getReturnUrl();
1082 $sReturn = sprintf('<p>' . _kt('Return to the original <a href="%s">folder</a>') . "</p>\n", $folderurl); 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 if(PEAR::isError($res)){ 1108 if(PEAR::isError($res)){
1085 $this->addErrorMessage($res->getMessage()); 1109 $this->addErrorMessage($res->getMessage());
@@ -1087,16 +1111,14 @@ class KTBrowseBulkExportAction extends KTBulkAction { @@ -1087,16 +1111,14 @@ class KTBrowseBulkExportAction extends KTBulkAction {
1087 } 1111 }
1088 1112
1089 $this->startTransaction(); 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 $result = parent::do_performaction(); 1115 $result = parent::do_performaction();
  1116 +
1096 $sExportCode = $this->oZip->createZipFile(); 1117 $sExportCode = $this->oZip->createZipFile();
1097 1118
1098 if(PEAR::isError($sExportCode)){ 1119 if(PEAR::isError($sExportCode)){
1099 $this->addErrorMessage($sExportCode->getMessage()); 1120 $this->addErrorMessage($sExportCode->getMessage());
  1121 + $this->rollbackTransaction();
1100 return $sReturn; 1122 return $sReturn;
1101 } 1123 }
1102 1124
@@ -1110,23 +1132,28 @@ class KTBrowseBulkExportAction extends KTBulkAction { @@ -1110,23 +1132,28 @@ class KTBrowseBulkExportAction extends KTBulkAction {
1110 1132
1111 $this->commitTransaction(); 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 $str .= sprintf('<p>' . _kt('Once your download is complete, click <a href="%s">here</a> to return to the original folder') . "</p>\n", $folderurl); 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 $str .= sprintf('<script language="JavaScript"> 1137 $str .= sprintf('<script language="JavaScript">
1118 function kt_bulkexport_redirect() { 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 return $str; 1144 return $str;
1126 } 1145 }
1127 1146
1128 function perform_action($oEntity) { 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 if(is_a($oEntity, 'Document')) { 1157 if(is_a($oEntity, 'Document')) {
1131 1158
1132 $oDocument = $oEntity; 1159 $oDocument = $oEntity;
@@ -1134,19 +1161,12 @@ class KTBrowseBulkExportAction extends KTBulkAction { @@ -1134,19 +1161,12 @@ class KTBrowseBulkExportAction extends KTBulkAction {
1134 $oDocument->switchToLinkedCore(); 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 }else if(is_a($oEntity, 'Folder')) { 1171 }else if(is_a($oEntity, 'Folder')) {
1152 $aDocuments = array(); 1172 $aDocuments = array();
@@ -1156,79 +1176,11 @@ class KTBrowseBulkExportAction extends KTBulkAction { @@ -1156,79 +1176,11 @@ class KTBrowseBulkExportAction extends KTBulkAction {
1156 $oFolder = $oFolder->getLinkedFolder(); 1176 $oFolder = $oFolder->getLinkedFolder();
1157 } 1177 }
1158 $sFolderId = $oFolder->getId(); 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 return true; 1186 return true;
@@ -1237,9 +1189,7 @@ class KTBrowseBulkExportAction extends KTBulkAction { @@ -1237,9 +1189,7 @@ class KTBrowseBulkExportAction extends KTBulkAction {
1237 function do_downloadZipFile() { 1189 function do_downloadZipFile() {
1238 $sCode = $this->oValidator->validateString($_REQUEST['exportcode']); 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 $res = $this->oZip->downloadZipFile($sCode); 1193 $res = $this->oZip->downloadZipFile($sCode);
1244 1194
1245 if(PEAR::isError($res)){ 1195 if(PEAR::isError($res)){
plugins/ktstandard/KTBulkExportPlugin.php
@@ -71,20 +71,44 @@ class KTBulkExportAction extends KTFolderAction { @@ -71,20 +71,44 @@ class KTBulkExportAction extends KTFolderAction {
71 } 71 }
72 72
73 function do_main() { 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 if(!$this->oZip->checkConvertEncoding()) { 81 if(!$this->oZip->checkConvertEncoding()) {
78 redirect(KTBrowseUtil::getUrlForFolder($this->oFolder)); 82 redirect(KTBrowseUtil::getUrlForFolder($this->oFolder));
79 exit(0); 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 $sCurrentFolderId = $this->oFolder->getId(); 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 $sWhereClause = "parent_folder_ids = '{$sCurrentFolderId}' OR 112 $sWhereClause = "parent_folder_ids = '{$sCurrentFolderId}' OR
89 parent_folder_ids LIKE '{$sCurrentFolderId},%' OR 113 parent_folder_ids LIKE '{$sCurrentFolderId},%' OR
90 parent_folder_ids LIKE '%,{$sCurrentFolderId},%' OR 114 parent_folder_ids LIKE '%,{$sCurrentFolderId},%' OR
@@ -173,16 +197,14 @@ class KTBulkExportAction extends KTFolderAction { @@ -173,16 +197,14 @@ class KTBulkExportAction extends KTFolderAction {
173 'ip' => Session::getClientIP(), 197 'ip' => Session::getClientIP(),
174 )); 198 ));
175 199
176 - $url = KTUtil::addQueryStringSelf(sprintf('action=downloadZipFile&fFolderId=%d&exportcode=%s', $this->oFolder->getId(), $sExportCode));  
177 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); 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 printf("</div></div></body></html>\n"); 202 printf("</div></div></body></html>\n");
181 printf('<script language="JavaScript"> 203 printf('<script language="JavaScript">
182 function kt_bulkexport_redirect() { 204 function kt_bulkexport_redirect() {
183 document.location.href = "%s"; 205 document.location.href = "%s";
184 } 206 }
185 - callLater(1, kt_bulkexport_redirect); 207 + callLater(2, kt_bulkexport_redirect);
186 208
187 </script>', $url); 209 </script>', $url);
188 210
@@ -230,8 +252,7 @@ class KTBulkExportAction extends KTFolderAction { @@ -230,8 +252,7 @@ class KTBulkExportAction extends KTFolderAction {
230 function do_downloadZipFile() { 252 function do_downloadZipFile() {
231 $sCode = $this->oValidator->validateString($_REQUEST['exportcode']); 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 $res = $this->oZip->downloadZipFile($sCode); 257 $res = $this->oZip->downloadZipFile($sCode);
237 258
sql/mysql/install/data.sql
@@ -287,7 +287,9 @@ INSERT INTO `config_settings` VALUES @@ -287,7 +287,9 @@ INSERT INTO `config_settings` VALUES
287 (112, 'urls', 'Var Directory', 'The path to the var directory.', 'varDirectory', 'default', '${fileSystemRoot}/var', 'string', NULL, 1), 287 (112, 'urls', 'Var Directory', 'The path to the var directory.', 'varDirectory', 'default', '${fileSystemRoot}/var', 'string', NULL, 1),
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), 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 (114, 'ui', 'System URL', 'The system url, used in the main logo.', 'systemUrl', 'default', 'http://www.knowledgetree.com', 'string', '', 1), 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 /*!40000 ALTER TABLE `config_settings` ENABLE KEYS */; 293 /*!40000 ALTER TABLE `config_settings` ENABLE KEYS */;
292 UNLOCK TABLES; 294 UNLOCK TABLES;
293 295
@@ -1370,7 +1372,8 @@ INSERT INTO `scheduler_tasks` VALUES @@ -1370,7 +1372,8 @@ INSERT INTO `scheduler_tasks` VALUES
1370 (7,'Cleanup Temporary Directory','search2/bin/cronCleanup.php','',0,'1min','2007-10-01 00:00:00',NULL,0,'enabled'), 1372 (7,'Cleanup Temporary Directory','search2/bin/cronCleanup.php','',0,'1min','2007-10-01 00:00:00',NULL,0,'enabled'),
1371 (8,'Disk Usage and Folder Utilisation Statistics','plugins/housekeeper/bin/UpdateStats.php','',0,'5mins','2007-10-01 00:00:00',NULL,0,'enabled'), 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 (9,'Refresh Index Statistics','search2/bin/cronIndexStats.php','',0,'1min','2007-10-01',NULL,0,'enabled'), 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 /*!40000 ALTER TABLE `scheduler_tasks` ENABLE KEYS */; 1378 /*!40000 ALTER TABLE `scheduler_tasks` ENABLE KEYS */;
1376 UNLOCK TABLES; 1379 UNLOCK TABLES;
sql/mysql/install/structure.sql
@@ -639,6 +639,22 @@ CREATE TABLE `download_files` ( @@ -639,6 +639,22 @@ CREATE TABLE `download_files` (
639 ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 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 -- Table structure for table `field_behaviour_options` 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 \ No newline at end of file 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 \ No newline at end of file 48 \ No newline at end of file