Merge "Introduce Special:RedirectExternal"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 17 Oct 2018 22:12:52 +0000 (22:12 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 17 Oct 2018 22:12:52 +0000 (22:12 +0000)
autoload.php
includes/specialpage/SpecialPageFactory.php
includes/specials/SpecialRedirectExternal.php [new file with mode: 0644]
languages/i18n/en.json
languages/i18n/qqq.json
languages/messages/MessagesEn.php
tests/phpunit/includes/specials/SpecialRedirectExternalTest.php [new file with mode: 0644]

index 3e6b4a2..f8fc6b2 100644 (file)
@@ -1396,6 +1396,7 @@ $wgAutoloadLocalClasses = [
        'SpecialRecentChanges' => __DIR__ . '/includes/specials/SpecialRecentchanges.php',
        'SpecialRecentChangesLinked' => __DIR__ . '/includes/specials/SpecialRecentchangeslinked.php',
        'SpecialRedirect' => __DIR__ . '/includes/specials/SpecialRedirect.php',
+       'SpecialRedirectExternal' => __DIR__ . '/includes/specials/SpecialRedirectExternal.php',
        'SpecialRedirectToSpecial' => __DIR__ . '/includes/specialpage/RedirectSpecialPage.php',
        'SpecialRemoveCredentials' => __DIR__ . '/includes/specials/SpecialRemoveCredentials.php',
        'SpecialResetTokens' => __DIR__ . '/includes/specials/SpecialResetTokens.php',
index 013ceb2..f29d265 100644 (file)
@@ -202,6 +202,7 @@ class SpecialPageFactory {
                'AllMyUploads' => \SpecialAllMyUploads::class,
                'PermanentLink' => \SpecialPermanentLink::class,
                'Redirect' => \SpecialRedirect::class,
+               'RedirectExternal' => \SpecialRedirectExternal::class,
                'Revisiondelete' => \SpecialRevisionDelete::class,
                'RunJobs' => \SpecialRunJobs::class,
                'Specialpages' => \SpecialSpecialpages::class,
diff --git a/includes/specials/SpecialRedirectExternal.php b/includes/specials/SpecialRedirectExternal.php
new file mode 100644 (file)
index 0000000..41a03ed
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * Implements Special:RedirectExternal.
+ *
+ * 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 SpecialPage
+ */
+
+/**
+ * An unlisted special page that accepts a URL as the first argument, and redirects the user to
+ * that page. Example: Special:Redirect/https://mediawiki.org
+ *
+ * At the moment, this is intended to be used by the GrowthExperiments project in order
+ * to track outbound visits to certain external links. But it could be extended in the future to
+ * provide parameters for showing a message to the user before redirecting, or explicitly requiring
+ * a user to click on the link. This can help improve security when users follow on-wiki links to
+ * off-wiki sites.
+ */
+class SpecialRedirectExternal extends UnlistedSpecialPage {
+
+       public function __construct() {
+               parent::__construct( 'RedirectExternal' );
+       }
+
+       /**
+        * @param string $url
+        * @return bool
+        * @throws HttpError
+        */
+       public function execute( $url = '' ) {
+               $dispatch = $this->dispatch( $url );
+               if ( $dispatch->getStatusValue()->isGood() ) {
+                       $this->getOutput()->redirect( $url );
+                       return true;
+               }
+               throw new HttpError( 400, $dispatch->getMessage() );
+       }
+
+       /**
+        * @param string $url
+        * @return Status
+        */
+       public function dispatch( $url ) {
+               if ( !$url ) {
+                       return Status::newFatal( 'redirectexternal-no-url' );
+               }
+               $url = filter_var( $url, FILTER_SANITIZE_URL );
+               if ( !filter_var( $url, FILTER_VALIDATE_URL ) ) {
+                       return Status::newFatal( 'redirectexternal-invalid-url', $url );
+               }
+               return Status::newGood();
+       }
+}
index ea63054..3ce047c 100644 (file)
        "lag-warn-normal": "Changes newer than $1 {{PLURAL:$1|second|seconds}} may not be shown in this list.",
        "lag-warn-high": "Due to high database server lag, changes newer than $1 {{PLURAL:$1|second|seconds}} may not be shown in this list.",
        "editwatchlist-summary": "",
+       "redirectexternal-summary":  "",
+       "redirectexternal-invalid-url": "$1 is not a valid URL",
+       "redirectexternal-no-url":  "No argument was provided to Special:RedirectExternal",
        "watchlistedit-normal-title": "Edit watchlist",
        "watchlistedit-normal-legend": "Remove titles from watchlist",
        "watchlistedit-normal-explain": "Titles on your watchlist are shown below.\nTo remove a title, check the box next to it, and click \"{{int:Watchlistedit-normal-submit}}\".\nYou can also [[Special:EditWatchlist/raw|edit the raw list]].",
index a17cfca..0c50f40 100644 (file)
        "nimagelinks": "Used on [[Special:MostLinkedFiles]] to indicate how often a specific file is used.\n\nParameters:\n* $1 - number of pages\nSee also:\n* {{msg-mw|Ntransclusions}}",
        "ntransclusions": "Used on [[Special:MostTranscludedPages]] to indicate how often a template is in use.\n\nParameters:\n* $1 - number of pages\nSee also:\n* {{msg-mw|Nimagelinks}}",
        "specialpage-empty": "Used on a special page when there is no data. For example on [[Special:Unusedimages]] when all images are used.",
+       "redirectexternal-summary":  "{{doc-specialpagessummary|redirectexternal}}",
+       "redirectexternal-invalid-url": "Error message shown when the argument to [[Special:RedirectExternal]] is an invalid URL.\n\nParameters:\n* $1 - The first URL argument to Special:RedirectExternal",
+       "redirectexternal-no-url": "Error message shown when no argument is supplied to [[Special:RedirectExternal]]",
        "lonelypages": "{{doc-special|LonelyPages}}",
        "lonelypages-summary": "{{doc-specialpagesummary|lonelypages}}",
        "lonelypagestext": "Text displayed in [[Special:LonelyPages]]",
index 7a7370f..e78f003 100644 (file)
@@ -482,6 +482,7 @@ $specialPageAliases = [
        'Recentchanges'             => [ 'RecentChanges' ],
        'Recentchangeslinked'       => [ 'RecentChangesLinked', 'RelatedChanges' ],
        'Redirect'                  => [ 'Redirect' ],
+       'RedirectExternal'          => [ 'RedirectExternal' ],
        'RemoveCredentials'         => [ 'RemoveCredentials' ],
        'ResetTokens'               => [ 'ResetTokens' ],
        'Revisiondelete'            => [ 'RevisionDelete' ],
diff --git a/tests/phpunit/includes/specials/SpecialRedirectExternalTest.php b/tests/phpunit/includes/specials/SpecialRedirectExternalTest.php
new file mode 100644 (file)
index 0000000..ab5b2cd
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * Test class for SpecialRedirectExternal class.
+ *
+ * @license GPL-2.0-or-later
+ */
+class SpecialRedirectExternalTest extends MediaWikiTestCase {
+
+       /**
+        * @dataProvider provideDispatch
+        * @covers SpecialRedirectExternal::dispatch
+        * @covers SpecialRedirectExternal
+        * @param $url
+        * @param $expectedStatus
+        */
+       public function testDispatch( $url, $expectedStatus ) {
+               $page = new SpecialRedirectExternal();
+               $this->assertEquals( $expectedStatus, $page->dispatch( $url )->isGood() );
+       }
+
+       /**
+        * @throws HttpError
+        * @expectedException HttpError
+        * @expectedExceptionMessage asdf is not a valid URL
+        * @covers SpecialRedirectExternal::execute
+        */
+       public function testExecuteInvalidUrl() {
+               $page = new SpecialRedirectExternal();
+               $page->execute( 'asdf' );
+       }
+
+       /**
+        * @throws HttpError
+        * @covers SpecialRedirectExternal::execute
+        */
+       public function testValidUrl() {
+               $page = new SpecialRedirectExternal();
+               $this->assertTrue( $page->execute( 'https://www.mediawiki.org' ) );
+       }
+
+       public static function provideDispatch() {
+               return [
+                       [ 'asdf', false ],
+                       [ null, false ],
+                       [ 'https://www.mediawiki.org?test=1', true ],
+               ];
+       }
+}