r102817 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r102816‎ | r102817 | r102818 >
Date:21:49, 11 November 2011
Author:catrope
Status:deferred (Comments)
Tags:
Comment:
[RL2] Add maintenance script for migrating old-style gadgets to the new style. Fully working, only needs protection against multiple runs (log to update_log, allow --force to override) and inclusion in the extension updaters so it's run automatically from update.php.
Modified paths:
  • /branches/RL2/extensions/Gadgets/Gadgets.i18n.php (modified) (history)
  • /branches/RL2/extensions/Gadgets/migrateGadgets.php (added) (history)

Diff [purge]

Index: branches/RL2/extensions/Gadgets/Gadgets.i18n.php
@@ -160,6 +160,11 @@
161161 'gadgets-formbuilder-editor-new-section' => 'Create a new section',
162162 'gadgets-formbuilder-editor-choose-title' => 'Choose the title of the new section:',
163163 'gadgets-formbuilder-editor-choose-title-title' => 'Choose section title',
 164+
 165+ # migrateGadgets.php
 166+ 'gadgets-migrate-editsummary-gadget' => 'Migrating gadget $1',
 167+ 'gadgets-migrate-movereason-gadget' => 'Migrating gadget $1',
 168+ 'gadgets-migrate-movereason-category' => 'Moving category title message',
164169 );
165170
166171 /** Message documentation (Message documentation)
@@ -237,6 +242,11 @@
238243 'group-gadgetmanagers' => '{{group|gadgetmanagers}}',
239244 'group-gadgetmanagers-member' => '{{group|gadgetmanagers|member}}',
240245 'grouppage-gadgetmanagers' => '{{group|gadgetmanagers|page}}',
 246+
 247+ # migrateGadgets.php
 248+ 'gadgets-migrate-editsummary-gadget' => 'Edit summary used by the migrateGadgets.php maintenance script when creating a gadget definition page. $1 is the name of the gadget.',
 249+ 'gadgets-migrate-movereason-gadget' => 'Move reason used by the migrateGadgets.php maintenance script when moving a gadget JS or CSS page. $1 is the name of the gadget.',
 250+ 'gadgets-migrate-movereason-category' => 'Move reason used by the migrateGadgets.php maintenance script when moving a gadget category message (MediaWiki: page).',
241251 );
242252
243253 /** Afrikaans (Afrikaans)
Index: branches/RL2/extensions/Gadgets/migrateGadgets.php
@@ -0,0 +1,272 @@
 2+<?php
 3+
 4+$IP = getenv( 'MW_INSTALL_PATH' );
 5+if ( $IP === false ) {
 6+ $IP = dirname( __FILE__ ) . '/../..';
 7+}
 8+require( "$IP/maintenance/Maintenance.php" );
 9+
 10+class MigrateGadgets extends Maintenance {
 11+ public function __construct() {
 12+ parent::__construct();
 13+ $this->mDescription = "Migrates old-style Gadgets defined in MediaWiki:Gadgets-definition to the new format";
 14+ }
 15+
 16+ public function execute() {
 17+ // TODO complain when we're being run twice, using update_log and --force
 18+ // TODO add this to the updaters
 19+ global $wgUser;
 20+ $wgUser->setName( 'Gadget migration script' );
 21+ $this->output( "Migrating old-style Gadgets from [[MediaWiki:Gadgets-definition]] ...\n" );
 22+
 23+ $g = wfMessage( 'gadgets-definition' )->inContentLanguage();
 24+ if ( !$g->exists() ) {
 25+ $this->output( "No Gadget definition page found.\n" );
 26+ return;
 27+ }
 28+ $wikitext = $g->plain();
 29+ $gadgets = $this->parseGadgets( $wikitext );
 30+ $this->output( count( $gadgets ) . " gadget definitions found.\n" );
 31+
 32+ $notResourceLoaded = array();
 33+ $notMoved = $notCreated = array();
 34+ $categories = array();
 35+ foreach ( $gadgets as $id => $gadget ) {
 36+ if ( !$gadget['resourceLoaded'] ) {
 37+ $notResourceLoaded[] = $id;
 38+ }
 39+ unset( $gadget['resourceLoaded'] );
 40+ if ( $gadget['settings']['category'] !== '' ) {
 41+ $categories[$gadget['settings']['category']] = true;
 42+ }
 43+
 44+ $this->output( "Converting $id ...\n" );
 45+ $moves = array(
 46+ "MediaWiki:Gadget-$id" => "MediaWiki:Gadget-$id-title"
 47+ );
 48+ foreach ( array_merge( $gadget['module']['scripts'], $gadget['module']['styles'] ) as $page ) {
 49+ $moves["MediaWiki:Gadget-$page"] = "Gadget:$page";
 50+ $moves["MediaWiki talk:Gadget-$page"] = "Gadget talk:$page";
 51+ }
 52+ $notMoved = array_merge( $notMoved, $this->processMoves( $moves,
 53+ wfMessage( 'gadgets-migrate-movereason-gadget', $id )->inContentLanguage()->plain()
 54+ ) );
 55+
 56+ $result = $this->createGadgetPage( $id, $gadget );
 57+ if ( $result === true ) {
 58+ $this->output( "Created [[Gadget definition:$id.js]]\n" );
 59+ } else {
 60+ $this->output( "ERROR when creating [[Gadget definition:$id.js]]: $result\n" );
 61+ $notCreated[] = $id;
 62+ }
 63+ }
 64+
 65+ $this->output( "Moving category title messages ...\n" );
 66+ $categoryMoves = array();
 67+ foreach ( $categories as $category => $unused ) {
 68+ $categoryMoves["MediaWiki:Gadget-section-$category"] = "MediaWiki:Gadgetcategory-$category";
 69+ }
 70+ $notMoved = array_merge( $notMoved, $this->processMoves( $categoryMoves,
 71+ wfMessage( 'gadgets-migrate-movereason-category' )->inContentLanguage()->plain()
 72+ ) );
 73+
 74+ if ( count( $notMoved ) ) {
 75+ $this->output( "There were ERRORS moving " . count( $notMoved ) . " pages.\n" );
 76+ $this->output( "The following pages were NOT successfully moved:\n" );
 77+ foreach ( $notMoved as $from => $to ) {
 78+ $this->output( "[[$from]] -> [[$to]]\n" );
 79+ }
 80+ }
 81+ if ( count( $notCreated ) ) {
 82+ $this->output( "There were ERRORS creating " . count( $notCreated ) . " pages.\n" );
 83+ $this->output( "The following pages were NOT successfully created:\n" );
 84+ foreach ( $notCreated as $page ) {
 85+ $this->output( "[[$page]]\n" );
 86+ }
 87+ }
 88+
 89+ if ( count( $notResourceLoaded ) ) {
 90+ $this->output( "WARNING: The following gadgets will now be loaded through ResourceLoader, but were not marked as supporting ResourceLoader. They may now be broken.\n" );
 91+ foreach ( $notResourceLoaded as $id ) {
 92+ $this->output( "$id\n" );
 93+ }
 94+ }
 95+
 96+ $this->output( "All done migrating gadgets\n" );
 97+
 98+ }
 99+
 100+ /**
 101+ * Parse an old-style MediaWiki:Gadgets-definition page. This basically contains
 102+ * the important parts of loadStructuredList() from the old Gadgets code.
 103+ * @param $wikitext string Wikitext
 104+ * @return array( id => blob structure ) where the blob structure is the unserialized version of a gadget JSON blob
 105+ * with an additional 'resourceLoaded' key.
 106+ */
 107+ protected function parseGadgets( $wikitext ) {
 108+ // Remove comments
 109+ $wikitext = preg_replace( '/<!--.*-->/s', '', $wikitext );
 110+ // Split by line
 111+ $lines = preg_split( '/(\r\n|\r|\n)+/', $wikitext );
 112+
 113+ $gadgets = array();
 114+ $category = '';
 115+
 116+ foreach ( $lines as $line ) {
 117+ $m = array();
 118+ if ( preg_match( '/^==+ *([^*:\s|]+?)\s*==+\s*$/', $line, $m ) ) {
 119+ $category = $m[1];
 120+ }
 121+ else {
 122+ $gadget = $this->parseGadgetDefinition( $line );
 123+ if ( $gadget ) {
 124+ $id = key( $gadget );
 125+ $gadget[$id]['settings']['category'] = $category;
 126+ $gadgets += $gadget;
 127+ }
 128+ }
 129+ }
 130+ return $gadgets;
 131+ }
 132+
 133+ /**
 134+ * Parse an old-style gadget definition. This is pretty much newFromDefinition() from the old Gadgets code,
 135+ * with small modifications to make it output an array structure.
 136+ * @param $definition string Old-style gadget definition from MediaWiki:Gadgets-definition
 137+ * @return array( id => blob structure ) where the blob structure is the unserialized version of a gadget JSON blob
 138+ * with an additional 'resourceLoaded' key.
 139+ */
 140+ protected function parseGadgetDefinition( $definition ) {
 141+ $gadget = array(
 142+ 'settings' => array(
 143+ 'rights' => array(),
 144+ 'default' => false,
 145+ 'hidden' => false,
 146+ 'shared' => false,
 147+ 'category' => ''
 148+ ),
 149+ 'module' => array(
 150+ 'scripts' => array(),
 151+ 'styles' => array(),
 152+ 'dependencies' => array(),
 153+ 'messages' => array()
 154+ ),
 155+ 'resourceLoaded' => false
 156+ );
 157+
 158+ if ( !preg_match( '/^\*+ *([a-zA-Z](?:[-_:.\w\d ]*[a-zA-Z0-9])?)(\s*\[.*?\])?\s*((\|[^|]*)+)\s*$/', $definition, $m ) ) {
 159+ return false;
 160+ }
 161+
 162+ $name = trim( str_replace( ' ', '_', $m[1] ) );
 163+ $options = trim( $m[2], ' []' );
 164+
 165+ foreach ( preg_split( '/\s*\|\s*/', $options, -1, PREG_SPLIT_NO_EMPTY ) as $option ) {
 166+ $arr = preg_split( '/\s*=\s*/', $option, 2 );
 167+ $option = $arr[0];
 168+ if ( isset( $arr[1] ) ) {
 169+ $params = explode( ',', $arr[1] );
 170+ $params = array_map( 'trim', $params );
 171+ } else {
 172+ $params = array();
 173+ }
 174+
 175+ switch ( $option ) {
 176+ case 'ResourceLoader':
 177+ $gadget['resourceLoaded'] = true;
 178+ break;
 179+ case 'dependencies':
 180+ $gadget['module']['dependencies'] = $params;
 181+ break;
 182+ case 'rights':
 183+ $gadget['settings']['rights'] = $params;
 184+ break;
 185+ case 'skins':
 186+ // TODO we'll need to implement skins support in RL2 too
 187+ //$gadget['settings']['skins'] = array_intersect( array_keys( Skin::getSkinNames() ), $params );
 188+ break;
 189+ case 'default':
 190+ $gadget['settings']['default'] = true;
 191+ break;
 192+ }
 193+ }
 194+
 195+ foreach ( preg_split( '/\s*\|\s*/', $m[3], -1, PREG_SPLIT_NO_EMPTY ) as $page ) {
 196+ if ( preg_match( '/\.js/', $page ) ) {
 197+ $gadget['module']['scripts'][] = $page;
 198+ } elseif ( preg_match( '/\.css/', $page ) ) {
 199+ $gadget['module']['styles'][] = $page;
 200+ }
 201+ }
 202+
 203+ return array( $name => $gadget );
 204+ }
 205+
 206+ protected function processMoves( $moves, $reason ) {
 207+ $notMoved = array();
 208+ foreach ( $moves as $from => $to ) {
 209+ $result = $this->moveGadgetPage( $from, $to, $reason );
 210+ if ( $result === true ) {
 211+ $this->output( "Moved [[$from]] to [[$to]]\n" );
 212+ } else if ( $result === false ) {
 213+ $this->output( "...skipping [[$from]], doesn't exist\n" );
 214+ } else {
 215+ $this->output( $result );
 216+ $notMoved[$from] = $to;
 217+ }
 218+ }
 219+ return $notMoved;
 220+ }
 221+
 222+ protected function moveGadgetPage( $from, $to, $reason ) {
 223+ $fromTitle = Title::newFromText( $from );
 224+ $toTitle = Title::newFromText( $to );
 225+ if ( !$fromTitle ) {
 226+ return "Invalid title `$from'";
 227+ }
 228+ if ( !$fromTitle->exists() ) {
 229+ return false;
 230+ }
 231+ if ( !$toTitle ) {
 232+ return "Invalid title: `$to'";
 233+ }
 234+
 235+ $errors = $fromTitle->moveTo( $toTitle, /* $auth = */ false,
 236+ $reason,
 237+ /* $createRedirect = */ false
 238+ );
 239+
 240+ if ( $errors === true ) {
 241+ return true;
 242+ } else {
 243+ $errorMsgs = array();
 244+ foreach ( $errors as $error ) {
 245+ $key = array_shift( $error );
 246+ $msg = wfMessage( $key, $error );
 247+ $errorMsgs[] = $msg->text();
 248+ }
 249+ return "ERROR when moving [[{$fromTitle->getPrefixedText()}]] to [[{$toTitle->getPrefixedText()}]]: " .
 250+ implode( "\n", $errorMsgs ) . "\n";
 251+ }
 252+ }
 253+
 254+ protected function createGadgetPage( $id, $gadget ) {
 255+ $title = Title::makeTitleSafe( NS_GADGET_DEFINITION, $id . '.js' );
 256+ if ( !$title ) {
 257+ return "Invalid title `Gadget definition:$id.js'";
 258+ }
 259+ $page = WikiPage::factory( $title );
 260+ $status = $page->doEdit(
 261+ FormatJson::encode( $gadget ),
 262+ wfMessage( 'gadgets-migrate-editsummary-gadget', $id )->plain()
 263+ );
 264+ if ( $status->isOK() ) {
 265+ return true;
 266+ } else {
 267+ return $status->getWikiText();
 268+ }
 269+ }
 270+}
 271+
 272+$maintClass = "MigrateGadgets";
 273+require_once( RUN_MAINTENANCE_IF_MAIN );
\ No newline at end of file
Property changes on: branches/RL2/extensions/Gadgets/migrateGadgets.php
___________________________________________________________________
Added: svn:eol-style
1274 + native

Sign-offs

UserFlagDate
Nikerabbitinspected15:15, 24 December 2011

Follow-up revisions

RevisionCommit summaryAuthorDate
r102850[RL2] Followup r102817: make the migration script inherit LoggedUpdateMainten...catrope09:56, 12 November 2011
r102854[RL2] Followup r102817: also move the talk page of the title message, and mov...catrope10:37, 12 November 2011
r102855[RL2] Followup r102817: move the old title message to -desc instead of -title...catrope11:06, 12 November 2011

Comments

#Comment by Krinkle (talk | contribs)   22:57, 13 November 2011
+				case 'skins':
+					// TODO we'll need to implement skins support in RL2 too
+					//$gadget['settings']['skins'] = array_intersect( array_keys( Skin::getSkinNames() ), $params );

That feature was recently added in r101828. I'll need to make an interface for this. Probably a group of checkboxes, or a {tag|select|o}}-"multiple", or perhaps a propCloud-instance like for "required rights" where the choice is also limited.

#Comment by Catrope (talk | contribs)   07:16, 14 November 2011

Right, this'll need work on your end too. I, of course, was only thinking about backend support :)

Status & tagging log