Index: trunk/phase3/includes/SpecialNewbieContributions.php |
— | — | @@ -1,107 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -/** |
5 | | - * Special page allowing users to view the contributions of new users. |
6 | | - * |
7 | | - * @package MediaWiki |
8 | | - * @subpackage Special pages |
9 | | - */ |
10 | | -class NewbieContributionsPage extends ContributionsPage { |
11 | | - |
12 | | - /** |
13 | | - * Constructor. No need for username. |
14 | | - */ |
15 | | - function __construct() { } |
16 | | - |
17 | | - /** |
18 | | - * @return string Name of this special page. |
19 | | - */ |
20 | | - function getName() { |
21 | | - return 'NewbieContributions'; |
22 | | - } |
23 | | - |
24 | | - /** |
25 | | - * No target user here. |
26 | | - */ |
27 | | - function getUsername() { |
28 | | - return ''; |
29 | | - } |
30 | | - |
31 | | - /** |
32 | | - * No target user => no subtitle. |
33 | | - */ |
34 | | - function getSubtitleForTarget() { |
35 | | - return ''; |
36 | | - } |
37 | | - |
38 | | - /** |
39 | | - * No target user => no deleted contribs link. |
40 | | - */ |
41 | | - function getDeletedContributionsLink() { |
42 | | - return ''; |
43 | | - } |
44 | | - |
45 | | - /** |
46 | | - * Construct the WHERE clause of the SQL SELECT statement for |
47 | | - * this query. |
48 | | - * @return string |
49 | | - */ |
50 | | - function makeSQLCond( $dbr ) { |
51 | | - $cond = ' page_id = rev_page'; |
52 | | - |
53 | | - $max = $dbr->selectField( 'user', 'max(user_id)', false, 'make_sql' ); |
54 | | - $cond .= ' AND rev_user > ' . (int)($max - $max / 100); |
55 | | - |
56 | | - if ( isset($this->namespace) ) |
57 | | - $cond .= ' AND page_namespace = ' . (int)$this->namespace; |
58 | | - |
59 | | - return $cond; |
60 | | - } |
61 | | - |
62 | | - /** |
63 | | - * Do a batch existence check for any user and user talk pages |
64 | | - * that will be shown in the list. |
65 | | - */ |
66 | | - function preprocessResults( $dbr, $res ) { |
67 | | - $linkBatch = new LinkBatch(); |
68 | | - while( $row = $dbr->fetchObject( $res ) ) { |
69 | | - $linkBatch->add( NS_USER, $row->username ); |
70 | | - $linkBatch->add( NS_USER_TALK, $row->username ); |
71 | | - } |
72 | | - $linkBatch->execute(); |
73 | | - |
74 | | - // Seek to start |
75 | | - if( $dbr->numRows( $res ) > 0 ) |
76 | | - $dbr->dataSeek( $res, 0 ); |
77 | | - } |
78 | | - |
79 | | - /** |
80 | | - * Get user links for output row. |
81 | | - * |
82 | | - * @param $skin Skin to use |
83 | | - * @param $row Result row |
84 | | - * @return string User links |
85 | | - */ |
86 | | - function getRowUserLinks( $skin, $row ) { |
87 | | - $user = ' . . ' . $skin->userLink( $row->userid, $row->username ) |
88 | | - . $skin->userToolLinks( $row->userid, $row->username ); |
89 | | - return $user; |
90 | | - } |
91 | | -} |
92 | | - |
93 | | -/** |
94 | | - * Show the special page. |
95 | | - */ |
96 | | -function wfSpecialNewbieContributions( $par = null ) { |
97 | | - global $wgRequest, $wgUser, $wgOut; |
98 | | - |
99 | | - $page = new NewbieContributionsPage(); |
100 | | - |
101 | | - $page->namespace = $wgRequest->getIntOrNull( 'namespace' ); |
102 | | - $page->botmode = ( $wgUser->isAllowed( 'rollback' ) && $wgRequest->getBool( 'bot' ) ); |
103 | | - |
104 | | - list( $limit, $offset ) = wfCheckLimits(); |
105 | | - return $page->doQuery( $offset, $limit ); |
106 | | -} |
107 | | - |
108 | | -?> |
Index: trunk/phase3/includes/SpecialContributions.php |
— | — | @@ -1,344 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -/** |
5 | | - * Special page allowing users to view their own contributions |
6 | | - * and those of others. |
7 | | - * |
8 | | - * @package MediaWiki |
9 | | - * @subpackage Special pages |
10 | | - */ |
11 | | -class ContributionsPage extends QueryPage { |
12 | | - var $user = null; |
13 | | - var $namespace = null; |
14 | | - var $botmode = false; |
15 | | - |
16 | | - /** |
17 | | - * Constructor. |
18 | | - * @param $username username to list contribs for (or "newbies" for extra magic) |
19 | | - */ |
20 | | - function __construct( $username='' ) { |
21 | | - $this->user = User::newFromName( $username, false ); |
22 | | - } |
23 | | - |
24 | | - function openList( $offset ) { |
25 | | - return "<ul class='special'>"; |
26 | | - } |
27 | | - |
28 | | - function closeList() { |
29 | | - return '</ul>'; |
30 | | - } |
31 | | - |
32 | | - /** |
33 | | - * @return string Name of this special page. |
34 | | - */ |
35 | | - function getName() { |
36 | | - return 'Contributions'; |
37 | | - } |
38 | | - |
39 | | - /** |
40 | | - * Not expensive, won't work with the query cache anyway. |
41 | | - */ |
42 | | - function isExpensive() { return false; } |
43 | | - |
44 | | - /** |
45 | | - * Should we? |
46 | | - */ |
47 | | - function isSyndicated() { return false; } |
48 | | - |
49 | | - /** |
50 | | - * Get target user name. May be overridden in subclasses. |
51 | | - * @return string username |
52 | | - */ |
53 | | - function getUsername() { |
54 | | - return $this->user->getName(); |
55 | | - } |
56 | | - |
57 | | - /** |
58 | | - * @return array Extra URL params for self-links. |
59 | | - */ |
60 | | - function linkParameters() { |
61 | | - $params['target'] = $this->getUsername(); |
62 | | - |
63 | | - if ( isset($this->namespace) ) |
64 | | - $params['namespace'] = $this->namespace; |
65 | | - |
66 | | - if ( $this->botmode ) |
67 | | - $params['bot'] = 1; |
68 | | - |
69 | | - return $params; |
70 | | - } |
71 | | - |
72 | | - /** |
73 | | - * Build the list of links to be shown in the subtitle. |
74 | | - * @return string Link list for "contribsub" UI message. |
75 | | - */ |
76 | | - function getTargetUserLinks() { |
77 | | - global $wgSysopUserBans, $wgLang, $wgUser; |
78 | | - |
79 | | - $skin = $wgUser->getSkin(); |
80 | | - |
81 | | - $username = $this->getUsername(); |
82 | | - $userpage = $this->user->getUserPage(); |
83 | | - $userlink = $skin->makeLinkObj( $userpage, $username ); |
84 | | - |
85 | | - // talk page link |
86 | | - $tools[] = $skin->makeLinkObj( $userpage->getTalkPage(), $wgLang->getNsText( NS_TALK ) ); |
87 | | - |
88 | | - // block or block log link |
89 | | - $id = $this->user->getId(); |
90 | | - if ( ( $id != 0 && $wgSysopUserBans ) || ( $id == 0 && User::isIP( $username ) ) ) { |
91 | | - if( $wgUser->isAllowed( 'block' ) ) |
92 | | - $tools[] = $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Blockip', $username ), |
93 | | - wfMsgHtml( 'blocklink' ) ); |
94 | | - else |
95 | | - $tools[] = $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), |
96 | | - htmlspecialchars( LogPage::logName( 'block' ) ), |
97 | | - 'type=block&page=' . $userpage->getPrefixedUrl() ); |
98 | | - } |
99 | | - |
100 | | - // other logs link |
101 | | - $tools[] = $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), |
102 | | - wfMsgHtml( 'log' ), |
103 | | - 'user=' . $userpage->getPartialUrl() ); |
104 | | - |
105 | | - return $userlink . ' (' . implode( ' | ', $tools ) . ')'; |
106 | | - } |
107 | | - |
108 | | - /** |
109 | | - * Generate "For User (...)" message in subtitle. Calls |
110 | | - * getTargetUserLinks() for most of the work. |
111 | | - * @return string |
112 | | - */ |
113 | | - function getSubtitleForTarget() { |
114 | | - return wfMsgHtml( 'contribsub', $this->getTargetUserLinks() ); |
115 | | - } |
116 | | - |
117 | | - /** |
118 | | - * If the user has deleted contributions and we are allowed to |
119 | | - * view them, generate a link to Special:DeletedContributions. |
120 | | - * @return string |
121 | | - */ |
122 | | - function getDeletedContributionsLink() { |
123 | | - global $wgUser; |
124 | | - |
125 | | - if( !$wgUser->isAllowed( 'deletedhistory' ) ) |
126 | | - return ''; |
127 | | - |
128 | | - $dbr = wfGetDB( DB_SLAVE ); |
129 | | - $n = $dbr->selectField( 'archive', 'count(*)', array( 'ar_user_text' => $this->getUsername() ), __METHOD__ ); |
130 | | - |
131 | | - if ( $n == 0 ) |
132 | | - return ''; |
133 | | - |
134 | | - $msg = wfMsg( ( $wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted' ), |
135 | | - $wgUser->getSkin()->makeKnownLinkObj( |
136 | | - SpecialPage::getTitleFor( 'DeletedContributions', $this->getUsername() ), |
137 | | - wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $n ) ) ); |
138 | | - |
139 | | - return "<p>$msg</p>"; |
140 | | - } |
141 | | - |
142 | | - /** |
143 | | - * Construct and output the page subtitle. |
144 | | - */ |
145 | | - function outputSubtitle() { |
146 | | - global $wgOut; |
147 | | - $subtitle = $this->getSubtitleForTarget(); |
148 | | - // $subtitle .= $this->getDeletedContributionsLink(); NOT YET... |
149 | | - $wgOut->setSubtitle( $subtitle ); |
150 | | - } |
151 | | - |
152 | | - /** |
153 | | - * Construct the namespace selector form. |
154 | | - * @return string |
155 | | - */ |
156 | | - function getNamespaceForm() { |
157 | | - $title = $this->getTitle(); |
158 | | - |
159 | | - $ns = $this->namespace; |
160 | | - if ( !isset($ns) ) |
161 | | - $ns = ''; |
162 | | - |
163 | | - $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $title->getLocalUrl() ) ); |
164 | | - $form .= wfMsgHtml( 'namespace' ) . ' '; |
165 | | - $form .= Xml::namespaceSelector( $ns, '' ) . ' '; |
166 | | - $form .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ); |
167 | | - $form .= Xml::hidden( 'offset', $this->offset ); |
168 | | - $form .= Xml::hidden( 'limit', $this->limit ); |
169 | | - $form .= Xml::hidden( 'target', $this->getUsername() ); |
170 | | - if ( $this->botmode ) |
171 | | - $form .= Xml::hidden( 'bot', 1 ); |
172 | | - $form .= '</form>'; |
173 | | - |
174 | | - return '<p>' . $form . '</p>'; |
175 | | - } |
176 | | - |
177 | | - /** |
178 | | - * Build the page header. Also calls outputSubtitle(). |
179 | | - * @return string |
180 | | - */ |
181 | | - function getPageHeader() { |
182 | | - $this->outputSubtitle(); |
183 | | - return $this->getNamespaceForm(); |
184 | | - } |
185 | | - |
186 | | - /** |
187 | | - * Construct the WHERE clause of the SQL SELECT statement for |
188 | | - * this query. |
189 | | - * @return string |
190 | | - */ |
191 | | - function makeSQLCond( $dbr ) { |
192 | | - $cond = ' page_id = rev_page'; |
193 | | - $cond .= ' AND rev_user_text = ' . $dbr->addQuotes( $this->getUsername() ); |
194 | | - |
195 | | - if ( isset($this->namespace) ) |
196 | | - $cond .= ' AND page_namespace = ' . (int)$this->namespace; |
197 | | - |
198 | | - return $cond; |
199 | | - } |
200 | | - |
201 | | - /** |
202 | | - * Construct the SQL SELECT statement for this query. |
203 | | - * @return string |
204 | | - */ |
205 | | - function getSQL() { |
206 | | - $dbr = wfGetDB( DB_SLAVE ); |
207 | | - |
208 | | - list( $page, $revision ) = $dbr->tableNamesN( 'page', 'revision' ); |
209 | | - |
210 | | - // XXX: the username and userid fields aren't used for much here, |
211 | | - // but some subclasses rely on them more than we do. |
212 | | - |
213 | | - return "SELECT 'Contributions' as type, |
214 | | - page_namespace AS namespace, |
215 | | - page_title AS title, |
216 | | - rev_timestamp AS value, |
217 | | - rev_user AS userid, |
218 | | - rev_user_text AS username, |
219 | | - rev_minor_edit AS is_minor, |
220 | | - page_latest AS cur_id, |
221 | | - rev_id AS rev_id, |
222 | | - rev_comment AS comment, |
223 | | - rev_deleted AS deleted |
224 | | - FROM $page, $revision |
225 | | - WHERE " . $this->makeSQLCond( $dbr ); |
226 | | - } |
227 | | - |
228 | | - /** |
229 | | - * Get user links for output row, for subclasses that may want |
230 | | - * such functionality. |
231 | | - * |
232 | | - * @param $skin Skin to use |
233 | | - * @param $row Result row |
234 | | - * @return string |
235 | | - */ |
236 | | - function getRowUserLinks( $skin, $row ) { return ''; } |
237 | | - |
238 | | - /** |
239 | | - * Format a row, providing the timestamp, links to the |
240 | | - * page/diff/history and a comment |
241 | | - * |
242 | | - * @param $skin Skin to use |
243 | | - * @param $row Result row |
244 | | - * @return string |
245 | | - */ |
246 | | - function formatResult( $skin, $row ) { |
247 | | - global $wgLang, $wgContLang, $wgUser; |
248 | | - |
249 | | - $dm = $wgContLang->getDirMark(); |
250 | | - |
251 | | - /* |
252 | | - * Cache UI messages in a static array so we don't |
253 | | - * have to regenerate them for each row. |
254 | | - */ |
255 | | - static $messages; |
256 | | - if( !isset( $messages ) ) { |
257 | | - foreach( explode( ' ', 'uctop diff newarticle rollbacklink diff hist minoreditletter' ) as $msg ) |
258 | | - $messages[$msg] = wfMsgExt( $msg, array( 'escape') ); |
259 | | - } |
260 | | - |
261 | | - $page = Title::makeTitle( $row->namespace, $row->title ); |
262 | | - |
263 | | - /* |
264 | | - * HACK: We need a revision object, so we make a very |
265 | | - * heavily stripped-down one. All we really need are |
266 | | - * the comment, the title and the deletion bitmask. |
267 | | - */ |
268 | | - $rev = new Revision( array( |
269 | | - 'comment' => $row->comment, |
270 | | - 'deleted' => $row->deleted, |
271 | | - 'user_text' => $row->username, |
272 | | - 'user' => $row->userid, |
273 | | - ) ); |
274 | | - $rev->setTitle( $page ); |
275 | | - |
276 | | - $ts = wfTimestamp( TS_MW, $row->value ); |
277 | | - $time = $wgLang->timeAndDate( $ts, true ); |
278 | | - $hist = $skin->makeKnownLinkObj( $page, $messages['hist'], 'action=history' ); |
279 | | - |
280 | | - if ( $rev->userCan( Revision::DELETED_TEXT ) ) |
281 | | - $diff = $skin->makeKnownLinkObj( $page, $messages['diff'], 'diff=prev&oldid=' . $row->rev_id ); |
282 | | - else |
283 | | - $diff = $messages['diff']; |
284 | | - |
285 | | - if( $row->is_minor ) |
286 | | - $mflag = '<span class="minor">' . $messages['minoreditletter'] . '</span> '; |
287 | | - else |
288 | | - $mflag = ''; |
289 | | - |
290 | | - $link = $skin->makeKnownLinkObj( $page ); |
291 | | - $comment = $skin->revComment( $rev ); |
292 | | - |
293 | | - $user = $this->getRowUserLinks( $skin, $row ); // for subclasses |
294 | | - |
295 | | - $notes = ''; |
296 | | - |
297 | | - if( $row->rev_id == $row->cur_id ) { |
298 | | - $notes .= ' <strong>' . $messages['uctop'] . '</strong>'; |
299 | | - |
300 | | - if( $wgUser->isAllowed( 'rollback' ) ) |
301 | | - $notes .= ' ' . $skin->generateRollback( $rev ); |
302 | | - } |
303 | | - |
304 | | - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { |
305 | | - $time = '<span class="history-deleted">' . $time . '</span>'; |
306 | | - $notes .= ' ' . wfMsgHtml( 'deletedrev' ); |
307 | | - } |
308 | | - |
309 | | - return "{$time} ({$hist}) ({$diff}) {$mflag} {$dm}{$link}{$user} {$comment}{$notes}"; |
310 | | - } |
311 | | -} |
312 | | - |
313 | | -/** |
314 | | - * Show the special page. |
315 | | - */ |
316 | | -function wfSpecialContributions( $par = null ) { |
317 | | - global $wgRequest, $wgUser, $wgOut; |
318 | | - |
319 | | - $username = ( isset($par) ? $par : $wgRequest->getVal( 'target' ) ); |
320 | | - |
321 | | - // compatibility hack |
322 | | - if ( $username == 'newbies' ) { |
323 | | - $wgOut->redirect( SpecialPage::getTitleFor( 'NewbieContributions' )->getFullURL() ); |
324 | | - return; |
325 | | - } |
326 | | - |
327 | | - $page = new ContributionsPage( $username ); |
328 | | - |
329 | | - if( !$page->user ) { |
330 | | - $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); |
331 | | - return; |
332 | | - } |
333 | | - |
334 | | - // hook for Contributionseditcount extension |
335 | | - if ( $page->user && $page->user->isLoggedIn() ) |
336 | | - wfRunHooks( 'SpecialContributionsBeforeMainOutput', $page->user->getId() ); |
337 | | - |
338 | | - $page->namespace = $wgRequest->getIntOrNull( 'namespace' ); |
339 | | - $page->botmode = ( $wgUser->isAllowed( 'rollback' ) && $wgRequest->getBool( 'bot' ) ); |
340 | | - |
341 | | - list( $limit, $offset ) = wfCheckLimits(); |
342 | | - return $page->doQuery( $offset, $limit ); |
343 | | -} |
344 | | - |
345 | | -?> |
Index: trunk/phase3/includes/SpecialContributions.php |
— | — | @@ -0,0 +1,435 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * @package MediaWiki |
| 5 | + * @subpackage SpecialPage |
| 6 | + */ |
| 7 | + |
| 8 | +/** @package MediaWiki */ |
| 9 | +class ContribsFinder { |
| 10 | + var $username, $offset, $limit, $namespace; |
| 11 | + var $dbr; |
| 12 | + |
| 13 | + function ContribsFinder( $username ) { |
| 14 | + $this->username = $username; |
| 15 | + $this->namespace = false; |
| 16 | + $this->dbr =& wfGetDB( DB_SLAVE ); |
| 17 | + } |
| 18 | + |
| 19 | + function setNamespace( $ns ) { |
| 20 | + $this->namespace = $ns; |
| 21 | + } |
| 22 | + |
| 23 | + function setLimit( $limit ) { |
| 24 | + $this->limit = $limit; |
| 25 | + } |
| 26 | + |
| 27 | + function setOffset( $offset ) { |
| 28 | + $this->offset = $offset; |
| 29 | + } |
| 30 | + |
| 31 | + function getEditLimit( $dir ) { |
| 32 | + list( $index, $usercond ) = $this->getUserCond(); |
| 33 | + $nscond = $this->getNamespaceCond(); |
| 34 | + $use_index = $this->dbr->useIndexClause( $index ); |
| 35 | + list( $revision, $page) = $this->dbr->tableNamesN( 'revision', 'page' ); |
| 36 | + $sql = "SELECT rev_timestamp " . |
| 37 | + " FROM $page,$revision $use_index " . |
| 38 | + " WHERE rev_page=page_id AND $usercond $nscond" . |
| 39 | + " ORDER BY rev_timestamp $dir LIMIT 1"; |
| 40 | + |
| 41 | + $res = $this->dbr->query( $sql, __METHOD__ ); |
| 42 | + $row = $this->dbr->fetchObject( $res ); |
| 43 | + if ( $row ) { |
| 44 | + return $row->rev_timestamp; |
| 45 | + } else { |
| 46 | + return false; |
| 47 | + } |
| 48 | + } |
| 49 | + |
| 50 | + function getEditLimits() { |
| 51 | + return array( |
| 52 | + $this->getEditLimit( "ASC" ), |
| 53 | + $this->getEditLimit( "DESC" ) |
| 54 | + ); |
| 55 | + } |
| 56 | + |
| 57 | + function getUserCond() { |
| 58 | + $condition = ''; |
| 59 | + |
| 60 | + if ( $this->username == 'newbies' ) { |
| 61 | + $max = $this->dbr->selectField( 'user', 'max(user_id)', false, 'make_sql' ); |
| 62 | + $condition = '>' . (int)($max - $max / 100); |
| 63 | + } |
| 64 | + |
| 65 | + if ( $condition == '' ) { |
| 66 | + $condition = ' rev_user_text=' . $this->dbr->addQuotes( $this->username ); |
| 67 | + $index = 'usertext_timestamp'; |
| 68 | + } else { |
| 69 | + $condition = ' rev_user '.$condition ; |
| 70 | + $index = 'user_timestamp'; |
| 71 | + } |
| 72 | + return array( $index, $condition ); |
| 73 | + } |
| 74 | + |
| 75 | + function getNamespaceCond() { |
| 76 | + if ( $this->namespace !== false ) |
| 77 | + return ' AND page_namespace = ' . (int)$this->namespace; |
| 78 | + return ''; |
| 79 | + } |
| 80 | + |
| 81 | + function getPreviousOffsetForPaging() { |
| 82 | + list( $index, $usercond ) = $this->getUserCond(); |
| 83 | + $nscond = $this->getNamespaceCond(); |
| 84 | + |
| 85 | + $use_index = $this->dbr->useIndexClause( $index ); |
| 86 | + list( $page, $revision ) = $this->dbr->tableNamesN( 'page', 'revision' ); |
| 87 | + |
| 88 | + $sql = "SELECT rev_timestamp FROM $page, $revision $use_index " . |
| 89 | + "WHERE page_id = rev_page AND rev_timestamp > '" . $this->offset . "' AND " . |
| 90 | + $usercond . $nscond; |
| 91 | + $sql .= " ORDER BY rev_timestamp ASC"; |
| 92 | + $sql = $this->dbr->limitResult( $sql, $this->limit, 0 ); |
| 93 | + $res = $this->dbr->query( $sql ); |
| 94 | + |
| 95 | + $numRows = $this->dbr->numRows( $res ); |
| 96 | + if ( $numRows ) { |
| 97 | + $this->dbr->dataSeek( $res, $numRows - 1 ); |
| 98 | + $row = $this->dbr->fetchObject( $res ); |
| 99 | + $offset = $row->rev_timestamp; |
| 100 | + } else { |
| 101 | + $offset = false; |
| 102 | + } |
| 103 | + $this->dbr->freeResult( $res ); |
| 104 | + return $offset; |
| 105 | + } |
| 106 | + |
| 107 | + function getFirstOffsetForPaging() { |
| 108 | + list( $index, $usercond ) = $this->getUserCond(); |
| 109 | + $use_index = $this->dbr->useIndexClause( $index ); |
| 110 | + list( $page, $revision ) = $this->dbr->tableNamesN( 'page', 'revision' ); |
| 111 | + $nscond = $this->getNamespaceCond(); |
| 112 | + $sql = "SELECT rev_timestamp FROM $page, $revision $use_index " . |
| 113 | + "WHERE page_id = rev_page AND " . |
| 114 | + $usercond . $nscond; |
| 115 | + $sql .= " ORDER BY rev_timestamp ASC"; |
| 116 | + $sql = $this->dbr->limitResult( $sql, $this->limit, 0 ); |
| 117 | + $res = $this->dbr->query( $sql ); |
| 118 | + |
| 119 | + $numRows = $this->dbr->numRows( $res ); |
| 120 | + if ( $numRows ) { |
| 121 | + $this->dbr->dataSeek( $res, $numRows - 1 ); |
| 122 | + $row = $this->dbr->fetchObject( $res ); |
| 123 | + $offset = $row->rev_timestamp; |
| 124 | + } else { |
| 125 | + $offset = false; |
| 126 | + } |
| 127 | + $this->dbr->freeResult( $res ); |
| 128 | + return $offset; |
| 129 | + } |
| 130 | + |
| 131 | + /* private */ function makeSql() { |
| 132 | + $offsetQuery = ''; |
| 133 | + |
| 134 | + list( $page, $revision ) = $this->dbr->tableNamesN( 'page', 'revision' ); |
| 135 | + list( $index, $userCond ) = $this->getUserCond(); |
| 136 | + |
| 137 | + if ( $this->offset ) |
| 138 | + $offsetQuery = "AND rev_timestamp <= '{$this->offset}'"; |
| 139 | + |
| 140 | + $nscond = $this->getNamespaceCond(); |
| 141 | + $use_index = $this->dbr->useIndexClause( $index ); |
| 142 | + $sql = "SELECT |
| 143 | + page_namespace,page_title,page_is_new,page_latest, |
| 144 | + rev_id,rev_page,rev_text_id,rev_timestamp,rev_comment,rev_minor_edit,rev_user,rev_user_text, |
| 145 | + rev_deleted |
| 146 | + FROM $page,$revision $use_index |
| 147 | + WHERE page_id=rev_page AND $userCond $nscond $offsetQuery |
| 148 | + ORDER BY rev_timestamp DESC"; |
| 149 | + $sql = $this->dbr->limitResult( $sql, $this->limit, 0 ); |
| 150 | + return $sql; |
| 151 | + } |
| 152 | + |
| 153 | + function find() { |
| 154 | + $contribs = array(); |
| 155 | + $res = $this->dbr->query( $this->makeSql(), __METHOD__ ); |
| 156 | + while ( $c = $this->dbr->fetchObject( $res ) ) |
| 157 | + $contribs[] = $c; |
| 158 | + $this->dbr->freeResult( $res ); |
| 159 | + return $contribs; |
| 160 | + } |
| 161 | +}; |
| 162 | + |
| 163 | +/** |
| 164 | + * Special page "user contributions". |
| 165 | + * Shows a list of the contributions of a user. |
| 166 | + * |
| 167 | + * @return none |
| 168 | + * @param $par String: (optional) user name of the user for which to show the contributions |
| 169 | + */ |
| 170 | +function wfSpecialContributions( $par = null ) { |
| 171 | + global $wgUser, $wgOut, $wgLang, $wgRequest; |
| 172 | + |
| 173 | + $target = isset( $par ) ? $par : $wgRequest->getVal( 'target' ); |
| 174 | + if ( !strlen( $target ) ) { |
| 175 | + $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); |
| 176 | + return; |
| 177 | + } |
| 178 | + |
| 179 | + $nt = Title::newFromURL( $target ); |
| 180 | + if ( !$nt ) { |
| 181 | + $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); |
| 182 | + return; |
| 183 | + } |
| 184 | + |
| 185 | + $options = array(); |
| 186 | + |
| 187 | + list( $options['limit'], $options['offset']) = wfCheckLimits(); |
| 188 | + $options['offset'] = $wgRequest->getVal( 'offset' ); |
| 189 | + /* Offset must be an integral. */ |
| 190 | + if ( !strlen( $options['offset'] ) || !preg_match( '/^[0-9]+$/', $options['offset'] ) ) |
| 191 | + $options['offset'] = ''; |
| 192 | + |
| 193 | + $title = SpecialPage::getTitleFor( 'Contributions' ); |
| 194 | + $options['target'] = $target; |
| 195 | + |
| 196 | + $nt =& Title::makeTitle( NS_USER, $nt->getDBkey() ); |
| 197 | + $finder = new ContribsFinder( ( $target == 'newbies' ) ? 'newbies' : $nt->getText() ); |
| 198 | + $finder->setLimit( $options['limit'] ); |
| 199 | + $finder->setOffset( $options['offset'] ); |
| 200 | + |
| 201 | + if ( ( $ns = $wgRequest->getVal( 'namespace', null ) ) !== null && $ns !== '' ) { |
| 202 | + $options['namespace'] = intval( $ns ); |
| 203 | + $finder->setNamespace( $options['namespace'] ); |
| 204 | + } else { |
| 205 | + $options['namespace'] = ''; |
| 206 | + } |
| 207 | + |
| 208 | + if ( $wgUser->isAllowed( 'rollback' ) && $wgRequest->getBool( 'bot' ) ) { |
| 209 | + $options['bot'] = '1'; |
| 210 | + } |
| 211 | + |
| 212 | + if ( $wgRequest->getText( 'go' ) == 'prev' ) { |
| 213 | + $offset = $finder->getPreviousOffsetForPaging(); |
| 214 | + if ( $offset !== false ) { |
| 215 | + $options['offset'] = $offset; |
| 216 | + $prevurl = $title->getLocalURL( wfArrayToCGI( $options ) ); |
| 217 | + $wgOut->redirect( $prevurl ); |
| 218 | + return; |
| 219 | + } |
| 220 | + } |
| 221 | + |
| 222 | + if ( $wgRequest->getText( 'go' ) == 'first' && $target != 'newbies') { |
| 223 | + $offset = $finder->getFirstOffsetForPaging(); |
| 224 | + if ( $offset !== false ) { |
| 225 | + $options['offset'] = $offset; |
| 226 | + $prevurl = $title->getLocalURL( wfArrayToCGI( $options ) ); |
| 227 | + $wgOut->redirect( $prevurl ); |
| 228 | + return; |
| 229 | + } |
| 230 | + } |
| 231 | + |
| 232 | + if ( $target == 'newbies' ) { |
| 233 | + $wgOut->setSubtitle( wfMsgHtml( 'sp-contributions-newbies-sub') ); |
| 234 | + } else { |
| 235 | + $wgOut->setSubtitle( wfMsgHtml( 'contribsub', contributionsSub( $nt ) ) ); |
| 236 | + } |
| 237 | + |
| 238 | + $id = User::idFromName( $nt->getText() ); |
| 239 | + wfRunHooks( 'SpecialContributionsBeforeMainOutput', $id ); |
| 240 | + |
| 241 | + $wgOut->addHTML( contributionsForm( $options) ); |
| 242 | + |
| 243 | + $contribs = $finder->find(); |
| 244 | + |
| 245 | + if ( count( $contribs ) == 0) { |
| 246 | + $wgOut->addWikiText( wfMsg( 'nocontribs' ) ); |
| 247 | + return; |
| 248 | + } |
| 249 | + |
| 250 | + list( $early, $late ) = $finder->getEditLimits(); |
| 251 | + $lastts = count( $contribs ) ? $contribs[count( $contribs ) - 1]->rev_timestamp : 0; |
| 252 | + $atstart = ( !count( $contribs ) || $late == $contribs[0]->rev_timestamp ); |
| 253 | + $atend = ( !count( $contribs ) || $early == $lastts ); |
| 254 | + |
| 255 | + // These four are defaults |
| 256 | + $newestlink = wfMsgHtml( 'sp-contributions-newest' ); |
| 257 | + $oldestlink = wfMsgHtml( 'sp-contributions-oldest' ); |
| 258 | + $newerlink = wfMsgHtml( 'sp-contributions-newer', $options['limit'] ); |
| 259 | + $olderlink = wfMsgHtml( 'sp-contributions-older', $options['limit'] ); |
| 260 | + |
| 261 | + if ( !$atstart ) { |
| 262 | + $stuff = $title->escapeLocalURL( wfArrayToCGI( array( 'offset' => '' ), $options ) ); |
| 263 | + $newestlink = "<a href=\"$stuff\">$newestlink</a>"; |
| 264 | + $stuff = $title->escapeLocalURL( wfArrayToCGI( array( 'go' => 'prev' ), $options ) ); |
| 265 | + $newerlink = "<a href=\"$stuff\">$newerlink</a>"; |
| 266 | + } |
| 267 | + |
| 268 | + if ( !$atend ) { |
| 269 | + $stuff = $title->escapeLocalURL( wfArrayToCGI( array( 'go' => 'first' ), $options ) ); |
| 270 | + $oldestlink = "<a href=\"$stuff\">$oldestlink</a>"; |
| 271 | + $stuff = $title->escapeLocalURL( wfArrayToCGI( array( 'offset' => $lastts ), $options ) ); |
| 272 | + $olderlink = "<a href=\"$stuff\">$olderlink</a>"; |
| 273 | + } |
| 274 | + |
| 275 | + if ( $target == 'newbies' ) { |
| 276 | + $firstlast ="($newestlink)"; |
| 277 | + } else { |
| 278 | + $firstlast = "($newestlink | $oldestlink)"; |
| 279 | + } |
| 280 | + |
| 281 | + $urls = array(); |
| 282 | + foreach ( array( 20, 50, 100, 250, 500 ) as $num ) { |
| 283 | + $stuff = $title->escapeLocalURL( wfArrayToCGI( array( 'limit' => $num ), $options ) ); |
| 284 | + $urls[] = "<a href=\"$stuff\">".$wgLang->formatNum( $num )."</a>"; |
| 285 | + } |
| 286 | + $bits = implode( $urls, ' | ' ); |
| 287 | + |
| 288 | + $prevnextbits = $firstlast .' '. wfMsgHtml( 'viewprevnext', $newerlink, $olderlink, $bits ); |
| 289 | + |
| 290 | + $wgOut->addHTML( "<p>{$prevnextbits}</p>\n" ); |
| 291 | + |
| 292 | + $wgOut->addHTML( "<ul>\n" ); |
| 293 | + |
| 294 | + $sk = $wgUser->getSkin(); |
| 295 | + foreach ( $contribs as $contrib ) |
| 296 | + $wgOut->addHTML( ucListEdit( $sk, $contrib ) ); |
| 297 | + |
| 298 | + $wgOut->addHTML( "</ul>\n" ); |
| 299 | + $wgOut->addHTML( "<p>{$prevnextbits}</p>\n" ); |
| 300 | +} |
| 301 | + |
| 302 | +/** |
| 303 | + * Generates the subheading with links |
| 304 | + * @param $nt @see Title object for the target |
| 305 | + */ |
| 306 | +function contributionsSub( $nt ) { |
| 307 | + global $wgSysopUserBans, $wgLang, $wgUser; |
| 308 | + |
| 309 | + $sk = $wgUser->getSkin(); |
| 310 | + $id = User::idFromName( $nt->getText() ); |
| 311 | + |
| 312 | + if ( 0 == $id ) { |
| 313 | + $ul = $nt->getText(); |
| 314 | + } else { |
| 315 | + $ul = $sk->makeLinkObj( $nt, htmlspecialchars( $nt->getText() ) ); |
| 316 | + } |
| 317 | + $talk = $nt->getTalkPage(); |
| 318 | + if( $talk ) { |
| 319 | + # Talk page link |
| 320 | + $tools[] = $sk->makeLinkObj( $talk, $wgLang->getNsText( NS_TALK ) ); |
| 321 | + if( ( $id != 0 && $wgSysopUserBans ) || ( $id == 0 && User::isIP( $nt->getText() ) ) ) { |
| 322 | + # Block link |
| 323 | + if( $wgUser->isAllowed( 'block' ) ) |
| 324 | + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ), wfMsgHtml( 'blocklink' ) ); |
| 325 | + # Block log link |
| 326 | + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), htmlspecialchars( LogPage::logName( 'block' ) ), 'type=block&page=' . $nt->getPrefixedUrl() ); |
| 327 | + } |
| 328 | + # Other logs link |
| 329 | + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), wfMsgHtml( 'log' ), 'user=' . $nt->getPartialUrl() ); |
| 330 | + $ul .= ' (' . implode( ' | ', $tools ) . ')'; |
| 331 | + } |
| 332 | + return $ul; |
| 333 | +} |
| 334 | + |
| 335 | +/** |
| 336 | + * Generates the namespace selector form with hidden attributes. |
| 337 | + * @param $options Array: the options to be included. |
| 338 | + */ |
| 339 | +function contributionsForm( $options ) { |
| 340 | + global $wgScript, $wgTitle; |
| 341 | + |
| 342 | + $options['title'] = $wgTitle->getPrefixedText(); |
| 343 | + |
| 344 | + $f = "<form method='get' action=\"$wgScript\">\n"; |
| 345 | + foreach ( $options as $name => $value ) { |
| 346 | + if( $name === 'namespace') continue; |
| 347 | + $f .= "\t" . wfElement( 'input', array( |
| 348 | + 'name' => $name, |
| 349 | + 'type' => 'hidden', |
| 350 | + 'value' => $value ) ) . "\n"; |
| 351 | + } |
| 352 | + |
| 353 | + $f .= '<p>' . wfMsgHtml( 'namespace' ) . ' ' . |
| 354 | + HTMLnamespaceselector( $options['namespace'], '' ) . |
| 355 | + wfElement( 'input', array( |
| 356 | + 'type' => 'submit', |
| 357 | + 'value' => wfMsg( 'allpagessubmit' ) ) |
| 358 | + ) . |
| 359 | + "</p></form>\n"; |
| 360 | + |
| 361 | + return $f; |
| 362 | +} |
| 363 | + |
| 364 | +/** |
| 365 | + * Generates each row in the contributions list. |
| 366 | + * |
| 367 | + * Contributions which are marked "top" are currently on top of the history. |
| 368 | + * For these contributions, a [rollback] link is shown for users with sysop |
| 369 | + * privileges. The rollback link restores the most recent version that was not |
| 370 | + * written by the target user. |
| 371 | + * |
| 372 | + * @todo This would probably look a lot nicer in a table. |
| 373 | + */ |
| 374 | +function ucListEdit( $sk, $row ) { |
| 375 | + $fname = 'ucListEdit'; |
| 376 | + wfProfileIn( $fname ); |
| 377 | + |
| 378 | + global $wgLang, $wgUser, $wgRequest; |
| 379 | + static $messages; |
| 380 | + if( !isset( $messages ) ) { |
| 381 | + foreach( explode( ' ', 'uctop diff newarticle rollbacklink diff hist minoreditletter' ) as $msg ) { |
| 382 | + $messages[$msg] = wfMsgExt( $msg, array( 'escape') ); |
| 383 | + } |
| 384 | + } |
| 385 | + |
| 386 | + $rev = new Revision( $row ); |
| 387 | + |
| 388 | + $page = Title::makeTitle( $row->page_namespace, $row->page_title ); |
| 389 | + $link = $sk->makeKnownLinkObj( $page ); |
| 390 | + $difftext = $topmarktext = ''; |
| 391 | + if( $row->rev_id == $row->page_latest ) { |
| 392 | + $topmarktext .= '<strong>' . $messages['uctop'] . '</strong>'; |
| 393 | + if( !$row->page_is_new ) { |
| 394 | + $difftext .= '(' . $sk->makeKnownLinkObj( $page, $messages['diff'], 'diff=0' ) . ')'; |
| 395 | + } else { |
| 396 | + $difftext .= $messages['newarticle']; |
| 397 | + } |
| 398 | + |
| 399 | + if( $wgUser->isAllowed( 'rollback' ) ) { |
| 400 | + $topmarktext .= ' '.$sk->generateRollback( $rev ); |
| 401 | + } |
| 402 | + |
| 403 | + } |
| 404 | + |
| 405 | + if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { |
| 406 | + if ( $rev->userCan( Revision::DELETED_TEXT ) ) { |
| 407 | + $difftext .= '(' . $sk->makeKnownLinkObj( $page, $messages['diff'], 'diff=prev&oldid='.$row->rev_id ) . ')'; |
| 408 | + } else { |
| 409 | + $difftext .= '(' . $messages['diff'] . ')'; |
| 410 | + } |
| 411 | + } |
| 412 | + $histlink='('.$sk->makeKnownLinkObj( $page, $messages['hist'], 'action=history' ) . ')'; |
| 413 | + |
| 414 | + $comment = $sk->revComment( $rev ); |
| 415 | + $d = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true ); |
| 416 | + |
| 417 | + if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { |
| 418 | + $d = '<span class="history-deleted">' . $d . '</span>'; |
| 419 | + } |
| 420 | + |
| 421 | + if( $row->rev_minor_edit ) { |
| 422 | + $mflag = '<span class="minor">' . $messages['minoreditletter'] . '</span> '; |
| 423 | + } else { |
| 424 | + $mflag = ''; |
| 425 | + } |
| 426 | + |
| 427 | + $ret = "{$d} {$histlink} {$difftext} {$mflag} {$link} {$comment} {$topmarktext}"; |
| 428 | + if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { |
| 429 | + $ret .= ' ' . wfMsgHtml( 'deletedrev' ); |
| 430 | + } |
| 431 | + $ret = "<li>$ret</li>\n"; |
| 432 | + wfProfileOut( $fname ); |
| 433 | + return $ret; |
| 434 | +} |
| 435 | + |
| 436 | +?> |
Index: trunk/phase3/includes/QueryPage.php |
— | — | @@ -14,7 +14,6 @@ |
15 | 15 | array( 'AncientPagesPage', 'Ancientpages' ), |
16 | 16 | array( 'BrokenRedirectsPage', 'BrokenRedirects' ), |
17 | 17 | array( 'CategoriesPage', 'Categories' ), |
18 | | - array( 'ContributionsPage', 'Contributions' ), |
19 | 18 | array( 'DeadendPagesPage', 'Deadendpages' ), |
20 | 19 | array( 'DisambiguationsPage', 'Disambiguations' ), |
21 | 20 | array( 'DoubleRedirectsPage', 'DoubleRedirects' ), |
— | — | @@ -28,7 +27,6 @@ |
29 | 28 | array( 'MostlinkedPage', 'Mostlinked' ), |
30 | 29 | array( 'MostrevisionsPage', 'Mostrevisions' ), |
31 | 30 | array( 'NewPagesPage', 'Newpages' ), |
32 | | - array( 'NewbieContributionsPage', 'NewbieContributions' ), |
33 | 31 | array( 'ShortPagesPage', 'Shortpages' ), |
34 | 32 | array( 'UncategorizedCategoriesPage', 'Uncategorizedcategories' ), |
35 | 33 | array( 'UncategorizedPagesPage', 'Uncategorizedpages' ), |
Index: trunk/phase3/includes/SpecialPage.php |
— | — | @@ -114,7 +114,6 @@ |
115 | 115 | 'Ipblocklist' => array( 'SpecialPage', 'Ipblocklist' ), |
116 | 116 | 'Specialpages' => array( 'UnlistedSpecialPage', 'Specialpages' ), |
117 | 117 | 'Contributions' => array( 'UnlistedSpecialPage', 'Contributions' ), |
118 | | - 'NewbieContributions' => array( 'SpecialPage', 'NewbieContributions' ), |
119 | 118 | 'Emailuser' => array( 'UnlistedSpecialPage', 'Emailuser' ), |
120 | 119 | 'Whatlinkshere' => array( 'UnlistedSpecialPage', 'Whatlinkshere' ), |
121 | 120 | 'Recentchangeslinked' => array( 'UnlistedSpecialPage', 'Recentchangeslinked' ), |
Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -242,9 +242,6 @@ |
243 | 243 | * {{REVISIONTIMESTAMP}} now uses site local timezone instead of user timezone |
244 | 244 | to ensure consistent behavior |
245 | 245 | * {{REVISIONTIMESTAMP}} and friends should now work on non-MySQL backends |
246 | | -* Special:Contributions has been rewritten to inherit from QueryPage |
247 | | -* New special page Special:NewbieContributions, with a (deprecated) |
248 | | - redirect from Special:Contributions/newbies for backwards compatibility |
249 | 246 | * (bug 7671) Observe canonical media namespace prefix in Linker::formatComment |
250 | 247 | * Added js variable wgCurRevisionId to the output |
251 | 248 | * (bug 8141) Cleanup of Parser::doTableStuff, patch by AzaTht |
Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -380,7 +380,6 @@ |
381 | 381 | 'Ipblocklist' => array( 'Ipblocklist' ), |
382 | 382 | 'Specialpages' => array( 'Specialpages' ), |
383 | 383 | 'Contributions' => array( 'Contributions' ), |
384 | | - 'NewbieContributions' => array( 'NewbieContributions' ), |
385 | 384 | 'Emailuser' => array( 'Emailuser' ), |
386 | 385 | 'Whatlinkshere' => array( 'Whatlinkshere' ), |
387 | 386 | 'Recentchangeslinked' => array( 'Recentchangeslinked' ), |
— | — | @@ -1755,13 +1754,21 @@ |
1756 | 1755 | 'contributions' => 'User contributions', |
1757 | 1756 | 'mycontris' => 'My contributions', |
1758 | 1757 | 'contribsub' => "For $1", |
| 1758 | +'nocontribs' => 'No changes were found matching these criteria.', |
| 1759 | +'ucnote' => "Below are this user's last <b>$1</b> changes in the last <b>$2</b> days.", |
| 1760 | +'uclinks' => "View the last $1 changes; view the last $2 days.", |
1759 | 1761 | 'uctop' => ' (top)' , |
| 1762 | +'newbies' => 'newbies', |
1760 | 1763 | |
1761 | | -# Newbie contributions |
1762 | | -# |
1763 | | -'newbiecontributions' => 'Newbie contributions', |
| 1764 | +'sp-newimages-showfrom' => 'Show new images starting from $1', |
1764 | 1765 | |
| 1766 | +'sp-contributions-newest' => 'Newest', |
| 1767 | +'sp-contributions-oldest' => 'Oldest', |
| 1768 | +'sp-contributions-newer' => 'Newer $1', |
| 1769 | +'sp-contributions-older' => 'Older $1', |
| 1770 | +'sp-contributions-newbies-sub' => 'For newbies', |
1765 | 1771 | |
| 1772 | + |
1766 | 1773 | # What links here |
1767 | 1774 | # |
1768 | 1775 | 'whatlinkshere' => 'What links here', |
— | — | @@ -2161,7 +2168,6 @@ |
2162 | 2169 | 'newimages-summary' => '', |
2163 | 2170 | 'showhidebots' => '($1 bots)', |
2164 | 2171 | 'noimages' => 'Nothing to see.', |
2165 | | -'sp-newimages-showfrom' => 'Show new images starting from $1', |
2166 | 2172 | |
2167 | 2173 | # short names for language variants used for language conversion links. |
2168 | 2174 | # to disable showing a particular link, set it to 'disable', e.g. |
Index: trunk/phase3/languages/messages/MessagesFi.php |
— | — | @@ -1137,11 +1137,17 @@ |
1138 | 1138 | 'contributions' => 'Käyttäjän muokkaukset', |
1139 | 1139 | 'mycontris' => 'Muokkaukset', |
1140 | 1140 | 'contribsub' => 'Käyttäjän $1 muokkaukset', |
| 1141 | +'nocontribs' => 'Näihin ehtoihin sopivia muokkauksia ei löytynyt.', |
| 1142 | +'ucnote' => 'Alla on \'\'\'$1\'\'\' viimeisintä tämän käyttäjän tekemää muokkausta viimeisten \'\'\'$2\'\'\' päivän aikana.', |
| 1143 | +'uclinks' => 'Katso $1 viimeisintä muokkausta; katso $2 viimeisintä päivää.', |
1141 | 1144 | 'uctop' => ' (uusin)' , |
| 1145 | +'newbies' => 'tulokkaat', |
1142 | 1146 | |
1143 | | -# Newbie contributions |
1144 | | -# |
1145 | | -'newbiecontributions' => 'Uusien tulokkaiden muokkaukset', |
| 1147 | +'sp-contributions-newest' => 'Uusimmat', |
| 1148 | +'sp-contributions-oldest' => 'Vanhimmat', |
| 1149 | +'sp-contributions-newer' => '← $1 uudempaa', |
| 1150 | +'sp-contributions-older' => '$1 vanhempaa →', |
| 1151 | +'sp-contributions-newbies-sub' => 'Uusien tulokkaiden muokkaukset', |
1146 | 1152 | |
1147 | 1153 | # What links here |
1148 | 1154 | # |