Krinkle has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/156533

Change subject: Add a script to delete manual user global.js/css pages
......................................................................

Add a script to delete manual user global.js/css pages

On Wikimedia wikis, users have created local common.js/css
pages that load their "global.js/css" pages from Meta-Wiki.
This script will check if those pages match a conservative
regex, and only have one revision. If so, it deletes them.

Bug: 68933
Change-Id: I4b3218ccbbc7754a89846f5fec4d288d86ed46bf
(cherry picked from commit dbdcceb182da5efbc3fe4013c26503ea721d4ea1)
---
M i18n/core/en.json
M i18n/core/qqq.json
A removeOldManualUserPages.php
A tests/RemoveOldManualUserPagesTest.php
4 files changed, 309 insertions(+), 2 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/GlobalCssJs 
refs/changes/33/156533/1

diff --git a/i18n/core/en.json b/i18n/core/en.json
index ea9749c..f4537c9 100644
--- a/i18n/core/en.json
+++ b/i18n/core/en.json
@@ -10,5 +10,7 @@
        "globalcssjs-warning-css": "Any CSS added to this page will be loaded 
on all wikis where you have an account.",
        "globalcssjs-custom-css-js": "Shared CSS/JavaScript for all wikis:",
        "globalcssjs-custom-js": "Custom JavaScript",
-       "globalcssjs-custom-css": "Custom CSS"
+       "globalcssjs-custom-css": "Custom CSS",
+       "globalcssjs-delete-css": "Deleting CSS page on user request due to 
deployment of [[mw:Help:Extension:GlobalCssJs]]",
+       "globalcssjs-delete-js": "Deleting JavaScript page on user request due 
to deployment of [[mw:Help:Extension:GlobalCssJs]]"
 }
diff --git a/i18n/core/qqq.json b/i18n/core/qqq.json
index 41457e2..1dde05f 100644
--- a/i18n/core/qqq.json
+++ b/i18n/core/qqq.json
@@ -10,5 +10,7 @@
        "globalcssjs-warning-css": "Notice on top of edit window",
        "globalcssjs-custom-css-js": "Help message next to links to a user's 
global.js/global.css pages",
        "globalcssjs-custom-js": "Link text for link to a user's global.js 
page.\n{{Identical|Custom JavaScript}}",
-       "globalcssjs-custom-css": "Link text for a link to a user's global.css 
page.\n{{Identical|Custom CSS}}"
+       "globalcssjs-custom-css": "Link text for a link to a user's global.css 
page.\n{{Identical|Custom CSS}}",
+       "globalcssjs-delete-css": "Used as a deletion summary when deleting a 
user's CSS page",
+       "globalcssjs-delete-js": "Used as a deletion summary when deleting a 
user's JavaScript page"
 }
diff --git a/removeOldManualUserPages.php b/removeOldManualUserPages.php
new file mode 100644
index 0000000..9f11ef4
--- /dev/null
+++ b/removeOldManualUserPages.php
@@ -0,0 +1,169 @@
+<?php
+
+$IP = getenv( 'MW_INSTALL_PATH' );
+if ( $IP === false ) {
+       $IP = __DIR__ . '/../../..';
+}
+require_once "$IP/maintenance/Maintenance.php";
+
+/**
+ * Script to remove manually created user .js and .css pages
+ * by users. You should run this script on every wiki where the user
+ * has an account.
+ */
+class RemoveOldManualUserPages extends Maintenance {
+
+       public function execute() {
+               $userName = $this->getOption( 'user' );
+               $user = User::newFromName( $userName );
+               if ( !class_exists( 'GlobalCssJsHooks' ) ) {
+                       $this->error( 'The GlobalCssJs extension is not enabled 
on this wiki.', 1 );
+               }
+
+               if ( !$user->getId() || !GlobalCssJsHooks::loadForUser( $user ) 
) {
+                       $this->output( "$userName does not load global modules 
on this wiki.\n" );
+                       return;
+               }
+
+               $skins = array_keys( Skin::getAllowedSkins() );
+               $skins[] = 'common';
+
+               // Batch look up the existence of pages
+               $lb = new LinkBatch();
+               foreach ( $skins as $name ) {
+                       $lb->addObj( $user->getUserPage()->getSubpage( 
"$name.js" ) );
+                       $lb->addObj( $user->getUserPage()->getSubpage( 
"$name.css" ) );
+               }
+               $lb->execute();
+
+               foreach ( $skins as $name ) {
+                       $this->removeJS( $user, $name );
+                       $this->removeCSS( $user, $name );
+               }
+       }
+
+       /**
+        * Generic checks to see if we should work on a title.
+        *
+        * @param Title $title
+        * @return Revision|bool
+        */
+       private function checkTitle( Title $title ) {
+               if ( !$title->exists() ) {
+                       $this->output( "{$title->getPrefixedText()} does not 
exist on this wiki.\n" );
+                       return false;
+               }
+
+
+               $rev = Revision::newFromTitle( $title );
+               if ( $title->getPreviousRevisionID( $rev->getId() ) !== false ) 
{
+                       $this->output( "{$title->getPrefixedText()} has more 
than one revision, skipping.\n" );
+                       return false;
+               }
+
+               return $rev;
+       }
+
+       /**
+        * Returns the domain name of the central wiki
+        * escaped to use in a regex.
+        *
+        * @return string
+        */
+       private function getCentralWikiDomain() {
+               global $wgGlobalCssJsConfig;
+               $rl = new ResourceLoader;
+               $sources = $rl->getSources();
+               // Use api.php instead of load.php because it's more likely to 
be on the same domain
+               $api = $sources[$wgGlobalCssJsConfig['source']]['apiScript'];
+               $parsed = wfParseUrl( $api );
+               return preg_quote( $parsed['host'] );
+       }
+
+       private function deletePage( Title $title, $reason ) {
+               $page = WikiPage::factory( $title );
+               $user = User::newFromName( 'GlobalCssJs migration script' );
+               $errors = array();
+               $page->doDeleteArticleReal( wfMessage( $reason 
)->inContentLanguage()->text(), false, 0, true, $errors, $user );
+               $this->output( "{$title->getPrefixedText()} was deleted.\n" );
+       }
+
+       public function checkCss( $text, $domain, $userName ) {
+               $userName = preg_quote( $userName );
+               preg_match( "/@import 
url\('(https?:)?\/\/$domain\/w\/index\.php\?title=User:$userName\/global\.css&action=raw&ctype=text\/css'\);/",
 $text, $matches );
+               return isset( $matches[0] ) ? $matches[0] === $text : false;
+       }
+
+       private function removeCSS( User $user, $skin ) {
+               $userName = $user->getName();
+               $title = $user->getUserPage()->getSubpage( $skin . '.css' );
+               $rev = $this->checkTitle( $title );
+               if ( !$rev ) {
+                       return;
+               }
+
+               /** @var CssContent $content */
+               $content = $rev->getContent();
+               $text = trim( $content->getNativeData() );
+               $domain = $this->getCentralWikiDomain();
+               if ( !$this->checkCss( $text, $domain, $userName ) ) {
+                       $this->output( "{$title->getPrefixedText()} did not 
match the specified regular expression. Skipping.\n" );
+                       return;
+               }
+
+               // Delete!
+               $this->deletePage( $title, 'globalcssjs-delete-css' );
+       }
+
+       /**
+        * Remove lines that are entirely comments, by checking if they start 
with //
+        * Also get rid of empty lines while we're at it.
+        *
+        * @param string $js
+        * @return string
+        */
+       private function stripComments( $js ) {
+               $exploded = explode( "\n", $js );
+               $new = array();
+               foreach ( $exploded as $line ) {
+                       $trimmed = trim( $line );
+                       if ( $trimmed !== '' && substr( $trimmed, 0, 2) !== 
'//' ) {
+                               $new[] = $line;
+                       }
+               }
+
+               return implode( '\n', $new );
+       }
+
+       public function checkJs( $text, $domain, $userName ) {
+               $text = $this->stripComments( $text );
+               $userName = preg_quote( $userName );
+               preg_match( 
"/(mw\.loader\.load|importScriptURI)\s*\(\s*'(https?:)?\/\/$domain\/w\/index\.php\?title=User:$userName\/global\.js&action=raw&ctype=text\/javascript(&smaxage=\d*?)?(&maxage=\d*?)?'\s*\)\s*;?/",
 $text, $matches );
+               return isset( $matches[0] ) ? $matches[0] === $text : false;
+       }
+
+       private function removeJS( User $user, $skin ) {
+               $userName = $user->getName();
+               $title = $user->getUserPage()->getSubpage( $skin . '.js' );
+               $rev = $this->checkTitle( $title );
+               if ( !$rev ) {
+                       return;
+               }
+
+               /** @var JavaScriptContent $content */
+               $content = $rev->getContent();
+               $text = trim( $content->getNativeData() );
+               $domain = $this->getCentralWikiDomain();
+               if ( !$this->checkJs( $text, $domain, $userName ) ) {
+                       $this->output( "{$title->getPrefixedText()} did not 
match the specified regular expression. Skipping.\n" );
+                       return;
+               }
+
+               // Delete!
+               $this->deletePage( $title, 'globalcssjs-delete-js' );
+       }
+
+}
+
+$maintClass = "RemoveOldManualUserPages";
+require_once( RUN_MAINTENANCE_IF_MAIN );
diff --git a/tests/RemoveOldManualUserPagesTest.php 
b/tests/RemoveOldManualUserPagesTest.php
new file mode 100644
index 0000000..0573366
--- /dev/null
+++ b/tests/RemoveOldManualUserPagesTest.php
@@ -0,0 +1,134 @@
+<?php
+
+class RemoveOldManualUserPagesTest extends MediaWikiTestCase {
+
+       /**
+        * No autoloader for maintenance scripts
+        */
+       private function load() {
+               require_once dirname( __DIR__ ) . 
'/removeOldManualUserPages.php';
+       }
+
+       public static function provideCheckJs() {
+               return array(
+                       array(
+                               
"mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:UserName/global.js&action=raw&ctype=text/javascript');",
+                               true,
+                               'mw.loader.load with a proto-rel link',
+                       ),
+                       array(
+                               
"importScriptURI('//meta.wikimedia.org/w/index.php?title=User:UserName/global.js&action=raw&ctype=text/javascript');",
+                               true,
+                               'using importScriptURI',
+                       ),
+                       array(
+                               
"mw.loader.load('http://meta.wikimedia.org/w/index.php?title=User:UserName/global.js&action=raw&ctype=text/javascript');",
+                               true,
+                               'mw.loader.load with a http:// link',
+                       ),
+                       array(
+                               
"mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:SomeOtherUserName/global.js&action=raw&ctype=text/javascript');",
+                               false,
+                               'Loading a different user\'s global.js',
+                       ),
+                       array(
+                               
"mw.loader.load('//en.wikipedia.org/w/index.php?title=User:UserName/global.js&action=raw&ctype=text/javascript');",
+                               false,
+                               'Loading from a different site',
+                       ),
+                       array(
+                               
"mw.loader.load('//en.wikipedia.org/w/index.php?title=User:UserName/common.js&action=raw&ctype=text/javascript');",
+                               false,
+                               'Loading from a different page',
+                       ),
+                       array(
+                               
"mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:UserName/global.js&action=raw&ctype=text/javascript')",
+                               true,
+                               'No trailing ;',
+                       ),
+                       array(
+                               "mw.loader.load ( 
'//meta.wikimedia.org/w/index.php?title=User:UserName/global.js&action=raw&ctype=text/javascript'
 ) ;",
+                               true,
+                               'Spaces around ( and )',
+                       ),
+                       array(
+                               "//some comment\n//another 
comment\nmw.loader.load('//meta.wikimedia.org/w/index.php?title=User:UserName/global.js&action=raw&ctype=text/javascript');",
+                               true,
+                               'comments before the mw.loader.load call',
+                       ),
+                       array(
+                               
"mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:UserName/global.js&action=raw&ctype=text/javascript');\nsomeOtherJavaScript();",
+                               false,
+                               'page contains some other javascript',
+                       ),
+                       array(
+                               "\n\n//some comment\n\n\n//another 
comment\n\nmw.loader.load('//meta.wikimedia.org/w/index.php?title=User:UserName/global.js&action=raw&ctype=text/javascript');",
+                               true,
+                               'empty lines are also stripped in between 
comments',
+                       ),
+                       array(
+                               
"mw.loader.load('//meta.wikimedia.org/w/index.php?title=User:UserName/global.js&action=raw&ctype=text/javascript&smaxage=86400&maxage=86400');",
+                               true,
+                               '(s)maxage parameters are accepted',
+                       ),
+               );
+       }
+
+       /**
+        * @covers RemoveOldManualUserPages::checkJs
+        * @dataProvider provideCheckJs
+        * @param string $text page content
+        * @param bool $expected should it match?
+        * @param string $desc description of test case
+        */
+       public function testCheckJs( $text, $expected, $desc) {
+               $this->load();
+               $r = new RemoveOldManualUserPages();
+               $matched = $r->checkJs( $text, 'meta\.wikimedia\.org', 
'UserName' );
+               $this->assertEquals( $expected, $matched, $desc );
+       }
+
+       public static function provideCheckCss() {
+               return array(
+                       array(
+                               "@import 
url('//meta.wikimedia.org/w/index.php?title=User:UserName/global.css&action=raw&ctype=text/css');",
+                               true,
+                               'standard @import with proto-rel'
+                       ),
+                       array(
+                               "@import 
url('https://meta.wikimedia.org/w/index.php?title=User:UserName/global.css&action=raw&ctype=text/css');",
+                               true,
+                               'standard @import with https'
+                       ),
+                       array(
+                               "@import 
url('//commons.wikimedia.org/w/index.php?title=User:UserName/global.css&action=raw&ctype=text/css');",
+                               false,
+                               'loading from a different wiki'
+                       ),
+                       array(
+                               "@import 
url('//meta.wikimedia.org/w/index.php?title=User:UserName/global.css&action=raw&ctype=text/css');\n
 body{ background-color: red; }",
+                               false,
+                               'some other CSS too',
+                       ),
+                       array(
+                               "@import 
url('//meta.wikimedia.org/w/index.php?title=User:SomeOtherUserName/global.css&action=raw&ctype=text/css');",
+                               false,
+                               'loading another user\'s CSS',
+                       ),
+               );
+       }
+
+       /**
+        * @covers RemoveOldManualUserPages::checkCss
+        * @dataProvider provideCheckCss
+        * @param string $text page content
+        * @param bool $expected should it match?
+        * @param string $desc description of test case
+        */
+       public function testCheckCss( $text, $expected, $desc) {
+               $this->load();
+               $r = new RemoveOldManualUserPages();
+               $matched = $r->checkCss( $text, 'meta\.wikimedia\.org', 
'UserName' );
+               $this->assertEquals( $expected, $matched, $desc );
+       }
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/156533
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I4b3218ccbbc7754a89846f5fec4d288d86ed46bf
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/GlobalCssJs
Gerrit-Branch: wmf/1.24wmf18
Gerrit-Owner: Krinkle <krinklem...@gmail.com>
Gerrit-Reviewer: Legoktm <legoktm.wikipe...@gmail.com>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to