Merge "Give the search page a little UI love"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Sat, 9 Nov 2013 00:58:50 +0000 (00:58 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Sat, 9 Nov 2013 00:58:50 +0000 (00:58 +0000)
29 files changed:
RELEASE-NOTES-1.23
docs/hooks.txt
includes/DefaultSettings.php
includes/EditPage.php
includes/FormOptions.php
includes/GlobalFunctions.php
includes/ProxyTools.php
includes/WebRequest.php
includes/api/ApiBase.php
includes/api/ApiProtect.php
includes/api/ApiQuerySiteinfo.php
includes/api/ApiUpload.php
includes/changes/EnhancedChangesList.php
includes/diff/DifferenceEngine.php
includes/filebackend/FileBackendStore.php
includes/filebackend/SwiftFileBackend.php
includes/installer/MysqlUpdater.php
includes/media/Bitmap.php
includes/parser/Parser.php
includes/specials/SpecialChangeEmail.php
includes/specials/SpecialUpload.php
includes/specials/SpecialWatchlist.php
includes/utils/StringUtils.php
languages/Language.php
maintenance/cleanupUploadStash.php
maintenance/runScript.php [new file with mode: 0644]
resources/mediawiki/mediawiki.util.js
tests/phpunit/includes/FormOptionsTest.php
tests/phpunit/includes/WebRequestTest.php

index 04be2a2..7cf5c8e 100644 (file)
@@ -18,6 +18,9 @@ production.
   exception metadata to JSON and logs it to the 'exception-json' log group.
   This makes MediaWiki easier to integrate with log aggregation and analysis
   tools.
+* $wgSquidServersNoPurge now supports the use of Classless Inter-Domain
+  Routing (CIDR) notation to specify contiguous blocks of IPv4 and/or IPv6
+  addresses that should be trusted to provide X-Forwarded-For headers.
 
 === New features in 1.23 ===
 * ResourceLoader can utilize the Web Storage API to cache modules client-side.
index 96a72df..7bd725a 100644 (file)
@@ -1847,7 +1847,7 @@ needs formatting. If nothing handles this hook, the default is to use "$key" to
 get the label, and "$key-value" or "$key-value-text"/"$key-value-html" to
 format the value.
 $key: Key for the limit report item (string)
-$value: Value of the limit report item
+&$value: Value of the limit report item
 &$report: String onto which to append the data
 $isHTML: If true, $report is an HTML table with two columns; if false, it's
        text intended for display in a monospaced font.
@@ -1855,7 +1855,8 @@ $localize: If false, $report should be output in English.
 
 'ParserLimitReportPrepare': Called at the end of Parser:parse() when the parser will
 include comments about size of the text parsed. Hooks should use
-$output->setLimitReportData() to populate data.
+$output->setLimitReportData() to populate data. Functions for this hook should
+not use $wgLang; do that in ParserLimitReportFormat instead.
 $parser: Parser object
 $output: ParserOutput object
 
index 92bb05e..2d1ddcb 100644 (file)
@@ -2285,7 +2285,8 @@ $wgSquidServers = array();
 
 /**
  * As above, except these servers aren't purged on page changes; use to set a
- * list of trusted proxies, etc.
+ * list of trusted proxies, etc. Supports both individual IP addresses and
+ * CIDR blocks.
  */
 $wgSquidServersNoPurge = array();
 
index 530e267..68691c5 100644 (file)
@@ -2937,7 +2937,7 @@ HTML
 
                foreach ( $output->getLimitReportData() as $key => $value ) {
                        if ( wfRunHooks( 'ParserLimitReportFormat',
-                               array( $key, $value, &$limitReport, true, true )
+                               array( $key, &$value, &$limitReport, true, true )
                        ) ) {
                                $keyMsg = wfMessage( $key );
                                $valueMsg = wfMessage( array( "$key-value-html", "$key-value" ) );
index 54822e3..cd6e207 100644 (file)
@@ -4,6 +4,7 @@
  *
  * Copyright © 2008, Niklas Laxström
  * Copyright © 2011, Antoine Musso
+ * Copyright © 2013, Bartosz Dziewoński
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -42,6 +43,9 @@ class FormOptions implements ArrayAccess {
        const STRING = 0;
        /** Integer type, maps guessType() to WebRequest::getInt() */
        const INT = 1;
+       /** Float type, maps guessType() to WebRequest::getFloat()
+         * @since 1.23 */
+       const FLOAT = 4;
        /** Boolean type, maps guessType() to WebRequest::getBool() */
        const BOOL = 2;
        /** Integer type or null, maps to WebRequest::getIntOrNull()
@@ -112,6 +116,8 @@ class FormOptions implements ArrayAccess {
                        return self::BOOL;
                } elseif ( is_int( $data ) ) {
                        return self::INT;
+               } elseif ( is_float( $data ) ) {
+                       return self::FLOAT;
                } elseif ( is_string( $data ) ) {
                        return self::STRING;
                } else {
@@ -234,19 +240,29 @@ class FormOptions implements ArrayAccess {
        }
 
        /**
-        * Validate and set an option integer value
-        * The value will be altered to fit in the range.
+        * @see validateBounds()
+        */
+       public function validateIntBounds( $name, $min, $max ) {
+               $this->validateBounds( $name, $min, $max );
+       }
+
+       /**
+        * Constrain a numeric value for a given option to a given range. The value will be altered to fit
+        * in the range.
         *
-        * @param string $name option name
-        * @param int $min minimum value
-        * @param int $max maximum value
+        * @since 1.23
+        *
+        * @param string $name Option name
+        * @param int|float $min Minimum value
+        * @param int|float $max Maximum value
         * @throws MWException If option is not of type INT
         */
-       public function validateIntBounds( $name, $min, $max ) {
+       public function validateBounds( $name, $min, $max ) {
                $this->validateName( $name, true );
+               $type = $this->options[$name]['type'];
 
-               if ( $this->options[$name]['type'] !== self::INT ) {
-                       throw new MWException( "Option $name is not of type int" );
+               if ( $type !== self::INT && $type !== self::FLOAT ) {
+                       throw new MWException( "Option $name is not of type INT or FLOAT" );
                }
 
                $value = $this->getValueReal( $this->options[$name] );
@@ -333,6 +349,9 @@ class FormOptions implements ArrayAccess {
                                case self::INT:
                                        $value = $r->getInt( $name, $default );
                                        break;
+                               case self::FLOAT:
+                                       $value = $r->getFloat( $name, $default );
+                                       break;
                                case self::STRING:
                                        $value = $r->getText( $name, $default );
                                        break;
index 93a2b02..1eb5c3e 100644 (file)
@@ -2855,6 +2855,15 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(),
        if ( $useLogPipe ) {
                $desc[3] = array( 'pipe', 'w' );
        }
+
+       # TODO/FIXME: This is a bad hack to workaround an HHVM bug that prevents
+       # proc_open() from opening stdin/stdout, so use /dev/null *for now*
+       # See bug 56597 / https://github.com/facebook/hhvm/issues/1247 for more info
+       if ( wfIsHHVM() ) {
+               $desc[0] = array( 'file', '/dev/null', 'r' );
+               $desc[2] = array( 'file', '/dev/null', 'w' );
+       }
+
        $pipes = null;
        $proc = proc_open( $cmd, $desc, $pipes );
        if ( !$proc ) {
index bf1c405..4efd347 100644 (file)
@@ -80,7 +80,19 @@ function wfIsTrustedProxy( $ip ) {
  */
 function wfIsConfiguredProxy( $ip ) {
        global $wgSquidServers, $wgSquidServersNoPurge;
-       $trusted = in_array( $ip, $wgSquidServers ) ||
-               in_array( $ip, $wgSquidServersNoPurge );
+
+       // quick check of known proxy servers
+       $trusted = in_array( $ip, $wgSquidServers );
+
+       if ( !$trusted ) {
+               // slightly slower check to see if the ip is listed directly or in a CIDR
+               // block in $wgSquidServersNoPurge
+               foreach ( $wgSquidServersNoPurge as $block ) {
+                       if ( IP::isInRange( $ip, $block ) ) {
+                               $trusted = true;
+                               break;
+                       }
+               }
+       }
        return $trusted;
 }
index 462b312..4ad7344 100644 (file)
@@ -480,6 +480,20 @@ class WebRequest {
                        : null;
        }
 
+       /**
+        * Fetch a floating point value from the input or return $default if not set.
+        * Guaranteed to return a float; non-numeric input will typically
+        * return 0.
+        *
+        * @since 1.23
+        * @param $name String
+        * @param $default Float
+        * @return Float
+        */
+       public function getFloat( $name, $default = 0 ) {
+               return floatval( $this->getVal( $name, $default ) );
+       }
+
        /**
         * Fetch a boolean value from the input or return $default if not set.
         * Guaranteed to return true or false, with normal PHP semantics for
index ce6ecda..6be044c 100644 (file)
@@ -818,7 +818,7 @@ abstract class ApiBase extends ContextSource {
         * @param string $watchlist Valid values: 'watch', 'unwatch', 'preferences', 'nochange'
         * @param $titleObj Title the page under consideration
         * @param string $userOption The user option to consider when $watchlist=preferences.
-        *      If not set will magically default to either watchdefault or watchcreations
+        *      If not set will use watchdefault always and watchcreations if $titleObj doesn't exist.
         * @return bool
         */
        protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
@@ -837,10 +837,10 @@ abstract class ApiBase extends ContextSource {
                                if ( $userWatching ) {
                                        return true;
                                }
-                               # If no user option was passed, use watchdefault or watchcreations
+                               # If no user option was passed, use watchdefault and watchcreations
                                if ( is_null( $userOption ) ) {
-                                       $userOption = $titleObj->exists()
-                                                       ? 'watchdefault' : 'watchcreations';
+                                       return $this->getUser()->getBoolOption( 'watchdefault' ) ||
+                                               $this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
                                }
                                # Watch the article based on the user preference
                                return $this->getUser()->getBoolOption( $userOption );
index 7830c8b..428eef3 100644 (file)
@@ -98,7 +98,7 @@ class ApiProtect extends ApiBase {
                $cascade = $params['cascade'];
 
                $watch = $params['watch'] ? 'watch' : $params['watchlist'];
-               $this->setWatch( $watch, $titleObj );
+               $this->setWatch( $watch, $titleObj, 'watchdefault' );
 
                $status = $pageObj->doUpdateRestrictions( $protections, $expiryarray, $cascade, $params['reason'], $this->getUser() );
 
index ac9e85a..ebd3fc0 100644 (file)
@@ -361,7 +361,9 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                        if ( $row['iw_local'] == '1' ) {
                                $val['local'] = '';
                        }
-                       // $val['trans'] = intval( $row['iw_trans'] ); // should this be exposed?
+                       if ( $row['iw_trans'] == '1' ) {
+                               $val['trans'] = '';
+                       }
                        if ( isset( $langNames[$prefix] ) ) {
                                $val['language'] = $langNames[$prefix];
                        }
index 467eccf..5839edc 100644 (file)
@@ -587,7 +587,19 @@ class ApiUpload extends ApiBase {
 
                /** @var $file File */
                $file = $this->mUpload->getLocalFile();
-               $watch = $this->getWatchlistValue( $this->mParams['watchlist'], $file->getTitle() );
+
+               // For preferences mode, we want to watch if 'watchdefault' is set or
+               // if the *file* doesn't exist and 'watchcreations' is set. But
+               // getWatchlistValue()'s automatic handling checks if the *title*
+               // exists or not, so we need to check both prefs manually.
+               $watch = $this->getWatchlistValue(
+                       $this->mParams['watchlist'], $file->getTitle(), 'watchdefault'
+               );
+               if ( !$watch && $this->mParams['watchlist'] == 'preferences' && !$file->exists() ) {
+                       $watch = $this->getWatchlistValue(
+                               $this->mParams['watchlist'], $file->getTitle(), 'watchcreations'
+                       );
+               }
 
                // Deprecated parameters
                if ( $this->mParams['watch'] ) {
index 433adb3..3b724f1 100644 (file)
@@ -42,8 +42,10 @@ class EnhancedChangesList extends ChangesList {
                        'jquery.makeCollapsible',
                        'mediawiki.icon',
                ) );
+
                return '';
        }
+
        /**
         * Format a line for enhanced recentchange (aka with javascript and block of lines).
         *
@@ -56,13 +58,18 @@ class EnhancedChangesList extends ChangesList {
                wfProfileIn( __METHOD__ );
 
                # Create a specialised object
-               $rc = RCCacheEntry::newFromParent( $baseRC );
+               $cacheEntry = RCCacheEntry::newFromParent( $baseRC );
 
-               $curIdEq = array( 'curid' => $rc->mAttribs['rc_cur_id'] );
+               $curIdEq = array( 'curid' => $cacheEntry->mAttribs['rc_cur_id'] );
 
                # If it's a new day, add the headline and flush the cache
-               $date = $this->getLanguage()->userDate( $rc->mAttribs['rc_timestamp'], $this->getUser() );
+               $date = $this->getLanguage()->userDate(
+                       $cacheEntry->mAttribs['rc_timestamp'],
+                       $this->getUser()
+               );
+
                $ret = '';
+
                if ( $date != $this->lastdate ) {
                        # Process current cache
                        $ret = $this->recentChangesBlock();
@@ -72,17 +79,19 @@ class EnhancedChangesList extends ChangesList {
                }
 
                # Should patrol-related stuff be shown?
-               $rc->unpatrolled = $this->showAsUnpatrolled( $rc );
+               $cacheEntry->unpatrolled = $this->showAsUnpatrolled( $cacheEntry );
 
                $showdifflinks = true;
+
                # Make article link
-               $type = $rc->mAttribs['rc_type'];
-               $logType = $rc->mAttribs['rc_log_type'];
+               $type = $cacheEntry->mAttribs['rc_type'];
+               $logType = $cacheEntry->mAttribs['rc_log_type'];
+
                // Page moves, very old style, not supported anymore
                if ( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) {
                // New unpatrolled pages
-               } elseif ( $rc->unpatrolled && $type == RC_NEW ) {
-                       $clink = Linker::linkKnown( $rc->getTitle() );
+               } elseif ( $cacheEntry->unpatrolled && $type == RC_NEW ) {
+                       $clink = Linker::linkKnown( $cacheEntry->getTitle() );
                // Log entries
                } elseif ( $type == RC_LOG ) {
                        if ( $logType ) {
@@ -91,33 +100,34 @@ class EnhancedChangesList extends ChangesList {
                                $logname = $logpage->getName()->escaped();
                                $clink = $this->msg( 'parentheses' )->rawParams( Linker::linkKnown( $logtitle, $logname ) )->escaped();
                        } else {
-                               $clink = Linker::link( $rc->getTitle() );
+                               $clink = Linker::link( $cacheEntry->getTitle() );
                        }
                        $watched = false;
                // Log entries (old format) and special pages
-               } elseif ( $rc->mAttribs['rc_namespace'] == NS_SPECIAL ) {
+               } elseif ( $cacheEntry->mAttribs['rc_namespace'] == NS_SPECIAL ) {
                        wfDebug( "Unexpected special page in recentchanges\n" );
                        $clink = '';
                // Edits
                } else {
-                       $clink = Linker::linkKnown( $rc->getTitle() );
+                       $clink = Linker::linkKnown( $cacheEntry->getTitle() );
                }
 
                # Don't show unusable diff links
-               if ( !ChangesList::userCan( $rc, Revision::DELETED_TEXT, $this->getUser() ) ) {
+               if ( !ChangesList::userCan( $cacheEntry, Revision::DELETED_TEXT, $this->getUser() ) ) {
                        $showdifflinks = false;
                }
 
-               $time = $this->getLanguage()->userTime( $rc->mAttribs['rc_timestamp'], $this->getUser() );
-               $rc->watched = $watched;
-               $rc->link = $clink;
-               $rc->timestamp = $time;
-               $rc->numberofWatchingusers = $baseRC->numberofWatchingusers;
+               $time = $this->getLanguage()->userTime( $cacheEntry->mAttribs['rc_timestamp'], $this->getUser() );
+
+               $cacheEntry->watched = $watched;
+               $cacheEntry->link = $clink;
+               $cacheEntry->timestamp = $time;
+               $cacheEntry->numberofWatchingusers = $baseRC->numberofWatchingusers;
 
                # Make "cur" and "diff" links.  Do not use link(), it is too slow if
                # called too many times (50% of CPU time on RecentChanges!).
-               $thisOldid = $rc->mAttribs['rc_this_oldid'];
-               $lastOldid = $rc->mAttribs['rc_last_oldid'];
+               $thisOldid = $cacheEntry->mAttribs['rc_this_oldid'];
+               $lastOldid = $cacheEntry->mAttribs['rc_last_oldid'];
 
                $querycur = $curIdEq + array( 'diff' => '0', 'oldid' => $thisOldid );
                $querydiff = $curIdEq + array( 'diff' => $thisOldid, 'oldid' => $lastOldid );
@@ -129,13 +139,13 @@ class EnhancedChangesList extends ChangesList {
                        if ( $type != RC_NEW ) {
                                $curLink = $this->message['cur'];
                        } else {
-                               $curUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querycur ) );
+                               $curUrl = htmlspecialchars( $cacheEntry->getTitle()->getLinkURL( $querycur ) );
                                $curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>";
                        }
                        $diffLink = $this->message['diff'];
                } else {
-                       $diffUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querydiff ) );
-                       $curUrl = htmlspecialchars( $rc->getTitle()->getLinkURL( $querycur ) );
+                       $diffUrl = htmlspecialchars( $cacheEntry->getTitle()->getLinkURL( $querydiff ) );
+                       $curUrl = htmlspecialchars( $cacheEntry->getTitle()->getLinkURL( $querycur ) );
                        $diffLink = "<a href=\"$diffUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['diff']}</a>";
                        $curLink = "<a href=\"$curUrl\" tabindex=\"{$baseRC->counter}\">{$this->message['cur']}</a>";
                }
@@ -146,29 +156,37 @@ class EnhancedChangesList extends ChangesList {
                } elseif ( in_array( $type, array( RC_LOG, RC_MOVE, RC_MOVE_OVER_REDIRECT ) ) ) {
                        $lastLink = $this->message['last'];
                } else {
-                       $lastLink = Linker::linkKnown( $rc->getTitle(), $this->message['last'],
+                       $lastLink = Linker::linkKnown( $cacheEntry->getTitle(), $this->message['last'],
                                array(), $curIdEq + array( 'diff' => $thisOldid, 'oldid' => $lastOldid ) );
                }
 
                # Make user links
-               if ( $this->isDeleted( $rc, Revision::DELETED_USER ) ) {
-                       $rc->userlink = ' <span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
+               if ( $this->isDeleted( $cacheEntry, Revision::DELETED_USER ) ) {
+                       $cacheEntry->userlink = ' <span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
                } else {
-                       $rc->userlink = Linker::userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
-                       $rc->usertalklink = Linker::userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] );
+                       $cacheEntry->userlink = Linker::userLink(
+                               $cacheEntry->mAttribs['rc_user'],
+                               $cacheEntry->mAttribs['rc_user_text']
+                       );
+
+                       $cacheEntry->usertalklink = Linker::userToolLinks(
+                               $cacheEntry->mAttribs['rc_user'],
+                               $cacheEntry->mAttribs['rc_user_text']
+                       );
                }
 
-               $rc->lastlink = $lastLink;
-               $rc->curlink = $curLink;
-               $rc->difflink = $diffLink;
+               $cacheEntry->lastlink = $lastLink;
+               $cacheEntry->curlink = $curLink;
+               $cacheEntry->difflink = $diffLink;
 
                # Put accumulated information into the cache, for later display
                # Page moves go on their own line
-               $title = $rc->getTitle();
+               $title = $cacheEntry->getTitle();
                $secureName = $title->getPrefixedDBkey();
+
                if ( $type == RC_MOVE || $type == RC_MOVE_OVER_REDIRECT ) {
                        # Use an @ character to prevent collision with page names
-                       $this->rc_cache['@@' . ( $this->rcMoveIndex++ )] = array( $rc );
+                       $this->rc_cache['@@' . ( $this->rcMoveIndex++ )] = array( $cacheEntry );
                } else {
                        # Logs are grouped by type
                        if ( $type == RC_LOG ) {
@@ -178,7 +196,7 @@ class EnhancedChangesList extends ChangesList {
                                $this->rc_cache[$secureName] = array();
                        }
 
-                       array_push( $this->rc_cache[$secureName], $rc );
+                       array_push( $this->rc_cache[$secureName], $cacheEntry );
                }
 
                wfProfileOut( __METHOD__ );
index 39f8a47..cf78ce0 100644 (file)
@@ -83,7 +83,7 @@ class DifferenceEngine extends ContextSource {
         * Constructor
         * @param $context IContextSource context to use, anything else will be ignored
         * @param $old Integer old ID we want to show and diff with.
-        * @param $new String|int either 'prev' or 'next'. Default: 0.
+        * @param $new String|int either revision ID or 'prev' or 'next'. Default: 0.
         * @param $rcid Integer Deprecated, no longer used!
         * @param $refreshCache boolean If set, refreshes the diff cache
         * @param $unhide boolean If set, allow viewing deleted revs
@@ -230,7 +230,6 @@ class DifferenceEngine extends ContextSource {
                }
 
                $rollback = '';
-               $undoLink = '';
 
                $query = array();
                # Carry over 'diffonly' param via navigation links
@@ -599,7 +598,8 @@ class DifferenceEngine extends ContextSource {
         *
         * @param string|bool $otitle Header for old text or false
         * @param string|bool $ntitle Header for new text or false
-        * @param string $notice
+        * @param string $notice HTML between diff header and body
+        *
         * @return bool
         */
        function showDiff( $otitle, $ntitle, $notice = '' ) {
@@ -867,6 +867,9 @@ class DifferenceEngine extends ContextSource {
        /**
         * Generate a debug comment indicating diff generating time,
         * server node, and generator backend.
+        *
+        * @param String $generator: What diff engine was used
+        *
         * @return string
         */
        protected function debug( $generator = "internal" ) {
@@ -879,16 +882,20 @@ class DifferenceEngine extends ContextSource {
                        $data[] = wfHostname();
                }
                $data[] = wfTimestamp( TS_DB );
-               return "<!-- diff generator: " .
-               implode( " ",
-               array_map(
+               return "<!-- diff generator: "
+                       . implode( " ",
+                               array_map(
                                        "htmlspecialchars",
-               $data ) ) .
-                       " -->\n";
+                               $data )
+                       )
+                       . " -->\n";
        }
 
        /**
         * Replace line numbers with the text in the user's language
+        *
+        * @param String $text
+        *
         * @return mixed
         */
        function localiseLineNumbers( $text ) {
@@ -1008,6 +1015,12 @@ class DifferenceEngine extends ContextSource {
        /**
         * Add the header to a diff body
         *
+        * @param String $diff: Diff body
+        * @param String $otitle: Old revision header
+        * @param String $ntitle: New revision header
+        * @param String $multi: Notice telling user that there are intermediate revisions between the ones being compared
+        * @param String $notice: Other notices, e.g. that user is viewing deleted content
+        *
         * @return string
         */
        function addHeader( $diff, $otitle, $ntitle, $multi = '', $notice = '' ) {
index 29089c9..10c8dc3 100644 (file)
@@ -52,7 +52,7 @@ abstract class FileBackendStore extends FileBackend {
        protected $maxFileSize = 4294967296; // integer bytes (4GiB)
 
        const CACHE_TTL = 10; // integer; TTL in seconds for process cache entries
-       const CACHE_CHEAP_SIZE = 300; // integer; max entries in "cheap cache"
+       const CACHE_CHEAP_SIZE = 500; // integer; max entries in "cheap cache"
        const CACHE_EXPENSIVE_SIZE = 5; // integer; max entries in "expensive cache"
 
        /**
index fc598db..ec2e2c0 100644 (file)
@@ -931,10 +931,10 @@ class SwiftFileBackend extends FileBackendStore {
         *
         * @param string $fullCont Resolved container name
         * @param string $dir Resolved storage directory with no trailing slash
-        * @param string|null $after Storage path of file to list items after
+        * @param string|null $after Resolved container relative path to list items after
         * @param integer $limit Max number of items to list
         * @param array $params Parameters for getDirectoryList()
-        * @return Array List of resolved paths of directories directly under $dir
+        * @return Array List of container relative resolved paths of directories directly under $dir
         * @throws FileBackendError
         */
        public function getDirListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
@@ -1006,14 +1006,14 @@ class SwiftFileBackend extends FileBackendStore {
         *
         * @param string $fullCont Resolved container name
         * @param string $dir Resolved storage directory with no trailing slash
-        * @param string|null $after Storage path of file to list items after
+        * @param string|null $after Resolved container relative path of file to list items after
         * @param integer $limit Max number of items to list
         * @param array $params Parameters for getDirectoryList()
-        * @return Array List of resolved paths of files under $dir
+        * @return Array List of resolved container relative paths of files under $dir
         * @throws FileBackendError
         */
        public function getFileListPageInternal( $fullCont, $dir, &$after, $limit, array $params ) {
-               $files = array();
+               $files = array(); // list of (path, stat array or null) entries
                if ( $after === INF ) {
                        return $files; // nothing more
                }
@@ -1022,38 +1022,33 @@ class SwiftFileBackend extends FileBackendStore {
                try {
                        $container = $this->getContainer( $fullCont );
                        $prefix = ( $dir == '' ) ? null : "{$dir}/";
+                       $objects = array(); // list of unfiltered names or CF_Object items
                        // Non-recursive: only list files right under $dir
-                       if ( !empty( $params['topOnly'] ) ) { // files and dirs
+                       if ( !empty( $params['topOnly'] ) ) {
                                if ( !empty( $params['adviseStat'] ) ) {
-                                       $limit = min( $limit, self::CACHE_CHEAP_SIZE );
                                        // Note: get_objects() does not include directories
-                                       $objects = $this->loadObjectListing( $params, $dir,
-                                               $container->get_objects( $limit, $after, $prefix, null, '/' ) );
-                                       $files = $objects;
+                                       $objects = $container->get_objects( $limit, $after, $prefix, null, '/' );
                                } else {
+                                       // Note: list_objects() includes directories here
                                        $objects = $container->list_objects( $limit, $after, $prefix, null, '/' );
-                                       foreach ( $objects as $object ) { // files and directories
-                                               if ( substr( $object, -1 ) !== '/' ) {
-                                                       $files[] = $object; // directories end in '/'
-                                               }
-                                       }
                                }
+                               $files = $this->buildFileObjectListing( $params, $dir, $objects );
                        // Recursive: list all files under $dir and its subdirs
-                       } else { // files
+                       } else {
+                               // Note: get_objects()/list_objects() here only return file objects
                                if ( !empty( $params['adviseStat'] ) ) {
-                                       $limit = min( $limit, self::CACHE_CHEAP_SIZE );
-                                       $objects = $this->loadObjectListing( $params, $dir,
-                                               $container->get_objects( $limit, $after, $prefix ) );
+                                       $objects = $container->get_objects( $limit, $after, $prefix );
                                } else {
                                        $objects = $container->list_objects( $limit, $after, $prefix );
                                }
-                               $files = $objects;
+                               $files = $this->buildFileObjectListing( $params, $dir, $objects );
                        }
                        // Page on the unfiltered object listing (what is returned may be filtered)
                        if ( count( $objects ) < $limit ) {
                                $after = INF; // avoid a second RTT
                        } else {
                                $after = end( $objects ); // update last item
+                               $after = is_object( $after ) ? $after->name : $after;
                        }
                } catch ( NoSuchContainerException $e ) {
                } catch ( CloudFilesException $e ) { // some other exception?
@@ -1066,34 +1061,42 @@ class SwiftFileBackend extends FileBackendStore {
        }
 
        /**
-        * Load a list of objects that belong under $dir into stat cache
-        * and return a list of the names of the objects in the same order.
+        * Build a list of file objects, filtering out any directories
+        * and extracting any stat info if provided in $objects (for CF_Objects)
         *
         * @param array $params Parameters for getDirectoryList()
         * @param string $dir Resolved container directory path
-        * @param array $cfObjects List of CF_Object items
-        * @return array List of object names
+        * @param array $objects List of CF_Object items or object names
+        * @return array List of (names,stat array or null) entries
         */
-       private function loadObjectListing( array $params, $dir, array $cfObjects ) {
+       private function buildFileObjectListing( array $params, $dir, array $objects ) {
                $names = array();
-               $storageDir = rtrim( $params['dir'], '/' );
-               $suffixStart = ( $dir === '' ) ? 0 : strlen( $dir ) + 1; // size of "path/to/dir/"
-               // Iterate over the list *backwards* as this primes the stat cache, which is LRU.
-               // If this fills the cache and the caller stats an uncached file before stating
-               // the ones on the listing, there would be zero cache hits if this went forwards.
-               for ( end( $cfObjects ); key( $cfObjects ) !== null; prev( $cfObjects ) ) {
-                       $object = current( $cfObjects );
-                       $path = "{$storageDir}/" . substr( $object->name, $suffixStart );
-                       $val = array(
-                               // Convert various random Swift dates to TS_MW
-                               'mtime'  => $this->convertSwiftDate( $object->last_modified, TS_MW ),
-                               'size'   => (int)$object->content_length,
-                               'latest' => false // eventually consistent
-                       );
-                       $this->cheapCache->set( $path, 'stat', $val );
-                       $names[] = $object->name;
+               foreach ( $objects as $object ) {
+                       if ( is_object( $object ) ) {
+                               $stat = array(
+                                       // Convert various random Swift dates to TS_MW
+                                       'mtime'  => $this->convertSwiftDate( $object->last_modified, TS_MW ),
+                                       'size'   => (int)$object->content_length,
+                                       'latest' => false // eventually consistent
+                               );
+                               $names[] = array( $object->name, $stat );
+                       } elseif ( substr( $object, -1 ) !== '/' ) {
+                               // Omit directories, which end in '/' in listings
+                               $names[] = array( $object, null );
+                       }
                }
-               return array_reverse( $names ); // keep the paths in original order
+               return $names;
+       }
+
+       /**
+        * Do not call this function outside of SwiftFileBackendFileList
+        *
+        * @param string $path Storage path
+        * @param array $val Stat value
+        * @return void
+        */
+       public function loadListingStatInternal( $path, array $val ) {
+               $this->cheapCache->set( $path, 'stat', $val );
        }
 
        protected function doGetFileSha1base36( array $params ) {
@@ -1571,7 +1574,7 @@ class SwiftFileOpHandle extends FileBackendStoreOpHandle {
  * @ingroup FileBackend
  */
 abstract class SwiftFileBackendList implements Iterator {
-       /** @var Array */
+       /** @var Array List of path or (path,stat array) entries */
        protected $bufferIter = array();
        protected $bufferAfter = null; // string; list items *after* this path
        protected $pos = 0; // integer
@@ -1699,7 +1702,13 @@ class SwiftFileBackendFileList extends SwiftFileBackendList {
         * @return string|bool String (relative path) or false
         */
        public function current() {
-               return substr( current( $this->bufferIter ), $this->suffixStart );
+               list( $path, $stat ) = current( $this->bufferIter );
+               $relPath = substr( $path, $this->suffixStart );
+               if ( is_array( $stat ) ) {
+                       $storageDir = rtrim( $this->params['dir'], '/' );
+                       $this->backend->loadListingStatInternal( "$storageDir/$path", $stat );
+               }
+               return $relPath;
        }
 
        /**
index 3674353..0f4faec 100644 (file)
@@ -699,7 +699,7 @@ class MysqlUpdater extends DatabaseUpdater {
 
                if ( !$this->db->tableExists( 'user_rights', __METHOD__ ) ) {
                        if ( $this->db->fieldExists( 'user', 'user_rights', __METHOD__ ) ) {
-                               $this->db->applyPatch(
+                               $this->applyPatch(
                                        'patch-user_rights.sql',
                                        false,
                                        'Upgrading from a 1.3 or older database? Breaking out user_rights for conversion'
index e2444a1..43ba117 100644 (file)
@@ -168,6 +168,13 @@ class BitmapHandler extends ImageHandler {
 
                # Transform functions and binaries need a FS source file
                $scalerParams['srcPath'] = $image->getLocalRefPath();
+               if ( $scalerParams['srcPath'] === false ) { // Failed to get local copy
+                       wfDebugLog( 'thumbnail',
+                               sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"',
+                                       wfHostname(), $image->getName() ) );
+                       return new MediaTransformError( 'thumbnail_error',
+                               $scalerParams['clientWidth'], $scalerParams['clientHeight'] );
+               }
 
                # Try a hook
                $mto = null;
index 848a1a0..2df3160 100644 (file)
@@ -541,7 +541,7 @@ class Parser {
                        }
                        foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
                                if ( wfRunHooks( 'ParserLimitReportFormat',
-                                       array( $key, $value, &$limitReport, false, false )
+                                       array( $key, &$value, &$limitReport, false, false )
                                ) ) {
                                        $keyMsg = wfMessage( $key )->inLanguage( 'en' )->useDatabase( false );
                                        $valueMsg = wfMessage( array( "$key-value-text", "$key-value" ) )
index aab839f..d02886f 100644 (file)
@@ -75,7 +75,7 @@ class SpecialChangeEmail extends UnlistedSpecialPage {
                $user = $this->getUser();
                $request = $this->getRequest();
 
-               if ( !$request->wasPosted() && !$user->isLoggedIn() ) {
+               if ( !$user->isLoggedIn() ) {
                        $this->error( 'changeemail-no-info' );
 
                        return;
index b79aaa9..0700c49 100644 (file)
@@ -60,7 +60,7 @@ class SpecialUpload extends SpecialPage {
 
        /** User input variables from the root section **/
        public $mIgnoreWarning;
-       public $mWatchThis;
+       public $mWatchthis;
        public $mCopyrightStatus;
        public $mCopyrightSource;
 
@@ -75,8 +75,6 @@ class SpecialUpload extends SpecialPage {
        public $uploadFormTextTop;
        public $uploadFormTextAfterSummary;
 
-       public $mWatchthis;
-
        /**
         * Initialize instance variables from request and create an Upload handler
         */
@@ -517,11 +515,17 @@ class SpecialUpload extends SpecialPage {
                        return true;
                }
 
+               $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
+               if ( $desiredTitleObj instanceof Title && $this->getUser()->isWatched( $desiredTitleObj ) ) {
+                       // Already watched, don't change that
+                       return true;
+               }
+
                $local = wfLocalFile( $this->mDesiredDestName );
                if ( $local && $local->exists() ) {
                        // We're uploading a new version of an existing file.
                        // No creation, so don't watch it if we're not already.
-                       return $this->getUser()->isWatched( $local->getTitle() );
+                       return false;
                } else {
                        // New page should get watched if that's our option.
                        return $this->getUser()->getOption( 'watchcreations' );
@@ -1011,7 +1015,7 @@ class UploadForm extends HTMLForm {
                                        'id' => 'wpWatchthis',
                                        'label-message' => 'watchthisupload',
                                        'section' => 'options',
-                                       'default' => $user->getOption( 'watchcreations' ),
+                                       'default' => $this->mWatch,
                                )
                        );
                }
index 336b05b..62c33a2 100644 (file)
@@ -60,15 +60,21 @@ class SpecialWatchlist extends SpecialPage {
                // Add feed links
                $wlToken = $user->getTokenFromOption( 'watchlisttoken' );
                if ( $wlToken ) {
-                       $this->addFeedLinks( array( 'action' => 'feedwatchlist', 'allrev' => 'allrev',
-                                                               'wlowner' => $user->getName(), 'wltoken' => $wlToken ) );
+                       $this->addFeedLinks( array(
+                               'action' => 'feedwatchlist',
+                               'allrev' => 1,
+                               'wlowner' => $user->getName(),
+                               'wltoken' => $wlToken,
+                       ) );
                }
 
                $this->setHeaders();
                $this->outputHeader();
 
-               $output->addSubtitle( $this->msg( 'watchlistfor2', $user->getName()
-                       )->rawParams( SpecialEditWatchlist::buildTools( null ) ) );
+               $output->addSubtitle(
+                       $this->msg( 'watchlistfor2', $user->getName() )
+                               ->rawParams( SpecialEditWatchlist::buildTools( null ) )
+               );
 
                $request = $this->getRequest();
 
@@ -205,10 +211,6 @@ class SpecialWatchlist extends SpecialPage {
                        $nonRevisionTypes = array( RC_LOG );
                        wfRunHooks( 'SpecialWatchlistGetNonRevisionTypes', array( &$nonRevisionTypes ) );
                        if ( $nonRevisionTypes ) {
-                               if ( count( $nonRevisionTypes ) === 1 ) {
-                                       // if only one use an equality instead of IN condition
-                                       $nonRevisionTypes = reset( $nonRevisionTypes );
-                               }
                                $conds[] = $dbr->makeList(
                                        array(
                                                'rc_this_oldid=page_latest',
index 68f6fd7..9cd3d3f 100644 (file)
@@ -333,7 +333,7 @@ class Replacer {
  * Class to replace regex matches with a string similar to that used in preg_replace()
  */
 class RegexlikeReplacer extends Replacer {
-       var $r;
+       private $r;
 
        /**
         * @param string $r
@@ -409,8 +409,8 @@ class HashtableReplacer extends Replacer {
  * Supports lazy initialisation of FSS resource
  */
 class ReplacementArray {
-       /*mostly private*/ var $data = false;
-       /*mostly private*/ var $fss = false;
+       private $data = false;
+       private $fss = false;
 
        /**
         * Create an object with the specified replacement array
@@ -525,19 +525,19 @@ class ReplacementArray {
  */
 class ExplodeIterator implements Iterator {
        // The subject string
-       var $subject, $subjectLength;
+       private $subject, $subjectLength;
 
        // The delimiter
-       var $delim, $delimLength;
+       private $delim, $delimLength;
 
        // The position of the start of the line
-       var $curPos;
+       private $curPos;
 
        // The position after the end of the next delimiter
-       var $endPos;
+       private $endPos;
 
        // The current token
-       var $current;
+       private $current;
 
        /**
         * Construct a DelimIterator
index f54ce83..6bbd8bf 100644 (file)
@@ -4480,7 +4480,7 @@ class Language {
                        $nlink = htmlspecialchars( $next );
                } else {
                        $nlink = $this->numLink( $title, $offset + $limit, $limit,
-                               $query, $next, 'prevn-title', 'mw-nextlink' );
+                               $query, $next, 'nextn-title', 'mw-nextlink' );
                }
 
                # Make links to set number of items per page
index bc43c67..2300694 100644 (file)
@@ -94,7 +94,7 @@ class UploadStashCleanup extends Maintenance {
 
                // Delete all the corresponding thumbnails...
                $dir = $tempRepo->getZonePath( 'thumb' );
-               $iterator = $tempRepo->getBackend()->getFileList( array( 'dir' => $dir ) );
+               $iterator = $tempRepo->getBackend()->getFileList( array( 'dir' => $dir, 'adviseStat' => 1 ) );
                $this->output( "Deleting old thumbnails...\n" );
                $i = 0;
                foreach ( $iterator as $file ) {
diff --git a/maintenance/runScript.php b/maintenance/runScript.php
new file mode 100644 (file)
index 0000000..385db15
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+/**
+ * Convenience maintenance script wrapper, useful for scripts
+ * or extensions located outside of standard locations.
+ *
+ * To use, give the maintenance script as a relative or full path.
+ *
+ * Example usage:
+ *
+ *  If your pwd is mediawiki base folder:
+ *   php maintenance/runScript.php extensions/Wikibase/lib/maintenance/dispatchChanges.php
+ *
+ * If your pwd is maintenance folder:
+ *  php runScript.php ../extensions/Wikibase/lib/maintenance/dispatchChanges.php
+ *
+ * Or full path:
+ *  php /var/www/mediawiki/maintenance/runScript.php maintenance/runJobs.php
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @author Katie Filbert < aude.wiki@gmail.com >
+ * @file
+ * @ingroup Maintenance
+ */
+$IP = getenv( 'MW_INSTALL_PATH' );
+
+if ( $IP === false ) {
+       $IP = dirname( __DIR__ );
+
+       putenv( "MW_INSTALL_PATH=$IP" );
+}
+
+require_once "$IP/maintenance/Maintenance.php";
+
+if ( !isset( $argv[1] ) ) {
+       fwrite( STDERR, "This script requires a maintainance script as an argument.\n"
+               . "Usage: runScript.php extensions/Wikibase/lib/maintenance/dispatchChanges\n" );
+       exit( 1 );
+}
+
+$scriptFilename = $argv[1];
+array_shift( $argv );
+
+$scriptFile = realpath( $scriptFilename );
+
+if ( !$scriptFile ) {
+       fwrite( STDERR, "The MediaWiki script file \"{$scriptFilename}\" does not exist.\n" );
+       exit( 1 );
+}
+
+require_once $scriptFile;
index 259f1c8..cfc717f 100644 (file)
                 */
                addCSS: function ( text ) {
                        var s = mw.loader.addStyleTag( text );
-                       return s.sheet || s;
+                       return s.sheet || s.styleSheet || s;
                },
 
                /**
index 08d6ba8..665fa39 100644 (file)
@@ -34,6 +34,7 @@ class FormOptionsTest extends MediaWikiTestCase {
                $this->object->add( 'string1', 'string one' );
                $this->object->add( 'string2', 'string two' );
                $this->object->add( 'integer', 0 );
+               $this->object->add( 'float', 0.0 );
                $this->object->add( 'intnull', 0, FormOptions::INTNULL );
        }
 
@@ -45,6 +46,9 @@ class FormOptionsTest extends MediaWikiTestCase {
        private function assertGuessInt( $data ) {
                $this->guess( FormOptions::INT, $data );
        }
+       private function assertGuessFloat( $data ) {
+               $this->guess( FormOptions::FLOAT, $data );
+       }
        private function assertGuessString( $data ) {
                $this->guess( FormOptions::STRING, $data );
        }
@@ -71,10 +75,15 @@ class FormOptionsTest extends MediaWikiTestCase {
                $this->assertGuessInt( 5 );
                $this->assertGuessInt( 0x0F );
 
+               $this->assertGuessFloat( 0.0 );
+               $this->assertGuessFloat( 1.5 );
+               $this->assertGuessFloat( 1e3 );
+
                $this->assertGuessString( 'true' );
                $this->assertGuessString( 'false' );
                $this->assertGuessString( '5' );
                $this->assertGuessString( '0' );
+               $this->assertGuessString( '1.5' );
        }
 
        /**
index f8ed14b..06ed1fd 100644 (file)
@@ -269,6 +269,28 @@ class WebRequestTest extends MediaWikiTestCase {
                                false,
                                'With X-Forwaded-For and private IP and hook (disallowed)'
                        ),
+                       array(
+                               '12.0.0.1',
+                               array(
+                                       'REMOTE_ADDR' => 'abcd:0001:002:03:4:555:6666:7777',
+                                       'HTTP_X_FORWARDED_FOR' => '12.0.0.1, abcd:0001:002:03:4:555:6666:7777',
+                               ),
+                               array( 'ABCD:1:2:3::/64' ),
+                               array(),
+                               false,
+                               'IPv6 CIDR'
+                       ),
+                       array(
+                               '12.0.0.3',
+                               array(
+                                       'REMOTE_ADDR' => '12.0.0.1',
+                                       'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2'
+                               ),
+                               array( '12.0.0.0/24' ),
+                               array(),
+                               false,
+                               'IPv4 CIDR'
+                       ),
                );
        }
 
@@ -277,6 +299,14 @@ class WebRequestTest extends MediaWikiTestCase {
         * @covers WebRequest::getIP
         */
        public function testGetIpLackOfRemoteAddrThrowAnException() {
+               // ensure that local install state doesn't interfere with test
+               $this->setMwGlobals( array(
+                       'wgSquidServersNoPurge' => array(),
+                       'wgSquidServers' => array(),
+                       'wgUsePrivateIPs' => false,
+                       'wgHooks' => array(),
+               ) );
+
                $request = new WebRequest();
                # Next call throw an exception about lacking an IP
                $request->getIP();