Path: blob/master/src/applications/differential/mail/DifferentialInlineCommentMailView.php
12262 views
<?php12final class DifferentialInlineCommentMailView3extends DifferentialMailView {45private $viewer;6private $inlines;7private $changesets;8private $authors;910public function setViewer(PhabricatorUser $viewer) {11$this->viewer = $viewer;12return $this;13}1415public function getViewer() {16return $this->viewer;17}1819public function setInlines($inlines) {20$this->inlines = $inlines;21return $this;22}2324public function getInlines() {25return $this->inlines;26}2728public function buildMailSection() {29$inlines = $this->getInlines();3031$comments = mpull($inlines, 'getComment');32$comments = mpull($comments, null, 'getPHID');33$parents = $this->loadParents($comments);34$all_comments = $comments + $parents;3536$this->changesets = $this->loadChangesets($all_comments);37$this->authors = $this->loadAuthors($all_comments);38$groups = $this->groupInlines($inlines);3940$hunk_parser = new DifferentialHunkParser();4142$spacer_text = null;43$spacer_html = phutil_tag('br');4445$section = new PhabricatorMetaMTAMailSection();4647$last_group_key = last_key($groups);48foreach ($groups as $changeset_id => $group) {49$changeset = $this->getChangeset($changeset_id);50if (!$changeset) {51continue;52}5354$is_last_group = ($changeset_id == $last_group_key);5556$last_inline_key = last_key($group);57foreach ($group as $inline_key => $inline) {58$comment = $inline->getComment();59$parent_phid = $comment->getReplyToCommentPHID();6061$inline_object = $comment->newInlineCommentObject();62$document_engine_key = $inline_object->getDocumentEngineKey();6364$is_last_inline = ($inline_key == $last_inline_key);6566$context_text = null;67$context_html = null;6869if ($parent_phid) {70$parent = idx($parents, $parent_phid);71if ($parent) {72$context_text = $this->renderInline($parent, false, true);73$context_html = $this->renderInline($parent, true, true);74}75} else if ($document_engine_key !== null) {76// See T13513. If an inline was left on a rendered document, don't77// include the patch context. Document engines currently can not78// render to mail targets, and using the line numbers as raw source79// lines produces misleading context.8081$patch_text = null;82$context_text = $this->renderPatch($comment, $patch_text, false);8384$patch_html = null;85$context_html = $this->renderPatch($comment, $patch_html, true);86} else {87$patch_text = $this->getPatch($hunk_parser, $comment, false);88$context_text = $this->renderPatch($comment, $patch_text, false);8990$patch_html = $this->getPatch($hunk_parser, $comment, true);91$context_html = $this->renderPatch($comment, $patch_html, true);92}9394$render_text = $this->renderInline($comment, false, false);95$render_html = $this->renderInline($comment, true, false);9697$section->addPlaintextFragment($context_text);98$section->addPlaintextFragment($spacer_text);99$section->addPlaintextFragment($render_text);100101$html_fragment = $this->renderContentBox(102array(103$context_html,104$render_html,105));106107$section->addHTMLFragment($html_fragment);108109if (!$is_last_group || !$is_last_inline) {110$section->addPlaintextFragment($spacer_text);111$section->addHTMLFragment($spacer_html);112}113}114}115116return $section;117}118119private function loadChangesets(array $comments) {120if (!$comments) {121return array();122}123124$ids = array();125foreach ($comments as $comment) {126$ids[] = $comment->getChangesetID();127}128129$changesets = id(new DifferentialChangesetQuery())130->setViewer($this->getViewer())131->withIDs($ids)132->needHunks(true)133->execute();134135return mpull($changesets, null, 'getID');136}137138private function loadParents(array $comments) {139$viewer = $this->getViewer();140141$phids = array();142foreach ($comments as $comment) {143$parent_phid = $comment->getReplyToCommentPHID();144if (!$parent_phid) {145continue;146}147$phids[] = $parent_phid;148}149150if (!$phids) {151return array();152}153154$parents = id(new DifferentialDiffInlineCommentQuery())155->setViewer($viewer)156->withPHIDs($phids)157->execute();158159return mpull($parents, null, 'getPHID');160}161162private function loadAuthors(array $comments) {163$viewer = $this->getViewer();164165$phids = array();166foreach ($comments as $comment) {167$author_phid = $comment->getAuthorPHID();168if (!$author_phid) {169continue;170}171$phids[] = $author_phid;172}173174if (!$phids) {175return array();176}177178return $viewer->loadHandles($phids);179}180181private function groupInlines(array $inlines) {182return DifferentialTransactionComment::sortAndGroupInlines(183$inlines,184$this->changesets);185}186187private function renderInline(188DifferentialTransactionComment $comment,189$is_html,190$is_quote) {191192$changeset = $this->getChangeset($comment->getChangesetID());193if (!$changeset) {194return null;195}196197$content = $comment->getContent();198$content = $this->renderRemarkupContent($content, $is_html);199200if ($is_quote) {201$header = $this->renderHeader($comment, $is_html, true);202} else {203$header = null;204}205206if ($is_html) {207$style = array(208'margin: 8px 0;',209'padding: 0 12px;',210);211212if ($is_quote) {213$style[] = 'color: #74777D;';214}215216$content = phutil_tag(217'div',218array(219'style' => implode(' ', $style),220),221$content);222}223224$parts = array(225$header,226"\n",227$content,228);229230if (!$is_html) {231$parts = implode('', $parts);232$parts = trim($parts);233}234235if ($is_quote) {236if ($is_html) {237$parts = $this->quoteHTML($parts);238} else {239$parts = $this->quoteText($parts);240}241}242243return $parts;244}245246private function renderRemarkupContent($content, $is_html) {247$viewer = $this->getViewer();248$production_uri = PhabricatorEnv::getProductionURI('/');249250if ($is_html) {251$mode = PhutilRemarkupEngine::MODE_HTML_MAIL;252} else {253$mode = PhutilRemarkupEngine::MODE_TEXT;254}255256$attributes = array(257'style' => 'padding: 0; margin: 8px;',258);259260$engine = PhabricatorMarkupEngine::newMarkupEngine(array())261->setConfig('viewer', $viewer)262->setConfig('uri.base', $production_uri)263->setConfig('default.p.attributes', $attributes)264->setMode($mode);265266try {267return $engine->markupText($content);268} catch (Exception $ex) {269return $content;270}271}272273private function getChangeset($id) {274return idx($this->changesets, $id);275}276277private function getAuthor($phid) {278if (isset($this->authors[$phid])) {279return $this->authors[$phid];280}281return null;282}283284private function quoteText($block) {285$block = phutil_split_lines($block);286foreach ($block as $key => $line) {287$block[$key] = '> '.$line;288}289290return implode('', $block);291}292293private function quoteHTML($block) {294$styles = array(295'padding: 0;',296'background: #F7F7F7;',297'border-color: #e3e4e8;',298'border-style: solid;',299'border-width: 0 0 1px 0;',300'margin: 0;',301);302303$styles = implode(' ', $styles);304305return phutil_tag(306'div',307array(308'style' => $styles,309),310$block);311}312313private function getPatch(314DifferentialHunkParser $parser,315DifferentialTransactionComment $comment,316$is_html) {317318$changeset = $this->getChangeset($comment->getChangesetID());319$is_new = $comment->getIsNewFile();320$start = $comment->getLineNumber();321$length = $comment->getLineLength();322323// By default, show one line of context around the target inline.324$context = 1;325326// If the inline is at least 3 lines long, don't show any extra context.327if ($length >= 2) {328$context = 0;329}330331// If the inline is more than 7 lines long, only show the first 7 lines.332if ($length >= 6) {333$length = 6;334}335336if (!$is_html) {337$hunks = $changeset->getHunks();338$patch = $parser->makeContextDiff(339$hunks,340$is_new,341$start,342$length,343$context);344$patch = phutil_split_lines($patch);345346// Remove the "@@ -x,y +u,v @@" line.347array_shift($patch);348349return implode('', $patch);350}351352$viewer = $this->getViewer();353$engine = new PhabricatorMarkupEngine();354355if ($is_new) {356$offset_mode = 'new';357} else {358$offset_mode = 'old';359}360361// See PHI894. Use the parse cache since we can end up with a large362// rendering cost otherwise when users or bots leave hundreds of inline363// comments on diffs with long recipient lists.364$cache_key = $changeset->getID();365366$viewstate = new PhabricatorChangesetViewState();367368$parser = id(new DifferentialChangesetParser())369->setRenderCacheKey($cache_key)370->setViewer($viewer)371->setViewstate($viewstate)372->setChangeset($changeset)373->setOffsetMode($offset_mode)374->setMarkupEngine($engine);375376$parser->setRenderer(new DifferentialChangesetOneUpMailRenderer());377378return $parser->render(379$start - $context,380$length + (2 * $context),381array());382}383384private function renderPatch(385DifferentialTransactionComment $comment,386$patch,387$is_html) {388389if ($is_html) {390if ($patch !== null) {391$patch = $this->renderCodeBlock($patch);392}393}394395$header = $this->renderHeader($comment, $is_html, false);396397if ($patch === null) {398$patch = array(399$header,400);401} else {402$patch = array(403$header,404"\n",405$patch,406);407}408409if (!$is_html) {410$patch = implode('', $patch);411$patch = $this->quoteText($patch);412} else {413$patch = $this->quoteHTML($patch);414}415416return $patch;417}418419private function renderHeader(420DifferentialTransactionComment $comment,421$is_html,422$with_author) {423424$changeset = $this->getChangeset($comment->getChangesetID());425$path = $changeset->getFilename();426427// Only show the filename.428$path = basename($path);429430$start = $comment->getLineNumber();431$length = $comment->getLineLength();432if ($length) {433$range = pht('%s-%s', $start, $start + $length);434} else {435$range = $start;436}437438$header = "{$path}:{$range}";439if ($is_html) {440$header = $this->renderHeaderBold($header);441}442443if ($with_author) {444$author = $this->getAuthor($comment->getAuthorPHID());445} else {446$author = null;447}448449if ($author) {450$byline = $author->getName();451452if ($is_html) {453$byline = $this->renderHeaderBold($byline);454}455456$header = pht('%s wrote in %s', $byline, $header);457}458459if ($is_html) {460$link_href = $this->getInlineURI($comment);461if ($link_href) {462$link_style = array(463'float: right;',464'text-decoration: none;',465);466467$link = phutil_tag(468'a',469array(470'style' => implode(' ', $link_style),471'href' => $link_href,472),473array(474pht('View Inline'),475476// See PHI920. Add a space after the link so we render this into477// the document:478//479// View Inline filename.txt480//481// Otherwise, we render "Inlinefilename.txt" and double-clicking482// the file name selects the word "Inline" as well.483' ',484));485} else {486$link = null;487}488489$header = $this->renderHeaderBlock(array($link, $header));490}491492return $header;493}494495private function getInlineURI(DifferentialTransactionComment $comment) {496$changeset = $this->getChangeset($comment->getChangesetID());497if (!$changeset) {498return null;499}500501$diff = $changeset->getDiff();502if (!$diff) {503return null;504}505506$revision = $diff->getRevision();507if (!$revision) {508return null;509}510511$link_href = '/'.$revision->getMonogram().'#inline-'.$comment->getID();512$link_href = PhabricatorEnv::getProductionURI($link_href);513514return $link_href;515}516517518}519520521