Completed
Pull Request — master (#103)
by
unknown
02:36
created

Wiki::resolveTitle()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
ccs 0
cts 3
cp 0
rs 9.4285
cc 1
eloc 4
nc 1
nop 2
crap 2
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
				'lgpassword' => $configuration['password'],
432
				'action'     => 'login',
433
			);
434
435
			if( !is_null( $token ) ) {
436
				$lgarray['lgtoken'] = $token;
437
			}
438
		} else {
439
			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...
440
			if( !isset( $configuration['consumerkey'] ) ) throw new LoginError( array( "Missing consumer key", "set consumerkey in cfg file" ) );
441
			if( !isset( $configuration['consumersecret'] ) ) throw new LoginError( array( "Missing consumer secret", "set consumersecret in cfg file" ) );
442
			if( !isset( $configuration['accesstoken'] ) ) throw new LoginError( array( "Missing access token", "set accesstoken in cfg file" ) );
443
			if( !isset( $configuration['accesssecret'] ) ) throw new LoginError( array( "Missing access token secret", "set accesssecret in cfg file" ) );
444
			if( !isset( $configuration['oauthurl'] ) ) throw new LoginError( array( "Missing OAuth URL", "set oauthurl in cfg file" ) );
445
			$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...
446
			$this->consumerKey = $configuration['consumerkey'];
447
			$this->consumerSecret = $configuration['consumersecret'];
448
			$this->accessToken = $configuration['accesstoken'];
449
			$this->accessTokenSecret = $configuration['accesssecret'];
450
			$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...
451
		}
452
		
453
		if( isset( $configuration['maxlag'] ) && $configuration['maxlag'] != "0" ) {
454
			$this->maxlag = $configuration['maxlag'];
455
			$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...
456
		}	
457
		
458
		// FIXME:   Why is there a return in a constructor? Should an error be thrown?
459
		if( isset( $configuration['nologin'] ) ) {
460
			$this->nologin = true;
461
			return;
462
		}
463
		
464
		$use_cookie_login = false;
465
		if( isset( $configuration['cookiejar'] ) ) {
466
			$this->http->setCookieJar( $configuration['cookiejar'] );
467
		} else {
468
469
			$this->http->setCookieJar( sys_get_temp_dir() . '/PeachyCookieSite' . sha1( $configuration['encodedparams'] ) );
470
471
			if( $this->is_logged_in() ) $use_cookie_login = true;
472
		}	
473
474
		if( $use_cookie_login ) {
475
			pecho( "Logging in to {$this->base_url} as {$this->username}, using a saved login cookie\n\n", PECHO_NORMAL );
476
477
			$this->runSuccess( $configuration );
478
		} elseif( !$this->nologin ) {
479
			Hooks::runHook( 'PreLogin', array( &$lgarray ) );
480
481
			if( !$recursed ) {
482
				pecho( "Logging in to {$this->base_url}...\n\n", PECHO_NOTICE );
483
			}
484
485
			if( isset( $configuration['method'] ) && $configuration['method'] == 'legacy' ) $loginRes = $this->apiQuery( $lgarray, true, true, false, false );
486
			else {
487
				$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...
488
				if ( !$loginRes ) {
489
					throw new LoginError( ( array( 'BadData', 'The server returned unparsable data' ) ) );
490
	            }
491
	            $err = json_decode( $loginRes );
492
	            if ( is_object( $err ) && isset( $err->error ) && $err->error === 'mwoauthdatastore-access-token-not-found' ) {
493
	                // We're not authorized!
494
	                throw new LoginError( ( array( 'AuthFailure', 'Missing authorization or authorization failed' ) ) );
495
	            }
496
497
	            // There are three fields in the response
498
	            $fields = explode( '.', $loginRes );
499
	            if ( count( $fields ) !== 3 ) {
500
	                throw new LoginError( ( array( 'BadResponse', 'Invalid identify response: ' . htmlspecialchars( $loginRes ) ) ) );
501
	            }
502
503
	            // Validate the header. MWOAuth always returns alg "HS256".
504
	            $header = base64_decode( strtr( $fields[0], '-_', '+/' ), true );
505
	            if ( $header !== false ) {
506
	                $header = json_decode( $header );
507
	            }
508
	            if ( !is_object( $header ) || $header->typ !== 'JWT' || $header->alg !== 'HS256' ) {
509
	                throw new LoginError( ( array( 'BadHeader', 'Invalid header in identify response: ' . htmlspecialchars( $loginRes ) ) ) );
510
	            }
511
512
	            // Verify the signature
513
	            $sig = base64_decode( strtr( $fields[2], '-_', '+/' ), true );
514
	            $check = hash_hmac( 'sha256', $fields[0] . '.' . $fields[1], $this->consumerSecret, true );
515
	            if ( $sig !== $check ) {
516
	                throw new LoginError( ( array( 'BadSignature', 'JWT signature validation failed: ' . htmlspecialchars( $loginRes ) ) ) );
517
	            }
518
519
	            // Decode the payload
520
	            $payload = base64_decode( strtr( $fields[1], '-_', '+/' ), true );
521
	            if ( $payload !== false ) {
522
	                $payload = json_decode( $payload );
523
	            }
524
	            if ( !is_object( $payload ) ) {
525
	                throw new LoginError( ( array( 'BadPayload', 'Invalid payload in identify response: ' . htmlspecialchars( $loginRes ) ) ) );
526
	            }
527
	            
528
	            pecho( "Successfully logged in to {$this->base_url} as {$payload->username}\n\n", PECHO_NORMAL );
529
530
				$this->runSuccess( $configuration );
531
			}
532
533
			Hooks::runHook( 'PostLogin', array( &$loginRes ) );
534
		}
535
536
		if( !$this->oauthEnabled && isset( $loginRes['login']['result'] ) ) {
537
			switch( $loginRes['login']['result'] ){
538
				case 'NoName':
539
					throw new LoginError( array( 'NoName', 'Username not specified' ) );
540
				case 'Illegal':
541
					throw new LoginError( array( 'Illegal', 'Username with illegal characters specified' ) );
542
				case 'NotExists':
543
					throw new LoginError( array( 'NotExists', 'Username specified does not exist' ) );
544
				case 'EmptyPass':
545
					throw new LoginError( array( 'EmptyPass', 'Password not specified' ) );
546
				case 'WrongPass':
547
					throw new LoginError( array( 'WrongPass', 'Incorrect password specified' ) );
548
				case 'WrongPluginPass':
549
					throw new LoginError( array( 'WrongPluginPass', 'Incorrect password specified' ) );
550
				case 'CreateBlocked':
551
					throw new LoginError( array( 'CreateBlocked', 'IP address has been blocked' ) );
552
				case 'Throttled':
553
					if( $recursed > 2 ) {
554
						throw new LoginError( array(
555
							'Throttled', 'Login attempts have been throttled'
556
						) );
557
					}
558
559
					$wait = $loginRes['login']['wait'];
560
					pecho( "Login throttled, waiting $wait seconds.\n\n", PECHO_NOTICE );
561
					sleep( $wait );
562
563
					$recres = $this->__construct( $configuration, $this->extensions, $recursed + 1 );
564
					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...
565
				case 'Blocked':
566
					throw new LoginError( array( 'Blocked', 'User specified has been blocked' ) );
567
				case 'NeedToken':
568
					if( $recursed > 2 ) throw new LoginError( array( 'NeedToken', 'Token was not specified' ) );
569
570
					$token = $loginRes['login']['token'];
571
572
					$recres = $this->__construct( $configuration, $this->extensions, $recursed + 1, $token );
573
					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...
574
				case 'Success':
575
					pecho( "Successfully logged in to {$this->base_url} as {$this->username}\n\n", PECHO_NORMAL );
576
577
					$this->runSuccess( $configuration );
578
			}
579
		}
580
	}
581
582
	public function is_logged_in() {
583
		$cookieInfo = $this->apiQuery( array( 'action' => 'query', 'meta' => 'userinfo' ) );
584
		if( $cookieInfo['query']['userinfo']['id'] != 0 ) return true;
585
		return false;
586
	}
587
588
589
	/**
590
	 * runSuccess function.
591
	 *
592
	 * @access protected
593
	 * @param mixed &$configuration
594
	 * @return void
595
	 */
596
	protected function runSuccess( &$configuration ) {
597
		$userInfoRes = $this->apiQuery(
598
			array(
599
				'action' => 'query',
600
				'meta'   => 'userinfo',
601
				'uiprop' => 'blockinfo|rights|groups'
602
			)
603
		);
604
605
		if( in_array( 'apihighlimits', $userInfoRes['query']['userinfo']['rights'] ) ) {
606
			$this->apiQueryLimit = 4999;
607
		} else {
608
			$this->apiQueryLimit = 499;
609
		}
610
611
		$this->userRights = $userInfoRes['query']['userinfo']['rights'];
612
613
		if( in_array( 'bot', $userInfoRes['query']['userinfo']['groups'] ) ) {
614
			$this->isFlagged = true;
615
		}
616
617
		$this->get_tokens();
618
619
		$this->configuration = $configuration;
620
		unset( $this->configuration['password'] );
621
	}
622
623
	/**
624
	 * Logs the user out of the wiki.
625
	 *
626
	 * @access public
627
	 * @return void
628
	 */
629
	public function logout() {
630
		pecho( "Logging out of {$this->base_url}...\n\n", PECHO_NOTICE );
631
632
		$this->apiQuery( array( 'action' => 'logout' ), true );
633
634
	}
635
636
	/**
637
	 * Sets a specific runpage for a script.
638
	 *
639
	 * @param string $page Page to set as the runpage. Default null.
640
	 * @access public
641
	 * @return void
642
	 */
643
	public function set_runpage( $page = null ) {
644
		$this->runpage = $page;
645
	}
646
    
647
    /**
648
     * Sets a specific taskname to comply with the nobots template.
649
     *
650
     * @param string $taskname Name of bot task. Default null.
651
     * @access public
652
     * @return void
653
     */
654
    public function set_taskname( $taskname = null ) {
655
        $this->nobotsTaskname = $taskname;
656
    }
657
    
658
    private function generateSignature( $method, $url, $params = array() ) {
659
	    $parts = parse_url( $url );
660
661
	    // We need to normalize the endpoint URL
662
	    $scheme = isset( $parts['scheme'] ) ? $parts['scheme'] : 'http';
663
	    $host = isset( $parts['host'] ) ? $parts['host'] : '';
664
	    $port = isset( $parts['port'] ) ? $parts['port'] : ( $scheme == 'https' ? '443' : '80' );
665
	    $path = isset( $parts['path'] ) ? $parts['path'] : '';
666
	    if ( ( $scheme == 'https' && $port != '443' ) ||
667
	        ( $scheme == 'http' && $port != '80' ) 
668
	    ) {
669
	        // Only include the port if it's not the default
670
	        $host = "$host:$port";
671
	    }
672
673
	    // Also the parameters
674
	    $pairs = array();
675
	    parse_str( isset( $parts['query'] ) ? $parts['query'] : '', $query );
676
	    $query += $params;
677
	    unset( $query['oauth_signature'] );
678
	    if ( $query ) {
679
	        $query = array_combine(
680
	            // rawurlencode follows RFC 3986 since PHP 5.3
681
	            array_map( 'rawurlencode', array_keys( $query ) ),
682
	            array_map( 'rawurlencode', array_values( $query ) )
683
	        );
684
	        ksort( $query, SORT_STRING );
685
	        foreach ( $query as $k => $v ) {
686
	            $pairs[] = "$k=$v";
687
	        }
688
	    }
689
690
	    $toSign = rawurlencode( strtoupper( $method ) ) . '&' .
691
	        rawurlencode( "$scheme://$host$path" ) . '&' .
692
	        rawurlencode( join( '&', $pairs ) );
693
	    $key = rawurlencode( $this->consumerSecret ) . '&' . rawurlencode( $this->accessTokenSecret );
694
	    return base64_encode( hash_hmac( 'sha1', $toSign, $key, true ) );
695
	}
696
697
	/**
698
	 * Queries the API.
699
	 *
700
	 * @access public
701
	 * @param array $arrayParams Parameters given to query with (default: array())
702
	 * @param bool $post Should it be a POST request? (default: false)
703
	 * @param bool $errorcheck
704
	 * @param bool $recursed Is this a recursed request (default: false)
705
	 * @param bool $assertcheck Use MediaWiki's assert feature to prevent unwanted edits (default: true)
706
	 * @throws LoggedOut
707
	 * @throws AssertFailure (see $assertcheck)
708
	 * @throws MWAPIError (API unavailable)
709
	 * @return array|bool Returns an array with the API result
710
	 */
711
	public function apiQuery( $arrayParams = array(), $post = false, $errorcheck = true, $recursed = false, $assertcheck = true, $talktoOauth = false ) {
712
713
		global $pgIP, $pgMaxAttempts, $pgThrowExceptions, $pgDisplayGetOutData, $pgLogSuccessfulCommunicationData, $pgLogFailedCommunicationData, $pgLogGetCommunicationData, $pgLogPostCommunicationData, $pgLogCommunicationData, $pgLogAPIError;
714
		$requestid = mt_rand();
715
		$header = "";
716
		if( $talktoOauth === false ) {
717
			$arrayParams['format'] = 'php';
718
			$arrayParams['servedby'] = '';
719
			$arrayParams['requestid'] = $requestid;
720
			$assert = false;	
721
		}
722
		$attempts = $pgMaxAttempts;
723
724
		if( !file_exists( $pgIP . 'Includes/Communication_Logs' ) ) mkdir( $pgIP . 'Includes/Communication_Logs', 02775 );
725
		if( $post && $this->requiresFlag && $assertcheck ) {
726
			$arrayParams['assert'] = 'bot';
727
			$assert = true;
728
			Hooks::runHook( 'QueryAssert', array( &$arrayParams['assert'], &$assert ) );
729
		} elseif( $post && !$this->allowLoggedOutEditing && $assertcheck ) {
730
			$arrayParams['assert'] = 'user';
731
			$assert = true;
732
			Hooks::runHook( 'QueryAssert', array( &$arrayParams['assert'], &$assert ) );
733
		} elseif( isset( $arrayParams['assert'] ) ) unset( $arrayParams['assert'] );
734
735
		pecho( "Running API query with params " . implode( ";", $arrayParams ) . "...\n\n", PECHO_VERBOSE ); 
736
737
		if( $post ) {
738
			$is_loggedin = $this->get_nologin();
739
			Hooks::runHook( 'APIQueryCheckLogin', array( &$is_loggedin ) );
740
			if( $is_loggedin && $errorcheck ) throw new LoggedOut();
741
742
			Hooks::runHook( 'PreAPIPostQuery', array( &$arrayParams ) );
743
			for( $i = 0; $i < $attempts; $i++ ){
744
				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...
745
					$headerArr = array(
746
                                // OAuth information
747
		                                'oauth_consumer_key' => $this->consumerKey,
748
		                                'oauth_token' => $this->accessToken,
749
		                                'oauth_version' => '1.0',
750
		                                'oauth_nonce' => md5( microtime() . mt_rand() ),
751
		                                'oauth_timestamp' => time(),
752
753
		                                // We're using secret key signatures here.
754
		                                'oauth_signature_method' => 'HMAC-SHA1',
755
		                            );
756
		            $signature = $this->generateSignature( 'POST', ( $talktoOauth ? $talktoOauth : $this->base_url ), $headerArr  );
757
		            $headerArr['oauth_signature'] = $signature; 
758
759
		            $header = array();
760
		            foreach ( $headerArr as $k => $v ) {
761
		                $header[] = rawurlencode( $k ) . '="' . rawurlencode( $v ) . '"';
762
		            }
763
		            $header = 'Authorization: OAuth ' . join( ', ', $header );
764
		            unset( $headerArr );
765
				}
766
				$logdata = "Date/Time: " . date( 'r' ) . "\nMethod: POST\nURL: {$this->base_url} (Parameters masked for security)\nRaw Data: ";
767
				$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 772 which is incompatible with the return type documented by Wiki::apiQuery of type array|boolean.
Loading history...
768
					( $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...
769
					$arrayParams,
770
					$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...
771
				);
772
				if( $talktoOauth !== false && $data !== false ) return $data;
773
				$logdata .= $data;
774
				$data2 = ( $data === false || is_null( $data ) ? false : unserialize( $data ) );
775
				if( $data2 === false && serialize( $data2 ) != $data ) {
776
					$logdata .= "\nUNSERIALIZATION FAILED\n\n";
777
					if( $pgLogFailedCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Faileddata.log', $logdata, FILE_APPEND );
778
				} else {
779
					$logdata .= "\nUNSERIALIZATION SUCCEEDED\n\n";
780
					if( $pgLogSuccessfulCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Succeededdata.log', $logdata, FILE_APPEND );
781
				}
782
783
				if( $pgLogPostCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Postdata.log', $logdata, FILE_APPEND );
784
				if( $pgLogCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Querydata.log', $logdata, FILE_APPEND );
785
786
				$data = $data2;
787
				unset( $data2 );
788
				if( isset( $data['error'] ) && $data['error']['code'] == 'badtoken' ) {
789
					pecho( "API Error...\n\nBadtoken detected retrying with new tokens...\n\n", PECHO_WARN );
790
					$tokens = $this->get_tokens( true );
791
					$arrayParams['token'] = $tokens[$arrayParams['action']];
792
					continue;
793
				}
794
				if( $this->get_http()->get_HTTP_code() == 503 && $errorcheck ) {
795
					pecho( "API Error...\n\nCode: error503\nText: HTTP Error 503\nThe webserver's service is currently unavailable", PECHO_WARN );
796
					$tempSetting = $pgDisplayGetOutData;
797
					$pgDisplayGetOutData = false;
798
					$histemp = $this->initPage( $arrayParams['title'] )->history( 1 );
799
					if( $arrayParams['action'] == 'edit' && $histemp[0]['user'] == $this->get_username() && $histemp[0]['comment'] == $arrayParams['summary'] && strtotime( $histemp[0]['timestamp'] ) - time() < 120 ) {
800
						pecho( ", however, the edit appears to have gone through.\n\n", PECHO_WARN );
801
						$pgDisplayGetOutData = $tempSetting;
802
						unset( $tempSetting );
803
						return array( 'edit' => array( 'result' => 'Success', 'newrevid' => $histemp[0]['revid'] ) );
804
					} else {
805
						pecho( ", retrying...\n\n", PECHO_WARN );
806
						$pgDisplayGetOutData = $tempSetting;
807
						unset( $tempSetting );
808
						continue;
809
					}
810
				}
811
812
				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...
813
				if( isset( $data['error'] ) && isset( $data['error']['code'] ) && $assert && $errorcheck ) {
814
					if( $data['error']['code'] == 'assertbotfailed' && $pgThrowExceptions ) {
815
						throw new AssertFailure( 'bot' );
816
					}
817
					if( $data['error']['code'] == 'assertuserfailed' && $pgThrowExceptions ) {
818
						throw new AssertFailure( 'user' );
819
					}
820
					if( $data['error']['code'] == 'assertbotfailed' && !$pgThrowExceptions ) {
821
						if( $this->is_logged_in() ) {
822
							pecho( "Assertion Failure: This user does not have the bot flag.  Waiting for bot flag...\n\n", PECHO_FATAL );
823
							$this->isFlagged = false;
824
							return false;
825
						} else {
826
							$data['error']['code'] = 'assertuserfailed'; //If we are logged out, log back in.
827
						}
828
					}
829
					if( $data['error']['code'] == 'assertuserfailed' && !$pgThrowExceptions ) {
830
						pecho( "Assertion Failure: This user has logged out.  Logging back in...\n\n", PECHO_FATAL );
831
						$this->logout();
832
						$this->__construct( $this->cached_config['config'], $this->cached_config['extensions'], 0, null );
833
						$this->get_tokens( true );
834
					}
835
				}
836
				if( !isset( $data['servedby'] ) && !isset( $data['requestid'] ) ) {
837
					pecho( "Warning: API is not responding, retrying...\n\n", PECHO_WARN );
838
				} else break;
839
			}
840
			if( $this->get_http()->get_HTTP_code() == 503 && $errorcheck ) {
841
				pecho( "API Error...\n\nCode: error503\nText: HTTP Error 503\nThe webserver's service is currently unavailable", PECHO_WARN );
842
				$tempSetting = $pgDisplayGetOutData;
843
				$pgDisplayGetOutData = false;
844
				$histemp = $this->initPage( $arrayParams['title'] )->history( 1 );
845
				if( $arrayParams['action'] == 'edit' && $histemp['user'] == $this->get_username() && $histemp['comment'] == $arrayParams['summary'] && strtotime( $histemp['timestamp'] ) - time() < 120 ) {
846
					pecho( ", however, the edit, finally, appears to have gone through.\n\n", PECHO_WARN );
847
					$pgDisplayGetOutData = $tempSetting;
848
					return array( 'edit' => array( 'result' => 'Success', 'newrevid' => $histemp['revid'] ) );
849
				} else {
850
					$pgDisplayGetOutData = $tempSetting;
851
					if( $pgThrowExceptions ) {
852
						pecho( ".  Terminating program.\n\n", PECHO_FATAL );
853
						throw new MWAPIError( array(
854
							'code' => 'error503', 'info' => 'nThe webserver\'s service is currently unavailable'
855
						) );
856
					} else {
857
						pecho( ".  Aborting attempts.", PECHO_FATAL );
858
						return false;
859
					}
860
				}
861
			}
862
863
			if( !isset( $data['servedby'] ) && !isset( $data['requestid'] ) ) {
864
				if( $pgThrowExceptions ) {
865
					pecho( "Fatal Error: API is not responding.  Terminating program.\n\n", PECHO_FATAL );
866
					throw new MWAPIError( array( 'code' => 'noresponse', 'info' => 'API is unresponsive' ) );
867
				} else {
868
					pecho( "API Error: API is not responding.  Aborting attempts.\n\n", PECHO_FATAL );
869
					return false;
870
				}
871
			}
872
873
			Hooks::runHook( 'PostAPIPostQuery', array( &$data ) );
874
875
			Hooks::runHook( 'APIQueryCheckError', array( &$data['error'] ) );
876
			if( isset( $data['error'] ) && $errorcheck ) {
877
				$txtMsg = "API Error...\n\nCode: {$data['error']['code']}\nText: {$data['error']['info']}\n\n";
878
				if( $pgLogAPIError ) file_put_contents( $pgIP . 'Includes/Communication_Logs/APIError.log', $txtMsg, FILE_APPEND );
879
				pecho( $txtMsg, PECHO_FATAL );
880
				return false;
881
			}
882
883
			if( isset( $data['servedby'] ) ) {
884
				$this->servedby = $data['servedby'];
885
			}
886
887
			if( isset( $data['requestid'] ) ) {
888
				if( $data['requestid'] != $requestid ) {
889
					if( $recursed ) {
890
						pecho( "API Error... requestid's didn't match twice.\n\n", PECHO_FATAL );
891
						return false;
892
					}
893
					return $this->apiQuery( $arrayParams, $post, $errorcheck, true );
894
				}
895
			}
896
897
			return $data;
898
		} else {
899
900
			Hooks::runHook( 'PreAPIGetQuery', array( &$arrayParams ) );
901
902
			for( $i = 0; $i < $attempts; $i++ ){
903
				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...
904
					$headerArr = array(
905
		                                // OAuth information
906
		                                'oauth_consumer_key' => $this->consumerKey,
907
		                                'oauth_token' => $this->accessToken,
908
		                                'oauth_version' => '1.0',
909
		                                'oauth_nonce' => md5( microtime() . mt_rand() ),
910
		                                'oauth_timestamp' => time(),
911
912
		                                // We're using secret key signatures here.
913
		                                'oauth_signature_method' => 'HMAC-SHA1',
914
		                            );
915
		            $signature = $this->generateSignature( 'GET', ( $talktoOauth ? $talktoOauth : $this->base_url ).(!empty( $arrayParams ) ? '?' . http_build_query( $arrayParams ) : "" ), $headerArr );
916
		            $headerArr['oauth_signature'] = $signature; 
917
918
		            $header = array();
919
		            foreach ( $headerArr as $k => $v ) {
920
		                $header[] = rawurlencode( $k ) . '="' . rawurlencode( $v ) . '"';
921
		            }
922
		            $header = 'Authorization: OAuth ' . join( ', ', $header );
923
		            unset( $headerArr );
924
				}
925
				$logdata = "Date/Time: " . date( 'r' ) . "\nMethod: GET\nURL: {$this->base_url}\nParameters: " . print_r( $arrayParams, true ) . "\nRaw Data: ";
926
				$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 931 which is incompatible with the return type documented by Wiki::apiQuery of type array|boolean.
Loading history...
927
					( $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...
928
					$arrayParams,
929
					$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...
930
				);
931
				if( $talktoOauth !== false && $data !== false ) return $data;
932
				$logdata .= $data;
933
				$data2 = ( $data === false || is_null( $data ) ? false : unserialize( $data ) );
934
				if( $data2 === false && serialize( $data2 ) != $data ) {
935
					$logdata .= "\nUNSERIALIZATION FAILED\n\n";
936
					if( $pgLogFailedCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Faileddata.log', $logdata, FILE_APPEND );
937
				} else {
938
					$logdata .= "\nUNSERIALIZATION SUCCEEDED\n\n";
939
					if( $pgLogSuccessfulCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Succeededdata.log', $logdata, FILE_APPEND );
940
				}
941
942
				if( $pgLogGetCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Getdata.log', $logdata, FILE_APPEND );
943
				if( $pgLogCommunicationData ) file_put_contents( $pgIP . 'Includes/Communication_Logs/Querydata.log', $logdata, FILE_APPEND );
944
945
				$data = $data2;
946
				unset( $data2 );
947
				if( $this->get_http()->get_HTTP_code() == 503 && $errorcheck ) {
948
					pecho( "API Error...\n\nCode: error503\nText: HTTP Error 503\nThe webserver's service is currently unavailable, retrying...", PECHO_WARN );
949
				}
950
951
				if( !isset( $data['servedby'] ) && !isset( $data['requestid'] ) ) {
952
					pecho( "Warning: API is not responding, retrying...\n\n", PECHO_WARN );
953
				} else break;
954
			}
955
956
			if( $this->get_http()->get_HTTP_code() == 503 && $errorcheck ) {
957
				if( $pgThrowExceptions ) {
958
					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 );
959
					throw new MWAPIError( array(
960
						'code' => 'error503', 'info' => 'nThe webserver\'s service is currently unavailable'
961
					) );
962
				} else {
963
					pecho( "API Error...\n\nCode: error503\nText: HTTP Error 503\nThe webserver's service is still not available.  Aborting attempts.\n\n", PECHO_FATAL );
964
					return false;
965
				}
966
967
			}
968
969
			if( !isset( $data['servedby'] ) && !isset( $data['requestid'] ) ) {
970
				if( $pgThrowExceptions ) {
971
					pecho( "Fatal Error: API is not responding.  Terminating program.\n\n", PECHO_FATAL );
972
					throw new MWAPIError( array( 'code' => 'noresponse', 'info' => 'API is unresponsive' ) );
973
				} else {
974
					pecho( "API Error: API is not responding.  Aborting attempts.\n\n", PECHO_FATAL );
975
					return false;
976
				}
977
			}
978
979
			Hooks::runHook( 'APIQueryCheckError', array( &$data['error'] ) );
980
			if( isset( $data['error'] ) && $errorcheck ) {
981
982
				pecho( "API Error...\n\nCode: {$data['error']['code']}\nText: {$data['error']['info']}\n\n", PECHO_FATAL );
983
				return false;
984
			}
985
986
			if( isset( $data['servedby'] ) ) {
987
				$this->servedby = $data['servedby'];
988
			}
989
990
			if( isset( $data['requestid'] ) ) {
991
				if( $data['requestid'] != $requestid ) {
992
					if( $recursed ) {
993
						pecho( "API Error... requestid's didn't match twice.\n\n", PECHO_FATAL );
994
						return false;
995
					}
996
					return $this->apiQuery( $arrayParams, $post, $errorcheck, true );
997
				}
998
			}
999
1000
			return $data;
1001
		}
1002
	}
1003
1004
	/**
1005
	 * Returns the server that handled the previous request. Only works on MediaWiki versions 1.17 and up
1006
	 *
1007
	 * @return string
1008
	 */
1009
	public function get_servedby() {
1010
		return $this->servedby;
1011
	}
1012
1013
	/**
1014
	 * Returns the version of MediaWiki that is on the server
1015
	 *
1016
	 * @return string
1017
	 */
1018
	public function get_mw_version() {
1019
		return $this->mwversion;
1020
	}
1021
1022
	/**
1023
	 * Simplifies the running of API queries, especially with continues and other parameters.
1024
	 *
1025
	 * @access public
1026
	 * @link http://wiki.peachy.compwhizii.net/wiki/Manual/Wiki::listHandler
1027
	 * @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)
1028
	 * @param array $resume Parameter passed back at the end of a list-handler operation.  Pass parameter back through to resume listhandler operation. (optional)
1029
     * @throws BadEntryError
1030
	 * @return array Returns an array with the API result
1031
	 */
1032
	public function listHandler( $tArray = array(), &$resume = null ) {
1033
1034
		if( isset( $tArray['_code'] ) ) {
1035
			$code = $tArray['_code'];
1036
			unset( $tArray['_code'] );
1037
		} else {
1038
			throw new BadEntryError( "listHandler", "Parameter _code is required." );
1039
		}
1040
		if( isset( $tArray['_limit'] ) ) {
1041
			$limit = $tArray['_limit'];
1042
			unset( $tArray['_limit'] );
1043
		} else {
1044
			$limit = null;
1045
		}
1046
		if( isset( $tArray['_lhtitle'] ) ) {
1047
			$lhtitle = $tArray['_lhtitle'];
1048
			unset( $tArray['_lhtitle'] );
1049
		} else {
1050
			$lhtitle = null;
1051
		}
1052
        if( !is_null( $resume ) ) {
1053
            $tArray = array_merge( $tArray, $resume );
1054
        } else {
1055
            $resume = array();
1056
        }
1057
1058
		$tArray['action'] = 'query';
1059
		$tArray[$code . 'limit'] = 'max';
1060
        $tArray['rawcontinue'] = 1;
1061
1062
		if( isset( $limit ) && !is_null( $limit ) ) {
1063
			if( !is_numeric( $limit ) ) {
1064
				throw new BadEntryError( "listHandler", "limit should be a number or null" );
1065
			} else {
1066
				$limit = intval( $limit );
1067
				if( $limit < 0 || ( floor( $limit ) != $limit ) ) {
1068
					if( !$limit == -1 ) {
1069
						throw new BadEntryError( "listHandler", "limit should an integer greater than 0" );
1070
					} else $limit = 'max';
1071
				}
1072
				$tArray[$code . 'limit'] = $limit;
1073
			}
1074
		}
1075
		if( isset( $tArray[$code . 'namespace'] ) && !is_null( $tArray[$code . 'namespace'] ) ) {
1076
			if( is_array( $tArray[$code . 'namespace'] ) ) {
1077
				$tArray[$code . 'namespace'] = implode( '|', $tArray[$code . 'namespace'] );
1078
			} elseif( strlen( $tArray[$code . 'namespace'] ) === 0 ) {
1079
				$tArray[$code . 'namespace'] = null;
1080
			} else {
1081
				$tArray[$code . 'namespace'] = (string)$tArray[$code . 'namespace'];
1082
			}
1083
		}
1084
1085
		$endArray = array();
1086
1087
		$continue = null;
1088
		$offset = null;
1089
		$start = null;
1090
		$from = null;
1091
1092
		pecho( "Running list handler function with params " . implode( ";", $tArray ) . "...\n\n", PECHO_VERBOSE );
1093
1094
		while( 1 ){
1095
1096
			if( !is_null( $continue ) ) $tArray[$code . 'continue'] = $continue;
1097
			if( !is_null( $offset ) ) $tArray[$code . 'offset'] = $offset;
1098
			if( !is_null( $start ) ) $tArray[$code . 'start'] = $start;
1099
			if( !is_null( $from ) ) $tArray[$code . 'from'] = $from;
1100
1101
			$tRes = $this->apiQuery( $tArray );
1102
			if( !isset( $tRes['query'] ) ) break;
1103
1104
			foreach( $tRes['query'] as $x ){
1105
				foreach( $x as $y ){
1106
					if( !is_null( $lhtitle ) ) {
1107
						if( isset( $y[$lhtitle] ) ) {
1108
							$y = $y[$lhtitle];
1109
                            if( is_array( $y ) ) $endArray = array_merge( $endArray, $y );
1110
                            else $endArray[] = $y;
1111
						    continue;
1112
                        } else {
1113
							continue;
1114
						}
1115
					}
1116
                    $endArray[] = $y;
1117
				}
1118
			}
1119
1120
			if( isset( $tRes['query-continue'] ) ) {
1121
				foreach( $tRes['query-continue'] as $z ){
1122
					if( isset( $z[$code . 'continue'] ) ) {
1123
						$continue = $resume[$code . 'continue'] = $z[$code . 'continue'];
1124
					} elseif( isset( $z[$code . 'offset'] ) ) {
1125
						$offset = $resume[$code . 'offset'] = $z[$code . 'offset'];
1126
					} elseif( isset( $z[$code . 'start'] ) ) {
1127
						$start = $resume[$code . 'start'] = $z[$code . 'start'];
1128
					} elseif( isset( $z[$code . 'from'] ) ) {
1129
						$from = $resume[$code . 'from'] = $z[$code . 'from'];
1130
					}
1131
				}
1132
			} else {
1133
                $resume = array();
1134
				break;
1135
			}
1136
            
1137
            if( !is_null( $limit ) && $limit != 'max' ) {
1138
                if( count( $endArray ) >= $limit ) {
1139
                    $endArray = array_slice( $endArray, 0, $limit );
1140
                    break;
1141
                }
1142
            }
1143
1144
		}
1145
1146
		return $endArray;
1147
	}
1148
1149
	/**
1150
	 * Returns a reference to the HTTP Class
1151
	 *
1152
	 * @access public
1153
	 * @see Wiki::$http
1154
	 * @return HTTP
1155
	 */
1156
	public function &get_http() {
1157
		return $this->http;
1158
	}
1159
1160
	/**
1161
	 * Returns whether or not to log in
1162
	 *
1163
	 * @access public
1164
	 * @see Wiki::$nologin
1165
	 * @return bool
1166
	 */
1167
	public function get_nologin() {
1168
		return $this->nologin;
1169
	}
1170
1171
	/**
1172
	 * Returns the base URL for the wiki.
1173
	 *
1174
	 * @access public
1175
	 * @see Wiki::$base_url
1176
	 * @return string base_url for the wiki
1177
	 */
1178
	public function get_base_url() {
1179
		return $this->base_url;
1180
	}
1181
1182
	/**
1183
	 * Returns the api query limit for the wiki.
1184
	 *
1185
	 * @access public
1186
	 * @see Wiki::$apiQueryLimit
1187
	 * @return int apiQueryLimit fot the wiki
1188
	 */
1189
	public function get_api_limit() {
1190
		return $this->apiQueryLimit;
1191
	}
1192
1193
	/**
1194
	 * Returns the runpage.
1195
	 *
1196
	 * @access public
1197
	 * @see Wiki::$runpage
1198
	 * @return string Runpage for the user
1199
	 */
1200
	public function get_runpage() {
1201
		return $this->runpage;
1202
	}
1203
1204
	/**
1205
	 * Returns if maxlag is on or what it is set to for the wiki.
1206
	 *
1207
	 * @access public
1208
	 * @see Wiki:$maxlag
1209
	 * @return bool|int Max lag for the wiki
1210
	 */
1211
	public function get_maxlag() {
1212
		return $this->maxlag;
1213
	}
1214
1215
	/**
1216
	 * Returns the edit rate in EPM for the wiki.
1217
	 *
1218
	 * @access public
1219
	 * @see Wiki::$edit_rate
1220
	 * @return int Edit rate in EPM for the wiki
1221
	 */
1222
	public function get_edit_rate() {
1223
		return $this->edit_rate;
1224
	}
1225
1226
	/**
1227
	 * Returns the username.
1228
	 *
1229
	 * @access public
1230
	 * @see Wiki::$pgUsername
1231
	 * @return string Username
1232
	 */
1233
	public function get_username() {
1234
		return $this->username;
1235
	}
1236
1237
	/**
1238
	 * Returns if the Wiki should follow nobots rules.
1239
	 *
1240
	 * @access public
1241
	 * @see Wiki::$nobots
1242
	 * @return bool True for following nobots
1243
	 */
1244
	public function get_nobots() {
1245
		return $this->nobots;
1246
	}
1247
1248
	/**
1249
	 * Returns if the script should not edit if the user has new messages
1250
	 *
1251
	 * @access public
1252
	 * @see Wiki::$stoponnewmessages
1253
	 * @return bool True for stopping on new messages
1254
	 */
1255
	public function get_stoponnewmessages() {
1256
		return $this->stoponnewmessages;
1257
	}
1258
1259
	/**
1260
	 * Returns the text to search for in the optout= field of the {{nobots}} template
1261
	 *
1262
	 * @access public
1263
	 * @see Wiki::$optout
1264
	 * @return null|string String to search for
1265
	 */
1266
	public function get_optout() {
1267
		return $this->optout;
1268
	}
1269
1270
	/**
1271
	 * Returns the configuration of the wiki
1272
	 *
1273
	 * @param string $conf_name Name of configuration setting to get. Default null, will return all configuration.
1274
	 * @access public
1275
	 * @see Wiki::$configuration
1276
	 * @return array Configuration array
1277
	 */
1278
	public function get_configuration( $conf_name = null ) {
1279
		if( is_null( $conf_name ) ) {
1280
			return $this->configuration;
1281
		} else {
1282
			return $this->configuration[$conf_name];
1283
		}
1284
	}
1285
1286
	public function get_conf( $conf_name = null ) {
1287
		return $this->get_configuration( $conf_name );
1288
	}
1289
1290
	/**
1291
	 * Purges a list of pages
1292
	 *
1293
	 * @access public
1294
	 * @param array|string $titles A list of titles to work on
1295
	 * @param array|string $pageids A list of page IDs to work on
1296
	 * @param array|string $revids A list of revision IDs to work on
1297
	 * @param bool $redirects Automatically resolve redirects. Default false.
1298
	 * @param bool $force Update the links tables. Default false.
1299
	 * @param bool $convert Convert titles to other variants if necessary. Default false.
1300
	 * @param string $generator Get the list of pages to work on by executing the specified query module. Default null.
1301
	 * @return boolean
1302
	 */
1303
	public function purge( $titles = null, $pageids = null, $revids = null, $force = false, $redirects = false, $convert = false, $generator = null ) {
1304
1305
		$apiArr = array(
1306
			'action' => 'purge'
1307
		);
1308
1309
		if( is_null( $titles ) && is_null( $pageids ) && is_null( $revids ) ) {
1310
			pecho( "Error: Nothing to purge.\n\n", PECHO_WARN );
1311
			return false;
1312
		}
1313
		Hooks::runHook( 'StartPurge', array( &$titles ) );
1314
		if( !is_null( $titles ) ) {
1315
			if( is_array( $titles ) ) $titles = implode( '|', $titles );
1316
			$apiArr['titles'] = $titles;
1317
		}
1318
		if( !is_null( $pageids ) ) {
1319
			if( is_array( $pageids ) ) $pageids = implode( '|', $pageids );
1320
			$apiArr['pageids'] = $pageids;
1321
		}
1322
		if( !is_null( $revids ) ) {
1323
			if( is_array( $revids ) ) $revids = implode( '|', $revids );
1324
			$apiArr['revids'] = $revids;
1325
		}
1326
		if( $redirects ) $apiArr['redirects'] = '';
1327
		if( $force ) $apiArr['forcelinkupdate'] = '';
1328
		if( $convert ) $apiArr['converttitles'] = '';
1329
1330
		$genparams = $this->generatorvalues;
1331
		if( !is_null( $generator ) ) {
1332
			if( in_array( $generator, $genparams ) ) {
1333
				$apiArr['generator'] = 'g' . $generator;
1334
			} else pecho( "Invalid generator value detected.  Omitting...\n\n", PECHO_WARN );
1335
		}
1336
1337
		pecho( "Purging...\n\n", PECHO_NOTICE );
1338
1339
		Hooks::runHook( 'StartPurge', array( &$apiArr ) );
1340
1341
		$result = $this->apiQuery( $apiArr, true, true, false );
1342
1343
		if( isset( $result['purge'] ) ) {
1344
			foreach( $result['purge'] as $page ){
1345
				if( !isset( $page['purged'] ) ) {
1346
					pecho( "Purge error on {$page['title']}...\n\n" . print_r( $page, true ) . "\n\n", PECHO_FATAL );
1347
					return false;
1348
				}
1349
			}
1350
			return true;
1351
1352
		} else {
1353
			pecho( "Purge error...\n\n" . print_r( $result, true ), PECHO_FATAL );
1354
			return false;
1355
		}
1356
	}
1357
1358
1359
	/**
1360
	 * Returns a list of recent changes
1361
	 *
1362
	 * @access public
1363
	 * @param integer|array|string $namespace Namespace(s) to check
1364
	 * @param string $pgTag Only list recent changes bearing this tag.
1365
	 * @param int $start Only list changes after this timestamp.
1366
	 * @param int $end Only list changes before this timestamp.
1367
	 * @param string $user Only list changes by this user.
1368
	 * @param string $excludeuser Only list changes not by this user.
1369
	 * @param string $dir 'older' lists changes, most recent first; 'newer' least recent first.
1370
	 * @param bool $minor Whether to only include minor edits (true), only non-minor edits (false) or both (null). Default null.
1371
	 * @param bool $bot Whether to only include bot edits (true), only non-bot edits (false) or both (null). Default null.
1372
	 * @param bool $anon Whether to only include anonymous edits (true), only non-anonymous edits (false) or both (null). Default null.
1373
	 * @param bool $redirect Whether to only include edits to redirects (true), edits to non-redirects (false) or both (null). Default null.
1374
	 * @param bool $patrolled Whether to only include patrolled edits (true), only non-patrolled edits (false) or both (null). Default null.
1375
	 * @param array $prop What properties to retrieve. Default array( 'user', 'comment', 'flags', 'timestamp', 'title', 'ids', 'sizes', 'tags' ).
1376
	 * @param int $limit A hard limit to impose on the number of results returned.
1377
	 * @return array Recent changes matching the description.
1378
	 */
1379
	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 ) {
1380
1381
		if( is_array( $namespace ) ) {
1382
			$namespace = implode( '|', $namespace );
1383
		}
1384
1385
		if( is_null( $prop ) ) {
1386
			$prop = array(
1387
				'user', 'comment', 'flags', 'timestamp', 'title', 'ids', 'sizes', 'tags'
1388
			);
1389
		}
1390
1391
		$rcArray = array(
1392
			'list'        => 'recentchanges',
1393
			'_code'       => 'rc',
1394
			'rcnamespace' => $namespace,
1395
			'rcdir'       => $dir,
1396
			'rcprop'      => implode( '|', $prop ),
1397
			'_limit'      => $limit
1398
		);
1399
1400
		if( !is_null( $pgTag ) ) $rcArray['rctag'] = $pgTag;
1401
		if( !is_null( $start ) ) $rcArray['rcstart'] = $start;
1402
		if( !is_null( $end ) ) $rcArray['rcend'] = $end;
1403
		if( !is_null( $user ) ) $rcArray['rcuser'] = $user;
1404
		if( !is_null( $excludeuser ) ) $rcArray['rcexcludeuser'] = $excludeuser;
1405
1406
		$rcshow = array();
1407
1408
		if( !is_null( $minor ) ) {
1409
			if( $minor ) {
1410
				$rcshow[] = 'minor';
1411
			} else {
1412
				$rcshow[] = '!minor';
1413
			}
1414
		}
1415
1416
		if( !is_null( $bot ) ) {
1417
			if( $bot ) {
1418
				$rcshow[] = 'bot';
1419
			} else {
1420
				$rcshow[] = '!bot';
1421
			}
1422
		}
1423
1424
		if( !is_null( $anon ) ) {
1425
			if( $minor ) {
1426
				$rcshow[] = 'anon';
1427
			} else {
1428
				$rcshow[] = '!anon';
1429
			}
1430
		}
1431
1432
		if( !is_null( $redirect ) ) {
1433
			if( $redirect ) {
1434
				$rcshow[] = 'redirect';
1435
			} else {
1436
				$rcshow[] = '!redirect';
1437
			}
1438
		}
1439
1440
		if( !is_null( $patrolled ) ) {
1441
			if( $minor ) {
1442
				$rcshow[] = 'patrolled';
1443
			} else {
1444
				$rcshow[] = '!patrolled';
1445
			}
1446
		}
1447
1448
		if( count( $rcshow ) ) $rcArray['rcshow'] = implode( '|', $rcshow );
1449
1450
		$rcArray['limit'] = $this->apiQueryLimit;
1451
1452
		Hooks::runHook( 'PreQueryRecentchanges', array( &$rcArray ) );
1453
1454
		pecho( "Getting recent changes...\n\n", PECHO_NORMAL );
1455
1456
		return $this->listHandler( $rcArray );
1457
1458
	}
1459
1460
	/**
1461
	 * Performs a search and retrieves the results
1462
	 *
1463
	 * @access public
1464
	 * @param string $search What to search for
1465
	 * @param bool $fulltext Whether to search the full text of pages (default, true) or just titles (false; may not be enabled on all wikis).
1466
	 * @param array $namespaces The namespaces to search in (default: array( 0 )).
1467
	 * @param array $prop What properties to retrieve (default: array('size', 'wordcount', 'timestamp', 'snippet') ).
1468
	 * @param bool $includeredirects Whether to include redirects or not (default: true).
1469
	 * @param int $limit A hard limit on the number of results to retrieve (default: null i.e. all).
1470
	 * @return array
1471
	 */
1472
	public function search( $search, $fulltext = true, $namespaces = array( 0 ), $prop = array(
1473
		'size', 'wordcount', 'timestamp', 'snippet'
1474
	), $includeredirects = true, $limit = 50 ) {
1475
1476
		$srArray = array(
1477
			'_code'       => 'sr',
1478
			'list'        => 'search',
1479
			'_limit'      => $limit,
1480
			'srsearch'    => $search,
1481
			'srnamespace' => $namespaces,
1482
			'srwhat'      => ( $fulltext ) ? "text" : "title",
1483
			'srinfo'      => '',
1484
			##FIXME: find a meaningful way of passing back 'totalhits' and 'suggestion' as required.
1485
			'srprop'      => implode( '|', $prop ),
1486
			'srredirects' => $includeredirects
1487
		);
1488
1489
		pecho( "Searching for $search...\n\n", PECHO_NORMAL );
1490
1491
		return $this->listHandler( $srArray );
1492
	}
1493
1494
	/**
1495
	 * Retrieves log entries from the wiki.
1496
	 *
1497
	 * @access public
1498
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Lists#logevents_.2F_le
1499
	 * @param bool|string $type Type of log to retrieve from the wiki (default: false)
1500
	 * @param bool|string $user Restrict the log to a certain user (default: false)
1501
	 * @param bool|string $title Restrict the log to a certain page (default: false)
1502
	 * @param bool|string $start Timestamp for the start of the log (default: false)
1503
	 * @param bool|string $end Timestamp for the end of the log (default: false)
1504
	 * @param string $dir Direction for retieving log entries (default: 'older')
1505
	 * @param bool $pgTag Restrict the log to entries with a certain tag (default: false)
1506
	 * @param array $prop Information to retieve from the log (default: array( 'ids', 'title', 'type', 'user', 'timestamp', 'comment', 'details' ))
1507
	 * @param int $limit How many results to retrieve (default: null i.e. all).
1508
	 * @return array Log entries
1509
	 */
1510
	public function logs( $type = false, $user = false, $title = false, $start = false, $end = false, $dir = 'older', $pgTag = false, $prop = array(
1511
		'ids', 'title', 'type', 'user', 'userid', 'timestamp', 'comment', 'parsedcomment', 'details', 'tags'
1512
	), $limit = 50 ) {
1513
1514
		$leArray = array(
1515
			'list'   => 'logevents',
1516
			'_code'  => 'le',
1517
			'ledir'  => $dir,
1518
			'leprop' => implode( '|', $prop ),
1519
			'_limit' => $limit
1520
		);
1521
1522
		if( $type ) $leArray['letype'] = $type;
1523
		if( $start ) $leArray['lestart'] = $start;
1524
		if( $end ) $leArray['leend'] = $end;
1525
		if( $user ) $leArray['leuser'] = $user;
1526
		if( $title ) $leArray['letitle'] = $title;
1527
		if( $pgTag ) $leArray['letag'] = $pgTag;
1528
1529
		Hooks::runHook( 'PreQueryLog', array( &$leArray ) );
1530
1531
		if( $type ) {
1532
			if( $title || $user ) $title = ' for ' . $title;
1533
			pecho( "Getting $type logs{$title}{$user}...\n\n", PECHO_NORMAL );
1534
		} else {
1535
			pecho( "Getting logs...\n\n", PECHO_NORMAL );
1536
		}
1537
1538
		return $this->listHandler( $leArray );
1539
	}
1540
1541
	/**
1542
	 * Enumerate all categories
1543
	 *
1544
	 * @access public
1545
	 * @link https://www.mediawiki.org/wiki/API:Allcategories
1546
	 * @param string $prefix Search for all category titles that begin with this value. (default: null)
1547
	 * @param string $from The category to start enumerating from. (default: null)
1548
	 * @param string $min Minimum number of category members. (default: null)
1549
	 * @param string $max Maximum number of category members. (default: null)
1550
	 * @param string $dir Direction to sort in. (default: 'ascending')
1551
	 * @param array $prop Information to retieve (default: array( 'size', 'hidden' ))
1552
	 * @param int $limit How many categories to return. (default: null i.e. all).
1553
	 * @return array List of categories
1554
	 */
1555
	public function allcategories( $prefix = null, $from = null, $min = null, $max = null, $dir = 'ascending', $prop = array(
1556
		'size', 'hidden'
1557
	), $limit = 50 ) {
1558
		$leArray = array(
1559
			'list'   => 'allcategories',
1560
			'_code'  => 'ac',
1561
			'acdir'  => $dir,
1562
			'acprop' => implode( '|', $prop ),
1563
			'_limit' => $limit
1564
		);
1565
1566
		if( !is_null( $from ) ) $leArray['acfrom'] = $from;
1567
		if( !is_null( $prefix ) ) $leArray['acprefix'] = $prefix;
1568
		if( !is_null( $min ) ) $leArray['acmin'] = $min;
1569
		if( !is_null( $max ) ) $leArray['acmax'] = $max;
1570
1571
		Hooks::runHook( 'PreQueryAllimages', array( &$leArray ) );
1572
1573
		pecho( "Getting list of all categories...\n\n", PECHO_NORMAL );
1574
1575
		return $this->listHandler( $leArray );
1576
1577
	}
1578
1579
	/**
1580
	 * Enumerate all images sequentially
1581
	 *
1582
	 * @access public
1583
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Lists#allimages_.2F_le
1584
	 * @param string $prefix Search for all image titles that begin with this value. (default: null)
1585
	 * @param string $sha1 SHA1 hash of image (default: null)
1586
	 * @param string $base36 SHA1 hash of image in base 36 (default: null)
1587
	 * @param string $from The image title to start enumerating from. (default: null)
1588
	 * @param string $minsize Limit to images with at least this many bytes (default: null)
1589
	 * @param string $maxsize Limit to images with at most this many bytes (default: null)
1590
	 * @param string $dir Direction in which to list (default: 'ascending')
1591
	 * @param array $prop Information to retieve (default: array( 'timestamp', 'user', 'comment', 'url', 'size', 'dimensions', 'sha1', 'mime', 'metadata', 'archivename', 'bitdepth' ))
1592
	 * @param int $limit How many results to retrieve (default: null i.e. all).
1593
	 * @return array List of images
1594
	 */
1595
	public function allimages( $prefix = null, $sha1 = null, $base36 = null, $from = null, $minsize = null, $maxsize = null, $dir = 'ascending', $prop = array(
1596
		'timestamp', 'user', 'comment', 'url', 'size', 'dimensions', 'sha1', 'mime', 'metadata', 'archivename',
1597
		'bitdepth'
1598
	), $limit = 50 ) {
1599
		$leArray = array(
1600
			'list'   => 'allimages',
1601
			'_code'  => 'ai',
1602
			'aidir'  => $dir,
1603
			'aiprop' => implode( '|', $prop ),
1604
			'_limit' => $limit
1605
		);
1606
1607
		if( !is_null( $from ) ) $leArray['aifrom'] = $from;
1608
		if( !is_null( $prefix ) ) $leArray['aiprefix'] = $prefix;
1609
		if( !is_null( $minsize ) ) $leArray['aiminsize'] = $minsize;
1610
		if( !is_null( $maxsize ) ) $leArray['aimaxsize'] = $maxsize;
1611
		if( !is_null( $sha1 ) ) $leArray['aisha1'] = $sha1;
1612
		if( !is_null( $base36 ) ) $leArray['aisha1base36'] = $base36;
1613
1614
		Hooks::runHook( 'PreQueryAllimages', array( &$leArray ) );
1615
1616
		pecho( "Getting list of all images...\n\n", PECHO_NORMAL );
1617
1618
		return $this->listHandler( $leArray );
1619
1620
	}
1621
1622
	/**
1623
	 * Enumerate all pages sequentially
1624
	 *
1625
	 * @access public
1626
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Lists#allpages_.2F_le
1627
	 * @param array $namespace The namespace to enumerate. (default: array( 0 ))
1628
	 * @param string $prefix Search for all page titles that begin with this value. (default: null)
1629
	 * @param string $from The page title to start enumerating from. (default: null)
1630
	 * @param string $redirects Which pages to list: all, redirects, or nonredirects (default: all)
1631
	 * @param string $minsize Limit to pages with at least this many bytes (default: null)
1632
	 * @param string $maxsize Limit to pages with at most this many bytes (default: null)
1633
	 * @param array $protectiontypes Limit to protected pages. Examples: array( 'edit' ), array( 'move' ), array( 'edit', 'move' ). (default: array())
1634
	 * @param array $protectionlevels Limit to protected pages. Examples: array( 'autoconfirmed' ), array( 'sysop' ), array( 'autoconfirmed', 'sysop' ). (default: array())
1635
	 * @param string $dir Direction in which to list (default: 'ascending')
1636
	 * @param string $interwiki Filter based on whether a page has langlinks (either withlanglinks, withoutlanglinks, or all (default))
1637
	 * @param int $limit How many results to retrieve (default: null i.e. all)
1638
	 * @return array List of pages
1639
	 */
1640
	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 ) {
1641
		$leArray = array(
1642
			'list'              => 'allpages',
1643
			'_code'             => 'ap',
1644
			'apdir'             => $dir,
1645
			'apnamespace'       => $namespace,
1646
			'apfilterredir'     => $redirects,
1647
			'apfilterlanglinks' => $interwiki,
1648
			'_limit'            => $limit
1649
		);
1650
1651
		if( count( $protectiontypes ) ) {
1652
			// Trying to filter by protection status
1653
			$leArray['apprtype'] = implode( '|', $protectiontypes );
1654
			if( count( $protectionlevels ) ) $leArray['apprlevel'] = implode( '|', $protectionlevels );
1655
		} elseif( count( $protectionlevels ) ) {
1656
			pecho( 'If $protectionlevels is specified, $protectiontypes must also be specified.', PECHO_FATAL );
1657
			return false;
1658
		}
1659
1660
		if( !is_null( $from ) ) $leArray['apfrom'] = $from; //
1661
		if( !is_null( $prefix ) ) $leArray['apprefix'] = $prefix; //
1662
		if( !is_null( $minsize ) ) $leArray['apminsize'] = $minsize; //
1663
		if( !is_null( $maxsize ) ) $leArray['apmaxsize'] = $maxsize; // 
1664
1665
		Hooks::runHook( 'PreQueryAllpages', array( &$leArray ) );
1666
1667
		pecho( "Getting list of all pages...\n\n", PECHO_NORMAL );
1668
1669
		return $this->listHandler( $leArray );
1670
	}
1671
1672
	/**
1673
	 * Enumerate all internal links that point to a given namespace
1674
	 *
1675
	 * @access public
1676
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Lists#alllinks_.2F_le
1677
	 * @param array $namespace The namespace to enumerate. (default: array( 0 ))
1678
	 * @param string $prefix Search for all page titles that begin with this value. (default: null)
1679
	 * @param string $from The page title to start enumerating from. (default: null)
1680
	 * @param string $continue When more results are available, use this to continue. (default: null)
1681
	 * @param bool $unique Set to true in order to only show unique links (default: true)
1682
	 * @param array $prop What pieces of information to include: ids and/or title. (default: array( 'ids', 'title' ))
1683
	 * @param int $limit How many results to retrieve (default: null i.e. all).
1684
	 * @return array List of links
1685
	 */
1686
	public function alllinks( $namespace = array( 0 ), $prefix = null, $from = null, $continue = null, $unique = false, $prop = array(
1687
		'ids', 'title'
1688
	), $limit = 50 ) {
1689
		$leArray = array(
1690
			'list'        => 'alllinks',
1691
			'_code'       => 'al',
1692
			'alnamespace' => $namespace,
1693
			'alprop'      => implode( '|', $prop ),
1694
			'_limit'      => $limit
1695
		);
1696
1697
		if( !is_null( $from ) ) $leArray['alfrom'] = $from;
1698
		if( !is_null( $prefix ) ) $leArray['alprefix'] = $prefix;
1699
		if( !is_null( $continue ) ) $leArray['alcontinue'] = $continue;
1700
		if( $unique ) $leArray['alunique'] = '';
1701
		$leArray['limit'] = $this->apiQueryLimit;
1702
1703
		Hooks::runHook( 'PreQueryAlllinks', array( &$leArray ) );
1704
1705
		pecho( "Getting list of all internal links...\n\n", PECHO_NORMAL );
1706
1707
		return $this->listHandler( $leArray );
1708
	}
1709
1710
	/**
1711
	 * Enumerate all registered users
1712
	 *
1713
	 * @access public
1714
	 * @link http://www.mediawiki.org/wiki/API:Query_-_Lists#alllinks_.2F_le
1715
	 * @param string $prefix Search for all usernames that begin with this value. (default: null)
1716
	 * @param array $groups Limit users to a given group name (default: array())
1717
	 * @param string $from The username to start enumerating from. (default: null)
1718
	 * @param bool $editsonly Set to true in order to only show users with edits (default: false)
1719
	 * @param array $prop What pieces of information to include (default: array( 'blockinfo', 'groups', 'editcount', 'registration' ))
1720
	 * @param int $limit How many results to retrieve (default: null i.e. all).
1721
	 * @return array List of users
1722
	 */
1723
	public function allusers( $prefix = null, $groups = array(), $from = null, $editsonly = false, $prop = array(
1724
		'blockinfo', 'groups', 'editcount', 'registration'
1725
	), $limit = 50 ) {
1726
		$leArray = array(
1727
			'list'   => 'allusers',
1728
			'_code'  => 'au',
1729
			'auprop' => implode( '|', $prop ),
1730
			'_limit' => $limit
1731
		);
1732
1733
		if( !is_null( $from ) ) $leArray['aufrom'] = $from;
1734
		if( !is_null( $prefix ) ) $leArray['auprefix'] = $prefix;
1735
		if( count( $groups ) ) $leArray['augroup'] = implode( '|', $groups );
1736
		if( $editsonly ) $leArray['auwitheditsonly'] = '';
1737
1738
		Hooks::runHook( 'PreQueryAllusers', array( &$leArray ) );
1739
1740
		pecho( "Getting list of all users...\n\n", PECHO_NORMAL );
1741
1742
		return $this->listHandler( $leArray );
1743
	}
1744
1745
	public function listblocks() {
1746
		pecho( "Error: " . __METHOD__ . " has not been programmed as of yet.\n\n", PECHO_ERROR );
1747
	}
1748
1749
	/**
1750
	 * Retrieves the titles of member pages of the given category
1751
	 *
1752
	 * @access public
1753
	 * @param string $category Category to retieve
1754
	 * @param bool $subcat Should subcategories be checked (default: false)
1755
	 * @param string|array $namespace Restrict results to the given namespace (default: null i.e. all)
1756
	 * @param int $limit How many results to retrieve (default: null i.e. all)
1757
	 * @return array Array of titles
1758
	 */
1759
	public function categorymembers( $category, $subcat = false, $namespace = null, $limit = 50 ) {
1760
		$cmArray = array(
1761
			'list'    => 'categorymembers',
1762
			'_code'   => 'cm',
1763
			'cmtitle' => $category,
1764
			'cmtype'  => 'page',
1765
			'_limit'  => $limit
1766
		);
1767
1768
		if( $subcat ) $cmArray['cmtype'] = 'page|subcat';
1769
		if( $namespace !== null ) {
1770
			if( is_array( $namespace ) ) $namespace = implode( '|', $namespace );
1771
			$cmArray['cmnamespace'] = $namespace;
1772
		}
1773
1774
		Hooks::runHook( 'PreQueryCategorymembers', array( &$cmArray ) );
1775
1776
		pecho( "Getting list of pages in the $category category...\n\n", PECHO_NORMAL );
1777
1778
		return $this->listHandler( $cmArray );
1779
	}
1780
1781
	/**
1782
	 * Returns array of pages that embed (transclude) the page given.
1783
	 *
1784
	 * @see Page::embeddedin()
1785
	 * @access public
1786
	 * @param string $title The title of the page being embedded.
1787
	 * @param array $namespace Which namespaces to search (default: null).
1788
	 * @param int $limit How many results to retrieve (default: null i.e. all).
1789
	 * @return array A list of pages the title is transcluded in.
1790
	 */
1791
	public function embeddedin( $title, $namespace = null, $limit = 50 ) {
1792
		Peachy::deprecatedWarn( 'Wiki::embeddedin()', 'Page::embeddedin()' );
1793
		$page = $this->initPage( $title );
1794
		return $page->embeddedin( $namespace, $limit );
1795
	}
1796
1797
	/**
1798
	 * List change tags enabled on the wiki.
1799
	 *
1800
	 * @access public
1801
	 * @param array $prop Which properties to retrieve (default: array( 'name', 'displayname', 'description', 'hitcount' ) i.e. all).
1802
	 * @param int $limit How many results to retrieve (default: null i.e. all).
1803
	 * @return array The tags retrieved.
1804
	 */
1805
	public function tags( $prop = array( 'name', 'displayname', 'description', 'hitcount' ), $limit = 50 ) {
1806
		$tgArray = array(
1807
			'list'   => 'tags',
1808
			'_code'  => 'tg',
1809
			'tgprop' => implode( '|', $prop ),
1810
			'_limit' => $limit
1811
		);
1812
1813
		Hooks::runHook( 'PreQueryTags', array( &$tgArray ) );
1814
1815
		pecho( "Getting list of all tags...\n\n", PECHO_NORMAL );
1816
1817
		return $this->listHandler( $tgArray );
1818
	}
1819
1820
    /**
1821
     * @FIXME   Implement this method
1822
     *
1823
     * @param null $minor
1824
     * @param null $bot
1825
     * @param null $anon
1826
     * @param null $patrolled
1827
     * @param null $namespace
1828
     * @param null $user
1829
     * @param null $excludeuser
1830
     * @param null $start
1831
     * @param null $end
1832
     * @param array $prop
1833
     * @param int $limit
1834
     */
1835
    public function get_watchlist(
1836
        $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...
1837
        $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...
1838
        $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...
1839
        $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...
1840
        $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...
1841
        $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...
1842
        $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...
1843
        $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...
1844
        $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...
1845
        $prop = array(
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...
1846
		'ids', 'title', 'flags', 'user', 'comment', 'parsedcomment', 'timestamp', 'patrol', 'sizes',
1847
		'notificationtimestamp'
1848
	), $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...
1849
		pecho( "Error: " . __METHOD__ . " has not been programmed as of yet.\n\n", PECHO_ERROR );
1850
	}
1851
1852
    /**
1853
     * @FIXME   Implement this method
1854
     *
1855
     * @param null $namespace
1856
     * @param null $changed
1857
     */
1858
    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...
1859
    {
1860
		pecho( "Error: " . __METHOD__ . " has not been programmed as of yet.\n\n", PECHO_ERROR );
1861
	}
1862
1863
	/**
1864
	 * Returns details of usage of an external URL on the wiki.
1865
	 *
1866
	 * @access public
1867
	 * @param string $url The url to search for links to, without a protocol. * can be used as a wildcard.
1868
	 * @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://'.
1869
	 * @param array $prop Properties to return in array form; the options are 'ids', 'title' and 'url'. Default null (all).
1870
	 * @param string $namespace A pipe '|' separated list of namespace numbers to check. Default null (all).
1871
	 * @param int $limit A hard limit on the number of transclusions to fetch. Default null (all).
1872
	 * @return array Details about the usage of that external link on the wiki.
1873
	 */
1874
	public function exturlusage( $url, $pgProtocol = 'http', $prop = array( 'title' ), $namespace = null, $limit = 50 ) {
1875
		$tArray = array(
1876
			'list'       => 'exturlusage',
1877
			'_code'      => 'eu',
1878
			'euquery'    => $url,
1879
			'euprotocol' => $pgProtocol,
1880
			'_limit'     => $limit,
1881
			'euprop'     => implode( '|', $prop )
1882
		);
1883
1884
		if( !is_null( $namespace ) ) {
1885
			$tArray['eunamespace'] = $namespace;
1886
		}
1887
1888
		Hooks::runHook( 'PreQueryExturlusage', array( &$tArray ) );
1889
1890
		pecho( "Getting list of all pages that $url is used in...\n\n", PECHO_NORMAL );
1891
1892
		return $this->listHandler( $tArray );
1893
1894
	}
1895
1896
    /**
1897
     * @FIXME   Implement this method
1898
     *
1899
     * @param array $users
1900
     * @param array $prop
1901
     */
1902
    public function users(
1903
        $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...
1904
        $prop = array(
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...
1905
		'blockinfo', 'groups', 'editcount', 'registration', 'emailable', 'gender'
1906
	) ) {
1907
		pecho( "Error: " . __METHOD__ . " has not been programmed as of yet.\n\n", PECHO_ERROR );
1908
	}
1909
1910
	/**
1911
	 * Returns the titles of some random pages.
1912
	 *
1913
	 * @access public
1914
	 * @param array|string $namespaces Namespaces to select from (default:  array( 0 ) ).
1915
	 * @param int $limit The number of titles to return (default: 1).
1916
	 * @param bool $onlyredirects Only include redirects (true) or only include non-redirects (default; false).
1917
	 * @return array A series of random titles.
1918
	 */
1919
	public function random( $namespaces = array( 0 ), $limit = 1, $onlyredirects = false ) {
1920
		$rnArray = array(
1921
			'_code'       => 'rn',
1922
			'list'        => 'random',
1923
			'rnnamespace' => $namespaces,
1924
			'_limit'      => $limit,
1925
			'rnredirect'  => ( is_null( $onlyredirects ) || !$onlyredirects ) ? null : "true",
1926
			'_lhtitle'    => 'title'
1927
		);
1928
1929
		Hooks::runHook( 'PreQueryRandom', array( &$rnArray ) );
1930
1931
		pecho( "Getting random page...\n\n", PECHO_NORMAL );
1932
1933
		return $this->listHandler( $rnArray );
1934
	}
1935
1936
    /**
1937
     * @FIXME   Implement this method
1938
     *
1939
     * @param array $namespace
1940
     */
1941
    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...
1942
    {
1943
		pecho( "Error: " . __METHOD__ . " has not been programmed as of yet.\n\n", PECHO_ERROR );
1944
	}
1945
1946
	/**
1947
	 * Returns meta information about the wiki itself
1948
	 *
1949
	 * @access public
1950
	 * @param array $prop Information to retrieve. Default: array( 'general', 'namespaces', 'namespacealiases', 'specialpagealiases', 'magicwords', 'interwikimap', 'dbrepllag', 'statistics', 'usergroups', 'extensions', 'fileextensions', 'rightsinfo', 'languages' )
1951
	 * @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
1952
	 * @return array
1953
	 */
1954
	public function siteinfo( $prop = array(
1955
		'general', 'namespaces', 'namespacealiases', 'specialpagealiases', 'magicwords', 'interwikimap', 'dbrepllag',
1956
		'statistics', 'usergroups', 'extensions', 'fileextensions', 'rightsinfo', 'languages'
1957
	), $iwfilter = null ) {
1958
1959
		$siArray = array(
1960
			'action' => 'query',
1961
			'meta'   => 'siteinfo',
1962
			'siprop' => implode( '|', $prop ),
1963
		);
1964
1965
		if( in_array( 'interwikimap', $prop ) && $iwfilter ) {
1966
			$siArray['sifilteriw'] = '';
1967
		} elseif( in_array( 'interwikimap', $prop ) && $iwfilter ) $siArray['sifilteriw'] = 'no';
1968
1969
		if( in_array( 'dbrepllag', $prop ) ) $siArray['sishowalldb'] = '';
1970
		if( in_array( 'usergroups', $prop ) ) $siArray['sinumberingroup'] = '';
1971
1972
		Hooks::runHook( 'PreQuerySiteInfo', array( &$siArray ) );
1973
1974
		pecho( "Getting site information...\n\n", PECHO_NORMAL );
1975
1976
		return $this->apiQuery( $siArray );
1977
	}
1978
1979
	/**
1980
	 * Returns a list of system messages (MediaWiki:... pages)
1981
	 *
1982
	 * @access public
1983
	 * @param string $filter Return only messages that contain this string. Default null
1984
	 * @param array $messages Which messages to output. Default array(), which means all.
1985
	 * @param bool $parse Set to true to enable parser, will preprocess the wikitext of message. (substitutes magic words, handle templates etc.) Default false
1986
	 * @param array $args Arguments to be substituted into message. Default array().
1987
	 * @param string $lang Return messages in this language. Default null
1988
	 * @return array
1989
	 */
1990
	public function allmessages( $filter = null, $messages = array(), $parse = false, $args = array(), $lang = null ) {
1991
		$amArray = array(
1992
			'action' => 'query',
1993
			'meta'   => 'allmessages',
1994
		);
1995
1996
		if( !is_null( $filter ) ) $amArray['amfilter'] = $filter;
1997
		if( count( $messages ) ) $amArray['ammessages'] = implode( '|', $messages );
1998
		if( $parse ) $amArray['amenableparser'] = '';
1999
		if( count( $args ) ) $amArray['amargs'] = implode( '|', $args );
2000
		if( !is_null( $lang ) ) $amArray['amlang'] = $lang;
2001
2002
		Hooks::runHook( 'PreQueryAllMessages', array( &$amArray ) );
2003
2004
		pecho( "Getting list of system messages...\n\n", PECHO_NORMAL );
2005
2006
		return $this->apiQuery( $amArray );
2007
	}
2008
2009
	/**
2010
	 * Expand and parse all templates in wikitext
2011
	 *
2012
	 * @access public
2013
	 * @param string $text Text to parse
2014
	 * @param string $title Title to use for expanding magic words, etc. (e.g. {{PAGENAME}}). Default 'API'.
2015
	 * @param bool $generatexml Generate XML parse tree. Default false
2016
	 * @return string
2017
	 */
2018
	public function expandtemplates( $text, $title = null, $generatexml = false ) {
2019
		$etArray = array(
2020
			'action' => 'expandtemplates',
2021
			'text'   => $text
2022
		);
2023
2024
		if( $generatexml ) $etArray['generatexml'] = '';
2025
		if( !is_null( $title ) ) $etArray['title'] = $title;
2026
2027
		Hooks::runHook( 'PreQueryExpandtemplates', array( &$etArray ) );
2028
2029
		pecho( "Parsing templates...\n\n", PECHO_NORMAL );
2030
2031
		$ret = $this->apiQuery( $etArray );
2032
		return $ret['expandtemplates']['*'];
2033
2034
	}
2035
2036
	/**
2037
	 * Parses wikitext and returns parser output
2038
	 *
2039
	 * @access public
2040
	 * @param string $text Wikitext to parse. Default null.
2041
	 * @param string $title Title of page the text belongs to, used for {{PAGENAME}}. Default null.
2042
	 * @param string $summary Summary to parse. Default null.
2043
	 * @param bool $pst Run a pre-save transform, expanding {{subst:}} and ~~~~. Default false.
2044
	 * @param bool $onlypst Run a pre-save transform, but don't parse it. Default false.
2045
	 * @param string $uselang Language to parse in. Default 'en'.
2046
	 * @param array $prop Properties to retrieve. Default array( 'text', 'langlinks', 'categories', 'links', 'templates', 'images', 'externallinks', 'sections', 'revid', 'displaytitle', 'headitems', 'headhtml' )
2047
	 * @param string $page Parse the content of this page. Cannot be used together with $text and $title.
2048
	 * @param string $oldid Parse the content of this revision. Overrides $page and $pageid.
2049
	 * @param string $pageid Parse the content of this page. Overrides page.
2050
	 * @param bool $redirects If the page or the pageid parameter is set to a redirect, resolve it. Default true.
2051
	 * @param string $section Only retrieve the content of this section number. Default null.
2052
	 * @param bool $disablepp Disable the PP Report from the parser output. Defaut false.
2053
	 * @param bool $generatexml Generate XML parse tree (requires prop=wikitext). Default false.
2054
	 * @param string $contentformat Content serialization format used for the input text. Default null.
2055
	 * @param string $contentmodel Content model of the new content. Default null.
2056
	 * @param string $mobileformat Return parse output in a format suitable for mobile devices. Default null.
2057
	 * @param bool $noimages Disable images in mobile output
2058
	 * @param bool $mainpage Apply mobile main page transformations
2059
	 * @return array
2060
	 */
2061
	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 ) {
2062
2063
		if( $prop === null ) {
2064
			$prop = array(
2065
				'text', 'langlinks', 'categories', 'categorieshtml', 'languageshtml', 'links', 'templates', 'images',
2066
				'externallinks', 'sections', 'revid', 'displaytitle', 'headitems', 'headhtml', 'iwlinks', 'wikitext',
2067
				'properties'
2068
			);
2069
		};
2070
2071
		$apiArray = array(
2072
			'action'  => 'parse',
2073
			'uselang' => $uselang,
2074
			'prop'    => implode( '|', $prop ),
2075
		);
2076
2077
		if( $generatexml ) {
2078
			if( !in_array( 'wikitext', $prop ) ) $prop[] = 'wikitext';
2079
			$apiArray['generatexml'] = '';
2080
		}
2081
2082
		if( !is_null( $text ) ) $apiArray['text'] = $text;
2083
		if( !is_null( $title ) ) $apiArray['title'] = $title;
2084
		if( !is_null( $summary ) ) $apiArray['summary'] = $summary;
2085
		if( !is_null( $pageid ) ) $apiArray['pageid'] = $pageid;
2086
		if( !is_null( $page ) ) $apiArray['page'] = $page;
2087
		if( !is_null( $oldid ) ) $apiArray['oldid'] = $oldid;
2088
		if( !is_null( $section ) ) $apiArray['section'] = $section;
2089
		if( !is_null( $contentformat ) ) {
2090
			if( $contentformat == 'text/x-wiki' || $contentformat == 'text/javascript' || $contentformat == 'text/css' || $contentformat == 'text/plain' ) {
2091
				$apiArray['contentformat'] = $contentformat;
2092
			} else pecho( "Error: contentformat not specified correctly.  Omitting value...\n\n", PECHO_ERROR );
2093
		}
2094
		if( !is_null( $contentmodel ) ) {
2095
			if( $contentmodel == 'wikitext' || $contentmodel == 'javascript' || $contentmodel == 'css' || $contentmodel == 'text' || $contentmodel == 'Scribunto' ) {
2096
				$apiArray['contentmodel'] = $contentmodel;
2097
			} else pecho( "Error: contentmodel not specified correctly.  Omitting value...\n\n", PECHO_ERROR );
2098
		}
2099
		if( !is_null( $mobileformat ) ) {
2100
			if( $mobileformat == 'wml' || $mobileformat == 'html' ) {
2101
				$apiArray['mobileformat'] = $mobileformat;
2102
			} else pecho( "Error: mobileformat not specified correctly.  Omitting value...\n\n", PECHO_ERROR );
2103
		}
2104
2105
		if( $pst ) $apiArray['pst'] = '';
2106
		if( $onlypst ) $apiArray['onlypst'] = '';
2107
		if( $redirects ) $apiArray['redirects'] = '';
2108
		if( $disablepp ) $apiArray['disablepp'] = '';
2109
		if( $noimages ) $apiArray['noimages'] = '';
2110
		if( $mainpage ) $apiArray['mainpage'] = '';
2111
2112
		Hooks::runHook( 'PreParse', array( &$apiArray ) );
2113
2114
		pecho( "Parsing...\n\n", PECHO_NORMAL );
2115
2116
		return $this->apiQuery( $apiArray );
2117
	}
2118
2119
	/**
2120
	 * Patrols a page or revision
2121
	 *
2122
	 * @access public
2123
	 * @param int $rcid Recent changes ID to patrol
2124
	 * @return array
2125
	 */
2126
	public function patrol( $rcid = 0 ) {
2127
		Hooks::runHook( 'PrePatrol', array( &$rcid ) );
2128
2129
		pecho( "Patrolling $rcid...\n\n", PECHO_NORMAL );
2130
2131
		$this->get_tokens();
2132
2133
		return $this->apiQuery(
2134
			array(
2135
				'action' => 'patrol',
2136
				'rcid'   => $rcid,
2137
				'token'  => $this->tokens['patrol']
2138
			)
2139
		);
2140
	}
2141
2142
	/**
2143
	 * Import a page from another wiki, or an XML file.
2144
	 *
2145
	 * @access public
2146
	 * @param mixed|string $page local XML file or page to another wiki.
2147
	 * @param string $summary Import summary. Default ''.
2148
	 * @param string $site For interwiki imports: wiki to import from.  Default null.
2149
	 * @param bool $fullhistory For interwiki imports: import the full history, not just the current version.  Default true.
2150
	 * @param bool $templates For interwiki imports: import all included templates as well.  Default true.
2151
	 * @param int $namespace For interwiki imports: import to this namespace
2152
	 * @param bool $root Import as subpage of this page.  Default false.
2153
	 * @return bool
2154
	 */
2155
	public function import( $page = null, $summary = '', $site = null, $fullhistory = true, $templates = true, $namespace = null, $root = false ) {
2156
2157
		$tokens = $this->get_tokens();
2158
2159
		$apiArray = array(
2160
			'action'  => 'import',
2161
			'summary' => $summary,
2162
			'token'   => $tokens['import']
2163
		);
2164
2165
		if( $root ) $apiArray['rootpage'] = '';
2166
2167
		if( !is_null( $page ) ) {
2168
			if( !is_file( $page ) ) {
2169
				$apiArray['interwikipage'] = $page;
2170
				if( !is_null( $site ) ) {
2171
					$apiArray['interwikisource'] = $site;
2172
				} else {
2173
					pecho( "Error: You must specify a site to import from.\n\n", PECHO_FATAL );
2174
					return false;
2175
				}
2176
				if( !is_null( $namespace ) ) $apiArray['namespace'] = $namespace;
2177
				if( $fullhistory ) $apiArray['fullhistory'] = '';
2178
				if( $templates ) $apiArray['templates'] = '';
2179
				pecho( "Importing $page from $site...\n\n", PECHO_NOTICE );
2180
			} else {
2181
				$apiArray['xml'] = "@$page";
2182
				pecho( "Uploading XML...\n\n", PECHO_NOTICE );
2183
			}
2184
		} else {
2185
			pecho( "Error: You must specify an interwiki page or a local XML file to import.\n\n", PECHO_FATAL );
2186
			return false;
2187
		}
2188
2189
		$result = $this->apiQuery( $apiArray, true );
2190
2191
		if( isset( $result['import'] ) ) {
2192
			if( isset( $result['import']['page'] ) ) {
2193
				return true;
2194
			} else {
2195
				pecho( "Import error...\n\n" . print_r( $result['import'], true ) . "\n\n", PECHO_FATAL );
2196
				return false;
2197
			}
2198
		} else {
2199
			pecho( "Import error...\n\n" . print_r( $result, true ), PECHO_FATAL );
2200
			return false;
2201
		}
2202
2203
	}
2204
2205
	public function export() {
2206
		pecho( "Error: " . __METHOD__ . " has not been programmed as of yet.\n\n", PECHO_ERROR );
2207
	}
2208
2209
2210
	/**
2211
	 * Generate a diff between two or three revision IDs
2212
	 *
2213
	 * @access public
2214
	 * @param string $method Revision method. Options: unified, inline, context, threeway, raw (default: 'unified')
2215
	 * @param mixed $rev1
2216
	 * @param mixed $rev2
2217
	 * @param mixed $rev3
2218
	 * @return string|bool False on failure
2219
	 * @see Diff::load
2220
     *
2221
     * @fixme: this uses Diff::load, which has been deprecated and Plugin removed from codebase
2222
	 */
2223
	public function diff( $method = 'unified', $rev1, $rev2, $rev3 = null ) {
2224
		$r1array = array(
2225
			'action' => 'query',
2226
			'prop'   => 'revisions',
2227
			'revids' => $rev1,
2228
			'rvprop' => 'content'
2229
		);
2230
		$r2array = array(
2231
			'action' => 'query',
2232
			'prop'   => 'revisions',
2233
			'revids' => $rev2,
2234
			'rvprop' => 'content'
2235
		);
2236
		$r3array = array(
2237
			'action' => 'query',
2238
			'prop'   => 'revisions',
2239
			'revids' => $rev3,
2240
			'rvprop' => 'content'
2241
		);
2242
2243
		Hooks::runHook( 'PreDiff', array( &$r1array, &$r2array, &$r3array, &$method ) );
2244
2245
		$r1text = $r2text = $r3text = null;
2246
		if( !is_null( $rev3 ) ) {
2247
			pecho( "Getting $method diff of revisions $rev1, $rev2, and $rev3...\n\n", PECHO_NORMAL );
2248
			$r3 = $this->apiQuery( $r3array );
2249
2250
			if( isset( $r3['query']['badrevids'] ) ) {
2251
				pecho( "A bad third revision ID was passed.\n\n", PECHO_FATAL );
2252
				return false;
2253
			}
2254
2255
			foreach( $r3['query']['pages'] as $r3pages ){
2256
				$r3text = $r3pages['revisions'][0]['*'];
2257
			}
2258
		} else {
2259
			pecho( "Getting $method diff of revisions $rev1 and $rev2...\n\n", PECHO_NORMAL );
2260
		}
2261
2262
		$r1 = $this->apiQuery( $r1array );
2263
		$r2 = $this->apiQuery( $r2array );
2264
2265
		if( isset( $r1['query']['badrevids'] ) ) {
2266
			pecho( "A bad first revision ID was passed.\n\n", PECHO_FATAL );
2267
			return false;
2268
		} elseif( isset( $r2['query']['badrevids'] ) ) {
2269
			pecho( "A bad second revision ID was passed.\n\n", PECHO_FATAL );
2270
			return false;
2271
		} else {
2272
			foreach( $r1['query']['pages'] as $r1pages ){
2273
				$r1text = $r1pages['revisions'][0]['*'];
2274
			}
2275
			foreach( $r2['query']['pages'] as $r2pages ){
2276
				$r2text = $r2pages['revisions'][0]['*'];
2277
			}
2278
2279
			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...
2280
			return Diff::load( $method, $r1text, $r2text, $r3text );
2281
		}
2282
	}
2283
2284
	/**
2285
	 * Regenerate and return edit tokens
2286
	 *
2287
	 * @access public
2288
	 * @param bool $force Whether to force use of the API, not cache.
2289
	 * @return array Edit tokens
2290
	 */
2291
	public function get_tokens( $force = false ) {
2292
		Hooks::runHook( 'GetTokens', array( &$this->tokens ) );
2293
2294
		if( !$force && !empty( $this->tokens ) ) return $this->tokens;
2295
2296
		$tokens = $this->apiQuery(
2297
			array(
2298
				'action' => 'tokens',
2299
				'type'   => 'block|delete|deleteglobalaccount|edit|email|import|move|options|patrol|protect|setglobalaccountstatus|unblock|watch'
2300
			)
2301
		);
2302
2303
		foreach( $tokens['tokens'] as $y => $z ){
2304
			if( in_string( 'token', $y ) ) {
2305
				$this->tokens[str_replace( 'token', '', $y )] = $z;
2306
			}
2307
		}
2308
2309
		$token = $this->apiQuery(
2310
			array(
2311
				'action'  => 'query',
2312
				'list'    => 'users',
2313
				'ususers' => $this->username,
2314
				'ustoken' => 'userrights'
2315
			)
2316
		);
2317
2318
		if( isset( $token['query']['users'][0]['userrightstoken'] ) ) {
2319
			$this->tokens['userrights'] = $token['query']['users'][0]['userrightstoken'];
2320
		} else {
2321
			pecho( "Error retrieving userrights token...\n\n", PECHO_FATAL );
2322
			return array();
2323
		}
2324
2325
		return $this->tokens;
2326
2327
	}
2328
2329
	/**
2330
	 * Returns extensions.
2331
	 *
2332
	 * @access public
2333
	 * @see Wiki::$extensions
2334
	 * @return array Extensions in format name => version
2335
	 */
2336
	public function get_extensions() {
2337
		return $this->extensions;
2338
	}
2339
2340
	/**
2341
	 * Returns an array of the namespaces used on the current wiki.
2342
	 *
2343
	 * @access public
2344
	 * @param bool $force Whether or not to force an update of any cached values first.
2345
	 * @return array The namespaces in use in the format index => local name.
2346
	 */
2347
	public function get_namespaces( $force = false ) {
2348
		if( is_null( $this->namespaces ) || $force ) {
2349
			$tArray = array(
2350
				'meta'   => 'siteinfo',
2351
				'action' => 'query',
2352
				'siprop' => 'namespaces'
2353
			);
2354
			$tRes = $this->apiQuery( $tArray );
2355
2356
			foreach( $tRes['query']['namespaces'] as $namespace ){
2357
				$this->namespaces[$namespace['id']] = $namespace['*'];
2358
				$this->allowSubpages[$namespace['id']] = ( ( isset( $namespace['subpages'] ) ) ? true : false );
2359
			}
2360
		}
2361
		return $this->namespaces;
2362
	}
2363
2364
	/**
2365
	 * Removes the namespace from a title
2366
	 *
2367
	 * @access public
2368
	 * @param string $title Title to remove namespace from
2369
	 * @return string
2370
	 */
2371
2372
	public function removeNamespace( $title ) {
2373
		$this->get_namespaces();
2374
2375
		$exploded = explode( ':', $title, 2 );
2376
2377
		foreach( $this->namespaces as $namespace ){
2378
			if( $namespace == $exploded[0] ) {
2379
				return $exploded[1];
2380
			}
2381
		}
2382
2383
		return $title;
2384
	}
2385
2386
	/**
2387
	 * Returns an array of subpage-allowing namespaces.
2388
	 *
2389
	 * @access public
2390
	 * @param bool $force Whether or not to force an update of any cached values first.
2391
	 * @return array An array of namespaces that allow subpages.
2392
	 */
2393
	public function get_allow_subpages( $force = false ) {
2394
		if( is_null( $this->allowSubpages ) || $force ) {
2395
			$this->get_namespaces( true );
2396
		}
2397
		return $this->allowSubpages;
2398
	}
2399
2400
	/**
2401
	 * Returns a boolean equal to whether or not the namespace with index given allows subpages.
2402
	 *
2403
	 * @access public
2404
	 * @param int $namespace The namespace that might allow subpages.
2405
	 * @return bool Whether or not that namespace allows subpages.
2406
	 */
2407
	public function get_ns_allows_subpages( $namespace = 0 ) {
2408
		$this->get_allow_subpages();
2409
2410
		return (bool)$this->allowSubpages[$namespace];
2411
	}
2412
2413
	/**
2414
	 * Returns user rights.
2415
	 *
2416
	 * @access public
2417
	 * @see Wiki::$userRights
2418
	 * @return array Array of user rights
2419
	 */
2420
	public function get_userrights() {
2421
		return $this->userRights;
2422
	}
2423
2424
	/**
2425
	 * Returns all the pages which start with a certain prefix, shortcut for {@link Wiki}::{@link allpages}()
2426
	 *
2427
	 * @param string $prefix Prefix to search for
2428
	 * @param array $namespace Namespace IDs to search in. Default array( 0 )
2429
	 * @param int $limit A hard limit on the number of pages to fetch. Default null (all).
2430
	 * @return array Titles of pages that transclude this page
2431
	 */
2432
	public function prefixindex( $prefix = null, $namespace = array( 0 ), $limit = 50 ) {
2433
		return $this->allpages( $namespace, $prefix, null, 'all', null, null, array(), array(), 'ascending', 'all', $limit );
2434
	}
2435
2436
	/**
2437
	 * Returns the normalized (or redirected) title of the Page class as specified by $title or $pageid
2438
	 *
2439
	 * @access public
2440
	 * @param mixed $title Title of the page (default: null)
2441
	 * @param mixed $pageid ID of the page (default: null)
2442
	 * @return Page title resolution
2443
	 * @package initFunctions
2444
	 */
2445
	public function &resolveTitle( $title = null, $pageid = null ) {
2446
		$page = new Page( $this, $title, $pageid, true, true );
2447
                $title = $page->get_title(true, true);
2448
		return $title;
2449
	}
2450
2451
	/**
2452
	 * Returns an instance of the Page class as specified by $title or $pageid
2453
	 *
2454
	 * @access public
2455
	 * @param mixed $title Title of the page (default: null)
2456
	 * @param mixed $pageid ID of the page (default: null)
2457
	 * @param bool $followRedir Should it follow a redirect when retrieving the page (default: true)
2458
	 * @param bool $normalize Should the class automatically normalize the title (default: true)
2459
	 * @param string $timestamp Timestamp of a reference point in the program.  Used to detect edit conflicts.
2460
	 * @return Page
2461
	 * @package initFunctions
2462
	 */
2463
	public function &initPage( $title = null, $pageid = null, $followRedir = true, $normalize = true, $timestamp = null ) {
2464
		$page = new Page( $this, $title, $pageid, $followRedir, $normalize, $timestamp );
2465
		return $page;
2466
	}
2467
2468
	/**
2469
	 * Returns an instance of the User class as specified by $pgUsername
2470
	 *
2471
	 * @access public
2472
	 * @param mixed $pgUsername Username
2473
	 * @return User
2474
	 * @package initFunctions
2475
	 */
2476
	public function &initUser( $pgUsername ) {
2477
		$user = new User( $this, $pgUsername );
2478
		return $user;
2479
	}
2480
2481
	/**
2482
	 * Returns an instance of the Image class as specified by $filename or $pageid
2483
	 *
2484
	 * @access public
2485
	 * @param string $filename Filename
2486
	 * @param int $pageid Page ID of image
2487
	 * @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...
2488
	 * @return Image
2489
	 * @package initFunctions
2490
	 */
2491
	public function &initImage( $filename = null, $pageid = null ) {
2492
		$image = new Image( $this, $filename, $pageid );
2493
		return $image;
2494
	}
2495
2496
	/**
2497
	 * Get the difference between 2 pages
2498
	 *
2499
	 * @access public
2500
	 * @param string $fromtitle First title to compare
2501
	 * @param string $fromid First page ID to compare
2502
	 * @param string $fromrev First revision to compare
2503
	 * @param string $totitle Second title to compare
2504
	 * @param string $toid Second page ID to compare
2505
	 * @param string $torev Second revision to compare
2506
	 * @return array
2507
	 */
2508
	public function compare( $fromtitle = null, $fromid = null, $fromrev = null, $totitle = null, $toid = null, $torev = null ) {
2509
2510
		pecho( "Getting differences...\n\n", PECHO_NORMAL );
2511
		$apiArray = array(
2512
			'action' => 'compare'
2513
		);
2514
		if( !is_null( $fromrev ) ) {
2515
			$apiArray['fromrev'] = $fromrev;
2516
		} else {
2517
			if( !is_null( $fromid ) ) {
2518
				$apiArray['fromid'] = $fromid;
2519
			} else {
2520
				if( !is_null( $fromtitle ) ) {
2521
					$apiArray['fromtitle'] = $fromtitle;
2522
				} else {
2523
					pecho( "Error: a from parameter must be specified.\n\n", PECHO_FATAL );
2524
					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...
2525
				}
2526
			}
2527
		}
2528
		if( !is_null( $torev ) ) {
2529
			$apiArray['torev'] = $torev;
2530
		} else {
2531
			if( !is_null( $toid ) ) {
2532
				$apiArray['toid'] = $toid;
2533
			} else {
2534
				if( !is_null( $totitle ) ) {
2535
					$apiArray['totitle'] = $totitle;
2536
				} else {
2537
					pecho( "Error: a to parameter must be specified.\n\n", PECHO_FATAL );
2538
					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...
2539
				}
2540
			}
2541
		}
2542
		$results = $this->apiQuery( $apiArray );
2543
2544
		if( isset( $results['compare']['*'] ) ) {
2545
			return $results['compare']['*'];
2546
		} else {
2547
			pecho( "Compare failure... Please check your parameters.\n\n", PECHO_FATAL );
2548
			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...
2549
		}
2550
	}
2551
2552
	/**
2553
	 * Search the wiki using the OpenSearch protocol.
2554
	 *
2555
	 * @access public
2556
	 * @param string $text Search string.  Default empty.
2557
	 * @param int $limit Maximum amount of results to return. Default 10.
2558
	 * @param array $namespaces Namespaces to search.  Default array(0).
2559
	 * @param bool $suggest Do nothing if $wgEnableOpenSearchSuggest is false. Default true.
2560
	 * @return array
2561
	 */
2562
	public function opensearch( $text = '', $limit = 10, $namespaces = array( 0 ), $suggest = true ) {
2563
2564
		$apiArray = array(
2565
			'search'    => $text,
2566
			'action'    => 'opensearch',
2567
			'limit'     => $limit,
2568
			'namespace' => implode( '|', $namespaces )
2569
		);
2570
		if( $suggest ) $apiArray['suggest'] = '';
2571
2572
		$OSres = $this->get_http()->get( $this->get_base_url(), $apiArray );
2573
		return ( $OSres === false || is_null( $OSres ) ? false : json_decode( $OSres, true ) );
2574
2575
	}
2576
2577
	/**
2578
	 * Export an RSD (Really Simple Discovery) schema.
2579
	 *
2580
	 * @access public
2581
	 * @return array
2582
	 */
2583
	public function rsd() {
2584
2585
		$apiArray = array(
2586
			'action' => 'rsd'
2587
		);
2588
2589
		$OSres = $this->get_http()->get( $this->get_base_url(), $apiArray );
2590
		return ( $OSres === false || is_null( $OSres ) ? false : XMLParse::load( $OSres ) );
2591
	}
2592
2593
	/**
2594
	 * Change preferences of the current user.
2595
	 *
2596
	 * @access public
2597
	 * @param bool $reset Resets preferences to the site defaults. Default false.
2598
	 * @param array|string $resetoptions List of types of options to reset when the "reset" option is set. Default 'all'.
2599
	 * @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.
2600
	 * @param string $optionname A name of a option which should have an optionvalue set. Default null.
2601
	 * @param string $optionvalue A value of the option specified by the optionname, can contain pipe characters. Default null.
2602
	 * @return boolean
2603
	 */
2604
	public function options( $reset = false, $resetoptions = array( 'all' ), $changeoptions = array(), $optionname = null, $optionvalue = null ) {
2605
		$this->get_tokens();
2606
		$apiArray = array(
2607
			'action' => 'options',
2608
			'token'  => $this->tokens['options']
2609
		);
2610
2611
		if( $reset ) {
2612
			$apiArray['reset'] = '';
2613
			$apiArray['resetkinds'] = implode( '|', $resetoptions );
2614
		}
2615
2616
		if( !empty( $changeoptions ) ) $apiArray['change'] = implode( '|', $changeoptions );
2617
2618
		if( !is_null( $optionname ) ) $apiArray['optionname'] = $optionname;
2619
		if( !is_null( $optionvalue ) ) $apiArray['optionvalue'] = $optionvalue;
2620
2621
		$result = $this->apiQuery( $apiArray, true );
2622
		if( isset( $result['options'] ) && $result['options'] == 'success' ) {
2623
			if( isset( $result['warnings'] ) ) pecho( "Options set successfully, however a warning was thrown:\n" . print_r( $result['warnings'], true ), PECHO_WARN );
2624
			return true;
2625
		} else {
2626
			pecho( "Options error...\n\n" . print_r( $result, true ), PECHO_FATAL );
2627
			return false;
2628
		}
2629
	}
2630
2631
	/**
2632
	 * Returns the SSH object
2633
	 *
2634
	 * @access public
2635
	 * @return Object
2636
	 */
2637
	public function getSSH() {
2638
		return $this->SSH;
2639
	}
2640
2641
    /**
2642
     * Performs nobots checking, new message checking, etc
2643
     *
2644
     * @param       string $action Name of action.
2645
     * @param       null|string $title Name of page to check for nobots
2646
     * @param       null $pageidp
2647
     * @throws      AssertFailure
2648
     * @throws      EditError
2649
     * @throws      LoggedOut
2650
     * @throws      MWAPIError
2651
     * @access      public
2652
     */
2653
	public function preEditChecks( $action = "Edit", $title = null, $pageidp = null ) {
2654
		global $pgDisablechecks, $pgMasterrunpage;
2655
		if( $pgDisablechecks ) return;
2656
		$preeditinfo = array(
2657
			'action' => 'query',
2658
			'meta'   => 'userinfo',
2659
			'uiprop' => 'hasmsg|blockinfo',
2660
			'prop'   => 'revisions',
2661
			'rvprop' => 'content'
2662
		);
2663
2664
		if( !is_null( $this->get_runpage() ) ) {
2665
			$preeditinfo['titles'] = $this->get_runpage();
2666
		}
2667
		if( !is_null( $title ) ) {
2668
			$preeditinfo['titles'] = ( !is_null( $this->get_runpage() ) ? $preeditinfo['titles'] . "|" : "" ) . $title;
2669
		}
2670
2671
		$preeditinfo = $this->apiQuery( $preeditinfo );
2672
2673
		$messages = false;
2674
		$blocked = false;
2675
		$oldtext = '';
2676
		$runtext = 'enable';
2677
		if( isset( $preeditinfo['query']['pages'] ) ) {
2678
			//$oldtext = $preeditinfo['query']['pages'][$this->pageid]['revisions'][0]['*'];
2679
			foreach( $preeditinfo['query']['pages'] as $pageid => $page ){
2680
				if( $pageid == $pageidp ) {
2681
					$oldtext = $page['revisions'][0]['*'];
2682
				} elseif( $pageid == "-1" ) {
2683
					if( $page['title'] == $this->get_runpage() ) {
2684
						pecho( "$action failed, enable page does not exist.\n\n", PECHO_WARN );
2685
						throw new EditError( "Enablepage", "Enable  page does not exist." );
2686
					} else {
2687
						$oldtext = '';
2688
					}
2689
				} else {
2690
					$runtext = $page['revisions'][0]['*'];
2691
				}
2692
			}
2693
			if( isset( $preeditinfo['query']['userinfo']['messages'] ) ) $messages = true;
2694
			if( isset( $preeditinfo['query']['userinfo']['blockedby'] ) ) $blocked = true;
2695
		}
2696
2697
		//Perform nobots checks, login checks, /Run checks
2698
		if( checkExclusion( $this, $oldtext, $this->get_username(), $this->get_optout(), $this->nobotsTaskname ) && $this->get_nobots() ) {
2699
			throw new EditError( "Nobots", "The page has a nobots template" );
2700
		}
2701
2702
		if( !is_null( $pgMasterrunpage ) && !preg_match( '/enable|yes|run|go|true/i', $this->initPage( $pgMasterrunpage )->get_text() ) ) {
2703
			throw new EditError( "Enablepage", "Script was disabled by Master Run page" );
2704
		}
2705
2706
		if( !is_null( $this->get_runpage() ) && !preg_match( '/enable|yes|run|go|true/i', $runtext ) ) {
2707
			throw new EditError( "Enablepage", "Script was disabled by Run page" );
2708
		}
2709
2710
		if( $messages && $this->get_stoponnewmessages() ) {
2711
			throw new EditError( "NewMessages", "User has new messages" );
2712
		}
2713
2714
		if( $blocked ) {
2715
			throw new EditError( "Blocked", "User has been blocked" );
2716
		}
2717
	}
2718
}
2719