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

Use standard infrastructure for Audit email generation

Summary: Ref T4896. Replace custom stuff with standard stuff.

Test Plan:
- Sent a bunch of email and it all looked sensible/correct.
- Made sure to test inlines, specifically, as they're a bit tricky.

Reviewers: joshuaspence, btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T4896

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

+173 -222
+4 -221
src/applications/audit/editor/PhabricatorAuditCommentEditor.php
··· 217 217 $commit->updateAuditStatus($requests); 218 218 $commit->save(); 219 219 220 + $commit->attachAudits($requests); 221 + 220 222 // Convert old comments into real transactions and apply them with a 221 223 // normal editor. 222 224 ··· 240 242 ->setContinueOnNoEffect(true) 241 243 ->setContinueOnMissingFields(true) 242 244 ->setContentSource($content_source) 245 + ->setExcludeMailRecipientPHIDs($this->getExcludeMailRecipientPHIDs()) 246 + ->setDisableEmail($this->noEmail) 243 247 ->applyTransactions($commit, $xactions); 244 248 245 249 $feed_dont_publish_phids = array(); ··· 261 265 $feed_phids = array_diff($requests_phids, $feed_dont_publish_phids); 262 266 foreach ($comments as $comment) { 263 267 $this->publishFeedStory($comment, $feed_phids); 264 - } 265 - 266 - if (!$this->noEmail) { 267 - $this->sendMail( 268 - $comments, 269 - $other_comments, 270 - $inline_comments, 271 - $requests); 272 268 } 273 269 } 274 270 ··· 335 331 'content' => $comment->getContent(), 336 332 )) 337 333 ->publish(); 338 - } 339 - 340 - private function sendMail( 341 - array $comments, 342 - array $other_comments, 343 - array $inline_comments, 344 - array $requests) { 345 - 346 - assert_instances_of($comments, 'PhabricatorAuditComment'); 347 - assert_instances_of($other_comments, 'PhabricatorAuditComment'); 348 - assert_instances_of($inline_comments, 'PhabricatorInlineCommentInterface'); 349 - 350 - $commit = $this->commit; 351 - 352 - $data = $commit->loadCommitData(); 353 - $summary = $data->getSummary(); 354 - 355 - $commit_phid = $commit->getPHID(); 356 - $phids = array($commit_phid); 357 - $handles = id(new PhabricatorHandleQuery()) 358 - ->setViewer($this->getActor()) 359 - ->withPHIDs($phids) 360 - ->execute(); 361 - $handle = $handles[$commit_phid]; 362 - 363 - $name = $handle->getName(); 364 - 365 - $map = array( 366 - PhabricatorAuditActionConstants::CONCERN => 'Raised Concern', 367 - PhabricatorAuditActionConstants::ACCEPT => 'Accepted', 368 - PhabricatorAuditActionConstants::RESIGN => 'Resigned', 369 - PhabricatorAuditActionConstants::CLOSE => 'Closed', 370 - PhabricatorAuditActionConstants::ADD_CCS => 'Added CCs', 371 - PhabricatorAuditActionConstants::ADD_AUDITORS => 'Added Auditors', 372 - ); 373 - if ($comments) { 374 - $any_action = head($comments)->getAction(); 375 - } else { 376 - $any_action = null; 377 - } 378 - $verb = idx($map, $any_action, 'Commented On'); 379 - 380 - $reply_handler = self::newReplyHandlerForCommit($commit); 381 - 382 - $prefix = PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix'); 383 - 384 - $repository = id(new PhabricatorRepositoryQuery()) 385 - ->setViewer($this->getActor()) 386 - ->withIDs(array($commit->getRepositoryID())) 387 - ->executeOne(); 388 - $threading = self::getMailThreading($repository, $commit); 389 - list($thread_id, $thread_topic) = $threading; 390 - 391 - $body = $this->renderMailBody( 392 - $comments, 393 - "{$name}: {$summary}", 394 - $handle, 395 - $reply_handler, 396 - $inline_comments); 397 - 398 - $email_to = array(); 399 - $email_cc = array(); 400 - 401 - $email_to[$this->getActor()->getPHID()] = true; 402 - 403 - $author_phid = $data->getCommitDetail('authorPHID'); 404 - if ($author_phid) { 405 - $email_to[$author_phid] = true; 406 - } 407 - 408 - foreach ($other_comments as $other_comment) { 409 - $email_cc[$other_comment->getActorPHID()] = true; 410 - } 411 - 412 - $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID( 413 - $commit->getPHID()); 414 - foreach ($subscribers as $subscriber) { 415 - $email_cc[$subscriber] = true; 416 - } 417 - 418 - foreach ($requests as $request) { 419 - switch ($request->getAuditStatus()) { 420 - case PhabricatorAuditStatusConstants::AUDIT_REQUIRED: 421 - $email_cc[$request->getAuditorPHID()] = true; 422 - break; 423 - case PhabricatorAuditStatusConstants::RESIGNED: 424 - unset($email_cc[$request->getAuditorPHID()]); 425 - break; 426 - case PhabricatorAuditStatusConstants::CONCERNED: 427 - case PhabricatorAuditStatusConstants::AUDIT_REQUESTED: 428 - $email_to[$request->getAuditorPHID()] = true; 429 - break; 430 - } 431 - } 432 - 433 - $email_to = array_keys($email_to); 434 - $email_cc = array_keys($email_cc); 435 - 436 - $phids = array_merge($email_to, $email_cc); 437 - $handles = id(new PhabricatorHandleQuery()) 438 - ->setViewer($this->getActor()) 439 - ->withPHIDs($phids) 440 - ->execute(); 441 - 442 - // NOTE: Always set $is_new to false, because the "first" mail in the 443 - // thread is the Herald notification of the commit. 444 - $is_new = false; 445 - 446 - $template = id(new PhabricatorMetaMTAMail()) 447 - ->setSubject("{$name}: {$summary}") 448 - ->setSubjectPrefix($prefix) 449 - ->setVarySubjectPrefix("[{$verb}]") 450 - ->setFrom($this->getActor()->getPHID()) 451 - ->setThreadID($thread_id, $is_new) 452 - ->addHeader('Thread-Topic', $thread_topic) 453 - ->setRelatedPHID($commit->getPHID()) 454 - ->setExcludeMailRecipientPHIDs($this->getExcludeMailRecipientPHIDs()) 455 - ->setIsBulk(true) 456 - ->setBody($body); 457 - 458 - $mails = $reply_handler->multiplexMail( 459 - $template, 460 - array_select_keys($handles, $email_to), 461 - array_select_keys($handles, $email_cc)); 462 - 463 - foreach ($mails as $mail) { 464 - $mail->saveAndSend(); 465 - } 466 - } 467 - 468 - public static function getMailThreading( 469 - PhabricatorRepository $repository, 470 - PhabricatorRepositoryCommit $commit) { 471 - 472 - return array( 473 - 'diffusion-audit-'.$commit->getPHID(), 474 - 'Commit r'.$repository->getCallsign().$commit->getCommitIdentifier(), 475 - ); 476 - } 477 - 478 - public static function newReplyHandlerForCommit($commit) { 479 - $reply_handler = PhabricatorEnv::newObjectFromConfig( 480 - 'metamta.diffusion.reply-handler'); 481 - $reply_handler->setMailReceiver($commit); 482 - return $reply_handler; 483 - } 484 - 485 - private function renderMailBody( 486 - array $comments, 487 - $cname, 488 - PhabricatorObjectHandle $handle, 489 - PhabricatorMailReplyHandler $reply_handler, 490 - array $inline_comments) { 491 - 492 - assert_instances_of($comments, 'PhabricatorAuditComment'); 493 - assert_instances_of($inline_comments, 'PhabricatorInlineCommentInterface'); 494 - 495 - $commit = $this->commit; 496 - $actor = $this->getActor(); 497 - $name = $actor->getUsername(); 498 - 499 - $body = new PhabricatorMetaMTAMailBody(); 500 - foreach ($comments as $comment) { 501 - if ($comment->getAction() == PhabricatorAuditActionConstants::INLINE) { 502 - continue; 503 - } 504 - 505 - $verb = PhabricatorAuditActionConstants::getActionPastTenseVerb( 506 - $comment->getAction()); 507 - 508 - $body->addRawSection("{$name} {$verb} commit {$cname}."); 509 - 510 - $content = $comment->getContent(); 511 - if (strlen($content)) { 512 - $body->addRawSection($comment->getContent()); 513 - } 514 - } 515 - 516 - if ($inline_comments) { 517 - $block = array(); 518 - 519 - $path_map = id(new DiffusionPathQuery()) 520 - ->withPathIDs(mpull($inline_comments, 'getPathID')) 521 - ->execute(); 522 - $path_map = ipull($path_map, 'path', 'id'); 523 - 524 - foreach ($inline_comments as $inline) { 525 - $path = idx($path_map, $inline->getPathID()); 526 - if ($path === null) { 527 - continue; 528 - } 529 - 530 - $start = $inline->getLineNumber(); 531 - $len = $inline->getLineLength(); 532 - if ($len) { 533 - $range = $start.'-'.($start + $len); 534 - } else { 535 - $range = $start; 536 - } 537 - 538 - $content = $inline->getContent(); 539 - $block[] = "{$path}:{$range} {$content}"; 540 - } 541 - 542 - $body->addTextSection(pht('INLINE COMMENTS'), implode("\n", $block)); 543 - } 544 - 545 - $body->addTextSection( 546 - pht('COMMIT'), 547 - PhabricatorEnv::getProductionURI($handle->getURI())); 548 - $body->addReplySection($reply_handler->getReplyHandlerInstructions()); 549 - 550 - return $body->render(); 551 334 } 552 335 553 336 }
+119
src/applications/audit/editor/PhabricatorAuditEditor.php
··· 115 115 return true; 116 116 } 117 117 118 + protected function shouldSendMail( 119 + PhabricatorLiskDAO $object, 120 + array $xactions) { 121 + return true; 122 + } 123 + 124 + protected function buildReplyHandler(PhabricatorLiskDAO $object) { 125 + $reply_handler = PhabricatorEnv::newObjectFromConfig( 126 + 'metamta.diffusion.reply-handler'); 127 + $reply_handler->setMailReceiver($object); 128 + return $reply_handler; 129 + } 130 + 131 + protected function getMailSubjectPrefix() { 132 + return PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix'); 133 + } 134 + 135 + protected function getMailThreadID(PhabricatorLiskDAO $object) { 136 + // For backward compatibility, use this legacy thread ID. 137 + return 'diffusion-audit-'.$object->getPHID(); 138 + } 139 + 140 + protected function buildMailTemplate(PhabricatorLiskDAO $object) { 141 + $identifier = $object->getCommitIdentifier(); 142 + $repository = $object->getRepository(); 143 + $monogram = $repository->getMonogram(); 144 + 145 + $summary = $object->getSummary(); 146 + $name = $repository->formatCommitName($identifier); 147 + 148 + $subject = "{$name}: {$summary}"; 149 + $thread_topic = "Commit {$monogram}{$identifier}"; 150 + 151 + return id(new PhabricatorMetaMTAMail()) 152 + ->setSubject($subject) 153 + ->addHeader('Thread-Topic', $thread_topic); 154 + } 155 + 156 + protected function getMailTo(PhabricatorLiskDAO $object) { 157 + $phids = array(); 158 + if ($object->getAuthorPHID()) { 159 + $phids[] = $object->getAuthorPHID(); 160 + } 161 + 162 + $status_resigned = PhabricatorAuditStatusConstants::RESIGNED; 163 + foreach ($object->getAudits() as $audit) { 164 + if ($audit->getAuditStatus() != $status_resigned) { 165 + $phids[] = $audit->getAuditorPHID(); 166 + } 167 + } 168 + 169 + return $phids; 170 + } 171 + 172 + protected function buildMailBody( 173 + PhabricatorLiskDAO $object, 174 + array $xactions) { 175 + 176 + $body = parent::buildMailBody($object, $xactions); 177 + 178 + $type_inline = PhabricatorAuditActionConstants::INLINE; 179 + 180 + $inlines = array(); 181 + foreach ($xactions as $xaction) { 182 + if ($xaction->getTransactionType() == $type_inline) { 183 + $inlines[] = $xaction; 184 + } 185 + } 186 + 187 + if ($inlines) { 188 + $body->addTextSection( 189 + pht('INLINE COMMENTS'), 190 + $this->renderInlineCommentsForMail($object, $inlines)); 191 + } 192 + 193 + $monogram = $object->getRepository()->formatCommitName( 194 + $object->getCommitIdentifier()); 195 + 196 + $body->addTextSection( 197 + pht('COMMIT'), 198 + PhabricatorEnv::getProductionURI('/'.$monogram)); 199 + 200 + return $body; 201 + } 202 + 203 + private function renderInlineCommentsForMail( 204 + PhabricatorLiskDAO $object, 205 + array $inline_xactions) { 206 + 207 + $inlines = mpull($inline_xactions, 'getComment'); 208 + 209 + $block = array(); 210 + 211 + $path_map = id(new DiffusionPathQuery()) 212 + ->withPathIDs(mpull($inlines, 'getPathID')) 213 + ->execute(); 214 + $path_map = ipull($path_map, 'path', 'id'); 215 + 216 + foreach ($inlines as $inline) { 217 + $path = idx($path_map, $inline->getPathID()); 218 + if ($path === null) { 219 + continue; 220 + } 221 + 222 + $start = $inline->getLineNumber(); 223 + $len = $inline->getLineLength(); 224 + if ($len) { 225 + $range = $start.'-'.($start + $len); 226 + } else { 227 + $range = $start; 228 + } 229 + 230 + $content = $inline->getContent(); 231 + $block[] = "{$path}:{$range} {$content}"; 232 + } 233 + 234 + return implode("\n", $block); 235 + } 236 + 118 237 }
+50 -1
src/applications/audit/storage/PhabricatorAuditTransaction.php
··· 42 42 return $phids; 43 43 } 44 44 45 + public function getActionName() { 46 + 47 + switch ($this->getTransactionType()) { 48 + case PhabricatorAuditActionConstants::ACTION: 49 + switch ($this->getNewValue()) { 50 + case PhabricatorAuditActionConstants::CONCERN: 51 + return pht('Raised Concern'); 52 + case PhabricatorAuditActionConstants::ACCEPT: 53 + return pht('Accepted'); 54 + case PhabricatorAuditActionConstants::RESIGN: 55 + return pht('Resigned'); 56 + case PhabricatorAuditActionConstants::CLOSE: 57 + return pht('Clsoed'); 58 + } 59 + break; 60 + case PhabricatorAuditActionConstants::ADD_AUDITORS: 61 + return pht('Added Auditors'); 62 + } 63 + 64 + return parent::getActionName(); 65 + } 66 + 45 67 public function getColor() { 46 68 47 69 $type = $this->getTransactionType(); ··· 63 85 $old = $this->getOldValue(); 64 86 $new = $this->getNewValue(); 65 87 66 - $author_handle = $this->getHandle($this->getAuthorPHID())->renderLink(); 88 + $author_handle = $this->renderHandleLink($this->getAuthorPHID()); 67 89 68 90 $type = $this->getTransactionType(); 69 91 ··· 155 177 } 156 178 157 179 return parent::getTitle(); 180 + } 181 + 182 + // TODO: These two mail methods can likely be abstracted by introducing a 183 + // formal concept of "inline comment" transactions. 184 + 185 + public function shouldHideForMail(array $xactions) { 186 + $type_inline = PhabricatorAuditActionConstants::INLINE; 187 + switch ($this->getTransactionType()) { 188 + case $type_inline: 189 + foreach ($xactions as $xaction) { 190 + if ($xaction->getTransactionType() != $type_inline) { 191 + return true; 192 + } 193 + } 194 + return ($this !== head($xactions)); 195 + } 196 + 197 + return parent::shouldHideForMail($xactions); 198 + } 199 + 200 + public function getBodyForMail() { 201 + switch ($this->getTransactionType()) { 202 + case PhabricatorAuditActionConstants::INLINE: 203 + return null; 204 + } 205 + 206 + return parent::getBodyForMail(); 158 207 } 159 208 160 209 }