Merge "Added heartbeat for pingback."
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 26 Mar 2018 18:21:13 +0000 (18:21 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 26 Mar 2018 18:21:13 +0000 (18:21 +0000)
14 files changed:
RELEASE-NOTES-1.31
autoload.php
includes/Title.php
includes/api/ApiBlock.php
includes/changetags/ChangeTags.php
includes/htmlform/HTMLForm.php
includes/htmlform/VFormHTMLForm.php
includes/installer/PostgresUpdater.php
includes/profiler/ProfileSection.php [deleted file]
includes/resourceloader/ResourceLoaderContext.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/specialpage/SpecialPageFactory.php
tests/parser/ParserTestRunner.php
tests/phpunit/includes/api/ApiBlockTest.php

index 4181a2c..bc142ae 100644 (file)
@@ -295,6 +295,8 @@ changes to languages because of Phabricator reports.
   * StripState::merge()
 * The "free" CSS class is now only applied to unbracketed URLs in wikitext. Links
   written using square brackets will get the class "text" not "free".
+* SpecialPageFactory::getList(), deprecated in 1.24, has been removed. You can
+  use ::getNames() instead.
 * OpenSearch::getOpenSearchTemplate(), deprecated in 1.25, has been removed. You
   can use ApiOpenSearch::getOpenSearchTemplate() instead.
 * The global function wfBaseConvert, deprecated in 1.27, has been removed. Use
@@ -311,6 +313,13 @@ changes to languages because of Phabricator reports.
 * The global function wfOutputHandler() was removed, use the its replacement
   MediaWiki\OutputHandler::handle() instead. The global function was only sometimes defined.
   Its replacement is always available via the autoloader.
+* ChangeTags::listExtensionActivatedTags and ::listExtensionDefinedTags, deprecated
+  in 1.28, have been removed.  Use ::listSoftwareActivatedTags() and
+  ::listSoftwareDefinedTags() instead.
+* Title::getTitleInvalidRegex(), deprecated in 1.25, has been removed. You
+  can use MediaWikiTitleCodec::getTitleInvalidRegex() instead.
+* HTMLForm & VFormHTMLForm::isVForm(), deprecated in 1.25, have been removed.
+* The ProfileSection class, deprecated in 1.25 and unused, has been removed.
 
 == Compatibility ==
 MediaWiki 1.31 requires PHP 5.5.9 or later. Although HHVM 3.18.5 or later is supported,
index 0b0c288..126362c 100644 (file)
@@ -1185,7 +1185,6 @@ $wgAutoloadLocalClasses = [
        'Preprocessor_Hash' => __DIR__ . '/includes/parser/Preprocessor_Hash.php',
        'ProcessCacheLRU' => __DIR__ . '/includes/libs/ProcessCacheLRU.php',
        'Processor' => __DIR__ . '/includes/registration/Processor.php',
-       'ProfileSection' => __DIR__ . '/includes/profiler/ProfileSection.php',
        'Profiler' => __DIR__ . '/includes/profiler/Profiler.php',
        'ProfilerOutput' => __DIR__ . '/includes/profiler/output/ProfilerOutput.php',
        'ProfilerOutputDb' => __DIR__ . '/includes/profiler/output/ProfilerOutputDb.php',
index 66aadeb..8dda01f 100644 (file)
@@ -625,20 +625,6 @@ class Title implements LinkTarget {
                return $wgLegalTitleChars;
        }
 
-       /**
-        * Returns a simple regex that will match on characters and sequences invalid in titles.
-        * Note that this doesn't pick up many things that could be wrong with titles, but that
-        * replacing this regex with something valid will make many titles valid.
-        *
-        * @deprecated since 1.25, use MediaWikiTitleCodec::getTitleInvalidRegex() instead
-        *
-        * @return string Regex string
-        */
-       static function getTitleInvalidRegex() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return MediaWikiTitleCodec::getTitleInvalidRegex();
-       }
-
        /**
         * Utility method for converting a character sequence from bytes to Unicode.
         *
index f4aea98..8f40283 100644 (file)
@@ -124,8 +124,8 @@ class ApiBlock extends ApiBase {
                        $res['id'] = $block->getId();
                } else {
                        # should be unreachable
-                       $res['expiry'] = '';
-                       $res['id'] = '';
+                       $res['expiry'] = ''; // @codeCoverageIgnore
+                       $res['id'] = ''; // @codeCoverageIgnore
                }
 
                $res['reason'] = $params['reason'];
index b30b82d..5b6088d 100644 (file)
@@ -1295,20 +1295,9 @@ class ChangeTags {
                );
        }
 
-       /**
-        * @see listSoftwareActivatedTags
-        * @deprecated since 1.28 call listSoftwareActivatedTags directly
-        * @return array
-        */
-       public static function listExtensionActivatedTags() {
-               wfDeprecated( __METHOD__, '1.28' );
-               return self::listSoftwareActivatedTags();
-       }
-
        /**
         * Basically lists defined tags which count even if they aren't applied to anything.
-        * It returns a union of the results of listExplicitlyDefinedTags() and
-        * listExtensionDefinedTags().
+        * It returns a union of the results of listExplicitlyDefinedTags()
         *
         * @return string[] Array of strings: tags
         */
@@ -1385,18 +1374,6 @@ class ChangeTags {
                );
        }
 
-       /**
-        * Call listSoftwareDefinedTags directly
-        *
-        * @see listSoftwareDefinedTags
-        * @deprecated since 1.28
-        * @return array
-        */
-       public static function listExtensionDefinedTags() {
-               wfDeprecated( __METHOD__, '1.28' );
-               return self::listSoftwareDefinedTags();
-       }
-
        /**
         * Invalidates the short-term cache of defined tags used by the
         * list*DefinedTags functions, as well as the tag statistics cache.
index 9b58f92..af1743e 100644 (file)
@@ -430,17 +430,6 @@ class HTMLForm extends ContextSource {
                return $this->displayFormat;
        }
 
-       /**
-        * Test if displayFormat is 'vform'
-        * @since 1.22
-        * @deprecated since 1.25
-        * @return bool
-        */
-       public function isVForm() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return false;
-       }
-
        /**
         * Get the HTMLFormField subclass for this descriptor.
         *
index 325526b..7bf5f9e 100644 (file)
@@ -37,11 +37,6 @@ class VFormHTMLForm extends HTMLForm {
         */
        protected $displayFormat = 'vform';
 
-       public function isVForm() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return true;
-       }
-
        public static function loadInputFromParameters( $fieldname, $descriptor,
                HTMLForm $parent = null
        ) {
index 48f47f5..ba6e968 100644 (file)
@@ -294,7 +294,7 @@ class PostgresUpdater extends DatabaseUpdater {
                                [ 'log_timestamp', 'timestamptz_ops', 'btree', 0 ],
                        ],
                        'CREATE INDEX "logging_times" ON "logging" USING "btree" ("log_timestamp")' ],
-                       [ 'dropIndex', 'oldimage', 'oi_name' ],
+                       [ 'dropPgIndex', 'oldimage', 'oi_name' ],
                        [ 'checkIndex', 'oi_name_archive_name', [
                                [ 'oi_name', 'text_ops', 'btree', 0 ],
                                [ 'oi_archive_name', 'text_ops', 'btree', 0 ],
@@ -353,7 +353,7 @@ class PostgresUpdater extends DatabaseUpdater {
                        [ 'checkOiNameConstraint' ],
                        [ 'checkPageDeletedTrigger' ],
                        [ 'checkRevUserFkey' ],
-                       [ 'dropIndex', 'ipblocks', 'ipb_address' ],
+                       [ 'dropPgIndex', 'ipblocks', 'ipb_address' ],
                        [ 'checkIndex', 'ipb_address_unique', [
                                [ 'ipb_address', 'text_ops', 'btree', 0 ],
                                [ 'ipb_user', 'int4_ops', 'btree', 0 ],
@@ -1060,7 +1060,7 @@ END;
                }
        }
 
-       protected function dropIndex( $table, $index, $patch = '', $fullpath = false ) {
+       protected function dropPgIndex( $table, $index ) {
                if ( $this->db->indexExists( $table, $index ) ) {
                        $this->output( "Dropping obsolete index '$index'\n" );
                        $this->db->query( "DROP INDEX \"" . $index . "\"" );
diff --git a/includes/profiler/ProfileSection.php b/includes/profiler/ProfileSection.php
deleted file mode 100644 (file)
index 124e2d3..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-/**
- * Function scope profiling assistant
- *
- * 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
- *
- * @file
- * @ingroup Profiler
- */
-
-/**
- * Class for handling function-scope profiling
- *
- * @since 1.22
- * @deprecated since 1.25 No-op now
- */
-class ProfileSection {
-       /**
-        * Begin profiling of a function and return an object that ends profiling
-        * of the function when that object leaves scope. As long as the object is
-        * not specifically linked to other objects, it will fall out of scope at
-        * the same moment that the function to be profiled terminates.
-        *
-        * This is typically called like:
-        * @code $section = new ProfileSection( __METHOD__ ); @endcode
-        *
-        * @param string $name Name of the function to profile
-        */
-       public function __construct( $name ) {
-               wfDeprecated( __CLASS__, '1.25' );
-       }
-}
index 370046a..c4e9884 100644 (file)
@@ -63,12 +63,8 @@ class ResourceLoaderContext implements MessageLocalizer {
                $this->request = $request;
                $this->logger = $resourceLoader->getLogger();
 
-               // Future developers: Avoid use of getVal() in this class, which performs
-               // expensive UTF normalisation by default. Use getRawVal() instead.
-               // Values here are either one of a finite number of internal IDs,
-               // or previously-stored user input (e.g. titles, user names) that were passed
-               // to this endpoint by ResourceLoader itself from the canonical value.
-               // Values do not come directly from user input and need not match.
+               // Future developers: Use WebRequest::getRawVal() instead getVal().
+               // The getVal() method performs slow Language+UTF logic. (f303bb9360)
 
                // List of modules
                $modules = $request->getRawVal( 'modules' );
index ae9520d..681e8dc 100644 (file)
@@ -206,7 +206,9 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
         */
        public function getModuleRegistrations( ResourceLoaderContext $context ) {
                $resourceLoader = $context->getResourceLoader();
-               $target = $context->getRequest()->getVal( 'target', 'desktop' );
+               // Future developers: Use WebRequest::getRawVal() instead getVal().
+               // The getVal() method performs slow Language+UTF logic. (f303bb9360)
+               $target = $context->getRequest()->getRawVal( 'target', 'desktop' );
                // Bypass target filter if this request is Special:JavaScriptTest.
                // To prevent misuse in production, this is only allowed if testing is enabled server-side.
                $byPassTargetFilter = $this->getConfig()->get( 'EnableJavaScriptTest' ) && $target === 'test';
index 9469e69..fdf4d52 100644 (file)
@@ -212,17 +212,6 @@ class SpecialPageFactory {
                return array_keys( self::getPageList() );
        }
 
-       /**
-        * Get the special page list as an array
-        *
-        * @deprecated since 1.24, use getNames() instead.
-        * @return array
-        */
-       public static function getList() {
-               wfDeprecated( __FUNCTION__, '1.24' );
-               return self::getPageList();
-       }
-
        /**
         * Get the special page list as an array
         *
index f0c815f..28335ec 100644 (file)
@@ -1108,6 +1108,7 @@ class ParserTestRunner {
 
                // Set content language. This invalidates the magic word cache and title services
                $lang = Language::factory( $langCode );
+               $lang->resetNamespaces();
                $setup['wgContLang'] = $lang;
                $reset = function () {
                        MagicWord::clearCache();
index 832a113..c456e9a 100644 (file)
@@ -8,13 +8,17 @@
  * @covers ApiBlock
  */
 class ApiBlockTest extends ApiTestCase {
+       protected $mUser = null;
+
        protected function setUp() {
                parent::setUp();
                $this->doLogin();
+
+               $this->mUser = $this->getMutableTestUser()->getUser();
        }
 
        protected function tearDown() {
-               $block = Block::newFromTarget( 'UTApiBlockee' );
+               $block = Block::newFromTarget( $this->mUser->getName() );
                if ( !is_null( $block ) ) {
                        $block->delete();
                }
@@ -25,80 +29,192 @@ class ApiBlockTest extends ApiTestCase {
                return $this->getTokenList( self::$users['sysop'] );
        }
 
-       function addDBDataOnce() {
-               $user = User::newFromName( 'UTApiBlockee' );
-
-               if ( $user->getId() == 0 ) {
-                       $user->addToDatabase();
-                       TestUser::setPasswordForUser( $user, 'UTApiBlockeePassword' );
-
-                       $user->saveSettings();
-               }
-       }
-
        /**
-        * This test has probably always been broken and use an invalid token
-        * Bug tracking brokenness is https://phabricator.wikimedia.org/T37646
-        *
-        * Root cause is https://gerrit.wikimedia.org/r/3434
-        * Which made the Block/Unblock API to actually verify the token
-        * previously always considered valid (T36212).
+        * @param array $extraParams Extra API parameters to pass to doApiRequest
+        * @param User  $blocker     User to do the blocking, null to pick
+        *                           arbitrarily
         */
-       public function testMakeNormalBlock() {
-               $tokens = $this->getTokens();
+       private function doBlock( array $extraParams = [], User $blocker = null ) {
+               if ( $blocker === null ) {
+                       $blocker = self::$users['sysop']->getUser();
+               }
 
-               $user = User::newFromName( 'UTApiBlockee' );
+               $tokens = $this->getTokens();
 
-               if ( !$user->getId() ) {
-                       $this->markTestIncomplete( "The user UTApiBlockee does not exist" );
-               }
+               $this->assertNotNull( $this->mUser, 'Sanity check' );
+               $this->assertNotSame( 0, $this->mUser->getId(), 'Sanity check' );
 
-               if ( !array_key_exists( 'blocktoken', $tokens ) ) {
-                       $this->markTestIncomplete( "No block token found" );
-               }
+               $this->assertArrayHasKey( 'blocktoken', $tokens, 'Sanity check' );
 
-               $this->doApiRequest( [
+               $params = [
                        'action' => 'block',
-                       'user' => 'UTApiBlockee',
+                       'user' => $this->mUser->getName(),
                        'reason' => 'Some reason',
-                       'token' => $tokens['blocktoken'] ], null, false, self::$users['sysop']->getUser() );
+                       'token' => $tokens['blocktoken'],
+               ];
+               if ( array_key_exists( 'userid', $extraParams ) ) {
+                       // Make sure we don't have both user and userid
+                       unset( $params['user'] );
+               }
+               $ret = $this->doApiRequest( array_merge( $params, $extraParams ), null,
+                       false, $blocker );
 
-               $block = Block::newFromTarget( 'UTApiBlockee' );
+               $block = Block::newFromTarget( $this->mUser->getName() );
 
                $this->assertTrue( !is_null( $block ), 'Block is valid' );
 
-               $this->assertEquals( 'UTApiBlockee', (string)$block->getTarget() );
-               $this->assertEquals( 'Some reason', $block->mReason );
-               $this->assertEquals( 'infinity', $block->mExpiry );
+               $this->assertSame( $this->mUser->getName(), (string)$block->getTarget() );
+               $this->assertSame( 'Some reason', $block->mReason );
+
+               return $ret;
+       }
+
+       /**
+        * Block by username
+        */
+       public function testNormalBlock() {
+               $this->doBlock();
        }
 
        /**
         * Block by user ID
         */
-       public function testMakeNormalBlockId() {
-               $tokens = $this->getTokens();
-               $user = User::newFromName( 'UTApiBlockee' );
+       public function testBlockById() {
+               $this->doBlock( [ 'userid' => $this->mUser->getId() ] );
+       }
 
-               if ( !$user->getId() ) {
-                       $this->markTestIncomplete( "The user UTApiBlockee does not exist." );
-               }
+       /**
+        * A blocked user can't block
+        */
+       public function testBlockByBlockedUser() {
+               $this->setExpectedException( ApiUsageException::class,
+                       'You cannot block or unblock other users because you are yourself blocked.' );
+
+               $blocked = $this->getMutableTestUser( [ 'sysop' ] )->getUser();
+               $block = new Block( [
+                       'address' => $blocked->getName(),
+                       'by' => self::$users['sysop']->getUser()->getId(),
+                       'reason' => 'Capriciousness',
+                       'timestamp' => '19370101000000',
+                       'expiry' => 'infinity',
+               ] );
+               $block->insert();
+
+               $this->doBlock( [], $blocked );
+       }
 
-               if ( !array_key_exists( 'blocktoken', $tokens ) ) {
-                       $this->markTestIncomplete( "No block token found" );
-               }
+       public function testBlockOfNonexistentUser() {
+               $this->setExpectedException( ApiUsageException::class,
+                       'There is no user by the name "Nonexistent". Check your spelling.' );
 
-               $data = $this->doApiRequest( [
-                       'action' => 'block',
-                       'userid' => $user->getId(),
-                       'reason' => 'Some reason',
-                       'token' => $tokens['blocktoken'] ], null, false, self::$users['sysop']->getUser() );
+               $this->doBlock( [ 'user' => 'Nonexistent' ] );
+       }
+
+       public function testBlockOfNonexistentUserId() {
+               $id = 948206325;
+               $this->setExpectedException( ApiUsageException::class,
+                       "There is no user with ID $id." );
+
+               $this->assertFalse( User::whoIs( $id ), 'Sanity check' );
+
+               $this->doBlock( [ 'userid' => $id ] );
+       }
+
+       public function testBlockWithTag() {
+               ChangeTags::defineTag( 'custom tag' );
+
+               $this->doBlock( [ 'tags' => 'custom tag' ] );
+
+               $dbw = wfGetDB( DB_MASTER );
+               $this->assertSame( 'custom tag', $dbw->selectField(
+                       [ 'change_tag', 'logging' ],
+                       'ct_tag',
+                       [ 'log_type' => 'block' ],
+                       __METHOD__,
+                       [],
+                       [ 'change_tag' => [ 'INNER JOIN', 'ct_log_id = log_id' ] ]
+               ) );
+       }
+
+       public function testBlockWithProhibitedTag() {
+               $this->setExpectedException( ApiUsageException::class,
+                       'You do not have permission to apply change tags along with your changes.' );
+
+               ChangeTags::defineTag( 'custom tag' );
+
+               $this->setMwGlobals( 'wgRevokePermissions',
+                       [ 'user' => [ 'applychangetags' => true ] ] );
+
+               $this->doBlock( [ 'tags' => 'custom tag' ] );
+       }
+
+       public function testBlockWithHide() {
+               global $wgGroupPermissions;
+               $newPermissions = $wgGroupPermissions['sysop'];
+               $newPermissions['hideuser'] = true;
+               $this->mergeMwGlobalArrayValue( 'wgGroupPermissions',
+                       [ 'sysop' => $newPermissions ] );
+
+               $res = $this->doBlock( [ 'hidename' => '' ] );
+
+               $dbw = wfGetDB( DB_MASTER );
+               $this->assertSame( '1', $dbw->selectField(
+                       'ipblocks',
+                       'ipb_deleted',
+                       [ 'ipb_id' => $res[0]['block']['id'] ],
+                       __METHOD__
+               ) );
+       }
+
+       public function testBlockWithProhibitedHide() {
+               $this->setExpectedException( ApiUsageException::class,
+                       "You don't have permission to hide user names from the block log." );
+
+               $this->doBlock( [ 'hidename' => '' ] );
+       }
+
+       public function testBlockWithEmailBlock() {
+               $res = $this->doBlock( [ 'noemail' => '' ] );
+
+               $dbw = wfGetDB( DB_MASTER );
+               $this->assertSame( '1', $dbw->selectField(
+                       'ipblocks',
+                       'ipb_block_email',
+                       [ 'ipb_id' => $res[0]['block']['id'] ],
+                       __METHOD__
+               ) );
+       }
+
+       public function testBlockWithProhibitedEmailBlock() {
+               $this->setExpectedException( ApiUsageException::class,
+                       "You don't have permission to block users from sending email through the wiki." );
+
+               $this->setMwGlobals( 'wgRevokePermissions',
+                       [ 'sysop' => [ 'blockemail' => true ] ] );
+
+               $this->doBlock( [ 'noemail' => '' ] );
+       }
+
+       public function testBlockWithExpiry() {
+               $res = $this->doBlock( [ 'expiry' => '1 day' ] );
+
+               $dbw = wfGetDB( DB_MASTER );
+               $expiry = $dbw->selectField(
+                       'ipblocks',
+                       'ipb_expiry',
+                       [ 'ipb_id' => $res[0]['block']['id'] ],
+                       __METHOD__
+               );
+
+               // Allow flakiness up to one second
+               $this->assertLessThanOrEqual( 1,
+                       abs( wfTimestamp( TS_UNIX, $expiry ) - ( time() + 86400 ) ) );
+       }
 
-               $block = Block::newFromTarget( 'UTApiBlockee' );
+       public function testBlockWithInvalidExpiry() {
+               $this->setExpectedException( ApiUsageException::class, "Expiry time invalid." );
 
-               $this->assertTrue( !is_null( $block ), 'Block is valid.' );
-               $this->assertEquals( 'UTApiBlockee', (string)$block->getTarget() );
-               $this->assertEquals( 'Some reason', $block->mReason );
-               $this->assertEquals( 'infinity', $block->mExpiry );
+               $this->doBlock( [ 'expiry' => '' ] );
        }
 
        /**
@@ -109,7 +225,7 @@ class ApiBlockTest extends ApiTestCase {
                $this->doApiRequest(
                        [
                                'action' => 'block',
-                               'user' => 'UTApiBlockee',
+                               'user' => $this->mUser->getName(),
                                'reason' => 'Some reason',
                        ],
                        null,