Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
99.03% |
102 / 103 |
|
88.89% |
8 / 9 |
CRAP | |
0.00% |
0 / 1 |
HtmlSplitConflictHeader | |
99.03% |
102 / 103 |
|
88.89% |
8 / 9 |
19 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
getLatestRevision | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getHtml | |
94.44% |
17 / 18 |
|
0.00% |
0 / 1 |
2.00 | |||
buildCurrentVersionHeader | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
3 | |||
buildYourVersionHeader | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
buildVersionHeader | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
3 | |||
getCopyLink | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
1 | |||
getFormattedDateTime | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 | |||
getMessageBox | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace TwoColConflict\Html; |
4 | |
5 | use IDBAccessObject; |
6 | use Language; |
7 | use MediaWiki\CommentFormatter\CommentFormatter; |
8 | use MediaWiki\Html\Html; |
9 | use MediaWiki\Linker\Linker; |
10 | use MediaWiki\Linker\LinkRenderer; |
11 | use MediaWiki\Linker\LinkTarget; |
12 | use MediaWiki\MediaWikiServices; |
13 | use MediaWiki\Page\WikiPageFactory; |
14 | use MediaWiki\Permissions\Authority; |
15 | use MediaWiki\Revision\RevisionRecord; |
16 | use MediaWiki\SpecialPage\SpecialPage; |
17 | use Message; |
18 | use MessageLocalizer; |
19 | use OOUI\HtmlSnippet; |
20 | use OOUI\MessageWidget; |
21 | use TwoColConflict\SplitConflictUtils; |
22 | use Wikimedia\Timestamp\ConvertibleTimestamp; |
23 | |
24 | /** |
25 | * @license GPL-2.0-or-later |
26 | * @author Andrew Kostka <andrew.kostka@wikimedia.de> |
27 | */ |
28 | class HtmlSplitConflictHeader { |
29 | |
30 | private LinkTarget $linkTarget; |
31 | private ?RevisionRecord $revision; |
32 | private Authority $authority; |
33 | private Language $language; |
34 | private MessageLocalizer $messageLocalizer; |
35 | private ConvertibleTimestamp $now; |
36 | private string $newEditSummary; |
37 | private LinkRenderer $linkRenderer; |
38 | private WikiPageFactory $wikiPageFactory; |
39 | private CommentFormatter $commentFormatter; |
40 | |
41 | /** |
42 | * @param LinkTarget $linkTarget |
43 | * @param Authority $authority |
44 | * @param string $newEditSummary |
45 | * @param Language $language |
46 | * @param MessageLocalizer $messageLocalizer |
47 | * @param CommentFormatter $commentFormatter |
48 | * @param string|int|false $now Current time for testing. Any value the ConvertibleTimestamp |
49 | * class accepts. False for the current time. |
50 | * @param RevisionRecord|null $revision Latest revision for testing, derived from the |
51 | * title otherwise. |
52 | */ |
53 | public function __construct( |
54 | LinkTarget $linkTarget, |
55 | Authority $authority, |
56 | string $newEditSummary, |
57 | Language $language, |
58 | MessageLocalizer $messageLocalizer, |
59 | CommentFormatter $commentFormatter, |
60 | $now = false, |
61 | RevisionRecord $revision = null |
62 | ) { |
63 | // TODO inject? |
64 | $services = MediaWikiServices::getInstance(); |
65 | $this->linkRenderer = $services->getLinkRenderer(); |
66 | $this->wikiPageFactory = $services->getWikiPageFactory(); |
67 | |
68 | $this->linkTarget = $linkTarget; |
69 | $this->revision = $revision ?? $this->getLatestRevision(); |
70 | $this->authority = $authority; |
71 | $this->language = $language; |
72 | $this->messageLocalizer = $messageLocalizer; |
73 | $this->commentFormatter = $commentFormatter; |
74 | $this->now = new ConvertibleTimestamp( $now ); |
75 | $this->newEditSummary = $newEditSummary; |
76 | } |
77 | |
78 | private function getLatestRevision(): ?RevisionRecord { |
79 | $wikiPage = $this->wikiPageFactory->newFromLinkTarget( $this->linkTarget ); |
80 | /** @see https://phabricator.wikimedia.org/T203085 */ |
81 | $wikiPage->loadPageData( IDBAccessObject::READ_LATEST ); |
82 | return $wikiPage->getRevisionRecord(); |
83 | } |
84 | |
85 | /** |
86 | * @param bool $isUsedAsBetaFeature |
87 | * |
88 | * @return string HTML |
89 | */ |
90 | public function getHtml( bool $isUsedAsBetaFeature = false ): string { |
91 | $hintMsg = $isUsedAsBetaFeature |
92 | ? 'twocolconflict-split-header-hint-beta' |
93 | : 'twocolconflict-split-header-hint'; |
94 | |
95 | $out = $this->getMessageBox( |
96 | 'twocolconflict-split-header-overview', 'error', 'mw-twocolconflict-overview' ); |
97 | $out .= $this->getMessageBox( $hintMsg, 'notice' ); |
98 | $out .= Html::rawElement( |
99 | 'div', |
100 | [ 'class' => 'mw-twocolconflict-split-header' ], |
101 | Html::rawElement( |
102 | 'div', |
103 | [ 'class' => 'mw-twocolconflict-split-flex-header' ], |
104 | $this->buildCurrentVersionHeader() . |
105 | ( new HtmlSideSelectorComponent( $this->messageLocalizer ) )->getHeaderHtml() . |
106 | $this->buildYourVersionHeader() |
107 | ) |
108 | ); |
109 | return $out; |
110 | } |
111 | |
112 | private function buildCurrentVersionHeader(): string { |
113 | $dateTime = $this->messageLocalizer->msg( 'just-now' )->text(); |
114 | $userTools = ''; |
115 | $summary = ''; |
116 | |
117 | if ( $this->revision ) { |
118 | $dateTime = $this->getFormattedDateTime( $this->revision->getTimestamp() ); |
119 | // FIXME: This blocks us from having pure unit tests for this class |
120 | $userTools = Linker::revUserTools( $this->revision ); |
121 | |
122 | $comment = $this->revision->getComment( RevisionRecord::FOR_THIS_USER, $this->authority ); |
123 | if ( $comment ) { |
124 | $summary = $comment->text; |
125 | } |
126 | } |
127 | |
128 | return $this->buildVersionHeader( |
129 | $this->messageLocalizer->msg( 'twocolconflict-split-current-version-header', $dateTime ), |
130 | $this->messageLocalizer->msg( 'twocolconflict-split-saved-at' )->rawParams( $userTools ), |
131 | $summary, |
132 | 'mw-twocolconflict-split-current-version-header' |
133 | ); |
134 | } |
135 | |
136 | private function buildYourVersionHeader(): string { |
137 | return $this->buildVersionHeader( |
138 | $this->messageLocalizer->msg( 'twocolconflict-split-your-version-header' ), |
139 | $this->messageLocalizer->msg( 'twocolconflict-split-not-saved-at' ), |
140 | $this->newEditSummary, |
141 | 'mw-twocolconflict-split-your-version-header', |
142 | true |
143 | ); |
144 | } |
145 | |
146 | /** |
147 | * @param Message $dateMsg |
148 | * @param Message $userMsg |
149 | * @param string $summary |
150 | * @param string $class |
151 | * @param bool|null $showCopy |
152 | * |
153 | * @return string HTML |
154 | */ |
155 | private function buildVersionHeader( |
156 | Message $dateMsg, |
157 | Message $userMsg, |
158 | string $summary, |
159 | string $class, |
160 | ?bool $showCopy = false |
161 | ): string { |
162 | $html = Html::element( |
163 | 'span', |
164 | [ 'class' => 'mw-twocolconflict-revision-label' ], |
165 | $dateMsg->text() |
166 | ); |
167 | if ( $showCopy ) { |
168 | $html .= $this->getCopyLink(); |
169 | } |
170 | $html .= Html::element( 'br' ) . |
171 | Html::rawElement( 'span', [], $userMsg->escaped() ); |
172 | |
173 | if ( $summary !== '' ) { |
174 | $summaryMsg = $this->messageLocalizer->msg( 'parentheses' ) |
175 | ->rawParams( $this->commentFormatter->format( $summary, $this->linkTarget ) ); |
176 | $html .= Html::element( 'br' ) . |
177 | Html::rawElement( 'span', [ 'class' => 'comment' ], $summaryMsg->escaped() ); |
178 | } |
179 | |
180 | return Html::rawElement( 'div', [ 'class' => $class ], $html ); |
181 | } |
182 | |
183 | private function getCopyLink(): string { |
184 | $specialPage = SpecialPage::getTitleValueFor( |
185 | 'TwoColConflictProvideSubmittedText', |
186 | (string)$this->linkTarget |
187 | ); |
188 | $label = $this->messageLocalizer->msg( 'twocolconflict-copy-tab-action' )->text(); |
189 | $tooltip = $this->messageLocalizer->msg( 'twocolconflict-copy-tab-tooltip' )->text(); |
190 | |
191 | $link = $this->linkRenderer->makeKnownLink( |
192 | $specialPage, |
193 | $label, |
194 | [ 'title' => $tooltip, 'target' => '_blank' ] |
195 | ); |
196 | |
197 | return ' ' . Html::rawElement( |
198 | 'span', |
199 | [ 'class' => 'mw-twocolconflict-copy-link' ], |
200 | $this->messageLocalizer->msg( 'parentheses' )->rawParams( $link ) |
201 | ); |
202 | } |
203 | |
204 | private function getFormattedDateTime( ?string $timestamp ): string { |
205 | $diff = ( new ConvertibleTimestamp( $timestamp ?: false ) )->diff( $this->now ); |
206 | |
207 | if ( $diff->days ) { |
208 | return $this->language->userTimeAndDate( $timestamp, $this->authority->getUser() ); |
209 | } |
210 | |
211 | if ( $diff->h ) { |
212 | $minutes = $diff->i + $diff->s / 60; |
213 | return $this->messageLocalizer->msg( 'hours-ago', round( $diff->h + $minutes / 60 ) )->text(); |
214 | } |
215 | |
216 | if ( $diff->i ) { |
217 | return $this->messageLocalizer->msg( 'minutes-ago', round( $diff->i + $diff->s / 60 ) )->text(); |
218 | } |
219 | |
220 | if ( $diff->s ) { |
221 | return $this->messageLocalizer->msg( 'seconds-ago', $diff->s )->text(); |
222 | } |
223 | |
224 | return $this->messageLocalizer->msg( 'just-now' )->text(); |
225 | } |
226 | |
227 | private function getMessageBox( string $messageKey, string $type, string ...$classes ): string { |
228 | $html = $this->messageLocalizer->msg( $messageKey )->parse(); |
229 | return ( new MessageWidget( [ |
230 | 'label' => new HtmlSnippet( SplitConflictUtils::addTargetBlankToLinks( $html ) ), |
231 | 'type' => $type, |
232 | ] ) ) |
233 | ->addClasses( [ 'mw-twocolconflict-messageWidget', ...$classes ] ) |
234 | ->toString(); |
235 | } |
236 | |
237 | } |