diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3464,6 +3464,7 @@ 'PhabricatorFileDataController' => 'applications/files/controller/PhabricatorFileDataController.php', 'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php', 'PhabricatorFileDeleteTransaction' => 'applications/files/xaction/PhabricatorFileDeleteTransaction.php', + 'PhabricatorFileDetachController' => 'applications/files/controller/PhabricatorFileDetachController.php', 'PhabricatorFileDocumentController' => 'applications/files/controller/PhabricatorFileDocumentController.php', 'PhabricatorFileDocumentRenderingEngine' => 'applications/files/document/render/PhabricatorFileDocumentRenderingEngine.php', 'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php', @@ -9919,6 +9920,7 @@ 'PhabricatorFileDataController' => 'PhabricatorFileController', 'PhabricatorFileDeleteController' => 'PhabricatorFileController', 'PhabricatorFileDeleteTransaction' => 'PhabricatorFileTransactionType', + 'PhabricatorFileDetachController' => 'PhabricatorFileController', 'PhabricatorFileDocumentController' => 'PhabricatorFileController', 'PhabricatorFileDocumentRenderingEngine' => 'PhabricatorDocumentRenderingEngine', 'PhabricatorFileDropUploadController' => 'PhabricatorFileController', diff --git a/src/applications/files/application/PhabricatorFilesApplication.php b/src/applications/files/application/PhabricatorFilesApplication.php --- a/src/applications/files/application/PhabricatorFilesApplication.php +++ b/src/applications/files/application/PhabricatorFilesApplication.php @@ -96,6 +96,8 @@ 'document/(?P[^/]+)/(?P[^/]+)/' => 'PhabricatorFileDocumentController', 'ui/' => array( + 'detach/(?P[^/]+)/(?P[^/]+)/' + => 'PhabricatorFileDetachController', 'curtain/' => array( 'list/(?P[^/]+)/' => 'PhabricatorFileUICurtainListController', diff --git a/src/applications/files/controller/PhabricatorFileUICurtainAttachController.php b/src/applications/files/controller/PhabricatorFileDetachController.php copy from src/applications/files/controller/PhabricatorFileUICurtainAttachController.php copy to src/applications/files/controller/PhabricatorFileDetachController.php --- a/src/applications/files/controller/PhabricatorFileUICurtainAttachController.php +++ b/src/applications/files/controller/PhabricatorFileDetachController.php @@ -1,6 +1,6 @@ setViewer($viewer) - ->withObjectPHIDs(array($object->getPHID())) - ->withFilePHIDs(array($file_phid)) - ->needFiles(true) - ->withVisibleFiles(true) - ->executeOne(); - if (!$attachment) { - return new Aphront404Response(); - } - - $file = $attachment->getFile(); - $file_phid = $file->getPHID(); - $handles = $viewer->loadHandles( array( $object_phid, @@ -39,62 +25,60 @@ $object_handle = $handles[$object_phid]; $file_handle = $handles[$file_phid]; - $cancel_uri = $object_handle->getURI(); + $cancel_uri = $file_handle->getURI(); $dialog = $this->newDialog() ->setViewer($viewer) - ->setTitle(pht('Attach File')) - ->addCancelButton($object_handle->getURI(), pht('Close')); + ->setTitle(pht('Detach File')) + ->addCancelButton($cancel_uri, pht('Close')); $file_link = phutil_tag('strong', array(), $file_handle->renderLink()); $object_link = phutil_tag('strong', array(), $object_handle->renderLink()); - if ($attachment->isPolicyAttachment()) { + $attachment = id(new PhabricatorFileAttachmentQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($object->getPHID())) + ->withFilePHIDs(array($file_phid)) + ->needFiles(true) + ->withVisibleFiles(true) + ->executeOne(); + if (!$attachment) { $body = pht( - 'The file %s is already attached to the object %s.', + 'The file %s is not attached to the object %s.', $file_link, $object_link); return $dialog->appendParagraph($body); } - if (!$request->isDialogFormPost()) { - $dialog->appendRemarkup( - pht( - '(WARNING) This file is referenced by this object, but '. - 'not formally attached to it. Users who can see the object may '. - 'not be able to see the file.')); - - $dialog->appendParagraph( - pht( - 'Do you want to attach the file %s to the object %s?', - $file_link, - $object_link)); - - $dialog->addSubmitButton(pht('Attach File')); + $mode_reference = PhabricatorFileAttachment::MODE_REFERENCE; + if ($attachment->getAttachmentMode() === $mode_reference) { + $body = pht( + 'The file %s is referenced by the object %s, but not attached to '. + 'it, so it can not be detached.', + $file_link, + $object_link); - return $dialog; + return $dialog->appendParagraph($body); } - if (!$request->getBool('confirm')) { - $dialog->setTitle(pht('Confirm File Attachment')); - - $dialog->addHiddenInput('confirm', 1); + if (!$attachment->canDetach()) { + $body = pht( + 'The file %s can not be detached from the object %s.', + $file_link, + $object_link); - $dialog->appendRemarkup( - pht( - '(IMPORTANT) If you attach this file to this object, any user who '. - 'has permission to view the object will be able to view and '. - 'download the file!')); + return $dialog->appendParagraph($body); + } + if (!$request->isDialogFormPost()) { $dialog->appendParagraph( pht( - 'Really attach the file %s to the object %s, allowing any user '. - 'who can view the object to view and download the file?', + 'Detach the file %s from the object %s?', $file_link, $object_link)); - $dialog->addSubmitButton(pht('Grant Permission')); + $dialog->addSubmitButton(pht('Detach File')); return $dialog; } @@ -103,7 +87,7 @@ $dialog->appendParagraph( pht( 'This object (of class "%s") does not implement the required '. - 'interface ("%s"), so files can not be manually attached to it.', + 'interface ("%s"), so files can not be manually detached from it.', get_class($object), 'PhabricatorApplicationTransactionInterface')); @@ -124,7 +108,7 @@ ->setTransactionType(PhabricatorTransactions::TYPE_FILE) ->setNewValue( array( - $file_phid => PhabricatorFileAttachment::MODE_ATTACH, + $file_phid => PhabricatorFileAttachment::MODE_DETACH, )); $editor->applyTransactions($object, $xactions); diff --git a/src/applications/files/controller/PhabricatorFileUICurtainAttachController.php b/src/applications/files/controller/PhabricatorFileUICurtainAttachController.php --- a/src/applications/files/controller/PhabricatorFileUICurtainAttachController.php +++ b/src/applications/files/controller/PhabricatorFileUICurtainAttachController.php @@ -28,9 +28,6 @@ return new Aphront404Response(); } - $file = $attachment->getFile(); - $file_phid = $file->getPHID(); - $handles = $viewer->loadHandles( array( $object_phid, @@ -44,7 +41,7 @@ $dialog = $this->newDialog() ->setViewer($viewer) ->setTitle(pht('Attach File')) - ->addCancelButton($object_handle->getURI(), pht('Close')); + ->addCancelButton($cancel_uri, pht('Close')); $file_link = phutil_tag('strong', array(), $file_handle->renderLink()); $object_link = phutil_tag('strong', array(), $object_handle->renderLink()); diff --git a/src/applications/files/controller/PhabricatorFileViewController.php b/src/applications/files/controller/PhabricatorFileViewController.php --- a/src/applications/files/controller/PhabricatorFileViewController.php +++ b/src/applications/files/controller/PhabricatorFileViewController.php @@ -320,20 +320,13 @@ $finfo->addProperty(pht('Default Alt Text'), $default_alt); } - $phids = $file->getObjectPHIDs(); - if ($phids) { - $attached = new PHUIPropertyListView(); - - $tab_group->addTab( - id(new PHUITabView()) - ->setName(pht('Attached')) - ->setKey('attached') - ->appendChild($attached)); - - $attached->addProperty( - pht('Attached To'), - $viewer->renderHandleList($phids)); - } + $attachments_table = $this->newAttachmentsView($file); + + $tab_group->addTab( + id(new PHUITabView()) + ->setName(pht('Attached')) + ->setKey('attached') + ->appendChild($attachments_table)); $engine = $this->loadStorageEngine($file); if ($engine) { @@ -420,4 +413,81 @@ return $engine->newDocumentView($ref); } + private function newAttachmentsView(PhabricatorFile $file) { + $viewer = $this->getViewer(); + + $attachments = id(new PhabricatorFileAttachmentQuery()) + ->setViewer($viewer) + ->withFilePHIDs(array($file->getPHID())) + ->execute(); + + $handles = $viewer->loadHandles(mpull($attachments, 'getObjectPHID')); + + $rows = array(); + + $mode_map = PhabricatorFileAttachment::getModeNameMap(); + $mode_attach = PhabricatorFileAttachment::MODE_ATTACH; + + foreach ($attachments as $attachment) { + $object_phid = $attachment->getObjectPHID(); + $handle = $handles[$object_phid]; + + $attachment_mode = $attachment->getAttachmentMode(); + + $mode_name = idx($mode_map, $attachment_mode); + if ($mode_name === null) { + $mode_name = pht('Unknown ("%s")', $attachment_mode); + } + + $detach_uri = urisprintf( + '/file/ui/detach/%s/%s/', + $object_phid, + $file->getPHID()); + + $is_disabled = !$attachment->canDetach(); + + $detach_button = id(new PHUIButtonView()) + ->setHref($detach_uri) + ->setTag('a') + ->setWorkflow(true) + ->setDisabled($is_disabled) + ->setColor(PHUIButtonView::GREY) + ->setSize(PHUIButtonView::SMALL) + ->setText(pht('Detach File')); + + javelin_tag( + 'a', + array( + 'href' => $detach_uri, + 'sigil' => 'workflow', + 'disabled' => true, + 'class' => 'small button button-grey disabled', + ), + pht('Detach File')); + + $rows[] = array( + $handle->renderLink(), + $mode_name, + $detach_button, + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + pht('Attached To'), + pht('Mode'), + null, + )) + ->setColumnClasses( + array( + 'pri wide', + null, + null, + )); + + return $table; + } + + } diff --git a/src/applications/files/storage/PhabricatorFileAttachment.php b/src/applications/files/storage/PhabricatorFileAttachment.php --- a/src/applications/files/storage/PhabricatorFileAttachment.php +++ b/src/applications/files/storage/PhabricatorFileAttachment.php @@ -46,6 +46,13 @@ ); } + public static function getModeNameMap() { + return array( + self::MODE_ATTACH => pht('Attached'), + self::MODE_REFERENCE => pht('Referenced'), + ); + } + public function isPolicyAttachment() { switch ($this->getAttachmentMode()) { case self::MODE_ATTACH: @@ -73,6 +80,15 @@ return $this->assertAttached($this->file); } + public function canDetach() { + switch ($this->getAttachmentMode()) { + case self::MODE_ATTACH: + return true; + } + + return false; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1803,6 +1803,13 @@ ), ), + '%s removed %s attached file(s): %s.' => array( + array( + '%s removed an attached file: %3$s.', + '%s removed attached files: %3$s.', + ), + ), + ); }