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