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