|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
declare( strict_types = 1 ); |
|
4
|
|
|
|
|
5
|
|
|
namespace Wikibase\Repo\Api; |
|
6
|
|
|
|
|
7
|
|
|
use ApiBase; |
|
8
|
|
|
use ApiMain; |
|
9
|
|
|
use ApiUsageException; |
|
10
|
|
|
use Exception; |
|
11
|
|
|
use InvalidArgumentException; |
|
12
|
|
|
use LogicException; |
|
13
|
|
|
use Wikibase\DataModel\Entity\EntityIdParser; |
|
14
|
|
|
use Wikibase\DataModel\Entity\EntityIdParsingException; |
|
15
|
|
|
use Wikibase\DataModel\Entity\ItemId; |
|
16
|
|
|
use Wikibase\Lib\Store\EntityRevision; |
|
17
|
|
|
use Wikibase\Repo\ChangeOp\ChangeOpsMerge; |
|
18
|
|
|
use Wikibase\Repo\Interactors\ItemMergeException; |
|
19
|
|
|
use Wikibase\Repo\Interactors\ItemMergeInteractor; |
|
20
|
|
|
use Wikibase\Repo\Interactors\RedirectCreationException; |
|
21
|
|
|
use Wikibase\Repo\WikibaseRepo; |
|
22
|
|
|
|
|
23
|
|
|
/** |
|
24
|
|
|
* @license GPL-2.0-or-later |
|
25
|
|
|
* @author Addshore |
|
26
|
|
|
* @author Daniel Kinzler |
|
27
|
|
|
* @author Lucie-Aimée Kaffee |
|
28
|
|
|
*/ |
|
29
|
|
|
class MergeItems extends ApiBase { |
|
30
|
|
|
|
|
31
|
|
|
/** |
|
32
|
|
|
* @var EntityIdParser |
|
33
|
|
|
*/ |
|
34
|
|
|
private $idParser; |
|
35
|
|
|
|
|
36
|
|
|
/** |
|
37
|
|
|
* @var ApiErrorReporter |
|
38
|
|
|
*/ |
|
39
|
|
|
private $errorReporter; |
|
40
|
|
|
|
|
41
|
|
|
/** |
|
42
|
|
|
* @var ItemMergeInteractor |
|
43
|
|
|
*/ |
|
44
|
|
|
private $interactor; |
|
45
|
|
|
|
|
46
|
|
|
/** |
|
47
|
|
|
* @var ResultBuilder |
|
48
|
|
|
*/ |
|
49
|
|
|
private $resultBuilder; |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* @see ApiBase::__construct |
|
53
|
|
|
* |
|
54
|
|
|
* @param ApiMain $mainModule |
|
55
|
|
|
* @param string $moduleName |
|
56
|
|
|
* @param EntityIdParser $idParser |
|
57
|
|
|
* @param ItemMergeInteractor $interactor |
|
58
|
|
|
* @param ApiErrorReporter $errorReporter |
|
59
|
|
|
* @param callable $resultBuilderInstantiator |
|
60
|
|
|
*/ |
|
61
|
|
|
public function __construct( |
|
62
|
|
|
ApiMain $mainModule, |
|
63
|
|
|
string $moduleName, |
|
64
|
|
|
EntityIdParser $idParser, |
|
65
|
|
|
ItemMergeInteractor $interactor, |
|
66
|
|
|
ApiErrorReporter $errorReporter, |
|
67
|
|
|
callable $resultBuilderInstantiator |
|
68
|
|
|
) { |
|
69
|
|
|
parent::__construct( $mainModule, $moduleName ); |
|
70
|
|
|
|
|
71
|
|
|
$this->idParser = $idParser; |
|
72
|
|
|
$this->interactor = $interactor; |
|
73
|
|
|
|
|
74
|
|
|
$this->errorReporter = $errorReporter; |
|
75
|
|
|
$this->resultBuilder = $resultBuilderInstantiator( $this ); |
|
76
|
|
|
} |
|
77
|
|
|
|
|
78
|
|
|
public static function factory( ApiMain $mainModule, string $moduleName ): self { |
|
79
|
|
|
$wikibaseRepo = WikibaseRepo::getDefaultInstance(); |
|
80
|
|
|
$apiHelperFactory = $wikibaseRepo->getApiHelperFactory( $mainModule->getContext() ); |
|
81
|
|
|
|
|
82
|
|
|
return new self( |
|
83
|
|
|
$mainModule, |
|
84
|
|
|
$moduleName, |
|
85
|
|
|
$wikibaseRepo->getEntityIdParser(), |
|
86
|
|
|
$wikibaseRepo->newItemMergeInteractor( $mainModule->getContext() ), |
|
87
|
|
|
$apiHelperFactory->getErrorReporter( $mainModule ), |
|
88
|
|
|
function ( $module ) use ( $apiHelperFactory ) { |
|
89
|
|
|
return $apiHelperFactory->getResultBuilder( $module ); |
|
90
|
|
|
} |
|
91
|
|
|
); |
|
92
|
|
|
} |
|
93
|
|
|
|
|
94
|
|
|
/** |
|
95
|
|
|
* @param array $parameters |
|
96
|
|
|
* @param string $name |
|
97
|
|
|
* |
|
98
|
|
|
* @return ItemId |
|
99
|
|
|
* @throws ApiUsageException if the given parameter is not a valid ItemId |
|
100
|
|
|
* @throws LogicException |
|
101
|
|
|
*/ |
|
102
|
|
|
private function getItemIdParam( array $parameters, string $name ): ItemId { |
|
103
|
|
|
if ( !isset( $parameters[$name] ) ) { |
|
104
|
|
|
$this->errorReporter->dieWithError( [ 'param-missing', $name ], 'param-missing' ); |
|
105
|
|
|
} |
|
106
|
|
|
|
|
107
|
|
|
$value = $parameters[$name]; |
|
108
|
|
|
|
|
109
|
|
|
try { |
|
110
|
|
|
return new ItemId( $value ); |
|
111
|
|
|
} catch ( InvalidArgumentException $ex ) { |
|
112
|
|
|
$this->errorReporter->dieError( $ex->getMessage(), 'invalid-entity-id' ); |
|
|
|
|
|
|
113
|
|
|
throw new LogicException( 'ApiErrorReporter::dieError did not throw an exception' ); |
|
114
|
|
|
} |
|
115
|
|
|
} |
|
116
|
|
|
|
|
117
|
|
|
/** |
|
118
|
|
|
* @inheritDoc |
|
119
|
|
|
*/ |
|
120
|
|
|
public function execute(): void { |
|
121
|
|
|
$params = $this->extractRequestParams(); |
|
122
|
|
|
|
|
123
|
|
|
try { |
|
124
|
|
|
$fromId = $this->getItemIdParam( $params, 'fromid' ); |
|
125
|
|
|
$toId = $this->getItemIdParam( $params, 'toid' ); |
|
126
|
|
|
|
|
127
|
|
|
$ignoreConflicts = $params['ignoreconflicts']; |
|
128
|
|
|
$summary = $params['summary']; |
|
129
|
|
|
|
|
130
|
|
|
if ( $ignoreConflicts === null ) { |
|
131
|
|
|
$ignoreConflicts = []; |
|
132
|
|
|
} |
|
133
|
|
|
|
|
134
|
|
|
$this->mergeItems( $fromId, $toId, $ignoreConflicts, $summary, $params['bot'] ); |
|
135
|
|
|
} catch ( EntityIdParsingException $ex ) { |
|
136
|
|
|
$this->errorReporter->dieException( $ex, 'invalid-entity-id' ); |
|
137
|
|
|
} catch ( ItemMergeException $ex ) { |
|
138
|
|
|
$this->handleException( $ex, $ex->getErrorCode() ); |
|
139
|
|
|
} catch ( RedirectCreationException $ex ) { |
|
140
|
|
|
$this->handleException( $ex, $ex->getErrorCode() ); |
|
141
|
|
|
} |
|
142
|
|
|
} |
|
143
|
|
|
|
|
144
|
|
|
/** |
|
145
|
|
|
* @param ItemId $fromId |
|
146
|
|
|
* @param ItemId $toId |
|
147
|
|
|
* @param string[] $ignoreConflicts |
|
148
|
|
|
* @param string|null $summary |
|
149
|
|
|
* @param bool $bot |
|
150
|
|
|
* @throws ItemMergeException |
|
151
|
|
|
* @throws RedirectCreationException |
|
152
|
|
|
*/ |
|
153
|
|
|
private function mergeItems( ItemId $fromId, ItemId $toId, array $ignoreConflicts, ?string $summary, bool $bot ): void { |
|
154
|
|
|
list( $newRevisionFrom, $newRevisionTo, $redirected ) |
|
155
|
|
|
= $this->interactor->mergeItems( $fromId, $toId, $ignoreConflicts, $summary, $bot ); |
|
156
|
|
|
|
|
157
|
|
|
$this->resultBuilder->setValue( null, 'success', 1 ); |
|
158
|
|
|
$this->resultBuilder->setValue( null, 'redirected', (int)$redirected ); |
|
159
|
|
|
|
|
160
|
|
|
$this->addEntityToOutput( $newRevisionFrom, 'from' ); |
|
161
|
|
|
$this->addEntityToOutput( $newRevisionTo, 'to' ); |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
/** |
|
165
|
|
|
* @param Exception $ex |
|
166
|
|
|
* @param string $errorCode |
|
167
|
|
|
* @param string[] $extraData |
|
168
|
|
|
* |
|
169
|
|
|
* @throws ApiUsageException always |
|
170
|
|
|
*/ |
|
171
|
|
|
private function handleException( Exception $ex, string $errorCode, array $extraData = [] ): void { |
|
172
|
|
|
$cause = $ex->getPrevious(); |
|
173
|
|
|
|
|
174
|
|
|
if ( $cause ) { |
|
175
|
|
|
$extraData[] = $ex->getMessage(); |
|
176
|
|
|
$this->handleException( $cause, $errorCode, $extraData ); |
|
177
|
|
|
} else { |
|
178
|
|
|
$this->errorReporter->dieException( $ex, $errorCode, 0, [ 'extradata' => $extraData ] ); |
|
179
|
|
|
} |
|
180
|
|
|
} |
|
181
|
|
|
|
|
182
|
|
|
private function addEntityToOutput( EntityRevision $entityRevision, string $name ): void { |
|
183
|
|
|
$entityId = $entityRevision->getEntity()->getId(); |
|
184
|
|
|
$revisionId = $entityRevision->getRevisionId(); |
|
185
|
|
|
|
|
186
|
|
|
$this->resultBuilder->addBasicEntityInformation( $entityId, $name ); |
|
|
|
|
|
|
187
|
|
|
|
|
188
|
|
|
$this->resultBuilder->setValue( |
|
189
|
|
|
$name, |
|
190
|
|
|
'lastrevid', |
|
191
|
|
|
(int)$revisionId |
|
192
|
|
|
); |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
/** |
|
196
|
|
|
* @see ApiBase::needsToken |
|
197
|
|
|
* |
|
198
|
|
|
* @return string |
|
199
|
|
|
*/ |
|
200
|
|
|
public function needsToken(): string { |
|
201
|
|
|
return 'csrf'; |
|
202
|
|
|
} |
|
203
|
|
|
|
|
204
|
|
|
/** |
|
205
|
|
|
* @inheritDoc |
|
206
|
|
|
*/ |
|
207
|
|
|
protected function getAllowedParams(): array { |
|
208
|
|
|
return [ |
|
209
|
|
|
'fromid' => [ |
|
210
|
|
|
self::PARAM_TYPE => 'string', |
|
211
|
|
|
self::PARAM_REQUIRED => true, |
|
212
|
|
|
], |
|
213
|
|
|
'toid' => [ |
|
214
|
|
|
self::PARAM_TYPE => 'string', |
|
215
|
|
|
self::PARAM_REQUIRED => true, |
|
216
|
|
|
], |
|
217
|
|
|
'ignoreconflicts' => [ |
|
218
|
|
|
self::PARAM_ISMULTI => true, |
|
219
|
|
|
self::PARAM_TYPE => ChangeOpsMerge::$conflictTypes, |
|
220
|
|
|
self::PARAM_REQUIRED => false, |
|
221
|
|
|
], |
|
222
|
|
|
'summary' => [ |
|
223
|
|
|
self::PARAM_TYPE => 'string', |
|
224
|
|
|
], |
|
225
|
|
|
'bot' => [ |
|
226
|
|
|
self::PARAM_TYPE => 'boolean', |
|
227
|
|
|
self::PARAM_DFLT => false, |
|
228
|
|
|
], |
|
229
|
|
|
'token' => [ |
|
230
|
|
|
self::PARAM_TYPE => 'string', |
|
231
|
|
|
self::PARAM_REQUIRED => true, |
|
232
|
|
|
] |
|
233
|
|
|
]; |
|
234
|
|
|
} |
|
235
|
|
|
|
|
236
|
|
|
/** |
|
237
|
|
|
* @inheritDoc |
|
238
|
|
|
*/ |
|
239
|
|
|
protected function getExamplesMessages(): array { |
|
240
|
|
|
return [ |
|
241
|
|
|
'action=wbmergeitems&fromid=Q42&toid=Q222' => |
|
242
|
|
|
'apihelp-wbmergeitems-example-1', |
|
243
|
|
|
'action=wbmergeitems&fromid=Q555&toid=Q3' => |
|
244
|
|
|
'apihelp-wbmergeitems-example-2', |
|
245
|
|
|
'action=wbmergeitems&fromid=Q66&toid=Q99&ignoreconflicts=sitelink' => |
|
246
|
|
|
'apihelp-wbmergeitems-example-3', |
|
247
|
|
|
'action=wbmergeitems&fromid=Q66&toid=Q99&ignoreconflicts=sitelink|description' => |
|
248
|
|
|
'apihelp-wbmergeitems-example-4', |
|
249
|
|
|
]; |
|
250
|
|
|
} |
|
251
|
|
|
|
|
252
|
|
|
/** |
|
253
|
|
|
* @see ApiBase::isWriteMode |
|
254
|
|
|
* |
|
255
|
|
|
* @return bool Always true. |
|
256
|
|
|
*/ |
|
257
|
|
|
public function isWriteMode(): bool { |
|
258
|
|
|
return true; |
|
259
|
|
|
} |
|
260
|
|
|
|
|
261
|
|
|
} |
|
262
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.