Completed
Push — master ( 61acad...8004a5 )
by adam
03:01
created

MediawikiApi::__construct()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5.0074

Importance

Changes 12
Bugs 1 Features 3
Metric Value
c 12
b 1
f 3
dl 0
loc 19
ccs 14
cts 15
cp 0.9333
rs 8.8571
cc 5
eloc 12
nc 7
nop 3
crap 5.0074
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 ) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

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 the else branch, consider inverting the condition.

Loading history...
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;
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 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