Path: blob/master/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
12262 views
<?php12abstract class DifferentialChangesetHTMLRenderer3extends DifferentialChangesetRenderer {45public static function getHTMLRendererByKey($key) {6switch ($key) {7case '1up':8return new DifferentialChangesetOneUpRenderer();9case '2up':10default:11return new DifferentialChangesetTwoUpRenderer();12}13throw new Exception(pht('Unknown HTML renderer "%s"!', $key));14}1516abstract protected function getRendererTableClass();17abstract public function getRowScaffoldForInline(18PHUIDiffInlineCommentView $view);1920protected function renderChangeTypeHeader($force) {21$changeset = $this->getChangeset();2223$change = $changeset->getChangeType();24$file = $changeset->getFileType();2526$messages = array();27switch ($change) {2829case DifferentialChangeType::TYPE_ADD:30switch ($file) {31case DifferentialChangeType::FILE_TEXT:32$messages[] = pht('This file was added.');33break;34case DifferentialChangeType::FILE_IMAGE:35$messages[] = pht('This image was added.');36break;37case DifferentialChangeType::FILE_DIRECTORY:38$messages[] = pht('This directory was added.');39break;40case DifferentialChangeType::FILE_BINARY:41$messages[] = pht('This binary file was added.');42break;43case DifferentialChangeType::FILE_SYMLINK:44$messages[] = pht('This symlink was added.');45break;46case DifferentialChangeType::FILE_SUBMODULE:47$messages[] = pht('This submodule was added.');48break;49}50break;5152case DifferentialChangeType::TYPE_DELETE:53switch ($file) {54case DifferentialChangeType::FILE_TEXT:55$messages[] = pht('This file was deleted.');56break;57case DifferentialChangeType::FILE_IMAGE:58$messages[] = pht('This image was deleted.');59break;60case DifferentialChangeType::FILE_DIRECTORY:61$messages[] = pht('This directory was deleted.');62break;63case DifferentialChangeType::FILE_BINARY:64$messages[] = pht('This binary file was deleted.');65break;66case DifferentialChangeType::FILE_SYMLINK:67$messages[] = pht('This symlink was deleted.');68break;69case DifferentialChangeType::FILE_SUBMODULE:70$messages[] = pht('This submodule was deleted.');71break;72}73break;7475case DifferentialChangeType::TYPE_MOVE_HERE:76$from = phutil_tag('strong', array(), $changeset->getOldFile());77switch ($file) {78case DifferentialChangeType::FILE_TEXT:79$messages[] = pht('This file was moved from %s.', $from);80break;81case DifferentialChangeType::FILE_IMAGE:82$messages[] = pht('This image was moved from %s.', $from);83break;84case DifferentialChangeType::FILE_DIRECTORY:85$messages[] = pht('This directory was moved from %s.', $from);86break;87case DifferentialChangeType::FILE_BINARY:88$messages[] = pht('This binary file was moved from %s.', $from);89break;90case DifferentialChangeType::FILE_SYMLINK:91$messages[] = pht('This symlink was moved from %s.', $from);92break;93case DifferentialChangeType::FILE_SUBMODULE:94$messages[] = pht('This submodule was moved from %s.', $from);95break;96}97break;9899case DifferentialChangeType::TYPE_COPY_HERE:100$from = phutil_tag('strong', array(), $changeset->getOldFile());101switch ($file) {102case DifferentialChangeType::FILE_TEXT:103$messages[] = pht('This file was copied from %s.', $from);104break;105case DifferentialChangeType::FILE_IMAGE:106$messages[] = pht('This image was copied from %s.', $from);107break;108case DifferentialChangeType::FILE_DIRECTORY:109$messages[] = pht('This directory was copied from %s.', $from);110break;111case DifferentialChangeType::FILE_BINARY:112$messages[] = pht('This binary file was copied from %s.', $from);113break;114case DifferentialChangeType::FILE_SYMLINK:115$messages[] = pht('This symlink was copied from %s.', $from);116break;117case DifferentialChangeType::FILE_SUBMODULE:118$messages[] = pht('This submodule was copied from %s.', $from);119break;120}121break;122123case DifferentialChangeType::TYPE_MOVE_AWAY:124$paths = phutil_tag(125'strong',126array(),127implode(', ', $changeset->getAwayPaths()));128switch ($file) {129case DifferentialChangeType::FILE_TEXT:130$messages[] = pht('This file was moved to %s.', $paths);131break;132case DifferentialChangeType::FILE_IMAGE:133$messages[] = pht('This image was moved to %s.', $paths);134break;135case DifferentialChangeType::FILE_DIRECTORY:136$messages[] = pht('This directory was moved to %s.', $paths);137break;138case DifferentialChangeType::FILE_BINARY:139$messages[] = pht('This binary file was moved to %s.', $paths);140break;141case DifferentialChangeType::FILE_SYMLINK:142$messages[] = pht('This symlink was moved to %s.', $paths);143break;144case DifferentialChangeType::FILE_SUBMODULE:145$messages[] = pht('This submodule was moved to %s.', $paths);146break;147}148break;149150case DifferentialChangeType::TYPE_COPY_AWAY:151$paths = phutil_tag(152'strong',153array(),154implode(', ', $changeset->getAwayPaths()));155switch ($file) {156case DifferentialChangeType::FILE_TEXT:157$messages[] = pht('This file was copied to %s.', $paths);158break;159case DifferentialChangeType::FILE_IMAGE:160$messages[] = pht('This image was copied to %s.', $paths);161break;162case DifferentialChangeType::FILE_DIRECTORY:163$messages[] = pht('This directory was copied to %s.', $paths);164break;165case DifferentialChangeType::FILE_BINARY:166$messages[] = pht('This binary file was copied to %s.', $paths);167break;168case DifferentialChangeType::FILE_SYMLINK:169$messages[] = pht('This symlink was copied to %s.', $paths);170break;171case DifferentialChangeType::FILE_SUBMODULE:172$messages[] = pht('This submodule was copied to %s.', $paths);173break;174}175break;176177case DifferentialChangeType::TYPE_MULTICOPY:178$paths = phutil_tag(179'strong',180array(),181implode(', ', $changeset->getAwayPaths()));182switch ($file) {183case DifferentialChangeType::FILE_TEXT:184$messages[] = pht(185'This file was deleted after being copied to %s.',186$paths);187break;188case DifferentialChangeType::FILE_IMAGE:189$messages[] = pht(190'This image was deleted after being copied to %s.',191$paths);192break;193case DifferentialChangeType::FILE_DIRECTORY:194$messages[] = pht(195'This directory was deleted after being copied to %s.',196$paths);197break;198case DifferentialChangeType::FILE_BINARY:199$messages[] = pht(200'This binary file was deleted after being copied to %s.',201$paths);202break;203case DifferentialChangeType::FILE_SYMLINK:204$messages[] = pht(205'This symlink was deleted after being copied to %s.',206$paths);207break;208case DifferentialChangeType::FILE_SUBMODULE:209$messages[] = pht(210'This submodule was deleted after being copied to %s.',211$paths);212break;213}214break;215216default:217switch ($file) {218case DifferentialChangeType::FILE_TEXT:219// This is the default case, so we only render this header if220// forced to since it's not very useful.221if ($force) {222$messages[] = pht('This file was not modified.');223}224break;225case DifferentialChangeType::FILE_IMAGE:226$messages[] = pht('This is an image.');227break;228case DifferentialChangeType::FILE_DIRECTORY:229$messages[] = pht('This is a directory.');230break;231case DifferentialChangeType::FILE_BINARY:232$messages[] = pht('This is a binary file.');233break;234case DifferentialChangeType::FILE_SYMLINK:235$messages[] = pht('This is a symlink.');236break;237case DifferentialChangeType::FILE_SUBMODULE:238$messages[] = pht('This is a submodule.');239break;240}241break;242}243244return $this->formatHeaderMessages($messages);245}246247protected function renderUndershieldHeader() {248$messages = array();249250$changeset = $this->getChangeset();251252$file = $changeset->getFileType();253254// If this is a text file with at least one hunk, we may have converted255// the text encoding. In this case, show a note.256$show_encoding = ($file == DifferentialChangeType::FILE_TEXT) &&257($changeset->getHunks());258259if ($show_encoding) {260$encoding = $this->getOriginalCharacterEncoding();261if ($encoding != 'utf8') {262if ($encoding) {263$messages[] = pht(264'This file was converted from %s for display.',265phutil_tag('strong', array(), $encoding));266} else {267$messages[] = pht('This file uses an unknown character encoding.');268}269}270}271272$blocks = $this->getDocumentEngineBlocks();273if ($blocks) {274foreach ($blocks->getMessages() as $message) {275$messages[] = $message;276}277} else {278if ($this->getHighlightingDisabled()) {279$byte_limit = DifferentialChangesetParser::HIGHLIGHT_BYTE_LIMIT;280$byte_limit = phutil_format_bytes($byte_limit);281$messages[] = pht(282'This file is larger than %s, so syntax highlighting is '.283'disabled by default.',284$byte_limit);285}286}287288return $this->formatHeaderMessages($messages);289}290291private function formatHeaderMessages(array $messages) {292if (!$messages) {293return null;294}295296foreach ($messages as $key => $message) {297$messages[$key] = phutil_tag('li', array(), $message);298}299300return phutil_tag(301'ul',302array(303'class' => 'differential-meta-notice',304),305$messages);306}307308protected function renderPropertyChangeHeader() {309$changeset = $this->getChangeset();310list($old, $new) = $this->getChangesetProperties($changeset);311312// If we don't have any property changes, don't render this table.313if ($old === $new) {314return null;315}316317$keys = array_keys($old + $new);318sort($keys);319320$key_map = array(321'unix:filemode' => pht('File Mode'),322'file:dimensions' => pht('Image Dimensions'),323'file:mimetype' => pht('MIME Type'),324'file:size' => pht('File Size'),325);326327$rows = array();328foreach ($keys as $key) {329$oval = idx($old, $key);330$nval = idx($new, $key);331if ($oval !== $nval) {332if ($oval === null) {333$oval = phutil_tag('em', array(), 'null');334} else {335$oval = phutil_escape_html_newlines($oval);336}337338if ($nval === null) {339$nval = phutil_tag('em', array(), 'null');340} else {341$nval = phutil_escape_html_newlines($nval);342}343344$readable_key = idx($key_map, $key, $key);345346$row = array(347$readable_key,348$oval,349$nval,350);351$rows[] = $row;352353}354}355356$classes = array('', 'oval', 'nval');357$headers = array(358pht('Property'),359pht('Old Value'),360pht('New Value'),361);362$table = id(new AphrontTableView($rows))363->setHeaders($headers)364->setColumnClasses($classes);365return phutil_tag(366'div',367array(368'class' => 'differential-property-table',369),370$table);371}372373public function renderShield($message, $force = 'default') {374$end = count($this->getOldLines());375$reference = $this->getRenderingReference();376377if ($force !== 'text' &&378$force !== 'none' &&379$force !== 'default') {380throw new Exception(381pht(382"Invalid '%s' parameter '%s'!",383'force',384$force));385}386387$range = "0-{$end}";388if ($force == 'text') {389// If we're forcing text, force the whole file to be rendered.390$range = "{$range}/0-{$end}";391}392393$meta = array(394'ref' => $reference,395'range' => $range,396);397398$content = array();399$content[] = $message;400if ($force !== 'none') {401$content[] = ' ';402$content[] = javelin_tag(403'a',404array(405'mustcapture' => true,406'sigil' => 'show-more',407'class' => 'complete',408'href' => '#',409'meta' => $meta,410),411pht('Show File Contents'));412}413414return $this->wrapChangeInTable(415javelin_tag(416'tr',417array(418'sigil' => 'context-target',419),420phutil_tag(421'td',422array(423'class' => 'differential-shield',424'colspan' => 6,425),426$content)));427}428429abstract protected function renderColgroup();430431432protected function wrapChangeInTable($content) {433if (!$content) {434return null;435}436437$classes = array();438$classes[] = 'differential-diff';439$classes[] = 'remarkup-code';440$classes[] = 'PhabricatorMonospaced';441$classes[] = $this->getRendererTableClass();442443$sigils = array();444$sigils[] = 'differential-diff';445foreach ($this->getTableSigils() as $sigil) {446$sigils[] = $sigil;447}448449return javelin_tag(450'table',451array(452'class' => implode(' ', $classes),453'sigil' => implode(' ', $sigils),454),455array(456$this->renderColgroup(),457$content,458));459}460461protected function getTableSigils() {462return array();463}464465protected function buildInlineComment(466PhabricatorInlineComment $comment,467$on_right = false) {468469$viewer = $this->getUser();470$edit = $viewer &&471($comment->getAuthorPHID() == $viewer->getPHID()) &&472($comment->isDraft())473&& $this->getShowEditAndReplyLinks();474$allow_reply = (bool)$viewer && $this->getShowEditAndReplyLinks();475$allow_done = !$comment->isDraft() && $this->getCanMarkDone();476477return id(new PHUIDiffInlineCommentDetailView())478->setViewer($viewer)479->setInlineComment($comment)480->setIsOnRight($on_right)481->setHandles($this->getHandles())482->setMarkupEngine($this->getMarkupEngine())483->setEditable($edit)484->setAllowReply($allow_reply)485->setCanMarkDone($allow_done)486->setObjectOwnerPHID($this->getObjectOwnerPHID());487}488489490/**491* Build links which users can click to show more context in a changeset.492*493* @param int Beginning of the line range to build links for.494* @param int Length of the line range to build links for.495* @param int Total number of lines in the changeset.496* @return markup Rendered links.497*/498protected function renderShowContextLinks(499$top,500$len,501$changeset_length,502$is_blocks = false) {503504$block_size = 20;505$end = ($top + $len) - $block_size;506507// If this is a large block, such that the "top" and "bottom" ranges are508// non-overlapping, we'll provide options to show the top, bottom or entire509// block. For smaller blocks, we only provide an option to show the entire510// block, since it would be silly to show the bottom 20 lines of a 25-line511// block.512$is_large_block = ($len > ($block_size * 2));513514$links = array();515516$block_display = new PhutilNumber($block_size);517518if ($is_large_block) {519$is_first_block = ($top == 0);520if ($is_first_block) {521if ($is_blocks) {522$text = pht('Show First %s Block(s)', $block_display);523} else {524$text = pht('Show First %s Line(s)', $block_display);525}526} else {527if ($is_blocks) {528$text = pht("\xE2\x96\xB2 Show %s Block(s)", $block_display);529} else {530$text = pht("\xE2\x96\xB2 Show %s Line(s)", $block_display);531}532}533534$links[] = $this->renderShowContextLink(535false,536"{$top}-{$len}/{$top}-20",537$text);538}539540if ($is_blocks) {541$text = pht('Show All %s Block(s)', new PhutilNumber($len));542} else {543$text = pht('Show All %s Line(s)', new PhutilNumber($len));544}545546$links[] = $this->renderShowContextLink(547true,548"{$top}-{$len}/{$top}-{$len}",549$text);550551if ($is_large_block) {552$is_last_block = (($top + $len) >= $changeset_length);553if ($is_last_block) {554if ($is_blocks) {555$text = pht('Show Last %s Block(s)', $block_display);556} else {557$text = pht('Show Last %s Line(s)', $block_display);558}559} else {560if ($is_blocks) {561$text = pht("\xE2\x96\xBC Show %s Block(s)", $block_display);562} else {563$text = pht("\xE2\x96\xBC Show %s Line(s)", $block_display);564}565}566567$links[] = $this->renderShowContextLink(568false,569"{$top}-{$len}/{$end}-20",570$text);571}572573return phutil_implode_html(" \xE2\x80\xA2 ", $links);574}575576577/**578* Build a link that shows more context in a changeset.579*580* See @{method:renderShowContextLinks}.581*582* @param bool Does this link show all context when clicked?583* @param string Range specification for lines to show.584* @param string Text of the link.585* @return markup Rendered link.586*/587private function renderShowContextLink($is_all, $range, $text) {588$reference = $this->getRenderingReference();589590return javelin_tag(591'a',592array(593'href' => '#',594'mustcapture' => true,595'sigil' => 'show-more',596'meta' => array(597'type' => ($is_all ? 'all' : null),598'range' => $range,599),600),601$text);602}603604/**605* Build the prefixes for line IDs used to track inline comments.606*607* @return pair<wild, wild> Left and right prefixes.608*/609protected function getLineIDPrefixes() {610// These look like "C123NL45", which means the line is line 45 on the611// "new" side of the file in changeset 123.612613// The "C" stands for "changeset", and is followed by a changeset ID.614615// "N" stands for "new" and means the comment should attach to the new file616// when stored. "O" stands for "old" and means the comment should attach to617// the old file. These are important because either the old or new part618// of a file may appear on the left or right side of the diff in the619// diff-of-diffs view.620621// The "L" stands for "line" and is followed by the line number.622623if ($this->getOldChangesetID()) {624$left_prefix = array();625$left_prefix[] = 'C';626$left_prefix[] = $this->getOldChangesetID();627$left_prefix[] = $this->getOldAttachesToNewFile() ? 'N' : 'O';628$left_prefix[] = 'L';629$left_prefix = implode('', $left_prefix);630} else {631$left_prefix = null;632}633634if ($this->getNewChangesetID()) {635$right_prefix = array();636$right_prefix[] = 'C';637$right_prefix[] = $this->getNewChangesetID();638$right_prefix[] = $this->getNewAttachesToNewFile() ? 'N' : 'O';639$right_prefix[] = 'L';640$right_prefix = implode('', $right_prefix);641} else {642$right_prefix = null;643}644645return array($left_prefix, $right_prefix);646}647648}649650651