Merge "http->https"
authorHashar <hashar@free.fr>
Fri, 23 Mar 2012 21:23:38 +0000 (21:23 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 23 Mar 2012 21:23:39 +0000 (21:23 +0000)
17 files changed:
.gitignore
RELEASE-NOTES-1.19
api.php
extensions/.gitignore [new file with mode: 0644]
img_auth.php
includes/AutoLoader.php
includes/CryptRand.php
includes/GitInfo.php [new file with mode: 0644]
includes/specials/SpecialNewimages.php
includes/specials/SpecialVersion.php
mw-config/index.php
profileinfo.php
resources/mediawiki.action/mediawiki.action.watch.ajax.js
resources/mediawiki.api/mediawiki.api.watch.js
skins/Vector.php
tests/phpunit/includes/RecentChangeTest.php
thumb.php

index 6688707..943378a 100644 (file)
@@ -19,3 +19,5 @@ cache/*.cdb
 images/[0-9a-f]
 images/temp
 images/thumb
+maintenance/dev/data/
+maintenance/.mweval_history
\ No newline at end of file
index 576cfa8..7ba2c83 100644 (file)
@@ -13,15 +13,15 @@ production.
 
 === Changes since 1.19 beta 1 ===
 * (bug 35014) Including a special page no longer sets the page's title to the
-  included page
-* (bug 35019) Edit summaries are no longer transformed in notification e-mails
-* (bug 35152) Help message for e-mail is shown again in user preferences
+  included page.
+* (bug 35019) Edit summaries are no longer transformed in notification e-mails.
+* (bug 35152) Help message for e-mail is shown again in user preferences.
 * (bug 34887) $3 and $4 parameters are now substituted correctly in message
-  "movepage-moved"
+  "movepage-moved".
 * (bug 34841) Edit links are no longer displayed when display old page versions
-* (bug 34889) User name should be normalized on Special:Contributions
-* (bug 35051) If heading has a trailing space after == then its name is not 
-  preloaded into edit summary on section edit
+* (bug 34889) User name should be normalized on Special:Contributions.
+* (bug 35051) If heading has a trailing space after == then its name is not
+  preloaded into edit summary on section edit.
 * (bug 31417) New ID mw-content-text around the actual page text, without categories,
   contentSub, ... The same div often also contains the class mw-content-ltr/rtl.
 * (bug 35303) Proxy and DNS blacklist blocking works again
@@ -31,6 +31,8 @@ production.
   core parser functions which operate on strings, such as padleft.
 * (bug 18295) Don't expose strip markers when a tag appears inside a link 
   inside a heading.
+* (bug 34907) Fixed exposure of tokens through load.php that could have facilitated
+  CSRF attacks
 
 === Configuration changes in 1.19 ===
 * Removed SkinTemplateSetupPageCss hook; use BeforePageDisplay instead.
@@ -45,6 +47,7 @@ production.
 * (bug 32239) Removed $wgEnableTooltipsAndAccesskeys.
 * Removed $wgVectorShowVariantName.
 * Removed $wgExtensionAliasesFiles. Use $wgExtensionMessagesFiles.
+* Removed $wgResourceLoaderInlinePrivateModules, now always enabled.
 
 === New features in 1.19 ===
 * (bug 19838) Add ability to get all interwiki prefixes also if the interwiki
@@ -128,8 +131,8 @@ production.
   200 status code instead of 404 for nonexistent articles.
 * (bug 33447) Link to the broken image tracking category from Special:Wantedfiles.
 * (bug 27724) Add timestamp to job queue.
-* (bug 30339) Implement SpecialPage for running javascript tests. Disabled by default, due to
-  tests potentially being harmful, not to be run on a production wiki.
+* (bug 30339) Implement SpecialPage for running javascript tests. Disabled by default,
+  due to tests potentially being harmful, not to be run on a production wiki.
   Enable by setting $wgEnableJavaScriptTest to true.
 * Extensions can use the RequestContextCreateSkin hook to override what skin is
   loaded in some contexts.
@@ -146,8 +149,8 @@ production.
 * Special:MovePage now has a dropdown menu for namespaces.
 * (bug 34420) Special:Version now shows git HEAD sha1 when available.
 * (bug 33952) Refactor mw.toolbar to allow dynamic additions at any time.
-* Now possible to specify separate section title and edit summary when adding a new section to a
-  page via the edit API action.
+* Now possible to specify separate section title and edit summary when adding
+  a new section to a page via the edit API action.
 
 === Bug fixes in 1.19 ===
 * $wgUploadNavigationUrl should be used for file redlinks if.
diff --git a/api.php b/api.php
index a5a2579..889c5f1 100644 (file)
--- a/api.php
+++ b/api.php
@@ -45,7 +45,7 @@ if ( !function_exists( 'version_compare' ) || version_compare( phpversion(), '5.
 
 // Initialise common code.
 if ( isset( $_SERVER['MW_COMPILED'] ) ) {
-       require ( 'phase3/includes/WebStart.php' );
+       require ( 'core/includes/WebStart.php' );
 } else {
        require ( dirname( __FILE__ ) . '/includes/WebStart.php' );
 }
diff --git a/extensions/.gitignore b/extensions/.gitignore
new file mode 100644 (file)
index 0000000..f847620
--- /dev/null
@@ -0,0 +1,3 @@
+*
+!README
+!.gitignore
index 3999bf3..82afef2 100644 (file)
@@ -28,7 +28,7 @@
 
 define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
 if ( isset( $_SERVER['MW_COMPILED'] ) ) {
-       require ( 'phase3/includes/WebStart.php' );
+       require ( 'core/includes/WebStart.php' );
 } else {
        require ( dirname( __FILE__ ) . '/includes/WebStart.php' );
 }
index 4ab5de8..e791873 100644 (file)
@@ -95,6 +95,7 @@ $wgAutoloadLocalClasses = array(
        'FormAction' => 'includes/Action.php',
        'FormOptions' => 'includes/FormOptions.php',
        'FormSpecialPage' => 'includes/SpecialPage.php',
+       'GitInfo' => 'includes/GitInfo.php',
        'HashtableReplacer' => 'includes/StringUtils.php',
        'HistoryBlob' => 'includes/HistoryBlob.php',
        'HistoryBlobCurStub' => 'includes/HistoryBlob.php',
index 10f379c..e4be1b3 100644 (file)
@@ -120,7 +120,7 @@ class MWCryptRand {
        /**
         * Randomly hash data while mixing in clock drift data for randomness
         *
-        * @param $data The data to randomly hash.
+        * @param $data string The data to randomly hash.
         * @return String The hashed bytes
         * @author Tim Starling
         */
@@ -166,7 +166,7 @@ class MWCryptRand {
 
        /**
         * Return a rolling random state initially build using data from unstable sources
-        * @return A new weak random state
+        * @return string A new weak random state
         */
        protected function randomState() {
                static $state = null;
@@ -184,6 +184,7 @@ class MWCryptRand {
 
        /**
         * Decide on the best acceptable hash algorithm we have available for hash()
+        * @throws MWException
         * @return String A hash algorithm
         */
        protected function hashAlgo() {
@@ -227,6 +228,7 @@ class MWCryptRand {
         * Generate an acceptably unstable one-way-hash of some text
         * making use of the best hash algorithm that we have available.
         *
+        * @param $data string
         * @return String A raw hash of the data
         */
        protected function hash( $data ) {
@@ -237,6 +239,8 @@ class MWCryptRand {
         * Generate an acceptably unstable one-way-hmac of some text
         * making use of the best hash algorithm that we have available.
         *
+        * @param $data string
+        * @param $key string
         * @return String A raw hash of the data
         */
        protected function hmac( $data, $key ) {
@@ -282,7 +286,7 @@ class MWCryptRand {
                                if ( $iv === false ) {
                                        wfDebug( __METHOD__ . ": mcrypt_create_iv returned false.\n" );
                                } else {
-                                       $bytes .= $iv;
+                                       $buffer .= $iv;
                                        wfDebug( __METHOD__ . ": mcrypt_create_iv generated " . strlen( $iv ) . " bytes of randomness.\n" );
                                }
                                wfProfileOut( __METHOD__ . '-mcrypt' );
@@ -409,6 +413,7 @@ class MWCryptRand {
 
        /**
         * Return a singleton instance of MWCryptRand
+        * @return MWCryptRand
         */
        protected static function singleton() {
                if ( is_null( self::$singleton ) ) {
diff --git a/includes/GitInfo.php b/includes/GitInfo.php
new file mode 100644 (file)
index 0000000..bc3f35e
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+/**
+ * A class to help return information about a git repo MediaWiki may be inside
+ * This is used by Special:Version and is also useful for the LocalSettings.php
+ * of anyone working on large branches in git to setup config that show up only
+ * when specific branches are currently checked out.
+ *
+ * @file
+ */
+
+class GitInfo {
+
+       /**
+        * Singleton for the repo at $IP
+        */
+       protected static $repo = null;
+
+       /**
+        * Location of the .git directory
+        */
+       protected $basedir;
+
+       /**
+        * @param $dir The root directory of the repo where the .git dir can be found
+        */
+       public function __construct( $dir ) {
+               $this->basedir = "{$dir}/.git/";
+       }
+
+       /**
+        * Return a singleton for the repo at $IP
+        * @return GitInfo
+        */
+       public static function repo() {
+               global $IP;
+               if ( is_null( self::$repo ) ) {
+                       self::$repo = new self( $IP );
+               }
+               return self::$repo;
+       }
+
+       /**
+        * Check if a string looks like a hex encoded SHA1 hash
+        *
+        * @param $str The string to check
+        * @return bool Whether or not the string looks like a SHA1
+        */
+       public static function isSHA1( $str ) {
+               return !!preg_match( '/^[0-9A-Z]{40}$/i', $str );
+       }
+
+       /**
+        * Return the HEAD of the repo (without any opening "ref: ")
+        * @return string The HEAD
+        */
+       public function getHead() {
+               $HEADfile = "{$this->basedir}/HEAD";
+
+               if ( !is_readable( $HEADfile ) ) {
+                       return false;
+               }
+
+               $HEAD = file_get_contents( $HEADfile );
+
+               if ( preg_match( "/ref: (.*)/", $HEAD, $m ) ) {
+                       return rtrim( $m[1] );
+               } else {
+                       return $HEAD;
+               }
+       }
+
+       /**
+        * Return the SHA1 for the current HEAD of the repo
+        * @return string A SHA1 or false
+        */
+       public function getHeadSHA1() {
+               $HEAD = $this->getHead();
+
+               // If detached HEAD may be a SHA1
+               if ( self::isSHA1( $HEAD ) ) {
+                       return $HEAD;
+               }
+
+               // If not a SHA1 it may be a ref:
+               $REFfile = "{$this->basedir}{$HEAD}";
+               if ( !is_readable( $REFfile ) ) {
+                       return false;
+               }
+
+               $sha1 = rtrim( file_get_contents( $REFfile ) );
+
+               return $sha1;
+       }
+
+       /**
+        * Return the name of the current branch, or HEAD if not found
+        * @return string The branch name, HEAD, or false
+        */
+       public function getCurrentBranch() {
+               $HEAD = $this->getHead();
+               if ( $HEAD && preg_match( "#^refs/heads/(.*)$#", $HEAD, $m ) ) {
+                       return $m[1];
+               } else {
+                       return $HEAD;
+               }
+       }
+
+       /**
+        * @see self::getHeadSHA1
+        */
+       public static function headSHA1() {
+               return self::repo()->getHeadSHA1();
+       }
+
+       /**
+        * @see self::getCurrentBranch
+        */
+       public static function currentBranch() {
+               return self::repo()->getCurrentBranch();
+       }
+
+}
\ No newline at end of file
index b88123d..45dbd36 100644 (file)
@@ -123,7 +123,7 @@ class NewFilesPager extends ReverseChronologicalPager {
                $this->gallery->add(
                        $title,
                        "$ul<br />\n<i>"
-                               . htmlspecialchars( $this->getLanguage()->timeanddate( $row->img_timestamp, true ) )
+                               . htmlspecialchars( $this->getLanguage()->userTimeAndDate( $row->img_timestamp, $this->getUser() ) )
                                . "</i><br />\n"
                );
        }
@@ -139,7 +139,7 @@ class NewFilesPager extends ReverseChronologicalPager {
                        ),
                        'showbots' => array(
                                'type' => 'check',
-                               'label' => wfMessage( 'showhidebots', wfMsg( 'show' ) ),
+                               'label' => $this->msg( 'showhidebots', $this->msg( 'show' )->plain() )->escaped(),
                                'name' => 'showbots',
                        #       'default' => $this->getRequest()->getBool( 'showbots', 0 ),
                        ),
@@ -161,9 +161,9 @@ class NewFilesPager extends ReverseChronologicalPager {
 
                $form = new HTMLForm( $fields, $this->getContext() );
                $form->setTitle( $this->getTitle() );
-               $form->setSubmitText( wfMsg( 'ilsubmit' ) );
+               $form->setSubmitTextMsg( 'ilsubmit' );
                $form->setMethod( 'get' );
-               $form->setWrapperLegend( wfMsg( 'newimages-legend' ) );
+               $form->setWrapperLegendMsg( 'newimages-legend' );
 
                return $form;
        }
index 2b44337..9181de0 100644 (file)
@@ -160,10 +160,14 @@ class SpecialVersion extends SpecialPage {
                global $wgVersion, $IP;
                wfProfileIn( __METHOD__ );
 
-               $info = self::getSvnInfo( $IP );
-               if ( !$info ) {
+               $gitInfo = self::getGitHeadSha1( $IP );
+               $svnInfo = self::getSvnInfo( $IP );
+               if ( !$svnInfo && !$gitInfo ) {
                        $version = $wgVersion;
-               } elseif( $flags === 'nodb' ) {
+               } elseif ( $gitInfo ) {
+                       $shortSha1 = substr( $gitInfo, 0, 7 );
+                       $version = "$wgVersion ($shortSha1)";
+               } elseif ( $flags === 'nodb' ) {
                        $version = "$wgVersion (r{$info['checkout-rev']})";
                } else {
                        $version = $wgVersion . ' ' .
@@ -717,24 +721,8 @@ class SpecialVersion extends SpecialPage {
         * @return bool|String sha1 of commit HEAD points to
         */
        public static function getGitHeadSha1( $dir ) {
-               $BASEDIR  = "{$dir}/.git/";
-               $HEADfile = "{$BASEDIR}/HEAD";
-
-               if( !file_exists( $HEADfile ) ) {
-                       return false;
-               }
-
-               preg_match( "/ref: (.*)/",
-                       file_get_contents( $HEADfile ), $m );
-
-               $REFfile = "{$BASEDIR}{$m[1]}";
-               if( !file_exists( $REFfile ) ) {
-                       return false;
-               }
-
-               $sha1 = rtrim( file_get_contents( $REFfile ) );
-
-               return $sha1;
+               $repo = new GitInfo( $dir );
+               return $repo->getHeadSHA1();
        }
 
        function showEasterEgg() {
index c65be69..edfae92 100644 (file)
@@ -10,7 +10,7 @@ define( 'MEDIAWIKI_INSTALL', true );
 
 chdir( dirname( dirname( __FILE__ ) ) );
 if ( isset( $_SERVER['MW_COMPILED'] ) ) {
-       require ( 'phase3/includes/WebStart.php' );
+       require ( 'core/includes/WebStart.php' );
 } else {
        require( dirname( dirname( __FILE__ ) ) . '/includes/WebStart.php' );
 }
index 1549349..ef038c1 100644 (file)
@@ -29,7 +29,7 @@ ini_set( 'zlib.output_compression', 'off' );
 
 $wgEnableProfileInfo = $wgProfileToDatabase = false;
 if ( isset( $_SERVER['MW_COMPILED'] ) ) {
-       require ( 'phase3/includes/WebStart.php' );
+       require ( 'core/includes/WebStart.php' );
 } else {
        require ( dirname( __FILE__ ) . '/includes/WebStart.php' );
 }
@@ -56,16 +56,20 @@ header( 'Content-Type: text/html; charset=utf-8' );
                text-align: right;
        }
        td.timep, td.tpc, td.tpr {
-               background-color: #fffff0;
+               background-color: #ffff80;
        }
        td.memoryp, td.mpc, td.mpr {
-               background-color: #f0f8ff;
+               background-color: #80f8ff;
        }
        td.count, td,cpr {
-               background-color: #f0fff0;
+               background-color: #80ff80;
        }
        td.name {
-               background-color: #f9f9f9;
+               background-color: #89f9f9;
+       }
+
+       tr:hover {
+               font-weight: bold;
        }
 </style>
 </head>
index f5f09f5..090e4c3 100644 (file)
  */
 ( function ( $, mw, undefined ) {
 
-/**
- * The name of the page to watch or unwatch.
- */
-var title = mw.config.get( 'wgRelevantPageName', mw.config.get( 'wgPageName' ) );
-
-/**
- * Update the link text, link href attribute and (if applicable)
- * "loading" class.
- *
- * @param $link {jQuery} Anchor tag of (un)watch link
- * @param action {String} One of 'watch', 'unwatch'.
- * @param state {String} [optional] 'idle' or 'loading'. Default is 'idle'.
- */
-function updateWatchLink( $link, action, state ) {
-       // message keys 'watch', 'watching', 'unwatch' or 'unwatching'.
-       var     msgKey = state === 'loading' ? action + 'ing' : action,
-               accesskeyTip = $link.attr( 'title' ).match( mw.util.tooltipAccessKeyRegexp ),
+       /**
+        * The name of the page to watch or unwatch.
+        */
+       var title = mw.config.get( 'wgRelevantPageName', mw.config.get( 'wgPageName' ) );
+
+       /**
+        * Update the link text, link href attribute and (if applicable)
+        * "loading" class.
+        *
+        * @param $link {jQuery} Anchor tag of (un)watch link.
+        * @param action {String} One of 'watch', 'unwatch'.
+        * @param state {String} [optional] 'idle' or 'loading'. Default is 'idle'.
+        */
+       function updateWatchLink( $link, action, state ) {
+               var accesskeyTip, msgKey, $li;
+
+               // message keys 'watch', 'watching', 'unwatch' or 'unwatching'.
+               msgKey = state === 'loading' ? action + 'ing' : action;
+               accesskeyTip = $link.attr( 'title' ).match( mw.util.tooltipAccessKeyRegexp );
                $li = $link.closest( 'li' );
 
-       $link
-               .text( mw.msg( msgKey ) )
-               .attr( 'title', mw.msg( 'tooltip-ca-' + action ) +
-                       ( accesskeyTip ? ' ' + accesskeyTip[0] : '' )
-               )
-               .attr( 'href', mw.util.wikiScript() + '?' + $.param({
-                               title: title,
-                               action: action
-                       })
-               );
-
-       // Special case for vector icon
-       if ( $li.hasClass( 'icon' ) ) {
-               if ( state === 'loading' ) {
-                       $link.addClass( 'loading' );
-               } else {
-                       $link.removeClass( 'loading' );
+               $link
+                       .text( mw.msg( msgKey ) )
+                       .attr( 'title', mw.msg( 'tooltip-ca-' + action ) +
+                               ( accesskeyTip ? ' ' + accesskeyTip[0] : '' )
+                       )
+                       .attr( 'href', mw.util.wikiScript() + '?' + $.param({
+                                       title: title,
+                                       action: action
+                               })
+                       );
+
+               // Special case for vector icon
+               if ( $li.hasClass( 'icon' ) ) {
+                       if ( state === 'loading' ) {
+                               $link.addClass( 'loading' );
+                       } else {
+                               $link.removeClass( 'loading' );
+                       }
                }
        }
-}
 
-/**
- * @todo This should be moved somewhere more accessible.
- * @param url {String}
- * @return {String} The extracted action, defaults to 'view'.
- */
-function mwUriGetAction( url ) {
-       var     actionPaths = mw.config.get( 'wgActionPaths' ),
-               key, parts, m, action;
-
-       // @todo: Does MediaWiki give action path or query param
-       // precedence ? If the former, move this to the bottom
-       action = mw.util.getParamValue( 'action', url );
-       if ( action !== null ) {
-               return action;
-       }
+       /**
+        * @todo This should be moved somewhere more accessible.
+        * @param url {String}
+        * @return {String} The extracted action, defaults to 'view'.
+        */
+       function mwUriGetAction( url ) {
+               var action, actionPaths, key, i, m, parts;
+
+               actionPaths = mw.config.get( 'wgActionPaths' );
+
+               // @todo: Does MediaWiki give action path or query param
+               // precedence ? If the former, move this to the bottom
+               action = mw.util.getParamValue( 'action', url );
+               if ( action !== null ) {
+                       return action;
+               }
+
+               for ( key in actionPaths ) {
+                       if ( actionPaths.hasOwnProperty( key ) ) {
+                               parts = actionPaths[key].split( '$1' );
+                               for ( i = 0; i < parts.length; i += 1 ) {
+                                       parts[i] = $.escapeRE( parts[i] );
+                               }
+                               m = new RegExp( parts.join( '(.+)' ) ).exec( url );
+                               if ( m && m[1] ) {
+                                       return key;
+                               }
 
-       for ( key in actionPaths ) {
-               if ( actionPaths.hasOwnProperty( key ) ) {
-                       parts = actionPaths[key].split( '$1' );
-                       for ( i = 0; i < parts.length; i += 1 ) {
-                               parts[i] = $.escapeRE( parts[i] );
-                       }
-                       m = new RegExp( parts.join( '(.+)' ) ).exec( url );
-                       if ( m && m[1] ) {
-                               return key;
                        }
-               
                }
+
+               return 'view';
        }
 
-       return 'view';
-}
+       $( document ).ready( function () {
+               var $links = $( '.mw-watchlink a, a.mw-watchlink, ' +
+                       '#ca-watch a, #ca-unwatch a, #mw-unwatch-link1, ' +
+                       '#mw-unwatch-link2, #mw-watch-link2, #mw-watch-link1' );
 
-$( document ).ready( function() {
-       var $links = $( '.mw-watchlink a, a.mw-watchlink, ' +
-               '#ca-watch a, #ca-unwatch a, #mw-unwatch-link1, ' +
-               '#mw-unwatch-link2, #mw-watch-link2, #mw-watch-link1' );
+               // Allowing people to add inline animated links is a little scary
+               $links = $links.filter( ':not( #bodyContent *, #content * )' );
 
-       // Allowing people to add inline animated links is a little scary
-       $links = $links.filter( ':not( #bodyContent *, #content * )' );
+               $links.click( function ( e ) {
+                       var action, api, $link;
 
-       $links.click( function( e ) {
-               var     $link, api,
                        action = mwUriGetAction( this.href );
 
-               if ( action !== 'watch' && action !== 'unwatch' ) {
-                       // Could not extract target action from link url,
-                       // let native browsing handle it further
-                       return true;
-               }
-               e.preventDefault();
-               e.stopPropagation();
-               
-               $link = $( this );
-
-               updateWatchLink( $link, action, 'loading' );
-
-               api = new mw.Api();
-               api[action](
-                       title,
-                       // Success
-                       function( watchResponse ) {
-                               var     otherAction = action === 'watch' ? 'unwatch' : 'watch',
-                                       $li = $link.closest( 'li' );
+                       if ( action !== 'watch' && action !== 'unwatch' ) {
+                               // Could not extract target action from link url,
+                               // let native browsing handle it further
+                               return true;
+                       }
+                       e.preventDefault();
+                       e.stopPropagation();
 
-                               mw.util.jsMessage( watchResponse.message, 'ajaxwatch' );
+                       $link = $( this );
 
-                               // Set link to opposite
-                               updateWatchLink( $link, otherAction );
+                       updateWatchLink( $link, action, 'loading' );
 
-                               // Most common ID style
-                               if ( $li.prop( 'id' ) === 'ca-' + otherAction || $li.prop( 'id' ) === 'ca-' + action ) {
-                                       $li.prop( 'id', 'ca-' + otherAction );
-                               }
-                               
-                               // Bug 12395 - update the watch checkbox on edit pages when the
-                               // page is watched or unwatched via the tab.
-                               if ( watchResponse.watched !== undefined ) {
-                                       $( '#wpWatchthis' ).prop( 'checked', true );
-                               } else {
-                                       $( '#wpWatchthis' ).removeProp( 'checked' );
-                               }
-                       },
-                       // Error
-                       function(){             
-
-                               // Reset link to non-loading mode
-                               updateWatchLink( $link, action );
-                               
-                               // Format error message
-                               var cleanTitle = title.replace( /_/g, ' ' );
-                               var link = mw.html.element(
-                                       'a', {
-                                               'href': mw.util.wikiGetlink( title ),
-                                               'title': cleanTitle
-                                       }, cleanTitle
-                               );
-                               var html = mw.msg( 'watcherrortext', link );
-                               
-                               // Report to user about the error
-                               mw.util.jsMessage( html, 'ajaxwatch' );
+                       api = new mw.Api();
+                       api[action](
+                               title,
+                               // Success
+                               function ( watchResponse ) {
+                                       var $li, otherAction;
 
-                       }
-               );
-       });
+                                       otherAction = action === 'watch' ? 'unwatch' : 'watch';
+                                       $li = $link.closest( 'li' );
 
-});
+                                       mw.util.jsMessage( watchResponse.message, 'ajaxwatch' );
+
+                                       // Set link to opposite
+                                       updateWatchLink( $link, otherAction );
+
+                                       // Most common ID style
+                                       if ( $li.prop( 'id' ) === 'ca-' + otherAction || $li.prop( 'id' ) === 'ca-' + action ) {
+                                               $li.prop( 'id', 'ca-' + otherAction );
+                                       }
+
+                                       // Bug 12395 - update the watch checkbox on edit pages when the
+                                       // page is watched or unwatched via the tab.
+                                       if ( watchResponse.watched !== undefined ) {
+                                               $( '#wpWatchthis' ).prop( 'checked', true );
+                                       } else {
+                                               $( '#wpWatchthis' ).removeProp( 'checked' );
+                                       }
+                               },
+                               // Error
+                               function () {
+                                       var cleanTitle, html, link;
+
+                                       // Reset link to non-loading mode
+                                       updateWatchLink( $link, action );
+
+                                       // Format error message
+                                       cleanTitle = title.replace( /_/g, ' ' );
+                                       link = mw.html.element(
+                                               'a', {
+                                                       href: mw.util.wikiGetlink( title ),
+                                                       title: cleanTitle
+                                               }, cleanTitle
+                                       );
+                                       html = mw.msg( 'watcherrortext', link );
+
+                                       // Report to user about the error
+                                       mw.util.jsMessage( html, 'ajaxwatch' );
+
+                               }
+                       );
+               });
+       });
 
-})( jQuery, mediaWiki );
+}( jQuery, mediaWiki ) );
index 8ed6832..302a2d3 100644 (file)
@@ -4,47 +4,51 @@
  */
 ( function( $, mw ) {
 
+       /**
+        * @context {mw.Api}
+        */
+       function doWatchInternal( page, success, err, addParams ) {
+               var params = {
+                       action: 'watch',
+                       title: String( page ),
+                       token: mw.user.tokens.get( 'watchToken' ),
+                       uselang: mw.config.get( 'wgUserLanguage' )
+               };
+               function ok( data ) {
+                       success( data.watch );
+               }
+               if ( addParams ) {
+                       $.extend( params, addParams );
+               }
+               return this.post( params, { ok: ok, err: err } );
+       }
+
        $.extend( mw.Api.prototype, {
                /**
                 * Convinience method for 'action=watch'.
                 *
                 * @param page {String|mw.Title} Full page name or instance of mw.Title
-                * @param success {Function} callback to which the watch object will be passed
-                * watch object contains 'title' (full page name), 'watched' (boolean) and
+                * @param success {Function} Callback to which the watch object will be passed.
+                * Watch object contains properties 'title' (full pagename), 'watched' (boolean) and
                 * 'message' (parsed HTML of the 'addedwatchtext' message).
-                * @param _unwatch {Boolean} Internally used to re-use this logic for unwatch(),
-                * do not use outside this module.
-                * @param err {Function} callback if error (optional)
+                * @param err {Function} Error callback (optional)
                 * @return {jqXHR}
                 */
-               watch: function( page, success, err, _unwatch ) {
-                       var params, ok;
-                       params = {
-                               action: 'watch',
-                               title: String( page ),
-                               token: mw.user.tokens.get( 'watchToken' ),
-                               uselang: mw.config.get( 'wgUserLanguage' )
-                       };
-                       if ( _unwatch ) {
-                               params.unwatch = 1;
-                       }
-                       ok = function( data ) {
-                               success( data.watch );
-                       };
-                       return this.post( params, { ok: ok, err: err } );
+               watch: function ( page, success, err ) {
+                       return doWatchInternal.call( this, page, success, err );
                },
                /**
                 * Convinience method for 'action=watch&unwatch=1'.
                 *
                 * @param page {String|mw.Title} Full page name or instance of mw.Title
-                * @param success {Function} callback to which the watch object will be passed
-                * watch object contains 'title' (full page name), 'unwatched' (boolean) and
+                * @param success {Function} Callback to which the watch object will be passed.
+                * Watch object contains properties 'title' (full pagename), 'watched' (boolean) and
                 * 'message' (parsed HTML of the 'removedwatchtext' message).
-                * @param err {Function} callback if error (optional)
+                * @param err {Function} Error callback (optional)
                 * @return {jqXHR}
                 */
-               unwatch: function( page, success, err ) {
-                       return this.watch( page, success, err, true );
+               unwatch: function ( page, success, err ) {
+                       return doWatchInternal.call( this, page, success, err, { unwatch: 1 } );
                }
 
        } );
index 501a267..d1b51a6 100644 (file)
@@ -72,7 +72,7 @@ class VectorTemplate extends BaseTemplate {
                $nav = $this->data['content_navigation'];
 
                if ( $wgVectorUseIconWatch ) {
-                       $mode = $this->getSkin()->getTitle()->userIsWatching() ? 'unwatch' : 'watch';
+                       $mode = $this->getSkin()->getRelevantTitle()->userIsWatching() ? 'unwatch' : 'watch';
                        if ( isset( $nav['actions'][$mode] ) ) {
                                $nav['views'][$mode] = $nav['actions'][$mode];
                                $nav['views'][$mode]['class'] = rtrim( 'icon ' . $nav['views'][$mode]['class'], ' ' );
index 7865ed2..fbf271c 100644 (file)
@@ -1,4 +1,7 @@
 <?php
+/**
+ * @group Database
+ */
 class RecentChangeTest extends MediaWikiTestCase {
        protected $title;
        protected $target;
index eb0d67d..6afc7e5 100644 (file)
--- a/thumb.php
+++ b/thumb.php
@@ -8,7 +8,7 @@
  */
 define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
 if ( isset( $_SERVER['MW_COMPILED'] ) ) {
-       require( 'phase3/includes/WebStart.php' );
+       require( 'core/includes/WebStart.php' );
 } else {
        require( dirname( __FILE__ ) . '/includes/WebStart.php' );
 }