Merge "Add .list-style-image-svg"
[lhc/web/wiklou.git] / resources / src / mediawiki / mediawiki.user.js
1 /**
2 * @class mw.user
3 * @singleton
4 */
5 ( function ( mw, $ ) {
6 var user, i,
7 deferreds = {},
8 byteToHex = [],
9 // Extend the skeleton mw.user from mediawiki.js
10 // This is kind of ugly but we're stuck with this for b/c reasons
11 options = mw.user.options || new mw.Map(),
12 tokens = mw.user.tokens || new mw.Map();
13
14 // Maps for number -> hex string conversion (with padding)
15 // idea from: https://github.com/broofa/node-uuid/blob/master/uuid.js
16 for ( i = 0; i < 256; i++ ) {
17 byteToHex[i] = (i + 0x100).toString(16).substr(1);
18 }
19
20 /**
21 * Get the current user's groups or rights
22 *
23 * @private
24 * @param {string} info One of 'groups' or 'rights'
25 * @return {jQuery.Promise}
26 */
27 function getUserInfo( info ) {
28 var api;
29 if ( !deferreds[info] ) {
30
31 deferreds.rights = $.Deferred();
32 deferreds.groups = $.Deferred();
33
34 api = new mw.Api();
35 api.get( {
36 action: 'query',
37 meta: 'userinfo',
38 uiprop: 'rights|groups'
39 } ).always( function ( data ) {
40 var rights, groups;
41 if ( data.query && data.query.userinfo ) {
42 rights = data.query.userinfo.rights;
43 groups = data.query.userinfo.groups;
44 }
45 deferreds.rights.resolve( rights || [] );
46 deferreds.groups.resolve( groups || [] );
47 } );
48
49 }
50
51 return deferreds[info].promise();
52 }
53
54 mw.user = user = {
55 options: options,
56 tokens: tokens,
57
58 /**
59 * Generate a random user session ID
60 * This information would potentially be stored in a cookie to identify a user during a
61 * session or series of sessions. Its uniqueness should
62 * not be depended on unless the browser supports the crypto API.
63 *
64 * Known problems with Math.random():
65 * Using the Math.random function we have seen sets
66 * with 1% of non uniques among 200.000 values with Safari providing most of these.
67 * Given the prevalence of Safari in mobile the percentage of duplicates in
68 * mobile usages of this code is probably higher.
69 *
70 * Rationale:
71 * We need about 64 bits to make sure that probability of collision
72 * on 500 million (5*10^8) is <= 1%
73 * See: https://en.wikipedia.org/wiki/Birthday_problem#Probability_table
74 *
75 * @return {string} 64 bit integer in hex format, padded
76 */
77 generateRandomSessionId: function () {
78 /*jshint bitwise:false */
79 var rnds, i, r, cryptoObj, hexRnds = new Array( 8 );
80 cryptoObj = window.crypto || window.msCrypto; // for IE 11
81
82 if ( cryptoObj ) {
83 // We fill an array with 8 random values, each of which is 8 bits.
84 // note that rnds is an array-like object not a true array
85 rnds = new Uint8Array( 8 );
86 cryptoObj.getRandomValues( rnds );
87 } else {
88 rnds = new Array( 8 );
89 // From: https://github.com/broofa/node-uuid/blob/master/uuid.js
90 for ( i = 0, r; i < 8; i++ ) {
91 if ( ( i & 0x03 ) === 0 ) {
92 r = Math.random() * 0x100000000;
93 }
94 rnds[i] = r >>> ( ( i & 0x03 ) << 3 ) & 0xff;
95 }
96 }
97 // convert to hex using byteToHex that already contains padding
98 for ( i = 0; i < rnds.length; i++ ) {
99 hexRnds[i] = byteToHex[rnds[i]];
100 }
101
102 // concatenation of two random integers with entrophy n and m
103 // returns a string with entrophy n+m if those strings are independent
104 return hexRnds.join( '' );
105 },
106
107 /**
108 * Get the current user's database id
109 *
110 * Not to be confused with #id.
111 *
112 * @return {number} Current user's id, or 0 if user is anonymous
113 */
114 getId: function () {
115 return mw.config.get( 'wgUserId', 0 );
116 },
117
118 /**
119 * Get the current user's name
120 *
121 * @return {string|null} User name string or null if user is anonymous
122 */
123 getName: function () {
124 return mw.config.get( 'wgUserName' );
125 },
126
127 /**
128 * Get date user registered, if available
129 *
130 * @return {Date|boolean|null} Date user registered, or false for anonymous users, or
131 * null when data is not available
132 */
133 getRegistration: function () {
134 var registration = mw.config.get( 'wgUserRegistration' );
135 if ( user.isAnon() ) {
136 return false;
137 }
138 if ( registration === null ) {
139 // Information may not be available if they signed up before
140 // MW began storing this.
141 return null;
142 }
143 return new Date( registration );
144 },
145
146 /**
147 * Whether the current user is anonymous
148 *
149 * @return {boolean}
150 */
151 isAnon: function () {
152 return user.getName() === null;
153 },
154
155 /**
156 * Get an automatically generated random ID (stored in a session cookie)
157 *
158 * This ID is ephemeral for everyone, staying in their browser only until they close
159 * their browser.
160 *
161 * @return {string} Random session ID
162 */
163 sessionId: function () {
164 var sessionId = $.cookie( 'mediaWiki.user.sessionId' );
165 if ( sessionId === undefined || sessionId === null ) {
166 sessionId = user.generateRandomSessionId();
167 $.cookie( 'mediaWiki.user.sessionId', sessionId, { expires: null, path: '/' } );
168 }
169 return sessionId;
170 },
171
172 /**
173 * Get the current user's name or the session ID
174 *
175 * Not to be confused with #getId.
176 *
177 * @return {string} User name or random session ID
178 */
179 id: function () {
180 return user.getName() || user.sessionId();
181 },
182
183 /**
184 * Get the user's bucket (place them in one if not done already)
185 *
186 * mw.user.bucket( 'test', {
187 * buckets: { ignored: 50, control: 25, test: 25 },
188 * version: 1,
189 * expires: 7
190 * } );
191 *
192 * @deprecated since 1.23
193 * @param {string} key Name of bucket
194 * @param {Object} options Bucket configuration options
195 * @param {Object} options.buckets List of bucket-name/relative-probability pairs (required,
196 * must have at least one pair)
197 * @param {number} [options.version=0] Version of bucket test, changing this forces
198 * rebucketing
199 * @param {number} [options.expires=30] Length of time (in days) until the user gets
200 * rebucketed
201 * @return {string} Bucket name - the randomly chosen key of the `options.buckets` object
202 */
203 bucket: function ( key, options ) {
204 var cookie, parts, version, bucket,
205 range, k, rand, total;
206
207 options = $.extend( {
208 buckets: {},
209 version: 0,
210 expires: 30
211 }, options || {} );
212
213 cookie = $.cookie( 'mediaWiki.user.bucket:' + key );
214
215 // Bucket information is stored as 2 integers, together as version:bucket like: "1:2"
216 if ( typeof cookie === 'string' && cookie.length > 2 && cookie.indexOf( ':' ) !== -1 ) {
217 parts = cookie.split( ':' );
218 if ( parts.length > 1 && Number( parts[0] ) === options.version ) {
219 version = Number( parts[0] );
220 bucket = String( parts[1] );
221 }
222 }
223
224 if ( bucket === undefined ) {
225 if ( !$.isPlainObject( options.buckets ) ) {
226 throw new Error( 'Invalid bucket. Object expected for options.buckets.' );
227 }
228
229 version = Number( options.version );
230
231 // Find range
232 range = 0;
233 for ( k in options.buckets ) {
234 range += options.buckets[k];
235 }
236
237 // Select random value within range
238 rand = Math.random() * range;
239
240 // Determine which bucket the value landed in
241 total = 0;
242 for ( k in options.buckets ) {
243 bucket = k;
244 total += options.buckets[k];
245 if ( total >= rand ) {
246 break;
247 }
248 }
249
250 $.cookie(
251 'mediaWiki.user.bucket:' + key,
252 version + ':' + bucket,
253 { path: '/', expires: Number( options.expires ) }
254 );
255 }
256
257 return bucket;
258 },
259
260 /**
261 * Get the current user's groups
262 *
263 * @param {Function} [callback]
264 * @return {jQuery.Promise}
265 */
266 getGroups: function ( callback ) {
267 return getUserInfo( 'groups' ).done( callback );
268 },
269
270 /**
271 * Get the current user's rights
272 *
273 * @param {Function} [callback]
274 * @return {jQuery.Promise}
275 */
276 getRights: function ( callback ) {
277 return getUserInfo( 'rights' ).done( callback );
278 }
279 };
280
281 }( mediaWiki, jQuery ) );