Wiki::parse()   F
last analyzed

Complexity

Conditions 31
Paths > 20000

Size

Total Lines 57
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 992

Importance

Changes 0
Metric Value
cc 31
eloc 41
nc 1327104
nop 19
dl 0
loc 57
rs 3.4487
c 0
b 0
f 0
ccs 0
cts 44
cp 0
crap 992

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * This file is part of Peachy MediaWiki Bot API
5
 *
6
 * Peachy is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
20
/**
21
 * @file
22
 * Wiki object
23
 */
24
25
/**
26
 * Wiki class
27
 * Stores and runs methods that don't fit in User, Page, or Image, etc.
28
 */
29
class Wiki {
30
31
	/**
32
	 * URL to the API for the wiki.
33
	 *
34
	 * @var string
35
	 * @access protected
36
	 */
37
	protected $base_url;
38
39
	/**
40
	 * SSH Object
41
	 *
42
	 * @var Object
43
	 * @access protected
44
	 */
45
	protected $SSH = false;
46
47
	/**
48
	 * Username for the user editing the wiki.
49
	 *
50
	 * @var string
51
	 * @access protected
52
	 */
53
	protected $username;
54
55
	/**
56
	 * Edit of editing for the wiki in EPM.
57
	 *
58
	 * @var int
59
	 * @access protected
60
	 */
61
	protected $edit_rate;
62
63
	/**
64
	 * Maximum db lag that the bot will accept. False to disable.
65
	 *
66
	 * (default value: false)
67
	 *
68
	 * @var bool|int
69
	 * @access protected
70
	 */
71
	protected $maxlag = false;
72
73
	/**
74
	 * Limit of results that can be returned by the API at one time.
75
	 *
76
	 * (default value: 49)
77
	 *
78
	 * @var int
79
	 * @access protected
80
	 */
81
	protected $apiQueryLimit = 49;
82
83
	/**
84
	 * Does the user have to have the bot flag to edit.
85
	 *
86
	 * (default value: false)
87
	 *
88
	 * @var bool
89
	 * @access protected
90
	 */
91
	protected $requiresFlag = false;
92
93
	/**
94
	 * Can the user continue editing logged out.
95
	 *
96
	 * (default value: false)
97
	 *
98
	 * @var bool
99
	 * @access protected
100
	 */
101
	protected $allowLoggedOutEditing = false;
102
103
	/**
104
	 * Does the user have a bot flag.
105
	 *
106
	 * (default value: false)
107
	 *
108
	 * @var bool
109
	 * @access protected
110
	 */
111
	protected $isFlagged = false;
112
113
	/**
114
	 * Array of extenstions on the Wiki in the form of name => version.
115
	 *
116
	 * @var array
117
	 * @access protected
118
	 */
119
	protected $extensions;
120
121
	/**
122
	 * Array of tokens for editing.
123
	 *
124
	 * (default value: array())
125
	 *
126
	 * @var array
127
	 * @access protected
128
	 */
129
	protected $tokens = array();
130
131
	/**
132
	 * Array of rights assigned to the user.
133
	 *
134
	 * (default value: array())
135
	 *
136
	 * @var array
137
	 * @access protected
138
	 */
139
	protected $userRights = array();
140
141
	/**
142
	 * Array of namespaces by ID.
143
	 *
144
	 * (default value: null)
145
	 *
146
	 * @var array
147
	 * @access protected
148
	 */
149
	protected $namespaces = null;
150
151
	/**
152
	 * array of namespaces that have subpages allowed, by namespace id.
153
	 *
154
	 * (default value: null)
155
	 *
156
	 * @var array
157
	 * @access protected
158
	 */
159
	protected $allowSubpages = null;
160
161
	/**
162
	 * Should the wiki follow nobots rules?
163
	 *
164
	 * (default value: true)
165
	 *
166
	 * @var bool
167
	 * @access protected
168
	 */
169
	protected $nobots = true;
170
    
171
    /**
172
     * Nobots task name if nobots is enables.  Perfect for multiple doing the same task.
173
     *
174
     * (default value: null)
175
     *
176
     * @var string
177
     * @access protected
178
     */
179
    protected $nobotsTaskname = null;
180
181
	/**
182
	 * Text to search for in the optout= field of the {{nobots}} template
183
	 *
184
	 * (default value: null)
185
	 *
186
	 * @var null
187
	 * @access protected
188
	 */
189
	protected $optout = null;
190
191
	/**
192
	 * Whether or not to not edit if the user has new messages
193
	 *
194
	 * (default value: false)
195
	 *
196
	 * @var bool
197
	 * @access protected
198
	 */
199
	protected $stoponnewmessages = false;
200
201
	/**
202
	 * Page title to use for enable page
203
	 *
204
	 * @var string
205
	 * @access protected
206
	 */
207
	protected $runpage;
208
209
	/**
210
	 * Configuration (sans password)
211
	 *
212
	 * @var array
213
	 * @access protected
214
	 */
215
	protected $configuration;
216
217
	/**
218
	 * HTTP Class
219
	 *
220
	 * @var HTTP
221
	 * @access protected
222
	 */
223
	protected $http;
224
225
	/**
226
	 * Whether or not to log in. True restricts logging in, false lets it log in. Setting to true restricts the available functions.
227
	 *
228
	 * @var bool
229
	 * @access protected
230
	 */
231
	protected $nologin;
232
233
	/**
234
	 * Server that handled the last API query
235
	 *
236
	 * @var string
237
	 * @access protected
238
	 */
239
	protected $servedby;
240
241
	/**
242
	 * Generator values for the generator parameter
243
	 *
244
	 * @var array
245
	 * @access protected
246
	 */
247
	protected $generatorvalues;
248
249
	/**
250
	 * Version of MediaWiki server is running.
251
	 * The only reason this is public is so the Peachy class can set it. It should not be changed again.
252
	 *
253
	 * @var string
254
	 * @access public
255
	 */
256
	public $mwversion;
257
258
	/**
259
	 * Caches configuration information for logging back in with identical settings.
260
	 *
261
	 * @var array
262
	 * @access protected
263
	 */
264
	protected $cached_config;
265
	
266
	/**
267
	 * If bot running with OAuth or not.
268
	 *
269
	 * @var array
270
	 * @access protected
271
	 */
272
	protected $oauthEnabled = false;
273
	
274
	/**
275
	 * OAuth Consumer Key.
276
	 *
277
	 * @var array
278
	 * @access protected
279
	 */
280
	protected $consumerKey = "";
281
	
282
	/**
283
	 * Key secret to establish owner verification.
284
	 *
285
	 * @var array
286
	 * @access protected
287
	 */
288
	protected $consumerSecret = "";
289
	
290
	/**
291
	 * OAuth Access token to request access.
292
	 *
293
	 * @var array
294
	 * @access protected
295
	 */
296
	protected $accessToken = "";
297
	
298
	/**
299
	 * Token Secret for Authorization.
300
	 *
301
	 * @var array
302
	 * @access protected
303
	 */
304
	protected $accessTokenSecret = "";
305
306
	/**
307
     * Construct function for the wiki. Handles login and related functions.
308
	 *
309
	 * @access public
310
	 * @see Peachy::newWiki()
311
	 * @param array $configuration Array with configuration data. At least needs username, password, and base_url.
312
	 * @param array $extensions Array of names of extensions installed on the wiki and their versions (default: array())
313
	 * @param int $recursed Is the function recursing itself? Used internally, don't use (default: 0)
314
	 * @param mixed $token Token if the wiki needs a token. Used internally, don't use (default: null)
315
	 * @throws LoginError
316
	 */
317
	public function __construct( $configuration, $extensions = array(), $recursed = 0, $token = null ) {
318
		global $pgProxy, $pgVerbose, $pgUseSSH, $pgHost, $pgPort, $pgUsername, $pgPrikey, $pgPassphrase, $pgProtocol, $pgTimeout;
319
320
		$this->cached_config['config'] = $configuration;
321
		$this->cached_config['extensions'] = $extensions;
322
323
		if( !array_key_exists( 'encodedparams', $configuration ) ) {
324
			$configuration['encodedparams'] = rawurlencode( serialize( $configuration ) );
325
		}
326
327
		$this->base_url = $configuration['baseurl'];
328
		$this->username = $configuration['username'];
329
		$this->extensions = $extensions;
330
		$this->generatorvalues = array(
331
			'allcategories', 'allimages', 'alllinks', 'allpages', 'alltransclusions', 'backlinks', 'categories',
332
			'categorymembers', 'duplicatefiles', 'embeddedin', 'exturlusage', 'geosearch', 'images', 'imageusage',
333
			'iwbacklinks', 'langbacklinks', 'links', 'oldreviewedpages', 'protectedtitles', 'querypage', 'random',
334
			'recentchanges', 'search', 'templates', 'watchlist', 'watchlistraw'
335
		);
336
337
		if( isset( $configuration['editwhileloggedout'] ) ) $this->allowLoggedOutEditing = true;
338
		if( isset( $configuration['requirebotflag'] ) ) $this->requiresFlag = true;
339
		if( isset( $configuration['editsperminute'] ) && $configuration['editsperminute'] != 0 ) {
340
			$this->edit_rate = $configuration['editsperminute'];
341
		}
342
343
		if( isset( $configuration['proxyaddr'] ) ) {
344
			$pgProxy['addr'] = $configuration['proxyaddr'];
345
346
			if( isset( $configuration['proxytype'] ) ) {
347
				$pgProxy['type'] = $configuration['proxytype'];
348
			}
349
350
			if( isset( $configuration['proxyport'] ) ) {
351
				$pgProxy['port'] = $configuration['proxyport'];
352
			}
353
354
			if( isset( $configuration['proxyuser'] ) && isset( $configuration['proxypass'] ) ) {
355
				$pgProxy['userpass'] = $configuration['proxyuser'] . ':' . $configuration['proxypass'];
356
			}
357
		}
358
359
		$http_echo = ( isset( $configuration['httpecho'] ) && $configuration['httpecho'] === "true" );
360
		if( is_null( $this->http ) ) $this->http = HTTP::getDefaultInstance( $http_echo );
361
362
		if( $pgUseSSH ) {
363
			if( !$this->SSH ) $this->SSH = new SSH( $pgHost, $pgPort, $pgUsername, $pgPassphrase, $pgPrikey, $pgProtocol, $pgTimeout, $this->http );
0 ignored issues
show
Bug introduced by
It seems like $this->http can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
364
			if( !$this->SSH->connected ) $this->SSH = null;
365
		} else $this->SSH = null;
366
367
		if( isset( $configuration['runpage'] ) ) {
368
			$this->runpage = $configuration['runpage'];
369
		}
370
371
		if( isset( $configuration['useragent'] ) ) {
372
			$this->http->setUserAgent( $configuration['useragent'] );
373
		}
374
375
		if( isset( $configuration['optout'] ) ) {
376
			$this->optout = $configuration['optout'];
377
		}
378
379
		if( isset( $configuration['stoponnewmessages'] ) ) {
380
			$this->stoponnewmessages = true;
381
		}
382
383
		if( isset( $configuration['verbose'] ) ) {
384
			$pgVerbose = array();
385
386
			$tmp = explode( '|', $configuration['verbose'] );
387
388
			foreach( $tmp as $setting ){
389
				if( $setting == "ALL" ) {
390
					$pgVerbose = array(
391
						PECHO_NORMAL,
392
						PECHO_NOTICE,
393
						PECHO_WARN,
394
						PECHO_ERROR,
395
						PECHO_FATAL
396
					);
397
					break;
398
				} else {
399
					switch( $setting ){
400
						case 'NORMAL':
401
							$pgVerbose[] = PECHO_NORMAL;
402
							break;
403
						case 'NOTICE':
404
							$pgVerbose[] = PECHO_NOTICE;
405
							break;
406
						case 'WARN':
407
							$pgVerbose[] = PECHO_WARN;
408
							break;
409
						case 'ERROR':
410
							$pgVerbose[] = PECHO_ERROR;
411
							break;
412
						case 'FATAL':
413
							$pgVerbose[] = PECHO_FATAL;
414
							break;
415
						case 'VERBOSE':
416
							$pgVerbose[] = PECHO_VERBOSE;
417
							break;
418
					}
419
				}
420
			}
421
422
			unset( $tmp );
423
		}
424
425
		if( ( isset( $configuration['nobots'] ) && $configuration['nobots'] == 'false' ) || strpos( $configuration['baseurl'], '//en.wikipedia.org/w/api.php' ) === false ) {
426
			$this->nobots = false;
427
		}
428
		if( isset( $configuration['method'] ) && $configuration['method'] == 'legacy' ) {
429
			$lgarray = array(
430
				'lgname'     => $this->username,
431
				'action'     => 'login',
432
			);
433
434
			// Password may not be set (for example, where nologin is being used)
435
			if( isset( $configuration['password'] ) ) {
436
				$lgarray['lgpassword'] = $configuration['password'];
437
			}
438
439
			if( !is_null( $token ) ) {
440
				$lgarray['lgtoken'] = $token;
441
			}
442
		} else {
443
			if( !isset( $extensions['OAuth'] ) ) throw new DependencyError( "OAuth", "https://www.mediawiki.org/wiki/Extension:OAuth or try setting \'method = \"legacy\"\' in the cfg file." );
0 ignored issues
show
Documentation introduced by
'https://www.mediawiki.o..."\\\' in the cfg file.' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
444
			if( !isset( $configuration['consumerkey'] ) ) throw new LoginError( array( "Missing consumer key", "set consumerkey in cfg file" ) );
445
			if( !isset( $configuration['consumersecret'] ) ) throw new LoginError( array( "Missing consumer secret", "set consumersecret in cfg file" ) );
446
			if( !isset( $configuration['accesstoken'] ) ) throw new LoginError( array( "Missing access token", "set accesstoken in cfg file" ) );
447
			if( !isset( $configuration['accesssecret'] ) ) throw new LoginError( array( "Missing access token secret", "set accesssecret in cfg file" ) );
448
			if( !isset( $configuration['oauthurl'] ) ) throw new LoginError( array( "Missing OAuth URL", "set oauthurl in cfg file" ) );
449
			$this->oauthEnabled = true;
0 ignored issues
show
Documentation Bug introduced by
It seems like true of type boolean is incompatible with the declared type array of property $oauthEnabled.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
450
			$this->consumerKey = $configuration['consumerkey'];
451
			$this->consumerSecret = $configuration['consumersecret'];
452
			$this->accessToken = $configuration['accesstoken'];
453
			$this->accessTokenSecret = $configuration['accesssecret'];
454
			$this->oauth_url = $configuration['oauthurl'];
0 ignored issues
show
Bug introduced by
The property oauth_url 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...
455
		}
456
		
457
		if( isset( $configuration['maxlag'] ) && $configuration['maxlag'] != "0" ) {
458
			$this->maxlag = $configuration['maxlag'];
459
			$lgarray['maxlag'] = $this->maxlag;
0 ignored issues
show
Bug introduced by
The variable $lgarray does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
460
		}	
461
		
462
		// FIXME:   Why is there a return in a constructor? Should an error be thrown?
463
		if( isset( $configuration['nologin'] ) ) {
464
			$this->nologin = true;
465
			return;
466
		}
467
		
468
		$use_cookie_login = false;
469
		if( isset( $configuration['cookiejar'] ) ) {
470
			$this->http->setCookieJar( $configuration['cookiejar'] );
471
		} else {
472
473
			$this->http->setCookieJar( sys_get_temp_dir() . '/PeachyCookieSite' . sha1( $configuration['encodedparams'] ) );
474
475
			if( $this->is_logged_in() ) $use_cookie_login = true;
476
		}	
477
478
		if( $use_cookie_login ) {
479
			pecho( "Logging in to {$this->base_url} as {$this->username}, using a saved login cookie\n\n", PECHO_NORMAL );
480
481
			$this->runSuccess( $configuration );
482
		} elseif( !$this->nologin ) {
483
			Hooks::runHook( 'PreLogin', array( &$lgarray ) );
484
485
			if( !$recursed ) {
486
				pecho( "Logging in to {$this->base_url}...\n\n", PECHO_NOTICE );
487
			}
488
489
			if( isset( $configuration['method'] ) && $configuration['method'] == 'legacy' ) $loginRes = $this->apiQuery( $lgarray, true, true, false, false );
490
			else {
491
				$loginRes = $this->apiQuery( array(), false, true, false, false, $this->oauth_url."/identify" );
0 ignored issues
show
Documentation introduced by
$this->oauth_url . '/identify' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
492
				if ( !$loginRes ) {
493
					throw new LoginError( ( array( 'BadData', 'The server returned unparsable data' ) ) );
494
				}
495
				$err = json_decode( $loginRes );
496
				if ( is_object( $err ) && isset( $err->error ) && $err->error === 'mwoauthdatastore-access-token-not-found' ) {
497
					// We're not authorized!
498
					throw new LoginError( ( array( 'AuthFailure', 'Missing authorization or authorization failed' ) ) );
499
				}
500
501
				// There are three fields in the response
502
				$fields = explode( '.', $loginRes );
503
				if ( count( $fields ) !== 3 ) {
504
					throw new LoginError( ( array( 'BadResponse', 'Invalid identify response: ' . htmlspecialchars( $loginRes ) ) ) );
505
				}
506
507
				// Validate the header. MWOAuth always returns alg "HS256".
508
				$header = base64_decode( strtr( $fields[0], '-_', '+/' ), true );
509
				if ( $header !== false ) {
510
					$header = json_decode( $header );
511
				}
512
				if ( !is_object( $header ) || $header->typ !== 'JWT' || $header->alg !== 'HS256' ) {
513
					throw new LoginError( ( array( 'BadHeader', 'Invalid header in identify response: ' . htmlspecialchars( $loginRes ) ) ) );
514
				}
515
516
				// Verify the signature
517
				$sig = base64_decode( strtr( $fields[2], '-_', '+/' ), true );
518
				$check = hash_hmac( 'sha256', $fields[0] . '.' . $fields[1], $this->consumerSecret, true );
519
				if ( $sig !== $check ) {
520
					throw new LoginError( ( array( 'BadSignature', 'JWT signature validation failed: ' . htmlspecialchars( $loginRes ) ) ) );
521
				}
522
523
				// Decode the payload
524
				$payload = base64_decode( strtr( $fields[1], '-_', '+/' ), true );
525
				if ( $payload !== false ) {
526
					$payload = json_decode( $payload );
527
				}
528
				if ( !is_object( $payload ) ) {
529
					throw new LoginError( ( array( 'BadPayload', 'Invalid payload in identify response: ' . htmlspecialchars( $loginRes ) ) ) );
530
				}
531
				
532
				pecho( "Successfully logged in to {$this->base_url} as {$payload->username}\n\n", PECHO_NORMAL );
533
534
				$this->runSuccess( $configuration );
535
			}
536
537
			Hooks::runHook( 'PostLogin', array( &$loginRes ) );
538
		}
539
540
		if( !$this->oauthEnabled && isset( $loginRes['login']['result'] ) ) {
541
			switch( $loginRes['login']['result'] ){
542
				case 'NoName':
543
					throw new LoginError( array( 'NoName', 'Username not specified' ) );
544
				case 'Illegal':
545
					throw new LoginError( array( 'Illegal', 'Username with illegal characters specified' ) );
546
				case 'NotExists':
547
					throw new LoginError( array( 'NotExists', 'Username specified does not exist' ) );
548
				case 'EmptyPass':
549
					throw new LoginError( array( 'EmptyPass', 'Password not specified' ) );
550
				case 'WrongPass':
551
					throw new LoginError( array( 'WrongPass', 'Incorrect password specified' ) );
552
				case 'WrongPluginPass':
553
					throw new LoginError( array( 'WrongPluginPass', 'Incorrect password specified' ) );
554
				case 'CreateBlocked':
555
					throw new LoginError( array( 'CreateBlocked', 'IP address has been blocked' ) );
556
				case 'Throttled':
557
					if( $recursed > 2 ) {
558
						throw new LoginError( array(
559
							'Throttled', 'Login attempts have been throttled'
560
						) );
561
					}
562
563
					$wait = $loginRes['login']['wait'];
564
					pecho( "Login throttled, waiting $wait seconds.\n\n", PECHO_NOTICE );
565
					sleep( $wait );
566
567
					$recres = $this->__construct( $configuration, $this->extensions, $recursed + 1 );
568
					return $recres;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
569
				case 'Blocked':
570
					throw new LoginError( array( 'Blocked', 'User specified has been blocked' ) );
571
				case 'NeedToken':
572
					if( $recursed > 2 ) throw new LoginError( array( 'NeedToken', 'Token was not specified' ) );
573
574
					$token = $loginRes['login']['token'];
575
576
					$recres = $this->__construct( $configuration, $this->extensions, $recursed + 1, $token );
577
					return $recres;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
578
				case 'Success':
579
					pecho( "Successfully logged in to {$this->base_url} as {$this->username}\n\n", PECHO_NORMAL );
580
581
					$this->runSuccess( $configuration );
582
			}
583
		}
584
	}
585
586
	public function is_logged_in() {
587
		$cookieInfo = $this->apiQuery( array( 'action' => 'query', 'meta' => 'userinfo' ) );
588
		if( $cookieInfo['query']['userinfo']['id'] != 0 ) return true;
589
		return false;
590
	}
591
592
593
	/**
594
	 * runSuccess function.
595
	 *
596
	 * @access protected
597
	 * @param mixed &$configuration
598
	 * @return void
599
	 */
600
	protected function runSuccess( &$configuration ) {
601
		$userInfoRes = $this->apiQuery(
602
			array(
603
				'action' => 'query',
604
				'meta'   => 'userinfo',
605
				'uiprop' => 'blockinfo|rights|groups'
606
			)
607
		);
608
609
		if( in_array( 'apihighlimits', $userInfoRes['query']['userinfo']['rights'] ) ) {
610
			$this->apiQueryLimit = 4999;
611
		} else {
612
			$this->apiQueryLimit = 499;
613
		}
614
615
		$this->userRights = $userInfoRes['query']['userinfo']['rights'];
616
617
		if( in_array( 'bot', $userInfoRes['query']['userinfo']['groups'] ) ) {
618
			$this->isFlagged = true;
619
		}
620
621
		$this->get_tokens();
622
623
		$this->configuration = $configuration;
624
		unset( $this->configuration['password'] );
625
	}
626
627
	/**
628
	 * Logs the user out of the wiki.
629
	 *
630
	 * @access public
631
	 * @return void
632
	 */
633
	public function logout() {
634
		pecho( "Logging out of {$this->base_url}...\n\n", PECHO_NOTICE );
635
636
		$this->apiQuery( array( 'action' => 'logout' ), true );
637
638
	}
639
640
	/**
641
	 * Sets a specific runpage for a script.
642
	 *
643
	 * @param string $page Page to set as the runpage. Default null.
644
	 * @access public
645
	 * @return void
646
	 */
647
	public function set_runpage( $page = null ) {
648
		$this->runpage = $page;
649
	}
650
651
	/**
652
	 * Sets a specific taskname to comply with the nobots template.
653
	 *
654
	 * @param string $taskname Name of bot task. Default null.
655
	 *
656
	 * @access public
657
	 * @return void
658
	 */
659
	public function set_taskname( $taskname = null ) {
660
		$this->nobotsTaskname = $taskname;
661
	}
662
663
	private function generateSignature( $method, $url, $params = array() ) {
664
665
		$parts = parse_url( $url );
666
667
		// We need to normalize the endpoint URL
668
		$scheme = isset( $parts['scheme'] ) ? $parts['scheme'] : 'http';
669
		$host = isset( $parts['host'] ) ? $parts['host'] : '';
670
		$port = isset( $parts['port'] ) ? $parts['port'] : ( $scheme == 'https' ? '443' : '80' );
671
		$path = isset( $parts['path'] ) ? $parts['path'] : '';
672
		if ( ( $scheme == 'https' && $port != '443' ) || ( $scheme == 'http' && $port != '80' ) ) {
673
			// Only include the port if it's not the default
674
			$host = "$host:$port";
675
		}
676
677
		// Also the parameters
678
		$pairs = array();
679
		parse_str( isset( $parts['query'] ) ? $parts['query'] : '', $query );
680
		$query += $params;
681
		unset( $query['oauth_signature'] );
682
		if ( $query ) {
683
			$query = array_combine(
684
			// rawurlencode follows RFC 3986 since PHP 5.3
685
				array_map( 'rawurlencode', array_keys( $query ) ),
686
				array_map( 'rawurlencode', array_values( $query ) )
687
			);
688
			ksort( $query, SORT_STRING );
689
			foreach ( $query as $k => $v ) {
690
				$pairs[] = "$k=$v";
691
			}
692
		}
693
694
		$toSign = rawurlencode( strtoupper( $method ) ) .
695
			'&' .
696
			rawurlencode( "$scheme://$host$path" ) .
697
			'&' .
698
			rawurlencode( join( '&', $pairs ) );
699
		$key = rawurlencode( $this->consumerSecret ) .
700
			'&' .
701
			rawurlencode( $this->accessTokenSecret );
702
703
		return base64_encode( hash_hmac( 'sha1', $toSign, $key, true ) );
704
	}
705
706
	/**
707
	 * Queries the API.
708
	 *
709
	 * @access public
710
	 * @param array $arrayParams Parameters given to query with (default: array())
711
	 * @param bool $post Should it be a POST request? (default: false)
712
	 * @param bool $errorcheck
713
	 * @param bool $recursed Is this a recursed request (default: false)
714
	 * @param bool $assertcheck Use MediaWiki's assert feature to prevent unwanted edits (default: true)
715
	 * @throws LoggedOut
716
	 * @throws AssertFailure (see $assertcheck)
717
	 * @throws MWAPIError (API unavailable)
718
	 * @return array|bool Returns an array with the API result
719
	 */
720
	public function apiQuery( $arrayParams = array(), $post = false, $errorcheck = true, $recursed = false, $assertcheck = true, $talktoOauth = false ) {
721
722
		global $pgIP, $pgMaxAttempts, $pgThrowExceptions, $pgDisplayGetOutData, $pgLogSuccessfulCommunicationData, $pgLogFailedCommunicationData, $pgLogGetCommunicationData, $pgLogPostCommunicationData, $pgLogCommunicationData;
723
		$requestid = mt_rand();
724
		$header = "";
725
		if( $talktoOauth === false ) {
726
			$arrayParams['format'] = 'php';
727
			$arrayParams['servedby'] = '';
728
			$arrayParams['requestid'] = $requestid;
729
			$assert = false;	
730
		}
731
		$attempts = $pgMaxAttempts;
732
733
		if( !file_exists( $pgIP . 'Includes/Communication_Logs' ) ) mkdir( $pgIP . 'Includes/Communication_Logs', 02775 );
734
		if( $post && $this->requiresFlag && $assertcheck ) {
735
			$arrayParams['assert'] = 'bot';
736
			$assert = true;
737
			Hooks::runHook( 'QueryAssert', array( &$arrayParams['assert'], &$assert ) );
738
		} elseif( $post && !$this->allowLoggedOutEditing && $assertcheck ) {
739
			$arrayParams['assert'] = 'user';
740
			$assert = true;
741
			Hooks::runHook( 'QueryAssert', array( &$arrayParams['assert'], &$assert ) );
742
		} elseif( isset( $arrayParams['assert'] ) ) unset( $arrayParams['assert'] );
743
744
		pecho( "Running API query with params " . implode( ";", $arrayParams ) . "...\n\n", PECHO_VERBOSE ); 
745
746
		if( $post ) {
747
			$is_loggedin = $this->get_nologin();
748
			Hooks::runHook( 'APIQueryCheckLogin', array( &$is_loggedin ) );
749
			if( $is_loggedin && $errorcheck ) throw new LoggedOut();
750
751
			Hooks::runHook( 'PreAPIPostQuery', array( &$arrayParams ) );
752
			for( $i = 0; $i < $attempts; $i++ ){
753
				if( $this->oauthEnabled ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->oauthEnabled of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
754
					$headerArr = array(
755
						// OAuth information
756
						'oauth_consumer_key' => $this->consumerKey,
757
						'oauth_token' => $this->accessToken,
758
						'oauth_version' => '1.0',
759
						'oauth_nonce' => md5( microtime() . mt_rand() ),
760
						'oauth_timestamp' => time(),
761
762
						// We're using secret key signatures here.
763
						'oauth_signature_method' => 'HMAC-SHA1',
764
					);
765
					$signature = $this->generateSignature(
766
						'POST',
767
						( $talktoOauth ? $talktoOauth : $this->base_url ),
768
						$headerArr
769
					);
770
					$headerArr['oauth_signature'] = $signature;
771
772
					$header = array();
773
					foreach ( $headerArr as $k => $v ) {
774
						$header[] = rawurlencode( $k ) . '="' . rawurlencode( $v ) . '"';
775
					}
776
					$header = 'Authorization: OAuth ' . join( ', ', $header );
777
					unset( $headerArr );
778
				}
779
				$logdata = "Date/Time: " . date( 'r' ) . "\nMethod: POST\nURL: {$this->base_url} (Parameters masked for security)\nRaw Data: ";
780
				$data = $this->get_http()->post(
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->get_http()->post(...$arrayParams, $header); of type false|string|null adds the type string to the return on line 785 which is incompatible with the return type documented by Wiki::apiQuery of type array|boolean.
Loading history...
781
					( $talktoOauth ? $talktoOauth : $this->base_url ),
0 ignored issues
show
Bug introduced by
It seems like $talktoOauth ? $talktoOauth : $this->base_url can also be of type boolean; however, HTTP::post() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
782
					$arrayParams,
783
					$header
0 ignored issues
show
Documentation introduced by
$header is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
784
				);
785
				if( $talktoOauth !== false && $data !== false ) return $data;
786
				$logdata .= $data;
787
				$data2 = ( $data === false || is_null( $data ) ? false : unserialize( $data ) );
788
				if( $data2 === false && serialize( $data2 ) != $data ) {
789
					$logdata .= "\nUNSERIALIZATION FAILED\n\n";
790
					if( $pgLogFailedCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Faileddata.log', $logdata, FILE_APPEND );
791
				} else {
792
					$logdata .= "\nUNSERIALIZATION SUCCEEDED\n\n";
793
					if( $pgLogSuccessfulCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Succeededdata.log', $logdata, FILE_APPEND );
794
				}
795
796
				if( $pgLogPostCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Postdata.log', $logdata, FILE_APPEND );
797
				if( $pgLogCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Querydata.log', $logdata, FILE_APPEND );
798
799
				$data = $data2;
800
				unset( $data2 );
801
				if( isset( $data['error'] ) && $data['error']['code'] == 'badtoken' ) {
802
					pecho( "API Error...\n\nBadtoken detected retrying with new tokens...\n\n", PECHO_WARN );
803
					$tokens = $this->get_tokens( true );
804
					$arrayParams['token'] = $tokens[$arrayParams['action']];
805
					continue;
806
				}
807
				if( $this->get_http()->get_HTTP_code() == 503 && $errorcheck ) {
808
					pecho( "API Error...\n\nCode: error503\nText: HTTP Error 503\nThe webserver's service is currently unavailable", PECHO_WARN );
809
					$tempSetting = $pgDisplayGetOutData;
810
					$pgDisplayGetOutData = false;
811
					$histemp = $this->initPage( $arrayParams['title'] )->history( 1 );
812
					if( $arrayParams['action'] == 'edit' && $histemp[0]['user'] == $this->get_username() && $histemp[0]['comment'] == $arrayParams['summary'] && strtotime( $histemp[0]['timestamp'] ) - time() < 120 ) {
813
						pecho( ", however, the edit appears to have gone through.\n\n", PECHO_WARN );
814
						$pgDisplayGetOutData = $tempSetting;
815
						unset( $tempSetting );
816
						return array( 'edit' => array( 'result' => 'Success', 'newrevid' => $histemp[0]['revid'] ) );
817
					} else {
818
						pecho( ", retrying...\n\n", PECHO_WARN );
819
						$pgDisplayGetOutData = $tempSetting;
820
						unset( $tempSetting );
821
						continue;
822
					}
823
				}
824
825
				Hooks::runHook( 'APIQueryCheckAssertion', array( &$assert, &$data['edit']['assert'] ) );
0 ignored issues
show
Bug introduced by
The variable $assert does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
826
				if( isset( $data['error'] ) && isset( $data['error']['code'] ) && $assert && $errorcheck ) {
827
					if( $data['error']['code'] == 'assertbotfailed' && $pgThrowExceptions ) {
828
						throw new AssertFailure( 'bot' );
829
					}
830
					if( $data['error']['code'] == 'assertuserfailed' && $pgThrowExceptions ) {
831
						throw new AssertFailure( 'user' );
832
					}
833
					if( $data['error']['code'] == 'assertbotfailed' && !$pgThrowExceptions ) {
834
						if( $this->is_logged_in() ) {
835
							pecho( "Assertion Failure: This user does not have the bot flag.  Waiting for bot flag...\n\n", PECHO_FATAL );
836
							$this->isFlagged = false;
837
							return false;
838
						} else {
839
							$data['error']['code'] = 'assertuserfailed'; //If we are logged out, log back in.
840
						}
841
					}
842
					if( $data['error']['code'] == 'assertuserfailed' && !$pgThrowExceptions ) {
843
						pecho( "Assertion Failure: This user has logged out.  Logging back in...\n\n", PECHO_FATAL );
844
						$this->logout();
845
						$this->__construct( $this->cached_config['config'], $this->cached_config['extensions'], 0, null );
846
						$this->get_tokens( true );
847
					}
848
				}
849
				if( !isset( $data['servedby'] ) && !isset( $data['requestid'] ) ) {
850
					pecho( "Warning: API is not responding, retrying...\n\n", PECHO_WARN );
851
				} else break;
852
			}
853
			if( $this->get_http()->get_HTTP_code() == 503 && $errorcheck ) {
854
				pecho( "API Error...\n\nCode: error503\nText: HTTP Error 503\nThe webserver's service is currently unavailable", PECHO_WARN );
855
				$tempSetting = $pgDisplayGetOutData;
856
				$pgDisplayGetOutData = false;
857
				$histemp = $this->initPage( $arrayParams['title'] )->history( 1 );
858
				if( $arrayParams['action'] == 'edit' && $histemp['user'] == $this->get_username() && $histemp['comment'] == $arrayParams['summary'] && strtotime( $histemp['timestamp'] ) - time() < 120 ) {
859
					pecho( ", however, the edit, finally, appears to have gone through.\n\n", PECHO_WARN );
860
					$pgDisplayGetOutData = $tempSetting;
861
					return array( 'edit' => array( 'result' => 'Success', 'newrevid' => $histemp['revid'] ) );
862
				} else {
863
					$pgDisplayGetOutData = $tempSetting;
864
					if( $pgThrowExceptions ) {
865
						pecho( ".  Terminating program.\n\n", PECHO_FATAL );
866
						throw new MWAPIError( array(
867
							'code' => 'error503', 'info' => 'nThe webserver\'s service is currently unavailable'
868
						) );
869
					} else {
870
						pecho( ".  Aborting attempts.", PECHO_FATAL );
871
						return false;
872
					}
873
				}
874
			}
875
876
			if( !isset( $data['servedby'] ) && !isset( $data['requestid'] ) ) {
877
				if( $pgThrowExceptions ) {
878
					pecho( "Fatal Error: API is not responding.  Terminating program.\n\n", PECHO_FATAL );
879
					throw new MWAPIError( array( 'code' => 'noresponse', 'info' => 'API is unresponsive' ) );
880
				} else {
881
					pecho( "API Error: API is not responding.  Aborting attempts.\n\n", PECHO_FATAL );
882
					return false;
883
				}
884
			}
885
886
			Hooks::runHook( 'PostAPIPostQuery', array( &$data ) );
887
888
			Hooks::runHook( 'APIQueryCheckError', array( &$data['error'] ) );
889
			if( isset( $data['error'] ) && $errorcheck ) {
890
891
				pecho( "API Error...\n\nCode: {$data['error']['code']}\nText: {$data['error']['info']}\n\n", PECHO_FATAL );
892
				return false;
893
			}
894
895
			if( isset( $data['servedby'] ) ) {
896
				$this->servedby = $data['servedby'];
897
			}
898
899
			if( isset( $data['requestid'] ) ) {
900
				if( $data['requestid'] != $requestid ) {
901
					if( $recursed ) {
902
						pecho( "API Error... requestid's didn't match twice.\n\n", PECHO_FATAL );
903
						return false;
904
					}
905
					return $this->apiQuery( $arrayParams, $post, $errorcheck, true );
906
				}
907
			}
908
909
			return $data;
910
		} else {
911
912
			Hooks::runHook( 'PreAPIGetQuery', array( &$arrayParams ) );
913
914
			for( $i = 0; $i < $attempts; $i++ ){
915
				if ( $this->oauthEnabled ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->oauthEnabled of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
916
					$headerArr = array(
917
						// OAuth information
918
						'oauth_consumer_key' => $this->consumerKey,
919
						'oauth_token' => $this->accessToken,
920
						'oauth_version' => '1.0',
921
						'oauth_nonce' => md5( microtime() . mt_rand() ),
922
						'oauth_timestamp' => time(),
923
924
						// We're using secret key signatures here.
925
						'oauth_signature_method' => 'HMAC-SHA1',
926
					);
927
					$signature = $this->generateSignature( 'GET',
928
						( $talktoOauth ? $talktoOauth : $this->base_url ) .
929
						( !empty( $arrayParams ) ? '?' . http_build_query( $arrayParams ) : "" ),
930
						$headerArr
931
					);
932
					$headerArr['oauth_signature'] = $signature;
933
934
					$header = array();
935
					foreach ( $headerArr as $k => $v ) {
936
						$header[] = rawurlencode( $k ) . '="' . rawurlencode( $v ) . '"';
937
					}
938
					$header = 'Authorization: OAuth ' . join( ', ', $header );
939
					unset( $headerArr );
940
				}
941
				$logdata = "Date/Time: " . date( 'r' ) . "\nMethod: GET\nURL: {$this->base_url}\nParameters: " . print_r( $arrayParams, true ) . "\nRaw Data: ";
942
				$data = $this->get_http()->get(
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->get_http()->get($...$arrayParams, $header); of type false|string|null adds the type string to the return on line 947 which is incompatible with the return type documented by Wiki::apiQuery of type array|boolean.
Loading history...
943
					( $talktoOauth ? $talktoOauth : $this->base_url ),
0 ignored issues
show
Bug introduced by
It seems like $talktoOauth ? $talktoOauth : $this->base_url can also be of type boolean; however, HTTP::get() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
944
					$arrayParams,
945
					$header
0 ignored issues
show
Documentation introduced by
$header is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
946
				);
947
				if( $talktoOauth !== false && $data !== false ) return $data;
948
				$logdata .= $data;
949
				$data2 = ( $data === false || is_null( $data ) ? false : unserialize( $data ) );
950
				if( $data2 === false && serialize( $data2 ) != $data ) {
951
					$logdata .= "\nUNSERIALIZATION FAILED\n\n";
952
					if( $pgLogFailedCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Faileddata.log', $logdata, FILE_APPEND );
953
				} else {
954
					$logdata .= "\nUNSERIALIZATION SUCCEEDED\n\n";
955
					if( $pgLogSuccessfulCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Succeededdata.log', $logdata, FILE_APPEND );
956
				}
957
958
				if( $pgLogGetCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Getdata.log', $logdata, FILE_APPEND );
959
				if( $pgLogCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Querydata.log', $logdata, FILE_APPEND );
960
961
				$data = $data2;
962
				unset( $data2 );
963
				if( $this->get_http()->get_HTTP_code() == 503 && $errorcheck ) {
964
					pecho( "API Error...\n\nCode: error503\nText: HTTP Error 503\nThe webserver's service is currently unavailable, retrying...", PECHO_WARN );
965
				}
966
967
				if( !isset( $data['servedby'] ) && !isset( $data['requestid'] ) ) {
968
					pecho( "Warning: API is not responding, retrying...\n\n", PECHO_WARN );
969
				} else break;
970
			}
971
972
			if( $this->get_http()->get_HTTP_code() == 503 && $errorcheck ) {
973
				if( $pgThrowExceptions ) {
974
					pecho( "Fatal Error: API Error...\n\nCode: error503\nText: HTTP Error 503\nThe webserver's service is still not available.  Terminating program.\n\n", PECHO_FATAL );
975
					throw new MWAPIError( array(
976
						'code' => 'error503', 'info' => 'nThe webserver\'s service is currently unavailable'
977
					) );
978
				} else {
979
					pecho( "API Error...\n\nCode: error503\nText: HTTP Error 503\nThe webserver's service is still not available.  Aborting attempts.\n\n", PECHO_FATAL );
980
					return false;
981
				}
982
983
			}
984
985
			if( !isset( $data['servedby'] ) && !isset( $data['requestid'] ) ) {
986
				if( $pgThrowExceptions ) {
987
					pecho( "Fatal Error: API is not responding.  Terminating program.\n\n", PECHO_FATAL );
988
					throw new MWAPIError( array( 'code' => 'noresponse', 'info' => 'API is unresponsive' ) );
989
				} else {
990
					pecho( "API Error: API is not responding.  Aborting attempts.\n\n", PECHO_FATAL );
991
					return false;
992
				}
993
			}
994
995
			Hooks::runHook( 'APIQueryCheckError', array( &$data['error'] ) );
996
			if( isset( $data['error'] ) && $errorcheck ) {
997
998
				pecho( "API Error...\n\nCode: {$data['error']['code']}\nText: {$data['error']['info']}\n\n", PECHO_FATAL );
999
				return false;
1000
			}
1001
1002
			if( isset( $data['servedby'] ) ) {
1003
				$this->servedby = $data['servedby'];
1004
			}
1005
1006
			if( isset( $data['requestid'] ) ) {
1007
				if( $data['requestid'] != $requestid ) {
1008
					if( $recursed ) {
1009
						pecho( "API Error... requestid's didn't match twice.\n\n", PECHO_FATAL );
1010
						return false;
1011
					}
1012
					return $this->apiQuery( $arrayParams, $post, $errorcheck, true );
1013
				}
1014
			}
1015
1016
			return $data;
1017
		}
1018
	}
1019
1020
	/**
1021
	 * Returns the server that handled the previous request. Only works on MediaWiki versions 1.17 and up
1022
	 *
1023
	 * @return string
1024
	 */
1025
	public function get_servedby() {
1026
		return $this->servedby;
1027
	}
1028
1029
	/**
1030
	 * Returns the version of MediaWiki that is on the server
1031
	 *
1032
	 * @return string
1033
	 */
1034
	public function get_mw_version() {
1035
		return $this->mwversion;
1036
	}
1037
1038
	/**
1039
	 * Simplifies the running of API queries, especially with continues and other parameters.
1040
	 *
1041
	 * @access public
1042
	 * @link http://wiki.peachy.compwhizii.net/wiki/Manual/Wiki::listHandler
1043
	 * @param array $tArray Parameters given to query with (default: array()). In addition to those recognised by the API, ['_code'] should be set to the first two characters of all the parameters in a list=XXX API call - for example, with allpages, the parameters start with 'ap', with recentchanges, the parameters start with 'rc' -  and is required; ['_limit'] imposes a hard limit on the number of results returned (optional) and ['_lhtitle'] simplifies a multidimensional result into a unidimensional result - lhtitle is the key of the sub-array to return. (optional)
1044
	 * @param array $resume Parameter passed back at the end of a list-handler operation.  Pass parameter back through to resume listhandler operation. (optional)
1045
	 * @throws BadEntryError
1046
	 * @return array Returns an array with the API result
1047
	 */
1048
	public function listHandler( $tArray = array(), &$resume = null ) {
1049
1050
		if( isset( $tArray['_code'] ) ) {
1051
			$code = $tArray['_code'];
1052
			unset( $tArray['_code'] );
1053
		} else {
1054
			throw new BadEntryError( "listHandler", "Parameter _code is required." );
1055
		}
1056
		if( isset( $tArray['_limit'] ) ) {
1057
			$limit = $tArray['_limit'];
1058
			unset( $tArray['_limit'] );
1059
		} else {
1060
			$limit = null;
1061
		}
1062
		if( isset( $tArray['_lhtitle'] ) ) {
1063
			$lhtitle = $tArray['_lhtitle'];
1064
			unset( $tArray['_lhtitle'] );
1065
		} else {
1066
			$lhtitle = null;
1067
		}
1068
		if ( !is_null( $resume ) ) {
1069
			$tArray = array_merge( $tArray, $resume );
1070
		} else {
1071
			$resume = array();
1072
		}
1073
1074
		$tArray['action'] = 'query';
1075
		$tArray[$code . 'limit'] = 'max';
1076
		$tArray['rawcontinue'] = 1;
1077
1078
		if( isset( $limit ) && !is_null( $limit ) ) {
1079
			if( !is_numeric( $limit ) ) {
1080
				throw new BadEntryError( "listHandler", "limit should be a number or null" );
1081
			} else {
1082
				$limit = intval( $limit );
1083
				if( $limit < 0 || ( floor( $limit ) != $limit ) ) {
1084
					if( !$limit == -1 ) {
1085
						throw new BadEntryError( "listHandler", "limit should an integer greater than 0" );
1086
					} else $limit = 'max';
1087
				}
1088
				$tArray[$code . 'limit'] = $limit;
1089
			}
1090
		}
1091
		if( isset( $tArray[$code . 'namespace'] ) && !is_null( $tArray[$code . 'namespace'] ) ) {
1092
			if( is_array( $tArray[$code . 'namespace'] ) ) {
1093
				$tArray[$code . 'namespace'] = implode( '|', $tArray[$code . 'namespace'] );
1094
			} elseif( strlen( $tArray[$code . 'namespace'] ) === 0 ) {
1095
				$tArray[$code . 'namespace'] = null;
1096
			} else {
1097
				$tArray[$code . 'namespace'] = (string)$tArray[$code . 'namespace'];
1098
			}
1099
		}
1100
1101
		$endArray = array();
1102
1103
		$continue = null;
1104
		$offset = null;
1105
		$start = null;
1106
		$from = null;
1107
1108
		pecho( "Running list handler function with params " . implode( ";", $tArray ) . "...\n\n", PECHO_VERBOSE );
1109
1110
		while( 1 ){
1111
1112
			if( !is_null( $continue ) ) $tArray[$code . 'continue'] = $continue;
1113
			if( !is_null( $offset ) ) $tArray[$code . 'offset'] = $offset;
1114
			if( !is_null( $start ) ) $tArray[$code . 'start'] = $start;
1115
			if( !is_null( $from ) ) $tArray[$code . 'from'] = $from;
1116
1117
			$tRes = $this->apiQuery( $tArray );
1118
			if( !isset( $tRes['query'] ) ) break;
1119
1120
			foreach ( $tRes['query'] as $x ) {
1121
				foreach ( $x as $y ) {
1122
					if ( !is_null( $lhtitle ) ) {
1123
						if ( isset( $y[$lhtitle] ) ) {
1124
							$y = $y[$lhtitle];
1125
							if ( is_array( $y ) ) {
1126
								$endArray = array_merge( $endArray, $y );
1127
							} else {
1128
								$endArray[] = $y;
1129
							}
1130
							continue;
1131
						} else {
1132
							continue;
1133
						}
1134
					}
1135
					$endArray[] = $y;
1136
				}
1137
			}
1138
1139
			if( isset( $tRes['query-continue'] ) ) {
1140
				foreach( $tRes['query-continue'] as $z ){
1141
					if( isset( $z[$code . 'continue'] ) ) {
1142
						$continue = $resume[$code . 'continue'] = $z[$code . 'continue'];
1143
					} elseif( isset( $z[$code . 'offset'] ) ) {
1144
						$offset = $resume[$code . 'offset'] = $z[$code . 'offset'];
1145
					} elseif( isset( $z[$code . 'start'] ) ) {
1146
						$start = $resume[$code . 'start'] = $z[$code . 'start'];
1147
					} elseif( isset( $z[$code . 'from'] ) ) {
1148
						$from = $resume[$code . 'from'] = $z[$code . 'from'];
1149
					}
1150
				}
1151
			} else {
1152
                $resume = array();
1153
				break;
1154
			}
1155
1156
			if ( !is_null( $limit ) && $limit != 'max' ) {
1157
				if ( count( $endArray ) >= $limit ) {
1158
					$endArray = array_slice( $endArray, 0, $limit );
1159
					break;
1160
				}
1161
			}
1162
1163
		}
1164
1165
		return $endArray;
1166
	}
1167
1168
	/**
1169
	 * Returns a reference to the HTTP Class
1170
	 *
1171
	 * @access public
1172
	 * @see Wiki::$http
1173
	 * @return HTTP
1174
	 */
1175
	public function &get_http() {
1176
		return $this->http;
1177
	}
1178
1179
	/**
1180
	 * Returns whether or not to log in
1181
	 *
1182
	 * @access public
1183
	 * @see Wiki::$nologin
1184
	 * @return bool
1185
	 */
1186
	public function get_nologin() {
1187
		return $this->nologin;
1188
	}
1189
1190
	/**
1191
	 * Returns the base URL for the wiki.
1192
	 *
1193
	 * @access public
1194
	 * @see Wiki::$base_url
1195
	 * @return string base_url for the wiki
1196
	 */
1197
	public function get_base_url() {
1198
		return $this->base_url;
1199
	}
1200
1201
	/**
1202
	 * Returns the api query limit for the wiki.
1203
	 *
1204
	 * @access public
1205
	 * @see Wiki::$apiQueryLimit
1206
	 * @return int apiQueryLimit fot the wiki
1207
	 */
1208
	public function get_api_limit() {
1209
		return $this->apiQueryLimit;
1210
	}
1211
1212
	/**
1213
	 * Returns the runpage.
1214
	 *
1215
	 * @access public
1216
	 * @see Wiki::$runpage
1217
	 * @return string Runpage for the user
1218
	 */
1219
	public function get_runpage() {
1220
		return $this->runpage;
1221
	}
1222
1223
	/**
1224
	 * Returns if maxlag is on or what it is set to for the wiki.
1225
	 *
1226
	 * @access public
1227
	 * @see Wiki:$maxlag
1228
	 * @return bool|int Max lag for the wiki
1229
	 */
1230
	public function get_maxlag() {
1231
		return $this->maxlag;
1232
	}
1233
1234
	/**
1235
	 * Returns the edit rate in EPM for the wiki.
1236
	 *
1237
	 * @access public
1238
	 * @see Wiki::$edit_rate
1239
	 * @return int Edit rate in EPM for the wiki
1240
	 */
1241
	public function get_edit_rate() {
1242
		return $this->edit_rate;
1243
	}
1244
1245
	/**
1246
	 * Returns the username.
1247
	 *
1248
	 * @access public
1249
	 * @see Wiki::$pgUsername
1250
	 * @return string Username
1251
	 */
1252
	public function get_username() {
1253
		return $this->username;
1254
	}
1255
1256
	/**
1257
	 * Returns if the Wiki should follow nobots rules.
1258
	 *
1259
	 * @access public
1260
	 * @see Wiki::$nobots
1261
	 * @return bool True for following nobots
1262
	 */
1263
	public function get_nobots() {
1264
		return $this->nobots;
1265
	}
1266
1267
	/**
1268
	 * Returns if the script should not edit if the user has new messages
1269
	 *
1270
	 * @access public
1271
	 * @see Wiki::$stoponnewmessages
1272
	 * @return bool True for stopping on new messages
1273
	 */
1274
	public function get_stoponnewmessages() {
1275
		return $this->stoponnewmessages;
1276
	}
1277
1278
	/**
1279
	 * Returns the text to search for in the optout= field of the {{nobots}} template
1280
	 *
1281
	 * @access public
1282
	 * @see Wiki::$optout
1283
	 * @return null|string String to search for
1284
	 */
1285
	public function get_optout() {
1286
		return $this->optout;
1287
	}
1288
1289
	/**
1290
	 * Returns the configuration of the wiki
1291
	 *
1292
	 * @param string $conf_name Name of configuration setting to get. Default null, will return all configuration.
1293
	 * @access public
1294
	 * @see Wiki::$configuration
1295
	 * @return array Configuration array
1296
	 */
1297
	public function get_configuration( $conf_name = null ) {
1298
		if( is_null( $conf_name ) ) {
1299
			return $this->configuration;
1300
		} else {
1301
			return $this->configuration[$conf_name];
1302
		}
1303
	}
1304
1305
	public function get_conf( $conf_name = null ) {
1306
		return $this->get_configuration( $conf_name );
1307
	}
1308
1309
	/**
1310
	 * Purges a list of pages
1311
	 *
1312
	 * @access public
1313
	 * @param array|string $titles A list of titles to work on
1314
	 * @param array|string $pageids A list of page IDs to work on
1315
	 * @param array|string $revids A list of revision IDs to work on
1316
	 * @param bool $redirects Automatically resolve redirects. Default false.
1317
	 * @param bool $force Update the links tables. Default false.
1318
	 * @param bool $convert Convert titles to other variants if necessary. Default false.
1319
	 * @param string $generator Get the list of pages to work on by executing the specified query module. Default null.
1320
	 * @return boolean
1321
	 */
1322
	public function purge( $titles = null, $pageids = null, $revids = null, $force = false, $redirects = false, $convert = false, $generator = null ) {
1323
1324
		$apiArr = array(
1325
			'action' => 'purge'
1326
		);
1327
1328
		if( is_null( $titles ) && is_null( $pageids ) && is_null( $revids ) ) {
1329
			pecho( "Error: Nothing to purge.\n\n", PECHO_WARN );
1330
			return false;
1331
		}
1332
		Hooks::runHook( 'StartPurge', array( &$titles ) );
1333
		if( !is_null( $titles ) ) {
1334
			if( is_array( $titles ) ) $titles = implode( '|', $titles );
1335
			$apiArr['titles'] = $titles;
1336
		}
1337
		if( !is_null( $pageids ) ) {
1338
			if( is_array( $pageids ) ) $pageids = implode( '|', $pageids );
1339
			$apiArr['pageids'] = $pageids;
1340
		}
1341
		if( !is_null( $revids ) ) {
1342
			if( is_array( $revids ) ) $revids = implode( '|', $revids );
1343
			$apiArr['revids'] = $revids;
1344
		}
1345
		if( $redirects ) $apiArr['redirects'] = '';
1346
		if( $force ) $apiArr['forcelinkupdate'] = '';
1347
		if( $convert ) $apiArr['converttitles'] = '';
1348
1349
		$genparams = $this->generatorvalues;
1350
		if( !is_null( $generator ) ) {
1351
			if( in_array( $generator, $genparams ) ) {
1352
				$apiArr['generator'] = 'g' . $generator;
1353
			} else pecho( "Invalid generator value detected.  Omitting...\n\n", PECHO_WARN );
1354
		}
1355
1356
		pecho( "Purging...\n\n", PECHO_NOTICE );
1357
1358
		Hooks::runHook( 'StartPurge', array( &$apiArr ) );
1359
1360
		$result = $this->apiQuery( $apiArr, true, true, false );
1361
1362
		if( isset( $result['purge'] ) ) {
1363
			foreach( $result['purge'] as $page ){
1364
				if( !isset( $page['purged'] ) ) {
1365
					pecho( "Purge error on {$page['title']}...\n\n" . print_r( $page, true ) . "\n\n", PECHO_FATAL );
1366
					return false;
1367
				}
1368
			}
1369
			return true;
1370
1371
		} else {
1372
			pecho( "Purge error...\n\n" . print_r( $result, true ), PECHO_FATAL );
1373
			return false;
1374
		}
1375
	}
1376
1377
1378
	/**
1379
	 * Returns a list of recent changes
1380
	 *
1381
	 * @access public
1382
	 * @param integer|array|string $namespace Namespace(s) to check
1383
	 * @param string $pgTag Only list recent changes bearing this tag.
1384
	 * @param int $start Only list changes after this timestamp.
1385
	 * @param int $end Only list changes before this timestamp.
1386
	 * @param string $user Only list changes by this user.
1387
	 * @param string $excludeuser Only list changes not by this user.
1388
	 * @param string $dir 'older' lists changes, most recent first; 'newer' least recent first.
1389
	 * @param bool $minor Whether to only include minor edits (true), only non-minor edits (false) or both (null). Default null.
1390
	 * @param bool $bot Whether to only include bot edits (true), only non-bot edits (false) or both (null). Default null.
1391
	 * @param bool $anon Whether to only include anonymous edits (true), only non-anonymous edits (false) or both (null). Default null.
1392
	 * @param bool $redirect Whether to only include edits to redirects (true), edits to non-redirects (false) or both (null). Default null.
1393
	 * @param bool $patrolled Whether to only include patrolled edits (true), only non-patrolled edits (false) or both (null). Default null.
1394
	 * @param array $prop What properties to retrieve. Default array( 'user', 'comment', 'flags', 'timestamp', 'title', 'ids', 'sizes', 'tags' ).
1395
	 * @param int $limit A hard limit to impose on the number of results returned.
1396
	 * @return array Recent changes matching the description.
1397
	 */
1398
	public function recentchanges( $namespace = 0, $pgTag = null, $start = null, $end = null, $user = null, $excludeuser = null, $dir = 'older', $minor = null, $bot = null, $anon = null, $redirect = null, $patrolled = null, $prop = null, $limit = 50 ) {
1399
1400
		if( is_array( $namespace ) ) {
1401
			$namespace = implode( '|', $namespace );
1402
		}
1403
1404
		if( is_null( $prop ) ) {
1405
			$prop = array(
1406
				'user', 'comment', 'flags', 'timestamp', 'title', 'ids', 'sizes', 'tags'
1407
			);
1408
		}
1409
1410
		$rcArray = array(
1411
			'list'        => 'recentchanges',
1412
			'_code'       => 'rc',
1413
			'rcnamespace' => $namespace,
1414
			'rcdir'       => $dir,
1415
			'rcprop'      => implode( '|', $prop ),
1416
			'_limit'      => $limit
1417
		);
1418
1419
		if( !is_null( $pgTag ) ) $rcArray['rctag'] = $pgTag;
1420
		if( !is_null( $start ) ) $rcArray['rcstart'] = $start;
1421
		if( !is_null( $end ) ) $rcArray['rcend'] = $end;
1422
		if( !is_null( $user ) ) $rcArray['rcuser'] = $user;
1423
		if( !is_null( $excludeuser ) ) $rcArray['rcexcludeuser'] = $excludeuser;
1424
1425
		$rcshow = array();
1426
1427
		if( !is_null( $minor ) ) {
1428
			if( $minor ) {
1429
				$rcshow[] = 'minor';
1430
			} else {
1431
				$rcshow[] = '!minor';
1432
			}
1433
		}
1434
1435
		if( !is_null( $bot ) ) {
1436
			if( $bot ) {
1437
				$rcshow[] = 'bot';
1438
			} else {
1439
				$rcshow[] = '!bot';
1440
			}
1441
		}
1442
1443
		if( !is_null( $anon ) ) {
1444
			if( $minor ) {
1445
				$rcshow[] = 'anon';
1446
			} else {
1447
				$rcshow[] = '!anon';
1448
			}
1449
		}
1450
1451
		if( !is_null( $redirect ) ) {
1452
			if( $redirect ) {
1453
				$rcshow[] = 'redirect';
1454
			} else {
1455
				$rcshow[] = '!redirect';
1456
			}
1457
		}
1458
1459
		if( !is_null( $patrolled ) ) {
1460
			if( $minor ) {
1461
				$rcshow[] = 'patrolled';
1462
			} else {
1463
				$rcshow[] = '!patrolled';
1464
			}
1465
		}
1466
1467
		if( count( $rcshow ) ) $rcArray['rcshow'] = implode( '|', $rcshow );
1468
1469
		$rcArray['limit'] = $this->apiQueryLimit;
1470
1471
		Hooks::runHook( 'PreQueryRecentchanges', array( &$rcArray ) );
1472
1473
		pecho( "Getting recent changes...\n\n", PECHO_NORMAL );
1474
1475
		return $this->listHandler( $rcArray );
1476
1477
	}
1478
1479
	/**
1480
	 * Performs a search and retrieves the results
1481
	 *
1482
	 * @access public
1483
	 * @param string $search What to search for
1484
	 * @param bool $fulltext Whether to search the full text of pages (default, true) or just titles (false; may not be enabled on all wikis).
1485
	 * @param array $namespaces The namespaces to search in (default: array( 0 )).
1486
	 * @param array $prop What properties to retrieve (default: array('size', 'wordcount', 'timestamp', 'snippet') ).
1487
	 * @param bool $includeredirects Whether to include redirects or not (default: true).
1488
	 * @param int $limit A hard limit on the number of results to retrieve (default: null i.e. all).
1489
	 * @return array
1490
	 */
1491
	public function search( $search, $fulltext = true, $namespaces = array( 0 ), $prop = array(
1492
		'size', 'wordcount', 'timestamp', 'snippet'
1493
	), $includeredirects = true, $limit = 50 ) {
1494
1495
		$srArray = array(
1496
			'_code'       => 'sr',
1497
			'list'        => 'search',
1498
			'_limit'      => $limit,
1499
			'srsearch'    => $search,
1500
			'srnamespace' => $namespaces,
1501
			'srwhat'      => ( $fulltext ) ? "text" : "title",
1502
			'srinfo'      => '',
1503
			##FIXME: find a meaningful way of passing back 'totalhits' and 'suggestion' as required.
1504
			'srprop'      => implode( '|', $prop ),
1505
			'srredirects' => $includeredirects
1506
		);
1507
1508
		pecho( "Searching for $search...\n\n", PECHO_NORMAL );
1509
1510
		return $this->listHandler( $srArray );
1511
	}
1512
1513
	/**
1514
	 * Retrieves log entries from the wiki.
1515
	 *
1516
	 * @access public
1517
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Lists#logevents_.2F_le
1518
	 * @param bool|string $type Type of log to retrieve from the wiki (default: false)
1519
	 * @param bool|string $user Restrict the log to a certain user (default: false)
1520
	 * @param bool|string $title Restrict the log to a certain page (default: false)
1521
	 * @param bool|string $start Timestamp for the start of the log (default: false)
1522
	 * @param bool|string $end Timestamp for the end of the log (default: false)
1523
	 * @param string $dir Direction for retieving log entries (default: 'older')
1524
	 * @param bool $pgTag Restrict the log to entries with a certain tag (default: false)
1525
	 * @param array $prop Information to retieve from the log (default: array( 'ids', 'title', 'type', 'user', 'timestamp', 'comment', 'details' ))
1526
	 * @param int $limit How many results to retrieve (default: null i.e. all).
1527
	 * @return array Log entries
1528
	 */
1529
	public function logs( $type = false, $user = false, $title = false, $start = false, $end = false, $dir = 'older', $pgTag = false, $prop = array(
1530
		'ids', 'title', 'type', 'user', 'userid', 'timestamp', 'comment', 'parsedcomment', 'details', 'tags'
1531
	), $limit = 50 ) {
1532
1533
		$leArray = array(
1534
			'list'   => 'logevents',
1535
			'_code'  => 'le',
1536
			'ledir'  => $dir,
1537
			'leprop' => implode( '|', $prop ),
1538
			'_limit' => $limit
1539
		);
1540
1541
		if( $type ) $leArray['letype'] = $type;
1542
		if( $start ) $leArray['lestart'] = $start;
1543
		if( $end ) $leArray['leend'] = $end;
1544
		if( $user ) $leArray['leuser'] = $user;
1545
		if( $title ) $leArray['letitle'] = $title;
1546
		if( $pgTag ) $leArray['letag'] = $pgTag;
1547
1548
		Hooks::runHook( 'PreQueryLog', array( &$leArray ) );
1549
1550
		if( $type ) {
1551
			if( $title || $user ) $title = ' for ' . $title;
1552
			pecho( "Getting $type logs{$title}{$user}...\n\n", PECHO_NORMAL );
1553
		} else {
1554
			pecho( "Getting logs...\n\n", PECHO_NORMAL );
1555
		}
1556
1557
		return $this->listHandler( $leArray );
1558
	}
1559
1560
	/**
1561
	 * Enumerate all categories
1562
	 *
1563
	 * @access public
1564
	 * @link https://www.mediawiki.org/wiki/API:Allcategories
1565
	 * @param string $prefix Search for all category titles that begin with this value. (default: null)
1566
	 * @param string $from The category to start enumerating from. (default: null)
1567
	 * @param string $min Minimum number of category members. (default: null)
1568
	 * @param string $max Maximum number of category members. (default: null)
1569
	 * @param string $dir Direction to sort in. (default: 'ascending')
1570
	 * @param array $prop Information to retieve (default: array( 'size', 'hidden' ))
1571
	 * @param int $limit How many categories to return. (default: null i.e. all).
1572
	 * @return array List of categories
1573
	 */
1574
	public function allcategories( $prefix = null, $from = null, $min = null, $max = null, $dir = 'ascending', $prop = array(
1575
		'size', 'hidden'
1576
	), $limit = 50 ) {
1577
		$leArray = array(
1578
			'list'   => 'allcategories',
1579
			'_code'  => 'ac',
1580
			'acdir'  => $dir,
1581
			'acprop' => implode( '|', $prop ),
1582
			'_limit' => $limit
1583
		);
1584
1585
		if( !is_null( $from ) ) $leArray['acfrom'] = $from;
1586
		if( !is_null( $prefix ) ) $leArray['acprefix'] = $prefix;
1587
		if( !is_null( $min ) ) $leArray['acmin'] = $min;
1588
		if( !is_null( $max ) ) $leArray['acmax'] = $max;
1589
1590
		Hooks::runHook( 'PreQueryAllimages', array( &$leArray ) );
1591
1592
		pecho( "Getting list of all categories...\n\n", PECHO_NORMAL );
1593
1594
		return $this->listHandler( $leArray );
1595
1596
	}
1597
1598
	/**
1599
	 * Enumerate all images sequentially
1600
	 *
1601
	 * @access public
1602
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Lists#allimages_.2F_le
1603
	 * @param string $prefix Search for all image titles that begin with this value. (default: null)
1604
	 * @param string $sha1 SHA1 hash of image (default: null)
1605
	 * @param string $base36 SHA1 hash of image in base 36 (default: null)
1606
	 * @param string $from The image title to start enumerating from. (default: null)
1607
	 * @param string $minsize Limit to images with at least this many bytes (default: null)
1608
	 * @param string $maxsize Limit to images with at most this many bytes (default: null)
1609
	 * @param string $dir Direction in which to list (default: 'ascending')
1610
	 * @param array $prop Information to retieve (default: array( 'timestamp', 'user', 'comment', 'url', 'size', 'dimensions', 'sha1', 'mime', 'metadata', 'archivename', 'bitdepth' ))
1611
	 * @param int $limit How many results to retrieve (default: null i.e. all).
1612
	 * @return array List of images
1613
	 */
1614
	public function allimages( $prefix = null, $sha1 = null, $base36 = null, $from = null, $minsize = null, $maxsize = null, $dir = 'ascending', $prop = array(
1615
		'timestamp', 'user', 'comment', 'url', 'size', 'dimensions', 'sha1', 'mime', 'metadata', 'archivename',
1616
		'bitdepth'
1617
	), $limit = 50 ) {
1618
		$leArray = array(
1619
			'list'   => 'allimages',
1620
			'_code'  => 'ai',
1621
			'aidir'  => $dir,
1622
			'aiprop' => implode( '|', $prop ),
1623
			'_limit' => $limit
1624
		);
1625
1626
		if( !is_null( $from ) ) $leArray['aifrom'] = $from;
1627
		if( !is_null( $prefix ) ) $leArray['aiprefix'] = $prefix;
1628
		if( !is_null( $minsize ) ) $leArray['aiminsize'] = $minsize;
1629
		if( !is_null( $maxsize ) ) $leArray['aimaxsize'] = $maxsize;
1630
		if( !is_null( $sha1 ) ) $leArray['aisha1'] = $sha1;
1631
		if( !is_null( $base36 ) ) $leArray['aisha1base36'] = $base36;
1632
1633
		Hooks::runHook( 'PreQueryAllimages', array( &$leArray ) );
1634
1635
		pecho( "Getting list of all images...\n\n", PECHO_NORMAL );
1636
1637
		return $this->listHandler( $leArray );
1638
1639
	}
1640
1641
	/**
1642
	 * Enumerate all pages sequentially
1643
	 *
1644
	 * @access public
1645
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Lists#allpages_.2F_le
1646
	 * @param array $namespace The namespace to enumerate. (default: array( 0 ))
1647
	 * @param string $prefix Search for all page titles that begin with this value. (default: null)
1648
	 * @param string $from The page title to start enumerating from. (default: null)
1649
	 * @param string $redirects Which pages to list: all, redirects, or nonredirects (default: all)
1650
	 * @param string $minsize Limit to pages with at least this many bytes (default: null)
1651
	 * @param string $maxsize Limit to pages with at most this many bytes (default: null)
1652
	 * @param array $protectiontypes Limit to protected pages. Examples: array( 'edit' ), array( 'move' ), array( 'edit', 'move' ). (default: array())
1653
	 * @param array $protectionlevels Limit to protected pages. Examples: array( 'autoconfirmed' ), array( 'sysop' ), array( 'autoconfirmed', 'sysop' ). (default: array())
1654
	 * @param string $dir Direction in which to list (default: 'ascending')
1655
	 * @param string $interwiki Filter based on whether a page has langlinks (either withlanglinks, withoutlanglinks, or all (default))
1656
	 * @param int $limit How many results to retrieve (default: null i.e. all)
1657
	 * @return array List of pages
1658
	 */
1659
	public function allpages( $namespace = array( 0 ), $prefix = null, $from = null, $redirects = 'all', $minsize = null, $maxsize = null, $protectiontypes = array(), $protectionlevels = array(), $dir = 'ascending', $interwiki = 'all', $limit = 50 ) {
1660
		$leArray = array(
1661
			'list'              => 'allpages',
1662
			'_code'             => 'ap',
1663
			'apdir'             => $dir,
1664
			'apnamespace'       => $namespace,
1665
			'apfilterredir'     => $redirects,
1666
			'apfilterlanglinks' => $interwiki,
1667
			'_limit'            => $limit
1668
		);
1669
1670
		if( count( $protectiontypes ) ) {
1671
			// Trying to filter by protection status
1672
			$leArray['apprtype'] = implode( '|', $protectiontypes );
1673
			if( count( $protectionlevels ) ) $leArray['apprlevel'] = implode( '|', $protectionlevels );
1674
		} elseif( count( $protectionlevels ) ) {
1675
			pecho( 'If $protectionlevels is specified, $protectiontypes must also be specified.', PECHO_FATAL );
1676
			return false;
1677
		}
1678
1679
		if( !is_null( $from ) ) $leArray['apfrom'] = $from; //
1680
		if( !is_null( $prefix ) ) $leArray['apprefix'] = $prefix; //
1681
		if( !is_null( $minsize ) ) $leArray['apminsize'] = $minsize; //
1682
		if( !is_null( $maxsize ) ) $leArray['apmaxsize'] = $maxsize; // 
1683
1684
		Hooks::runHook( 'PreQueryAllpages', array( &$leArray ) );
1685
1686
		pecho( "Getting list of all pages...\n\n", PECHO_NORMAL );
1687
1688
		return $this->listHandler( $leArray );
1689
	}
1690
1691
	/**
1692
	 * Enumerate all internal links that point to a given namespace
1693
	 *
1694
	 * @access public
1695
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Lists#alllinks_.2F_le
1696
	 * @param array $namespace The namespace to enumerate. (default: array( 0 ))
1697
	 * @param string $prefix Search for all page titles that begin with this value. (default: null)
1698
	 * @param string $from The page title to start enumerating from. (default: null)
1699
	 * @param string $continue When more results are available, use this to continue. (default: null)
1700
	 * @param bool $unique Set to true in order to only show unique links (default: true)
1701
	 * @param array $prop What pieces of information to include: ids and/or title. (default: array( 'ids', 'title' ))
1702
	 * @param int $limit How many results to retrieve (default: null i.e. all).
1703
	 * @return array List of links
1704
	 */
1705
	public function alllinks( $namespace = array( 0 ), $prefix = null, $from = null, $continue = null, $unique = false, $prop = array(
1706
		'ids', 'title'
1707
	), $limit = 50 ) {
1708
		$leArray = array(
1709
			'list'        => 'alllinks',
1710
			'_code'       => 'al',
1711
			'alnamespace' => $namespace,
1712
			'alprop'      => implode( '|', $prop ),
1713
			'_limit'      => $limit
1714
		);
1715
1716
		if( !is_null( $from ) ) $leArray['alfrom'] = $from;
1717
		if( !is_null( $prefix ) ) $leArray['alprefix'] = $prefix;
1718
		if( !is_null( $continue ) ) $leArray['alcontinue'] = $continue;
1719
		if( $unique ) $leArray['alunique'] = '';
1720
		$leArray['limit'] = $this->apiQueryLimit;
1721
1722
		Hooks::runHook( 'PreQueryAlllinks', array( &$leArray ) );
1723
1724
		pecho( "Getting list of all internal links...\n\n", PECHO_NORMAL );
1725
1726
		return $this->listHandler( $leArray );
1727
	}
1728
1729
	/**
1730
	 * Enumerate all registered users
1731
	 *
1732
	 * @access public
1733
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Lists#alllinks_.2F_le
1734
	 * @param string $prefix Search for all usernames that begin with this value. (default: null)
1735
	 * @param array $groups Limit users to a given group name (default: array())
1736
	 * @param string $from The username to start enumerating from. (default: null)
1737
	 * @param bool $editsonly Set to true in order to only show users with edits (default: false)
1738
	 * @param array $prop What pieces of information to include (default: array( 'blockinfo', 'groups', 'editcount', 'registration' ))
1739
	 * @param int $limit How many results to retrieve (default: null i.e. all).
1740
	 * @return array List of users
1741
	 */
1742
	public function allusers( $prefix = null, $groups = array(), $from = null, $editsonly = false, $prop = array(
1743
		'blockinfo', 'groups', 'editcount', 'registration'
1744
	), $limit = 50 ) {
1745
		$leArray = array(
1746
			'list'   => 'allusers',
1747
			'_code'  => 'au',
1748
			'auprop' => implode( '|', $prop ),
1749
			'_limit' => $limit
1750
		);
1751
1752
		if( !is_null( $from ) ) $leArray['aufrom'] = $from;
1753
		if( !is_null( $prefix ) ) $leArray['auprefix'] = $prefix;
1754
		if( count( $groups ) ) $leArray['augroup'] = implode( '|', $groups );
1755
		if( $editsonly ) $leArray['auwitheditsonly'] = '';
1756
1757
		Hooks::runHook( 'PreQueryAllusers', array( &$leArray ) );
1758
1759
		pecho( "Getting list of all users...\n\n", PECHO_NORMAL );
1760
1761
		return $this->listHandler( $leArray );
1762
	}
1763
1764
	public function listblocks() {
1765
		pecho( "Error: " . __METHOD__ . " has not been programmed as of yet.\n\n", PECHO_ERROR );
1766
	}
1767
1768
	/**
1769
	 * Retrieves the titles of member pages of the given category
1770
	 *
1771
	 * @access public
1772
	 * @param string $category Category to retieve
1773
	 * @param bool $subcat Should subcategories be checked (default: false)
1774
	 * @param string|array $namespace Restrict results to the given namespace (default: null i.e. all)
1775
	 * @param int $limit How many results to retrieve (default: null i.e. all)
1776
	 * @return array Array of titles
1777
	 */
1778
	public function categorymembers( $category, $subcat = false, $namespace = null, $limit = 50 ) {
1779
		$cmArray = array(
1780
			'list'    => 'categorymembers',
1781
			'_code'   => 'cm',
1782
			'cmtitle' => $category,
1783
			'cmtype'  => 'page',
1784
			'_limit'  => $limit
1785
		);
1786
1787
		if( $subcat ) $cmArray['cmtype'] = 'page|subcat';
1788
		if( $namespace !== null ) {
1789
			if( is_array( $namespace ) ) $namespace = implode( '|', $namespace );
1790
			$cmArray['cmnamespace'] = $namespace;
1791
		}
1792
1793
		Hooks::runHook( 'PreQueryCategorymembers', array( &$cmArray ) );
1794
1795
		pecho( "Getting list of pages in the $category category...\n\n", PECHO_NORMAL );
1796
1797
		return $this->listHandler( $cmArray );
1798
	}
1799
1800
	/**
1801
	 * Returns array of pages that embed (transclude) the page given.
1802
	 *
1803
	 * @see Page::embeddedin()
1804
	 * @access public
1805
	 * @param string $title The title of the page being embedded.
1806
	 * @param array $namespace Which namespaces to search (default: null).
1807
	 * @param int $limit How many results to retrieve (default: null i.e. all).
1808
	 * @return array A list of pages the title is transcluded in.
1809
	 */
1810
	public function embeddedin( $title, $namespace = null, $limit = 50 ) {
1811
		Peachy::deprecatedWarn( 'Wiki::embeddedin()', 'Page::embeddedin()' );
1812
		$page = $this->initPage( $title );
1813
		return $page->embeddedin( $namespace, $limit );
1814
	}
1815
1816
	/**
1817
	 * List change tags enabled on the wiki.
1818
	 *
1819
	 * @access public
1820
	 * @param array $prop Which properties to retrieve (default: array( 'name', 'displayname', 'description', 'hitcount' ) i.e. all).
1821
	 * @param int $limit How many results to retrieve (default: null i.e. all).
1822
	 * @return array The tags retrieved.
1823
	 */
1824
	public function tags( $prop = array( 'name', 'displayname', 'description', 'hitcount' ), $limit = 50 ) {
1825
		$tgArray = array(
1826
			'list'   => 'tags',
1827
			'_code'  => 'tg',
1828
			'tgprop' => implode( '|', $prop ),
1829
			'_limit' => $limit
1830
		);
1831
1832
		Hooks::runHook( 'PreQueryTags', array( &$tgArray ) );
1833
1834
		pecho( "Getting list of all tags...\n\n", PECHO_NORMAL );
1835
1836
		return $this->listHandler( $tgArray );
1837
	}
1838
1839
	/**
1840
	 * @FIXME   Implement this method
1841
	 *
1842
	 * @param null $minor
1843
	 * @param null $bot
1844
	 * @param null $anon
1845
	 * @param null $patrolled
1846
	 * @param null $namespace
1847
	 * @param null $user
1848
	 * @param null $excludeuser
1849
	 * @param null $start
1850
	 * @param null $end
1851
	 * @param array $prop
1852
	 * @param int $limit
1853
	 */
1854
	public function get_watchlist(
1855
		$minor = null,
0 ignored issues
show
Unused Code introduced by
The parameter $minor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1856
		$bot = null,
0 ignored issues
show
Unused Code introduced by
The parameter $bot is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1857
		$anon = null,
0 ignored issues
show
Unused Code introduced by
The parameter $anon is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1858
		$patrolled = null,
0 ignored issues
show
Unused Code introduced by
The parameter $patrolled is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1859
		$namespace = null,
0 ignored issues
show
Unused Code introduced by
The parameter $namespace is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1860
		$user = null,
0 ignored issues
show
Unused Code introduced by
The parameter $user is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1861
		$excludeuser = null,
0 ignored issues
show
Unused Code introduced by
The parameter $excludeuser is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1862
		$start = null,
0 ignored issues
show
Unused Code introduced by
The parameter $start is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1863
		$end = null,
0 ignored issues
show
Unused Code introduced by
The parameter $end is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1864
		$prop = array( 'ids', 'title', 'flags', 'user', 'comment', 'parsedcomment', 'timestamp', 'patrol', 'sizes', 'notificationtimestamp' ),
0 ignored issues
show
Unused Code introduced by
The parameter $prop is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1865
		$limit = 50
0 ignored issues
show
Unused Code introduced by
The parameter $limit is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1866
	) {
1867
1868
		pecho( "Error: " . __METHOD__ . " has not been programmed as of yet.\n\n", PECHO_ERROR );
1869
	}
1870
1871
	/**
1872
	 * @FIXME   Implement this method
1873
	 *
1874
	 * @param null $namespace
1875
	 * @param null $changed
1876
	 */
1877
	public function get_watchlistraw( $namespace = null, $changed = null ) {
0 ignored issues
show
Unused Code introduced by
The parameter $namespace is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $changed is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1878
		pecho( "Error: " . __METHOD__ . " has not been programmed as of yet.\n\n", PECHO_ERROR );
1879
	}
1880
1881
	/**
1882
	 * Returns details of usage of an external URL on the wiki.
1883
	 *
1884
	 * @access public
1885
	 * @param string $url The url to search for links to, without a protocol. * can be used as a wildcard.
1886
	 * @param string $pgProtocol The protocol to accompany the URL. Only certain values are allowed, depending on how $wgUrlProtocols is set on the wiki; by default the allowed values are 'http://', 'https://', 'ftp://', 'irc://', 'gopher://', 'telnet://', 'nntp://', 'worldwind://', 'mailto:' and 'news:'. Default 'http://'.
1887
	 * @param array $prop Properties to return in array form; the options are 'ids', 'title' and 'url'. Default null (all).
1888
	 * @param string $namespace A pipe '|' separated list of namespace numbers to check. Default null (all).
1889
	 * @param int $limit A hard limit on the number of transclusions to fetch. Default null (all).
1890
	 * @return array Details about the usage of that external link on the wiki.
1891
	 */
1892
	public function exturlusage( $url, $pgProtocol = 'http', $prop = array( 'title' ), $namespace = null, $limit = 50 ) {
1893
		$tArray = array(
1894
			'list'       => 'exturlusage',
1895
			'_code'      => 'eu',
1896
			'euquery'    => $url,
1897
			'euprotocol' => $pgProtocol,
1898
			'_limit'     => $limit,
1899
			'euprop'     => implode( '|', $prop )
1900
		);
1901
1902
		if( !is_null( $namespace ) ) {
1903
			$tArray['eunamespace'] = $namespace;
1904
		}
1905
1906
		Hooks::runHook( 'PreQueryExturlusage', array( &$tArray ) );
1907
1908
		pecho( "Getting list of all pages that $url is used in...\n\n", PECHO_NORMAL );
1909
1910
		return $this->listHandler( $tArray );
1911
1912
	}
1913
1914
	/**
1915
	 * @FIXME   Implement this method
1916
	 *
1917
	 * @param array $users
1918
	 * @param array $prop
1919
	 */
1920
	public function users(
1921
		$users = array(),
0 ignored issues
show
Unused Code introduced by
The parameter $users is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1922
		$prop = array( 'blockinfo', 'groups', 'editcount', 'registration', 'emailable', 'gender' )
0 ignored issues
show
Unused Code introduced by
The parameter $prop is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1923
	) {
1924
		pecho( "Error: " . __METHOD__ . " has not been programmed as of yet.\n\n", PECHO_ERROR );
1925
	}
1926
1927
	/**
1928
	 * Returns the titles of some random pages.
1929
	 *
1930
	 * @access public
1931
	 * @param array|string $namespaces Namespaces to select from (default:  array( 0 ) ).
1932
	 * @param int $limit The number of titles to return (default: 1).
1933
	 * @param bool $onlyredirects Only include redirects (true) or only include non-redirects (default; false).
1934
	 * @return array A series of random titles.
1935
	 */
1936
	public function random( $namespaces = array( 0 ), $limit = 1, $onlyredirects = false ) {
1937
		$rnArray = array(
1938
			'_code'       => 'rn',
1939
			'list'        => 'random',
1940
			'rnnamespace' => $namespaces,
1941
			'_limit'      => $limit,
1942
			'rnredirect'  => ( is_null( $onlyredirects ) || !$onlyredirects ) ? null : "true",
1943
			'_lhtitle'    => 'title'
1944
		);
1945
1946
		Hooks::runHook( 'PreQueryRandom', array( &$rnArray ) );
1947
1948
		pecho( "Getting random page...\n\n", PECHO_NORMAL );
1949
1950
		return $this->listHandler( $rnArray );
1951
	}
1952
1953
	/**
1954
	 * @FIXME   Implement this method
1955
	 *
1956
	 * @param array $namespace
1957
	 */
1958
	public function protectedtitles( $namespace = array( 0 ) ) {
0 ignored issues
show
Unused Code introduced by
The parameter $namespace is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1959
1960
		pecho( "Error: " . __METHOD__ . " has not been programmed as of yet.\n\n", PECHO_ERROR );
1961
	}
1962
1963
	/**
1964
	 * Returns meta information about the wiki itself
1965
	 *
1966
	 * @access public
1967
	 * @param array $prop Information to retrieve. Default: array( 'general', 'namespaces', 'namespacealiases', 'specialpagealiases', 'magicwords', 'interwikimap', 'dbrepllag', 'statistics', 'usergroups', 'extensions', 'fileextensions', 'rightsinfo', 'languages' )
1968
	 * @param bool $iwfilter When used with prop 'interwikimap', returns only local or only nonlocal entries of the interwiki map. True = local, false = nonlocal. Default null
1969
	 * @return array
1970
	 */
1971
	public function siteinfo( $prop = array(
1972
		'general', 'namespaces', 'namespacealiases', 'specialpagealiases', 'magicwords', 'interwikimap', 'dbrepllag',
1973
		'statistics', 'usergroups', 'extensions', 'fileextensions', 'rightsinfo', 'languages'
1974
	), $iwfilter = null ) {
1975
1976
		$siArray = array(
1977
			'action' => 'query',
1978
			'meta'   => 'siteinfo',
1979
			'siprop' => implode( '|', $prop ),
1980
		);
1981
1982
		if( in_array( 'interwikimap', $prop ) && $iwfilter ) {
1983
			$siArray['sifilteriw'] = '';
1984
		} elseif( in_array( 'interwikimap', $prop ) && $iwfilter ) $siArray['sifilteriw'] = 'no';
1985
1986
		if( in_array( 'dbrepllag', $prop ) ) $siArray['sishowalldb'] = '';
1987
		if( in_array( 'usergroups', $prop ) ) $siArray['sinumberingroup'] = '';
1988
1989
		Hooks::runHook( 'PreQuerySiteInfo', array( &$siArray ) );
1990
1991
		pecho( "Getting site information...\n\n", PECHO_NORMAL );
1992
1993
		return $this->apiQuery( $siArray );
1994
	}
1995
1996
	/**
1997
	 * Returns a list of system messages (MediaWiki:... pages)
1998
	 *
1999
	 * @access public
2000
	 * @param string $filter Return only messages that contain this string. Default null
2001
	 * @param array $messages Which messages to output. Default array(), which means all.
2002
	 * @param bool $parse Set to true to enable parser, will preprocess the wikitext of message. (substitutes magic words, handle templates etc.) Default false
2003
	 * @param array $args Arguments to be substituted into message. Default array().
2004
	 * @param string $lang Return messages in this language. Default null
2005
	 * @return array
2006
	 */
2007
	public function allmessages( $filter = null, $messages = array(), $parse = false, $args = array(), $lang = null ) {
2008
		$amArray = array(
2009
			'action' => 'query',
2010
			'meta'   => 'allmessages',
2011
		);
2012
2013
		if( !is_null( $filter ) ) $amArray['amfilter'] = $filter;
2014
		if( count( $messages ) ) $amArray['ammessages'] = implode( '|', $messages );
2015
		if( $parse ) $amArray['amenableparser'] = '';
2016
		if( count( $args ) ) $amArray['amargs'] = implode( '|', $args );
2017
		if( !is_null( $lang ) ) $amArray['amlang'] = $lang;
2018
2019
		Hooks::runHook( 'PreQueryAllMessages', array( &$amArray ) );
2020
2021
		pecho( "Getting list of system messages...\n\n", PECHO_NORMAL );
2022
2023
		return $this->apiQuery( $amArray );
2024
	}
2025
2026
	/**
2027
	 * Expand and parse all templates in wikitext
2028
	 *
2029
	 * @access public
2030
	 * @param string $text Text to parse
2031
	 * @param string $title Title to use for expanding magic words, etc. (e.g. {{PAGENAME}}). Default 'API'.
2032
	 * @param bool $generatexml Generate XML parse tree. Default false
2033
	 * @return string
2034
	 */
2035
	public function expandtemplates( $text, $title = null, $generatexml = false ) {
2036
		$etArray = array(
2037
			'action' => 'expandtemplates',
2038
			'text'   => $text
2039
		);
2040
2041
		if( $generatexml ) $etArray['generatexml'] = '';
2042
		if( !is_null( $title ) ) $etArray['title'] = $title;
2043
2044
		Hooks::runHook( 'PreQueryExpandtemplates', array( &$etArray ) );
2045
2046
		pecho( "Parsing templates...\n\n", PECHO_NORMAL );
2047
2048
		$ret = $this->apiQuery( $etArray );
2049
		return $ret['expandtemplates']['*'];
2050
2051
	}
2052
2053
	/**
2054
	 * Parses wikitext and returns parser output
2055
	 *
2056
	 * @access public
2057
	 * @param string $text Wikitext to parse. Default null.
2058
	 * @param string $title Title of page the text belongs to, used for {{PAGENAME}}. Default null.
2059
	 * @param string $summary Summary to parse. Default null.
2060
	 * @param bool $pst Run a pre-save transform, expanding {{subst:}} and ~~~~. Default false.
2061
	 * @param bool $onlypst Run a pre-save transform, but don't parse it. Default false.
2062
	 * @param string $uselang Language to parse in. Default 'en'.
2063
	 * @param array $prop Properties to retrieve. Default array( 'text', 'langlinks', 'categories', 'links', 'templates', 'images', 'externallinks', 'sections', 'revid', 'displaytitle', 'headitems', 'headhtml' )
2064
	 * @param string $page Parse the content of this page. Cannot be used together with $text and $title.
2065
	 * @param string $oldid Parse the content of this revision. Overrides $page and $pageid.
2066
	 * @param string $pageid Parse the content of this page. Overrides page.
2067
	 * @param bool $redirects If the page or the pageid parameter is set to a redirect, resolve it. Default true.
2068
	 * @param string $section Only retrieve the content of this section number. Default null.
2069
	 * @param bool $disablepp Disable the PP Report from the parser output. Defaut false.
2070
	 * @param bool $generatexml Generate XML parse tree (requires prop=wikitext). Default false.
2071
	 * @param string $contentformat Content serialization format used for the input text. Default null.
2072
	 * @param string $contentmodel Content model of the new content. Default null.
2073
	 * @param string $mobileformat Return parse output in a format suitable for mobile devices. Default null.
2074
	 * @param bool $noimages Disable images in mobile output
2075
	 * @param bool $mainpage Apply mobile main page transformations
2076
	 * @return array
2077
	 */
2078
	public function parse( $text = null, $title = null, $summary = null, $pst = false, $onlypst = false, $prop = null, $uselang = 'en', $page = null, $oldid = null, $pageid = null, $redirects = false, $section = null, $disablepp = false, $generatexml = false, $contentformat = null, $contentmodel = null, $mobileformat = null, $noimages = false, $mainpage = false ) {
2079
2080
		if( $prop === null ) {
2081
			$prop = array(
2082
				'text', 'langlinks', 'categories', 'categorieshtml', 'languageshtml', 'links', 'templates', 'images',
2083
				'externallinks', 'sections', 'revid', 'displaytitle', 'headitems', 'headhtml', 'iwlinks', 'wikitext',
2084
				'properties'
2085
			);
2086
		};
2087
2088
		$apiArray = array(
2089
			'action'  => 'parse',
2090
			'uselang' => $uselang,
2091
			'prop'    => implode( '|', $prop ),
2092
		);
2093
2094
		if( $generatexml ) {
2095
			if( !in_array( 'wikitext', $prop ) ) $prop[] = 'wikitext';
2096
			$apiArray['generatexml'] = '';
2097
		}
2098
2099
		if( !is_null( $text ) ) $apiArray['text'] = $text;
2100
		if( !is_null( $title ) ) $apiArray['title'] = $title;
2101
		if( !is_null( $summary ) ) $apiArray['summary'] = $summary;
2102
		if( !is_null( $pageid ) ) $apiArray['pageid'] = $pageid;
2103
		if( !is_null( $page ) ) $apiArray['page'] = $page;
2104
		if( !is_null( $oldid ) ) $apiArray['oldid'] = $oldid;
2105
		if( !is_null( $section ) ) $apiArray['section'] = $section;
2106
		if( !is_null( $contentformat ) ) {
2107
			if( $contentformat == 'text/x-wiki' || $contentformat == 'text/javascript' || $contentformat == 'text/css' || $contentformat == 'text/plain' ) {
2108
				$apiArray['contentformat'] = $contentformat;
2109
			} else pecho( "Error: contentformat not specified correctly.  Omitting value...\n\n", PECHO_ERROR );
2110
		}
2111
		if( !is_null( $contentmodel ) ) {
2112
			if( $contentmodel == 'wikitext' || $contentmodel == 'javascript' || $contentmodel == 'css' || $contentmodel == 'text' || $contentmodel == 'Scribunto' ) {
2113
				$apiArray['contentmodel'] = $contentmodel;
2114
			} else pecho( "Error: contentmodel not specified correctly.  Omitting value...\n\n", PECHO_ERROR );
2115
		}
2116
		if( !is_null( $mobileformat ) ) {
2117
			if( $mobileformat == 'wml' || $mobileformat == 'html' ) {
2118
				$apiArray['mobileformat'] = $mobileformat;
2119
			} else pecho( "Error: mobileformat not specified correctly.  Omitting value...\n\n", PECHO_ERROR );
2120
		}
2121
2122
		if( $pst ) $apiArray['pst'] = '';
2123
		if( $onlypst ) $apiArray['onlypst'] = '';
2124
		if( $redirects ) $apiArray['redirects'] = '';
2125
		if( $disablepp ) $apiArray['disablepp'] = '';
2126
		if( $noimages ) $apiArray['noimages'] = '';
2127
		if( $mainpage ) $apiArray['mainpage'] = '';
2128
2129
		Hooks::runHook( 'PreParse', array( &$apiArray ) );
2130
2131
		pecho( "Parsing...\n\n", PECHO_NORMAL );
2132
2133
		return $this->apiQuery( $apiArray );
2134
	}
2135
2136
	/**
2137
	 * Patrols a page or revision
2138
	 *
2139
	 * @access public
2140
	 * @param int $rcid Recent changes ID to patrol
2141
	 * @return array
2142
	 */
2143
	public function patrol( $rcid = 0 ) {
2144
		Hooks::runHook( 'PrePatrol', array( &$rcid ) );
2145
2146
		pecho( "Patrolling $rcid...\n\n", PECHO_NORMAL );
2147
2148
		$this->get_tokens();
2149
2150
		return $this->apiQuery(
2151
			array(
2152
				'action' => 'patrol',
2153
				'rcid'   => $rcid,
2154
				'token'  => $this->tokens['patrol']
2155
			)
2156
		);
2157
	}
2158
2159
	/**
2160
	 * Import a page from another wiki, or an XML file.
2161
	 *
2162
	 * @access public
2163
	 * @param mixed|string $page local XML file or page to another wiki.
2164
	 * @param string $summary Import summary. Default ''.
2165
	 * @param string $site For interwiki imports: wiki to import from.  Default null.
2166
	 * @param bool $fullhistory For interwiki imports: import the full history, not just the current version.  Default true.
2167
	 * @param bool $templates For interwiki imports: import all included templates as well.  Default true.
2168
	 * @param int $namespace For interwiki imports: import to this namespace
2169
	 * @param bool $root Import as subpage of this page.  Default false.
2170
	 * @return bool
2171
	 */
2172
	public function import( $page = null, $summary = '', $site = null, $fullhistory = true, $templates = true, $namespace = null, $root = false ) {
2173
2174
		$tokens = $this->get_tokens();
2175
2176
		$apiArray = array(
2177
			'action'  => 'import',
2178
			'summary' => $summary,
2179
			'token'   => $tokens['import']
2180
		);
2181
2182
		if( $root ) $apiArray['rootpage'] = '';
2183
2184
		if( !is_null( $page ) ) {
2185
			if( !is_file( $page ) ) {
2186
				$apiArray['interwikipage'] = $page;
2187
				if( !is_null( $site ) ) {
2188
					$apiArray['interwikisource'] = $site;
2189
				} else {
2190
					pecho( "Error: You must specify a site to import from.\n\n", PECHO_FATAL );
2191
					return false;
2192
				}
2193
				if( !is_null( $namespace ) ) $apiArray['namespace'] = $namespace;
2194
				if( $fullhistory ) $apiArray['fullhistory'] = '';
2195
				if( $templates ) $apiArray['templates'] = '';
2196
				pecho( "Importing $page from $site...\n\n", PECHO_NOTICE );
2197
			} else {
2198
				$apiArray['xml'] = "@$page";
2199
				pecho( "Uploading XML...\n\n", PECHO_NOTICE );
2200
			}
2201
		} else {
2202
			pecho( "Error: You must specify an interwiki page or a local XML file to import.\n\n", PECHO_FATAL );
2203
			return false;
2204
		}
2205
2206
		$result = $this->apiQuery( $apiArray, true );
2207
2208
		if( isset( $result['import'] ) ) {
2209
			if( isset( $result['import']['page'] ) ) {
2210
				return true;
2211
			} else {
2212
				pecho( "Import error...\n\n" . print_r( $result['import'], true ) . "\n\n", PECHO_FATAL );
2213
				return false;
2214
			}
2215
		} else {
2216
			pecho( "Import error...\n\n" . print_r( $result, true ), PECHO_FATAL );
2217
			return false;
2218
		}
2219
2220
	}
2221
2222
	public function export() {
2223
		pecho( "Error: " . __METHOD__ . " has not been programmed as of yet.\n\n", PECHO_ERROR );
2224
	}
2225
2226
	/**
2227
	 * Generate a diff between two or three revision IDs
2228
	 *
2229
	 * @access public
2230
	 *
2231
	 * @param string $method Revision method. Options: unified, inline, context, threeway, raw
2232
	 *     (default: 'unified')
2233
	 * @param mixed $rev1
2234
	 * @param mixed $rev2
2235
	 * @param mixed $rev3
2236
	 *
2237
	 * @return string|bool False on failure
2238
	 * @see Diff::load
2239
	 *
2240
	 * @fixme: this uses Diff::load, which has been deprecated and Plugin removed from codebase
2241
	 */
2242
	public function diff( $method = 'unified', $rev1, $rev2, $rev3 = null ) {
2243
		$r1array = array(
2244
			'action' => 'query',
2245
			'prop'   => 'revisions',
2246
			'revids' => $rev1,
2247
			'rvprop' => 'content'
2248
		);
2249
		$r2array = array(
2250
			'action' => 'query',
2251
			'prop'   => 'revisions',
2252
			'revids' => $rev2,
2253
			'rvprop' => 'content'
2254
		);
2255
		$r3array = array(
2256
			'action' => 'query',
2257
			'prop'   => 'revisions',
2258
			'revids' => $rev3,
2259
			'rvprop' => 'content'
2260
		);
2261
2262
		Hooks::runHook( 'PreDiff', array( &$r1array, &$r2array, &$r3array, &$method ) );
2263
2264
		$r1text = $r2text = $r3text = null;
2265
		if( !is_null( $rev3 ) ) {
2266
			pecho( "Getting $method diff of revisions $rev1, $rev2, and $rev3...\n\n", PECHO_NORMAL );
2267
			$r3 = $this->apiQuery( $r3array );
2268
2269
			if( isset( $r3['query']['badrevids'] ) ) {
2270
				pecho( "A bad third revision ID was passed.\n\n", PECHO_FATAL );
2271
				return false;
2272
			}
2273
2274
			foreach( $r3['query']['pages'] as $r3pages ){
2275
				$r3text = $r3pages['revisions'][0]['*'];
2276
			}
2277
		} else {
2278
			pecho( "Getting $method diff of revisions $rev1 and $rev2...\n\n", PECHO_NORMAL );
2279
		}
2280
2281
		$r1 = $this->apiQuery( $r1array );
2282
		$r2 = $this->apiQuery( $r2array );
2283
2284
		if( isset( $r1['query']['badrevids'] ) ) {
2285
			pecho( "A bad first revision ID was passed.\n\n", PECHO_FATAL );
2286
			return false;
2287
		} elseif( isset( $r2['query']['badrevids'] ) ) {
2288
			pecho( "A bad second revision ID was passed.\n\n", PECHO_FATAL );
2289
			return false;
2290
		} else {
2291
			foreach( $r1['query']['pages'] as $r1pages ){
2292
				$r1text = $r1pages['revisions'][0]['*'];
2293
			}
2294
			foreach( $r2['query']['pages'] as $r2pages ){
2295
				$r2text = $r2pages['revisions'][0]['*'];
2296
			}
2297
2298
			if( $method == "raw" ) return array( $r1text, $r2text, $r3text );
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array($r1text, $r2text, $r3text); (array) is incompatible with the return type documented by Wiki::diff of type string|boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
2299
			return Diff::load( $method, $r1text, $r2text, $r3text );
2300
		}
2301
	}
2302
2303
	/**
2304
	 * Regenerate and return edit tokens
2305
	 *
2306
	 * @access public
2307
	 * @param bool $force Whether to force use of the API, not cache.
2308
	 * @return array Edit tokens
2309
	 */
2310
	public function get_tokens( $force = false ) {
2311
		Hooks::runHook( 'GetTokens', array( &$this->tokens ) );
2312
2313
		if( !$force && !empty( $this->tokens ) ) return $this->tokens;
2314
2315
		$tokens = $this->apiQuery(
2316
			array(
2317
				'action' => 'tokens',
2318
				'type'   => 'block|delete|deleteglobalaccount|edit|email|import|move|options|patrol|protect|setglobalaccountstatus|unblock|watch'
2319
			)
2320
		);
2321
2322
		foreach( $tokens['tokens'] as $y => $z ){
2323
			if( in_string( 'token', $y ) ) {
2324
				$this->tokens[str_replace( 'token', '', $y )] = $z;
2325
			}
2326
		}
2327
2328
		$token = $this->apiQuery(
2329
			array(
2330
				'action'  => 'query',
2331
				'list'    => 'users',
2332
				'ususers' => $this->username,
2333
				'ustoken' => 'userrights'
2334
			)
2335
		);
2336
2337
		if( isset( $token['query']['users'][0]['userrightstoken'] ) ) {
2338
			$this->tokens['userrights'] = $token['query']['users'][0]['userrightstoken'];
2339
		} else {
2340
			pecho( "Error retrieving userrights token...\n\n", PECHO_FATAL );
2341
			return array();
2342
		}
2343
2344
		return $this->tokens;
2345
2346
	}
2347
2348
	/**
2349
	 * Returns extensions.
2350
	 *
2351
	 * @access public
2352
	 * @see Wiki::$extensions
2353
	 * @return array Extensions in format name => version
2354
	 */
2355
	public function get_extensions() {
2356
		return $this->extensions;
2357
	}
2358
2359
	/**
2360
	 * Returns an array of the namespaces used on the current wiki.
2361
	 *
2362
	 * @access public
2363
	 * @param bool $force Whether or not to force an update of any cached values first.
2364
	 * @return array The namespaces in use in the format index => local name.
2365
	 */
2366
	public function get_namespaces( $force = false ) {
2367
		if( is_null( $this->namespaces ) || $force ) {
2368
			$tArray = array(
2369
				'meta'   => 'siteinfo',
2370
				'action' => 'query',
2371
				'siprop' => 'namespaces'
2372
			);
2373
			$tRes = $this->apiQuery( $tArray );
2374
2375
			foreach( $tRes['query']['namespaces'] as $namespace ){
2376
				$this->namespaces[$namespace['id']] = $namespace['*'];
2377
				$this->allowSubpages[$namespace['id']] = ( ( isset( $namespace['subpages'] ) ) ? true : false );
2378
			}
2379
		}
2380
		return $this->namespaces;
2381
	}
2382
2383
	/**
2384
	 * Removes the namespace from a title
2385
	 *
2386
	 * @access public
2387
	 * @param string $title Title to remove namespace from
2388
	 * @return string
2389
	 */
2390
2391
	public function removeNamespace( $title ) {
2392
		$this->get_namespaces();
2393
2394
		$exploded = explode( ':', $title, 2 );
2395
2396
		foreach( $this->namespaces as $namespace ){
2397
			if( $namespace == $exploded[0] ) {
2398
				return $exploded[1];
2399
			}
2400
		}
2401
2402
		return $title;
2403
	}
2404
2405
	/**
2406
	 * Returns an array of subpage-allowing namespaces.
2407
	 *
2408
	 * @access public
2409
	 * @param bool $force Whether or not to force an update of any cached values first.
2410
	 * @return array An array of namespaces that allow subpages.
2411
	 */
2412
	public function get_allow_subpages( $force = false ) {
2413
		if( is_null( $this->allowSubpages ) || $force ) {
2414
			$this->get_namespaces( true );
2415
		}
2416
		return $this->allowSubpages;
2417
	}
2418
2419
	/**
2420
	 * Returns a boolean equal to whether or not the namespace with index given allows subpages.
2421
	 *
2422
	 * @access public
2423
	 * @param int $namespace The namespace that might allow subpages.
2424
	 * @return bool Whether or not that namespace allows subpages.
2425
	 */
2426
	public function get_ns_allows_subpages( $namespace = 0 ) {
2427
		$this->get_allow_subpages();
2428
2429
		return (bool)$this->allowSubpages[$namespace];
2430
	}
2431
2432
	/**
2433
	 * Returns user rights.
2434
	 *
2435
	 * @access public
2436
	 * @see Wiki::$userRights
2437
	 * @return array Array of user rights
2438
	 */
2439
	public function get_userrights() {
2440
		return $this->userRights;
2441
	}
2442
2443
	/**
2444
	 * Returns all the pages which start with a certain prefix, shortcut for {@link Wiki}::{@link allpages}()
2445
	 *
2446
	 * @param string $prefix Prefix to search for
2447
	 * @param array $namespace Namespace IDs to search in. Default array( 0 )
2448
	 * @param int $limit A hard limit on the number of pages to fetch. Default null (all).
2449
	 * @return array Titles of pages that transclude this page
2450
	 */
2451
	public function prefixindex( $prefix = null, $namespace = array( 0 ), $limit = 50 ) {
2452
		return $this->allpages( $namespace, $prefix, null, 'all', null, null, array(), array(), 'ascending', 'all', $limit );
2453
	}
2454
2455
	/**
2456
	 * Returns an instance of the Page class as specified by $title or $pageid
2457
	 *
2458
	 * @access public
2459
	 * @param mixed $title Title of the page (default: null)
2460
	 * @param mixed $pageid ID of the page (default: null)
2461
	 * @param bool $followRedir Should it follow a redirect when retrieving the page (default: true)
2462
	 * @param bool $normalize Should the class automatically normalize the title (default: true)
2463
	 * @param string $timestamp Timestamp of a reference point in the program.  Used to detect edit conflicts.
2464
	 * @return Page
2465
	 * @package initFunctions
2466
	 */
2467
	public function &initPage( $title = null, $pageid = null, $followRedir = true, $normalize = true, $timestamp = null ) {
2468
		$page = new Page( $this, $title, $pageid, $followRedir, $normalize, $timestamp );
2469
		return $page;
2470
	}
2471
2472
	/**
2473
	 * Returns an instance of the User class as specified by $pgUsername
2474
	 *
2475
	 * @access public
2476
	 * @param mixed $pgUsername Username
2477
	 * @return User
2478
	 * @package initFunctions
2479
	 */
2480
	public function &initUser( $pgUsername ) {
2481
		return new User( $this, $pgUsername );
2482
	}
2483
2484
	/**
2485
	 * Returns an instance of the Image class as specified by $filename or $pageid
2486
	 *
2487
	 * @access public
2488
	 * @param string $filename Filename
2489
	 * @param int $pageid Page ID of image
2490
	 * @param array $prop Informatation to set. Default array( 'timestamp', 'user', 'comment', 'url', 'size', 'dimensions', 'sha1', 'mime', 'metadata', 'archivename', 'bitdepth' )
0 ignored issues
show
Bug introduced by
There is no parameter named $prop. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
2491
	 * @return Image
2492
	 * @package initFunctions
2493
	 */
2494
	public function &initImage( $filename = null, $pageid = null ) {
2495
		$image = new Image( $this, $filename, $pageid );
2496
		return $image;
2497
	}
2498
2499
	/**
2500
	 * Get the difference between 2 pages
2501
	 *
2502
	 * @access public
2503
	 * @param string $fromtitle First title to compare
2504
	 * @param string $fromid First page ID to compare
2505
	 * @param string $fromrev First revision to compare
2506
	 * @param string $totitle Second title to compare
2507
	 * @param string $toid Second page ID to compare
2508
	 * @param string $torev Second revision to compare
2509
	 * @return array
2510
	 */
2511
	public function compare( $fromtitle = null, $fromid = null, $fromrev = null, $totitle = null, $toid = null, $torev = null ) {
2512
2513
		pecho( "Getting differences...\n\n", PECHO_NORMAL );
2514
		$apiArray = array(
2515
			'action' => 'compare'
2516
		);
2517
		if( !is_null( $fromrev ) ) {
2518
			$apiArray['fromrev'] = $fromrev;
2519
		} else {
2520
			if( !is_null( $fromid ) ) {
2521
				$apiArray['fromid'] = $fromid;
2522
			} else {
2523
				if( !is_null( $fromtitle ) ) {
2524
					$apiArray['fromtitle'] = $fromtitle;
2525
				} else {
2526
					pecho( "Error: a from parameter must be specified.\n\n", PECHO_FATAL );
2527
					return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Wiki::compare of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
2528
				}
2529
			}
2530
		}
2531
		if( !is_null( $torev ) ) {
2532
			$apiArray['torev'] = $torev;
2533
		} else {
2534
			if( !is_null( $toid ) ) {
2535
				$apiArray['toid'] = $toid;
2536
			} else {
2537
				if( !is_null( $totitle ) ) {
2538
					$apiArray['totitle'] = $totitle;
2539
				} else {
2540
					pecho( "Error: a to parameter must be specified.\n\n", PECHO_FATAL );
2541
					return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Wiki::compare of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
2542
				}
2543
			}
2544
		}
2545
		$results = $this->apiQuery( $apiArray );
2546
2547
		if( isset( $results['compare']['*'] ) ) {
2548
			return $results['compare']['*'];
2549
		} else {
2550
			pecho( "Compare failure... Please check your parameters.\n\n", PECHO_FATAL );
2551
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Wiki::compare of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
2552
		}
2553
	}
2554
2555
	/**
2556
	 * Search the wiki using the OpenSearch protocol.
2557
	 *
2558
	 * @access public
2559
	 * @param string $text Search string.  Default empty.
2560
	 * @param int $limit Maximum amount of results to return. Default 10.
2561
	 * @param array $namespaces Namespaces to search.  Default array(0).
2562
	 * @param bool $suggest Do nothing if $wgEnableOpenSearchSuggest is false. Default true.
2563
	 * @return array
2564
	 */
2565
	public function opensearch( $text = '', $limit = 10, $namespaces = array( 0 ), $suggest = true ) {
2566
2567
		$apiArray = array(
2568
			'search'    => $text,
2569
			'action'    => 'opensearch',
2570
			'limit'     => $limit,
2571
			'namespace' => implode( '|', $namespaces )
2572
		);
2573
		if( $suggest ) $apiArray['suggest'] = '';
2574
2575
		$OSres = $this->get_http()->get( $this->get_base_url(), $apiArray );
2576
		return ( $OSres === false || is_null( $OSres ) ? false : json_decode( $OSres, true ) );
2577
2578
	}
2579
2580
	/**
2581
	 * Export an RSD (Really Simple Discovery) schema.
2582
	 *
2583
	 * @access public
2584
	 * @return array
2585
	 */
2586
	public function rsd() {
2587
2588
		$apiArray = array(
2589
			'action' => 'rsd'
2590
		);
2591
2592
		$OSres = $this->get_http()->get( $this->get_base_url(), $apiArray );
2593
		return ( $OSres === false || is_null( $OSres ) ? false : XMLParse::load( $OSres ) );
2594
	}
2595
2596
	/**
2597
	 * Change preferences of the current user.
2598
	 *
2599
	 * @access public
2600
	 * @param bool $reset Resets preferences to the site defaults. Default false.
2601
	 * @param array|string $resetoptions List of types of options to reset when the "reset" option is set. Default 'all'.
2602
	 * @param array|string $changeoptions PartList of changes, formatted name=value (e.g. skin=vector), value cannot contain pipe characters. If no value is given (not even an equals sign), e.g., optionname|otheroption|..., the option will be reset to its default value. Default empty.
2603
	 * @param string $optionname A name of a option which should have an optionvalue set. Default null.
2604
	 * @param string $optionvalue A value of the option specified by the optionname, can contain pipe characters. Default null.
2605
	 * @return boolean
2606
	 */
2607
	public function options( $reset = false, $resetoptions = array( 'all' ), $changeoptions = array(), $optionname = null, $optionvalue = null ) {
2608
		$this->get_tokens();
2609
		$apiArray = array(
2610
			'action' => 'options',
2611
			'token'  => $this->tokens['options']
2612
		);
2613
2614
		if( $reset ) {
2615
			$apiArray['reset'] = '';
2616
			$apiArray['resetkinds'] = implode( '|', $resetoptions );
2617
		}
2618
2619
		if( !empty( $changeoptions ) ) $apiArray['change'] = implode( '|', $changeoptions );
2620
2621
		if( !is_null( $optionname ) ) $apiArray['optionname'] = $optionname;
2622
		if( !is_null( $optionvalue ) ) $apiArray['optionvalue'] = $optionvalue;
2623
2624
		$result = $this->apiQuery( $apiArray, true );
2625
		if( isset( $result['options'] ) && $result['options'] == 'success' ) {
2626
			if( isset( $result['warnings'] ) ) pecho( "Options set successfully, however a warning was thrown:\n" . print_r( $result['warnings'], true ), PECHO_WARN );
2627
			return true;
2628
		} else {
2629
			pecho( "Options error...\n\n" . print_r( $result, true ), PECHO_FATAL );
2630
			return false;
2631
		}
2632
	}
2633
2634
	/**
2635
	 * Returns the SSH object
2636
	 *
2637
	 * @access public
2638
	 * @return Object
2639
	 */
2640
	public function getSSH() {
2641
		return $this->SSH;
2642
	}
2643
2644
	/**
2645
	 * Performs nobots checking, new message checking, etc
2646
	 *
2647
	 * @param       string $action Name of action.
2648
	 * @param       null|string $title Name of page to check for nobots
2649
	 * @param       null $pageidp
2650
	 *
2651
	 * @throws      AssertFailure
2652
	 * @throws      EditError
2653
	 * @throws      LoggedOut
2654
	 * @throws      MWAPIError
2655
	 * @access      public
2656
	 */
2657
	public function preEditChecks( $action = "Edit", $title = null, $pageidp = null ) {
2658
		global $pgDisablechecks, $pgMasterrunpage;
2659
		if( $pgDisablechecks ) return;
2660
		$preeditinfo = array(
2661
			'action' => 'query',
2662
			'meta'   => 'userinfo',
2663
			'uiprop' => 'hasmsg|blockinfo',
2664
			'prop'   => 'revisions',
2665
			'rvprop' => 'content'
2666
		);
2667
2668
		if( !is_null( $this->get_runpage() ) ) {
2669
			$preeditinfo['titles'] = $this->get_runpage();
2670
		}
2671
		if( !is_null( $title ) ) {
2672
			$preeditinfo['titles'] = ( !is_null( $this->get_runpage() ) ? $preeditinfo['titles'] . "|" : "" ) . $title;
2673
		}
2674
2675
		$preeditinfo = $this->apiQuery( $preeditinfo );
2676
2677
		$messages = false;
2678
		$blocked = false;
2679
		$oldtext = '';
2680
		$runtext = 'enable';
2681
		if( isset( $preeditinfo['query']['pages'] ) ) {
2682
			//$oldtext = $preeditinfo['query']['pages'][$this->pageid]['revisions'][0]['*'];
2683
			foreach( $preeditinfo['query']['pages'] as $pageid => $page ){
2684
				if( $pageid == $pageidp ) {
2685
					$oldtext = $page['revisions'][0]['*'];
2686
				} elseif( $pageid == "-1" ) {
2687
					if( $page['title'] == $this->get_runpage() ) {
2688
						pecho( "$action failed, enable page does not exist.\n\n", PECHO_WARN );
2689
						throw new EditError( "Enablepage", "Enable  page does not exist." );
2690
					} else {
2691
						$oldtext = '';
2692
					}
2693
				} else {
2694
					$runtext = $page['revisions'][0]['*'];
2695
				}
2696
			}
2697
			if( isset( $preeditinfo['query']['userinfo']['messages'] ) ) $messages = true;
2698
			if( isset( $preeditinfo['query']['userinfo']['blockedby'] ) ) $blocked = true;
2699
		}
2700
2701
		//Perform nobots checks, login checks, /Run checks
2702
		if( checkExclusion( $this, $oldtext, $this->get_username(), $this->get_optout(), $this->nobotsTaskname ) && $this->get_nobots() ) {
2703
			throw new EditError( "Nobots", "The page has a nobots template" );
2704
		}
2705
2706
		if( !is_null( $pgMasterrunpage ) && !preg_match( '/enable|yes|run|go|true/i', $this->initPage( $pgMasterrunpage )->get_text() ) ) {
2707
			throw new EditError( "Enablepage", "Script was disabled by Master Run page" );
2708
		}
2709
2710
		if( !is_null( $this->get_runpage() ) && !preg_match( '/enable|yes|run|go|true/i', $runtext ) ) {
2711
			throw new EditError( "Enablepage", "Script was disabled by Run page" );
2712
		}
2713
2714
		if( $messages && $this->get_stoponnewmessages() ) {
2715
			throw new EditError( "NewMessages", "User has new messages" );
2716
		}
2717
2718
		if( $blocked ) {
2719
			throw new EditError( "Blocked", "User has been blocked" );
2720
		}
2721
	}
2722
}
2723