r36682 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r36681‎ | r36682 | r36683 >
Date:19:12, 26 June 2008
Author:ialex
Status:old
Tags:
Comment:
Rewritten Special:Recentchangeslinked, now using a subclass of SpecialRecentchanges:
* (bugs 4832, 9481, 12890) Now has all options present in Special:Recentchanges (hide myself, bots, ...)
* Using "showlinkedto" on a template now uses the templatelinks table to fetch links instead of pagelinks
* [[Special:Recentchanges/0]] now works as expected, thanks to Nikerabbit for pointing this out
Modified paths:
  • /trunk/phase3/RELEASE-NOTES (modified) (history)
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/SpecialPage.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialRecentchanges.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialRecentchangeslinked.php (modified) (history)

Diff [purge]

Index: trunk/phase3/includes/AutoLoader.php
@@ -423,7 +423,8 @@
424424 'SpecialMostlinkedtemplates' => 'includes/specials/SpecialMostlinkedtemplates.php',
425425 'SpecialPrefixindex' => 'includes/specials/SpecialPrefixindex.php',
426426 'SpecialRandomredirect' => 'includes/specials/SpecialRandomredirect.php',
427 - 'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.php',
 427+ 'SpecialRecentchanges' => 'includes/specials/SpecialRecentchanges.php',
 428+ 'SpecialRecentchangeslinked' => 'includes/specials/SpecialRecentchangeslinked.php',
428429 'SpecialSearch' => 'includes/specials/SpecialSearch.php',
429430 'SpecialVersion' => 'includes/specials/SpecialVersion.php',
430431 'UncategorizedCategoriesPage' => 'includes/specials/SpecialUncategorizedcategories.php',
Index: trunk/phase3/includes/specials/SpecialRecentchangeslinked.php
@@ -1,191 +1,130 @@
22 <?php
 3+
34 /**
45 * This is to display changes made to all articles linked in an article.
5 - * @file
66 * @ingroup SpecialPage
77 */
 8+class SpecialRecentchangeslinked extends SpecialRecentchanges {
89
9 -require_once( 'SpecialRecentchanges.php' );
 10+ function __construct(){
 11+ SpecialPage::SpecialPage( 'Recentchangeslinked' );
 12+ }
1013
11 -/**
12 - * Entrypoint
13 - * @param string $par parent page we will look at
14 - */
15 -function wfSpecialRecentchangeslinked( $par = NULL ) {
16 - global $wgUser, $wgOut, $wgLang, $wgContLang, $wgRequest, $wgTitle, $wgScript;
 14+ public function getDefaultOptions() {
 15+ $opts = parent::getDefaultOptions();
 16+ $opts->add( 'target', '' );
 17+ $opts->add( 'showlinkedto', false );
 18+ return $opts;
 19+ }
1720
18 - $days = $wgRequest->getInt( 'days' );
19 - $target = isset($par) ? $par : $wgRequest->getVal( 'target' );
20 - $hideminor = $wgRequest->getBool( 'hideminor' ) ? 1 : 0;
21 - $showlinkedto = $wgRequest->getBool( 'showlinkedto' ) ? 1 : 0;
22 -
23 - $title = Title::newFromURL( $target );
24 - $target = $title ? $title->getPrefixedText() : '';
25 -
26 - $wgOut->setPagetitle( wfMsg( 'recentchangeslinked' ) );
27 - $sk = $wgUser->getSkin();
28 -
29 - $wgOut->addHTML(
30 - Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
31 - Xml::openElement( 'fieldset' ) .
32 - Xml::element( 'legend', array(), wfMsg( 'recentchangeslinked' ) ) . "\n" .
33 - Xml::inputLabel( wfMsg( 'recentchangeslinked-page' ), 'target', 'recentchangeslinked-target', 40, $target ) .
34 - "&nbsp;&nbsp;&nbsp;<span style='white-space: nowrap'>" .
35 - Xml::check( 'showlinkedto', $showlinkedto, array('id' => 'showlinkedto') ) . ' ' .
36 - Xml::label( wfMsg("recentchangeslinked-to"), 'showlinkedto' ) .
37 - "</span><br/>\n" .
38 - Xml::hidden( 'title', $wgTitle->getPrefixedText() ). "\n" .
39 - Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
40 - Xml::closeElement( 'fieldset' ) .
41 - Xml::closeElement( 'form' ) . "\n"
42 - );
43 -
44 - if ( !$target ) {
45 - return;
 21+ public function parseParameters( $par, FormOptions $opts ) {
 22+ $opts['target'] = $par;
4623 }
47 - $nt = Title::newFromURL( $target );
48 - if( !$nt ) {
49 - $wgOut->showErrorPage( 'notargettitle', 'notargettext' );
50 - return;
51 - }
52 - $id = $nt->getArticleId();
5324
54 - $wgOut->setPageTitle( wfMsg( 'recentchangeslinked-title', $nt->getPrefixedText() ) );
55 - $wgOut->setSyndicated();
56 - $wgOut->setFeedAppendQuery( "target=" . urlencode( $target ) );
57 -
58 - if ( !$days ) {
59 - $days = (int)$wgUser->getOption( 'rcdays', 7 );
 25+ public function feedSetup(){
 26+ global $wgRequest;
 27+ $opts = parent::feedSetup();
 28+ $opts['target'] = $wgRequest->getVal( 'target' );
 29+ return $opts;
6030 }
61 - list( $limit, /* offset */ ) = wfCheckLimits( 100, 'rclimit' );
6231
63 - $dbr = wfGetDB( DB_SLAVE,'recentchangeslinked' );
64 - $cutoff = $dbr->timestamp( time() - ( $days * 86400 ) );
65 -
66 - $hideminor = ($hideminor ? 1 : 0);
67 - if ( $hideminor ) {
68 - $mlink = $sk->makeKnownLink( $wgContLang->specialPage( 'Recentchangeslinked' ),
69 - wfMsg( 'show' ), 'target=' . htmlspecialchars( $nt->getPrefixedURL() ) .
70 - "&days={$days}&limit={$limit}&hideminor=0&showlinkedto={$showlinkedto}" );
71 - } else {
72 - $mlink = $sk->makeKnownLink( $wgContLang->specialPage( "Recentchangeslinked" ),
73 - wfMsg( "hide" ), "target=" . htmlspecialchars( $nt->getPrefixedURL() ) .
74 - "&days={$days}&limit={$limit}&hideminor=1&showlinkedto={$showlinkedto}" );
 32+ public function getFeedObject( $feedFormat ){
 33+ $feed = new ChangesFeed( $feedFormat, false );
 34+ $feedObj = $feed->getFeedObject(
 35+ wfMsgForContent( 'recentchangeslinked-title', $this->mTargetTitle->getPrefixedText() ),
 36+ wfMsgForContent( 'recentchangeslinked' )
 37+ );
 38+ return array( $feed, $feedObj );
7539 }
76 - if ( $hideminor ) {
77 - $cmq = 'AND rc_minor=0';
78 - } else { $cmq = ''; }
7940
80 - list($recentchanges, $categorylinks, $pagelinks, $watchlist) =
81 - $dbr->tableNamesN( 'recentchanges', 'categorylinks', 'pagelinks', "watchlist" );
 41+ public function doMainQuery( $conds, $opts ) {
 42+ global $wgUser, $wgOut;
8243
83 - $uid = $wgUser->getId();
84 - // The fields we are selecting
85 - $fields = "rc_cur_id,rc_namespace,rc_title,
86 - rc_user,rc_comment,rc_user_text,rc_timestamp,rc_minor,rc_log_type,rc_log_action,rc_params,rc_deleted,
87 - rc_new, rc_id, rc_this_oldid, rc_last_oldid, rc_bot, rc_patrolled, rc_type, rc_old_len, rc_new_len";
88 - $fields .= $uid ? ",wl_user" : "";
 44+ $title = Title::newFromURL( $opts['target'] );
 45+ $showlinkedto = $opts['showlinkedto'];
 46+ $limit = $opts['limit'];
8947
90 - // Check if this should be a feed
91 -
92 - $feed = false;
93 - global $wgFeedLimit;
94 - $feedFormat = $wgRequest->getVal( 'feed' );
95 - if( $feedFormat ) {
96 - $feed = new ChangesFeed( $feedFormat, false );
97 - # Sanity check
98 - if( $limit > $wgFeedLimit ) {
99 - $limit = $wgFeedLimit;
 48+ $target = $title ? $title->getPrefixedText() : '';
 49+ if ( $target === '' ) {
 50+ return false;
10051 }
101 - }
 52+ if( !$title ){
 53+ global $wgOut;
 54+ $wgOut->showErrorPage( 'notargettitle', 'notargettext' );
 55+ return false;
 56+ }
10257
103 - // If target is a Category, use categorylinks and invert from and to
104 - if( $nt->getNamespace() == NS_CATEGORY ) {
105 - $catkey = $dbr->addQuotes( $nt->getDBkey() );
106 - # The table clauses
107 - $tables = "$categorylinks, $recentchanges";
108 - $tables .= $uid ? " LEFT JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : "";
 58+ $wgOut->setPageTitle( wfMsg( 'recentchangeslinked-title', $target ) );
 59+ $this->mTargetTitle = $title;
10960
110 - $sql = "SELECT /* wfSpecialRecentchangeslinked */ $fields FROM $tables
111 - WHERE rc_timestamp > '{$cutoff}' {$cmq}
112 - AND cl_from=rc_cur_id
113 - AND cl_to=$catkey
114 - GROUP BY $fields ORDER BY rc_timestamp DESC LIMIT {$limit}"; // Shitty-ass GROUP BY by for postgres apparently
115 - } else {
116 - if( $showlinkedto ) {
117 - $ns = $dbr->addQuotes( $nt->getNamespace() );
118 - $dbkey = $dbr->addQuotes( $nt->getDBkey() );
119 - $joinConds = "AND pl_namespace={$ns} AND pl_title={$dbkey} AND pl_from=rc_cur_id";
 61+ $dbr = wfGetDB( DB_SLAVE, 'recentchangeslinked' );
 62+ $id = $title->getArticleId();
 63+
 64+ $tables = array( 'recentchanges' );
 65+ $select = array( $dbr->tableName( 'recentchanges' ) . '.*' );
 66+ $join_conds = array();
 67+
 68+ if( $title->getNamespace() == NS_CATEGORY ) {
 69+ $tables[] = 'categorylinks';
 70+ $conds['cl_to'] = $title->getDBkey();
 71+ $join_conds['categorylinks'] = array( 'LEFT JOIN', 'cl_from=rc_cur_id' );
12072 } else {
121 - $joinConds = "AND pl_namespace=rc_namespace AND pl_title=rc_title AND pl_from=$id";
 73+ if( $showlinkedto ) {
 74+ if( $title->getNamespace() == NS_TEMPLATE ){
 75+ $tables[] = 'templatelinks';
 76+ $conds['tl_namespace'] = $title->getNamespace();
 77+ $conds['tl_title'] = $title->getDBkey();
 78+ $join_conds['templatelinks'] = array( 'LEFT JOIN', 'tl_from=rc_cur_id' );
 79+ } else {
 80+ $tables[] = 'pagelinks';
 81+ $conds['pl_namespace'] = $title->getNamespace();
 82+ $conds['pl_title'] = $title->getDBkey();
 83+ $join_conds['pagelinks'] = array( 'LEFT JOIN', 'pl_from=rc_cur_id' );
 84+ }
 85+ } else {
 86+ $tables[] = 'pagelinks';
 87+ $conds['pl_from'] = $id;
 88+ $join_conds['pagelinks'] = array( 'LEFT JOIN', 'pl_namespace = rc_namespace AND pl_title = rc_title' );
 89+ }
12290 }
123 - # The table clauses
124 - $tables = "$pagelinks, $recentchanges";
125 - $tables .= $uid ? " LEFT JOIN $watchlist ON wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace " : "";
12691
127 - $sql = "SELECT /* wfSpecialRecentchangeslinked */ $fields FROM $tables
128 - WHERE rc_timestamp > '{$cutoff}' {$cmq}
129 - {$joinConds}
130 - GROUP BY $fields ORDER BY rc_timestamp DESC LIMIT {$limit}"; // Shitty-ass GROUP BY by for postgres apparently
131 - }
132 - # Actually do the query
133 - $res = $dbr->query( $sql, __METHOD__ );
134 - $count = $dbr->numRows( $res );
135 - $rchanges = array();
136 - # Output feeds now and be done with it!
137 - if( $feed ) {
138 - if( $count ) {
139 - $counter = 1;
140 - while ( $limit ) {
141 - if ( 0 == $count ) { break; }
142 - $obj = $dbr->fetchObject( $res );
143 - --$count;
144 - $rc = RecentChange::newFromRow( $obj );
145 - $rc->counter = $counter++;
146 - --$limit;
147 - $rchanges[] = $obj;
148 - }
 92+ if( $uid = $wgUser->getId() ) {
 93+ $tables[] = 'watchlist';
 94+ $join_conds['watchlist'] = array( 'LEFT JOIN', "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace" );
 95+ $select[] = 'wl_user';
14996 }
150 - $wgOut->disable();
15197
152 - $feedObj = $feed->getFeedObject(
153 - wfMsgForContent( 'recentchangeslinked-title', $nt->getPrefixedText() ),
154 - wfMsgForContent( 'recentchangeslinked' )
155 - );
156 - ChangesFeed::generateFeed( $rchanges, $feedObj );
157 - return;
158 - }
159 -
160 - # Otherwise, carry on with regular output...
161 - $wgOut->addHTML("&lt; ".$sk->makeLinkObj($nt, "", "redirect=no" )."<br />\n");
162 - $note = wfMsgExt( "rcnote", array ( 'parseinline' ), $limit, $days, $wgLang->timeAndDate( wfTimestampNow(), true ) );
163 - $wgOut->addHTML( "<hr />\n{$note}\n<br />" );
 98+ $res = $dbr->select( $tables, $select, $conds, __METHOD__,
 99+ array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ), $join_conds );
164100
165 - $note = rcDayLimitlinks( $days, $limit, "Recentchangeslinked",
166 - "target=" . $nt->getPrefixedURL() . "&hideminor={$hideminor}&showlinkedto={$showlinkedto}",
167 - false, $mlink );
 101+ if( $dbr->numRows( $res ) == 0 )
 102+ $this->mResultEmpty = true;
168103
169 - $wgOut->addHTML( $note."\n" );
170 -
171 - $list = ChangesList::newFromUser( $wgUser );
172 - $s = $list->beginRecentChangesList();
173 -
174 - if ( $count ) {
175 - $counter = 1;
176 - while ( $limit ) {
177 - if ( 0 == $count ) { break; }
178 - $obj = $dbr->fetchObject( $res );
179 - --$count;
180 - $rc = RecentChange::newFromRow( $obj );
181 - $rc->counter = $counter++;
182 - $s .= $list->recentChangesLine( $rc , !empty( $obj->wl_user) );
183 - --$limit;
 104+ return $res;
 105+ }
 106+
 107+ function getExtraOptions( $opts ){
 108+ $opts->consumeValues( array( 'showlinkedto', 'target' ) );
 109+ $extraOpts = array();
 110+ $extraOpts['namespace'] = $this->namespaceFilterForm( $opts );
 111+ $extraOpts['target'] = array( wfMsg( 'recentchangeslinked-page' ),
 112+ Xml::input( 'target', 40, $opts['target'] ) .
 113+ Xml::check( 'showlinkedto', $opts['showlinkedto'], array('id' => 'showlinkedto') ) . ' ' .
 114+ Xml::label( wfMsg("recentchangeslinked-to"), 'showlinkedto' ) );
 115+ $extraOpts['submit'] = Xml::submitbutton( wfMsg('allpagessubmit') );
 116+ return $extraOpts;
 117+ }
 118+
 119+ function setTopText( &$out, $opts ){}
 120+
 121+ function setBottomText( &$out, $opts ){
 122+ if( $target = $opts['target'] ){
 123+ global $wgUser;
 124+ $out->setFeedAppendQuery( "target=" . urlencode( $target ) );
 125+ $out->addHTML("&lt; ".$wgUser->getSkin()->makeLinkObj( Title::newFromUrl( $target ), "", "redirect=no" )."<hr />\n");
184126 }
185 - } else {
186 - $wgOut->addWikiMsg('recentchangeslinked-noresult');
 127+ if( isset( $this->mResultEmpty ) && $this->mResultEmpty ){
 128+ $out->addWikiMsg( 'recentchangeslinked-noresult' );
 129+ }
187130 }
188 - $s .= $list->endRecentChangesList();
189 -
190 - $dbr->freeResult( $res );
191 - $wgOut->addHTML( $s );
192131 }
Index: trunk/phase3/includes/specials/SpecialRecentchanges.php
@@ -1,15 +1,20 @@
22 <?php
 3+
34 /**
4 - * @file
 5+ * Implements Special:Recentchanges
56 * @ingroup SpecialPage
67 */
7 -
88 class SpecialRecentChanges extends SpecialPage {
99 public function __construct() {
10 - SpecialPage::SpecialPage( 'Recentchanges' );
 10+ SpecialPage::SpecialPage( 'Recentchanges' );
1111 $this->includable( true );
1212 }
1313
 14+ /**
 15+ * Get a FormOptions object containing the default options
 16+ *
 17+ * @return FormOptions
 18+ */
1419 public function getDefaultOptions() {
1520 $opts = new FormOptions();
1621
@@ -31,8 +36,13 @@
3237 $opts->add( 'categories_any', false );
3338
3439 return $opts;
35 -}
 40+ }
3641
 42+ /**
 43+ * Get a FormOptions object with options as specified by the user
 44+ *
 45+ * @return FormOptions
 46+ */
3747 public function setup( $parameters ) {
3848 global $wgUser, $wgRequest;
3949
@@ -51,6 +61,11 @@
5262 return $opts;
5363 }
5464
 65+ /**
 66+ * Get a FormOptions object sepcific for feed requests
 67+ *
 68+ * @return FormOptions
 69+ */
5570 public function feedSetup() {
5671 global $wgFeedLimit, $wgRequest;
5772 $opts = $this->getDefaultOptions();
@@ -59,6 +74,11 @@
6075 return $opts;
6176 }
6277
 78+ /**
 79+ * Main execution point
 80+ *
 81+ * @param $parameters string
 82+ */
6383 public function execute( $parameters ) {
6484 global $wgRequest, $wgOut;
6585 $feedFormat = $wgRequest->getVal( 'feed' );
@@ -73,12 +93,17 @@
7494
7595 $opts = $feedFormat ? $this->feedSetup() : $this->setup( $parameters );
7696 $this->setHeaders();
 97+ $this->outputHeader();
7798
7899 // Fetch results, prepare a batch link existence check query
79100 $rows = array();
80101 $batch = new LinkBatch;
81102 $conds = $this->buildMainQueryConds( $opts );
82103 $res = $this->doMainQuery( $conds, $opts );
 104+ if( $res === false ){
 105+ $this->doHeader( $opts );
 106+ return;
 107+ }
83108 $dbr = wfGetDB( DB_SLAVE );
84109 while( $row = $dbr->fetchObject( $res ) ){
85110 $rows[] = $row;
@@ -92,11 +117,7 @@
93118 $dbr->freeResult( $res );
94119
95120 if ( $feedFormat ) {
96 - $feed = new ChangesFeed( $feedFormat, 'rcfeed' );
97 - $feedObj = $feed->getFeedObject(
98 - wfMsgForContent( 'recentchanges' ),
99 - wfMsgForContent( 'recentchanges-feed-description' )
100 - );
 121+ list( $feed, $feedObj ) = $this->getFeedObject( $feedFormat );
101122 $feed->execute( $feedObj, $rows, $opts['limit'], $opts['hideminor'], $lastmod );
102123 } else {
103124 $batch->execute();
@@ -104,6 +125,27 @@
105126 }
106127 }
107128
 129+ /**
 130+ * Return an array with a ChangesFeed object and ChannelFeed object
 131+ *
 132+ * @return array
 133+ */
 134+ public function getFeedObject( $feedFormat ){
 135+ $feed = new ChangesFeed( $feedFormat, 'rcfeed' );
 136+ $feedObj = $feed->getFeedObject(
 137+ wfMsgForContent( 'recentchanges' ),
 138+ wfMsgForContent( 'recentchanges-feed-description' )
 139+ );
 140+ return array( $feed, $feedObj );
 141+ }
 142+
 143+ /**
 144+ * Process $par and put options found if $opts
 145+ * Mainly used when including the page
 146+ *
 147+ * @param $par String
 148+ * @param $opts FormOptions
 149+ */
108150 public function parseParameters( $par, FormOptions $opts ) {
109151 $bits = preg_split( '/\s*,\s*/', trim( $par ) );
110152 foreach ( $bits as $bit ) {
@@ -124,8 +166,14 @@
125167 }
126168 }
127169
128 - # Get last modified date, for client caching
129 - # Don't use this if we are using the patrol feature, patrol changes don't update the timestamp
 170+ /**
 171+ * Get last modified date, for client caching
 172+ * Don't use this if we are using the patrol feature, patrol changes don't
 173+ * update the timestamp
 174+ *
 175+ * @param $feedFormat String
 176+ * @return int or false
 177+ */
130178 public function checkLastModified( $feedFormat ) {
131179 global $wgUseRCPatrol, $wgOut;
132180 $dbr = wfGetDB( DB_SLAVE );
@@ -139,6 +187,12 @@
140188 return $lastmod;
141189 }
142190
 191+ /**
 192+ * Return an array of conditions depending of options set in $opts
 193+ *
 194+ * @param $opts FormOptions
 195+ * @return array
 196+ */
143197 public function buildMainQueryConds( FormOptions $opts ) {
144198 global $wgUser;
145199
@@ -191,7 +245,7 @@
192246 $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $wgUser->getName() );
193247 }
194248 }
195 -
 249+
196250 # Namespace filtering
197251 if ( $opts['namespace'] !== '' ) {
198252 if ( !$opts['invert'] ) {
@@ -204,6 +258,13 @@
205259 return $conds;
206260 }
207261
 262+ /**
 263+ * Process the query
 264+ *
 265+ * @param $conds array
 266+ * @param $opts FormOptions
 267+ * @return database result or false (for Recentchangeslinked only)
 268+ */
208269 public function doMainQuery( $conds, $opts ) {
209270 global $wgUser;
210271
@@ -217,7 +278,7 @@
218279 $invert = $opts['invert'];
219280
220281 // JOIN on watchlist for users
221 - if( $wgUser->getId() ) {
 282+ if( $uid ) {
222283 $tables[] = 'watchlist';
223284 $join_conds = array( 'watchlist' => array('LEFT JOIN',"wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace") );
224285 }
@@ -228,7 +289,7 @@
229290 // Also, if this is "all" or main namespace, just use timestamp index.
230291 if( is_null($namespace) || $invert || $namespace == NS_MAIN ) {
231292 $res = $dbr->select( $tables, '*', $conds, __METHOD__,
232 - array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit,
 293+ array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit,
233294 'USE INDEX' => array('recentchanges' => 'rc_timestamp') ),
234295 $join_conds );
235296 // We have a new_namespace_time index! UNION over new=(0,1) and sort result set!
@@ -237,14 +298,14 @@
238299 $sqlNew = $dbr->selectSQLText( $tables, '*',
239300 array( 'rc_new' => 1 ) + $conds,
240301 __METHOD__,
241 - array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit,
 302+ array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit,
242303 'USE INDEX' => array('recentchanges' => 'new_name_timestamp') ),
243304 $join_conds );
244305 // Old pages
245306 $sqlOld = $dbr->selectSQLText( $tables, '*',
246307 array( 'rc_new' => 0 ) + $conds,
247308 __METHOD__,
248 - array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit,
 309+ array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit,
249310 'USE INDEX' => array('recentchanges' => 'new_name_timestamp') ),
250311 $join_conds );
251312 # Join the two fast queries, and sort the result set
@@ -255,6 +316,12 @@
256317 return $res;
257318 }
258319
 320+ /**
 321+ * Send output to $wgOut, only called if not used feeds
 322+ *
 323+ * @param $rows array of database rows
 324+ * @param $opts FormOptions
 325+ */
259326 public function webOutput( $rows, $opts ) {
260327 global $wgOut, $wgUser, $wgRCShowWatchingUsers, $wgShowUpdatedMarker;
261328 global $wgAllowCategorizedRecentChanges;
@@ -272,7 +339,7 @@
273340 $list = ChangesList::newFromUser( $wgUser );
274341
275342 if ( $wgAllowCategorizedRecentChanges ) {
276 - rcFilterByCategories( $rows, $opts );
 343+ $this->filterByCategories( $rows, $opts );
277344 }
278345
279346 $s = $list->beginRecentChangesList();
@@ -323,28 +390,26 @@
324391 $wgOut->addHTML( $s );
325392 }
326393
 394+ /**
 395+ * Return the text to be displayed above the changes
 396+ *
 397+ * @param $opts FormOptions
 398+ * @return String: XHTML
 399+ */
327400 public function doHeader( $opts ) {
328401 global $wgScript, $wgOut;
329 - $wgOut->addWikiText( wfMsgForContentNoTrans( 'recentchangestext' ) );
330402
 403+ $this->setTopText( $wgOut, $opts );
 404+
331405 $defaults = $opts->getAllValues();
332406 $nondefaults = $opts->getChangedValues();
333407 $opts->consumeValues( array( 'namespace', 'invert' ) );
334408
335409 $panel = array();
336 - $panel[] = rcOptionsPanel( $defaults, $nondefaults );
 410+ $panel[] = $this->optionsPanel( $defaults, $nondefaults );
337411
338 - $extraOpts = array();
339 - $extraOpts['namespace'] = $this->namespaceFilterForm( $opts );
 412+ $extraOpts = $this->getExtraOptions( $opts );
340413
341 - global $wgAllowCategorizedRecentChanges;
342 - if ( $wgAllowCategorizedRecentChanges ) {
343 - $extraOpts['category'] = $this->categoryFilterForm( $opts );
344 - }
345 -
346 - wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) );
347 - $extraOpts['submit'] = Xml::submitbutton( wfMsg('allpagessubmit') );
348 -
349414 $out = Xml::openElement( 'table' );
350415 foreach ( $extraOpts as $optionRow ) {
351416 $out .= Xml::openElement( 'tr' );
@@ -370,13 +435,55 @@
371436 $panelString = implode( "\n", $panel );
372437
373438 $wgOut->addHTML( '<div class="rcoptions">' . $panelString . '</div>' );
 439+
 440+ $this->setBottomText( $wgOut, $opts );
374441 }
375442
376443 /**
377 - * Creates the choose namespace selection
378 - *
379 - * @return string
380 - */
 444+ * Get options to be displayed in a form
 445+ *
 446+ * @param $opts FormOptions
 447+ * @return array
 448+ */
 449+ function getExtraOptions( $opts ){
 450+ $extraOpts = array();
 451+ $extraOpts['namespace'] = $this->namespaceFilterForm( $opts );
 452+
 453+ global $wgAllowCategorizedRecentChanges;
 454+ if ( $wgAllowCategorizedRecentChanges ) {
 455+ $extraOpts['category'] = $this->categoryFilterForm( $opts );
 456+ }
 457+
 458+ wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) );
 459+ $extraOpts['submit'] = Xml::submitbutton( wfMsg('allpagessubmit') );
 460+ return $extraOpts;
 461+ }
 462+
 463+ /**
 464+ * Send the text to be displayed above the options
 465+ *
 466+ * @param $out OutputPage
 467+ * @param $opts FormOptions
 468+ */
 469+ function setTopText( &$out, $opts ){
 470+ $out->addWikiText( wfMsgForContentNoTrans( 'recentchangestext' ) );
 471+ }
 472+
 473+ /**
 474+ * Send the text to be displayed after the options, for use in
 475+ * Recentchangeslinked
 476+ *
 477+ * @param $out OutputPage
 478+ * @param $opts FormOptions
 479+ */
 480+ function setBottomText( &$out, $opts ){}
 481+
 482+ /**
 483+ * Creates the choose namespace selection
 484+ *
 485+ * @param $opts FormOptions
 486+ * @return string
 487+ */
381488 protected function namespaceFilterForm( FormOptions $opts ) {
382489 $nsSelect = HTMLnamespaceselector( $opts['namespace'], '' );
383490 $nsLabel = Xml::label( wfMsg('namespace'), 'namespace' );
@@ -384,6 +491,12 @@
385492 return array( $nsLabel, "$nsSelect $invert" );
386493 }
387494
 495+ /**
 496+ * Create a input to filter changes by categories
 497+ *
 498+ * @param $opts FormOptions
 499+ * @return array
 500+ */
388501 protected function categoryFilterForm( FormOptions $opts ) {
389502 list( $label, $input ) = Xml::inputLabelSep( wfMsg('rc_categories'),
390503 'categories', 'mw-categories', false, $opts['categories'] );
@@ -394,216 +507,151 @@
395508 return array( $label, $input );
396509 }
397510
398 -}
 511+ /**
 512+ * Filter $rows by categories set in $opts
 513+ *
 514+ * @param $rows array of database rows
 515+ * @param $opts FormOptions
 516+ */
 517+ function filterByCategories( &$rows, FormOptions $opts ) {
 518+ $categories = array_map( 'trim', explode( "|" , $opts['categories'] ) );
399519
400 -function rcFilterByCategories ( &$rows, FormOptions $opts ) {
401 - $categories = array_map( 'trim', explode( "|" , $opts['categories'] ) );
 520+ if( empty($categories) ) {
 521+ return;
 522+ }
402523
403 - if( empty($categories) ) {
404 - return;
405 - }
 524+ # Filter categories
 525+ $cats = array();
 526+ foreach ( $categories as $cat ) {
 527+ $cat = trim( $cat );
 528+ if ( $cat == "" ) continue;
 529+ $cats[] = $cat;
 530+ }
406531
407 - # Filter categories
408 - $cats = array();
409 - foreach ( $categories as $cat ) {
410 - $cat = trim( $cat );
411 - if ( $cat == "" ) continue;
412 - $cats[] = $cat;
413 - }
414 -
415 - # Filter articles
416 - $articles = array();
417 - $a2r = array();
418 - foreach ( $rows AS $k => $r ) {
419 - $nt = Title::makeTitle( $r->rc_namespace, $r->rc_title );
420 - $id = $nt->getArticleID();
421 - if ( $id == 0 ) continue; # Page might have been deleted...
422 - if ( !in_array($id, $articles) ) {
423 - $articles[] = $id;
 532+ # Filter articles
 533+ $articles = array();
 534+ $a2r = array();
 535+ foreach ( $rows AS $k => $r ) {
 536+ $nt = Title::makeTitle( $r->rc_namespace, $r->rc_title );
 537+ $id = $nt->getArticleID();
 538+ if ( $id == 0 ) continue; # Page might have been deleted...
 539+ if ( !in_array($id, $articles) ) {
 540+ $articles[] = $id;
 541+ }
 542+ if ( !isset($a2r[$id]) ) {
 543+ $a2r[$id] = array();
 544+ }
 545+ $a2r[$id][] = $k;
424546 }
425 - if ( !isset($a2r[$id]) ) {
426 - $a2r[$id] = array();
427 - }
428 - $a2r[$id][] = $k;
429 - }
430547
431 - # Shortcut?
432 - if ( !count($articles) || !count($cats) )
433 - return ;
 548+ # Shortcut?
 549+ if ( !count($articles) || !count($cats) )
 550+ return ;
434551
435 - # Look up
436 - $c = new Categoryfinder ;
437 - $c->seed( $articles, $cats, $opts['categories_any'] ? "OR" : "AND" ) ;
438 - $match = $c->run();
 552+ # Look up
 553+ $c = new Categoryfinder ;
 554+ $c->seed( $articles, $cats, $opts['categories_any'] ? "OR" : "AND" ) ;
 555+ $match = $c->run();
439556
440 - # Filter
441 - $newrows = array();
442 - foreach ( $match AS $id ) {
443 - foreach ( $a2r[$id] AS $rev ) {
444 - $k = $rev;
445 - $newrows[$k] = $rows[$k];
 557+ # Filter
 558+ $newrows = array();
 559+ foreach ( $match AS $id ) {
 560+ foreach ( $a2r[$id] AS $rev ) {
 561+ $k = $rev;
 562+ $newrows[$k] = $rows[$k];
 563+ }
446564 }
 565+ $rows = $newrows;
447566 }
448 - $rows = $newrows;
449 -}
450567
451 -/**
452 - *
453 - */
454 -function rcCountLink( $lim, $d, $page='Recentchanges', $more='', $active = false ) {
455 - global $wgUser, $wgLang, $wgContLang;
456 - $sk = $wgUser->getSkin();
457 - $s = $sk->makeKnownLink( $wgContLang->specialPage( $page ),
458 - ($lim ? $wgLang->formatNum( "{$lim}" ) : wfMsg( 'recentchangesall' ) ), "{$more}" .
459 - ($d ? "days={$d}&" : '') . 'limit='.$lim, '', '',
460 - $active ? 'style="font-weight: bold;"' : '' );
461 - return $s;
462 -}
463 -
464 -/**
465 - *
466 - */
467 -function rcDaysLink( $lim, $d, $page='Recentchanges', $more='', $active = false ) {
468 - global $wgUser, $wgLang, $wgContLang;
469 - $sk = $wgUser->getSkin();
470 - $s = $sk->makeKnownLink( $wgContLang->specialPage( $page ),
471 - ($d ? $wgLang->formatNum( "{$d}" ) : wfMsg( 'recentchangesall' ) ), $more.'days='.$d .
472 - ($lim ? '&limit='.$lim : ''), '', '',
473 - $active ? 'style="font-weight: bold;"' : '' );
474 - return $s;
475 -}
476 -
477 -/**
478 - * Used by Recentchangeslinked
479 - */
480 -function rcDayLimitLinks( $days, $limit, $page='Recentchanges', $more='', $doall = false, $minorLink = '',
481 - $botLink = '', $liuLink = '', $patrLink = '', $myselfLink = '' ) {
482 - global $wgRCLinkLimits, $wgRCLinkDays;
483 - if ($more != '') $more .= '&';
484 -
485 - # Sort data for display and make sure it's unique after we've added user data.
486 - # FIXME: why does this piss around with globals like this? Why is $limit added on globally?
487 - $wgRCLinkLimits[] = $limit;
488 - $wgRCLinkDays[] = $days;
489 - sort($wgRCLinkLimits);
490 - sort($wgRCLinkDays);
491 - $wgRCLinkLimits = array_unique($wgRCLinkLimits);
492 - $wgRCLinkDays = array_unique($wgRCLinkDays);
493 -
494 - $cl = array();
495 - foreach( $wgRCLinkLimits as $countLink ) {
496 - $cl[] = rcCountLink( $countLink, $days, $page, $more, $countLink == $limit );
 568+ /**
 569+ * Makes change an option link which carries all the other options
 570+ * @param $title see Title
 571+ * @param $override
 572+ * @param $options
 573+ */
 574+ function makeOptionsLink( $title, $override, $options, $active = false ) {
 575+ global $wgUser, $wgContLang;
 576+ $sk = $wgUser->getSkin();
 577+ return $sk->makeKnownLinkObj( $this->getTitle(),
 578+ htmlspecialchars( $title ), wfArrayToCGI( $override, $options ), '', '',
 579+ $active ? 'style="font-weight: bold;"' : '' );
497580 }
498 - if( $doall ) $cl[] = rcCountLink( 0, $days, $page, $more );
499 - $cl = implode( ' | ', $cl);
500 -
501 - $dl = array();
502 - foreach( $wgRCLinkDays as $daysLink ) {
503 - $dl[] = rcDaysLink( $limit, $daysLink, $page, $more, $daysLink == $days );
504 - }
505 - if( $doall ) $dl[] = rcDaysLink( $limit, 0, $page, $more );
506 - $dl = implode( ' | ', $dl);
507 -
508 - $linkParts = array( 'minorLink' => 'minor', 'botLink' => 'bots', 'liuLink' => 'liu', 'patrLink' => 'patr', 'myselfLink' => 'mine' );
509 - foreach( $linkParts as $linkVar => $linkMsg ) {
510 - if( $$linkVar != '' )
511 - $links[] = wfMsgHtml( 'rcshowhide' . $linkMsg, $$linkVar );
512 - }
513581
514 - $shm = implode( ' | ', $links );
515 - $note = wfMsg( 'rclinks', $cl, $dl, $shm );
516 - return $note;
517 -}
 582+ /**
 583+ * Creates the options panel.
 584+ * @param $defaults array
 585+ * @param $nondefaults array
 586+ */
 587+ function optionsPanel( $defaults, $nondefaults ) {
 588+ global $wgLang, $wgUser, $wgRCLinkLimits, $wgRCLinkDays;
518589
 590+ $options = $nondefaults + $defaults;
519591
520 -/**
521 - * Makes change an option link which carries all the other options
522 - * @param $title see Title
523 - * @param $override
524 - * @param $options
525 - */
526 -function makeOptionsLink( $title, $override, $options, $active = false ) {
527 - global $wgUser, $wgContLang;
528 - $sk = $wgUser->getSkin();
529 - return $sk->makeKnownLink( $wgContLang->specialPage( 'Recentchanges' ),
530 - htmlspecialchars( $title ), wfArrayToCGI( $override, $options ), '', '',
531 - $active ? 'style="font-weight: bold;"' : '' );
532 -}
 592+ if( $options['from'] )
 593+ $note = wfMsgExt( 'rcnotefrom', array( 'parseinline' ),
 594+ $wgLang->formatNum( $options['limit'] ),
 595+ $wgLang->timeanddate( $options['from'], true ) );
 596+ else
 597+ $note = wfMsgExt( 'rcnote', array( 'parseinline' ),
 598+ $wgLang->formatNum( $options['limit'] ),
 599+ $wgLang->formatNum( $options['days'] ),
 600+ $wgLang->timeAndDate( wfTimestampNow(), true ) );
533601
534 -/**
535 - * Creates the options panel.
536 - * @param $defaults
537 - * @param $nondefaults
538 - */
539 -function rcOptionsPanel( $defaults, $nondefaults ) {
540 - global $wgLang, $wgUser, $wgRCLinkLimits, $wgRCLinkDays;
 602+ # Sort data for display and make sure it's unique after we've added user data.
 603+ $wgRCLinkLimits[] = $options['limit'];
 604+ $wgRCLinkDays[] = $options['days'];
 605+ sort($wgRCLinkLimits);
 606+ sort($wgRCLinkDays);
 607+ $wgRCLinkLimits = array_unique($wgRCLinkLimits);
 608+ $wgRCLinkDays = array_unique($wgRCLinkDays);
541609
542 - $options = $nondefaults + $defaults;
 610+ // limit links
 611+ foreach( $wgRCLinkLimits as $value ) {
 612+ $cl[] = $this->makeOptionsLink( $wgLang->formatNum( $value ),
 613+ array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ) ;
 614+ }
 615+ $cl = implode( ' | ', $cl);
543616
544 - if( $options['from'] )
545 - $note = wfMsgExt( 'rcnotefrom', array( 'parseinline' ),
546 - $wgLang->formatNum( $options['limit'] ),
547 - $wgLang->timeanddate( $options['from'], true ) );
548 - else
549 - $note = wfMsgExt( 'rcnote', array( 'parseinline' ),
550 - $wgLang->formatNum( $options['limit'] ),
551 - $wgLang->formatNum( $options['days'] ),
552 - $wgLang->timeAndDate( wfTimestampNow(), true ) );
 617+ // day links, reset 'from' to none
 618+ foreach( $wgRCLinkDays as $value ) {
 619+ $dl[] = $this->makeOptionsLink( $wgLang->formatNum( $value ),
 620+ array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ) ;
 621+ }
 622+ $dl = implode( ' | ', $dl);
553623
554 - # Sort data for display and make sure it's unique after we've added user data.
555 - $wgRCLinkLimits[] = $options['limit'];
556 - $wgRCLinkDays[] = $options['days'];
557 - sort($wgRCLinkLimits);
558 - sort($wgRCLinkDays);
559 - $wgRCLinkLimits = array_unique($wgRCLinkLimits);
560 - $wgRCLinkDays = array_unique($wgRCLinkDays);
561 -
562 - // limit links
563 - foreach( $wgRCLinkLimits as $value ) {
564 - $cl[] = makeOptionsLink( $wgLang->formatNum( $value ),
565 - array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ) ;
566 - }
567 - $cl = implode( ' | ', $cl);
568624
569 - // day links, reset 'from' to none
570 - foreach( $wgRCLinkDays as $value ) {
571 - $dl[] = makeOptionsLink( $wgLang->formatNum( $value ),
572 - array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ) ;
573 - }
574 - $dl = implode( ' | ', $dl);
 625+ // show/hide links
 626+ $showhide = array( wfMsg( 'show' ), wfMsg( 'hide' ));
 627+ $minorLink = $this->makeOptionsLink( $showhide[1-$options['hideminor']],
 628+ array( 'hideminor' => 1-$options['hideminor'] ), $nondefaults);
 629+ $botLink = $this->makeOptionsLink( $showhide[1-$options['hidebots']],
 630+ array( 'hidebots' => 1-$options['hidebots'] ), $nondefaults);
 631+ $anonsLink = $this->makeOptionsLink( $showhide[ 1 - $options['hideanons'] ],
 632+ array( 'hideanons' => 1 - $options['hideanons'] ), $nondefaults );
 633+ $liuLink = $this->makeOptionsLink( $showhide[1-$options['hideliu']],
 634+ array( 'hideliu' => 1-$options['hideliu'] ), $nondefaults);
 635+ $patrLink = $this->makeOptionsLink( $showhide[1-$options['hidepatrolled']],
 636+ array( 'hidepatrolled' => 1-$options['hidepatrolled'] ), $nondefaults);
 637+ $myselfLink = $this->makeOptionsLink( $showhide[1-$options['hidemyself']],
 638+ array( 'hidemyself' => 1-$options['hidemyself'] ), $nondefaults);
575639
 640+ $links[] = wfMsgHtml( 'rcshowhideminor', $minorLink );
 641+ $links[] = wfMsgHtml( 'rcshowhidebots', $botLink );
 642+ $links[] = wfMsgHtml( 'rcshowhideanons', $anonsLink );
 643+ $links[] = wfMsgHtml( 'rcshowhideliu', $liuLink );
 644+ if( $wgUser->useRCPatrol() )
 645+ $links[] = wfMsgHtml( 'rcshowhidepatr', $patrLink );
 646+ $links[] = wfMsgHtml( 'rcshowhidemine', $myselfLink );
 647+ $hl = implode( ' | ', $links );
576648
577 - // show/hide links
578 - $showhide = array( wfMsg( 'show' ), wfMsg( 'hide' ));
579 - $minorLink = makeOptionsLink( $showhide[1-$options['hideminor']],
580 - array( 'hideminor' => 1-$options['hideminor'] ), $nondefaults);
581 - $botLink = makeOptionsLink( $showhide[1-$options['hidebots']],
582 - array( 'hidebots' => 1-$options['hidebots'] ), $nondefaults);
583 - $anonsLink = makeOptionsLink( $showhide[ 1 - $options['hideanons'] ],
584 - array( 'hideanons' => 1 - $options['hideanons'] ), $nondefaults );
585 - $liuLink = makeOptionsLink( $showhide[1-$options['hideliu']],
586 - array( 'hideliu' => 1-$options['hideliu'] ), $nondefaults);
587 - $patrLink = makeOptionsLink( $showhide[1-$options['hidepatrolled']],
588 - array( 'hidepatrolled' => 1-$options['hidepatrolled'] ), $nondefaults);
589 - $myselfLink = makeOptionsLink( $showhide[1-$options['hidemyself']],
590 - array( 'hidemyself' => 1-$options['hidemyself'] ), $nondefaults);
 649+ // show from this onward link
 650+ $now = $wgLang->timeanddate( wfTimestampNow(), true );
 651+ $tl = $this->makeOptionsLink( $now, array( 'from' => wfTimestampNow()), $nondefaults );
591652
592 - $links[] = wfMsgHtml( 'rcshowhideminor', $minorLink );
593 - $links[] = wfMsgHtml( 'rcshowhidebots', $botLink );
594 - $links[] = wfMsgHtml( 'rcshowhideanons', $anonsLink );
595 - $links[] = wfMsgHtml( 'rcshowhideliu', $liuLink );
596 - if( $wgUser->useRCPatrol() )
597 - $links[] = wfMsgHtml( 'rcshowhidepatr', $patrLink );
598 - $links[] = wfMsgHtml( 'rcshowhidemine', $myselfLink );
599 - $hl = implode( ' | ', $links );
600 -
601 - // show from this onward link
602 - $now = $wgLang->timeanddate( wfTimestampNow(), true );
603 - $tl = makeOptionsLink( $now, array( 'from' => wfTimestampNow()), $nondefaults );
604 -
605 - $rclinks = wfMsgExt( 'rclinks', array( 'parseinline', 'replaceafter'),
606 - $cl, $dl, $hl );
607 - $rclistfrom = wfMsgExt( 'rclistfrom', array( 'parseinline', 'replaceafter'), $tl );
608 - return "$note<br />$rclinks<br />$rclistfrom";
609 -
610 -}
\ No newline at end of file
 653+ $rclinks = wfMsgExt( 'rclinks', array( 'parseinline', 'replaceafter'),
 654+ $cl, $dl, $hl );
 655+ $rclistfrom = wfMsgExt( 'rclistfrom', array( 'parseinline', 'replaceafter'), $tl );
 656+ return "$note<br />$rclinks<br />$rclistfrom";
 657+ }
 658+}
Index: trunk/phase3/includes/SpecialPage.php
@@ -128,7 +128,7 @@
129129 'Contributions' => array( 'SpecialPage', 'Contributions' ),
130130 'Emailuser' => array( 'UnlistedSpecialPage', 'Emailuser' ),
131131 'Whatlinkshere' => array( 'SpecialPage', 'Whatlinkshere' ),
132 - 'Recentchangeslinked' => array( 'SpecialPage', 'Recentchangeslinked' ),
 132+ 'Recentchangeslinked' => 'SpecialRecentchangeslinked',
133133 'Movepage' => array( 'UnlistedSpecialPage', 'Movepage' ),
134134 'Blockme' => array( 'UnlistedSpecialPage', 'Blockme' ),
135135 'Resetpass' => array( 'UnlistedSpecialPage', 'Resetpass' ),
Index: trunk/phase3/RELEASE-NOTES
@@ -162,10 +162,14 @@
163163 * (bug 14558) New system message (emailuserfooter) is now added to the footer of
164164 e-mails sent with Special:Emailuser
165165 * Add support for Hijri (Islamic) calendar
166 -* Add a new hook LinkerMakeExternalImage to allow extensions to modify the output of
167 - external (hotlinked) images.
168 -* (bug 14604) Introduced the following features for the LanguageConverter: Multi-tag support, single conversion flag, remove conversion flag on a single page, description flag, variant name, multi-variant fallbacks.
 166+* Add a new hook LinkerMakeExternalImage to allow extensions to modify the output
 167+ of external (hotlinked) images.
 168+* (bug 14604) Introduced the following features for the LanguageConverter:
 169+ Multi-tag support, single conversion flag, remove conversion flag on a single
 170+ page, description flag, variant name, multi-variant fallbacks.
169171 * Add zh-mo and zh-my variants for the zh language
 172+* (bugs 4832, 9481, 12890) Special:Recentchangeslinked now has all options that
 173+ are in Special:Recentchanges
170174
171175 === Bug fixes in 1.13 ===
172176

Follow-up revisions

RevisionCommit summaryAuthorDate
r36727Tweaks for r36682:...ialex11:43, 27 June 2008
r86778Remove unused messages 'recentchangesall' (removed in r36682) and 'imagelista...robin16:18, 23 April 2011

Status & tagging log