diff --git a/browse.php b/browse.php index 6e786bc..7cdee25 100755 --- a/browse.php +++ b/browse.php @@ -155,6 +155,10 @@ class BrowseDispatcher extends KTStandardDispatcher { exit(0); } $this->oQuery = new ValueBrowseQuery($oField, $oValue); + $this->resultURL = KTUtil::addQueryString($_SERVER['PHP_SELF'], sprintf("fBrowseMode=lookup_value&fField=%d&fValue=%d", $field, $value)); + $this->aBreadcrumbs[] = array('name' => _('Lookup Values'), 'url' => KTUtil::addQueryString($_SERVER['PHP_SELF'], 'action=selectField')); + $this->aBreadcrumbs[] = array('name' => $oField->getName(), 'url' => KTUtil::addQueryString($_SERVER['PHP_SELF'], 'action=selectLookup&fField=' . $oField->getId())); + $this->aBreadcrumbs[] = array('name' => $oField->getName(), 'url' => KTUtil::addQueryString($_SERVER['PHP_SELF'], sprintf("fBrowseMode=lookup_value&fField=%d&fValue=%d", $field, $value))); } else if ($this->browse_mode == 'document_type') { $this->editable = false; // FIXME implement document_type browsing. @@ -168,7 +172,8 @@ class BrowseDispatcher extends KTStandardDispatcher { $this->oQuery = new TypeBrowseQuery($oDocType); // FIXME probably want to redirect to self + action=selectType - $this->aBreadcrumbs[] = array('name' => _('Document Types')); + $this->aBreadcrumbs[] = array('name' => _('Document Types'), 'url' => KTUtil::addQueryString($_SERVER['PHP_SELF'], 'action=selectType')); + $this->aBreadcrumbs[] = array('name' => $oDocType->getName(), 'url' => KTUtil::addQueryString($_SERVER['PHP_SELF'], 'fBrowseMode=document_type&fType=' . $oDocType->getId())); $this->resultURL = KTUtil::addQueryString($_SERVER['PHP_SELF'], sprintf("fType=%s&fBrowseMode=document_type", $doctype));; } else { diff --git a/lib/documentmanagement/documentmetadataversion.inc.php b/lib/documentmanagement/documentmetadataversion.inc.php index a32df14..fa83d83 100644 --- a/lib/documentmanagement/documentmetadataversion.inc.php +++ b/lib/documentmanagement/documentmetadataversion.inc.php @@ -59,6 +59,7 @@ class KTDocumentMetadataVersion extends KTEntity { function getMetadataVersion() { return $this->iMetadataVersion; } function setMetadataVersion($iNewValue) { $this->iMetadataVersion = $iNewValue; } function getContentVersionId() { return $this->iContentVersionId; } + function setContentVersionId($iNewValue) { $this->iContentVersionId = $iNewValue; } function setContentVersion($iNewValue) { $this->iContentVersion = $iNewValue; } function getDocumentTypeId() { return $this->iDocumentTypeId; } function setDocumentTypeId($iNewValue) { $this->iDocumentTypeId = $iNewValue; } diff --git a/lib/documentmanagement/documentutil.inc.php b/lib/documentmanagement/documentutil.inc.php index e6c7ef8..363fb41 100644 --- a/lib/documentmanagement/documentutil.inc.php +++ b/lib/documentmanagement/documentutil.inc.php @@ -627,6 +627,7 @@ class KTDocumentUtil { KTDocumentUtil::updateSearchableText($oDocument); } + function canBeMoved($oDocument) { if ($oDocument->getIsCheckedOut()) { return false; @@ -636,6 +637,78 @@ class KTDocumentUtil { } return true; } + + + function copy($oDocument, $oDestinationFolder) { + // 1. generate a new triad of content, metadata and core objects. + // 2. update the storage path. + + + // grab the "source "data + $sTable = KTUtil::getTableName('documents'); + $sQuery = 'SELECT * FROM ' . $sTable . ' WHERE id = ?'; + $aParams = array($oDocument->getId()); + $aCoreRow = DBUtil::getOneResult(array($sQuery, $aParams)); + unset($aCoreRow['id']); + + $aCoreRow['folder_id'] = $oDestinationFolder->getId(); // new location. + $id = DBUtil::autoInsert($sTable, $aCoreRow); + if (PEAR::isError($id)) { return $id; } + // we still have a bogus md_version, but integrity holds, so fix it now. + $oCore = KTDocumentCore::get($id); + + $sTable = KTUtil::getTableName('document_metadata_version'); + $sQuery = 'SELECT * FROM ' . $sTable . ' WHERE id = ?'; + $aParams = array($oDocument->getMetadataVersionId()); + $aMDRow = DBUtil::getOneResult(array($sQuery, $aParams)); + unset($aMDRow['id']); + $aMDRow['document_id'] = $oCore->getId(); + $id = DBUtil::autoInsert($sTable, $aMDRow); + if (PEAR::isError($id)) { return $id; } + $oCore->setMetadataVersionId($id); + $oMDV = KTDocumentMetadataVersion::get($id); + + $sTable = KTUtil::getTableName('document_content_version'); + $sQuery = 'SELECT * FROM ' . $sTable . ' WHERE id = ?'; + $aParams = array($oDocument->_oDocumentContentVersion->getId()); + $aContentRow = DBUtil::getOneResult(array($sQuery, $aParams)); + unset($aContentRow['id']); + $aContentRow['document_id'] = $oCore->getId(); + $id = DBUtil::autoInsert($sTable, $aContentRow); + if (PEAR::isError($id)) { return $id; } + $oMDV->setContentVersionId($id); + + $res = $oCore->update(); + if (PEAR::isError($res)) { return $res; } + $res = $oMDV->update(); + if (PEAR::isError($res)) { return $res; } + + // now, we have a semi-sane document object. get it. + $oNewDocument = Document::get($oCore->getId()); + + // copy the metadata from old to new. + $res = KTDocumentUtil::copyMetadata($oNewDocument, $oDocument->getMetadataVersionId()); + if (PEAR::isError($res)) { return $res; } + + // finally, copy the actual file. + $oStorage =& KTStorageManagerUtil::getSingleton(); + $res = $oStorage->copy($oDocument, $oNewDocument); + + + $oOriginalFolder = Folder::get($oDocument->getFolderId()); + $iOriginalFolderPermissionObjectId = $oOriginalFolder->getPermissionObjectId(); + $iDocumentPermissionObjectId = $oDocument->getPermissionObjectId(); + + if ($iDocumentPermissionObjectId === $iOriginalFolderPermissionObjectId) { + $oNewDocument->setPermissionObjectId($oDestinationFolder->getPermissionObjectId()); + } + + $res = $oNewDocument->update(); + if (PEAR::isError($res)) { return $res; } + + return $oNewDocument; + } + } class KTMetadataValidationError extends PEAR_Error { diff --git a/lib/storage/ondiskpathstoragemanager.inc.php b/lib/storage/ondiskpathstoragemanager.inc.php index dbb8aae..6811f09 100644 --- a/lib/storage/ondiskpathstoragemanager.inc.php +++ b/lib/storage/ondiskpathstoragemanager.inc.php @@ -239,7 +239,27 @@ class KTOnDiskPathStorageManager extends KTStorageManager { return KTUtil::moveDirectory($sSrc, $sDst); } - + /** + * Perform any storage changes necessary to account for a copied + * document object. + */ + function copy($oSrcDocument, &$oNewDocument) { + // we get the Folder object + $oVersion = $oNewDocument->_oDocumentContentVersion; + $oConfig =& KTConfig::getSingleton(); + $sDocumentRoot = $oConfig->get('urls/documentRoot'); + + $sOldPath = sprintf("%s/%s-%s", KTDocumentCore::_generateFolderPath($oSrcDocument->getFolderID()), $oSrcDocument->_oDocumentContentVersion->getId(), $oSrcDocument->_oDocumentContentVersion->getFileName()); + $sNewPath = sprintf("%s/%s-%s", KTDocumentCore::_generateFolderPath($oNewDocument->getFolderID()), $oNewDocument->_oDocumentContentVersion->getId(), $oNewDocument->_oDocumentContentVersion->getFileName()); + $sFullOldPath = sprintf("%s/%s", $sDocumentRoot, $sOldPath); + $sFullNewPath = sprintf("%s/%s", $sDocumentRoot, $sNewPath); + + $res = KTUtil::copyFile($sFullOldPath, $sFullNewPath); + if (PEAR::isError($res)) { return $res; } + $oVersion->setStoragePath($sNewPath); + $oVersion->update(); + } + /** * Deletes a document- moves it to the Deleted/ folder * diff --git a/lib/storage/storagemanager.inc.php b/lib/storage/storagemanager.inc.php index 48f973b..ae07b33 100644 --- a/lib/storage/storagemanager.inc.php +++ b/lib/storage/storagemanager.inc.php @@ -67,6 +67,14 @@ class KTStorageManager { } /** + * Perform any storage changes necessary to account for a copied + * document object. + */ + function copy ($oSrcDocument, &$oNewDocument) { + return PEAR::raiseError("Not implemented"); + } + + /** * Performs any storage changes necessary to account for the * document being marked as deleted. */ diff --git a/lib/util/ktutil.inc b/lib/util/ktutil.inc index 3c5413a..955aec1 100644 --- a/lib/util/ktutil.inc +++ b/lib/util/ktutil.inc @@ -318,6 +318,33 @@ class KTUtil { } } // }}} + + // {{{ copyFile + function copyFile ($sSrc, $sDst) { + // Only 4.3.3 and above allow us to use rename across partitions + // on Unix-like systems. + if (OS_UNIX) { + $aSrcStat = stat($sSrc); + if ($aSrcStat === false) { + return PEAR::raiseError("Couldn't stat source file: $sSrc"); + } + $aDstStat = stat(dirname($sDst)); + if ($aDstStat === false) { + return PEAR::raiseError("Couldn't stat destination location: $sDst"); + } + + $res = @copy($sSrc, $sDst); + if ($res === false) { + return PEAR::raiseError("Could not copy to destination: $sDst"); + } + } else { + $res = @copy($sSrc, $sDst); + if ($res === false) { + return PEAR::raiseError("Could not copy to destination: $sDst"); + } + } + } + // }}} // {{{ getTableName /** diff --git a/plugins/ktcore/KTCorePlugin.php b/plugins/ktcore/KTCorePlugin.php index 806f79a..01befb4 100644 --- a/plugins/ktcore/KTCorePlugin.php +++ b/plugins/ktcore/KTCorePlugin.php @@ -16,6 +16,7 @@ class KTCorePlugin extends KTPlugin { $this->registerAction('documentaction', 'KTDocumentEditAction', 'ktcore.actions.document.edit', 'KTDocumentActions.php'); $this->registerAction('documentaction', 'KTDocumentDeleteAction', 'ktcore.actions.document.delete', 'KTDocumentActions.php'); $this->registerAction('documentaction', 'KTDocumentMoveAction', 'ktcore.actions.document.move', 'KTDocumentActions.php'); + $this->registerAction('documentaction', 'KTDocumentCopyAction', 'ktcore.actions.document.copy', 'KTDocumentActions.php'); $this->registerAction('documentaction', 'KTDocumentTransactionHistoryAction', 'ktcore.actions.document.transactionhistory', 'KTDocumentActions.php'); $this->registerAction('documentaction', 'KTDocumentVersionHistoryAction', 'ktcore.actions.document.versionhistory', 'KTDocumentActions.php'); $this->registerAction('documentaction', 'KTDocumentArchiveAction', 'ktcore.actions.document.archive', 'KTDocumentActions.php'); diff --git a/plugins/ktcore/KTDocumentActions.php b/plugins/ktcore/KTDocumentActions.php index 7efe0b5..6ab84b0 100644 --- a/plugins/ktcore/KTDocumentActions.php +++ b/plugins/ktcore/KTDocumentActions.php @@ -553,6 +553,172 @@ class KTDocumentMoveAction extends KTDocumentAction { } // }}} + +class KTDocumentCopyColumn extends TitleColumn { + function KTDocumentCopyColumn($sLabel, $sName, $oDocument) { + $this->oDocument = $oDocument; + parent::TitleColumn($sLabel, $sName); + } + function buildFolderLink($aDataRow) { + return KTUtil::addQueryString($_SERVER['PHP_SELF'], sprintf('fDocumentId=%d&fFolderId=%d', $this->oDocument->getId(), $aDataRow["folder"]->getId())); + } +} + +// {{{ KTDocumentMoveAction +class KTDocumentCopyAction extends KTDocumentAction { + var $sDisplayName = 'Copy'; + var $sName = 'ktcore.actions.document.copy'; + + var $_sShowPermission = "ktcore.permissions.read"; + + function getInfo() { + if ($this->oDocument->getIsCheckedOut()) { + return null; + } + return parent::getInfo(); + } + + function check() { + $res = parent::check(); + if ($res !== true) { + return $res; + } + if ($this->oDocument->getIsCheckedOut()) { + $_SESSION["KTErrorMessage"][]= _("This document can't be copied because it is checked out"); + controllerRedirect('viewDocument', 'fDocumentId=' . $this->oDocument->getId()); + exit(0); + } + $iFolderId = KTUtil::arrayGet($_REQUEST, 'fFolderId', $this->oDocument->getFolderId()); + $this->oFolder = $this->oValidator->validateFolder($iFolderId); + $this->oDocumentFolder = $this->oValidator->validateFolder($this->oDocument->getFolderId()); + return true; + } + + function do_main() { + $this->oPage->setBreadcrumbDetails(_("Copy")); + $oTemplate =& $this->oValidator->validateTemplate('ktcore/action/copy'); + $move_fields = array(); + $aNames = $this->oDocumentFolder->getPathArray(); + $aNames[] = $this->oDocument->getName(); + $sDocumentName = join(" » ", $aNames); + $move_fields[] = new KTStaticTextWidget(_('Document to copy'), '', 'fDocumentId', $sDocumentName, $this->oPage, false); + + $collection = new DocumentCollection(); + $collection->addColumn(new KTDocumentMoveColumn("Test 1 (title)","title", $this->oDocument)); + $qObj = new FolderBrowseQuery($this->oFolder->getId()); + $collection->setQueryObject($qObj); + + $batchPage = (int) KTUtil::arrayGet($_REQUEST, "page", 0); + $batchSize = 20; + + $resultURL = KTUtil::addQueryString($_SERVER['PHP_SELF'], sprintf("fDocumentId=%d&fFolderId=%d", $this->oDocument->getId(), $this->oFolder->getId())); + $collection->setBatching($resultURL, $batchPage, $batchSize); + + // ordering. (direction and column) + $displayOrder = KTUtil::arrayGet($_REQUEST, 'sort_order', "asc"); + if ($displayOrder !== "asc") { $displayOrder = "desc"; } + $displayControl = KTUtil::arrayGet($_REQUEST, 'sort_on', "title"); + + $collection->setSorting($displayControl, $displayOrder); + + $collection->getResults(); + + $aBreadcrumbs = array(); + $folder_path_names = $this->oFolder->getPathArray(); + $folder_path_ids = explode(',', $this->oFolder->getParentFolderIds()); + $folder_path_ids[] = $this->oFolder->getId(); + if ($folder_path_ids[0] == 0) { + array_shift($folder_path_ids); + array_shift($folder_path_names); + } + + foreach (range(0, count($folder_path_ids) - 1) as $index) { + $id = $folder_path_ids[$index]; + $url = KTUtil::addQueryString($_SERVER['PHP_SELF'], sprintf("fDocumentId=%d&fFolderId=%d", $this->oDocument->getId(), $id)); + $aBreadcrumbs[] = array("url" => $url, "name" => $folder_path_names[$index]); + } + + $oTemplate->setData(array( + 'context' => &$this, + 'move_fields' => $move_fields, + 'collection' => $collection, + 'collection_breadcrumbs' => $aBreadcrumbs, + )); + return $oTemplate->render(); + } + + function do_copy() { + $this->oPage->setBreadcrumbDetails(_("Copy")); + $oTemplate =& $this->oValidator->validateTemplate('ktcore/action/copy_final'); + $sFolderPath = join(" » ", $this->oFolder->getPathArray()); + $aNames = $this->oDocumentFolder->getPathArray(); + $aNames[] = $this->oDocument->getName(); + $sDocumentName = join(" » ", $aNames); + $move_fields = array(); + $move_fields[] = new KTStaticTextWidget(_('Document to move'), '', 'fDocumentId', $sDocumentName, $this->oPage, false); + $move_fields[] = new KTStaticTextWidget(_('Target folder'), '', 'fFolderId', $sFolderPath, $this->oPage, false); + $move_fields[] = new KTStringWidget(_('Reason'), _('The reason for this document to be moved.'), 'reason', "", $this->oPage, true); + + $oTemplate->setData(array( + 'context' => &$this, + 'move_fields' => $move_fields, + )); + return $oTemplate->render(); + } + + function do_copy_final() { + $sReason = KTUtil::arrayGet($_REQUEST, 'reason'); + $aOptions = array( + 'message' => _("No reason given"), + 'redirect_to' => array('move', sprintf('fDocumentId=%d&fFolderId=%d', $this->oDocument->getId(), $this->oFolder->getId())), + ); + $this->oValidator->notEmpty($sReason, $aOptions); + + if (!Permission::userHasFolderWritePermission($this->oFolder)) { + $this->errorRedirectTo("main", _("You do not have permission to move a document to this location"), sprintf("fDocumentId=%d&fFolderId=%d", $this->oDocument->getId(), $this->oFolder->getId())); + exit(0); + } + + // FIXME agree on document-duplication rules re: naming, etc. + + $this->startTransaction(); + + $oNewDoc = KTDocumentUtil::copy($this->oDocument, $this->oFolder); + if (PEAR::isError($oNewDoc)) { + $this->errorRedirectTo("main", _("Failed to move document: ") . $oNewDoc->getMessage(), sprintf("fDocumentId=%d&fFolderId=%d", $this->oDocument->getId(), $this->oFolder->getId())); + exit(0); + } + + $this->commitTransaction(); + + + // FIXME do we need to refactor all trigger usage into the util function? + $oKTTriggerRegistry = KTTriggerRegistry::getSingleton(); + $aTriggers = $oKTTriggerRegistry->getTriggers('copyDocument', 'postValidate'); + foreach ($aTriggers as $aTrigger) { + $sTrigger = $aTrigger[0]; + $oTrigger = new $sTrigger; + $aInfo = array( + "document" => $oNewDocument, + "old_folder" => $this->oDocumentFolder, + "new_folder" => $this->oFolder, + ); + $oTrigger->setInfo($aInfo); + $ret = $oTrigger->postValidate(); + } + + $aOptions = array('user' => $oUser); + $oDocumentTransaction = & new DocumentTransaction($oNewDoc, "Document copied from old version.", 'ktcore.transactions.create', $aOptions); + $res = $oDocumentTransaction->create(); + + $_SESSION['KTInfoMessage'][] = _('Document copied.'); + + controllerRedirect('viewDocument', 'fDocumentId=' . $oNewDoc->getId()); + exit(0); + } +} +// }}} + // {{{ KTDocumentHistoryAction class KTDocumentTransactionHistoryAction extends KTDocumentAction { var $sDisplayName = 'Transaction History';