applyEntityNamespacesToSettings()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 3
nc 3
nop 2
1
<?php
2
3
namespace Wikibase\Lib;
4
5
use ExtensionRegistry;
6
use Hooks;
7
use MWException;
8
9
/**
10
 * WikibaseSettings is a static access point to Wikibase settings defined as global state
11
 * (typically in LocalSettings.php).
12
 *
13
 * @note WikibaseSettings is intended for internal use by bootstrapping code. Application service
14
 * logic should have individual settings injected, static entry points to application logic should
15
 * use top level factory methods such as WikibaseRepo::getSettings() and
16
 * WikibaseClient::getSettings().
17
 *
18
 * @todo Move this to a separate component.
19
 *
20
 * @license GPL-2.0-or-later
21
 * @author Daniel Kinzler
22
 */
23
class WikibaseSettings {
24
25
	/**
26
	 * @return bool True if and only if the Wikibase repository component is enabled on this wiki.
27
	 */
28
	public static function isRepoEnabled() {
29
		return ExtensionRegistry::getInstance()->isLoaded( 'WikibaseRepository' );
30
	}
31
32
	/**
33
	 * @note This runs the WikibaseRepoEntityNamespaces hook to allow extensions to modify
34
	 *       the repo 'entityNamespaces' setting.
35
	 *
36
	 * @throws MWException
37
	 *
38
	 * @return SettingsArray
39
	 */
40
	public static function getRepoSettings() {
41
		global $wgWBRepoSettings;
42
		if ( !self::isRepoEnabled() ) {
43
			throw new MWException( 'Cannot access repo settings: Wikibase Repository component is not enabled!' );
44
		}
45
46
		$repoSettings = array_merge(
47
			require __DIR__ . '/../config/WikibaseLib.default.php',
48
			require __DIR__ . '/../../repo/config/Wikibase.default.php'
49
		);
50
		// Hack to make a proper merge strategy for these configs in case they are being overriden by sysadmins
51
		// More info T257447
52
		// TODO: This should move to a proper mediawiki config handler: T258658
53
		$overrideArrays = [ 'entityDataFormats' ];
54
		$twoDArrayMerge = [ 'string-limits', 'pagePropertiesRdf' ];
55
		$falseMeansRemove = [ 'urlSchemes', 'canonicalLanguageCodes', 'globeUris' ];
56
57
		$settings = self::mergeSettings(
58
			$repoSettings,
59
			$wgWBRepoSettings ?? [],
60
			$overrideArrays,
61
			$twoDArrayMerge,
62
			$falseMeansRemove
63
		);
64
65
		$entityNamespaces = self::buildEntityNamespaceConfigurations( $settings );
66
67
		Hooks::run( 'WikibaseRepoEntityNamespaces', [ &$entityNamespaces ] );
68
69
		self::applyEntityNamespacesToSettings( $settings, $entityNamespaces );
70
		return $settings;
71
	}
72
73
	/**
74
	 * @return bool True if and only if the Wikibase client component is enabled on this wiki.
75
	 */
76
	public static function isClientEnabled() {
77
		return ExtensionRegistry::getInstance()->isLoaded( 'WikibaseClient' );
78
	}
79
80
	/**
81
	 * @throws MWException
82
	 *
83
	 * @return SettingsArray
84
	 */
85
	public static function getClientSettings() {
86
		global $wgWBClientSettings;
87
88
		if ( !self::isClientEnabled() ) {
89
			throw new MWException( 'Cannot access client settings: Wikibase Client component is not enabled!' );
90
		}
91
92
		$clientSettings = array_merge(
93
			require __DIR__ . '/../config/WikibaseLib.default.php',
94
			require __DIR__ . '/../../client/config/WikibaseClient.default.php'
95
		);
96
97
		$settings = self::mergeSettings( $clientSettings, $wgWBClientSettings ?? [] );
98
99
		$entityNamespaces = self::buildEntityNamespaceConfigurations( $settings );
100
101
		Hooks::run( 'WikibaseClientEntityNamespaces', [ &$entityNamespaces ] );
102
103
		self::applyEntityNamespacesToSettings( $settings, $entityNamespaces );
104
105
		return $settings;
106
	}
107
108
	/**
109
	 * Merge two arrays of default and custom settings,
110
	 * so that it looks like the custom settings were added on top of the default settings.
111
	 *
112
	 * Originally, Wikibase extensions were loaded and configured somewhat like this:
113
	 *
114
	 *     require_once "$IP/extensions/Wikibase/client/WikibaseClient.php";
115
	 *     $wgWBClientSettings['repoUrl'] = 'https://pool.my.wiki';
116
	 *
117
	 * Here, $wgWBClientSettings would be initialized by WikibaseClient.php.
118
	 * However, with the move to extension registration and wfLoadExtension(),
119
	 * this is no longer possible, and $wgWBClientSettings will start out empty.
120
	 * This method returns an array that looks like the custom settings
121
	 * were added on top of existing default settings as above,
122
	 * even though the default settings were in fact only loaded later.
123
	 *
124
	 * @param array $defaultSettings The default settings loaded from some other config file.
125
	 * @param array $customSettings The custom settings from a configuration global.
126
	 * @param string[] $overrideArrays
127
	 * @param string[] $twoDArrayMerge
128
	 * @param string[] $falseMeansRemove
129
	 * @return SettingsArray The merged settings.
130
	 */
131
	private static function mergeSettings(
132
		array $defaultSettings,
133
		array $customSettings,
134
		array $overrideArrays = [],
135
		array $twoDArrayMerge = [],
136
		array $falseMeansRemove = []
137
	): SettingsArray {
138
		foreach ( $customSettings as $key => $value ) {
139
			$defaultValue = $defaultSettings[$key] ?? [];
140
			if ( is_array( $value ) && is_array( $defaultValue ) ) {
141
				$defaultSettings[$key] = self::mergeComplexArrays(
142
					$key,
143
					$value,
144
					$defaultValue,
145
					$twoDArrayMerge,
146
					$overrideArrays,
147
					$falseMeansRemove
148
				);
149
			} else {
150
				$defaultSettings[$key] = $value;
151
			}
152
		}
153
154
		return new SettingsArray( $defaultSettings );
155
	}
156
157
	private static function mergeComplexArrays(
158
		string $key,
159
		array $value,
160
		array $defaultValue,
161
		array $twoDArrayMerge,
162
		array $overrideArrays,
163
		array $falseMeansRemove
164
	): array {
165
		if ( in_array( $key, $twoDArrayMerge ) ) {
166
			return wfArrayPlus2d( $value, $defaultValue );
167
		}
168
		if ( in_array( $key, $overrideArrays ) ) {
169
			return $value;
170
		}
171
		if ( in_array( $key, $falseMeansRemove ) ) {
172
			$result = array_merge( $defaultValue, $value );
173
			foreach ( $result as $valueKey => $valueValue ) {
174
				if ( $valueValue === false ) {
175
					unset( $result[$valueKey] );
176
					$index = array_search( $valueKey, $result );
177
					if ( $index !== false ) {
178
						unset( $result[$index] );
179
					}
180
				}
181
			}
182
183
			// Keep the non-numeric keys but drop the numeric ones
184
			return array_merge( $result );
185
		}
186
		return array_merge( $defaultValue, $value );
187
	}
188
189
	/**
190
	 * @throws MWException in case of a misconfiguration
191
	 * @return int[] An array mapping entity type identifiers to namespace numbers.
192
	 */
193
	private static function buildEntityNamespaceConfigurations( SettingsArray $settings ) {
194
		if ( !$settings->hasSetting( 'repositories' ) && !$settings->hasSetting( 'entityNamespaces' ) ) {
195
			throw new MWException( 'Wikibase: Incomplete configuration: '
196
				. 'The \'entityNamespaces\' setting has to be set to an '
197
				. 'array mapping entity types to namespace IDs. '
198
				. 'See Wikibase.example.php for details and examples.' );
199
		}
200
201
		$namespaces = $settings->hasSetting( 'entityNamespaces' )
202
			? $settings->getSetting( 'entityNamespaces' )
203
			: self::getEntityNamespacesFromRepositorySettings( $settings->getSetting( 'repositories' ) );
204
205
		return $namespaces;
206
	}
207
208
	private static function getEntityNamespacesFromRepositorySettings( array $repositorySettings ) {
209
		return array_reduce(
210
			$repositorySettings,
211
			function ( array $result, array $repoSettings ) {
212
				return array_merge( $result, $repoSettings['entityNamespaces'] );
213
			},
214
			[]
215
		);
216
	}
217
218
	private static function applyEntityNamespacesToSettings( SettingsArray $settings, array $entityNamespaces ) {
219
		if ( $settings->hasSetting( 'entityNamespaces' ) ) {
220
			$settings->setSetting( 'entityNamespaces', $entityNamespaces );
0 ignored issues
show
Unused Code introduced by
The call to the method Wikibase\Lib\SettingsArray::setSetting() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
221
			return;
222
		}
223
224
		$repositorySettings = $settings->getSetting( 'repositories' );
225
		$namespacesDefinedForRepositories = self::getEntityNamespacesFromRepositorySettings( $repositorySettings );
226
227
		$namespacesInNoRepository = array_diff_key( $entityNamespaces, $namespacesDefinedForRepositories );
228
229
		if ( $namespacesInNoRepository ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $namespacesInNoRepository of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
230
			$repositorySettings['']['entityNamespaces'] += $namespacesInNoRepository;
231
			$settings->setSetting( 'repositories', $repositorySettings );
0 ignored issues
show
Unused Code introduced by
The call to the method Wikibase\Lib\SettingsArray::setSetting() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
232
		}
233
	}
234
235
}
236