diff --git a/scripts/mail/mail_handler.php b/scripts/mail/mail_handler.php index 19e315364d..11d8f1c29f 100755 --- a/scripts/mail/mail_handler.php +++ b/scripts/mail/mail_handler.php @@ -1,67 +1,96 @@ #!/usr/bin/env php 1) { - $_SERVER['PHABRICATOR_ENV'] = $argv[1]; + foreach (array_slice($argv, 1) as $arg) { + if (!preg_match('/^-/', $arg)) { + $_SERVER['PHABRICATOR_ENV'] = $arg; + break; + } + } } $root = dirname(dirname(dirname(__FILE__))); require_once $root.'/scripts/__init_script__.php'; require_once $root.'/externals/mimemailparser/MimeMailParser.class.php'; +$args = new PhutilArgumentParser($argv); +$args->parseStandardArguments(); +$args->parse( + array( + array( + 'name' => 'process-duplicates', + 'help' => pht( + "Process this message, even if it's a duplicate of another message. ". + "This is mostly useful when debugging issues with mail routing."), + ), + array( + 'name' => 'env', + 'wildcard' => true, + ), + )); + $parser = new MimeMailParser(); $parser->setText(file_get_contents('php://stdin')); $text_body = $parser->getMessageBody('text'); $text_body_headers = $parser->getMessageBodyHeaders('text'); $content_type = idx($text_body_headers, 'content-type'); if ( !phutil_is_utf8($text_body) && (preg_match('/charset="(.*?)"/', $content_type, $matches) || preg_match('/charset=(\S+)/', $content_type, $matches)) ) { $text_body = phutil_utf8_convert($text_body, "UTF-8", $matches[1]); } $headers = $parser->getHeaders(); $headers['subject'] = iconv_mime_decode($headers['subject'], 0, "UTF-8"); $headers['from'] = iconv_mime_decode($headers['from'], 0, "UTF-8"); +if ($args->getArg('process-duplicates')) { + $headers['message-id'] = Filesystem::readRandomCharacters(64); +} + $received = new PhabricatorMetaMTAReceivedMail(); $received->setHeaders($headers); $received->setBodies(array( 'text' => $text_body, 'html' => $parser->getMessageBody('html'), )); $attachments = array(); foreach ($parser->getAttachments() as $attachment) { if (preg_match('@text/(plain|html)@', $attachment->getContentType()) && $attachment->getContentDisposition() == 'inline') { // If this is an "inline" attachment with some sort of text content-type, // do not treat it as a file for attachment. MimeMailParser already picked // it up in the getMessageBody() call above. We still want to treat 'inline' // attachments with other content types (e.g., images) as attachments. continue; } $file = PhabricatorFile::newFromFileData( $attachment->getContent(), array( 'name' => $attachment->getFilename(), )); $attachments[] = $file->getPHID(); } try { $received->setAttachments($attachments); $received->save(); $received->processReceivedMail(); } catch (Exception $e) { $received ->setMessage('EXCEPTION: '.$e->getMessage()) ->save(); + + throw $e; } diff --git a/src/applications/conpherence/mail/ConpherenceCreateThreadMailReceiver.php b/src/applications/conpherence/mail/ConpherenceCreateThreadMailReceiver.php index 6e2fa8cd71..aba7adc756 100644 --- a/src/applications/conpherence/mail/ConpherenceCreateThreadMailReceiver.php +++ b/src/applications/conpherence/mail/ConpherenceCreateThreadMailReceiver.php @@ -1,65 +1,67 @@ getMailUsernames($mail); if (!$usernames) { return false; } $users = $this->loadMailUsers($mail); if (count($users) != count($usernames)) { // At least some of the addresses are not users, so don't accept this as // a new Conpherence thread. return false; } return true; } private function getMailUsernames(PhabricatorMetaMTAReceivedMail $mail) { $usernames = array(); foreach ($mail->getToAddresses() as $to_address) { $address = self::stripMailboxPrefix($to_address); $usernames[] = id(new PhutilEmailAddress($address))->getLocalPart(); } return array_unique($usernames); } private function loadMailUsers(PhabricatorMetaMTAReceivedMail $mail) { $usernames = $this->getMailUsernames($mail); if (!$usernames) { return array(); } return id(new PhabricatorUser())->loadAllWhere( 'username in (%Ls)', $usernames); } protected function processReceivedMail( PhabricatorMetaMTAReceivedMail $mail, PhabricatorUser $sender) { $users = $this->loadMailUsers($mail); $phids = mpull($users, 'getPHID'); $conpherence = id(new ConpherenceReplyHandler()) - ->setMailReceiver(new ConpherenceThread()) + ->setMailReceiver(ConpherenceThread::initializeNewThread($sender)) ->setMailAddedParticipantPHIDs($phids) ->setActor($sender) ->setExcludeMailRecipientPHIDs($mail->loadExcludeMailRecipientPHIDs()) ->processEmail($mail); - $mail->setRelatedPHID($conpherence->getPHID()); + if ($conpherence) { + $mail->setRelatedPHID($conpherence->getPHID()); + } } } diff --git a/src/applications/conpherence/storage/ConpherenceThread.php b/src/applications/conpherence/storage/ConpherenceThread.php index 3e632fec27..0622f9fa9f 100644 --- a/src/applications/conpherence/storage/ConpherenceThread.php +++ b/src/applications/conpherence/storage/ConpherenceThread.php @@ -1,194 +1,200 @@ setMessageCount(0) + ->setTitle(''); + } + public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'recentParticipantPHIDs' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorConpherencePHIDTypeThread::TYPECONST); } public function save() { if (!$this->getMailKey()) { $this->setMailKey(Filesystem::readRandomCharacters(20)); } return parent::save(); } public function attachParticipants(array $participants) { assert_instances_of($participants, 'ConpherenceParticipant'); $this->participants = $participants; return $this; } public function getParticipants() { return $this->assertAttached($this->participants); } public function getParticipant($phid) { $participants = $this->getParticipants(); return $participants[$phid]; } public function getParticipantPHIDs() { $participants = $this->getParticipants(); return array_keys($participants); } public function attachHandles(array $handles) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $this->handles = $handles; return $this; } public function getHandles() { return $this->assertAttached($this->handles); } public function attachTransactions(array $transactions) { assert_instances_of($transactions, 'ConpherenceTransaction'); $this->transactions = $transactions; return $this; } public function getTransactions() { return $this->assertAttached($this->transactions); } public function getTransactionsFrom($begin = 0, $amount = null) { $length = count($this->transactions); return array_slice( $this->getTransactions(), $length - $begin - $amount, $amount); } public function attachFilePHIDs(array $file_phids) { $this->filePHIDs = $file_phids; return $this; } public function getFilePHIDs() { return $this->assertAttached($this->filePHIDs); } public function attachWidgetData(array $widget_data) { $this->widgetData = $widget_data; return $this; } public function getWidgetData() { return $this->assertAttached($this->widgetData); } public function getDisplayData(PhabricatorUser $user) { $recent_phids = $this->getRecentParticipantPHIDs(); $handles = $this->getHandles(); // luck has little to do with it really; most recent participant who isn't // the user.... $lucky_phid = null; $lucky_index = null; foreach ($recent_phids as $index => $phid) { if ($phid == $user->getPHID()) { continue; } $lucky_phid = $phid; break; } reset($recent_phids); if ($lucky_phid) { $lucky_handle = $handles[$lucky_phid]; // this will be just the user talking to themselves. weirdos. } else { $lucky_handle = reset($handles); } $title = $js_title = $this->getTitle(); if (!$title) { $title = $lucky_handle->getName(); $js_title = pht('[No Title]'); } $img_src = $lucky_handle->getImageURI(); $count = 0; $final = false; $subtitle = null; foreach ($recent_phids as $phid) { if ($phid == $user->getPHID()) { continue; } $handle = $handles[$phid]; if ($subtitle) { if ($final) { $subtitle .= '...'; break; } else { $subtitle .= ', '; } } $subtitle .= $handle->getName(); $count++; $final = $count == 3; } $participants = $this->getParticipants(); $user_participation = $participants[$user->getPHID()]; $unread_count = $this->getMessageCount() - $user_participation->getSeenMessageCount(); return array( 'title' => $title, 'js_title' => $js_title, 'subtitle' => $subtitle, 'unread_count' => $unread_count, 'epoch' => $this->getDateModified(), 'image' => $img_src, ); } /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { return PhabricatorPolicies::POLICY_NOONE; } public function hasAutomaticCapability($capability, PhabricatorUser $user) { // this bad boy isn't even created yet so go nuts $user if (!$this->getID()) { return true; } $participants = $this->getParticipants(); return isset($participants[$user->getPHID()]); } public function describeAutomaticCapability($capability) { return pht("Participants in a thread can always view and edit it."); } }