Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 265 |
|
0.00% |
0 / 18 |
CRAP | |
0.00% |
0 / 1 |
TalkpageView | |
0.00% |
0 / 265 |
|
0.00% |
0 / 18 |
2652 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
setTalkPage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
customizeTalkpageNavigation | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
customizeNavigation | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
showHeader | |
0.00% |
0 / 43 |
|
0.00% |
0 / 1 |
42 | |||
getTOC | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
42 | |||
getList | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getArchiveWidget | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
showTalkpageViewOptions | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
2 | |||
show | |
0.00% |
0 / 96 |
|
0.00% |
0 / 1 |
306 | |||
getSearchBox | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
2 | |||
getPager | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getPageThreads | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getSortType | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
30 | |||
hideItems | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
showItems | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
shouldShow | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setShownItems | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | use MediaWiki\Html\Html; |
4 | use MediaWiki\MediaWikiServices; |
5 | use MediaWiki\SpecialPage\SpecialPage; |
6 | |
7 | class 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 | } |