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 ) { |
|
|
|
|
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
|
|
|
|
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.