Completed
Push — master ( a65bfc...74ce99 )
by Maximilian
04:53
created

Wiki::generateSignature()   D

Complexity

Conditions 13
Paths 128

Size

Total Lines 38
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 182
Metric Value
dl 0
loc 38
rs 4.8178
ccs 0
cts 29
cp 0
cc 13
eloc 25
nc 128
nop 3
crap 182

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

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