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

HTML emails

Summary:
Added support for side-by-side HTML and plaintext email building.

We can control if the HTML stuff is sent by by a new config, metamta.html-emails

Test Plan:
Been running this in our deployment for a few months now.

====Well behaved clients====
- Gmail
- Mail.app

====Bad clients====

- [[ http://airmailapp.com/ | Airmail ]]. They confuse Gmail too, though.

====Need testing====
- Outlook (Windows + Mac)

Reviewers: chad, #blessed_reviewers, epriestley

Reviewed By: #blessed_reviewers, epriestley

Subscribers: webframp, taoqiping, chad, epriestley, Korvin

Maniphest Tasks: T992

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

authored by

Tal Shiri and committed by
epriestley
4c57e6d3 dc69c4e5

+264 -95
+1
src/__phutil_library_map__.php
··· 1728 1728 'PhabricatorMetaMTAMail' => 'applications/metamta/storage/PhabricatorMetaMTAMail.php', 1729 1729 'PhabricatorMetaMTAMailBody' => 'applications/metamta/view/PhabricatorMetaMTAMailBody.php', 1730 1730 'PhabricatorMetaMTAMailBodyTestCase' => 'applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php', 1731 + 'PhabricatorMetaMTAMailSection' => 'applications/metamta/view/PhabricatorMetaMTAMailSection.php', 1731 1732 'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php', 1732 1733 'PhabricatorMetaMTAMailableDatasource' => 'applications/metamta/typeahead/PhabricatorMetaMTAMailableDatasource.php', 1733 1734 'PhabricatorMetaMTAMailgunReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php',
+1 -1
src/applications/config/option/PhabricatorMetaMTAConfigOptions.php
··· 331 331 'in bytes.')) 332 332 ->setSummary(pht('Global cap for size of generated emails (bytes).')) 333 333 ->addExample(524288, pht('Truncate at 512KB')) 334 - ->addExample(1048576, pht('Truncate at 1MB')) 334 + ->addExample(1048576, pht('Truncate at 1MB')), 335 335 ); 336 336 } 337 337
+28 -14
src/applications/differential/editor/DifferentialTransactionEditor.php
··· 1179 1179 $config_attach = PhabricatorEnv::getEnvConfig($config_key_attach); 1180 1180 1181 1181 if ($config_inline || $config_attach) { 1182 - $patch = $this->renderPatchForMail($diff); 1183 - $lines = count(phutil_split_lines($patch)); 1182 + $patch_section = $this->renderPatchForMail($diff); 1183 + $lines = count(phutil_split_lines($patch_section->getPlaintext())); 1184 1184 1185 1185 if ($config_inline && ($lines <= $config_inline)) { 1186 1186 $body->addTextSection( 1187 1187 pht('CHANGE DETAILS'), 1188 - $patch); 1188 + $patch_section); 1189 1189 } 1190 1190 1191 1191 if ($config_attach) { 1192 1192 $name = pht('D%s.%s.patch', $object->getID(), $diff->getID()); 1193 1193 $mime_type = 'text/x-patch; charset=utf-8'; 1194 1194 $body->addAttachment( 1195 - new PhabricatorMetaMTAAttachment($patch, $name, $mime_type)); 1195 + new PhabricatorMetaMTAAttachment( 1196 + $patch_section->getPlaintext(), $name, $mime_type)); 1196 1197 } 1197 1198 } 1198 1199 } ··· 1330 1331 $hunk_parser = new DifferentialHunkParser(); 1331 1332 } 1332 1333 1333 - $result = array(); 1334 + $section = new PhabricatorMetaMTAMailSection(); 1334 1335 foreach ($inline_groups as $changeset_id => $group) { 1335 1336 $changeset = idx($changesets, $changeset_id); 1336 1337 if (!$changeset) { ··· 1351 1352 $inline_content = $comment->getContent(); 1352 1353 1353 1354 if (!$show_context) { 1354 - $result[] = "{$file}:{$range} {$inline_content}"; 1355 + $section->addFragment("{$file}:{$range} {$inline_content}"); 1355 1356 } else { 1356 - $result[] = '================'; 1357 - $result[] = 'Comment at: '.$file.':'.$range; 1358 - $result[] = $hunk_parser->makeContextDiff( 1357 + $patch = $hunk_parser->makeContextDiff( 1359 1358 $changeset->getHunks(), 1360 1359 $comment->getIsNewFile(), 1361 1360 $comment->getLineNumber(), 1362 1361 $comment->getLineLength(), 1363 1362 1); 1364 - $result[] = '----------------'; 1365 1363 1366 - $result[] = $inline_content; 1367 - $result[] = null; 1364 + $section->addFragment('================') 1365 + ->addFragment('Comment at: '.$file.':'.$range) 1366 + ->addPlaintextFragment($patch) 1367 + ->addHTMLFragment($this->renderPatchHTMLForMail($patch)) 1368 + ->addFragment('----------------') 1369 + ->addFragment($inline_content) 1370 + ->addFragment(null); 1368 1371 } 1369 1372 } 1370 1373 } 1371 1374 1372 - return implode("\n", $result); 1375 + return $section; 1373 1376 } 1374 1377 1375 1378 private function loadDiff($phid, $need_changesets = false) { ··· 1762 1765 return implode("\n", $filenames); 1763 1766 } 1764 1767 1768 + private function renderPatchHTMLForMail($patch) { 1769 + return phutil_tag('pre', 1770 + array('style' => 'font-family: monospace;'), $patch); 1771 + } 1772 + 1765 1773 private function renderPatchForMail(DifferentialDiff $diff) { 1766 1774 $format = PhabricatorEnv::getEnvConfig('metamta.differential.patch-format'); 1767 1775 1768 - return id(new DifferentialRawDiffRenderer()) 1776 + $patch = id(new DifferentialRawDiffRenderer()) 1769 1777 ->setViewer($this->getActor()) 1770 1778 ->setFormat($format) 1771 1779 ->setChangesets($diff->getChangesets()) 1772 1780 ->buildPatch(); 1781 + 1782 + $section = new PhabricatorMetaMTAMailSection(); 1783 + $section->addHTMLFragment($this->renderPatchHTMLForMail($patch)); 1784 + $section->addPlaintextFragment($patch); 1785 + 1786 + return $section; 1773 1787 } 1774 1788 1775 1789 }
+2 -2
src/applications/metamta/adapter/PhabricatorMailImplementationAdapter.php
··· 8 8 abstract public function addCCs(array $emails); 9 9 abstract public function addAttachment($data, $filename, $mimetype); 10 10 abstract public function addHeader($header_name, $header_value); 11 - abstract public function setBody($body); 11 + abstract public function setBody($plaintext_body); 12 + abstract public function setHTMLBody($html_body); 12 13 abstract public function setSubject($subject); 13 - abstract public function setIsHTML($is_html); 14 14 15 15 /** 16 16 * Some mailers, notably Amazon SES, do not support us setting a specific
+7 -8
src/applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php
··· 57 57 return $this; 58 58 } 59 59 60 - public function setSubject($subject) { 61 - $this->params['subject'] = $subject; 60 + public function setHTMLBody($html_body) { 61 + $this->params['html-body'] = $html_body; 62 62 return $this; 63 63 } 64 64 65 - public function setIsHTML($is_html) { 66 - $this->params['is-html'] = $is_html; 65 + public function setSubject($subject) { 66 + $this->params['subject'] = $subject; 67 67 return $this; 68 68 } 69 69 ··· 78 78 79 79 $params['to'] = implode(', ', idx($this->params, 'tos', array())); 80 80 $params['subject'] = idx($this->params, 'subject'); 81 + $params['text'] = idx($this->params, 'body'); 81 82 82 - if (idx($this->params, 'is-html')) { 83 - $params['html'] = idx($this->params, 'body'); 84 - } else { 85 - $params['text'] = idx($this->params, 'body'); 83 + if (idx($this->params, 'html-body')) { 84 + $params['html'] = idx($this->params, 'html-body'); 86 85 } 87 86 88 87 $from = idx($this->params, 'from');
+6 -4
src/applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php
··· 91 91 } 92 92 93 93 public function setBody($body) { 94 + $this->mailer->IsHTML(false); 94 95 $this->mailer->Body = $body; 95 96 return $this; 96 97 } 97 98 98 - public function setSubject($subject) { 99 - $this->mailer->Subject = $subject; 99 + public function setHTMLBody($html_body) { 100 + $this->mailer->IsHTML(true); 101 + $this->mailer->Body = $html_body; 100 102 return $this; 101 103 } 102 104 103 - public function setIsHTML($is_html) { 104 - $this->mailer->IsHTML($is_html); 105 + public function setSubject($subject) { 106 + $this->mailer->Subject = $subject; 105 107 return $this; 106 108 } 107 109
+12 -4
src/applications/metamta/adapter/PhabricatorMailImplementationPHPMailerLiteAdapter.php
··· 70 70 71 71 public function setBody($body) { 72 72 $this->mailer->Body = $body; 73 + $this->mailer->IsHTML(false); 73 74 return $this; 74 75 } 75 76 76 - public function setSubject($subject) { 77 - $this->mailer->Subject = $subject; 77 + 78 + /** 79 + * Note: phpmailer-lite does NOT support sending messages with mixed version 80 + * (plaintext and html). So for now lets just use HTML if it's available. 81 + * @param $html 82 + */ 83 + public function setHTMLBody($html_body) { 84 + $this->mailer->Body = $html_body; 85 + $this->mailer->IsHTML(true); 78 86 return $this; 79 87 } 80 88 81 - public function setIsHTML($is_html) { 82 - $this->mailer->IsHTML($is_html); 89 + public function setSubject($subject) { 90 + $this->mailer->Subject = $subject; 83 91 return $this; 84 92 } 85 93
+9 -8
src/applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php
··· 56 56 return $this; 57 57 } 58 58 59 - public function setSubject($subject) { 60 - $this->params['subject'] = $subject; 59 + public function setHTMLBody($body) { 60 + $this->params['html-body'] = $body; 61 61 return $this; 62 62 } 63 63 64 - public function setIsHTML($is_html) { 65 - $this->params['is-html'] = $is_html; 64 + 65 + public function setSubject($subject) { 66 + $this->params['subject'] = $subject; 66 67 return $this; 67 68 } 68 69 ··· 89 90 } 90 91 91 92 $params['subject'] = idx($this->params, 'subject'); 92 - if (idx($this->params, 'is-html')) { 93 - $params['html'] = idx($this->params, 'body'); 94 - } else { 95 - $params['text'] = idx($this->params, 'body'); 93 + $params['text'] = idx($this->params, 'body'); 94 + 95 + if (idx($this->params, 'html-body')) { 96 + $params['html'] = idx($this->params, 'html-body'); 96 97 } 97 98 98 99 $params['from'] = idx($this->params, 'from');
+4 -4
src/applications/metamta/adapter/PhabricatorMailImplementationTestAdapter.php
··· 64 64 return $this; 65 65 } 66 66 67 - public function setSubject($subject) { 68 - $this->guts['subject'] = $subject; 67 + public function setHTMLBody($html_body) { 68 + $this->guts['html-body'] = $html_body; 69 69 return $this; 70 70 } 71 71 72 - public function setIsHTML($is_html) { 73 - $this->guts['is-html'] = $is_html; 72 + public function setSubject($subject) { 73 + $this->guts['subject'] = $subject; 74 74 return $this; 75 75 } 76 76
+10 -2
src/applications/metamta/management/PhabricatorMailManagementSendTestWorkflow.php
··· 105 105 $subject = $args->getArg('subject'); 106 106 $tags = $args->getArg('tag'); 107 107 $attach = $args->getArg('attach'); 108 - $is_html = $args->getArg('html'); 109 108 $is_bulk = $args->getArg('bulk'); 110 109 111 110 $console->writeErr("%s\n", pht('Reading message body from stdin...')); ··· 117 116 ->addCCs($ccs) 118 117 ->setSubject($subject) 119 118 ->setBody($body) 120 - ->setIsHTML($is_html) 121 119 ->setIsBulk($is_bulk) 122 120 ->setMailTags($tags); 121 + 122 + if ($args->getArg('html')) { 123 + $mail->setBody( 124 + pht('(This is a placeholder plaintext email body for a test message '. 125 + 'sent with --html.)')); 126 + 127 + $mail->setHTMLBody($body); 128 + } else { 129 + $mail->setBody($body); 130 + } 123 131 124 132 if ($from) { 125 133 $mail->setFrom($from->getPHID());
+51 -45
src/applications/metamta/storage/PhabricatorMetaMTAMail.php
··· 212 212 return $this; 213 213 } 214 214 215 - public function getBody() { 216 - return $this->getParam('body'); 215 + public function setHTMLBody($html) { 216 + $this->setParam('html-body', $html); 217 + return $this; 217 218 } 218 219 219 - public function setIsHTML($html) { 220 - $this->setParam('is-html', $html); 221 - return $this; 220 + public function getBody() { 221 + return $this->getParam('body'); 222 222 } 223 223 224 224 public function setIsErrorEmail($is_error) { ··· 377 377 $add_cc = array(); 378 378 $add_to = array(); 379 379 380 + // Only try to use preferences if everything is multiplexed, so we 381 + // get consistent behavior. 382 + $use_prefs = self::shouldMultiplexAllMail(); 383 + 384 + $prefs = null; 385 + if ($use_prefs) { 386 + 387 + // If multiplexing is enabled, some recipients will be in "Cc" 388 + // rather than "To". We'll move them to "To" later (or supply a 389 + // dummy "To") but need to look for the recipient in either the 390 + // "To" or "Cc" fields here. 391 + $target_phid = head(idx($params, 'to', array())); 392 + if (!$target_phid) { 393 + $target_phid = head(idx($params, 'cc', array())); 394 + } 395 + 396 + if ($target_phid) { 397 + $user = id(new PhabricatorUser())->loadOneWhere( 398 + 'phid = %s', 399 + $target_phid); 400 + if ($user) { 401 + $prefs = $user->loadPreferences(); 402 + } 403 + } 404 + } 405 + 380 406 foreach ($params as $key => $value) { 381 407 switch ($key) { 382 408 case 'from': ··· 444 470 $attachment->getMimeType()); 445 471 } 446 472 break; 447 - case 'body': 448 - $max = PhabricatorEnv::getEnvConfig('metamta.email-body-limit'); 449 - if (strlen($value) > $max) { 450 - $value = phutil_utf8_shorten($value, $max); 451 - $value .= "\n"; 452 - $value .= pht('(This email was truncated at %d bytes.)', $max); 453 - } 454 - $mailer->setBody($value); 455 - break; 456 473 case 'subject': 457 - // Only try to use preferences if everything is multiplexed, so we 458 - // get consistent behavior. 459 - $use_prefs = self::shouldMultiplexAllMail(); 460 - 461 - $prefs = null; 462 - if ($use_prefs) { 463 - 464 - // If multiplexing is enabled, some recipients will be in "Cc" 465 - // rather than "To". We'll move them to "To" later (or supply a 466 - // dummy "To") but need to look for the recipient in either the 467 - // "To" or "Cc" fields here. 468 - $target_phid = head(idx($params, 'to', array())); 469 - if (!$target_phid) { 470 - $target_phid = head(idx($params, 'cc', array())); 471 - } 472 - 473 - if ($target_phid) { 474 - $user = id(new PhabricatorUser())->loadOneWhere( 475 - 'phid = %s', 476 - $target_phid); 477 - if ($user) { 478 - $prefs = $user->loadPreferences(); 479 - } 480 - } 481 - } 482 - 483 474 $subject = array(); 484 475 485 476 if ($is_threaded) { ··· 518 509 519 510 $mailer->setSubject(implode(' ', array_filter($subject))); 520 511 break; 521 - case 'is-html': 522 - if ($value) { 523 - $mailer->setIsHTML(true); 524 - } 525 - break; 526 512 case 'is-bulk': 527 513 if ($value) { 528 514 if (PhabricatorEnv::getEnvConfig('metamta.precedence-bulk')) { ··· 568 554 default: 569 555 // Just discard. 570 556 } 557 + } 558 + 559 + $body = idx($params, 'body', ''); 560 + $max = PhabricatorEnv::getEnvConfig('metamta.email-body-limit'); 561 + if (strlen($body) > $max) { 562 + $body = phutil_utf8_shorten($body, $max); 563 + $body .= "\n"; 564 + $body .= pht('(This email was truncated at %d bytes.)', $max); 565 + } 566 + $mailer->setBody($body); 567 + 568 + $html_emails = false; 569 + if ($use_prefs && $prefs) { 570 + $html_emails = $prefs->getPreference( 571 + PhabricatorUserPreferences::PREFERENCE_HTML_EMAILS, 572 + $html_emails); 573 + } 574 + 575 + if ($html_emails && isset($params['html-body'])) { 576 + $mailer->setHTMLBody($params['html-body']); 571 577 } 572 578 573 579 if (!$add_to && !$add_cc) {
+45 -2
src/applications/metamta/view/PhabricatorMetaMTAMailBody.php
··· 9 9 final class PhabricatorMetaMTAMailBody { 10 10 11 11 private $sections = array(); 12 + private $htmlSections = array(); 12 13 private $attachments = array(); 13 14 14 15 ··· 24 25 */ 25 26 public function addRawSection($text) { 26 27 if (strlen($text)) { 27 - $this->sections[] = rtrim($text); 28 + $text = rtrim($text); 29 + $this->sections[] = $text; 30 + $this->htmlSections[] = phutil_escape_html_newlines( 31 + phutil_tag('div', array(), $text)); 28 32 } 29 33 return $this; 30 34 } 31 35 36 + public function addRawPlaintextSection($text) { 37 + if (strlen($text)) { 38 + $text = rtrim($text); 39 + $this->sections[] = $text; 40 + } 41 + return $this; 42 + } 43 + 44 + public function addRawHTMLSection($html) { 45 + $this->htmlSections[] = phutil_safe_html($html); 46 + return $this; 47 + } 48 + 32 49 33 50 /** 34 51 * Add a block of text with a section header. This is rendered like this: ··· 41 58 * @return this 42 59 * @task compose 43 60 */ 44 - public function addTextSection($header, $text) { 61 + public function addTextSection($header, $section) { 62 + if ($section instanceof PhabricatorMetaMTAMailSection) { 63 + $plaintext = $section->getPlaintext(); 64 + $html = $section->getHTML(); 65 + } else { 66 + $plaintext = $section; 67 + $html = phutil_escape_html_newlines(phutil_tag('div', array(), $section)); 68 + } 69 + 70 + $this->addPlaintextSection($header, $plaintext); 71 + $this->addHTMLSection($header, $html); 72 + return $this; 73 + } 74 + 75 + public function addPlaintextSection($header, $text) { 45 76 $this->sections[] = $header."\n".$this->indent($text); 46 77 return $this; 47 78 } 48 79 80 + public function addHTMLSection($header, $html_fragment) { 81 + $this->htmlSections[] = array( 82 + phutil_tag('div', array('style' => 'font-weight:800;'), $header), 83 + $html_fragment); 84 + 85 + return $this; 86 + } 49 87 50 88 /** 51 89 * Add a Herald section with a rule management URI and a transcript URI. ··· 114 152 return implode("\n\n", $this->sections)."\n"; 115 153 } 116 154 155 + public function renderHTML() { 156 + $br = phutil_tag('br'); 157 + $body = phutil_implode_html(array($br, $br), $this->htmlSections); 158 + return (string)hsprintf('%s', array($body, $br)); 159 + } 117 160 118 161 /** 119 162 * Retrieve attachments.
+40
src/applications/metamta/view/PhabricatorMetaMTAMailSection.php
··· 1 + <?php 2 + /** 3 + * Helper for building a rendered section. 4 + * 5 + * @task compose Composition 6 + * @task render Rendering 7 + * @group metamta 8 + */ 9 + 10 + final class PhabricatorMetaMTAMailSection { 11 + private $plaintextFragments = array(); 12 + private $htmlFragments = array(); 13 + 14 + public function getHTML() { 15 + return $this->htmlFragments; 16 + } 17 + 18 + public function getPlaintext() { 19 + return implode("\n", $this->plaintextFragments); 20 + } 21 + 22 + public function addHTMLFragment($fragment) { 23 + $this->htmlFragments[] = $fragment; 24 + return $this; 25 + 26 + } 27 + 28 + public function addPlaintextFragment($fragment) { 29 + $this->plaintextFragments[] = $fragment; 30 + return $this; 31 + } 32 + 33 + public function addFragment($fragment) { 34 + $this->plaintextFragments[] = $fragment; 35 + $this->htmlFragments[] = 36 + phutil_escape_html_newlines(phutil_tag('div', array(), $fragment)); 37 + 38 + return $this; 39 + } 40 + }
+45
src/applications/settings/panel/PhabricatorSettingsPanelEmailFormat.php
··· 22 22 23 23 $pref_re_prefix = PhabricatorUserPreferences::PREFERENCE_RE_PREFIX; 24 24 $pref_vary = PhabricatorUserPreferences::PREFERENCE_VARY_SUBJECT; 25 + $prefs_html_email = PhabricatorUserPreferences::PREFERENCE_HTML_EMAILS; 25 26 26 27 $errors = array(); 27 28 if ($request->isFormPost()) { ··· 42 43 $pref_vary, 43 44 $request->getBool($pref_vary)); 44 45 } 46 + 47 + if ($request->getStr($prefs_html_email) == 'default') { 48 + $preferences->unsetPreference($prefs_html_email); 49 + } else { 50 + $preferences->setPreference( 51 + $prefs_html_email, 52 + $request->getBool($prefs_html_email)); 53 + } 45 54 } 46 55 47 56 $preferences->save(); ··· 58 67 ? pht('Vary') 59 68 : pht('Do Not Vary'); 60 69 70 + $html_emails_default = 'Plain Text'; 71 + 61 72 $re_prefix_value = $preferences->getPreference($pref_re_prefix); 62 73 if ($re_prefix_value === null) { 63 74 $re_prefix_value = 'default'; ··· 76 87 : 'false'; 77 88 } 78 89 90 + $html_emails_value = $preferences->getPreference($prefs_html_email); 91 + if ($html_emails_value === null) { 92 + $html_emails_value = 'default'; 93 + } else { 94 + $html_emails_value = $html_emails_value 95 + ? 'true' 96 + : 'false'; 97 + } 98 + 79 99 $form = new AphrontFormView(); 80 100 $form 81 101 ->setUser($user); 82 102 83 103 if (PhabricatorMetaMTAMail::shouldMultiplexAllMail()) { 104 + $html_email_control = id(new AphrontFormSelectControl()) 105 + ->setName($prefs_html_email) 106 + ->setOptions( 107 + array( 108 + 'default' => pht('Default (%s)', $html_emails_default), 109 + 'true' => pht('Send HTML Email'), 110 + 'false' => pht('Send Plain Text Email'), 111 + )) 112 + ->setValue($html_emails_value); 113 + 84 114 $re_control = id(new AphrontFormSelectControl()) 85 115 ->setName($pref_re_prefix) 86 116 ->setOptions( ··· 101 131 )) 102 132 ->setValue($vary_value); 103 133 } else { 134 + $html_email_control = id(new AphrontFormStaticControl()) 135 + ->setValue('Server Default ('.$html_emails_default.')'); 136 + 104 137 $re_control = id(new AphrontFormStaticControl()) 105 138 ->setValue('Server Default ('.$re_prefix_default.')'); 106 139 ··· 124 157 } 125 158 126 159 $form 160 + ->appendRemarkupInstructions( 161 + pht( 162 + "You can use the **HTML Email** setting to control whether ". 163 + "Phabricator send you HTML email (which has more color and ". 164 + "formatting) or plain text email (which is more compatible).\n". 165 + "\n". 166 + "WARNING: This feature is new and experimental! If you enable ". 167 + "it, mail may not render properly and replying to mail may not ". 168 + "work as well.")) 169 + ->appendChild( 170 + $html_email_control 171 + ->setLabel(pht('HTML Email'))) 127 172 ->appendRemarkupInstructions('') 128 173 ->appendRemarkupInstructions( 129 174 pht(
+1
src/applications/settings/storage/PhabricatorUserPreferences.php
··· 15 15 const PREFERENCE_NO_MAIL = 'no-mail'; 16 16 const PREFERENCE_MAILTAGS = 'mailtags'; 17 17 const PREFERENCE_VARY_SUBJECT = 'vary-subject'; 18 + const PREFERENCE_HTML_EMAILS = 'html-emails'; 18 19 19 20 const PREFERENCE_SEARCHBAR_JUMP = 'searchbar-jump'; 20 21 const PREFERENCE_SEARCH_SHORTCUT = 'search-shortcut';
+2 -1
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
··· 1865 1865 ->setExcludeMailRecipientPHIDs($this->getExcludeMailRecipientPHIDs()) 1866 1866 ->setMailTags($mail_tags) 1867 1867 ->setIsBulk(true) 1868 - ->setBody($body->render()); 1868 + ->setBody($body->render()) 1869 + ->setHTMLBody($body->renderHTML()); 1869 1870 1870 1871 foreach ($body->getAttachments() as $attachment) { 1871 1872 $template->addAttachment($attachment);