Completed
Push — master ( e32220...fe9971 )
by adam
04:59
created

MediawikiApi::getRequest()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 9
Bugs 0 Features 4
Metric Value
c 9
b 0
f 4
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 9.4286
cc 1
eloc 5
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Mediawiki\Api;
4
5
use GuzzleHttp\Client;
6
use GuzzleHttp\ClientInterface;
7
use GuzzleHttp\Cookie\CookieJar;
8
use GuzzleHttp\Exception\RequestException;
9
use GuzzleHttp\Promise\PromiseInterface;
10
use InvalidArgumentException;
11
use Psr\Http\Message\ResponseInterface;
12
use Psr\Log\LoggerAwareInterface;
13
use Psr\Log\LoggerInterface;
14
use Psr\Log\LogLevel;
15
use Psr\Log\NullLogger;
16
use RuntimeException;
17
18
/**
19
 * @author Addshore
20
 */
21
class MediawikiApi implements LoggerAwareInterface {
22
23
	/**
24
	 * @var ClientInterface
25
	 */
26
	private $client;
27
28
	/**
29
	 * @var bool|string
30
	 */
31
	private $isLoggedIn;
32
33
	/**
34
	 * @var MediawikiSession
35
	 */
36
	private $session;
37
38
	/**
39
	 * @var string
40
	 */
41
	private $version;
42
43
	/**
44
	 * @var LoggerInterface
45
	 */
46
	private $logger;
47
48
	/**
49
	 * @param string $apiUrl The API Url
50
	 * @param ClientInterface|null $client Guzzle Client
51 23
	 * @param MediawikiSession|null $session Inject a custom session here
52 23
	 */
53 4
	public function __construct( $apiUrl, ClientInterface $client = null, MediawikiSession $session = null ) {
54
		if( !is_string( $apiUrl ) ) {
55 19
			throw new InvalidArgumentException( '$apiUrl must be a string' );
56 3
		}
57 3
		if( $client === null ) {
58 19
			$client = new Client();
59 19
		}
60 19
		if( $session === null ) {
61
			$session = new MediawikiSession( $this );
62 19
		}
63 19
64 19
		$this->apiUrl = $apiUrl;
0 ignored issues
show
Bug introduced by
The property apiUrl does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
65
		$this->client = $client;
66 19
		$this->session = $session;
67 19
68
		$this->logger = new NullLogger();
69
	}
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 8
	 * @param Request $request
89 8
	 *
90 8
	 * @return PromiseInterface
91
	 *         Normally promising an array, though can be mixed (json_decode result)
92 8
	 *         Can throw UsageExceptions or RejectionExceptions
93 8
	 */
94
	public function getRequestAsync( Request $request ) {
95 7
		$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 8
	}
104 8
105 8
	/**
106
	 * @since 2.0
107 8
	 *
108 8
	 * @param Request $request
109
	 *
110 7
	 * @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 8
		);
119 8
120 8
		return $promise->then( function( ResponseInterface $response ) {
121 8
			return call_user_func( array( $this, 'decodeResponse' ), $response );
122 8
		} );
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
		return $this->decodeResponse( $response );
139
	}
140
141
	/**
142
	 * @since 0.2
143
	 *
144
	 * @param Request $request
145
	 *
146 16
	 * @return mixed Normally an array
147
	 */
148 16
	public function postRequest( Request $request ) {
149 16
		$response = $this->client->post(
150 16
			$this->apiUrl,
151
			$this->getClientRequestOptions( $request, 'form_params' )
152
		);
153
154
		return $this->decodeResponse( $response );
155
	}
156 16
157
	/**
158 16
	 * @param ResponseInterface $response
159 16
	 *
160
	 * @return mixed
161
	 * @throws UsageException
162 16
	 */
163 16
	private function decodeResponse( ResponseInterface $response ) {
164 16
		$resultArray = json_decode( $response->getBody(), true );
165
166
		$this->logWarnings( $resultArray );
167 16
		$this->throwUsageExceptions( $resultArray );
168
169
		return $resultArray;
170
	}
171
172
	/**
173 16
	 * @param Request $request
174 16
	 * @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 16
	 */
180
	private function getClientRequestOptions( Request $request, $paramsKey ) {
181
		return array(
182
			$paramsKey => array_merge( $request->getParams(), array( 'format' => 'json' ) ),
183
			'headers' => array_merge( $this->getDefaultHeaders(), $request->getHeaders() ),
184
		);
185
	}
186 16
187 16
	/**
188 2
	 * @return array
189 2
	 */
190 2
	private function getDefaultHeaders() {
191
		return array(
192 2
			'User-Agent' => $this->getUserAgent(),
193
		);
194 14
	}
195
196
	private function getUserAgent() {
197
		$loggedIn = $this->isLoggedin();
198
		if( $loggedIn ) {
199
			return 'addwiki-mediawiki-client/' . $loggedIn;
200
		}
201 16
		return 'addwiki-mediawiki-client';
202 16
	}
203
204
	/**
205
	 * @param $result
206
	 */
207
	private function logWarnings( $result ) {
208
		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 2
	}
214 2
215
	/**
216
	 * @param array $result
217 2
	 *
218 2
	 * @throws UsageException
219 2
	 */
220
	private function throwUsageExceptions( $result ) {
221 2
		if( is_array( $result ) && array_key_exists( 'error', $result ) ) {
222
			throw new UsageException(
223
				$result['error']['code'],
224
				$result['error']['info'],
225 2
				$result
226 2
			);
227 2
		}
228 2
	}
229 2
230 1
	/**
231 1
	 * @since 0.1
232
	 *
233
	 * @return bool|string false or the name of the current user
234 1
	 */
235 1
	public function isLoggedin() {
236
		return $this->isLoggedIn;
237
	}
238
239
	/**
240
	 * @since 0.1
241
	 *
242
	 * @param ApiUser $apiUser
243
	 *
244 1
	 * @throws UsageException
245 1
	 * @return bool success
246
	 */
247 1
	public function login( ApiUser $apiUser ) {
248
		$this->logger->log( LogLevel::DEBUG, 'Logging in' );
249
250
		$credentials = array(
251
			'lgname' => $apiUser->getUsername(),
252
			'lgpassword' => $apiUser->getPassword(),
253 1
		);
254
255
		if( !is_null( $apiUser->getDomain() ) ) {
256
			$credentials['lgdomain'] = $apiUser->getDomain();
257
		}
258
259 1
		$result = $this->postRequest( new SimpleRequest( 'login', $credentials ) );
260
		if ( $result['login']['result'] == "NeedToken" ) {
261
			$result = $this->postRequest( new SimpleRequest( 'login', array_merge( array( 'lgtoken' => $result['login']['token'] ), $credentials) ) );
262
		}
263
		if ( $result['login']['result'] == "Success" ) {
264
			$this->isLoggedIn = $apiUser->getUsername();
265 1
			return true;
266
		}
267
268
		$this->isLoggedIn = false;
269
		$this->throwLoginUsageException( $result );
270
		return false;
271 1
	}
272
273
	/**
274
	 * @param array $result
275
	 *
276
	 * @throws UsageException
277 1
	 */
278
	private function throwLoginUsageException( $result ) {
279
		$loginResult = $result['login']['result'];
280
		switch( $loginResult ) {
281
			case 'Illegal';
282
				throw new UsageException(
283 1
					'login-' . $loginResult,
284
					'You provided an illegal username',
285
					$result
286
				);
287
			case 'NotExists';
288
				throw new UsageException(
289 1
					'login-' . $loginResult,
290
					'The username you provided doesn\'t exist',
291
					$result
292
				);
293
			case 'WrongPass';
294
				throw new UsageException(
295 1
					'login-' . $loginResult,
296 1
					'The password you provided is incorrect',
297 1
					$result
298 1
				);
299
			case 'WrongPluginPass';
300 1
				throw new UsageException(
301 1
					'login-' . $loginResult,
302
					'An authentication plugin rather than MediaWiki itself rejected the password',
303
					$result
304
				);
305
			case 'CreateBlocked';
306
				throw new UsageException(
307
					'login-' . $loginResult,
308 2
					'The wiki tried to automatically create a new account for you, but your IP address has been blocked from account creation',
309 2
					$result
310 2
				);
311 2
			case 'Throttled';
312 1
				throw new UsageException(
313 1
					'login-' . $loginResult,
314 1
					'You\'ve logged in too many times in a short time.',
315
					$result
316 1
				);
317
			case 'Blocked';
318
				throw new UsageException(
319
					'login-' . $loginResult,
320
					'User is blocked',
321
					$result
322
				);
323
			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
			default:
330
				throw new UsageException(
331
					'login-' . $loginResult,
332
					$loginResult,
333
					$result
334 1
				);
335 1
		}
336 1
	}
337
338
	/**
339
	 * @since 0.1
340
	 * @return bool success
341 4
	 */
342 4
	public function logout() {
343 4
		$this->logger->log( LogLevel::DEBUG, 'Logging out' );
344 4
		$result = $this->postRequest( new SimpleRequest( 'logout' ) );
345 4
		if( $result === array() ) {
346 4
			$this->isLoggedIn = false;
347 4
			$this->clearTokens();
348 4
			return true;
349 4
		}
350
		return false;
351 4
	}
352 4
353 4
	/**
354 4
	 * @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
	public function clearTokens() {
369
		$this->session->clearTokens();
370
	}
371
372
	/**
373
	 * @return string
374
	 */
375
	public function getVersion(){
376
		if( !isset( $this->version ) ) {
377
			$result = $this->getRequest( new SimpleRequest( 'query', array(
378
				'meta' => 'siteinfo',
379
				'continue' => '',
380
			) ) );
381
			preg_match(
382
				'/\d+(?:\.\d+)+/',
383
				$result['query']['general']['generator'],
384
				$versionParts
385
			);
386
			$this->version = $versionParts[0];
387
		}
388
		return $this->version;
389
	}
390
391
}
392