Select the types of activity you want to include in your feed.
@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
···11+<?php
22+33+/*
44+ * Copyright 2011 Facebook, Inc.
55+ *
66+ * Licensed under the Apache License, Version 2.0 (the "License");
77+ * you may not use this file except in compliance with the License.
88+ * You may obtain a copy of the License at
99+ *
1010+ * http://www.apache.org/licenses/LICENSE-2.0
1111+ *
1212+ * Unless required by applicable law or agreed to in writing, software
1313+ * distributed under the License is distributed on an "AS IS" BASIS,
1414+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1515+ * See the License for the specific language governing permissions and
1616+ * limitations under the License.
1717+ */
1818+1919+abstract class DifferentialMail {
2020+2121+ const SUBJECT_PREFIX = '[Differential]';
2222+2323+ protected $to = array();
2424+ protected $cc = array();
2525+2626+ protected $actorName;
2727+ protected $actorID;
2828+2929+ protected $revision;
3030+ protected $feedback;
3131+ protected $changesets;
3232+ protected $inlineComments;
3333+ protected $isFirstMailAboutRevision;
3434+ protected $isFirstMailToRecipients;
3535+ protected $heraldTranscriptURI;
3636+ protected $heraldRulesHeader;
3737+3838+ public function getActorName() {
3939+ return $this->actorName;
4040+ }
4141+4242+ public function setActorName($actor_name) {
4343+ $this->actorName = $actor_name;
4444+ return $this;
4545+ }
4646+4747+ abstract protected function renderSubject();
4848+ abstract protected function renderBody();
4949+5050+ public function setXHeraldRulesHeader($header) {
5151+ $this->heraldRulesHeader = $header;
5252+ return $this;
5353+ }
5454+5555+ public function send() {
5656+ $to_phids = $this->getToPHIDs();
5757+ if (!$to_phids) {
5858+ throw new Exception('No "To:" users provided!');
5959+ }
6060+6161+ $message_id = $this->getMessageID();
6262+6363+ $cc_phids = $this->getCCPHIDs();
6464+ $subject = $this->buildSubject();
6565+ $body = $this->buildBody();
6666+6767+ $mail = new PhabricatorMetaMTAMail();
6868+ if ($this->getActorID()) {
6969+ $mail->setFrom($this->getActorID());
7070+ $mail->setReplyTo($this->getReplyHandlerEmailAddress());
7171+ } else {
7272+ $mail->setFrom($this->getReplyHandlerEmailAddress());
7373+ }
7474+7575+ $mail
7676+ ->addTos($to_phids)
7777+ ->addCCs($cc_phids)
7878+ ->setSubject($subject)
7979+ ->setBody($body)
8080+ ->setIsHTML($this->shouldMarkMailAsHTML())
8181+ ->addHeader('Thread-Topic', $this->getRevision()->getTitle())
8282+ ->addHeader('Thread-Index', $this->generateThreadIndex());
8383+8484+ if ($this->isFirstMailAboutRevision()) {
8585+ $mail->addHeader('Message-ID', $message_id);
8686+ } else {
8787+ $mail->addHeader('In-Reply-To', $message_id);
8888+ $mail->addHeader('References', $message_id);
8989+ }
9090+9191+ if ($this->heraldRulesHeader) {
9292+ $mail->addHeader('X-Herald-Rules', $this->heraldRulesHeader);
9393+ }
9494+9595+ $mail->setRelatedPHID($this->getRevision()->getPHID());
9696+9797+ // Save this to the MetaMTA queue for later delivery to the MTA.
9898+ $mail->save();
9999+ }
100100+101101+ protected function buildSubject() {
102102+ return self::SUBJECT_PREFIX.' '.$this->renderSubject();
103103+ }
104104+105105+ protected function shouldMarkMailAsHTML() {
106106+ return false;
107107+ }
108108+109109+ protected function buildBody() {
110110+111111+ $actions = array();
112112+ $body = $this->renderBody();
113113+ $body .= <<<EOTEXT
114114+115115+ACTIONS
116116+ Reply to comment, or !accept, !reject, !abandon, !resign, or !showdiff.
117117+118118+EOTEXT;
119119+120120+ if ($this->getHeraldTranscriptURI() && $this->isFirstMailToRecipients()) {
121121+ $xscript_uri = $this->getHeraldTranscriptURI();
122122+ $body .= <<<EOTEXT
123123+124124+MANAGE HERALD RULES
125125+ http://todo.com/herald/
126126+127127+WHY DID I GET THIS EMAIL?
128128+ {$xscript_uri}
129129+130130+Tip: use the X-Herald-Rules header to filter Herald messages in your client.
131131+132132+EOTEXT;
133133+ }
134134+135135+ return $body;
136136+ }
137137+138138+ protected function getReplyHandlerEmailAddress() {
139139+ // TODO
140140+ $phid = $this->getRevision()->getPHID();
141141+ $server = 'todo.example.com';
142142+ return "differential+{$phid}@{$server}";
143143+ }
144144+145145+ protected function formatText($text) {
146146+ $text = explode("\n", $text);
147147+ foreach ($text as &$line) {
148148+ $line = rtrim(' '.$line);
149149+ }
150150+ unset($line);
151151+ return implode("\n", $text);
152152+ }
153153+154154+ public function setToPHIDs(array $to) {
155155+ $this->to = $this->filterContactPHIDs($to);
156156+ return $this;
157157+ }
158158+159159+ public function setCCPHIDs(array $cc) {
160160+ $this->cc = $this->filterContactPHIDs($cc);
161161+ return $this;
162162+ }
163163+164164+ protected function filterContactPHIDs(array $phids) {
165165+ return $phids;
166166+167167+ // TODO: actually do this?
168168+169169+ // Differential revisions use Subscriptions for CCs, so any arbitrary
170170+ // PHID can end up CC'd to them. Only try to actually send email PHIDs
171171+ // which have ToolsHandle types that are marked emailable. If we don't
172172+ // filter here, sending the email will fail.
173173+/*
174174+ $handles = array();
175175+ prep(new ToolsHandleData($phids, $handles));
176176+ foreach ($handles as $phid => $handle) {
177177+ if (!$handle->isEmailable()) {
178178+ unset($handles[$phid]);
179179+ }
180180+ }
181181+ return array_keys($handles);
182182+*/
183183+ }
184184+185185+ protected function getToPHIDs() {
186186+ return $this->to;
187187+ }
188188+189189+ protected function getCCPHIDs() {
190190+ return $this->cc;
191191+ }
192192+193193+ public function setActorID($actor_id) {
194194+ $this->actorID = $actor_id;
195195+ return $this;
196196+ }
197197+198198+ public function getActorID() {
199199+ return $this->actorID;
200200+ }
201201+202202+ public function setRevision($revision) {
203203+ $this->revision = $revision;
204204+ return $this;
205205+ }
206206+207207+ public function getRevision() {
208208+ return $this->revision;
209209+ }
210210+211211+ protected function getMessageID() {
212212+ $phid = $this->getRevision()->getPHID();
213213+ // TODO
214214+ return "<differential-rev-{$phid}-req@TODO.com>";
215215+ }
216216+217217+ public function setFeedback($feedback) {
218218+ $this->feedback = $feedback;
219219+ return $this;
220220+ }
221221+222222+ public function getFeedback() {
223223+ return $this->feedback;
224224+ }
225225+226226+ public function setChangesets($changesets) {
227227+ $this->changesets = $changesets;
228228+ return $this;
229229+ }
230230+231231+ public function getChangesets() {
232232+ return $this->changesets;
233233+ }
234234+235235+ public function setInlineComments(array $inline_comments) {
236236+ $this->inlineComments = $inline_comments;
237237+ return $this;
238238+ }
239239+240240+ public function getInlineComments() {
241241+ return $this->inlineComments;
242242+ }
243243+244244+ public function renderRevisionDetailLink() {
245245+ $uri = $this->getRevisionURI();
246246+ return "REVISION DETAIL\n {$uri}";
247247+ }
248248+249249+ public function getRevisionURI() {
250250+ // TODO
251251+ return 'http://local.aphront.com/D'.$this->getRevision()->getID();
252252+ }
253253+254254+ public function setIsFirstMailToRecipients($first) {
255255+ $this->isFirstMailToRecipients = $first;
256256+ return $this;
257257+ }
258258+259259+ public function isFirstMailToRecipients() {
260260+ return $this->isFirstMailToRecipients;
261261+ }
262262+263263+ public function setIsFirstMailAboutRevision($first) {
264264+ $this->isFirstMailAboutRevision = $first;
265265+ return $this;
266266+ }
267267+268268+ public function isFirstMailAboutRevision() {
269269+ return $this->isFirstMailAboutRevision;
270270+ }
271271+272272+ protected function generateThreadIndex() {
273273+ // When threading, Outlook ignores the 'References' and 'In-Reply-To'
274274+ // headers that most clients use. Instead, it uses a custom 'Thread-Index'
275275+ // header. The format of this header is something like this (from
276276+ // camel-exchange-folder.c in Evolution Exchange):
277277+278278+ /* A new post to a folder gets a 27-byte-long thread index. (The value
279279+ * is apparently unique but meaningless.) Each reply to a post gets a
280280+ * 32-byte-long thread index whose first 27 bytes are the same as the
281281+ * parent's thread index. Each reply to any of those gets a
282282+ * 37-byte-long thread index, etc. The Thread-Index header contains a
283283+ * base64 representation of this value.
284284+ */
285285+286286+ // The specific implementation uses a 27-byte header for the first email
287287+ // a recipient receives, and a random 5-byte suffix (32 bytes total)
288288+ // thereafter. This means that all the replies are (incorrectly) siblings,
289289+ // but it would be very difficult to keep track of the entire tree and this
290290+ // gets us reasonable client behavior.
291291+292292+ $base = substr(md5($this->getRevision()->getPHID()), 0, 27);
293293+ if (!$this->isFirstMailAboutRevision()) {
294294+ // not totally sure, but it seems like outlook orders replies by
295295+ // thread-index rather than timestamp, so to get these to show up in the
296296+ // right order we use the time as the last 4 bytes.
297297+ $base .= ' ' . pack("N", time());
298298+ }
299299+ return base64_encode($base);
300300+ }
301301+302302+ public function setHeraldTranscriptURI($herald_transcript_uri) {
303303+ $this->heraldTranscriptURI = $herald_transcript_uri;
304304+ return $this;
305305+ }
306306+307307+ public function getHeraldTranscriptURI() {
308308+ return $this->heraldTranscriptURI;
309309+ }
310310+311311+}
···11+<?php
22+/**
33+ * This file is automatically generated. Lint this module to rebuild it.
44+ * @generated
55+ */
66+77+88+99+phutil_require_module('phabricator', 'applications/metamta/storage/mail');
1010+1111+1212+phutil_require_source('DifferentialMail.php');
···11+<?php
22+33+/*
44+ * Copyright 2011 Facebook, Inc.
55+ *
66+ * Licensed under the Apache License, Version 2.0 (the "License");
77+ * you may not use this file except in compliance with the License.
88+ * You may obtain a copy of the License at
99+ *
1010+ * http://www.apache.org/licenses/LICENSE-2.0
1111+ *
1212+ * Unless required by applicable law or agreed to in writing, software
1313+ * distributed under the License is distributed on an "AS IS" BASIS,
1414+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1515+ * See the License for the specific language governing permissions and
1616+ * limitations under the License.
1717+ */
1818+1919+class DifferentialCCWelcomeMail extends DifferentialReviewRequestMail {
2020+2121+ protected function renderSubject() {
2222+ $revision = $this->getRevision();
2323+ return 'Added to CC: '.$revision->getName();
2424+ }
2525+2626+ protected function renderBody() {
2727+2828+ $actor = $this->getActorName();
2929+ $name = $this->getRevision()->getName();
3030+ $body = array();
3131+3232+ $body[] = "{$actor} added you to the CC list for the revision \"{$name}\".";
3333+ $body[] = null;
3434+3535+ $body[] = $this->renderReviewRequestBody();
3636+3737+ return implode("\n", $body);
3838+ }
3939+}
···11+<?php
22+/**
33+ * This file is automatically generated. Lint this module to rebuild it.
44+ * @generated
55+ */
66+77+88+99+phutil_require_module('phabricator', 'applications/differential/mail/reviewrequest');
1010+1111+1212+phutil_require_source('DifferentialCCWelcomeMail.php');
···11+<?php
22+33+/*
44+ * Copyright 2011 Facebook, Inc.
55+ *
66+ * Licensed under the Apache License, Version 2.0 (the "License");
77+ * you may not use this file except in compliance with the License.
88+ * You may obtain a copy of the License at
99+ *
1010+ * http://www.apache.org/licenses/LICENSE-2.0
1111+ *
1212+ * Unless required by applicable law or agreed to in writing, software
1313+ * distributed under the License is distributed on an "AS IS" BASIS,
1414+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1515+ * See the License for the specific language governing permissions and
1616+ * limitations under the License.
1717+ */
1818+1919+class DifferentialDiffContentMail extends DifferentialMail {
2020+2121+ protected $content;
2222+2323+ public function __construct(DifferentialRevision $revision, $content) {
2424+ $this->setRevision($revision);
2525+ $this->content = $content;
2626+ }
2727+2828+ protected function renderSubject() {
2929+ return "Content: ".$this->getRevision()->getName();
3030+ }
3131+3232+ protected function renderBody() {
3333+ return $this->content;
3434+ }
3535+}
···11+<?php
22+/**
33+ * This file is automatically generated. Lint this module to rebuild it.
44+ * @generated
55+ */
66+77+88+99+phutil_require_module('phabricator', 'applications/differential/mail/base');
1010+1111+1212+phutil_require_source('DifferentialDiffContentMail.php');
···11+<?php
22+33+/*
44+ * Copyright 2011 Facebook, Inc.
55+ *
66+ * Licensed under the Apache License, Version 2.0 (the "License");
77+ * you may not use this file except in compliance with the License.
88+ * You may obtain a copy of the License at
99+ *
1010+ * http://www.apache.org/licenses/LICENSE-2.0
1111+ *
1212+ * Unless required by applicable law or agreed to in writing, software
1313+ * distributed under the License is distributed on an "AS IS" BASIS,
1414+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1515+ * See the License for the specific language governing permissions and
1616+ * limitations under the License.
1717+ */
1818+1919+class DifferentialNewDiffMail extends DifferentialReviewRequestMail {
2020+2121+ protected function renderSubject() {
2222+ $revision = $this->getRevision();
2323+ $line_count = $revision->getLineCount();
2424+ $lines = ($line_count == 1 ? "1 line" : "{$line_count} lines");
2525+2626+ if ($this->isFirstMailToRecipients()) {
2727+ $verb = 'Request';
2828+ } else {
2929+ $verb = 'Updated';
3030+ }
3131+3232+ return "{$verb} ({$lines}): ".$revision->getTitle();
3333+ }
3434+3535+ protected function buildSubject() {
3636+ if (!$this->isFirstMailToRecipients()) {
3737+ return parent::buildSubject();
3838+ }
3939+4040+ $prefix = self::SUBJECT_PREFIX;
4141+4242+ $subject = $this->renderSubject();
4343+4444+ return "{$prefix} {$subject}";
4545+ }
4646+4747+ protected function renderBody() {
4848+ $actor = $this->getActorName();
4949+5050+ $name = $this->getRevision()->getTitle();
5151+5252+ $body = array();
5353+5454+ if ($this->isFirstMailToRecipients()) {
5555+ $body[] = "{$actor} requested code review of \"{$name}\".";
5656+ } else {
5757+ $body[] = "{$actor} updated the revision \"{$name}\".";
5858+ }
5959+ $body[] = null;
6060+6161+ $body[] = $this->renderReviewRequestBody();
6262+6363+ return implode("\n", $body);
6464+ }
6565+}
···11+<?php
22+/**
33+ * This file is automatically generated. Lint this module to rebuild it.
44+ * @generated
55+ */
66+77+88+99+phutil_require_module('phabricator', 'applications/differential/mail/reviewrequest');
1010+1111+1212+phutil_require_source('DifferentialNewDiffMail.php');
···11+<?php
22+33+/*
44+ * Copyright 2011 Facebook, Inc.
55+ *
66+ * Licensed under the Apache License, Version 2.0 (the "License");
77+ * you may not use this file except in compliance with the License.
88+ * You may obtain a copy of the License at
99+ *
1010+ * http://www.apache.org/licenses/LICENSE-2.0
1111+ *
1212+ * Unless required by applicable law or agreed to in writing, software
1313+ * distributed under the License is distributed on an "AS IS" BASIS,
1414+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1515+ * See the License for the specific language governing permissions and
1616+ * limitations under the License.
1717+ */
1818+1919+abstract class DifferentialReviewRequestMail extends DifferentialMail {
2020+2121+ protected $comments;
2222+2323+ public function setComments($comments) {
2424+ $this->comments = $comments;
2525+ return $this;
2626+ }
2727+2828+ public function getComments() {
2929+ return $this->comments;
3030+ }
3131+3232+ public function __construct(
3333+ DifferentialRevision $revision,
3434+ $actor_id,
3535+ array $changesets) {
3636+3737+ $this->setRevision($revision);
3838+ $this->setActorID($actor_id);
3939+ $this->setChangesets($changesets);
4040+ }
4141+4242+ protected function renderReviewRequestBody() {
4343+ $revision = $this->getRevision();
4444+4545+ $body = array();
4646+ if ($this->isFirstMailToRecipients()) {
4747+ $body[] = $this->formatText($revision->getSummary());
4848+ $body[] = null;
4949+5050+ $body[] = 'TEST PLAN';
5151+ $body[] = $this->formatText($revision->getTestPlan());
5252+ $body[] = null;
5353+ } else {
5454+ if (strlen($this->getComments())) {
5555+ $body[] = $this->formatText($this->getComments());
5656+ $body[] = null;
5757+ }
5858+ }
5959+6060+ $body[] = $this->renderRevisionDetailLink();
6161+ $body[] = null;
6262+6363+ $changesets = $this->getChangesets();
6464+ if ($changesets) {
6565+ $body[] = 'AFFECTED FILES';
6666+ foreach ($changesets as $changeset) {
6767+ $body[] = ' '.$changeset->getFilename();
6868+ }
6969+ $body[] = null;
7070+ }
7171+7272+ return implode("\n", $body);
7373+ }
7474+}
···11+<?php
22+/**
33+ * This file is automatically generated. Lint this module to rebuild it.
44+ * @generated
55+ */
66+77+88+99+phutil_require_module('phabricator', 'applications/differential/mail/base');
1010+1111+1212+phutil_require_source('DifferentialReviewRequestMail.php');