@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.

Allow users to remove their own comments, and administrators to remove any comment

Summary:
Fixes T4909. Adds a "remove" link next to the edit link, which permanently hides a comment. Addresses two use cases:

- Allowing administrators to clean up spam.
- Allowing users to try to put the genie back in the bottle if they post passwords or sensitive links, etc.

The user who removed the comment is named in the removal text to enforce some level of administrative accountability.

No data is deleted, but there's currently no method to restore these comments. We'll see if we need one.

This is cheating a little bit by storing "removed" as "2" in the isDeleted field. This doesn't seem tooooo bad for now.

Test Plan:
- Removed some of my comments.
- As an administrator, removed other users' comments.
- Failed to view history of a removed comment.
- Failed to edit a removed comment.
- Failed to remove a removed comment.
- Verified feed doesn't show the old comment after comment removal.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: qgil, chad, epriestley

Maniphest Tasks: T4909

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

+181 -19
+3
src/__phutil_library_map__.php
··· 1164 1164 'PhabricatorApplicationTransactionCommentEditor' => 'applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php', 1165 1165 'PhabricatorApplicationTransactionCommentHistoryController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentHistoryController.php', 1166 1166 'PhabricatorApplicationTransactionCommentQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionCommentQuery.php', 1167 + 'PhabricatorApplicationTransactionCommentRemoveController' => 'applications/transactions/controller/PhabricatorApplicationTransactionCommentRemoveController.php', 1167 1168 'PhabricatorApplicationTransactionCommentView' => 'applications/transactions/view/PhabricatorApplicationTransactionCommentView.php', 1168 1169 'PhabricatorApplicationTransactionController' => 'applications/transactions/controller/PhabricatorApplicationTransactionController.php', 1169 1170 'PhabricatorApplicationTransactionDetailController' => 'applications/transactions/controller/PhabricatorApplicationTransactionDetailController.php', ··· 3647 3648 4 => 'PhabricatorFlaggableInterface', 3648 3649 5 => 'PhrequentTrackableInterface', 3649 3650 6 => 'PhabricatorCustomFieldInterface', 3651 + 7 => 'PhabricatorDestructableInterface', 3650 3652 ), 3651 3653 'ManiphestTaskDescriptionPreviewController' => 'ManiphestController', 3652 3654 'ManiphestTaskDetailController' => 'ManiphestController', ··· 3934 3936 'PhabricatorApplicationTransactionCommentEditor' => 'PhabricatorEditor', 3935 3937 'PhabricatorApplicationTransactionCommentHistoryController' => 'PhabricatorApplicationTransactionController', 3936 3938 'PhabricatorApplicationTransactionCommentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 3939 + 'PhabricatorApplicationTransactionCommentRemoveController' => 'PhabricatorApplicationTransactionController', 3937 3940 'PhabricatorApplicationTransactionCommentView' => 'AphrontView', 3938 3941 'PhabricatorApplicationTransactionController' => 'PhabricatorController', 3939 3942 'PhabricatorApplicationTransactionDetailController' => 'PhabricatorApplicationTransactionController',
+2
src/applications/transactions/application/PhabricatorApplicationTransactions.php
··· 15 15 '/transactions/' => array( 16 16 'edit/(?<phid>[^/]+)/' 17 17 => 'PhabricatorApplicationTransactionCommentEditController', 18 + 'remove/(?<phid>[^/]+)/' 19 + => 'PhabricatorApplicationTransactionCommentRemoveController', 18 20 'history/(?<phid>[^/]+)/' 19 21 => 'PhabricatorApplicationTransactionCommentHistoryController', 20 22 'detail/(?<phid>[^/]+)/'
+6 -1
src/applications/transactions/controller/PhabricatorApplicationTransactionCommentEditController.php
··· 28 28 return new Aphront404Response(); 29 29 } 30 30 31 + if ($xaction->getComment()->getIsRemoved()) { 32 + // You can't edit history of a transaction with a removed comment. 33 + return new Aphront400Response(); 34 + } 35 + 31 36 $obj_phid = $xaction->getObjectPHID(); 32 37 $obj_handle = id(new PhabricatorHandleQuery()) 33 38 ->setViewer($user) ··· 75 80 ->setValue($xaction->getComment()->getContent()))); 76 81 77 82 $dialog 78 - ->addSubmitButton(pht('Edit Comment')) 83 + ->addSubmitButton(pht('Save Changes')) 79 84 ->addCancelButton($obj_handle->getURI()); 80 85 81 86 return id(new AphrontDialogResponse())->setDialog($dialog);
+5
src/applications/transactions/controller/PhabricatorApplicationTransactionCommentHistoryController.php
··· 31 31 return new Aphront404Response(); 32 32 } 33 33 34 + if ($xaction->getComment()->getIsRemoved()) { 35 + // You can't view history of a transaction with a removed comment. 36 + return new Aphront400Response(); 37 + } 38 + 34 39 $comments = id(new PhabricatorApplicationTransactionCommentQuery()) 35 40 ->setViewer($user) 36 41 ->setTemplate($xaction->getApplicationTransactionCommentObject())
+82
src/applications/transactions/controller/PhabricatorApplicationTransactionCommentRemoveController.php
··· 1 + <?php 2 + 3 + final class PhabricatorApplicationTransactionCommentRemoveController 4 + extends PhabricatorApplicationTransactionController { 5 + 6 + private $phid; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->phid = $data['phid']; 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + $xaction = id(new PhabricatorObjectQuery()) 17 + ->withPHIDs(array($this->phid)) 18 + ->setViewer($viewer) 19 + ->executeOne(); 20 + if (!$xaction) { 21 + return new Aphront404Response(); 22 + } 23 + 24 + if (!$xaction->getComment()) { 25 + return new Aphront404Response(); 26 + } 27 + 28 + if ($xaction->getComment()->getIsRemoved()) { 29 + // You can't remove an already-removed comment. 30 + return new Aphront400Response(); 31 + } 32 + 33 + $obj_phid = $xaction->getObjectPHID(); 34 + $obj_handle = id(new PhabricatorHandleQuery()) 35 + ->setViewer($viewer) 36 + ->withPHIDs(array($obj_phid)) 37 + ->executeOne(); 38 + 39 + if ($request->isDialogFormPost()) { 40 + $comment = $xaction->getApplicationTransactionCommentObject() 41 + ->setContent('') 42 + ->setIsRemoved(true); 43 + 44 + $editor = id(new PhabricatorApplicationTransactionCommentEditor()) 45 + ->setActor($viewer) 46 + ->setContentSource(PhabricatorContentSource::newFromRequest($request)) 47 + ->applyEdit($xaction, $comment); 48 + 49 + if ($request->isAjax()) { 50 + return id(new PhabricatorApplicationTransactionResponse()) 51 + ->setViewer($viewer) 52 + ->setTransactions(array($xaction)) 53 + ->setAnchorOffset($request->getStr('anchor')); 54 + } else { 55 + return id(new AphrontReloadResponse())->setURI($obj_handle->getURI()); 56 + } 57 + } 58 + 59 + $form = id(new AphrontFormView()) 60 + ->setUser($viewer); 61 + 62 + $dialog = $this->newDialog() 63 + ->setTitle(pht('Remove Comment')); 64 + 65 + $dialog 66 + ->addHiddenInput('anchor', $request->getStr('anchor')) 67 + ->appendParagraph( 68 + pht( 69 + "Removing a comment prevents anyone (including you) from reading ". 70 + "it. Removing a comment also hides the comment's edit history ". 71 + "and prevents it from being edited.")) 72 + ->appendParagraph( 73 + pht('Really remove this comment?')); 74 + 75 + $dialog 76 + ->addSubmitButton(pht('Remove Comment')) 77 + ->addCancelButton($obj_handle->getURI()); 78 + 79 + return $dialog; 80 + } 81 + 82 + }
+9 -4
src/applications/transactions/editor/PhabricatorApplicationTransactionCommentEditor.php
··· 95 95 $xaction, 96 96 PhabricatorPolicyCapability::CAN_VIEW); 97 97 98 - PhabricatorPolicyFilter::requireCapability( 99 - $actor, 100 - $xaction, 101 - PhabricatorPolicyCapability::CAN_EDIT); 98 + if ($comment->getIsRemoved() && $actor->getIsAdmin()) { 99 + // NOTE: Administrators can remove comments by any user, and don't need 100 + // to pass the edit check. 101 + } else { 102 + PhabricatorPolicyFilter::requireCapability( 103 + $actor, 104 + $xaction, 105 + PhabricatorPolicyCapability::CAN_EDIT); 106 + } 102 107 } 103 108 104 109
+4
src/applications/transactions/storage/PhabricatorApplicationTransaction.php
··· 248 248 break; 249 249 } 250 250 251 + if ($this->getComment()) { 252 + $phids[] = array($this->getComment()->getAuthorPHID()); 253 + } 254 + 251 255 return array_mergev($phids); 252 256 } 253 257
+13
src/applications/transactions/storage/PhabricatorApplicationTransactionComment.php
··· 59 59 return PhabricatorContentSource::newFromSerialized($this->contentSource); 60 60 } 61 61 62 + public function getIsRemoved() { 63 + return ($this->getIsDeleted() == 2); 64 + } 65 + 66 + public function setIsRemoved($removed) { 67 + if ($removed) { 68 + $this->setIsDeleted(2); 69 + } else { 70 + $this->setIsDeleted(0); 71 + } 72 + return $this; 73 + } 74 + 62 75 63 76 /* -( PhabricatorMarkupInterface )----------------------------------------- */ 64 77
+22 -3
src/applications/transactions/view/PhabricatorApplicationTransactionView.php
··· 220 220 $comment = $xaction->getComment(); 221 221 222 222 if ($comment) { 223 - if ($comment->getIsDeleted()) { 223 + if ($comment->getIsRemoved()) { 224 + return javelin_tag( 225 + 'span', 226 + array( 227 + 'class' => 'comment-deleted', 228 + 'sigil' => 'transaction-comment', 229 + 'meta' => array('phid' => $comment->getTransactionPHID()), 230 + ), 231 + pht( 232 + 'This comment was removed by %s.', 233 + $xaction->getHandle($comment->getAuthorPHID())->renderLink())); 234 + } else if ($comment->getIsDeleted()) { 224 235 return javelin_tag( 225 236 'span', 226 237 array( ··· 357 368 $has_deleted_comment = $xaction->getComment() && 358 369 $xaction->getComment()->getIsDeleted(); 359 370 371 + $has_removed_comment = $xaction->getComment() && 372 + $xaction->getComment()->getIsRemoved(); 373 + 360 374 if ($this->getShowEditActions() && !$this->isPreview) { 361 - if ($xaction->getCommentVersion() > 1) { 375 + if ($xaction->getCommentVersion() > 1 && !$has_removed_comment) { 362 376 $event->setIsEdited(true); 363 377 } 364 378 ··· 369 383 $viewer, 370 384 $xaction, 371 385 $can_edit); 372 - if ($has_edit_capability) { 386 + if ($has_edit_capability && !$has_removed_comment) { 373 387 $event->setIsEditable(true); 388 + } 389 + if ($has_edit_capability || $viewer->getIsAdmin()) { 390 + if (!$has_removed_comment) { 391 + $event->setIsRemovable(true); 392 + } 374 393 } 375 394 } 376 395 }
+20
src/view/phui/PHUITimelineEventView.php
··· 14 14 private $anchor; 15 15 private $isEditable; 16 16 private $isEdited; 17 + private $isRemovable; 17 18 private $transactionPHID; 18 19 private $isPreview; 19 20 private $eventGroup = array(); ··· 64 65 65 66 public function getIsEditable() { 66 67 return $this->isEditable; 68 + } 69 + 70 + public function setIsRemovable($is_removable) { 71 + $this->isRemovable = $is_removable; 72 + return $this; 73 + } 74 + 75 + public function getIsRemovable() { 76 + return $this->isRemovable; 67 77 } 68 78 69 79 public function setDateCreated($date_created) { ··· 365 375 'sigil' => 'workflow transaction-edit', 366 376 ), 367 377 pht('Edit')); 378 + } 379 + 380 + if ($this->getIsRemovable()) { 381 + $extra[] = javelin_tag( 382 + 'a', 383 + array( 384 + 'href' => '/transactions/remove/'.$xaction_phid.'/', 385 + 'sigil' => 'workflow transaction-remove', 386 + ), 387 + pht('Remove')); 368 388 } 369 389 370 390 if ($is_first_extra) {
+15 -11
webroot/rsrc/js/application/transactions/behavior-transaction-list.js
··· 96 96 } 97 97 } 98 98 99 - JX.DOM.listen(list, 'click', 'transaction-edit', function(e) { 100 - if (!e.isNormalClick()) { 101 - return; 102 - } 99 + JX.DOM.listen( 100 + list, 101 + 'click', 102 + ['transaction-edit', 'transaction-remove'], 103 + function(e) { 104 + if (!e.isNormalClick()) { 105 + return; 106 + } 103 107 104 - var transaction = e.getNode('transaction'); 108 + var transaction = e.getNode('transaction'); 105 109 106 - JX.Workflow.newFromLink(e.getTarget()) 107 - .setData({anchor: e.getNodeData('transaction').anchor}) 108 - .setHandler(JX.bind(null, edittransaction, transaction)) 109 - .start(); 110 + JX.Workflow.newFromLink(e.getTarget()) 111 + .setData({anchor: e.getNodeData('transaction').anchor}) 112 + .setHandler(JX.bind(null, edittransaction, transaction)) 113 + .start(); 110 114 111 - e.kill(); 112 - }); 115 + e.kill(); 116 + }); 113 117 114 118 JX.Stratcom.listen( 115 119 ['submit', 'didSyntheticSubmit'],