Merge "Add ss_active_users in SiteStats::isSane"
[mediawiki.git] / resources / mediawiki / mediawiki.Title.js
blobb86a14baeb673703dc0d2425be1f391f3ea7315f
1 /*!
2  * @author Neil Kandalgaonkar, 2010
3  * @author Timo Tijhof, 2011
4  * @since 1.18
5  *
6  * Relies on: mw.config (wgFormattedNamespaces, wgNamespaceIds, wgCaseSensitiveNamespaces), mw.util.wikiGetlink
7  */
8 ( function ( mw, $ ) {
10         /* Local space */
12         /**
13          * @class mw.Title
14          *
15          * @constructor
16          * @param {string} title Title of the page. If no second argument given,
17          * this will be searched for a namespace.
18          * @param {number} [namespace] Namespace id. If given, title will be taken as-is.
19          */
20         function Title( title, namespace ) {
21                 this.ns = 0; // integer namespace id
22                 this.name = null; // name in canonical 'database' form
23                 this.ext = null; // extension
25                 if ( arguments.length === 2 ) {
26                         setNameAndExtension( this, title );
27                         this.ns = fixNsId( namespace );
28                 } else if ( arguments.length === 1 ) {
29                         setAll( this, title );
30                 }
31                 return this;
32         }
34 var
35         /* Public methods (defined later) */
36         fn,
38         /**
39          * Strip some illegal chars: control chars, colon, less than, greater than,
40          * brackets, braces, pipe, whitespace and normal spaces. This still leaves some insanity
41          * intact, like unicode bidi chars, but it's a good start..
42          * @ignore
43          * @param {string} s
44          * @return {string}
45          */
46         clean = function ( s ) {
47                 if ( s !== undefined ) {
48                         return s.replace( /[\x00-\x1f\x23\x3c\x3e\x5b\x5d\x7b\x7c\x7d\x7f\s]+/g, '_' );
49                 }
50         },
52         /**
53          * Convert db-key to readable text.
54          * @ignore
55          * @param {string} s
56          * @return {string}
57          */
58         text = function ( s ) {
59                 if ( s !== null && s !== undefined ) {
60                         return s.replace( /_/g, ' ' );
61                 } else {
62                         return '';
63                 }
64         },
66         /**
67          * Sanitize name.
68          * @ignore
69          */
70         fixName = function ( s ) {
71                 return clean( $.trim( s ) );
72         },
74         /**
75          * Sanitize extension.
76          * @ignore
77          */
78         fixExt = function ( s ) {
79                 return clean( s );
80         },
82         /**
83          * Sanitize namespace id.
84          * @ignore
85          * @param id {Number} Namespace id.
86          * @return {Number|Boolean} The id as-is or boolean false if invalid.
87          */
88         fixNsId = function ( id ) {
89                 // wgFormattedNamespaces is an object of *string* key-vals (ie. arr["0"] not arr[0] )
90                 var ns = mw.config.get( 'wgFormattedNamespaces' )[id.toString()];
92                 // Check only undefined (may be false-y, such as '' (main namespace) ).
93                 if ( ns === undefined ) {
94                         return false;
95                 } else {
96                         return Number( id );
97                 }
98         },
100         /**
101          * Get namespace id from namespace name by any known namespace/id pair (localized, canonical or alias).
102          * Example: On a German wiki this would return 6 for any of 'File', 'Datei', 'Image' or even 'Bild'.
103          * @ignore
104          * @param ns {String} Namespace name (case insensitive, leading/trailing space ignored).
105          * @return {Number|Boolean} Namespace id or boolean false if unrecognized.
106          */
107         getNsIdByName = function ( ns ) {
108                 // Don't cast non-strings to strings, because null or undefined
109                 // should not result in returning the id of a potential namespace
110                 // called "Null:" (e.g. on nullwiki.example.org)
111                 // Also, toLowerCase throws exception on null/undefined, because
112                 // it is a String.prototype method.
113                 if ( typeof ns !== 'string' ) {
114                         return false;
115                 }
116                 ns = clean( $.trim( ns.toLowerCase() ) ); // Normalize
117                 var id = mw.config.get( 'wgNamespaceIds' )[ns];
118                 if ( id === undefined ) {
119                         mw.log( 'mw.Title: Unrecognized namespace: ' + ns );
120                         return false;
121                 }
122                 return fixNsId( id );
123         },
125         /**
126          * Helper to extract namespace, name and extension from a string.
127          *
128          * @ignore
129          * @param {mw.Title} title
130          * @param {string} raw
131          * @return {mw.Title}
132          */
133         setAll = function ( title, s ) {
134                 // In normal browsers the match-array contains null/undefined if there's no match,
135                 // IE returns an empty string.
136                 var matches = s.match( /^(?:([^:]+):)?(.*?)(?:\.(\w+))?$/ ),
137                         nsMatch = getNsIdByName( matches[1] );
139                 // Namespace must be valid, and title must be a non-empty string.
140                 if ( nsMatch && typeof matches[2] === 'string' && matches[2] !== '' ) {
141                         title.ns = nsMatch;
142                         title.name = fixName( matches[2] );
143                         if ( typeof matches[3] === 'string' && matches[3] !== '' ) {
144                                 title.ext = fixExt( matches[3] );
145                         }
146                 } else {
147                         // Consistency with MediaWiki PHP: Unknown namespace -> fallback to main namespace.
148                         title.ns = 0;
149                         setNameAndExtension( title, s );
150                 }
151                 return title;
152         },
154         /**
155          * Helper to extract name and extension from a string.
156          *
157          * @ignore
158          * @param {mw.Title} title
159          * @param {string} raw
160          * @return {mw.Title}
161          */
162         setNameAndExtension = function ( title, raw ) {
163                 // In normal browsers the match-array contains null/undefined if there's no match,
164                 // IE returns an empty string.
165                 var matches = raw.match( /^(?:)?(.*?)(?:\.(\w+))?$/ );
167                 // Title must be a non-empty string.
168                 if ( typeof matches[1] === 'string' && matches[1] !== '' ) {
169                         title.name = fixName( matches[1] );
170                         if ( typeof matches[2] === 'string' && matches[2] !== '' ) {
171                                 title.ext = fixExt( matches[2] );
172                         }
173                 } else {
174                         throw new Error( 'mw.Title: Could not parse title "' + raw + '"' );
175                 }
176                 return title;
177         };
180         /* Static space */
182         /**
183          * Whether this title exists on the wiki.
184          * @static
185          * @param {Mixed} title prefixed db-key name (string) or instance of Title
186          * @return {Mixed} Boolean true/false if the information is available. Otherwise null.
187          */
188         Title.exists = function ( title ) {
189                 var type = $.type( title ), obj = Title.exist.pages, match;
190                 if ( type === 'string' ) {
191                         match = obj[title];
192                 } else if ( type === 'object' && title instanceof Title ) {
193                         match = obj[title.toString()];
194                 } else {
195                         throw new Error( 'mw.Title.exists: title must be a string or an instance of Title' );
196                 }
197                 if ( typeof match === 'boolean' ) {
198                         return match;
199                 }
200                 return null;
201         };
203         /**
204          * @static
205          * @property
206          */
207         Title.exist = {
208                 /**
209                  * @static
210                  * @property {Object} exist.pages Keyed by PrefixedDb title.
211                  * Boolean true value indicates page does exist.
212                  */
213                 pages: {},
214                 /**
215                  * Example to declare existing titles:
216                  *     Title.exist.set(['User:John_Doe', ...]);
217                  * Eample to declare titles nonexistent:
218                  *     Title.exist.set(['File:Foo_bar.jpg', ...], false);
219                  *
220                  * @static
221                  * @property exist.set
222                  * @param {string|Array} titles Title(s) in strict prefixedDb title form.
223                  * @param {boolean} [state] State of the given titles. Defaults to true.
224                  * @return {boolean}
225                  */
226                 set: function ( titles, state ) {
227                         titles = $.isArray( titles ) ? titles : [titles];
228                         state = state === undefined ? true : !!state;
229                         var pages = this.pages, i, len = titles.length;
230                         for ( i = 0; i < len; i++ ) {
231                                 pages[ titles[i] ] = state;
232                         }
233                         return true;
234                 }
235         };
237         /* Public methods */
239         fn = {
240                 constructor: Title,
242                 /**
243                  * Get the namespace number.
244                  * @return {number}
245                  */
246                 getNamespaceId: function (){
247                         return this.ns;
248                 },
250                 /**
251                  * Get the namespace prefix (in the content-language).
252                  * In NS_MAIN this is '', otherwise namespace name plus ':'
253                  * @return {string}
254                  */
255                 getNamespacePrefix: function (){
256                         return mw.config.get( 'wgFormattedNamespaces' )[this.ns].replace( / /g, '_' ) + (this.ns === 0 ? '' : ':');
257                 },
259                 /**
260                  * The name, like "Foo_bar"
261                  * @return {string}
262                  */
263                 getName: function () {
264                         if ( $.inArray( this.ns, mw.config.get( 'wgCaseSensitiveNamespaces' ) ) !== -1 ) {
265                                 return this.name;
266                         } else {
267                                 return $.ucFirst( this.name );
268                         }
269                 },
271                 /**
272                  * The name, like "Foo bar"
273                  * @return {string}
274                  */
275                 getNameText: function () {
276                         return text( this.getName() );
277                 },
279                 /**
280                  * Get full name in prefixed DB form, like File:Foo_bar.jpg,
281                  * most useful for API calls, anything that must identify the "title".
282                  * @return {string}
283                  */
284                 getPrefixedDb: function () {
285                         return this.getNamespacePrefix() + this.getMain();
286                 },
288                 /**
289                  * Get full name in text form, like "File:Foo bar.jpg".
290                  * @return {string}
291                  */
292                 getPrefixedText: function () {
293                         return text( this.getPrefixedDb() );
294                 },
296                 /**
297                  * The main title (without namespace), like "Foo_bar.jpg"
298                  * @return {string}
299                  */
300                 getMain: function () {
301                         return this.getName() + this.getDotExtension();
302                 },
304                 /**
305                  * The "text" form, like "Foo bar.jpg"
306                  * @return {string}
307                  */
308                 getMainText: function () {
309                         return text( this.getMain() );
310                 },
312                 /**
313                  * Get the extension (returns null if there was none)
314                  * @return {string|null}
315                  */
316                 getExtension: function () {
317                         return this.ext;
318                 },
320                 /**
321                  * Convenience method: return string like ".jpg", or "" if no extension
322                  * @return {string}
323                  */
324                 getDotExtension: function () {
325                         return this.ext === null ? '' : '.' + this.ext;
326                 },
328                 /**
329                  * Return the URL to this title
330                  * @see mw.util#wikiGetlink
331                  * @return {string}
332                  */
333                 getUrl: function () {
334                         return mw.util.wikiGetlink( this.toString() );
335                 },
337                 /**
338                  * Whether this title exists on the wiki.
339                  * @see #static-method-exists
340                  * @return {boolean|null} If the information is available. Otherwise null.
341                  */
342                 exists: function () {
343                         return Title.exists( this );
344                 }
345         };
347         // Alias
348         fn.toString = fn.getPrefixedDb;
349         fn.toText = fn.getPrefixedText;
351         // Assign
352         Title.prototype = fn;
354         // Expose
355         mw.Title = Title;
357 }( mediaWiki, jQuery ) );