@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 any user to watch any project they can see

Summary:
Ref T6183. Ref T10054. Historically, only members could watch projects because there were some weird special cases with policies. These policy issues have been resolved and Herald is generally powerful enough to do equivalent watches on most objects anyway.

Also puts a "Watch Project" button on the feed panel to make the behavior and meaning more obvious.

Test Plan:
- Watched a project I was not a member of.
- Clicked the feed watch/unwatch button.

{F1064909}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T6183, T10054

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

+87 -177
-4
src/applications/badges/storage/PhabricatorBadgesBadge.php
··· 185 185 return true; 186 186 } 187 187 188 - public function shouldAllowSubscription($phid) { 189 - return true; 190 - } 191 - 192 188 193 189 /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ 194 190
-4
src/applications/calendar/storage/PhabricatorCalendarEvent.php
··· 535 535 return true; 536 536 } 537 537 538 - public function shouldAllowSubscription($phid) { 539 - return true; 540 - } 541 - 542 538 /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ 543 539 544 540
-4
src/applications/countdown/storage/PhabricatorCountdown.php
··· 74 74 return true; 75 75 } 76 76 77 - public function shouldAllowSubscription($phid) { 78 - return true; 79 - } 80 - 81 77 /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 82 78 83 79
-4
src/applications/differential/storage/DifferentialRevision.php
··· 485 485 return true; 486 486 } 487 487 488 - public function shouldAllowSubscription($phid) { 489 - return true; 490 - } 491 - 492 488 493 489 /* -( PhabricatorCustomFieldInterface )------------------------------------ */ 494 490
-4
src/applications/files/storage/PhabricatorFile.php
··· 1352 1352 return true; 1353 1353 } 1354 1354 1355 - public function shouldAllowSubscription($phid) { 1356 - return true; 1357 - } 1358 - 1359 1355 1360 1356 /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ 1361 1357
-4
src/applications/fund/storage/FundInitiative.php
··· 182 182 return true; 183 183 } 184 184 185 - public function shouldAllowSubscription($phid) { 186 - return true; 187 - } 188 - 189 185 190 186 /* -( PhabricatorTokenRecevierInterface )---------------------------------- */ 191 187
-4
src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php
··· 129 129 return true; 130 130 } 131 131 132 - public function shouldAllowSubscription($phid) { 133 - return true; 134 - } 135 - 136 132 137 133 /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 138 134
-4
src/applications/herald/storage/HeraldRule.php
··· 332 332 return true; 333 333 } 334 334 335 - public function shouldAllowSubscription($phid) { 336 - return true; 337 - } 338 - 339 335 340 336 /* -( PhabricatorDestructibleInterface )----------------------------------- */ 341 337
-4
src/applications/legalpad/storage/LegalpadDocument.php
··· 167 167 return true; 168 168 } 169 169 170 - public function shouldAllowSubscription($phid) { 171 - return true; 172 - } 173 - 174 170 175 171 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 176 172
-4
src/applications/macro/storage/PhabricatorFileImageMacro.php
··· 115 115 return true; 116 116 } 117 117 118 - public function shouldAllowSubscription($phid) { 119 - return true; 120 - } 121 - 122 118 123 119 /* -( PhabricatorTokenRecevierInterface )---------------------------------- */ 124 120
-4
src/applications/maniphest/storage/ManiphestTask.php
··· 227 227 return true; 228 228 } 229 229 230 - public function shouldAllowSubscription($phid) { 231 - return true; 232 - } 233 - 234 230 235 231 /* -( Markup Interface )--------------------------------------------------- */ 236 232
-4
src/applications/passphrase/storage/PassphraseCredential.php
··· 161 161 return true; 162 162 } 163 163 164 - public function shouldAllowSubscription($phid) { 165 - return true; 166 - } 167 - 168 164 169 165 /* -( PhabricatorDestructibleInterface )----------------------------------- */ 170 166
-4
src/applications/paste/storage/PhabricatorPaste.php
··· 159 159 return true; 160 160 } 161 161 162 - public function shouldAllowSubscription($phid) { 163 - return true; 164 - } 165 - 166 162 167 163 /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ 168 164
-4
src/applications/phame/storage/PhameBlog.php
··· 340 340 return true; 341 341 } 342 342 343 - public function shouldAllowSubscription($phid) { 344 - return true; 345 - } 346 - 347 343 348 344 /* -( PhabricatorConduitResultInterface )---------------------------------- */ 349 345
-4
src/applications/phame/storage/PhamePost.php
··· 286 286 return true; 287 287 } 288 288 289 - public function shouldAllowSubscription($phid) { 290 - return true; 291 - } 292 - 293 289 294 290 /* -( PhabricatorConduitResultInterface )---------------------------------- */ 295 291
-4
src/applications/pholio/storage/PholioMock.php
··· 188 188 return true; 189 189 } 190 190 191 - public function shouldAllowSubscription($phid) { 192 - return true; 193 - } 194 - 195 191 196 192 /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ 197 193
-4
src/applications/phriction/storage/PhrictionDocument.php
··· 198 198 return true; 199 199 } 200 200 201 - public function shouldAllowSubscription($phid) { 202 - return true; 203 - } 204 - 205 201 /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 206 202 207 203
-4
src/applications/phurl/storage/PhabricatorPhurlURL.php
··· 173 173 return true; 174 174 } 175 175 176 - public function shouldAllowSubscription($phid) { 177 - return true; 178 - } 179 - 180 176 /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ 181 177 182 178
-4
src/applications/ponder/storage/PonderAnswer.php
··· 253 253 return true; 254 254 } 255 255 256 - public function shouldAllowSubscription($phid) { 257 - return true; 258 - } 259 - 260 256 261 257 /* -( PhabricatorDestructibleInterface )----------------------------------- */ 262 258
-4
src/applications/ponder/storage/PonderQuestion.php
··· 252 252 return true; 253 253 } 254 254 255 - public function shouldAllowSubscription($phid) { 256 - return true; 257 - } 258 - 259 255 260 256 /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ 261 257
+15 -15
src/applications/project/controller/PhabricatorProjectMembersViewController.php
··· 152 152 ->setDisabled(!$can_leave) 153 153 ->setWorkflow(true) 154 154 ->setName(pht('Leave Project'))); 155 + } 155 156 156 - if (!$project->isUserWatcher($viewer->getPHID())) { 157 - $view->addAction( 158 - id(new PhabricatorActionView()) 159 - ->setWorkflow(true) 160 - ->setHref('/project/watch/'.$project->getID().'/') 161 - ->setIcon('fa-eye') 162 - ->setName(pht('Watch Project'))); 163 - } else { 164 - $view->addAction( 165 - id(new PhabricatorActionView()) 166 - ->setWorkflow(true) 167 - ->setHref('/project/unwatch/'.$project->getID().'/') 168 - ->setIcon('fa-eye-slash') 169 - ->setName(pht('Unwatch Project'))); 170 - } 157 + if (!$project->isUserWatcher($viewer->getPHID())) { 158 + $view->addAction( 159 + id(new PhabricatorActionView()) 160 + ->setWorkflow(true) 161 + ->setHref('/project/watch/'.$project->getID().'/') 162 + ->setIcon('fa-eye') 163 + ->setName(pht('Watch Project'))); 164 + } else { 165 + $view->addAction( 166 + id(new PhabricatorActionView()) 167 + ->setWorkflow(true) 168 + ->setHref('/project/unwatch/'.$project->getID().'/') 169 + ->setIcon('fa-eye-slash') 170 + ->setName(pht('Unwatch Project'))); 171 171 } 172 172 173 173 $can_add = $can_edit && $supports_edit;
+36 -1
src/applications/project/controller/PhabricatorProjectProfileController.php
··· 52 52 $nav = $this->getProfileMenu(); 53 53 $nav->selectFilter(PhabricatorProject::PANEL_PROFILE); 54 54 55 + $watch_action = $this->renderWatchAction($project); 55 56 56 57 $stories = id(new PhabricatorFeedQuery()) 57 58 ->setViewer($viewer) ··· 62 63 ->setLimit(50) 63 64 ->execute(); 64 65 66 + 65 67 $feed = $this->renderStories($stories); 66 68 69 + $feed_header = id(new PHUIHeaderView()) 70 + ->setHeader(pht('Recent Activity')) 71 + ->addActionLink($watch_action); 72 + 67 73 $feed = id(new PHUIObjectBoxView()) 68 - ->setHeaderText(pht('Recent Activity')) 74 + ->setHeader($feed_header) 69 75 ->appendChild($feed); 70 76 71 77 $columns = id(new AphrontMultiColumnView()) ··· 143 149 144 150 return phutil_tag_div('profile-feed', $view->render()); 145 151 } 152 + 153 + private function renderWatchAction(PhabricatorProject $project) { 154 + $viewer = $this->getViewer(); 155 + $viewer_phid = $viewer->getPHID(); 156 + $id = $project->getID(); 157 + 158 + $is_watcher = ($viewer_phid && $project->isUserWatcher($viewer_phid)); 159 + 160 + if (!$is_watcher) { 161 + $watch_icon = 'fa-eye'; 162 + $watch_text = pht('Watch Project'); 163 + $watch_href = "/project/watch/{$id}/?via=profile"; 164 + } else { 165 + $watch_icon = 'fa-eye-slash'; 166 + $watch_text = pht('Unwatch Project'); 167 + $watch_href = "/project/unwatch/{$id}/?via=profile"; 168 + } 169 + 170 + $watch_icon = id(new PHUIIconView()) 171 + ->setIconFont($watch_icon); 172 + 173 + return id(new PHUIButtonView()) 174 + ->setTag('a') 175 + ->setWorkflow(true) 176 + ->setIcon($watch_icon) 177 + ->setText($watch_text) 178 + ->setHref($watch_href); 179 + } 180 + 146 181 147 182 }
+8 -7
src/applications/project/controller/PhabricatorProjectWatchController.php
··· 18 18 return new Aphront404Response(); 19 19 } 20 20 21 - $done_uri = "/project/members/{$id}/"; 22 - 23 - // You must be a member of a project to watch it. 24 - if (!$project->isUserMember($viewer->getPHID())) { 25 - return new Aphront400Response(); 21 + $via = $request->getStr('via'); 22 + if ($via == 'profile') { 23 + $done_uri = $project->getURI(); 24 + } else { 25 + $done_uri = "/project/members/{$id}/"; 26 26 } 27 27 28 28 if ($request->isDialogFormPost()) { ··· 38 38 break; 39 39 } 40 40 41 - $type_member = PhabricatorObjectHasWatcherEdgeType::EDGECONST; 41 + $type_watcher = PhabricatorObjectHasWatcherEdgeType::EDGECONST; 42 42 $member_spec = array( 43 43 $edge_action => array($viewer->getPHID() => $viewer->getPHID()), 44 44 ); ··· 46 46 $xactions = array(); 47 47 $xactions[] = id(new PhabricatorProjectTransaction()) 48 48 ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 49 - ->setMetadataValue('edge:type', $type_member) 49 + ->setMetadataValue('edge:type', $type_watcher) 50 50 ->setNewValue($member_spec); 51 51 52 52 $editor = id(new PhabricatorProjectTransactionEditor($project)) ··· 82 82 83 83 return $this->newDialog() 84 84 ->setTitle($title) 85 + ->addHiddenInput('via', $via) 85 86 ->appendParagraph($body) 86 87 ->addCancelButton($done_uri) 87 88 ->addSubmitButton($submit);
+8 -26
src/applications/project/editor/PhabricatorProjectTransactionEditor.php
··· 188 188 switch ($edge_type) { 189 189 case PhabricatorProjectProjectHasMemberEdgeType::EDGECONST: 190 190 case PhabricatorObjectHasWatcherEdgeType::EDGECONST: 191 + $edge_const = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; 192 + if ($edge_type != $edge_const) { 193 + break; 194 + } 195 + 191 196 $old = $xaction->getOldValue(); 192 197 $new = $xaction->getNewValue(); 193 198 194 - // When adding members or watchers, we add subscriptions. 199 + // When adding members, we add subscriptions. When removing 200 + // members, we remove subscriptions. 195 201 $add = array_keys(array_diff_key($new, $old)); 196 - 197 - // When removing members, we remove their subscription too. 198 - // When unwatching, we leave subscriptions, since it's fine to be 199 - // subscribed to a project but not be a member of it. 200 - $edge_const = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; 201 - if ($edge_type == $edge_const) { 202 - $rem = array_keys(array_diff_key($old, $new)); 203 - } else { 204 - $rem = array(); 205 - } 202 + $rem = array_keys(array_diff_key($old, $new)); 206 203 207 204 // NOTE: The subscribe is "explicit" because there's no implicit 208 205 // unsubscribe, so Join -> Leave -> Join doesn't resubscribe you ··· 211 208 // would not write the unsubscribe row) is justified to deal with 212 209 // this, which is a fairly weird edge case and pretty arguable both 213 210 // ways. 214 - 215 - // Subscriptions caused by watches should also clearly be explicit, 216 - // and that case is unambiguous. 217 211 218 212 id(new PhabricatorSubscriptionsEditor()) 219 213 ->setActor($this->requireActor()) ··· 221 215 ->subscribeExplicit($add) 222 216 ->unsubscribe($rem) 223 217 ->save(); 224 - 225 - if ($rem) { 226 - // When removing members, also remove any watches on the project. 227 - $edge_editor = new PhabricatorEdgeEditor(); 228 - foreach ($rem as $rem_phid) { 229 - $edge_editor->removeEdge( 230 - $object->getPHID(), 231 - PhabricatorObjectHasWatcherEdgeType::EDGECONST, 232 - $rem_phid); 233 - } 234 - $edge_editor->save(); 235 - } 236 218 break; 237 219 } 238 220 break;
-5
src/applications/project/storage/PhabricatorProject.php
··· 548 548 return false; 549 549 } 550 550 551 - public function shouldAllowSubscription($phid) { 552 - return $this->isUserMember($phid) && 553 - !$this->isUserWatcher($phid); 554 - } 555 - 556 551 557 552 /* -( PhabricatorCustomFieldInterface )------------------------------------ */ 558 553
+20 -4
src/applications/project/view/PhabricatorProjectUserListView.php
··· 79 79 ->setHref($handle->getURI()) 80 80 ->setImageURI($handle->getImageURI()); 81 81 82 - if ($can_edit) { 82 + if ($can_edit && !$limit) { 83 83 $remove_uri = $this->getRemoveURI($user_phid); 84 84 85 85 $item->addAction( ··· 94 94 } 95 95 96 96 if ($user_phids) { 97 - $header = pht( 97 + $header_text = pht( 98 98 '%s (%s)', 99 99 $this->getHeaderText(), 100 100 phutil_count($user_phids)); 101 101 } else { 102 - $header = $this->getHeaderText(); 102 + $header_text = $this->getHeaderText(); 103 + } 104 + 105 + $id = $project->getID(); 106 + 107 + $header = id(new PHUIHeaderView()) 108 + ->setHeader($header_text); 109 + 110 + if ($limit) { 111 + $header->addActionLink( 112 + id(new PHUIButtonView()) 113 + ->setTag('a') 114 + ->setIcon( 115 + id(new PHUIIconView()) 116 + ->setIconFont('fa-list-ul')) 117 + ->setText(pht('View All')) 118 + ->setHref("/project/members/{$id}/")); 103 119 } 104 120 105 121 return id(new PHUIObjectBoxView()) 106 - ->setHeaderText($header) 122 + ->setHeader($header) 107 123 ->setObjectList($list); 108 124 } 109 125
-4
src/applications/repository/storage/PhabricatorRepositoryCommit.php
··· 443 443 return true; 444 444 } 445 445 446 - public function shouldAllowSubscription($phid) { 447 - return true; 448 - } 449 - 450 446 451 447 /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 452 448
-4
src/applications/slowvote/storage/PhabricatorSlowvotePoll.php
··· 183 183 return true; 184 184 } 185 185 186 - public function shouldAllowSubscription($phid) { 187 - return true; 188 - } 189 - 190 186 191 187 /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ 192 188
-7
src/applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php
··· 61 61 $handle->getURI()); 62 62 } 63 63 64 - if (!$object->shouldAllowSubscription($viewer->getPHID())) { 65 - return $this->buildErrorResponse( 66 - pht('You Can Not Subscribe'), 67 - pht('You can not subscribe to this object.'), 68 - $handle->getURI()); 69 - } 70 - 71 64 if ($object instanceof PhabricatorApplicationTransactionInterface) { 72 65 if ($is_add) { 73 66 $xaction_value = array(
-5
src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php
··· 34 34 return; 35 35 } 36 36 37 - if (!$object->shouldAllowSubscription($user_phid)) { 38 - // This object doesn't allow the viewer to subscribe. 39 - return; 40 - } 41 - 42 37 if ($user_phid && $object->isAutomaticallySubscribed($user_phid)) { 43 38 $sub_action = id(new PhabricatorActionView()) 44 39 ->setWorkflow(true)
-15
src/applications/subscriptions/interface/PhabricatorSubscribableInterface.php
··· 22 22 */ 23 23 public function shouldShowSubscribersProperty(); 24 24 25 - 26 - /** 27 - * Return `true` to indicate that the "Subscribe" action should be shown and 28 - * enabled when rendering action lists for this object, or `false` to omit 29 - * the action. 30 - * 31 - * @param phid Viewing or acting user PHID. 32 - * @return bool True to allow the user to subscribe. 33 - */ 34 - public function shouldAllowSubscription($phid); 35 - 36 25 } 37 26 38 27 // TEMPLATE IMPLEMENTATION ///////////////////////////////////////////////////// ··· 45 34 } 46 35 47 36 public function shouldShowSubscribersProperty() { 48 - return true; 49 - } 50 - 51 - public function shouldAllowSubscription($phid) { 52 37 return true; 53 38 } 54 39
-4
src/infrastructure/daemon/workers/storage/PhabricatorWorkerBulkJob.php
··· 222 222 return true; 223 223 } 224 224 225 - public function shouldAllowSubscription($phid) { 226 - return true; 227 - } 228 - 229 225 230 226 /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 231 227