Completed
Push — master ( 3ca12e...d41f98 )
by
unknown
02:46
created

ImportConstraintEntities::importEntityFromJson()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 33
rs 9.0808
c 0
b 0
f 0
cc 5
nc 16
nop 2
1
<?php
2
3
namespace WikibaseQuality\ConstraintReport\Maintenance;
4
5
use Config;
6
use Deserializers\Deserializer;
7
use Maintenance;
8
use Serializers\Serializer;
9
use User;
10
use Wikibase\DataModel\Entity\Item;
11
use Wikibase\DataModel\SiteLinkList;
12
use Wikibase\DataModel\Statement\StatementListProvider;
13
use Wikibase\Lib\Store\EntityStore;
14
use Wikibase\Lib\Store\StorageException;
15
use Wikibase\Repo\WikibaseRepo;
16
17
// @codeCoverageIgnoreStart
18
$basePath = getenv( "MW_INSTALL_PATH" ) !== false
19
	? getenv( "MW_INSTALL_PATH" ) : __DIR__ . "/../../..";
20
21
require_once $basePath . "/maintenance/Maintenance.php";
22
// @codeCoverageIgnoreEnd
23
24
/**
25
 * Imports entities needed for constraint checks from Wikidata into the local repository.
26
 *
27
 * @license GPL-2.0-or-later
28
 */
29
class ImportConstraintEntities extends Maintenance {
30
31
	/**
32
	 * @var Serializer
33
	 */
34
	private $entitySerializer;
35
36
	/**
37
	 * @var Deserializer
38
	 */
39
	private $entityDeserializer;
40
41
	/**
42
	 * @var EntityStore
43
	 */
44
	private $entityStore;
45
46
	/**
47
	 * @var User|null (null in dry-run mode, non-null otherwise)
48
	 */
49
	private $user;
50
51
	public function __construct() {
52
		parent::__construct();
53
54
		$this->addDescription(
55
			'Import entities needed for constraint checks ' .
56
			'from Wikidata into the local repository.'
57
		);
58
		$this->addOption(
59
			'config-format',
60
			'The format in which the resulting configuration will be omitted: ' .
61
			'"globals" for directly settings global variables, suitable for inclusion in LocalSettings.php (default), ' .
62
			'or "wgConf" for printing parts of arrays suitable for inclusion in $wgConf->settings.'
63
		);
64
		$this->addOption(
65
			'dry-run',
66
			'Don’t actually import entities, just print which ones would be imported.'
67
		);
68
	}
69
70
	/**
71
	 * (This cannot happen in the constructor because the autoloader is not yet initialized there.)
72
	 */
73
	private function setupServices() {
74
		$repo = WikibaseRepo::getDefaultInstance();
75
		$this->entitySerializer = $repo->getAllTypesEntitySerializer();
76
		$this->entityDeserializer = $repo->getInternalFormatEntityDeserializer();
77
		$this->entityStore = $repo->getEntityStore();
78
		if ( !$this->getOption( 'dry-run', false ) ) {
79
			$this->user = User::newSystemUser( 'WikibaseQualityConstraints importer' );
80
		}
81
	}
82
83
	public function execute() {
84
		$this->setupServices();
85
86
		$configUpdates = [];
87
88
		$extensionJsonFile = __DIR__ . '/../extension.json';
89
		$extensionJsonText = file_get_contents( $extensionJsonFile );
90
		$extensionJson = json_decode( $extensionJsonText, /* assoc = */ true );
91
		$wikidataEntityIds = $this->getEntitiesToImport( $extensionJson['config'], $this->getConfig() );
92
93
		foreach ( $wikidataEntityIds as $key => $wikidataEntityId ) {
94
			$localEntityId = $this->importEntityFromWikidata( $wikidataEntityId );
95
			$configUpdates[$key] = [
96
				'wikidata' => $wikidataEntityId,
97
				'local' => $localEntityId,
98
			];
99
		}
100
101
		$this->outputConfigUpdates( $configUpdates );
102
	}
103
104
	/**
105
	 * @param array $extensionJsonConfig
106
	 * @param Config $wikiConfig
107
	 * @return string[]
108
	 */
109
	private function getEntitiesToImport( array $extensionJsonConfig, Config $wikiConfig ) {
110
		$wikidataEntityIds = [];
111
112
		foreach ( $extensionJsonConfig as $key => $value ) {
113
			if ( !preg_match( '/Id$/', $key ) ) {
114
				continue;
115
			}
116
117
			$wikidataEntityId = $value['value'];
118
			$localEntityId = $wikiConfig->get( $key );
119
120
			if ( $localEntityId === $wikidataEntityId ) {
121
				$wikidataEntityIds[$key] = $wikidataEntityId;
122
			}
123
		}
124
125
		return $wikidataEntityIds;
126
	}
127
128
	/**
129
	 * @param string $wikidataEntityId
130
	 * @return string local entity ID
131
	 */
132
	private function importEntityFromWikidata( $wikidataEntityId ) {
133
		$wikidataEntityUrl = "https://www.wikidata.org/wiki/Special:EntityData/$wikidataEntityId.json";
134
		$wikidataEntitiesJson = file_get_contents( $wikidataEntityUrl );
135
		return $this->importEntityFromJson( $wikidataEntityId, $wikidataEntitiesJson );
136
	}
137
138
	/**
139
	 * @param string $wikidataEntityId
140
	 * @param string $wikidataEntitiesJson
141
	 * @return string local entity ID
142
	 */
143
	private function importEntityFromJson( $wikidataEntityId, $wikidataEntitiesJson ) {
144
		$wikidataEntityArray = json_decode( $wikidataEntitiesJson, true )['entities'][$wikidataEntityId];
145
		$wikidataEntity = $this->entityDeserializer->deserialize( $wikidataEntityArray );
146
147
		$wikidataEntity->setId( null );
148
149
		if ( $wikidataEntity instanceof StatementListProvider ) {
150
			$wikidataEntity->getStatements()->clear();
151
		}
152
153
		if ( $wikidataEntity instanceof Item ) {
154
			$wikidataEntity->setSiteLinkList( new SiteLinkList() );
155
		}
156
157
		if ( $this->getOption( 'dry-run', false ) ) {
158
			$wikidataEntityJson = json_encode( $this->entitySerializer->serialize( $wikidataEntity ) );
159
			$this->output( $wikidataEntityJson . "\n" );
160
			return "-$wikidataEntityId";
161
		}
162
163
		try {
164
			$localEntity = $this->entityStore->saveEntity(
165
				$wikidataEntity,
166
				"imported from [[wikidata:$wikidataEntityId]]",
167
				$this->user,
168
				EDIT_NEW | EDIT_FORCE_BOT
169
			)->getEntity();
170
171
			return $localEntity->getId()->getSerialization();
172
		} catch ( StorageException $storageException ) {
0 ignored issues
show
Bug introduced by
The class Wikibase\Lib\Store\StorageException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
173
			return $this->storageExceptionToEntityId( $storageException );
174
		}
175
	}
176
177
	private function storageExceptionToEntityId( StorageException $storageException ) {
178
		$message = $storageException->getMessage();
179
		// example messages:
180
		// * Item [[Item:Q475|Q475]] already has label "as references" associated with language code en, using the same description text.
181
		// * Item [[Q475]] already has label "as references" associated with language code en, using the same description text.
182
		// * Property [[Property:P694|P694]] already has label "instance of" associated with language code en.
183
		$pattern = '/[[|]([^][|]*)]] already has label .* associated with language code/';
184
		if ( preg_match( $pattern, $message, $matches ) ) {
185
			return $matches[1];
186
		} else {
187
			throw $storageException;
188
		}
189
	}
190
191
	private function outputConfigUpdates( array $configUpdates ) {
192
		$configFormat = $this->getOption( 'config-format', 'globals' );
193
		switch ( $configFormat ) {
194
			case 'globals':
195
				$this->outputConfigUpdatesGlobals( $configUpdates );
196
				break;
197
			case 'wgConf':
198
				$this->outputConfigUpdatesWgConf( $configUpdates );
199
				break;
200
			default:
201
				$this->error( "Invalid config format \"$configFormat\", using \"globals\"" );
202
				$this->outputConfigUpdatesGlobals( $configUpdates );
203
				break;
204
		}
205
	}
206
207
	private function outputConfigUpdatesGlobals( array $configUpdates ) {
208
		foreach ( $configUpdates as $key => $value ) {
209
			$localValueCode = var_export( $value['local'], true );
210
			$this->output( "\$wg$key = $localValueCode;\n" );
211
		}
212
	}
213
214
	private function outputConfigUpdatesWgConf( array $configUpdates ) {
215
		foreach ( $configUpdates as $key => $value ) {
216
			$keyCode = var_export( $key, true );
217
			$wikidataValueCode = var_export( $value['wikidata'], true );
218
			$localValueCode = var_export( $value['local'], true );
219
			$wikiIdCode = var_export( wfWikiID(), true );
220
			$block = <<< EOF
221
$keyCode => [
222
	'default' => $wikidataValueCode,
223
	$wikiIdCode => $localValueCode,
224
],
225
226
227
EOF;
228
			$this->output( $block );
229
		}
230
	}
231
232
}
233
234
// @codeCoverageIgnoreStart
235
$maintClass = ImportConstraintEntities::class;
236
require_once RUN_MAINTENANCE_IF_MAIN;
237
// @codeCoverageIgnoreEnd
238