Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 265
0.00% covered (danger)
0.00%
0 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
TalkpageView
0.00% covered (danger)
0.00%
0 / 265
0.00% covered (danger)
0.00%
0 / 18
2652
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 setTalkPage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 customizeTalkpageNavigation
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 customizeNavigation
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 showHeader
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 1
42
 getTOC
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
42
 getList
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getArchiveWidget
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 showTalkpageViewOptions
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
2
 show
0.00% covered (danger)
0.00%
0 / 96
0.00% covered (danger)
0.00%
0 / 1
306
 getSearchBox
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
2
 getPager
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getPageThreads
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getSortType
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 hideItems
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 showItems
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 shouldShow
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setShownItems
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3use MediaWiki\Html\Html;
4use MediaWiki\MediaWikiServices;
5use MediaWiki\SpecialPage\SpecialPage;
6use MediaWiki\Xml\Xml;
7use MediaWiki\Xml\XmlSelect;
8
9class TalkpageView extends LqtView {
10    public const LQT_NEWEST_CHANGES = 'nc';
11    public const LQT_NEWEST_THREADS = 'nt';
12    public const LQT_OLDEST_THREADS = 'ot';
13
14    /** @var string[] */
15    protected $mShowItems = [ 'toc', 'options', 'header' ];
16    /** @var Article */
17    protected $talkpage;
18
19    /**
20     * @var \MediaWiki\Linker\LinkRenderer
21     */
22    protected $linkRenderer;
23
24    public function __construct( &$output, &$article, &$title, &$user, &$request ) {
25        parent::__construct( $output, $article, $title, $user, $request );
26
27        $this->talkpage = $article;
28        $this->linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
29    }
30
31    public function setTalkPage( $tp ) {
32        $this->talkpage = $tp;
33    }
34
35    public static function customizeTalkpageNavigation( $skin, &$links, $view ) {
36        $remove = [ 'views/edit', 'views/viewsource', 'actions/delete' ];
37
38        foreach ( $remove as $rem ) {
39            [ $section, $item ] = explode( '/', $rem, 2 );
40            unset( $links[$section][$item] );
41        }
42
43        if ( isset( $links['views']['history'] ) ) {
44            $title = $view->article->getTitle();
45            $history_url = $title->getLocalURL( 'lqt_method=talkpage_history' );
46            $links['views']['history']['href'] = $history_url;
47        }
48    }
49
50    public function customizeNavigation( $skintemplate, &$links ) {
51        self::customizeTalkpageNavigation( $skintemplate, $links, $this );
52    }
53
54    public function showHeader() {
55        /* Show the contents of the actual talkpage article if it exists. */
56
57        $article = $this->talkpage;
58        $quickCanEdit = MediaWikiServices::getInstance()->getPermissionManager()
59            ->quickUserCan( 'edit', $this->user, $article->getTitle() );
60
61        // If $article_text == "", the talkpage was probably just created
62        // when the first thread was posted to make the links blue.
63        if ( $article->getPage()->exists() ) {
64            $html = '';
65
66            $article->view();
67
68            $actionLinks = [];
69            $msgKey = $quickCanEdit ? 'edit' : 'viewsource';
70            $actionLinks[] = $this->linkRenderer->makeLink(
71                $article->getTitle(),
72                new HtmlArmor( wfMessage( $msgKey )->parse() . "↑" ),
73                [],
74                [ 'action' => 'edit' ]
75            );
76
77            $actionLinks[] = $this->linkRenderer->makeLink(
78                $this->title,
79                new HtmlArmor( wfMessage( 'history_short' )->parse() . "↑" ),
80                [],
81                [ 'action' => 'history' ]
82            );
83
84            if ( $this->user->isAllowed( 'delete' ) ) {
85                $actionLinks[] = $this->linkRenderer->makeLink(
86                    $article->getTitle(),
87                    new HtmlArmor( wfMessage( 'delete' )->parse() . '↑' ),
88                    [],
89                    [ 'action' => 'delete' ]
90                );
91            }
92
93            $actions = '';
94            foreach ( $actionLinks as $link ) {
95                $actions .= Xml::tags( 'li', null, "[$link]" ) . "\n";
96            }
97            $actions = Xml::tags( 'ul', [ 'class' => 'lqt_header_commands' ], $actions );
98            $html .= $actions;
99
100            $html = Xml::tags( 'div', [ 'class' => 'lqt_header_content' ], $html );
101
102            $this->output->addHTML( $html );
103        } elseif ( $quickCanEdit ) {
104            $editLink = $this->linkRenderer->makeLink(
105                $this->talkpage->getTitle(),
106                new HtmlArmor( wfMessage( 'lqt_add_header' )->parse() ),
107                [],
108                [ 'action' => 'edit' ]
109            );
110
111            $html = Xml::tags( 'p', [ 'class' => 'lqt_header_notice' ], "[$editLink]" );
112
113            $this->output->addHTML( $html );
114        }
115    }
116
117    /**
118     * @param Thread[] $threads
119     * @return string
120     */
121    public function getTOC( array $threads ) {
122        global $wgLang;
123
124        $html = '';
125
126        $h2_header = Xml::tags( 'h2', null, wfMessage( 'lqt_contents_title' )->parse() );
127
128        // Header row
129        $headerRow = '';
130        $headers = [ 'lqt_toc_thread_title',
131                'lqt_toc_thread_replycount', 'lqt_toc_thread_modified' ];
132        foreach ( $headers as $msg ) {
133            $headerRow .= Xml::tags( 'th', null, wfMessage( $msg )->parse() );
134        }
135        $headerRow = Xml::tags( 'tr', null, $headerRow );
136        $headerRow = Xml::tags( 'thead', null, $headerRow );
137
138        // Table body
139        $rows = [];
140        $services = MediaWikiServices::getInstance();
141        $contLang = $services->getContentLanguage();
142        $langConv = $services
143                ->getLanguageConverterFactory()
144                ->getLanguageConverter( $services->getContentLanguage() );
145        foreach ( $threads as $thread ) {
146            if ( $thread->root() && !$thread->root()->getPage()->getContent() &&
147                !LqtView::threadContainsRepliesWithContent( $thread )
148            ) {
149                continue;
150            }
151
152            $row = '';
153            $anchor = '#' . $this->anchorName( $thread );
154            $subject = Xml::tags( 'a', [ 'href' => $anchor ],
155                    Threads::stripHTML( $langConv->convert( $thread->formattedSubject() ) ) );
156            $row .= Xml::tags( 'td', [ 'dir' => $contLang->getDir() ], $subject );
157
158            $row .= Xml::element( 'td', null, $wgLang->formatNum( $thread->replyCount() ) );
159
160            $timestamp = $wgLang->timeanddate( $thread->modified(), true );
161            $row .= Xml::element( 'td', null, $timestamp );
162
163            $row = Xml::tags( 'tr', null, $row );
164            $rows[] = $row;
165        }
166
167        $html .= $headerRow . "\n" . Xml::tags( 'tbody', null, implode( "\n", $rows ) );
168        $html = $h2_header . Xml::tags( 'table', [ 'class' => 'lqt_toc' ], $html );
169        // wrap our output in a div for containment
170        $html = Xml::tags( 'div', [ 'class' => 'lqt-contents-wrapper' ], $html );
171
172        return $html;
173    }
174
175    public function getList( $kind, $class, $id, $contents ) {
176        $html = '';
177        foreach ( $contents as $li ) {
178            $html .= Xml::tags( 'li', null, $li );
179        }
180        $html = Xml::tags( $kind, [ 'class' => $class, 'id' => $id ], $html );
181
182        return $html;
183    }
184
185    public function getArchiveWidget() {
186        $html = '';
187        $html = Xml::tags( 'div', [ 'class' => 'lqt_archive_teaser' ], $html );
188        return $html;
189    }
190
191    public function showTalkpageViewOptions() {
192        $form_action_url = $this->talkpageUrl( $this->title, 'talkpage_sort_order' );
193        $html = '';
194
195        $html .= Xml::label( wfMessage( 'lqt_sorting_order' )->text(), 'lqt_sort_select' ) . ' ';
196
197        $sortOrderSelect =
198            new XmlSelect( 'lqt_order', 'lqt_sort_select', $this->getSortType() );
199
200        $sortOrderSelect->setAttribute( 'class', 'lqt_sort_select' );
201        $sortOrderSelect->addOption(
202            wfMessage( 'lqt_sort_newest_changes' )->text(),
203            self::LQT_NEWEST_CHANGES
204        );
205        $sortOrderSelect->addOption(
206            wfMessage( 'lqt_sort_newest_threads' )->text(),
207            self::LQT_NEWEST_THREADS
208        );
209        $sortOrderSelect->addOption(
210            wfMessage( 'lqt_sort_oldest_threads' )->text(),
211            self::LQT_OLDEST_THREADS
212        );
213        $html .= $sortOrderSelect->getHTML();
214
215        $html .= Xml::submitButton(
216            wfMessage( 'lqt-changesortorder' )->text(),
217            [ 'class' => 'lqt_go_sort' ]
218        );
219        $html .= Html::hidden( 'title', $this->title->getPrefixedText() );
220
221        $html = Xml::tags(
222            'form',
223            [
224                'action' => $form_action_url,
225                'method' => 'get',
226                'name' => 'lqt_sort'
227            ],
228            $html
229        );
230        $html = Xml::tags( 'div', [ 'class' => 'lqt_view_options' ], $html );
231
232        return $html;
233    }
234
235    /**
236     * @return bool
237     */
238    public function show() {
239        $this->output->addModules( 'ext.liquidThreads' );
240
241        $article = $this->talkpage;
242        if ( !LqtDispatch::isLqtPage( $article->getTitle() ) ) {
243            $this->output->addWikiMsg( 'lqt-not-discussion-page' );
244            return false;
245        }
246
247        $this->output->setPageTitle( $this->title->getPrefixedText() );
248
249        // Expose feed links.
250        global $wgFeedClasses;
251        $apiParams = [ 'action' => 'feedthreads', 'type' => 'replies|newthreads',
252                'talkpage' => $this->title->getPrefixedText() ];
253        $urlPrefix = wfScript( 'api' ) . '?';
254        foreach ( $wgFeedClasses as $format => $class ) {
255            $theseParams = $apiParams + [ 'feedformat' => $format ];
256            $url = $urlPrefix . wfArrayToCgi( $theseParams );
257            $this->output->addFeedLink( $format, $url );
258        }
259
260        if ( $this->request->getBool( 'lqt_inline' ) ) {
261            $this->doInlineEditForm();
262            return false;
263        }
264
265        $this->output->addHTML(
266            Xml::openElement( 'div', [ 'class' => 'lqt-talkpage' ] )
267        );
268
269        // Search!
270        if ( $this->request->getCheck( 'lqt_search' ) ) {
271            $q = $this->request->getText( 'lqt_search' );
272            $q .= ' ondiscussionpage:' . $article->getTitle()->getPrefixedText();
273
274            $params = [
275                'search' => $q,
276                'fulltext' => 1,
277                'ns' . NS_LQT_THREAD => 1,
278                'srbackend' => 'LuceneSearch',
279            ];
280
281            $t = SpecialPage::getTitleFor( 'Search' );
282            $url = $t->getLocalURL( wfArrayToCgi( $params ) );
283
284            $this->output->redirect( $url );
285            return true;
286        }
287
288        if ( $this->shouldShow( 'header' ) ) {
289            $this->showHeader();
290        }
291
292        global $wgLang;
293
294        // This closes the div of mw-content-ltr/rtl containing lang and dir attributes
295        $this->output->addHTML(
296            Html::closeElement( 'div' ) . Html::openElement( 'div', [
297                'class' => 'lqt-talkpage',
298                'lang' => $wgLang->getCode(),
299                'dir' => $wgLang->getDir()
300            ]
301        ) );
302
303        $html = '';
304
305        // Set up a per-page header for new threads, search box, and sorting stuff.
306
307        $talkpageHeader = '';
308        $newThreadLink = '';
309
310        if ( Thread::canUserPost( $this->user, $this->talkpage, 'quick' ) ) {
311            $newThreadText = new HtmlArmor( wfMessage( 'lqt_new_thread' )->parse() );
312            $newThreadLink = $this->linkRenderer->makeKnownLink(
313                $this->title, $newThreadText,
314                [ 'lqt_talkpage' => $this->talkpage->getTitle()->getPrefixedText() ],
315                [ 'lqt_method' => 'talkpage_new_thread' ]
316            );
317
318            $newThreadLink = Xml::tags(
319                'strong',
320                [ 'class' => 'lqt_start_discussion' ],
321                $newThreadLink
322            );
323
324            $talkpageHeader .= $newThreadLink;
325        }
326
327        global $wgSearchTypeAlternatives, $wgSearchType;
328        if ( $wgSearchType == "LuceneSearch"
329            || in_array( "LuceneSearch", $wgSearchTypeAlternatives ?: [] )
330        ) {
331            $talkpageHeader .= $this->getSearchBox();
332        }
333        $talkpageHeader .= $this->showTalkpageViewOptions();
334        $talkpageHeader = Xml::tags(
335            'div',
336            [ 'class' => 'lqt-talkpage-header' ],
337            $talkpageHeader
338        );
339
340        if ( $this->shouldShow( 'options' ) ) {
341            $this->output->addHTML( $talkpageHeader );
342        } elseif ( $this->shouldShow( 'simplenew' ) ) {
343            $this->output->addHTML( $newThreadLink );
344        }
345
346        if ( $this->methodApplies( 'talkpage_new_thread' ) ) {
347            $this->showNewThreadForm( $this->talkpage );
348        } else {
349            $this->output->addHTML( Xml::tags( 'div',
350                [ 'class' => 'lqt-new-thread lqt-edit-form' ], '' ) );
351        }
352
353        $pager = $this->getPager();
354
355        $threads = $this->getPageThreads( $pager );
356
357        if ( count( $threads ) > 0 && $this->shouldShow( 'toc' ) ) {
358            $html .= $this->getTOC( $threads );
359        } elseif ( count( $threads ) == 0 ) {
360            $html .= Xml::tags( 'div', [ 'class' => 'lqt-no-threads' ],
361                wfMessage( 'lqt-no-threads' )->parse() );
362        }
363
364        $this->output->addModuleStyles( $pager->getModuleStyles() );
365
366        $html .= $pager->getNavigationBar();
367        $html .= Xml::openElement( 'div', [ 'class' => 'lqt-threads lqt-talkpage-threads' ] );
368
369        $this->output->addHTML( $html );
370
371        foreach ( $threads as $t ) {
372            $this->showThread( $t );
373        }
374
375        $this->output->addHTML(
376            Xml::closeElement( 'div' ) .
377            $pager->getNavigationBar() .
378            Xml::closeElement( 'div' )
379        );
380
381        return false;
382    }
383
384    private function getSearchBox() {
385        $html = '';
386        $html .= Xml::inputLabel(
387            wfMessage( 'lqt-search-label' )->text(),
388            'lqt_search',
389            'lqt-search-box',
390            45
391        );
392
393        $html .= ' ' . Xml::submitButton( wfMessage( 'lqt-search-button' )->text() );
394        $html .= Html::hidden( 'title', $this->title->getPrefixedText() );
395        $html = Xml::tags(
396            'form',
397            [
398                'action' => $this->title->getLocalURL(),
399                'method' => 'get'
400            ],
401            $html
402        );
403
404        $html = Xml::tags( 'div', [ 'class' => 'lqt-talkpage-search' ], $html );
405
406        return $html;
407    }
408
409    private function getPager() {
410        $sortType = $this->getSortType();
411        return new LqtDiscussionPager( $this->talkpage, $sortType );
412    }
413
414    private function getPageThreads( $pager ) {
415        $rows = $pager->getRows();
416
417        return Thread::bulkLoad( $rows );
418    }
419
420    private function getSortType() {
421        // Determine sort order
422        if ( $this->request->getCheck( 'lqt_order' ) ) {
423            // Sort order is explicitly specified through UI
424            $lqt_order = $this->request->getVal( 'lqt_order' );
425            switch ( $lqt_order ) {
426                case 'nc':
427                    return self::LQT_NEWEST_CHANGES;
428                case 'nt':
429                    return self::LQT_NEWEST_THREADS;
430                case 'ot':
431                    return self::LQT_OLDEST_THREADS;
432            }
433        }
434
435        // Default
436        return self::LQT_NEWEST_CHANGES;
437    }
438
439    /**
440     * Hide a number of items from the view
441     * Valid values: toc, options, header
442     *
443     * @param string[]|string $items
444     */
445    public function hideItems( $items ) {
446        $this->mShowItems = array_diff( $this->mShowItems, (array)$items );
447    }
448
449    /**
450     * Show a number of items in the view
451     * Valid values: toc, options, header
452     *
453     * @param string[]|string $items
454     */
455    public function showItems( $items ) {
456        $this->mShowItems = array_merge( $this->mShowItems, (array)$items );
457    }
458
459    /**
460     * Whether or not to show an item
461     *
462     * @param string $item
463     * @return bool
464     */
465    public function shouldShow( $item ) {
466        return in_array( $item, $this->mShowItems );
467    }
468
469    /**
470     * @param string[] $items
471     */
472    public function setShownItems( $items ) {
473        $this->mShowItems = $items;
474    }
475}