Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
97.75% |
87 / 89 |
|
87.50% |
14 / 16 |
CRAP | |
0.00% |
0 / 1 |
CommentFormatter | |
97.75% |
87 / 89 |
|
87.50% |
14 / 16 |
31 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createBatch | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
format | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
formatBlock | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
formatLinksUnsafe | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
formatLinks | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
formatInternal | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
formatStrings | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
formatStringsAsBlock | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
formatRevision | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
formatRevisions | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
createRevisionBatch | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
formatItems | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
formatItemsInternal | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
3 | |||
wrapCommentWithBlock | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
4.05 | |||
preprocessRevComment | |
95.00% |
19 / 20 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace MediaWiki\CommentFormatter; |
4 | |
5 | use MediaWiki\Linker\Linker; |
6 | use MediaWiki\Linker\LinkTarget; |
7 | use MediaWiki\Permissions\Authority; |
8 | use MediaWiki\Revision\RevisionRecord; |
9 | use Traversable; |
10 | |
11 | /** |
12 | * This is the main service interface for converting single-line comments from |
13 | * various DB comment fields into HTML. |
14 | * |
15 | * @since 1.38 |
16 | */ |
17 | class CommentFormatter { |
18 | /** @var CommentParserFactory */ |
19 | protected $parserFactory; |
20 | |
21 | /** |
22 | * @internal Use MediaWikiServices::getCommentFormatter() |
23 | * |
24 | * @param CommentParserFactory $parserFactory |
25 | */ |
26 | public function __construct( CommentParserFactory $parserFactory ) { |
27 | $this->parserFactory = $parserFactory; |
28 | } |
29 | |
30 | /** |
31 | * Format comments using a fluent interface. |
32 | * |
33 | * @return CommentBatch |
34 | */ |
35 | public function createBatch() { |
36 | return new CommentBatch( $this ); |
37 | } |
38 | |
39 | /** |
40 | * Format a single comment. Similar to the old Linker::formatComment(). |
41 | * |
42 | * @param string $comment |
43 | * @param LinkTarget|null $selfLinkTarget The title used for fragment-only |
44 | * and section links, formerly $title. |
45 | * @param bool $samePage If true, self links are rendered with a fragment- |
46 | * only URL. Formerly $local. |
47 | * @param string|false|null $wikiId ID of the wiki to link to (if not the local |
48 | * wiki), as used by WikiMap. |
49 | * @return string |
50 | */ |
51 | public function format( string $comment, LinkTarget $selfLinkTarget = null, |
52 | $samePage = false, $wikiId = false |
53 | ) { |
54 | return $this->formatInternal( $comment, true, false, false, |
55 | $selfLinkTarget, $samePage, $wikiId ); |
56 | } |
57 | |
58 | /** |
59 | * Wrap a comment in standard punctuation and formatting if |
60 | * it's non-empty, otherwise return an empty string. |
61 | * |
62 | * @param string $comment |
63 | * @param LinkTarget|null $selfLinkTarget The title used for fragment-only |
64 | * and section links, formerly $title. |
65 | * @param bool $samePage If true, self links are rendered with a fragment- |
66 | * only URL. Formerly $local. |
67 | * @param string|false|null $wikiId ID of the wiki to link to (if not the local |
68 | * wiki), as used by WikiMap. |
69 | * @param bool $useParentheses |
70 | * @return string |
71 | */ |
72 | public function formatBlock( string $comment, LinkTarget $selfLinkTarget = null, |
73 | $samePage = false, $wikiId = false, $useParentheses = true |
74 | ) { |
75 | return $this->formatInternal( $comment, true, true, $useParentheses, |
76 | $selfLinkTarget, $samePage, $wikiId ); |
77 | } |
78 | |
79 | /** |
80 | * Format a comment, passing through HTML in the input to the output. |
81 | * This is unsafe and exists only for backwards compatibility with |
82 | * Linker::formatLinksInComment(). |
83 | * |
84 | * In new code, use formatLinks() or createBatch()->disableSectionLinks(). |
85 | * |
86 | * @internal |
87 | * |
88 | * @param string $comment |
89 | * @param LinkTarget|null $selfLinkTarget The title used for fragment-only |
90 | * and section links, formerly $title. |
91 | * @param bool $samePage If true, self links are rendered with a fragment- |
92 | * only URL. Formerly $local. |
93 | * @param string|false|null $wikiId ID of the wiki to link to (if not the local |
94 | * wiki), as used by WikiMap. |
95 | * @return string |
96 | */ |
97 | public function formatLinksUnsafe( string $comment, LinkTarget $selfLinkTarget = null, |
98 | $samePage = false, $wikiId = false |
99 | ) { |
100 | $parser = $this->parserFactory->create(); |
101 | $preprocessed = $parser->preprocessUnsafe( $comment, $selfLinkTarget, |
102 | $samePage, $wikiId, false ); |
103 | return $parser->finalize( $preprocessed ); |
104 | } |
105 | |
106 | /** |
107 | * Format links in a comment, ignoring section links in C-style comments. |
108 | * |
109 | * @param string $comment |
110 | * @param LinkTarget|null $selfLinkTarget The title used for fragment-only |
111 | * and section links, formerly $title. |
112 | * @param bool $samePage If true, self links are rendered with a fragment- |
113 | * only URL. Formerly $local. |
114 | * @param string|false|null $wikiId ID of the wiki to link to (if not the local |
115 | * wiki), as used by WikiMap. |
116 | * @return string |
117 | */ |
118 | public function formatLinks( string $comment, LinkTarget $selfLinkTarget = null, |
119 | $samePage = false, $wikiId = false |
120 | ) { |
121 | return $this->formatInternal( $comment, false, false, false, |
122 | $selfLinkTarget, $samePage, $wikiId ); |
123 | } |
124 | |
125 | /** |
126 | * Format a single comment with many ugly boolean parameters. |
127 | * |
128 | * @param string $comment |
129 | * @param bool $enableSectionLinks |
130 | * @param bool $useBlock |
131 | * @param bool $useParentheses |
132 | * @param LinkTarget|null $selfLinkTarget The title used for fragment-only |
133 | * and section links, formerly $title. |
134 | * @param bool $samePage If true, self links are rendered with a fragment- |
135 | * only URL. Formerly $local. |
136 | * @param string|false|null $wikiId ID of the wiki to link to (if not the local |
137 | * wiki), as used by WikiMap. |
138 | * @return string|string[] |
139 | */ |
140 | private function formatInternal( $comment, $enableSectionLinks, $useBlock, $useParentheses, |
141 | $selfLinkTarget = null, $samePage = false, $wikiId = false |
142 | ) { |
143 | $parser = $this->parserFactory->create(); |
144 | $preprocessed = $parser->preprocess( $comment, $selfLinkTarget, $samePage, $wikiId, |
145 | $enableSectionLinks ); |
146 | $output = $parser->finalize( $preprocessed ); |
147 | if ( $useBlock ) { |
148 | $output = $this->wrapCommentWithBlock( $output, $useParentheses ); |
149 | } |
150 | return $output; |
151 | } |
152 | |
153 | /** |
154 | * Format comments which are provided as strings and all have the same |
155 | * self-link target and other options. |
156 | * |
157 | * If you need a different title for each comment, use createBatch(). |
158 | * |
159 | * @param string[] $strings |
160 | * @param LinkTarget|null $selfLinkTarget The title used for fragment-only |
161 | * and section links, formerly $title. |
162 | * @param bool $samePage If true, self links are rendered with a fragment- |
163 | * only URL. Formerly $local. |
164 | * @param string|false|null $wikiId ID of the wiki to link to (if not the local |
165 | * wiki), as used by WikiMap. |
166 | * @return string[] |
167 | */ |
168 | public function formatStrings( $strings, LinkTarget $selfLinkTarget = null, |
169 | $samePage = false, $wikiId = false |
170 | ) { |
171 | $parser = $this->parserFactory->create(); |
172 | $outputs = []; |
173 | foreach ( $strings as $i => $comment ) { |
174 | $outputs[$i] = $parser->preprocess( $comment, $selfLinkTarget, $samePage, $wikiId ); |
175 | } |
176 | return $parser->finalize( $outputs ); |
177 | } |
178 | |
179 | /** |
180 | * Given an array of comments as strings which all have the same self link |
181 | * target, format the comments and wrap them in standard punctuation and |
182 | * formatting. |
183 | * |
184 | * If you need a different title for each comment, use createBatch(). |
185 | * |
186 | * @param string[] $strings |
187 | * @param LinkTarget|null $selfLinkTarget The title used for fragment-only |
188 | * and section links, formerly $title. |
189 | * @param bool $samePage If true, self links are rendered with a fragment- |
190 | * only URL. Formerly $local. |
191 | * @param string|false|null $wikiId ID of the wiki to link to (if not the local |
192 | * wiki), as used by WikiMap. |
193 | * @param bool $useParentheses |
194 | * @return string[] |
195 | */ |
196 | public function formatStringsAsBlock( $strings, LinkTarget $selfLinkTarget = null, |
197 | $samePage = false, $wikiId = false, $useParentheses = true |
198 | ) { |
199 | $parser = $this->parserFactory->create(); |
200 | $outputs = []; |
201 | foreach ( $strings as $i => $comment ) { |
202 | $outputs[$i] = $this->wrapCommentWithBlock( |
203 | $parser->preprocess( $comment, $selfLinkTarget, $samePage, $wikiId ), |
204 | $useParentheses ); |
205 | } |
206 | return $parser->finalize( $outputs ); |
207 | } |
208 | |
209 | /** |
210 | * Wrap and format the given revision's comment block, if the specified |
211 | * user is allowed to view it. |
212 | * |
213 | * This method produces HTML that requires CSS styles in mediawiki.interface.helpers.styles. |
214 | * |
215 | * NOTE: revision comments are special. This is not the same as getting a |
216 | * revision comment as a string and then formatting it with format(). |
217 | * |
218 | * @param RevisionRecord $revision The revision to extract the comment and |
219 | * title from. The title should always be populated, to avoid an additional |
220 | * DB query. |
221 | * @param Authority $authority The user viewing the comment |
222 | * @param bool $samePage If true, self links are rendered with a fragment- |
223 | * only URL. Formerly $local. |
224 | * @param bool $isPublic Show only if all users can see it |
225 | * @param bool $useParentheses Whether the comment is wrapped in parentheses |
226 | * @return string |
227 | */ |
228 | public function formatRevision( |
229 | RevisionRecord $revision, |
230 | Authority $authority, |
231 | $samePage = false, |
232 | $isPublic = false, |
233 | $useParentheses = true |
234 | ) { |
235 | $parser = $this->parserFactory->create(); |
236 | return $parser->finalize( $this->preprocessRevComment( |
237 | $parser, $authority, $revision, $samePage, $isPublic, $useParentheses ) ); |
238 | } |
239 | |
240 | /** |
241 | * Format multiple revision comments. |
242 | * |
243 | * @see CommentFormatter::formatRevision() |
244 | * |
245 | * @param iterable<RevisionRecord> $revisions |
246 | * @param Authority $authority |
247 | * @param bool $samePage |
248 | * @param bool $isPublic |
249 | * @param bool $useParentheses |
250 | * @param bool $indexById |
251 | * @return string|string[] |
252 | */ |
253 | public function formatRevisions( |
254 | $revisions, |
255 | Authority $authority, |
256 | $samePage = false, |
257 | $isPublic = false, |
258 | $useParentheses = true, |
259 | $indexById = false |
260 | ) { |
261 | $parser = $this->parserFactory->create(); |
262 | $outputs = []; |
263 | foreach ( $revisions as $i => $rev ) { |
264 | if ( $indexById ) { |
265 | $key = $rev->getId(); |
266 | } else { |
267 | $key = $i; |
268 | } |
269 | // @phan-suppress-next-line PhanTypeMismatchDimAssignment getId does not return null here |
270 | $outputs[$key] = $this->preprocessRevComment( |
271 | $parser, $authority, $rev, $samePage, $isPublic, $useParentheses ); |
272 | } |
273 | return $parser->finalize( $outputs ); |
274 | } |
275 | |
276 | /** |
277 | * Format a batch of revision comments using a fluent interface. |
278 | * |
279 | * @return RevisionCommentBatch |
280 | */ |
281 | public function createRevisionBatch() { |
282 | return new RevisionCommentBatch( $this ); |
283 | } |
284 | |
285 | /** |
286 | * Format an iterator over CommentItem objects |
287 | * |
288 | * A shortcut for createBatch()->comments()->execute() for when you |
289 | * need to pass no other options. |
290 | * |
291 | * @param iterable<CommentItem>|Traversable $items |
292 | * @return string[] |
293 | */ |
294 | public function formatItems( $items ) { |
295 | return $this->formatItemsInternal( $items ); |
296 | } |
297 | |
298 | /** |
299 | * @internal For use by CommentBatch |
300 | * |
301 | * Format comments with nullable batch options. |
302 | * |
303 | * @param iterable<CommentItem> $items |
304 | * @param LinkTarget|null $selfLinkTarget |
305 | * @param bool|null $samePage |
306 | * @param string|false|null $wikiId |
307 | * @param bool|null $enableSectionLinks |
308 | * @param bool|null $useBlock |
309 | * @param bool|null $useParentheses |
310 | * @return string[] |
311 | */ |
312 | public function formatItemsInternal( $items, $selfLinkTarget = null, |
313 | $samePage = null, $wikiId = null, $enableSectionLinks = null, |
314 | $useBlock = null, $useParentheses = null |
315 | ) { |
316 | $outputs = []; |
317 | $parser = $this->parserFactory->create(); |
318 | foreach ( $items as $index => $item ) { |
319 | $preprocessed = $parser->preprocess( |
320 | $item->comment, |
321 | $item->selfLinkTarget ?? $selfLinkTarget, |
322 | $item->samePage ?? $samePage ?? false, |
323 | $item->wikiId ?? $wikiId ?? false, |
324 | $enableSectionLinks ?? true |
325 | ); |
326 | if ( $useBlock ?? false ) { |
327 | $preprocessed = $this->wrapCommentWithBlock( |
328 | $preprocessed, |
329 | $useParentheses ?? true |
330 | ); |
331 | } |
332 | $outputs[$index] = $preprocessed; |
333 | } |
334 | return $parser->finalize( $outputs ); |
335 | } |
336 | |
337 | /** |
338 | * Wrap a comment in standard punctuation and formatting if |
339 | * it's non-empty, otherwise return empty string. |
340 | * |
341 | * @param string $formatted |
342 | * @param bool $useParentheses Whether the comment is wrapped in parentheses |
343 | * |
344 | * @return string |
345 | */ |
346 | protected function wrapCommentWithBlock( |
347 | $formatted, $useParentheses |
348 | ) { |
349 | // '*' used to be the comment inserted by the software way back |
350 | // in antiquity in case none was provided, here for backwards |
351 | // compatibility, acc. to [brooke] -ævar |
352 | if ( $formatted == '' || $formatted == '*' ) { |
353 | return ''; |
354 | } |
355 | if ( $useParentheses ) { |
356 | $formatted = wfMessage( 'parentheses' )->rawParams( $formatted )->escaped(); |
357 | $classNames = 'comment'; |
358 | } else { |
359 | $classNames = 'comment comment--without-parentheses'; |
360 | } |
361 | return " <span class=\"$classNames\">$formatted</span>"; |
362 | } |
363 | |
364 | /** |
365 | * Preprocess and wrap a revision comment. |
366 | * |
367 | * @param CommentParser $parser |
368 | * @param Authority $authority |
369 | * @param RevisionRecord $revRecord |
370 | * @param bool $samePage Whether section links should refer to local page |
371 | * @param bool $isPublic Show only if all users can see it |
372 | * @param bool $useParentheses (optional) Wrap comments in parentheses where needed |
373 | * @return string HTML fragment with link markers |
374 | */ |
375 | private function preprocessRevComment( |
376 | CommentParser $parser, |
377 | Authority $authority, |
378 | RevisionRecord $revRecord, |
379 | $samePage = false, |
380 | $isPublic = false, |
381 | $useParentheses = true |
382 | ) { |
383 | if ( $revRecord->getComment( RevisionRecord::RAW ) === null ) { |
384 | return ""; |
385 | } |
386 | if ( $revRecord->audienceCan( |
387 | RevisionRecord::DELETED_COMMENT, |
388 | $isPublic ? RevisionRecord::FOR_PUBLIC : RevisionRecord::FOR_THIS_USER, |
389 | $authority ) |
390 | ) { |
391 | $comment = $revRecord->getComment( RevisionRecord::FOR_THIS_USER, $authority ); |
392 | $block = $parser->preprocess( |
393 | $comment ? $comment->text : '', |
394 | $revRecord->getPageAsLinkTarget(), |
395 | $samePage, |
396 | null, |
397 | true |
398 | ); |
399 | $block = $this->wrapCommentWithBlock( $block, $useParentheses ); |
400 | } else { |
401 | $block = " <span class=\"comment\">" . wfMessage( 'rev-deleted-comment' )->escaped() . "</span>"; |
402 | } |
403 | if ( $revRecord->isDeleted( RevisionRecord::DELETED_COMMENT ) ) { |
404 | $class = Linker::getRevisionDeletedClass( $revRecord ); |
405 | return " <span class=\"$class comment\">$block</span>"; |
406 | } |
407 | return $block; |
408 | } |
409 | |
410 | } |