Index: trunk/phase3/includes/OutputPage.php |
— | — | @@ -2396,7 +2396,6 @@ |
2397 | 2397 | $wgResourceLoaderInlinePrivateModules; |
2398 | 2398 | // Lazy-load ResourceLoader |
2399 | 2399 | // TODO: Should this be a static function of ResourceLoader instead? |
2400 | | - // TODO: Divide off modules starting with "user", and add the user parameter to them |
2401 | 2400 | $baseQuery = array( |
2402 | 2401 | 'lang' => $this->getContext()->getLang()->getCode(), |
2403 | 2402 | 'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false', |
— | — | @@ -2475,7 +2474,7 @@ |
2476 | 2475 | continue; |
2477 | 2476 | } |
2478 | 2477 | |
2479 | | - $query['modules'] = implode( '|', array_keys( $modules ) ); |
| 2478 | + $query['modules'] = ResourceLoader::makePackedModulesString( array_keys( $modules ) ); |
2480 | 2479 | |
2481 | 2480 | // Support inlining of private modules if configured as such |
2482 | 2481 | if ( $group === 'private' && $wgResourceLoaderInlinePrivateModules ) { |
Index: trunk/phase3/includes/resourceloader/ResourceLoader.php |
— | — | @@ -199,6 +199,7 @@ |
200 | 200 | * this may also be a ResourceLoaderModule object. Optional when using |
201 | 201 | * multiple-registration calling style. |
202 | 202 | * @throws MWException: If a duplicate module registration is attempted |
| 203 | + * @throws MWException: If a module name contains illegal characters (pipes or commas) |
203 | 204 | * @throws MWException: If something other than a ResourceLoaderModule is being registered |
204 | 205 | * @return Boolean: False if there were any errors, in which case one or more modules were not |
205 | 206 | * registered |
— | — | @@ -223,6 +224,11 @@ |
224 | 225 | 'Another module has already been registered as ' . $name |
225 | 226 | ); |
226 | 227 | } |
| 228 | + |
| 229 | + // Check $name for illegal characters |
| 230 | + if ( preg_match( '/[|,]/', $name ) ) { |
| 231 | + throw new MWException( "ResourceLoader module name '$name' is invalid. Names may not contain pipes (|) or commas (,)" ); |
| 232 | + } |
227 | 233 | |
228 | 234 | // Attach module |
229 | 235 | if ( is_object( $info ) ) { |
— | — | @@ -699,6 +705,31 @@ |
700 | 706 | } |
701 | 707 | |
702 | 708 | /** |
| 709 | + * Convert an array of module names to a packed query string. |
| 710 | + * |
| 711 | + * For example, array( 'foo.bar', 'foo.baz', 'bar.baz', 'bar.quux' ) |
| 712 | + * becomes 'foo.bar,baz|bar.baz,quux' |
| 713 | + * @param $modules array of module names (strings) |
| 714 | + * @return string Packed query string |
| 715 | + */ |
| 716 | + public static function makePackedModulesString( $modules ) { |
| 717 | + $groups = array(); // array( prefix => array( suffixes ) ) |
| 718 | + foreach ( $modules as $module ) { |
| 719 | + $pos = strrpos( $module, '.' ); |
| 720 | + $prefix = $pos === false ? '' : substr( $module, 0, $pos ); |
| 721 | + $suffix = $pos === false ? $module : substr( $module, $pos + 1 ); |
| 722 | + $groups[$prefix][] = $suffix; |
| 723 | + } |
| 724 | + |
| 725 | + $arr = array(); |
| 726 | + foreach ( $groups as $prefix => $suffixes ) { |
| 727 | + $p = $prefix === '' ? '' : $prefix . '.'; |
| 728 | + $arr[] = $p . implode( ',', $suffixes ); |
| 729 | + } |
| 730 | + return implode( '|', $arr ); |
| 731 | + } |
| 732 | + |
| 733 | + /** |
703 | 734 | * Determine whether debug mode was requested |
704 | 735 | * Order of priority is 1) request param, 2) cookie, 3) $wg setting |
705 | 736 | * @return bool |
Index: trunk/phase3/includes/resourceloader/ResourceLoaderContext.php |
— | — | @@ -51,7 +51,7 @@ |
52 | 52 | // Interpret request |
53 | 53 | // List of modules |
54 | 54 | $modules = $request->getVal( 'modules' ); |
55 | | - $this->modules = $modules ? explode( '|', $modules ) : array(); |
| 55 | + $this->modules = $modules ? self::expandModuleNames( $modules ) : array(); |
56 | 56 | // Various parameters |
57 | 57 | $this->skin = $request->getVal( 'skin' ); |
58 | 58 | $this->user = $request->getVal( 'user' ); |
— | — | @@ -63,6 +63,34 @@ |
64 | 64 | $this->skin = $wgDefaultSkin; |
65 | 65 | } |
66 | 66 | } |
| 67 | + |
| 68 | + /** |
| 69 | + * Expand a string of the form jquery.foo,bar|jquery.ui.baz,quux to |
| 70 | + * an array of module names like array( 'jquery.foo', 'jquery.bar', |
| 71 | + * 'jquery.ui.baz', 'jquery.ui.quux' ) |
| 72 | + * @param $modules String Packed module name list |
| 73 | + * @return array of module names |
| 74 | + */ |
| 75 | + public static function expandModuleNames( $modules ) { |
| 76 | + $retval = array(); |
| 77 | + $exploded = explode( '|', $modules ); |
| 78 | + foreach ( $exploded as $group ) { |
| 79 | + if ( strpos( $group, ',' ) === false ) { |
| 80 | + // This is not a set of modules in foo.bar,baz notation |
| 81 | + // but a single module |
| 82 | + $retval[] = $group; |
| 83 | + } else { |
| 84 | + // This is a set of modules in foo.bar,baz notation |
| 85 | + $pos = strrpos( $group, '.' ); |
| 86 | + $prefix = substr( $group, 0, $pos ); // 'foo' |
| 87 | + $suffixes = explode( ',', substr( $group, $pos + 1 ) ); // array( 'bar', 'baz' ) |
| 88 | + foreach ( $suffixes as $suffix ) { |
| 89 | + $retval[] = "$prefix.$suffix"; |
| 90 | + } |
| 91 | + } |
| 92 | + } |
| 93 | + return $retval; |
| 94 | + } |
67 | 95 | |
68 | 96 | public function getResourceLoader() { |
69 | 97 | return $this->resourceLoader; |
Index: trunk/phase3/resources/mediawiki/mediawiki.js |
— | — | @@ -863,6 +863,20 @@ |
864 | 864 | } |
865 | 865 | return sorted; |
866 | 866 | } |
| 867 | + |
| 868 | + /** |
| 869 | + * Converts a module map of the form { foo: [ 'bar', 'baz' ], bar: [ 'baz, 'quux' ] } |
| 870 | + * to a query string of the form foo.bar,baz|bar.baz,quux |
| 871 | + */ |
| 872 | + function buildModulesString( moduleMap ) { |
| 873 | + var arr = []; |
| 874 | + for ( var prefix in moduleMap ) { |
| 875 | + var p = prefix === '' ? '' : prefix + '.'; |
| 876 | + arr.push( p + moduleMap[prefix].join( ',' ) ); |
| 877 | + } |
| 878 | + return arr.join( '|' ); |
| 879 | + } |
| 880 | + |
867 | 881 | |
868 | 882 | /* Public Methods */ |
869 | 883 | |
— | — | @@ -920,32 +934,38 @@ |
921 | 935 | var reqBaseLength = $.param( reqBase ).length; |
922 | 936 | var reqs = []; |
923 | 937 | var limit = mw.config.get( 'wgResourceLoaderMaxQueryLength', -1 ); |
924 | | - if ( limit > 0 ) { |
925 | | - // We may need to split up the request to honor the query string length limit |
926 | | - // So build it piece by piece |
927 | | - var l = reqBaseLength + 9; // '&modules='.length == 9 |
928 | | - var r = 0; |
929 | | - reqs[0] = []; |
930 | | - for ( var i = 0; i < groups[group].length; i++ ) { |
931 | | - // If the request would become too long, create a new one, |
932 | | - // but don't create empty requests |
933 | | - // '%7C'.length == 3 |
934 | | - if ( reqs[r].length > 0 && l + 3 + groups[group][i].length > limit ) { |
935 | | - // This request would become too long, create a new one |
936 | | - r++; |
937 | | - reqs[r] = []; |
938 | | - l = reqBaseLength + 9; |
939 | | - } |
940 | | - reqs[r][reqs[r].length] = groups[group][i]; |
941 | | - l += groups[group][i].length + 3; |
| 938 | + // We may need to split up the request to honor the query string length limit |
| 939 | + // So build it piece by piece |
| 940 | + var l = reqBaseLength + 9; // '&modules='.length == 9 |
| 941 | + var r = 0; |
| 942 | + reqs[0] = {}; // { prefix: [ suffixes ] } |
| 943 | + for ( var i = 0; i < groups[group].length; i++ ) { |
| 944 | + // Determine how many bytes this module would add to the query string |
| 945 | + var lastDotIndex = groups[group][i].lastIndexOf( '.' ); |
| 946 | + // Note that these substr() calls work even if lastDotIndex == -1 |
| 947 | + var prefix = groups[group][i].substr( 0, lastDotIndex ); |
| 948 | + var suffix = groups[group][i].substr( lastDotIndex + 1 ); |
| 949 | + var bytesAdded = prefix in reqs[r] ? |
| 950 | + suffix.length + 3 : // '%2C'.length == 3 |
| 951 | + groups[group][i].length + 3; // '%7C'.length == 3 |
| 952 | + |
| 953 | + // If the request would become too long, create a new one, |
| 954 | + // but don't create empty requests |
| 955 | + if ( limit > 0 && reqs[r] != {} && l + bytesAdded > limit ) { |
| 956 | + // This request would become too long, create a new one |
| 957 | + r++; |
| 958 | + reqs[r] = {}; |
| 959 | + l = reqBaseLength + 9; |
942 | 960 | } |
943 | | - } else { |
944 | | - // No splitting needed |
945 | | - reqs = [ groups[group] ]; |
| 961 | + if ( !( prefix in reqs[r] ) ) { |
| 962 | + reqs[r][prefix] = []; |
| 963 | + } |
| 964 | + reqs[r][prefix].push( suffix ); |
| 965 | + l += bytesAdded; |
946 | 966 | } |
947 | 967 | for ( var r = 0; r < reqs.length; r++ ) { |
948 | 968 | requests[requests.length] = $.extend( |
949 | | - { 'modules': reqs[r].join( '|' ) }, reqBase |
| 969 | + { 'modules': buildModulesString( reqs[r] ) }, reqBase |
950 | 970 | ); |
951 | 971 | } |
952 | 972 | } |