r51018 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r51017‎ | r51018 | r51019 >
Date:13:36, 26 May 2009
Author:werdna
Status:deferred
Tags:
Comment:
Break out LqtBaseView.php into classes/LqtDispatch.php and classes/LqtView.php to reflect the two classes defined in that file
Modified paths:
  • /trunk/extensions/LiquidThreads/LiquidThreads.php (modified) (history)
  • /trunk/extensions/LiquidThreads/LqtBaseView.php (deleted) (history)
  • /trunk/extensions/LiquidThreads/classes/LqtDispatch.php (added) (history)
  • /trunk/extensions/LiquidThreads/classes/LqtView.php (added) (history)

Diff [purge]

Index: trunk/extensions/LiquidThreads/LqtBaseView.php
@@ -1,1029 +0,0 @@
2 -<?php
3 -
4 -/**
5 -* @package MediaWiki
6 -* @subpackage LiquidThreads
7 -* @author David McCabe <davemccabe@gmail.com>
8 -* @licence GPL2
9 -*/
10 -
11 -if ( !defined( 'MEDIAWIKI' ) ) {
12 - echo( "This file is an extension to the MediaWiki software and cannot be used standalone.\n" );
13 - die( - 1 );
14 -}
15 -
16 -class LqtDispatch {
17 - public static $views = array(
18 - 'TalkpageArchiveView' => 'TalkpageArchiveView',
19 - 'TalkpageHeaderView' => 'TalkpageHeaderView',
20 - 'TalkpageView' => 'TalkpageView',
21 - 'ThreadHistoryListingView' => 'ThreadHistoryListingView',
22 - 'ThreadHistoricalRevisionView' => 'ThreadHistoricalRevisionView',
23 - 'IndividualThreadHistoryView' => 'IndividualThreadHistoryView',
24 - 'ThreadDiffView' => 'ThreadDiffView',
25 - 'ThreadPermalinkView' => 'ThreadPermalinkView',
26 - 'ThreadProtectionFormView' => 'ThreadProtectionFormView',
27 - 'ThreadWatchView' => 'ThreadWatchView',
28 - 'SummaryPageView' => 'SummaryPageView'
29 - );
30 -
31 - static function talkpageMain( &$output, &$talk_article, &$title, &$user, &$request ) {
32 - // We are given a talkpage article and title. Find the associated
33 - // non-talk article and pass that to the view.
34 - $article = new Article( $title->getSubjectPage() );
35 -
36 - if ( $title->getSubjectPage()->getNamespace() == NS_LQT_THREAD ) {
37 - // Threads don't have talk pages; redirect to the thread page.
38 - $output->redirect( $title->getSubjectPage()->getFullUrl() );
39 - }
40 -
41 - /* Certain actions apply to the "header", which is stored in the actual talkpage
42 - in the database. Drop everything and behave like a normal page if those
43 - actions come up, to avoid hacking the various history, editing, etc. code. */
44 - $action = $request->getVal( 'action' );
45 - $header_actions = array( 'history', 'edit', 'submit' );
46 - global $wgRequest;
47 - if ( $request->getVal( 'lqt_method', null ) === null &&
48 - ( in_array( $action, $header_actions ) ||
49 - $request->getVal( 'diff', null ) !== null ) ) {
50 - $viewname = self::$views['TalkpageHeaderView'];
51 - } else if ( $action == 'protect' || $action == 'unprotect' ) {
52 - $viewname = self::$views['ThreadProtectionFormView'];
53 - } else if ( $request->getVal( 'lqt_method' ) == 'talkpage_archive' ) {
54 - $viewname = self::$views['TalkpageArchiveView'];
55 - } else {
56 - $viewname = self::$views['TalkpageView'];
57 - }
58 - $view = new $viewname( $output, $article, $title, $user, $request );
59 - return $view->show();
60 - }
61 -
62 - static function threadPermalinkMain( &$output, &$article, &$title, &$user, &$request ) {
63 -
64 - $action = $request->getVal( 'action' );
65 - $lqt_method = $request->getVal( 'lqt_method' );
66 -
67 - if ( $lqt_method == 'thread_history' ) {
68 - $viewname = self::$views['ThreadHistoryListingView'];
69 - }
70 - else if ( $lqt_method == 'diff' ) { // this clause and the next must be in this order.
71 - $viewname = self::$views['ThreadDiffView'];
72 - }
73 - else if ( $action == 'history'
74 - || $request->getVal( 'diff', null ) !== null
75 - || $request->getVal( 'oldid', null ) !== null ) {
76 - $viewname = self::$views['IndividualThreadHistoryView'];
77 - }
78 - else if ( $action == 'protect' || $action == 'unprotect' ) {
79 - $viewname = self::$views['ThreadProtectionFormView'];
80 - }
81 - else if ( $request->getVal( 'lqt_oldid', null ) !== null ) {
82 - $viewname = self::$views['ThreadHistoricalRevisionView'];
83 - }
84 - else if ( $action == 'watch' || $action == 'unwatch' ) {
85 - $viewname = self::$views['ThreadWatchView'];
86 - }
87 - else {
88 - $viewname = self::$views['ThreadPermalinkView'];
89 - }
90 - $view = new $viewname( $output, $article, $title, $user, $request );
91 - return $view->show();
92 - }
93 -
94 - static function threadSummaryMain( &$output, &$article, &$title, &$user, &$request ) {
95 - $viewname = self::$views['SummaryPageView'];
96 - $view = new $viewname( $output, $article, $title, $user, $request );
97 - return $view->show();
98 - }
99 -
100 - /**
101 - * If the page we recieve is a Liquid Threads page of any kind, process it
102 - * as needed and return True. If it's a normal, non-liquid page, return false.
103 - */
104 - static function tryPage( $output, $article, $title, $user, $request ) {
105 - if ( $title->isTalkPage() ) {
106 - return self::talkpageMain ( $output, $article, $title, $user, $request );
107 - } else if ( $title->getNamespace() == NS_LQT_THREAD ) {
108 - return self::threadPermalinkMain( $output, $article, $title, $user, $request );
109 - } else if ( $title->getNamespace() == NS_LQT_SUMMARY ) {
110 - return self::threadSummaryMain( $output, $article, $title, $user, $request );
111 - }
112 - return true;
113 - }
114 -
115 - static function onPageMove( $movepage, $ot, $nt ) {
116 - // We are being invoked on the subject page, not the talk page.
117 -
118 - $threads = Threads::where( array( Threads::articleClause( new Article( $ot ) ),
119 - Threads::topLevelClause() ) );
120 -
121 - foreach ( $threads as $t ) {
122 - $t->moveToSubjectPage( $nt, false );
123 - }
124 -
125 - return true;
126 - }
127 -
128 - static function makeLinkObj( &$returnValue, &$linker, $nt, $text, $query, $trail, $prefix ) {
129 - if ( ! $nt->isTalkPage() )
130 - return true;
131 -
132 - // Talkpages with headers.
133 - if ( $nt->getArticleID() != 0 )
134 - return true;
135 -
136 - // Talkpages without headers -- check existance of threads.
137 - $article = new Article( $nt->getSubjectPage() );
138 - $threads = Threads::where( Threads::articleClause( $article ), "LIMIT 1" );
139 - if ( count( $threads ) == 0 ) {
140 - // We want it to look like a broken link, but not have action=edit, since that
141 - // will edit the header, so we can't use makeBrokenLinkObj. This code is copied
142 - // from the body of that method.
143 - $url = $nt->escapeLocalURL( $query );
144 - if ( '' == $text )
145 - $text = htmlspecialchars( $nt->getPrefixedText() );
146 - $style = $linker->getInternalLinkAttributesObj( $nt, $text, "yes" );
147 - list( $inside, $trail ) = Linker::splitTrail( $trail );
148 - $returnValue = "<a href=\"{$url}\"{$style}>{$prefix}{$text}{$inside}</a>{$trail}";
149 - }
150 - else {
151 - $returnValue = $linker->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix );
152 - }
153 - return false;
154 - }
155 -
156 - // One major place that doesn't use makeLinkObj is the tabs. So override known/unknown there too.
157 - static function tabAction( &$skintemplate, $title, $message, $selected, $checkEdit,
158 - &$classes, &$query, &$text, &$result ) {
159 - if ( ! $title->isTalkPage() )
160 - return true;
161 - if ( $title->getArticleID() != 0 ) {
162 - $query = "";
163 - return true;
164 - }
165 - // It's a talkpage without a header. Get rid of action=edit always,
166 - // color as apropriate.
167 - $query = "";
168 - $article = new Article( $title->getSubjectPage() );
169 - $threads = Threads::where( Threads::articleClause( $article ), "LIMIT 1" );
170 - if ( count( $threads ) != 0 ) {
171 - $i = array_search( 'new', $classes ); if ( $i !== false ) {
172 - array_splice( $classes, $i, 1 );
173 - }
174 - }
175 - return true;
176 - }
177 -
178 - static function customizeOldChangesList( &$changeslist, &$s, &$rc ) {
179 - if ( $rc->getTitle()->getNamespace() == NS_LQT_THREAD ) {
180 - $thread = Threads::withRoot( new Post( $rc->getTitle() ) );
181 - if ( !$thread ) return true;
182 -
183 - LqtView::addJSandCSS(); // TODO only do this once.
184 - wfLoadExtensionMessages( 'LiquidThreads' );
185 -
186 - if ( $rc->mAttribs['rc_type'] != RC_NEW ) {
187 - // Add whether it was original author.
188 - // TODO: this only asks whether ANY edit has been by another, not this edit.
189 - // But maybe that's what we want.
190 - if ( $thread->editedness() == Threads::EDITED_BY_OTHERS )
191 - $appendix = ' <span class="lqt_rc_author_notice lqt_rc_author_notice_others">' .
192 - wfMsg( 'lqt_rc_author_others' ) . '</span>';
193 - else
194 - $appendix = ' <span class="lqt_rc_author_notice lqt_rc_author_notice_original">' .
195 - wfMsg( 'lqt_rc_author_original' ) . '</span>';
196 - $s = preg_replace( '/\<\/li\>$/', $appendix . '</li>', $s );
197 - }
198 - else {
199 - $sig = "";
200 - $changeslist->insertUserRelatedLinks( $sig, $rc );
201 -
202 - // This should be stored in RC.
203 - $quote = Revision::newFromId( $rc->mAttribs['rc_this_oldid'] )->getText();
204 - if ( strlen( $quote ) > 230 ) {
205 - $quote = substr( $quote, 0, 200 ) .
206 - $changeslist->skin->link( $thread->title(), wfMsg( 'lqt_rc_ellipsis' ),
207 - array( 'class' => 'lqt_rc_ellipsis' ), array(), array( 'known' ) );
208 - }
209 - // TODO we must parse or sanitize the quote.
210 -
211 - if ( $thread->isTopmostThread() ) {
212 - $message_name = 'lqt_rc_new_discussion';
213 - $tmp_title = $thread->title();
214 - } else {
215 - $message_name = 'lqt_rc_new_reply';
216 - $tmp_title = $thread->topmostThread()->title();
217 - $tmp_title->setFragment( '#' . LqtView::anchorName( $thread ) );
218 - }
219 -
220 - $thread_link = $changeslist->skin->link(
221 - $tmp_title,
222 - $thread->subjectWithoutIncrement(),
223 - array(), array(), array( 'known' ) );
224 -
225 - $talkpage_link = $changeslist->skin->link(
226 - $thread->article()->getTitle()->getTalkPage(),
227 - null,
228 - array(), array(), array( 'known' ) );
229 -
230 - $s = wfMsg( $message_name, $thread_link, $talkpage_link, $sig )
231 - . "<blockquote class=\"lqt_rc_blockquote\">$quote</blockquote>";
232 - }
233 - }
234 - return true;
235 - }
236 -
237 - static function setNewtalkHTML( $skintemplate, $tpl ) {
238 - global $wgUser, $wgTitle, $wgOut;
239 - wfLoadExtensionMessages( 'LiquidThreads' );
240 - $newmsg_t = SpecialPage::getTitleFor( 'NewMessages' );
241 - $watchlist_t = SpecialPage::getTitleFor( 'Watchlist' );
242 - $usertalk_t = $wgUser->getTalkPage();
243 - if ( $wgUser->getNewtalk()
244 - && ! $newmsg_t->equals( $wgTitle )
245 - && ! $watchlist_t->equals( $wgTitle )
246 - && ! $usertalk_t->equals( $wgTitle )
247 - ) {
248 - $s = wfMsgExt( 'lqt_youhavenewmessages', array( 'parseinline' ), $newmsg_t->getFullURL() );
249 - $tpl->set( "newtalk", $s );
250 - $wgOut->setSquidMaxage( 0 );
251 - } else {
252 - $tpl->set( "newtalk", '' );
253 - }
254 -
255 - return true;
256 - }
257 -}
258 -
259 -
260 -class LqtView {
261 - protected $article;
262 - protected $output;
263 - protected $user;
264 - protected $title;
265 - protected $request;
266 -
267 - protected $headerLevel = 2; /* h1, h2, h3, etc. */
268 - protected $maxIndentationLevel = 4;
269 - protected $lastUnindentedSuperthread;
270 -
271 - protected $user_colors;
272 - protected $user_color_index;
273 - const number_of_user_colors = 6;
274 -
275 - protected $queries;
276 -
277 - protected $sort_order = LQT_NEWEST_CHANGES;
278 -
279 - function __construct( &$output, &$article, &$title, &$user, &$request ) {
280 - $this->article = $article;
281 - $this->output = $output;
282 - $this->user = $user;
283 - $this->title = $title;
284 - $this->request = $request;
285 - $this->user_colors = array();
286 - $this->user_color_index = 1;
287 - $this->queries = $this->initializeQueries();
288 - }
289 -
290 - function setHeaderLevel( $int ) {
291 - $this->headerLevel = $int;
292 - }
293 -
294 - function initializeQueries() {
295 -
296 - if ( $this->methodApplies( 'talkpage_sort_order' ) ) {
297 - // Sort order is explicitly specified through UI
298 - global $wgRequest;
299 - $lqt_order = $wgRequest->getVal( 'lqt_order' );
300 - switch( $lqt_order ) {
301 - case 'nc':
302 - $this->sort_order = LQT_NEWEST_CHANGES;
303 - break;
304 - case 'nt':
305 - $this->sort_order = LQT_NEWEST_THREADS;
306 - break;
307 - case 'ot':
308 - $this->sort_order = LQT_OLDEST_THREADS;
309 - break;
310 - }
311 - } else {
312 - // Sort order set in user preferences overrides default
313 - global $wgUser;
314 - $user_order = $wgUser->getOption( 'lqt_sort_order' ) ;
315 - if ( $user_order ) {
316 - $this->sort_order = $user_order;
317 - }
318 - }
319 - global $wgOut, $wgLqtThreadArchiveStartDays, $wgLqtThreadArchiveInactiveDays;
320 - $g = new QueryGroup();
321 - $startdate = Date::now()->nDaysAgo( $wgLqtThreadArchiveStartDays )->midnight();
322 - $recentstartdate = $startdate->nDaysAgo( $wgLqtThreadArchiveInactiveDays );
323 - $article_clause = Threads::articleClause( $this->article );
324 - if ( $this->sort_order == LQT_NEWEST_CHANGES ) {
325 - $sort_clause = 'ORDER BY thread.thread_modified DESC';
326 - } elseif ( $this->sort_order == LQT_NEWEST_THREADS ) {
327 - $sort_clause = 'ORDER BY thread.thread_created DESC';
328 - } elseif ( $this->sort_order == LQT_OLDEST_THREADS ) {
329 - $sort_clause = 'ORDER BY thread.thread_created ASC';
330 - }
331 - $g->addQuery( 'fresh',
332 - array( $article_clause,
333 - 'thread.thread_parent is null',
334 - '(thread.thread_modified >= ' . $startdate->text() .
335 - ' OR (thread.thread_summary_page is NULL' .
336 - ' AND thread.thread_type=' . Threads::TYPE_NORMAL . '))' ),
337 - array( $sort_clause ) );
338 - $g->addQuery( 'archived',
339 - array( $article_clause,
340 - 'thread.thread_parent is null',
341 - '(thread.thread_summary_page is not null' .
342 - ' OR thread.thread_type=' . Threads::TYPE_NORMAL . ')',
343 - 'thread.thread_modified < ' . $startdate->text() ),
344 - array( $sort_clause ) );
345 - $g->extendQuery( 'archived', 'recently-archived',
346 - array( '( thread.thread_modified >=' . $recentstartdate->text() .
347 - ' OR rev_timestamp >= ' . $recentstartdate->text() . ')',
348 - 'summary_page.page_id = thread.thread_summary_page', 'summary_page.page_latest = rev_id' ),
349 - array(),
350 - array( 'page summary_page', 'revision' ) );
351 - return $g;
352 - }
353 -
354 - static protected $occupied_titles = array();
355 -
356 - /*************************
357 - * (1) linking to liquidthreads pages and
358 - * (2) figuring out what page you're on and what you need to do.
359 - *************************/
360 -
361 - static function queryStringFromArray( $vars ) {
362 - $q = '';
363 - if ( $vars && count( $vars ) != 0 ) {
364 - foreach ( $vars as $name => $value )
365 - $q .= "$name=$value&";
366 - }
367 - return $q;
368 - }
369 -
370 - function methodAppliesToThread( $method, $thread ) {
371 - return $this->request->getVal( 'lqt_method' ) == $method &&
372 - $this->request->getVal( 'lqt_operand' ) == $thread->id();
373 - }
374 - function methodApplies( $method ) {
375 - return $this->request->getVal( 'lqt_method' ) == $method;
376 - }
377 -
378 - static function permalinkUrl( $thread, $method = null, $operand = null ) {
379 - $query = $method ? "lqt_method=$method" : "";
380 - $query = $operand ? "$query&lqt_operand={$operand->id()}" : $query;
381 - return $thread->root()->getTitle()->getFullUrl( $query );
382 - }
383 -
384 - /* This is used for action=history so that the history tab works, which is
385 - why we break the lqt_method paradigm. */
386 - static function permalinkUrlWithQuery( $thread, $query ) {
387 - if ( is_array( $query ) ) $query = self::queryStringFromArray( $query );
388 - return $thread->root()->getTitle()->getFullUrl( $query );
389 - }
390 -
391 - static function permalinkUrlWithDiff( $thread ) {
392 - $changed_thread = $thread->changeObject();
393 - $curr_rev_id = $changed_thread->rootRevision();
394 - $curr_rev = Revision::newFromTitle( $changed_thread->root()->getTitle(), $curr_rev_id );
395 - $prev_rev = $curr_rev->getPrevious();
396 - $oldid = $prev_rev ? $prev_rev->getId() : "";
397 - return self::permalinkUrlWithQuery( $changed_thread, array( 'lqt_method' => 'diff', 'diff' => $curr_rev_id, 'oldid' => $oldid ) );
398 - }
399 -
400 - static function talkpageUrl( $title, $method = null, $operand = null, $includeFragment = true ) {
401 - global $wgRequest; // TODO global + ugly hack.
402 - $query = $method ? "lqt_method=$method" : "";
403 - $query = $operand ? "$query&lqt_operand={$operand->id()}" : $query;
404 - $oldid = $wgRequest->getVal( 'oldid', null ); if ( $oldid !== null ) {
405 - // this is an immensely ugly hack to make editing old revisions work.
406 - $query = "$query&oldid=$oldid";
407 - }
408 - return $title->getFullURL( $query ) . ( $operand && $includeFragment ? "#lqt_thread_{$operand->id()}" : "" );
409 - }
410 -
411 -
412 - /**
413 - * Return a URL for the current page, including Title and query vars,
414 - * with the given replacements made.
415 - * @param $repls array( 'name'=>new_value, ... )
416 - */
417 - function queryReplace( $repls ) {
418 - $vs = $this->request->getValues();
419 - $rs = array();
420 -/* foreach ($vs as $k => $v) {
421 - if ( array_key_exists( $k, $repls ) ) {
422 - $rs[$k] = $repls[$k];
423 - } else {
424 - $rs[$k] = $vs[$k];
425 - }
426 - }
427 -*/
428 - foreach ( $repls as $k => $v ) {
429 - $vs[$k] = $v;
430 - }
431 - return $this->title->getFullURL( self::queryStringFromArray( $vs ) );
432 - }
433 -
434 - /*************************************************************
435 - * Editing methods (here be dragons) *
436 - * Forget dragons: This section distorts the rest of the code *
437 - * like a star bending spacetime around itself. *
438 - *************************************************************/
439 -
440 - /**
441 - * Return an HTML form element whose value is gotten from the request.
442 - * TODO: figure out a clean way to expand this to other forms.
443 - */
444 - function perpetuate( $name, $as ) {
445 - $value = $this->request->getVal( $name, '' );
446 - if ( $as == 'hidden' ) {
447 - return <<<HTML
448 - <input type="hidden" name="$name" id="$name" value="$value">
449 -HTML;
450 - }
451 - }
452 -
453 - function showReplyProtectedNotice( $thread ) {
454 - wfLoadExtensionMessages( 'LiquidThreads' );
455 - $log_url = SpecialPage::getTitleFor( 'Log' )->getFullURL(
456 - "type=protect&user=&page={$thread->title()->getPrefixedURL()}" );
457 - $this->output->addHTML( '<p>' . wfMsg( 'lqt_protectedfromreply',
458 - '<a href="' . $log_url . '">' . wfMsg( 'lqt_protectedfromreply_link' ) . '</a>' ) );
459 - }
460 -
461 - function showNewThreadForm() {
462 - $this->showEditingFormInGeneral( null, 'new', null );
463 - }
464 -
465 - function showPostEditingForm( $thread ) {
466 - $this->showEditingFormInGeneral( $thread, 'editExisting', null );
467 - }
468 -
469 - function showReplyForm( $thread ) {
470 - if ( $thread->root()->getTitle()->userCan( 'edit' ) ) {
471 - $this->showEditingFormInGeneral( null, 'reply', $thread );
472 - } else {
473 - $this->showReplyProtectedNotice( $thread );
474 - }
475 - }
476 -
477 - function showSummarizeForm( $thread ) {
478 - $this->showEditingFormInGeneral( null, 'summarize', $thread );
479 - }
480 -
481 - private function showEditingFormInGeneral( $thread, $edit_type, $edit_applies_to ) {
482 - /*
483 - EditPage needs an Article. If there isn't a real one, as for new posts,
484 - replies, and new summaries, we need to generate a title. Auto-generated
485 - titles are based on the subject line. If the subject line is blank, we
486 - can temporarily use a random scratch title. It's fine if the title changes
487 - throughout the edit cycle, since the article doesn't exist yet anyways.
488 - */
489 - if ( $edit_type == 'summarize' && $edit_applies_to->summary() ) {
490 - $article = $edit_applies_to->summary();
491 - } else if ( $edit_type == 'summarize' ) {
492 - $t = $this->newSummaryTitle( $edit_applies_to );
493 - $article = new Article( $t );
494 - } else if ( $thread == null ) {
495 - $subject = $this->request->getVal( 'lqt_subject_field', '' );
496 - if ( $edit_type == 'new' ) {
497 - $t = $this->newScratchTitle( $subject );
498 - } else if ( $edit_type == 'reply' ) {
499 - $t = $this->newReplyTitle( $subject, $edit_applies_to );
500 - }
501 - $article = new Article( $t );
502 - } else {
503 - $article = $thread->root();
504 - }
505 -
506 - $e = new EditPage( $article );
507 -
508 - $e->suppressIntro = true;
509 - $e->editFormTextBeforeContent .=
510 - $this->perpetuate( 'lqt_method', 'hidden' ) .
511 - $this->perpetuate( 'lqt_operand', 'hidden' );
512 -
513 - if ( $edit_type == 'new' || ( $thread && !$thread->hasSuperthread() ) ) {
514 - wfLoadExtensionMessages( 'LiquidThreads' );
515 - // This is a top-level post; show the subject line.
516 - $db_subject = $thread ? $thread->subjectWithoutIncrement() : '';
517 - $subject = $this->request->getVal( 'lqt_subject_field', $db_subject );
518 - $subject_label = wfMsg( 'lqt_subject' );
519 - $e->editFormTextBeforeContent .= <<<HTML
520 - <label for="lqt_subject_field">$subject_label</label>
521 - <input type="text" size="60" name="lqt_subject_field" id="lqt_subject_field" value="$subject" tabindex="1"><br />
522 -HTML;
523 - }
524 -
525 - $e->edit();
526 -
527 - // Override what happens in EditPage::showEditForm, called from $e->edit():
528 -
529 - $this->output->setArticleFlag( false );
530 -
531 - // For replies and new posts, insert the associated thread object into the DB.
532 - if ( $edit_type != 'editExisting' && $edit_type != 'summarize' && $e->didSave ) {
533 - if ( $edit_type == 'reply' ) {
534 - $thread = Threads::newThread( $article, $this->article, $edit_applies_to, $e->summary );
535 - $edit_applies_to->commitRevision( Threads::CHANGE_REPLY_CREATED, $thread, $e->summary );
536 - } else {
537 - $thread = Threads::newThread( $article, $this->article, null, $e->summary );
538 - }
539 - }
540 -
541 - if ( $edit_type == 'summarize' && $e->didSave ) {
542 - $edit_applies_to->setSummary( $article );
543 - $edit_applies_to->commitRevision( Threads::CHANGE_EDITED_SUMMARY, $edit_applies_to, $e->summary );
544 - }
545 -
546 - // Move the thread and replies if subject changed.
547 - if ( $edit_type == 'editExisting' && $e->didSave ) {
548 - $subject = $this->request->getVal( 'lqt_subject_field', '' );
549 - if ( $subject && $subject != $thread->subjectWithoutIncrement() ) {
550 -
551 - $this->renameThread( $thread, $subject, $e->summary );
552 - }
553 - // this is unrelated to the subject change and is for all edits:
554 - $thread->setRootRevision( Revision::newFromTitle( $thread->root()->getTitle() ) );
555 - $thread->commitRevision( Threads::CHANGE_EDITED_ROOT, $thread, $e->summary );
556 - }
557 -
558 - // A redirect without $e->didSave will happen if the new text is blank (EditPage::attemptSave).
559 - // This results in a new Thread object not being created for replies and new discussions,
560 - // so $thread is null. In that case, just allow editpage to redirect back to the talk page.
561 - if ( $this->output->getRedirect() != '' && $thread ) {
562 - $this->output->redirect( $this->title->getFullURL() . '#' . 'lqt_thread_' . $thread->id() );
563 - } else if ( $this->output->getRedirect() != '' && $edit_applies_to ) {
564 - // For summaries:
565 - $this->output->redirect( $edit_applies_to->title()->getFullURL() . '#' . 'lqt_thread_' . $edit_applies_to->id() );
566 - }
567 - }
568 -
569 - function renameThread( $t, $s, $reason ) {
570 - $this->simplePageMove( $t->root()->getTitle(), $s, $reason );
571 - // TODO here create a redirect from old page to new.
572 - foreach ( $t->subthreads() as $st ) {
573 - $this->renameThread( $st, $s, $reason );
574 - }
575 - }
576 -
577 - function scratchTitle() {
578 - $token = md5( uniqid( rand(), true ) );
579 - return Title::newFromText( "Thread:$token" );
580 - }
581 - function newScratchTitle( $subject ) {
582 - wfLoadExtensionMessages( 'LiquidThreads' );
583 - return $this->incrementedTitle( $subject ? $subject:wfMsg( 'lqt_nosubject' ), NS_LQT_THREAD );
584 - }
585 - function newSummaryTitle( $t ) {
586 - return $this->incrementedTitle( $t->subject(), NS_LQT_SUMMARY );
587 - }
588 - function newReplyTitle( $s, $t ) {
589 - return $this->incrementedTitle( $t->subjectWithoutIncrement(), NS_LQT_THREAD );
590 - }
591 - /** Keep trying titles starting with $basename until one is unoccupied. */
592 - public static function incrementedTitle( $basename, $namespace ) {
593 - $i = 1; do {
594 - $t = Title::newFromText( $basename . '_(' . $i . ')', $namespace );
595 - $i++;
596 - } while ( $t->exists() || in_array( $t->getPrefixedDBkey(), self::$occupied_titles ) );
597 - return $t;
598 - }
599 -
600 - /* Adapted from MovePageForm::doSubmit in SpecialMovepage.php. */
601 - function simplePageMove( $old_title, $new_subject, $reason ) {
602 - if ( $this->user->pingLimiter( 'move' ) ) {
603 - $this->out->rateLimited();
604 - return false;
605 - }
606 -
607 - # Variables beginning with 'o' for old article 'n' for new article
608 -
609 - $ot = $old_title;
610 - $nt = $this->incrementedTitle( $new_subject, $old_title->getNamespace() );
611 -
612 - self::$occupied_titles[] = $nt->getPrefixedDBkey();
613 -
614 - # don't allow moving to pages with # in
615 - if ( !$nt || $nt->getFragment() != '' ) {
616 - echo "malformed title"; // TODO real error reporting.
617 - return false;
618 - }
619 -
620 - $error = $ot->moveTo( $nt, true, "Changed thread subject: $reason" );
621 - if ( $error !== true ) {
622 - var_dump( $error );
623 - echo "something bad happened trying to rename the thread."; // TODO
624 - return false;
625 - }
626 -
627 - # Move the talk page if relevant, if it exists, and if we've been told to
628 - // TODO we need to implement correct moving of talk pages everywhere later.
629 - // Snipped.
630 -
631 - return true;
632 - }
633 -
634 - /**
635 - * Example return value:
636 - * array (
637 - * 0 => array( 'label' => 'Edit',
638 - * 'href' => 'http...',
639 - * 'enabled' => false ),
640 - * 1 => array( 'label' => 'Reply',
641 - * 'href' => 'http...',
642 - * 'enabled' => true )
643 - * )
644 - */
645 - function threadFooterCommands( $thread ) {
646 - wfLoadExtensionMessages( 'LiquidThreads' );
647 - $commands = array();
648 -
649 - $user_can_edit = $thread->root()->getTitle()->quickUserCan( 'edit' );
650 -
651 - $commands[] = array( 'label' => $user_can_edit ? wfMsg( 'edit' ) : wfMsg( 'viewsource' ),
652 - 'href' => $this->talkpageUrl( $this->title, 'edit', $thread ),
653 - 'enabled' => true );
654 -
655 - $commands[] = array( 'label' => wfMsg( 'history_short' ),
656 - 'href' => $this->permalinkUrlWithQuery( $thread, 'action=history' ),
657 - 'enabled' => true );
658 -
659 - $commands[] = array( 'label' => wfMsg( 'lqt_permalink' ),
660 - 'href' => $this->permalinkUrl( $thread ),
661 - 'enabled' => true );
662 -
663 - if ( in_array( 'delete', $this->user->getRights() ) ) {
664 - $delete_url = SpecialPage::getTitleFor( 'DeleteThread' )->getFullURL()
665 - . '/' . $thread->title()->getPrefixedURL();
666 - $commands[] = array( 'label' => $thread->type() == Threads::TYPE_DELETED ? wfMsg( 'lqt_undelete' ) : wfMsg( 'delete' ),
667 - 'href' => $delete_url,
668 - 'enabled' => true );
669 - }
670 -
671 - $commands[] = array( 'label' => '<b class="lqt_reply_link">' . wfMsg( 'lqt_reply' ) . '</b>',
672 - 'href' => $this->talkpageUrl( $this->title, 'reply', $thread ),
673 - 'enabled' => $user_can_edit );
674 -
675 - return $commands;
676 - }
677 -
678 - function topLevelThreadCommands( $thread ) {
679 - wfLoadExtensionMessages( 'LiquidThreads' );
680 - $commands = array();
681 -
682 - $commands[] = array( 'label' => wfMsg( 'history_short' ),
683 - 'href' => $this->permalinkUrl( $thread, 'thread_history' ),
684 - 'enabled' => true );
685 -
686 - if ( in_array( 'move', $this->user->getRights() ) ) {
687 - $move_href = SpecialPage::getTitleFor( 'MoveThread' )->getFullURL()
688 - . '/' . $thread->title()->getPrefixedURL();
689 - $commands[] = array( 'label' => wfMsg( 'move' ),
690 - 'href' => $move_href,
691 - 'enabled' => true );
692 - }
693 - if ( !$this->user->isAnon() && !$thread->title()->userIsWatching() ) {
694 - $commands[] = array( 'label' => wfMsg( 'watch' ),
695 - 'href' => $this->permalinkUrlWithQuery( $thread, 'action=watch' ),
696 - 'enabled' => true );
697 - } else if ( !$this->user->isAnon() ) {
698 - $commands[] = array( 'label' => wfMsg( 'unwatch' ),
699 - 'href' => $this->permalinkUrlWithQuery( $thread, 'action=unwatch' ),
700 - 'enabled' => true );
701 - }
702 -
703 - return $commands;
704 - }
705 -
706 - /*************************
707 - * Output methods *
708 - *************************/
709 -
710 - static function addJSandCSS() {
711 - // Changed this to be static so that we can call it from
712 - // wfLqtBeforeWatchlistHook.
713 - global $wgJsMimeType, $wgScriptPath, $wgStyleVersion; // TODO globals.
714 - global $wgOut;
715 - $s = <<< HTML
716 - <script type="{$wgJsMimeType}" src="{$wgScriptPath}/extensions/LiquidThreads/lqt.js"><!-- lqt js --></script>
717 - <style type="text/css" media="screen, projection">/*<![CDATA[*/
718 - @import "{$wgScriptPath}/extensions/LiquidThreads/lqt.css?{$wgStyleVersion}";
719 - /*]]>*/</style>
720 -
721 -HTML;
722 - $wgOut->addScript( $s );
723 - }
724 -
725 - /* @return False if the article and revision do not exist and we didn't show it, true if we did. */
726 - function showPostBody( $post, $oldid = null ) {
727 - /* Why isn't this all encapsulated in Article somewhere? TODO */
728 - global $wgEnableParserCache;
729 -
730 - // Should the parser cache be used?
731 - $pcache = $wgEnableParserCache &&
732 - intval( $this->user->getOption( 'stubthreshold' ) ) == 0 &&
733 - $post->exists() &&
734 - $oldid === null;
735 - wfDebug( 'LqtView::showPostBody using parser cache: ' . ( $pcache ? 'yes' : 'no' ) . "\n" );
736 - if ( $this->user->getOption( 'stubthreshold' ) ) {
737 - wfIncrStats( 'pcache_miss_stub' );
738 - }
739 -
740 - $outputDone = false;
741 - if ( $pcache ) {
742 - $outputDone = $this->output->tryParserCache( $post, $this->user );
743 - }
744 -
745 - if ( !$outputDone ) {
746 - // Cache miss; parse and output it.
747 - $rev = Revision::newFromTitle( $post->getTitle(), $oldid );
748 - if ( $rev && $oldid ) {
749 - // don't save oldids in the parser cache.
750 - $this->output->addWikiText( $rev->getText() );
751 - return true;
752 - }
753 - else if ( $rev ) {
754 - $post->outputWikiText( $rev->getText(), true );
755 - return true;
756 - } else {
757 - return false;
758 - }
759 - } else {
760 - return true;
761 - }
762 - }
763 -
764 - function colorTest() {
765 - $this->output->addHTML( '<div class="lqt_footer"><li class="lqt_footer_sig">' );
766 - for ( $i = 1; $i <= self::number_of_user_colors; $i++ ) {
767 - $this->output->addHTML( "<span class=\"lqt_post_color_{$i}\"><a href=\"foo\">DavidMcCabe</a></span>" );
768 - }
769 - $this->output->addHTML( '</li></div>' );
770 - }
771 -
772 - function showThreadFooter( $thread ) {
773 - global $wgLang; // TODO global.
774 -
775 - $author = $thread->root()->originalAuthor();
776 - $color_number = $this->selectNewUserColor( $author );
777 -
778 - $sig = $this->user->getSkin()->userLink( $author->getID(), $author->getName() ) .
779 - $this->user->getSkin()->userToolLinks( $author->getID(), $author->getName() );
780 -
781 - $timestamp = $wgLang->timeanddate( $thread->created(), true );
782 -
783 - $this->output->addHTML( <<<HTML
784 -<ul class="lqt_footer">
785 -<span class="lqt_footer_sig">
786 -<li class="lqt_author_sig lqt_post_color_{$color_number}">$sig</li>
787 -HTML
788 - );
789 -
790 - if ( $thread->editedness() == Threads::EDITED_BY_AUTHOR || $thread->editedness() == Threads::EDITED_BY_OTHERS ) {
791 - wfLoadExtensionMessages( 'LiquidThreads' );
792 - $editedness_url = $this->permalinkUrlWithQuery( $thread, 'action=history' );
793 - $editedness_color_number = $thread->editedness() == Threads::EDITED_BY_AUTHOR ?
794 - $color_number : ( $color_number == self::number_of_user_colors ? 1 : $color_number + 1 );
795 - $this->output->addHTML( "<li class=\"lqt_edited_notice lqt_post_color_{$editedness_color_number}\">" .
796 - '<a href="' . $editedness_url . '">' . wfMsg( 'lqt_edited_notice' ) . '</a>' . '</li>' );
797 - }
798 -
799 - $this->output->addHTML( "</span><li>$timestamp</li>" );
800 -
801 - $this->output->addHTML( '<span class="lqt_footer_commands">' .
802 - $this->listItemsForCommands( $this->threadFooterCommands( $thread ) ) .
803 - '</span>' );
804 -
805 - $this->output->addHTML( '</ul>' );
806 - }
807 -
808 - function listItemsForCommands( $commands ) {
809 - $result = array();
810 - foreach ( $commands as $command ) {
811 - $label = $command['label'];
812 - $href = $command['href'];
813 - $enabled = $command['enabled'];
814 -
815 - if ( $enabled ) {
816 - $result[] = "<li><a href=\"$href\">$label</a></li>";
817 - } else {
818 - $result[] = "<li><span class=\"lqt_command_disabled\">$label</span></li>";
819 - }
820 - }
821 - return join( "", $result );
822 - }
823 -
824 - function selectNewUserColor( $user ) {
825 - $userkey = $user->isAnon() ? "anon:" . $user->getName() : "user:" . $user->getId();
826 -
827 - if ( !array_key_exists( $userkey, $this->user_colors ) ) {
828 - $this->user_colors[$userkey] = $this->user_color_index;
829 - $this->user_color_index += 1;
830 - if ( $this->user_color_index > self::number_of_user_colors ) {
831 - $this->user_color_index = 1;
832 - }
833 - }
834 - return $this->user_colors[$userkey];
835 - }
836 -
837 - function showRootPost( $thread ) {
838 - $popts = $this->output->parserOptions();
839 - $previous_editsection = $popts->getEditSection();
840 - $popts->setEditSection( false );
841 - $this->output->parserOptions( $popts );
842 -
843 - $post = $thread->root();
844 -
845 - // This is a bit of a hack to have individual histories work.
846 - // We can grab oldid either from lqt_oldid (which is a thread rev),
847 - // or from oldid (which is a page rev). But oldid only applies to the
848 - // thread being requested, not any replies. TODO: eliminate the need
849 - // for article-level histories.
850 - $page_rev = $this->request->getVal( 'oldid', null );
851 - if ( $page_rev !== null && $this->title->equals( $thread->root()->getTitle() ) ) {
852 - $oldid = $page_rev;
853 - } else {
854 - $oldid = $thread->isHistorical() ? $thread->rootRevision() : null;
855 - }
856 -
857 - $this->openDiv( $this->postDivClass( $thread ) );
858 -
859 - if ( $this->methodAppliesToThread( 'edit', $thread ) ) {
860 - $this->showPostEditingForm( $thread );
861 - } else {
862 - $this->showPostBody( $post, $oldid );
863 - $this->showThreadFooter( $thread );
864 - }
865 -
866 - $this->closeDiv();
867 -
868 - if ( $this->methodAppliesToThread( 'reply', $thread ) ) {
869 - $this->indent( $thread );
870 - $this->showReplyForm( $thread );
871 - $this->unindent( $thread );
872 - }
873 -
874 - $popts->setEditSection( $previous_editsection );
875 - $this->output->parserOptions( $popts );
876 - }
877 -
878 - function showThreadHeading( $thread ) {
879 - if ( $thread->hasDistinctSubject() ) {
880 - if ( $thread->hasSuperthread() ) {
881 - $commands_html = "";
882 - } else {
883 - $lis = $this->listItemsForCommands( $this->topLevelThreadCommands( $thread ) );
884 - $commands_html = "<ul class=\"lqt_threadlevel_commands\">$lis</ul>";
885 - }
886 -
887 - $html = $thread->subjectWithoutIncrement() .
888 - ' <span class="lqt_subject_increment">(' .
889 - $thread->increment() . ')</span>';
890 - $this->output->addHTML( "<h{$this->headerLevel} class=\"lqt_header\">
891 - <span class=\"mw-headline\">" . $html . "</span></h{$this->headerLevel}>$commands_html" );
892 - }
893 - }
894 -
895 - function postDivClass( $thread ) {
896 - return 'lqt_post';
897 - }
898 -
899 - static function anchorName( $thread ) {
900 - return "lqt_thread_{$thread->id()}";
901 - }
902 -
903 - function showThread( $thread ) {
904 - global $wgLang; # TODO global.
905 -
906 - if ( $thread->type() == Threads::TYPE_DELETED
907 - && ! $this->request->getBool( 'lqt_show_deleted_threads' ) )
908 - return;
909 -
910 - if ( $this->lastUnindentedSuperthread ) {
911 - wfLoadExtensionMessages( 'LiquidThreads' );
912 - $tmp = $this->lastUnindentedSuperthread;
913 - $msg = wfMsg( 'lqt_in_response_to',
914 - '<a href="#lqt_thread_' . $tmp->id() . '">' . $tmp->title()->getText() . '</a>',
915 - $tmp->root()->originalAuthor()->getName() );
916 - $this->output->addHTML( '<span class="lqt_nonindent_message">&larr;' . $msg . '</span>' );
917 - }
918 -
919 -
920 - $this->showThreadHeading( $thread );
921 -
922 - $this->output->addHTML( "<a name=\"{$this->anchorName($thread)}\" ></a>" );
923 -
924 - if ( $thread->type() == Threads::TYPE_MOVED ) {
925 - wfLoadExtensionMessages( 'LiquidThreads' );
926 - $revision = Revision::newFromTitle( $thread->title() );
927 - $target = Title::newFromRedirect( $revision->getText() );
928 - $t_thread = Threads::withRoot( new Article( $target ) );
929 - $author = $thread->root()->originalAuthor();
930 - $sig = $this->user->getSkin()->userLink( $author->getID(), $author->getName() ) .
931 - $this->user->getSkin()->userToolLinks( $author->getID(), $author->getName() );
932 - $this->output->addHTML( wfMsg( 'lqt_move_placeholder',
933 - '<a href="' . $target->getFullURL() . '">' . $target->getText() . '</a>',
934 - $sig,
935 - $wgLang->date( $thread->modified() ),
936 - $wgLang->time( $thread->modified() )
937 - ) );
938 - return;
939 - }
940 -
941 - if ( $thread->type() == Threads::TYPE_DELETED ) {
942 - wfLoadExtensionMessages( 'LiquidThreads' );
943 - if ( in_array( 'deletedhistory', $this->user->getRights() ) ) {
944 - $this->output->addHTML( '<p>' . wfMsg( 'lqt_thread_deleted_for_sysops' ) . '</p>' );
945 - }
946 - else {
947 - $this->output->addHTML( '<p><em>' . wfMsg( 'lqt_thread_deleted' ) . '</em></p>' );
948 - return;
949 - }
950 - }
951 -
952 - global $wgLqtThreadArchiveStartDays, $wgLqtThreadArchiveInactiveDays;
953 -
954 - $timestamp = new Date( $thread->modified() );
955 - if ( $thread->summary() ) {
956 - $this->showSummary( $thread );
957 - } else if ( $timestamp->isBefore( Date::now()->nDaysAgo( $wgLqtThreadArchiveStartDays ) )
958 - && !$thread->summary() && !$thread->hasSuperthread() && !$thread->isHistorical() )
959 - {
960 - wfLoadExtensionMessages( 'LiquidThreads' );
961 - $this->output->addHTML( '<p class="lqt_summary_notice">' . wfMsgExt( 'lqt_summary_notice', 'parsemag',
962 - '<a href="' . $this->permalinkUrl( $thread, 'summarize' ) . '">' . wfMsg( 'lqt_summary_notice_link' ) . '</a>',
963 - $wgLqtThreadArchiveStartDays
964 - ) . '</p>' );
965 - }
966 -
967 - $this->openDiv( 'lqt_thread', "lqt_thread_id_{$thread->id()}" );
968 -
969 - $this->showRootPost( $thread );
970 -
971 - if ( $thread->hasSubthreads() ) $this->indent( $thread );
972 - foreach ( $thread->subthreads() as $st ) {
973 - $this->showThread( $st );
974 - }
975 - if ( $thread->hasSubthreads() ) $this->unindent( $thread );
976 -
977 - $this->closeDiv();
978 - }
979 -
980 - function indent( $thread ) {
981 - if ( $this->headerLevel <= $this->maxIndentationLevel ) {
982 - $this->output->addHTML( '<dl class="lqt_replies"><dd>' );
983 - } else {
984 - $this->output->addHTML( '<div class="lqt_replies_without_indent">' );
985 - }
986 - $this->lastUnindentedSuperthread = null;
987 - $this->headerLevel += 1;
988 - }
989 - function unindent( $thread ) {
990 - if ( $this->headerLevel <= $this->maxIndentationLevel + 1 ) {
991 - $this->output->addHTML( '</dd></dl>' );
992 - } else {
993 - $this->output->addHTML( '</div>' );
994 - }
995 - // See the beginning of showThread().
996 - $this->lastUnindentedSuperthread = $thread->superthread();
997 - $this->headerLevel -= 1;
998 - }
999 -
1000 - function openDiv( $class = '', $id = '' ) {
1001 - $this->output->addHTML( Xml::openElement( 'div', array( 'class' => $class, 'id' => $id ) ) );
1002 - }
1003 -
1004 - function closeDiv() {
1005 - $this->output->addHTML( Xml::closeElement( 'div' ) );
1006 - }
1007 -
1008 - function showSummary( $t ) {
1009 - if ( !$t->summary() ) return;
1010 - wfLoadExtensionMessages( 'LiquidThreads' );
1011 - $label = wfMsg( 'lqt_summary_label' );
1012 - $edit = strtolower( wfMsg( 'edit' ) );
1013 - $link = strtolower( wfMsg( 'lqt_permalink' ) );
1014 - $this->output->addHTML( <<<HTML
1015 - <div class='lqt_thread_permalink_summary'>
1016 - <span class="lqt_thread_permalink_summary_title">
1017 - $label
1018 - </span><span class="lqt_thread_permalink_summary_edit">
1019 - [<a href="{$t->summary()->getTitle()->getFullURL()}">$link</a>]
1020 - [<a href="{$this->permalinkUrl($t,'summarize')}">$edit</a>]
1021 - </span>
1022 -HTML
1023 - );
1024 - $this->openDiv( 'lqt_thread_permalink_summary_body' );
1025 - $this->showPostBody( $t->summary() );
1026 - $this->closeDiv();
1027 - $this->closeDiv();
1028 - }
1029 -
1030 -}
Index: trunk/extensions/LiquidThreads/LiquidThreads.php
@@ -53,8 +53,8 @@
5454 $wgSpecialPages['NewMessages'] = 'SpecialNewMessages';
5555 $wgSpecialPageGroups['NewMessages'] = 'wiki';
5656
57 -$wgAutoloadClasses['LqtDispatch'] = $dir . 'LqtBaseView.php';
58 -$wgAutoloadClasses['LqtView'] = $dir . 'LqtBaseView.php';
 57+$wgAutoloadClasses['LqtDispatch'] = $dir . 'classes/LqtDispatch.php';
 58+$wgAutoloadClasses['LqtView'] = $dir . 'classes/LqtView.php';
5959 $wgAutoloadClasses['Date'] = $dir . 'classes/LqtDate.php';
6060 $wgAutoloadClasses['Post'] = $dir . 'classes/LqtPost.php';
6161 $wgAutoloadClasses['ThreadHistoryIterator'] = $dir . 'classes/LqtThreadHistoryIterator.php';
Index: trunk/extensions/LiquidThreads/classes/LqtView.php
@@ -0,0 +1,785 @@
 2+<?php
 3+
 4+/**
 5+* @package MediaWiki
 6+* @subpackage LiquidThreads
 7+* @author David McCabe <davemccabe@gmail.com>
 8+* @licence GPL2
 9+*/
 10+
 11+if ( !defined( 'MEDIAWIKI' ) ) {
 12+ echo( "This file is an extension to the MediaWiki software and cannot be used standalone.\n" );
 13+ die( - 1 );
 14+}
 15+
 16+class LqtView {
 17+ protected $article;
 18+ protected $output;
 19+ protected $user;
 20+ protected $title;
 21+ protected $request;
 22+
 23+ protected $headerLevel = 2; /* h1, h2, h3, etc. */
 24+ protected $maxIndentationLevel = 4;
 25+ protected $lastUnindentedSuperthread;
 26+
 27+ protected $user_colors;
 28+ protected $user_color_index;
 29+ const number_of_user_colors = 6;
 30+
 31+ protected $queries;
 32+
 33+ protected $sort_order = LQT_NEWEST_CHANGES;
 34+
 35+ function __construct( &$output, &$article, &$title, &$user, &$request ) {
 36+ $this->article = $article;
 37+ $this->output = $output;
 38+ $this->user = $user;
 39+ $this->title = $title;
 40+ $this->request = $request;
 41+ $this->user_colors = array();
 42+ $this->user_color_index = 1;
 43+ $this->queries = $this->initializeQueries();
 44+ }
 45+
 46+ function setHeaderLevel( $int ) {
 47+ $this->headerLevel = $int;
 48+ }
 49+
 50+ function initializeQueries() {
 51+
 52+ if ( $this->methodApplies( 'talkpage_sort_order' ) ) {
 53+ // Sort order is explicitly specified through UI
 54+ global $wgRequest;
 55+ $lqt_order = $wgRequest->getVal( 'lqt_order' );
 56+ switch( $lqt_order ) {
 57+ case 'nc':
 58+ $this->sort_order = LQT_NEWEST_CHANGES;
 59+ break;
 60+ case 'nt':
 61+ $this->sort_order = LQT_NEWEST_THREADS;
 62+ break;
 63+ case 'ot':
 64+ $this->sort_order = LQT_OLDEST_THREADS;
 65+ break;
 66+ }
 67+ } else {
 68+ // Sort order set in user preferences overrides default
 69+ global $wgUser;
 70+ $user_order = $wgUser->getOption( 'lqt_sort_order' ) ;
 71+ if ( $user_order ) {
 72+ $this->sort_order = $user_order;
 73+ }
 74+ }
 75+ global $wgOut, $wgLqtThreadArchiveStartDays, $wgLqtThreadArchiveInactiveDays;
 76+ $g = new QueryGroup();
 77+ $startdate = Date::now()->nDaysAgo( $wgLqtThreadArchiveStartDays )->midnight();
 78+ $recentstartdate = $startdate->nDaysAgo( $wgLqtThreadArchiveInactiveDays );
 79+ $article_clause = Threads::articleClause( $this->article );
 80+ if ( $this->sort_order == LQT_NEWEST_CHANGES ) {
 81+ $sort_clause = 'ORDER BY thread.thread_modified DESC';
 82+ } elseif ( $this->sort_order == LQT_NEWEST_THREADS ) {
 83+ $sort_clause = 'ORDER BY thread.thread_created DESC';
 84+ } elseif ( $this->sort_order == LQT_OLDEST_THREADS ) {
 85+ $sort_clause = 'ORDER BY thread.thread_created ASC';
 86+ }
 87+ $g->addQuery( 'fresh',
 88+ array( $article_clause,
 89+ 'thread.thread_parent is null',
 90+ '(thread.thread_modified >= ' . $startdate->text() .
 91+ ' OR (thread.thread_summary_page is NULL' .
 92+ ' AND thread.thread_type=' . Threads::TYPE_NORMAL . '))' ),
 93+ array( $sort_clause ) );
 94+ $g->addQuery( 'archived',
 95+ array( $article_clause,
 96+ 'thread.thread_parent is null',
 97+ '(thread.thread_summary_page is not null' .
 98+ ' OR thread.thread_type=' . Threads::TYPE_NORMAL . ')',
 99+ 'thread.thread_modified < ' . $startdate->text() ),
 100+ array( $sort_clause ) );
 101+ $g->extendQuery( 'archived', 'recently-archived',
 102+ array( '( thread.thread_modified >=' . $recentstartdate->text() .
 103+ ' OR rev_timestamp >= ' . $recentstartdate->text() . ')',
 104+ 'summary_page.page_id = thread.thread_summary_page', 'summary_page.page_latest = rev_id' ),
 105+ array(),
 106+ array( 'page summary_page', 'revision' ) );
 107+ return $g;
 108+ }
 109+
 110+ static protected $occupied_titles = array();
 111+
 112+ /*************************
 113+ * (1) linking to liquidthreads pages and
 114+ * (2) figuring out what page you're on and what you need to do.
 115+ *************************/
 116+
 117+ static function queryStringFromArray( $vars ) {
 118+ $q = '';
 119+ if ( $vars && count( $vars ) != 0 ) {
 120+ foreach ( $vars as $name => $value )
 121+ $q .= "$name=$value&";
 122+ }
 123+ return $q;
 124+ }
 125+
 126+ function methodAppliesToThread( $method, $thread ) {
 127+ return $this->request->getVal( 'lqt_method' ) == $method &&
 128+ $this->request->getVal( 'lqt_operand' ) == $thread->id();
 129+ }
 130+ function methodApplies( $method ) {
 131+ return $this->request->getVal( 'lqt_method' ) == $method;
 132+ }
 133+
 134+ static function permalinkUrl( $thread, $method = null, $operand = null ) {
 135+ $query = $method ? "lqt_method=$method" : "";
 136+ $query = $operand ? "$query&lqt_operand={$operand->id()}" : $query;
 137+ return $thread->root()->getTitle()->getFullUrl( $query );
 138+ }
 139+
 140+ /* This is used for action=history so that the history tab works, which is
 141+ why we break the lqt_method paradigm. */
 142+ static function permalinkUrlWithQuery( $thread, $query ) {
 143+ if ( is_array( $query ) ) $query = self::queryStringFromArray( $query );
 144+ return $thread->root()->getTitle()->getFullUrl( $query );
 145+ }
 146+
 147+ static function permalinkUrlWithDiff( $thread ) {
 148+ $changed_thread = $thread->changeObject();
 149+ $curr_rev_id = $changed_thread->rootRevision();
 150+ $curr_rev = Revision::newFromTitle( $changed_thread->root()->getTitle(), $curr_rev_id );
 151+ $prev_rev = $curr_rev->getPrevious();
 152+ $oldid = $prev_rev ? $prev_rev->getId() : "";
 153+ return self::permalinkUrlWithQuery( $changed_thread, array( 'lqt_method' => 'diff', 'diff' => $curr_rev_id, 'oldid' => $oldid ) );
 154+ }
 155+
 156+ static function talkpageUrl( $title, $method = null, $operand = null, $includeFragment = true ) {
 157+ global $wgRequest; // TODO global + ugly hack.
 158+ $query = $method ? "lqt_method=$method" : "";
 159+ $query = $operand ? "$query&lqt_operand={$operand->id()}" : $query;
 160+ $oldid = $wgRequest->getVal( 'oldid', null ); if ( $oldid !== null ) {
 161+ // this is an immensely ugly hack to make editing old revisions work.
 162+ $query = "$query&oldid=$oldid";
 163+ }
 164+ return $title->getFullURL( $query ) . ( $operand && $includeFragment ? "#lqt_thread_{$operand->id()}" : "" );
 165+ }
 166+
 167+
 168+ /**
 169+ * Return a URL for the current page, including Title and query vars,
 170+ * with the given replacements made.
 171+ * @param $repls array( 'name'=>new_value, ... )
 172+ */
 173+ function queryReplace( $repls ) {
 174+ $vs = $this->request->getValues();
 175+ $rs = array();
 176+/* foreach ($vs as $k => $v) {
 177+ if ( array_key_exists( $k, $repls ) ) {
 178+ $rs[$k] = $repls[$k];
 179+ } else {
 180+ $rs[$k] = $vs[$k];
 181+ }
 182+ }
 183+*/
 184+ foreach ( $repls as $k => $v ) {
 185+ $vs[$k] = $v;
 186+ }
 187+ return $this->title->getFullURL( self::queryStringFromArray( $vs ) );
 188+ }
 189+
 190+ /*************************************************************
 191+ * Editing methods (here be dragons) *
 192+ * Forget dragons: This section distorts the rest of the code *
 193+ * like a star bending spacetime around itself. *
 194+ *************************************************************/
 195+
 196+ /**
 197+ * Return an HTML form element whose value is gotten from the request.
 198+ * TODO: figure out a clean way to expand this to other forms.
 199+ */
 200+ function perpetuate( $name, $as ) {
 201+ $value = $this->request->getVal( $name, '' );
 202+ if ( $as == 'hidden' ) {
 203+ return <<<HTML
 204+ <input type="hidden" name="$name" id="$name" value="$value">
 205+HTML;
 206+ }
 207+ }
 208+
 209+ function showReplyProtectedNotice( $thread ) {
 210+ wfLoadExtensionMessages( 'LiquidThreads' );
 211+ $log_url = SpecialPage::getTitleFor( 'Log' )->getFullURL(
 212+ "type=protect&user=&page={$thread->title()->getPrefixedURL()}" );
 213+ $this->output->addHTML( '<p>' . wfMsg( 'lqt_protectedfromreply',
 214+ '<a href="' . $log_url . '">' . wfMsg( 'lqt_protectedfromreply_link' ) . '</a>' ) );
 215+ }
 216+
 217+ function showNewThreadForm() {
 218+ $this->showEditingFormInGeneral( null, 'new', null );
 219+ }
 220+
 221+ function showPostEditingForm( $thread ) {
 222+ $this->showEditingFormInGeneral( $thread, 'editExisting', null );
 223+ }
 224+
 225+ function showReplyForm( $thread ) {
 226+ if ( $thread->root()->getTitle()->userCan( 'edit' ) ) {
 227+ $this->showEditingFormInGeneral( null, 'reply', $thread );
 228+ } else {
 229+ $this->showReplyProtectedNotice( $thread );
 230+ }
 231+ }
 232+
 233+ function showSummarizeForm( $thread ) {
 234+ $this->showEditingFormInGeneral( null, 'summarize', $thread );
 235+ }
 236+
 237+ private function showEditingFormInGeneral( $thread, $edit_type, $edit_applies_to ) {
 238+ /*
 239+ EditPage needs an Article. If there isn't a real one, as for new posts,
 240+ replies, and new summaries, we need to generate a title. Auto-generated
 241+ titles are based on the subject line. If the subject line is blank, we
 242+ can temporarily use a random scratch title. It's fine if the title changes
 243+ throughout the edit cycle, since the article doesn't exist yet anyways.
 244+ */
 245+ if ( $edit_type == 'summarize' && $edit_applies_to->summary() ) {
 246+ $article = $edit_applies_to->summary();
 247+ } else if ( $edit_type == 'summarize' ) {
 248+ $t = $this->newSummaryTitle( $edit_applies_to );
 249+ $article = new Article( $t );
 250+ } else if ( $thread == null ) {
 251+ $subject = $this->request->getVal( 'lqt_subject_field', '' );
 252+ if ( $edit_type == 'new' ) {
 253+ $t = $this->newScratchTitle( $subject );
 254+ } else if ( $edit_type == 'reply' ) {
 255+ $t = $this->newReplyTitle( $subject, $edit_applies_to );
 256+ }
 257+ $article = new Article( $t );
 258+ } else {
 259+ $article = $thread->root();
 260+ }
 261+
 262+ $e = new EditPage( $article );
 263+
 264+ $e->suppressIntro = true;
 265+ $e->editFormTextBeforeContent .=
 266+ $this->perpetuate( 'lqt_method', 'hidden' ) .
 267+ $this->perpetuate( 'lqt_operand', 'hidden' );
 268+
 269+ if ( $edit_type == 'new' || ( $thread && !$thread->hasSuperthread() ) ) {
 270+ wfLoadExtensionMessages( 'LiquidThreads' );
 271+ // This is a top-level post; show the subject line.
 272+ $db_subject = $thread ? $thread->subjectWithoutIncrement() : '';
 273+ $subject = $this->request->getVal( 'lqt_subject_field', $db_subject );
 274+ $subject_label = wfMsg( 'lqt_subject' );
 275+ $e->editFormTextBeforeContent .= <<<HTML
 276+ <label for="lqt_subject_field">$subject_label</label>
 277+ <input type="text" size="60" name="lqt_subject_field" id="lqt_subject_field" value="$subject" tabindex="1"><br />
 278+HTML;
 279+ }
 280+
 281+ $e->edit();
 282+
 283+ // Override what happens in EditPage::showEditForm, called from $e->edit():
 284+
 285+ $this->output->setArticleFlag( false );
 286+
 287+ // For replies and new posts, insert the associated thread object into the DB.
 288+ if ( $edit_type != 'editExisting' && $edit_type != 'summarize' && $e->didSave ) {
 289+ if ( $edit_type == 'reply' ) {
 290+ $thread = Threads::newThread( $article, $this->article, $edit_applies_to, $e->summary );
 291+ $edit_applies_to->commitRevision( Threads::CHANGE_REPLY_CREATED, $thread, $e->summary );
 292+ } else {
 293+ $thread = Threads::newThread( $article, $this->article, null, $e->summary );
 294+ }
 295+ }
 296+
 297+ if ( $edit_type == 'summarize' && $e->didSave ) {
 298+ $edit_applies_to->setSummary( $article );
 299+ $edit_applies_to->commitRevision( Threads::CHANGE_EDITED_SUMMARY, $edit_applies_to, $e->summary );
 300+ }
 301+
 302+ // Move the thread and replies if subject changed.
 303+ if ( $edit_type == 'editExisting' && $e->didSave ) {
 304+ $subject = $this->request->getVal( 'lqt_subject_field', '' );
 305+ if ( $subject && $subject != $thread->subjectWithoutIncrement() ) {
 306+
 307+ $this->renameThread( $thread, $subject, $e->summary );
 308+ }
 309+ // this is unrelated to the subject change and is for all edits:
 310+ $thread->setRootRevision( Revision::newFromTitle( $thread->root()->getTitle() ) );
 311+ $thread->commitRevision( Threads::CHANGE_EDITED_ROOT, $thread, $e->summary );
 312+ }
 313+
 314+ // A redirect without $e->didSave will happen if the new text is blank (EditPage::attemptSave).
 315+ // This results in a new Thread object not being created for replies and new discussions,
 316+ // so $thread is null. In that case, just allow editpage to redirect back to the talk page.
 317+ if ( $this->output->getRedirect() != '' && $thread ) {
 318+ $this->output->redirect( $this->title->getFullURL() . '#' . 'lqt_thread_' . $thread->id() );
 319+ } else if ( $this->output->getRedirect() != '' && $edit_applies_to ) {
 320+ // For summaries:
 321+ $this->output->redirect( $edit_applies_to->title()->getFullURL() . '#' . 'lqt_thread_' . $edit_applies_to->id() );
 322+ }
 323+ }
 324+
 325+ function renameThread( $t, $s, $reason ) {
 326+ $this->simplePageMove( $t->root()->getTitle(), $s, $reason );
 327+ // TODO here create a redirect from old page to new.
 328+ foreach ( $t->subthreads() as $st ) {
 329+ $this->renameThread( $st, $s, $reason );
 330+ }
 331+ }
 332+
 333+ function scratchTitle() {
 334+ $token = md5( uniqid( rand(), true ) );
 335+ return Title::newFromText( "Thread:$token" );
 336+ }
 337+ function newScratchTitle( $subject ) {
 338+ wfLoadExtensionMessages( 'LiquidThreads' );
 339+ return $this->incrementedTitle( $subject ? $subject:wfMsg( 'lqt_nosubject' ), NS_LQT_THREAD );
 340+ }
 341+ function newSummaryTitle( $t ) {
 342+ return $this->incrementedTitle( $t->subject(), NS_LQT_SUMMARY );
 343+ }
 344+ function newReplyTitle( $s, $t ) {
 345+ return $this->incrementedTitle( $t->subjectWithoutIncrement(), NS_LQT_THREAD );
 346+ }
 347+ /** Keep trying titles starting with $basename until one is unoccupied. */
 348+ public static function incrementedTitle( $basename, $namespace ) {
 349+ $i = 1; do {
 350+ $t = Title::newFromText( $basename . '_(' . $i . ')', $namespace );
 351+ $i++;
 352+ } while ( $t->exists() || in_array( $t->getPrefixedDBkey(), self::$occupied_titles ) );
 353+ return $t;
 354+ }
 355+
 356+ /* Adapted from MovePageForm::doSubmit in SpecialMovepage.php. */
 357+ function simplePageMove( $old_title, $new_subject, $reason ) {
 358+ if ( $this->user->pingLimiter( 'move' ) ) {
 359+ $this->out->rateLimited();
 360+ return false;
 361+ }
 362+
 363+ # Variables beginning with 'o' for old article 'n' for new article
 364+
 365+ $ot = $old_title;
 366+ $nt = $this->incrementedTitle( $new_subject, $old_title->getNamespace() );
 367+
 368+ self::$occupied_titles[] = $nt->getPrefixedDBkey();
 369+
 370+ # don't allow moving to pages with # in
 371+ if ( !$nt || $nt->getFragment() != '' ) {
 372+ echo "malformed title"; // TODO real error reporting.
 373+ return false;
 374+ }
 375+
 376+ $error = $ot->moveTo( $nt, true, "Changed thread subject: $reason" );
 377+ if ( $error !== true ) {
 378+ var_dump( $error );
 379+ echo "something bad happened trying to rename the thread."; // TODO
 380+ return false;
 381+ }
 382+
 383+ # Move the talk page if relevant, if it exists, and if we've been told to
 384+ // TODO we need to implement correct moving of talk pages everywhere later.
 385+ // Snipped.
 386+
 387+ return true;
 388+ }
 389+
 390+ /**
 391+ * Example return value:
 392+ * array (
 393+ * 0 => array( 'label' => 'Edit',
 394+ * 'href' => 'http...',
 395+ * 'enabled' => false ),
 396+ * 1 => array( 'label' => 'Reply',
 397+ * 'href' => 'http...',
 398+ * 'enabled' => true )
 399+ * )
 400+ */
 401+ function threadFooterCommands( $thread ) {
 402+ wfLoadExtensionMessages( 'LiquidThreads' );
 403+ $commands = array();
 404+
 405+ $user_can_edit = $thread->root()->getTitle()->quickUserCan( 'edit' );
 406+
 407+ $commands[] = array( 'label' => $user_can_edit ? wfMsg( 'edit' ) : wfMsg( 'viewsource' ),
 408+ 'href' => $this->talkpageUrl( $this->title, 'edit', $thread ),
 409+ 'enabled' => true );
 410+
 411+ $commands[] = array( 'label' => wfMsg( 'history_short' ),
 412+ 'href' => $this->permalinkUrlWithQuery( $thread, 'action=history' ),
 413+ 'enabled' => true );
 414+
 415+ $commands[] = array( 'label' => wfMsg( 'lqt_permalink' ),
 416+ 'href' => $this->permalinkUrl( $thread ),
 417+ 'enabled' => true );
 418+
 419+ if ( in_array( 'delete', $this->user->getRights() ) ) {
 420+ $delete_url = SpecialPage::getTitleFor( 'DeleteThread' )->getFullURL()
 421+ . '/' . $thread->title()->getPrefixedURL();
 422+ $commands[] = array( 'label' => $thread->type() == Threads::TYPE_DELETED ? wfMsg( 'lqt_undelete' ) : wfMsg( 'delete' ),
 423+ 'href' => $delete_url,
 424+ 'enabled' => true );
 425+ }
 426+
 427+ $commands[] = array( 'label' => '<b class="lqt_reply_link">' . wfMsg( 'lqt_reply' ) . '</b>',
 428+ 'href' => $this->talkpageUrl( $this->title, 'reply', $thread ),
 429+ 'enabled' => $user_can_edit );
 430+
 431+ return $commands;
 432+ }
 433+
 434+ function topLevelThreadCommands( $thread ) {
 435+ wfLoadExtensionMessages( 'LiquidThreads' );
 436+ $commands = array();
 437+
 438+ $commands[] = array( 'label' => wfMsg( 'history_short' ),
 439+ 'href' => $this->permalinkUrl( $thread, 'thread_history' ),
 440+ 'enabled' => true );
 441+
 442+ if ( in_array( 'move', $this->user->getRights() ) ) {
 443+ $move_href = SpecialPage::getTitleFor( 'MoveThread' )->getFullURL()
 444+ . '/' . $thread->title()->getPrefixedURL();
 445+ $commands[] = array( 'label' => wfMsg( 'move' ),
 446+ 'href' => $move_href,
 447+ 'enabled' => true );
 448+ }
 449+ if ( !$this->user->isAnon() && !$thread->title()->userIsWatching() ) {
 450+ $commands[] = array( 'label' => wfMsg( 'watch' ),
 451+ 'href' => $this->permalinkUrlWithQuery( $thread, 'action=watch' ),
 452+ 'enabled' => true );
 453+ } else if ( !$this->user->isAnon() ) {
 454+ $commands[] = array( 'label' => wfMsg( 'unwatch' ),
 455+ 'href' => $this->permalinkUrlWithQuery( $thread, 'action=unwatch' ),
 456+ 'enabled' => true );
 457+ }
 458+
 459+ return $commands;
 460+ }
 461+
 462+ /*************************
 463+ * Output methods *
 464+ *************************/
 465+
 466+ static function addJSandCSS() {
 467+ // Changed this to be static so that we can call it from
 468+ // wfLqtBeforeWatchlistHook.
 469+ global $wgJsMimeType, $wgScriptPath, $wgStyleVersion; // TODO globals.
 470+ global $wgOut;
 471+ $s = <<< HTML
 472+ <script type="{$wgJsMimeType}" src="{$wgScriptPath}/extensions/LiquidThreads/lqt.js"><!-- lqt js --></script>
 473+ <style type="text/css" media="screen, projection">/*<![CDATA[*/
 474+ @import "{$wgScriptPath}/extensions/LiquidThreads/lqt.css?{$wgStyleVersion}";
 475+ /*]]>*/</style>
 476+
 477+HTML;
 478+ $wgOut->addScript( $s );
 479+ }
 480+
 481+ /* @return False if the article and revision do not exist and we didn't show it, true if we did. */
 482+ function showPostBody( $post, $oldid = null ) {
 483+ /* Why isn't this all encapsulated in Article somewhere? TODO */
 484+ global $wgEnableParserCache;
 485+
 486+ // Should the parser cache be used?
 487+ $pcache = $wgEnableParserCache &&
 488+ intval( $this->user->getOption( 'stubthreshold' ) ) == 0 &&
 489+ $post->exists() &&
 490+ $oldid === null;
 491+ wfDebug( 'LqtView::showPostBody using parser cache: ' . ( $pcache ? 'yes' : 'no' ) . "\n" );
 492+ if ( $this->user->getOption( 'stubthreshold' ) ) {
 493+ wfIncrStats( 'pcache_miss_stub' );
 494+ }
 495+
 496+ $outputDone = false;
 497+ if ( $pcache ) {
 498+ $outputDone = $this->output->tryParserCache( $post, $this->user );
 499+ }
 500+
 501+ if ( !$outputDone ) {
 502+ // Cache miss; parse and output it.
 503+ $rev = Revision::newFromTitle( $post->getTitle(), $oldid );
 504+ if ( $rev && $oldid ) {
 505+ // don't save oldids in the parser cache.
 506+ $this->output->addWikiText( $rev->getText() );
 507+ return true;
 508+ }
 509+ else if ( $rev ) {
 510+ $post->outputWikiText( $rev->getText(), true );
 511+ return true;
 512+ } else {
 513+ return false;
 514+ }
 515+ } else {
 516+ return true;
 517+ }
 518+ }
 519+
 520+ function colorTest() {
 521+ $this->output->addHTML( '<div class="lqt_footer"><li class="lqt_footer_sig">' );
 522+ for ( $i = 1; $i <= self::number_of_user_colors; $i++ ) {
 523+ $this->output->addHTML( "<span class=\"lqt_post_color_{$i}\"><a href=\"foo\">DavidMcCabe</a></span>" );
 524+ }
 525+ $this->output->addHTML( '</li></div>' );
 526+ }
 527+
 528+ function showThreadFooter( $thread ) {
 529+ global $wgLang; // TODO global.
 530+
 531+ $author = $thread->root()->originalAuthor();
 532+ $color_number = $this->selectNewUserColor( $author );
 533+
 534+ $sig = $this->user->getSkin()->userLink( $author->getID(), $author->getName() ) .
 535+ $this->user->getSkin()->userToolLinks( $author->getID(), $author->getName() );
 536+
 537+ $timestamp = $wgLang->timeanddate( $thread->created(), true );
 538+
 539+ $this->output->addHTML( <<<HTML
 540+<ul class="lqt_footer">
 541+<span class="lqt_footer_sig">
 542+<li class="lqt_author_sig lqt_post_color_{$color_number}">$sig</li>
 543+HTML
 544+ );
 545+
 546+ if ( $thread->editedness() == Threads::EDITED_BY_AUTHOR || $thread->editedness() == Threads::EDITED_BY_OTHERS ) {
 547+ wfLoadExtensionMessages( 'LiquidThreads' );
 548+ $editedness_url = $this->permalinkUrlWithQuery( $thread, 'action=history' );
 549+ $editedness_color_number = $thread->editedness() == Threads::EDITED_BY_AUTHOR ?
 550+ $color_number : ( $color_number == self::number_of_user_colors ? 1 : $color_number + 1 );
 551+ $this->output->addHTML( "<li class=\"lqt_edited_notice lqt_post_color_{$editedness_color_number}\">" .
 552+ '<a href="' . $editedness_url . '">' . wfMsg( 'lqt_edited_notice' ) . '</a>' . '</li>' );
 553+ }
 554+
 555+ $this->output->addHTML( "</span><li>$timestamp</li>" );
 556+
 557+ $this->output->addHTML( '<span class="lqt_footer_commands">' .
 558+ $this->listItemsForCommands( $this->threadFooterCommands( $thread ) ) .
 559+ '</span>' );
 560+
 561+ $this->output->addHTML( '</ul>' );
 562+ }
 563+
 564+ function listItemsForCommands( $commands ) {
 565+ $result = array();
 566+ foreach ( $commands as $command ) {
 567+ $label = $command['label'];
 568+ $href = $command['href'];
 569+ $enabled = $command['enabled'];
 570+
 571+ if ( $enabled ) {
 572+ $result[] = "<li><a href=\"$href\">$label</a></li>";
 573+ } else {
 574+ $result[] = "<li><span class=\"lqt_command_disabled\">$label</span></li>";
 575+ }
 576+ }
 577+ return join( "", $result );
 578+ }
 579+
 580+ function selectNewUserColor( $user ) {
 581+ $userkey = $user->isAnon() ? "anon:" . $user->getName() : "user:" . $user->getId();
 582+
 583+ if ( !array_key_exists( $userkey, $this->user_colors ) ) {
 584+ $this->user_colors[$userkey] = $this->user_color_index;
 585+ $this->user_color_index += 1;
 586+ if ( $this->user_color_index > self::number_of_user_colors ) {
 587+ $this->user_color_index = 1;
 588+ }
 589+ }
 590+ return $this->user_colors[$userkey];
 591+ }
 592+
 593+ function showRootPost( $thread ) {
 594+ $popts = $this->output->parserOptions();
 595+ $previous_editsection = $popts->getEditSection();
 596+ $popts->setEditSection( false );
 597+ $this->output->parserOptions( $popts );
 598+
 599+ $post = $thread->root();
 600+
 601+ // This is a bit of a hack to have individual histories work.
 602+ // We can grab oldid either from lqt_oldid (which is a thread rev),
 603+ // or from oldid (which is a page rev). But oldid only applies to the
 604+ // thread being requested, not any replies. TODO: eliminate the need
 605+ // for article-level histories.
 606+ $page_rev = $this->request->getVal( 'oldid', null );
 607+ if ( $page_rev !== null && $this->title->equals( $thread->root()->getTitle() ) ) {
 608+ $oldid = $page_rev;
 609+ } else {
 610+ $oldid = $thread->isHistorical() ? $thread->rootRevision() : null;
 611+ }
 612+
 613+ $this->openDiv( $this->postDivClass( $thread ) );
 614+
 615+ if ( $this->methodAppliesToThread( 'edit', $thread ) ) {
 616+ $this->showPostEditingForm( $thread );
 617+ } else {
 618+ $this->showPostBody( $post, $oldid );
 619+ $this->showThreadFooter( $thread );
 620+ }
 621+
 622+ $this->closeDiv();
 623+
 624+ if ( $this->methodAppliesToThread( 'reply', $thread ) ) {
 625+ $this->indent( $thread );
 626+ $this->showReplyForm( $thread );
 627+ $this->unindent( $thread );
 628+ }
 629+
 630+ $popts->setEditSection( $previous_editsection );
 631+ $this->output->parserOptions( $popts );
 632+ }
 633+
 634+ function showThreadHeading( $thread ) {
 635+ if ( $thread->hasDistinctSubject() ) {
 636+ if ( $thread->hasSuperthread() ) {
 637+ $commands_html = "";
 638+ } else {
 639+ $lis = $this->listItemsForCommands( $this->topLevelThreadCommands( $thread ) );
 640+ $commands_html = "<ul class=\"lqt_threadlevel_commands\">$lis</ul>";
 641+ }
 642+
 643+ $html = $thread->subjectWithoutIncrement() .
 644+ ' <span class="lqt_subject_increment">(' .
 645+ $thread->increment() . ')</span>';
 646+ $this->output->addHTML( "<h{$this->headerLevel} class=\"lqt_header\">
 647+ <span class=\"mw-headline\">" . $html . "</span></h{$this->headerLevel}>$commands_html" );
 648+ }
 649+ }
 650+
 651+ function postDivClass( $thread ) {
 652+ return 'lqt_post';
 653+ }
 654+
 655+ static function anchorName( $thread ) {
 656+ return "lqt_thread_{$thread->id()}";
 657+ }
 658+
 659+ function showThread( $thread ) {
 660+ global $wgLang; # TODO global.
 661+
 662+ if ( $thread->type() == Threads::TYPE_DELETED
 663+ && ! $this->request->getBool( 'lqt_show_deleted_threads' ) )
 664+ return;
 665+
 666+ if ( $this->lastUnindentedSuperthread ) {
 667+ wfLoadExtensionMessages( 'LiquidThreads' );
 668+ $tmp = $this->lastUnindentedSuperthread;
 669+ $msg = wfMsg( 'lqt_in_response_to',
 670+ '<a href="#lqt_thread_' . $tmp->id() . '">' . $tmp->title()->getText() . '</a>',
 671+ $tmp->root()->originalAuthor()->getName() );
 672+ $this->output->addHTML( '<span class="lqt_nonindent_message">&larr;' . $msg . '</span>' );
 673+ }
 674+
 675+
 676+ $this->showThreadHeading( $thread );
 677+
 678+ $this->output->addHTML( "<a name=\"{$this->anchorName($thread)}\" ></a>" );
 679+
 680+ if ( $thread->type() == Threads::TYPE_MOVED ) {
 681+ wfLoadExtensionMessages( 'LiquidThreads' );
 682+ $revision = Revision::newFromTitle( $thread->title() );
 683+ $target = Title::newFromRedirect( $revision->getText() );
 684+ $t_thread = Threads::withRoot( new Article( $target ) );
 685+ $author = $thread->root()->originalAuthor();
 686+ $sig = $this->user->getSkin()->userLink( $author->getID(), $author->getName() ) .
 687+ $this->user->getSkin()->userToolLinks( $author->getID(), $author->getName() );
 688+ $this->output->addHTML( wfMsg( 'lqt_move_placeholder',
 689+ '<a href="' . $target->getFullURL() . '">' . $target->getText() . '</a>',
 690+ $sig,
 691+ $wgLang->date( $thread->modified() ),
 692+ $wgLang->time( $thread->modified() )
 693+ ) );
 694+ return;
 695+ }
 696+
 697+ if ( $thread->type() == Threads::TYPE_DELETED ) {
 698+ wfLoadExtensionMessages( 'LiquidThreads' );
 699+ if ( in_array( 'deletedhistory', $this->user->getRights() ) ) {
 700+ $this->output->addHTML( '<p>' . wfMsg( 'lqt_thread_deleted_for_sysops' ) . '</p>' );
 701+ }
 702+ else {
 703+ $this->output->addHTML( '<p><em>' . wfMsg( 'lqt_thread_deleted' ) . '</em></p>' );
 704+ return;
 705+ }
 706+ }
 707+
 708+ global $wgLqtThreadArchiveStartDays, $wgLqtThreadArchiveInactiveDays;
 709+
 710+ $timestamp = new Date( $thread->modified() );
 711+ if ( $thread->summary() ) {
 712+ $this->showSummary( $thread );
 713+ } else if ( $timestamp->isBefore( Date::now()->nDaysAgo( $wgLqtThreadArchiveStartDays ) )
 714+ && !$thread->summary() && !$thread->hasSuperthread() && !$thread->isHistorical() )
 715+ {
 716+ wfLoadExtensionMessages( 'LiquidThreads' );
 717+ $this->output->addHTML( '<p class="lqt_summary_notice">' . wfMsgExt( 'lqt_summary_notice', 'parsemag',
 718+ '<a href="' . $this->permalinkUrl( $thread, 'summarize' ) . '">' . wfMsg( 'lqt_summary_notice_link' ) . '</a>',
 719+ $wgLqtThreadArchiveStartDays
 720+ ) . '</p>' );
 721+ }
 722+
 723+ $this->openDiv( 'lqt_thread', "lqt_thread_id_{$thread->id()}" );
 724+
 725+ $this->showRootPost( $thread );
 726+
 727+ if ( $thread->hasSubthreads() ) $this->indent( $thread );
 728+ foreach ( $thread->subthreads() as $st ) {
 729+ $this->showThread( $st );
 730+ }
 731+ if ( $thread->hasSubthreads() ) $this->unindent( $thread );
 732+
 733+ $this->closeDiv();
 734+ }
 735+
 736+ function indent( $thread ) {
 737+ if ( $this->headerLevel <= $this->maxIndentationLevel ) {
 738+ $this->output->addHTML( '<dl class="lqt_replies"><dd>' );
 739+ } else {
 740+ $this->output->addHTML( '<div class="lqt_replies_without_indent">' );
 741+ }
 742+ $this->lastUnindentedSuperthread = null;
 743+ $this->headerLevel += 1;
 744+ }
 745+ function unindent( $thread ) {
 746+ if ( $this->headerLevel <= $this->maxIndentationLevel + 1 ) {
 747+ $this->output->addHTML( '</dd></dl>' );
 748+ } else {
 749+ $this->output->addHTML( '</div>' );
 750+ }
 751+ // See the beginning of showThread().
 752+ $this->lastUnindentedSuperthread = $thread->superthread();
 753+ $this->headerLevel -= 1;
 754+ }
 755+
 756+ function openDiv( $class = '', $id = '' ) {
 757+ $this->output->addHTML( Xml::openElement( 'div', array( 'class' => $class, 'id' => $id ) ) );
 758+ }
 759+
 760+ function closeDiv() {
 761+ $this->output->addHTML( Xml::closeElement( 'div' ) );
 762+ }
 763+
 764+ function showSummary( $t ) {
 765+ if ( !$t->summary() ) return;
 766+ wfLoadExtensionMessages( 'LiquidThreads' );
 767+ $label = wfMsg( 'lqt_summary_label' );
 768+ $edit = strtolower( wfMsg( 'edit' ) );
 769+ $link = strtolower( wfMsg( 'lqt_permalink' ) );
 770+ $this->output->addHTML( <<<HTML
 771+ <div class='lqt_thread_permalink_summary'>
 772+ <span class="lqt_thread_permalink_summary_title">
 773+ $label
 774+ </span><span class="lqt_thread_permalink_summary_edit">
 775+ [<a href="{$t->summary()->getTitle()->getFullURL()}">$link</a>]
 776+ [<a href="{$this->permalinkUrl($t,'summarize')}">$edit</a>]
 777+ </span>
 778+HTML
 779+ );
 780+ $this->openDiv( 'lqt_thread_permalink_summary_body' );
 781+ $this->showPostBody( $t->summary() );
 782+ $this->closeDiv();
 783+ $this->closeDiv();
 784+ }
 785+
 786+}
Property changes on: trunk/extensions/LiquidThreads/classes/LqtView.php
___________________________________________________________________
Name: svn:eol-style
1787 + native
Index: trunk/extensions/LiquidThreads/classes/LqtDispatch.php
@@ -0,0 +1,244 @@
 2+<?php
 3+
 4+class LqtDispatch {
 5+ public static $views = array(
 6+ 'TalkpageArchiveView' => 'TalkpageArchiveView',
 7+ 'TalkpageHeaderView' => 'TalkpageHeaderView',
 8+ 'TalkpageView' => 'TalkpageView',
 9+ 'ThreadHistoryListingView' => 'ThreadHistoryListingView',
 10+ 'ThreadHistoricalRevisionView' => 'ThreadHistoricalRevisionView',
 11+ 'IndividualThreadHistoryView' => 'IndividualThreadHistoryView',
 12+ 'ThreadDiffView' => 'ThreadDiffView',
 13+ 'ThreadPermalinkView' => 'ThreadPermalinkView',
 14+ 'ThreadProtectionFormView' => 'ThreadProtectionFormView',
 15+ 'ThreadWatchView' => 'ThreadWatchView',
 16+ 'SummaryPageView' => 'SummaryPageView'
 17+ );
 18+
 19+ static function talkpageMain( &$output, &$talk_article, &$title, &$user, &$request ) {
 20+ // We are given a talkpage article and title. Find the associated
 21+ // non-talk article and pass that to the view.
 22+ $article = new Article( $title->getSubjectPage() );
 23+
 24+ if ( $title->getSubjectPage()->getNamespace() == NS_LQT_THREAD ) {
 25+ // Threads don't have talk pages; redirect to the thread page.
 26+ $output->redirect( $title->getSubjectPage()->getFullUrl() );
 27+ }
 28+
 29+ /* Certain actions apply to the "header", which is stored in the actual talkpage
 30+ in the database. Drop everything and behave like a normal page if those
 31+ actions come up, to avoid hacking the various history, editing, etc. code. */
 32+ $action = $request->getVal( 'action' );
 33+ $header_actions = array( 'history', 'edit', 'submit' );
 34+ global $wgRequest;
 35+ if ( $request->getVal( 'lqt_method', null ) === null &&
 36+ ( in_array( $action, $header_actions ) ||
 37+ $request->getVal( 'diff', null ) !== null ) ) {
 38+ $viewname = self::$views['TalkpageHeaderView'];
 39+ } else if ( $action == 'protect' || $action == 'unprotect' ) {
 40+ $viewname = self::$views['ThreadProtectionFormView'];
 41+ } else if ( $request->getVal( 'lqt_method' ) == 'talkpage_archive' ) {
 42+ $viewname = self::$views['TalkpageArchiveView'];
 43+ } else {
 44+ $viewname = self::$views['TalkpageView'];
 45+ }
 46+ $view = new $viewname( $output, $article, $title, $user, $request );
 47+ return $view->show();
 48+ }
 49+
 50+ static function threadPermalinkMain( &$output, &$article, &$title, &$user, &$request ) {
 51+
 52+ $action = $request->getVal( 'action' );
 53+ $lqt_method = $request->getVal( 'lqt_method' );
 54+
 55+ if ( $lqt_method == 'thread_history' ) {
 56+ $viewname = self::$views['ThreadHistoryListingView'];
 57+ }
 58+ else if ( $lqt_method == 'diff' ) { // this clause and the next must be in this order.
 59+ $viewname = self::$views['ThreadDiffView'];
 60+ }
 61+ else if ( $action == 'history'
 62+ || $request->getVal( 'diff', null ) !== null
 63+ || $request->getVal( 'oldid', null ) !== null ) {
 64+ $viewname = self::$views['IndividualThreadHistoryView'];
 65+ }
 66+ else if ( $action == 'protect' || $action == 'unprotect' ) {
 67+ $viewname = self::$views['ThreadProtectionFormView'];
 68+ }
 69+ else if ( $request->getVal( 'lqt_oldid', null ) !== null ) {
 70+ $viewname = self::$views['ThreadHistoricalRevisionView'];
 71+ }
 72+ else if ( $action == 'watch' || $action == 'unwatch' ) {
 73+ $viewname = self::$views['ThreadWatchView'];
 74+ }
 75+ else {
 76+ $viewname = self::$views['ThreadPermalinkView'];
 77+ }
 78+ $view = new $viewname( $output, $article, $title, $user, $request );
 79+ return $view->show();
 80+ }
 81+
 82+ static function threadSummaryMain( &$output, &$article, &$title, &$user, &$request ) {
 83+ $viewname = self::$views['SummaryPageView'];
 84+ $view = new $viewname( $output, $article, $title, $user, $request );
 85+ return $view->show();
 86+ }
 87+
 88+ /**
 89+ * If the page we recieve is a Liquid Threads page of any kind, process it
 90+ * as needed and return True. If it's a normal, non-liquid page, return false.
 91+ */
 92+ static function tryPage( $output, $article, $title, $user, $request ) {
 93+ if ( $title->isTalkPage() ) {
 94+ return self::talkpageMain ( $output, $article, $title, $user, $request );
 95+ } else if ( $title->getNamespace() == NS_LQT_THREAD ) {
 96+ return self::threadPermalinkMain( $output, $article, $title, $user, $request );
 97+ } else if ( $title->getNamespace() == NS_LQT_SUMMARY ) {
 98+ return self::threadSummaryMain( $output, $article, $title, $user, $request );
 99+ }
 100+ return true;
 101+ }
 102+
 103+ static function onPageMove( $movepage, $ot, $nt ) {
 104+ // We are being invoked on the subject page, not the talk page.
 105+
 106+ $threads = Threads::where( array( Threads::articleClause( new Article( $ot ) ),
 107+ Threads::topLevelClause() ) );
 108+
 109+ foreach ( $threads as $t ) {
 110+ $t->moveToSubjectPage( $nt, false );
 111+ }
 112+
 113+ return true;
 114+ }
 115+
 116+ static function makeLinkObj( &$returnValue, &$linker, $nt, $text, $query, $trail, $prefix ) {
 117+ if ( ! $nt->isTalkPage() )
 118+ return true;
 119+
 120+ // Talkpages with headers.
 121+ if ( $nt->getArticleID() != 0 )
 122+ return true;
 123+
 124+ // Talkpages without headers -- check existance of threads.
 125+ $article = new Article( $nt->getSubjectPage() );
 126+ $threads = Threads::where( Threads::articleClause( $article ), "LIMIT 1" );
 127+ if ( count( $threads ) == 0 ) {
 128+ // We want it to look like a broken link, but not have action=edit, since that
 129+ // will edit the header, so we can't use makeBrokenLinkObj. This code is copied
 130+ // from the body of that method.
 131+ $url = $nt->escapeLocalURL( $query );
 132+ if ( '' == $text )
 133+ $text = htmlspecialchars( $nt->getPrefixedText() );
 134+ $style = $linker->getInternalLinkAttributesObj( $nt, $text, "yes" );
 135+ list( $inside, $trail ) = Linker::splitTrail( $trail );
 136+ $returnValue = "<a href=\"{$url}\"{$style}>{$prefix}{$text}{$inside}</a>{$trail}";
 137+ }
 138+ else {
 139+ $returnValue = $linker->makeKnownLinkObj( $nt, $text, $query, $trail, $prefix );
 140+ }
 141+ return false;
 142+ }
 143+
 144+ // One major place that doesn't use makeLinkObj is the tabs. So override known/unknown there too.
 145+ static function tabAction( &$skintemplate, $title, $message, $selected, $checkEdit,
 146+ &$classes, &$query, &$text, &$result ) {
 147+ if ( ! $title->isTalkPage() )
 148+ return true;
 149+ if ( $title->getArticleID() != 0 ) {
 150+ $query = "";
 151+ return true;
 152+ }
 153+ // It's a talkpage without a header. Get rid of action=edit always,
 154+ // color as apropriate.
 155+ $query = "";
 156+ $article = new Article( $title->getSubjectPage() );
 157+ $threads = Threads::where( Threads::articleClause( $article ), "LIMIT 1" );
 158+ if ( count( $threads ) != 0 ) {
 159+ $i = array_search( 'new', $classes ); if ( $i !== false ) {
 160+ array_splice( $classes, $i, 1 );
 161+ }
 162+ }
 163+ return true;
 164+ }
 165+
 166+ static function customizeOldChangesList( &$changeslist, &$s, &$rc ) {
 167+ if ( $rc->getTitle()->getNamespace() == NS_LQT_THREAD ) {
 168+ $thread = Threads::withRoot( new Post( $rc->getTitle() ) );
 169+ if ( !$thread ) return true;
 170+
 171+ LqtView::addJSandCSS(); // TODO only do this once.
 172+ wfLoadExtensionMessages( 'LiquidThreads' );
 173+
 174+ if ( $rc->mAttribs['rc_type'] != RC_NEW ) {
 175+ // Add whether it was original author.
 176+ // TODO: this only asks whether ANY edit has been by another, not this edit.
 177+ // But maybe that's what we want.
 178+ if ( $thread->editedness() == Threads::EDITED_BY_OTHERS )
 179+ $appendix = ' <span class="lqt_rc_author_notice lqt_rc_author_notice_others">' .
 180+ wfMsg( 'lqt_rc_author_others' ) . '</span>';
 181+ else
 182+ $appendix = ' <span class="lqt_rc_author_notice lqt_rc_author_notice_original">' .
 183+ wfMsg( 'lqt_rc_author_original' ) . '</span>';
 184+ $s = preg_replace( '/\<\/li\>$/', $appendix . '</li>', $s );
 185+ }
 186+ else {
 187+ $sig = "";
 188+ $changeslist->insertUserRelatedLinks( $sig, $rc );
 189+
 190+ // This should be stored in RC.
 191+ $quote = Revision::newFromId( $rc->mAttribs['rc_this_oldid'] )->getText();
 192+ if ( strlen( $quote ) > 230 ) {
 193+ $quote = substr( $quote, 0, 200 ) .
 194+ $changeslist->skin->link( $thread->title(), wfMsg( 'lqt_rc_ellipsis' ),
 195+ array( 'class' => 'lqt_rc_ellipsis' ), array(), array( 'known' ) );
 196+ }
 197+ // TODO we must parse or sanitize the quote.
 198+
 199+ if ( $thread->isTopmostThread() ) {
 200+ $message_name = 'lqt_rc_new_discussion';
 201+ $tmp_title = $thread->title();
 202+ } else {
 203+ $message_name = 'lqt_rc_new_reply';
 204+ $tmp_title = $thread->topmostThread()->title();
 205+ $tmp_title->setFragment( '#' . LqtView::anchorName( $thread ) );
 206+ }
 207+
 208+ $thread_link = $changeslist->skin->link(
 209+ $tmp_title,
 210+ $thread->subjectWithoutIncrement(),
 211+ array(), array(), array( 'known' ) );
 212+
 213+ $talkpage_link = $changeslist->skin->link(
 214+ $thread->article()->getTitle()->getTalkPage(),
 215+ null,
 216+ array(), array(), array( 'known' ) );
 217+
 218+ $s = wfMsg( $message_name, $thread_link, $talkpage_link, $sig )
 219+ . "<blockquote class=\"lqt_rc_blockquote\">$quote</blockquote>";
 220+ }
 221+ }
 222+ return true;
 223+ }
 224+
 225+ static function setNewtalkHTML( $skintemplate, $tpl ) {
 226+ global $wgUser, $wgTitle, $wgOut;
 227+ wfLoadExtensionMessages( 'LiquidThreads' );
 228+ $newmsg_t = SpecialPage::getTitleFor( 'NewMessages' );
 229+ $watchlist_t = SpecialPage::getTitleFor( 'Watchlist' );
 230+ $usertalk_t = $wgUser->getTalkPage();
 231+ if ( $wgUser->getNewtalk()
 232+ && ! $newmsg_t->equals( $wgTitle )
 233+ && ! $watchlist_t->equals( $wgTitle )
 234+ && ! $usertalk_t->equals( $wgTitle )
 235+ ) {
 236+ $s = wfMsgExt( 'lqt_youhavenewmessages', array( 'parseinline' ), $newmsg_t->getFullURL() );
 237+ $tpl->set( "newtalk", $s );
 238+ $wgOut->setSquidMaxage( 0 );
 239+ } else {
 240+ $tpl->set( "newtalk", '' );
 241+ }
 242+
 243+ return true;
 244+ }
 245+}

Status & tagging log