},
"config": {
"type": "object",
- "description": "Configuration options for this extension"
+ "description": "Configuration options for this extension",
+ "patternProperties": {
+ "^[a-zA-Z_\u007f-\u00ff][a-zA-Z0-9_\u007f-\u00ff]*$": {
+ "type": ["object", "array", "string", "integer", "null", "boolean"],
+ "properties": {
+ "_merge_strategy": {
+ "type": "string",
+ "enum": [
+ "array_merge_recursive",
+ "array_plus_2d",
+ "array_plus",
+ "array_merge"
+ ],
+ "default": "array_merge"
+ }
+ }
+ }
+ }
},
"ParserTestFiles": {
"type": "array",
'ValidSkinNames',
);
+ /**
+ * Mapping of global settings to their specific merge strategies.
+ *
+ * @see ExtensionRegistry::exportExtractedData
+ * @see getExtractedInfo
+ * @var array
+ */
+ protected static $mergeStrategies = array(
+ 'wgGroupPermissions' => 'array_plus_2d',
+ 'wgRevokePermissions' => 'array_plus_2d',
+ 'wgHooks' => 'array_merge_recursive',
+ 'wgExtensionCredits' => 'array_merge_recursive',
+ 'wgExtraNamespaces' => 'array_plus',
+ 'wgExtraGenderNamespaces' => 'array_plus',
+ 'wgNamespacesWithSubpages' => 'array_plus',
+ 'wgNamespaceContentModels' => 'array_plus',
+ );
+
/**
* Keys that are part of the extension credits
*
}
public function getExtractedInfo() {
+ // Make sure the merge strategies are set
+ foreach ( $this->globals as $key => $val ) {
+ if ( isset( self::$mergeStrategies[$key] ) ) {
+ $this->globals[$key][ExtensionRegistry::MERGE_STRATEGY] = self::$mergeStrategies[$key];
+ }
+ }
+
return array(
'globals' => $this->globals,
'defines' => $this->defines,
*/
const OLDEST_MANIFEST_VERSION = 1;
+ /**
+ * Special key that defines the merge strategy
+ *
+ * @since 1.26
+ */
+ const MERGE_STRATEGY = '_merge_strategy';
+
/**
* @var BagOStuff
*/
protected function exportExtractedData( array $info ) {
foreach ( $info['globals'] as $key => $val ) {
+ // If a merge strategy is set, read it and remove it from the value
+ // so it doesn't accidentally end up getting set.
+ // Need to check $val is an array for PHP 5.3 which will return
+ // true on isset( 'string'['foo'] ).
+ if ( isset( $val[self::MERGE_STRATEGY] ) && is_array( $val ) ) {
+ $mergeStrategy = $val[self::MERGE_STRATEGY];
+ unset( $val[self::MERGE_STRATEGY] );
+ } else {
+ $mergeStrategy = 'array_merge';
+ }
+
+ // Optimistic: If the global is not set, or is an empty array, replace it entirely.
+ // Will be O(1) performance.
if ( !isset( $GLOBALS[$key] ) || ( is_array( $GLOBALS[$key] ) && !$GLOBALS[$key] ) ) {
$GLOBALS[$key] = $val;
- } elseif ( $key === 'wgHooks' || $key === 'wgExtensionCredits' ) {
- // Special case $wgHooks and $wgExtensionCredits, which require a recursive merge.
- // Ideally it would have been taken care of in the first if block though.
- $GLOBALS[$key] = array_merge_recursive( $GLOBALS[$key], $val );
- } elseif ( $key === 'wgGroupPermissions' || $key === 'wgRevokePermissions' ) {
- // First merge individual groups
- foreach ( $GLOBALS[$key] as $name => &$groupVal ) {
- if ( isset( $val[$name] ) ) {
- $groupVal += $val[$name];
+ continue;
+ }
+
+ if ( !is_array( $GLOBALS[$key] ) || !is_array( $val ) ) {
+ // config setting that has already been overridden, don't set it
+ continue;
+ }
+
+ switch ( $mergeStrategy ) {
+ case 'array_merge_recursive':
+ $GLOBALS[$key] = array_merge_recursive( $GLOBALS[$key], $val );
+ break;
+ case 'array_plus_2d':
+ // First merge items that are in both arrays
+ foreach ( $GLOBALS[$key] as $name => &$groupVal ) {
+ if ( isset( $val[$name] ) ) {
+ $groupVal += $val[$name];
+ }
}
- }
- // Now merge groups that didn't exist yet
- $GLOBALS[$key] += $val;
- } elseif ( is_array( $GLOBALS[$key] ) && is_array( $val ) ) {
- $GLOBALS[$key] = array_merge( $val, $GLOBALS[$key] );
- } // else case is a config setting where it has already been overriden, so don't set it
+ // Now add items that didn't exist yet
+ $GLOBALS[$key] += $val;
+ break;
+ case 'array_plus':
+ $GLOBALS[$key] = $val + $GLOBALS[$key];
+ break;
+ case 'array_merge':
+ $GLOBALS[$key] = array_merge( $val, $GLOBALS[$key] );
+ break;
+ default:
+ throw new UnexpectedValueException( "Unknown merge strategy '$mergeStrategy'" );
+ }
}
+
foreach ( $info['defines'] as $name => $val ) {
define( $name, $val );
}
}
public static function provideRegisterHooks() {
+ $merge = array( ExtensionRegistry::MERGE_STRATEGY => 'array_merge_recursive' );
// Format:
// Current $wgHooks
// Content in extension.json
array(
array(),
self::$default,
- array(),
+ $merge,
),
// No current hooks, adding one for "FooBaz"
array(
array(),
array( 'Hooks' => array( 'FooBaz' => 'FooBazCallback' ) ) + self::$default,
- array( 'FooBaz' => array( 'FooBazCallback' ) ),
+ array( 'FooBaz' => array( 'FooBazCallback' ) ) + $merge,
),
// Hook for "FooBaz", adding another one
array(
array( 'FooBaz' => array( 'PriorCallback' ) ),
array( 'Hooks' => array( 'FooBaz' => 'FooBazCallback' ) ) + self::$default,
- array( 'FooBaz' => array( 'PriorCallback', 'FooBazCallback' ) ),
+ array( 'FooBaz' => array( 'PriorCallback', 'FooBazCallback' ) ) + $merge,
),
// Hook for "BarBaz", adding one for "FooBaz"
array(
array(
'BarBaz' => array( 'BarBazCallback' ),
'FooBaz' => array( 'FooBazCallback' ),
- ),
+ ) + $merge,
),
// Callbacks for FooBaz wrapped in an array
array(
array( 'Hooks' => array( 'FooBaz' => array( 'Callback1' ) ) ) + self::$default,
array(
'FooBaz' => array( 'Callback1' ),
- ),
+ ) + $merge,
),
// Multiple callbacks for FooBaz hook
array(
array( 'Hooks' => array( 'FooBaz' => array( 'Callback1', 'Callback2' ) ) ) + self::$default,
array(
'FooBaz' => array( 'Callback1', 'Callback2' ),
- ),
+ ) + $merge,
),
);
}
),
)
),
+ array(
+ 'Global already set, 1d array that appends',
+ array(
+ 'mwAvailableRights' => array(
+ 'foobar',
+ 'foo'
+ ),
+ ),
+ array(
+ 'mwAvailableRights' => array(
+ 'barbaz',
+ ),
+ ),
+ array(
+ 'mwAvailableRights' => array(
+ 'barbaz',
+ 'foobar',
+ 'foo',
+ ),
+ )
+ ),
+ array(
+ 'Global already set, 2d array with integer keys',
+ array(
+ 'mwNamespacesFoo' => array(
+ 100 => true,
+ 102 => false
+ ),
+ ),
+ array(
+ 'mwNamespacesFoo' => array(
+ 100 => false,
+ 500 => true,
+ ExtensionRegistry::MERGE_STRATEGY => 'array_plus',
+ ),
+ ),
+ array(
+ 'mwNamespacesFoo' => array(
+ 100 => false,
+ 102 => false,
+ 500 => true,
+ ),
+ )
+ ),
array(
'No global already set, $wgHooks',
array(
'FooBarEvent' => array(
'FooBarClass::onFooBarEvent'
),
+ ExtensionRegistry::MERGE_STRATEGY => 'array_merge_recursive'
),
),
array(
'FooBarEvent' => array(
'BazBarClass::onFooBarEvent',
),
+ ExtensionRegistry::MERGE_STRATEGY => 'array_merge_recursive',
),
),
array(
'right' => true,
'somethingtwo' => false,
'nonduplicated' => true,
- )
+ ),
+ ExtensionRegistry::MERGE_STRATEGY => 'array_plus_2d',
),
),
array(