r1535 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r1534‎ | r1535 | r1536 >
Date:10:13, 9 August 2003
Author:vibber
Status:old
Tags:
Comment:
Porting some work from unstable:
* Watchlist raw list & mass remove page
* Watchlist time cutoffs rewritten
* Watchlist uses 1-hour cutoff for very long lists
* User addSalt function can be disabled in config for compat
* CacheManager class split off; doesn't duplicate files if using gzip
* Database connection has option for alternate server, support for future changes
* If database connection fails, emergency abort falls back to cached pages
* Section edit links hidden in printable mode
* Preferences search namespaces has localizable main namespace name
Modified paths:
  • /branches/stable/phase3/includes/Article.php (modified) (history)
  • /branches/stable/phase3/includes/CacheManager.php (modified) (history)
  • /branches/stable/phase3/includes/DatabaseFunctions.php (modified) (history)
  • /branches/stable/phase3/includes/DefaultSettings.php (modified) (history)
  • /branches/stable/phase3/includes/OutputPage.php (modified) (history)
  • /branches/stable/phase3/includes/Skin.php (modified) (history)
  • /branches/stable/phase3/includes/SpecialPreferences.php (modified) (history)
  • /branches/stable/phase3/includes/SpecialWatchlist.php (modified) (history)
  • /branches/stable/phase3/includes/User.php (modified) (history)
  • /branches/stable/phase3/languages/Language.php (modified) (history)

Diff [purge]

Index: branches/stable/phase3/includes/User.php
@@ -282,7 +282,11 @@
283283
284284 function addSalt( $p )
285285 {
286 - return md5( "{$this->mId}-{$p}" );
 286+ global $wgPasswordSalt;
 287+ if($wgPasswordSalt)
 288+ return md5( "{$this->mId}-{$p}" );
 289+ else
 290+ return $p;
287291 }
288292
289293 function encryptPassword( $p )
Index: branches/stable/phase3/includes/Article.php
@@ -2,6 +2,8 @@
33 # Class representing a Wikipedia article and history.
44 # See design.doc for an overview.
55
 6+include_once( "CacheManager.php" );
 7+
68 class Article {
79 /* private */ var $mContent, $mContentLoaded;
810 /* private */ var $mUser, $mTimestamp, $mUserText;
@@ -1623,15 +1625,25 @@
16241626
16251627 /* Caching functions */
16261628
1627 - function tryFileCache() {
 1629+ function tryFileCache() {
 1630+ global $wgTitle;
 1631+
16281632 if($this->isFileCacheable()) {
1629 - if($this->isFileCacheGood()) {
1630 - wfDebug( " tryFileCache() - about to load\n" );
1631 - $this->loadFromFileCache();
 1633+ $cache = new CacheManager( $wgTitle );
 1634+ if($cache->isFileCacheGood( $this->mTouched )) {
 1635+ wfDebug( " tryFileCache() - about to load\n" );
 1636+ $cache->loadFromFileCache();
16321637 exit;
16331638 } else {
1634 - wfDebug( " tryFileCache() - starting buffer\n" );
1635 - ob_start( array(&$this, 'saveToFileCache' ) );
 1639+ wfDebug( " tryFileCache() - starting buffer\n" );
 1640+ if($cache->useGzip() && wfClientAcceptsGzip()) {
 1641+ /* For some reason, adding this header line over in
 1642+ CacheManager::saveToFileCache() fails on my test
 1643+ setup at home, though it works on the live install.
 1644+ Make double-sure... --brion */
 1645+ header( "Content-Encoding: gzip" );
 1646+ }
 1647+ ob_start( array(&$cache, 'saveToFileCache' ) );
16361648 }
16371649 } else {
16381650 wfDebug( " tryFileCache() - not cacheable\n" );
@@ -1652,104 +1664,8 @@
16531665 and (!isset($redirect))
16541666 and (!isset($printable))
16551667 and (!$this->mRedirectedFrom);
1656 -
16571668 }
1658 -
1659 - function fileCacheName() {
1660 - global $wgTitle, $wgFileCacheDirectory, $wgLang;
1661 - if( !$this->mFileCache ) {
1662 - $hash = md5( $key = $wgTitle->getDbkey() );
1663 - if( $wgTitle->getNamespace() )
1664 - $key = $wgLang->getNsText( $wgTitle->getNamespace() ) . ":" . $key;
1665 - $key = str_replace( ".", "%2E", urlencode( $key ) );
1666 - $hash1 = substr( $hash, 0, 1 );
1667 - $hash2 = substr( $hash, 0, 2 );
1668 - $this->mFileCache = "{$wgFileCacheDirectory}/{$hash1}/{$hash2}/{$key}.html";
1669 - wfDebug( " fileCacheName() - {$this->mFileCache}\n" );
1670 - }
1671 - return $this->mFileCache;
1672 - }
16731669
1674 - function isFileCacheGood() {
1675 - global $wgUser, $wgCacheEpoch;
1676 - if(!file_exists( $fn = $this->fileCacheName() ) ) return false;
1677 - $cachetime = wfUnix2Timestamp( filemtime( $fn ) );
1678 - $good = (( $this->mTouched <= $cachetime ) &&
1679 - ($wgCacheEpoch <= $cachetime ));
1680 - wfDebug(" isFileCacheGood() - cachetime $cachetime, touched {$this->mTouched} epoch {$wgCacheEpoch}, good $good\n");
1681 - return $good;
1682 - }
1683 -
1684 - function loadFromFileCache() {
1685 - global $wgUseGzip, $wgOut;
1686 - wfDebug(" loadFromFileCache()\n");
1687 - $filename=$this->fileCacheName();
1688 - $filenamegz = "{$filename}.gz";
1689 - $wgOut->sendCacheControl();
1690 - if( $wgUseGzip
1691 - && wfClientAcceptsGzip()
1692 - && file_exists( $filenamegz)
1693 - && ( filemtime( $filenamegz ) >= filemtime( $filename ) ) ) {
1694 - wfDebug(" sending gzip\n");
1695 - header( "Content-Encoding: gzip" );
1696 - header( "Vary: Accept-Encoding" );
1697 - $filename = $filenamegz;
1698 - }
1699 - readfile( $filename );
1700 - }
1701 -
1702 - function saveToFileCache( $text ) {
1703 - global $wgUseGzip, $wgCompressByDefault;
1704 - if(strcmp($text,"") == 0) return "";
1705 -
1706 - wfDebug(" saveToFileCache()\n", false);
1707 - $filename=$this->fileCacheName();
1708 - $mydir2=substr($filename,0,strrpos($filename,"/")); # subdirectory level 2
1709 - $mydir1=substr($mydir2,0,strrpos($mydir2,"/")); # subdirectory level 1
1710 - if(!file_exists($mydir1)) { mkdir($mydir1,0775); } # create if necessary
1711 - if(!file_exists($mydir2)) { mkdir($mydir2,0775); }
1712 -
1713 - $f = fopen( $filename, "w" );
1714 - if($f) {
1715 - $now = wfTimestampNow();
1716 - fwrite( $f, str_replace( "</html>",
1717 - "<!-- Cached $now -->\n</html>",
1718 - $text ) );
1719 - fclose( $f );
1720 - if( $wgUseGzip and $wgCompressByDefault ) {
1721 - $start = microtime();
1722 - wfDebug(" saving gzip\n");
1723 - $gzout = gzencode( str_replace( "</html>",
1724 - "<!-- Cached/compressed $now -->\n</html>",
1725 - $text ) );
1726 - if( $gzout === false ) {
1727 - wfDebug(" failed to gzip compress, sending plaintext\n");
1728 - return $text;
1729 - }
1730 - if( $f = fopen( "{$filename}.gz", "w" ) ) {
1731 - fwrite( $f, $gzout );
1732 - fclose( $f );
1733 - $end = microtime();
1734 -
1735 - list($usec1, $sec1) = explode(" ",$start);
1736 - list($usec2, $sec2) = explode(" ",$end);
1737 - $interval = ((float)$usec2 + (float)$sec2) -
1738 - ((float)$usec1 + (float)$sec1);
1739 - wfDebug(" saved gzip in $interval\n");
1740 - } else {
1741 - wfDebug(" failed to write gzip, still sending\n" );
1742 - }
1743 - if(wfClientAcceptsGzip()) {
1744 - header( "Content-Encoding: gzip" );
1745 - header( "Vary: Accept-Encoding" );
1746 - wfDebug(" sending NEW gzip now...\n" );
1747 - return $gzout;
1748 - }
1749 - }
1750 - }
1751 - return $text;
1752 - }
1753 -
17541670 }
17551671
17561672 ?>
Index: branches/stable/phase3/includes/SpecialPreferences.php
@@ -181,7 +181,7 @@
182182 $checked = " checked";
183183 }
184184 $name = str_replace( "_", " ", $ns[$i] );
185 - if ( "" == $name ) { $name = "(Main)"; }
 185+ if ( "" == $name ) { $name = wfMsg( "blanknamespace" ); }
186186
187187 if ( 0 != $i ) { $r1 .= " "; }
188188 $r1 .= "<label><input type=checkbox value=\"1\" name=\"" .
Index: branches/stable/phase3/includes/DefaultSettings.php
@@ -86,8 +86,14 @@
8787 # but this will increase CPU usage.
8888 # Requires zlib support enabled in PHP.
8989 $wgUseGzip = false;
90 -$wgCompressByDefault = true;
9190
 91+# For security, the user password hashes include "salt" to
 92+# make it more difficult for someone who somehow gets ahold
 93+# of the hashes to crack them all at once.
 94+#
 95+# For compatibility with old installations, set to false.
 96+$wgPasswordSalt = true;
 97+
9298 # Which namespaces should support subpages?
9399 # See Language.php for a list of namespaces.
94100 #
Index: branches/stable/phase3/includes/Skin.php
@@ -1713,8 +1713,10 @@
17141714
17151715 function editSectionLink($section) {
17161716
 1717+ global $printable;
17171718 global $wgTitle,$wgUser,$oldid;
17181719 if($oldid) return "";
 1720+ if ($printable) return "";
17191721 $editurl="&section={$section}";
17201722 $url=$this->makeKnownLink($wgTitle->getPrefixedText(),wfMsg("editsection"),"action=edit".$editurl);
17211723 return "<div style=\"float:right;margin-left:5px;\"><small>[".$url."]</small></div>";
Index: branches/stable/phase3/includes/DatabaseFunctions.php
@@ -1,10 +1,11 @@
22 <?
33 global $IP;
44 include_once( "$IP/FulltextStoplist.php" );
 5+include_once( "$IP/CacheManager.php" );
56
67 $wgLastDatabaseQuery = "";
78
8 -function wfGetDB( $altuser = "", $altpassword = "" )
 9+function wfGetDB( $altuser = "", $altpassword = "", $altserver = "", $altdb = "" )
910 {
1011 global $wgDBserver, $wgDBuser, $wgDBpassword;
1112 global $wgDBname, $wgDBconnection, $wgEmergencyContact;
@@ -17,30 +18,73 @@
1819 $wgEmergencyContact . "\">Wikipedia developers</a>.</p>";
1920
2021 if ( $altuser != "" ) {
21 - $wgDBconnection = mysql_connect( $wgDBserver, $altuser, $altpassword )
 22+ $serve = ($altserver ? $altserver : $wgDBserver );
 23+ $db = ($altdb ? $altdb : $wgDBname );
 24+ $wgDBconnection = mysql_connect( $serve, $altuser, $altpassword )
2225 or die( "bad sql user" );
2326 mysql_select_db( $wgDBname, $wgDBconnection ) or die(
2427 htmlspecialchars(mysql_error()) );
2528 }
2629
2730 if ( ! $wgDBconnection ) {
28 - $wgDBconnection = mysql_pconnect( $wgDBserver, $wgDBuser,
29 - $wgDBpassword ) or die( $noconn .
30 - "\n<p><b>" . htmlspecialchars(mysql_error()) . "</b></p>\n" . $helpme );
 31+ @$wgDBconnection = mysql_pconnect( $wgDBserver, $wgDBuser, $wgDBpassword )
 32+ or wfEmergencyAbort();
 33+
3134 if( !mysql_select_db( $wgDBname, $wgDBconnection ) ) {
 35+ /* Persistent connections may become stuck in an unusable state */
3236 wfDebug( "Persistent connection is broken?\n", true );
3337
34 - $wgDBconnection = mysql_connect( $wgDBserver, $wgDBuser,
35 - $wgDBpassword ) or die( $noconn .
36 - "\n<p><b>" . htmlspecialchars(mysql_error()) . "</b> (tried non-p connect)</p>\n" . $helpme );
37 - mysql_select_db( $wgDBname, $wgDBconnection ) or die( $nodb .
38 - "\n<p><b>" . htmlspecialchars(mysql_error()) . "</b> (tried non-p connect)</p>\n" . $helpme );
39 - }
 38+ @$wgDBconnection = mysql_connect( $wgDBserver, $wgDBuser, $wgDBpassword )
 39+ or wfEmergencyAbort();
 40+
 41+ @mysql_select_db( $db, $wgDBconnection )
 42+ or wfEmergencyAbort();
 43+ }
4044 }
4145 # mysql_ping( $wgDBconnection );
4246 return $wgDBconnection;
4347 }
4448
 49+/* Call this function if we couldn't contact the database...
 50+ We'll try to use the cache to display something in the meantime */
 51+function wfEmergencyAbort( $msg = "" ) {
 52+ global $wgTitle, $wgUseFileCache, $title, $wgOutputEncoding;
 53+
 54+ header( "Content-type: text/html; charset=$wgOutputEncoding" );
 55+ if($msg == "") $msg = wfMsg( "noconnect" );
 56+ $text = $msg;
 57+
 58+ if($wgUseFileCache) {
 59+ if($wgTitle) {
 60+ $t =& $wgTitle;
 61+ } else {
 62+ if($title) {
 63+ $t = Title::newFromURL( $title );
 64+ } else {
 65+ $t = Title::newFromText( wfMsg("mainpage") );
 66+ }
 67+ }
 68+
 69+ $cache = new CacheManager( $t );
 70+ if( $cache->isFileCached() ) {
 71+ $msg = "<p style='color: red'><b>$msg<br>\n" .
 72+ wfMsg( "cachederror" ) . "</b></p>\n";
 73+
 74+ $tag = "<div id='article'>";
 75+ $text = str_replace(
 76+ $tag,
 77+ $tag . $msg,
 78+ $cache->fetchPageText() );
 79+ }
 80+ }
 81+
 82+ /* Don't cache error pages! They cause no end of trouble... */
 83+ header( "Cache-control: none" );
 84+ header( "Pragma: nocache" );
 85+ echo $text;
 86+ exit;
 87+}
 88+
4589 function wfQuery( $sql, $fname = "" )
4690 {
4791 global $wgLastDatabaseQuery, $wgOut;
Index: branches/stable/phase3/includes/OutputPage.php
@@ -302,11 +302,13 @@
303303 if( $this->mLastModified != "" ) {
304304 wfDebug( "** private caching; {$this->mLastModified} **\n", false );
305305 header( "Cache-Control: private, must-revalidate, max-age=0" );
 306+ header( "Vary: Accept-Encoding" );
306307 header( "Last-modified: {$this->mLastModified}" );
307308 } else {
308309 wfDebug( "** no caching **\n", false );
309310 header( "Cache-Control: no-cache" ); # Experimental - see below
310311 header( "Pragma: no-cache" );
 312+ header( "Vary: Accept-Encoding" );
311313 header( "Last-modified: " . gmdate( "D, j M Y H:i:s" ) . " GMT" );
312314 }
313315 header( "Expires: Mon, 15 Jan 2001 00:00:00 GMT" ); # Cachers always validate the page!
Index: branches/stable/phase3/includes/SpecialWatchlist.php
@@ -11,52 +11,148 @@
1212 $wgOut->setPagetitle( wfMsg( "watchlist" ) );
1313 $sub = str_replace( "$1", $wgUser->getName(), wfMsg( "watchlistsub" ) );
1414 $wgOut->setSubtitle( $sub );
15 - $wgOut->setRobotpolicy( "index,follow" );
 15+ $wgOut->setRobotpolicy( "noindex,nofollow" );
1616
 17+ $uid = $wgUser->getID();
 18+ if( $uid == 0 ) {
 19+ $wgOut->addHTML( wfMsg( "nowatchlist" ) );
 20+ return;
 21+ }
 22+
 23+ global $action,$remove,$id;
 24+ if(($action == "submit") && isset($remove) && is_array($id)) {
 25+ $wgOut->addHTML( wfMsg( "removingchecked" ) );
 26+ foreach($id as $one) {
 27+ $t = Title::newFromURL( $one );
 28+ if($t->getDBkey() != "") {
 29+ $sql = "DELETE FROM watchlist WHERE wl_user=$uid AND " .
 30+ "wl_namespace=" . $t->getNamespace() . " AND " .
 31+ "wl_title='" . wfStrencode( $t->getDBkey() ) . "'";
 32+ $res = wfQuery( $sql );
 33+ if($res === FALSE) {
 34+ $wgOut->addHTML( "<br />\n" . wfMsg( "couldntremove", htmlspecialchars($one) ) );
 35+ } else {
 36+ $wgOut->addHTML( " (" . htmlspecialchars($one) . ")" );
 37+ }
 38+ } else {
 39+ $wgOut->addHTML( "<br />\n" . wfMsg( "iteminvalidname", htmlspecialchars($one) ) );
 40+ }
 41+ }
 42+ $wgOut->addHTML( "done.\n<p>" );
 43+ }
 44+
 45+ $sql = "SELECT COUNT(*) AS n FROM watchlist WHERE wl_user=$uid";
 46+ $res = wfQuery( $sql );
 47+ $s = wfFetchObject( $res );
 48+ $nitems = $s->n;
 49+
 50+ if($nitems == 0) {
 51+ $wgOut->addHTML( wfMsg( "nowatchlist" ) );
 52+ return;
 53+ }
 54+
1755 if ( ! isset( $days ) ) {
18 - $days = $wgUser->getOption( "rcdays" );
19 - if ( ! $days ) { $days = 3; }
 56+ $big = 250;
 57+ if($nitems > $big) {
 58+ # Set default cutoff shorter
 59+ $days = (1.0 / 24.0); # 1 hour...
 60+ } else {
 61+ $days = 0; # no time cutoff for shortlisters
 62+ }
 63+ } else {
 64+ $days = floatval($days);
2065 }
21 - $days = (int)$days;
22 - list( $limit, $offset ) = wfCheckLimits( 100, "rclimit" );
2366
2467 if ( $days <= 0 ) {
2568 $docutoff = '';
 69+ $cutoff = false;
 70+ $npages = wfMsg( "all" );
2671 } else {
27 - $docutoff = "cur_timestamp > '" .
28 - wfUnix2Timestamp( time() - ( $days * 86400 ) )
29 - . "' AND";
 72+ $docutoff = "AND cur_timestamp > '" .
 73+ ( $cutoff = wfUnix2Timestamp( time() - intval( $days * 86400 ) ) )
 74+ . "'";
 75+ $sql = "SELECT COUNT(*) AS n FROM cur WHERE cur_timestamp>'$cutoff'";
 76+ $res = wfQuery( $sql );
 77+ $s = wfFetchObject( $res );
 78+ $npages = $s->n;
3079 }
31 - if ( $limit == 0 ) {
32 - $dolimit = "";
33 - } else {
34 - $dolimit = "LIMIT $limit";
35 - }
3680
37 - $uid = $wgUser->getID();
38 - if( $uid == 0 ) {
39 - $wgOut->addHTML( wfMsg( "nowatchlist" ) );
 81+ if(isset($_REQUEST['magic'])) {
 82+ $wgOut->addHTML( wfMsg( "watchlistcontains", $nitems ) .
 83+ "<p>" . wfMsg( "watcheditlist" ) . "</p>\n" );
 84+
 85+ $wgOut->addHTML( "<form action='" .
 86+ wfLocalUrl( $wgLang->specialPage( "Watchlist" ), "action=submit" ) .
 87+ "' method='post'>\n" .
 88+ "<ul>\n" );
 89+ $sql = "SELECT wl_namespace,wl_title FROM watchlist WHERE wl_user=$uid";
 90+ $res = wfQuery( $sql );
 91+ global $wgUser, $wgLang;
 92+ $sk = $wgUser->getSkin();
 93+ while( $s = wfFetchObject( $res ) ) {
 94+ $t = Title::makeTitle( $s->wl_namespace, $s->wl_title );
 95+ $t = $t->getPrefixedText();
 96+ $wgOut->addHTML( "<li><input type='checkbox' name='id[]' value=\"" . htmlspecialchars($t) . "\">" .
 97+ $sk->makeKnownLink( $t, $t ) .
 98+ "</li>\n" );
 99+ }
 100+ $wgOut->addHTML( "</ul>\n" .
 101+ "<input type='submit' name='remove' value='" .
 102+ wfMsg( "removechecked" ) . "'>\n" .
 103+ "</form>\n" );
 104+
40105 return;
41106 }
 107+
 108+ # If the watchlist is relatively short, it's simplest to zip
 109+ # down its entirety and then sort the results.
 110+
 111+ # If it's relatively long, it may be worth our while to zip
 112+ # through the time-sorted page list checking for watched items.
 113+
 114+ # Up estimate of watched items by 15% to compensate for talk pages...
 115+ if( $cutoff && ( $nitems*1.15 > $npages ) ) {
 116+ $x = "cur_timestamp";
 117+ $y = wfMsg( "watchmethod-recent" );
 118+ $z = "wl_namespace=cur_namespace&65534";
 119+ } else {
 120+ $x = "name_title_timestamp";
 121+ $y = wfMsg( "watchmethod-list" );
 122+ $z = "(wl_namespace=cur_namespace OR wl_namespace+1=cur_namespace)";
 123+ }
42124
43 - $sql = "SELECT DISTINCT
44 - cur_id,cur_namespace,cur_title,cur_comment,
 125+ $wgOut->addHTML( "<i>" . wfMsg( "watchdetails", $nitems, $npages, $y,
 126+ wfLocalUrl( $wgLang->specialPage("Watchlist"),"magic=yes" ) ) . "</i><br>\n" );
 127+
 128+
 129+ $sql = "SELECT
 130+ cur_namespace,cur_title,cur_comment,
45131 cur_user,cur_user_text,cur_timestamp,cur_minor_edit,cur_is_new
46 - FROM cur,watchlist
47 - WHERE wl_user={$uid} AND wl_title=cur_title
48 - AND (cur_namespace=wl_namespace OR cur_namespace=wl_namespace+1)
49 - ORDER BY inverse_timestamp {$dolimit}";
 132+ FROM watchlist,cur USE INDEX ($x)
 133+ WHERE wl_user=$uid
 134+ AND $z
 135+ AND wl_title=cur_title
 136+ $docutoff
 137+ ORDER BY cur_timestamp DESC";
 138+
 139+
50140 $res = wfQuery( $sql, $fname );
 141+
 142+ if($days >= 1)
 143+ $note = wfMsg( "rcnote", $limit, $days );
 144+ elseif($days > 0)
 145+ $note = wfMsg( "wlnote", $limit, round($days*24) );
 146+ else
 147+ $note = "";
 148+ $wgOut->addHTML( "\n<hr>\n{$note}\n<br>" );
 149+ $note = wlCutoffLinks( $days, $limit );
 150+ $wgOut->addHTML( "{$note}\n" );
 151+
51152 if ( wfNumRows( $res ) == 0 ) {
52 - $wgOut->addHTML( wfMsg( "nowatchlist" ) );
 153+ $wgOut->addHTML( "<p><i>" . wfMsg( "watchnochange" ) . "</i></p>" );
53154 return;
54155 }
55156
56 - $note = wfMsg( "rcnote", $limit, $days );
57 - $wgOut->addHTML( "\n<hr>\n{$note}\n<br>" );
58 - $note = rcDayLimitlinks( $days, $limit, "Watchlist", "", true );
59 - $wgOut->addHTML( "{$note}\n" );
60 -
61157 $sk = $wgUser->getSkin();
62158 $s = $sk->beginRecentChangesList();
63159
@@ -79,4 +175,45 @@
80176 $wgOut->addHTML( $s );
81177 }
82178
 179+
 180+function wlHoursLink( $h, $page ) {
 181+ global $wgUser, $wgLang;
 182+ $sk = $wgUser->getSkin();
 183+ $s = $sk->makeKnownLink(
 184+ $wgLang->specialPage( $page ),
 185+ $h, "days=" . ($h / 24.0) );
 186+ return $s;
 187+}
 188+
 189+
 190+function wlDaysLink( $d, $page ) {
 191+ global $wgUser, $wgLang;
 192+ $sk = $wgUser->getSkin();
 193+ $s = $sk->makeKnownLink(
 194+ $wgLang->specialPage( $page ),
 195+ ($d ? $d : wfMsg( "all" ) ), "days=$d" );
 196+ return $s;
 197+}
 198+
 199+function wlCutoffLinks( $days, $limit, $page = "Watchlist" )
 200+{
 201+ $hours = array( 1, 2, 6, 12 );
 202+ $days = array( 1, 3, 7 );
 203+ $cl = "";
 204+ $i = 0;
 205+ foreach( $hours as $h ) {
 206+ $hours[$i++] = wlHoursLink( $h, $page );
 207+ }
 208+ $i = 0;
 209+ foreach( $days as $d ) {
 210+ $days[$i++] = wlDaysLink( $d, $page );
 211+ }
 212+ return
 213+ "Show last " .
 214+ implode(" | ", $hours) . " hours " .
 215+ implode(" | ", $days) . " days " .
 216+ wlDaysLink( 0, $page );
 217+# $note = wfMsg( "rclinks", $cl, $dl, $mlink );
 218+}
 219+
83220 ?>
Index: branches/stable/phase3/languages/Language.php
@@ -394,8 +394,9 @@
395395 \"$1\"
396396 from within function \"$2\".
397397 MySQL returned error \"$3: $4\".\n",
398 -"noconnect" => "Could not connect to DB on $1",
 398+"noconnect" => "Sorry! The wiki is experiencing some technical difficulties, and cannot contact the database server.",
399399 "nodb" => "Could not select database $1",
 400+"cachederror" => "The following is a cached copy of the requested page, and may not be up to date.",
400401 "readonly" => "Database locked",
401402 "enterlockreason" => "Enter a reason for the lock, including an estimate
402403 of when the lock will be released",
@@ -908,7 +909,25 @@
909910 "watchthispage" => "Watch this page",
910911 "unwatchthispage" => "Stop watching",
911912 "notanarticle" => "Not an article",
 913+"watchnochange" => "None of your watched items were edited in the time period displayed.",
 914+"watchdetails" => "($1 pages watched not counting talk pages;
 915+$2 total pages edited since cutoff;
 916+$3...
 917+<a href='$4'>show and edit complete list</a>.)",
 918+"watchmethod-recent" => "checking recent edits for watched pages",
 919+"watchmethod-list" => "checking watched pages for recent edits",
 920+"removechecked" => "Remove checked items from watchlist",
 921+"watchlistcontains" => "Your watchlist contains $1 pages.",
 922+"watcheditlist" => "Here's an alphabetical list of your
 923+watched pages. Check the boxes of pages you want to remove
 924+from your watchlist and click the 'remove checked' button
 925+at the bottom of the screen.",
 926+"removingchecked" => "Removing requested items from watchlist...",
 927+"couldntremove" => "Couldn't remove item '$1'...",
 928+"iteminvalidname" => "Problem with item '$1', invalid name...",
 929+"wlnote" => "Below are the last $1 changes in the last <b>$2</b> hours.",
912930
 931+
913932 # Delete/protect/revert
914933 #
915934 "deletepage" => "Delete page",

Status & tagging log