Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
6.93% covered (danger)
6.93%
23 / 332
9.30% covered (danger)
9.30%
4 / 43
CRAP
0.00% covered (danger)
0.00%
0 / 1
UrlGenerator
6.93% covered (danger)
6.93%
23 / 332
9.30% covered (danger)
9.30%
4 / 43
2770.17
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 resolveTitle
18.18% covered (danger)
18.18%
2 / 11
0.00% covered (danger)
0.00%
0 / 1
12.76
 newTopicLink
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 editHeaderLink
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 headerRevisionLink
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 topicRevisionLink
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 postRevisionLink
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 summaryRevisionLink
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 topicLink
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 postLink
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 postHistoryLink
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 workflowHistoryLink
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 boardHistoryLink
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 undoAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 undoEditPostAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 undoEditHeaderAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 undoEditSummaryAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 diffLink
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 diffHeaderLink
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 diffPostLink
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 diffSummaryLink
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 workflowLink
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 watchTopicLink
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 unwatchTopicLink
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 boardLink
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 replyAction
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 editTopicSummaryAction
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 lockTopicAction
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 restoreTopicAction
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 restorePostAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 createHeaderAction
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 editHeaderAction
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 editTitleAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 editPostAction
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
2
 hideTopicAction
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 hidePostAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 deleteTopicAction
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 deletePostAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 suppressTopicAction
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 suppressPostAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 newTopicAction
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 thankAction
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 markRevisionPatrolledAction
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace Flow;
4
5use Flow\Collection\PostCollection;
6use Flow\Data\Mapper\CachingObjectMapper;
7use Flow\Exception\FlowException;
8use Flow\Exception\InvalidInputException;
9use Flow\Model\AbstractRevision;
10use Flow\Model\Anchor;
11use Flow\Model\Header;
12use Flow\Model\PostRevision;
13use Flow\Model\PostSummary;
14use Flow\Model\UUID;
15use MediaWiki\Context\RequestContext;
16use MediaWiki\SpecialPage\SpecialPage;
17use MediaWiki\Title\Title;
18use RecentChange;
19
20/**
21 * Provides url generation capabilities for Flow. Ties together an
22 * i18n message with a specific Title, query parameters and fragment.
23 *
24 * URL generation methods mostly accept either a Title or a UUID
25 * representing the Workflow. URL generation methods all return
26 * Anchor instances..
27 */
28class UrlGenerator {
29    /**
30     * @var CachingObjectMapper
31     */
32    private $workflowMapper;
33
34    public function __construct( CachingObjectMapper $workflowMapper ) {
35        $this->workflowMapper = $workflowMapper;
36    }
37
38    /**
39     * @param Title|null $title
40     * @param UUID|null $workflowId
41     * @return Title
42     * @throws FlowException
43     */
44    protected function resolveTitle( ?Title $title = null, ?UUID $workflowId = null ) {
45        if ( $title !== null ) {
46            return $title;
47        }
48        if ( $workflowId === null ) {
49            throw new FlowException( 'No title or workflow given' );
50        }
51
52        $alpha = $workflowId->getAlphadecimal();
53        $workflow = $this->workflowMapper->get( [
54            'workflow_id' => $alpha,
55        ] );
56        if ( $workflow === null ) {
57            throw new InvalidInputException( 'Unloaded workflow:' . $alpha, 'invalid-workflow' );
58        }
59        return $workflow->getArticleTitle();
60    }
61
62    /**
63     * Link to create new topic on a topiclist.
64     *
65     * @param Title|null $title
66     * @param UUID|null $workflowId
67     * @return Anchor
68     */
69    public function newTopicLink( ?Title $title = null, ?UUID $workflowId = null ) {
70        return new Anchor(
71            wfMessage( 'flow-topic-action-new' ),
72            $this->resolveTitle( $title, $workflowId ),
73            [ 'action' => 'new-topic' ]
74        );
75    }
76
77    /**
78     * Edit the header at the specified workflow.
79     *
80     * @param Title|null $title
81     * @param UUID $workflowId
82     * @return Anchor
83     */
84    public function editHeaderLink( ?Title $title, UUID $workflowId ) {
85        return new Anchor(
86            wfMessage( 'flow-edit-header' ),
87            $this->resolveTitle( $title, $workflowId ),
88            [ 'action' => 'edit-header' ]
89        );
90    }
91
92    /**
93     * View a specific revision of a header workflow.
94     *
95     * @param Title|null $title
96     * @param UUID $workflowId
97     * @param UUID $revId
98     * @return Anchor
99     */
100    public function headerRevisionLink( ?Title $title, UUID $workflowId, UUID $revId ) {
101        return new Anchor(
102            wfMessage( 'flow-link-header-revision' ),
103            $this->resolveTitle( $title, $workflowId ),
104            [
105                'header_revId' => $revId->getAlphadecimal(),
106                'action' => 'view-header'
107            ]
108        );
109    }
110
111    /**
112     * View a specific revision of a topic title
113     *
114     * @param Title|null $title
115     * @param UUID $workflowId
116     * @param UUID $revId
117     * @return Anchor
118     */
119    public function topicRevisionLink( ?Title $title, UUID $workflowId, UUID $revId ) {
120        return new Anchor(
121            wfMessage( 'flow-link-topic-revision' ),
122            $this->resolveTitle( $title, $workflowId ),
123            [
124                'topic_revId' => $revId->getAlphadecimal(),
125                'action' => 'single-view'
126            ]
127        );
128    }
129
130    /**
131     * View a specific revision of a post within a topic workflow.
132     *
133     * @param Title|null $title
134     * @param UUID $workflowId
135     * @param UUID $postId
136     * @param UUID $revId
137     * @return Anchor
138     */
139    public function postRevisionLink(
140        ?Title $title,
141        UUID $workflowId,
142        UUID $postId,
143        UUID $revId
144    ) {
145        return new Anchor(
146            wfMessage( 'flow-link-post-revision' ),
147            $this->resolveTitle( $title, $workflowId ),
148            [
149                'topic_postId' => $postId->getAlphadecimal(),
150                'topic_revId' => $revId->getAlphadecimal(),
151                'action' => 'single-view'
152            ]
153        );
154    }
155
156    /**
157     * View a specific revision of topic summary.
158     *
159     * @param Title|null $title
160     * @param UUID $workflowId
161     * @param UUID $revId
162     * @return Anchor
163     */
164    public function summaryRevisionLink( ?Title $title, UUID $workflowId, UUID $revId ) {
165        return new Anchor(
166            wfMessage( 'flow-link-summary-revision' ),
167            $this->resolveTitle( $title, $workflowId ),
168            [
169                'topicsummary_revId' => $revId->getAlphadecimal(),
170                'action' => 'view-topic-summary'
171            ]
172        );
173    }
174
175    /**
176     * View the topic at the specified workflow.
177     *
178     * @param Title|null $title
179     * @param UUID $workflowId
180     * @return Anchor
181     */
182    public function topicLink( ?Title $title, UUID $workflowId ) {
183        return new Anchor(
184            wfMessage( 'flow-link-topic' ),
185            $this->resolveTitle( $title, $workflowId )
186        );
187    }
188
189    /**
190     * View a topic scrolled down to the provided post at the
191     * specified workflow.
192     *
193     * @param Title|null $title
194     * @param UUID $workflowId
195     * @param UUID $postId
196     * @return Anchor
197     */
198    public function postLink( ?Title $title, UUID $workflowId, UUID $postId ) {
199        return new Anchor(
200            wfMessage( 'flow-link-post' ),
201            $this->resolveTitle( $title, $workflowId ),
202            [
203                // If the post is moderated this will flag the backend to still
204                // include the content in the html response.
205                'topic_showPostId' => $postId->getAlphadecimal()
206            ],
207            '#flow-post-' . $postId->getAlphadecimal()
208        );
209    }
210
211    /**
212     * Show the history of a specific post within a topic workflow
213     *
214     * @param Title|null $title
215     * @param UUID $workflowId
216     * @param UUID $postId
217     * @return Anchor
218     */
219    public function postHistoryLink( ?Title $title, UUID $workflowId, UUID $postId ) {
220        return new Anchor(
221            wfMessage( 'flow-post-action-post-history' ),
222            $this->resolveTitle( $title, $workflowId ),
223            [
224                'action' => 'history',
225                'topic_postId' => $postId->getAlphadecimal(),
226            ]
227        );
228    }
229
230    /**
231     * Show the history of a workflow.
232     *
233     * @param Title|null $title
234     * @param UUID $workflowId
235     * @return Anchor
236     */
237    public function workflowHistoryLink( ?Title $title, UUID $workflowId ) {
238        return new Anchor(
239            wfMessage( 'flow-topic-action-history' ),
240            $this->resolveTitle( $title, $workflowId ),
241            [ 'action' => 'history' ]
242        );
243    }
244
245    /**
246     * Show the history of a flow board.
247     *
248     * @param Title $title
249     * @return Anchor
250     */
251    public function boardHistoryLink( Title $title ) {
252        return new Anchor(
253            wfMessage( 'hist' ),
254            $title,
255            [ 'action' => 'history' ]
256        );
257    }
258
259    /**
260     * Generate a link to undo the specified revision.  Note that this will only work if
261     * that is the most recent content edit against the revision type.
262     *
263     * @param AbstractRevision $revision The revision to undo.
264     * @param Title|null $title The title the revision belongs to
265     * @param UUID $workflowId The workflow id the revision belongs to
266     * @return Anchor
267     * @throws FlowException When the provided revision is not known
268     */
269    public function undoAction( AbstractRevision $revision, ?Title $title, UUID $workflowId ) {
270        $startId = $revision->getPrevRevisionId();
271        $endId = $revision->getRevisionId();
272        if ( $revision instanceof PostRevision ) {
273            return $this->undoEditPostAction( $title, $workflowId, $startId, $endId );
274        } elseif ( $revision instanceof Header ) {
275            return $this->undoEditHeaderAction( $title, $workflowId, $startId, $endId );
276        } elseif ( $revision instanceof PostSummary ) {
277            return $this->undoEditSummaryAction( $title, $workflowId, $startId, $endId );
278        } else {
279            throw new FlowException( 'Unknown revision type: ' . get_class( $revision ) );
280        }
281    }
282
283    /**
284     * @param Title|null $title The title the post belongs to, or null
285     * @param UUID $workflowId The workflowId the post belongs to
286     * @param UUID $startId The revision to start undo from.
287     * @param UUID $endId The revision to stop undoing at
288     * @return Anchor
289     */
290    public function undoEditPostAction(
291        ?Title $title,
292        UUID $workflowId,
293        UUID $startId,
294        UUID $endId
295    ) {
296        return new Anchor(
297            wfMessage( 'flow-undo' ),
298            $this->resolveTitle( $title, $workflowId ),
299            [
300                'action' => 'undo-edit-post',
301                'topic_startId' => $startId->getAlphadecimal(),
302                'topic_endId' => $endId->getAlphadecimal(),
303            ]
304        );
305    }
306
307    /**
308     * @param Title|null $title The title the header belongs to, or null
309     * @param UUID $workflowId The workflowId the header belongs to
310     * @param UUID $startId The revision to start undo from.
311     * @param UUID $endId The revision to stop undoing at
312     * @return Anchor
313     */
314    public function undoEditHeaderAction(
315        ?Title $title,
316        UUID $workflowId,
317        UUID $startId,
318        UUID $endId
319    ) {
320        return new Anchor(
321            wfMessage( 'flow-undo' ),
322            $this->resolveTitle( $title, $workflowId ),
323            [
324                'action' => 'undo-edit-header',
325                'header_startId' => $startId->getAlphadecimal(),
326                'header_endId' => $endId->getAlphadecimal(),
327            ]
328        );
329    }
330
331    /**
332     * @param Title|null $title The title the summary belongs to, or null
333     * @param UUID $workflowId The workflowId the summary belongs to
334     * @param UUID $startId The revision to start undo from.
335     * @param UUID $endId The revision to stop undoing at
336     * @return Anchor
337     */
338    public function undoEditSummaryAction(
339        ?Title $title,
340        UUID $workflowId,
341        UUID $startId,
342        UUID $endId
343    ) {
344        return new Anchor(
345            wfMessage( 'flow-undo' ),
346            $this->resolveTitle( $title, $workflowId ),
347            [
348                'action' => 'undo-edit-topic-summary',
349                'topicsummary_startId' => $startId->getAlphadecimal(),
350                'topicsummary_endId' => $endId->getAlphadecimal(),
351            ]
352        );
353    }
354
355    /**
356     * @param AbstractRevision $revision
357     * @param Title|null $title
358     * @param UUID $workflowId
359     * @param UUID|null $oldRevId
360     * @return Anchor
361     * @throws FlowException When $revision is not PostRevision, Header or PostSummary
362     */
363    public function diffLink(
364        AbstractRevision $revision,
365        ?Title $title,
366        UUID $workflowId,
367        ?UUID $oldRevId = null
368    ) {
369        if ( $revision instanceof PostRevision ) {
370            return $this->diffPostLink( $title, $workflowId, $revision->getRevisionId(), $oldRevId );
371        } elseif ( $revision instanceof Header ) {
372            return $this->diffHeaderLink( $title, $workflowId, $revision->getRevisionId(), $oldRevId );
373        } elseif ( $revision instanceof PostSummary ) {
374            return $this->diffSummaryLink( $title, $workflowId, $revision->getRevisionId(), $oldRevId );
375        } else {
376            throw new FlowException( 'Unknown revision type: ' . get_class( $revision ) );
377        }
378    }
379
380    /**
381     * Show the differences between two revisions of a header.
382     *
383     * When $oldRevId is null shows the differences between $revId and the revision
384     * immediately prior.  If $oldRevId is provided shows the differences between
385     * $oldRevId and $revId.
386     *
387     * @param Title|null $title
388     * @param UUID $workflowId
389     * @param UUID $revId
390     * @param UUID|null $oldRevId
391     * @return Anchor
392     */
393    public function diffHeaderLink(
394        ?Title $title,
395        UUID $workflowId,
396        UUID $revId,
397        ?UUID $oldRevId = null
398    ) {
399        return new Anchor(
400            wfMessage( 'diff' ),
401            $this->resolveTitle( $title, $workflowId ),
402            [
403                'action' => 'compare-header-revisions',
404                'header_newRevision' => $revId->getAlphadecimal(),
405            ] + ( $oldRevId === null ? [] : [
406                'header_oldRevision' => $oldRevId->getAlphadecimal(),
407            ] )
408        );
409    }
410
411    /**
412     * Show the differences between two revisions of a post.
413     *
414     * When $oldRevId is null shows the differences between $revId and the revision
415     * immediately prior.  If $oldRevId is provided shows the differences between
416     * $oldRevId and $revId.
417     *
418     * @param Title|null $title
419     * @param UUID $workflowId
420     * @param UUID $revId
421     * @param UUID|null $oldRevId
422     * @return Anchor
423     */
424    public function diffPostLink(
425        ?Title $title,
426        UUID $workflowId,
427        UUID $revId,
428        ?UUID $oldRevId = null
429    ) {
430        return new Anchor(
431            wfMessage( 'diff' ),
432            $this->resolveTitle( $title, $workflowId ),
433            [
434                'action' => 'compare-post-revisions',
435                'topic_newRevision' => $revId->getAlphadecimal(),
436            ] + ( $oldRevId === null ? [] : [
437                'topic_oldRevision' => $oldRevId->getAlphadecimal(),
438            ] )
439        );
440    }
441
442    /**
443     * Show the differences between two revisions of a summary.
444     *
445     * When $oldRevId is null shows the differences between $revId and the revision
446     * immediately prior.  If $oldRevId is provided shows the differences between
447     * $oldRevId and $revId.
448     *
449     * @param Title|null $title
450     * @param UUID $workflowId
451     * @param UUID $revId
452     * @param UUID|null $oldRevId
453     * @return Anchor
454     */
455    public function diffSummaryLink(
456        ?Title $title,
457        UUID $workflowId,
458        UUID $revId,
459        ?UUID $oldRevId = null
460    ) {
461        return new Anchor(
462            wfMessage( 'diff' ),
463            $this->resolveTitle( $title, $workflowId ),
464            [
465                'action' => 'compare-postsummary-revisions',
466                'topicsummary_newRevision' => $revId->getAlphadecimal(),
467            ] + ( $oldRevId === null ? [] : [
468                'topicsummary_oldRevision' => $oldRevId->getAlphadecimal(),
469            ] )
470        );
471    }
472
473    /**
474     * View the specified workflow.
475     *
476     * @param Title|null $title
477     * @param UUID $workflowId
478     * @return Anchor
479     */
480    public function workflowLink( ?Title $title, UUID $workflowId ) {
481        return new Anchor(
482            wfMessage( 'flow-workflow' ),
483            $this->resolveTitle( $title, $workflowId )
484        );
485    }
486
487    /**
488     * Watch topic link
489     * @todo Replace title with a flow topic namespace topic
490     *
491     * @param Title|null $title
492     * @param UUID $workflowId
493     * @return Anchor
494     */
495    public function watchTopicLink( ?Title $title, UUID $workflowId ) {
496        return new Anchor(
497            wfMessage( 'watch' ),
498            $this->resolveTitle( $title, $workflowId ),
499            [ 'action' => 'watch' ]
500        );
501    }
502
503    /**
504     * Unwatch topic link
505     * @todo Replace title with a flow topic namespace topic
506     *
507     * @param Title|null $title
508     * @param UUID $workflowId
509     * @return Anchor
510     */
511    public function unwatchTopicLink( ?Title $title, UUID $workflowId ) {
512        return new Anchor(
513            wfMessage( 'unwatch' ),
514            $this->resolveTitle( $title, $workflowId ),
515            [ 'action' => 'unwatch' ]
516        );
517    }
518
519    /**
520     * View the flow board at the specified title
521     *
522     * Makes the assumption the title is flow-enabled.
523     *
524     * @param Title $title
525     * @param string|null $sortBy
526     * @param bool $saveSortBy
527     * @return Anchor
528     */
529    public function boardLink( Title $title, $sortBy = null, $saveSortBy = false ) {
530        $options = [];
531
532        if ( $sortBy !== null ) {
533            $options['topiclist_sortby'] = $sortBy;
534            if ( $saveSortBy ) {
535                $options['topiclist_savesortby'] = '1';
536            }
537        }
538
539        return new Anchor(
540            $title->getPrefixedText(),
541            $title,
542            $options
543        );
544    }
545
546    /**
547     * Reply to an individual post in a topic workflow.
548     *
549     * @param Title|null $title
550     * @param UUID $workflowId
551     * @param UUID $postId
552     * @param bool $isTopLevelReply
553     * @return Anchor
554     */
555    public function replyAction(
556        ?Title $title,
557        UUID $workflowId,
558        UUID $postId,
559        $isTopLevelReply
560    ) {
561        $hash = "#flow-post-{$postId->getAlphadecimal()}";
562        if ( $isTopLevelReply ) {
563            $hash .= "-form-content";
564        }
565        return new Anchor(
566            wfMessage( 'flow-reply-link' ),
567            $this->resolveTitle( $title, $workflowId ),
568            [
569                'action' => 'reply',
570                'topic_postId' => $postId->getAlphadecimal(),
571            ],
572            $hash
573        );
574    }
575
576    /**
577     * Edit the specified topic summary
578     *
579     * @param Title|null $title
580     * @param UUID $workflowId
581     * @return Anchor
582     */
583    public function editTopicSummaryAction( ?Title $title, UUID $workflowId ) {
584        return new Anchor(
585            wfMessage( 'flow-topic-action-summarize-topic' ),
586            $this->resolveTitle( $title, $workflowId ),
587            [ 'action' => 'edit-topic-summary' ]
588        );
589    }
590
591    /**
592     * Lock the specified topic
593     *
594     * @param Title|null $title
595     * @param UUID $workflowId
596     * @return Anchor
597     */
598    public function lockTopicAction( ?Title $title, UUID $workflowId ) {
599        return new Anchor(
600            wfMessage( 'flow-topic-action-lock-topic' ),
601            $this->resolveTitle( $title, $workflowId ),
602            [
603                'action' => 'lock-topic',
604                'flow_moderationState' => AbstractRevision::MODERATED_LOCKED,
605            ]
606        );
607    }
608
609    /**
610     * Restore the specified topic to unmoderated status.
611     *
612     * @param Title|null $title
613     * @param UUID $workflowId
614     * @param string $moderationAction
615     * @param string $flowAction
616     * @return Anchor
617     */
618    public function restoreTopicAction(
619        ?Title $title,
620        UUID $workflowId,
621        $moderationAction,
622        $flowAction = 'moderate-topic'
623    ) {
624        return new Anchor(
625            wfMessage( 'flow-topic-action-' . $moderationAction . '-topic' ),
626            $this->resolveTitle( $title, $workflowId ),
627            [
628                'action' => $flowAction,
629                'flow_moderationState' => $moderationAction,
630            ]
631        );
632    }
633
634    /**
635     * Restore the specified post to unmoderated status.
636     *
637     * @param Title|null $title
638     * @param UUID $workflowId
639     * @param UUID $postId
640     * @param string $moderationAction
641     * @param string $flowAction
642     * @return Anchor
643     */
644    public function restorePostAction(
645        ?Title $title,
646        UUID $workflowId,
647        UUID $postId,
648        $moderationAction,
649        $flowAction = 'moderate-post'
650    ) {
651        return new Anchor(
652            wfMessage( 'flow-post-action-' . $moderationAction . '-post' ),
653            $this->resolveTitle( $title, $workflowId ),
654            [
655                'action' => $flowAction,
656                'topic_moderationState' => $moderationAction,
657                'topic_postId' => $postId->getAlphadecimal(),
658            ]
659        );
660    }
661
662    /**
663     * Create a header for the specified page
664     *
665     * @param Title $title
666     * @return Anchor
667     */
668    public function createHeaderAction( Title $title ) {
669        return new Anchor(
670            wfMessage( 'flow-edit-header-link' ),
671            $title,
672            [ 'action' => 'edit-header' ]
673        );
674    }
675
676    /**
677     * Edit the specified header
678     *
679     * @param Title|null $title
680     * @param UUID $workflowId
681     * @param UUID $revId
682     * @return Anchor
683     */
684    public function editHeaderAction( ?Title $title, UUID $workflowId, UUID $revId ) {
685        return new Anchor(
686            wfMessage( 'flow-edit-header-link' ),
687            $this->resolveTitle( $title, $workflowId ),
688            [ 'action' => 'edit-header' ]
689        );
690    }
691
692    /**
693     * Edit the specified topic title
694     *
695     * @param Title|null $title
696     * @param UUID $workflowId
697     * @param UUID $postId
698     * @param UUID $revId
699     * @return Anchor
700     */
701    public function editTitleAction(
702        ?Title $title,
703        UUID $workflowId,
704        UUID $postId,
705        UUID $revId
706    ) {
707        return new Anchor(
708            wfMessage( 'flow-topic-action-edit-title' ),
709            $this->resolveTitle( $title, $workflowId ),
710            [
711                'action' => 'edit-title',
712                'topic_postId' => $postId->getAlphadecimal(),
713                'topic_format' => 'wikitext',
714            ]
715        );
716    }
717
718    /**
719     * Edit the specified post within the specified workflow
720     *
721     * @param Title|null $title
722     * @param UUID $workflowId
723     * @param UUID $postId
724     * @param UUID $revId
725     * @return Anchor
726     */
727    public function editPostAction(
728        ?Title $title,
729        UUID $workflowId,
730        UUID $postId,
731        UUID $revId
732    ) {
733        return new Anchor(
734            wfMessage( 'flow-post-action-edit-post' ),
735            $this->resolveTitle( $title, $workflowId ),
736            [
737                'action' => 'edit-post',
738                'topic_postId' => $postId->getAlphadecimal(),
739                // @todo not necessary?
740                'topic_revId' => $revId->getAlphadecimal(),
741            ],
742            '#flow-post-' . $postId->getAlphadecimal()
743
744        );
745    }
746
747    /**
748     * Hide the specified topic
749     *
750     * @param Title|null $title
751     * @param UUID $workflowId
752     * @return Anchor
753     */
754    public function hideTopicAction( ?Title $title, UUID $workflowId ) {
755        return new Anchor(
756            wfMessage( 'flow-topic-action-hide-topic' ),
757            $this->resolveTitle( $title, $workflowId ),
758            [
759                'action' => 'moderate-topic',
760                'topic_moderationState' => AbstractRevision::MODERATED_HIDDEN,
761            ]
762        );
763    }
764
765    /**
766     * Hide the specified post within the specified workflow
767     *
768     * @param Title|null $title
769     * @param UUID $workflowId
770     * @param UUID $postId
771     * @return Anchor
772     */
773    public function hidePostAction( ?Title $title, UUID $workflowId, UUID $postId ) {
774        return new Anchor(
775            wfMessage( 'flow-post-action-hide-post' ),
776            $this->resolveTitle( $title, $workflowId ),
777            [
778                'action' => 'moderate-post',
779                'topic_postId' => $postId->getAlphadecimal(),
780                'topic_moderationState' => AbstractRevision::MODERATED_HIDDEN,
781            ]
782        );
783    }
784
785    /**
786     * Delete the specified topic workflow
787     *
788     * @param Title|null $title
789     * @param UUID $workflowId
790     * @return Anchor
791     */
792    public function deleteTopicAction( ?Title $title, UUID $workflowId ) {
793        return new Anchor(
794            wfMessage( 'flow-topic-action-delete-topic' ),
795            $this->resolveTitle( $title, $workflowId ),
796            [
797                'action' => 'moderate-topic',
798                'topic_moderationState' => AbstractRevision::MODERATED_DELETED,
799            ]
800        );
801    }
802
803    /**
804     * Delete the specified post within the specified workflow
805     *
806     * @param Title|null $title
807     * @param UUID $workflowId
808     * @param UUID $postId
809     * @return Anchor
810     */
811    public function deletePostAction( ?Title $title, UUID $workflowId, UUID $postId ) {
812        return new Anchor(
813            wfMessage( 'flow-post-action-delete-post' ),
814            $this->resolveTitle( $title, $workflowId ),
815            [
816                'action' => 'moderate-post',
817                'topic_postId' => $postId->getAlphadecimal(),
818                'topic_moderationState' => AbstractRevision::MODERATED_DELETED,
819            ]
820        );
821    }
822
823    /**
824     * Suppress the specified topic workflow
825     *
826     * @param Title|null $title
827     * @param UUID $workflowId
828     * @return Anchor
829     */
830    public function suppressTopicAction( ?Title $title, UUID $workflowId ) {
831        return new Anchor(
832            wfMessage( 'flow-topic-action-suppress-topic' ),
833            $this->resolveTitle( $title, $workflowId ),
834            [
835                'action' => 'moderate-topic',
836                'topic_moderationState' => AbstractRevision::MODERATED_SUPPRESSED,
837            ]
838        );
839    }
840
841    /**
842     * Suppress the specified post within the specified workflow
843     *
844     * @param Title|null $title
845     * @param UUID $workflowId
846     * @param UUID $postId
847     * @return Anchor
848     */
849    public function suppressPostAction( ?Title $title, UUID $workflowId, UUID $postId ) {
850        return new Anchor(
851            wfMessage( 'flow-post-action-suppress-post' ),
852            $this->resolveTitle( $title, $workflowId ),
853            [
854                'action' => 'moderate-post',
855                'topic_postId' => $postId->getAlphadecimal(),
856                'topic_moderationState' => AbstractRevision::MODERATED_SUPPRESSED,
857            ]
858        );
859    }
860
861    /**
862     * @param Title|null $title
863     * @param UUID|null $workflowId
864     * @return Anchor
865     */
866    public function newTopicAction( ?Title $title = null, ?UUID $workflowId = null ) {
867        return new Anchor(
868            wfMessage( 'flow-newtopic-start-placeholder' ),
869            // resolveTitle doesn't accept null uuid
870            $this->resolveTitle( $title, $workflowId ),
871            [
872                'action' => 'new-topic'
873            ]
874        );
875    }
876
877    public function thankAction( UUID $postId ) {
878        $sender = RequestContext::getMain()->getUser();
879        $recipient = $sender; // Default to current user's gender if we can't find the recipient
880        $postCollection = PostCollection::newFromId( $postId );
881        $postRevision = $postCollection->getLastRevision();
882        $recipient = $postRevision->getCreatorTuple()->createUser();
883
884        return new Anchor(
885            wfMessage( 'flow-thank-link', $sender, $recipient )->text(),
886            SpecialPage::getTitleFor( 'Thanks', 'Flow/' . $postId->getAlphadecimal() ),
887            [],
888            null,
889            wfMessage( 'flow-thank-link-title', $sender, $recipient )->text()
890        );
891    }
892
893    /**
894     * Mark a revision as patrolled
895     *
896     * @param Title|null $title
897     * @param UUID $workflowId
898     * @param RecentChange $rc
899     * @param string $token
900     * @return Anchor
901     * @throws FlowException
902     * @throws InvalidInputException
903     */
904    public function markRevisionPatrolledAction(
905        ?Title $title,
906        UUID $workflowId,
907        RecentChange $rc,
908        $token
909    ) {
910        return new Anchor(
911            wfMessage( 'flow-mark-revision-patrolled-link-text' )->text(),
912            $this->resolveTitle( $title, $workflowId ),
913            [
914                'action' => 'markpatrolled',
915                'rcid' => $rc->getAttribute( 'rc_id' ),
916                'token' => $token,
917            ]
918        );
919    }
920}