1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Mediawiki\Api; |
4
|
|
|
|
5
|
|
|
use GuzzleHttp\Client; |
6
|
|
|
use GuzzleHttp\ClientInterface; |
7
|
|
|
use GuzzleHttp\Exception\RequestException; |
8
|
|
|
use GuzzleHttp\Promise\PromiseInterface; |
9
|
|
|
use InvalidArgumentException; |
10
|
|
|
use Psr\Http\Message\ResponseInterface; |
11
|
|
|
use Psr\Log\LoggerAwareInterface; |
12
|
|
|
use Psr\Log\LoggerInterface; |
13
|
|
|
use Psr\Log\LogLevel; |
14
|
|
|
use Psr\Log\NullLogger; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* @author Addshore |
18
|
|
|
*/ |
19
|
|
|
class MediawikiApi implements LoggerAwareInterface { |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @var ClientInterface |
23
|
|
|
*/ |
24
|
|
|
private $client; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @var bool|string |
28
|
|
|
*/ |
29
|
|
|
private $isLoggedIn; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @var MediawikiSession |
33
|
|
|
*/ |
34
|
|
|
private $session; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* @var string |
38
|
|
|
*/ |
39
|
|
|
private $version; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @var LoggerInterface |
43
|
|
|
*/ |
44
|
|
|
private $logger; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @param string $apiUrl The API Url |
48
|
|
|
* @param ClientInterface|null $client Guzzle Client |
49
|
|
|
* @param MediawikiSession|null $session Inject a custom session here |
50
|
|
|
*/ |
51
|
23 |
|
public function __construct( $apiUrl, ClientInterface $client = null, MediawikiSession $session = null ) { |
52
|
23 |
|
if( !is_string( $apiUrl ) ) { |
53
|
4 |
|
throw new InvalidArgumentException( '$apiUrl must be a string' ); |
54
|
|
|
} |
55
|
19 |
|
if( $client === null ) { |
56
|
3 |
|
$client = new Client( array( 'cookies' => true ) ); |
57
|
19 |
|
} elseif( $client->getConfig( 'cookies' ) === false ) { |
|
|
|
|
58
|
|
|
// TODO Somehow flag that things will not work? |
59
|
|
|
} |
60
|
19 |
|
if( $session === null ) { |
61
|
19 |
|
$session = new MediawikiSession( $this ); |
62
|
19 |
|
} |
63
|
|
|
|
64
|
19 |
|
$this->apiUrl = $apiUrl; |
|
|
|
|
65
|
19 |
|
$this->client = $client; |
66
|
19 |
|
$this->session = $session; |
67
|
|
|
|
68
|
19 |
|
$this->logger = new NullLogger(); |
69
|
19 |
|
} |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Sets a logger instance on the object |
73
|
|
|
* |
74
|
|
|
* @since 1.1 |
75
|
|
|
* |
76
|
|
|
* @param LoggerInterface $logger |
77
|
|
|
* |
78
|
|
|
* @return null |
79
|
|
|
*/ |
80
|
|
|
public function setLogger( LoggerInterface $logger ) { |
81
|
|
|
$this->logger = $logger; |
82
|
|
|
$this->session->setLogger( $logger ); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @since 2.0 |
87
|
|
|
* |
88
|
|
|
* @param Request $request |
89
|
|
|
* |
90
|
|
|
* @return PromiseInterface |
91
|
|
|
* Normally promising an array, though can be mixed (json_decode result) |
92
|
|
|
* Can throw UsageExceptions or RejectionExceptions |
93
|
|
|
*/ |
94
|
|
|
public function getRequestAsync( Request $request ) { |
95
|
|
|
$promise = $this->client->getAsync( |
96
|
|
|
$this->apiUrl, |
97
|
|
|
$this->getClientRequestOptions( $request, 'query' ) |
98
|
|
|
); |
99
|
|
|
|
100
|
|
|
return $promise->then( function( ResponseInterface $response ) { |
101
|
|
|
return call_user_func( array( $this, 'decodeResponse' ), $response ); |
102
|
|
|
} ); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* @since 2.0 |
107
|
|
|
* |
108
|
|
|
* @param Request $request |
109
|
|
|
* |
110
|
|
|
* @return PromiseInterface |
111
|
|
|
* Normally promising an array, though can be mixed (json_decode result) |
112
|
|
|
* Can throw UsageExceptions or RejectionExceptions |
113
|
|
|
*/ |
114
|
|
|
public function postRequestAsync( Request $request ) { |
115
|
|
|
$promise = $this->client->postAsync( |
116
|
|
|
$this->apiUrl, |
117
|
|
|
$this->getClientRequestOptions( $request, 'form_params' ) |
118
|
|
|
); |
119
|
|
|
|
120
|
|
|
return $promise->then( function( ResponseInterface $response ) { |
121
|
|
|
return call_user_func( array( $this, 'decodeResponse' ), $response ); |
122
|
|
|
} ); |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
/** |
126
|
|
|
* @since 0.2 |
127
|
|
|
* |
128
|
|
|
* @param Request $request |
129
|
|
|
* |
130
|
|
|
* @return mixed Normally an array |
131
|
|
|
*/ |
132
|
8 |
|
public function getRequest( Request $request ) { |
133
|
8 |
|
$response = $this->client->get( |
134
|
8 |
|
$this->apiUrl, |
135
|
8 |
|
$this->getClientRequestOptions( $request, 'query' ) |
136
|
8 |
|
); |
137
|
|
|
|
138
|
8 |
|
return $this->decodeResponse( $response ); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* @since 0.2 |
143
|
|
|
* |
144
|
|
|
* @param Request $request |
145
|
|
|
* |
146
|
|
|
* @return mixed Normally an array |
147
|
|
|
*/ |
148
|
8 |
|
public function postRequest( Request $request ) { |
149
|
8 |
|
$response = $this->client->post( |
150
|
8 |
|
$this->apiUrl, |
151
|
8 |
|
$this->getClientRequestOptions( $request, 'form_params' ) |
152
|
8 |
|
); |
153
|
|
|
|
154
|
8 |
|
return $this->decodeResponse( $response ); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* @param ResponseInterface $response |
159
|
|
|
* |
160
|
|
|
* @return mixed |
161
|
|
|
* @throws UsageException |
162
|
|
|
*/ |
163
|
16 |
|
private function decodeResponse( ResponseInterface $response ) { |
164
|
16 |
|
$resultArray = json_decode( $response->getBody(), true ); |
165
|
|
|
|
166
|
16 |
|
$this->logWarnings( $resultArray ); |
167
|
16 |
|
$this->throwUsageExceptions( $resultArray ); |
168
|
|
|
|
169
|
14 |
|
return $resultArray; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* @param Request $request |
174
|
|
|
* @param string $paramsKey either 'query' or 'form_params' |
175
|
|
|
* |
176
|
|
|
* @throws RequestException |
177
|
|
|
* |
178
|
|
|
* @return array as needed by ClientInterface::get and ClientInterface::post |
179
|
|
|
*/ |
180
|
16 |
|
private function getClientRequestOptions( Request $request, $paramsKey ) { |
181
|
|
|
return array( |
182
|
16 |
|
$paramsKey => array_merge( $request->getParams(), array( 'format' => 'json' ) ), |
183
|
16 |
|
'headers' => array_merge( $this->getDefaultHeaders(), $request->getHeaders() ), |
184
|
16 |
|
); |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** |
188
|
|
|
* @return array |
189
|
|
|
*/ |
190
|
16 |
|
private function getDefaultHeaders() { |
191
|
|
|
return array( |
192
|
16 |
|
'User-Agent' => $this->getUserAgent(), |
193
|
16 |
|
); |
194
|
|
|
} |
195
|
|
|
|
196
|
16 |
|
private function getUserAgent() { |
197
|
16 |
|
$loggedIn = $this->isLoggedin(); |
198
|
16 |
|
if( $loggedIn ) { |
199
|
|
|
return 'addwiki-mediawiki-client/' . $loggedIn; |
200
|
|
|
} |
201
|
16 |
|
return 'addwiki-mediawiki-client'; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* @param $result |
206
|
|
|
*/ |
207
|
16 |
|
private function logWarnings( $result ) { |
208
|
16 |
|
if( is_array( $result ) && array_key_exists( 'warnings', $result ) ) { |
209
|
|
|
foreach( $result['warnings'] as $module => $warningData ) { |
210
|
|
|
$this->logger->log( LogLevel::WARNING, $module . ': ' . $warningData['*'], array( 'data' => $warningData ) ); |
211
|
|
|
} |
212
|
|
|
} |
213
|
16 |
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* @param array $result |
217
|
|
|
* |
218
|
|
|
* @throws UsageException |
219
|
|
|
*/ |
220
|
16 |
|
private function throwUsageExceptions( $result ) { |
221
|
16 |
|
if( is_array( $result ) && array_key_exists( 'error', $result ) ) { |
222
|
2 |
|
throw new UsageException( |
223
|
2 |
|
$result['error']['code'], |
224
|
2 |
|
$result['error']['info'], |
225
|
|
|
$result |
226
|
2 |
|
); |
227
|
|
|
} |
228
|
14 |
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* @since 0.1 |
232
|
|
|
* |
233
|
|
|
* @return bool|string false or the name of the current user |
234
|
|
|
*/ |
235
|
16 |
|
public function isLoggedin() { |
236
|
16 |
|
return $this->isLoggedIn; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/** |
240
|
|
|
* @since 0.1 |
241
|
|
|
* |
242
|
|
|
* @param ApiUser $apiUser |
243
|
|
|
* |
244
|
|
|
* @throws UsageException |
245
|
|
|
* @return bool success |
246
|
|
|
*/ |
247
|
2 |
|
public function login( ApiUser $apiUser ) { |
248
|
2 |
|
$this->logger->log( LogLevel::DEBUG, 'Logging in' ); |
249
|
|
|
|
250
|
|
|
$credentials = array( |
251
|
2 |
|
'lgname' => $apiUser->getUsername(), |
252
|
2 |
|
'lgpassword' => $apiUser->getPassword(), |
253
|
2 |
|
); |
254
|
|
|
|
255
|
2 |
|
if( !is_null( $apiUser->getDomain() ) ) { |
256
|
|
|
$credentials['lgdomain'] = $apiUser->getDomain(); |
257
|
|
|
} |
258
|
|
|
|
259
|
2 |
|
$result = $this->postRequest( new SimpleRequest( 'login', $credentials ) ); |
260
|
2 |
|
if ( $result['login']['result'] == "NeedToken" ) { |
261
|
2 |
|
$result = $this->postRequest( new SimpleRequest( 'login', array_merge( array( 'lgtoken' => $result['login']['token'] ), $credentials) ) ); |
262
|
2 |
|
} |
263
|
2 |
|
if ( $result['login']['result'] == "Success" ) { |
264
|
1 |
|
$this->isLoggedIn = $apiUser->getUsername(); |
265
|
1 |
|
return true; |
266
|
|
|
} |
267
|
|
|
|
268
|
1 |
|
$this->isLoggedIn = false; |
269
|
1 |
|
$this->throwLoginUsageException( $result ); |
270
|
|
|
return false; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* @param array $result |
275
|
|
|
* |
276
|
|
|
* @throws UsageException |
277
|
|
|
*/ |
278
|
1 |
|
private function throwLoginUsageException( $result ) { |
279
|
1 |
|
$loginResult = $result['login']['result']; |
280
|
|
|
switch( $loginResult ) { |
281
|
1 |
|
case 'Illegal'; |
282
|
|
|
throw new UsageException( |
283
|
|
|
'login-' . $loginResult, |
284
|
|
|
'You provided an illegal username', |
285
|
|
|
$result |
286
|
|
|
); |
287
|
1 |
|
case 'NotExists'; |
288
|
|
|
throw new UsageException( |
289
|
|
|
'login-' . $loginResult, |
290
|
|
|
'The username you provided doesn\'t exist', |
291
|
|
|
$result |
292
|
|
|
); |
293
|
1 |
|
case 'WrongPass'; |
294
|
|
|
throw new UsageException( |
295
|
|
|
'login-' . $loginResult, |
296
|
|
|
'The password you provided is incorrect', |
297
|
|
|
$result |
298
|
|
|
); |
299
|
1 |
|
case 'WrongPluginPass'; |
300
|
|
|
throw new UsageException( |
301
|
|
|
'login-' . $loginResult, |
302
|
|
|
'An authentication plugin rather than MediaWiki itself rejected the password', |
303
|
|
|
$result |
304
|
|
|
); |
305
|
1 |
|
case 'CreateBlocked'; |
306
|
|
|
throw new UsageException( |
307
|
|
|
'login-' . $loginResult, |
308
|
|
|
'The wiki tried to automatically create a new account for you, but your IP address has been blocked from account creation', |
309
|
|
|
$result |
310
|
|
|
); |
311
|
1 |
|
case 'Throttled'; |
312
|
|
|
throw new UsageException( |
313
|
|
|
'login-' . $loginResult, |
314
|
|
|
'You\'ve logged in too many times in a short time.', |
315
|
|
|
$result |
316
|
|
|
); |
317
|
1 |
|
case 'Blocked'; |
318
|
|
|
throw new UsageException( |
319
|
|
|
'login-' . $loginResult, |
320
|
|
|
'User is blocked', |
321
|
|
|
$result |
322
|
|
|
); |
323
|
1 |
|
case 'NeedToken'; |
324
|
|
|
throw new UsageException( |
325
|
|
|
'login-' . $loginResult, |
326
|
|
|
'Either you did not provide the login token or the sessionid cookie.', |
327
|
|
|
$result |
328
|
|
|
); |
329
|
1 |
|
default: |
330
|
1 |
|
throw new UsageException( |
331
|
1 |
|
'login-' . $loginResult, |
332
|
1 |
|
$loginResult, |
333
|
|
|
$result |
334
|
1 |
|
); |
335
|
1 |
|
} |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* @since 0.1 |
340
|
|
|
* @return bool success |
341
|
|
|
*/ |
342
|
2 |
|
public function logout() { |
343
|
2 |
|
$this->logger->log( LogLevel::DEBUG, 'Logging out' ); |
344
|
2 |
|
$result = $this->postRequest( new SimpleRequest( 'logout' ) ); |
345
|
2 |
|
if( $result === array() ) { |
346
|
1 |
|
$this->isLoggedIn = false; |
347
|
1 |
|
$this->clearTokens(); |
348
|
1 |
|
return true; |
349
|
|
|
} |
350
|
1 |
|
return false; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* @since 0.1 |
355
|
|
|
* |
356
|
|
|
* @param string $type |
357
|
|
|
* |
358
|
|
|
* @return string |
359
|
|
|
*/ |
360
|
|
|
public function getToken( $type = 'csrf' ) { |
361
|
|
|
return $this->session->getToken( $type ); |
362
|
|
|
} |
363
|
|
|
|
364
|
|
|
/** |
365
|
|
|
* @since 0.1 |
366
|
|
|
* Clears all tokens stored by the api |
367
|
|
|
*/ |
368
|
1 |
|
public function clearTokens() { |
369
|
1 |
|
$this->session->clearTokens(); |
370
|
1 |
|
} |
371
|
|
|
|
372
|
|
|
/** |
373
|
|
|
* @return string |
374
|
|
|
*/ |
375
|
4 |
|
public function getVersion(){ |
376
|
4 |
|
if( !isset( $this->version ) ) { |
377
|
4 |
|
$result = $this->getRequest( new SimpleRequest( 'query', array( |
378
|
4 |
|
'meta' => 'siteinfo', |
379
|
4 |
|
'continue' => '', |
380
|
4 |
|
) ) ); |
381
|
4 |
|
preg_match( |
382
|
4 |
|
'/\d+(?:\.\d+)+/', |
383
|
4 |
|
$result['query']['general']['generator'], |
384
|
|
|
$versionParts |
385
|
4 |
|
); |
386
|
4 |
|
$this->version = $versionParts[0]; |
387
|
4 |
|
} |
388
|
4 |
|
return $this->version; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
} |
392
|
|
|
|
This check looks for the bodies of
elseif
statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.These
elseif
bodies can be removed. If you have an empty elseif but statements in theelse
branch, consider inverting the condition.