Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
57.93% |
325 / 561 |
|
37.50% |
6 / 16 |
CRAP | |
0.00% |
0 / 1 |
RevisionFormatter | |
57.93% |
325 / 561 |
|
37.50% |
6 / 16 |
2536.78 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
setIncludeHistoryProperties | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setIncludeContent | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setContentFormat | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
formatApi | |
74.81% |
98 / 131 |
|
0.00% |
0 / 1 |
23.18 | |||
serializeUserLinks | |
83.72% |
36 / 43 |
|
0.00% |
0 / 1 |
5.11 | |||
serializeUser | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
4 | |||
getDateFormats | |
20.00% |
2 / 10 |
|
0.00% |
0 / 1 |
4.05 | |||
buildActions | |
50.38% |
66 / 131 |
|
0.00% |
0 / 1 |
453.90 | |||
buildLinks | |
45.45% |
40 / 88 |
|
0.00% |
0 / 1 |
135.70 | |||
buildProperties | |
15.38% |
2 / 13 |
|
0.00% |
0 / 1 |
13.69 | |||
processParam | |
30.49% |
25 / 82 |
|
0.00% |
0 / 1 |
471.30 | |||
msg | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
decideContentFormat | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
4 | |||
decideTopicTitleContentFormat | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
7 | |||
decideNonTopicTitleContentFormat | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace Flow\Formatter; |
4 | |
5 | use ApiResult; |
6 | use ExtensionRegistry; |
7 | use Flow\Collection\PostCollection; |
8 | use Flow\Conversion\Utils; |
9 | use Flow\Exception\FlowException; |
10 | use Flow\Exception\InvalidInputException; |
11 | use Flow\Exception\PermissionException; |
12 | use Flow\Model\AbstractRevision; |
13 | use Flow\Model\Anchor; |
14 | use Flow\Model\PostRevision; |
15 | use Flow\Model\PostSummary; |
16 | use Flow\Model\UUID; |
17 | use Flow\Repository\UserNameBatch; |
18 | use Flow\RevisionActionPermissions; |
19 | use Flow\Templating; |
20 | use Flow\UrlGenerator; |
21 | use MediaWiki\Cache\GenderCache; |
22 | use MediaWiki\Context\IContextSource; |
23 | use MediaWiki\Logger\LoggerFactory; |
24 | use MediaWiki\MediaWikiServices; |
25 | use MediaWiki\Message\Message; |
26 | use MediaWiki\SpecialPage\SpecialPage; |
27 | use MediaWiki\Title\Title; |
28 | use MediaWiki\User\User; |
29 | use MediaWiki\User\UserGroupManager; |
30 | use MediaWiki\WikiMap\WikiMap; |
31 | use Wikimedia\Timestamp\TimestampException; |
32 | |
33 | /** |
34 | * This implements a serializer for converting revision objects |
35 | * into an array of localized and sanitized data ready for user |
36 | * consumption. |
37 | * |
38 | * The formatApi method is the primary method of interacting with |
39 | * this serializer. The results of formatApi can be passed on to |
40 | * html formatting or emitted directly as an api response. |
41 | * |
42 | * For performance considerations of special purpose formatters like |
43 | * CheckUser methods that build pieces of the api response are also |
44 | * public. |
45 | * |
46 | * @todo can't output as api yet, Message instances are returned |
47 | * for the various strings. |
48 | * |
49 | * @todo this needs a better name, RevisionSerializer? not sure yet |
50 | */ |
51 | class RevisionFormatter { |
52 | |
53 | /** |
54 | * @var RevisionActionPermissions |
55 | */ |
56 | protected $permissions; |
57 | |
58 | /** |
59 | * @var Templating |
60 | */ |
61 | protected $templating; |
62 | |
63 | /** |
64 | * @var UrlGenerator |
65 | */ |
66 | protected $urlGenerator; |
67 | |
68 | /** |
69 | * @var bool |
70 | */ |
71 | protected $includeProperties = false; |
72 | |
73 | /** |
74 | * @var bool |
75 | */ |
76 | protected $includeContent = true; |
77 | |
78 | /** |
79 | * @var string[] Allowed content formats |
80 | * |
81 | * See setContentFormat. |
82 | */ |
83 | protected $allowedContentFormats = [ 'html', 'wikitext', 'fixed-html', |
84 | 'topic-title-html', 'topic-title-wikitext' ]; |
85 | |
86 | /** |
87 | * @var string Default content format for revision output |
88 | */ |
89 | protected $contentFormat = 'fixed-html'; |
90 | |
91 | /** |
92 | * @var array Map from alphadecimal revision id to content format override |
93 | */ |
94 | protected $revisionContentFormat = []; |
95 | |
96 | /** |
97 | * @var int |
98 | */ |
99 | protected $maxThreadingDepth; |
100 | |
101 | /** |
102 | * @var Message[] |
103 | */ |
104 | protected $messages = []; |
105 | |
106 | /** |
107 | * @var array |
108 | */ |
109 | protected $userLinks = []; |
110 | |
111 | /** |
112 | * @var UserNameBatch |
113 | */ |
114 | protected $usernames; |
115 | |
116 | /** |
117 | * @var GenderCache |
118 | */ |
119 | protected $genderCache; |
120 | |
121 | /** |
122 | * @var UserGroupManager |
123 | */ |
124 | protected $userGroupManager; |
125 | |
126 | /** |
127 | * @param RevisionActionPermissions $permissions |
128 | * @param Templating $templating |
129 | * @param UrlGenerator $urlGenerator |
130 | * @param UserNameBatch $usernames |
131 | * @param int $maxThreadingDepth |
132 | */ |
133 | public function __construct( |
134 | RevisionActionPermissions $permissions, |
135 | Templating $templating, |
136 | UrlGenerator $urlGenerator, |
137 | UserNameBatch $usernames, |
138 | $maxThreadingDepth |
139 | ) { |
140 | $this->permissions = $permissions; |
141 | $this->templating = $templating; |
142 | $this->urlGenerator = $urlGenerator; |
143 | $this->usernames = $usernames; |
144 | $this->genderCache = MediaWikiServices::getInstance()->getGenderCache(); |
145 | $this->userGroupManager = MediaWikiServices::getInstance()->getUserGroupManager(); |
146 | $this->maxThreadingDepth = $maxThreadingDepth; |
147 | } |
148 | |
149 | /** |
150 | * The self::buildProperties method is fairly expensive and only used for rendering |
151 | * history entries. As such it is optimistically disabled unless requested |
152 | * here |
153 | * |
154 | * @param bool $shouldInclude |
155 | */ |
156 | public function setIncludeHistoryProperties( $shouldInclude ) { |
157 | $this->includeProperties = (bool)$shouldInclude; |
158 | } |
159 | |
160 | /** |
161 | * Outputing content can be somehwat expensive, as most of the content is loaded |
162 | * into DOMDocuemnts for processing of relidlinks and badimages. Set this to false |
163 | * if the content will not be used such as for recent changes. |
164 | * @param bool $shouldInclude |
165 | */ |
166 | public function setIncludeContent( $shouldInclude ) { |
167 | $this->includeContent = (bool)$shouldInclude; |
168 | } |
169 | |
170 | /** |
171 | * Sets the content format for all revisions formatted by this formatter, or a |
172 | * particular revision. |
173 | * |
174 | * @param string $format Format to use for revision content. If no revision ID is |
175 | * given, this is a default format, and the allowed formats are 'html', 'wikitext', |
176 | * and 'fixed-html'. |
177 | * |
178 | * For the default format, 'fixed-html' will be converted to 'topic-title-html' |
179 | * when formatting a topic title. 'html' and 'wikitext' will be converted to |
180 | * 'topic-title-wikitext' for topic titles (because 'html' and 'wikitext' are |
181 | * editable, and 'topic-title-html' is not editable). |
182 | * |
183 | * If a revision ID is given, the allowed formats are 'html', 'wikitext', |
184 | * 'fixed-html', 'topic-title-html', and 'topic-title-wikitext'. However, the |
185 | * format will not be converted, and must be valid for the given revision ('html', |
186 | * 'wikitext', and 'fixed-html' are valid only for non-topic titles. |
187 | * 'topic-title-html' and 'topic-title-wikitext' are only valid for topic titles. |
188 | * Otherwise, an exception will be thrown later. |
189 | * @param UUID|null $revisionId Revision ID this format applies for. |
190 | * @throws FlowException |
191 | * @throws InvalidInputException |
192 | */ |
193 | public function setContentFormat( $format, UUID $revisionId = null ) { |
194 | if ( !in_array( $format, $this->allowedContentFormats ) ) { |
195 | throw new InvalidInputException( "Unknown content format: $format" ); |
196 | } |
197 | if ( $revisionId === null ) { |
198 | // set default content format |
199 | $this->contentFormat = $format; |
200 | } else { |
201 | // set per-revision content format |
202 | $this->revisionContentFormat[$revisionId->getAlphadecimal()] = $format; |
203 | } |
204 | } |
205 | |
206 | /** |
207 | * @param FormatterRow $row |
208 | * @param IContextSource $ctx |
209 | * @param string $action action from FlowActions |
210 | * @return array|bool |
211 | * @throws FlowException |
212 | * @throws PermissionException |
213 | * @throws \Exception |
214 | * @throws \Flow\Exception\InvalidInputException |
215 | * @throws TimestampException |
216 | * @suppress PhanUndeclaredMethod Phan doesn't infer types from the instanceofs |
217 | */ |
218 | public function formatApi( FormatterRow $row, IContextSource $ctx, $action = 'view' ) { |
219 | $this->permissions->setUser( $ctx->getUser() ); |
220 | |
221 | if ( !$this->permissions->isAllowed( $row->revision, $action ) ) { |
222 | LoggerFactory::getInstance( 'Flow' )->debug( |
223 | __METHOD__ . ': Permission denied for user on action {action}', |
224 | [ |
225 | 'action' => $action, |
226 | 'revision_id' => $row->revision->getRevisionId()->getAlphadecimal(), |
227 | 'user_id' => $ctx->getUser()->getId(), |
228 | ] |
229 | ); |
230 | return false; |
231 | } |
232 | |
233 | $moderatedRevision = $this->templating->getModeratedRevision( $row->revision ); |
234 | $ts = $row->revision->getRevisionId()->getTimestampObj(); |
235 | $res = [ |
236 | ApiResult::META_BC_BOOLS => [ |
237 | 'isOriginalContent', |
238 | 'isModerated', |
239 | 'isLocked', |
240 | 'isModeratedNotLocked', |
241 | ], |
242 | 'workflowId' => $row->workflow->getId()->getAlphadecimal(), |
243 | 'articleTitle' => $row->workflow->getArticleTitle()->getPrefixedText(), |
244 | 'revisionId' => $row->revision->getRevisionId()->getAlphadecimal(), |
245 | 'timestamp' => $ts->getTimestamp( TS_MW ), |
246 | 'changeType' => $row->revision->getChangeType(), |
247 | // @todo push all date formatting to the render side? |
248 | 'dateFormats' => $this->getDateFormats( $row->revision, $ctx ), |
249 | 'properties' => $this->buildProperties( $row->workflow->getId(), $row->revision, $ctx, $row ), |
250 | 'isOriginalContent' => $row->revision->isOriginalContent(), |
251 | 'isModerated' => $moderatedRevision->isModerated(), |
252 | // These are read urls |
253 | 'links' => $this->buildLinks( $row ), |
254 | // These are write urls |
255 | 'actions' => $this->buildActions( $row ), |
256 | 'size' => [ |
257 | 'old' => $row->revision->getPreviousContentLength(), |
258 | 'new' => $row->revision->getContentLength(), |
259 | ], |
260 | 'author' => $this->serializeUser( |
261 | $row->revision->getUserWiki(), |
262 | $row->revision->getUserId(), |
263 | $row->revision->getUserIp() |
264 | ), |
265 | 'lastEditUser' => $this->serializeUser( |
266 | $row->revision->getLastContentEditUserWiki(), |
267 | $row->revision->getLastContentEditUserId(), |
268 | $row->revision->getLastContentEditUserIp() |
269 | ), |
270 | 'lastEditId' => $row->revision->isOriginalContent() |
271 | ? null : $row->revision->getLastContentEditId()->getAlphadecimal(), |
272 | 'previousRevisionId' => $row->revision->isFirstRevision() |
273 | ? null |
274 | : $row->revision->getPrevRevisionId()->getAlphadecimal(), |
275 | ]; |
276 | |
277 | if ( $res['isModerated'] ) { |
278 | $res['moderator'] = $this->serializeUser( |
279 | $moderatedRevision->getModeratedByUserWiki(), |
280 | $moderatedRevision->getModeratedByUserId(), |
281 | $moderatedRevision->getModeratedByUserIp() |
282 | ); |
283 | // @todo why moderate instead of moderated or something else? |
284 | $res['moderateState'] = $moderatedRevision->getModerationState(); |
285 | $res['moderateReason'] = [ |
286 | 'content' => $moderatedRevision->getModeratedReason(), |
287 | 'format' => 'plaintext', |
288 | ]; |
289 | $res['isLocked'] = $moderatedRevision->isLocked(); |
290 | } else { |
291 | $res['isLocked'] = false; |
292 | } |
293 | // to avoid doing this check in handlebars |
294 | $res['isModeratedNotLocked'] = $moderatedRevision->isModerated() && !$moderatedRevision->isLocked(); |
295 | |
296 | if ( $this->includeContent ) { |
297 | $contentFormat = $this->decideContentFormat( $row->revision ); |
298 | |
299 | // @todo better name? |
300 | $res['content'] = [ |
301 | 'content' => $this->templating->getContent( $row->revision, $contentFormat ), |
302 | 'format' => $contentFormat |
303 | ]; |
304 | } |
305 | |
306 | if ( $row instanceof TopicRow ) { |
307 | $res[ApiResult::META_BC_BOOLS] = array_merge( |
308 | $res[ApiResult::META_BC_BOOLS], |
309 | [ |
310 | 'isWatched', |
311 | 'watchable', |
312 | ] |
313 | ); |
314 | if ( $row->summary ) { |
315 | $summary = $this->formatApi( $row->summary, $ctx, $action ); |
316 | if ( $summary ) { |
317 | $res['summary'] = [ |
318 | 'revision' => $summary, |
319 | ]; |
320 | } |
321 | } |
322 | |
323 | // Only non-anon users can watch/unwatch a flow topic |
324 | // isWatched - the topic is watched by current user |
325 | // watchable - the user could watch the topic, eg, anon-user can't watch a topic |
326 | if ( $ctx->getUser()->isRegistered() ) { |
327 | // default topic is not watched and topic is not always watched |
328 | $res['isWatched'] = (bool)$row->isWatched; |
329 | $res['watchable'] = true; |
330 | } else { |
331 | $res['watchable'] = false; |
332 | } |
333 | } |
334 | |
335 | if ( $row->revision instanceof PostRevision ) { |
336 | $res[ApiResult::META_BC_BOOLS] = array_merge( |
337 | $res[ApiResult::META_BC_BOOLS], |
338 | [ |
339 | 'isMaxThreadingDepth', |
340 | 'isNewPage', |
341 | ] |
342 | ); |
343 | |
344 | $replyTo = $row->revision->getReplyToId(); |
345 | $res['replyToId'] = $replyTo ? $replyTo->getAlphadecimal() : null; |
346 | $res['postId'] = $row->revision->getPostId()->getAlphadecimal(); |
347 | $res['isMaxThreadingDepth'] = $row->revision->getDepth() >= $this->maxThreadingDepth; |
348 | $res['creator'] = $this->serializeUser( |
349 | $row->revision->getCreatorWiki(), |
350 | $row->revision->getCreatorId(), |
351 | $row->revision->getCreatorIp() |
352 | ); |
353 | |
354 | // Always output this along with topic titles so they |
355 | // have a safe parameter to use within l10n for content |
356 | // output. |
357 | if ( $row->revision->isTopicTitle() && !isset( $res['properties']['topic-of-post'] ) ) { |
358 | $res['properties']['topic-of-post'] = $this->processParam( |
359 | 'topic-of-post', |
360 | $row->revision, |
361 | $row->workflow->getId(), |
362 | $ctx, |
363 | $row |
364 | ); |
365 | |
366 | $res['properties']['topic-of-post-text-from-html'] = $this->processParam( |
367 | 'topic-of-post-text-from-html', |
368 | $row->revision, |
369 | $row->workflow->getId(), |
370 | $ctx, |
371 | $row |
372 | ); |
373 | |
374 | // moderated posts won't have that property |
375 | if ( isset( $res['properties']['topic-of-post-text-from-html']['plaintext'] ) ) { |
376 | $res['content']['plaintext'] = |
377 | $res['properties']['topic-of-post-text-from-html']['plaintext']; |
378 | } |
379 | } |
380 | |
381 | $res['isNewPage'] = $row->isFirstReply && $row->revision->isFirstRevision(); |
382 | |
383 | } elseif ( $row->revision instanceof PostSummary ) { |
384 | $res['creator'] = $this->serializeUser( |
385 | $row->revision->getCreatorWiki(), |
386 | $row->revision->getCreatorId(), |
387 | $row->revision->getCreatorIp() |
388 | ); |
389 | } |
390 | |
391 | return $res; |
392 | } |
393 | |
394 | /** |
395 | * @param array $userData Contains `name`, `wiki`, and `gender` keys |
396 | * @return array |
397 | */ |
398 | public function serializeUserLinks( $userData ) { |
399 | $name = $userData['name']; |
400 | if ( isset( $this->userLinks[$name] ) ) { |
401 | return $this->userLinks[$name]; |
402 | } |
403 | |
404 | $talkPageTitle = null; |
405 | $userTitle = Title::newFromText( $name, NS_USER ); |
406 | if ( $userTitle ) { |
407 | $talkPageTitle = $userTitle->getTalkPage(); |
408 | } |
409 | |
410 | $blockTitle = SpecialPage::getTitleFor( 'Block', $name ); |
411 | |
412 | $userContribsTitle = SpecialPage::getTitleFor( 'Contributions', $name ); |
413 | $userLinksBCBools = [ |
414 | '_BC_bools' => [ |
415 | 'exists', |
416 | ], |
417 | ]; |
418 | $links = [ |
419 | 'contribs' => [ |
420 | 'url' => $userContribsTitle->getLinkURL(), |
421 | 'title' => $userContribsTitle->getText(), |
422 | 'exists' => true, |
423 | ] + $userLinksBCBools, |
424 | 'userpage' => [ |
425 | 'url' => $userTitle->getLinkURL(), |
426 | 'title' => $userTitle->getText(), |
427 | 'exists' => $userTitle->isKnown(), |
428 | ] + $userLinksBCBools, |
429 | ]; |
430 | |
431 | if ( $talkPageTitle ) { |
432 | $links['talk'] = [ |
433 | 'url' => $talkPageTitle->getLinkURL(), |
434 | 'title' => $talkPageTitle->getPrefixedText(), |
435 | 'exists' => $talkPageTitle->isKnown() |
436 | ] + $userLinksBCBools; |
437 | } |
438 | // is this right permissions? typically this would |
439 | // be sourced from Linker::userToolLinks, but that |
440 | // only undertands html strings. |
441 | if ( MediaWikiServices::getInstance()->getPermissionManager() |
442 | ->userHasRight( $this->permissions->getUser(), 'block' ) |
443 | ) { |
444 | // only is the user has blocking rights |
445 | $links += [ |
446 | "block" => [ |
447 | 'url' => $blockTitle->getLinkURL(), |
448 | 'title' => wfMessage( 'blocklink' ), |
449 | 'exists' => true |
450 | ] + $userLinksBCBools, |
451 | ]; |
452 | } |
453 | |
454 | $this->userLinks[$name] = $links; |
455 | return $this->userLinks[$name]; |
456 | } |
457 | |
458 | public function serializeUser( $userWiki, $userId, $userIp ) { |
459 | $res = [ |
460 | 'name' => $this->usernames->get( $userWiki, $userId, $userIp ), |
461 | 'wiki' => $userWiki, |
462 | 'gender' => 'unknown', |
463 | 'links' => [], |
464 | 'id' => $userId |
465 | ]; |
466 | // Only works for the local wiki |
467 | if ( $res['name'] && WikiMap::getCurrentWikiId() === $userWiki ) { |
468 | $res['gender'] = $this->genderCache->getGenderOf( $res['name'], __METHOD__ ); |
469 | } |
470 | if ( $res['name'] ) { |
471 | $res['links'] = $this->serializeUserLinks( $res ); |
472 | } |
473 | |
474 | return $res; |
475 | } |
476 | |
477 | /** |
478 | * @param AbstractRevision $revision |
479 | * @param IContextSource $ctx |
480 | * @return string[] Contains [timeAndDate, date, time] |
481 | */ |
482 | public function getDateFormats( AbstractRevision $revision, IContextSource $ctx ) { |
483 | // also restricted to history |
484 | if ( $this->includeProperties === false ) { |
485 | return []; |
486 | } |
487 | |
488 | $timestamp = $revision->getRevisionId()->getTimestampObj()->getTimestamp( TS_MW ); |
489 | $user = $ctx->getUser(); |
490 | $lang = $ctx->getLanguage(); |
491 | |
492 | return [ |
493 | 'timeAndDate' => $lang->userTimeAndDate( $timestamp, $user ), |
494 | 'date' => $lang->userDate( $timestamp, $user ), |
495 | 'time' => $lang->userTime( $timestamp, $user ), |
496 | ]; |
497 | } |
498 | |
499 | /** |
500 | * @param FormatterRow $row |
501 | * @return array |
502 | * @throws FlowException |
503 | */ |
504 | public function buildActions( FormatterRow $row ) { |
505 | global $wgThanksSendToBots; |
506 | |
507 | $user = $this->permissions->getUser(); |
508 | $workflow = $row->workflow; |
509 | $title = $workflow->getArticleTitle(); |
510 | |
511 | // If a user does not have rights to perform actions on this page return |
512 | // an empty array of actions. |
513 | if ( !$workflow->userCan( 'edit', $user ) ) { |
514 | return []; |
515 | } |
516 | |
517 | $revision = $row->revision; |
518 | $action = $revision->getChangeType(); |
519 | $workflowId = $workflow->getId(); |
520 | $revId = $revision->getRevisionId(); |
521 | // @phan-suppress-next-line PhanUndeclaredMethod Checks method_exists |
522 | $postId = method_exists( $revision, 'getPostId' ) ? $revision->getPostId() : null; |
523 | $actionTypes = $this->permissions->getActions()->getValue( $action, 'actions' ); |
524 | if ( $actionTypes === null ) { |
525 | wfDebugLog( 'Flow', __METHOD__ . ": No actions defined for action: $action" ); |
526 | return []; |
527 | } |
528 | |
529 | // actions primarily vary by revision type... |
530 | $links = []; |
531 | foreach ( $actionTypes as $type ) { |
532 | if ( !$this->permissions->isAllowed( $revision, $type ) ) { |
533 | continue; |
534 | } |
535 | switch ( $type ) { |
536 | case 'thank': |
537 | $targetedUser = User::newFromId( $revision->getCreatorId() ); |
538 | if ( |
539 | // thanks extension must be available |
540 | ExtensionRegistry::getInstance()->isLoaded( 'Thanks' ) && |
541 | // anons can't give a thank |
542 | $user->isRegistered() && |
543 | // can only thank for PostRevisions |
544 | // (other revision objects have no getCreator* methods) |
545 | $revision instanceof PostRevision && |
546 | // only thank a logged in user |
547 | $targetedUser->isRegistered() && |
548 | // can't thank self |
549 | $user->getId() !== $revision->getCreatorId() && |
550 | // can't thank bots |
551 | !( !$wgThanksSendToBots && in_array( 'bot', $this->userGroupManager->getUserGroups( $targetedUser ) ) ) |
552 | ) { |
553 | $links['thank'] = $this->urlGenerator->thankAction( $postId ); |
554 | } |
555 | break; |
556 | |
557 | case 'reply': |
558 | if ( !$postId ) { |
559 | throw new FlowException( "$type called without \$postId" ); |
560 | } elseif ( !$revision instanceof PostRevision ) { |
561 | throw new FlowException( "$type called without PostRevision object" ); |
562 | } |
563 | |
564 | /* |
565 | * If the post being replied to is the most recent post |
566 | * of its depth, the reply link should point to parent |
567 | */ |
568 | $replyToId = $postId; |
569 | $replyToRevision = $revision; |
570 | if ( $row->isLastReply ) { |
571 | $replyToId = $replyToRevision->getReplyToId(); |
572 | $replyToRevision = PostCollection::newFromId( $replyToId )->getLastRevision(); |
573 | } |
574 | |
575 | /* |
576 | * If the post being replied to is at or exceeds the max |
577 | * threading depth, the reply link should point to parent. |
578 | */ |
579 | while ( $replyToRevision->getDepth() >= $this->maxThreadingDepth ) { |
580 | $replyToId = $replyToRevision->getReplyToId(); |
581 | $replyToRevision = PostCollection::newFromId( $replyToId )->getLastRevision(); |
582 | } |
583 | |
584 | $links['reply'] = $this->urlGenerator->replyAction( |
585 | $title, |
586 | $workflowId, |
587 | $replyToId, |
588 | $revision->isTopicTitle() |
589 | ); |
590 | break; |
591 | |
592 | case 'edit-header': |
593 | $links['edit'] = $this->urlGenerator->editHeaderAction( $title, $workflowId, $revId ); |
594 | break; |
595 | |
596 | case 'edit-title': |
597 | if ( !$postId ) { |
598 | throw new FlowException( "$type called without \$postId" ); |
599 | } |
600 | $links['edit'] = $this->urlGenerator |
601 | ->editTitleAction( $title, $workflowId, $postId, $revId ); |
602 | break; |
603 | |
604 | case 'edit-post': |
605 | if ( !$postId ) { |
606 | throw new FlowException( "$type called without \$postId" ); |
607 | } |
608 | $links['edit'] = $this->urlGenerator |
609 | ->editPostAction( $title, $workflowId, $postId, $revId ); |
610 | break; |
611 | |
612 | case 'undo-edit-header': |
613 | case 'undo-edit-post': |
614 | case 'undo-edit-topic-summary': |
615 | if ( !$revision->isFirstRevision() ) { |
616 | $links['undo'] = $this->urlGenerator->undoAction( $revision, $title, $workflowId ); |
617 | } |
618 | break; |
619 | |
620 | case 'hide-post': |
621 | if ( !$postId ) { |
622 | throw new FlowException( "$type called without \$postId" ); |
623 | } |
624 | $links['hide'] = $this->urlGenerator->hidePostAction( $title, $workflowId, $postId ); |
625 | break; |
626 | |
627 | case 'delete-topic': |
628 | $links['delete'] = $this->urlGenerator->deleteTopicAction( $title, $workflowId ); |
629 | break; |
630 | |
631 | case 'delete-post': |
632 | if ( !$postId ) { |
633 | throw new FlowException( "$type called without \$postId" ); |
634 | } |
635 | $links['delete'] = $this->urlGenerator->deletePostAction( $title, $workflowId, $postId ); |
636 | break; |
637 | |
638 | case 'suppress-topic': |
639 | $links['suppress'] = $this->urlGenerator->suppressTopicAction( $title, $workflowId ); |
640 | break; |
641 | |
642 | case 'suppress-post': |
643 | if ( !$postId ) { |
644 | throw new FlowException( "$type called without \$postId" ); |
645 | } |
646 | $links['suppress'] = $this->urlGenerator->suppressPostAction( $title, $workflowId, $postId ); |
647 | break; |
648 | |
649 | case 'lock-topic': |
650 | // lock topic link is only available to topics |
651 | if ( !$revision instanceof PostRevision || !$revision->isTopicTitle() ) { |
652 | break; |
653 | } |
654 | |
655 | $links['lock'] = $this->urlGenerator->lockTopicAction( $title, $workflowId ); |
656 | break; |
657 | |
658 | case 'restore-topic': |
659 | $moderateAction = $flowAction = null; |
660 | switch ( $revision->getModerationState() ) { |
661 | case AbstractRevision::MODERATED_LOCKED: |
662 | $moderateAction = 'unlock'; |
663 | $flowAction = 'lock-topic'; |
664 | break; |
665 | case AbstractRevision::MODERATED_HIDDEN: |
666 | case AbstractRevision::MODERATED_DELETED: |
667 | case AbstractRevision::MODERATED_SUPPRESSED: |
668 | $moderateAction = 'un' . $revision->getModerationState(); |
669 | $flowAction = 'moderate-topic'; |
670 | break; |
671 | } |
672 | if ( $moderateAction && $flowAction ) { |
673 | $links[$moderateAction] = $this->urlGenerator->restoreTopicAction( |
674 | $title, $workflowId, $moderateAction, $flowAction ); |
675 | } |
676 | break; |
677 | |
678 | case 'restore-post': |
679 | if ( !$postId ) { |
680 | throw new FlowException( "$type called without \$postId" ); |
681 | } |
682 | $moderateAction = $flowAction = null; |
683 | switch ( $revision->getModerationState() ) { |
684 | case AbstractRevision::MODERATED_HIDDEN: |
685 | case AbstractRevision::MODERATED_DELETED: |
686 | case AbstractRevision::MODERATED_SUPPRESSED: |
687 | $moderateAction = 'un' . $revision->getModerationState(); |
688 | $flowAction = 'moderate-post'; |
689 | break; |
690 | } |
691 | if ( $moderateAction && $flowAction ) { |
692 | $links[$moderateAction] = $this->urlGenerator->restorePostAction( |
693 | $title, $workflowId, $postId, $moderateAction, $flowAction ); |
694 | } |
695 | break; |
696 | |
697 | case 'hide-topic': |
698 | $links['hide'] = $this->urlGenerator->hideTopicAction( $title, $workflowId ); |
699 | break; |
700 | |
701 | // Need to use 'edit-topic-summary' to match FlowActions |
702 | case 'edit-topic-summary': |
703 | // summarize link is only available to topic workflow |
704 | if ( !in_array( $workflow->getType(), [ 'topic', 'topicsummary' ] ) ) { |
705 | break; |
706 | } |
707 | $links['summarize'] = $this->urlGenerator->editTopicSummaryAction( $title, $workflowId ); |
708 | break; |
709 | |
710 | default: |
711 | wfDebugLog( 'Flow', __METHOD__ . ': unkown action link type: ' . $type ); |
712 | break; |
713 | } |
714 | } |
715 | |
716 | return $links; |
717 | } |
718 | |
719 | /** |
720 | * @param FormatterRow $row |
721 | * @return Anchor[] |
722 | * @throws FlowException |
723 | */ |
724 | public function buildLinks( FormatterRow $row ) { |
725 | $workflow = $row->workflow; |
726 | $revision = $row->revision; |
727 | $title = $workflow->getArticleTitle(); |
728 | $action = $revision->getChangeType(); |
729 | $workflowId = $workflow->getId(); |
730 | $revId = $revision->getRevisionId(); |
731 | // @phan-suppress-next-line PhanUndeclaredMethod Checks method_exists |
732 | $postId = method_exists( $revision, 'getPostId' ) ? $revision->getPostId() : null; |
733 | |
734 | $linkTypes = $this->permissions->getActions()->getValue( $action, 'links' ); |
735 | if ( $linkTypes === null ) { |
736 | wfDebugLog( 'Flow', __METHOD__ . ": No links defined for action: $action" ); |
737 | return []; |
738 | } |
739 | |
740 | $links = []; |
741 | $diffCallback = null; |
742 | foreach ( $linkTypes as $type ) { |
743 | switch ( $type ) { |
744 | case 'watch-topic': |
745 | $links['watch-topic'] = $this->urlGenerator->watchTopicLink( $title, $workflowId ); |
746 | break; |
747 | |
748 | case 'unwatch-topic': |
749 | $links['unwatch-topic'] = $this->urlGenerator->unwatchTopicLink( $title, $workflowId ); |
750 | break; |
751 | |
752 | case 'topic': |
753 | $links['topic'] = $this->urlGenerator->topicLink( $title, $workflowId ); |
754 | break; |
755 | |
756 | case 'post': |
757 | if ( !$postId ) { |
758 | wfDebugLog( 'Flow', __METHOD__ . ': No postId available to render post link' ); |
759 | break; |
760 | } |
761 | $links['post'] = $this->urlGenerator->postLink( $title, $workflowId, $postId ); |
762 | break; |
763 | |
764 | case 'header-revision': |
765 | $links['header-revision'] = $this->urlGenerator |
766 | ->headerRevisionLink( $title, $workflowId, $revId ); |
767 | break; |
768 | |
769 | case 'topic-revision': |
770 | if ( !$postId ) { |
771 | wfDebugLog( 'Flow', __METHOD__ . ': No postId available to render revision link' ); |
772 | break; |
773 | } |
774 | |
775 | $links['topic-revision'] = $this->urlGenerator |
776 | ->topicRevisionLink( $title, $workflowId, $revId ); |
777 | break; |
778 | |
779 | case 'post-revision': |
780 | if ( !$postId ) { |
781 | wfDebugLog( 'Flow', __METHOD__ . ': No postId available to render revision link' ); |
782 | break; |
783 | } |
784 | |
785 | $links['post-revision'] = $this->urlGenerator |
786 | ->postRevisionLink( $title, $workflowId, $postId, $revId ); |
787 | break; |
788 | |
789 | case 'summary-revision': |
790 | $links['summary-revision'] = $this->urlGenerator |
791 | ->summaryRevisionLink( $title, $workflowId, $revId ); |
792 | break; |
793 | |
794 | case 'post-history': |
795 | if ( !$postId ) { |
796 | wfDebugLog( 'Flow', __METHOD__ . ': No postId available to render post-history link' ); |
797 | break; |
798 | } |
799 | $links['post-history'] = $this->urlGenerator->postHistoryLink( $title, $workflowId, $postId ); |
800 | break; |
801 | |
802 | case 'topic-history': |
803 | $links['topic-history'] = $this->urlGenerator->workflowHistoryLink( $title, $workflowId ); |
804 | break; |
805 | |
806 | case 'board-history': |
807 | $links['board-history'] = $this->urlGenerator->boardHistoryLink( $title ); |
808 | break; |
809 | |
810 | case 'diff-header': |
811 | $diffCallback ??= [ $this->urlGenerator, 'diffHeaderLink' ]; |
812 | // don't break, diff links are rendered below |
813 | case 'diff-post': |
814 | $diffCallback ??= [ $this->urlGenerator, 'diffPostLink' ]; |
815 | // don't break, diff links are rendered below |
816 | case 'diff-post-summary': |
817 | $diffCallback ??= [ $this->urlGenerator, 'diffSummaryLink' ]; |
818 | |
819 | /* |
820 | * To diff against previous revision, we don't really need that |
821 | * revision id; if no particular diff id is specified, it will |
822 | * assume a diff against previous revision. However, we do want |
823 | * to make sure that a previous revision actually exists to diff |
824 | * against. This could result in a network request (fetching the |
825 | * current revision), but it's likely being loaded anyways. |
826 | */ |
827 | if ( $revision->getPrevRevisionId() !== null ) { |
828 | $links['diff'] = $diffCallback( $title, $workflowId, $revId ); |
829 | |
830 | /* |
831 | * Different formatters have different terminology for the link |
832 | * that diffs a certain revision to the previous revision. |
833 | * |
834 | * E.g.: Special:Contributions has "diff" ($links['diff']), |
835 | * ?action=history has "prev" ($links['prev']). |
836 | */ |
837 | $links['diff-prev'] = clone $links['diff']; |
838 | $lastMsg = new Message( 'last' ); |
839 | $links['diff-prev']->setTitleMessage( $lastMsg ); |
840 | $links['diff-prev']->setMessage( $lastMsg ); |
841 | } |
842 | |
843 | /* |
844 | * To diff against the current revision, we need to know the id |
845 | * of this last revision. This could be an additional network |
846 | * request, though anything using formatter likely already needs |
847 | * to request the most current revision (e.g. to check |
848 | * permissions) so we should be able to get it from local cache. |
849 | */ |
850 | $cur = $row->currentRevision; |
851 | if ( !$revId->equals( $cur->getRevisionId() ) ) { |
852 | $links['diff-cur'] = $diffCallback( $title, $workflowId, $cur->getRevisionId(), $revId ); |
853 | $curMsg = new Message( 'cur' ); |
854 | $links['diff-cur']->setTitleMessage( $curMsg ); |
855 | $links['diff-cur']->setMessage( $curMsg ); |
856 | } |
857 | break; |
858 | |
859 | case 'workflow': |
860 | $links['workflow'] = $this->urlGenerator->workflowLink( $title, $workflowId ); |
861 | break; |
862 | |
863 | default: |
864 | wfDebugLog( 'Flow', __METHOD__ . ': unkown action link type: ' . $type ); |
865 | break; |
866 | } |
867 | } |
868 | |
869 | return $links; |
870 | } |
871 | |
872 | /** |
873 | * Build api properties defined in FlowActions for this change type |
874 | * |
875 | * This is a fairly expensive function(compared to the other methods in this class). |
876 | * As such its only output when specifically requested |
877 | * |
878 | * @param UUID $workflowId |
879 | * @param AbstractRevision $revision |
880 | * @param IContextSource $ctx |
881 | * @param FormatterRow|null $row |
882 | * @return array |
883 | */ |
884 | public function buildProperties( |
885 | UUID $workflowId, |
886 | AbstractRevision $revision, |
887 | IContextSource $ctx, |
888 | FormatterRow $row = null |
889 | ) { |
890 | if ( $this->includeProperties === false ) { |
891 | return []; |
892 | } |
893 | |
894 | $changeType = $revision->getChangeType(); |
895 | $actions = $this->permissions->getActions(); |
896 | $params = $actions->getValue( $changeType, 'history', 'i18n-params' ); |
897 | if ( !$params ) { |
898 | // should we have a sigil for i18n with no parameters? |
899 | wfDebugLog( 'Flow', __METHOD__ . ": No i18n params for changeType $changeType on " . |
900 | $revision->getRevisionId()->getAlphadecimal() ); |
901 | return []; |
902 | } |
903 | |
904 | $res = [ '_key' => $actions->getValue( $changeType, 'history', 'i18n-message' ) ]; |
905 | foreach ( $params as $param ) { |
906 | $res[$param] = $this->processParam( $param, $revision, $workflowId, $ctx, $row ); |
907 | } |
908 | |
909 | return $res; |
910 | } |
911 | |
912 | /** |
913 | * Mimic Echo parameter formatting |
914 | * |
915 | * @param string $param The requested i18n parameter |
916 | * @param AbstractRevision|AbstractRevision[] $revision The revision or |
917 | * revisions to format or an array of revisions |
918 | * @param UUID $workflowId The UUID of the workflow $revision belongs tow |
919 | * @param IContextSource $ctx |
920 | * @param FormatterRow|null $row |
921 | * @return mixed A valid parameter for a core Message instance. These |
922 | * parameters will be used with Message::parse |
923 | * @throws FlowException |
924 | */ |
925 | public function processParam( |
926 | $param, |
927 | $revision, |
928 | UUID $workflowId, |
929 | IContextSource $ctx, |
930 | FormatterRow $row = null |
931 | ) { |
932 | $isWikiText = str_ends_with( $param, 'wikitext' ); |
933 | $format = $isWikiText ? $revision->getWikitextFormat() : $revision->getHtmlFormat(); |
934 | |
935 | switch ( $param ) { |
936 | case 'creator-text': |
937 | if ( $revision instanceof PostRevision ) { |
938 | return $this->usernames->getFromTuple( $revision->getCreatorTuple() ); |
939 | } else { |
940 | return ''; |
941 | } |
942 | |
943 | case 'user-text': |
944 | return $this->usernames->getFromTuple( $revision->getUserTuple() ); |
945 | |
946 | case 'user-links': |
947 | return Message::rawParam( $this->templating->getUserLinks( $revision ) ); |
948 | |
949 | case 'summary': |
950 | if ( !$this->permissions->isAllowed( $revision, 'view' ) ) { |
951 | return ''; |
952 | } |
953 | |
954 | /* |
955 | * Fetch in HTML; unparsed wikitext in summary is pointless. |
956 | * Larger-scale wikis will likely also store content in html, so no |
957 | * Parsoid roundtrip is needed then (and if it *is*, it'll already |
958 | * be needed to render Flow discussions, so this is manageable) |
959 | */ |
960 | $content = $this->templating->getContent( $revision, 'fixed-html' ); |
961 | // strip html tags and decode to plaintext |
962 | $content = Utils::htmlToPlaintext( $content, 140, $ctx->getLanguage() ); |
963 | return Message::plaintextParam( $content ); |
964 | |
965 | case 'wikitext': |
966 | case 'plaintext': |
967 | if ( !$this->permissions->isAllowed( $revision, 'view' ) ) { |
968 | return ''; |
969 | } |
970 | |
971 | $content = $this->templating->getContent( $revision, $format ); |
972 | if ( !$isWikiText ) { |
973 | $content = Utils::htmlToPlaintext( $content ); |
974 | } |
975 | // This must be escaped and marked raw to prevent special chars in |
976 | // content, like $1, from changing the i18n result |
977 | return Message::plaintextParam( $content ); |
978 | |
979 | // This is potentially two networked round trips, much too expensive for |
980 | // the rendering loop |
981 | case 'prev-wikitext': |
982 | case 'prev-plaintext': |
983 | if ( $revision->isFirstRevision() ) { |
984 | return ''; |
985 | } |
986 | if ( $row === null ) { |
987 | $previousRevision = $revision->getCollection()->getPrevRevision( $revision ); |
988 | } else { |
989 | $previousRevision = $row->previousRevision; |
990 | } |
991 | if ( !$previousRevision ) { |
992 | return ''; |
993 | } |
994 | if ( !$this->permissions->isAllowed( $previousRevision, 'view' ) ) { |
995 | return ''; |
996 | } |
997 | |
998 | $content = $this->templating->getContent( $previousRevision, $format ); |
999 | if ( !$isWikiText ) { |
1000 | $content = Utils::htmlToPlaintext( $content ); |
1001 | } |
1002 | return Message::plaintextParam( $content ); |
1003 | |
1004 | case 'workflow-url': |
1005 | return $this->urlGenerator |
1006 | ->workflowLink( null, $workflowId ) |
1007 | ->getFullURL(); |
1008 | |
1009 | case 'post-url': |
1010 | if ( !$revision instanceof PostRevision ) { |
1011 | throw new FlowException( 'Expected PostRevision but received' . get_class( $revision ) ); |
1012 | } |
1013 | return $this->urlGenerator |
1014 | ->postLink( null, $workflowId, $revision->getPostId() ) |
1015 | ->getFullURL(); |
1016 | |
1017 | case 'moderated-reason': |
1018 | // don't parse wikitext in the moderation reason |
1019 | return Message::plaintextParam( $revision->getModeratedReason() ?? '' ); |
1020 | |
1021 | case 'topic-of-post': |
1022 | if ( !$revision instanceof PostRevision ) { |
1023 | throw new FlowException( 'Expected PostRevision but received ' . get_class( $revision ) ); |
1024 | } |
1025 | |
1026 | $root = $revision->getRootPost(); |
1027 | if ( !$this->permissions->isAllowed( $root, 'view-topic-title' ) ) { |
1028 | return ''; |
1029 | } |
1030 | |
1031 | $content = $this->templating->getContent( $root, 'topic-title-wikitext' ); |
1032 | |
1033 | // TODO: We need to use plaintextParam or similar to avoid parsing, |
1034 | // but the API output says "plaintext", which is confusing and |
1035 | // should be fixed. From the API consumer's perspective, it's |
1036 | // topic-title-wikitext. |
1037 | return Message::plaintextParam( $content ); |
1038 | |
1039 | // Strip the tags from the HTML version to produce text: |
1040 | // [[Red link 3]], [[Adrines]], [[Media:Earth.jpg]], http://example.com => |
1041 | // Red link 3, Adrines, Media:Earth.jpg, http://example.com |
1042 | case 'topic-of-post-text-from-html': |
1043 | if ( !$revision instanceof PostRevision ) { |
1044 | throw new FlowException( 'Expected PostRevision but received ' . get_class( $revision ) ); |
1045 | } |
1046 | |
1047 | $root = $revision->getRootPost(); |
1048 | if ( !$this->permissions->isAllowed( $root, 'view-topic-title' ) ) { |
1049 | return ''; |
1050 | } |
1051 | |
1052 | $content = $this->templating->getContent( $root, 'topic-title-plaintext' ); |
1053 | |
1054 | return Message::plaintextParam( $content ); |
1055 | |
1056 | case 'post-of-summary': |
1057 | if ( !$revision instanceof PostSummary ) { |
1058 | throw new FlowException( 'Expected PostSummary but received ' . get_class( $revision ) ); |
1059 | } |
1060 | |
1061 | /** @var PostRevision $post */ |
1062 | $post = $revision->getCollection()->getPost()->getLastRevision(); |
1063 | // @phan-suppress-next-line PhanUndeclaredMethod Type not correctly inferred |
1064 | $permissionAction = $post->isTopicTitle() ? 'view-topic-title' : 'view'; |
1065 | if ( !$this->permissions->isAllowed( $post, $permissionAction ) ) { |
1066 | return ''; |
1067 | } |
1068 | |
1069 | // @phan-suppress-next-line PhanUndeclaredMethod Type not correctly inferred |
1070 | if ( $post->isTopicTitle() ) { |
1071 | return Message::plaintextParam( $this->templating->getContent( |
1072 | $post, 'topic-title-plaintext' ) ); |
1073 | } else { |
1074 | return Message::rawParam( $this->templating->getContent( $post, 'fixed-html' ) ); |
1075 | } |
1076 | |
1077 | case 'bundle-count': |
1078 | return Message::numParam( count( $revision ) ); |
1079 | |
1080 | default: |
1081 | wfWarn( __METHOD__ . ': Unknown formatter parameter: ' . $param ); |
1082 | return ''; |
1083 | } |
1084 | } |
1085 | |
1086 | protected function msg( $key, ...$params ) { |
1087 | if ( $params ) { |
1088 | return wfMessage( $key, ...$params ); |
1089 | } |
1090 | if ( !isset( $this->messages[$key] ) ) { |
1091 | $this->messages[$key] = new Message( $key ); |
1092 | } |
1093 | return $this->messages[$key]; |
1094 | } |
1095 | |
1096 | /** |
1097 | * Determines the exact output content format, given the requested content format |
1098 | * and the revision type. |
1099 | * |
1100 | * @param AbstractRevision $revision |
1101 | * @return string Content format |
1102 | * @throws FlowException If a per-revision format was given and it is |
1103 | * invalid for the revision type (topic title/non-topic title). |
1104 | */ |
1105 | public function decideContentFormat( AbstractRevision $revision ) { |
1106 | $requestedRevFormat = null; |
1107 | $requestedDefaultFormat = null; |
1108 | |
1109 | $alpha = $revision->getRevisionId()->getAlphadecimal(); |
1110 | if ( isset( $this->revisionContentFormat[$alpha] ) ) { |
1111 | $requestedRevFormat = $this->revisionContentFormat[$alpha]; |
1112 | } else { |
1113 | $requestedDefaultFormat = $this->contentFormat; |
1114 | } |
1115 | |
1116 | if ( $revision instanceof PostRevision && $revision->isTopicTitle() ) { |
1117 | return $this->decideTopicTitleContentFormat( |
1118 | $revision, $requestedRevFormat, $requestedDefaultFormat ); |
1119 | } else { |
1120 | return $this->decideNonTopicTitleContentFormat( |
1121 | $revision, $requestedRevFormat, $requestedDefaultFormat ); |
1122 | } |
1123 | } |
1124 | |
1125 | /** |
1126 | * Decide the content format for a topic title |
1127 | * |
1128 | * @param PostRevision $topicTitle Topic title revision |
1129 | * @param string|null $requestedRevFormat Format requested for this specific revision |
1130 | * @param string|null $requestedDefaultFormat Default format requested |
1131 | * @return string |
1132 | * @throws FlowException If a per-revision format was given and it is |
1133 | * invalid for topic titles. |
1134 | */ |
1135 | protected function decideTopicTitleContentFormat( |
1136 | PostRevision $topicTitle, |
1137 | $requestedRevFormat, |
1138 | $requestedDefaultFormat |
1139 | ) { |
1140 | if ( $requestedRevFormat !== null ) { |
1141 | if ( $requestedRevFormat !== 'topic-title-html' && |
1142 | $requestedRevFormat !== 'topic-title-wikitext' |
1143 | ) { |
1144 | throw new FlowException( 'Per-revision format for a topic title must be ' . |
1145 | '\'topic-title-html\' or \'topic-title-wikitext\'' ); |
1146 | } |
1147 | return $requestedRevFormat; |
1148 | } else { |
1149 | // Since this is a default format, we'll canonicalize it. |
1150 | |
1151 | // Because these are both editable formats, and this is the only |
1152 | // editable topic title format. |
1153 | if ( $requestedDefaultFormat === 'topic-title-wikitext' || $requestedDefaultFormat === 'html' || |
1154 | $requestedDefaultFormat === 'wikitext' |
1155 | ) { |
1156 | return 'topic-title-wikitext'; |
1157 | } else { |
1158 | return 'topic-title-html'; |
1159 | } |
1160 | } |
1161 | } |
1162 | |
1163 | /** |
1164 | * Decide the content format for revisions other than topic titles |
1165 | * |
1166 | * @param AbstractRevision $revision Revision to decide format for |
1167 | * @param string|null $requestedRevFormat Format requested for this specific revision |
1168 | * @param string|null $requestedDefaultFormat Default format requested |
1169 | * @return string |
1170 | * @throws FlowException If a per-revision format was given and it is |
1171 | * invalid for this type |
1172 | */ |
1173 | protected function decideNonTopicTitleContentFormat( |
1174 | AbstractRevision $revision, |
1175 | $requestedRevFormat, |
1176 | $requestedDefaultFormat |
1177 | ) { |
1178 | if ( $requestedRevFormat !== null ) { |
1179 | if ( $requestedRevFormat === 'topic-title-html' || |
1180 | $requestedRevFormat === 'topic-title-wikitext' |
1181 | ) { |
1182 | throw new FlowException( 'Invalid per-revision format. Only topic titles can use ' . |
1183 | '\'topic-title-html\' and \'topic-title-wikitext\'' ); |
1184 | } |
1185 | return $requestedRevFormat; |
1186 | } else { |
1187 | if ( $requestedDefaultFormat === 'topic-title-html' || |
1188 | $requestedDefaultFormat === 'topic-title-wikitext' |
1189 | ) { |
1190 | throw new FlowException( 'Default format of \'topic-title-html\' or ' . |
1191 | '\'topic-title-wikitext\' can only be used to format topic titles.' ); |
1192 | } |
1193 | |
1194 | return $requestedDefaultFormat; |
1195 | } |
1196 | } |
1197 | } |