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

Maniphest - use subscribers framework properly

Summary: Fixes T5604. This should fix some random bugs, lets us move forward more easily, and all that good stuff about killing code debt.

Test Plan:
- Conduit method maniphest.createtask
- verified creating user subscribed
- verified subscription transaction
- Conduit method maniphest.update
- verified subscribers set as specified to ccPHIDs parameter
- verified subscription transaction
- Herald
- verified herald rule to add subscriber worked
- verified no subscribers removed accidentally
- edit controller
- test create and verify author gets added IFF they put themselves in subscribers control box
- test update gets set to exactly what user enters
- lipsum generator'd tasks work
- bulk add subscribers works
- bulk remove subscriber works
- detail controller
- added myself by leaving a comment
- added another user via explicit action
- added another user via implicit mention
- task merge via search attach controller
- mail reply handler
- add subscriber via ./bin/mail receive-test
- unsubscribe via ./bin/mail receive-test

Reviewers: epriestley

Reviewed By: epriestley

Subscribers: Korvin, epriestley

Maniphest Tasks: T5604

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

+195 -230
+9
resources/sql/autopatches/20141210.maniphestsubscribersmig.1.sql
··· 1 + INSERT IGNORE INTO {$NAMESPACE}_maniphest.edge (src, type, dst) 2 + SELECT taskPHID, 21, subscriberPHID 3 + FROM {$NAMESPACE}_maniphest.maniphest_tasksubscriber 4 + WHERE subscriberPHID != ''; 5 + 6 + INSERT IGNORE INTO {$NAMESPACE}_maniphest.edge (src, type, dst) 7 + SELECT subscriberPHID, 22, taskPHID 8 + FROM {$NAMESPACE}_maniphest.maniphest_tasksubscriber 9 + WHERE subscriberPHID != '';
+2
resources/sql/autopatches/20141210.maniphestsubscribersmig.2.sql
··· 1 + ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task 2 + DROP ccPHIDs;
+1 -2
src/__phutil_library_map__.php
··· 1001 1001 'ManiphestSearchIndexer' => 'applications/maniphest/search/ManiphestSearchIndexer.php', 1002 1002 'ManiphestStatusConfigOptionType' => 'applications/maniphest/config/ManiphestStatusConfigOptionType.php', 1003 1003 'ManiphestSubpriorityController' => 'applications/maniphest/controller/ManiphestSubpriorityController.php', 1004 - 'ManiphestSubscribeController' => 'applications/maniphest/controller/ManiphestSubscribeController.php', 1005 1004 'ManiphestTask' => 'applications/maniphest/storage/ManiphestTask.php', 1006 1005 'ManiphestTaskDescriptionPreviewController' => 'applications/maniphest/controller/ManiphestTaskDescriptionPreviewController.php', 1007 1006 'ManiphestTaskDetailController' => 'applications/maniphest/controller/ManiphestTaskDetailController.php', ··· 4071 4070 'ManiphestSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 4072 4071 'ManiphestStatusConfigOptionType' => 'PhabricatorConfigJSONOptionType', 4073 4072 'ManiphestSubpriorityController' => 'ManiphestController', 4074 - 'ManiphestSubscribeController' => 'ManiphestController', 4075 4073 'ManiphestTask' => array( 4076 4074 'ManiphestDAO', 4075 + 'PhabricatorSubscribableInterface', 4077 4076 'PhabricatorMarkupInterface', 4078 4077 'PhabricatorPolicyInterface', 4079 4078 'PhabricatorTokenReceiverInterface',
+1 -1
src/applications/herald/adapter/HeraldManiphestTaskAdapter.php
··· 139 139 case self::FIELD_ASSIGNEE: 140 140 return $this->getTask()->getOwnerPHID(); 141 141 case self::FIELD_CC: 142 - return $this->getTask()->getCCPHIDs(); 142 + return $this->getTask()->getSubscriberPHIDs(); 143 143 case self::FIELD_PROJECTS: 144 144 return PhabricatorEdgeQuery::loadDestinationPHIDs( 145 145 $this->getTask()->getPHID(),
-2
src/applications/maniphest/application/PhabricatorManiphestApplication.php
··· 66 66 ), 67 67 'export/(?P<key>[^/]+)/' => 'ManiphestExportController', 68 68 'subpriority/' => 'ManiphestSubpriorityController', 69 - 'subscribe/(?P<action>add|rem)/(?P<id>[1-9]\d*)/' 70 - => 'ManiphestSubscribeController', 71 69 ), 72 70 ); 73 71 }
+12 -2
src/applications/maniphest/conduit/ManiphestConduitAPIMethod.php
··· 62 62 $task->setDescription((string)$request->getValue('description')); 63 63 $changes[ManiphestTransaction::TYPE_STATUS] = 64 64 ManiphestTaskStatus::getDefaultStatus(); 65 + $changes[PhabricatorTransactions::TYPE_SUBSCRIBERS] = 66 + array('+' => array($request->getUser()->getPHID())); 65 67 } else { 66 68 67 69 $comments = $request->getValue('comments'); ··· 111 113 112 114 $ccs = $request->getValue('ccPHIDs'); 113 115 if ($ccs !== null) { 114 - $changes[ManiphestTransaction::TYPE_CCS] = $ccs; 116 + $changes[PhabricatorTransactions::TYPE_SUBSCRIBERS] = 117 + array('=' => array_fuse($ccs)); 115 118 } 116 119 117 120 $transactions = array(); ··· 228 231 $event->setUser($request->getUser()); 229 232 $event->setConduitRequest($request); 230 233 PhutilEventEngine::dispatchEvent($event); 234 + 235 + // reload the task now that we've done all the fun stuff 236 + return id(new ManiphestTaskQuery()) 237 + ->setViewer($request->getUser()) 238 + ->withPHIDs(array($task->getPHID())) 239 + ->needSubscriberPHIDs(true) 240 + ->executeOne(); 231 241 } 232 242 233 243 protected function buildTaskInfoDictionaries(array $tasks) { ··· 265 275 'phid' => $task->getPHID(), 266 276 'authorPHID' => $task->getAuthorPHID(), 267 277 'ownerPHID' => $task->getOwnerPHID(), 268 - 'ccPHIDs' => $task->getCCPHIDs(), 278 + 'ccPHIDs' => $task->getSubscriberPHIDs(), 269 279 'status' => $task->getStatus(), 270 280 'statusName' => ManiphestTaskStatus::getTaskStatusName( 271 281 $task->getStatus()),
+1 -1
src/applications/maniphest/conduit/ManiphestCreateTaskConduitAPIMethod.php
··· 28 28 protected function execute(ConduitAPIRequest $request) { 29 29 $task = ManiphestTask::initializeNewTask($request->getUser()); 30 30 31 - $this->applyRequest($task, $request, $is_new = true); 31 + $task = $this->applyRequest($task, $request, $is_new = true); 32 32 33 33 return $this->buildTaskInfoDictionary($task); 34 34 }
+1
src/applications/maniphest/conduit/ManiphestInfoConduitAPIMethod.php
··· 32 32 $task = id(new ManiphestTaskQuery()) 33 33 ->setViewer($request->getUser()) 34 34 ->withIDs(array($task_id)) 35 + ->needSubscriberPHIDs(true) 35 36 ->executeOne(); 36 37 if (!$task) { 37 38 throw new ConduitException('ERR_BAD_TASK');
+1
src/applications/maniphest/conduit/ManiphestQueryConduitAPIMethod.php
··· 64 64 $query = new ManiphestTaskQuery(); 65 65 66 66 $query->setViewer($request->getUser()); 67 + $query->needSubscriberPHIDs(true); 67 68 68 69 $task_ids = $request->getValue('ids'); 69 70 if ($task_ids) {
+3 -1
src/applications/maniphest/conduit/ManiphestUpdateConduitAPIMethod.php
··· 38 38 $task = id(new ManiphestTaskQuery()) 39 39 ->setViewer($request->getUser()) 40 40 ->withIDs(array($id)) 41 + ->needSubscriberPHIDs(true) 41 42 ->executeOne(); 42 43 } else { 43 44 $task = id(new ManiphestTaskQuery()) 44 45 ->setViewer($request->getUser()) 45 46 ->withPHIDs(array($phid)) 47 + ->needSubscriberPHIDs(true) 46 48 ->executeOne(); 47 49 } 48 50 ··· 58 60 throw new ConduitException('ERR-BAD-TASK'); 59 61 } 60 62 61 - $this->applyRequest($task, $request, $is_new = false); 63 + $task = $this->applyRequest($task, $request, $is_new = false); 62 64 63 65 return $this->buildTaskInfoDictionary($task); 64 66 }
+40 -11
src/applications/maniphest/controller/ManiphestBatchEditController.php
··· 18 18 PhabricatorPolicyCapability::CAN_VIEW, 19 19 PhabricatorPolicyCapability::CAN_EDIT, 20 20 )) 21 + ->needSubscriberPHIDs(true) 21 22 ->execute(); 22 23 23 24 $actions = $request->getStr('actions'); ··· 171 172 'priority' => ManiphestTransaction::TYPE_PRIORITY, 172 173 'add_project' => ManiphestTransaction::TYPE_PROJECTS, 173 174 'remove_project' => ManiphestTransaction::TYPE_PROJECTS, 174 - 'add_ccs' => ManiphestTransaction::TYPE_CCS, 175 - 'remove_ccs' => ManiphestTransaction::TYPE_CCS, 175 + 'add_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS, 176 + 'remove_ccs' => PhabricatorTransactions::TYPE_SUBSCRIBERS, 176 177 ); 177 178 178 179 $edge_edit_types = array( ··· 215 216 case ManiphestTransaction::TYPE_PROJECTS: 216 217 $current = $task->getProjectPHIDs(); 217 218 break; 218 - case ManiphestTransaction::TYPE_CCS: 219 - $current = $task->getCCPHIDs(); 219 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 220 + $current = $task->getSubscriberPHIDs(); 220 221 break; 221 222 } 222 223 } ··· 246 247 continue 2; 247 248 } 248 249 break; 249 - case ManiphestTransaction::TYPE_CCS: 250 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 250 251 if (empty($value)) { 251 252 continue 2; 252 253 } ··· 274 275 } 275 276 break; 276 277 case ManiphestTransaction::TYPE_PROJECTS: 277 - case ManiphestTransaction::TYPE_CCS: 278 - $remove_actions = array( 279 - 'remove_project' => true, 280 - 'remove_ccs' => true, 281 - ); 282 - $is_remove = isset($remove_actions[$action['action']]); 278 + $is_remove = $action['action'] == 'remove_project'; 283 279 284 280 $current = array_fill_keys($current, true); 285 281 $value = array_fill_keys($value, true); ··· 308 304 } 309 305 310 306 $value = array_keys($new); 307 + break; 308 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 309 + $is_remove = $action['action'] == 'remove_ccs'; 310 + 311 + $current = array_fill_keys($current, true); 312 + 313 + $new = array(); 314 + $did_something = false; 315 + 316 + if ($is_remove) { 317 + foreach ($value as $phid) { 318 + if (isset($current[$phid])) { 319 + $new[$phid] = true; 320 + $did_something = true; 321 + } 322 + } 323 + if ($new) { 324 + $value = array('-' => array_keys($new)); 325 + } 326 + } else { 327 + $new = array(); 328 + foreach ($value as $phid) { 329 + $new[$phid] = true; 330 + $did_something = true; 331 + } 332 + if ($new) { 333 + $value = array('+' => array_keys($new)); 334 + } 335 + } 336 + if (!$did_something) { 337 + continue 2; 338 + } 339 + 311 340 break; 312 341 } 313 342
-51
src/applications/maniphest/controller/ManiphestSubscribeController.php
··· 1 - <?php 2 - 3 - final class ManiphestSubscribeController extends ManiphestController { 4 - 5 - private $id; 6 - private $action; 7 - 8 - public function willProcessRequest(array $data) { 9 - $this->id = $data['id']; 10 - $this->action = $data['action']; 11 - } 12 - 13 - public function processRequest() { 14 - 15 - $request = $this->getRequest(); 16 - $user = $request->getUser(); 17 - 18 - $task = id(new ManiphestTaskQuery()) 19 - ->setViewer($user) 20 - ->withIDs(array($this->id)) 21 - ->executeOne(); 22 - if (!$task) { 23 - return new Aphront404Response(); 24 - } 25 - 26 - $ccs = $task->getCCPHIDs(); 27 - switch ($this->action) { 28 - case 'add': 29 - $ccs[] = $user->getPHID(); 30 - break; 31 - case 'rem': 32 - $ccs = array_diff($ccs, array($user->getPHID())); 33 - break; 34 - default: 35 - return new Aphront400Response(); 36 - } 37 - 38 - $xaction = id(new ManiphestTransaction()) 39 - ->setTransactionType(ManiphestTransaction::TYPE_CCS) 40 - ->setNewValue($ccs); 41 - 42 - $editor = id(new ManiphestTransactionEditor()) 43 - ->setActor($user) 44 - ->setContentSourceFromRequest($request) 45 - ->setContinueOnNoEffect(true) 46 - ->setContinueOnMissingFields(true) 47 - ->applyTransactions($task, array($xaction)); 48 - 49 - return id(new AphrontRedirectResponse())->setURI('/T'.$task->getID()); 50 - } 51 - }
+13 -47
src/applications/maniphest/controller/ManiphestTaskDetailController.php
··· 23 23 $task = id(new ManiphestTaskQuery()) 24 24 ->setViewer($user) 25 25 ->withIDs(array($this->id)) 26 + ->needSubscriberPHIDs(true) 26 27 ->executeOne(); 27 28 if (!$task) { 28 29 return new Aphront404Response(); ··· 65 66 $edges = idx($query->execute(), $phid); 66 67 $phids = array_fill_keys($query->getDestinationPHIDs(), true); 67 68 68 - foreach ($task->getCCPHIDs() as $phid) { 69 - $phids[$phid] = true; 70 - } 71 - foreach ($task->getProjectPHIDs() as $phid) { 72 - $phids[$phid] = true; 73 - } 74 69 if ($task->getOwnerPHID()) { 75 70 $phids[$task->getOwnerPHID()] = true; 76 71 } ··· 139 134 $resolution_types = ManiphestTaskStatus::getTaskStatusMap(); 140 135 141 136 $transaction_types = array( 142 - PhabricatorTransactions::TYPE_COMMENT => pht('Comment'), 143 - ManiphestTransaction::TYPE_STATUS => pht('Change Status'), 144 - ManiphestTransaction::TYPE_OWNER => pht('Reassign / Claim'), 145 - ManiphestTransaction::TYPE_CCS => pht('Add CCs'), 146 - ManiphestTransaction::TYPE_PRIORITY => pht('Change Priority'), 147 - ManiphestTransaction::TYPE_PROJECTS => pht('Associate Projects'), 137 + PhabricatorTransactions::TYPE_COMMENT => pht('Comment'), 138 + ManiphestTransaction::TYPE_STATUS => pht('Change Status'), 139 + ManiphestTransaction::TYPE_OWNER => pht('Reassign / Claim'), 140 + PhabricatorTransactions::TYPE_SUBSCRIBERS => pht('Add CCs'), 141 + ManiphestTransaction::TYPE_PRIORITY => pht('Change Priority'), 142 + ManiphestTransaction::TYPE_PROJECTS => pht('Associate Projects'), 148 143 ); 149 144 150 145 // Remove actions the user doesn't have permission to take. ··· 265 260 ->setValue(pht('Submit'))); 266 261 267 262 $control_map = array( 268 - ManiphestTransaction::TYPE_STATUS => 'resolution', 269 - ManiphestTransaction::TYPE_OWNER => 'assign_to', 270 - ManiphestTransaction::TYPE_CCS => 'ccs', 271 - ManiphestTransaction::TYPE_PRIORITY => 'priority', 272 - ManiphestTransaction::TYPE_PROJECTS => 'projects', 263 + ManiphestTransaction::TYPE_STATUS => 'resolution', 264 + ManiphestTransaction::TYPE_OWNER => 'assign_to', 265 + PhabricatorTransactions::TYPE_SUBSCRIBERS => 'ccs', 266 + ManiphestTransaction::TYPE_PRIORITY => 'priority', 267 + ManiphestTransaction::TYPE_PROJECTS => 'projects', 273 268 ); 274 269 275 270 $projects_source = new PhabricatorProjectDatasource(); ··· 289 284 'limit' => 1, 290 285 'placeholder' => $users_source->getPlaceholderText(), 291 286 ), 292 - ManiphestTransaction::TYPE_CCS => array( 287 + PhabricatorTransactions::TYPE_SUBSCRIBERS => array( 293 288 'id' => 'cc-tokenizer', 294 289 'src' => $mailable_source->getDatasourceURI(), 295 290 'placeholder' => $mailable_source->getPlaceholderText(), ··· 397 392 private function buildActionView(ManiphestTask $task) { 398 393 $viewer = $this->getRequest()->getUser(); 399 394 $viewer_phid = $viewer->getPHID(); 400 - $viewer_is_cc = in_array($viewer_phid, $task->getCCPHIDs()); 401 395 402 396 $id = $task->getID(); 403 397 $phid = $task->getPHID(); ··· 419 413 ->setHref($this->getApplicationURI("/task/edit/{$id}/")) 420 414 ->setDisabled(!$can_edit) 421 415 ->setWorkflow(!$can_edit)); 422 - 423 - if ($task->getOwnerPHID() === $viewer_phid) { 424 - $view->addAction( 425 - id(new PhabricatorActionView()) 426 - ->setName(pht('Automatically Subscribed')) 427 - ->setDisabled(true) 428 - ->setIcon('fa-check-circle')); 429 - } else { 430 - $action = $viewer_is_cc ? 'rem' : 'add'; 431 - $name = $viewer_is_cc ? pht('Unsubscribe') : pht('Subscribe'); 432 - $icon = $viewer_is_cc ? 'fa-minus-circle' : 'fa-plus-circle'; 433 - 434 - $view->addAction( 435 - id(new PhabricatorActionView()) 436 - ->setName($name) 437 - ->setHref("/maniphest/subscribe/{$action}/{$id}/") 438 - ->setRenderAsForm(true) 439 - ->setUser($viewer) 440 - ->setIcon($icon)); 441 - } 442 416 443 417 $view->addAction( 444 418 id(new PhabricatorActionView()) ··· 489 463 $view->addProperty( 490 464 pht('Priority'), 491 465 ManiphestTaskPriority::getTaskPriorityName($task->getPriority())); 492 - 493 - $handles = $this->getLoadedHandles(); 494 - $cc_handles = array_select_keys($handles, $task->getCCPHIDs()); 495 - $subscriber_html = id(new SubscriptionListStringBuilder()) 496 - ->setObjectPHID($task->getPHID()) 497 - ->setHandles($cc_handles) 498 - ->buildPropertyString(); 499 - $view->addProperty(pht('Subscribers'), $subscriber_html); 500 466 501 467 $view->addProperty( 502 468 pht('Author'),
+11 -8
src/applications/maniphest/controller/ManiphestTaskEditController.php
··· 38 38 PhabricatorPolicyCapability::CAN_EDIT, 39 39 )) 40 40 ->withIDs(array($this->id)) 41 + ->needSubscriberPHIDs(true) 41 42 ->executeOne(); 42 43 if (!$task) { 43 44 return new Aphront404Response(); ··· 215 216 $task->setDescription($new_desc); 216 217 $task->setPriority($request->getInt('priority')); 217 218 $task->setOwnerPHID($owner_phid); 218 - $task->setCCPHIDs($request->getArr('cc')); 219 + $task->attachSubscriberPHIDs($request->getArr('cc')); 219 220 $task->attachProjectPHIDs($request->getArr('projects')); 220 221 } else { 221 222 ··· 227 228 $changes[ManiphestTransaction::TYPE_OWNER] = $owner_phid; 228 229 } 229 230 230 - $changes[ManiphestTransaction::TYPE_CCS] = $request->getArr('cc'); 231 + $changes[PhabricatorTransactions::TYPE_SUBSCRIBERS] = 232 + array('=' => $request->getArr('cc')); 231 233 232 234 if ($can_edit_projects) { 233 235 $projects = $request->getArr('projects'); ··· 436 438 } 437 439 } else { 438 440 if (!$task->getID()) { 439 - $task->setCCPHIDs(array( 441 + $task->attachSubscriberPHIDs(array( 440 442 $user->getPHID(), 441 443 )); 442 444 if ($template_id) { 443 445 $template_task = id(new ManiphestTaskQuery()) 444 446 ->setViewer($user) 445 447 ->withIDs(array($template_id)) 448 + ->needSubscriberPHIDs(true) 446 449 ->executeOne(); 447 450 if ($template_task) { 448 451 $cc_phids = array_unique(array_merge( 449 - $template_task->getCCPHIDs(), 452 + $template_task->getSubscriberPHIDs(), 450 453 array($user->getPHID()))); 451 - $task->setCCPHIDs($cc_phids); 454 + $task->attachSubscriberPHIDs($cc_phids); 452 455 $task->attachProjectPHIDs($template_task->getProjectPHIDs()); 453 456 $task->setOwnerPHID($template_task->getOwnerPHID()); 454 457 $task->setPriority($template_task->getPriority()); ··· 486 489 487 490 $phids = array_merge( 488 491 array($task->getOwnerPHID()), 489 - $task->getCCPHIDs(), 492 + $task->getSubscriberPHIDs(), 490 493 $task->getProjectPHIDs()); 491 494 492 495 if ($parent_task) { ··· 512 515 $assigned_value = array(); 513 516 } 514 517 515 - if ($task->getCCPHIDs()) { 516 - $cc_value = array_select_keys($handles, $task->getCCPHIDs()); 518 + if ($task->getSubscriberPHIDs()) { 519 + $cc_value = array_select_keys($handles, $task->getSubscriberPHIDs()); 517 520 } else { 518 521 $cc_value = array(); 519 522 }
+5 -8
src/applications/maniphest/controller/ManiphestTransactionPreviewController.php
··· 58 58 } 59 59 $transaction->setNewValue($value); 60 60 break; 61 - case ManiphestTransaction::TYPE_CCS: 61 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 62 62 if ($value) { 63 63 $value = json_decode($value); 64 64 } 65 65 if (!$value) { 66 66 $value = array(); 67 67 } 68 - $phids = $value; 69 - 70 - foreach ($task->getCCPHIDs() as $cc_phid) { 68 + $phids = array(); 69 + foreach ($value as $cc_phid) { 71 70 $phids[] = $cc_phid; 72 - $value[] = $cc_phid; 73 71 } 74 - 75 - $transaction->setOldValue($task->getCCPHIDs()); 76 - $transaction->setNewValue($value); 72 + $transaction->setOldValue(array()); 73 + $transaction->setNewValue($phids); 77 74 break; 78 75 case ManiphestTransaction::TYPE_PROJECTS: 79 76 if ($value) {
+9 -6
src/applications/maniphest/controller/ManiphestTransactionSaveController.php
··· 9 9 $task = id(new ManiphestTaskQuery()) 10 10 ->setViewer($user) 11 11 ->withIDs(array($request->getStr('taskID'))) 12 + ->needSubscriberPHIDs(true) 12 13 ->executeOne(); 13 14 if (!$task) { 14 15 return new Aphront404Response(); ··· 33 34 34 35 $cc_transaction = new ManiphestTransaction(); 35 36 $cc_transaction 36 - ->setTransactionType(ManiphestTransaction::TYPE_CCS); 37 + ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS); 37 38 38 39 $transaction = new ManiphestTransaction(); 39 40 $transaction ··· 64 65 '+' => array_fuse($projects), 65 66 )); 66 67 break; 67 - case ManiphestTransaction::TYPE_CCS: 68 + case PhabricatorTransactions::TYPE_SUBSCRIBERS: 68 69 // Accumulate the new explicit CCs into the array that we'll add in 69 70 // the CC transaction later. 70 71 $added_ccs = array_merge($added_ccs, $request->getArr('ccs')); ··· 135 136 if (!$user_owns_task) { 136 137 // If we aren't making the user the new task owner and they aren't the 137 138 // existing task owner, add them to CC unless they're aleady CC'd. 138 - if (!in_array($user->getPHID(), $task->getCCPHIDs())) { 139 + if (!in_array($user->getPHID(), $task->getSubscriberPHIDs())) { 139 140 $added_ccs[] = $user->getPHID(); 140 141 } 141 142 } 142 143 143 144 // Evade no-effect detection in the new editor stuff until we can switch 144 145 // to subscriptions. 145 - $added_ccs = array_filter(array_diff($added_ccs, $task->getCCPHIDs())); 146 + $added_ccs = array_filter(array_diff( 147 + $added_ccs, 148 + $task->getSubscriberPHIDs())); 146 149 147 150 if ($added_ccs) { 148 151 // We've added CCs, so include a CC transaction. 149 - $all_ccs = array_merge($task->getCCPHIDs(), $added_ccs); 150 - $cc_transaction->setNewValue($all_ccs); 152 + $all_ccs = array_merge($task->getSubscriberPHIDs(), $added_ccs); 153 + $cc_transaction->setNewValue(array('=' => $all_ccs)); 151 154 $transactions[] = $cc_transaction; 152 155 } 153 156
+5 -26
src/applications/maniphest/editor/ManiphestTransactionEditor.php
··· 23 23 $types[] = ManiphestTransaction::TYPE_TITLE; 24 24 $types[] = ManiphestTransaction::TYPE_DESCRIPTION; 25 25 $types[] = ManiphestTransaction::TYPE_OWNER; 26 - $types[] = ManiphestTransaction::TYPE_CCS; 27 26 $types[] = ManiphestTransaction::TYPE_SUBPRIORITY; 28 27 $types[] = ManiphestTransaction::TYPE_PROJECT_COLUMN; 29 28 $types[] = ManiphestTransaction::TYPE_MERGED_INTO; ··· 62 61 return $object->getDescription(); 63 62 case ManiphestTransaction::TYPE_OWNER: 64 63 return nonempty($object->getOwnerPHID(), null); 65 - case ManiphestTransaction::TYPE_CCS: 66 - return array_values(array_unique($object->getCCPHIDs())); 67 64 case ManiphestTransaction::TYPE_PROJECT_COLUMN: 68 65 // These are pre-populated. 69 66 return $xaction->getOldValue(); ··· 82 79 switch ($xaction->getTransactionType()) { 83 80 case ManiphestTransaction::TYPE_PRIORITY: 84 81 return (int)$xaction->getNewValue(); 85 - case ManiphestTransaction::TYPE_CCS: 86 - return array_values(array_unique($xaction->getNewValue())); 87 82 case ManiphestTransaction::TYPE_OWNER: 88 83 return nonempty($xaction->getNewValue(), null); 89 84 case ManiphestTransaction::TYPE_STATUS: ··· 106 101 $new = $xaction->getNewValue(); 107 102 108 103 switch ($xaction->getTransactionType()) { 109 - case ManiphestTransaction::TYPE_CCS: 110 - sort($old); 111 - sort($new); 112 - return ($old !== $new); 113 104 case ManiphestTransaction::TYPE_PROJECT_COLUMN: 114 105 $new_column_phids = $new['columnPHIDs']; 115 106 $old_column_phids = $old['columnPHIDs']; ··· 155 146 } 156 147 157 148 return $object->setOwnerPHID($phid); 158 - case ManiphestTransaction::TYPE_CCS: 159 - return $object->setCCPHIDs($xaction->getNewValue()); 160 149 case ManiphestTransaction::TYPE_SUBPRIORITY: 161 150 $data = $xaction->getNewValue(); 162 151 $new_sub = $this->getNextSubpriority( ··· 433 422 protected function getMailCC(PhabricatorLiskDAO $object) { 434 423 $phids = array(); 435 424 436 - foreach ($object->getCCPHIDs() as $phid) { 437 - $phids[] = $phid; 438 - } 439 - 440 425 foreach (parent::getMailCC($object) as $phid) { 441 426 $phids[] = $phid; 442 427 } ··· 561 546 HeraldAdapter $adapter, 562 547 HeraldTranscript $transcript) { 563 548 564 - // TODO: Convert these to transactions. The way Maniphest deals with these 565 - // transactions is currently unconventional and messy. 549 + $this->heraldEmailPHIDs = $adapter->getEmailPHIDs(); 550 + $xactions = array(); 566 551 567 - $save_again = false; 568 552 $cc_phids = $adapter->getCcPHIDs(); 569 553 if ($cc_phids) { 570 - $existing_cc = $object->getCCPHIDs(); 571 - $new_cc = array_unique(array_merge($cc_phids, $existing_cc)); 572 - $object->setCCPHIDs($new_cc); 573 - $object->save(); 554 + $xactions[] = id(new ManiphestTransaction()) 555 + ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) 556 + ->setNewValue(array('+' => $cc_phids)); 574 557 } 575 - 576 - $this->heraldEmailPHIDs = $adapter->getEmailPHIDs(); 577 - 578 - $xactions = array(); 579 558 580 559 $assign_phid = $adapter->getAssignPHID(); 581 560 if ($assign_phid) {
+2 -2
src/applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php
··· 28 28 $this->generateTaskStatus(); 29 29 $changes[ManiphestTransaction::TYPE_PRIORITY] = 30 30 $this->generateTaskPriority(); 31 - $changes[ManiphestTransaction::TYPE_CCS] = 32 - $this->getCCPHIDs(); 31 + $changes[PhabricatorTransactions::TYPE_SUBSCRIBERS] = 32 + array('=' => $this->getCCPHIDs()); 33 33 $transactions = array(); 34 34 foreach ($changes as $type => $value) { 35 35 $transaction = clone $template;
+7 -6
src/applications/maniphest/mail/ManiphestReplyHandler.php
··· 106 106 break; 107 107 case 'unsubscribe': 108 108 $is_unsub = true; 109 - $ttype = ManiphestTransaction::TYPE_CCS; 110 - $ccs = $task->getCCPHIDs(); 109 + $ttype = PhabricatorTransactions::TYPE_SUBSCRIBERS; 110 + $ccs = $task->getSubscriberPHIDs(); 111 111 foreach ($ccs as $k => $phid) { 112 112 if ($phid == $user->getPHID()) { 113 113 unset($ccs[$k]); 114 114 } 115 115 } 116 - $new_value = array_values($ccs); 116 + $new_value = array('=' => array_values($ccs)); 117 117 break; 118 118 } 119 119 ··· 135 135 } 136 136 137 137 $ccs = $mail->loadCCPHIDs(); 138 - $old_ccs = $task->getCCPHIDs(); 138 + $old_ccs = $task->getSubscriberPHIDs(); 139 139 $new_ccs = array_merge($old_ccs, $ccs); 140 140 if (!$is_unsub) { 141 141 $new_ccs[] = $user->getPHID(); ··· 144 144 145 145 if (array_diff($new_ccs, $old_ccs)) { 146 146 $cc_xaction = clone $template; 147 - $cc_xaction->setTransactionType(ManiphestTransaction::TYPE_CCS); 148 - $cc_xaction->setNewValue($new_ccs); 147 + $cc_xaction->setTransactionType( 148 + PhabricatorTransactions::TYPE_SUBSCRIBERS); 149 + $cc_xaction->setNewValue(array('=' => $new_ccs)); 149 150 $xactions[] = $cc_xaction; 150 151 } 151 152
+1
src/applications/maniphest/mail/ManiphestTaskMailReceiver.php
··· 17 17 $results = id(new ManiphestTaskQuery()) 18 18 ->setViewer($viewer) 19 19 ->withIDs(array($id)) 20 + ->needSubscriberPHIDs(true) 20 21 ->execute(); 21 22 22 23 return head($results);
+29 -18
src/applications/maniphest/query/ManiphestTaskQuery.php
··· 50 50 const ORDER_MODIFIED = 'order-modified'; 51 51 const ORDER_TITLE = 'order-title'; 52 52 53 + private $needSubscriberPHIDs; 54 + 53 55 const DEFAULT_PAGE_SIZE = 1000; 54 56 55 57 public function withAuthors(array $authors) { ··· 178 180 return $this; 179 181 } 180 182 183 + public function needSubscriberPHIDs($bool) { 184 + $this->needSubscriberPHIDs = $bool; 185 + return $this; 186 + } 187 + 181 188 public function loadPage() { 182 189 // TODO: (T603) It is possible for a user to find the PHID of a project 183 190 // they can't see, then query for tasks in that project and deduce the ··· 196 203 $where[] = $this->buildPrioritiesWhereClause($conn); 197 204 $where[] = $this->buildAuthorWhereClause($conn); 198 205 $where[] = $this->buildOwnerWhereClause($conn); 199 - $where[] = $this->buildSubscriberWhereClause($conn); 200 206 $where[] = $this->buildProjectWhereClause($conn); 201 207 $where[] = $this->buildAnyProjectWhereClause($conn); 202 208 $where[] = $this->buildAnyUserProjectWhereClause($conn); ··· 332 338 } 333 339 334 340 protected function didFilterPage(array $tasks) { 341 + $phids = mpull($tasks, 'getPHID'); 342 + 335 343 // TODO: Eventually, we should make this optional and introduce a 336 344 // needProjectPHIDs() method, but for now there's a lot of code which 337 345 // assumes the data is always populated. 338 346 339 347 $edge_query = id(new PhabricatorEdgeQuery()) 340 - ->withSourcePHIDs(mpull($tasks, 'getPHID')) 348 + ->withSourcePHIDs($phids) 341 349 ->withEdgeTypes( 342 350 array( 343 351 PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, ··· 345 353 $edge_query->execute(); 346 354 347 355 foreach ($tasks as $task) { 348 - $phids = $edge_query->getDestinationPHIDs(array($task->getPHID())); 349 - $task->attachProjectPHIDs($phids); 356 + $project_phids = $edge_query->getDestinationPHIDs( 357 + array($task->getPHID())); 358 + $task->attachProjectPHIDs($project_phids); 359 + } 360 + 361 + if ($this->needSubscriberPHIDs) { 362 + $subscriber_sets = id(new PhabricatorSubscribersQuery()) 363 + ->withObjectPHIDs($phids) 364 + ->execute(); 365 + foreach ($tasks as $task) { 366 + $subscribers = idx($subscriber_sets, $task->getPHID(), array()); 367 + $task->attachSubscriberPHIDs($subscribers); 368 + } 350 369 } 351 370 352 371 return $tasks; ··· 495 514 $conn, 496 515 'phid IN (%Ls)', 497 516 $fulltext_results); 498 - } 499 - 500 - private function buildSubscriberWhereClause(AphrontDatabaseConnection $conn) { 501 - if (!$this->subscriberPHIDs) { 502 - return null; 503 - } 504 - 505 - return qsprintf( 506 - $conn, 507 - 'subscriber.subscriberPHID IN (%Ls)', 508 - $this->subscriberPHIDs); 509 517 } 510 518 511 519 private function buildProjectWhereClause(AphrontDatabaseConnection $conn) { ··· 708 716 } 709 717 710 718 if ($this->subscriberPHIDs) { 711 - $subscriber_dao = new ManiphestTaskSubscriber(); 712 719 $joins[] = qsprintf( 713 720 $conn_r, 714 - 'JOIN %T subscriber ON subscriber.taskPHID = task.phid', 715 - $subscriber_dao->getTableName()); 721 + 'JOIN %T e_ccs ON e_ccs.src = task.phid '. 722 + 'AND e_ccs.type = %s '. 723 + 'AND e_ccs.dst in (%Ls)', 724 + PhabricatorEdgeConfig::TABLE_NAME_EDGE, 725 + PhabricatorEdgeConfig::TYPE_OBJECT_HAS_SUBSCRIBER, 726 + $this->subscriberPHIDs); 716 727 } 717 728 718 729 switch ($this->groupBy) {
-15
src/applications/maniphest/search/ManiphestSearchIndexer.php
··· 54 54 $task->getDateCreated()); 55 55 } 56 56 57 - // We need to load handles here since non-users may subscribe (mailing 58 - // lists, e.g.) 59 - $ccs = $task->getCCPHIDs(); 60 - $handles = id(new PhabricatorHandleQuery()) 61 - ->setViewer(PhabricatorUser::getOmnipotentUser()) 62 - ->withPHIDs($ccs) 63 - ->execute(); 64 - foreach ($ccs as $cc) { 65 - $doc->addRelationship( 66 - PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER, 67 - $handles[$cc]->getPHID(), 68 - $handles[$cc]->getType(), 69 - time()); 70 - } 71 - 72 57 return $doc; 73 58 } 74 59
+24 -16
src/applications/maniphest/storage/ManiphestTask.php
··· 2 2 3 3 final class ManiphestTask extends ManiphestDAO 4 4 implements 5 + PhabricatorSubscribableInterface, 5 6 PhabricatorMarkupInterface, 6 7 PhabricatorPolicyInterface, 7 8 PhabricatorTokenReceiverInterface, ··· 17 18 18 19 protected $authorPHID; 19 20 protected $ownerPHID; 20 - protected $ccPHIDs = array(); 21 21 22 22 protected $status; 23 23 protected $priority; ··· 33 33 34 34 protected $attached = array(); 35 35 protected $projectPHIDs = array(); 36 - private $subscribersNeedUpdate; 37 36 38 37 protected $ownerOrdering; 39 38 39 + private $subscriberPHIDs = self::ATTACHABLE; 40 40 private $groupByProjectPHID = self::ATTACHABLE; 41 41 private $customFields = self::ATTACHABLE; 42 42 private $edgeProjectPHIDs = self::ATTACHABLE; ··· 56 56 ->setAuthorPHID($actor->getPHID()) 57 57 ->setViewPolicy($view_policy) 58 58 ->setEditPolicy($edit_policy) 59 - ->attachProjectPHIDs(array()); 59 + ->attachProjectPHIDs(array()) 60 + ->attachSubscriberPHIDs(array()); 60 61 } 61 62 62 63 public function getConfiguration() { ··· 141 142 return PhabricatorPHID::generateNewPHID(ManiphestTaskPHIDType::TYPECONST); 142 143 } 143 144 144 - public function getCCPHIDs() { 145 - return array_values(nonempty($this->ccPHIDs, array())); 145 + public function getSubscriberPHIDs() { 146 + return $this->assertAttached($this->subscriberPHIDs); 146 147 } 147 148 148 149 public function getProjectPHIDs() { ··· 154 155 return $this; 155 156 } 156 157 157 - public function setCCPHIDs(array $phids) { 158 - $this->ccPHIDs = array_values($phids); 159 - $this->subscribersNeedUpdate = true; 158 + public function attachSubscriberPHIDs(array $phids) { 159 + $this->subscriberPHIDs = $phids; 160 160 return $this; 161 161 } 162 162 163 163 public function setOwnerPHID($phid) { 164 164 $this->ownerPHID = nonempty($phid, null); 165 - $this->subscribersNeedUpdate = true; 166 165 return $this; 167 166 } 168 167 ··· 194 193 195 194 $result = parent::save(); 196 195 197 - if ($this->subscribersNeedUpdate) { 198 - // If we've changed the subscriber PHIDs for this task, update the link 199 - // table. 200 - ManiphestTaskSubscriber::updateTaskSubscribers($this); 201 - $this->subscribersNeedUpdate = false; 202 - } 203 - 204 196 return $result; 205 197 } 206 198 ··· 214 206 -$this->getSubpriority(), 215 207 $this->getID(), 216 208 ); 209 + } 210 + 211 + 212 + /* -( PhabricatorSubscribableInterface )----------------------------------- */ 213 + 214 + 215 + public function isAutomaticallySubscribed($phid) { 216 + return ($phid == $this->getOwnerPHID()); 217 + } 218 + 219 + public function shouldShowSubscribersProperty() { 220 + return true; 221 + } 222 + 223 + public function shouldAllowSubscription($phid) { 224 + return true; 217 225 } 218 226 219 227
+3
src/applications/maniphest/storage/ManiphestTaskSubscriber.php
··· 1 1 <?php 2 2 3 + /** 4 + * Deprecated; delete me. 5 + */ 3 6 final class ManiphestTaskSubscriber extends ManiphestDAO { 4 7 5 8 protected $taskPHID;
+6 -1
src/applications/maniphest/storage/ManiphestTransaction.php
··· 7 7 const TYPE_STATUS = 'status'; 8 8 const TYPE_DESCRIPTION = 'description'; 9 9 const TYPE_OWNER = 'reassign'; 10 - const TYPE_CCS = 'ccs'; 11 10 const TYPE_PROJECTS = 'projects'; 12 11 const TYPE_PRIORITY = 'priority'; 13 12 const TYPE_EDGE = 'edge'; ··· 21 20 // NOTE: this type is deprecated. Keep it around for legacy installs 22 21 // so any transactions render correctly. 23 22 const TYPE_ATTACH = 'attach'; 23 + /** 24 + * TYPE_CCS is legacy and depracted in favor of 25 + * PhabricatorTransactions::TYPE_SUBSCRIBERS; keep it around for legacy 26 + * transaction-rendering. 27 + */ 28 + const TYPE_CCS = 'ccs'; 24 29 25 30 26 31 const MAILTAG_STATUS = 'maniphest-status';
+9 -4
src/applications/search/controller/PhabricatorSearchAttachController.php
··· 143 143 $targets = id(new ManiphestTaskQuery()) 144 144 ->setViewer($user) 145 145 ->withPHIDs(array_keys($phids)) 146 + ->needSubscriberPHIDs(true) 146 147 ->execute(); 147 148 148 149 if (empty($targets)) { ··· 156 157 ->setContinueOnMissingFields(true); 157 158 158 159 $cc_vector = array(); 159 - $cc_vector[] = $task->getCCPHIDs(); 160 + // since we loaded this via a generic object query, go ahead and get the 161 + // attach the cc phids now 162 + $task->attachSubscriberPHIDs( 163 + PhabricatorSubscribersQuery::loadSubscribersForPHID($task->getPHID())); 164 + $cc_vector[] = $task->getSubscriberPHIDs(); 160 165 foreach ($targets as $target) { 161 - $cc_vector[] = $target->getCCPHIDs(); 166 + $cc_vector[] = $target->getSubscriberPHIDs(); 162 167 $cc_vector[] = array( 163 168 $target->getAuthorPHID(), 164 169 $target->getOwnerPHID(), ··· 178 183 $all_ccs = array_unique($all_ccs); 179 184 180 185 $add_ccs = id(new ManiphestTransaction()) 181 - ->setTransactionType(ManiphestTransaction::TYPE_CCS) 182 - ->setNewValue($all_ccs); 186 + ->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS) 187 + ->setNewValue(array('=' => $all_ccs)); 183 188 184 189 $merged_from_txn = id(new ManiphestTransaction()) 185 190 ->setTransactionType(ManiphestTransaction::TYPE_MERGED_FROM)
-2
src/applications/subscriptions/controller/PhabricatorSubscriptionsListController.php
··· 23 23 if ($object instanceof PhabricatorSubscribableInterface) { 24 24 $subscriber_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( 25 25 $phid); 26 - } else if ($object instanceof ManiphestTask) { 27 - $subscriber_phids = $object->getCCPHIDs(); 28 26 } 29 27 30 28 $handle_phids = $subscriber_phids;