Merge "Add functionality to check & set opt-in flag from code, also for anons"

This commit is contained in:
jenkins-bot 2014-06-11 21:48:13 +00:00 committed by Gerrit Code Review
commit b53f7b2e60
9 changed files with 392 additions and 11 deletions

View file

@ -745,6 +745,16 @@ $wgResourceModules += array(
),
),
'mmv.Config' => $wgMediaViewerResourceTemplate + array(
'scripts' => array(
'mmv/mmv.Config.js',
),
'dependencies' => array(
'mmv.base',
),
),
'mmv' => $wgMediaViewerResourceTemplate + array(
'scripts' => array(
'mmv/mmv.js',
@ -793,6 +803,7 @@ $wgResourceModules += array(
'dependencies' => array(
'jquery.hashchange',
'mediawiki.Title',
'mmv.Config',
'mmv.ActionLogger',
'mmv.HtmlUtils',
'mmv.DurationLogger',

View file

@ -189,6 +189,7 @@ class MultimediaViewerHooks {
'tests/qunit/mmv/mmv.lightboximage.test.js',
'tests/qunit/mmv/mmv.ThumbnailWidthCalculator.test.js',
'tests/qunit/mmv/mmv.EmbedFileFormatter.test.js',
'tests/qunit/mmv/mmv.Config.test.js',
'tests/qunit/mmv/mmv.HtmlUtils.test.js',
'tests/qunit/mmv/mmv.performance.test.js',
'tests/qunit/mmv/mmv.ActionLogger.test.js',

View file

@ -7,6 +7,7 @@
"classes": [
"mw.mmv.Api",
"mw.mmv.ActionLogger",
"mw.mmv.Config",
"mw.mmv.DurationLogger",
"mw.mmv.EmbedFileFormatter",
"mw.mmv.HtmlUtils",
@ -84,6 +85,7 @@
"mw",
"mw.Api",
"mw.Title",
"mw.Map",
"mw.eventLog"
]
},

View file

@ -19,6 +19,12 @@
* <https://www.mediawiki.org/wiki/Extension:EventLogging>
*/
/**
* @class mw.Map
* Associative array which is used for various configuration objects, most prominently mw.config:
* <https://www.mediawiki.org/wiki/Manual:Interface/JavaScript#mw.config>
*/
/**
* @class HTMLElement
* An HTML element.

175
resources/mmv/mmv.Config.js Normal file
View file

@ -0,0 +1,175 @@
/*
* This file is part of the MediaWiki extension MediaViewer.
*
* MediaViewer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* MediaViewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MediaViewer. If not, see <http://www.gnu.org/licenses/>.
*/
( function ( mw, $ ) {
var CP;
/**
* @class mw.mmv.Config
* Contains/retrieves configuration/environment information for MediaViewer.
* @constructor
*/
function Config( viewerConfig, mwConfig, mwUser, api, localStorage ) {
/**
* A plain object storing MediaViewer-specific settings
* @type {Object}
*/
this.viewerConfig = viewerConfig;
/**
* The mw.config object, for dependency injection
* @type {mw.Map}
*/
this.mwConfig = mwConfig;
/**
* mw.user object, for dependency injection
* @type {Object}
*/
this.mwUser = mwUser;
/**
* API object, for dependency injction
* @type {mw.Api}
*/
this.api = api;
/**
* The localStorage object, for dependency injection
* @type {Object}
*/
this.localStorage = localStorage;
}
CP = Config.prototype;
/**
* Get value from local storage or fail gracefully.
* @param {string} key
* @param {*} [fallback] value to return when key is not set or localStorage is not supported
* @returns {*} stored value or fallback or null if neither exists
*/
CP.getFromLocalStorage = function ( key, fallback ) {
var value = null;
if ( this.localStorage ) {
value = this.localStorage.getItem( key );
}
if ( value === null && fallback !== undefined ) {
value = fallback;
}
return value;
};
/**
* Set item in local storage or fail gracefully.
* @param {string} key
* @param {*} value
* @return {boolean} whether storing the item was successful
*/
CP.setInLocalStorage = function ( key, value ) {
var success = false;
if ( this.localStorage ) {
try {
this.localStorage.setItem( key, value );
success = true;
} catch ( e ) {}
}
return success;
};
/**
* Remove item from local storage or fail gracefully.
* @param {string} key
* @return {boolean} whether storing the item was successful
*/
CP.removeFromLocalStorage = function ( key ) {
if ( this.localStorage ) {
try {
this.localStorage.removeItem( key );
return true;
} catch ( e ) {
return false;
}
}
return true; // since we never even stored the value, this is considered a success
};
/**
* Set user preference via AJAX
* @param {string} key
* @param {string} value
* @returns {jQuery.Promise} a deferred which resolves/rejects on success/failure respectively
*/
CP.setUserPreference = function ( key, value ) {
return this.api.postWithToken( 'options', {
action: 'options',
optionname: key,
optionvalue: value
} );
};
/**
* Returns true if MediaViewer should handle thumbnail clicks.
*/
CP.isMediaViewerEnabledOnClick = function () {
// IMPORTANT: mmv.head.js uses the same logic but does not use this class to be lightweight. Make sure to keep it in sync.
return this.mwConfig.get( 'wgMediaViewer' ) // global opt-out switch, can be set in user JS
&& this.mwConfig.get( 'wgMediaViewerOnClick' ) // thumbnail opt-out, can be set in preferences
&& ( !this.mwUser.isAnon() || this.getFromLocalStorage( 'wgMediaViewerOnClick', 1 ) === 1 ); // thumbnail opt-out for anons
};
/**
* (Semi-)permanently stores the setting whether MediaViewer should handle thumbnail clicks.
* - for logged-in users, we use preferences
* - for anons, we use localStorage
* - for anons with old browsers, we don't do anything
* @param {boolean} enabled
* @return {jQuery.Promise} a deferred which resolves/rejects on success/failure respectively
*/
CP.setMediaViewerEnabledOnClick = function ( enabled ) {
var config = this,
success = true;
if ( this.mwUser.isAnon() ) {
if ( !enabled ) {
success = this.setInLocalStorage( 'wgMediaViewerOnClick', '0' ); // localStorage stringifies everything, best use strings in the first place
} else {
success = this.removeFromLocalStorage( 'wgMediaViewerOnClick' );
}
if ( success ) {
config.mwConfig.set( 'wgMediaViewerOnClick', enabled );
return $.Deferred().resolve();
} else {
return $.Deferred().reject();
}
} else {
return this.setUserPreference( 'multimediaviewer-enable', enabled ? true : '').then( function () { // wow our prefs API sucks
// make the change work without a reload
config.mwConfig.set( 'wgMediaViewerOnClick', enabled );
} );
}
};
/**
* Returns true if #setMediaViewerEnabledOnClick() is supported.
* @return {boolean}
*/
CP.canSetMediaViewerEnabledOnClick = function () {
return !this.mwUser.isAnon() || !!this.localStorage;
};
mw.mmv.Config = Config;
} ( mediaWiki, jQuery ) );

View file

@ -39,6 +39,17 @@
this.readinessWaitDuration = 100;
this.hoverWaitDuration = 200;
// TODO lazy-load config and htmlUtils
/** @property {mw.mmv.Config} config - */
this.config = new mw.mmv.Config(
mw.config.get( 'wgMultimediaViewer', {} ),
mw.config,
mw.user,
new mw.Api(),
window.localStorage
);
/** @property {mw.mmv.HtmlUtils} htmlUtils - */
this.htmlUtils = new mw.mmv.HtmlUtils();
@ -190,7 +201,7 @@
// If this is a thumb, we preload JS/CSS when the mouse cursor hovers the thumb container (thumb image + caption + border)
$thumbContain.mouseenter( function() {
// There is no point preloading if clicking the thumb won't open Media Viewer
if ( mw.config.get( 'wgMediaViewerOnClick' ) !== true ) {
if ( !bs.config.isMediaViewerEnabledOnClick() ) {
return;
}
bs.preloadOnHoverTimer = setTimeout( function() {
@ -253,7 +264,7 @@
}
// Don't load if someone has specifically stopped us from doing so
if ( mw.config.get( 'wgMediaViewerOnClick' ) !== true && overridePreference !== true ) {
if ( !this.config.isMediaViewerEnabledOnClick() && overridePreference !== true ) {
return;
}

View file

@ -25,7 +25,10 @@
// If the user disabled MediaViewer in his preferences, we do not set up click handling.
// This is loaded before user JS so we cannot check wgMediaViewer.
if ( mw.config.get( 'wgMediaViewerOnClick' ) !== true ) {
if (
mw.config.get( 'wgMediaViewerOnClick' ) !== true
|| mw.user.isAnon() && window.localStorage && localStorage.getItem( 'wgMediaViewerOnClick' ) === false
) {
return;
}

View file

@ -0,0 +1,176 @@
/*
* This file is part of the MediaWiki extension MediaViewer.
*
* MediaViewer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* MediaViewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MediaViewer. If not, see <http://www.gnu.org/licenses/>.
*/
( function ( mw, $ ) {
QUnit.module( 'mmv.Config', QUnit.newMwEnvironment() );
QUnit.test( 'Constructor sanity test', 1, function ( assert ) {
var config = new mw.mmv.Config( {}, {}, {}, {}, null );
assert.ok( config );
} );
QUnit.test( 'Localstorage get', 8, function ( assert ) {
var localStorage, config;
localStorage = undefined; // no browser support
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.getFromLocalStorage( 'foo' ), null, 'Returns null when not supported' );
assert.strictEqual( config.getFromLocalStorage( 'foo', 'bar' ), 'bar', 'Returns fallback when not supported' );
localStorage = null; // browser supports it but disabled
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.getFromLocalStorage( 'foo' ), null, 'Returns null when disabled' );
assert.strictEqual( config.getFromLocalStorage( 'foo', 'bar' ), 'bar', 'Returns fallback when disabled' );
localStorage = { getItem: this.sandbox.stub() };
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
localStorage.getItem.withArgs( 'foo' ).returns( null );
assert.strictEqual( config.getFromLocalStorage( 'foo' ), null, 'Returns null when key not set' );
assert.strictEqual( config.getFromLocalStorage( 'foo', 'bar' ), 'bar', 'Returns fallback when key not set' );
localStorage.getItem.reset();
localStorage.getItem.withArgs( 'foo' ).returns( 'boom' );
assert.strictEqual( config.getFromLocalStorage( 'foo' ), 'boom', 'Returns correct value' );
assert.strictEqual( config.getFromLocalStorage( 'foo', 'bar' ), 'boom', 'Returns correct value ignoring fallback' );
} );
QUnit.test( 'Localstorage set', 4, function ( assert ) {
var localStorage, config;
localStorage = undefined; // no browser support
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.setInLocalStorage( 'foo', 'bar' ), false, 'Returns false when not supported' );
localStorage = null; // browser supports it but disabled
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.setInLocalStorage( 'foo', 'bar' ), false, 'Returns false when disabled' );
localStorage = { setItem: this.sandbox.stub() };
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.setInLocalStorage( 'foo', 'bar' ), true, 'Returns true when works' );
localStorage.setItem.throwsException( 'localStorage full!' );
assert.strictEqual( config.setInLocalStorage( 'foo', 'bar' ), false, 'Returns false on error' );
} );
QUnit.test( 'Localstorage remove', 4, function ( assert ) {
var localStorage, config;
localStorage = undefined; // no browser support
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.removeFromLocalStorage( 'foo' ), true, 'Returns true when not supported' );
localStorage = null; // browser supports it but disabled
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.removeFromLocalStorage( 'foo' ), true , 'Returns true when disabled' );
localStorage = { removeItem: this.sandbox.stub() };
config = new mw.mmv.Config( {}, {}, {}, {}, localStorage );
assert.strictEqual( config.removeFromLocalStorage( 'foo' ), true, 'Returns true when works' );
localStorage.removeItem.throwsException( 'cannot write localStorage!' );
assert.strictEqual( config.removeFromLocalStorage( 'foo' ), false, 'Returns false on error' );
} );
QUnit.test( 'isMediaViewerEnabledOnClick', 7, function ( assert ) {
var localStorage = { getItem: this.sandbox.stub() },
mwConfig = { get: this.sandbox.stub() },
mwUser = { isAnon: this.sandbox.stub() },
config = new mw.mmv.Config( {}, mwConfig, mwUser, {}, localStorage );
mwUser.isAnon.returns( false );
mwConfig.get.withArgs( 'wgMediaViewer' ).returns( true );
mwConfig.get.withArgs( 'wgMediaViewerOnClick' ).returns( true );
assert.strictEqual( config.isMediaViewerEnabledOnClick(), true, 'Returns true for logged-in with standard settings' );
mwUser.isAnon.returns( false );
mwConfig.get.withArgs( 'wgMediaViewer' ).returns( false );
mwConfig.get.withArgs( 'wgMediaViewerOnClick' ).returns( true );
assert.strictEqual( config.isMediaViewerEnabledOnClick(), false, 'Returns false if opted out via user JS flag' );
mwUser.isAnon.returns( false );
mwConfig.get.withArgs( 'wgMediaViewer' ).returns( true );
mwConfig.get.withArgs( 'wgMediaViewerOnClick' ).returns( false );
assert.strictEqual( config.isMediaViewerEnabledOnClick(), false, 'Returns false if opted out via preferences' );
mwUser.isAnon.returns( true );
mwConfig.get.withArgs( 'wgMediaViewer' ).returns( false );
mwConfig.get.withArgs( 'wgMediaViewerOnClick' ).returns( true );
assert.strictEqual( config.isMediaViewerEnabledOnClick(), false, 'Returns false if anon user opted out via user JS flag' );
mwUser.isAnon.returns( true );
mwConfig.get.withArgs( 'wgMediaViewer' ).returns( true );
mwConfig.get.withArgs( 'wgMediaViewerOnClick' ).returns( false );
assert.strictEqual( config.isMediaViewerEnabledOnClick(), false, 'Returns false if anon user opted out in some weird way' ); // apparently someone created a browser extension to do this
mwUser.isAnon.returns( true );
mwConfig.get.withArgs( 'wgMediaViewer' ).returns( true );
mwConfig.get.withArgs( 'wgMediaViewerOnClick' ).returns( true );
localStorage.getItem.withArgs( 'wgMediaViewerOnClick').returns( null );
assert.strictEqual( config.isMediaViewerEnabledOnClick(), true, 'Returns true for anon with standard settings' );
mwUser.isAnon.returns( true );
mwConfig.get.withArgs( 'wgMediaViewer' ).returns( true );
mwConfig.get.withArgs( 'wgMediaViewerOnClick' ).returns( true );
localStorage.getItem.withArgs( 'wgMediaViewerOnClick').returns( '0' );
assert.strictEqual( config.isMediaViewerEnabledOnClick(), false, 'Returns true for anon opted out via localSettings' );
} );
QUnit.test( 'setMediaViewerEnabledOnClick sanity check', 3, function ( assert ) {
var localStorage = { setItem: this.sandbox.stub(), removeItem: this.sandbox.stub() },
mwUser = { isAnon: this.sandbox.stub() },
mwConfig = { set: this.sandbox.stub() },
api = { postWithToken: this.sandbox.stub() },
config = new mw.mmv.Config( {}, mwConfig, mwUser, api, localStorage );
mwUser.isAnon.returns( false );
api.postWithToken.returns( $.Deferred().resolve() );
config.setMediaViewerEnabledOnClick( false );
assert.ok( api.postWithToken.called, 'For logged-in users, pref change is via API' );
mwUser.isAnon.returns( true );
config.setMediaViewerEnabledOnClick( false );
assert.ok( localStorage.setItem.called, 'For anons, opt-out is set in localStorage' );
mwUser.isAnon.returns( true );
config.setMediaViewerEnabledOnClick( true );
assert.ok( localStorage.removeItem.called, 'For anons, opt-in means clearing localStorage' );
} );
QUnit.test( 'canSetMediaViewerEnabledOnClick', 4, function ( assert ) {
var mwUser = { isAnon: this.sandbox.stub() },
config = new mw.mmv.Config( {}, {}, mwUser, {}, {} );
mwUser.isAnon.returns( false );
assert.strictEqual( config.canSetMediaViewerEnabledOnClick(), true, 'Logged-in users can always disable' );
mwUser.isAnon.returns( true );
config = new mw.mmv.Config( {}, {}, mwUser, {}, {} );
assert.strictEqual( config.canSetMediaViewerEnabledOnClick(), true, 'Anons can disable when they have localStorage support' );
mwUser.isAnon.returns( true );
config = new mw.mmv.Config( {}, {}, mwUser, {}, null );
assert.strictEqual( config.canSetMediaViewerEnabledOnClick(), false, 'Anons cannot disable when they have no localStorage support' );
mwUser.isAnon.returns( true );
config = new mw.mmv.Config( {}, {}, mwUser, {}, undefined );
assert.strictEqual( config.canSetMediaViewerEnabledOnClick(), false, 'Anons cannot disable when disabled localStorage' );
} );
} ( mediaWiki, jQuery ) );

View file

@ -1,16 +1,12 @@
( function ( mw, $ ) {
var backup = {};
QUnit.module( 'mmv.bootstrap', QUnit.newMwEnvironment( {
setup: function () {
backup.onclick = mw.config.get( 'wgMediaViewerOnClick' );
mw.config.set( 'wgMediaViewer', true );
mw.config.set( 'wgMediaViewerOnClick', true );
this.sandbox.stub( mw.user, 'isAnon').returns( false );
this.clock = this.sandbox.useFakeTimers();
},
teardown: function () {
mw.config.set( 'wgMediaViewerOnClick', backup.onclick );
} } ) );
}
} ) );
function createGallery( imageSrc ) {
var div = $( '<div>' ).addClass( 'gallery' ).appendTo( '#qunit-fixture' ),