Index: trunk/extensions/LiquidThreads/i18n/Lqt.i18n.php |
— | — | @@ -274,6 +274,16 @@ |
275 | 275 | 'lqt-talkpage-history-title' => 'Discussion page history', |
276 | 276 | 'lqt-talkpage-history-subtitle' => 'For $1', |
277 | 277 | 'lqt-talkpage-history-tab' => 'Header', |
| 278 | + |
| 279 | + // Protection |
| 280 | + 'restriction-reply' => 'Post replies', |
| 281 | + 'restriction-newthread' => 'Post new threads', |
| 282 | + 'lqt-protected-reply-thread' => 'You cannot post in this thread because it has been '. |
| 283 | + 'protected from new posts.', |
| 284 | + 'lqt-protected-reply-talkpage' => 'You cannot post in this thread because this '. |
| 285 | + 'discussion page has been protected from replies to its threads.', |
| 286 | + 'lqt-protected-newthread' => 'You cannot post new threads to this discussion '. |
| 287 | + 'page because it has been protected from new threads.', |
278 | 288 | ); |
279 | 289 | |
280 | 290 | /** Message documentation (Message documentation) |
Index: trunk/extensions/LiquidThreads/LiquidThreads.php |
— | — | @@ -24,10 +24,10 @@ |
25 | 25 | define( 'LQT_OLDEST_THREADS', 'ot' ); |
26 | 26 | |
27 | 27 | // FIXME: would be neat if it was possible to somehow localise this. |
28 | | -$wgCanonicalNamespaceNames[NS_LQT_THREAD] = 'Thread'; |
29 | | -$wgCanonicalNamespaceNames[NS_LQT_THREAD_TALK] = 'Thread_talk'; |
30 | | -$wgCanonicalNamespaceNames[NS_LQT_SUMMARY] = 'Summary'; |
31 | | -$wgCanonicalNamespaceNames[NS_LQT_SUMMARY_TALK] = 'Summary_talk'; |
| 28 | +$wgCanonicalNamespaceNames[NS_LQT_THREAD] = 'Thread'; |
| 29 | +$wgCanonicalNamespaceNames[NS_LQT_THREAD_TALK] = 'Thread_talk'; |
| 30 | +$wgCanonicalNamespaceNames[NS_LQT_SUMMARY] = 'Summary'; |
| 31 | +$wgCanonicalNamespaceNames[NS_LQT_SUMMARY_TALK] = 'Summary_talk'; |
32 | 32 | |
33 | 33 | // FIXME: would be neat if it was possible to somehow localise this. |
34 | 34 | $wgExtraNamespaces[NS_LQT_THREAD] = 'Thread'; |
— | — | @@ -94,6 +94,9 @@ |
95 | 95 | // Blocking |
96 | 96 | $wgHooks['UserIsBlockedFrom'][] = 'LqtHooks::userIsBlockedFrom'; |
97 | 97 | |
| 98 | +// Protection |
| 99 | +$wgHooks['ProtectionFormGetApplicableTypes'][] = 'LqtHooks::getProtectionTypes'; |
| 100 | + |
98 | 101 | // Special pages |
99 | 102 | $wgSpecialPages['MoveThread'] = 'SpecialMoveThread'; |
100 | 103 | $wgSpecialPages['NewMessages'] = 'SpecialNewMessages'; |
Index: trunk/extensions/LiquidThreads/classes/Threads.php |
— | — | @@ -74,22 +74,31 @@ |
75 | 75 | } |
76 | 76 | } |
77 | 77 | |
78 | | - static function loadFromResult( $res, $db ) { |
| 78 | + static function loadFromResult( $res, $db, $bulkLoad ) { |
79 | 79 | $rows = array(); |
| 80 | + $threads = array(); |
80 | 81 | |
81 | 82 | while ( $row = $db->fetchObject( $res ) ) { |
82 | 83 | $rows[] = $row; |
| 84 | + |
| 85 | + if (!$bulkLoad) { |
| 86 | + $threads[$row->thread_id] = Thread::newFromRow( $row ); |
| 87 | + } |
83 | 88 | } |
84 | 89 | |
| 90 | + if (!$bulkLoad) { |
| 91 | + return $threads; |
| 92 | + } |
| 93 | + |
85 | 94 | return Thread::bulkLoad( $rows ); |
86 | 95 | } |
87 | 96 | |
88 | | - static function where( $where, $options = array() ) { |
| 97 | + static function where( $where, $options = array(), $bulkLoad = true ) { |
89 | 98 | global $wgDBprefix; |
90 | 99 | $dbr = wfGetDB( DB_SLAVE ); |
91 | 100 | |
92 | 101 | $res = $dbr->select( 'thread', '*', $where, __METHOD__, $options ); |
93 | | - $threads = Threads::loadFromResult( $res, $dbr ); |
| 102 | + $threads = Threads::loadFromResult( $res, $dbr, $bulkLoad ); |
94 | 103 | |
95 | 104 | foreach ( $threads as $thread ) { |
96 | 105 | if ( $thread->root() ) { |
— | — | @@ -115,7 +124,7 @@ |
116 | 125 | } |
117 | 126 | } |
118 | 127 | |
119 | | - static function withRoot( $post ) { |
| 128 | + static function withRoot( $post, $bulkLoad = true ) { |
120 | 129 | if ( $post->getTitle()->getNamespace() != NS_LQT_THREAD ) { |
121 | 130 | // No articles outside the thread namespace have threads associated with them; |
122 | 131 | return null; |
— | — | @@ -125,23 +134,25 @@ |
126 | 135 | return self::$cache_by_root[$post->getID()]; |
127 | 136 | } |
128 | 137 | |
129 | | - $ts = Threads::where( array( 'thread_root' => $post->getID() ) ); |
| 138 | + $ts = Threads::where( array( 'thread_root' => $post->getID() ), array(), |
| 139 | + $bulkLoad ); |
130 | 140 | |
131 | 141 | return self::assertSingularity( $ts, 'thread_root', $post->getID() ); |
132 | 142 | } |
133 | 143 | |
134 | | - static function withId( $id ) { |
| 144 | + static function withId( $id, $bulkLoad = true ) { |
135 | 145 | if ( array_key_exists( $id, self::$cache_by_id ) ) { |
136 | 146 | return self::$cache_by_id[$id]; |
137 | 147 | } |
138 | 148 | |
139 | | - $ts = Threads::where( array( 'thread_id' => $id ) ); |
| 149 | + $ts = Threads::where( array( 'thread_id' => $id ), array(), $bulkLoad ); |
140 | 150 | |
141 | 151 | return self::assertSingularity( $ts, 'thread_id', $id ); |
142 | 152 | } |
143 | 153 | |
144 | | - static function withSummary( $article ) { |
145 | | - $ts = Threads::where( array( 'thread_summary_page' => $article->getId() ) ); |
| 154 | + static function withSummary( $article, $bulkLoad = true ) { |
| 155 | + $ts = Threads::where( array( 'thread_summary_page' => $article->getId() ), |
| 156 | + array(), $bulkLoad); |
146 | 157 | return self::assertSingularity( $ts, 'thread_summary_page', $article->getId() ); |
147 | 158 | } |
148 | 159 | |
Index: trunk/extensions/LiquidThreads/classes/View.php |
— | — | @@ -334,6 +334,21 @@ |
335 | 335 | can temporarily use a random scratch title. It's fine if the title changes |
336 | 336 | throughout the edit cycle, since the article doesn't exist yet anyways. |
337 | 337 | */ |
| 338 | + |
| 339 | + // Check permissions |
| 340 | + if ( $edit_type == 'new' ) { |
| 341 | + if ( Thread::canUserPost( $this->user, $this->article ) !== true ) { |
| 342 | + $this->output->addWikiMsg( 'lqt-protected-newthread' ); |
| 343 | + return; |
| 344 | + } |
| 345 | + } elseif ( $edit_type == 'reply' ) { |
| 346 | + $perm_result = $edit_applies_to->canUserReply( $this->user ); |
| 347 | + if ( $perm_result !== true ) { |
| 348 | + $msg = "lqt-protected-reply-$perm_result"; |
| 349 | + $this->output->addWikiMsg( $msg ); |
| 350 | + return; |
| 351 | + } |
| 352 | + } |
338 | 353 | |
339 | 354 | // Check if we actually want a subject, pull the submitted subject, and validate it. |
340 | 355 | $subject_expected = ( $edit_type == 'new' || $thread && $thread->isTopmostThread() ); |
— | — | @@ -678,15 +693,17 @@ |
679 | 694 | 'enabled' => true, 'tooltip' => $label ); |
680 | 695 | } |
681 | 696 | |
682 | | - $commands['reply'] = array( |
683 | | - 'label' => wfMsgExt( 'lqt_reply', 'parseinline' ), |
684 | | - 'href' => $this->talkpageUrl( $this->title, 'reply', $thread, |
685 | | - true /* include fragment */, $this->request ), |
686 | | - 'enabled' => true, |
687 | | - 'icon' => 'reply.png', |
688 | | - 'showlabel' => 1, |
689 | | - 'tooltip' => wfMsg( 'lqt_reply' ) |
690 | | - ); |
| 697 | + if ( $thread->canUserReply( $this->user ) === true ) { |
| 698 | + $commands['reply'] = array( |
| 699 | + 'label' => wfMsgExt( 'lqt_reply', 'parseinline' ), |
| 700 | + 'href' => $this->talkpageUrl( $this->title, 'reply', $thread, |
| 701 | + true /* include fragment */, $this->request ), |
| 702 | + 'enabled' => true, |
| 703 | + 'icon' => 'reply.png', |
| 704 | + 'showlabel' => 1, |
| 705 | + 'tooltip' => wfMsg( 'lqt_reply' ) |
| 706 | + ); |
| 707 | + } |
691 | 708 | |
692 | 709 | $commands['link'] = array( |
693 | 710 | 'label' => wfMsgExt( 'lqt_permalink', 'parseinline' ), |
— | — | @@ -725,6 +742,22 @@ |
726 | 743 | 'href' => $move_href, |
727 | 744 | 'enabled' => true ); |
728 | 745 | } |
| 746 | + |
| 747 | + if ( $this->user->isAllowed( 'protect' ) ) { |
| 748 | + $protect_href = $thread->title()->getFullURL( 'action=protect' ); |
| 749 | + |
| 750 | + // Check if it's already protected |
| 751 | + if ( !$thread->title()->isProtected() ) { |
| 752 | + $label = wfMsg( 'protect' ); |
| 753 | + } else { |
| 754 | + $label = wfMsg( 'unprotect' ); |
| 755 | + } |
| 756 | + |
| 757 | + $commands['protect'] = array( 'label' => $label, |
| 758 | + 'href' => $protect_href, |
| 759 | + 'enabled' => true, ); |
| 760 | + } |
| 761 | + |
729 | 762 | if ( !$this->user->isAnon() && !$thread->title()->userIsWatching() ) { |
730 | 763 | $commands['watch'] = array( 'label' => wfMsg( 'watch' ), |
731 | 764 | 'href' => self::permalinkUrlWithQuery( $thread, 'action=watch' ), |
Index: trunk/extensions/LiquidThreads/classes/Hooks.php |
— | — | @@ -471,4 +471,29 @@ |
472 | 472 | |
473 | 473 | return true; |
474 | 474 | } |
| 475 | + |
| 476 | + static function getProtectionTypes( $title, &$types ) { |
| 477 | + $isLqtPage = LqtDispatch::isLqtPage( $title ); |
| 478 | + $isThread = $title->getNamespace() == NS_LQT_THREAD; |
| 479 | + |
| 480 | + // Bulk load is a no-no, causes infinite recursion. |
| 481 | + $thread = Threads::withRoot( new Article( $title ), false ); |
| 482 | + |
| 483 | + $isThread = $isThread && $thread && $thread->isTopmostThread(); |
| 484 | + |
| 485 | + if ( !$isLqtPage && !$isThread ) { |
| 486 | + return true; |
| 487 | + } |
| 488 | + |
| 489 | + if ( $isLqtPage ) { |
| 490 | + $types[] = 'newthread'; |
| 491 | + $types[] = 'reply'; |
| 492 | + } |
| 493 | + |
| 494 | + if ( $isThread ) { |
| 495 | + $types[] = 'reply'; |
| 496 | + } |
| 497 | + |
| 498 | + return true; |
| 499 | + } |
475 | 500 | } |
Index: trunk/extensions/LiquidThreads/classes/Thread.php |
— | — | @@ -1385,4 +1385,36 @@ |
1386 | 1386 | |
1387 | 1387 | return $ok; |
1388 | 1388 | } |
| 1389 | + |
| 1390 | + /* N.B. Returns true, or a string with either thread or talkpage, noting which is |
| 1391 | + protected */ |
| 1392 | + public function canUserReply( $user ) { |
| 1393 | + $threadRestrictions = $this->topmostThread()->title()->getRestrictions('reply'); |
| 1394 | + $talkpageRestrictions = $this->article()->getTitle()->getRestrictions('reply'); |
| 1395 | + |
| 1396 | + $threadRestrictions = array_fill_keys( $threadRestrictions, 'thread' ); |
| 1397 | + $talkpageRestrictions = array_fill_keys( $talkpageRestrictions, 'talkpage' ); |
| 1398 | + |
| 1399 | + $restrictions = array_merge( $threadRestrictions, $talkpageRestrictions ); |
| 1400 | + |
| 1401 | + foreach( $restrictions as $right => $source ) { |
| 1402 | + if ( !$user->isAllowed( $right ) ) { |
| 1403 | + return $source; |
| 1404 | + } |
| 1405 | + } |
| 1406 | + |
| 1407 | + return true; |
| 1408 | + } |
| 1409 | + |
| 1410 | + public static function canUserPost( $user, $talkpage ) { |
| 1411 | + $restrictions = $talkpage->getTitle()->getRestrictions('newthread'); |
| 1412 | + |
| 1413 | + foreach( $restrictions as $right ) { |
| 1414 | + if ( !$user->isAllowed( $right ) ) { |
| 1415 | + return false; |
| 1416 | + } |
| 1417 | + } |
| 1418 | + |
| 1419 | + return true; |
| 1420 | + } |
1389 | 1421 | } |
Index: trunk/extensions/LiquidThreads/pages/TalkpageView.php |
— | — | @@ -228,14 +228,18 @@ |
229 | 229 | |
230 | 230 | $talkpageHeader = ''; |
231 | 231 | |
232 | | - $newThreadText = wfMsgExt( 'lqt_new_thread', 'parseinline' ); |
233 | | - $newThreadLink = $sk->link( $this->title, $newThreadText, |
234 | | - array( ), |
235 | | - array( 'lqt_method' => 'talkpage_new_thread' ), |
236 | | - array( 'known' ) ); |
237 | | - |
238 | | - $talkpageHeader .= Xml::tags( 'strong', array( 'class' => 'lqt_start_discussion' ), |
239 | | - $newThreadLink ); |
| 232 | + if ( Thread::canUserPost( $this->user, $this->article ) ) { |
| 233 | + $newThreadText = wfMsgExt( 'lqt_new_thread', 'parseinline' ); |
| 234 | + $newThreadLink = $sk->link( $this->title, $newThreadText, |
| 235 | + array( ), |
| 236 | + array( 'lqt_method' => 'talkpage_new_thread' ), |
| 237 | + array( 'known' ) ); |
| 238 | + |
| 239 | + $talkpageHeader .= Xml::tags( 'strong', |
| 240 | + array( 'class' => 'lqt_start_discussion' ), |
| 241 | + $newThreadLink ); |
| 242 | + } |
| 243 | + |
240 | 244 | $talkpageHeader .= $this->getSearchBox(); |
241 | 245 | $talkpageHeader .= $this->showTalkpageViewOptions( $article ); |
242 | 246 | $talkpageHeader = Xml::tags( 'div', array( 'class' => 'lqt-talkpage-header' ), |
Index: trunk/extensions/LiquidThreads/api/ApiThreadAction.php |
— | — | @@ -277,6 +277,12 @@ |
278 | 278 | } |
279 | 279 | $talkpage = new Article( $talkpageTitle ); |
280 | 280 | |
| 281 | + // Check if we can post. |
| 282 | + if ( Thread::canUserPost( $wgUser, $talkpage ) !== true ) { |
| 283 | + $this->dieUsage( 'You cannot post to the specified talkpage, '. |
| 284 | + 'because it is protected from new posts', 'talkpage-protected' ); |
| 285 | + return; |
| 286 | + } |
281 | 287 | |
282 | 288 | // Validate subject, generate a title |
283 | 289 | if ( empty( $params['subject'] ) ) { |
— | — | @@ -384,6 +390,15 @@ |
385 | 391 | } |
386 | 392 | $replyTo = array_pop( $threads ); |
387 | 393 | |
| 394 | + // Check if we can reply to that thread. |
| 395 | + $perm_result = $replyTo->canUserReply( $wgUser ); |
| 396 | + if ( $perm_result !== true ) { |
| 397 | + $this->dieUsage( "You cannot reply to this thread, because the ". |
| 398 | + $perm_result." is protected from replies.", |
| 399 | + $perm_result.'-protected' ); |
| 400 | + return; |
| 401 | + } |
| 402 | + |
388 | 403 | // Validate text parameter |
389 | 404 | if ( empty( $params['text'] ) ) { |
390 | 405 | $this->dieUsage( 'You must include text in your post', 'no-text' ); |