mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 00:32:42 +01:00
Update "Files" attachment table to show more attachment details and support detachment
Summary: Ref T13682. Make the "Attached" list in Files a bit more detailed, and add a "Detach" button. Test Plan: Tried to detach unrelated, referenced, and attached files. Saw attached files detach. Maniphest Tasks: T13682 Differential Revision: https://secure.phabricator.com/D21840
This commit is contained in:
parent
5aa159a830
commit
7e5f7b9640
7 changed files with 231 additions and 17 deletions
|
@ -3464,6 +3464,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorFileDataController' => 'applications/files/controller/PhabricatorFileDataController.php',
|
'PhabricatorFileDataController' => 'applications/files/controller/PhabricatorFileDataController.php',
|
||||||
'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php',
|
'PhabricatorFileDeleteController' => 'applications/files/controller/PhabricatorFileDeleteController.php',
|
||||||
'PhabricatorFileDeleteTransaction' => 'applications/files/xaction/PhabricatorFileDeleteTransaction.php',
|
'PhabricatorFileDeleteTransaction' => 'applications/files/xaction/PhabricatorFileDeleteTransaction.php',
|
||||||
|
'PhabricatorFileDetachController' => 'applications/files/controller/PhabricatorFileDetachController.php',
|
||||||
'PhabricatorFileDocumentController' => 'applications/files/controller/PhabricatorFileDocumentController.php',
|
'PhabricatorFileDocumentController' => 'applications/files/controller/PhabricatorFileDocumentController.php',
|
||||||
'PhabricatorFileDocumentRenderingEngine' => 'applications/files/document/render/PhabricatorFileDocumentRenderingEngine.php',
|
'PhabricatorFileDocumentRenderingEngine' => 'applications/files/document/render/PhabricatorFileDocumentRenderingEngine.php',
|
||||||
'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php',
|
'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php',
|
||||||
|
@ -9919,6 +9920,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorFileDataController' => 'PhabricatorFileController',
|
'PhabricatorFileDataController' => 'PhabricatorFileController',
|
||||||
'PhabricatorFileDeleteController' => 'PhabricatorFileController',
|
'PhabricatorFileDeleteController' => 'PhabricatorFileController',
|
||||||
'PhabricatorFileDeleteTransaction' => 'PhabricatorFileTransactionType',
|
'PhabricatorFileDeleteTransaction' => 'PhabricatorFileTransactionType',
|
||||||
|
'PhabricatorFileDetachController' => 'PhabricatorFileController',
|
||||||
'PhabricatorFileDocumentController' => 'PhabricatorFileController',
|
'PhabricatorFileDocumentController' => 'PhabricatorFileController',
|
||||||
'PhabricatorFileDocumentRenderingEngine' => 'PhabricatorDocumentRenderingEngine',
|
'PhabricatorFileDocumentRenderingEngine' => 'PhabricatorDocumentRenderingEngine',
|
||||||
'PhabricatorFileDropUploadController' => 'PhabricatorFileController',
|
'PhabricatorFileDropUploadController' => 'PhabricatorFileController',
|
||||||
|
|
|
@ -96,6 +96,8 @@ final class PhabricatorFilesApplication extends PhabricatorApplication {
|
||||||
'document/(?P<engineKey>[^/]+)/(?P<phid>[^/]+)/'
|
'document/(?P<engineKey>[^/]+)/(?P<phid>[^/]+)/'
|
||||||
=> 'PhabricatorFileDocumentController',
|
=> 'PhabricatorFileDocumentController',
|
||||||
'ui/' => array(
|
'ui/' => array(
|
||||||
|
'detach/(?P<objectPHID>[^/]+)/(?P<filePHID>[^/]+)/'
|
||||||
|
=> 'PhabricatorFileDetachController',
|
||||||
'curtain/' => array(
|
'curtain/' => array(
|
||||||
'list/(?P<phid>[^/]+)/'
|
'list/(?P<phid>[^/]+)/'
|
||||||
=> 'PhabricatorFileUICurtainListController',
|
=> 'PhabricatorFileUICurtainListController',
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorFileDetachController
|
||||||
|
extends PhabricatorFileController {
|
||||||
|
|
||||||
|
public function handleRequest(AphrontRequest $request) {
|
||||||
|
$viewer = $request->getViewer();
|
||||||
|
|
||||||
|
$object_phid = $request->getURIData('objectPHID');
|
||||||
|
$file_phid = $request->getURIData('filePHID');
|
||||||
|
|
||||||
|
$object = id(new PhabricatorObjectQuery())
|
||||||
|
->setViewer($viewer)
|
||||||
|
->withPHIDs(array($object_phid))
|
||||||
|
->executeOne();
|
||||||
|
if (!$object) {
|
||||||
|
return new Aphront404Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
$handles = $viewer->loadHandles(
|
||||||
|
array(
|
||||||
|
$object_phid,
|
||||||
|
$file_phid,
|
||||||
|
));
|
||||||
|
|
||||||
|
$object_handle = $handles[$object_phid];
|
||||||
|
$file_handle = $handles[$file_phid];
|
||||||
|
$cancel_uri = $file_handle->getURI();
|
||||||
|
|
||||||
|
$dialog = $this->newDialog()
|
||||||
|
->setViewer($viewer)
|
||||||
|
->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());
|
||||||
|
|
||||||
|
$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 not attached to the object %s.',
|
||||||
|
$file_link,
|
||||||
|
$object_link);
|
||||||
|
|
||||||
|
return $dialog->appendParagraph($body);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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->appendParagraph($body);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$attachment->canDetach()) {
|
||||||
|
$body = pht(
|
||||||
|
'The file %s can not be detached from the object %s.',
|
||||||
|
$file_link,
|
||||||
|
$object_link);
|
||||||
|
|
||||||
|
return $dialog->appendParagraph($body);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$request->isDialogFormPost()) {
|
||||||
|
$dialog->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'Detach the file %s from the object %s?',
|
||||||
|
$file_link,
|
||||||
|
$object_link));
|
||||||
|
|
||||||
|
$dialog->addSubmitButton(pht('Detach File'));
|
||||||
|
|
||||||
|
return $dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($object instanceof PhabricatorApplicationTransactionInterface)) {
|
||||||
|
$dialog->appendParagraph(
|
||||||
|
pht(
|
||||||
|
'This object (of class "%s") does not implement the required '.
|
||||||
|
'interface ("%s"), so files can not be manually detached from it.',
|
||||||
|
get_class($object),
|
||||||
|
'PhabricatorApplicationTransactionInterface'));
|
||||||
|
|
||||||
|
return $dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
$editor = $object->getApplicationTransactionEditor()
|
||||||
|
->setActor($viewer)
|
||||||
|
->setContentSourceFromRequest($request)
|
||||||
|
->setContinueOnNoEffect(true)
|
||||||
|
->setContinueOnMissingFields(true);
|
||||||
|
|
||||||
|
$template = $object->getApplicationTransactionTemplate();
|
||||||
|
|
||||||
|
$xactions = array();
|
||||||
|
|
||||||
|
$xactions[] = id(clone $template)
|
||||||
|
->setTransactionType(PhabricatorTransactions::TYPE_FILE)
|
||||||
|
->setNewValue(
|
||||||
|
array(
|
||||||
|
$file_phid => PhabricatorFileAttachment::MODE_DETACH,
|
||||||
|
));
|
||||||
|
|
||||||
|
$editor->applyTransactions($object, $xactions);
|
||||||
|
|
||||||
|
return $this->newRedirect()
|
||||||
|
->setURI($cancel_uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -28,9 +28,6 @@ final class PhabricatorFileUICurtainAttachController
|
||||||
return new Aphront404Response();
|
return new Aphront404Response();
|
||||||
}
|
}
|
||||||
|
|
||||||
$file = $attachment->getFile();
|
|
||||||
$file_phid = $file->getPHID();
|
|
||||||
|
|
||||||
$handles = $viewer->loadHandles(
|
$handles = $viewer->loadHandles(
|
||||||
array(
|
array(
|
||||||
$object_phid,
|
$object_phid,
|
||||||
|
@ -44,7 +41,7 @@ final class PhabricatorFileUICurtainAttachController
|
||||||
$dialog = $this->newDialog()
|
$dialog = $this->newDialog()
|
||||||
->setViewer($viewer)
|
->setViewer($viewer)
|
||||||
->setTitle(pht('Attach File'))
|
->setTitle(pht('Attach File'))
|
||||||
->addCancelButton($object_handle->getURI(), pht('Close'));
|
->addCancelButton($cancel_uri, pht('Close'));
|
||||||
|
|
||||||
$file_link = phutil_tag('strong', array(), $file_handle->renderLink());
|
$file_link = phutil_tag('strong', array(), $file_handle->renderLink());
|
||||||
$object_link = phutil_tag('strong', array(), $object_handle->renderLink());
|
$object_link = phutil_tag('strong', array(), $object_handle->renderLink());
|
||||||
|
|
|
@ -320,20 +320,13 @@ final class PhabricatorFileViewController extends PhabricatorFileController {
|
||||||
$finfo->addProperty(pht('Default Alt Text'), $default_alt);
|
$finfo->addProperty(pht('Default Alt Text'), $default_alt);
|
||||||
}
|
}
|
||||||
|
|
||||||
$phids = $file->getObjectPHIDs();
|
$attachments_table = $this->newAttachmentsView($file);
|
||||||
if ($phids) {
|
|
||||||
$attached = new PHUIPropertyListView();
|
|
||||||
|
|
||||||
$tab_group->addTab(
|
$tab_group->addTab(
|
||||||
id(new PHUITabView())
|
id(new PHUITabView())
|
||||||
->setName(pht('Attached'))
|
->setName(pht('Attached'))
|
||||||
->setKey('attached')
|
->setKey('attached')
|
||||||
->appendChild($attached));
|
->appendChild($attachments_table));
|
||||||
|
|
||||||
$attached->addProperty(
|
|
||||||
pht('Attached To'),
|
|
||||||
$viewer->renderHandleList($phids));
|
|
||||||
}
|
|
||||||
|
|
||||||
$engine = $this->loadStorageEngine($file);
|
$engine = $this->loadStorageEngine($file);
|
||||||
if ($engine) {
|
if ($engine) {
|
||||||
|
@ -420,4 +413,81 @@ final class PhabricatorFileViewController extends PhabricatorFileController {
|
||||||
return $engine->newDocumentView($ref);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,13 @@ final class PhabricatorFileAttachment
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getModeNameMap() {
|
||||||
|
return array(
|
||||||
|
self::MODE_ATTACH => pht('Attached'),
|
||||||
|
self::MODE_REFERENCE => pht('Referenced'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function isPolicyAttachment() {
|
public function isPolicyAttachment() {
|
||||||
switch ($this->getAttachmentMode()) {
|
switch ($this->getAttachmentMode()) {
|
||||||
case self::MODE_ATTACH:
|
case self::MODE_ATTACH:
|
||||||
|
@ -73,6 +80,15 @@ final class PhabricatorFileAttachment
|
||||||
return $this->assertAttached($this->file);
|
return $this->assertAttached($this->file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function canDetach() {
|
||||||
|
switch ($this->getAttachmentMode()) {
|
||||||
|
case self::MODE_ATTACH:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||||
|
|
||||||
|
|
|
@ -1803,6 +1803,13 @@ final class PhabricatorUSEnglishTranslation
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
'%s removed %s attached file(s): %s.' => array(
|
||||||
|
array(
|
||||||
|
'%s removed an attached file: %3$s.',
|
||||||
|
'%s removed attached files: %3$s.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue