851 lines
29 KiB
PHP
851 lines
29 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* $Id:
|
||
|
|
*
|
||
|
|
* KnowledgeTree Community Edition
|
||
|
|
* Document Management Made Simple
|
||
|
|
* Copyright (C) 2008, 2009 KnowledgeTree Inc.
|
||
|
|
*
|
||
|
|
*
|
||
|
|
* This program is free software; you can redistribute it and/or modify it under
|
||
|
|
* the terms of the GNU General Public License version 3 as published by the
|
||
|
|
* Free Software Foundation.
|
||
|
|
*
|
||
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||
|
|
* details.
|
||
|
|
*
|
||
|
|
* You should have received a copy of the GNU General Public License
|
||
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
*
|
||
|
|
* You can contact KnowledgeTree Inc., PO Box 7775 #87847, San Francisco,
|
||
|
|
* California 94120-7775, or email info@knowledgetree.com.
|
||
|
|
*
|
||
|
|
* The interactive user interfaces in modified source and object code versions
|
||
|
|
* of this program must display Appropriate Legal Notices, as required under
|
||
|
|
* Section 5 of the GNU General Public License version 3.
|
||
|
|
*
|
||
|
|
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||
|
|
* these Appropriate Legal Notices must retain the display of the "Powered by
|
||
|
|
* KnowledgeTree" logo and retain the original copyright notice. If the display of the
|
||
|
|
* logo is not reasonably feasible for technical reasons, the Appropriate Legal Notices
|
||
|
|
* must display the words "Powered by KnowledgeTree" and retain the original
|
||
|
|
* copyright notice.
|
||
|
|
* Contributor( s): ______________________________________
|
||
|
|
*
|
||
|
|
*/
|
||
|
|
|
||
|
|
require_once('File/Archive.php');
|
||
|
|
require_once(KT_LIB_DIR . '/util/ktpclzip.inc.php');
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Class to create and download a zip file
|
||
|
|
*/
|
||
|
|
class ZipFolder {
|
||
|
|
|
||
|
|
var $sTmpPath = '';
|
||
|
|
var $sZipFileName = '';
|
||
|
|
var $sZipFile = '';
|
||
|
|
var $sPattern = '';
|
||
|
|
var $sFolderPattern = '';
|
||
|
|
var $aPaths = array();
|
||
|
|
var $aReplaceKeys = array();
|
||
|
|
var $aReplaceValues = array();
|
||
|
|
var $sOutputEncoding = 'UTF-8';
|
||
|
|
var $extension = 'zip';
|
||
|
|
var $exportCode = null;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Constructor
|
||
|
|
*
|
||
|
|
* @param string $sZipFileName The name of the zip file - gets ignored at the moment.
|
||
|
|
* @param string $exportCode The code to use if a zip file has already been created.
|
||
|
|
*/
|
||
|
|
function ZipFolder($sZipFileName = null, $exportCode = null, $extension = 'zip') {
|
||
|
|
$this->oKTConfig =& KTConfig::getSingleton();
|
||
|
|
$this->oStorage =& KTStorageManagerUtil::getSingleton();
|
||
|
|
|
||
|
|
$this->sOutputEncoding = $this->oKTConfig->get('export/encoding', 'UTF-8');
|
||
|
|
$this->extension = $extension;
|
||
|
|
|
||
|
|
$this->sPattern = "[\*|\%|\\\|\/|\<|\>|\+|\:|\?|\||\'|\"]";
|
||
|
|
$this->sFolderPattern = "[\*|\%|\<|\>|\+|\:|\?|\||\'|\"]";
|
||
|
|
|
||
|
|
if(!empty($exportCode)){
|
||
|
|
$this->exportCode = $exportCode;
|
||
|
|
}else{
|
||
|
|
$this->exportCode = KTUtil::randomString();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if the temp directory has been created and stored in session
|
||
|
|
$aData = KTUtil::arrayGet($_SESSION['zipcompression'], $exportCode);
|
||
|
|
if(!empty($aData) && isset($aData['dir'])){
|
||
|
|
$sTmpPath = $aData['dir'];
|
||
|
|
}else {
|
||
|
|
$sBasedir = $this->oKTConfig->get("urls/tmpDirectory");
|
||
|
|
$sTmpPath = tempnam($sBasedir, 'kt_compress_zip');
|
||
|
|
|
||
|
|
unlink($sTmpPath);
|
||
|
|
mkdir($sTmpPath, 0755);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Hard coding the zip file name.
|
||
|
|
// It normally uses the folder name but if there are special characters in the name then it doesn't download properly.
|
||
|
|
$sZipFileName = 'kt_zip';
|
||
|
|
|
||
|
|
$this->sTmpPath = $sTmpPath;
|
||
|
|
$this->sZipFileName = $sZipFileName;
|
||
|
|
$this->aPaths = array();
|
||
|
|
|
||
|
|
$aReplace = array(
|
||
|
|
"[" => "[[]",
|
||
|
|
" " => "[ ]",
|
||
|
|
"*" => "[*]",
|
||
|
|
"?" => "[?]",
|
||
|
|
);
|
||
|
|
|
||
|
|
$this->aReplaceKeys = array_keys($aReplace);
|
||
|
|
$this->aReplaceValues = array_values($aReplace);
|
||
|
|
}
|
||
|
|
|
||
|
|
static public function get($exportCode)
|
||
|
|
{
|
||
|
|
static $zipFolder = null;
|
||
|
|
if(is_null($zipFolder)){
|
||
|
|
$zipFolder = new ZipFolder('', $exportCode);
|
||
|
|
}
|
||
|
|
return $zipFolder;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Return the full path
|
||
|
|
*
|
||
|
|
* @param mixed $oFolderOrDocument May be a Folder or Document
|
||
|
|
*/
|
||
|
|
function getFullFolderPath($oFolder)
|
||
|
|
{
|
||
|
|
static $sRootFolder = null;
|
||
|
|
|
||
|
|
if (is_null($sRootFolder))
|
||
|
|
{
|
||
|
|
$oRootFolder = Folder::get(1);
|
||
|
|
$sRootFolder = $oRootFolder->getName();
|
||
|
|
}
|
||
|
|
|
||
|
|
$sFullPath = $sRootFolder . '/';
|
||
|
|
$sFullPath .= $oFolder->getFullPath();
|
||
|
|
|
||
|
|
|
||
|
|
if (substr($sFullPath,-1) == '/') $sFullPath = substr($sFullPath,0,-1);
|
||
|
|
return $sFullPath;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add a document to the zip file
|
||
|
|
*/
|
||
|
|
function addDocumentToZip($oDocument, $oFolder = null) {
|
||
|
|
if(empty($oFolder)){
|
||
|
|
$oFolder = Folder::get($oDocument->getFolderID());
|
||
|
|
}
|
||
|
|
|
||
|
|
$sDocPath = $this->getFullFolderPath($oFolder);
|
||
|
|
$sDocPath = preg_replace($this->sFolderPattern, '-', $sDocPath);
|
||
|
|
$sDocPath = $this->_convertEncoding($sDocPath, true);
|
||
|
|
|
||
|
|
|
||
|
|
$sDocName = $oDocument->getFileName();
|
||
|
|
$sDocName = preg_replace($this->sPattern, '-', $sDocName);
|
||
|
|
$sDocName = $this->_convertEncoding($sDocName, true);
|
||
|
|
|
||
|
|
$sParentFolder = $this->sTmpPath.'/'.$sDocPath;
|
||
|
|
$newDir = $this->sTmpPath;
|
||
|
|
|
||
|
|
$aFullPath = split('/', $sDocPath);
|
||
|
|
foreach ($aFullPath as $dirPart) {
|
||
|
|
$newDir = sprintf("%s/%s", $newDir, $dirPart);
|
||
|
|
if (!file_exists($newDir)) {
|
||
|
|
mkdir($newDir, 0700);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$sOrigFile = $this->oStorage->temporaryFile($oDocument);
|
||
|
|
$sFilename = $sParentFolder.'/'.$sDocName;
|
||
|
|
@copy($sOrigFile, $sFilename);
|
||
|
|
|
||
|
|
$this->aPaths[] = $sDocPath.'/'.$sDocName;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add a folder to the zip file
|
||
|
|
*/
|
||
|
|
function addFolderToZip($oFolder) {
|
||
|
|
$sFolderPath = $this->getFullFolderPath($oFolder) .'/';
|
||
|
|
$sFolderPath = preg_replace($this->sFolderPattern, '-', $sFolderPath);
|
||
|
|
$sFolderPath = $this->_convertEncoding($sFolderPath, true);
|
||
|
|
|
||
|
|
$newDir = $this->sTmpPath;
|
||
|
|
|
||
|
|
$aFullPath = split('/', $sFolderPath);
|
||
|
|
foreach ($aFullPath as $dirPart) {
|
||
|
|
$newDir = sprintf("%s/%s", $newDir, $dirPart);
|
||
|
|
if (!file_exists($newDir)) {
|
||
|
|
mkdir($newDir, 0700);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->aPaths[] = $sFolderPath;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Zip the temp folder
|
||
|
|
*/
|
||
|
|
function createZipFile($bEchoStatus = FALSE) {
|
||
|
|
if(empty($this->aPaths)){
|
||
|
|
return PEAR::raiseError(_kt("No folders or documents found to compress"));
|
||
|
|
}
|
||
|
|
|
||
|
|
$sZipFile = sprintf("%s/%s.".$this->extension, $this->sTmpPath, $this->sZipFileName);
|
||
|
|
$sZipFile = str_replace('<', '', str_replace('</', '', str_replace('>', '', $sZipFile)));
|
||
|
|
|
||
|
|
$archive = new KTPclZip($sZipFile);
|
||
|
|
$archive->createZipFile($this->sTmpPath.'/Root Folder');
|
||
|
|
|
||
|
|
/*
|
||
|
|
$config = KTConfig::getSingleton();
|
||
|
|
$useBinary = true; //$config->get('export/useBinary', false);
|
||
|
|
|
||
|
|
// Set environment language to output character encoding
|
||
|
|
$loc = $this->sOutputEncoding;
|
||
|
|
putenv("LANG=$loc");
|
||
|
|
putenv("LANGUAGE=$loc");
|
||
|
|
$loc = setlocale(LC_ALL, $loc);
|
||
|
|
|
||
|
|
if($useBinary){
|
||
|
|
$sManifest = sprintf("%s/%s", $this->sTmpPath, "MANIFEST");
|
||
|
|
file_put_contents($sManifest, join("\n", $this->aPaths));
|
||
|
|
}
|
||
|
|
|
||
|
|
$sZipFile = sprintf("%s/%s.".$this->extension, $this->sTmpPath, $this->sZipFileName);
|
||
|
|
$sZipFile = str_replace('<', '', str_replace('</', '', str_replace('>', '', $sZipFile)));
|
||
|
|
|
||
|
|
if($useBinary){
|
||
|
|
$sZipCommand = KTUtil::findCommand("export/zip", "zip");
|
||
|
|
$aCmd = array($sZipCommand, "-r", $sZipFile, ".", "-i@MANIFEST");
|
||
|
|
$sOldPath = getcwd();
|
||
|
|
chdir($this->sTmpPath);
|
||
|
|
|
||
|
|
// Note that the popen means that pexec will return a file descriptor
|
||
|
|
$aOptions = array('popen' => 'r');
|
||
|
|
$fh = KTUtil::pexec($aCmd, $aOptions);
|
||
|
|
|
||
|
|
if($bEchoStatus){
|
||
|
|
$last_beat = time();
|
||
|
|
while(!feof($fh)) {
|
||
|
|
if ($i % 1000 == 0) {
|
||
|
|
$this_beat = time();
|
||
|
|
if ($last_beat + 1 < $this_beat) {
|
||
|
|
$last_beat = $this_beat;
|
||
|
|
print " ";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$contents = fread($fh, 4096);
|
||
|
|
if ($contents) {
|
||
|
|
print nl2br($this->_convertEncoding($contents, false));
|
||
|
|
}
|
||
|
|
$i++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
pclose($fh);
|
||
|
|
}else{
|
||
|
|
// Create the zip archive using the PEAR File_Archive
|
||
|
|
File_Archive::extract(
|
||
|
|
File_Archive::read($this->sTmpPath.'/Root Folder'),
|
||
|
|
File_Archive::toArchive($this->sZipFileName.'.'.$this->extension, File_Archive::toFiles($this->sTmpPath), $this->extension)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
*/
|
||
|
|
|
||
|
|
// Save the zip file and path into session
|
||
|
|
$_SESSION['zipcompression'] = KTUtil::arrayGet($_SESSION, 'zipcompression', array());
|
||
|
|
$sExportCode = $this->exportCode;
|
||
|
|
$_SESSION['zipcompression'][$sExportCode] = array(
|
||
|
|
'file' => $sZipFile,
|
||
|
|
'dir' => $this->sTmpPath,
|
||
|
|
);
|
||
|
|
$_SESSION['zipcompression']['exportcode'] = $sExportCode;
|
||
|
|
|
||
|
|
$this->sZipFile = $sZipFile;
|
||
|
|
return $sExportCode;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Download the zip file
|
||
|
|
*/
|
||
|
|
function downloadZipFile($exportCode = NULL) {
|
||
|
|
if(!(isset($exportCode) && !empty($exportCode))) {
|
||
|
|
$exportCode = KTUtil::arrayGet($_SESSION['zipcompression'], 'exportcode');
|
||
|
|
}
|
||
|
|
|
||
|
|
$aData = KTUtil::arrayGet($_SESSION['zipcompression'], $exportCode);
|
||
|
|
|
||
|
|
if(!empty($aData)){
|
||
|
|
$sZipFile = $aData['file'];
|
||
|
|
$sTmpPath = $aData['dir'];
|
||
|
|
}else{
|
||
|
|
$sZipFile = $this->sZipFile;
|
||
|
|
$sTmpPath = $this->sTmpPath;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!file_exists($sZipFile)) {
|
||
|
|
return PEAR::raiseError(_kt('The zip file has not been created, if you are downloading a large number of documents
|
||
|
|
or a large document then it may take a few minutes to finish.'));
|
||
|
|
}
|
||
|
|
|
||
|
|
$mimeType = 'application/zip; charset=utf-8;';
|
||
|
|
$fileSize = filesize($sZipFile);
|
||
|
|
$fileName = $this->sZipFileName .'.'.$this->extension;
|
||
|
|
|
||
|
|
KTUtil::download($sZipFile, $mimeType, $fileSize, $fileName);
|
||
|
|
KTUtil::deleteDirectory($sTmpPath);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
function checkArchiveExists($exportCode = null)
|
||
|
|
{
|
||
|
|
if(!(isset($exportCode) && !empty($exportCode))) {
|
||
|
|
$exportCode = KTUtil::arrayGet($_SESSION['zipcompression'], 'exportcode');
|
||
|
|
}
|
||
|
|
|
||
|
|
$aData = KTUtil::arrayGet($_SESSION['zipcompression'], $exportCode);
|
||
|
|
|
||
|
|
if(!empty($aData)){
|
||
|
|
$sZipFile = $aData['file'];
|
||
|
|
$sTmpPath = $aData['dir'];
|
||
|
|
}else{
|
||
|
|
$sZipFile = $this->sZipFile;
|
||
|
|
$sTmpPath = $this->sTmpPath;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!file_exists($sZipFile)) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check that iconv exists and that the selected encoding is supported.
|
||
|
|
*/
|
||
|
|
function checkConvertEncoding() {
|
||
|
|
if(!function_exists("iconv")) {
|
||
|
|
return PEAR::raiseError(_kt('IConv PHP extension not installed. The zip file compression could not handle output filename encoding conversion !'));
|
||
|
|
}
|
||
|
|
$oKTConfig = $this->oKTConfig;
|
||
|
|
$this->sOutputEncoding = $oKTConfig->get('export/encoding', 'UTF-8');
|
||
|
|
|
||
|
|
// Test the specified encoding
|
||
|
|
if(iconv("UTF-8", $this->sOutputEncoding, "") === FALSE) {
|
||
|
|
return PEAR::raiseError(_kt('Specified output encoding for the zip files compression does not exists !'));
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
function _convertEncoding($sMystring, $bEncode) {
|
||
|
|
if (strcasecmp($this->sOutputEncoding, "UTF-8") === 0) {
|
||
|
|
return $sMystring;
|
||
|
|
}
|
||
|
|
if ($bEncode) {
|
||
|
|
return iconv("UTF-8", $this->sOutputEncoding, $sMystring);
|
||
|
|
} else {
|
||
|
|
return iconv($this->sOutputEncoding, "UTF-8", $sMystring);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static public function checkDownloadSize($object)
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
|
||
|
|
if($object instanceof Document || $object instanceof DocumentProxy){
|
||
|
|
}
|
||
|
|
|
||
|
|
if($object instanceof Folder || $object instanceof FolderProxy){
|
||
|
|
$id = $object->iId;
|
||
|
|
|
||
|
|
// If we're working with the root folder
|
||
|
|
if($id = 1){
|
||
|
|
$sql = 'SELECT count(*) as cnt FROM documents where folder_id = 1';
|
||
|
|
}else{
|
||
|
|
$sql[] = "SELECT count(*) as cnt FROM documents where parent_folder_ids like '%,?' OR parent_folder_ids like '%,?,%' OR folder_id = ?";
|
||
|
|
$sql[] = array($id, $id, $id);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
SELECT count(*) FROM documents d
|
||
|
|
INNER JOIN document_metadata_version m ON d.metadata_version_id = m.id
|
||
|
|
INNER JOIN document_content_version c ON m.content_version_id = c.id
|
||
|
|
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
|
||
|
|
*/
|
||
|
|
|
||
|
|
$result = DBUtil::getOneResult($sql);
|
||
|
|
|
||
|
|
if($result['cnt'] > 10){
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Class to manage the queue of bulk downloads
|
||
|
|
*
|
||
|
|
*/
|
||
|
|
class DownloadQueue
|
||
|
|
{
|
||
|
|
private $bNoisy;
|
||
|
|
private $bNotifications;
|
||
|
|
private $errors;
|
||
|
|
private $lockFile;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Construct download queue object
|
||
|
|
*
|
||
|
|
* @return DownloadQueue
|
||
|
|
*/
|
||
|
|
public function __construct()
|
||
|
|
{
|
||
|
|
$config = KTConfig::getSingleton();
|
||
|
|
$this->bNoisy = $config->get('tweaks/noisyBulkOperations', false);
|
||
|
|
$this->bNotifications = ($config->get('export/enablenotifications', 'on') == 'on') ? true : false;
|
||
|
|
$this->lockFile = $config->get('cache/cacheDirectory') . '/download_queue_lock.lock';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add an item to the download queue
|
||
|
|
*
|
||
|
|
* @param string $code The identification string for the download
|
||
|
|
* @param string $id The object id
|
||
|
|
* @param string $type The type of object Folder | Document
|
||
|
|
*/
|
||
|
|
static public function addItem($code, $folderId, $id, $type)
|
||
|
|
{
|
||
|
|
$fields = array();
|
||
|
|
$fields['code'] = $code;
|
||
|
|
$fields['folder_id'] = $folderId;
|
||
|
|
$fields['object_id'] = $id;
|
||
|
|
$fields['object_type'] = $type;
|
||
|
|
$fields['user_id'] = $_SESSION['userID'];
|
||
|
|
$fields['date_added'] = date('Y-m-d H:i:s');
|
||
|
|
|
||
|
|
$res = DBUtil::autoInsert('download_queue', $fields);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Remove an item from the download queue
|
||
|
|
*
|
||
|
|
* @param string $code Identification string for the download item
|
||
|
|
* @return boolean | PEAR_Error
|
||
|
|
*/
|
||
|
|
public function removeItem($code)
|
||
|
|
{
|
||
|
|
$where = array('code' => $code);
|
||
|
|
$res = DBUtil::whereDelete('download_queue', $where);
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all download items in the queue
|
||
|
|
*
|
||
|
|
* @return Queue array | PEAR_Error
|
||
|
|
*/
|
||
|
|
public function getQueue()
|
||
|
|
{
|
||
|
|
$sql = 'SELECT * FROM download_queue d WHERE status = 0 ORDER BY date_added, code';
|
||
|
|
$rows = DBUtil::getResultArray($sql);
|
||
|
|
|
||
|
|
if(PEAR::isError($rows)){
|
||
|
|
return $rows;
|
||
|
|
}
|
||
|
|
|
||
|
|
$queue = array();
|
||
|
|
foreach ($rows as $item){
|
||
|
|
$queue[$item['code']][] = $item;
|
||
|
|
}
|
||
|
|
return $queue;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the status of an item in the queue
|
||
|
|
* 0 = new item; 1 = in progress; 2 = completed; 3 = error;
|
||
|
|
*
|
||
|
|
* @param string $code
|
||
|
|
* @return Queue array
|
||
|
|
*/
|
||
|
|
public function getItemStatus($code)
|
||
|
|
{
|
||
|
|
$sql = array();
|
||
|
|
$sql[] = 'SELECT status, errors FROM download_queue WHERE code = ?';
|
||
|
|
$sql[] = $code;
|
||
|
|
$result = DBUtil::getResultArray($sql);
|
||
|
|
return $result;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Set the status of an item and any errors that may have occurred while trying to archive it.
|
||
|
|
*
|
||
|
|
* @param string $code The identification string
|
||
|
|
* @param integer $status The new status of the item
|
||
|
|
* @param string $error Optional. The error's generated during the archive
|
||
|
|
* @return boolean
|
||
|
|
*/
|
||
|
|
public function setItemStatus($code, $status = 1, $error = null)
|
||
|
|
{
|
||
|
|
$fields = array();
|
||
|
|
$fields['status'] = $status;
|
||
|
|
$fields['errors'] = !empty($error) ? json_encode($error) : null;
|
||
|
|
$where = array('code' => $code);
|
||
|
|
$res = DBUtil::whereUpdate('download_queue', $fields, $where);
|
||
|
|
return $res;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Loop through the queue and archive the items.
|
||
|
|
*
|
||
|
|
* @return unknown
|
||
|
|
*/
|
||
|
|
public function processQueue()
|
||
|
|
{
|
||
|
|
global $default;
|
||
|
|
|
||
|
|
// get items from queue
|
||
|
|
$queue = $this->getQueue();
|
||
|
|
if(PEAR::isError($queue)){
|
||
|
|
$default->log->debug('Download Queue: error on fetching queue - '.$queue->getMessage());
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Set queue as locked
|
||
|
|
touch($this->lockFile);
|
||
|
|
|
||
|
|
// Loop through items and create downloads
|
||
|
|
foreach ($queue as $code => $download){
|
||
|
|
// reset the error messages
|
||
|
|
$this->errors = null;
|
||
|
|
|
||
|
|
// if the user_id is not set then skip
|
||
|
|
if(!isset($download[0]['user_id']) || empty($download[0]['user_id'])){
|
||
|
|
$default->log->debug('Download Queue: no user id set for download code '.$code);
|
||
|
|
$error = array(_kt('No user id has been set, the archive cannot be created.'));
|
||
|
|
$result = $this->setItemStatus($code, 3, $error);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Force a session for the user
|
||
|
|
$_SESSION['userID'] = $download[0]['user_id'];
|
||
|
|
$baseFolderId = $download[0]['folder_id'];
|
||
|
|
|
||
|
|
// Create a new instance of the archival class
|
||
|
|
$zip = new ZipFolder('', $code);
|
||
|
|
$res = $zip->checkConvertEncoding();
|
||
|
|
|
||
|
|
if(PEAR::isError($res)){
|
||
|
|
$default->log->error('Download Queue: Archive class check convert encoding error - '.$res->getMessage());
|
||
|
|
$error = array(_kt('The archive cannot be created. An error occurred in the encoding.'));
|
||
|
|
$result = $this->setItemStatus($code, 3, $error);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
$result = $this->setItemStatus($code, 1);
|
||
|
|
if(PEAR::isError($result)){
|
||
|
|
$default->log->error('Download Queue: item status could not be set for user: '.$_SESSION['userID'].', code: '.$code.', error: '.$result->getMessage());
|
||
|
|
}
|
||
|
|
|
||
|
|
$default->log->debug('Download Queue: Creating download for user: '.$_SESSION['userID'].', code: '.$code);
|
||
|
|
|
||
|
|
DBUtil::startTransaction();
|
||
|
|
|
||
|
|
// Add the individual files and folders into the archive
|
||
|
|
foreach ($download as $item){
|
||
|
|
if($item['object_type'] == 'document'){
|
||
|
|
$docId = $item['object_id'];
|
||
|
|
$this->addDocument($zip, $docId);
|
||
|
|
}
|
||
|
|
if($item['object_type'] == 'folder'){
|
||
|
|
$folderId = $item['object_id'];
|
||
|
|
$this->addFolder($zip, $folderId);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$res = $zip->createZipFile();
|
||
|
|
|
||
|
|
if(PEAR::isError($res)){
|
||
|
|
$default->log->debug('Download Queue: Archive could not be created. Exiting transaction. '.$res->getMessage());
|
||
|
|
DBUtil::rollback();
|
||
|
|
|
||
|
|
$error = array(_kt('The archive could not be created.'));
|
||
|
|
$result = $this->setItemStatus($code, 3, $error);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
$default->log->debug('Download Queue: Archival successful');
|
||
|
|
|
||
|
|
$oTransaction = KTFolderTransaction::createFromArray(array(
|
||
|
|
'folderid' => $baseFolderId,
|
||
|
|
'comment' => "Bulk export",
|
||
|
|
'transactionNS' => 'ktstandard.transactions.bulk_export',
|
||
|
|
'userid' => $_SESSION['userID'],
|
||
|
|
'ip' => Session::getClientIP(),
|
||
|
|
));
|
||
|
|
|
||
|
|
if(PEAR::isError($oTransaction)){
|
||
|
|
$default->log->debug('Download Queue: transaction could not be logged. '.$oTransaction->getMessage());
|
||
|
|
}
|
||
|
|
|
||
|
|
DBUtil::commit();
|
||
|
|
|
||
|
|
// Set status for the download
|
||
|
|
$this->errors['archive'] = $_SESSION['zipcompression'];
|
||
|
|
$result = $this->setItemStatus($code, 2, $this->errors);
|
||
|
|
if(PEAR::isError($result)){
|
||
|
|
$default->log->error('Download Queue: item status could not be set for user: '.$_SESSION['userID'].', code: '.$code.', error: '.$result->getMessage());
|
||
|
|
}
|
||
|
|
// reset the error messages
|
||
|
|
$this->errors = null;
|
||
|
|
$_SESSION['zipcompression'] = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Remove lock file
|
||
|
|
@unlink($this->lockFile);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add a document to the archive
|
||
|
|
*
|
||
|
|
* @param unknown_type $zip
|
||
|
|
* @param unknown_type $docId
|
||
|
|
* @return unknown
|
||
|
|
*/
|
||
|
|
public function addDocument(&$zip, $docId)
|
||
|
|
{
|
||
|
|
|
||
|
|
$oDocument = Document::get($docId);
|
||
|
|
if(PEAR::isError($oDocument)){
|
||
|
|
$this->errors[] = _kt('Document cannot be exported, an error occurred: ').$oDocument->getMessage();
|
||
|
|
return $oDocument;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($this->bNoisy) {
|
||
|
|
$oDocumentTransaction = new DocumentTransaction($oDocument, "Document part of bulk export", 'ktstandard.transactions.bulk_export', array());
|
||
|
|
$oDocumentTransaction->create();
|
||
|
|
}
|
||
|
|
|
||
|
|
// fire subscription alerts for the downloaded document - if global config is set
|
||
|
|
if($this->bNotifications){
|
||
|
|
$oSubscriptionEvent = new SubscriptionEvent();
|
||
|
|
$oFolder = Folder::get($oDocument->getFolderID());
|
||
|
|
$oSubscriptionEvent->DownloadDocument($oDocument, $oFolder);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $zip->addDocumentToZip($oDocument);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add a folder to the archive
|
||
|
|
*
|
||
|
|
* @param unknown_type $zip
|
||
|
|
* @param unknown_type $folderId
|
||
|
|
* @return unknown
|
||
|
|
*/
|
||
|
|
public function addFolder(&$zip, $folderId)
|
||
|
|
{
|
||
|
|
$oFolder = Folder::get($folderId);
|
||
|
|
|
||
|
|
if(PEAR::isError($oFolder)){
|
||
|
|
$this->errors[] = _kt('Folder cannot be exported, an error occurred: ').$oFolder->getMessage();
|
||
|
|
return $oFolder;
|
||
|
|
}
|
||
|
|
|
||
|
|
$sFolderDocs = $oFolder->getDocumentIDs($folderId);
|
||
|
|
if(PEAR::isError($sFolderDocs)){
|
||
|
|
$default->log->error('Download Queue: get document ids for folder caused an error: '.$sFolderDocs->getMessage());
|
||
|
|
$sFolderDocs = '';
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add folder to zip
|
||
|
|
$zip->addFolderToZip($oFolder);
|
||
|
|
|
||
|
|
$aDocuments = array();
|
||
|
|
if(!empty($sFolderDocs)){
|
||
|
|
$aDocuments = explode(',', $sFolderDocs);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get all the folders within the current folder
|
||
|
|
$sWhereClause = "parent_folder_ids like '%,{$folderId}'
|
||
|
|
OR parent_folder_ids like '%,{$folderId},%'
|
||
|
|
OR parent_folder_ids like '{$folderId},%'
|
||
|
|
OR parent_id = {$folderId}";
|
||
|
|
|
||
|
|
$aFolderList = $oFolder->getList($sWhereClause);
|
||
|
|
$aLinkingFolders = $this->getLinkingEntities($aFolderList);
|
||
|
|
$aFolderList = array_merge($aFolderList,$aLinkingFolders);
|
||
|
|
|
||
|
|
$aFolderObjects = array();
|
||
|
|
$aFolderObjects[$folderId] = $oFolder;
|
||
|
|
|
||
|
|
// Export the folder structure to ensure the export of empty directories
|
||
|
|
if(!empty($aFolderList)){
|
||
|
|
foreach($aFolderList as $k => $oFolderItem){
|
||
|
|
if($oFolderItem->isSymbolicLink()){
|
||
|
|
$oFolderItem = $oFolderItem->getLinkedFolder();
|
||
|
|
}
|
||
|
|
if(Permission::userHasFolderReadPermission($oFolderItem)){
|
||
|
|
// Get documents for each folder
|
||
|
|
$sFolderItemId = $oFolderItem->getID();
|
||
|
|
$sFolderItemDocs = $oFolderItem->getDocumentIDs($sFolderItemId);
|
||
|
|
|
||
|
|
if(!empty($sFolderItemDocs)){
|
||
|
|
$aFolderDocs = explode(',', $sFolderItemDocs);
|
||
|
|
$aDocuments = array_merge($aDocuments, $aFolderDocs);
|
||
|
|
}
|
||
|
|
$zip->addFolderToZip($oFolderItem);
|
||
|
|
$aFolderObjects[$oFolderItem->getId()] = $oFolderItem;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add all documents to the export
|
||
|
|
if(!empty($aDocuments)){
|
||
|
|
foreach($aDocuments as $sDocumentId){
|
||
|
|
$oDocument = Document::get($sDocumentId);
|
||
|
|
if($oDocument->isSymbolicLink()){
|
||
|
|
$oDocument->switchToLinkedCore();
|
||
|
|
}
|
||
|
|
if(Permission::userHasDocumentReadPermission($oDocument)){
|
||
|
|
|
||
|
|
if(!KTWorkflowUtil::actionEnabledForDocument($oDocument, 'ktcore.actions.document.view')){
|
||
|
|
$this->errors[] = $oDocument->getName().': '._kt('Document cannot be exported as it is restricted by the workflow.');
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
$sDocFolderId = $oDocument->getFolderID();
|
||
|
|
$oFolder = isset($aFolderObjects[$sDocFolderId]) ? $aFolderObjects[$sDocFolderId] : Folder::get($sDocFolderId);
|
||
|
|
|
||
|
|
if ($this->bNoisy) {
|
||
|
|
$oDocumentTransaction = new DocumentTransaction($oDocument, "Document part of bulk export", 'ktstandard.transactions.bulk_export', array());
|
||
|
|
$oDocumentTransaction->create();
|
||
|
|
}
|
||
|
|
|
||
|
|
// fire subscription alerts for the downloaded document
|
||
|
|
if($this->bNotifications){
|
||
|
|
$oSubscriptionEvent = new SubscriptionEvent();
|
||
|
|
$oSubscriptionEvent->DownloadDocument($oDocument, $oFolder);
|
||
|
|
}
|
||
|
|
|
||
|
|
$zip->addDocumentToZip($oDocument, $oFolder);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Fetch any linked folders
|
||
|
|
*
|
||
|
|
* @param Folder array $aFolderList
|
||
|
|
* @return unknown
|
||
|
|
*/
|
||
|
|
function getLinkingEntities($aFolderList){
|
||
|
|
$aSearchFolders = array();
|
||
|
|
if(!empty($aFolderList)){
|
||
|
|
foreach($aFolderList as $oFolderItem){
|
||
|
|
if(Permission::userHasFolderReadPermission($oFolderItem)){
|
||
|
|
// If it is a shortcut, we should do some more searching
|
||
|
|
if($oFolderItem->isSymbolicLink()){
|
||
|
|
$oFolderItem = $oFolderItem->getLinkedFolder();
|
||
|
|
$aSearchFolders[] = $oFolderItem->getID();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
$aLinkingFolders = array();
|
||
|
|
$aSearchCompletedFolders = array();
|
||
|
|
$count = 0;
|
||
|
|
while(count($aSearchFolders)>0){
|
||
|
|
$count++;
|
||
|
|
$oFolder = Folder::get(array_pop($aSearchFolders));
|
||
|
|
$folderId = $oFolder->getId();
|
||
|
|
// Get all the folders within the current folder
|
||
|
|
$sWhereClause = "parent_folder_ids = '{$folderId}' OR
|
||
|
|
parent_folder_ids LIKE '{$folderId},%' OR
|
||
|
|
parent_folder_ids LIKE '%,{$folderId},%' OR
|
||
|
|
parent_folder_ids LIKE '%,{$folderId}'";
|
||
|
|
$aFolderList = $this->oFolder->getList($sWhereClause);
|
||
|
|
foreach($aFolderList as $oFolderItem){
|
||
|
|
if($oFolderItem->isSymbolicLink()){
|
||
|
|
$oFolderItem = $oFolderItem->getLinkedFolder();
|
||
|
|
}
|
||
|
|
if(Permission::userHasFolderReadPermission($oFolderItem)){
|
||
|
|
if($aSearchCompletedFolders[$oFolderItem->getID()] != true){
|
||
|
|
$aSearchFolders[] = $oFolderItem->getID();
|
||
|
|
$aSearchCompletedFolders[$oFolderItem->getID()] = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if(!isset($aLinkingFolders[$oFolder->getId()])){
|
||
|
|
$aLinkingFolders[$oFolder->getId()] = $oFolder;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return $aLinkingFolders;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if the archive has been created and is ready for download
|
||
|
|
*
|
||
|
|
* @param string $code
|
||
|
|
* @return boolean
|
||
|
|
*/
|
||
|
|
public function isDownloadAvailable($code)
|
||
|
|
{
|
||
|
|
$check = $this->getItemStatus($code);
|
||
|
|
$status = $check[0]['status'];
|
||
|
|
|
||
|
|
if($status < 2){
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$message = $check[0]['errors'];
|
||
|
|
$message = json_decode($message, true);
|
||
|
|
|
||
|
|
if($status > 2){
|
||
|
|
return $message;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create the archive session variables
|
||
|
|
$_SESSION['zipcompression'] = $message['archive'];
|
||
|
|
unset($message['archive']);
|
||
|
|
|
||
|
|
// Check that the archive has been created
|
||
|
|
$zip = new ZipFolder('', $code);
|
||
|
|
if($zip->checkArchiveExists($code)){
|
||
|
|
// Clean up the download queue and return errors
|
||
|
|
$this->removeItem($code);
|
||
|
|
return $message;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if the queue is locked and busy processing
|
||
|
|
*
|
||
|
|
* @return boolean
|
||
|
|
*/
|
||
|
|
public function isLocked()
|
||
|
|
{
|
||
|
|
return file_exists($this->lockFile);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
?>
|