@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Provide a simple "Attach File" explicit workflow for files referenced but not attached

Summary: Ref T13682. Allow users to manually attach files which are referenced (but not attached) via the UI.

Test Plan: Reference files via `{F...}`, then attached them via the UI workflow.

Maniphest Tasks: T13682

Differential Revision: https://secure.phabricator.com/D21837

+464 -23
+3 -3
resources/celerity/map.php
··· 9 9 'names' => array( 10 10 'conpherence.pkg.css' => '0e3cf785', 11 11 'conpherence.pkg.js' => '020aebcf', 12 - 'core.pkg.css' => '00a2e7f4', 12 + 'core.pkg.css' => 'b816811e', 13 13 'core.pkg.js' => 'd2de90d9', 14 14 'dark-console.pkg.js' => '187792c2', 15 15 'differential.pkg.css' => 'ffb69e3d', ··· 147 147 'rsrc/css/phui/phui-comment-form.css' => '68a2d99a', 148 148 'rsrc/css/phui/phui-comment-panel.css' => 'ec4e31c0', 149 149 'rsrc/css/phui/phui-crumbs-view.css' => '614f43cf', 150 - 'rsrc/css/phui/phui-curtain-object-ref-view.css' => '5f752bdb', 150 + 'rsrc/css/phui/phui-curtain-object-ref-view.css' => '51d93266', 151 151 'rsrc/css/phui/phui-curtain-view.css' => '68c5efb6', 152 152 'rsrc/css/phui/phui-document-pro.css' => 'b9613a10', 153 153 'rsrc/css/phui/phui-document-summary.css' => 'b068eed1', ··· 838 838 'phui-comment-form-css' => '68a2d99a', 839 839 'phui-comment-panel-css' => 'ec4e31c0', 840 840 'phui-crumbs-view-css' => '614f43cf', 841 - 'phui-curtain-object-ref-view-css' => '5f752bdb', 841 + 'phui-curtain-object-ref-view-css' => '51d93266', 842 842 'phui-curtain-view-css' => '68c5efb6', 843 843 'phui-document-summary-view-css' => 'b068eed1', 844 844 'phui-document-view-css' => '52b748a5',
+2
src/__phutil_library_map__.php
··· 3508 3508 'PhabricatorFileTransformController' => 'applications/files/controller/PhabricatorFileTransformController.php', 3509 3509 'PhabricatorFileTransformListController' => 'applications/files/controller/PhabricatorFileTransformListController.php', 3510 3510 'PhabricatorFileTransformTestCase' => 'applications/files/transform/__tests__/PhabricatorFileTransformTestCase.php', 3511 + 'PhabricatorFileUICurtainAttachController' => 'applications/files/controller/PhabricatorFileUICurtainAttachController.php', 3511 3512 'PhabricatorFileUICurtainListController' => 'applications/files/controller/PhabricatorFileUICurtainListController.php', 3512 3513 'PhabricatorFileUploadController' => 'applications/files/controller/PhabricatorFileUploadController.php', 3513 3514 'PhabricatorFileUploadDialogController' => 'applications/files/controller/PhabricatorFileUploadDialogController.php', ··· 9972 9973 'PhabricatorFileTransformController' => 'PhabricatorFileController', 9973 9974 'PhabricatorFileTransformListController' => 'PhabricatorFileController', 9974 9975 'PhabricatorFileTransformTestCase' => 'PhabricatorTestCase', 9976 + 'PhabricatorFileUICurtainAttachController' => 'PhabricatorFileController', 9975 9977 'PhabricatorFileUICurtainListController' => 'PhabricatorFileController', 9976 9978 'PhabricatorFileUploadController' => 'PhabricatorFileController', 9977 9979 'PhabricatorFileUploadDialogController' => 'PhabricatorFileController',
+4
src/applications/base/controller/PhabricatorController.php
··· 420 420 ->setSubmitURI($submit_uri); 421 421 } 422 422 423 + public function newRedirect() { 424 + return id(new AphrontRedirectResponse()); 425 + } 426 + 423 427 public function newPage() { 424 428 $page = id(new PhabricatorStandardPageView()) 425 429 ->setRequest($this->getRequest())
+8 -2
src/applications/files/application/PhabricatorFilesApplication.php
··· 95 95 ), 96 96 'document/(?P<engineKey>[^/]+)/(?P<phid>[^/]+)/' 97 97 => 'PhabricatorFileDocumentController', 98 - 'ui/curtainlist/(?P<phid>[^/]+)/' 99 - => 'PhabricatorFileUICurtainListController', 98 + 'ui/' => array( 99 + 'curtain/' => array( 100 + 'list/(?P<phid>[^/]+)/' 101 + => 'PhabricatorFileUICurtainListController', 102 + 'attach/(?P<objectPHID>[^/]+)/(?P<filePHID>[^/]+)/' 103 + => 'PhabricatorFileUICurtainAttachController', 104 + ), 105 + ), 100 106 ) + $this->getResourceSubroutes(), 101 107 ); 102 108 }
+136
src/applications/files/controller/PhabricatorFileUICurtainAttachController.php
··· 1 + <?php 2 + 3 + final class PhabricatorFileUICurtainAttachController 4 + extends PhabricatorFileController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getViewer(); 8 + 9 + $object_phid = $request->getURIData('objectPHID'); 10 + $file_phid = $request->getURIData('filePHID'); 11 + 12 + $object = id(new PhabricatorObjectQuery()) 13 + ->setViewer($viewer) 14 + ->withPHIDs(array($object_phid)) 15 + ->executeOne(); 16 + if (!$object) { 17 + return new Aphront404Response(); 18 + } 19 + 20 + $attachment = id(new PhabricatorFileAttachmentQuery()) 21 + ->setViewer($viewer) 22 + ->withObjectPHIDs(array($object->getPHID())) 23 + ->withFilePHIDs(array($file_phid)) 24 + ->needFiles(true) 25 + ->withVisibleFiles(true) 26 + ->executeOne(); 27 + if (!$attachment) { 28 + return new Aphront404Response(); 29 + } 30 + 31 + $file = $attachment->getFile(); 32 + $file_phid = $file->getPHID(); 33 + 34 + $handles = $viewer->loadHandles( 35 + array( 36 + $object_phid, 37 + $file_phid, 38 + )); 39 + 40 + $object_handle = $handles[$object_phid]; 41 + $file_handle = $handles[$file_phid]; 42 + $cancel_uri = $object_handle->getURI(); 43 + 44 + $dialog = $this->newDialog() 45 + ->setViewer($viewer) 46 + ->setTitle(pht('Attach File')) 47 + ->addCancelButton($object_handle->getURI(), pht('Close')); 48 + 49 + $file_link = phutil_tag('strong', array(), $file_handle->renderLink()); 50 + $object_link = phutil_tag('strong', array(), $object_handle->renderLink()); 51 + 52 + if ($attachment->isPolicyAttachment()) { 53 + $body = pht( 54 + 'The file %s is already attached to the object %s.', 55 + $file_link, 56 + $object_link); 57 + 58 + return $dialog->appendParagraph($body); 59 + } 60 + 61 + if (!$request->isDialogFormPost()) { 62 + $dialog->appendRemarkup( 63 + pht( 64 + '(WARNING) This file is referenced by this object, but '. 65 + 'not formally attached to it. Users who can see the object may '. 66 + 'not be able to see the file.')); 67 + 68 + $dialog->appendParagraph( 69 + pht( 70 + 'Do you want to attach the file %s to the object %s?', 71 + $file_link, 72 + $object_link)); 73 + 74 + $dialog->addSubmitButton(pht('Attach File')); 75 + 76 + return $dialog; 77 + } 78 + 79 + if (!$request->getBool('confirm')) { 80 + $dialog->setTitle(pht('Confirm File Attachment')); 81 + 82 + $dialog->addHiddenInput('confirm', 1); 83 + 84 + $dialog->appendRemarkup( 85 + pht( 86 + '(IMPORTANT) If you attach this file to this object, any user who '. 87 + 'has permission to view the object will be able to view and '. 88 + 'download the file!')); 89 + 90 + $dialog->appendParagraph( 91 + pht( 92 + 'Really attach the file %s to the object %s, allowing any user '. 93 + 'who can view the object to view and download the file?', 94 + $file_link, 95 + $object_link)); 96 + 97 + $dialog->addSubmitButton(pht('Grant Permission')); 98 + 99 + return $dialog; 100 + } 101 + 102 + if (!($object instanceof PhabricatorApplicationTransactionInterface)) { 103 + $dialog->appendParagraph( 104 + pht( 105 + 'This object (of class "%s") does not implement the required '. 106 + 'interface ("%s"), so files can not be manually attached to it.', 107 + get_class($object), 108 + 'PhabricatorApplicationTransactionInterface')); 109 + 110 + return $dialog; 111 + } 112 + 113 + $editor = $object->getApplicationTransactionEditor() 114 + ->setActor($viewer) 115 + ->setContentSourceFromRequest($request) 116 + ->setContinueOnNoEffect(true) 117 + ->setContinueOnMissingFields(true); 118 + 119 + $template = $object->getApplicationTransactionTemplate(); 120 + 121 + $xactions = array(); 122 + 123 + $xactions[] = id(clone $template) 124 + ->setTransactionType(PhabricatorTransactions::TYPE_FILE) 125 + ->setNewValue( 126 + array( 127 + $file_phid => PhabricatorFileAttachment::MODE_ATTACH, 128 + )); 129 + 130 + $editor->applyTransactions($object, $xactions); 131 + 132 + return $this->newRedirect() 133 + ->setURI($cancel_uri); 134 + } 135 + 136 + }
+1 -1
src/applications/files/controller/PhabricatorFileUICurtainListController.php
··· 53 53 return $this->newDialog() 54 54 ->setViewer($viewer) 55 55 ->setWidth(AphrontDialogView::WIDTH_FORM) 56 - ->setTitle(pht('Attached Files')) 56 + ->setTitle(pht('Referenced Files')) 57 57 ->setObjectList($list) 58 58 ->addCancelButton($object_handle->getURI(), pht('Close')); 59 59 }
+40 -10
src/applications/files/engineextension/PhabricatorFilesCurtainExtension.php
··· 34 34 35 35 $handles = $viewer->loadHandles($visible_phids); 36 36 37 - PhabricatorPolicyFilterSet::loadHandleViewCapabilities( 38 - $viewer, 39 - $handles, 40 - array($object)); 41 - 42 37 $ref_list = id(new PHUICurtainObjectRefListView()) 43 38 ->setViewer($viewer) 44 39 ->setEmptyMessage(pht('None')); 45 40 41 + $view_capability = PhabricatorPolicyCapability::CAN_VIEW; 42 + $object_policies = PhabricatorPolicyQuery::loadPolicies( 43 + $viewer, 44 + $object); 45 + $object_policy = idx($object_policies, $view_capability); 46 + 46 47 foreach ($visible_attachments as $attachment) { 47 48 $file_phid = $attachment->getFilePHID(); 48 49 $handle = $handles[$file_phid]; ··· 50 51 $ref = $ref_list->newObjectRefView() 51 52 ->setHandle($handle); 52 53 53 - if ($handle->hasCapabilities()) { 54 - if (!$handle->hasViewCapability($object)) { 55 - $ref->setExiled(true); 54 + $file = $attachment->getFile(); 55 + if (!$file) { 56 + // ... 57 + } else { 58 + if (!$attachment->isPolicyAttachment()) { 59 + $file_policies = PhabricatorPolicyQuery::loadPolicies( 60 + $viewer, 61 + $file); 62 + $file_policy = idx($file_policies, $view_capability); 63 + 64 + if ($object_policy->isStrongerThanOrEqualTo($file_policy)) { 65 + // The file is not attached to the object, but the file policy 66 + // allows anyone who can see the object to see the file too, so 67 + // there is no material problem with the file not being attached. 68 + } else { 69 + $attach_uri = urisprintf( 70 + '/file/ui/curtain/attach/%s/%s/', 71 + $object->getPHID(), 72 + $file->getPHID()); 73 + 74 + $attached_link = javelin_tag( 75 + 'a', 76 + array( 77 + 'href' => $attach_uri, 78 + 'sigil' => 'workflow', 79 + ), 80 + pht('File Not Attached')); 81 + 82 + $ref->setExiled( 83 + true, 84 + $attached_link); 85 + } 56 86 } 57 87 } 58 88 ··· 63 93 $show_all = (count($visible_attachments) < count($attachments)); 64 94 if ($show_all) { 65 95 $view_all_uri = urisprintf( 66 - '/file/ui/curtainlist/%s/', 96 + '/file/ui/curtain/list/%s/', 67 97 $object->getPHID()); 68 98 69 99 $loaded_count = count($attachments); ··· 80 110 } 81 111 82 112 return $this->newPanel() 83 - ->setHeaderText(pht('Attached Files')) 113 + ->setHeaderText(pht('Referenced Files')) 84 114 ->setOrder(15000) 85 115 ->appendChild($ref_list); 86 116 }
+3
src/applications/files/phid/PhabricatorFileFilePHIDType.php
··· 39 39 $handle->setName("F{$id}"); 40 40 $handle->setFullName("F{$id}: {$name}"); 41 41 $handle->setURI($uri); 42 + 43 + $icon = FileTypeIcon::getFileIcon($name); 44 + $handle->setIcon($icon); 42 45 } 43 46 } 44 47
+25
src/applications/files/query/PhabricatorFileAttachmentQuery.php
··· 4 4 extends PhabricatorCursorPagedPolicyAwareQuery { 5 5 6 6 private $objectPHIDs; 7 + private $filePHIDs; 7 8 private $needFiles; 9 + private $visibleFiles; 8 10 9 11 public function withObjectPHIDs(array $object_phids) { 10 12 $this->objectPHIDs = $object_phids; 13 + return $this; 14 + } 15 + 16 + public function withFilePHIDs(array $file_phids) { 17 + $this->filePHIDs = $file_phids; 18 + return $this; 19 + } 20 + 21 + public function withVisibleFiles($visible_files) { 22 + $this->visibleFiles = $visible_files; 11 23 return $this; 12 24 } 13 25 ··· 32 44 $conn, 33 45 'attachments.objectPHID IN (%Ls)', 34 46 $this->objectPHIDs); 47 + } 48 + 49 + if ($this->filePHIDs !== null) { 50 + $where[] = qsprintf( 51 + $conn, 52 + 'attachments.filePHID IN (%Ls)', 53 + $this->filePHIDs); 35 54 } 36 55 37 56 return $where; ··· 91 110 foreach ($attachments as $key => $attachment) { 92 111 $file_phid = $attachment->getFilePHID(); 93 112 $file = idx($files, $file_phid); 113 + 114 + if ($this->visibleFiles && !$file) { 115 + $this->didRejectResult($attachment); 116 + unset($attachments[$key]); 117 + continue; 118 + } 94 119 95 120 $attachment->attachFile($file); 96 121 }
+9
src/applications/files/storage/PhabricatorFileAttachment.php
··· 46 46 ); 47 47 } 48 48 49 + public function isPolicyAttachment() { 50 + switch ($this->getAttachmentMode()) { 51 + case self::MODE_ATTACH: 52 + return true; 53 + default: 54 + return false; 55 + } 56 + } 57 + 49 58 public function attachObject($object) { 50 59 $this->object = $object; 51 60 return $this;
+13 -2
src/applications/policy/storage/PhabricatorPolicy.php
··· 417 417 PhabricatorPolicies::POLICY_NOONE => 1, 418 418 ); 419 419 420 - $this_strength = idx($strengths, $this->getPHID(), 0); 421 - $other_strength = idx($strengths, $other->getPHID(), 0); 420 + $this_strength = idx($strengths, $this_policy, 0); 421 + $other_strength = idx($strengths, $other_policy, 0); 422 422 423 423 return ($this_strength > $other_strength); 424 + } 425 + 426 + public function isStrongerThanOrEqualTo(PhabricatorPolicy $other) { 427 + $this_policy = $this->getPHID(); 428 + $other_policy = $other->getPHID(); 429 + 430 + if ($this_policy === $other_policy) { 431 + return true; 432 + } 433 + 434 + return $this->isStrongerThan($other); 424 435 } 425 436 426 437 public function isValidPolicyForEdit() {
+1
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 2321 2321 2322 2322 $xaction = $object->getApplicationTransactionTemplate() 2323 2323 ->setTransactionType(PhabricatorTransactions::TYPE_FILE) 2324 + ->setMetadataValue('attach.implicit', true) 2324 2325 ->setNewValue($new_map); 2325 2326 2326 2327 return $xaction;
+193 -2
src/applications/transactions/storage/PhabricatorApplicationTransaction.php
··· 341 341 $phids[] = $old; 342 342 $phids[] = $new; 343 343 break; 344 + case PhabricatorTransactions::TYPE_FILE: 345 + $phids[] = array_keys($old + $new); 346 + break; 344 347 case PhabricatorTransactions::TYPE_EDGE: 345 348 $record = PhabricatorEdgeChangeRecord::newFromTransaction($this); 346 349 $phids[] = $record->getChangedPHIDs(); ··· 592 595 593 596 // Always hide file attach/detach transactions. 594 597 if ($xaction_type === PhabricatorTransactions::TYPE_FILE) { 595 - return true; 598 + if ($this->getMetadataValue('attach.implicit')) { 599 + return true; 600 + } 596 601 } 597 602 598 603 // Hide creation transactions if the old value is empty. These are ··· 1042 1047 $this->renderHandleLink($author_phid)); 1043 1048 } 1044 1049 break; 1050 + case PhabricatorTransactions::TYPE_FILE: 1051 + $add = array_diff_key($new, $old); 1052 + $add = array_keys($add); 1053 + 1054 + $rem = array_diff_key($old, $new); 1055 + $rem = array_keys($rem); 1056 + 1057 + $mod = array(); 1058 + foreach ($old + $new as $key => $ignored) { 1059 + if (!isset($old[$key])) { 1060 + continue; 1061 + } 1062 + 1063 + if (!isset($new[$key])) { 1064 + continue; 1065 + } 1066 + 1067 + if ($old[$key] === $new[$key]) { 1068 + continue; 1069 + } 1070 + 1071 + $mod[] = $key; 1072 + } 1073 + 1074 + // Specialize the specific case of only modifying files and upgrading 1075 + // references to attachments. This is accessible via the UI and can 1076 + // be shown more clearly than the generic default transaction shows 1077 + // it. 1078 + 1079 + $mode_reference = PhabricatorFileAttachment::MODE_REFERENCE; 1080 + $mode_attach = PhabricatorFileAttachment::MODE_ATTACH; 1081 + 1082 + $is_refattach = false; 1083 + if ($mod && !$add && !$rem) { 1084 + $all_refattach = true; 1085 + foreach ($mod as $phid) { 1086 + if (idx($old, $phid) !== $mode_reference) { 1087 + $all_refattach = false; 1088 + break; 1089 + } 1090 + if (idx($new, $phid) !== $mode_attach) { 1091 + $all_refattach = false; 1092 + break; 1093 + } 1094 + } 1095 + $is_refattach = $all_refattach; 1096 + } 1097 + 1098 + if ($is_refattach) { 1099 + return pht( 1100 + '%s attached %s referenced file(s): %s.', 1101 + $this->renderHandleLink($author_phid), 1102 + phutil_count($mod), 1103 + $this->renderHandleList($mod)); 1104 + } else if ($add && $rem && $mod) { 1105 + return pht( 1106 + '%s updated %s attached file(s), added %s: %s; removed %s: %s; '. 1107 + 'modified %s: %s.', 1108 + $this->renderHandleLink($author_phid), 1109 + new PhutilNumber(count($add) + count($rem)), 1110 + phutil_count($add), 1111 + $this->renderHandleList($add), 1112 + phutil_count($rem), 1113 + $this->renderHandleList($rem), 1114 + phutil_count($mod), 1115 + $this->renderHandleList($mod)); 1116 + } else if ($add && $rem) { 1117 + return pht( 1118 + '%s updated %s attached file(s), added %s: %s; removed %s: %s.', 1119 + $this->renderHandleLink($author_phid), 1120 + new PhutilNumber(count($add) + count($rem)), 1121 + phutil_count($add), 1122 + $this->renderHandleList($add), 1123 + phutil_count($rem), 1124 + $this->renderHandleList($rem)); 1125 + } else if ($add && $mod) { 1126 + return pht( 1127 + '%s updated %s attached file(s), added %s: %s; modified %s: %s.', 1128 + $this->renderHandleLink($author_phid), 1129 + new PhutilNumber(count($add) + count($mod)), 1130 + phutil_count($add), 1131 + $this->renderHandleList($add), 1132 + phutil_count($mod), 1133 + $this->renderHandleList($mod)); 1134 + } else if ($rem && $mod) { 1135 + return pht( 1136 + '%s updated %s attached file(s), removed %s: %s; modified %s: %s.', 1137 + $this->renderHandleLink($author_phid), 1138 + new PhutilNumber(count($rem) + count($mod)), 1139 + phutil_count($rem), 1140 + $this->renderHandleList($rem), 1141 + phutil_count($mod), 1142 + $this->renderHandleList($mod)); 1143 + } else if ($add) { 1144 + return pht( 1145 + '%s attached %s file(s): %s.', 1146 + $this->renderHandleLink($author_phid), 1147 + phutil_count($add), 1148 + $this->renderHandleList($add)); 1149 + } else if ($rem) { 1150 + return pht( 1151 + '%s removed %s attached file(s): %s.', 1152 + $this->renderHandleLink($author_phid), 1153 + phutil_count($rem), 1154 + $this->renderHandleList($rem)); 1155 + } else if ($mod) { 1156 + return pht( 1157 + '%s modified %s attached file(s): %s.', 1158 + $this->renderHandleLink($author_phid), 1159 + phutil_count($mod), 1160 + $this->renderHandleList($mod)); 1161 + } else { 1162 + return pht( 1163 + '%s attached files...', 1164 + $this->renderHandleLink($author_phid)); 1165 + } 1166 + 1167 + break; 1045 1168 case PhabricatorTransactions::TYPE_EDGE: 1046 1169 $record = PhabricatorEdgeChangeRecord::newFromTransaction($this); 1047 1170 $add = $record->getAddedPHIDs(); ··· 1479 1602 1480 1603 public function hasChangeDetails() { 1481 1604 switch ($this->getTransactionType()) { 1605 + case PhabricatorTransactions::TYPE_FILE: 1606 + return true; 1482 1607 case PhabricatorTransactions::TYPE_CUSTOMFIELD: 1483 1608 $field = $this->getTransactionCustomField(); 1484 1609 if ($field) { ··· 1494 1619 } 1495 1620 1496 1621 public function renderChangeDetailsForMail(PhabricatorUser $viewer) { 1622 + switch ($this->getTransactionType()) { 1623 + case PhabricatorTransactions::TYPE_FILE: 1624 + return false; 1625 + } 1626 + 1497 1627 $view = $this->renderChangeDetails($viewer); 1498 1628 if ($view instanceof PhabricatorApplicationTransactionTextDiffDetailView) { 1499 1629 return $view->renderForMail(); ··· 1503 1633 1504 1634 public function renderChangeDetails(PhabricatorUser $viewer) { 1505 1635 switch ($this->getTransactionType()) { 1636 + case PhabricatorTransactions::TYPE_FILE: 1637 + return $this->newFileTransactionChangeDetails($viewer); 1506 1638 case PhabricatorTransactions::TYPE_CUSTOMFIELD: 1507 1639 $field = $this->getTransactionCustomField(); 1508 1640 if ($field) { ··· 1769 1901 ->addInt(-$this->getActionStrength()); 1770 1902 } 1771 1903 1904 + private function newFileTransactionChangeDetails(PhabricatorUser $viewer) { 1905 + $old = $this->getOldValue(); 1906 + $new = $this->getNewValue(); 1907 + 1908 + $phids = array_keys($old + $new); 1909 + $handles = $viewer->loadHandles($phids); 1910 + 1911 + $names = array( 1912 + PhabricatorFileAttachment::MODE_REFERENCE => pht('Referenced'), 1913 + PhabricatorFileAttachment::MODE_ATTACH => pht('Attached'), 1914 + ); 1915 + 1916 + $rows = array(); 1917 + foreach ($old + $new as $phid => $ignored) { 1918 + $handle = $handles[$phid]; 1919 + 1920 + $old_mode = idx($old, $phid); 1921 + $new_mode = idx($new, $phid); 1922 + 1923 + if ($old_mode === null) { 1924 + $old_name = pht('None'); 1925 + } else if (isset($names[$old_mode])) { 1926 + $old_name = $names[$old_mode]; 1927 + } else { 1928 + $old_name = pht('Unknown ("%s")', $old_mode); 1929 + } 1930 + 1931 + if ($new_mode === null) { 1932 + $new_name = pht('Detached'); 1933 + } else if (isset($names[$new_mode])) { 1934 + $new_name = $names[$new_mode]; 1935 + } else { 1936 + $new_name = pht('Unknown ("%s")', $new_mode); 1937 + } 1938 + 1939 + $rows[] = array( 1940 + $handle->renderLink(), 1941 + $old_name, 1942 + $new_name, 1943 + ); 1944 + } 1945 + 1946 + $table = id(new AphrontTableView($rows)) 1947 + ->setHeaders( 1948 + array( 1949 + pht('File'), 1950 + pht('Old Mode'), 1951 + pht('New Mode'), 1952 + )) 1953 + ->setColumnClasses( 1954 + array( 1955 + 'pri', 1956 + )); 1957 + 1958 + return id(new PHUIBoxView()) 1959 + ->addMargin(PHUI::MARGIN_SMALL) 1960 + ->appendChild($table); 1961 + } 1962 + 1963 + 1772 1964 1773 1965 /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ 1774 1966 ··· 1835 2027 $this->delete(); 1836 2028 $this->saveTransaction(); 1837 2029 } 1838 - 1839 2030 1840 2031 }
+14
src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php
··· 1789 1789 'Executed %s tasks.', 1790 1790 ), 1791 1791 1792 + '%s modified %s attached file(s): %s.' => array( 1793 + array( 1794 + '%s modified an attached file: %3$s.', 1795 + '%s modified attached files: %3$s.', 1796 + ), 1797 + ), 1798 + 1799 + '%s attached %s referenced file(s): %s.' => array( 1800 + array( 1801 + '%s attached a referenced file: %3$s.', 1802 + '%s attached referenced files: %3$s.', 1803 + ), 1804 + ), 1805 + 1792 1806 ); 1793 1807 } 1794 1808
+10 -2
src/view/phui/PHUICurtainObjectRefView.php
··· 7 7 private $epoch; 8 8 private $highlighted; 9 9 private $exiled; 10 + private $exileNote = false; 10 11 11 12 public function setHandle(PhabricatorObjectHandle $handle) { 12 13 $this->handle = $handle; ··· 23 24 return $this; 24 25 } 25 26 26 - public function setExiled($is_exiled) { 27 + public function setExiled($is_exiled, $note = false) { 27 28 $this->exiled = $is_exiled; 29 + $this->exileNote = $note; 28 30 return $this; 29 31 } 30 32 ··· 72 74 } 73 75 74 76 if ($this->exiled) { 77 + if ($this->exileNote !== false) { 78 + $exile_note = $this->exileNote; 79 + } else { 80 + $exile_note = pht('No View Permission'); 81 + } 82 + 75 83 $exiled_view = array( 76 84 id(new PHUIIconView())->setIcon('fa-eye-slash red'), 77 85 ' ', 78 - pht('No View Permission'), 86 + $exile_note, 79 87 ); 80 88 81 89 $exiled_cells = array();
+2 -1
webroot/rsrc/css/phui/phui-curtain-object-ref-view.css
··· 92 92 opacity: 0.75; 93 93 } 94 94 95 - .phui-curtain-object-ref-view-exiled-cell { 95 + .phui-curtain-object-ref-view-exiled-cell, 96 + .phui-curtain-object-ref-view-exiled-cell a { 96 97 color: {$red}; 97 98 }