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 | use MediaWiki\Xml\Xml; |
7 | use MediaWiki\Xml\XmlSelect; |
8 | |
9 | class 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 | } |