http://www.mediawiki.org/wiki/Special:Code/MediaWiki/88986
Revision: 88986
Author: salvatoreingala
Date: 2011-05-27 18:13:15 +0000 (Fri, 27 May 2011)
Log Message:
-----------
- Completed a simple prototype with most features
- This is incomplete, buggy and inefficient; only intended as a
concept to check architectural feasibility
- Most incomplete/bad things should be marked with TODOs, though
Modified Paths:
--------------
branches/salvatoreingala/Gadgets/Gadgets.i18n.php
branches/salvatoreingala/Gadgets/Gadgets.php
branches/salvatoreingala/Gadgets/GadgetsAjax.php
branches/salvatoreingala/Gadgets/Gadgets_body.php
branches/salvatoreingala/Gadgets/Gadgets_tests.php
branches/salvatoreingala/Gadgets/modules/ext.gadgets.preferences.js
Property Changed:
----------------
branches/salvatoreingala/Gadgets/
branches/salvatoreingala/Gadgets/Gadgets_body.php
Property changes on: branches/salvatoreingala/Gadgets
___________________________________________________________________
Added: svn:mergeinfo
+ /trunk/extensions/Gadgets:88253-88717
Modified: branches/salvatoreingala/Gadgets/Gadgets.i18n.php
===================================================================
--- branches/salvatoreingala/Gadgets/Gadgets.i18n.php 2011-05-27 17:59:32 UTC
(rev 88985)
+++ branches/salvatoreingala/Gadgets/Gadgets.i18n.php 2011-05-27 18:13:15 UTC
(rev 88986)
@@ -917,6 +917,8 @@
'gadgets-pagetext' => 'Lischt vu spezielle Gadgets, wu fir jede
Benutzer in syyne [[Special:Preferences|persenlige Yystellige]] verfiegbar sin,
wie s [[MediaWiki:Gadgets-definition|definiert]] isch.
Die Ibersicht bietet e direkte Zuegang zue dr Syschtemnochrichte, wu d
Bschryybig un dr Programmcode vu jedem Gadget din sin.',
'gadgets-uses' => 'Bruucht',
+ 'gadgets-required-rights' => 'Brucht {{PLURAL:$2|des Rächt:|die
Rächt:}} $1',
+ 'gadgets-default' => 'Fir alli standardmäßig aktiviert.',
'gadgets-export' => 'Exportiere',
'gadgets-export-title' => 'Hälferli exportiere',
'gadgets-not-found' => 'Hälferli „$1“ isch nit gfunde wore.',
@@ -1752,6 +1754,7 @@
'gadgets-title' => 'Tilleggsfunksjoner',
'gadgets-pagetext' => 'Nedenfor er en liste over tilleggsfunksjoner
brukere kan slå på i [[Special:Preferences|innstillingene]], som definert på
[[MediaWiki:Gadgets-definition]]. Denne oversikten gir lett tilgang til
systembeskjedsidene som definerer hvert verktøys beskrivelse og kode.',
'gadgets-uses' => 'Bruk',
+ 'gadgets-required-rights' => 'Krever {{PLURAL:$2|$1 rettighet|følgende
rettigheter: $1}}.',
'gadgets-default' => 'Aktivert for alle som standard',
'gadgets-export' => 'Eksporter',
'gadgets-export-title' => 'Tilleggsfunksjon eksport',
@@ -2067,6 +2070,7 @@
Этот список позволяет легко получить доступ к страницам системных сообщений,
определяющих описания и исходные коды гаджетов.',
'gadgets-uses' => 'Туһанар',
'gadgets-required-rights' => '$2 бэйэбил (быраап) ирдэнэр: «$1»',
+ 'gadgets-default' => 'Барыларыгар холбоно сылдьар.',
'gadgets-export' => 'Экспортаа',
'gadgets-export-title' => 'Ҕааддьыты таһаарыы (экспорт)',
'gadgets-not-found' => '"$1" ҕааддьыт көстүбэтэ.',
@@ -2287,6 +2291,8 @@
'gadgets-pagetext' => 'Härunder finns en lista över finesser som
användare kan aktivera i sina [[Special:Preferences|inställningar]], definierad
av [[MediaWiki:Gadgets-definition|definieringarna]].
Den här översikten ger enkel åtkomst till de systemmeddelanden som definierar
beskrivningarna och koden för varje finess.',
'gadgets-uses' => 'Använder',
+ 'gadgets-required-rights' => 'Kräver {{PLURAL:$2|$1 rättighet|följande
rättigheter: $1}}.',
+ 'gadgets-default' => 'Som standard aktiverat för alla.',
'gadgets-export' => 'Exportera',
'gadgets-export-title' => 'Exportera finess',
'gadgets-not-found' => 'Tillägg "$1" hittades inte.',
Modified: branches/salvatoreingala/Gadgets/Gadgets.php
===================================================================
--- branches/salvatoreingala/Gadgets/Gadgets.php 2011-05-27 17:59:32 UTC
(rev 88985)
+++ branches/salvatoreingala/Gadgets/Gadgets.php 2011-05-27 18:13:15 UTC
(rev 88986)
@@ -34,6 +34,7 @@
$wgHooks['GetPreferences'][] = 'GadgetHooks::getPreferences';
$wgHooks['ResourceLoaderRegisterModules'][] = 'GadgetHooks::registerModules';
$wgHooks['UnitTestsList'][] = 'GadgetHooks::unitTestsList';
+$wgHooks['UserLoadOptions'][] = 'GadgetHooks::userLoadOptions';
$dir = dirname(__FILE__) . '/';
$wgExtensionMessagesFiles['Gadgets'] = $dir . 'Gadgets.i18n.php';
@@ -45,8 +46,8 @@
$wgAutoloadClasses['GadgetHooks'] = $dir . 'Gadgets_body.php';
$wgAutoloadClasses['GadgetResourceLoaderModule'] = $dir . 'Gadgets_body.php';
$wgAutoloadClasses['SpecialGadgets'] = $dir . 'SpecialGadgets.php';
+$wgAutoloadClasses['GadgetsGlobalModule'] = $dir . 'Gadgets_body.php';
$wgAutoloadClasses['GadgetsAjax'] = $dir . 'GadgetsAjax.php';
-$wgAutoloadClasses['GadgetsSpecialPreferencesTweaksModule'] = $dir .
'GadgetsAjax.php';
$wgSpecialPages['Gadgets'] = 'SpecialGadgets';
$wgSpecialPageGroups['Gadgets'] = 'wiki';
@@ -57,6 +58,13 @@
$wgAjaxExportList[] = 'GadgetsAjax::getUI';
$wgAjaxExportList[] = 'GadgetsAjax::setPreferences';
+$wgResourceModules['ext.gadgets'] = array(
+ 'class' => 'GadgetsGlobalModule'
+);
+
$wgResourceModules['ext.gadgets.preferences'] = array(
- 'class' => 'GadgetsSpecialPreferencesTweaksModule'
+ 'scripts' => array( 'ext.gadgets.preferences.js' ),
+ 'dependencies' => array( 'jquery', 'jquery.json', 'jquery.ui.dialog',
'mediawiki.htmlform', 'ext.gadgets' ),
+ 'localBasePath' => $dir . 'modules/',
+ 'remoteExtPath' => 'Gadgets/modules'
);
Modified: branches/salvatoreingala/Gadgets/GadgetsAjax.php
===================================================================
--- branches/salvatoreingala/Gadgets/GadgetsAjax.php 2011-05-27 17:59:32 UTC
(rev 88985)
+++ branches/salvatoreingala/Gadgets/GadgetsAjax.php 2011-05-27 18:13:15 UTC
(rev 88986)
@@ -1,24 +1,39 @@
<?php
-class GadgetsAjax {
- public static function getUI( /*$args...*/ ) {
- global $wgUser;
-
- if ( $wgUser->isAnon() ) {
+class GadgetsAjax {
+
+ //Common validation code
+ //Checks if the user is logged and check params syntax
+ //returns error string if vaildation is failed, true otherwise
+ private static function validateSyntax( $args ) {
+ $user = RequestContext::getMain()->getUser();
+ if ( $user->isAnon() ) {
return '<err#>' . wfMsgExt( 'gadgets-ajax-notallowed',
'parseinline' );
}
-
- //params are in the format "param|val"
- $args = func_get_args();
+ //checks if all params are of the form 'param|value'
foreach ( $args as $arg ) {
$set = explode( '|', $arg, 2 );
if ( count( $set ) != 2 ) {
return '<err#>' . wfMsgExt(
'gadgets-ajax-wrongparams', 'parseinline' );
}
+ }
+
+ return true;
+ }
+
+ public static function getUI( /*$args...*/ ) {
+ //params are in the format "param|val"
+ $args = func_get_args();
+
+ $res = self::validateSyntax( $args );
+ if ( $res !== true ) {
+ return $res;
+ }
+
+ foreach ( $args as $arg ) {
+ list( $par, $val ) = explode( '|', $arg, 2 );;
- list( $par, $val ) = $set;
-
switch( $par ) {
case "gadget":
$gadget = $val;
@@ -49,14 +64,72 @@
//TODO: options of "select" and similar fields cannot be passed
as messages
$form = new HTMLForm( $prefs, RequestContext::getMain() );
- $form->mFieldData = Gadget::getUserPrefs( $wgUser, $gadget );
+ $user = RequestContext::getMain()->getUser();
+
+ $form->mFieldData = Gadget::getUserPrefs( $user, $gadget );
- //TODO: HTMLForm::getBody is not meant to be public, a
refactoring is needed
+ //TODO: HTMLForm::getBody is not meant to be public, a
refactoring is needed
+ // (or a completely different solution)
return $form->getBody();
}
public static function setPreferences( /* args */ ) {
- //TODO
- throw new MWException( 'Not implemented' );
+ //TODO: should probably add tokens
+
+ //params are in the format "param|val"
+ $args = func_get_args();
+
+ $res = self::validateSyntax( $args );
+ if ( $res !== true ) {
+ return $res;
+ }
+
+ foreach ( $args as $arg ) {
+ list( $par, $val ) = explode( '|', $arg, 2 );;
+
+ switch( $par ) {
+ case "gadget":
+ $gadget = $val;
+ break;
+ case "json":
+ $json = $val;
+ break;
+ default:
+ return '<err#>' . wfMsgExt(
'gadgets-ajax-wrongparams', 'parseinline' );
+ }
+ }
+
+ if ( !isset( $gadget ) || !isset( $json ) ) {
+ return '<err#>' . wfMsgExt( 'gadgets-ajax-wrongparams',
'parseinline' );
+ }
+
+ $prefsDescriptionJson = Gadget::getGadgetPrefsDescription(
$gadget );
+ $prefsDescription = FormatJson::decode( $prefsDescriptionJson,
true );
+
+ if ( $prefsDescription === null ) {
+ //either the gadget doesn't exists or it exists but it
has no prefs
+ return '<err#>' . wfMsgExt( 'gadgets-ajax-wrongparams',
'parseinline' );
+ }
+
+ $userPrefs = FormatJson::decode( $json, true );
+
+ if ( $userPrefs === null ) {
+ return '<err#>' . wfMsgExt( 'gadgets-ajax-wrongparams',
'parseinline' );
+ }
+
+ foreach ( $userPrefs as $pref => $value ) {
+ if ( !isset( $prefsDescription[$pref] ) ){
+ //Nonexisting configuration parameter; ignore it
+ unset( $userPrefs[$pref] );
+ } else {
+ //TODO: convert values to proper type, check
coherency with specification
+ // and fix fields that don't pass
validation
+ }
+ }
+
+ $user = RequestContext::getMain()->getUser();
+ Gadget::setUserPrefs( $user, $gadget, $userPrefs );
+
+ return 'true';
}
}
Modified: branches/salvatoreingala/Gadgets/Gadgets_body.php
===================================================================
--- branches/salvatoreingala/Gadgets/Gadgets_body.php 2011-05-27 17:59:32 UTC
(rev 88985)
+++ branches/salvatoreingala/Gadgets/Gadgets_body.php 2011-05-27 18:13:15 UTC
(rev 88986)
@@ -155,6 +155,24 @@
}
/**
+ * UserLoadOptions hook handler.
+ * @param $user
+ * @param &$options
+ */
+ public static function userLoadOptions( $user, &$options ) {
+ //Remove gadget-*-config options, since they must not be
delivered like other user preferences
+ foreach ( $options as $option => $value ){
+ //TODO: Regexsp not coherent with current gadget's
naming rules
+ if ( preg_match(
'/gadget-[a-zA-Z][a-zA-Z0-9_]*-config/', $option ) ) {
+ //TODO: cache them before unsetting
+ unset( $options[$option] );
+ }
+ }
+ return true;
+ }
+
+
+ /**
* Adds one legacy script to output.
*
* @param $page String: Unprefixed page title
@@ -548,6 +566,8 @@
}
+ //TODO: put the following static methods somewhere else
+
public static function isGadgetPrefsDescriptionValid( $prefsJson ) {
$prefs = FormatJson::decode( $prefsJson, true );
@@ -610,24 +630,89 @@
//Get user's preferences for a specific gadget
public static function getUserPrefs( $user, $gadget ) {
- //TODO
- //for now, we just return defaults
- $prefsJson = Gadget::getGadgetPrefsDescription( $gadget );
+ //TODO: cache!
+
+ $prefsDescriptionJson = Gadget::getGadgetPrefsDescription(
$gadget );
- if ( $prefsJson === null || $prefsJson === '' ) {
+ if ( $prefsDescriptionJson === null || $prefsDescriptionJson
=== '' ) {
return null;
}
- $prefs = FormatJson::decode( $prefsJson, true );
+ $prefsDescription = FormatJson::decode( $prefsDescriptionJson,
true );
- $userPrefs = array();
- foreach ( $prefs as $pref => $value ) {
- $userPrefs[$pref] = $value["default"];
+ $dbr = wfGetDB( DB_SLAVE );
+
+ $id = $user->getId();
+ $property = "gadget-{$gadget}-config";
+
+ $res = $dbr->selectRow(
+ 'user_properties',
+ array('up_value'),
+ array(
+ "up_user={$id}",
+ "up_property='{$property}'"
+ ),
+ __METHOD__);
+
+
+ if ( !$res ) {
+ return null;
}
-
+
+ $userPrefsJson = $res->up_value;
+
+ $userPrefs = FormatJson::decode( $userPrefsJson, true );
+
+ //TODO: validate against description, fix mismatches
+
return $userPrefs;
}
+
+ //Set user's preferences for a specific gadget
+ public static function setUserPrefs( $user, $gadget, $preferences ) {
+ //TODO: proper param checking
+
+ $preferencesJson = FormatJson::encode( $preferences );
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ $id = $user->getId();
+ $property = "gadget-{$gadget}-config";
+
+ $row = array(
+ 'up_user' => $id,
+ 'up_property' => $property,
+ 'up_value' => $preferencesJson
+ );
+
+ //Could probably be done with the "ON DUPLICATE KEY UPDATE"
syntax
+
+ $res = $dbw->update(
+ 'user_properties',
+ $row,
+ array(
+ "up_user={$id}",
+ "up_property='{$property}'"
+ ),
+ __METHOD__
+ );
+
+
+ $rc = $dbw->affectedRows();
+ if ( $rc == 0 ) {
+ $dbw->insert(
+ 'user_properties',
+ $row,
+ __METHOD__,
+ array( 'IGNORE' ) //ignore insertions without
any changes
+ );
+ }
+
+ //Invalidate cache and update user_touched
+ $user->invalidateCache( true );
+ }
+
}
/**
@@ -664,26 +749,52 @@
* @return Array: Names of resources this module depends on
*/
public function getDependencies() {
+ //return array_merge( $this->dependencies, array(
'mediawiki.user' ) );
return $this->dependencies;
}
+
+ public function getScript( ResourceLoaderContext $context ) {
+ $moduleName = $this->getName();
+ $gadget = substr( $moduleName, strlen( 'ext.gadget.' ) );
+
+
+ $user = RequestContext::getMain()->getUser();
+
+ $prefs = Gadget::getUserPrefs( $user, $gadget );
+
+ //Enclose gadget's code in a closure, with "this" bound to the
+ //configuration object (or to "window" for non-configurable
gadgets)
+ $header = '(function(){';
+
+ $boundObject = array( 'config' => $prefs );
+
+ if ( $prefs !== NULL ) {
+ //Bind configuration object to "this".
+ //TODO: would be nice add other metadata for the gadget
+ $footer = '}).' . Xml::encodeJsCall( 'apply',
+ array( $boundObject, array() )
+ ) . ';';
+ } else {
+ //Bind window to "this"
+ $footer = '}).apply( window, [] );';
+ }
+
+ return $header . parent::getScript( $context ) . $footer;
+ }
+
+
+ public function getModifiedTime( ResourceLoaderContext $context ) {
+ $touched = RequestContext::getMain()->getUser()->getTouched();
+
+ return max( parent::getModifiedTime( $context ), $touched );
+ }
}
-
-//Module to tweak Special:Preferences
-class GadgetsSpecialPreferencesTweaksModule extends ResourceLoaderFileModule {
- public function __construct() {
- parent::__construct( array(
- 'scripts' => array(
'ext.gadgets.preferences.js' ),
- 'dependencies' => array( 'jquery',
'jquery.ui.dialog', 'mediawiki.htmlform' ),
- 'localBasePath' => dirname( __FILE__ ) .
'/modules/',
- 'remoteExtPath' => 'Gadgets/modules'
- )
- );
- }
+//Implements ext.gadgets. Required by ext.gadgets.preferences
+class GadgetsGlobalModule extends ResourceLoaderModule {
+ //TODO: should override getModifiedTime()
public function getScript( ResourceLoaderContext $context ) {
- global $wgUser;
-
$configurableGadgets = array();
$gadgetsList = Gadget::loadStructuredList();
@@ -695,14 +806,10 @@
}
}
}
-
- //TODO: broken in debug mode
- //create the mw.gadgets object
+
$script = "mw.gadgets = {}\n";
- //needed by ext.gadgets.preferences.js
$script .= "mw.gadgets.configurableGadgets = " .
Xml::encodeJsVar( $configurableGadgets ) . ";\n";
- $script .= parent::getScript( $context );
-
return $script;
}
}
+
Property changes on: branches/salvatoreingala/Gadgets/Gadgets_body.php
___________________________________________________________________
Modified: svn:mergeinfo
- /branches/Gadgets-work/Gadgets_body.php:73145-76526
/branches/wmf/1.17wmf1/extensions/Gadgets/Gadgets_body.php:81884
+ /branches/Gadgets-work/Gadgets_body.php:73145-76526
/branches/wmf/1.17wmf1/extensions/Gadgets/Gadgets_body.php:81884
/trunk/extensions/Gadgets/Gadgets_body.php:88253-88717
Modified: branches/salvatoreingala/Gadgets/Gadgets_tests.php
===================================================================
--- branches/salvatoreingala/Gadgets/Gadgets_tests.php 2011-05-27 17:59:32 UTC
(rev 88985)
+++ branches/salvatoreingala/Gadgets/Gadgets_tests.php 2011-05-27 18:13:15 UTC
(rev 88986)
@@ -45,8 +45,20 @@
}
function testPreferences() {
- global $wgUser, $wgOut;
+ global $wgUser;
+
+ // This test makes call to the parser which requires valids
Outputpage
+ // and Title objects. Set them up there, they will be released
at the
+ // end of the test.
+ global $wgOut, $wgTitle;
+ $old_wgOut = $wgOut;
+ $old_wgTitle = $wgTitle;
+ $wgTitle = Title::newFromText( 'Parser test for Gadgets
extension' );
+
+ // Proceed with test setup:
$prefs = array();
+ $context = new RequestContext();
+ $wgOut = $context->getOutput();
$wgOut->setTitle( Title::newFromText( 'test' ) );
Gadget::loadStructuredList( '* foo | foo.js
@@ -62,5 +74,9 @@
$this->assertFalse( isset(
$options['<gadget-section-remove-section>'] ), 'Must not show empty
sections' );
$this->assertTrue( isset(
$options['<gadget-section-keep-section1>'] ) );
$this->assertTrue( isset(
$options['<gadget-section-keep-section2>'] ) );
+
+ // Restore globals
+ $wgOut = $old_wgOut;
+ $wgTitle = $old_wgTitle;
}
-}
\ No newline at end of file
+}
Modified: branches/salvatoreingala/Gadgets/modules/ext.gadgets.preferences.js
===================================================================
--- branches/salvatoreingala/Gadgets/modules/ext.gadgets.preferences.js
2011-05-27 17:59:32 UTC (rev 88985)
+++ branches/salvatoreingala/Gadgets/modules/ext.gadgets.preferences.js
2011-05-27 18:13:15 UTC (rev 88986)
@@ -2,6 +2,45 @@
* JavaScript tweaks for Special:Preferences
*/
( function( $, mw ) {
+
+ //"Save" button click handler
+ function saveConfig( $dialog, gadget ) {
+ var config = {};
+
+ //Inputs are all the children of $dialog whose id starts with
"mw-input-wp"
+ //TODO: fix this, there is no warranty that this doesn't
conflicts with other existing ids.
+ $dialog.find( '[id ^= "mw-input-wp"]' ).each( function( i, el )
{
+ var param = el.id.substring( "mw-input-wp".length );
+ config[param] = $( el ).val(); //TODO: this only work
for simpler fields
+ } );
+
+ var json = $.toJSON( config );
+
+ var postData = 'action=ajax&rs=GadgetsAjax::setPreferences' +
+ '&rsargs[]=gadget|' + encodeURIComponent(
gadget ) +
+ '&rsargs[]=json|' + encodeURIComponent( json );
+
+ $.ajax( {
+ url: mw.config.get( 'wgScriptPath' ) + '/index.php',
+ type: "POST",
+ data: postData,
+ dataType: "json",
+ success: function( response ) {
+ if ( response === true ) {
+ alert( 'Configuration saved
successfully' );
+ $dialog.dialog( 'close' );
+ } else {
+ //TODO
+ alert( 'Something wrong happened' );
+ }
+ },
+ error: function( response ) {
+ //TODO
+ alert( 'Something wrong happened' );
+ }
+ } );
+ }
+
$( '#mw-htmlform-gadgets input[name="wpgadgets[]"]' ).each( function(
idx, input ) {
var id = input.id,
gadget = id.substr( "mw-input-wpgadgets-".length );
@@ -18,13 +57,13 @@
.click( function() {
var postData =
'action=ajax&rs=GadgetsAjax::getUI' +
'&rsargs[]=gadget|' +
encodeURIComponent( gadget );
- // Send POST request via AJAX!
+
$.ajax( {
url: mw.config.get(
'wgScriptPath' ) + '/index.php',
type: "POST",
data: postData,
dataType: "html", // response
type
- success : function( response ) {
+ success: function( response ) {
//Show dialog
$( response ).dialog( {
modal: true,
@@ -32,13 +71,12 @@
resizable:
false,
title:
'Configuration of ' + gadget, //TODO: use messages
close:
function() {
-
$(this).dialog('destroy').empty(); //completely destroy on close
+ $( this
).dialog( 'destroy' ).empty(); //completely destroy on close
},
buttons: {
//TODO:
add "Restore defaults" button
"Save":
function() {
-
//TODO
-
alert( "I should save changes now, but I don't know how :'(..." );
+
saveConfig( $( this ), gadget );
},
"Cancel": function() {
$( this ).dialog( "close" );
@@ -49,7 +87,7 @@
error: function( response ) {
//TODO
alert( 'Something wrong
happened' );
- },
+ }
} );
return false; //prevent event
propagation
@@ -65,3 +103,4 @@
}
} );
} )( jQuery, mediaWiki );
+
_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs