Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.17% covered (success)
98.17%
107 / 109
95.45% covered (success)
95.45%
21 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
PagerNavigationBuilder
98.17% covered (success)
98.17%
107 / 109
95.45% covered (success)
95.45%
21 / 22
36
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
 setPage
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setLinkQuery
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setPrevLinkQuery
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setPrevMsg
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setPrevTooltipMsg
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setNextLinkQuery
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setNextMsg
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setNextTooltipMsg
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setFirstLinkQuery
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setFirstMsg
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setFirstTooltipMsg
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setLastLinkQuery
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setLastMsg
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setLastTooltipMsg
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setCurrentLimit
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setLimits
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setLimitLinkQueryParam
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setLimitTooltipMsg
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 msg
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 makeLink
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
2
 getHtml
96.00% covered (success)
96.00%
48 / 50
0.00% covered (danger)
0.00%
0 / 1
14
1<?php
2
3namespace MediaWiki\Navigation;
4
5use MediaWiki\Html\Html;
6use MediaWiki\Language\RawMessage;
7use MediaWiki\Message\Message;
8use MediaWiki\Page\PageReference;
9use MediaWiki\Title\Title;
10use MessageLocalizer;
11use RuntimeException;
12
13/**
14 * Build the navigation for a pager, with links to prev/next page, links to change limits, and
15 * optionally links to first/last page.
16 *
17 * @since 1.39
18 */
19class PagerNavigationBuilder {
20    /** @var MessageLocalizer */
21    private $messageLocalizer;
22
23    /** @var PageReference */
24    protected $page;
25    /** @var array<string,?string> */
26    protected $linkQuery = [];
27
28    /** @var array<string,?string>|null */
29    private $prevLinkQuery = null;
30    /** @var string */
31    private $prevMsg = 'prevn';
32    /** @var string|null */
33    private $prevTooltipMsg = null;
34
35    /** @var array<string,?string>|null */
36    private $nextLinkQuery = null;
37    /** @var string */
38    private $nextMsg = 'nextn';
39    /** @var string|null */
40    private $nextTooltipMsg = null;
41
42    /** @var array<string,?string>|null */
43    private $firstLinkQuery = null;
44    /** @var string|null */
45    private $firstMsg = null;
46    /** @var string|null */
47    private $firstTooltipMsg = null;
48
49    /** @var array<string,?string>|null */
50    private $lastLinkQuery = null;
51    /** @var string|null */
52    private $lastMsg = null;
53    /** @var string|null */
54    private $lastTooltipMsg = null;
55
56    /** @var int */
57    private $currentLimit = 50;
58    /** @var int[] */
59    private $limits = [ 20, 50, 100, 250, 500 ];
60    /** @var string */
61    private $limitLinkQueryParam = 'limit';
62    /** @var string|null */
63    private $limitTooltipMsg = null;
64
65    /**
66     * @param MessageLocalizer $messageLocalizer
67     */
68    public function __construct( MessageLocalizer $messageLocalizer ) {
69        $this->messageLocalizer = $messageLocalizer;
70    }
71
72    /**
73     * @param PageReference $page
74     * @return $this
75     */
76    public function setPage( PageReference $page ): PagerNavigationBuilder {
77        $this->page = $page;
78        return $this;
79    }
80
81    /**
82     * @param array<string,?string> $linkQuery
83     * @return $this
84     */
85    public function setLinkQuery( array $linkQuery ): PagerNavigationBuilder {
86        $this->linkQuery = $linkQuery;
87        return $this;
88    }
89
90    /**
91     * @param array<string,?string>|null $prevLinkQuery
92     * @return $this
93     */
94    public function setPrevLinkQuery( ?array $prevLinkQuery ): PagerNavigationBuilder {
95        $this->prevLinkQuery = $prevLinkQuery;
96        return $this;
97    }
98
99    /**
100     * @param string $prevMsg
101     * @return $this
102     */
103    public function setPrevMsg( string $prevMsg ): PagerNavigationBuilder {
104        $this->prevMsg = $prevMsg;
105        return $this;
106    }
107
108    /**
109     * @param string|null $prevTooltipMsg
110     * @return $this
111     */
112    public function setPrevTooltipMsg( ?string $prevTooltipMsg ): PagerNavigationBuilder {
113        $this->prevTooltipMsg = $prevTooltipMsg;
114        return $this;
115    }
116
117    /**
118     * @param array<string,?string>|null $nextLinkQuery
119     * @return $this
120     */
121    public function setNextLinkQuery( ?array $nextLinkQuery ): PagerNavigationBuilder {
122        $this->nextLinkQuery = $nextLinkQuery;
123        return $this;
124    }
125
126    /**
127     * @param string $nextMsg
128     * @return $this
129     */
130    public function setNextMsg( string $nextMsg ): PagerNavigationBuilder {
131        $this->nextMsg = $nextMsg;
132        return $this;
133    }
134
135    /**
136     * @param string|null $nextTooltipMsg
137     * @return $this
138     */
139    public function setNextTooltipMsg( ?string $nextTooltipMsg ): PagerNavigationBuilder {
140        $this->nextTooltipMsg = $nextTooltipMsg;
141        return $this;
142    }
143
144    /**
145     * @param array<string,?string>|null $firstLinkQuery
146     * @return $this
147     */
148    public function setFirstLinkQuery( ?array $firstLinkQuery ): PagerNavigationBuilder {
149        $this->firstLinkQuery = $firstLinkQuery;
150        return $this;
151    }
152
153    /**
154     * @param string|null $firstMsg
155     * @return $this
156     */
157    public function setFirstMsg( ?string $firstMsg ): PagerNavigationBuilder {
158        $this->firstMsg = $firstMsg;
159        return $this;
160    }
161
162    /**
163     * @param string|null $firstTooltipMsg
164     * @return $this
165     */
166    public function setFirstTooltipMsg( ?string $firstTooltipMsg ): PagerNavigationBuilder {
167        $this->firstTooltipMsg = $firstTooltipMsg;
168        return $this;
169    }
170
171    /**
172     * @param array<string,?string>|null $lastLinkQuery
173     * @return $this
174     */
175    public function setLastLinkQuery( ?array $lastLinkQuery ): PagerNavigationBuilder {
176        $this->lastLinkQuery = $lastLinkQuery;
177        return $this;
178    }
179
180    /**
181     * @param string|null $lastMsg
182     * @return $this
183     */
184    public function setLastMsg( ?string $lastMsg ): PagerNavigationBuilder {
185        $this->lastMsg = $lastMsg;
186        return $this;
187    }
188
189    /**
190     * @param string|null $lastTooltipMsg
191     * @return $this
192     */
193    public function setLastTooltipMsg( ?string $lastTooltipMsg ): PagerNavigationBuilder {
194        $this->lastTooltipMsg = $lastTooltipMsg;
195        return $this;
196    }
197
198    /**
199     * @param int $currentLimit
200     * @return $this
201     */
202    public function setCurrentLimit( int $currentLimit ): PagerNavigationBuilder {
203        $this->currentLimit = $currentLimit;
204        return $this;
205    }
206
207    /**
208     * @param int[] $limits
209     * @return $this
210     */
211    public function setLimits( array $limits ): PagerNavigationBuilder {
212        $this->limits = $limits;
213        return $this;
214    }
215
216    /**
217     * @param string $limitLinkQueryParam
218     * @return $this
219     */
220    public function setLimitLinkQueryParam( string $limitLinkQueryParam ): PagerNavigationBuilder {
221        $this->limitLinkQueryParam = $limitLinkQueryParam;
222        return $this;
223    }
224
225    /**
226     * @param string|null $limitTooltipMsg
227     * @return $this
228     */
229    public function setLimitTooltipMsg( ?string $limitTooltipMsg ): PagerNavigationBuilder {
230        $this->limitTooltipMsg = $limitTooltipMsg;
231        return $this;
232    }
233
234    /**
235     * @param mixed $key
236     * @param mixed ...$params
237     * @return Message
238     */
239    private function msg( $key, ...$params ): Message {
240        return $this->messageLocalizer
241            ->msg( $key, ...$params )
242            ->page( $this->page );
243    }
244
245    /**
246     * @stable to override
247     * @param array|null $query
248     * @param string|null $class
249     * @param string $text
250     * @param string|null $tooltip
251     * @param string|null $rel
252     * @return string HTML
253     */
254    protected function makeLink(
255        ?array $query, ?string $class, string $text, ?string $tooltip, ?string $rel = null
256    ): string {
257        if ( $query !== null ) {
258            $title = Title::newFromPageReference( $this->page );
259            return Html::element(
260                'a',
261                [
262                    'href' => $title->getLocalURL( array_merge( $this->linkQuery, $query ) ),
263                    'rel' => $rel,
264                    'title' => $tooltip,
265                    'class' => $class,
266                ],
267                $text
268            );
269        } else {
270            return Html::element(
271                'span',
272                [
273                    'class' => $class,
274                ],
275                $text
276            );
277        }
278    }
279
280    /**
281     * Get the navigation HTML.
282     * @return string HTML
283     */
284    public function getHtml(): string {
285        if ( !isset( $this->page ) ) {
286            throw new RuntimeException( 'page must be set' );
287        }
288        if ( isset( $this->firstMsg ) !== isset( $this->lastMsg ) ) {
289            throw new RuntimeException( 'firstMsg and lastMsg must be both set or both unset' );
290        }
291
292        $prevText = $this->msg( $this->prevMsg )->numParams( $this->currentLimit )->text();
293        $prevTooltip = $this->prevTooltipMsg ?
294            $this->msg( $this->prevTooltipMsg )->numParams( $this->currentLimit )->text() :
295            null;
296        $prevLink = $this->makeLink( $this->prevLinkQuery, 'mw-prevlink', $prevText, $prevTooltip, 'prev' );
297
298        $nextText = $this->msg( $this->nextMsg )->numParams( $this->currentLimit )->text();
299        $nextTooltip = $this->nextTooltipMsg ?
300            $this->msg( $this->nextTooltipMsg )->numParams( $this->currentLimit )->text() :
301            null;
302        $nextLink = $this->makeLink( $this->nextLinkQuery, 'mw-nextlink', $nextText, $nextTooltip, 'next' );
303
304        if ( $this->firstMsg ) {
305            $firstText = $this->msg( $this->firstMsg )->text();
306            $firstTooltip = $this->firstTooltipMsg ?
307                $this->msg( $this->firstTooltipMsg )->text() :
308                null;
309            $firstLink = $this->makeLink( $this->firstLinkQuery, 'mw-firstlink', $firstText, $firstTooltip );
310        }
311
312        if ( $this->lastMsg ) {
313            $lastText = $this->msg( $this->lastMsg )->text();
314            $lastTooltip = $this->lastTooltipMsg ?
315                $this->msg( $this->lastTooltipMsg )->text() :
316                null;
317            $lastLink = $this->makeLink( $this->lastLinkQuery, 'mw-lastlink', $lastText, $lastTooltip );
318        }
319
320        $limitLinks = [];
321        foreach ( $this->limits as $limit ) {
322            $limitText = $this->msg( new RawMessage( '$1' ) )->numParams( $limit )->text();
323            $limitTooltip = $this->limitTooltipMsg ?
324                $this->msg( $this->limitTooltipMsg )->numParams( $limit )->text() :
325                null;
326            $limitQuery = $limit === $this->currentLimit ? null : [ $this->limitLinkQueryParam => $limit ];
327            $limitLinks[] = $this->makeLink( $limitQuery, 'mw-numlink', $limitText, $limitTooltip );
328        }
329
330        $html = '';
331        if ( isset( $firstLink ) && isset( $lastLink ) ) {
332            $html .= $this->msg( 'parentheses' )->params(
333                Message::listParam( [
334                    Message::rawParam( $firstLink ),
335                    Message::rawParam( $lastLink )
336                ], 'pipe' )
337            )->escaped() . ' ';
338        }
339        $html .= $this->msg( 'viewprevnext' )->params(
340            Message::rawParam( $prevLink ),
341            Message::rawParam( $nextLink ),
342            Message::listParam( array_map( static function ( $limitLink ) {
343                return Message::rawParam( $limitLink );
344            }, $limitLinks ), 'pipe' )
345        )->escaped();
346
347        return Html::rawElement( 'div', [ 'class' => 'mw-pager-navigation-bar' ], $html );
348    }
349}