@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

Introduce basic `bin/mail` with a `resend` workflow

Summary:
Fixes T2458. Ref T2843. @tido's email from T2843 has exhausted its retries and failed, but we want to try it again with the patch from D5464 to capture the actual error. This sort of thing has come up a few times in debugging, too.

Also fixed some stuff that came up while debugging this.

Test Plan:
- Ran command with no args.
- Ran resend with no args.
- Ran resend with bad IDs.
- Ran resend with already-queued messages, got "already queued" error.
- Ran resend with already-sent message, got requeue.

Reviewers: btrahan, tido

Reviewed By: tido

CC: aran

Maniphest Tasks: T2458, T2843

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

+140 -34
+1
bin/mail
··· 1 + ../scripts/mail/manage_mail.php
-29
scripts/mail/create_worker_tasks.php
··· 1 - #!/usr/bin/env php 2 - <?php 3 - 4 - /* 5 - * After upgrading to/past D1723, the handling of messages queued for delivery 6 - * is a bit different. Running this script will take any messages that are 7 - * queued for delivery, but don't have a worker task created, and create that 8 - * worker task. Without the worker task, the message will just sit at "queued 9 - * for delivery" and nothing will happen to it. 10 - */ 11 - 12 - $root = dirname(dirname(dirname(__FILE__))); 13 - require_once $root.'/scripts/__init_script__.php'; 14 - 15 - $messages = id(new PhabricatorMetaMTAMail())->loadAllWhere( 16 - 'status = %s', PhabricatorMetaMTAMail::STATUS_QUEUE); 17 - 18 - foreach ($messages as $message) { 19 - if (!$message->getWorkerTaskID()) { 20 - $mailer_task = PhabricatorWorker::scheduleTask( 21 - 'PhabricatorMetaMTAWorker', 22 - $message->getID()); 23 - 24 - $message->setWorkerTaskID($mailer_task->getID()); 25 - $message->save(); 26 - $id = $message->getID(); 27 - echo "#$id\n"; 28 - } 29 - }
+22
scripts/mail/manage_mail.php
··· 1 + #!/usr/bin/env php 2 + <?php 3 + 4 + $root = dirname(dirname(dirname(__FILE__))); 5 + require_once $root.'/scripts/__init_script__.php'; 6 + 7 + $args = new PhutilArgumentParser($argv); 8 + $args->setTagline('manage mail'); 9 + $args->setSynopsis(<<<EOSYNOPSIS 10 + **mail** __command__ [__options__] 11 + Manage Phabricator mail stuff. 12 + 13 + EOSYNOPSIS 14 + ); 15 + $args->parseStandardArguments(); 16 + 17 + $workflows = array( 18 + new PhabricatorMailManagementResendWorkflow(), 19 + new PhutilHelpArgumentWorkflow(), 20 + ); 21 + 22 + $args->parseWorkflows($workflows);
+4
src/__phutil_library_map__.php
··· 1048 1048 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerLiteAdapter.php', 1049 1049 'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php', 1050 1050 'PhabricatorMailImplementationTestAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationTestAdapter.php', 1051 + 'PhabricatorMailManagementResendWorkflow' => 'applications/metamta/management/PhabricatorMailManagementResendWorkflow.php', 1052 + 'PhabricatorMailManagementWorkflow' => 'applications/metamta/management/PhabricatorMailManagementWorkflow.php', 1051 1053 'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php', 1052 1054 'PhabricatorMailingListsController' => 'applications/mailinglists/controller/PhabricatorMailingListsController.php', 1053 1055 'PhabricatorMailingListsEditController' => 'applications/mailinglists/controller/PhabricatorMailingListsEditController.php', ··· 2714 2716 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', 2715 2717 'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter', 2716 2718 'PhabricatorMailImplementationTestAdapter' => 'PhabricatorMailImplementationAdapter', 2719 + 'PhabricatorMailManagementResendWorkflow' => 'PhabricatorSearchManagementWorkflow', 2720 + 'PhabricatorMailManagementWorkflow' => 'PhutilArgumentWorkflow', 2717 2721 'PhabricatorMailingListsController' => 'PhabricatorController', 2718 2722 'PhabricatorMailingListsEditController' => 'PhabricatorMailingListsController', 2719 2723 'PhabricatorMailingListsListController' => 'PhabricatorMailingListsController',
+75
src/applications/metamta/management/PhabricatorMailManagementResendWorkflow.php
··· 1 + <?php 2 + 3 + final class PhabricatorMailManagementResendWorkflow 4 + extends PhabricatorSearchManagementWorkflow { 5 + 6 + protected function didConstruct() { 7 + $this 8 + ->setName('resend') 9 + ->setSynopsis('Send mail again.') 10 + ->setExamples( 11 + "**resend** --id 1 --id 2") 12 + ->setArguments( 13 + array( 14 + array( 15 + 'name' => 'id', 16 + 'param' => 'id', 17 + 'help' => 'Send mail with a given ID again.', 18 + 'repeat' => true, 19 + ), 20 + )); 21 + } 22 + 23 + public function execute(PhutilArgumentParser $args) { 24 + $console = PhutilConsole::getConsole(); 25 + 26 + $ids = $args->getArg('id'); 27 + if (!$ids) { 28 + throw new PhutilArgumentUsageException( 29 + "Use the '--id' flag to specify one or more messages to resend."); 30 + } 31 + 32 + $messages = id(new PhabricatorMetaMTAMail())->loadAllWhere( 33 + 'id IN (%Ld)', 34 + $ids); 35 + 36 + if ($ids) { 37 + $ids = array_fuse($ids); 38 + $missing = array_diff_key($ids, $messages); 39 + if ($missing) { 40 + throw new PhutilArgumentUsageException( 41 + "Some specified messages do not exist: ". 42 + implode(', ', array_keys($missing))); 43 + } 44 + } 45 + 46 + foreach ($messages as $message) { 47 + if ($message->getStatus() == PhabricatorMetaMTAMail::STATUS_QUEUE) { 48 + if ($message->getWorkerTaskID()) { 49 + $console->writeOut( 50 + "Message #%d is already queued with an assigned send task.\n", 51 + $message->getID()); 52 + continue; 53 + } 54 + } 55 + 56 + $message->setStatus(PhabricatorMetaMTAMail::STATUS_QUEUE); 57 + $message->setRetryCount(0); 58 + $message->setNextRetry(time()); 59 + 60 + $message->save(); 61 + 62 + $mailer_task = PhabricatorWorker::scheduleTask( 63 + 'PhabricatorMetaMTAWorker', 64 + $message->getID()); 65 + 66 + $message->setWorkerTaskID($mailer_task->getID()); 67 + $message->save(); 68 + 69 + $console->writeOut( 70 + "Queued message #%d for resend.\n", 71 + $message->getID()); 72 + } 73 + } 74 + 75 + }
+10
src/applications/metamta/management/PhabricatorMailManagementWorkflow.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorMailManagementWorkflow 4 + extends PhutilArgumentWorkflow { 5 + 6 + final public function isExecutable() { 7 + return true; 8 + } 9 + 10 + }
+16
src/applications/metamta/storage/PhabricatorMetaMTAAttachment.php
··· 37 37 $this->mimetype = $mimetype; 38 38 return $this; 39 39 } 40 + 41 + public function toDictionary() { 42 + return array( 43 + 'filename' => $this->getFilename(), 44 + 'mimetype' => $this->getMimetype(), 45 + 'data' => $this->getData(), 46 + ); 47 + } 48 + 49 + public static function newFromDictionary(array $dict) { 50 + return new PhabricatorMetaMTAAttachment( 51 + idx($dict, 'data'), 52 + idx($dict, 'filename'), 53 + idx($dict, 'mimetype')); 54 + } 55 + 40 56 }
+12 -5
src/applications/metamta/storage/PhabricatorMetaMTAMail.php
··· 165 165 } 166 166 167 167 public function addAttachment(PhabricatorMetaMTAAttachment $attachment) { 168 - $this->parameters['attachments'][] = $attachment; 168 + $this->parameters['attachments'][] = $attachment->toDictionary(); 169 169 return $this; 170 170 } 171 171 172 172 public function getAttachments() { 173 - return $this->getParam('attachments'); 173 + $dicts = $this->getParam('attachments'); 174 + 175 + $result = array(); 176 + foreach ($dicts as $dict) { 177 + $result[] = PhabricatorMetaMTAAttachment::newFromDictionary($dict); 178 + } 179 + return $result; 174 180 } 175 181 176 182 public function setAttachments(array $attachments) { 177 183 assert_instances_of($attachments, 'PhabricatorMetaMTAAttachment'); 178 - $this->setParam('attachments', $attachments); 184 + $this->setParam('attachments', mpull($attachments, 'toDictionary')); 179 185 return $this; 180 186 } 181 187 ··· 430 436 } 431 437 break; 432 438 case 'attachments': 439 + $value = $this->getAttachments(); 433 440 foreach ($value as $attachment) { 434 441 $mailer->addAttachment( 435 442 $attachment->getData(), ··· 715 722 $is_mailable = false; 716 723 switch ($value) { 717 724 case PhabricatorPHIDConstants::PHID_TYPE_USER: 718 - $user = $users[$phid]; 725 + $user = idx($users, $phid); 719 726 if ($user) { 720 727 $name = $this->getUserName($user); 721 728 $is_mailable = !$user->getIsDisabled() 722 729 && !$user->getIsSystemAgent(); 723 730 } 724 - $email = $user_emails[$phid] ? 731 + $email = isset($user_emails[$phid]) ? 725 732 $user_emails[$phid]->getAddress() : 726 733 $default; 727 734 break;