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 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 "&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 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
... ...