mediawiki.Title: Fix typo "add" in documentation
[lhc/web/wiklou.git] / resources / src / mediawiki / mediawiki.Title.js
index 661ab74..ffb3041 100644 (file)
@@ -4,15 +4,33 @@
  * @since 1.18
  */
 ( function ( mw, $ ) {
+       /*jshint latedef:false */
 
        /**
-        * @class mw.Title
-        *
         * Parse titles into an object structure. Note that when using the constructor
         * directly, passing invalid titles will result in an exception. Use #newFromText to use the
         * logic directly and get null for invalid titles which is easier to work with.
         *
-        * @constructor
+        * @class mw.Title
+        */
+       /**
+        * Note that in the constructor and #newFromText method, `namespace` is the **default** namespace
+        * only, and can be overridden by a namespace prefix in `title`. If you do not want this behavior,
+        * use #makeTitle. Compare:
+        *
+        *     new mw.Title( 'Foo', NS_TEMPLATE ).getPrefixedText();                  // => 'Template:Foo'
+        *     mw.Title.newFromText( 'Foo', NS_TEMPLATE ).getPrefixedText();          // => 'Template:Foo'
+        *     mw.Title.makeTitle( NS_TEMPLATE, 'Foo' ).getPrefixedText();            // => 'Template:Foo'
+        *
+        *     new mw.Title( 'Category:Foo', NS_TEMPLATE ).getPrefixedText();         // => 'Category:Foo'
+        *     mw.Title.newFromText( 'Category:Foo', NS_TEMPLATE ).getPrefixedText(); // => 'Category:Foo'
+        *     mw.Title.makeTitle( NS_TEMPLATE, 'Category:Foo' ).getPrefixedText();   // => 'Template:Category:Foo'
+        *
+        *     new mw.Title( 'Template:Foo', NS_TEMPLATE ).getPrefixedText();         // => 'Template:Foo'
+        *     mw.Title.newFromText( 'Template:Foo', NS_TEMPLATE ).getPrefixedText(); // => 'Template:Foo'
+        *     mw.Title.makeTitle( NS_TEMPLATE, 'Template:Foo' ).getPrefixedText();   // => 'Template:Template:Foo'
+        *
+        * @method constructor
         * @param {string} title Title of the page. If no second argument given,
         *  this will be searched for a namespace
         * @param {number} [namespace=NS_MAIN] If given, will used as default namespace for the given title
                        return false;
                }
                ns = ns.toLowerCase();
-               id = mw.config.get( 'wgNamespaceIds' )[ns];
+               id = mw.config.get( 'wgNamespaceIds' )[ ns ];
                if ( id === undefined ) {
                        return false;
                }
                return id;
        },
 
+       /**
+        * @private
+        * @method getNamespacePrefix_
+        * @param {number} namespace
+        * @return {string}
+        */
+       getNamespacePrefix = function ( namespace ) {
+               return namespace === NS_MAIN ?
+                       '' :
+                       ( mw.config.get( 'wgFormattedNamespaces' )[ namespace ].replace( / /g, '_' ) + ':' );
+       },
+
        rUnderscoreTrim = /^_+|_+$/g,
 
        rSplit = /^(.+?)_*:_*(.*)$/,
        ],
 
        /**
-        * Internal helper for #constructor and #newFromtext.
+        * Internal helper for #constructor and #newFromText.
         *
         * Based on Title.php#secureAndSplit
         *
                        .replace( rUnderscoreTrim, '' );
 
                // Process initial colon
-               if ( title !== '' && title[0] === ':' ) {
+               if ( title !== '' && title[ 0 ] === ':' ) {
                        // Initial colon means main namespace instead of specified default
                        namespace = NS_MAIN;
                        title = title
                // Process namespace prefix (if any)
                m = title.match( rSplit );
                if ( m ) {
-                       id = getNsIdByName( m[1] );
+                       id = getNsIdByName( m[ 1 ] );
                        if ( id !== false ) {
                                // Ordinary namespace
                                namespace = id;
-                               title = m[2];
+                               title = m[ 2 ];
 
                                // For Talk:X pages, make sure X has no "namespace" prefix
                                if ( namespace === NS_TALK && ( m = title.match( rSplit ) ) ) {
                                        // Disallow titles like Talk:File:x (subject should roundtrip: talk:file:x -> file:x -> file_talk:x)
-                                       if ( getNsIdByName( m[1] ) !== false ) {
+                                       if ( getNsIdByName( m[ 1 ] ) !== false ) {
                                                return false;
                                        }
                                }
                }
 
                // Any remaining initial :s are illegal.
-               if ( title[0] === ':' ) {
+               if ( title[ 0 ] === ':' ) {
                        return false;
                }
 
                        rules = sanitationRules;
 
                for ( i = 0, ruleLength = rules.length; i < ruleLength; ++i ) {
-                       rule = rules[i];
+                       rule = rules[ i ];
                        for ( m = 0, filterLength = filter.length; m < filterLength; ++m ) {
-                               if ( rule[filter[m]] ) {
+                               if ( rule[ filter[ m ] ] ) {
                                        s = s.replace( rule.pattern, rule.replace );
                                }
                        }
        /**
         * Constructor for Title objects with a null return instead of an exception for invalid titles.
         *
+        * Note that `namespace` is the **default** namespace only, and can be overridden by a namespace
+        * prefix in `title`. If you do not want this behavior, use #makeTitle. See #constructor for
+        * details.
+        *
         * @static
         * @param {string} title
         * @param {number} [namespace=NS_MAIN] Default namespace
                return t;
        };
 
+       /**
+        * Constructor for Title objects with predefined namespace.
+        *
+        * Unlike #newFromText or #constructor, this function doesn't allow the given `namespace` to be
+        * overridden by a namespace prefix in `title`. See #constructor for details about this behavior.
+        *
+        * The single exception to this is when `namespace` is 0, indicating the main namespace. The
+        * function behaves like #newFromText in that case.
+        *
+        * @static
+        * @param {number} namespace Namespace to use for the title
+        * @param {string} title
+        * @return {mw.Title|null} A valid Title object or null if the title is invalid
+        */
+       Title.makeTitle = function ( namespace, title ) {
+               return mw.Title.newFromText( getNamespacePrefix( namespace ) + title );
+       };
+
        /**
         * Constructor for Title objects from user input altering that input to
         * produce a title that MediaWiki will accept as legal
         * @param {number} [defaultNamespace=NS_MAIN]
         *  If given, will used as default namespace for the given title.
         * @param {Object} [options] additional options
-        * @param {string} [options.fileExtension='']
-        *  If the title is about to be created for the Media or File namespace,
-        *  ensures the resulting Title has the correct extension. Useful, for example
-        *  on systems that predict the type by content-sniffing, not by file extension.
-        *  If different from empty string, `forUploading` is assumed.
         * @param {boolean} [options.forUploading=true]
         *  Makes sure that a file is uploadable under the title returned.
         *  There are pages in the file namespace under which file upload is impossible.
         * @return {mw.Title|null} A valid Title object or null if the input cannot be turned into a valid title
         */
        Title.newFromUserInput = function ( title, defaultNamespace, options ) {
-               var namespace, m, id, ext, parts, normalizeExtension;
+               var namespace, m, id, ext, parts;
 
                // defaultNamespace is optional; check whether options moves up
                if ( arguments.length < 3 && $.type( defaultNamespace ) === 'object' ) {
 
                // merge options into defaults
                options = $.extend( {
-                       fileExtension: '',
                        forUploading: true
                }, options );
 
-               normalizeExtension = function ( extension ) {
-                       // Remove only trailing space (that is removed by MW anyway)
-                       extension = extension.toLowerCase().replace( /\s*$/, '' );
-                       return extension;
-               };
-
                namespace = defaultNamespace === undefined ? NS_MAIN : defaultNamespace;
 
                // Normalise whitespace and remove duplicates
                title = $.trim( title.replace( rWhitespace, ' ' ) );
 
                // Process initial colon
-               if ( title !== '' && title[0] === ':' ) {
+               if ( title !== '' && title[ 0 ] === ':' ) {
                        // Initial colon means main namespace instead of specified default
                        namespace = NS_MAIN;
                        title = title
                // Process namespace prefix (if any)
                m = title.match( rSplit );
                if ( m ) {
-                       id = getNsIdByName( m[1] );
+                       id = getNsIdByName( m[ 1 ] );
                        if ( id !== false ) {
                                // Ordinary namespace
                                namespace = id;
-                               title = m[2];
+                               title = m[ 2 ];
                        }
                }
 
                if ( namespace === NS_MEDIA
-                       || ( ( options.forUploading || options.fileExtension ) && ( namespace === NS_FILE ) )
+                       || ( options.forUploading && ( namespace === NS_FILE ) )
                ) {
 
                        title = sanitize( title, [ 'generalRule', 'fileRule' ] );
                                // Get the last part, which is supposed to be the file extension
                                ext = parts.pop();
 
-                               // Does the supplied file name carry the desired file extension?
-                               if ( options.fileExtension
-                                       && normalizeExtension( ext ) !== normalizeExtension( options.fileExtension )
-                               ) {
-
-                                       // No, push back, whatever there was after the dot
-                                       parts.push( ext );
-
-                                       // And add the desired file extension later
-                                       ext = options.fileExtension;
-                               }
-
                                // Remove whitespace of the name part (that W/O extension)
                                title = $.trim( parts.join( '.' ) );
 
                                // Missing file extension
                                title = $.trim( parts.join( '.' ) );
 
-                               if ( options.fileExtension ) {
-
-                                       // Cut, if too long and append the desired file extension
-                                       title = trimFileNameToByteLength( title, options.fileExtension );
-
-                               } else {
-
-                                       // Name has no file extension and a fallback wasn't provided either
-                                       return null;
-                               }
+                               // Name has no file extension and a fallback wasn't provided either
+                               return null;
                        }
                } else {
 
         * @static
         * @param {string} uncleanName The unclean file name including file extension but
         *   without namespace
-        * @param {string} [fileExtension] the desired file extension
         * @return {mw.Title|null} A valid Title object or null if the title is invalid
         */
-       Title.newFromFileName = function ( uncleanName, fileExtension ) {
+       Title.newFromFileName = function ( uncleanName ) {
 
                return Title.newFromUserInput( 'File:' + uncleanName, {
-                       fileExtension: fileExtension,
                        forUploading: true
                } );
        };
                        thumbPhpRegex = /thumb\.php/,
                        regexes = [
                                // Thumbnails
-                               /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s\/]+)\/[^\s\/]+-(?:\1|thumbnail)[^\s\/]*$/,
+                               /\/[a-f0-9]\/[a-f0-9]{2}\/([^\s\/]+)\/[^\s\/]+-[^\s\/]*$/,
 
                                // Thumbnails in non-hashed upload directories
                                /\/([^\s\/]+)\/[^\s\/]+-(?:\1|thumbnail)[^\s\/]*$/,
 
                        recount = regexes.length;
 
-               src = img.jquery ? img[0].src : img.src;
+               src = img.jquery ? img[ 0 ].src : img.src;
 
                matches = src.match( thumbPhpRegex );
 
                decodedSrc = decodeURIComponent( src );
 
                for ( i = 0; i < recount; i++ ) {
-                       regex = regexes[i];
+                       regex = regexes[ i ];
                        matches = decodedSrc.match( regex );
 
-                       if ( matches && matches[1] ) {
-                               return mw.Title.newFromText( 'File:' + matches[1] );
+                       if ( matches && matches[ 1 ] ) {
+                               return mw.Title.newFromText( 'File:' + matches[ 1 ] );
                        }
                }
 
                        obj = Title.exist.pages;
 
                if ( type === 'string' ) {
-                       match = obj[title];
+                       match = obj[ title ];
                } else if ( type === 'object' && title instanceof Title ) {
-                       match = obj[title.toString()];
+                       match = obj[ title.toString() ];
                } else {
                        throw new Error( 'mw.Title.exists: title must be a string or an instance of Title' );
                }
                pages: {},
 
                set: function ( titles, state ) {
-                       titles = $.isArray( titles ) ? titles : [titles];
+                       titles = $.isArray( titles ) ? titles : [ titles ];
                        state = state === undefined ? true : !!state;
                        var i,
                                pages = this.pages,
                                len = titles.length;
 
                        for ( i = 0; i < len; i++ ) {
-                               pages[ titles[i] ] = state;
+                               pages[ titles[ i ] ] = state;
                        }
                        return true;
                }
        };
 
+       /**
+        * Normalize a file extension to the common form, making it lowercase and checking some synonyms,
+        * and ensure it's clean. Extensions with non-alphanumeric characters will be discarded.
+        * Keep in sync with File::normalizeExtension() in PHP.
+        *
+        * @param {string} extension File extension (without the leading dot)
+        * @return {string} File extension in canonical form
+        */
+       Title.normalizeExtension = function ( extension ) {
+               var
+                       lower = extension.toLowerCase(),
+                       squish = {
+                               htm: 'html',
+                               jpeg: 'jpg',
+                               mpeg: 'mpg',
+                               tiff: 'tif',
+                               ogv: 'ogg'
+                       };
+               if ( squish.hasOwnProperty( lower ) ) {
+                       return squish[ lower ];
+               } else if ( /^[0-9a-z]+$/.test( lower ) ) {
+                       return lower;
+               } else {
+                       return '';
+               }
+       };
+
        /* Public members */
 
        Title.prototype = {
                 * @return {string}
                 */
                getNamespacePrefix: function () {
-                       return this.namespace === NS_MAIN ?
-                               '' :
-                               ( mw.config.get( 'wgFormattedNamespaces' )[ this.namespace ].replace( / /g, '_' ) + ':' );
+                       return getNamespacePrefix( this.namespace );
                },
 
                /**
                        ) {
                                return this.title;
                        }
-                       return this.title[0].toUpperCase() + this.title.slice( 1 );
+                       return this.title[ 0 ].toUpperCase() + this.title.slice( 1 );
                },
 
                /**
                 * @return {string}
                 */
                getUrl: function ( params ) {
-                       return mw.util.getUrl( this.toString(), params );
+                       var fragment = this.getFragment();
+                       if ( fragment ) {
+                               return mw.util.getUrl( this.toString() + '#' + this.getFragment(), params );
+                       } else {
+                               return mw.util.getUrl( this.toString(), params );
+                       }
                },
 
                /**