Merge "Update docs/hooks.txt for ShowSearchHitTitle"
[mediawiki.git] / resources / src / mediawiki / mediawiki.user.js
blobc4c91f95b16eadce36583c8b4395f89018cebda0
1 /**
2  * @class mw.user
3  * @singleton
4  */
5 /* global Uint32Array */
6 ( function ( mw, $ ) {
7         var userInfoPromise;
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         }
22         // mw.user with the properties options and tokens gets defined in mediawiki.js.
23         $.extend( mw.user, {
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;
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                         }
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                 },
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                 },
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                 },
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                 },
111                 /**
112                  * Whether the current user is anonymous
113                  *
114                  * @return {boolean}
115                  */
116                 isAnon: function () {
117                         return mw.user.getName() === null;
118                 },
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                 },
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                 },
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;
172                         options = $.extend( {
173                                 buckets: {},
174                                 version: 0,
175                                 expires: 30
176                         }, options || {} );
178                         cookie = mw.cookie.get( 'mwuser-bucket:' + key );
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                         }
189                         if ( bucket === undefined ) {
190                                 if ( !$.isPlainObject( options.buckets ) ) {
191                                         throw new Error( 'Invalid bucket. Object expected for options.buckets.' );
192                                 }
194                                 version = Number( options.version );
196                                 // Find range
197                                 range = 0;
198                                 for ( k in options.buckets ) {
199                                         range += options.buckets[ k ];
200                                 }
202                                 // Select random value within range
203                                 rand = Math.random() * range;
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                                 }
215                                 mw.cookie.set(
216                                         'mwuser-bucket:' + key,
217                                         version + ':' + bucket,
218                                         { expires: Number( options.expires ) * 86400 }
219                                 );
220                         }
222                         return bucket;
223                 },
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', [] );
234                         // Uses promise for backwards compatibility
235                         return $.Deferred().resolve( userGroups ).done( callback );
236                 },
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         } );
252 }( mediaWiki, jQuery ) );