r98729 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r98728‎ | r98729 | r98730 >
Date:01:26, 3 October 2011
Author:krinkle
Status:resolved (Comments)
Tags:
Comment:
[RL2] Merge gadget manager into SpecialGadgets

* Rewrite SpecialGadgets to use the new repository backend. While at it, made it use context variables instead of globals.--
Modified paths:
  • /branches/RL2/extensions/Gadgets/GadgetHooks.php (modified) (history)
  • /branches/RL2/extensions/Gadgets/Gadgets.i18n.php (modified) (history)
  • /branches/RL2/extensions/Gadgets/Gadgets.php (modified) (history)
  • /branches/RL2/extensions/Gadgets/SpecialGadgets.php (modified) (history)
  • /branches/RL2/extensions/Gadgets/api/ApiQueryGadgetCategories.php (modified) (history)
  • /branches/RL2/extensions/Gadgets/modules/ext.gadgets.api.js (modified) (history)
  • /branches/RL2/extensions/Gadgets/modules/ext.gadgets.gadgetmanager.css (modified) (history)
  • /branches/RL2/extensions/Gadgets/modules/ext.gadgets.gadgetmanager.js (modified) (history)
  • /branches/RL2/extensions/Gadgets/modules/ext.gadgets.preferences.js (modified) (history)
  • /branches/RL2/extensions/Gadgets/modules/ext.gadgets.prejs.css (modified) (history)
  • /branches/RL2/extensions/Gadgets/modules/images/edit-faded.png (added) (history)
  • /branches/RL2/extensions/Gadgets/modules/images/edit.png (added) (history)

Diff [purge]

Index: branches/RL2/extensions/Gadgets/Gadgets.i18n.php
@@ -15,41 +15,64 @@
1616 */
1717 $messages['en'] = array(
1818 # For Special:Version
19 - 'gadgets-desc' => 'Lets users select custom [[Special:Gadgets|CSS and JavaScript gadgets]] in their [[Special:Preferences#mw-prefsection-gadgets|preferences]].',
 19+ 'gadgets-desc' => 'Lets users select custom [[Special:Gadgets|CSS and JavaScript gadgets]] in their [[Special:Preferences#mw-prefsection-gadgets|preferences]].',
2020
2121 # For Special:Preferences
22 - 'prefs-gadgets' => 'Gadgets',
 22+ 'prefs-gadgets' => 'Gadgets',
2323 'prefs-gadgets-shared' => 'Shared gadgets',
2424 'gadgets-prefstext' => 'Below is a list of gadgets you can enable for your account.
2525 These gadgets are mostly based on JavaScript, so JavaScript has to be enabled in your browser for them to work.
2626 Note that these gadgets will have no effect on this preferences page.
2727
2828 Also note that these gadgets are not part of the MediaWiki software, and are usually developed and maintained by users of the wiki.
29 -Administrators manage to the [[Special:GadgetManager|gadget definitions]] and the [[Special:Gadgets|titles and descriptions]] of available gadgets.',
 29+Administrators manage the [[Special:Gadgets|gadget definitions, titles and descriptions]] of available gadgets.',
3030 'gadgets-preference-description' => '$1: $2',
3131 'gadgets-sharedprefstext' => 'Below is a list of gadgets from other wikis. TODO: This needs more text',
3232
3333 # For Special:Gadgets
34 - 'gadgets' => 'Gadgets',
35 - 'gadgets-title' => 'Gadgets',
36 - 'gadgets-pagetext' => "Below is a list of gadgets available on this wiki. Users can enable or disable these through their [[Special:Preferences#mw-prefsection-gadgets|preferences page]].
37 -This overview provides easy access to the system message pages that define each gadget's description and title. Welcome to the gadget management interface. Below is an overview of all the configurable options for this gadget. defined on this wiki. Users can opt in or opt out of these through their [[Special:Preferences#mw-prefsection-gadgets|preferences page]]. All modifications to gadget definitions can be followed in the [$1 recent changes]",
 34+ // General
 35+ 'gadgets' => 'Gadgets',
 36+ 'gadgets-title' => 'Gadgets',
 37+ 'gadgets-not-found' => 'Gadget "$1" not found.',
 38+ 'gadgets-nosuchaction' => 'No such action',
 39+
 40+ // Main page
 41+ 'gadgets-pagetext' => "Below is a list of gadgets available on this wiki. Users can enable or disable these through their [[Special:Preferences#mw-prefsection-gadgets|preferences page]].
 42+This overview provides easy access to the gadget defintions and system message pages that define each gadget's description and title.
 43+
 44+* [[$1|recent changes]]", // @todo FIXME: Make the 'recent changes' link more integrated (a tab perhaps)
3845 'gadgets-nogadgets' => 'This wiki currently has no gadgets defined.',
39 - 'gadgets-uses' => 'Uses',
40 - 'gadgets-required-rights' => 'Requires the {{PLURAL:$2|$1 right|following rights: $1}}.',
41 - 'gadgets-default' => 'Enabled for everyone by default.',
42 - 'gadgets-export' => 'Export',
43 - 'gadgets-export-title' => 'Gadget export',
44 - 'gadgets-not-found' => 'Gadget "$1" not found.',
45 - 'gadgets-export-text' => 'To export the $1 gadget, click on "{{int:gadgets-export-download}}" button, save the downloaded file,
46 -go to Special:Import on destination wiki and upload it. You must have appropriate permissions on the destination wiki (including the right to edit in the {{ns:Gadget}} and {{ns:Gadget definition}} namespaces) and the import from file uploads must be enabled.',
 46+ 'gadgets-uncategorized' => 'Uncategorized',
 47+ 'gadgets-message-edit' => 'Edit',
 48+ 'gadgets-message-edit-tooltip' => 'Edit this message at $1',
 49+ 'gadgets-desc-edit' => 'Edit description',
 50+ 'gadgets-desc-edit-tooltip' => 'Edit the description at $1',
 51+ 'gadgets-desc-add' => 'Add description',
 52+ 'gadgets-desc-add-tooltip' => 'Add a description at $1',
 53+ 'gadgets-gadget-permalink' => 'Permalink',
 54+ 'gadgets-gadget-permalink-tooltip' => 'Permalink to the definition of this gadget',
 55+ 'gadgets-gadget-export' => 'Export',
 56+ 'gadgets-gadget-export-tooltip' => 'Export this gadget',
 57+ 'gadgets-gadget-modify' => 'Modify gadget',
 58+ 'gadgets-gadget-modify-tooltip' => 'Open the edit form for the gadget definition of $1',
 59+ 'gadgets-gadget-delete' => 'Delete gadget',
 60+ 'gadgets-gadget-delete-tooltip' => 'Delete this gadget',
 61+
 62+ // Single gadget page
 63+ 'gadgets-gadget-title' => 'Gadget "$1"',
 64+ 'gadgets-prop-default-on' => 'This gadget is enabled for everyone by default.',
 65+ 'gadgets-prop-hidden-on' => 'This is a hidden gadget.',
 66+ 'gadgets-prop-shared-on' => 'This gadget is shared.',
 67+
 68+ // Export page
 69+ 'gadgets-export-title' => 'Exporting gadget "$1"',
 70+ 'gadgets-export-text' => 'To export this gadget, click on "{{int:gadgets-export-download}}" button, save the downloaded file,
 71+go to Special:Import on destination wiki and upload it. You must have appropriate permissions on the destination wiki ($3) including the right to edit in the {{ns:Gadget}} and {{ns:Gadget_definition}} namespaces.',
4772 'gadgets-export-download' => 'Download',
4873
4974 # For the ext.gadgets.gadgetmanager module
50 - 'gadgetmanager-title' => 'Gadget management',
51 - 'gadgetmanager-uncategorized' => 'Uncategorized',
52 - 'gadgetmanager-tablehead-lastmod' => 'Last modified',
53 - 'gadgetmanager-tablecell-lastmod' => '$1 by $2',
 75+ 'gadgets-gadget-create' => 'Create',
 76+ 'gadgets-gadget-create-tooltip' => 'Create new gadget',
5477 'gadgetmanager-editor-title' => 'Editing $1:',
5578 'gadgetmanager-editor-removeprop-tooltip' => 'Remove this item',
5679 'gadgetmanager-editor-save' => 'Save gadget',
@@ -62,14 +85,11 @@
6386 'gadgetmanager-prop-messages' => 'Messages',
6487 'gadgetmanager-prop-category' => 'Category',
6588 'gadgetmanager-prop-rights' => 'Required user rights',
66 - 'gadgetmanager-prop-default' => 'Enable for everyone by default',
 89+ 'gadgetmanager-prop-default' => 'Enable by default',
6790 'gadgetmanager-prop-hidden' => 'Hide gadget',
6891 'gadgetmanager-prop-shared' => 'Share gadget',
69 - 'gadgetmanager-prop-default-yes' => 'This gadget is loaded by default.',
70 - 'gadgetmanager-prop-hidden-yes' => 'This is a hidden gadget.',
71 - 'gadgetmanager-prop-shared-yes' => 'This gadget is shared.',
72 - 'gadgetmanager-comment-modify' => 'Modified definition of gadget [[Special:GadgetManager/$1|$1]]',
73 -
 92+ 'gadgetmanager-comment-modify' => 'Modified definition of gadget [[Special:Gadgets/$1|$1]]',
 93+
7494 # Validation error messages
7595 'gadget-validate-invalidjson' => 'The gadget definition page contents are not a valid JSON object.',
7696 'gadget-validate-notset' => 'The property $1 is not set.',
@@ -112,34 +132,37 @@
113133
114134 # For Special:Gadgets
115135 'gadgets-title' => '{{Identical|Gadgets}}',
116 - 'gadgets-uses' => "This is used as a verb in third-person singular. It appears in front of a script name. Example: \"''Uses: Gadget-UTCLiveClock.js''\"
 136+ 'gadgets-export' => 'Used on [[Special:Gadgets]]. This is a verb, not noun.
117137
118 -See [http://mediawiki.org/wiki/Special:Gadgets Gadgets overview on mediawiki.org]",
119 - 'gadgets-required-rights' => 'Parameters:
120 -* $1 - a list.
121 -* $2 - the number of items in list $1 for PLURAL use.',
122 - 'gadgets-export' => 'Used on [[Special:Gadgets]]. This is a verb, not noun.
123138 {{Identical|Export}}',
124139 'gadgets-export-download' => 'Use the verb for this message. Submit button.
 140+
125141 {{Identical|Download}}',
 142+ 'gadgets-message-edit' => 'Used as linktext for the link to edit the cateogory title, gadget title or gadget description.
126143
 144+{{Identical|Edit}}',
 145+ 'gadgets-message-edit-tooltip' => 'Parameters:
 146+* $1: Page name in the MediaWiki namespace where this message is defined.',
 147+ 'gadgets-desc-add' => 'Used as linktext for the link to create gadget description.
 148+
 149+{{Identical|Add}}.',
 150+ 'gadgets-desc-add-tooltip' => 'Parameters:
 151+* $1: Page name in the MediaWiki namespace where this message is defined.',
 152+ 'gadgets-nosuchaction' => 'Identical to core:
 153+* {{mw-msg|nosuchaction}}.',
 154+ 'gadgets-gadget-create' => 'Identical to core:
 155+* {{mw-msg|vector-view-create}}
 156+* {{mw-msg|create}}',
 157+
 158+ # For the ext.gadgets.gadgetmanager module
 159+ 'gadgetmanager-comment-modify' => 'Edit summary used when editing definitions from [[Special:Gadgets]].',
 160+
127161 # Validation error messages
128162 'gadget-validate-notset' => '$1 is the name of the property, e.g. settings.rights .',
129163 'gadget-validate-wrongtype' => '* $1 is the name of the property, e.g. settings.rights or module.messages[3].
130164 * $2 is the type that this property is expected to have
131165 * $3 is the type it actually had',
132166
133 - # For Special:GadgetManager
134 - 'gadgetmanager-tablehead-lastmodified' => '{{Identical|Last modified}}
135 -{{Output|plain}}',
136 - 'gadgetmanager-tablecell-lastmod' => 'This message is used on Special:GadgetManager to indicate the last modified date, time and user for gadget definitions.
137 -* $1 is a time and date (duplicated in $3 and $4)
138 -* $2 is a link to a user page with a user name as link text, followed by a series of related links
139 -* $3 is the date
140 -* $4 is the time
141 -* $5 is the user name which can be used with GENDER',
142 - 'gadgetmanager-comment-modify' => 'Edit summary used when editing definitions from [[Special:GadgetManager]].',
143 -
144167 # User rights
145168 'right-gadgets-edit' => '{{doc-right}}',
146169 'right-gadgets-definition-create' => '{{doc-right}}',
Index: branches/RL2/extensions/Gadgets/Gadgets.php
@@ -33,7 +33,7 @@
3434
3535 /**
3636 * Add gadget repositories here.
37 - *
 37+ *
3838 * For foreign DB-based gadget repositories, use:
3939 * // TODO: Document better by looking at WMF ForeignFileRepo config
4040 * $wgGadgetRepositories[] = array(
@@ -49,7 +49,7 @@
5050 * 'tablePrefix' => 'mw_', // Table prefix for the foreign wiki's database, or '' if no prefix
5151 * 'hasSharedCache' => true, // Whether the foreign wiki's cache is accessible through $wgMemc
5252 * );
53 - *
 53+ *
5454 * For foreign API-based gadget repositories, use:
5555 * $wgGadgetRepositories[] = array(
5656 * 'class' => 'ForeignAPIGadgetRepo',
@@ -122,13 +122,10 @@
123123 $wgAutoloadClasses['GadgetRepo'] = $dir . 'backend/GadgetRepo.php';
124124 $wgAutoloadClasses['GadgetResourceLoaderModule'] = $dir . 'backend/GadgetResourceLoaderModule.php';
125125 $wgAutoloadClasses['LocalGadgetRepo'] = $dir . 'backend/LocalGadgetRepo.php';
126 -$wgAutoloadClasses['SpecialGadgetManager'] = $dir . 'SpecialGadgetManager.php';
127126 $wgAutoloadClasses['SpecialGadgets'] = $dir . 'SpecialGadgets.php';
128127
129128 $wgSpecialPages['Gadgets'] = 'SpecialGadgets';
130129 $wgSpecialPageGroups['Gadgets'] = 'wiki';
131 -#$wgSpecialPages['GadgetManager'] = 'SpecialGadgetManager';
132 -#$wgSpecialPageGroups['GadgetManager'] = 'wiki';
133130
134131 $wgAPIListModules['gadgetcategories'] = 'ApiQueryGadgetCategories';
135132 $wgAPIListModules['gadgets'] = 'ApiQueryGadgets';
@@ -168,6 +165,8 @@
169166 'jquery.json',
170167 ),
171168 'messages' => array(
 169+ 'gadgets-gadget-create',
 170+ 'gadgets-gadget-create-tooltip',
172171 'gadgetmanager-editor-title',
173172 'gadgetmanager-editor-prop-remove',
174173 'gadgetmanager-editor-removeprop-tooltip',
Index: branches/RL2/extensions/Gadgets/GadgetHooks.php
@@ -29,8 +29,17 @@
3030 }
3131
3232 /**
 33+ * Get a Title object of the gadget definition page from a gadget id
 34+ * @param $is String
 35+ * @return Title|null
 36+ */
 37+ public static function getDefinitionTitleFromID( $id ) {
 38+ return Title::makeTitleSafe( NS_GADGET_DEFINITION, $id . '.js' );
 39+ }
 40+
 41+ /**
3342 * ArticleDeleteComplete hook handler.
34 - *
 43+ *
3544 * @param $article Article
3645 * @param $user User
3746 * @param $reason String: Deletion summary
@@ -41,7 +50,7 @@
4251 if ( !$id ) {
4352 return true;
4453 }
45 -
 54+
4655 $repo = LocalGadgetRepo::singleton();
4756 $repo->deleteGadget( $id );
4857 // deleteGadget() may return an error if the Gadget didn't exist, but we don't care here
@@ -50,7 +59,7 @@
5160
5261 /**
5362 * ArticleSaveComplete hook handler.
54 - *
 63+ *
5564 * @param $article Article
5665 * @param $user User
5766 * @param $text String: New page text
@@ -68,23 +77,23 @@
6978 if ( !$id ) {
7079 return true;
7180 }
72 -
 81+
7382 $previousRev = $revision->getPrevious();
7483 $prevTs = $previousRev instanceof Revision ? $previousRev->getTimestamp() : wfTimestampNow();
75 -
 84+
7685 // Update the database entry for this gadget
7786 $repo = LocalGadgetRepo::singleton();
7887 // TODO: Timestamp in the constructor is ugly
7988 $gadget = new Gadget( $id, $repo, $text, $prevTs );
8089 $repo->modifyGadget( $gadget, $revision->getTimestamp() );
81 -
 90+
8291 // modifyGadget() returns a Status object with an error if there was a conflict,
8392 // but we don't care. If a conflict occurred, that must be because a newer edit's
8493 // DB update occurred before ours, in which case the right thing to do is to occu
85 -
 94+
8695 return true;
8796 }
88 -
 97+
8998 /**
9099 * Update the database entry for a gadget if the description page is
91100 * newer than the database entry.
@@ -95,24 +104,24 @@
96105 if ( !$id ) {
97106 return;
98107 }
99 -
 108+
100109 // Check whether this undeletion changed the latest revision of the page, by comparing
101110 // the timestamp of the latest revision with the timestamp in the DB
102111 $repo = LocalGadgetRepo::singleton();
103112 $gadget = $repo->getGadget( $id );
104113 $gadgetTS = $gadget ? $gadget->getTimestamp() : 0;
105 -
 114+
106115 $rev = Revision::newFromTitle( $title );
107116 if ( wfTimestamp( TS_MW, $rev->getTimestamp() ) ===
108117 wfTimestamp( TS_MW, $gadgetTS ) ) {
109118 // The latest rev didn't change. Someone must've undeleted an older revision
110119 return;
111120 }
112 -
 121+
113122 // Update the database entry for this gadget
114123 $newGadget = new Gadget( $id, $repo, $rev->getRawText(), $gadgetTS );
115124 $repo->modifyGadget( $newGadget, $rev->getTimestamp() );
116 -
 125+
117126 // modifyGadget() returns a Status object with an error if there was a conflict,
118127 // but we do't care, see similar comment in articleSaveComplete()
119128 return;
@@ -128,7 +137,7 @@
129138 self::gadgetDefinitionUpdateIfChanged( $title );
130139 return true;
131140 }
132 -
 141+
133142 public static function gadgetDefinitionImport( $title, $origTitle, $revCount, $sRevCount, $pageInfo ) {
134143 self::gadgetDefinitionUpdateIfChanged( $title );
135144 return true;
@@ -136,7 +145,7 @@
137146
138147 /**
139148 * ArticleDeleteComplete hook handler.
140 - *
 149+ *
141150 * @param $article Article
142151 * @param $user User
143152 * @param $reason String: Deletion summary
@@ -146,10 +155,10 @@
147156 GadgetPageList::delete( $article->getTitle() );
148157 return true;
149158 }
150 -
 159+
151160 /**
152161 * ArticleSaveComplete hook handler.
153 - *
 162+ *
154163 * @param $article Article
155164 * @param $user User
156165 * @param $text String: New page text
@@ -160,7 +169,7 @@
161170 * @param $flags: Int: Bitmap of flags passed to WikiPage::doEdit()
162171 * @param $revision: Revision object for the new revision
163172 */
164 - public static function cssOrJsPageSave( $article, $user, $text, $summary, $isMinor,
 173+ public static function cssOrJsPageSave( $article, $user, $text, $summary, $isMinor,
165174 $isWatch, $section, $flags, $revision )
166175 {
167176 $title = $article->getTitle();
@@ -183,7 +192,7 @@
184193 // Delete the old title from the list. Even if it still exists after the move,
185194 // it'll be a redirect and we don't want those in there
186195 GadgetPageList::delete( $oldTitle );
187 -
 196+
188197 GadgetPageList::updatePageStatus( $newTitle );
189198 return true;
190199 }
@@ -220,7 +229,7 @@
221230 continue;
222231 }
223232 $category = $gadget->getCategory();
224 -
 233+
225234 // Add the Gadget to the right category
226235 $title = htmlspecialchars( $gadget->getTitleMessage() );
227236 $description = $gadget->getDescriptionMessage(); // Is parsed, doesn't need escaping
@@ -236,7 +245,7 @@
237246 $default[] = $id;
238247 }
239248 }
240 -
 249+
241250 $options = array(); // array( desc1 => gadget1, category1 => array( desc2 => gadget2 ) )
242251 foreach ( $categories as $category => $gadgets ) {
243252 if ( $category !== '' ) {
@@ -246,7 +255,7 @@
247256 $options += $gadgets;
248257 }
249258 }
250 -
 259+
251260 $preferences['gadgets-intro'] =
252261 array(
253262 'type' => 'info',
@@ -258,7 +267,7 @@
259268 'raw' => 1,
260269 'rawrow' => 1,
261270 );
262 - $preferences['gadgets'] =
 271+ $preferences['gadgets'] =
263272 array(
264273 'type' => 'multiselect',
265274 'options' => $options,
@@ -267,7 +276,7 @@
268277 'prefix' => 'gadget-',
269278 'default' => $default,
270279 );
271 -
 280+
272281 // Add tab for shared gadgets
273282 $preferences['gadgets-intro-shared'] =
274283 array(
@@ -318,12 +327,12 @@
319328 $out->addModules( $gadget->getModuleName() );
320329 }
321330 }
322 -
 331+
323332 // Add preferences JS if we're on Special:Preferences
324333 if ( $out->getTitle()->equals( SpecialPage::getTitleFor( 'Preferences' ) ) ) {
325334 $out->addModules( 'ext.gadgets.preferences' );
326335 }
327 -
 336+
328337 wfProfileOut( __METHOD__ );
329338 return true;
330339 }
@@ -335,13 +344,14 @@
336345 */
337346 public static function makeGlobalVariablesScript( &$vars, $out ) {
338347 $title = $out->getTitle();
 348+ $user = $out->getUser();
339349 // FIXME: This is not a nice way to do it. Maybe we should check for the presence
340350 // of a module instead or something.
341 - if ( $title->equals( SpecialPage::getTitleFor( 'GadgetManager' ) ) ||
 351+ if ( $title->equals( SpecialPage::getTitleFor( 'Gadgets' ) ) ||
342352 $title->equals( SpecialPage::getTitleFor( 'Preferences' ) ) )
343353 {
344354 global $wgGadgetEnableSharing;
345 -
 355+
346356 // Pass the source data for each source that is used by a repository
347357 $repos = GadgetRepo::getAllRepos();
348358 $sources = $out->getResourceLoader()->getSources();
@@ -353,7 +363,13 @@
354364 $vars['gadgetsConf'] = array(
355365 'enableSharing' => $wgGadgetEnableSharing,
356366 'allRights' => User::getAllRights(),
357 - 'repos' => $repoData
 367+ 'repos' => $repoData,
 368+ 'userIsAllowed' => array(
 369+ 'editinterface' => $user->isAllowed( 'editinterface' ),
 370+ 'gadgets-definition-create' => $user->isAllowed( 'gadgets-definition-create' ),
 371+ 'gadgets-definition-edit' => $user->isAllowed( 'gadgets-definition-edit' ),
 372+ 'gadgets-definition-delete' => $user->isAllowed( 'gadgets-definition-delete' ),
 373+ ),
358374 );
359375 }
360376 return true;
@@ -382,7 +398,7 @@
383399 $list[NS_GADGET_DEFINITION_TALK] = 'Gadget_definition_talk';
384400 return true;
385401 }
386 -
 402+
387403 public static function titleIsCssOrJsPage( $title, &$result ) {
388404 if ( ( $title->getNamespace() == NS_GADGET || $title->getNamespace() == NS_GADGET_DEFINITION ) &&
389405 preg_match( '!\.(css|js)$!u', $title->getText() ) )
@@ -391,14 +407,14 @@
392408 }
393409 return true;
394410 }
395 -
 411+
396412 public static function titleIsMovable( $title, &$result ) {
397413 if ( $title->getNamespace() == NS_GADGET_DEFINITION ) {
398414 $result = false;
399415 }
400416 return true;
401417 }
402 -
 418+
403419 public static function getUserPermissionsErrors( $title, $user, $action, &$result ) {
404420 if ( $title->getNamespace() == NS_GADGET_DEFINITION ) {
405421 // Enforce restrictions on the Gadget_definition namespace
Index: branches/RL2/extensions/Gadgets/SpecialGadgets.php
@@ -1,168 +1,469 @@
22 <?php
33 /**
4 - * Special:Gadgets, provides a preview of MediaWiki:Gadgets.
 4+ * SpecialPage for Gadgets.
55 *
66 * @file
7 - * @ingroup SpecialPage
8 - * @author Daniel Kinzler, brightbyte.de
9 - * @copyright © 2007 Daniel Kinzler
10 - * @license GNU General Public License 2.0 or later
 7+ * @ingroup Extensions
118 */
129
13 -if( !defined( 'MEDIAWIKI' ) ) {
14 - echo( "not a valid entry point.\n" );
15 - die( 1 );
16 -}
17 -
18 -/**
19 - *
20 - */
2110 class SpecialGadgets extends SpecialPage {
2211
2312 /**
24 - * Constructor
 13+ * @var $par Array: Parameters passed to the page.
 14+ * - gadget String: Gadget id
 15+ * - action String: Action ('view', 'export')
2516 */
26 - function __construct() {
27 - parent::__construct( 'Gadgets', '', true );
 17+ protected $params = array(
 18+ 'gadget' => null,
 19+ 'action' => 'view',
 20+ );
 21+
 22+ public function __construct() {
 23+ parent::__construct( 'Gadgets' );
2824 }
2925
3026 /**
31 - * Main execution function
32 - * @param $par Parameters passed to the page
 27+ * Main execution function.
 28+ * @todo: Add canonical links to <head> to avoid indexing of link variations and stuff like
 29+ * [[Special:Gadgets/id/export/bablabla]]. Those should either redirect and/or have a canonical
 30+ * link in the <head> ($out->addLink).
 31+ * @param $par String: Parameters passed to the page.
3332 */
34 - function execute( $par ) {
35 - $parts = explode( '/', $par );
36 - if ( count( $parts ) == 2 && $parts[0] == 'export' ) {
37 - $this->showExportForm( $parts[1] );
38 - } else {
39 - $this->showMainForm();
 33+ public function execute( $par ) {
 34+ $this->par = $par;
 35+ $out = $this->getOutput();
 36+ $out->addModuleStyles( 'ext.gadgets.prejs' );
 37+
 38+ // Map title parts to query string
 39+ if ( is_string( $par ) ) {
 40+ $parts = explode( '/', $par, 3 );
 41+ $this->params['gadget'] = $parts[0];
 42+ if ( isset( $parts[1] ) ) {
 43+ $this->params['action'] = $parts[1];
 44+ }
4045 }
 46+
 47+ // Parameters (overrides title parts)
 48+ $this->params['gadget'] = $this->getRequest()->getVal( 'gadget', $this->params['gadget'] );
 49+ $this->params['action'] = $this->getRequest()->getVal( 'action', $this->params['action'] );
 50+
 51+ // Get instance of Gadget
 52+ $gadget = false;
 53+ if ( !is_null( $this->params['gadget'] ) ) {
 54+ $repo = LocalGadgetRepo::singleton();
 55+ $gadget = $repo->getGadget( $this->params['gadget'] );
 56+ if ( !is_object( $gadget ) ) {
 57+ $out->showErrorPage( 'error', 'gadgets-not-found', array( $this->params['gadget'] ) );
 58+ return;
 59+ }
 60+ }
 61+
 62+ // Handle the the query
 63+ switch( $this->params['action'] ) {
 64+ case 'view':
 65+ if ( $gadget ) {
 66+ $this->showSingleGadget( $gadget );
 67+ } else {
 68+ $this->showAllGadgets();
 69+ }
 70+ break;
 71+ case 'export':
 72+ if ( $gadget ) {
 73+ $this->showExportForm( $gadget );
 74+ } else {
 75+ $out->showErrorPage( 'error', 'gadgets-nosuchaction' );
 76+ }
 77+ break;
 78+ default:
 79+ $out->showErrorPage( 'error', 'gadgets-nosuchaction' );
 80+ break;
 81+ }
4182 }
42 -
 83+
4384 /**
44 - * Displays form showing the list of installed gadgets
 85+ * Returns one <div class="mw-gadgets-gadget">..</div>
 86+ * for the given Gadget object.
4587 */
46 - public function showMainForm() {
47 - global $wgOut, $wgUser, $wgLang, $wgContLang;
 88+ protected function getGadgetHtml( Gadget $gadget ) {
 89+ global $wgContLang;
 90+ $user = $this->getUser();
 91+ $userlang = $this->getLang();
4892
49 - $skin = $wgUser->getSkin();
 93+ // Suffix needed after page names in links to NS_MEDIAWIKI,
 94+ // e.g. to link to [[MediaWiki:Foo/nl]] instead of [[MediaWiki:Foo]]
 95+ $suffix = '';
 96+ if ( $userlang->getCode() !== $wgContLang->getCode() ) {
 97+ $suffix = '/' . $userlang->getCode();
 98+ }
5099
 100+ $html = Html::openElement( 'div', array(
 101+ 'class' => 'mw-gadgets-gadget',
 102+ 'data-gadget-id' => $gadget->getId(),
 103+ ) );
 104+
 105+ // Gadgetlinks section in the Gadget title heading
 106+ $extra = array();
 107+
 108+ $extra[] = Linker::link(
 109+ $this->getTitle( $gadget->getId() ),
 110+ wfMessage( 'gadgets-gadget-permalink' )->escaped(),
 111+ array(
 112+ 'title' => wfMessage( 'gadgets-gadget-permalink-tooltip' )->plain(),
 113+ 'class' => 'mw-gadgets-permalink',
 114+ )
 115+ );
 116+
 117+ $extra[] = Linker::link(
 118+ $this->getTitle( "{$gadget->getId()}/export" ),
 119+ wfMessage( 'gadgets-gadget-export' )->escaped(),
 120+ array(
 121+ 'title' => wfMessage( 'gadgets-gadget-export-tooltip' )->plain(),
 122+ 'class' => 'mw-gadgets-export',
 123+ )
 124+
 125+ );
 126+ if ( $user->isAllowed( 'gadgets-definition-edit' ) ) {
 127+ $extra[] = Linker::link(
 128+ GadgetHooks::getDefinitionTitleFromID( $gadget->getId() ),
 129+ wfMessage( 'gadgets-gadget-modify' )->escaped(),
 130+ array(
 131+ 'title' => wfMessage( 'gadgets-gadget-modify-tooltip' )->plain(),
 132+ 'class' => 'mw-gadgets-modify',
 133+ ),
 134+ array( 'action' => 'edit' )
 135+ );
 136+ }
 137+
 138+ if ( $user->isAllowed( 'gadgets-definition-delete' ) ) {
 139+ $extra[] = Linker::link(
 140+ GadgetHooks::getDefinitionTitleFromID( $gadget->getId() ),
 141+ wfMessage( 'gadgets-gadget-delete' )->escaped(),
 142+ array(
 143+ 'title' => wfMessage( 'gadgets-gadget-delete-tooltip' )->plain(),
 144+ 'class' => 'mw-gadgets-delete',
 145+ ),
 146+ array( 'action' => 'delete' )
 147+ );
 148+ }
 149+
 150+ // Edit interface (gadget title and description)
 151+ $editTitle = $editDescription = '';
 152+ if ( $user->isAllowed( 'editinterface' ) ) {
 153+ $t = Title::makeTitleSafe( NS_MEDIAWIKI, $gadget->getTitleMessageKey() . $suffix );
 154+ $editLink = Linker::link(
 155+ $t,
 156+ wfMessage( 'gadgets-message-edit' )->escaped(),
 157+ array( 'title' => wfMessage( 'gadgets-message-edit-tooltip', $t->getPrefixedText() ) ),
 158+ array( 'action' => 'edit' )
 159+ );
 160+ $editTitle = '<span class="mw-gadgets-messagelink">' . $editLink . '</span>';
 161+
 162+ $t = Title::makeTitleSafe( NS_MEDIAWIKI, $gadget->getDescriptionMessageKey() . $suffix );
 163+ $editLink = Linker::link(
 164+ $t,
 165+ wfMessage( $t->isKnown() ? 'gadgets-desc-edit' : 'gadgets-desc-add' )->escaped(),
 166+ array( 'title' => wfMessage( $t->isKnown() ? 'gadgets-desc-edit-tooltip' : 'gadgets-desc-add-tooltip', $t->getPrefixedText() ) ),
 167+ array( 'action' => 'edit' )
 168+ );
 169+ $editDescription = '<span class="mw-gadgets-messagelink">' . $editLink . '</span>';
 170+ }
 171+
 172+ // Gadget heading
 173+ $html .= '<div class="mw-gadgets-title">'
 174+ . htmlspecialchars( $gadget->getTitleMessage() )
 175+ . ' &#160; ' . $editTitle
 176+ . Html::rawElement( 'span', array(
 177+ 'class' => 'mw-gadgets-gadgetlinks',
 178+ 'data-gadget-id' => $gadget->getId()
 179+ ), implode( '', $extra )
 180+ )
 181+ . '</div>';
 182+
 183+ // Description
 184+ $html .= Html::rawElement( 'p', array(
 185+ 'class' => 'mw-gadgets-description'
 186+ ), $gadget->getDescriptionMessage() . '&#160;' . $editDescription );
 187+
 188+ $html .= '</div>';
 189+ return $html;
 190+ }
 191+
 192+ /**
 193+ * Handles [[Special:Gadgets]].
 194+ * Displays form showing the list of installed gadgets.
 195+ */
 196+ public function showAllGadgets() {
 197+ global $wgContLang;
 198+ $out = $this->getOutput();
 199+ $user = $this->getUser();
 200+ $userlang = $this->getLang();
 201+
51202 $this->setHeaders();
52 - $wgOut->setPagetitle( wfMsg( "gadgets-title" ) );
53 - $wgOut->addWikiMsg( 'gadgets-pagetext' );
 203+ $out->setPagetitle( wfMsg( 'gadgets-title' ) );
54204
55 - $gadgets = Gadget::loadStructuredList();
56 - if ( !$gadgets ) return;
 205+ $repo = LocalGadgetRepo::singleton();
 206+ $gadgetsByCategory = $repo->getGadgetsByCategory();
57207
58 - $lang = "";
59 - if ( $wgLang->getCode() != $wgContLang->getCode() ) {
60 - $lang = "/" . $wgLang->getCode();
 208+ // If there there are no gadgets at all, exit early.
 209+ if ( !count( $gadgetsByCategory ) ) {
 210+ $noGadgetsMsgHtml = Html::element( 'p',
 211+ array(
 212+ 'class' => 'mw-gadgets-nogadgets'
 213+ ), wfMessage( 'gadgets-nogadgets' )->plain()
 214+ );
 215+ $this->getOutput()->addHtml( $noGadgetsMsgHtml );
 216+ return;
61217 }
62218
63 - $listOpen = false;
 219+ // There is atleast one gadget, let's get started.
 220+ $out->addWikiMsg( 'gadgets-pagetext',
 221+ Title::newFromText( 'Special:Recentchanges/namespace=' . NS_GADGET_DEFINITION )->getPrefixedText()
 222+ );
64223
65 - $msgOpt = array( 'parseinline', 'parsemag' );
66 - $editInterfaceAllowed = $wgUser->isAllowed( 'editinterface' );
67 -
68 - foreach ( $gadgets as $section => $entries ) {
69 - if ( $section !== false && $section !== '' ) {
70 - $t = Title::makeTitleSafe( NS_MEDIAWIKI, "Gadget-section-$section$lang" );
71 - if ( $editInterfaceAllowed ) {
72 - $lnkTarget = $t
73 - ? $skin->link( $t, wfMsgHTML( 'edit' ), array(), array( 'action' => 'edit' ) )
74 - : htmlspecialchars( $section );
75 - $lnk = "&#160; &#160; [$lnkTarget]";
76 - } else {
77 - $lnk = '';
78 - }
79 - $ttext = wfMsgExt( "gadget-section-$section", $msgOpt );
 224+ // Only load the gadget manager module if needed
 225+ if ( $user->isAllowed( 'gadgets-definition-delete' )
 226+ || $user->isAllowed( 'gadgets-definition-edit' )
 227+ || $user->isAllowed( 'gadgets-definition-create' )
 228+ ) {
 229+ $out->addModules( 'ext.gadgets.gadgetmanager' );
 230+ }
80231
81 - if( $listOpen ) {
82 - $wgOut->addHTML( Xml::closeElement( 'ul' ) . "\n" );
83 - $listOpen = false;
84 - }
85 - $wgOut->addHTML( Html::rawElement( 'h2', array(), $ttext . $lnk ) . "\n" );
86 - }
 232+ // Sort categories alphabetically
 233+ ksort( $gadgetsByCategory );
87234
88 - foreach ( $entries as $gadget ) {
89 - $t = Title::makeTitleSafe( NS_MEDIAWIKI, "Gadget-{$gadget->getId()}$lang" );
90 - if ( !$t ) continue;
 235+ // ksort causes key "''" to be sorted on top, we want it to be at the bottom,
 236+ // removing and re-adding the value.
 237+ if ( isset( $gadgetsByCategory[''] ) ) {
 238+ $uncat = $gadgetsByCategory[''];
 239+ unset( $gadgetsByCategory[''] );
 240+ $gadgetsByCategory[''] = $uncat;
 241+ }
91242
92 - $links = array();
93 - if ( $editInterfaceAllowed ) {
94 - $links[] = $skin->link( $t, wfMsgHTML( 'edit' ), array(), array( 'action' => 'edit' ) );
95 - }
96 - $links[] = $skin->link( $this->getTitle( "export/{$gadget->getId()}" ), wfMsgHtml( 'gadgets-export' ) );
97 -
98 - $ttext = wfMsgExt( "gadget-{$gadget->getId()}", $msgOpt );
 243+ // Suffix needed after page names in links to NS_MEDIAWIKI,
 244+ // e.g. to link to [[MediaWiki:Foo/nl]] instead of [[MediaWiki:Foo]]
 245+ $suffix = '';
 246+ if ( $userlang->getCode() !== $wgContLang->getCode() ) {
 247+ $suffix = '/' . $userlang->getCode();
 248+ }
99249
100 - if( !$listOpen ) {
101 - $listOpen = true;
102 - $wgOut->addHTML( Xml::openElement( 'ul' ) );
103 - }
104 - $lnk = '&#160;&#160;' . wfMsg( 'parentheses', $wgLang->pipeList( $links ) );
105 - $wgOut->addHTML( Xml::openElement( 'li' ) .
106 - $ttext . $lnk . "<br />" .
107 - wfMsgHTML( 'gadgets-uses' ) . wfMsg( 'colon-separator' )
 250+
 251+ $html = '';
 252+
 253+ foreach ( $gadgetsByCategory as $category => $gadgets ) {
 254+
 255+ // Avoid broken or empty headings. Fallback to a special message
 256+ // for uncategorized gadgets (e.g. gadgets with category '' ).
 257+ if ( $category !== '' ) {
 258+ $categoryTitle = $repo->getCategoryTitle( $category );
 259+ } else {
 260+ $categoryTitle = wfMessage( 'gadgets-uncategorized' )->plain();
 261+ }
 262+
 263+ $editLink = '';
 264+ if ( $user->isAllowed( 'editinterface' ) && $category !== '' ) {
 265+ $t = Title::makeTitleSafe( NS_MEDIAWIKI, "gadgetcategory-{$category}{$suffix}" );
 266+ $editLink = Linker::link(
 267+ $t,
 268+ wfMessage( 'gadgets-message-edit' )->escaped(),
 269+ array( 'title' => wfMessage( 'gadgets-message-edit-tooltip', $t->getPrefixedText() ) ),
 270+ array( 'action' => 'edit' )
108271 );
 272+ $editLink = '<span class="mw-gadgets-messagelink">' . $editLink . '</span>';
 273+ }
109274
110 - $lnk = array();
111 - foreach ( $gadget->getScriptsAndStyles() as $codePage ) {
112 - $t = Title::makeTitleSafe( NS_MEDIAWIKI, $codePage );
113 - if ( !$t ) continue;
 275+ // Category heading
 276+ $html .= Html::rawElement( 'h2', array(), htmlspecialchars( $categoryTitle ) . ' &#160; ' . $editLink );
114277
115 - $lnk[] = $skin->link( $t, htmlspecialchars( $t->getText() ) );
116 - }
117 - $wgOut->addHTML( $wgLang->commaList( $lnk ) );
118 - $rights = $gadget->getRequiredRights();
119 - if ( count( $rights ) ) {
120 - $wgOut->addHTML( '<br />' .
121 - wfMessage( 'gadgets-required-rights', $wgLang->commaList( $rights ), count( $rights ) )->parse()
122 - );
123 - }
124 - if ( $gadget->isOnByDefault() ) {
125 - $wgOut->addHTML( '<br />' . wfMessage( 'gadgets-default' )->parse() );
126 - }
127 -
128 - $wgOut->addHTML( Xml::closeElement( 'li' ) . "\n" );
 278+ // Start gadgets list
 279+ $html .= '<div class="mw-gadgets-list">';
 280+
 281+ foreach( $gadgets as $gadgetId => $gadget ) {
 282+ $html .= $this->getGadgetHtml( $gadget );
 283+
129284 }
 285+
 286+ $html .= '</div>';
130287 }
131288
132 - if( $listOpen ) {
133 - $wgOut->addHTML( Xml::closeElement( 'ul' ) . "\n" );
134 - }
 289+ $out->addHtml( $html );
135290 }
136291
137292 /**
138 - * Exports a gadget with its dependencies in a serialized form
139 - * @param $gadget String Name of gadget to export
 293+ * Handles [[Special:Gadgets/id/export]].
 294+ * Exports a gadget with its dependencies in a serialized form.
 295+ * Should not be called if the gadget does not exist. $gadget must be
 296+ * an instance of Gadget, not null.
 297+ * @param $gadget Gadget: Gadget object of gadget to export.
140298 */
141299 public function showExportForm( $gadget ) {
142 - global $wgOut, $wgScript;
 300+ $this->doSubpageMode();
 301+ $out = $this->getOutput();
143302
144 - $gadgets = Gadget::loadList();
145 - if ( !isset( $gadgets[$gadget] ) ) {
146 - $wgOut->showErrorPage( 'error', 'gadgets-not-found', array( $gadget ) );
147 - return;
 303+ /**
 304+ * @todo: Add note somewhere with link to mw.org help pages about gadget repos
 305+ * if this is a shared gadget and the user owns the wiki, he is recommended
 306+ * to instead pull from this repo natively.
 307+ */
 308+
 309+ $rights = array(
 310+ 'gadgets-definition-create',
 311+ 'gadgets-definition-edit',
 312+ 'gadgets-edit',
 313+ 'importupload',
 314+ );
 315+ $msg = array();
 316+ foreach( $rights as $right ) {
 317+ $msg[] = Html::element( 'code', array(
 318+ 'style' => 'white-space:nowrap',
 319+ 'title' => wfMsg( "right-{$right}" )
 320+ ), $right
 321+ );
148322 }
149 -
150 - $g = $gadgets[$gadget];
 323+
151324 $this->setHeaders();
152 - $wgOut->setPagetitle( wfMsg( "gadgets-export-title" ) );
153 - $wgOut->addWikiMsg( 'gadgets-export-text', $gadget, $g->getDefinition() );
 325+ $out->setPagetitle( wfMsg( 'gadgets-export-title', $gadget->getTitleMessage() ) );
154326
155 - $exportList = "MediaWiki:gadget-$gadget\n";
156 - foreach ( $g->getScriptsAndStyles() as $page ) {
157 - $exportList .= "MediaWiki:$page\n";
 327+ // Make a list of all pagenames to be exported:
 328+ $exportTitles = array();
 329+
 330+ // NS_GADGET_DEFINITION page of this gadget
 331+ $exportTitles[] = GadgetHooks::getDefinitionTitleFromID( $gadget->getId() );
 332+
 333+ // Title message in NS_MEDIAWIKI
 334+ $exportTitles[] = Title::makeTitleSafe( NS_MEDIAWIKI, $gadget->getTitleMessageKey() );
 335+
 336+ // Translation subpages of title message
 337+ // @todo
 338+
 339+ // Description message in NS_MEDIAWIKI
 340+ $exportTitles[] = Title::makeTitleSafe( NS_MEDIAWIKI, $gadget->getDescriptionMessageKey() );
 341+
 342+ // Translation subpages of description message
 343+ // @todo
 344+
 345+ // Module script and styles in NS_GADGET
 346+ foreach ( $gadget->getScripts() as $script ) {
 347+ $exportTitles[] = Title::makeTitleSafe( NS_GADGET, $script );
 348+ }
 349+ foreach ( $gadget->getStyles() as $style ) {
 350+ $exportTitles[] = Title::makeTitleSafe( NS_GADGET, $style );
 351+ }
 352+
 353+ $gadgetModule = $gadget->getModule();
 354+
 355+ // Module messages in NS_MEDIAWIKI
 356+ foreach( $gadgetModule->getMessages() as $message ) {
 357+ $exportTitles[] = Title::makeTitleSafe( NS_MEDIAWIKI, $message );
 358+ }
 359+
 360+ // Translation subpages of module messages
 361+ // @todo
 362+
 363+ // Get prefixed strings separated by new lines
 364+ $exportList = '';
 365+ foreach ( $exportTitles as $exportTitle ) {
 366+ // Make sure it's not null (for inexisting or invalid title)
 367+ // and addionally check exists() to avoid exporting messages
 368+ // from NS_MEDIAWIKI that don't exist but are 'isAlwaysKnown'
 369+ // due to their default value from PHP messages files
 370+ // (which we don't want to export)
 371+ if ( is_object( $exportTitle ) && $exportTitle->exists() ) {
 372+ $exportList .= $exportTitle->getPrefixedDBkey() . "\n";
 373+ }
158374 }
159375
160 - $wgOut->addHTML( Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) )
 376+ global $wgScript;
 377+ $form =
 378+ Html::openElement( 'form', array(
 379+ 'method' => 'get',
 380+ 'action' => $wgScript,
 381+ 'class' => 'mw-gadgets-exportform'
 382+ ) )
 383+ . '<fieldset><p>'
 384+ . wfMessage( 'gadgets-export-text' )
 385+ ->rawParams(
 386+ htmlspecialchars( $gadget->getId() ),
 387+ '', // $2 is no longer used. To avoid breaking backwards compatibility, skipped here and
 388+ // $3 is used for the new message part
 389+ $this->getLang()->listToText( $msg )
 390+ )
 391+ ->escaped()
 392+ . '</p>'
161393 . Html::hidden( 'title', SpecialPage::getTitleFor( 'Export' )->getPrefixedDBKey() )
162394 . Html::hidden( 'pages', $exportList )
163395 . Html::hidden( 'wpDownload', '1' )
164396 . Html::hidden( 'templates', '1' )
165397 . Xml::submitButton( wfMsg( 'gadgets-export-download' ) )
166 - . Html::closeElement( 'form' )
167 - );
 398+ . '</fieldset></form>';
 399+
 400+ $out->addHTML( $form );
168401 }
 402+
 403+ /**
 404+ * Exports a gadget with its dependencies in a serialized form.
 405+ * Should not be called if the gadget does not exist. $gadget must be
 406+ * an instance of Gadget, not null.
 407+ * @param $gadget Gadget
 408+ */
 409+ public function showSingleGadget( Gadget $gadget ) {
 410+ $this->doSubpageMode();
 411+ $out = $this->getOutput();
 412+
 413+ $this->setHeaders();
 414+ $out->setPagetitle( wfMsg( 'gadgets-gadget-title', $gadget->getTitleMessage() ) );
 415+
 416+ $out->addHTML( '<div class="mw-gadgets-list">' . $this->getGadgetHtml( $gadget ) . '</div>' );
 417+ }
 418+
 419+
 420+ /**
 421+ * Call this method internally to include a breadcrumb navigation on top of the page.
 422+ * Cannot be undone, should only be called once.
 423+ * @return Boolean: True if added, false if not added because already added.
 424+ */
 425+ public function doSubpageMode() {
 426+ static $done = false;
 427+ if ( $done ) {
 428+ return false;
 429+ }
 430+ $done = true;
 431+
 432+ // Would be nice if we wouldn't have to duplicate
 433+ // this from Skin::subPageSubtitle. Slightly modified though
 434+ $subpages = '';
 435+ $ptext = $this->getTitle( $this->par )->getPrefixedText();
 436+ if ( preg_match( '/\//', $ptext ) ) {
 437+ $links = explode( '/', $ptext );
 438+ array_pop( $links );
 439+ $growinglink = '';
 440+ $display = '';
 441+ $c = 0;
 442+
 443+ foreach ( $links as $link ) {
 444+ $growinglink .= $link;
 445+ $display .= $link;
 446+ $linkObj = Title::newFromText( $growinglink );
 447+
 448+ if ( is_object( $linkObj ) ) {
 449+ $getlink = Linker::link( $linkObj, htmlspecialchars( $display ) );
 450+
 451+ $c++;
 452+ if ( $c > 1 ) {
 453+ $subpages .= wfMessage( 'pipe-separator' )->escaped();
 454+ } else {
 455+ // First iteration
 456+ $subpages .= '&lt; ';
 457+ }
 458+
 459+ $subpages .= $getlink;
 460+ $display = '';
 461+ } else {
 462+ $display .= '/';
 463+ }
 464+ $growinglink .= '/';
 465+ }
 466+ }
 467+ $this->getOutput()->setSubtitle( $subpages );
 468+ return true;
 469+ }
169470 }
Index: branches/RL2/extensions/Gadgets/modules/ext.gadgets.api.js
@@ -15,12 +15,12 @@
1616 gadgetCache = {},
1717 /**
1818 * @var {Object} Keyed by repo, array of category objects
19 - * @example { repoName: [ {name: .., title: .., members: .. }, { .. }, { .. } ] }
 19+ * @example { repoName: [ {name: .., title: .., members: .. }, { .. }, { .. } ] }
2020 */
2121 gadgetCategoryCache = {};
22 -
 22+
2323 /* Local functions */
24 -
 24+
2525 /**
2626 * For most returns from api.* functions, a clone is made when data from
2727 * cache is used. This is to avoid situations where later modifications
@@ -42,32 +42,32 @@
4343 */
4444 return $.extend( true /* recursive */, {}, obj );
4545 }
46 -
 46+
4747 function arrClone( arr ) {
4848 return arr.slice();
4949 }
50 -
 50+
5151 /**
5252 * Reformat an array of gadget objects, into an object keyed by the id.
5353 * Note: Maintains object reference
5454 * @param arr {Array}
5555 * @return {Object}
56 - */
 56+ */
5757 function gadgetArrToObj( arr ) {
58 - for( var obj = {}, i = 0, g = arr[i], len = arr.length; i < len; g = arr[++i] ) {
59 - obj[g.id] = g;
60 - }
61 - return obj;
 58+ for( var obj = {}, i = 0, g = arr[i], len = arr.length; i < len; g = arr[++i] ) {
 59+ obj[g.id] = g;
 60+ }
 61+ return obj;
6262 }
63 -
 63+
6464 /**
6565 * Write data to gadgetCache, taking into account that id may be null
6666 * and working around JS's annoying refusal to just let us do
6767 * var foo = {}; foo[bar][baz] = quux;
68 - *
 68+ *
6969 * This sets gadgetCache[repoName][id] = data; if id is not null,
7070 * or gadgetCache[repoName] = data; if id is null.
71 - *
 71+ *
7272 * @param repoName {String} Repository name
7373 * @param id {String|null} Gadget ID or null
7474 * @param data {Object} Data to put in the cache
@@ -82,7 +82,7 @@
8383 gadgetCache[repoName][id] = data;
8484 }
8585 }
86 -
 86+
8787 /**
8888 * Call an asynchronous function for each repository, and merge
8989 * their return values into an object keyed by repository name.
@@ -98,7 +98,7 @@
9999 for ( repo in mw.gadgets.conf.repos ) {
100100 numRepos++;
101101 }
102 -
 102+
103103 // Use $.each instead of a for loop so we can access repoName in the success callback
104104 // without annoying issues
105105 $.each( mw.gadgets.conf.repos, function( repoName, repoData ) {
@@ -114,9 +114,9 @@
115115 );
116116 } );
117117 }
118 -
 118+
119119 /* Public functions */
120 -
 120+
121121 mw.gadgets = {
122122 /**
123123 * @todo: Add something derived from $wgGadgetRepositories to gadgetsConf
@@ -126,7 +126,7 @@
127127 api: {
128128 /**
129129 * Get the gadget blobs for all gadgets from all repositories.
130 - *
 130+ *
131131 * @param success {Function} To be called with an object of arrays of gadget objects, keyed by repository name, as first argument.
132132 * @param error {Function} To be called with a string (error code) as first argument.
133133 */
@@ -136,17 +136,18 @@
137137 success, error
138138 );
139139 },
140 -
 140+
141141 /**
142142 * Get the gadget categories from all repositories.
143 - *
144 - * @param success {Function} To be called with an array
 143+ *
 144+ * @param success {Function} To be called with an array
145145 * @param success {Function} To be called with an object of arrays of category objects, keyed by repository name, as first argument.
146146 * @param error {Function} To be called with a string (error code) as the first argument.
147147 */
148148 getForeignGadgetCategories: function( success, error ) {
149149 mergeRepositoryData( mw.gadgets.api.getGadgetCategories, success, error );
150150 },
 151+
151152 /**
152153 * Get gadget blob from the API (or from cache if available).
153154 *
@@ -212,9 +213,10 @@
213214 }
214215 });
215216 },
 217+
216218 /**
217219 * Get the gadget categories for a certain repository from the API.
218 - *
 220+ *
219221 * @param success {Function} To be called with an array as first argument.
220222 * @param error {Function} To be called with a string (error code) as first argument.
221223 * @param repoName {String} Name of the repository, key in mw.gadgets.conf.repos . Defaults to 'local'
@@ -264,6 +266,7 @@
265267 }
266268 });
267269 },
 270+
268271 /**
269272 * Creates or edits an existing gadget definition.
270273 *
@@ -319,6 +322,7 @@
320323 }
321324 });
322325 },
 326+
323327 /**
324328 * Deletes a gadget definition.
325329 *
Index: branches/RL2/extensions/Gadgets/modules/images/edit.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: branches/RL2/extensions/Gadgets/modules/images/edit.png
___________________________________________________________________
Added: svn:mime-type
326330 + application/octet-stream
Index: branches/RL2/extensions/Gadgets/modules/images/edit-faded.png
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes on: branches/RL2/extensions/Gadgets/modules/images/edit-faded.png
___________________________________________________________________
Added: svn:mime-type
327331 + application/octet-stream
Index: branches/RL2/extensions/Gadgets/modules/ext.gadgets.prejs.css
@@ -1,14 +1,60 @@
2 -.mw-gadgetmanager-gadgets.mw-datatable th {
3 - padding: 2px 21px 2px 5px;
 2+/* Gadget list */
 3+.mw-gadgets-list {
 4+ width: 100%;
 5+ border-bottom: 1px solid #ccc;
46 }
57
6 -.mw-gadgetmanager-gadgets.mw-datatable td {
7 - padding: 2px 5px;
 8+.mw-gadgets-gadget {
 9+ overflow: hidden;
 10+ position: relative;
 11+ padding: 0.5em;
 12+ border: 1px solid #ccc;
 13+ border-bottom: 0;
814 }
915
10 -.mw-gadgetmanager-gadgets-default,
11 -.mw-gadgetmanager-gadgets-hidden,
12 -.mw-gadgetmanager-gadgets-shared {
13 - width: 4em;
14 - text-align: center;
 16+.mw-gadgets-gadget:hover {
 17+ background: #f9f9ff;
1518 }
 19+
 20+.mw-gadgets-title {
 21+ font-weight: bold;
 22+ min-height: 1.6em;
 23+}
 24+
 25+/* Tool links */
 26+
 27+.mw-gadgets-messagelink {
 28+ font-size: 75%;
 29+ font-weight: normal;
 30+}
 31+
 32+.mw-gadgets-messagelink a {
 33+ padding-left: 18px;
 34+ /* @embed */
 35+ background-image: url(images/edit-faded.png);
 36+ background-position: left top;
 37+ background-repeat: no-repeat;
 38+}
 39+
 40+.mw-gadgets-messagelink a:hover {
 41+ /* @embed */
 42+ background-image: url(images/edit.png);
 43+}
 44+
 45+.mw-gadgets-gadgetlinks {
 46+ position: absolute;
 47+ top: 0;
 48+ right: 0;
 49+ height: 1.6em;
 50+ padding: 9px;
 51+ font-size: 75%;
 52+ font-weight: normal;
 53+}
 54+
 55+.mw-gadgets-gadgetlinks {
 56+ float: right;
 57+}
 58+
 59+.mw-gadgets-gadgetlinks a {
 60+ margin: 0 9px;
 61+}
Index: branches/RL2/extensions/Gadgets/modules/ext.gadgets.preferences.js
@@ -1,6 +1,6 @@
22 /**
33 * JavaScript to populate the shared gadgets tab on the preferences page.
4 - *
 4+ *
55 * @author Roan Kattouw
66 * @copyright © 2011 Roan Kattouw
77 * @license GNU General Public Licence 2.0 or later
@@ -19,14 +19,14 @@
2020 .text( text );
2121 return $div.append( $input ).append( '&nbsp;' ).append( $label );
2222 }
23 -
 23+
2424 function buildForm( gadgetsByCategory, categoryNames ) {
2525 var $container = $( '#mw-prefsection-gadgets-shared .mw-input' ),
2626 // Detach the container from the DOM, so we can fill it without visible build-up.
2727 // This is faster, too. In order to put it back where it was, we need to store its parent.
2828 $containerParent = $container.parent();
2929 $container.detach();
30 -
 30+
3131 for ( var category in gadgetsByCategory ) {
3232 if ( category !== '' ) {
3333 $container.append( $( '<h1>' ).text( categoryNames[category] ) );
@@ -38,7 +38,7 @@
3939 // Re-attach the container
4040 $containerParent.append( $container );
4141 }
42 -
 42+
4343 // Temporary testing data
4444 var categoryNames = {
4545 'foo': 'The Foreign Category of Foo'
@@ -55,7 +55,7 @@
5656 'a': 'Gadget A'
5757 }
5858 };
59 -
 59+
6060 $( function() { buildForm( gadgetsByCategory, categoryNames ) } );
61 -
 61+
6262 } )( jQuery );
Index: branches/RL2/extensions/Gadgets/modules/ext.gadgets.gadgetmanager.css
@@ -13,6 +13,10 @@
1414 width: 100%;
1515 }
1616
 17+.mw-gadgetmanager-label {
 18+ width: 20%;
 19+}
 20+
1721 .mw-gadgetmanager-form td,
1822 .mw-gadgetmanager-form th {
1923 vertical-align: top;
@@ -27,8 +31,6 @@
2832 overflow: hidden;
2933 }
3034
31 -.mw-gadgetmanager-propcontainer {}
32 -
3335 .mw-gadgetmanager-prop {
3436 float: left;
3537 margin: 2px 5px 5px 2px;
@@ -39,8 +41,6 @@
4042 line-height: 1;
4143 }
4244
43 -.mw-gadgetmanager-prop-label {}
44 -
4545 .mw-gadgetmanager-prop-delete {
4646 display: inline-block;
4747 width: 10px;
Index: branches/RL2/extensions/Gadgets/modules/ext.gadgets.gadgetmanager.js
@@ -21,19 +21,19 @@
2222 <legend><html:msg key="gadgetmanager-propsgroup-module" /></legend>\
2323 <table>\
2424 <tr>\
25 - <td><label for="mw-gadgetmanager-input-scripts"><html:msg key="gadgetmanager-prop-scripts" /></label></td>\
 25+ <td class="mw-gadgetmanager-label"><label for="mw-gadgetmanager-input-scripts"><html:msg key="gadgetmanager-prop-scripts" /></label></td>\
2626 <td><input type="text" id="mw-gadgetmanager-input-scripts" /></td>\
2727 </tr>\
2828 <tr>\
29 - <td><label for="mw-gadgetmanager-input-styles"><html:msg key="gadgetmanager-prop-styles" /></label></td>\
 29+ <td class="mw-gadgetmanager-label"><label for="mw-gadgetmanager-input-styles"><html:msg key="gadgetmanager-prop-styles" /></label></td>\
3030 <td><input type="text" id="mw-gadgetmanager-input-styles" /></td>\
3131 </tr>\
3232 <tr>\
33 - <td><label for="mw-gadgetmanager-input-dependencies"><html:msg key="gadgetmanager-prop-dependencies" /></label></td>\
 33+ <td class="mw-gadgetmanager-label"><label for="mw-gadgetmanager-input-dependencies"><html:msg key="gadgetmanager-prop-dependencies" /></label></td>\
3434 <td><input type="text" id="mw-gadgetmanager-input-dependencies" /></td>\
3535 </tr>\
3636 <tr>\
37 - <td><label for="mw-gadgetmanager-input-messages"><html:msg key="gadgetmanager-prop-messages" /></label></td>\
 37+ <td class="mw-gadgetmanager-label"><label for="mw-gadgetmanager-input-messages"><html:msg key="gadgetmanager-prop-messages" /></label></td>\
3838 <td><input type="text" id="mw-gadgetmanager-input-messages" /></td>\
3939 </tr>\
4040 </table>\
@@ -42,23 +42,23 @@
4343 <legend><html:msg key="gadgetmanager-propsgroup-settings" /></legend>\
4444 <table>\
4545 <tr>\
46 - <td><label for="mw-gadgetmanager-input-category"><html:msg key="gadgetmanager-prop-category" /></label></td>\
 46+ <td class="mw-gadgetmanager-label"><label for="mw-gadgetmanager-input-category"><html:msg key="gadgetmanager-prop-category" /></label></td>\
4747 <td><select id="mw-gadgetmanager-input-category"></select></td>\
4848 </tr>\
4949 <tr>\
50 - <td><label for="mw-gadgetmanager-input-rights"><html:msg key="gadgetmanager-prop-rights" /></label></td>\
 50+ <td class="mw-gadgetmanager-label"><label for="mw-gadgetmanager-input-rights"><html:msg key="gadgetmanager-prop-rights" /></label></td>\
5151 <td><input type="text" id="mw-gadgetmanager-input-rights" /></td>\
5252 </tr>\
5353 <tr>\
54 - <td><label for="mw-gadgetmanager-input-default"><html:msg key="gadgetmanager-prop-default" /></label></td>\
 54+ <td class="mw-gadgetmanager-label"><label for="mw-gadgetmanager-input-default"><html:msg key="gadgetmanager-prop-default" /></label></td>\
5555 <td><input type="checkbox" id="mw-gadgetmanager-input-default" /></td>\
5656 </tr>\
5757 <tr>\
58 - <td><label for="mw-gadgetmanager-input-hidden"><html:msg key="gadgetmanager-prop-hidden"></label></td>\
 58+ <td class="mw-gadgetmanager-label"><label for="mw-gadgetmanager-input-hidden"><html:msg key="gadgetmanager-prop-hidden"></label></td>\
5959 <td><input type="checkbox" id="mw-gadgetmanager-input-hidden" /></td>\
6060 </tr>\
6161 ' + ( ga.conf.enableSharing ? '<tr>\
62 - <td><label for="mw-gadgetmanager-input-shared"><html:msg key="gadgetmanager-prop-shared" /></label></td>\
 62+ <td class="mw-gadgetmanager-label"><label for="mw-gadgetmanager-input-shared"><html:msg key="gadgetmanager-prop-shared" /></label></td>\
6363 <td><input type="checkbox" id="mw-gadgetmanager-input-shared" /></td>\
6464 </tr>\
6565 ' : '' ) + '</table>\
@@ -96,14 +96,14 @@
9797 * Utility function to pad a zero
9898 * to single digit number. Used by ISODateString().
9999 * @param n {Number}
100 - * @return {String}
 100+ * @return {String|Number}
101101 */
102102 function pad( n ) {
103103 return n < 10 ? '0' + n : n;
104104 }
105105 /**
106106 * Format a date in an ISO 8601 format using UTC.
107 - * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date#Example:_ISO_8601_formatted_dates
 107+ * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date#Example:_ISO_8601
108108 *
109109 * @param d {Date}
110110 * @return {String}
@@ -125,12 +125,40 @@
126126 * to the anchor tags in the table.
127127 */
128128 initUI: function() {
129 - // Bind trigger to the links
130 - $( '.mw-gadgetmanager-gadgets .mw-gadgetmanager-gadgets-title a' )
131 - .click( function( e ) {
 129+ // Add ajax links
 130+ $( '.mw-gadgets-gadgetlinks' ).each( function( i, el ) {
 131+ var $el = $( el );
 132+ if ( ga.conf.userIsAllowed['gadgets-definition-edit'] ) {
 133+ $el.find( '.mw-gadgets-modify' ).click( function( e ) {
 134+ e.preventDefault();
 135+ ga.ui.startGadgetEditor( $el.data( 'gadget-id' ) );
 136+ });
 137+ }
 138+ if ( ga.conf.userIsAllowed['gadgets-definition-delete'] ) {
 139+ $el.find( '.mw-gadgets-modify' ).click( function( e ) {
 140+ e.preventDefault();
 141+ // @todo: Show delete action form
 142+ });
 143+ }
 144+ } );
 145+
 146+ if ( ga.conf.userIsAllowed['gadgets-definition-create'] ) {
 147+ var createTab = mw.util.addPortletLink(
 148+ // Not all skins use the new separated tabs yet,
 149+ // Fall back to the general 'p-cactions'.
 150+ $( '#p-views' ).length ? 'p-views' : 'p-cactions',
 151+ '#',
 152+ mw.msg( 'gadgets-gadget-create' ),
 153+ 'ca-create', // Use whatever core has for pages ? Or use gadget-create ?
 154+ mw.msg( 'gadgets-gadget-create-tooltip' ),
 155+ 'e' // Same as core for ca-edit
 156+ );
 157+ $( createTab ).click( function( e ) {
132158 e.preventDefault();
133 - ga.ui.startGadgetEditor( $( this ).data( 'gadget-id' ) );
134 - });
 159+ // @todo: Trigger edit form with editable field for gadget id.
 160+ } );
 161+ }
 162+
135163 },
136164
137165 /**
@@ -232,6 +260,7 @@
233261 response( suggestCacheScripts[data.term] );
234262 return;
235263 }
 264+
236265 $.getJSON( mw.util.wikiScript( 'api' ), {
237266 format: 'json',
238267 action: 'query',
@@ -394,7 +423,6 @@
395424 .prop( 'checked', metadata.settings.shared )
396425 .change( function() { metadata.settings.shared = this.checked; });
397426
398 -
399427 return $form;
400428 }
401429 };
Index: branches/RL2/extensions/Gadgets/api/ApiQueryGadgetCategories.php
@@ -54,7 +54,7 @@
5555 }
5656 if ( isset( $this->props['title'] ) ) {
5757 if ( $category === '' ) {
58 - $row['title'] = wfMessage( 'gadgetmanager-uncategorized' )->plain();
 58+ $row['title'] = wfMessage( 'gadgets-uncategorized' )->plain();
5959 } else {
6060 $row['title'] = $repo->getCategoryTitle( $category, $this->language );
6161 }

Follow-up revisions

RevisionCommit summaryAuthorDate
r98853Followup r98729: fix MIME type for imagescatrope10:24, 4 October 2011
r98854Fix various typos in r97513 and r98729catrope10:31, 4 October 2011
r98876[RL2] Address r98729 CR (Title::makeSafeTitle possibly returning null)...krinkle17:46, 4 October 2011
r98938[RL2] Bring constancy in tooltip messages on Special:Gadgets...krinkle22:12, 4 October 2011
r98940[RL2] Address r98729 CR...krinkle22:25, 4 October 2011

Comments

#Comment by Krinkle (talk | contribs)   01:27, 3 October 2011

Commit message cut off.


[RL2] Merge gadget manager into SpecialGadgets

  • Rewrite SpecialGadgets to use the new repository backend. While at it, made it use context variables instead of globals.
  • Removed left-overs from SpecialGadgetManager
  • Redesign the layout. Previously it was mostly just a long unordered list with titles, descriptions and comma-separated lists for the metadata. Redesigned to be less tech-savvy. Using Vector-style icons and progressive enhancements.
  • The export-feature of the old SpecialGadgets was kept. Although changed to use modern core interface and different page collections. The gadget manager structor also made an annoying thing obsolete – the need to manually copy-paste the definition on to MediaWiki:Gadgets-definition (which Special:Export/Import can't do). Since that's a separate wiki page. We can now completely export and import an entire gadget including it's definition, js/css resources and interface messages and translations. Awesome!
  • The permalink-system is still a stub but it's there (Special:Gadgets/id). For now this is only linked from the edit summaries used by the gadget manager when saving to NS_GADGET_DEFINITION.
  • Added a group of boolean variables to gadgetConf in mw.config. Related to whether the current logged-in user has the ability to gadgets-edit, gadget-definition-create, gadget-definition-edit and gadget-definition-delete. We need those in JS.
    • In JS checking these variables to determine wether to add a 'Create' tab, and whether or not to add the 'modify gadget' and 'delete gadget' tool links to each gadget block.
  • Reorganize i18n messages since SpecialGadgetManager is no more. (also small edit in ApiQueryGadgetCategories.php to use the new message key)
  • Moved some logic out of SpecialGadget::execute into their own method. SpecialGadgets::execute is now purely an interpreter of the query-string and the subpage-syntax and dispatching from there calling the appropriate method.
  • Creating modify/delete links from php now instead of from JavaScript. In PHP they link to the NS_GADGET_DEFINITION pages (action=[edit/delete]). JavaScript then binds events to them to trigger the modal dialogs. This also prevents any remote scenario where the links 'jump' as they're already there now.
  • Added new utility function getDefinitionTitleFromID to avoid repeating or decentralizing this logic. Perhaps this can get a better place in one of the backend classes. Put in GadgetHooks for now (next to getIDFromTitle)
#Comment by Catrope (talk | contribs)   10:31, 4 October 2011

The 'gadgets-nosuchaction' message is used in two different cases: one where the action really doesn't exist, and one where the action is export and the gadget doesn't exist. In the latter case, using the nosuchaction message is confusing.

+				GadgetHooks::getDefinitionTitleFromID( $gadget->getId() ),
[...]
+			$t = Title::makeTitleSafe( NS_MEDIAWIKI, $gadget->getTitleMessageKey() . $suffix );

getDefinitionTitleFromID() and makeTitleSafe() can return null, but this is not checked. In the latter case (which occurs twice), that can lead to a fatal. Marking fixme.

+				array( 'title' => wfMessage( 'gadgets-message-edit-tooltip', $t->getPrefixedText() ) ),

Please explicitly call ->plain() on the Message object here. Best I can tell, the default behavior of Message::toString() seems to be 'parse', which is not what you want here. (Occurs twice.)

+			Title::newFromText( 'Special:Recentchanges/namespace=' . NS_GADGET_DEFINITION )->getPrefixedText()

You'll want to use localized titles here, using SpecialPage::getTitleFor( 'Recentchanges', 'namespace=' . NS_GADGET_DEFINITION )

+				$t = Title::makeTitleSafe( NS_MEDIAWIKI, "gadgetcategory-{$category}{$suffix}" );
+				$editLink = Linker::link(
+					$t,
+					wfMessage( 'gadgets-message-edit' )->escaped(),
+					array( 'title' => wfMessage( 'gadgets-message-edit-tooltip', $t->getPrefixedText() ) ),

Again, $t is not checked for null and ->plain() is not called on the tooltip Message object.

+		// NS_GADGET_DEFINITION page of this gadget
+			$exportTitles[] = GadgetHooks::getDefinitionTitleFromID( $gadget->getId() );
+
+		// Title message in NS_MEDIAWIKI
+				$exportTitles[] = Title::makeTitleSafe( NS_MEDIAWIKI, $gadget->getTitleMessageKey() );

Indentation?!? It's messed up for the next 10-15 lines as well.

+	 * Exports a gadget with its dependencies in a serialized form.

The word "Exports" is misleading and suggests the functionality is related to Special:Export.

+		// this from Skin::subPageSubtitle. Slightly modified though

sofixit. It looks like the only thing wrong with subPageSubtitle() is that it checks for if ( $out->isArticle() && MWNamespace::hasSubpages( $out->getTitle()->getNamespace() ) ) { . The code inside that could be factored out into another function, which SpecialGadgets would then call directly.

+				if ( ga.conf.userIsAllowed['gadgets-definition-delete'] ) {
+					$el.find( '.mw-gadgets-modify' ).click( function( e ) {

This typo was fixed in r98837.

OK otherwise, but marking fixme for lack of null checks and lack of explicit ->plain() calls.

#Comment by Krinkle (talk | contribs)   22:41, 4 October 2011

getDefinitionTitleFromID() and makeTitleSafe() can return null, but this is not checked. In the latter case (which occurs twice), that can lead to a fatal. Marking fixme.

getDefinitionTitleFromID 's documentation covers for it returning in instance of Title or null. Fixed the other calls to Title::makeTitleSafe in r98876.

Please explicitly call ->plain() on the Message object here. Best I can tell, the default behavior of Message::toString() seems to be 'parse', which is not what you want here. (Occurs twice.)

Together with other message fine-tuning, done in r98938.


+		$out->addWikiMsg( 'gadgets-pagetext',
+			Title::newFromText( 'Special:Recentchanges/namespace=' . NS_GADGET_DEFINITION )->getPrefixedText()
+		);

You'll want to use localized titles here, using SpecialPage::getTitleFor( 'Recentchanges', 'namespace=' . NS_GADGET_DEFINITION )

The first argument is used as a link target ([[$1|foo]]). When the linker parses this it localizes the url. That's why I didn't see it on my local dutch wiki (I specifically tested this to make sure it wouldn't parse a link that redirects). However were $1 to be used in a different way, then it'd be English only. Fixed in r98940.

Whitespace fixed in r98940 also.

The word "Exports" is misleading and suggests the functionality is related to Special:Export.

It is indeed related. This code was mostly kept from the old Gadgets extension. It's a hidden form that posts to Special:Export.

+		// Would be nice if we wouldn't have to duplicate
+		// this from Skin::subPageSubtitle. Slightly modified though

sofixit. It looks like the only thing wrong with subPageSubtitle() is that it checks for if ( $out->isArticle() && MWNamespace::hasSubpages( $out->getTitle()->getNamespace() ) ) { . The code inside that could be factored out into another function, which SpecialGadgets would then call directly.

That's something we could do later in core. Note though that the code that follows has been slightly modified (as noted), I'm not sure the core function would be useful even if the code inside that if statement would be moved into a separate Skin-class method.

#Comment by Catrope (talk | contribs)   09:21, 5 October 2011

Re the exports issue: what I meant is that that comment is also used for showSingleGadget(), which looks like a copypaste snafu now that I look at it again.

Status & tagging log